mixdog 1.3.1 → 1.3.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/README.md +8 -4
- package/package.json +2 -2
- package/scripts/native-release-status.mjs +238 -0
- package/src/defaults/shipped-history.json +5 -1
- 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 +2 -2
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- 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 +80 -31
- 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 +67 -38
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- 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-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- 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 +9 -0
- 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-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -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 +12 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -5
- 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 +2 -0
- 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 +17 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +133 -38
- 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/execution-tail.mjs +14 -11
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
- 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 +5 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -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 +230 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +136 -32
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -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 +189 -105
- 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 +3 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -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/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -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/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-lifecycle.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +3 -8
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
- 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 +42 -10
- 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 +120 -9
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
- 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/sweep-record.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -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/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 +35 -13
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -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/lib/shell-job-records.mjs +6 -14
- 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 +34 -21
- 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/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/store.mjs +49 -30
- 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 +5 -2
- package/src/runtime/effort-judge/model-install.mjs +16 -8
- 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/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 +4 -0
- 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-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 +30 -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/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/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 +6 -0
- 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 +44 -2
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +41 -42
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
- package/src/runtime/shared/worktrees/registry.mjs +26 -26
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +555 -199
- package/src/session-runtime/agent-room-delivery.mjs +143 -36
- package/src/session-runtime/agent-task-sessions.mjs +667 -165
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +45 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +11 -1
- 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 +2 -2
- 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 +36 -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 +19 -4
- 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-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/room-member-gate.mjs +16 -3
- 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-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/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 +4 -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 +14 -4
- 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/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +8 -2
- package/src/session-runtime/setup-tool/settings-contract.mjs +31 -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 +265 -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 +165 -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 +47 -369
- package/src/session-runtime/tool-surface.mjs +5 -3
- 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 +2 -1
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +519 -333
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +46 -46
- 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/workspace-state.mjs +7 -6
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-session-control.mjs +51 -10
- package/src/standalone/daemon.mjs +28 -10
- package/src/standalone/local-session-runtime.mjs +22 -14
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-service.mjs +11 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +35 -9
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- 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 +529 -333
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/session-api/intake/abort.mjs +19 -1
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -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 +185 -20
- 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
|
@@ -15,6 +15,11 @@
|
|
|
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 the ROOM's log as that member's row and in its own
|
|
22
|
+
* member session, never in its home (agent-home-delivery.mjs enqueueTask).
|
|
18
23
|
*/
|
|
19
24
|
import { randomUUID } from 'node:crypto';
|
|
20
25
|
import { realpathSync, statSync } from 'node:fs';
|
|
@@ -22,6 +27,9 @@ import { basename, resolve } from 'node:path';
|
|
|
22
27
|
import { clean } from '../runtime/agent/orchestrator/runtime-core/session-text.mjs';
|
|
23
28
|
import { collectTurnArtifacts, handoffContent, sessionRoots, turnSlice } from './handoff-attachments.mjs';
|
|
24
29
|
import { roomMemberOfSession } from '../runtime/agent/orchestrator/session/agent-room.mjs';
|
|
30
|
+
import { wrapAgentMessage } from '../runtime/shared/agent-message-envelope.mjs';
|
|
31
|
+
import { sessionsWithPendingWork } from '../runtime/shared/pending-agent-work.mjs';
|
|
32
|
+
import { agentTaskIdOfExecution } from './services/agent-tool/restart-notify.mjs';
|
|
25
33
|
|
|
26
34
|
const TITLE_LIMIT = 60;
|
|
27
35
|
|
|
@@ -105,12 +113,549 @@ export function submissionOutcome(messages, runId) {
|
|
|
105
113
|
return 'open';
|
|
106
114
|
}
|
|
107
115
|
|
|
116
|
+
// `agent` tool calls that can start a job (a spawn, or a send that begins one).
|
|
117
|
+
const STARTING_AGENT_CALLS = new Set(['spawn', 'send']);
|
|
118
|
+
|
|
119
|
+
const callArguments = (call) => {
|
|
120
|
+
const raw = call?.arguments ?? call?.function?.arguments;
|
|
121
|
+
if (raw && typeof raw === 'object') return raw;
|
|
122
|
+
try {
|
|
123
|
+
return JSON.parse(String(raw || '{}')) || {};
|
|
124
|
+
} catch {
|
|
125
|
+
return {};
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
/** The agent task ids a woken turn's prompt delivered, from its stored
|
|
130
|
+
* provenance: the batch's `consumedExecutionIds` (session-flow/drain.mjs)
|
|
131
|
+
* or the single `execution` of a queue-fed turn; null for an id that names
|
|
132
|
+
* no task. Empty when the turn recorded none. */
|
|
133
|
+
function consumedTaskIds(message) {
|
|
134
|
+
const recorded = message?.meta?.transcript?.consumedExecutionIds;
|
|
135
|
+
const ids = Array.isArray(recorded) ? recorded : message?.meta?.execution?.id ? [message.meta.execution.id] : [];
|
|
136
|
+
return ids.map((id) => (id ? agentTaskIdOfExecution(id) : null));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** The latest turn of the chain that submission `runId` started in a
|
|
140
|
+
* session's stored `messages`, from structured provenance only: runId's own
|
|
141
|
+
* turn, then each turn that took a result of an agent task the chain started
|
|
142
|
+
* — as its prompt (a woken turn) or steered into it while it ran, even a turn
|
|
143
|
+
* the user started (its `agent` spawn/send results record
|
|
144
|
+
* `agentTaskIds`, tool-batch/finalize.mjs; the tasks those turns start join
|
|
145
|
+
* the chain). A turn the user typed, or one woken only by other agents,
|
|
146
|
+
* never joins it. `ambiguous` is set when provenance a chain decision needs
|
|
147
|
+
* is missing — a starting agent call of the chain without `agentTaskIds`,
|
|
148
|
+
* or a woken turn naming no task — so its answer may not be the final one.
|
|
149
|
+
* `{ id, outcome, at, ambiguous }`: that turn's submission id, its
|
|
150
|
+
* submissionOutcome and when its end was recorded (0 when unknown); null
|
|
151
|
+
* when no turn took runId. */
|
|
152
|
+
export function latestTurnOutcome(messages, runId) {
|
|
153
|
+
const list = Array.isArray(messages) ? messages : [];
|
|
154
|
+
const start = list.findIndex((message) => submissionIdsOf(message).includes(runId));
|
|
155
|
+
if (start < 0) return null;
|
|
156
|
+
const started = new Set();
|
|
157
|
+
const startingCalls = new Set();
|
|
158
|
+
let at = start;
|
|
159
|
+
let id = runId;
|
|
160
|
+
let inChain = true;
|
|
161
|
+
let ambiguous = false;
|
|
162
|
+
// The turn the walk is in: where its prompt is stored and its submission id.
|
|
163
|
+
let turnAt = start;
|
|
164
|
+
let turnId = runId;
|
|
165
|
+
for (let index = start; index < list.length; index += 1) {
|
|
166
|
+
const message = list[index];
|
|
167
|
+
const checkpointId = index > start && message?.role === 'user' ? message.meta?.transcript?.checkpointId : '';
|
|
168
|
+
if (checkpointId) {
|
|
169
|
+
turnAt = index;
|
|
170
|
+
turnId = checkpointId;
|
|
171
|
+
inChain = false;
|
|
172
|
+
}
|
|
173
|
+
// A completion delivered as a turn's prompt (queue/bus wake), or steered
|
|
174
|
+
// into a turn already running (loop/steering.mjs, no checkpoint of its
|
|
175
|
+
// own): the turn that took the chain's result joins the chain, even one
|
|
176
|
+
// the user started, so its answer becomes the candidate.
|
|
177
|
+
const delivered =
|
|
178
|
+
index > start && message?.role === 'user' && message.meta?.source === 'task-notification'
|
|
179
|
+
? consumedTaskIds(message)
|
|
180
|
+
: null;
|
|
181
|
+
if (delivered && !inChain) {
|
|
182
|
+
if (delivered.some((taskId) => taskId && started.has(taskId))) {
|
|
183
|
+
inChain = true;
|
|
184
|
+
at = turnAt;
|
|
185
|
+
id = turnId;
|
|
186
|
+
} else if (!delivered.length || delivered.includes(null)) {
|
|
187
|
+
ambiguous = true;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (checkpointId || delivered) continue;
|
|
191
|
+
if (!inChain) continue;
|
|
192
|
+
if (message?.role === 'assistant') {
|
|
193
|
+
for (const call of Array.isArray(message.toolCalls) ? message.toolCalls : []) {
|
|
194
|
+
const name = String(call?.name ?? call?.function?.name ?? '');
|
|
195
|
+
const type = String(callArguments(call).type || 'spawn').trim();
|
|
196
|
+
if (name === 'agent' && STARTING_AGENT_CALLS.has(type) && call?.id) startingCalls.add(call.id);
|
|
197
|
+
}
|
|
198
|
+
continue;
|
|
199
|
+
}
|
|
200
|
+
if (message?.role !== 'tool' || !startingCalls.has(message.toolCallId)) continue;
|
|
201
|
+
if (Array.isArray(message.agentTaskIds)) for (const taskId of message.agentTaskIds) started.add(String(taskId));
|
|
202
|
+
else if (message.toolKind !== 'error') ambiguous = true;
|
|
203
|
+
}
|
|
204
|
+
const turn = list.slice(at);
|
|
205
|
+
const ended = turn.find((message) => message?.meta?.transcript?.completion);
|
|
206
|
+
return { id, outcome: submissionOutcome(turn, id), at: Number(ended?.meta?.transcript?.at) || 0, ambiguous };
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** Whether task session `sessionId` still waits on agent work it handed off,
|
|
210
|
+
* from `agentWorking` (Lead ids with an Agent session in flight), `owed`
|
|
211
|
+
* (agent results queued for it that no turn has taken yet), `pool` (agent
|
|
212
|
+
* pool rows: the desktop notifier's rule, pending-agent-work.mjs) and
|
|
213
|
+
* `agentTaskOwners` (the sessions owning a queued, spawning or running agent
|
|
214
|
+
* task — one still waiting for admission has no turn in flight, and its pool
|
|
215
|
+
* row reads `unknown`). An agent task owned by an Agent session under it, at
|
|
216
|
+
* any depth, counts too: every descendant's pool row names the Lead as owner. */
|
|
217
|
+
export function pendingWorkOf(
|
|
218
|
+
sessionId,
|
|
219
|
+
{ agentWorking = new Set(), owed = [], pool = [], agentTaskOwners = new Set() }
|
|
220
|
+
) {
|
|
221
|
+
if (agentWorking.has(sessionId) || owed.length > 0 || agentTaskOwners.has(sessionId)) return true;
|
|
222
|
+
if (sessionsWithPendingWork(pool).has(sessionId)) return true;
|
|
223
|
+
return pool.some((row) => row?.ownerSessionId === sessionId && agentTaskOwners.has(String(row.sessionId || '')));
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** The agent results queued for `sessionId` that no turn has taken yet (its
|
|
227
|
+
* durable pending queue: an agent's completion, a restart failure): `{ text,
|
|
228
|
+
* meta }` as the notification bus delivers them. */
|
|
229
|
+
export async function owedAgentResults(sessionId) {
|
|
230
|
+
const [{ _getPendingMessagesForSession }, { completionIsDelivered, isCompletionNotificationEntry }] =
|
|
231
|
+
await Promise.all([
|
|
232
|
+
import('../runtime/agent/orchestrator/session/manager/pending-messages.mjs'),
|
|
233
|
+
import('../runtime/agent/orchestrator/session/manager/pending-message-entry.mjs'),
|
|
234
|
+
]);
|
|
235
|
+
return _getPendingMessagesForSession(sessionId)
|
|
236
|
+
.filter((entry) => isCompletionNotificationEntry(entry) && !completionIsDelivered(entry))
|
|
237
|
+
.map((entry) => ({
|
|
238
|
+
text: String(entry.text || entry.content || ''),
|
|
239
|
+
meta: {
|
|
240
|
+
type: 'agent_task_result',
|
|
241
|
+
execution_surface: entry.execution?.surface || 'agent',
|
|
242
|
+
execution_id: entry.executionId || entry.execution?.id || null,
|
|
243
|
+
status: entry.execution?.status || null,
|
|
244
|
+
caller_session_id: sessionId,
|
|
245
|
+
},
|
|
246
|
+
}));
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** pendingWorkOf over this process's state: the in-flight runtimes, the
|
|
250
|
+
* durable pending queue, the agent pool and the background agent tasks. */
|
|
251
|
+
export async function taskSessionPendingWork(sessionId, { owedResults = owedAgentResults } = {}) {
|
|
252
|
+
const [{ inFlightSessionActivity }, { listStoredAgentWorkersAsync }, { activeBackgroundTaskSessionIds }] =
|
|
253
|
+
await Promise.all([
|
|
254
|
+
import('../runtime/agent/orchestrator/session/manager/runtime-state.mjs'),
|
|
255
|
+
import('../runtime/agent/orchestrator/session/store-summary-reader.mjs'),
|
|
256
|
+
import('../runtime/shared/background-tasks.mjs'),
|
|
257
|
+
]);
|
|
258
|
+
const { working, agentWorking } = inFlightSessionActivity();
|
|
259
|
+
const agentTaskOwners = activeBackgroundTaskSessionIds({ surface: 'agent' });
|
|
260
|
+
if (pendingWorkOf(sessionId, { agentWorking, agentTaskOwners })) return true;
|
|
261
|
+
if ((await owedResults(sessionId)).length) return true;
|
|
262
|
+
const pool = await listStoredAgentWorkersAsync({ runningSessionIds: working });
|
|
263
|
+
return pendingWorkOf(sessionId, { pool, agentTaskOwners });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// A task session idle with nothing pending must stay so this long before its
|
|
267
|
+
// latest answer is final: a finished agent's result can start the Lead's next
|
|
268
|
+
// turn right after (the desktop notifier's quiet period).
|
|
269
|
+
const FINAL_SETTLE_MS = 2500;
|
|
270
|
+
// While a report waits on pending work it re-checks this often (state changes
|
|
271
|
+
// of the session re-check at once).
|
|
272
|
+
const FINAL_POLL_MS = 15_000;
|
|
273
|
+
|
|
274
|
+
const REPORT_RETRY_DELAYS_MS = [200, 1000, 5000];
|
|
275
|
+
|
|
108
276
|
const LOST_TEXT = 'The message never ran: the app restarted before its turn started.';
|
|
109
277
|
const GONE_TEXT = 'The session no longer exists.';
|
|
278
|
+
const RESUME_FAILED_TEXT =
|
|
279
|
+
'The agent task session could not continue after the app restarted: its agents’ results never reached it. Check that session.';
|
|
280
|
+
const PENDING_CAP_MS = 30 * 60_000;
|
|
281
|
+
const STILL_WORKING_TEXT =
|
|
282
|
+
'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.';
|
|
283
|
+
// Deliveries of a restarted task session's queued agent results before its
|
|
284
|
+
// report gives up as RESUME_FAILED_TEXT.
|
|
285
|
+
const OWED_WAKE_TRIES = 3;
|
|
286
|
+
|
|
287
|
+
// Why a send or create reports nothing back (its `reportsBackReason`).
|
|
288
|
+
const NOTIFY_OFF_REASON = 'this agent task session was created with notify: false, so its turns do not report back';
|
|
289
|
+
const WATCH_CAP_REASON =
|
|
290
|
+
'this chat already waits on as many unreported session results as it can track; earlier ones must report first';
|
|
110
291
|
|
|
111
292
|
// The last-used model search stops after this many catalog pages.
|
|
112
293
|
const LAST_USED_ROUTE_MAX_PAGES = 20;
|
|
113
294
|
|
|
295
|
+
/** A report's `from` when its origin is a group-room member session: the
|
|
296
|
+
* report goes to that member's room instead of the agent's home. */
|
|
297
|
+
const roomFrom = (originSessionId) => (roomMemberOfSession(originSessionId) ? { from: originSessionId } : {});
|
|
298
|
+
|
|
299
|
+
/** The route of `modelRef`: a provider/model[@effort…] ref (a superseded
|
|
300
|
+
* model runs on its successor) or a preset name. */
|
|
301
|
+
async function configModelRoute(modelRef) {
|
|
302
|
+
const [{ modelRouteFields, parseScheduleModelRef }, { migrateCodexRoute }, { loadConfig }] = await Promise.all([
|
|
303
|
+
import('../runtime/shared/schedule-model-ref.mjs'),
|
|
304
|
+
import('../runtime/agent/orchestrator/providers/openai-oauth-catalog-store.mjs'),
|
|
305
|
+
import('../runtime/agent/orchestrator/config.mjs'),
|
|
306
|
+
]);
|
|
307
|
+
const ref = parseScheduleModelRef(modelRef);
|
|
308
|
+
if (ref && typeof ref === 'object') return modelRouteFields(migrateCodexRoute(ref));
|
|
309
|
+
const preset = loadConfig({ secrets: false }).presets?.find((p) => p.id === ref || p.name === ref);
|
|
310
|
+
if (!preset) throw new Error(`model "${ref}" is neither a provider/model route nor a known preset name`);
|
|
311
|
+
return modelRouteFields(preset);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** The Lead (Main) route of the config — what a desktop New task starts on.
|
|
315
|
+
* Null while none is chosen. */
|
|
316
|
+
async function configLeadRoute() {
|
|
317
|
+
const [{ modelRouteFields }, { resolveRoute }, { loadConfig }] = await Promise.all([
|
|
318
|
+
import('../runtime/shared/schedule-model-ref.mjs'),
|
|
319
|
+
import('./boot/shared.mjs'),
|
|
320
|
+
import('../runtime/agent/orchestrator/config.mjs'),
|
|
321
|
+
]);
|
|
322
|
+
const route = resolveRoute(loadConfig({ secrets: false }), {});
|
|
323
|
+
return clean(route?.provider) && clean(route?.model) ? modelRouteFields(route) : null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** The model the user last used in a coding session (the runtime's stand-in
|
|
327
|
+
* for the desktop's last New-task model): the first row that has one in the
|
|
328
|
+
* catalog, which lists newest activity first. Pages on (bounded) until a
|
|
329
|
+
* row qualifies; null when none does. */
|
|
330
|
+
async function lastUsedSessionRoute(sessionService) {
|
|
331
|
+
let cursor = null;
|
|
332
|
+
for (let page = 0; page < LAST_USED_ROUTE_MAX_PAGES; page += 1) {
|
|
333
|
+
const listed = await sessionService.listSessionCatalog({ page: { limit: 50, cursor } });
|
|
334
|
+
const used = (Array.isArray(listed?.sessions) ? listed.sessions : []).find(
|
|
335
|
+
(row) => row?.sessionMode !== 'agent' && clean(row?.provider) && clean(row?.model)
|
|
336
|
+
);
|
|
337
|
+
if (used) return { provider: clean(used.provider), model: clean(used.model) };
|
|
338
|
+
cursor = listed?.nextCursor || null;
|
|
339
|
+
if (!cursor) return null;
|
|
340
|
+
}
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** Wait for the final answer of the task turn `runId` of `sessionId` ended
|
|
345
|
+
* completed: the end of the latest turn since it (a Lead that handed work
|
|
346
|
+
* to agents ends its turn and answers in the turn their results wake),
|
|
347
|
+
* once the session is idle with no pending agent work (pendingWork) for
|
|
348
|
+
* `settleMs`. `immediate` is runId's own end, when this process saw it.
|
|
349
|
+
* `finish(status, text)` runs once; `check` re-evaluates (call it on every
|
|
350
|
+
* state change of `runtime`), `stop` ends the wait. */
|
|
351
|
+
function followTaskToFinal(
|
|
352
|
+
{ loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, pendingCapMs, log },
|
|
353
|
+
{ sessionId, runId, runtime, immediate = null, wakeOwed = false, finish, progress = null }
|
|
354
|
+
) {
|
|
355
|
+
let progressed = false;
|
|
356
|
+
let pendingSince = 0;
|
|
357
|
+
// Agent work still pending `pendingCapMs` after the chain's latest turn
|
|
358
|
+
// ended: `progress(text)` reports that answer once, marked still in
|
|
359
|
+
// progress, and the wait for the final one goes on.
|
|
360
|
+
async function reportProgress() {
|
|
361
|
+
if (progressed || typeof progress !== 'function') return;
|
|
362
|
+
const latest = latestTurnOutcome((await loadSession(sessionId))?.messages, runId);
|
|
363
|
+
let end = latest?.outcome && latest.outcome !== 'open' ? latest.outcome : null;
|
|
364
|
+
if (immediate && (!latest || latest.id === runId)) end = immediate;
|
|
365
|
+
if (!end) return;
|
|
366
|
+
pendingSince ||= Date.now();
|
|
367
|
+
if (Date.now() - (latest?.at || pendingSince) < pendingCapMs) return;
|
|
368
|
+
progressed = true;
|
|
369
|
+
await progress(`${STILL_WORKING_TEXT}\n\n${end.text}`);
|
|
370
|
+
}
|
|
371
|
+
let done = false;
|
|
372
|
+
let checking = false;
|
|
373
|
+
let again = false;
|
|
374
|
+
let candidate = '';
|
|
375
|
+
let candidateAt = 0;
|
|
376
|
+
let timer = null;
|
|
377
|
+
let wakes = 0;
|
|
378
|
+
let wokeAt = 0;
|
|
379
|
+
const unwoken = (why) => {
|
|
380
|
+
log(`${sessionId} could not take its agents' results after the restart: ${why}`);
|
|
381
|
+
return { key: 'unwoken', status: 'failed', text: RESUME_FAILED_TEXT, now: true };
|
|
382
|
+
};
|
|
383
|
+
// `wakeOwed` (a restarted watch): deliver the agent results queued for
|
|
384
|
+
// the idle session through the notification bus so a turn takes them, at
|
|
385
|
+
// most OWED_WAKE_TRIES times, a poll apart; null while that turn is owed.
|
|
386
|
+
async function wakeForOwed() {
|
|
387
|
+
const owed = await owedResults(sessionId);
|
|
388
|
+
if (!owed.length) return undefined;
|
|
389
|
+
if (Date.now() - wokeAt < pollMs) return null;
|
|
390
|
+
if (wakes >= OWED_WAKE_TRIES) return unwoken(`still not taken after ${wakes} deliveries`);
|
|
391
|
+
wakes += 1;
|
|
392
|
+
wokeAt = Date.now();
|
|
393
|
+
let delivered = false;
|
|
394
|
+
try {
|
|
395
|
+
if (typeof deliverCompletion === 'function') {
|
|
396
|
+
const results = await Promise.all(owed.map(({ text, meta }) => deliverCompletion(sessionId, text, meta)));
|
|
397
|
+
delivered = results.some((result) => result === true);
|
|
398
|
+
}
|
|
399
|
+
} catch (error) {
|
|
400
|
+
return unwoken(error?.message || error);
|
|
401
|
+
}
|
|
402
|
+
return delivered ? null : unwoken('no session runtime took the delivery');
|
|
403
|
+
}
|
|
404
|
+
const arm = (ms) => {
|
|
405
|
+
clearTimeout(timer);
|
|
406
|
+
timer = setTimeout(() => void check().catch(failed), ms);
|
|
407
|
+
timer.unref?.();
|
|
408
|
+
};
|
|
409
|
+
const stop = () => {
|
|
410
|
+
done = true;
|
|
411
|
+
clearTimeout(timer);
|
|
412
|
+
};
|
|
413
|
+
const failed = (error) => log(`following ${sessionId} to its final answer failed: ${error?.message || error}`);
|
|
414
|
+
// The end to report now, or null while the task still works.
|
|
415
|
+
async function ready() {
|
|
416
|
+
if (runtime?.getState?.()?.busy) return null;
|
|
417
|
+
if (wakeOwed) {
|
|
418
|
+
const woke = await wakeForOwed();
|
|
419
|
+
if (woke !== undefined) return woke;
|
|
420
|
+
}
|
|
421
|
+
if (await pendingWork(sessionId)) {
|
|
422
|
+
await reportProgress();
|
|
423
|
+
return null;
|
|
424
|
+
}
|
|
425
|
+
const stored = await loadSession(sessionId);
|
|
426
|
+
if (!stored && !immediate) return { key: 'gone', status: 'cancelled', text: GONE_TEXT };
|
|
427
|
+
const latest = latestTurnOutcome(stored?.messages, runId);
|
|
428
|
+
// Provenance the chain needs is missing: its answer may not be final.
|
|
429
|
+
if (latest?.ambiguous) {
|
|
430
|
+
await reportProgress();
|
|
431
|
+
return null;
|
|
432
|
+
}
|
|
433
|
+
if (immediate && (!latest || latest.id === runId)) return { key: runId, ...immediate };
|
|
434
|
+
if (!latest?.outcome || latest.outcome === 'open') return null;
|
|
435
|
+
return { key: `${latest.id}:${latest.outcome.status}`, ...latest.outcome };
|
|
436
|
+
}
|
|
437
|
+
async function check() {
|
|
438
|
+
if (done) return;
|
|
439
|
+
if (checking) {
|
|
440
|
+
again = true;
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
checking = true;
|
|
444
|
+
try {
|
|
445
|
+
do {
|
|
446
|
+
again = false;
|
|
447
|
+
const end = await ready();
|
|
448
|
+
if (done) return;
|
|
449
|
+
if (!end) {
|
|
450
|
+
candidate = '';
|
|
451
|
+
arm(pollMs);
|
|
452
|
+
} else if (!end.now && settleMs > 0 && (end.key !== candidate || Date.now() - candidateAt < settleMs)) {
|
|
453
|
+
// Final only if it is still the answer after the quiet period.
|
|
454
|
+
if (end.key !== candidate) {
|
|
455
|
+
candidate = end.key;
|
|
456
|
+
candidateAt = Date.now();
|
|
457
|
+
}
|
|
458
|
+
arm(candidateAt + settleMs - Date.now());
|
|
459
|
+
} else {
|
|
460
|
+
stop();
|
|
461
|
+
await finish(end.status, end.text);
|
|
462
|
+
}
|
|
463
|
+
} while (again && !done);
|
|
464
|
+
} finally {
|
|
465
|
+
checking = false;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
return { check: () => check().catch(failed), stop };
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
/** Store the durable watch of turn `id` before it can start, so no end goes
|
|
472
|
+
* unreported across a restart; a turn whose watch could not be stored runs
|
|
473
|
+
* without a report (reportsBack false) rather than claim one. `noReport`:
|
|
474
|
+
* why the turn will not report back, told to the bot (reportsBackReason). */
|
|
475
|
+
async function watchTurn({ watches, owner, log }, sessionId, origin, id) {
|
|
476
|
+
if (origin.notify === false) return { notify: false, noReport: NOTIFY_OFF_REASON };
|
|
477
|
+
try {
|
|
478
|
+
await watches.add({
|
|
479
|
+
runId: id,
|
|
480
|
+
agentId: origin.agentId,
|
|
481
|
+
homeSessionId: origin.sessionId,
|
|
482
|
+
taskSessionId: sessionId,
|
|
483
|
+
title: origin.title,
|
|
484
|
+
...(origin.target === 'session' ? { target: 'session' } : {}),
|
|
485
|
+
...(origin.textOnly ? { textOnly: true } : {}),
|
|
486
|
+
...(origin.hop > 0 ? { hop: origin.hop } : {}),
|
|
487
|
+
owner,
|
|
488
|
+
at: Date.now(),
|
|
489
|
+
});
|
|
490
|
+
return { notify: true, noReport: '' };
|
|
491
|
+
} catch (error) {
|
|
492
|
+
log(`watch of ${sessionId} failed, sending without a report: ${error?.message || error}`);
|
|
493
|
+
return {
|
|
494
|
+
notify: false,
|
|
495
|
+
noReport:
|
|
496
|
+
error?.code === 'REPORT_WATCH_CAP'
|
|
497
|
+
? WATCH_CAP_REASON
|
|
498
|
+
: `its report could not be recorded (${error?.message || error}); check that session for the result`,
|
|
499
|
+
};
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
/** Deliver report `payload` of task session `sessionId` to agent `agentId`,
|
|
504
|
+
* retrying after each of `retryDelaysMs`; the last failure throws. */
|
|
505
|
+
async function deliverWithRetries({ deliver, retryDelaysMs, log }, agentId, sessionId, payload) {
|
|
506
|
+
for (let attempt = 0; ; attempt += 1) {
|
|
507
|
+
try {
|
|
508
|
+
return await deliver(agentId, payload);
|
|
509
|
+
} catch (error) {
|
|
510
|
+
if (attempt >= retryDelaysMs.length) throw error;
|
|
511
|
+
log(`report of ${sessionId} failed (try ${attempt + 1}), retrying: ${error?.message || error}`);
|
|
512
|
+
await new Promise((resolveWait) => setTimeout(resolveWait, retryDelaysMs[attempt]));
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
/** Start task session `sessionId` in its own worktree; a repo that cannot
|
|
518
|
+
* have one keeps the Project folder. */
|
|
519
|
+
async function requestTaskWorktree(sessions, sessionId, title, requestId) {
|
|
520
|
+
try {
|
|
521
|
+
await sessions.configureSession(
|
|
522
|
+
{
|
|
523
|
+
sessionId,
|
|
524
|
+
action: 'requestWorktree',
|
|
525
|
+
args: [{ provision: true, carryChanges: 'leave', prompt: title, txId: requestId }],
|
|
526
|
+
},
|
|
527
|
+
{ revision: 1 }
|
|
528
|
+
);
|
|
529
|
+
} catch (error) {
|
|
530
|
+
if (error?.code !== 'WORKTREE_UNSUPPORTED_REPO') throw error;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
/** The origin a `send` of agent `agentId` (from its home or room member
|
|
535
|
+
* session `callerSessionId`) reports to: the stored one of a task session
|
|
536
|
+
* it created, else one for this send only — never stored on the target,
|
|
537
|
+
* whose own origin and owner stay as they are. Either reports to the
|
|
538
|
+
* conversation the send came from. Null for a caller that is no agent
|
|
539
|
+
* conversation. */
|
|
540
|
+
async function sendReportOrigin({ loadSession, agentName }, sessionId, agentId, callerSessionId) {
|
|
541
|
+
if (!agentId) return null;
|
|
542
|
+
const target = await loadSession(sessionId);
|
|
543
|
+
const origin = target?.taskOrigin;
|
|
544
|
+
if (origin?.agentId === agentId) {
|
|
545
|
+
return callerSessionId && callerSessionId !== origin.sessionId ? { ...origin, sessionId: callerSessionId } : origin;
|
|
546
|
+
}
|
|
547
|
+
const title = (clean(target?.title) || sessionId).slice(0, TITLE_LIMIT);
|
|
548
|
+
// Files come back only from sessions the agent created (above) or its own
|
|
549
|
+
// room member threads; any other session reports its text alone.
|
|
550
|
+
const own = roomMemberOfSession(sessionId)?.agentId === agentId;
|
|
551
|
+
return {
|
|
552
|
+
agentId,
|
|
553
|
+
sessionId: callerSessionId,
|
|
554
|
+
title,
|
|
555
|
+
notify: true,
|
|
556
|
+
target: 'session',
|
|
557
|
+
...(own ? {} : { textOnly: true, envelopeName: await agentName(agentId) }),
|
|
558
|
+
};
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
/** Re-attach one watch a stopped daemon left: report the turn's end once
|
|
562
|
+
* from the target's record, or watch it until it ends. */
|
|
563
|
+
async function rearmTaskWatch(
|
|
564
|
+
{ deliver, loadSession, materializeSession, queuedSubmissionIds, artifactFields, followToFinal, log },
|
|
565
|
+
watch
|
|
566
|
+
) {
|
|
567
|
+
const sessionId = watch.taskSessionId;
|
|
568
|
+
const finish = async (status, text = '') =>
|
|
569
|
+
deliver(watch.agentId, {
|
|
570
|
+
runId: `${watch.runId}_end`,
|
|
571
|
+
taskSessionId: sessionId,
|
|
572
|
+
title: watch.title,
|
|
573
|
+
status,
|
|
574
|
+
text,
|
|
575
|
+
...(watch.target === 'session' ? { target: 'session' } : {}),
|
|
576
|
+
...(watch.hop > 0 ? { hop: watch.hop } : {}),
|
|
577
|
+
...roomFrom(watch.homeSessionId),
|
|
578
|
+
watchId: watch.runId,
|
|
579
|
+
...(watch.textOnly || text === GONE_TEXT || text === LOST_TEXT
|
|
580
|
+
? {}
|
|
581
|
+
: await artifactFields(sessionId, watch.runId)),
|
|
582
|
+
});
|
|
583
|
+
if (!(await loadSession(sessionId))) return finish('cancelled', GONE_TEXT);
|
|
584
|
+
let runtime;
|
|
585
|
+
try {
|
|
586
|
+
runtime = await materializeSession(sessionId);
|
|
587
|
+
} catch (error) {
|
|
588
|
+
// It can never take its agents' results: say so once instead of waiting.
|
|
589
|
+
log(`${sessionId} could not be loaded after the restart: ${error?.message || error}`);
|
|
590
|
+
return finish('failed', RESUME_FAILED_TEXT);
|
|
591
|
+
}
|
|
592
|
+
let done = false;
|
|
593
|
+
let checking = false;
|
|
594
|
+
let again = false;
|
|
595
|
+
let unsubscribe = () => {};
|
|
596
|
+
// Set once the watched turn is found ended completed: the report then
|
|
597
|
+
// waits for the task's final answer, as in the process that sent it.
|
|
598
|
+
let follow = null;
|
|
599
|
+
const check = async () => {
|
|
600
|
+
if (done) return;
|
|
601
|
+
if (follow) return follow.check();
|
|
602
|
+
if (checking) {
|
|
603
|
+
again = true;
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
checking = true;
|
|
607
|
+
try {
|
|
608
|
+
do {
|
|
609
|
+
again = false;
|
|
610
|
+
const outcome = submissionOutcome((await loadSession(sessionId))?.messages, watch.runId);
|
|
611
|
+
const state = runtime?.getState?.() || {};
|
|
612
|
+
let end = outcome && outcome !== 'open' ? outcome : null;
|
|
613
|
+
if (!end && !state.busy) {
|
|
614
|
+
const queued = (state.queued || []).some((entry) => entry?.id === watch.runId);
|
|
615
|
+
if (outcome === 'open' && !queued) end = { status: 'cancelled', text: '' };
|
|
616
|
+
else if (!outcome && !queued && !(await queuedSubmissionIds(sessionId)).includes(watch.runId)) {
|
|
617
|
+
end = { status: 'cancelled', text: LOST_TEXT };
|
|
618
|
+
}
|
|
619
|
+
}
|
|
620
|
+
if (end?.status === 'completed') {
|
|
621
|
+
follow = followToFinal({
|
|
622
|
+
sessionId,
|
|
623
|
+
runId: watch.runId,
|
|
624
|
+
runtime,
|
|
625
|
+
wakeOwed: true,
|
|
626
|
+
progress: (text) =>
|
|
627
|
+
deliver(watch.agentId, {
|
|
628
|
+
runId: `${watch.runId}_progress`,
|
|
629
|
+
taskSessionId: sessionId,
|
|
630
|
+
title: watch.title,
|
|
631
|
+
status: 'working',
|
|
632
|
+
text,
|
|
633
|
+
...(watch.target === 'session' ? { target: 'session' } : {}),
|
|
634
|
+
...(watch.hop > 0 ? { hop: watch.hop } : {}),
|
|
635
|
+
...roomFrom(watch.homeSessionId),
|
|
636
|
+
}),
|
|
637
|
+
finish: async (status, text) => {
|
|
638
|
+
done = true;
|
|
639
|
+
unsubscribe();
|
|
640
|
+
await finish(status, text);
|
|
641
|
+
},
|
|
642
|
+
});
|
|
643
|
+
} else if (end) {
|
|
644
|
+
done = true;
|
|
645
|
+
unsubscribe();
|
|
646
|
+
await finish(end.status, end.text);
|
|
647
|
+
}
|
|
648
|
+
} while (again && !done && !follow);
|
|
649
|
+
} finally {
|
|
650
|
+
checking = false;
|
|
651
|
+
}
|
|
652
|
+
if (follow) await follow.check();
|
|
653
|
+
};
|
|
654
|
+
const failed = (error) => log(`re-attaching ${sessionId} failed: ${error?.message || error}`);
|
|
655
|
+
unsubscribe = runtime?.subscribe?.(() => void check().catch(failed)) || unsubscribe;
|
|
656
|
+
await check();
|
|
657
|
+
}
|
|
658
|
+
|
|
114
659
|
export function createAgentTaskSessions({
|
|
115
660
|
getSessionService,
|
|
116
661
|
deliver = async (agentId, report) => (await import('./agent-home-delivery.mjs')).deliverAgentTask(agentId, report),
|
|
@@ -130,48 +675,11 @@ export function createAgentTaskSessions({
|
|
|
130
675
|
listProjects = async () => (await import('../runtime/shared/projects.mjs')).listProjects(),
|
|
131
676
|
newTaskDefault = async () =>
|
|
132
677
|
(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
|
-
},
|
|
678
|
+
modelRoute = configModelRoute,
|
|
147
679
|
// 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
|
-
},
|
|
680
|
+
leadRoute = configLeadRoute,
|
|
681
|
+
// The model the user last used in a coding session.
|
|
682
|
+
lastUsedRoute = lastUsedSessionRoute,
|
|
175
683
|
// The files turn `runId` of `sessionId` produced (handoff-attachments.mjs),
|
|
176
684
|
// read from its live runtime when one is given, else its stored record.
|
|
177
685
|
turnArtifacts = async (sessionId, runId, runtime = null) => {
|
|
@@ -180,6 +688,27 @@ export function createAgentTaskSessions({
|
|
|
180
688
|
const messages = turnSlice(Array.isArray(live?.messages) ? live.messages : stored?.messages, { runId });
|
|
181
689
|
return collectTurnArtifacts(messages, { cwd: stored?.cwd, roots: sessionRoots(stored) });
|
|
182
690
|
},
|
|
691
|
+
// The name a bot goes by in the envelope of its messages to other sessions.
|
|
692
|
+
agentName = async (agentId) =>
|
|
693
|
+
(await import('../runtime/agent/orchestrator/context/role-instructions.mjs')).agentDisplayName(agentId),
|
|
694
|
+
// Waits before each in-process retry of a report that could not be queued.
|
|
695
|
+
retryDelaysMs = REPORT_RETRY_DELAYS_MS,
|
|
696
|
+
// Whether a task session still waits on agent work it handed off; its
|
|
697
|
+
// turn's end then is not the final answer yet.
|
|
698
|
+
pendingWork = taskSessionPendingWork,
|
|
699
|
+
settleMs = FINAL_SETTLE_MS,
|
|
700
|
+
pollMs = FINAL_POLL_MS,
|
|
701
|
+
// After a restart: the agent results queued for a task session, and the
|
|
702
|
+
// notification-bus delivery (the session runtime host's
|
|
703
|
+
// notifySessionCompletion) that makes it take them in a turn — the restart
|
|
704
|
+
// left nothing to wake it. `settleAgentResults` first turns the agents the
|
|
705
|
+
// restart killed into queued failure results (agent-tool restart-notify).
|
|
706
|
+
owedResults = owedAgentResults,
|
|
707
|
+
deliverCompletion = null,
|
|
708
|
+
settleAgentResults = async () => {},
|
|
709
|
+
// Pending agent work this long after the latest answer reports that answer
|
|
710
|
+
// as still in progress (once); the final report still follows.
|
|
711
|
+
pendingCapMs = PENDING_CAP_MS,
|
|
183
712
|
log = (line) => process.stderr.write(`mixdog task sessions: ${line}\n`),
|
|
184
713
|
}) {
|
|
185
714
|
/** The report fields of turn `runId`'s files; none when it produced none. */
|
|
@@ -200,55 +729,48 @@ export function createAgentTaskSessions({
|
|
|
200
729
|
return value;
|
|
201
730
|
};
|
|
202
731
|
|
|
732
|
+
const followToFinal = (follow) =>
|
|
733
|
+
followTaskToFinal(
|
|
734
|
+
{ loadSession, owedResults, deliverCompletion, pendingWork, settleMs, pollMs, pendingCapMs, log },
|
|
735
|
+
follow
|
|
736
|
+
);
|
|
737
|
+
|
|
203
738
|
/** Submit `prompt` as the origin agent's turn; its end (and a wait for the
|
|
204
739
|
* user in it) reports to the agent's home. Resolves on intake. */
|
|
205
740
|
async function agentTurn(sessionId, prompt, origin, id = `atask_${randomUUID()}`, beforeReport = null) {
|
|
206
741
|
const sessions = service();
|
|
207
742
|
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
|
-
}
|
|
743
|
+
const { notify, noReport } = await watchTurn({ watches, owner: processToken, log }, sessionId, origin, id);
|
|
230
744
|
const dropWatch = () =>
|
|
231
745
|
watches.remove(id).catch((error) => log(`dropping the watch of ${sessionId} failed: ${error?.message || error}`));
|
|
232
746
|
let runtime = null;
|
|
747
|
+
// A report that cannot be queued is retried in this process; when every try
|
|
748
|
+
// fails the durable watch stays and restart recovery delivers it (the queue
|
|
749
|
+
// dedupes by runId, so a retry never posts twice).
|
|
233
750
|
const report = (status, text = '') =>
|
|
234
751
|
Promise.resolve(beforeReport)
|
|
235
752
|
.then(async () =>
|
|
236
|
-
deliver
|
|
237
|
-
|
|
753
|
+
deliverWithRetries({ deliver, retryDelaysMs, log }, origin.agentId, sessionId, {
|
|
754
|
+
// 'working': a still-in-progress answer (followToFinal), not the end.
|
|
755
|
+
runId: `${id}_${{ blocked: 'blocked', working: 'progress' }[status] || 'end'}`,
|
|
238
756
|
taskSessionId: sessionId,
|
|
239
757
|
title: origin.title,
|
|
240
758
|
status,
|
|
241
759
|
text,
|
|
242
760
|
...target,
|
|
761
|
+
...(origin.hop > 0 ? { hop: origin.hop } : {}),
|
|
762
|
+
...roomFrom(origin.sessionId),
|
|
243
763
|
// A turn's end brings back the files it produced, when the agent
|
|
244
764
|
// may receive them (reportOrigin `textOnly`).
|
|
245
|
-
...(status === 'blocked'
|
|
765
|
+
...(status === 'blocked' || status === 'working'
|
|
246
766
|
? {}
|
|
247
767
|
: { watchId: id, ...(origin.textOnly ? {} : await artifactFields(sessionId, id, runtime)) }),
|
|
248
768
|
})
|
|
249
769
|
)
|
|
250
770
|
.catch((error) => log(`report of ${sessionId} failed: ${error?.message || error}`));
|
|
251
771
|
let unwatch = () => {};
|
|
772
|
+
// Set once the turn ended completed: the report waits for the final answer.
|
|
773
|
+
let follow = null;
|
|
252
774
|
let result;
|
|
253
775
|
try {
|
|
254
776
|
if (notify) {
|
|
@@ -256,23 +778,55 @@ export function createAgentTaskSessions({
|
|
|
256
778
|
let blocked = false;
|
|
257
779
|
unwatch =
|
|
258
780
|
runtime?.subscribe?.(() => {
|
|
781
|
+
follow?.check();
|
|
259
782
|
if (blocked || !runtime.getState?.()?.toolApproval) return;
|
|
260
783
|
blocked = true;
|
|
261
784
|
void report('blocked');
|
|
262
785
|
}) || unwatch;
|
|
263
786
|
}
|
|
787
|
+
// A bot's send to a session that is not its own task session reaches the
|
|
788
|
+
// model inside the sender envelope (`envelopeName`, set by reportOrigin).
|
|
789
|
+
const sent = origin.envelopeName
|
|
790
|
+
? wrapAgentMessage(prompt, { name: origin.envelopeName, agentId: origin.agentId })
|
|
791
|
+
: prompt;
|
|
264
792
|
result = await sessions.submitSession({
|
|
265
793
|
sessionId,
|
|
266
|
-
prompt,
|
|
794
|
+
prompt: sent,
|
|
267
795
|
options: {
|
|
268
796
|
id,
|
|
269
797
|
mode: 'prompt',
|
|
270
|
-
|
|
798
|
+
// `fromAgent`: the sending bot, recorded when it writes into another
|
|
799
|
+
// bot's home chat (otherwise the row would read as the user's);
|
|
800
|
+
// `hop`: its place in a bot-to-bot chain (bot-hops.mjs).
|
|
801
|
+
transcriptMeta: {
|
|
802
|
+
sender: 'lead',
|
|
803
|
+
...(origin.envelopeName ? { fromAgent: origin.agentId } : {}),
|
|
804
|
+
...(origin.hop > 0 ? { hop: origin.hop } : {}),
|
|
805
|
+
},
|
|
271
806
|
...(notify
|
|
272
807
|
? {
|
|
273
808
|
onSettled: (detail) => {
|
|
274
|
-
|
|
275
|
-
|
|
809
|
+
const status = turnStatus(detail);
|
|
810
|
+
const text = detail?.result?.content || detail?.error || '';
|
|
811
|
+
// A failed or cancelled turn reports at once; a completed one
|
|
812
|
+
// reports the task's final answer (followToFinal).
|
|
813
|
+
if (status !== 'completed') {
|
|
814
|
+
unwatch();
|
|
815
|
+
void report(status, text);
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
follow = followToFinal({
|
|
819
|
+
sessionId,
|
|
820
|
+
runId: id,
|
|
821
|
+
runtime,
|
|
822
|
+
immediate: { status, text },
|
|
823
|
+
finish: async (endStatus, endText) => {
|
|
824
|
+
unwatch();
|
|
825
|
+
await report(endStatus, endText);
|
|
826
|
+
},
|
|
827
|
+
progress: (progressText) => report('working', progressText),
|
|
828
|
+
});
|
|
829
|
+
follow.check();
|
|
276
830
|
},
|
|
277
831
|
}
|
|
278
832
|
: {}),
|
|
@@ -288,71 +842,38 @@ export function createAgentTaskSessions({
|
|
|
288
842
|
unwatch();
|
|
289
843
|
if (notify) await dropWatch();
|
|
290
844
|
}
|
|
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
|
-
}
|
|
845
|
+
return {
|
|
846
|
+
accepted,
|
|
847
|
+
reportsBack: accepted && notify,
|
|
848
|
+
...(accepted && !notify ? { reportsBackReason: noReport } : {}),
|
|
346
849
|
};
|
|
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
850
|
}
|
|
351
851
|
|
|
852
|
+
const rearm = (watch) =>
|
|
853
|
+
rearmTaskWatch(
|
|
854
|
+
{
|
|
855
|
+
deliver,
|
|
856
|
+
loadSession,
|
|
857
|
+
materializeSession: (sessionId) => service().materializeSession(sessionId),
|
|
858
|
+
queuedSubmissionIds,
|
|
859
|
+
artifactFields,
|
|
860
|
+
followToFinal,
|
|
861
|
+
log,
|
|
862
|
+
},
|
|
863
|
+
watch
|
|
864
|
+
);
|
|
865
|
+
|
|
352
866
|
/** Daemon start: re-attach every reporting turn an earlier process watched.
|
|
353
867
|
* Resolves once each is reported or watched. */
|
|
354
868
|
async function recover() {
|
|
355
869
|
const pending = (await watches.list()).filter((watch) => watch?.owner !== processToken);
|
|
870
|
+
// The agents the restart killed become queued failure results first, so
|
|
871
|
+
// each re-attached task session has them to take (followToFinal wakeOwed).
|
|
872
|
+
if (pending.length) {
|
|
873
|
+
await Promise.resolve()
|
|
874
|
+
.then(settleAgentResults)
|
|
875
|
+
.catch((error) => log(`settling the agents of the previous process failed: ${error?.message || error}`));
|
|
876
|
+
}
|
|
356
877
|
await Promise.all(
|
|
357
878
|
pending.map((watch) =>
|
|
358
879
|
rearm(watch).catch((error) => log(`re-attaching ${watch?.taskSessionId} failed: ${error?.message || error}`))
|
|
@@ -362,7 +883,10 @@ export function createAgentTaskSessions({
|
|
|
362
883
|
}
|
|
363
884
|
|
|
364
885
|
/** Create the task session for `brief` and start it; returns at once. */
|
|
365
|
-
async function create(
|
|
886
|
+
async function create(
|
|
887
|
+
args = {},
|
|
888
|
+
{ callerSessionId, callerAgentId, callerCwd, callerHop = 0, attachmentParts = [] } = {}
|
|
889
|
+
) {
|
|
366
890
|
if (!callerAgentId) throw new Error('session: create is only for an agent conversation');
|
|
367
891
|
const brief = typeof args.brief === 'string' ? args.brief.trim() : '';
|
|
368
892
|
if (!brief) throw new Error('session: brief is required for action=create');
|
|
@@ -394,18 +918,7 @@ export function createAgentTaskSessions({
|
|
|
394
918
|
if (!sessionId) throw new Error('session: task session creation returned no session id');
|
|
395
919
|
// The user's New task default: a Project task starts in its own worktree.
|
|
396
920
|
if (where.project && (await newTaskDefault()) === 'worktree') {
|
|
397
|
-
|
|
398
|
-
await sessions.configureSession(
|
|
399
|
-
{
|
|
400
|
-
sessionId,
|
|
401
|
-
action: 'requestWorktree',
|
|
402
|
-
args: [{ provision: true, carryChanges: 'leave', prompt: title, txId: requestId }],
|
|
403
|
-
},
|
|
404
|
-
{ revision: 1 }
|
|
405
|
-
);
|
|
406
|
-
} catch (error) {
|
|
407
|
-
if (error?.code !== 'WORKTREE_UNSUPPORTED_REPO') throw error;
|
|
408
|
-
}
|
|
921
|
+
await requestTaskWorktree(sessions, sessionId, title, requestId);
|
|
409
922
|
}
|
|
410
923
|
// "Started" lands in the home before any report of the brief's turn.
|
|
411
924
|
let started = () => {};
|
|
@@ -414,7 +927,13 @@ export function createAgentTaskSessions({
|
|
|
414
927
|
});
|
|
415
928
|
// The brief carries the files the agent forwarded (workspace-tools.mjs).
|
|
416
929
|
const prompt = handoffContent(brief, attachmentParts);
|
|
417
|
-
const { accepted, reportsBack } = await agentTurn(
|
|
930
|
+
const { accepted, reportsBack, reportsBackReason } = await agentTurn(
|
|
931
|
+
sessionId,
|
|
932
|
+
prompt,
|
|
933
|
+
callerHop > 0 ? { ...taskOrigin, hop: callerHop } : taskOrigin,
|
|
934
|
+
requestId,
|
|
935
|
+
ready
|
|
936
|
+
);
|
|
418
937
|
if (!accepted) throw new Error(`session: task session ${sessionId} did not accept the brief`);
|
|
419
938
|
if (reportsBack) {
|
|
420
939
|
await deliver(callerAgentId, {
|
|
@@ -422,39 +941,22 @@ export function createAgentTaskSessions({
|
|
|
422
941
|
taskSessionId: sessionId,
|
|
423
942
|
title,
|
|
424
943
|
status: 'started',
|
|
944
|
+
...roomFrom(callerSessionId),
|
|
425
945
|
}).catch((error) => log(`start line of ${sessionId} failed: ${error?.message || error}`));
|
|
426
946
|
}
|
|
427
947
|
started();
|
|
948
|
+
// Started, not done: the final answer is reported later.
|
|
428
949
|
return {
|
|
429
950
|
session_id: sessionId,
|
|
951
|
+
status: 'started',
|
|
430
952
|
title,
|
|
431
953
|
project: where.project?.path ?? null,
|
|
432
|
-
...(taskOrigin.notify && !reportsBack ? { reportsBack: false } : {}),
|
|
954
|
+
...(taskOrigin.notify && !reportsBack ? { reportsBack: false, reportsBackReason } : {}),
|
|
433
955
|
};
|
|
434
956
|
}
|
|
435
957
|
|
|
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
|
-
}
|
|
958
|
+
const reportOrigin = (sessionId, agentId, callerSessionId) =>
|
|
959
|
+
sendReportOrigin({ loadSession, agentName }, sessionId, agentId, callerSessionId);
|
|
458
960
|
|
|
459
961
|
return { create, reportOrigin, recover, send: (sessionId, prompt, origin) => agentTurn(sessionId, prompt, origin) };
|
|
460
962
|
}
|