mixdog 0.9.18 → 0.9.20
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/LICENSE +21 -0
- package/README.md +93 -29
- package/package.json +5 -3
- package/scripts/build-runtime-windows.ps1 +242 -242
- package/scripts/build-tui.mjs +6 -0
- package/scripts/hook-bus-test.mjs +8 -0
- package/scripts/log-writer-guard-smoke.mjs +131 -0
- package/scripts/output-style-smoke.mjs +2 -2
- package/scripts/reactive-compact-persist-smoke.mjs +22 -6
- package/scripts/recall-bench-cases.json +10 -0
- package/scripts/recall-bench.mjs +91 -2
- package/scripts/recall-quality-cases.json +1 -2
- package/scripts/recall-usecase-cases.json +1 -1
- package/scripts/session-ingest-compaction-smoke.mjs +241 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -106
- package/scripts/tool-efficiency-diag.mjs +5 -2
- package/scripts/tool-smoke.mjs +251 -72
- package/src/agents/debugger/AGENT.md +3 -3
- package/src/agents/heavy-worker/AGENT.md +7 -10
- package/src/agents/maintainer/AGENT.md +1 -2
- package/src/agents/reviewer/AGENT.md +1 -2
- package/src/agents/worker/AGENT.md +5 -8
- package/src/defaults/agents.json +3 -0
- package/src/help.mjs +2 -5
- package/src/lib/mixdog-debug.cjs +13 -0
- package/src/mixdog-session-runtime.mjs +7 -3311
- package/src/rules/agent/00-core.md +4 -3
- package/src/rules/agent/30-explorer.md +53 -22
- package/src/rules/lead/02-channels.md +3 -3
- package/src/rules/lead/lead-tool.md +3 -2
- package/src/rules/shared/01-tool.md +24 -29
- package/src/runtime/agent/orchestrator/context/collect.mjs +33 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +24 -3
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +663 -0
- package/src/runtime/agent/orchestrator/session/compact/engine.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +153 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +49 -0
- package/src/runtime/agent/orchestrator/session/loop/steering-ladder.mjs +250 -35
- package/src/runtime/agent/orchestrator/session/loop/stored-tool-args.mjs +9 -1
- package/src/runtime/agent/orchestrator/session/loop.mjs +8 -1860
- package/src/runtime/agent/orchestrator/session/manager/agent-runtime-singleton.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +686 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +60 -12
- package/src/runtime/agent/orchestrator/session/manager/env-utils.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +159 -0
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +143 -0
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +268 -0
- package/src/runtime/agent/orchestrator/session/manager/provider-cache-key.mjs +22 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +124 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +258 -0
- package/src/runtime/agent/orchestrator/session/manager/session-errors.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-id.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +475 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +23 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +6 -4
- package/src/runtime/agent/orchestrator/session/manager.mjs +88 -2285
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +440 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +153 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +642 -0
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +23 -3
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +198 -6
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +10 -2
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +13 -15
- package/src/runtime/agent/orchestrator/tools/builtin/read-batch.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +45 -3
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +195 -3
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +108 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-process.mjs +15 -3
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +24 -2
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +17 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +38 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +72 -8
- package/src/runtime/agent/orchestrator/tools/shell-policy-danger-target.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/shell-state.mjs +1 -1
- package/src/runtime/channels/backends/discord-gateway.mjs +14 -1
- package/src/runtime/channels/backends/discord.mjs +43 -1
- package/src/runtime/channels/index.mjs +6 -2096
- package/src/runtime/channels/lib/inbound-handler.mjs +328 -0
- package/src/runtime/channels/lib/inbound-routing.mjs +19 -12
- package/src/runtime/channels/lib/interaction-handlers.mjs +260 -0
- package/src/runtime/channels/lib/memory-client.mjs +32 -14
- package/src/runtime/channels/lib/network-retry.mjs +23 -0
- package/src/runtime/channels/lib/output-forwarder.mjs +38 -7
- package/src/runtime/channels/lib/owned-runtime.mjs +547 -0
- package/src/runtime/channels/lib/owner-heartbeat.mjs +13 -4
- package/src/runtime/channels/lib/runtime-paths.mjs +35 -1
- package/src/runtime/channels/lib/tool-dispatch.mjs +17 -7
- package/src/runtime/channels/lib/transcript-binding.mjs +336 -0
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +39 -2
- package/src/runtime/channels/lib/worker-bootstrap.mjs +97 -0
- package/src/runtime/channels/lib/worker-ipc.mjs +201 -0
- package/src/runtime/channels/lib/worker-main.mjs +771 -0
- package/src/runtime/memory/index.mjs +73 -1725
- package/src/runtime/memory/lib/embedding-provider.mjs +4 -2
- package/src/runtime/memory/lib/embedding-worker.mjs +47 -6
- package/src/runtime/memory/lib/http-router.mjs +772 -0
- package/src/runtime/memory/lib/memory-action-handlers.mjs +901 -0
- package/src/runtime/memory/lib/memory-embed.mjs +28 -7
- package/src/runtime/memory/lib/memory-recall-scope-filter.mjs +8 -2
- package/src/runtime/memory/lib/memory-recall-store.mjs +182 -9
- package/src/runtime/memory/lib/query-handlers.mjs +38 -6
- package/src/runtime/memory/lib/recall-format.mjs +106 -6
- package/src/runtime/memory/lib/session-ingest-runtime.mjs +401 -0
- package/src/runtime/memory/lib/session-ingest.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +10 -4
- package/src/runtime/shared/background-tasks.mjs +4 -2
- package/src/runtime/shared/tool-execution-contract.mjs +1 -1
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +30 -1
- package/src/session-runtime/boot-profile.mjs +36 -0
- package/src/session-runtime/channel-config-api.mjs +70 -0
- package/src/session-runtime/config-lifecycle.mjs +1 -1
- package/src/session-runtime/context-status.mjs +181 -0
- package/src/session-runtime/cwd-plugins.mjs +46 -3
- package/src/session-runtime/env.mjs +17 -0
- package/src/session-runtime/lifecycle-api.mjs +242 -0
- package/src/session-runtime/mcp-glue.mjs +24 -3
- package/src/session-runtime/model-route-api.mjs +198 -0
- package/src/session-runtime/output-styles.mjs +44 -10
- package/src/session-runtime/provider-auth-api.mjs +135 -0
- package/src/session-runtime/resource-api.mjs +282 -0
- package/src/session-runtime/runtime-core.mjs +2046 -0
- package/src/session-runtime/session-turn-api.mjs +274 -0
- package/src/session-runtime/tool-catalog.mjs +18 -264
- package/src/session-runtime/tool-defs.mjs +2 -2
- package/src/session-runtime/workflow-agents-api.mjs +238 -0
- package/src/session-runtime/workflow.mjs +16 -1
- package/src/standalone/agent-tool.mjs +2 -2
- package/src/standalone/channel-worker.mjs +78 -5
- package/src/standalone/explore-tool.mjs +1 -1
- package/src/standalone/memory-runtime-proxy.mjs +56 -6
- package/src/tui/App.jsx +88 -97
- package/src/tui/app/channel-pickers.mjs +45 -0
- package/src/tui/app/core-memory-picker.mjs +1 -1
- package/src/tui/app/slash-commands.mjs +0 -1
- package/src/tui/app/slash-dispatch.mjs +0 -16
- package/src/tui/app/transcript-window.mjs +44 -1
- package/src/tui/app/use-mouse-input.mjs +15 -2
- package/src/tui/app/use-prompt-handlers.mjs +7 -94
- package/src/tui/app/use-transcript-scroll.mjs +82 -4
- package/src/tui/app/use-transcript-window.mjs +65 -5
- package/src/tui/components/PromptInput.jsx +33 -64
- package/src/tui/components/ToolExecution.jsx +2 -2
- package/src/tui/dist/index.mjs +7908 -7558
- package/src/tui/engine/context-state.mjs +145 -0
- package/src/tui/engine/prompt-history.mjs +27 -0
- package/src/tui/engine/session-api-ext.mjs +478 -0
- package/src/tui/engine/session-api.mjs +545 -0
- package/src/tui/engine/session-flow.mjs +485 -0
- package/src/tui/engine/turn.mjs +1078 -0
- package/src/tui/engine.mjs +69 -2582
- package/src/tui/index.jsx +7 -0
- package/src/tui/lib/voice-setup.mjs +166 -0
- package/src/tui/paste-attachments.mjs +12 -5
- package/src/tui/prompt-history-store.mjs +125 -12
- package/vendor/ink/build/ink.js +16 -1
- package/vendor/ink/build/output.js +30 -4
- package/vendor/ink/build/render.js +5 -0
- package/scripts/bench/cache-probe-tasks.json +0 -8
- package/scripts/bench/lead-review-tasks-r3.json +0 -20
- package/scripts/bench/lead-review-tasks.json +0 -20
- package/scripts/bench/r4-mixed-tasks.json +0 -20
- package/scripts/bench/r5-orchestrated-task.json +0 -7
- package/scripts/bench/review-tasks.json +0 -20
- package/scripts/bench/round-codex.json +0 -114
- package/scripts/bench/round-mixdog-lead-r3.json +0 -269
- package/scripts/bench/round-mixdog-lead.json +0 -269
- package/scripts/bench/round-mixdog.json +0 -126
- package/scripts/bench/round-r10-bigsample.json +0 -679
- package/scripts/bench/round-r11-codexalign.json +0 -257
- package/scripts/bench/round-r13-clientmeta.json +0 -464
- package/scripts/bench/round-r14-betafeatures.json +0 -466
- package/scripts/bench/round-r15-fulldefault.json +0 -462
- package/scripts/bench/round-r16-sessionid.json +0 -466
- package/scripts/bench/round-r17-wirebytes.json +0 -456
- package/scripts/bench/round-r18-prewarm.json +0 -468
- package/scripts/bench/round-r19-clean.json +0 -472
- package/scripts/bench/round-r20-prewarm-clean.json +0 -475
- package/scripts/bench/round-r21-delta-retry.json +0 -473
- package/scripts/bench/round-r22-full-probe.json +0 -693
- package/scripts/bench/round-r23-itemprobe.json +0 -701
- package/scripts/bench/round-r24-shapefix.json +0 -677
- package/scripts/bench/round-r25-serial.json +0 -464
- package/scripts/bench/round-r26-parallel3.json +0 -671
- package/scripts/bench/round-r27-parallel10.json +0 -894
- package/scripts/bench/round-r28-parallel10-stagger.json +0 -882
- package/scripts/bench/round-r29-parallel10-stagger166.json +0 -886
- package/scripts/bench/round-r30-instid.json +0 -253
- package/scripts/bench/round-r31-upgradeprobe.json +0 -256
- package/scripts/bench/round-r32-vs-codex-lead.json +0 -254
- package/scripts/bench/round-r33-vs-codex-codex.json +0 -115
- package/scripts/bench/round-r34-orchestrated.json +0 -120
- package/scripts/bench/round-r35-orchestrated-codex.json +0 -61
- package/scripts/bench/round-r36-orchestrated-capped.json +0 -128
- package/scripts/bench/round-r4-codex.json +0 -114
- package/scripts/bench/round-r4-mixed.json +0 -225
- package/scripts/bench/round-r5-gpt-lead.json +0 -259
- package/scripts/bench/round-r6-codex.json +0 -114
- package/scripts/bench/round-r6-solo.json +0 -257
- package/scripts/bench/round-r7-full.json +0 -254
- package/scripts/bench/round-r8-fulldefault.json +0 -255
- package/src/tui/components/prompt-input/voice-indicator.mjs +0 -39
- package/src/tui/lib/voice-recorder.mjs +0 -469
- package/src/workflows/sequential/WORKFLOW.md +0 -51
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/context-state.mjs - route/context/agent-status derivations.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from engine.mjs unchanged. These read the live runtime + store
|
|
5
|
+
* snapshot and (for the two sync helpers) mutate state.stats / the display
|
|
6
|
+
* context fields IN PLACE — the exact same object the store owns — so the
|
|
7
|
+
* immutable-emit contract is preserved by their callers, which follow up with
|
|
8
|
+
* a set({ stats: { ...state.stats }, ...routeState() }). getState() must return
|
|
9
|
+
* the live (latest) store object; getPendingSessionReset() gates the stats
|
|
10
|
+
* sync exactly as the old inline `pendingSessionReset` closure did.
|
|
11
|
+
*/
|
|
12
|
+
export function createContextState({ runtime, getState, getPendingSessionReset }) {
|
|
13
|
+
const autoClearState = () => runtime.getAutoClear?.() || runtime.autoClear || { enabled: true, idleMs: 60 * 60 * 1000, custom: false, providerDefault: 60 * 60 * 1000, provider: null, minContextPercent: 10 };
|
|
14
|
+
const AGENT_STATUS_CACHE_MS = 250;
|
|
15
|
+
let agentStatusCache = null;
|
|
16
|
+
let agentStatusCacheAt = 0;
|
|
17
|
+
const agentStatusState = ({ force = false } = {}) => {
|
|
18
|
+
const now = Date.now();
|
|
19
|
+
if (!force && agentStatusCache && now - agentStatusCacheAt < AGENT_STATUS_CACHE_MS) return agentStatusCache;
|
|
20
|
+
const status = runtime.agentStatus?.() || {};
|
|
21
|
+
agentStatusCache = {
|
|
22
|
+
agentWorkers: Array.isArray(status.agentWorkers) ? status.agentWorkers : [],
|
|
23
|
+
agentJobs: Array.isArray(status.agentJobs) ? status.agentJobs : [],
|
|
24
|
+
agentScope: status.agentScope || null,
|
|
25
|
+
};
|
|
26
|
+
agentStatusCacheAt = now;
|
|
27
|
+
return agentStatusCache;
|
|
28
|
+
};
|
|
29
|
+
const baseRouteState = () => ({
|
|
30
|
+
sessionId: runtime.id,
|
|
31
|
+
clientHostPid: runtime.clientHostPid || null,
|
|
32
|
+
model: runtime.model,
|
|
33
|
+
provider: runtime.provider,
|
|
34
|
+
effort: runtime.effort,
|
|
35
|
+
effortOptions: runtime.effortOptions,
|
|
36
|
+
fast: runtime.fast,
|
|
37
|
+
fastCapable: runtime.fastCapable,
|
|
38
|
+
contextWindow: runtime.contextWindow,
|
|
39
|
+
rawContextWindow: runtime.rawContextWindow,
|
|
40
|
+
effectiveContextWindowPercent: runtime.effectiveContextWindowPercent,
|
|
41
|
+
cwd: runtime.cwd || process.cwd(),
|
|
42
|
+
systemShell: runtime.systemShell || { source: 'auto', command: '', effective: '' },
|
|
43
|
+
searchRoute: runtime.getSearchRoute?.() || runtime.searchRoute || null,
|
|
44
|
+
autoClear: autoClearState(),
|
|
45
|
+
workflow: runtime.workflow || null,
|
|
46
|
+
remoteEnabled: runtime.isRemoteEnabled?.() === true,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const routeState = () => {
|
|
50
|
+
const state = getState();
|
|
51
|
+
return {
|
|
52
|
+
...baseRouteState(),
|
|
53
|
+
displayContextWindow: state.displayContextWindow || 0,
|
|
54
|
+
compactBoundaryTokens: state.compactBoundaryTokens || 0,
|
|
55
|
+
autoCompactTokenLimit: state.autoCompactTokenLimit || 0,
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
function syncContextDisplayFields(ctx = null) {
|
|
60
|
+
const status = ctx || runtime.contextStatus?.() || null;
|
|
61
|
+
if (!status) return;
|
|
62
|
+
const state = getState();
|
|
63
|
+
const displayWindow = Number(status.contextWindow || 0);
|
|
64
|
+
const compactBoundary = Number(status.compaction?.boundaryTokens || 0);
|
|
65
|
+
// Prefer the resolved trigger (boundary - buffer): the statusline uses it
|
|
66
|
+
// as the display denominator so context % reads 100% exactly when
|
|
67
|
+
// auto-compact fires, instead of stalling at ~90% of the boundary.
|
|
68
|
+
const autoCompact = Number(
|
|
69
|
+
status.compaction?.triggerTokens
|
|
70
|
+
|| status.compaction?.autoCompactTokenLimit
|
|
71
|
+
|| runtime.session?.autoCompactTokenLimit
|
|
72
|
+
|| 0,
|
|
73
|
+
);
|
|
74
|
+
if (displayWindow > 0) state.displayContextWindow = displayWindow;
|
|
75
|
+
if (compactBoundary > 0) state.compactBoundaryTokens = compactBoundary;
|
|
76
|
+
if (autoCompact > 0) state.autoCompactTokenLimit = autoCompact;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const syncContextStats = ({ allowEstimated = false } = {}) => {
|
|
80
|
+
if (getPendingSessionReset()) return null;
|
|
81
|
+
const ctx = runtime.contextStatus?.() || null;
|
|
82
|
+
if (!ctx) return null;
|
|
83
|
+
syncContextDisplayFields(ctx);
|
|
84
|
+
const state = getState();
|
|
85
|
+
const hasProviderUsage = Number(state.stats.latestPromptTokens || state.stats.latestInputTokens || state.stats.inputTokens || 0) > 0;
|
|
86
|
+
const hasApiContextUsage = Number(ctx?.lastApiRequestTokens ?? ctx?.usage?.lastContextTokens ?? 0) > 0;
|
|
87
|
+
const hasTurnActivity = state.busy === true
|
|
88
|
+
|| state.spinner != null
|
|
89
|
+
|| state.thinking != null;
|
|
90
|
+
const isFreshSession = !hasProviderUsage && !hasApiContextUsage && !hasTurnActivity;
|
|
91
|
+
if (isFreshSession) {
|
|
92
|
+
state.stats.currentEstimatedContextTokens = 0;
|
|
93
|
+
state.stats.currentContextTokens = 0;
|
|
94
|
+
state.stats.currentContextSource = null;
|
|
95
|
+
state.stats.currentContextUpdatedAt = Date.now();
|
|
96
|
+
return ctx;
|
|
97
|
+
}
|
|
98
|
+
const estimatedTokens = Math.max(0, Number(ctx.currentEstimatedTokens ?? ctx.usedTokens ?? 0));
|
|
99
|
+
const usedTokens = Math.max(0, Number(ctx.usedTokens ?? estimatedTokens ?? 0));
|
|
100
|
+
const usedSource = String(ctx.usedSource || '').toLowerCase();
|
|
101
|
+
const shouldPublishEstimate = allowEstimated && (
|
|
102
|
+
usedSource === 'estimated'
|
|
103
|
+
|| Number(ctx.currentEstimatedTokens) > 0
|
|
104
|
+
|| usedTokens > 0
|
|
105
|
+
);
|
|
106
|
+
if (!allowEstimated && !hasProviderUsage && usedSource !== 'last_api_request') return ctx;
|
|
107
|
+
if (shouldPublishEstimate) {
|
|
108
|
+
state.stats.currentEstimatedContextTokens = estimatedTokens;
|
|
109
|
+
state.stats.currentContextSource = 'estimated';
|
|
110
|
+
state.stats.currentContextTokens = 0;
|
|
111
|
+
} else if (allowEstimated && (hasProviderUsage || hasApiContextUsage || hasTurnActivity)) {
|
|
112
|
+
state.stats.currentEstimatedContextTokens = estimatedTokens;
|
|
113
|
+
state.stats.currentContextSource = usedSource || (estimatedTokens > 0 ? 'estimated' : null);
|
|
114
|
+
const publishedSource = String(state.stats.currentContextSource || '').toLowerCase();
|
|
115
|
+
if (publishedSource === 'last_api_request') {
|
|
116
|
+
const apiUsed = Math.max(0, Number(ctx.lastApiRequestTokens ?? usedTokens ?? 0));
|
|
117
|
+
state.stats.currentContextTokens = apiUsed;
|
|
118
|
+
} else if (publishedSource === 'estimated') {
|
|
119
|
+
state.stats.currentContextTokens = 0;
|
|
120
|
+
} else {
|
|
121
|
+
state.stats.currentContextTokens = usedTokens > 0 ? usedTokens : 0;
|
|
122
|
+
}
|
|
123
|
+
} else {
|
|
124
|
+
state.stats.currentEstimatedContextTokens = 0;
|
|
125
|
+
if (usedSource === 'last_api_request' && Number(ctx.lastApiRequestTokens ?? usedTokens ?? 0) > 0) {
|
|
126
|
+
state.stats.currentContextTokens = Math.max(0, Number(ctx.lastApiRequestTokens ?? usedTokens ?? 0));
|
|
127
|
+
state.stats.currentContextSource = 'last_api_request';
|
|
128
|
+
} else {
|
|
129
|
+
state.stats.currentContextTokens = 0;
|
|
130
|
+
state.stats.currentContextSource = null;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
state.stats.currentContextUpdatedAt = Date.now();
|
|
134
|
+
return ctx;
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
autoClearState,
|
|
139
|
+
agentStatusState,
|
|
140
|
+
baseRouteState,
|
|
141
|
+
routeState,
|
|
142
|
+
syncContextDisplayFields,
|
|
143
|
+
syncContextStats,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/prompt-history.mjs - pure prompt-history derivation.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from engine.mjs unchanged: the newest-first, deduped user-prompt
|
|
5
|
+
* history the engine publishes on state.promptHistoryList. Pure (input items
|
|
6
|
+
* → array); callers decide when/whether to publish so the store's
|
|
7
|
+
* immutable-emit contract is preserved.
|
|
8
|
+
*/
|
|
9
|
+
export const PROMPT_HISTORY_LIMIT = 50;
|
|
10
|
+
|
|
11
|
+
export const promptHistoryKey = (value) => String(value || '').trim().replace(/\s+/g, ' ');
|
|
12
|
+
|
|
13
|
+
export function recomputePromptHistory(sourceItems, limit = PROMPT_HISTORY_LIMIT) {
|
|
14
|
+
const items = Array.isArray(sourceItems) ? sourceItems : [];
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
const history = [];
|
|
17
|
+
for (let i = items.length - 1; i >= 0 && history.length < limit; i -= 1) {
|
|
18
|
+
const item = items[i];
|
|
19
|
+
if (item?.kind !== 'user') continue;
|
|
20
|
+
const text = String(item.text || '').trim();
|
|
21
|
+
const key = promptHistoryKey(text);
|
|
22
|
+
if (!key || seen.has(key)) continue;
|
|
23
|
+
seen.add(key);
|
|
24
|
+
history.push(text);
|
|
25
|
+
}
|
|
26
|
+
return history;
|
|
27
|
+
}
|
|
@@ -0,0 +1,478 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/engine/session-api-ext.mjs - part of the public engine session object.
|
|
3
|
+
*/
|
|
4
|
+
import { listThemes, getThemeSetting, setThemeSetting } from '../theme.mjs';
|
|
5
|
+
import { resetAllStreamingMarkdownStablePrefixes } from '../markdown/streaming-markdown.mjs';
|
|
6
|
+
import { toolResultText } from './tool-result-text.mjs';
|
|
7
|
+
import { parseSyntheticAgentMessage } from './agent-envelope.mjs';
|
|
8
|
+
import { flushTuiSteeringPersist } from './tui-steering-persist.mjs';
|
|
9
|
+
|
|
10
|
+
export function createEngineApiB(bag) {
|
|
11
|
+
const {
|
|
12
|
+
runtime, nextId, flags, lifecycle, listeners, getState, set, replaceItems, pushNotice, removeNotice, setProgressHint, clearToastTimers, routeState, syncContextStats, finishToolApproval, denyAllToolApprovals, restoreLeadSteeringFromDisk, resetStats, clearUiActivityBeforeContextSync, resetTuiForPendingSessionReset, snapshotTuiBeforeSessionReset, restoreTuiAfterFailedSessionReset, resetStatsAndSyncContext,
|
|
13
|
+
} = bag;
|
|
14
|
+
return {
|
|
15
|
+
resolveToolApproval: (id, decision = {}) => {
|
|
16
|
+
const approved = decision === true || decision?.approved === true;
|
|
17
|
+
return finishToolApproval(id, approved, decision?.reason || (approved ? 'approved by user' : 'denied by user'));
|
|
18
|
+
},
|
|
19
|
+
listPresets: () => {
|
|
20
|
+
return runtime.listPresets();
|
|
21
|
+
},
|
|
22
|
+
listProviderModels: (options = {}) => {
|
|
23
|
+
return runtime.listProviderModels(options);
|
|
24
|
+
},
|
|
25
|
+
getSearchRoute: () => {
|
|
26
|
+
return runtime.getSearchRoute?.() || runtime.searchRoute || null;
|
|
27
|
+
},
|
|
28
|
+
listSearchModels: (options = {}) => {
|
|
29
|
+
return runtime.listSearchModels?.(options) || [];
|
|
30
|
+
},
|
|
31
|
+
setSearchRoute: async (opts) => {
|
|
32
|
+
if (getState().commandBusy) return null;
|
|
33
|
+
const beforeRouteState = routeState();
|
|
34
|
+
const optimisticSearchRoute = opts?.provider && opts?.model
|
|
35
|
+
? {
|
|
36
|
+
provider: String(opts.provider).trim(),
|
|
37
|
+
model: String(opts.model).trim(),
|
|
38
|
+
...(opts.effort ? { effort: opts.effort } : {}),
|
|
39
|
+
...(opts.fast === true ? { fast: true } : {}),
|
|
40
|
+
...(opts.toolType ? { toolType: opts.toolType } : {}),
|
|
41
|
+
}
|
|
42
|
+
: null;
|
|
43
|
+
set({ commandBusy: true });
|
|
44
|
+
try {
|
|
45
|
+
if (optimisticSearchRoute?.provider && optimisticSearchRoute.model) {
|
|
46
|
+
set({ searchRoute: optimisticSearchRoute });
|
|
47
|
+
}
|
|
48
|
+
const result = await runtime.setSearchRoute?.(opts);
|
|
49
|
+
set({ ...routeState(), stats: { ...getState().stats } });
|
|
50
|
+
return result;
|
|
51
|
+
} catch (e) {
|
|
52
|
+
set({ searchRoute: beforeRouteState.searchRoute || null });
|
|
53
|
+
throw e;
|
|
54
|
+
} finally {
|
|
55
|
+
set({ commandBusy: false });
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
listAgents: () => {
|
|
59
|
+
return runtime.listAgents?.() || [];
|
|
60
|
+
},
|
|
61
|
+
listWorkflows: () => {
|
|
62
|
+
return runtime.listWorkflows?.() || [];
|
|
63
|
+
},
|
|
64
|
+
getOutputStyle: () => {
|
|
65
|
+
return runtime.getOutputStyle?.() || runtime.listOutputStyles?.() || null;
|
|
66
|
+
},
|
|
67
|
+
listOutputStyles: () => {
|
|
68
|
+
return runtime.listOutputStyles?.() || runtime.getOutputStyle?.() || { styles: [], current: null, configured: 'default' };
|
|
69
|
+
},
|
|
70
|
+
setOutputStyle: async (styleId) => {
|
|
71
|
+
if (getState().commandBusy) return null;
|
|
72
|
+
set({ commandBusy: true });
|
|
73
|
+
try {
|
|
74
|
+
const result = await runtime.setOutputStyle?.(styleId);
|
|
75
|
+
resetStats();
|
|
76
|
+
set({ ...routeState(), stats: { ...getState().stats } });
|
|
77
|
+
// Defer the context recompute (transcript scan) off this tick so
|
|
78
|
+
// the style change repaints immediately; stats settle right after.
|
|
79
|
+
setTimeout(() => {
|
|
80
|
+
syncContextStats({ allowEstimated: true });
|
|
81
|
+
set({ stats: { ...getState().stats } });
|
|
82
|
+
}, 0);
|
|
83
|
+
return result;
|
|
84
|
+
} finally {
|
|
85
|
+
set({ commandBusy: false });
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
setWorkflow: async (workflowId) => {
|
|
89
|
+
if (getState().commandBusy) return null;
|
|
90
|
+
set({ commandBusy: true });
|
|
91
|
+
try {
|
|
92
|
+
const result = await runtime.setWorkflow?.(workflowId);
|
|
93
|
+
set({ ...routeState(), stats: { ...getState().stats } });
|
|
94
|
+
return result;
|
|
95
|
+
} finally {
|
|
96
|
+
set({ commandBusy: false });
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
// Toggle Discord remote mode for this session. Flips the runtime's
|
|
100
|
+
// remoteEnabled flag (booting/stopping the channel worker) and returns the
|
|
101
|
+
// NEW enabled getState() so the caller can render an ON/OFF notice.
|
|
102
|
+
toggleRemote: () => {
|
|
103
|
+
const enabled = runtime.isRemoteEnabled?.() === true;
|
|
104
|
+
if (enabled) runtime.stopRemote?.();
|
|
105
|
+
else runtime.startRemote?.();
|
|
106
|
+
const next = runtime.isRemoteEnabled?.() === true;
|
|
107
|
+
set({ remoteEnabled: next });
|
|
108
|
+
return next;
|
|
109
|
+
},
|
|
110
|
+
// Force-claim remote for this session (single-holder, last-wins). Always
|
|
111
|
+
// turns remote ON here and steals the bridge seat; the previous holder is
|
|
112
|
+
// superseded and flips itself OFF via onRemoteStateChange. Used by the
|
|
113
|
+
// `/remote` slash command — repeated /remote just re-claims (idempotent).
|
|
114
|
+
claimRemote: () => {
|
|
115
|
+
runtime.startRemote?.();
|
|
116
|
+
const next = runtime.isRemoteEnabled?.() === true;
|
|
117
|
+
set({ remoteEnabled: next });
|
|
118
|
+
return next;
|
|
119
|
+
},
|
|
120
|
+
isRemoteEnabled: () => runtime.isRemoteEnabled?.() === true,
|
|
121
|
+
// Theme is a TUI-local concern (no runtime round-trip). listThemes returns
|
|
122
|
+
// picker metadata; getTheme reports the active id; setTheme applies the
|
|
123
|
+
// palette in-place + persists ui.theme and bumps a themeEpoch so the React
|
|
124
|
+
// tree re-renders (markdown/status/spinner colorizers re-resolve).
|
|
125
|
+
listThemes: () => listThemes(),
|
|
126
|
+
getTheme: () => getThemeSetting(),
|
|
127
|
+
setTheme: (id, options = {}) => {
|
|
128
|
+
const applied = setThemeSetting(id, options);
|
|
129
|
+
set({ themeEpoch: (getState().themeEpoch || 0) + 1 });
|
|
130
|
+
return applied;
|
|
131
|
+
},
|
|
132
|
+
setAgentRoute: async (agentId, opts) => {
|
|
133
|
+
return await runtime.setAgentRoute?.(agentId, opts);
|
|
134
|
+
},
|
|
135
|
+
setDefaultProvider: async (provider) => {
|
|
136
|
+
return await runtime.setDefaultProvider?.(provider);
|
|
137
|
+
},
|
|
138
|
+
listProviders: () => {
|
|
139
|
+
return runtime.listProviders();
|
|
140
|
+
},
|
|
141
|
+
getProviderSetup: () => {
|
|
142
|
+
return runtime.getProviderSetup();
|
|
143
|
+
},
|
|
144
|
+
getUsageDashboard: async (options = {}) => {
|
|
145
|
+
return await runtime.getUsageDashboard?.(options);
|
|
146
|
+
},
|
|
147
|
+
getOnboardingStatus: () => {
|
|
148
|
+
return runtime.getOnboardingStatus?.() || { completed: true, workflowRoutes: {} };
|
|
149
|
+
},
|
|
150
|
+
skipOnboarding: () => {
|
|
151
|
+
// Completed-marking only; no route/agent/provider writes.
|
|
152
|
+
return runtime.skipOnboarding?.() || null;
|
|
153
|
+
},
|
|
154
|
+
completeOnboarding: async (payload = {}) => {
|
|
155
|
+
if (getState().commandBusy) return null;
|
|
156
|
+
set({ commandBusy: true });
|
|
157
|
+
try {
|
|
158
|
+
const result = await runtime.completeOnboarding?.(payload);
|
|
159
|
+
resetStatsAndSyncContext();
|
|
160
|
+
set({ ...routeState(), stats: { ...getState().stats } });
|
|
161
|
+
pushNotice('first-run setup saved', 'info');
|
|
162
|
+
return result;
|
|
163
|
+
} finally {
|
|
164
|
+
set({ commandBusy: false });
|
|
165
|
+
}
|
|
166
|
+
},
|
|
167
|
+
loginOAuthProvider: async (provider) => {
|
|
168
|
+
if (getState().commandBusy) return false;
|
|
169
|
+
set({ commandBusy: true });
|
|
170
|
+
try {
|
|
171
|
+
const result = await runtime.loginOAuthProvider(provider);
|
|
172
|
+
pushNotice(`provider oauth ok: ${result.provider}`, 'info');
|
|
173
|
+
return true;
|
|
174
|
+
} finally {
|
|
175
|
+
set({ commandBusy: false });
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
beginOAuthProviderLogin: async (provider) => {
|
|
179
|
+
if (getState().commandBusy) throw new Error('command busy');
|
|
180
|
+
set({ commandBusy: true });
|
|
181
|
+
try {
|
|
182
|
+
const result = await runtime.beginOAuthProviderLogin(provider);
|
|
183
|
+
pushNotice(`provider oauth started: ${result.provider}`, 'info');
|
|
184
|
+
return result;
|
|
185
|
+
} finally {
|
|
186
|
+
set({ commandBusy: false });
|
|
187
|
+
}
|
|
188
|
+
},
|
|
189
|
+
saveProviderApiKey: (provider, secret) => {
|
|
190
|
+
const result = runtime.saveProviderApiKey(provider, secret);
|
|
191
|
+
pushNotice(`provider api key saved: ${result.provider}`, 'info');
|
|
192
|
+
return true;
|
|
193
|
+
},
|
|
194
|
+
saveOpenCodeGoUsageAuth: (opts) => {
|
|
195
|
+
const result = runtime.saveOpenCodeGoUsageAuth(opts);
|
|
196
|
+
pushNotice(result.workspaceId
|
|
197
|
+
? `OpenCode Go usage auth saved: ${result.workspaceId}`
|
|
198
|
+
: 'OpenCode Go usage auth saved',
|
|
199
|
+
'info');
|
|
200
|
+
return true;
|
|
201
|
+
},
|
|
202
|
+
loginOpenCodeGoUsage: async () => {
|
|
203
|
+
if (getState().commandBusy) throw new Error('command busy');
|
|
204
|
+
set({ commandBusy: true });
|
|
205
|
+
try {
|
|
206
|
+
return await runtime.loginOpenCodeGoUsage();
|
|
207
|
+
} finally {
|
|
208
|
+
set({ commandBusy: false });
|
|
209
|
+
}
|
|
210
|
+
},
|
|
211
|
+
saveOpenAIUsageSessionKey: (secret) => {
|
|
212
|
+
runtime.saveOpenAIUsageSessionKey(secret);
|
|
213
|
+
pushNotice('OpenAI usage auth saved', 'info');
|
|
214
|
+
return true;
|
|
215
|
+
},
|
|
216
|
+
setLocalProvider: (provider, opts) => {
|
|
217
|
+
const result = runtime.setLocalProvider(provider, opts);
|
|
218
|
+
pushNotice(`local provider ${result.enabled ? 'enabled' : 'disabled'}: ${result.provider}`, 'info');
|
|
219
|
+
return true;
|
|
220
|
+
},
|
|
221
|
+
authenticateProvider: async (provider, secret) => {
|
|
222
|
+
if (getState().commandBusy) return false;
|
|
223
|
+
set({ commandBusy: true });
|
|
224
|
+
try {
|
|
225
|
+
const result = await runtime.authenticateProvider(provider, secret);
|
|
226
|
+
pushNotice(`provider auth ok: ${result.provider} (${result.type})`, 'info');
|
|
227
|
+
return true;
|
|
228
|
+
} finally {
|
|
229
|
+
set({ commandBusy: false });
|
|
230
|
+
}
|
|
231
|
+
},
|
|
232
|
+
forgetProviderAuth: (provider) => {
|
|
233
|
+
const result = runtime.forgetProviderAuth(provider);
|
|
234
|
+
pushNotice(`provider auth forgotten: ${result.provider}`, 'info');
|
|
235
|
+
return true;
|
|
236
|
+
},
|
|
237
|
+
getChannelSetup: () => {
|
|
238
|
+
return runtime.getChannelSetup();
|
|
239
|
+
},
|
|
240
|
+
getChannelWorkerStatus: () => runtime.getChannelWorkerStatus?.(),
|
|
241
|
+
setBackend: (name) => runtime.setBackend?.(name),
|
|
242
|
+
saveDiscordToken: (token) => {
|
|
243
|
+
const result = runtime.saveDiscordToken(token);
|
|
244
|
+
pushNotice('discord token saved', 'info');
|
|
245
|
+
return result;
|
|
246
|
+
},
|
|
247
|
+
forgetDiscordToken: () => {
|
|
248
|
+
const result = runtime.forgetDiscordToken();
|
|
249
|
+
pushNotice('discord token forgotten', 'info');
|
|
250
|
+
return result;
|
|
251
|
+
},
|
|
252
|
+
saveTelegramToken: (token) => {
|
|
253
|
+
const result = runtime.saveTelegramToken?.(token);
|
|
254
|
+
pushNotice('telegram token saved', 'info');
|
|
255
|
+
return result;
|
|
256
|
+
},
|
|
257
|
+
forgetTelegramToken: () => {
|
|
258
|
+
const result = runtime.forgetTelegramToken?.();
|
|
259
|
+
pushNotice('telegram token forgotten', 'info');
|
|
260
|
+
return result;
|
|
261
|
+
},
|
|
262
|
+
saveWebhookAuthtoken: (token) => {
|
|
263
|
+
const result = runtime.saveWebhookAuthtoken(token);
|
|
264
|
+
pushNotice('webhook/ngrok authtoken saved', 'info');
|
|
265
|
+
return result;
|
|
266
|
+
},
|
|
267
|
+
forgetWebhookAuthtoken: () => {
|
|
268
|
+
const result = runtime.forgetWebhookAuthtoken();
|
|
269
|
+
pushNotice('webhook/ngrok authtoken forgotten', 'info');
|
|
270
|
+
return result;
|
|
271
|
+
},
|
|
272
|
+
setChannel: (entry) => {
|
|
273
|
+
const result = runtime.setChannel(entry);
|
|
274
|
+
pushNotice('channel saved', 'info');
|
|
275
|
+
return result;
|
|
276
|
+
},
|
|
277
|
+
setWebhookConfig: (patch) => {
|
|
278
|
+
const result = runtime.setWebhookConfig(patch);
|
|
279
|
+
pushNotice('webhook config updated', 'info');
|
|
280
|
+
return result;
|
|
281
|
+
},
|
|
282
|
+
saveSchedule: (entry) => {
|
|
283
|
+
const result = runtime.saveSchedule(entry);
|
|
284
|
+
pushNotice(`schedule saved: ${result.name}`, 'info');
|
|
285
|
+
return result;
|
|
286
|
+
},
|
|
287
|
+
deleteSchedule: (name) => {
|
|
288
|
+
const result = runtime.deleteSchedule(name);
|
|
289
|
+
pushNotice(`schedule deleted: ${name}`, 'info');
|
|
290
|
+
return result;
|
|
291
|
+
},
|
|
292
|
+
setScheduleEnabled: (name, enabled) => {
|
|
293
|
+
const result = runtime.setScheduleEnabled(name, enabled);
|
|
294
|
+
pushNotice(`schedule ${enabled ? 'enabled' : 'disabled'}: ${name}`, 'info');
|
|
295
|
+
return result;
|
|
296
|
+
},
|
|
297
|
+
saveWebhook: (entry) => {
|
|
298
|
+
const result = runtime.saveWebhook(entry);
|
|
299
|
+
pushNotice(`webhook saved: ${result.name}`, 'info');
|
|
300
|
+
return result;
|
|
301
|
+
},
|
|
302
|
+
deleteWebhook: (name) => {
|
|
303
|
+
const result = runtime.deleteWebhook(name);
|
|
304
|
+
pushNotice(`webhook deleted: ${name}`, 'info');
|
|
305
|
+
return result;
|
|
306
|
+
},
|
|
307
|
+
setWebhookEnabled: (name, enabled) => {
|
|
308
|
+
const result = runtime.setWebhookEnabled(name, enabled);
|
|
309
|
+
pushNotice(`webhook ${enabled ? 'enabled' : 'disabled'}: ${name}`, 'info');
|
|
310
|
+
return result;
|
|
311
|
+
},
|
|
312
|
+
setRoute: async (opts) => {
|
|
313
|
+
if (getState().commandBusy) return false;
|
|
314
|
+
set({ commandBusy: true });
|
|
315
|
+
try {
|
|
316
|
+
const routeOpts = opts && typeof opts === 'object' ? opts : {};
|
|
317
|
+
// Default: apply to the NEXT session only. Only an explicit
|
|
318
|
+
// `applyToCurrentSession: true` rewrites the live session in place.
|
|
319
|
+
const applyToCurrentSession = routeOpts.applyToCurrentSession === true;
|
|
320
|
+
const { applyToCurrentSession: _drop, ...nextRoute } = routeOpts;
|
|
321
|
+
await runtime.setRoute(nextRoute, { applyToCurrentSession });
|
|
322
|
+
if (applyToCurrentSession) syncContextStats({ allowEstimated: true });
|
|
323
|
+
set({ ...routeState(), stats: { ...getState().stats } });
|
|
324
|
+
return true;
|
|
325
|
+
} finally {
|
|
326
|
+
set({ commandBusy: false });
|
|
327
|
+
}
|
|
328
|
+
},
|
|
329
|
+
pushNotice,
|
|
330
|
+
removeNotice,
|
|
331
|
+
setProgressHint,
|
|
332
|
+
clear: async () => {
|
|
333
|
+
if (getState().commandBusy) return false;
|
|
334
|
+
set({ commandBusy: true });
|
|
335
|
+
clearToastTimers();
|
|
336
|
+
resetAllStreamingMarkdownStablePrefixes();
|
|
337
|
+
const rollbackSnapshot = snapshotTuiBeforeSessionReset();
|
|
338
|
+
resetTuiForPendingSessionReset();
|
|
339
|
+
set({
|
|
340
|
+
items: getState().items,
|
|
341
|
+
toasts: getState().toasts,
|
|
342
|
+
queued: getState().queued,
|
|
343
|
+
thinking: null,
|
|
344
|
+
spinner: null,
|
|
345
|
+
lastTurn: null,
|
|
346
|
+
sessionId: null,
|
|
347
|
+
stats: { ...getState().stats },
|
|
348
|
+
});
|
|
349
|
+
try {
|
|
350
|
+
await runtime.clear({ recoverAgent: true });
|
|
351
|
+
clearUiActivityBeforeContextSync();
|
|
352
|
+
flags.pendingSessionReset = false;
|
|
353
|
+
resetStatsAndSyncContext();
|
|
354
|
+
set({ items: replaceItems([]), toasts: [], queued: [], thinking: null, spinner: null, lastTurn: null, ...routeState(), stats: { ...getState().stats } });
|
|
355
|
+
flags.lastUserActivityAt = Date.now();
|
|
356
|
+
return true;
|
|
357
|
+
} catch (error) {
|
|
358
|
+
restoreTuiAfterFailedSessionReset(rollbackSnapshot);
|
|
359
|
+
throw error;
|
|
360
|
+
} finally {
|
|
361
|
+
flags.pendingSessionReset = false;
|
|
362
|
+
set({ commandBusy: false });
|
|
363
|
+
}
|
|
364
|
+
},
|
|
365
|
+
listSessions: () => {
|
|
366
|
+
return runtime.listSessions();
|
|
367
|
+
},
|
|
368
|
+
newSession: async () => {
|
|
369
|
+
if (getState().commandBusy) return false;
|
|
370
|
+
set({ commandBusy: true });
|
|
371
|
+
clearToastTimers();
|
|
372
|
+
resetAllStreamingMarkdownStablePrefixes();
|
|
373
|
+
const rollbackSnapshot = snapshotTuiBeforeSessionReset();
|
|
374
|
+
resetTuiForPendingSessionReset();
|
|
375
|
+
set({
|
|
376
|
+
items: getState().items,
|
|
377
|
+
toasts: getState().toasts,
|
|
378
|
+
queued: getState().queued,
|
|
379
|
+
thinking: null,
|
|
380
|
+
spinner: null,
|
|
381
|
+
lastTurn: null,
|
|
382
|
+
sessionId: null,
|
|
383
|
+
stats: { ...getState().stats },
|
|
384
|
+
});
|
|
385
|
+
try {
|
|
386
|
+
await runtime.newSession();
|
|
387
|
+
clearUiActivityBeforeContextSync();
|
|
388
|
+
flags.pendingSessionReset = false;
|
|
389
|
+
resetStatsAndSyncContext();
|
|
390
|
+
set({ items: replaceItems([]), toasts: [], queued: [], thinking: null, spinner: null, lastTurn: null, ...routeState(), stats: { ...getState().stats } });
|
|
391
|
+
return true;
|
|
392
|
+
} catch (error) {
|
|
393
|
+
restoreTuiAfterFailedSessionReset(rollbackSnapshot);
|
|
394
|
+
throw error;
|
|
395
|
+
} finally {
|
|
396
|
+
flags.pendingSessionReset = false;
|
|
397
|
+
set({ commandBusy: false });
|
|
398
|
+
}
|
|
399
|
+
},
|
|
400
|
+
resume: async (id) => {
|
|
401
|
+
if (getState().commandBusy) return false;
|
|
402
|
+
set({ commandBusy: true, commandStatus: { active: true, verb: 'Resuming conversation', startedAt: Date.now(), mode: 'resuming' } });
|
|
403
|
+
clearToastTimers();
|
|
404
|
+
try {
|
|
405
|
+
const r = await runtime.resume(id);
|
|
406
|
+
if (!r) return false;
|
|
407
|
+
resetStatsAndSyncContext();
|
|
408
|
+
const items = [];
|
|
409
|
+
for (const m of r.messages || []) {
|
|
410
|
+
if (m.role === 'user') {
|
|
411
|
+
// content may be a string OR an array of parts (text/tool-call
|
|
412
|
+
// interleaving) — toolResultText coerces both to readable text so
|
|
413
|
+
// array-content messages aren't silently dropped.
|
|
414
|
+
const text = (typeof m.content === 'string' ? m.content : toolResultText(m.content)).trim();
|
|
415
|
+
if (text) {
|
|
416
|
+
const synthetic = parseSyntheticAgentMessage(text);
|
|
417
|
+
if (synthetic) {
|
|
418
|
+
const label = synthetic.label || 'notification';
|
|
419
|
+
items.push({
|
|
420
|
+
kind: 'tool',
|
|
421
|
+
id: nextId(),
|
|
422
|
+
name: synthetic.name || 'agent',
|
|
423
|
+
args: synthetic.args || {
|
|
424
|
+
type: label,
|
|
425
|
+
task_id: synthetic.taskId || undefined,
|
|
426
|
+
description: synthetic.summary || 'agent notification',
|
|
427
|
+
},
|
|
428
|
+
result: synthetic.result,
|
|
429
|
+
rawResult: synthetic.rawResult ?? text,
|
|
430
|
+
isError: synthetic.isError ?? /^(failed|error|killed|cancelled)$/i.test(label),
|
|
431
|
+
expanded: false,
|
|
432
|
+
count: 1,
|
|
433
|
+
completedCount: 1,
|
|
434
|
+
startedAt: Date.now(),
|
|
435
|
+
completedAt: Date.now(),
|
|
436
|
+
});
|
|
437
|
+
} else {
|
|
438
|
+
items.push({ kind: 'user', id: nextId(), text });
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
} else if (m.role === 'assistant') {
|
|
442
|
+
const text = (typeof m.content === 'string' ? m.content : toolResultText(m.content)).trim();
|
|
443
|
+
if (text) items.push({ kind: 'assistant', id: nextId(), text });
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
set({
|
|
447
|
+
items: replaceItems(items),
|
|
448
|
+
toasts: [],
|
|
449
|
+
queued: [],
|
|
450
|
+
thinking: null,
|
|
451
|
+
spinner: null,
|
|
452
|
+
lastTurn: null,
|
|
453
|
+
...routeState(),
|
|
454
|
+
stats: { ...getState().stats },
|
|
455
|
+
});
|
|
456
|
+
void restoreLeadSteeringFromDisk();
|
|
457
|
+
return true;
|
|
458
|
+
} finally {
|
|
459
|
+
set({ commandBusy: false, commandStatus: null });
|
|
460
|
+
}
|
|
461
|
+
},
|
|
462
|
+
|
|
463
|
+
dispose: async (reason = 'cli-react-exit', options = {}) => {
|
|
464
|
+
if (flags.disposed) return;
|
|
465
|
+
flags.disposed = true;
|
|
466
|
+
clearToastTimers();
|
|
467
|
+
try { clearInterval(lifecycle.runtimePulseTimer); } catch {}
|
|
468
|
+
try { lifecycle.unsubscribeRuntimeNotifications?.(); } catch {}
|
|
469
|
+
lifecycle.unsubscribeRuntimeNotifications = null;
|
|
470
|
+
try { lifecycle.unsubscribeRemoteState?.(); } catch {}
|
|
471
|
+
lifecycle.unsubscribeRemoteState = null;
|
|
472
|
+
denyAllToolApprovals('runtime closing');
|
|
473
|
+
await flushTuiSteeringPersist();
|
|
474
|
+
await runtime.close(reason, options);
|
|
475
|
+
listeners.clear();
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
}
|