mixdog 0.9.93 → 0.9.94
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 +1 -1
- package/src/rules/agent/30-explorer.md +22 -16
- package/src/rules/shared/01-tool.md +23 -18
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +22 -6
- package/src/runtime/agent/orchestrator/context/collect.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +75 -15
- package/src/runtime/agent/orchestrator/session/cache/scoped-cache.mjs +42 -2
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +100 -86
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +98 -3
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +12 -3
- package/src/runtime/agent/orchestrator/tools/builtin/grep-formatting.mjs +22 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +491 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +91 -13
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +90 -27
- package/src/runtime/agent/orchestrator/tools/builtin/path-utils.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +19 -9
- package/src/runtime/agent/orchestrator/tools/builtin/read-streaming.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +32 -4
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +16 -1
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +543 -19
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-output.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/builtin/tool-output-limit.mjs +48 -0
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +71 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +51 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +6 -17
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +2 -4
- package/src/runtime/agent/orchestrator/tools/patch/dispatch.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +3 -3
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +1 -1
- package/src/runtime/memory/lib/tool-call-handler.mjs +16 -1
- package/src/runtime/shared/background-tasks.mjs +10 -3
- package/src/runtime/shared/child-spawn-gate.mjs +50 -26
- package/src/runtime/shared/task-notification-envelope.mjs +11 -2
- package/src/runtime/shared/tool-card-model.mjs +6 -2
- package/src/runtime/shared/tool-surface.mjs +7 -2
- package/src/session-runtime/provider-usage.mjs +26 -2
- package/src/standalone/explore-tool.mjs +1 -1
- package/src/tui/app/use-transcript-window.mjs +7 -1
- package/src/tui/components/Spinner.jsx +18 -9
- package/src/tui/dist/index.mjs +111 -25
- package/src/tui/engine/live-share.mjs +23 -3
- package/src/tui/engine/session-api.mjs +7 -0
- package/src/tui/engine/turn.mjs +77 -4
- package/src/tui/engine.mjs +14 -6
- package/src/tui/index.jsx +7 -0
|
@@ -24,6 +24,15 @@ export {
|
|
|
24
24
|
backgroundTaskHeaderStatus,
|
|
25
25
|
};
|
|
26
26
|
|
|
27
|
+
// The full command is already visible in the start response / task record;
|
|
28
|
+
// the envelope only needs an identifying prefix. Flatten whitespace and cap
|
|
29
|
+
// so multi-line commands do not re-echo hundreds of chars per notification.
|
|
30
|
+
function compactCommand(command) {
|
|
31
|
+
if (!command) return null;
|
|
32
|
+
const flat = String(command).replace(/\s+/g, ' ').trim();
|
|
33
|
+
return flat.length > 160 ? `${flat.slice(0, 160)}…` : flat;
|
|
34
|
+
}
|
|
35
|
+
|
|
27
36
|
// Render the bracketed shell *completion* envelope body.
|
|
28
37
|
// Byte-compatible with the historical inline assembly in shell-jobs.fire().
|
|
29
38
|
export function renderShellCompletionEnvelope({
|
|
@@ -42,7 +51,7 @@ export function renderShellCompletionEnvelope({
|
|
|
42
51
|
`[status: ${status}]`,
|
|
43
52
|
`[exit: ${exitCode === null ? 'n/a' : exitCode}]`,
|
|
44
53
|
elapsedMs !== null ? `[elapsed: ${elapsedMs} ms]` : null,
|
|
45
|
-
command ? `[command: ${command}]` : null,
|
|
54
|
+
command ? `[command: ${compactCommand(command)}]` : null,
|
|
46
55
|
].filter((l) => l !== null);
|
|
47
56
|
const bodySections = [
|
|
48
57
|
summary ? `Summary: ${summary}` : null,
|
|
@@ -86,7 +95,7 @@ export function renderShellPromptStallEnvelope({
|
|
|
86
95
|
'[status: running]',
|
|
87
96
|
`[stalled: no output growth for ${stalledMs} ms]`,
|
|
88
97
|
(elapsedMs !== null && elapsedMs >= 0) ? `[elapsed: ${elapsedMs} ms]` : null,
|
|
89
|
-
command ? `[command: ${command}]` : null,
|
|
98
|
+
command ? `[command: ${compactCommand(command)}]` : null,
|
|
90
99
|
'',
|
|
91
100
|
'This background shell task appears to be waiting for interactive input. Background tasks cannot answer prompts automatically; cancel it or rerun with non-interactive flags/input.',
|
|
92
101
|
tailText ? `\n${tailText}` : null,
|
|
@@ -160,9 +160,13 @@ export function shellDisplayStatus({ pending = false, failedCount = 0, exitFaile
|
|
|
160
160
|
return 'completed';
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
export function shellHeader(status, count = 1) {
|
|
163
|
+
export function shellHeader(status, count = 1, verifying = false) {
|
|
164
164
|
const n = Math.max(1, Number(count) || 1);
|
|
165
165
|
const object = `${n} ${plural(n, 'command')}`;
|
|
166
|
+
// A shell that follows an edit in the same batch IS the verification —
|
|
167
|
+
// label it as such while healthy; failures keep the neutral Ran + detail.
|
|
168
|
+
if (verifying && status === 'running') return `Verifying ${object}`;
|
|
169
|
+
if (verifying && status === 'completed') return `Verified ${object}`;
|
|
166
170
|
if (status === 'running') return `Running ${object}`;
|
|
167
171
|
return `Ran ${object}`;
|
|
168
172
|
}
|
|
@@ -708,7 +712,7 @@ export function deriveToolCardModel(input = {}, options = {}) {
|
|
|
708
712
|
if (isAgentResponse) labelText = agentResponseTitle(parsedArgs, groupCount);
|
|
709
713
|
else if (isBackgroundResponse) labelText = backgroundTaskResultTitle(normalizedName, backgroundMeta || parsedArgs);
|
|
710
714
|
else if (isBackgroundMetadataResult) labelText = backgroundTaskActionTitle(normalizedName, backgroundMeta);
|
|
711
|
-
else if (isShellSurface) labelText = shellHeader(shellStatus, groupCount);
|
|
715
|
+
else if (isShellSurface) labelText = shellHeader(shellStatus, groupCount, parsedArgs?.verifyShell === true);
|
|
712
716
|
else labelText = (isAgentTool(normalizedName) ? agentActionTitle(parsedArgs) : '')
|
|
713
717
|
|| formatToolActionHeader(name, args, { pending: headerPending, count: groupCount });
|
|
714
718
|
labelText = safeInlineText(labelText);
|
|
@@ -467,10 +467,15 @@ export function toolWorkUnit(name, args = {}, category = '') {
|
|
|
467
467
|
noun: 'file',
|
|
468
468
|
});
|
|
469
469
|
}
|
|
470
|
+
// post_shell runs a verification command inside the SAME tool call after
|
|
471
|
+
// the patch applies; without the suffix the spinner sits on "Editing 1
|
|
472
|
+
// file" for the whole (possibly long) verify run and reads like a stuck
|
|
473
|
+
// edit. Surface both phases in the active label.
|
|
474
|
+
const verifying = typeof a.post_shell === 'string' && a.post_shell.trim() !== '';
|
|
470
475
|
return unitDescriptor('Patch', {
|
|
471
476
|
count: patchFileCount(a) || 1,
|
|
472
|
-
active: creating ? 'Creating' : deleting ? 'Deleting' : 'Editing',
|
|
473
|
-
done: creating ? 'Created' : deleting ? 'Deleted' : 'Edited',
|
|
477
|
+
active: (creating ? 'Creating' : deleting ? 'Deleting' : 'Editing') + (verifying ? ' & verifying' : ''),
|
|
478
|
+
done: (creating ? 'Created' : deleting ? 'Deleted' : 'Edited') + (verifying ? ' & verified' : ''),
|
|
474
479
|
noun: 'file',
|
|
475
480
|
});
|
|
476
481
|
}
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
// live-binding is preserved.
|
|
7
7
|
import { clean } from './session-text.mjs';
|
|
8
8
|
|
|
9
|
+
// A post-redeem dashboard rebuild is best effort: past this budget the caller
|
|
10
|
+
// gets its confirmed outcome and the surface revalidates on its own cadence.
|
|
11
|
+
const REDEEM_REFRESH_BUDGET_MS = 15_000;
|
|
12
|
+
|
|
9
13
|
export function createProviderUsage({
|
|
10
14
|
caches,
|
|
11
15
|
getConfig,
|
|
@@ -134,9 +138,29 @@ export function createProviderUsage({
|
|
|
134
138
|
const providerObj = reg().getProvider('openai-oauth');
|
|
135
139
|
if (!providerObj) throw new Error('Codex is not signed in');
|
|
136
140
|
const result = await consumeOpenAICodexResetCredit(providerObj, options);
|
|
141
|
+
// The redeem is DONE and its outcome is authoritative. Rebuilding the whole
|
|
142
|
+
// dashboard is a COURTESY refresh: it forces a sweep across every provider,
|
|
143
|
+
// so binding the answer to it once turned a spent credit into "reset could
|
|
144
|
+
// not be confirmed" whenever that sweep was slow or failed.
|
|
137
145
|
caches.usageDashboardCache = {};
|
|
138
|
-
const dashboard = await
|
|
139
|
-
return { ...result, dashboard };
|
|
146
|
+
const dashboard = await refreshedUsageDashboardWithin(REDEEM_REFRESH_BUDGET_MS);
|
|
147
|
+
return { ...result, ...(dashboard ? { dashboard } : {}) };
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function refreshedUsageDashboardWithin(budgetMs) {
|
|
151
|
+
let timer = null;
|
|
152
|
+
const budget = new Promise((resolve) => {
|
|
153
|
+
timer = setTimeout(() => resolve(null), budgetMs);
|
|
154
|
+
timer?.unref?.();
|
|
155
|
+
});
|
|
156
|
+
try {
|
|
157
|
+
return await Promise.race([
|
|
158
|
+
getUsageDashboard({ refresh: true, refreshSetup: false }).catch(() => null),
|
|
159
|
+
budget,
|
|
160
|
+
]);
|
|
161
|
+
} finally {
|
|
162
|
+
if (timer) clearTimeout(timer);
|
|
163
|
+
}
|
|
140
164
|
}
|
|
141
165
|
|
|
142
166
|
return {
|
|
@@ -34,7 +34,7 @@ export const EXPLORE_TOOL = {
|
|
|
34
34
|
// (50MB) plus a smart-read summariser downstream; the standalone path returns
|
|
35
35
|
// the merged text in-turn, so we keep a sane bound to protect the Lead context.
|
|
36
36
|
const EXPLORE_OUTPUT_CHAR_CAP = 24_000;
|
|
37
|
-
const EXPLORE_TRUNCATION_MARKER = '\n\n[explore: output
|
|
37
|
+
const EXPLORE_TRUNCATION_MARKER = '\n\n[explore: output capped; remainder omitted]';
|
|
38
38
|
// Bound fan-out so a hostile/poisoned query array cannot spawn unbounded subs.
|
|
39
39
|
export const MAX_FANOUT_QUERIES = 8;
|
|
40
40
|
// Mechanical turn cap per explorer sub-session: 3 free turns (contract: tool
|
|
@@ -769,7 +769,13 @@ export function useTranscriptWindow({
|
|
|
769
769
|
stopSmoothScroll();
|
|
770
770
|
scrollTargetRef.current = desired;
|
|
771
771
|
scrollPositionRef.current = Math.max(0, Math.min(maxRows, currentPosition + appliedDelta));
|
|
772
|
-
|
|
772
|
+
// The render path already applied `desired` synchronously through the
|
|
773
|
+
// absolute anchor lock. Keep the refs as the committed scroll authority;
|
|
774
|
+
// mirroring the same correction back into React state from a layout effect
|
|
775
|
+
// creates a render → layout update feedback path when transcript geometry
|
|
776
|
+
// and viewport geometry change together (React #185). The next manual
|
|
777
|
+
// scroll writes state from these refs, while anchored renders continue to
|
|
778
|
+
// resolve directly from the current prefix table.
|
|
773
779
|
}, [transcriptWindow.totalRows, transcriptWindow.maxScrollRows, transcriptRowIndex, transcriptContentHeight, scrollOffset, stopSmoothScroll]);
|
|
774
780
|
useLayoutEffect(() => {
|
|
775
781
|
if (transcriptAnchorRef.current || transcriptAnchorDirtyRef.current || followingRef.current) return;
|
|
@@ -44,19 +44,25 @@ const GLIMMER_SPEED_MS = { requesting: 70, reconnecting: 70, compacting: 120, 'a
|
|
|
44
44
|
const GLIMMER_TRAIL = 4;
|
|
45
45
|
const THINKING_GLIMMER_SPEED_MS = 120;
|
|
46
46
|
const THINKING_GLIMMER_TRAIL = 4;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
// Fixed 30s cadence (user decision): the verb rotates deterministically every
|
|
48
|
+
// 30 seconds instead of the earlier randomized 10–20s / 65% coin flip that
|
|
49
|
+
// often never visibly fired.
|
|
50
|
+
const VERB_ROTATE_MIN_MS = 30000;
|
|
51
|
+
const VERB_ROTATE_SPREAD_MS = 0;
|
|
52
|
+
const VERB_CHANGE_PROBABILITY = 1;
|
|
50
53
|
|
|
51
54
|
const MODE_VERBS = {
|
|
52
|
-
requesting: ['Requesting'
|
|
55
|
+
requesting: ['Requesting'],
|
|
53
56
|
compacting: ['Compacting conversation'],
|
|
54
57
|
'auto-clear': ['Auto-clearing conversation'],
|
|
55
58
|
resuming: ['Resuming conversation'],
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
59
|
+
// Conservative phrasing (user decision): each pool only rotates through
|
|
60
|
+
// synonyms of what the mode ACTUALLY means — never a concrete action the
|
|
61
|
+
// engine may not be doing (no "Checking files" during a web search).
|
|
62
|
+
thinking: ['Thinking', 'Considering', 'Organizing'],
|
|
63
|
+
'tool-use': ['Working', 'Running tools', 'Reviewing output'],
|
|
64
|
+
'tool-input': ['Working', 'Running tools', 'Reviewing output'],
|
|
65
|
+
responding: ['Writing', 'Wrapping up'],
|
|
60
66
|
};
|
|
61
67
|
|
|
62
68
|
function interpolateColor(a, b, t) {
|
|
@@ -232,7 +238,10 @@ export function Spinner({ verb = 'Working', startedAt, outputTokens = 0, tokens
|
|
|
232
238
|
// --- Verb shimmer (traveling highlight) ---
|
|
233
239
|
if (!displayVerbRef.current || displayVerbModeRef.current !== mode) {
|
|
234
240
|
displayVerbRef.current = stableModeVerb(mode, verb);
|
|
235
|
-
|
|
241
|
+
// Mode flips (thinking→tool-use→responding…) must NOT re-arm the timer:
|
|
242
|
+
// frequent flips otherwise reset the countdown forever and the rotation
|
|
243
|
+
// never fires. The cadence holds from the first mount of the turn.
|
|
244
|
+
if (!nextVerbCheckRef.current) nextVerbCheckRef.current = nextVerbCheckAt(now);
|
|
236
245
|
}
|
|
237
246
|
displayVerbModeRef.current = mode;
|
|
238
247
|
if (now >= nextVerbCheckRef.current) {
|
package/src/tui/dist/index.mjs
CHANGED
|
@@ -5273,10 +5273,11 @@ function toolWorkUnit(name, args = {}, category = "") {
|
|
|
5273
5273
|
noun: "file"
|
|
5274
5274
|
});
|
|
5275
5275
|
}
|
|
5276
|
+
const verifying = typeof a.post_shell === "string" && a.post_shell.trim() !== "";
|
|
5276
5277
|
return unitDescriptor("Patch", {
|
|
5277
5278
|
count: patchFileCount(a) || 1,
|
|
5278
|
-
active: creating ? "Creating" : deleting ? "Deleting" : "Editing",
|
|
5279
|
-
done: creating ? "Created" : deleting ? "Deleted" : "Edited",
|
|
5279
|
+
active: (creating ? "Creating" : deleting ? "Deleting" : "Editing") + (verifying ? " & verifying" : ""),
|
|
5280
|
+
done: (creating ? "Created" : deleting ? "Deleted" : "Edited") + (verifying ? " & verified" : ""),
|
|
5280
5281
|
noun: "file"
|
|
5281
5282
|
});
|
|
5282
5283
|
}
|
|
@@ -5927,18 +5928,21 @@ var GLIMMER_SPEED_MS = { requesting: 70, reconnecting: 70, compacting: 120, "aut
|
|
|
5927
5928
|
var GLIMMER_TRAIL = 4;
|
|
5928
5929
|
var THINKING_GLIMMER_SPEED_MS = 120;
|
|
5929
5930
|
var THINKING_GLIMMER_TRAIL = 4;
|
|
5930
|
-
var VERB_ROTATE_MIN_MS =
|
|
5931
|
-
var VERB_ROTATE_SPREAD_MS =
|
|
5932
|
-
var VERB_CHANGE_PROBABILITY =
|
|
5931
|
+
var VERB_ROTATE_MIN_MS = 3e4;
|
|
5932
|
+
var VERB_ROTATE_SPREAD_MS = 0;
|
|
5933
|
+
var VERB_CHANGE_PROBABILITY = 1;
|
|
5933
5934
|
var MODE_VERBS = {
|
|
5934
|
-
requesting: ["Requesting"
|
|
5935
|
+
requesting: ["Requesting"],
|
|
5935
5936
|
compacting: ["Compacting conversation"],
|
|
5936
5937
|
"auto-clear": ["Auto-clearing conversation"],
|
|
5937
5938
|
resuming: ["Resuming conversation"],
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5939
|
+
// Conservative phrasing (user decision): each pool only rotates through
|
|
5940
|
+
// synonyms of what the mode ACTUALLY means — never a concrete action the
|
|
5941
|
+
// engine may not be doing (no "Checking files" during a web search).
|
|
5942
|
+
thinking: ["Thinking", "Considering", "Organizing"],
|
|
5943
|
+
"tool-use": ["Working", "Running tools", "Reviewing output"],
|
|
5944
|
+
"tool-input": ["Working", "Running tools", "Reviewing output"],
|
|
5945
|
+
responding: ["Writing", "Wrapping up"]
|
|
5942
5946
|
};
|
|
5943
5947
|
function interpolateColor(a, b, t) {
|
|
5944
5948
|
return {
|
|
@@ -6062,7 +6066,7 @@ function Spinner({ verb = "Working", startedAt, outputTokens = 0, tokens = 0, th
|
|
|
6062
6066
|
)) : theme.spinnerGlyph;
|
|
6063
6067
|
if (!displayVerbRef.current || displayVerbModeRef.current !== mode) {
|
|
6064
6068
|
displayVerbRef.current = stableModeVerb(mode, verb);
|
|
6065
|
-
nextVerbCheckRef.current = nextVerbCheckAt(now);
|
|
6069
|
+
if (!nextVerbCheckRef.current) nextVerbCheckRef.current = nextVerbCheckAt(now);
|
|
6066
6070
|
}
|
|
6067
6071
|
displayVerbModeRef.current = mode;
|
|
6068
6072
|
if (now >= nextVerbCheckRef.current) {
|
|
@@ -16356,9 +16360,11 @@ function shellDisplayStatus({ pending = false, failedCount = 0, exitFailedCount
|
|
|
16356
16360
|
if (isError || failedCount > 0) return "failed";
|
|
16357
16361
|
return "completed";
|
|
16358
16362
|
}
|
|
16359
|
-
function shellHeader(status, count = 1) {
|
|
16363
|
+
function shellHeader(status, count = 1, verifying = false) {
|
|
16360
16364
|
const n = Math.max(1, Number(count) || 1);
|
|
16361
16365
|
const object = `${n} ${plural(n, "command")}`;
|
|
16366
|
+
if (verifying && status === "running") return `Verifying ${object}`;
|
|
16367
|
+
if (verifying && status === "completed") return `Verified ${object}`;
|
|
16362
16368
|
if (status === "running") return `Running ${object}`;
|
|
16363
16369
|
return `Ran ${object}`;
|
|
16364
16370
|
}
|
|
@@ -16825,7 +16831,7 @@ function deriveToolCardModel(input = {}, options = {}) {
|
|
|
16825
16831
|
if (isAgentResponse) labelText = agentResponseTitle(parsedArgs, groupCount);
|
|
16826
16832
|
else if (isBackgroundResponse) labelText = backgroundTaskResultTitle(normalizedName, backgroundMeta || parsedArgs);
|
|
16827
16833
|
else if (isBackgroundMetadataResult) labelText = backgroundTaskActionTitle(normalizedName, backgroundMeta);
|
|
16828
|
-
else if (isShellSurface) labelText = shellHeader(shellStatus, groupCount);
|
|
16834
|
+
else if (isShellSurface) labelText = shellHeader(shellStatus, groupCount, parsedArgs?.verifyShell === true);
|
|
16829
16835
|
else labelText = (isAgentTool(normalizedName) ? agentActionTitle(parsedArgs) : "") || formatToolActionHeader(name, args, { pending: headerPending, count: groupCount });
|
|
16830
16836
|
labelText = safeInlineText(labelText);
|
|
16831
16837
|
const toolSearchSummary = !pending && normalizedName === "load_tool" && hasResult ? toolSearchLoadedSummary(displayedResultText) : "";
|
|
@@ -22926,7 +22932,6 @@ function useTranscriptWindow({
|
|
|
22926
22932
|
stopSmoothScroll();
|
|
22927
22933
|
scrollTargetRef.current = desired;
|
|
22928
22934
|
scrollPositionRef.current = Math.max(0, Math.min(maxRows, currentPosition + appliedDelta));
|
|
22929
|
-
setScrollOffset(Math.max(0, Math.round(desired)));
|
|
22930
22935
|
}, [transcriptWindow.totalRows, transcriptWindow.maxScrollRows, transcriptRowIndex, transcriptContentHeight, scrollOffset, stopSmoothScroll]);
|
|
22931
22936
|
useLayoutEffect4(() => {
|
|
22932
22937
|
if (transcriptAnchorRef.current || transcriptAnchorDirtyRef.current || followingRef.current) return;
|
|
@@ -27638,6 +27643,7 @@ function createRunTurn(bag) {
|
|
|
27638
27643
|
const toolCards = [];
|
|
27639
27644
|
const toolGroups = /* @__PURE__ */ new Map();
|
|
27640
27645
|
const resultsDone = /* @__PURE__ */ new Set();
|
|
27646
|
+
const postShellTwins = /* @__PURE__ */ new Map();
|
|
27641
27647
|
let liveTailTimer = null;
|
|
27642
27648
|
let lastLiveTailPatched = "";
|
|
27643
27649
|
const clearLiveTailTimer = () => {
|
|
@@ -27898,7 +27904,10 @@ function createRunTurn(bag) {
|
|
|
27898
27904
|
const syncAggregateHeader = (aggregate) => {
|
|
27899
27905
|
if (!aggregate?.itemId) return;
|
|
27900
27906
|
const patch = {
|
|
27901
|
-
args: {
|
|
27907
|
+
args: {
|
|
27908
|
+
categoryOrder: aggregate.categoryOrder.slice(),
|
|
27909
|
+
...aggregate.verifyShell ? { verifyShell: true } : {}
|
|
27910
|
+
},
|
|
27902
27911
|
count: aggregate.calls.size,
|
|
27903
27912
|
completedCount: [...aggregate.calls.values()].filter((r) => r.resolved || r.completedEarly).length,
|
|
27904
27913
|
categories: Object.fromEntries(aggregate.categories)
|
|
@@ -28111,7 +28120,41 @@ function createRunTurn(bag) {
|
|
|
28111
28120
|
const deliverToolResultMessage = (message) => {
|
|
28112
28121
|
if (message?.__earlyNotify === true) {
|
|
28113
28122
|
const earlyCallId = toolResultCallId(message);
|
|
28114
|
-
if (earlyCallId)
|
|
28123
|
+
if (earlyCallId) {
|
|
28124
|
+
markToolCardCompletedState(earlyCallId, message);
|
|
28125
|
+
const earlyTwin = postShellTwins.get(earlyCallId);
|
|
28126
|
+
if (earlyTwin) markToolCardCompletedState(earlyTwin, message);
|
|
28127
|
+
}
|
|
28128
|
+
return;
|
|
28129
|
+
}
|
|
28130
|
+
const parentCallId = toolResultCallId(message);
|
|
28131
|
+
const twinCallId = parentCallId ? postShellTwins.get(parentCallId) : null;
|
|
28132
|
+
if (twinCallId && !resultsDone.has(twinCallId)) {
|
|
28133
|
+
const combined = String(toolResultText(message?.content) ?? "");
|
|
28134
|
+
const headerMatch = combined.match(/\n+--- post_shell( FAILED[^\n]*)? ---\n?/);
|
|
28135
|
+
const skippedMatch = headerMatch ? null : combined.match(/\n+--- post_shell skipped: patch failed ---\s*$/);
|
|
28136
|
+
let patchPart = combined;
|
|
28137
|
+
let shellPart = null;
|
|
28138
|
+
if (headerMatch) {
|
|
28139
|
+
patchPart = combined.slice(0, headerMatch.index);
|
|
28140
|
+
shellPart = combined.slice(headerMatch.index + headerMatch[0].length);
|
|
28141
|
+
if (headerMatch[1]) shellPart = `Error: post_shell FAILED (patch is applied; fix and re-verify)
|
|
28142
|
+
${shellPart}`;
|
|
28143
|
+
} else if (skippedMatch) {
|
|
28144
|
+
patchPart = combined.slice(0, skippedMatch.index);
|
|
28145
|
+
shellPart = "Error: post_shell skipped: patch failed";
|
|
28146
|
+
}
|
|
28147
|
+
if (shellPart !== null) {
|
|
28148
|
+
flushToolResults([
|
|
28149
|
+
{ role: "tool", tool_call_id: parentCallId, content: patchPart },
|
|
28150
|
+
{ role: "tool", tool_call_id: twinCallId, content: shellPart }
|
|
28151
|
+
], toolCards, cardByCallId, toolGroups, resultsDone);
|
|
28152
|
+
return;
|
|
28153
|
+
}
|
|
28154
|
+
flushToolResults([
|
|
28155
|
+
message,
|
|
28156
|
+
{ role: "tool", tool_call_id: twinCallId, content: "(post_shell output included in the patch result above)" }
|
|
28157
|
+
], toolCards, cardByCallId, toolGroups, resultsDone);
|
|
28115
28158
|
return;
|
|
28116
28159
|
}
|
|
28117
28160
|
flushToolResults([message], toolCards, cardByCallId, toolGroups, resultsDone);
|
|
@@ -28154,6 +28197,25 @@ function createRunTurn(bag) {
|
|
|
28154
28197
|
}
|
|
28155
28198
|
const batchCalls = (calls || []).filter(Boolean);
|
|
28156
28199
|
if (batchCalls.length === 0) return;
|
|
28200
|
+
const displayCalls = [];
|
|
28201
|
+
for (const c of batchCalls) {
|
|
28202
|
+
displayCalls.push(c);
|
|
28203
|
+
if (toolCallName(c) !== "apply_patch") continue;
|
|
28204
|
+
let patchCallArgs = toolCallArgs(c);
|
|
28205
|
+
if (typeof patchCallArgs === "string") {
|
|
28206
|
+
try {
|
|
28207
|
+
patchCallArgs = JSON.parse(patchCallArgs);
|
|
28208
|
+
} catch {
|
|
28209
|
+
patchCallArgs = null;
|
|
28210
|
+
}
|
|
28211
|
+
}
|
|
28212
|
+
const postShellCmd = typeof patchCallArgs?.post_shell === "string" ? patchCallArgs.post_shell.trim() : "";
|
|
28213
|
+
const parentCallId = toolCallId(c);
|
|
28214
|
+
if (!postShellCmd || !parentCallId) continue;
|
|
28215
|
+
const twinCallId = `${parentCallId}::post_shell`;
|
|
28216
|
+
postShellTwins.set(parentCallId, twinCallId);
|
|
28217
|
+
displayCalls.push({ id: twinCallId, name: "shell", args: { command: postShellCmd } });
|
|
28218
|
+
}
|
|
28157
28219
|
const agentBatch = ++providerToolBatch;
|
|
28158
28220
|
const committedAssistantSegment = commitAssistantSegment({ sealToolBlock: true });
|
|
28159
28221
|
if (committedAssistantSegment) {
|
|
@@ -28162,11 +28224,14 @@ function createRunTurn(bag) {
|
|
|
28162
28224
|
}
|
|
28163
28225
|
const touchedAggregates = /* @__PURE__ */ new Set();
|
|
28164
28226
|
let standaloneReserve = null;
|
|
28165
|
-
|
|
28166
|
-
|
|
28227
|
+
let sawEditInBatch = false;
|
|
28228
|
+
for (let i = 0; i < displayCalls.length; i++) {
|
|
28229
|
+
const c = displayCalls[i];
|
|
28167
28230
|
const name = toolCallName(c);
|
|
28168
28231
|
const args = toolCallArgs(c);
|
|
28169
28232
|
const category = classifyToolCategory(name, args);
|
|
28233
|
+
const shellAfterEdit = category === "Shell" && sawEditInBatch;
|
|
28234
|
+
if (category === "Patch" && args?.dry_run !== true) sawEditInBatch = true;
|
|
28170
28235
|
const bucket = aggregateBucketForCategory(category, { agentBatch });
|
|
28171
28236
|
const callId = toolCallId(c);
|
|
28172
28237
|
const callKey = callId || `__tool_${toolCards.length}_${i}`;
|
|
@@ -28204,6 +28269,7 @@ function createRunTurn(bag) {
|
|
|
28204
28269
|
}
|
|
28205
28270
|
const categoryEntry = aggregateToolCategoryEntry(name, args, category);
|
|
28206
28271
|
const aggregateCard = ensureAggregateCard(bucket);
|
|
28272
|
+
if (shellAfterEdit) aggregateCard.verifyShell = true;
|
|
28207
28273
|
if (!aggregateCard.categories.has(categoryEntry.key)) aggregateCard.categoryOrder.push(categoryEntry.key);
|
|
28208
28274
|
const prevCategory = aggregateCard.categories.get(categoryEntry.key);
|
|
28209
28275
|
aggregateCard.categories.set(categoryEntry.key, {
|
|
@@ -29956,6 +30022,10 @@ function createEngineApiA(bag) {
|
|
|
29956
30022
|
},
|
|
29957
30023
|
setCwd: (path4, options = {}) => {
|
|
29958
30024
|
const next = runtime.setCwd(path4);
|
|
30025
|
+
try {
|
|
30026
|
+
touchProjectSelected(next);
|
|
30027
|
+
} catch {
|
|
30028
|
+
}
|
|
29959
30029
|
const sessionList = recomputePromptHistory(getState().items);
|
|
29960
30030
|
set({ cwd: next, promptHistoryList: buildMergedPromptHistory(sessionList, loadPromptHistory(next)) });
|
|
29961
30031
|
if (options?.notice !== false) {
|
|
@@ -30659,7 +30729,12 @@ function createLiveShare({
|
|
|
30659
30729
|
});
|
|
30660
30730
|
attachLineReader(socket, (frame) => {
|
|
30661
30731
|
if (frame.t === "submit" && typeof frame.text === "string" && frame.text.trim()) {
|
|
30662
|
-
|
|
30732
|
+
const id2 = typeof frame.id === "string" && frame.id.trim() ? frame.id : void 0;
|
|
30733
|
+
const submittedAt = Number(frame.submittedAt);
|
|
30734
|
+
onRemoteSubmit(frame.text, {
|
|
30735
|
+
...id2 ? { id: id2 } : {},
|
|
30736
|
+
...Number.isFinite(submittedAt) && submittedAt > 0 ? { submittedAt: Math.round(submittedAt) } : {}
|
|
30737
|
+
});
|
|
30663
30738
|
} else if (frame.t === "abort") {
|
|
30664
30739
|
onRemoteAbort?.();
|
|
30665
30740
|
} else if (frame.t === "sync") {
|
|
@@ -30960,10 +31035,17 @@ function createLiveShare({
|
|
|
30960
31035
|
ensure: ensureShare,
|
|
30961
31036
|
viewerConnected: () => clientUp,
|
|
30962
31037
|
waitForViewerSync,
|
|
30963
|
-
sendSubmit(text) {
|
|
31038
|
+
sendSubmit(text, meta = null) {
|
|
30964
31039
|
if (!clientUp || !client) return false;
|
|
30965
31040
|
try {
|
|
30966
|
-
|
|
31041
|
+
const id = meta && meta.id != null && String(meta.id).trim() ? String(meta.id) : void 0;
|
|
31042
|
+
const submittedAt = Number(meta?.submittedAt);
|
|
31043
|
+
client.write(frameLine({
|
|
31044
|
+
t: "submit",
|
|
31045
|
+
text: String(text),
|
|
31046
|
+
...id ? { id } : {},
|
|
31047
|
+
...Number.isFinite(submittedAt) && submittedAt > 0 ? { submittedAt: Math.round(submittedAt) } : {}
|
|
31048
|
+
}));
|
|
30967
31049
|
return true;
|
|
30968
31050
|
} catch {
|
|
30969
31051
|
return false;
|
|
@@ -32098,9 +32180,9 @@ async function createEngineSession({
|
|
|
32098
32180
|
socketPathFor: (id) => liveSharePipePath(id, sessionPath(id)),
|
|
32099
32181
|
getPublishedState: () => publishedState,
|
|
32100
32182
|
listeners,
|
|
32101
|
-
onRemoteSubmit: (text) => {
|
|
32183
|
+
onRemoteSubmit: (text, meta) => {
|
|
32102
32184
|
if (flags.disposed || state.sessionRemoteAttached) return;
|
|
32103
|
-
bag.enqueue(text);
|
|
32185
|
+
bag.enqueue(text, meta && meta.id ? { id: meta.id, submittedAt: meta.submittedAt } : {});
|
|
32104
32186
|
void bag.drain();
|
|
32105
32187
|
},
|
|
32106
32188
|
onRemoteAbort: () => {
|
|
@@ -32147,7 +32229,7 @@ async function createEngineSession({
|
|
|
32147
32229
|
liveShare.ensure();
|
|
32148
32230
|
} catch {
|
|
32149
32231
|
}
|
|
32150
|
-
if (text && liveShare.sendSubmit(text)) return true;
|
|
32232
|
+
if (text && liveShare.sendSubmit(text, { id: options.id, submittedAt: options.submittedAt })) return true;
|
|
32151
32233
|
return runtime.enqueueRemoteAttachedPrompt?.(prompt) === true;
|
|
32152
32234
|
}
|
|
32153
32235
|
return baseSubmit(prompt, options);
|
|
@@ -32176,7 +32258,7 @@ async function createEngineSession({
|
|
|
32176
32258
|
reconcileLiveShareNow();
|
|
32177
32259
|
if (method === "resume" && result === true && state.sessionRemoteAttached) {
|
|
32178
32260
|
const id = String(state.sessionId || "");
|
|
32179
|
-
if (id && await liveShare.waitForViewerSync(id)) bag.flushEmit();
|
|
32261
|
+
if (id && await liveShare.waitForViewerSync(id, 400)) bag.flushEmit();
|
|
32180
32262
|
}
|
|
32181
32263
|
return result;
|
|
32182
32264
|
};
|
|
@@ -32694,6 +32776,10 @@ async function runTui({ provider, model, toolMode, remote, forceOnboarding } = {
|
|
|
32694
32776
|
const outcome = await storeOutcomePromise;
|
|
32695
32777
|
if (outcome.error) throw outcome.error;
|
|
32696
32778
|
store = outcome.store;
|
|
32779
|
+
try {
|
|
32780
|
+
touchProjectSelected(store.getState?.()?.cwd || process.cwd());
|
|
32781
|
+
} catch {
|
|
32782
|
+
}
|
|
32697
32783
|
bootProfile2("store:ready", { ms: (performance4.now() - startedAt).toFixed(1) });
|
|
32698
32784
|
} catch (error) {
|
|
32699
32785
|
splash.stop();
|
|
@@ -287,7 +287,18 @@ export function createLiveShare({
|
|
|
287
287
|
});
|
|
288
288
|
attachLineReader(socket, (frame) => {
|
|
289
289
|
if (frame.t === 'submit' && typeof frame.text === 'string' && frame.text.trim()) {
|
|
290
|
-
|
|
290
|
+
// Optional submission metadata (additive, old viewers omit it): the
|
|
291
|
+
// originating surface's submission id must survive the pipe so its
|
|
292
|
+
// optimistic user row releases when the SAME id settles in the
|
|
293
|
+
// owner's transcript (user: 방금 친 메세지가 2개 남는다).
|
|
294
|
+
const id = typeof frame.id === 'string' && frame.id.trim() ? frame.id : undefined;
|
|
295
|
+
const submittedAt = Number(frame.submittedAt);
|
|
296
|
+
onRemoteSubmit(frame.text, {
|
|
297
|
+
...(id ? { id } : {}),
|
|
298
|
+
...(Number.isFinite(submittedAt) && submittedAt > 0
|
|
299
|
+
? { submittedAt: Math.round(submittedAt) }
|
|
300
|
+
: {}),
|
|
301
|
+
});
|
|
291
302
|
} else if (frame.t === 'abort') {
|
|
292
303
|
// Viewer stop button: interrupt the owner's active turn here — the
|
|
293
304
|
// viewer process has no turn of its own to cancel.
|
|
@@ -589,10 +600,19 @@ export function createLiveShare({
|
|
|
589
600
|
ensure: ensureShare,
|
|
590
601
|
viewerConnected: () => clientUp,
|
|
591
602
|
waitForViewerSync,
|
|
592
|
-
sendSubmit(text) {
|
|
603
|
+
sendSubmit(text, meta = null) {
|
|
593
604
|
if (!clientUp || !client) return false;
|
|
594
605
|
try {
|
|
595
|
-
|
|
606
|
+
const id = meta && meta.id != null && String(meta.id).trim() ? String(meta.id) : undefined;
|
|
607
|
+
const submittedAt = Number(meta?.submittedAt);
|
|
608
|
+
client.write(frameLine({
|
|
609
|
+
t: 'submit',
|
|
610
|
+
text: String(text),
|
|
611
|
+
...(id ? { id } : {}),
|
|
612
|
+
...(Number.isFinite(submittedAt) && submittedAt > 0
|
|
613
|
+
? { submittedAt: Math.round(submittedAt) }
|
|
614
|
+
: {}),
|
|
615
|
+
}));
|
|
596
616
|
return true;
|
|
597
617
|
} catch {
|
|
598
618
|
return false;
|
|
@@ -8,6 +8,7 @@ import { createEngineApiB } from './session-api-ext.mjs';
|
|
|
8
8
|
import { buildDoctorReport } from '../app/doctor.mjs';
|
|
9
9
|
import { recomputePromptHistory } from './prompt-history.mjs';
|
|
10
10
|
import { buildMergedPromptHistory, loadPromptHistory } from '../prompt-history-store.mjs';
|
|
11
|
+
import { touchProjectSelected } from '../../standalone/projects.mjs';
|
|
11
12
|
|
|
12
13
|
// Upper bound on how long a manual Esc abort may leave the store busy while the
|
|
13
14
|
// in-flight runtime.ask() unwinds. runtime.abort() normally rejects the pending
|
|
@@ -255,6 +256,12 @@ export function createEngineApiA(bag) {
|
|
|
255
256
|
},
|
|
256
257
|
setCwd: (path, options = {}) => {
|
|
257
258
|
const next = runtime.setCwd(path);
|
|
259
|
+
// "Last opened" recency belongs to the ACTUAL cwd switch, not to one
|
|
260
|
+
// caller: the project picker, its Current Path row and the /cwd prompt
|
|
261
|
+
// all land here, so the project list stays ordered by what was really
|
|
262
|
+
// opened last. Unregistered folders are ignored — this never registers
|
|
263
|
+
// a project on its own (addProject remains the only registration path).
|
|
264
|
+
try { touchProjectSelected(next); } catch { /* best-effort recency */ }
|
|
258
265
|
// Republish up-arrow history for the NEW project: current session prompts
|
|
259
266
|
// merged with the cwd-scoped persisted store for the new cwd.
|
|
260
267
|
const sessionList = recomputePromptHistory(getState().items);
|