mixdog 1.3.5 → 1.4.0
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/NOTICE.md +2 -2
- package/README.md +2 -2
- package/package.json +2 -11
- package/scripts/daemon-stub.mjs +1 -1
- package/scripts/gate-local.mjs +4 -2
- package/scripts/native-release-status.mjs +2 -2
- package/scripts/recall-history-cases.json +1 -1
- package/scripts/suite-health.mjs +10 -1
- package/scripts/test.mjs +7 -11
- package/src/agents/reviewer/AGENT.md +7 -11
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +17 -1
- package/src/defaults/skills/browser-use/SKILL.md +59 -11
- package/src/defaults/skills/code-tidy/SKILL.md +142 -270
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
- package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
- package/src/defaults/skills/computer-use/SKILL.md +33 -9
- package/src/defaults/skills/setup/SKILL.md +22 -0
- package/src/defaults/skills/setup/references/actions.md +39 -5
- package/src/defaults/skills/setup/references/surfaces.md +11 -2
- package/src/defaults/skills/visualize/SKILL.md +2 -1
- package/src/lib/rules-builder.cjs +10 -8
- package/src/rules/agent/AGENT.md +6 -6
- package/src/rules/lead/LEAD.md +18 -20
- package/src/rules/shared/05-tool-calls.md +5 -6
- package/src/rules/shared/10-tool-workflow.md +2 -0
- package/src/rules/shared/60-verification.md +7 -5
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
- package/src/runtime/agent/orchestrator/config.mjs +17 -1
- package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +176 -72
- package/src/runtime/agent/orchestrator/mcp/client.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +181 -133
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +42 -6
- package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +145 -66
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +123 -102
- package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -13
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
- package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/registry.mjs +26 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
- package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +133 -22
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +0 -2
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +3 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +12 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
- package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +36 -9
- package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
- package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
- package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +8 -6
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
- package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
- package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +18 -1
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
- package/src/runtime/browser-bridge/action-schema.mjs +13 -2
- package/src/runtime/browser-bridge/client.mjs +73 -8
- package/src/runtime/browser-bridge/input-fields.mjs +8 -9
- package/src/runtime/browser-bridge/mail-links.mjs +107 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
- package/src/runtime/channels/lib/config.mjs +6 -61
- package/src/runtime/channels/lib/crash-log.mjs +1 -1
- package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
- package/src/runtime/channels/lib/event-queue.mjs +3 -18
- package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
- package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
- package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
- package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
- package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
- package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
- package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
- package/src/runtime/channels/lib/scheduler.mjs +33 -147
- package/src/runtime/channels/lib/schedules-db.mjs +34 -11
- package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
- package/src/runtime/channels/lib/tool-dispatch.mjs +1 -2
- package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
- package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
- package/src/runtime/channels/lib/worker-main.mjs +10 -34
- package/src/runtime/computer-bridge/action-schema.mjs +42 -1
- package/src/runtime/computer-bridge/actions.mjs +9 -1
- package/src/runtime/computer-bridge/client.mjs +180 -8
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
- package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
- package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
- package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
- package/src/runtime/connectors/gmail-app-password.mjs +172 -39
- package/src/runtime/connectors/google-mcp.mjs +10 -12
- package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
- package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
- package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
- package/src/runtime/session-search/session-search-db.mjs +73 -9
- package/src/runtime/session-search/session-search-ingest.mjs +110 -20
- package/src/runtime/shared/abort-race.mjs +89 -0
- package/src/runtime/shared/agent-avatar.d.mts +0 -1
- package/src/runtime/shared/agent-kind.mjs +4 -3
- package/src/runtime/shared/bounded-download.mjs +8 -3
- package/src/runtime/shared/config.mjs +2 -13
- package/src/runtime/shared/connector-accounts.mjs +20 -10
- package/src/runtime/shared/connector-catalog.d.mts +29 -0
- package/src/runtime/shared/connector-catalog.mjs +150 -0
- package/src/runtime/shared/connector-triggers.mjs +22 -19
- package/src/runtime/shared/defaults-separation.mjs +2 -14
- package/src/runtime/shared/delegated-approval-host.mjs +103 -11
- package/src/runtime/shared/err-text.mjs +24 -15
- package/src/runtime/shared/file-permissions.d.mts +0 -8
- package/src/runtime/shared/file-permissions.mjs +0 -11
- package/src/runtime/shared/git-direct.test-support.mjs +19 -0
- package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
- package/src/runtime/shared/heartbeat-interval.mjs +19 -0
- package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
- package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
- package/src/runtime/shared/mixy-presence.mjs +22 -0
- package/src/runtime/shared/orchestration.d.mts +2 -0
- package/src/runtime/shared/pg-schema-db.mjs +27 -16
- package/src/runtime/shared/profile-config.cjs +1 -1
- package/src/runtime/shared/prompt-origin.d.mts +4 -0
- package/src/runtime/shared/prompt-origin.mjs +21 -0
- package/src/runtime/shared/provider-accounts.mjs +7 -1
- package/src/runtime/shared/provider-display-name.mjs +29 -0
- package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
- package/src/runtime/shared/routine-trigger.d.mts +2 -0
- package/src/runtime/shared/routine-trigger.mjs +10 -0
- package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
- package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
- package/src/runtime/shared/session-activity-signal.mjs +34 -1
- package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
- package/src/runtime/shared/session-sidebar-host.mjs +7 -3
- package/src/runtime/shared/session-turn-prelude.mjs +18 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -14
- package/src/runtime/shared/worktrees/test-support.mjs +9 -4
- package/src/runtime/terminal-bridge/client.mjs +10 -1
- package/src/runtime/visualize-bridge/client.mjs +27 -8
- package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
- package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
- package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
- package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
- package/src/session-runtime/agent-home-delivery.mjs +448 -185
- package/src/session-runtime/agent-task-sessions.mjs +329 -180
- package/src/session-runtime/boot/apis.mjs +9 -0
- package/src/session-runtime/boot/infrastructure.mjs +4 -0
- package/src/session-runtime/boot/providers.mjs +3 -0
- package/src/session-runtime/boot/tools.mjs +6 -1
- package/src/session-runtime/config-lifecycle.mjs +6 -0
- package/src/session-runtime/connector-merge-migration.mjs +305 -0
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
- package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
- package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
- package/src/session-runtime/lifecycle/teardown.mjs +23 -5
- package/src/session-runtime/mcp-glue/server-catalog.mjs +55 -3
- package/src/session-runtime/mcp-oauth-login.mjs +5 -3
- package/src/session-runtime/mixy-heartbeat.mjs +153 -33
- package/src/session-runtime/notification-bus.mjs +31 -117
- package/src/session-runtime/notification-completion-queue.mjs +54 -0
- package/src/session-runtime/notification-listeners.mjs +76 -0
- package/src/session-runtime/owned-automation-run.mjs +2 -1
- package/src/session-runtime/prewarm/channel-start.mjs +3 -7
- package/src/session-runtime/resource-mcp-api.mjs +143 -224
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
- package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
- package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
- package/src/session-runtime/resource-mcp-servers.mjs +78 -0
- package/src/session-runtime/resource-mcp-store.mjs +49 -0
- package/src/session-runtime/routine-owner.mjs +40 -9
- package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
- package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
- package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
- package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
- package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
- package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
- package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
- package/src/session-runtime/services/channel-admin.mjs +7 -13
- package/src/session-runtime/services/channel-session-router.mjs +4 -2
- package/src/session-runtime/services/seeds.mjs +0 -28
- package/src/session-runtime/session-compaction-sync.mjs +27 -0
- package/src/session-runtime/session-create/session-options.mjs +1 -0
- package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
- package/src/session-runtime/settings-compaction-api.mjs +2 -40
- package/src/session-runtime/settings-system-api.mjs +13 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
- package/src/session-runtime/setup-tool/executor.mjs +145 -22
- package/src/session-runtime/setup-tool/extended-actions.mjs +8 -3
- package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +4 -3
- package/src/session-runtime/setup-tool/tool-defs.mjs +64 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
- package/src/session-runtime/tool-policy/policy-stage.mjs +16 -2
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
- package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
- package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
- package/src/standalone/channel-transport/client-registry.mjs +24 -163
- package/src/standalone/channel-transport.mjs +1 -1
- package/src/standalone/daemon-channels-loader.mjs +10 -8
- package/src/standalone/daemon-session-control/list-page.mjs +72 -0
- package/src/standalone/daemon-session-control/request.mjs +51 -0
- package/src/standalone/daemon-session-control/room-host.mjs +57 -0
- package/src/standalone/daemon-session-control/route-control.mjs +48 -0
- package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
- package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
- package/src/standalone/daemon-session-control.mjs +124 -188
- package/src/standalone/daemon.mjs +26 -3
- package/src/standalone/session-client.mjs +7 -3
- package/src/standalone/session-protocol.mjs +5 -0
- package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
- package/src/standalone/session-service/quota-resume.mjs +3 -0
- package/src/standalone/session-service/retention/busy.mjs +65 -1
- package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
- package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
- package/src/standalone/session-tool-output.mjs +15 -7
- package/src/tui/app/model-options.mjs +3 -28
- package/src/tui/dist/index.mjs +58 -64
- package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/notice-surface.mjs +7 -1
- package/src/tui/session/queue-helpers.mjs +3 -42
- package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
- package/src/tui/session/session-api/routes-write.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +17 -1
- package/src/tui/session/session-flow/auto-clear.mjs +4 -33
- package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
- package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
- package/src/tui/session/session-flow/drain-retry.mjs +35 -0
- package/src/tui/session/session-flow/drain.mjs +118 -149
- package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
- package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
- package/src/tui/session/tool-approval-delegation.mjs +64 -0
- package/src/tui/session/tool-approval-request.mjs +39 -0
- package/src/tui/session/tool-approval-settle.mjs +62 -0
- package/src/tui/session/tool-approval.mjs +69 -115
- package/src/tui/session/tui-steering-persist.mjs +8 -1
- package/src/tui/session/turn-stream.mjs +19 -2
- package/src/tui/session/turn.mjs +1 -0
- package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
- package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
- package/scripts/code-graph-description-contract.mjs +0 -188
- package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/session/session-flow/drain-closed-queue.mjs - settling the queue of a
|
|
3
|
+
* session whose canonical close completed (drain.mjs admission `terminal`).
|
|
4
|
+
*/
|
|
5
|
+
import { dropTuiSteeringPersist } from '../tui-steering-persist.mjs';
|
|
6
|
+
|
|
7
|
+
export function createClosedQueueSettler({
|
|
8
|
+
pending,
|
|
9
|
+
pendingNotificationKeys,
|
|
10
|
+
clearBlockedDrainRetry,
|
|
11
|
+
queue,
|
|
12
|
+
steering,
|
|
13
|
+
submissions,
|
|
14
|
+
}) {
|
|
15
|
+
// Canonical close settled: nothing queued can ever run. Every accepted
|
|
16
|
+
// submission is settled explicitly (rejected), never silently dropped, and
|
|
17
|
+
// its durable steering mirror is released so a restart cannot resurrect it
|
|
18
|
+
// into the closed conversation.
|
|
19
|
+
return function settleClosedQueue() {
|
|
20
|
+
const entries = pending.splice(0);
|
|
21
|
+
pendingNotificationKeys.clear();
|
|
22
|
+
clearBlockedDrainRetry();
|
|
23
|
+
if (entries.length === 0) return;
|
|
24
|
+
queue.removeQueuedEntries(entries);
|
|
25
|
+
const mirrored = entries.filter((entry) => steering.shouldMirrorSteeringEntry(entry) && entry.steeringPersistId);
|
|
26
|
+
if (mirrored.length > 0) {
|
|
27
|
+
void Promise.resolve(dropTuiSteeringPersist(steering.leadSessionId(), mirrored)).catch(() => {});
|
|
28
|
+
}
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
if (entry.id) submissions.forget(entry.id);
|
|
31
|
+
try {
|
|
32
|
+
entry.onSettled?.({ status: 'rejected', reason: 'session-closed', result: null, session: null });
|
|
33
|
+
} catch {
|
|
34
|
+
// A throwing callback must not leave the remaining entries unsettled.
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/session/session-flow/drain-enqueue.mjs - idempotent intake of the
|
|
3
|
+
* unified prompt queue (drain.mjs): admission fence, duplicate submissions,
|
|
4
|
+
* notification keys, task-wait wakes and the durable steering mirror.
|
|
5
|
+
*/
|
|
6
|
+
import { isQueuedEntryEditable, isQueuedEntryVisible } from '../queue-helpers.mjs';
|
|
7
|
+
import { appendTuiSteeringPersist } from '../tui-steering-persist.mjs';
|
|
8
|
+
|
|
9
|
+
export function createEnqueue({
|
|
10
|
+
admission,
|
|
11
|
+
tuiDebug,
|
|
12
|
+
flags,
|
|
13
|
+
pending,
|
|
14
|
+
pendingNotificationKeys,
|
|
15
|
+
getState,
|
|
16
|
+
set,
|
|
17
|
+
flushEmitImmediate,
|
|
18
|
+
getRuntime,
|
|
19
|
+
queue,
|
|
20
|
+
steering,
|
|
21
|
+
submissions,
|
|
22
|
+
drain,
|
|
23
|
+
}) {
|
|
24
|
+
return function enqueue(text, options = {}) {
|
|
25
|
+
// Admission fence. While closing, only user prompts are accepted (retained
|
|
26
|
+
// so a failed close loses nothing); notification-driven/model-visible
|
|
27
|
+
// enqueues are refused. A closed session accepts nothing.
|
|
28
|
+
const state = admission();
|
|
29
|
+
const mode = options.mode || 'prompt';
|
|
30
|
+
if (state !== 'open' && (state === 'terminal' || mode !== 'prompt')) {
|
|
31
|
+
tuiDebug(`enqueue refused: session admission=${state} mode=${mode}`);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
const submissionId = String(options.id || '').trim();
|
|
35
|
+
// Idempotent intake: a transport that re-sent this exact submission (its
|
|
36
|
+
// first attempt landed but the response was lost) must not double-post.
|
|
37
|
+
if (submissionId && submissions.has(submissionId)) {
|
|
38
|
+
tuiDebug(`prompt-duplicate id=${submissionId} ignored`);
|
|
39
|
+
// This is an idempotent success, not a rejected submit. Returning false
|
|
40
|
+
// makes Desktop restore and retry a message that is already queued.
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
const entry = queue.makeQueueEntry(text, options);
|
|
44
|
+
if (entry.mode === 'task-notification' && entry.key) {
|
|
45
|
+
if (pendingNotificationKeys.has(entry.key)) return false;
|
|
46
|
+
pendingNotificationKeys.add(entry.key);
|
|
47
|
+
}
|
|
48
|
+
submissions.remember(submissionId);
|
|
49
|
+
pending.push(entry);
|
|
50
|
+
// Typed input or a deliverable notification ends a passive task wait or
|
|
51
|
+
// shell hold so the next boundary hands it to the model now.
|
|
52
|
+
const wake = steering.taskWaitWakeForEntry(entry);
|
|
53
|
+
if (wake) {
|
|
54
|
+
try {
|
|
55
|
+
getRuntime()?.interruptTaskWait?.(wake, wake === 'notification' ? entry.execution?.id || null : null);
|
|
56
|
+
} catch {}
|
|
57
|
+
}
|
|
58
|
+
const needsDurableSteering =
|
|
59
|
+
(getState().busy || getState().commandBusy || flags.autoClearRunning || flags.quotaResumePending) &&
|
|
60
|
+
steering.shouldMirrorSteeringEntry(entry);
|
|
61
|
+
const persistence = needsDurableSteering ? appendTuiSteeringPersist(steering.leadSessionId(), entry) : null;
|
|
62
|
+
if (isQueuedEntryVisible(entry)) {
|
|
63
|
+
set({ queued: [...getState().queued, entry] });
|
|
64
|
+
if (isQueuedEntryEditable(entry)) flushEmitImmediate?.();
|
|
65
|
+
}
|
|
66
|
+
const queueAgeMs = Math.max(0, Date.now() - Number(entry.submittedAt || Date.now()));
|
|
67
|
+
tuiDebug(`prompt-queued id=${String(entry.id)} busy=${getState().busy ? 1 : 0} ageMs=${queueAgeMs}`);
|
|
68
|
+
if (getState().busy) tuiDebug(`busy-queue enqueue mode=${entry.mode} pending=${pending.length}`);
|
|
69
|
+
void drain();
|
|
70
|
+
if (options.awaitPersistence === true && persistence) {
|
|
71
|
+
return Promise.resolve(persistence).then((persisted) => {
|
|
72
|
+
if (persisted !== false) return true;
|
|
73
|
+
const index = pending.indexOf(entry);
|
|
74
|
+
// A turn already took the prompt while its durable mirror was being
|
|
75
|
+
// written: it was delivered, so rejecting it would hand the sender a
|
|
76
|
+
// copy of a message the model already has.
|
|
77
|
+
if (index < 0) return true;
|
|
78
|
+
pending.splice(index, 1);
|
|
79
|
+
queue.removeQueuedEntries([entry]);
|
|
80
|
+
if (submissionId) submissions.forget(submissionId);
|
|
81
|
+
return false;
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/session/session-flow/drain-retry.mjs - the re-check timer of a drain
|
|
3
|
+
* that found a gate closed (drain.mjs). One timer at most, kept on
|
|
4
|
+
* `flags.blockedDrainRetryTimer`.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Re-check cadence for a drain that found a gate closed. A gate is state, not
|
|
8
|
+
// an event: the queue re-reads it instead of trusting that whoever closes it
|
|
9
|
+
// will remember to kick the queue on the way out. Both timers exist only while
|
|
10
|
+
// the queue actually holds work, so an idle session never polls.
|
|
11
|
+
const BLOCKED_DRAIN_RETRY_MS = 50;
|
|
12
|
+
// Session commands (setModel/newSession/resume/auto-clear) release in seconds;
|
|
13
|
+
// an active turn can run for many minutes, so its gate is re-read coarsely.
|
|
14
|
+
export const ACTIVE_TURN_RECHECK_MS = 2_000;
|
|
15
|
+
|
|
16
|
+
export function createBlockedDrainRetry({ flags, pending, drain }) {
|
|
17
|
+
function scheduleBlockedDrainRetry(delayMs = BLOCKED_DRAIN_RETRY_MS) {
|
|
18
|
+
if (pending.length === 0 || flags.disposed) return;
|
|
19
|
+
if (flags.blockedDrainRetryTimer) return;
|
|
20
|
+
const timer = setTimeout(() => {
|
|
21
|
+
flags.blockedDrainRetryTimer = null;
|
|
22
|
+
if (pending.length > 0) void drain();
|
|
23
|
+
}, delayMs);
|
|
24
|
+
if (typeof timer.unref === 'function') timer.unref();
|
|
25
|
+
flags.blockedDrainRetryTimer = timer;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function clearBlockedDrainRetry() {
|
|
29
|
+
if (!flags.blockedDrainRetryTimer) return;
|
|
30
|
+
clearTimeout(flags.blockedDrainRetryTimer);
|
|
31
|
+
flags.blockedDrainRetryTimer = null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return { scheduleBlockedDrainRetry, clearBlockedDrainRetry };
|
|
35
|
+
}
|
|
@@ -6,24 +6,18 @@
|
|
|
6
6
|
import {
|
|
7
7
|
isGoalQueuedEntry,
|
|
8
8
|
isQueuedEntryEditable,
|
|
9
|
-
isQueuedEntryVisible,
|
|
10
9
|
isSlashQueuedEntry,
|
|
11
10
|
mergePromptContents,
|
|
12
11
|
mergePastedImages,
|
|
13
12
|
mergePastedTexts,
|
|
14
13
|
} from '../queue-helpers.mjs';
|
|
15
|
-
import { appendTuiSteeringPersist, dropTuiSteeringPersist } from '../tui-steering-persist.mjs';
|
|
16
14
|
import { parseModelVisibleCompletionWrapper } from '../agent-envelope.mjs';
|
|
17
15
|
import { rewoundFailedTurnItems } from './failed-turn-rewind.mjs';
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const BLOCKED_DRAIN_RETRY_MS = 50;
|
|
24
|
-
// Session commands (setModel/newSession/resume/auto-clear) release in seconds;
|
|
25
|
-
// an active turn can run for many minutes, so its gate is re-read coarsely.
|
|
26
|
-
const ACTIVE_TURN_RECHECK_MS = 2_000;
|
|
16
|
+
import { PROMPT_ORIGIN_AGENT_REPORT, PROMPT_ORIGIN_CHANNEL } from '../../../runtime/shared/prompt-origin.mjs';
|
|
17
|
+
import { ACTIVE_TURN_RECHECK_MS, createBlockedDrainRetry } from './drain-retry.mjs';
|
|
18
|
+
import { createClosedQueueSettler } from './drain-closed-queue.mjs';
|
|
19
|
+
import { createEnqueue } from './drain-enqueue.mjs';
|
|
20
|
+
import { sessionTurnPrelude } from '../../../runtime/shared/session-turn-prelude.mjs';
|
|
27
21
|
|
|
28
22
|
function earliestSubmittedAt(batch) {
|
|
29
23
|
const earliest = batch.reduce((min, entry) => {
|
|
@@ -89,6 +83,18 @@ function batchPromptSource(batch) {
|
|
|
89
83
|
return undefined;
|
|
90
84
|
}
|
|
91
85
|
|
|
86
|
+
// The batch's provenance for the prompt-cache warmer (cache-warm.mjs
|
|
87
|
+
// cacheWarmTurnKind): one inbound channel message makes it real external
|
|
88
|
+
// activity whatever its presentation; it is an agent report only when every
|
|
89
|
+
// entry is one (a user prompt merged into it is the user speaking).
|
|
90
|
+
function batchPromptOrigin(batch) {
|
|
91
|
+
if (batch.some((entry) => entry?.origin === PROMPT_ORIGIN_CHANNEL)) return { promptOrigin: PROMPT_ORIGIN_CHANNEL };
|
|
92
|
+
if (batch.length > 0 && batch.every((entry) => entry?.origin === PROMPT_ORIGIN_AGENT_REPORT)) {
|
|
93
|
+
return { promptOrigin: PROMPT_ORIGIN_AGENT_REPORT };
|
|
94
|
+
}
|
|
95
|
+
return {};
|
|
96
|
+
}
|
|
97
|
+
|
|
92
98
|
/** The batch's transcript metadata; a batch of completion wakes also records
|
|
93
99
|
* the execution ids it delivers (`consumedExecutionIds`, from each entry's
|
|
94
100
|
* execution or resume keys; null for one with neither), the structured
|
|
@@ -117,6 +123,7 @@ export function turnOptionsForBatch(batch, steering) {
|
|
|
117
123
|
);
|
|
118
124
|
return {
|
|
119
125
|
promptSource: batchPromptSource(batch),
|
|
126
|
+
...batchPromptOrigin(batch),
|
|
120
127
|
retryFailedTurn: batch[0]?.retryFailedTurn === true,
|
|
121
128
|
retryTurnId: batch[0]?.retryTurnId || null,
|
|
122
129
|
retryAttemptId: batch[0]?.retryAttemptId || null,
|
|
@@ -151,19 +158,36 @@ export function turnOptionsForBatch(batch, steering) {
|
|
|
151
158
|
for (const entry of batch) {
|
|
152
159
|
try {
|
|
153
160
|
entry.onToolResult?.(message);
|
|
154
|
-
} catch {
|
|
161
|
+
} catch {
|
|
162
|
+
// One submitter's callback must not stop the batch's other entries.
|
|
163
|
+
}
|
|
155
164
|
}
|
|
156
165
|
},
|
|
157
166
|
onSettled: (detail) => {
|
|
158
167
|
for (const entry of batch) {
|
|
159
168
|
try {
|
|
160
169
|
entry.onSettled?.(detail);
|
|
161
|
-
} catch {
|
|
170
|
+
} catch {
|
|
171
|
+
// One submitter's callback must not stop the batch's other entries.
|
|
172
|
+
}
|
|
162
173
|
}
|
|
163
174
|
},
|
|
164
175
|
};
|
|
165
176
|
}
|
|
166
177
|
|
|
178
|
+
// A failed-turn retry: drop the failed turn's rows when it produced no
|
|
179
|
+
// output, so the resubmitted prompt renders once (the session rewinds the
|
|
180
|
+
// matching message under its own lock).
|
|
181
|
+
function rewindFailedTurnRows(batch, { getState, set, replaceItems }) {
|
|
182
|
+
const entry = batch[0];
|
|
183
|
+
if (entry?.retryFailedTurn !== true || entry.retryTurnId) return;
|
|
184
|
+
const rewound = rewoundFailedTurnItems(getState().items, entry.text);
|
|
185
|
+
if (!rewound) return;
|
|
186
|
+
set({
|
|
187
|
+
items: replaceItems(rewound, { preserveStreamingTail: true, preserveSpill: true, preserveTranscriptView: true }),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
167
191
|
export function createDrainLoop(
|
|
168
192
|
bag,
|
|
169
193
|
{ queue, steering, submissions, flushDeferredClearedSessionUi, autoClearBeforeWake }
|
|
@@ -181,35 +205,7 @@ export function createDrainLoop(
|
|
|
181
205
|
flushDeferredExecutionPendingResumeKick,
|
|
182
206
|
} = bag;
|
|
183
207
|
|
|
184
|
-
|
|
185
|
-
// output, so the resubmitted prompt renders once (the session rewinds the
|
|
186
|
-
// matching message under its own lock).
|
|
187
|
-
function rewindFailedTurnRows(batch) {
|
|
188
|
-
const entry = batch[0];
|
|
189
|
-
if (entry?.retryFailedTurn !== true || entry.retryTurnId) return;
|
|
190
|
-
const rewound = rewoundFailedTurnItems(getState().items, entry.text);
|
|
191
|
-
if (!rewound) return;
|
|
192
|
-
set({
|
|
193
|
-
items: replaceItems(rewound, { preserveStreamingTail: true, preserveSpill: true, preserveTranscriptView: true }),
|
|
194
|
-
});
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function scheduleBlockedDrainRetry(delayMs = BLOCKED_DRAIN_RETRY_MS) {
|
|
198
|
-
if (pending.length === 0 || flags.disposed) return;
|
|
199
|
-
if (flags.blockedDrainRetryTimer) return;
|
|
200
|
-
const timer = setTimeout(() => {
|
|
201
|
-
flags.blockedDrainRetryTimer = null;
|
|
202
|
-
if (pending.length > 0) void drain();
|
|
203
|
-
}, delayMs);
|
|
204
|
-
if (typeof timer.unref === 'function') timer.unref();
|
|
205
|
-
flags.blockedDrainRetryTimer = timer;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
function clearBlockedDrainRetry() {
|
|
209
|
-
if (!flags.blockedDrainRetryTimer) return;
|
|
210
|
-
clearTimeout(flags.blockedDrainRetryTimer);
|
|
211
|
-
flags.blockedDrainRetryTimer = null;
|
|
212
|
-
}
|
|
208
|
+
const { scheduleBlockedDrainRetry, clearBlockedDrainRetry } = createBlockedDrainRetry({ flags, pending, drain });
|
|
213
209
|
|
|
214
210
|
function hasModelDrainablePending() {
|
|
215
211
|
return pending.some((entry) => !isSlashQueuedEntry(entry));
|
|
@@ -217,45 +213,34 @@ export function createDrainLoop(
|
|
|
217
213
|
|
|
218
214
|
const admission = () => flags.admission || 'open';
|
|
219
215
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (entries.length === 0) return;
|
|
229
|
-
queue.removeQueuedEntries(entries);
|
|
230
|
-
const mirrored = entries.filter((entry) => steering.shouldMirrorSteeringEntry(entry) && entry.steeringPersistId);
|
|
231
|
-
if (mirrored.length > 0) {
|
|
232
|
-
void Promise.resolve(dropTuiSteeringPersist(steering.leadSessionId(), mirrored)).catch(() => {});
|
|
233
|
-
}
|
|
234
|
-
for (const entry of entries) {
|
|
235
|
-
if (entry.id) submissions.forget(entry.id);
|
|
236
|
-
try {
|
|
237
|
-
entry.onSettled?.({ status: 'rejected', reason: 'session-closed', result: null, session: null });
|
|
238
|
-
} catch {}
|
|
239
|
-
}
|
|
240
|
-
}
|
|
216
|
+
const settleClosedQueue = createClosedQueueSettler({
|
|
217
|
+
pending,
|
|
218
|
+
pendingNotificationKeys,
|
|
219
|
+
clearBlockedDrainRetry,
|
|
220
|
+
queue,
|
|
221
|
+
steering,
|
|
222
|
+
submissions,
|
|
223
|
+
});
|
|
241
224
|
|
|
242
|
-
|
|
225
|
+
// Whether a drain may start now. A closed gate either settles (terminal),
|
|
226
|
+
// waits for its own release edge, or schedules a re-check of itself.
|
|
227
|
+
function mayStartDrain() {
|
|
243
228
|
// Execution admission fence: a closing session starts nothing (the close
|
|
244
229
|
// path re-kicks the queue if the durable close fails); a closed one
|
|
245
230
|
// settles what was queued.
|
|
246
|
-
if (admission() === 'closing') return;
|
|
231
|
+
if (admission() === 'closing') return false;
|
|
247
232
|
if (admission() === 'terminal') {
|
|
248
233
|
settleClosedQueue();
|
|
249
|
-
return;
|
|
234
|
+
return false;
|
|
250
235
|
}
|
|
251
|
-
if (flags.draining) return;
|
|
236
|
+
if (flags.draining) return false;
|
|
252
237
|
// Bail while any session command holds commandBusy (auto-clear implies it,
|
|
253
238
|
// but so do setModel/newSession/resume/etc). Running a turn concurrently
|
|
254
239
|
// with a command that swaps or reroutes the live session is a race; the
|
|
255
240
|
// commandBusy-release hook re-kicks drain once the command finishes.
|
|
256
241
|
if (flags.autoClearRunning || flags.quotaResumePending || getState().commandBusy) {
|
|
257
242
|
scheduleBlockedDrainRetry();
|
|
258
|
-
return;
|
|
243
|
+
return false;
|
|
259
244
|
}
|
|
260
245
|
// A queued prompt/notification can arrive while a
|
|
261
246
|
// provider turn is already in flight (scheduled message, webhook, agent
|
|
@@ -273,8 +258,44 @@ export function createDrainLoop(
|
|
|
273
258
|
// a lead idled 24 minutes on ten queued agent completions and only moved
|
|
274
259
|
// when the user typed. Queued work re-reads this gate on its own.
|
|
275
260
|
scheduleBlockedDrainRetry(ACTIVE_TURN_RECHECK_MS);
|
|
276
|
-
return;
|
|
261
|
+
return false;
|
|
277
262
|
}
|
|
263
|
+
return true;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// Slash commands must run through the TUI command dispatcher, not be
|
|
267
|
+
// delivered to the model as plain text, so slash entries are handled
|
|
268
|
+
// outside the queued-command drain.
|
|
269
|
+
function dequeueModelBatch(firstBatch) {
|
|
270
|
+
return queue.dequeueQueueBatch('later', {
|
|
271
|
+
limit: firstBatch ? 1 : Infinity,
|
|
272
|
+
predicate: (entry) => !isSlashQueuedEntry(entry),
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// The batch's rows go on the transcript before its turn starts; returns the
|
|
277
|
+
// merged prompt the turn sends.
|
|
278
|
+
function presentBatch(batch) {
|
|
279
|
+
tuiDebug(`busy-queue drain batch=${batch.length} remaining=${pending.length}`);
|
|
280
|
+
const merged = mergePromptContents(batch);
|
|
281
|
+
rewindFailedTurnRows(batch, { getState, set, replaceItems });
|
|
282
|
+
renderBatchItems(batch, pushUserOrSyntheticItem);
|
|
283
|
+
return merged;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// The drain that still owns the loop hands off: settle a closed queue, run
|
|
287
|
+
// again for model work that arrived meanwhile, or release the deferred
|
|
288
|
+
// pending-resume kick.
|
|
289
|
+
function finishDrain() {
|
|
290
|
+
flags.draining = false;
|
|
291
|
+
flushDeferredClearedSessionUi();
|
|
292
|
+
if (admission() === 'terminal') settleClosedQueue();
|
|
293
|
+
else if (hasModelDrainablePending()) void drain();
|
|
294
|
+
else flushDeferredExecutionPendingResumeKick();
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function drain() {
|
|
298
|
+
if (!mayStartDrain()) return;
|
|
278
299
|
clearBlockedDrainRetry();
|
|
279
300
|
const drainEpoch = (Number(flags.drainEpoch) || 0) + 1;
|
|
280
301
|
flags.drainEpoch = drainEpoch;
|
|
@@ -293,13 +314,7 @@ export function createDrainLoop(
|
|
|
293
314
|
// Drain one priority/mode bucket at a time (unified command queue):
|
|
294
315
|
// prompt steering stays editable and task notifications stay
|
|
295
316
|
// non-editable but model-visible.
|
|
296
|
-
const batch =
|
|
297
|
-
limit: firstBatch ? 1 : Infinity,
|
|
298
|
-
// Slash commands must run through the TUI command dispatcher, not be
|
|
299
|
-
// delivered to the model as plain text, so slash entries are handled
|
|
300
|
-
// outside the queued-command drain.
|
|
301
|
-
predicate: (entry) => !isSlashQueuedEntry(entry),
|
|
302
|
-
});
|
|
317
|
+
const batch = dequeueModelBatch(firstBatch);
|
|
303
318
|
firstBatch = false;
|
|
304
319
|
if (batch.length === 0) break;
|
|
305
320
|
if (isGoalQueuedEntry(batch[0]) && bag.shouldRunGoalContinuation?.(batch[0]) !== true) {
|
|
@@ -314,10 +329,17 @@ export function createDrainLoop(
|
|
|
314
329
|
return;
|
|
315
330
|
}
|
|
316
331
|
}
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
332
|
+
// Rows owed to this thread before its next prompt (a bot's progress
|
|
333
|
+
// updates from the sessions it handed work to) land first.
|
|
334
|
+
const prelude = sessionTurnPrelude(bag.runtime?.id);
|
|
335
|
+
if (prelude) {
|
|
336
|
+
await prelude.catch((error) => tuiDebug(`turn prelude failed: ${error?.message || error}`));
|
|
337
|
+
if (flags.drainEpoch !== drainEpoch || admission() !== 'open' || getState().commandBusy) {
|
|
338
|
+
queue.requeueEntriesFront(batch);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const merged = presentBatch(batch);
|
|
321
343
|
const turnStatus = await bag.runTurn(merged, turnOptionsForBatch(batch, steering));
|
|
322
344
|
if (flags.drainEpoch !== drainEpoch) return;
|
|
323
345
|
// A deferred cleared-session UI sync (from a late-settling abandoned
|
|
@@ -330,78 +352,25 @@ export function createDrainLoop(
|
|
|
330
352
|
if (turnStatus === 'cancelled' && pending.length === 0) break;
|
|
331
353
|
}
|
|
332
354
|
} finally {
|
|
333
|
-
if (flags.drainEpoch === drainEpoch)
|
|
334
|
-
flags.draining = false;
|
|
335
|
-
flushDeferredClearedSessionUi();
|
|
336
|
-
if (admission() === 'terminal') settleClosedQueue();
|
|
337
|
-
else if (hasModelDrainablePending()) void drain();
|
|
338
|
-
else flushDeferredExecutionPendingResumeKick();
|
|
339
|
-
}
|
|
355
|
+
if (flags.drainEpoch === drainEpoch) finishDrain();
|
|
340
356
|
}
|
|
341
357
|
}
|
|
342
358
|
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
// This is an idempotent success, not a rejected submit. Returning false
|
|
359
|
-
// makes Desktop restore and retry a message that is already queued.
|
|
360
|
-
return true;
|
|
361
|
-
}
|
|
362
|
-
const entry = queue.makeQueueEntry(text, options);
|
|
363
|
-
if (entry.mode === 'task-notification' && entry.key) {
|
|
364
|
-
if (pendingNotificationKeys.has(entry.key)) return false;
|
|
365
|
-
pendingNotificationKeys.add(entry.key);
|
|
366
|
-
}
|
|
367
|
-
submissions.remember(submissionId);
|
|
368
|
-
pending.push(entry);
|
|
369
|
-
// Typed input or a deliverable notification ends a passive task wait or
|
|
370
|
-
// shell hold so the next boundary hands it to the model now.
|
|
371
|
-
const wake = steering.taskWaitWakeForEntry(entry);
|
|
372
|
-
if (wake) {
|
|
373
|
-
try {
|
|
374
|
-
bag.runtime?.interruptTaskWait?.(wake, wake === 'notification' ? entry.execution?.id || null : null);
|
|
375
|
-
} catch {}
|
|
376
|
-
}
|
|
377
|
-
const needsDurableSteering =
|
|
378
|
-
(getState().busy || getState().commandBusy || flags.autoClearRunning || flags.quotaResumePending) &&
|
|
379
|
-
steering.shouldMirrorSteeringEntry(entry);
|
|
380
|
-
const persistence = needsDurableSteering ? appendTuiSteeringPersist(steering.leadSessionId(), entry) : null;
|
|
381
|
-
if (isQueuedEntryVisible(entry)) {
|
|
382
|
-
set({ queued: [...getState().queued, entry] });
|
|
383
|
-
if (isQueuedEntryEditable(entry)) flushEmitImmediate?.();
|
|
384
|
-
}
|
|
385
|
-
const queueAgeMs = Math.max(0, Date.now() - Number(entry.submittedAt || Date.now()));
|
|
386
|
-
tuiDebug(`prompt-queued id=${String(entry.id)} busy=${getState().busy ? 1 : 0} ageMs=${queueAgeMs}`);
|
|
387
|
-
if (getState().busy) tuiDebug(`busy-queue enqueue mode=${entry.mode} pending=${pending.length}`);
|
|
388
|
-
void drain();
|
|
389
|
-
if (options.awaitPersistence === true && persistence) {
|
|
390
|
-
return Promise.resolve(persistence).then((persisted) => {
|
|
391
|
-
if (persisted !== false) return true;
|
|
392
|
-
const index = pending.indexOf(entry);
|
|
393
|
-
// A turn already took the prompt while its durable mirror was being
|
|
394
|
-
// written: it was delivered, so rejecting it would hand the sender a
|
|
395
|
-
// copy of a message the model already has.
|
|
396
|
-
if (index < 0) return true;
|
|
397
|
-
pending.splice(index, 1);
|
|
398
|
-
queue.removeQueuedEntries([entry]);
|
|
399
|
-
if (submissionId) submissions.forget(submissionId);
|
|
400
|
-
return false;
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
return true;
|
|
404
|
-
}
|
|
359
|
+
const enqueue = createEnqueue({
|
|
360
|
+
admission,
|
|
361
|
+
tuiDebug,
|
|
362
|
+
flags,
|
|
363
|
+
pending,
|
|
364
|
+
pendingNotificationKeys,
|
|
365
|
+
getState,
|
|
366
|
+
set,
|
|
367
|
+
flushEmitImmediate,
|
|
368
|
+
getRuntime: () => bag.runtime,
|
|
369
|
+
queue,
|
|
370
|
+
steering,
|
|
371
|
+
submissions,
|
|
372
|
+
drain,
|
|
373
|
+
});
|
|
405
374
|
|
|
406
375
|
return { drain, enqueue, settleClosedQueue };
|
|
407
376
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
promptDisplayText,
|
|
10
10
|
promptContentImageMeta,
|
|
11
11
|
} from '../../queue-helpers.mjs';
|
|
12
|
+
import { promptOriginOf } from '../../../../runtime/shared/prompt-origin.mjs';
|
|
12
13
|
|
|
13
14
|
export function makeQueueEntry(text, options = {}, nextId) {
|
|
14
15
|
const mode = options.mode || 'prompt';
|
|
@@ -33,6 +34,10 @@ export function makeQueueEntry(text, options = {}, nextId) {
|
|
|
33
34
|
...(options.execution && typeof options.execution === 'object' ? { execution: { ...options.execution } } : {}),
|
|
34
35
|
priority,
|
|
35
36
|
key: options.key || null,
|
|
37
|
+
// Provenance apart from the mode (shared/prompt-origin.mjs): an inbound
|
|
38
|
+
// channel message queued as a task notification is real external
|
|
39
|
+
// activity; an agent report queued as a prompt is the runtime's.
|
|
40
|
+
origin: promptOriginOf(options.origin),
|
|
36
41
|
skipSlashCommands: options.skipSlashCommands === true,
|
|
37
42
|
displayText: mode === 'task-notification' ? notificationDisplayText(displayText) : String(displayText || ''),
|
|
38
43
|
suppressDisplay: options.suppressDisplay === true,
|
|
@@ -19,6 +19,9 @@ const attributionOf = (entry) =>
|
|
|
19
19
|
entry?.transcriptMeta?.hop || 0
|
|
20
20
|
}`;
|
|
21
21
|
|
|
22
|
+
/** A retry of a failed turn runs alone: its options belong to its own turn. */
|
|
23
|
+
const isRetryEntry = (entry) => entry.retryFailedTurn === true || Boolean(entry.retryTurnId);
|
|
24
|
+
|
|
22
25
|
export function createTakeEntriesOps({ pending, pendingNotificationKeys, removeQueuedEntries, leadSessionId }) {
|
|
23
26
|
function dequeueQueueBatch(maxPriority = 'later', options = {}) {
|
|
24
27
|
if (pending.length === 0) return [];
|
|
@@ -45,14 +48,14 @@ export function createTakeEntriesOps({ pending, pendingNotificationKeys, removeQ
|
|
|
45
48
|
(entry.mode || 'prompt') === targetMode &&
|
|
46
49
|
queuePriorityValue(entry.priority) === bestPriority
|
|
47
50
|
) {
|
|
48
|
-
if (batch.length > 0 && entry
|
|
51
|
+
if (batch.length > 0 && isRetryEntry(entry)) break;
|
|
49
52
|
// One turn records one sender: a bot's message never merges with the user's.
|
|
50
53
|
if (batch.length > 0 && attributionOf(entry) !== attributionOf(batch[0])) break;
|
|
51
54
|
batch.push(entry);
|
|
52
55
|
pending.splice(i, 1);
|
|
53
56
|
if (entry.mode === 'task-notification' && entry.key) pendingNotificationKeys.delete(entry.key);
|
|
54
57
|
// A routine fire is a turn of its own, never merged with another.
|
|
55
|
-
if (entry
|
|
58
|
+
if (isRetryEntry(entry) || entry.mode === 'routine' || batch.length >= limit) break;
|
|
56
59
|
} else {
|
|
57
60
|
i += 1;
|
|
58
61
|
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* src/tui/session/tool-approval-delegation.mjs — where else a tool-approval
|
|
3
|
+
* request shows: the top-level chat above a sub-agent (`delegationSurface`) or
|
|
4
|
+
* the chats handing this session work (`handsOff`, `offerToHandoffs`;
|
|
5
|
+
* runtime/shared/delegated-approval-host.mjs).
|
|
6
|
+
*/
|
|
7
|
+
import { agentIdOfHomeSession } from '../../runtime/agent/orchestrator/session/agent-home.mjs';
|
|
8
|
+
import { roomMemberOfSession } from '../../runtime/agent/orchestrator/session/agent-room.mjs';
|
|
9
|
+
import { delegateToHandoffs } from '../../runtime/shared/delegated-approval-host.mjs';
|
|
10
|
+
|
|
11
|
+
/** Requests a sub-agent (or a session a chat handed work to) shows in the
|
|
12
|
+
* conversation the user watches too. A secret and a takeover go there as
|
|
13
|
+
* well: the value goes to this PC by its request id and the sign-in window
|
|
14
|
+
* is bound to the asking session, wherever the card is answered. A
|
|
15
|
+
* sub-agent's question stays in its own session (its answer belongs to that
|
|
16
|
+
* conversation); a handed-off session's question shows in the chats handing
|
|
17
|
+
* it work too, beside their hand-off line (tool-approval.mjs
|
|
18
|
+
* `handoffQuestions`), never in their queue. */
|
|
19
|
+
const DELEGATED_KINDS = new Set(['', 'approval', 'outbound', 'permission', 'secret', 'takeover']);
|
|
20
|
+
const HANDOFF_KINDS = new Set([...DELEGATED_KINDS, 'question']);
|
|
21
|
+
|
|
22
|
+
export function createApprovalDelegation({ surfaceOf, ownSessionId }) {
|
|
23
|
+
/** `{ sessionId, chain }` of the top-level chat a sub-agent's request also
|
|
24
|
+
* shows in, or null (no delegation, another kind, or this is the top). */
|
|
25
|
+
function delegationSurface(request) {
|
|
26
|
+
if (typeof surfaceOf !== 'function' || !DELEGATED_KINDS.has(request.kind)) return null;
|
|
27
|
+
const own = String(request.sessionId || ownSessionId());
|
|
28
|
+
let surface = null;
|
|
29
|
+
try {
|
|
30
|
+
surface = surfaceOf(own);
|
|
31
|
+
} catch {
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
const sessionId = String(surface?.sessionId || '');
|
|
35
|
+
if (!sessionId || sessionId === own || sessionId === ownSessionId()) return null;
|
|
36
|
+
return { sessionId, chain: Array.isArray(surface.chain) ? surface.chain.map(String) : [] };
|
|
37
|
+
}
|
|
38
|
+
/** Whether a request this (top-level) session itself makes also shows in
|
|
39
|
+
* the chats handing it work (delegated-approval-host.mjs), while it waits. */
|
|
40
|
+
function handsOff(request) {
|
|
41
|
+
if (typeof surfaceOf !== 'function' || !HANDOFF_KINDS.has(request.kind)) return false;
|
|
42
|
+
const own = ownSessionId();
|
|
43
|
+
return Boolean(own) && String(request.sessionId || own) === own;
|
|
44
|
+
}
|
|
45
|
+
function offerToHandoffs(id, request, answer) {
|
|
46
|
+
const own = ownSessionId();
|
|
47
|
+
const agentId = agentIdOfHomeSession(own) || roomMemberOfSession(own)?.agentId || '';
|
|
48
|
+
// Each chat handing this session work names it its own way.
|
|
49
|
+
delegateToHandoffs({
|
|
50
|
+
id,
|
|
51
|
+
sessionId: own,
|
|
52
|
+
request,
|
|
53
|
+
answer,
|
|
54
|
+
copyFor: (chat, { title }) => ({
|
|
55
|
+
...request,
|
|
56
|
+
sessionId: request.sessionId || own,
|
|
57
|
+
chain: [],
|
|
58
|
+
surfaceSessionId: chat,
|
|
59
|
+
handoff: { title, ...(agentId ? { agentId } : {}) },
|
|
60
|
+
}),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
return { delegationSurface, handsOff, offerToHandoffs };
|
|
64
|
+
}
|