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
|
@@ -4,19 +4,29 @@ import { requestMemoryPressureSnapshot } from './memory-snapshot.mjs';
|
|
|
4
4
|
|
|
5
5
|
const MB = 1024 * 1024;
|
|
6
6
|
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// overrides (MIXDOG_MAX_CONCURRENT_AGENTS / _SHELLS / _HIGH_LOAD).
|
|
7
|
+
// The unified daemon queues bursts instead of starting an unbounded number of
|
|
8
|
+
// agent/provider/shell tasks in one process. Environment overrides remain
|
|
9
|
+
// available for larger hosts; memory thresholds are diagnostic only.
|
|
11
10
|
export const RESOURCE_ADMISSION_DEFAULTS = Object.freeze({
|
|
12
|
-
maxAgents:
|
|
13
|
-
maxShells:
|
|
14
|
-
maxHighLoad:
|
|
15
|
-
maxQueue:
|
|
16
|
-
minFreeMemoryMb:
|
|
17
|
-
maxRssMb:
|
|
11
|
+
maxAgents: 8,
|
|
12
|
+
maxShells: 8,
|
|
13
|
+
maxHighLoad: 12,
|
|
14
|
+
maxQueue: 1024,
|
|
15
|
+
minFreeMemoryMb: 0,
|
|
16
|
+
maxRssMb: 0,
|
|
18
17
|
});
|
|
19
18
|
|
|
19
|
+
const PRIORITY_RANK = Object.freeze({
|
|
20
|
+
'user-blocking': 0,
|
|
21
|
+
'user-visible': 1,
|
|
22
|
+
'best-effort': 2,
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
function normalizePriority(value) {
|
|
26
|
+
const key = String(value || 'user-visible').toLowerCase();
|
|
27
|
+
return Object.hasOwn(PRIORITY_RANK, key) ? key : 'user-visible';
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
function positiveInt(value, fallback) {
|
|
21
31
|
const parsed = Math.floor(Number(value));
|
|
22
32
|
return Number.isFinite(parsed) && parsed >= 1 ? parsed : fallback;
|
|
@@ -54,19 +64,6 @@ function abortError(signal) {
|
|
|
54
64
|
: new Error(String(signal?.reason || 'resource admission canceled'));
|
|
55
65
|
}
|
|
56
66
|
|
|
57
|
-
function sleep(ms, signal) {
|
|
58
|
-
return new Promise((resolve) => {
|
|
59
|
-
const finish = () => {
|
|
60
|
-
clearTimeout(timer);
|
|
61
|
-
signal?.removeEventListener?.('abort', finish);
|
|
62
|
-
resolve();
|
|
63
|
-
};
|
|
64
|
-
const timer = setTimeout(finish, ms);
|
|
65
|
-
timer.unref?.();
|
|
66
|
-
signal?.addEventListener?.('abort', finish, { once: true });
|
|
67
|
-
});
|
|
68
|
-
}
|
|
69
|
-
|
|
70
67
|
class ResourcePressureError extends Error {
|
|
71
68
|
constructor(message, details = {}) {
|
|
72
69
|
super(`resource pressure: ${message}`);
|
|
@@ -102,48 +99,37 @@ export class ResourceAdmissionController {
|
|
|
102
99
|
this.metrics = metrics;
|
|
103
100
|
this.now = now;
|
|
104
101
|
this.active = { agent: 0, shell: 0 };
|
|
102
|
+
this.activeByOwner = { agent: new Map(), shell: new Map() };
|
|
103
|
+
this.fairCursor = { agent: null, shell: null };
|
|
105
104
|
this.queue = [];
|
|
106
105
|
// Live leases for saturation diagnostics only (labels/ages in snapshot()).
|
|
107
106
|
this.activeLeases = new Set();
|
|
108
107
|
this.context = new AsyncLocalStorage();
|
|
109
|
-
// Host free memory is an INSTANTANEOUS sample: a GC pause or a just-exited
|
|
110
|
-
// child can dip it below the floor for a few hundred ms. Re-measure a
|
|
111
|
-
// bounded number of times before refusing, so a transient dip costs
|
|
112
|
-
// latency instead of a failed tool call.
|
|
113
|
-
this.memoryRetry = {
|
|
114
|
-
attempts: nonNegativeInt(env.MIXDOG_MEMORY_PRESSURE_RETRIES, 3),
|
|
115
|
-
delayMs: nonNegativeInt(env.MIXDOG_MEMORY_PRESSURE_RETRY_MS, 400),
|
|
116
|
-
};
|
|
117
108
|
}
|
|
118
109
|
|
|
119
|
-
|
|
110
|
+
_recordMemoryPressure(kind) {
|
|
111
|
+
// Thresholds are an operator-opt-in telemetry trigger only. The OS owns
|
|
112
|
+
// memory pressure policy; Mixdog never turns a valid agent/shell request
|
|
113
|
+
// into a failed task because of an RSS or host-free-memory sample.
|
|
114
|
+
if (this.limits.maxRssMb <= 0 && this.limits.minFreeMemoryMb <= 0) return false;
|
|
120
115
|
let sample;
|
|
121
116
|
try { sample = this.metrics() || {}; }
|
|
122
|
-
catch
|
|
123
|
-
return new ResourcePressureError(`memory metrics unavailable; refusing new ${kind} work`, {
|
|
124
|
-
kind,
|
|
125
|
-
cause: error,
|
|
126
|
-
});
|
|
127
|
-
}
|
|
117
|
+
catch { return false; }
|
|
128
118
|
const rssMb = Number(sample.rssBytes) / MB;
|
|
129
119
|
const freeMb = Number(sample.freeMemoryBytes) / MB;
|
|
130
120
|
if (this.limits.maxRssMb > 0 && Number.isFinite(rssMb) && rssMb >= this.limits.maxRssMb) {
|
|
131
|
-
|
|
132
|
-
`Mixdog RSS ${Math.ceil(rssMb)} MB reached ${this.limits.maxRssMb} MB
|
|
133
|
-
{ kind, rssMb, limitMb: this.limits.maxRssMb, metric: 'rss' },
|
|
121
|
+
requestMemoryPressureSnapshot(
|
|
122
|
+
`diagnostic threshold: Mixdog RSS ${Math.ceil(rssMb)} MB reached ${this.limits.maxRssMb} MB while starting ${kind}`,
|
|
134
123
|
);
|
|
135
|
-
|
|
136
|
-
return error;
|
|
124
|
+
return true;
|
|
137
125
|
}
|
|
138
126
|
if (this.limits.minFreeMemoryMb > 0 && Number.isFinite(freeMb) && freeMb < this.limits.minFreeMemoryMb) {
|
|
139
|
-
|
|
140
|
-
`host free memory ${Math.floor(freeMb)} MB is below ${this.limits.minFreeMemoryMb} MB
|
|
141
|
-
{ kind, freeMb, limitMb: this.limits.minFreeMemoryMb, metric: 'free-memory' },
|
|
127
|
+
requestMemoryPressureSnapshot(
|
|
128
|
+
`diagnostic threshold: host free memory ${Math.floor(freeMb)} MB is below ${this.limits.minFreeMemoryMb} MB while starting ${kind}`,
|
|
142
129
|
);
|
|
143
|
-
|
|
144
|
-
return error;
|
|
130
|
+
return true;
|
|
145
131
|
}
|
|
146
|
-
return
|
|
132
|
+
return false;
|
|
147
133
|
}
|
|
148
134
|
|
|
149
135
|
_canStart(kind) {
|
|
@@ -158,6 +144,12 @@ export class ResourceAdmissionController {
|
|
|
158
144
|
if (parent.counted) {
|
|
159
145
|
parent.counted = false;
|
|
160
146
|
this.active[parent.kind] = Math.max(0, this.active[parent.kind] - 1);
|
|
147
|
+
if (parent.ownerKey) {
|
|
148
|
+
const owners = this.activeByOwner[parent.kind];
|
|
149
|
+
const count = Math.max(0, (owners.get(parent.ownerKey) || 0) - 1);
|
|
150
|
+
if (count > 0) owners.set(parent.ownerKey, count);
|
|
151
|
+
else owners.delete(parent.ownerKey);
|
|
152
|
+
}
|
|
161
153
|
}
|
|
162
154
|
return parent;
|
|
163
155
|
}
|
|
@@ -175,6 +167,8 @@ export class ResourceAdmissionController {
|
|
|
175
167
|
queuedAt: this.now(),
|
|
176
168
|
signal: parent.signal,
|
|
177
169
|
parent,
|
|
170
|
+
ownerKey: parent.ownerKey,
|
|
171
|
+
priority: parent.priority,
|
|
178
172
|
resolve: pending.resolve,
|
|
179
173
|
reject: pending.reject,
|
|
180
174
|
canceled: false,
|
|
@@ -197,8 +191,10 @@ export class ResourceAdmissionController {
|
|
|
197
191
|
return pending.promise;
|
|
198
192
|
}
|
|
199
193
|
|
|
200
|
-
_lease(kind, queuedAt = null, parent = null) {
|
|
194
|
+
_lease(kind, queuedAt = null, parent = null, ownerKey = '', priority = 'user-visible') {
|
|
201
195
|
this.active[kind] += 1;
|
|
196
|
+
const owner = String(ownerKey || '');
|
|
197
|
+
if (owner) this.activeByOwner[kind].set(owner, (this.activeByOwner[kind].get(owner) || 0) + 1);
|
|
202
198
|
const lease = {
|
|
203
199
|
controller: this,
|
|
204
200
|
kind,
|
|
@@ -209,6 +205,8 @@ export class ResourceAdmissionController {
|
|
|
209
205
|
dependencyDepth: 0,
|
|
210
206
|
restorePending: null,
|
|
211
207
|
parent,
|
|
208
|
+
ownerKey: owner,
|
|
209
|
+
priority: normalizePriority(priority),
|
|
212
210
|
releasePromise: null,
|
|
213
211
|
startedAt: this.now(),
|
|
214
212
|
queuedMs: queuedAt == null ? 0 : Math.max(0, this.now() - queuedAt),
|
|
@@ -227,6 +225,11 @@ export class ResourceAdmissionController {
|
|
|
227
225
|
if (lease.counted) {
|
|
228
226
|
lease.counted = false;
|
|
229
227
|
this.active[kind] = Math.max(0, this.active[kind] - 1);
|
|
228
|
+
if (lease.ownerKey) {
|
|
229
|
+
const count = Math.max(0, (this.activeByOwner[kind].get(lease.ownerKey) || 0) - 1);
|
|
230
|
+
if (count > 0) this.activeByOwner[kind].set(lease.ownerKey, count);
|
|
231
|
+
else this.activeByOwner[kind].delete(lease.ownerKey);
|
|
232
|
+
}
|
|
230
233
|
}
|
|
231
234
|
lease.releasePromise = this._resumeParent(lease.parent);
|
|
232
235
|
this._drain();
|
|
@@ -252,29 +255,15 @@ export class ResourceAdmissionController {
|
|
|
252
255
|
return this.context.run(lease, task);
|
|
253
256
|
}
|
|
254
257
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
await sleep(delayMs, options.signal);
|
|
260
|
-
if (options.signal?.aborted) throw abortError(options.signal);
|
|
261
|
-
if (!this._memoryError(lane)) return this.acquire(kind, options);
|
|
262
|
-
}
|
|
263
|
-
throw this._memoryError(lane) || pressure;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
acquire(kind, { signal = null, label = null, dependency = 'scoped' } = {}) {
|
|
258
|
+
acquire(kind, {
|
|
259
|
+
signal = null, label = null, dependency = 'scoped', ownerKey = null,
|
|
260
|
+
priority = 'user-visible',
|
|
261
|
+
} = {}) {
|
|
267
262
|
const lane = kind === 'shell' ? 'shell' : 'agent';
|
|
263
|
+
const owner = ownerKey == null ? '' : String(ownerKey).trim().slice(0, 240);
|
|
264
|
+
const taskPriority = normalizePriority(priority);
|
|
268
265
|
if (signal?.aborted) return Promise.reject(abortError(signal));
|
|
269
|
-
|
|
270
|
-
if (pressure) {
|
|
271
|
-
// Only host free memory self-heals on this timescale; our own RSS does
|
|
272
|
-
// not, so RSS pressure still rejects immediately.
|
|
273
|
-
if (pressure.metric !== 'free-memory' || this.memoryRetry.attempts < 1) {
|
|
274
|
-
return Promise.reject(pressure);
|
|
275
|
-
}
|
|
276
|
-
return this._acquireAfterMemoryDip(kind, { signal, label, dependency }, pressure);
|
|
277
|
-
}
|
|
266
|
+
this._recordMemoryPressure(lane);
|
|
278
267
|
const ambientParent = this.context.getStore();
|
|
279
268
|
const detachedDependency = dependency === 'detached' && ambientParent?.controller === this;
|
|
280
269
|
const parent = detachedDependency ? null : this._suspendParent(ambientParent);
|
|
@@ -287,7 +276,7 @@ export class ResourceAdmissionController {
|
|
|
287
276
|
return Promise.reject(error);
|
|
288
277
|
}
|
|
289
278
|
if (this._canStart(lane) && this.queue.length === 0) {
|
|
290
|
-
const lease = this._lease(lane, null, parent);
|
|
279
|
+
const lease = this._lease(lane, null, parent, owner, taskPriority);
|
|
291
280
|
lease.label = label;
|
|
292
281
|
lease.signal = signal;
|
|
293
282
|
return Promise.resolve(lease);
|
|
@@ -310,6 +299,8 @@ export class ResourceAdmissionController {
|
|
|
310
299
|
canceled: false,
|
|
311
300
|
onAbort: null,
|
|
312
301
|
parent,
|
|
302
|
+
ownerKey: owner,
|
|
303
|
+
priority: taskPriority,
|
|
313
304
|
};
|
|
314
305
|
if (signal) {
|
|
315
306
|
item.onAbort = () => {
|
|
@@ -339,35 +330,64 @@ export class ResourceAdmissionController {
|
|
|
339
330
|
}
|
|
340
331
|
}
|
|
341
332
|
|
|
342
|
-
|
|
343
|
-
|
|
333
|
+
_nextFairIndex() {
|
|
334
|
+
const runnable = [];
|
|
335
|
+
for (let index = 0; index < this.queue.length; index += 1) {
|
|
344
336
|
const item = this.queue[index];
|
|
337
|
+
if (item.canceled || item.signal?.aborted) continue;
|
|
338
|
+
if (item.restore && item.parent.released) continue;
|
|
339
|
+
if (!this._canStart(item.kind)) continue;
|
|
340
|
+
runnable.push({ index, item });
|
|
341
|
+
}
|
|
342
|
+
if (runnable.length === 0) return -1;
|
|
343
|
+
// USER_BLOCKING work (abort/recovery), then USER_VISIBLE tool work, then
|
|
344
|
+
// BEST_EFFORT maintenance. Preserve FIFO between resource kinds inside a
|
|
345
|
+
// priority and round-robin sessions within that lane.
|
|
346
|
+
const bestRank = Math.min(...runnable.map(({ item }) =>
|
|
347
|
+
PRIORITY_RANK[normalizePriority(item.priority)]));
|
|
348
|
+
const prioritized = runnable.filter(({ item }) =>
|
|
349
|
+
PRIORITY_RANK[normalizePriority(item.priority)] === bestRank);
|
|
350
|
+
const lane = prioritized[0].item.kind;
|
|
351
|
+
const laneRows = prioritized.filter((row) => row.item.kind === lane);
|
|
352
|
+
const owners = [];
|
|
353
|
+
for (const { item } of laneRows) {
|
|
354
|
+
const owner = item.ownerKey || '';
|
|
355
|
+
if (!owners.includes(owner)) owners.push(owner);
|
|
356
|
+
}
|
|
357
|
+
let selected = owners[0] || '';
|
|
358
|
+
if (owners.length > 1) {
|
|
359
|
+
const previous = this.fairCursor[lane];
|
|
360
|
+
const previousIndex = owners.indexOf(previous);
|
|
361
|
+
selected = owners[(previousIndex + 1 + owners.length) % owners.length];
|
|
362
|
+
}
|
|
363
|
+
this.fairCursor[lane] = selected;
|
|
364
|
+
return laneRows.find((row) => (row.item.ownerKey || '') === selected)?.index ?? laneRows[0].index;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
_drain() {
|
|
368
|
+
for (;;) {
|
|
369
|
+
// Remove dead/cancelled rows before selecting an owner. Their callbacks
|
|
370
|
+
// may enqueue restoration work, so restart selection after each cleanup.
|
|
371
|
+
let cleaned = false;
|
|
372
|
+
for (let index = 0; index < this.queue.length; index += 1) {
|
|
373
|
+
const item = this.queue[index];
|
|
345
374
|
if (item.restore) {
|
|
346
375
|
if (item.parent.released) {
|
|
347
376
|
this.queue.splice(index, 1);
|
|
348
377
|
this._detach(item);
|
|
349
378
|
item.parent.restorePending = null;
|
|
350
379
|
item.resolve();
|
|
351
|
-
|
|
380
|
+
cleaned = true;
|
|
381
|
+
break;
|
|
352
382
|
}
|
|
353
383
|
if (item.signal?.aborted) {
|
|
354
384
|
this.queue.splice(index, 1);
|
|
355
385
|
this._detach(item);
|
|
356
386
|
item.parent.restorePending = null;
|
|
357
387
|
item.reject(abortError(item.signal));
|
|
358
|
-
|
|
388
|
+
cleaned = true;
|
|
389
|
+
break;
|
|
359
390
|
}
|
|
360
|
-
if (!this._canStart(item.kind)) {
|
|
361
|
-
index += 1;
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
this.queue.splice(index, 1);
|
|
365
|
-
this._detach(item);
|
|
366
|
-
item.parent.restorePending = null;
|
|
367
|
-
item.parent.counted = true;
|
|
368
|
-
this.active[item.kind] += 1;
|
|
369
|
-
item.resolve();
|
|
370
|
-
continue;
|
|
371
391
|
}
|
|
372
392
|
if (item.canceled || item.signal?.aborted) {
|
|
373
393
|
this.queue.splice(index, 1);
|
|
@@ -377,23 +397,37 @@ export class ResourceAdmissionController {
|
|
|
377
397
|
() => item.reject(error),
|
|
378
398
|
(restoreError) => item.reject(restoreError),
|
|
379
399
|
);
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
index += 1;
|
|
384
|
-
continue;
|
|
400
|
+
cleaned = true;
|
|
401
|
+
break;
|
|
402
|
+
}
|
|
385
403
|
}
|
|
386
|
-
|
|
404
|
+
if (cleaned) continue;
|
|
405
|
+
const index = this._nextFairIndex();
|
|
406
|
+
if (index < 0) return;
|
|
407
|
+
const item = this.queue[index];
|
|
408
|
+
this._recordMemoryPressure(item.kind);
|
|
387
409
|
this.queue.splice(index, 1);
|
|
388
410
|
this._detach(item);
|
|
389
|
-
if (
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
)
|
|
411
|
+
if (item.restore) {
|
|
412
|
+
item.parent.restorePending = null;
|
|
413
|
+
item.parent.counted = true;
|
|
414
|
+
this.active[item.kind] += 1;
|
|
415
|
+
if (item.ownerKey) {
|
|
416
|
+
this.activeByOwner[item.kind].set(
|
|
417
|
+
item.ownerKey,
|
|
418
|
+
(this.activeByOwner[item.kind].get(item.ownerKey) || 0) + 1,
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
item.resolve();
|
|
394
422
|
continue;
|
|
395
423
|
}
|
|
396
|
-
const lease = this._lease(
|
|
424
|
+
const lease = this._lease(
|
|
425
|
+
item.kind,
|
|
426
|
+
item.queuedAt,
|
|
427
|
+
item.parent,
|
|
428
|
+
item.ownerKey,
|
|
429
|
+
item.priority,
|
|
430
|
+
);
|
|
397
431
|
lease.label = item.label;
|
|
398
432
|
lease.signal = item.signal;
|
|
399
433
|
item.resolve(lease);
|
|
@@ -402,15 +436,31 @@ export class ResourceAdmissionController {
|
|
|
402
436
|
|
|
403
437
|
snapshot() {
|
|
404
438
|
const now = this.now();
|
|
439
|
+
const wireLimit = (value) => Number.isFinite(value) ? value : null;
|
|
405
440
|
return {
|
|
406
441
|
active: { ...this.active },
|
|
442
|
+
activeOwners: {
|
|
443
|
+
agent: this.activeByOwner.agent.size,
|
|
444
|
+
shell: this.activeByOwner.shell.size,
|
|
445
|
+
},
|
|
407
446
|
queued: this.queue.length,
|
|
408
|
-
limits: {
|
|
447
|
+
limits: {
|
|
448
|
+
...this.limits,
|
|
449
|
+
maxAgents: wireLimit(this.limits.maxAgents),
|
|
450
|
+
maxShells: wireLimit(this.limits.maxShells),
|
|
451
|
+
maxHighLoad: wireLimit(this.limits.maxHighLoad),
|
|
452
|
+
},
|
|
409
453
|
activeLeases: [...this.activeLeases].map((lease) => ({
|
|
410
454
|
kind: lease.kind,
|
|
411
455
|
label: lease.label,
|
|
456
|
+
ownerKey: lease.ownerKey || null,
|
|
457
|
+
priority: lease.priority,
|
|
412
458
|
ageMs: Math.max(0, now - (lease.startedAt || now)),
|
|
413
459
|
})),
|
|
460
|
+
oldestQueuedMs: this.queue.reduce(
|
|
461
|
+
(oldest, item) => Math.max(oldest, Math.max(0, now - (item.queuedAt || now))),
|
|
462
|
+
0,
|
|
463
|
+
),
|
|
414
464
|
};
|
|
415
465
|
}
|
|
416
466
|
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
// Provider adapters also report transport/ack progress so stall watchdogs can
|
|
2
|
+
// distinguish a live connection from a dead one. Those events are not visible
|
|
3
|
+
// model output and must not satisfy user-facing TTFT or launch heavy warmups.
|
|
4
|
+
export function isVisibleStreamProgress(kind) {
|
|
5
|
+
return kind === 'text' || kind === 'reasoning' || kind === 'tool';
|
|
6
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
|
+
|
|
3
|
+
const toolExecutionOwner = new AsyncLocalStorage();
|
|
4
|
+
|
|
5
|
+
export function runWithToolExecutionOwner(ownerKey, run) {
|
|
6
|
+
const owner = String(ownerKey || '').trim().slice(0, 240) || 'anonymous';
|
|
7
|
+
return toolExecutionOwner.run(owner, run);
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function currentToolExecutionOwner() {
|
|
11
|
+
return toolExecutionOwner.getStore() || 'anonymous';
|
|
12
|
+
}
|
|
@@ -24,7 +24,7 @@ function countNonEmptyLines(text) {
|
|
|
24
24
|
|
|
25
25
|
// Zero-result recognizer (audit HIGH): result text that SAYS "nothing found"
|
|
26
26
|
// must summarize as an explicit zero, not be line-counted into "1 match".
|
|
27
|
-
// Matches the shapes emitted by grep/glob/find/list/recall
|
|
27
|
+
// Matches the shapes emitted by grep/glob/find/list/recall providers:
|
|
28
28
|
// "(no matches)", "no matches found", "(no results)", "No results",
|
|
29
29
|
// "(no fuzzy match for \"...\")", "0 matches", "(empty)", "(no entries)".
|
|
30
30
|
function looksLikeZeroResultText(text) {
|
|
@@ -70,6 +70,40 @@ export {
|
|
|
70
70
|
isMemorySurface,
|
|
71
71
|
};
|
|
72
72
|
|
|
73
|
+
function patchOperationProfile(args = {}) {
|
|
74
|
+
const a = parseToolArgs(args);
|
|
75
|
+
const patchText = String(a.patch ?? '');
|
|
76
|
+
const counts = new Map();
|
|
77
|
+
const add = (kind, count = 1) => {
|
|
78
|
+
counts.set(kind, Number(counts.get(kind) || 0) + Math.max(1, Number(count || 1)));
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
for (const line of patchText.split('\n')) {
|
|
82
|
+
const match = /^\*\*\*\s+(Update|Add|Delete) File:\s+.+\s*$/i.exec(line);
|
|
83
|
+
if (!match) continue;
|
|
84
|
+
add(match[1].toLowerCase());
|
|
85
|
+
}
|
|
86
|
+
if (counts.size > 0) return counts;
|
|
87
|
+
|
|
88
|
+
const gitSections = patchText
|
|
89
|
+
.split(/(?=^diff --git )/m)
|
|
90
|
+
.filter((section) => /^diff --git /m.test(section));
|
|
91
|
+
const unifiedSections = gitSections.length > 0
|
|
92
|
+
? gitSections
|
|
93
|
+
: (/^---\s+.+\n\+\+\+\s+.+$/m.test(patchText) ? [patchText] : []);
|
|
94
|
+
for (const section of unifiedSections) {
|
|
95
|
+
if (/^new file mode /m.test(section) || /^---\s+\/dev\/null(?:\s|$)/m.test(section)) add('add');
|
|
96
|
+
else if (/^deleted file mode /m.test(section) || /^\+\+\+\s+\/dev\/null(?:\s|$)/m.test(section)) add('delete');
|
|
97
|
+
else add('update');
|
|
98
|
+
}
|
|
99
|
+
if (counts.size > 0) return counts;
|
|
100
|
+
|
|
101
|
+
if (a.old_string === '') add('add');
|
|
102
|
+
else if (a.new_string === '' && a.old_string != null) add('delete');
|
|
103
|
+
else add('update', patchFileCount(a) || 1);
|
|
104
|
+
return counts;
|
|
105
|
+
}
|
|
106
|
+
|
|
73
107
|
export function displayToolName(name, args = {}) {
|
|
74
108
|
if (isExternalMcpToolName(name)) {
|
|
75
109
|
const mcp = parseMcpToolName(name);
|
|
@@ -84,7 +118,11 @@ export function displayToolName(name, args = {}) {
|
|
|
84
118
|
case 'apply_patch': {
|
|
85
119
|
const parsed = parseToolArgs(args);
|
|
86
120
|
if (parsed && parsed.dry_run === true) return 'Check';
|
|
87
|
-
|
|
121
|
+
const operations = patchOperationProfile(parsed);
|
|
122
|
+
if (operations.size > 1) return 'Change';
|
|
123
|
+
if (operations.has('add')) return 'Create';
|
|
124
|
+
if (operations.has('delete')) return 'Delete';
|
|
125
|
+
return 'Update';
|
|
88
126
|
}
|
|
89
127
|
case 'shell':
|
|
90
128
|
case 'bash':
|
|
@@ -433,6 +471,29 @@ function queryCount(args, ...keys) {
|
|
|
433
471
|
return collectionCount(...keys.map((key) => args?.[key]));
|
|
434
472
|
}
|
|
435
473
|
|
|
474
|
+
function patchMutationUnits(args = {}) {
|
|
475
|
+
const a = parseToolArgs(args);
|
|
476
|
+
if (a.dry_run === true) {
|
|
477
|
+
return [unitDescriptor('Patch', {
|
|
478
|
+
count: patchFileCount(a) || 1,
|
|
479
|
+
active: 'Checking',
|
|
480
|
+
done: 'Checked',
|
|
481
|
+
noun: 'file',
|
|
482
|
+
})];
|
|
483
|
+
}
|
|
484
|
+
const copy = {
|
|
485
|
+
add: { active: 'Creating', done: 'Created' },
|
|
486
|
+
delete: { active: 'Deleting', done: 'Deleted' },
|
|
487
|
+
update: { active: 'Editing', done: 'Edited' },
|
|
488
|
+
};
|
|
489
|
+
return [...patchOperationProfile(a)].map(([kind, count]) => unitDescriptor('Patch', {
|
|
490
|
+
count,
|
|
491
|
+
active: copy[kind]?.active || 'Editing',
|
|
492
|
+
done: copy[kind]?.done || 'Edited',
|
|
493
|
+
noun: 'file',
|
|
494
|
+
}));
|
|
495
|
+
}
|
|
496
|
+
|
|
436
497
|
export function toolWorkUnit(name, args = {}, category = '') {
|
|
437
498
|
const a = parseToolArgs(args);
|
|
438
499
|
const normalized = normalizeToolName(name);
|
|
@@ -452,30 +513,12 @@ export function toolWorkUnit(name, args = {}, category = '') {
|
|
|
452
513
|
case 'read_mcp_resource':
|
|
453
514
|
return unitDescriptor('Read', { count: queryCount(a, 'uri', 'uris') || 1, noun: 'resource' });
|
|
454
515
|
case 'apply_patch': {
|
|
455
|
-
const
|
|
456
|
-
|
|
457
|
-
const deleting = (!creating && a.new_string === '' && a.old_string != null)
|
|
458
|
-
|| /^\*\*\*\s+Delete File:/mi.test(patchText);
|
|
459
|
-
// A dry_run patch validates the diff WITHOUT writing any file, so the
|
|
460
|
-
// header must not claim "Editing/Edited" (which made a pure validation
|
|
461
|
-
// look like a real edit). Surface it as "Checking/Checked" instead.
|
|
462
|
-
if (a.dry_run === true) {
|
|
463
|
-
return unitDescriptor('Patch', {
|
|
464
|
-
count: patchFileCount(a) || 1,
|
|
465
|
-
active: 'Checking',
|
|
466
|
-
done: 'Checked',
|
|
467
|
-
noun: 'file',
|
|
468
|
-
});
|
|
469
|
-
}
|
|
516
|
+
const units = patchMutationUnits(a);
|
|
517
|
+
if (units.length === 1) return units[0];
|
|
470
518
|
return unitDescriptor('Patch', {
|
|
471
|
-
count:
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
// real command, its output and its failure banner (turn.mjs
|
|
475
|
-
// postShellTwins). Repeating "& verifying" in the header declared the
|
|
476
|
-
// verify phase twice and truncated the label on narrow panes.
|
|
477
|
-
active: creating ? 'Creating' : deleting ? 'Deleting' : 'Editing',
|
|
478
|
-
done: creating ? 'Created' : deleting ? 'Deleted' : 'Edited',
|
|
519
|
+
count: units.reduce((total, unit) => total + unit.count, 0),
|
|
520
|
+
active: 'Changing',
|
|
521
|
+
done: 'Changed',
|
|
479
522
|
noun: 'file',
|
|
480
523
|
});
|
|
481
524
|
}
|
|
@@ -606,6 +649,26 @@ export function aggregateToolCategoryEntry(name, args = {}, category = '') {
|
|
|
606
649
|
};
|
|
607
650
|
}
|
|
608
651
|
|
|
652
|
+
export function aggregateToolCategoryEntries(name, args = {}, category = '') {
|
|
653
|
+
const cat = category || classifyToolCategory(name, args);
|
|
654
|
+
const normalized = normalizeToolName(name);
|
|
655
|
+
const units = normalized === 'apply_patch'
|
|
656
|
+
? patchMutationUnits(args)
|
|
657
|
+
: [toolWorkUnit(name, args, cat)];
|
|
658
|
+
return units.map((unit) => {
|
|
659
|
+
const key = [cat, unit.active, unit.done, unit.noun, unit.pluralNoun].join('|');
|
|
660
|
+
return {
|
|
661
|
+
key,
|
|
662
|
+
category: cat,
|
|
663
|
+
active: unit.active,
|
|
664
|
+
done: unit.done,
|
|
665
|
+
noun: unit.noun,
|
|
666
|
+
pluralNoun: unit.pluralNoun,
|
|
667
|
+
count: Math.max(1, Number(unit.count || 1)),
|
|
668
|
+
};
|
|
669
|
+
});
|
|
670
|
+
}
|
|
671
|
+
|
|
609
672
|
/**
|
|
610
673
|
* Rebuild the per-category count map for the DONE state. Counts ATTEMPTS —
|
|
611
674
|
* failed calls included — so the collapsed header total always agrees with
|
|
@@ -618,9 +681,10 @@ export function aggregateDoneCategories(calls = []) {
|
|
|
618
681
|
const map = new Map();
|
|
619
682
|
for (const rec of calls || []) {
|
|
620
683
|
if (!rec) continue;
|
|
621
|
-
const entry
|
|
622
|
-
|
|
623
|
-
|
|
684
|
+
for (const entry of aggregateToolCategoryEntries(rec.name, rec.args, rec.category)) {
|
|
685
|
+
const prev = map.get(entry.key);
|
|
686
|
+
map.set(entry.key, { ...entry, count: Number(prev?.count || 0) + Number(entry.count || 1) });
|
|
687
|
+
}
|
|
624
688
|
}
|
|
625
689
|
return Object.fromEntries(map);
|
|
626
690
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { createOwnerFairGate } from './owner-fair-gate.mjs';
|
|
2
|
+
|
|
3
|
+
function positiveInt(value, fallback) {
|
|
4
|
+
const parsed = Math.floor(Number(value));
|
|
5
|
+
return Number.isFinite(parsed) && parsed >= 1 ? parsed : fallback;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const readIoAdmission = createOwnerFairGate({
|
|
9
|
+
name: 'read I/O',
|
|
10
|
+
activeMax: positiveInt(process.env.MIXDOG_READ_MAX_INFLIGHT, 16),
|
|
11
|
+
queueMax: positiveInt(process.env.MIXDOG_READ_MAX_QUEUE, 2048),
|
|
12
|
+
minOwnerQueue: 16,
|
|
13
|
+
waitTimeoutMs: positiveInt(process.env.MIXDOG_READ_WAIT_TIMEOUT_MS, 30_000),
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
export const codeGraphSourceIoAdmission = createOwnerFairGate({
|
|
17
|
+
name: 'code graph source I/O',
|
|
18
|
+
activeMax: positiveInt(process.env.MIXDOG_CODE_GRAPH_SOURCE_MAX_INFLIGHT, 16),
|
|
19
|
+
queueMax: positiveInt(process.env.MIXDOG_CODE_GRAPH_SOURCE_MAX_QUEUE, 2048),
|
|
20
|
+
minOwnerQueue: 16,
|
|
21
|
+
waitTimeoutMs: positiveInt(process.env.MIXDOG_CODE_GRAPH_SOURCE_WAIT_TIMEOUT_MS, 30_000),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export function toolWorkloadSnapshot() {
|
|
25
|
+
return {
|
|
26
|
+
readIo: readIoAdmission.snapshot(),
|
|
27
|
+
codeGraphSourceIo: codeGraphSourceIoAdmission.snapshot(),
|
|
28
|
+
};
|
|
29
|
+
}
|