mixdog 1.2.3 → 1.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/scripts/gate-local.mjs +6 -4
- package/scripts/lib/gate-scheduler.mjs +29 -11
- package/scripts/probe-provider-catalog.mjs +31 -0
- package/scripts/provider-pingpong-live.mjs +128 -38
- package/scripts/session-sweep.mjs +5 -25
- package/src/agents/maintainer/AGENT.md +1 -1
- package/src/agents/maintainer/agent.json +1 -1
- package/src/agents/reviewer/AGENT.md +2 -4
- package/src/agents/security/AGENT.md +1 -2
- package/src/agents/writer/AGENT.md +2 -3
- package/src/defaults/shipped-history.json +6 -1
- package/src/defaults/skills/image/SKILL.md +21 -23
- package/src/lib/keychain-cjs.cjs +13 -11
- package/src/rules/agent/AGENT.md +7 -5
- package/src/rules/lead/LEAD.md +7 -9
- package/src/rules/shared/45-destructive.md +4 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +1 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +28 -7
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -11
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +101 -23
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-api-key-fetch.mjs +36 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-error-contract.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +2 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +26 -44
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +2 -5
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +4 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +41 -12
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +13 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +81 -10
- package/src/runtime/agent/orchestrator/providers/antigravity-retry-policy.mjs +109 -5
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +107 -37
- package/src/runtime/agent/orchestrator/providers/command-code-oauth.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/cursor-auth.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-native-context.mjs +48 -0
- package/src/runtime/agent/orchestrator/providers/cursor-wire-exec.mjs +144 -34
- package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +28 -4
- package/src/runtime/agent/orchestrator/providers/cursor-wire-request.mjs +8 -4
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-bridge.mjs +41 -11
- package/src/runtime/agent/orchestrator/providers/cursor-wire-stream-messages.mjs +67 -5
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +14 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +9 -6
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +26 -4
- package/src/runtime/agent/orchestrator/providers/gemini-cache.mjs +12 -6
- package/src/runtime/agent/orchestrator/providers/gemini-error-contract.mjs +14 -6
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +4 -4
- package/src/runtime/agent/orchestrator/providers/gemini-response.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/gemini-rest-stream/watchdogs.mjs +16 -5
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/gemini-schema.mjs +22 -10
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/gemini-thinking.mjs +33 -7
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +11 -2
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +9 -2
- package/src/runtime/agent/orchestrator/providers/lib/claude-bridge-schema.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/lib/request-compression.mjs +86 -0
- package/src/runtime/agent/orchestrator/providers/lib/wire-pairing.mjs +4 -2
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -5
- package/src/runtime/agent/orchestrator/providers/mimo-oauth.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +60 -16
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +11 -13
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-send.mjs +30 -19
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream/stream-state.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-chat-stream.mjs +5 -0
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +42 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-reasoning-field.mjs +13 -9
- package/src/runtime/agent/orchestrator/providers/openai-compat-response-normalization.mjs +1 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses-parity.fixtures.mjs +18 -7
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +36 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat-retry-profile.mjs +51 -3
- package/src/runtime/agent/orchestrator/providers/openai-compat.mjs +11 -1
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-request.mjs +64 -52
- package/src/runtime/agent/orchestrator/providers/openai-http-sse-response-state/response-events/terminal-frames.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-input.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-responses-payload.mjs +7 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-delta.mjs +1 -4
- package/src/runtime/agent/orchestrator/providers/openai-ws-response-state/tool-calls.mjs +0 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +7 -6
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +13 -2
- package/src/runtime/agent/orchestrator/providers/opencode-go.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/openrouter.mjs +1 -2
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +5 -4
- package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/retry-classification.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +11 -4
- package/src/runtime/agent/orchestrator/providers/wire-route.mjs +14 -4
- package/src/runtime/agent/orchestrator/providers/xai-tool-search.fixtures.mjs +15 -3
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +6 -2
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +8 -6
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-availability.mjs +4 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-store.mjs +30 -2
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +3 -2
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +8 -3
- package/src/runtime/agent/orchestrator/runtime-core/skill-tool-loading.mjs +3 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge-contract.mjs +6 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +10 -10
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +3 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +46 -8
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +10 -8
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -2
- package/src/runtime/agent/orchestrator/session/agent-pool-progress.mjs +24 -0
- package/src/runtime/agent/orchestrator/session/cache/tool-result-reuse.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/catalog-db/index.mjs +2 -3
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +3 -11
- package/src/runtime/agent/orchestrator/session/catalog-db/worker.mjs +6 -22
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +28 -0
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/conversation-import/claude.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/conversation-import/codex.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/eager-dispatch/admission.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/historical-session-closure.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +2 -5
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +2 -4
- package/src/runtime/agent/orchestrator/session/loop/context-overflow.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +0 -27
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -10
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +2 -9
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/provider-tool-exclusions.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/request-boundary.mjs +1 -4
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +76 -0
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/loop/step-auto-effort.mjs +8 -9
- package/src/runtime/agent/orchestrator/session/loop/tool-classify.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +15 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +1 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +3 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +33 -3
- package/src/runtime/agent/orchestrator/session/manager/ask-support.mjs +0 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +21 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +38 -13
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-failure.mjs +33 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +19 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +57 -13
- package/src/runtime/agent/orchestrator/session/manager/failed-turn-rewind.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +6 -120
- package/src/runtime/agent/orchestrator/session/manager/pending-persist-queue.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +29 -8
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +42 -49
- package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +50 -0
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +32 -14
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +17 -33
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +29 -13
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +3 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-recovery.mjs +45 -0
- package/src/runtime/agent/orchestrator/session/manager.mjs +2 -9
- package/src/runtime/agent/orchestrator/session/send-instrumentation.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/side-question.mjs +12 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +5 -12
- package/src/runtime/agent/orchestrator/session/store/lifecycle-read.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/listing.mjs +2 -6
- package/src/runtime/agent/orchestrator/session/store/live-cache-eviction.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/liveness-veto.mjs +46 -37
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/load-session.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +5 -11
- package/src/runtime/agent/orchestrator/session/store/session-paths.mjs +59 -0
- package/src/runtime/agent/orchestrator/session/store/summary-cache.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-finish.mjs +27 -32
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +0 -8
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +6 -18
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +33 -106
- package/src/runtime/agent/orchestrator/session/store/write-admission.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-agent-worker-pool.mjs +5 -8
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +136 -196
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +50 -116
- package/src/runtime/agent/orchestrator/session/store-summary-locations.mjs +3 -29
- package/src/runtime/agent/orchestrator/session/store-summary-reader-catalog-fixture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +7 -8
- package/src/runtime/agent/orchestrator/session/store-transcript-projection.mjs +9 -20
- package/src/runtime/agent/orchestrator/session/store.mjs +3 -16
- package/src/runtime/agent/orchestrator/session/tool-batch/execute-call.mjs +11 -12
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +37 -13
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +15 -4
- package/src/runtime/agent/orchestrator/session/tool-schema-rejection.mjs +4 -2
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +72 -15
- package/src/runtime/agent/orchestrator/session/turn-failure.mjs +45 -0
- package/src/runtime/agent/orchestrator/stall-policy.mjs +50 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/inline-hoist.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/builtin/external-tool-adapters.mjs +204 -10
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +160 -39
- package/src/runtime/agent/orchestrator/tools/builtin/git-partial-stage.mjs +7 -5
- package/src/runtime/agent/orchestrator/tools/builtin/grep-formatting.mjs +14 -5
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-context-expander.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-output.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/grep-path-fanout.mjs +48 -44
- package/src/runtime/agent/orchestrator/tools/builtin/lib/search-input-helpers.mjs +10 -3
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +200 -17
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-task-output.mjs +5 -4
- package/src/runtime/agent/orchestrator/tools/builtin/read-formatting.mjs +6 -1
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +20 -0
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +101 -2
- package/src/runtime/agent/orchestrator/tools/code-graph-prewarm-worker.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/lib/owned-shell-trees.mjs +67 -0
- package/src/runtime/agent/orchestrator/tools/lib/process-table-fixture.mjs +50 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +303 -63
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-background/promote-job.mjs +2 -0
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +108 -18
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +77 -11
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +81 -5
- package/src/runtime/attachments/store.d.mts +9 -0
- package/src/runtime/attachments/store.mjs +80 -9
- package/src/runtime/browser-bridge/browser-action-contract.d.mts +0 -1
- package/src/runtime/browser-bridge/browser-action-contract.mjs +0 -2
- package/src/runtime/computer-bridge/action-schema.mjs +178 -178
- package/src/runtime/computer-bridge/linux-readiness.d.mts +0 -2
- package/src/runtime/computer-bridge/linux-readiness.mjs +0 -16
- package/src/runtime/effort-judge/judge-client.mjs +21 -4
- package/src/runtime/effort-judge/judge-worker.mjs +4 -1
- package/src/runtime/github/tool.mjs +118 -17
- package/src/runtime/media/adapters/codex-image.mjs +140 -21
- package/src/runtime/media/catalog.mjs +4 -4
- package/src/runtime/media/chatgpt-usage.mjs +55 -0
- package/src/runtime/media/defaults.mjs +37 -0
- package/src/runtime/media/jobs.mjs +12 -2
- package/src/runtime/media/lanes.mjs +12 -0
- package/src/runtime/media/tool-defs.mjs +15 -4
- package/src/runtime/media/tool.mjs +41 -12
- package/src/runtime/media/upstream-error.mjs +72 -6
- package/src/runtime/memory/lib/ingest/transcript-rows.mjs +1 -0
- package/src/runtime/memory/lib/pg/process.mjs +5 -7
- package/src/runtime/memory/lib/pg/supervisor.mjs +4 -1
- package/src/runtime/office/authoring/docx-html-runner.mjs +1 -1
- package/src/runtime/office/authoring/html-browser.mjs +18 -8
- package/src/runtime/office/authoring/pdf-html-runner.mjs +1 -1
- package/src/runtime/office/authoring/pptx-html-measure.mjs +1 -1
- package/src/runtime/office/authoring/xlsx-html-runner.mjs +1 -1
- package/src/runtime/office/core/office-write-ledger.mjs +1 -5
- package/src/runtime/office/index.mjs +2 -3
- package/src/runtime/office/pdf/pdf-analysis.mjs +0 -68
- package/src/runtime/session-search/session-search-ingest.mjs +1 -1
- package/src/runtime/shared/agent-reap-state.mjs +13 -0
- package/src/runtime/shared/atomic-file.d.mts +1 -1
- package/src/runtime/shared/background-tasks.mjs +6 -1
- package/src/runtime/shared/bounded-line-diff.mjs +11 -2
- package/src/runtime/shared/browser-launch.mjs +15 -0
- package/src/runtime/shared/err-text.mjs +42 -4
- package/src/runtime/shared/file-lock.mjs +6 -5
- package/src/runtime/shared/llm/anthropic-betas.mjs +1 -2
- package/src/runtime/shared/llm/provider-model-identities.mjs +1 -0
- package/src/runtime/shared/model-route-chain.d.mts +8 -0
- package/src/runtime/shared/model-route-chain.mjs +48 -0
- package/src/runtime/shared/model-route-fallback.mjs +22 -0
- package/src/runtime/shared/pid-liveness.mjs +12 -0
- package/src/runtime/shared/provider-api-key.mjs +6 -0
- package/src/runtime/shared/provider-timeout-phase.mjs +17 -0
- package/src/runtime/shared/quiet-progress.d.mts +35 -0
- package/src/runtime/shared/quiet-progress.mjs +101 -0
- package/src/runtime/shared/quota-resume-store.mjs +32 -1
- package/src/runtime/shared/read-row-numbers.mjs +44 -0
- package/src/runtime/shared/remote-intent.mjs +2 -42
- package/src/runtime/shared/slow-span.mjs +8 -1
- package/src/runtime/shared/statusline/statusline-segments.mjs +24 -2
- package/src/runtime/shared/tool-card-model/card-header.mjs +2 -1
- package/src/runtime/shared/tool-output-budget.mjs +2 -0
- package/src/runtime/shared/tool-result-summary.mjs +16 -2
- package/src/runtime/shared/tool-status.mjs +3 -2
- package/src/runtime/shared/tool-surface.mjs +2 -3
- package/src/runtime/shared/tool-work-units.mjs +22 -15
- package/src/runtime/shared/transcript-metadata.mjs +49 -1
- package/src/runtime/shared/transcript-turn-keys.mjs +33 -0
- package/src/runtime/shared/transcript-writer/transcript-file.mjs +10 -1
- package/src/runtime/shared/transcript-writer.mjs +14 -4
- package/src/runtime/shared/turn-changes.mjs +2 -1
- package/src/runtime/shared/turn-snapshot.mjs +11 -3
- package/src/runtime/shared/worktrees/activity-gate.mjs +175 -0
- package/src/runtime/shared/worktrees/branch-management.mjs +179 -0
- package/src/runtime/shared/worktrees/carry.mjs +6 -4
- package/src/runtime/shared/worktrees/contract.mjs +27 -1
- package/src/runtime/shared/worktrees/dependencies.mjs +456 -0
- package/src/runtime/shared/worktrees/file-recovery.mjs +29 -13
- package/src/runtime/shared/worktrees/git-head.mjs +22 -13
- package/src/runtime/shared/worktrees/include.mjs +125 -7
- package/src/runtime/shared/worktrees/lock.mjs +1 -1
- package/src/runtime/shared/worktrees/maintenance.mjs +20 -2
- package/src/runtime/shared/worktrees/manager.mjs +378 -32
- package/src/runtime/shared/worktrees/owner.mjs +2 -1
- package/src/runtime/shared/worktrees/preparation-report.mjs +79 -0
- package/src/runtime/shared/worktrees/setup-commands.mjs +232 -0
- package/src/runtime/shared/worktrees/workspace-visibility.mjs +17 -0
- package/src/runtime/terminal-bridge/client.mjs +14 -10
- package/src/session-runtime/boot/apis.mjs +7 -1
- package/src/session-runtime/boot/config.mjs +2 -0
- package/src/session-runtime/boot/facade.mjs +3 -0
- package/src/session-runtime/boot/session.mjs +3 -2
- package/src/session-runtime/boot/tools.mjs +3 -0
- package/src/session-runtime/conversation-import/service.mjs +1 -1
- package/src/session-runtime/goal-facade-api.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +44 -18
- package/src/session-runtime/internal-tool-executor/knowledge-tools.mjs +4 -1
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +22 -8
- package/src/session-runtime/internal-tool-executor.mjs +2 -0
- package/src/session-runtime/lifecycle/cold-heir.mjs +0 -1
- package/src/session-runtime/lifecycle/session-catalog.mjs +14 -16
- package/src/session-runtime/lifecycle/session-switch/session-delete.mjs +18 -53
- package/src/session-runtime/lifecycle/session-switch.mjs +1 -1
- package/src/session-runtime/lifecycle/shared.mjs +5 -6
- package/src/session-runtime/lifecycle/teardown/session-close.mjs +6 -5
- package/src/session-runtime/lifecycle/teardown.mjs +7 -16
- package/src/session-runtime/media-api.mjs +8 -0
- package/src/session-runtime/model-route/route-ownership.mjs +60 -0
- package/src/session-runtime/model-route/route-persist.mjs +3 -2
- package/src/session-runtime/model-route/set-route.mjs +114 -34
- package/src/session-runtime/model-route/web-search-route.mjs +7 -2
- package/src/session-runtime/model-route-api.mjs +4 -2
- package/src/session-runtime/native-web-search/routes.mjs +37 -24
- package/src/session-runtime/native-web-search.mjs +9 -8
- package/src/session-runtime/provider-auth/setup.mjs +4 -0
- package/src/session-runtime/provider-models/catalog-load.mjs +9 -0
- package/src/session-runtime/provider-models/web-search-catalog.mjs +23 -3
- package/src/session-runtime/provider-models.mjs +1 -0
- package/src/session-runtime/provider-usage.mjs +4 -0
- package/src/session-runtime/route-resolution.mjs +3 -1
- package/src/session-runtime/runtime-core.mjs +4 -0
- package/src/session-runtime/runtime-facade.mjs +6 -0
- package/src/session-runtime/services/agent-task-status.mjs +2 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +0 -3
- package/src/session-runtime/services/agent-tool/close-flow.mjs +6 -7
- package/src/session-runtime/services/agent-tool/execute-send.mjs +2 -2
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +36 -3
- package/src/session-runtime/services/agent-tool/execute.mjs +9 -1
- package/src/session-runtime/services/agent-tool/helpers.mjs +30 -7
- package/src/session-runtime/services/agent-tool/job-task-reconcile.mjs +7 -2
- package/src/session-runtime/services/agent-tool/job-views/session-progress.mjs +6 -3
- package/src/session-runtime/services/agent-tool/lead-worker-index/lead-rows.mjs +3 -4
- package/src/session-runtime/services/agent-tool/lead-worker-index/reap-timers.mjs +9 -3
- package/src/session-runtime/services/agent-tool/lead-worker-index/row-settlement.mjs +7 -18
- package/src/session-runtime/services/agent-tool/lead-worker-index.mjs +9 -6
- package/src/session-runtime/services/agent-tool/model-select.mjs +82 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +24 -6
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +3 -1
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +19 -5
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +5 -4
- package/src/session-runtime/services/agent-tool/spawn-flow.mjs +2 -1
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +3 -0
- package/src/session-runtime/services/agent-tool/tag-registry/tag-lookup.mjs +1 -3
- package/src/session-runtime/services/agent-tool/tag-registry/tag-refresh.mjs +1 -3
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +3 -9
- package/src/session-runtime/services/agent-tool/terminal-reaper/scan-admission.mjs +8 -41
- package/src/session-runtime/services/agent-tool/terminal-reaper/stale-rows.mjs +3 -3
- package/src/session-runtime/services/agent-tool/terminal-reaper.mjs +6 -23
- package/src/session-runtime/services/agent-tool/terminal-trace.mjs +0 -1
- package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +32 -20
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +5 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +28 -8
- package/src/session-runtime/services/agent-tool.mjs +4 -0
- package/src/session-runtime/services/agent-watchdog-registry.mjs +2 -0
- package/src/session-runtime/services/provider-admin.mjs +77 -0
- package/src/session-runtime/session-create/session-options.mjs +7 -5
- package/src/session-runtime/session-create/session-start-hook.mjs +2 -1
- package/src/session-runtime/session-transcript.mjs +4 -5
- package/src/session-runtime/session-turn-api.mjs +3 -9
- package/src/session-runtime/settings-builtin-tools-api.mjs +9 -6
- package/src/session-runtime/setup-tool/executor.mjs +23 -5
- package/src/session-runtime/setup-tool/extended-actions.mjs +6 -6
- package/src/session-runtime/setup-tool/settings-contract.mjs +7 -2
- package/src/session-runtime/setup-tool/tool-defs.mjs +13 -1
- package/src/session-runtime/tool-policy-refresh.mjs +83 -32
- package/src/session-runtime/turn/agent-ops.mjs +49 -2
- package/src/session-runtime/turn/side-question-ops.mjs +1 -4
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +2 -0
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +4 -0
- package/src/session-runtime/turn/turn-run/turn-settle.mjs +8 -2
- package/src/session-runtime/turn/turn-run.mjs +1 -36
- package/src/session-runtime/usage-stats-api.mjs +4 -1
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +16 -1
- package/src/session-runtime/workspace/admission-gate.mjs +44 -15
- package/src/session-runtime/workspace/project-branches.mjs +28 -36
- package/src/session-runtime/workspace/transition-steps.mjs +96 -95
- package/src/session-runtime/workspace/transitions.mjs +248 -68
- package/src/standalone/channel-transport.mjs +3 -4
- package/src/standalone/daemon-desktop-runtime.mjs +32 -1
- package/src/standalone/daemon-pg-stop.mjs +1 -3
- package/src/standalone/daemon-session-control.mjs +88 -0
- package/src/standalone/daemon-stored-session-views.mjs +23 -3
- package/src/standalone/daemon.mjs +2 -0
- package/src/standalone/local-session-runtime.mjs +11 -12
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-runtime-inline-host.mjs +2 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +91 -49
- package/src/standalone/session-service/agent-tree/agent-child.mjs +19 -1
- package/src/standalone/session-service/agent-tree/agent-registry.mjs +25 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +9 -1
- package/src/standalone/session-service/agent-tree.mjs +28 -2
- package/src/standalone/session-service/entries/session-loads.mjs +16 -5
- package/src/standalone/session-service/entries.mjs +1 -0
- package/src/standalone/session-service/projection/transcript-window.mjs +39 -0
- package/src/standalone/session-service/quota-resume.mjs +28 -13
- package/src/standalone/session-service/session-calls/session-turns.mjs +17 -6
- package/src/standalone/session-service/session-calls/session-views.mjs +6 -0
- package/src/standalone/session-tool-output.mjs +82 -0
- package/src/tui/app/app-view.jsx +2 -1
- package/src/tui/app/provider-setup/provider-items.mjs +8 -0
- package/src/tui/app/provider-setup-picker.mjs +15 -3
- package/src/tui/app/slash-commands.mjs +6 -0
- package/src/tui/app/slash-dispatch/session-commands.mjs +49 -0
- package/src/tui/components/Spinner.jsx +4 -0
- package/src/tui/components/SpinnerByline.jsx +21 -2
- package/src/tui/components/status-line/local-line.mjs +10 -2
- package/src/tui/dist/index.mjs +478 -251
- package/src/tui/session/active-tool-summary.mjs +1 -1
- package/src/tui/session/context-state.mjs +0 -3
- package/src/tui/session/goal-continuation/continuation-queue.mjs +2 -3
- package/src/tui/session/goal-continuation/deadline-delivery.mjs +4 -2
- package/src/tui/session/goal-continuation/session-boundary.mjs +21 -0
- package/src/tui/session/quota-resume.mjs +140 -61
- package/src/tui/session/session-api/commands.mjs +4 -10
- package/src/tui/session/session-api/controls.mjs +12 -0
- package/src/tui/session/session-api/lifecycle/dispose.mjs +2 -9
- package/src/tui/session/session-api/lifecycle/resume.mjs +2 -7
- package/src/tui/session/session-api/lifecycle.mjs +13 -11
- package/src/tui/session/session-api/media.mjs +2 -0
- package/src/tui/session/session-api/routes.mjs +81 -57
- package/src/tui/session/session-api/settings.mjs +3 -3
- package/src/tui/session/session-api/worktree.mjs +3 -2
- package/src/tui/session/session-flow/auto-clear.mjs +1 -3
- package/src/tui/session/session-flow/drain.mjs +6 -4
- package/src/tui/session/session-flow/queue/entry-shape.mjs +2 -0
- package/src/tui/session/session-flow/queue/take-entries.mjs +2 -1
- package/src/tui/session/session-flow/queue.mjs +1 -1
- package/src/tui/session/tool-approval.mjs +17 -8
- package/src/tui/session/transcript-store/streaming-tail.mjs +1 -1
- package/src/tui/session/tui-steering-persist.mjs +3 -5
- package/src/tui/session/turn-quiet.mjs +53 -0
- package/src/tui/session/turn-stream.mjs +5 -1
- package/src/tui/session/turn.mjs +38 -31
- package/src/tui/session/worktree-setup-notice.mjs +38 -0
- package/src/tui/spinner-meta.mjs +10 -0
- package/src/tui/spinner-verbs.mjs +3 -0
- package/src/ui/statusline.mjs +13 -3
- package/src/runtime/agent/orchestrator/providers/model-pricing-rates.mjs +0 -1
- package/src/runtime/agent/orchestrator/session/manager/foreign-pending-messages.mjs +0 -346
- package/src/runtime/agent/orchestrator/session/manager/session-owner-liveness.mjs +0 -117
- package/src/runtime/agent/orchestrator/session/store/paths-heartbeat.mjs +0 -302
- package/src/session-runtime/services/agent-tool/terminal-reaper/reap-timers.mjs +0 -142
- package/src/session-runtime/turn/share-ops.mjs +0 -77
- package/src/tui/session/cross-surface-share.mjs +0 -406
- package/src/tui/session/live-share/delta.mjs +0 -153
- package/src/tui/session/live-share/mirror.mjs +0 -106
- package/src/tui/session/live-share/owner.mjs +0 -254
- package/src/tui/session/live-share/retry.mjs +0 -54
- package/src/tui/session/live-share/viewer/frame-router.mjs +0 -32
- package/src/tui/session/live-share/viewer/pipe-client.mjs +0 -108
- package/src/tui/session/live-share/viewer/submit-acks.mjs +0 -65
- package/src/tui/session/live-share/viewer/sync-waiters.mjs +0 -53
- package/src/tui/session/live-share/viewer.mjs +0 -99
- package/src/tui/session/live-share/wire.mjs +0 -44
- package/src/tui/session/live-share.mjs +0 -154
- package/src/tui/session/shared-dir-watch.mjs +0 -69
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { providerErrorKind } from '../agent/orchestrator/providers/provider-error-kind.mjs';
|
|
2
|
+
import { readStreamOutcome } from '../agent/orchestrator/providers/lib/stream-outcome.mjs';
|
|
3
|
+
|
|
4
|
+
const ELIGIBLE = new Set(['rate_limited', 'quota_exhausted', 'overloaded', 'transient']);
|
|
5
|
+
|
|
6
|
+
export function canFallbackRoute(error, { signal, outcome = readStreamOutcome(error), committed = false } = {}) {
|
|
7
|
+
if (signal?.aborted || committed || outcome.userAbort || outcome.stallObserved) return false;
|
|
8
|
+
if (
|
|
9
|
+
!outcome.replaySafe ||
|
|
10
|
+
outcome.observedOutput ||
|
|
11
|
+
outcome.toolCallsStarted ||
|
|
12
|
+
outcome.pendingToolInput ||
|
|
13
|
+
outcome.continuation ||
|
|
14
|
+
outcome.terminalObserved
|
|
15
|
+
)
|
|
16
|
+
return false;
|
|
17
|
+
const { kind, status } = providerErrorKind(error);
|
|
18
|
+
if (!ELIGIBLE.has(kind)) return false;
|
|
19
|
+
// A rejected HTTP request or a provider account-pool refusal proves no
|
|
20
|
+
// generation started. A transport disconnect alone does not.
|
|
21
|
+
return status === 429 || status === 503 || (kind === 'quota_exhausted' && error?.quotaExhausted != null);
|
|
22
|
+
}
|
|
@@ -25,3 +25,15 @@ export function isPidAlive(value) {
|
|
|
25
25
|
return error?.code === 'EPERM';
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
|
+
|
|
29
|
+
/** Destructive operations require proof of death, not merely a failed probe. */
|
|
30
|
+
export function isPidAliveOrUnknown(value) {
|
|
31
|
+
const pid = parsePid(value);
|
|
32
|
+
if (!pid) return true;
|
|
33
|
+
try {
|
|
34
|
+
process.kill(pid, 0);
|
|
35
|
+
return true;
|
|
36
|
+
} catch (error) {
|
|
37
|
+
return error?.code !== 'ESRCH';
|
|
38
|
+
}
|
|
39
|
+
}
|
|
@@ -76,6 +76,12 @@ export function resolveProviderApiKey(provider, config) {
|
|
|
76
76
|
return config?.apiKey || getAgentApiKey(provider) || '';
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
/** Presence twin of resolveProviderApiKey, same precedence, no key value read. */
|
|
80
|
+
export function hasProviderApiKey(provider, config) {
|
|
81
|
+
if (config?.apiKeyEnv) return Boolean(process.env[config.apiKeyEnv]);
|
|
82
|
+
return Boolean(config?.apiKey) || hasAgentApiKey(provider);
|
|
83
|
+
}
|
|
84
|
+
|
|
79
85
|
/** Auth-error reload: the stored/environment key is re-read first so a
|
|
80
86
|
* rotated key beats the stale one captured in config. */
|
|
81
87
|
export function reloadProviderApiKey(provider, config) {
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// The phase a provider timeout names on its `timeoutPhase` field. A provider
|
|
2
|
+
// stamps it where the timer that fired is known; retry policy and error
|
|
3
|
+
// presentation read it instead of matching the timeout's message text.
|
|
4
|
+
export const TIMEOUT_PHASE = Object.freeze({
|
|
5
|
+
// The response headers did not arrive in the first-byte window.
|
|
6
|
+
HEADERS: 'headers',
|
|
7
|
+
// The headers arrived, the first chunk did not.
|
|
8
|
+
FIRST_CHUNK: 'first-chunk',
|
|
9
|
+
// A stream that already produced output went silent.
|
|
10
|
+
IDLE: 'idle',
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
/** A timeout before the model produced anything: headers or first chunk. */
|
|
14
|
+
export function isFirstByteTimeout(err) {
|
|
15
|
+
const phase = err?.timeoutPhase;
|
|
16
|
+
return phase === TIMEOUT_PHASE.HEADERS || phase === TIMEOUT_PHASE.FIRST_CHUNK;
|
|
17
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type QuietWaitKind = 'provider' | 'thinking' | 'model' | 'tool' | 'slot' | 'cancel' | '';
|
|
2
|
+
|
|
3
|
+
export const MODEL_THINKING_STAGE: 'model_thinking';
|
|
4
|
+
export function awaitingModelResponse(liveness: unknown): boolean;
|
|
5
|
+
export function displayProgressStage(liveness: unknown): string | null;
|
|
6
|
+
|
|
7
|
+
export interface QuietProgress {
|
|
8
|
+
since: number;
|
|
9
|
+
wait: QuietWaitKind;
|
|
10
|
+
tool?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface QuietProgressParts {
|
|
14
|
+
quietMs: number;
|
|
15
|
+
minutes: number;
|
|
16
|
+
wait: QuietWaitKind;
|
|
17
|
+
tool: string;
|
|
18
|
+
escalated: boolean;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const QUIET_PROGRESS_CUE_MS: number;
|
|
22
|
+
export function lastProgressAt(liveness: unknown): number;
|
|
23
|
+
export function quietWaitKind(stage: unknown): QuietWaitKind;
|
|
24
|
+
export function quietProgress(options: {
|
|
25
|
+
progressAt: unknown;
|
|
26
|
+
stage: unknown;
|
|
27
|
+
tool?: unknown;
|
|
28
|
+
now?: number;
|
|
29
|
+
}): QuietProgress | null;
|
|
30
|
+
export function quietProgressParts(
|
|
31
|
+
quiet: { since?: unknown; wait?: unknown; tool?: unknown } | null | undefined,
|
|
32
|
+
now: number,
|
|
33
|
+
warnMs?: unknown
|
|
34
|
+
): QuietProgressParts | null;
|
|
35
|
+
export function quietProgressLabel(parts: QuietProgressParts | null): string;
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// Quiet-time cue for a running turn: how long it has gone without progress
|
|
2
|
+
// the stall watchdog counts, and what it is waiting on. Display only — the
|
|
3
|
+
// watchdog's abort policy lives in stall-policy.mjs and is never read or
|
|
4
|
+
// changed here. Shared by the TUI spinner, the desktop live status line and
|
|
5
|
+
// the desktop agent rows, so every surface says the same thing.
|
|
6
|
+
|
|
7
|
+
/** A running turn with no counted progress for this long reads "quiet".
|
|
8
|
+
* Lenient on purpose: effort-mode models can think for minutes without a
|
|
9
|
+
* single semantic delta (stall-policy.mjs, the semantic-idle notes), so the
|
|
10
|
+
* cue stays neutral text; only the watchdog's own warn point escalates it. */
|
|
11
|
+
export const QUIET_PROGRESS_CUE_MS = 120_000;
|
|
12
|
+
|
|
13
|
+
/** The newest progress stamp of a runtime liveness entry or progress
|
|
14
|
+
* snapshot: the same signals the stall watchdog counts (stage changes,
|
|
15
|
+
* semantic stream deltas, tool starts and tool heartbeats, the turn start). */
|
|
16
|
+
export function lastProgressAt(liveness) {
|
|
17
|
+
if (!liveness || typeof liveness !== 'object') return 0;
|
|
18
|
+
return Math.max(
|
|
19
|
+
Number(liveness.lastProgressAt) || 0,
|
|
20
|
+
Number(liveness.lastStreamDeltaAt) || 0,
|
|
21
|
+
Number(liveness.toolStartedAt) || 0,
|
|
22
|
+
Number(liveness.askStartedAt) || 0
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** The stage a provider reports once its request is on the wire and the
|
|
27
|
+
* response headers are still pending. The runtime keeps the turn in
|
|
28
|
+
* `requesting` and records the send time (`requestSentAt`); the display stage
|
|
29
|
+
* below reads "model thinking" from that record. */
|
|
30
|
+
export const MODEL_THINKING_STAGE = 'model_thinking';
|
|
31
|
+
|
|
32
|
+
/** Request sent, response headers (transport) not yet received — read from
|
|
33
|
+
* a runtime liveness entry or progress snapshot. */
|
|
34
|
+
export function awaitingModelResponse(liveness) {
|
|
35
|
+
if (!liveness || typeof liveness !== 'object' || liveness.stage !== 'requesting') return false;
|
|
36
|
+
const sentAt = Number(liveness.requestSentAt) || 0;
|
|
37
|
+
return sentAt > 0 && !((Number(liveness.lastTransportAt) || 0) >= sentAt);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** The stage a surface shows: `model_thinking` while the request waits for
|
|
41
|
+
* its headers, otherwise the runtime stage. */
|
|
42
|
+
export function displayProgressStage(liveness) {
|
|
43
|
+
if (awaitingModelResponse(liveness)) return MODEL_THINKING_STAGE;
|
|
44
|
+
return typeof liveness?.stage === 'string' ? liveness.stage : null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const WAIT_BY_STAGE = {
|
|
48
|
+
connecting: 'provider',
|
|
49
|
+
requesting: 'provider',
|
|
50
|
+
[MODEL_THINKING_STAGE]: 'thinking',
|
|
51
|
+
streaming: 'model',
|
|
52
|
+
tool_running: 'tool',
|
|
53
|
+
resource_wait: 'slot',
|
|
54
|
+
cancelling: 'cancel',
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/** What a quiet turn waits on, from its runtime stage ('' when unknown). */
|
|
58
|
+
export function quietWaitKind(stage) {
|
|
59
|
+
return WAIT_BY_STAGE[String(stage || '')] || '';
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The quiet state of a running turn at `now`, or null while it is not
|
|
63
|
+
* quiet. `since` is the frozen progress stamp, so the value stays identical
|
|
64
|
+
* for the whole silence and publishing it costs one update per silence. */
|
|
65
|
+
export function quietProgress({ progressAt, stage, tool = null, now = Date.now() }) {
|
|
66
|
+
const since = Number(progressAt) || 0;
|
|
67
|
+
if (!since || now - since < QUIET_PROGRESS_CUE_MS) return null;
|
|
68
|
+
const wait = quietWaitKind(stage);
|
|
69
|
+
return { since, wait, ...(wait === 'tool' && tool ? { tool: String(tool) } : {}) };
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Display parts of a quiet state at `now`: whole quiet minutes, and whether
|
|
73
|
+
* the silence reached the watchdog's warn point (`warnMs`, 0 = none). */
|
|
74
|
+
export function quietProgressParts(quiet, now, warnMs = 0) {
|
|
75
|
+
const since = Number(quiet?.since) || 0;
|
|
76
|
+
if (!since) return null;
|
|
77
|
+
const quietMs = Math.max(0, now - since);
|
|
78
|
+
if (quietMs < QUIET_PROGRESS_CUE_MS) return null;
|
|
79
|
+
return {
|
|
80
|
+
quietMs,
|
|
81
|
+
minutes: Math.floor(quietMs / 60_000),
|
|
82
|
+
wait: String(quiet.wait || ''),
|
|
83
|
+
tool: typeof quiet.tool === 'string' ? quiet.tool : '',
|
|
84
|
+
escalated: Number(warnMs) > 0 && quietMs >= Number(warnMs),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const WAIT_TEXT = {
|
|
89
|
+
provider: 'waiting on provider',
|
|
90
|
+
thinking: 'model thinking',
|
|
91
|
+
model: 'waiting on model',
|
|
92
|
+
slot: 'waiting for a slot',
|
|
93
|
+
cancel: 'stopping',
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
/** The TUI's lowercase English cue: `quiet for 2m · waiting on provider`. */
|
|
97
|
+
export function quietProgressLabel(parts) {
|
|
98
|
+
if (!parts) return '';
|
|
99
|
+
const wait = parts.wait === 'tool' ? `running ${parts.tool || 'a tool'}` : WAIT_TEXT[parts.wait] || '';
|
|
100
|
+
return [`quiet for ${parts.minutes}m`, wait].filter(Boolean).join(' · ');
|
|
101
|
+
}
|
|
@@ -131,6 +131,32 @@ export function listQuotaResumeEntries() {
|
|
|
131
131
|
return [...load().entries.values()];
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
/** Rekey a proven legacy execution and its Goal together; invalidate old fires. */
|
|
135
|
+
export function rekeyQuotaResumeTurn(sessionId, fromTurnKey, toTurnKey) {
|
|
136
|
+
const current = load();
|
|
137
|
+
const id = text(sessionId);
|
|
138
|
+
const from = text(fromTurnKey);
|
|
139
|
+
const to = text(toTurnKey);
|
|
140
|
+
if (!id || !from || !to || from === to) return false;
|
|
141
|
+
const entry = current.entries.get(id);
|
|
142
|
+
const suspension = current.suspensions.get(id);
|
|
143
|
+
const moveEntry = entry?.turnKey === from;
|
|
144
|
+
const moveGoal = suspension?.turnKey === from;
|
|
145
|
+
if (!moveEntry && !moveGoal) return false;
|
|
146
|
+
const next = {
|
|
147
|
+
...current,
|
|
148
|
+
generation: current.generation + Number(moveEntry),
|
|
149
|
+
entries: new Map(current.entries),
|
|
150
|
+
suspensions: new Map(current.suspensions),
|
|
151
|
+
};
|
|
152
|
+
if (moveEntry) next.entries.set(id, normalizeEntry({ ...entry, turnKey: to, generation: next.generation }));
|
|
153
|
+
if (moveGoal) next.suspensions.set(id, normalizeSuspension({ ...suspension, turnKey: to }));
|
|
154
|
+
persist(next);
|
|
155
|
+
state = next;
|
|
156
|
+
notify(id);
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
134
160
|
/** The session snapshot's `quotaResume` value for an entry (or null). */
|
|
135
161
|
export function publicQuotaResume(entry) {
|
|
136
162
|
if (!entry) return null;
|
|
@@ -244,7 +270,12 @@ export function isAgentOnlySession(session) {
|
|
|
244
270
|
const lower = (value) => text(value).toLowerCase();
|
|
245
271
|
if (lower(session.visibility) === 'agent-only') return true;
|
|
246
272
|
const parentId = text(session.parentSessionId || session.ownerSessionId);
|
|
247
|
-
return
|
|
273
|
+
return (
|
|
274
|
+
lower(session.owner) === 'agent' &&
|
|
275
|
+
lower(session.agent) !== 'lead' &&
|
|
276
|
+
parentId !== '' &&
|
|
277
|
+
parentId !== text(session.id)
|
|
278
|
+
);
|
|
248
279
|
}
|
|
249
280
|
|
|
250
281
|
/** Global setting, default on. */
|
|
@@ -61,3 +61,47 @@ export function readRowsForDisplay(text) {
|
|
|
61
61
|
}
|
|
62
62
|
return out.join('\n');
|
|
63
63
|
}
|
|
64
|
+
|
|
65
|
+
/** Decode one contiguous model-facing read window, never display metadata.
|
|
66
|
+
* Unknown or disjoint views are not safe substitutes for file text. Per-line
|
|
67
|
+
* clipping is not visible in the text: the read executor reports it
|
|
68
|
+
* structurally, never inferred from row content.
|
|
69
|
+
* The display format does not preserve original byte size or final newline.
|
|
70
|
+
*/
|
|
71
|
+
export function readSourceWindow(text) {
|
|
72
|
+
if (typeof text !== 'string') return null;
|
|
73
|
+
if (/^<system-reminder>File exists but has empty contents: [^\n]*<\/system-reminder>$/.test(text))
|
|
74
|
+
return { content: '', totalLines: 0, startLine: 1, endLine: 0, truncated: false };
|
|
75
|
+
const emptyRange = /^\(no lines in range; file has (\d+) lines\)$/.exec(text);
|
|
76
|
+
if (emptyRange)
|
|
77
|
+
return { content: '', totalLines: Number(emptyRange[1]), startLine: null, endLine: null, truncated: false };
|
|
78
|
+
if (!RUN_MARKER_RE.test(text.split('\n', 1)[0])) return null;
|
|
79
|
+
const rows = [];
|
|
80
|
+
let totalLines;
|
|
81
|
+
let footer = null;
|
|
82
|
+
for (const line of readRowsForDisplay(text).split('\n')) {
|
|
83
|
+
const row = /^(\d+)→([\s\S]*)$/.exec(line);
|
|
84
|
+
if (row) {
|
|
85
|
+
if (footer) return null;
|
|
86
|
+
const number = Number(row[1]);
|
|
87
|
+
if (rows.length && number !== rows.at(-1).number + 1) return null;
|
|
88
|
+
rows.push({ number, content: row[2] });
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
const metadata = /^\[lines (\d+)-(\d+) of (\d+)(?:; pass offset:\d+ to continue)?\]$/.exec(line);
|
|
92
|
+
if (!metadata || footer) return null;
|
|
93
|
+
footer = metadata;
|
|
94
|
+
totalLines = Number(metadata[3]);
|
|
95
|
+
}
|
|
96
|
+
if (!rows.length) return null;
|
|
97
|
+
const startLine = rows[0].number;
|
|
98
|
+
const endLine = rows.at(-1).number;
|
|
99
|
+
if (footer && (Number(footer[1]) !== startLine || Number(footer[2]) !== endLine || totalLines < endLine)) return null;
|
|
100
|
+
return {
|
|
101
|
+
content: rows.map((row) => row.content).join('\n'),
|
|
102
|
+
...(totalLines === undefined ? {} : { totalLines }),
|
|
103
|
+
startLine,
|
|
104
|
+
endLine,
|
|
105
|
+
truncated: totalLines === undefined || endLine < totalLines,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
@@ -1,47 +1,7 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
// The daemon's channel transport OWNS this file (writes/clears it); everything
|
|
4
|
-
// else only asks one question: "which session is pinned to the channel?".
|
|
5
|
-
// That session reclaims its link on boot
|
|
6
|
-
// instead of waiting for a manual toggle — the reconnect path that used to
|
|
7
|
-
// look like the channel had silently unlinked itself.
|
|
8
|
-
import { readFileSync } from 'node:fs';
|
|
9
|
-
import { join, resolve } from 'node:path';
|
|
1
|
+
// Path of the durable channel-link record (channel-remote-intent.json).
|
|
2
|
+
import { join } from 'node:path';
|
|
10
3
|
import { resolveRuntimeRoot } from './runtime-root.mjs';
|
|
11
4
|
|
|
12
5
|
export function remoteIntentPath(root = resolveRuntimeRoot()) {
|
|
13
6
|
return join(root, 'channel-remote-intent.json');
|
|
14
7
|
}
|
|
15
|
-
|
|
16
|
-
export function readRemoteIntent(path = remoteIntentPath()) {
|
|
17
|
-
try {
|
|
18
|
-
const value = JSON.parse(readFileSync(path, 'utf8'));
|
|
19
|
-
const sessionId = String(value?.sessionId || '').trim();
|
|
20
|
-
const transcriptPath = String(value?.transcriptPath || '').trim();
|
|
21
|
-
if (!/^[A-Za-z0-9_-]+$/.test(sessionId) || !transcriptPath) return null;
|
|
22
|
-
return {
|
|
23
|
-
sessionId,
|
|
24
|
-
transcriptPath,
|
|
25
|
-
cwd: value?.cwd == null ? null : String(value.cwd),
|
|
26
|
-
};
|
|
27
|
-
} catch {
|
|
28
|
-
return null;
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// cwd is compared only when the record carries one: the intent is session
|
|
33
|
-
// pinned, and a resumed session keeps its id across restarts.
|
|
34
|
-
export function remoteIntentMatchesSession(sessionId, cwd = null, path = undefined) {
|
|
35
|
-
const id = String(sessionId || '').trim();
|
|
36
|
-
if (!id) return false;
|
|
37
|
-
const intent = readRemoteIntent(path);
|
|
38
|
-
if (!intent || intent.sessionId !== id) return false;
|
|
39
|
-
if (intent.cwd && cwd) {
|
|
40
|
-
try {
|
|
41
|
-
if (resolve(intent.cwd) !== resolve(cwd)) return false;
|
|
42
|
-
} catch {
|
|
43
|
-
return false;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return true;
|
|
47
|
-
}
|
|
@@ -64,7 +64,14 @@ export function createSlowSpans({
|
|
|
64
64
|
span.end = now();
|
|
65
65
|
span.ms = span.end - span.start;
|
|
66
66
|
if (span.ms >= minMs) {
|
|
67
|
-
ring.push({
|
|
67
|
+
ring.push({
|
|
68
|
+
label: span.label,
|
|
69
|
+
start: span.start,
|
|
70
|
+
end: span.end,
|
|
71
|
+
ms: span.ms,
|
|
72
|
+
phases: span.phases,
|
|
73
|
+
size: span.size,
|
|
74
|
+
});
|
|
68
75
|
if (ring.length > capacity) ring.shift();
|
|
69
76
|
}
|
|
70
77
|
if (span.ms >= slowMs) {
|
|
@@ -15,6 +15,11 @@ import { join } from 'node:path';
|
|
|
15
15
|
import { formatElapsed } from './statusline-format.mjs';
|
|
16
16
|
import { positiveInt } from '../numbers.mjs';
|
|
17
17
|
import { isPidAlive } from '../pid-liveness.mjs';
|
|
18
|
+
import { workspaceSees } from '../worktrees/workspace-visibility.mjs';
|
|
19
|
+
|
|
20
|
+
/** Whether an orphaned held shell (`status.orphaned`) belongs in the list of a
|
|
21
|
+
* session working in `sessionCwd`. */
|
|
22
|
+
export const shellJobVisibleFrom = (job, sessionCwd) => workspaceSees(job?.cwd, sessionCwd);
|
|
18
23
|
|
|
19
24
|
const DEFAULT_MIXDOG_HOME = process.env.MIXDOG_HOME || join(homedir(), '.mixdog');
|
|
20
25
|
const DEFAULT_STANDALONE_DATA_DIR = join(DEFAULT_MIXDOG_HOME, 'data');
|
|
@@ -87,6 +92,12 @@ export function shellJobsStatus({ clientHostPid, sessionId } = {}) {
|
|
|
87
92
|
return (scope && sessions?.[scope]) || EMPTY_SHELL_JOBS_SESSION;
|
|
88
93
|
}
|
|
89
94
|
|
|
95
|
+
/** Listed jobs held only because their termination is unconfirmed. */
|
|
96
|
+
export function unconfirmedShellCount(status) {
|
|
97
|
+
const jobs = Array.isArray(status?.jobs) ? status.jobs : [];
|
|
98
|
+
return jobs.filter((job) => job?.terminationUnconfirmed === true).length;
|
|
99
|
+
}
|
|
100
|
+
|
|
90
101
|
// Awaited fresh scan for pollers that own their cadence (desktop): the result
|
|
91
102
|
// reflects the disk at call time instead of the previous poll. The CLI render
|
|
92
103
|
// path keeps using the cache-only shellJobsStatus.
|
|
@@ -171,6 +182,7 @@ async function refreshShellJobsStatus(ownerPid) {
|
|
|
171
182
|
let count = 0;
|
|
172
183
|
let oldestMs = Infinity;
|
|
173
184
|
const jobs = [];
|
|
185
|
+
const orphaned = [];
|
|
174
186
|
// sessionId -> { count, oldestMs, jobs }. Jobs with no session stamp (legacy
|
|
175
187
|
// records, plain CLI runs) still count toward the owner total but belong to
|
|
176
188
|
// no pane, so they never light up a session's indicator.
|
|
@@ -183,11 +195,16 @@ async function refreshShellJobsStatus(ownerPid) {
|
|
|
183
195
|
} catch {
|
|
184
196
|
continue;
|
|
185
197
|
}
|
|
186
|
-
|
|
198
|
+
// A killed command whose processes are not confirmed gone has already
|
|
199
|
+
// reported its result, so its tool card is closed: it is listed until
|
|
200
|
+
// its owner retires the record. Its root pid may be gone while a
|
|
201
|
+
// descendant lives, so the owner's liveness decides, not the root's.
|
|
202
|
+
const unconfirmed = detail?.terminationUnconfirmed === true;
|
|
203
|
+
if (unconfirmed ? !isPidAlive(ownerByJob.get(id) || 0) : !(await isShellJobAlive(detail, p, dir, id))) continue;
|
|
187
204
|
// A foreground command is awaited in its own tool card; listing it here
|
|
188
205
|
// made every short command flash in and out of the shell list. Only
|
|
189
206
|
// backgrounded jobs (including promoted ones) are background work.
|
|
190
|
-
if (detail?.foreground === true) continue;
|
|
207
|
+
if (detail?.foreground === true && !unconfirmed) continue;
|
|
191
208
|
// Owner-wide totals stay this process's own; session buckets take every
|
|
192
209
|
// live job regardless of which host published it.
|
|
193
210
|
const ownedByThisHost = ownerByJob.get(id) === ownerPid;
|
|
@@ -204,8 +221,12 @@ async function refreshShellJobsStatus(ownerPid) {
|
|
|
204
221
|
command: String(detail?.command || '').trim(),
|
|
205
222
|
cwd: String(detail?.cwd || '').trim(),
|
|
206
223
|
startedAt: detail?.startedAt || (Number.isFinite(jobMs) ? jobMs : null),
|
|
224
|
+
...(unconfirmed ? { terminationUnconfirmed: true } : {}),
|
|
207
225
|
};
|
|
208
226
|
if (ownedByThisHost) jobs.push(job);
|
|
227
|
+
// Held by a session that has closed: every session that sees its
|
|
228
|
+
// folder lists it (shellJobVisibleFrom), not only the closed owner.
|
|
229
|
+
if (unconfirmed && detail?.ownerSessionClosed === true) orphaned.push(job);
|
|
209
230
|
if (!owner) continue;
|
|
210
231
|
const bucket = bySession.get(owner) || { count: 0, oldestMs: Infinity, jobs: [], finished: [] };
|
|
211
232
|
bucket.count += 1;
|
|
@@ -244,6 +265,7 @@ async function refreshShellJobsStatus(ownerPid) {
|
|
|
244
265
|
// count stays 0 (its publisher is a shard, not this host), so an empty
|
|
245
266
|
// aggregate must NOT discard the session buckets.
|
|
246
267
|
if (count || bySession.size) value = summarizeShellJobs({ count, oldestMs, jobs, bySession });
|
|
268
|
+
if (orphaned.length) value = { ...value, orphaned };
|
|
247
269
|
} catch {
|
|
248
270
|
value = EMPTY_SHELL_JOBS;
|
|
249
271
|
}
|
|
@@ -24,7 +24,8 @@ function headerLabel(base, surface, display, status, detail) {
|
|
|
24
24
|
// Bridge load_tool describes definitions; it never loads a tool.
|
|
25
25
|
const described =
|
|
26
26
|
normalizedName === 'load_tool' && !headerPending ? bridgeDescribedToolNames(display.displayedResultText) : null;
|
|
27
|
-
if (described)
|
|
27
|
+
if (described)
|
|
28
|
+
return described.length ? `Described ${described.map(displayToolSearchTarget).join(', ')}` : 'Described';
|
|
28
29
|
return (
|
|
29
30
|
(isAgentTool(normalizedName) ? agentActionTitle(parsedArgs, display.displayedResultText) : '') ||
|
|
30
31
|
formatToolActionHeader(name, args, {
|
|
@@ -640,8 +640,22 @@ function summarizeAgentResult({ text, trimmed, translate }) {
|
|
|
640
640
|
|
|
641
641
|
const summarizeGeneric = ({ text, translate }) => summarizeGenericResult(text, translate);
|
|
642
642
|
|
|
643
|
-
const summarizeAskUserResult = ({ text }) => {
|
|
644
|
-
|
|
643
|
+
const summarizeAskUserResult = ({ text, translate }) => {
|
|
644
|
+
// A composer message answered: `User replied in chat: "<json>". ` then the
|
|
645
|
+
// picks sentence or only the closing one. Summarized as the reply itself.
|
|
646
|
+
const replied = /^User replied in chat: ("(?:[^"\\]|\\.)*")\. /s.exec(String(text ?? '').trim());
|
|
647
|
+
if (replied) {
|
|
648
|
+
let reply = '';
|
|
649
|
+
try {
|
|
650
|
+
reply = String(JSON.parse(replied[1]));
|
|
651
|
+
} catch {}
|
|
652
|
+
const label = translate('Answered in chat', 'Answered in chat', {});
|
|
653
|
+
return truncateSingleLine(reply ? `${label}: ${reply}` : label, AGENT_SURFACE_BRIEF_MAX);
|
|
654
|
+
}
|
|
655
|
+
const answers =
|
|
656
|
+
/^User has answered your questions:\s*(.*?)(?:\s*You can now continue with these answers in mind\.)?$/s.exec(
|
|
657
|
+
String(text ?? '').trim()
|
|
658
|
+
);
|
|
645
659
|
return truncateSingleLine(answers ? answers[1] : String(text ?? '').trim(), AGENT_SURFACE_BRIEF_MAX) || null;
|
|
646
660
|
};
|
|
647
661
|
|
|
@@ -49,9 +49,10 @@ export function isUserControlCancellation(text) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
// A non-zero exit the tool itself marked as a signal (search no-match, a
|
|
52
|
-
// `git diff --exit-code` difference
|
|
52
|
+
// `git diff --exit-code` difference, a `git merge-tree` conflict report) rather
|
|
53
|
+
// than a command failure.
|
|
53
54
|
export function hasBenignExitOutcome(text) {
|
|
54
|
-
return /^\[outcome:\s*(?:no-match|no-change)\]\s*$/im.test(String(text || ''));
|
|
55
|
+
return /^\[outcome:\s*(?:no-match|no-change|conflicts)\]\s*$/im.test(String(text || ''));
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
// The status a task/bridge envelope reports in its leading block. A `<status>`
|
|
@@ -452,9 +452,8 @@ function summarizeGithub(a, max) {
|
|
|
452
452
|
const id = a.number ?? a.id;
|
|
453
453
|
const hasId = id != null && String(id).trim() !== '';
|
|
454
454
|
const title = truncateToolText(firstText(a.title, a.workflow, a.tag), Math.min(max, 60));
|
|
455
|
-
return
|
|
456
|
-
|
|
457
|
-
: compactParts([repo ? `${phrase} ${repo}` : phrase, title]);
|
|
455
|
+
if (hasId) return compactParts([`${phrase} ${String(id).trim()}`, repo, title]);
|
|
456
|
+
return compactParts([repo ? `${phrase} ${repo}` : phrase, title]);
|
|
458
457
|
}
|
|
459
458
|
|
|
460
459
|
export function formatToolSurface(name, args, opts = {}) {
|
|
@@ -344,12 +344,18 @@ function officeUnit(a) {
|
|
|
344
344
|
|
|
345
345
|
function visualizeUnit(a) {
|
|
346
346
|
const action = String(a.action || '').toLowerCase();
|
|
347
|
-
if (action === 'capture')
|
|
348
|
-
|
|
349
|
-
if (action === '
|
|
350
|
-
|
|
351
|
-
if (action === '
|
|
352
|
-
|
|
347
|
+
if (action === 'capture')
|
|
348
|
+
return unitDescriptor('Visualize', { count: 1, active: 'Capturing', done: 'Captured', noun: 'screenshot' });
|
|
349
|
+
if (action === 'export')
|
|
350
|
+
return unitDescriptor('Visualize', { count: 1, active: 'Exporting', done: 'Exported', noun: 'file' });
|
|
351
|
+
if (action === 'annotate')
|
|
352
|
+
return unitDescriptor('Visualize', { count: 1, active: 'Marking', done: 'Marked', noun: 'annotation' });
|
|
353
|
+
if (action === 'check')
|
|
354
|
+
return unitDescriptor('Visualize', { count: 1, active: 'Checking', done: 'Checked', noun: 'page' });
|
|
355
|
+
if (action === 'inspect')
|
|
356
|
+
return unitDescriptor('Visualize', { count: 1, active: 'Inspecting', done: 'Inspected', noun: 'page' });
|
|
357
|
+
if (action === 'close')
|
|
358
|
+
return unitDescriptor('Visualize', { count: 1, active: 'Closing', done: 'Closed', noun: 'tab' });
|
|
353
359
|
return unitDescriptor('Visualize', { count: 1, active: 'Showing', done: 'Showed', noun: 'artifact' });
|
|
354
360
|
}
|
|
355
361
|
|
|
@@ -592,14 +598,10 @@ function cwdUnit(a) {
|
|
|
592
598
|
}
|
|
593
599
|
if (action === 'favorite' || action === 'archive') {
|
|
594
600
|
const off = a.value === false;
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
: ['Favoriting', 'Favorited']
|
|
600
|
-
: off
|
|
601
|
-
? ['Unarchiving', 'Unarchived']
|
|
602
|
-
: ['Archiving', 'Archived'];
|
|
601
|
+
let words;
|
|
602
|
+
if (action === 'favorite') words = off ? ['Unfavoriting', 'Unfavorited'] : ['Favoriting', 'Favorited'];
|
|
603
|
+
else words = off ? ['Unarchiving', 'Unarchived'] : ['Archiving', 'Archived'];
|
|
604
|
+
const [active, done] = words;
|
|
603
605
|
return unitDescriptor('Setup', { active, done, noun: 'session', effect: true });
|
|
604
606
|
}
|
|
605
607
|
if (action.endsWith('-worktree') || action.endsWith('-worktrees')) {
|
|
@@ -613,7 +615,12 @@ function cwdUnit(a) {
|
|
|
613
615
|
if (action === 'status') {
|
|
614
616
|
return unitDescriptor('Setup', { active: 'Checking', done: 'Checked', noun: 'session' });
|
|
615
617
|
}
|
|
616
|
-
return unitDescriptor('Setup', {
|
|
618
|
+
return unitDescriptor('Setup', {
|
|
619
|
+
active: 'Checking',
|
|
620
|
+
done: 'Checked',
|
|
621
|
+
noun: 'working directory',
|
|
622
|
+
pluralNoun: 'working directories',
|
|
623
|
+
});
|
|
617
624
|
}
|
|
618
625
|
|
|
619
626
|
const TOOL_UNITS = new Map([
|
|
@@ -21,6 +21,37 @@ export function createTranscriptRouteMetadata(session, route = {}, at = Date.now
|
|
|
21
21
|
return { at, ...transcriptRouteMetadataFields({ modelId, model, provider, agent }) };
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
+
const ATTACHMENT_ID = /^[a-f0-9]{64}$/;
|
|
25
|
+
const ATTACHMENT_KINDS = new Set(['image', 'file', 'text']);
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The attachments a user message carries (`meta.transcript.attachments`):
|
|
29
|
+
* `[{ id, attachmentRef, kind, name, mimeType, size }]`, the id being the
|
|
30
|
+
* content hash under which the attachment store keeps the bytes. Anything that
|
|
31
|
+
* is not a well-formed record is dropped, so a row never names an id the store
|
|
32
|
+
* could not hold. Each record repeats the hash under `attachmentRef` — the
|
|
33
|
+
* key the store's census marks as referenced.
|
|
34
|
+
*/
|
|
35
|
+
export function normalizeMessageAttachments(value) {
|
|
36
|
+
if (!Array.isArray(value)) return [];
|
|
37
|
+
const seen = new Set();
|
|
38
|
+
const out = [];
|
|
39
|
+
for (const raw of value) {
|
|
40
|
+
const id = typeof raw?.id === 'string' ? raw.id : '';
|
|
41
|
+
if (!ATTACHMENT_ID.test(id) || seen.has(id) || out.length >= 64) continue;
|
|
42
|
+
seen.add(id);
|
|
43
|
+
out.push({
|
|
44
|
+
id,
|
|
45
|
+
attachmentRef: id,
|
|
46
|
+
kind: ATTACHMENT_KINDS.has(raw.kind) ? raw.kind : 'file',
|
|
47
|
+
name: String(raw.name || 'attachment').slice(0, 200),
|
|
48
|
+
mimeType: String(raw.mimeType || 'application/octet-stream').slice(0, 100),
|
|
49
|
+
size: Number.isFinite(Number(raw.size)) && Number(raw.size) >= 0 ? Number(raw.size) : 0,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return out;
|
|
53
|
+
}
|
|
54
|
+
|
|
24
55
|
/** A new stable message id (`meta.transcript.messageId`). */
|
|
25
56
|
export function newTranscriptMessageId() {
|
|
26
57
|
return `msg_${globalThis.crypto.randomUUID().replaceAll('-', '')}`;
|
|
@@ -36,6 +67,7 @@ export function persistedUserTranscriptMetadata(value) {
|
|
|
36
67
|
if (!value || typeof value !== 'object') return null;
|
|
37
68
|
return {
|
|
38
69
|
messageId: stableMessageId(value.messageId),
|
|
70
|
+
...(typeof value.attemptId === 'string' && value.attemptId ? { attemptId: value.attemptId } : {}),
|
|
39
71
|
...(Number.isFinite(Number(value.at)) ? { at: Number(value.at) } : {}),
|
|
40
72
|
...transcriptRouteMetadataFields(value),
|
|
41
73
|
...(typeof value.sender === 'string' && value.sender ? { sender: value.sender } : {}),
|
|
@@ -44,14 +76,30 @@ export function persistedUserTranscriptMetadata(value) {
|
|
|
44
76
|
// The turn's change-record key (the outer prompt id), so a restored user
|
|
45
77
|
// row still addresses its turn's Undo/Reapply.
|
|
46
78
|
...(typeof value.checkpointId === 'string' && value.checkpointId ? { checkpointId: value.checkpointId } : {}),
|
|
79
|
+
...attachmentsField(value.attachments),
|
|
47
80
|
};
|
|
48
81
|
}
|
|
49
82
|
|
|
83
|
+
function attachmentsField(value) {
|
|
84
|
+
const attachments = normalizeMessageAttachments(value);
|
|
85
|
+
return attachments.length ? { attachments } : {};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** The `attachments` field of a transcript row (absent when there are none). */
|
|
89
|
+
export function transcriptAttachmentFields(value) {
|
|
90
|
+
return attachmentsField(value?.attachments);
|
|
91
|
+
}
|
|
92
|
+
|
|
50
93
|
export function persistedAssistantTranscriptMetadata(value, fallbackAt = Date.now()) {
|
|
51
94
|
if (!value || typeof value !== 'object') return null;
|
|
52
95
|
const candidateAt = Number(value.assistantAt);
|
|
53
96
|
const assistantAt = Number.isFinite(candidateAt) && candidateAt > 0 ? candidateAt : fallbackAt;
|
|
54
97
|
value.assistantAt = assistantAt;
|
|
55
98
|
value.assistantMessageId = stableMessageId(value.assistantMessageId);
|
|
56
|
-
return {
|
|
99
|
+
return {
|
|
100
|
+
messageId: value.assistantMessageId,
|
|
101
|
+
at: assistantAt,
|
|
102
|
+
...(typeof value.attemptId === 'string' && value.attemptId ? { attemptId: value.attemptId } : {}),
|
|
103
|
+
...transcriptRouteMetadataFields(value),
|
|
104
|
+
};
|
|
57
105
|
}
|