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
|
@@ -89,6 +89,7 @@ function createDiffTracker(meta = {}) {
|
|
|
89
89
|
return {
|
|
90
90
|
ownerSessionId: clean(meta.ownerSessionId) || null,
|
|
91
91
|
ownerGeneration: Number(meta.ownerGeneration) || 0,
|
|
92
|
+
checkpointId: clean(meta.checkpointId),
|
|
92
93
|
worktreeRequest: clean(meta.worktreeRequest),
|
|
93
94
|
worktreeContended: false,
|
|
94
95
|
valid: true,
|
|
@@ -445,20 +446,26 @@ function publicAgentReviews(sessionId) {
|
|
|
445
446
|
}
|
|
446
447
|
|
|
447
448
|
/** Start a new user turn and invalidate the prior turn's child review group. */
|
|
448
|
-
export async function beginTurnSnapshot(_worktree, sessionId) {
|
|
449
|
+
export async function beginTurnSnapshot(_worktree, sessionId, options = {}) {
|
|
449
450
|
const ownerSessionId = clean(sessionId);
|
|
450
451
|
if (DISABLED || !ownerSessionId) return;
|
|
451
452
|
const generation = (_turnsBySession.get(ownerSessionId)?.generation || 0) + 1;
|
|
453
|
+
const checkpointId = clean(options?.checkpointId);
|
|
452
454
|
for (const [trackedSessionId, tracker] of _diffTrackersBySession) {
|
|
453
455
|
if (tracker.ownerSessionId === ownerSessionId) _diffTrackersBySession.delete(trackedSessionId);
|
|
454
456
|
}
|
|
455
457
|
_turnsBySession.delete(ownerSessionId);
|
|
456
458
|
_turnsBySession.set(ownerSessionId, {
|
|
457
459
|
generation,
|
|
460
|
+
checkpointId,
|
|
458
461
|
agents: new Map(),
|
|
459
462
|
});
|
|
460
463
|
const worktreeRequest = pathKey(_worktree);
|
|
461
|
-
const tracker = resetDiffTracker(ownerSessionId, {
|
|
464
|
+
const tracker = resetDiffTracker(ownerSessionId, {
|
|
465
|
+
ownerGeneration: generation,
|
|
466
|
+
checkpointId,
|
|
467
|
+
worktreeRequest,
|
|
468
|
+
});
|
|
462
469
|
trimTurnCache();
|
|
463
470
|
if (!tracker) return;
|
|
464
471
|
for (const [otherSessionId, other] of _diffTrackersBySession) {
|
|
@@ -575,6 +582,7 @@ async function persistTurnSnapshotRecord(ownerSessionId, tracker) {
|
|
|
575
582
|
try {
|
|
576
583
|
await saveTurnSnapshotRecord(ownerSessionId, {
|
|
577
584
|
generation: _turnsBySession.get(ownerSessionId)?.generation || 0,
|
|
585
|
+
checkpointId: tracker.checkpointId,
|
|
578
586
|
root: snapshot.root,
|
|
579
587
|
baselineTree: snapshot.baselineTree,
|
|
580
588
|
toolFiles: owned,
|
|
@@ -669,6 +677,7 @@ async function scopedReviewFromRecord(ownerSessionId) {
|
|
|
669
677
|
patch: resumed.snapshot.patch || '',
|
|
670
678
|
snapshotKind: 'scoped',
|
|
671
679
|
revertMode: 'scoped',
|
|
680
|
+
checkpointId: clean(resumed.record.checkpointId),
|
|
672
681
|
patchTruncated: resumed.snapshot.patchTruncated === true,
|
|
673
682
|
authoritative: true,
|
|
674
683
|
agents: publicAgentReviews(ownerSessionId),
|
|
@@ -677,9 +686,20 @@ async function scopedReviewFromRecord(ownerSessionId) {
|
|
|
677
686
|
}
|
|
678
687
|
|
|
679
688
|
/** Restore recorded paths — every one of them, or just the file the user picked. */
|
|
680
|
-
|
|
689
|
+
function assertCheckpointMatches(expectedCheckpointId, actualCheckpointId) {
|
|
690
|
+
const expected = clean(expectedCheckpointId);
|
|
691
|
+
if (!expected) return;
|
|
692
|
+
const actual = clean(actualCheckpointId);
|
|
693
|
+
if (!actual) throw new Error('turn review checkpoint is unavailable');
|
|
694
|
+
if (actual !== expected) {
|
|
695
|
+
throw new Error('turn review checkpoint changed; refresh the review');
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
async function revertFromRecord(worktree, ownerSessionId, file, expectedCheckpointId = '') {
|
|
681
700
|
const resumed = await resumeRecordedSnapshot(ownerSessionId);
|
|
682
701
|
if (!resumed) return null;
|
|
702
|
+
assertCheckpointMatches(expectedCheckpointId, resumed.record.checkpointId);
|
|
683
703
|
const owned = resumed.record.toolFiles;
|
|
684
704
|
const targets = file
|
|
685
705
|
? owned.filter((path) => recordedPathMatchesFile(path, resumed.record.root, file))
|
|
@@ -723,6 +743,7 @@ export async function getTurnReviewDiff(_worktree, sessionId, options = {}) {
|
|
|
723
743
|
patch: snapshot?.patch ?? tracker?.unifiedDiff ?? '',
|
|
724
744
|
snapshotKind: snapshot ? 'worktree' : 'tool',
|
|
725
745
|
revertMode: snapshot ? 'worktree' : trackedRevertAvailable ? 'tracked' : '',
|
|
746
|
+
checkpointId: clean(turn?.checkpointId || tracker?.checkpointId),
|
|
726
747
|
patchTruncated: snapshot?.patchTruncated === true,
|
|
727
748
|
authoritative: Boolean(turn && tracker),
|
|
728
749
|
agents: publicAgentReviews(ownerSessionId),
|
|
@@ -738,9 +759,10 @@ function unavailableTrackedRevertReason(tracker) {
|
|
|
738
759
|
}
|
|
739
760
|
|
|
740
761
|
/** Restore one reviewed file to this turn's worktree baseline, never to HEAD. */
|
|
741
|
-
export async function revertTurnReviewFile(_worktree, sessionId, file) {
|
|
762
|
+
export async function revertTurnReviewFile(_worktree, sessionId, file, expectedCheckpointId = '') {
|
|
742
763
|
const ownerSessionId = clean(sessionId);
|
|
743
764
|
const tracker = _diffTrackersBySession.get(ownerSessionId);
|
|
765
|
+
if (tracker) assertCheckpointMatches(expectedCheckpointId, tracker.checkpointId);
|
|
744
766
|
if (tracker && !worktreeSnapshotUsable(tracker)) {
|
|
745
767
|
const pairs = trackedPairs(tracker);
|
|
746
768
|
if (pairs.length > 0) {
|
|
@@ -749,7 +771,7 @@ export async function revertTurnReviewFile(_worktree, sessionId, file) {
|
|
|
749
771
|
await revertTrackedPairs(tracker, _worktree, [pair]);
|
|
750
772
|
return await getTurnReviewDiff(_worktree, ownerSessionId);
|
|
751
773
|
}
|
|
752
|
-
const recorded = await revertFromRecord(_worktree, ownerSessionId, file);
|
|
774
|
+
const recorded = await revertFromRecord(_worktree, ownerSessionId, file, expectedCheckpointId);
|
|
753
775
|
if (recorded) return recorded;
|
|
754
776
|
throw new Error(unavailableTrackedRevertReason(tracker));
|
|
755
777
|
}
|
|
@@ -757,22 +779,23 @@ export async function revertTurnReviewFile(_worktree, sessionId, file) {
|
|
|
757
779
|
await revertTurnWorktreeFile(tracker.worktreeSnapshot, file);
|
|
758
780
|
return await getTurnReviewDiff(_worktree, ownerSessionId);
|
|
759
781
|
}
|
|
760
|
-
const recorded = await revertFromRecord(_worktree, ownerSessionId, file);
|
|
782
|
+
const recorded = await revertFromRecord(_worktree, ownerSessionId, file, expectedCheckpointId);
|
|
761
783
|
if (recorded) return recorded;
|
|
762
784
|
throw new Error('turn worktree snapshot is unavailable');
|
|
763
785
|
}
|
|
764
786
|
|
|
765
787
|
/** Restore every file in the reviewed turn to its turn-start state. */
|
|
766
|
-
export async function revertTurnReview(_worktree, sessionId) {
|
|
788
|
+
export async function revertTurnReview(_worktree, sessionId, expectedCheckpointId = '') {
|
|
767
789
|
const ownerSessionId = clean(sessionId);
|
|
768
790
|
const tracker = _diffTrackersBySession.get(ownerSessionId);
|
|
791
|
+
if (tracker) assertCheckpointMatches(expectedCheckpointId, tracker.checkpointId);
|
|
769
792
|
if (tracker && !worktreeSnapshotUsable(tracker)) {
|
|
770
793
|
const pairs = trackedPairs(tracker);
|
|
771
794
|
if (pairs.length > 0) {
|
|
772
795
|
await revertTrackedPairs(tracker, _worktree, pairs);
|
|
773
796
|
return await getTurnReviewDiff(_worktree, ownerSessionId);
|
|
774
797
|
}
|
|
775
|
-
const recorded = await revertFromRecord(_worktree, ownerSessionId, null);
|
|
798
|
+
const recorded = await revertFromRecord(_worktree, ownerSessionId, null, expectedCheckpointId);
|
|
776
799
|
if (recorded) return recorded;
|
|
777
800
|
throw new Error(unavailableTrackedRevertReason(tracker));
|
|
778
801
|
}
|
|
@@ -780,7 +803,7 @@ export async function revertTurnReview(_worktree, sessionId) {
|
|
|
780
803
|
await revertTurnWorktreeSnapshot(tracker.worktreeSnapshot);
|
|
781
804
|
return await getTurnReviewDiff(_worktree, ownerSessionId);
|
|
782
805
|
}
|
|
783
|
-
const recorded = await revertFromRecord(_worktree, ownerSessionId, null);
|
|
806
|
+
const recorded = await revertFromRecord(_worktree, ownerSessionId, null, expectedCheckpointId);
|
|
784
807
|
if (recorded) return recorded;
|
|
785
808
|
throw new Error('turn worktree snapshot is unavailable');
|
|
786
809
|
}
|
|
@@ -6,15 +6,13 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
* user-cwd.mjs — shared helper to resolve the user's working directory
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* precedence when set.
|
|
9
|
+
* user-cwd.mjs — shared helper to resolve the user's working directory.
|
|
10
|
+
* Inline runtimes use AsyncLocalStorage; an inherited
|
|
11
|
+
* MIXDOG_SESSION_CWD remains only a boot/isolated-process fallback.
|
|
13
12
|
*
|
|
14
13
|
* Single-source-of-truth model:
|
|
15
|
-
* - captureOriginalUserCwd() reads
|
|
16
|
-
*
|
|
17
|
-
* call. No in-memory freeze.
|
|
14
|
+
* - captureOriginalUserCwd() reads the async session scope first, then
|
|
15
|
+
* MIXDOG_SESSION_CWD/user-cwd.txt fresh on every call.
|
|
18
16
|
* - rawUserCwd() reads ONLY user-cwd.txt (no env consult) — exposed
|
|
19
17
|
* for the cwd-tool auto-init path so the env-var fallback cannot
|
|
20
18
|
* become self-referential.
|
|
@@ -32,6 +30,12 @@ import { resolvePluginData, mixdogRoot } from './plugin-paths.mjs'
|
|
|
32
30
|
|
|
33
31
|
const _cwdOverride = new AsyncLocalStorage()
|
|
34
32
|
|
|
33
|
+
function _activeCwdOverride() {
|
|
34
|
+
const store = _cwdOverride.getStore()
|
|
35
|
+
if (typeof store === 'string') return store
|
|
36
|
+
return store && typeof store.cwd === 'string' ? store.cwd : null
|
|
37
|
+
}
|
|
38
|
+
|
|
35
39
|
function _dataFile(name) {
|
|
36
40
|
return join(resolvePluginData(), name)
|
|
37
41
|
}
|
|
@@ -75,10 +79,9 @@ function _normalizePlatformCwd(p) {
|
|
|
75
79
|
|
|
76
80
|
/**
|
|
77
81
|
* Resolve the session cwd from EXPLICIT signals only:
|
|
78
|
-
* 1.
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* 2. user-cwd.txt — single source of truth maintained by Mixdog
|
|
82
|
+
* 1. AsyncLocalStorage — current inline-session scope.
|
|
83
|
+
* 2. process.env.MIXDOG_SESSION_CWD — boot/isolated-process override.
|
|
84
|
+
* 3. user-cwd.txt — persisted fallback maintained by Mixdog
|
|
82
85
|
* (rewritten at every session start).
|
|
83
86
|
* Returns null when neither is available.
|
|
84
87
|
*
|
|
@@ -89,6 +92,8 @@ function _normalizePlatformCwd(p) {
|
|
|
89
92
|
* project_id resolution; use pwd() for relative-path resolution.
|
|
90
93
|
*/
|
|
91
94
|
export function explicitSessionCwd() {
|
|
95
|
+
const scoped = _activeCwdOverride()
|
|
96
|
+
if (scoped) return scoped
|
|
92
97
|
const sessionRaw = process.env.MIXDOG_SESSION_CWD
|
|
93
98
|
if (typeof sessionRaw === 'string' && sessionRaw.length > 0) {
|
|
94
99
|
const normalized = _normalizePlatformCwd(sessionRaw)
|
|
@@ -208,8 +213,22 @@ export function readLastSessionCwd(keyPid) {
|
|
|
208
213
|
* Run fn inside an async context where pwd() returns cwd.
|
|
209
214
|
* All descendant async calls within fn see cwd as their working directory.
|
|
210
215
|
*/
|
|
211
|
-
function runWithCwdOverride(cwd, fn) {
|
|
212
|
-
|
|
216
|
+
export function runWithCwdOverride(cwd, fn) {
|
|
217
|
+
const normalized = _normalizePlatformCwd(cwd)
|
|
218
|
+
return _cwdOverride.run({ cwd: normalized || String(cwd || '') }, fn)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Retarget the current async session after an in-turn `cwd set`.
|
|
223
|
+
* Other concurrent sessions hold different AsyncLocalStorage records.
|
|
224
|
+
*/
|
|
225
|
+
export function updateCurrentCwdOverride(cwd) {
|
|
226
|
+
const store = _cwdOverride.getStore()
|
|
227
|
+
if (!store || typeof store !== 'object') return false
|
|
228
|
+
const normalized = _normalizePlatformCwd(cwd)
|
|
229
|
+
if (!normalized) return false
|
|
230
|
+
store.cwd = normalized
|
|
231
|
+
return true
|
|
213
232
|
}
|
|
214
233
|
|
|
215
234
|
/**
|
|
@@ -217,5 +236,5 @@ function runWithCwdOverride(cwd, fn) {
|
|
|
217
236
|
* override set by runWithCwdOverride (innermost wins) ?? original user cwd.
|
|
218
237
|
*/
|
|
219
238
|
export function pwd() {
|
|
220
|
-
return
|
|
239
|
+
return _activeCwdOverride() ?? captureOriginalUserCwd()
|
|
221
240
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdtempSync, mkdirSync, rmSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
explicitSessionCwd,
|
|
9
|
+
pwd,
|
|
10
|
+
runWithCwdOverride,
|
|
11
|
+
updateCurrentCwdOverride,
|
|
12
|
+
} from './user-cwd.mjs';
|
|
13
|
+
|
|
14
|
+
test('cwd overrides stay isolated across concurrent inline sessions', async () => {
|
|
15
|
+
const root = mkdtempSync(join(tmpdir(), 'mixdog-user-cwd-'));
|
|
16
|
+
const a = join(root, 'a');
|
|
17
|
+
const b = join(root, 'b');
|
|
18
|
+
const next = join(root, 'next');
|
|
19
|
+
mkdirSync(a);
|
|
20
|
+
mkdirSync(b);
|
|
21
|
+
mkdirSync(next);
|
|
22
|
+
try {
|
|
23
|
+
await Promise.all([
|
|
24
|
+
runWithCwdOverride(a, async () => {
|
|
25
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
26
|
+
assert.equal(pwd(), a);
|
|
27
|
+
assert.equal(explicitSessionCwd(), a);
|
|
28
|
+
assert.equal(updateCurrentCwdOverride(next), true);
|
|
29
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
30
|
+
assert.equal(pwd(), next);
|
|
31
|
+
assert.equal(explicitSessionCwd(), next);
|
|
32
|
+
}),
|
|
33
|
+
runWithCwdOverride(b, async () => {
|
|
34
|
+
assert.equal(pwd(), b);
|
|
35
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
36
|
+
assert.equal(pwd(), b);
|
|
37
|
+
assert.equal(explicitSessionCwd(), b);
|
|
38
|
+
}),
|
|
39
|
+
]);
|
|
40
|
+
} finally {
|
|
41
|
+
rmSync(root, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
});
|
|
@@ -4,16 +4,18 @@
|
|
|
4
4
|
// that still means "follow the Main Model" when left unset.
|
|
5
5
|
import test from 'node:test';
|
|
6
6
|
import assert from 'node:assert/strict';
|
|
7
|
-
import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs';
|
|
7
|
+
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from 'node:fs';
|
|
8
8
|
import { tmpdir } from 'node:os';
|
|
9
9
|
import { join } from 'node:path';
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
|
+
DEFAULT_DISABLED_AGENT_IDS,
|
|
12
13
|
canonicalizeAgentRouteStorage,
|
|
13
14
|
disabledAgentIds,
|
|
14
15
|
isAgentDisabled,
|
|
15
16
|
withAgentDisabled,
|
|
16
17
|
} from '../runtime/shared/agent-route-config.mjs';
|
|
18
|
+
import { buildDefaultConfig } from '../runtime/agent/orchestrator/config.mjs';
|
|
17
19
|
import {
|
|
18
20
|
createWorkflowHelpers,
|
|
19
21
|
isDefaultWebSearchRouteConfig,
|
|
@@ -43,6 +45,23 @@ function fixture(agentIds = ['worker', 'reviewer']) {
|
|
|
43
45
|
return { root, data, helpers };
|
|
44
46
|
}
|
|
45
47
|
|
|
48
|
+
test('fresh installs ship every starter Agent disabled with a complete definition', () => {
|
|
49
|
+
const defaults = buildDefaultConfig({ detectCredentials: false });
|
|
50
|
+
assert.deepEqual(disabledAgentIds(defaults), [...DEFAULT_DISABLED_AGENT_IDS].sort());
|
|
51
|
+
|
|
52
|
+
const agentsRoot = join(process.cwd(), 'src', 'agents');
|
|
53
|
+
for (const id of DEFAULT_DISABLED_AGENT_IDS) {
|
|
54
|
+
const manifestPath = join(agentsRoot, id, 'agent.json');
|
|
55
|
+
const rulesPath = join(agentsRoot, id, 'AGENT.md');
|
|
56
|
+
assert.equal(existsSync(manifestPath), true, `${id} manifest must ship`);
|
|
57
|
+
assert.equal(existsSync(rulesPath), true, `${id} rules must ship`);
|
|
58
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
59
|
+
assert.equal(manifest.id, id);
|
|
60
|
+
assert.ok(String(manifest.description || '').trim(), `${id} description must be non-empty`);
|
|
61
|
+
assert.ok(readFileSync(rulesPath, 'utf8').trim(), `${id} rules must be non-empty`);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
|
|
46
65
|
test('disabled agents are stored apart from the route and survive canonicalization', () => {
|
|
47
66
|
const config = { agents: { worker: { provider: 'anthropic-oauth', model: 'claude-opus-5' } } };
|
|
48
67
|
const off = canonicalizeAgentRouteStorage(withAgentDisabled(config, 'worker', true));
|
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
import { SUMMARY_PREFIX } from '../runtime/agent/orchestrator/session/compact.mjs';
|
|
12
12
|
import { hasUserConversationMessage } from '../runtime/agent/orchestrator/session/manager/prompt-utils.mjs';
|
|
13
13
|
import {
|
|
14
|
+
resolveContextUsageSnapshot,
|
|
14
15
|
resolveGaugeContextTokens,
|
|
15
16
|
resolveWorkerCompactPolicy,
|
|
16
17
|
} from '../runtime/agent/orchestrator/session/loop/compact-policy.mjs';
|
|
@@ -115,6 +116,7 @@ export function createContextStatus({
|
|
|
115
116
|
compactionTriggerTokens: Number(compaction.triggerTokens || 0),
|
|
116
117
|
compactionLastChangedAt: Number(compaction.lastChangedAt || 0),
|
|
117
118
|
compactionLastCompactAt: Number(compaction.lastCompactAt || 0),
|
|
119
|
+
contextUsageSnapshot: session?.contextUsageSnapshot || null,
|
|
118
120
|
};
|
|
119
121
|
}
|
|
120
122
|
|
|
@@ -268,9 +270,13 @@ export function createContextStatus({
|
|
|
268
270
|
...resolveSessionCompactPolicy(session || {}, compactBoundaryTokens),
|
|
269
271
|
tokenCalibration: providerTokenCalibration(session?.provider || route.provider),
|
|
270
272
|
};
|
|
271
|
-
//
|
|
272
|
-
//
|
|
273
|
-
|
|
273
|
+
// A successful compaction publishes one durable post-mutation reading.
|
|
274
|
+
// Polling and cold resume keep that exact value until a fresh provider
|
|
275
|
+
// baseline or a changed transcript/route/tool surface invalidates it.
|
|
276
|
+
const usageSnapshot = (!lastContextTokens || lastUsageStale)
|
|
277
|
+
? resolveContextUsageSnapshot(session, compactPolicy, { messages })
|
|
278
|
+
: null;
|
|
279
|
+
const usedTokens = usageSnapshot?.usedTokens ?? resolveGaugeContextTokens(
|
|
274
280
|
messageSummary.estimatedTokens,
|
|
275
281
|
compactPolicy,
|
|
276
282
|
{ messages, sessionRef: session },
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { discoverPluginMcp } from './plugin-mcp.mjs';
|
|
2
2
|
import { featureEnvOverride, memoryToolsEnabled } from './config-helpers.mjs';
|
|
3
3
|
import { readSessionCoreMemoryPayload } from '../runtime/memory/lib/core-memory-file.mjs';
|
|
4
|
+
import { saveSession } from '../runtime/agent/orchestrator/session/store.mjs';
|
|
4
5
|
|
|
5
6
|
// cwd-plugins.mjs — cwd resolution/apply + plugins-status + core-memory context,
|
|
6
7
|
// extracted from mixdog-session-runtime.mjs. Dependency-injected factory that
|
|
@@ -15,16 +16,14 @@ export function createCwdPlugins({
|
|
|
15
16
|
setCurrentCwd,
|
|
16
17
|
getConfig,
|
|
17
18
|
getSession,
|
|
19
|
+
getDesktopSession,
|
|
20
|
+
setDesktopSession,
|
|
18
21
|
getRoute,
|
|
19
|
-
getLastProjectMcpKey,
|
|
20
|
-
setLastProjectMcpKey,
|
|
21
22
|
isCodeGraphPrewarmLazy,
|
|
22
23
|
isCodeGraphFirstTurnPrewarmDone,
|
|
23
24
|
getCodeGraphPrewarmDelayMs,
|
|
24
25
|
setSessionNeedsCwdRefresh,
|
|
25
26
|
// callbacks / deps
|
|
26
|
-
connectConfiguredMcp,
|
|
27
|
-
invalidatePreSessionToolSurface,
|
|
28
27
|
scheduleCodeGraphPrewarm,
|
|
29
28
|
hooks,
|
|
30
29
|
hookCommonPayload,
|
|
@@ -37,8 +36,9 @@ export function createCwdPlugins({
|
|
|
37
36
|
pluginMcpServerName,
|
|
38
37
|
mcpScriptForPlugin,
|
|
39
38
|
countSkillFiles,
|
|
40
|
-
readProjectMcpServers,
|
|
41
39
|
writeLastSessionCwd,
|
|
40
|
+
updateCurrentCwdOverride,
|
|
41
|
+
persistSession = (session) => saveSession(session, { immediate: true }),
|
|
42
42
|
// shared utils
|
|
43
43
|
clean,
|
|
44
44
|
resolve,
|
|
@@ -98,17 +98,31 @@ export function createCwdPlugins({
|
|
|
98
98
|
return next;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
function applyResolvedCwd(nextCwd, {
|
|
101
|
+
function applyResolvedCwd(nextCwd, {
|
|
102
|
+
markRefresh = true,
|
|
103
|
+
waitForMcpReset = false,
|
|
104
|
+
persistProjectSelection = false,
|
|
105
|
+
} = {}) {
|
|
102
106
|
const resolved = resolve(nextCwd);
|
|
103
107
|
const stat = statSync(resolved);
|
|
104
108
|
if (!stat.isDirectory()) throw new Error(`cwd: not a directory: ${resolved}`);
|
|
105
109
|
const changed = resolve(getCurrentCwd()) !== resolved;
|
|
106
110
|
setCurrentCwd(resolved);
|
|
107
111
|
const currentCwd = resolved;
|
|
108
|
-
process.env.MIXDOG_SESSION_CWD = currentCwd;
|
|
109
|
-
writeLastSessionCwd(currentCwd);
|
|
110
112
|
const session = getSession();
|
|
111
113
|
if (session) session.cwd = currentCwd;
|
|
114
|
+
updateCurrentCwdOverride?.(currentCwd);
|
|
115
|
+
writeLastSessionCwd(currentCwd, session?.clientHostPid);
|
|
116
|
+
if (persistProjectSelection && session) {
|
|
117
|
+
const desktop = getDesktopSession?.() || session.desktopSession;
|
|
118
|
+
if (desktop && typeof desktop === 'object') {
|
|
119
|
+
const nextDesktop = { classification: 'project', projectPath: currentCwd };
|
|
120
|
+
setDesktopSession?.(nextDesktop);
|
|
121
|
+
session.desktopSession = nextDesktop;
|
|
122
|
+
}
|
|
123
|
+
session.updatedAt = Date.now();
|
|
124
|
+
persistSession(session);
|
|
125
|
+
}
|
|
112
126
|
// cwd changes NEVER recreate the session: a mid-conversation cwd switch must
|
|
113
127
|
// preserve the full message history. We retarget the live execution cwd in
|
|
114
128
|
// place; the BP3 session snapshot remains the session-start environment.
|
|
@@ -125,29 +139,14 @@ export function createCwdPlugins({
|
|
|
125
139
|
const delay = getCodeGraphPrewarmDelayMs();
|
|
126
140
|
scheduleCodeGraphPrewarm(changed ? 0 : delay, changed ? 'cwd-change' : 'cwd');
|
|
127
141
|
}
|
|
128
|
-
//
|
|
129
|
-
|
|
130
|
-
// an awaitable reset so the next session/turn cannot observe the old registry.
|
|
131
|
-
let mcpReset = null;
|
|
132
|
-
if (changed) {
|
|
133
|
-
try {
|
|
134
|
-
const nextKey = resolved + '\u0000' + JSON.stringify(readProjectMcpServers(resolved));
|
|
135
|
-
if (nextKey !== getLastProjectMcpKey()) {
|
|
136
|
-
setLastProjectMcpKey(nextKey);
|
|
137
|
-
mcpReset = Promise.resolve(connectConfiguredMcp({ reset: true }))
|
|
138
|
-
.then(() => invalidatePreSessionToolSurface())
|
|
139
|
-
.catch(() => {});
|
|
140
|
-
}
|
|
141
|
-
} catch {}
|
|
142
|
-
}
|
|
142
|
+
// Extension settings are global; cwd changes never reload MCP or Skills.
|
|
143
|
+
void waitForMcpReset;
|
|
143
144
|
// CwdChanged: bridge an effective cwd switch to the standard hook bus.
|
|
144
145
|
// No matcher event — payload is minimal { cwd }. Fire-and-forget.
|
|
145
146
|
if (changed) {
|
|
146
147
|
try { void hooks.dispatch('CwdChanged', hookCommonPayload({ cwd: currentCwd })); } catch {}
|
|
147
148
|
}
|
|
148
|
-
return
|
|
149
|
-
? Promise.resolve(mcpReset).then(() => currentCwd)
|
|
150
|
-
: currentCwd;
|
|
149
|
+
return currentCwd;
|
|
151
150
|
}
|
|
152
151
|
|
|
153
152
|
async function refreshSessionForCwdIfNeeded(reason = 'cwd-change') {
|
|
@@ -183,6 +182,7 @@ export function createCwdPlugins({
|
|
|
183
182
|
sourceUrl: clean(entry.source),
|
|
184
183
|
sourceType: clean(entry.sourceType) || 'git',
|
|
185
184
|
managed: entry.managed !== false,
|
|
185
|
+
enabled: entry.enabled !== false,
|
|
186
186
|
root,
|
|
187
187
|
installedAt: entry.installedAt || null,
|
|
188
188
|
updatedAt: entry.updatedAt || null,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdtempSync, mkdirSync, rmSync, statSync } from 'node:fs';
|
|
3
|
+
import { tmpdir } from 'node:os';
|
|
4
|
+
import { resolve, join } from 'node:path';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
|
|
7
|
+
import { createCwdPlugins } from './cwd-plugins.mjs';
|
|
8
|
+
|
|
9
|
+
test('explicit cwd selection persists execution cwd and desktop project metadata together', () => {
|
|
10
|
+
const root = mkdtempSync(join(tmpdir(), 'mixdog-cwd-persist-'));
|
|
11
|
+
const before = join(root, 'before');
|
|
12
|
+
const after = join(root, 'after');
|
|
13
|
+
mkdirSync(before);
|
|
14
|
+
mkdirSync(after);
|
|
15
|
+
let currentCwd = before;
|
|
16
|
+
let desktopSession = { classification: 'project', projectPath: before };
|
|
17
|
+
const session = {
|
|
18
|
+
id: 'sess-cwd-persist',
|
|
19
|
+
cwd: before,
|
|
20
|
+
desktopSession,
|
|
21
|
+
clientHostPid: 4321,
|
|
22
|
+
};
|
|
23
|
+
const persisted = [];
|
|
24
|
+
const sentinels = [];
|
|
25
|
+
const overrides = [];
|
|
26
|
+
try {
|
|
27
|
+
const { applyResolvedCwd } = createCwdPlugins({
|
|
28
|
+
getCurrentCwd: () => currentCwd,
|
|
29
|
+
setCurrentCwd: (next) => { currentCwd = next; },
|
|
30
|
+
getConfig: () => ({}),
|
|
31
|
+
getSession: () => session,
|
|
32
|
+
getDesktopSession: () => desktopSession,
|
|
33
|
+
setDesktopSession: (next) => { desktopSession = next; },
|
|
34
|
+
getRoute: () => ({}),
|
|
35
|
+
isCodeGraphPrewarmLazy: () => true,
|
|
36
|
+
isCodeGraphFirstTurnPrewarmDone: () => false,
|
|
37
|
+
getCodeGraphPrewarmDelayMs: () => 0,
|
|
38
|
+
setSessionNeedsCwdRefresh: () => {},
|
|
39
|
+
scheduleCodeGraphPrewarm: () => {},
|
|
40
|
+
hooks: { dispatch: async () => {} },
|
|
41
|
+
hookCommonPayload: (value) => value,
|
|
42
|
+
bootProfile: () => {},
|
|
43
|
+
getMemoryModule: async () => null,
|
|
44
|
+
listRegisteredPlugins: () => [],
|
|
45
|
+
pluginAdminStatus: () => ({}),
|
|
46
|
+
pluginManifest: () => ({}),
|
|
47
|
+
pluginMcpServerName: () => '',
|
|
48
|
+
mcpScriptForPlugin: () => '',
|
|
49
|
+
countSkillFiles: () => 0,
|
|
50
|
+
writeLastSessionCwd: (cwd, pid) => sentinels.push({ cwd, pid }),
|
|
51
|
+
updateCurrentCwdOverride: (cwd) => overrides.push(cwd),
|
|
52
|
+
persistSession: (value) => persisted.push(structuredClone(value)),
|
|
53
|
+
clean: (value) => String(value || '').trim(),
|
|
54
|
+
resolve,
|
|
55
|
+
statSync,
|
|
56
|
+
existsSync: () => false,
|
|
57
|
+
cfgMod: {},
|
|
58
|
+
STANDALONE_DATA_DIR: root,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
applyResolvedCwd(after, { persistProjectSelection: true });
|
|
62
|
+
|
|
63
|
+
assert.equal(currentCwd, after);
|
|
64
|
+
assert.equal(session.cwd, after);
|
|
65
|
+
assert.deepEqual(desktopSession, { classification: 'project', projectPath: after });
|
|
66
|
+
assert.deepEqual(session.desktopSession, desktopSession);
|
|
67
|
+
assert.equal(persisted.length, 1);
|
|
68
|
+
assert.equal(persisted[0].cwd, after);
|
|
69
|
+
assert.deepEqual(persisted[0].desktopSession, desktopSession);
|
|
70
|
+
assert.deepEqual(sentinels, [{ cwd: after, pid: 4321 }]);
|
|
71
|
+
assert.deepEqual(overrides, [after]);
|
|
72
|
+
} finally {
|
|
73
|
+
rmSync(root, { recursive: true, force: true });
|
|
74
|
+
}
|
|
75
|
+
});
|
|
@@ -14,6 +14,8 @@ test('cwd internal tool stays bound to its caller when another runtime owns the
|
|
|
14
14
|
mkdirSync(cwdA);
|
|
15
15
|
mkdirSync(cwdB);
|
|
16
16
|
mkdirSync(cwdNext);
|
|
17
|
+
const previousProcessCwd = process.env.MIXDOG_SESSION_CWD;
|
|
18
|
+
process.env.MIXDOG_SESSION_CWD = 'process-global-must-not-change';
|
|
17
19
|
|
|
18
20
|
const runtimeA = await createMixdogSessionRuntime({ cwd: cwdA, toolMode: 'full' });
|
|
19
21
|
const runtimeB = await createMixdogSessionRuntime({ cwd: cwdB, toolMode: 'full' });
|
|
@@ -22,6 +24,8 @@ test('cwd internal tool stays bound to its caller when another runtime owns the
|
|
|
22
24
|
runtimeA.close('cwd-routing-test', { waitForExit: false }),
|
|
23
25
|
runtimeB.close('cwd-routing-test', { waitForExit: false }),
|
|
24
26
|
]);
|
|
27
|
+
if (previousProcessCwd === undefined) delete process.env.MIXDOG_SESSION_CWD;
|
|
28
|
+
else process.env.MIXDOG_SESSION_CWD = previousProcessCwd;
|
|
25
29
|
rmSync(root, { recursive: true, force: true });
|
|
26
30
|
});
|
|
27
31
|
|
|
@@ -53,4 +57,5 @@ test('cwd internal tool stays bound to its caller when another runtime owns the
|
|
|
53
57
|
assert.equal(setResult.sessionId, callerSession.id);
|
|
54
58
|
assert.equal(runtimeA.cwd, cwdNext);
|
|
55
59
|
assert.equal(runtimeB.cwd, cwdB);
|
|
60
|
+
assert.equal(process.env.MIXDOG_SESSION_CWD, 'process-global-must-not-change');
|
|
56
61
|
});
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { clean } from './session-text.mjs';
|
|
2
|
+
|
|
3
|
+
function compactDescription(value) {
|
|
4
|
+
return String(value || '')
|
|
5
|
+
.replace(/\s+/g, ' ')
|
|
6
|
+
.replace(/[<>]/g, '')
|
|
7
|
+
.trim()
|
|
8
|
+
.slice(0, 240);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizedDelta(value) {
|
|
12
|
+
const source = value && typeof value === 'object' ? value : {};
|
|
13
|
+
const addedByName = new Map();
|
|
14
|
+
for (const entry of Array.isArray(source.added) ? source.added : []) {
|
|
15
|
+
const name = clean(entry?.name);
|
|
16
|
+
if (name) addedByName.set(name, { name, description: compactDescription(entry?.description) });
|
|
17
|
+
}
|
|
18
|
+
const removed = new Set(
|
|
19
|
+
(Array.isArray(source.removed) ? source.removed : [])
|
|
20
|
+
.map((entry) => clean(typeof entry === 'string' ? entry : entry?.name))
|
|
21
|
+
.filter(Boolean),
|
|
22
|
+
);
|
|
23
|
+
for (const name of addedByName.keys()) removed.delete(name);
|
|
24
|
+
return {
|
|
25
|
+
revision: Math.max(0, Number(source.revision) || 0),
|
|
26
|
+
addedByName,
|
|
27
|
+
removed,
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function mergePendingDeferredToolDelta(session, change = {}) {
|
|
32
|
+
if (!session || typeof session !== 'object') return null;
|
|
33
|
+
const current = normalizedDelta(session.pendingDeferredToolDelta);
|
|
34
|
+
const nextRevision = Math.max(
|
|
35
|
+
current.revision,
|
|
36
|
+
Math.max(0, Number(session.deferredToolDeltaRevision) || 0),
|
|
37
|
+
) + 1;
|
|
38
|
+
for (const entry of Array.isArray(change.added) ? change.added : []) {
|
|
39
|
+
const name = clean(entry?.name);
|
|
40
|
+
if (!name) continue;
|
|
41
|
+
if (current.removed.delete(name)) continue;
|
|
42
|
+
current.addedByName.set(name, {
|
|
43
|
+
name,
|
|
44
|
+
description: compactDescription(entry?.description),
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
for (const entry of Array.isArray(change.removed) ? change.removed : []) {
|
|
48
|
+
const name = clean(typeof entry === 'string' ? entry : entry?.name);
|
|
49
|
+
if (!name) continue;
|
|
50
|
+
if (current.addedByName.delete(name)) continue;
|
|
51
|
+
current.removed.add(name);
|
|
52
|
+
}
|
|
53
|
+
const added = [...current.addedByName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
54
|
+
const removed = [...current.removed].sort();
|
|
55
|
+
if (!added.length && !removed.length) {
|
|
56
|
+
session.deferredToolDeltaRevision = nextRevision;
|
|
57
|
+
delete session.pendingDeferredToolDelta;
|
|
58
|
+
return null;
|
|
59
|
+
}
|
|
60
|
+
session.deferredToolDeltaRevision = nextRevision;
|
|
61
|
+
session.pendingDeferredToolDelta = {
|
|
62
|
+
version: 1,
|
|
63
|
+
type: 'deferred_tools_delta',
|
|
64
|
+
revision: nextRevision,
|
|
65
|
+
added,
|
|
66
|
+
removed,
|
|
67
|
+
};
|
|
68
|
+
session.updatedAt = Date.now();
|
|
69
|
+
return session.pendingDeferredToolDelta;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function snapshotPendingDeferredToolDelta(session) {
|
|
73
|
+
const current = normalizedDelta(session?.pendingDeferredToolDelta);
|
|
74
|
+
const added = [...current.addedByName.values()].sort((a, b) => a.name.localeCompare(b.name));
|
|
75
|
+
const removed = [...current.removed].sort();
|
|
76
|
+
if (!added.length && !removed.length) return null;
|
|
77
|
+
const lines = ['<system-reminder>', '<deferred_tools_delta>'];
|
|
78
|
+
if (added.length) {
|
|
79
|
+
lines.push('Added deferred tools:');
|
|
80
|
+
for (const entry of added) {
|
|
81
|
+
lines.push(entry.description ? `- ${entry.name}: ${entry.description}` : `- ${entry.name}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
if (removed.length) {
|
|
85
|
+
lines.push('Removed deferred tools:');
|
|
86
|
+
for (const name of removed) lines.push(`- ${name}`);
|
|
87
|
+
}
|
|
88
|
+
lines.push('</deferred_tools_delta>', '</system-reminder>');
|
|
89
|
+
return {
|
|
90
|
+
revision: current.revision,
|
|
91
|
+
added,
|
|
92
|
+
removed,
|
|
93
|
+
content: lines.join('\n'),
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function acknowledgePendingDeferredToolDelta(session, revision) {
|
|
98
|
+
if (!session || typeof session !== 'object') return false;
|
|
99
|
+
const current = normalizedDelta(session.pendingDeferredToolDelta);
|
|
100
|
+
if (!current.revision || current.revision !== Number(revision)) return false;
|
|
101
|
+
delete session.pendingDeferredToolDelta;
|
|
102
|
+
session.updatedAt = Date.now();
|
|
103
|
+
return true;
|
|
104
|
+
}
|