| author | |
| committer | |
| log | a7223f3969428170cc13bd7859aa59098b2f94b1 |
| tree | 4f8ceeb3626f25899c0ee918e037d6a6237ccfcb |
| parent | 9cc57680cb5f762f553acd1fa5f3efcf17d8fc67 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>12 files changed, 171 insertions(+), 32 deletions(-)
apps/mobile/src/features/threads/ThreadFeed.tsx+6-2| ... | ... | @@ -1719,11 +1719,15 @@ function renderFeedEntry( |
| 1719 | 1719 | className="mb-5 items-end" |
| 1720 | 1720 | {...(enterAnimated ? { entering: FadeInUp.duration(220) } : {})} |
| 1721 | 1721 | > |
| 1722 | {presentation.isAutomation ? ( | |
| 1722 | {presentation.attribution === "automation" ? ( | |
| 1723 | 1723 | <Text className="mb-1 pr-1 font-t3-medium text-2xs text-foreground-muted opacity-60"> |
| 1724 | 1724 | Sent by automation |
| 1725 | 1725 | </Text> |
| 1726 | ) : message.createdBy === "agent" ? ( | |
| 1726 | ) : presentation.attribution === "t3code" ? ( | |
| 1727 | <Text className="mb-1 pr-1 font-t3-medium text-2xs text-foreground-muted opacity-60"> | |
| 1728 | Sent by T3 Code | |
| 1729 | </Text> | |
| 1730 | ) : presentation.attribution === "agent" ? ( | |
| 1727 | 1731 | <AgentMessageAttribution |
| 1728 | 1732 | environmentId={props.environmentId} |
| 1729 | 1733 | senderThreadId={message.senderThreadId} |
apps/mobile/src/lib/threadActivity.test.ts+19-1| ... | ... | @@ -85,6 +85,24 @@ it("shows only the structured path in expanded mobile read details", () => { |
| 85 | 85 | expect(withoutPath?.canExpand).toBe(false); |
| 86 | 86 | }); |
| 87 | 87 | |
| 88 | it("expands a T3 Code notice to the prompt the agent got", () => { | |
| 89 | const item = { | |
| 90 | ...base("restart-notice", "2026-06-20T00:00:03.000Z", 2), | |
| 91 | type: "notification", | |
| 92 | source: { kind: "system" }, | |
| 93 | outcome: "updated", | |
| 94 | summary: "T3 Code restarted and resumed this turn", | |
| 95 | detail: "Continue where you left off.", | |
| 96 | } as OrchestrationV2TurnItem; | |
| 97 | const [activity] = buildThreadFeed([projected(item, 0)]).flatMap((entry) => | |
| 98 | entry.type === "activity-group" ? entry.activities : [], | |
| 99 | ); | |
| 100 | ||
| 101 | expect(activity?.summary).toBe("T3 Code restarted and resumed this turn"); | |
| 102 | expect(activity?.canExpand).toBe(true); | |
| 103 | expect(activity?.getFullDetail()).toBe("Continue where you left off."); | |
| 104 | }); | |
| 105 | ||
| 88 | 106 | it("labels file searches with the adapter title and its search target", () => { |
| 89 | 107 | const item: OrchestrationV2TurnItem = { |
| 90 | 108 | ...base("file-search", "2026-06-20T00:00:03.000Z", 2), |
| ... | ... | @@ -317,7 +335,7 @@ describe("buildThreadFeed", () => { |
| 317 | 335 | expect(messageEntry).toBeDefined(); |
| 318 | 336 | expect(resolveUserMessagePresentation(messageEntry!.message)).toMatchObject({ |
| 319 | 337 | text: "Run checks", |
| 320 | isAutomation: true, | |
| 338 | attribution: "automation", | |
| 321 | 339 | }); |
| 322 | 340 | }); |
| 323 | 341 |
apps/mobile/src/lib/threadActivity.ts+6-1| ... | ... | @@ -521,6 +521,7 @@ function itemIcon(item: OrchestrationV2TurnItem): ThreadFeedActivity["icon"] { |
| 521 | 521 | case "monitor": |
| 522 | 522 | return "eye"; |
| 523 | 523 | case "background_task": |
| 524 | case "system": | |
| 524 | 525 | return "zap"; |
| 525 | 526 | default: |
| 526 | 527 | source satisfies never; |
| ... | ... | @@ -814,7 +815,11 @@ function toFeedActivity( |
| 814 | 815 | ? collectToolFilePaths(item) |
| 815 | 816 | : null; |
| 816 | 817 | const getFullDetail = memoizeValue(() => |
| 817 | readPaths ? readPaths.join("\n") || null : formatItemFullDetail(row, item), | |
| 818 | readPaths | |
| 819 | ? readPaths.join("\n") || null | |
| 820 | : item.type === "notification" | |
| 821 | ? item.detail?.trim() || null | |
| 822 | : formatItemFullDetail(row, item), | |
| 818 | 823 | ); |
| 819 | 824 | const getCopyText = memoizeValue(() => |
| 820 | 825 | [summary, detail, getFullDetail()] |
apps/server/src/orchestration-v2/Orchestrator.ts+7-1| ... | ... | @@ -4674,11 +4674,17 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio |
| 4674 | 4674 | dispatchMode = { type: "start_immediately" }; |
| 4675 | 4675 | } |
| 4676 | 4676 | } |
| 4677 | // A restart continuation resumes the interrupted turn, so it starts | |
| 4678 | // immediately rather than queueing like other wakes. | |
| 4677 | 4679 | if ( |
| 4678 | 4680 | command.notification !== undefined && |
| 4679 | 4681 | (command.createdBy !== "agent" || |
| 4680 | 4682 | (command.creationSource !== "server" && command.creationSource !== "provider") || |
| 4681 | dispatchMode.type !== "queue_after_active") | |
| 4683 | (dispatchMode.type !== "queue_after_active" && | |
| 4684 | !( | |
| 4685 | command.restartContinuationOfRunId !== undefined && | |
| 4686 | dispatchMode.type === "start_immediately" | |
| 4687 | ))) | |
| 4682 | 4688 | ) { |
| 4683 | 4689 | return yield* new OrchestratorDispatchError({ |
| 4684 | 4690 | commandId: command.commandId, |
apps/server/src/orchestration-v2/RestartContinuation.test.ts+22-4| ... | ... | @@ -10,6 +10,7 @@ import { |
| 10 | 10 | RunAttemptId, |
| 11 | 11 | RunId, |
| 12 | 12 | ThreadId, |
| 13 | type OrchestrationV2Command, | |
| 13 | 14 | type OrchestrationV2ThreadProjection, |
| 14 | 15 | } from "@t3tools/contracts"; |
| 15 | 16 | import * as DateTime from "effect/DateTime"; |
| ... | ... | @@ -606,9 +607,9 @@ it.effect("does not cancel or resume a run that completes while shutdown intent |
| 606 | 607 | }), |
| 607 | 608 | ); |
| 608 | 609 | |
| 609 | const continuationTexts = (projection: OrchestrationV2ThreadProjection) => | |
| 610 | const continuationDispatches = (projection: OrchestrationV2ThreadProjection) => | |
| 610 | 611 | Effect.gen(function* () { |
| 611 | const texts: Array<string> = []; | |
| 612 | const dispatches: Array<Extract<OrchestrationV2Command, { type: "message.dispatch" }>> = []; | |
| 612 | 613 | yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe( |
| 613 | 614 | Effect.provide( |
| 614 | 615 | Layer.merge( |
| ... | ... | @@ -616,7 +617,7 @@ const continuationTexts = (projection: OrchestrationV2ThreadProjection) => |
| 616 | 617 | getThreadRecords: () => Effect.succeed(projection), |
| 617 | 618 | recoverDelegatedTask: () => Effect.void, |
| 618 | 619 | dispatch: (command) => { |
| 619 | if (command.type === "message.dispatch") texts.push(command.text); | |
| 620 | if (command.type === "message.dispatch") dispatches.push(command); | |
| 620 | 621 | return Effect.succeed({} as never); |
| 621 | 622 | }, |
| 622 | 623 | }), |
| ... | ... | @@ -624,9 +625,14 @@ const continuationTexts = (projection: OrchestrationV2ThreadProjection) => |
| 624 | 625 | ), |
| 625 | 626 | ), |
| 626 | 627 | ); |
| 627 | return texts; | |
| 628 | return dispatches; | |
| 628 | 629 | }); |
| 629 | 630 | |
| 631 | const continuationTexts = (projection: OrchestrationV2ThreadProjection) => | |
| 632 | continuationDispatches(projection).pipe( | |
| 633 | Effect.map((dispatches) => dispatches.map((command) => command.text)), | |
| 634 | ); | |
| 635 | ||
| 630 | 636 | const cutMidTurn = (extra: Record<string, unknown> = {}) => { |
| 631 | 637 | const base = makeProjection(); |
| 632 | 638 | return { |
| ... | ... | @@ -740,6 +746,18 @@ it.effect("tells a turn cut mid-way about the background work it lost", () => |
| 740 | 746 | }), |
| 741 | 747 | ); |
| 742 | 748 | |
| 749 | it.effect("shows a continuation as a T3 Code notice whose detail is the prompt", () => | |
| 750 | Effect.gen(function* () { | |
| 751 | const [resumed] = yield* continuationDispatches(cutMidTurn()); | |
| 752 | assert.deepEqual(resumed?.notification, { | |
| 753 | source: { kind: "system" }, | |
| 754 | outcome: "updated", | |
| 755 | summary: "T3 Code restarted and resumed this turn", | |
| 756 | detail: "Continue where you left off.", | |
| 757 | }); | |
| 758 | }), | |
| 759 | ); | |
| 760 | ||
| 743 | 761 | it.effect( |
| 744 | 762 | "carries the note forward when its continuation was cut before reaching the provider", |
| 745 | 763 | () => |
apps/server/src/orchestration-v2/RestartContinuation.ts+16-6| ... | ... | @@ -153,17 +153,27 @@ export const continueRestartedRun = Effect.fn("RestartContinuation.continueResta |
| 153 | 153 | ); |
| 154 | 154 | const noteText = |
| 155 | 155 | note.work.length === 0 ? undefined : restartCancelledBackgroundWorkNote(note.work); |
| 156 | const text = | |
| 157 | noteText === undefined | |
| 158 | ? CONTINUE_PROMPT | |
| 159 | : note.settled | |
| 160 | ? noteText | |
| 161 | : `${noteText}\n\n${CONTINUE_PROMPT}`; | |
| 156 | 162 | yield* threads.dispatch({ |
| 157 | 163 | type: "message.dispatch", |
| 158 | 164 | commandId: CommandId.make(`command:restart-continuation:${input.sourceRunId}`), |
| 159 | 165 | threadId: input.threadId, |
| 160 | 166 | messageId, |
| 161 | text: | |
| 162 | noteText === undefined | |
| 163 | ? CONTINUE_PROMPT | |
| 164 | : note.settled | |
| 165 | ? noteText | |
| 166 | : `${noteText}\n\n${CONTINUE_PROMPT}`, | |
| 167 | text, | |
| 168 | // Shown as a work log row; the prompt the agent got is its detail. | |
| 169 | notification: { | |
| 170 | source: { kind: "system" }, | |
| 171 | outcome: "updated", | |
| 172 | summary: note.settled | |
| 173 | ? "T3 Code restarted and stopped background work" | |
| 174 | : "T3 Code restarted and resumed this turn", | |
| 175 | detail: text, | |
| 176 | }, | |
| 167 | 177 | attachments: [], |
| 168 | 178 | modelSelection: source.modelSelection, |
| 169 | 179 | dispatchMode: { type: "start_immediately" }, |
apps/server/src/orchestration-v2/runtimeLayer.test.ts+17| ... | ... | @@ -3211,6 +3211,12 @@ it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => { |
| 3211 | 3211 | threadId, |
| 3212 | 3212 | messageId: MessageId.make("restart-automatic-message"), |
| 3213 | 3213 | text: "Continue where you left off.", |
| 3214 | notification: { | |
| 3215 | source: { kind: "system" as const }, | |
| 3216 | outcome: "updated" as const, | |
| 3217 | summary: "T3 Code restarted and resumed this turn", | |
| 3218 | detail: "Continue where you left off.", | |
| 3219 | }, | |
| 3214 | 3220 | attachments: [], |
| 3215 | 3221 | modelSelection, |
| 3216 | 3222 | dispatchMode: { type: "start_immediately" as const }, |
| ... | ... | @@ -3221,6 +3227,17 @@ it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => { |
| 3221 | 3227 | const admitted = yield* orchestrator.getThreadProjection(threadId); |
| 3222 | 3228 | assert.lengthOf(admitted.runs, 2); |
| 3223 | 3229 | assert.equal(admitted.runs[1]?.restartContinuationOfRunId, original.id); |
| 3230 | // The timeline shows a work log row; the prompt stays in its detail. | |
| 3231 | const continuationItems = admitted.turnItems.filter( | |
| 3232 | (item) => item.runId === admitted.runs[1]?.id, | |
| 3233 | ); | |
| 3234 | assert.isFalse(continuationItems.some((item) => item.type === "user_message")); | |
| 3235 | assert.deepInclude( | |
| 3236 | continuationItems.flatMap((item) => | |
| 3237 | item.type === "notification" ? [[item.summary, item.detail]] : [], | |
| 3238 | ), | |
| 3239 | ["T3 Code restarted and resumed this turn", "Continue where you left off."], | |
| 3240 | ); | |
| 3224 | 3241 | // A differently identified stale delivery still must not create another run. |
| 3225 | 3242 | yield* orchestrator.dispatch({ |
| 3226 | 3243 | ...command, |
apps/web/src/components/chat/MessagesTimeline.tsx+11-2| ... | ... | @@ -2357,7 +2357,7 @@ function UserTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "message" |
| 2357 | 2357 | |
| 2358 | 2358 | return ( |
| 2359 | 2359 | <div className="group flex flex-col items-end gap-1"> |
| 2360 | {userMessage.isAutomation ? ( | |
| 2360 | {userMessage.attribution === "automation" ? ( | |
| 2361 | 2361 | <p |
| 2362 | 2362 | className="me-1 text-2xs text-muted-foreground/70" |
| 2363 | 2363 | data-user-message-attribution="automation" |
| ... | ... | @@ -2377,7 +2377,14 @@ function UserTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "message" |
| 2377 | 2377 | "Sent by automation" |
| 2378 | 2378 | )} |
| 2379 | 2379 | </p> |
| 2380 | ) : row.message.createdBy === "agent" ? ( | |
| 2380 | ) : userMessage.attribution === "t3code" ? ( | |
| 2381 | <p | |
| 2382 | className="me-1 text-2xs text-muted-foreground/70" | |
| 2383 | data-user-message-attribution="t3code" | |
| 2384 | > | |
| 2385 | Sent by T3 Code | |
| 2386 | </p> | |
| 2387 | ) : userMessage.attribution === "agent" ? ( | |
| 2381 | 2388 | <p className="me-1 text-2xs text-muted-foreground/70" data-user-message-attribution="agent"> |
| 2382 | 2389 | {senderThreadId ? ( |
| 2383 | 2390 | <InlineButton |
| ... | ... | @@ -5276,6 +5283,8 @@ function workEntryIconName(workEntry: TimelineWorkEntry): WorkEntryIconName { |
| 5276 | 5283 | return "eye"; |
| 5277 | 5284 | case "background_task": |
| 5278 | 5285 | return "zap"; |
| 5286 | case "system": | |
| 5287 | return "t3-code"; | |
| 5279 | 5288 | default: |
| 5280 | 5289 | source satisfies never; |
| 5281 | 5290 | return "zap"; |
packages/client-runtime/src/state/threadExecution.ts+1| ... | ... | @@ -444,6 +444,7 @@ export function notificationChildThreadId( |
| 444 | 444 | case "command": |
| 445 | 445 | case "monitor": |
| 446 | 446 | case "background_task": |
| 447 | case "system": | |
| 447 | 448 | return undefined; |
| 448 | 449 | default: |
| 449 | 450 | source satisfies never; |
packages/client-runtime/src/userMessage.test.ts+26-5| ... | ... | @@ -9,7 +9,11 @@ describe("resolveUserMessagePresentation", () => { |
| 9 | 9 | it("removes attribution from legacy scheduled prompts", () => { |
| 10 | 10 | expect( |
| 11 | 11 | resolveUserMessagePresentation({ role: "user", createdBy: "agent", text: legacyText }), |
| 12 | ).toEqual({ text: "Check for crashes.\n", isAutomation: true, scheduledTaskId: undefined }); | |
| 12 | ).toEqual({ | |
| 13 | text: "Check for crashes.\n", | |
| 14 | attribution: "automation", | |
| 15 | scheduledTaskId: undefined, | |
| 16 | }); | |
| 13 | 17 | }); |
| 14 | 18 | |
| 15 | 19 | it("uses task metadata without changing the prompt", () => { |
| ... | ... | @@ -20,7 +24,7 @@ describe("resolveUserMessagePresentation", () => { |
| 20 | 24 | scheduledTaskId: ScheduledTaskId.make("task-1"), |
| 21 | 25 | text: legacyText, |
| 22 | 26 | }), |
| 23 | ).toEqual({ text: legacyText, isAutomation: true, scheduledTaskId: "task-1" }); | |
| 27 | ).toEqual({ text: legacyText, attribution: "automation", scheduledTaskId: "task-1" }); | |
| 24 | 28 | }); |
| 25 | 29 | |
| 26 | 30 | it("preserves user-written and assistant-quoted schedule headers", () => { |
| ... | ... | @@ -31,7 +35,7 @@ describe("resolveUserMessagePresentation", () => { |
| 31 | 35 | ]) { |
| 32 | 36 | expect(resolveUserMessagePresentation({ ...message, text: legacyText })).toEqual({ |
| 33 | 37 | text: legacyText, |
| 34 | isAutomation: false, | |
| 38 | attribution: null, | |
| 35 | 39 | scheduledTaskId: undefined, |
| 36 | 40 | }); |
| 37 | 41 | } |
| ... | ... | @@ -45,12 +49,29 @@ describe("resolveUserMessagePresentation", () => { |
| 45 | 49 | ]) { |
| 46 | 50 | expect(resolveUserMessagePresentation({ role: "user", createdBy: "agent", text })).toEqual({ |
| 47 | 51 | text, |
| 48 | isAutomation: false, | |
| 52 | attribution: "agent", | |
| 49 | 53 | scheduledTaskId: undefined, |
| 50 | 54 | }); |
| 51 | 55 | } |
| 52 | 56 | }); |
| 53 | 57 | |
| 58 | it("attributes older server-sent restart continuations to T3 Code, not another agent", () => { | |
| 59 | const text = "Note: the T3 server restarted."; | |
| 60 | expect( | |
| 61 | resolveUserMessagePresentation({ | |
| 62 | role: "user", | |
| 63 | createdBy: "agent", | |
| 64 | creationSource: "server", | |
| 65 | text, | |
| 66 | }), | |
| 67 | ).toMatchObject({ attribution: "t3code" }); | |
| 68 | for (const creationSource of ["mcp", "provider"] as const) { | |
| 69 | expect( | |
| 70 | resolveUserMessagePresentation({ role: "user", createdBy: "agent", creationSource, text }), | |
| 71 | ).toMatchObject({ attribution: "agent" }); | |
| 72 | } | |
| 73 | }); | |
| 74 | ||
| 54 | 75 | it("recovers the triggering automation from older scheduler message ids", () => { |
| 55 | 76 | for (const trigger of ["scheduled", "manual"]) { |
| 56 | 77 | expect( |
| ... | ... | @@ -62,7 +83,7 @@ describe("resolveUserMessagePresentation", () => { |
| 62 | 83 | }), |
| 63 | 84 | ).toEqual({ |
| 64 | 85 | text: "Check for crashes.\n", |
| 65 | isAutomation: true, | |
| 86 | attribution: "automation", | |
| 66 | 87 | scheduledTaskId: "task:daily-audit", |
| 67 | 88 | }); |
| 68 | 89 | } |
packages/client-runtime/src/userMessage.ts+38-10| ... | ... | @@ -1,31 +1,59 @@ |
| 1 | import { type OrchestrationV2Actor, ScheduledTaskId } from "@t3tools/contracts"; | |
| 1 | import { | |
| 2 | type OrchestrationV2Actor, | |
| 3 | type OrchestrationV2CreationSource, | |
| 4 | ScheduledTaskId, | |
| 5 | } from "@t3tools/contracts"; | |
| 2 | 6 | |
| 3 | 7 | const LEGACY_AUTOMATION_PREFIX = /^\[Triggered by schedule task: [^\r\n]+\]\r?\n\r?\n/; |
| 4 | 8 | const LEGACY_AUTOMATION_MESSAGE_ID = /^scheduled-task-message:(.+):\d+:(?:scheduled|manual)$/; |
| 5 | 9 | |
| 6 | /** Older scheduled messages stored their attribution in the prompt itself. */ | |
| 10 | /** | |
| 11 | * Text and sender label for a user-role message. Older scheduled messages | |
| 12 | * stored their attribution in the prompt itself. | |
| 13 | */ | |
| 7 | 14 | export function resolveUserMessagePresentation(message: { |
| 8 | 15 | readonly id?: string; |
| 9 | 16 | readonly role: string; |
| 10 | 17 | readonly text: string; |
| 11 | 18 | readonly createdBy?: OrchestrationV2Actor; |
| 19 | readonly creationSource?: OrchestrationV2CreationSource; | |
| 12 | 20 | readonly scheduledTaskId?: ScheduledTaskId; |
| 13 | }) { | |
| 21 | }): { | |
| 22 | readonly text: string; | |
| 23 | /** Who sent a user-role message the user did not type. */ | |
| 24 | readonly attribution: "automation" | "agent" | "t3code" | null; | |
| 25 | readonly scheduledTaskId: ScheduledTaskId | undefined; | |
| 26 | } { | |
| 14 | 27 | if (message.role !== "user") { |
| 15 | return { text: message.text, isAutomation: false, scheduledTaskId: undefined }; | |
| 28 | return { text: message.text, attribution: null, scheduledTaskId: undefined }; | |
| 16 | 29 | } |
| 17 | 30 | if (message.scheduledTaskId !== undefined) { |
| 18 | return { text: message.text, isAutomation: true, scheduledTaskId: message.scheduledTaskId }; | |
| 31 | return { | |
| 32 | text: message.text, | |
| 33 | attribution: "automation", | |
| 34 | scheduledTaskId: message.scheduledTaskId, | |
| 35 | }; | |
| 19 | 36 | } |
| 20 | 37 | const legacyPrefix = LEGACY_AUTOMATION_PREFIX.exec(message.text); |
| 21 | 38 | const legacyTaskId = legacyPrefix |
| 22 | 39 | ? LEGACY_AUTOMATION_MESSAGE_ID.exec(message.id ?? "")?.[1] |
| 23 | 40 | : undefined; |
| 24 | const isAutomation = | |
| 25 | legacyPrefix !== null && (legacyTaskId !== undefined || message.createdBy === "agent"); | |
| 41 | if (legacyPrefix !== null && (legacyTaskId !== undefined || message.createdBy === "agent")) { | |
| 42 | return { | |
| 43 | text: message.text.slice(legacyPrefix[0].length), | |
| 44 | attribution: "automation", | |
| 45 | scheduledTaskId: legacyTaskId === undefined ? undefined : ScheduledTaskId.make(legacyTaskId), | |
| 46 | }; | |
| 47 | } | |
| 26 | 48 | return { |
| 27 | text: isAutomation ? message.text.slice(legacyPrefix[0].length) : message.text, | |
| 28 | isAutomation, | |
| 29 | scheduledTaskId: legacyTaskId === undefined ? undefined : ScheduledTaskId.make(legacyTaskId), | |
| 49 | text: message.text, | |
| 50 | // Restart continuations were sent as the agent before they became notices. | |
| 51 | attribution: | |
| 52 | message.createdBy !== "agent" | |
| 53 | ? null | |
| 54 | : message.creationSource === "server" | |
| 55 | ? "t3code" | |
| 56 | : "agent", | |
| 57 | scheduledTaskId: undefined, | |
| 30 | 58 | }; |
| 31 | 59 | } |
packages/contracts/src/orchestrationV2.ts+2| ... | ... | @@ -1097,6 +1097,8 @@ export const OrchestrationV2NotificationSource = kindUnionWithFallback( |
| 1097 | 1097 | CommandNotificationSource, |
| 1098 | 1098 | Schema.Struct({ kind: Schema.Literal("monitor") }), |
| 1099 | 1099 | Schema.Struct({ kind: Schema.Literal("background_task") }), |
| 1100 | /** T3 Code itself, such as a restart continuing an interrupted turn. */ | |
| 1101 | Schema.Struct({ kind: Schema.Literal("system") }), | |
| 1100 | 1102 | ], |
| 1101 | 1103 | (kind) => Schema.Struct({ kind }), |
| 1102 | 1104 | () => ({ kind: "background_task" }), |