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
|
@@ -20,10 +20,21 @@ import {
|
|
|
20
20
|
materializePromptSubmission,
|
|
21
21
|
preparePromptSubmissionForProvider,
|
|
22
22
|
} from '../runtime/attachments/store.mjs';
|
|
23
|
-
import {
|
|
23
|
+
import {
|
|
24
|
+
cancelBackgroundTasks,
|
|
25
|
+
hasActiveBackgroundTasks,
|
|
26
|
+
} from '../runtime/shared/background-tasks.mjs';
|
|
24
27
|
|
|
25
28
|
const MAX_CLONE_DEPTH = 24;
|
|
29
|
+
const SESSION_ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
26
30
|
const requireDesktopService = createRequire(import.meta.url);
|
|
31
|
+
const EXTERNAL_SESSION_ACTIONS = new Set([
|
|
32
|
+
...SESSION_READ_ACTION_SET,
|
|
33
|
+
...SESSION_CONFIGURE_ACTION_SET,
|
|
34
|
+
'submitAsync',
|
|
35
|
+
'abort',
|
|
36
|
+
'resolveToolApproval',
|
|
37
|
+
]);
|
|
27
38
|
|
|
28
39
|
async function loadDesktopServiceModule(moduleUrl) {
|
|
29
40
|
const parsed = new URL(moduleUrl);
|
|
@@ -97,6 +108,11 @@ export function createSessionService({
|
|
|
97
108
|
createSessionRuntime = null,
|
|
98
109
|
sessionExists = null,
|
|
99
110
|
readStoredSession = null,
|
|
111
|
+
readStoredGoal = null,
|
|
112
|
+
listStoredActiveGoalSessionIds = null,
|
|
113
|
+
subscribeExternalSessionStates = null,
|
|
114
|
+
invokeExternalSessionAction = null,
|
|
115
|
+
readExternalSessionState = null,
|
|
100
116
|
listSessions = null,
|
|
101
117
|
getRemoteSessionState = null,
|
|
102
118
|
desktopRuntime = null,
|
|
@@ -123,6 +139,15 @@ export function createSessionService({
|
|
|
123
139
|
// addressed by clients; sessionId is the only identity outside this module.
|
|
124
140
|
const sessions = new Set();
|
|
125
141
|
const sessionsById = new Map();
|
|
142
|
+
// Agent children are catalog metadata over ordinary daemon-owned sessions.
|
|
143
|
+
// Their transcript/execution state remains exclusively in `sessionsById`;
|
|
144
|
+
// this index carries only the Parent–Child relationship and public Agent
|
|
145
|
+
// routing fields needed before/after a turn.
|
|
146
|
+
const agentSessions = new Map();
|
|
147
|
+
const agentChildren = new Map();
|
|
148
|
+
const agentCancelRuns = new Map();
|
|
149
|
+
let agentRehydrated = false;
|
|
150
|
+
let agentRehydratePromise = null;
|
|
126
151
|
let busyEntries = 0;
|
|
127
152
|
// Desktop adapters are keyed by their exact module URL so a dev preview and
|
|
128
153
|
// installed build never share module state accidentally.
|
|
@@ -138,7 +163,7 @@ export function createSessionService({
|
|
|
138
163
|
// destroying anything, this sweep is the ONLY reclaim path besides shutdown.
|
|
139
164
|
const IDLE_EVICT_MS = Number(idleEvictMs) > 0
|
|
140
165
|
? Number(idleEvictMs)
|
|
141
|
-
: Math.max(60_000, Number(process.env.MIXDOG_SESSION_IDLE_EVICT_MS) ||
|
|
166
|
+
: Math.max(60_000, Number(process.env.MIXDOG_SESSION_IDLE_EVICT_MS) || 5 * 60_000);
|
|
142
167
|
const EVICT_SWEEP_MS = Number(evictSweepMs) > 0 ? Number(evictSweepMs) : 30_000;
|
|
143
168
|
let evictTimer = null;
|
|
144
169
|
|
|
@@ -170,6 +195,7 @@ export function createSessionService({
|
|
|
170
195
|
// the entry the moment one materializes, so its live frames start flowing
|
|
171
196
|
// without a second subscribe round-trip.
|
|
172
197
|
const pendingViewers = new Map(); // sessionId -> Set<clientToken>
|
|
198
|
+
const externalViewEntries = new Map(); // sessionId -> ordinary projected entry
|
|
173
199
|
|
|
174
200
|
function trackPendingViewer(sessionId, ctx) {
|
|
175
201
|
const token = subscriberToken(ctx);
|
|
@@ -216,6 +242,10 @@ export function createSessionService({
|
|
|
216
242
|
startEvictionSweep();
|
|
217
243
|
log(`session ${currentSessionId(entry) || '(creating)'} unwatched (client ${token} gone) — retained`);
|
|
218
244
|
}
|
|
245
|
+
for (const [sessionId, entry] of [...externalViewEntries]) {
|
|
246
|
+
entry.subscribers?.delete(token);
|
|
247
|
+
if ((entry.subscribers?.size || 0) === 0) externalViewEntries.delete(sessionId);
|
|
248
|
+
}
|
|
219
249
|
for (const service of desktopServicesById.values()) {
|
|
220
250
|
service.subscribers.delete(token);
|
|
221
251
|
}
|
|
@@ -428,11 +458,22 @@ export function createSessionService({
|
|
|
428
458
|
if (entry) entry.indexedSessionId = '';
|
|
429
459
|
return '';
|
|
430
460
|
}
|
|
461
|
+
// External agent projections use the same frame machinery but are not a
|
|
462
|
+
// daemon execution owner. Keeping them out of sessionsById lets a later
|
|
463
|
+
// ordinary session materialization adopt the viewers and take authority.
|
|
464
|
+
if (entry.externalView === true) return nextId;
|
|
431
465
|
const existing = sessionsById.get(nextId);
|
|
432
466
|
if (existing && existing !== entry && !existing.disposed) {
|
|
433
467
|
// Do not redirect an established address to a second session runtime. The load
|
|
434
468
|
throw new Error(`duplicate session address: ${nextId}`);
|
|
435
469
|
}
|
|
470
|
+
const external = externalViewEntries.get(nextId);
|
|
471
|
+
if (external) {
|
|
472
|
+
externalViewEntries.delete(nextId);
|
|
473
|
+
for (const token of external.subscribers || []) {
|
|
474
|
+
addSubscriber(entry, { clientToken: token });
|
|
475
|
+
}
|
|
476
|
+
}
|
|
436
477
|
sessionsById.set(nextId, entry);
|
|
437
478
|
entry.indexedSessionId = nextId;
|
|
438
479
|
return nextId;
|
|
@@ -460,6 +501,72 @@ export function createSessionService({
|
|
|
460
501
|
}, entry.subscribers);
|
|
461
502
|
}
|
|
462
503
|
|
|
504
|
+
function externalEntryForView(sessionId) {
|
|
505
|
+
return externalViewEntries.get(String(sessionId || '')) || null;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function publishExternalSessionState(update) {
|
|
509
|
+
const sessionId = String(update?.sessionId || '');
|
|
510
|
+
const snapshot = update?.snapshot;
|
|
511
|
+
if (!sessionId || !snapshot || typeof snapshot !== 'object') return;
|
|
512
|
+
// A daemon-owned runtime is the canonical owner if this address was
|
|
513
|
+
// materialized. External agent projection frames can arrive one tick late
|
|
514
|
+
// after that promotion and must not overwrite it.
|
|
515
|
+
if (sessionOwner(sessionId)) return;
|
|
516
|
+
let entry = externalViewEntries.get(sessionId);
|
|
517
|
+
if (!entry) {
|
|
518
|
+
if (!pendingViewers.has(sessionId)) return;
|
|
519
|
+
let state = { ...snapshot, sessionId };
|
|
520
|
+
const runtime = {
|
|
521
|
+
isWireSafe: true,
|
|
522
|
+
externalAction: typeof invokeExternalSessionAction === 'function',
|
|
523
|
+
getState: () => state,
|
|
524
|
+
setState: (next) => { state = next; },
|
|
525
|
+
};
|
|
526
|
+
Object.defineProperties(runtime, {
|
|
527
|
+
id: { get: () => sessionId },
|
|
528
|
+
provider: { get: () => String(state.provider || '') },
|
|
529
|
+
model: { get: () => String(state.model || '') },
|
|
530
|
+
session: {
|
|
531
|
+
get: () => ({
|
|
532
|
+
id: sessionId,
|
|
533
|
+
provider: String(state.provider || ''),
|
|
534
|
+
model: String(state.model || ''),
|
|
535
|
+
}),
|
|
536
|
+
},
|
|
537
|
+
});
|
|
538
|
+
if (typeof invokeExternalSessionAction === 'function') {
|
|
539
|
+
for (const name of EXTERNAL_SESSION_ACTIONS) {
|
|
540
|
+
runtime[name] = (...args) => invokeExternalSessionAction(sessionId, name, args);
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
entry = {
|
|
544
|
+
runtime,
|
|
545
|
+
subscribers: new Set(),
|
|
546
|
+
disposed: false,
|
|
547
|
+
timer: null,
|
|
548
|
+
lastPublishedAt: 0,
|
|
549
|
+
publishedSessionId: '',
|
|
550
|
+
indexedSessionId: '',
|
|
551
|
+
addressedSessionId: sessionId,
|
|
552
|
+
revision: revisionEpoch,
|
|
553
|
+
busy: null,
|
|
554
|
+
externalView: true,
|
|
555
|
+
};
|
|
556
|
+
externalViewEntries.set(sessionId, entry);
|
|
557
|
+
adoptPendingViewers(entry, sessionId);
|
|
558
|
+
} else {
|
|
559
|
+
entry.runtime.setState({ ...snapshot, sessionId });
|
|
560
|
+
}
|
|
561
|
+
const step = advance(entry);
|
|
562
|
+
if (step.changed) publishStep(entry, step);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const unsubscribeExternalSessionStates =
|
|
566
|
+
typeof subscribeExternalSessionStates === 'function'
|
|
567
|
+
? subscribeExternalSessionStates(publishExternalSessionState)
|
|
568
|
+
: () => {};
|
|
569
|
+
|
|
463
570
|
/** Response body for the CALLER, which announced the revision it holds. */
|
|
464
571
|
function bodyForClient(step, baseRevision) {
|
|
465
572
|
if (!step.changed && baseRevision === step.revision) return { revision: step.revision };
|
|
@@ -522,9 +629,13 @@ export function createSessionService({
|
|
|
522
629
|
cwd: params.cwd || process.cwd(),
|
|
523
630
|
provider: params.provider,
|
|
524
631
|
model: params.model,
|
|
632
|
+
effort: params.effort,
|
|
633
|
+
fast: params.fast,
|
|
634
|
+
modelParameters: params.modelParameters,
|
|
525
635
|
toolMode: params.toolMode || 'full',
|
|
526
636
|
remote: params.remote === true,
|
|
527
637
|
desktopSession: params.desktopSession ?? null,
|
|
638
|
+
sessionProfile: params.sessionProfile ?? null,
|
|
528
639
|
});
|
|
529
640
|
const entry = {
|
|
530
641
|
runtime, cwd: params.cwd || process.cwd(), timer: null, disposed: false,
|
|
@@ -595,11 +706,64 @@ export function createSessionService({
|
|
|
595
706
|
return entry;
|
|
596
707
|
}
|
|
597
708
|
|
|
709
|
+
/** Bind a retained external Agent snapshot into the ordinary view entry
|
|
710
|
+
* without materializing a daemon runtime. publishExternalSessionState
|
|
711
|
+
* adopts pending viewers so a completed turn is visible immediately. */
|
|
712
|
+
async function bindExternalSessionView(sessionId) {
|
|
713
|
+
const id = String(sessionId || '');
|
|
714
|
+
if (!id) return null;
|
|
715
|
+
const owner = sessionOwner(id);
|
|
716
|
+
if (owner) return owner;
|
|
717
|
+
const existing = externalEntryForView(id);
|
|
718
|
+
if (existing) return existing;
|
|
719
|
+
if (typeof readExternalSessionState !== 'function') return null;
|
|
720
|
+
|
|
721
|
+
// Register the address before the retained-snapshot read. Even a
|
|
722
|
+
// synchronous reader crosses an `await` boundary, so a newer external
|
|
723
|
+
// frame can otherwise arrive in that microtask, find no pending viewer,
|
|
724
|
+
// and be dropped before the stale retained snapshot is bound.
|
|
725
|
+
const placeholder = new Set();
|
|
726
|
+
const ownsPlaceholder = !pendingViewers.has(id);
|
|
727
|
+
if (ownsPlaceholder) pendingViewers.set(id, placeholder);
|
|
728
|
+
const clearPlaceholder = () => {
|
|
729
|
+
if (ownsPlaceholder
|
|
730
|
+
&& pendingViewers.get(id) === placeholder
|
|
731
|
+
&& placeholder.size === 0) {
|
|
732
|
+
pendingViewers.delete(id);
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
let snapshot = null;
|
|
737
|
+
try {
|
|
738
|
+
snapshot = await readExternalSessionState(id);
|
|
739
|
+
} catch (error) {
|
|
740
|
+
log(`external session state read failed session=${id}: ${error?.message || error}`);
|
|
741
|
+
const raced = sessionOwner(id) || externalEntryForView(id);
|
|
742
|
+
clearPlaceholder();
|
|
743
|
+
return raced;
|
|
744
|
+
}
|
|
745
|
+
const raced = sessionOwner(id) || externalEntryForView(id);
|
|
746
|
+
if (raced) {
|
|
747
|
+
clearPlaceholder();
|
|
748
|
+
return raced;
|
|
749
|
+
}
|
|
750
|
+
if (!snapshot || typeof snapshot !== 'object') {
|
|
751
|
+
clearPlaceholder();
|
|
752
|
+
return null;
|
|
753
|
+
}
|
|
754
|
+
publishExternalSessionState({ sessionId: id, snapshot });
|
|
755
|
+
const bound = sessionOwner(id) || externalEntryForView(id);
|
|
756
|
+
if (!bound) clearPlaceholder();
|
|
757
|
+
return bound;
|
|
758
|
+
}
|
|
759
|
+
|
|
598
760
|
/** The runtime hosting sessionId: the existing owner, or a fresh load. One
|
|
599
761
|
* load per session at a time — concurrent panes converge on one session runtime. */
|
|
600
762
|
async function entryForSession(sessionId, hints = {}) {
|
|
601
763
|
const owner = sessionOwner(sessionId);
|
|
602
764
|
if (owner) return owner;
|
|
765
|
+
const external = await bindExternalSessionView(sessionId);
|
|
766
|
+
if (external?.runtime?.externalAction === true) return external;
|
|
603
767
|
const inFlight = sessionLoads.get(sessionId);
|
|
604
768
|
if (inFlight) return inFlight;
|
|
605
769
|
let loading;
|
|
@@ -646,9 +810,19 @@ export function createSessionService({
|
|
|
646
810
|
return null;
|
|
647
811
|
}
|
|
648
812
|
if (!snapshot || typeof snapshot !== 'object') return null;
|
|
813
|
+
let goal;
|
|
814
|
+
if (typeof readStoredGoal === 'function') {
|
|
815
|
+
try {
|
|
816
|
+
goal = await readStoredGoal(sessionId) ?? null;
|
|
817
|
+
} catch (err) {
|
|
818
|
+
log(`stored Goal projection failed session=${sessionId}: ${err?.message || err}`);
|
|
819
|
+
goal = null;
|
|
820
|
+
}
|
|
821
|
+
}
|
|
649
822
|
return sanitizeForWire({
|
|
650
823
|
...snapshot,
|
|
651
824
|
sessionId,
|
|
825
|
+
...(typeof readStoredGoal === 'function' ? { goal } : {}),
|
|
652
826
|
queued: Array.isArray(snapshot.queued) ? snapshot.queued : [],
|
|
653
827
|
});
|
|
654
828
|
}
|
|
@@ -744,7 +918,12 @@ export function createSessionService({
|
|
|
744
918
|
if (typeof listSessions !== 'function') {
|
|
745
919
|
throw new Error('session catalog is unavailable');
|
|
746
920
|
}
|
|
747
|
-
const sessions = await listSessions(
|
|
921
|
+
const sessions = await listSessions({
|
|
922
|
+
...(options || {}),
|
|
923
|
+
// Agent-only records are an internal ancestry/reuse source, never part
|
|
924
|
+
// of the ordinary session catalog returned over the public transport.
|
|
925
|
+
includeAgentOnly: false,
|
|
926
|
+
});
|
|
748
927
|
const remoteSession = typeof getRemoteSessionState === 'function'
|
|
749
928
|
? await getRemoteSessionState()
|
|
750
929
|
: null;
|
|
@@ -911,7 +1090,9 @@ export function createSessionService({
|
|
|
911
1090
|
}
|
|
912
1091
|
const id = String(sessionId || '');
|
|
913
1092
|
if (!id) throw new TypeError('sessionId is required');
|
|
914
|
-
const live = await liveEntryForView(id)
|
|
1093
|
+
const live = await liveEntryForView(id)
|
|
1094
|
+
|| externalEntryForView(id)
|
|
1095
|
+
|| await bindExternalSessionView(id);
|
|
915
1096
|
if (live) {
|
|
916
1097
|
const step = advance(live);
|
|
917
1098
|
retainUnwatched(live, 'headless session read');
|
|
@@ -922,7 +1103,7 @@ export function createSessionService({
|
|
|
922
1103
|
}
|
|
923
1104
|
if (typeof readStoredSession === 'function') {
|
|
924
1105
|
const projection = await storedSessionProjection(id, openHints);
|
|
925
|
-
const lateOwner = sessionOwner(id);
|
|
1106
|
+
const lateOwner = sessionOwner(id) || externalEntryForView(id);
|
|
926
1107
|
if (lateOwner) {
|
|
927
1108
|
const step = advance(lateOwner);
|
|
928
1109
|
retainUnwatched(lateOwner, 'headless session read');
|
|
@@ -952,7 +1133,9 @@ export function createSessionService({
|
|
|
952
1133
|
) {
|
|
953
1134
|
const id = String(sessionId || '');
|
|
954
1135
|
if (!id) throw new TypeError('sessionId is required');
|
|
955
|
-
const live = await liveEntryForView(id)
|
|
1136
|
+
const live = await liveEntryForView(id)
|
|
1137
|
+
|| externalEntryForView(id)
|
|
1138
|
+
|| await bindExternalSessionView(id);
|
|
956
1139
|
if (live) {
|
|
957
1140
|
addSubscriber(live, ctx);
|
|
958
1141
|
const step = advance(live);
|
|
@@ -964,7 +1147,7 @@ export function createSessionService({
|
|
|
964
1147
|
// guarantees either adoption or the live re-check below.
|
|
965
1148
|
trackPendingViewer(id, ctx);
|
|
966
1149
|
const projection = await storedSessionProjection(id, openHints);
|
|
967
|
-
const lateOwner = sessionOwner(id);
|
|
1150
|
+
const lateOwner = sessionOwner(id) || externalEntryForView(id);
|
|
968
1151
|
if (lateOwner) {
|
|
969
1152
|
dropPendingViewer(id, ctx);
|
|
970
1153
|
addSubscriber(lateOwner, ctx);
|
|
@@ -988,7 +1171,15 @@ export function createSessionService({
|
|
|
988
1171
|
if (!id) throw new TypeError('sessionId is required');
|
|
989
1172
|
dropPendingViewer(id, ctx);
|
|
990
1173
|
const owner = sessionOwner(id);
|
|
991
|
-
if (!owner)
|
|
1174
|
+
if (!owner) {
|
|
1175
|
+
const external = externalEntryForView(id);
|
|
1176
|
+
if (external) {
|
|
1177
|
+
const token = subscriberToken(ctx);
|
|
1178
|
+
if (token) external.subscribers?.delete(token);
|
|
1179
|
+
if ((external.subscribers?.size || 0) === 0) externalViewEntries.delete(id);
|
|
1180
|
+
}
|
|
1181
|
+
return { sessionId: id, unsubscribed: true };
|
|
1182
|
+
}
|
|
992
1183
|
const entry = owner;
|
|
993
1184
|
const token = subscriberToken(ctx);
|
|
994
1185
|
if (token) entry.subscribers?.delete(token);
|
|
@@ -1022,7 +1213,18 @@ export function createSessionService({
|
|
|
1022
1213
|
// Await intake only: submitAsync resolves once the prompt is represented by
|
|
1023
1214
|
// the queue/user row, while provider execution remains daemon-owned and
|
|
1024
1215
|
// detached.
|
|
1025
|
-
const
|
|
1216
|
+
const submissionOptions = entry.runtime.externalAction === true
|
|
1217
|
+
? {
|
|
1218
|
+
...intake.options,
|
|
1219
|
+
transcriptMeta: {
|
|
1220
|
+
...(intake.options?.transcriptMeta && typeof intake.options.transcriptMeta === 'object'
|
|
1221
|
+
? intake.options.transcriptMeta
|
|
1222
|
+
: {}),
|
|
1223
|
+
sender: 'user',
|
|
1224
|
+
},
|
|
1225
|
+
}
|
|
1226
|
+
: intake.options;
|
|
1227
|
+
const accepted = await Promise.resolve(target.call(entry.runtime, intake.prompt, submissionOptions));
|
|
1026
1228
|
const firstSubmit = accepted === true && entry.reservedOnly;
|
|
1027
1229
|
if (accepted === true) {
|
|
1028
1230
|
entry.reservedOnly = false;
|
|
@@ -1047,6 +1249,42 @@ export function createSessionService({
|
|
|
1047
1249
|
return entry.runtime;
|
|
1048
1250
|
}
|
|
1049
1251
|
|
|
1252
|
+
async function recoverActiveGoals() {
|
|
1253
|
+
if (typeof listStoredActiveGoalSessionIds !== 'function') {
|
|
1254
|
+
return { found: 0, resumed: 0, skipped: 0, failed: 0 };
|
|
1255
|
+
}
|
|
1256
|
+
let listed;
|
|
1257
|
+
try {
|
|
1258
|
+
listed = await listStoredActiveGoalSessionIds();
|
|
1259
|
+
} catch (err) {
|
|
1260
|
+
log(`active Goal discovery failed: ${err?.message || err}`);
|
|
1261
|
+
return { found: 0, resumed: 0, skipped: 0, failed: 1 };
|
|
1262
|
+
}
|
|
1263
|
+
const sessionIds = [...new Set(Array.isArray(listed) ? listed : [])]
|
|
1264
|
+
.map((sessionId) => String(sessionId || ''))
|
|
1265
|
+
.filter((sessionId) => SESSION_ID_PATTERN.test(sessionId));
|
|
1266
|
+
let resumed = 0;
|
|
1267
|
+
let skipped = 0;
|
|
1268
|
+
let failed = 0;
|
|
1269
|
+
for (const sessionId of sessionIds) {
|
|
1270
|
+
try {
|
|
1271
|
+
if (typeof readStoredGoal === 'function') {
|
|
1272
|
+
const goal = await readStoredGoal(sessionId);
|
|
1273
|
+
if (goal?.status !== 'active') {
|
|
1274
|
+
skipped += 1;
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
}
|
|
1278
|
+
await materializeSession(sessionId);
|
|
1279
|
+
resumed += 1;
|
|
1280
|
+
} catch (err) {
|
|
1281
|
+
failed += 1;
|
|
1282
|
+
log(`active Goal recovery failed session=${sessionId}: ${err?.message || err}`);
|
|
1283
|
+
}
|
|
1284
|
+
}
|
|
1285
|
+
return { found: sessionIds.length, resumed, skipped, failed };
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1050
1288
|
async function abortSession({
|
|
1051
1289
|
sessionId, open: openHints = {}, options = {},
|
|
1052
1290
|
baseRevision = null,
|
|
@@ -1056,7 +1294,32 @@ export function createSessionService({
|
|
|
1056
1294
|
const entry = await entryForSession(id, openHints || {});
|
|
1057
1295
|
const target = entry.runtime.abort;
|
|
1058
1296
|
if (typeof target !== 'function') throw new TypeError('session action abort is unavailable');
|
|
1059
|
-
const
|
|
1297
|
+
const state = entry.runtime.getState?.() || {};
|
|
1298
|
+
const parentId = String(state.parentSessionId || state.ownerSessionId || '').trim();
|
|
1299
|
+
const isLegacyAgentChild = String(state.owner || '').trim().toLowerCase() === 'agent'
|
|
1300
|
+
&& String(state.agent || '').trim().toLowerCase() !== 'lead'
|
|
1301
|
+
&& parentId
|
|
1302
|
+
&& parentId !== id;
|
|
1303
|
+
const abortsAgentTurn = agentSessions.has(id)
|
|
1304
|
+
|| String(state.visibility || '').trim().toLowerCase() === 'agent-only'
|
|
1305
|
+
|| isLegacyAgentChild;
|
|
1306
|
+
let rawResult;
|
|
1307
|
+
try {
|
|
1308
|
+
rawResult = target.call(entry.runtime, options || {});
|
|
1309
|
+
} finally {
|
|
1310
|
+
// Lead cancellation must leave delegated work alive. An Agent cancelling
|
|
1311
|
+
// its own turn, however, is the live parent signal for Agent work nested
|
|
1312
|
+
// under that turn; task cancellation reaches the child's own controller
|
|
1313
|
+
// without enumerating durable sessions.
|
|
1314
|
+
if (abortsAgentTurn) {
|
|
1315
|
+
cancelBackgroundTasks({
|
|
1316
|
+
surface: 'agent',
|
|
1317
|
+
callerSessionId: id,
|
|
1318
|
+
reason: 'parent Agent turn aborted',
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
rawResult = await rawResult;
|
|
1060
1323
|
const abortResult = rawResult && typeof rawResult === 'object'
|
|
1061
1324
|
? rawResult
|
|
1062
1325
|
: { aborted: rawResult === true };
|
|
@@ -1066,6 +1329,437 @@ export function createSessionService({
|
|
|
1066
1329
|
return sessionResult(entry, step, baseRevision, abortResult);
|
|
1067
1330
|
}
|
|
1068
1331
|
|
|
1332
|
+
function linkAgentDescriptor(descriptor) {
|
|
1333
|
+
const sessionId = String(descriptor?.id || '').trim();
|
|
1334
|
+
const parentSessionId = String(descriptor?.parentSessionId || '').trim();
|
|
1335
|
+
if (!sessionId || !parentSessionId) {
|
|
1336
|
+
throw new TypeError('agent child requires session and parent ids');
|
|
1337
|
+
}
|
|
1338
|
+
const previous = agentSessions.get(sessionId);
|
|
1339
|
+
if (previous?.parentSessionId && previous.parentSessionId !== parentSessionId) {
|
|
1340
|
+
agentChildren.get(previous.parentSessionId)?.delete(sessionId);
|
|
1341
|
+
}
|
|
1342
|
+
const linked = {
|
|
1343
|
+
...(previous || {}),
|
|
1344
|
+
...descriptor,
|
|
1345
|
+
id: sessionId,
|
|
1346
|
+
parentSessionId,
|
|
1347
|
+
ownerSessionId: String(
|
|
1348
|
+
descriptor.ownerSessionId
|
|
1349
|
+
|| previous?.ownerSessionId
|
|
1350
|
+
|| agentSessions.get(parentSessionId)?.ownerSessionId
|
|
1351
|
+
|| parentSessionId,
|
|
1352
|
+
),
|
|
1353
|
+
owner: 'agent',
|
|
1354
|
+
visibility: 'agent-only',
|
|
1355
|
+
closed: descriptor.closed === true,
|
|
1356
|
+
};
|
|
1357
|
+
agentSessions.set(sessionId, linked);
|
|
1358
|
+
let children = agentChildren.get(parentSessionId);
|
|
1359
|
+
if (!children) agentChildren.set(parentSessionId, children = new Set());
|
|
1360
|
+
children.add(sessionId);
|
|
1361
|
+
return linked;
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
function validLinkedSessionId(value, ownId = '') {
|
|
1365
|
+
const id = String(value || '').trim();
|
|
1366
|
+
return SESSION_ID_PATTERN.test(id) && id !== ownId ? id : '';
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
function storedAgentCandidate(row) {
|
|
1370
|
+
const id = String(row?.id || '').trim();
|
|
1371
|
+
if (!SESSION_ID_PATTERN.test(id)) return null;
|
|
1372
|
+
const parentSessionId = validLinkedSessionId(
|
|
1373
|
+
row?.parentSessionId || row?.ownerSessionId,
|
|
1374
|
+
id,
|
|
1375
|
+
);
|
|
1376
|
+
if (!parentSessionId) return null;
|
|
1377
|
+
const declaredVisibility = String(
|
|
1378
|
+
row?.visibility || row?.sessionVisibility || '',
|
|
1379
|
+
).trim().toLowerCase() === 'agent-only';
|
|
1380
|
+
const legacyAgentChild = String(row?.owner || '').trim().toLowerCase() === 'agent';
|
|
1381
|
+
if (!declaredVisibility && !legacyAgentChild) return null;
|
|
1382
|
+
return { row, id, parentSessionId };
|
|
1383
|
+
}
|
|
1384
|
+
|
|
1385
|
+
function lastStoredAgentHandoff(row) {
|
|
1386
|
+
if (typeof row?.lastHandoff === 'string') return row.lastHandoff;
|
|
1387
|
+
const messages = Array.isArray(row?.messages) ? row.messages : [];
|
|
1388
|
+
const assistant = [...messages].reverse().find((message) => (
|
|
1389
|
+
message?.role === 'assistant'
|
|
1390
|
+
&& (typeof message.content === 'string' ? message.content.trim() : message.content)
|
|
1391
|
+
));
|
|
1392
|
+
if (!assistant) return '';
|
|
1393
|
+
return typeof assistant.content === 'string'
|
|
1394
|
+
? assistant.content
|
|
1395
|
+
: JSON.stringify(assistant.content);
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
/** Rebuild the Agent-only routing layer from lightweight durable summaries
|
|
1399
|
+
* after daemon replacement. Transcripts remain store/runtime owned and are
|
|
1400
|
+
* loaded by exact canonical session id only when a caller needs one. */
|
|
1401
|
+
async function rehydrateAgentSessions() {
|
|
1402
|
+
if (agentRehydrated) return agentSessions.size;
|
|
1403
|
+
if (agentRehydratePromise) return agentRehydratePromise;
|
|
1404
|
+
if (typeof listSessions !== 'function') {
|
|
1405
|
+
agentRehydrated = true;
|
|
1406
|
+
return agentSessions.size;
|
|
1407
|
+
}
|
|
1408
|
+
let loading;
|
|
1409
|
+
loading = (async () => {
|
|
1410
|
+
const stored = await listSessions({
|
|
1411
|
+
includeAgentOnly: true,
|
|
1412
|
+
summaryOnly: true,
|
|
1413
|
+
refreshFromStorage: false,
|
|
1414
|
+
});
|
|
1415
|
+
let candidates = (Array.isArray(stored) ? stored : [])
|
|
1416
|
+
.map(storedAgentCandidate)
|
|
1417
|
+
.filter(Boolean);
|
|
1418
|
+
if (typeof readStoredSession === 'function') {
|
|
1419
|
+
candidates = await Promise.all(candidates.map(async (candidate) => {
|
|
1420
|
+
if (candidate.row?.parentSessionId) return candidate;
|
|
1421
|
+
try {
|
|
1422
|
+
const metadata = await readStoredSession(candidate.id, { metadataOnly: true });
|
|
1423
|
+
return storedAgentCandidate({
|
|
1424
|
+
...candidate.row,
|
|
1425
|
+
...(metadata && typeof metadata === 'object' ? metadata : {}),
|
|
1426
|
+
id: candidate.id,
|
|
1427
|
+
}) || candidate;
|
|
1428
|
+
} catch (error) {
|
|
1429
|
+
log(`agent metadata migration failed session=${candidate.id}: ${error?.message || error}`);
|
|
1430
|
+
return candidate;
|
|
1431
|
+
}
|
|
1432
|
+
}));
|
|
1433
|
+
}
|
|
1434
|
+
const byId = new Map(candidates.map((candidate) => [candidate.id, candidate]));
|
|
1435
|
+
const roots = new Map();
|
|
1436
|
+
const resolveRoot = (candidate, seen = new Set()) => {
|
|
1437
|
+
if (!candidate || seen.has(candidate.id)) return candidate?.parentSessionId || '';
|
|
1438
|
+
if (roots.has(candidate.id)) return roots.get(candidate.id);
|
|
1439
|
+
seen.add(candidate.id);
|
|
1440
|
+
const explicitOwner = validLinkedSessionId(candidate.row?.ownerSessionId, candidate.id);
|
|
1441
|
+
if (explicitOwner && explicitOwner !== candidate.parentSessionId) {
|
|
1442
|
+
roots.set(candidate.id, explicitOwner);
|
|
1443
|
+
return explicitOwner;
|
|
1444
|
+
}
|
|
1445
|
+
const parent = byId.get(candidate.parentSessionId);
|
|
1446
|
+
const root = parent ? resolveRoot(parent, seen) : (explicitOwner || candidate.parentSessionId);
|
|
1447
|
+
roots.set(candidate.id, root);
|
|
1448
|
+
return root;
|
|
1449
|
+
};
|
|
1450
|
+
for (const candidate of candidates) {
|
|
1451
|
+
// A child created while the summary load was in flight is newer than
|
|
1452
|
+
// the stored row and must never be rolled back by rehydration.
|
|
1453
|
+
if (agentSessions.has(candidate.id)) continue;
|
|
1454
|
+
const row = candidate.row;
|
|
1455
|
+
linkAgentDescriptor({
|
|
1456
|
+
id: candidate.id,
|
|
1457
|
+
parentSessionId: candidate.parentSessionId,
|
|
1458
|
+
ownerSessionId: resolveRoot(candidate),
|
|
1459
|
+
owner: 'agent',
|
|
1460
|
+
visibility: 'agent-only',
|
|
1461
|
+
agent: row.agent || row.sourceName || 'worker',
|
|
1462
|
+
agentTag: row.agentTag || row.tag || null,
|
|
1463
|
+
cwd: row.cwd || process.cwd(),
|
|
1464
|
+
provider: row.provider || null,
|
|
1465
|
+
model: row.model || null,
|
|
1466
|
+
presetName: row.presetName || row.preset || row.profileId || null,
|
|
1467
|
+
effort: row.effort || null,
|
|
1468
|
+
fast: row.fast === true,
|
|
1469
|
+
modelParameters: row.modelParameters || null,
|
|
1470
|
+
taskType: row.taskType || null,
|
|
1471
|
+
maxLoopIterations: row.maxLoopIterations,
|
|
1472
|
+
permission: row.permission || null,
|
|
1473
|
+
permissionMode: row.permissionMode || null,
|
|
1474
|
+
toolPermission: row.toolPermission || null,
|
|
1475
|
+
schemaAllowedTools: Array.isArray(row.schemaAllowedTools)
|
|
1476
|
+
? row.schemaAllowedTools
|
|
1477
|
+
: null,
|
|
1478
|
+
sourceType: row.sourceType || 'agent',
|
|
1479
|
+
sourceName: row.sourceName || row.agent || 'agent',
|
|
1480
|
+
clientHostPid: row.clientHostPid || null,
|
|
1481
|
+
createdAt: row.createdAt || null,
|
|
1482
|
+
updatedAt: row.updatedAt || row.lastUsedAt || null,
|
|
1483
|
+
status: row.closed === true ? 'closed' : (row.status || 'idle'),
|
|
1484
|
+
stage: row.closed === true ? 'closed' : (row.stage || row.status || 'idle'),
|
|
1485
|
+
messageCount: Number(row.messageCount)
|
|
1486
|
+
|| (Array.isArray(row.messages) ? row.messages.length : 0),
|
|
1487
|
+
lastHandoff: lastStoredAgentHandoff(row),
|
|
1488
|
+
closed: row.closed === true,
|
|
1489
|
+
});
|
|
1490
|
+
}
|
|
1491
|
+
agentRehydrated = true;
|
|
1492
|
+
return agentSessions.size;
|
|
1493
|
+
})().finally(() => {
|
|
1494
|
+
if (agentRehydratePromise === loading) agentRehydratePromise = null;
|
|
1495
|
+
});
|
|
1496
|
+
agentRehydratePromise = loading;
|
|
1497
|
+
return loading;
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function agentDescriptor(sessionId) {
|
|
1501
|
+
const id = String(sessionId || '').trim();
|
|
1502
|
+
const descriptor = agentSessions.get(id);
|
|
1503
|
+
if (!descriptor) return null;
|
|
1504
|
+
const owner = sessionOwner(id);
|
|
1505
|
+
const state = owner?.runtime?.getState?.() || {};
|
|
1506
|
+
const status = descriptor.closed
|
|
1507
|
+
? (descriptor.status || 'closed')
|
|
1508
|
+
: stateBusy(state)
|
|
1509
|
+
? 'running'
|
|
1510
|
+
: (descriptor.status || 'idle');
|
|
1511
|
+
return {
|
|
1512
|
+
...descriptor,
|
|
1513
|
+
status,
|
|
1514
|
+
stage: status,
|
|
1515
|
+
messageCount: Array.isArray(state.items) && state.items.length > 0
|
|
1516
|
+
? state.items.length
|
|
1517
|
+
: Math.max(0, Number(descriptor.messageCount) || 0),
|
|
1518
|
+
updatedAt: descriptor.updatedAt || Date.now(),
|
|
1519
|
+
};
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
function rootOwnerSessionId(sessionId) {
|
|
1523
|
+
const id = String(sessionId || '').trim();
|
|
1524
|
+
return agentSessions.get(id)?.ownerSessionId || id || null;
|
|
1525
|
+
}
|
|
1526
|
+
|
|
1527
|
+
async function createAgentChild({ spec = {}, prompt = '', tag = null } = {}) {
|
|
1528
|
+
await rehydrateAgentSessions();
|
|
1529
|
+
const parentSessionId = String(spec.parentSessionId || '').trim();
|
|
1530
|
+
if (!parentSessionId) throw new TypeError('agent child parentSessionId is required');
|
|
1531
|
+
const ownerSessionId = String(
|
|
1532
|
+
spec.ownerSessionId
|
|
1533
|
+
|| agentSessions.get(parentSessionId)?.ownerSessionId
|
|
1534
|
+
|| parentSessionId,
|
|
1535
|
+
);
|
|
1536
|
+
const preset = spec.preset && typeof spec.preset === 'object' ? spec.preset : {};
|
|
1537
|
+
const provider = String(preset.provider || spec.provider || '').trim();
|
|
1538
|
+
const model = String(preset.model || spec.model || '').trim();
|
|
1539
|
+
if (!provider || !model) throw new Error('agent child route is incomplete');
|
|
1540
|
+
const sessionProfile = {
|
|
1541
|
+
owner: 'agent',
|
|
1542
|
+
agent: String(spec.agent || 'worker'),
|
|
1543
|
+
parentSessionId,
|
|
1544
|
+
ownerSessionId,
|
|
1545
|
+
visibility: 'agent-only',
|
|
1546
|
+
agentTag: String(spec.agentTag || tag || '').trim() || null,
|
|
1547
|
+
taskType: spec.taskType || null,
|
|
1548
|
+
maxLoopIterations: spec.maxLoopIterations,
|
|
1549
|
+
permission: spec.permission || null,
|
|
1550
|
+
permissionMode: spec.permissionMode || null,
|
|
1551
|
+
schemaAllowedTools: Array.isArray(spec.schemaAllowedTools)
|
|
1552
|
+
? spec.schemaAllowedTools
|
|
1553
|
+
: null,
|
|
1554
|
+
sourceType: spec.sourceType || 'agent',
|
|
1555
|
+
sourceName: spec.sourceName || spec.agent || 'agent',
|
|
1556
|
+
clientHostPid: spec.clientHostPid || null,
|
|
1557
|
+
};
|
|
1558
|
+
const created = await createSession({
|
|
1559
|
+
cwd: spec.cwd || process.cwd(),
|
|
1560
|
+
provider,
|
|
1561
|
+
model,
|
|
1562
|
+
effort: preset.effort,
|
|
1563
|
+
fast: preset.fast === true,
|
|
1564
|
+
modelParameters: preset.modelParameters,
|
|
1565
|
+
toolMode: 'full',
|
|
1566
|
+
sessionProfile,
|
|
1567
|
+
});
|
|
1568
|
+
const descriptor = linkAgentDescriptor({
|
|
1569
|
+
id: created.sessionId,
|
|
1570
|
+
...sessionProfile,
|
|
1571
|
+
cwd: spec.cwd || process.cwd(),
|
|
1572
|
+
provider,
|
|
1573
|
+
model,
|
|
1574
|
+
presetName: preset.id || preset.name || null,
|
|
1575
|
+
effort: preset.effort || null,
|
|
1576
|
+
fast: preset.fast === true,
|
|
1577
|
+
createdAt: Date.now(),
|
|
1578
|
+
updatedAt: Date.now(),
|
|
1579
|
+
status: 'idle',
|
|
1580
|
+
stage: 'idle',
|
|
1581
|
+
});
|
|
1582
|
+
void prompt;
|
|
1583
|
+
return { session: descriptor, effectiveCwd: descriptor.cwd };
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1586
|
+
async function runAgentTurn({
|
|
1587
|
+
session,
|
|
1588
|
+
prompt,
|
|
1589
|
+
context = null,
|
|
1590
|
+
onToolResult,
|
|
1591
|
+
onTerminalResult,
|
|
1592
|
+
} = {}) {
|
|
1593
|
+
await rehydrateAgentSessions();
|
|
1594
|
+
const sessionId = String(session?.id || session || '').trim();
|
|
1595
|
+
const descriptor = agentSessions.get(sessionId);
|
|
1596
|
+
if (!descriptor || descriptor.closed) {
|
|
1597
|
+
throw new Error(`agent session ${sessionId || '(empty)'} is closed`);
|
|
1598
|
+
}
|
|
1599
|
+
const entry = await entryForSession(sessionId, {
|
|
1600
|
+
cwd: descriptor.cwd,
|
|
1601
|
+
provider: descriptor.provider,
|
|
1602
|
+
model: descriptor.model,
|
|
1603
|
+
toolMode: 'full',
|
|
1604
|
+
});
|
|
1605
|
+
const target = entry.runtime.submitAndWait;
|
|
1606
|
+
if (typeof target !== 'function') {
|
|
1607
|
+
throw new TypeError('session runtime must implement submitAndWait');
|
|
1608
|
+
}
|
|
1609
|
+
descriptor.status = 'running';
|
|
1610
|
+
descriptor.stage = 'running';
|
|
1611
|
+
descriptor.updatedAt = Date.now();
|
|
1612
|
+
try {
|
|
1613
|
+
const options = {
|
|
1614
|
+
id: `agent-turn-${randomUUID()}`,
|
|
1615
|
+
mode: 'prompt',
|
|
1616
|
+
priority: 'next',
|
|
1617
|
+
context,
|
|
1618
|
+
transcriptMeta: { sender: 'lead' },
|
|
1619
|
+
...(entry.runtime.isWireSafe === true || typeof onToolResult !== 'function'
|
|
1620
|
+
? {}
|
|
1621
|
+
: { onToolResult }),
|
|
1622
|
+
};
|
|
1623
|
+
const detail = await target.call(entry.runtime, String(prompt || ''), options);
|
|
1624
|
+
if (detail?.status === 'failed') {
|
|
1625
|
+
throw new Error(String(detail.error || 'agent session turn failed'));
|
|
1626
|
+
}
|
|
1627
|
+
if (detail?.status === 'cancelled') {
|
|
1628
|
+
throw new Error('agent session turn cancelled');
|
|
1629
|
+
}
|
|
1630
|
+
const result = detail?.result || { content: '' };
|
|
1631
|
+
descriptor.status = 'idle';
|
|
1632
|
+
descriptor.stage = 'idle';
|
|
1633
|
+
descriptor.lastHandoff = typeof result?.content === 'string' ? result.content : '';
|
|
1634
|
+
try { onTerminalResult?.(result); } catch {}
|
|
1635
|
+
return result;
|
|
1636
|
+
} catch (error) {
|
|
1637
|
+
descriptor.status = /cancel/i.test(String(error?.message || '')) ? 'cancelled' : 'error';
|
|
1638
|
+
descriptor.stage = descriptor.status;
|
|
1639
|
+
throw error;
|
|
1640
|
+
} finally {
|
|
1641
|
+
descriptor.updatedAt = Date.now();
|
|
1642
|
+
retainUnwatched(entry, 'agent child idle');
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
function cancelAgentTree(sessionId, reason = 'agent session cancelled') {
|
|
1647
|
+
const id = String(sessionId || '').trim();
|
|
1648
|
+
if (!id) return Promise.resolve(false);
|
|
1649
|
+
const active = agentCancelRuns.get(id);
|
|
1650
|
+
if (active) return active;
|
|
1651
|
+
let run;
|
|
1652
|
+
run = (async () => {
|
|
1653
|
+
await rehydrateAgentSessions();
|
|
1654
|
+
cancelBackgroundTasks({
|
|
1655
|
+
surface: 'agent',
|
|
1656
|
+
callerSessionId: id,
|
|
1657
|
+
reason,
|
|
1658
|
+
});
|
|
1659
|
+
const children = [...(agentChildren.get(id) || [])];
|
|
1660
|
+
await Promise.all(children.map((childId) => cancelAgentTree(childId, reason)));
|
|
1661
|
+
const descriptor = agentSessions.get(id);
|
|
1662
|
+
if (!descriptor) return children.length > 0;
|
|
1663
|
+
if (descriptor.closed) return true;
|
|
1664
|
+
let entry = sessionOwner(id);
|
|
1665
|
+
if (!entry) {
|
|
1666
|
+
try {
|
|
1667
|
+
entry = await entryForSession(id, {
|
|
1668
|
+
cwd: descriptor.cwd,
|
|
1669
|
+
provider: descriptor.provider,
|
|
1670
|
+
model: descriptor.model,
|
|
1671
|
+
toolMode: 'full',
|
|
1672
|
+
});
|
|
1673
|
+
} catch (error) {
|
|
1674
|
+
log(`agent cancel load failed session=${id}: ${error?.message || error}`);
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
const closeCanonical = entry?.runtime?.closeCanonicalSession;
|
|
1678
|
+
if (typeof closeCanonical !== 'function') {
|
|
1679
|
+
throw new TypeError('session runtime must implement closeCanonicalSession');
|
|
1680
|
+
}
|
|
1681
|
+
const closed = await closeCanonical.call(entry.runtime, reason);
|
|
1682
|
+
if (closed !== true) throw new Error(`agent session ${id} could not be durably closed`);
|
|
1683
|
+
descriptor.closed = true;
|
|
1684
|
+
descriptor.status = 'closed';
|
|
1685
|
+
descriptor.stage = 'closed';
|
|
1686
|
+
descriptor.updatedAt = Date.now();
|
|
1687
|
+
return true;
|
|
1688
|
+
})().finally(() => {
|
|
1689
|
+
if (agentCancelRuns.get(id) === run) agentCancelRuns.delete(id);
|
|
1690
|
+
});
|
|
1691
|
+
agentCancelRuns.set(id, run);
|
|
1692
|
+
return run;
|
|
1693
|
+
}
|
|
1694
|
+
|
|
1695
|
+
async function cancelAgentDescendants(parentSessionId, reason = 'parent session cancelled') {
|
|
1696
|
+
await rehydrateAgentSessions();
|
|
1697
|
+
const parentId = String(parentSessionId || '');
|
|
1698
|
+
cancelBackgroundTasks({
|
|
1699
|
+
surface: 'agent',
|
|
1700
|
+
callerSessionId: parentId,
|
|
1701
|
+
reason,
|
|
1702
|
+
});
|
|
1703
|
+
const children = [...(agentChildren.get(parentId) || [])];
|
|
1704
|
+
if (!children.length) return false;
|
|
1705
|
+
await Promise.all(children.map((childId) => cancelAgentTree(childId, reason)));
|
|
1706
|
+
return true;
|
|
1707
|
+
}
|
|
1708
|
+
|
|
1709
|
+
function agentDescendantSessionIds(parentSessionId) {
|
|
1710
|
+
const descendants = [];
|
|
1711
|
+
const seen = new Set();
|
|
1712
|
+
const visit = (parentId) => {
|
|
1713
|
+
for (const childId of agentChildren.get(String(parentId || '')) || []) {
|
|
1714
|
+
if (seen.has(childId)) continue;
|
|
1715
|
+
seen.add(childId);
|
|
1716
|
+
descendants.push(childId);
|
|
1717
|
+
visit(childId);
|
|
1718
|
+
}
|
|
1719
|
+
};
|
|
1720
|
+
visit(parentSessionId);
|
|
1721
|
+
return descendants;
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
const agentSurface = Object.freeze({
|
|
1725
|
+
canonical: true,
|
|
1726
|
+
canRun: (session) => Boolean(agentSessions.get(String(session?.id || ''))),
|
|
1727
|
+
createChild: createAgentChild,
|
|
1728
|
+
runTurn: runAgentTurn,
|
|
1729
|
+
});
|
|
1730
|
+
|
|
1731
|
+
const agentManager = Object.freeze({
|
|
1732
|
+
rehydrateAgentSessions,
|
|
1733
|
+
descendantSessionIds: agentDescendantSessionIds,
|
|
1734
|
+
getSession: (sessionId) => agentDescriptor(sessionId),
|
|
1735
|
+
listSessions: ({ includeClosed = false } = {}) => [...agentSessions.keys()]
|
|
1736
|
+
.map(agentDescriptor)
|
|
1737
|
+
.filter((session) => session && (includeClosed || session.closed !== true)),
|
|
1738
|
+
getSessionRuntime: (sessionId) => {
|
|
1739
|
+
const session = agentDescriptor(sessionId);
|
|
1740
|
+
return session ? { stage: session.stage || session.status || 'idle' } : null;
|
|
1741
|
+
},
|
|
1742
|
+
async readSessionHandoff(sessionId) {
|
|
1743
|
+
const id = String(sessionId || '').trim();
|
|
1744
|
+
const descriptor = agentSessions.get(id);
|
|
1745
|
+
if (!descriptor) return '';
|
|
1746
|
+
if (typeof descriptor.lastHandoff === 'string' && descriptor.lastHandoff.trim()) {
|
|
1747
|
+
return descriptor.lastHandoff;
|
|
1748
|
+
}
|
|
1749
|
+
if (typeof readStoredSession !== 'function') return '';
|
|
1750
|
+
const stored = await readStoredSession(id, { includeMessages: true });
|
|
1751
|
+
const handoff = lastStoredAgentHandoff(stored);
|
|
1752
|
+
if (handoff) descriptor.lastHandoff = handoff;
|
|
1753
|
+
return handoff;
|
|
1754
|
+
},
|
|
1755
|
+
async closeSession(sessionId, reason = 'agent session closed') {
|
|
1756
|
+
await rehydrateAgentSessions();
|
|
1757
|
+
return cancelAgentTree(sessionId, reason);
|
|
1758
|
+
},
|
|
1759
|
+
unloadSessionRuntime: () => false,
|
|
1760
|
+
hideSessionFromList: () => false,
|
|
1761
|
+
});
|
|
1762
|
+
|
|
1069
1763
|
async function approveSession({
|
|
1070
1764
|
sessionId, approvalId, decision, open: openHints = {}, baseRevision = null,
|
|
1071
1765
|
} = {}) {
|
|
@@ -1280,6 +1974,10 @@ export function createSessionService({
|
|
|
1280
1974
|
|
|
1281
1975
|
async function stop(reason = 'service stop') {
|
|
1282
1976
|
closed = true;
|
|
1977
|
+
try { unsubscribeExternalSessionStates(); } catch {}
|
|
1978
|
+
externalViewEntries.clear();
|
|
1979
|
+
agentSessions.clear();
|
|
1980
|
+
agentChildren.clear();
|
|
1283
1981
|
if (evictTimer) { clearInterval(evictTimer); evictTimer = null; }
|
|
1284
1982
|
const services = [...new Set(desktopServicesById.values())];
|
|
1285
1983
|
desktopServicesById.clear();
|
|
@@ -1297,8 +1995,10 @@ export function createSessionService({
|
|
|
1297
1995
|
|
|
1298
1996
|
return {
|
|
1299
1997
|
handleCall, listSessionCatalog, createSession, readSession, subscribeSession, unsubscribeSession,
|
|
1300
|
-
submitSession, materializeSession, abortSession, approveSession,
|
|
1998
|
+
submitSession, materializeSession, recoverActiveGoals, abortSession, approveSession,
|
|
1301
1999
|
configureSession, stop, releaseClient,
|
|
2000
|
+
agentSurface, agentManager, agentDescriptor, rootOwnerSessionId, rehydrateAgentSessions,
|
|
2001
|
+
cancelAgentTree, cancelAgentDescendants,
|
|
1302
2002
|
get size() { return sessions.size; },
|
|
1303
2003
|
/** Live work the daemon must not abandon (self-shutdown guard). */
|
|
1304
2004
|
get busyCount() { return liveBusyCount(); },
|