@jini-ai/daemon 0.3.0 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/agent-executor.d.ts +216 -24
- package/dist/agent-executor.d.ts.map +1 -1
- package/dist/agent-executor.js +373 -38
- package/dist/agent-executor.js.map +1 -1
- package/dist/run-lifecycle.d.ts +36 -1
- package/dist/run-lifecycle.d.ts.map +1 -1
- package/dist/run-lifecycle.js +89 -0
- package/dist/run-lifecycle.js.map +1 -1
- package/package.json +5 -5
- package/dist/artifacts/index.d.ts +0 -14
- package/dist/artifacts/index.d.ts.map +0 -1
- package/dist/artifacts/index.js +0 -14
- package/dist/artifacts/index.js.map +0 -1
- package/dist/artifacts/manifest.d.ts +0 -90
- package/dist/artifacts/manifest.d.ts.map +0 -1
- package/dist/artifacts/manifest.js +0 -229
- package/dist/artifacts/manifest.js.map +0 -1
- package/dist/artifacts/publication-guard.d.ts +0 -28
- package/dist/artifacts/publication-guard.d.ts.map +0 -1
- package/dist/artifacts/publication-guard.js +0 -77
- package/dist/artifacts/publication-guard.js.map +0 -1
- package/dist/artifacts/runtime-compat.d.ts +0 -37
- package/dist/artifacts/runtime-compat.d.ts.map +0 -1
- package/dist/artifacts/runtime-compat.js +0 -33
- package/dist/artifacts/runtime-compat.js.map +0 -1
- package/dist/artifacts/store.d.ts +0 -85
- package/dist/artifacts/store.d.ts.map +0 -1
- package/dist/artifacts/store.js +0 -92
- package/dist/artifacts/store.js.map +0 -1
- package/dist/artifacts/stub-guard.d.ts +0 -73
- package/dist/artifacts/stub-guard.d.ts.map +0 -1
- package/dist/artifacts/stub-guard.js +0 -198
- package/dist/artifacts/stub-guard.js.map +0 -1
- package/dist/artifacts/text-suppression.d.ts +0 -68
- package/dist/artifacts/text-suppression.d.ts.map +0 -1
- package/dist/artifacts/text-suppression.js +0 -184
- package/dist/artifacts/text-suppression.js.map +0 -1
package/dist/agent-executor.js
CHANGED
|
@@ -94,7 +94,7 @@ import { promises as fsPromises } from 'node:fs';
|
|
|
94
94
|
import { homedir, tmpdir } from 'node:os';
|
|
95
95
|
import { join } from 'node:path';
|
|
96
96
|
import { redactSecrets } from '@jini-ai/core';
|
|
97
|
-
import { applyAgentLaunchEnv, createClaudeStreamHandler, createCopilotStreamHandler, createJsonEventStreamHandler, createQoderStreamHandler, getAgentDef, resolveAgentLaunch, attachAcpSession, attachPiRpcSession, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, prepareAgentLogFile, preparePromptFileForAgent, } from '@jini-ai/agent-runtime';
|
|
97
|
+
import { agentCapabilities, applyAgentLaunchEnv, createClaudeStreamHandler, createCopilotStreamHandler, createJsonEventStreamHandler, createQoderStreamHandler, getAgentDef, resolveAgentLaunch, attachAcpSession, attachPiRpcSession, checkPromptArgvBudget, checkWindowsCmdShimCommandLineBudget, checkWindowsDirectExeCommandLineBudget, prepareAgentLogFile, preparePromptFileForAgent, } from '@jini-ai/agent-runtime';
|
|
98
98
|
import { collectProcessTreePids, createCommandInvocation, listProcessSnapshots, stopProcesses, } from '@jini-ai/platform';
|
|
99
99
|
import { classifyRunCloseStatus } from './close-status.js';
|
|
100
100
|
import { resolveContinuationTransport } from './continuation/continuation-transport.js';
|
|
@@ -743,6 +743,60 @@ export function mergeEnvContentMcpConfig(existingRaw, entry) {
|
|
|
743
743
|
};
|
|
744
744
|
return JSON.stringify({ ...doc, mcp });
|
|
745
745
|
}
|
|
746
|
+
/**
|
|
747
|
+
* Merges a staged system-prompt overlay file's path into the `instructions` array of the same
|
|
748
|
+
* OpenCode-schema config document {@link mergeEnvContentMcpConfig} writes `mcp` into — for a
|
|
749
|
+
* `systemPromptDelivery: { strategy: 'config-instructions-file' }` def (`opencode` today).
|
|
750
|
+
*
|
|
751
|
+
* Confirmed live (2026-09-01, opencode-cli 1.17.10), not inferred from docs alone:
|
|
752
|
+
* 1. `instructions` is honored — a run configured with it visibly followed the file's directive
|
|
753
|
+
* (a required exact-token prefix), while an identical run without it did not.
|
|
754
|
+
* 2. It appends, never replaces: the same run that followed the custom instruction ALSO still
|
|
755
|
+
* answered correctly using opencode's own baked-in environment-context system prompt (asked
|
|
756
|
+
* for its cwd, with nothing about cwd anywhere in the custom instructions file) — proof
|
|
757
|
+
* opencode's own defaults survive alongside a custom `instructions` entry, not just proof the
|
|
758
|
+
* file was read at all.
|
|
759
|
+
* 3. Adding this key alongside `mcp` in the same `OPENCODE_CONFIG_CONTENT` document disturbs
|
|
760
|
+
* neither: in one combined run, the MCP bridge still got its connection attempt (logged
|
|
761
|
+
* `key=jini type=local`) AND the custom instruction was still followed — same as running each
|
|
762
|
+
* key alone.
|
|
763
|
+
* 4. `instructions` is re-read fresh from the env on every spawn, including a `-s <id>`-resumed
|
|
764
|
+
* turn (proved by swapping in a second instructions file between two turns of one resumed
|
|
765
|
+
* session and seeing the second turn immediately reflect it while still recalling
|
|
766
|
+
* conversation memory from turn one) — so this mechanism is safe to redeliver every turn like
|
|
767
|
+
* `'append-flag'`/`'env-var'`, exempt from the prompt-prefix fallback's create-only gating
|
|
768
|
+
* (see {@link resolveSystemPromptOverlayDelivery}'s doc): nothing here is ever baked into
|
|
769
|
+
* opencode's own persisted session state the way re-injecting fallback prompt text would be.
|
|
770
|
+
*
|
|
771
|
+
* @param existingRaw - Whatever the spawn env already held for this variable (already possibly
|
|
772
|
+
* carrying `mcp`, if `mergeEnvContentMcpConfig` ran first on the same value — order between the two
|
|
773
|
+
* doesn't matter, each only touches its own top-level key), or `undefined`.
|
|
774
|
+
* @param instructionsFilePath - The staged overlay file's absolute path (see
|
|
775
|
+
* {@link prepareSystemPromptOverlayFileIfNeeded}).
|
|
776
|
+
* @returns The full JSON string to set as the env var's value. Appends to, never clobbers, any
|
|
777
|
+
* `instructions` entries already present — the same "merge, never clobber" discipline
|
|
778
|
+
* {@link mergeEnvContentMcpConfig} applies to `mcp`, in case a host is already using this same
|
|
779
|
+
* config-content variable to carry the operator's own instruction files.
|
|
780
|
+
* @complexity O(1) plus `JSON.parse`/`JSON.stringify` over a small config document.
|
|
781
|
+
* @overallScore 100/100
|
|
782
|
+
*/
|
|
783
|
+
export function mergeEnvContentInstructions(existingRaw, instructionsFilePath) {
|
|
784
|
+
let doc = {};
|
|
785
|
+
if (existingRaw !== undefined && existingRaw.length > 0) {
|
|
786
|
+
try {
|
|
787
|
+
const parsed = JSON.parse(existingRaw);
|
|
788
|
+
if (isRecord(parsed))
|
|
789
|
+
doc = parsed;
|
|
790
|
+
}
|
|
791
|
+
catch {
|
|
792
|
+
doc = {};
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
const existingInstructions = Array.isArray(doc.instructions)
|
|
796
|
+
? doc.instructions.filter((entry) => typeof entry === 'string')
|
|
797
|
+
: [];
|
|
798
|
+
return JSON.stringify({ ...doc, instructions: [...existingInstructions, instructionsFilePath] });
|
|
799
|
+
}
|
|
746
800
|
/**
|
|
747
801
|
* TOML basic-string escaping for the narrow value shapes {@link buildCodexMcpServerToml} emits (a
|
|
748
802
|
* command name, an argv token, an env var value — never multi-line or control-character-heavy
|
|
@@ -795,31 +849,61 @@ export function buildCodexMcpServerToml(entry) {
|
|
|
795
849
|
return serverTable;
|
|
796
850
|
return `${serverTable}\n[mcp_servers.${JINI_MCP_SERVER_KEY}.env]\n${envLines.join('\n')}\n`;
|
|
797
851
|
}
|
|
852
|
+
/**
|
|
853
|
+
* Removes any pre-existing `[mcp_servers.{@link JINI_MCP_SERVER_KEY}]` table — and its
|
|
854
|
+
* `[mcp_servers.{@link JINI_MCP_SERVER_KEY}.*]` subtables (e.g. `.env`) — from a real Codex
|
|
855
|
+
* `config.toml`'s raw text, so {@link buildCodexHomeConfigToml} can append this run's own table
|
|
856
|
+
* without producing the duplicate TOML key Codex's parser rejects at startup.
|
|
857
|
+
*
|
|
858
|
+
* Line-oriented, not a real TOML parser (matching {@link buildCodexMcpServerToml}'s own
|
|
859
|
+
* no-TOML-dependency constraint): a table header is any line that is, after trimming, exactly
|
|
860
|
+
* `[...]`. Once such a header's key matches the jini table or one of its subtables, every following
|
|
861
|
+
* line is dropped until the next table header (of any name) or EOF. Every other table, key, and
|
|
862
|
+
* blank line is passed through untouched.
|
|
863
|
+
* @param existingRaw - The real Codex home's `config.toml` content, already known to be defined
|
|
864
|
+
* (callers pass `''` for a missing file).
|
|
865
|
+
* @returns `existingRaw` with any jini table/subtable removed.
|
|
866
|
+
* @complexity O(n) in the number of lines.
|
|
867
|
+
*/
|
|
868
|
+
function stripExistingJiniMcpServerTable(existingRaw) {
|
|
869
|
+
const jiniTableKey = `mcp_servers.${JINI_MCP_SERVER_KEY}`;
|
|
870
|
+
const kept = [];
|
|
871
|
+
let skipping = false;
|
|
872
|
+
for (const line of existingRaw.split('\n')) {
|
|
873
|
+
const header = /^\s*\[([^[\]]+)\]\s*$/.exec(line);
|
|
874
|
+
if (header) {
|
|
875
|
+
const key = (header[1] ?? '').trim();
|
|
876
|
+
skipping = key === jiniTableKey || key.startsWith(`${jiniTableKey}.`);
|
|
877
|
+
if (skipping)
|
|
878
|
+
continue;
|
|
879
|
+
}
|
|
880
|
+
if (!skipping)
|
|
881
|
+
kept.push(line);
|
|
882
|
+
}
|
|
883
|
+
return kept.join('\n');
|
|
884
|
+
}
|
|
798
885
|
/**
|
|
799
886
|
* Builds the full `config.toml` a run's scratch `CODEX_HOME` gets: the real Codex home's own
|
|
800
|
-
* config,
|
|
887
|
+
* config, with any pre-existing `[mcp_servers.jini]` table removed (see
|
|
888
|
+
* {@link stripExistingJiniMcpServerTable}), then this run's own table appended.
|
|
801
889
|
*
|
|
802
|
-
* **Append-only by design, not a parse-and-merge.**
|
|
803
|
-
* above can safely parse-merge-reserialize because
|
|
804
|
-
* (`JSON.parse`); this driver has no TOML parser in
|
|
805
|
-
* `buildCodexMcpServerToml`'s doc), and every other setting a real Codex
|
|
806
|
-
* choice, sandbox policy, the trusted-project list, the operator's own
|
|
807
|
-
* survive a spawn byte-for-byte.
|
|
808
|
-
*
|
|
809
|
-
*
|
|
810
|
-
* is this integration's own reserved server name (see {@link JINI_MCP_SERVER_KEY}), never suggested
|
|
811
|
-
* to an operator for their own config — rather than solved with a full TOML parser for one
|
|
812
|
-
* collision case.
|
|
890
|
+
* **Append-only by design, not a parse-and-merge, for everything but the jini table itself.**
|
|
891
|
+
* `mergeMcpJsonContent`/`mergeEnvContentMcpConfig` above can safely parse-merge-reserialize because
|
|
892
|
+
* their formats have a JS-native parser (`JSON.parse`); this driver has no general TOML parser in
|
|
893
|
+
* its dependency graph (see `buildCodexMcpServerToml`'s doc), and every other setting a real Codex
|
|
894
|
+
* install carries — model choice, sandbox policy, the trusted-project list, the operator's own
|
|
895
|
+
* other MCP servers — must survive a spawn byte-for-byte. Only the one table this driver itself
|
|
896
|
+
* owns (`jini` is this integration's own reserved server name — see {@link JINI_MCP_SERVER_KEY}) is
|
|
897
|
+
* ever removed, via the narrow line-oriented scan above, never a full TOML parse.
|
|
813
898
|
* @param existingRaw - The real Codex home's `config.toml` content, or `undefined` when it does not
|
|
814
899
|
* exist (a fresh Codex install — degrades to "start from just this run's block", matching
|
|
815
900
|
* {@link mergeMcpJsonContent}'s own "missing file" handling).
|
|
816
901
|
* @param entry - The shared bridge entry.
|
|
817
902
|
* @returns The full text to write to the scratch `CODEX_HOME`'s `config.toml`.
|
|
818
903
|
* @complexity O(n) in the existing config's length.
|
|
819
|
-
* @overallScore 100/100
|
|
820
904
|
*/
|
|
821
905
|
export function buildCodexHomeConfigToml(existingRaw, entry) {
|
|
822
|
-
const base = existingRaw ?? '';
|
|
906
|
+
const base = stripExistingJiniMcpServerTable(existingRaw ?? '');
|
|
823
907
|
const separator = base.length === 0 ? '' : base.endsWith('\n') ? '\n' : '\n\n';
|
|
824
908
|
return `${base}${separator}${buildCodexMcpServerToml(entry)}`;
|
|
825
909
|
}
|
|
@@ -875,6 +959,8 @@ export function buildMcpBridgeDelivery(input) {
|
|
|
875
959
|
return { kind: 'env-content', envVarName: ENV_CONTENT_VAR_BY_STRATEGY[strategy], serverEntry };
|
|
876
960
|
case 'codex-toml':
|
|
877
961
|
return { kind: 'codex-toml', serverEntry };
|
|
962
|
+
case 'env-passthrough':
|
|
963
|
+
return { kind: 'env-passthrough', serverEntry };
|
|
878
964
|
}
|
|
879
965
|
}
|
|
880
966
|
function defaultReadMcpJsonFile(path) {
|
|
@@ -1810,28 +1896,47 @@ export async function resolveMcpBridgeForRun(input, deps) {
|
|
|
1810
1896
|
}
|
|
1811
1897
|
}
|
|
1812
1898
|
/**
|
|
1813
|
-
* Phase 6a/10c: the subprocess environment every env-riding
|
|
1899
|
+
* Phase 6a/10c: the subprocess environment every env-riding mechanism uses — mechanism 3+4
|
|
1814
1900
|
* (`'opencode-env-content'`/`'mimo-env-content'`, merged into whatever the host already set there,
|
|
1815
1901
|
* never a CLI argument: the config embeds `JINI_DAEMON_TOKEN`, and process arguments are readable
|
|
1816
|
-
* by any other local user through `ps`)
|
|
1817
|
-
*
|
|
1818
|
-
*
|
|
1902
|
+
* by any other local user through `ps`), mechanism 5 (`'codex-toml'`, `CODEX_HOME` relocation),
|
|
1903
|
+
* mechanism 6 (`'env-passthrough'`, the bridge entry's flat env vars set directly with no carrier
|
|
1904
|
+
* document — see {@link McpBridgeDelivery}'s own doc), a
|
|
1905
|
+
* `systemPromptDelivery: 'env-var'` def's overlay (`reasonix`'s `REASONIX_ACP_SYSTEM_APPEND` today
|
|
1906
|
+
* — see `resolveSystemPromptOverlayDelivery`'s own doc), and a `'config-instructions-file'` def's
|
|
1907
|
+
* staged overlay file (`opencode` today — see {@link mergeEnvContentInstructions}'s own doc). Pure
|
|
1908
|
+
* — `codexHomeDir` and `stagedInstructionsFile` arrive already staged by
|
|
1909
|
+
* {@link prepareCodexHomeIfNeeded} and {@link prepareSystemPromptOverlayFileIfNeeded} respectively,
|
|
1910
|
+
* the only parts of this mechanism that are NOT pure (real `mkdtemp`/`writeFile` calls).
|
|
1819
1911
|
* @param spawnEnv - The env every other spawn-time step (launch-path resolution, `applyAgentLaunchEnv`) already computed.
|
|
1820
1912
|
* @param mcpBridge - This run's resolved bridge delivery, or `null` for an unconfigured host / no-strategy def.
|
|
1821
1913
|
* @param codexHomeDir - The staged scratch `CODEX_HOME` path for a `'codex-toml'` def, or `undefined` for every other run (including a `'codex-toml'` def when `mcpJsonInjection` was never configured — see `prepareCodexHomeIfNeeded`'s own gate).
|
|
1822
|
-
* @
|
|
1914
|
+
* @param systemPromptEnvOverrides - `resolveSystemPromptOverlayDelivery`'s `envOverrides` — `{}` (default) for every def but an `'env-var'`-strategy one with an overlay present, in which case it carries that one var. Applied after `codexHomeDir`, so it can never be shadowed by it — the two never share a key (`CODEX_HOME` vs. e.g. `REASONIX_ACP_SYSTEM_APPEND`), so the ordering is a documentation choice, not a correctness one.
|
|
1915
|
+
* @param stagedInstructionsFile - `varName` (from the def's own `systemPromptDelivery` declaration) and the staged overlay file's `path`, or `undefined` for every def but a `'config-instructions-file'` one with an overlay present. Merged into `varName`'s value AFTER the `mcp` merge above (reading `envContentApplied`, not the original `spawnEnv`, for that same key) so both a `mcp` entry and an `instructions` entry from the two mechanisms survive together in one document — confirmed live this coexistence is safe (see {@link mergeEnvContentInstructions}'s doc).
|
|
1916
|
+
* @complexity O(1) plus `mergeEnvContentMcpConfig`'s and `mergeEnvContentInstructions`'s own `JSON.parse`/`JSON.stringify` cost.
|
|
1823
1917
|
* @overallScore 100/100
|
|
1824
1918
|
*/
|
|
1825
|
-
export function computeChildEnv(spawnEnv, mcpBridge, codexHomeDir) {
|
|
1919
|
+
export function computeChildEnv(spawnEnv, mcpBridge, codexHomeDir, systemPromptEnvOverrides, stagedInstructionsFile) {
|
|
1826
1920
|
const envContentApplied = mcpBridge?.kind === 'env-content'
|
|
1827
1921
|
? {
|
|
1828
1922
|
...spawnEnv,
|
|
1829
1923
|
[mcpBridge.envVarName]: mergeEnvContentMcpConfig(spawnEnv[mcpBridge.envVarName], mcpBridge.serverEntry),
|
|
1830
1924
|
}
|
|
1831
1925
|
: spawnEnv;
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1926
|
+
// `'env-passthrough'` (antigravity): no document, no named carrier variable — the bridge
|
|
1927
|
+
// entry's own `env` keys (`JINI_RUN_ID`/`JINI_DAEMON_URL`/`JINI_DAEMON_TOKEN`) are set directly
|
|
1928
|
+
// on the child's environment, for the spawned CLI to inherit down to its own globally
|
|
1929
|
+
// pre-registered MCP child in turn. See `McpBridgeDelivery`'s own doc for why this def has no
|
|
1930
|
+
// config document to merge into at all.
|
|
1931
|
+
const envPassthroughApplied = mcpBridge?.kind === 'env-passthrough' ? { ...envContentApplied, ...mcpBridge.serverEntry.env } : envContentApplied;
|
|
1932
|
+
const instructionsApplied = stagedInstructionsFile === undefined
|
|
1933
|
+
? envPassthroughApplied
|
|
1934
|
+
: {
|
|
1935
|
+
...envPassthroughApplied,
|
|
1936
|
+
[stagedInstructionsFile.varName]: mergeEnvContentInstructions(envPassthroughApplied[stagedInstructionsFile.varName], stagedInstructionsFile.path),
|
|
1937
|
+
};
|
|
1938
|
+
const codexHomeApplied = codexHomeDir === undefined ? instructionsApplied : { ...instructionsApplied, CODEX_HOME: codexHomeDir };
|
|
1939
|
+
return systemPromptEnvOverrides === undefined ? codexHomeApplied : { ...codexHomeApplied, ...systemPromptEnvOverrides };
|
|
1835
1940
|
}
|
|
1836
1941
|
/**
|
|
1837
1942
|
* Phase 6b: the `RuntimeContext` `buildArgs` receives — `undefined` unless a file, bridge path, or
|
|
@@ -1898,10 +2003,130 @@ export function buildAgentBuildArgsOptions(input, systemPromptOverlay) {
|
|
|
1898
2003
|
...(hasOverlay ? { systemPromptOverlay } : {}),
|
|
1899
2004
|
};
|
|
1900
2005
|
}
|
|
1901
|
-
/**
|
|
2006
|
+
/**
|
|
2007
|
+
* **The single dispatch point from a computed system-prompt overlay to its delivery mechanism** —
|
|
2008
|
+
* see `RuntimeAgentDef.systemPromptDelivery`'s own doc for the declared shape. Pure and
|
|
2009
|
+
* synchronous, mirroring {@link buildMcpBridgeDelivery}'s "keyed off the declared strategy, never
|
|
2010
|
+
* off the def's id" contract: a def earns overlay delivery by declaring a strategy, not by being
|
|
2011
|
+
* named in this file. That is what makes every def with no declaration work via the fallback
|
|
2012
|
+
* without any of their own files being touched.
|
|
2013
|
+
*
|
|
2014
|
+
* The fallback (no declared strategy — every def but `claude` today) prefixes the overlay directly
|
|
2015
|
+
* onto the composed prompt text, clearly delimited from the user's own request. It is gated on
|
|
2016
|
+
* session state, not merely on whether an overlay exists: a def that carries its own conversation
|
|
2017
|
+
* memory across spawns (`resumesSessionViaCli` / `resumesSessionViaAcpLoad`) persists whatever its
|
|
2018
|
+
* session-creating turn sends it — see `RuntimeContext.resumeSessionId`'s own doc: its presence on
|
|
2019
|
+
* a run means "continue a prior session", not "start one". Prefixing on every later turn of that
|
|
2020
|
+
* same session would therefore bake the overlay into the CLI's own stored history again and again,
|
|
2021
|
+
* compounding without bound turn over turn. So the fallback prefixes only when there is no resume
|
|
2022
|
+
* target yet (the session's own first turn, or a def with no session memory at all, which never
|
|
2023
|
+
* replays anything back at the CLI and so gets it on every turn).
|
|
2024
|
+
*
|
|
2025
|
+
* `'append-flag'` and `'env-var'` defs are the opposite case: the flag/env var is a fresh,
|
|
2026
|
+
* un-stored per-spawn directive — never part of what a resumed session replays — so it is set on
|
|
2027
|
+
* every turn unconditionally, exactly `claude`'s pre-existing (now-centralized) behavior before
|
|
2028
|
+
* this function existed.
|
|
2029
|
+
*
|
|
2030
|
+
* @param input.defId - Looks up this def's probed capabilities for an `'append-flag'` strategy's
|
|
2031
|
+
* `capabilityKey`. Otherwise unused — the dispatch itself is keyed off `systemPromptDelivery`, per
|
|
2032
|
+
* this function's own doc above, never off the id.
|
|
2033
|
+
* @param input.systemPromptDelivery - The def's declared strategy, or `undefined` for the fallback.
|
|
2034
|
+
* @param input.resumesSessionViaCli - The def's own flag (see `RuntimeAgentDef`'s doc).
|
|
2035
|
+
* @param input.resumesSessionViaAcpLoad - The def's own flag (see `RuntimeAgentDef`'s doc).
|
|
2036
|
+
* @param input.overlay - The computed `PromptAugmenter.systemOverlay()` result. `null`/`undefined`/
|
|
2037
|
+
* empty short-circuits to "no delivery" — byte-identical to no `PromptAugmenter` configured at all.
|
|
2038
|
+
* @param input.prompt - The composed prompt `buildArgs` would otherwise receive verbatim.
|
|
2039
|
+
* @param input.resumeSessionId - This run's `RuntimeContext.resumeSessionId`; presence means an
|
|
2040
|
+
* existing session is being continued, not created.
|
|
2041
|
+
* @returns A {@link SystemPromptOverlayDelivery}: the prefix this run's prompt text must carry
|
|
2042
|
+
* (`''` unless the fallback applies), that prefix already applied to `input.prompt`, any extra argv
|
|
2043
|
+
* to append to whatever `buildArgs` itself returns, and any env var overrides to merge into the
|
|
2044
|
+
* spawn env (`{}` for every strategy but `'env-var'`). Every one of `run()`'s four prompt
|
|
2045
|
+
* transports consumes this same result — see the interface's own no-double-delivery note.
|
|
2046
|
+
* @complexity O(n) in the overlay/prompt lengths — string concatenation only, no I/O.
|
|
2047
|
+
* @overallScore 100/100
|
|
2048
|
+
*/
|
|
2049
|
+
export function resolveSystemPromptOverlayDelivery(input) {
|
|
2050
|
+
const { defId, systemPromptDelivery, resumesSessionViaCli, resumesSessionViaAcpLoad, overlay, prompt, resumeSessionId } = input;
|
|
2051
|
+
if (typeof overlay !== 'string' || overlay.length === 0) {
|
|
2052
|
+
return { promptPrefix: '', prompt, extraArgs: [], envOverrides: {} };
|
|
2053
|
+
}
|
|
2054
|
+
if (systemPromptDelivery?.strategy === 'append-flag') {
|
|
2055
|
+
const capabilityKey = systemPromptDelivery.capabilityKey;
|
|
2056
|
+
// `!== false`, not a truthiness check: mirrors `claude.ts`'s own pre-existing
|
|
2057
|
+
// `agentCapabilities.get('claude') || {}` gate exactly (moved here, not changed) — an
|
|
2058
|
+
// undetected/never-probed capability defaults to allowed, and only an EXPLICIT `false` (the
|
|
2059
|
+
// `--help` probe ran and did not find the flag) withholds it. `capabilityKey === undefined`
|
|
2060
|
+
// (e.g. `pi`'s existing `--append-system-prompt`, trusted unconditionally) always passes, same
|
|
2061
|
+
// as an absent key.
|
|
2062
|
+
const capabilityOk = capabilityKey === undefined || agentCapabilities.get(defId)?.[capabilityKey] !== false;
|
|
2063
|
+
return { promptPrefix: '', prompt, extraArgs: capabilityOk ? [systemPromptDelivery.flag, overlay] : [], envOverrides: {} };
|
|
2064
|
+
}
|
|
2065
|
+
if (systemPromptDelivery?.strategy === 'env-var') {
|
|
2066
|
+
// No capability gate, unlike `'append-flag'`: an unrecognized env var is inert to a CLI (it
|
|
2067
|
+
// simply never reads it), never a fatal "unknown option" exit — there is no equivalent hazard
|
|
2068
|
+
// to probe-gate against here. Set verbatim, not merged with any existing value — a dedicated
|
|
2069
|
+
// single-purpose var, not a shared config channel (see this field's own `types.ts` doc).
|
|
2070
|
+
return { promptPrefix: '', prompt, extraArgs: [], envOverrides: { [systemPromptDelivery.varName]: overlay } };
|
|
2071
|
+
}
|
|
2072
|
+
if (systemPromptDelivery?.strategy === 'config-instructions-file') {
|
|
2073
|
+
// Delivered elsewhere, not here: unlike `'append-flag'`/`'env-var'`, this mechanism needs real
|
|
2074
|
+
// filesystem I/O (staging the overlay to a temp file — `opencode`'s `instructions` array only
|
|
2075
|
+
// accepts a file path or URL, confirmed live, never inline text), which this function's "pure
|
|
2076
|
+
// and synchronous" contract cannot perform. `prepareSystemPromptOverlayFileIfNeeded` (a separate
|
|
2077
|
+
// async phase in `run()`, gated on this same strategy check) stages the file, and
|
|
2078
|
+
// `computeChildEnv` merges its path into the config document via `mergeEnvContentInstructions`.
|
|
2079
|
+
// This branch's only job is to make sure the universal prefix fallback below does NOT ALSO run
|
|
2080
|
+
// for a def that already has this strategy declared — the same "no double delivery" concern
|
|
2081
|
+
// `imageDelivery`'s doc calls out for its own native-vs-fallback split.
|
|
2082
|
+
return { promptPrefix: '', prompt, extraArgs: [], envOverrides: {} };
|
|
2083
|
+
}
|
|
2084
|
+
const isContinuingExistingSession = (resumesSessionViaCli === true || resumesSessionViaAcpLoad === true) &&
|
|
2085
|
+
typeof resumeSessionId === 'string' &&
|
|
2086
|
+
resumeSessionId.length > 0;
|
|
2087
|
+
if (isContinuingExistingSession) {
|
|
2088
|
+
return { promptPrefix: '', prompt, extraArgs: [], envOverrides: {} };
|
|
2089
|
+
}
|
|
2090
|
+
// KNOWN TRADE-OFF, deliberate: for a resume-capable def with no `'append-flag'`/`'env-var'`
|
|
2091
|
+
// mechanism yet (`codex`, `codebuddy`, `opencode`, `amr` — all four presently on this fallback),
|
|
2092
|
+
// the overlay is therefore only injected on the SESSION-CREATING turn, not every turn. A host
|
|
2093
|
+
// whose `PromptAugmenter.systemOverlay()` result can change mid-conversation (e.g. a host that
|
|
2094
|
+
// lets an operator edit its own stored instructions and re-reads them before every run — see
|
|
2095
|
+
// `prompt-augmenter.ts`'s own doc for the seam) will see NO effect from such an edit until a NEW
|
|
2096
|
+
// session starts for one of these four defs specifically — a real, silent limitation, not a
|
|
2097
|
+
// theoretical one. This is the correct
|
|
2098
|
+
// trade against the alternative (re-injecting every turn would bake the overlay into that def's
|
|
2099
|
+
// own CLI-persisted session history again and again, compounding without bound) — do not change
|
|
2100
|
+
// this gating to "fix" the staleness. The actual fix is giving each of the four its own
|
|
2101
|
+
// `'append-flag'`-equivalent `systemPromptDelivery` (an argv flag or an env var, neither of which
|
|
2102
|
+
// is part of what a resumed session replays), which removes this limitation entirely for that
|
|
2103
|
+
// def. See `reasonix.ts`'s and `opencode.ts`'s module docs for the two already-identified,
|
|
2104
|
+
// not-yet-wired native mechanisms.
|
|
2105
|
+
const promptPrefix = `${overlay}\n\n---\n\n`;
|
|
2106
|
+
return { promptPrefix, prompt: `${promptPrefix}${prompt}`, extraArgs: [], envOverrides: {} };
|
|
2107
|
+
}
|
|
2108
|
+
/**
|
|
2109
|
+
* Phase 9b: calls the def's `buildArgs`, releasing staged resources and failing the run on a throw.
|
|
2110
|
+
*
|
|
2111
|
+
* @param input.overlayDelivery - This run's already-resolved overlay decision, computed once in
|
|
2112
|
+
* `run()` rather than here. It is resolved upstream because `buildArgs` is only ONE of the four
|
|
2113
|
+
* channels that can carry the prompt: the staged prompt file is written *before* this function
|
|
2114
|
+
* runs, and stdin/ACP/pi-rpc send theirs *after* spawn. A decision made inside this function could
|
|
2115
|
+
* therefore only ever reach the 7 defs whose `buildArgs` reads its first argument at all — the
|
|
2116
|
+
* other 17 declare it `_prompt` and discard it, which is exactly how the overlay used to go missing.
|
|
2117
|
+
*/
|
|
1902
2118
|
export async function buildRunArgs(input, deps) {
|
|
1903
2119
|
try {
|
|
1904
|
-
|
|
2120
|
+
const delivery = input.overlayDelivery;
|
|
2121
|
+
const args = input.def.buildArgs(delivery.prompt, [...(input.imagePaths ?? [])], input.imageDelivery.extraAllowedDirs === undefined ? undefined : [...input.imageDelivery.extraAllowedDirs], buildAgentBuildArgsOptions(input.runInput, input.systemPromptOverlay), input.runtimeContext);
|
|
2122
|
+
// `'append-flag'` delivery's extra argv (empty for every other def/strategy) is appended after
|
|
2123
|
+
// whatever the def's own `buildArgs` returned — safe because it is only ever non-empty for a
|
|
2124
|
+
// `promptViaStdin` def with no trailing positional argv (`claude`/`pi` today; see
|
|
2125
|
+
// `resolveSystemPromptOverlayDelivery`'s doc for why a future 'append-flag' def must keep that
|
|
2126
|
+
// property too). `envOverrides` (non-empty only for `'env-var'` — `reasonix` today) is handed
|
|
2127
|
+
// back rather than applied here, since the spawn env isn't finalized until `computeChildEnv`
|
|
2128
|
+
// runs, later in `run()`.
|
|
2129
|
+
return { args: [...args, ...delivery.extraArgs], envOverrides: delivery.envOverrides };
|
|
1905
2130
|
}
|
|
1906
2131
|
catch (err) {
|
|
1907
2132
|
await deps.releaseStagedResources();
|
|
@@ -1922,6 +2147,47 @@ export async function writeMcpJsonIfNeeded(input, deps) {
|
|
|
1922
2147
|
return deps.failBeforeSpawn(input.runId, 'AGENT_SPAWN_FAILED', `AgentExecutor: could not write .mcp.json for agent "${input.def.id}": ${errorMessage(err)}`);
|
|
1923
2148
|
}
|
|
1924
2149
|
}
|
|
2150
|
+
/**
|
|
2151
|
+
* Phase 10b1: `systemPromptDelivery: { strategy: 'config-instructions-file' }`'s one effect —
|
|
2152
|
+
* stages the computed overlay to a fresh, run-scoped temp file, so `computeChildEnv` has a real
|
|
2153
|
+
* path to merge into that def's `instructions` config array (see
|
|
2154
|
+
* {@link mergeEnvContentInstructions}'s own doc for the live verification this mechanism rests on).
|
|
2155
|
+
* `null` for every other strategy, an unset `systemPromptDelivery`, or no overlay present at all —
|
|
2156
|
+
* byte-identical to before this mechanism existed, matching {@link writeMcpJsonIfNeeded}'s and
|
|
2157
|
+
* {@link prepareCodexHomeIfNeeded}'s identical no-op-when-inapplicable gate.
|
|
2158
|
+
*
|
|
2159
|
+
* `opencode`'s `instructions` field only accepts a file path or a remote URL — confirmed live
|
|
2160
|
+
* (2026-09-01): a literal instruction string in the array is silently ignored (no error, just never
|
|
2161
|
+
* honored), so an inline-text shortcut is not available and this staging step is load-bearing, not
|
|
2162
|
+
* a defensive extra.
|
|
2163
|
+
* @param input.def - Only used for its `id`, in the failure message, and its `systemPromptDelivery` declaration.
|
|
2164
|
+
* @param input.overlay - The computed `PromptAugmenter.systemOverlay()` result for this run.
|
|
2165
|
+
* @complexity O(1) plus one directory creation and one file write.
|
|
2166
|
+
* @overallScore 100/100
|
|
2167
|
+
*/
|
|
2168
|
+
export async function prepareSystemPromptOverlayFileIfNeeded(input, deps) {
|
|
2169
|
+
if (input.def.systemPromptDelivery?.strategy !== 'config-instructions-file' ||
|
|
2170
|
+
typeof input.overlay !== 'string' ||
|
|
2171
|
+
input.overlay.length === 0) {
|
|
2172
|
+
return null;
|
|
2173
|
+
}
|
|
2174
|
+
try {
|
|
2175
|
+
const safeRunId = input.runId.replace(/[^A-Za-z0-9_-]/g, '-').slice(0, 80) || 'run';
|
|
2176
|
+
const dir = await fsPromises.mkdtemp(join(tmpdir(), `jini-system-prompt-overlay-${safeRunId}-`));
|
|
2177
|
+
const filePath = join(dir, 'overlay.md');
|
|
2178
|
+
await fsPromises.writeFile(filePath, input.overlay, { encoding: 'utf8', mode: 0o600 });
|
|
2179
|
+
return {
|
|
2180
|
+
path: filePath,
|
|
2181
|
+
cleanup: async () => {
|
|
2182
|
+
await fsPromises.rm(dir, { recursive: true, force: true });
|
|
2183
|
+
},
|
|
2184
|
+
};
|
|
2185
|
+
}
|
|
2186
|
+
catch (err) {
|
|
2187
|
+
await deps.releaseStagedResources();
|
|
2188
|
+
return deps.failBeforeSpawn(input.runId, 'AGENT_SPAWN_FAILED', `AgentExecutor: could not stage a system-prompt overlay file for agent "${input.def.id}": ${errorMessage(err)}`);
|
|
2189
|
+
}
|
|
2190
|
+
}
|
|
1925
2191
|
/**
|
|
1926
2192
|
* Phase 10b: mechanism 5 of 5's one effect — stages this run's scratch `CODEX_HOME` directory,
|
|
1927
2193
|
* returning the prepared handle `cleanupStagedFiles` should later release (`null` for every other
|
|
@@ -2125,13 +2391,46 @@ export function createAgentExecutor(options) {
|
|
|
2125
2391
|
// defs' own native protocol already delivers the image and must never also get this
|
|
2126
2392
|
// treatment (the double-delivery hazard this mechanism exists to avoid).
|
|
2127
2393
|
const imageDelivery = await resolveImageDeliveryAndArgvBudget({ runId: input.runId, def, prompt: input.prompt, imagePaths: input.imagePaths, extraAllowedDirs: input.extraAllowedDirs }, { failBeforeSpawn });
|
|
2394
|
+
// Phase 8, hoisted deliberately above EVERY step that writes or sends the prompt. Four
|
|
2395
|
+
// different channels carry a prompt in this driver, and they do not all run at the same point:
|
|
2396
|
+
// the `promptViaFile` staging below writes its file BEFORE `buildArgs`, `buildArgs` itself runs
|
|
2397
|
+
// mid-`run()`, and stdin/ACP/pi-rpc all send theirs AFTER spawn. Resolving the overlay once,
|
|
2398
|
+
// here, is what lets all four consume the same decision; resolving it later (as this used to,
|
|
2399
|
+
// inside `buildRunArgs`) could only ever reach `buildArgs`, so the 17 defs that discard that
|
|
2400
|
+
// argument, plus grok-build's staged file, silently received no overlay at all.
|
|
2401
|
+
//
|
|
2402
|
+
// `computeSystemPromptOverlay`'s third argument is the pre-staging `RuntimeContext`: it reads
|
|
2403
|
+
// only `hasPriorAssistantTurn`, which `computeRuntimeContext` never populates from any of the
|
|
2404
|
+
// staged-file/MCP inputs added to the fuller context built further down, so nothing between
|
|
2405
|
+
// here and there can change the overlay this returns. Passing the narrower context makes that
|
|
2406
|
+
// independence explicit rather than relying on the ordering staying lucky.
|
|
2407
|
+
//
|
|
2408
|
+
// `turnIndex` is a coarse 0/1 proxy (no exact turn counter exists on this driver) — sufficient
|
|
2409
|
+
// because every `PromptAugmenter.systemOverlay()` implementation this seam has today wants the
|
|
2410
|
+
// same overlay on every turn, not a first-turn-only one; a caller that needs finer-grained turn
|
|
2411
|
+
// numbering can track it itself and ignore this arg.
|
|
2412
|
+
const preStagingRuntimeContext = computeRuntimeContext(null, null, null, input.resumeSessionId, input.newSessionId);
|
|
2413
|
+
const systemPromptOverlay = computeSystemPromptOverlay(promptAugmenter, def.id, preStagingRuntimeContext);
|
|
2414
|
+
const overlayDelivery = resolveSystemPromptOverlayDelivery({
|
|
2415
|
+
defId: def.id,
|
|
2416
|
+
systemPromptDelivery: def.systemPromptDelivery,
|
|
2417
|
+
resumesSessionViaCli: def.resumesSessionViaCli,
|
|
2418
|
+
resumesSessionViaAcpLoad: def.resumesSessionViaAcpLoad,
|
|
2419
|
+
overlay: systemPromptOverlay,
|
|
2420
|
+
prompt: imageDelivery.prompt,
|
|
2421
|
+
resumeSessionId: preStagingRuntimeContext?.resumeSessionId,
|
|
2422
|
+
});
|
|
2128
2423
|
const resolvedEnv = resolveRunEnv(input, process.env);
|
|
2129
2424
|
const launch = await resolveLaunch({ runId: input.runId, def, resolvedEnv }, { resolveAgentLaunch: resolveAgentLaunchFn, failBeforeSpawn });
|
|
2130
2425
|
const spawnEnv = applyAgentLaunchEnvFn({ ...resolvedEnv }, launch);
|
|
2131
2426
|
// Stage a promptViaFile def's (grok-build) prompt to a temp file before buildArgs runs — its
|
|
2132
2427
|
// buildArgs throws without runtimeContext.promptFilePath. A no-op (returns null) for every
|
|
2133
2428
|
// def without promptViaFile: true (preparePromptFileForAgent's own guard).
|
|
2134
|
-
|
|
2429
|
+
//
|
|
2430
|
+
// `overlayDelivery.prompt`, not `imageDelivery.prompt`: for a `promptViaFile` def this file IS
|
|
2431
|
+
// the prompt transport — its `buildArgs` declares `_prompt` and passes only the path — so the
|
|
2432
|
+
// overlay has to be in the bytes written here or the CLI never sees it at all.
|
|
2433
|
+
const preparedPromptFile = await stagePromptFile({ runId: input.runId, def, prompt: overlayDelivery.prompt }, { preparePromptFileForAgent: preparePromptFileForAgentFn, failBeforeSpawn });
|
|
2135
2434
|
// Stage a needsAgentLogFile def's (antigravity) diagnostic-log path, on the same terms and at
|
|
2136
2435
|
// the same point as the prompt file above: before buildArgs, since buildArgs is what turns the
|
|
2137
2436
|
// path into a `--log-file <path>` argument. A no-op (returns null) for every def without
|
|
@@ -2159,6 +2458,14 @@ export function createAgentExecutor(options) {
|
|
|
2159
2458
|
* Only the `'codex-toml'` mechanism stages a directory at all.
|
|
2160
2459
|
*/
|
|
2161
2460
|
let preparedCodexHome = null;
|
|
2461
|
+
/**
|
|
2462
|
+
* Set once `prepareSystemPromptOverlayFileIfNeeded` has actually staged this run's overlay file
|
|
2463
|
+
* for a `'config-instructions-file'` def, so `cleanupStagedFiles` knows there is a temp
|
|
2464
|
+
* directory to remove. Cleared as it is consumed, matching `preparedCodexHome`'s identical
|
|
2465
|
+
* single-removal discipline. Only that one strategy stages a file this way — `null` for every
|
|
2466
|
+
* other def/strategy/no-overlay run.
|
|
2467
|
+
*/
|
|
2468
|
+
let preparedSystemPromptOverlayFile = null;
|
|
2162
2469
|
const cleanupStagedFiles = async () => {
|
|
2163
2470
|
if (preparedPromptFile)
|
|
2164
2471
|
await preparedPromptFile.cleanup();
|
|
@@ -2174,6 +2481,11 @@ export function createAgentExecutor(options) {
|
|
|
2174
2481
|
preparedCodexHome = null;
|
|
2175
2482
|
await codexHomeToRemove.cleanup();
|
|
2176
2483
|
}
|
|
2484
|
+
if (preparedSystemPromptOverlayFile) {
|
|
2485
|
+
const overlayFileToRemove = preparedSystemPromptOverlayFile;
|
|
2486
|
+
preparedSystemPromptOverlayFile = null;
|
|
2487
|
+
await overlayFileToRemove.cleanup();
|
|
2488
|
+
}
|
|
2177
2489
|
};
|
|
2178
2490
|
// Resolve this run's MCP bridge delivery once, before buildArgs — the `'claude-mcp-json'`
|
|
2179
2491
|
// variant's path has to be in `runtimeContext` for that def's own `--mcp-config` argv, and
|
|
@@ -2205,12 +2517,6 @@ export function createAgentExecutor(options) {
|
|
|
2205
2517
|
releaseRuntimeLock();
|
|
2206
2518
|
await cleanupStagedFiles();
|
|
2207
2519
|
};
|
|
2208
|
-
// Computed once per `run()`, not per-token/per-event: a system-prompt overlay is a spawn-time
|
|
2209
|
-
// CLI arg, not something that varies mid-run. `turnIndex` is a coarse 0/1 proxy (no exact turn
|
|
2210
|
-
// counter exists on this driver) — sufficient because every `PromptAugmenter.systemOverlay()`
|
|
2211
|
-
// implementation this seam has today wants the same overlay on every turn, not a first-turn-only
|
|
2212
|
-
// one; a caller that needs finer-grained turn numbering can track it itself and ignore this arg.
|
|
2213
|
-
const systemPromptOverlay = computeSystemPromptOverlay(promptAugmenter, def.id, runtimeContext);
|
|
2214
2520
|
// Guarded, like every other step between staging and spawn: a `runtimeLock` def's `buildArgs` is
|
|
2215
2521
|
// guarded precisely *because* it performs real filesystem writes (antigravity writes its model
|
|
2216
2522
|
// choice into a shared settings file), so EACCES on a read-only home, ENOSPC, or a malformed
|
|
@@ -2218,7 +2524,7 @@ export function createAgentExecutor(options) {
|
|
|
2218
2524
|
// `Error` — breaking this driver's "never a bare throw, always an `AgentExecutorError`" contract
|
|
2219
2525
|
// — and left the run `'running'` forever while still holding the process-global mutex and both
|
|
2220
2526
|
// staged files, so no later run of that def could ever acquire the lock either.
|
|
2221
|
-
const args = await buildRunArgs({ runId: input.runId, def, imageDelivery, imagePaths: input.imagePaths, runInput: input, systemPromptOverlay, runtimeContext }, { releaseStagedResources, failBeforeSpawn });
|
|
2527
|
+
const { args, envOverrides: systemPromptEnvOverrides } = await buildRunArgs({ runId: input.runId, def, imageDelivery, imagePaths: input.imagePaths, runInput: input, systemPromptOverlay, overlayDelivery, runtimeContext }, { releaseStagedResources, failBeforeSpawn });
|
|
2222
2528
|
// Mechanism 1 of 5's one effect — stage this run's own MCP config file (run-scoped, see
|
|
2223
2529
|
// `mcpJsonPathForRun`) before spawn so the `--mcp-config <path>` argv buildArgs just produced
|
|
2224
2530
|
// points at a real file. Skipped entirely for the other four mechanisms and whenever no bridge
|
|
@@ -2231,12 +2537,21 @@ export function createAgentExecutor(options) {
|
|
|
2231
2537
|
// so — unlike the `.mcp.json` staging above — this can run after `buildArgs` with no ordering
|
|
2232
2538
|
// constraint of its own; it is placed here only to keep the two staging steps adjacent.
|
|
2233
2539
|
preparedCodexHome = await prepareCodexHomeIfNeeded({ runId: input.runId, def, mcpBridge }, { mcpJsonInjection, hostEnv: process.env, releaseStagedResources, failBeforeSpawn });
|
|
2540
|
+
// `'config-instructions-file'`'s one effect — stage the overlay to a temp file so
|
|
2541
|
+
// `computeChildEnv` below has a real path to merge into that def's `instructions` array. A
|
|
2542
|
+
// no-op (`null`) for every other def/strategy or a run with no overlay at all. Independent of
|
|
2543
|
+
// `mcpBridge`/`preparedCodexHome` above (a different strategy field entirely), so placed here
|
|
2544
|
+
// only to stay adjacent to the other pre-`computeChildEnv` staging steps, not for any ordering
|
|
2545
|
+
// requirement between them.
|
|
2546
|
+
preparedSystemPromptOverlayFile = await prepareSystemPromptOverlayFileIfNeeded({ runId: input.runId, def, overlay: systemPromptOverlay }, { releaseStagedResources, failBeforeSpawn });
|
|
2234
2547
|
// Computed only now, not right after `mcpBridge` resolution: mechanism 5's directory path is
|
|
2235
2548
|
// not known until the staging step directly above actually runs `mkdtemp` (see
|
|
2236
2549
|
// `McpBridgeDelivery`'s `'codex-toml'` variant doc for why it cannot be pre-computed the way
|
|
2237
2550
|
// `'claude-mcp-json'`'s deterministic path is). Nothing between the old, earlier call site and
|
|
2238
2551
|
// here ever read `childEnv`, so moving the call cost nothing.
|
|
2239
|
-
const childEnv = computeChildEnv(spawnEnv, mcpBridge, preparedCodexHome?.path
|
|
2552
|
+
const childEnv = computeChildEnv(spawnEnv, mcpBridge, preparedCodexHome?.path, systemPromptEnvOverrides, preparedSystemPromptOverlayFile && def.systemPromptDelivery?.strategy === 'config-instructions-file'
|
|
2553
|
+
? { varName: def.systemPromptDelivery.varName, path: preparedSystemPromptOverlayFile.path }
|
|
2554
|
+
: undefined);
|
|
2240
2555
|
// Post-buildArgs guard for argv-bound defs whose resolved binary is a
|
|
2241
2556
|
// Windows .cmd/.bat shim or a direct .exe: a prompt under the raw byte
|
|
2242
2557
|
// budget can still expand past CreateProcess's command-line cap once
|
|
@@ -2289,7 +2604,14 @@ export function createAgentExecutor(options) {
|
|
|
2289
2604
|
runId: input.runId,
|
|
2290
2605
|
agentId: def.id,
|
|
2291
2606
|
child,
|
|
2292
|
-
|
|
2607
|
+
// `overlayDelivery.promptPrefix` applied to `input.prompt`, NOT `overlayDelivery.prompt`:
|
|
2608
|
+
// this call site deliberately sends the raw input prompt rather than the image-delivery
|
|
2609
|
+
// rewrite (see `resolveImageDeliveryAndArgvBudget`'s call site above — an ACP def delivers
|
|
2610
|
+
// images natively and must never also get `'prompt-path'`'s appended paths), and the prefix
|
|
2611
|
+
// is the part of the overlay decision that applies to whatever prompt text a transport was
|
|
2612
|
+
// already sending. `''` for `reasonix` (env-var) and any resumed ACP session, so those keep
|
|
2613
|
+
// sending byte-identical text and never receive the overlay twice.
|
|
2614
|
+
prompt: `${overlayDelivery.promptPrefix}${input.prompt}`,
|
|
2293
2615
|
cwd: input.cwd,
|
|
2294
2616
|
model: input.model,
|
|
2295
2617
|
imagePaths: input.imagePaths ?? [],
|
|
@@ -2316,7 +2638,10 @@ export function createAgentExecutor(options) {
|
|
|
2316
2638
|
runId: input.runId,
|
|
2317
2639
|
agentId: def.id,
|
|
2318
2640
|
child,
|
|
2319
|
-
|
|
2641
|
+
// Same reasoning as the ACP call site above. `pi` declares an `'append-flag'` strategy,
|
|
2642
|
+
// so its prefix is `''` and this stays byte-identical to the raw prompt — the overlay
|
|
2643
|
+
// rides `--append-system-prompt` instead, exactly once.
|
|
2644
|
+
prompt: `${overlayDelivery.promptPrefix}${input.prompt}`,
|
|
2320
2645
|
cwd: input.cwd,
|
|
2321
2646
|
model: input.model,
|
|
2322
2647
|
imagePaths: input.imagePaths ?? [],
|
|
@@ -2336,7 +2661,17 @@ export function createAgentExecutor(options) {
|
|
|
2336
2661
|
});
|
|
2337
2662
|
return;
|
|
2338
2663
|
}
|
|
2339
|
-
|
|
2664
|
+
// The overlay reaches stdin only for a def that declares stdin as its prompt transport. The
|
|
2665
|
+
// four `plain`-format defs that do not (`aider`/`antigravity`/`deepseek` put the prompt in
|
|
2666
|
+
// argv, `grok-build` in a staged file) are still written to and closed here exactly as before,
|
|
2667
|
+
// because this driver always spawns with `stdio: ['pipe','pipe','pipe']` and their CLIs need
|
|
2668
|
+
// the EOF — but their prompt already carried the overlay through argv or the staged file, so
|
|
2669
|
+
// sending the overlaid text here too would deliver it twice. This is the one place the
|
|
2670
|
+
// resolver's own `''`-prefix rule is not sufficient on its own: those four defs are on the
|
|
2671
|
+
// fallback strategy, so their prefix is genuinely non-empty; what makes stdin the wrong
|
|
2672
|
+
// channel for them is the def's declared transport, not the strategy.
|
|
2673
|
+
const stdinPrompt = def.promptViaStdin === true ? overlayDelivery.prompt : imageDelivery.prompt;
|
|
2674
|
+
writePromptToStdin(def, child, stdinPrompt, stdinHandle);
|
|
2340
2675
|
}
|
|
2341
2676
|
return { run };
|
|
2342
2677
|
}
|