@springbrand/agent-runtime 0.2.0-alpha.72 → 0.3.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +3 -3
- package/src/{agent-tool-runtime.ts → adapter/cloudflare/agent-tool.ts} +1 -1
- package/src/adapter/cloudflare/agent.ts +1539 -0
- package/src/{db/index.ts → adapter/cloudflare/database.ts} +18 -55
- package/src/{runtime-agent.ts → adapter/cloudflare/define-agent.ts} +25 -35
- package/src/adapter/cloudflare/dynamic-worker-observability.ts +1 -1
- package/src/adapter/cloudflare/resources/runtime-resources.ts +4 -4
- package/src/adapter/cloudflare/sandbox/adapter.ts +1 -1
- package/src/{db → adapter/cloudflare}/schema.ts +16 -83
- package/src/adapter/cloudflare/subagent/definition.ts +63 -33
- package/src/adapter/cloudflare/subagent/runner.ts +174 -120
- package/src/adapter/cloudflare/subagent/tools.ts +13 -14
- package/src/adapter/cloudflare/universal-agent/preparation.ts +7 -7
- package/src/adapter/cloudflare/universal-agent/tools.ts +10 -10
- package/src/adapter/cloudflare/workspace/git-fs.ts +1 -1
- package/src/adapter/cloudflare/workspace/scoped-workspace.ts +1 -1
- package/src/adapter/cloudflare/workspace/version-control.ts +2 -2
- package/src/config/activation.ts +140 -0
- package/src/{runtime-agent-context.ts → config/agent-context.ts} +3 -3
- package/src/{runtime-assembler.ts → config/assembler.ts} +20 -19
- package/src/{kernel/runtime-assembly.ts → config/assembly-projection.ts} +4 -4
- package/src/{pi/assembly/snapshot.ts → config/assembly-snapshot.ts} +6 -6
- package/src/{runtime-definition.ts → config/definition.ts} +13 -13
- package/src/config/harness-runtime.ts +182 -0
- package/src/config/index.ts +14 -0
- package/src/{pi/runtime-adapter/assembly.ts → config/pinning.ts} +38 -16
- package/src/{kernel/bindings.ts → config/ports.ts} +9 -13
- package/src/{kernel → config}/profile.ts +7 -0
- package/src/{kernel → config}/public-contracts.ts +6 -6
- package/src/{kernel → config}/runtime-load.ts +1 -1
- package/src/{pi/assembly/context.ts → context/assembly.ts} +4 -206
- package/src/{pi/assembly/extensions.ts → context/extension/assembly.ts} +5 -5
- package/src/context/extension/index.ts +3 -0
- package/src/context/index.ts +6 -0
- package/src/{layers/context → context/memory}/budget/gate.ts +2 -2
- package/src/conversation/harness-resume.ts +288 -0
- package/src/conversation/harness-stream.ts +98 -0
- package/src/{pi/runtime-adapter/transcript.ts → conversation/history.ts} +206 -233
- package/src/conversation/index.ts +6 -0
- package/src/{pi → conversation}/message/contract.ts +3 -0
- package/src/{pi → conversation}/message/conversion.ts +75 -0
- package/src/{pi → conversation}/message/index.ts +1 -0
- package/src/{pi → conversation}/message/model-input.ts +1 -1
- package/src/{pi → conversation}/message/projection.ts +76 -10
- package/src/{db/message-ui.repo.ts → conversation/repository.ts} +4 -0
- package/src/conversation/state.ts +175 -0
- package/src/harness/index.ts +7 -0
- package/src/harness/session-harness.ts +481 -0
- package/src/harness/submission-operation.ts +49 -0
- package/src/index.ts +38 -43
- package/src/lib/mcp.ts +1 -1
- package/src/{pi/runtime-adapter/models.ts → models/index.ts} +64 -18
- package/src/{pi/turn/tool-recovery.ts → session/cloudflare/legacy-tool-recovery.ts} +33 -525
- package/src/{pi/session → session/cloudflare}/migration.ts +334 -8
- package/src/{pi/session → session/cloudflare}/storage.ts +8 -36
- package/src/session/index.ts +39 -0
- package/src/submission/admission.ts +275 -0
- package/src/{kernel/receipts.ts → submission/contracts.ts} +14 -0
- package/src/submission/delivery.ts +212 -0
- package/src/submission/finalization.ts +111 -0
- package/src/submission/index.ts +9 -0
- package/src/{kernel/submission-lifecycle.ts → submission/lifecycle.ts} +41 -28
- package/src/{db/submission.repo.ts → submission/repository.ts} +61 -118
- package/src/submission/steer-delivery.ts +167 -0
- package/src/{db/steer.repo.ts → submission/steer-repository.ts} +27 -5
- package/src/telemetry/contract.ts +12 -0
- package/src/telemetry/coordinator.ts +65 -0
- package/src/telemetry/delivery.ts +2 -2
- package/src/telemetry/index.ts +5 -0
- package/src/{model-usage.ts → telemetry/model-usage.ts} +1 -1
- package/src/telemetry/recorder.ts +1 -1
- package/src/telemetry/runtime-telemetry.ts +12 -0
- package/src/{kernel/durable-lifecycle.ts → telemetry/subagent-lifecycle.ts} +1 -1
- package/src/telemetry/turn-events.ts +279 -0
- package/src/{pi/tool → tools}/ai-adapter.ts +2 -2
- package/src/tools/approval/index.ts +1 -0
- package/src/{db/approval.repo.ts → tools/approval/repository.ts} +10 -30
- package/src/{pi/tool → tools}/base.ts +14 -30
- package/src/tools/compiler.ts +327 -0
- package/src/{pi/tool → tools}/core-host.ts +3 -3
- package/src/{pi/tool → tools}/core.ts +3 -3
- package/src/tools/decision-gate.ts +227 -0
- package/src/{pi/tool → tools}/gateway.ts +1 -1
- package/src/tools/human-decisions.ts +127 -0
- package/src/tools/index.ts +30 -0
- package/src/tools/interaction/index.ts +1 -0
- package/src/{pi/tool → tools}/mcp.ts +1 -1
- package/src/{pi/tool → tools}/schedule.ts +3 -3
- package/src/{pi/tool → tools}/skill.ts +4 -4
- package/src/{kernel → tools}/subagent-runtime.ts +1 -1
- package/src/{pi/tool → tools}/subagent.ts +4 -4
- package/src/tools/temporary-agent/approvals.ts +126 -0
- package/src/{layers/orchestration/temporary-agent/core.ts → tools/temporary-agent/coordinator.ts} +41 -49
- package/src/tools/temporary-agent/core.ts +53 -0
- package/src/{layers/orchestration → tools}/temporary-agent/runner.ts +1 -1
- package/src/{layers/orchestration → tools}/temporary-agent/workspace.ts +1 -1
- package/src/{pi/tool → tools}/web-search/api.ts +1 -1
- package/src/{pi/tool → tools}/workspace-revision.ts +2 -2
- package/src/{pi/tool → tools}/workspace-sandbox.ts +2 -2
- package/src/{pi/tool → tools}/workspace-state-connector.ts +1 -1
- package/src/db/milestone.repo.ts +0 -104
- package/src/db/settlement.repo.ts +0 -104
- package/src/kernel/approval-lifecycle.ts +0 -638
- package/src/kernel/interaction-lifecycle.ts +0 -433
- package/src/kernel/recoverable-chat-agent.ts +0 -1020
- package/src/kernel/state.ts +0 -91
- package/src/pi/assembly/index.ts +0 -19
- package/src/pi/runtime-adapter/execution.ts +0 -1163
- package/src/pi/runtime-adapter/index.ts +0 -293
- package/src/pi/runtime-adapter/recovery.ts +0 -982
- package/src/pi/session/index.ts +0 -24
- package/src/pi/tool/compiler.ts +0 -483
- package/src/pi/tool/index.ts +0 -38
- package/src/pi/turn/approval.ts +0 -181
- package/src/pi/turn/index.ts +0 -82
- package/src/pi/turn/interaction.ts +0 -181
- package/src/runtime.ts +0 -4760
- /package/src/{kernel/runtime-assembly-view.ts → config/assembly-view.ts} +0 -0
- /package/src/{kernel → config}/degradation.ts +0 -0
- /package/src/{kernel → config}/runtime-config.ts +0 -0
- /package/src/{kernel/extensions.ts → context/extension/contracts.ts} +0 -0
- /package/src/{db/ext-context.repo.ts → context/extension/repository.ts} +0 -0
- /package/src/{pi/runtime-adapter/openrouter-messages.ts → models/openrouter/messages.ts} +0 -0
- /package/src/{pi/runtime-adapter → models/openrouter}/test-fixtures/openrouter-tool-search.sse +0 -0
- /package/src/{db/submission-admission.repo.ts → submission/admission-repository.ts} +0 -0
- /package/src/{db/telemetry-outbox.repo.ts → telemetry/repository.ts} +0 -0
- /package/src/{db/runtime-event-outbox.repo.ts → telemetry/runtime-event-repository.ts} +0 -0
- /package/src/{pi/tool → tools}/declared.ts +0 -0
- /package/src/{db/interaction.repo.ts → tools/interaction/repository.ts} +0 -0
- /package/src/{pi/tool → tools}/nested-tools.ts +0 -0
- /package/src/{kernel/submission-authority.ts → tools/subagent-authority.ts} +0 -0
- /package/src/{db/agent-tool.repo.ts → tools/subagent-run-repository.ts} +0 -0
- /package/src/{layers/orchestration/subagents/agent-types → tools/subagent-types}/contract.ts +0 -0
- /package/src/{layers/orchestration/subagents/agent-types → tools/subagent-types}/extract/index.ts +0 -0
- /package/src/{layers/orchestration/subagents/agent-types → tools/subagent-types}/fanout/index.ts +0 -0
- /package/src/{layers/orchestration/subagents/agent-types → tools/subagent-types}/registry.ts +0 -0
- /package/src/{pi/tool → tools}/time.ts +0 -0
- /package/src/{pi/tool → tools}/web-fetch.ts +0 -0
- /package/src/{pi/tool → tools}/web-search/index.ts +0 -0
- /package/src/{pi/tool → tools}/web-search/web-search.ts +0 -0
- /package/src/{workspace-versioning.ts → tools/workspace-versioning.ts} +0 -0
|
@@ -1,5 +1,80 @@
|
|
|
1
1
|
import type { UserMessage } from "@earendil-works/pi-ai";
|
|
2
2
|
import type { UIMessage } from "ai";
|
|
3
|
+
import type { RequestedCapability } from "./contract";
|
|
4
|
+
|
|
5
|
+
export function requestedCapabilitiesOf(metadata: unknown): RequestedCapability[] {
|
|
6
|
+
if (metadata === null || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
9
|
+
const record = metadata as Record<string, unknown>;
|
|
10
|
+
if (!("requestedCapabilities" in record)) return [];
|
|
11
|
+
if (!Array.isArray(record.requestedCapabilities)) {
|
|
12
|
+
throw new Error("requestedCapabilities must be an array");
|
|
13
|
+
}
|
|
14
|
+
const result: RequestedCapability[] = [];
|
|
15
|
+
const seen = new Set<string>();
|
|
16
|
+
for (const value of record.requestedCapabilities) {
|
|
17
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
18
|
+
throw new Error("requestedCapabilities contains an invalid capability");
|
|
19
|
+
}
|
|
20
|
+
const capability = value as Record<string, unknown>;
|
|
21
|
+
if (
|
|
22
|
+
(capability.kind !== "skill" && capability.kind !== "tool" && capability.kind !== "plan") ||
|
|
23
|
+
typeof capability.name !== "string" ||
|
|
24
|
+
!capability.name.trim() ||
|
|
25
|
+
typeof capability.label !== "string" ||
|
|
26
|
+
!capability.label.trim()
|
|
27
|
+
) {
|
|
28
|
+
throw new Error("requestedCapabilities contains an invalid capability");
|
|
29
|
+
}
|
|
30
|
+
const key = `${capability.kind}:${capability.name}`;
|
|
31
|
+
if (seen.has(key)) continue;
|
|
32
|
+
seen.add(key);
|
|
33
|
+
result.push({ kind: capability.kind, name: capability.name, label: capability.label });
|
|
34
|
+
}
|
|
35
|
+
return result;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Resolve user-selected capabilities against the loaded Runtime before model input conversion. */
|
|
39
|
+
export function normalizeUIUserInput(
|
|
40
|
+
message: UIMessage & { role: "user" },
|
|
41
|
+
installed: {
|
|
42
|
+
readonly hasSkill: (name: string) => boolean;
|
|
43
|
+
readonly hasTool: (name: string) => boolean;
|
|
44
|
+
},
|
|
45
|
+
): UserMessage {
|
|
46
|
+
const context: string[] = [];
|
|
47
|
+
for (const capability of requestedCapabilitiesOf(message.metadata)) {
|
|
48
|
+
if (capability.kind === "skill") {
|
|
49
|
+
if (!installed.hasSkill(capability.name)) {
|
|
50
|
+
throw new Error(`Requested Skill is not installed: ${capability.name}`);
|
|
51
|
+
}
|
|
52
|
+
context.push(
|
|
53
|
+
`requested capability: skill/${capability.name}`,
|
|
54
|
+
`required action: call activate_skill for "${capability.name}" before handling the task`,
|
|
55
|
+
);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (capability.kind === "tool") {
|
|
59
|
+
if (!installed.hasTool(capability.name)) {
|
|
60
|
+
throw new Error(`Requested Tool is not installed: ${capability.name}`);
|
|
61
|
+
}
|
|
62
|
+
context.push(
|
|
63
|
+
`requested capability: tool/${capability.name}`,
|
|
64
|
+
`required action: call tool "${capability.name}" when handling the task`,
|
|
65
|
+
);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (capability.name !== "plan") {
|
|
69
|
+
throw new Error(`Unknown plan capability: ${capability.name}`);
|
|
70
|
+
}
|
|
71
|
+
context.push(
|
|
72
|
+
"requested capability: plan/plan",
|
|
73
|
+
"required action: call update_plan with the complete plan before other work, then wait for user confirmation before execution",
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return uiUserMessageToPi(message, context.join("\n"));
|
|
77
|
+
}
|
|
3
78
|
|
|
4
79
|
export interface UIUserSidecar {
|
|
5
80
|
readonly metadata?: unknown;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Message, UserMessage } from "@earendil-works/pi-ai";
|
|
2
|
-
import type { RuntimeModelInputModality } from "../../
|
|
2
|
+
import type { RuntimeModelInputModality } from "../../config/profile";
|
|
3
3
|
|
|
4
4
|
const UNSUPPORTED_FILE_PLACEHOLDER =
|
|
5
5
|
"(file omitted: model API does not support this file type)";
|
|
@@ -11,7 +11,7 @@ import type {
|
|
|
11
11
|
UIMessage,
|
|
12
12
|
UIMessageChunk,
|
|
13
13
|
} from "ai";
|
|
14
|
-
import { USER_STOP_REASON } from "../../
|
|
14
|
+
import { USER_STOP_REASON } from "../../submission/contracts";
|
|
15
15
|
|
|
16
16
|
const MAX_OUTPUT_BYTES = 256 * 1024;
|
|
17
17
|
const MAX_OUTPUT_PREVIEW = 16 * 1024;
|
|
@@ -174,6 +174,7 @@ export class PiChunkEncoder {
|
|
|
174
174
|
private readonly messageId: string;
|
|
175
175
|
private readonly startedAt?: number;
|
|
176
176
|
private readonly turnId?: string;
|
|
177
|
+
private readonly userMessageId?: string;
|
|
177
178
|
private started = false;
|
|
178
179
|
private finished = false;
|
|
179
180
|
private firstTurnStarted = false;
|
|
@@ -192,10 +193,12 @@ export class PiChunkEncoder {
|
|
|
192
193
|
messageId: string;
|
|
193
194
|
startedAt?: number;
|
|
194
195
|
turnId?: string;
|
|
196
|
+
userMessageId?: string | null;
|
|
195
197
|
}) {
|
|
196
198
|
this.messageId = options.messageId;
|
|
197
199
|
this.startedAt = options.startedAt;
|
|
198
200
|
this.turnId = options.turnId;
|
|
201
|
+
this.userMessageId = options.userMessageId ?? undefined;
|
|
199
202
|
}
|
|
200
203
|
|
|
201
204
|
start(): UIMessageChunk[] {
|
|
@@ -213,6 +216,7 @@ export class PiChunkEncoder {
|
|
|
213
216
|
createdAt: this.startedAt,
|
|
214
217
|
turnStatus: "running",
|
|
215
218
|
...(this.turnId ? { turnId: this.turnId } : {}),
|
|
219
|
+
...(this.userMessageId ? { userMessageId: this.userMessageId } : {}),
|
|
216
220
|
},
|
|
217
221
|
}),
|
|
218
222
|
},
|
|
@@ -220,6 +224,54 @@ export class PiChunkEncoder {
|
|
|
220
224
|
];
|
|
221
225
|
}
|
|
222
226
|
|
|
227
|
+
/** Seed this encoder and its open part IDs from one Pi-backed UI snapshot. */
|
|
228
|
+
restore(message: UIMessage, streaming: boolean): UIMessageChunk[] {
|
|
229
|
+
if (this.started) throw new Error("Cannot restore an already-started stream encoder");
|
|
230
|
+
this.started = true;
|
|
231
|
+
this.firstTurnStarted = message.parts.length === 0;
|
|
232
|
+
const chunks: UIMessageChunk[] = [{ type: "start", messageId: this.messageId,
|
|
233
|
+
...(message.metadata === undefined ? {} : { messageMetadata: message.metadata }) }];
|
|
234
|
+
let contentIndex = 0;
|
|
235
|
+
let hasStep = false;
|
|
236
|
+
let lastContentIndex = message.parts.length - 1;
|
|
237
|
+
while (lastContentIndex >= 0 && message.parts[lastContentIndex]?.type === "step-start") lastContentIndex -= 1;
|
|
238
|
+
for (const [partIndex, part] of message.parts.entries()) {
|
|
239
|
+
if (part.type === "step-start") {
|
|
240
|
+
if (hasStep) { chunks.push({ type: "finish-step" }); this.stepIndex += 1; }
|
|
241
|
+
chunks.push({ type: "start-step" });
|
|
242
|
+
hasStep = true;
|
|
243
|
+
contentIndex = 0;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
if (!hasStep) { chunks.push({ type: "start-step" }); hasStep = true; }
|
|
247
|
+
if (part.type === "text" || part.type === "reasoning") {
|
|
248
|
+
const kind = part.type;
|
|
249
|
+
const id = this.partId(kind, contentIndex);
|
|
250
|
+
chunks.push({ type: `${kind}-start`, id });
|
|
251
|
+
if (part.text) chunks.push({ type: `${kind}-delta`, id, delta: part.text });
|
|
252
|
+
if (streaming && partIndex === lastContentIndex) {
|
|
253
|
+
(kind === "text" ? this.activeText : this.activeReasoning).add(contentIndex);
|
|
254
|
+
} else chunks.push({ type: `${kind}-end`, id });
|
|
255
|
+
} else if (part.type === "dynamic-tool") {
|
|
256
|
+
if (part.state === "input-streaming") {
|
|
257
|
+
chunks.push({ type: "tool-input-start", toolCallId: part.toolCallId, toolName: part.toolName, dynamic: true });
|
|
258
|
+
this.toolCallIdByIndex.set(contentIndex, part.toolCallId);
|
|
259
|
+
this.streamingToolById.set(part.toolCallId, { toolName: part.toolName, started: true, deltas: [] });
|
|
260
|
+
} else {
|
|
261
|
+
chunks.push(...this.toolInput(part.toolCallId, part.toolName, part.input));
|
|
262
|
+
if (part.state === "approval-requested") chunks.push(...this.approvalRequested(part.toolCallId, part.approval.id));
|
|
263
|
+
else if (part.state === "output-available") chunks.push({ type: "tool-output-available", toolCallId: part.toolCallId, output: part.output, preliminary: part.preliminary, dynamic: true });
|
|
264
|
+
else if (part.state === "output-error") chunks.push({ type: "tool-output-error", toolCallId: part.toolCallId, errorText: part.errorText, dynamic: true });
|
|
265
|
+
else if (part.state === "output-denied") chunks.push({ type: "tool-output-denied", toolCallId: part.toolCallId });
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
contentIndex += 1;
|
|
269
|
+
}
|
|
270
|
+
if (!hasStep) chunks.push({ type: "start-step" });
|
|
271
|
+
else if (!streaming) chunks.push({ type: "finish-step" });
|
|
272
|
+
return chunks;
|
|
273
|
+
}
|
|
274
|
+
|
|
223
275
|
toolInput(
|
|
224
276
|
toolCallId: string,
|
|
225
277
|
toolName: string,
|
|
@@ -262,7 +314,10 @@ export class PiChunkEncoder {
|
|
|
262
314
|
];
|
|
263
315
|
}
|
|
264
316
|
|
|
265
|
-
encode(
|
|
317
|
+
encode(
|
|
318
|
+
event: AgentEvent,
|
|
319
|
+
options: { readonly terminal?: boolean } = {},
|
|
320
|
+
): UIMessageChunk[] {
|
|
266
321
|
if (this.finished) return [];
|
|
267
322
|
switch (event.type) {
|
|
268
323
|
case "agent_start":
|
|
@@ -318,7 +373,7 @@ export class PiChunkEncoder {
|
|
|
318
373
|
case "message_end":
|
|
319
374
|
this.firstTurnStarted = false;
|
|
320
375
|
return event.message.role === "assistant"
|
|
321
|
-
? this.endAssistant(event.message)
|
|
376
|
+
? this.endAssistant(event.message, options.terminal !== false)
|
|
322
377
|
: [];
|
|
323
378
|
case "agent_end":
|
|
324
379
|
case "turn_end":
|
|
@@ -468,15 +523,25 @@ export class PiChunkEncoder {
|
|
|
468
523
|
return chunks;
|
|
469
524
|
}
|
|
470
525
|
|
|
471
|
-
private endAssistant(message: AssistantMessage): UIMessageChunk[] {
|
|
526
|
+
private endAssistant(message: AssistantMessage, terminal = true): UIMessageChunk[] {
|
|
472
527
|
this.pendingToolByIndex.clear();
|
|
473
528
|
this.toolCallIdByIndex.clear();
|
|
474
529
|
const chunks = this.closeOpenParts();
|
|
475
|
-
if (message.stopReason === "toolUse") {
|
|
530
|
+
if (message.stopReason === "toolUse" || !terminal) {
|
|
476
531
|
chunks.push({ type: "finish-step" });
|
|
477
532
|
return chunks;
|
|
478
533
|
}
|
|
534
|
+
const outcome = classifyPiTerminalStopReason(message.stopReason);
|
|
535
|
+
if (outcome.outcome === "succeeded") chunks.push({ type: "finish-step" });
|
|
536
|
+
chunks.push(...this.finish(message));
|
|
537
|
+
return chunks;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/** Finalize the UI operation after its last model step has already closed. */
|
|
541
|
+
finish(message: Pick<AssistantMessage, "stopReason" | "timestamp" | "errorMessage">): UIMessageChunk[] {
|
|
542
|
+
if (this.finished) return [];
|
|
479
543
|
this.finished = true;
|
|
544
|
+
const chunks: UIMessageChunk[] = [];
|
|
480
545
|
const publicError = publicAssistantError(message.errorMessage);
|
|
481
546
|
const terminal = classifyPiTerminalStopReason(message.stopReason);
|
|
482
547
|
if (this.startedAt !== undefined) {
|
|
@@ -507,7 +572,6 @@ export class PiChunkEncoder {
|
|
|
507
572
|
});
|
|
508
573
|
return chunks;
|
|
509
574
|
}
|
|
510
|
-
chunks.push({ type: "finish-step" });
|
|
511
575
|
chunks.push({
|
|
512
576
|
type: "finish",
|
|
513
577
|
finishReason: terminal.finishReason ?? "other",
|
|
@@ -555,7 +619,7 @@ function projectedTool(
|
|
|
555
619
|
const questions: unknown = JSON.parse(input.questions);
|
|
556
620
|
if (Array.isArray(questions)) input.questions = questions;
|
|
557
621
|
} catch {
|
|
558
|
-
// Preserve malformed
|
|
622
|
+
// Preserve malformed provider output for the normal unavailable state.
|
|
559
623
|
}
|
|
560
624
|
}
|
|
561
625
|
const base = {
|
|
@@ -601,15 +665,16 @@ export function piAssistantToUIMessage(
|
|
|
601
665
|
readonly id: string;
|
|
602
666
|
readonly metadata?: unknown;
|
|
603
667
|
readonly approvals?: Readonly<Record<string, PiToolApprovalView | undefined>>;
|
|
668
|
+
readonly state?: "streaming" | "done";
|
|
604
669
|
},
|
|
605
670
|
): UIMessage {
|
|
606
671
|
const parts = message.content.flatMap(
|
|
607
672
|
(content): Array<UIMessage["parts"][number]> => {
|
|
608
673
|
if (content.type === "text") {
|
|
609
|
-
return [{ type: "text", text: content.text, state: "done" }];
|
|
674
|
+
return [{ type: "text", text: content.text, state: options.state ?? "done" }];
|
|
610
675
|
}
|
|
611
676
|
if (content.type === "thinking") {
|
|
612
|
-
return [{ type: "reasoning", text: content.thinking, state: "done" }];
|
|
677
|
+
return [{ type: "reasoning", text: content.thinking, state: options.state ?? "done" }];
|
|
613
678
|
}
|
|
614
679
|
if (content.type === "toolCall") {
|
|
615
680
|
return [projectedTool(content, options.approvals?.[content.id])];
|
|
@@ -629,6 +694,7 @@ export function piAssistantToUIMessage(
|
|
|
629
694
|
export function applyPiToolResult(
|
|
630
695
|
message: UIMessage,
|
|
631
696
|
result: ToolResultMessage,
|
|
697
|
+
options: { readonly preliminary?: boolean } = {},
|
|
632
698
|
): UIMessage {
|
|
633
699
|
return {
|
|
634
700
|
...message,
|
|
@@ -658,7 +724,7 @@ export function applyPiToolResult(
|
|
|
658
724
|
state: "output-available",
|
|
659
725
|
input: part.input,
|
|
660
726
|
output: toolOutput({ content: result.content, details: result.details }),
|
|
661
|
-
preliminary: false,
|
|
727
|
+
preliminary: options.preliminary ?? false,
|
|
662
728
|
...(approval ? { approval } : {}),
|
|
663
729
|
};
|
|
664
730
|
}),
|
|
@@ -30,6 +30,10 @@ export class MessageUiRepository {
|
|
|
30
30
|
`;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
delete(messageId: string): void {
|
|
34
|
+
this.sql`DELETE FROM pi_message_ui WHERE message_id = ${messageId}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
33
37
|
// 删除全部 Message UI 补充数据。
|
|
34
38
|
// Transcript.clear 在清空标准会话时调用,并由上层事务把两类数据一起提交。
|
|
35
39
|
// 该责任曾属于 RuntimeDatabase.clearAll,现在归 Transcript 所有;不能两处分别维护清理规则。
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import type { ApprovalReceipt } from "../submission/contracts";
|
|
2
|
+
import type { StoredSubmission } from "../submission/repository";
|
|
3
|
+
import type { PendingApprovalSummary } from "../tools/approval/repository";
|
|
4
|
+
import type { PiCanonicalUserInput } from "./message/contract";
|
|
5
|
+
|
|
6
|
+
export type RuntimeLoadPhase = "config" | "assembly" | "mcp" | "pi";
|
|
7
|
+
|
|
8
|
+
export type RuntimeActivity = "idle" | "working" | "needs-input";
|
|
9
|
+
|
|
10
|
+
export interface RuntimeActivityProjection {
|
|
11
|
+
activity: RuntimeActivity;
|
|
12
|
+
/**
|
|
13
|
+
* 是否仍有 Agent Tool 子运行在执行。
|
|
14
|
+
*
|
|
15
|
+
* `activity` 三态互斥,表达不了「等人回应的同时后台还在跑」。Host 的列表需要
|
|
16
|
+
* 这两件事各自成立,所以它是一个独立维度,而不是第四种活动值。
|
|
17
|
+
*/
|
|
18
|
+
backgroundWork: boolean;
|
|
19
|
+
revision: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Runtime 最近一次装载尝试的公开状态。
|
|
24
|
+
*
|
|
25
|
+
* `available` 与本次尝试分开表达:强制重载失败时,旧 Runtime 仍可能继续服务。
|
|
26
|
+
* 该状态由 Agents SDK 的 Agent state 同步给浏览器,不包含底层异常文本。
|
|
27
|
+
*/
|
|
28
|
+
export type RuntimeLoadState =
|
|
29
|
+
| {
|
|
30
|
+
status: "idle";
|
|
31
|
+
available: false;
|
|
32
|
+
}
|
|
33
|
+
| {
|
|
34
|
+
status: "loading";
|
|
35
|
+
phase: RuntimeLoadPhase;
|
|
36
|
+
available: boolean;
|
|
37
|
+
startedAt: number;
|
|
38
|
+
updatedAt: number;
|
|
39
|
+
}
|
|
40
|
+
| {
|
|
41
|
+
status: "ready";
|
|
42
|
+
available: true;
|
|
43
|
+
startedAt: number;
|
|
44
|
+
completedAt: number;
|
|
45
|
+
}
|
|
46
|
+
| {
|
|
47
|
+
status: "error";
|
|
48
|
+
phase: RuntimeLoadPhase;
|
|
49
|
+
available: boolean;
|
|
50
|
+
startedAt: number;
|
|
51
|
+
failedAt: number;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
export interface RuntimeQueuedSubmission {
|
|
55
|
+
submissionId: string;
|
|
56
|
+
messageId: string;
|
|
57
|
+
preview: string;
|
|
58
|
+
position: number;
|
|
59
|
+
createdAt: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface RuntimeTurnState {
|
|
63
|
+
activeSubmissionId?: string;
|
|
64
|
+
activeRequestId?: string;
|
|
65
|
+
activeMessageId?: string;
|
|
66
|
+
steerable: boolean;
|
|
67
|
+
hasPendingSteer: boolean;
|
|
68
|
+
queued: RuntimeQueuedSubmission[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 保存 Kernel 在当前会话里自己维护的审批状态。
|
|
73
|
+
*
|
|
74
|
+
* @remarks
|
|
75
|
+
* `RuntimeAgent` 把它作为 Agent state 的形状,并在审批记录变化时读写。
|
|
76
|
+
*
|
|
77
|
+
* 它故意不保存用户、Agent 或 Session 等业务身份,避免 Runtime 越过 Host 的归属边界。
|
|
78
|
+
*
|
|
79
|
+
* `Runtime`、`Kernel` 与 `Host` 等核心术语见 `../index.ts`。
|
|
80
|
+
*/
|
|
81
|
+
export interface RuntimeState {
|
|
82
|
+
/** 当前实例的 Runtime 装载状态;旧状态记录没有该字段时等同 `idle`。 */
|
|
83
|
+
runtimeLoad?: RuntimeLoadState;
|
|
84
|
+
/** 供 Host 列表持久化的粗粒度活动状态;Submission 与 Approval 仍是执行真相。 */
|
|
85
|
+
activity?: RuntimeActivityProjection;
|
|
86
|
+
approvals?: ApprovalReceipt[];
|
|
87
|
+
turn?: RuntimeTurnState;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type SubmissionProjection = Pick<
|
|
91
|
+
StoredSubmission,
|
|
92
|
+
"submissionId" | "requestId" | "assistantMessageId" | "userMessageId" |
|
|
93
|
+
"queuedInputJson" | "createdAt"
|
|
94
|
+
>;
|
|
95
|
+
|
|
96
|
+
/** Rebuild client-visible activity from current facts without retaining lifecycle state. */
|
|
97
|
+
export function projectRuntimeState(input: {
|
|
98
|
+
readonly approvals: readonly PendingApprovalSummary[];
|
|
99
|
+
readonly temporaryApprovals: readonly ApprovalReceipt[];
|
|
100
|
+
readonly running: SubmissionProjection | null | undefined;
|
|
101
|
+
readonly pending: readonly SubmissionProjection[];
|
|
102
|
+
readonly hasPendingApproval: (submissionId: string) => boolean;
|
|
103
|
+
readonly hasPendingInteraction: (submissionId: string) => boolean;
|
|
104
|
+
readonly hasPendingSteer: (submissionId: string) => boolean;
|
|
105
|
+
readonly hasRunningAgentTools: boolean;
|
|
106
|
+
readonly previousState: RuntimeState;
|
|
107
|
+
}): { state: RuntimeState; changed: boolean } {
|
|
108
|
+
const approvals: ApprovalReceipt[] = [
|
|
109
|
+
...input.approvals.map((approval) => ({
|
|
110
|
+
executionId: approval.executionId,
|
|
111
|
+
source: approval.source as ApprovalReceipt["source"],
|
|
112
|
+
action: approval.toolName,
|
|
113
|
+
summary: approval.summary,
|
|
114
|
+
executionLevel: approval.executionLevel,
|
|
115
|
+
requiredExecutionLevel: approval.requiredExecutionLevel,
|
|
116
|
+
inputJson: approval.inputJson,
|
|
117
|
+
requestId: approval.requestId,
|
|
118
|
+
})),
|
|
119
|
+
...input.temporaryApprovals,
|
|
120
|
+
];
|
|
121
|
+
const current = input.running ?? input.pending[0];
|
|
122
|
+
const queued = (input.running ? input.pending : input.pending.slice(1)).map(
|
|
123
|
+
(submission, index): RuntimeQueuedSubmission => {
|
|
124
|
+
let preview = "Queued message";
|
|
125
|
+
if (submission.queuedInputJson) {
|
|
126
|
+
try {
|
|
127
|
+
const message = JSON.parse(submission.queuedInputJson) as PiCanonicalUserInput;
|
|
128
|
+
preview = (typeof message.content === "string" ? message.content :
|
|
129
|
+
message.content.flatMap((part) => part.type === "text" ? [part.text] : []).join(" "))
|
|
130
|
+
.trim().slice(0, 160) || preview;
|
|
131
|
+
} catch {
|
|
132
|
+
// The queue pump reports corrupt payloads when it activates the Submission.
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
submissionId: submission.submissionId,
|
|
137
|
+
messageId: submission.userMessageId ?? submission.submissionId,
|
|
138
|
+
preview,
|
|
139
|
+
position: index + 1,
|
|
140
|
+
createdAt: submission.createdAt,
|
|
141
|
+
};
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
const turn: RuntimeTurnState = {
|
|
145
|
+
...(current ? {
|
|
146
|
+
activeSubmissionId: current.submissionId,
|
|
147
|
+
activeMessageId: current.assistantMessageId,
|
|
148
|
+
...(current.requestId ? { activeRequestId: current.requestId } : {}),
|
|
149
|
+
} : {}),
|
|
150
|
+
steerable: Boolean(current && !input.hasPendingApproval(current.submissionId)),
|
|
151
|
+
hasPendingSteer: Boolean(current && input.hasPendingSteer(current.submissionId)),
|
|
152
|
+
queued,
|
|
153
|
+
};
|
|
154
|
+
const activity: RuntimeActivity = approvals.length > 0 ||
|
|
155
|
+
(current && input.hasPendingInteraction(current.submissionId))
|
|
156
|
+
? "needs-input"
|
|
157
|
+
: current || queued.length > 0 ? "working" : "idle";
|
|
158
|
+
const previousActivity = input.previousState.activity;
|
|
159
|
+
const nextActivity: RuntimeActivityProjection = previousActivity?.activity === activity &&
|
|
160
|
+
previousActivity.backgroundWork === input.hasRunningAgentTools
|
|
161
|
+
? previousActivity
|
|
162
|
+
: {
|
|
163
|
+
activity,
|
|
164
|
+
backgroundWork: input.hasRunningAgentTools,
|
|
165
|
+
revision: (previousActivity?.revision ?? 0) + 1,
|
|
166
|
+
};
|
|
167
|
+
const changed = input.previousState.approvals === undefined ||
|
|
168
|
+
JSON.stringify(input.previousState.approvals) !== JSON.stringify(approvals) ||
|
|
169
|
+
JSON.stringify(input.previousState.turn) !== JSON.stringify(turn) ||
|
|
170
|
+
nextActivity !== previousActivity;
|
|
171
|
+
return {
|
|
172
|
+
state: changed ? { ...input.previousState, activity: nextActivity, approvals, turn } : input.previousState,
|
|
173
|
+
changed,
|
|
174
|
+
};
|
|
175
|
+
}
|