mixdog 0.8.0 → 0.9.0
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/README.md +47 -23
- package/package.json +33 -27
- package/scripts/_test-folder-dialog.mjs +30 -0
- package/scripts/agent-parallel-smoke.mjs +388 -0
- package/scripts/agent-tag-reuse-smoke.mjs +183 -0
- package/scripts/background-task-meta-smoke.mjs +38 -0
- package/scripts/boot-smoke.mjs +52 -9
- package/scripts/build-runtime-linux.sh +348 -0
- package/scripts/build-runtime-macos.sh +217 -0
- package/scripts/build-runtime-windows.ps1 +242 -0
- package/scripts/compact-active-turn-test.mjs +68 -0
- package/scripts/compact-smoke.mjs +859 -129
- package/scripts/compact-trigger-migration-smoke.mjs +187 -0
- package/scripts/fix-brief-fn.mjs +35 -0
- package/scripts/fix-format-tool-surface.mjs +24 -0
- package/scripts/fix-tool-exec-visible.mjs +42 -0
- package/scripts/generate-runtime-manifest.mjs +166 -0
- package/scripts/hook-bus-test.mjs +330 -0
- package/scripts/lead-workflow-smoke.mjs +33 -39
- package/scripts/live-worker-smoke.mjs +43 -37
- package/scripts/llm-trace-summary.mjs +315 -0
- package/scripts/memory-meta-concurrency-test.mjs +20 -0
- package/scripts/output-style-smoke.mjs +56 -15
- package/scripts/parent-abort-link-test.mjs +44 -0
- package/scripts/patch-agent-brief.mjs +48 -0
- package/scripts/patch-app.mjs +21 -0
- package/scripts/patch-app2.mjs +18 -0
- package/scripts/patch-dist-brief.mjs +96 -0
- package/scripts/patch-tool-exec.mjs +70 -0
- package/scripts/pretool-ask-runtime-test.mjs +54 -0
- package/scripts/provider-toolcall-test.mjs +376 -0
- package/scripts/reactive-compact-persist-smoke.mjs +124 -0
- package/scripts/sanitize-tool-pairs-test.mjs +260 -0
- package/scripts/session-context-bench.mjs +344 -0
- package/scripts/session-ingest-smoke.mjs +177 -0
- package/scripts/set-effort-config-test.mjs +41 -0
- package/scripts/smoke-runtime-negative.ps1 +106 -0
- package/scripts/smoke-runtime-negative.sh +97 -0
- package/scripts/smoke.mjs +25 -0
- package/scripts/tool-result-hook-test.mjs +48 -0
- package/scripts/tool-smoke.mjs +1223 -95
- package/scripts/toolcall-args-test.mjs +150 -0
- package/scripts/tui-background-failure-smoke.mjs +73 -0
- package/scripts/usage-metrics-epoch-smoke.mjs +114 -0
- package/src/agents/debugger/AGENT.md +8 -0
- package/src/agents/explore/AGENT.md +4 -0
- package/src/agents/heavy-worker/AGENT.md +9 -3
- package/src/agents/maintainer/AGENT.md +4 -0
- package/src/agents/reviewer/AGENT.md +8 -0
- package/src/agents/scheduler-task/AGENT.md +12 -0
- package/src/agents/scheduler-task/agent.json +6 -0
- package/src/agents/webhook-handler/AGENT.md +12 -0
- package/src/agents/webhook-handler/agent.json +6 -0
- package/src/agents/worker/AGENT.md +9 -3
- package/src/app.mjs +77 -3
- package/src/defaults/hidden-roles.json +17 -12
- package/src/headless-role.mjs +117 -0
- package/src/help.mjs +30 -0
- package/src/hooks/lib/permission-evaluator.cjs +11 -475
- package/src/lib/keychain-cjs.cjs +9 -1
- package/src/lib/mixdog-debug.cjs +0 -7
- package/src/lib/rules-builder.cjs +240 -96
- package/src/lib/text-utils.cjs +1 -1
- package/src/mixdog-session-runtime.mjs +2325 -450
- package/src/output-styles/default.md +12 -28
- package/src/output-styles/extreme-simple.md +9 -6
- package/src/output-styles/simple.md +22 -9
- package/src/repl.mjs +118 -59
- package/src/rules/agent/00-common.md +15 -0
- package/src/rules/{bridge → agent}/20-skip-protocol.md +1 -2
- package/src/rules/agent/30-explorer.md +22 -0
- package/src/rules/{bridge → agent}/40-cycle1-agent.md +7 -0
- package/src/rules/{bridge → agent}/41-cycle2-agent.md +7 -0
- package/src/rules/{bridge → agent}/42-cycle3-agent.md +7 -0
- package/src/rules/lead/01-general.md +9 -5
- package/src/rules/lead/04-workflow.md +51 -12
- package/src/rules/lead/lead-tool.md +6 -0
- package/src/rules/shared/01-tool.md +12 -1
- package/src/runtime/agent/orchestrator/activity-bus.mjs +7 -18
- package/src/runtime/agent/orchestrator/agent-owner.mjs +11 -0
- package/src/runtime/agent/orchestrator/{smart-bridge/bridge-llm.mjs → agent-runtime/agent-dispatch.mjs} +138 -111
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +94 -0
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/cache-strategy.mjs +32 -23
- package/src/runtime/agent/orchestrator/{smart-bridge → agent-runtime}/session-builder.mjs +33 -27
- package/src/runtime/agent/orchestrator/{bridge-trace.mjs → agent-trace.mjs} +132 -81
- package/src/runtime/agent/orchestrator/cache-mtime.mjs +0 -21
- package/src/runtime/agent/orchestrator/config.mjs +174 -55
- package/src/runtime/agent/orchestrator/context/collect.mjs +195 -487
- package/src/runtime/agent/orchestrator/dispatch-persist.mjs +1 -1
- package/src/runtime/agent/orchestrator/internal-roles.mjs +77 -29
- package/src/runtime/agent/orchestrator/internal-tools.mjs +5 -6
- package/src/runtime/agent/orchestrator/mcp/client.mjs +15 -9
- package/src/runtime/agent/orchestrator/providers/anthropic-betas.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +377 -243
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +146 -93
- package/src/runtime/agent/orchestrator/providers/api-usage.mjs +236 -4
- package/src/runtime/agent/orchestrator/providers/custom-tool-wire.mjs +49 -0
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +58 -13
- package/src/runtime/agent/orchestrator/providers/grok-oauth.mjs +67 -149
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +132 -2
- package/src/runtime/agent/orchestrator/providers/model-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +61 -116
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +25 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +79 -255
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +221 -70
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +477 -147
- package/src/runtime/agent/orchestrator/providers/openai-oauth.mjs +343 -496
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +6 -6
- package/src/runtime/agent/orchestrator/providers/registry.mjs +88 -51
- package/src/runtime/agent/orchestrator/providers/retry-classifier.mjs +31 -11
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +41 -8
- package/src/runtime/agent/orchestrator/session/cache/post-edit-marks.mjs +4 -4
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compact.mjs +1173 -267
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +199 -36
- package/src/runtime/agent/orchestrator/session/loop.mjs +851 -674
- package/src/runtime/agent/orchestrator/session/manager.mjs +1593 -466
- package/src/runtime/agent/orchestrator/session/manager.reactive-persist.test.mjs +107 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +291 -46
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +2 -2
- package/src/runtime/agent/orchestrator/stall-policy.mjs +31 -16
- package/src/runtime/agent/orchestrator/tool-loop-guard.mjs +3 -219
- package/src/runtime/agent/orchestrator/tools/bash-session.mjs +34 -7
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +19 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +9 -9
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +60 -37
- package/src/runtime/agent/orchestrator/tools/builtin/cache-layers.mjs +21 -2
- package/src/runtime/agent/orchestrator/tools/builtin/device-paths.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/diagnostics-tool.mjs +0 -7
- package/src/runtime/agent/orchestrator/tools/builtin/glob-walk.mjs +1 -3
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +36 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-args.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-constants.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +5 -12
- package/src/runtime/agent/orchestrator/tools/builtin/read-image-resize.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +4 -36
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +2 -40
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +148 -27
- package/src/runtime/agent/orchestrator/tools/builtin/search-builders.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/search-tool.mjs +43 -75
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +90 -20
- package/src/runtime/agent/orchestrator/tools/builtin/snapshot-store.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin.mjs +59 -5
- package/src/runtime/agent/orchestrator/tools/code-graph-state.mjs +86 -0
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +11 -11
- package/src/runtime/agent/orchestrator/tools/code-graph.mjs +4106 -4019
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +33 -4
- package/src/runtime/agent/orchestrator/tools/patch.mjs +90 -6
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +6 -4
- package/src/runtime/agent/orchestrator/tools/result-compression.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +8 -1
- package/src/runtime/agent/orchestrator/tools/shell-snapshot.mjs +4 -4
- package/src/runtime/channels/index.mjs +152 -24
- package/src/runtime/channels/lib/scheduler.mjs +18 -14
- package/src/runtime/channels/lib/session-discovery.mjs +3 -2
- package/src/runtime/channels/lib/tool-format.mjs +0 -2
- package/src/runtime/channels/lib/transcript-discovery.mjs +3 -2
- package/src/runtime/channels/lib/webhook.mjs +1 -1
- package/src/runtime/channels/tool-defs.mjs +29 -29
- package/src/runtime/memory/index.mjs +635 -107
- package/src/runtime/memory/lib/agent-ipc.mjs +29 -12
- package/src/runtime/memory/lib/core-memory-store.mjs +2 -2
- package/src/runtime/memory/lib/embedding-model-config.mjs +55 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +31 -4
- package/src/runtime/memory/lib/embedding-worker.mjs +19 -10
- package/src/runtime/memory/lib/memory-cycle1.mjs +38 -17
- package/src/runtime/memory/lib/memory-cycle2.mjs +6 -7
- package/src/runtime/memory/lib/memory-cycle3.mjs +4 -4
- package/src/runtime/memory/lib/memory-ops-policy.mjs +2 -1
- package/src/runtime/memory/lib/memory-session-merge.mjs +38 -0
- package/src/runtime/memory/lib/memory.mjs +88 -9
- package/src/runtime/memory/lib/model-profile.mjs +1 -1
- package/src/runtime/memory/lib/pg/adapter.mjs +15 -1
- package/src/runtime/memory/lib/pg/supervisor.mjs +12 -0
- package/src/runtime/memory/lib/runtime-fetcher.mjs +37 -3
- package/src/runtime/memory/lib/session-ingest.mjs +194 -0
- package/src/runtime/memory/lib/trace-store.mjs +96 -51
- package/src/runtime/memory/tool-defs.mjs +46 -37
- package/src/runtime/search/index.mjs +102 -466
- package/src/runtime/search/lib/web-tools.mjs +45 -25
- package/src/runtime/search/tool-defs.mjs +16 -23
- package/src/runtime/shared/abort-controller.mjs +1 -1
- package/src/runtime/shared/atomic-file.mjs +4 -3
- package/src/runtime/shared/background-tasks.mjs +122 -11
- package/src/runtime/shared/child-spawn-gate.mjs +145 -0
- package/src/runtime/shared/config.mjs +7 -4
- package/src/runtime/shared/err-text.mjs +131 -4
- package/src/runtime/shared/llm/cost.mjs +2 -2
- package/src/runtime/shared/llm/http-agent.mjs +23 -7
- package/src/runtime/shared/llm/index.mjs +34 -11
- package/src/runtime/shared/llm/usage-log.mjs +4 -4
- package/src/runtime/shared/markdown-frontmatter.mjs +56 -0
- package/src/runtime/shared/singleton-owner.mjs +104 -0
- package/src/runtime/shared/tool-execution-contract.mjs +199 -20
- package/src/runtime/shared/tool-execution-contract.test.mjs +183 -0
- package/src/runtime/shared/tool-surface.mjs +624 -98
- package/src/runtime/shared/user-data-guard.mjs +0 -2
- package/src/standalone/agent-task-status.mjs +203 -0
- package/src/standalone/agent-task-status.test.mjs +76 -0
- package/src/standalone/agent-tool.mjs +1913 -0
- package/src/standalone/channel-worker.mjs +370 -14
- package/src/standalone/explore-tool.mjs +165 -70
- package/src/standalone/folder-dialog.mjs +314 -0
- package/src/standalone/hook-bus.mjs +898 -22
- package/src/standalone/memory-runtime-proxy.mjs +320 -0
- package/src/standalone/projects.mjs +226 -0
- package/src/standalone/provider-admin.mjs +41 -24
- package/src/standalone/seeds.mjs +2 -69
- package/src/standalone/usage-dashboard.mjs +96 -8
- package/src/tui/App.jsx +4798 -2153
- package/src/tui/components/AnsiText.jsx +39 -28
- package/src/tui/components/ContextPanel.jsx +87 -29
- package/src/tui/components/Markdown.jsx +43 -77
- package/src/tui/components/MarkdownTable.jsx +9 -184
- package/src/tui/components/Message.jsx +28 -11
- package/src/tui/components/Picker.jsx +95 -56
- package/src/tui/components/PromptInput.jsx +367 -239
- package/src/tui/components/QueuedCommands.jsx +1 -1
- package/src/tui/components/SlashCommandPalette.jsx +27 -21
- package/src/tui/components/Spinner.jsx +67 -38
- package/src/tui/components/StatusLine.jsx +606 -38
- package/src/tui/components/TextEntryPanel.jsx +128 -9
- package/src/tui/components/ToolExecution.jsx +617 -368
- package/src/tui/components/TurnDone.jsx +3 -3
- package/src/tui/components/UsagePanel.jsx +3 -5
- package/src/tui/components/tool-output-format.mjs +365 -0
- package/src/tui/components/tool-output-format.test.mjs +220 -0
- package/src/tui/dist/index.mjs +8915 -2418
- package/src/tui/engine-runtime-notification.test.mjs +115 -0
- package/src/tui/engine-tool-result-text.test.mjs +75 -0
- package/src/tui/engine.mjs +1455 -279
- package/src/tui/figures.mjs +21 -40
- package/src/tui/index.jsx +75 -31
- package/src/tui/input-editing.mjs +25 -0
- package/src/tui/markdown/format-token.mjs +511 -68
- package/src/tui/markdown/format-token.test.mjs +216 -0
- package/src/tui/markdown/render-ansi.mjs +94 -0
- package/src/tui/markdown/render-ansi.test.mjs +108 -0
- package/src/tui/markdown/stream-fence.mjs +34 -0
- package/src/tui/markdown/stream-fence.test.mjs +26 -0
- package/src/tui/markdown/table-layout.mjs +250 -0
- package/src/tui/paste-attachments.mjs +0 -7
- package/src/tui/spinner-verbs.mjs +1 -2
- package/src/tui/statusline-ansi-bridge.mjs +172 -0
- package/src/tui/statusline-ansi-bridge.test.mjs +159 -0
- package/src/tui/theme.mjs +746 -24
- package/src/tui/time-format.mjs +1 -1
- package/src/tui/transcript-tool-failures.mjs +67 -0
- package/src/tui/transcript-tool-failures.test.mjs +111 -0
- package/src/ui/ansi.mjs +1 -2
- package/src/ui/markdown.mjs +85 -26
- package/src/ui/markdown.test.mjs +70 -0
- package/src/ui/model-display.mjs +121 -0
- package/src/ui/session-stats.mjs +44 -0
- package/src/ui/statusline-context-label.test.mjs +15 -0
- package/src/ui/statusline.mjs +386 -178
- package/src/ui/tool-card.mjs +3 -16
- package/src/vendor/statusline/bin/statusline-lib.mjs +8 -4
- package/src/vendor/statusline/bin/statusline-route.mjs +169 -37
- package/src/vendor/statusline/bin/statusline-route.test.mjs +80 -0
- package/src/vendor/statusline/scripts/lib/gateway-settings.mjs +3 -3
- package/src/vendor/statusline/src/gateway/claude-current.mjs +1 -1
- package/src/vendor/statusline/src/gateway/route-meta.mjs +44 -6
- package/src/vendor/statusline/src/gateway/session-routes.mjs +1 -1
- package/src/workflows/default/WORKFLOW.md +12 -5
- package/src/workflows/default/workflow.json +0 -1
- package/src/workflows/solo/WORKFLOW.md +15 -0
- package/src/workflows/solo/workflow.json +7 -0
- package/vendor/ink/build/output.js +6 -1
- package/src/agents/scheduler-task.md +0 -3
- package/src/agents/web-researcher/AGENT.md +0 -3
- package/src/agents/web-researcher/agent.json +0 -6
- package/src/agents/webhook-handler.md +0 -3
- package/src/rules/bridge/00-common.md +0 -5
- package/src/rules/bridge/30-explorer.md +0 -4
- package/src/rules/lead/00-tool-lead.md +0 -5
- package/src/rules/shared/00-language.md +0 -3
- package/src/runtime/agent/orchestrator/tools/builtin/native-edit-runner.mjs +0 -110
- package/src/runtime/agent/orchestrator/tools/mutation-content-cache.mjs +0 -67
- package/src/runtime/memory/lib/bridge-trace-queries.mjs +0 -120
- package/src/runtime/shared/llm/pid-cleanup.mjs +0 -27
- package/src/standalone/bridge-tool.mjs +0 -1414
- package/src/tui/runtime/shared/process-shutdown.mjs +0 -1
|
@@ -50,10 +50,77 @@ function formatDurationMs(value) {
|
|
|
50
50
|
return `${ms}ms`;
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
function compactDurationForStatus(value, { preferMinutes = false } = {}) {
|
|
54
|
+
const ms = Math.max(0, Number(value) || 0);
|
|
55
|
+
if (ms >= 1_000) {
|
|
56
|
+
if (preferMinutes && ms >= 60_000 && ms % 60_000 === 0) return `${Math.round(ms / 60_000)}m`;
|
|
57
|
+
if (ms < 600_000 && ms % 1_000 === 0) return `${Math.round(ms / 1_000)}s`;
|
|
58
|
+
if (ms >= 60_000 && ms % 60_000 === 0) return `${Math.round(ms / 60_000)}m`;
|
|
59
|
+
return `${Math.round(ms / 1_000)}s`;
|
|
60
|
+
}
|
|
61
|
+
return `${ms}ms`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function parseDurationTokenToMs(token) {
|
|
65
|
+
const t = String(token || '').trim().toLowerCase();
|
|
66
|
+
const minutes = /^(\d+)m$/.exec(t);
|
|
67
|
+
if (minutes) return Number(minutes[1]) * 60_000;
|
|
68
|
+
const seconds = /^(\d+)s$/.exec(t);
|
|
69
|
+
if (seconds) return Number(seconds[1]) * 1_000;
|
|
70
|
+
const millis = /^(\d+)ms$/.exec(t);
|
|
71
|
+
if (millis) return Number(millis[1]);
|
|
72
|
+
return NaN;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function watchdogMsFromRawError(raw) {
|
|
76
|
+
const value = String(raw || '');
|
|
77
|
+
const timeout = /first response stale\s*\((\d+)ms\)/i.exec(value);
|
|
78
|
+
if (timeout) return { kind: 'timeout', ms: Number(timeout[1]) };
|
|
79
|
+
const stale = /(?:agent )?(?:task|tool running) stale\s*\((\d+)ms/i.exec(value);
|
|
80
|
+
if (stale) return { kind: 'stale', ms: Number(stale[1]) };
|
|
81
|
+
return null;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function compactReasonFromNormalizedTimeout(presented) {
|
|
85
|
+
const match = /no first response from (?:the )?[\w\s]*within (\d+m|\d+s|\d+ms)/i.exec(String(presented || ''));
|
|
86
|
+
if (!match) return '';
|
|
87
|
+
const ms = parseDurationTokenToMs(match[1]);
|
|
88
|
+
return Number.isFinite(ms)
|
|
89
|
+
? `No first response ${compactDurationForStatus(ms)}`
|
|
90
|
+
: `No first response ${match[1]}`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function compactReasonFromNormalizedStale(presented) {
|
|
94
|
+
const match = /went stale after (\d+m|\d+s|\d+ms)/i.exec(String(presented || ''));
|
|
95
|
+
if (!match) return '';
|
|
96
|
+
const ms = parseDurationTokenToMs(match[1]);
|
|
97
|
+
return Number.isFinite(ms)
|
|
98
|
+
? `No progress ${compactDurationForStatus(ms, { preferMinutes: true })}`
|
|
99
|
+
: `No progress ${match[1]}`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function isBackgroundErrorOnlyBody(body, error = '') {
|
|
103
|
+
const trimmed = String(body ?? '').trim();
|
|
104
|
+
if (!trimmed) return false;
|
|
105
|
+
const err = String(error ?? '').trim();
|
|
106
|
+
const lines = trimmed.split('\n').map((line) => line.trim()).filter(Boolean);
|
|
107
|
+
if (lines.length !== 1) return false;
|
|
108
|
+
const line = lines[0];
|
|
109
|
+
if (/^error:\s*/i.test(line)) return true;
|
|
110
|
+
if (!err) return /^error:\s*/i.test(line);
|
|
111
|
+
const stripped = stripErrorPrefix(line);
|
|
112
|
+
const presented = presentErrorText(err, { max: 500 });
|
|
113
|
+
return line === err
|
|
114
|
+
|| line === `Error: ${err}`
|
|
115
|
+
|| stripped === err
|
|
116
|
+
|| line === `Error: ${presented}`
|
|
117
|
+
|| stripped === presented;
|
|
118
|
+
}
|
|
119
|
+
|
|
53
120
|
function subjectForSurface(surface) {
|
|
54
121
|
const value = String(surface || '').toLowerCase();
|
|
55
122
|
if (value.includes('search') || value.includes('web')) return 'web search agent';
|
|
56
|
-
if (value.includes('
|
|
123
|
+
if (value.includes('agent') || value.includes('task')) return 'agent';
|
|
57
124
|
return 'tool';
|
|
58
125
|
}
|
|
59
126
|
|
|
@@ -75,19 +142,26 @@ export function presentErrorText(error, options = {}) {
|
|
|
75
142
|
text = oneLine(stripErrorPrefix(text));
|
|
76
143
|
if (!text) return 'Unknown error';
|
|
77
144
|
|
|
78
|
-
if (/\
|
|
145
|
+
if (/\bAGENT_CONTEXT_OVERFLOW\b|agent context overflow|latest turn cannot fit|context budget|context window/i.test(text)) {
|
|
79
146
|
return 'Context too large.';
|
|
80
147
|
}
|
|
81
148
|
if (/\bcompact(?:ion)?\b.*\b(?:failed|error|overflow)\b|\b(?:failed|error)\b.*\bcompact(?:ion)?\b/i.test(text)) {
|
|
82
149
|
return 'Compact failed.';
|
|
83
150
|
}
|
|
84
151
|
|
|
85
|
-
const
|
|
152
|
+
const quotaRetry = /retryAfter=([^\s:]+)/i.exec(text);
|
|
153
|
+
if (/\b429\b|rate[_ -]?limit|quota|too many requests|resource exhausted|insufficient_quota|quota_exceeded/i.test(text)) {
|
|
154
|
+
const provider = /Anthropic OAuth/i.test(text) ? 'Anthropic' : 'Provider';
|
|
155
|
+
return `${provider} quota/rate limit hit${quotaRetry?.[1] ? `; retry after ${quotaRetry[1]}` : ''}.`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const firstResponse = /(?:agent\s+)?first response stale\s*\((\d+)ms\)/i.exec(text);
|
|
86
159
|
if (firstResponse) {
|
|
87
160
|
return `No first response from the ${subject} within ${formatDurationMs(firstResponse[1])}.`;
|
|
88
161
|
}
|
|
89
162
|
|
|
90
|
-
const stale = /
|
|
163
|
+
const stale = /agent (?:task|tool running) stale\s*\((\d+)ms[^)]*\)/i.exec(text)
|
|
164
|
+
|| /tool running stale\s*\((\d+)ms[^)]*\)/i.exec(text)
|
|
91
165
|
|| /task stale\s*\((\d+)ms[^)]*\)/i.exec(text);
|
|
92
166
|
if (stale) {
|
|
93
167
|
return `The ${subject} went stale after ${formatDurationMs(stale[1])} without new stream/tool progress.`;
|
|
@@ -101,6 +175,10 @@ export function presentErrorText(error, options = {}) {
|
|
|
101
175
|
return `${subject[0].toUpperCase()}${subject.slice(1)} stopped before the response completed.`;
|
|
102
176
|
}
|
|
103
177
|
|
|
178
|
+
if (/^Session closed:\s*(?:closeSession|closed|aborted)\b/i.test(text)) {
|
|
179
|
+
return `${subject[0].toUpperCase()}${subject.slice(1)} stopped.`;
|
|
180
|
+
}
|
|
181
|
+
|
|
104
182
|
if (/parent signal aborted/i.test(text)) {
|
|
105
183
|
return `${subject[0].toUpperCase()}${subject.slice(1)} was cancelled by its caller.`;
|
|
106
184
|
}
|
|
@@ -115,6 +193,55 @@ export function presentErrorText(error, options = {}) {
|
|
|
115
193
|
return capText(text || 'Unknown error', max);
|
|
116
194
|
}
|
|
117
195
|
|
|
196
|
+
/**
|
|
197
|
+
* Compact `Failed · reason` label for background task/agent failure cards.
|
|
198
|
+
*/
|
|
199
|
+
export function backgroundTaskFailureStatusLabel(status, error, options = {}) {
|
|
200
|
+
const surface = options.surface || options.tool || '';
|
|
201
|
+
const raw = String(error ?? '').trim();
|
|
202
|
+
const statusNorm = String(status || '').trim().toLowerCase();
|
|
203
|
+
const terminal = /^(failed|error|timeout|cancelled|canceled|killed)$/i.test(statusNorm);
|
|
204
|
+
if (!raw && !terminal) return '';
|
|
205
|
+
|
|
206
|
+
const presented = presentErrorText(error, { surface, max: 160 });
|
|
207
|
+
const watchdog = watchdogMsFromRawError(raw);
|
|
208
|
+
|
|
209
|
+
let head = 'Failed';
|
|
210
|
+
if (/^(cancelled|canceled)$/i.test(statusNorm)) head = 'Cancelled';
|
|
211
|
+
else if (
|
|
212
|
+
/^timeout$/i.test(statusNorm)
|
|
213
|
+
|| watchdog?.kind === 'timeout'
|
|
214
|
+
|| /first response stale/i.test(raw)
|
|
215
|
+
|| /no first response from/i.test(presented)
|
|
216
|
+
) head = 'Timeout';
|
|
217
|
+
else if (
|
|
218
|
+
watchdog?.kind === 'stale'
|
|
219
|
+
|| /(?:agent )?(?:task|tool running) stale|went stale without/i.test(raw)
|
|
220
|
+
|| /went stale after/i.test(presented)
|
|
221
|
+
) head = 'Stale';
|
|
222
|
+
|
|
223
|
+
let reason = '';
|
|
224
|
+
if (/\bcontext too large\b/i.test(presented)) {
|
|
225
|
+
reason = 'Context too large';
|
|
226
|
+
} else if (head === 'Timeout') {
|
|
227
|
+
if (watchdog?.kind === 'timeout') {
|
|
228
|
+
reason = `No first response ${compactDurationForStatus(watchdog.ms)}`;
|
|
229
|
+
} else {
|
|
230
|
+
reason = compactReasonFromNormalizedTimeout(presented) || presented.replace(/\.$/, '');
|
|
231
|
+
}
|
|
232
|
+
} else if (head === 'Stale') {
|
|
233
|
+
if (watchdog?.kind === 'stale') {
|
|
234
|
+
reason = `No progress ${compactDurationForStatus(watchdog.ms, { preferMinutes: true })}`;
|
|
235
|
+
} else {
|
|
236
|
+
reason = compactReasonFromNormalizedStale(presented) || presented.replace(/\.$/, '');
|
|
237
|
+
}
|
|
238
|
+
} else {
|
|
239
|
+
reason = presented.replace(/\.$/, '');
|
|
240
|
+
}
|
|
241
|
+
if (!reason) return head;
|
|
242
|
+
return `${head} · ${reason}`;
|
|
243
|
+
}
|
|
244
|
+
|
|
118
245
|
export function errorLine(error, options = {}) {
|
|
119
246
|
const text = presentErrorText(error, options);
|
|
120
247
|
return /^error\s*:/i.test(text) ? text : `Error: ${text}`;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Per-call cost estimator for
|
|
2
|
+
* Per-call cost estimator for agent-trace usage rows.
|
|
3
3
|
*
|
|
4
4
|
* Pricing is pulled from the LiteLLM catalog (already warmed by providers/
|
|
5
5
|
* agent bootstrap). All four token slots — input / output / cacheRead /
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import { getModelMetadataSync } from '../../agent/orchestrator/providers/model-catalog.mjs';
|
|
15
15
|
|
|
16
|
-
// OpenAI /
|
|
16
|
+
// OpenAI OAuth / OpenAI API / Gemini report `input_tokens` as the total prompt token
|
|
17
17
|
// count *including* the cached portion (inclusive). Anthropic reports the
|
|
18
18
|
// uncached remainder only and bills cached_read / cached_write as separate
|
|
19
19
|
// additive slots (additive). Cost and prompt-total math has to branch on this.
|
|
@@ -66,7 +66,7 @@ function proxyConfigured() {
|
|
|
66
66
|
* The shared singleton dispatcher for LLM API requests. Created on first use.
|
|
67
67
|
*
|
|
68
68
|
* keepAliveTimeout (~60s) keeps a socket warm across the typical idle gap
|
|
69
|
-
* between
|
|
69
|
+
* between agent turns; connections caps concurrent sockets per origin so a
|
|
70
70
|
* burst of parallel calls can't open an unbounded number of handshakes.
|
|
71
71
|
*
|
|
72
72
|
* Returns `undefined` when a proxy / any custom (non-default) global dispatcher
|
|
@@ -92,9 +92,19 @@ export function getLlmDispatcher() {
|
|
|
92
92
|
return _globalInstalled ? undefined : _agent
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
// Origins
|
|
96
|
-
//
|
|
97
|
-
|
|
95
|
+
// Origins warmed (or warming) recently, with the timestamp of the last warm.
|
|
96
|
+
// A time-based gate (instead of a permanent Set) lets us RE-warm a socket once
|
|
97
|
+
// the kept-alive window has lapsed: a one-shot warm at provider construction
|
|
98
|
+
// went cold after keepAliveTimeout (~60s idle between turns), so the first
|
|
99
|
+
// request after a pause paid the full TLS handshake again. Re-warming just
|
|
100
|
+
// before a turn keeps the hot-path send on a live socket.
|
|
101
|
+
const _preconnectedAt = new Map()
|
|
102
|
+
// Re-warm cadence: slightly below the keep-alive window so a socket is renewed
|
|
103
|
+
// before it can lapse. Capped so an explicit short keepAlive still re-warms.
|
|
104
|
+
function _preconnectTtlMs() {
|
|
105
|
+
const keepAlive = envInt('MIXDOG_LLM_KEEPALIVE_MS', 60_000)
|
|
106
|
+
return Math.max(5_000, keepAlive - 10_000)
|
|
107
|
+
}
|
|
98
108
|
|
|
99
109
|
/**
|
|
100
110
|
* Best-effort warm a kept-alive socket to `origin` so the first real request
|
|
@@ -113,8 +123,10 @@ export function preconnect(origin) {
|
|
|
113
123
|
try { url = new URL(origin) } catch { return }
|
|
114
124
|
if (url.protocol !== 'https:' && url.protocol !== 'http:') return
|
|
115
125
|
const key = url.origin
|
|
116
|
-
|
|
117
|
-
|
|
126
|
+
const now = Date.now()
|
|
127
|
+
const last = _preconnectedAt.get(key) || 0
|
|
128
|
+
if (now - last < _preconnectTtlMs()) return
|
|
129
|
+
_preconnectedAt.set(key, now)
|
|
118
130
|
// A throwaway HEAD lands a pooled socket without fetching a body. Any
|
|
119
131
|
// failure (offline, DNS, 4xx/5xx) is irrelevant — the handshake is the
|
|
120
132
|
// point, and the real request will surface genuine errors.
|
|
@@ -124,6 +136,10 @@ export function preconnect(origin) {
|
|
|
124
136
|
signal: AbortSignal.timeout(10_000),
|
|
125
137
|
})
|
|
126
138
|
.then((res) => res.body?.dump?.())
|
|
127
|
-
.catch(() => {
|
|
139
|
+
.catch(() => {
|
|
140
|
+
// Warm failed — clear the timestamp so the next call can retry instead
|
|
141
|
+
// of waiting out the full TTL on a connection that never landed.
|
|
142
|
+
_preconnectedAt.delete(key)
|
|
143
|
+
})
|
|
128
144
|
} catch { /* never let warmup break construction */ }
|
|
129
145
|
}
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Shared LLM helpers (post v0.6.46).
|
|
3
3
|
*
|
|
4
4
|
* The legacy `callLLM` dispatcher and direct CLI/HTTP runners have been
|
|
5
|
-
* removed — every LLM call now flows through `
|
|
6
|
-
* (`
|
|
5
|
+
* removed — every LLM call now flows through `agent-dispatch.mjs`
|
|
6
|
+
* (`makeAgentDispatch({ taskType })`) and, for memory maintenance specifically,
|
|
7
7
|
* through `maintenance-llm.mjs`'s thin wrapper.
|
|
8
8
|
*
|
|
9
9
|
* Only preset resolution remains here: memory-cycle and future backend
|
|
@@ -24,18 +24,41 @@ function loadAgentConfig() {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
|
-
* Resolve maintenance
|
|
28
|
-
*
|
|
29
|
-
*
|
|
27
|
+
* Resolve the maintenance model ROUTE for a given task from agent-config.
|
|
28
|
+
*
|
|
29
|
+
* Maintenance slots now store a direct `{provider, model, effort?, fast?}`
|
|
30
|
+
* route (parity with `agents.<role>`). This returns that route object. For
|
|
31
|
+
* backward compatibility a slot still holding a legacy preset-NAME string is
|
|
32
|
+
* resolved against config.presets and returned as a route. Falls back to the
|
|
33
|
+
* canonical DEFAULT_MAINTENANCE route. Returns null only when nothing resolves.
|
|
34
|
+
*
|
|
35
|
+
* The returned value is passed straight through as `opts.preset` to
|
|
36
|
+
* makeAgentDispatch, whose resolveMaintenanceRoute()/maintenanceRouteToPreset()
|
|
37
|
+
* accept either a route object or a legacy name string.
|
|
30
38
|
*/
|
|
31
39
|
export function resolveMaintenancePreset(task, agentConfig) {
|
|
32
40
|
const cfg = agentConfig || loadAgentConfig()
|
|
33
41
|
const maint = cfg?.maintenance || {}
|
|
34
|
-
const presetId = maint[task] || DEFAULT_MAINTENANCE[task]
|
|
35
42
|
const presets = cfg?.presets || []
|
|
36
|
-
|
|
37
|
-
//
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
43
|
+
const slot = maint[task]
|
|
44
|
+
// Preferred: slot already holds a direct route object.
|
|
45
|
+
if (slot && typeof slot === 'object' && !Array.isArray(slot)
|
|
46
|
+
&& slot.provider && slot.model) {
|
|
47
|
+
return { ...slot }
|
|
48
|
+
}
|
|
49
|
+
// Legacy: slot holds a preset-NAME string — resolve it to a route.
|
|
50
|
+
const name = String(slot || '').trim()
|
|
51
|
+
if (name) {
|
|
52
|
+
const preset = presets.find(p => p && (p.id === name || p.name === name))
|
|
53
|
+
if (preset && preset.provider && preset.model) {
|
|
54
|
+
const route = { provider: preset.provider, model: preset.model }
|
|
55
|
+
if (preset.effort) route.effort = preset.effort
|
|
56
|
+
if (preset.fast === true) route.fast = true
|
|
57
|
+
return route
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// Canonical default route (DEFAULT_MAINTENANCE is now route-shaped).
|
|
61
|
+
const def = DEFAULT_MAINTENANCE[task]
|
|
62
|
+
if (def && def.provider && def.model) return { ...def }
|
|
63
|
+
return null
|
|
41
64
|
}
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Unified LLM usage logger.
|
|
3
3
|
*
|
|
4
|
-
* Phase 2: Routes via
|
|
5
|
-
* instead of writing directly to
|
|
4
|
+
* Phase 2: Routes via appendAgentTrace (HTTP buffer to memory-service)
|
|
5
|
+
* instead of writing directly to the local trace jsonl.
|
|
6
6
|
*
|
|
7
7
|
* Signature unchanged — callers are unaffected.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import { appendAgentTrace } from '../../agent/orchestrator/agent-trace.mjs';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
13
|
* Append a usage entry to the trace store.
|
|
@@ -34,7 +34,7 @@ function warnMissingProviderOnce(key) {
|
|
|
34
34
|
export function logLlmCall(entry, opts = {}) {
|
|
35
35
|
try {
|
|
36
36
|
if (!entry.provider) warnMissingProviderOnce(entry.model || '?');
|
|
37
|
-
|
|
37
|
+
appendAgentTrace({
|
|
38
38
|
ts: entry.ts || Date.now(),
|
|
39
39
|
kind: 'usage',
|
|
40
40
|
...entry,
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const FRONTMATTER_RE = /^---[ \t]*\r?\n([\s\S]*?)\r?\n---[ \t]*(?:\r?\n|$)/;
|
|
2
|
+
|
|
3
|
+
const AGENT_PERMISSION_ALIASES = new Map([
|
|
4
|
+
['none', 'none'],
|
|
5
|
+
['readonly', 'read'],
|
|
6
|
+
['read-only', 'read'],
|
|
7
|
+
['read', 'read'],
|
|
8
|
+
['write', 'read-write'],
|
|
9
|
+
['readwrite', 'read-write'],
|
|
10
|
+
['read_write', 'read-write'],
|
|
11
|
+
['read-write', 'read-write'],
|
|
12
|
+
['mcp', 'mcp'],
|
|
13
|
+
['full', 'full'],
|
|
14
|
+
]);
|
|
15
|
+
|
|
16
|
+
function clean(value) {
|
|
17
|
+
return String(value ?? '').trim();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function unquote(value) {
|
|
21
|
+
return clean(value).replace(/^['"]|['"]$/g, '').trim();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function parseMarkdownFrontmatter(markdown) {
|
|
25
|
+
const match = String(markdown || '').match(FRONTMATTER_RE);
|
|
26
|
+
if (!match) return {};
|
|
27
|
+
const meta = {};
|
|
28
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
29
|
+
const kv = line.match(/^([A-Za-z0-9_-]+):\s*(.*?)\s*$/);
|
|
30
|
+
if (!kv) continue;
|
|
31
|
+
meta[kv[1]] = unquote(kv[2]);
|
|
32
|
+
}
|
|
33
|
+
return meta;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function stripMarkdownFrontmatter(markdown) {
|
|
37
|
+
return String(markdown || '').replace(FRONTMATTER_RE, '').trim();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function readMarkdownDocument(markdown) {
|
|
41
|
+
return {
|
|
42
|
+
frontmatter: parseMarkdownFrontmatter(markdown),
|
|
43
|
+
body: stripMarkdownFrontmatter(markdown),
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function normalizeAgentPermission(value) {
|
|
48
|
+
const key = clean(value).toLowerCase();
|
|
49
|
+
return AGENT_PERMISSION_ALIASES.get(key) || null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function normalizeAgentPermissionOrNone(value) {
|
|
53
|
+
const raw = clean(value);
|
|
54
|
+
if (!raw) return null;
|
|
55
|
+
return normalizeAgentPermission(raw) || 'none';
|
|
56
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
|
|
4
|
+
export function isPidAlive(pid) {
|
|
5
|
+
const n = Number(pid);
|
|
6
|
+
if (!Number.isInteger(n) || n <= 0) return false;
|
|
7
|
+
try {
|
|
8
|
+
process.kill(n, 0);
|
|
9
|
+
return true;
|
|
10
|
+
} catch (error) {
|
|
11
|
+
return error?.code === 'EPERM';
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function readJson(path) {
|
|
16
|
+
try {
|
|
17
|
+
if (!existsSync(path)) return null;
|
|
18
|
+
return JSON.parse(readFileSync(path, 'utf8'));
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function writeJsonAtomic(path, value) {
|
|
25
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
26
|
+
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
27
|
+
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`);
|
|
28
|
+
renameSync(tmp, path);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function waitSync(ms) {
|
|
32
|
+
try {
|
|
33
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
34
|
+
} catch {}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function acquireClaimLock(path) {
|
|
38
|
+
const lockPath = `${path}.claim.lock`;
|
|
39
|
+
for (let i = 0; i < 20; i++) {
|
|
40
|
+
try {
|
|
41
|
+
mkdirSync(lockPath, { recursive: false });
|
|
42
|
+
return lockPath;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
45
|
+
try {
|
|
46
|
+
const ageMs = Date.now() - statSync(lockPath).mtimeMs;
|
|
47
|
+
if (ageMs > 5000) {
|
|
48
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
} catch {}
|
|
52
|
+
waitSync(25);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export function readSingletonOwner(path) {
|
|
59
|
+
const owner = readJson(path);
|
|
60
|
+
if (!owner || typeof owner !== 'object') return { owner: null, alive: false };
|
|
61
|
+
return { owner, alive: isPidAlive(owner.pid) };
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function claimSingletonOwner(path, {
|
|
65
|
+
kind = 'runtime',
|
|
66
|
+
pid = process.pid,
|
|
67
|
+
meta = {},
|
|
68
|
+
} = {}) {
|
|
69
|
+
const owner = {
|
|
70
|
+
kind,
|
|
71
|
+
pid,
|
|
72
|
+
claimedAt: new Date().toISOString(),
|
|
73
|
+
...meta,
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const current = readJson(path);
|
|
77
|
+
if (current?.pid && Number(current.pid) !== Number(pid) && isPidAlive(current.pid)) {
|
|
78
|
+
return { owned: false, owner: current };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const lockPath = acquireClaimLock(path);
|
|
82
|
+
if (!lockPath) return { owned: false, owner: readJson(path) };
|
|
83
|
+
try {
|
|
84
|
+
const lockedCurrent = readJson(path);
|
|
85
|
+
if (lockedCurrent?.pid && Number(lockedCurrent.pid) !== Number(pid) && isPidAlive(lockedCurrent.pid)) {
|
|
86
|
+
return { owned: false, owner: lockedCurrent };
|
|
87
|
+
}
|
|
88
|
+
writeJsonAtomic(path, owner);
|
|
89
|
+
return { owned: true, owner };
|
|
90
|
+
} finally {
|
|
91
|
+
rmSync(lockPath, { recursive: true, force: true });
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function releaseSingletonOwner(path, pid = process.pid) {
|
|
96
|
+
const current = readJson(path);
|
|
97
|
+
if (!current?.pid || Number(current.pid) !== Number(pid)) return false;
|
|
98
|
+
try {
|
|
99
|
+
rmSync(path, { force: true });
|
|
100
|
+
return true;
|
|
101
|
+
} catch {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
}
|