@mono-agent/agent-app 0.6.2 → 0.8.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/README.md +210 -19
- package/dist/adapter-send-proxy.d.ts +18 -0
- package/dist/adapter-send-proxy.d.ts.map +1 -0
- package/dist/adapter-send-proxy.js +229 -0
- package/dist/adapter-send-proxy.js.map +1 -0
- package/dist/adapter-send-tools-main.js +62 -4
- package/dist/adapter-send-tools-main.js.map +1 -1
- package/dist/adapter-send-tools.d.ts +33 -6
- package/dist/adapter-send-tools.d.ts.map +1 -1
- package/dist/adapter-send-tools.js +485 -87
- package/dist/adapter-send-tools.js.map +1 -1
- package/dist/app-config.d.ts.map +1 -1
- package/dist/app-config.js +8 -0
- package/dist/app-config.js.map +1 -1
- package/dist/app.d.ts +2 -0
- package/dist/app.d.ts.map +1 -1
- package/dist/app.js +528 -135
- package/dist/app.js.map +1 -1
- package/dist/broadcast-recorder.d.ts.map +1 -1
- package/dist/broadcast-recorder.js +24 -2
- package/dist/broadcast-recorder.js.map +1 -1
- package/dist/channels.d.ts.map +1 -1
- package/dist/channels.js +16 -6
- package/dist/channels.js.map +1 -1
- package/dist/cli.d.ts +76 -4
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +2102 -102
- package/dist/cli.js.map +1 -1
- package/dist/config-reference.d.ts.map +1 -1
- package/dist/config-reference.js +195 -6
- package/dist/config-reference.js.map +1 -1
- package/dist/configuration-proposal-main.d.ts +3 -0
- package/dist/configuration-proposal-main.d.ts.map +1 -0
- package/dist/configuration-proposal-main.js +13 -0
- package/dist/configuration-proposal-main.js.map +1 -0
- package/dist/configuration-proposal-tool.d.ts +26 -0
- package/dist/configuration-proposal-tool.d.ts.map +1 -0
- package/dist/configuration-proposal-tool.js +139 -0
- package/dist/configuration-proposal-tool.js.map +1 -0
- package/dist/configured-agent.d.ts +12 -0
- package/dist/configured-agent.d.ts.map +1 -1
- package/dist/configured-agent.js +150 -18
- package/dist/configured-agent.js.map +1 -1
- package/dist/consumer-contract.d.ts +3 -0
- package/dist/consumer-contract.d.ts.map +1 -1
- package/dist/consumer-contract.js +44 -1
- package/dist/consumer-contract.js.map +1 -1
- package/dist/doctor.d.ts +27 -5
- package/dist/doctor.d.ts.map +1 -1
- package/dist/doctor.js +1197 -101
- package/dist/doctor.js.map +1 -1
- package/dist/first-run-managed-memory.d.ts +39 -0
- package/dist/first-run-managed-memory.d.ts.map +1 -0
- package/dist/first-run-managed-memory.js +521 -0
- package/dist/first-run-managed-memory.js.map +1 -0
- package/dist/first-run-readiness.d.ts +111 -0
- package/dist/first-run-readiness.d.ts.map +1 -0
- package/dist/first-run-readiness.js +757 -0
- package/dist/first-run-readiness.js.map +1 -0
- package/dist/index.d.ts +8 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +88 -3
- package/dist/init.d.ts.map +1 -1
- package/dist/init.js +1212 -23
- package/dist/init.js.map +1 -1
- package/dist/interaction-bridge.d.ts +25 -1
- package/dist/interaction-bridge.d.ts.map +1 -1
- package/dist/interaction-bridge.js +281 -19
- package/dist/interaction-bridge.js.map +1 -1
- package/dist/local-configuration.d.ts +56 -0
- package/dist/local-configuration.d.ts.map +1 -0
- package/dist/local-configuration.js +914 -0
- package/dist/local-configuration.js.map +1 -0
- package/dist/memory-command.d.ts +4 -0
- package/dist/memory-command.d.ts.map +1 -1
- package/dist/memory-command.js +751 -27
- package/dist/memory-command.js.map +1 -1
- package/dist/memory-recall.d.ts +38 -10
- package/dist/memory-recall.d.ts.map +1 -1
- package/dist/memory-recall.js +82 -25
- package/dist/memory-recall.js.map +1 -1
- package/dist/memory-retrieval.d.ts +66 -0
- package/dist/memory-retrieval.d.ts.map +1 -0
- package/dist/memory-retrieval.js +332 -0
- package/dist/memory-retrieval.js.map +1 -0
- package/dist/modules/base.d.ts +6 -5
- package/dist/modules/base.d.ts.map +1 -1
- package/dist/modules/base.js +11 -7
- package/dist/modules/base.js.map +1 -1
- package/dist/modules/catalog.d.ts.map +1 -1
- package/dist/modules/catalog.js +54 -20
- package/dist/modules/catalog.js.map +1 -1
- package/dist/modules/index.d.ts +2 -2
- package/dist/modules/index.d.ts.map +1 -1
- package/dist/modules/index.js +1 -1
- package/dist/modules/index.js.map +1 -1
- package/dist/modules/known-tools.d.ts +3 -0
- package/dist/modules/known-tools.d.ts.map +1 -1
- package/dist/modules/known-tools.js +11 -1
- package/dist/modules/known-tools.js.map +1 -1
- package/dist/modules/types.d.ts +20 -6
- package/dist/modules/types.d.ts.map +1 -1
- package/dist/modules/types.js.map +1 -1
- package/dist/package-version.d.ts +3 -0
- package/dist/package-version.d.ts.map +1 -0
- package/dist/package-version.js +14 -0
- package/dist/package-version.js.map +1 -0
- package/dist/pi-auth-store-inspection.d.ts +21 -0
- package/dist/pi-auth-store-inspection.d.ts.map +1 -0
- package/dist/pi-auth-store-inspection.js +120 -0
- package/dist/pi-auth-store-inspection.js.map +1 -0
- package/dist/pi-oauth-login-main.d.ts +3 -0
- package/dist/pi-oauth-login-main.d.ts.map +1 -0
- package/dist/pi-oauth-login-main.js +14 -0
- package/dist/pi-oauth-login-main.js.map +1 -0
- package/dist/pi-oauth-login.d.ts +22 -0
- package/dist/pi-oauth-login.d.ts.map +1 -0
- package/dist/pi-oauth-login.js +79 -0
- package/dist/pi-oauth-login.js.map +1 -0
- package/dist/project-skills.d.ts +40 -0
- package/dist/project-skills.d.ts.map +1 -0
- package/dist/project-skills.js +573 -0
- package/dist/project-skills.js.map +1 -0
- package/dist/provider-setup.d.ts +117 -2
- package/dist/provider-setup.d.ts.map +1 -1
- package/dist/provider-setup.js +1505 -49
- package/dist/provider-setup.js.map +1 -1
- package/dist/readiness-probe-worker.d.ts +12 -0
- package/dist/readiness-probe-worker.d.ts.map +1 -0
- package/dist/readiness-probe-worker.js +276 -0
- package/dist/readiness-probe-worker.js.map +1 -0
- package/dist/readiness-probe.d.ts +100 -0
- package/dist/readiness-probe.d.ts.map +1 -0
- package/dist/readiness-probe.js +780 -0
- package/dist/readiness-probe.js.map +1 -0
- package/dist/request-model-override.d.ts +43 -1
- package/dist/request-model-override.d.ts.map +1 -1
- package/dist/request-model-override.js +178 -19
- package/dist/request-model-override.js.map +1 -1
- package/dist/run-history.d.ts +32 -0
- package/dist/run-history.d.ts.map +1 -0
- package/dist/run-history.js +859 -0
- package/dist/run-history.js.map +1 -0
- package/dist/runs-health.d.ts.map +1 -1
- package/dist/runs-health.js +12 -4
- package/dist/runs-health.js.map +1 -1
- package/dist/runtime-option-extensions.d.ts +14 -0
- package/dist/runtime-option-extensions.d.ts.map +1 -0
- package/dist/runtime-option-extensions.js +98 -0
- package/dist/runtime-option-extensions.js.map +1 -0
- package/dist/runtime-routes.d.ts +9 -0
- package/dist/runtime-routes.d.ts.map +1 -0
- package/dist/runtime-routes.js +13 -0
- package/dist/runtime-routes.js.map +1 -0
- package/dist/sandbox-manager.d.ts +69 -0
- package/dist/sandbox-manager.d.ts.map +1 -0
- package/dist/sandbox-manager.js +856 -0
- package/dist/sandbox-manager.js.map +1 -0
- package/dist/supermemory-plugin.d.ts +54 -0
- package/dist/supermemory-plugin.d.ts.map +1 -0
- package/dist/supermemory-plugin.js +136 -0
- package/dist/supermemory-plugin.js.map +1 -0
- package/dist/tui-command.d.ts +15 -0
- package/dist/tui-command.d.ts.map +1 -1
- package/dist/tui-command.js +46 -6
- package/dist/tui-command.js.map +1 -1
- package/dist/web-command.d.ts +8 -0
- package/dist/web-command.d.ts.map +1 -1
- package/dist/web-command.js +174 -17
- package/dist/web-command.js.map +1 -1
- package/dist/wizard/answers.d.ts +31 -4
- package/dist/wizard/answers.d.ts.map +1 -1
- package/dist/wizard/answers.js +145 -16
- package/dist/wizard/answers.js.map +1 -1
- package/dist/wizard/from-flags.d.ts +7 -0
- package/dist/wizard/from-flags.d.ts.map +1 -1
- package/dist/wizard/from-flags.js +36 -2
- package/dist/wizard/from-flags.js.map +1 -1
- package/dist/wizard/index.d.ts +4 -4
- package/dist/wizard/index.d.ts.map +1 -1
- package/dist/wizard/index.js +2 -2
- package/dist/wizard/index.js.map +1 -1
- package/dist/wizard/model-discovery.d.ts +33 -2
- package/dist/wizard/model-discovery.d.ts.map +1 -1
- package/dist/wizard/model-discovery.js +826 -108
- package/dist/wizard/model-discovery.js.map +1 -1
- package/dist/wizard/presets.d.ts +1 -1
- package/dist/wizard/presets.d.ts.map +1 -1
- package/dist/wizard/presets.js +7 -12
- package/dist/wizard/presets.js.map +1 -1
- package/dist/wizard/prompts.d.ts +56 -9
- package/dist/wizard/prompts.d.ts.map +1 -1
- package/dist/wizard/prompts.js +172 -20
- package/dist/wizard/prompts.js.map +1 -1
- package/dist/wizard/run.d.ts +59 -3
- package/dist/wizard/run.d.ts.map +1 -1
- package/dist/wizard/run.js +1133 -191
- package/dist/wizard/run.js.map +1 -1
- package/package.json +21 -16
- package/resources/srt/package-lock.json +66 -0
- package/resources/srt/package.json +9 -0
- package/schema/mono-agent.config.schema.json +381 -17
- package/skills/mono-agent-composer/SKILL.md +4 -4
- package/skills/mono-agent-composer/references/config-blueprint.md +5 -4
- package/skills/mono-agent-composer/references/discovery-questions.md +8 -8
- package/skills/mono-agent-composer/references/feature-coverage.md +8 -6
- package/skills/mono-agent-composer/references/package-map.md +1 -1
- package/skills/mono-agent-composer/references/playbooks.md +8 -7
- package/skills/mono-agent-composer/references/validation.md +1 -1
|
@@ -0,0 +1,780 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
3
|
+
import { tmpdir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { Worker } from "node:worker_threads";
|
|
6
|
+
import { loadAppCoreConfig } from "./app-config.js";
|
|
7
|
+
import { validateMonoAgentFolder } from "./doctor.js";
|
|
8
|
+
const CLOUD_READINESS_TIMEOUT_MS = 90_000;
|
|
9
|
+
const LOCAL_READINESS_TIMEOUT_MS = 240_000;
|
|
10
|
+
const MAX_SAFE_MESSAGE_CHARS = 400;
|
|
11
|
+
const WORKER_SHUTDOWN_GRACE_MS = 1_000;
|
|
12
|
+
const LOCAL_PI_PROVIDERS = new Set(["ollama", "lmstudio", "llamacpp"]);
|
|
13
|
+
const SENSITIVE_ENV_NAME = /(api.?key|credential|password|secret|token)/iu;
|
|
14
|
+
const SENSITIVE_FINGERPRINT_KEY = /(api.?key|authorization|cookie|credential|password|secret|token)/iu;
|
|
15
|
+
/** Provider-aware hard deadline for the one-turn primary-model probe. */
|
|
16
|
+
export function readinessProbeTimeoutMs(model) {
|
|
17
|
+
return model.sdk === "pi" && model.provider !== undefined && LOCAL_PI_PROVIDERS.has(model.provider)
|
|
18
|
+
? LOCAL_READINESS_TIMEOUT_MS
|
|
19
|
+
: CLOUD_READINESS_TIMEOUT_MS;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* The probe must not inherit a configured agent from the shell it happens to
|
|
23
|
+
* run in. Provider auth/runtime variables remain available, while every
|
|
24
|
+
* ambient MONO_AGENT_* override is excluded before layered config loading.
|
|
25
|
+
* Selected wizard secrets are an in-memory overlay and are intentionally added
|
|
26
|
+
* afterwards; they are never written to the disposable workspace.
|
|
27
|
+
*/
|
|
28
|
+
export function readinessProbeEnvironment(hostEnv, secretValues = {}, options = {}) {
|
|
29
|
+
const sanitized = Object.fromEntries(Object.entries(hostEnv).filter((entry) => !entry[0].startsWith("MONO_AGENT_") && typeof entry[1] === "string"));
|
|
30
|
+
const piAuthPath = options.resolvedPiAuthPath?.trim();
|
|
31
|
+
return {
|
|
32
|
+
...sanitized,
|
|
33
|
+
...secretValues,
|
|
34
|
+
...(piAuthPath === undefined || piAuthPath.length === 0
|
|
35
|
+
? {}
|
|
36
|
+
: { MONO_AGENT_PI_AUTH_PATH: piAuthPath }),
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function safeMessage(value, secretValues, fallback) {
|
|
40
|
+
let message = typeof value === "string"
|
|
41
|
+
? value
|
|
42
|
+
: value instanceof Error
|
|
43
|
+
? value.message
|
|
44
|
+
: value === null || value === undefined
|
|
45
|
+
? fallback
|
|
46
|
+
: String(value);
|
|
47
|
+
for (const secret of Object.values(secretValues)) {
|
|
48
|
+
if (secret.length >= 4) {
|
|
49
|
+
message = message.replaceAll(secret, "[REDACTED]");
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
message = message
|
|
53
|
+
.replace(/\bBearer\s+[^\s,;]+/giu, "Bearer [REDACTED]")
|
|
54
|
+
.replace(/\b(api[ _-]?key|access[ _-]?token|auth[ _-]?token|password|secret)(\s*[=:]\s*)([^\s,;]+)/giu, (_match, label, separator) => `${label}${separator}[REDACTED]`)
|
|
55
|
+
.replace(/\s+/gu, " ")
|
|
56
|
+
.trim();
|
|
57
|
+
if (message.length === 0) {
|
|
58
|
+
return fallback;
|
|
59
|
+
}
|
|
60
|
+
return message.length > MAX_SAFE_MESSAGE_CHARS
|
|
61
|
+
? `${message.slice(0, MAX_SAFE_MESSAGE_CHARS - 1)}…`
|
|
62
|
+
: message;
|
|
63
|
+
}
|
|
64
|
+
function redactionValues(hostEnv, secretValues) {
|
|
65
|
+
const sensitiveHostValues = Object.fromEntries(Object.entries(hostEnv).filter((entry) => SENSITIVE_ENV_NAME.test(entry[0]) && typeof entry[1] === "string" && entry[1].length > 0));
|
|
66
|
+
return { ...sensitiveHostValues, ...secretValues };
|
|
67
|
+
}
|
|
68
|
+
const DIRECT_TOOL_EVENT_TYPES = new Set([
|
|
69
|
+
"bash",
|
|
70
|
+
"collab_agent_tool_call",
|
|
71
|
+
"command_execution",
|
|
72
|
+
"dynamic_tool_call",
|
|
73
|
+
"file_change",
|
|
74
|
+
"image_generation",
|
|
75
|
+
"image_view",
|
|
76
|
+
"mcp_tool_call",
|
|
77
|
+
"sleep",
|
|
78
|
+
"subagent_activity",
|
|
79
|
+
"tool_call",
|
|
80
|
+
"tool_result",
|
|
81
|
+
"tool_use",
|
|
82
|
+
"web_search",
|
|
83
|
+
]);
|
|
84
|
+
function normalizedEventType(value) {
|
|
85
|
+
return typeof value === "string"
|
|
86
|
+
? value.replace(/([a-z0-9])([A-Z])/gu, "$1_$2").replace(/[./-]+/gu, "_").toLowerCase()
|
|
87
|
+
: "";
|
|
88
|
+
}
|
|
89
|
+
function toolActionInEvent(event) {
|
|
90
|
+
const eventType = normalizedEventType(event.type);
|
|
91
|
+
if (DIRECT_TOOL_EVENT_TYPES.has(eventType)) {
|
|
92
|
+
return eventType;
|
|
93
|
+
}
|
|
94
|
+
const item = event.item;
|
|
95
|
+
if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
96
|
+
const itemType = normalizedEventType(item.type);
|
|
97
|
+
if (DIRECT_TOOL_EVENT_TYPES.has(itemType)) {
|
|
98
|
+
return itemType;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
const message = event.message;
|
|
102
|
+
if (typeof message !== "object" || message === null || Array.isArray(message)) {
|
|
103
|
+
return undefined;
|
|
104
|
+
}
|
|
105
|
+
const content = message.content;
|
|
106
|
+
if (!Array.isArray(content)) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
for (const block of content) {
|
|
110
|
+
if (typeof block !== "object" || block === null || Array.isArray(block)) {
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
const blockType = normalizedEventType(block.type);
|
|
114
|
+
if (blockType === "tool_use" || blockType === "tool_call" || blockType === "tool_result") {
|
|
115
|
+
return blockType;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
function positiveTimeout(value, model) {
|
|
121
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0
|
|
122
|
+
? Math.trunc(value)
|
|
123
|
+
: readinessProbeTimeoutMs(model);
|
|
124
|
+
}
|
|
125
|
+
function isReadinessWorkerMessage(value) {
|
|
126
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
const message = value;
|
|
130
|
+
if (message.type === "disposed") {
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
if (message.type === "error") {
|
|
134
|
+
return typeof message.message === "string" && message.message.length <= MAX_SAFE_MESSAGE_CHARS;
|
|
135
|
+
}
|
|
136
|
+
if (message.type === "tool") {
|
|
137
|
+
return typeof message.action === "string" && DIRECT_TOOL_EVENT_TYPES.has(message.action);
|
|
138
|
+
}
|
|
139
|
+
return message.type === "result"
|
|
140
|
+
&& typeof message.hasText === "boolean"
|
|
141
|
+
&& typeof message.cancelled === "boolean"
|
|
142
|
+
&& (message.failureKind === undefined
|
|
143
|
+
|| (typeof message.failureKind === "string" && message.failureKind.length <= MAX_SAFE_MESSAGE_CHARS))
|
|
144
|
+
&& (message.errorMessage === undefined
|
|
145
|
+
|| (typeof message.errorMessage === "string" && message.errorMessage.length <= MAX_SAFE_MESSAGE_CHARS));
|
|
146
|
+
}
|
|
147
|
+
function workerResult(message) {
|
|
148
|
+
return {
|
|
149
|
+
text: message.hasText ? "ready" : "",
|
|
150
|
+
...(message.cancelled ? { cancelled: true } : {}),
|
|
151
|
+
...(message.failureKind === undefined ? {} : { failureKind: message.failureKind }),
|
|
152
|
+
...(message.errorMessage === undefined ? {} : { error: message.errorMessage }),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
function startReadinessWorker(input) {
|
|
156
|
+
const model = input.runtime.model;
|
|
157
|
+
const worker = new Worker(input.workerUrl ?? new URL("./readiness-probe-worker.js", import.meta.url), {
|
|
158
|
+
// The parent has already loaded and validated the disposable config. Only
|
|
159
|
+
// this clone-safe, credential-free execution spec crosses workerData.
|
|
160
|
+
workerData: {
|
|
161
|
+
cwd: input.cwd,
|
|
162
|
+
runtime: {
|
|
163
|
+
model: {
|
|
164
|
+
sdk: model.sdk,
|
|
165
|
+
model: model.model,
|
|
166
|
+
...(model.provider === undefined ? {} : { provider: model.provider }),
|
|
167
|
+
...(model.reference === undefined ? {} : { reference: model.reference }),
|
|
168
|
+
},
|
|
169
|
+
...(input.runtime.executionMode === undefined
|
|
170
|
+
? {}
|
|
171
|
+
: { executionMode: input.runtime.executionMode }),
|
|
172
|
+
...(input.runtime.effort === undefined ? {} : { effort: input.runtime.effort }),
|
|
173
|
+
workspace: input.runtime.workspace,
|
|
174
|
+
artifactDir: input.runtime.artifactDir,
|
|
175
|
+
...(input.runtime.piAuthPath === undefined ? {} : { piAuthPath: input.runtime.piAuthPath }),
|
|
176
|
+
},
|
|
177
|
+
},
|
|
178
|
+
env: { ...input.env },
|
|
179
|
+
// Provider SDKs must not be able to leak credentials through inherited
|
|
180
|
+
// worker stdout/stderr. The streams are deliberately drained below.
|
|
181
|
+
stdout: true,
|
|
182
|
+
stderr: true,
|
|
183
|
+
});
|
|
184
|
+
worker.stdout?.resume();
|
|
185
|
+
worker.stderr?.resume();
|
|
186
|
+
let exited = false;
|
|
187
|
+
let outcomeSettled = false;
|
|
188
|
+
let resolveOutcome;
|
|
189
|
+
let resolveStopped;
|
|
190
|
+
const stopped = new Promise((resolve) => {
|
|
191
|
+
resolveStopped = resolve;
|
|
192
|
+
});
|
|
193
|
+
const outcome = new Promise((resolve) => {
|
|
194
|
+
resolveOutcome = resolve;
|
|
195
|
+
});
|
|
196
|
+
const settleOutcome = (next) => {
|
|
197
|
+
if (!outcomeSettled) {
|
|
198
|
+
outcomeSettled = true;
|
|
199
|
+
resolveOutcome?.(next);
|
|
200
|
+
}
|
|
201
|
+
};
|
|
202
|
+
const abort = () => {
|
|
203
|
+
if (exited) {
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
try {
|
|
207
|
+
worker.postMessage({ type: "abort" });
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
// The worker exited between the state check and postMessage.
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
worker.on("message", (value) => {
|
|
214
|
+
if (!isReadinessWorkerMessage(value)) {
|
|
215
|
+
settleOutcome({ type: "error", error: new Error("The isolated readiness worker returned an invalid response.") });
|
|
216
|
+
abort();
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (value.type === "disposed") {
|
|
220
|
+
resolveStopped?.();
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (value.type === "error") {
|
|
224
|
+
settleOutcome({ type: "error", error: new Error(value.message) });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (value.type === "tool") {
|
|
228
|
+
settleOutcome({ type: "tool", action: value.action });
|
|
229
|
+
abort();
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
settleOutcome({ type: "result", result: workerResult(value) });
|
|
233
|
+
});
|
|
234
|
+
worker.once("error", () => {
|
|
235
|
+
// Deliberately discard the worker error object: a provider SDK may include
|
|
236
|
+
// credential-bearing request details in it.
|
|
237
|
+
settleOutcome({ type: "error", error: new Error("The isolated readiness worker failed.") });
|
|
238
|
+
resolveStopped?.();
|
|
239
|
+
});
|
|
240
|
+
worker.once("exit", () => {
|
|
241
|
+
exited = true;
|
|
242
|
+
settleOutcome({ type: "error", error: new Error("The isolated readiness worker exited before returning a result.") });
|
|
243
|
+
resolveStopped?.();
|
|
244
|
+
});
|
|
245
|
+
return {
|
|
246
|
+
outcome,
|
|
247
|
+
abort,
|
|
248
|
+
async shutdown() {
|
|
249
|
+
abort();
|
|
250
|
+
let timer;
|
|
251
|
+
try {
|
|
252
|
+
await Promise.race([
|
|
253
|
+
stopped,
|
|
254
|
+
new Promise((resolve) => {
|
|
255
|
+
timer = setTimeout(resolve, WORKER_SHUTDOWN_GRACE_MS);
|
|
256
|
+
}),
|
|
257
|
+
]);
|
|
258
|
+
}
|
|
259
|
+
finally {
|
|
260
|
+
if (timer !== undefined) {
|
|
261
|
+
clearTimeout(timer);
|
|
262
|
+
}
|
|
263
|
+
if (!exited) {
|
|
264
|
+
// disposeAllSessions in the worker is best-effort and bounded. A
|
|
265
|
+
// provider that ignores both abort and disposal cannot keep worker JS
|
|
266
|
+
// alive. Any SDK-spawned orphan still has only the worker's exact env.
|
|
267
|
+
await worker.terminate().catch(() => undefined);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
function selectedReadinessRoutes(plan, options = {}) {
|
|
274
|
+
const runtime = (plan.configJson.runtime ?? {});
|
|
275
|
+
const primary = typeof runtime.model === "string" ? runtime.model : "";
|
|
276
|
+
const inheritedEffort = typeof runtime.effort === "string" ? runtime.effort : undefined;
|
|
277
|
+
const authored = [];
|
|
278
|
+
if (primary.length > 0) {
|
|
279
|
+
authored.push({ model: primary, ...(inheritedEffort === undefined ? {} : { effort: inheritedEffort }) });
|
|
280
|
+
}
|
|
281
|
+
// Canonical fallbacks have independent effort semantics: omission means the
|
|
282
|
+
// provider default, not inheritance from the primary route.
|
|
283
|
+
if (Array.isArray(runtime.fallbacks)) {
|
|
284
|
+
for (const raw of runtime.fallbacks) {
|
|
285
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw))
|
|
286
|
+
continue;
|
|
287
|
+
const entry = raw;
|
|
288
|
+
if (typeof entry.model !== "string" || entry.model.length === 0)
|
|
289
|
+
continue;
|
|
290
|
+
authored.push({
|
|
291
|
+
model: entry.model,
|
|
292
|
+
...(typeof entry.effort === "string" ? { effort: entry.effort } : {}),
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
else if (Array.isArray(runtime.fallbackModels)) {
|
|
297
|
+
// Legacy fallbackModels intentionally retain their historical inheritance.
|
|
298
|
+
for (const model of runtime.fallbackModels) {
|
|
299
|
+
if (typeof model !== "string" || model.length === 0)
|
|
300
|
+
continue;
|
|
301
|
+
authored.push({ model, ...(inheritedEffort === undefined ? {} : { effort: inheritedEffort }) });
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
const immutableRoutes = authored.map((route, index) => ({ index, model: route.model, effort: route.effort ?? null }));
|
|
305
|
+
const executionRuntime = { ...runtime };
|
|
306
|
+
delete executionRuntime.model;
|
|
307
|
+
delete executionRuntime.effort;
|
|
308
|
+
delete executionRuntime.fallbacks;
|
|
309
|
+
delete executionRuntime.fallbackModels;
|
|
310
|
+
delete executionRuntime.session;
|
|
311
|
+
delete executionRuntime.workspace;
|
|
312
|
+
const nonSecretEnvironment = Object.fromEntries(Object.entries(options.hostEnv ?? process.env)
|
|
313
|
+
.filter((entry) => typeof entry[1] === "string"
|
|
314
|
+
&& !SENSITIVE_ENV_NAME.test(entry[0])
|
|
315
|
+
&& !entry[0].startsWith("MONO_AGENT_"))
|
|
316
|
+
.sort(([left], [right]) => left.localeCompare(right)));
|
|
317
|
+
const immutable = canonicalFingerprintValue({
|
|
318
|
+
version: 2,
|
|
319
|
+
routes: immutableRoutes,
|
|
320
|
+
runtime: executionRuntime,
|
|
321
|
+
providers: plan.configJson.providers ?? null,
|
|
322
|
+
resolvedPiAuthPath: options.resolvedPiAuthPath ?? null,
|
|
323
|
+
nonSecretEnvironment,
|
|
324
|
+
selectedSecretNames: Object.keys(options.secretValues ?? {}).sort(),
|
|
325
|
+
timeoutMs: options.timeoutMs ?? null,
|
|
326
|
+
});
|
|
327
|
+
const fingerprint = createHash("sha256")
|
|
328
|
+
.update(JSON.stringify(immutable))
|
|
329
|
+
.digest("hex");
|
|
330
|
+
return {
|
|
331
|
+
fingerprint,
|
|
332
|
+
routes: authored.map((route, index) => ({
|
|
333
|
+
index,
|
|
334
|
+
...route,
|
|
335
|
+
key: createHash("sha256")
|
|
336
|
+
.update(JSON.stringify({ version: 1, index, model: route.model, effort: route.effort ?? null }))
|
|
337
|
+
.digest("hex"),
|
|
338
|
+
})),
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
function canonicalFingerprintValue(value, key = "") {
|
|
342
|
+
if (SENSITIVE_FINGERPRINT_KEY.test(key))
|
|
343
|
+
return "[secret-redacted]";
|
|
344
|
+
if (Array.isArray(value))
|
|
345
|
+
return value.map((entry) => canonicalFingerprintValue(entry));
|
|
346
|
+
if (typeof value !== "object" || value === null)
|
|
347
|
+
return value;
|
|
348
|
+
return Object.fromEntries(Object.entries(value)
|
|
349
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
350
|
+
.map(([entryKey, entryValue]) => [entryKey, canonicalFingerprintValue(entryValue, entryKey)]));
|
|
351
|
+
}
|
|
352
|
+
function singleRouteWizardPlan(plan, route) {
|
|
353
|
+
const configJson = structuredClone(plan.configJson);
|
|
354
|
+
const runtime = {
|
|
355
|
+
...(configJson.runtime ?? {}),
|
|
356
|
+
model: route.model,
|
|
357
|
+
};
|
|
358
|
+
delete runtime.fallbacks;
|
|
359
|
+
delete runtime.fallbackModels;
|
|
360
|
+
delete runtime.session;
|
|
361
|
+
if (route.effort === undefined)
|
|
362
|
+
delete runtime.effort;
|
|
363
|
+
else
|
|
364
|
+
runtime.effort = route.effort;
|
|
365
|
+
configJson.runtime = runtime;
|
|
366
|
+
return { ...plan, configJson: configJson };
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Make one real, sequential no-tool turn for every selected persistent runtime
|
|
370
|
+
* route. A route is never allowed to invoke its configured fallback chain.
|
|
371
|
+
* Ordinary failures are collected so the operator gets one complete summary;
|
|
372
|
+
* caller cancellation stops the current route and records interruption state.
|
|
373
|
+
*/
|
|
374
|
+
export async function runReadinessProbe(options) {
|
|
375
|
+
const selected = selectedReadinessRoutes(options.plan, options);
|
|
376
|
+
if (selected.routes.length <= 1 && options.resume === undefined) {
|
|
377
|
+
return runSingleReadinessProbe(options);
|
|
378
|
+
}
|
|
379
|
+
return runSelectedReadinessRoutes(options, selected);
|
|
380
|
+
}
|
|
381
|
+
/**
|
|
382
|
+
* Always return the ordered route ledger, including for a primary-only plan.
|
|
383
|
+
* Guided setup uses this surface so a successful call can be resumed by its
|
|
384
|
+
* stable key; legacy callers may keep using runReadinessProbe's compact result.
|
|
385
|
+
*/
|
|
386
|
+
export async function runAllRouteReadinessProbe(options) {
|
|
387
|
+
return runSelectedReadinessRoutes(options, selectedReadinessRoutes(options.plan, options));
|
|
388
|
+
}
|
|
389
|
+
async function runSelectedReadinessRoutes(options, selected) {
|
|
390
|
+
if (selected.routes.length === 0) {
|
|
391
|
+
return {
|
|
392
|
+
ok: false,
|
|
393
|
+
kind: "invalid_plan",
|
|
394
|
+
message: "The readiness plan does not contain a selected runtime model.",
|
|
395
|
+
planFingerprint: selected.fingerprint,
|
|
396
|
+
routes: [],
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
const mayResume = options.resume?.planFingerprint === selected.fingerprint;
|
|
400
|
+
const successful = mayResume
|
|
401
|
+
? new Set(options.resume?.successfulRouteKeys ?? [])
|
|
402
|
+
: new Set();
|
|
403
|
+
const results = [];
|
|
404
|
+
for (const route of selected.routes) {
|
|
405
|
+
const displayRoute = {
|
|
406
|
+
...route,
|
|
407
|
+
model: boundedRouteField(route.model, 200),
|
|
408
|
+
...(route.effort === undefined ? {} : { effort: boundedRouteField(route.effort, 32) }),
|
|
409
|
+
};
|
|
410
|
+
await notifyReadinessProgress(options.onRouteStart, {
|
|
411
|
+
...displayRoute,
|
|
412
|
+
total: selected.routes.length,
|
|
413
|
+
});
|
|
414
|
+
if (options.abortSignal?.aborted === true) {
|
|
415
|
+
const completed = {
|
|
416
|
+
...displayRoute,
|
|
417
|
+
status: "interrupted",
|
|
418
|
+
kind: "cancelled",
|
|
419
|
+
message: "The route check was interrupted before it started.",
|
|
420
|
+
};
|
|
421
|
+
results.push(completed);
|
|
422
|
+
await notifyReadinessProgress(options.onRouteComplete, completed);
|
|
423
|
+
return {
|
|
424
|
+
ok: false,
|
|
425
|
+
kind: "cancelled",
|
|
426
|
+
message: `Readiness was interrupted after ${results.filter((entry) => entry.status === "verified" || entry.status === "skipped_verified").length} of ${selected.routes.length} routes were verified.`,
|
|
427
|
+
planFingerprint: selected.fingerprint,
|
|
428
|
+
routes: results,
|
|
429
|
+
interrupted: true,
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
if (successful.has(route.key)) {
|
|
433
|
+
const completed = { ...displayRoute, status: "skipped_verified" };
|
|
434
|
+
results.push(completed);
|
|
435
|
+
await notifyReadinessProgress(options.onRouteComplete, completed);
|
|
436
|
+
continue;
|
|
437
|
+
}
|
|
438
|
+
const { resume: _resume, ...singleOptions } = options;
|
|
439
|
+
const result = await runSingleReadinessProbe({
|
|
440
|
+
...singleOptions,
|
|
441
|
+
plan: singleRouteWizardPlan(options.plan, route),
|
|
442
|
+
});
|
|
443
|
+
if (result.ok) {
|
|
444
|
+
const completed = { ...displayRoute, status: "verified" };
|
|
445
|
+
results.push(completed);
|
|
446
|
+
await notifyReadinessProgress(options.onRouteComplete, completed);
|
|
447
|
+
continue;
|
|
448
|
+
}
|
|
449
|
+
const interrupted = result.kind === "cancelled";
|
|
450
|
+
const completed = {
|
|
451
|
+
...displayRoute,
|
|
452
|
+
status: interrupted ? "interrupted" : "failed",
|
|
453
|
+
kind: result.kind,
|
|
454
|
+
message: result.message,
|
|
455
|
+
};
|
|
456
|
+
results.push(completed);
|
|
457
|
+
await notifyReadinessProgress(options.onRouteComplete, completed);
|
|
458
|
+
if (interrupted) {
|
|
459
|
+
return {
|
|
460
|
+
ok: false,
|
|
461
|
+
kind: "cancelled",
|
|
462
|
+
message: `Readiness was interrupted while checking route ${route.index + 1} of ${selected.routes.length}.`,
|
|
463
|
+
planFingerprint: selected.fingerprint,
|
|
464
|
+
routes: results,
|
|
465
|
+
interrupted: true,
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const failures = results.filter((entry) => entry.status === "failed");
|
|
470
|
+
if (failures.length > 0) {
|
|
471
|
+
const first = failures[0];
|
|
472
|
+
return {
|
|
473
|
+
ok: false,
|
|
474
|
+
kind: first?.kind ?? "provider_failed",
|
|
475
|
+
message: `${failures.length} of ${selected.routes.length} runtime route checks failed. ${first?.model ?? "Selected route"}: ${first?.message ?? "provider failure"}`,
|
|
476
|
+
planFingerprint: selected.fingerprint,
|
|
477
|
+
routes: results,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
return { ok: true, planFingerprint: selected.fingerprint, routes: results };
|
|
481
|
+
}
|
|
482
|
+
async function notifyReadinessProgress(callback, value) {
|
|
483
|
+
try {
|
|
484
|
+
await callback?.(value);
|
|
485
|
+
}
|
|
486
|
+
catch {
|
|
487
|
+
// Rendering/progress observers never alter readiness semantics.
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function boundedRouteField(value, limit) {
|
|
491
|
+
const normalized = value.replace(/[\u0000-\u001f\u007f]/gu, " ").replace(/\s+/gu, " ").trim();
|
|
492
|
+
return normalized.length <= limit ? normalized : `${normalized.slice(0, Math.max(0, limit - 1))}…`;
|
|
493
|
+
}
|
|
494
|
+
/** Run exactly one route with no fallback chain. */
|
|
495
|
+
async function runSingleReadinessProbe(options) {
|
|
496
|
+
const selectedModel = options.plan.configJson.runtime?.model;
|
|
497
|
+
if (typeof selectedModel === "string" && selectedModel.startsWith("opencode:")) {
|
|
498
|
+
return {
|
|
499
|
+
ok: false,
|
|
500
|
+
kind: "unsupported_guided_probe",
|
|
501
|
+
message: "Direct opencode:* is an advanced config-only backend and cannot run the guided no-tool readiness probe. " +
|
|
502
|
+
"Choose pi:opencode-go:<model>, or use non-interactive --model opencode:<provider>:<model> scaffolding and validate the explicit runtime.permissionMode configuration.",
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
const dir = await mkdtemp(join(tmpdir(), "mono-agent-readiness-"));
|
|
506
|
+
const secrets = options.secretValues ?? {};
|
|
507
|
+
const safeValues = redactionValues(options.hostEnv ?? process.env, secrets);
|
|
508
|
+
try {
|
|
509
|
+
const config = structuredClone(options.plan.configJson);
|
|
510
|
+
config.tools = { allowedTools: [], disallowedTools: [] };
|
|
511
|
+
const runtime = config.runtime;
|
|
512
|
+
config.runtime = { ...runtime, workspace: ".mono-agent/workspace" };
|
|
513
|
+
delete config.runtime.fallbackModels;
|
|
514
|
+
delete config.runtime.fallbacks;
|
|
515
|
+
delete config.runtime.session;
|
|
516
|
+
delete config.memory;
|
|
517
|
+
delete config.artifacts;
|
|
518
|
+
delete config.traceability;
|
|
519
|
+
delete config.observability;
|
|
520
|
+
const providers = config.providers;
|
|
521
|
+
if (providers?.piNative !== undefined && typeof providers.piNative === "object" && providers.piNative !== null) {
|
|
522
|
+
const piNative = { ...providers.piNative };
|
|
523
|
+
delete piNative.piSessionsRoot;
|
|
524
|
+
config.providers = { ...providers, piNative };
|
|
525
|
+
}
|
|
526
|
+
delete config.webhook;
|
|
527
|
+
delete config.telegram;
|
|
528
|
+
delete config.slack;
|
|
529
|
+
delete config.openaiApi;
|
|
530
|
+
delete config.cron;
|
|
531
|
+
// The target's context can point to files outside this ephemeral workspace.
|
|
532
|
+
// A probe validates a known-good, self-contained identity instead.
|
|
533
|
+
config.context = { identityPath: "./IDENTITY.md", selectedSkills: [] };
|
|
534
|
+
await writeFile(join(dir, "mono-agent.config.json"), `${JSON.stringify(config, null, 2)}\n`, { mode: 0o600 });
|
|
535
|
+
await writeFile(join(dir, "IDENTITY.md"), "# Readiness probe identity\n\nYou are checking that the selected model can produce one concise response.\n", { mode: 0o600 });
|
|
536
|
+
await mkdir(join(dir, ".mono-agent", "workspace"), { recursive: true });
|
|
537
|
+
await mkdir(join(dir, ".mono-agent", "artifacts"), { recursive: true });
|
|
538
|
+
const overlay = readinessProbeEnvironment(options.hostEnv ?? process.env, secrets, {
|
|
539
|
+
...(options.resolvedPiAuthPath === undefined ? {} : { resolvedPiAuthPath: options.resolvedPiAuthPath }),
|
|
540
|
+
});
|
|
541
|
+
const loaded = await loadAppCoreConfig({
|
|
542
|
+
cwd: dir,
|
|
543
|
+
configPath: join(dir, "mono-agent.config.json"),
|
|
544
|
+
env: overlay,
|
|
545
|
+
});
|
|
546
|
+
// This checks the generated config plus its IDENTITY.md before contacting a
|
|
547
|
+
// provider. No channel driver is loaded, no liveness probe runs, and writes
|
|
548
|
+
// are disabled; the disposable probe remains side-effect free.
|
|
549
|
+
const validation = await validateMonoAgentFolder({
|
|
550
|
+
cwd: dir,
|
|
551
|
+
configPath: join(dir, "mono-agent.config.json"),
|
|
552
|
+
env: overlay,
|
|
553
|
+
drivers: [],
|
|
554
|
+
liveness: false,
|
|
555
|
+
allowFilesystemWrites: false,
|
|
556
|
+
codexNoToolsProbe: true,
|
|
557
|
+
});
|
|
558
|
+
if (!validation.ok) {
|
|
559
|
+
return {
|
|
560
|
+
ok: false,
|
|
561
|
+
kind: "invalid_plan",
|
|
562
|
+
message: safeMessage(validation.sections.flatMap((section) => section.details).join(" "), safeValues, "The generated primary-model probe configuration is invalid."),
|
|
563
|
+
};
|
|
564
|
+
}
|
|
565
|
+
let firstToolAction;
|
|
566
|
+
const timeoutMs = positiveTimeout(options.timeoutMs, loaded.runtime.model);
|
|
567
|
+
const controller = new AbortController();
|
|
568
|
+
let resolveToolOutcome;
|
|
569
|
+
const toolOutcome = new Promise((resolve) => {
|
|
570
|
+
resolveToolOutcome = resolve;
|
|
571
|
+
});
|
|
572
|
+
let callerAbortListener;
|
|
573
|
+
const callerCancellation = new Promise((resolve) => {
|
|
574
|
+
if (options.abortSignal === undefined) {
|
|
575
|
+
return;
|
|
576
|
+
}
|
|
577
|
+
callerAbortListener = () => {
|
|
578
|
+
// Settle this race branch before aborting the provider. A runtime may
|
|
579
|
+
// synchronously reject on abort; the caller's cancellation must remain
|
|
580
|
+
// the reported reason.
|
|
581
|
+
resolve({ type: "cancelled" });
|
|
582
|
+
controller.abort();
|
|
583
|
+
};
|
|
584
|
+
if (options.abortSignal.aborted) {
|
|
585
|
+
callerAbortListener();
|
|
586
|
+
}
|
|
587
|
+
else {
|
|
588
|
+
options.abortSignal.addEventListener("abort", callerAbortListener, { once: true });
|
|
589
|
+
}
|
|
590
|
+
});
|
|
591
|
+
const runOptions = {
|
|
592
|
+
model: loaded.runtime.model,
|
|
593
|
+
...(loaded.runtime.executionMode === undefined ? {} : { executionMode: loaded.runtime.executionMode }),
|
|
594
|
+
...(loaded.runtime.effort === undefined ? {} : { effort: loaded.runtime.effort }),
|
|
595
|
+
messages: [{ role: "user", content: "Reply with a short readiness acknowledgement." }],
|
|
596
|
+
abortSignal: controller.signal,
|
|
597
|
+
cwd: dir,
|
|
598
|
+
maxTurns: 1,
|
|
599
|
+
allowedTools: [],
|
|
600
|
+
disallowedTools: [],
|
|
601
|
+
mcpServers: {},
|
|
602
|
+
// Codex app-server has no general allowlist. This dedicated mode uses a
|
|
603
|
+
// read-only/never-approve payload and fails on the first tool action.
|
|
604
|
+
codexNoToolsProbe: true,
|
|
605
|
+
onEvent: (event) => {
|
|
606
|
+
const action = toolActionInEvent(event);
|
|
607
|
+
if (action !== undefined && firstToolAction === undefined) {
|
|
608
|
+
firstToolAction = action;
|
|
609
|
+
// As with caller cancellation, publish the policy failure before
|
|
610
|
+
// aborting so an abort-triggered provider rejection cannot mask it.
|
|
611
|
+
resolveToolOutcome?.({ type: "tool", action });
|
|
612
|
+
controller.abort();
|
|
613
|
+
}
|
|
614
|
+
},
|
|
615
|
+
sessionKeepAlive: false,
|
|
616
|
+
// The production runtime receives this environment through worker
|
|
617
|
+
// isolation. The explicit immutable copy gives injected runs the same
|
|
618
|
+
// deterministic contract without replacing the host process environment.
|
|
619
|
+
providerEnv: Object.freeze({ ...overlay }),
|
|
620
|
+
};
|
|
621
|
+
let workerHandle;
|
|
622
|
+
let workerAbortListener;
|
|
623
|
+
let providerOutcome;
|
|
624
|
+
if (options.abortSignal?.aborted === true) {
|
|
625
|
+
providerOutcome = new Promise(() => { });
|
|
626
|
+
}
|
|
627
|
+
else if (options.run !== undefined) {
|
|
628
|
+
const injectedRun = options.run;
|
|
629
|
+
const runPromise = Promise.resolve().then(() => injectedRun({ config: loaded, options: runOptions }));
|
|
630
|
+
// The timeout wins independently of provider abort support. Keep an explicit
|
|
631
|
+
// rejection consumer because an ignored abort may settle after we return.
|
|
632
|
+
void runPromise.catch(() => { });
|
|
633
|
+
providerOutcome = runPromise.then((result) => ({ type: "result", result }), (error) => ({ type: "error", error }));
|
|
634
|
+
}
|
|
635
|
+
else {
|
|
636
|
+
try {
|
|
637
|
+
workerHandle = startReadinessWorker({
|
|
638
|
+
cwd: dir,
|
|
639
|
+
runtime: {
|
|
640
|
+
model: loaded.runtime.model,
|
|
641
|
+
...(loaded.runtime.executionMode === undefined
|
|
642
|
+
? {}
|
|
643
|
+
: { executionMode: loaded.runtime.executionMode }),
|
|
644
|
+
...(loaded.runtime.effort === undefined ? {} : { effort: loaded.runtime.effort }),
|
|
645
|
+
workspace: loaded.runtime.workspace,
|
|
646
|
+
artifactDir: loaded.artifacts.dir,
|
|
647
|
+
...(loaded.providers?.piAuthPath === undefined
|
|
648
|
+
? {}
|
|
649
|
+
: { piAuthPath: loaded.providers.piAuthPath }),
|
|
650
|
+
},
|
|
651
|
+
env: overlay,
|
|
652
|
+
...(options.workerUrl === undefined ? {} : { workerUrl: options.workerUrl }),
|
|
653
|
+
});
|
|
654
|
+
workerAbortListener = () => workerHandle?.abort();
|
|
655
|
+
controller.signal.addEventListener("abort", workerAbortListener, { once: true });
|
|
656
|
+
providerOutcome = workerHandle.outcome;
|
|
657
|
+
}
|
|
658
|
+
catch {
|
|
659
|
+
// Worker constructor details are not surfaced: exec arguments or a
|
|
660
|
+
// provider loader may have included sensitive values.
|
|
661
|
+
providerOutcome = Promise.resolve({
|
|
662
|
+
type: "error",
|
|
663
|
+
error: new Error("The isolated readiness worker could not be started."),
|
|
664
|
+
});
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
let timer;
|
|
668
|
+
const timeout = new Promise((resolve) => {
|
|
669
|
+
timer = setTimeout(() => {
|
|
670
|
+
resolve({ type: "timeout" });
|
|
671
|
+
controller.abort();
|
|
672
|
+
}, timeoutMs);
|
|
673
|
+
});
|
|
674
|
+
let outcome;
|
|
675
|
+
try {
|
|
676
|
+
outcome = await Promise.race([
|
|
677
|
+
providerOutcome,
|
|
678
|
+
timeout,
|
|
679
|
+
callerCancellation,
|
|
680
|
+
toolOutcome,
|
|
681
|
+
]);
|
|
682
|
+
}
|
|
683
|
+
finally {
|
|
684
|
+
if (timer !== undefined) {
|
|
685
|
+
clearTimeout(timer);
|
|
686
|
+
}
|
|
687
|
+
if (callerAbortListener !== undefined) {
|
|
688
|
+
options.abortSignal?.removeEventListener("abort", callerAbortListener);
|
|
689
|
+
}
|
|
690
|
+
if (workerAbortListener !== undefined) {
|
|
691
|
+
controller.signal.removeEventListener("abort", workerAbortListener);
|
|
692
|
+
}
|
|
693
|
+
if (workerHandle !== undefined) {
|
|
694
|
+
await workerHandle.shutdown();
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
await options.dispose?.();
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
if (outcome.type === "cancelled") {
|
|
701
|
+
return {
|
|
702
|
+
ok: false,
|
|
703
|
+
kind: "cancelled",
|
|
704
|
+
message: "The primary-model check was cancelled before completion.",
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
if (outcome.type === "tool") {
|
|
708
|
+
return {
|
|
709
|
+
ok: false,
|
|
710
|
+
kind: "tool_used",
|
|
711
|
+
message: `The selected model attempted a tool action (${outcome.action}) during the no-tool primary-model check.`,
|
|
712
|
+
};
|
|
713
|
+
}
|
|
714
|
+
if (outcome.type === "timeout") {
|
|
715
|
+
return {
|
|
716
|
+
ok: false,
|
|
717
|
+
kind: "timeout",
|
|
718
|
+
message: `The selected model did not finish the primary-model check within ${Math.ceil(timeoutMs / 1_000)} seconds.`,
|
|
719
|
+
};
|
|
720
|
+
}
|
|
721
|
+
if (outcome.type === "error") {
|
|
722
|
+
return {
|
|
723
|
+
ok: false,
|
|
724
|
+
kind: "probe_failed",
|
|
725
|
+
message: safeMessage(outcome.error, safeValues, "The primary-model check failed unexpectedly."),
|
|
726
|
+
};
|
|
727
|
+
}
|
|
728
|
+
const result = outcome.result;
|
|
729
|
+
for (const event of result.events ?? []) {
|
|
730
|
+
firstToolAction ??= toolActionInEvent(event);
|
|
731
|
+
}
|
|
732
|
+
if (firstToolAction !== undefined || result.failureKind === "tool_policy_violation") {
|
|
733
|
+
return {
|
|
734
|
+
ok: false,
|
|
735
|
+
kind: "tool_used",
|
|
736
|
+
message: `The selected model attempted a tool action (${firstToolAction ?? "provider tool"}) during the no-tool primary-model check.`,
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
if (result.cancelled === true) {
|
|
740
|
+
return {
|
|
741
|
+
ok: false,
|
|
742
|
+
kind: "cancelled",
|
|
743
|
+
message: "The selected model cancelled before completing the primary-model check.",
|
|
744
|
+
};
|
|
745
|
+
}
|
|
746
|
+
if (result.failureKind !== undefined && result.failureKind !== null) {
|
|
747
|
+
return {
|
|
748
|
+
ok: false,
|
|
749
|
+
kind: "provider_failed",
|
|
750
|
+
message: safeMessage(result.error, safeValues, `The selected model reported ${safeMessage(result.failureKind, safeValues, "a provider failure")}.`),
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
if (result.error !== undefined && result.error !== null) {
|
|
754
|
+
return {
|
|
755
|
+
ok: false,
|
|
756
|
+
kind: "provider_failed",
|
|
757
|
+
message: safeMessage(result.error, safeValues, "The selected model reported an error."),
|
|
758
|
+
};
|
|
759
|
+
}
|
|
760
|
+
if ((result.text ?? "").trim().length === 0) {
|
|
761
|
+
return {
|
|
762
|
+
ok: false,
|
|
763
|
+
kind: "empty_response",
|
|
764
|
+
message: "The selected model returned an empty primary-model response.",
|
|
765
|
+
};
|
|
766
|
+
}
|
|
767
|
+
return { ok: true };
|
|
768
|
+
}
|
|
769
|
+
catch (error) {
|
|
770
|
+
return {
|
|
771
|
+
ok: false,
|
|
772
|
+
kind: "probe_failed",
|
|
773
|
+
message: safeMessage(error, safeValues, "The primary-model check failed unexpectedly."),
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
finally {
|
|
777
|
+
await rm(dir, { recursive: true, force: true });
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
//# sourceMappingURL=readiness-probe.js.map
|