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
|
@@ -14,6 +14,11 @@ import { join } from 'path';
|
|
|
14
14
|
// authoritative store uses, so the cold catalog cannot disagree with it.
|
|
15
15
|
import { probePath, readTextFile, PROBE_PRESENT, PROBE_ABSENT } from './store/fs-probe.mjs';
|
|
16
16
|
import { readTopLevelLifecycleRecord, isLifecycleUnreadable } from './lifecycle-scan.mjs';
|
|
17
|
+
import {
|
|
18
|
+
isAgentOnlySession,
|
|
19
|
+
isRootLeadSession,
|
|
20
|
+
sessionVisibility,
|
|
21
|
+
} from './store-summary-visibility.mjs';
|
|
17
22
|
|
|
18
23
|
const SESSION_SUMMARY_INDEX_VERSION = 2;
|
|
19
24
|
const DEAD_AGENT_STATUS =
|
|
@@ -45,7 +50,8 @@ const LEAD_OWNERS = new Set(['cli', 'user', 'mixdog']);
|
|
|
45
50
|
|
|
46
51
|
function isLeadVisibleRow(row) {
|
|
47
52
|
const owner = String(row.owner || 'user').trim().toLowerCase();
|
|
48
|
-
if (
|
|
53
|
+
if (isAgentOnlySession(row)) return false;
|
|
54
|
+
if (!isRootLeadSession(row) && owner && !LEAD_OWNERS.has(owner)) return false;
|
|
49
55
|
// Mirror listLeadSessions: a previewless zero-message row is an unusable
|
|
50
56
|
// scratch (desktop boot leftovers, crashed first turns) — resuming it
|
|
51
57
|
// shows an empty conversation, so the catalog hides it.
|
|
@@ -72,10 +78,35 @@ function positiveNumber(value, fallback = 0) {
|
|
|
72
78
|
return Number.isFinite(number) && number > 0 ? number : fallback;
|
|
73
79
|
}
|
|
74
80
|
|
|
81
|
+
function runtimeAlive(pid) {
|
|
82
|
+
const id = positiveNumber(pid, 0);
|
|
83
|
+
if (id <= 0 || id === process.pid) return true;
|
|
84
|
+
try {
|
|
85
|
+
process.kill(id, 0);
|
|
86
|
+
return true;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
return error?.code === 'EPERM';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
75
92
|
function cleanValue(value) {
|
|
76
93
|
return String(value || '').trim();
|
|
77
94
|
}
|
|
78
95
|
|
|
96
|
+
function workerOwnerSessionId(row, session) {
|
|
97
|
+
return cleanValue(
|
|
98
|
+
row?.ownerSessionId || session?.ownerSessionId
|
|
99
|
+
|| row?.parentSessionId || session?.parentSessionId,
|
|
100
|
+
) || null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function workerParentSessionId(row, session) {
|
|
104
|
+
return cleanValue(
|
|
105
|
+
row?.parentSessionId || session?.parentSessionId
|
|
106
|
+
|| row?.ownerSessionId || session?.ownerSessionId,
|
|
107
|
+
) || null;
|
|
108
|
+
}
|
|
109
|
+
|
|
79
110
|
function archivedAgentNotification(content, sessionId) {
|
|
80
111
|
const text = typeof content === 'string' ? content : '';
|
|
81
112
|
const marker = '\n\nResult:\n';
|
|
@@ -265,23 +296,52 @@ export function storedLeadWorkerIndexPath() {
|
|
|
265
296
|
return join(dataDir(), 'lead-workers.json');
|
|
266
297
|
}
|
|
267
298
|
|
|
268
|
-
|
|
269
|
-
* cross-process running source even when their durable session is detached
|
|
270
|
-
* (`closed`) and a terminal reaper has already removed the worker-index row.
|
|
271
|
-
* The child and Lead indexes remain additive for living rows (running or idle)
|
|
272
|
-
* published before the heartbeat or by runtimes without a sidecar. No runtime
|
|
273
|
-
* starts and durable session history is never projected into either pool. */
|
|
274
|
-
export function listStoredAgentWorkers() {
|
|
299
|
+
function storedAgentWorkerIndexRows() {
|
|
275
300
|
let parsed = null;
|
|
276
301
|
try {
|
|
277
302
|
parsed = JSON.parse(readFileSync(storedAgentWorkerIndexPath(), 'utf8'));
|
|
278
|
-
} catch {
|
|
279
|
-
|
|
303
|
+
} catch {
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
return Array.isArray(parsed?.workers)
|
|
280
307
|
? parsed.workers
|
|
281
308
|
: parsed?.workers && typeof parsed.workers === 'object'
|
|
282
309
|
? Object.values(parsed.workers)
|
|
283
310
|
: [];
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Lightweight ancestry seam used to migrate pre-parentSessionId summary
|
|
314
|
+
* rows. It reads only agent-workers.json and never opens session transcripts. */
|
|
315
|
+
export function listStoredAgentWorkerLinks() {
|
|
316
|
+
return storedAgentWorkerIndexRows()
|
|
317
|
+
.map((row) => {
|
|
318
|
+
if (!row || typeof row !== 'object') return null;
|
|
319
|
+
const sessionId = cleanValue(row.sessionId);
|
|
320
|
+
const parentSessionId = cleanValue(row.parentSessionId);
|
|
321
|
+
const ownerSessionId = cleanValue(row.ownerSessionId || row.parentSessionId);
|
|
322
|
+
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || (!parentSessionId && !ownerSessionId)) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
return {
|
|
326
|
+
sessionId,
|
|
327
|
+
parentSessionId: parentSessionId || ownerSessionId,
|
|
328
|
+
ownerSessionId: ownerSessionId || parentSessionId,
|
|
329
|
+
};
|
|
330
|
+
})
|
|
331
|
+
.filter(Boolean);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** Process-global active agent pool. Fresh child heartbeat sidecars are the
|
|
335
|
+
* cross-process running source even when their durable session is detached
|
|
336
|
+
* (`closed`) and a terminal reaper has already removed the worker-index row.
|
|
337
|
+
* The child and Lead indexes remain additive for living rows (running or idle)
|
|
338
|
+
* published before the heartbeat or by runtimes without a sidecar. No runtime
|
|
339
|
+
* starts and durable session history is never projected into either pool. */
|
|
340
|
+
export function listStoredAgentWorkers() {
|
|
341
|
+
const source = storedAgentWorkerIndexRows();
|
|
284
342
|
const bySessionId = new Map();
|
|
343
|
+
const now = Date.now();
|
|
344
|
+
const heartbeatMtimes = sessionHeartbeatMtimes();
|
|
285
345
|
for (const row of source) {
|
|
286
346
|
if (!row || typeof row !== 'object') continue;
|
|
287
347
|
// Cancelled rows stay visible so the 2-minute heartbeat lease cannot
|
|
@@ -300,28 +360,45 @@ export function listStoredAgentWorkers() {
|
|
|
300
360
|
'utf8',
|
|
301
361
|
));
|
|
302
362
|
} catch { /* a new row may precede its first session save */ }
|
|
363
|
+
const declaredStatus = cleanValue(row.status) || 'running';
|
|
364
|
+
const declaredStage = cleanValue(row.stage || row.status) || 'running';
|
|
365
|
+
const declaredWorking = WORKING_AGENT_STATUS.test(declaredStatus)
|
|
366
|
+
|| WORKING_AGENT_STATUS.test(declaredStage);
|
|
367
|
+
const heartbeatAt = heartbeatMtimes.get(sessionId) || 0;
|
|
368
|
+
const heartbeatFresh = heartbeatAt > 0
|
|
369
|
+
&& now - heartbeatAt <= AGENT_POOL_HEARTBEAT_FRESH_MS;
|
|
370
|
+
const updatedAt = Date.parse(cleanValue(row.updatedAt)) || 0;
|
|
371
|
+
const recentlyUpdated = updatedAt > 0
|
|
372
|
+
&& now - updatedAt <= AGENT_POOL_HEARTBEAT_FRESH_MS;
|
|
373
|
+
const runtimePid = positiveNumber(row.runtimePid, 0);
|
|
374
|
+
const working = declaredWorking
|
|
375
|
+
&& (!runtimePid || runtimeAlive(runtimePid))
|
|
376
|
+
&& (heartbeatFresh || recentlyUpdated);
|
|
377
|
+
const projectedStatus = declaredWorking && !working ? 'idle' : declaredStatus;
|
|
378
|
+
const projectedStage = declaredWorking && !working ? 'idle' : declaredStage;
|
|
303
379
|
bySessionId.set(sessionId, {
|
|
304
380
|
tag,
|
|
305
381
|
sessionId,
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
382
|
+
// Root ownership and immediate ancestry are independent. Nested
|
|
383
|
+
// descendants keep the Lead/root owner while pointing at the Agent
|
|
384
|
+
// session that directly spawned them.
|
|
385
|
+
ownerSessionId: workerOwnerSessionId(row, session),
|
|
386
|
+
parentSessionId: workerParentSessionId(row, session),
|
|
310
387
|
title: cleanValue(row.title || session?.title) || null,
|
|
311
388
|
agent: cleanValue(row.agent || session?.agent) || null,
|
|
312
389
|
provider: cleanValue(row.provider || session?.provider) || null,
|
|
313
390
|
model: cleanValue(row.model || session?.model) || null,
|
|
314
391
|
effort: cleanValue(row.effort || session?.effort) || null,
|
|
315
392
|
fast: row.fast === true || session?.fast === true,
|
|
316
|
-
status:
|
|
317
|
-
stage:
|
|
393
|
+
status: projectedStatus,
|
|
394
|
+
stage: projectedStage,
|
|
318
395
|
startedAt: row.startedAt || row.createdAt || session?.createdAt || null,
|
|
319
|
-
turnStartedAt: row.turnStartedAt || null,
|
|
396
|
+
turnStartedAt: working ? (row.turnStartedAt || null) : null,
|
|
320
397
|
createdAt: row.createdAt || session?.createdAt || null,
|
|
321
398
|
updatedAt: row.updatedAt || session?.updatedAt || null,
|
|
322
399
|
idleSince: frozenIdleSince(
|
|
323
400
|
sessionId,
|
|
324
|
-
|
|
401
|
+
working,
|
|
325
402
|
row.finishedAt || row.updatedAt || session?.updatedAt || null,
|
|
326
403
|
),
|
|
327
404
|
reapAt: row.reapAt || null,
|
|
@@ -330,8 +407,6 @@ export function listStoredAgentWorkers() {
|
|
|
330
407
|
taskId: cleanValue(row.task_id || row.taskId) || null,
|
|
331
408
|
});
|
|
332
409
|
}
|
|
333
|
-
const now = Date.now();
|
|
334
|
-
const heartbeatMtimes = sessionHeartbeatMtimes();
|
|
335
410
|
for (const [sessionId, heartbeatAt] of heartbeatMtimes) {
|
|
336
411
|
if (now - heartbeatAt > AGENT_POOL_HEARTBEAT_FRESH_MS) continue;
|
|
337
412
|
let session;
|
|
@@ -343,13 +418,12 @@ export function listStoredAgentWorkers() {
|
|
|
343
418
|
} catch {
|
|
344
419
|
continue;
|
|
345
420
|
}
|
|
346
|
-
const
|
|
347
|
-
|
|
348
|
-
);
|
|
421
|
+
const current = bySessionId.get(sessionId) || {};
|
|
422
|
+
const ownerSessionId = workerOwnerSessionId(current, session);
|
|
423
|
+
const parentSessionId = workerParentSessionId(current, session);
|
|
349
424
|
const owner = cleanValue(session?.owner).toLowerCase();
|
|
350
425
|
const agent = cleanValue(session?.agent);
|
|
351
426
|
if (!ownerSessionId || (owner !== 'agent' && (!agent || agent === 'lead'))) continue;
|
|
352
|
-
const current = bySessionId.get(sessionId) || {};
|
|
353
427
|
// The durable index row is authoritative for FINISHED work: a worker's
|
|
354
428
|
// runtime unloads at turn end but its heartbeat sidecar stays fresh for
|
|
355
429
|
// up to the 2-minute window, and overwriting an idle row to `running`
|
|
@@ -381,6 +455,7 @@ export function listStoredAgentWorkers() {
|
|
|
381
455
|
|| `${agent || 'agent'}:${sessionId}`,
|
|
382
456
|
sessionId,
|
|
383
457
|
ownerSessionId,
|
|
458
|
+
parentSessionId,
|
|
384
459
|
title: cleanValue(session?.title) || current.title || null,
|
|
385
460
|
agent: agent || current.agent || null,
|
|
386
461
|
provider: cleanValue(session?.provider) || current.provider || null,
|
|
@@ -409,6 +484,7 @@ export function listStoredAgentWorkers() {
|
|
|
409
484
|
|| `${agent || 'agent'}:${sessionId}`,
|
|
410
485
|
sessionId,
|
|
411
486
|
ownerSessionId,
|
|
487
|
+
parentSessionId,
|
|
412
488
|
title: cleanValue(session?.title) || current.title || null,
|
|
413
489
|
agent: agent || current.agent || null,
|
|
414
490
|
provider: cleanValue(session?.provider) || current.provider || null,
|
|
@@ -467,6 +543,7 @@ export function listStoredAgentWorkers() {
|
|
|
467
543
|
tag: `lead:${sessionId}`,
|
|
468
544
|
sessionId,
|
|
469
545
|
ownerSessionId: sessionId,
|
|
546
|
+
parentSessionId: null,
|
|
470
547
|
agent: 'lead',
|
|
471
548
|
provider: cleanValue(row.provider) || null,
|
|
472
549
|
model: cleanValue(row.model) || null,
|
|
@@ -548,7 +625,8 @@ function normalizedRow(row, heartbeatAt = 0) {
|
|
|
548
625
|
sourceName: row.sourceName || null,
|
|
549
626
|
sourceDelivery: row.sourceDelivery || null,
|
|
550
627
|
scopeKey: row.scopeKey || null,
|
|
551
|
-
ownerSessionId: row.ownerSessionId || null,
|
|
628
|
+
ownerSessionId: row.ownerSessionId || row.parentSessionId || null,
|
|
629
|
+
visibility: sessionVisibility(row),
|
|
552
630
|
clientHostPid: positiveNumber(row.clientHostPid, 0) || null,
|
|
553
631
|
cwd: row.cwd || '',
|
|
554
632
|
desktopSession: desktopSession(row.desktopSession, row.cwd),
|
|
@@ -761,11 +839,47 @@ export async function readStoredSessionTranscript(id, options = {}) {
|
|
|
761
839
|
// Same strict authority as the store, and the same fail-closed rule: an
|
|
762
840
|
// absent, unreadable, ambiguous or foreign record yields no transcript.
|
|
763
841
|
const read = readTextFile(join(dataDir(), 'sessions', `${sessionId}.json`));
|
|
764
|
-
if (read.state === PROBE_ABSENT)
|
|
842
|
+
if (read.state === PROBE_ABSENT) {
|
|
843
|
+
return options.metadataOnly === true ? null : readArchivedAgentResult(sessionId);
|
|
844
|
+
}
|
|
765
845
|
if (read.state !== PROBE_PRESENT) return null;
|
|
766
846
|
const record = readTopLevelLifecycleRecord(read.text);
|
|
767
847
|
if (isLifecycleUnreadable(record) || record.id !== sessionId) return null;
|
|
768
848
|
let session = record.doc;
|
|
849
|
+
if (options.metadataOnly === true) {
|
|
850
|
+
return {
|
|
851
|
+
id: sessionId,
|
|
852
|
+
sessionId,
|
|
853
|
+
owner: session.owner || null,
|
|
854
|
+
agent: session.agent || null,
|
|
855
|
+
parentSessionId: session.parentSessionId || null,
|
|
856
|
+
ownerSessionId: session.ownerSessionId || session.parentSessionId || null,
|
|
857
|
+
visibility: sessionVisibility(session),
|
|
858
|
+
agentTag: session.agentTag || null,
|
|
859
|
+
cwd: session.cwd || '',
|
|
860
|
+
provider: session.provider || null,
|
|
861
|
+
model: session.model || null,
|
|
862
|
+
presetName: session.presetName || session.profileId || null,
|
|
863
|
+
effort: session.effort || null,
|
|
864
|
+
fast: session.fast === true,
|
|
865
|
+
modelParameters: session.modelParameters || null,
|
|
866
|
+
taskType: session.taskType || null,
|
|
867
|
+
maxLoopIterations: session.maxLoopIterations ?? null,
|
|
868
|
+
permission: session.permission || null,
|
|
869
|
+
permissionMode: session.permissionMode || null,
|
|
870
|
+
toolPermission: session.toolPermission || null,
|
|
871
|
+
schemaAllowedTools: Array.isArray(session.schemaAllowedTools)
|
|
872
|
+
? session.schemaAllowedTools
|
|
873
|
+
: null,
|
|
874
|
+
sourceType: session.sourceType || null,
|
|
875
|
+
sourceName: session.sourceName || null,
|
|
876
|
+
clientHostPid: session.clientHostPid || null,
|
|
877
|
+
createdAt: session.createdAt || null,
|
|
878
|
+
updatedAt: session.updatedAt || session.lastUsedAt || null,
|
|
879
|
+
status: session.status || (session.closed === true ? 'closed' : 'idle'),
|
|
880
|
+
closed: session.closed === true,
|
|
881
|
+
};
|
|
882
|
+
}
|
|
769
883
|
const owner = cleanValue(session.owner).toLowerCase();
|
|
770
884
|
const agent = cleanValue(session.agent).toLowerCase();
|
|
771
885
|
const liveDetachedAgent = session.closed === true
|
|
@@ -774,11 +888,11 @@ export async function readStoredSessionTranscript(id, options = {}) {
|
|
|
774
888
|
// Only a PROVABLY absent checkpoint skips recovery: an unreadable probe
|
|
775
889
|
// must not silently downgrade an interrupted turn to a plain cold read.
|
|
776
890
|
if (probePath(join(dataDir(), 'turn-checkpoints', `${sessionId}.json`)).state !== PROBE_ABSENT) {
|
|
891
|
+
const {
|
|
892
|
+
projectTurnCheckpointMessages,
|
|
893
|
+
readTurnCheckpoint,
|
|
894
|
+
} = await import('./manager/turn-checkpoint.mjs');
|
|
777
895
|
if (liveDetachedAgent) {
|
|
778
|
-
const {
|
|
779
|
-
projectTurnCheckpointMessages,
|
|
780
|
-
readTurnCheckpoint,
|
|
781
|
-
} = await import('./manager/turn-checkpoint.mjs');
|
|
782
896
|
const checkpoint = readTurnCheckpoint(sessionId);
|
|
783
897
|
if (checkpoint) {
|
|
784
898
|
session = {
|
|
@@ -789,6 +903,19 @@ export async function readStoredSessionTranscript(id, options = {}) {
|
|
|
789
903
|
} else {
|
|
790
904
|
const { recoverSessionAfterProcessRestart } = await import('./manager.mjs');
|
|
791
905
|
session = recoverSessionAfterProcessRestart(sessionId) || session;
|
|
906
|
+
// A live foreign owner is intentionally not recovered/mutated.
|
|
907
|
+
// Project its durable working checkpoint read-only so the cold pane
|
|
908
|
+
// never repaints the stale pre-compaction session.messages while it
|
|
909
|
+
// waits for the owner's live-share frame.
|
|
910
|
+
if (session?.activeTurnCheckpoint) {
|
|
911
|
+
const checkpoint = readTurnCheckpoint(sessionId);
|
|
912
|
+
if (checkpoint) {
|
|
913
|
+
session = {
|
|
914
|
+
...session,
|
|
915
|
+
messages: projectTurnCheckpointMessages(session, checkpoint),
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
}
|
|
792
919
|
}
|
|
793
920
|
}
|
|
794
921
|
const {
|
|
@@ -581,3 +581,67 @@ test('cancel-unconfirmed stays distinguishable from a confirmed cancel under a f
|
|
|
581
581
|
assert.notEqual(row?.status, 'cancelled');
|
|
582
582
|
});
|
|
583
583
|
});
|
|
584
|
+
|
|
585
|
+
test('stored Agent pool preserves root ownership and immediate nested parent links', () => {
|
|
586
|
+
withAgentPoolRoot('mixdog-agent-pool-nested-', (root) => {
|
|
587
|
+
writeAgentChild(root, 'child-direct', {
|
|
588
|
+
ownerSessionId: 'root-lead',
|
|
589
|
+
parentSessionId: 'root-lead',
|
|
590
|
+
tag: 'direct',
|
|
591
|
+
});
|
|
592
|
+
writeAgentChild(root, 'grandchild', {
|
|
593
|
+
ownerSessionId: 'root-lead',
|
|
594
|
+
parentSessionId: 'child-direct',
|
|
595
|
+
tag: 'nested',
|
|
596
|
+
closed: true,
|
|
597
|
+
status: 'closed',
|
|
598
|
+
stage: 'closed',
|
|
599
|
+
});
|
|
600
|
+
writeFileSync(join(root, 'sessions', 'root-lead.json'), JSON.stringify({
|
|
601
|
+
id: 'root-lead',
|
|
602
|
+
owner: 'user',
|
|
603
|
+
ownerSessionId: 'root-lead',
|
|
604
|
+
agent: 'lead',
|
|
605
|
+
status: 'idle',
|
|
606
|
+
messages: [{ role: 'user', content: 'root task' }],
|
|
607
|
+
}));
|
|
608
|
+
writeAgentWorkerIndex(root, {
|
|
609
|
+
direct: {
|
|
610
|
+
tag: 'direct',
|
|
611
|
+
sessionId: 'child-direct',
|
|
612
|
+
ownerSessionId: 'root-lead',
|
|
613
|
+
parentSessionId: 'root-lead',
|
|
614
|
+
agent: 'reviewer',
|
|
615
|
+
status: 'idle',
|
|
616
|
+
stage: 'idle',
|
|
617
|
+
},
|
|
618
|
+
});
|
|
619
|
+
writeFileSync(join(root, 'lead-workers.json'), JSON.stringify({
|
|
620
|
+
workers: {
|
|
621
|
+
root: {
|
|
622
|
+
sessionId: 'root-lead',
|
|
623
|
+
ownerSessionId: 'root-lead',
|
|
624
|
+
agent: 'lead',
|
|
625
|
+
status: 'idle',
|
|
626
|
+
stage: 'idle',
|
|
627
|
+
updatedAt: new Date().toISOString(),
|
|
628
|
+
reapAt: new Date(Date.now() + 60_000).toISOString(),
|
|
629
|
+
},
|
|
630
|
+
},
|
|
631
|
+
}));
|
|
632
|
+
// No worker-index row: the heartbeat/session fallback must carry the same
|
|
633
|
+
// production ancestry shape for a live nested descendant.
|
|
634
|
+
writeFreshHeartbeat(root, 'grandchild');
|
|
635
|
+
|
|
636
|
+
const rows = listStoredAgentWorkers();
|
|
637
|
+
const rootLead = rows.find((row) => row.sessionId === 'root-lead');
|
|
638
|
+
const direct = rows.find((row) => row.sessionId === 'child-direct');
|
|
639
|
+
const nested = rows.find((row) => row.sessionId === 'grandchild');
|
|
640
|
+
assert.equal(rootLead?.ownerSessionId, 'root-lead');
|
|
641
|
+
assert.equal(rootLead?.parentSessionId, null);
|
|
642
|
+
assert.equal(direct?.ownerSessionId, 'root-lead');
|
|
643
|
+
assert.equal(direct?.parentSessionId, 'root-lead');
|
|
644
|
+
assert.equal(nested?.ownerSessionId, 'root-lead');
|
|
645
|
+
assert.equal(nested?.parentSessionId, 'child-direct');
|
|
646
|
+
});
|
|
647
|
+
});
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export const SESSION_VISIBILITY_ORDINARY = 'ordinary';
|
|
2
|
+
export const SESSION_VISIBILITY_AGENT_ONLY = 'agent-only';
|
|
3
|
+
|
|
4
|
+
const SESSION_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
5
|
+
|
|
6
|
+
function clean(value) {
|
|
7
|
+
return String(value || '').trim();
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function linkedParentSessionId(value) {
|
|
11
|
+
if (!value || typeof value !== 'object') return '';
|
|
12
|
+
for (const candidate of [value.ownerSessionId, value.parentSessionId]) {
|
|
13
|
+
const id = clean(candidate);
|
|
14
|
+
if (SESSION_ID_PATTERN.test(id)) return id;
|
|
15
|
+
}
|
|
16
|
+
return '';
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** A root Lead may carry a self owner link, but it is not a child session. */
|
|
20
|
+
export function isRootLeadSession(value) {
|
|
21
|
+
if (!value || typeof value !== 'object') return false;
|
|
22
|
+
if (clean(value.agent).toLowerCase() !== 'lead') return false;
|
|
23
|
+
const id = clean(value.id);
|
|
24
|
+
const parentId = linkedParentSessionId(value);
|
|
25
|
+
return !parentId || parentId === id;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Backward-compatible classification for child records written before the
|
|
29
|
+
* durable visibility field existed. Both Agent ownership and an external,
|
|
30
|
+
* valid parent link are required so ownerless/root sessions remain ordinary. */
|
|
31
|
+
export function isCanonicalAgentChildSession(value) {
|
|
32
|
+
if (!value || typeof value !== 'object') return false;
|
|
33
|
+
const id = clean(value.id);
|
|
34
|
+
const parentId = linkedParentSessionId(value);
|
|
35
|
+
return clean(value.owner).toLowerCase() === 'agent'
|
|
36
|
+
&& Boolean(parentId)
|
|
37
|
+
&& parentId !== id;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function sessionVisibility(value) {
|
|
41
|
+
// Root Lead sessions are ordinary catalog entries even if stale metadata
|
|
42
|
+
// accidentally retained an agent-only declaration.
|
|
43
|
+
if (isRootLeadSession(value)) return SESSION_VISIBILITY_ORDINARY;
|
|
44
|
+
const declared = clean(value?.visibility || value?.sessionVisibility).toLowerCase();
|
|
45
|
+
if (declared === SESSION_VISIBILITY_AGENT_ONLY || isCanonicalAgentChildSession(value)) {
|
|
46
|
+
return SESSION_VISIBILITY_AGENT_ONLY;
|
|
47
|
+
}
|
|
48
|
+
return SESSION_VISIBILITY_ORDINARY;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function isAgentOnlySession(value) {
|
|
52
|
+
return sessionVisibility(value) === SESSION_VISIBILITY_AGENT_ONLY;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export function isOrdinarySession(value) {
|
|
56
|
+
return !isAgentOnlySession(value);
|
|
57
|
+
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import assert from 'node:assert/strict';
|
|
2
|
+
import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } 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
|
+
_normalizeSummaryIndex,
|
|
9
|
+
_sessionSummary,
|
|
10
|
+
} from './store-summary-index.mjs';
|
|
11
|
+
import {
|
|
12
|
+
listStoredAgentWorkerLinks,
|
|
13
|
+
listStoredAgentWorkers,
|
|
14
|
+
listStoredSessionSummaries as listOrdinarySummaries,
|
|
15
|
+
readStoredSessionTranscript,
|
|
16
|
+
storedSessionExists,
|
|
17
|
+
} from './store-summary-reader.mjs';
|
|
18
|
+
import {
|
|
19
|
+
listStoredSessions,
|
|
20
|
+
listStoredSessionSummaries as listInternalSummaries,
|
|
21
|
+
} from './store/listing.mjs';
|
|
22
|
+
|
|
23
|
+
function session(id, extra = {}) {
|
|
24
|
+
return {
|
|
25
|
+
id,
|
|
26
|
+
createdAt: 1_000,
|
|
27
|
+
updatedAt: 2_000,
|
|
28
|
+
lastUsedAt: 2_000,
|
|
29
|
+
status: 'idle',
|
|
30
|
+
owner: 'user',
|
|
31
|
+
messages: [{ role: 'user', content: `prompt for ${id}` }],
|
|
32
|
+
...extra,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test('summary visibility is durable and classifies legacy Agent-owned children', () => {
|
|
37
|
+
const legacy = session('legacy-child', {
|
|
38
|
+
owner: 'agent',
|
|
39
|
+
parentSessionId: 'root-lead',
|
|
40
|
+
agent: 'reviewer',
|
|
41
|
+
});
|
|
42
|
+
const summary = _sessionSummary(legacy);
|
|
43
|
+
assert.equal(summary.ownerSessionId, 'root-lead');
|
|
44
|
+
assert.equal(summary.visibility, 'agent-only');
|
|
45
|
+
|
|
46
|
+
const [normalized] = _normalizeSummaryIndex({
|
|
47
|
+
version: 2,
|
|
48
|
+
rows: [{
|
|
49
|
+
...summary,
|
|
50
|
+
visibility: undefined,
|
|
51
|
+
ownerSessionId: 'root-lead',
|
|
52
|
+
}],
|
|
53
|
+
}).rows;
|
|
54
|
+
assert.equal(normalized.visibility, 'agent-only');
|
|
55
|
+
|
|
56
|
+
const nested = _sessionSummary(session('nested-child', {
|
|
57
|
+
owner: 'agent',
|
|
58
|
+
parentSessionId: 'parent-child',
|
|
59
|
+
ownerSessionId: 'root-lead',
|
|
60
|
+
agent: 'reviewer',
|
|
61
|
+
visibility: 'agent-only',
|
|
62
|
+
provider: 'test-provider',
|
|
63
|
+
model: 'test-model',
|
|
64
|
+
presetName: 'Test route',
|
|
65
|
+
effort: 'high',
|
|
66
|
+
fast: true,
|
|
67
|
+
modelParameters: { temperature: 0.2 },
|
|
68
|
+
permissionMode: 'strict',
|
|
69
|
+
schemaAllowedTools: ['read', 'grep'],
|
|
70
|
+
taskType: 'review',
|
|
71
|
+
maxLoopIterations: 7,
|
|
72
|
+
}));
|
|
73
|
+
const [normalizedNested] = _normalizeSummaryIndex({
|
|
74
|
+
version: 2,
|
|
75
|
+
rows: [nested],
|
|
76
|
+
}).rows;
|
|
77
|
+
assert.equal(normalizedNested.parentSessionId, 'parent-child');
|
|
78
|
+
assert.equal(normalizedNested.ownerSessionId, 'root-lead');
|
|
79
|
+
assert.equal(normalizedNested.presetName, 'Test route');
|
|
80
|
+
assert.equal(normalizedNested.effort, 'high');
|
|
81
|
+
assert.equal(normalizedNested.fast, true);
|
|
82
|
+
assert.deepEqual(normalizedNested.modelParameters, { temperature: 0.2 });
|
|
83
|
+
assert.equal(normalizedNested.permissionMode, 'strict');
|
|
84
|
+
assert.deepEqual(normalizedNested.schemaAllowedTools, ['read', 'grep']);
|
|
85
|
+
assert.equal(normalizedNested.taskType, 'review');
|
|
86
|
+
assert.equal(normalizedNested.maxLoopIterations, 7);
|
|
87
|
+
|
|
88
|
+
const rootLead = _sessionSummary(session('root-lead', {
|
|
89
|
+
owner: 'agent',
|
|
90
|
+
ownerSessionId: 'root-lead',
|
|
91
|
+
agent: 'lead',
|
|
92
|
+
visibility: 'agent-only',
|
|
93
|
+
}));
|
|
94
|
+
assert.equal(rootLead.visibility, 'ordinary');
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('ordinary catalogs hide durable and legacy children while Agent and exact-ID discovery remain available', async () => {
|
|
98
|
+
const root = mkdtempSync(join(tmpdir(), 'mixdog-agent-only-catalog-'));
|
|
99
|
+
const previous = process.env.MIXDOG_DATA_DIR;
|
|
100
|
+
process.env.MIXDOG_DATA_DIR = root;
|
|
101
|
+
try {
|
|
102
|
+
const sessionsDir = join(root, 'sessions');
|
|
103
|
+
mkdirSync(sessionsDir);
|
|
104
|
+
const records = [
|
|
105
|
+
session('root-lead', {
|
|
106
|
+
owner: 'agent',
|
|
107
|
+
ownerSessionId: 'root-lead',
|
|
108
|
+
agent: 'lead',
|
|
109
|
+
sourceType: 'lead',
|
|
110
|
+
}),
|
|
111
|
+
session('ordinary-user'),
|
|
112
|
+
session('declared-child', {
|
|
113
|
+
ownerSessionId: 'root-lead',
|
|
114
|
+
agent: 'reviewer',
|
|
115
|
+
visibility: 'agent-only',
|
|
116
|
+
}),
|
|
117
|
+
session('legacy-child', {
|
|
118
|
+
owner: 'agent',
|
|
119
|
+
parentSessionId: 'root-lead',
|
|
120
|
+
agent: 'reviewer',
|
|
121
|
+
agentTag: 'review',
|
|
122
|
+
closed: true,
|
|
123
|
+
status: 'closed',
|
|
124
|
+
}),
|
|
125
|
+
];
|
|
126
|
+
for (const record of records) {
|
|
127
|
+
writeFileSync(
|
|
128
|
+
join(sessionsDir, `${record.id}.json`),
|
|
129
|
+
JSON.stringify(record),
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const indexRows = records.map((record) => _sessionSummary(record));
|
|
134
|
+
delete indexRows.find((row) => row.id === 'legacy-child').visibility;
|
|
135
|
+
writeFileSync(join(root, 'session-summaries.json'), JSON.stringify({
|
|
136
|
+
version: 2,
|
|
137
|
+
updatedAt: Date.now(),
|
|
138
|
+
rows: indexRows,
|
|
139
|
+
}));
|
|
140
|
+
writeFileSync(join(root, 'agent-workers.json'), JSON.stringify({
|
|
141
|
+
workers: {
|
|
142
|
+
review: {
|
|
143
|
+
tag: 'review',
|
|
144
|
+
sessionId: 'legacy-child',
|
|
145
|
+
parentSessionId: 'root-lead',
|
|
146
|
+
ownerSessionId: 'root-lead',
|
|
147
|
+
agent: 'reviewer',
|
|
148
|
+
status: 'idle',
|
|
149
|
+
stage: 'idle',
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
}));
|
|
153
|
+
|
|
154
|
+
const expectedOrdinaryIds = ['ordinary-user', 'root-lead'];
|
|
155
|
+
assert.deepEqual(
|
|
156
|
+
listOrdinarySummaries({ rebuildIfMissing: false })
|
|
157
|
+
.map((row) => row.id)
|
|
158
|
+
.sort(),
|
|
159
|
+
expectedOrdinaryIds,
|
|
160
|
+
);
|
|
161
|
+
assert.deepEqual(
|
|
162
|
+
listOrdinarySummaries({ refreshFromStorage: true })
|
|
163
|
+
.map((row) => row.id)
|
|
164
|
+
.sort(),
|
|
165
|
+
expectedOrdinaryIds,
|
|
166
|
+
);
|
|
167
|
+
assert.deepEqual(
|
|
168
|
+
listStoredSessions().map((row) => row.id).sort(),
|
|
169
|
+
expectedOrdinaryIds,
|
|
170
|
+
);
|
|
171
|
+
assert.deepEqual(
|
|
172
|
+
listStoredSessions({ includeAgentOnly: true }).map((row) => row.id).sort(),
|
|
173
|
+
records.map((row) => row.id).sort(),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
const internal = listInternalSummaries({ refreshFromStorage: true });
|
|
177
|
+
assert.deepEqual(
|
|
178
|
+
internal.map((row) => row.id).sort(),
|
|
179
|
+
records.map((row) => row.id).sort(),
|
|
180
|
+
);
|
|
181
|
+
assert.equal(
|
|
182
|
+
internal.find((row) => row.id === 'declared-child')?.visibility,
|
|
183
|
+
'agent-only',
|
|
184
|
+
);
|
|
185
|
+
assert.equal(
|
|
186
|
+
internal.find((row) => row.id === 'legacy-child')?.visibility,
|
|
187
|
+
'agent-only',
|
|
188
|
+
);
|
|
189
|
+
const durableIndex = JSON.parse(readFileSync(
|
|
190
|
+
join(root, 'session-summaries.json'),
|
|
191
|
+
'utf8',
|
|
192
|
+
));
|
|
193
|
+
assert.equal(
|
|
194
|
+
durableIndex.rows.find((row) => row.id === 'legacy-child')?.visibility,
|
|
195
|
+
'agent-only',
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
assert.equal(
|
|
199
|
+
listStoredAgentWorkers().some((row) => row.sessionId === 'legacy-child'),
|
|
200
|
+
true,
|
|
201
|
+
);
|
|
202
|
+
assert.deepEqual(listStoredAgentWorkerLinks(), [{
|
|
203
|
+
sessionId: 'legacy-child',
|
|
204
|
+
parentSessionId: 'root-lead',
|
|
205
|
+
ownerSessionId: 'root-lead',
|
|
206
|
+
}]);
|
|
207
|
+
assert.equal(storedSessionExists('legacy-child'), true);
|
|
208
|
+
const metadata = await readStoredSessionTranscript('legacy-child', { metadataOnly: true });
|
|
209
|
+
assert.equal(metadata?.parentSessionId, 'root-lead');
|
|
210
|
+
assert.equal(Object.hasOwn(metadata || {}, 'items'), false);
|
|
211
|
+
const exact = await readStoredSessionTranscript('legacy-child');
|
|
212
|
+
assert.equal(exact?.sessionId, 'legacy-child');
|
|
213
|
+
assert.equal(exact?.readOnlyDetachedAgent, true);
|
|
214
|
+
} finally {
|
|
215
|
+
if (previous === undefined) delete process.env.MIXDOG_DATA_DIR;
|
|
216
|
+
else process.env.MIXDOG_DATA_DIR = previous;
|
|
217
|
+
rmSync(root, { recursive: true, force: true });
|
|
218
|
+
}
|
|
219
|
+
});
|