mixdog 0.9.96 → 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/README.md +0 -17
- package/package.json +8 -7
- package/scripts/build-tui.mjs +6 -6
- package/scripts/{channel-daemon-stub.mjs → daemon-stub.mjs} +18 -12
- 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/lead/01-general.md +1 -2
- package/src/rules/lead/lead-tool.md +0 -3
- package/src/rules/shared/01-tool.md +11 -10
- package/src/runtime/agent/orchestrator/agent-trace-io.mjs +4 -13
- package/src/runtime/agent/orchestrator/mcp/client.mjs +81 -33
- 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/session/agent-loop.mjs +8 -6
- package/src/runtime/agent/orchestrator/session/compact.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +40 -25
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/manager/prefetch-bridge.mjs +12 -6
- package/src/runtime/agent/orchestrator/session/manager/session-lock.mjs +38 -2
- 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 +59 -42
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +12 -22
- package/src/runtime/agent/orchestrator/tools/builtin/binary-file.mjs +92 -19
- package/src/runtime/agent/orchestrator/tools/builtin/fs-reachability.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-tool.mjs +80 -30
- package/src/runtime/agent/orchestrator/tools/builtin/read-tool.mjs +41 -22
- package/src/runtime/agent/orchestrator/tools/builtin/rg-runner.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +55 -11
- package/src/runtime/agent/orchestrator/tools/code-graph/search-references.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/search.mjs +55 -18
- package/src/runtime/agent/orchestrator/tools/code-graph-tool-defs.mjs +1 -0
- package/src/runtime/agent/orchestrator/tools/env-scrub.mjs +1 -2
- package/src/runtime/agent/orchestrator/tools/lib/pwsh-standby-pool.mjs +2 -2
- package/src/runtime/agent/orchestrator/tools/patch/native-server.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/patch/orchestrator.mjs +67 -21
- package/src/runtime/agent/orchestrator/tools/patch-tool-defs.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -1
- package/src/runtime/channels/lib/config.mjs +33 -33
- package/src/runtime/channels/lib/inbound-handler.mjs +7 -7
- 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/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 +72 -211
- package/src/runtime/memory/lib/agent-ipc.mjs +9 -8
- package/src/runtime/memory/lib/cycle-llm-adapters.mjs +2 -2
- package/src/runtime/memory/lib/http-router.mjs +1 -6
- package/src/runtime/memory/lib/memory-config-flags.mjs +1 -1
- package/src/runtime/memory/lib/memory-cycle2-shared.mjs +4 -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/session-ingest.mjs +1 -1
- package/src/runtime/search/index.mjs +2 -2
- package/src/runtime/shared/abort-race.mjs +70 -0
- package/src/runtime/shared/child-spawn-gate.mjs +101 -63
- package/src/runtime/shared/config.mjs +11 -24
- 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 +6 -5
- 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 -22
- package/src/runtime/shared/tool-workload-gates.mjs +29 -0
- package/src/runtime/shared/turn-snapshot.mjs +17 -2
- package/src/session-runtime/channel-config-api.mjs +5 -5
- 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/prewarm.mjs +1 -1
- 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 +52 -28
- package/src/session-runtime/self-update.mjs +1 -1
- package/src/session-runtime/session-lifecycle.mjs +36 -15
- package/src/session-runtime/session-title.mjs +1 -1
- package/src/session-runtime/session-turn-api.mjs +55 -15
- package/src/session-runtime/settings-api.mjs +7 -7
- package/src/standalone/agent-dispatch-broker.mjs +1 -1
- package/src/standalone/channel-admin.mjs +42 -53
- package/src/standalone/{channel-daemon-client.mjs → channel-client.mjs} +16 -18
- package/src/standalone/{channel-daemon-transport.mjs → channel-transport.mjs} +45 -46
- package/src/standalone/channel-worker.mjs +124 -647
- package/src/standalone/{backend-daemon.mjs → daemon.mjs} +187 -132
- package/src/standalone/memory-runtime-proxy.mjs +58 -1
- package/src/standalone/{engine-daemon-client.mjs → session-client.mjs} +151 -86
- package/src/standalone/session-protocol.mjs +8 -4
- 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} +170 -157
- package/src/standalone/{engine-daemon-transport.mjs → session-transport.mjs} +139 -61
- package/src/standalone/session-wire.mjs +55 -0
- package/src/tui/App.jsx +4 -4
- package/src/tui/app/app-format.mjs +1 -1
- package/src/tui/app/channel-pickers.mjs +41 -41
- package/src/tui/app/input-parsers.mjs +2 -2
- package/src/tui/app/onboarding-steps.mjs +2 -2
- package/src/tui/app/project-picker.mjs +8 -8
- package/src/tui/app/prompt-submit.mjs +8 -8
- package/src/tui/app/settings-picker.mjs +25 -25
- package/src/tui/app/use-prompt-queue-history.mjs +1 -1
- package/src/tui/app/use-transcript-scroll.mjs +1 -1
- package/src/tui/components/ToolExecution.jsx +1 -1
- package/src/tui/components/prompt-input/restore-policy.mjs +1 -1
- package/src/tui/components/tool-execution/surface-detail.mjs +1 -1
- package/src/tui/dist/index.mjs +154 -127
- package/src/tui/hooks/useSession.mjs +16 -0
- package/src/tui/index.jsx +11 -11
- 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 +7 -7
- 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 -17
- package/src/tui/{engine → session}/session-api.mjs +7 -60
- package/src/tui/{engine → session}/session-flow.mjs +1 -1
- 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 +4 -4
- package/src/tui/{engine → session}/tool-result-status.mjs +5 -5
- package/src/tui/{engine → session}/tool-result-text.mjs +3 -3
- package/src/tui/{engine → session}/transcript-spill.mjs +34 -34
- package/src/tui/{engine → session}/turn.mjs +29 -107
- package/src/tui/{engine-local-session.mjs → session-local.mjs} +59 -75
- package/src/tui/session.mjs +22 -0
- 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-protocol.mjs +0 -32
- 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
package/README.md
CHANGED
|
@@ -65,23 +65,6 @@ measured cache writes; the archived OpenAI runs did not retain
|
|
|
65
65
|
artifacts, the exact run commands, and the metric scripts that recompute
|
|
66
66
|
every number above live under `benchmarks/terminal-bench-2.1/`.
|
|
67
67
|
|
|
68
|
-
## Boot overhead — measured, not estimated
|
|
69
|
-
|
|
70
|
-
Fixed cost of the default system prompt + tool schemas, measured with a
|
|
71
|
-
minimal one-turn ping ("hi"). Every number below is what the provider's own
|
|
72
|
-
usage accounting reported for the first API call — same tokenizer on both
|
|
73
|
-
sides of each row, no offline estimates (2026-08).
|
|
74
|
-
|
|
75
|
-
| Tokenizer | Baseline harness | First-call prompt | mixdog Lead | mixdog worker agent |
|
|
76
|
-
|-----------|------------------|------------------:|------------:|--------------------:|
|
|
77
|
-
| Anthropic — pristine containers, TB2.1 harness | Claude Code 2.1.220 | 19,149 tok (median, n=89) | **7,295 (−62%)** | 4,856 (−75%) |
|
|
78
|
-
| OpenAI — same host, same model (`gpt-5.6-sol`) | Codex CLI 0.145.0 | 17,706 tok | **7,488 (−58%)** | 4,616 (−74%) |
|
|
79
|
-
|
|
80
|
-
The Lead surface carries the full default rule set, skills manifest, and
|
|
81
|
-
per-user profile; spawning a sub-agent costs a flat ~4.6–4.9k tokens. Low
|
|
82
|
-
boot overhead compounds: it is re-read on every request, so it is the
|
|
83
|
-
baseline of every cache write, compaction, and long-session turn.
|
|
84
|
-
|
|
85
68
|
## Why mixdog
|
|
86
69
|
|
|
87
70
|
**Maximum performance at minimum cost**
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mixdog",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.97",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "Standalone mixdog coding-agent CLI/TUI workspace.",
|
|
@@ -71,18 +71,19 @@
|
|
|
71
71
|
"test:agent-job-views": "node --test scripts/agent-job-terminal-view-test.mjs",
|
|
72
72
|
"test:agent-fanout": "node scripts/agent-parallel-smoke.mjs && node --test scripts/agent-route-batch-test.mjs scripts/execution-completion-dedup-test.mjs",
|
|
73
73
|
"test:toolcall": "node --test scripts/toolcall-args-test.mjs",
|
|
74
|
-
"test:tool-batching": "node --test scripts/eager-patch-shell-order-test.mjs scripts/session-bench-batching-test.mjs",
|
|
74
|
+
"test:tool-batching": "node --test scripts/eager-patch-shell-order-test.mjs scripts/session-bench-batching-test.mjs scripts/tool-cwd-rebind-test.mjs",
|
|
75
75
|
"test:shipmode": "node --test scripts/ship-mode-test.mjs",
|
|
76
76
|
"test:shellhardening": "node --test scripts/shell-hardening-test.mjs scripts/shell-failure-diagnostics-test.mjs scripts/windows-hide-spawn-options-test.mjs scripts/shell-job-session-scope-test.mjs",
|
|
77
77
|
"test:placeholder": "node --test scripts/compacted-placeholder-scrub-test.mjs",
|
|
78
78
|
"test:providers": "node --test scripts/provider-toolcall-test.mjs scripts/provider-contract-test.mjs scripts/provider-stream-stall-test.mjs scripts/provider-stream-outcome-test.mjs scripts/stream-frame-fault-matrix-test.mjs scripts/gemini-provider-test.mjs scripts/anthropic-transport-policy-test.mjs scripts/anthropic-native-block-replay-test.mjs scripts/openai-oauth-ws-1006-retry-test.mjs scripts/stream-close-retry-test.mjs scripts/openai-end-turn-signal-test.mjs",
|
|
79
79
|
"test:provider-admission": "node --test scripts/provider-admission-scheduler-test.mjs scripts/provider-stream-json-test.mjs",
|
|
80
|
-
"test:resource-admission": "node --test scripts/resource-admission-test.mjs scripts/child-spawn-isolation-test.mjs",
|
|
80
|
+
"test:resource-admission": "node --test scripts/resource-admission-test.mjs scripts/child-spawn-isolation-test.mjs scripts/owner-fair-gate-test.mjs",
|
|
81
81
|
"test:deferred-tools": "node --test scripts/deferred-tool-loading-test.mjs",
|
|
82
82
|
"test:anthropic-oauth-race": "node --test scripts/anthropic-oauth-refresh-race-test.mjs",
|
|
83
83
|
"test:grok-oauth-race": "node --test scripts/grok-oauth-refresh-race-test.mjs",
|
|
84
84
|
"test:atomiclock": "node --test scripts/atomic-lock-tryonce-test.mjs",
|
|
85
85
|
"test:keychain": "node --test scripts/keychain-prewarm-test.mjs",
|
|
86
|
+
"test:memory-leaks": "node --expose-gc --test scripts/memory-retention-test.mjs scripts/memory-worker-stability-test.mjs scripts/session-transport-test.mjs apps/desktop/src/renderer/memory-retention.test.mjs",
|
|
86
87
|
"test:memory-routing": "node --test scripts/memory-cycle-routing-test.mjs scripts/maintenance-default-routes-test.mjs scripts/embedding-worker-exit-test.mjs scripts/embedding-runtime-prune-test.mjs",
|
|
87
88
|
"test:embedding-runtime": "node --test scripts/embedding-runtime-prune-test.mjs scripts/memory-pg-recovery-test.mjs && node scripts/verify-embedding-runtime.mjs",
|
|
88
89
|
"test:embedding-runtime:core": "node --test scripts/embedding-runtime-prune-test.mjs scripts/memory-pg-recovery-test.mjs && node scripts/verify-embedding-runtime.mjs --core",
|
|
@@ -98,9 +99,9 @@
|
|
|
98
99
|
"test:patch-binary-cache": "node --test scripts/patch-binary-cache-test.mjs",
|
|
99
100
|
"test:patch-parity": "node --test scripts/v4a-parity-test.mjs",
|
|
100
101
|
"test:project-registry": "node --test scripts/project-registry-isolation-test.mjs",
|
|
101
|
-
"test:
|
|
102
|
-
"smoke:
|
|
103
|
-
"test:session": "node --test scripts/session-orphan-sweep-test.mjs scripts/interrupted-turn-history-test.mjs scripts/turn-checkpoint-crash-test.mjs scripts/turn-outcome-fault-matrix-test.mjs scripts/session-save-fault-store-test.mjs scripts/session-disk-authority-test.mjs scripts/session-load-cache-race-test.mjs scripts/agent-loop-complete-turn-test.mjs scripts/session-heartbeat-lifecycle-test.mjs scripts/remote-transition-order-test.mjs scripts/session-new-reset-test.mjs scripts/turn-ttft-critical-path-test.mjs",
|
|
102
|
+
"test:session-transport": "node --test scripts/fair-call-scheduler-test.mjs scripts/session-transport-test.mjs scripts/session-tui-parity-test.mjs scripts/session-recovery-test.mjs scripts/session-submit-delivery-test.mjs scripts/daemon-process-lifetime-test.mjs scripts/session-multipane-stress-test.mjs && node --test scripts/runtime-isolation-stress-test.mjs",
|
|
103
|
+
"smoke:session": "node scripts/session-smoke.mjs",
|
|
104
|
+
"test:session": "node --test scripts/runtime-turn-contract-test.mjs scripts/session-orphan-sweep-test.mjs scripts/interrupted-turn-history-test.mjs scripts/turn-checkpoint-crash-test.mjs scripts/turn-outcome-fault-matrix-test.mjs scripts/session-save-fault-store-test.mjs scripts/session-disk-authority-test.mjs scripts/session-load-cache-race-test.mjs scripts/agent-loop-complete-turn-test.mjs scripts/session-heartbeat-lifecycle-test.mjs scripts/remote-transition-order-test.mjs scripts/session-new-reset-test.mjs scripts/turn-ttft-critical-path-test.mjs",
|
|
104
105
|
"test:live-canary": "node --test scripts/live-canary-test.mjs",
|
|
105
106
|
"test:rebindtail": "node --test scripts/forwarder-rebind-tail-test.mjs scripts/channel-remote-format-test.mjs",
|
|
106
107
|
"test:workflow-editor": "node --test scripts/workflow-id-test.mjs scripts/workflow-pack-editor-test.mjs",
|
|
@@ -122,7 +123,7 @@
|
|
|
122
123
|
"bench:explore": "node scripts/explore-bench.mjs",
|
|
123
124
|
"bench:output-style": "node scripts/output-style-bench.mjs",
|
|
124
125
|
"bench:session-context": "node scripts/session-context-bench.mjs",
|
|
125
|
-
"bench:
|
|
126
|
+
"bench:session-transport": "node scripts/session-transport-bench.mjs",
|
|
126
127
|
"bench:tool-overhead": "node scripts/tool-overhead-microbench.mjs",
|
|
127
128
|
"bench:internal-comms": "node scripts/internal-comms-bench.mjs",
|
|
128
129
|
"audit:models": "node scripts/model-catalog-audit.mjs",
|
package/scripts/build-tui.mjs
CHANGED
|
@@ -41,13 +41,13 @@ const sharedRuntimeExternalPlugin = {
|
|
|
41
41
|
},
|
|
42
42
|
};
|
|
43
43
|
|
|
44
|
-
const
|
|
45
|
-
name: 'mixdog-
|
|
44
|
+
const sessionClientExternalPlugin = {
|
|
45
|
+
name: 'mixdog-session-client-external',
|
|
46
46
|
setup(build) {
|
|
47
|
-
build.onResolve({ filter: /^\.\.\/standalone\/
|
|
47
|
+
build.onResolve({ filter: /^\.\.\/standalone\/session-client\.mjs$/ }, () => ({
|
|
48
48
|
// Keep this module outside the TUI bundle so its import.meta.url stays
|
|
49
|
-
// anchored in src/standalone, where
|
|
50
|
-
path: '../../standalone/
|
|
49
|
+
// anchored in src/standalone, where daemon.mjs actually lives.
|
|
50
|
+
path: '../../standalone/session-client.mjs',
|
|
51
51
|
external: true,
|
|
52
52
|
}));
|
|
53
53
|
},
|
|
@@ -90,7 +90,7 @@ await build({
|
|
|
90
90
|
plugins: [
|
|
91
91
|
mixdogInkAliasPlugin,
|
|
92
92
|
sharedRuntimeExternalPlugin,
|
|
93
|
-
|
|
93
|
+
sessionClientExternalPlugin,
|
|
94
94
|
],
|
|
95
95
|
logLevel: 'info',
|
|
96
96
|
});
|
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
// Stub
|
|
2
|
-
// src/standalone/
|
|
1
|
+
// Stub channel service for the flip smoke: same lifecycle contract as
|
|
2
|
+
// src/standalone/daemon.mjs (pid-verified singleton claim, HTTP+SSE
|
|
3
3
|
// transport, discovery file, ready handshake, client-grace self-shutdown) but
|
|
4
4
|
// with a STUB runtime instead of worker-main — no Discord token needed. The
|
|
5
5
|
// real channel-worker.mjs spawn-or-attach path forks THIS via
|
|
6
|
-
//
|
|
6
|
+
// MIXDOG_DAEMON_ENTRY, so the flip is exercised end to end.
|
|
7
7
|
process.env.MIXDOG_WORKER_MODE = process.env.MIXDOG_WORKER_MODE || '1';
|
|
8
8
|
|
|
9
9
|
import os from 'node:os';
|
|
10
10
|
import path from 'node:path';
|
|
11
|
-
import { mkdirSync, writeFileSync } from 'node:fs';
|
|
11
|
+
import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
12
|
+
import { writeJsonAtomicSync } from '../src/runtime/shared/atomic-file.mjs';
|
|
12
13
|
import { claimSingletonOwner, releaseSingletonOwner } from '../src/runtime/shared/singleton-owner.mjs';
|
|
13
14
|
import { safeIpcSend } from '../src/runtime/shared/safe-ipc-send.mjs';
|
|
14
|
-
import {
|
|
15
|
+
import { createChannelTransport } from '../src/standalone/channel-transport.mjs';
|
|
15
16
|
|
|
16
17
|
function runtimeRoot() {
|
|
17
18
|
return process.env.MIXDOG_RUNTIME_ROOT
|
|
@@ -20,8 +21,8 @@ function runtimeRoot() {
|
|
|
20
21
|
}
|
|
21
22
|
const RUNTIME_ROOT = runtimeRoot();
|
|
22
23
|
const DATA_DIR = process.env.MIXDOG_DATA_DIR ? path.resolve(process.env.MIXDOG_DATA_DIR) : RUNTIME_ROOT;
|
|
23
|
-
const DISCOVERY_PATH = path.join(RUNTIME_ROOT, '
|
|
24
|
-
const OWNER_PATH = path.join(DATA_DIR, '
|
|
24
|
+
const DISCOVERY_PATH = path.join(RUNTIME_ROOT, 'daemon.json');
|
|
25
|
+
const OWNER_PATH = path.join(DATA_DIR, 'daemon-owner.json');
|
|
25
26
|
|
|
26
27
|
function log(line) { if (process.env.DAEMON_SMOKE_VERBOSE) process.stderr.write(`[stub-daemon] ${line}\n`); }
|
|
27
28
|
|
|
@@ -31,23 +32,24 @@ async function shutdown(reason, code = 0) {
|
|
|
31
32
|
if (shuttingDown) return;
|
|
32
33
|
shuttingDown = true;
|
|
33
34
|
try { await transport?.stop?.(); } catch {}
|
|
35
|
+
try { rmSync(DISCOVERY_PATH, { force: true }); } catch {}
|
|
34
36
|
try { releaseSingletonOwner(OWNER_PATH, process.pid); } catch {}
|
|
35
37
|
process.exit(code);
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
async function main() {
|
|
39
41
|
try { mkdirSync(RUNTIME_ROOT, { recursive: true }); } catch {}
|
|
40
|
-
const claim = claimSingletonOwner(OWNER_PATH, { kind: '
|
|
42
|
+
const claim = claimSingletonOwner(OWNER_PATH, { kind: 'mixdog-daemon', pid: process.pid, meta: { cwd: process.cwd() } });
|
|
41
43
|
if (!claim.owned) { process.exit(0); } // race loser → spawner attaches to winner
|
|
42
44
|
process.on('exit', () => { try { releaseSingletonOwner(OWNER_PATH, process.pid); } catch {} });
|
|
43
45
|
|
|
44
46
|
// Optional env dump for the tool smoke: proves daemonEnv() spawned us with
|
|
45
|
-
// the
|
|
47
|
+
// the host-mode flags (MIXDOG_DAEMON_HOST=1, MIXDOG_CLI_OWNED=0).
|
|
46
48
|
if (process.env.SMOKE_CHANNEL_ENV_OUT) {
|
|
47
49
|
try {
|
|
48
50
|
writeFileSync(process.env.SMOKE_CHANNEL_ENV_OUT, JSON.stringify({
|
|
49
51
|
cliOwned: process.env.MIXDOG_CLI_OWNED,
|
|
50
|
-
|
|
52
|
+
host: process.env.MIXDOG_DAEMON_HOST,
|
|
51
53
|
}));
|
|
52
54
|
} catch { /* smoke-only, best-effort */ }
|
|
53
55
|
}
|
|
@@ -61,15 +63,19 @@ async function main() {
|
|
|
61
63
|
return { ok: true, name, args, leadPid: ctx.leadPid };
|
|
62
64
|
};
|
|
63
65
|
|
|
64
|
-
transport =
|
|
66
|
+
transport = createChannelTransport({
|
|
65
67
|
handleCall,
|
|
66
|
-
discoveryPath: DISCOVERY_PATH,
|
|
67
68
|
clientGraceMs: 250,
|
|
68
69
|
sweepMs: 1000,
|
|
69
70
|
log,
|
|
70
71
|
onClientsEmpty: () => { void shutdown('no live clients'); },
|
|
71
72
|
});
|
|
72
73
|
const { port, token } = await transport.start();
|
|
74
|
+
writeJsonAtomicSync(DISCOVERY_PATH, {
|
|
75
|
+
pid: process.pid,
|
|
76
|
+
startedAt: Date.now(),
|
|
77
|
+
endpoints: { channel: { port, token } },
|
|
78
|
+
}, { compact: true });
|
|
73
79
|
safeIpcSend(process, { type: 'ready', port, token });
|
|
74
80
|
log(`ready port=${port} pid=${process.pid}`);
|
|
75
81
|
}
|
|
@@ -132,7 +132,7 @@ apply it, and verify the result.
|
|
|
132
132
|
### Discord / Telegram / voice channels
|
|
133
133
|
|
|
134
134
|
1. Check `/channels` (or `/setting` → Channel) and runtime channel status.
|
|
135
|
-
2. Update the requested
|
|
135
|
+
2. Update the requested provider, credential, or main channel/chat setting.
|
|
136
136
|
3. Restart/reconnect only when the channel implementation requires it.
|
|
137
137
|
4. Verify channel presence and a non-secret status signal.
|
|
138
138
|
|
package/src/lib/keychain-cjs.cjs
CHANGED
|
@@ -6,7 +6,7 @@ const fs = require('fs');
|
|
|
6
6
|
const { resolvePluginData } = require('./plugin-paths.cjs');
|
|
7
7
|
|
|
8
8
|
const SERVICE = 'mixdog';
|
|
9
|
-
// Shared bound for every synchronous keychain
|
|
9
|
+
// Shared bound for every synchronous keychain provider (DPAPI/PowerShell on
|
|
10
10
|
// Windows, security(1) on macOS). A single env override keeps them consistent.
|
|
11
11
|
const KEYCHAIN_TIMEOUT_MS = Number(process.env.MIXDOG_KEYCHAIN_TIMEOUT_MS || 15000);
|
|
12
12
|
const POWERSHELL_TIMEOUT_MS = KEYCHAIN_TIMEOUT_MS;
|
package/src/lib/mixdog-debug.cjs
CHANGED
|
@@ -102,8 +102,7 @@ const CANONICAL_PLUGIN_LOG_NAMES = new Set([
|
|
|
102
102
|
'webhook.log',
|
|
103
103
|
'perf.log',
|
|
104
104
|
'tool-events.log',
|
|
105
|
-
'
|
|
106
|
-
'channels-worker-standalone.log',
|
|
105
|
+
'daemon.log',
|
|
107
106
|
'mixdog-tui.stderr.log',
|
|
108
107
|
]);
|
|
109
108
|
|
|
@@ -183,7 +182,7 @@ function rotateBoundedLog(filePath, maxBytes, keepBytes) {
|
|
|
183
182
|
}
|
|
184
183
|
|
|
185
184
|
// Shared bound for unbounded per-writer plugin logs (tool-events,
|
|
186
|
-
// memory
|
|
185
|
+
// memory runtime and daemon logs). Keep a tail so recent
|
|
187
186
|
// context survives rotation while the file stays under the 10 MB cap.
|
|
188
187
|
const PLUGIN_LOG_MAX_BYTES = 10 * 1024 * 1024;
|
|
189
188
|
const PLUGIN_LOG_KEEP_BYTES = 2 * 1024 * 1024;
|
|
@@ -7,12 +7,11 @@
|
|
|
7
7
|
- Destructive/hard-to-reverse action needs explicit confirmation and explicit
|
|
8
8
|
validated target paths — never `~`, a root, or unresolved variables/globs;
|
|
9
9
|
report material deletions with recoverability.
|
|
10
|
-
-
|
|
10
|
+
- Ask only for decisions.
|
|
11
11
|
- Build only what the task requires; trust internal and framework guarantees.
|
|
12
12
|
- Mid-task input: a replacement supersedes current work, an addition folds
|
|
13
13
|
into it, a status question gets a brief answer while work continues; after
|
|
14
14
|
context compaction continue from the summary — never restart or redo
|
|
15
15
|
finished work.
|
|
16
|
-
- When blocked, exhaust safe in-scope checks once and report the blocker.
|
|
17
16
|
- Your final message ends the turn: answer only when the work is done. After a
|
|
18
17
|
failed tool call, fix and re-run it, or state plainly that it is unresolved.
|
|
@@ -1,6 +1,3 @@
|
|
|
1
1
|
# Lead Tools
|
|
2
2
|
|
|
3
|
-
- Write-role agents self-verify with `shell`. Lead uses `shell` only for git,
|
|
4
|
-
benches, and cross-scope verification no retrieval tool can produce;
|
|
5
|
-
inspection stays on `read`/`grep`/`glob`/`list`/`find`/`code_graph`.
|
|
6
3
|
- Use the current project/workspace unless the request or tool requires another.
|
|
@@ -7,16 +7,17 @@
|
|
|
7
7
|
`read`; symbol/relation→`code_graph`; web/current→`search`; returned URL body→
|
|
8
8
|
`web_fetch`; prior work→`recall`; durable compact English memory→`memory`;
|
|
9
9
|
explicit project change→`cwd`; explicit user-requested conversation reset→
|
|
10
|
-
`session_manage`; process/env, git, build/run/test→`shell`.
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
10
|
+
`session_manage`; process/env, git, build/run/test→`shell`. Call `shell` only
|
|
11
|
+
when the task actually requires one of those operations; do not treat it as
|
|
12
|
+
a routine investigation or completion step. Never use shell equivalents for
|
|
13
|
+
file discovery or content retrieval.
|
|
14
|
+
- Use verified paths (cwd/project/user/tool); explicit paths may be outside cwd;
|
|
15
|
+
guessed fragments use `find`. Maximize fan-out across routed retrieval tools
|
|
16
|
+
and fetch all information needed in one batch; never re-fetch an unchanged
|
|
17
|
+
span.
|
|
18
|
+
- Once the edit is determined, finish in one assistant turn with one
|
|
19
|
+
`apply_patch` for all edits. If final verification actually requires `shell`,
|
|
20
|
+
use one verification chain after the edit; otherwise finish without it.
|
|
20
21
|
- After a call returns a background `task_id`, end the turn; its completion
|
|
21
22
|
notification resumes work. Never poll; use task control only for recovery or
|
|
22
23
|
a required blocking result.
|
|
@@ -9,7 +9,8 @@ import { isDiagnosticIOEnabled } from '../../../lib/mixdog-debug.cjs';
|
|
|
9
9
|
const WARNED_KEYS = new Set();
|
|
10
10
|
|
|
11
11
|
// ---------------------------------------------------------------------------
|
|
12
|
-
// In-memory buffer
|
|
12
|
+
// In-memory buffer. Product sessions flush directly into the daemon-hosted
|
|
13
|
+
// memory module; the standalone path can still use its local HTTP service.
|
|
13
14
|
// ---------------------------------------------------------------------------
|
|
14
15
|
let _buffer = [];
|
|
15
16
|
const _BUFFER_MAX = 2000;
|
|
@@ -225,21 +226,11 @@ try {
|
|
|
225
226
|
function _resolveServiceUrl() {
|
|
226
227
|
if (_serviceUrl) return _serviceUrl;
|
|
227
228
|
try {
|
|
228
|
-
//
|
|
229
|
-
// pid-validated; fall back to legacy active-instance.json memory_port.
|
|
229
|
+
// Standalone memory HTTP uses its pid-validated discovery advert.
|
|
230
230
|
const advertPort = readServicePort('memory', { requirePid: false });
|
|
231
231
|
if (advertPort) { _serviceAdvertPort = advertPort; _serviceUrl = `http://127.0.0.1:${advertPort}`; return _serviceUrl; }
|
|
232
232
|
_serviceAdvertPort = null;
|
|
233
|
-
|
|
234
|
-
? join(process.env.MIXDOG_RUNTIME_ROOT)
|
|
235
|
-
: join(os.tmpdir(), 'mixdog');
|
|
236
|
-
const activeFile = join(runtimeRoot, 'active-instance.json');
|
|
237
|
-
if (!existsSync(activeFile)) return null;
|
|
238
|
-
const active = JSON.parse(readFileSync(activeFile, 'utf-8'));
|
|
239
|
-
const port = Number(active && active.memory_port);
|
|
240
|
-
if (!Number.isFinite(port) || port <= 0) return null;
|
|
241
|
-
_serviceUrl = `http://127.0.0.1:${port}`;
|
|
242
|
-
return _serviceUrl;
|
|
233
|
+
return null;
|
|
243
234
|
} catch {
|
|
244
235
|
return null;
|
|
245
236
|
}
|
|
@@ -9,6 +9,8 @@ import { readServicePort, markServiceUnreachable, isConnRefuseError } from '../.
|
|
|
9
9
|
import { makeToolEnvelope, normalizeToolEnvelope } from '../session/tool-envelope.mjs';
|
|
10
10
|
import { classifyResultKind } from '../session/result-classification.mjs';
|
|
11
11
|
import { createKeyedSingleflight } from './reconnect-singleflight.mjs';
|
|
12
|
+
import { createOwnerFairGate } from '../../../shared/owner-fair-gate.mjs';
|
|
13
|
+
import { currentToolExecutionOwner } from '../../../shared/tool-execution-owner.mjs';
|
|
12
14
|
// --- Types ---
|
|
13
15
|
/** Known auto-detect targets: port file path relative to tmpdir.
|
|
14
16
|
* Note: `mixdog` used to self-loopback via active-instance.json's
|
|
@@ -17,7 +19,7 @@ import { createKeyedSingleflight } from './reconnect-singleflight.mjs';
|
|
|
17
19
|
* in-process through agent's toolExecutor (see orchestrator/internal-tools),
|
|
18
20
|
* so this registry is for genuinely external port-based MCP targets only. */
|
|
19
21
|
const AUTO_DETECT_PORTS = {
|
|
20
|
-
'mixdog-memory': { discovery: 'memory',
|
|
22
|
+
'mixdog-memory': { discovery: 'memory', endpoint: '/mcp' },
|
|
21
23
|
};
|
|
22
24
|
const DEFAULT_MCP_CALL_TIMEOUT_MS = 120000;
|
|
23
25
|
// Per-server STARTUP handshake budget (connect + listTools): 10s.
|
|
@@ -25,6 +27,7 @@ const DEFAULT_MCP_STARTUP_TIMEOUT_MS = 10000;
|
|
|
25
27
|
// --- State ---
|
|
26
28
|
const servers = new Map();
|
|
27
29
|
const reconnects = createKeyedSingleflight();
|
|
30
|
+
const callAdmissions = new Map();
|
|
28
31
|
let mcpSdkPromise = null;
|
|
29
32
|
// Memo for mcpToolHasField(name, field) — keyed by `${toolName}|${field}`.
|
|
30
33
|
// The lookup (regex parse + servers Map get + tools.find + schema property
|
|
@@ -165,6 +168,45 @@ export function getMcpServerStatus() {
|
|
|
165
168
|
})(),
|
|
166
169
|
}));
|
|
167
170
|
}
|
|
171
|
+
function positiveInt(value, fallback) {
|
|
172
|
+
const parsed = Math.floor(Number(value));
|
|
173
|
+
return Number.isFinite(parsed) && parsed >= 1 ? parsed : fallback;
|
|
174
|
+
}
|
|
175
|
+
function callAdmissionFor(server) {
|
|
176
|
+
let gate = callAdmissions.get(server.name);
|
|
177
|
+
if (gate) return gate;
|
|
178
|
+
const cfg = server?.cfg || {};
|
|
179
|
+
gate = createOwnerFairGate({
|
|
180
|
+
name: `MCP ${server.name}`,
|
|
181
|
+
activeMax: positiveInt(
|
|
182
|
+
cfg.maxConcurrency ?? cfg.max_concurrency ?? process.env.MIXDOG_MCP_MAX_INFLIGHT,
|
|
183
|
+
8,
|
|
184
|
+
),
|
|
185
|
+
queueMax: positiveInt(
|
|
186
|
+
cfg.maxQueue ?? cfg.max_queue ?? process.env.MIXDOG_MCP_MAX_QUEUE,
|
|
187
|
+
256,
|
|
188
|
+
),
|
|
189
|
+
minOwnerQueue: 8,
|
|
190
|
+
waitTimeoutMs: positiveInt(
|
|
191
|
+
cfg.waitTimeoutMs ?? cfg.wait_timeout_ms ?? process.env.MIXDOG_MCP_WAIT_TIMEOUT_MS,
|
|
192
|
+
30_000,
|
|
193
|
+
),
|
|
194
|
+
});
|
|
195
|
+
callAdmissions.set(server.name, gate);
|
|
196
|
+
return gate;
|
|
197
|
+
}
|
|
198
|
+
function closeCallAdmission(name, reason) {
|
|
199
|
+
const gate = callAdmissions.get(name);
|
|
200
|
+
if (!gate) return;
|
|
201
|
+
callAdmissions.delete(name);
|
|
202
|
+
gate.close(reason || `MCP ${name} disconnected`);
|
|
203
|
+
}
|
|
204
|
+
export function getMcpAdmissionSnapshot() {
|
|
205
|
+
return [...callAdmissions.entries()].map(([name, gate]) => ({
|
|
206
|
+
name,
|
|
207
|
+
...gate.snapshot(),
|
|
208
|
+
}));
|
|
209
|
+
}
|
|
168
210
|
|
|
169
211
|
/** Snapshot of MCP initialize `instructions` per connected server (handshake time). */
|
|
170
212
|
export function getMcpServerInstructionsMap() {
|
|
@@ -199,7 +241,7 @@ async function reconnectMcpServer(serverName, failedServer) {
|
|
|
199
241
|
* Execute an MCP tool call.
|
|
200
242
|
* Name format: `mcp__{serverName}__{toolName}`
|
|
201
243
|
*/
|
|
202
|
-
export async function executeMcpTool(name, args) {
|
|
244
|
+
export async function executeMcpTool(name, args, options = {}) {
|
|
203
245
|
// Parse: mcp__{server}__{tool}
|
|
204
246
|
const match = name.match(/^mcp__(.+?)__(.+)$/);
|
|
205
247
|
if (!match)
|
|
@@ -208,35 +250,40 @@ export async function executeMcpTool(name, args) {
|
|
|
208
250
|
const server = servers.get(serverName);
|
|
209
251
|
if (!server)
|
|
210
252
|
throw new Error(`MCP server "${serverName}" not connected`);
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
result
|
|
214
|
-
} catch (firstErr) {
|
|
215
|
-
const firstMsg = firstErr instanceof Error ? firstErr.message : String(firstErr);
|
|
216
|
-
if (isMcpToolCallTimeoutError(firstErr)) {
|
|
217
|
-
mcpLog(`[mcp-client] Tool call timed out; skipping reconnect retry for "${serverName}/${toolName}".\n`);
|
|
218
|
-
throw firstErr;
|
|
219
|
-
}
|
|
220
|
-
mcpLog(`[mcp-client] Tool call failed, attempting shared reconnect...\n`);
|
|
221
|
-
let retryServer;
|
|
253
|
+
const gate = callAdmissionFor(server);
|
|
254
|
+
return gate.run(options?.ownerKey || currentToolExecutionOwner(), async () => {
|
|
255
|
+
let result;
|
|
222
256
|
try {
|
|
223
|
-
|
|
224
|
-
} catch (
|
|
225
|
-
const
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
257
|
+
result = await _callToolWithTimeout(server, toolName, args);
|
|
258
|
+
} catch (firstErr) {
|
|
259
|
+
const firstMsg = firstErr instanceof Error ? firstErr.message : String(firstErr);
|
|
260
|
+
if (isMcpToolCallTimeoutError(firstErr)) {
|
|
261
|
+
mcpLog(`[mcp-client] Tool call timed out; skipping reconnect retry for "${serverName}/${toolName}".\n`);
|
|
262
|
+
throw firstErr;
|
|
263
|
+
}
|
|
264
|
+
mcpLog(`[mcp-client] Tool call failed, attempting shared reconnect...\n`);
|
|
265
|
+
let retryServer;
|
|
266
|
+
try {
|
|
267
|
+
retryServer = await reconnectMcpServer(serverName, server);
|
|
268
|
+
} catch (reconnectErr) {
|
|
269
|
+
const reconnectMsg = reconnectErr instanceof Error ? reconnectErr.message : String(reconnectErr);
|
|
270
|
+
throw new Error(`Tool call failed: ${firstMsg}; reconnect also failed: ${reconnectMsg}`);
|
|
271
|
+
}
|
|
272
|
+
try {
|
|
273
|
+
result = await _callToolWithTimeout(retryServer, toolName, args);
|
|
274
|
+
} catch (retryErr) {
|
|
275
|
+
const retryMsg = retryErr instanceof Error ? retryErr.message : String(retryErr);
|
|
276
|
+
throw new Error(`Tool call failed: ${firstMsg}; retry after reconnect also failed: ${retryMsg}`);
|
|
277
|
+
}
|
|
233
278
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
279
|
+
const normalized = normalizeToolEnvelope(normalizeMcpToolResult(result));
|
|
280
|
+
const text = capMcpOutput(normalized.result);
|
|
281
|
+
return normalized.explicitSuccess
|
|
282
|
+
? makeToolEnvelope(text, [], { explicitSuccess: true })
|
|
283
|
+
: text;
|
|
284
|
+
}, {
|
|
285
|
+
signal: options?.signal || null,
|
|
286
|
+
});
|
|
240
287
|
}
|
|
241
288
|
|
|
242
289
|
// Preserve MCP failure metadata across the object→string boundary. The
|
|
@@ -425,6 +472,7 @@ export async function disconnectAll() {
|
|
|
425
472
|
}
|
|
426
473
|
catch { /* ignore */ }
|
|
427
474
|
servers.delete(name);
|
|
475
|
+
closeCallAdmission(name, `MCP ${name} disconnected`);
|
|
428
476
|
}
|
|
429
477
|
_invalidateMcpToolFieldMemo();
|
|
430
478
|
}
|
|
@@ -442,6 +490,7 @@ export async function disconnectMcpServer(name) {
|
|
|
442
490
|
}
|
|
443
491
|
catch { /* ignore */ }
|
|
444
492
|
servers.delete(name);
|
|
493
|
+
closeCallAdmission(name, `MCP ${name} disconnected`);
|
|
445
494
|
_invalidateMcpToolFieldMemo();
|
|
446
495
|
return true;
|
|
447
496
|
}
|
|
@@ -494,13 +543,11 @@ async function connectServer(name, cfg, genAtStart = _connectAbortGeneration) {
|
|
|
494
543
|
const spec = AUTO_DETECT_PORTS[cfg.autoDetect];
|
|
495
544
|
if (!spec)
|
|
496
545
|
throw new Error(`Unknown autoDetect target: "${cfg.autoDetect}"`);
|
|
497
|
-
//
|
|
498
|
-
// pid-validated. Fall back to the legacy active-instance.json portField
|
|
499
|
-
// when no live advert is present (cross-version compat).
|
|
546
|
+
// Read the pid-validated single-writer discovery advert.
|
|
500
547
|
let port = spec.discovery ? readServicePort(spec.discovery, { requirePid: false }) : null;
|
|
501
548
|
if (port && spec.discovery) _autoDetectAdvert = { service: spec.discovery, port };
|
|
502
549
|
let portFile = null;
|
|
503
|
-
if (!port) {
|
|
550
|
+
if (!port && spec.file) {
|
|
504
551
|
portFile = spec.dir === 'mixdog' && process.env.MIXDOG_RUNTIME_ROOT
|
|
505
552
|
? join(process.env.MIXDOG_RUNTIME_ROOT, spec.file)
|
|
506
553
|
: join(tmpdir(), spec.dir, spec.file);
|
|
@@ -525,6 +572,7 @@ async function connectServer(name, cfg, genAtStart = _connectAbortGeneration) {
|
|
|
525
572
|
port = parseInt(raw, 10);
|
|
526
573
|
}
|
|
527
574
|
}
|
|
575
|
+
if (!port) throw new Error(`autoDetect server "${name}": live service advert missing`);
|
|
528
576
|
if (!Number.isFinite(port) || port < 1 || port > 65535) {
|
|
529
577
|
throw new Error(`autoDetect server "${name}": invalid port value${portFile ? ` in ${portFile}` : ''}`);
|
|
530
578
|
}
|
|
@@ -535,12 +535,9 @@ export async function _streamResponse({
|
|
|
535
535
|
// only) trips a short, named terminal StreamStalledError instead of coasting
|
|
536
536
|
// to the 300s inter-chunk cap / 30-min agent watchdog.
|
|
537
537
|
let semanticIdleTimer = null;
|
|
538
|
-
// WS
|
|
539
|
-
//
|
|
540
|
-
//
|
|
541
|
-
// request race the TUI 300s watchdog. Keep websocket stalls provider-owned:
|
|
542
|
-
// fail/retry/fallback here first, then let the TUI watchdog remain a final
|
|
543
|
-
// safety net only.
|
|
538
|
+
// WS owns its semantic-idle window. The TUI backstop is deliberately much
|
|
539
|
+
// longer, so this timeout can fail/retry/fallback at the provider boundary
|
|
540
|
+
// without racing an outer turn abort (Claude Code/Codex parity).
|
|
544
541
|
const semanticIdleMs = PROVIDER_WS_SEMANTIC_IDLE_TIMEOUT_MS;
|
|
545
542
|
const semanticIdleEnabled = PROVIDER_SSE_IDLE_WATCHDOG_ENABLED && semanticIdleMs > 0;
|
|
546
543
|
// First-meaningful-frame watchdog timer + one-shot latch. Armed alongside
|
|
@@ -35,7 +35,7 @@ let _initChain = Promise.resolve();
|
|
|
35
35
|
let _inFlightPromise = null;
|
|
36
36
|
let _inFlightSig = null;
|
|
37
37
|
let _lastAppliedSig = null;
|
|
38
|
-
// Provider instances are process-global inside the
|
|
38
|
+
// Provider instances are process-global inside the daemon. Catalog
|
|
39
39
|
// readers use this revision to share one raw model snapshot while still
|
|
40
40
|
// rebuilding their cheap route/config projection after auth/config changes.
|
|
41
41
|
let _providerCatalogRevision = 0;
|
|
@@ -367,7 +367,7 @@ function warmupCatalogs() {
|
|
|
367
367
|
}
|
|
368
368
|
}
|
|
369
369
|
|
|
370
|
-
// Force-refresh each provider's /models catalog
|
|
370
|
+
// Force-refresh each provider's /models catalog once per daemon.
|
|
371
371
|
// lifetime. Every session runtime joins this process-global promise and then
|
|
372
372
|
// reads the same provider-instance caches. Unlike
|
|
373
373
|
// warmupCatalogs (which calls listModels() and so respects the 24h provider
|
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
} from './loop/tool-classify.mjs';
|
|
31
31
|
import { preDispatchDenyForSession } from './loop/pre-dispatch-deny.mjs';
|
|
32
32
|
import { runRecallFastTrackCompact } from './loop/recall-fasttrack.mjs';
|
|
33
|
-
import { executeTool, _scopedCacheOutcomeForCall } from './loop/tool-exec.mjs';
|
|
33
|
+
import { executeTool, _scopedCacheOutcomeForCall, resolveLiveToolCwd } from './loop/tool-exec.mjs';
|
|
34
34
|
|
|
35
35
|
// classifyResultKind is imported from result-classification.mjs at the top of
|
|
36
36
|
// this file; import it from there directly rather than via this module.
|
|
@@ -420,11 +420,9 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
420
420
|
const name = String(call?.name || call?.toolName || call?.function?.name || '').toLowerCase();
|
|
421
421
|
return name === 'sleep' || name.endsWith('/sleep') || name.endsWith('.sleep');
|
|
422
422
|
};
|
|
423
|
-
//
|
|
424
|
-
//
|
|
425
|
-
|
|
426
|
-
// against the host/plugin root instead of the worker workspace.
|
|
427
|
-
cwd = cwd || sessionRef?.cwd || undefined;
|
|
423
|
+
// sessionRef.cwd is the live SSOT. The legacy positional cwd is only the
|
|
424
|
+
// turn-start snapshot and becomes stale after an in-turn cwd tool call.
|
|
425
|
+
cwd = resolveLiveToolCwd(cwd, sessionRef);
|
|
428
426
|
// Staged pre-cap warnings + one true hard stop. The ONLY count-based
|
|
429
427
|
// forced termination is the hard cap at maxLoopIterations (default 200):
|
|
430
428
|
// a genuine runaway guard. Before it, staged warnings fire at 50%/75%/90%
|
|
@@ -445,6 +443,10 @@ export async function agentLoop(provider, messages, model, tools, onToolCall, cw
|
|
|
445
443
|
Math.floor(maxLoopIterations * 0.9),
|
|
446
444
|
];
|
|
447
445
|
while (true) {
|
|
446
|
+
// A cwd tool call updates sessionRef in place. Refresh before building
|
|
447
|
+
// this iteration's eager dispatcher and cache keys so every following
|
|
448
|
+
// tool family, including apply_patch, observes the new write root.
|
|
449
|
+
cwd = resolveLiveToolCwd(cwd, sessionRef);
|
|
448
450
|
const _iterT0 = Date.now();
|
|
449
451
|
throwIfAborted();
|
|
450
452
|
if (iterations >= maxLoopIterations) {
|