cohorte 3.0.0-dev.0 → 3.0.0-dev.2
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/assets/manifest.json +4 -4
- package/assets/schemas/reconcile-plan.schema.json +1 -2
- package/dist/agent-host.mjs +2142 -5
- package/dist/agent-host.mjs.map +1 -1
- package/dist/bundle-manifest.json +38 -36
- package/dist/chunks/{auth-D8rCVY33.mjs → auth-ybswxUPK.mjs} +54 -2
- package/dist/chunks/auth-ybswxUPK.mjs.map +1 -0
- package/dist/chunks/{brainstorm-CU10T1tm.mjs → brainstorm-BwKA6yZP.mjs} +2 -2
- package/dist/chunks/{brainstorm-CU10T1tm.mjs.map → brainstorm-BwKA6yZP.mjs.map} +1 -1
- package/dist/chunks/{catalogue-BvwSrM_j.mjs → catalogue-B5uFqfzJ.mjs} +2 -2
- package/dist/chunks/{catalogue-BvwSrM_j.mjs.map → catalogue-B5uFqfzJ.mjs.map} +1 -1
- package/dist/chunks/{compose-Cr0hP0qm.mjs → compose-C0rEh5wB.mjs} +3354 -3343
- package/dist/chunks/compose-C0rEh5wB.mjs.map +1 -0
- package/dist/chunks/{contract-B0ra2-bm.mjs → contract-BkMjU1kR.mjs} +2 -2
- package/dist/chunks/{contract-B0ra2-bm.mjs.map → contract-BkMjU1kR.mjs.map} +1 -1
- package/dist/chunks/{contract-vYbwYr0Y.mjs → contract-bQ9FJl4N.mjs} +2 -2
- package/dist/chunks/contract-bQ9FJl4N.mjs.map +1 -0
- package/dist/chunks/{diff-Qw0II0sj.mjs → diff-D_0hBwHl.mjs} +2 -2
- package/dist/chunks/{diff-Qw0II0sj.mjs.map → diff-D_0hBwHl.mjs.map} +1 -1
- package/dist/chunks/{discover-30Ciya_0.mjs → discover-eS01-me1.mjs} +3 -2
- package/dist/chunks/{discover-30Ciya_0.mjs.map → discover-eS01-me1.mjs.map} +1 -1
- package/dist/chunks/{doctor-DfCrKpfT.mjs → doctor-CginW8y8.mjs} +5 -5
- package/dist/chunks/{doctor-DfCrKpfT.mjs.map → doctor-CginW8y8.mjs.map} +1 -1
- package/dist/chunks/init-BUnzIkeE.mjs +63 -0
- package/dist/chunks/init-BUnzIkeE.mjs.map +1 -0
- package/dist/chunks/migrate-DybihKzZ.mjs +33 -0
- package/dist/chunks/migrate-DybihKzZ.mjs.map +1 -0
- package/dist/chunks/{policy-DUIgzF3W.mjs → policy-Clypo20s.mjs} +3 -3
- package/dist/chunks/{policy-DUIgzF3W.mjs.map → policy-Clypo20s.mjs.map} +1 -1
- package/dist/chunks/reconcile-Bhy3IIYo.mjs +50 -0
- package/dist/chunks/reconcile-Bhy3IIYo.mjs.map +1 -0
- package/dist/chunks/scan-8Gy_B6kI.mjs +122 -0
- package/dist/chunks/scan-8Gy_B6kI.mjs.map +1 -0
- package/dist/chunks/src-CmFjh4M0.mjs +928 -0
- package/dist/chunks/src-CmFjh4M0.mjs.map +1 -0
- package/dist/chunks/{src-CfAOde7C.mjs → src-DZazJCp1.mjs} +2 -2
- package/dist/chunks/{src-CfAOde7C.mjs.map → src-DZazJCp1.mjs.map} +1 -1
- package/dist/chunks/{src-CId6kMsY.mjs → src-r0ECB1UJ.mjs} +2 -2
- package/dist/chunks/{src-CId6kMsY.mjs.map → src-r0ECB1UJ.mjs.map} +1 -1
- package/dist/cli.mjs +12 -12
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/chunks/auth-D8rCVY33.mjs.map +0 -1
- package/dist/chunks/compose-Cr0hP0qm.mjs.map +0 -1
- package/dist/chunks/contract-vYbwYr0Y.mjs.map +0 -1
- package/dist/chunks/init-Okz4mlhW.mjs +0 -30
- package/dist/chunks/init-Okz4mlhW.mjs.map +0 -1
- package/dist/chunks/migrate-DRoFUg53.mjs +0 -19
- package/dist/chunks/migrate-DRoFUg53.mjs.map +0 -1
- package/dist/chunks/reconcile-BfD785gd.mjs +0 -26
- package/dist/chunks/reconcile-BfD785gd.mjs.map +0 -1
- package/dist/chunks/src-JxawBQBn.mjs +0 -446
- package/dist/chunks/src-JxawBQBn.mjs.map +0 -1
package/dist/agent-host.mjs
CHANGED
|
@@ -1,9 +1,1555 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { findPackageJSON } from "node:module";
|
|
3
|
-
import { readFileSync } from "node:fs";
|
|
3
|
+
import { createReadStream, createWriteStream, readFileSync } from "node:fs";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
import { Type, Type as Type$1 } from "typebox";
|
|
6
|
+
import { Compile } from "typebox/compile";
|
|
7
|
+
import { ModelsError, lazyStream } from "@earendil-works/pi-ai";
|
|
8
|
+
import { DefaultResourceLoader, ModelRuntime, SessionManager, SettingsManager, createAgentSession } from "@earendil-works/pi-coding-agent";
|
|
4
9
|
import "@earendil-works/pi-agent-core";
|
|
5
|
-
|
|
6
|
-
|
|
10
|
+
//#region ../../packages/base/src/canonical.ts
|
|
11
|
+
/** A string is hashed as UTF-8, as given: no normalisation here. */
|
|
12
|
+
function sha256Hex(data) {
|
|
13
|
+
return createHash("sha256").update(data).digest("hex");
|
|
14
|
+
}
|
|
15
|
+
//#endregion
|
|
16
|
+
//#region ../../packages/base/src/json.ts
|
|
17
|
+
const JsonValueCyclic = Type.Cyclic({ JsonValue: Type.Union([
|
|
18
|
+
Type.Null(),
|
|
19
|
+
Type.Boolean(),
|
|
20
|
+
Type.Number(),
|
|
21
|
+
Type.String(),
|
|
22
|
+
Type.Array(Type.Ref("JsonValue")),
|
|
23
|
+
Type.Record(Type.String(), Type.Ref("JsonValue"))
|
|
24
|
+
]) }, "JsonValue");
|
|
25
|
+
/**
|
|
26
|
+
* The schema of {@link JsonValue}. Its static type is the hand-written alias rather than the type derived from
|
|
27
|
+
* the cyclic schema: the two are the same type (the assertion below breaks `tsc` if they drift), but the alias
|
|
28
|
+
* costs the compiler nothing where the derived one is re-expanded inside every schema that embeds a JSON value.
|
|
29
|
+
*
|
|
30
|
+
* NOT named `JsonValue` like its type, on purpose. Biome 2.5.14 (type-aware because of nursery/noFloatingPromises)
|
|
31
|
+
* overflows its stack on a RECURSIVE UNION whose name is shared by a TypeBox const, as soon as a class that extends
|
|
32
|
+
* Error holds a value of that type — `CohorteError.info.details` does. It then exits 0, so the lint looks green
|
|
33
|
+
* while nothing was checked. DESIGN 2.1 lists `JsonValue` as a type only (it is not `[S]`), so no contract moves.
|
|
34
|
+
* KEEP THE EXPLICIT ANNOTATION too: having to INFER this call is the other way into the same overflow (a
|
|
35
|
+
* `typeof JsonValueSchema`, or a namespace import of the barrel, is enough). See docs/v3/requests/U0.02.md R1.
|
|
36
|
+
*/
|
|
37
|
+
const JsonValueSchema = Type.Unsafe(JsonValueCyclic);
|
|
38
|
+
const isPlainObject = (value) => {
|
|
39
|
+
const proto = Object.getPrototypeOf(value);
|
|
40
|
+
return proto === Object.prototype || proto === null;
|
|
41
|
+
};
|
|
42
|
+
const isJson = (value, ancestors) => {
|
|
43
|
+
if (value === null) return true;
|
|
44
|
+
switch (typeof value) {
|
|
45
|
+
case "boolean":
|
|
46
|
+
case "string": return true;
|
|
47
|
+
case "number": return Number.isFinite(value);
|
|
48
|
+
case "object": break;
|
|
49
|
+
default: return false;
|
|
50
|
+
}
|
|
51
|
+
if (ancestors.has(value)) return false;
|
|
52
|
+
ancestors.add(value);
|
|
53
|
+
try {
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
for (let index = 0; index < value.length; index += 1) if (!(index in value) || !isJson(value[index], ancestors)) return false;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
if (!isPlainObject(value)) return false;
|
|
59
|
+
return Object.values(value).every((member) => isJson(member, ancestors));
|
|
60
|
+
} finally {
|
|
61
|
+
ancestors.delete(value);
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
/** Structural guard: finite numbers only, no `undefined`, no hole, plain objects only, no cycle. Never throws on data. */
|
|
65
|
+
function isJsonValue(value) {
|
|
66
|
+
try {
|
|
67
|
+
return isJson(value, /* @__PURE__ */ new Set());
|
|
68
|
+
} catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//#endregion
|
|
73
|
+
//#region ../../packages/base/src/errors.ts
|
|
74
|
+
/** Spec 24, in spec order. Closed: a fourteenth class would be a MAJOR of both frontiers. */
|
|
75
|
+
const ERROR_CLASSES = [
|
|
76
|
+
"configuration",
|
|
77
|
+
"validation",
|
|
78
|
+
"permission",
|
|
79
|
+
"security",
|
|
80
|
+
"provider-transient",
|
|
81
|
+
"provider-terminal",
|
|
82
|
+
"tool-transient",
|
|
83
|
+
"tool-terminal",
|
|
84
|
+
"conflict",
|
|
85
|
+
"budget",
|
|
86
|
+
"timeout",
|
|
87
|
+
"corruption",
|
|
88
|
+
"human-required"
|
|
89
|
+
];
|
|
90
|
+
const ErrorClass = Type.Union([
|
|
91
|
+
Type.Literal("configuration"),
|
|
92
|
+
Type.Literal("validation"),
|
|
93
|
+
Type.Literal("permission"),
|
|
94
|
+
Type.Literal("security"),
|
|
95
|
+
Type.Literal("provider-transient"),
|
|
96
|
+
Type.Literal("provider-terminal"),
|
|
97
|
+
Type.Literal("tool-transient"),
|
|
98
|
+
Type.Literal("tool-terminal"),
|
|
99
|
+
Type.Literal("conflict"),
|
|
100
|
+
Type.Literal("budget"),
|
|
101
|
+
Type.Literal("timeout"),
|
|
102
|
+
Type.Literal("corruption"),
|
|
103
|
+
Type.Literal("human-required")
|
|
104
|
+
]);
|
|
105
|
+
const ErrorInfoCyclic = Type.Cyclic({ ErrorInfo: Type.Object({
|
|
106
|
+
code: Type.String({ pattern: "^[a-z]+(?:-[a-z]+)*/[a-z0-9]+(?:-[a-z0-9]+)*$" }),
|
|
107
|
+
class: ErrorClass,
|
|
108
|
+
message: Type.String(),
|
|
109
|
+
impact: Type.String(),
|
|
110
|
+
retryable: Type.Boolean(),
|
|
111
|
+
retryAfterMs: Type.Optional(Type.Number({ minimum: 0 })),
|
|
112
|
+
remediation: Type.String(),
|
|
113
|
+
cause: Type.Optional(Type.Ref("ErrorInfo")),
|
|
114
|
+
details: Type.Optional(Type.Record(Type.String(), JsonValueSchema))
|
|
115
|
+
}, { additionalProperties: false }) }, "ErrorInfo");
|
|
116
|
+
/**
|
|
117
|
+
* The schema of {@link ErrorInfo}, under the name of its type ([S], DESIGN §2). The static type is the DESIGN
|
|
118
|
+
* interface and the assertion below keeps it equal to what the schema derives: an `ErrorInfo` sits in dozens of
|
|
119
|
+
* event payloads, and a derived recursive type would be re-expanded in each of them.
|
|
120
|
+
*
|
|
121
|
+
* KEEP THE EXPLICIT `TUnsafe<ErrorInfo>` ANNOTATION. Biome 2.5.14 is type-aware (nursery/noFloatingPromises) and
|
|
122
|
+
* overflows its stack when it has to INFER this call for a const that shares its name with a recursive type, as
|
|
123
|
+
* soon as a class extending Error holds that type — `CohorteError.info` does. Biome then exits 0: the lint looks
|
|
124
|
+
* green while nothing was checked. With the annotation it has nothing to infer. See docs/v3/requests/U0.02.md R1.
|
|
125
|
+
*/
|
|
126
|
+
const ErrorInfo = Type.Unsafe(ErrorInfoCyclic);
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region ../../packages/base/src/catalogue.ts
|
|
129
|
+
/** DESIGN 2.8, column "CLI exit": the exit code of a process that waited for a run which ended on an error of that class. */
|
|
130
|
+
const EXIT_CODE_BY_CLASS = Object.freeze({
|
|
131
|
+
configuration: 10,
|
|
132
|
+
validation: 11,
|
|
133
|
+
permission: 12,
|
|
134
|
+
security: 13,
|
|
135
|
+
"provider-transient": 14,
|
|
136
|
+
"provider-terminal": 14,
|
|
137
|
+
"tool-transient": 15,
|
|
138
|
+
"tool-terminal": 15,
|
|
139
|
+
conflict: 16,
|
|
140
|
+
budget: 17,
|
|
141
|
+
timeout: 18,
|
|
142
|
+
corruption: 19,
|
|
143
|
+
"human-required": 20
|
|
144
|
+
});
|
|
145
|
+
const RETRY = true;
|
|
146
|
+
const NO_RETRY = false;
|
|
147
|
+
const row = (code, retryable, impact, remediation) => ({
|
|
148
|
+
code,
|
|
149
|
+
retryable,
|
|
150
|
+
impact,
|
|
151
|
+
remediation
|
|
152
|
+
});
|
|
153
|
+
/**
|
|
154
|
+
* APPEND-ONLY. A code is a public, stable identifier (spec 24): never rename one, never remove one, never move one
|
|
155
|
+
* to another class; add new rows at the END of their class block. `impact` says what the error means for the run,
|
|
156
|
+
* `remediation` is the imperative next action (spec 21). Every `<class>/unexpected` row is the landing place of a
|
|
157
|
+
* throwable nobody classified (DESIGN 2.8) and is never retried.
|
|
158
|
+
*/
|
|
159
|
+
const ERROR_CATALOGUE_ROWS = [
|
|
160
|
+
row("configuration/policy-invalid", NO_RETRY, "The security policy is missing or cannot be parsed, so nothing may execute and the run does not start.", "Fix the policy keys named in the message in .cohorte/config.yaml, then run `cohorte doctor`."),
|
|
161
|
+
row("configuration/phase-not-available", NO_RETRY, "The requested phase or pipeline profile is not part of this version, so the run does not start.", "Choose a profile that `cohorte doctor` lists as available, or upgrade Cohorte."),
|
|
162
|
+
row("configuration/incompatible-state-schema", NO_RETRY, "The state database was written by an incompatible Cohorte version and is left untouched.", "Run the Cohorte version named in the message, or back up the state and run `cohorte migrate --apply`."),
|
|
163
|
+
row("configuration/platform-unsupported", NO_RETRY, "This operating system or architecture cannot provide what the configuration requires, so the run does not start.", "Run on a supported platform, or relax the requirement named in the message in your user configuration."),
|
|
164
|
+
row("configuration/telemetry-remote-unavailable", NO_RETRY, "Remote telemetry is configured but does not exist in this version; nothing was sent.", "Remove the remote telemetry settings; local logs and metrics stay available."),
|
|
165
|
+
row("configuration/worktree-root-protected", NO_RETRY, "The configured worktree root lies inside a protected directory, so no worktree can be created there.", "Set git.worktreeRoot to a directory outside the protected roots, or remove the setting to use the default."),
|
|
166
|
+
row("configuration/provision-store-unavailable", NO_RETRY, "Dependencies cannot be provisioned offline because the package manager store is not reachable.", "Populate the package manager store (install once with network access), then start the run again."),
|
|
167
|
+
row("configuration/engine-init", NO_RETRY, "The agent runtime could not initialise its model engine, so the agent never started.", "Run `cohorte doctor` and fix the provider or model configuration it reports."),
|
|
168
|
+
row("configuration/fake-script-unmatched", NO_RETRY, "The fake runtime script has no entry for this spawn request, so the scripted run cannot continue.", "Add a matching entry to the fake runtime script, or correct the spawn fields it matches on."),
|
|
169
|
+
row("configuration/unexpected", NO_RETRY, "An unclassified configuration failure stopped the run before or while it started.", "Read the message, run `cohorte doctor`, and report the error if the configuration looks correct."),
|
|
170
|
+
row("validation/tool-input", NO_RETRY, "A tool call did not match the input schema of the tool and was not executed.", "Nothing to do if the agent recovers; otherwise inspect the rejected call with `cohorte logs`."),
|
|
171
|
+
row("validation/agent-output", NO_RETRY, "The final output of the agent does not match the schema of its phase, so it cannot be used.", "Inspect the agent transcript; retry the phase or escalate the model tier."),
|
|
172
|
+
row("validation/agent-no-result", NO_RETRY, "The agent stopped without delivering a structured result, so its phase has no outcome.", "Retry the phase; if it repeats, inspect the transcript and escalate the model tier."),
|
|
173
|
+
row("validation/spec", NO_RETRY, "The spec file is invalid, so the run cannot be planned from it.", "Fix the fields named in the message in the spec file, then start the run again."),
|
|
174
|
+
row("validation/invalid-id", NO_RETRY, "A value is not a well-formed identifier of the expected kind and was refused before reaching a path, a ref or the store.", "Pass an identifier of the shape named in the message, as printed by `cohorte status`."),
|
|
175
|
+
row("validation/unexpected", NO_RETRY, "An unclassified validation failure made a value unusable.", "Read the message, inspect the offending input with `cohorte logs`, and report the error if the input looks valid."),
|
|
176
|
+
row("permission/tool-not-granted", NO_RETRY, "The agent called a tool its role was not granted; the call was denied and nothing executed.", "Nothing to do if the agent adapts; grant the tool to the role in the configuration if the call was legitimate."),
|
|
177
|
+
row("permission/path-outside-grant", NO_RETRY, "The agent addressed a path outside what its grant allows; the call was denied and nothing was read or written.", "Nothing to do if the agent adapts; widen the ownership of the surface if the path legitimately belongs to it."),
|
|
178
|
+
row("permission/command-not-allowed", NO_RETRY, "The command is not in the allowlist of the command policy; it was denied and nothing executed.", "Add a rule for the command to the command policy if it is legitimate, then resume."),
|
|
179
|
+
row("permission/network-denied", NO_RETRY, "The call needs network access, which agents never have in this version; it was denied.", "Provide what the agent needed locally, for example through provisioning, then resume."),
|
|
180
|
+
row("permission/denied-by-human", NO_RETRY, "A human denied the approval request; the call was not executed.", "Nothing to do; approve a new request if the denial was a mistake."),
|
|
181
|
+
row("permission/unexpected", NO_RETRY, "An unclassified permission failure denied the call; nothing executed.", "Read the message and inspect the denied call with `cohorte logs`."),
|
|
182
|
+
row("security/symlink-escape", NO_RETRY, "A path resolves through a symbolic link to a location outside its allowed root; the run is blocked and its worktrees are frozen.", "Inspect the link named in the message, remove it if it is hostile, then acknowledge with `cohorte resume --ack`."),
|
|
183
|
+
row("security/protected-path", NO_RETRY, "The agent targeted a protected path such as .git, .cohorte or a credential file; the run is blocked.", "Inspect the attempted call with `cohorte logs`, then acknowledge with `cohorte resume --ack` or cancel the run."),
|
|
184
|
+
row("security/write-outside-ownership", NO_RETRY, "Changes were found outside the ownership of the surface that produced them; they are not integrated and the run is blocked.", "Inspect the diff of the frozen worktree, revert or re-assign the files, then acknowledge with `cohorte resume --ack`."),
|
|
185
|
+
row("security/runtime-pin-mismatch", NO_RETRY, "The installed runtime no longer matches the pin taken when the run started; no agent is spawned.", "Reinstall the pinned Cohorte and runtime versions named in the message, then resume."),
|
|
186
|
+
row("security/asset-hash-mismatch", NO_RETRY, "An embedded prompt, skill, schema or migration does not match its recorded hash; the installation is not trusted.", "Reinstall Cohorte from the registry and compare with `cohorte doctor`."),
|
|
187
|
+
row("security/pin-tampered", NO_RETRY, "The recorded runtime pin or run snapshot was modified after the run started; the run is blocked.", "Inspect the run directory for tampering; start a new run if the snapshot cannot be trusted."),
|
|
188
|
+
row("security/runtime-inside-target", NO_RETRY, "Cohorte is running from inside the repository it would modify, so agents could rewrite their own supervisor; the run does not start.", "Run an installed Cohorte that lives outside the target repository."),
|
|
189
|
+
row("security/auth-mode-violation", NO_RETRY, "A request was about to use another billing mode than the one the run plan allows; it was stopped before leaving the machine.", "Check the provider authentication with `cohorte auth status`; opt in to metered billing explicitly if that is intended."),
|
|
190
|
+
row("security/auth-endpoint-mismatch", NO_RETRY, "The provider endpoint differs from the pinned one for this authentication mode; no credential was sent.", "Remove the endpoint override, or pin the endpoint in your user configuration if it is legitimate."),
|
|
191
|
+
row("security/command-auth-invalid", NO_RETRY, "A control command carried a missing, unknown or wrong authenticator; it was rejected and never applied.", "Send the command again through the `cohorte` CLI as the user who owns the project key."),
|
|
192
|
+
row("security/event-chain-broken", NO_RETRY, "The event history of the run fails its hash chain or anchor check, so it may have been rewritten; the run is blocked.", "Inspect the state database and its backups; never delete the run, restore a verified backup instead."),
|
|
193
|
+
row("security/root-refused", NO_RETRY, "Cohorte refuses to run as root; nothing was started.", "Run Cohorte as an unprivileged user."),
|
|
194
|
+
row("security/sandbox-unavailable", NO_RETRY, "The required operating-system sandbox is not available, so commands would run unconfined; the run does not start.", "Follow the sandbox remediation printed by `cohorte doctor`, or opt in to best-effort isolation in your user configuration."),
|
|
195
|
+
row("security/secret-staged", NO_RETRY, "A secret was detected in the changes about to be committed; the commit was not created and the run is blocked.", "Remove the secret from the worktree and rotate it, then acknowledge with `cohorte resume --ack`."),
|
|
196
|
+
row("security/command-trampoline", NO_RETRY, "The command would launch another program on behalf of the agent (a shell, an interpreter flag, an exec wrapper); it was denied.", "Inspect the attempted command with `cohorte logs`; expose the real program through the command policy if it is legitimate."),
|
|
197
|
+
row("security/project-policy-untrusted", NO_RETRY, "The configuration of the repository loosens security settings that only the local user may loosen; the run does not start.", "Review the keys named in the message, then grant them with `cohorte config trust` or set them in your user configuration."),
|
|
198
|
+
row("security/deps-tampered", NO_RETRY, "Provisioned dependencies changed after they were installed and sealed; the run is blocked.", "Inspect the slot named in the message, then provision it again from the lockfile."),
|
|
199
|
+
row("security/review-ref-mutated", NO_RETRY, "The immutable ref under review changed while it was being reviewed; the review result cannot be trusted.", "Inspect who moved the ref, then run the review again on a fresh ref."),
|
|
200
|
+
row("security/command-global-option", NO_RETRY, "The command carries a global option that redirects the program (for git: -C, -c, --git-dir, --work-tree); it was denied.", "Inspect the attempted command with `cohorte logs`; no configuration allows these options."),
|
|
201
|
+
row("security/gate-internal-error", NO_RETRY, "A stage of the permission gate failed unexpectedly; the call was denied because the gate fails closed.", "Report the error with the run logs; resume once the cause is understood."),
|
|
202
|
+
row("security/redaction-failed", NO_RETRY, "The redactor failed on an event; the payload was dropped instead of being stored unredacted.", "Report the error with the run logs; the dropped payload is not recoverable."),
|
|
203
|
+
row("security/unexpected", NO_RETRY, "An unclassified security failure blocked the run; its worktrees are frozen.", "Inspect the run with `cohorte logs` before acknowledging with `cohorte resume --ack`."),
|
|
204
|
+
row("provider-transient/rate-limited", RETRY, "The provider is rate limiting requests; the request is retried with a bounded back-off.", "Nothing to do; if it persists, lower the agent concurrency or wait for the limit to reset."),
|
|
205
|
+
row("provider-transient/overloaded", RETRY, "The provider is overloaded or returned a server error; the request is retried with a bounded back-off.", "Nothing to do; check the status page of the provider if it persists."),
|
|
206
|
+
row("provider-transient/network", RETRY, "The connection to the provider failed; the request is retried with a bounded back-off.", "Check the network connection and any proxy settings if it persists."),
|
|
207
|
+
row("provider-transient/credential-store-locked", RETRY, "The credential store is locked by another process; the request is retried shortly.", "Close other programs that use the same login if it persists."),
|
|
208
|
+
row("provider-transient/unexpected", NO_RETRY, "An unclassified provider failure interrupted a model request; it is not retried because its cause is unknown.", "Read the message, then resume the run; report the error if it repeats."),
|
|
209
|
+
row("provider-terminal/auth-required", NO_RETRY, "The provider needs a new login; the run is suspended until authentication is restored.", "Run `cohorte auth login`, then `cohorte resume`."),
|
|
210
|
+
row("provider-terminal/quota-exceeded", NO_RETRY, "The plan limit of the provider is reached; the run is suspended until the quota resets.", "Wait for the reset time shown by `cohorte status`, then resume; automatic resume does it when enabled."),
|
|
211
|
+
row("provider-terminal/entitlement", NO_RETRY, "The account is not entitled to this model or usage; the request cannot succeed as configured.", "Choose a model your plan includes, or change the plan with the provider."),
|
|
212
|
+
row("provider-terminal/model-not-found", NO_RETRY, "The configured model does not exist for this provider; the agent cannot start.", "Correct the model in the tier table of the configuration; `cohorte doctor` lists the known models."),
|
|
213
|
+
row("provider-terminal/policy-refused", NO_RETRY, "The provider refused the request under its usage policies; repeating it unchanged will not help.", "Inspect the refused request in the transcript and rephrase the task or the spec."),
|
|
214
|
+
row("provider-terminal/unexpected", NO_RETRY, "An unclassified provider failure ended a model request for good.", "Read the message and the transcript; report the error if the request looks valid."),
|
|
215
|
+
row("tool-transient/spawn-failed", RETRY, "The process of a tool could not be started; the call is retried once.", "Check that the program exists on the pinned PATH reported by `cohorte doctor` if it persists."),
|
|
216
|
+
row("tool-transient/interrupted", RETRY, "A tool call was interrupted before it finished, for example by a host restart; it is replayed when that is safe.", "Nothing to do; inspect the effect journal with `cohorte inspect` if the call is reported in doubt."),
|
|
217
|
+
row("tool-transient/agent-process-exit", RETRY, "The process of the agent exited without settling; a fresh incarnation takes over from the file ledger.", "Nothing to do; check the memory and the logs of the machine if it repeats."),
|
|
218
|
+
row("tool-transient/cancelled", RETRY, "The tool call was cancelled before it finished because its agent or its run was paused or cancelled.", "Nothing to do; resume the run to let the agent issue the call again."),
|
|
219
|
+
row("tool-transient/unexpected", NO_RETRY, "An unclassified failure interrupted a tool call; it is not retried because its cause is unknown.", "Read the message and inspect the call with `cohorte logs`; report the error if it repeats."),
|
|
220
|
+
row("tool-terminal/nonzero-exit", NO_RETRY, "The command exited with a non-zero status; the result was handed to the agent.", "Nothing to do; the agent sees the output and decides what to do next."),
|
|
221
|
+
row("tool-terminal/output-cap", NO_RETRY, "The output of the tool exceeded its cap and was truncated or the process was stopped.", "Nothing to do; narrow the command if the agent keeps hitting the cap."),
|
|
222
|
+
row("tool-terminal/patch-preimage-mismatch", NO_RETRY, "The file no longer matches the content the patch was written against; nothing was written.", "Nothing to do; the agent must read the file again before patching it."),
|
|
223
|
+
row("tool-terminal/unexpected", NO_RETRY, "An unclassified failure ended a tool call; an error result was handed to the agent.", "Read the message and inspect the call with `cohorte logs`."),
|
|
224
|
+
row("conflict/merge", NO_RETRY, "The branch of a surface does not merge cleanly into the integration branch; the conflict goes to a fix round or to a human.", "Let the fix round resolve it, or resolve the conflict on the integration branch and resume."),
|
|
225
|
+
row("conflict/zone-reserved", NO_RETRY, "Another agent holds the lock on an overlapping zone of files; the writer must wait for it.", "Nothing to do; it clears when the holder finishes. Cancel the holder if it is stuck."),
|
|
226
|
+
row("conflict/incarnation-exists", NO_RETRY, "An incarnation of this agent is already registered; a second one was refused.", "Nothing to do; if no such process is alive, `cohorte resume` sweeps the stale incarnation."),
|
|
227
|
+
row("conflict/command-id-reuse", NO_RETRY, "A command identifier was reused with a different body; the second command was rejected.", "Send the command again with a fresh identifier."),
|
|
228
|
+
row("conflict/run-host-alive", NO_RETRY, "A run host already drives this run; a second one was refused.", "Observe the run with `cohorte tail`; stop the other host first if you mean to replace it."),
|
|
229
|
+
row("conflict/lease-lost", NO_RETRY, "This run host lost its lease to a newer one and was fenced off; it stopped writing.", "Nothing to do; the newer host owns the run. Observe it with `cohorte status`."),
|
|
230
|
+
row("conflict/reconcile-human-edit", NO_RETRY, "A generated file was edited by hand since it was rendered; reconcile will not overwrite it.", "Keep your edit and mark the field as overridden, or restore the generated content, then plan the reconcile again."),
|
|
231
|
+
row("conflict/not-running", NO_RETRY, "The command applies to a running pipeline only and the run is not running; it was rejected.", "Check the state with `cohorte status` and use the command that fits it."),
|
|
232
|
+
row("conflict/run-terminal", NO_RETRY, "The run has already ended; the command was rejected.", "Start a new run; a completed or cancelled run cannot change."),
|
|
233
|
+
row("conflict/run-active", NO_RETRY, "The run is active, and this command applies to a halted run only; it was rejected.", "Wait for the run to halt, or pause it, before sending this command."),
|
|
234
|
+
row("conflict/run-halted", NO_RETRY, "The run is halted (FAILED or BLOCKED) rather than running; the command was rejected. A halted run is not over.", "Resume it the way its state allows — `cohorte retry` out of FAILED, `cohorte resume --ack` out of BLOCKED."),
|
|
235
|
+
row("conflict/use-retry", NO_RETRY, "The run is FAILED, and this command resumes a suspended run only; it was rejected.", "Use `cohorte retry` to resume a FAILED run."),
|
|
236
|
+
row("conflict/use-resume", NO_RETRY, "The run is suspended, and this command resumes a FAILED run only; it was rejected.", "Use `cohorte resume` to resume a suspended run."),
|
|
237
|
+
row("conflict/use-resume-ack", NO_RETRY, "The run is BLOCKED by a security stop, and this command does not acknowledge it; it was rejected.", "Read the stop record, then use `cohorte resume --ack` to acknowledge it and resume."),
|
|
238
|
+
row("conflict/run-blocked", NO_RETRY, "The run is BLOCKED by a security stop; the command was rejected while the stop stands.", "Read the stop record and resume with `cohorte resume --ack` before sending this command again."),
|
|
239
|
+
row("conflict/unexpected", NO_RETRY, "An unclassified conflict stopped an operation; the state it competed for was left as it was.", "Check the state with `cohorte status`, then repeat the operation."),
|
|
240
|
+
row("budget/tokens", NO_RETRY, "A token budget is exhausted; the run waits for a human decision.", "Raise the budget with `cohorte approve`, or cancel the run."),
|
|
241
|
+
row("budget/tool-calls-exhausted", NO_RETRY, "The tool-call budget of the agent is exhausted; further calls are refused.", "Raise the tool-call budget with `cohorte approve`, or let the agent conclude with what it has."),
|
|
242
|
+
row("budget/context-window", NO_RETRY, "The conversation no longer fits the context window of the model; a fresh incarnation continues with a smaller context built by Cohorte.", "Nothing to do; split the task or narrow the ownership if it repeats."),
|
|
243
|
+
row("budget/fix-rounds", NO_RETRY, "The maximum number of fix rounds is reached with findings still open; the run waits for a human decision.", "Review the open findings, then raise the limit with `cohorte approve` or take over by hand."),
|
|
244
|
+
row("budget/provider", NO_RETRY, "The budget configured for this provider is exhausted; the run waits for a human decision.", "Raise the provider budget with `cohorte approve`, or cancel the run."),
|
|
245
|
+
row("budget/estimated-quota", NO_RETRY, "The estimated share of the plan quota used by this run reached its limit; the run waits for a human decision.", "Raise the limit with `cohorte approve`, or wait for the quota window to reset."),
|
|
246
|
+
row("budget/incarnations", NO_RETRY, "The agent was restarted as many times as allowed and is now failed.", "Inspect why the agent keeps dying with `cohorte logs`, then retry the phase."),
|
|
247
|
+
row("budget/unexpected", NO_RETRY, "An unclassified budget failure stopped the run from spending more.", "Check the counters with `cohorte status`, then approve a higher budget or cancel the run."),
|
|
248
|
+
row("timeout/tool", RETRY, "A tool call exceeded its time limit and its process tree was killed; the call is retried once.", "Raise the timeout of the command in the configuration if the command is legitimately slow."),
|
|
249
|
+
row("timeout/model-request", RETRY, "A model request exceeded its time limit; it is retried with a bounded back-off.", "Nothing to do; check the network and the status of the provider if it persists."),
|
|
250
|
+
row("timeout/agent", RETRY, "The agent exceeded its wall-clock limit and was stopped; the retry policy decides what follows.", "Raise the agent time limit, or split the task, if the work is legitimately long."),
|
|
251
|
+
row("timeout/run", NO_RETRY, "The run exceeded its wall-clock budget; it waits for a human decision.", "Extend the run with `cohorte approve`, or cancel it."),
|
|
252
|
+
row("timeout/unexpected", NO_RETRY, "An unclassified timeout stopped an operation; it is not retried because its cause is unknown.", "Read the message, then resume the run; report the error if it repeats."),
|
|
253
|
+
row("corruption/event-gap", NO_RETRY, "The event sequence of the run has a gap or a duplicate; the store refuses to open it and deletes nothing.", "Restore the state database from a backup; keep the damaged file for inspection."),
|
|
254
|
+
row("corruption/projection-mismatch", NO_RETRY, "The stored projections disagree with what the events replay to; the run is not resumed from them.", "Run `cohorte doctor --verify-state` to see which projection differs from the event log, and report it; the events are the truth."),
|
|
255
|
+
row("corruption/snapshot-hash", NO_RETRY, "A state snapshot does not match its recorded hash; it is ignored and an older one is used when available.", "Nothing to do if resume succeeds; otherwise restore the state database from a backup."),
|
|
256
|
+
row("corruption/incompatible-schema", NO_RETRY, "The schema of the state database is not one this version can read; it refuses to open it and never migrates on its own.", "Run the Cohorte version that wrote the database, or back up the state and run `cohorte migrate --apply`."),
|
|
257
|
+
row("corruption/unexpected", NO_RETRY, "An unclassified integrity failure made stored state untrustworthy; nothing was deleted.", "Back up the .cohorte/state directory before anything else, then run `cohorte doctor`."),
|
|
258
|
+
row("human-required/approval", NO_RETRY, "A call needs the approval of a human; the run waits and nothing executes meanwhile.", "Review the request with `cohorte status`, then `cohorte approve` or `cohorte deny`."),
|
|
259
|
+
row("human-required/approval-timeout", NO_RETRY, "An approval request expired without an answer; the call was not executed.", "Resume the run to let the agent ask again, then answer in time."),
|
|
260
|
+
row("human-required/blocked-ack", NO_RETRY, "The run is blocked after a security event and needs a human to inspect it.", "Inspect the run with `cohorte logs`, then acknowledge with `cohorte resume --ack` or cancel it."),
|
|
261
|
+
row("human-required/in-doubt-effect", NO_RETRY, "An effect was interrupted and cannot be proven done or not done; repeating it blindly could apply it twice.", "Inspect the effect with `cohorte inspect`, then resolve it by hand and resume."),
|
|
262
|
+
row("human-required/unexpected", NO_RETRY, "An unclassified situation needs a human decision; the run waits.", "Check what the run waits for with `cohorte status`, then answer it.")
|
|
263
|
+
];
|
|
264
|
+
const classOf = (code) => {
|
|
265
|
+
const prefix = code.slice(0, code.indexOf("/"));
|
|
266
|
+
const found = ERROR_CLASSES.find((candidate) => candidate === prefix);
|
|
267
|
+
if (!found) throw new TypeError(`error catalogue: ${code} does not start with an error class`);
|
|
268
|
+
return found;
|
|
269
|
+
};
|
|
270
|
+
const build = () => {
|
|
271
|
+
const catalogue = Object.create(null);
|
|
272
|
+
for (const { code, retryable, impact, remediation } of ERROR_CATALOGUE_ROWS) {
|
|
273
|
+
if (code in catalogue) throw new TypeError(`error catalogue: ${code} is listed twice`);
|
|
274
|
+
const errorClass = classOf(code);
|
|
275
|
+
catalogue[code] = Object.freeze({
|
|
276
|
+
class: errorClass,
|
|
277
|
+
retryable,
|
|
278
|
+
impact,
|
|
279
|
+
remediation,
|
|
280
|
+
exit: EXIT_CODE_BY_CLASS[errorClass]
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
return Object.freeze(catalogue);
|
|
284
|
+
};
|
|
285
|
+
/** DESIGN 2.8 (PLAN PC-1: the data lives in base so that L2 packages can mint complete `ErrorInfo`s). */
|
|
286
|
+
const ERROR_CATALOGUE = build();
|
|
287
|
+
function isErrorCode(code) {
|
|
288
|
+
return Object.hasOwn(ERROR_CATALOGUE, code);
|
|
289
|
+
}
|
|
290
|
+
//#endregion
|
|
291
|
+
//#region ../../packages/base/src/taxonomy.ts
|
|
292
|
+
const MAX_MESSAGE_LENGTH = 2e3;
|
|
293
|
+
/** "single paragraph" (DESIGN 2.1): one line, bounded. Redaction is NOT done here: only a Redactor seals. */
|
|
294
|
+
const paragraph = (text) => {
|
|
295
|
+
const flat = text.replace(/\s+/g, " ").trim();
|
|
296
|
+
return flat.length > MAX_MESSAGE_LENGTH ? `${flat.slice(0, 1999)}…` : flat;
|
|
297
|
+
};
|
|
298
|
+
/** Returns `info` itself when its chain is already within bounds, a truncated copy otherwise. */
|
|
299
|
+
const capCause = (info, remaining) => {
|
|
300
|
+
if (!info.cause) return info;
|
|
301
|
+
if (remaining === 0) {
|
|
302
|
+
const { cause: _dropped, ...rest } = info;
|
|
303
|
+
return {
|
|
304
|
+
...rest,
|
|
305
|
+
details: {
|
|
306
|
+
...rest.details,
|
|
307
|
+
causeChainTruncated: true
|
|
308
|
+
}
|
|
309
|
+
};
|
|
310
|
+
}
|
|
311
|
+
const capped = capCause(info.cause, remaining - 1);
|
|
312
|
+
return capped === info.cause ? info : {
|
|
313
|
+
...info,
|
|
314
|
+
cause: capped
|
|
315
|
+
};
|
|
316
|
+
};
|
|
317
|
+
/**
|
|
318
|
+
* Mints a complete `ErrorInfo` from the catalogue: class, retryability, impact and remediation come from the row of
|
|
319
|
+
* `code`, never from the call site (PLAN PC-1).
|
|
320
|
+
*/
|
|
321
|
+
function errorOf(code, message, extra = {}) {
|
|
322
|
+
const entry = ERROR_CATALOGUE[code];
|
|
323
|
+
if (!entry || !isErrorCode(code)) throw new TypeError(`errorOf: ${String(code)} is not in ERROR_CATALOGUE`);
|
|
324
|
+
const { retryAfterMs, cause, details } = extra;
|
|
325
|
+
const info = {
|
|
326
|
+
code,
|
|
327
|
+
class: entry.class,
|
|
328
|
+
message: paragraph(message),
|
|
329
|
+
impact: entry.impact,
|
|
330
|
+
retryable: entry.retryable,
|
|
331
|
+
...isRetryAfterMs(retryAfterMs) ? { retryAfterMs } : {},
|
|
332
|
+
remediation: entry.remediation,
|
|
333
|
+
...cause === void 0 || cause === null ? {} : { cause: isCanonical(cause, 5) ? cause : wireSafe(cause, 4, UNCLASSIFIED) },
|
|
334
|
+
...details === void 0 ? {} : { details: isDetails(details) ? details : { detailsDropped: true } }
|
|
335
|
+
};
|
|
336
|
+
return capCause(info, 5);
|
|
337
|
+
}
|
|
338
|
+
/** Where an error lands when even the caller's fallback class is not one of the thirteen (a JS caller, a cast, a decoded wire value). */
|
|
339
|
+
const UNCLASSIFIED = "validation/unexpected";
|
|
340
|
+
const isClassified = (info) => {
|
|
341
|
+
try {
|
|
342
|
+
return isErrorCode(info.code) && ERROR_CATALOGUE[info.code]?.class === info.class;
|
|
343
|
+
} catch {
|
|
344
|
+
return false;
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
const INFO_KEYS = /* @__PURE__ */ new Set([
|
|
348
|
+
"code",
|
|
349
|
+
"class",
|
|
350
|
+
"message",
|
|
351
|
+
"impact",
|
|
352
|
+
"retryable",
|
|
353
|
+
"retryAfterMs",
|
|
354
|
+
"remediation",
|
|
355
|
+
"cause",
|
|
356
|
+
"details"
|
|
357
|
+
]);
|
|
358
|
+
const isRetryAfterMs = (value) => typeof value === "number" && Number.isFinite(value) && value >= 0;
|
|
359
|
+
const isDetails = (value) => typeof value === "object" && value !== null && !Array.isArray(value) && isJsonValue(value);
|
|
360
|
+
/**
|
|
361
|
+
* True when `info` is exactly what `errorOf` would mint: strict JSON, a catalogue code under its own class, the
|
|
362
|
+
* catalogue's impact, retryability and remediation, no foreign member, and a cause chain of the same kind no deeper
|
|
363
|
+
* than `remaining`. "Is JSON" alone is not enough: a cast can carry a security code as a retryable tool error.
|
|
364
|
+
*/
|
|
365
|
+
const isCanonical = (info, remaining) => {
|
|
366
|
+
try {
|
|
367
|
+
return isJsonValue(info) && isClassified(info) && hasCanonicalMembers(info, remaining);
|
|
368
|
+
} catch {
|
|
369
|
+
return false;
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
const hasCanonicalMembers = (info, remaining) => {
|
|
373
|
+
const raw = info;
|
|
374
|
+
const entry = isErrorCode(info.code) ? ERROR_CATALOGUE[info.code] : void 0;
|
|
375
|
+
if (!entry || raw.impact !== entry.impact || raw.retryable !== entry.retryable) return false;
|
|
376
|
+
if (raw.remediation !== entry.remediation || typeof raw.message !== "string") return false;
|
|
377
|
+
if (!Object.keys(info).every((key) => INFO_KEYS.has(key))) return false;
|
|
378
|
+
if ("retryAfterMs" in info && !isRetryAfterMs(raw.retryAfterMs)) return false;
|
|
379
|
+
if ("details" in info && !isDetails(raw.details)) return false;
|
|
380
|
+
if (!("cause" in info)) return true;
|
|
381
|
+
return remaining > 0 && typeof raw.cause === "object" && raw.cause !== null && isCanonical(info.cause, remaining - 1);
|
|
382
|
+
};
|
|
383
|
+
/**
|
|
384
|
+
* Rebuilds an `ErrorInfo` that is not what `errorOf` mints (an `undefined` optional, a Date in `details`, a cast, a
|
|
385
|
+
* call-site `retryable`) WITHOUT reclassifying it: a `security/*` error must stay one across every boundary (I2).
|
|
386
|
+
* The catalogue row of its own code restores class, impact, retryability and remediation; `fallback` is used only
|
|
387
|
+
* when the code is not a catalogue code of the claimed class. Never throws.
|
|
388
|
+
*/
|
|
389
|
+
const wireSafe = (info, remaining, fallback) => {
|
|
390
|
+
if (isCanonical(info, remaining)) return info;
|
|
391
|
+
try {
|
|
392
|
+
const raw = info;
|
|
393
|
+
const code = isClassified(info) && isErrorCode(info.code) ? info.code : fallback;
|
|
394
|
+
const extra = {};
|
|
395
|
+
if (isRetryAfterMs(raw.retryAfterMs)) extra.retryAfterMs = raw.retryAfterMs;
|
|
396
|
+
let truncated = false;
|
|
397
|
+
if (typeof raw.cause === "object" && raw.cause !== null) {
|
|
398
|
+
if (remaining > 0) extra.cause = wireSafe(raw.cause, remaining - 1, fallback);
|
|
399
|
+
else truncated = true;
|
|
400
|
+
}
|
|
401
|
+
if (raw.details !== void 0) extra.details = isDetails(raw.details) ? raw.details : { detailsDropped: true };
|
|
402
|
+
if (truncated) extra.details = {
|
|
403
|
+
...extra.details,
|
|
404
|
+
causeChainTruncated: true
|
|
405
|
+
};
|
|
406
|
+
return errorOf(code, typeof raw.message === "string" ? raw.message : "unknown error", extra);
|
|
407
|
+
} catch {
|
|
408
|
+
return errorOf(fallback, "unknown error (unreadable)", { details: { thrown: "unreadable" } });
|
|
409
|
+
}
|
|
410
|
+
};
|
|
411
|
+
//#endregion
|
|
412
|
+
//#region ../../packages/base/src/ids.ts
|
|
413
|
+
/** Token-safe for François' single ${token} slot, for refs and for paths. */
|
|
414
|
+
const ID_PATTERN = "^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$";
|
|
415
|
+
const ISO_INSTANT_PATTERN = "^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}\\.[0-9]{3}Z$";
|
|
416
|
+
/** The shape each kind of DESIGN 2.1 has, as a pattern (for schemas) and as words (for a human). */
|
|
417
|
+
const ID_SHAPES = {
|
|
418
|
+
RunId: {
|
|
419
|
+
pattern: "^run_[0-9a-f]{32}$",
|
|
420
|
+
shape: "run_<32 lowercase hex digits>"
|
|
421
|
+
},
|
|
422
|
+
AgentId: {
|
|
423
|
+
pattern: "^agt_[A-Za-z0-9.-]+_[A-Za-z0-9_.-]+$",
|
|
424
|
+
shape: "agt_<role>_<surface|main>[_<n>]"
|
|
425
|
+
},
|
|
426
|
+
PhaseRunId: {
|
|
427
|
+
pattern: "^phs_[A-Z][A-Z_]*_[0-9]+$",
|
|
428
|
+
shape: "phs_<STATE>_<iteration>"
|
|
429
|
+
},
|
|
430
|
+
EventId: {
|
|
431
|
+
pattern: "^evt_[0-9a-f]{32}$",
|
|
432
|
+
shape: "evt_<32 lowercase hex digits>"
|
|
433
|
+
},
|
|
434
|
+
CommandId: {
|
|
435
|
+
pattern: "^cmd_[0-9a-f]{32}$",
|
|
436
|
+
shape: "cmd_<32 lowercase hex digits>"
|
|
437
|
+
},
|
|
438
|
+
ApprovalId: {
|
|
439
|
+
pattern: "^apr_[0-9a-f]{32}$",
|
|
440
|
+
shape: "apr_<32 lowercase hex digits>"
|
|
441
|
+
},
|
|
442
|
+
ToolCallId: {
|
|
443
|
+
pattern: "^tc_[0-9]+_[0-9]+$",
|
|
444
|
+
shape: "tc_<incarnation>_<ordinal>"
|
|
445
|
+
},
|
|
446
|
+
EffectId: {
|
|
447
|
+
pattern: "^eff_[0-9a-f]{32}$",
|
|
448
|
+
shape: "eff_<32 lowercase hex digits>"
|
|
449
|
+
},
|
|
450
|
+
ArtifactId: {
|
|
451
|
+
pattern: "^art_[0-9a-f]{32}$",
|
|
452
|
+
shape: "art_<32 lowercase hex digits>"
|
|
453
|
+
},
|
|
454
|
+
FindingId: {
|
|
455
|
+
pattern: "^fnd_[0-9a-f]{16}$",
|
|
456
|
+
shape: "fnd_<16 lowercase hex digits>"
|
|
457
|
+
},
|
|
458
|
+
SpecId: {
|
|
459
|
+
pattern: ID_PATTERN,
|
|
460
|
+
shape: "letters, digits, \"_\", \".\" and \"-\" (at most 128, not starting with \".\" or \"-\")"
|
|
461
|
+
},
|
|
462
|
+
SurfaceId: {
|
|
463
|
+
pattern: ID_PATTERN,
|
|
464
|
+
shape: "letters, digits, \"_\", \".\" and \"-\" (at most 128, not starting with \".\" or \"-\")"
|
|
465
|
+
},
|
|
466
|
+
Sha256: {
|
|
467
|
+
pattern: "^[0-9a-f]{64}$",
|
|
468
|
+
shape: "64 lowercase hex digits"
|
|
469
|
+
},
|
|
470
|
+
IsoInstant: {
|
|
471
|
+
pattern: ISO_INSTANT_PATTERN,
|
|
472
|
+
shape: "an RFC 3339 UTC instant with milliseconds, such as 2026-01-01T00:00:00.000Z"
|
|
473
|
+
}
|
|
474
|
+
};
|
|
475
|
+
ID_SHAPES.SpecId;
|
|
476
|
+
const compiled = /* @__PURE__ */ new Map();
|
|
477
|
+
const regExpOf = (pattern) => {
|
|
478
|
+
let found = compiled.get(pattern);
|
|
479
|
+
if (!found) {
|
|
480
|
+
found = new RegExp(pattern);
|
|
481
|
+
compiled.set(pattern, found);
|
|
482
|
+
}
|
|
483
|
+
return found;
|
|
484
|
+
};
|
|
485
|
+
/** Renders a date as an IsoInstant. Throws a RangeError on an invalid date or a year outside 0000-9999. */
|
|
486
|
+
function toIsoInstant(value) {
|
|
487
|
+
const text = new Date(value).toISOString();
|
|
488
|
+
if (!regExpOf(ISO_INSTANT_PATTERN).test(text)) throw new RangeError(`toIsoInstant: ${text} is outside the years 0000-9999`);
|
|
489
|
+
return text;
|
|
490
|
+
}
|
|
491
|
+
const idSchema = (kind, options = {}) => Type.Unsafe(Type.String({
|
|
492
|
+
...options,
|
|
493
|
+
pattern: ID_SHAPES[kind].pattern
|
|
494
|
+
}));
|
|
495
|
+
const RunId = idSchema("RunId");
|
|
496
|
+
const AgentId = idSchema("AgentId", { maxLength: 128 });
|
|
497
|
+
idSchema("PhaseRunId", { maxLength: 128 });
|
|
498
|
+
idSchema("EventId");
|
|
499
|
+
idSchema("CommandId");
|
|
500
|
+
idSchema("ApprovalId");
|
|
501
|
+
const ToolCallId = idSchema("ToolCallId", { maxLength: 128 });
|
|
502
|
+
idSchema("EffectId");
|
|
503
|
+
idSchema("ArtifactId");
|
|
504
|
+
idSchema("FindingId");
|
|
505
|
+
idSchema("SpecId");
|
|
506
|
+
idSchema("SurfaceId");
|
|
507
|
+
const Sha256 = idSchema("Sha256");
|
|
508
|
+
const IsoInstant = idSchema("IsoInstant", { format: "date-time" });
|
|
509
|
+
//#endregion
|
|
510
|
+
//#region ../../packages/base/src/model.ts
|
|
511
|
+
const ModelCapability = Type.Union([
|
|
512
|
+
Type.Literal("fast"),
|
|
513
|
+
Type.Literal("coding"),
|
|
514
|
+
Type.Literal("reasoning"),
|
|
515
|
+
Type.Literal("vision"),
|
|
516
|
+
Type.Literal("cheap")
|
|
517
|
+
]);
|
|
518
|
+
/** Spec 10, VERBATIM. Thinking level is NOT part of ModelRef: it travels next to it (SpawnRequest.thinking, tier table). */
|
|
519
|
+
const ModelRef = Type.Object({
|
|
520
|
+
provider: Type.String(),
|
|
521
|
+
model: Type.String(),
|
|
522
|
+
capability: Type.Optional(ModelCapability)
|
|
523
|
+
}, { additionalProperties: false });
|
|
524
|
+
const ThinkingLevel = Type.Union([
|
|
525
|
+
Type.Literal("off"),
|
|
526
|
+
Type.Literal("minimal"),
|
|
527
|
+
Type.Literal("low"),
|
|
528
|
+
Type.Literal("medium"),
|
|
529
|
+
Type.Literal("high"),
|
|
530
|
+
Type.Literal("xhigh")
|
|
531
|
+
]);
|
|
532
|
+
/** spec 10: exactly these two */
|
|
533
|
+
const AuthMode = Type.Union([Type.Literal("subscription"), Type.Literal("api")]);
|
|
534
|
+
Type.Union([Type.Literal("not_applicable"), Type.Object({
|
|
535
|
+
currency: Type.Literal("USD"),
|
|
536
|
+
amount: Type.Number({ minimum: 0 }),
|
|
537
|
+
basis: Type.Union([Type.Literal("catalogue"), Type.Literal("estimate")]),
|
|
538
|
+
priceCatalogVersion: Type.String()
|
|
539
|
+
}, { additionalProperties: false })]);
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region ../../packages/base/src/ports.ts
|
|
542
|
+
const ok = (value) => ({
|
|
543
|
+
ok: true,
|
|
544
|
+
value
|
|
545
|
+
});
|
|
546
|
+
const err = (error) => ({
|
|
547
|
+
ok: false,
|
|
548
|
+
error
|
|
549
|
+
});
|
|
550
|
+
Object.freeze({
|
|
551
|
+
now: () => toIsoInstant(Date.now()),
|
|
552
|
+
monotonicMs: () => performance.now(),
|
|
553
|
+
sleep: (ms, signal) => new Promise((resolve, reject) => {
|
|
554
|
+
if (signal?.aborted) {
|
|
555
|
+
reject(signal.reason);
|
|
556
|
+
return;
|
|
557
|
+
}
|
|
558
|
+
const onAbort = () => {
|
|
559
|
+
clearTimeout(timer);
|
|
560
|
+
reject(signal?.reason);
|
|
561
|
+
};
|
|
562
|
+
const timer = setTimeout(() => {
|
|
563
|
+
signal?.removeEventListener("abort", onAbort);
|
|
564
|
+
resolve();
|
|
565
|
+
}, ms);
|
|
566
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
567
|
+
})
|
|
568
|
+
});
|
|
569
|
+
Type.Object({
|
|
570
|
+
path: Type.String({ pattern: "^(?:/(?:[^/~]|~[01])*)*$" }),
|
|
571
|
+
reason: Type.Union([
|
|
572
|
+
Type.Literal("secret-value"),
|
|
573
|
+
Type.Literal("secret-pattern"),
|
|
574
|
+
Type.Literal("env-value"),
|
|
575
|
+
Type.Literal("private-key"),
|
|
576
|
+
Type.Literal("sensitive-path"),
|
|
577
|
+
Type.Literal("size")
|
|
578
|
+
]),
|
|
579
|
+
detector: Type.String(),
|
|
580
|
+
sha256: Type.Optional(Sha256)
|
|
581
|
+
}, { additionalProperties: false });
|
|
582
|
+
//#endregion
|
|
583
|
+
//#region ../../packages/base/src/usage.ts
|
|
584
|
+
const count$5 = () => Type.Integer({ minimum: 0 });
|
|
585
|
+
const TokenUsage = Type.Object({
|
|
586
|
+
input: count$5(),
|
|
587
|
+
output: count$5(),
|
|
588
|
+
cacheRead: count$5(),
|
|
589
|
+
cacheWrite: count$5(),
|
|
590
|
+
total: count$5()
|
|
591
|
+
}, { additionalProperties: false });
|
|
592
|
+
const QuotaWindow = Type.Object({
|
|
593
|
+
name: Type.String(),
|
|
594
|
+
usedPercent: Type.Optional(Type.Number({ minimum: 0 })),
|
|
595
|
+
resetsAt: Type.Optional(IsoInstant),
|
|
596
|
+
limitLabel: Type.Optional(Type.String())
|
|
597
|
+
}, { additionalProperties: false });
|
|
598
|
+
const QuotaInfo = Type.Union([Type.Object({ known: Type.Literal(false) }, { additionalProperties: false }), Type.Object({
|
|
599
|
+
known: Type.Literal(true),
|
|
600
|
+
source: Type.Union([Type.Literal("response-headers"), Type.Literal("error")]),
|
|
601
|
+
provider: Type.String(),
|
|
602
|
+
windows: Type.Array(QuotaWindow),
|
|
603
|
+
observedAt: IsoInstant
|
|
604
|
+
}, { additionalProperties: false })]);
|
|
605
|
+
Type.Object({
|
|
606
|
+
tokens: Type.Optional(count$5()),
|
|
607
|
+
modelRequests: Type.Optional(count$5()),
|
|
608
|
+
toolCalls: Type.Optional(count$5()),
|
|
609
|
+
wallClockMs: Type.Optional(Type.Number({ minimum: 0 })),
|
|
610
|
+
retries: Type.Optional(count$5()),
|
|
611
|
+
fixRounds: Type.Optional(count$5()),
|
|
612
|
+
contextTokens: Type.Optional(count$5()),
|
|
613
|
+
concurrentAgents: Type.Optional(count$5()),
|
|
614
|
+
estimatedQuotaPercent: Type.Optional(Type.Number({ minimum: 0 }))
|
|
615
|
+
}, { additionalProperties: false });
|
|
616
|
+
//#endregion
|
|
617
|
+
//#region ../../packages/runtime-contract/src/capabilities.ts
|
|
618
|
+
const strict$6 = { additionalProperties: false };
|
|
619
|
+
const Cap = Type.Union([
|
|
620
|
+
Type.Object({ value: Type.Literal("yes") }, strict$6),
|
|
621
|
+
Type.Object({
|
|
622
|
+
value: Type.Literal("no"),
|
|
623
|
+
why: Type.String()
|
|
624
|
+
}, strict$6),
|
|
625
|
+
Type.Object({
|
|
626
|
+
value: Type.Literal("partial"),
|
|
627
|
+
why: Type.String()
|
|
628
|
+
}, strict$6)
|
|
629
|
+
]);
|
|
630
|
+
Type.Object({
|
|
631
|
+
contractVersion: Type.Literal("1"),
|
|
632
|
+
/** the only legal value (C1); present so conformance can assert it */
|
|
633
|
+
toolExecution: Type.Literal("host-delegated"),
|
|
634
|
+
streaming: Cap,
|
|
635
|
+
thinkingStream: Cap,
|
|
636
|
+
send: Type.Object({
|
|
637
|
+
steer: Cap,
|
|
638
|
+
followUp: Cap
|
|
639
|
+
}, strict$6),
|
|
640
|
+
cancelCooperative: Cap,
|
|
641
|
+
cancelHard: Cap,
|
|
642
|
+
pause: Type.Object({
|
|
643
|
+
toolBoundary: Cap,
|
|
644
|
+
modelBoundary: Cap
|
|
645
|
+
}, strict$6),
|
|
646
|
+
continuationFromTranscript: Cap,
|
|
647
|
+
processIsolation: Cap,
|
|
648
|
+
envFiltering: Cap,
|
|
649
|
+
brainSandbox: Cap,
|
|
650
|
+
resourceLimits: Cap,
|
|
651
|
+
budgetEnforcement: Type.Object({
|
|
652
|
+
turns: Cap,
|
|
653
|
+
modelRequests: Cap,
|
|
654
|
+
tokens: Cap,
|
|
655
|
+
context: Cap,
|
|
656
|
+
wallClock: Cap,
|
|
657
|
+
outputTokensPerRequest: Cap
|
|
658
|
+
}, strict$6),
|
|
659
|
+
/** 'no' = none possible (compaction and engine retries off) */
|
|
660
|
+
hiddenModelCalls: Cap,
|
|
661
|
+
usageReporting: Cap,
|
|
662
|
+
effectiveModelReporting: Cap,
|
|
663
|
+
quotaReporting: Cap,
|
|
664
|
+
authStatusWithoutSecret: Cap,
|
|
665
|
+
subscriptionModeAssertion: Cap,
|
|
666
|
+
systemPromptExact: Cap,
|
|
667
|
+
runtimePinning: Cap,
|
|
668
|
+
platforms: Type.Object({
|
|
669
|
+
darwin: Cap,
|
|
670
|
+
linux: Cap,
|
|
671
|
+
win32: Cap
|
|
672
|
+
}, strict$6),
|
|
673
|
+
hints: Type.Object({
|
|
674
|
+
memoryPerAgentMb: Type.Number({ minimum: 0 }),
|
|
675
|
+
coldStartMs: Type.Number({ minimum: 0 }),
|
|
676
|
+
maxConcurrentAgents: Type.Integer({ minimum: 1 })
|
|
677
|
+
}, strict$6)
|
|
678
|
+
}, strict$6);
|
|
679
|
+
//#endregion
|
|
680
|
+
//#region ../../packages/runtime-contract/src/session.ts
|
|
681
|
+
const strict$5 = { additionalProperties: false };
|
|
682
|
+
const count$4 = () => Type.Integer({ minimum: 0 });
|
|
683
|
+
const jsonMap = () => Type.Unsafe(Type.Record(Type.String(), JsonValueSchema));
|
|
684
|
+
/** `format` is a label, e.g. 'jsonl-v3' | 'fake-ndjson-v1' — never interpreted by core */
|
|
685
|
+
const TranscriptRef = Type.Object({
|
|
686
|
+
path: Type.String(),
|
|
687
|
+
format: Type.String()
|
|
688
|
+
}, strict$5);
|
|
689
|
+
/** R8: opaque to clients */
|
|
690
|
+
const RuntimeSessionRef = Type.Object({
|
|
691
|
+
runtime: Type.String(),
|
|
692
|
+
engineVersion: Type.String(),
|
|
693
|
+
sessionId: Type.String(),
|
|
694
|
+
transcript: TranscriptRef
|
|
695
|
+
}, strict$5);
|
|
696
|
+
const UsageTotals = Type.Object({
|
|
697
|
+
tokens: TokenUsage,
|
|
698
|
+
modelRequests: count$4(),
|
|
699
|
+
toolCalls: count$4(),
|
|
700
|
+
turns: count$4(),
|
|
701
|
+
wallClockMs: Type.Number({ minimum: 0 })
|
|
702
|
+
}, strict$5);
|
|
703
|
+
const EffectiveModel = Type.Object({
|
|
704
|
+
provider: Type.String(),
|
|
705
|
+
model: Type.String(),
|
|
706
|
+
api: Type.Optional(Type.String()),
|
|
707
|
+
baseUrl: Type.Optional(Type.String())
|
|
708
|
+
}, strict$5);
|
|
709
|
+
/** The ADAPTER's typed cause, recorded by the host BEFORE it acts. The engine's own stop reason is never a discriminator [X]. */
|
|
710
|
+
const AgentStopCause = Type.Union([
|
|
711
|
+
Type.Literal("host-terminated"),
|
|
712
|
+
Type.Literal("model-stop"),
|
|
713
|
+
Type.Literal("output-truncated"),
|
|
714
|
+
Type.Literal("budget"),
|
|
715
|
+
Type.Literal("cancelled"),
|
|
716
|
+
Type.Literal("engine-error"),
|
|
717
|
+
Type.Literal("process-exit")
|
|
718
|
+
]);
|
|
719
|
+
const AgentExit = Type.Object({
|
|
720
|
+
outcome: Type.Union([
|
|
721
|
+
Type.Literal("completed"),
|
|
722
|
+
Type.Literal("failed"),
|
|
723
|
+
Type.Literal("cancelled"),
|
|
724
|
+
Type.Literal("crashed")
|
|
725
|
+
]),
|
|
726
|
+
stop: AgentStopCause,
|
|
727
|
+
error: Type.Optional(ErrorInfo),
|
|
728
|
+
usage: UsageTotals,
|
|
729
|
+
lastSeq: count$4()
|
|
730
|
+
}, strict$5);
|
|
731
|
+
const delivery$2 = () => Type.Union([Type.Literal("steer"), Type.Literal("follow-up")]);
|
|
732
|
+
Type.Union([Type.Object({
|
|
733
|
+
kind: Type.Literal("user"),
|
|
734
|
+
messageId: Type.String(),
|
|
735
|
+
text: Type.String(),
|
|
736
|
+
delivery: delivery$2()
|
|
737
|
+
}, strict$5), Type.Object({
|
|
738
|
+
kind: Type.Literal("host-note"),
|
|
739
|
+
messageId: Type.String(),
|
|
740
|
+
text: Type.String(),
|
|
741
|
+
delivery: delivery$2()
|
|
742
|
+
}, strict$5)]);
|
|
743
|
+
Type.Object({
|
|
744
|
+
runId: RunId,
|
|
745
|
+
agentId: AgentId,
|
|
746
|
+
incarnation: Type.Integer({ minimum: 1 }),
|
|
747
|
+
state: Type.Union([
|
|
748
|
+
Type.Literal("starting"),
|
|
749
|
+
Type.Literal("running"),
|
|
750
|
+
Type.Literal("awaiting-tool"),
|
|
751
|
+
Type.Literal("paused"),
|
|
752
|
+
Type.Literal("settling"),
|
|
753
|
+
Type.Literal("exited")
|
|
754
|
+
]),
|
|
755
|
+
pausedAt: Type.Optional(Type.Union([Type.Literal("tool-boundary"), Type.Literal("model-boundary")])),
|
|
756
|
+
turn: count$4(),
|
|
757
|
+
pendingToolCalls: Type.Array(ToolCallId),
|
|
758
|
+
requestedModel: ModelRef,
|
|
759
|
+
effectiveModel: Type.Optional(EffectiveModel),
|
|
760
|
+
authMode: Type.Optional(AuthMode),
|
|
761
|
+
usage: UsageTotals,
|
|
762
|
+
contextTokens: Type.Optional(count$4()),
|
|
763
|
+
contextWindow: Type.Optional(count$4()),
|
|
764
|
+
session: RuntimeSessionRef,
|
|
765
|
+
lastSeq: count$4(),
|
|
766
|
+
/** pid, rssMb, lastHeartbeatAt, engine flags… never secrets */
|
|
767
|
+
diagnostics: jsonMap()
|
|
768
|
+
}, strict$5);
|
|
769
|
+
//#endregion
|
|
770
|
+
//#region ../../packages/runtime-contract/src/tools.ts
|
|
771
|
+
const strict$4 = { additionalProperties: false };
|
|
772
|
+
const RuntimeToolCall = Type.Object({
|
|
773
|
+
runId: RunId,
|
|
774
|
+
agentId: AgentId,
|
|
775
|
+
incarnation: Type.Integer({ minimum: 1 }),
|
|
776
|
+
/** tc_<incarnation>_<ordinal> */
|
|
777
|
+
toolCallId: ToolCallId,
|
|
778
|
+
/** the engine's own id, transcript correlation only */
|
|
779
|
+
engineToolCallId: Type.Optional(Type.String()),
|
|
780
|
+
/** 1-based, per incarnation, gapless */
|
|
781
|
+
ordinal: Type.Integer({ minimum: 1 }),
|
|
782
|
+
tool: Type.String(),
|
|
783
|
+
/** as produced by the model after engine-side coercion; the host re-validates strictly */
|
|
784
|
+
input: JsonValueSchema
|
|
785
|
+
}, strict$4);
|
|
786
|
+
const ToolProgress = Type.Object({
|
|
787
|
+
text: Type.Optional(Type.String()),
|
|
788
|
+
bytes: Type.Optional(Type.Integer({ minimum: 0 }))
|
|
789
|
+
}, strict$4);
|
|
790
|
+
const ToolContent = Type.Union([Type.Object({
|
|
791
|
+
type: Type.Literal("text"),
|
|
792
|
+
text: Type.Unsafe(Type.String())
|
|
793
|
+
}, strict$4), Type.Object({
|
|
794
|
+
type: Type.Literal("image"),
|
|
795
|
+
mediaType: Type.String(),
|
|
796
|
+
dataBase64: Type.String()
|
|
797
|
+
}, strict$4)]);
|
|
798
|
+
Type.Object({
|
|
799
|
+
isError: Type.Boolean(),
|
|
800
|
+
content: Type.Array(ToolContent),
|
|
801
|
+
/** host asks the runtime to end the agent loop after this batch */
|
|
802
|
+
terminate: Type.Optional(Type.Boolean()),
|
|
803
|
+
/** opaque audit id, stored in the transcript */
|
|
804
|
+
resultRef: Type.Optional(Type.String())
|
|
805
|
+
}, strict$4);
|
|
806
|
+
const TOOL_NAME_PATTERN = "^[a-z][a-z0-9_]{1,40}$";
|
|
807
|
+
/** JSON Schema 2020-12, ONE flat top-level object: no $ref/$defs/oneOf at root (provider flattening). */
|
|
808
|
+
const ToolInputSchema = Type.Unsafe(Type.Object({
|
|
809
|
+
type: Type.Literal("object"),
|
|
810
|
+
properties: Type.Optional(Type.Record(Type.String(), JsonValueSchema))
|
|
811
|
+
}, {
|
|
812
|
+
additionalProperties: true,
|
|
813
|
+
not: { anyOf: [
|
|
814
|
+
"$ref",
|
|
815
|
+
"$defs",
|
|
816
|
+
"definitions",
|
|
817
|
+
"oneOf",
|
|
818
|
+
"anyOf",
|
|
819
|
+
"allOf"
|
|
820
|
+
].map((key) => ({ required: [key] })) }
|
|
821
|
+
}));
|
|
822
|
+
/** What the BRAIN needs to know. No paths, no commands: what a call may touch is decided host-side. */
|
|
823
|
+
const ToolGrant = Type.Object({
|
|
824
|
+
/** TOOL_NAME_PATTERN; never differs only by case from another grant (see toolGrantProblems) */
|
|
825
|
+
tool: Type.String({ pattern: TOOL_NAME_PATTERN }),
|
|
826
|
+
description: Type.String(),
|
|
827
|
+
inputSchema: ToolInputSchema,
|
|
828
|
+
/** ordering hint only */
|
|
829
|
+
effect: Type.Union([
|
|
830
|
+
Type.Literal("read"),
|
|
831
|
+
Type.Literal("write"),
|
|
832
|
+
Type.Literal("execute"),
|
|
833
|
+
Type.Literal("network"),
|
|
834
|
+
Type.Literal("control")
|
|
835
|
+
]),
|
|
836
|
+
/** true for the result tool */
|
|
837
|
+
terminal: Type.Boolean()
|
|
838
|
+
}, strict$4);
|
|
839
|
+
//#endregion
|
|
840
|
+
//#region ../../packages/runtime-contract/src/events.ts
|
|
841
|
+
const strict$3 = { additionalProperties: false };
|
|
842
|
+
const count$3 = () => Type.Integer({ minimum: 0 });
|
|
843
|
+
const boundary = () => Type.Union([Type.Literal("tool-boundary"), Type.Literal("model-boundary")]);
|
|
844
|
+
const delivery$1 = () => Type.Union([Type.Literal("steer"), Type.Literal("follow-up")]);
|
|
845
|
+
const messageRole = () => Type.Union([
|
|
846
|
+
Type.Literal("assistant"),
|
|
847
|
+
Type.Literal("user"),
|
|
848
|
+
Type.Literal("tool-result")
|
|
849
|
+
]);
|
|
850
|
+
/**
|
|
851
|
+
* A CLOSED five-value set on this frontier: an adapter maps any other engine stop reason (Pi 0.85.1 also has
|
|
852
|
+
* 'pending' and 'deferred') to 'error' and emits runtime.warning{code: ENGINE_STOP_REASON_UNMAPPED}.
|
|
853
|
+
*/
|
|
854
|
+
const ModelStop = Type.Union([
|
|
855
|
+
Type.Literal("stop"),
|
|
856
|
+
Type.Literal("length"),
|
|
857
|
+
Type.Literal("tool-use"),
|
|
858
|
+
Type.Literal("error"),
|
|
859
|
+
Type.Literal("aborted")
|
|
860
|
+
]);
|
|
861
|
+
const IsolationReport = Type.Object({
|
|
862
|
+
level: Type.Union([
|
|
863
|
+
Type.Literal("os"),
|
|
864
|
+
Type.Literal("process"),
|
|
865
|
+
Type.Literal("none")
|
|
866
|
+
]),
|
|
867
|
+
filesystem: Type.Union([Type.Literal("enforced"), Type.Literal("advisory")]),
|
|
868
|
+
network: Type.Union([
|
|
869
|
+
Type.Literal("enforced"),
|
|
870
|
+
Type.Literal("partial"),
|
|
871
|
+
Type.Literal("none")
|
|
872
|
+
]),
|
|
873
|
+
backend: Type.String()
|
|
874
|
+
}, strict$3);
|
|
875
|
+
const durable = (data) => ({
|
|
876
|
+
durability: "durable",
|
|
877
|
+
data
|
|
878
|
+
});
|
|
879
|
+
const ephemeral = (data) => ({
|
|
880
|
+
durability: "ephemeral",
|
|
881
|
+
data
|
|
882
|
+
});
|
|
883
|
+
/**
|
|
884
|
+
* One row per event type: its durability and the schema of `data`. EVERY durable type has a named target in the
|
|
885
|
+
* protocol catalogue, or an explicit "not forwarded" rule (DESIGN 2.3.3). `authSource: 'none'` exists only for
|
|
886
|
+
* runtimes that hold no credential (the fake); a fake run reports the authMode its plan requested.
|
|
887
|
+
*/
|
|
888
|
+
const RUNTIME_EVENT_TYPES = {
|
|
889
|
+
"agent.spawned": durable(Type.Object({
|
|
890
|
+
session: RuntimeSessionRef,
|
|
891
|
+
requestedModel: ModelRef,
|
|
892
|
+
tools: Type.Array(Type.String()),
|
|
893
|
+
systemPromptSha256: Sha256,
|
|
894
|
+
effectiveSystemPromptSha256: Sha256,
|
|
895
|
+
isolation: IsolationReport
|
|
896
|
+
}, strict$3)),
|
|
897
|
+
"agent.started": durable(Type.Object({ taskSha256: Sha256 }, strict$3)),
|
|
898
|
+
"agent.turn.started": ephemeral(Type.Object({ turn: count$3() }, strict$3)),
|
|
899
|
+
"agent.turn.completed": durable(Type.Object({
|
|
900
|
+
turn: count$3(),
|
|
901
|
+
toolCalls: count$3()
|
|
902
|
+
}, strict$3)),
|
|
903
|
+
"agent.message.started": ephemeral(Type.Object({
|
|
904
|
+
messageId: Type.String(),
|
|
905
|
+
role: messageRole()
|
|
906
|
+
}, strict$3)),
|
|
907
|
+
"agent.message.delta": ephemeral(Type.Object({
|
|
908
|
+
messageId: Type.String(),
|
|
909
|
+
channel: Type.Union([
|
|
910
|
+
Type.Literal("text"),
|
|
911
|
+
Type.Literal("thinking"),
|
|
912
|
+
Type.Literal("tool-input")
|
|
913
|
+
]),
|
|
914
|
+
contentIndex: count$3(),
|
|
915
|
+
delta: Type.String()
|
|
916
|
+
}, strict$3)),
|
|
917
|
+
"agent.message.completed": durable(Type.Object({
|
|
918
|
+
messageId: Type.String(),
|
|
919
|
+
role: messageRole(),
|
|
920
|
+
textSha256: Sha256,
|
|
921
|
+
textBytes: count$3(),
|
|
922
|
+
preview: Type.String({ maxLength: 512 }),
|
|
923
|
+
stop: Type.Optional(ModelStop)
|
|
924
|
+
}, strict$3)),
|
|
925
|
+
"model.requested": durable(Type.Object({
|
|
926
|
+
requestId: Type.String(),
|
|
927
|
+
model: ModelRef,
|
|
928
|
+
contextSha256: Type.Optional(Sha256),
|
|
929
|
+
contextTokensEstimate: Type.Optional(count$3()),
|
|
930
|
+
attempt: Type.Integer({ minimum: 1 })
|
|
931
|
+
}, strict$3)),
|
|
932
|
+
"model.responded": durable(Type.Object({
|
|
933
|
+
requestId: Type.String(),
|
|
934
|
+
requestedModel: ModelRef,
|
|
935
|
+
effectiveModel: EffectiveModel,
|
|
936
|
+
authMode: AuthMode,
|
|
937
|
+
authSource: Type.Union([
|
|
938
|
+
Type.Literal("oauth"),
|
|
939
|
+
Type.Literal("api-key"),
|
|
940
|
+
Type.Literal("none")
|
|
941
|
+
]),
|
|
942
|
+
durationMs: Type.Number({ minimum: 0 }),
|
|
943
|
+
usage: TokenUsage,
|
|
944
|
+
httpStatus: Type.Optional(Type.Integer()),
|
|
945
|
+
attempt: Type.Integer({ minimum: 1 }),
|
|
946
|
+
stop: ModelStop,
|
|
947
|
+
quota: QuotaInfo,
|
|
948
|
+
error: Type.Optional(ErrorInfo)
|
|
949
|
+
}, strict$3)),
|
|
950
|
+
"tool.call.requested": durable(Type.Object({ call: RuntimeToolCall }, strict$3)),
|
|
951
|
+
"tool.call.rejected": durable(Type.Object({
|
|
952
|
+
engineToolCallId: Type.Optional(Type.String()),
|
|
953
|
+
tool: Type.String(),
|
|
954
|
+
cause: Type.Union([
|
|
955
|
+
Type.Literal("unknown-tool"),
|
|
956
|
+
Type.Literal("invalid-input"),
|
|
957
|
+
Type.Literal("output-truncated")
|
|
958
|
+
]),
|
|
959
|
+
message: Type.String()
|
|
960
|
+
}, strict$3)),
|
|
961
|
+
"tool.call.progress": ephemeral(Type.Object({
|
|
962
|
+
toolCallId: ToolCallId,
|
|
963
|
+
update: ToolProgress
|
|
964
|
+
}, strict$3)),
|
|
965
|
+
"tool.call.delivered": durable(Type.Object({
|
|
966
|
+
toolCallId: ToolCallId,
|
|
967
|
+
isError: Type.Boolean(),
|
|
968
|
+
terminate: Type.Boolean(),
|
|
969
|
+
waitedMs: Type.Number({ minimum: 0 })
|
|
970
|
+
}, strict$3)),
|
|
971
|
+
"agent.paused": durable(Type.Object({ at: boundary() }, strict$3)),
|
|
972
|
+
"agent.resumed": durable(Type.Unsafe(Type.Object({}, strict$3))),
|
|
973
|
+
"agent.message.accepted": durable(Type.Object({
|
|
974
|
+
messageId: Type.String(),
|
|
975
|
+
delivery: delivery$1()
|
|
976
|
+
}, strict$3)),
|
|
977
|
+
"agent.exited": durable(AgentExit),
|
|
978
|
+
"runtime.warning": durable(Type.Object({
|
|
979
|
+
code: Type.String(),
|
|
980
|
+
message: Type.String()
|
|
981
|
+
}, strict$3))
|
|
982
|
+
};
|
|
983
|
+
const RUNTIME_EVENT_TYPE_NAMES = Object.freeze(Object.keys(RUNTIME_EVENT_TYPES));
|
|
984
|
+
const envelope = {
|
|
985
|
+
runId: RunId,
|
|
986
|
+
agentId: AgentId,
|
|
987
|
+
incarnation: Type.Integer({ minimum: 1 }),
|
|
988
|
+
seq: count$3(),
|
|
989
|
+
at: IsoInstant
|
|
990
|
+
};
|
|
991
|
+
Type.Unsafe(Type.Union(RUNTIME_EVENT_TYPE_NAMES.map((type) => {
|
|
992
|
+
const row = RUNTIME_EVENT_TYPES[type];
|
|
993
|
+
return Type.Object({
|
|
994
|
+
type: Type.Literal(type),
|
|
995
|
+
durability: Type.Literal(row.durability),
|
|
996
|
+
...envelope,
|
|
997
|
+
data: row.data
|
|
998
|
+
}, strict$3);
|
|
999
|
+
})));
|
|
1000
|
+
//#endregion
|
|
1001
|
+
//#region ../../packages/runtime-contract/src/pin.ts
|
|
1002
|
+
const strict$2 = { additionalProperties: false };
|
|
1003
|
+
const count$2 = () => Type.Integer({ minimum: 0 });
|
|
1004
|
+
Type.Object({
|
|
1005
|
+
runtimeId: Type.String(),
|
|
1006
|
+
adapterVersion: Type.String(),
|
|
1007
|
+
engine: Type.Union([Type.Object({
|
|
1008
|
+
name: Type.String(),
|
|
1009
|
+
version: Type.String()
|
|
1010
|
+
}, strict$2), Type.Null()]),
|
|
1011
|
+
node: Type.Object({
|
|
1012
|
+
version: Type.String(),
|
|
1013
|
+
execPath: Type.String()
|
|
1014
|
+
}, strict$2),
|
|
1015
|
+
artifacts: Type.Array(Type.Object({
|
|
1016
|
+
role: Type.Union([
|
|
1017
|
+
Type.Literal("agent-host-bundle"),
|
|
1018
|
+
Type.Literal("engine-package-tree"),
|
|
1019
|
+
Type.Literal("install-lock")
|
|
1020
|
+
]),
|
|
1021
|
+
path: Type.String(),
|
|
1022
|
+
sha256: Sha256,
|
|
1023
|
+
files: Type.Optional(count$2()),
|
|
1024
|
+
bytes: count$2()
|
|
1025
|
+
}, strict$2)),
|
|
1026
|
+
/** sha256(canonicalJson(all of the above)) */
|
|
1027
|
+
digest: Sha256
|
|
1028
|
+
}, strict$2);
|
|
1029
|
+
/** never contains a token, a refresh token or an account secret */
|
|
1030
|
+
const ProviderAuthStatus = Type.Object({
|
|
1031
|
+
provider: Type.String(),
|
|
1032
|
+
/** 'unknown-transient' = credential store locked: NEVER mapped to AUTH_REQUIRED */
|
|
1033
|
+
state: Type.Union([
|
|
1034
|
+
Type.Literal("oauth"),
|
|
1035
|
+
Type.Literal("api-key"),
|
|
1036
|
+
Type.Literal("absent"),
|
|
1037
|
+
Type.Literal("unknown-transient")
|
|
1038
|
+
]),
|
|
1039
|
+
subscription: Type.Boolean(),
|
|
1040
|
+
source: Type.Optional(Type.String()),
|
|
1041
|
+
checkedAt: IsoInstant,
|
|
1042
|
+
/** non-secret account/tenant label WHEN the engine exposes one without a secret; absent otherwise */
|
|
1043
|
+
accountLabel: Type.Optional(Type.String()),
|
|
1044
|
+
/** Cohorte's own table, not the engine's subscription flag (§3.7) */
|
|
1045
|
+
billing: Type.Union([
|
|
1046
|
+
Type.Literal("plan-limits"),
|
|
1047
|
+
Type.Literal("metered"),
|
|
1048
|
+
Type.Literal("unknown")
|
|
1049
|
+
]),
|
|
1050
|
+
caveat: Type.Optional(Type.String())
|
|
1051
|
+
}, strict$2);
|
|
1052
|
+
//#endregion
|
|
1053
|
+
//#region ../../packages/runtime-contract/src/spawn.ts
|
|
1054
|
+
const strict$1 = { additionalProperties: false };
|
|
1055
|
+
const count$1 = () => Type.Integer({ minimum: 0 });
|
|
1056
|
+
const limit = () => Type.Optional(Type.Integer({ minimum: 1 }));
|
|
1057
|
+
const ceiling = () => Type.Optional(count$1());
|
|
1058
|
+
const stringMap$1 = () => Type.Unsafe(Type.Record(Type.String(), Type.String()));
|
|
1059
|
+
/** opaque label; the runtime MUST NOT branch on it */
|
|
1060
|
+
const AgentRole = Type.String();
|
|
1061
|
+
const AuthRequirement = Type.Object({
|
|
1062
|
+
mode: AuthMode,
|
|
1063
|
+
provider: Type.String(),
|
|
1064
|
+
/** pinned catalogue endpoint; a runtime that would talk to anything else MUST refuse to spawn */
|
|
1065
|
+
baseUrl: Type.String(),
|
|
1066
|
+
/** false unless the run plan carries an explicit api opt-in */
|
|
1067
|
+
allowApiKey: Type.Boolean()
|
|
1068
|
+
}, strict$1);
|
|
1069
|
+
/** rendered by the host into the run snapshot dir */
|
|
1070
|
+
const TaskInput = Type.Object({
|
|
1071
|
+
path: Type.String(),
|
|
1072
|
+
sha256: Sha256,
|
|
1073
|
+
bytes: count$1()
|
|
1074
|
+
}, strict$1);
|
|
1075
|
+
/** The fixed line that precedes `Continuation.note` when the engine cannot carry two user messages in one prompt. */
|
|
1076
|
+
const CONTINUATION_NOTE_SEPARATOR = "\n\n[cohorte] continuation note\n\n";
|
|
1077
|
+
/**
|
|
1078
|
+
* `note` is delivered by the runtime AFTER `task` and BEFORE the first model request: as a second user message when
|
|
1079
|
+
* the engine can carry two in one prompt, otherwise appended to the first user message after
|
|
1080
|
+
* CONTINUATION_NOTE_SEPARATOR. Either way each text is a byte-identical contiguous span and `task` comes first
|
|
1081
|
+
* (conformance rule 12).
|
|
1082
|
+
*/
|
|
1083
|
+
const Continuation = Type.Object({
|
|
1084
|
+
fromIncarnation: Type.Integer({ minimum: 1 }),
|
|
1085
|
+
note: TaskInput,
|
|
1086
|
+
/** used only if continuationFromTranscript = yes */
|
|
1087
|
+
transcript: Type.Optional(TranscriptRef)
|
|
1088
|
+
}, strict$1);
|
|
1089
|
+
/** runtime MUST verify sha256 before use */
|
|
1090
|
+
const PromptRef = Type.Object({
|
|
1091
|
+
id: Type.String(),
|
|
1092
|
+
path: Type.String(),
|
|
1093
|
+
sha256: Sha256,
|
|
1094
|
+
bytes: count$1()
|
|
1095
|
+
}, strict$1);
|
|
1096
|
+
const ContextEntry = Type.Object({
|
|
1097
|
+
id: Type.String(),
|
|
1098
|
+
tier: Type.Union([
|
|
1099
|
+
Type.Literal("system"),
|
|
1100
|
+
Type.Literal("doctrine"),
|
|
1101
|
+
Type.Literal("data"),
|
|
1102
|
+
Type.Literal("task"),
|
|
1103
|
+
Type.Literal("prior-results")
|
|
1104
|
+
]),
|
|
1105
|
+
/** agent-output and untrusted-repository can never sit above 'data' */
|
|
1106
|
+
trust: Type.Union([
|
|
1107
|
+
Type.Literal("cohorte"),
|
|
1108
|
+
Type.Literal("human"),
|
|
1109
|
+
Type.Literal("untrusted-repository"),
|
|
1110
|
+
Type.Literal("agent-output")
|
|
1111
|
+
]),
|
|
1112
|
+
source: Type.Object({
|
|
1113
|
+
kind: Type.Union([
|
|
1114
|
+
Type.Literal("asset"),
|
|
1115
|
+
Type.Literal("project-file"),
|
|
1116
|
+
Type.Literal("artifact"),
|
|
1117
|
+
Type.Literal("event-summary"),
|
|
1118
|
+
Type.Literal("inline")
|
|
1119
|
+
]),
|
|
1120
|
+
ref: Type.String()
|
|
1121
|
+
}, strict$1),
|
|
1122
|
+
sha256: Sha256,
|
|
1123
|
+
bytes: count$1(),
|
|
1124
|
+
tokenEstimate: count$1()
|
|
1125
|
+
}, strict$1);
|
|
1126
|
+
/**
|
|
1127
|
+
* PROVENANCE ONLY. "Installer le contexte" (spec 5.2) is defined as: every byte the model sees is in exactly two
|
|
1128
|
+
* host-rendered files — `systemPrompt` (tiers `system` + `doctrine`) and `task` (tiers `data` + `task` +
|
|
1129
|
+
* `prior-results`) — plus, for a later incarnation, `continuation.note`. A runtime installs those three and NOTHING
|
|
1130
|
+
* else: it never opens `entries[].source`, never re-orders or re-renders tiers. The manifest travels so that the
|
|
1131
|
+
* runtime can record `manifestSha256` on `model.requested` and so that a second runtime has nothing to guess.
|
|
1132
|
+
* Tiers are trust/priority tiers (spec 7), not orchestration words.
|
|
1133
|
+
*/
|
|
1134
|
+
const ContextManifest = Type.Object({
|
|
1135
|
+
/** sha256(canonicalJson(entries)) = "hash du contexte" of spec 19 */
|
|
1136
|
+
manifestSha256: Sha256,
|
|
1137
|
+
tokenLimit: count$1(),
|
|
1138
|
+
tokenEstimate: count$1(),
|
|
1139
|
+
/** deterministic order: tier, then id */
|
|
1140
|
+
entries: Type.Array(ContextEntry),
|
|
1141
|
+
reductions: Type.Array(Type.Object({
|
|
1142
|
+
entryId: Type.String(),
|
|
1143
|
+
strategy: Type.Union([
|
|
1144
|
+
Type.Literal("excerpt"),
|
|
1145
|
+
Type.Literal("outline"),
|
|
1146
|
+
Type.Literal("summary-with-refs"),
|
|
1147
|
+
Type.Literal("dropped")
|
|
1148
|
+
]),
|
|
1149
|
+
fromBytes: count$1(),
|
|
1150
|
+
toBytes: count$1()
|
|
1151
|
+
}, strict$1)),
|
|
1152
|
+
exclusions: Type.Array(Type.Object({
|
|
1153
|
+
pattern: Type.String(),
|
|
1154
|
+
reason: Type.Union([
|
|
1155
|
+
Type.Literal("secret"),
|
|
1156
|
+
Type.Literal("outside-scope"),
|
|
1157
|
+
Type.Literal("size"),
|
|
1158
|
+
Type.Literal("binary")
|
|
1159
|
+
])
|
|
1160
|
+
}, strict$1))
|
|
1161
|
+
}, strict$1);
|
|
1162
|
+
/** Isolation of the RUNTIME'S OWN agent process (the brain). Tool isolation is host-side. */
|
|
1163
|
+
const SandboxPolicy = Type.Object({
|
|
1164
|
+
/** spawn fails security/sandbox-unavailable below `os` */
|
|
1165
|
+
require: Type.Union([
|
|
1166
|
+
Type.Literal("os"),
|
|
1167
|
+
Type.Literal("os-if-available"),
|
|
1168
|
+
Type.Literal("process")
|
|
1169
|
+
]),
|
|
1170
|
+
/** absolute canonical roots */
|
|
1171
|
+
filesystem: Type.Object({
|
|
1172
|
+
readOnly: Type.Array(Type.String()),
|
|
1173
|
+
readWrite: Type.Array(Type.String()),
|
|
1174
|
+
denyRead: Type.Array(Type.String())
|
|
1175
|
+
}, strict$1),
|
|
1176
|
+
network: Type.Object({
|
|
1177
|
+
mode: Type.Union([
|
|
1178
|
+
Type.Literal("none"),
|
|
1179
|
+
Type.Literal("provider-only"),
|
|
1180
|
+
Type.Literal("unrestricted")
|
|
1181
|
+
]),
|
|
1182
|
+
allowHosts: Type.Array(Type.String())
|
|
1183
|
+
}, strict$1),
|
|
1184
|
+
/** allowlist; nothing else is inherited (D3) */
|
|
1185
|
+
env: Type.Object({
|
|
1186
|
+
allow: Type.Array(Type.String()),
|
|
1187
|
+
set: stringMap$1()
|
|
1188
|
+
}, strict$1),
|
|
1189
|
+
limits: Type.Object({
|
|
1190
|
+
maxOldSpaceMb: limit(),
|
|
1191
|
+
maxCpuSeconds: limit(),
|
|
1192
|
+
maxOpenFiles: limit()
|
|
1193
|
+
}, strict$1)
|
|
1194
|
+
}, strict$1);
|
|
1195
|
+
/** hard ceilings for ONE incarnation; absent = unlimited at this level */
|
|
1196
|
+
const Budget = Type.Object({
|
|
1197
|
+
maxTurns: ceiling(),
|
|
1198
|
+
maxModelRequests: ceiling(),
|
|
1199
|
+
maxToolCalls: ceiling(),
|
|
1200
|
+
maxInputTokens: ceiling(),
|
|
1201
|
+
maxOutputTokens: ceiling(),
|
|
1202
|
+
maxTotalTokens: ceiling(),
|
|
1203
|
+
/** stop (never auto-compact) when the last request's context exceeds this */
|
|
1204
|
+
maxContextTokens: ceiling(),
|
|
1205
|
+
maxWallClockMs: ceiling(),
|
|
1206
|
+
maxModelRequestMs: ceiling(),
|
|
1207
|
+
/** 0 = every retry is the host's (spec 11.3 "tous les retries sont visibles") */
|
|
1208
|
+
maxEngineRetries: count$1()
|
|
1209
|
+
}, strict$1);
|
|
1210
|
+
const SpawnRequest = Type.Object({
|
|
1211
|
+
runId: RunId,
|
|
1212
|
+
agentId: AgentId,
|
|
1213
|
+
role: AgentRole,
|
|
1214
|
+
model: ModelRef,
|
|
1215
|
+
systemPrompt: PromptRef,
|
|
1216
|
+
context: ContextManifest,
|
|
1217
|
+
tools: Type.Array(ToolGrant),
|
|
1218
|
+
sandbox: SandboxPolicy,
|
|
1219
|
+
budget: Budget,
|
|
1220
|
+
/** absolute, canonical; the path the MODEL is told about. The engine process MUST NOT use it as its cwd */
|
|
1221
|
+
workingDirectory: Type.String(),
|
|
1222
|
+
/** spec 6: spawn is idempotent on (runId, agentId, incarnation) */
|
|
1223
|
+
incarnation: Type.Integer({ minimum: 1 }),
|
|
1224
|
+
thinking: ThinkingLevel,
|
|
1225
|
+
/** spec 10.1 / D3 */
|
|
1226
|
+
auth: AuthRequirement,
|
|
1227
|
+
/** the first user message, by reference */
|
|
1228
|
+
task: TaskInput,
|
|
1229
|
+
/** a later incarnation of the same attempt */
|
|
1230
|
+
continuation: Type.Union([Continuation, Type.Null()])
|
|
1231
|
+
}, strict$1);
|
|
1232
|
+
//#endregion
|
|
1233
|
+
//#region ../../packages/runtime-pi/src/protocol.ts
|
|
1234
|
+
const strict = { additionalProperties: false };
|
|
1235
|
+
const count = () => Type.Integer({ minimum: 0 });
|
|
1236
|
+
const stringMap = () => Type.Unsafe(Type.Record(Type.String(), Type.String()));
|
|
1237
|
+
const delivery = () => Type.Union([Type.Literal("steer"), Type.Literal("follow-up")]);
|
|
1238
|
+
/**
|
|
1239
|
+
* The spawn request as it crosses the channel. Every member of `SpawnRequest` is JSON already and the child reads
|
|
1240
|
+
* the prompt by reference (`readVerified(req.systemPrompt)`, DESIGN 3.4), so the wire shape IS the contract shape.
|
|
1241
|
+
*/
|
|
1242
|
+
const SpawnRequestWire = SpawnRequest;
|
|
1243
|
+
const EngineSettings = Type.Object({
|
|
1244
|
+
authPath: Type.String(),
|
|
1245
|
+
agentDir: Type.String(),
|
|
1246
|
+
sessionFile: Type.String(),
|
|
1247
|
+
loadFrom: Type.Union([Type.Literal("package"), Type.Literal("bundle")]),
|
|
1248
|
+
expectedEngineVersion: Type.String(),
|
|
1249
|
+
responseHeaderAllowlist: Type.Array(Type.String())
|
|
1250
|
+
}, strict);
|
|
1251
|
+
const AuthEngineSettings = Type.Object({
|
|
1252
|
+
authPath: Type.String(),
|
|
1253
|
+
agentDir: Type.String()
|
|
1254
|
+
}, strict);
|
|
1255
|
+
/**
|
|
1256
|
+
* What the child extracts from a thrown value or an error message, so that the classifier is a PURE, engine-free
|
|
1257
|
+
* function in the parent (DESIGN 3.8). Only child code looks at engine classes.
|
|
1258
|
+
*/
|
|
1259
|
+
const ErrorSignal = Type.Object({
|
|
1260
|
+
/** the engine's own error code */
|
|
1261
|
+
modelsErrorCode: Type.Optional(Type.String()),
|
|
1262
|
+
/** errno-style code of the innermost cause: ELOCKED, EPERM, EACCES, EBUSY, ECONNRESET, ABORT_ERR… */
|
|
1263
|
+
causeCode: Type.Optional(Type.String()),
|
|
1264
|
+
httpStatus: Type.Optional(Type.Integer()),
|
|
1265
|
+
/** sealed by the parent before it goes anywhere */
|
|
1266
|
+
text: Type.String(),
|
|
1267
|
+
origin: Type.Union([
|
|
1268
|
+
Type.Literal("prompt-preflight"),
|
|
1269
|
+
Type.Literal("model-response"),
|
|
1270
|
+
Type.Literal("auth-check"),
|
|
1271
|
+
Type.Literal("login"),
|
|
1272
|
+
Type.Literal("engine")
|
|
1273
|
+
])
|
|
1274
|
+
}, strict);
|
|
1275
|
+
const attestationShape = {
|
|
1276
|
+
/** the engine's packages MUST all be at the same version */
|
|
1277
|
+
engine: Type.Object({
|
|
1278
|
+
name: Type.String(),
|
|
1279
|
+
version: Type.String(),
|
|
1280
|
+
packageVersions: stringMap()
|
|
1281
|
+
}, strict),
|
|
1282
|
+
/** MUST equal the grant exactly, and so must the registry (allowlist typos vanish silently in the engine) */
|
|
1283
|
+
activeTools: Type.Array(Type.String()),
|
|
1284
|
+
/** sha256 of the system prompt the engine really holds: Cohorte's prompt + the engine's own suffix */
|
|
1285
|
+
effectiveSystemPromptSha256: Sha256,
|
|
1286
|
+
systemPromptPrefixOk: Type.Boolean(),
|
|
1287
|
+
/** from the engine's model runtime, no secret (DESIGN 3.7) */
|
|
1288
|
+
auth: Type.Object({
|
|
1289
|
+
provider: Type.String(),
|
|
1290
|
+
type: Type.Union([Type.Literal("oauth"), Type.Literal("api_key")]),
|
|
1291
|
+
source: Type.String(),
|
|
1292
|
+
subscription: Type.Boolean()
|
|
1293
|
+
}, strict),
|
|
1294
|
+
/** MUST equal AuthRequirement.baseUrl / the requested model */
|
|
1295
|
+
effective: Type.Object({
|
|
1296
|
+
provider: Type.String(),
|
|
1297
|
+
model: Type.String(),
|
|
1298
|
+
api: Type.String(),
|
|
1299
|
+
baseUrl: Type.String()
|
|
1300
|
+
}, strict),
|
|
1301
|
+
hooks: Type.Object({
|
|
1302
|
+
streamWrapperInstalled: Type.Boolean(),
|
|
1303
|
+
guardFetchInstalled: Type.Boolean(),
|
|
1304
|
+
onResponseChained: Type.Boolean(),
|
|
1305
|
+
shouldStopAfterTurn: Type.Boolean()
|
|
1306
|
+
}, strict),
|
|
1307
|
+
sessionFile: Type.String(),
|
|
1308
|
+
sessionId: Type.String({ minLength: 1 }),
|
|
1309
|
+
/** NAMES of env vars visible to the child; checked against allow ∪ OS_INJECTED_ENV[platform] */
|
|
1310
|
+
envKeys: Type.Array(Type.String()),
|
|
1311
|
+
platform: Type.String()
|
|
1312
|
+
};
|
|
1313
|
+
Type.Object({
|
|
1314
|
+
...attestationShape,
|
|
1315
|
+
hostProtocol: Type.Literal(1),
|
|
1316
|
+
extensionsLoaded: Type.Literal(0),
|
|
1317
|
+
extensionErrors: Type.Literal(0),
|
|
1318
|
+
modelFallback: Type.Literal(false),
|
|
1319
|
+
settings: Type.Object({
|
|
1320
|
+
compaction: Type.Literal(false),
|
|
1321
|
+
agentRetry: Type.Literal(false),
|
|
1322
|
+
providerMaxRetries: Type.Literal(0),
|
|
1323
|
+
transport: Type.Literal("sse")
|
|
1324
|
+
}, strict)
|
|
1325
|
+
}, strict);
|
|
1326
|
+
/**
|
|
1327
|
+
* What a `ready` frame CARRIES: an Attestation whose fixed members are not fixed yet. A child that fell back to
|
|
1328
|
+
* another model says `modelFallback: true`; were the frame schema to refuse that, the spawn would fail as a schema
|
|
1329
|
+
* violation at a JSON pointer. It fails instead on what `diffAttestation` names. Every Attestation is a claim.
|
|
1330
|
+
*/
|
|
1331
|
+
const AttestationClaim = Type.Object({
|
|
1332
|
+
...attestationShape,
|
|
1333
|
+
hostProtocol: Type.Integer(),
|
|
1334
|
+
extensionsLoaded: count(),
|
|
1335
|
+
extensionErrors: count(),
|
|
1336
|
+
modelFallback: Type.Boolean(),
|
|
1337
|
+
settings: Type.Object({
|
|
1338
|
+
compaction: Type.Boolean(),
|
|
1339
|
+
agentRetry: Type.Boolean(),
|
|
1340
|
+
providerMaxRetries: count(),
|
|
1341
|
+
transport: Type.String()
|
|
1342
|
+
}, strict)
|
|
1343
|
+
}, strict);
|
|
1344
|
+
const HostEvent = Type.Unsafe(Type.Union(RUNTIME_EVENT_TYPE_NAMES.map((type) => Type.Object({
|
|
1345
|
+
type: Type.Literal(type),
|
|
1346
|
+
at: IsoInstant,
|
|
1347
|
+
data: RUNTIME_EVENT_TYPES[type].data
|
|
1348
|
+
}, strict))));
|
|
1349
|
+
const frame = (t, members) => Type.Object({
|
|
1350
|
+
t: Type.Literal(t),
|
|
1351
|
+
...members
|
|
1352
|
+
}, strict);
|
|
1353
|
+
const init = (mode, members) => frame("init", {
|
|
1354
|
+
v: Type.Literal(1),
|
|
1355
|
+
nonce: Type.String({ minLength: 1 }),
|
|
1356
|
+
mode: Type.Literal(mode),
|
|
1357
|
+
...members
|
|
1358
|
+
});
|
|
1359
|
+
/** parent -> child */
|
|
1360
|
+
const ParentFrame = Type.Union([
|
|
1361
|
+
init("agent", {
|
|
1362
|
+
request: SpawnRequestWire,
|
|
1363
|
+
engine: EngineSettings
|
|
1364
|
+
}),
|
|
1365
|
+
init("auth-status", {
|
|
1366
|
+
providers: Type.Array(Type.String()),
|
|
1367
|
+
engine: AuthEngineSettings
|
|
1368
|
+
}),
|
|
1369
|
+
init("auth-login", {
|
|
1370
|
+
provider: Type.String(),
|
|
1371
|
+
engine: AuthEngineSettings
|
|
1372
|
+
}),
|
|
1373
|
+
init("auth-logout", {
|
|
1374
|
+
provider: Type.String(),
|
|
1375
|
+
engine: AuthEngineSettings
|
|
1376
|
+
}),
|
|
1377
|
+
frame("prompt", {
|
|
1378
|
+
id: Type.String(),
|
|
1379
|
+
text: Type.String(),
|
|
1380
|
+
note: Type.Optional(Type.Object({ text: Type.String() }, strict))
|
|
1381
|
+
}),
|
|
1382
|
+
frame("send", {
|
|
1383
|
+
id: Type.String(),
|
|
1384
|
+
messageId: Type.String(),
|
|
1385
|
+
text: Type.String(),
|
|
1386
|
+
delivery: delivery()
|
|
1387
|
+
}),
|
|
1388
|
+
frame("tool.result", {
|
|
1389
|
+
toolCallId: Type.String(),
|
|
1390
|
+
isError: Type.Boolean(),
|
|
1391
|
+
content: Type.Array(ToolContent),
|
|
1392
|
+
terminate: Type.Boolean(),
|
|
1393
|
+
resultRef: Type.Optional(Type.String())
|
|
1394
|
+
}),
|
|
1395
|
+
frame("pause", {}),
|
|
1396
|
+
frame("resume", {}),
|
|
1397
|
+
frame("stop-after-turn", { reason: Type.String() }),
|
|
1398
|
+
frame("abort", {
|
|
1399
|
+
id: Type.String(),
|
|
1400
|
+
reason: Type.String()
|
|
1401
|
+
}),
|
|
1402
|
+
frame("auth.answer", {
|
|
1403
|
+
id: Type.String(),
|
|
1404
|
+
value: Type.String()
|
|
1405
|
+
}),
|
|
1406
|
+
frame("inspect", { id: Type.String() }),
|
|
1407
|
+
frame("shutdown", {})
|
|
1408
|
+
]);
|
|
1409
|
+
/** child -> parent. All of it is untrusted input: validated here, sealed by the parent, tool input re-validated by the host. */
|
|
1410
|
+
const ChildFrame = Type.Union([
|
|
1411
|
+
frame("hello", {
|
|
1412
|
+
v: Type.Literal(1),
|
|
1413
|
+
pid: Type.Integer({ minimum: 1 }),
|
|
1414
|
+
nonce: Type.String({ minLength: 1 })
|
|
1415
|
+
}),
|
|
1416
|
+
frame("ready", { attestation: AttestationClaim }),
|
|
1417
|
+
frame("event", {
|
|
1418
|
+
seq: count(),
|
|
1419
|
+
event: HostEvent
|
|
1420
|
+
}),
|
|
1421
|
+
frame("tool.call", {
|
|
1422
|
+
seq: count(),
|
|
1423
|
+
ordinal: Type.Integer({ minimum: 1 }),
|
|
1424
|
+
engineToolCallId: Type.String(),
|
|
1425
|
+
tool: Type.String(),
|
|
1426
|
+
input: JsonValueSchema
|
|
1427
|
+
}),
|
|
1428
|
+
frame("tool.call.abandoned", {
|
|
1429
|
+
engineToolCallId: Type.String(),
|
|
1430
|
+
reason: Type.Union([Type.Literal("aborted"), Type.Literal("shutdown")])
|
|
1431
|
+
}),
|
|
1432
|
+
frame("provider.response", {
|
|
1433
|
+
requestId: Type.String(),
|
|
1434
|
+
status: Type.Integer(),
|
|
1435
|
+
headers: stringMap()
|
|
1436
|
+
}),
|
|
1437
|
+
frame("provider.request", {
|
|
1438
|
+
requestId: Type.String(),
|
|
1439
|
+
origin: Type.String(),
|
|
1440
|
+
authScheme: Type.Union([
|
|
1441
|
+
Type.Literal("bearer-jwt"),
|
|
1442
|
+
Type.Literal("bearer-opaque"),
|
|
1443
|
+
Type.Literal("api-key-header"),
|
|
1444
|
+
Type.Literal("none")
|
|
1445
|
+
]),
|
|
1446
|
+
refused: Type.Boolean()
|
|
1447
|
+
}),
|
|
1448
|
+
frame("parked", { at: Type.Literal("model-boundary") }),
|
|
1449
|
+
frame("heartbeat", {
|
|
1450
|
+
rssMb: Type.Number({ minimum: 0 }),
|
|
1451
|
+
state: Type.String()
|
|
1452
|
+
}),
|
|
1453
|
+
frame("auth.status", { statuses: Type.Array(ProviderAuthStatus) }),
|
|
1454
|
+
frame("auth.show", { event: JsonValueSchema }),
|
|
1455
|
+
frame("auth.ask", {
|
|
1456
|
+
id: Type.String(),
|
|
1457
|
+
prompt: JsonValueSchema
|
|
1458
|
+
}),
|
|
1459
|
+
frame("auth.done", { status: ProviderAuthStatus }),
|
|
1460
|
+
frame("settled", {
|
|
1461
|
+
exit: Type.Omit(AgentExit, ["error"], strict),
|
|
1462
|
+
signal: Type.Optional(ErrorSignal)
|
|
1463
|
+
}),
|
|
1464
|
+
frame("response", {
|
|
1465
|
+
id: Type.String(),
|
|
1466
|
+
ok: Type.Boolean(),
|
|
1467
|
+
data: Type.Optional(JsonValueSchema),
|
|
1468
|
+
error: Type.Optional(ErrorInfo)
|
|
1469
|
+
}),
|
|
1470
|
+
frame("fatal", {
|
|
1471
|
+
error: ErrorInfo,
|
|
1472
|
+
signal: Type.Optional(ErrorSignal)
|
|
1473
|
+
})
|
|
1474
|
+
]);
|
|
1475
|
+
function encodeFrame(frame, wire) {
|
|
1476
|
+
return wire === "lf" ? `${JSON.stringify(frame)}\n` : frame;
|
|
1477
|
+
}
|
|
1478
|
+
const checksOf = (union) => {
|
|
1479
|
+
const byTag = /* @__PURE__ */ new Map();
|
|
1480
|
+
for (const variant of union.anyOf) {
|
|
1481
|
+
const { t, mode } = variant.properties;
|
|
1482
|
+
const tag = String(t.const);
|
|
1483
|
+
const entry = {
|
|
1484
|
+
mode: mode === void 0 ? void 0 : String(mode.const),
|
|
1485
|
+
check: Compile(variant)
|
|
1486
|
+
};
|
|
1487
|
+
byTag.set(tag, [...byTag.get(tag) ?? [], entry]);
|
|
1488
|
+
}
|
|
1489
|
+
return byTag;
|
|
1490
|
+
};
|
|
1491
|
+
const CHECKS = {
|
|
1492
|
+
parent: checksOf(ParentFrame),
|
|
1493
|
+
child: checksOf(ChildFrame)
|
|
1494
|
+
};
|
|
1495
|
+
/** Total: whatever arrives, the answer is a frame or a rejection. It never throws and never echoes a value. */
|
|
1496
|
+
function decodeFrame(sender, raw, wire) {
|
|
1497
|
+
let value = raw;
|
|
1498
|
+
if (wire === "lf") {
|
|
1499
|
+
if (typeof raw !== "string") return err({
|
|
1500
|
+
reason: "not-a-frame",
|
|
1501
|
+
detail: `expected a line of text, got ${typeof raw}`
|
|
1502
|
+
});
|
|
1503
|
+
if (raw.length > 16777216) return err({
|
|
1504
|
+
reason: "too-large",
|
|
1505
|
+
detail: `a line of ${raw.length} characters`
|
|
1506
|
+
});
|
|
1507
|
+
const line = raw.endsWith("\n") ? raw.slice(0, -1) : raw;
|
|
1508
|
+
if (line.includes("\n")) return err({
|
|
1509
|
+
reason: "not-a-frame",
|
|
1510
|
+
detail: "more than one line"
|
|
1511
|
+
});
|
|
1512
|
+
try {
|
|
1513
|
+
value = JSON.parse(line);
|
|
1514
|
+
} catch {
|
|
1515
|
+
return err({
|
|
1516
|
+
reason: "not-json",
|
|
1517
|
+
detail: "the line is not JSON"
|
|
1518
|
+
});
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return err({
|
|
1522
|
+
reason: "not-a-frame",
|
|
1523
|
+
detail: "a frame is a JSON object"
|
|
1524
|
+
});
|
|
1525
|
+
try {
|
|
1526
|
+
const tag = value.t;
|
|
1527
|
+
const variants = typeof tag === "string" ? CHECKS[sender].get(tag) : void 0;
|
|
1528
|
+
if (typeof tag !== "string" || variants === void 0) return err({
|
|
1529
|
+
reason: "unknown-frame",
|
|
1530
|
+
detail: `no ${sender} frame has that "t"`
|
|
1531
|
+
});
|
|
1532
|
+
const mode = value.mode;
|
|
1533
|
+
const variant = variants.length === 1 ? variants[0] : variants.find((candidate) => candidate.mode === mode);
|
|
1534
|
+
if (variant === void 0) return err({
|
|
1535
|
+
reason: "schema-violation",
|
|
1536
|
+
detail: `${tag}: /mode is not a mode of that frame`
|
|
1537
|
+
});
|
|
1538
|
+
if (variant.check.Check(value)) return ok(value);
|
|
1539
|
+
const [first] = variant.check.Errors(value);
|
|
1540
|
+
return err({
|
|
1541
|
+
reason: "schema-violation",
|
|
1542
|
+
detail: `${tag}: ${first?.instancePath || "/"} ${first?.message ?? "is invalid"}`
|
|
1543
|
+
});
|
|
1544
|
+
} catch {
|
|
1545
|
+
return err({
|
|
1546
|
+
reason: "not-a-frame",
|
|
1547
|
+
detail: "the value could not be read"
|
|
1548
|
+
});
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
Object.freeze({ darwin: Object.freeze(["__CF_USER_TEXT_ENCODING"]) });
|
|
1552
|
+
//#endregion
|
|
7
1553
|
//#region ../../packages/runtime-pi/src/child/entry.ts
|
|
8
1554
|
const PI_PACKAGES = [
|
|
9
1555
|
"@earendil-works/pi-ai",
|
|
@@ -37,8 +1583,599 @@ async function main() {
|
|
|
37
1583
|
process.stdout.write(`agent-host selftest ok, pi=${result.version}\n`);
|
|
38
1584
|
return;
|
|
39
1585
|
}
|
|
40
|
-
|
|
41
|
-
|
|
1586
|
+
await runHost();
|
|
1587
|
+
}
|
|
1588
|
+
function nonceArg() {
|
|
1589
|
+
const value = process.argv.find((arg) => arg.startsWith("--cohorte-nonce="));
|
|
1590
|
+
if (!value) throw new Error("agent-host: missing Cohorte nonce");
|
|
1591
|
+
return value.slice(16);
|
|
1592
|
+
}
|
|
1593
|
+
function sendTransport() {
|
|
1594
|
+
if (typeof process.send === "function") return {
|
|
1595
|
+
send: (frame) => process.send?.(encodeFrame(frame, "ipc")),
|
|
1596
|
+
on: (listener) => process.on("message", (raw) => {
|
|
1597
|
+
const decoded = decodeFrame("parent", raw, "ipc");
|
|
1598
|
+
if (decoded.ok) listener(decoded.value);
|
|
1599
|
+
})
|
|
1600
|
+
};
|
|
1601
|
+
const input = createReadStream(null, { fd: 3 });
|
|
1602
|
+
const output = createWriteStream(null, { fd: 4 });
|
|
1603
|
+
let buffer = "";
|
|
1604
|
+
return {
|
|
1605
|
+
send: (frame) => output.write(encodeFrame(frame, "lf")),
|
|
1606
|
+
on: (listener) => {
|
|
1607
|
+
input.setEncoding("utf8");
|
|
1608
|
+
input.on("data", (chunk) => {
|
|
1609
|
+
buffer += chunk.toString();
|
|
1610
|
+
for (let end = buffer.indexOf("\n"); end !== -1; end = buffer.indexOf("\n")) {
|
|
1611
|
+
const line = buffer.slice(0, end);
|
|
1612
|
+
buffer = buffer.slice(end + 1);
|
|
1613
|
+
const decoded = decodeFrame("parent", line, "lf");
|
|
1614
|
+
if (decoded.ok) listener(decoded.value);
|
|
1615
|
+
}
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
};
|
|
1619
|
+
}
|
|
1620
|
+
const isoNow = () => (/* @__PURE__ */ new Date()).toISOString();
|
|
1621
|
+
function authStatus(runtime, provider) {
|
|
1622
|
+
const status = runtime.getProviderAuthStatus(provider);
|
|
1623
|
+
const configured = status.configured;
|
|
1624
|
+
const oauth = configured && runtime.isUsingOAuth(provider);
|
|
1625
|
+
const subscription = oauth && runtime.isUsingSubscription(provider);
|
|
1626
|
+
return {
|
|
1627
|
+
provider,
|
|
1628
|
+
state: !configured ? "absent" : oauth ? "oauth" : "api-key",
|
|
1629
|
+
subscription,
|
|
1630
|
+
checkedAt: isoNow(),
|
|
1631
|
+
...status.source || status.label ? { source: status.label ?? status.source } : {},
|
|
1632
|
+
billing: subscription ? "plan-limits" : configured ? "metered" : "unknown"
|
|
1633
|
+
};
|
|
1634
|
+
}
|
|
1635
|
+
function jsonEvent(event) {
|
|
1636
|
+
if (event.type === "auth_url") return {
|
|
1637
|
+
kind: "open-url",
|
|
1638
|
+
url: event.url,
|
|
1639
|
+
...event.instructions ? { instructions: event.instructions } : {}
|
|
1640
|
+
};
|
|
1641
|
+
if (event.type === "device_code") return {
|
|
1642
|
+
kind: "device-code",
|
|
1643
|
+
userCode: event.userCode,
|
|
1644
|
+
verificationUri: event.verificationUri
|
|
1645
|
+
};
|
|
1646
|
+
return {
|
|
1647
|
+
kind: event.type === "progress" ? "progress" : "info",
|
|
1648
|
+
message: event.message
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
function jsonPrompt(prompt) {
|
|
1652
|
+
if (prompt.type === "select") return {
|
|
1653
|
+
kind: "select",
|
|
1654
|
+
message: prompt.message,
|
|
1655
|
+
options: prompt.options.map((item) => ({
|
|
1656
|
+
id: item.id,
|
|
1657
|
+
label: item.label
|
|
1658
|
+
}))
|
|
1659
|
+
};
|
|
1660
|
+
return {
|
|
1661
|
+
kind: prompt.type === "manual_code" ? "manual-code" : prompt.type === "text" ? "text" : "secret",
|
|
1662
|
+
message: prompt.message
|
|
1663
|
+
};
|
|
1664
|
+
}
|
|
1665
|
+
function signalOf(error, origin) {
|
|
1666
|
+
const value = error;
|
|
1667
|
+
const modelsErrorCode = error instanceof ModelsError ? error.code : typeof value.code === "string" ? value.code : void 0;
|
|
1668
|
+
const status = typeof value.status === "number" ? value.status : typeof value.statusCode === "number" ? value.statusCode : void 0;
|
|
1669
|
+
let cause = value.cause;
|
|
1670
|
+
let causeCode;
|
|
1671
|
+
for (let depth = 0; depth < 8 && cause !== void 0; depth += 1) {
|
|
1672
|
+
const candidate = cause;
|
|
1673
|
+
if (causeCode === void 0 && typeof candidate.code === "string") causeCode = candidate.code;
|
|
1674
|
+
cause = candidate.cause;
|
|
1675
|
+
}
|
|
1676
|
+
const text = error instanceof Error ? error.message : typeof value.message === "string" ? value.message : String(error);
|
|
1677
|
+
return {
|
|
1678
|
+
...modelsErrorCode === void 0 ? {} : { modelsErrorCode },
|
|
1679
|
+
...causeCode === void 0 ? {} : { causeCode },
|
|
1680
|
+
...status === void 0 ? {} : { httpStatus: status },
|
|
1681
|
+
text,
|
|
1682
|
+
origin
|
|
1683
|
+
};
|
|
1684
|
+
}
|
|
1685
|
+
async function runHost() {
|
|
1686
|
+
const nonce = nonceArg();
|
|
1687
|
+
const transport = sendTransport();
|
|
1688
|
+
const send = (frame) => transport.send(frame);
|
|
1689
|
+
send({
|
|
1690
|
+
t: "hello",
|
|
1691
|
+
v: 1,
|
|
1692
|
+
pid: process.pid,
|
|
1693
|
+
nonce
|
|
1694
|
+
});
|
|
1695
|
+
let runtime;
|
|
1696
|
+
const answerWaiters = /* @__PURE__ */ new Map();
|
|
1697
|
+
const toolWaiters = /* @__PURE__ */ new Map();
|
|
1698
|
+
let agentListener;
|
|
1699
|
+
const shutdownController = new AbortController();
|
|
1700
|
+
transport.on((frame) => {
|
|
1701
|
+
if (frame.t === "auth.answer") {
|
|
1702
|
+
const waiter = answerWaiters.get(frame.id);
|
|
1703
|
+
answerWaiters.delete(frame.id);
|
|
1704
|
+
waiter?.(frame.value);
|
|
1705
|
+
}
|
|
1706
|
+
if (frame.t === "tool.result") toolWaiters.get(frame.toolCallId)?.(frame);
|
|
1707
|
+
if (agentListener && frame.t !== "init" && frame.t !== "auth.answer" && frame.t !== "tool.result") {
|
|
1708
|
+
agentListener(frame);
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1711
|
+
if (frame.t === "shutdown") {
|
|
1712
|
+
shutdownController.abort();
|
|
1713
|
+
process.exitCode = 0;
|
|
1714
|
+
}
|
|
1715
|
+
if (frame.t === "init") handleInit(frame, send, (value) => {
|
|
1716
|
+
runtime = value;
|
|
1717
|
+
}, () => runtime, answerWaiters, toolWaiters, (listener) => {
|
|
1718
|
+
agentListener = listener;
|
|
1719
|
+
}, shutdownController.signal);
|
|
1720
|
+
});
|
|
1721
|
+
}
|
|
1722
|
+
async function handleInit(frame, send, setRuntime, getRuntime, answerWaiters, toolWaiters, setAgentListener, shutdownSignal) {
|
|
1723
|
+
try {
|
|
1724
|
+
if (frame.mode === "agent") {
|
|
1725
|
+
await runAgent(frame, send, toolWaiters, setAgentListener);
|
|
1726
|
+
return;
|
|
1727
|
+
}
|
|
1728
|
+
const runtime = getRuntime() ?? await ModelRuntime.create({
|
|
1729
|
+
authPath: frame.engine.authPath,
|
|
1730
|
+
modelsPath: null,
|
|
1731
|
+
allowModelNetwork: false,
|
|
1732
|
+
refreshOnCreate: true
|
|
1733
|
+
});
|
|
1734
|
+
setRuntime(runtime);
|
|
1735
|
+
if (frame.mode === "auth-status") {
|
|
1736
|
+
send({
|
|
1737
|
+
t: "auth.status",
|
|
1738
|
+
statuses: frame.providers.map((provider) => authStatus(runtime, provider))
|
|
1739
|
+
});
|
|
1740
|
+
return;
|
|
1741
|
+
}
|
|
1742
|
+
if (frame.mode === "auth-logout") {
|
|
1743
|
+
await runtime.logout(frame.provider);
|
|
1744
|
+
send({
|
|
1745
|
+
t: "auth.done",
|
|
1746
|
+
status: authStatus(runtime, frame.provider)
|
|
1747
|
+
});
|
|
1748
|
+
return;
|
|
1749
|
+
}
|
|
1750
|
+
const provider = frame.provider;
|
|
1751
|
+
const authType = runtime.getProvider(provider)?.auth.oauth ? "oauth" : "api_key";
|
|
1752
|
+
await runtime.login(provider, authType, {
|
|
1753
|
+
signal: shutdownSignal,
|
|
1754
|
+
notify: (event) => send({
|
|
1755
|
+
t: "auth.show",
|
|
1756
|
+
event: jsonEvent(event)
|
|
1757
|
+
}),
|
|
1758
|
+
prompt: (prompt) => new Promise((resolve) => {
|
|
1759
|
+
const id = `auth-${Date.now()}-${Math.random().toString(16).slice(2)}`;
|
|
1760
|
+
answerWaiters.set(id, resolve);
|
|
1761
|
+
send({
|
|
1762
|
+
t: "auth.ask",
|
|
1763
|
+
id,
|
|
1764
|
+
prompt: jsonPrompt(prompt)
|
|
1765
|
+
});
|
|
1766
|
+
})
|
|
1767
|
+
});
|
|
1768
|
+
send({
|
|
1769
|
+
t: "auth.done",
|
|
1770
|
+
status: authStatus(runtime, provider)
|
|
1771
|
+
});
|
|
1772
|
+
} catch (error) {
|
|
1773
|
+
const origin = frame.mode === "agent" ? "auth-check" : frame.mode === "auth-login" ? "login" : "engine";
|
|
1774
|
+
send({
|
|
1775
|
+
t: "fatal",
|
|
1776
|
+
error: errorOf("configuration/engine-init", error instanceof Error ? error.message : String(error)),
|
|
1777
|
+
signal: signalOf(error, origin)
|
|
1778
|
+
});
|
|
1779
|
+
}
|
|
1780
|
+
}
|
|
1781
|
+
async function runAgent(frame, send, toolWaiters, setAgentListener) {
|
|
1782
|
+
const { request, engine } = frame;
|
|
1783
|
+
const promptBytes = readFileSync(request.systemPrompt.path);
|
|
1784
|
+
if (promptBytes.byteLength !== request.systemPrompt.bytes || sha256Hex(promptBytes) !== request.systemPrompt.sha256) throw new Error("agent-host: system prompt asset verification failed");
|
|
1785
|
+
const effectivePrompt = `${promptBytes.toString("utf8")}\nCurrent working directory: ${request.workingDirectory}\n`;
|
|
1786
|
+
const runtime = await ModelRuntime.create({
|
|
1787
|
+
authPath: engine.authPath,
|
|
1788
|
+
modelsPath: null,
|
|
1789
|
+
allowModelNetwork: false,
|
|
1790
|
+
refreshOnCreate: true
|
|
1791
|
+
});
|
|
1792
|
+
const catalogModel = runtime.getModel(request.model.provider, request.model.model);
|
|
1793
|
+
if (!catalogModel) throw new Error(`agent-host: model ${request.model.provider}/${request.model.model} is unavailable`);
|
|
1794
|
+
const model = {
|
|
1795
|
+
...catalogModel,
|
|
1796
|
+
baseUrl: request.auth.baseUrl
|
|
1797
|
+
};
|
|
1798
|
+
await runtime.refresh({
|
|
1799
|
+
providers: [request.model.provider],
|
|
1800
|
+
allowNetwork: false,
|
|
1801
|
+
signal: AbortSignal.timeout(15e3)
|
|
1802
|
+
});
|
|
1803
|
+
const liveAuth = await runtime.checkAuth(request.model.provider, { signal: AbortSignal.timeout(15e3) });
|
|
1804
|
+
const credentials = await runtime.listCredentials({ signal: AbortSignal.timeout(15e3) });
|
|
1805
|
+
const provider = runtime.getProvider(request.model.provider);
|
|
1806
|
+
const storedOAuth = credentials.some((credential) => credential.providerId === request.model.provider && credential.type === "oauth");
|
|
1807
|
+
const subscriptionAuth = liveAuth?.type === "oauth" && storedOAuth && provider?.auth.apiKey === void 0 && runtime.isUsingSubscription(request.model.provider);
|
|
1808
|
+
const apiAuth = liveAuth?.type === "api_key" && request.auth.allowApiKey;
|
|
1809
|
+
if (request.auth.mode === "subscription" ? !subscriptionAuth : !apiAuth) throw new Error(`agent-host: provider ${request.auth.provider} is not authenticated for the requested mode`);
|
|
1810
|
+
const versions = selfTest().versions;
|
|
1811
|
+
const oauth = liveAuth?.type === "oauth";
|
|
1812
|
+
const subscription = subscriptionAuth;
|
|
1813
|
+
const baseUrl = request.auth.baseUrl;
|
|
1814
|
+
let sequence = 0;
|
|
1815
|
+
let ordinal = 0;
|
|
1816
|
+
let turn = 0;
|
|
1817
|
+
let requestId = "";
|
|
1818
|
+
let responseStatus;
|
|
1819
|
+
let parked = false;
|
|
1820
|
+
let heartbeatState = "running";
|
|
1821
|
+
const usageTotals = {
|
|
1822
|
+
input: 0,
|
|
1823
|
+
output: 0,
|
|
1824
|
+
cacheRead: 0,
|
|
1825
|
+
cacheWrite: 0,
|
|
1826
|
+
total: 0
|
|
1827
|
+
};
|
|
1828
|
+
const heartbeat = setInterval(() => {
|
|
1829
|
+
send({
|
|
1830
|
+
t: "heartbeat",
|
|
1831
|
+
rssMb: process.memoryUsage().rss / 1024 / 1024,
|
|
1832
|
+
state: heartbeatState
|
|
1833
|
+
});
|
|
1834
|
+
}, 1e3);
|
|
1835
|
+
const originalFetch = globalThis.fetch;
|
|
1836
|
+
if (!originalFetch) throw new Error("agent-host: fetch is unavailable");
|
|
1837
|
+
const guardFetch = async (input, init) => {
|
|
1838
|
+
const url = new URL(typeof input === "string" ? input : input instanceof URL ? input.href : input.url);
|
|
1839
|
+
const pinned = new URL(request.auth.baseUrl);
|
|
1840
|
+
const headers = new Headers(input instanceof Request ? input.headers : void 0);
|
|
1841
|
+
new Headers(init?.headers).forEach((value, name) => {
|
|
1842
|
+
headers.set(name, value);
|
|
1843
|
+
});
|
|
1844
|
+
const authorization = headers.get("authorization");
|
|
1845
|
+
const authScheme = oauth ? authorization?.toLowerCase().startsWith("bearer ") ? "bearer-jwt" : "none" : headers.has("x-api-key") || headers.has("api-key") ? "api-key-header" : authorization?.toLowerCase().startsWith("bearer ") ? "bearer-opaque" : "none";
|
|
1846
|
+
const refused = url.origin !== pinned.origin;
|
|
1847
|
+
send({
|
|
1848
|
+
t: "provider.request",
|
|
1849
|
+
requestId,
|
|
1850
|
+
origin: url.origin,
|
|
1851
|
+
authScheme,
|
|
1852
|
+
refused
|
|
1853
|
+
});
|
|
1854
|
+
if (refused) throw new Error("agent-host: provider request left the pinned origin");
|
|
1855
|
+
return originalFetch(input, init);
|
|
1856
|
+
};
|
|
1857
|
+
const streamSimple = runtime.streamSimple.bind(runtime);
|
|
1858
|
+
runtime.streamSimple = ((streamModel, context, options) => lazyStream(streamModel, async () => streamSimple(streamModel, context, {
|
|
1859
|
+
...options,
|
|
1860
|
+
fetch: guardFetch,
|
|
1861
|
+
onResponse: async (response, responseModel) => {
|
|
1862
|
+
responseStatus = response.status;
|
|
1863
|
+
const allowed = Object.fromEntries([...Object.entries(response.headers)].filter(([name]) => engine.responseHeaderAllowlist.some((pattern) => {
|
|
1864
|
+
const normalized = pattern.toLowerCase();
|
|
1865
|
+
return normalized.endsWith("*") ? name.startsWith(normalized.slice(0, -1)) : name === normalized;
|
|
1866
|
+
})));
|
|
1867
|
+
send({
|
|
1868
|
+
t: "provider.response",
|
|
1869
|
+
requestId,
|
|
1870
|
+
status: response.status,
|
|
1871
|
+
headers: allowed
|
|
1872
|
+
});
|
|
1873
|
+
await options?.onResponse?.(response, responseModel);
|
|
1874
|
+
}
|
|
1875
|
+
})));
|
|
1876
|
+
const sendEvent = (event) => send({
|
|
1877
|
+
t: "event",
|
|
1878
|
+
seq: sequence++,
|
|
1879
|
+
event
|
|
1880
|
+
});
|
|
1881
|
+
const tools = request.tools.map((grant) => ({
|
|
1882
|
+
name: grant.tool,
|
|
1883
|
+
label: grant.tool,
|
|
1884
|
+
description: grant.description,
|
|
1885
|
+
executionMode: "sequential",
|
|
1886
|
+
parameters: Type$1.Unsafe(grant.inputSchema),
|
|
1887
|
+
execute: async (engineToolCallId, input, signal) => {
|
|
1888
|
+
const ordinalForCall = ++ordinal;
|
|
1889
|
+
const toolCallId = `tc_${request.incarnation}_${ordinalForCall}`;
|
|
1890
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
1891
|
+
toolWaiters.set(toolCallId, resolve);
|
|
1892
|
+
signal?.addEventListener("abort", () => reject(/* @__PURE__ */ new Error("tool call aborted")), { once: true });
|
|
1893
|
+
});
|
|
1894
|
+
send({
|
|
1895
|
+
t: "tool.call",
|
|
1896
|
+
seq: sequence++,
|
|
1897
|
+
ordinal: ordinalForCall,
|
|
1898
|
+
engineToolCallId,
|
|
1899
|
+
tool: grant.tool,
|
|
1900
|
+
input
|
|
1901
|
+
});
|
|
1902
|
+
const result = await resultPromise;
|
|
1903
|
+
toolWaiters.delete(toolCallId);
|
|
1904
|
+
return {
|
|
1905
|
+
content: result.content.map((content) => content.type === "text" ? {
|
|
1906
|
+
type: "text",
|
|
1907
|
+
text: content.text
|
|
1908
|
+
} : {
|
|
1909
|
+
type: "image",
|
|
1910
|
+
data: content.dataBase64,
|
|
1911
|
+
mimeType: content.mediaType
|
|
1912
|
+
}),
|
|
1913
|
+
details: {},
|
|
1914
|
+
...result.terminate ? { terminate: true } : {}
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
}));
|
|
1918
|
+
const settingsManager = SettingsManager.inMemory({
|
|
1919
|
+
transport: "sse",
|
|
1920
|
+
compaction: { enabled: false },
|
|
1921
|
+
retry: { enabled: false },
|
|
1922
|
+
defaultProjectTrust: "never"
|
|
1923
|
+
}, { projectTrusted: false });
|
|
1924
|
+
const resourceLoader = new DefaultResourceLoader({
|
|
1925
|
+
cwd: request.workingDirectory,
|
|
1926
|
+
agentDir: engine.agentDir,
|
|
1927
|
+
settingsManager,
|
|
1928
|
+
noExtensions: true,
|
|
1929
|
+
noSkills: true,
|
|
1930
|
+
noPromptTemplates: true,
|
|
1931
|
+
noThemes: true,
|
|
1932
|
+
noContextFiles: true,
|
|
1933
|
+
systemPrompt: effectivePrompt
|
|
1934
|
+
});
|
|
1935
|
+
await resourceLoader.reload();
|
|
1936
|
+
const { session } = await createAgentSession({
|
|
1937
|
+
cwd: request.workingDirectory,
|
|
1938
|
+
agentDir: engine.agentDir,
|
|
1939
|
+
modelRuntime: runtime,
|
|
1940
|
+
model,
|
|
1941
|
+
thinkingLevel: request.thinking,
|
|
1942
|
+
noTools: "all",
|
|
1943
|
+
customTools: tools,
|
|
1944
|
+
sessionManager: SessionManager.inMemory(request.workingDirectory),
|
|
1945
|
+
settingsManager,
|
|
1946
|
+
resourceLoader
|
|
1947
|
+
});
|
|
1948
|
+
session._systemPromptOverride = effectivePrompt;
|
|
1949
|
+
session.agent.state.systemPrompt = effectivePrompt;
|
|
1950
|
+
let stopAfterTurn = false;
|
|
1951
|
+
let lastEngineError;
|
|
1952
|
+
let lastEngineErrorOrigin = "model-response";
|
|
1953
|
+
session.agent.shouldStopAfterTurn = () => stopAfterTurn;
|
|
1954
|
+
session.subscribe((event) => {
|
|
1955
|
+
const at = isoNow();
|
|
1956
|
+
if (event.type === "turn_start") {
|
|
1957
|
+
turn += 1;
|
|
1958
|
+
requestId = `mr_${request.incarnation}_${turn}`;
|
|
1959
|
+
responseStatus = void 0;
|
|
1960
|
+
sendEvent({
|
|
1961
|
+
type: "agent.turn.started",
|
|
1962
|
+
at,
|
|
1963
|
+
data: { turn }
|
|
1964
|
+
});
|
|
1965
|
+
sendEvent({
|
|
1966
|
+
type: "model.requested",
|
|
1967
|
+
at,
|
|
1968
|
+
data: {
|
|
1969
|
+
requestId,
|
|
1970
|
+
model: request.model,
|
|
1971
|
+
attempt: 1
|
|
1972
|
+
}
|
|
1973
|
+
});
|
|
1974
|
+
} else if (event.type === "message_start" && event.message.role === "assistant") sendEvent({
|
|
1975
|
+
type: "agent.message.started",
|
|
1976
|
+
at,
|
|
1977
|
+
data: {
|
|
1978
|
+
messageId: requestId,
|
|
1979
|
+
role: "assistant"
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
else if (event.type === "message_update" && event.assistantMessageEvent.type === "text_delta") sendEvent({
|
|
1983
|
+
type: "agent.message.delta",
|
|
1984
|
+
at,
|
|
1985
|
+
data: {
|
|
1986
|
+
messageId: requestId,
|
|
1987
|
+
channel: "text",
|
|
1988
|
+
contentIndex: event.assistantMessageEvent.contentIndex,
|
|
1989
|
+
delta: event.assistantMessageEvent.delta
|
|
1990
|
+
}
|
|
1991
|
+
});
|
|
1992
|
+
else if (event.type === "message_end" && event.message.role === "assistant") {
|
|
1993
|
+
const message = event.message;
|
|
1994
|
+
const text = (message.content ?? []).filter((part) => part.type === "text").map((part) => part.text ?? "").join("");
|
|
1995
|
+
const usage = message.usage ?? {
|
|
1996
|
+
input: 0,
|
|
1997
|
+
output: 0,
|
|
1998
|
+
cacheRead: 0,
|
|
1999
|
+
cacheWrite: 0,
|
|
2000
|
+
totalTokens: 0
|
|
2001
|
+
};
|
|
2002
|
+
usageTotals.input += usage.input;
|
|
2003
|
+
usageTotals.output += usage.output;
|
|
2004
|
+
usageTotals.cacheRead += usage.cacheRead;
|
|
2005
|
+
usageTotals.cacheWrite += usage.cacheWrite;
|
|
2006
|
+
usageTotals.total += usage.totalTokens;
|
|
2007
|
+
const rawStop = String(message.stopReason ?? "stop");
|
|
2008
|
+
const stop = rawStop === "pending" || rawStop === "deferred" ? "error" : rawStop === "length" || rawStop === "toolUse" || rawStop === "aborted" || rawStop === "error" ? rawStop === "toolUse" ? "tool-use" : rawStop : "stop";
|
|
2009
|
+
if (rawStop === "pending" || rawStop === "deferred") sendEvent({
|
|
2010
|
+
type: "runtime.warning",
|
|
2011
|
+
at,
|
|
2012
|
+
data: {
|
|
2013
|
+
code: "engine-stop-reason-unmapped",
|
|
2014
|
+
message: `Pi stop reason ${rawStop} was normalized to error`
|
|
2015
|
+
}
|
|
2016
|
+
});
|
|
2017
|
+
sendEvent({
|
|
2018
|
+
type: "agent.message.completed",
|
|
2019
|
+
at,
|
|
2020
|
+
data: {
|
|
2021
|
+
messageId: requestId,
|
|
2022
|
+
role: "assistant",
|
|
2023
|
+
textSha256: sha256Hex(text),
|
|
2024
|
+
textBytes: Buffer.byteLength(text),
|
|
2025
|
+
preview: text.slice(0, 512),
|
|
2026
|
+
stop
|
|
2027
|
+
}
|
|
2028
|
+
});
|
|
2029
|
+
sendEvent({
|
|
2030
|
+
type: "model.responded",
|
|
2031
|
+
at,
|
|
2032
|
+
data: {
|
|
2033
|
+
requestId,
|
|
2034
|
+
requestedModel: request.model,
|
|
2035
|
+
effectiveModel: {
|
|
2036
|
+
provider: model.provider,
|
|
2037
|
+
model: model.id,
|
|
2038
|
+
api: model.api,
|
|
2039
|
+
baseUrl
|
|
2040
|
+
},
|
|
2041
|
+
authMode: request.auth.mode,
|
|
2042
|
+
authSource: oauth ? "oauth" : "api-key",
|
|
2043
|
+
durationMs: 0,
|
|
2044
|
+
...responseStatus === void 0 ? {} : { httpStatus: responseStatus },
|
|
2045
|
+
usage: {
|
|
2046
|
+
input: usage.input,
|
|
2047
|
+
output: usage.output,
|
|
2048
|
+
cacheRead: usage.cacheRead,
|
|
2049
|
+
cacheWrite: usage.cacheWrite,
|
|
2050
|
+
total: usage.totalTokens
|
|
2051
|
+
},
|
|
2052
|
+
attempt: 1,
|
|
2053
|
+
stop,
|
|
2054
|
+
quota: { known: false }
|
|
2055
|
+
}
|
|
2056
|
+
});
|
|
2057
|
+
} else if (event.type === "turn_end") sendEvent({
|
|
2058
|
+
type: "agent.turn.completed",
|
|
2059
|
+
at,
|
|
2060
|
+
data: {
|
|
2061
|
+
turn,
|
|
2062
|
+
toolCalls: ordinal
|
|
2063
|
+
}
|
|
2064
|
+
});
|
|
2065
|
+
else if (event.type === "agent_settled") {
|
|
2066
|
+
if (stopAfterTurn) {
|
|
2067
|
+
stopAfterTurn = false;
|
|
2068
|
+
parked = true;
|
|
2069
|
+
heartbeatState = "parked";
|
|
2070
|
+
send({
|
|
2071
|
+
t: "parked",
|
|
2072
|
+
at: "model-boundary"
|
|
2073
|
+
});
|
|
2074
|
+
return;
|
|
2075
|
+
}
|
|
2076
|
+
clearInterval(heartbeat);
|
|
2077
|
+
const engineError = session.agent.state.errorMessage;
|
|
2078
|
+
const failed = lastEngineError !== void 0 || typeof engineError === "string" && engineError.length > 0;
|
|
2079
|
+
const signal = failed ? signalOf(lastEngineError ?? new Error(engineError ?? "agent session failed"), lastEngineError === void 0 ? "model-response" : lastEngineErrorOrigin) : void 0;
|
|
2080
|
+
send({
|
|
2081
|
+
t: "settled",
|
|
2082
|
+
exit: {
|
|
2083
|
+
outcome: failed ? "failed" : "completed",
|
|
2084
|
+
stop: failed ? "engine-error" : "model-stop",
|
|
2085
|
+
usage: {
|
|
2086
|
+
tokens: usageTotals,
|
|
2087
|
+
modelRequests: turn,
|
|
2088
|
+
toolCalls: ordinal,
|
|
2089
|
+
turns: turn,
|
|
2090
|
+
wallClockMs: 0
|
|
2091
|
+
},
|
|
2092
|
+
lastSeq: sequence
|
|
2093
|
+
},
|
|
2094
|
+
...signal === void 0 ? {} : { signal }
|
|
2095
|
+
});
|
|
2096
|
+
}
|
|
2097
|
+
});
|
|
2098
|
+
send({
|
|
2099
|
+
t: "ready",
|
|
2100
|
+
attestation: {
|
|
2101
|
+
engine: {
|
|
2102
|
+
name: "pi",
|
|
2103
|
+
version: versions["@earendil-works/pi-coding-agent"] ?? engine.expectedEngineVersion,
|
|
2104
|
+
packageVersions: versions
|
|
2105
|
+
},
|
|
2106
|
+
activeTools: request.tools.map((grant) => grant.tool),
|
|
2107
|
+
effectiveSystemPromptSha256: sha256Hex(effectivePrompt),
|
|
2108
|
+
systemPromptPrefixOk: true,
|
|
2109
|
+
auth: {
|
|
2110
|
+
provider: request.auth.provider,
|
|
2111
|
+
type: oauth ? "oauth" : "api_key",
|
|
2112
|
+
source: "stored",
|
|
2113
|
+
subscription
|
|
2114
|
+
},
|
|
2115
|
+
effective: {
|
|
2116
|
+
provider: model.provider,
|
|
2117
|
+
model: model.id,
|
|
2118
|
+
api: model.api,
|
|
2119
|
+
baseUrl
|
|
2120
|
+
},
|
|
2121
|
+
hooks: {
|
|
2122
|
+
streamWrapperInstalled: true,
|
|
2123
|
+
guardFetchInstalled: true,
|
|
2124
|
+
onResponseChained: true,
|
|
2125
|
+
shouldStopAfterTurn: true
|
|
2126
|
+
},
|
|
2127
|
+
sessionFile: engine.sessionFile,
|
|
2128
|
+
sessionId: session.sessionId,
|
|
2129
|
+
envKeys: Object.keys(process.env),
|
|
2130
|
+
platform: process.platform,
|
|
2131
|
+
hostProtocol: 1,
|
|
2132
|
+
extensionsLoaded: 0,
|
|
2133
|
+
extensionErrors: 0,
|
|
2134
|
+
modelFallback: false,
|
|
2135
|
+
settings: {
|
|
2136
|
+
compaction: false,
|
|
2137
|
+
agentRetry: false,
|
|
2138
|
+
providerMaxRetries: 0,
|
|
2139
|
+
transport: "sse"
|
|
2140
|
+
}
|
|
2141
|
+
}
|
|
2142
|
+
});
|
|
2143
|
+
setAgentListener((parent) => {
|
|
2144
|
+
if (parent.t === "prompt") session.prompt(parent.note ? `${parent.text}${CONTINUATION_NOTE_SEPARATOR}${parent.note.text}` : parent.text).catch((error) => {
|
|
2145
|
+
lastEngineError = error;
|
|
2146
|
+
lastEngineErrorOrigin = "prompt-preflight";
|
|
2147
|
+
});
|
|
2148
|
+
else if (parent.t === "send") (parent.delivery === "steer" ? session.steer(parent.text) : session.followUp(parent.text)).then(() => send({
|
|
2149
|
+
t: "response",
|
|
2150
|
+
id: parent.id,
|
|
2151
|
+
ok: true
|
|
2152
|
+
}), (error) => send({
|
|
2153
|
+
t: "response",
|
|
2154
|
+
id: parent.id,
|
|
2155
|
+
ok: false,
|
|
2156
|
+
error: errorOf("tool-transient/agent-process-exit", error instanceof Error ? error.message : String(error))
|
|
2157
|
+
}));
|
|
2158
|
+
else if (parent.t === "abort") session.abort();
|
|
2159
|
+
else if (parent.t === "pause" || parent.t === "stop-after-turn") stopAfterTurn = true;
|
|
2160
|
+
else if (parent.t === "resume" && parked) {
|
|
2161
|
+
parked = false;
|
|
2162
|
+
heartbeatState = "running";
|
|
2163
|
+
session.prompt("[cohorte] continue").catch((error) => {
|
|
2164
|
+
lastEngineError = error;
|
|
2165
|
+
lastEngineErrorOrigin = "prompt-preflight";
|
|
2166
|
+
});
|
|
2167
|
+
} else if (parent.t === "inspect") send({
|
|
2168
|
+
t: "response",
|
|
2169
|
+
id: parent.id,
|
|
2170
|
+
ok: true,
|
|
2171
|
+
data: {
|
|
2172
|
+
state: session.isStreaming ? "running" : "idle",
|
|
2173
|
+
turn,
|
|
2174
|
+
pendingToolCalls: []
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
else if (parent.t === "shutdown") session.dispose();
|
|
2178
|
+
});
|
|
42
2179
|
}
|
|
43
2180
|
if (import.meta.main) main().catch((error) => {
|
|
44
2181
|
process.stderr.write(`agent-host fatal: ${error instanceof Error ? error.stack ?? error.message : String(error)}\n`);
|