mixdog 0.9.151 → 0.9.152
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 +110 -68
- package/package.json +3 -2
- package/scripts/tool-stress.mjs +0 -2
- package/src/defaults/skills/setup/SKILL.md +18 -27
- package/src/headless-exec.mjs +3 -10
- package/src/headless-exec.test.mjs +2 -7
- package/src/runtime/agent/orchestrator/config.mjs +6 -66
- package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +153 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +49 -45
- package/src/runtime/agent/orchestrator/mcp/client.mjs +242 -15
- package/src/runtime/agent/orchestrator/mcp/features.test.mjs +66 -0
- package/src/runtime/agent/orchestrator/mcp/security.test.mjs +36 -20
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +54 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -8
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +10 -2
- package/src/runtime/agent/orchestrator/providers/lib/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/lib/sse-framing.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +0 -2
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +4 -7
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +235 -0
- package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +719 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +399 -39
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +14 -4
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +24 -1
- package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +161 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +14 -4
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +31 -18
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +6 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +1 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +556 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +457 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +105 -186
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +23 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +14 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -10
- package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +30 -14
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +15 -4
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +73 -15
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +76 -2
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +24 -683
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +64 -72
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -5
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.mjs +75 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +24 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +24 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +12 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +35 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +28 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +54 -0
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +8 -0
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
- package/src/runtime/browser-bridge/client.mjs +103 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +42 -0
- package/src/runtime/computer-bridge/client.mjs +101 -0
- package/src/runtime/computer-bridge/tool-defs.mjs +53 -0
- package/src/runtime/media/store.mjs +18 -8
- package/src/runtime/media/store.test.mjs +14 -0
- package/src/runtime/memory/index.mjs +1 -1
- package/src/runtime/memory/lib/transcript-ingest.mjs +8 -6
- package/src/runtime/memory/lib/transcript-ingest.test.mjs +52 -0
- package/src/runtime/shared/child-spawn-gate.mjs +17 -0
- package/src/runtime/shared/child-spawn-remote.mjs +69 -7
- package/src/runtime/shared/child-spawn-remote.test.mjs +124 -0
- package/src/runtime/shared/pristine-execution-contract.json +2 -1
- package/src/runtime/shared/provider-api-key.mjs +1 -0
- package/src/runtime/shared/session-runtime-health.mjs +91 -0
- package/src/runtime/shared/session-runtime-health.test.mjs +53 -0
- package/src/runtime/shared/skill-document.mjs +86 -0
- package/src/runtime/shared/skill-document.test.mjs +81 -0
- package/src/runtime/shared/tool-surface.mjs +18 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +1 -0
- package/src/runtime/shared/turn-snapshot.mjs +32 -9
- package/src/runtime/shared/user-cwd.mjs +33 -14
- package/src/runtime/shared/user-cwd.test.mjs +43 -0
- package/src/session-runtime/cwd-plugins.mjs +26 -26
- package/src/session-runtime/cwd-plugins.test.mjs +75 -0
- package/src/session-runtime/cwd-tool-routing.test.mjs +5 -0
- package/src/session-runtime/global-extensions.mjs +26 -0
- package/src/session-runtime/global-extensions.test.mjs +28 -0
- package/src/session-runtime/goal-runtime.mjs +721 -0
- package/src/session-runtime/goal-runtime.test.mjs +108 -0
- package/src/session-runtime/lifecycle-api.mjs +18 -4
- package/src/session-runtime/lifecycle-api.test.mjs +21 -0
- package/src/session-runtime/mcp-glue.mjs +49 -47
- package/src/session-runtime/plugin-mcp-standard.test.mjs +177 -0
- package/src/session-runtime/plugin-mcp.mjs +96 -22
- package/src/session-runtime/prewarm.mjs +1 -10
- package/src/session-runtime/resource-api-global.test.mjs +69 -0
- package/src/session-runtime/resource-api.mjs +173 -58
- package/src/session-runtime/runtime-core.mjs +148 -20
- package/src/session-runtime/runtime-tunables.mjs +6 -8
- package/src/session-runtime/session-turn-api.mjs +22 -11
- package/src/session-runtime/skills-api.mjs +79 -25
- package/src/session-runtime/skills-api.test.mjs +99 -0
- package/src/session-runtime/tool-catalog-schema.mjs +0 -5
- package/src/session-runtime/tool-catalog.mjs +4 -13
- package/src/session-runtime/tool-policy-surface.test.mjs +33 -11
- package/src/session-runtime/tool-surface.mjs +12 -0
- package/src/standalone/agent-dispatch-broker.mjs +5 -9
- package/src/standalone/agent-tool/notify.mjs +6 -8
- package/src/standalone/agent-tool/notify.test.mjs +37 -0
- package/src/standalone/daemon.mjs +31 -15
- package/src/standalone/hook-bus/config.mjs +1 -0
- package/src/standalone/plugin-admin.mjs +17 -0
- package/src/standalone/plugin-admin.test.mjs +7 -0
- package/src/standalone/provider-admin.mjs +1 -0
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-runtime-agent-control-client.mjs +133 -0
- package/src/standalone/session-runtime-agent-control-client.test.mjs +71 -0
- package/src/standalone/session-runtime-dispatch-cancel.test.mjs +139 -0
- package/src/standalone/session-runtime-host-factory.mjs +17 -0
- package/src/standalone/session-runtime-host-factory.test.mjs +31 -0
- package/src/standalone/session-runtime-host-health.test.mjs +5 -0
- package/src/standalone/session-runtime-host.mjs +696 -58
- package/src/standalone/session-runtime-inline-host.mjs +351 -0
- package/src/standalone/session-runtime-inline-host.test.mjs +83 -0
- package/src/standalone/session-runtime-provider-cooldown.mjs +78 -0
- package/src/standalone/session-runtime-provider-cooldown.test.mjs +80 -0
- package/src/standalone/session-runtime-shard-host.test.mjs +569 -0
- package/src/standalone/session-runtime-shard-router.mjs +149 -0
- package/src/standalone/session-runtime-shard-router.test.mjs +101 -0
- package/src/standalone/session-runtime-worker.mjs +476 -18
- package/src/standalone/session-service.mjs +81 -6
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/core-memory-picker.mjs +2 -0
- package/src/tui/app/extension-pickers.mjs +1 -1
- package/src/tui/app/maintenance-pickers.mjs +3 -3
- package/src/tui/app/model-options.mjs +2 -0
- package/src/tui/app/model-picker.mjs +14 -4
- package/src/tui/app/panel-handoff.test.mjs +311 -0
- package/src/tui/app/project-picker.mjs +1 -0
- package/src/tui/app/route-pickers.mjs +21 -6
- package/src/tui/app/settings-picker.mjs +26 -3
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch.mjs +73 -22
- package/src/tui/app/theme-effort-pickers.mjs +7 -2
- package/src/tui/components/Picker.jsx +10 -1
- package/src/tui/dist/index.mjs +163 -44
- package/src/tui/session/context-state.mjs +1 -0
- package/src/tui/session/goal-continuation.mjs +110 -0
- package/src/tui/session/goal-continuation.test.mjs +84 -0
- package/src/tui/session/queue-helpers.mjs +5 -2
- package/src/tui/session/session-action-surface.test.mjs +108 -0
- package/src/tui/session/session-api-ext.mjs +39 -8
- package/src/tui/session/session-api.mjs +58 -0
- package/src/tui/session/session-flow.mjs +6 -0
- package/src/tui/session/turn.mjs +1 -0
- package/src/tui/session-local.mjs +15 -1
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -301
|
@@ -1,59 +1,27 @@
|
|
|
1
|
-
import {
|
|
2
|
-
existsSync,
|
|
3
|
-
mkdirSync,
|
|
4
|
-
readFileSync,
|
|
5
|
-
unlinkSync,
|
|
6
|
-
writeFileSync,
|
|
7
|
-
} from 'fs';
|
|
8
|
-
import { writeFile } from 'fs/promises';
|
|
9
|
-
import { randomBytes } from 'crypto';
|
|
10
|
-
import { join } from 'path';
|
|
11
|
-
import { getPluginData } from '../../config.mjs';
|
|
12
|
-
import { renameWithRetrySync } from '../../../../shared/atomic-file.mjs';
|
|
13
|
-
import { sanitizeContentForStoredHistory } from '../../providers/media-normalization.mjs';
|
|
14
|
-
import { promptContentText } from './prompt-utils.mjs';
|
|
1
|
+
import { existsSync, unlinkSync } from 'fs';
|
|
15
2
|
import { finalizeTurnInterruptionSnapshot } from './turn-interruption.mjs';
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
function matchingUserMessage(message, currentUserContent) {
|
|
40
|
-
return message?.role === 'user'
|
|
41
|
-
&& (message.content === currentUserContent
|
|
42
|
-
|| promptContentText(message.content) === promptContentText(currentUserContent));
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function turnMessagesForCheckpoint(messages, currentUserContent) {
|
|
46
|
-
const source = Array.isArray(messages) ? messages : [];
|
|
47
|
-
let start = -1;
|
|
48
|
-
for (let index = source.length - 1; index >= 0; index -= 1) {
|
|
49
|
-
if (matchingUserMessage(source[index], currentUserContent)) {
|
|
50
|
-
start = index;
|
|
51
|
-
break;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
const turn = start >= 0 ? source.slice(start) : [];
|
|
55
|
-
return turn.map(checkpointMessage);
|
|
56
|
-
}
|
|
3
|
+
import {
|
|
4
|
+
appendJournalLines,
|
|
5
|
+
cancelJournalWrites,
|
|
6
|
+
createTurnJournalEncoder,
|
|
7
|
+
emptyInterruptionSnapshot,
|
|
8
|
+
findTurnStart,
|
|
9
|
+
journalHeadLines,
|
|
10
|
+
matchingUserMessage,
|
|
11
|
+
openJournalForTurn,
|
|
12
|
+
readTurnCheckpointHeader,
|
|
13
|
+
removeTurnJournal,
|
|
14
|
+
replayTurnJournal,
|
|
15
|
+
settleTurnJournalWrites,
|
|
16
|
+
turnCheckpointPath,
|
|
17
|
+
turnMessagesForCheckpoint,
|
|
18
|
+
writeCheckpointHeader,
|
|
19
|
+
} from './turn-checkpoint-journal.mjs';
|
|
20
|
+
|
|
21
|
+
export { turnCheckpointPath, turnMessagesForCheckpoint };
|
|
22
|
+
|
|
23
|
+
/** Resolve once every queued journal append for a session has been written. */
|
|
24
|
+
export const settleTurnCheckpointWrites = settleTurnJournalWrites;
|
|
57
25
|
|
|
58
26
|
/** Read-only projection for a turn still owned by another process. Unlike
|
|
59
27
|
* recoverTurnCheckpoint this never mutates the session, clears the checkpoint,
|
|
@@ -85,160 +53,110 @@ export function projectTurnCheckpointMessages(session, checkpoint) {
|
|
|
85
53
|
];
|
|
86
54
|
}
|
|
87
55
|
|
|
88
|
-
// ──
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
92
|
-
//
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
56
|
+
// ── Journal-backed checkpoint writes ─────────────────────────────────────────
|
|
57
|
+
// A turn is checkpointed as a write-once header plus an append-only delta
|
|
58
|
+
// journal (turn-checkpoint-journal.mjs). The recorder owns exactly one turn:
|
|
59
|
+
// the FIRST record() writes the header synchronously — it is the crash
|
|
60
|
+
// durability anchor for the prompt and must land before the provider runs —
|
|
61
|
+
// and every later record() appends ONLY what changed since the previous flush.
|
|
62
|
+
// Per-flush cost is therefore O(new bytes), not O(whole growing turn), so a
|
|
63
|
+
// long tool-heavy turn no longer amplifies into the shared engine thread that
|
|
64
|
+
// every fanned-out agent session shares.
|
|
65
|
+
export function createTurnCheckpointRecorder({ sessionId, generation, turnToken, startedAt }) {
|
|
66
|
+
const encoder = createTurnJournalEncoder({ turnToken });
|
|
67
|
+
let opened = false;
|
|
68
|
+
let stopped = false;
|
|
69
|
+
return {
|
|
70
|
+
get opened() { return opened; },
|
|
71
|
+
/** True when this call wrote or queued durable state. */
|
|
72
|
+
record({ currentUserContent, turnOutgoing, interruption }) {
|
|
73
|
+
if (stopped || !sessionId || !turnToken) return false;
|
|
74
|
+
if (!opened) {
|
|
75
|
+
const seed = encoder.seed(turnOutgoing, currentUserContent, interruption);
|
|
76
|
+
writeCheckpointHeader({
|
|
77
|
+
sessionId,
|
|
78
|
+
generation,
|
|
79
|
+
turnToken,
|
|
80
|
+
startedAt,
|
|
81
|
+
currentUserContent,
|
|
82
|
+
turnMessages: seed.turnMessages,
|
|
83
|
+
interruption: typeof interruption?.snapshot === 'function'
|
|
84
|
+
? interruption.snapshot()
|
|
85
|
+
: emptyInterruptionSnapshot(),
|
|
86
|
+
});
|
|
87
|
+
// Opening the journal is part of the anchor: a leftover journal
|
|
88
|
+
// from an earlier turn of this session must never replay onto
|
|
89
|
+
// the new header. It lands synchronously when the lane is idle
|
|
90
|
+
// and is otherwise FENCED behind the older in-flight write, so
|
|
91
|
+
// an old turn can never appear beneath this turn's token (the
|
|
92
|
+
// head record's token guards the interim window).
|
|
93
|
+
openJournalForTurn(sessionId, seed.openLines);
|
|
94
|
+
opened = true;
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
const lines = encoder.encode({ turnOutgoing, currentUserContent, interruption });
|
|
98
|
+
if (lines.length === 0) return false;
|
|
99
|
+
appendJournalLines(sessionId, lines);
|
|
100
|
+
return true;
|
|
101
|
+
},
|
|
102
|
+
stop() {
|
|
103
|
+
if (stopped) return;
|
|
104
|
+
stopped = true;
|
|
105
|
+
cancelPendingTurnCheckpoint(sessionId);
|
|
106
|
+
},
|
|
107
|
+
};
|
|
104
108
|
}
|
|
105
109
|
|
|
106
|
-
/** Retire queued
|
|
110
|
+
/** Retire queued (not yet written) journal appends for a session. The durable
|
|
111
|
+
* prefix stays on disk on purpose: between turn commit and clearTurnCheckpoint
|
|
112
|
+
* a crash must still recover the turn's work, not just its opening header. */
|
|
107
113
|
export function cancelPendingTurnCheckpoint(sessionId) {
|
|
108
|
-
|
|
109
|
-
if (!entry) return;
|
|
110
|
-
entry.queued = null;
|
|
111
|
-
entry.epoch += 1;
|
|
112
|
-
if (!entry.writing) _pendingCheckpointWrites.delete(sessionId);
|
|
114
|
+
cancelJournalWrites(sessionId);
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
_pendingCheckpointWrites.delete(sessionId);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
const { target, json } = entry.queued;
|
|
123
|
-
entry.queued = null;
|
|
124
|
-
entry.writing = true;
|
|
125
|
-
const epoch = entry.epoch;
|
|
126
|
-
const temp = `${target}.${randomBytes(6).toString('hex')}.tmp`;
|
|
127
|
-
writeFile(temp, json, 'utf8')
|
|
128
|
-
.then(() => {
|
|
129
|
-
// A clear/stop after this write was dequeued must win: renaming a
|
|
130
|
-
// retired payload would resurrect a checkpoint the turn already
|
|
131
|
-
// finalized. The epoch check and rename run in one synchronous
|
|
132
|
-
// block, so cancel (also synchronous) can never interleave.
|
|
133
|
-
if (entry.epoch !== epoch) {
|
|
134
|
-
try { unlinkSync(temp); } catch { /* best-effort */ }
|
|
135
|
-
return;
|
|
136
|
-
}
|
|
137
|
-
renameWithRetrySync(temp, target);
|
|
138
|
-
})
|
|
139
|
-
.catch((error) => {
|
|
140
|
-
try { unlinkSync(temp); } catch { /* best-effort */ }
|
|
141
|
-
_warnAsyncWriteOnce(sessionId, error);
|
|
142
|
-
})
|
|
143
|
-
.finally(() => {
|
|
144
|
-
entry.writing = false;
|
|
145
|
-
_pumpCheckpointWrites(sessionId);
|
|
146
|
-
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Hard-exit parity with the old sync behavior: a queued-but-unwritten payload
|
|
150
|
-
// is flushed synchronously so a clean process exit never loses the newest
|
|
151
|
-
// checkpoint state (crash loss remains bounded by the caller's throttle).
|
|
152
|
-
process.on('exit', () => {
|
|
153
|
-
for (const [sessionId, entry] of _pendingCheckpointWrites) {
|
|
154
|
-
if (!entry.queued) continue;
|
|
155
|
-
const { target, json } = entry.queued;
|
|
156
|
-
entry.queued = null;
|
|
157
|
-
const temp = `${target}.${randomBytes(6).toString('hex')}.tmp`;
|
|
158
|
-
try {
|
|
159
|
-
writeFileSync(temp, json, 'utf8');
|
|
160
|
-
renameWithRetrySync(temp, target);
|
|
161
|
-
} catch (error) {
|
|
162
|
-
try { unlinkSync(temp); } catch { /* best-effort */ }
|
|
163
|
-
_warnAsyncWriteOnce(sessionId, error);
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
export function writeTurnCheckpoint(checkpoint, { sync = true } = {}) {
|
|
117
|
+
/** Full-snapshot write (header + journal reset). The streaming path uses
|
|
118
|
+
* createTurnCheckpointRecorder; this stays for callers holding a complete
|
|
119
|
+
* checkpoint object that must become durable in one shot. */
|
|
120
|
+
export function writeTurnCheckpoint(checkpoint) {
|
|
169
121
|
if (!checkpoint?.sessionId || !checkpoint?.turnToken) return false;
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
version: TURN_CHECKPOINT_VERSION,
|
|
174
|
-
currentUserContent: sanitizeContentForStoredHistory(checkpoint.currentUserContent),
|
|
175
|
-
turnMessages: (Array.isArray(checkpoint.turnMessages) ? checkpoint.turnMessages : [])
|
|
176
|
-
.map(checkpointMessage),
|
|
177
|
-
updatedAt: Date.now(),
|
|
178
|
-
};
|
|
179
|
-
if (!sync) {
|
|
180
|
-
const json = JSON.stringify(payload);
|
|
181
|
-
const entry = _pendingCheckpointWrites.get(checkpoint.sessionId)
|
|
182
|
-
?? { queued: null, writing: false, epoch: 0 };
|
|
183
|
-
_pendingCheckpointWrites.set(checkpoint.sessionId, entry);
|
|
184
|
-
entry.queued = { target, json };
|
|
185
|
-
_pumpCheckpointWrites(checkpoint.sessionId);
|
|
186
|
-
return true;
|
|
187
|
-
}
|
|
188
|
-
const temp = `${target}.${randomBytes(6).toString('hex')}.tmp`;
|
|
189
|
-
try {
|
|
190
|
-
writeFileSync(temp, JSON.stringify(payload), 'utf8');
|
|
191
|
-
renameWithRetrySync(temp, target);
|
|
192
|
-
return true;
|
|
193
|
-
} catch (error) {
|
|
194
|
-
try { unlinkSync(temp); } catch {}
|
|
195
|
-
throw error;
|
|
196
|
-
}
|
|
122
|
+
writeCheckpointHeader(checkpoint);
|
|
123
|
+
openJournalForTurn(checkpoint.sessionId, journalHeadLines(checkpoint.turnToken));
|
|
124
|
+
return true;
|
|
197
125
|
}
|
|
198
126
|
|
|
199
127
|
export function readTurnCheckpoint(sessionId) {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
if (value?.version !== TURN_CHECKPOINT_VERSION
|
|
204
|
-
|| value?.sessionId !== sessionId
|
|
205
|
-
|| !value?.turnToken
|
|
206
|
-
|| !Array.isArray(value?.turnMessages)) {
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
return value;
|
|
210
|
-
} catch {
|
|
211
|
-
return null;
|
|
212
|
-
}
|
|
128
|
+
const header = readTurnCheckpointHeader(sessionId);
|
|
129
|
+
if (!header) return null;
|
|
130
|
+
return replayTurnJournal(header);
|
|
213
131
|
}
|
|
214
132
|
|
|
215
133
|
export function clearTurnCheckpoint(sessionId, turnToken = null) {
|
|
216
134
|
const target = turnCheckpointPath(sessionId);
|
|
217
|
-
// Queued
|
|
218
|
-
//
|
|
219
|
-
//
|
|
135
|
+
// Queued appends are stale the moment the caller decides to clear this
|
|
136
|
+
// turn's checkpoint; retire them BEFORE the token guard reads disk so a
|
|
137
|
+
// lagging write can never resurrect state after the unlink.
|
|
220
138
|
if (!turnToken) cancelPendingTurnCheckpoint(sessionId);
|
|
221
|
-
if (!existsSync(target))
|
|
139
|
+
if (!existsSync(target)) {
|
|
140
|
+
// Header already gone: drop any orphan journal (a late in-flight append
|
|
141
|
+
// can recreate the file after a previous clear).
|
|
142
|
+
if (!turnToken) removeTurnJournal(sessionId);
|
|
143
|
+
return true;
|
|
144
|
+
}
|
|
222
145
|
if (turnToken) {
|
|
223
|
-
const current =
|
|
146
|
+
const current = readTurnCheckpointHeader(sessionId);
|
|
224
147
|
// A token guard prevents an older turn's late terminal save from
|
|
225
148
|
// deleting the checkpoint already created by its queued follow-up.
|
|
226
149
|
if (current?.turnToken && current.turnToken !== turnToken) return false;
|
|
227
150
|
cancelPendingTurnCheckpoint(sessionId);
|
|
228
151
|
}
|
|
152
|
+
let removed = true;
|
|
229
153
|
try {
|
|
230
154
|
unlinkSync(target);
|
|
231
|
-
return true;
|
|
232
155
|
} catch {
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
function findPersistedTurnStart(messages, currentUserContent) {
|
|
238
|
-
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
239
|
-
if (matchingUserMessage(messages[index], currentUserContent)) return index;
|
|
156
|
+
removed = false;
|
|
240
157
|
}
|
|
241
|
-
|
|
158
|
+
removeTurnJournal(sessionId);
|
|
159
|
+
return removed;
|
|
242
160
|
}
|
|
243
161
|
|
|
244
162
|
/**
|
|
@@ -277,13 +195,14 @@ export function recoverTurnCheckpoint(session) {
|
|
|
277
195
|
abortReason: 'process-crash',
|
|
278
196
|
});
|
|
279
197
|
const current = Array.isArray(session.messages) ? session.messages : [];
|
|
280
|
-
const start =
|
|
198
|
+
const start = findTurnStart(current, checkpoint.currentUserContent);
|
|
281
199
|
session.messages = [
|
|
282
200
|
...(start >= 0 ? current.slice(0, start) : current),
|
|
283
201
|
...finalized.messages,
|
|
284
202
|
];
|
|
285
203
|
delete session.activeTurnCheckpoint;
|
|
286
204
|
delete session.providerState;
|
|
205
|
+
delete session._providerPrefixGuardState;
|
|
287
206
|
session.updatedAt = Date.now();
|
|
288
207
|
session.lastUsedAt = Date.now();
|
|
289
208
|
return {
|
|
@@ -188,6 +188,14 @@ export function createTurnInterruptionTracker() {
|
|
|
188
188
|
let phase = 'streaming';
|
|
189
189
|
const observedToolCalls = new Map();
|
|
190
190
|
const observedToolResults = new Map();
|
|
191
|
+
// Epochs make the checkpoint journal's delta encoding sound WITHOUT
|
|
192
|
+
// comparing whole buffers on every flush: an epoch bump means "this buffer
|
|
193
|
+
// changed in a way that is not a plain append", so the encoder falls back
|
|
194
|
+
// to a full (still small) set record instead of an append record.
|
|
195
|
+
let textEpoch = 0;
|
|
196
|
+
let reasoningEpoch = 0;
|
|
197
|
+
let tombEpoch = 0;
|
|
198
|
+
let callsEpoch = 0;
|
|
191
199
|
|
|
192
200
|
return {
|
|
193
201
|
recordTextDelta(chunk) {
|
|
@@ -206,11 +214,16 @@ export function createTurnInterruptionTracker() {
|
|
|
206
214
|
0,
|
|
207
215
|
cutAt,
|
|
208
216
|
);
|
|
217
|
+
textEpoch += 1;
|
|
218
|
+
tombEpoch += 1;
|
|
209
219
|
},
|
|
210
220
|
restoreTombstonedText() {
|
|
211
221
|
if (!tombstonedAssistantContent) return false;
|
|
222
|
+
// Append-only for the partial buffer (no textEpoch bump); only the
|
|
223
|
+
// tombstone side is structurally reset.
|
|
212
224
|
partialAssistantContent += tombstonedAssistantContent;
|
|
213
225
|
tombstonedAssistantContent = '';
|
|
226
|
+
tombEpoch += 1;
|
|
214
227
|
return true;
|
|
215
228
|
},
|
|
216
229
|
hasResponseStarted() {
|
|
@@ -235,6 +248,10 @@ export function createTurnInterruptionTracker() {
|
|
|
235
248
|
tombstonedAssistantContent = '';
|
|
236
249
|
partialReasoningContent = '';
|
|
237
250
|
observedToolCalls.clear();
|
|
251
|
+
textEpoch += 1;
|
|
252
|
+
reasoningEpoch += 1;
|
|
253
|
+
tombEpoch += 1;
|
|
254
|
+
callsEpoch += 1;
|
|
238
255
|
},
|
|
239
256
|
recordToolCalls(calls, { eagerStarted = false } = {}) {
|
|
240
257
|
for (const call of Array.isArray(calls) ? calls : []) {
|
|
@@ -283,6 +300,104 @@ export function createTurnInterruptionTracker() {
|
|
|
283
300
|
observedToolResults: [...observedToolResults.entries()],
|
|
284
301
|
};
|
|
285
302
|
},
|
|
303
|
+
/**
|
|
304
|
+
* Delta against an opaque cursor from a previous call (null = seed).
|
|
305
|
+
* The checkpoint journal uses this instead of snapshot() so a flush
|
|
306
|
+
* serializes only what changed: appended text/reasoning, newly observed
|
|
307
|
+
* tool calls/results, and phase/responseStarted transitions. Cost is
|
|
308
|
+
* O(new bytes + observed tool entries), never O(turn).
|
|
309
|
+
* Returns { changed, delta, cursor }.
|
|
310
|
+
*/
|
|
311
|
+
journalDelta(cursor) {
|
|
312
|
+
const prev = cursor && typeof cursor === 'object' ? cursor : null;
|
|
313
|
+
const prevCalls = prev?.calls instanceof Map ? prev.calls : new Map();
|
|
314
|
+
const prevResults = prev?.results instanceof Map ? prev.results : new Map();
|
|
315
|
+
const delta = {};
|
|
316
|
+
let changed = false;
|
|
317
|
+
if (prev ? prev.responseStarted !== responseStarted : responseStarted) {
|
|
318
|
+
delta.rs = responseStarted;
|
|
319
|
+
changed = true;
|
|
320
|
+
}
|
|
321
|
+
if (prev ? prev.phase !== phase : phase !== 'streaming') {
|
|
322
|
+
delta.ph = phase;
|
|
323
|
+
changed = true;
|
|
324
|
+
}
|
|
325
|
+
if (!prev || prev.textEpoch !== textEpoch
|
|
326
|
+
|| partialAssistantContent.length < prev.textLen) {
|
|
327
|
+
if (prev || partialAssistantContent) {
|
|
328
|
+
delta.ts = partialAssistantContent;
|
|
329
|
+
changed = true;
|
|
330
|
+
}
|
|
331
|
+
} else if (partialAssistantContent.length > prev.textLen) {
|
|
332
|
+
delta.ta = partialAssistantContent.slice(prev.textLen);
|
|
333
|
+
changed = true;
|
|
334
|
+
}
|
|
335
|
+
if (!prev || prev.reasoningEpoch !== reasoningEpoch
|
|
336
|
+
|| partialReasoningContent.length < prev.reasoningLen) {
|
|
337
|
+
if (prev || partialReasoningContent) {
|
|
338
|
+
delta.qs = partialReasoningContent;
|
|
339
|
+
changed = true;
|
|
340
|
+
}
|
|
341
|
+
} else if (partialReasoningContent.length > prev.reasoningLen) {
|
|
342
|
+
delta.qa = partialReasoningContent.slice(prev.reasoningLen);
|
|
343
|
+
changed = true;
|
|
344
|
+
}
|
|
345
|
+
if (!prev || prev.tombEpoch !== tombEpoch) {
|
|
346
|
+
if (prev || tombstonedAssistantContent) {
|
|
347
|
+
delta.tb = tombstonedAssistantContent;
|
|
348
|
+
changed = true;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// A cleared call map is a structural reset: journal the clear and
|
|
352
|
+
// re-emit whatever was recorded after it.
|
|
353
|
+
const callsReset = Boolean(prev) && prev.callsEpoch !== callsEpoch;
|
|
354
|
+
if (callsReset) {
|
|
355
|
+
delta.cc = true;
|
|
356
|
+
changed = true;
|
|
357
|
+
}
|
|
358
|
+
const callSet = [];
|
|
359
|
+
for (const [id, entry] of observedToolCalls) {
|
|
360
|
+
if (!callsReset && prevCalls.get(id) === entry) continue;
|
|
361
|
+
callSet.push([id, entry]);
|
|
362
|
+
}
|
|
363
|
+
if (callSet.length > 0) {
|
|
364
|
+
delta.cs = callSet;
|
|
365
|
+
changed = true;
|
|
366
|
+
}
|
|
367
|
+
const resultSet = [];
|
|
368
|
+
for (const [id, entry] of observedToolResults) {
|
|
369
|
+
if (prevResults.get(id) === entry) continue;
|
|
370
|
+
resultSet.push([id, entry]);
|
|
371
|
+
}
|
|
372
|
+
if (resultSet.length > 0) {
|
|
373
|
+
delta.os = resultSet;
|
|
374
|
+
changed = true;
|
|
375
|
+
}
|
|
376
|
+
const resultDeleted = [];
|
|
377
|
+
for (const id of prevResults.keys()) {
|
|
378
|
+
if (!observedToolResults.has(id)) resultDeleted.push(id);
|
|
379
|
+
}
|
|
380
|
+
if (resultDeleted.length > 0) {
|
|
381
|
+
delta.od = resultDeleted;
|
|
382
|
+
changed = true;
|
|
383
|
+
}
|
|
384
|
+
return {
|
|
385
|
+
changed,
|
|
386
|
+
delta,
|
|
387
|
+
cursor: {
|
|
388
|
+
responseStarted,
|
|
389
|
+
phase,
|
|
390
|
+
textEpoch,
|
|
391
|
+
textLen: partialAssistantContent.length,
|
|
392
|
+
reasoningEpoch,
|
|
393
|
+
reasoningLen: partialReasoningContent.length,
|
|
394
|
+
tombEpoch,
|
|
395
|
+
callsEpoch,
|
|
396
|
+
calls: new Map(observedToolCalls),
|
|
397
|
+
results: new Map(observedToolResults),
|
|
398
|
+
},
|
|
399
|
+
};
|
|
400
|
+
},
|
|
286
401
|
finalize({ turnOutgoing, currentUserContent, abortReason = null }) {
|
|
287
402
|
return finalizeInterruptedTurn({
|
|
288
403
|
turnOutgoing,
|
|
@@ -33,6 +33,7 @@ import { isContextOverflowError } from '../providers/retry-classifier.mjs';
|
|
|
33
33
|
import { traceAgentCompact, messagePrefixHash } from '../agent-trace.mjs';
|
|
34
34
|
import { invalidateProviderRequestToolsScope } from '../../../../session-runtime/provider-request-tools.mjs';
|
|
35
35
|
import { bumpUsageMetricsEpoch } from './manager.mjs';
|
|
36
|
+
import { resetReadStateAfterCompaction } from './read-dedup.mjs';
|
|
36
37
|
|
|
37
38
|
const RECOVERED_ERROR_MESSAGE_MAX_CHARS = 300;
|
|
38
39
|
const ANSI_ESCAPE_RE = /[\u001B\u009B][[\]()#;?]*(?:(?:[a-zA-Z\d]*(?:;[-a-zA-Z\d/#&.:=?%@~_]+)*)?\u0007|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\\dA-PR-TZcf-nq-uy=><~]))/g;
|
|
@@ -444,6 +445,7 @@ export async function runPreSendCompactPass(state) {
|
|
|
444
445
|
if (compactChanged) {
|
|
445
446
|
messages.length = 0;
|
|
446
447
|
messages.push(...compacted);
|
|
448
|
+
resetReadStateAfterCompaction(sessionId);
|
|
447
449
|
// This attempt's provider-tool scope was keyed to the old
|
|
448
450
|
// transcript shape. Invalidate it synchronously before any
|
|
449
451
|
// post-compact callback or subsequent async continuation.
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from './image-strip-recovery.mjs';
|
|
25
25
|
import { setTimeout as sleepMs } from 'timers/promises';
|
|
26
26
|
import { readStreamOutcome } from '../providers/lib/stream-outcome.mjs';
|
|
27
|
+
import { cloneProviderReplay } from '../providers/lib/provider-replay.mjs';
|
|
27
28
|
import { resolveWorkerCompactPolicy } from './loop/compact-policy.mjs';
|
|
28
29
|
import { agentContextOverflowError } from './loop/context-overflow.mjs';
|
|
29
30
|
import { estimateMessagesTokensSafe } from './loop/compact-debug.mjs';
|
|
@@ -262,6 +263,7 @@ export async function sendWithRecovery(ctx) {
|
|
|
262
263
|
&& outcome.toolCallsStarted !== true
|
|
263
264
|
&& outcome.sideEffectDispatched !== true
|
|
264
265
|
) {
|
|
266
|
+
const partialProviderReplay = cloneProviderReplay(sendErr.partialProviderReplay);
|
|
265
267
|
response = {
|
|
266
268
|
content: sendErr.partialContent,
|
|
267
269
|
model: sendErr.model || model,
|
|
@@ -269,6 +271,7 @@ export async function sendWithRecovery(ctx) {
|
|
|
269
271
|
usage: normalizedIncompleteUsage(sendErr.rawUsage),
|
|
270
272
|
stopReason: sendErr.finishReason,
|
|
271
273
|
truncated: true,
|
|
274
|
+
...(partialProviderReplay ? { providerReplay: partialProviderReplay } : {}),
|
|
272
275
|
providerMetadata: sendErr.providerMetadata,
|
|
273
276
|
providerState: opts.providerState,
|
|
274
277
|
providerIncompleteRecovery: true,
|
|
@@ -393,6 +396,7 @@ export async function sendWithRecovery(ctx) {
|
|
|
393
396
|
partialContentLen: typeof sendErr.partialContent === 'string' ? sendErr.partialContent.length : 0,
|
|
394
397
|
});
|
|
395
398
|
} catch { /* best-effort */ }
|
|
399
|
+
const partialProviderReplay = cloneProviderReplay(sendErr.partialProviderReplay);
|
|
396
400
|
response = {
|
|
397
401
|
content: typeof sendErr.partialContent === 'string' ? sendErr.partialContent : '',
|
|
398
402
|
model: sendErr.partialModel || model,
|
|
@@ -416,6 +420,9 @@ export async function sendWithRecovery(ctx) {
|
|
|
416
420
|
...(Array.isArray(sendErr.partialAssistantBlocks) && sendErr.partialAssistantBlocks.length
|
|
417
421
|
? { assistantBlocks: sendErr.partialAssistantBlocks }
|
|
418
422
|
: {}),
|
|
423
|
+
...(partialProviderReplay
|
|
424
|
+
? { providerReplay: partialProviderReplay }
|
|
425
|
+
: {}),
|
|
419
426
|
providerMetadata: sendErr.providerMetadata,
|
|
420
427
|
partialToolRecovery: true,
|
|
421
428
|
};
|
|
@@ -95,6 +95,7 @@ export { parkSessionSnapshotForDrain, unparkSessionSnapshotForDrain } from './st
|
|
|
95
95
|
// no timer retry and no exit-drain entry survives the unlink. Layering stays
|
|
96
96
|
// intact: the store never imports manager code.
|
|
97
97
|
const _sessionPurgeHooks = new Set();
|
|
98
|
+
const _liveSessionSubscribers = new Set();
|
|
98
99
|
|
|
99
100
|
export function registerSessionPurgeHook(hook) {
|
|
100
101
|
if (typeof hook !== 'function') return () => {};
|
|
@@ -102,6 +103,24 @@ export function registerSessionPurgeHook(hook) {
|
|
|
102
103
|
return () => _sessionPurgeHooks.delete(hook);
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
/** In-process session publication seam.
|
|
107
|
+
*
|
|
108
|
+
* The runtime worker uses this to project agent sessions through the daemon's
|
|
109
|
+
* ordinary session-state lane. Persistence remains independent: subscribers
|
|
110
|
+
* observe the same immutable session object that was admitted to the live
|
|
111
|
+
* cache, before disk debounce or I/O can delay a visible pane. */
|
|
112
|
+
export function subscribeLiveSessions(listener) {
|
|
113
|
+
if (typeof listener !== 'function') return () => {};
|
|
114
|
+
_liveSessionSubscribers.add(listener);
|
|
115
|
+
return () => _liveSessionSubscribers.delete(listener);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function _publishLiveSession(session) {
|
|
119
|
+
for (const listener of [..._liveSessionSubscribers]) {
|
|
120
|
+
try { listener(session); } catch { /* observers never affect persistence */ }
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
105
124
|
function _runSessionPurgeHooks(id) {
|
|
106
125
|
for (const hook of _sessionPurgeHooks) {
|
|
107
126
|
try { hook(id); } catch { /* a cleanup hook never breaks delete */ }
|
|
@@ -318,7 +337,10 @@ export function saveSession(session, opts) {
|
|
|
318
337
|
// summary row. The write itself still travels the normal path, where the
|
|
319
338
|
// strict under-lock admission refuses it and produces the established
|
|
320
339
|
// outcome/markers — only the local "owned" caches are never touched.
|
|
321
|
-
if (!refusal)
|
|
340
|
+
if (!refusal) {
|
|
341
|
+
setLiveSession(session);
|
|
342
|
+
_publishLiveSession(session);
|
|
343
|
+
}
|
|
322
344
|
const summaryVersion = refusal ? null : _cacheSessionSummary(session);
|
|
323
345
|
// Identity of THIS attempt: only a landed save at least this new may clear
|
|
324
346
|
// the id's failure/drop markers (see live-state _clearSaveStateIfCurrent),
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import test from 'node:test';
|
|
3
|
+
|
|
4
|
+
import { buildShellSpawnEnv } from './bash-tool.mjs';
|
|
5
|
+
|
|
6
|
+
test('shell child receives the caller session cwd without mutating the daemon env', () => {
|
|
7
|
+
const base = {
|
|
8
|
+
PATH: process.env.PATH || '',
|
|
9
|
+
MIXDOG_SESSION_CWD: 'C:\\wrong-session',
|
|
10
|
+
};
|
|
11
|
+
const env = buildShellSpawnEnv('C:\\Project\\mixdog', base);
|
|
12
|
+
assert.equal(env.MIXDOG_SESSION_CWD, 'C:\\Project\\mixdog');
|
|
13
|
+
assert.equal(base.MIXDOG_SESSION_CWD, 'C:\\wrong-session');
|
|
14
|
+
});
|
|
@@ -401,6 +401,24 @@ const ARTIFACT_RETRY_ATTEMPTS = 10;
|
|
|
401
401
|
const PENDING_ARTIFACT_PATHS = new Set();
|
|
402
402
|
let artifactExitHookInstalled = false;
|
|
403
403
|
|
|
404
|
+
export function buildShellSpawnEnv(cwd, baseEnv = process.env) {
|
|
405
|
+
const spawnEnv = {
|
|
406
|
+
...baseEnv,
|
|
407
|
+
LANG: 'C.UTF-8',
|
|
408
|
+
LC_ALL: 'C.UTF-8',
|
|
409
|
+
MIXDOG_SESSION_CWD: String(cwd || ''),
|
|
410
|
+
};
|
|
411
|
+
if (process.platform === 'win32') {
|
|
412
|
+
if (spawnEnv.PYTHONUTF8 === undefined) spawnEnv.PYTHONUTF8 = '1';
|
|
413
|
+
if (spawnEnv.PYTHONIOENCODING === undefined) spawnEnv.PYTHONIOENCODING = 'utf-8';
|
|
414
|
+
}
|
|
415
|
+
scrubProviderSecrets(spawnEnv);
|
|
416
|
+
scrubLoaderVars(spawnEnv);
|
|
417
|
+
scrubRuntimeRootVars(spawnEnv);
|
|
418
|
+
applyShellEgressPolicy(spawnEnv);
|
|
419
|
+
return spawnEnv;
|
|
420
|
+
}
|
|
421
|
+
|
|
404
422
|
function removeTransportFile(file) {
|
|
405
423
|
try {
|
|
406
424
|
unlinkSync(file);
|
|
@@ -661,16 +679,7 @@ export async function executeBashTool(args, workDir, options = {}) {
|
|
|
661
679
|
|
|
662
680
|
try {
|
|
663
681
|
const { shell, shellArg, shellArgs, shellType } = resolvedSpec;
|
|
664
|
-
const spawnEnv =
|
|
665
|
-
if (process.platform === 'win32') {
|
|
666
|
-
if (spawnEnv.PYTHONUTF8 === undefined) spawnEnv.PYTHONUTF8 = '1';
|
|
667
|
-
if (spawnEnv.PYTHONIOENCODING === undefined) spawnEnv.PYTHONIOENCODING = 'utf-8';
|
|
668
|
-
}
|
|
669
|
-
// R5/R11: same scrub as background/persistent spawn sites (env-scrub.mjs).
|
|
670
|
-
scrubProviderSecrets(spawnEnv);
|
|
671
|
-
scrubLoaderVars(spawnEnv);
|
|
672
|
-
scrubRuntimeRootVars(spawnEnv);
|
|
673
|
-
applyShellEgressPolicy(spawnEnv);
|
|
682
|
+
const spawnEnv = buildShellSpawnEnv(bashWorkDir);
|
|
674
683
|
let wrappedCommand;
|
|
675
684
|
let execScript = null;
|
|
676
685
|
let _teePlan = null;
|