@springbrand/agent-runtime 0.2.0-alpha.72 → 0.3.0-alpha.10
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 +7 -7
- package/src/{agent-tool-runtime.ts → adapter/cloudflare/agent-tool.ts} +1 -1
- package/src/adapter/cloudflare/agent.ts +1682 -0
- package/src/{db/index.ts → adapter/cloudflare/database.ts} +18 -55
- package/src/{runtime-agent.ts → adapter/cloudflare/define-agent.ts} +37 -42
- package/src/adapter/cloudflare/dynamic-worker-observability.ts +1 -1
- package/src/adapter/cloudflare/index.ts +18 -0
- package/src/adapter/cloudflare/resources/runtime-resources.ts +4 -4
- package/src/adapter/cloudflare/sandbox/adapter.ts +1 -1
- package/src/adapter/cloudflare/scheduling/schedule-rule.ts +322 -0
- package/src/adapter/cloudflare/scheduling/zoned-cron.ts +11 -6
- package/src/{db → adapter/cloudflare}/schema.ts +28 -80
- 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 -12
- 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} +45 -46
- 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 +190 -0
- package/src/config/index.ts +14 -0
- package/src/config/model-guidance.ts +21 -0
- package/src/{pi/runtime-adapter/assembly.ts → config/pinning.ts} +60 -19
- package/src/{kernel/bindings.ts → config/ports.ts} +32 -16
- 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} +5 -207
- 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 +21 -5
- package/src/conversation/harness-resume.ts +288 -0
- package/src/conversation/harness-stream.ts +99 -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 +113 -30
- package/src/{db/message-ui.repo.ts → conversation/repository.ts} +4 -0
- package/src/conversation/state.ts +196 -0
- package/src/harness/index.ts +7 -0
- package/src/harness/session-harness.ts +562 -0
- package/src/harness/submission-operation.ts +49 -0
- package/src/index.ts +38 -43
- package/src/lib/artifacts.ts +18 -0
- package/src/lib/execution-level.ts +13 -0
- package/src/lib/mcp.ts +1 -1
- package/src/lib/prompt.ts +7 -6
- package/src/lib/token-estimate.ts +8 -0
- package/src/{pi/runtime-adapter/models.ts → models/index.ts} +64 -17
- package/src/{pi/turn/tool-recovery.ts → session/cloudflare/legacy-tool-recovery.ts} +33 -525
- package/src/{pi/session → session/cloudflare}/migration.ts +404 -14
- package/src/{pi/session → session/cloudflare}/storage.ts +8 -36
- package/src/session/index.ts +39 -0
- package/src/skills/springbrand-worker-website/index.ts +1 -1
- package/src/submission/admission.ts +286 -0
- package/src/{kernel/receipts.ts → submission/contracts.ts} +56 -2
- 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} +117 -114
- package/src/submission/steer-delivery.ts +167 -0
- package/src/{db/steer.repo.ts → submission/steer-repository.ts} +27 -5
- package/src/submission/unattended.ts +40 -0
- 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/tools/README.md +44 -0
- package/src/{pi/tool/ai-adapter.ts → tools/adapters/ai-to-pi.ts} +6 -9
- package/src/{pi/tool → tools/adapters}/declared.ts +6 -2
- package/src/{pi/tool/nested-tools.ts → tools/adapters/pi-to-ai.ts} +2 -2
- package/src/tools/assistant/ask-user.ts +317 -0
- package/src/tools/assistant/candidate.ts +15 -0
- package/src/tools/assistant/followups.ts +34 -0
- package/src/tools/assistant/index.ts +19 -0
- package/src/tools/assistant/memory.ts +73 -0
- package/src/tools/assistant/plan.ts +98 -0
- package/src/{pi/tool → tools/assistant}/time.ts +1 -1
- package/src/{pi/tool/core-host.ts → tools/codemode/adapter.ts} +50 -49
- package/src/{pi/tool/core.ts → tools/codemode/index.ts} +35 -91
- package/src/tools/codemode/workspace-connector.ts +198 -0
- package/src/tools/extension/index.ts +42 -0
- package/src/tools/index.ts +32 -0
- package/src/{pi/tool → tools/mcp}/gateway.ts +19 -19
- package/src/{pi/tool/mcp.ts → tools/mcp/index.ts} +5 -5
- package/src/tools/runtime/approval/index.ts +1 -0
- package/src/{db/approval.repo.ts → tools/runtime/approval/repository.ts} +22 -41
- package/src/tools/runtime/compiler.ts +264 -0
- package/src/tools/runtime/contracts.ts +117 -0
- package/src/tools/runtime/decision-gate.ts +337 -0
- package/src/tools/runtime/human-decisions.ts +127 -0
- package/src/tools/runtime/interaction/index.ts +1 -0
- package/src/{db/interaction.repo.ts → tools/runtime/interaction/repository.ts} +1 -7
- package/src/tools/runtime/result.ts +22 -0
- package/src/tools/runtime/unattended-error.ts +4 -0
- package/src/tools/sandbox/index.ts +228 -0
- package/src/tools/schedule/index.ts +301 -0
- package/src/tools/skill/contracts.ts +20 -0
- package/src/tools/skill/index.ts +99 -0
- package/src/tools/skill/resources.ts +322 -0
- package/src/tools/skill/scripts.ts +168 -0
- package/src/{kernel/submission-authority.ts → tools/subagent/authority.ts} +31 -31
- package/src/{pi/tool/subagent.ts → tools/subagent/index.ts} +33 -36
- package/src/{db/agent-tool.repo.ts → tools/subagent/repository.ts} +1 -1
- package/src/{kernel/subagent-runtime.ts → tools/subagent/runtime.ts} +1 -1
- package/src/tools/subagent/temporary/approvals.ts +123 -0
- package/src/{layers/orchestration/temporary-agent/core.ts → tools/subagent/temporary/coordinator.ts} +41 -49
- package/src/tools/subagent/temporary/core.ts +53 -0
- package/src/{layers/orchestration/temporary-agent → tools/subagent/temporary}/runner.ts +1 -1
- package/src/{layers/orchestration/temporary-agent → tools/subagent/temporary}/workspace.ts +1 -4
- package/src/{pi/tool/web-fetch.ts → tools/web/fetch.ts} +4 -13
- package/src/tools/web/index.ts +2 -0
- package/src/tools/web/search/api.ts +1320 -0
- package/src/tools/web/search/index.ts +2 -0
- package/src/{pi/tool/web-search/web-search.ts → tools/web/search/search.ts} +5 -7
- package/src/{pi/tool/workspace-sandbox.ts → tools/workspace/index.ts} +12 -272
- package/src/{pi/tool/workspace-revision.ts → tools/workspace/revision.ts} +2 -2
- 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/base.ts +0 -550
- package/src/pi/tool/compiler.ts +0 -483
- package/src/pi/tool/index.ts +0 -38
- package/src/pi/tool/schedule.ts +0 -261
- package/src/pi/tool/skill.ts +0 -565
- package/src/pi/tool/web-search/api.ts +0 -1318
- package/src/pi/tool/web-search/index.ts +0 -2
- package/src/pi/tool/workspace-state-connector.ts +0 -146
- 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/{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 +1 -1
- /package/src/{workspace-versioning.ts → tools/workspace/versioning.ts} +0 -0
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
type
|
|
5
|
-
type AgentEvent,
|
|
6
|
-
type AgentTool,
|
|
2
|
+
type AgentHarnessTool,
|
|
3
|
+
type HarnessEvent,
|
|
4
|
+
type Session,
|
|
7
5
|
type StreamFn,
|
|
8
6
|
} from "@earendil-works/pi-agent-core";
|
|
9
|
-
import
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
} from "@earendil-works/pi-
|
|
7
|
+
import {
|
|
8
|
+
awaitWithContext,
|
|
9
|
+
BACKGROUND_CONTEXT,
|
|
10
|
+
withAbortSignal,
|
|
11
|
+
} from "@earendil-works/pi-agent-core/harness/context";
|
|
12
|
+
import type { Api, AssistantMessage, Model, Models } from "@earendil-works/pi-ai";
|
|
14
13
|
import { z } from "zod";
|
|
15
14
|
import { assembleSubagentPrompt } from "../../../lib/prompt";
|
|
16
|
-
import type { AgentType } from "../../../
|
|
17
|
-
import { structuredOutputStream } from "../../../
|
|
15
|
+
import type { AgentType } from "../../../tools/subagent/types/contract";
|
|
16
|
+
import { structuredOutputStream } from "../../../models";
|
|
17
|
+
import {
|
|
18
|
+
openSessionHarness,
|
|
19
|
+
type SessionHarness,
|
|
20
|
+
type SessionHarnessOptions,
|
|
21
|
+
} from "../../../harness";
|
|
18
22
|
|
|
19
23
|
const RECURSIVE_TOOLS = new Set([
|
|
20
24
|
"run_agent",
|
|
@@ -30,17 +34,21 @@ export interface CloudflareSubAgentRun {
|
|
|
30
34
|
input: unknown;
|
|
31
35
|
model: Model<Api>;
|
|
32
36
|
streamFn: StreamFn;
|
|
33
|
-
|
|
37
|
+
models?: Models;
|
|
38
|
+
/** A fresh Pi Session facade is required after each Harness release. */
|
|
39
|
+
openSession(): Session | Promise<Session>;
|
|
40
|
+
operationId?: string;
|
|
41
|
+
/** Tools compiled by the package's single Harness adapter. */
|
|
42
|
+
tools?: AgentHarnessTool<object | undefined>[];
|
|
34
43
|
signal?: AbortSignal;
|
|
35
44
|
timeoutMs?: number;
|
|
36
|
-
|
|
45
|
+
/** Native operation events for projections that must survive model retries. */
|
|
46
|
+
onEvent?: (event: HarnessEvent, signal: AbortSignal) => void | Promise<void>;
|
|
37
47
|
}
|
|
38
48
|
|
|
39
49
|
function textOf(message: AssistantMessage): string {
|
|
40
|
-
return message.content
|
|
41
|
-
.
|
|
42
|
-
.join("")
|
|
43
|
-
.trim();
|
|
50
|
+
return message.content.flatMap((part) => part.type === "text" ? [part.text] : [])
|
|
51
|
+
.join("").trim();
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
function structuredText(text: string): string {
|
|
@@ -49,30 +57,22 @@ function structuredText(text: string): string {
|
|
|
49
57
|
|
|
50
58
|
function toolErrorText(result: unknown): string {
|
|
51
59
|
if (
|
|
52
|
-
typeof result === "object" &&
|
|
53
|
-
result !== null &&
|
|
60
|
+
typeof result === "object" && result !== null &&
|
|
54
61
|
Array.isArray((result as { content?: unknown }).content)
|
|
55
62
|
) {
|
|
56
|
-
const text = (result as { content: unknown[] }).content
|
|
57
|
-
|
|
58
|
-
typeof part === "object" &&
|
|
59
|
-
part !== null &&
|
|
63
|
+
const text = (result as { content: unknown[] }).content.flatMap((part) =>
|
|
64
|
+
typeof part === "object" && part !== null &&
|
|
60
65
|
(part as { type?: unknown }).type === "text" &&
|
|
61
66
|
typeof (part as { text?: unknown }).text === "string"
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
.join("")
|
|
66
|
-
.trim();
|
|
67
|
+
? [(part as { text: string }).text]
|
|
68
|
+
: []
|
|
69
|
+
).join("").trim();
|
|
67
70
|
if (text) return text;
|
|
68
71
|
}
|
|
69
72
|
return "tool execution failed";
|
|
70
73
|
}
|
|
71
74
|
|
|
72
|
-
function taskPrompt(
|
|
73
|
-
input: unknown,
|
|
74
|
-
outputSchema?: Record<string, unknown>,
|
|
75
|
-
): string {
|
|
75
|
+
function taskPrompt(input: unknown, outputSchema?: Record<string, unknown>): string {
|
|
76
76
|
return [
|
|
77
77
|
"Complete this bounded task.",
|
|
78
78
|
`Input:\n${JSON.stringify(input)}`,
|
|
@@ -82,112 +82,166 @@ function taskPrompt(
|
|
|
82
82
|
].join("\n\n");
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
|
|
86
|
-
run
|
|
87
|
-
|
|
85
|
+
function harnessModels(run: CloudflareSubAgentRun, streamFn: StreamFn): Models {
|
|
86
|
+
const base = run.models;
|
|
87
|
+
return new Proxy(base ?? {} as Models, {
|
|
88
|
+
get(target, property) {
|
|
89
|
+
if (property === "getModel") {
|
|
90
|
+
return (provider: string, modelId: string) =>
|
|
91
|
+
provider === run.model.provider && modelId === run.model.id
|
|
92
|
+
? run.model
|
|
93
|
+
: base?.getModel(provider, modelId);
|
|
94
|
+
}
|
|
95
|
+
if (property === "streamSimple") return streamFn;
|
|
96
|
+
const value = Reflect.get(target, property, target);
|
|
97
|
+
return typeof value === "function" ? value.bind(target) : value;
|
|
98
|
+
},
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** Execute one bounded mechanism SubAgent through the official AgentHarness. */
|
|
103
|
+
export async function runCloudflareSubAgent(run: CloudflareSubAgentRun): Promise<unknown> {
|
|
88
104
|
const input = run.type.inputSchema.safeParse(run.input);
|
|
89
105
|
if (!input.success) {
|
|
90
|
-
throw new Error(
|
|
91
|
-
`SubAgent input failed schema validation: ${input.error.message}`,
|
|
92
|
-
);
|
|
106
|
+
throw new Error(`SubAgent input failed schema validation: ${input.error.message}`);
|
|
93
107
|
}
|
|
94
|
-
|
|
108
|
+
const declaredTools = run.tools ?? [];
|
|
109
|
+
for (const tool of declaredTools) {
|
|
95
110
|
if (RECURSIVE_TOOLS.has(tool.name)) {
|
|
96
111
|
throw new Error(`SubAgent cannot use recursive tool "${tool.name}"`);
|
|
97
112
|
}
|
|
98
113
|
}
|
|
99
114
|
if (run.signal?.aborted) throw new Error("SubAgent run aborted");
|
|
100
|
-
const { $schema: _, ...outputSchema } = z.toJSONSchema(run.type.outputSchema);
|
|
101
|
-
const constrainedStream = structuredOutputStream(
|
|
102
|
-
run.streamFn,
|
|
103
|
-
run.model,
|
|
104
|
-
outputSchema,
|
|
105
|
-
);
|
|
106
115
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
116
|
+
const { $schema: _, ...outputSchema } = z.toJSONSchema(run.type.outputSchema);
|
|
117
|
+
const structuredStream = structuredOutputStream(run.streamFn, run.model, outputSchema);
|
|
118
|
+
const streamFn = structuredStream ?? run.streamFn;
|
|
119
|
+
const operationId = run.operationId ?? crypto.randomUUID();
|
|
110
120
|
const controller = new AbortController();
|
|
111
|
-
const signal = run.signal
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
121
|
+
const signal = run.signal
|
|
122
|
+
? AbortSignal.any([run.signal, controller.signal])
|
|
123
|
+
: controller.signal;
|
|
124
|
+
const context = withAbortSignal(signal, BACKGROUND_CONTEXT);
|
|
125
|
+
const options = {
|
|
126
|
+
models: harnessModels(run, streamFn),
|
|
127
|
+
model: run.model,
|
|
128
|
+
thinkingLevel: run.type.model === "main" ? "medium" : "off",
|
|
129
|
+
systemPrompt: assembleSubagentPrompt(run.type.persona),
|
|
130
|
+
tools: declaredTools,
|
|
131
|
+
streamOptions: { maxRetries: 0 },
|
|
132
|
+
toolExecution: "parallel",
|
|
133
|
+
compaction: { enabled: false, reserveTokens: 1, keepRecentTokens: 1 },
|
|
134
|
+
} satisfies Omit<SessionHarnessOptions<object | undefined>, "session">;
|
|
135
|
+
let runtime: SessionHarness<object | undefined> | undefined;
|
|
117
136
|
let toolFailure: { name: string; error: string } | undefined;
|
|
118
|
-
const emit = async (event: AgentEvent) => {
|
|
119
|
-
if (event.type === "tool_execution_end" && event.isError) {
|
|
120
|
-
toolFailure ??= {
|
|
121
|
-
name: event.toolName,
|
|
122
|
-
error: toolErrorText(event.result),
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
await run.onEvent?.(event, signal);
|
|
126
|
-
};
|
|
127
137
|
|
|
128
138
|
let timedOut = false;
|
|
129
139
|
const timeout = setTimeout(() => {
|
|
130
140
|
timedOut = true;
|
|
131
|
-
controller.abort();
|
|
141
|
+
controller.abort(new Error("SubAgent run timed out"));
|
|
132
142
|
}, run.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
133
|
-
|
|
134
|
-
let newMessages: AgentMessage[];
|
|
135
143
|
try {
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
144
|
+
let settled: Awaited<ReturnType<SessionHarness<object | undefined>["run"]>>;
|
|
145
|
+
while (true) {
|
|
146
|
+
signal.throwIfAborted();
|
|
147
|
+
if (!runtime) {
|
|
148
|
+
runtime = await openSessionHarness({
|
|
149
|
+
...options,
|
|
150
|
+
session: await run.openSession(),
|
|
151
|
+
}, context);
|
|
152
|
+
if (runtime.openOperation && runtime.openOperation.operationId !== operationId) {
|
|
153
|
+
throw new Error("SubAgent Session has an unrelated open operation");
|
|
154
|
+
}
|
|
155
|
+
const harness = runtime.harness;
|
|
156
|
+
const onEvent = run.onEvent;
|
|
157
|
+
if (onEvent) {
|
|
158
|
+
for (const type of ["run_start", "run_resume", "run_end", "turn_start", "message_start", "message_update", "message_end", "tool_start", "tool_update", "tool_end"] as const) {
|
|
159
|
+
harness.events.on(type, (event) => onEvent(event, signal));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
harness.events.on("tool_end", (event) => {
|
|
163
|
+
if (event.isError) {
|
|
164
|
+
toolFailure ??= { name: event.toolName, error: toolErrorText(event.result) };
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
const result = await runtime.run(
|
|
169
|
+
!runtime.openOperation
|
|
170
|
+
? {
|
|
171
|
+
kind: "prompt",
|
|
172
|
+
operationId,
|
|
173
|
+
prompt: taskPrompt(input.data, structuredStream ? undefined : outputSchema),
|
|
174
|
+
}
|
|
175
|
+
: undefined,
|
|
176
|
+
context,
|
|
177
|
+
{ waitForRetry: true, pollDeferred: true },
|
|
178
|
+
);
|
|
179
|
+
if (result.kind === "released") {
|
|
180
|
+
await runtime.close(BACKGROUND_CONTEXT);
|
|
181
|
+
runtime = undefined;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (result.kind === "waiting") {
|
|
185
|
+
const delay = result.reason === "deferred"
|
|
186
|
+
? result.deferred.pollAfterMs ?? 1_000
|
|
187
|
+
: Math.max(0, result.notBefore - Date.now());
|
|
188
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
189
|
+
try {
|
|
190
|
+
await awaitWithContext(new Promise<void>((resolve) => {
|
|
191
|
+
timer = setTimeout(resolve, delay);
|
|
192
|
+
}), context);
|
|
193
|
+
} finally {
|
|
194
|
+
clearTimeout(timer);
|
|
195
|
+
}
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
settled = result;
|
|
199
|
+
break;
|
|
200
|
+
}
|
|
201
|
+
if (timedOut) throw new Error("SubAgent run timed out");
|
|
202
|
+
if (run.signal?.aborted || settled.outcome.status === "aborted") {
|
|
203
|
+
throw new Error("SubAgent run aborted");
|
|
204
|
+
}
|
|
205
|
+
if (settled.outcome.status === "failed") {
|
|
163
206
|
throw new Error(
|
|
164
|
-
|
|
207
|
+
toolFailure
|
|
208
|
+
? `SubAgent tool "${toolFailure.name}" failed: ${toolFailure.error}`
|
|
209
|
+
: `SubAgent model failed: ${settled.outcome.error?.message ?? "unknown provider error"}`,
|
|
165
210
|
);
|
|
166
211
|
}
|
|
167
|
-
|
|
168
|
-
`SubAgent
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
)
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
212
|
+
if (toolFailure) {
|
|
213
|
+
throw new Error(`SubAgent tool "${toolFailure.name}" failed: ${toolFailure.error}`);
|
|
214
|
+
}
|
|
215
|
+
const assistant = (await runtime.lane.findEntries(
|
|
216
|
+
{ type: "message", order: "newestFirst" },
|
|
217
|
+
context,
|
|
218
|
+
)).find((entry) => entry.type === "message" && entry.message.role === "assistant");
|
|
219
|
+
if (!assistant || assistant.type !== "message" || assistant.message.role !== "assistant") {
|
|
220
|
+
throw new Error("SubAgent produced no structured output");
|
|
221
|
+
}
|
|
222
|
+
const text = textOf(assistant.message);
|
|
223
|
+
if (!text) throw new Error("SubAgent produced no structured output");
|
|
224
|
+
let output: unknown;
|
|
225
|
+
try {
|
|
226
|
+
output = JSON.parse(structuredText(text));
|
|
227
|
+
} catch {
|
|
228
|
+
throw new Error("SubAgent output is not valid JSON");
|
|
229
|
+
}
|
|
230
|
+
const result = run.type.outputSchema.safeParse(output);
|
|
231
|
+
if (!result.success) {
|
|
232
|
+
throw new Error(`SubAgent output failed schema validation: ${result.error.message}`);
|
|
233
|
+
}
|
|
234
|
+
return result.data;
|
|
235
|
+
} catch (error) {
|
|
236
|
+
if (timedOut) throw new Error("SubAgent run timed out");
|
|
237
|
+
if (run.signal?.aborted) throw new Error("SubAgent run aborted");
|
|
238
|
+
throw error;
|
|
239
|
+
} finally {
|
|
240
|
+
clearTimeout(timeout);
|
|
241
|
+
if (signal.aborted && runtime?.openOperation) {
|
|
242
|
+
await runtime.requestAbort(operationId, BACKGROUND_CONTEXT).catch(() => undefined);
|
|
243
|
+
await runtime.run(undefined, BACKGROUND_CONTEXT).catch(() => undefined);
|
|
244
|
+
}
|
|
245
|
+
await runtime?.close(BACKGROUND_CONTEXT).catch(() => undefined);
|
|
191
246
|
}
|
|
192
|
-
return result.data;
|
|
193
247
|
}
|
|
@@ -9,18 +9,19 @@ import {
|
|
|
9
9
|
} from "@cloudflare/shell";
|
|
10
10
|
import { stateToolsFromBackend } from "@cloudflare/shell/workers";
|
|
11
11
|
import type {
|
|
12
|
+
AgentHarnessTool,
|
|
12
13
|
AgentTool,
|
|
13
14
|
AgentToolResult,
|
|
14
15
|
} from "@earendil-works/pi-agent-core";
|
|
15
16
|
import { Type } from "@earendil-works/pi-ai";
|
|
16
|
-
import type { WorkspacePort } from "../../../
|
|
17
|
-
import type { AgentType } from "../../../
|
|
17
|
+
import type { WorkspacePort } from "../../../config/ports";
|
|
18
|
+
import type { AgentType } from "../../../tools/subagent/types/contract";
|
|
18
19
|
import {
|
|
19
|
-
|
|
20
|
-
type
|
|
20
|
+
compileHarnessTools,
|
|
21
|
+
type CompileHarnessToolsOptions,
|
|
21
22
|
type PiToolCandidate,
|
|
22
|
-
} from "../../../
|
|
23
|
-
import { workspacePiToolCandidates } from "../../../
|
|
23
|
+
} from "../../../tools";
|
|
24
|
+
import { workspacePiToolCandidates } from "../../../tools/workspace";
|
|
24
25
|
|
|
25
26
|
const READONLY_WORKSPACE_TOOLS = new Set([
|
|
26
27
|
"read",
|
|
@@ -183,14 +184,12 @@ function fetchCandidate(options: {
|
|
|
183
184
|
}
|
|
184
185
|
|
|
185
186
|
/**
|
|
186
|
-
* SubAgent
|
|
187
|
-
* (requiresPiToolApproval → park → 等用户裁决),而子 Agent 直接调用 `runAgentLoopContinue`
|
|
188
|
-
* 直接跑,parked 的请求没有任何界面能落地、也没有人能批。
|
|
187
|
+
* SubAgent 链路上没有人工审批通道,因此只允许 safe Tool 进入它的 Harness。
|
|
189
188
|
*
|
|
190
189
|
* 只有 `safe` Tool 可以挂到这条没有审批通道的链路;更高档位必须在装配时失败关闭。
|
|
191
190
|
*
|
|
192
191
|
* 所以把这个不变量钉在装配处:要的是「装不上」,不是「跑起来才炸」。而且这里是唯一还能
|
|
193
|
-
*
|
|
192
|
+
* 检查必须在 Candidate 被编译为 AgentHarnessTool 之前完成。
|
|
194
193
|
*/
|
|
195
194
|
function assertSafeExecutionLevel(
|
|
196
195
|
candidates: readonly PiToolCandidate[],
|
|
@@ -221,8 +220,8 @@ export function createCloudflareSubAgentTools(options: {
|
|
|
221
220
|
workspace: WorkspacePort;
|
|
222
221
|
loader: WorkerLoader;
|
|
223
222
|
outbound: Fetcher;
|
|
224
|
-
settle:
|
|
225
|
-
}):
|
|
223
|
+
settle: NonNullable<CompileHarnessToolsOptions["onSettlement"]>;
|
|
224
|
+
}): AgentHarnessTool<object | undefined>[] {
|
|
226
225
|
const workspace = workspacePiToolCandidates(options.workspace).filter(
|
|
227
226
|
(candidate) =>
|
|
228
227
|
options.profile === "capable" ||
|
|
@@ -245,7 +244,7 @@ export function createCloudflareSubAgentTools(options: {
|
|
|
245
244
|
);
|
|
246
245
|
assertSafeExecutionLevel(mounted);
|
|
247
246
|
|
|
248
|
-
return
|
|
249
|
-
|
|
247
|
+
return compileHarnessTools(mounted, {
|
|
248
|
+
onSettlement: options.settle,
|
|
250
249
|
});
|
|
251
250
|
}
|
|
@@ -8,17 +8,17 @@ import type {
|
|
|
8
8
|
RuntimePlatformPort,
|
|
9
9
|
RuntimeSubagentPort,
|
|
10
10
|
WorkspacePort,
|
|
11
|
-
} from "../../../
|
|
12
|
-
import { decodeSubagentCompletion } from "../../../
|
|
13
|
-
import type { RuntimeDegradation } from "../../../
|
|
14
|
-
import type { RuntimeMemoryProfile } from "../../../
|
|
15
|
-
import { AGENT_TYPES } from "../../../
|
|
16
|
-
import type { RuntimeAgentConfigContext } from "../../../
|
|
11
|
+
} from "../../../config/ports";
|
|
12
|
+
import { decodeSubagentCompletion } from "../../../tools/subagent/runtime";
|
|
13
|
+
import type { RuntimeDegradation } from "../../../config/degradation";
|
|
14
|
+
import type { RuntimeMemoryProfile } from "../../../config/profile";
|
|
15
|
+
import { AGENT_TYPES } from "../../../tools/subagent/types/registry";
|
|
16
|
+
import type { RuntimeAgentConfigContext } from "../../../config/agent-context";
|
|
17
17
|
import {
|
|
18
18
|
createBrowserExecutionFactory,
|
|
19
19
|
createWorkspaceCodeExecutionFactory,
|
|
20
20
|
type RuntimeBrowserBinding,
|
|
21
|
-
} from "../../../
|
|
21
|
+
} from "../../../tools/codemode/adapter";
|
|
22
22
|
import {
|
|
23
23
|
createCloudflareSandboxAdapter,
|
|
24
24
|
type SandboxAdmission,
|
|
@@ -4,18 +4,16 @@ import type {
|
|
|
4
4
|
RuntimeSchedulePort,
|
|
5
5
|
RuntimeSubagentPort,
|
|
6
6
|
WorkspacePort,
|
|
7
|
-
} from "../../../
|
|
8
|
-
import type { RuntimeDegradation } from "../../../
|
|
9
|
-
import type { RuntimeMemoryProfile } from "../../../
|
|
10
|
-
import type { WorkspaceRevisionRestorePort } from "../../../workspace
|
|
11
|
-
import { schedulePiToolCandidates } from "../../../
|
|
12
|
-
import { workspaceRevisionPiToolCandidate } from "../../../
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
} from "../../../
|
|
16
|
-
import type {
|
|
17
|
-
import type { ToolAssemblyResult } from "../../../runtime-definition";
|
|
18
|
-
import type { PiToolCandidate } from "../../../pi/tool/compiler";
|
|
7
|
+
} from "../../../config/ports";
|
|
8
|
+
import type { RuntimeDegradation } from "../../../config/degradation";
|
|
9
|
+
import type { RuntimeMemoryProfile } from "../../../config/profile";
|
|
10
|
+
import type { WorkspaceRevisionRestorePort } from "../../../tools/workspace/versioning";
|
|
11
|
+
import { schedulePiToolCandidates } from "../../../tools/schedule";
|
|
12
|
+
import { workspaceRevisionPiToolCandidate } from "../../../tools/workspace/revision";
|
|
13
|
+
import { sandboxPiToolCandidates } from "../../../tools/sandbox";
|
|
14
|
+
import type { RuntimeCodeExecutionFactory } from "../../../config/ports";
|
|
15
|
+
import type { ToolAssemblyResult } from "../../../config/definition";
|
|
16
|
+
import type { PiToolCandidate } from "../../../tools/runtime/contracts";
|
|
19
17
|
|
|
20
18
|
export function assembleUniversalAgentTools(options: {
|
|
21
19
|
hostTools?: readonly PiToolCandidate[];
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import * as git from "isomorphic-git";
|
|
2
|
-
import type { WorkspacePort } from "../../../
|
|
2
|
+
import type { WorkspacePort } from "../../../config/ports";
|
|
3
3
|
import {
|
|
4
4
|
WorkspaceRevisionNotFoundError,
|
|
5
5
|
type WorkspaceRevision,
|
|
6
6
|
type WorkspaceRevisionManifest,
|
|
7
7
|
type WorkspaceVersionPort,
|
|
8
|
-
} from "../../../workspace
|
|
8
|
+
} from "../../../tools/workspace/versioning";
|
|
9
9
|
import { createWorkspaceGitFs } from "./git-fs";
|
|
10
10
|
|
|
11
11
|
const PAGE_SIZE = 256;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { connectConfiguredMcpServers } from "../lib/mcp";
|
|
2
|
+
import { sha256Hex } from "../lib/artifacts";
|
|
3
|
+
import type { RuntimeCandidate, RuntimeSnapshot } from "./assembler";
|
|
4
|
+
import type { HarnessRuntimeConfig } from "./harness-runtime";
|
|
5
|
+
import type { PreparedPiRuntime, PreparePiRuntimeOptions } from "./pinning";
|
|
6
|
+
import type { RuntimeGatewaySession, RuntimeSubmissionAdmissionHook } from "./ports";
|
|
7
|
+
import { withRuntimeLoadTimeout } from "./runtime-load";
|
|
8
|
+
import type { RuntimeLoadPhase } from "../conversation/state";
|
|
9
|
+
|
|
10
|
+
export interface ActiveRuntime {
|
|
11
|
+
readonly snapshot: RuntimeSnapshot;
|
|
12
|
+
readonly revision: string;
|
|
13
|
+
readonly runtimeKey: string;
|
|
14
|
+
readonly pi: PreparedPiRuntime;
|
|
15
|
+
readonly gatewaySession?: RuntimeGatewaySession;
|
|
16
|
+
readonly admission?: RuntimeSubmissionAdmissionHook;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function closeRuntimeGatewaySession(
|
|
20
|
+
session: RuntimeGatewaySession | undefined,
|
|
21
|
+
step: string,
|
|
22
|
+
): Promise<void> {
|
|
23
|
+
if (!session) return;
|
|
24
|
+
await withRuntimeLoadTimeout(step, () => session.close()).catch(
|
|
25
|
+
() => undefined,
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface RuntimeActivationOptions {
|
|
30
|
+
readonly previous?: ActiveRuntime;
|
|
31
|
+
readonly pi: HarnessRuntimeConfig;
|
|
32
|
+
readonly mcpHost: PreparePiRuntimeOptions["mcpHost"] & Parameters<typeof connectConfiguredMcpServers>[0];
|
|
33
|
+
readonly sessionId: string;
|
|
34
|
+
readonly createExtensionHostBinding: PreparePiRuntimeOptions["createExtensionHostBinding"];
|
|
35
|
+
advance(phase: RuntimeLoadPhase): void;
|
|
36
|
+
isExecuting(): boolean;
|
|
37
|
+
commit(runtime: ActiveRuntime): void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Prepare optional connections and atomically install a validated Runtime candidate. */
|
|
41
|
+
export async function activateRuntimeCandidate(
|
|
42
|
+
candidate: RuntimeCandidate,
|
|
43
|
+
options: RuntimeActivationOptions,
|
|
44
|
+
): Promise<void> {
|
|
45
|
+
const previous = options.previous;
|
|
46
|
+
const candidateSnapshot = candidate.snapshot;
|
|
47
|
+
options.advance("assembly");
|
|
48
|
+
options.advance("mcp");
|
|
49
|
+
await connectConfiguredMcpServers(
|
|
50
|
+
options.mcpHost,
|
|
51
|
+
candidateSnapshot.profile.mcpServers.filter(server => !server.hostManaged),
|
|
52
|
+
);
|
|
53
|
+
const gatewayDegradations = [];
|
|
54
|
+
let gatewaySession: RuntimeGatewaySession | undefined;
|
|
55
|
+
if (candidateSnapshot.bindings.gateway) {
|
|
56
|
+
try {
|
|
57
|
+
gatewaySession = await withRuntimeLoadTimeout(
|
|
58
|
+
"gateway.open",
|
|
59
|
+
() => candidateSnapshot.bindings.gateway!.open(
|
|
60
|
+
`${options.sessionId}:${crypto.randomUUID()}`,
|
|
61
|
+
),
|
|
62
|
+
{
|
|
63
|
+
onLateResult: (session) =>
|
|
64
|
+
closeRuntimeGatewaySession(session, "gateway.close.late"),
|
|
65
|
+
},
|
|
66
|
+
);
|
|
67
|
+
} catch {
|
|
68
|
+
gatewayDegradations.push({
|
|
69
|
+
capability: "connector" as const,
|
|
70
|
+
reason: "unavailable" as const,
|
|
71
|
+
detail: "Connector Gateway MCP",
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
options.advance("pi");
|
|
77
|
+
let prepared: PreparedPiRuntime;
|
|
78
|
+
try {
|
|
79
|
+
prepared = await options.pi.prepare({
|
|
80
|
+
snapshot: candidateSnapshot,
|
|
81
|
+
mcpHost: options.mcpHost,
|
|
82
|
+
gatewaySession,
|
|
83
|
+
additionalDegradations: gatewayDegradations,
|
|
84
|
+
createExtensionHostBinding: (permissions, ownContextLabels) =>
|
|
85
|
+
options.createExtensionHostBinding(
|
|
86
|
+
permissions,
|
|
87
|
+
ownContextLabels,
|
|
88
|
+
),
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
await closeRuntimeGatewaySession(gatewaySession, "gateway.close.failed");
|
|
92
|
+
throw error;
|
|
93
|
+
}
|
|
94
|
+
const revision = await sha256Hex(prepared.revisionDescriptor);
|
|
95
|
+
const contested = Boolean(previous) &&
|
|
96
|
+
revision !== previous?.revision &&
|
|
97
|
+
options.isExecuting();
|
|
98
|
+
if (contested) {
|
|
99
|
+
await closeRuntimeGatewaySession(gatewaySession, "gateway.close.contested");
|
|
100
|
+
throw new Error(
|
|
101
|
+
"Cannot reload Runtime while a revision-pinned SpringBrand Turn is active",
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
const next = Object.freeze({
|
|
105
|
+
snapshot: candidateSnapshot,
|
|
106
|
+
revision,
|
|
107
|
+
runtimeKey: candidate.runtimeKey ?? revision,
|
|
108
|
+
pi: prepared,
|
|
109
|
+
...(gatewaySession ? { gatewaySession } : {}),
|
|
110
|
+
...(candidate.admission
|
|
111
|
+
? { admission: candidate.admission }
|
|
112
|
+
: {}),
|
|
113
|
+
}) satisfies ActiveRuntime;
|
|
114
|
+
let activated = false;
|
|
115
|
+
const previousGatewaySession = previous?.gatewaySession;
|
|
116
|
+
try {
|
|
117
|
+
for (const [index, guard] of candidate.commitGuards.entries()) {
|
|
118
|
+
await withRuntimeLoadTimeout(`commit-guard:${index}`, guard);
|
|
119
|
+
}
|
|
120
|
+
options.pi.activate(candidateSnapshot);
|
|
121
|
+
activated = true;
|
|
122
|
+
options.commit(next);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
if (activated && previous) options.pi.activate(previous.snapshot);
|
|
125
|
+
await closeRuntimeGatewaySession(gatewaySession, "gateway.close.aborted");
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
await closeRuntimeGatewaySession(
|
|
129
|
+
previousGatewaySession,
|
|
130
|
+
"gateway.close.previous",
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
const degradations = prepared.degradations;
|
|
134
|
+
if (degradations.length > 0) {
|
|
135
|
+
console.warn(
|
|
136
|
+
"[runtime-load:degraded]",
|
|
137
|
+
JSON.stringify({ degradations }),
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|