mixdog 0.9.87 → 0.9.89
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/package.json +13 -7
- package/scripts/tool-failures.mjs +60 -24
- package/src/agents/heavy-worker/AGENT.md +3 -4
- package/src/agents/worker/AGENT.md +1 -2
- package/src/cli.mjs +8 -0
- package/src/defaults/cycle3-review-prompt.md +4 -4
- package/src/rules/agent/00-core.md +3 -0
- package/src/rules/agent/42-cycle3-agent.md +5 -6
- package/src/rules/lead/01-general.md +2 -0
- package/src/rules/shared/01-tool.md +29 -35
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +3 -51
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +59 -0
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +2 -2
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +67 -0
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +57 -4
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +2 -3
- package/src/runtime/agent/orchestrator/config.mjs +29 -11
- package/src/runtime/agent/orchestrator/context/collect.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-agents.mjs +1 -1
- package/src/runtime/agent/orchestrator/mcp/client.mjs +0 -24
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +25 -17
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +321 -22
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +35 -22
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +28 -23
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +10 -2
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +0 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-tokens.mjs +0 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +2 -5
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-native-blocks.mjs +27 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/lib/stream-outcome.mjs +329 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +23 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +107 -20
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +4 -3
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +124 -12
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +19 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +110 -75
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +96 -113
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +160 -11
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +18 -1
- package/src/runtime/agent/orchestrator/session/lifecycle-scan.mjs +147 -117
- package/src/runtime/agent/orchestrator/session/loop/stop-hooks.mjs +88 -0
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +50 -8
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +25 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +47 -6
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +485 -24
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +100 -2
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +80 -14
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +142 -2
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +128 -32
- package/src/runtime/agent/orchestrator/session/manager.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/save-session-worker.mjs +77 -4
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +70 -39
- package/src/runtime/agent/orchestrator/session/store/fs-probe.mjs +91 -0
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +141 -64
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +253 -0
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +211 -28
- package/src/runtime/agent/orchestrator/session/store/save-fault.mjs +313 -0
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +622 -53
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +43 -10
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +53 -8
- package/src/runtime/agent/orchestrator/session/store/summary-rebuild-worker.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/store/write-guards.mjs +113 -9
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +476 -52
- package/src/runtime/agent/orchestrator/session/store.mjs +721 -196
- package/src/runtime/agent/orchestrator/session/token-native.mjs +2 -4
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +4 -6
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +146 -90
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +6 -0
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +93 -17
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +5 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +7 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +85 -5
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +52 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +54 -51
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +14 -15
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +132 -8
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +86 -3
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +210 -38
- package/src/runtime/agent/orchestrator/tools/patch/paths.mjs +23 -1
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +87 -19
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -8
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +12 -2
- package/src/runtime/media/lanes.mjs +2 -2
- package/src/runtime/media/renditions.mjs +101 -17
- package/src/runtime/media/renditions.test.mjs +54 -0
- package/src/runtime/media/store.mjs +111 -43
- package/src/runtime/media/store.test.mjs +53 -11
- package/src/runtime/memory/index.mjs +26 -50
- package/src/runtime/memory/lib/core-memory-candidates.mjs +2 -7
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -9
- package/src/runtime/memory/lib/cycle-scheduler.mjs +10 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +3 -3
- package/src/runtime/memory/lib/embedding-worker.mjs +60 -15
- package/src/runtime/memory/lib/http-router.mjs +4 -0
- package/src/runtime/memory/lib/ko-morph.mjs +49 -6
- package/src/runtime/memory/lib/memory-action-handlers.mjs +21 -18
- package/src/runtime/memory/lib/memory-cycle2.mjs +3 -3
- package/src/runtime/memory/lib/memory-cycle3.mjs +1 -3
- package/src/runtime/memory/lib/memory-recall-store.mjs +24 -0
- package/src/runtime/memory/lib/query-handlers.mjs +26 -28
- package/src/runtime/memory/tool-defs.mjs +3 -3
- package/src/runtime/shared/atomic-file.mjs +5 -1
- package/src/runtime/shared/child-guardian.mjs +73 -2
- package/src/runtime/shared/provider-api-key.mjs +22 -5
- package/src/runtime/shared/tool-execution-contract.mjs +53 -0
- package/src/session-runtime/config-helpers.mjs +6 -5
- package/src/session-runtime/lifecycle-api.mjs +16 -4
- package/src/session-runtime/media-api.mjs +31 -16
- package/src/session-runtime/prewarm.mjs +2 -24
- package/src/session-runtime/runtime-core.mjs +33 -15
- package/src/session-runtime/runtime-tunables.mjs +0 -3
- package/src/session-runtime/session-lifecycle.mjs +0 -6
- package/src/session-runtime/session-title.mjs +228 -0
- package/src/session-runtime/session-turn-api.mjs +11 -7
- package/src/session-runtime/workflow-agents-api.mjs +16 -0
- package/src/session-runtime/workflow.mjs +7 -5
- package/src/standalone/agent-tool/job-views.mjs +90 -36
- package/src/standalone/agent-tool/spawn-flow.mjs +25 -216
- package/src/standalone/agent-tool/worker-index.mjs +8 -0
- package/src/standalone/agent-tool/worker-rows.mjs +2 -1
- package/src/standalone/agent-tool.mjs +17 -17
- package/src/standalone/agent-watchdog-registry.mjs +19 -2
- package/src/standalone/channel-daemon.mjs +8 -0
- package/src/standalone/explore-tool.mjs +1 -1
- package/src/standalone/memory-runtime-proxy.mjs +4 -7
- package/src/standalone/projects.mjs +4 -1
- package/src/standalone/usage-dashboard.mjs +25 -3
- package/src/tui/App.jsx +5 -3
- package/src/tui/app/resume-picker.mjs +7 -2
- package/src/tui/app/route-pickers.mjs +11 -117
- package/src/tui/app/slash-dispatch.mjs +13 -3
- package/src/tui/app/transcript-row-estimate.mjs +1 -1
- package/src/tui/app/transcript-window.mjs +17 -93
- package/src/tui/app/use-transcript-scroll.mjs +32 -2
- package/src/tui/app/use-transcript-window.mjs +64 -114
- package/src/tui/components/Markdown.jsx +19 -2
- package/src/tui/components/Message.jsx +8 -3
- package/src/tui/components/ToolExecution.jsx +7 -3
- package/src/tui/dist/index.mjs +243 -223
- package/src/tui/engine/live-share.mjs +15 -5
- package/src/tui/engine/render-timing.mjs +2 -2
- package/src/tui/engine/session-api-ext.mjs +74 -4
- package/src/tui/engine/session-flow.mjs +3 -1
- package/src/tui/engine.mjs +2 -2
- package/src/tui/hooks/useSharedTick.mjs +0 -2
- package/src/tui/index.jsx +5 -5
- package/src/ui/statusline-agents.mjs +44 -11
- package/src/vendor/statusline/bin/statusline-route.mjs +26 -3
- package/src/workflows/default/WORKFLOW.md +3 -3
- package/src/workflows/solo/WORKFLOW.md +5 -3
- package/src/runtime/media/index.mjs +0 -18
- package/src/runtime/memory/lib/embedding-warmup.mjs +0 -68
- package/src/standalone/agent-shard/shard-child.mjs +0 -300
- package/src/standalone/agent-shard/shard-pool.mjs +0 -443
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// Codex-style turn stop hook (refs/codex core/src/session/turn.rs:372-404).
|
|
2
|
+
//
|
|
3
|
+
// Codex semantics: a sampling request that needs no follow-up ends the turn.
|
|
4
|
+
// Before breaking, the turn runs its stop hooks; a hook may block ONCE by
|
|
5
|
+
// returning continuation fragments, which are recorded as a prompt message
|
|
6
|
+
// before sampling resumes (`stop_hook_active = true`, never cleared inside the
|
|
7
|
+
// turn). Nothing else can force another turn: no mandatory terminal tool, no
|
|
8
|
+
// completion strikes, no lexical inspection of the assistant text.
|
|
9
|
+
//
|
|
10
|
+
// The single hook implemented here preserves the original failed-apply_patch
|
|
11
|
+
// protection: while a real tool failure is unresolved, the first terminal
|
|
12
|
+
// assistant message is blocked once with a structural continuation prompt.
|
|
13
|
+
// Afterwards ordinary final text (or further tool calls) is accepted.
|
|
14
|
+
|
|
15
|
+
export const STOP_HOOK_SOURCE = 'tool-failure-stop-hook';
|
|
16
|
+
|
|
17
|
+
// Only a genuinely EXECUTED result resolves a failure, i.e. kind 'normal'.
|
|
18
|
+
// Cache hits ('cache-hit' / 'scoped-cache-hit') replay an earlier result
|
|
19
|
+
// without running anything, and dedup/guard skips ('skipped') execute nothing
|
|
20
|
+
// either, so both are neutral: they neither arm nor clear the hook.
|
|
21
|
+
const EXECUTED_SUCCESS_TOOL_KINDS = new Set(['normal']);
|
|
22
|
+
|
|
23
|
+
function bareToolName(name) {
|
|
24
|
+
const raw = String(name || '');
|
|
25
|
+
return raw.startsWith('mcp__') ? raw.split('__').pop() : raw;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function toolFailureContinuationPrompt(failedTool) {
|
|
29
|
+
const which = failedTool ? `\`${failedTool}\`` : 'a tool call';
|
|
30
|
+
return `[mixdog-runtime] Stop hook: ${which} failed and no tool call has succeeded since.`
|
|
31
|
+
+ ' Re-run the fixed action (or a verifying tool) if it is recoverable; otherwise say in your'
|
|
32
|
+
+ ' final message that it stays unresolved and why. This hook fires once — your next message ends the turn.';
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Structural unresolved-tool-failure stop hook.
|
|
37
|
+
* - observeToolResult(message) / endBatch(calls): a batch containing any
|
|
38
|
+
* failed tool result arms it; a later batch with a genuinely executed
|
|
39
|
+
* success clears it.
|
|
40
|
+
* - takeContinuationPrompt(): returns the continuation prompt the FIRST time
|
|
41
|
+
* a terminal assistant message arrives while a failure is unresolved, then
|
|
42
|
+
* stays silent for the rest of the turn (Codex `stop_hook_active`).
|
|
43
|
+
*/
|
|
44
|
+
export function createToolFailureStopHook() {
|
|
45
|
+
let unresolvedFailure = false;
|
|
46
|
+
let lastFailedTool = null;
|
|
47
|
+
let batchFailure = false;
|
|
48
|
+
let batchSuccess = false;
|
|
49
|
+
let failedCallIds = new Set();
|
|
50
|
+
let active = false;
|
|
51
|
+
return {
|
|
52
|
+
get unresolvedFailure() { return unresolvedFailure; },
|
|
53
|
+
get lastFailedTool() { return lastFailedTool; },
|
|
54
|
+
get active() { return active; },
|
|
55
|
+
observeToolResult(message) {
|
|
56
|
+
if (!message || message.role !== 'tool') return;
|
|
57
|
+
// Guard skips (repeat-failure guard) are tagged as errors so every
|
|
58
|
+
// downstream consumer keeps treating the call as unresolved, but
|
|
59
|
+
// nothing was dispatched — they must not arm the hook.
|
|
60
|
+
if (message.guardSkip === true) return;
|
|
61
|
+
if (message.toolKind === 'error') {
|
|
62
|
+
batchFailure = true;
|
|
63
|
+
if (message.toolCallId) failedCallIds.add(message.toolCallId);
|
|
64
|
+
} else if (EXECUTED_SUCCESS_TOOL_KINDS.has(message.toolKind)) {
|
|
65
|
+
batchSuccess = true;
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
endBatch(calls) {
|
|
69
|
+
if (batchFailure) {
|
|
70
|
+
unresolvedFailure = true;
|
|
71
|
+
const failed = (Array.isArray(calls) ? calls : [])
|
|
72
|
+
.find((call) => call?.id && failedCallIds.has(call.id));
|
|
73
|
+
if (failed) lastFailedTool = bareToolName(failed.name);
|
|
74
|
+
} else if (batchSuccess) {
|
|
75
|
+
unresolvedFailure = false;
|
|
76
|
+
lastFailedTool = null;
|
|
77
|
+
}
|
|
78
|
+
batchFailure = false;
|
|
79
|
+
batchSuccess = false;
|
|
80
|
+
failedCallIds = new Set();
|
|
81
|
+
},
|
|
82
|
+
takeContinuationPrompt() {
|
|
83
|
+
if (active || !unresolvedFailure) return null;
|
|
84
|
+
active = true;
|
|
85
|
+
return toolFailureContinuationPrompt(lastFailedTool);
|
|
86
|
+
},
|
|
87
|
+
};
|
|
88
|
+
}
|
|
@@ -5,6 +5,14 @@
|
|
|
5
5
|
// patch text is the model's own draft, and leaving only the marker made models
|
|
6
6
|
// copy `[mixdog compacted …]` back as literal patch input. Bodies of calls that
|
|
7
7
|
// did NOT fail stay compacted so an already-applied patch cannot be replayed.
|
|
8
|
+
//
|
|
9
|
+
// Mutation bodies additionally get ONE turn of grace: the push that commits a
|
|
10
|
+
// tool-call turn defers body compaction (deferBodies), so the send that
|
|
11
|
+
// immediately follows a successful patch still shows the model its own patch
|
|
12
|
+
// verbatim. compactSettledToolCallBodies then collapses settled successful
|
|
13
|
+
// bodies to markers on the next push / loop entry. This closes the observed
|
|
14
|
+
// failure where a model writing a follow-up patch right after a success copies
|
|
15
|
+
// the marker it was just shown as literal patch input.
|
|
8
16
|
import { createHash } from 'crypto';
|
|
9
17
|
|
|
10
18
|
const STORED_TOOL_ARG_BODY_KEY_RE = /^(?:content|old_string|new_string|patch|rewrite)$/i;
|
|
@@ -20,14 +28,20 @@ const STORED_TOOL_ARG_LIMIT = 10_000;
|
|
|
20
28
|
const STORED_TOOL_ARG_PREVIEW_HEAD = 360;
|
|
21
29
|
const STORED_TOOL_ARG_PREVIEW_TAIL = 160;
|
|
22
30
|
|
|
23
|
-
function compactStoredToolArgString(value, key = '') {
|
|
31
|
+
function compactStoredToolArgString(value, key = '', opts = {}) {
|
|
24
32
|
if (typeof value !== 'string') return value;
|
|
25
33
|
const isBody = STORED_TOOL_ARG_BODY_KEY_RE.test(key);
|
|
34
|
+
if (isBody && opts.deferBodies === true) return value;
|
|
26
35
|
const isLong = isBody || STORED_TOOL_ARG_LONG_KEY_RE.test(key);
|
|
27
36
|
const limit = isLong ? STORED_TOOL_ARG_LIMIT : Infinity;
|
|
28
37
|
if (value.length <= limit) return value;
|
|
29
38
|
const hash = createHash('sha256').update(value).digest('hex').slice(0, 16);
|
|
30
|
-
|
|
39
|
+
// Body markers carry the recovery instruction inline: the compaction
|
|
40
|
+
// detectors only require the `[mixdog compacted ...]` shape (no ']' or
|
|
41
|
+
// newline inside), so the longer text stays fully compatible.
|
|
42
|
+
const marker = isBody
|
|
43
|
+
? `[mixdog compacted ${key}: ${value.length} chars, sha256:${hash}; already applied - do not copy; re-read the file and write a fresh patch]`
|
|
44
|
+
: `[mixdog compacted ${key || 'string'}: ${value.length} chars, sha256:${hash}; do not copy]`;
|
|
31
45
|
// Body args (patch / old_string / new_string / content / rewrite) are
|
|
32
46
|
// apply_patch / edit inputs. Keeping a head/tail preview leaves real patch
|
|
33
47
|
// fragments (a "*** Begin Patch" opening, diff lines) inside a SUCCESSFUL
|
|
@@ -40,32 +54,60 @@ function compactStoredToolArgString(value, key = '') {
|
|
|
40
54
|
return `${marker}\n${head}\n... [middle omitted from stored tool-call args] ...\n${tail}`;
|
|
41
55
|
}
|
|
42
56
|
|
|
43
|
-
function compactStoredToolArgValue(value, key = '', depth = 0) {
|
|
57
|
+
function compactStoredToolArgValue(value, key = '', depth = 0, opts = {}) {
|
|
44
58
|
if (value === null || value === undefined) return value;
|
|
45
|
-
if (typeof value === 'string') return compactStoredToolArgString(value, key);
|
|
59
|
+
if (typeof value === 'string') return compactStoredToolArgString(value, key, opts);
|
|
46
60
|
if (typeof value !== 'object') return value;
|
|
47
61
|
if (depth >= 6) return Array.isArray(value) ? `[${value.length} items]` : '{...}';
|
|
48
62
|
if (Array.isArray(value)) {
|
|
49
|
-
return value.map((item) => compactStoredToolArgValue(item, key, depth + 1));
|
|
63
|
+
return value.map((item) => compactStoredToolArgValue(item, key, depth + 1, opts));
|
|
50
64
|
}
|
|
51
65
|
const out = {};
|
|
52
66
|
for (const [k, v] of Object.entries(value)) {
|
|
53
|
-
out[k] = compactStoredToolArgValue(v, k, depth + 1);
|
|
67
|
+
out[k] = compactStoredToolArgValue(v, k, depth + 1, opts);
|
|
54
68
|
}
|
|
55
69
|
return out;
|
|
56
70
|
}
|
|
57
71
|
|
|
58
|
-
export function compactToolCallsForHistory(calls) {
|
|
72
|
+
export function compactToolCallsForHistory(calls, opts = {}) {
|
|
59
73
|
if (!Array.isArray(calls)) return calls;
|
|
60
74
|
return calls.map((call) => {
|
|
61
75
|
if (!call || typeof call !== 'object') return call;
|
|
62
76
|
return {
|
|
63
77
|
...call,
|
|
64
|
-
arguments: compactStoredToolArgValue(call.arguments),
|
|
78
|
+
arguments: compactStoredToolArgValue(call.arguments, '', 0, opts),
|
|
65
79
|
};
|
|
66
80
|
});
|
|
67
81
|
}
|
|
68
82
|
|
|
83
|
+
// Collapse the body args of every SETTLED, non-failed tool call in history to
|
|
84
|
+
// their compacted markers. Runs at loop entry and before each new assistant
|
|
85
|
+
// push, so a deferred verbatim body survives exactly the send(s) that follow
|
|
86
|
+
// its own turn and no longer. Contract guards:
|
|
87
|
+
// - a call whose result is an error keeps its full body (retry-safe, same
|
|
88
|
+
// contract as restoreToolCallBodyForId);
|
|
89
|
+
// - a call with no result row yet (current batch / interrupted turn) is
|
|
90
|
+
// left untouched.
|
|
91
|
+
export function compactSettledToolCallBodies(messages) {
|
|
92
|
+
if (!Array.isArray(messages)) return;
|
|
93
|
+
const resultKinds = new Map();
|
|
94
|
+
for (const message of messages) {
|
|
95
|
+
if (message?.role === 'tool' && message.toolCallId) {
|
|
96
|
+
resultKinds.set(message.toolCallId, message.toolKind || 'normal');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
for (const message of messages) {
|
|
100
|
+
if (message?.role !== 'assistant' || !Array.isArray(message.toolCalls)) continue;
|
|
101
|
+
for (const call of message.toolCalls) {
|
|
102
|
+
if (!call || typeof call !== 'object') continue;
|
|
103
|
+
if (!call.arguments || typeof call.arguments !== 'object') continue;
|
|
104
|
+
const kind = call.id ? resultKinds.get(call.id) : undefined;
|
|
105
|
+
if (kind === undefined || kind === 'error') continue;
|
|
106
|
+
call.arguments = compactStoredToolArgValue(call.arguments);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
69
111
|
// Restore retry-safe long command/script text for ONE failed tool call inside a
|
|
70
112
|
// history assistant message whose toolCalls were compacted at push time.
|
|
71
113
|
// Mutation bodies (patch, old_string, new_string, content, rewrite) are restored
|
|
@@ -24,6 +24,31 @@ export function isOutputLimitStopReason(reason) {
|
|
|
24
24
|
&& OUTPUT_LIMIT_STOP_REASONS.has(reason.trim().toLowerCase());
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// Provider-declared continuation signals. A response carrying one of these is
|
|
28
|
+
// NOT a completed assistant turn even when it holds final-looking text: the
|
|
29
|
+
// provider explicitly stated it intends to keep sampling inside the SAME user
|
|
30
|
+
// turn. Two structured shapes are honored:
|
|
31
|
+
// - `endTurn === false` (Codex/Responses `end_turn=false`; see
|
|
32
|
+
// codex-rs/core/src/session/turn.rs Completed{ end_turn } → needs_follow_up)
|
|
33
|
+
// - Anthropic `stop_reason: 'pause_turn'` (server-side pause; the turn is
|
|
34
|
+
// resumed by replaying the transcript unchanged)
|
|
35
|
+
// Structural only — no lexical/progress-text heuristic is consulted, and a
|
|
36
|
+
// provider that omits both fields is unaffected (returns null → terminal).
|
|
37
|
+
const CONTINUATION_STOP_REASONS = new Set(['pause_turn']);
|
|
38
|
+
|
|
39
|
+
export function providerContinuationSignal(response) {
|
|
40
|
+
if (!response || typeof response !== 'object') return null;
|
|
41
|
+
// Only an explicit boolean false continues; undefined/null/absent means
|
|
42
|
+
// "provider did not signal" and keeps legacy terminal semantics.
|
|
43
|
+
if (response.endTurn === false || response.end_turn === false) return 'end_turn_false';
|
|
44
|
+
const stopReason = response.stopReason ?? response.stop_reason ?? null;
|
|
45
|
+
if (typeof stopReason === 'string'
|
|
46
|
+
&& CONTINUATION_STOP_REASONS.has(stopReason.trim().toLowerCase())) {
|
|
47
|
+
return 'pause_turn';
|
|
48
|
+
}
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
27
52
|
// Classify WHY the loop ended so agent-tool can promote an empty/abnormal
|
|
28
53
|
// finish to an explicit Lead-facing error instead of a silent empty
|
|
29
54
|
// "completed". Determine "has content" exactly the way the no-tool-call
|
|
@@ -179,7 +179,7 @@ export async function executeTool(name, args, cwd, callerSessionId, sessionRef,
|
|
|
179
179
|
if (name === 'code_graph') {
|
|
180
180
|
// cwd chain: args.cwd (caller-explicit) → session cwd → undefined (handler throws)
|
|
181
181
|
const graphCwd = (typeof args?.cwd === 'string' && args.cwd.trim()) ? args.cwd.trim() : cwd;
|
|
182
|
-
return executeCodeGraphToolLazy(name, args, graphCwd, null, toolOpts);
|
|
182
|
+
return executeCodeGraphToolLazy(name, args, graphCwd, executeOpts.signal || null, toolOpts);
|
|
183
183
|
}
|
|
184
184
|
if (isInternalTool(name)) {
|
|
185
185
|
// callerSessionId propagates into server.mjs dispatchTool so that
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
// helper it used in manager.mjs is now imported from its split module.
|
|
6
6
|
import { createHash, randomUUID } from 'crypto';
|
|
7
7
|
import { getProvider } from '../../providers/registry.mjs';
|
|
8
|
+
import { readStreamOutcome } from '../../providers/lib/stream-outcome.mjs';
|
|
8
9
|
import { normalizeCompactType, DEFAULT_COMPACT_TYPE } from '../compact.mjs';
|
|
9
10
|
import { loadSession, saveSession, saveSessionAsync, saveSessionAsyncDeferred, readSessionLifecycleFromDisk } from '../store.mjs';
|
|
10
11
|
import { createAbortController } from '../../../../shared/abort-controller.mjs';
|
|
@@ -57,6 +58,7 @@ import {
|
|
|
57
58
|
} from './message-sanitize.mjs';
|
|
58
59
|
import { createTurnInterruptionTracker } from './turn-interruption.mjs';
|
|
59
60
|
import {
|
|
61
|
+
cancelPendingTurnCheckpoint,
|
|
60
62
|
clearTurnCheckpoint,
|
|
61
63
|
recoverTurnCheckpoint,
|
|
62
64
|
turnMessagesForCheckpoint,
|
|
@@ -342,6 +344,13 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
|
|
|
342
344
|
currentUserContent: cancelledUserTurnContent,
|
|
343
345
|
turnMessages: turnMessagesForCheckpoint(_turnOutgoing, cancelledUserTurnContent),
|
|
344
346
|
interruption: _turnInterruption.snapshot(),
|
|
347
|
+
}, {
|
|
348
|
+
// The turn's first checkpoint is the crash-durability
|
|
349
|
+
// anchor for the prompt and must land before the provider
|
|
350
|
+
// runs; every later streaming/tool-boundary flush moves to
|
|
351
|
+
// the async latest-wins lane so serialization+disk never
|
|
352
|
+
// stall the engine thread mid-stream.
|
|
353
|
+
sync: _turnCheckpointLastAt === 0,
|
|
345
354
|
});
|
|
346
355
|
if (written) _turnCheckpointLastAt = Date.now();
|
|
347
356
|
return written;
|
|
@@ -373,6 +382,7 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
|
|
|
373
382
|
_turnCheckpointStopped = true;
|
|
374
383
|
if (_turnCheckpointTimer) clearTimeout(_turnCheckpointTimer);
|
|
375
384
|
_turnCheckpointTimer = null;
|
|
385
|
+
cancelPendingTurnCheckpoint(sessionId);
|
|
376
386
|
};
|
|
377
387
|
const _sessionStartMetaInjectedBeforeTurn = preSession.sessionStartMetaInjected === true;
|
|
378
388
|
let _interruptionSnapshot = null;
|
|
@@ -679,11 +689,31 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
|
|
|
679
689
|
if (Array.isArray(drained)) out.push(...drained);
|
|
680
690
|
} catch { /* best-effort steering drain */ }
|
|
681
691
|
}
|
|
682
|
-
//
|
|
683
|
-
//
|
|
684
|
-
//
|
|
685
|
-
// queued_command filters.
|
|
686
|
-
//
|
|
692
|
+
// Manager/pending-messages entries carry no
|
|
693
|
+
// mode/priority/slash metadata, so they stay OUT of the
|
|
694
|
+
// mid-chain (post-tool-batch) drain — that would bypass
|
|
695
|
+
// Claude Code's queued_command filters. At the TERMINAL
|
|
696
|
+
// boundary they are exactly Codex `has_pending_input`
|
|
697
|
+
// (turn.rs:304-318): an `agent type=send` queued while
|
|
698
|
+
// the terminal sample was in flight must be folded into
|
|
699
|
+
// THIS turn before any stop hook runs, instead of losing
|
|
700
|
+
// its slot to a synthetic continuation prompt. The mutex
|
|
701
|
+
// is held for the whole ask, so this drain races nothing.
|
|
702
|
+
// Entries consumed here join _turnPendingEntries: their
|
|
703
|
+
// delivery/ack (and release on failure) rides this turn,
|
|
704
|
+
// and the post-loop drain can no longer see them.
|
|
705
|
+
if (drainOptions?.stage === 'terminal') {
|
|
706
|
+
const _pendingNow = drainPendingMessages(sessionId);
|
|
707
|
+
if (_pendingNow.length > 0) {
|
|
708
|
+
const _mergedNow = _mergePendingMessageEntries(_pendingNow);
|
|
709
|
+
if (_mergedNow?.content) {
|
|
710
|
+
_turnPendingEntries.push(..._pendingNow);
|
|
711
|
+
out.push(_mergedNow);
|
|
712
|
+
} else {
|
|
713
|
+
releasePendingMessages(sessionId, _pendingNow);
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
687
717
|
return out;
|
|
688
718
|
},
|
|
689
719
|
onSteerMessage: (text, detail) => {
|
|
@@ -1029,8 +1059,19 @@ export async function askSession(sessionId, prompt, context, onToolCall, cwdOver
|
|
|
1029
1059
|
// non-streaming request starts. If that restart fails, restore the
|
|
1030
1060
|
// tombstone and persist the one exposed partial as interruption
|
|
1031
1061
|
// history. Failed/absent acknowledgements never tombstoned it.
|
|
1062
|
+
// Provider-specific legacy flags are NOT consulted here: the
|
|
1063
|
+
// canonical stream-outcome contract answers "did the provider
|
|
1064
|
+
// produce output we must commit?" for every transport. Anthropic's
|
|
1065
|
+
// stall/truncation errors carry neither liveTextEmitted nor
|
|
1066
|
+
// unsafeToRetry — only partialContent/partialToolCalls — and used
|
|
1067
|
+
// to drop the streamed summary from history on that provider.
|
|
1068
|
+
const _providerOutcome = readStreamOutcome(err);
|
|
1032
1069
|
const preserveProviderPartial = restoredResetText
|
|
1033
|
-
||
|
|
1070
|
+
|| _providerOutcome.observedOutput === true
|
|
1071
|
+
// Positive exposure evidence only: an error with no observed
|
|
1072
|
+
// output must not be mistaken for a turn that produced
|
|
1073
|
+
// provider output.
|
|
1074
|
+
|| _providerOutcome.replayUnsafe === true
|
|
1034
1075
|
|| err?.unsafeToRetry === true;
|
|
1035
1076
|
let _errorStateDurable = false;
|
|
1036
1077
|
if (preserveProviderPartial && activeSession && _turnInterruption.hasResponseStarted()) {
|