mixdog 1.4.1 → 1.4.2
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/package.json +3 -2
- package/scripts/build-shipped-history.mjs +115 -33
- package/scripts/gate-local.mjs +172 -61
- package/scripts/lib/gate-scheduler.mjs +138 -32
- package/scripts/lib/gate-timing-reporter.mjs +16 -0
- package/scripts/lib/run-node-tests.mjs +54 -29
- package/scripts/lib/scoped-temp.mjs +1 -0
- package/scripts/lib/test-failure-records.mjs +14 -1
- package/scripts/lib/test-failure-summary.mjs +16 -4
- package/scripts/lib/test-spawn-ledger.mjs +12 -3
- package/scripts/pptx-kit-index.mjs +5 -6
- package/scripts/suite-health.mjs +110 -28
- package/scripts/test.mjs +9 -14
- package/src/agents/advisor/AGENT.md +4 -4
- package/src/agents/advisor/agent.json +1 -1
- package/src/agents/explore/AGENT.md +22 -0
- package/src/agents/explore/agent.json +6 -0
- package/src/cli.mjs +7 -0
- package/src/defaults/agents.json +7 -0
- package/src/defaults/shipped-history.json +18 -2
- package/src/defaults/skills/browser-use/SKILL.md +4 -1
- package/src/defaults/skills/code-tidy/SKILL.md +71 -102
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +66 -114
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +19 -35
- package/src/defaults/skills/code-tidy/references/dead-code.md +3 -9
- package/src/defaults/skills/code-tidy/references/test-suite-slop.md +16 -27
- package/src/defaults/skills/computer-use/SKILL.md +12 -2
- package/src/defaults/skills/goal-management/SKILL.md +1 -1
- package/src/defaults/skills/setup/references/actions.md +12 -6
- package/src/defaults/skills/setup/references/surfaces.md +2 -2
- package/src/defaults/skills/setup-guide/SKILL.md +5 -2
- package/src/defaults/skills/setup-guide/model-recommendations.json +13 -0
- package/src/defaults/skills/skill-creator/SKILL.md +85 -190
- package/src/defaults/skills/skill-creator/references/design.md +70 -162
- package/src/defaults/skills/skill-creator/references/evaluation.md +33 -92
- package/src/defaults/skills/test-suite/SKILL.md +194 -0
- package/src/defaults/skills/test-suite/references/case-management.md +51 -0
- package/src/defaults/skills/test-suite/references/flaky-policy.md +39 -0
- package/src/defaults/skills/test-suite/references/reliability.md +49 -0
- package/src/defaults/skills/test-suite/references/suite-design.md +78 -0
- package/src/defaults/skills/test-suite/scripts/inventory.mjs +335 -0
- package/src/defaults/skills/visualize/SKILL.md +6 -2
- package/src/lib/rules-builder.cjs +6 -30
- package/src/rules/lead/LEAD.md +3 -3
- package/src/rules/shared/20-research.md +3 -6
- package/src/rules/shared/40-editing.md +1 -0
- package/src/rules/shared/60-verification.md +10 -9
- package/src/rules/shared/70-delivery.md +4 -3
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/abort-link.mjs +5 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch.mjs +10 -1
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +130 -21
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +13 -0
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +4 -1
- package/src/runtime/agent/orchestrator/config.mjs +12 -7
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +7 -7
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +5 -26
- package/src/runtime/agent/orchestrator/internal-agents.mjs +3 -14
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +122 -12
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +5 -3
- package/src/runtime/agent/orchestrator/mcp/client-config.mjs +13 -2
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +152 -58
- package/src/runtime/agent/orchestrator/mcp/client.mjs +51 -18
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +83 -52
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +42 -26
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +43 -8
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +30 -28
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +31 -21
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-login.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-tokens.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +40 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider-keys.mjs +34 -0
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +14 -7
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +3 -2
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +2 -2
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/grok-oauth-login.mjs +48 -31
- package/src/runtime/agent/orchestrator/providers/lib/oauth-page.mjs +0 -18
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +138 -6
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/model-cache.mjs +100 -33
- package/src/runtime/agent/orchestrator/providers/muse-code-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/oauth-secret-fields.fixtures.mjs +45 -0
- package/src/runtime/agent/orchestrator/providers/openai-oauth-login.mjs +26 -28
- package/src/runtime/agent/orchestrator/providers/openai-ws-stream/event-switch.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-watchdogs.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/registry.mjs +33 -2
- package/src/runtime/agent/orchestrator/providers/vertex-ai-auth.mjs +622 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-maas.mjs +185 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai-models.mjs +445 -0
- package/src/runtime/agent/orchestrator/providers/vertex-ai.mjs +567 -0
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +14 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -5
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +171 -37
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +94 -10
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +10 -5
- package/src/runtime/agent/orchestrator/session/conversation-import/convert.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/conversation-import/turns.mjs +11 -1
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -3
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -6
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +21 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +23 -1
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +13 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +50 -28
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +3 -4
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +7 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +25 -14
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +17 -14
- package/src/runtime/agent/orchestrator/session/manager/session-scratch-environment.mjs +70 -0
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +0 -3
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +47 -29
- package/src/runtime/agent/orchestrator/session/manager/turn-reap.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/restart-prefix-parity.fixture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +243 -38
- package/src/runtime/agent/orchestrator/session/store-transcript-cache.mjs +163 -64
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +164 -4
- package/src/runtime/agent/orchestrator/session/store-transcript-worker.mjs +142 -18
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +36 -26
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +3 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +58 -20
- package/src/runtime/agent/orchestrator/tools/builtin/runtime-capabilities.mjs +20 -15
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +96 -20
- package/src/runtime/agent/orchestrator/tools/builtin/task-tool.mjs +59 -10
- package/src/runtime/agent/orchestrator/tools/graph-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/lib/native-spawn-client.mjs +99 -2
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +103 -13
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +3 -1
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +49 -15
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +45 -27
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +34 -10
- package/src/runtime/agent/orchestrator/tools/lib/shell-warm-standby.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/wait-until-fixture.mjs +26 -0
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +7 -2
- package/src/runtime/agent/orchestrator/tools/spawn-manifest.json +13 -13
- package/src/runtime/channels/lib/whisper-port.mjs +19 -10
- package/src/runtime/channels/lib/whisper-server.mjs +10 -1
- package/src/runtime/connectors/connector-accounts-mcp.mjs +236 -103
- package/src/runtime/connectors/gmail-app-password.mjs +3 -3
- package/src/runtime/explore/explorer.mjs +93 -0
- package/src/runtime/explore/tool-defs.mjs +35 -0
- package/src/runtime/explore/tool.mjs +123 -0
- package/src/runtime/memory/index.mjs +4 -0
- package/src/runtime/memory/lib/embedding-provider.mjs +4 -1
- package/src/runtime/memory/lib/embedding-worker.mjs +4 -0
- package/src/runtime/office/quality/assurance-structure-docx.mjs +1 -1
- package/src/runtime/shared/agent-route-config.mjs +17 -2
- package/src/runtime/shared/atomic-file.mjs +103 -2
- package/src/runtime/shared/background-tasks.mjs +15 -6
- package/src/runtime/shared/connector-accounts.mjs +5 -5
- package/src/runtime/shared/connector-catalog.d.mts +11 -4
- package/src/runtime/shared/connector-catalog.mjs +60 -52
- package/src/runtime/shared/custom-provider-keys.mjs +20 -0
- package/src/runtime/shared/file-has-data.mjs +26 -2
- package/src/runtime/shared/file-lock.mjs +13 -15
- package/src/runtime/shared/git-launch.d.mts +9 -0
- package/src/runtime/shared/git-launch.mjs +213 -0
- package/src/runtime/shared/git-runner-worker.mjs +2 -1
- package/src/runtime/shared/llm/cost.mjs +6 -1
- package/src/runtime/shared/llm/http-agent.mjs +313 -29
- package/src/runtime/shared/llm/model-catalog-projection.mjs +3 -2
- package/src/runtime/shared/llm/model-catalog.mjs +46 -6
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +3 -0
- package/src/runtime/shared/llm/system-proxy.mjs +452 -0
- package/src/runtime/shared/provider-accounts.mjs +52 -2
- package/src/runtime/shared/staged-install-worker.mjs +5 -0
- package/src/runtime/shared/staged-update.mjs +47 -2
- package/src/runtime/shared/tool-surface.mjs +110 -36
- package/src/runtime/shared/tool-work-units.mjs +4 -0
- package/src/runtime/shared/turn-worktree-snapshot.mjs +414 -54
- package/src/runtime/shared/worktrees/activity-gate.mjs +215 -318
- package/src/runtime/shared/worktrees/activity-ledger-worker.mjs +56 -0
- package/src/runtime/shared/worktrees/activity-ledger.mjs +391 -0
- package/src/runtime/shared/worktrees/branch-management.mjs +9 -3
- package/src/runtime/shared/worktrees/git.mjs +57 -0
- package/src/runtime/shared/worktrees/manager.mjs +205 -137
- package/src/runtime/tidy/install.mjs +95 -15
- package/src/runtime/tidy/languages.mjs +17 -15
- package/src/runtime/tidy/process.mjs +7 -4
- package/src/runtime/tidy/resolve.mjs +19 -16
- package/src/runtime/tidy/runners/biome.mjs +3 -3
- package/src/runtime/tidy/tool.mjs +1 -1
- package/src/runtime/tidy/worktree.mjs +10 -6
- package/src/runtime/visualize-bridge/client.mjs +4 -1
- package/src/runtime/visualize-bridge/tool-defs.mjs +7 -2
- package/src/runtime/web-search/index.mjs +1 -1
- package/src/session-runtime/agent-task-sessions.mjs +410 -353
- package/src/session-runtime/boot/tools.mjs +2 -0
- package/src/session-runtime/bot-status.mjs +103 -103
- package/src/session-runtime/connector-merge-migration.mjs +18 -15
- package/src/session-runtime/conversation-import/discovery.mjs +159 -40
- package/src/session-runtime/conversation-import/service.mjs +9 -3
- package/src/session-runtime/handoff-attachments.mjs +1 -1
- package/src/session-runtime/held-requests.mjs +3 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -0
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +8 -0
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +143 -116
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -1
- package/src/session-runtime/lifecycle/teardown.mjs +1 -1
- package/src/session-runtime/mcp-glue/server-catalog.mjs +16 -10
- package/src/session-runtime/mcp-glue/server-input.mjs +4 -1
- package/src/session-runtime/mixy-heartbeat.mjs +1 -1
- package/src/session-runtime/provider-auth/login.mjs +2 -2
- package/src/session-runtime/resource-mcp-api.mjs +98 -35
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +44 -31
- package/src/session-runtime/resource-mcp-secrets.mjs +28 -11
- package/src/session-runtime/resource-mcp-servers.mjs +5 -4
- package/src/session-runtime/services/agent-tool/send-flow.mjs +10 -2
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +8 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +4 -1
- package/src/session-runtime/services/agent-watchdog-registry.mjs +32 -4
- package/src/session-runtime/services/channel-worker/daemon-spawn.mjs +2 -0
- package/src/session-runtime/services/daemon-candidate.mjs +4 -1
- package/src/session-runtime/services/memory-runtime-proxy/daemon-spawn.mjs +4 -0
- package/src/session-runtime/services/plugin-admin.mjs +10 -6
- package/src/session-runtime/services/provider-admin.mjs +281 -10
- package/src/session-runtime/session-turn-api.mjs +2 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +4 -1
- package/src/session-runtime/settings-system-api.mjs +1 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +13 -3
- package/src/session-runtime/setup-tool/connector-widen-approval.mjs +96 -0
- package/src/session-runtime/setup-tool/executor.mjs +6 -2
- package/src/session-runtime/setup-tool/tool-defs.mjs +2 -1
- package/src/session-runtime/turn/agent-ops.mjs +23 -0
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +49 -11
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +10 -1
- package/src/session-runtime/workspace/activity-guards.mjs +176 -0
- package/src/session-runtime/workspace/session-view.mjs +149 -0
- package/src/session-runtime/workspace/side-checkouts.mjs +203 -0
- package/src/session-runtime/workspace/transition-runner.mjs +188 -0
- package/src/session-runtime/workspace/transitions.mjs +108 -1066
- package/src/session-runtime/workspace/workspace-merge.mjs +132 -0
- package/src/session-runtime/workspace/worktree-intents.mjs +242 -0
- package/src/session-runtime/workspace/worktree-maintenance.mjs +80 -0
- package/src/session-runtime/workspace/worktree-setup.mjs +85 -0
- package/src/session-runtime/workspace/worktree-tool.mjs +54 -54
- package/src/standalone/daemon-desktop-runtime.mjs +2 -0
- package/src/standalone/daemon-session-control.mjs +151 -136
- package/src/standalone/daemon.mjs +7 -0
- package/src/standalone/desktop-service-registry.mjs +9 -0
- package/src/standalone/local-session-runtime.mjs +5 -1
- package/src/standalone/session-client/daemon-unavailable.mjs +3 -0
- package/src/standalone/session-client.mjs +5 -1
- package/src/standalone/session-frame-stream.mjs +83 -11
- package/src/standalone/session-protocol.mjs +4 -0
- package/src/standalone/session-service/agent-tree.mjs +45 -9
- package/src/standalone/session-service/projection/transcript-window.mjs +700 -64
- package/src/standalone/session-service/session-calls/session-views.mjs +160 -104
- package/src/standalone/session-service/stored-reader.mjs +127 -40
- package/src/tui/App.jsx +154 -164
- package/src/tui/app/app-view/floating-panel.jsx +3 -0
- package/src/tui/app/app-view/text-entry-labels.mjs +1 -0
- package/src/tui/app/clipboard.mjs +21 -10
- package/src/tui/app/prompt-handlers/use-prompt-history-nav.mjs +1 -1
- package/src/tui/app/prompt-handlers/use-prompt-interrupt.mjs +3 -0
- package/src/tui/app/prompt-submit/provider-prompts.mjs +3 -1
- package/src/tui/app/provider-setup/api-key-actions.mjs +31 -1
- package/src/tui/app/provider-setup/oauth-actions.mjs +25 -6
- package/src/tui/app/provider-setup-picker.mjs +20 -13
- package/src/tui/app/shell-layout/row-budgets.mjs +12 -10
- package/src/tui/app/slash-commands.mjs +1 -0
- package/src/tui/app/slash-dispatch/session-commands.mjs +46 -0
- package/src/tui/app/slash-dispatch.mjs +1 -1
- package/src/tui/app/use-btw-panel.mjs +29 -0
- package/src/tui/app/use-context-panel-refresh.mjs +31 -0
- package/src/tui/app/use-model-catalog-cache.mjs +13 -10
- package/src/tui/app/use-mouse-input.mjs +11 -0
- package/src/tui/app/use-onboarding-start.mjs +8 -3
- package/src/tui/app/use-panel-transition.mjs +10 -1
- package/src/tui/app/use-pasted-buffers.mjs +28 -20
- package/src/tui/app/use-prompt-draft-flow.mjs +7 -0
- package/src/tui/app/use-prompt-layout-rows.mjs +3 -3
- package/src/tui/app/use-prompt-queue-history.mjs +3 -2
- package/src/tui/app/use-selection-focus.mjs +11 -2
- package/src/tui/app/use-selection-geometry.mjs +18 -9
- package/src/tui/app/use-selection-paint.mjs +4 -4
- package/src/tui/app/use-selection-stitch.mjs +3 -3
- package/src/tui/app/use-slash-palette.mjs +62 -0
- package/src/tui/app/use-terminal-chrome.mjs +2 -2
- package/src/tui/app/use-transcript-activity.mjs +47 -32
- package/src/tui/app/use-transcript-follow.mjs +12 -4
- package/src/tui/app/use-transcript-scroll.mjs +46 -17
- package/src/tui/app/use-transcript-swap-reset.mjs +27 -0
- package/src/tui/app/use-transcript-window/geometry.mjs +19 -5
- package/src/tui/app/use-transcript-window/instance-state.mjs +4 -0
- package/src/tui/app/use-transcript-window/selection-shift.mjs +5 -1
- package/src/tui/app/use-transcript-window.mjs +21 -11
- package/src/tui/app/use-tui-ready.mjs +18 -0
- package/src/tui/app/use-welcome-prompt-hint.mjs +5 -0
- package/src/tui/app/use-workflow-tab-cycle.mjs +5 -3
- package/src/tui/components/Picker.jsx +27 -3
- package/src/tui/components/SignInUrl.jsx +68 -0
- package/src/tui/components/TextEntryPanel.jsx +76 -46
- package/src/tui/components/sign-in-url-layout.mjs +24 -0
- package/src/tui/dist/index.mjs +1896 -1341
- package/src/tui/session/goal-turn-state.mjs +6 -2
- package/src/tui/session/session-api/controls.mjs +4 -0
- package/src/tui/session/session-api/extensions.mjs +8 -0
- package/src/tui/session/session-api/intake/abort.mjs +2 -1
- package/src/tui/session/session-api/intake.mjs +2 -0
- package/src/tui/session/session-api/integrations.mjs +2 -2
- package/src/tui/session/session-api/routes-agent-definitions.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +2 -2
- package/src/tui/session/transcript-spill/spill-writer.mjs +15 -0
- package/src/tui/session/transcript-spill.mjs +92 -4
- package/src/tui/session/transcript-store.mjs +2 -0
- package/src/defaults/skills/skill-creator/references/source-review.md +0 -94
|
@@ -1094,23 +1094,392 @@ function startedGate() {
|
|
|
1094
1094
|
return { ready, started };
|
|
1095
1095
|
}
|
|
1096
1096
|
|
|
1097
|
+
/** The report delivery of send `id` of `origin` to task session `sessionId`.
|
|
1098
|
+
* `turn` (`{ runtime, sendIds }`) is what its turn knows when a report goes
|
|
1099
|
+
* out: the live runtime and the sends its end closes. A report that cannot
|
|
1100
|
+
* be queued is retried in this process; when every try fails the durable
|
|
1101
|
+
* watch stays and restart recovery delivers it (the queue dedupes by runId,
|
|
1102
|
+
* so a retry never posts twice). `answerId`: the turn whose answer a
|
|
1103
|
+
* progress update or the end carries. `deliverReport` rejects once every
|
|
1104
|
+
* try failed (a progress update is tried again at the follow's next check);
|
|
1105
|
+
* `report` logs it instead. */
|
|
1106
|
+
function sendReporter({ deliver, retryDelaysMs, artifactFields, log }, { sessionId, id, origin, beforeReport, turn }) {
|
|
1107
|
+
const target = origin.target === 'session' ? { target: 'session' } : {};
|
|
1108
|
+
const deliverReport = (status, text = '', { approvalId = '', answerId = '' } = {}) =>
|
|
1109
|
+
Promise.resolve(beforeReport).then(async () =>
|
|
1110
|
+
deliverWithRetries({ deliver, retryDelaysMs, log }, origin.agentId, sessionId, {
|
|
1111
|
+
runId: turnReportRunId(sessionId, id, status, { approvalId, answerId }),
|
|
1112
|
+
taskSessionId: sessionId,
|
|
1113
|
+
title: origin.title,
|
|
1114
|
+
status,
|
|
1115
|
+
text,
|
|
1116
|
+
...target,
|
|
1117
|
+
...(origin.hop > 0 ? { hop: origin.hop } : {}),
|
|
1118
|
+
...roomFrom(origin.sessionId),
|
|
1119
|
+
// A progress update moves this send's durable mark to its turn
|
|
1120
|
+
// (agent-home-delivery.mjs enqueueTask), in the same write.
|
|
1121
|
+
...(status === 'working' ? { progressWatchId: id, answerId } : {}),
|
|
1122
|
+
// A turn's end brings back the files it produced, when the agent
|
|
1123
|
+
// may receive them (reportOrigin `textOnly`).
|
|
1124
|
+
...(status === 'blocked' || status === 'working'
|
|
1125
|
+
? {}
|
|
1126
|
+
: {
|
|
1127
|
+
watchId: id,
|
|
1128
|
+
...(turn.sendIds.length > 1 ? { watchIds: turn.sendIds } : {}),
|
|
1129
|
+
...progressOf(id, answerId),
|
|
1130
|
+
...(origin.textOnly ? {} : await artifactFields(sessionId, id, turn.runtime)),
|
|
1131
|
+
}),
|
|
1132
|
+
})
|
|
1133
|
+
);
|
|
1134
|
+
const report = (status, text = '', options = {}) =>
|
|
1135
|
+
deliverReport(status, text, options).catch((error) =>
|
|
1136
|
+
log(`report of ${sessionId} failed: ${error?.message || error}`)
|
|
1137
|
+
);
|
|
1138
|
+
return { deliverReport, report };
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/** The agent's work in `sessionId` for send `id`: owned from the send's
|
|
1142
|
+
* acceptance until it settles or is cancelled, whether or not it reports. A
|
|
1143
|
+
* send that holds no report watch keeps a durable record of the work instead
|
|
1144
|
+
* (`watches.own`), so a restart re-attaches it (recover); the writes of one
|
|
1145
|
+
* send go in order, so a record never outlives its work. */
|
|
1146
|
+
function sendWork({ watches, owner, log }, { sessionId, id, agentId }) {
|
|
1147
|
+
let owning = true;
|
|
1148
|
+
let recorded = false;
|
|
1149
|
+
let recording = Promise.resolve();
|
|
1150
|
+
const record = (write) => {
|
|
1151
|
+
recording = recording
|
|
1152
|
+
.then(write)
|
|
1153
|
+
.catch((error) => log(`recording the work of ${sessionId} failed: ${error?.message || error}`));
|
|
1154
|
+
return recording;
|
|
1155
|
+
};
|
|
1156
|
+
return {
|
|
1157
|
+
/** The work of every send `ids` this turn answered ends together: one
|
|
1158
|
+
* write drops all their records (a send standing down for the turn's
|
|
1159
|
+
* first leaves its record to that one write). */
|
|
1160
|
+
disown(ids = [id]) {
|
|
1161
|
+
owning = false;
|
|
1162
|
+
for (const done of ids) endBotWork(done);
|
|
1163
|
+
if (recorded || ids.length > 1) void record(() => watches.disown?.(ids));
|
|
1164
|
+
},
|
|
1165
|
+
/** Standing down for the turn's first send, which owns the same work. */
|
|
1166
|
+
standDown() {
|
|
1167
|
+
owning = false;
|
|
1168
|
+
endBotWork(id);
|
|
1169
|
+
},
|
|
1170
|
+
/** The send was accepted: unless it already settled (nothing then is left
|
|
1171
|
+
* to end the work), the work is the agent's, recorded when `notify` is off.
|
|
1172
|
+
* Returns the recording to await, or nothing when there is none. */
|
|
1173
|
+
claim(notify) {
|
|
1174
|
+
if (!owning) return undefined;
|
|
1175
|
+
noteBotWork({ runId: id, sessionId, agentId });
|
|
1176
|
+
if (notify) return undefined;
|
|
1177
|
+
recorded = true;
|
|
1178
|
+
return record(() => watches.own?.({ runId: id, agentId, taskSessionId: sessionId, owner, at: Date.now() }));
|
|
1179
|
+
},
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/** Submit `prompt` as the origin agent's turn; its end (and a wait for the
|
|
1184
|
+
* user in it) reports to the agent's home. Resolves on intake. `submit`
|
|
1185
|
+
* takes the turn in another way than a prompt to `sessionId` (a message
|
|
1186
|
+
* into a group room, whose member answers in `sessionId`): given `{ id,
|
|
1187
|
+
* onSettled }`, it resolves `{ accepted }`. */
|
|
1188
|
+
async function runAgentTurn(
|
|
1189
|
+
deps,
|
|
1190
|
+
sessionId,
|
|
1191
|
+
prompt,
|
|
1192
|
+
origin,
|
|
1193
|
+
id = `atask_${randomUUID()}`,
|
|
1194
|
+
beforeReport = null,
|
|
1195
|
+
submit = null
|
|
1196
|
+
) {
|
|
1197
|
+
const { watches, followToFinal, settlement, processToken, log } = deps;
|
|
1198
|
+
const sessions = deps.service();
|
|
1199
|
+
const { notify, noReport } = await watchTurn({ watches, owner: processToken, log }, sessionId, origin, id);
|
|
1200
|
+
const dropWatch = () =>
|
|
1201
|
+
watches.remove(id).catch((error) => log(`dropping the watch of ${sessionId} failed: ${error?.message || error}`));
|
|
1202
|
+
// `sendIds`: the sends this turn's end closes: this one, plus any that its
|
|
1203
|
+
// turn answered too (createSendSettlement).
|
|
1204
|
+
const turn = { runtime: null, sendIds: [id] };
|
|
1205
|
+
const { deliverReport, report } = sendReporter(deps, { sessionId, id, origin, beforeReport, turn });
|
|
1206
|
+
let unwatch = () => {};
|
|
1207
|
+
const work = sendWork({ watches, owner: processToken, log }, { sessionId, id, agentId: origin.agentId });
|
|
1208
|
+
// Set once the turn ended completed: the work and any report wait for the
|
|
1209
|
+
// final answer.
|
|
1210
|
+
let follow = null;
|
|
1211
|
+
let result;
|
|
1212
|
+
/** This send ends the turn `detail` for the sends `answered` (createSendSettlement). */
|
|
1213
|
+
const settleTurn = (detail, answered) => {
|
|
1214
|
+
turn.sendIds = answered;
|
|
1215
|
+
const status = turnStatus(detail);
|
|
1216
|
+
const text = detail?.result?.content || detail?.error || '';
|
|
1217
|
+
// A failed or cancelled turn ends at once; a completed one ends with
|
|
1218
|
+
// the task's final answer (followToFinal).
|
|
1219
|
+
if (status !== 'completed') {
|
|
1220
|
+
unwatch();
|
|
1221
|
+
work.disown(turn.sendIds);
|
|
1222
|
+
if (notify) void report(status, text);
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
follow = followToFinal({
|
|
1226
|
+
sessionId,
|
|
1227
|
+
runId: id,
|
|
1228
|
+
runtime: turn.runtime,
|
|
1229
|
+
immediate: { status, text },
|
|
1230
|
+
finish: async (endStatus, endText, answerId) => {
|
|
1231
|
+
unwatch();
|
|
1232
|
+
work.disown(turn.sendIds);
|
|
1233
|
+
if (notify) await report(endStatus, endText, { answerId });
|
|
1234
|
+
},
|
|
1235
|
+
// Each other answer of its chain: a progress update that wakes nobody
|
|
1236
|
+
// (only a send that reports gives them).
|
|
1237
|
+
...(notify ? { interim: (answer) => deliverReport('working', answer.text, { answerId: answer.id }) } : {}),
|
|
1238
|
+
});
|
|
1239
|
+
follow.check();
|
|
1240
|
+
};
|
|
1241
|
+
try {
|
|
1242
|
+
const runtime = await sessions.materializeSession(sessionId);
|
|
1243
|
+
turn.runtime = runtime;
|
|
1244
|
+
const onState = notify ? approvalWaitReporter(id, report) : null;
|
|
1245
|
+
unwatch =
|
|
1246
|
+
runtime?.subscribe?.(() => {
|
|
1247
|
+
follow?.check();
|
|
1248
|
+
onState?.(runtime.getState?.() || {});
|
|
1249
|
+
}) || unwatch;
|
|
1250
|
+
// A bot's send to a session that is not its own task session reaches the
|
|
1251
|
+
// model inside the sender envelope (`envelopeName`, set by reportOrigin).
|
|
1252
|
+
const sent = origin.envelopeName
|
|
1253
|
+
? wrapAgentMessage(prompt, { name: origin.envelopeName, agentId: origin.agentId })
|
|
1254
|
+
: prompt;
|
|
1255
|
+
const take = submit ?? ((options) => sessions.submitSession({ sessionId, prompt: sent, options }));
|
|
1256
|
+
const seq = settlement.nextSeq();
|
|
1257
|
+
result = await take({
|
|
1258
|
+
id,
|
|
1259
|
+
mode: 'prompt',
|
|
1260
|
+
transcriptMeta: sendTranscriptMeta(origin),
|
|
1261
|
+
// A room answers this send in a turn under another submission id (one
|
|
1262
|
+
// turn for several sends, or a fresh turn for one cut short): the
|
|
1263
|
+
// watch follows that turn, recorded before it runs.
|
|
1264
|
+
...(notify ? { follow: (submissionId) => watches.follow?.(id, submissionId) } : {}),
|
|
1265
|
+
onSettled: (detail) =>
|
|
1266
|
+
settlement.join(detail, origin, {
|
|
1267
|
+
seq,
|
|
1268
|
+
id,
|
|
1269
|
+
notify,
|
|
1270
|
+
unwatch: () => {
|
|
1271
|
+
unwatch();
|
|
1272
|
+
work.standDown();
|
|
1273
|
+
},
|
|
1274
|
+
settle: (answered) => settleTurn(detail, answered),
|
|
1275
|
+
}),
|
|
1276
|
+
});
|
|
1277
|
+
} catch (error) {
|
|
1278
|
+
unwatch();
|
|
1279
|
+
work.disown();
|
|
1280
|
+
if (notify) await dropWatch();
|
|
1281
|
+
throw error;
|
|
1282
|
+
}
|
|
1283
|
+
const accepted = result?.accepted === true;
|
|
1284
|
+
if (!accepted) {
|
|
1285
|
+
unwatch();
|
|
1286
|
+
work.disown();
|
|
1287
|
+
if (notify) await dropWatch();
|
|
1288
|
+
} else {
|
|
1289
|
+
const recording = work.claim(notify);
|
|
1290
|
+
if (recording) await recording;
|
|
1291
|
+
}
|
|
1292
|
+
return sendResult(accepted, notify, id, noReport);
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/** Daemon start: re-attach every reporting turn an earlier process watched,
|
|
1296
|
+
* and every turn of a bot's that reported nothing but whose work it owned
|
|
1297
|
+
* (nothing is reported for those, they are only the bot's work again).
|
|
1298
|
+
* Resolves once each is reported or watched. */
|
|
1299
|
+
async function recoverTaskWatches({ watches, settleAgentResults, rearm, processToken, log }) {
|
|
1300
|
+
const earlier = (list) => list.filter((entry) => entry?.owner !== processToken);
|
|
1301
|
+
const pending = earlier(await watches.list());
|
|
1302
|
+
// A store without records of quiet work (`owned` is optional) has none; a
|
|
1303
|
+
// failed read of them never keeps the reporting turns from re-attaching.
|
|
1304
|
+
const quiet = earlier(
|
|
1305
|
+
await Promise.resolve(watches.owned?.() ?? []).catch((error) => {
|
|
1306
|
+
log(`reading the records of bot work failed: ${error?.message || error}`);
|
|
1307
|
+
return [];
|
|
1308
|
+
})
|
|
1309
|
+
);
|
|
1310
|
+
// The agents the restart killed become queued failure results first, so
|
|
1311
|
+
// each re-attached task session has them to take (followToFinal wakeOwed).
|
|
1312
|
+
if (pending.length) {
|
|
1313
|
+
await Promise.resolve()
|
|
1314
|
+
.then(settleAgentResults)
|
|
1315
|
+
.catch((error) => log(`settling the agents of the previous process failed: ${error?.message || error}`));
|
|
1316
|
+
}
|
|
1317
|
+
const reattach = (work, options) =>
|
|
1318
|
+
rearm(work, options).catch((error) =>
|
|
1319
|
+
log(`re-attaching ${work?.taskSessionId} failed: ${error?.message || error}`)
|
|
1320
|
+
);
|
|
1321
|
+
await Promise.all([
|
|
1322
|
+
...pending.map((watch) => reattach(watch)),
|
|
1323
|
+
...quiet.map((work) => reattach(work, { quiet: true })),
|
|
1324
|
+
]);
|
|
1325
|
+
return pending.length + quiet.length;
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
/** The route a task session starts on: it runs in coding mode under Lead: an
|
|
1329
|
+
* explicit model wins, else the Lead route, else the last-used model. Never a
|
|
1330
|
+
* model-less session — the composer would show no model and its first turn
|
|
1331
|
+
* would fail. The catalog is read only when a display name or an effort
|
|
1332
|
+
* needs it. */
|
|
1333
|
+
async function taskRoute({ modelRoute, modelCatalog, leadRoute, lastUsedRoute, service }, args, callerSessionId) {
|
|
1334
|
+
let catalog = null;
|
|
1335
|
+
const catalogRows = async () => (catalog ??= await modelCatalog(callerSessionId));
|
|
1336
|
+
let route = clean(args.model)
|
|
1337
|
+
? await modelRoute(clean(args.model), async (text) =>
|
|
1338
|
+
routeByDisplayName(text, await catalogRows(), args.effort !== undefined)
|
|
1339
|
+
)
|
|
1340
|
+
: (await leadRoute()) || (await lastUsedRoute(service()));
|
|
1341
|
+
if (!route) throw new Error('session: no model to start the task session on. Choose a Lead model first.');
|
|
1342
|
+
// An explicit effort is the setting the menu's effort pick makes (session.effort,
|
|
1343
|
+
// Auto effort off); it wins over one a model ref or name carried.
|
|
1344
|
+
if (args.effort !== undefined) route = withEffort(route, args.effort, await catalogRows());
|
|
1345
|
+
return route;
|
|
1346
|
+
}
|
|
1347
|
+
|
|
1348
|
+
/** Nothing was sent after the worktree failure `error`: drop the empty task
|
|
1349
|
+
* session `sessionId` so a retry starts clean, then throw `error` (an
|
|
1350
|
+
* orphanSessionError when the session could not be dropped). */
|
|
1351
|
+
async function dropEmptyTaskSession(sessions, sessionId, error, log) {
|
|
1352
|
+
let dropFailure = null;
|
|
1353
|
+
try {
|
|
1354
|
+
const dropped = await sessions.configureSession(
|
|
1355
|
+
{ sessionId, action: 'deleteSession', args: [sessionId] },
|
|
1356
|
+
{ revision: 1 }
|
|
1357
|
+
);
|
|
1358
|
+
// A resolved `false` is a refusal, not a deletion.
|
|
1359
|
+
if (dropped?.value === false) dropFailure = new Error('the session service refused the deletion');
|
|
1360
|
+
} catch (deleteError) {
|
|
1361
|
+
dropFailure = deleteError;
|
|
1362
|
+
}
|
|
1363
|
+
if (dropFailure) log(`dropping ${sessionId} failed: ${dropFailure?.message || dropFailure}`);
|
|
1364
|
+
if (!dropFailure) throw error;
|
|
1365
|
+
throw orphanSessionError(error, sessionId, dropFailure);
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
/** Create the task session for `brief` and start it; returns at once.
|
|
1369
|
+
* `callerScope` is the caller's trusted worktree scope (worktree-access.mjs
|
|
1370
|
+
* worktreeCallerScope): provisioning a worktree is a mutation only an
|
|
1371
|
+
* attended conversation may make, so an explicit worktree from any other
|
|
1372
|
+
* caller is refused and the New task default keeps it in the Project folder. */
|
|
1373
|
+
async function createTaskSession(
|
|
1374
|
+
deps,
|
|
1375
|
+
args = {},
|
|
1376
|
+
{
|
|
1377
|
+
callerSessionId,
|
|
1378
|
+
callerAgentId,
|
|
1379
|
+
callerCwd,
|
|
1380
|
+
callerHop = 0,
|
|
1381
|
+
callerBotChain = [],
|
|
1382
|
+
callerScope,
|
|
1383
|
+
attachmentParts = [],
|
|
1384
|
+
} = {}
|
|
1385
|
+
) {
|
|
1386
|
+
const { service, listProjects, newTaskDefault, deliver, agentTurn, log } = deps;
|
|
1387
|
+
const { brief, location } = createRequest(args, callerAgentId, callerScope);
|
|
1388
|
+
const where = createWhere(args, await listProjects(), callerCwd, location);
|
|
1389
|
+
const requestId = `atask_${randomUUID()}`;
|
|
1390
|
+
const title = (clean(args.title) || clean(brief.split('\n')[0])).slice(0, TITLE_LIMIT);
|
|
1391
|
+
const taskOrigin = {
|
|
1392
|
+
agentId: callerAgentId,
|
|
1393
|
+
sessionId: callerSessionId,
|
|
1394
|
+
requestId,
|
|
1395
|
+
title,
|
|
1396
|
+
notify: args.notify !== false,
|
|
1397
|
+
};
|
|
1398
|
+
const route = await taskRoute(deps, args, callerSessionId);
|
|
1399
|
+
const sessions = service();
|
|
1400
|
+
// Where it starts: the `worktree` option, else the user's New task default
|
|
1401
|
+
// for a caller that may provision one. Decided before the session exists,
|
|
1402
|
+
// so a failure here leaves nothing behind.
|
|
1403
|
+
const explicit = location.mode === 'worktree';
|
|
1404
|
+
const inWorktree =
|
|
1405
|
+
explicit ||
|
|
1406
|
+
(location.mode === 'default' &&
|
|
1407
|
+
where.project &&
|
|
1408
|
+
callerScope?.attended === true &&
|
|
1409
|
+
(await newTaskDefault()) === 'worktree');
|
|
1410
|
+
const created = await sessions.createSession({
|
|
1411
|
+
...route,
|
|
1412
|
+
cwd: where.cwd,
|
|
1413
|
+
desktopSession: where.desktopSession,
|
|
1414
|
+
sessionProfile: { taskOrigin },
|
|
1415
|
+
});
|
|
1416
|
+
const sessionId = String(created?.sessionId || '');
|
|
1417
|
+
if (!sessionId) throw new Error('session: task session creation returned no session id');
|
|
1418
|
+
if (inWorktree) {
|
|
1419
|
+
try {
|
|
1420
|
+
await requestTaskWorktree(sessions, sessionId, {
|
|
1421
|
+
title,
|
|
1422
|
+
requestId,
|
|
1423
|
+
name: location.name,
|
|
1424
|
+
confirmLarge: args.confirm_large === true,
|
|
1425
|
+
explicit,
|
|
1426
|
+
});
|
|
1427
|
+
} catch (error) {
|
|
1428
|
+
await dropEmptyTaskSession(sessions, sessionId, error, log);
|
|
1429
|
+
}
|
|
1430
|
+
}
|
|
1431
|
+
const { ready, started } = startedGate();
|
|
1432
|
+
// The brief carries the files the agent forwarded (workspace-tools.mjs).
|
|
1433
|
+
const prompt = handoffContent(brief, attachmentParts);
|
|
1434
|
+
const botChain = botChainOf(callerBotChain);
|
|
1435
|
+
const { accepted, reportsBack, reportsBackReason } = await agentTurn(
|
|
1436
|
+
sessionId,
|
|
1437
|
+
prompt,
|
|
1438
|
+
{ ...taskOrigin, ...(callerHop > 0 ? { hop: callerHop } : {}), ...(botChain.length ? { botChain } : {}) },
|
|
1439
|
+
requestId,
|
|
1440
|
+
ready
|
|
1441
|
+
);
|
|
1442
|
+
if (!accepted) throw new Error(`session: task session ${sessionId} did not accept the brief`);
|
|
1443
|
+
if (reportsBack) {
|
|
1444
|
+
await deliver(callerAgentId, {
|
|
1445
|
+
runId: `${requestId}_started`,
|
|
1446
|
+
taskSessionId: sessionId,
|
|
1447
|
+
title,
|
|
1448
|
+
status: 'started',
|
|
1449
|
+
...roomFrom(callerSessionId),
|
|
1450
|
+
}).catch((error) => log(`start line of ${sessionId} failed: ${error?.message || error}`));
|
|
1451
|
+
}
|
|
1452
|
+
started();
|
|
1453
|
+
// Started, not done: the final answer is reported later.
|
|
1454
|
+
return {
|
|
1455
|
+
session_id: sessionId,
|
|
1456
|
+
status: 'started',
|
|
1457
|
+
title,
|
|
1458
|
+
project: where.project?.path ?? null,
|
|
1459
|
+
...(taskOrigin.notify && !reportsBack ? { reportsBack: false, reportsBackReason } : {}),
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
|
|
1463
|
+
const homeDelivery = () => import('./agent-home-delivery.mjs');
|
|
1464
|
+
|
|
1465
|
+
// The durable watch of each reporting turn until its end is queued. A bot's
|
|
1466
|
+
// turn that reports nothing (reports off, or a watch that could not be
|
|
1467
|
+
// stored) keeps a record of its work instead (`own`, `owned`, `disown`),
|
|
1468
|
+
// which no report and no hand-off follows: a restart re-attaches both.
|
|
1469
|
+
const HOME_DELIVERY_WATCHES = {
|
|
1470
|
+
add: async (watch) => (await homeDelivery()).watchAgentTask(watch),
|
|
1471
|
+
list: async () => (await homeDelivery()).agentTaskWatches(),
|
|
1472
|
+
remove: async (runId) => (await homeDelivery()).unwatchAgentTask(runId),
|
|
1473
|
+
follow: async (runId, submissionId) => (await homeDelivery()).followAgentTask(runId, submissionId),
|
|
1474
|
+
own: async (work) => (await homeDelivery()).ownAgentTask(work),
|
|
1475
|
+
owned: async () => (await homeDelivery()).agentOwnedTasks(),
|
|
1476
|
+
disown: async (runId) => (await homeDelivery()).disownAgentTask(runId),
|
|
1477
|
+
};
|
|
1478
|
+
|
|
1097
1479
|
export function createAgentTaskSessions({
|
|
1098
1480
|
getSessionService,
|
|
1099
|
-
deliver = async (agentId, report) => (await
|
|
1100
|
-
|
|
1101
|
-
// turn that reports nothing (reports off, or a watch that could not be
|
|
1102
|
-
// stored) keeps a record of its work instead (`own`, `owned`, `disown`),
|
|
1103
|
-
// which no report and no hand-off follows: a restart re-attaches both.
|
|
1104
|
-
watches = {
|
|
1105
|
-
add: async (watch) => (await import('./agent-home-delivery.mjs')).watchAgentTask(watch),
|
|
1106
|
-
list: async () => (await import('./agent-home-delivery.mjs')).agentTaskWatches(),
|
|
1107
|
-
remove: async (runId) => (await import('./agent-home-delivery.mjs')).unwatchAgentTask(runId),
|
|
1108
|
-
follow: async (runId, submissionId) =>
|
|
1109
|
-
(await import('./agent-home-delivery.mjs')).followAgentTask(runId, submissionId),
|
|
1110
|
-
own: async (work) => (await import('./agent-home-delivery.mjs')).ownAgentTask(work),
|
|
1111
|
-
owned: async () => (await import('./agent-home-delivery.mjs')).agentOwnedTasks(),
|
|
1112
|
-
disown: async (runId) => (await import('./agent-home-delivery.mjs')).disownAgentTask(runId),
|
|
1113
|
-
},
|
|
1481
|
+
deliver = async (agentId, report) => (await homeDelivery()).deliverAgentTask(agentId, report),
|
|
1482
|
+
watches = HOME_DELIVERY_WATCHES,
|
|
1114
1483
|
// Submissions still queued (durably) for a session after a restart.
|
|
1115
1484
|
queuedSubmissionIds = async (sessionId) =>
|
|
1116
1485
|
(await (await import('../tui/session/tui-steering-persist.mjs')).readTuiSteeringPersist(sessionId))
|
|
@@ -1177,189 +1546,18 @@ export function createAgentTaskSessions({
|
|
|
1177
1546
|
const followToFinal = (follow) =>
|
|
1178
1547
|
followTaskToFinal({ loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, log }, follow);
|
|
1179
1548
|
|
|
1180
|
-
const
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
beforeReport = null,
|
|
1193
|
-
submit = null
|
|
1194
|
-
) {
|
|
1195
|
-
const sessions = service();
|
|
1196
|
-
const target = origin.target === 'session' ? { target: 'session' } : {};
|
|
1197
|
-
const { notify, noReport } = await watchTurn({ watches, owner: processToken, log }, sessionId, origin, id);
|
|
1198
|
-
const dropWatch = () =>
|
|
1199
|
-
watches.remove(id).catch((error) => log(`dropping the watch of ${sessionId} failed: ${error?.message || error}`));
|
|
1200
|
-
let runtime = null;
|
|
1201
|
-
// The sends this turn's end closes: this one, plus any that its turn
|
|
1202
|
-
// answered too (createSendSettlement).
|
|
1203
|
-
let sendIds = [id];
|
|
1204
|
-
// A report that cannot be queued is retried in this process; when every try
|
|
1205
|
-
// fails the durable watch stays and restart recovery delivers it (the queue
|
|
1206
|
-
// dedupes by runId, so a retry never posts twice).
|
|
1207
|
-
// `answerId`: the turn whose answer a progress update or the end carries.
|
|
1208
|
-
// `deliverReport` rejects once every try failed (a progress update is
|
|
1209
|
-
// tried again at the follow's next check); `report` logs it instead.
|
|
1210
|
-
const deliverReport = (status, text = '', { approvalId = '', answerId = '' } = {}) =>
|
|
1211
|
-
Promise.resolve(beforeReport).then(async () =>
|
|
1212
|
-
deliverWithRetries({ deliver, retryDelaysMs, log }, origin.agentId, sessionId, {
|
|
1213
|
-
runId: turnReportRunId(sessionId, id, status, { approvalId, answerId }),
|
|
1214
|
-
taskSessionId: sessionId,
|
|
1215
|
-
title: origin.title,
|
|
1216
|
-
status,
|
|
1217
|
-
text,
|
|
1218
|
-
...target,
|
|
1219
|
-
...(origin.hop > 0 ? { hop: origin.hop } : {}),
|
|
1220
|
-
...roomFrom(origin.sessionId),
|
|
1221
|
-
// A progress update moves this send's durable mark to its turn
|
|
1222
|
-
// (agent-home-delivery.mjs enqueueTask), in the same write.
|
|
1223
|
-
...(status === 'working' ? { progressWatchId: id, answerId } : {}),
|
|
1224
|
-
// A turn's end brings back the files it produced, when the agent
|
|
1225
|
-
// may receive them (reportOrigin `textOnly`).
|
|
1226
|
-
...(status === 'blocked' || status === 'working'
|
|
1227
|
-
? {}
|
|
1228
|
-
: {
|
|
1229
|
-
watchId: id,
|
|
1230
|
-
...(sendIds.length > 1 ? { watchIds: sendIds } : {}),
|
|
1231
|
-
...progressOf(id, answerId),
|
|
1232
|
-
...(origin.textOnly ? {} : await artifactFields(sessionId, id, runtime)),
|
|
1233
|
-
}),
|
|
1234
|
-
})
|
|
1235
|
-
);
|
|
1236
|
-
const report = (status, text = '', options = {}) =>
|
|
1237
|
-
deliverReport(status, text, options).catch((error) =>
|
|
1238
|
-
log(`report of ${sessionId} failed: ${error?.message || error}`)
|
|
1239
|
-
);
|
|
1240
|
-
let unwatch = () => {};
|
|
1241
|
-
// The agent's work in `sessionId`: owned from the send's acceptance until
|
|
1242
|
-
// it settles or is cancelled, whether or not it reports (`notify`). A send
|
|
1243
|
-
// that holds no report watch keeps a durable record of the work instead
|
|
1244
|
-
// (`watches.own`), so a restart re-attaches it (recover); the writes of
|
|
1245
|
-
// one send go in order, so a record never outlives its work.
|
|
1246
|
-
let owning = true;
|
|
1247
|
-
let recorded = false;
|
|
1248
|
-
let recording = Promise.resolve();
|
|
1249
|
-
const record = (write) => {
|
|
1250
|
-
recording = recording
|
|
1251
|
-
.then(write)
|
|
1252
|
-
.catch((error) => log(`recording the work of ${sessionId} failed: ${error?.message || error}`));
|
|
1253
|
-
return recording;
|
|
1254
|
-
};
|
|
1255
|
-
// The work of every send `ids` this turn answered ends together: one
|
|
1256
|
-
// write drops all their records (a send standing down for the turn's
|
|
1257
|
-
// first leaves its record to that one write).
|
|
1258
|
-
const disown = (ids = [id]) => {
|
|
1259
|
-
owning = false;
|
|
1260
|
-
for (const done of ids) endBotWork(done);
|
|
1261
|
-
if (recorded || ids.length > 1) void record(() => watches.disown?.(ids));
|
|
1262
|
-
};
|
|
1263
|
-
const standDown = () => {
|
|
1264
|
-
unwatch();
|
|
1265
|
-
owning = false;
|
|
1266
|
-
endBotWork(id);
|
|
1267
|
-
};
|
|
1268
|
-
// Set once the turn ended completed: the work and any report wait for the
|
|
1269
|
-
// final answer.
|
|
1270
|
-
let follow = null;
|
|
1271
|
-
let result;
|
|
1272
|
-
/** This send ends the turn `detail` for the sends `answered` (createSendSettlement). */
|
|
1273
|
-
const settleTurn = (detail, answered) => {
|
|
1274
|
-
sendIds = answered;
|
|
1275
|
-
const status = turnStatus(detail);
|
|
1276
|
-
const text = detail?.result?.content || detail?.error || '';
|
|
1277
|
-
// A failed or cancelled turn ends at once; a completed one ends with
|
|
1278
|
-
// the task's final answer (followToFinal).
|
|
1279
|
-
if (status !== 'completed') {
|
|
1280
|
-
unwatch();
|
|
1281
|
-
disown(sendIds);
|
|
1282
|
-
if (notify) void report(status, text);
|
|
1283
|
-
return;
|
|
1284
|
-
}
|
|
1285
|
-
follow = followToFinal({
|
|
1286
|
-
sessionId,
|
|
1287
|
-
runId: id,
|
|
1288
|
-
runtime,
|
|
1289
|
-
immediate: { status, text },
|
|
1290
|
-
finish: async (endStatus, endText, answerId) => {
|
|
1291
|
-
unwatch();
|
|
1292
|
-
disown(sendIds);
|
|
1293
|
-
if (notify) await report(endStatus, endText, { answerId });
|
|
1294
|
-
},
|
|
1295
|
-
// Each other answer of its chain: a progress update that wakes nobody
|
|
1296
|
-
// (only a send that reports gives them).
|
|
1297
|
-
...(notify ? { interim: (answer) => deliverReport('working', answer.text, { answerId: answer.id }) } : {}),
|
|
1298
|
-
});
|
|
1299
|
-
follow.check();
|
|
1300
|
-
};
|
|
1301
|
-
try {
|
|
1302
|
-
runtime = await sessions.materializeSession(sessionId);
|
|
1303
|
-
const onState = notify ? approvalWaitReporter(id, report) : null;
|
|
1304
|
-
unwatch =
|
|
1305
|
-
runtime?.subscribe?.(() => {
|
|
1306
|
-
follow?.check();
|
|
1307
|
-
onState?.(runtime.getState?.() || {});
|
|
1308
|
-
}) || unwatch;
|
|
1309
|
-
// A bot's send to a session that is not its own task session reaches the
|
|
1310
|
-
// model inside the sender envelope (`envelopeName`, set by reportOrigin).
|
|
1311
|
-
const sent = origin.envelopeName
|
|
1312
|
-
? wrapAgentMessage(prompt, { name: origin.envelopeName, agentId: origin.agentId })
|
|
1313
|
-
: prompt;
|
|
1314
|
-
const take = submit ?? ((options) => sessions.submitSession({ sessionId, prompt: sent, options }));
|
|
1315
|
-
const seq = settlement.nextSeq();
|
|
1316
|
-
result = await take({
|
|
1317
|
-
id,
|
|
1318
|
-
mode: 'prompt',
|
|
1319
|
-
transcriptMeta: sendTranscriptMeta(origin),
|
|
1320
|
-
// A room answers this send in a turn under another submission id (one
|
|
1321
|
-
// turn for several sends, or a fresh turn for one cut short): the
|
|
1322
|
-
// watch follows that turn, recorded before it runs.
|
|
1323
|
-
...(notify ? { follow: (submissionId) => watches.follow?.(id, submissionId) } : {}),
|
|
1324
|
-
onSettled: (detail) =>
|
|
1325
|
-
settlement.join(detail, origin, {
|
|
1326
|
-
seq,
|
|
1327
|
-
id,
|
|
1328
|
-
notify,
|
|
1329
|
-
// Standing down for the turn's first send, which owns the same work.
|
|
1330
|
-
unwatch: standDown,
|
|
1331
|
-
settle: (answered) => settleTurn(detail, answered),
|
|
1332
|
-
}),
|
|
1333
|
-
});
|
|
1334
|
-
} catch (error) {
|
|
1335
|
-
unwatch();
|
|
1336
|
-
disown();
|
|
1337
|
-
if (notify) await dropWatch();
|
|
1338
|
-
throw error;
|
|
1339
|
-
}
|
|
1340
|
-
const accepted = result?.accepted === true;
|
|
1341
|
-
if (!accepted) {
|
|
1342
|
-
unwatch();
|
|
1343
|
-
disown();
|
|
1344
|
-
if (notify) await dropWatch();
|
|
1345
|
-
} else if (owning) {
|
|
1346
|
-
// Unless it already settled: nothing then is left to end the work.
|
|
1347
|
-
noteBotWork({ runId: id, sessionId, agentId: origin.agentId });
|
|
1348
|
-
if (!notify) {
|
|
1349
|
-
recorded = true;
|
|
1350
|
-
await record(() =>
|
|
1351
|
-
watches.own?.({
|
|
1352
|
-
runId: id,
|
|
1353
|
-
agentId: origin.agentId,
|
|
1354
|
-
taskSessionId: sessionId,
|
|
1355
|
-
owner: processToken,
|
|
1356
|
-
at: Date.now(),
|
|
1357
|
-
})
|
|
1358
|
-
);
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
return sendResult(accepted, notify, id, noReport);
|
|
1362
|
-
}
|
|
1549
|
+
const turnDeps = {
|
|
1550
|
+
service,
|
|
1551
|
+
watches,
|
|
1552
|
+
deliver,
|
|
1553
|
+
retryDelaysMs,
|
|
1554
|
+
artifactFields,
|
|
1555
|
+
followToFinal,
|
|
1556
|
+
settlement: createSendSettlement(log),
|
|
1557
|
+
processToken,
|
|
1558
|
+
log,
|
|
1559
|
+
};
|
|
1560
|
+
const agentTurn = (...turnArgs) => runAgentTurn(turnDeps, ...turnArgs);
|
|
1363
1561
|
|
|
1364
1562
|
const rearm = (watch, options) =>
|
|
1365
1563
|
rearmTaskWatch(
|
|
@@ -1377,167 +1575,26 @@ export function createAgentTaskSessions({
|
|
|
1377
1575
|
options
|
|
1378
1576
|
);
|
|
1379
1577
|
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
return [];
|
|
1393
|
-
})
|
|
1394
|
-
);
|
|
1395
|
-
// The agents the restart killed become queued failure results first, so
|
|
1396
|
-
// each re-attached task session has them to take (followToFinal wakeOwed).
|
|
1397
|
-
if (pending.length) {
|
|
1398
|
-
await Promise.resolve()
|
|
1399
|
-
.then(settleAgentResults)
|
|
1400
|
-
.catch((error) => log(`settling the agents of the previous process failed: ${error?.message || error}`));
|
|
1401
|
-
}
|
|
1402
|
-
const reattach = (work, options) =>
|
|
1403
|
-
rearm(work, options).catch((error) =>
|
|
1404
|
-
log(`re-attaching ${work?.taskSessionId} failed: ${error?.message || error}`)
|
|
1405
|
-
);
|
|
1406
|
-
await Promise.all([
|
|
1407
|
-
...pending.map((watch) => reattach(watch)),
|
|
1408
|
-
...quiet.map((work) => reattach(work, { quiet: true })),
|
|
1409
|
-
]);
|
|
1410
|
-
return pending.length + quiet.length;
|
|
1411
|
-
}
|
|
1412
|
-
|
|
1413
|
-
/** Create the task session for `brief` and start it; returns at once.
|
|
1414
|
-
* `callerScope` is the caller's trusted worktree scope (worktree-access.mjs
|
|
1415
|
-
* worktreeCallerScope): provisioning a worktree is a mutation only an
|
|
1416
|
-
* attended conversation may make, so an explicit worktree from any other
|
|
1417
|
-
* caller is refused and the New task default keeps it in the Project folder. */
|
|
1418
|
-
async function create(
|
|
1419
|
-
args = {},
|
|
1420
|
-
{
|
|
1421
|
-
callerSessionId,
|
|
1422
|
-
callerAgentId,
|
|
1423
|
-
callerCwd,
|
|
1424
|
-
callerHop = 0,
|
|
1425
|
-
callerBotChain = [],
|
|
1426
|
-
callerScope,
|
|
1427
|
-
attachmentParts = [],
|
|
1428
|
-
} = {}
|
|
1429
|
-
) {
|
|
1430
|
-
const { brief, location } = createRequest(args, callerAgentId, callerScope);
|
|
1431
|
-
const where = createWhere(args, await listProjects(), callerCwd, location);
|
|
1432
|
-
const requestId = `atask_${randomUUID()}`;
|
|
1433
|
-
const title = (clean(args.title) || clean(brief.split('\n')[0])).slice(0, TITLE_LIMIT);
|
|
1434
|
-
const taskOrigin = {
|
|
1435
|
-
agentId: callerAgentId,
|
|
1436
|
-
sessionId: callerSessionId,
|
|
1437
|
-
requestId,
|
|
1438
|
-
title,
|
|
1439
|
-
notify: args.notify !== false,
|
|
1440
|
-
};
|
|
1441
|
-
// A task session runs in coding mode under Lead: an explicit model wins,
|
|
1442
|
-
// else the Lead route, else the last-used model. Never a model-less
|
|
1443
|
-
// session — the composer would show no model and its first turn would fail.
|
|
1444
|
-
// The catalog is read only when a display name or an effort needs it.
|
|
1445
|
-
let catalog = null;
|
|
1446
|
-
const catalogRows = async () => (catalog ??= await modelCatalog(callerSessionId));
|
|
1447
|
-
let route = clean(args.model)
|
|
1448
|
-
? await modelRoute(clean(args.model), async (text) =>
|
|
1449
|
-
routeByDisplayName(text, await catalogRows(), args.effort !== undefined)
|
|
1450
|
-
)
|
|
1451
|
-
: (await leadRoute()) || (await lastUsedRoute(service()));
|
|
1452
|
-
if (!route) throw new Error('session: no model to start the task session on. Choose a Lead model first.');
|
|
1453
|
-
// An explicit effort is the setting the menu's effort pick makes (session.effort,
|
|
1454
|
-
// Auto effort off); it wins over one a model ref or name carried.
|
|
1455
|
-
if (args.effort !== undefined) route = withEffort(route, args.effort, await catalogRows());
|
|
1456
|
-
const sessions = service();
|
|
1457
|
-
// Where it starts: the `worktree` option, else the user's New task default
|
|
1458
|
-
// for a caller that may provision one. Decided before the session exists,
|
|
1459
|
-
// so a failure here leaves nothing behind.
|
|
1460
|
-
const explicit = location.mode === 'worktree';
|
|
1461
|
-
const inWorktree =
|
|
1462
|
-
explicit ||
|
|
1463
|
-
(location.mode === 'default' &&
|
|
1464
|
-
where.project &&
|
|
1465
|
-
callerScope?.attended === true &&
|
|
1466
|
-
(await newTaskDefault()) === 'worktree');
|
|
1467
|
-
const created = await sessions.createSession({
|
|
1468
|
-
...route,
|
|
1469
|
-
cwd: where.cwd,
|
|
1470
|
-
desktopSession: where.desktopSession,
|
|
1471
|
-
sessionProfile: { taskOrigin },
|
|
1472
|
-
});
|
|
1473
|
-
const sessionId = String(created?.sessionId || '');
|
|
1474
|
-
if (!sessionId) throw new Error('session: task session creation returned no session id');
|
|
1475
|
-
if (inWorktree) {
|
|
1476
|
-
try {
|
|
1477
|
-
await requestTaskWorktree(sessions, sessionId, {
|
|
1478
|
-
title,
|
|
1479
|
-
requestId,
|
|
1480
|
-
name: location.name,
|
|
1481
|
-
confirmLarge: args.confirm_large === true,
|
|
1482
|
-
explicit,
|
|
1483
|
-
});
|
|
1484
|
-
} catch (error) {
|
|
1485
|
-
// Nothing was sent: drop the empty session so a retry starts clean.
|
|
1486
|
-
let dropFailure = null;
|
|
1487
|
-
try {
|
|
1488
|
-
const dropped = await sessions.configureSession(
|
|
1489
|
-
{ sessionId, action: 'deleteSession', args: [sessionId] },
|
|
1490
|
-
{ revision: 1 }
|
|
1491
|
-
);
|
|
1492
|
-
// A resolved `false` is a refusal, not a deletion.
|
|
1493
|
-
if (dropped?.value === false) dropFailure = new Error('the session service refused the deletion');
|
|
1494
|
-
} catch (deleteError) {
|
|
1495
|
-
dropFailure = deleteError;
|
|
1496
|
-
}
|
|
1497
|
-
if (dropFailure) log(`dropping ${sessionId} failed: ${dropFailure?.message || dropFailure}`);
|
|
1498
|
-
if (!dropFailure) throw error;
|
|
1499
|
-
throw orphanSessionError(error, sessionId, dropFailure);
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
const { ready, started } = startedGate();
|
|
1503
|
-
// The brief carries the files the agent forwarded (workspace-tools.mjs).
|
|
1504
|
-
const prompt = handoffContent(brief, attachmentParts);
|
|
1505
|
-
const botChain = botChainOf(callerBotChain);
|
|
1506
|
-
const { accepted, reportsBack, reportsBackReason } = await agentTurn(
|
|
1507
|
-
sessionId,
|
|
1508
|
-
prompt,
|
|
1509
|
-
{ ...taskOrigin, ...(callerHop > 0 ? { hop: callerHop } : {}), ...(botChain.length ? { botChain } : {}) },
|
|
1510
|
-
requestId,
|
|
1511
|
-
ready
|
|
1512
|
-
);
|
|
1513
|
-
if (!accepted) throw new Error(`session: task session ${sessionId} did not accept the brief`);
|
|
1514
|
-
if (reportsBack) {
|
|
1515
|
-
await deliver(callerAgentId, {
|
|
1516
|
-
runId: `${requestId}_started`,
|
|
1517
|
-
taskSessionId: sessionId,
|
|
1518
|
-
title,
|
|
1519
|
-
status: 'started',
|
|
1520
|
-
...roomFrom(callerSessionId),
|
|
1521
|
-
}).catch((error) => log(`start line of ${sessionId} failed: ${error?.message || error}`));
|
|
1522
|
-
}
|
|
1523
|
-
started();
|
|
1524
|
-
// Started, not done: the final answer is reported later.
|
|
1525
|
-
return {
|
|
1526
|
-
session_id: sessionId,
|
|
1527
|
-
status: 'started',
|
|
1528
|
-
title,
|
|
1529
|
-
project: where.project?.path ?? null,
|
|
1530
|
-
...(taskOrigin.notify && !reportsBack ? { reportsBack: false, reportsBackReason } : {}),
|
|
1531
|
-
};
|
|
1532
|
-
}
|
|
1578
|
+
const createDeps = {
|
|
1579
|
+
service,
|
|
1580
|
+
listProjects,
|
|
1581
|
+
newTaskDefault,
|
|
1582
|
+
modelRoute,
|
|
1583
|
+
modelCatalog,
|
|
1584
|
+
leadRoute,
|
|
1585
|
+
lastUsedRoute,
|
|
1586
|
+
deliver,
|
|
1587
|
+
agentTurn,
|
|
1588
|
+
log,
|
|
1589
|
+
};
|
|
1533
1590
|
|
|
1534
1591
|
const reportOrigin = (sessionId, agentId, callerSessionId) =>
|
|
1535
1592
|
sendReportOrigin({ loadSession, agentName }, sessionId, agentId, callerSessionId);
|
|
1536
1593
|
|
|
1537
1594
|
return {
|
|
1538
|
-
create,
|
|
1595
|
+
create: (args, caller) => createTaskSession(createDeps, args, caller),
|
|
1539
1596
|
reportOrigin,
|
|
1540
|
-
recover,
|
|
1597
|
+
recover: () => recoverTaskWatches({ watches, settleAgentResults, rearm, processToken, log }),
|
|
1541
1598
|
send: (sessionId, prompt, origin, submit = null) => agentTurn(sessionId, prompt, origin, undefined, null, submit),
|
|
1542
1599
|
};
|
|
1543
1600
|
}
|