- {userMessage.isAutomation ? (
+ {userMessage.attribution === "automation" ? (
- ) : row.message.createdBy === "agent" ? (
+ ) : userMessage.attribution === "t3code" ? (
+
+ Sent by T3 Code
+
+ ) : userMessage.attribution === "agent" ? (
{senderThreadId ? (
{
it("removes attribution from legacy scheduled prompts", () => {
expect(
resolveUserMessagePresentation({ role: "user", createdBy: "agent", text: legacyText }),
- ).toEqual({ text: "Check for crashes.\n", isAutomation: true, scheduledTaskId: undefined });
+ ).toEqual({
+ text: "Check for crashes.\n",
+ attribution: "automation",
+ scheduledTaskId: undefined,
+ });
});
it("uses task metadata without changing the prompt", () => {
@@ -20,7 +24,7 @@ describe("resolveUserMessagePresentation", () => {
scheduledTaskId: ScheduledTaskId.make("task-1"),
text: legacyText,
}),
- ).toEqual({ text: legacyText, isAutomation: true, scheduledTaskId: "task-1" });
+ ).toEqual({ text: legacyText, attribution: "automation", scheduledTaskId: "task-1" });
});
it("preserves user-written and assistant-quoted schedule headers", () => {
@@ -31,7 +35,7 @@ describe("resolveUserMessagePresentation", () => {
]) {
expect(resolveUserMessagePresentation({ ...message, text: legacyText })).toEqual({
text: legacyText,
- isAutomation: false,
+ attribution: null,
scheduledTaskId: undefined,
});
}
@@ -45,12 +49,29 @@ describe("resolveUserMessagePresentation", () => {
]) {
expect(resolveUserMessagePresentation({ role: "user", createdBy: "agent", text })).toEqual({
text,
- isAutomation: false,
+ attribution: "agent",
scheduledTaskId: undefined,
});
}
});
+ it("attributes older server-sent restart continuations to T3 Code, not another agent", () => {
+ const text = "Note: the T3 server restarted.";
+ expect(
+ resolveUserMessagePresentation({
+ role: "user",
+ createdBy: "agent",
+ creationSource: "server",
+ text,
+ }),
+ ).toMatchObject({ attribution: "t3code" });
+ for (const creationSource of ["mcp", "provider"] as const) {
+ expect(
+ resolveUserMessagePresentation({ role: "user", createdBy: "agent", creationSource, text }),
+ ).toMatchObject({ attribution: "agent" });
+ }
+ });
+
it("recovers the triggering automation from older scheduler message ids", () => {
for (const trigger of ["scheduled", "manual"]) {
expect(
@@ -62,7 +83,7 @@ describe("resolveUserMessagePresentation", () => {
}),
).toEqual({
text: "Check for crashes.\n",
- isAutomation: true,
+ attribution: "automation",
scheduledTaskId: "task:daily-audit",
});
}
diff --git a/packages/client-runtime/src/userMessage.ts b/packages/client-runtime/src/userMessage.ts
index 5acad0a4480276406e461abc8cfd6cd3fb6bc41b..b292bafe0ca4f62ab5ff57838671b73a5ea517d0 100644
--- a/packages/client-runtime/src/userMessage.ts
+++ b/packages/client-runtime/src/userMessage.ts
@@ -1,31 +1,59 @@
-import { type OrchestrationV2Actor, ScheduledTaskId } from "@t3tools/contracts";
+import {
+ type OrchestrationV2Actor,
+ type OrchestrationV2CreationSource,
+ ScheduledTaskId,
+} from "@t3tools/contracts";
const LEGACY_AUTOMATION_PREFIX = /^\[Triggered by schedule task: [^\r\n]+\]\r?\n\r?\n/;
const LEGACY_AUTOMATION_MESSAGE_ID = /^scheduled-task-message:(.+):\d+:(?:scheduled|manual)$/;
-/** Older scheduled messages stored their attribution in the prompt itself. */
+/**
+ * Text and sender label for a user-role message. Older scheduled messages
+ * stored their attribution in the prompt itself.
+ */
export function resolveUserMessagePresentation(message: {
readonly id?: string;
readonly role: string;
readonly text: string;
readonly createdBy?: OrchestrationV2Actor;
+ readonly creationSource?: OrchestrationV2CreationSource;
readonly scheduledTaskId?: ScheduledTaskId;
-}) {
+}): {
+ readonly text: string;
+ /** Who sent a user-role message the user did not type. */
+ readonly attribution: "automation" | "agent" | "t3code" | null;
+ readonly scheduledTaskId: ScheduledTaskId | undefined;
+} {
if (message.role !== "user") {
- return { text: message.text, isAutomation: false, scheduledTaskId: undefined };
+ return { text: message.text, attribution: null, scheduledTaskId: undefined };
}
if (message.scheduledTaskId !== undefined) {
- return { text: message.text, isAutomation: true, scheduledTaskId: message.scheduledTaskId };
+ return {
+ text: message.text,
+ attribution: "automation",
+ scheduledTaskId: message.scheduledTaskId,
+ };
}
const legacyPrefix = LEGACY_AUTOMATION_PREFIX.exec(message.text);
const legacyTaskId = legacyPrefix
? LEGACY_AUTOMATION_MESSAGE_ID.exec(message.id ?? "")?.[1]
: undefined;
- const isAutomation =
- legacyPrefix !== null && (legacyTaskId !== undefined || message.createdBy === "agent");
+ if (legacyPrefix !== null && (legacyTaskId !== undefined || message.createdBy === "agent")) {
+ return {
+ text: message.text.slice(legacyPrefix[0].length),
+ attribution: "automation",
+ scheduledTaskId: legacyTaskId === undefined ? undefined : ScheduledTaskId.make(legacyTaskId),
+ };
+ }
return {
- text: isAutomation ? message.text.slice(legacyPrefix[0].length) : message.text,
- isAutomation,
- scheduledTaskId: legacyTaskId === undefined ? undefined : ScheduledTaskId.make(legacyTaskId),
+ text: message.text,
+ // Restart continuations were sent as the agent before they became notices.
+ attribution:
+ message.createdBy !== "agent"
+ ? null
+ : message.creationSource === "server"
+ ? "t3code"
+ : "agent",
+ scheduledTaskId: undefined,
};
}
diff --git a/packages/contracts/src/orchestrationV2.ts b/packages/contracts/src/orchestrationV2.ts
index 423f8234178b902a4515d00bfb5ea7927f0b2b74..7aa8c74fd80a5e69c67d5503cf9496dd427fe18a 100644
--- a/packages/contracts/src/orchestrationV2.ts
+++ b/packages/contracts/src/orchestrationV2.ts
@@ -1097,6 +1097,8 @@ export const OrchestrationV2NotificationSource = kindUnionWithFallback(
CommandNotificationSource,
Schema.Struct({ kind: Schema.Literal("monitor") }),
Schema.Struct({ kind: Schema.Literal("background_task") }),
+ /** T3 Code itself, such as a restart continuing an interrupted turn. */
+ Schema.Struct({ kind: Schema.Literal("system") }),
],
(kind) => Schema.Struct({ kind }),
() => ({ kind: "background_task" }),