@mono-agent/agent-runtime 0.20.11 → 0.21.0
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/ARCHITECTURE.md +50 -11
- package/MIGRATION.md +288 -26
- package/README.md +352 -477
- package/package.json +13 -44
- package/src/agent/tool-bloat.js +145 -9
- package/src/agent/tools/agent-tool.js +108 -9
- package/src/agent/tools/bash.js +11 -26
- package/src/agent/tools/codex-subscription-search.js +123 -29
- package/src/agent/tools/exec.js +10 -2
- package/src/agent/tools/index.js +7 -0
- package/src/agent/tools/monitor.js +149 -0
- package/src/agent/tools/pi-bridge.js +123 -19
- package/src/agent/tools/shared/bash-environment.js +31 -0
- package/src/agent/tools/shared/monitors.js +293 -0
- package/src/agent/tools/shared/path-resolver.js +25 -6
- package/src/agent/tools/shared/process-jobs.js +6 -1
- package/src/agent/tools/shared/process-runner.js +26 -6
- package/src/agent/tools/shared/tool-context.js +8 -0
- package/src/agent/tools/web-access-interstitial.js +70 -0
- package/src/agent/tools/web-browser-render.js +83 -58
- package/src/agent/tools/web-controller.js +112 -21
- package/src/agent/tools/web-document-extractor.js +379 -0
- package/src/agent/tools/web-fetch.js +271 -243
- package/src/agent/tools/web-request.js +65 -0
- package/src/agent/tools/web-search-output.js +165 -0
- package/src/agent/tools/web-search-state.js +75 -0
- package/src/agent/tools/web-search.js +532 -71
- package/src/ai/cost.js +13 -68
- package/src/ai/failure.js +3 -3
- package/src/ai/index.js +5 -17
- package/src/ai/observer.js +8 -0
- package/src/ai/pi-interop.js +221 -1
- package/src/ai/pi-oauth-compat.js +1 -1
- package/src/ai/provider-check.js +131 -0
- package/src/ai/providers/codex/app-server-client.js +592 -0
- package/src/ai/providers/pi-models.js +18 -10
- package/src/ai/providers/pi-native/compaction-driver.js +94 -42
- package/src/ai/providers/pi-native/compaction-summary.js +140 -0
- package/src/ai/providers/pi-native/harness-adapter.js +376 -0
- package/src/ai/providers/pi-native/prompt-cache-diagnostics.js +103 -0
- package/src/ai/providers/pi-native/provider-attribution.js +102 -0
- package/src/ai/providers/pi-native/result-builder.js +38 -14
- package/src/ai/providers/pi-native/session-lifecycle.js +253 -55
- package/src/ai/providers/pi-native/stream-subscriber.js +52 -6
- package/src/ai/providers/pi-native/terminal-recovery.js +40 -0
- package/src/ai/providers/pi-native/turn-runner.js +279 -28
- package/src/ai/providers/pi-native.js +206 -61
- package/src/ai/runtime/capabilities.js +11 -56
- package/src/ai/runtime/live-input-events.js +250 -54
- package/src/ai/runtime/model-refs.js +118 -153
- package/src/ai/runtime/registry.js +22 -56
- package/src/ai/runtime/router.js +76 -417
- package/src/ai/runtime/session-liveness.js +3 -4
- package/src/ai/runtime/sessions.js +4 -5
- package/src/ai/runtime/tool-policy.js +0 -2
- package/src/ai/tool-lifecycle.js +32 -18
- package/src/ai/types.js +37 -112
- package/src/index.js +0 -6
- package/src/runtime.js +29 -16
- package/types/agent/tool-bloat.d.ts +1 -1
- package/types/agent/tools/agent-tool.d.ts +4 -2
- package/types/agent/tools/bash.d.ts +5 -3
- package/types/agent/tools/codex-subscription-search.d.ts +7 -3
- package/types/agent/tools/exec.d.ts +5 -3
- package/types/agent/tools/index.d.ts +1 -0
- package/types/agent/tools/monitor.d.ts +47 -0
- package/types/agent/tools/pi-bridge.d.ts +7 -4
- package/types/agent/tools/shared/bash-environment.d.ts +4 -0
- package/types/agent/tools/shared/monitors.d.ts +98 -0
- package/types/agent/tools/shared/process-jobs.d.ts +5 -1
- package/types/agent/tools/shared/process-runner.d.ts +14 -4
- package/types/agent/tools/shared/tool-context.d.ts +2 -0
- package/types/agent/tools/web-access-interstitial.d.ts +23 -0
- package/types/agent/tools/web-browser-render.d.ts +4 -1
- package/types/agent/tools/web-controller.d.ts +4 -2
- package/types/agent/tools/web-document-extractor.d.ts +27 -0
- package/types/agent/tools/web-fetch.d.ts +19 -24
- package/types/agent/tools/web-request.d.ts +20 -0
- package/types/agent/tools/web-search-output.d.ts +31 -0
- package/types/agent/tools/web-search-state.d.ts +21 -0
- package/types/agent/tools/web-search.d.ts +10 -45
- package/types/ai/cost.d.ts +1 -2
- package/types/ai/index.d.ts +2 -4
- package/types/ai/observer.d.ts +6 -0
- package/types/ai/pi-interop.d.ts +81 -0
- package/types/ai/provider-check.d.ts +53 -0
- package/types/ai/providers/codex/app-server-client.d.ts +37 -0
- package/types/ai/providers/pi-native/compaction-driver.d.ts +2 -1
- package/types/ai/providers/pi-native/compaction-summary.d.ts +19 -0
- package/types/ai/providers/pi-native/harness-adapter.d.ts +58 -0
- package/types/ai/providers/pi-native/prompt-cache-diagnostics.d.ts +3 -0
- package/types/ai/providers/pi-native/provider-attribution.d.ts +26 -0
- package/types/ai/providers/pi-native/result-builder.d.ts +14 -4
- package/types/ai/providers/pi-native/session-lifecycle.d.ts +25 -6
- package/types/ai/providers/pi-native/stream-subscriber.d.ts +2 -2
- package/types/ai/providers/pi-native/terminal-recovery.d.ts +2 -0
- package/types/ai/providers/pi-native/turn-runner.d.ts +68 -10
- package/types/ai/providers/pi-native.d.ts +21 -4
- package/types/ai/runtime/capabilities.d.ts +21 -70
- package/types/ai/runtime/live-input-events.d.ts +32 -8
- package/types/ai/runtime/model-refs.d.ts +0 -24
- package/types/ai/runtime/router.d.ts +3 -10
- package/types/ai/runtime/tool-policy.d.ts +0 -2
- package/types/ai/tool-lifecycle.d.ts +4 -3
- package/types/ai/types.d.ts +162 -256
- package/types/index.d.ts +0 -1
- package/src/ai/providers/acp-client.js +0 -1149
- package/src/ai/providers/acp-privacy.js +0 -124
- package/src/ai/providers/acp-public.js +0 -21
- package/src/ai/providers/acp-session-tokens.js +0 -282
- package/src/ai/providers/acp-transport.js +0 -356
- package/src/ai/providers/acp.js +0 -543
- package/src/ai/providers/claude-cli.js +0 -883
- package/src/ai/providers/claude-sandbox.js +0 -71
- package/src/ai/providers/claude-sdk-discovery-worker.js +0 -53
- package/src/ai/providers/claude-sdk-discovery.js +0 -352
- package/src/ai/providers/claude-sdk.js +0 -1127
- package/src/ai/providers/claude-subagent-activity.js +0 -719
- package/src/ai/providers/claude-subagents.js +0 -88
- package/src/ai/providers/codex-app.js +0 -2946
- package/src/ai/providers/opencode-app.js +0 -1109
- package/src/ai/providers/opencode-discovery.js +0 -39
- package/src/ai/providers/opencode-server.js +0 -508
- package/src/ai/runtime/context-windows.js +0 -46
- package/src/ai/runtime/fast-mode.js +0 -8
- package/src/ai/streaming/codex-events.js +0 -146
- package/src/ai/streaming/opencode-events.js +0 -59
- package/types/ai/providers/acp-client.d.ts +0 -227
- package/types/ai/providers/acp-privacy.d.ts +0 -25
- package/types/ai/providers/acp-public.d.ts +0 -7
- package/types/ai/providers/acp-session-tokens.d.ts +0 -41
- package/types/ai/providers/acp-transport.d.ts +0 -45
- package/types/ai/providers/acp.d.ts +0 -93
- package/types/ai/providers/claude-cli.d.ts +0 -305
- package/types/ai/providers/claude-sandbox.d.ts +0 -79
- package/types/ai/providers/claude-sdk-discovery-worker.d.ts +0 -1
- package/types/ai/providers/claude-sdk-discovery.d.ts +0 -97
- package/types/ai/providers/claude-sdk.d.ts +0 -138
- package/types/ai/providers/claude-subagent-activity.d.ts +0 -53
- package/types/ai/providers/claude-subagents.d.ts +0 -18
- package/types/ai/providers/codex-app.d.ts +0 -151
- package/types/ai/providers/opencode-app.d.ts +0 -96
- package/types/ai/providers/opencode-discovery.d.ts +0 -4
- package/types/ai/providers/opencode-server.d.ts +0 -20
- package/types/ai/runtime/context-windows.d.ts +0 -9
- package/types/ai/runtime/fast-mode.d.ts +0 -2
- package/types/ai/streaming/codex-events.d.ts +0 -40
- package/types/ai/streaming/opencode-events.d.ts +0 -42
|
@@ -32,12 +32,30 @@ function toolResultOutcome(result) {
|
|
|
32
32
|
code: "code",
|
|
33
33
|
backend: "backend",
|
|
34
34
|
signal: "signal",
|
|
35
|
+
contentKind: "content_kind",
|
|
36
|
+
charset: "charset",
|
|
37
|
+
charsetSource: "charset_source",
|
|
38
|
+
extractionStage: "extraction_stage",
|
|
39
|
+
renderReason: "render_reason",
|
|
40
|
+
retryAt: "retry_at",
|
|
41
|
+
nextAction: "next_action",
|
|
35
42
|
};
|
|
36
43
|
const numbers = {
|
|
37
44
|
attempts: "attempts",
|
|
45
|
+
queueWaitMs: "queue_wait_ms",
|
|
46
|
+
backendDurationMs: "backend_duration_ms",
|
|
47
|
+
retryAfterMs: "retry_after_ms",
|
|
48
|
+
maxRequestsPerRun: "max_requests_per_run",
|
|
49
|
+
requestsThisCall: "requests_this_call",
|
|
50
|
+
requestsUsed: "requests_used",
|
|
51
|
+
requestsRemaining: "requests_remaining",
|
|
52
|
+
cooldownSkipCount: "cooldown_skip_count",
|
|
53
|
+
quotaSkipCount: "quota_skip_count",
|
|
38
54
|
bytes: "bytes",
|
|
39
55
|
exitCode: "exit_code",
|
|
40
56
|
statusCode: "status_code",
|
|
57
|
+
redirectCount: "redirect_count",
|
|
58
|
+
parserFailureCount: "parser_failure_count",
|
|
41
59
|
};
|
|
42
60
|
const booleans = {
|
|
43
61
|
retryable: "retryable",
|
|
@@ -45,6 +63,11 @@ function toolResultOutcome(result) {
|
|
|
45
63
|
truncated: "truncated",
|
|
46
64
|
timedOut: "timed_out",
|
|
47
65
|
rendered: "rendered",
|
|
66
|
+
renderFailed: "render_failed",
|
|
67
|
+
browserRecommended: "browser_recommended",
|
|
68
|
+
retryInRun: "retry_in_run",
|
|
69
|
+
fallbackUsed: "fallback_used",
|
|
70
|
+
hadDecodingReplacement: "had_decoding_replacement",
|
|
48
71
|
};
|
|
49
72
|
for (const [input, output] of Object.entries(strings)) {
|
|
50
73
|
if (typeof source[input] === "string") bounded[output] = source[input].slice(0, 120);
|
|
@@ -76,8 +99,8 @@ function toolResultOutcome(result) {
|
|
|
76
99
|
|
|
77
100
|
/**
|
|
78
101
|
* Build the harness subscribe handler. `harness` is passed for the maxTurns
|
|
79
|
-
* abort; it is already constructed when this is wired (subscribe follows
|
|
80
|
-
* AgentHarness
|
|
102
|
+
* abort; it is already constructed when this is wired (subscribe follows
|
|
103
|
+
* AgentHarness.create()).
|
|
81
104
|
* @param {StreamSubscriberState} runState
|
|
82
105
|
* @param {{onEvent: (event: any) => void, options: any, toolLimits: any, harness: any, sdk: string, model: string}} deps
|
|
83
106
|
* @returns {(event: any) => void}
|
|
@@ -107,21 +130,44 @@ export function createStreamSubscriber(runState, { onEvent, options, toolLimits,
|
|
|
107
130
|
onEvent({ type: "assistant", message: { content: [{ type: "thinking", text: streamEvent.content }] } });
|
|
108
131
|
}
|
|
109
132
|
}
|
|
110
|
-
} else if (event.type === "message_end") {
|
|
133
|
+
} else if (event.type === "message_end" && event.message?.role === "assistant") {
|
|
111
134
|
const contextUsage = contextUsageFromAssistantMessage(event.message);
|
|
112
135
|
if (contextUsage) {
|
|
136
|
+
const { costUsd, ...contextTokens } = contextUsage;
|
|
113
137
|
const contextWindow = Number(harness?.getModel?.()?.contextWindow) || 0;
|
|
114
138
|
const measurementId = typeof event.message?.id === "string" && event.message.id.trim().length > 0
|
|
115
139
|
? event.message.id
|
|
116
140
|
: undefined;
|
|
117
141
|
onEvent({
|
|
118
142
|
type: "context_usage",
|
|
143
|
+
...harness?.getPromptCacheRequest?.(),
|
|
119
144
|
sdk,
|
|
120
145
|
model,
|
|
121
146
|
timestamp: Date.now(),
|
|
122
147
|
...(measurementId === undefined ? {} : { measurementId }),
|
|
123
148
|
...(contextWindow > 0 ? { contextWindow } : {}),
|
|
124
|
-
tokens:
|
|
149
|
+
tokens: contextTokens,
|
|
150
|
+
costUsd,
|
|
151
|
+
providerCostUsd: typeof event.message.usage?.cost?.total === "number" && Number.isFinite(event.message.usage.cost.total) && event.message.usage.cost.total >= 0 ? event.message.usage.cost.total : null,
|
|
152
|
+
costSource: "pi_usage",
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
const hasVisibleContent = Array.isArray(event.message?.content)
|
|
156
|
+
&& event.message.content.some((block) => block
|
|
157
|
+
&& typeof block === "object"
|
|
158
|
+
&& ((block.type === "text"
|
|
159
|
+
&& typeof block.text === "string"
|
|
160
|
+
&& block.text.length > 0)
|
|
161
|
+
|| (block.type === "thinking"
|
|
162
|
+
&& ((typeof block.thinking === "string" && block.thinking.length > 0)
|
|
163
|
+
|| (typeof block.text === "string" && block.text.length > 0)))));
|
|
164
|
+
if (hasVisibleContent) {
|
|
165
|
+
const messageId = typeof event.message?.id === "string" && event.message.id.trim().length > 0
|
|
166
|
+
? event.message.id
|
|
167
|
+
: undefined;
|
|
168
|
+
onEvent({
|
|
169
|
+
type: "assistant_message_boundary",
|
|
170
|
+
...(messageId === undefined ? {} : { messageId }),
|
|
125
171
|
});
|
|
126
172
|
}
|
|
127
173
|
} else if (event.type === "tool_execution_start") {
|
|
@@ -198,7 +244,7 @@ export function createStreamSubscriber(runState, { onEvent, options, toolLimits,
|
|
|
198
244
|
});
|
|
199
245
|
} else if (event.type === "turn_end") {
|
|
200
246
|
runState.turnCount += 1;
|
|
201
|
-
// NON-DELEGABLE (verified against @earendil-works/pi-agent-core 0.
|
|
247
|
+
// NON-DELEGABLE (verified against @earendil-works/pi-agent-core 0.85.1).
|
|
202
248
|
// pi's only after-turn stop hook is `shouldStopAfterTurn` on the LOW-LEVEL
|
|
203
249
|
// `AgentLoopConfig` (dist/types.d.ts) — the config passed to the raw
|
|
204
250
|
// `agentLoop`. It is NOT surfaced on `AgentHarnessOptions`
|
|
@@ -217,7 +263,7 @@ export function createStreamSubscriber(runState, { onEvent, options, toolLimits,
|
|
|
217
263
|
&& runState.turnCount >= Number(options.maxTurns)
|
|
218
264
|
&& event.message?.stopReason === "toolUse") {
|
|
219
265
|
runState.maxTurnsHit = true;
|
|
220
|
-
harness.abort();
|
|
266
|
+
void Promise.resolve(harness.abort()).catch(() => {});
|
|
221
267
|
}
|
|
222
268
|
}
|
|
223
269
|
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
import { transformMessages } from "@earendil-works/pi-ai/api/transform-messages";
|
|
3
|
+
|
|
4
|
+
/** Validate the effective provider projection; Pi supplies honest missing results. */
|
|
5
|
+
export function validRecoveryProjection(messages, model) {
|
|
6
|
+
try {
|
|
7
|
+
const projected = transformMessages(messages, model);
|
|
8
|
+
const seen = new Set();
|
|
9
|
+
const pending = new Map();
|
|
10
|
+
for (const message of projected) {
|
|
11
|
+
if (!["user", "assistant", "toolResult"].includes(message?.role)) return false;
|
|
12
|
+
if (message.role !== "toolResult" && pending.size) return false;
|
|
13
|
+
if (message.role === "user" && typeof message.content === "string") continue;
|
|
14
|
+
if (!Array.isArray(message.content)) return false;
|
|
15
|
+
if (message.role === "toolResult") {
|
|
16
|
+
if (!pending.has(message.toolCallId) || pending.get(message.toolCallId) !== message.toolName) return false;
|
|
17
|
+
pending.delete(message.toolCallId);
|
|
18
|
+
}
|
|
19
|
+
if (message.role === "assistant" && ["error", "aborted", "deferred"].includes(message.stopReason)) return false;
|
|
20
|
+
for (const block of message.content) {
|
|
21
|
+
if (block?.type === "text") { if (typeof block.text !== "string") return false; }
|
|
22
|
+
else if (block?.type === "image") { if (typeof block.data !== "string" || typeof block.mimeType !== "string") return false; }
|
|
23
|
+
else if (block?.type === "thinking") {
|
|
24
|
+
if (message.role !== "assistant" || typeof block.thinking !== "string"
|
|
25
|
+
|| (block.thinkingSignature !== undefined && (typeof block.thinkingSignature !== "string" || !block.thinkingSignature))) return false;
|
|
26
|
+
if (block.thinkingSignature && String(model.api).includes("responses")) {
|
|
27
|
+
const signature = JSON.parse(block.thinkingSignature);
|
|
28
|
+
if (signature?.type !== "reasoning" || typeof signature.id !== "string" || !Array.isArray(signature.summary)) return false;
|
|
29
|
+
}
|
|
30
|
+
} else if (block?.type === "toolCall") {
|
|
31
|
+
if (message.role !== "assistant" || typeof block.id !== "string" || !block.id || seen.has(block.id)
|
|
32
|
+
|| typeof block.name !== "string" || !block.name || !block.arguments || typeof block.arguments !== "object") return false;
|
|
33
|
+
seen.add(block.id);
|
|
34
|
+
pending.set(block.id, block.name);
|
|
35
|
+
} else return false;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return pending.size === 0;
|
|
39
|
+
} catch { return false; }
|
|
40
|
+
}
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
// state; run state (harness, removeAbortHandler, externalAbort) lives on the
|
|
8
8
|
// caller-owned runState.
|
|
9
9
|
|
|
10
|
-
import { AgentHarness } from "@earendil-works/pi-agent-core";
|
|
11
10
|
import {
|
|
12
11
|
createStructuredOutputTool,
|
|
13
12
|
getPiBuiltinTools,
|
|
@@ -17,6 +16,7 @@ import { createNodeReplController } from "../../../agent/tools/node-repl.js";
|
|
|
17
16
|
import { createWebToolController } from "../../../agent/tools/web-controller.js";
|
|
18
17
|
import { readToolRuntime } from "../../../agent/tools/shared/runtime-context.js";
|
|
19
18
|
import { formatLiveInputGuidance } from "../../live-input-prompt.js";
|
|
19
|
+
import { createPiHarnessAdapter } from "./harness-adapter.js";
|
|
20
20
|
import { appendStructuredOutputInstruction } from "./structured-output.js";
|
|
21
21
|
import { createStreamSubscriber } from "./stream-subscriber.js";
|
|
22
22
|
|
|
@@ -78,6 +78,8 @@ export async function buildTurnTools(runState, {
|
|
|
78
78
|
? null
|
|
79
79
|
: createWebToolController({
|
|
80
80
|
searchConfig: options.webSearchConfig,
|
|
81
|
+
searchState: options.webSearchState,
|
|
82
|
+
coordinator: options.webRequestCoordinator,
|
|
81
83
|
fetchConfig: options.webFetchConfig,
|
|
82
84
|
sandboxPolicy: options.sandboxPolicy,
|
|
83
85
|
sandboxEngine,
|
|
@@ -121,13 +123,14 @@ export async function buildTurnTools(runState, {
|
|
|
121
123
|
nodeReplController,
|
|
122
124
|
webController,
|
|
123
125
|
processJobsController: options.processJobs,
|
|
126
|
+
processJobsAvailability: options.processJobsAvailability,
|
|
127
|
+
monitorsController: options.monitors,
|
|
124
128
|
toolExecutionMode,
|
|
125
129
|
subagents: options.subagents,
|
|
126
130
|
// The child inherits the parent's route and workspace unless its profile
|
|
127
131
|
// pins a model; the tool closure reads these to build each child request.
|
|
128
132
|
subagentContext: {
|
|
129
133
|
model: options.model,
|
|
130
|
-
executionMode: options.executionMode,
|
|
131
134
|
cwd: options.cwd,
|
|
132
135
|
parentRunId: runCtx?.runId,
|
|
133
136
|
// Same policy + engine this turn's own tools are confined by, so a
|
|
@@ -143,6 +146,9 @@ export async function buildTurnTools(runState, {
|
|
|
143
146
|
skills: options.skills,
|
|
144
147
|
skillsRoot: options.skillsRoot,
|
|
145
148
|
toolEnvironment: options.toolEnvironment,
|
|
149
|
+
webSearchConfig: options.webSearchConfig,
|
|
150
|
+
webRequestCoordinator: options.webRequestCoordinator,
|
|
151
|
+
webFetchConfig: options.webFetchConfig,
|
|
146
152
|
},
|
|
147
153
|
ctx: runCtx,
|
|
148
154
|
}));
|
|
@@ -163,6 +169,7 @@ export async function buildTurnTools(runState, {
|
|
|
163
169
|
qaOutputDir,
|
|
164
170
|
onTruncate,
|
|
165
171
|
limits: toolLimits,
|
|
172
|
+
mcpCallNoTotalTimeoutTools: options.mcpCallNoTotalTimeoutTools,
|
|
166
173
|
toolPayloadMaxBytes: toolLimits.toolPayloadMaxBytes,
|
|
167
174
|
sandboxPolicy: options.sandboxPolicy,
|
|
168
175
|
sandboxEngine,
|
|
@@ -251,7 +258,7 @@ export function toolResultErrorOverride(details) {
|
|
|
251
258
|
return failed ? { isError: true } : undefined;
|
|
252
259
|
}
|
|
253
260
|
|
|
254
|
-
export function buildTurnHarness(runState, {
|
|
261
|
+
export async function buildTurnHarness(runState, {
|
|
255
262
|
session,
|
|
256
263
|
piModels,
|
|
257
264
|
model,
|
|
@@ -263,18 +270,9 @@ export function buildTurnHarness(runState, {
|
|
|
263
270
|
maxRetries,
|
|
264
271
|
maxRetryDelayMs,
|
|
265
272
|
steeringMode,
|
|
266
|
-
onEvent,
|
|
267
273
|
options,
|
|
268
|
-
toolLimits,
|
|
269
|
-
sdk,
|
|
270
|
-
reference,
|
|
271
274
|
}) {
|
|
272
|
-
|
|
273
|
-
// ExecutionEnv now reaches tools through the generic per-turn `toolContext`
|
|
274
|
-
// instead. mono-agent needs neither — it uses none of pi's built-in
|
|
275
|
-
// file/shell tools, and its own tools close over what they need — so the
|
|
276
|
-
// option is dropped rather than migrated.
|
|
277
|
-
const harness = new AgentHarness({
|
|
275
|
+
const harness = await createPiHarnessAdapter(session, {
|
|
278
276
|
session,
|
|
279
277
|
models: piModels,
|
|
280
278
|
model,
|
|
@@ -284,6 +282,8 @@ export function buildTurnHarness(runState, {
|
|
|
284
282
|
streamOptions: { transport, maxRetries, maxRetryDelayMs },
|
|
285
283
|
steeringMode,
|
|
286
284
|
followUpMode: steeringMode,
|
|
285
|
+
promptCacheDiagnostics: options.promptCacheDiagnostics,
|
|
286
|
+
onEvent: options.onEvent,
|
|
287
287
|
});
|
|
288
288
|
// MCP `CallToolResult.isError` is a successful protocol response, so pi's
|
|
289
289
|
// execute() promise resolves. The bridge records that bit in result details;
|
|
@@ -297,8 +297,37 @@ export function buildTurnHarness(runState, {
|
|
|
297
297
|
// every resolved execute(), so without this the model would be told a failed,
|
|
298
298
|
// timed-out, or empty delegation succeeded.
|
|
299
299
|
harness.on("tool_result", (event) => toolResultErrorOverride(event?.details));
|
|
300
|
+
// Pi 0.85 records in-flight work durably. mono-agent tools do not yet use the
|
|
301
|
+
// new replay-memo contract, so abort an interrupted operation instead of
|
|
302
|
+
// risking a duplicate external effect. Do this before subscribing so stale
|
|
303
|
+
// recovery events cannot contaminate the new run's transcript counters.
|
|
304
|
+
try {
|
|
305
|
+
await harness.abortOpenOperations();
|
|
306
|
+
} catch (error) {
|
|
307
|
+
try { await harness.close(); } catch { /* best-effort */ }
|
|
308
|
+
throw error;
|
|
309
|
+
}
|
|
310
|
+
|
|
300
311
|
runState.harness = harness;
|
|
301
312
|
|
|
313
|
+
return harness;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Subscribe only after prior history has been seeded. Pi emits message
|
|
318
|
+
* lifecycle events for manual appendMessage() calls; subscribing earlier makes
|
|
319
|
+
* restored assistant messages look like fresh output boundaries.
|
|
320
|
+
* @param {any} runState
|
|
321
|
+
* @param {{harness: any, onEvent: (event: any) => void, options: any, toolLimits: any, sdk: string, reference: string}} deps
|
|
322
|
+
*/
|
|
323
|
+
export function activateTurnHarness(runState, {
|
|
324
|
+
harness,
|
|
325
|
+
onEvent,
|
|
326
|
+
options,
|
|
327
|
+
toolLimits,
|
|
328
|
+
sdk,
|
|
329
|
+
reference,
|
|
330
|
+
}) {
|
|
302
331
|
harness.subscribe(createStreamSubscriber(runState, {
|
|
303
332
|
onEvent,
|
|
304
333
|
options,
|
|
@@ -310,13 +339,12 @@ export function buildTurnHarness(runState, {
|
|
|
310
339
|
|
|
311
340
|
const abortHandler = () => {
|
|
312
341
|
runState.externalAbort = true;
|
|
313
|
-
harness.abort();
|
|
342
|
+
void harness.abort().catch(() => {});
|
|
314
343
|
};
|
|
315
344
|
if (options.abortSignal) {
|
|
316
345
|
options.abortSignal.addEventListener("abort", abortHandler, { once: true });
|
|
317
346
|
runState.removeAbortHandler = () => options.abortSignal.removeEventListener?.("abort", abortHandler);
|
|
318
347
|
}
|
|
319
|
-
return harness;
|
|
320
348
|
}
|
|
321
349
|
|
|
322
350
|
/**
|
|
@@ -324,10 +352,10 @@ export function buildTurnHarness(runState, {
|
|
|
324
352
|
* the harness mid-run; the consumer is tied to run completion (an internal
|
|
325
353
|
* runComplete flag) so it stops steering once the run finishes and does not
|
|
326
354
|
* swallow a follow-up meant for a later turn. Returns a `stop()` teardown.
|
|
327
|
-
* @param {{harness: any, options: any, onEvent: (event: any) => void}} deps
|
|
355
|
+
* @param {{harness: any, options: any, onEvent: (event: any) => void, promptEpoch?: any}} deps
|
|
328
356
|
* @returns {{stop: () => Promise<void>}}
|
|
329
357
|
*/
|
|
330
|
-
export function startLiveInput({ harness, options, onEvent }) {
|
|
358
|
+
export function startLiveInput({ harness, options, onEvent, promptEpoch }) {
|
|
331
359
|
if (!options.liveInput) return { stop: async () => {} };
|
|
332
360
|
const iterator = typeof options.liveInput[Symbol.asyncIterator] === "function"
|
|
333
361
|
? options.liveInput[Symbol.asyncIterator]()
|
|
@@ -336,6 +364,8 @@ export function startLiveInput({ harness, options, onEvent }) {
|
|
|
336
364
|
/** @type {() => void} */
|
|
337
365
|
let signalStop = () => {};
|
|
338
366
|
const stopped = new Promise((resolve) => { signalStop = () => resolve(); });
|
|
367
|
+
/** @type {Array<{entryId: string, message: any}>} */
|
|
368
|
+
const acceptedEntries = [];
|
|
339
369
|
const task = (async () => {
|
|
340
370
|
try {
|
|
341
371
|
while (!runComplete && !options.abortSignal?.aborted) {
|
|
@@ -345,8 +375,19 @@ export function startLiveInput({ harness, options, onEvent }) {
|
|
|
345
375
|
]);
|
|
346
376
|
if (next.done || runComplete || options.abortSignal?.aborted) break;
|
|
347
377
|
try {
|
|
348
|
-
await harness.steer(formatLiveInputGuidance(next.value.body, options.prompts));
|
|
349
|
-
|
|
378
|
+
const entryId = await harness.steer(formatLiveInputGuidance(next.value.body, options.prompts));
|
|
379
|
+
if (typeof entryId !== "string" || entryId.length === 0) {
|
|
380
|
+
next.value.accepted?.();
|
|
381
|
+
next.value.uncertain?.({ reason: "delivery_uncertain" });
|
|
382
|
+
continue;
|
|
383
|
+
}
|
|
384
|
+
const evidence = {
|
|
385
|
+
providerEntryId: entryId,
|
|
386
|
+
...(promptEpoch?.ownedRunId() === undefined ? {} : { providerRunId: promptEpoch.ownedRunId() }),
|
|
387
|
+
};
|
|
388
|
+
acceptedEntries.push({ entryId, message: next.value });
|
|
389
|
+
next.value.accepted?.(evidence);
|
|
390
|
+
promptEpoch?.register(entryId, next.value);
|
|
350
391
|
} catch (err) {
|
|
351
392
|
next.value.reject?.(err);
|
|
352
393
|
throw err;
|
|
@@ -360,6 +401,8 @@ export function startLiveInput({ harness, options, onEvent }) {
|
|
|
360
401
|
});
|
|
361
402
|
}
|
|
362
403
|
})();
|
|
404
|
+
/** @type {Promise<void>|undefined} */
|
|
405
|
+
let stopPromise;
|
|
363
406
|
return {
|
|
364
407
|
// The run is done: stop the live-steering consumer so it cannot steer a
|
|
365
408
|
// finished harness or swallow a follow-up meant for the next turn. We signal
|
|
@@ -367,15 +410,214 @@ export function startLiveInput({ harness, options, onEvent }) {
|
|
|
367
410
|
// race releases the task even when a third-party iterator's return() does
|
|
368
411
|
// not unblock its pending next(); awaiting the task still closes any steer
|
|
369
412
|
// acknowledgement already in progress.
|
|
370
|
-
stop:
|
|
413
|
+
stop: () => {
|
|
414
|
+
stopPromise ??= performStop();
|
|
415
|
+
return stopPromise;
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
|
|
419
|
+
async function performStop() {
|
|
371
420
|
runComplete = true;
|
|
372
421
|
signalStop();
|
|
373
422
|
if (iterator && typeof iterator.return === "function") {
|
|
374
423
|
try { void Promise.resolve(iterator.return()).catch(() => {}); } catch { /* best-effort */ }
|
|
375
424
|
}
|
|
376
425
|
await task;
|
|
426
|
+
for (const accepted of acceptedEntries) {
|
|
427
|
+
if (promptEpoch?.isConsumed(accepted.entryId)) continue;
|
|
428
|
+
try {
|
|
429
|
+
if (typeof harness.cancelQueued !== "function") {
|
|
430
|
+
accepted.message.uncertain?.({
|
|
431
|
+
reason: "delivery_uncertain",
|
|
432
|
+
providerEntryId: accepted.entryId,
|
|
433
|
+
...(promptEpoch?.ownedRunId() === undefined ? {} : { providerRunId: promptEpoch.ownedRunId() }),
|
|
434
|
+
});
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
const cancellation = await harness.cancelQueued(accepted.entryId);
|
|
438
|
+
if (cancellation?.kind === "cancelled") {
|
|
439
|
+
accepted.message.reject?.({ code: "native_queue_removed" });
|
|
440
|
+
} else if (!promptEpoch?.isConsumed(accepted.entryId)) {
|
|
441
|
+
accepted.message.uncertain?.({
|
|
442
|
+
reason: "delivery_uncertain",
|
|
443
|
+
providerEntryId: accepted.entryId,
|
|
444
|
+
...(promptEpoch?.ownedRunId() === undefined ? {} : { providerRunId: promptEpoch.ownedRunId() }),
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
} catch {
|
|
448
|
+
accepted.message.uncertain?.({
|
|
449
|
+
reason: "delivery_uncertain",
|
|
450
|
+
providerEntryId: accepted.entryId,
|
|
451
|
+
...(promptEpoch?.ownedRunId() === undefined ? {} : { providerRunId: promptEpoch.ownedRunId() }),
|
|
452
|
+
});
|
|
453
|
+
onEvent({
|
|
454
|
+
type: "runtime_warning",
|
|
455
|
+
warning_kind: "live_input_cancellation_failed",
|
|
456
|
+
message: "Unable to prove whether queued live input was removed.",
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/**
|
|
464
|
+
* Own exact Pi run/entry correlation for the one main prompt in this Mono run.
|
|
465
|
+
* @param {{harness: any, onEvent: (event: any) => void}} deps
|
|
466
|
+
*/
|
|
467
|
+
export function createLiveInputPromptEpoch({ harness, onEvent }) {
|
|
468
|
+
const BUFFER_LIMIT = 101;
|
|
469
|
+
/** @type {string|undefined} */
|
|
470
|
+
let runId;
|
|
471
|
+
let eventWindowClosed = false;
|
|
472
|
+
let invalid = false;
|
|
473
|
+
/** @type {Array<{entryId: string, runId: string}>} */
|
|
474
|
+
const buffered = [];
|
|
475
|
+
/** @type {Map<string, {message: any, observed: boolean, consumed: boolean}>} */
|
|
476
|
+
const entries = new Map();
|
|
477
|
+
// The operation id Pi admitted for the main prompt (via confirm) or that the
|
|
478
|
+
// settled prompt reported (via finish). Consumption is only acknowledged once
|
|
479
|
+
// the observed main-lane run_start carries this exact id.
|
|
480
|
+
/** @type {string|undefined} */
|
|
481
|
+
let admittedOperationId;
|
|
482
|
+
let operationConfirmed = false;
|
|
483
|
+
|
|
484
|
+
const remove = harness.subscribe((event) => {
|
|
485
|
+
if (!event || event.lane !== "main") return;
|
|
486
|
+
if (event.type === "run_start" && typeof event.runId === "string" && event.runId.length > 0) {
|
|
487
|
+
if (runId === undefined) {
|
|
488
|
+
runId = event.runId;
|
|
489
|
+
if (admittedOperationId !== undefined && admittedOperationId !== runId) {
|
|
490
|
+
invalidate("operation_mismatch");
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (admittedOperationId !== undefined) operationConfirmed = true;
|
|
494
|
+
consumeBuffered();
|
|
495
|
+
} else if (runId !== event.runId) {
|
|
496
|
+
invalidate("multiple_run_start");
|
|
497
|
+
}
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
if (event.type === "run_end" && event.runId === runId) {
|
|
501
|
+
eventWindowClosed = true;
|
|
502
|
+
return;
|
|
503
|
+
}
|
|
504
|
+
if (
|
|
505
|
+
event.type !== "message_end"
|
|
506
|
+
|| eventWindowClosed
|
|
507
|
+
|| event.message?.role !== "user"
|
|
508
|
+
|| typeof event.entryId !== "string"
|
|
509
|
+
|| event.entryId.length === 0
|
|
510
|
+
|| typeof event.runId !== "string"
|
|
511
|
+
|| event.runId.length === 0
|
|
512
|
+
) return;
|
|
513
|
+
if (buffered.some((item) => item.entryId === event.entryId && item.runId === event.runId)) {
|
|
514
|
+
consumeBuffered();
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
if (buffered.length >= BUFFER_LIMIT) {
|
|
518
|
+
invalidate("event_buffer_overflow");
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
buffered.push({ entryId: event.entryId, runId: event.runId });
|
|
522
|
+
consumeBuffered();
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
return {
|
|
526
|
+
ownedRunId: () => runId,
|
|
527
|
+
consumedInputIds: () => invalid ? null : [...entries.values()].filter((entry) => entry.consumed).map((entry) => entry.message.id),
|
|
528
|
+
register(entryId, message) {
|
|
529
|
+
if (!entries.has(entryId)) entries.set(entryId, { message, observed: false, consumed: false });
|
|
530
|
+
if (invalid) {
|
|
531
|
+
settleUncertain(entries.get(entryId), entryId);
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
consumeBuffered();
|
|
535
|
+
},
|
|
536
|
+
isConsumed: (entryId) => entries.get(entryId)?.consumed === true,
|
|
537
|
+
/**
|
|
538
|
+
* Own the admitted operation as soon as Pi reports its id, before the run
|
|
539
|
+
* settles, so entries consumed mid-run are acknowledged when their
|
|
540
|
+
* message_end arrives rather than in one batch at the end of the run.
|
|
541
|
+
* Safe in either order with run_start; a conflicting id invalidates.
|
|
542
|
+
*/
|
|
543
|
+
confirm(operationId) {
|
|
544
|
+
if (invalid) return;
|
|
545
|
+
if (
|
|
546
|
+
typeof operationId !== "string"
|
|
547
|
+
|| operationId.length === 0
|
|
548
|
+
|| (admittedOperationId !== undefined && admittedOperationId !== operationId)
|
|
549
|
+
|| (runId !== undefined && runId !== operationId)
|
|
550
|
+
) {
|
|
551
|
+
invalidate("operation_mismatch");
|
|
552
|
+
return;
|
|
553
|
+
}
|
|
554
|
+
admittedOperationId = operationId;
|
|
555
|
+
if (runId === undefined) return;
|
|
556
|
+
operationConfirmed = true;
|
|
557
|
+
confirmObserved();
|
|
558
|
+
},
|
|
559
|
+
finish(operationId) {
|
|
560
|
+
if (
|
|
561
|
+
typeof operationId !== "string"
|
|
562
|
+
|| operationId.length === 0
|
|
563
|
+
|| runId === undefined
|
|
564
|
+
|| operationId !== runId
|
|
565
|
+
|| (admittedOperationId !== undefined && admittedOperationId !== operationId)
|
|
566
|
+
) {
|
|
567
|
+
invalidate("operation_mismatch");
|
|
568
|
+
return;
|
|
569
|
+
}
|
|
570
|
+
admittedOperationId = operationId;
|
|
571
|
+
operationConfirmed = true;
|
|
572
|
+
confirmObserved();
|
|
573
|
+
},
|
|
574
|
+
close() {
|
|
575
|
+
remove?.();
|
|
377
576
|
},
|
|
378
577
|
};
|
|
578
|
+
|
|
579
|
+
function consumeBuffered() {
|
|
580
|
+
if (invalid || runId === undefined) return;
|
|
581
|
+
for (const evidence of buffered) {
|
|
582
|
+
if (evidence.runId !== runId) continue;
|
|
583
|
+
const entry = entries.get(evidence.entryId);
|
|
584
|
+
if (entry === undefined || entry.consumed) continue;
|
|
585
|
+
entry.observed = true;
|
|
586
|
+
}
|
|
587
|
+
if (operationConfirmed) confirmObserved();
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
function confirmObserved() {
|
|
591
|
+
if (!operationConfirmed || runId === undefined || invalid) return;
|
|
592
|
+
for (const [entryId, entry] of entries) {
|
|
593
|
+
if (!entry.observed || entry.consumed) continue;
|
|
594
|
+
entry.consumed = true;
|
|
595
|
+
entry.message.acknowledge?.({ providerEntryId: entryId, providerRunId: runId });
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
function invalidate(reason) {
|
|
600
|
+
if (invalid) return;
|
|
601
|
+
invalid = true;
|
|
602
|
+
try {
|
|
603
|
+
onEvent({
|
|
604
|
+
type: "runtime_warning",
|
|
605
|
+
warning_kind: "live_input_correlation_invalid",
|
|
606
|
+
message: "Live-input consumption could not be correlated to exactly one provider operation.",
|
|
607
|
+
reason,
|
|
608
|
+
});
|
|
609
|
+
} catch { /* diagnostics do not alter settlement */ }
|
|
610
|
+
for (const [entryId, entry] of entries) settleUncertain(entry, entryId);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function settleUncertain(entry, entryId) {
|
|
614
|
+
if (entry === undefined || entry.consumed) return;
|
|
615
|
+
entry.message.uncertain?.({
|
|
616
|
+
reason: "delivery_uncertain",
|
|
617
|
+
providerEntryId: entryId,
|
|
618
|
+
...(runId === undefined ? {} : { providerRunId: runId }),
|
|
619
|
+
});
|
|
620
|
+
}
|
|
379
621
|
}
|
|
380
622
|
|
|
381
623
|
/**
|
|
@@ -385,23 +627,32 @@ export function startLiveInput({ harness, options, onEvent }) {
|
|
|
385
627
|
* @param {any} harness
|
|
386
628
|
* @param {string} promptText
|
|
387
629
|
* @param {Array<any>} promptImages
|
|
388
|
-
* @
|
|
630
|
+
* @param {{onOperationAdmitted?: (operationId: string) => void}} [hooks]
|
|
631
|
+
* `onOperationAdmitted` fires as soon as Pi admits the run, before any
|
|
632
|
+
* provider request, so the live-input epoch can own the operation up front.
|
|
633
|
+
* @returns {Promise<{runError: any, operationId?: string}>}
|
|
389
634
|
*/
|
|
390
|
-
export async function runHarnessPrompt(harness, promptText, promptImages) {
|
|
635
|
+
export async function runHarnessPrompt(harness, promptText, promptImages, hooks) {
|
|
391
636
|
let runError = null;
|
|
637
|
+
let operationId;
|
|
392
638
|
try {
|
|
393
639
|
// Pass structured images (when present) so multimodal input reaches the
|
|
394
640
|
// model as image blocks rather than stringified text. AgentHarness.prompt
|
|
395
641
|
// takes them under an options object (`{ images }`); a bare array would be
|
|
396
642
|
// read as `options` and silently dropped (options?.images === undefined).
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
643
|
+
const promptOptions = {
|
|
644
|
+
...(Array.isArray(promptImages) && promptImages.length > 0 ? { images: promptImages } : {}),
|
|
645
|
+
...(typeof hooks?.onOperationAdmitted === "function"
|
|
646
|
+
? { onOperationAdmitted: hooks.onOperationAdmitted }
|
|
647
|
+
: {}),
|
|
648
|
+
};
|
|
649
|
+
const result = Object.keys(promptOptions).length > 0
|
|
650
|
+
? await harness.prompt(promptText, promptOptions)
|
|
651
|
+
: await harness.prompt(promptText);
|
|
652
|
+
operationId = result?.operationId;
|
|
402
653
|
} catch (err) {
|
|
403
654
|
runError = err;
|
|
404
655
|
}
|
|
405
656
|
await harness.waitForIdle();
|
|
406
|
-
return { runError };
|
|
657
|
+
return { runError, ...(operationId === undefined ? {} : { operationId }) };
|
|
407
658
|
}
|