mixdog 1.3.1 → 1.3.3
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/README.md +8 -4
- package/package.json +8 -8
- package/scripts/native-release-status.mjs +238 -0
- package/scripts/verify-runtime-dependency-versions.mjs +158 -0
- package/src/agents/reviewer/AGENT.md +5 -7
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +9 -1
- package/src/defaults/skills/browser-use/SKILL.md +6 -3
- package/src/defaults/skills/local-provider/SKILL.md +33 -6
- package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
- package/src/defaults/skills/setup/SKILL.md +23 -1
- package/src/defaults/skills/setup/references/actions.md +50 -3
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +7 -3
- package/src/defaults/skills/visualize/SKILL.md +9 -6
- package/src/defaults/skills/visualize/references/craft.md +8 -1
- package/src/help.mjs +1 -1
- package/src/lib/keychain-cjs.cjs +4 -1
- package/src/lib/rules-builder.cjs +14 -7
- package/src/rules/agent-mode/AGENT_MODE.md +8 -5
- package/src/rules/agent-mode/MIXY.md +6 -8
- package/src/rules/lead/LEAD.md +7 -10
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- package/src/rules/shared/45-destructive.md +5 -1
- package/src/rules/shared/60-verification.md +6 -4
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
- package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
- package/src/runtime/agent/orchestrator/config.mjs +11 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
- package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
- package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
- package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
- package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
- package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
- package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
- package/src/runtime/attachments/attachment-reference.mjs +155 -0
- package/src/runtime/attachments/legacy-office.mjs +110 -0
- package/src/runtime/attachments/limits.mjs +5 -1
- package/src/runtime/attachments/office-extract.mjs +3 -6
- package/src/runtime/attachments/pdf-extract.mjs +84 -10
- package/src/runtime/attachments/prompt-restore.mjs +70 -0
- package/src/runtime/attachments/store.mjs +109 -33
- package/src/runtime/browser-bridge/client.mjs +73 -20
- package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +14 -7
- package/src/runtime/channels/lib/state-file.mjs +15 -15
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
- package/src/runtime/channels/lib/worker-main.mjs +3 -2
- package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
- package/src/runtime/effort-judge/judge-client.mjs +66 -16
- package/src/runtime/effort-judge/model-install.mjs +47 -7
- package/src/runtime/local-provider/asset-installer.mjs +10 -4
- package/src/runtime/local-provider/catalog.mjs +5 -2
- package/src/runtime/local-provider/registered-models.mjs +64 -27
- package/src/runtime/local-provider/server.mjs +2 -3
- package/src/runtime/media/adapters/image-api.mjs +89 -0
- package/src/runtime/media/auth.mjs +64 -1
- package/src/runtime/media/catalog.mjs +66 -4
- package/src/runtime/media/jobs.mjs +4 -0
- package/src/runtime/media/lanes.mjs +46 -5
- package/src/runtime/media/media-usage.mjs +17 -0
- package/src/runtime/media/renditions.mjs +15 -15
- package/src/runtime/media/store.mjs +36 -35
- package/src/runtime/media/tool.mjs +8 -1
- package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
- package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
- package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
- package/src/runtime/memory/lib/pg/process.mjs +8 -4
- package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
- package/src/runtime/office/core/journal.mjs +7 -14
- package/src/runtime/office/design/library/design-library-core.mjs +4 -16
- package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
- package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
- package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
- package/src/runtime/office/pdf/pdf-render.mjs +30 -18
- package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +135 -29
- package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
- package/src/runtime/shared/agent-avatar.mjs +3 -2
- package/src/runtime/shared/agent-kind-migration.mjs +200 -0
- package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
- package/src/runtime/shared/agent-message-envelope.mjs +44 -0
- package/src/runtime/shared/agent-reap-state.d.mts +4 -0
- package/src/runtime/shared/agent-room-host.d.mts +31 -0
- package/src/runtime/shared/agent-room-host.mjs +58 -0
- package/src/runtime/shared/agent-store.mjs +24 -7
- package/src/runtime/shared/agent-task-starts.mjs +27 -0
- package/src/runtime/shared/atomic-file.d.mts +50 -0
- package/src/runtime/shared/atomic-file.mjs +394 -132
- package/src/runtime/shared/background-tasks.mjs +7 -4
- package/src/runtime/shared/bot-hops.mjs +35 -0
- package/src/runtime/shared/bot-split-migration.mjs +1 -1
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/defaults-separation.mjs +6 -2
- package/src/runtime/shared/delegated-approval-host.mjs +74 -0
- package/src/runtime/shared/err-text.mjs +4 -0
- package/src/runtime/shared/error-presentation.mjs +22 -4
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/process-lifecycle.mjs +8 -12
- package/src/runtime/shared/projects.mjs +50 -41
- package/src/runtime/shared/provider-accounts.mjs +17 -12
- package/src/runtime/shared/redact-directories.mjs +18 -0
- package/src/runtime/shared/remove-directory.mjs +28 -0
- package/src/runtime/shared/routine-trigger.d.mts +6 -1
- package/src/runtime/shared/routine-trigger.mjs +14 -6
- package/src/runtime/shared/run-child.d.mts +3 -0
- package/src/runtime/shared/run-child.mjs +24 -3
- package/src/runtime/shared/session-baseline-store.mjs +8 -2
- package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
- package/src/runtime/shared/session-sidebar-host.mjs +16 -3
- package/src/runtime/shared/singleton-owner.mjs +7 -6
- package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
- package/src/runtime/shared/staged-update.mjs +4 -2
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
- package/src/runtime/shared/tool-execution-contract.mjs +10 -0
- package/src/runtime/shared/tool-work-units.mjs +12 -9
- package/src/runtime/shared/transcript-metadata.mjs +28 -3
- package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
- package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
- package/src/runtime/shared/turn-snapshot.mjs +277 -124
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +92 -61
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
- package/src/runtime/shared/worktrees/contract.mjs +11 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
- package/src/runtime/shared/worktrees/manager.mjs +419 -52
- package/src/runtime/shared/worktrees/registry.mjs +29 -27
- package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/tidy/process.mjs +41 -8
- package/src/runtime/tidy/resolve.mjs +10 -7
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +542 -204
- package/src/session-runtime/agent-profile-changes.mjs +16 -0
- package/src/session-runtime/agent-room-delivery.mjs +81 -35
- package/src/session-runtime/agent-task-sessions.mjs +811 -179
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +48 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +16 -7
- package/src/session-runtime/boot/infrastructure.mjs +14 -2
- package/src/session-runtime/boot/providers.mjs +2 -2
- package/src/session-runtime/boot/shared.mjs +12 -6
- package/src/session-runtime/boot/tools.mjs +23 -3
- package/src/session-runtime/bot-status.mjs +432 -0
- package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
- package/src/session-runtime/config-lifecycle.mjs +60 -55
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- package/src/session-runtime/held-requests.mjs +31 -33
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- package/src/session-runtime/lifecycle/teardown.mjs +11 -3
- package/src/session-runtime/mcp-glue/connect.mjs +100 -186
- package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
- package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
- package/src/session-runtime/media-api.mjs +5 -2
- package/src/session-runtime/model-recency.mjs +1 -0
- package/src/session-runtime/model-route/route-tuning.mjs +4 -1
- package/src/session-runtime/model-route/set-route.mjs +61 -1
- package/src/session-runtime/owned-automation-run.mjs +8 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
- package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
- package/src/session-runtime/route-resolution.mjs +16 -8
- package/src/session-runtime/route-state.mjs +1 -59
- package/src/session-runtime/routine-home-turn.mjs +5 -3
- package/src/session-runtime/runtime-bootstrap.mjs +5 -3
- package/src/session-runtime/runtime-paths.mjs +11 -5
- package/src/session-runtime/runtime-review-api.mjs +4 -1
- package/src/session-runtime/runtime-tunables.mjs +6 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
- package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
- package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
- package/src/session-runtime/services/agent-tool.mjs +6 -0
- package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
- package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
- package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
- package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
- package/src/session-runtime/services/plugin-admin.mjs +26 -26
- package/src/session-runtime/services/provider-admin.mjs +26 -6
- package/src/session-runtime/services/schedule-access.mjs +33 -4
- package/src/session-runtime/services/seeds.mjs +4 -3
- package/src/session-runtime/services/usage-dashboard.mjs +1 -1
- package/src/session-runtime/services/usage-stats-model.mjs +2 -1
- package/src/session-runtime/services/worktree-access.mjs +27 -0
- package/src/session-runtime/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/session-hooks.mjs +9 -17
- package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +12 -2
- package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
- package/src/session-runtime/skills-api.mjs +12 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
- package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
- package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
- package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
- package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
- package/src/session-runtime/tool-policy-refresh.mjs +48 -369
- package/src/session-runtime/tool-surface.mjs +10 -4
- package/src/session-runtime/turn/session-ops.mjs +8 -0
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +7 -2
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
- package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
- package/src/session-runtime/workspace/transition-steps.mjs +1 -18
- package/src/session-runtime/workspace/transitions.mjs +279 -67
- package/src/session-runtime/workspace/workspace-state.mjs +7 -6
- package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-lag-profiler.mjs +3 -1
- package/src/standalone/daemon-session-control.mjs +173 -15
- package/src/standalone/daemon-telemetry.mjs +9 -0
- package/src/standalone/daemon.mjs +33 -10
- package/src/standalone/local-session-runtime.mjs +33 -16
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +8 -0
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
- package/src/standalone/session-service/agent-route-follow.mjs +69 -0
- package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
- package/src/standalone/session-service.mjs +21 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +36 -9
- package/src/standalone/staged-import.mjs +47 -0
- package/src/tui/App.jsx +2 -0
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- package/src/tui/app/message-selector.mjs +15 -2
- package/src/tui/app/model-options.mjs +16 -2
- package/src/tui/app/model-picker.mjs +2 -1
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
- package/src/tui/dist/index.mjs +617 -369
- package/src/tui/paste-attachments.mjs +0 -1
- package/src/tui/paste-text-policy.mjs +4 -5
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/notice-surface.mjs +3 -0
- package/src/tui/session/session-api/intake/abort.mjs +52 -18
- package/src/tui/session/session-api/intake/rewind.mjs +39 -4
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
- package/src/tui/session/session-api/routes-write.mjs +203 -0
- package/src/tui/session/session-api/routes.mjs +23 -247
- package/src/tui/session/session-api/settings.mjs +14 -1
- package/src/tui/session/session-flow/drain.mjs +28 -3
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +8 -3
- package/src/tui/session/tool-approval.mjs +142 -14
- package/src/tui/session/transcript-intake.mjs +2 -0
- package/src/tui/session/turn.mjs +23 -14
- package/src/tui/transcript-tool-failures.mjs +8 -2
- package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
- package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
- package/src/rules/agent-mode/ROUTINE.md +0 -6
- package/src/session-runtime/room-member-gate.mjs +0 -48
|
@@ -15,6 +15,16 @@
|
|
|
15
15
|
* (agent-home-delivery.mjs `watches`) until its end is queued, so a daemon
|
|
16
16
|
* restart before that re-attaches it (recover): an ended turn reports once
|
|
17
17
|
* from the target's record, a queued or running one is watched to its end.
|
|
18
|
+
*
|
|
19
|
+
* A group-room member (its member session `sess_rmem_<room>_<agent>`) opens
|
|
20
|
+
* and drives task sessions the same way; its reports carry `from` (the member
|
|
21
|
+
* session), so they land in that member session alone, never in its home or
|
|
22
|
+
* the room's log, and an ended turn gives the member a turn in its room to
|
|
23
|
+
* relay it (agent-home-delivery.mjs enqueueTask).
|
|
24
|
+
*
|
|
25
|
+
* A send to a group room's log goes to one member through the desktop room
|
|
26
|
+
* host (daemon-session-control.mjs): the member's turn in its member session
|
|
27
|
+
* is the reporting turn, taken by the room (`submit`), not by a prompt.
|
|
18
28
|
*/
|
|
19
29
|
import { randomUUID } from 'node:crypto';
|
|
20
30
|
import { realpathSync, statSync } from 'node:fs';
|
|
@@ -22,6 +32,12 @@ import { basename, resolve } from 'node:path';
|
|
|
22
32
|
import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
23
33
|
import { collectTurnArtifacts, handoffContent, sessionRoots, turnSlice } from './handoff-attachments.mjs';
|
|
24
34
|
import { roomMemberOfSession } from '../runtime/agent/orchestrator/session/agent-room.mjs';
|
|
35
|
+
import { wrapAgentMessage } from '../runtime/shared/agent-message-envelope.mjs';
|
|
36
|
+
import { sessionsWithPendingWork } from '../runtime/shared/pending-agent-work.mjs';
|
|
37
|
+
import { agentTaskIdOfExecution } from './services/agent-tool/restart-notify.mjs';
|
|
38
|
+
import { worktreeError, worktreeNameError } from '../runtime/shared/worktrees/contract.mjs';
|
|
39
|
+
import { samePath } from '../runtime/shared/worktrees/paths.mjs';
|
|
40
|
+
import { assertWorktreeMutationAllowed } from './services/worktree-access.mjs';
|
|
25
41
|
|
|
26
42
|
const TITLE_LIMIT = 60;
|
|
27
43
|
|
|
@@ -105,12 +121,600 @@ export function submissionOutcome(messages, runId) {
|
|
|
105
121
|
return 'open';
|
|
106
122
|
}
|
|
107
123
|
|
|
124
|
+
// `agent` tool calls that can start a job (a spawn, or a send that begins one).
|
|
125
|
+
const STARTING_AGENT_CALLS = new Set(['spawn', 'send']);
|
|
126
|
+
|
|
127
|
+
const callArguments = (call) => {
|
|
128
|
+
const raw = call?.arguments ?? call?.function?.arguments;
|
|
129
|
+
if (raw && typeof raw === 'object') return raw;
|
|
130
|
+
try {
|
|
131
|
+
return JSON.parse(String(raw || '{}')) || {};
|
|
132
|
+
} catch {
|
|
133
|
+
return {};
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/** The agent task ids a woken turn's prompt delivered, from its stored
|
|
138
|
+
* provenance: the batch's `consumedExecutionIds` (session-flow/drain.mjs)
|
|
139
|
+
* or the single `execution` of a queue-fed turn; null for an id that names
|
|
140
|
+
* no task. Empty when the turn recorded none. */
|
|
141
|
+
function consumedTaskIds(message) {
|
|
142
|
+
const recorded = message?.meta?.transcript?.consumedExecutionIds;
|
|
143
|
+
const ids = Array.isArray(recorded) ? recorded : message?.meta?.execution?.id ? [message.meta.execution.id] : [];
|
|
144
|
+
return ids.map((id) => (id ? agentTaskIdOfExecution(id) : null));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/** The latest turn of the chain that submission `runId` started in a
|
|
148
|
+
* session's stored `messages`, from structured provenance only: runId's own
|
|
149
|
+
* turn, then each turn that took a result of an agent task the chain started
|
|
150
|
+
* — as its prompt (a woken turn) or steered into it while it ran, even a turn
|
|
151
|
+
* the user started (its `agent` spawn/send results record
|
|
152
|
+
* `agentTaskIds`, tool-batch/finalize.mjs; the tasks those turns start join
|
|
153
|
+
* the chain). A turn the user typed, or one woken only by other agents,
|
|
154
|
+
* never joins it. `ambiguous` is set when provenance a chain decision needs
|
|
155
|
+
* is missing — a starting agent call of the chain without `agentTaskIds`,
|
|
156
|
+
* or a woken turn naming no task — so its answer may not be the final one.
|
|
157
|
+
* `{ id, outcome, at, ambiguous }`: that turn's submission id, its
|
|
158
|
+
* submissionOutcome and when its end was recorded (0 when unknown); null
|
|
159
|
+
* when no turn took runId. */
|
|
160
|
+
export function latestTurnOutcome(messages, runId) {
|
|
161
|
+
const list = Array.isArray(messages) ? messages : [];
|
|
162
|
+
const start = list.findIndex((message) => submissionIdsOf(message).includes(runId));
|
|
163
|
+
if (start < 0) return null;
|
|
164
|
+
const started = new Set();
|
|
165
|
+
const startingCalls = new Set();
|
|
166
|
+
let at = start;
|
|
167
|
+
let id = runId;
|
|
168
|
+
let inChain = true;
|
|
169
|
+
let ambiguous = false;
|
|
170
|
+
// The turn the walk is in: where its prompt is stored and its submission id.
|
|
171
|
+
let turnAt = start;
|
|
172
|
+
let turnId = runId;
|
|
173
|
+
for (let index = start; index < list.length; index += 1) {
|
|
174
|
+
const message = list[index];
|
|
175
|
+
const checkpointId = index > start && message?.role === 'user' ? message.meta?.transcript?.checkpointId : '';
|
|
176
|
+
if (checkpointId) {
|
|
177
|
+
turnAt = index;
|
|
178
|
+
turnId = checkpointId;
|
|
179
|
+
inChain = false;
|
|
180
|
+
}
|
|
181
|
+
// A completion delivered as a turn's prompt (queue/bus wake), or steered
|
|
182
|
+
// into a turn already running (loop/steering.mjs, no checkpoint of its
|
|
183
|
+
// own): the turn that took the chain's result joins the chain, even one
|
|
184
|
+
// the user started, so its answer becomes the candidate.
|
|
185
|
+
const delivered =
|
|
186
|
+
index > start && message?.role === 'user' && message.meta?.source === 'task-notification'
|
|
187
|
+
? consumedTaskIds(message)
|
|
188
|
+
: null;
|
|
189
|
+
if (delivered && !inChain) {
|
|
190
|
+
if (delivered.some((taskId) => taskId && started.has(taskId))) {
|
|
191
|
+
inChain = true;
|
|
192
|
+
at = turnAt;
|
|
193
|
+
id = turnId;
|
|
194
|
+
} else if (!delivered.length || delivered.includes(null)) {
|
|
195
|
+
ambiguous = true;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (checkpointId || delivered) continue;
|
|
199
|
+
if (!inChain) continue;
|
|
200
|
+
if (message?.role === 'assistant') {
|
|
201
|
+
for (const call of Array.isArray(message.toolCalls) ? message.toolCalls : []) {
|
|
202
|
+
const name = String(call?.name ?? call?.function?.name ?? '');
|
|
203
|
+
const type = String(callArguments(call).type || 'spawn').trim();
|
|
204
|
+
if (name === 'agent' && STARTING_AGENT_CALLS.has(type) && call?.id) startingCalls.add(call.id);
|
|
205
|
+
}
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (message?.role !== 'tool' || !startingCalls.has(message.toolCallId)) continue;
|
|
209
|
+
if (Array.isArray(message.agentTaskIds)) for (const taskId of message.agentTaskIds) started.add(String(taskId));
|
|
210
|
+
else if (message.toolKind !== 'error') ambiguous = true;
|
|
211
|
+
}
|
|
212
|
+
const turn = list.slice(at);
|
|
213
|
+
const ended = turn.find((message) => message?.meta?.transcript?.completion);
|
|
214
|
+
return { id, outcome: submissionOutcome(turn, id), at: Number(ended?.meta?.transcript?.at) || 0, ambiguous };
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Whether task session `sessionId` still waits on agent work it handed off,
|
|
218
|
+
* from `agentWorking` (Lead ids with an Agent session in flight), `owed`
|
|
219
|
+
* (agent results queued for it that no turn has taken yet), `pool` (agent
|
|
220
|
+
* pool rows: the desktop notifier's rule, pending-agent-work.mjs) and
|
|
221
|
+
* `agentTaskOwners` (the sessions owning a queued, spawning or running agent
|
|
222
|
+
* task — one still waiting for admission has no turn in flight, and its pool
|
|
223
|
+
* row reads `unknown`). An agent task owned by an Agent session under it, at
|
|
224
|
+
* any depth, counts too: every descendant's pool row names the Lead as owner. */
|
|
225
|
+
export function pendingWorkOf(
|
|
226
|
+
sessionId,
|
|
227
|
+
{ agentWorking = new Set(), owed = [], pool = [], agentTaskOwners = new Set() }
|
|
228
|
+
) {
|
|
229
|
+
if (agentWorking.has(sessionId) || owed.length > 0 || agentTaskOwners.has(sessionId)) return true;
|
|
230
|
+
if (sessionsWithPendingWork(pool).has(sessionId)) return true;
|
|
231
|
+
return pool.some((row) => row?.ownerSessionId === sessionId && agentTaskOwners.has(String(row.sessionId || '')));
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** The agent results queued for `sessionId` that no turn has taken yet (its
|
|
235
|
+
* durable pending queue: an agent's completion, a restart failure): `{ text,
|
|
236
|
+
* meta }` as the notification bus delivers them. */
|
|
237
|
+
export async function owedAgentResults(sessionId) {
|
|
238
|
+
const [{ _getPendingMessagesForSession }, { completionIsDelivered, isCompletionNotificationEntry }] =
|
|
239
|
+
await Promise.all([
|
|
240
|
+
import('../runtime/agent/orchestrator/session/manager/pending-messages.mjs'),
|
|
241
|
+
import('../runtime/agent/orchestrator/session/manager/pending-message-entry.mjs'),
|
|
242
|
+
]);
|
|
243
|
+
return _getPendingMessagesForSession(sessionId)
|
|
244
|
+
.filter((entry) => isCompletionNotificationEntry(entry) && !completionIsDelivered(entry))
|
|
245
|
+
.map((entry) => ({
|
|
246
|
+
text: String(entry.text || entry.content || ''),
|
|
247
|
+
meta: {
|
|
248
|
+
type: 'agent_task_result',
|
|
249
|
+
execution_surface: entry.execution?.surface || 'agent',
|
|
250
|
+
execution_id: entry.executionId || entry.execution?.id || null,
|
|
251
|
+
status: entry.execution?.status || null,
|
|
252
|
+
caller_session_id: sessionId,
|
|
253
|
+
},
|
|
254
|
+
}));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** pendingWorkOf over this process's state: the in-flight runtimes, the
|
|
258
|
+
* durable pending queue, the agent pool and the background agent tasks. */
|
|
259
|
+
export async function taskSessionPendingWork(sessionId, { owedResults = owedAgentResults } = {}) {
|
|
260
|
+
const [{ inFlightSessionActivity }, { listStoredAgentWorkersAsync }, { activeBackgroundTaskSessionIds }] =
|
|
261
|
+
await Promise.all([
|
|
262
|
+
import('../runtime/agent/orchestrator/session/manager/runtime-state.mjs'),
|
|
263
|
+
import('../runtime/agent/orchestrator/session/store-summary-reader.mjs'),
|
|
264
|
+
import('../runtime/shared/background-tasks.mjs'),
|
|
265
|
+
]);
|
|
266
|
+
const { working, agentWorking } = inFlightSessionActivity();
|
|
267
|
+
const agentTaskOwners = activeBackgroundTaskSessionIds({ surface: 'agent' });
|
|
268
|
+
if (pendingWorkOf(sessionId, { agentWorking, agentTaskOwners })) return true;
|
|
269
|
+
if ((await owedResults(sessionId)).length) return true;
|
|
270
|
+
const pool = await listStoredAgentWorkersAsync({ runningSessionIds: working });
|
|
271
|
+
return pendingWorkOf(sessionId, { pool, agentTaskOwners });
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// A task session idle with nothing pending must stay so this long before its
|
|
275
|
+
// latest answer is final: a finished agent's result can start the Lead's next
|
|
276
|
+
// turn right after (the desktop notifier's quiet period).
|
|
277
|
+
const FINAL_SETTLE_MS = 2500;
|
|
278
|
+
// While a report waits on pending work it re-checks this often (state changes
|
|
279
|
+
// of the session re-check at once).
|
|
280
|
+
const FINAL_POLL_MS = 15_000;
|
|
281
|
+
|
|
282
|
+
const REPORT_RETRY_DELAYS_MS = [200, 1000, 5000];
|
|
283
|
+
|
|
108
284
|
const LOST_TEXT = 'The message never ran: the app restarted before its turn started.';
|
|
109
285
|
const GONE_TEXT = 'The session no longer exists.';
|
|
286
|
+
const RESUME_FAILED_TEXT =
|
|
287
|
+
'The agent task session could not continue after the app restarted: its agents’ results never reached it. Check that session.';
|
|
288
|
+
const PENDING_CAP_MS = 30 * 60_000;
|
|
289
|
+
const STILL_WORKING_TEXT =
|
|
290
|
+
'Still in progress: its agents are still working 30+ minutes after this answer. This is the latest answer; the final one is reported when they finish.';
|
|
291
|
+
// Deliveries of a restarted task session's queued agent results before its
|
|
292
|
+
// report gives up as RESUME_FAILED_TEXT.
|
|
293
|
+
const OWED_WAKE_TRIES = 3;
|
|
294
|
+
|
|
295
|
+
// Why a send or create reports nothing back (its `reportsBackReason`).
|
|
296
|
+
const NOTIFY_OFF_REASON = 'this agent task session was created with notify: false, so its turns do not report back';
|
|
297
|
+
const WATCH_CAP_REASON =
|
|
298
|
+
'this chat already waits on as many unreported session results as it can track; earlier ones must report first';
|
|
110
299
|
|
|
111
300
|
// The last-used model search stops after this many catalog pages.
|
|
112
301
|
const LAST_USED_ROUTE_MAX_PAGES = 20;
|
|
113
302
|
|
|
303
|
+
/** A report's `from` when its origin is a group-room member session: the
|
|
304
|
+
* report goes to that member session instead of the agent's home. */
|
|
305
|
+
const roomFrom = (originSessionId) => (roomMemberOfSession(originSessionId) ? { from: originSessionId } : {});
|
|
306
|
+
|
|
307
|
+
/** The route of `modelRef`: a provider/model[@effort…] ref (a superseded
|
|
308
|
+
* model runs on its successor) or a preset name. */
|
|
309
|
+
async function configModelRoute(modelRef) {
|
|
310
|
+
const [{ modelRouteFields, parseScheduleModelRef }, { migrateCodexRoute }, { loadConfig }] = await Promise.all([
|
|
311
|
+
import('../runtime/shared/schedule-model-ref.mjs'),
|
|
312
|
+
import('../runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs'),
|
|
313
|
+
import('../runtime/agent/orchestrator/config.mjs'),
|
|
314
|
+
]);
|
|
315
|
+
const ref = parseScheduleModelRef(modelRef);
|
|
316
|
+
if (ref && typeof ref === 'object') return modelRouteFields(migrateCodexRoute(ref));
|
|
317
|
+
const preset = loadConfig({ secrets: false }).presets?.find((p) => p.id === ref || p.name === ref);
|
|
318
|
+
if (!preset) throw new Error(`model "${ref}" is neither a provider/model route nor a known preset name`);
|
|
319
|
+
return modelRouteFields(preset);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** The Lead (Main) route of the config — what a desktop New task starts on.
|
|
323
|
+
* Null while none is chosen. */
|
|
324
|
+
async function configLeadRoute() {
|
|
325
|
+
const [{ modelRouteFields }, { resolveRoute }, { loadConfig }] = await Promise.all([
|
|
326
|
+
import('../runtime/shared/schedule-model-ref.mjs'),
|
|
327
|
+
import('./boot/shared.mjs'),
|
|
328
|
+
import('../runtime/agent/orchestrator/config.mjs'),
|
|
329
|
+
]);
|
|
330
|
+
const route = resolveRoute(loadConfig({ secrets: false }), {});
|
|
331
|
+
return clean(route?.provider) && clean(route?.model) ? modelRouteFields(route) : null;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/** The model the user last used in a coding session (the runtime's stand-in
|
|
335
|
+
* for the desktop's last New-task model): the first row that has one in the
|
|
336
|
+
* catalog, which lists newest activity first. Pages on (bounded) until a
|
|
337
|
+
* row qualifies; null when none does. */
|
|
338
|
+
async function lastUsedSessionRoute(sessionService) {
|
|
339
|
+
let cursor = null;
|
|
340
|
+
for (let page = 0; page < LAST_USED_ROUTE_MAX_PAGES; page += 1) {
|
|
341
|
+
const listed = await sessionService.listSessionCatalog({ page: { limit: 50, cursor } });
|
|
342
|
+
const used = (Array.isArray(listed?.sessions) ? listed.sessions : []).find(
|
|
343
|
+
(row) => row?.sessionMode !== 'agent' && clean(row?.provider) && clean(row?.model)
|
|
344
|
+
);
|
|
345
|
+
if (used) return { provider: clean(used.provider), model: clean(used.model) };
|
|
346
|
+
cursor = listed?.nextCursor || null;
|
|
347
|
+
if (!cursor) return null;
|
|
348
|
+
}
|
|
349
|
+
return null;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/** Wait for the final answer of the task turn `runId` of `sessionId` ended
|
|
353
|
+
* completed: the end of the latest turn since it (a Lead that handed work
|
|
354
|
+
* to agents ends its turn and answers in the turn their results wake),
|
|
355
|
+
* once the session is idle with no pending agent work (pendingWork) for
|
|
356
|
+
* `settleMs`. `immediate` is runId's own end, when this process saw it.
|
|
357
|
+
* `finish(status, text)` runs once; `check` re-evaluates (call it on every
|
|
358
|
+
* state change of `runtime`), `stop` ends the wait. */
|
|
359
|
+
function followTaskToFinal(
|
|
360
|
+
{ loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, pendingCapMs, log },
|
|
361
|
+
{ sessionId, runId, runtime, immediate = null, wakeOwed = false, finish, progress = null }
|
|
362
|
+
) {
|
|
363
|
+
let progressed = false;
|
|
364
|
+
let pendingSince = 0;
|
|
365
|
+
// Agent work still pending `pendingCapMs` after the chain's latest turn
|
|
366
|
+
// ended: `progress(text)` reports that answer once, marked still in
|
|
367
|
+
// progress, and the wait for the final one goes on.
|
|
368
|
+
async function reportProgress() {
|
|
369
|
+
if (progressed || typeof progress !== 'function') return;
|
|
370
|
+
const latest = latestTurnOutcome((await loadSession(sessionId))?.messages, runId);
|
|
371
|
+
let end = latest?.outcome && latest.outcome !== 'open' ? latest.outcome : null;
|
|
372
|
+
if (immediate && (!latest || latest.id === runId)) end = immediate;
|
|
373
|
+
if (!end) return;
|
|
374
|
+
pendingSince ||= Date.now();
|
|
375
|
+
if (Date.now() - (latest?.at || pendingSince) < pendingCapMs) return;
|
|
376
|
+
progressed = true;
|
|
377
|
+
await progress(`${STILL_WORKING_TEXT}\n\n${end.text}`);
|
|
378
|
+
}
|
|
379
|
+
let done = false;
|
|
380
|
+
let checking = false;
|
|
381
|
+
let again = false;
|
|
382
|
+
let candidate = '';
|
|
383
|
+
let candidateAt = 0;
|
|
384
|
+
let timer = null;
|
|
385
|
+
let wakes = 0;
|
|
386
|
+
let wokeAt = 0;
|
|
387
|
+
const unwoken = (why) => {
|
|
388
|
+
log(`${sessionId} could not take its agents' results after the restart: ${why}`);
|
|
389
|
+
return { key: 'unwoken', status: 'failed', text: RESUME_FAILED_TEXT, now: true };
|
|
390
|
+
};
|
|
391
|
+
// `wakeOwed` (a restarted watch): deliver the agent results queued for
|
|
392
|
+
// the idle session through the notification bus so a turn takes them, at
|
|
393
|
+
// most OWED_WAKE_TRIES times, a poll apart; null while that turn is owed.
|
|
394
|
+
async function wakeForOwed() {
|
|
395
|
+
const owed = await owedResults(sessionId);
|
|
396
|
+
if (!owed.length) return undefined;
|
|
397
|
+
if (Date.now() - wokeAt < pollMs) return null;
|
|
398
|
+
if (wakes >= OWED_WAKE_TRIES) return unwoken(`still not taken after ${wakes} deliveries`);
|
|
399
|
+
wakes += 1;
|
|
400
|
+
wokeAt = Date.now();
|
|
401
|
+
let delivered = false;
|
|
402
|
+
try {
|
|
403
|
+
if (typeof deliverCompletion === 'function') {
|
|
404
|
+
const results = await Promise.all(owed.map(({ text, meta }) => deliverCompletion(sessionId, text, meta)));
|
|
405
|
+
delivered = results.some((result) => result === true);
|
|
406
|
+
}
|
|
407
|
+
} catch (error) {
|
|
408
|
+
return unwoken(error?.message || error);
|
|
409
|
+
}
|
|
410
|
+
return delivered ? null : unwoken('no session runtime took the delivery');
|
|
411
|
+
}
|
|
412
|
+
const arm = (ms) => {
|
|
413
|
+
clearTimeout(timer);
|
|
414
|
+
timer = setTimeout(() => void check().catch(failed), ms);
|
|
415
|
+
timer.unref?.();
|
|
416
|
+
};
|
|
417
|
+
const stop = () => {
|
|
418
|
+
done = true;
|
|
419
|
+
clearTimeout(timer);
|
|
420
|
+
};
|
|
421
|
+
const failed = (error) => log(`following ${sessionId} to its final answer failed: ${error?.message || error}`);
|
|
422
|
+
// The end to report now, or null while the task still works.
|
|
423
|
+
async function ready() {
|
|
424
|
+
if (runtime?.getState?.()?.busy) return null;
|
|
425
|
+
if (wakeOwed) {
|
|
426
|
+
const woke = await wakeForOwed();
|
|
427
|
+
if (woke !== undefined) return woke;
|
|
428
|
+
}
|
|
429
|
+
if (await pendingWork(sessionId)) {
|
|
430
|
+
await reportProgress();
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
const stored = await loadSession(sessionId);
|
|
434
|
+
if (!stored && !immediate) return { key: 'gone', status: 'cancelled', text: GONE_TEXT };
|
|
435
|
+
const latest = latestTurnOutcome(stored?.messages, runId);
|
|
436
|
+
// Provenance the chain needs is missing: its answer may not be final.
|
|
437
|
+
if (latest?.ambiguous) {
|
|
438
|
+
await reportProgress();
|
|
439
|
+
return null;
|
|
440
|
+
}
|
|
441
|
+
if (immediate && (!latest || latest.id === runId)) return { key: runId, ...immediate };
|
|
442
|
+
if (!latest?.outcome || latest.outcome === 'open') return null;
|
|
443
|
+
return { key: `${latest.id}:${latest.outcome.status}`, ...latest.outcome };
|
|
444
|
+
}
|
|
445
|
+
async function check() {
|
|
446
|
+
if (done) return;
|
|
447
|
+
if (checking) {
|
|
448
|
+
again = true;
|
|
449
|
+
return;
|
|
450
|
+
}
|
|
451
|
+
checking = true;
|
|
452
|
+
try {
|
|
453
|
+
do {
|
|
454
|
+
again = false;
|
|
455
|
+
const end = await ready();
|
|
456
|
+
if (done) return;
|
|
457
|
+
if (!end) {
|
|
458
|
+
candidate = '';
|
|
459
|
+
arm(pollMs);
|
|
460
|
+
} else if (!end.now && settleMs > 0 && (end.key !== candidate || Date.now() - candidateAt < settleMs)) {
|
|
461
|
+
// Final only if it is still the answer after the quiet period.
|
|
462
|
+
if (end.key !== candidate) {
|
|
463
|
+
candidate = end.key;
|
|
464
|
+
candidateAt = Date.now();
|
|
465
|
+
}
|
|
466
|
+
arm(candidateAt + settleMs - Date.now());
|
|
467
|
+
} else {
|
|
468
|
+
stop();
|
|
469
|
+
await finish(end.status, end.text);
|
|
470
|
+
}
|
|
471
|
+
} while (again && !done);
|
|
472
|
+
} finally {
|
|
473
|
+
checking = false;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return { check: () => check().catch(failed), stop };
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/** Store the durable watch of turn `id` before it can start, so no end goes
|
|
480
|
+
* unreported across a restart; a turn whose watch could not be stored runs
|
|
481
|
+
* without a report (reportsBack false) rather than claim one. `noReport`:
|
|
482
|
+
* why the turn will not report back, told to the bot (reportsBackReason). */
|
|
483
|
+
async function watchTurn({ watches, owner, log }, sessionId, origin, id) {
|
|
484
|
+
if (origin.notify === false) return { notify: false, noReport: NOTIFY_OFF_REASON };
|
|
485
|
+
try {
|
|
486
|
+
await watches.add({
|
|
487
|
+
runId: id,
|
|
488
|
+
agentId: origin.agentId,
|
|
489
|
+
homeSessionId: origin.sessionId,
|
|
490
|
+
taskSessionId: sessionId,
|
|
491
|
+
title: origin.title,
|
|
492
|
+
...(origin.target === 'session' ? { target: 'session' } : {}),
|
|
493
|
+
...(origin.textOnly ? { textOnly: true } : {}),
|
|
494
|
+
...(origin.hop > 0 ? { hop: origin.hop } : {}),
|
|
495
|
+
owner,
|
|
496
|
+
at: Date.now(),
|
|
497
|
+
});
|
|
498
|
+
return { notify: true, noReport: '' };
|
|
499
|
+
} catch (error) {
|
|
500
|
+
log(`watch of ${sessionId} failed, sending without a report: ${error?.message || error}`);
|
|
501
|
+
return {
|
|
502
|
+
notify: false,
|
|
503
|
+
noReport:
|
|
504
|
+
error?.code === 'REPORT_WATCH_CAP'
|
|
505
|
+
? WATCH_CAP_REASON
|
|
506
|
+
: `its report could not be recorded (${error?.message || error}); check that session for the result`,
|
|
507
|
+
};
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/** Deliver report `payload` of task session `sessionId` to agent `agentId`,
|
|
512
|
+
* retrying after each of `retryDelaysMs`; the last failure throws. */
|
|
513
|
+
async function deliverWithRetries({ deliver, retryDelaysMs, log }, agentId, sessionId, payload) {
|
|
514
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
515
|
+
try {
|
|
516
|
+
return await deliver(agentId, payload);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
if (attempt >= retryDelaysMs.length) throw error;
|
|
519
|
+
log(`report of ${sessionId} failed (try ${attempt + 1}), retrying: ${error?.message || error}`);
|
|
520
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, retryDelaysMs[attempt]));
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/** Where create's `worktree` option starts the task session: `{ mode }` is
|
|
526
|
+
* 'default' (the user's New task setting), 'branch' (the Project folder on its
|
|
527
|
+
* current branch) or 'worktree' (its own new one, `name` optional). */
|
|
528
|
+
function taskLocation(option) {
|
|
529
|
+
if (option === undefined || option === null) return { mode: 'default' };
|
|
530
|
+
if (typeof option !== 'object' || Array.isArray(option) || !['branch', 'worktree'].includes(option.mode)) {
|
|
531
|
+
throw new Error('session: worktree.mode must be "branch" or "worktree"');
|
|
532
|
+
}
|
|
533
|
+
if (option.name === undefined) return { mode: option.mode };
|
|
534
|
+
if (option.mode !== 'worktree') throw new Error('session: worktree.name applies only to mode "worktree"');
|
|
535
|
+
const reason = worktreeNameError(option.name);
|
|
536
|
+
if (reason) throw new Error(`session: worktree.name is invalid: ${reason}`);
|
|
537
|
+
return { mode: 'worktree', name: option.name };
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
const megabytes = (bytes) => `${Math.round(Number(bytes) / (1024 * 1024))} MB`;
|
|
541
|
+
|
|
542
|
+
/** A typed worktree failure as the model should read it: its code and, for a
|
|
543
|
+
* large repository, the size to put to the user. */
|
|
544
|
+
function taskWorktreeFailure(error) {
|
|
545
|
+
const details = error?.details;
|
|
546
|
+
const size =
|
|
547
|
+
error?.code === 'WORKTREE_LARGE_REPO_CONFIRM' && Number.isFinite(details?.estimateBytes)
|
|
548
|
+
? ` The repository is about ${megabytes(details.estimateBytes)} (warning threshold ${megabytes(details.thresholdBytes)}). Ask the user whether to create the worktree anyway; only if they confirm, call create again with confirm_large: true.`
|
|
549
|
+
: '';
|
|
550
|
+
return Object.assign(new Error(`session: ${error.code}: ${error.message}${size}`), {
|
|
551
|
+
code: error.code,
|
|
552
|
+
...(details ? { details } : {}),
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
/** Start task session `sessionId` in its own worktree and return its workspace
|
|
557
|
+
* value. A New task never carries the Project folder's changes. Unless
|
|
558
|
+
* `explicit`, a repo that cannot have one keeps the Project folder (null).
|
|
559
|
+
* The session must end up bound: the brief goes out only after it. */
|
|
560
|
+
async function requestTaskWorktree(sessions, sessionId, { title, requestId, name, confirmLarge, explicit }) {
|
|
561
|
+
try {
|
|
562
|
+
const result = await sessions.configureSession(
|
|
563
|
+
{
|
|
564
|
+
sessionId,
|
|
565
|
+
action: 'requestWorktree',
|
|
566
|
+
args: [
|
|
567
|
+
{
|
|
568
|
+
provision: true,
|
|
569
|
+
carryChanges: 'leave',
|
|
570
|
+
prompt: title,
|
|
571
|
+
txId: requestId,
|
|
572
|
+
...(name ? { name } : {}),
|
|
573
|
+
...(confirmLarge ? { confirmLarge: true } : {}),
|
|
574
|
+
},
|
|
575
|
+
],
|
|
576
|
+
},
|
|
577
|
+
{ revision: 1 }
|
|
578
|
+
);
|
|
579
|
+
const value = result?.value;
|
|
580
|
+
if (value?.pending !== false || typeof value.workspace?.worktreeId !== 'string') {
|
|
581
|
+
throw worktreeError('WORKTREE_NOT_IMPLEMENTED', 'The session did not enter its worktree before the brief.');
|
|
582
|
+
}
|
|
583
|
+
return value;
|
|
584
|
+
} catch (error) {
|
|
585
|
+
if (!explicit && error?.code === 'WORKTREE_UNSUPPORTED_REPO') return null;
|
|
586
|
+
throw typeof error?.code === 'string' && error.code.startsWith('WORKTREE_') ? taskWorktreeFailure(error) : error;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/** The origin a `send` of agent `agentId` (from its home or room member
|
|
591
|
+
* session `callerSessionId`) reports to: the stored one of a task session
|
|
592
|
+
* it created, else one for this send only — never stored on the target,
|
|
593
|
+
* whose own origin and owner stay as they are. Either reports to the
|
|
594
|
+
* conversation the send came from. Null for a caller that is no agent
|
|
595
|
+
* conversation. */
|
|
596
|
+
async function sendReportOrigin({ loadSession, agentName }, sessionId, agentId, callerSessionId) {
|
|
597
|
+
if (!agentId) return null;
|
|
598
|
+
const target = await loadSession(sessionId);
|
|
599
|
+
const origin = target?.taskOrigin;
|
|
600
|
+
if (origin?.agentId === agentId) {
|
|
601
|
+
return callerSessionId && callerSessionId !== origin.sessionId ? { ...origin, sessionId: callerSessionId } : origin;
|
|
602
|
+
}
|
|
603
|
+
const title = (clean(target?.title) || sessionId).slice(0, TITLE_LIMIT);
|
|
604
|
+
// Files come back only from sessions the agent created (above) or its own
|
|
605
|
+
// room member threads; any other session reports its text alone.
|
|
606
|
+
const own = roomMemberOfSession(sessionId)?.agentId === agentId;
|
|
607
|
+
return {
|
|
608
|
+
agentId,
|
|
609
|
+
sessionId: callerSessionId,
|
|
610
|
+
title,
|
|
611
|
+
notify: true,
|
|
612
|
+
target: 'session',
|
|
613
|
+
...(own ? {} : { textOnly: true, envelopeName: await agentName(agentId) }),
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
/** Re-attach one watch a stopped daemon left: report the turn's end once
|
|
618
|
+
* from the target's record, or watch it until it ends. */
|
|
619
|
+
async function rearmTaskWatch(
|
|
620
|
+
{ deliver, loadSession, materializeSession, queuedSubmissionIds, artifactFields, followToFinal, log },
|
|
621
|
+
watch
|
|
622
|
+
) {
|
|
623
|
+
const sessionId = watch.taskSessionId;
|
|
624
|
+
// The turn that answers: the watched submission's own, or the one it
|
|
625
|
+
// follows (a room member's turn under another submission id).
|
|
626
|
+
const turnId = watch.follows || watch.runId;
|
|
627
|
+
const finish = async (status, text = '') =>
|
|
628
|
+
deliver(watch.agentId, {
|
|
629
|
+
runId: `${watch.runId}_end`,
|
|
630
|
+
taskSessionId: sessionId,
|
|
631
|
+
title: watch.title,
|
|
632
|
+
status,
|
|
633
|
+
text,
|
|
634
|
+
...(watch.target === 'session' ? { target: 'session' } : {}),
|
|
635
|
+
...(watch.hop > 0 ? { hop: watch.hop } : {}),
|
|
636
|
+
...roomFrom(watch.homeSessionId),
|
|
637
|
+
watchId: watch.runId,
|
|
638
|
+
...(watch.textOnly || text === GONE_TEXT || text === LOST_TEXT
|
|
639
|
+
? {}
|
|
640
|
+
: await artifactFields(sessionId, turnId)),
|
|
641
|
+
});
|
|
642
|
+
if (!(await loadSession(sessionId))) return finish('cancelled', GONE_TEXT);
|
|
643
|
+
let runtime;
|
|
644
|
+
try {
|
|
645
|
+
runtime = await materializeSession(sessionId);
|
|
646
|
+
} catch (error) {
|
|
647
|
+
// It can never take its agents' results: say so once instead of waiting.
|
|
648
|
+
log(`${sessionId} could not be loaded after the restart: ${error?.message || error}`);
|
|
649
|
+
return finish('failed', RESUME_FAILED_TEXT);
|
|
650
|
+
}
|
|
651
|
+
let done = false;
|
|
652
|
+
let checking = false;
|
|
653
|
+
let again = false;
|
|
654
|
+
let unsubscribe = () => {};
|
|
655
|
+
// Set once the watched turn is found ended completed: the report then
|
|
656
|
+
// waits for the task's final answer, as in the process that sent it.
|
|
657
|
+
let follow = null;
|
|
658
|
+
const check = async () => {
|
|
659
|
+
if (done) return;
|
|
660
|
+
if (follow) return follow.check();
|
|
661
|
+
if (checking) {
|
|
662
|
+
again = true;
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
checking = true;
|
|
666
|
+
try {
|
|
667
|
+
do {
|
|
668
|
+
again = false;
|
|
669
|
+
const outcome = submissionOutcome((await loadSession(sessionId))?.messages, turnId);
|
|
670
|
+
const state = runtime?.getState?.() || {};
|
|
671
|
+
let end = outcome && outcome !== 'open' ? outcome : null;
|
|
672
|
+
if (!end && !state.busy) {
|
|
673
|
+
const queued = (state.queued || []).some((entry) => entry?.id === turnId);
|
|
674
|
+
if (outcome === 'open' && !queued) end = { status: 'cancelled', text: '' };
|
|
675
|
+
else if (!outcome && !queued && !(await queuedSubmissionIds(sessionId)).includes(turnId)) {
|
|
676
|
+
end = { status: 'cancelled', text: LOST_TEXT };
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
if (end?.status === 'completed') {
|
|
680
|
+
follow = followToFinal({
|
|
681
|
+
sessionId,
|
|
682
|
+
runId: turnId,
|
|
683
|
+
runtime,
|
|
684
|
+
wakeOwed: true,
|
|
685
|
+
progress: (text) =>
|
|
686
|
+
deliver(watch.agentId, {
|
|
687
|
+
runId: `${watch.runId}_progress`,
|
|
688
|
+
taskSessionId: sessionId,
|
|
689
|
+
title: watch.title,
|
|
690
|
+
status: 'working',
|
|
691
|
+
text,
|
|
692
|
+
...(watch.target === 'session' ? { target: 'session' } : {}),
|
|
693
|
+
...(watch.hop > 0 ? { hop: watch.hop } : {}),
|
|
694
|
+
...roomFrom(watch.homeSessionId),
|
|
695
|
+
}),
|
|
696
|
+
finish: async (status, text) => {
|
|
697
|
+
done = true;
|
|
698
|
+
unsubscribe();
|
|
699
|
+
await finish(status, text);
|
|
700
|
+
},
|
|
701
|
+
});
|
|
702
|
+
} else if (end) {
|
|
703
|
+
done = true;
|
|
704
|
+
unsubscribe();
|
|
705
|
+
await finish(end.status, end.text);
|
|
706
|
+
}
|
|
707
|
+
} while (again && !done && !follow);
|
|
708
|
+
} finally {
|
|
709
|
+
checking = false;
|
|
710
|
+
}
|
|
711
|
+
if (follow) await follow.check();
|
|
712
|
+
};
|
|
713
|
+
const failed = (error) => log(`re-attaching ${sessionId} failed: ${error?.message || error}`);
|
|
714
|
+
unsubscribe = runtime?.subscribe?.(() => void check().catch(failed)) || unsubscribe;
|
|
715
|
+
await check();
|
|
716
|
+
}
|
|
717
|
+
|
|
114
718
|
export function createAgentTaskSessions({
|
|
115
719
|
getSessionService,
|
|
116
720
|
deliver = async (agentId, report) => (await import('./agent-home-delivery.mjs')).deliverAgentTask(agentId, report),
|
|
@@ -119,6 +723,8 @@ export function createAgentTaskSessions({
|
|
|
119
723
|
add: async (watch) => (await import('./agent-home-delivery.mjs')).watchAgentTask(watch),
|
|
120
724
|
list: async () => (await import('./agent-home-delivery.mjs')).agentTaskWatches(),
|
|
121
725
|
remove: async (runId) => (await import('./agent-home-delivery.mjs')).unwatchAgentTask(runId),
|
|
726
|
+
follow: async (runId, submissionId) =>
|
|
727
|
+
(await import('./agent-home-delivery.mjs')).followAgentTask(runId, submissionId),
|
|
122
728
|
},
|
|
123
729
|
// Submissions still queued (durably) for a session after a restart.
|
|
124
730
|
queuedSubmissionIds = async (sessionId) =>
|
|
@@ -130,48 +736,11 @@ export function createAgentTaskSessions({
|
|
|
130
736
|
listProjects = async () => (await import('../runtime/shared/projects.mjs')).listProjects(),
|
|
131
737
|
newTaskDefault = async () =>
|
|
132
738
|
(await import('../runtime/agent/orchestrator/config.mjs')).loadConfig({ secrets: false }).worktree?.newTaskDefault,
|
|
133
|
-
modelRoute =
|
|
134
|
-
const [{ modelRouteFields, parseScheduleModelRef }, { migrateCodexRoute }, { loadConfig }] = await Promise.all([
|
|
135
|
-
import('../runtime/shared/schedule-model-ref.mjs'),
|
|
136
|
-
import('../runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs'),
|
|
137
|
-
import('../runtime/agent/orchestrator/config.mjs'),
|
|
138
|
-
]);
|
|
139
|
-
// A provider/model[@effort…] ref (a superseded model runs on its
|
|
140
|
-
// successor) or a preset name.
|
|
141
|
-
const ref = parseScheduleModelRef(modelRef);
|
|
142
|
-
if (ref && typeof ref === 'object') return modelRouteFields(migrateCodexRoute(ref));
|
|
143
|
-
const preset = loadConfig({ secrets: false }).presets?.find((p) => p.id === ref || p.name === ref);
|
|
144
|
-
if (!preset) throw new Error(`model "${ref}" is neither a provider/model route nor a known preset name`);
|
|
145
|
-
return modelRouteFields(preset);
|
|
146
|
-
},
|
|
739
|
+
modelRoute = configModelRoute,
|
|
147
740
|
// The Lead (Main) route of the config — what a desktop New task starts on.
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
import('../runtime/shared/schedule-model-ref.mjs'),
|
|
152
|
-
import('./boot/shared.mjs'),
|
|
153
|
-
import('../runtime/agent/orchestrator/config.mjs'),
|
|
154
|
-
]);
|
|
155
|
-
const route = resolveRoute(loadConfig({ secrets: false }), {});
|
|
156
|
-
return clean(route?.provider) && clean(route?.model) ? modelRouteFields(route) : null;
|
|
157
|
-
},
|
|
158
|
-
// The model the user last used in a coding session (the runtime's stand-in
|
|
159
|
-
// for the desktop's last New-task model): the first row that has one in the
|
|
160
|
-
// catalog, which lists newest activity first. Pages on (bounded) until a
|
|
161
|
-
// row qualifies; null when none does.
|
|
162
|
-
lastUsedRoute = async (sessionService) => {
|
|
163
|
-
let cursor = null;
|
|
164
|
-
for (let page = 0; page < LAST_USED_ROUTE_MAX_PAGES; page += 1) {
|
|
165
|
-
const listed = await sessionService.listSessionCatalog({ page: { limit: 50, cursor } });
|
|
166
|
-
const used = (Array.isArray(listed?.sessions) ? listed.sessions : []).find(
|
|
167
|
-
(row) => row?.sessionMode !== 'agent' && clean(row?.provider) && clean(row?.model)
|
|
168
|
-
);
|
|
169
|
-
if (used) return { provider: clean(used.provider), model: clean(used.model) };
|
|
170
|
-
cursor = listed?.nextCursor || null;
|
|
171
|
-
if (!cursor) return null;
|
|
172
|
-
}
|
|
173
|
-
return null;
|
|
174
|
-
},
|
|
741
|
+
leadRoute = configLeadRoute,
|
|
742
|
+
// The model the user last used in a coding session.
|
|
743
|
+
lastUsedRoute = lastUsedSessionRoute,
|
|
175
744
|
// The files turn `runId` of `sessionId` produced (handoff-attachments.mjs),
|
|
176
745
|
// read from its live runtime when one is given, else its stored record.
|
|
177
746
|
turnArtifacts = async (sessionId, runId, runtime = null) => {
|
|
@@ -180,6 +749,27 @@ export function createAgentTaskSessions({
|
|
|
180
749
|
const messages = turnSlice(Array.isArray(live?.messages) ? live.messages : stored?.messages, { runId });
|
|
181
750
|
return collectTurnArtifacts(messages, { cwd: stored?.cwd, roots: sessionRoots(stored) });
|
|
182
751
|
},
|
|
752
|
+
// The name a bot goes by in the envelope of its messages to other sessions.
|
|
753
|
+
agentName = async (agentId) =>
|
|
754
|
+
(await import('../runtime/agent/orchestrator/context/role-instructions.mjs')).agentDisplayName(agentId),
|
|
755
|
+
// Waits before each in-process retry of a report that could not be queued.
|
|
756
|
+
retryDelaysMs = REPORT_RETRY_DELAYS_MS,
|
|
757
|
+
// Whether a task session still waits on agent work it handed off; its
|
|
758
|
+
// turn's end then is not the final answer yet.
|
|
759
|
+
pendingWork = taskSessionPendingWork,
|
|
760
|
+
settleMs = FINAL_SETTLE_MS,
|
|
761
|
+
pollMs = FINAL_POLL_MS,
|
|
762
|
+
// After a restart: the agent results queued for a task session, and the
|
|
763
|
+
// notification-bus delivery (the session runtime host's
|
|
764
|
+
// notifySessionCompletion) that makes it take them in a turn — the restart
|
|
765
|
+
// left nothing to wake it. `settleAgentResults` first turns the agents the
|
|
766
|
+
// restart killed into queued failure results (agent-tool restart-notify).
|
|
767
|
+
owedResults = owedAgentResults,
|
|
768
|
+
deliverCompletion = null,
|
|
769
|
+
settleAgentResults = async () => {},
|
|
770
|
+
// Pending agent work this long after the latest answer reports that answer
|
|
771
|
+
// as still in progress (once); the final report still follows.
|
|
772
|
+
pendingCapMs = PENDING_CAP_MS,
|
|
183
773
|
log = (line) => process.stderr.write(`mixdog task sessions: ${line}\n`),
|
|
184
774
|
}) {
|
|
185
775
|
/** The report fields of turn `runId`'s files; none when it produced none. */
|
|
@@ -200,55 +790,51 @@ export function createAgentTaskSessions({
|
|
|
200
790
|
return value;
|
|
201
791
|
};
|
|
202
792
|
|
|
793
|
+
const followToFinal = (follow) =>
|
|
794
|
+
followTaskToFinal(
|
|
795
|
+
{ loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, pendingCapMs, log },
|
|
796
|
+
follow
|
|
797
|
+
);
|
|
798
|
+
|
|
203
799
|
/** Submit `prompt` as the origin agent's turn; its end (and a wait for the
|
|
204
|
-
* user in it) reports to the agent's home. Resolves on intake.
|
|
205
|
-
|
|
800
|
+
* user in it) reports to the agent's home. Resolves on intake. `submit`
|
|
801
|
+
* takes the turn in another way than a prompt to `sessionId` (a message
|
|
802
|
+
* into a group room, whose member answers in `sessionId`): given `{ id,
|
|
803
|
+
* onSettled }`, it resolves `{ accepted }`. */
|
|
804
|
+
async function agentTurn(sessionId, prompt, origin, id = `atask_${randomUUID()}`, beforeReport = null, submit = null) {
|
|
206
805
|
const sessions = service();
|
|
207
806
|
const target = origin.target === 'session' ? { target: 'session' } : {};
|
|
208
|
-
|
|
209
|
-
// unreported across a restart; a turn whose watch could not be stored
|
|
210
|
-
// runs without a report (reportsBack false) rather than claim one.
|
|
211
|
-
let notify = false;
|
|
212
|
-
if (origin.notify !== false) {
|
|
213
|
-
try {
|
|
214
|
-
await watches.add({
|
|
215
|
-
runId: id,
|
|
216
|
-
agentId: origin.agentId,
|
|
217
|
-
homeSessionId: origin.sessionId,
|
|
218
|
-
taskSessionId: sessionId,
|
|
219
|
-
title: origin.title,
|
|
220
|
-
...target,
|
|
221
|
-
...(origin.textOnly ? { textOnly: true } : {}),
|
|
222
|
-
owner: processToken,
|
|
223
|
-
at: Date.now(),
|
|
224
|
-
});
|
|
225
|
-
notify = true;
|
|
226
|
-
} catch (error) {
|
|
227
|
-
log(`watch of ${sessionId} failed, sending without a report: ${error?.message || error}`);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
807
|
+
const { notify, noReport } = await watchTurn({ watches, owner: processToken, log }, sessionId, origin, id);
|
|
230
808
|
const dropWatch = () =>
|
|
231
809
|
watches.remove(id).catch((error) => log(`dropping the watch of ${sessionId} failed: ${error?.message || error}`));
|
|
232
810
|
let runtime = null;
|
|
811
|
+
// A report that cannot be queued is retried in this process; when every try
|
|
812
|
+
// fails the durable watch stays and restart recovery delivers it (the queue
|
|
813
|
+
// dedupes by runId, so a retry never posts twice).
|
|
233
814
|
const report = (status, text = '') =>
|
|
234
815
|
Promise.resolve(beforeReport)
|
|
235
816
|
.then(async () =>
|
|
236
|
-
deliver
|
|
237
|
-
|
|
817
|
+
deliverWithRetries({ deliver, retryDelaysMs, log }, origin.agentId, sessionId, {
|
|
818
|
+
// 'working': a still-in-progress answer (followToFinal), not the end.
|
|
819
|
+
runId: `${id}_${{ blocked: 'blocked', working: 'progress' }[status] || 'end'}`,
|
|
238
820
|
taskSessionId: sessionId,
|
|
239
821
|
title: origin.title,
|
|
240
822
|
status,
|
|
241
823
|
text,
|
|
242
824
|
...target,
|
|
825
|
+
...(origin.hop > 0 ? { hop: origin.hop } : {}),
|
|
826
|
+
...roomFrom(origin.sessionId),
|
|
243
827
|
// A turn's end brings back the files it produced, when the agent
|
|
244
828
|
// may receive them (reportOrigin `textOnly`).
|
|
245
|
-
...(status === 'blocked'
|
|
829
|
+
...(status === 'blocked' || status === 'working'
|
|
246
830
|
? {}
|
|
247
831
|
: { watchId: id, ...(origin.textOnly ? {} : await artifactFields(sessionId, id, runtime)) }),
|
|
248
832
|
})
|
|
249
833
|
)
|
|
250
834
|
.catch((error) => log(`report of ${sessionId} failed: ${error?.message || error}`));
|
|
251
835
|
let unwatch = () => {};
|
|
836
|
+
// Set once the turn ended completed: the report waits for the final answer.
|
|
837
|
+
let follow = null;
|
|
252
838
|
let result;
|
|
253
839
|
try {
|
|
254
840
|
if (notify) {
|
|
@@ -256,27 +842,60 @@ export function createAgentTaskSessions({
|
|
|
256
842
|
let blocked = false;
|
|
257
843
|
unwatch =
|
|
258
844
|
runtime?.subscribe?.(() => {
|
|
845
|
+
follow?.check();
|
|
259
846
|
if (blocked || !runtime.getState?.()?.toolApproval) return;
|
|
260
847
|
blocked = true;
|
|
261
848
|
void report('blocked');
|
|
262
849
|
}) || unwatch;
|
|
263
850
|
}
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
851
|
+
// A bot's send to a session that is not its own task session reaches the
|
|
852
|
+
// model inside the sender envelope (`envelopeName`, set by reportOrigin).
|
|
853
|
+
const sent = origin.envelopeName
|
|
854
|
+
? wrapAgentMessage(prompt, { name: origin.envelopeName, agentId: origin.agentId })
|
|
855
|
+
: prompt;
|
|
856
|
+
const take = submit ?? ((options) => sessions.submitSession({ sessionId, prompt: sent, options }));
|
|
857
|
+
result = await take({
|
|
858
|
+
id,
|
|
859
|
+
mode: 'prompt',
|
|
860
|
+
// `fromAgent`: the sending bot, recorded when it writes into another
|
|
861
|
+
// bot's home chat (otherwise the row would read as the user's);
|
|
862
|
+
// `hop`: its place in a bot-to-bot chain (bot-hops.mjs).
|
|
863
|
+
transcriptMeta: {
|
|
864
|
+
sender: 'lead',
|
|
865
|
+
...(origin.envelopeName ? { fromAgent: origin.agentId } : {}),
|
|
866
|
+
...(origin.hop > 0 ? { hop: origin.hop } : {}),
|
|
279
867
|
},
|
|
868
|
+
...(notify
|
|
869
|
+
? {
|
|
870
|
+
// A room answers this send in a turn under another submission
|
|
871
|
+
// id (one turn for several sends, or a fresh turn for one cut
|
|
872
|
+
// short): the watch follows that turn, recorded before it runs.
|
|
873
|
+
follow: (submissionId) => watches.follow?.(id, submissionId),
|
|
874
|
+
onSettled: (detail) => {
|
|
875
|
+
const status = turnStatus(detail);
|
|
876
|
+
const text = detail?.result?.content || detail?.error || '';
|
|
877
|
+
// A failed or cancelled turn reports at once; a completed one
|
|
878
|
+
// reports the task's final answer (followToFinal).
|
|
879
|
+
if (status !== 'completed') {
|
|
880
|
+
unwatch();
|
|
881
|
+
void report(status, text);
|
|
882
|
+
return;
|
|
883
|
+
}
|
|
884
|
+
follow = followToFinal({
|
|
885
|
+
sessionId,
|
|
886
|
+
runId: id,
|
|
887
|
+
runtime,
|
|
888
|
+
immediate: { status, text },
|
|
889
|
+
finish: async (endStatus, endText) => {
|
|
890
|
+
unwatch();
|
|
891
|
+
await report(endStatus, endText);
|
|
892
|
+
},
|
|
893
|
+
progress: (progressText) => report('working', progressText),
|
|
894
|
+
});
|
|
895
|
+
follow.check();
|
|
896
|
+
},
|
|
897
|
+
}
|
|
898
|
+
: {}),
|
|
280
899
|
});
|
|
281
900
|
} catch (error) {
|
|
282
901
|
unwatch();
|
|
@@ -288,71 +907,38 @@ export function createAgentTaskSessions({
|
|
|
288
907
|
unwatch();
|
|
289
908
|
if (notify) await dropWatch();
|
|
290
909
|
}
|
|
291
|
-
return {
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
* from the target's record, or watch it until it ends. */
|
|
296
|
-
async function rearm(watch) {
|
|
297
|
-
const sessionId = watch.taskSessionId;
|
|
298
|
-
const finish = async (status, text = '') =>
|
|
299
|
-
deliver(watch.agentId, {
|
|
300
|
-
runId: `${watch.runId}_end`,
|
|
301
|
-
taskSessionId: sessionId,
|
|
302
|
-
title: watch.title,
|
|
303
|
-
status,
|
|
304
|
-
text,
|
|
305
|
-
...(watch.target === 'session' ? { target: 'session' } : {}),
|
|
306
|
-
watchId: watch.runId,
|
|
307
|
-
...(watch.textOnly || text === GONE_TEXT || text === LOST_TEXT
|
|
308
|
-
? {}
|
|
309
|
-
: await artifactFields(sessionId, watch.runId)),
|
|
310
|
-
});
|
|
311
|
-
if (!(await loadSession(sessionId))) return finish('cancelled', GONE_TEXT);
|
|
312
|
-
const runtime = await service().materializeSession(sessionId);
|
|
313
|
-
let done = false;
|
|
314
|
-
let checking = false;
|
|
315
|
-
let again = false;
|
|
316
|
-
let unsubscribe = () => {};
|
|
317
|
-
const check = async () => {
|
|
318
|
-
if (done) return;
|
|
319
|
-
if (checking) {
|
|
320
|
-
again = true;
|
|
321
|
-
return;
|
|
322
|
-
}
|
|
323
|
-
checking = true;
|
|
324
|
-
try {
|
|
325
|
-
do {
|
|
326
|
-
again = false;
|
|
327
|
-
const outcome = submissionOutcome((await loadSession(sessionId))?.messages, watch.runId);
|
|
328
|
-
const state = runtime?.getState?.() || {};
|
|
329
|
-
let end = outcome && outcome !== 'open' ? outcome : null;
|
|
330
|
-
if (!end && !state.busy) {
|
|
331
|
-
const queued = (state.queued || []).some((entry) => entry?.id === watch.runId);
|
|
332
|
-
if (outcome === 'open' && !queued) end = { status: 'cancelled', text: '' };
|
|
333
|
-
else if (!outcome && !queued && !(await queuedSubmissionIds(sessionId)).includes(watch.runId)) {
|
|
334
|
-
end = { status: 'cancelled', text: LOST_TEXT };
|
|
335
|
-
}
|
|
336
|
-
}
|
|
337
|
-
if (end) {
|
|
338
|
-
done = true;
|
|
339
|
-
unsubscribe();
|
|
340
|
-
await finish(end.status, end.text);
|
|
341
|
-
}
|
|
342
|
-
} while (again && !done);
|
|
343
|
-
} finally {
|
|
344
|
-
checking = false;
|
|
345
|
-
}
|
|
910
|
+
return {
|
|
911
|
+
accepted,
|
|
912
|
+
reportsBack: accepted && notify,
|
|
913
|
+
...(accepted && !notify ? { reportsBackReason: noReport } : {}),
|
|
346
914
|
};
|
|
347
|
-
const failed = (error) => log(`re-attaching ${sessionId} failed: ${error?.message || error}`);
|
|
348
|
-
unsubscribe = runtime?.subscribe?.(() => void check().catch(failed)) || unsubscribe;
|
|
349
|
-
await check();
|
|
350
915
|
}
|
|
351
916
|
|
|
917
|
+
const rearm = (watch) =>
|
|
918
|
+
rearmTaskWatch(
|
|
919
|
+
{
|
|
920
|
+
deliver,
|
|
921
|
+
loadSession,
|
|
922
|
+
materializeSession: (sessionId) => service().materializeSession(sessionId),
|
|
923
|
+
queuedSubmissionIds,
|
|
924
|
+
artifactFields,
|
|
925
|
+
followToFinal,
|
|
926
|
+
log,
|
|
927
|
+
},
|
|
928
|
+
watch
|
|
929
|
+
);
|
|
930
|
+
|
|
352
931
|
/** Daemon start: re-attach every reporting turn an earlier process watched.
|
|
353
932
|
* Resolves once each is reported or watched. */
|
|
354
933
|
async function recover() {
|
|
355
934
|
const pending = (await watches.list()).filter((watch) => watch?.owner !== processToken);
|
|
935
|
+
// The agents the restart killed become queued failure results first, so
|
|
936
|
+
// each re-attached task session has them to take (followToFinal wakeOwed).
|
|
937
|
+
if (pending.length) {
|
|
938
|
+
await Promise.resolve()
|
|
939
|
+
.then(settleAgentResults)
|
|
940
|
+
.catch((error) => log(`settling the agents of the previous process failed: ${error?.message || error}`));
|
|
941
|
+
}
|
|
356
942
|
await Promise.all(
|
|
357
943
|
pending.map((watch) =>
|
|
358
944
|
rearm(watch).catch((error) => log(`re-attaching ${watch?.taskSessionId} failed: ${error?.message || error}`))
|
|
@@ -361,12 +947,31 @@ export function createAgentTaskSessions({
|
|
|
361
947
|
return pending.length;
|
|
362
948
|
}
|
|
363
949
|
|
|
364
|
-
/** Create the task session for `brief` and start it; returns at once.
|
|
365
|
-
|
|
950
|
+
/** Create the task session for `brief` and start it; returns at once.
|
|
951
|
+
* `callerScope` is the caller's trusted worktree scope (worktree-access.mjs
|
|
952
|
+
* worktreeCallerScope): provisioning a worktree is a mutation only an
|
|
953
|
+
* attended conversation may make, so an explicit worktree from any other
|
|
954
|
+
* caller is refused and the New task default keeps it in the Project folder. */
|
|
955
|
+
async function create(
|
|
956
|
+
args = {},
|
|
957
|
+
{ callerSessionId, callerAgentId, callerCwd, callerHop = 0, callerScope, attachmentParts = [] } = {}
|
|
958
|
+
) {
|
|
366
959
|
if (!callerAgentId) throw new Error('session: create is only for an agent conversation');
|
|
367
960
|
const brief = typeof args.brief === 'string' ? args.brief.trim() : '';
|
|
368
961
|
if (!brief) throw new Error('session: brief is required for action=create');
|
|
369
|
-
const
|
|
962
|
+
const location = taskLocation(args.worktree);
|
|
963
|
+
if (location.mode === 'worktree') assertWorktreeMutationAllowed(callerScope, 'session: create with a worktree');
|
|
964
|
+
const projects = await listProjects();
|
|
965
|
+
const where = resolveTaskProject(args.project, { projects, cwd: callerCwd || process.cwd() });
|
|
966
|
+
// An explicit worktree is never silently dropped: it needs a registered Project.
|
|
967
|
+
const registered = where.project && projects.some((entry) => samePath(entry.path, where.project.path));
|
|
968
|
+
if (location.mode === 'worktree' && !registered) {
|
|
969
|
+
throw taskWorktreeFailure(
|
|
970
|
+
worktreeError('WORKTREE_PROJECT_UNKNOWN', 'A worktree needs a registered Project; run list-projects.', {
|
|
971
|
+
projectPath: where.project?.path ?? null,
|
|
972
|
+
})
|
|
973
|
+
);
|
|
974
|
+
}
|
|
370
975
|
const requestId = `atask_${randomUUID()}`;
|
|
371
976
|
const title = (clean(args.title) || clean(brief.split('\n')[0])).slice(0, TITLE_LIMIT);
|
|
372
977
|
const taskOrigin = {
|
|
@@ -384,6 +989,16 @@ export function createAgentTaskSessions({
|
|
|
384
989
|
: (await leadRoute()) || (await lastUsedRoute(service()));
|
|
385
990
|
if (!route) throw new Error('session: no model to start the task session on. Choose a Lead model first.');
|
|
386
991
|
const sessions = service();
|
|
992
|
+
// Where it starts: the `worktree` option, else the user's New task default
|
|
993
|
+
// for a caller that may provision one. Decided before the session exists,
|
|
994
|
+
// so a failure here leaves nothing behind.
|
|
995
|
+
const explicit = location.mode === 'worktree';
|
|
996
|
+
const inWorktree =
|
|
997
|
+
explicit ||
|
|
998
|
+
(location.mode === 'default' &&
|
|
999
|
+
where.project &&
|
|
1000
|
+
callerScope?.attended === true &&
|
|
1001
|
+
(await newTaskDefault()) === 'worktree');
|
|
387
1002
|
const created = await sessions.createSession({
|
|
388
1003
|
...route,
|
|
389
1004
|
cwd: where.cwd,
|
|
@@ -392,19 +1007,42 @@ export function createAgentTaskSessions({
|
|
|
392
1007
|
});
|
|
393
1008
|
const sessionId = String(created?.sessionId || '');
|
|
394
1009
|
if (!sessionId) throw new Error('session: task session creation returned no session id');
|
|
395
|
-
|
|
396
|
-
if (where.project && (await newTaskDefault()) === 'worktree') {
|
|
1010
|
+
if (inWorktree) {
|
|
397
1011
|
try {
|
|
398
|
-
await sessions
|
|
1012
|
+
await requestTaskWorktree(sessions, sessionId, {
|
|
1013
|
+
title,
|
|
1014
|
+
requestId,
|
|
1015
|
+
name: location.name,
|
|
1016
|
+
confirmLarge: args.confirm_large === true,
|
|
1017
|
+
explicit,
|
|
1018
|
+
});
|
|
1019
|
+
} catch (error) {
|
|
1020
|
+
// Nothing was sent: drop the empty session so a retry starts clean.
|
|
1021
|
+
let dropFailure = null;
|
|
1022
|
+
try {
|
|
1023
|
+
const dropped = await sessions.configureSession(
|
|
1024
|
+
{ sessionId, action: 'deleteSession', args: [sessionId] },
|
|
1025
|
+
{ revision: 1 }
|
|
1026
|
+
);
|
|
1027
|
+
// A resolved `false` is a refusal, not a deletion.
|
|
1028
|
+
if (dropped?.value === false) dropFailure = new Error('the session service refused the deletion');
|
|
1029
|
+
} catch (deleteError) {
|
|
1030
|
+
dropFailure = deleteError;
|
|
1031
|
+
}
|
|
1032
|
+
if (dropFailure) log(`dropping ${sessionId} failed: ${dropFailure?.message || dropFailure}`);
|
|
1033
|
+
if (!dropFailure) throw error;
|
|
1034
|
+
// The model must know the empty session is still there to delete.
|
|
1035
|
+
throw Object.assign(
|
|
1036
|
+
new Error(
|
|
1037
|
+
`${error?.message || error} The empty task session ${sessionId} could not be deleted either (${dropFailure?.message || dropFailure}); delete it with session action=delete session_id=${sessionId}.`
|
|
1038
|
+
),
|
|
399
1039
|
{
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
1040
|
+
...(typeof error?.code === 'string' ? { code: error.code } : {}),
|
|
1041
|
+
...(error?.details ? { details: error.details } : {}),
|
|
1042
|
+
orphanSessionId: sessionId,
|
|
1043
|
+
cause: error,
|
|
1044
|
+
}
|
|
405
1045
|
);
|
|
406
|
-
} catch (error) {
|
|
407
|
-
if (error?.code !== 'WORKTREE_UNSUPPORTED_REPO') throw error;
|
|
408
1046
|
}
|
|
409
1047
|
}
|
|
410
1048
|
// "Started" lands in the home before any report of the brief's turn.
|
|
@@ -414,7 +1052,13 @@ export function createAgentTaskSessions({
|
|
|
414
1052
|
});
|
|
415
1053
|
// The brief carries the files the agent forwarded (workspace-tools.mjs).
|
|
416
1054
|
const prompt = handoffContent(brief, attachmentParts);
|
|
417
|
-
const { accepted, reportsBack } = await agentTurn(
|
|
1055
|
+
const { accepted, reportsBack, reportsBackReason } = await agentTurn(
|
|
1056
|
+
sessionId,
|
|
1057
|
+
prompt,
|
|
1058
|
+
callerHop > 0 ? { ...taskOrigin, hop: callerHop } : taskOrigin,
|
|
1059
|
+
requestId,
|
|
1060
|
+
ready
|
|
1061
|
+
);
|
|
418
1062
|
if (!accepted) throw new Error(`session: task session ${sessionId} did not accept the brief`);
|
|
419
1063
|
if (reportsBack) {
|
|
420
1064
|
await deliver(callerAgentId, {
|
|
@@ -422,39 +1066,27 @@ export function createAgentTaskSessions({
|
|
|
422
1066
|
taskSessionId: sessionId,
|
|
423
1067
|
title,
|
|
424
1068
|
status: 'started',
|
|
1069
|
+
...roomFrom(callerSessionId),
|
|
425
1070
|
}).catch((error) => log(`start line of ${sessionId} failed: ${error?.message || error}`));
|
|
426
1071
|
}
|
|
427
1072
|
started();
|
|
1073
|
+
// Started, not done: the final answer is reported later.
|
|
428
1074
|
return {
|
|
429
1075
|
session_id: sessionId,
|
|
1076
|
+
status: 'started',
|
|
430
1077
|
title,
|
|
431
1078
|
project: where.project?.path ?? null,
|
|
432
|
-
...(taskOrigin.notify && !reportsBack ? { reportsBack: false } : {}),
|
|
1079
|
+
...(taskOrigin.notify && !reportsBack ? { reportsBack: false, reportsBackReason } : {}),
|
|
433
1080
|
};
|
|
434
1081
|
}
|
|
435
1082
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
* this send only — never stored on the target, whose own origin and owner
|
|
439
|
-
* stay as they are. Null for a caller that is no agent conversation. */
|
|
440
|
-
async function reportOrigin(sessionId, agentId, callerSessionId) {
|
|
441
|
-
if (!agentId) return null;
|
|
442
|
-
const target = await loadSession(sessionId);
|
|
443
|
-
const origin = target?.taskOrigin;
|
|
444
|
-
if (origin?.agentId === agentId) return origin;
|
|
445
|
-
const title = (clean(target?.title) || sessionId).slice(0, TITLE_LIMIT);
|
|
446
|
-
// Files come back only from sessions the agent created (above) or its own
|
|
447
|
-
// room member threads; any other session reports its text alone.
|
|
448
|
-
const own = roomMemberOfSession(sessionId)?.agentId === agentId;
|
|
449
|
-
return {
|
|
450
|
-
agentId,
|
|
451
|
-
sessionId: callerSessionId,
|
|
452
|
-
title,
|
|
453
|
-
notify: true,
|
|
454
|
-
target: 'session',
|
|
455
|
-
...(own ? {} : { textOnly: true }),
|
|
456
|
-
};
|
|
457
|
-
}
|
|
1083
|
+
const reportOrigin = (sessionId, agentId, callerSessionId) =>
|
|
1084
|
+
sendReportOrigin({ loadSession, agentName }, sessionId, agentId, callerSessionId);
|
|
458
1085
|
|
|
459
|
-
return {
|
|
1086
|
+
return {
|
|
1087
|
+
create,
|
|
1088
|
+
reportOrigin,
|
|
1089
|
+
recover,
|
|
1090
|
+
send: (sessionId, prompt, origin, submit = null) => agentTurn(sessionId, prompt, origin, undefined, null, submit),
|
|
1091
|
+
};
|
|
460
1092
|
}
|