mixdog 1.3.1 → 1.3.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +8 -4
- package/package.json +8 -8
- package/scripts/native-release-status.mjs +238 -0
- package/scripts/verify-runtime-dependency-versions.mjs +158 -0
- package/src/agents/reviewer/AGENT.md +5 -7
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +9 -1
- package/src/defaults/skills/browser-use/SKILL.md +6 -3
- package/src/defaults/skills/local-provider/SKILL.md +33 -6
- package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
- package/src/defaults/skills/setup/SKILL.md +23 -1
- package/src/defaults/skills/setup/references/actions.md +50 -3
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +7 -3
- package/src/defaults/skills/visualize/SKILL.md +9 -6
- package/src/defaults/skills/visualize/references/craft.md +8 -1
- package/src/help.mjs +1 -1
- package/src/lib/keychain-cjs.cjs +4 -1
- package/src/lib/rules-builder.cjs +14 -7
- package/src/rules/agent-mode/AGENT_MODE.md +8 -5
- package/src/rules/agent-mode/MIXY.md +6 -8
- package/src/rules/lead/LEAD.md +7 -10
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- package/src/rules/shared/45-destructive.md +5 -1
- package/src/rules/shared/60-verification.md +6 -4
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
- package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
- package/src/runtime/agent/orchestrator/config.mjs +11 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +9 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +84 -32
- package/src/runtime/agent/orchestrator/providers/anthropic-max-tokens.mjs +38 -11
- package/src/runtime/agent/orchestrator/providers/anthropic-messages.mjs +15 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +84 -42
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
- package/src/runtime/agent/orchestrator/providers/cursor-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider-config.mjs +14 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
- package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/wire-gateway.mjs +13 -1
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
- package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-runtime.mjs +5 -0
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-turns.mjs +10 -1
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +4 -6
- package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +52 -6
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +35 -13
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +30 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +196 -45
- package/src/runtime/agent/orchestrator/session/attachment-preflight.mjs +80 -0
- package/src/runtime/agent/orchestrator/session/catalog-db/schema.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compact/continuation.mjs +15 -6
- package/src/runtime/agent/orchestrator/session/compact/execution-tail.mjs +45 -19
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/messages.mjs +44 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +185 -37
- package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +240 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +102 -35
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +19 -9
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +199 -109
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/continuation.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/empty-nudge.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/output-limit.mjs +62 -13
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +22 -6
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +33 -8
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/call-prep.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +27 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/manager/agent-listing-notice.mjs +102 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +13 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +27 -14
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-steering-drain.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/idle-cleanup.mjs +32 -1
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +115 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/prompt-utils.mjs +36 -0
- package/src/runtime/agent/orchestrator/session/manager/rules-cache.mjs +40 -1
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-state.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/manager/session-crud.mjs +28 -9
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +4 -9
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +14 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +7 -3
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +52 -11
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +126 -12
- package/src/runtime/agent/orchestrator/session/runtime-user-context.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +16 -3
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/stale-sweep.mjs +5 -4
- package/src/runtime/agent/orchestrator/session/store/sweep/sub-agent-retention.mjs +140 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-plan.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +15 -5
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +49 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +80 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +33 -1
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.d.mts +5 -0
- package/src/runtime/agent/orchestrator/session/store-summary-visibility.mjs +37 -0
- package/src/runtime/agent/orchestrator/session/store.mjs +1 -0
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +35 -5
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +47 -14
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +6 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/git-command-tool.mjs +127 -20
- package/src/runtime/agent/orchestrator/tools/builtin/git-worktree-tool.mjs +557 -0
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +10 -12
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +42 -23
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
- package/src/runtime/attachments/attachment-reference.mjs +155 -0
- package/src/runtime/attachments/legacy-office.mjs +110 -0
- package/src/runtime/attachments/limits.mjs +5 -1
- package/src/runtime/attachments/office-extract.mjs +3 -6
- package/src/runtime/attachments/pdf-extract.mjs +84 -10
- package/src/runtime/attachments/prompt-restore.mjs +70 -0
- package/src/runtime/attachments/store.mjs +109 -33
- package/src/runtime/browser-bridge/client.mjs +73 -20
- package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +14 -7
- package/src/runtime/channels/lib/state-file.mjs +15 -15
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
- package/src/runtime/channels/lib/worker-main.mjs +3 -2
- package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
- package/src/runtime/effort-judge/judge-client.mjs +66 -16
- package/src/runtime/effort-judge/model-install.mjs +47 -7
- package/src/runtime/local-provider/asset-installer.mjs +10 -4
- package/src/runtime/local-provider/catalog.mjs +5 -2
- package/src/runtime/local-provider/registered-models.mjs +64 -27
- package/src/runtime/local-provider/server.mjs +2 -3
- package/src/runtime/media/adapters/image-api.mjs +89 -0
- package/src/runtime/media/auth.mjs +64 -1
- package/src/runtime/media/catalog.mjs +66 -4
- package/src/runtime/media/jobs.mjs +4 -0
- package/src/runtime/media/lanes.mjs +46 -5
- package/src/runtime/media/media-usage.mjs +17 -0
- package/src/runtime/media/renditions.mjs +15 -15
- package/src/runtime/media/store.mjs +36 -35
- package/src/runtime/media/tool.mjs +8 -1
- package/src/runtime/memory/lib/cycle-scheduler/health-ledger.mjs +8 -2
- package/src/runtime/memory/lib/embedding-model-files.mjs +30 -7
- package/src/runtime/memory/lib/ingest/transcript-tail.mjs +16 -7
- package/src/runtime/memory/lib/memory-chunk-quality.mjs +14 -2
- package/src/runtime/memory/lib/pg/process.mjs +8 -4
- package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
- package/src/runtime/office/core/journal.mjs +7 -14
- package/src/runtime/office/design/library/design-library-core.mjs +4 -16
- package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
- package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
- package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
- package/src/runtime/office/pdf/pdf-render.mjs +30 -18
- package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +135 -29
- package/src/runtime/shared/agent-avatar-sprites.mjs +23 -8
- package/src/runtime/shared/agent-avatar.mjs +3 -2
- package/src/runtime/shared/agent-kind-migration.mjs +200 -0
- package/src/runtime/shared/agent-message-envelope.d.mts +6 -0
- package/src/runtime/shared/agent-message-envelope.mjs +44 -0
- package/src/runtime/shared/agent-reap-state.d.mts +4 -0
- package/src/runtime/shared/agent-room-host.d.mts +31 -0
- package/src/runtime/shared/agent-room-host.mjs +58 -0
- package/src/runtime/shared/agent-store.mjs +24 -7
- package/src/runtime/shared/agent-task-starts.mjs +27 -0
- package/src/runtime/shared/atomic-file.d.mts +50 -0
- package/src/runtime/shared/atomic-file.mjs +394 -132
- package/src/runtime/shared/background-tasks.mjs +7 -4
- package/src/runtime/shared/bot-hops.mjs +35 -0
- package/src/runtime/shared/bot-split-migration.mjs +1 -1
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/defaults-separation.mjs +6 -2
- package/src/runtime/shared/delegated-approval-host.mjs +74 -0
- package/src/runtime/shared/err-text.mjs +4 -0
- package/src/runtime/shared/error-presentation.mjs +22 -4
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/process-lifecycle.mjs +8 -12
- package/src/runtime/shared/projects.mjs +50 -41
- package/src/runtime/shared/provider-accounts.mjs +17 -12
- package/src/runtime/shared/redact-directories.mjs +18 -0
- package/src/runtime/shared/remove-directory.mjs +28 -0
- package/src/runtime/shared/routine-trigger.d.mts +6 -1
- package/src/runtime/shared/routine-trigger.mjs +14 -6
- package/src/runtime/shared/run-child.d.mts +3 -0
- package/src/runtime/shared/run-child.mjs +24 -3
- package/src/runtime/shared/session-baseline-store.mjs +8 -2
- package/src/runtime/shared/session-sidebar-host.d.mts +11 -2
- package/src/runtime/shared/session-sidebar-host.mjs +16 -3
- package/src/runtime/shared/singleton-owner.mjs +7 -6
- package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
- package/src/runtime/shared/staged-update.mjs +4 -2
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
- package/src/runtime/shared/tool-execution-contract.mjs +10 -0
- package/src/runtime/shared/tool-work-units.mjs +12 -9
- package/src/runtime/shared/transcript-metadata.mjs +28 -3
- package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
- package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
- package/src/runtime/shared/turn-snapshot.mjs +277 -124
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +92 -61
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +251 -61
- package/src/runtime/shared/worktrees/contract.mjs +11 -3
- package/src/runtime/shared/worktrees/maintenance.mjs +11 -2
- package/src/runtime/shared/worktrees/manager.mjs +419 -52
- package/src/runtime/shared/worktrees/registry.mjs +29 -27
- package/src/runtime/shared/worktrees/tool-host.mjs +39 -0
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/tidy/process.mjs +41 -8
- package/src/runtime/tidy/resolve.mjs +10 -7
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +542 -204
- package/src/session-runtime/agent-profile-changes.mjs +16 -0
- package/src/session-runtime/agent-room-delivery.mjs +81 -35
- package/src/session-runtime/agent-task-sessions.mjs +811 -179
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +48 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +16 -7
- package/src/session-runtime/boot/infrastructure.mjs +14 -2
- package/src/session-runtime/boot/providers.mjs +2 -2
- package/src/session-runtime/boot/shared.mjs +12 -6
- package/src/session-runtime/boot/tools.mjs +23 -3
- package/src/session-runtime/bot-status.mjs +432 -0
- package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
- package/src/session-runtime/config-lifecycle.mjs +60 -55
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- package/src/session-runtime/held-requests.mjs +31 -33
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +24 -26
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- package/src/session-runtime/lifecycle/teardown.mjs +11 -3
- package/src/session-runtime/mcp-glue/connect.mjs +100 -186
- package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
- package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
- package/src/session-runtime/media-api.mjs +5 -2
- package/src/session-runtime/model-recency.mjs +1 -0
- package/src/session-runtime/model-route/route-tuning.mjs +4 -1
- package/src/session-runtime/model-route/set-route.mjs +61 -1
- package/src/session-runtime/owned-automation-run.mjs +8 -1
- package/src/session-runtime/provider-models/row-hydration.mjs +8 -1
- package/src/session-runtime/quick-model-rows/model-meta.mjs +2 -0
- package/src/session-runtime/quick-model-rows/provider-rows.mjs +7 -1
- package/src/session-runtime/route-resolution.mjs +16 -8
- package/src/session-runtime/route-state.mjs +1 -59
- package/src/session-runtime/routine-home-turn.mjs +5 -3
- package/src/session-runtime/runtime-bootstrap.mjs +5 -3
- package/src/session-runtime/runtime-paths.mjs +11 -5
- package/src/session-runtime/runtime-review-api.mjs +4 -1
- package/src/session-runtime/runtime-tunables.mjs +6 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
- package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
- package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
- package/src/session-runtime/services/agent-tool/job-views/agent-list.mjs +34 -19
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +7 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +8 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
- package/src/session-runtime/services/agent-tool.mjs +6 -0
- package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
- package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
- package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
- package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
- package/src/session-runtime/services/plugin-admin.mjs +26 -26
- package/src/session-runtime/services/provider-admin.mjs +26 -6
- package/src/session-runtime/services/schedule-access.mjs +33 -4
- package/src/session-runtime/services/seeds.mjs +4 -3
- package/src/session-runtime/services/usage-dashboard.mjs +1 -1
- package/src/session-runtime/services/usage-stats-model.mjs +2 -1
- package/src/session-runtime/services/worktree-access.mjs +27 -0
- package/src/session-runtime/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/session-hooks.mjs +9 -17
- package/src/session-runtime/settings-builtin-tools-api.mjs +65 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +12 -2
- package/src/session-runtime/setup-tool/settings-contract.mjs +34 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
- package/src/session-runtime/skills-api.mjs +12 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +349 -0
- package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
- package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +183 -0
- package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
- package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
- package/src/session-runtime/tool-policy-refresh.mjs +48 -369
- package/src/session-runtime/tool-surface.mjs +10 -4
- package/src/session-runtime/turn/session-ops.mjs +8 -0
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +7 -2
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +527 -333
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +85 -63
- package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
- package/src/session-runtime/workspace/transition-steps.mjs +1 -18
- package/src/session-runtime/workspace/transitions.mjs +279 -67
- package/src/session-runtime/workspace/workspace-state.mjs +7 -6
- package/src/session-runtime/workspace/worktree-tool.mjs +194 -0
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-lag-profiler.mjs +3 -1
- package/src/standalone/daemon-session-control.mjs +173 -15
- package/src/standalone/daemon-telemetry.mjs +9 -0
- package/src/standalone/daemon.mjs +33 -10
- package/src/standalone/local-session-runtime.mjs +33 -16
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +8 -0
- package/src/standalone/session-runtime-inline-host/boot-loaders.mjs +2 -0
- package/src/standalone/session-service/agent-route-follow.mjs +69 -0
- package/src/standalone/session-service/session-calls/session-turns.mjs +33 -4
- package/src/standalone/session-service.mjs +21 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +36 -9
- package/src/standalone/staged-import.mjs +47 -0
- package/src/tui/App.jsx +2 -0
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- package/src/tui/app/message-selector.mjs +15 -2
- package/src/tui/app/model-options.mjs +16 -2
- package/src/tui/app/model-picker.mjs +2 -1
- package/src/tui/app/route-pickers.mjs +3 -1
- package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
- package/src/tui/dist/index.mjs +617 -369
- package/src/tui/paste-attachments.mjs +0 -1
- package/src/tui/paste-text-policy.mjs +4 -5
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/agent-job-feed/execution-delivery.mjs +4 -1
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/notice-surface.mjs +3 -0
- package/src/tui/session/session-api/intake/abort.mjs +52 -18
- package/src/tui/session/session-api/intake/rewind.mjs +39 -4
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +76 -0
- package/src/tui/session/session-api/routes-write.mjs +203 -0
- package/src/tui/session/session-api/routes.mjs +23 -247
- package/src/tui/session/session-api/settings.mjs +14 -1
- package/src/tui/session/session-flow/drain.mjs +28 -3
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +8 -3
- package/src/tui/session/tool-approval.mjs +142 -14
- package/src/tui/session/transcript-intake.mjs +2 -0
- package/src/tui/session/turn.mjs +23 -14
- package/src/tui/transcript-tool-failures.mjs +8 -2
- package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
- package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
- package/src/rules/agent-mode/ROUTINE.md +0 -6
- package/src/session-runtime/room-member-gate.mjs +0 -48
|
@@ -2,6 +2,7 @@ import { statSync, readFileSync } from 'node:fs';
|
|
|
2
2
|
import { getPluginData } from '../../config.mjs';
|
|
3
3
|
import { readTopLevelLifecycleRecord, isLifecycleUnreadable } from '../lifecycle-scan.mjs';
|
|
4
4
|
import { shareJsonStrings } from '../../../../shared/json-snapshot.mjs';
|
|
5
|
+
import { holdsNoData } from '../../../../shared/atomic-file.mjs';
|
|
5
6
|
import {
|
|
6
7
|
sessionStampOf,
|
|
7
8
|
ownCommittedLifecycle,
|
|
@@ -227,6 +228,13 @@ function _ownedSession(id, text) {
|
|
|
227
228
|
return _ownedRecord(id, text)?.doc ?? null;
|
|
228
229
|
}
|
|
229
230
|
|
|
231
|
+
// A file that holds no data (empty or NUL-filled by a crash) is not a session
|
|
232
|
+
// that could own the identity: it reads as absent, so the id can be saved over
|
|
233
|
+
// or deleted instead of staying a ghost.
|
|
234
|
+
function _outcomeOfText(id, text) {
|
|
235
|
+
return holdsNoData(text) ? { exists: false, session: null } : { exists: true, session: _ownedSession(id, text) };
|
|
236
|
+
}
|
|
237
|
+
|
|
230
238
|
// `entry` is a cached (in-map) entry.
|
|
231
239
|
function _selectCachedSession(entry, preferInMemory) {
|
|
232
240
|
const preferred = preferInMemory?.(entry.header);
|
|
@@ -297,7 +305,7 @@ function _decisiveReplacementSnapshot(id, path) {
|
|
|
297
305
|
const code = err?.code || 'EUNKNOWN';
|
|
298
306
|
return ABSENT_CODES.has(code) ? { exists: false, session: null } : { exists: true, session: null };
|
|
299
307
|
}
|
|
300
|
-
return
|
|
308
|
+
return _outcomeOfText(id, text);
|
|
301
309
|
}
|
|
302
310
|
|
|
303
311
|
export function _readStoredSessionCached(id, path, { preferInMemory = null } = {}) {
|
|
@@ -391,7 +399,11 @@ export function _readStoredSessionCached(id, path, { preferInMemory = null } = {
|
|
|
391
399
|
// (uncached, since no identity was verified) rather than invent an
|
|
392
400
|
// absence/null for a perfectly readable replacement; otherwise
|
|
393
401
|
// stay conservatively present-but-invalid.
|
|
394
|
-
return
|
|
402
|
+
return _outcomeOfText(id, text);
|
|
403
|
+
}
|
|
404
|
+
if (holdsNoData(text)) {
|
|
405
|
+
_dropCached(path);
|
|
406
|
+
return { exists: false, session: null };
|
|
395
407
|
}
|
|
396
408
|
// Stable snapshot: `text` provably belongs to the file identified by
|
|
397
409
|
// `before`. Whatever it says is now authoritative, including "this is
|
|
@@ -13,6 +13,7 @@ import { getStoreDir, sessionPath } from './session-paths.mjs';
|
|
|
13
13
|
import {
|
|
14
14
|
readCanonicalSessionRecord as _readCanonicalRecord,
|
|
15
15
|
CANONICAL_RECORD_UNREADABLE as LIFECYCLE_AMBIGUOUS,
|
|
16
|
+
setAsideDamagedSessionRecord,
|
|
16
17
|
} from './canonical-reader.mjs';
|
|
17
18
|
import { probePath, PROBE_PRESENT, PROBE_ABSENT } from './fs-probe.mjs';
|
|
18
19
|
import {
|
|
@@ -106,9 +107,22 @@ export function deleteSession(id, options = {}) {
|
|
|
106
107
|
// probe and this read another process can rename a different session's
|
|
107
108
|
// record (or a torn write) onto this path, and an unlink is
|
|
108
109
|
// irreversible. Ambiguous/foreign bytes are therefore never deleted.
|
|
109
|
-
|
|
110
|
+
let record = _readCanonicalRecord(path);
|
|
111
|
+
// A record that holds no data or is not JSON is damage, not somebody's
|
|
112
|
+
// session: keep its bytes aside so the delete can finish.
|
|
113
|
+
if (record === null || record === LIFECYCLE_AMBIGUOUS) {
|
|
114
|
+
try {
|
|
115
|
+
if (setAsideDamagedSessionRecord(path)) record = null;
|
|
116
|
+
} catch {
|
|
117
|
+
return false; // could not move it: nothing was deleted
|
|
118
|
+
}
|
|
119
|
+
}
|
|
110
120
|
if (record === LIFECYCLE_AMBIGUOUS) return false;
|
|
111
121
|
if (record !== null) {
|
|
122
|
+
// Damage-only mode (a caller's no-data verdict was taken before this
|
|
123
|
+
// lock): content that is valid now was repaired or republished since,
|
|
124
|
+
// and is never deleted.
|
|
125
|
+
if (options.damagedOnly === true) return false;
|
|
112
126
|
if (record.id !== id) return false;
|
|
113
127
|
try {
|
|
114
128
|
unlinkSync(path);
|
|
@@ -21,8 +21,8 @@ function* sweepStaleSessionSteps(ttlMs, options = {}) {
|
|
|
21
21
|
// An unreadable store dir is not an empty one: sweeping (deleting,
|
|
22
22
|
// closing, pruning) on a probe we could not make is never allowed.
|
|
23
23
|
if (probePath(dir).state !== PROBE_PRESENT) {
|
|
24
|
-
const { openCandidates: _candidates, ...empty } = createSweepTally();
|
|
25
|
-
return empty;
|
|
24
|
+
const { openCandidates: _candidates, subAgents, ...empty } = createSweepTally();
|
|
25
|
+
return plan.retainSubAgents ? { ...empty, subAgents } : empty;
|
|
26
26
|
}
|
|
27
27
|
const rows = collectSweepRows(dir);
|
|
28
28
|
pruneSweepRecordCache(new Set(rows.map((row) => row?.id).filter(Boolean)));
|
|
@@ -41,8 +41,9 @@ function* sweepStaleSessionSteps(ttlMs, options = {}) {
|
|
|
41
41
|
yield* reapOrphanSidecars(ctx);
|
|
42
42
|
queueDeletedSummaryPrune(ctx.tally);
|
|
43
43
|
persistSweepRecordCache();
|
|
44
|
-
const { openCandidates: _candidates, ...result } = ctx.tally;
|
|
45
|
-
|
|
44
|
+
const { openCandidates: _candidates, subAgents, ...result } = ctx.tally;
|
|
45
|
+
// `subAgents` only for a caller that applies them (idle-cleanup.mjs).
|
|
46
|
+
return plan.retainSubAgents ? { ...result, subAgents } : result;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
/** Synchronous compatibility surface for explicit maintenance commands/tests. */
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// sweep/sub-agent-retention.mjs
|
|
2
|
+
// Retention of finished sub-agent sessions: the agent-only children (Worker,
|
|
3
|
+
// Reviewer, ...) a Lead's agents spawn, each its own stored session. The idle
|
|
4
|
+
// sweep names every sub-agent record with its STORED activity time (the clock
|
|
5
|
+
// survives restarts); a tree — a top sub-agent under a Lead with every
|
|
6
|
+
// sub-agent below it — is archived once each member is idle
|
|
7
|
+
// SUB_AGENT_ARCHIVE_AFTER_MS and deleted once each is idle
|
|
8
|
+
// SUB_AGENT_DELETE_AFTER_MS. Otherwise the whole tree stays as it is.
|
|
9
|
+
// Never a member: a root Lead, a task session from `session create`, a bot
|
|
10
|
+
// home chat or a group room's log/member session.
|
|
11
|
+
import { agentParentIds, isAgentOnlySession, isRootLeadSession } from '../../store-summary-visibility.mjs';
|
|
12
|
+
import { probePath, PROBE_PRESENT } from '../fs-probe.mjs';
|
|
13
|
+
import { sessionPath } from '../session-paths.mjs';
|
|
14
|
+
import { _queueSummaryIndexPrune } from '../summary-cache.mjs';
|
|
15
|
+
import { deleteSession } from '../session-delete.mjs';
|
|
16
|
+
import { readSweepRecord } from './sweep-record.mjs';
|
|
17
|
+
|
|
18
|
+
const DAY_MS = 24 * 60 * 60 * 1000;
|
|
19
|
+
export const SUB_AGENT_ARCHIVE_AFTER_MS = 3 * DAY_MS;
|
|
20
|
+
export const SUB_AGENT_DELETE_AFTER_MS = 30 * DAY_MS;
|
|
21
|
+
const PERMANENT_ID = /^sess_(?:home|room|rmem)_/;
|
|
22
|
+
// The desktop change an automatic archive applies: that one session, no
|
|
23
|
+
// cascade (an explicit archive of a parent takes its sub-agents along).
|
|
24
|
+
export const SUB_AGENT_AUTO_ARCHIVE = Object.freeze({ archived: true, cascade: false });
|
|
25
|
+
|
|
26
|
+
const positive = (value) => (Number(value) > 0 ? Number(value) : 0);
|
|
27
|
+
|
|
28
|
+
/** Last activity of a stored record: its last conversation turn (`lastUsedAt`;
|
|
29
|
+
* `updatedAt` also moves on detach/resume bookkeeping) or its heartbeat. */
|
|
30
|
+
export function subAgentActivityAt(doc) {
|
|
31
|
+
return Math.max(
|
|
32
|
+
positive(doc?.lastUsedAt) || positive(doc?.updatedAt) || positive(doc?.createdAt),
|
|
33
|
+
positive(doc?.lastHeartbeatAt)
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** `{ id, parentIds, lastActive }` for a sub-agent record (open or closed);
|
|
38
|
+
* null for anything else. `parentIds` are the owner and parent links the
|
|
39
|
+
* record names (agentParentIds), the same ones an archive cascade follows. */
|
|
40
|
+
export function subAgentMember(id, doc) {
|
|
41
|
+
if (!doc || PERMANENT_ID.test(id) || doc.taskOrigin) return null;
|
|
42
|
+
if (isRootLeadSession(doc) || !isAgentOnlySession(doc)) return null;
|
|
43
|
+
return { id, parentIds: agentParentIds({ ...doc, id }), lastActive: subAgentActivityAt(doc) };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The members grouped into trees: members joined by any owner/parent link
|
|
47
|
+
* between them (a link to a non-member joins nothing), each tree listing a
|
|
48
|
+
* top member (no member parent) first and its members below in link order. */
|
|
49
|
+
export function subAgentTrees(members) {
|
|
50
|
+
const byId = new Map(members.map((member) => [member.id, member]));
|
|
51
|
+
const links = new Map(members.map((member) => [member.id, new Set()]));
|
|
52
|
+
const hasMemberParent = new Set();
|
|
53
|
+
for (const member of members) {
|
|
54
|
+
for (const parent of (member.parentIds || []).filter((parentId) => byId.has(parentId))) {
|
|
55
|
+
links.get(member.id).add(parent);
|
|
56
|
+
links.get(parent).add(member.id);
|
|
57
|
+
hasMemberParent.add(member.id);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const seen = new Set();
|
|
61
|
+
const trees = [];
|
|
62
|
+
for (const start of [...members.filter((m) => !hasMemberParent.has(m.id)), ...members]) {
|
|
63
|
+
if (seen.has(start.id)) continue;
|
|
64
|
+
const tree = [start];
|
|
65
|
+
seen.add(start.id);
|
|
66
|
+
for (let index = 0; index < tree.length; index++) {
|
|
67
|
+
for (const id of links.get(tree[index].id)) {
|
|
68
|
+
if (seen.has(id)) continue;
|
|
69
|
+
seen.add(id);
|
|
70
|
+
tree.push(byId.get(id));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
trees.push(tree);
|
|
74
|
+
}
|
|
75
|
+
return trees;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The stored activity time of `id` now; null when its record cannot be read. */
|
|
79
|
+
function storedActivityAt(id) {
|
|
80
|
+
const path = sessionPath(id);
|
|
81
|
+
const probe = probePath(path);
|
|
82
|
+
if (probe.state !== PROBE_PRESENT) return null;
|
|
83
|
+
const record = readSweepRecord(id, path, probe);
|
|
84
|
+
return record?.id === id ? subAgentActivityAt(record.doc) : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Archives or deletes each idle tree. A tree with a member running here
|
|
89
|
+
* (`isSessionLive`), or one whose stored activity moved since the sweep read
|
|
90
|
+
* it, is left alone. An archive goes through the desktop's archive flag
|
|
91
|
+
* (`setSidebar(id, SUB_AGENT_AUTO_ARCHIVE)`: exactly the members not yet
|
|
92
|
+
* archived, never a cascade past them — needs
|
|
93
|
+
* `isArchived`, the desktop's flags); a deleted member that was archived loses
|
|
94
|
+
* its flag (`{ forgotten: true }`). Returns `{ archived, deleted }` (ids).
|
|
95
|
+
*/
|
|
96
|
+
export async function applySubAgentRetention(
|
|
97
|
+
members,
|
|
98
|
+
{ isSessionLive = null, isArchived = null, setSidebar = null, now = Date.now() } = {}
|
|
99
|
+
) {
|
|
100
|
+
const archived = [];
|
|
101
|
+
const deleted = [];
|
|
102
|
+
for (const tree of subAgentTrees(members)) {
|
|
103
|
+
// A member of unknown age (no valid stored timestamp) keeps its tree.
|
|
104
|
+
if (!tree.every((member) => Number.isFinite(member.lastActive) && member.lastActive > 0)) continue;
|
|
105
|
+
const idle = now - Math.max(...tree.map((member) => member.lastActive));
|
|
106
|
+
const expired = idle >= SUB_AGENT_DELETE_AFTER_MS;
|
|
107
|
+
if (!expired && (idle < SUB_AGENT_ARCHIVE_AFTER_MS || !isArchived)) continue;
|
|
108
|
+
if (tree.some((member) => isSessionLive?.(member.id))) continue;
|
|
109
|
+
if (tree.some((member) => storedActivityAt(member.id) !== member.lastActive)) continue;
|
|
110
|
+
if (expired) {
|
|
111
|
+
for (const { id } of tree.toReversed()) {
|
|
112
|
+
try {
|
|
113
|
+
if (deleteSession(id, { deferSummaryUpdate: true, isSessionLive, legacyOwnerVeto: true })) deleted.push(id);
|
|
114
|
+
} catch {
|
|
115
|
+
/* kept; the next pass decides again */
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
continue;
|
|
119
|
+
}
|
|
120
|
+
for (const { id } of tree) {
|
|
121
|
+
if (isArchived(id)) continue;
|
|
122
|
+
try {
|
|
123
|
+
await setSidebar(id, SUB_AGENT_AUTO_ARCHIVE);
|
|
124
|
+
archived.push(id);
|
|
125
|
+
} catch {
|
|
126
|
+
/* not archived; the next pass decides again */
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (deleted.length) _queueSummaryIndexPrune(new Set(deleted));
|
|
131
|
+
for (const id of deleted) {
|
|
132
|
+
if (!isArchived?.(id)) continue;
|
|
133
|
+
try {
|
|
134
|
+
await setSidebar(id, { forgotten: true });
|
|
135
|
+
} catch {
|
|
136
|
+
/* a stale archive stamp hides nothing */
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { archived, deleted };
|
|
140
|
+
}
|
|
@@ -26,6 +26,9 @@ export function resolveSweepPlan(ttlMs, options = {}) {
|
|
|
26
26
|
retainOpen: sweepIdle && options.retainOpenSessions !== false,
|
|
27
27
|
openMaxAgeMs: Number.isFinite(optAge) && optAge > 0 ? optAge : RESUMABLE_OPEN_MAX_AGE_MS,
|
|
28
28
|
openMaxCount: Number.isFinite(optCount) && optCount >= 0 ? optCount : RESUMABLE_OPEN_MAX_COUNT,
|
|
29
|
+
// Name every sub-agent session for the tree retention its caller applies
|
|
30
|
+
// (sweep/sub-agent-retention.mjs). Idle sweep only.
|
|
31
|
+
retainSubAgents: sweepIdle && options.retainSubAgents === true,
|
|
29
32
|
};
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -69,5 +72,7 @@ export function createSweepTally() {
|
|
|
69
72
|
openCandidates: [],
|
|
70
73
|
openPruned: 0,
|
|
71
74
|
openPrunedDetails: [],
|
|
75
|
+
// Sub-agent sessions with their stored activity (plan.retainSubAgents).
|
|
76
|
+
subAgents: [],
|
|
72
77
|
};
|
|
73
78
|
}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// idle / retention / blank-scratch thresholds.
|
|
5
5
|
import { readFileSync } from 'node:fs';
|
|
6
6
|
import { join } from 'node:path';
|
|
7
|
-
import { writeJsonAtomicSync } from '../../../../../shared/atomic-file.mjs';
|
|
7
|
+
import { holdsNoData, writeJsonAtomicSync } from '../../../../../shared/atomic-file.mjs';
|
|
8
8
|
import { getPluginData } from '../../../config.mjs';
|
|
9
9
|
import { readTopLevelLifecycleRecord, isLifecycleUnreadable } from '../../lifecycle-scan.mjs';
|
|
10
10
|
|
|
@@ -48,7 +48,7 @@ export const BLANK_SCRATCH_MAX_AGE_MS = 60 * 60 * 1000; // 1h
|
|
|
48
48
|
// running is still parsed again.
|
|
49
49
|
const sweepRecordCache = new Map();
|
|
50
50
|
const SWEEP_RECORD_CACHE_FILE = 'session-sweep-records.json';
|
|
51
|
-
const SWEEP_RECORD_CACHE_VERSION =
|
|
51
|
+
const SWEEP_RECORD_CACHE_VERSION = 3;
|
|
52
52
|
let sweepRecordCacheDataDir = null;
|
|
53
53
|
let sweepRecordCacheDirty = false;
|
|
54
54
|
let sweepRecordParses = 0;
|
|
@@ -82,7 +82,7 @@ function syncSweepRecordCache() {
|
|
|
82
82
|
}
|
|
83
83
|
|
|
84
84
|
// The only document fields sweep-row reads (effectiveFields, the closed
|
|
85
|
-
// check). Caching the whole top-level document kept
|
|
85
|
+
// check, the sub-agent retention verdict). Caching the whole top-level document kept
|
|
86
86
|
// every session's tool catalogs, schemas and provider state alive — one
|
|
87
87
|
// entry per session file, measured at ~290MB of a 470MB daemon heap.
|
|
88
88
|
const SWEEP_DOC_FIELDS = [
|
|
@@ -97,6 +97,11 @@ const SWEEP_DOC_FIELDS = [
|
|
|
97
97
|
'createdAt',
|
|
98
98
|
'ownerSessionId',
|
|
99
99
|
'parentSessionId',
|
|
100
|
+
'lastUsedAt',
|
|
101
|
+
'agent',
|
|
102
|
+
'visibility',
|
|
103
|
+
'sessionVisibility',
|
|
104
|
+
'taskOrigin',
|
|
100
105
|
];
|
|
101
106
|
|
|
102
107
|
function conversationCountOf(doc) {
|
|
@@ -108,8 +113,12 @@ function conversationCountOf(doc) {
|
|
|
108
113
|
return count;
|
|
109
114
|
}
|
|
110
115
|
|
|
111
|
-
/**
|
|
112
|
-
|
|
116
|
+
/** The file holds no data (empty or NUL-filled by a crash); never cached. */
|
|
117
|
+
export const SWEEP_RECORD_NO_DATA = Symbol('sweep-record-no-data');
|
|
118
|
+
|
|
119
|
+
/** `null` when the file could not be read; SWEEP_RECORD_NO_DATA for a file
|
|
120
|
+
* without data; otherwise the lifecycle record (possibly
|
|
121
|
+
* LIFECYCLE_SCAN_CONFLICT, which is never cached). */
|
|
113
122
|
export function readSweepRecord(id, jsonPath, probe) {
|
|
114
123
|
syncSweepRecordCache();
|
|
115
124
|
const cached = sweepRecordCache.get(id);
|
|
@@ -121,6 +130,7 @@ export function readSweepRecord(id, jsonPath, probe) {
|
|
|
121
130
|
} catch {
|
|
122
131
|
return null;
|
|
123
132
|
}
|
|
133
|
+
if (holdsNoData(raw)) return SWEEP_RECORD_NO_DATA;
|
|
124
134
|
sweepRecordParses++;
|
|
125
135
|
const full = readTopLevelLifecycleRecord(raw);
|
|
126
136
|
if (isLifecycleUnreadable(full)) return full;
|
|
@@ -12,7 +12,8 @@ import { probePath, PROBE_PRESENT, PROBE_ABSENT } from '../fs-probe.mjs';
|
|
|
12
12
|
import { _queueSessionSummaryUpsert, _queueSessionSummaryRemoval } from '../summary-cache.mjs';
|
|
13
13
|
import { deleteSession } from '../session-delete.mjs';
|
|
14
14
|
import { markSessionClosed } from '../lifecycle-barriers.mjs';
|
|
15
|
-
import { BLANK_SCRATCH_MAX_AGE_MS, RUNNING_STALL_MS, readSweepRecord } from './sweep-record.mjs';
|
|
15
|
+
import { BLANK_SCRATCH_MAX_AGE_MS, RUNNING_STALL_MS, SWEEP_RECORD_NO_DATA, readSweepRecord } from './sweep-record.mjs';
|
|
16
|
+
import { subAgentMember } from './sub-agent-retention.mjs';
|
|
16
17
|
import { isAgentHomeSessionId } from '../../agent-home.mjs';
|
|
17
18
|
import { isRoomThreadSessionId } from '../../agent-room.mjs';
|
|
18
19
|
|
|
@@ -28,6 +29,14 @@ const effectiveFields = (actual, row) => ({
|
|
|
28
29
|
createdAt: Number(actual?.createdAt) > 0 ? Number(actual.createdAt) : row.createdAt || 0,
|
|
29
30
|
});
|
|
30
31
|
|
|
32
|
+
/** Every sub-agent record, open or closed, fresh or not, for the tree
|
|
33
|
+
* retention its caller applies (sub-agent-retention.mjs). */
|
|
34
|
+
function noteSubAgent(ctx, id, doc) {
|
|
35
|
+
if (!ctx.plan.retainSubAgents) return;
|
|
36
|
+
const member = subAgentMember(id, doc);
|
|
37
|
+
if (member) ctx.tally.subAgents.push(member);
|
|
38
|
+
}
|
|
39
|
+
|
|
31
40
|
/** A closed record: a mature tombstone is deleted; otherwise a stale summary
|
|
32
41
|
* row that still claims the session open is repaired and the row is kept. */
|
|
33
42
|
function sweepClosedRecord(ctx, row, actual, jsonPath) {
|
|
@@ -153,6 +162,7 @@ function sweepOpenRecord(ctx, row, record, { jsonMtime }) {
|
|
|
153
162
|
tally.remaining++;
|
|
154
163
|
return;
|
|
155
164
|
}
|
|
165
|
+
noteSubAgent(ctx, row.id, record.doc);
|
|
156
166
|
const eff = effectiveFields(record.doc, row);
|
|
157
167
|
const ownerNamed = typeof eff.owner === 'string' && eff.owner.length > 0;
|
|
158
168
|
// Freshness gate — recently-touched open sessions are skipped cheaply here.
|
|
@@ -204,6 +214,37 @@ function sweepOpenRecord(ctx, row, record, { jsonMtime }) {
|
|
|
204
214
|
tally.remaining++;
|
|
205
215
|
}
|
|
206
216
|
|
|
217
|
+
/** A record a crash left empty or NUL-filled holds nothing to preserve and
|
|
218
|
+
* would otherwise be reported forever as an unreadable session: its bytes are
|
|
219
|
+
* kept aside as `<id>.json.corrupt-<ts>` (deleteSession, under the commit
|
|
220
|
+
* lock and its liveness vetoes) and the session's row is retired with it. */
|
|
221
|
+
function sweepNoDataRecord(ctx, row) {
|
|
222
|
+
const { plan, tally } = ctx;
|
|
223
|
+
if (plan.isSessionLive?.(row.id)) {
|
|
224
|
+
tally.remaining++;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
try {
|
|
228
|
+
if (
|
|
229
|
+
deleteSession(row.id, {
|
|
230
|
+
deferSummaryUpdate: true,
|
|
231
|
+
isSessionLive: plan.isSessionLive,
|
|
232
|
+
legacyOwnerVeto: true,
|
|
233
|
+
// The no-data verdict predates the commit lock: a record repaired or
|
|
234
|
+
// republished since is valid content and must survive.
|
|
235
|
+
damagedOnly: true,
|
|
236
|
+
})
|
|
237
|
+
) {
|
|
238
|
+
tally.openPruned++;
|
|
239
|
+
tally.openPrunedDetails.push({ id: row.id, ageSeconds: 0 });
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
} catch {
|
|
243
|
+
/* keep the row on failure */
|
|
244
|
+
}
|
|
245
|
+
tally.remaining++;
|
|
246
|
+
}
|
|
247
|
+
|
|
207
248
|
export function sweepRow(ctx, row) {
|
|
208
249
|
if (!row?.id) return;
|
|
209
250
|
const { tally } = ctx;
|
|
@@ -230,6 +271,10 @@ export function sweepRow(ctx, row) {
|
|
|
230
271
|
// unreadable record, or a record naming another id (including a foreign
|
|
231
272
|
// TOMBSTONE that would look perfectly mature) is preserved untouched.
|
|
232
273
|
const record = readSweepRecord(row.id, jsonPath, jsonProbe);
|
|
274
|
+
if (record === SWEEP_RECORD_NO_DATA) {
|
|
275
|
+
sweepNoDataRecord(ctx, row);
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
233
278
|
if (record === null || isLifecycleUnreadable(record) || record.id !== row.id) {
|
|
234
279
|
tally.remaining++;
|
|
235
280
|
return;
|
|
@@ -242,7 +287,10 @@ export function sweepRow(ctx, row) {
|
|
|
242
287
|
return;
|
|
243
288
|
}
|
|
244
289
|
if (record.closed === true || actual.status === 'closed') {
|
|
290
|
+
const cleanedBefore = ctx.tally.tombstonesCleaned;
|
|
245
291
|
sweepClosedRecord(ctx, row, actual, jsonPath);
|
|
292
|
+
// A tombstone this sweep just deleted is gone, not a member.
|
|
293
|
+
if (ctx.tally.tombstonesCleaned === cleanedBefore) noteSubAgent(ctx, row.id, actual);
|
|
246
294
|
return;
|
|
247
295
|
}
|
|
248
296
|
sweepOpenRecord(ctx, row, record, { jsonMtime: jsonProbe.mtimeMs || 0 });
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
import { readdirSync, readFileSync } from 'node:fs';
|
|
13
13
|
import { stat } from 'node:fs/promises';
|
|
14
14
|
import { join } from 'node:path';
|
|
15
|
+
import { performance } from 'node:perf_hooks';
|
|
15
16
|
// Leaf helpers only: the three-way present/absent/unreadable classification
|
|
16
17
|
// and the strict record parser the authoritative store uses, so the catalog
|
|
17
18
|
// cannot disagree with it.
|
|
@@ -22,7 +23,7 @@ import { isAgentOnlySession, isRootLeadSession, sessionVisibility } from './stor
|
|
|
22
23
|
import { applySummaryLogText, summaryLogPath } from './store-summary-log.mjs';
|
|
23
24
|
import { dataDir } from './store-summary-locations.mjs';
|
|
24
25
|
import { normalizeRoutineProvenance } from './routine-result.mjs';
|
|
25
|
-
import { lastMessageLine, lastMessageSummary } from './store-summary-last-message.mjs';
|
|
26
|
+
import { lastMessageLine, lastMessageSummary, normalizedIncomingTail } from './store-summary-last-message.mjs';
|
|
26
27
|
import {
|
|
27
28
|
desktopSession,
|
|
28
29
|
importKey,
|
|
@@ -211,6 +212,9 @@ export function normalizedRow(row) {
|
|
|
211
212
|
permission: row.permission || null,
|
|
212
213
|
toolPermission: row.toolPermission || null,
|
|
213
214
|
messageCount: Math.max(0, Math.floor(Number(row.messageCount) || 0)),
|
|
215
|
+
// Absent from a row read off a session file: its assistant text is not
|
|
216
|
+
// decoded there, so the unread count falls back until the next save.
|
|
217
|
+
...(Array.isArray(row.incomingTail) ? { incomingTail: normalizedIncomingTail(row.incomingTail) } : {}),
|
|
214
218
|
realTurnCount: realTurnCount(row),
|
|
215
219
|
title: cleanText(row.title, 100),
|
|
216
220
|
preview: cleanText(row.preview),
|
|
@@ -381,6 +385,66 @@ function scanSessionEntries(directory, indexRowsById, forceRead) {
|
|
|
381
385
|
return { entries };
|
|
382
386
|
}
|
|
383
387
|
|
|
388
|
+
// The async rescan yields a turn once a slice of reconciles has run this long:
|
|
389
|
+
// a full authoritative rescan strict-reads every session file (≈4 s for 1.8k
|
|
390
|
+
// files / 1.3 GB), which must not hold the daemon loop in one piece.
|
|
391
|
+
let scanSliceMs = 25;
|
|
392
|
+
|
|
393
|
+
/** Test seam: the async rescan's slice budget (0 yields after every file). */
|
|
394
|
+
export function _setCatalogScanSliceMsForTest(ms) {
|
|
395
|
+
scanSliceMs = Math.max(0, Number(ms) || 0);
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
const nextTurn = () => new Promise((resolve) => setImmediate(resolve));
|
|
399
|
+
|
|
400
|
+
/** The entry a file still has: `entry` when the file's stamp is unchanged
|
|
401
|
+
* since it was reconciled, else a fresh reconcile (null once it is gone). */
|
|
402
|
+
function settledEntry(directory, storageId, entry, retained, forceRead) {
|
|
403
|
+
if (entry?.stamp) {
|
|
404
|
+
let stamp = null;
|
|
405
|
+
try {
|
|
406
|
+
stamp = statSessionStamp(join(directory, `${storageId}.json`));
|
|
407
|
+
} catch {
|
|
408
|
+
// Gone or unstatable: the reconcile below decides which.
|
|
409
|
+
}
|
|
410
|
+
if (stamp && sameSessionStamp(entry.stamp, stamp)) return entry;
|
|
411
|
+
}
|
|
412
|
+
return reconcileSessionFile(directory, storageId, retained, forceRead);
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** scanSessionEntries in slices, yielding the loop between them. Files can
|
|
416
|
+
* change while it yields, so the answer is settled in one synchronous pass at
|
|
417
|
+
* the end: the directory is listed again, a file whose stamp moved since its
|
|
418
|
+
* read (or that appeared) is reconciled again, and a file that is no longer
|
|
419
|
+
* listed is dropped — the entries match storage when they are returned, as
|
|
420
|
+
* the one-piece scan's do. That pass is a stat per file (≈20 ms for 1.5k). */
|
|
421
|
+
async function scanSessionEntriesAsync(directory, indexRowsById, forceRead, onSettle) {
|
|
422
|
+
const sessionIds = listSessionIds(directory);
|
|
423
|
+
if (!sessionIds) {
|
|
424
|
+
onSettle?.();
|
|
425
|
+
return null;
|
|
426
|
+
}
|
|
427
|
+
const scanned = new Map();
|
|
428
|
+
let sliceStart = performance.now();
|
|
429
|
+
for (const storageId of sessionIds) {
|
|
430
|
+
if (performance.now() - sliceStart >= scanSliceMs) {
|
|
431
|
+
await nextTurn();
|
|
432
|
+
sliceStart = performance.now();
|
|
433
|
+
}
|
|
434
|
+
const entry = reconcileSessionFile(directory, storageId, indexRowsById.get(storageId), forceRead);
|
|
435
|
+
if (entry) scanned.set(storageId, entry);
|
|
436
|
+
}
|
|
437
|
+
onSettle?.();
|
|
438
|
+
const settledIds = listSessionIds(directory);
|
|
439
|
+
if (!settledIds) return null;
|
|
440
|
+
const entries = new Map();
|
|
441
|
+
for (const storageId of settledIds) {
|
|
442
|
+
const entry = settledEntry(directory, storageId, scanned.get(storageId), indexRowsById.get(storageId), forceRead);
|
|
443
|
+
if (entry) entries.set(storageId, entry);
|
|
444
|
+
}
|
|
445
|
+
return { entries };
|
|
446
|
+
}
|
|
447
|
+
|
|
384
448
|
/** The row an entry contributes, or null. */
|
|
385
449
|
function entryRow(entry, id, indexRowsById) {
|
|
386
450
|
if (!entry || entry.kind === 'none') return null;
|
|
@@ -416,6 +480,21 @@ export function scanSessionFiles(indexRowsById, forceRead) {
|
|
|
416
480
|
return scan && assembleCatalog(scan.entries, indexRowsById);
|
|
417
481
|
}
|
|
418
482
|
|
|
483
|
+
/** scanSessionFiles with the same answer, its file reconciles run in slices
|
|
484
|
+
* that yield the event loop (the daemon's session.list storage refresh).
|
|
485
|
+
* `onSettle` runs once, when the answer's final synchronous part begins: from
|
|
486
|
+
* then on storage changes are no longer seen by this scan. */
|
|
487
|
+
export async function scanSessionFilesAsync(indexRowsById, forceRead, { onSettle } = {}) {
|
|
488
|
+
const directory = join(dataDir(), 'sessions');
|
|
489
|
+
const dirProbe = probePath(directory);
|
|
490
|
+
if (dirProbe.state !== PROBE_PRESENT) {
|
|
491
|
+
onSettle?.();
|
|
492
|
+
return dirProbe.state === PROBE_ABSENT ? [] : null;
|
|
493
|
+
}
|
|
494
|
+
const scan = await scanSessionEntriesAsync(directory, indexRowsById, forceRead, onSettle);
|
|
495
|
+
return scan && assembleCatalog(scan.entries, indexRowsById);
|
|
496
|
+
}
|
|
497
|
+
|
|
419
498
|
/** Listing from the index rows alone, or null without a valid index. */
|
|
420
499
|
export function indexListing(index) {
|
|
421
500
|
return index.rows && leadRows(index.rows.map((row) => ({ ...row }))).sort(byCatalogOrder);
|
|
@@ -29,7 +29,15 @@ import {
|
|
|
29
29
|
} from './store-summary-fields.mjs';
|
|
30
30
|
import { isRealTurnMessage } from './compact/user-message-kind.mjs';
|
|
31
31
|
import { isRoutineResultMessage, normalizeRoutineProvenance, routineProvenance } from './routine-result.mjs';
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
INCOMING_TAIL_MAX,
|
|
34
|
+
LAST_MESSAGE_PREVIEW_MAX,
|
|
35
|
+
cleanSummaryPreview,
|
|
36
|
+
isIncomingKind,
|
|
37
|
+
normalizedIncomingTail,
|
|
38
|
+
lastMessageRawText,
|
|
39
|
+
wantsIncomingTail,
|
|
40
|
+
} from './store-summary-last-message.mjs';
|
|
33
41
|
|
|
34
42
|
export const SESSION_SUMMARY_INDEX_VERSION = 2;
|
|
35
43
|
|
|
@@ -111,6 +119,13 @@ function _sessionMessageProjection(session) {
|
|
|
111
119
|
let real = 0;
|
|
112
120
|
let preview = '';
|
|
113
121
|
let previewMessage = null;
|
|
122
|
+
// Where the incoming messages with text sit among the counted ones (see
|
|
123
|
+
// incomingTailOf), kept only for the threads that show an unread count.
|
|
124
|
+
const wantsIncoming = wantsIncomingTail(session?.id);
|
|
125
|
+
let incoming = [];
|
|
126
|
+
// The totals ahead of the newest message, which a streamed reply fills in
|
|
127
|
+
// place after it was counted: an append redoes that message from here.
|
|
128
|
+
let ahead = null;
|
|
114
129
|
const canAppend =
|
|
115
130
|
cached &&
|
|
116
131
|
messages.length >= cached.length &&
|
|
@@ -119,6 +134,12 @@ function _sessionMessageProjection(session) {
|
|
|
119
134
|
start = cached.length;
|
|
120
135
|
count = cached.count;
|
|
121
136
|
real = cached.real;
|
|
137
|
+
incoming = cached.incoming.slice();
|
|
138
|
+
if (cached.ahead) {
|
|
139
|
+
start = cached.length - 1;
|
|
140
|
+
({ count, real } = cached.ahead);
|
|
141
|
+
incoming = cached.ahead.incoming.slice();
|
|
142
|
+
}
|
|
122
143
|
preview = cached.preview;
|
|
123
144
|
previewMessage = cached.previewMessage;
|
|
124
145
|
// The first real user message is stable in normal append-only session
|
|
@@ -131,6 +152,7 @@ function _sessionMessageProjection(session) {
|
|
|
131
152
|
start = 0;
|
|
132
153
|
count = 0;
|
|
133
154
|
real = 0;
|
|
155
|
+
incoming = [];
|
|
134
156
|
preview = '';
|
|
135
157
|
previewMessage = null;
|
|
136
158
|
}
|
|
@@ -138,7 +160,14 @@ function _sessionMessageProjection(session) {
|
|
|
138
160
|
}
|
|
139
161
|
for (let index = start; index < messages.length; index += 1) {
|
|
140
162
|
const message = messages[index];
|
|
141
|
-
if (
|
|
163
|
+
if (index === messages.length - 1) ahead = { count, real, incoming: incoming.slice() };
|
|
164
|
+
if (message && (message.role === 'user' || message.role === 'assistant')) {
|
|
165
|
+
count += 1;
|
|
166
|
+
if (wantsIncoming && isIncomingKind(message) && _lastPreviewFromMessage(message)) {
|
|
167
|
+
incoming.push(count);
|
|
168
|
+
if (incoming.length > INCOMING_TAIL_MAX) incoming.shift();
|
|
169
|
+
}
|
|
170
|
+
}
|
|
142
171
|
if (isRealTurnMessage(message)) real += 1;
|
|
143
172
|
if (!preview) {
|
|
144
173
|
const candidate = _previewFromMessage(message);
|
|
@@ -165,11 +194,13 @@ function _sessionMessageProjection(session) {
|
|
|
165
194
|
lastMessage: messages[messages.length - 1] || null,
|
|
166
195
|
count,
|
|
167
196
|
real,
|
|
197
|
+
incoming,
|
|
198
|
+
ahead,
|
|
168
199
|
preview,
|
|
169
200
|
previewMessage,
|
|
170
201
|
});
|
|
171
202
|
}
|
|
172
|
-
return { count, real, preview, last, lastRoutine };
|
|
203
|
+
return { count, real, incoming: wantsIncoming ? incoming : null, preview, last, lastRoutine };
|
|
173
204
|
}
|
|
174
205
|
|
|
175
206
|
function _desktopSessionSummary(value, cwd = null) {
|
|
@@ -233,6 +264,7 @@ export function _sessionSummary(session) {
|
|
|
233
264
|
: null,
|
|
234
265
|
taskType: session.taskType || null,
|
|
235
266
|
messageCount: messageProjection.count,
|
|
267
|
+
...(messageProjection.incoming ? { incomingTail: messageProjection.incoming.slice() } : {}),
|
|
236
268
|
realTurnCount: messageProjection.real,
|
|
237
269
|
title: _cleanPreview(session.title || '', 100),
|
|
238
270
|
preview: messageProjection.preview,
|
|
@@ -291,6 +323,7 @@ function _normalizeSummaryRow(row) {
|
|
|
291
323
|
: null,
|
|
292
324
|
taskType: row.taskType || null,
|
|
293
325
|
messageCount: Math.max(0, Math.floor(Number(row.messageCount) || 0)),
|
|
326
|
+
...(Array.isArray(row.incomingTail) ? { incomingTail: normalizedIncomingTail(row.incomingTail) } : {}),
|
|
294
327
|
realTurnCount: realTurnCount(row),
|
|
295
328
|
title: _cleanPreview(row.title || '', 100),
|
|
296
329
|
preview: _cleanPreview(row.preview || ''),
|
|
@@ -561,12 +594,14 @@ export function summaryLockFailureStats() {
|
|
|
561
594
|
|
|
562
595
|
// ── Stale temp sweep ────────────────────────────────────────────────────────
|
|
563
596
|
// A crash between an atomic write's temp file and its rename orphans
|
|
564
|
-
//
|
|
597
|
+
// `session-summaries.json.mixdog-tmp-<16 hex>` (multi-MB each; before the
|
|
598
|
+
// cloud-sync fix `.session-summaries.json.<24 hex>.tmp`). Only this store's
|
|
565
599
|
// pattern is touched. Deleted only when older than SUMMARY_TMP_MIN_AGE_MS and
|
|
566
600
|
// while holding the index lock: every writer of these temps (full rewrite,
|
|
567
601
|
// compaction) holds that lock for the whole write, so no live writer can own a
|
|
568
602
|
// temp file the sweep sees.
|
|
569
|
-
const SUMMARY_TMP_PATTERN =
|
|
603
|
+
const SUMMARY_TMP_PATTERN =
|
|
604
|
+
/^(?:\.session-summaries\.json\.[0-9a-f]{24}\.tmp|session-summaries\.json\.mixdog-tmp-[0-9a-f]{16,24})$/;
|
|
570
605
|
const SUMMARY_TMP_MIN_AGE_MS = 10 * 60 * 1000;
|
|
571
606
|
let _sweptSummaryDir = '';
|
|
572
607
|
|