mixdog 0.9.38 → 0.9.40
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 +9 -4
- package/scripts/abort-recovery-test.mjs +43 -2
- package/scripts/agent-tag-reuse-smoke.mjs +146 -6
- package/scripts/agent-terminal-reap-test.mjs +127 -0
- package/scripts/agent-trace-io-test.mjs +69 -0
- package/scripts/code-graph-disk-hit-test.mjs +224 -0
- package/scripts/execution-completion-dedup-test.mjs +157 -0
- package/scripts/execution-pending-resume-kick-test.mjs +57 -2
- package/scripts/execution-resume-esc-integration-test.mjs +176 -0
- package/scripts/explore-bench.mjs +38 -2
- package/scripts/explore-prompt-policy-test.mjs +152 -11
- package/scripts/find-fuzzy-hidden-test.mjs +122 -0
- package/scripts/internal-comms-bench-test.mjs +226 -0
- package/scripts/internal-comms-bench.mjs +185 -58
- package/scripts/internal-comms-smoke.mjs +171 -23
- package/scripts/live-worker-smoke.mjs +38 -2
- package/scripts/memory-cycle-routing-test.mjs +111 -0
- package/scripts/memory-rule-contract-test.mjs +93 -0
- package/scripts/output-style-smoke.mjs +2 -2
- package/scripts/rg-runner-test.mjs +240 -0
- package/scripts/routing-corpus-test.mjs +349 -0
- package/scripts/routing-corpus.mjs +211 -32
- package/scripts/session-orphan-sweep-test.mjs +83 -0
- package/scripts/steering-drain-buckets-test.mjs +316 -2
- package/scripts/tool-smoke.mjs +21 -13
- package/scripts/tool-tui-presentation-test.mjs +202 -0
- package/scripts/tui-transcript-perf-test.mjs +279 -0
- package/src/agents/heavy-worker/AGENT.md +10 -7
- package/src/agents/reviewer/AGENT.md +6 -4
- package/src/agents/worker/AGENT.md +7 -5
- package/src/rules/agent/00-common.md +4 -4
- package/src/rules/agent/00-core.md +11 -14
- package/src/rules/agent/20-skip-protocol.md +3 -3
- package/src/rules/agent/30-explorer.md +50 -60
- package/src/rules/agent/40-cycle1-agent.md +15 -24
- package/src/rules/agent/41-cycle2-agent.md +33 -57
- package/src/rules/agent/42-cycle3-agent.md +28 -42
- package/src/rules/lead/01-general.md +7 -10
- package/src/rules/lead/lead-brief.md +11 -14
- package/src/rules/lead/lead-tool.md +6 -5
- package/src/rules/shared/01-tool.md +44 -45
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +44 -1
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +20 -5
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +21 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +17 -38
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +17 -8
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +270 -78
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/delivered-completions.mjs +123 -0
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +15 -2
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +67 -2
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +232 -43
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +43 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +27 -13
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +25 -20
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +118 -53
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +106 -29
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-range-index.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-snapshot-runtime.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +151 -64
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +37 -13
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +84 -49
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +172 -23
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +3 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/disk-cache.mjs +68 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +17 -3
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +24 -10
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +4 -7
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -4
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +4 -3
- package/src/runtime/memory/lib/memory-cycle3.mjs +12 -2
- package/src/runtime/shared/buffered-appender.mjs +13 -2
- package/src/runtime/shared/tool-primitives.mjs +4 -1
- package/src/runtime/shared/tool-status.mjs +27 -0
- package/src/runtime/shared/tool-surface.mjs +6 -3
- package/src/session-runtime/config-helpers.mjs +14 -0
- package/src/session-runtime/context-status.mjs +1 -0
- package/src/session-runtime/effort.mjs +6 -2
- package/src/session-runtime/lifecycle-api.mjs +4 -0
- package/src/session-runtime/model-recency.mjs +5 -2
- package/src/session-runtime/runtime-core.mjs +36 -2
- package/src/session-runtime/session-turn-api.mjs +12 -0
- package/src/session-runtime/tool-catalog.mjs +34 -0
- package/src/standalone/agent-tool/notify.mjs +13 -0
- package/src/standalone/agent-tool.mjs +53 -70
- package/src/standalone/explore-tool.mjs +6 -7
- package/src/tui/App.jsx +33 -1
- package/src/tui/app/model-options.mjs +5 -3
- package/src/tui/app/model-picker.mjs +12 -24
- package/src/tui/app/transcript-window.mjs +10 -10
- package/src/tui/app/use-transcript-window.mjs +38 -56
- package/src/tui/components/ToolExecution.jsx +11 -6
- package/src/tui/components/TranscriptItem.jsx +1 -1
- package/src/tui/components/tool-execution/surface-detail.mjs +24 -10
- package/src/tui/components/tool-execution/text-format.mjs +10 -19
- package/src/tui/dist/index.mjs +866 -300
- package/src/tui/engine/agent-job-feed.mjs +216 -19
- package/src/tui/engine/agent-response-tail.mjs +68 -0
- package/src/tui/engine/notification-plan.mjs +16 -0
- package/src/tui/engine/session-api.mjs +14 -2
- package/src/tui/engine/session-flow.mjs +22 -2
- package/src/tui/engine/tool-card-results.mjs +64 -37
- package/src/tui/engine/tool-result-status.mjs +75 -21
- package/src/tui/engine/turn.mjs +172 -77
- package/src/tui/engine.mjs +199 -39
- package/src/tui/index.jsx +2 -2
- package/src/workflows/bench/WORKFLOW.md +25 -35
- package/src/workflows/default/WORKFLOW.md +38 -32
- package/src/workflows/solo/WORKFLOW.md +19 -22
- package/scripts/_jitter-fuzz.mjs +0 -44410
- package/scripts/_jitter-fuzz2.mjs +0 -44400
- package/scripts/_jitter-probe.mjs +0 -44397
- package/scripts/_jp2.mjs +0 -45614
|
@@ -10,17 +10,18 @@
|
|
|
10
10
|
* the factory argument (getters/callbacks) — never stale snapshots. Every body
|
|
11
11
|
* is the original engine.mjs logic verbatim.
|
|
12
12
|
*/
|
|
13
|
-
import { summarizeToolResult, aggregateDoneCategories
|
|
13
|
+
import { summarizeToolResult, aggregateDoneCategories } from '../../runtime/shared/tool-surface.mjs';
|
|
14
14
|
import { toolResultText, toolErrorDisplay, toolGroupedDisplayFallback } from './tool-result-text.mjs';
|
|
15
15
|
import { toolResultCallId } from './tool-call-fields.mjs';
|
|
16
|
-
import {
|
|
17
|
-
import { parseAgentJob, toolResultStatus, isErrorToolStatus } from './agent-envelope.mjs';
|
|
16
|
+
import { parseAgentJob } from './agent-envelope.mjs';
|
|
18
17
|
import {
|
|
19
18
|
withCancelledResultMarker,
|
|
20
19
|
groupedToolResultText,
|
|
21
20
|
aggregateRawResult,
|
|
22
21
|
aggregateSummaries,
|
|
23
22
|
assignAggregateSummaryOrder,
|
|
23
|
+
failureDetailText,
|
|
24
|
+
toolCallOutcome,
|
|
24
25
|
} from './tool-result-status.mjs';
|
|
25
26
|
import { formatAggregateDetail } from '../../runtime/shared/tool-surface.mjs';
|
|
26
27
|
import { carryTranscriptMeasuredRowsCache } from '../app/transcript-window.mjs';
|
|
@@ -33,12 +34,28 @@ export function createToolCardResults({
|
|
|
33
34
|
updateAgentJobCard,
|
|
34
35
|
buildAgentJobCardPatch,
|
|
35
36
|
agentStatusState,
|
|
37
|
+
itemIndexById,
|
|
36
38
|
}) {
|
|
39
|
+
const itemById = (id) => {
|
|
40
|
+
const index = itemIndexById?.get(id);
|
|
41
|
+
return Number.isInteger(index) ? getState().items[index]?.id === id ? getState().items[index] : null : null;
|
|
42
|
+
};
|
|
43
|
+
// A finalized/failed non-aggregate card must never carry an empty body:
|
|
44
|
+
// an empty-body error card is classified fully-failed-with-no-body upstream
|
|
45
|
+
// (transcript-tool-failures) and null-renders (card disappears). Stamp a
|
|
46
|
+
// minimal non-empty fallback, preferring meaningful text, then Exit N, then
|
|
47
|
+
// a bare Failed status.
|
|
48
|
+
function finalizedErrorFallbackBody(body, text, exitCode) {
|
|
49
|
+
if (String(body || '').trim()) return body;
|
|
50
|
+
if (String(text || '').trim()) return text;
|
|
51
|
+
if (exitCode != null) return `Exit ${exitCode}`;
|
|
52
|
+
return 'Failed';
|
|
53
|
+
}
|
|
37
54
|
function patchToolItem(id, patch) {
|
|
38
|
-
const prev =
|
|
55
|
+
const prev = itemById(id);
|
|
39
56
|
const ok = patchItem(id, patch);
|
|
40
57
|
if (!ok || !prev) return ok;
|
|
41
|
-
const next =
|
|
58
|
+
const next = itemById(id);
|
|
42
59
|
if (next && next !== prev) carryTranscriptMeasuredRowsCache(prev, next);
|
|
43
60
|
return ok;
|
|
44
61
|
}
|
|
@@ -60,22 +77,11 @@ export function createToolCardResults({
|
|
|
60
77
|
// Aggregate card handling — collect semantic summaries per call
|
|
61
78
|
const aggregate = card.aggregate;
|
|
62
79
|
const callRec = aggregate && callId ? aggregate.calls.get(callId) : null;
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
const isMemoryCall = classifyToolCategory(callRec?.name || card?.name || '', callRec?.args || {}) === 'Memory';
|
|
69
|
-
// Split the failure signal into two:
|
|
70
|
-
// isCallError — a REAL tool-call failure (backend isError / error
|
|
71
|
-
// toolKind). ONLY this paints the ● dot red.
|
|
72
|
-
// isResultError — a command/result failure (shell exit code, [error…]
|
|
73
|
-
// text, failed status text, flattened core memory-op
|
|
74
|
-
// failure). These still mark the card Failed in the L2
|
|
75
|
-
// detail but must NOT turn the dot red.
|
|
76
|
-
const isCallError = message?.isError === true || message?.toolKind === 'error';
|
|
77
|
-
const isResultError = /^\s*\[?error/i.test(rawText) || isErrorToolStatus(toolResultStatus(rawText)) || (isMemoryCall && memoryCoreResultErrorText(rawText) != null);
|
|
78
|
-
const isError = isCallError || isResultError;
|
|
80
|
+
// Only a provider-marked invocation failure contributes to failure count,
|
|
81
|
+
// red state, or Failed aggregate copy. Tool-reported HTTP/domain/status
|
|
82
|
+
// outcomes remain successful calls with their raw/semantic result detail.
|
|
83
|
+
const { exitCode, isExitError, isCallError } = toolCallOutcome(message, rawText);
|
|
84
|
+
const isError = isCallError;
|
|
79
85
|
const text = isError ? toolErrorDisplay(rawText, card?.name || 'tool') : rawText;
|
|
80
86
|
|
|
81
87
|
if (aggregate && card.itemId === aggregate.itemId) {
|
|
@@ -89,21 +95,24 @@ export function createToolCardResults({
|
|
|
89
95
|
assignAggregateSummaryOrder(aggregate, callRec);
|
|
90
96
|
callRec.isError = isError;
|
|
91
97
|
callRec.isCallError = isCallError;
|
|
98
|
+
callRec.isExitError = isExitError;
|
|
99
|
+
callRec.exitCode = exitCode;
|
|
92
100
|
callRec.resultText = text;
|
|
93
101
|
callRec.resolved = true;
|
|
94
102
|
const allCalls = [...aggregate.calls.values()];
|
|
95
103
|
const completed = allCalls.filter((r) => r.resolved).length;
|
|
96
104
|
const errors = allCalls.filter((r) => r.isError).length;
|
|
97
105
|
const callErrors = allCalls.filter((r) => r.isCallError).length;
|
|
106
|
+
const exitErrors = allCalls.filter((r) => r.isExitError).length;
|
|
98
107
|
// Collapsed detail carries the merged per-call count summary
|
|
99
108
|
// ("512 lines, 6 matches, 3 files") so the finished card answers "how
|
|
100
109
|
// much" without ctrl+o. Failures keep a bare 'N Ok · N Failed' status so
|
|
101
110
|
// an error stays visible while collapsed.
|
|
102
|
-
const succeeded = completed - errors;
|
|
103
|
-
const detailText = errors > 0
|
|
104
|
-
? (succeeded
|
|
111
|
+
const succeeded = Math.max(0, completed - errors - exitErrors);
|
|
112
|
+
const detailText = errors > 0 || exitErrors > 0
|
|
113
|
+
? failureDetailText({ succeeded, realErrors: callErrors, exitErrors, exitCode: allCalls.find((r) => r.isExitError)?.exitCode })
|
|
105
114
|
: formatAggregateDetail(aggregateSummaries(aggregate));
|
|
106
|
-
const currentItem =
|
|
115
|
+
const currentItem = itemById(card.itemId);
|
|
107
116
|
const earlyCompleted = allCalls.filter((r) => r.resolved || r.completedEarly).length;
|
|
108
117
|
const visualCompleted = Math.max(completed, earlyCompleted, Math.min(allCalls.length, Number(currentItem?.completedCount || 0)));
|
|
109
118
|
const rawResult = aggregateRawResult(allCalls);
|
|
@@ -116,6 +125,7 @@ export function createToolCardResults({
|
|
|
116
125
|
isError: errors > 0,
|
|
117
126
|
errorCount: errors,
|
|
118
127
|
callErrorCount: callErrors,
|
|
128
|
+
exitErrorCount: exitErrors,
|
|
119
129
|
count: allCalls.length,
|
|
120
130
|
completedCount: visualCompleted,
|
|
121
131
|
doneCategories: aggregateDoneCategories(allCalls),
|
|
@@ -127,20 +137,25 @@ export function createToolCardResults({
|
|
|
127
137
|
}
|
|
128
138
|
|
|
129
139
|
// Non-aggregate (legacy agent-job cards, etc.)
|
|
130
|
-
const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, callErrors: 0, results: [] };
|
|
140
|
+
const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, callErrors: 0, exitErrors: 0, results: [] };
|
|
131
141
|
group.completed = Math.min(group.count, group.completed + 1);
|
|
132
142
|
group.errors += isError ? 1 : 0;
|
|
133
143
|
group.callErrors = (group.callErrors || 0) + (isCallError ? 1 : 0);
|
|
134
|
-
group.
|
|
144
|
+
group.exitErrors = (group.exitErrors || 0) + (isExitError ? 1 : 0);
|
|
145
|
+
group.results.push({ text, isError, isExitError, exitCode });
|
|
135
146
|
toolGroups.set(card.itemId, group);
|
|
136
147
|
const resultText = groupedToolResultText(group);
|
|
137
|
-
|
|
148
|
+
let displayResult = toolGroupedDisplayFallback(resultText, text, rawText);
|
|
149
|
+
if (group.errors > 0 && !String(displayResult || '').trim()) {
|
|
150
|
+
displayResult = finalizedErrorFallbackBody(displayResult, text, exitCode);
|
|
151
|
+
}
|
|
138
152
|
const patch = {
|
|
139
153
|
result: displayResult,
|
|
140
154
|
text: displayResult,
|
|
141
155
|
isError: group.errors > 0,
|
|
142
156
|
errorCount: group.errors,
|
|
143
157
|
callErrorCount: group.callErrors || 0,
|
|
158
|
+
exitErrorCount: group.exitErrors || 0,
|
|
144
159
|
count: group.count,
|
|
145
160
|
completedCount: group.completed,
|
|
146
161
|
completedAt: Date.now(),
|
|
@@ -159,6 +174,11 @@ export function createToolCardResults({
|
|
|
159
174
|
// The agent fields win (final display) while patch keeps the completion
|
|
160
175
|
// metadata (count/completedCount/completedAt/rawResult) for expand.
|
|
161
176
|
Object.assign(patch, buildAgentJobCardPatch(card.itemId, rawText, isError));
|
|
177
|
+
// Re-apply the empty-body guard: buildAgentJobCardPatch may overwrite the
|
|
178
|
+
// stamped fallback with an empty agent-job body, letting the card vanish.
|
|
179
|
+
if (group.errors > 0 && !String(patch.result || '').trim()) {
|
|
180
|
+
patch.result = patch.text = finalizedErrorFallbackBody(patch.result, text, exitCode);
|
|
181
|
+
}
|
|
162
182
|
}
|
|
163
183
|
patchToolItem(card.itemId, patch);
|
|
164
184
|
card.done = true;
|
|
@@ -223,18 +243,20 @@ export function createToolCardResults({
|
|
|
223
243
|
const totalCompleted = completed;
|
|
224
244
|
const errors = allCalls.filter((r) => r.isError).length;
|
|
225
245
|
const callErrors = allCalls.filter((r) => r.isCallError).length;
|
|
226
|
-
const
|
|
246
|
+
const exitErrors = allCalls.filter((r) => r.isExitError).length;
|
|
247
|
+
const succeeded = Math.max(0, completed - errors - exitErrors);
|
|
227
248
|
const rawResult = aggregateRawResult(allCalls);
|
|
228
|
-
// Collapsed detail carries the merged per-call count summary;
|
|
229
|
-
// keep
|
|
230
|
-
|
|
231
|
-
|
|
249
|
+
// Collapsed detail carries the merged per-call count summary; real
|
|
250
|
+
// failures keep 'N Failed', shell command-exits show 'Exit N'/'Y Exit'.
|
|
251
|
+
// Raw is kept for ctrl+o.
|
|
252
|
+
let displayDetail = errors > 0 || exitErrors > 0
|
|
253
|
+
? failureDetailText({ succeeded, realErrors: callErrors, exitErrors, exitCode: allCalls.find((r) => r.isExitError)?.exitCode })
|
|
232
254
|
: formatAggregateDetail(aggregateSummaries(aggregate));
|
|
233
255
|
if (cancelled) {
|
|
234
256
|
// Cancelled aggregates MUST keep the [status: cancelled] marker on the
|
|
235
257
|
// result so terminalStatus parsing resolves to 'cancelled'. Only normal
|
|
236
258
|
// completions drop the summary; cancelled ones prepend the marker.
|
|
237
|
-
const currentItem =
|
|
259
|
+
const currentItem = itemById(card.itemId);
|
|
238
260
|
displayDetail = withCancelledResultMarker(displayDetail, currentItem);
|
|
239
261
|
}
|
|
240
262
|
patchToolItem(card.itemId, {
|
|
@@ -244,6 +266,7 @@ export function createToolCardResults({
|
|
|
244
266
|
isError: errors > 0,
|
|
245
267
|
errorCount: errors,
|
|
246
268
|
callErrorCount: callErrors,
|
|
269
|
+
exitErrorCount: exitErrors,
|
|
247
270
|
count: allCalls.length,
|
|
248
271
|
completedCount: totalCompleted,
|
|
249
272
|
doneCategories: aggregateDoneCategories(allCalls),
|
|
@@ -257,15 +280,19 @@ export function createToolCardResults({
|
|
|
257
280
|
continue;
|
|
258
281
|
}
|
|
259
282
|
// Non-aggregate finalize
|
|
260
|
-
const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, results: [] };
|
|
283
|
+
const group = toolGroups.get(card.itemId) || { count: 1, completed: 0, errors: 0, exitErrors: 0, results: [] };
|
|
261
284
|
group.completed = Math.min(group.count, group.completed + 1);
|
|
262
285
|
toolGroups.set(card.itemId, group);
|
|
263
286
|
let resultText = groupedToolResultText(group);
|
|
287
|
+
if (group.errors > 0 && !String(resultText || '').trim()) {
|
|
288
|
+
const exitRec = (group.results || []).find((r) => r && r.isExitError);
|
|
289
|
+
resultText = finalizedErrorFallbackBody(resultText, exitRec?.text, exitRec?.exitCode);
|
|
290
|
+
}
|
|
264
291
|
if (cancelled) {
|
|
265
|
-
const currentItem =
|
|
292
|
+
const currentItem = itemById(card.itemId);
|
|
266
293
|
resultText = withCancelledResultMarker(resultText, currentItem);
|
|
267
294
|
}
|
|
268
|
-
patchToolItem(card.itemId, { result: resultText, text: resultText, isError: group.errors > 0, errorCount: group.errors, callErrorCount: group.callErrors || 0, count: group.count, completedCount: group.completed, completedAt: Date.now() });
|
|
295
|
+
patchToolItem(card.itemId, { result: resultText, text: resultText, isError: group.errors > 0, errorCount: group.errors, callErrorCount: group.callErrors || 0, exitErrorCount: group.exitErrors || 0, count: group.count, completedCount: group.completed, completedAt: Date.now() });
|
|
269
296
|
card.done = true;
|
|
270
297
|
if (card.callId) done.add(card.callId);
|
|
271
298
|
}
|
|
@@ -8,27 +8,75 @@
|
|
|
8
8
|
* keeps calling them unchanged.
|
|
9
9
|
*/
|
|
10
10
|
import { toolErrorDisplay } from './tool-result-text.mjs';
|
|
11
|
+
import {
|
|
12
|
+
normalizeToolTerminalStatus,
|
|
13
|
+
toolResultTerminalStatus,
|
|
14
|
+
} from '../../runtime/shared/tool-status.mjs';
|
|
11
15
|
|
|
12
16
|
export const CANCELLED_RESULT_STATUS_LINE = '[status: cancelled]';
|
|
13
17
|
|
|
18
|
+
// Detect a shell command that RAN but exited non-zero (a process exit code)
|
|
19
|
+
// as opposed to a real tool-call failure (`[shell-tool-failed]`) or a
|
|
20
|
+
// timeout/abort. bash-tool.mjs emits `Error: [shell-run-failed] [exit code: N]`
|
|
21
|
+
// for a plain non-zero exit; timeout/signal cases carry `[timeout: …]`/
|
|
22
|
+
// `[signal: …]` instead of an `[exit code: …]` marker. Returns the numeric
|
|
23
|
+
// exit code (>= 0) for a command-exit, or null otherwise.
|
|
24
|
+
export function shellCommandExitCode(text) {
|
|
25
|
+
const body = String(text || '');
|
|
26
|
+
// Anchor to the START of the result so a success/non-shell body that merely
|
|
27
|
+
// QUOTES the marker mid-output is never misclassified. bash-tool emits
|
|
28
|
+
// `Error: [shell-run-failed] [exit code: N]` as the leading marker header.
|
|
29
|
+
if (!/^\s*(?:Error:\s*)?\[shell-run-failed\]/i.test(body)) return null;
|
|
30
|
+
// Restrict marker parsing to the header region (first line) so only the
|
|
31
|
+
// engine-emitted status header — not quoted command output below — counts.
|
|
32
|
+
const header = body.split('\n', 1)[0] || '';
|
|
33
|
+
// Timeout / signal / abort are NOT a plain command exit — keep them "Failed".
|
|
34
|
+
if (/\[timeout:|\[signal:|timed out|aborted|interrupted/i.test(header)) return null;
|
|
35
|
+
const m = header.match(/\[exit code:\s*(\d+)\]/i);
|
|
36
|
+
if (!m) return null;
|
|
37
|
+
const code = Number(m[1]);
|
|
38
|
+
return Number.isFinite(code) ? code : null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Only provider envelope metadata establishes a failed invocation. Result
|
|
42
|
+
// bodies can contain "Error:"/HTTP/domain/task status text without making the
|
|
43
|
+
// call itself fail. A recognized plain shell exit wins over provider envelope
|
|
44
|
+
// error flags: adapters commonly label non-zero process exits as tool errors
|
|
45
|
+
// even though the shell tool itself ran successfully.
|
|
46
|
+
export function toolCallOutcome(message, rawText) {
|
|
47
|
+
const exitCode = shellCommandExitCode(rawText);
|
|
48
|
+
if (exitCode != null) {
|
|
49
|
+
return { isCallError: false, isExitError: true, exitCode };
|
|
50
|
+
}
|
|
51
|
+
const isCallError = message?.isError === true || message?.toolKind === 'error';
|
|
52
|
+
return {
|
|
53
|
+
isCallError,
|
|
54
|
+
isExitError: false,
|
|
55
|
+
exitCode,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Build the collapsed failure/exit detail string. Real tool-call/result
|
|
60
|
+
// failures keep the red-adjacent "Failed" wording; shell command-exits render
|
|
61
|
+
// as the distinct neutral "Exit" state ("Exit N" for a single exit, "Y Exit"
|
|
62
|
+
// grouped). A mixed group surfaces both ("1 Ok · 1 Failed · 1 Exit").
|
|
63
|
+
export function failureDetailText({ succeeded = 0, realErrors = 0, exitErrors = 0, exitCode } = {}) {
|
|
64
|
+
const parts = [];
|
|
65
|
+
if (succeeded > 0) parts.push(`${succeeded} Ok`);
|
|
66
|
+
if (realErrors > 0) parts.push(`${realErrors} Failed`);
|
|
67
|
+
if (exitErrors > 0) {
|
|
68
|
+
const solo = exitErrors === 1 && realErrors === 0 && succeeded === 0;
|
|
69
|
+
parts.push(solo && Number.isFinite(exitCode) ? `Exit ${exitCode}` : `${exitErrors} Exit`);
|
|
70
|
+
}
|
|
71
|
+
return parts.join(' · ');
|
|
72
|
+
}
|
|
73
|
+
|
|
14
74
|
export function normalizedResultStatusToken(value) {
|
|
15
|
-
|
|
16
|
-
if (!raw) return '';
|
|
17
|
-
if (/^(running|pending|queued|in_progress|in-progress)$/.test(raw)) return 'running';
|
|
18
|
-
if (/^(completed|complete|done|success|succeeded|ok)$/.test(raw)) return 'completed';
|
|
19
|
-
if (/^(failed|fail|error|errored|timeout|timed_out|killed)$/.test(raw)) return 'failed';
|
|
20
|
-
if (/^(cancelled|canceled|cancel)$/.test(raw)) return 'cancelled';
|
|
21
|
-
return '';
|
|
75
|
+
return normalizeToolTerminalStatus(value);
|
|
22
76
|
}
|
|
23
77
|
|
|
24
78
|
export function resultTextTerminalStatus(text) {
|
|
25
|
-
|
|
26
|
-
const tagged = body.match(/<status[^>]*>([\s\S]*?)<\/status>/i)?.[1]?.trim();
|
|
27
|
-
if (tagged) return normalizedResultStatusToken(tagged);
|
|
28
|
-
const bracketed = body.match(/^\[status:\s*([^\]]*)\]/mi)?.[1]?.trim();
|
|
29
|
-
if (bracketed) return normalizedResultStatusToken(bracketed);
|
|
30
|
-
const inline = body.match(/^(?:status|state):\s*([^\s·,;]+)/mi)?.[1]?.trim();
|
|
31
|
-
return normalizedResultStatusToken(inline);
|
|
79
|
+
return toolResultTerminalStatus(text);
|
|
32
80
|
}
|
|
33
81
|
|
|
34
82
|
export function itemHasKnownTerminalStatus(item, texts = []) {
|
|
@@ -57,16 +105,18 @@ export function withCancelledResultMarker(text, item) {
|
|
|
57
105
|
export function groupedToolResultText(group) {
|
|
58
106
|
const completed = Math.min(group.count, group.completed);
|
|
59
107
|
if (group.count <= 1) return group.results.at(-1)?.text ?? '';
|
|
60
|
-
|
|
61
|
-
|
|
108
|
+
const exitErrors = Number(group.exitErrors || 0);
|
|
109
|
+
if (group.errors > 0 || exitErrors > 0) {
|
|
110
|
+
const realErrors = Math.max(0, Number(group.callErrors || group.errors || 0));
|
|
111
|
+
const succeeded = Math.max(0, completed - group.errors - exitErrors);
|
|
112
|
+
const exitCode = group.results.find((result) => result?.isExitError)?.exitCode;
|
|
113
|
+
// Command-exits carry no failure reason line; only real failures do.
|
|
62
114
|
const reasons = group.results
|
|
63
|
-
.filter((result) => result?.isError)
|
|
115
|
+
.filter((result) => result?.isError && !result?.isExitError)
|
|
64
116
|
.map((result) => firstErrorLine(result?.text))
|
|
65
117
|
.filter(Boolean);
|
|
66
118
|
const uniqueReasons = [...new Set(reasons)].slice(0, 2);
|
|
67
|
-
const base = succeeded
|
|
68
|
-
? `${succeeded} Ok · ${group.errors} Failed`
|
|
69
|
-
: `${group.errors} Failed`;
|
|
119
|
+
const base = failureDetailText({ succeeded, realErrors, exitErrors, exitCode });
|
|
70
120
|
return [
|
|
71
121
|
`${base}${uniqueReasons[0] ? ` · ${uniqueReasons[0]}` : ''}`,
|
|
72
122
|
...uniqueReasons.slice(1),
|
|
@@ -102,7 +152,7 @@ export function aggregateRawResult(calls) {
|
|
|
102
152
|
return chunks.join('\n\n');
|
|
103
153
|
}
|
|
104
154
|
|
|
105
|
-
export function aggregateBucketForCategory(category) {
|
|
155
|
+
export function aggregateBucketForCategory(category, { agentBatch = '' } = {}) {
|
|
106
156
|
// Merge consecutive tool calls of the SAME category into one aggregate card;
|
|
107
157
|
// a different category opens a fresh card (no cross-category merge). The
|
|
108
158
|
// bucket key is the category itself, so a run of Search calls collapses into
|
|
@@ -117,6 +167,10 @@ export function aggregateBucketForCategory(category) {
|
|
|
117
167
|
// header still spells out both. State-changing categories (Patch/Shell/…)
|
|
118
168
|
// stay separate.
|
|
119
169
|
if (key === 'Read' || key === 'Search') return 'category:Read+Search';
|
|
170
|
+
// Agent actions are grouped only inside the provider callback that emitted
|
|
171
|
+
// them. This keeps a later provider batch from changing an older outbound
|
|
172
|
+
// card above an intervening result/transcript boundary.
|
|
173
|
+
if (key === 'Agent') return agentBatch ? `category:Agent:${agentBatch}` : 'category:Agent';
|
|
120
174
|
return key ? `category:${key}` : 'default';
|
|
121
175
|
}
|
|
122
176
|
|