@tangle-network/agent-runtime 0.117.0 → 0.120.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/{activation-BZ2FyX0O.js → activation-DdIpwQ0k.js} +3 -3
- package/dist/{activation-BZ2FyX0O.js.map → activation-DdIpwQ0k.js.map} +1 -1
- package/dist/agent.d.ts +1 -1
- package/dist/agent.js +3 -3
- package/dist/candidate-execution/index.d.ts +2 -2
- package/dist/candidate-execution/index.js +4 -4
- package/dist/{candidate-execution-PX61zXd2.js → candidate-execution-DDkSRPjY.js} +4 -4
- package/dist/{candidate-execution-PX61zXd2.js.map → candidate-execution-DDkSRPjY.js.map} +1 -1
- package/dist/{environment-provider-CTZjdIUg.d.ts → environment-provider-Bh4nX2qt.d.ts} +9 -1
- package/dist/{environment-provider-CzcEyRL7.js → environment-provider-Cr75N-rU.js} +2 -2
- package/dist/{environment-provider-CzcEyRL7.js.map → environment-provider-Cr75N-rU.js.map} +1 -1
- package/dist/environment-provider.d.ts +1 -1
- package/dist/environment-provider.js +1 -1
- package/dist/{improvement-cycle-Bcqz8rZI.js → improvement-cycle-zjR-MXwK.js} +7 -7
- package/dist/improvement-cycle-zjR-MXwK.js.map +1 -0
- package/dist/{index-X2YwyWkc.d.ts → index-B3bHsVLw.d.ts} +2 -2
- package/dist/{index-BHSYaWrC.d.ts → index-BLsKcxNd.d.ts} +3 -3
- package/dist/{index-DPWJxSRa.d.ts → index-CGADWaa_.d.ts} +20 -165
- package/dist/{index-BTUG4Xln.d.ts → index-I35151Fr.d.ts} +9 -9
- package/dist/index.d.ts +8 -8
- package/dist/index.js +12 -12
- package/dist/index.js.map +1 -1
- package/dist/intelligence.d.ts +4 -4
- package/dist/intelligence.js +6 -6
- package/dist/kernel.d.ts +4 -4
- package/dist/kernel.js +8 -7
- package/dist/{knowledge-CDlrayRc.js → knowledge-I1e9zQGj.js} +5 -5
- package/dist/{knowledge-CDlrayRc.js.map → knowledge-I1e9zQGj.js.map} +1 -1
- package/dist/knowledge.d.ts +1 -1
- package/dist/knowledge.js +1 -1
- package/dist/{local-harness-t6cDWDQ2.d.ts → local-harness-BIajef4A.d.ts} +29 -7
- package/dist/{loop-runner-bin-oyL7it5O.d.ts → loop-runner-bin-BeG9vTdE.d.ts} +3 -3
- package/dist/{loop-runner-bin-BxhhDtmG.js → loop-runner-bin-L996YVPS.js} +3 -3
- package/dist/{loop-runner-bin-BxhhDtmG.js.map → loop-runner-bin-L996YVPS.js.map} +1 -1
- package/dist/loop-runner-bin.d.ts +1 -1
- package/dist/loop-runner-bin.js +1 -1
- package/dist/mcp/bin.js +2 -2
- package/dist/mcp/bin.js.map +1 -1
- package/dist/mcp/index.d.ts +5 -5
- package/dist/mcp/index.js +8 -13
- package/dist/mcp/index.js.map +1 -1
- package/dist/{openai-tools-CO9X36zy.js → openai-tools-CvFEzw6f.js} +2 -2
- package/dist/{openai-tools-CO9X36zy.js.map → openai-tools-CvFEzw6f.js.map} +1 -1
- package/dist/{prepare-BbI6qGOW.js → prepare-DpV6np9e.js} +44 -34
- package/dist/{prepare-BbI6qGOW.js.map → prepare-DpV6np9e.js.map} +1 -1
- package/dist/primeintellect/index.d.ts +1 -1
- package/dist/{protected-model-port-B0yXMpLL.d.ts → protected-model-port-CoJOiINX.d.ts} +2 -2
- package/dist/{protected-model-port-COnyZfML.js → protected-model-port-vQLBRoAQ.js} +2 -2
- package/dist/{protected-model-port-COnyZfML.js.map → protected-model-port-vQLBRoAQ.js.map} +1 -1
- package/dist/{redact-BEtQtvd6.d.ts → redact-PQzmE1Jn.d.ts} +4 -4
- package/dist/run-layout-C2FGmZ3v.js +158 -0
- package/dist/run-layout-C2FGmZ3v.js.map +1 -0
- package/dist/{runtime-Ce39zfqn.js → runtime-D4e3GP5K.js} +7 -142
- package/dist/runtime-D4e3GP5K.js.map +1 -0
- package/dist/{spawn-journal-B_mu_oH2.js → spawn-journal-IeXpidO2.js} +2 -2
- package/dist/{spawn-journal-B_mu_oH2.js.map → spawn-journal-IeXpidO2.js.map} +1 -1
- package/dist/{structural-rollout-DLwW69R4.js → structural-rollout-DQHO3b2Y.js} +3 -3
- package/dist/{structural-rollout-DLwW69R4.js.map → structural-rollout-DQHO3b2Y.js.map} +1 -1
- package/dist/{supervise-U7_K5yuV.js → supervise-Cq50UT5-.js} +289 -1084
- package/dist/supervise-Cq50UT5-.js.map +1 -0
- package/dist/{supervisor-CAXiCKnX.js → supervisor-CpT9yAxL.js} +113 -35
- package/dist/supervisor-CpT9yAxL.js.map +1 -0
- package/dist/testing.js +10 -10
- package/dist/top-app-5unxqovu.js +1088 -0
- package/dist/top-app-5unxqovu.js.map +1 -0
- package/dist/tui/bin.d.ts +1 -0
- package/dist/tui/bin.js +19 -0
- package/dist/tui/bin.js.map +1 -0
- package/dist/tui/index.d.ts +209 -0
- package/dist/tui/index.js +2 -0
- package/dist/{util-Cc9g9Y-o.js → util-MVgdwuIS.js} +2 -2
- package/dist/{util-Cc9g9Y-o.js.map → util-MVgdwuIS.js.map} +1 -1
- package/dist/{workspace-archive-CqPIijFA.js → workspace-archive-B4SkNJjw.js} +2 -2
- package/dist/{workspace-archive-CqPIijFA.js.map → workspace-archive-B4SkNJjw.js.map} +1 -1
- package/package.json +11 -5
- package/dist/improvement-cycle-Bcqz8rZI.js.map +0 -1
- package/dist/runtime-Ce39zfqn.js.map +0 -1
- package/dist/supervise-U7_K5yuV.js.map +0 -1
- package/dist/supervisor-CAXiCKnX.js.map +0 -1
|
@@ -1,23 +1,43 @@
|
|
|
1
1
|
import { c as RuntimeRunStateError, i as ConfigError, o as NotFoundError, t as AgentEvalError$1, u as ValidationError } from "./errors-DEAvWQPy.js";
|
|
2
|
-
import { S as detachedSnapshot, b as workerTraceAnalysisStore, d as writeAllBytes, i as InMemorySpawnJournal, l as parseCommittedJsonLines, n as FileSpawnJournal, r as InMemoryResultBlobStore, t as FileResultBlobStore, u as prepareJsonlAppend, x as contentAddress } from "./spawn-journal-
|
|
3
|
-
import { a as
|
|
4
|
-
import { C as buildLoopSpanNodes, Ct as
|
|
2
|
+
import { S as detachedSnapshot, b as workerTraceAnalysisStore, d as writeAllBytes, i as InMemorySpawnJournal, l as parseCommittedJsonLines, n as FileSpawnJournal, r as InMemoryResultBlobStore, t as FileResultBlobStore, u as prepareJsonlAppend, x as contentAddress } from "./spawn-journal-IeXpidO2.js";
|
|
3
|
+
import { a as randomSuffix, c as stringifySafe, d as withTimeout, f as zeroTokenUsage, i as mapWithConcurrency, l as throwAbort, n as deleteBoxSafe, o as randomUuid, s as sleep, t as addTokenUsage, u as throwIfAborted } from "./util-MVgdwuIS.js";
|
|
4
|
+
import { $ as WORKER_TOKEN_FLOOR, At as promptControlProfileMaterialization, C as buildLoopSpanNodes, Ct as removeWorktree, Dt as defineProfileMaterializationContract, E as createOtelExporter, Et as controlProfileMaterialization, G as describeToolArgs, J as freeSlots, Lt as worktreeCliProfileMaterialization, Ot as fullProfileMaterialization, Q as teardownExecutor, St as createWorktree, Tt as assertProfileMaterialization, W as createActivityLog, Z as DEFAULT_SUCCESSFUL_SHUTDOWN_MS, a as pickBestDelivered, at as attestRuntimeOwnedScopeOwner, bt as worktreeProfileExecutionPlan, c as driverChild, ct as runtimeOwnedExecutorExecutionBinding, d as deriveNodeExecutionIdentity, dt as routerBrain, et as workerTokenFloor, f as recordScopeOwnerMaterialization, g as workerTraceEnv, gt as runBrainLoop, it as attestRuntimeOwnedExecutor, j as toOtelAttributes, jt as promptModelProfileMaterialization, k as generateSpanId, kt as profileMaterializationAxes, l as withDriverExecutor, lt as runtimeOwnedExecutorMaterialization, n as createSupervisor, o as runFinalizer, ot as inheritRuntimeOwnedExecutorAttestation, p as scopeOwnerExecutorNodeContext, pt as routerChatWithUsage, r as bestDelivered, rt as spendFromUsageEvents, s as runTree, st as newExecutionAttemptId, tt as assertValidBudget, ut as runtimeOwnedScopeOwnerRuntime, vt as runWorktreeChecks, xt as captureWorktreeDiff, yt as runWorktreeHarness } from "./supervisor-CpT9yAxL.js";
|
|
5
5
|
import { i as notifyRuntimeHookEvent, t as composeRuntimeHooks } from "./runtime-hooks-C7iJOWm3.js";
|
|
6
6
|
import { a as notifySandboxEventObserver, n as extractLlmCallEvent } from "./sandbox-events-Yhd1GYWl.js";
|
|
7
|
-
import { i as resolveAgentEnvironmentProvider, n as providerAsExecutor, r as providerAsSandboxClient, s as buildBackendOptions } from "./environment-provider-
|
|
7
|
+
import { i as resolveAgentEnvironmentProvider, n as providerAsExecutor, r as providerAsSandboxClient, s as buildBackendOptions } from "./environment-provider-Cr75N-rU.js";
|
|
8
8
|
import { t as createStdioToolServer } from "./tool-server-RcWgLIsL.js";
|
|
9
9
|
import { n as UI_LENSES } from "./substrate-B0TYNrXn.js";
|
|
10
10
|
import { argHash, computeFindingId, errorStreakDetector, estimateCost, isModelPriced, makeFinding, observeAll, repeatedActionDetector } from "@tangle-network/agent-eval";
|
|
11
11
|
import { agentProfileSchema, canonicalCandidateDigest, mergeAgentProfiles, validateAgentProfileSecurity } from "@tangle-network/agent-interface";
|
|
12
12
|
import { randomUUID } from "node:crypto";
|
|
13
13
|
import { mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
14
|
-
import
|
|
15
|
-
import path, { dirname, isAbsolute, join, resolve, sep } from "node:path";
|
|
14
|
+
import path, { dirname, resolve } from "node:path";
|
|
16
15
|
import { spawn } from "node:child_process";
|
|
17
|
-
import { homedir, tmpdir } from "node:os";
|
|
18
16
|
import { createServer, request } from "node:http";
|
|
19
17
|
import { request as request$1 } from "node:https";
|
|
20
18
|
import { Readable, Writable } from "node:stream";
|
|
19
|
+
//#region src/runtime/harness-role.ts
|
|
20
|
+
/**
|
|
21
|
+
* Harness ids that select a router/no-agent mode rather than a coding-agent runtime.
|
|
22
|
+
*
|
|
23
|
+
* `nanoclaw` is deliberately ABSENT: the shared capability table clamps its reasoning ceiling the
|
|
24
|
+
* same way it clamps `cli-base`, but nanoclaw does run an agent (a socket-bridge runner to the
|
|
25
|
+
* NanoClaw daemon) and every caller here treats it as a full harness. Add a row only with the
|
|
26
|
+
* behavior change stated, never to make the two clamps look symmetric.
|
|
27
|
+
*/
|
|
28
|
+
const NO_AGENT_HARNESSES = /* @__PURE__ */ new Set(["cli-base"]);
|
|
29
|
+
/** Whether this profile harness selects a real coding-agent runtime (vs. the router/no-agent mode
|
|
30
|
+
* or no preference at all). Accepts the looser `string` some local profile shapes declare, and
|
|
31
|
+
* preserves the caller's own harness type when it is narrower. */
|
|
32
|
+
function harnessRunsAgent(harness) {
|
|
33
|
+
return harness != null && !NO_AGENT_HARNESSES.has(harness);
|
|
34
|
+
}
|
|
35
|
+
/** The coding-agent harness this profile selects, or `undefined` when it selects none — the shape
|
|
36
|
+
* callers want when a no-agent mode must fall through to the router arm. */
|
|
37
|
+
function agentHarness(harness) {
|
|
38
|
+
return harnessRunsAgent(harness) ? harness : void 0;
|
|
39
|
+
}
|
|
40
|
+
//#endregion
|
|
21
41
|
//#region src/runtime/supervise/completion-gate.ts
|
|
22
42
|
/**
|
|
23
43
|
*
|
|
@@ -1949,402 +1969,16 @@ function createInbox() {
|
|
|
1949
1969
|
};
|
|
1950
1970
|
}
|
|
1951
1971
|
//#endregion
|
|
1952
|
-
//#region src/runtime/supervise/pi-mcp.ts
|
|
1953
|
-
/**
|
|
1954
|
-
*
|
|
1955
|
-
* MCP for the `pi` harness — the mount `piExecutor` performs before it spawns pi.
|
|
1956
|
-
*
|
|
1957
|
-
* pi ships NO MCP support of its own. MCP arrives through the `pi-mcp-adapter` extension, and the
|
|
1958
|
-
* adapter takes its config from a path the CALLER names: it registers a `--mcp-config <path>` flag
|
|
1959
|
-
* (`pi-mcp-adapter` `index.ts:252`, read back at `init.ts:101` and off raw argv at `utils.ts:55`).
|
|
1960
|
-
* So "give pi an MCP server" means: write the canonical `{ mcpServers: … }` object to a path this
|
|
1961
|
-
* run owns, pass that path with `--mcp-config`, make sure the adapter is loaded, and delete the
|
|
1962
|
-
* file afterwards. This module does exactly that and nothing else.
|
|
1963
|
-
*
|
|
1964
|
-
* Three rules carry the whole design:
|
|
1965
|
-
*
|
|
1966
|
-
* 1. **Fail loud, never fail safe.** If the profile declares MCP servers and `pi-mcp-adapter` is not
|
|
1967
|
-
* installed, `preparePiMcp` throws BEFORE any file is written and before pi is spawned. A run
|
|
1968
|
-
* whose tools never existed must fail, not score zero for the wrong reason.
|
|
1969
|
-
* 2. **`--no-extensions` is a trap.** When `extensions.pi.load` is an array, pi runs
|
|
1970
|
-
* `--no-extensions`, which suppresses ALL auto-discovery — cwd `.pi/extensions`, global
|
|
1971
|
-
* `~/.pi/agent/extensions`, AND the `settings.json` `packages` list that would otherwise have
|
|
1972
|
-
* auto-loaded the adapter. So `load: ['pi-memory']` alongside `mcp` kills MCP dead even though
|
|
1973
|
-
* the adapter IS installed. When MCP is declared, this module adds `pi-mcp-adapter` to the
|
|
1974
|
-
* resolved load list and REPORTS that it did (`PiMcpReceipt.adapterInjected`). The caller's
|
|
1975
|
-
* profile object is never mutated.
|
|
1976
|
-
* 3. **One config file per worker EXECUTION, never one per workspace.** `PiSeam.cwd` comes from the
|
|
1977
|
-
* `ExecutorConfig` and is therefore shared by every worker the factory builds, so a config
|
|
1978
|
-
* discovered by cwd is a file two concurrent siblings fight over. `mountPiMcpConfig` instead
|
|
1979
|
-
* creates a fresh `mkdtemp` directory — OS-guaranteed unique, so no counter, lock, merge or
|
|
1980
|
-
* restore is needed — and hands its path to `--mcp-config`. Concurrent workers built from ONE
|
|
1981
|
-
* seam get ONE config each and never collide.
|
|
1982
|
-
*
|
|
1983
|
-
* Why the flag is safe to emit here even though pi only lists it under `Extension CLI Flags`: it is
|
|
1984
|
-
* emitted ONLY when the profile declares at least one usable MCP server, and that case has already
|
|
1985
|
-
* passed the `piMcpAdapterAvailable()` gate above, so the extension that registers the flag is
|
|
1986
|
-
* guaranteed loadable (auto-discovered, or force-loaded by the `--extension` this module appends).
|
|
1987
|
-
* A machine without the adapter never reaches the flag — it fails at the gate with an install
|
|
1988
|
-
* command instead.
|
|
1989
|
-
*
|
|
1990
|
-
* Where the run's servers sit in pi's config layering, stated plainly because it is a real
|
|
1991
|
-
* behavioral edge: `--mcp-config` REPLACES pi's user-global override file (`config.ts:310`,
|
|
1992
|
-
* `getPiGlobalConfigPath(overridePath)`), so the operator's own `~/.pi/agent/mcp.json` servers no
|
|
1993
|
-
* longer leak into a worker — good. It does NOT suppress project discovery: the adapter still
|
|
1994
|
-
* layers `<cwd>/.mcp.json` and `<cwd>/.pi/mcp.json` on top (`config.ts:352-373`), and those are
|
|
1995
|
-
* later sources, so a workspace that ships a server under the SAME NAME overrides this run's entry.
|
|
1996
|
-
* The consequence is worth naming: an A/B whose arms differ only in `profile.mcp` is confounded
|
|
1997
|
-
* when the shared workspace carries its own MCP config. Deleting a file the run did not create
|
|
1998
|
-
* would be worse — a run cannot quietly edit the repo it was pointed at.
|
|
1999
|
-
*
|
|
2000
|
-
* Upstream note: `@tangle-network/agent-profile-materialize` is the shared owner of profile → harness
|
|
2001
|
-
* mapping and already declares `pi` a first-class `HarnessId`, but as of 0.9.4 its pi arm answers
|
|
2002
|
-
* `unsupported('mcp', 'pi MCP requires a loaded MCP extension; the base Pi runtime has no MCP
|
|
2003
|
-
* control')` and its pi extensions arm answers `unsupported('extensions', …)`. That refusal is now
|
|
2004
|
-
* factually stale — the shape is exactly its `kimi-code` arm's (write a config file, push a config
|
|
2005
|
-
* flag, because the harness has no cwd discovery worth trusting). Teaching the materializer these
|
|
2006
|
-
* two rows is the durable home for this mapping; until it lands, this module is the only thing in
|
|
2007
|
-
* the `backend: 'pi'` path that can honor `profile.mcp` at all.
|
|
2008
|
-
*
|
|
2009
|
-
* Verified against pi 0.83.0 and pi-mcp-adapter 2.15.0.
|
|
2010
|
-
*
|
|
2011
|
-
* @experimental
|
|
2012
|
-
*/
|
|
2013
|
-
/** The pi extension that gives pi MCP at all. Everything here is gated on it being loadable. */
|
|
2014
|
-
const PI_MCP_ADAPTER = "pi-mcp-adapter";
|
|
2015
|
-
/** Overrides adapter detection for installs whose path does not carry the adapter's name. */
|
|
2016
|
-
const PI_MCP_ADAPTER_ENV = "AGENT_RUNTIME_PI_MCP_ADAPTER";
|
|
2017
|
-
/** The adapter-registered flag that names this run's config file (`pi-mcp-adapter` `index.ts:252`). */
|
|
2018
|
-
const PI_MCP_CONFIG_FLAG = "--mcp-config";
|
|
2019
|
-
/** pi's agent home. Holds `settings.json` and `npm/node_modules/<extension>`. */
|
|
2020
|
-
function piAgentDir() {
|
|
2021
|
-
return process.env.PI_CODING_AGENT_DIR ?? join(homedir(), ".pi", "agent");
|
|
2022
|
-
}
|
|
2023
|
-
/** `<agentDir>/npm`, whose `node_modules/<pkg>` holds installed pi extensions. */
|
|
2024
|
-
function piNpmRoot() {
|
|
2025
|
-
return join(piAgentDir(), "npm");
|
|
2026
|
-
}
|
|
2027
|
-
/**
|
|
2028
|
-
* True when pi can actually consume MCP config — i.e. `pi-mcp-adapter` is installed. Ported from
|
|
2029
|
-
* cli-bridge `pi.ts:189`: an env override first (for vendored installs whose path does not carry the
|
|
2030
|
-
* adapter's name), then the npm install directory, then a scan of `settings.json` `packages` in
|
|
2031
|
-
* which a local-path entry is resolved by reading the target's `package.json` `name`. An
|
|
2032
|
-
* unreadable or absent `settings.json` reports "not detected"; it never throws and never assumes.
|
|
2033
|
-
*/
|
|
2034
|
-
function piMcpAdapterAvailable() {
|
|
2035
|
-
const override = process.env[PI_MCP_ADAPTER_ENV];
|
|
2036
|
-
if (override === "1" || override === "true") return true;
|
|
2037
|
-
if (override === "0" || override === "false") return false;
|
|
2038
|
-
const agentDir = piAgentDir();
|
|
2039
|
-
if (existsSync(join(agentDir, "npm", "node_modules", "pi-mcp-adapter"))) return true;
|
|
2040
|
-
try {
|
|
2041
|
-
const settings = JSON.parse(readFileSync(join(agentDir, "settings.json"), "utf-8"));
|
|
2042
|
-
if (!Array.isArray(settings.packages)) return false;
|
|
2043
|
-
return settings.packages.some((entry) => packageEntryIsAdapter(entry, agentDir));
|
|
2044
|
-
} catch {
|
|
2045
|
-
return false;
|
|
2046
|
-
}
|
|
2047
|
-
}
|
|
2048
|
-
/** One `settings.json` `packages[]` entry: a name containing the adapter, or a local path to it. */
|
|
2049
|
-
function packageEntryIsAdapter(entry, agentDir) {
|
|
2050
|
-
if (typeof entry !== "string") return false;
|
|
2051
|
-
if (entry.includes("pi-mcp-adapter")) return true;
|
|
2052
|
-
const spec = entry.replace(/^(file|path):(\/\/)?/, "");
|
|
2053
|
-
const windowsAbsolute = /^[A-Za-z]:[\\/]/.test(spec);
|
|
2054
|
-
if (!isAbsolute(spec) && !windowsAbsolute && !spec.startsWith(".")) return false;
|
|
2055
|
-
return readPackageName(isAbsolute(spec) || windowsAbsolute ? spec : join(agentDir, spec)) === PI_MCP_ADAPTER;
|
|
2056
|
-
}
|
|
2057
|
-
function readPackageName(packageDir) {
|
|
2058
|
-
try {
|
|
2059
|
-
const manifest = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf-8"));
|
|
2060
|
-
return typeof manifest.name === "string" ? manifest.name : void 0;
|
|
2061
|
-
} catch {
|
|
2062
|
-
return;
|
|
2063
|
-
}
|
|
2064
|
-
}
|
|
2065
|
-
/**
|
|
2066
|
-
* Read one `AgentProfileConfigValue` as public bytes. A `secret-ref` is REFUSED rather than
|
|
2067
|
-
* stringified: this executor has no secret provider, so the only alternatives are writing a
|
|
2068
|
-
* placeholder into the config (an auth failure disguised as a tool bug) or writing the literal
|
|
2069
|
-
* reference object (nonsense to the adapter). Plain strings, numbers and booleans are accepted
|
|
2070
|
-
* because profiles authored as JSON commonly carry them where the type says `{kind:'public'}`.
|
|
2071
|
-
*/
|
|
2072
|
-
function publicConfigString(value, where) {
|
|
2073
|
-
if (typeof value === "string") return value;
|
|
2074
|
-
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
2075
|
-
if (value && typeof value === "object" && value.kind === "public") return value.value;
|
|
2076
|
-
if (value && typeof value === "object" && value.kind === "secret-ref") throw new ConfigError(`piExecutor: ${where} is a secret-ref ("${value.key}") and piExecutor has no secret provider; resolve it before handing the profile to the executor, or declare a public value`);
|
|
2077
|
-
throw new ValidationError(`piExecutor: ${where} is not a public configuration value`);
|
|
2078
|
-
}
|
|
2079
|
-
/**
|
|
2080
|
-
* Build the canonical `{ mcpServers }` body the adapter reads, from `AgentProfile.mcp`.
|
|
2081
|
-
*
|
|
2082
|
-
* Every server is written with `directTools: true` (`pi-mcp-adapter` types.ts:351), which
|
|
2083
|
-
* registers its tools as NATIVE pi tools instead of hiding them behind the generic `mcp` tool.
|
|
2084
|
-
* Without it an agent must discover its own verbs before it can use them — connect to the server,
|
|
2085
|
-
* then describe each tool — and a measured run spent 58 turns and 639,632 input tokens doing that
|
|
2086
|
-
* before it could delegate once. A profile that declares an MCP server is asking for its tools,
|
|
2087
|
-
* not for a directory it has to browse.
|
|
2088
|
-
*
|
|
2089
|
-
* This is cli-bridge's `buildCanonicalMcpServers`
|
|
2090
|
-
* (`/home/drew/code/cli-bridge/src/backends/profile-support.ts:253`) reproduced field for field and
|
|
2091
|
-
* in the same key order — same drop of `enabled:false`, same stdio-versus-remote decision
|
|
2092
|
-
* (`isStdioMcpSpec`, `profile-support.ts:177`), same `{command, args, env}` and `{type, url,
|
|
2093
|
-
* headers}` bodies, same silent drop of an entry with nothing to connect to. For any server both
|
|
2094
|
-
* input types can express, the emitted JSON is byte-identical.
|
|
2095
|
-
*
|
|
2096
|
-
* The two input types are not the same type, so three differences remain and each is a field one
|
|
2097
|
-
* side cannot express:
|
|
2098
|
-
*
|
|
2099
|
-
* 1. **`cwd` (emitted here, absent there).** `AgentProfileLocalMcpServer` has `cwd`; cli-bridge's
|
|
2100
|
-
* `McpServerSpec` (`src/backends/types.ts:53`) has no such field, so it can never emit one. The
|
|
2101
|
-
* adapter honors it (`pi-mcp-adapter` `types.ts:325`, `ServerEntry.cwd`), so dropping it would
|
|
2102
|
-
* silently discard a declared setting. It is emitted where cli-bridge emits `timeout`.
|
|
2103
|
-
* 2. **`timeout` (emitted there, absent here).** `AgentProfileMcpServer` has no timeout field, so
|
|
2104
|
-
* there is nothing to emit. (The adapter's own knob is `requestTimeoutMs`, not `timeout`.)
|
|
2105
|
-
* 3. **A `url` with no declared transport.** cli-bridge DROPS it — its remote branch requires an
|
|
2106
|
-
* explicit `spec.type` — even though its own `McpServerSpec` docstring says "http is implied if
|
|
2107
|
-
* `url` is set" (`types.ts:53-56`). `transport` is optional on `AgentProfileRemoteMcpServer`, so
|
|
2108
|
-
* dropping such a server here would be exactly the silent-drop bug this module exists to fix.
|
|
2109
|
-
* We honor the documented convention and emit `type: 'http'`.
|
|
2110
|
-
*
|
|
2111
|
-
* `sse` is ACCEPTED and forwarded verbatim, as cli-bridge forwards it. The adapter connects a URL
|
|
2112
|
-
* server by probing StreamableHTTP and falling back to `SSEClientTransport` when the probe fails
|
|
2113
|
-
* (`pi-mcp-adapter` `server-manager.ts:765`), so an SSE-only endpoint does connect. Its
|
|
2114
|
-
* `ServerEntry` (`types.ts:319`) carries no transport discriminator at all, which means the `type`
|
|
2115
|
-
* key is inert to the adapter either way — but it is what cli-bridge writes, and a config file that
|
|
2116
|
-
* round-trips the caller's declared transport is the one that stays readable by both.
|
|
2117
|
-
*/
|
|
2118
|
-
function buildPiMcpServers(mcp) {
|
|
2119
|
-
const out = {};
|
|
2120
|
-
if (!mcp || typeof mcp !== "object") return out;
|
|
2121
|
-
for (const [name, raw] of Object.entries(mcp)) {
|
|
2122
|
-
if (!name || !raw || typeof raw !== "object") continue;
|
|
2123
|
-
if (raw.enabled === false) continue;
|
|
2124
|
-
const transport = raw.transport;
|
|
2125
|
-
const command = typeof raw.command === "string" && raw.command.length > 0 ? raw.command : null;
|
|
2126
|
-
const url = typeof raw.url === "string" && raw.url.length > 0 ? raw.url : null;
|
|
2127
|
-
if ((transport === "http" || transport === "sse" ? false : command !== null) && command !== null) {
|
|
2128
|
-
const args = Array.isArray(raw.args) ? raw.args.map((a, i) => publicConfigString(a, `mcp["${name}"].args[${i}]`)) : [];
|
|
2129
|
-
const env = readConfigRecord(raw.env, `mcp["${name}"].env`);
|
|
2130
|
-
out[name] = {
|
|
2131
|
-
command,
|
|
2132
|
-
...args.length > 0 ? { args } : {},
|
|
2133
|
-
...Object.keys(env).length > 0 ? { env } : {},
|
|
2134
|
-
...typeof raw.cwd === "string" && raw.cwd.length > 0 ? { cwd: raw.cwd } : {},
|
|
2135
|
-
directTools: true
|
|
2136
|
-
};
|
|
2137
|
-
continue;
|
|
2138
|
-
}
|
|
2139
|
-
if (url !== null && transport !== "stdio") {
|
|
2140
|
-
const headers = readConfigRecord(raw.headers, `mcp["${name}"].headers`);
|
|
2141
|
-
out[name] = {
|
|
2142
|
-
type: transport === "sse" ? "sse" : "http",
|
|
2143
|
-
url,
|
|
2144
|
-
...Object.keys(headers).length > 0 ? { headers } : {},
|
|
2145
|
-
directTools: true
|
|
2146
|
-
};
|
|
2147
|
-
}
|
|
2148
|
-
}
|
|
2149
|
-
return out;
|
|
2150
|
-
}
|
|
2151
|
-
function readConfigRecord(record, where) {
|
|
2152
|
-
if (!record || typeof record !== "object") return {};
|
|
2153
|
-
const out = {};
|
|
2154
|
-
for (const [key, value] of Object.entries(record)) {
|
|
2155
|
-
if (value === void 0) continue;
|
|
2156
|
-
out[key] = publicConfigString(value, `${where}["${key}"]`);
|
|
2157
|
-
}
|
|
2158
|
-
return out;
|
|
2159
|
-
}
|
|
2160
|
-
/**
|
|
2161
|
-
* Resolve one `extensions.pi.load` entry to an absolute extension entry file. Ported from cli-bridge
|
|
2162
|
-
* `resolvePiExtensionEntry` (`pi.ts:123`): an absolute path is used verbatim; a bare package name
|
|
2163
|
-
* resolves to `<npmRoot>/node_modules/<pkg>/<package.json main>`, `main` defaulting to `index.ts`.
|
|
2164
|
-
* The resolution is required because pi's per-file loader does NOT resolve bare specifiers against
|
|
2165
|
-
* `~/.pi/agent/npm/node_modules`. Fail-closed: an unreadable manifest or a missing entry file
|
|
2166
|
-
* rejects the run rather than letting an ablation arm run without the extension it declared.
|
|
2167
|
-
*/
|
|
2168
|
-
function resolvePiExtensionEntry(spec, npmRoot) {
|
|
2169
|
-
let entry;
|
|
2170
|
-
if (isAbsolute(spec)) entry = spec;
|
|
2171
|
-
else {
|
|
2172
|
-
const packageDir = join(npmRoot, "node_modules", spec);
|
|
2173
|
-
let main = "index.ts";
|
|
2174
|
-
try {
|
|
2175
|
-
const manifest = JSON.parse(readFileSync(join(packageDir, "package.json"), "utf-8"));
|
|
2176
|
-
if (typeof manifest.main === "string" && manifest.main) main = manifest.main;
|
|
2177
|
-
} catch (err) {
|
|
2178
|
-
throw new ConfigError(`piExecutor: cannot load pi extension "${spec}": ${join(packageDir, "package.json")} is not readable (install it with \`pi install npm:${spec}\`): ${err instanceof Error ? err.message : String(err)}`);
|
|
2179
|
-
}
|
|
2180
|
-
entry = join(packageDir, main);
|
|
2181
|
-
}
|
|
2182
|
-
if (!existsSync(entry)) throw new ConfigError(`piExecutor: cannot load pi extension "${spec}": resolved entry ${entry} does not exist`);
|
|
2183
|
-
return entry;
|
|
2184
|
-
}
|
|
2185
|
-
/** Does this `load` entry name the MCP adapter, before resolution? */
|
|
2186
|
-
function entryNamesAdapter(spec) {
|
|
2187
|
-
if (!isAbsolute(spec)) return spec === PI_MCP_ADAPTER;
|
|
2188
|
-
if (spec.split(/[\\/]/).includes("pi-mcp-adapter")) return true;
|
|
2189
|
-
const parent = spec.slice(0, spec.lastIndexOf(sep) + 1);
|
|
2190
|
-
return parent.length > 0 && readPackageName(parent) === "pi-mcp-adapter";
|
|
2191
|
-
}
|
|
2192
|
-
/**
|
|
2193
|
-
* Lower `AgentProfile.extensions.pi.load` into pi argv, honoring the `--no-extensions` trap.
|
|
2194
|
-
*
|
|
2195
|
-
* `load` absent (not an array) → no flags at all, so pi keeps its default discovery and the adapter
|
|
2196
|
-
* auto-loads from `settings.json` `packages`. `load` present → `--no-extensions` plus one
|
|
2197
|
-
* `--extension <absolute entry>` per listed extension, which is what makes an ablation arm
|
|
2198
|
-
* reproducible and leak-free.
|
|
2199
|
-
*
|
|
2200
|
-
* When `mcpRequested` is true and an explicit `load` array does not already name the adapter, the
|
|
2201
|
-
* adapter is APPENDED — otherwise `--no-extensions` would suppress the very extension that turns
|
|
2202
|
-
* the `--mcp-config` file into tools. The caller's profile is not touched; the addition is reported
|
|
2203
|
-
* through `adapterInjected` so it reaches the executor receipt and the live progress channel.
|
|
2204
|
-
*
|
|
2205
|
-
* A non-string or blank entry is a schema error here, not a silent skip: an ablation arm that
|
|
2206
|
-
* quietly drops a typo'd extension is the same class of structural false-null this module exists to
|
|
2207
|
-
* eliminate.
|
|
2208
|
-
*/
|
|
2209
|
-
function derivePiExtensionArgs(profile, mcpRequested) {
|
|
2210
|
-
const load = profile?.extensions?.pi?.load;
|
|
2211
|
-
if (!Array.isArray(load)) return {
|
|
2212
|
-
args: [],
|
|
2213
|
-
entries: [],
|
|
2214
|
-
adapterInjected: false
|
|
2215
|
-
};
|
|
2216
|
-
const declared = [];
|
|
2217
|
-
for (const [index, entry] of load.entries()) {
|
|
2218
|
-
if (typeof entry !== "string" || !entry.trim()) throw new ValidationError(`piExecutor: extensions.pi.load[${index}] must be a non-empty string (got ${entry === null ? "null" : typeof entry})`);
|
|
2219
|
-
declared.push(entry.trim());
|
|
2220
|
-
}
|
|
2221
|
-
const adapterInjected = mcpRequested && !declared.some(entryNamesAdapter);
|
|
2222
|
-
const resolvedSpecs = adapterInjected ? [...declared, PI_MCP_ADAPTER] : declared;
|
|
2223
|
-
const npmRoot = piNpmRoot();
|
|
2224
|
-
const entries = resolvedSpecs.map((spec) => resolvePiExtensionEntry(spec, npmRoot));
|
|
2225
|
-
const args = ["--no-extensions"];
|
|
2226
|
-
for (const entry of entries) args.push("--extension", entry);
|
|
2227
|
-
return {
|
|
2228
|
-
args,
|
|
2229
|
-
entries,
|
|
2230
|
-
adapterInjected
|
|
2231
|
-
};
|
|
2232
|
-
}
|
|
2233
|
-
/** Directory-name-safe form of a run id; the `mkdtemp` suffix supplies the uniqueness. */
|
|
2234
|
-
function runIdSlug(runId) {
|
|
2235
|
-
const slug = runId.replace(/[^A-Za-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
2236
|
-
return slug.length > 0 ? slug.slice(0, 60) : "worker";
|
|
2237
|
-
}
|
|
2238
|
-
/**
|
|
2239
|
-
* Write this worker execution's MCP config to a directory only it owns, and hand back the removal.
|
|
2240
|
-
*
|
|
2241
|
-
* Every property of the old cwd-discovered mount — a cross-process lock, a merge under whatever the
|
|
2242
|
-
* workspace already had, a verbatim byte restore, a symlink guard on `.pi` — existed because two
|
|
2243
|
-
* workers sharing a `PiSeam` shared one file path. `mkdtemp` deletes the shared path, and with it
|
|
2244
|
-
* the entire class of problem: the directory did not exist a moment ago, nothing else can name it,
|
|
2245
|
-
* `cleanup()` removes it whole, and two siblings started in the same millisecond get two configs.
|
|
2246
|
-
*
|
|
2247
|
-
* Returns `null` when there are no usable servers, so the no-MCP path writes nothing at all.
|
|
2248
|
-
*/
|
|
2249
|
-
function mountPiMcpConfig(mcpServers, options) {
|
|
2250
|
-
const serverNames = Object.keys(mcpServers);
|
|
2251
|
-
if (serverNames.length === 0) return null;
|
|
2252
|
-
const slug = runIdSlug(options.runId);
|
|
2253
|
-
const base = tmpdir();
|
|
2254
|
-
const prefix = `agent-runtime-pi-mcp-${slug}-`;
|
|
2255
|
-
let dir;
|
|
2256
|
-
try {
|
|
2257
|
-
dir = mkdtempSync(join(base, prefix));
|
|
2258
|
-
} catch (err) {
|
|
2259
|
-
throw new ConfigError(`piExecutor: cannot create a private MCP config directory under ${base}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2260
|
-
}
|
|
2261
|
-
const configPath = join(dir, "mcp.json");
|
|
2262
|
-
try {
|
|
2263
|
-
writeFileSync(configPath, JSON.stringify({ mcpServers }, null, 2), {
|
|
2264
|
-
flag: "wx",
|
|
2265
|
-
mode: 384
|
|
2266
|
-
});
|
|
2267
|
-
} catch (err) {
|
|
2268
|
-
rmSync(dir, {
|
|
2269
|
-
recursive: true,
|
|
2270
|
-
force: true
|
|
2271
|
-
});
|
|
2272
|
-
throw new ConfigError(`piExecutor: failed to write the MCP config at ${configPath}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2273
|
-
}
|
|
2274
|
-
let cleaned = false;
|
|
2275
|
-
return {
|
|
2276
|
-
configPath,
|
|
2277
|
-
serverNames,
|
|
2278
|
-
cleanup: () => {
|
|
2279
|
-
if (cleaned) return;
|
|
2280
|
-
cleaned = true;
|
|
2281
|
-
rmSync(dir, {
|
|
2282
|
-
recursive: true,
|
|
2283
|
-
force: true
|
|
2284
|
-
});
|
|
2285
|
-
}
|
|
2286
|
-
};
|
|
2287
|
-
}
|
|
2288
|
-
/**
|
|
2289
|
-
* The one call `piExecutor` makes before spawning pi: decide what pi must be told, fail loud if it
|
|
2290
|
-
* cannot work, then mount.
|
|
2291
|
-
*
|
|
2292
|
-
* Order matters and is the point:
|
|
2293
|
-
*
|
|
2294
|
-
* 1. Canonicalize `profile.mcp` first, so the adapter gate keys off servers that would ACTUALLY be
|
|
2295
|
-
* written — a block of `enabled:false` entries must not trip it.
|
|
2296
|
-
* 2. If any server survives and `pi-mcp-adapter` is not installed, throw. No file has been written
|
|
2297
|
-
* and pi has not been spawned.
|
|
2298
|
-
* 3. Lower `extensions.pi.load` (which may itself throw on an unresolvable extension) — still before
|
|
2299
|
-
* any file exists.
|
|
2300
|
-
* 4. Only then create this execution's private config directory and emit `--mcp-config`.
|
|
2301
|
-
*
|
|
2302
|
-
* A profile with no usable MCP server is a pure no-op: no adapter check, no directory, no flags, no
|
|
2303
|
-
* throw. `extensions.pi.load` on its own still produces flags, because reproducible ablation arms
|
|
2304
|
-
* need them whether or not MCP is involved.
|
|
2305
|
-
*
|
|
2306
|
-
* The caller MUST call `mount.cleanup()` on every exit path — settle, throw, abort, timeout, and
|
|
2307
|
-
* spawn failure — so the directory does not outlive the worker that owns it.
|
|
2308
|
-
*/
|
|
2309
|
-
function preparePiMcp(profile, options) {
|
|
2310
|
-
const mcpServers = buildPiMcpServers(profile?.mcp);
|
|
2311
|
-
const requested = Object.keys(mcpServers);
|
|
2312
|
-
if (requested.length > 0 && !piMcpAdapterAvailable()) throw new ConfigError(`piExecutor: cannot mount MCP servers for pi (${requested.join(", ")}): the ${PI_MCP_ADAPTER} extension is not installed. pi ships no MCP support of its own, so these servers would never connect and the worker would run tool-less. Looked in ${join(piNpmRoot(), "node_modules", PI_MCP_ADAPTER)} and in the \`packages\` list of ${join(piAgentDir(), "settings.json")}. Install it with \`pi install npm:${PI_MCP_ADAPTER}\`, or set ${PI_MCP_ADAPTER_ENV}=1 if it is vendored somewhere this check cannot see.`);
|
|
2313
|
-
const extensions = derivePiExtensionArgs(profile, requested.length > 0);
|
|
2314
|
-
if (requested.length === 0) return {
|
|
2315
|
-
args: extensions.args,
|
|
2316
|
-
mount: null,
|
|
2317
|
-
receipt: void 0
|
|
2318
|
-
};
|
|
2319
|
-
const mount = mountPiMcpConfig(mcpServers, options);
|
|
2320
|
-
if (!mount) return {
|
|
2321
|
-
args: extensions.args,
|
|
2322
|
-
mount: null,
|
|
2323
|
-
receipt: void 0
|
|
2324
|
-
};
|
|
2325
|
-
return {
|
|
2326
|
-
args: [
|
|
2327
|
-
...extensions.args,
|
|
2328
|
-
PI_MCP_CONFIG_FLAG,
|
|
2329
|
-
mount.configPath
|
|
2330
|
-
],
|
|
2331
|
-
mount,
|
|
2332
|
-
receipt: {
|
|
2333
|
-
servers: mount.serverNames,
|
|
2334
|
-
configPath: mount.configPath,
|
|
2335
|
-
extensions: extensions.entries,
|
|
2336
|
-
adapterInjected: extensions.adapterInjected
|
|
2337
|
-
}
|
|
2338
|
-
};
|
|
2339
|
-
}
|
|
2340
|
-
//#endregion
|
|
2341
1972
|
//#region src/runtime/supervise/trace-source.ts
|
|
2342
1973
|
/** Project a normalized tool step into the canonical agent-eval `ToolSpan`. When the step carries
|
|
2343
1974
|
* real wall-clock (`startedAt`/`endedAt`) the span gets a true duration; otherwise it collapses to
|
|
2344
|
-
* the single instant `at` (order + counts only — the historical behavior).
|
|
1975
|
+
* the single instant `at` (order + counts only — the historical behavior). A step whose source
|
|
1976
|
+
* never saw the call finish (`statusCaptured: false`) gets NO `status` — `ToolSpan.status` is
|
|
1977
|
+
* optional precisely so an unobserved outcome can stay unstated rather than defaulting to 'ok'. */
|
|
2345
1978
|
function toToolSpan(input, runId, seq, at) {
|
|
2346
1979
|
const startedAt = typeof input.startedAt === "number" ? input.startedAt : at;
|
|
2347
1980
|
const endedAt = typeof input.endedAt === "number" ? input.endedAt : startedAt;
|
|
1981
|
+
const status = input.statusCaptured === false ? void 0 : input.status ?? "ok";
|
|
2348
1982
|
return {
|
|
2349
1983
|
spanId: `${runId}-t${seq}`,
|
|
2350
1984
|
runId,
|
|
@@ -2353,7 +1987,7 @@ function toToolSpan(input, runId, seq, at) {
|
|
|
2353
1987
|
toolName: input.toolName,
|
|
2354
1988
|
args: input.args,
|
|
2355
1989
|
...input.argsCaptured === false ? { argsCaptured: false } : {},
|
|
2356
|
-
status:
|
|
1990
|
+
...status === void 0 ? {} : { status },
|
|
2357
1991
|
...input.error !== void 0 ? { error: input.error } : {},
|
|
2358
1992
|
startedAt,
|
|
2359
1993
|
endedAt,
|
|
@@ -2542,653 +2176,6 @@ function sandboxSessionTraceSource(box, sessionId, opts = {}) {
|
|
|
2542
2176
|
});
|
|
2543
2177
|
}
|
|
2544
2178
|
//#endregion
|
|
2545
|
-
//#region src/runtime/supervise/pi-executor.ts
|
|
2546
|
-
/**
|
|
2547
|
-
*
|
|
2548
|
-
* `piExecutor` — pi wrapped behind `Executor`, NOT forked.
|
|
2549
|
-
*
|
|
2550
|
-
* pi already implements everything a steerable worker needs, and implements it well: a queued
|
|
2551
|
-
* steering channel delivered between turns, follow-ups, abort, compaction, session persistence
|
|
2552
|
-
* with fork/branch, and an out-of-process line-delimited JSON protocol over stdin/stdout
|
|
2553
|
-
* (`pi --mode rpc`). Reimplementing any of that here would mean owning a turn loop someone else
|
|
2554
|
-
* already maintains. So this module is a thin protocol adapter, and every capability maps onto a
|
|
2555
|
-
* verb pi already has:
|
|
2556
|
-
*
|
|
2557
|
-
* `execute` → `prompt`, draining pi's event stream until `agent_settled`
|
|
2558
|
-
* `deliver` → `prompt` with `streamingBehavior` — pi owns the queue, we do not
|
|
2559
|
-
* `teardown` → `abort`, then close stdin and reap the process
|
|
2560
|
-
* `progress` → pi's `tool_execution_start`/`_end` + `turn_end` events, plus `get_state`'s
|
|
2561
|
-
* `pendingMessageCount` mirrored locally so the read stays synchronous
|
|
2562
|
-
* `traceSource` → the same tool events decoded into the shared `ToolSpan` currency
|
|
2563
|
-
* `resultArtifact` → the terminal successful assistant turn
|
|
2564
|
-
*
|
|
2565
|
-
* It is registered through the DOCUMENTED extension point (`ExecutorRegistry.register('pi', …)`),
|
|
2566
|
-
* so nothing in the resolver switches on it and a consumer can replace it wholesale.
|
|
2567
|
-
*
|
|
2568
|
-
* agent-runtime does NOT take a dependency on pi. The wire shapes (`RpcCommand`, `AgentEvent`)
|
|
2569
|
-
* are read structurally off JSON lines, so a pi that adds commands stays compatible and a pi that
|
|
2570
|
-
* is not installed simply fails loud at spawn instead of at import.
|
|
2571
|
-
*
|
|
2572
|
-
* Usage accounting: pi repeats the same assistant receipt in `message_end` and `turn_end`.
|
|
2573
|
-
* Only `turn_end` is counted, once per model call. Pi reports cache traffic separately from fresh
|
|
2574
|
-
* input, so all three input classes are folded into Runtime's two-field token total. Subscription
|
|
2575
|
-
* usage whose dollar price is absent or zero stays explicitly unknown through the live usage
|
|
2576
|
-
* stream and terminal artifact.
|
|
2577
|
-
*
|
|
2578
|
-
* ## What of the `AgentProfile` this executor honors, and what it does not
|
|
2579
|
-
*
|
|
2580
|
-
* This adapter is NOT a profile materializer. It reads a deliberately small part of
|
|
2581
|
-
* `spec.profile`, and a caller who needs the rest must lower it before handing the profile over.
|
|
2582
|
-
* Stated exhaustively so a silent drop is never a surprise:
|
|
2583
|
-
*
|
|
2584
|
-
* | Field | Status |
|
|
2585
|
-
* | --- | --- |
|
|
2586
|
-
* | `profile.name` | honored — trace `runId` label only, no behavioral effect |
|
|
2587
|
-
* | `profile.prompt.systemPrompt` | honored — prepended to the task text (pi RPC takes no separate system-prompt channel) |
|
|
2588
|
-
* | `profile.mcp` | honored — written to this execution's own file and passed as `--mcp-config` for `pi-mcp-adapter`; see `pi-mcp.ts` |
|
|
2589
|
-
* | `profile.extensions.pi.load` | honored — lowered to `--no-extensions` + `--extension <abs>` |
|
|
2590
|
-
* | `profile.prompt.instructions` | honored — appended to the system prompt, one per line |
|
|
2591
|
-
* | `profile.model.default` | honored — overrides the seam's `model`; the seam is the fallback for profiles that select none |
|
|
2592
|
-
* | `profile.model.reasoningEffort` | DROPPED — no `--thinking` flag is emitted, so pi's configured `defaultThinkingLevel` applies |
|
|
2593
|
-
* | `profile.tools` | DROPPED — no `--no-tools` / allow-deny mapping; pi runs its full builtin tool set |
|
|
2594
|
-
* | `profile.permissions` | DROPPED |
|
|
2595
|
-
* | `profile.resources` (context / skills / commands / subagents / instructions) | DROPPED — nothing is written into the run cwd |
|
|
2596
|
-
* | `profile.hooks` | DROPPED |
|
|
2597
|
-
* | `profile.subagents`, `profile.connections`, `profile.modes`, `profile.confidential` | DROPPED |
|
|
2598
|
-
* | `profile.resources.failOnError` | not consulted — the MCP path above is unconditionally fail-closed, which is the stricter reading |
|
|
2599
|
-
* | every other `profile.extensions.<ns>` | DROPPED — only the `pi` namespace is read |
|
|
2600
|
-
*
|
|
2601
|
-
* Closing those gaps belongs in `@tangle-network/agent-profile-materialize`, whose plan this
|
|
2602
|
-
* executor would then apply, rather than in a second mapping grown here.
|
|
2603
|
-
*
|
|
2604
|
-
* @experimental
|
|
2605
|
-
*/
|
|
2606
|
-
/** The runtime name `piExecutor` registers under. */
|
|
2607
|
-
const PI_RUNTIME = "pi";
|
|
2608
|
-
/** Seam key the registry threads a `PiSeam` through (`ExecutorContext.seams['pi']`). */
|
|
2609
|
-
const piSeamKey = "pi";
|
|
2610
|
-
/** Build the `Executor` for one pi worker. Registered as runtime `'pi'`. */
|
|
2611
|
-
const piExecutor = (spec, ctx) => {
|
|
2612
|
-
const seam = {
|
|
2613
|
-
...readPiSeam(ctx),
|
|
2614
|
-
...spec.profile.model?.default ? { model: spec.profile.model.default } : {}
|
|
2615
|
-
};
|
|
2616
|
-
const traceEnv = workerTraceEnv(ctx);
|
|
2617
|
-
const inbox = createInbox();
|
|
2618
|
-
const activity = createActivityLog(seam.activityWindow ?? 12);
|
|
2619
|
-
const runId = `pi-${spec.profile.name ?? "worker"}-${Date.now()}`;
|
|
2620
|
-
const trace = createPushTraceSource({ runId });
|
|
2621
|
-
const executionId = ctx.node?.nodeId ?? `pi-run-${randomUUID()}`;
|
|
2622
|
-
const attemptId = ctx.node?.attemptId ?? newExecutionAttemptId(executionId);
|
|
2623
|
-
const derived = [];
|
|
2624
|
-
const controller = new AbortController();
|
|
2625
|
-
const cascade = () => controller.abort();
|
|
2626
|
-
if (ctx.signal.aborted) controller.abort();
|
|
2627
|
-
else ctx.signal.addEventListener("abort", cascade, { once: true });
|
|
2628
|
-
const state = {
|
|
2629
|
-
turns: 0,
|
|
2630
|
-
lastText: "",
|
|
2631
|
-
note: "starting",
|
|
2632
|
-
proc: void 0,
|
|
2633
|
-
artifact: void 0
|
|
2634
|
-
};
|
|
2635
|
-
const executor = {
|
|
2636
|
-
runtime: "pi",
|
|
2637
|
-
deliver: (m) => inbox.deliver(m),
|
|
2638
|
-
progress: () => ({
|
|
2639
|
-
turns: state.turns,
|
|
2640
|
-
pendingMessages: inbox.pending(),
|
|
2641
|
-
recentActivity: activity.read(),
|
|
2642
|
-
...derived.length > 0 ? { derived: [...derived] } : {},
|
|
2643
|
-
note: state.note
|
|
2644
|
-
}),
|
|
2645
|
-
traceSource: () => trace.source,
|
|
2646
|
-
execute(task, signal) {
|
|
2647
|
-
return streamPiSession({
|
|
2648
|
-
task,
|
|
2649
|
-
signal,
|
|
2650
|
-
controller,
|
|
2651
|
-
seam,
|
|
2652
|
-
traceEnv,
|
|
2653
|
-
spec,
|
|
2654
|
-
runId,
|
|
2655
|
-
inbox,
|
|
2656
|
-
activity,
|
|
2657
|
-
derived,
|
|
2658
|
-
record: (step) => {
|
|
2659
|
-
trace.record(step);
|
|
2660
|
-
},
|
|
2661
|
-
state
|
|
2662
|
-
});
|
|
2663
|
-
},
|
|
2664
|
-
async teardown(grace) {
|
|
2665
|
-
controller.abort();
|
|
2666
|
-
const proc = state.proc;
|
|
2667
|
-
if (!proc || proc.exitCode !== null || proc.killed) return { destroyed: true };
|
|
2668
|
-
sendCommand(proc, { type: "abort" });
|
|
2669
|
-
return killPi(proc, grace);
|
|
2670
|
-
},
|
|
2671
|
-
resultArtifact() {
|
|
2672
|
-
if (!state.artifact) throw new ValidationError("piExecutor: resultArtifact() read before stream drained");
|
|
2673
|
-
return state.artifact;
|
|
2674
|
-
}
|
|
2675
|
-
};
|
|
2676
|
-
if (ctx.node === void 0) return executor;
|
|
2677
|
-
return attestRuntimeOwnedExecutor(executor, {
|
|
2678
|
-
effectiveProfile: spec.profile,
|
|
2679
|
-
backend: "pi",
|
|
2680
|
-
model: seam.model ? {
|
|
2681
|
-
status: "known",
|
|
2682
|
-
id: seam.model
|
|
2683
|
-
} : {
|
|
2684
|
-
status: "unknown",
|
|
2685
|
-
reason: "pi selected its configured default model"
|
|
2686
|
-
},
|
|
2687
|
-
execution: {
|
|
2688
|
-
kind: "run",
|
|
2689
|
-
id: executionId
|
|
2690
|
-
},
|
|
2691
|
-
materializer: "pi-rpc-agent-profile",
|
|
2692
|
-
plan: {
|
|
2693
|
-
kind: "pi-rpc-session",
|
|
2694
|
-
bin: seam.bin ?? "pi",
|
|
2695
|
-
args: seam.args ?? [],
|
|
2696
|
-
cwd: seam.cwd ?? null,
|
|
2697
|
-
model: seam.model ?? null,
|
|
2698
|
-
turnTimeoutMs: seam.turnTimeoutMs ?? null
|
|
2699
|
-
}
|
|
2700
|
-
}, {
|
|
2701
|
-
attemptId,
|
|
2702
|
-
binding: {
|
|
2703
|
-
executionId,
|
|
2704
|
-
bin: seam.bin ?? "pi",
|
|
2705
|
-
cwd: seam.cwd ?? null,
|
|
2706
|
-
model: seam.model ?? null
|
|
2707
|
-
},
|
|
2708
|
-
descriptor: {
|
|
2709
|
-
kind: "pi-rpc-run",
|
|
2710
|
-
transport: "process",
|
|
2711
|
-
backend: "pi"
|
|
2712
|
-
}
|
|
2713
|
-
});
|
|
2714
|
-
};
|
|
2715
|
-
/**
|
|
2716
|
-
* One pi RPC session, run to `agent_settled`. Every steer delivered while the turn is in flight is
|
|
2717
|
-
* forwarded to pi IMMEDIATELY (pi queues and delivers it at its own turn boundary — the queue we
|
|
2718
|
-
* deliberately do not reimplement), and any steer still unread when pi settles re-prompts it.
|
|
2719
|
-
* `agent_end` is deliberately not terminal: Pi may auto-retry or compact after emitting it.
|
|
2720
|
-
*/
|
|
2721
|
-
async function* streamPiSession(args) {
|
|
2722
|
-
const { seam, inbox, activity, state } = args;
|
|
2723
|
-
const started = Date.now();
|
|
2724
|
-
const tokens = {
|
|
2725
|
-
input: 0,
|
|
2726
|
-
output: 0
|
|
2727
|
-
};
|
|
2728
|
-
let usd = 0;
|
|
2729
|
-
let usdKnown = true;
|
|
2730
|
-
let tokensKnown = true;
|
|
2731
|
-
throwIfAborted(args.signal);
|
|
2732
|
-
throwIfAborted(args.controller.signal);
|
|
2733
|
-
const piMcp = preparePiMcp(args.spec.profile, {
|
|
2734
|
-
...seam.cwd !== void 0 ? { cwd: seam.cwd } : {},
|
|
2735
|
-
runId: args.runId
|
|
2736
|
-
});
|
|
2737
|
-
if (piMcp.receipt) {
|
|
2738
|
-
const injected = piMcp.receipt.adapterInjected ? ` (+${PI_MCP_ADAPTER} added)` : "";
|
|
2739
|
-
activity.push({
|
|
2740
|
-
at: Date.now(),
|
|
2741
|
-
kind: "note",
|
|
2742
|
-
label: "mcp",
|
|
2743
|
-
detail: `${piMcp.receipt.servers.join(", ")}${injected}`
|
|
2744
|
-
});
|
|
2745
|
-
args.derived.push(`mcp: mounted ${piMcp.receipt.servers.join(", ")} via --mcp-config ${piMcp.receipt.configPath}`);
|
|
2746
|
-
if (piMcp.receipt.adapterInjected) args.derived.push(`extensions: added ${PI_MCP_ADAPTER} to extensions.pi.load — the profile's own list would have run under --no-extensions and suppressed it, mounting zero servers`);
|
|
2747
|
-
}
|
|
2748
|
-
let proc;
|
|
2749
|
-
try {
|
|
2750
|
-
proc = spawnPi(seam, piMcp.args, args.traceEnv);
|
|
2751
|
-
} catch (spawnFailure) {
|
|
2752
|
-
piMcp.mount?.cleanup();
|
|
2753
|
-
throw spawnFailure;
|
|
2754
|
-
}
|
|
2755
|
-
state.proc = proc;
|
|
2756
|
-
state.note = "connected";
|
|
2757
|
-
const events = [];
|
|
2758
|
-
const pendingTools = /* @__PURE__ */ new Map();
|
|
2759
|
-
const awaitingPromptResponses = /* @__PURE__ */ new Set();
|
|
2760
|
-
let settled = false;
|
|
2761
|
-
let acceptedPromptSinceSettlement = false;
|
|
2762
|
-
let promptSequence = 0;
|
|
2763
|
-
let lastAssistant;
|
|
2764
|
-
let failure;
|
|
2765
|
-
let abortDeadline;
|
|
2766
|
-
let exited = false;
|
|
2767
|
-
let exitCode;
|
|
2768
|
-
let wake;
|
|
2769
|
-
const notify = () => {
|
|
2770
|
-
const w = wake;
|
|
2771
|
-
wake = void 0;
|
|
2772
|
-
w?.();
|
|
2773
|
-
};
|
|
2774
|
-
const stdoutLines = readJsonLines(proc, (value) => {
|
|
2775
|
-
const ev = value;
|
|
2776
|
-
events.push(ev);
|
|
2777
|
-
notify();
|
|
2778
|
-
});
|
|
2779
|
-
proc.once("exit", (code) => {
|
|
2780
|
-
exited = true;
|
|
2781
|
-
exitCode = code;
|
|
2782
|
-
notify();
|
|
2783
|
-
});
|
|
2784
|
-
proc.once("error", (e) => {
|
|
2785
|
-
failure = new ValidationError(`piExecutor: pi failed to start: ${e.message}`);
|
|
2786
|
-
notify();
|
|
2787
|
-
});
|
|
2788
|
-
const abortAll = () => {
|
|
2789
|
-
if (abortDeadline !== void 0) return;
|
|
2790
|
-
sendCommand(proc, { type: "abort" });
|
|
2791
|
-
abortDeadline = Date.now() + PI_ABORT_RECEIPT_MS;
|
|
2792
|
-
state.note = "aborting";
|
|
2793
|
-
notify();
|
|
2794
|
-
};
|
|
2795
|
-
args.signal.addEventListener("abort", abortAll, { once: true });
|
|
2796
|
-
args.controller.signal.addEventListener("abort", abortAll, { once: true });
|
|
2797
|
-
const system = [args.spec.profile.prompt?.systemPrompt, ...args.spec.profile.prompt?.instructions ?? []].filter((line) => typeof line === "string" && line.trim().length > 0).join("\n");
|
|
2798
|
-
const opening = system ? `${system}\n\n${taskText(args.task)}` : taskText(args.task);
|
|
2799
|
-
const deadline = seam.turnTimeoutMs ? Date.now() + seam.turnTimeoutMs : void 0;
|
|
2800
|
-
const sendPrompt = (message, streamingBehavior) => {
|
|
2801
|
-
const id = `agent-runtime-prompt-${++promptSequence}`;
|
|
2802
|
-
awaitingPromptResponses.add(id);
|
|
2803
|
-
sendCommand(proc, {
|
|
2804
|
-
id,
|
|
2805
|
-
type: "prompt",
|
|
2806
|
-
message,
|
|
2807
|
-
...streamingBehavior ? { streamingBehavior } : {}
|
|
2808
|
-
});
|
|
2809
|
-
};
|
|
2810
|
-
try {
|
|
2811
|
-
if (args.signal.aborted || args.controller.signal.aborted) throw abortError();
|
|
2812
|
-
sendPrompt(opening);
|
|
2813
|
-
state.note = "turn 0";
|
|
2814
|
-
for (;;) {
|
|
2815
|
-
if (!settled && abortDeadline === void 0) forwardPending(inbox, activity, sendPrompt);
|
|
2816
|
-
while (events.length > 0) {
|
|
2817
|
-
const ev = events.shift();
|
|
2818
|
-
if (ev.type === "response" && ev.command === "prompt" && typeof ev.id === "string" && awaitingPromptResponses.delete(ev.id)) if (ev.success === true) acceptedPromptSinceSettlement = true;
|
|
2819
|
-
else failure = new ValidationError(`piExecutor: Pi rejected prompt: ${ev.error ?? "unknown RPC error"}`);
|
|
2820
|
-
if (ev.type === "agent_start") settled = false;
|
|
2821
|
-
if (ev.type === "agent_settled") {
|
|
2822
|
-
settled = true;
|
|
2823
|
-
acceptedPromptSinceSettlement = false;
|
|
2824
|
-
}
|
|
2825
|
-
const projected = projectPiEvent(ev, args, tokens, pendingTools);
|
|
2826
|
-
if (projected.assistant) lastAssistant = projected.assistant;
|
|
2827
|
-
if (projected.tokensUnknown) tokensKnown = false;
|
|
2828
|
-
for (const usage of projected.events) {
|
|
2829
|
-
if (usage.kind === "cost") {
|
|
2830
|
-
usd += usage.usd;
|
|
2831
|
-
if (usage.usdKnown === false) usdKnown = false;
|
|
2832
|
-
}
|
|
2833
|
-
yield usage;
|
|
2834
|
-
}
|
|
2835
|
-
}
|
|
2836
|
-
if (failure) throw failure;
|
|
2837
|
-
if (abortDeadline !== void 0 && (settled || exited)) throw abortError();
|
|
2838
|
-
if (abortDeadline === void 0 && exited && (!settled || awaitingPromptResponses.size > 0 || acceptedPromptSinceSettlement)) throw new ValidationError(`piExecutor: pi exited before agent_settled (code ${exitCode ?? "unknown"})`);
|
|
2839
|
-
if (abortDeadline === void 0 && deadline !== void 0 && Date.now() > deadline) throw new ValidationError("piExecutor: turn exceeded turnTimeoutMs");
|
|
2840
|
-
if (abortDeadline !== void 0 && Date.now() > abortDeadline) {
|
|
2841
|
-
const error = abortError();
|
|
2842
|
-
error.message = `piExecutor: abort did not settle within ${PI_ABORT_RECEIPT_MS}ms`;
|
|
2843
|
-
throw error;
|
|
2844
|
-
}
|
|
2845
|
-
if (settled && awaitingPromptResponses.size === 0 && !acceptedPromptSinceSettlement) {
|
|
2846
|
-
const pending = inbox.drain();
|
|
2847
|
-
if (pending.length === 0) break;
|
|
2848
|
-
settled = false;
|
|
2849
|
-
lastAssistant = void 0;
|
|
2850
|
-
sendPrompt(inbox.fold(pending));
|
|
2851
|
-
state.note = `turn ${state.turns}`;
|
|
2852
|
-
continue;
|
|
2853
|
-
}
|
|
2854
|
-
await new Promise((resolve) => {
|
|
2855
|
-
wake = resolve;
|
|
2856
|
-
const t = setTimeout(() => {
|
|
2857
|
-
if (wake === resolve) {
|
|
2858
|
-
wake = void 0;
|
|
2859
|
-
resolve();
|
|
2860
|
-
}
|
|
2861
|
-
}, 50);
|
|
2862
|
-
if (typeof t.unref === "function") t.unref();
|
|
2863
|
-
});
|
|
2864
|
-
}
|
|
2865
|
-
} finally {
|
|
2866
|
-
stdoutLines();
|
|
2867
|
-
state.note = "settled";
|
|
2868
|
-
args.signal.removeEventListener("abort", abortAll);
|
|
2869
|
-
args.controller.signal.removeEventListener("abort", abortAll);
|
|
2870
|
-
await killPi(proc, 2e3).catch(() => ({ destroyed: false }));
|
|
2871
|
-
piMcp.mount?.cleanup();
|
|
2872
|
-
}
|
|
2873
|
-
if (args.signal.aborted || args.controller.signal.aborted) throw abortError();
|
|
2874
|
-
if (!lastAssistant) throw new ValidationError("piExecutor: agent_settled without an assistant turn");
|
|
2875
|
-
if (lastAssistant.stopReason === "aborted") throw abortError();
|
|
2876
|
-
if (lastAssistant.stopReason === "error") throw new ValidationError(`piExecutor: Pi assistant failed: ${lastAssistant.errorMessage ?? "unknown provider error"}`);
|
|
2877
|
-
state.lastText = lastAssistant.text;
|
|
2878
|
-
const spent = {
|
|
2879
|
-
iterations: state.turns,
|
|
2880
|
-
tokens,
|
|
2881
|
-
...tokensKnown ? {} : { tokensKnown: false },
|
|
2882
|
-
usd,
|
|
2883
|
-
...usdKnown ? {} : { usdKnown: false },
|
|
2884
|
-
ms: Date.now() - started
|
|
2885
|
-
};
|
|
2886
|
-
const out = {
|
|
2887
|
-
content: state.lastText,
|
|
2888
|
-
turns: state.turns,
|
|
2889
|
-
...piMcp.receipt ? { mcp: piMcp.receipt } : {}
|
|
2890
|
-
};
|
|
2891
|
-
state.artifact = {
|
|
2892
|
-
outRef: `pi:${hash(state.lastText)}`,
|
|
2893
|
-
out,
|
|
2894
|
-
spent
|
|
2895
|
-
};
|
|
2896
|
-
}
|
|
2897
|
-
/** Route messages through pi's state-safe prompt command; pi owns the queue and idle transition. */
|
|
2898
|
-
function forwardPending(inbox, activity, sendPrompt) {
|
|
2899
|
-
const pending = inbox.drain();
|
|
2900
|
-
for (const m of pending) {
|
|
2901
|
-
sendPrompt(renderOne(m), m.interrupt ? "steer" : "followUp");
|
|
2902
|
-
activity.push({
|
|
2903
|
-
at: Date.now(),
|
|
2904
|
-
kind: "note",
|
|
2905
|
-
label: m.interrupt ? "steer" : "follow-up",
|
|
2906
|
-
detail: m.text.length > 80 ? `${m.text.slice(0, 77)}...` : m.text
|
|
2907
|
-
});
|
|
2908
|
-
}
|
|
2909
|
-
}
|
|
2910
|
-
function renderOne(m) {
|
|
2911
|
-
if (m.kind === "answer") return `Answer to your question${m.questionId ? ` (${m.questionId})` : ""}: ${m.text}`;
|
|
2912
|
-
return `New instruction from your supervisor: ${m.text}`;
|
|
2913
|
-
}
|
|
2914
|
-
/** Project one pi event onto usage events, updating turn count / activity / trace as a side
|
|
2915
|
-
* effect. pi's tool events are the live trace; its `turn_end` bumps the turn counter. */
|
|
2916
|
-
function projectPiEvent(ev, args, tokens, pendingTools) {
|
|
2917
|
-
const out = [];
|
|
2918
|
-
const at = Date.now();
|
|
2919
|
-
if (ev.type === "tool_execution_start" && typeof ev.toolName === "string") {
|
|
2920
|
-
args.activity.push({
|
|
2921
|
-
at,
|
|
2922
|
-
kind: "tool",
|
|
2923
|
-
label: ev.toolName,
|
|
2924
|
-
detail: describeArgs$1(ev.args)
|
|
2925
|
-
});
|
|
2926
|
-
if (typeof ev.toolCallId === "string") pendingTools.set(ev.toolCallId, {
|
|
2927
|
-
args: ev.args ?? {},
|
|
2928
|
-
startedAt: at
|
|
2929
|
-
});
|
|
2930
|
-
return { events: out };
|
|
2931
|
-
}
|
|
2932
|
-
if (ev.type === "tool_execution_end" && typeof ev.toolName === "string") {
|
|
2933
|
-
const status = ev.isError === true ? "error" : "ok";
|
|
2934
|
-
const callId = typeof ev.toolCallId === "string" ? ev.toolCallId : void 0;
|
|
2935
|
-
const started = callId ? pendingTools.get(callId) : void 0;
|
|
2936
|
-
if (callId) pendingTools.delete(callId);
|
|
2937
|
-
const error = status === "error" ? describeToolError(ev.result) : void 0;
|
|
2938
|
-
args.activity.push({
|
|
2939
|
-
at,
|
|
2940
|
-
kind: "tool",
|
|
2941
|
-
label: ev.toolName,
|
|
2942
|
-
status
|
|
2943
|
-
});
|
|
2944
|
-
args.record({
|
|
2945
|
-
toolName: ev.toolName,
|
|
2946
|
-
args: started?.args ?? {},
|
|
2947
|
-
...started ? {} : { argsCaptured: false },
|
|
2948
|
-
status,
|
|
2949
|
-
...ev.result !== void 0 ? { result: ev.result } : {},
|
|
2950
|
-
...error !== void 0 ? { error } : {},
|
|
2951
|
-
...callId ? { callId } : {},
|
|
2952
|
-
...started ? { startedAt: started.startedAt } : {},
|
|
2953
|
-
endedAt: at
|
|
2954
|
-
});
|
|
2955
|
-
return { events: out };
|
|
2956
|
-
}
|
|
2957
|
-
if (ev.type === "message_end") return { events: out };
|
|
2958
|
-
if (ev.type === "turn_end") {
|
|
2959
|
-
const usage = readUsage(ev.message);
|
|
2960
|
-
if (usage && (usage.input || usage.output)) {
|
|
2961
|
-
tokens.input += usage.input;
|
|
2962
|
-
tokens.output += usage.output;
|
|
2963
|
-
out.push({
|
|
2964
|
-
kind: "tokens",
|
|
2965
|
-
input: usage.input,
|
|
2966
|
-
output: usage.output
|
|
2967
|
-
});
|
|
2968
|
-
}
|
|
2969
|
-
out.push(usage?.usd !== void 0 ? {
|
|
2970
|
-
kind: "cost",
|
|
2971
|
-
usd: usage.usd
|
|
2972
|
-
} : {
|
|
2973
|
-
kind: "cost",
|
|
2974
|
-
usd: 0,
|
|
2975
|
-
usdKnown: false
|
|
2976
|
-
});
|
|
2977
|
-
args.state.turns += 1;
|
|
2978
|
-
args.activity.push({
|
|
2979
|
-
at,
|
|
2980
|
-
kind: "turn",
|
|
2981
|
-
label: `turn ${args.state.turns}`
|
|
2982
|
-
});
|
|
2983
|
-
out.push({ kind: "iteration" });
|
|
2984
|
-
const assistant = readAssistantOutcome(ev.message);
|
|
2985
|
-
const tokensUnknown = !usage || usage.tokensKnown === false;
|
|
2986
|
-
return {
|
|
2987
|
-
events: out,
|
|
2988
|
-
...assistant ? { assistant } : {},
|
|
2989
|
-
...tokensUnknown ? { tokensUnknown: true } : {}
|
|
2990
|
-
};
|
|
2991
|
-
}
|
|
2992
|
-
return { events: out };
|
|
2993
|
-
}
|
|
2994
|
-
/** Pi's fresh input excludes cache reads and writes. Runtime's input channel includes all model
|
|
2995
|
-
* input, so combine them once at the assistant receipt. A missing or zero price is unknown because
|
|
2996
|
-
* subscription-backed providers report zero even when compute was not free. */
|
|
2997
|
-
function readUsage(message) {
|
|
2998
|
-
if (!message || typeof message !== "object") return void 0;
|
|
2999
|
-
const usage = message.usage;
|
|
3000
|
-
if (!usage || typeof usage !== "object") return void 0;
|
|
3001
|
-
const u = usage;
|
|
3002
|
-
const promptTokens = num(u.prompt_tokens);
|
|
3003
|
-
const freshInput = num(u.input) ?? num(u.inputTokens);
|
|
3004
|
-
const outputRaw = num(u.output) ?? num(u.outputTokens) ?? num(u.completion_tokens);
|
|
3005
|
-
const input = promptTokens ?? (freshInput ?? 0) + (num(u.cacheRead) ?? num(u.cache_read_input_tokens) ?? num(u.cacheReadInputTokens) ?? 0) + (num(u.cacheWrite) ?? num(u.cache_creation_input_tokens) ?? num(u.cacheCreationInputTokens) ?? 0);
|
|
3006
|
-
const output = outputRaw ?? 0;
|
|
3007
|
-
const costRaw = u.cost;
|
|
3008
|
-
const reportedUsd = num(costRaw) ?? (costRaw && typeof costRaw === "object" ? num(costRaw.total) ?? num(costRaw.totalCost) : void 0);
|
|
3009
|
-
return {
|
|
3010
|
-
input,
|
|
3011
|
-
output,
|
|
3012
|
-
...reportedUsd !== void 0 && reportedUsd > 0 ? { usd: reportedUsd } : {},
|
|
3013
|
-
tokensKnown: promptTokens !== void 0 || freshInput !== void 0 || outputRaw !== void 0
|
|
3014
|
-
};
|
|
3015
|
-
}
|
|
3016
|
-
function readAssistantOutcome(message) {
|
|
3017
|
-
if (!message || typeof message !== "object") return void 0;
|
|
3018
|
-
const value = message;
|
|
3019
|
-
if (value.role !== "assistant") return void 0;
|
|
3020
|
-
return {
|
|
3021
|
-
text: readText(message) ?? "",
|
|
3022
|
-
...typeof value.stopReason === "string" ? { stopReason: value.stopReason } : {},
|
|
3023
|
-
...typeof value.errorMessage === "string" ? { errorMessage: value.errorMessage } : {}
|
|
3024
|
-
};
|
|
3025
|
-
}
|
|
3026
|
-
function readText(message) {
|
|
3027
|
-
if (!message || typeof message !== "object") return void 0;
|
|
3028
|
-
const content = message.content;
|
|
3029
|
-
if (typeof content === "string" && content.length > 0) return content;
|
|
3030
|
-
if (!Array.isArray(content)) return void 0;
|
|
3031
|
-
const text = content.map((block) => block && typeof block === "object" && typeof block.text === "string" ? block.text ?? "" : "").join("");
|
|
3032
|
-
return text.length > 0 ? text : void 0;
|
|
3033
|
-
}
|
|
3034
|
-
function num(v) {
|
|
3035
|
-
return typeof v === "number" && Number.isFinite(v) && v >= 0 ? v : void 0;
|
|
3036
|
-
}
|
|
3037
|
-
function describeArgs$1(argsValue) {
|
|
3038
|
-
if (!argsValue || typeof argsValue !== "object") return void 0;
|
|
3039
|
-
const a = argsValue;
|
|
3040
|
-
for (const key of [
|
|
3041
|
-
"filePath",
|
|
3042
|
-
"file_path",
|
|
3043
|
-
"path",
|
|
3044
|
-
"file",
|
|
3045
|
-
"command",
|
|
3046
|
-
"cmd",
|
|
3047
|
-
"pattern"
|
|
3048
|
-
]) {
|
|
3049
|
-
const v = a[key];
|
|
3050
|
-
if (typeof v === "string" && v.length > 0) return v.length > 120 ? `${v.slice(0, 117)}...` : v;
|
|
3051
|
-
}
|
|
3052
|
-
}
|
|
3053
|
-
function describeToolError(result) {
|
|
3054
|
-
if (typeof result === "string" && result.length > 0) return result;
|
|
3055
|
-
if (result && typeof result === "object") {
|
|
3056
|
-
const value = result;
|
|
3057
|
-
const direct = value.error ?? value.message;
|
|
3058
|
-
if (typeof direct === "string" && direct.length > 0) return direct;
|
|
3059
|
-
if (Array.isArray(value.content)) {
|
|
3060
|
-
const text = value.content.map((block) => block && typeof block === "object" && typeof block.text === "string" ? block.text : "").filter(Boolean).join("\n");
|
|
3061
|
-
if (text.length > 0) return text;
|
|
3062
|
-
}
|
|
3063
|
-
}
|
|
3064
|
-
}
|
|
3065
|
-
function taskText(task) {
|
|
3066
|
-
if (typeof task === "string") return task;
|
|
3067
|
-
try {
|
|
3068
|
-
return JSON.stringify(task) ?? String(task);
|
|
3069
|
-
} catch {
|
|
3070
|
-
return String(task);
|
|
3071
|
-
}
|
|
3072
|
-
}
|
|
3073
|
-
/**
|
|
3074
|
-
* Launch `pi --mode rpc`, plus provider/model flags derived from the seam's `provider/model` and
|
|
3075
|
-
* the profile-derived extension flags (`--no-extensions` / `--extension`). Seam args go LAST so an
|
|
3076
|
-
* operator's explicit flag wins over a derived one under pi's last-flag-wins parsing; RPC mode has
|
|
3077
|
-
* no positional prompt, so nothing here has to precede an argument.
|
|
3078
|
-
*
|
|
3079
|
-
* `traceEnv` is the inherited `TRACE_ID` / `PARENT_SPAN_ID` pair (empty when the run records no
|
|
3080
|
-
* spans). It sits ABOVE the supervisor's ambient `process.env` — a supervisor that was itself
|
|
3081
|
-
* launched as someone's worker holds ids describing ITS place in an outer trace, which are the
|
|
3082
|
-
* wrong parent for this child — and BELOW `seam.env`, so an operator who sets either id explicitly
|
|
3083
|
-
* still wins. See `worker-trace.ts` for the full precedence rule.
|
|
3084
|
-
*/
|
|
3085
|
-
function spawnPi(seam, profileArgs = [], traceEnv = {}) {
|
|
3086
|
-
const bin = seam.bin ?? "pi";
|
|
3087
|
-
const argv = ["--mode", "rpc"];
|
|
3088
|
-
if (seam.model) {
|
|
3089
|
-
const slash = seam.model.indexOf("/");
|
|
3090
|
-
if (slash > 0) argv.push("--provider", seam.model.slice(0, slash), "--model", seam.model.slice(slash + 1));
|
|
3091
|
-
else argv.push("--model", seam.model);
|
|
3092
|
-
}
|
|
3093
|
-
argv.push(...profileArgs);
|
|
3094
|
-
if (seam.args) argv.push(...seam.args);
|
|
3095
|
-
return spawn(bin, argv, {
|
|
3096
|
-
...seam.cwd ? { cwd: seam.cwd } : {},
|
|
3097
|
-
env: {
|
|
3098
|
-
...process.env,
|
|
3099
|
-
...traceEnv,
|
|
3100
|
-
...seam.env ?? {}
|
|
3101
|
-
},
|
|
3102
|
-
stdio: [
|
|
3103
|
-
"pipe",
|
|
3104
|
-
"pipe",
|
|
3105
|
-
"pipe"
|
|
3106
|
-
]
|
|
3107
|
-
});
|
|
3108
|
-
}
|
|
3109
|
-
/** Write one `RpcCommand` as a JSON line on pi's stdin. Best-effort: a dead process is not an
|
|
3110
|
-
* error here (the stream loop already observes the exit and fails loud there). */
|
|
3111
|
-
function sendCommand(proc, command) {
|
|
3112
|
-
try {
|
|
3113
|
-
proc.stdin?.write(`${JSON.stringify(command)}\n`);
|
|
3114
|
-
} catch {}
|
|
3115
|
-
}
|
|
3116
|
-
/** Read newline-delimited JSON off pi's stdout; returns an unsubscribe. Malformed lines are
|
|
3117
|
-
* ignored (pi writes only JSONL on stdout; anything else is noise from a wrapper script). */
|
|
3118
|
-
function readJsonLines(proc, onValue) {
|
|
3119
|
-
let buffer = "";
|
|
3120
|
-
const onData = (chunk) => {
|
|
3121
|
-
buffer += typeof chunk === "string" ? chunk : chunk.toString("utf8");
|
|
3122
|
-
for (;;) {
|
|
3123
|
-
const nl = buffer.indexOf("\n");
|
|
3124
|
-
if (nl < 0) break;
|
|
3125
|
-
const line = buffer.slice(0, nl).trim();
|
|
3126
|
-
buffer = buffer.slice(nl + 1);
|
|
3127
|
-
if (!line) continue;
|
|
3128
|
-
try {
|
|
3129
|
-
onValue(JSON.parse(line));
|
|
3130
|
-
} catch {}
|
|
3131
|
-
}
|
|
3132
|
-
};
|
|
3133
|
-
proc.stdout?.on("data", onData);
|
|
3134
|
-
return () => {
|
|
3135
|
-
proc.stdout?.off("data", onData);
|
|
3136
|
-
};
|
|
3137
|
-
}
|
|
3138
|
-
/** The grace window pi gets to act on the `abort` it was just sent, before a signal is used.
|
|
3139
|
-
* Without it the SIGTERM races the command down the pipe and pi never sees the abort at all —
|
|
3140
|
-
* which defeats the reason to wrap pi rather than kill it (a clean abort finalizes its session). */
|
|
3141
|
-
const PI_ABORT_GRACE_MS = 500;
|
|
3142
|
-
/** Maximum wait for Pi's aborted receipt and `agent_settled` before forced teardown. */
|
|
3143
|
-
const PI_ABORT_RECEIPT_MS = 2e3;
|
|
3144
|
-
async function killPi(proc, grace) {
|
|
3145
|
-
if (proc.exitCode !== null || proc.killed) return { destroyed: true };
|
|
3146
|
-
if (grace === "brutalKill") {
|
|
3147
|
-
endStdin(proc);
|
|
3148
|
-
proc.kill("SIGKILL");
|
|
3149
|
-
return { destroyed: true };
|
|
3150
|
-
}
|
|
3151
|
-
const total = grace === "infinity" ? 1e4 : Math.max(0, grace);
|
|
3152
|
-
if (await waitForExit(proc, Math.min(PI_ABORT_GRACE_MS, total))) return { destroyed: true };
|
|
3153
|
-
endStdin(proc);
|
|
3154
|
-
proc.kill("SIGTERM");
|
|
3155
|
-
if (await waitForExit(proc, Math.max(0, total - PI_ABORT_GRACE_MS))) return { destroyed: true };
|
|
3156
|
-
proc.kill("SIGKILL");
|
|
3157
|
-
return { destroyed: true };
|
|
3158
|
-
}
|
|
3159
|
-
function endStdin(proc) {
|
|
3160
|
-
try {
|
|
3161
|
-
proc.stdin?.end();
|
|
3162
|
-
} catch {}
|
|
3163
|
-
}
|
|
3164
|
-
function waitForExit(proc, ms) {
|
|
3165
|
-
if (proc.exitCode !== null) return Promise.resolve(true);
|
|
3166
|
-
return new Promise((resolve) => {
|
|
3167
|
-
const timer = setTimeout(() => resolve(false), ms);
|
|
3168
|
-
if (typeof timer.unref === "function") timer.unref();
|
|
3169
|
-
proc.once("exit", () => {
|
|
3170
|
-
clearTimeout(timer);
|
|
3171
|
-
resolve(true);
|
|
3172
|
-
});
|
|
3173
|
-
});
|
|
3174
|
-
}
|
|
3175
|
-
function readPiSeam(ctx) {
|
|
3176
|
-
const seam = ctx.seams["pi"];
|
|
3177
|
-
if (seam === void 0) return {};
|
|
3178
|
-
if (!seam || typeof seam !== "object") throw new ValidationError(`piExecutor: seams['pi'] must be a PiSeam object`);
|
|
3179
|
-
return seam;
|
|
3180
|
-
}
|
|
3181
|
-
/** FNV-1a over the terminal text — the same non-cryptographic content address the other leaf
|
|
3182
|
-
* executors mint for their `outRef` dedup hint (the journal re-derives the canonical one). */
|
|
3183
|
-
function hash(value) {
|
|
3184
|
-
let h = 2166136261;
|
|
3185
|
-
for (let i = 0; i < value.length; i += 1) {
|
|
3186
|
-
h ^= value.charCodeAt(i);
|
|
3187
|
-
h = Math.imul(h, 16777619);
|
|
3188
|
-
}
|
|
3189
|
-
return (h >>> 0).toString(16).padStart(8, "0");
|
|
3190
|
-
}
|
|
3191
|
-
//#endregion
|
|
3192
2179
|
//#region src/runtime/supervise/sandbox-session.ts
|
|
3193
2180
|
/** Ceiling on continuation turns. Turn 0 is the task; every later turn is a folded steer, so
|
|
3194
2181
|
* this bounds how many times a supervisor may redirect ONE worker before it must respawn. */
|
|
@@ -3226,7 +2213,7 @@ function createSteerableSandboxSession(args) {
|
|
|
3226
2213
|
kind: "tool",
|
|
3227
2214
|
label: step.toolName,
|
|
3228
2215
|
...step.status ? { status: step.status } : {},
|
|
3229
|
-
...
|
|
2216
|
+
...describeToolArgs(step.args) ? { detail: describeToolArgs(step.args) } : {}
|
|
3230
2217
|
};
|
|
3231
2218
|
activity.push(note);
|
|
3232
2219
|
}
|
|
@@ -3392,25 +2379,6 @@ function readFinalText(event) {
|
|
|
3392
2379
|
const alt = data.text;
|
|
3393
2380
|
return typeof alt === "string" && alt.length > 0 ? alt : void 0;
|
|
3394
2381
|
}
|
|
3395
|
-
/** A short, already-truncated descriptor of a tool call's target — the file/command a driver
|
|
3396
|
-
* needs to tell "editing the right module" from "re-reading the same file for the fifth time",
|
|
3397
|
-
* without pulling the worker's transcript across the wire. */
|
|
3398
|
-
function describeArgs(argsValue) {
|
|
3399
|
-
if (!argsValue || typeof argsValue !== "object") return void 0;
|
|
3400
|
-
const a = argsValue;
|
|
3401
|
-
for (const key of [
|
|
3402
|
-
"filePath",
|
|
3403
|
-
"file_path",
|
|
3404
|
-
"path",
|
|
3405
|
-
"file",
|
|
3406
|
-
"command",
|
|
3407
|
-
"cmd",
|
|
3408
|
-
"pattern"
|
|
3409
|
-
]) {
|
|
3410
|
-
const v = a[key];
|
|
3411
|
-
if (typeof v === "string" && v.length > 0) return v.length > 120 ? `${v.slice(0, 117)}...` : v;
|
|
3412
|
-
}
|
|
3413
|
-
}
|
|
3414
2382
|
/** Prompt options the composed loop context already carries (agent profile / cwd), forwarded so
|
|
3415
2383
|
* a steerable worker runs with the same box configuration as the single-shot path. */
|
|
3416
2384
|
function readPromptOptions(loopCtx) {
|
|
@@ -4374,11 +3342,25 @@ function killWithGrace(proc, grace) {
|
|
|
4374
3342
|
*/
|
|
4375
3343
|
/** Resolve the bridge wire model for this spawn. Per-create matrix settings win, then the
|
|
4376
3344
|
* canonical profile's harness/model preferences, then the bridge's configured fallback. */
|
|
3345
|
+
/**
|
|
3346
|
+
* A profile's selected model with its provider attached, the way a harness addresses one.
|
|
3347
|
+
*
|
|
3348
|
+
* Returns undefined when the profile selects no model, and the id unchanged when it is already
|
|
3349
|
+
* qualified or when the profile names no provider — there, the harness's own provider resolution
|
|
3350
|
+
* IS the caller's declared intent rather than a gap to fill.
|
|
3351
|
+
*/
|
|
3352
|
+
function qualifyProviderModel(model) {
|
|
3353
|
+
const id = model?.default;
|
|
3354
|
+
if (!id) return void 0;
|
|
3355
|
+
const provider = model?.provider;
|
|
3356
|
+
if (!provider || id.includes("/")) return id;
|
|
3357
|
+
return `${provider}/${id}`;
|
|
3358
|
+
}
|
|
4377
3359
|
function bridgeCellModel(seamModel, ctx, profile) {
|
|
4378
3360
|
const backend = ctx.seams.createOptions?.backend;
|
|
4379
|
-
const profileHarness = profile.harness
|
|
3361
|
+
const profileHarness = agentHarness(profile.harness);
|
|
4380
3362
|
const harness = backend?.type ?? profileHarness;
|
|
4381
|
-
const model = backend?.model?.model ?? profile.model
|
|
3363
|
+
const model = backend?.model?.model ?? qualifyProviderModel(profile.model);
|
|
4382
3364
|
if (!harness && !model) return seamModel;
|
|
4383
3365
|
if (!harness) return model;
|
|
4384
3366
|
if (model) return model.startsWith(`${harness}/`) ? model : `${harness}/${model}`;
|
|
@@ -4404,9 +3386,44 @@ const bridgeExecutor = (spec, ctx) => {
|
|
|
4404
3386
|
const inbox = createInbox();
|
|
4405
3387
|
let artifact;
|
|
4406
3388
|
const activeRuns = /* @__PURE__ */ new Map();
|
|
3389
|
+
const observation = {
|
|
3390
|
+
turns: 0,
|
|
3391
|
+
note: "starting",
|
|
3392
|
+
activity: createActivityLog(seam.activityWindow ?? 12),
|
|
3393
|
+
derived: [],
|
|
3394
|
+
refreshedAt: 0,
|
|
3395
|
+
refreshing: false,
|
|
3396
|
+
closed: false
|
|
3397
|
+
};
|
|
3398
|
+
if (base.agentProfile !== void 0) observation.derived.push("profile: merged the bridge seam's agentProfile over the spawn profile before materialization");
|
|
3399
|
+
if (seam.model !== base.model) observation.derived.push(`model: resolved the bridge wire model to ${seam.model} (seam default ${base.model ?? "none"})`);
|
|
3400
|
+
const trace = createPushTraceSource({ runId: sessionId });
|
|
4407
3401
|
return attestRuntimeOwnedExecutor({
|
|
4408
3402
|
runtime: "cli",
|
|
4409
3403
|
deliver: (m) => inbox.deliver(m),
|
|
3404
|
+
/**
|
|
3405
|
+
* The LIVE read of this worker, answered entirely from local mirrors so it is synchronous
|
|
3406
|
+
* and cannot block or fail the run. The bridge's own run state is refreshed OUT OF BAND —
|
|
3407
|
+
* a read schedules a fetch whose answer lands for the NEXT read — because the executor
|
|
3408
|
+
* cannot know from its own stream whether a silent run is thinking, detached mid-reconnect,
|
|
3409
|
+
* or already cancelled server-side, and that is exactly the distinction a supervisor's
|
|
3410
|
+
* respawn decision turns on.
|
|
3411
|
+
*/
|
|
3412
|
+
progress: () => {
|
|
3413
|
+
try {
|
|
3414
|
+
scheduleBridgeRunStateRefresh(seam, activeRuns, observation);
|
|
3415
|
+
return {
|
|
3416
|
+
turns: observation.turns,
|
|
3417
|
+
pendingMessages: inbox.pending(),
|
|
3418
|
+
recentActivity: observation.activity.read(),
|
|
3419
|
+
...observation.derived.length > 0 ? { derived: [...observation.derived] } : {},
|
|
3420
|
+
note: bridgeProgressNote(observation, liveBridgeRunId(activeRuns))
|
|
3421
|
+
};
|
|
3422
|
+
} catch {
|
|
3423
|
+
return;
|
|
3424
|
+
}
|
|
3425
|
+
},
|
|
3426
|
+
traceSource: () => trace.source,
|
|
4410
3427
|
execute(task, signal) {
|
|
4411
3428
|
return streamBridgeSession({
|
|
4412
3429
|
task,
|
|
@@ -4418,6 +3435,10 @@ const bridgeExecutor = (spec, ctx) => {
|
|
|
4418
3435
|
inbox,
|
|
4419
3436
|
controller,
|
|
4420
3437
|
activeRuns,
|
|
3438
|
+
observation,
|
|
3439
|
+
record: (step) => {
|
|
3440
|
+
trace.record(step);
|
|
3441
|
+
},
|
|
4421
3442
|
onArtifact: (a) => {
|
|
4422
3443
|
artifact = a;
|
|
4423
3444
|
}
|
|
@@ -4471,6 +3492,103 @@ const bridgeExecutor = (spec, ctx) => {
|
|
|
4471
3492
|
}
|
|
4472
3493
|
});
|
|
4473
3494
|
};
|
|
3495
|
+
/** Minimum spacing between out-of-band run-state reads. A driver polling `observe_agent` in a
|
|
3496
|
+
* tight loop must not turn an observability read into load on the bridge. */
|
|
3497
|
+
const BRIDGE_RUN_STATE_REFRESH_MS = 1e3;
|
|
3498
|
+
/** Ceiling on one run-state read. A bridge that hangs must not pin `refreshing` forever and
|
|
3499
|
+
* silently stop every later refresh. */
|
|
3500
|
+
const BRIDGE_RUN_STATE_TIMEOUT_MS = 2e3;
|
|
3501
|
+
/** One human-readable line: the local turn phase, plus the bridge's own run state when a snapshot
|
|
3502
|
+
* has been read for a run that is still live. The snapshot's AGE is stated because a driver
|
|
3503
|
+
* reading "running" must be able to tell a fresh read from a 40-second-old one; and once this
|
|
3504
|
+
* executor holds terminal proof the line is DROPPED rather than left repeating a last-known
|
|
3505
|
+
* "running" that stopped being true the moment the run ended. */
|
|
3506
|
+
function bridgeProgressNote(observation, liveRunId) {
|
|
3507
|
+
const run = observation.runState;
|
|
3508
|
+
if (!run || run.runId !== liveRunId) return observation.note;
|
|
3509
|
+
const age = Math.max(0, Date.now() - run.at);
|
|
3510
|
+
return `${observation.note} · bridge run ${run.state} (status ${run.status}, seq ${run.lastSeq}, read ${age}ms ago)`;
|
|
3511
|
+
}
|
|
3512
|
+
/** The run this executor still believes live, if any — the one worth reporting on and the only one
|
|
3513
|
+
* worth asking the bridge about. */
|
|
3514
|
+
function liveBridgeRunId(activeRuns) {
|
|
3515
|
+
for (const run of activeRuns.values()) if (!run.terminal) return run.id;
|
|
3516
|
+
}
|
|
3517
|
+
/**
|
|
3518
|
+
* Start a NON-BLOCKING read of the bridge's run state; the answer lands on `observation` for a
|
|
3519
|
+
* later `progress()` call. Nothing here is awaited by the caller and no failure escapes: a bridge
|
|
3520
|
+
* that is down, slow, or has forgotten the run leaves the previous snapshot in place, and
|
|
3521
|
+
* `progress()` still returns the local mirror.
|
|
3522
|
+
*/
|
|
3523
|
+
function scheduleBridgeRunStateRefresh(seam, activeRuns, observation) {
|
|
3524
|
+
if (observation.closed) return;
|
|
3525
|
+
if (observation.refreshing) return;
|
|
3526
|
+
const now = Date.now();
|
|
3527
|
+
if (now - observation.refreshedAt < BRIDGE_RUN_STATE_REFRESH_MS) return;
|
|
3528
|
+
const runId = liveBridgeRunId(activeRuns);
|
|
3529
|
+
if (runId === void 0) return;
|
|
3530
|
+
observation.refreshedAt = now;
|
|
3531
|
+
observation.refreshing = true;
|
|
3532
|
+
(async () => {
|
|
3533
|
+
try {
|
|
3534
|
+
const snapshot = await bridgeRunStateGet(seam, runId);
|
|
3535
|
+
if (snapshot) {
|
|
3536
|
+
observation.runState = snapshot;
|
|
3537
|
+
if (snapshot.terminal) observation.closed = true;
|
|
3538
|
+
}
|
|
3539
|
+
} catch {} finally {
|
|
3540
|
+
observation.refreshing = false;
|
|
3541
|
+
}
|
|
3542
|
+
})();
|
|
3543
|
+
}
|
|
3544
|
+
/** `GET /v1/runs/:id` — the bridge's durable-run registry read. Resolves `undefined` for any
|
|
3545
|
+
* non-200 or non-conforming body rather than throwing: this is only ever an observability read. */
|
|
3546
|
+
function bridgeRunStateGet(seam, runId) {
|
|
3547
|
+
const target = new URL(`${seam.bridgeUrl.replace(/\/$/, "")}/v1/runs/${encodeURIComponent(runId)}`);
|
|
3548
|
+
target.searchParams.set("wait_ms", "0");
|
|
3549
|
+
const requestFn = target.protocol === "https:" ? request$1 : request;
|
|
3550
|
+
return new Promise((resolve, reject) => {
|
|
3551
|
+
const req = requestFn(target, {
|
|
3552
|
+
method: "GET",
|
|
3553
|
+
headers: { authorization: `Bearer ${seam.bridgeBearer}` },
|
|
3554
|
+
timeout: BRIDGE_RUN_STATE_TIMEOUT_MS
|
|
3555
|
+
}, (res) => {
|
|
3556
|
+
(async () => {
|
|
3557
|
+
const chunks = [];
|
|
3558
|
+
for await (const chunk of res) chunks.push(Buffer.from(chunk));
|
|
3559
|
+
if (res.statusCode !== 200) {
|
|
3560
|
+
resolve(void 0);
|
|
3561
|
+
return;
|
|
3562
|
+
}
|
|
3563
|
+
resolve(readBridgeRunState(runId, Buffer.concat(chunks).toString("utf8")));
|
|
3564
|
+
})().catch(reject);
|
|
3565
|
+
});
|
|
3566
|
+
req.on("timeout", () => req.destroy(/* @__PURE__ */ new Error("bridgeExecutor: run state read timed out")));
|
|
3567
|
+
req.on("error", reject);
|
|
3568
|
+
req.end();
|
|
3569
|
+
});
|
|
3570
|
+
}
|
|
3571
|
+
/** Decode one run snapshot. A body that does not carry the fields we report is `undefined`, never
|
|
3572
|
+
* a partially-invented snapshot. */
|
|
3573
|
+
function readBridgeRunState(runId, body) {
|
|
3574
|
+
let parsed;
|
|
3575
|
+
try {
|
|
3576
|
+
parsed = JSON.parse(body);
|
|
3577
|
+
} catch {
|
|
3578
|
+
return;
|
|
3579
|
+
}
|
|
3580
|
+
if (parsed.id !== runId) return void 0;
|
|
3581
|
+
if (typeof parsed.status !== "string" || typeof parsed.state !== "string") return void 0;
|
|
3582
|
+
if (typeof parsed.terminal !== "boolean" || typeof parsed.lastSeq !== "number") return void 0;
|
|
3583
|
+
return {
|
|
3584
|
+
runId,
|
|
3585
|
+
status: parsed.status,
|
|
3586
|
+
state: parsed.state,
|
|
3587
|
+
terminal: parsed.terminal,
|
|
3588
|
+
lastSeq: parsed.lastSeq,
|
|
3589
|
+
at: Date.now()
|
|
3590
|
+
};
|
|
3591
|
+
}
|
|
4474
3592
|
/**
|
|
4475
3593
|
* One resumable cli-bridge session, run as a streamed turn loop. Turn 0 sends the
|
|
4476
3594
|
* task; each subsequent turn fires ONLY when the inbox has a steer/answer to fold —
|
|
@@ -4479,7 +3597,7 @@ const bridgeExecutor = (spec, ctx) => {
|
|
|
4479
3597
|
* full streamed harness session call rather than a single chat round.
|
|
4480
3598
|
*/
|
|
4481
3599
|
async function* streamBridgeSession(args) {
|
|
4482
|
-
const { seam, inbox } = args;
|
|
3600
|
+
const { seam, inbox, observation } = args;
|
|
4483
3601
|
const started = Date.now();
|
|
4484
3602
|
const external = mergeAbortSignals(args.signal, args.controller.signal);
|
|
4485
3603
|
const tokens = zeroTokenUsage();
|
|
@@ -4495,8 +3613,15 @@ async function* streamBridgeSession(args) {
|
|
|
4495
3613
|
if (pending.length) {
|
|
4496
3614
|
const folded = inbox.fold(pending);
|
|
4497
3615
|
nextPrompt = t === 0 && nextPrompt ? `${nextPrompt}\n\n${folded}` : folded;
|
|
3616
|
+
for (const m of pending) observation.activity.push({
|
|
3617
|
+
at: Date.now(),
|
|
3618
|
+
kind: "note",
|
|
3619
|
+
label: m.interrupt ? "steer" : "follow-up",
|
|
3620
|
+
detail: m.text.length > 80 ? `${m.text.slice(0, 77)}...` : m.text
|
|
3621
|
+
});
|
|
4498
3622
|
}
|
|
4499
3623
|
if (nextPrompt === void 0) break;
|
|
3624
|
+
observation.note = `turn ${t}`;
|
|
4500
3625
|
const messages = [];
|
|
4501
3626
|
messages.push({
|
|
4502
3627
|
role: "user",
|
|
@@ -4546,7 +3671,16 @@ async function* streamBridgeSession(args) {
|
|
|
4546
3671
|
run: activeRun
|
|
4547
3672
|
})) {
|
|
4548
3673
|
if (chunk.content) turnText += chunk.content;
|
|
4549
|
-
|
|
3674
|
+
for (const step of chunk.toolCalls ?? []) {
|
|
3675
|
+
toolCalls.push(step.toolName);
|
|
3676
|
+
observation.activity.push({
|
|
3677
|
+
at: Date.now(),
|
|
3678
|
+
kind: "tool",
|
|
3679
|
+
label: step.toolName,
|
|
3680
|
+
...describeToolArgs(step.args) ? { detail: describeToolArgs(step.args) } : {}
|
|
3681
|
+
});
|
|
3682
|
+
args.record(step);
|
|
3683
|
+
}
|
|
4550
3684
|
if (chunk.usage) {
|
|
4551
3685
|
turnTokensKnown = true;
|
|
4552
3686
|
tokens.input += chunk.usage.input;
|
|
@@ -4581,13 +3715,23 @@ async function* streamBridgeSession(args) {
|
|
|
4581
3715
|
}
|
|
4582
3716
|
if (interruptSig.aborted && !args.signal.aborted && !args.controller.signal.aborted) interrupted = true;
|
|
4583
3717
|
turns += 1;
|
|
3718
|
+
observation.turns = turns;
|
|
3719
|
+
observation.activity.push({
|
|
3720
|
+
at: Date.now(),
|
|
3721
|
+
kind: "turn",
|
|
3722
|
+
label: `turn ${turns}`
|
|
3723
|
+
});
|
|
4584
3724
|
if (!turnTokensKnown) tokensKnown = false;
|
|
4585
3725
|
if (!turnUsdKnown) usdKnown = false;
|
|
4586
3726
|
yield { kind: "iteration" };
|
|
4587
3727
|
if (!interrupted && turnText) lastText = turnText;
|
|
4588
|
-
if (interrupted)
|
|
3728
|
+
if (interrupted) {
|
|
3729
|
+
observation.note = `turn ${turns} interrupted, resuming`;
|
|
3730
|
+
continue;
|
|
3731
|
+
}
|
|
4589
3732
|
if (inbox.pending() === 0) break;
|
|
4590
3733
|
}
|
|
3734
|
+
observation.note = "settled";
|
|
4591
3735
|
const spent = {
|
|
4592
3736
|
iterations: turns,
|
|
4593
3737
|
tokens,
|
|
@@ -4833,6 +3977,50 @@ function errorMessage$1(error) {
|
|
|
4833
3977
|
return error instanceof Error ? error.message : String(error);
|
|
4834
3978
|
}
|
|
4835
3979
|
/**
|
|
3980
|
+
* Decode the OpenAI-shaped `tool_calls` of one delta into the shared `ToolStepInput` currency,
|
|
3981
|
+
* through the SAME `decodeOpenAiPart` adapter the sandbox/parts trace source uses — the wire shape
|
|
3982
|
+
* cli-bridge emits (`{index, id, type:'function', function:{name, arguments}}`) is exactly the one
|
|
3983
|
+
* that decoder owns, so there is no second mapping to drift.
|
|
3984
|
+
*
|
|
3985
|
+
* FIDELITY, stated once: this wire carries the model's DECISION to call a tool. cli-bridge's
|
|
3986
|
+
* backends surface the call the moment the harness announces it and NEVER report the call
|
|
3987
|
+
* finishing, so no outcome, result, or duration exists to read. Each step is therefore marked
|
|
3988
|
+
* `statusCaptured: false` and carries no `startedAt`/`endedAt` — its span is an instant with no
|
|
3989
|
+
* status. Synthesising an end time would inject a fabricated 0ms latency, and defaulting to 'ok'
|
|
3990
|
+
* would count an unobserved call as a success; both would silently corrupt any downstream latency
|
|
3991
|
+
* or error-rate analysis. An honest lower-fidelity span beats a fabricated one. A harness whose
|
|
3992
|
+
* native protocol reports tool completion could carry true durations; this wire does not.
|
|
3993
|
+
*
|
|
3994
|
+
* cli-bridge emits each call complete in ONE delta (`{id, name, arguments}` together), so no
|
|
3995
|
+
* cross-delta argument-fragment assembly is needed; a frame that carries argument bytes without a
|
|
3996
|
+
* name decodes to nothing rather than to a nameless call.
|
|
3997
|
+
*/
|
|
3998
|
+
function decodeBridgeToolCalls(raw) {
|
|
3999
|
+
if (!Array.isArray(raw)) return [];
|
|
4000
|
+
const steps = [];
|
|
4001
|
+
for (const call of raw) {
|
|
4002
|
+
if (!call || typeof call !== "object") continue;
|
|
4003
|
+
const record = call;
|
|
4004
|
+
const fn = record.function;
|
|
4005
|
+
const step = decodeOpenAiPart(typeof fn?.name === "string" && fn.name.length > 0 ? {
|
|
4006
|
+
...record,
|
|
4007
|
+
type: "function"
|
|
4008
|
+
} : record);
|
|
4009
|
+
if (!step) continue;
|
|
4010
|
+
const rawArgs = fn?.arguments ?? record.arguments;
|
|
4011
|
+
const argsCaptured = typeof rawArgs === "string" ? rawArgs.trim().length > 0 : rawArgs !== void 0;
|
|
4012
|
+
steps.push({
|
|
4013
|
+
...step,
|
|
4014
|
+
...argsCaptured ? {} : {
|
|
4015
|
+
args: {},
|
|
4016
|
+
argsCaptured: false
|
|
4017
|
+
},
|
|
4018
|
+
statusCaptured: false
|
|
4019
|
+
});
|
|
4020
|
+
}
|
|
4021
|
+
return steps;
|
|
4022
|
+
}
|
|
4023
|
+
/**
|
|
4836
4024
|
* Parse cli-bridge's OpenAI-compatible SSE stream into normalized chunks. Each
|
|
4837
4025
|
* `data:` line is an OpenAI chat-completion chunk (`choices[].delta`). Every
|
|
4838
4026
|
* run-owned frame, including an id-only comment, is returned so the caller can
|
|
@@ -4919,8 +4107,8 @@ function parseSseFrame(frame) {
|
|
|
4919
4107
|
const choice = parsed.choices?.[0];
|
|
4920
4108
|
const content = choice?.delta?.content ?? choice?.message?.content;
|
|
4921
4109
|
if (typeof content === "string" && content.length > 0) out.content = content;
|
|
4922
|
-
const
|
|
4923
|
-
if (
|
|
4110
|
+
const toolCalls = decodeBridgeToolCalls(choice?.delta?.tool_calls);
|
|
4111
|
+
if (toolCalls.length > 0) out.toolCalls = toolCalls;
|
|
4924
4112
|
const u = parsed.usage;
|
|
4925
4113
|
if (u && (typeof u.prompt_tokens === "number" || typeof u.completion_tokens === "number")) out.usage = {
|
|
4926
4114
|
input: u.prompt_tokens ?? 0,
|
|
@@ -5221,8 +4409,7 @@ function snapshotExecutorConfig(config) {
|
|
|
5221
4409
|
}
|
|
5222
4410
|
case "router":
|
|
5223
4411
|
case "bridge":
|
|
5224
|
-
case "cli":
|
|
5225
|
-
case "pi": return detachedSnapshot(config, `createExecutor ${config.backend} config`);
|
|
4412
|
+
case "cli": return detachedSnapshot(config, `createExecutor ${config.backend} config`);
|
|
5226
4413
|
}
|
|
5227
4414
|
}
|
|
5228
4415
|
/** A backend config reused for multiple workers/managers cannot pin execution identity or carry a
|
|
@@ -5254,7 +4441,6 @@ function bindReusableExecutorExecutionId(captured, executionId) {
|
|
|
5254
4441
|
case "router-tools":
|
|
5255
4442
|
case "cli":
|
|
5256
4443
|
case "provider":
|
|
5257
|
-
case "pi":
|
|
5258
4444
|
case "sandbox": return captured;
|
|
5259
4445
|
}
|
|
5260
4446
|
}
|
|
@@ -5284,7 +4470,6 @@ function createExecutor(config) {
|
|
|
5284
4470
|
case "bridge": return bridgeExecutor(spec, seamed);
|
|
5285
4471
|
case "cli": return cliExecutor(spec, seamed);
|
|
5286
4472
|
case "cli-worktree": return cliWorktreeExecutor(spec, seamed);
|
|
5287
|
-
case "pi": return piExecutor(spec, seamed);
|
|
5288
4473
|
case "provider": {
|
|
5289
4474
|
const providerSeam = readSeam(seamed, providerSeamKey, "provider");
|
|
5290
4475
|
const provider = resolveAgentEnvironmentProvider(providerSeam.provider, providerSeam.registry);
|
|
@@ -5354,7 +4539,6 @@ function createExecutorRegistry() {
|
|
|
5354
4539
|
factories.set("inline", routerInlineExecutor);
|
|
5355
4540
|
factories.set("sandbox", sandboxExecutor);
|
|
5356
4541
|
factories.set("cli", cliExecutor);
|
|
5357
|
-
factories.set("pi", piExecutor);
|
|
5358
4542
|
return {
|
|
5359
4543
|
register(runtime, factory) {
|
|
5360
4544
|
if (factories.has(runtime)) throw new ValidationError(`executor registry: runtime "${runtime}" already registered`);
|
|
@@ -5661,6 +4845,19 @@ function createEventBus(now = Date.now) {
|
|
|
5661
4845
|
*
|
|
5662
4846
|
* @experimental
|
|
5663
4847
|
*/
|
|
4848
|
+
const measuredFloors = Object.entries(WORKER_TOKEN_FLOOR).flatMap(([harness, floor]) => floor === null ? [] : [[harness, floor]]);
|
|
4849
|
+
const measuredFloorSentence = measuredFloors.length === 0 ? "" : " A harness child spends a measured minimum before any work — a maxTokens under that minimum is refused (`error: \"below-runtime-floor\"`). Measured floors (input tokens): " + measuredFloors.map(([harness, floor]) => `${harness}=${floor}`).join(", ") + ". Unmeasured harnesses have no floor and are admitted.";
|
|
4850
|
+
/**
|
|
4851
|
+
* The actionable fact behind a `below-runtime-floor` refusal: the floor for the harness the
|
|
4852
|
+
* root declared, and that the only fix is to raise maxTokens to at least that floor. Falls back
|
|
4853
|
+
* to the whole measured census if the declared harness resolves to no floor (a guard fired on a
|
|
4854
|
+
* spec-level harness this profile did not name).
|
|
4855
|
+
*/
|
|
4856
|
+
function belowFloorHint(harness) {
|
|
4857
|
+
const floor = workerTokenFloor(harness ?? null);
|
|
4858
|
+
if (floor !== null) return `The ${harness} harness spends a measured minimum of ${floor} input tokens before any work, so this budget's maxTokens can never be satisfied. Raise maxTokens to at least ${floor}; retrying with a smaller budget will fail identically.`;
|
|
4859
|
+
return "This budget's maxTokens is below the measured minimum a harness child spends before any work, so it can never be satisfied. Raise maxTokens to at least the floor for the child harness — measured floors (input tokens): " + measuredFloors.map(([h, f]) => `${h}=${f}`).join(", ") + " — retrying with a smaller budget will fail identically.";
|
|
4860
|
+
}
|
|
5664
4861
|
/** Default ceiling for a single `await_event` block (ms). Chosen well under any reasonable remote
|
|
5665
4862
|
* MCP client request timeout so the call returns a `pending` liveness snapshot instead of erroring;
|
|
5666
4863
|
* the supervisor re-polls until the worker settles. */
|
|
@@ -6366,7 +5563,7 @@ function createCoordinationTools(opts) {
|
|
|
6366
5563
|
},
|
|
6367
5564
|
budget: {
|
|
6368
5565
|
type: "object",
|
|
6369
|
-
description: "Optional per-spawn budget that merges over the per-worker default (per field). Only set the ceilings this sub-task needs raised; the conserved pool still fences.",
|
|
5566
|
+
description: "Optional per-spawn budget that merges over the per-worker default (per field). Only set the ceilings this sub-task needs raised; the conserved pool still fences." + measuredFloorSentence,
|
|
6370
5567
|
properties: {
|
|
6371
5568
|
maxIterations: {
|
|
6372
5569
|
type: "number",
|
|
@@ -6458,6 +5655,7 @@ function createCoordinationTools(opts) {
|
|
|
6458
5655
|
} : {
|
|
6459
5656
|
error: res.reason,
|
|
6460
5657
|
...res.reason === "usd-unbudgeted" ? { hint: "This run's root budget declares no maxUsd, so a child budget naming maxUsd can never be admitted — at any amount. Retrying with a smaller maxUsd will fail identically. Spawn with a budget that omits maxUsd, or ask the caller to give the run a root maxUsd." } : {},
|
|
5658
|
+
...res.reason === "below-runtime-floor" ? { hint: belowFloorHint(profile.harness) } : {},
|
|
6461
5659
|
live: liveWorkerCount(),
|
|
6462
5660
|
freeSlots: freeWorkerSlots()
|
|
6463
5661
|
});
|
|
@@ -9537,7 +8735,7 @@ function supervisorAgent(profile, deps) {
|
|
|
9537
8735
|
const nodeContextSeed = deps.nodeContext === void 0 ? void 0 : detachedSnapshot(deps.nodeContext, "supervisorAgent node context");
|
|
9538
8736
|
if ((resolveTools || observeNodeEvent) && !nodeContextSeed) throw new ValidationError("supervisorAgent: nodeContext is required with resolveSupervisorTools or observeNodeEvent");
|
|
9539
8737
|
const name = stableProfile.name ?? "supervisor";
|
|
9540
|
-
const harness = stableProfile.harness
|
|
8738
|
+
const harness = agentHarness(stableProfile.harness) ?? null;
|
|
9541
8739
|
const profilePrompt = resolveSupervisorSystemPrompt(stableProfile);
|
|
9542
8740
|
const coordination = deps.coordination ? { ...deps.coordination } : void 0;
|
|
9543
8741
|
assertCoordinationBinding(coordination);
|
|
@@ -9774,6 +8972,14 @@ function externalExecutionId(kind, identity) {
|
|
|
9774
8972
|
identity
|
|
9775
8973
|
}).slice(7)}`;
|
|
9776
8974
|
}
|
|
8975
|
+
/**
|
|
8976
|
+
* NOT a harness-name test — `ExecutorConfig.backend` is a discriminated-union TAG naming HOW a
|
|
8977
|
+
* profile is materialized (bridge / sandbox / cli-worktree / router / cli / provider), which is a
|
|
8978
|
+
* different axis from WHICH CLI runs. An exhaustive switch on a closed union tag is the correct
|
|
8979
|
+
* shape and must stay: it is what makes a new executor kind a compile error here rather than a
|
|
8980
|
+
* silently weaker materialization contract. Every other `backend.backend === …` in this file and
|
|
8981
|
+
* in `runtime.ts` is the same tag; none of them are harness names.
|
|
8982
|
+
*/
|
|
9777
8983
|
function backendProfileMaterialization(backend) {
|
|
9778
8984
|
switch (backend.backend) {
|
|
9779
8985
|
case "bridge":
|
|
@@ -9781,8 +8987,7 @@ function backendProfileMaterialization(backend) {
|
|
|
9781
8987
|
case "provider": return fullProfileMaterialization;
|
|
9782
8988
|
case "cli-worktree": return backend.bridge ? fullProfileMaterialization : worktreeCliProfileMaterialization;
|
|
9783
8989
|
case "router":
|
|
9784
|
-
case "router-tools":
|
|
9785
|
-
case "pi": return promptModelProfileMaterialization;
|
|
8990
|
+
case "router-tools": return promptModelProfileMaterialization;
|
|
9786
8991
|
case "cli": return controlProfileMaterialization;
|
|
9787
8992
|
}
|
|
9788
8993
|
}
|
|
@@ -9836,7 +9041,7 @@ const DEFAULT_AUTHORED_PROFILE_SECURITY_POLICY = Object.freeze({
|
|
|
9836
9041
|
allowConnections: false
|
|
9837
9042
|
});
|
|
9838
9043
|
function isExternalSupervisor(profile) {
|
|
9839
|
-
return profile.harness
|
|
9044
|
+
return harnessRunsAgent(profile.harness);
|
|
9840
9045
|
}
|
|
9841
9046
|
function automaticDriverBackendSupported(backend) {
|
|
9842
9047
|
return backend.backend === "bridge";
|
|
@@ -10515,6 +9720,6 @@ function supervise(profile, task, opts) {
|
|
|
10515
9720
|
return start();
|
|
10516
9721
|
}
|
|
10517
9722
|
//#endregion
|
|
10518
|
-
export { allOf as $,
|
|
9723
|
+
export { allOf as $, DELEGATE_DESCRIPTION as A, runAgentRounds as At, DELEGATION_TRACE_MAX_SPANS as B, assessAuthoredProfile as Bt, createDelegateUiAuditHandler as C, createSteerableSandboxSession as Ct, DELEGATE_FEEDBACK_TOOL_NAME as D, createInbox as Dt, DELEGATE_FEEDBACK_INPUT_SCHEMA as E, sandboxSessionTraceSource as Et, defaultDelegateBudget as F, FileCoordinationLog as Ft, DelegationPersistenceError as G, supervisorInstructions as Gt, capDelegationTrace as H, canonicalizeAuthoredProfile as Ht, delegate as I, createSupervisorSpanRecorder as It, InMemoryDelegationStore as J, DelegationStateCorruptError as K, gateOnDeliverable as Kt, DelegationTaskQueue as L, assertModelAllowed as Lt, DELEGATE_TOOL_NAME as M, createSandboxLineage as Mt, createDelegateHandler as N, probeSandboxCapabilities as Nt, createDelegateFeedbackHandler as O, createSandboxForSpec as Ot, validateDelegateArgs as P, acquireSandbox as Pt, finalizeBestDelivered as Q, hashIdempotencyInput as R, assertProfileModelsAllowed as Rt, DELEGATE_UI_AUDIT_TOOL_NAME as S, DEFAULT_SANDBOX_STEERING_MAX_TURNS as St, DELEGATE_FEEDBACK_DESCRIPTION as T, decodeToolPart as Tt, composeLoopTraceEmitters as U, defaultProfileRichnessThresholds as Ut, buildDelegationTraceSpans as V, authoredWorker as Vt, createDelegationTraceCollector as W, profileRichnessFinding as Wt, eventToSnapshot as X, InMemoryFeedbackStore as Y, driverAgent as Z, DELEGATION_HISTORY_TOOL_NAME as _, createInMemoryRunContext as _t, resolveSupervisorProfile as a, sampleFromSettled as at, DELEGATE_UI_AUDIT_DESCRIPTION as b, createExecutorRegistry as bt, createInProcessTransport as c, bestSoFar as ct, DELEGATION_STATUS_INPUT_SCHEMA as d, DEFAULT_AWAIT_EVENT_TIMEOUT_MS as dt, allWorkersStalled as et, DELEGATION_STATUS_TOOL_NAME as f, createCoordinationTools as ft, DELEGATION_HISTORY_INPUT_SCHEMA as g, createFileRunContext as gt, DELEGATION_HISTORY_DESCRIPTION as h, watchTrace as ht, assertCoordinationBinding as i, plateau as it, DELEGATE_INPUT_SCHEMA as j, runLoop as jt, validateDelegateFeedbackArgs as k, defaultSelectWinner as kt, createMcpServer as l, plateauLength as lt, validateDelegationStatusArgs as m, defaultToolDetectors as mt, supervise as n, createProgressTracker as nt, supervisorAgent as o, anytimeReport as ot, createDelegationStatusHandler as p, createEventBus as pt, FileDelegationStore as q, workerFromBackend as r, noProgressFor as rt, serveCoordinationMcp as s, areaUnderCurve as st, DEFAULT_AUTHORED_PROFILE_SECURITY_POLICY as t, anyOf as tt, DELEGATION_STATUS_DESCRIPTION as u, renderAnytimeTable as ut, createDelegationHistoryHandler as v, cliWorktreeExecutor as vt, validateDelegateUiAuditArgs as w, createPushTraceSource as wt, DELEGATE_UI_AUDIT_INPUT_SCHEMA as x, createWorktreeCliExecutor as xt, validateDelegationHistoryArgs as y, createExecutor as yt, DELEGATION_TRACE_MAX_BYTES as z, asAuthoredProfile as zt };
|
|
10519
9724
|
|
|
10520
|
-
//# sourceMappingURL=supervise-
|
|
9725
|
+
//# sourceMappingURL=supervise-Cq50UT5-.js.map
|