authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-10-09 23:09:20-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-10-09 23:09:20-07:00
loga7223f3969428170cc13bd7859aa59098b2f94b1
tree4f8ceeb3626f25899c0ee918e037d6a6237ccfcb
parent9cc57680cb5f762f553acd1fa5f3efcf17d8fc67
signature Signed by PGP key B5690EEEBB952194

fix(clients): restart continuations show as a T3 Code notice, not another agent's message (#17723)

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,11 +1719,15 @@ function renderFeedEntry(
1719 className="mb-5 items-end"1719 className="mb-5 items-end"
1720 {...(enterAnimated ? { entering: FadeInUp.duration(220) } : {})}1720 {...(enterAnimated ? { entering: FadeInUp.duration(220) } : {})}
1721 >1721 >
1722 {presentation.isAutomation ? (1722 {presentation.attribution === "automation" ? (
1723 <Text className="mb-1 pr-1 font-t3-medium text-2xs text-foreground-muted opacity-60">1723 <Text className="mb-1 pr-1 font-t3-medium text-2xs text-foreground-muted opacity-60">
1724 Sent by automation1724 Sent by automation
1725 </Text>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 <AgentMessageAttribution1731 <AgentMessageAttribution
1728 environmentId={props.environmentId}1732 environmentId={props.environmentId}
1729 senderThreadId={message.senderThreadId}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,6 +85,24 @@ it("shows only the structured path in expanded mobile read details", () => {
85 expect(withoutPath?.canExpand).toBe(false);85 expect(withoutPath?.canExpand).toBe(false);
86});86});
8787
88it("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
88it("labels file searches with the adapter title and its search target", () => {106it("labels file searches with the adapter title and its search target", () => {
89 const item: OrchestrationV2TurnItem = {107 const item: OrchestrationV2TurnItem = {
90 ...base("file-search", "2026-06-20T00:00:03.000Z", 2),108 ...base("file-search", "2026-06-20T00:00:03.000Z", 2),
...@@ -317,7 +335,7 @@ describe("buildThreadFeed", () => {...@@ -317,7 +335,7 @@ describe("buildThreadFeed", () => {
317 expect(messageEntry).toBeDefined();335 expect(messageEntry).toBeDefined();
318 expect(resolveUserMessagePresentation(messageEntry!.message)).toMatchObject({336 expect(resolveUserMessagePresentation(messageEntry!.message)).toMatchObject({
319 text: "Run checks",337 text: "Run checks",
320 isAutomation: true,338 attribution: "automation",
321 });339 });
322 });340 });
323341
apps/mobile/src/lib/threadActivity.ts+6-1
...@@ -521,6 +521,7 @@ function itemIcon(item: OrchestrationV2TurnItem): ThreadFeedActivity["icon"] {...@@ -521,6 +521,7 @@ function itemIcon(item: OrchestrationV2TurnItem): ThreadFeedActivity["icon"] {
521 case "monitor":521 case "monitor":
522 return "eye";522 return "eye";
523 case "background_task":523 case "background_task":
524 case "system":
524 return "zap";525 return "zap";
525 default:526 default:
526 source satisfies never;527 source satisfies never;
...@@ -814,7 +815,11 @@ function toFeedActivity(...@@ -814,7 +815,11 @@ function toFeedActivity(
814 ? collectToolFilePaths(item)815 ? collectToolFilePaths(item)
815 : null;816 : null;
816 const getFullDetail = memoizeValue(() =>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 const getCopyText = memoizeValue(() =>824 const getCopyText = memoizeValue(() =>
820 [summary, detail, getFullDetail()]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,11 +4674,17 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio
4674 dispatchMode = { type: "start_immediately" };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 if (4679 if (
4678 command.notification !== undefined &&4680 command.notification !== undefined &&
4679 (command.createdBy !== "agent" ||4681 (command.createdBy !== "agent" ||
4680 (command.creationSource !== "server" && command.creationSource !== "provider") ||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 return yield* new OrchestratorDispatchError({4689 return yield* new OrchestratorDispatchError({
4684 commandId: command.commandId,4690 commandId: command.commandId,
apps/server/src/orchestration-v2/RestartContinuation.test.ts+22-4
...@@ -10,6 +10,7 @@ import {...@@ -10,6 +10,7 @@ import {
10 RunAttemptId,10 RunAttemptId,
11 RunId,11 RunId,
12 ThreadId,12 ThreadId,
13 type OrchestrationV2Command,
13 type OrchestrationV2ThreadProjection,14 type OrchestrationV2ThreadProjection,
14} from "@t3tools/contracts";15} from "@t3tools/contracts";
15import * as DateTime from "effect/DateTime";16import * as DateTime from "effect/DateTime";
...@@ -606,9 +607,9 @@ it.effect("does not cancel or resume a run that completes while shutdown intent...@@ -606,9 +607,9 @@ it.effect("does not cancel or resume a run that completes while shutdown intent
606 }),607 }),
607);608);
608609
609const continuationTexts = (projection: OrchestrationV2ThreadProjection) =>610const continuationDispatches = (projection: OrchestrationV2ThreadProjection) =>
610 Effect.gen(function* () {611 Effect.gen(function* () {
611 const texts: Array<string> = [];612 const dispatches: Array<Extract<OrchestrationV2Command, { type: "message.dispatch" }>> = [];
612 yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe(613 yield* continueRestartedRun({ threadId, sourceRunId: runId }).pipe(
613 Effect.provide(614 Effect.provide(
614 Layer.merge(615 Layer.merge(
...@@ -616,7 +617,7 @@ const continuationTexts = (projection: OrchestrationV2ThreadProjection) =>...@@ -616,7 +617,7 @@ const continuationTexts = (projection: OrchestrationV2ThreadProjection) =>
616 getThreadRecords: () => Effect.succeed(projection),617 getThreadRecords: () => Effect.succeed(projection),
617 recoverDelegatedTask: () => Effect.void,618 recoverDelegatedTask: () => Effect.void,
618 dispatch: (command) => {619 dispatch: (command) => {
619 if (command.type === "message.dispatch") texts.push(command.text);620 if (command.type === "message.dispatch") dispatches.push(command);
620 return Effect.succeed({} as never);621 return Effect.succeed({} as never);
621 },622 },
622 }),623 }),
...@@ -624,9 +625,14 @@ const continuationTexts = (projection: OrchestrationV2ThreadProjection) =>...@@ -624,9 +625,14 @@ const continuationTexts = (projection: OrchestrationV2ThreadProjection) =>
624 ),625 ),
625 ),626 ),
626 );627 );
627 return texts;628 return dispatches;
628 });629 });
629630
631const continuationTexts = (projection: OrchestrationV2ThreadProjection) =>
632 continuationDispatches(projection).pipe(
633 Effect.map((dispatches) => dispatches.map((command) => command.text)),
634 );
635
630const cutMidTurn = (extra: Record<string, unknown> = {}) => {636const cutMidTurn = (extra: Record<string, unknown> = {}) => {
631 const base = makeProjection();637 const base = makeProjection();
632 return {638 return {
...@@ -740,6 +746,18 @@ it.effect("tells a turn cut mid-way about the background work it lost", () =>...@@ -740,6 +746,18 @@ it.effect("tells a turn cut mid-way about the background work it lost", () =>
740 }),746 }),
741);747);
742748
749it.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
743it.effect(761it.effect(
744 "carries the note forward when its continuation was cut before reaching the provider",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,17 +153,27 @@ export const continueRestartedRun = Effect.fn("RestartContinuation.continueResta
153 );153 );
154 const noteText =154 const noteText =
155 note.work.length === 0 ? undefined : restartCancelledBackgroundWorkNote(note.work);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 yield* threads.dispatch({162 yield* threads.dispatch({
157 type: "message.dispatch",163 type: "message.dispatch",
158 commandId: CommandId.make(`command:restart-continuation:${input.sourceRunId}`),164 commandId: CommandId.make(`command:restart-continuation:${input.sourceRunId}`),
159 threadId: input.threadId,165 threadId: input.threadId,
160 messageId,166 messageId,
161 text:167 text,
162 noteText === undefined168 // Shown as a work log row; the prompt the agent got is its detail.
163 ? CONTINUE_PROMPT169 notification: {
164 : note.settled170 source: { kind: "system" },
165 ? noteText171 outcome: "updated",
166 : `${noteText}\n\n${CONTINUE_PROMPT}`,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 attachments: [],177 attachments: [],
168 modelSelection: source.modelSelection,178 modelSelection: source.modelSelection,
169 dispatchMode: { type: "start_immediately" },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,6 +3211,12 @@ it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => {
3211 threadId,3211 threadId,
3212 messageId: MessageId.make("restart-automatic-message"),3212 messageId: MessageId.make("restart-automatic-message"),
3213 text: "Continue where you left off.",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 attachments: [],3220 attachments: [],
3215 modelSelection,3221 modelSelection,
3216 dispatchMode: { type: "start_immediately" as const },3222 dispatchMode: { type: "start_immediately" as const },
...@@ -3221,6 +3227,17 @@ it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => {...@@ -3221,6 +3227,17 @@ it.layer(layerTest)("OrchestrationV2LayerLive lifecycle", (it) => {
3221 const admitted = yield* orchestrator.getThreadProjection(threadId);3227 const admitted = yield* orchestrator.getThreadProjection(threadId);
3222 assert.lengthOf(admitted.runs, 2);3228 assert.lengthOf(admitted.runs, 2);
3223 assert.equal(admitted.runs[1]?.restartContinuationOfRunId, original.id);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 // A differently identified stale delivery still must not create another run.3241 // A differently identified stale delivery still must not create another run.
3225 yield* orchestrator.dispatch({3242 yield* orchestrator.dispatch({
3226 ...command,3243 ...command,
apps/web/src/components/chat/MessagesTimeline.tsx+11-2
...@@ -2357,7 +2357,7 @@ function UserTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "message"...@@ -2357,7 +2357,7 @@ function UserTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "message"
23572357
2358 return (2358 return (
2359 <div className="group flex flex-col items-end gap-1">2359 <div className="group flex flex-col items-end gap-1">
2360 {userMessage.isAutomation ? (2360 {userMessage.attribution === "automation" ? (
2361 <p2361 <p
2362 className="me-1 text-2xs text-muted-foreground/70"2362 className="me-1 text-2xs text-muted-foreground/70"
2363 data-user-message-attribution="automation"2363 data-user-message-attribution="automation"
...@@ -2377,7 +2377,14 @@ function UserTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "message"...@@ -2377,7 +2377,14 @@ function UserTimelineRow({ row }: { row: Extract<TimelineRow, { kind: "message"
2377 "Sent by automation"2377 "Sent by automation"
2378 )}2378 )}
2379 </p>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 <p className="me-1 text-2xs text-muted-foreground/70" data-user-message-attribution="agent">2388 <p className="me-1 text-2xs text-muted-foreground/70" data-user-message-attribution="agent">
2382 {senderThreadId ? (2389 {senderThreadId ? (
2383 <InlineButton2390 <InlineButton
...@@ -5276,6 +5283,8 @@ function workEntryIconName(workEntry: TimelineWorkEntry): WorkEntryIconName {...@@ -5276,6 +5283,8 @@ function workEntryIconName(workEntry: TimelineWorkEntry): WorkEntryIconName {
5276 return "eye";5283 return "eye";
5277 case "background_task":5284 case "background_task":
5278 return "zap";5285 return "zap";
5286 case "system":
5287 return "t3-code";
5279 default:5288 default:
5280 source satisfies never;5289 source satisfies never;
5281 return "zap";5290 return "zap";
packages/client-runtime/src/state/threadExecution.ts+1
...@@ -444,6 +444,7 @@ export function notificationChildThreadId(...@@ -444,6 +444,7 @@ export function notificationChildThreadId(
444 case "command":444 case "command":
445 case "monitor":445 case "monitor":
446 case "background_task":446 case "background_task":
447 case "system":
447 return undefined;448 return undefined;
448 default:449 default:
449 source satisfies never;450 source satisfies never;
packages/client-runtime/src/userMessage.test.ts+26-5
...@@ -9,7 +9,11 @@ describe("resolveUserMessagePresentation", () => {...@@ -9,7 +9,11 @@ describe("resolveUserMessagePresentation", () => {
9 it("removes attribution from legacy scheduled prompts", () => {9 it("removes attribution from legacy scheduled prompts", () => {
10 expect(10 expect(
11 resolveUserMessagePresentation({ role: "user", createdBy: "agent", text: legacyText }),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 });
1418
15 it("uses task metadata without changing the prompt", () => {19 it("uses task metadata without changing the prompt", () => {
...@@ -20,7 +24,7 @@ describe("resolveUserMessagePresentation", () => {...@@ -20,7 +24,7 @@ describe("resolveUserMessagePresentation", () => {
20 scheduledTaskId: ScheduledTaskId.make("task-1"),24 scheduledTaskId: ScheduledTaskId.make("task-1"),
21 text: legacyText,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 });
2529
26 it("preserves user-written and assistant-quoted schedule headers", () => {30 it("preserves user-written and assistant-quoted schedule headers", () => {
...@@ -31,7 +35,7 @@ describe("resolveUserMessagePresentation", () => {...@@ -31,7 +35,7 @@ describe("resolveUserMessagePresentation", () => {
31 ]) {35 ]) {
32 expect(resolveUserMessagePresentation({ ...message, text: legacyText })).toEqual({36 expect(resolveUserMessagePresentation({ ...message, text: legacyText })).toEqual({
33 text: legacyText,37 text: legacyText,
34 isAutomation: false,38 attribution: null,
35 scheduledTaskId: undefined,39 scheduledTaskId: undefined,
36 });40 });
37 }41 }
...@@ -45,12 +49,29 @@ describe("resolveUserMessagePresentation", () => {...@@ -45,12 +49,29 @@ describe("resolveUserMessagePresentation", () => {
45 ]) {49 ]) {
46 expect(resolveUserMessagePresentation({ role: "user", createdBy: "agent", text })).toEqual({50 expect(resolveUserMessagePresentation({ role: "user", createdBy: "agent", text })).toEqual({
47 text,51 text,
48 isAutomation: false,52 attribution: "agent",
49 scheduledTaskId: undefined,53 scheduledTaskId: undefined,
50 });54 });
51 }55 }
52 });56 });
5357
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 it("recovers the triggering automation from older scheduler message ids", () => {75 it("recovers the triggering automation from older scheduler message ids", () => {
55 for (const trigger of ["scheduled", "manual"]) {76 for (const trigger of ["scheduled", "manual"]) {
56 expect(77 expect(
...@@ -62,7 +83,7 @@ describe("resolveUserMessagePresentation", () => {...@@ -62,7 +83,7 @@ describe("resolveUserMessagePresentation", () => {
62 }),83 }),
63 ).toEqual({84 ).toEqual({
64 text: "Check for crashes.\n",85 text: "Check for crashes.\n",
65 isAutomation: true,86 attribution: "automation",
66 scheduledTaskId: "task:daily-audit",87 scheduledTaskId: "task:daily-audit",
67 });88 });
68 }89 }
packages/client-runtime/src/userMessage.ts+38-10
...@@ -1,31 +1,59 @@...@@ -1,31 +1,59 @@
1import { type OrchestrationV2Actor, ScheduledTaskId } from "@t3tools/contracts";1import {
2 type OrchestrationV2Actor,
3 type OrchestrationV2CreationSource,
4 ScheduledTaskId,
5} from "@t3tools/contracts";
26
3const LEGACY_AUTOMATION_PREFIX = /^\[Triggered by schedule task: [^\r\n]+\]\r?\n\r?\n/;7const LEGACY_AUTOMATION_PREFIX = /^\[Triggered by schedule task: [^\r\n]+\]\r?\n\r?\n/;
4const LEGACY_AUTOMATION_MESSAGE_ID = /^scheduled-task-message:(.+):\d+:(?:scheduled|manual)$/;8const LEGACY_AUTOMATION_MESSAGE_ID = /^scheduled-task-message:(.+):\d+:(?:scheduled|manual)$/;
59
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 */
7export function resolveUserMessagePresentation(message: {14export function resolveUserMessagePresentation(message: {
8 readonly id?: string;15 readonly id?: string;
9 readonly role: string;16 readonly role: string;
10 readonly text: string;17 readonly text: string;
11 readonly createdBy?: OrchestrationV2Actor;18 readonly createdBy?: OrchestrationV2Actor;
19 readonly creationSource?: OrchestrationV2CreationSource;
12 readonly scheduledTaskId?: ScheduledTaskId;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 if (message.role !== "user") {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 if (message.scheduledTaskId !== undefined) {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 const legacyPrefix = LEGACY_AUTOMATION_PREFIX.exec(message.text);37 const legacyPrefix = LEGACY_AUTOMATION_PREFIX.exec(message.text);
21 const legacyTaskId = legacyPrefix38 const legacyTaskId = legacyPrefix
22 ? LEGACY_AUTOMATION_MESSAGE_ID.exec(message.id ?? "")?.[1]39 ? LEGACY_AUTOMATION_MESSAGE_ID.exec(message.id ?? "")?.[1]
23 : undefined;40 : undefined;
24 const isAutomation =41 if (legacyPrefix !== null && (legacyTaskId !== undefined || message.createdBy === "agent")) {
25 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 return {48 return {
27 text: isAutomation ? message.text.slice(legacyPrefix[0].length) : message.text,49 text: message.text,
28 isAutomation,50 // Restart continuations were sent as the agent before they became notices.
29 scheduledTaskId: legacyTaskId === undefined ? undefined : ScheduledTaskId.make(legacyTaskId),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,6 +1097,8 @@ export const OrchestrationV2NotificationSource = kindUnionWithFallback(
1097 CommandNotificationSource,1097 CommandNotificationSource,
1098 Schema.Struct({ kind: Schema.Literal("monitor") }),1098 Schema.Struct({ kind: Schema.Literal("monitor") }),
1099 Schema.Struct({ kind: Schema.Literal("background_task") }),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 (kind) => Schema.Struct({ kind }),1103 (kind) => Schema.Struct({ kind }),
1102 () => ({ kind: "background_task" }),1104 () => ({ kind: "background_task" }),