Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 40 additions & 2 deletions apps/server/src/provider/prime/PrimeAgentDaemonAdapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6661,6 +6661,39 @@ describe("PrimeAgentDaemonAdapter", () => {
},
});
}
yield* offer(captures, {
_tag: "ChildUpdated",
child: {
id: "child-private-progress",
parentId: "parent",
activeSessionId: "native-private-100",
model: "child-model",
label: "private-safe-label",
status: "running",
tokenCount: 10,
toolUseCount: 1,
activity: { kind: "executing", toolName: "bash" },
progressNote: "Checking the failing test",
lastActivityAt: 1_000,
},
});
yield* awaitObservedType(subscription.observed, "task.progress");
yield* offer(captures, {
_tag: "ChildUpdated",
child: {
id: "child-private-progress",
parentId: "parent",
activeSessionId: "native-private-100",
model: "child-model",
label: "private-safe-label",
status: "running",
tokenCount: 10,
toolUseCount: 1,
activity: { kind: "executing", toolName: "bash" },
progressNote: "Checking the failing test",
lastActivityAt: 2_000,
},
});
captures.agentRoster = [
{
id: "child-private-progress",
Expand All @@ -6676,6 +6709,7 @@ describe("PrimeAgentDaemonAdapter", () => {
activity: { kind: "executing", toolName: "bash" },
answerPreview: "private-answer-roster",
recap: "private-recap-roster",
progressNote: "Checking the failing test",
},
];
expect(
Expand Down Expand Up @@ -6718,17 +6752,21 @@ describe("PrimeAgentDaemonAdapter", () => {
yield* awaitObservedType(subscription.observed, "session.goal.updated");

const durableEvents = subscription.events.slice(durableStart);
expect(durableEvents.filter((event) => event.type === "task.progress")).toEqual([]);
expect(durableEvents.filter((event) => event.type === "task.progress")).toEqual([
expect.objectContaining({
payload: expect.objectContaining({ summary: "Checking the failing test" }),
}),
]);
const completed = durableEvents.filter((event) => event.type === "task.completed");
expect(completed).toHaveLength(1);
expect(completed[0]).toMatchObject({
payload: {
taskId: "child-private-progress",
status: "failed",
summary: "Failed",
typedUsage: { totalTokens: 12, toolUses: 1, durationMs: 1_500 },
},
});
expect(completed[0]?.payload).not.toHaveProperty("summary");
const persisted = encodeUnknownJson(durableEvents);
expect(persisted).not.toContain("private-answer");
expect(persisted).not.toContain("private-recap");
Expand Down
3 changes: 3 additions & 0 deletions apps/server/src/provider/prime/PrimeAgentDaemonAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,7 @@ type PrimeAgentDaemonChildDurableProjection = {
readonly messageable: boolean;
readonly waiting: boolean;
readonly lastToolName: string | undefined;
readonly progressNote: string | undefined;
readonly tokenCount: number | undefined;
readonly toolUseCount: number | undefined;
readonly terminalDurationMs: number | undefined;
Expand Down Expand Up @@ -560,6 +561,7 @@ function durableChildProjection(
child.status === "running" && child.activity?.kind !== "waiting"
? durableChildText(child.activity?.toolName)
: undefined,
progressNote: active ? durableChildText(child.progressNote) : undefined,
tokenCount: durableChildCount(child.tokenCount),
toolUseCount: durableChildCount(child.toolUseCount),
terminalDurationMs: active ? undefined : durableChildCount(child.durationMs),
Expand All @@ -581,6 +583,7 @@ function durableChildChanged(
left.messageable !== right.messageable ||
left.waiting !== right.waiting ||
left.lastToolName !== right.lastToolName ||
left.progressNote !== right.progressNote ||
left.tokenCount !== right.tokenCount ||
left.toolUseCount !== right.toolUseCount ||
left.terminalDurationMs !== right.terminalDurationMs
Expand Down
20 changes: 20 additions & 0 deletions apps/server/src/provider/prime/PrimeAgentDaemonEvents.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -819,6 +819,26 @@ describe("PrimeAgentDaemonEvents", () => {
_tag: "ChildUpdated",
child: { id: "child-1", status: "running", activity: { toolName: "ipython" } },
});
expect(
decodePrimeAgentDaemonEvent(
sessionEvent({
type: "rlm_child_update",
child: {
...child,
progressNote: "Checking tests",
lastActivityAt: 1_700_000_000_000,
activityStaleMs: 600_000,
},
}),
),
).toMatchObject({
_tag: "ChildUpdated",
child: {
progressNote: "Checking tests",
lastActivityAt: 1_700_000_000_000,
activityStaleMs: 600_000,
},
});
const queueChanged = decodePrimeAgentDaemonEvent(
sessionEvent({ type: "session_action_update", actions }),
);
Expand Down
9 changes: 9 additions & 0 deletions apps/server/src/provider/prime/PrimeAgentDaemonEvents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,9 @@ const rlmChild = Schema.Struct({
toolUseCount: Schema.optional(Schema.Number),
tokenCount: Schema.optional(Schema.Number),
recap: Schema.optional(Schema.String),
progressNote: Schema.optional(Schema.String),
lastActivityAt: Schema.optional(Schema.Finite),
activityStaleMs: Schema.optional(Schema.Int.check(Schema.isGreaterThanOrEqualTo(0))),
sessionDir: Schema.String,
activity: Schema.optional(
Schema.Struct({
Expand Down Expand Up @@ -1309,6 +1312,9 @@ export type PrimeDaemonEvent = (
readonly toolUseCount?: number | undefined;
readonly tokenCount?: number | undefined;
readonly recap?: string | undefined;
readonly progressNote?: string | undefined;
readonly lastActivityAt?: number | undefined;
readonly activityStaleMs?: number | undefined;
readonly activity?:
| {
readonly kind: "waiting" | "writing" | "executing";
Expand Down Expand Up @@ -1656,6 +1662,9 @@ function mapChild(
toolUseCount: value.toolUseCount,
tokenCount: value.tokenCount,
recap: optionalBounded(value.recap, MAX_PREVIEW_LENGTH),
progressNote: optionalBounded(value.progressNote, 512),
lastActivityAt: value.lastActivityAt,
activityStaleMs: value.activityStaleMs,
activity: value.activity,
error: optionalBounded(value.error, MAX_PREVIEW_LENGTH),
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -660,6 +660,9 @@ describe("mapPrimeAgentDaemonRuntimeEventDrafts", () => {
tokenCount: 90,
answerPreview: "private live answer",
recap: "private recap",
progressNote: "Checking the failing test",
lastActivityAt: 1_700_000_000_000,
activityStaleMs: 600_000,
error: "private transient error",
activity: { kind: "executing", toolName: "bash" },
},
Expand All @@ -676,6 +679,7 @@ describe("mapPrimeAgentDaemonRuntimeEventDrafts", () => {
taskId: RuntimeTaskId.make("child-1"),
description: "Review tests",
status: "running",
summary: "Checking the failing test",
typedUsage: { totalTokens: 90, toolUses: 4, durationMs: 250 },
lastToolName: "bash",
taskType: "subagent",
Expand All @@ -693,6 +697,8 @@ describe("mapPrimeAgentDaemonRuntimeEventDrafts", () => {
expect(JSON.stringify(running)).not.toContain("private live answer");
expect(JSON.stringify(running)).not.toContain("private recap");
expect(JSON.stringify(running)).not.toContain("private transient error");
expect(JSON.stringify(running)).not.toContain("activityStaleMs");
expect(JSON.stringify(running)).not.toContain("lastActivityAt");

const terminal = mapPrimeAgentDaemonRuntimeEventDrafts({
...context,
Expand All @@ -716,12 +722,12 @@ describe("mapPrimeAgentDaemonRuntimeEventDrafts", () => {
payload: {
taskId: RuntimeTaskId.make("child-1"),
status: "failed",
summary: "Failed",
typedUsage: { totalTokens: 100 },
messageable: false,
timelineBypass: true,
},
});
expect(terminal[0]?.payload).not.toHaveProperty("summary");
expect(JSON.stringify(terminal)).not.toContain("private terminal");
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,8 @@ function childDraft(
};
case "running": {
const lastToolName = boundedNonEmpty(child.activity?.toolName, MAX_SCALAR_LENGTH);
if (child.activity?.kind === "waiting") {
const progressNote = boundedNonEmpty(child.progressNote?.trim(), 512);
if (child.activity?.kind === "waiting" && progressNote === undefined) {
return {
...runtimeBase(input),
type: "task.updated",
Expand All @@ -320,7 +321,8 @@ function childDraft(
payload: {
taskId,
description,
status: "running",
status: child.activity?.kind === "waiting" ? "waiting" : "running",
...(progressNote === undefined ? {} : { summary: progressNote }),
...(typedUsage === undefined ? {} : { typedUsage }),
...(lastToolName === undefined ? {} : { lastToolName }),
...linkage,
Expand All @@ -337,6 +339,8 @@ function childDraft(
taskId,
status:
child.status === "done" ? "completed" : child.status === "error" ? "failed" : "stopped",
summary:
child.status === "done" ? "Completed" : child.status === "error" ? "Failed" : "Stopped",
...(typedUsage === undefined ? {} : { typedUsage }),
...linkage,
},
Expand Down
23 changes: 23 additions & 0 deletions packages/client-runtime/src/state/subagentRuntime.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,29 @@ function fold(rows: ReadonlyArray<OrchestrationThreadActivity>) {
}

describe("foldSubagentActivities", () => {
it("shows a Prime child note while active and its outcome after completion", () => {
const [agent] = fold([
activity("task.progress", {
taskId: "prime-child-1",
taskType: "subagent",
title: "Review tests",
status: "running",
summary: "Checking the failing test",
}),
activity("task.completed", {
taskId: "prime-child-1",
taskType: "subagent",
status: "completed",
summary: "Completed",
}),
]);
expect(agent).toMatchObject({
status: "completed",
progress: "Checking the failing test",
result: "Completed",
});
});

it("shows the batch status limit after its parent turn ends without claiming a result", () => {
const running = activity("task.progress", {
taskId: "batch-1",
Expand Down
Loading