mixdog 0.9.151 → 0.9.153
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/browser-import-NOTICE.txt +11 -0
- package/NOTICE.md +11 -0
- package/README.md +108 -68
- package/package.json +17 -4
- package/scripts/computer-task-cost-report.mjs +144 -0
- package/scripts/run-office-live-tests.mjs +32 -0
- package/scripts/tool-stress.mjs +0 -2
- package/src/agents/front-worker/AGENT.md +13 -0
- package/src/agents/front-worker/agent.json +6 -0
- package/src/agents/heavy-worker/AGENT.md +10 -11
- package/src/agents/heavy-worker/agent.json +1 -1
- package/src/agents/maintainer/AGENT.md +0 -2
- package/src/agents/reviewer/AGENT.md +9 -13
- package/src/agents/reviewer/agent.json +1 -1
- package/src/agents/security/AGENT.md +14 -0
- package/src/agents/security/agent.json +6 -0
- package/src/agents/worker/AGENT.md +8 -8
- package/src/agents/worker/agent.json +1 -1
- package/src/agents/writer/AGENT.md +14 -0
- package/src/agents/writer/agent.json +6 -0
- package/src/defaults/skills/setup/SKILL.md +18 -27
- package/src/headless-exec.mjs +3 -10
- package/src/headless-exec.test.mjs +2 -7
- package/src/rules/agent/00-common.md +4 -7
- package/src/rules/lead/01-general.md +0 -3
- package/src/rules/shared/10-tool-workflow.md +10 -2
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +183 -26
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.test.mjs +378 -0
- package/src/runtime/agent/orchestrator/agent-runtime/session-builder.mjs +4 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +109 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.test.mjs +115 -0
- package/src/runtime/agent/orchestrator/config.mjs +13 -68
- package/src/runtime/agent/orchestrator/context/collect-skills.test.mjs +153 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +49 -45
- package/src/runtime/agent/orchestrator/mcp/client.mjs +242 -15
- package/src/runtime/agent/orchestrator/mcp/features.test.mjs +66 -0
- package/src/runtime/agent/orchestrator/mcp/security.test.mjs +36 -20
- package/src/runtime/agent/orchestrator/providers/admission-scheduler-metrics.test.mjs +47 -0
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +16 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +54 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +3 -8
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -3
- package/src/runtime/agent/orchestrator/providers/lib/provider-replay.mjs +50 -0
- package/src/runtime/agent/orchestrator/providers/lib/sse-framing.mjs +93 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +30 -9
- package/src/runtime/agent/orchestrator/providers/media-parity.test.mjs +59 -0
- package/src/runtime/agent/orchestrator/providers/model-list-sanitize.mjs +3 -3
- package/src/runtime/agent/orchestrator/providers/openai-codex-identity.test.mjs +71 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +18 -26
- package/src/runtime/agent/orchestrator/providers/openai-compat-presets.mjs +8 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-stream.mjs +7 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +35 -4
- package/src/runtime/agent/orchestrator/providers/openai-oauth-http-sse.mjs +47 -2
- package/src/runtime/agent/orchestrator/providers/openai-oauth-ws.mjs +103 -12
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/openai-turn-state.mjs +68 -0
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta-parity.test.mjs +122 -6
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +36 -11
- package/src/runtime/agent/orchestrator/providers/openai-ws-pool.mjs +144 -93
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream.mjs +18 -13
- package/src/runtime/agent/orchestrator/providers/provider-replay.test.mjs +288 -0
- package/src/runtime/agent/orchestrator/providers/sse-framing.test.mjs +719 -0
- package/src/runtime/agent/orchestrator/providers/stream-json-pool.mjs +399 -39
- package/src/runtime/agent/orchestrator/providers/stream-json-worker.mjs +26 -0
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +86 -7
- package/src/runtime/agent/orchestrator/session/agent-loop.test.mjs +222 -0
- package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.mjs +61 -0
- package/src/runtime/agent/orchestrator/session/browser-snapshot-supersession.test.mjs +83 -0
- package/src/runtime/agent/orchestrator/session/cache/read-cache.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +26 -2
- package/src/runtime/agent/orchestrator/session/compact-policy.test.mjs +112 -0
- package/src/runtime/agent/orchestrator/session/compaction-read-reset.test.mjs +192 -0
- package/src/runtime/agent/orchestrator/session/context-usage-restart.test.mjs +72 -0
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +100 -15
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +17 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.test.mjs +74 -0
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.mjs +12 -8
- package/src/runtime/agent/orchestrator/session/image-strip-recovery.test.mjs +20 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +110 -2
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +3 -16
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.test.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/loop/recall-fasttrack.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +111 -39
- package/src/runtime/agent/orchestrator/session/manager/ask-session.test.mjs +46 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -6
- package/src/runtime/agent/orchestrator/session/manager/message-sanitize.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +27 -3
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +26 -54
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.test.mjs +63 -1
- package/src/runtime/agent/orchestrator/session/manager/status-telemetry.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +4 -110
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +580 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.test.mjs +593 -0
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +119 -190
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +43 -8
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +129 -12
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +9 -2
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +8 -1
- package/src/runtime/agent/orchestrator/session/store/save-worker.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store-live-publication.test.mjs +83 -0
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +36 -2
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +157 -30
- package/src/runtime/agent/orchestrator/session/store-summary-reader.test.mjs +64 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +57 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.test.mjs +219 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +26 -2
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +22 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool-cwd-env.test.mjs +14 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -10
- package/src/runtime/agent/orchestrator/tools/builtin/find-search-budget.test.mjs +30 -14
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-policy.mjs +15 -4
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +73 -15
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.test.mjs +76 -2
- package/src/runtime/agent/orchestrator/tools/builtin/list-tool.mjs +24 -683
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-client.mjs +64 -72
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-runner.mjs +2 -5
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.mjs +75 -0
- package/src/runtime/agent/orchestrator/tools/builtin/native-search-transport.test.mjs +24 -0
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +6 -6
- package/src/runtime/agent/orchestrator/tools/code-graph/build.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +4 -1
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.mjs +24 -5
- package/src/runtime/agent/orchestrator/tools/code-graph/dispatch.test.mjs +44 -0
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-binary.mjs +12 -2
- package/src/runtime/agent/orchestrator/tools/code-graph/graph-model.mjs +35 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.mjs +28 -13
- package/src/runtime/agent/orchestrator/tools/code-graph/memory-cache.test.mjs +54 -0
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +11 -11
- package/src/runtime/agent/orchestrator/tools/progress-message.mjs +12 -0
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +11 -11
- package/src/runtime/attachments/pdf-extract.mjs +3 -0
- package/src/runtime/attachments/pdfjs-runtime.mjs +13 -0
- package/src/runtime/bridge-clients.test.mjs +827 -0
- package/src/runtime/browser-bridge/action-schema.mjs +251 -0
- package/src/runtime/browser-bridge/client.mjs +152 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +159 -0
- package/src/runtime/computer-bridge/action-schema.mjs +653 -0
- package/src/runtime/computer-bridge/client.mjs +231 -0
- package/src/runtime/computer-bridge/tool-defs.mjs +33 -0
- package/src/runtime/media/store.mjs +18 -8
- package/src/runtime/media/store.test.mjs +14 -0
- package/src/runtime/memory/index.mjs +1 -1
- package/src/runtime/memory/lib/transcript-ingest.mjs +8 -6
- package/src/runtime/memory/lib/transcript-ingest.test.mjs +52 -0
- package/src/runtime/office/assurance-benchmark.mjs +358 -0
- package/src/runtime/office/assurance.mjs +833 -0
- package/src/runtime/office/benchmark.mjs +496 -0
- package/src/runtime/office/capabilities.mjs +551 -0
- package/src/runtime/office/com-adapter.mjs +283 -0
- package/src/runtime/office/content-model.mjs +183 -0
- package/src/runtime/office/contract-benchmark.mjs +105 -0
- package/src/runtime/office/design-library.mjs +1562 -0
- package/src/runtime/office/design-system.mjs +1966 -0
- package/src/runtime/office/index.mjs +2413 -0
- package/src/runtime/office/journal.mjs +82 -0
- package/src/runtime/office/office-assurance.test.mjs +371 -0
- package/src/runtime/office/office-com-host.ps1 +3781 -0
- package/src/runtime/office/office-com-session-host.ps1 +855 -0
- package/src/runtime/office/office-live-runtime.test.mjs +819 -0
- package/src/runtime/office/office-runtime.test.mjs +2070 -0
- package/src/runtime/office/ooxml-validator.mjs +271 -0
- package/src/runtime/office/pagination.mjs +132 -0
- package/src/runtime/office/pdf-adapter.mjs +591 -0
- package/src/runtime/office/pdf-analysis.mjs +372 -0
- package/src/runtime/office/pdf-render.mjs +167 -0
- package/src/runtime/office/pdf-security.mjs +63 -0
- package/src/runtime/office/polish-benchmark.mjs +190 -0
- package/src/runtime/office/portable-ooxml.mjs +1867 -0
- package/src/runtime/office/quality-live-benchmark.mjs +486 -0
- package/src/runtime/office/quality-pipeline.mjs +136 -0
- package/src/runtime/office/tabular.mjs +302 -0
- package/src/runtime/office/templates/mixdog-executive.pptx +0 -0
- package/src/runtime/office/templates/mixdog-executive.pptx.mixdog.json +190 -0
- package/src/runtime/office/tool-defs.mjs +113 -0
- package/src/runtime/office/visual-diff.mjs +89 -0
- package/src/runtime/office/xlsx-assertions.mjs +172 -0
- package/src/runtime/office/xlsx-contract.mjs +147 -0
- package/src/runtime/shared/agent-route-config.mjs +9 -0
- package/src/runtime/shared/child-spawn-gate.mjs +17 -0
- package/src/runtime/shared/child-spawn-remote.mjs +69 -7
- package/src/runtime/shared/child-spawn-remote.test.mjs +124 -0
- package/src/runtime/shared/pristine-execution-contract.json +2 -1
- package/src/runtime/shared/provider-api-key.mjs +1 -0
- package/src/runtime/shared/session-runtime-health.mjs +91 -0
- package/src/runtime/shared/session-runtime-health.test.mjs +53 -0
- package/src/runtime/shared/skill-document.mjs +86 -0
- package/src/runtime/shared/skill-document.test.mjs +81 -0
- package/src/runtime/shared/tool-execution-contract.mjs +16 -0
- package/src/runtime/shared/tool-surface.mjs +27 -0
- package/src/runtime/shared/turn-snapshot-store.mjs +1 -0
- package/src/runtime/shared/turn-snapshot.mjs +32 -9
- package/src/runtime/shared/user-cwd.mjs +33 -14
- package/src/runtime/shared/user-cwd.test.mjs +43 -0
- package/src/session-runtime/agent-disable.test.mjs +20 -1
- package/src/session-runtime/context-status.mjs +9 -3
- package/src/session-runtime/cwd-plugins.mjs +26 -26
- package/src/session-runtime/cwd-plugins.test.mjs +75 -0
- package/src/session-runtime/cwd-tool-routing.test.mjs +5 -0
- package/src/session-runtime/deferred-tool-delta.mjs +104 -0
- package/src/session-runtime/deferred-tool-delta.test.mjs +40 -0
- package/src/session-runtime/global-extensions.mjs +26 -0
- package/src/session-runtime/global-extensions.test.mjs +28 -0
- package/src/session-runtime/global-settings-session-identity.test.mjs +76 -0
- package/src/session-runtime/goal-runtime.mjs +1141 -0
- package/src/session-runtime/goal-runtime.test.mjs +645 -0
- package/src/session-runtime/lifecycle-api.mjs +58 -11
- package/src/session-runtime/lifecycle-api.test.mjs +183 -0
- package/src/session-runtime/mcp-glue.mjs +49 -47
- package/src/session-runtime/plugin-mcp-standard.test.mjs +177 -0
- package/src/session-runtime/plugin-mcp.mjs +96 -22
- package/src/session-runtime/prewarm.mjs +1 -10
- package/src/session-runtime/provider-request-snapshot.mjs +33 -9
- package/src/session-runtime/provider-request-snapshot.test.mjs +27 -5
- package/src/session-runtime/resource-api-global.test.mjs +109 -0
- package/src/session-runtime/resource-api.mjs +168 -116
- package/src/session-runtime/runtime-core.mjs +208 -29
- package/src/session-runtime/runtime-tunables.mjs +6 -8
- package/src/session-runtime/session-lifecycle.mjs +34 -12
- package/src/session-runtime/session-title.mjs +2 -2
- package/src/session-runtime/session-turn-api.mjs +37 -25
- package/src/session-runtime/skills-api.mjs +79 -25
- package/src/session-runtime/skills-api.test.mjs +99 -0
- package/src/session-runtime/tool-catalog-data.mjs +1 -1
- package/src/session-runtime/tool-catalog-schema.mjs +0 -5
- package/src/session-runtime/tool-catalog.mjs +42 -112
- package/src/session-runtime/tool-defs.mjs +6 -1
- package/src/session-runtime/tool-policy-surface.test.mjs +93 -17
- package/src/session-runtime/tool-surface.mjs +30 -8
- package/src/session-runtime/workflow-agents-api.mjs +8 -25
- package/src/session-runtime/workflow.mjs +8 -6
- package/src/standalone/agent-dispatch-broker.mjs +5 -9
- package/src/standalone/agent-tool/helpers.mjs +2 -2
- package/src/standalone/agent-tool/job-views.mjs +3 -1
- package/src/standalone/agent-tool/notify.mjs +11 -9
- package/src/standalone/agent-tool/notify.test.mjs +37 -0
- package/src/standalone/agent-tool/render.mjs +3 -0
- package/src/standalone/agent-tool/spawn-flow.mjs +61 -16
- package/src/standalone/agent-tool/spawn-flow.test.mjs +41 -0
- package/src/standalone/agent-tool/tool-def.mjs +1 -1
- package/src/standalone/agent-tool/worker-index.mjs +123 -0
- package/src/standalone/agent-tool/worker-index.test.mjs +132 -0
- package/src/standalone/agent-tool/worker-rows.mjs +2 -1
- package/src/standalone/agent-tool.mjs +57 -10
- package/src/standalone/daemon.mjs +113 -15
- package/src/standalone/hook-bus/config.mjs +1 -0
- package/src/standalone/plugin-admin.mjs +17 -0
- package/src/standalone/plugin-admin.test.mjs +7 -0
- package/src/standalone/provider-admin.mjs +1 -0
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-runtime-agent-control-client.mjs +133 -0
- package/src/standalone/session-runtime-agent-control-client.test.mjs +71 -0
- package/src/standalone/session-runtime-dispatch-cancel.test.mjs +139 -0
- package/src/standalone/session-runtime-host-factory.mjs +17 -0
- package/src/standalone/session-runtime-host-factory.test.mjs +31 -0
- package/src/standalone/session-runtime-host-health.test.mjs +5 -0
- package/src/standalone/session-runtime-host.mjs +709 -58
- package/src/standalone/session-runtime-inline-host.mjs +258 -0
- package/src/standalone/session-runtime-inline-host.test.mjs +385 -0
- package/src/standalone/session-runtime-provider-cooldown.mjs +78 -0
- package/src/standalone/session-runtime-provider-cooldown.test.mjs +80 -0
- package/src/standalone/session-runtime-shard-host.test.mjs +642 -0
- package/src/standalone/session-runtime-shard-router.mjs +149 -0
- package/src/standalone/session-runtime-shard-router.test.mjs +101 -0
- package/src/standalone/session-runtime-worker.mjs +302 -26
- package/src/standalone/session-service-agent-tree.test.mjs +984 -0
- package/src/standalone/session-service-goal-recovery.test.mjs +177 -0
- package/src/standalone/session-service.mjs +711 -11
- package/src/tui/app/app-view.jsx +1 -0
- package/src/tui/app/core-memory-picker.mjs +2 -0
- package/src/tui/app/extension-pickers.mjs +1 -1
- package/src/tui/app/maintenance-pickers.mjs +3 -3
- package/src/tui/app/model-options.mjs +2 -0
- package/src/tui/app/model-picker.mjs +14 -4
- package/src/tui/app/panel-handoff.test.mjs +311 -0
- package/src/tui/app/project-picker.mjs +1 -0
- package/src/tui/app/route-pickers.mjs +21 -6
- package/src/tui/app/settings-picker.mjs +26 -3
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch.mjs +73 -22
- package/src/tui/app/theme-effort-pickers.mjs +7 -2
- package/src/tui/components/Picker.jsx +10 -1
- package/src/tui/dist/index.mjs +187 -45
- package/src/tui/lib/voice-setup.mjs +4 -1
- package/src/tui/session/agent-message-source.test.mjs +37 -0
- package/src/tui/session/completion-card-restore.test.mjs +50 -0
- package/src/tui/session/context-state.mjs +3 -13
- package/src/tui/session/goal-continuation.mjs +141 -0
- package/src/tui/session/goal-continuation.test.mjs +203 -0
- package/src/tui/session/queue-helpers.mjs +5 -2
- package/src/tui/session/session-action-surface.test.mjs +190 -0
- package/src/tui/session/session-api-ext.mjs +48 -8
- package/src/tui/session/session-api.mjs +73 -1
- package/src/tui/session/session-flow.mjs +31 -1
- package/src/tui/session/turn.mjs +88 -15
- package/src/tui/session-local.mjs +39 -3
- package/src/vendor/statusline/src/gateway/route-meta.mjs +31 -0
- package/src/runtime/agent/orchestrator/tools/builtin/fuzzy-match.mjs +0 -301
|
@@ -59,7 +59,11 @@ import { createChannelSessionRouter } from './channel-session-router.mjs';
|
|
|
59
59
|
import { createSessionTransport } from './session-transport.mjs';
|
|
60
60
|
import { createSessionService } from './session-service.mjs';
|
|
61
61
|
import { createSessionProtocolClient } from './session-protocol.mjs';
|
|
62
|
-
import {
|
|
62
|
+
import {
|
|
63
|
+
listStoredActiveGoalSessionIds,
|
|
64
|
+
readStoredGoalSnapshot,
|
|
65
|
+
} from '../session-runtime/goal-runtime.mjs';
|
|
66
|
+
import { createDaemonSessionRuntimeHost } from './session-runtime-host-factory.mjs';
|
|
63
67
|
import { getStandaloneMemoryRuntime } from './memory-runtime-proxy.mjs';
|
|
64
68
|
import {
|
|
65
69
|
compareRuntimeVersions,
|
|
@@ -244,10 +248,10 @@ const eventLoopDelay = monitorEventLoopDelay({ resolution: 20 });
|
|
|
244
248
|
eventLoopDelay.enable();
|
|
245
249
|
let eventLoopLagTimer = null;
|
|
246
250
|
|
|
247
|
-
function
|
|
248
|
-
//
|
|
249
|
-
//
|
|
250
|
-
//
|
|
251
|
+
function registerMemoryRuntimeLazy() {
|
|
252
|
+
// Register the shared proxy immediately so daemon shutdown owns its
|
|
253
|
+
// lifecycle, but keep the isolated process off the startup path. A real
|
|
254
|
+
// memory call or the post-connect idle warmup starts the exact singleton.
|
|
251
255
|
if (process.env.MIXDOG_DAEMON_SKIP_MEMORY === '1') {
|
|
252
256
|
log('memory runtime skipped (MIXDOG_DAEMON_SKIP_MEMORY=1)');
|
|
253
257
|
return;
|
|
@@ -259,9 +263,7 @@ function startMemoryRuntimeEarly() {
|
|
|
259
263
|
dataDir: DATA_DIR,
|
|
260
264
|
cwd: CWD,
|
|
261
265
|
});
|
|
262
|
-
|
|
263
|
-
.then(() => log('memory runtime ready in isolated process'))
|
|
264
|
-
.catch((e) => log(`memory.start failed (non-fatal): ${e?.message || e}`));
|
|
266
|
+
log('memory runtime registered for lazy start');
|
|
265
267
|
} catch (e) { log(`memory.start setup failed (non-fatal): ${e?.message || e}`); }
|
|
266
268
|
}
|
|
267
269
|
|
|
@@ -414,11 +416,10 @@ async function main() {
|
|
|
414
416
|
process.exit(0);
|
|
415
417
|
}
|
|
416
418
|
process.on('exit', () => { try { releaseSingletonOwner(OWNER_PATH, process.pid); } catch {} });
|
|
417
|
-
|
|
419
|
+
registerMemoryRuntimeLazy();
|
|
418
420
|
agentDispatchBroker = createAgentDispatchBroker({
|
|
419
|
-
// Memory-cycle agents
|
|
420
|
-
//
|
|
421
|
-
// them, so its commit stays flat across cycles.
|
|
421
|
+
// Memory-cycle agents use the same lazy provider/orchestrator graph as
|
|
422
|
+
// session actors; it stays unloaded until a real cycle requests it.
|
|
422
423
|
dispatchAgent: (payload, options) => {
|
|
423
424
|
if (!sessionRuntimeHost) throw new Error('session runtime host is not ready');
|
|
424
425
|
return sessionRuntimeHost.agentDispatch(payload, options);
|
|
@@ -580,10 +581,64 @@ async function main() {
|
|
|
580
581
|
};
|
|
581
582
|
let sessionRuntimePrewarmStarted = false;
|
|
582
583
|
let sessionRuntimePrewarmPromise = null;
|
|
583
|
-
|
|
584
|
+
let canonicalAgentToolPromise = null;
|
|
585
|
+
function canonicalAgentTool() {
|
|
586
|
+
if (!sessionService) {
|
|
587
|
+
return Promise.reject(new Error('canonical session service is not ready'));
|
|
588
|
+
}
|
|
589
|
+
canonicalAgentToolPromise ??= Promise.all([
|
|
590
|
+
import('./agent-tool.mjs'),
|
|
591
|
+
import('../runtime/agent/orchestrator/config.mjs'),
|
|
592
|
+
import('../runtime/agent/orchestrator/providers/registry.mjs'),
|
|
593
|
+
]).then(([agentModule, cfgMod, reg]) => agentModule.createStandaloneAgent({
|
|
594
|
+
cfgMod,
|
|
595
|
+
reg,
|
|
596
|
+
mgr: sessionService.agentManager,
|
|
597
|
+
dataDir: cfgMod.getPluginData(),
|
|
598
|
+
cwd: CWD,
|
|
599
|
+
awaitKeychainPrewarm: async () => {},
|
|
600
|
+
isKeychainPrewarmReady: () => true,
|
|
601
|
+
sessionSurface: sessionService.agentSurface,
|
|
602
|
+
notifySessionCompletion(ownerSessionId, text, meta = {}) {
|
|
603
|
+
return sessionRuntimeHost?.notifySessionCompletion?.(ownerSessionId, text, meta) === true;
|
|
604
|
+
},
|
|
605
|
+
})).catch((error) => {
|
|
606
|
+
canonicalAgentToolPromise = null;
|
|
607
|
+
throw error;
|
|
608
|
+
});
|
|
609
|
+
return canonicalAgentToolPromise;
|
|
610
|
+
}
|
|
611
|
+
async function executeCanonicalAgentControl(args = {}, context = {}) {
|
|
612
|
+
const tool = await canonicalAgentTool();
|
|
613
|
+
const parentSessionId = String(context?.callerSessionId || '').trim();
|
|
614
|
+
await sessionService.rehydrateAgentSessions();
|
|
615
|
+
const scopedContext = {
|
|
616
|
+
...context,
|
|
617
|
+
callerSessionId: parentSessionId || null,
|
|
618
|
+
ownerSessionId: sessionService.rootOwnerSessionId(parentSessionId),
|
|
619
|
+
};
|
|
620
|
+
if (String(args?.type || '') === '__close_all') {
|
|
621
|
+
await tool.closeAll?.(
|
|
622
|
+
String(args?.reason || 'agent owner closed'),
|
|
623
|
+
{ callerSessionId: parentSessionId || null },
|
|
624
|
+
);
|
|
625
|
+
await sessionService.cancelAgentDescendants(
|
|
626
|
+
parentSessionId,
|
|
627
|
+
String(args?.reason || 'agent owner closed'),
|
|
628
|
+
);
|
|
629
|
+
return 'agent close all: ok';
|
|
630
|
+
}
|
|
631
|
+
return await tool.execute(args, scopedContext);
|
|
632
|
+
}
|
|
633
|
+
sessionRuntimeHost = createDaemonSessionRuntimeHost({
|
|
584
634
|
cwd: CWD,
|
|
585
635
|
log,
|
|
636
|
+
executeAgentControl: executeCanonicalAgentControl,
|
|
586
637
|
});
|
|
638
|
+
// Codex-style runtime: daemon routing and independent async session actors
|
|
639
|
+
// share one V8 isolate and module graph. CPU-heavy work stays in bounded
|
|
640
|
+
// native helpers/worker pools instead of duplicating the whole runtime.
|
|
641
|
+
log(`session runtime mode=${sessionRuntimeHost.status.mode}`);
|
|
587
642
|
function prewarmSessionRuntime() {
|
|
588
643
|
if (sessionRuntimePrewarmPromise) return sessionRuntimePrewarmPromise;
|
|
589
644
|
sessionRuntimePrewarmStarted = true;
|
|
@@ -592,7 +647,7 @@ async function main() {
|
|
|
592
647
|
.catch((error) => log(`image pipeline prewarm failed (non-fatal): ${error?.message || error}`));
|
|
593
648
|
sessionRuntimePrewarmPromise = sessionRuntimeHost.prewarm()
|
|
594
649
|
.then(() => {
|
|
595
|
-
log('session runtime
|
|
650
|
+
log('session runtime/agent-loop/keychain prewarm ready');
|
|
596
651
|
return true;
|
|
597
652
|
})
|
|
598
653
|
.catch((error) => {
|
|
@@ -601,7 +656,7 @@ async function main() {
|
|
|
601
656
|
log(`session runtime/keychain prewarm failed (non-fatal): ${error?.message || error}`);
|
|
602
657
|
return false;
|
|
603
658
|
});
|
|
604
|
-
|
|
659
|
+
return sessionRuntimePrewarmPromise;
|
|
605
660
|
}
|
|
606
661
|
sessionService = createSessionService({
|
|
607
662
|
createSessionRuntime: (options) => sessionRuntimeHost.create(options),
|
|
@@ -617,7 +672,31 @@ async function main() {
|
|
|
617
672
|
if (typeof store.readStoredSessionTranscript !== 'function') return null;
|
|
618
673
|
return await store.readStoredSessionTranscript(sessionId, options) ?? null;
|
|
619
674
|
},
|
|
675
|
+
readStoredGoal: async (sessionId) => readStoredGoalSnapshot({
|
|
676
|
+
dataDir: DATA_DIR,
|
|
677
|
+
sessionId,
|
|
678
|
+
}),
|
|
679
|
+
listStoredActiveGoalSessionIds: async () => listStoredActiveGoalSessionIds({
|
|
680
|
+
dataDir: DATA_DIR,
|
|
681
|
+
}),
|
|
620
682
|
listSessions: async (options = {}) => {
|
|
683
|
+
if (options.includeAgentOnly === true) {
|
|
684
|
+
// Agent discovery is metadata-only. Exact session reads/subscriptions
|
|
685
|
+
// keep using the canonical session id after the user opens a worker.
|
|
686
|
+
const store = await import('../runtime/agent/orchestrator/session/store.mjs');
|
|
687
|
+
const summaries = store.listStoredSessionSummaries({
|
|
688
|
+
refreshFromStorage: options.refreshFromStorage === true,
|
|
689
|
+
});
|
|
690
|
+
const viewStore = await desktopRuntime.loadSessionStore();
|
|
691
|
+
const links = viewStore.listStoredAgentWorkerLinks?.() || [];
|
|
692
|
+
if (!links.length) return summaries;
|
|
693
|
+
const linksById = new Map(links.map((link) => [link.sessionId, link]));
|
|
694
|
+
return summaries.map((row) => {
|
|
695
|
+
if (row?.parentSessionId) return row;
|
|
696
|
+
const link = linksById.get(row?.id);
|
|
697
|
+
return link ? { ...row, ...link, id: row.id } : row;
|
|
698
|
+
});
|
|
699
|
+
}
|
|
621
700
|
const store = await desktopRuntime.loadSessionStore();
|
|
622
701
|
return store.listStoredSessionSummaries({
|
|
623
702
|
refreshFromStorage: options.refreshFromStorage === true,
|
|
@@ -715,11 +794,30 @@ async function main() {
|
|
|
715
794
|
safeIpcSend(process, { type: 'ready', port, token });
|
|
716
795
|
}
|
|
717
796
|
log(`ready port=${port} pid=${process.pid} in ${(performance.now() - startedAt).toFixed(0)}ms`);
|
|
797
|
+
void prewarmSessionRuntime()
|
|
798
|
+
.then(() => sessionService.recoverActiveGoals())
|
|
799
|
+
.then(({ found, resumed, skipped, failed }) => {
|
|
800
|
+
log(`active Goal recovery found=${found} resumed=${resumed} skipped=${skipped} failed=${failed}`);
|
|
801
|
+
})
|
|
802
|
+
.catch((error) => {
|
|
803
|
+
log(`active Goal recovery failed: ${error?.message || error}`);
|
|
804
|
+
});
|
|
718
805
|
eventLoopLagTimer = setInterval(() => {
|
|
719
806
|
const status = eventLoopStatus();
|
|
720
807
|
if (status.eventLoopP99Ms >= 250) {
|
|
721
808
|
log(`event-loop lag p95=${status.eventLoopP95Ms}ms p99=${status.eventLoopP99Ms}ms max=${status.eventLoopMaxMs}ms`);
|
|
722
809
|
}
|
|
810
|
+
// Legacy external runtime hosts may still report shard-local lag. The
|
|
811
|
+
// production in-process host is already covered by daemon loop telemetry.
|
|
812
|
+
const shards = sessionRuntimeHost?.status?.shards || [];
|
|
813
|
+
const lagging = shards.filter((shard) => Number(shard?.lag?.p99Ms) >= 250 || shard?.degraded);
|
|
814
|
+
if (lagging.length > 0) {
|
|
815
|
+
log(`session runtime shard lag ${lagging.map((shard) => (
|
|
816
|
+
`#${shard.index}${shard.degraded ? '(quarantined)' : ''}`
|
|
817
|
+
+ ` p95=${shard.lag?.p95Ms ?? -1}ms p99=${shard.lag?.p99Ms ?? -1}ms`
|
|
818
|
+
+ ` max=${shard.lag?.maxMs ?? -1}ms runtimes=${shard.runtimes}`
|
|
819
|
+
)).join(' ')}`);
|
|
820
|
+
}
|
|
723
821
|
eventLoopDelay.reset();
|
|
724
822
|
}, 30_000);
|
|
725
823
|
eventLoopLagTimer.unref?.();
|
|
@@ -81,6 +81,7 @@ function pluginHookConfigEntries(dataDir) {
|
|
|
81
81
|
}
|
|
82
82
|
const entries = [];
|
|
83
83
|
for (const plugin of plugins || []) {
|
|
84
|
+
if (plugin?.enabled === false) continue;
|
|
84
85
|
const root = String(plugin?.root || '').trim();
|
|
85
86
|
if (!root || !existsSync(root)) continue;
|
|
86
87
|
const id = cleanHookId(plugin.id || plugin.name || plugin.title || root.split(/[\\/]/).pop());
|
|
@@ -242,6 +242,7 @@ function enrichEntry(entry) {
|
|
|
242
242
|
const name = clean(manifest.name) || clean(manifest.id) || clean(entry.name) || displayNameFromUrl(entry.source);
|
|
243
243
|
return {
|
|
244
244
|
...entry,
|
|
245
|
+
enabled: entry.enabled !== false,
|
|
245
246
|
name,
|
|
246
247
|
title: clean(manifest.title) || clean(manifest.displayName) || clean(entry.title) || name,
|
|
247
248
|
version: clean(manifest.version) || clean(entry.version) || null,
|
|
@@ -280,6 +281,7 @@ export function addPlugin(sourceInput, { dataDir = resolvePluginData(), name } =
|
|
|
280
281
|
title: clean(manifest.title) || clean(manifest.displayName) || clean(name) || displayNameFromUrl(normalized.displaySource),
|
|
281
282
|
version: clean(manifest.version) || null,
|
|
282
283
|
description: clean(manifest.description),
|
|
284
|
+
enabled: true,
|
|
283
285
|
installedAt: nowIso(),
|
|
284
286
|
updatedAt: nowIso(),
|
|
285
287
|
};
|
|
@@ -338,6 +340,21 @@ export function updatePlugin(idOrName, { dataDir = resolvePluginData() } = {}) {
|
|
|
338
340
|
});
|
|
339
341
|
}
|
|
340
342
|
|
|
343
|
+
export function setPluginEnabled(idOrName, enabled, { dataDir = resolvePluginData() } = {}) {
|
|
344
|
+
const key = clean(idOrName);
|
|
345
|
+
if (!key) throw new Error('plugin id/name is required');
|
|
346
|
+
return mutateRegistry(dataDir, (registry) => {
|
|
347
|
+
const index = pluginIndex(registry, key);
|
|
348
|
+
if (index < 0) throw new Error(`plugin not registered: ${key}`);
|
|
349
|
+
const next = {
|
|
350
|
+
...registry.plugins[index],
|
|
351
|
+
enabled: enabled !== false,
|
|
352
|
+
};
|
|
353
|
+
registry.plugins[index] = next;
|
|
354
|
+
return enrichEntry(next);
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
|
|
341
358
|
export function removePlugin(idOrName, { dataDir = resolvePluginData() } = {}) {
|
|
342
359
|
const key = clean(idOrName);
|
|
343
360
|
if (!key) throw new Error('plugin id/name is required');
|
|
@@ -18,7 +18,9 @@ import {
|
|
|
18
18
|
_publishManagedPluginRoot,
|
|
19
19
|
addPlugin,
|
|
20
20
|
listRegisteredPlugins,
|
|
21
|
+
setPluginEnabled,
|
|
21
22
|
} from './plugin-admin.mjs';
|
|
23
|
+
import { hookConfigEntries } from './hook-bus/config.mjs';
|
|
22
24
|
|
|
23
25
|
test('plugin registry persists atomically with owner-only permissions', () => {
|
|
24
26
|
const root = mkdtempSync(join(tmpdir(), 'mixdog-plugin-registry-'));
|
|
@@ -33,6 +35,11 @@ test('plugin registry persists atomically with owner-only permissions', () => {
|
|
|
33
35
|
const registry = JSON.parse(readFileSync(registryPath, 'utf8'));
|
|
34
36
|
assert.equal(registry.plugins.length, 1);
|
|
35
37
|
assert.equal(listRegisteredPlugins({ dataDir })[0].name, 'local-test');
|
|
38
|
+
assert.equal(listRegisteredPlugins({ dataDir })[0].enabled, true);
|
|
39
|
+
assert.equal(hookConfigEntries(dataDir, root).some((entry) => entry.sourceType === 'plugin'), true);
|
|
40
|
+
setPluginEnabled('local-test', false, { dataDir });
|
|
41
|
+
assert.equal(listRegisteredPlugins({ dataDir })[0].enabled, false);
|
|
42
|
+
assert.equal(hookConfigEntries(dataDir, root).some((entry) => entry.sourceType === 'plugin'), false);
|
|
36
43
|
if (process.platform !== 'win32') {
|
|
37
44
|
assert.equal(statSync(registryPath).mode & 0o777, 0o600);
|
|
38
45
|
}
|
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
|
|
45
45
|
const API_PROVIDERS = Object.freeze([
|
|
46
46
|
Object.freeze({ id: 'opencode-go', name: 'OpenCode Go API', env: 'OPENCODE_API_KEY', url: 'https://opencode.ai' }),
|
|
47
|
+
Object.freeze({ id: 'openrouter', name: 'OpenRouter', env: 'OPENROUTER_API_KEY', url: 'https://openrouter.ai/keys' }),
|
|
47
48
|
Object.freeze({ id: 'openai', name: 'OpenAI API', env: 'OPENAI_API_KEY', url: 'https://platform.openai.com/api-keys' }),
|
|
48
49
|
Object.freeze({ id: 'anthropic', name: 'Anthropic API', env: 'ANTHROPIC_API_KEY', url: 'https://console.anthropic.com/settings/keys' }),
|
|
49
50
|
Object.freeze({ id: 'gemini', name: 'Gemini API', env: 'GEMINI_API_KEY', url: 'https://aistudio.google.com/apikey' }),
|
|
@@ -15,6 +15,7 @@ export const SESSION_READ_ACTIONS = Object.freeze([
|
|
|
15
15
|
'getCompactionSettings',
|
|
16
16
|
'getDisabledSkills',
|
|
17
17
|
'getMediaJob',
|
|
18
|
+
'getMcpServerConfig',
|
|
18
19
|
'getOnboardingStatus',
|
|
19
20
|
'getOAuthProviderLoginStatus',
|
|
20
21
|
'getOutputStyle',
|
|
@@ -79,6 +80,7 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
|
|
|
79
80
|
'deleteWorkflow',
|
|
80
81
|
'enablePluginMcp',
|
|
81
82
|
'forgetProviderAuth',
|
|
83
|
+
'goalControl',
|
|
82
84
|
'inheritFrom',
|
|
83
85
|
'loginOAuthProvider',
|
|
84
86
|
'loginOpenCodeGoUsage',
|
|
@@ -107,10 +109,12 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
|
|
|
107
109
|
'runScheduleNow',
|
|
108
110
|
'runUpdateNow',
|
|
109
111
|
'saveAgentDefinition',
|
|
112
|
+
'saveMcpServer',
|
|
110
113
|
'saveOpenAIUsageSessionKey',
|
|
111
114
|
'saveOpenCodeGoUsageAuth',
|
|
112
115
|
'saveProviderApiKey',
|
|
113
116
|
'saveSchedule',
|
|
117
|
+
'saveSkill',
|
|
114
118
|
'saveWebhook',
|
|
115
119
|
'saveWorkflowPack',
|
|
116
120
|
'selectTools',
|
|
@@ -129,6 +133,7 @@ export const SESSION_CONFIGURE_ACTIONS = Object.freeze([
|
|
|
129
133
|
'setMemoryToolsEnabled',
|
|
130
134
|
'setModel',
|
|
131
135
|
'setOutputStyle',
|
|
136
|
+
'setPluginEnabled',
|
|
132
137
|
'setProfile',
|
|
133
138
|
'setProgressHint',
|
|
134
139
|
'setRecapEnabled',
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Runtime-shard client for distributed Agent control.
|
|
4
|
+
//
|
|
5
|
+
// A Lead runtime and every Subagent it creates used to share one worker
|
|
6
|
+
// process even after top-level session sharding. Agent control now crosses the
|
|
7
|
+
// existing shard IPC link: the daemon chooses a target shard, while the target
|
|
8
|
+
// keeps the ordinary persistent Agent tool/tag/session implementation.
|
|
9
|
+
import { safeIpcSend } from '../runtime/shared/safe-ipc-send.mjs';
|
|
10
|
+
import { isSessionRuntimeWorkerProcess } from '../runtime/shared/child-spawn-remote.mjs';
|
|
11
|
+
|
|
12
|
+
const pending = new Map();
|
|
13
|
+
let sequence = 0;
|
|
14
|
+
let listening = false;
|
|
15
|
+
|
|
16
|
+
function remoteError(body) {
|
|
17
|
+
const error = new Error(String(body?.message || 'remote agent control failed'));
|
|
18
|
+
if (body?.name) error.name = String(body.name);
|
|
19
|
+
if (body?.stack) error.stack = String(body.stack);
|
|
20
|
+
if (body?.code) error.code = String(body.code);
|
|
21
|
+
return error;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function ensureListener() {
|
|
25
|
+
if (listening) return;
|
|
26
|
+
listening = true;
|
|
27
|
+
process.on('message', (message) => {
|
|
28
|
+
if (!message || message.type !== 'agent-control-result') return;
|
|
29
|
+
const controlId = String(message.controlId || '');
|
|
30
|
+
const request = pending.get(controlId);
|
|
31
|
+
if (!request) return;
|
|
32
|
+
pending.delete(controlId);
|
|
33
|
+
request.cleanup();
|
|
34
|
+
if (message.ok === false) request.reject(remoteError(message.error));
|
|
35
|
+
else request.resolve(message.value);
|
|
36
|
+
});
|
|
37
|
+
process.on('disconnect', () => {
|
|
38
|
+
for (const [controlId, request] of pending) {
|
|
39
|
+
pending.delete(controlId);
|
|
40
|
+
request.cleanup();
|
|
41
|
+
request.reject(new Error('runtime shard disconnected during agent control'));
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function remoteAgentControlEnabled(env = process.env) {
|
|
47
|
+
return isSessionRuntimeWorkerProcess(env)
|
|
48
|
+
&& typeof process.send === 'function'
|
|
49
|
+
&& process.connected === true;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function executeRemoteAgentControl(args = {}, context = {}) {
|
|
53
|
+
if (!remoteAgentControlEnabled()) {
|
|
54
|
+
return Promise.reject(new Error('remote agent control is unavailable'));
|
|
55
|
+
}
|
|
56
|
+
ensureListener();
|
|
57
|
+
const controlId = `agent-control-${process.pid}-${++sequence}`;
|
|
58
|
+
const signal = context?.signal || null;
|
|
59
|
+
if (signal?.aborted) {
|
|
60
|
+
return Promise.reject(signal.reason instanceof Error
|
|
61
|
+
? signal.reason
|
|
62
|
+
: new Error(String(signal.reason || 'agent control canceled')));
|
|
63
|
+
}
|
|
64
|
+
return new Promise((resolve, reject) => {
|
|
65
|
+
let timer = null;
|
|
66
|
+
let onAbort = null;
|
|
67
|
+
const cleanup = () => {
|
|
68
|
+
if (timer) clearTimeout(timer);
|
|
69
|
+
timer = null;
|
|
70
|
+
if (onAbort && signal) {
|
|
71
|
+
try { signal.removeEventListener('abort', onAbort); } catch {}
|
|
72
|
+
}
|
|
73
|
+
onAbort = null;
|
|
74
|
+
};
|
|
75
|
+
pending.set(controlId, { resolve, reject, cleanup });
|
|
76
|
+
if (signal) {
|
|
77
|
+
onAbort = () => {
|
|
78
|
+
safeIpcSend(process, {
|
|
79
|
+
type: 'agent-control-cancel',
|
|
80
|
+
controlId,
|
|
81
|
+
reason: String(signal.reason?.message || signal.reason || 'agent control canceled'),
|
|
82
|
+
}, { onError: () => {} });
|
|
83
|
+
const request = pending.get(controlId);
|
|
84
|
+
if (!request) return;
|
|
85
|
+
pending.delete(controlId);
|
|
86
|
+
cleanup();
|
|
87
|
+
reject(signal.reason instanceof Error
|
|
88
|
+
? signal.reason
|
|
89
|
+
: new Error(String(signal.reason || 'agent control canceled')));
|
|
90
|
+
};
|
|
91
|
+
signal.addEventListener('abort', onAbort, { once: true });
|
|
92
|
+
}
|
|
93
|
+
timer = setTimeout(() => {
|
|
94
|
+
const request = pending.get(controlId);
|
|
95
|
+
if (!request) return;
|
|
96
|
+
pending.delete(controlId);
|
|
97
|
+
cleanup();
|
|
98
|
+
reject(new Error('remote agent control timed out'));
|
|
99
|
+
}, 180_000);
|
|
100
|
+
timer.unref?.();
|
|
101
|
+
const sent = safeIpcSend(process, {
|
|
102
|
+
type: 'agent-control',
|
|
103
|
+
controlId,
|
|
104
|
+
args: args && typeof args === 'object' ? args : {},
|
|
105
|
+
context: {
|
|
106
|
+
callerCwd: typeof context?.callerCwd === 'string' ? context.callerCwd : null,
|
|
107
|
+
invocationSource: typeof context?.invocationSource === 'string'
|
|
108
|
+
? context.invocationSource
|
|
109
|
+
: null,
|
|
110
|
+
callerSessionId: typeof context?.callerSessionId === 'string'
|
|
111
|
+
? context.callerSessionId
|
|
112
|
+
: null,
|
|
113
|
+
clientHostPid: Number(context?.clientHostPid) || null,
|
|
114
|
+
},
|
|
115
|
+
}, {
|
|
116
|
+
onError: (error) => {
|
|
117
|
+
const request = pending.get(controlId);
|
|
118
|
+
if (!request) return;
|
|
119
|
+
pending.delete(controlId);
|
|
120
|
+
cleanup();
|
|
121
|
+
reject(error);
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
if (!sent) {
|
|
125
|
+
const request = pending.get(controlId);
|
|
126
|
+
if (request) {
|
|
127
|
+
pending.delete(controlId);
|
|
128
|
+
cleanup();
|
|
129
|
+
reject(new Error('runtime shard IPC is unavailable'));
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
});
|
|
133
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { fork } from 'node:child_process';
|
|
3
|
+
import { mkdtemp, rm, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import test from 'node:test';
|
|
7
|
+
|
|
8
|
+
const CLIENT_URL = new URL('./session-runtime-agent-control-client.mjs', import.meta.url).href;
|
|
9
|
+
|
|
10
|
+
test('runtime shard Agent client sends serializable context and receives the routed result', async () => {
|
|
11
|
+
const dir = await mkdtemp(join(tmpdir(), 'mixdog-agent-control-client-'));
|
|
12
|
+
const entry = join(dir, 'client.mjs');
|
|
13
|
+
await writeFile(entry, `
|
|
14
|
+
process.env.MIXDOG_SESSION_RUNTIME_WORKER_PID = String(process.pid);
|
|
15
|
+
const client = await import(${JSON.stringify(CLIENT_URL)});
|
|
16
|
+
const value = await client.executeRemoteAgentControl(
|
|
17
|
+
{ type: 'spawn', tag: 'routed', agent: 'worker', prompt: 'run' },
|
|
18
|
+
{
|
|
19
|
+
callerCwd: 'C:/Project/test',
|
|
20
|
+
invocationSource: 'model-tool',
|
|
21
|
+
callerSessionId: 'sess_lead',
|
|
22
|
+
clientHostPid: 4242,
|
|
23
|
+
},
|
|
24
|
+
);
|
|
25
|
+
process.send({ type: 'report', value });
|
|
26
|
+
`, 'utf8');
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
const observed = await new Promise((resolve, reject) => {
|
|
30
|
+
const child = fork(entry, [], {
|
|
31
|
+
stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
|
|
32
|
+
env: { ...process.env },
|
|
33
|
+
});
|
|
34
|
+
const timer = setTimeout(() => {
|
|
35
|
+
try { child.kill(); } catch {}
|
|
36
|
+
reject(new Error('agent control client test timed out'));
|
|
37
|
+
}, 10_000);
|
|
38
|
+
child.on('message', (message) => {
|
|
39
|
+
if (message?.type === 'agent-control') {
|
|
40
|
+
assert.deepEqual(message.args, {
|
|
41
|
+
type: 'spawn',
|
|
42
|
+
tag: 'routed',
|
|
43
|
+
agent: 'worker',
|
|
44
|
+
prompt: 'run',
|
|
45
|
+
});
|
|
46
|
+
assert.deepEqual(message.context, {
|
|
47
|
+
callerCwd: 'C:/Project/test',
|
|
48
|
+
invocationSource: 'model-tool',
|
|
49
|
+
callerSessionId: 'sess_lead',
|
|
50
|
+
clientHostPid: 4242,
|
|
51
|
+
});
|
|
52
|
+
child.send({
|
|
53
|
+
type: 'agent-control-result',
|
|
54
|
+
controlId: message.controlId,
|
|
55
|
+
ok: true,
|
|
56
|
+
value: 'agent task: task_routed',
|
|
57
|
+
});
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
if (message?.type !== 'report') return;
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
try { child.kill(); } catch {}
|
|
63
|
+
resolve(message.value);
|
|
64
|
+
});
|
|
65
|
+
child.on('error', reject);
|
|
66
|
+
});
|
|
67
|
+
assert.equal(observed, 'agent task: task_routed');
|
|
68
|
+
} finally {
|
|
69
|
+
await rm(dir, { recursive: true, force: true });
|
|
70
|
+
}
|
|
71
|
+
});
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
// Cold-start cancellation contract, exercised against the REAL runtime worker
|
|
2
|
+
// child (no stub): a cancel that arrives before or during the dispatch's
|
|
3
|
+
// module/provider cold start must never be answered "not running" and then let
|
|
4
|
+
// the dispatch run anyway.
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { fork } from 'node:child_process';
|
|
7
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
8
|
+
import { tmpdir } from 'node:os';
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import test from 'node:test';
|
|
12
|
+
|
|
13
|
+
const WORKER_ENTRY = fileURLToPath(new URL('./session-runtime-worker.mjs', import.meta.url));
|
|
14
|
+
const REPO_ROOT = join(dirname(WORKER_ENTRY), '..', '..');
|
|
15
|
+
|
|
16
|
+
function startWorker(runtimeRoot) {
|
|
17
|
+
const child = fork(WORKER_ENTRY, [], {
|
|
18
|
+
cwd: REPO_ROOT,
|
|
19
|
+
execArgv: [],
|
|
20
|
+
stdio: ['ignore', 'ignore', 'pipe', 'ipc'],
|
|
21
|
+
env: {
|
|
22
|
+
...process.env,
|
|
23
|
+
MIXDOG_RUNTIME_ROOT: runtimeRoot,
|
|
24
|
+
MIXDOG_DATA_DIR: runtimeRoot,
|
|
25
|
+
MIXDOG_SESSION_RUNTIME_SHARD: '0',
|
|
26
|
+
MIXDOG_SESSION_RUNTIME_SHARD_COUNT: '2',
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
const responses = new Map();
|
|
30
|
+
const waiters = new Map();
|
|
31
|
+
child.on('message', (message) => {
|
|
32
|
+
if (message?.type !== 'response') return;
|
|
33
|
+
const id = String(message.requestId || '');
|
|
34
|
+
const waiter = waiters.get(id);
|
|
35
|
+
if (waiter) {
|
|
36
|
+
waiters.delete(id);
|
|
37
|
+
waiter(message);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
responses.set(id, message);
|
|
41
|
+
});
|
|
42
|
+
const send = (payload) => child.send(payload);
|
|
43
|
+
const wait = (requestId, timeoutMs = 30_000) => new Promise((resolve, reject) => {
|
|
44
|
+
const settled = responses.get(requestId);
|
|
45
|
+
if (settled) {
|
|
46
|
+
responses.delete(requestId);
|
|
47
|
+
resolve(settled);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const timer = setTimeout(
|
|
51
|
+
() => reject(new Error(`timed out waiting for ${requestId}`)),
|
|
52
|
+
timeoutMs,
|
|
53
|
+
);
|
|
54
|
+
waiters.set(requestId, (message) => { clearTimeout(timer); resolve(message); });
|
|
55
|
+
});
|
|
56
|
+
return { child, send, wait };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async function stopWorker(worker) {
|
|
60
|
+
try {
|
|
61
|
+
worker.send({ type: 'shutdown', requestId: 'shutdown-1', reason: 'test complete' });
|
|
62
|
+
await worker.wait('shutdown-1', 8_000).catch(() => {});
|
|
63
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
64
|
+
} finally {
|
|
65
|
+
try { worker.child.kill(); } catch {}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
test('a cancel that arrives before the dispatch is retained and aborts it', async () => {
|
|
70
|
+
const runtimeRoot = await mkdtemp(join(tmpdir(), 'mixdog-dispatch-cancel-'));
|
|
71
|
+
const worker = startWorker(runtimeRoot);
|
|
72
|
+
try {
|
|
73
|
+
worker.send({
|
|
74
|
+
type: 'agent-dispatch-cancel',
|
|
75
|
+
requestId: 'cancel-early',
|
|
76
|
+
dispatchId: 'dispatch-cold',
|
|
77
|
+
reason: 'user aborted during cold start',
|
|
78
|
+
});
|
|
79
|
+
const cancel = await worker.wait('cancel-early', 15_000);
|
|
80
|
+
assert.equal(cancel.ok, true);
|
|
81
|
+
// Never "cancelled: false": an unknown id means not-registered-YET.
|
|
82
|
+
assert.equal(cancel.value.cancelled, true);
|
|
83
|
+
assert.equal(cancel.value.retained, true);
|
|
84
|
+
|
|
85
|
+
const startedAt = Date.now();
|
|
86
|
+
worker.send({
|
|
87
|
+
type: 'agent-dispatch',
|
|
88
|
+
requestId: 'dispatch-cold-1',
|
|
89
|
+
dispatchId: 'dispatch-cold',
|
|
90
|
+
agent: 'memory',
|
|
91
|
+
params: { prompt: 'must never run' },
|
|
92
|
+
});
|
|
93
|
+
const dispatch = await worker.wait('dispatch-cold-1', 20_000);
|
|
94
|
+
assert.equal(dispatch.ok, false);
|
|
95
|
+
assert.match(String(dispatch.error?.message || ''), /user aborted during cold start/);
|
|
96
|
+
// The abort is adopted at registration — before the orchestrator/provider
|
|
97
|
+
// cold start, which alone takes seconds in this worker.
|
|
98
|
+
assert.ok(
|
|
99
|
+
Date.now() - startedAt < 3_000,
|
|
100
|
+
'aborted dispatch never entered module/provider setup',
|
|
101
|
+
);
|
|
102
|
+
} finally {
|
|
103
|
+
await stopWorker(worker);
|
|
104
|
+
await rm(runtimeRoot, { recursive: true, force: true });
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
test('a cancel racing a cold-start dispatch is never answered "not running"', async () => {
|
|
109
|
+
const runtimeRoot = await mkdtemp(join(tmpdir(), 'mixdog-dispatch-cancel-warm-'));
|
|
110
|
+
const worker = startWorker(runtimeRoot);
|
|
111
|
+
try {
|
|
112
|
+
worker.send({
|
|
113
|
+
type: 'agent-dispatch',
|
|
114
|
+
requestId: 'dispatch-warm-1',
|
|
115
|
+
dispatchId: 'dispatch-warm',
|
|
116
|
+
agent: 'memory',
|
|
117
|
+
params: { prompt: 'cancelled while importing' },
|
|
118
|
+
});
|
|
119
|
+
// The cold start compiles the orchestrator graph and blocks this loop, so
|
|
120
|
+
// the cancel frame can only be handled once that window closes. Whichever
|
|
121
|
+
// side wins, the abort must be honoured or retained — never dropped with
|
|
122
|
+
// `cancelled: false`, which is what let a canceled dispatch keep running.
|
|
123
|
+
worker.send({
|
|
124
|
+
type: 'agent-dispatch-cancel',
|
|
125
|
+
requestId: 'cancel-warm',
|
|
126
|
+
dispatchId: 'dispatch-warm',
|
|
127
|
+
reason: 'aborted mid cold start',
|
|
128
|
+
});
|
|
129
|
+
const cancel = await worker.wait('cancel-warm', 60_000);
|
|
130
|
+
assert.equal(cancel.ok, true);
|
|
131
|
+
assert.equal(cancel.value.cancelled, true);
|
|
132
|
+
|
|
133
|
+
const dispatch = await worker.wait('dispatch-warm-1', 60_000);
|
|
134
|
+
assert.equal(dispatch.ok, false);
|
|
135
|
+
} finally {
|
|
136
|
+
await stopWorker(worker);
|
|
137
|
+
await rm(runtimeRoot, { recursive: true, force: true });
|
|
138
|
+
}
|
|
139
|
+
});
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { createInlineSessionRuntimeHost } from './session-runtime-inline-host.mjs';
|
|
2
|
+
import { createSessionRuntimeHost } from './session-runtime-host.mjs';
|
|
3
|
+
|
|
4
|
+
export function resolveSessionRuntimeMode(value = process.env.MIXDOG_SESSION_RUNTIME_MODE) {
|
|
5
|
+
const mode = String(value || 'inline').trim().toLowerCase();
|
|
6
|
+
if (mode === 'inline') return 'inline';
|
|
7
|
+
if (mode === 'process') return 'process';
|
|
8
|
+
throw new Error(`unsupported MIXDOG_SESSION_RUNTIME_MODE=${JSON.stringify(value)}`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function createDaemonSessionRuntimeHost(options = {}, {
|
|
12
|
+
createInline = createInlineSessionRuntimeHost,
|
|
13
|
+
createProcess = createSessionRuntimeHost,
|
|
14
|
+
mode = resolveSessionRuntimeMode(),
|
|
15
|
+
} = {}) {
|
|
16
|
+
return mode === 'process' ? createProcess(options) : createInline(options);
|
|
17
|
+
}
|