mixdog 0.9.95 → 0.9.97
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/LICENSES/Apache-2.0.txt +201 -0
- package/LICENSES/MIT.txt +56 -0
- package/LICENSES/codex-NOTICE.txt +6 -0
- package/NOTICE.md +46 -9
- package/README.md +0 -17
- package/package.json +16 -12
- package/scripts/.tmp-coverage-report.mjs +96 -0
- package/scripts/build-tui.mjs +17 -1
- package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
- package/scripts/fixtures/patch-replay-corpus.json +98 -0
- package/src/defaults/mixdog-config.template.json +5 -8
- package/src/defaults/skills/setup/SKILL.md +1 -1
- package/src/lib/keychain-cjs.cjs +1 -1
- package/src/lib/mixdog-debug.cjs +2 -3
- package/src/rules/agent/30-explorer.md +7 -4
- package/src/rules/lead/01-general.md +1 -2
- package/src/rules/lead/lead-tool.md +0 -3
- package/src/rules/shared/01-tool.md +21 -29
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +18 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +31 -1
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +6 -17
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +12 -1
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
- package/src/runtime/agent/orchestrator/config.mjs +246 -67
- package/src/runtime/agent/orchestrator/mcp/client.mjs +104 -41
- package/src/runtime/agent/orchestrator/mcp/reconnect-singleflight.mjs +23 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +113 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +5 -1
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +32 -11
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +89 -2
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +12 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-xai.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +144 -39
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +22 -8
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +15 -1
- package/src/runtime/agent/orchestrator/providers/openai-transport-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +3 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +3 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +67 -7
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +291 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +36 -10
- package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -33
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/loop/termination.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +0 -24
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +21 -29
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +7 -10
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +45 -25
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +142 -84
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/session/manager/runtime-loaders.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +31 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +29 -9
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/result-classification.mjs +65 -0
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +86 -8
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +10 -3
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +71 -42
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +4 -33
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +4 -5
- package/src/runtime/agent/orchestrator/stall-policy.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +35 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +116 -26
- package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +8 -9
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +6 -15
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +12 -26
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +83 -33
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +42 -45
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +27 -9
- package/src/runtime/agent/orchestrator/tools/builtin/search-path-diagnostics.mjs +7 -0
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +17 -39
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +73 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-job-spawn.mjs +0 -2
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +3 -8
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +30 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +5 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +142 -26
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +47 -19
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +83 -13
- package/src/runtime/agent/orchestrator/tools/patch/matcher.mjs +39 -1
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +94 -20
- package/src/runtime/agent/orchestrator/tools/patch/parsing.mjs +16 -0
- package/src/runtime/agent/orchestrator/tools/patch/v4a-convert.mjs +108 -12
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +12 -11
- package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +63 -9
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +2 -2
- package/src/runtime/channels/lib/config.mjs +34 -34
- package/src/runtime/channels/lib/inbound-handler.mjs +8 -8
- package/src/runtime/channels/lib/interaction-handlers.mjs +6 -6
- package/src/runtime/channels/lib/memory-client.mjs +31 -157
- package/src/runtime/channels/lib/output-forwarder.mjs +16 -16
- package/src/runtime/channels/lib/owned-runtime.mjs +60 -60
- package/src/runtime/channels/lib/owner-heartbeat.mjs +1 -1
- package/src/runtime/channels/lib/{backend-dispatch.mjs → provider-dispatch.mjs} +6 -6
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -30
- package/src/runtime/channels/lib/scheduler.mjs +2 -2
- package/src/runtime/channels/lib/status-snapshot.mjs +3 -3
- package/src/runtime/channels/lib/tool-dispatch.mjs +4 -4
- package/src/runtime/channels/lib/worker-bootstrap.mjs +1 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +7 -7
- package/src/runtime/channels/lib/worker-main.mjs +38 -38
- package/src/runtime/channels/{backends → providers}/discord-access.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord-gateway.mjs +1 -1
- package/src/runtime/channels/{backends → providers}/discord.mjs +3 -3
- package/src/runtime/channels/{backends → providers}/telegram.mjs +12 -12
- package/src/runtime/channels/tool-defs.mjs +1 -1
- package/src/runtime/memory/index.mjs +76 -248
- package/src/runtime/memory/lib/agent-ipc.mjs +146 -81
- package/src/runtime/memory/lib/compact-vector-cache.mjs +88 -0
- package/src/runtime/memory/lib/core-memory-store.mjs +18 -1
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +13 -34
- package/src/runtime/memory/lib/embedding-provider.mjs +47 -26
- package/src/runtime/memory/lib/embedding-worker.mjs +30 -34
- package/src/runtime/memory/lib/http-router.mjs +9 -6
- package/src/runtime/memory/lib/ko-morph.mjs +3 -20
- package/src/runtime/memory/lib/memory-action-handlers.mjs +2 -1
- package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle1.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-gate.mjs +4 -2
- package/src/runtime/memory/lib/memory-cycle2-mutations.mjs +1 -0
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -1
- package/src/runtime/memory/lib/memory-cycle3.mjs +3 -1
- package/src/runtime/memory/lib/memory-daemon-lifecycle.mjs +5 -0
- package/src/runtime/memory/lib/memory-log.mjs +1 -1
- package/src/runtime/memory/lib/memory-process-lock.mjs +5 -60
- package/src/runtime/memory/lib/memory.mjs +1 -1
- package/src/runtime/memory/lib/pg/process.mjs +32 -9
- package/src/runtime/memory/lib/session-ingest.mjs +1 -1
- package/src/runtime/memory/tool-defs.mjs +1 -5
- package/src/runtime/search/index.mjs +2 -2
- package/src/runtime/search/tool-defs.mjs +2 -2
- package/src/runtime/shared/abort-race.mjs +70 -0
- package/src/runtime/shared/agent-route-config.mjs +119 -0
- package/src/runtime/shared/atomic-file.mjs +80 -4
- package/src/runtime/shared/channel-notification-routing.mjs +2 -2
- package/src/runtime/shared/child-guardian.mjs +204 -108
- package/src/runtime/shared/child-spawn-gate.mjs +139 -72
- package/src/runtime/shared/config.mjs +120 -47
- package/src/runtime/shared/llm/index.mjs +1 -1
- package/src/runtime/shared/owner-fair-gate.mjs +134 -0
- package/src/runtime/shared/resource-admission.mjs +150 -100
- package/src/runtime/shared/stream-progress.mjs +6 -0
- package/src/runtime/shared/tool-execution-owner.mjs +12 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -1
- package/src/runtime/shared/tool-surface.mjs +91 -27
- package/src/runtime/shared/tool-workload-gates.mjs +29 -0
- package/src/runtime/shared/turn-snapshot.mjs +47 -6
- package/src/runtime/shared/turn-worktree-snapshot.mjs +14 -2
- package/src/session-runtime/channel-config-api.mjs +5 -5
- package/src/session-runtime/config-helpers.mjs +39 -37
- package/src/session-runtime/config-lifecycle.mjs +27 -27
- package/src/session-runtime/lifecycle-api.mjs +22 -3
- package/src/session-runtime/memory-runtime-prewarm.mjs +15 -0
- package/src/session-runtime/model-route-api.mjs +7 -5
- package/src/session-runtime/prewarm.mjs +1 -1
- package/src/session-runtime/provider-auth-api.mjs +0 -7
- package/src/session-runtime/provider-models.mjs +1 -1
- package/src/session-runtime/remote-control.mjs +6 -7
- package/src/session-runtime/runtime-core.mjs +63 -39
- package/src/session-runtime/runtime-tunables.mjs +4 -4
- package/src/session-runtime/self-update.mjs +1 -1
- package/src/session-runtime/session-lifecycle.mjs +45 -17
- package/src/session-runtime/session-title.mjs +1 -1
- package/src/session-runtime/session-turn-api.mjs +96 -24
- package/src/session-runtime/settings-api.mjs +12 -24
- package/src/session-runtime/tool-defs.mjs +3 -3
- package/src/session-runtime/workflow-agents-api.mjs +55 -81
- package/src/session-runtime/workflow.mjs +14 -24
- package/src/standalone/agent-dispatch-broker.mjs +198 -0
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/agent-tool/spawn-flow.mjs +2 -0
- package/src/standalone/agent-tool/spawn-preset.mjs +11 -21
- package/src/standalone/agent-tool/tool-def.mjs +0 -14
- package/src/standalone/channel-admin.mjs +53 -64
- package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +33 -19
- package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +127 -46
- package/src/standalone/channel-worker.mjs +124 -647
- package/src/standalone/{backend-daemon.mjs → daemon.mjs} +270 -154
- package/src/standalone/explore-tool.mjs +187 -70
- package/src/standalone/fair-call-scheduler.mjs +264 -0
- package/src/standalone/memory-runtime-proxy.mjs +58 -1
- package/src/standalone/session-client.mjs +1134 -0
- package/src/standalone/session-protocol.mjs +192 -0
- package/src/standalone/session-runtime-pool.mjs +393 -0
- package/src/standalone/session-runtime-record.mjs +20 -0
- package/src/standalone/session-runtime-worker.mjs +203 -0
- package/src/standalone/{engine-daemon-service.mjs → session-service.mjs} +481 -224
- package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +390 -121
- package/src/standalone/session-wire.mjs +55 -0
- package/src/tui/App.jsx +34 -21
- package/src/tui/app/app-format.mjs +1 -1
- package/src/tui/app/app-view.jsx +5 -2
- package/src/tui/app/channel-pickers.mjs +41 -41
- package/src/tui/app/core-memory-picker.mjs +19 -19
- package/src/tui/app/doctor.mjs +5 -11
- package/src/tui/app/input-parsers.mjs +2 -2
- package/src/tui/app/message-selector.mjs +103 -0
- package/src/tui/app/onboarding-steps.mjs +3 -4
- package/src/tui/app/project-picker.mjs +80 -56
- package/src/tui/app/prompt-submit.mjs +48 -43
- package/src/tui/app/resume-picker.mjs +2 -2
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/app/settings-picker.mjs +31 -33
- package/src/tui/app/transcript-window.mjs +38 -0
- package/src/tui/app/use-prompt-draft-flow.mjs +5 -7
- package/src/tui/app/use-prompt-handlers.mjs +86 -21
- package/src/tui/app/use-prompt-hint.mjs +3 -2
- package/src/tui/app/use-prompt-queue-history.mjs +10 -3
- package/src/tui/app/use-transcript-scroll.mjs +10 -3
- package/src/tui/app/use-transcript-window.mjs +50 -17
- package/src/tui/components/PromptInput.jsx +71 -38
- package/src/tui/components/Spinner.jsx +1 -1
- package/src/tui/components/ToolExecution.jsx +1 -1
- package/src/tui/components/prompt-input/edit-helpers.mjs +9 -11
- package/src/tui/components/prompt-input/escape-policy.mjs +42 -0
- package/src/tui/components/prompt-input/immediate-render.mjs +0 -10
- package/src/tui/components/prompt-input/interrupt-policy.mjs +10 -0
- package/src/tui/components/prompt-input/restore-policy.mjs +10 -0
- package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
- package/src/tui/dist/index.mjs +1031 -1676
- package/src/tui/hooks/useSession.mjs +16 -0
- package/src/tui/index.jsx +12 -13
- package/src/tui/{engine → session}/agent-envelope.mjs +3 -3
- package/src/tui/{engine → session}/agent-job-feed.mjs +4 -4
- package/src/tui/{engine → session}/boot-profile.mjs +2 -2
- package/src/tui/{engine → session}/context-state.mjs +3 -3
- package/src/tui/{engine → session}/labels.mjs +2 -2
- package/src/tui/{engine → session}/live-share.mjs +38 -4
- package/src/tui/{engine → session}/notice-text.mjs +2 -2
- package/src/tui/{engine → session}/notification-plan.mjs +3 -3
- package/src/tui/session/oauth-flows.mjs +140 -0
- package/src/tui/{engine → session}/prompt-history.mjs +3 -3
- package/src/tui/{engine → session}/queue-helpers.mjs +2 -2
- package/src/tui/{engine → session}/render-timing.mjs +2 -2
- package/src/tui/{engine → session}/session-api-ext.mjs +30 -20
- package/src/tui/{engine → session}/session-api.mjs +110 -93
- package/src/tui/{engine → session}/session-flow.mjs +8 -2
- package/src/tui/{engine → session}/session-stats.mjs +3 -3
- package/src/tui/{engine → session}/tool-approval.mjs +3 -3
- package/src/tui/{engine → session}/tool-call-fields.mjs +2 -2
- package/src/tui/{engine → session}/tool-card-results.mjs +8 -6
- package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
- package/src/tui/{engine → session}/tool-result-text.mjs +13 -3
- package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
- package/src/tui/{engine → session}/turn.mjs +38 -176
- package/src/tui/{engine-local-session.mjs → session-local.mjs} +103 -103
- package/src/tui/session.mjs +22 -0
- package/src/tui/spinner-meta.mjs +4 -4
- package/src/tui/spinner-verbs.mjs +51 -3
- package/src/ui/statusline-segments.mjs +1 -1
- package/src/ui/statusline.mjs +1 -1
- package/vendor/ink/build/components/App.js +10 -1
- package/src/session-runtime/memory-daemon-probe.mjs +0 -61
- package/src/standalone/engine-daemon-client.mjs +0 -894
- package/src/standalone/engine-daemon-local-bridge.mjs +0 -20
- package/src/standalone/engine-daemon-protocol.mjs +0 -33
- package/src/tui/engine/turn-watchdog.mjs +0 -92
- package/src/tui/engine.mjs +0 -22
- package/src/tui/hooks/useEngine.mjs +0 -16
- /package/src/runtime/agent/orchestrator/session/compact/{engine.mjs → runner.mjs} +0 -0
- /package/src/runtime/channels/{backends → providers}/discord-attachments.mjs +0 -0
- /package/src/tui/{engine → session}/agent-response-tail.mjs +0 -0
- /package/src/tui/{engine → session}/frame-batched-store.mjs +0 -0
- /package/src/tui/{engine → session}/tui-steering-persist.mjs +0 -0
|
@@ -6,8 +6,10 @@ import { executeInternalTool } from '../../internal-tools.mjs';
|
|
|
6
6
|
import { classifyResultKind } from '../result-classification.mjs';
|
|
7
7
|
import { tryPrefetchCached, setPrefetchCached } from '../read-dedup.mjs';
|
|
8
8
|
import { _executeCodeGraphToolLazy } from './runtime-loaders.mjs';
|
|
9
|
+
import { runAbortable, throwIfAborted } from '../../../../shared/abort-race.mjs';
|
|
9
10
|
|
|
10
|
-
export async function _tryBridgeExplicitPrefetch(session, explicitPrefetch) {
|
|
11
|
+
export async function _tryBridgeExplicitPrefetch(session, explicitPrefetch, signal = null) {
|
|
12
|
+
throwIfAborted(signal);
|
|
11
13
|
if (!explicitPrefetch || typeof explicitPrefetch !== 'object') return null;
|
|
12
14
|
if (!isAgentOwner(session)) return null;
|
|
13
15
|
const parts = [];
|
|
@@ -52,7 +54,10 @@ export async function _tryBridgeExplicitPrefetch(session, explicitPrefetch) {
|
|
|
52
54
|
totalEntries.push(...files);
|
|
53
55
|
// R20: per-file prefetch cache (cross-dispatch, process-local).
|
|
54
56
|
// Try each file from cache first; batch misses into one disk read.
|
|
55
|
-
const { resolve: _pfResolve, isAbsolute: _pfIsAbs, normalize: _pfNorm } = await
|
|
57
|
+
const { resolve: _pfResolve, isAbsolute: _pfIsAbs, normalize: _pfNorm } = await runAbortable(
|
|
58
|
+
signal,
|
|
59
|
+
() => import('path'),
|
|
60
|
+
);
|
|
56
61
|
const _pfCwd = session.cwd || null;
|
|
57
62
|
function _pfAbsPath(f) {
|
|
58
63
|
const abs = _pfIsAbs(f) ? f : _pfResolve(_pfCwd || process.cwd(), f);
|
|
@@ -79,7 +84,7 @@ export async function _tryBridgeExplicitPrefetch(session, explicitPrefetch) {
|
|
|
79
84
|
if (missFiles.length > 0) {
|
|
80
85
|
// Read each miss file individually so we can cache per-file.
|
|
81
86
|
// The files list is small (typically 2-5), so N awaits is fine.
|
|
82
|
-
await Promise.all(missFiles.map(async (f) => {
|
|
87
|
+
await runAbortable(signal, () => Promise.all(missFiles.map(async (f) => {
|
|
83
88
|
const opts = _readOptsByFile.get(f) || {};
|
|
84
89
|
const readArgs = { path: f };
|
|
85
90
|
if (opts.mode === 'full') {
|
|
@@ -95,7 +100,7 @@ export async function _tryBridgeExplicitPrefetch(session, explicitPrefetch) {
|
|
|
95
100
|
if (out !== null) {
|
|
96
101
|
missResults[f] = String(out);
|
|
97
102
|
}
|
|
98
|
-
}));
|
|
103
|
+
})));
|
|
99
104
|
// Cache successful miss results.
|
|
100
105
|
for (const { file, abs } of fileMisses) {
|
|
101
106
|
const content = missResults[file];
|
|
@@ -157,7 +162,7 @@ export async function _tryBridgeExplicitPrefetch(session, explicitPrefetch) {
|
|
|
157
162
|
return { symbol, out: null };
|
|
158
163
|
});
|
|
159
164
|
});
|
|
160
|
-
const callerResults = await Promise.allSettled(callerTasks);
|
|
165
|
+
const callerResults = await runAbortable(signal, () => Promise.allSettled(callerTasks));
|
|
161
166
|
for (const r of callerResults) {
|
|
162
167
|
const { symbol, out } = r.status === 'fulfilled' ? r.value : { symbol: '?', out: null };
|
|
163
168
|
if (out && classifyResultKind(String(out)) !== 'error') {
|
|
@@ -182,7 +187,7 @@ export async function _tryBridgeExplicitPrefetch(session, explicitPrefetch) {
|
|
|
182
187
|
return { symbol, out: null };
|
|
183
188
|
});
|
|
184
189
|
});
|
|
185
|
-
const refResults = await Promise.allSettled(refTasks);
|
|
190
|
+
const refResults = await runAbortable(signal, () => Promise.allSettled(refTasks));
|
|
186
191
|
for (const r of refResults) {
|
|
187
192
|
const { symbol, out } = r.status === 'fulfilled' ? r.value : { symbol: '?', out: null };
|
|
188
193
|
if (out && classifyResultKind(String(out)) !== 'error') {
|
|
@@ -192,6 +197,7 @@ export async function _tryBridgeExplicitPrefetch(session, explicitPrefetch) {
|
|
|
192
197
|
}
|
|
193
198
|
}
|
|
194
199
|
}
|
|
200
|
+
throwIfAborted(signal);
|
|
195
201
|
if (session && typeof session === 'object' && (callers.length > 0 || references.length > 0)) {
|
|
196
202
|
if (!session.prefetchStats) session.prefetchStats = { files: 0, cached: 0, miss: 0, failed: 0, callers: 0, references: 0 };
|
|
197
203
|
session.prefetchStats.callers = (session.prefetchStats.callers || 0) + callers.length;
|
|
@@ -17,6 +17,10 @@ export async function _getAgentLoop() {
|
|
|
17
17
|
if (typeof mod.agentLoop !== 'function') throw new Error('agent loop runtime is not available');
|
|
18
18
|
return mod.agentLoop;
|
|
19
19
|
}
|
|
20
|
+
export async function prewarmAgentLoop() {
|
|
21
|
+
await _getAgentLoop();
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
20
24
|
export function _closeBashSessionLazy(sessionId, reason) {
|
|
21
25
|
if (!sessionId) return;
|
|
22
26
|
_bashSessionRuntimePromise ??= import('../../tools/bash-session.mjs');
|
|
@@ -6,7 +6,7 @@ import { loadSession, saveSessionAsync, setLiveSession, evictLiveSession, listSt
|
|
|
6
6
|
import { estimateMessagesTokens, estimateTranscriptContextUsage } from '../context-utils.mjs';
|
|
7
7
|
import { normalizeCompactType, DEFAULT_COMPACT_TYPE, SUMMARY_PREFIX } from '../compact.mjs';
|
|
8
8
|
import { runSessionCompaction } from './compaction-runner.mjs';
|
|
9
|
-
import { hasUserConversationMessage } from './prompt-utils.mjs';
|
|
9
|
+
import { hasUserConversationMessage, promptContentText } from './prompt-utils.mjs';
|
|
10
10
|
import { getProvider } from '../../providers/registry.mjs';
|
|
11
11
|
import { isSessionCompactionBlocked, getSessionAbortSignal, _runtimeEntries } from './runtime-liveness.mjs';
|
|
12
12
|
import { mintSessionId } from './session-id.mjs';
|
|
@@ -314,3 +314,33 @@ export async function updateSessionStatus(id, status) {
|
|
|
314
314
|
await saveSessionAsync(session, { expectedGeneration: session.generation });
|
|
315
315
|
return true;
|
|
316
316
|
}
|
|
317
|
+
|
|
318
|
+
// --- Rewind to a previous user message (message selector) ---
|
|
319
|
+
// Claude Code's "restore conversation": the selected prompt and everything
|
|
320
|
+
// after it leave the model history so the user can edit and resubmit it.
|
|
321
|
+
// Earlier turns — including any compaction rewrite that precedes them — stay
|
|
322
|
+
// exactly as they are. Idle-only; the caller interrupts a live turn first.
|
|
323
|
+
export async function rewindSessionMessagesTo(sessionId, options = {}) {
|
|
324
|
+
const session = loadSession(sessionId);
|
|
325
|
+
if (!session || session.closed === true) return null;
|
|
326
|
+
const target = String(options?.text ?? '').trim();
|
|
327
|
+
if (!target) return null;
|
|
328
|
+
const messages = Array.isArray(session.messages) ? session.messages : [];
|
|
329
|
+
let start = -1;
|
|
330
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
331
|
+
const message = messages[index];
|
|
332
|
+
if (message?.role !== 'user') continue;
|
|
333
|
+
if (promptContentText(message.content).trim() !== target) continue;
|
|
334
|
+
start = index;
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
if (start < 0) return null;
|
|
338
|
+
const removed = messages.length - start;
|
|
339
|
+
session.messages = messages.slice(0, start);
|
|
340
|
+
// The provider cache is keyed on the exact prefix we just truncated.
|
|
341
|
+
session.providerState = undefined;
|
|
342
|
+
session.updatedAt = Date.now();
|
|
343
|
+
session.lastUsedAt = Date.now();
|
|
344
|
+
await saveSessionAsync(session, { expectedGeneration: session.generation });
|
|
345
|
+
return { removed, remaining: session.messages.length };
|
|
346
|
+
}
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// Per-session mutex extracted verbatim from manager.mjs. Queues concurrent
|
|
3
3
|
// askSession calls (and their drained pending-message tail turns) to prevent
|
|
4
4
|
// message loss / interleaving.
|
|
5
|
+
import { abortReason } from '../../../../shared/abort-race.mjs';
|
|
6
|
+
|
|
5
7
|
const _sessionLocks = new Map();
|
|
6
|
-
export function acquireSessionLock(sessionId) {
|
|
8
|
+
export function acquireSessionLock(sessionId, signal = null) {
|
|
7
9
|
let entry = _sessionLocks.get(sessionId);
|
|
8
10
|
if (!entry) {
|
|
9
11
|
entry = { promise: Promise.resolve(), count: 0 };
|
|
@@ -15,9 +17,43 @@ export function acquireSessionLock(sessionId) {
|
|
|
15
17
|
entry.promise = new Promise(r => { release = r; });
|
|
16
18
|
// Self-heal: if the previous holder rejected, swallow so subsequent
|
|
17
19
|
// queued waiters don't propagate that rejection and brick the lock chain.
|
|
18
|
-
|
|
20
|
+
const previousReady = prev.catch(() => {});
|
|
21
|
+
let slotReleased = false;
|
|
22
|
+
const releaseSlot = () => {
|
|
23
|
+
if (slotReleased) return;
|
|
24
|
+
slotReleased = true;
|
|
19
25
|
entry.count--;
|
|
20
26
|
if (entry.count === 0) _sessionLocks.delete(sessionId);
|
|
21
27
|
release();
|
|
28
|
+
};
|
|
29
|
+
const unlock = () => releaseSlot();
|
|
30
|
+
if (!signal) return previousReady.then(() => unlock);
|
|
31
|
+
|
|
32
|
+
// A cancelled waiter must reject immediately without opening a hole in the
|
|
33
|
+
// mutex. Its reserved slot is skipped only after the previous holder exits,
|
|
34
|
+
// so later waiters still cannot overlap the live owner.
|
|
35
|
+
return new Promise((resolve, reject) => {
|
|
36
|
+
let settled = false;
|
|
37
|
+
const removeAbort = () => {
|
|
38
|
+
try { signal.removeEventListener('abort', onAbort); } catch {}
|
|
39
|
+
};
|
|
40
|
+
const onAbort = () => {
|
|
41
|
+
if (settled) return;
|
|
42
|
+
settled = true;
|
|
43
|
+
removeAbort();
|
|
44
|
+
void previousReady.then(releaseSlot);
|
|
45
|
+
reject(abortReason(signal, `Session "${sessionId}" lock wait aborted`));
|
|
46
|
+
};
|
|
47
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
48
|
+
if (signal.aborted) {
|
|
49
|
+
onAbort();
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
previousReady.then(() => {
|
|
53
|
+
if (settled) return;
|
|
54
|
+
settled = true;
|
|
55
|
+
removeAbort();
|
|
56
|
+
resolve(unlock);
|
|
57
|
+
});
|
|
22
58
|
});
|
|
23
59
|
}
|
|
@@ -41,18 +41,25 @@ function assistantToolCallIds(message) {
|
|
|
41
41
|
return ids;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
function
|
|
44
|
+
function provisionalUserTurnIndex(messages, currentUserContent) {
|
|
45
45
|
const currentText = promptContentText(currentUserContent);
|
|
46
46
|
for (let i = messages.length - 1; i >= 0; i -= 1) {
|
|
47
47
|
const message = messages[i];
|
|
48
48
|
if (message?.role !== 'user') continue;
|
|
49
49
|
if (message.content === currentUserContent
|
|
50
50
|
|| promptContentText(message.content) === currentText) {
|
|
51
|
+
return i;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return -1;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function rewindProvisionalUserTurn(messages, currentUserContent) {
|
|
58
|
+
const index = provisionalUserTurnIndex(messages, currentUserContent);
|
|
59
|
+
if (index >= 0) {
|
|
51
60
|
// Remove the provisional user turn plus pre-send hooks appended
|
|
52
61
|
// after it, while retaining any compaction rewrite before it.
|
|
53
|
-
|
|
54
|
-
break;
|
|
55
|
-
}
|
|
62
|
+
messages.splice(index);
|
|
56
63
|
}
|
|
57
64
|
return messages;
|
|
58
65
|
}
|
|
@@ -69,13 +76,26 @@ function finalizeInterruptedTurn({
|
|
|
69
76
|
abortReason,
|
|
70
77
|
}) {
|
|
71
78
|
const messages = filterModelVisibleSessionMessages(turnOutgoing).slice();
|
|
79
|
+
// Null/unknown reasons keep the legacy rewind (status quo for wrapped
|
|
80
|
+
// aborts without a closeReason enum); named non-user reasons preserve.
|
|
81
|
+
const userCancelled = abortReason == null
|
|
82
|
+
|| USER_CANCEL_ABORT_REASONS.has(abortReason);
|
|
83
|
+
// Claude Code parity: a turn cancelled while the model was only THINKING
|
|
84
|
+
// produced no model-visible message, so the provisional user turn is
|
|
85
|
+
// rewound and the prompt goes back to the input box. Anything the model
|
|
86
|
+
// actually said or did — committed messages from this turn, buffered text,
|
|
87
|
+
// tool calls/results — keeps the turn (reasoning alone does not).
|
|
88
|
+
const producedOutput = String(partialAssistantContent || '').trim() !== ''
|
|
89
|
+
|| observedToolCalls.size > 0
|
|
90
|
+
|| observedToolResults.size > 0
|
|
91
|
+
|| (() => {
|
|
92
|
+
const index = provisionalUserTurnIndex(messages, currentUserContent);
|
|
93
|
+
return index >= 0 && index < messages.length - 1;
|
|
94
|
+
})();
|
|
72
95
|
const preserveResponse = responseStarted
|
|
73
|
-
&& !isInternalRuntimeNotificationText(currentUserContent)
|
|
96
|
+
&& !isInternalRuntimeNotificationText(currentUserContent)
|
|
97
|
+
&& (producedOutput || !userCancelled);
|
|
74
98
|
if (!preserveResponse) {
|
|
75
|
-
// Null/unknown reasons keep the legacy rewind (status quo for wrapped
|
|
76
|
-
// aborts without a closeReason enum); named non-user reasons preserve.
|
|
77
|
-
const userCancelled = abortReason == null
|
|
78
|
-
|| USER_CANCEL_ABORT_REASONS.has(abortReason);
|
|
79
99
|
if (!userCancelled) {
|
|
80
100
|
return {
|
|
81
101
|
messages,
|
|
@@ -101,6 +101,7 @@ export {
|
|
|
101
101
|
// ── Session lifecycle / ask / crud / close / cleanup ──────────────────────
|
|
102
102
|
export { SessionClosedError } from './manager/session-errors.mjs';
|
|
103
103
|
export { setAgentRuntime } from './manager/agent-runtime-singleton.mjs';
|
|
104
|
+
export { prewarmAgentLoop } from './manager/runtime-loaders.mjs';
|
|
104
105
|
export {
|
|
105
106
|
createSession,
|
|
106
107
|
updateSessionRoute,
|
|
@@ -120,6 +121,7 @@ export {
|
|
|
120
121
|
listSessions,
|
|
121
122
|
findSessionByScopeKey,
|
|
122
123
|
clearSessionMessages,
|
|
124
|
+
rewindSessionMessagesTo,
|
|
123
125
|
compactSessionMessages,
|
|
124
126
|
updateSessionStatus,
|
|
125
127
|
updateSessionGeneratedTitle,
|
|
@@ -96,3 +96,68 @@ export function isInformationalShellExitOne(result) {
|
|
|
96
96
|
if (payload.startsWith('[stderr') || payload.includes('\n[stderr')) return false;
|
|
97
97
|
return true;
|
|
98
98
|
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Shell-result outcome test. The shell tool reports failure through its OWN
|
|
102
|
+
* status markers (bash-tool.mjs `_shellFailureStatus`): `Error:
|
|
103
|
+
* [shell-run-failed] …`, `Error: [shell-tool-failed] …`, or a bare
|
|
104
|
+
* `[exit code: N]` / `[timeout: …]` / `[signal: …]` marker — optionally behind
|
|
105
|
+
* ⚠️ destructive-warning lines, which are prepended AFTER the error prefix is
|
|
106
|
+
* composed. A generic leading `Error:` is NOT a shell failure: it is just as
|
|
107
|
+
* likely the command's own stdout, which is why classifyResultKind (correct
|
|
108
|
+
* for tools that own the `Error:` convention) over-reports on shell output.
|
|
109
|
+
*
|
|
110
|
+
* @param {unknown} result
|
|
111
|
+
* @returns {boolean}
|
|
112
|
+
*/
|
|
113
|
+
export function isShellFailureResult(result) {
|
|
114
|
+
if (typeof result !== 'string') return false;
|
|
115
|
+
const body = result.replace(/^(?:\s*⚠️[^\n]*\n)+/, '').trimStart();
|
|
116
|
+
if (/^error:\s*\[shell-(?:run|tool)-failed\]/i.test(body)) return true;
|
|
117
|
+
return /^\[(?:exit code:|timeout:|signal:)/i.test(body);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Evidence that a command's OUTPUT reports a real failure. Used to separate a
|
|
121
|
+
// legitimate non-zero exit (a probe or report that prints its result and ends
|
|
122
|
+
// with 1) from a genuine failure that happens to write only to stdout — test
|
|
123
|
+
// runners, compilers and package managers all announce themselves in the text.
|
|
124
|
+
// Deliberately literal: only well-known failure banners count, so an unknown
|
|
125
|
+
// tool's report is never mislabelled a failure.
|
|
126
|
+
const SHELL_OUTPUT_FAILURE_EVIDENCE = [
|
|
127
|
+
/^not ok \d/m,
|
|
128
|
+
/^# fail [1-9]/m,
|
|
129
|
+
/\bAssertionError\b/,
|
|
130
|
+
/\bTraceback \(most recent call last\)/,
|
|
131
|
+
/^npm ERR!/m,
|
|
132
|
+
/\berror TS\d+\b/,
|
|
133
|
+
// Indented banners count too: runners print " FAIL <case>" under a header.
|
|
134
|
+
/^\s*FAIL(?:ED|URE)?\b/mi,
|
|
135
|
+
/\bFAILED[:!]/,
|
|
136
|
+
/^\s*✗/m,
|
|
137
|
+
/\b\d+ (?:tests? )?failed\b/i,
|
|
138
|
+
/^fatal:/m,
|
|
139
|
+
/^error:/mi,
|
|
140
|
+
/\bSyntaxError\b|\bReferenceError\b|\bTypeError\b/,
|
|
141
|
+
/\bCannot find module\b/,
|
|
142
|
+
];
|
|
143
|
+
export function shellOutputReportsFailure(text) {
|
|
144
|
+
const body = String(text ?? '');
|
|
145
|
+
if (!body.trim()) return false;
|
|
146
|
+
return SHELL_OUTPUT_FAILURE_EVIDENCE.some((pattern) => pattern.test(body));
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Classify a non-zero shell exit as a real failure or a legitimate report.
|
|
151
|
+
* Legitimate: exit code 1 exactly, no signal, NOTHING on stderr, real output
|
|
152
|
+
* on stdout, and no failure banner in that output. Everything else — any other
|
|
153
|
+
* exit code, a signal, any stderr, an empty capture, or a failure banner —
|
|
154
|
+
* stays a failure.
|
|
155
|
+
*/
|
|
156
|
+
export function isLegitimateShellExit({ exitCode, signal, stdout, stderr } = {}) {
|
|
157
|
+
if (signal) return false;
|
|
158
|
+
if (exitCode !== 1) return false;
|
|
159
|
+
if (String(stderr ?? '').trim()) return false;
|
|
160
|
+
const out = String(stdout ?? '').trim();
|
|
161
|
+
if (!out || out === '(no output)') return false;
|
|
162
|
+
return !shellOutputReportsFailure(out);
|
|
163
|
+
}
|
|
@@ -5,13 +5,19 @@
|
|
|
5
5
|
// pass), and unrecoverable errors throw. Behavior identical to the inline
|
|
6
6
|
// try/catch it replaced.
|
|
7
7
|
import { appendAgentTrace } from '../agent-trace.mjs';
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
classifyError,
|
|
10
|
+
isContextOverflowError,
|
|
11
|
+
isNonTerminalStreamClose,
|
|
12
|
+
jitterDelayMs,
|
|
13
|
+
} from '../providers/retry-classifier.mjs';
|
|
9
14
|
import { setTimeout as sleepMs } from 'timers/promises';
|
|
10
15
|
import { readStreamOutcome } from '../providers/lib/stream-outcome.mjs';
|
|
11
16
|
import { resolveWorkerCompactPolicy } from './loop/compact-policy.mjs';
|
|
12
17
|
import { agentContextOverflowError } from './loop/context-overflow.mjs';
|
|
13
18
|
import { estimateMessagesTokensSafe } from './loop/compact-debug.mjs';
|
|
14
19
|
import { isOutputLimitStopReason } from './loop/termination.mjs';
|
|
20
|
+
import { isVisibleStreamProgress } from '../../../shared/stream-progress.mjs';
|
|
15
21
|
|
|
16
22
|
function normalizedIncompleteUsage(raw) {
|
|
17
23
|
if (!raw || typeof raw !== 'object') return undefined;
|
|
@@ -45,9 +51,35 @@ function normalizedIncompleteUsage(raw) {
|
|
|
45
51
|
// the identical request is side-effect-free, so wait out the blip and retry
|
|
46
52
|
// the send at the loop level. Two attempts, 5s/15s — combined with the
|
|
47
53
|
// provider envelope this covers ~50s outages before failing honestly.
|
|
48
|
-
|
|
54
|
+
// Env-overridable (comma-separated ms) so tests can drive the ladder without
|
|
55
|
+
// real waits and an operator can widen the window for a flaky uplink; the
|
|
56
|
+
// LENGTH of the list is the retry budget, exactly like codex's
|
|
57
|
+
// stream_max_retries + backoff table.
|
|
58
|
+
const TRANSPORT_RETRY_BACKOFF_MS = Object.freeze((() => {
|
|
59
|
+
const raw = process.env.MIXDOG_TRANSPORT_RETRY_BACKOFF_MS;
|
|
60
|
+
if (typeof raw === 'string' && raw.trim()) {
|
|
61
|
+
const parsed = raw.split(',')
|
|
62
|
+
.map((value) => Number(value.trim()))
|
|
63
|
+
.filter((value) => Number.isFinite(value) && value >= 0);
|
|
64
|
+
if (parsed.length) return parsed;
|
|
65
|
+
}
|
|
66
|
+
// Three steps (~50s of outage) instead of two: the provider envelope
|
|
67
|
+
// already covers short blips, so this ladder exists for the long ones.
|
|
68
|
+
// codex allows 5 stream reconnects and cc 10 request retries.
|
|
69
|
+
return [5_000, 15_000, 30_000];
|
|
70
|
+
})());
|
|
49
71
|
export const TRANSPORT_RETRY_MAX = TRANSPORT_RETRY_BACKOFF_MS.length;
|
|
50
72
|
|
|
73
|
+
// codex jitters every backoff by 0.9–1.1 so parallel workers that lose the
|
|
74
|
+
// same uplink do not resume in lockstep; our provider layers already jitter
|
|
75
|
+
// (10% WS / 20% shared), this brings the loop ladder in line.
|
|
76
|
+
const TRANSPORT_RETRY_JITTER_RATIO = 0.1;
|
|
77
|
+
|
|
78
|
+
function transportRetryWaitMs(attemptIndex) {
|
|
79
|
+
const base = TRANSPORT_RETRY_BACKOFF_MS[attemptIndex];
|
|
80
|
+
return jitterDelayMs(base, TRANSPORT_RETRY_JITTER_RATIO);
|
|
81
|
+
}
|
|
82
|
+
|
|
51
83
|
export async function sendWithRecovery(ctx) {
|
|
52
84
|
const {
|
|
53
85
|
provider, messages, model, sendTools, tools, opts,
|
|
@@ -56,7 +88,8 @@ export async function sendWithRecovery(ctx) {
|
|
|
56
88
|
} = ctx;
|
|
57
89
|
let response;
|
|
58
90
|
// Bench-only turn timing (MIXDOG_TURN_TIMING=1): one stderr line per
|
|
59
|
-
// provider request — TTFT (first
|
|
91
|
+
// provider request — TTFT (first visible model progress) and total stream
|
|
92
|
+
// time. Transport/header and response-created acknowledgements do not count.
|
|
60
93
|
// Inert unless the env flag is set; used to profile harness vs model
|
|
61
94
|
// latency in Terminal-Bench runs.
|
|
62
95
|
const turnT0 = process.env.MIXDOG_TURN_TIMING === '1' ? Date.now() : 0;
|
|
@@ -67,7 +100,7 @@ export async function sendWithRecovery(ctx) {
|
|
|
67
100
|
timedOpts = {
|
|
68
101
|
...(opts || {}),
|
|
69
102
|
onStreamDelta: (kind) => {
|
|
70
|
-
if (!turnFirstDelta) turnFirstDelta = Date.now();
|
|
103
|
+
if (!turnFirstDelta && isVisibleStreamProgress(kind)) turnFirstDelta = Date.now();
|
|
71
104
|
if (prevDelta) prevDelta(kind);
|
|
72
105
|
},
|
|
73
106
|
};
|
|
@@ -92,6 +125,22 @@ export async function sendWithRecovery(ctx) {
|
|
|
92
125
|
// overflow-retry branch intentionally clears opts.onToolCall, and that
|
|
93
126
|
// clear must survive the restore.
|
|
94
127
|
const relayWitness = { textEmitted: false, toolCallsDispatched: 0 };
|
|
128
|
+
// Caller-visible reconnect progress for LOOP-level replays, mirroring the
|
|
129
|
+
// transports' own emitReconnectProgress (openai-oauth-ws). Without it the
|
|
130
|
+
// 5s/15s wait rendered as a frozen turn with no explanation, while codex
|
|
131
|
+
// surfaces "Reconnecting... n/max" through notify_stream_error and cc
|
|
132
|
+
// yields a SystemAPIErrorMessage for the same situation.
|
|
133
|
+
const emitLoopReconnectProgress = (attempt, waitMs, classifier) => {
|
|
134
|
+
try {
|
|
135
|
+
opts?.onStageChange?.('reconnecting', {
|
|
136
|
+
attempt,
|
|
137
|
+
max: TRANSPORT_RETRY_MAX,
|
|
138
|
+
classifier: classifier || null,
|
|
139
|
+
waitMs,
|
|
140
|
+
message: `Reconnecting... ${attempt}/${TRANSPORT_RETRY_MAX}`,
|
|
141
|
+
});
|
|
142
|
+
} catch { /* progress reporting must never break recovery */ }
|
|
143
|
+
};
|
|
95
144
|
const prevOnTextDelta = typeof opts?.onTextDelta === 'function' ? opts.onTextDelta : null;
|
|
96
145
|
const prevOnToolCall = typeof opts?.onToolCall === 'function' ? opts.onToolCall : null;
|
|
97
146
|
const witnessedOnTextDelta = prevOnTextDelta
|
|
@@ -116,7 +165,15 @@ export async function sendWithRecovery(ctx) {
|
|
|
116
165
|
}
|
|
117
166
|
try {
|
|
118
167
|
try {
|
|
119
|
-
|
|
168
|
+
const admittedOpts = timedOpts?.admissionOwner
|
|
169
|
+
? timedOpts
|
|
170
|
+
: { ...(timedOpts || {}), admissionOwner: sessionId };
|
|
171
|
+
response = await provider.send(
|
|
172
|
+
messages,
|
|
173
|
+
model,
|
|
174
|
+
sendTools.length ? sendTools : undefined,
|
|
175
|
+
admittedOpts,
|
|
176
|
+
);
|
|
120
177
|
logTurnTiming('ok');
|
|
121
178
|
} catch (sendErr) {
|
|
122
179
|
logTurnTiming(`err:${sendErr?.code || sendErr?.name || 'unknown'}`);
|
|
@@ -220,7 +277,7 @@ export async function sendWithRecovery(ctx) {
|
|
|
220
277
|
transportRetriesUsed < TRANSPORT_RETRY_MAX
|
|
221
278
|
&& await retractExposedTextForReplay()
|
|
222
279
|
) {
|
|
223
|
-
const waitMs =
|
|
280
|
+
const waitMs = transportRetryWaitMs(transportRetriesUsed);
|
|
224
281
|
try {
|
|
225
282
|
process.stderr.write(
|
|
226
283
|
`[loop] exposed-text stall retracted (sess=${sessionId || 'unknown'} `
|
|
@@ -238,6 +295,11 @@ export async function sendWithRecovery(ctx) {
|
|
|
238
295
|
partialContentLen: sendErr.partialContent.length,
|
|
239
296
|
});
|
|
240
297
|
} catch { /* best-effort */ }
|
|
298
|
+
emitLoopReconnectProgress(
|
|
299
|
+
transportRetriesUsed + 1,
|
|
300
|
+
waitMs,
|
|
301
|
+
outcome.stallObserved ? 'stream_stalled' : 'stream_closed',
|
|
302
|
+
);
|
|
241
303
|
await sleepMs(waitMs, undefined, signal ? { signal } : undefined);
|
|
242
304
|
return { action: 'retry_transport' };
|
|
243
305
|
}
|
|
@@ -332,12 +394,23 @@ export async function sendWithRecovery(ctx) {
|
|
|
332
394
|
&& (
|
|
333
395
|
(outcome.replaySafe === true && classifyError(sendErr) === 'transient')
|
|
334
396
|
|| (
|
|
335
|
-
|
|
397
|
+
// classifyError reports 'permanent' the moment anything
|
|
398
|
+
// was exposed, so an exposed disconnect could never
|
|
399
|
+
// reach the retraction path: name the transport
|
|
400
|
+
// symptoms directly (stall OR non-terminal close).
|
|
401
|
+
(classifyError(sendErr) === 'transient'
|
|
402
|
+
|| outcome.stallObserved === true
|
|
403
|
+
// Same inversion for the truncated-stream class
|
|
404
|
+
// (anthropic message_start-without-message_stop,
|
|
405
|
+
// gemini/compat EOF): classifyError calls it
|
|
406
|
+
// 'transient' only while nothing was exposed.
|
|
407
|
+
|| outcome.truncatedStream === true
|
|
408
|
+
|| isNonTerminalStreamClose(sendErr))
|
|
336
409
|
&& await retractExposedTextForReplay()
|
|
337
410
|
)
|
|
338
411
|
)
|
|
339
412
|
) {
|
|
340
|
-
const waitMs =
|
|
413
|
+
const waitMs = transportRetryWaitMs(transportRetriesUsed);
|
|
341
414
|
try {
|
|
342
415
|
process.stderr.write(
|
|
343
416
|
`[loop] transient send failure with no observed output (sess=${sessionId || 'unknown'} `
|
|
@@ -356,6 +429,11 @@ export async function sendWithRecovery(ctx) {
|
|
|
356
429
|
status: sendErr?.status ?? null,
|
|
357
430
|
});
|
|
358
431
|
} catch { /* best-effort */ }
|
|
432
|
+
emitLoopReconnectProgress(
|
|
433
|
+
transportRetriesUsed + 1,
|
|
434
|
+
waitMs,
|
|
435
|
+
sendErr?.retryClassifier || sendErr?.midstreamClassifier || sendErr?.code || null,
|
|
436
|
+
);
|
|
359
437
|
await sleepMs(waitMs, undefined, signal ? { signal } : undefined);
|
|
360
438
|
return { action: 'retry_transport' };
|
|
361
439
|
} else
|
|
@@ -121,7 +121,11 @@ export function _queueSessionSummaryUpsert(session, version = null) {
|
|
|
121
121
|
*/
|
|
122
122
|
export function _queueSessionSummaryUpsertRow(row, version = null) {
|
|
123
123
|
if (!row) return;
|
|
124
|
-
|
|
124
|
+
const fileProbe = probePath(join(getStoreDir(), `${row.id}.json`));
|
|
125
|
+
const durableRow = fileProbe.state === PROBE_PRESENT
|
|
126
|
+
? { ...row, storageMtimeMs: fileProbe.mtimeMs, storageSize: fileProbe.size }
|
|
127
|
+
: row;
|
|
128
|
+
_setCachedBaseSummary(durableRow);
|
|
125
129
|
if (version === null || (_summaryCacheVersions.get(row.id) || 0) === version) {
|
|
126
130
|
_summaryCacheUpserts.delete(row.id);
|
|
127
131
|
if (_summaryCacheLatestRows.get(row.id)?.version === version) {
|
|
@@ -130,7 +134,7 @@ export function _queueSessionSummaryUpsertRow(row, version = null) {
|
|
|
130
134
|
_summaryCacheRemovals.delete(row.id);
|
|
131
135
|
_invalidateSummaryRowsView();
|
|
132
136
|
}
|
|
133
|
-
_upsertSessionSummaryRow(
|
|
137
|
+
_upsertSessionSummaryRow(durableRow);
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
export function _queueSessionSummaryRemoval(id) {
|
|
@@ -220,7 +224,10 @@ export function _scanStoredSessionSummaryRows() {
|
|
|
220
224
|
else markInvalid(f);
|
|
221
225
|
continue;
|
|
222
226
|
}
|
|
223
|
-
const
|
|
227
|
+
const summary = session ? _sessionSummary(session) : null;
|
|
228
|
+
const row = summary
|
|
229
|
+
? { ...summary, storageMtimeMs: fileProbe.mtimeMs, storageSize: fileProbe.size }
|
|
230
|
+
: null;
|
|
224
231
|
_summaryScanCache.set(f, { mtimeMs: fileProbe.mtimeMs, size: fileProbe.size, row });
|
|
225
232
|
changed = true;
|
|
226
233
|
if (row) rows.push(row);
|
|
@@ -154,6 +154,8 @@ export function _sessionSummary(session) {
|
|
|
154
154
|
preview: messageProjection.preview,
|
|
155
155
|
generation: typeof session.generation === 'number' ? session.generation : 0,
|
|
156
156
|
implicitBashSessionId: session.implicitBashSessionId || null,
|
|
157
|
+
storageMtimeMs: _positiveNumber(session.storageMtimeMs, 0),
|
|
158
|
+
storageSize: _positiveNumber(session.storageSize, 0),
|
|
157
159
|
// Lifecycle provenance for catalog filters: resume-machinery scratch
|
|
158
160
|
// forks (detachedReason='cli-resume') must never surface in Recent.
|
|
159
161
|
detachedReason: session.detachedReason || null,
|
|
@@ -190,6 +192,8 @@ function _normalizeSummaryRow(row) {
|
|
|
190
192
|
preview: _cleanPreview(row.preview || ''),
|
|
191
193
|
generation: typeof row.generation === 'number' ? row.generation : 0,
|
|
192
194
|
implicitBashSessionId: row.implicitBashSessionId || null,
|
|
195
|
+
storageMtimeMs: _positiveNumber(row.storageMtimeMs, 0),
|
|
196
|
+
storageSize: _positiveNumber(row.storageSize, 0),
|
|
193
197
|
detachedReason: row.detachedReason || null,
|
|
194
198
|
};
|
|
195
199
|
}
|