mixdog 1.3.4 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSES/oh-my-pi-MIT.txt +23 -0
- package/NOTICE.md +17 -2
- package/README.md +11 -0
- package/package.json +3 -11
- package/scripts/daemon-stub.mjs +1 -1
- package/scripts/embed-google-oauth-client.mjs +49 -0
- package/scripts/gate-local.mjs +4 -2
- package/scripts/lib/test-spawn-ledger.mjs +46 -1
- package/scripts/native-release-status.mjs +2 -2
- package/scripts/recall-history-cases.json +1 -1
- package/scripts/suite-health.mjs +10 -1
- package/scripts/test.mjs +12 -11
- package/src/agents/reviewer/AGENT.md +7 -11
- package/src/agents/reviewer/agent.json +1 -1
- package/src/defaults/shipped-history.json +25 -1
- package/src/defaults/skills/browser-use/SKILL.md +77 -17
- package/src/defaults/skills/code-tidy/SKILL.md +142 -270
- package/src/defaults/skills/code-tidy/references/agent-cleanup.md +4 -4
- package/src/defaults/skills/code-tidy/references/cleanup-report.md +31 -76
- package/src/defaults/skills/code-tidy/references/dead-code.md +6 -12
- package/src/defaults/skills/computer-use/SKILL.md +33 -9
- package/src/defaults/skills/setup/SKILL.md +29 -2
- package/src/defaults/skills/setup/references/actions.md +117 -28
- package/src/defaults/skills/setup/references/surfaces.md +77 -35
- package/src/defaults/skills/setup-guide/SKILL.md +3 -3
- package/src/defaults/skills/visualize/SKILL.md +2 -1
- package/src/lib/keychain-cjs.cjs +359 -29
- package/src/lib/rules-builder.cjs +10 -8
- package/src/rules/agent/AGENT.md +6 -6
- package/src/rules/agent-mode/AGENT_MODE.md +10 -2
- package/src/rules/agent-mode/MIXY.md +12 -0
- package/src/rules/lead/LEAD.md +18 -20
- package/src/rules/shared/05-tool-calls.md +5 -6
- package/src/rules/shared/10-tool-workflow.md +2 -0
- package/src/rules/shared/60-verification.md +7 -5
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +2 -3
- package/src/runtime/agent/orchestrator/agent-runtime/heartbeat-completion.mjs +193 -0
- package/src/runtime/agent/orchestrator/agent-runtime/title-completion.mjs +3 -1
- package/src/runtime/agent/orchestrator/agent-trace-format.mjs +3 -1
- package/src/runtime/agent/orchestrator/config.mjs +34 -0
- package/src/runtime/agent/orchestrator/context/collect.mjs +3 -0
- package/src/runtime/agent/orchestrator/context/deferred-tools.mjs +72 -67
- package/src/runtime/agent/orchestrator/context/skill-catalog.mjs +54 -1
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +3 -3
- package/src/runtime/agent/orchestrator/mcp/client-transport.mjs +177 -73
- package/src/runtime/agent/orchestrator/mcp/client.mjs +25 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-callback.mjs +31 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-client.mjs +16 -3
- package/src/runtime/agent/orchestrator/mcp/oauth-login.mjs +42 -7
- package/src/runtime/agent/orchestrator/mcp/oauth-provider.mjs +21 -2
- package/src/runtime/agent/orchestrator/mcp/oauth-shared.mjs +23 -0
- package/src/runtime/agent/orchestrator/mcp/oauth.mjs +11 -1
- package/src/runtime/agent/orchestrator/providers/account-pool.mjs +230 -137
- package/src/runtime/agent/orchestrator/providers/admission-scheduler.mjs +64 -7
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +18 -5
- package/src/runtime/agent/orchestrator/providers/anthropic-cache-warm.mjs +165 -0
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-credentials.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-request/gzip-post.mjs +14 -14
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +160 -69
- package/src/runtime/agent/orchestrator/providers/anthropic-overload-fallback.mjs +25 -3
- package/src/runtime/agent/orchestrator/providers/anthropic-sse.mjs +64 -33
- package/src/runtime/agent/orchestrator/providers/anthropic.mjs +135 -87
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +52 -8
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth.mjs +10 -1
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +22 -6
- package/src/runtime/agent/orchestrator/providers/antigravity-response.mjs +15 -4
- package/src/runtime/agent/orchestrator/providers/antigravity-stream.mjs +17 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-transport.mjs +142 -104
- package/src/runtime/agent/orchestrator/providers/cache-warm-admission.mjs +90 -0
- package/src/runtime/agent/orchestrator/providers/codex-client-meta.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/cursor-wire-normalization.mjs +10 -4
- package/src/runtime/agent/orchestrator/providers/cursor-wire-protobuf.mjs +10 -217
- package/src/runtime/agent/orchestrator/providers/cursor-wire-transport.mjs +25 -9
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +22 -6
- package/src/runtime/agent/orchestrator/providers/devin-auth.mjs +409 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-families.mjs +187 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-identity.mjs +342 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-models.mjs +541 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-protobuf.mjs +305 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-request.mjs +558 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-schema.mjs +880 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-session.mjs +190 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-tool-args.mjs +351 -0
- package/src/runtime/agent/orchestrator/providers/devin-wire-transport.mjs +560 -0
- package/src/runtime/agent/orchestrator/providers/devin.mjs +491 -0
- package/src/runtime/agent/orchestrator/providers/effort-configuration.mjs +3 -0
- package/src/runtime/agent/orchestrator/providers/gemini-output-limit.mjs +46 -0
- package/src/runtime/agent/orchestrator/providers/gemini-reasoning-relay.mjs +30 -0
- package/src/runtime/agent/orchestrator/providers/gemini-request-body.mjs +7 -11
- package/src/runtime/agent/orchestrator/providers/gemini-retry-policy.mjs +61 -22
- package/src/runtime/agent/orchestrator/providers/gemini-sdk-request.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/gemini-stream.mjs +71 -4
- package/src/runtime/agent/orchestrator/providers/gemini.mjs +25 -2
- package/src/runtime/agent/orchestrator/providers/grok-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +96 -5
- package/src/runtime/agent/orchestrator/providers/lib/oauth-token-utils.mjs +17 -3
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +23 -5
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +52 -10
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.d.mts +8 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-channel.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/npm-cli-version.mjs +11 -3
- package/src/runtime/agent/orchestrator/providers/oauth-credential-probes.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/oauth-usage.mjs +39 -3
- 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 +2 -0
- package/src/runtime/agent/orchestrator/providers/openai-error-contract.mjs +6 -1
- package/src/runtime/agent/orchestrator/providers/openai-oauth-tokens.mjs +10 -9
- package/src/runtime/agent/orchestrator/providers/openai-ws-send/send-context.mjs +22 -3
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/attempt-context.mjs +43 -5
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/handshake-failure.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws-send-attempts/stream-failure.mjs +6 -3
- package/src/runtime/agent/orchestrator/providers/opencode-go-gemini.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/provider-availability.mjs +19 -0
- package/src/runtime/agent/orchestrator/providers/provider-error-kind.mjs +65 -1
- package/src/runtime/agent/orchestrator/providers/quota-reading.mjs +38 -12
- package/src/runtime/agent/orchestrator/providers/registry.mjs +43 -3
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +39 -7
- package/src/runtime/agent/orchestrator/providers/statusline-route-meta.mjs +6 -0
- package/src/runtime/agent/orchestrator/providers/wire-protobuf-codec.mjs +368 -0
- package/src/runtime/agent/orchestrator/runtime-core/ask-user-tool-defs.mjs +12 -7
- package/src/runtime/agent/orchestrator/runtime-core/builtin-features.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/compaction-config-keys.mjs +62 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +37 -16
- package/src/runtime/agent/orchestrator/runtime-core/deferred-tool-delta.mjs +100 -23
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/reaction-tool-defs.mjs +133 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-bridge.mjs +19 -4
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog-data.mjs +3 -0
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +125 -30
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +23 -5
- package/src/runtime/agent/orchestrator/runtime-core/tool-profile.mjs +35 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +57 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +9 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow-ids.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/agent-loop.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +0 -3
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +10 -4
- package/src/runtime/agent/orchestrator/session/cache-warm.mjs +792 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +148 -25
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +475 -58
- package/src/runtime/agent/orchestrator/session/compaction-archive.mjs +42 -17
- package/src/runtime/agent/orchestrator/session/eager-dispatch.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +47 -9
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +36 -4
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/loop/assistant-commit.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +18 -22
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +32 -3
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn/segments.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/no-tool-turn.mjs +51 -3
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +17 -3
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/after-hook.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +14 -4
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +16 -2
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +16 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +10 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +3 -10
- package/src/runtime/agent/orchestrator/session/loop/tool-phase.mjs +23 -5
- package/src/runtime/agent/orchestrator/session/loop/turn-limits.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-queue.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-session.mjs +19 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +133 -90
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-loop-options.mjs +14 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-state.mjs +31 -4
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +30 -18
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +23 -13
- package/src/runtime/agent/orchestrator/session/manager/pending-message-entry.mjs +11 -2
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/route-fallback-chain.mjs +129 -0
- package/src/runtime/agent/orchestrator/session/manager/runtime-liveness.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/manager/session-close.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/manager/session-lifecycle.mjs +8 -0
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/session-tool-surface.mjs +13 -0
- package/src/runtime/agent/orchestrator/session/manager/tool-resolution.mjs +7 -2
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +102 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +18 -5
- package/src/runtime/agent/orchestrator/session/message-ids.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/message-reactions.mjs +89 -0
- package/src/runtime/agent/orchestrator/session/message-reactions.test-support.mjs +75 -0
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +19 -8
- package/src/runtime/agent/orchestrator/session/prompt-cache-idle-gaps.mjs +113 -0
- package/src/runtime/agent/orchestrator/session/prompt-cache-touch-store.mjs +127 -0
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +0 -2
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +47 -11
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +20 -2
- package/src/runtime/agent/orchestrator/session/sensitive-projection.mjs +100 -0
- package/src/runtime/agent/orchestrator/session/sensitive-values.mjs +257 -0
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +20 -7
- package/src/runtime/agent/orchestrator/session/side-question.mjs +18 -0
- package/src/runtime/agent/orchestrator/session/skill-update-notice.mjs +60 -0
- package/src/runtime/agent/orchestrator/session/store/live-state.mjs +6 -3
- package/src/runtime/agent/orchestrator/session/store/save-delta.mjs +9 -5
- package/src/runtime/agent/orchestrator/session/store/serialize.mjs +9 -8
- package/src/runtime/agent/orchestrator/session/store/session-persistence-events.mjs +29 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-header-cache.mjs +85 -0
- package/src/runtime/agent/orchestrator/session/store-agent-worker-rows.mjs +133 -18
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +12 -2
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +44 -14
- package/src/runtime/agent/orchestrator/session/store-summary-reader.mjs +84 -3
- package/src/runtime/agent/orchestrator/session/stored-attachment-history.mjs +32 -0
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/thinking-binding.mjs +108 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/pre-dispatch.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/tool-batch/record-outcome.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/tool-batch/state.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-batch.mjs +5 -0
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +25 -2
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-status.mjs +9 -7
- package/src/runtime/agent/orchestrator/session/transcript-restore/tool-result-text.mjs +2 -2
- package/src/runtime/agent/orchestrator/session/turn-failure.mjs +5 -1
- package/src/runtime/agent/orchestrator/tools/builtin/arg-guard.mjs +5 -2
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +12 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/misuse-guard.mjs +240 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/result-format.mjs +1 -1
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/shell-cwd.mjs +38 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +65 -7
- package/src/runtime/agent/orchestrator/tools/builtin/shell-runtime.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +36 -13
- package/src/runtime/browser-bridge/action-schema.mjs +13 -2
- package/src/runtime/browser-bridge/client.mjs +73 -8
- package/src/runtime/browser-bridge/input-fields.mjs +8 -9
- package/src/runtime/browser-bridge/mail-links.mjs +107 -0
- package/src/runtime/browser-bridge/tool-defs.mjs +1 -1
- package/src/runtime/channels/lib/config.mjs +6 -61
- package/src/runtime/channels/lib/crash-log.mjs +1 -1
- package/src/runtime/channels/lib/event-pipeline.mjs +4 -19
- package/src/runtime/channels/lib/event-queue.mjs +3 -18
- package/src/runtime/channels/lib/owned-runtime/automation-services.mjs +2 -2
- package/src/runtime/channels/lib/owned-runtime/automation-start.mjs +3 -4
- package/src/runtime/channels/lib/owned-runtime/config-reload.mjs +4 -59
- package/src/runtime/channels/lib/owned-runtime/owned-start.mjs +33 -115
- package/src/runtime/channels/lib/owned-runtime/owned-stop.mjs +5 -22
- package/src/runtime/channels/lib/owned-runtime/state.mjs +2 -8
- package/src/runtime/channels/lib/owned-runtime.mjs +6 -23
- package/src/runtime/channels/lib/parent-bridge.mjs +2 -2
- package/src/runtime/channels/lib/runtime-paths.mjs +4 -43
- package/src/runtime/channels/lib/schedule-errors.mjs +51 -0
- package/src/runtime/channels/lib/scheduler.mjs +135 -154
- package/src/runtime/channels/lib/schedules-db.mjs +83 -11
- package/src/runtime/channels/lib/status-snapshot.mjs +1 -1
- package/src/runtime/channels/lib/tool-dispatch.mjs +14 -2
- package/src/runtime/channels/lib/webhooks-db.mjs +5 -9
- package/src/runtime/channels/lib/worker-bootstrap.mjs +0 -1
- package/src/runtime/channels/lib/worker-ipc.mjs +0 -1
- package/src/runtime/channels/lib/worker-main.mjs +19 -34
- package/src/runtime/computer-bridge/action-schema.mjs +42 -1
- package/src/runtime/computer-bridge/actions.mjs +9 -1
- package/src/runtime/computer-bridge/client.mjs +180 -8
- package/src/runtime/computer-bridge/core-actions.mjs +2 -2
- package/src/runtime/computer-bridge/error-recovery.mjs +27 -2
- package/src/runtime/computer-bridge/linux-readiness.mjs +65 -23
- package/src/runtime/computer-bridge/pending-continuation.mjs +1 -1
- package/src/runtime/connectors/connector-accounts-mcp.mjs +230 -0
- package/src/runtime/connectors/gmail-app-password.mjs +172 -39
- package/src/runtime/connectors/google-mcp.mjs +10 -12
- package/src/runtime/memory/lib/pg/adapter.mjs +77 -70
- package/src/runtime/memory/lib/pg/supervisor.mjs +24 -12
- package/src/runtime/memory/lib/runtime-fetcher.mjs +23 -5
- package/src/runtime/office/com/com-adapter.mjs +17 -1
- package/src/runtime/office/com/office-com-host.ps1 +51 -1
- package/src/runtime/office/pdf/cfb-fixture.mjs +328 -0
- package/src/runtime/office/pdf/cfb-protection.mjs +311 -0
- package/src/runtime/office/pdf/document-preview.mjs +326 -8
- package/src/runtime/office/pdf/ooxml-crypto.mjs +469 -0
- package/src/runtime/office/pdf/ooxml-key-worker.mjs +14 -0
- package/src/runtime/office/portable/portable-soffice.mjs +45 -5
- package/src/runtime/session-search/session-search-db.mjs +73 -9
- package/src/runtime/session-search/session-search-ingest.mjs +110 -20
- package/src/runtime/shared/abort-race.mjs +89 -0
- package/src/runtime/shared/agent-avatar.d.mts +0 -1
- package/src/runtime/shared/agent-kind.mjs +4 -3
- package/src/runtime/shared/bot-hops.mjs +62 -2
- package/src/runtime/shared/bounded-download.mjs +8 -3
- package/src/runtime/shared/config.mjs +51 -35
- package/src/runtime/shared/connector-accounts.mjs +20 -10
- package/src/runtime/shared/connector-catalog.d.mts +33 -2
- package/src/runtime/shared/connector-catalog.mjs +166 -8
- package/src/runtime/shared/connector-triggers.mjs +22 -19
- package/src/runtime/shared/defaults-separation.mjs +2 -14
- package/src/runtime/shared/delegated-approval-host.mjs +103 -11
- package/src/runtime/shared/developer-options.mjs +32 -1
- package/src/runtime/shared/err-text.mjs +24 -15
- package/src/runtime/shared/file-permissions.d.mts +0 -8
- package/src/runtime/shared/file-permissions.mjs +0 -11
- package/src/runtime/shared/git-direct.test-support.mjs +19 -0
- package/src/runtime/shared/google-oauth-client.generated.json +4 -0
- package/src/runtime/shared/google-oauth-client.mjs +34 -9
- package/src/runtime/shared/heartbeat-interval.d.mts +4 -0
- package/src/runtime/shared/heartbeat-interval.mjs +19 -0
- package/src/runtime/shared/idle-auto-clear-gate.mjs +104 -0
- package/src/runtime/shared/llm/anthropic-betas.mjs +7 -0
- package/src/runtime/shared/llm/anthropic-thinking-contract.mjs +12 -0
- package/src/runtime/shared/llm/provider-catalog-registry.mjs +1 -0
- package/src/runtime/shared/llm/usage-rollup.mjs +1 -1
- package/src/runtime/shared/llm/well-formed.mjs +66 -0
- package/src/runtime/shared/mixy-presence.mjs +22 -0
- package/src/runtime/shared/orchestration.d.mts +2 -0
- package/src/runtime/shared/pg-schema-db.mjs +27 -16
- package/src/runtime/shared/profile-config.cjs +1 -1
- package/src/runtime/shared/prompt-origin.d.mts +4 -0
- package/src/runtime/shared/prompt-origin.mjs +21 -0
- package/src/runtime/shared/provider-accounts.mjs +231 -59
- package/src/runtime/shared/provider-api-key.mjs +19 -8
- package/src/runtime/shared/provider-display-name.mjs +29 -0
- package/src/runtime/shared/provider-timeout-phase.mjs +2 -0
- package/src/runtime/shared/routine-trigger.d.mts +2 -0
- package/src/runtime/shared/routine-trigger.mjs +52 -0
- package/src/runtime/shared/sensitive-path-rules.d.mts +2 -10
- package/src/runtime/shared/sensitive-path-rules.mjs +19 -41
- package/src/runtime/shared/session-activity-signal.mjs +34 -1
- package/src/runtime/shared/session-sidebar-host.d.mts +1 -1
- package/src/runtime/shared/session-sidebar-host.mjs +7 -3
- package/src/runtime/shared/session-turn-prelude.mjs +18 -0
- package/src/runtime/shared/tool-execution-contract.mjs +2 -0
- package/src/runtime/shared/tool-result-summary.mjs +1 -14
- package/src/runtime/shared/transcript-metadata.mjs +38 -0
- package/src/runtime/shared/user-data-guard.mjs +30 -2
- package/src/runtime/shared/worktrees/activity-gate.mjs +12 -5
- package/src/runtime/shared/worktrees/test-support.mjs +9 -4
- package/src/runtime/terminal-bridge/client.mjs +10 -1
- package/src/runtime/visualize-bridge/client.mjs +27 -8
- package/src/runtime/visualize-bridge/tool-defs.mjs +22 -8
- package/src/runtime/visualize-bridge/tool-meta.d.mts +5 -0
- package/src/runtime/visualize-bridge/tool-meta.mjs +22 -0
- package/src/runtime/web-search/lib/fetch-pipeline.mjs +2 -2
- package/src/session-runtime/agent-home-delivery.mjs +486 -179
- package/src/session-runtime/agent-room-delivery.mjs +28 -1
- package/src/session-runtime/agent-task-sessions.mjs +561 -199
- package/src/session-runtime/boot/apis.mjs +9 -0
- package/src/session-runtime/boot/begin.mjs +3 -2
- package/src/session-runtime/boot/infrastructure.mjs +4 -0
- package/src/session-runtime/boot/providers.mjs +3 -0
- package/src/session-runtime/boot/tools.mjs +8 -1
- package/src/session-runtime/bot-chain.mjs +32 -0
- package/src/session-runtime/bot-status.mjs +7 -4
- package/src/session-runtime/channel-config-api.mjs +63 -17
- package/src/session-runtime/config-lifecycle.mjs +6 -0
- package/src/session-runtime/connector-merge-migration.mjs +305 -0
- package/src/session-runtime/internal-tool-executor/ask-user-tool.mjs +132 -28
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +2 -0
- package/src/session-runtime/internal-tool-executor/reaction-tool.mjs +45 -0
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +72 -5
- package/src/session-runtime/internal-tool-executor.mjs +4 -0
- package/src/session-runtime/lifecycle/inheritance.mjs +16 -3
- package/src/session-runtime/lifecycle/session-catalog.mjs +3 -1
- package/src/session-runtime/lifecycle/teardown/runtime-stops.mjs +50 -4
- package/src/session-runtime/lifecycle/teardown.mjs +23 -5
- package/src/session-runtime/mcp-glue/server-catalog.mjs +61 -3
- package/src/session-runtime/mcp-oauth-login.mjs +5 -3
- package/src/session-runtime/mixy-heartbeat.mjs +927 -0
- package/src/session-runtime/model-route/route-tuning.mjs +10 -6
- package/src/session-runtime/notification-bus.mjs +31 -117
- package/src/session-runtime/notification-completion-queue.mjs +54 -0
- package/src/session-runtime/notification-listeners.mjs +76 -0
- package/src/session-runtime/owned-automation-run.mjs +35 -13
- package/src/session-runtime/prewarm/channel-start.mjs +3 -7
- package/src/session-runtime/provider-auth/login.mjs +7 -0
- package/src/session-runtime/provider-models/row-hydration.mjs +13 -2
- package/src/session-runtime/resource-mcp-api.mjs +143 -224
- package/src/session-runtime/resource-mcp-connector-accounts.mjs +163 -0
- package/src/session-runtime/resource-mcp-oauth-api.mjs +217 -0
- package/src/session-runtime/resource-mcp-secrets.mjs +82 -0
- package/src/session-runtime/resource-mcp-servers.mjs +78 -0
- package/src/session-runtime/resource-mcp-store.mjs +49 -0
- package/src/session-runtime/routine-home-turn.mjs +33 -6
- package/src/session-runtime/routine-owner.mjs +40 -9
- package/src/session-runtime/schedule-session-run.mjs +7 -3
- package/src/session-runtime/services/agent-tool/close-flow.mjs +3 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-plan.mjs +8 -12
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +7 -2
- package/src/session-runtime/services/agent-tool/tag-registry.mjs +1 -0
- package/src/session-runtime/services/agent-tool/worker-index/awaiting-user-sync.mjs +51 -0
- package/src/session-runtime/services/agent-tool/worker-index/disposal.mjs +55 -0
- package/src/session-runtime/services/agent-tool/worker-index/released-store.mjs +123 -0
- package/src/session-runtime/services/agent-tool/worker-index/restart-settlement.mjs +112 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-reads.mjs +36 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-shape.mjs +2 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-store.mjs +17 -4
- package/src/session-runtime/services/agent-tool/worker-index/row-writes.mjs +58 -0
- package/src/session-runtime/services/agent-tool/worker-index.mjs +41 -241
- package/src/session-runtime/services/agent-tool/worker-rows.mjs +3 -0
- package/src/session-runtime/services/channel-admin.mjs +73 -31
- package/src/session-runtime/services/channel-session-router.mjs +4 -2
- package/src/session-runtime/services/channel-worker.mjs +1 -1
- package/src/session-runtime/services/provider-admin.mjs +128 -25
- package/src/session-runtime/services/schedule-access.mjs +14 -0
- package/src/session-runtime/services/seeds.mjs +0 -28
- package/src/session-runtime/services/usage-dashboard-model.mjs +1 -0
- package/src/session-runtime/session-compaction-sync.mjs +27 -0
- package/src/session-runtime/session-create/session-options.mjs +8 -4
- package/src/session-runtime/settings-builtin-tools-api.mjs +11 -0
- package/src/session-runtime/settings-compaction-api.mjs +2 -40
- package/src/session-runtime/settings-system-api.mjs +28 -1
- package/src/session-runtime/setup-tool/agent-settings-actions.mjs +65 -0
- package/src/session-runtime/setup-tool/executor.mjs +187 -29
- package/src/session-runtime/setup-tool/extended-actions.mjs +33 -2
- package/src/session-runtime/setup-tool/session-route-actions.mjs +152 -0
- package/src/session-runtime/setup-tool/settings-contract.mjs +68 -6
- package/src/session-runtime/setup-tool/tool-defs.mjs +92 -6
- package/src/session-runtime/task-session-route.mjs +99 -0
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +17 -13
- package/src/session-runtime/tool-policy/policy-stage.mjs +21 -4
- package/src/session-runtime/tool-policy/surface-helpers.mjs +2 -3
- package/src/session-runtime/tool-surface.mjs +4 -0
- package/src/session-runtime/turn/turn-run/ask-callbacks.mjs +1 -0
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +98 -21
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +4 -0
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -1
- package/src/session-runtime/workspace/transitions.mjs +10 -2
- package/src/standalone/channel-transport/client-registry/client-records.mjs +65 -0
- package/src/standalone/channel-transport/client-registry/client-registration.mjs +118 -0
- package/src/standalone/channel-transport/client-registry.mjs +24 -163
- package/src/standalone/channel-transport.mjs +1 -1
- package/src/standalone/daemon-channels-loader.mjs +10 -8
- package/src/standalone/daemon-session-control/list-page.mjs +72 -0
- package/src/standalone/daemon-session-control/request.mjs +51 -0
- package/src/standalone/daemon-session-control/room-host.mjs +57 -0
- package/src/standalone/daemon-session-control/route-control.mjs +48 -0
- package/src/standalone/daemon-session-control/send-turn.mjs +60 -0
- package/src/standalone/daemon-session-control/worktree-control.mjs +40 -0
- package/src/standalone/daemon-session-control.mjs +161 -157
- package/src/standalone/daemon.mjs +33 -2
- package/src/standalone/session-client.mjs +7 -3
- package/src/standalone/session-protocol.mjs +10 -0
- package/src/standalone/session-service/agent-tree/agent-cancel.mjs +6 -2
- package/src/standalone/session-service/agent-tree/agent-child.mjs +1 -0
- package/src/standalone/session-service/quota-resume.mjs +3 -0
- package/src/standalone/session-service/retention/busy.mjs +65 -1
- package/src/standalone/session-service/session-calls/session-action.mjs +23 -2
- package/src/standalone/session-service/session-calls/session-turns.mjs +6 -0
- package/src/standalone/session-service/session-calls/session-views.mjs +8 -0
- package/src/standalone/session-service.mjs +13 -0
- package/src/standalone/session-tool-output.mjs +21 -10
- package/src/tui/app/model-options.mjs +4 -27
- package/src/tui/dist/index.mjs +81 -65
- package/src/tui/session/agent-job-feed/model-notification.mjs +3 -0
- package/src/tui/session/initial-state.mjs +3 -0
- package/src/tui/session/notice-surface.mjs +23 -1
- package/src/tui/session/queue-helpers.mjs +3 -42
- package/src/tui/session/session-api/integrations.mjs +1 -0
- package/src/tui/session/session-api/lifecycle.mjs +8 -0
- package/src/tui/session/session-api/routes-agent-definitions.mjs +14 -2
- package/src/tui/session/session-api/routes-write.mjs +5 -1
- package/src/tui/session/session-api/routes.mjs +1 -0
- package/src/tui/session/session-api/settings.mjs +30 -2
- package/src/tui/session/session-flow/auto-clear.mjs +4 -33
- package/src/tui/session/session-flow/drain-closed-queue.mjs +38 -0
- package/src/tui/session/session-flow/drain-enqueue.mjs +86 -0
- package/src/tui/session/session-flow/drain-retry.mjs +35 -0
- package/src/tui/session/session-flow/drain.mjs +118 -149
- package/src/tui/session/session-flow/queue/entry-shape.mjs +5 -0
- package/src/tui/session/session-flow/queue/take-entries.mjs +5 -2
- package/src/tui/session/tool-approval-delegation.mjs +64 -0
- package/src/tui/session/tool-approval-request.mjs +39 -0
- package/src/tui/session/tool-approval-settle.mjs +62 -0
- package/src/tui/session/tool-approval.mjs +69 -115
- package/src/tui/session/tui-steering-persist.mjs +8 -1
- package/src/tui/session/turn-stream.mjs +36 -3
- package/src/tui/session/turn.mjs +23 -2
- package/src/vendor/statusline/bin/statusline-route.mjs +1 -219
- package/src/vendor/statusline/src/gateway/route-meta.mjs +35 -13
- package/scripts/code-graph-description-contract.mjs +0 -188
- package/src/runtime/channels/lib/owned-runtime/ownership-refresh.mjs +0 -36
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// The stored form of a message once its session has sensitive values
|
|
2
|
+
// (sensitive-values.mjs): only what a message carries as text changes —
|
|
3
|
+
// - its content's text (media-normalization.mjs mapContentText: a string,
|
|
4
|
+
// text parts, a part's string content; media and attachment references
|
|
5
|
+
// stay as they are), tool results included;
|
|
6
|
+
// - its tool calls' argument values (the arguments object, or the JSON text
|
|
7
|
+
// a provider sends them as, parsed and written again).
|
|
8
|
+
// Ids, roles, timestamps and every other field stay as they are. Text that is
|
|
9
|
+
// a JSON document stays one: its string values are redacted inside it, a
|
|
10
|
+
// number that spells a value becomes the marker string, and its keys are kept.
|
|
11
|
+
// A message that changes, or whose provider replay state holds a value, is
|
|
12
|
+
// stored without that state (manager/provider-replay.mjs): the state belongs
|
|
13
|
+
// to the exact message the provider produced, and a provider would check it
|
|
14
|
+
// against what it sent — as after a route change, the text and tool calls stay.
|
|
15
|
+
import { mapContentText } from '../providers/media-normalization.mjs';
|
|
16
|
+
import { providerReplayState, stripProviderReplayMessage } from './manager/provider-replay.mjs';
|
|
17
|
+
import { MARKER, containsSensitive, redactSensitiveText, sensitiveValuesVersion } from './sensitive-values.mjs';
|
|
18
|
+
|
|
19
|
+
function redactJsonValue(sessionId, value) {
|
|
20
|
+
if (typeof value === 'string') return redactTextOrJson(sessionId, value);
|
|
21
|
+
if (typeof value === 'number') return containsSensitive(sessionId, String(value)) ? MARKER : value;
|
|
22
|
+
if (!value || typeof value !== 'object') return value;
|
|
23
|
+
const list = Array.isArray(value);
|
|
24
|
+
const out = list ? [] : {};
|
|
25
|
+
let changed = false;
|
|
26
|
+
for (const [key, item] of Object.entries(value)) {
|
|
27
|
+
const next = redactJsonValue(sessionId, item);
|
|
28
|
+
if (next !== item) changed = true;
|
|
29
|
+
out[key] = next;
|
|
30
|
+
}
|
|
31
|
+
return changed ? out : value;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** Text redacted as text, or — when it is a JSON object, array or string (RFC 8259) —
|
|
35
|
+
* as the JSON document it is, so it still parses afterwards. */
|
|
36
|
+
function redactTextOrJson(sessionId, text) {
|
|
37
|
+
// A JSON document is judged by its decoded values, never its raw text: an
|
|
38
|
+
// escape (`\u0036…`) spells a value the raw text does not contain.
|
|
39
|
+
const head = text.trimStart()[0];
|
|
40
|
+
if (head === '{' || head === '[' || head === '"') {
|
|
41
|
+
let parsed;
|
|
42
|
+
try {
|
|
43
|
+
parsed = JSON.parse(text);
|
|
44
|
+
} catch {
|
|
45
|
+
parsed = undefined;
|
|
46
|
+
}
|
|
47
|
+
if ((parsed && typeof parsed === 'object') || typeof parsed === 'string') {
|
|
48
|
+
const redacted = redactJsonValue(sessionId, parsed);
|
|
49
|
+
return redacted === parsed ? text : JSON.stringify(redacted);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return redactSensitiveText(sessionId, text);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Message content with the session's values redacted in its text. */
|
|
56
|
+
export function redactSensitiveContent(sessionId, content) {
|
|
57
|
+
if (!sensitiveValuesVersion(sessionId)) return content;
|
|
58
|
+
return mapContentText(content, (text) => redactTextOrJson(sessionId, text));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** A tool call with the session's values redacted in its argument values. */
|
|
62
|
+
export function redactSensitiveToolCall(sessionId, call) {
|
|
63
|
+
if (!sensitiveValuesVersion(sessionId) || !call || typeof call !== 'object' || !('arguments' in call)) return call;
|
|
64
|
+
const args = redactJsonValue(sessionId, call.arguments);
|
|
65
|
+
return args === call.arguments ? call : { ...call, arguments: args };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** `message` as it is stored for `sessionId`; the same object when unchanged. */
|
|
69
|
+
export function redactSensitiveMessage(sessionId, message) {
|
|
70
|
+
if (!sensitiveValuesVersion(sessionId) || !message || typeof message !== 'object') return message;
|
|
71
|
+
const out = { ...message };
|
|
72
|
+
let changed = false;
|
|
73
|
+
if ('content' in message) {
|
|
74
|
+
out.content = redactSensitiveContent(sessionId, message.content);
|
|
75
|
+
changed = out.content !== message.content;
|
|
76
|
+
}
|
|
77
|
+
if (Array.isArray(message.toolCalls)) {
|
|
78
|
+
const calls = message.toolCalls.map((call) => redactSensitiveToolCall(sessionId, call));
|
|
79
|
+
if (calls.some((call, index) => call !== message.toolCalls[index])) {
|
|
80
|
+
out.toolCalls = calls;
|
|
81
|
+
changed = true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const replay = providerReplayState(message);
|
|
85
|
+
const replayHolds = replay !== null && containsSensitive(sessionId, JSON.stringify(replay));
|
|
86
|
+
if (!changed && !replayHolds) return message;
|
|
87
|
+
return replay === null ? out : stripProviderReplayMessage(out);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/** Messages as they are stored for `sessionId`; the same array when unchanged. */
|
|
91
|
+
export function redactSensitiveMessages(sessionId, messages) {
|
|
92
|
+
if (!sensitiveValuesVersion(sessionId) || !Array.isArray(messages)) return messages;
|
|
93
|
+
let changed = false;
|
|
94
|
+
const out = messages.map((message) => {
|
|
95
|
+
const next = redactSensitiveMessage(sessionId, message);
|
|
96
|
+
if (next !== message) changed = true;
|
|
97
|
+
return next;
|
|
98
|
+
});
|
|
99
|
+
return changed ? out : messages;
|
|
100
|
+
}
|
|
@@ -0,0 +1,257 @@
|
|
|
1
|
+
// Values a tool call marked sensitive — a browser `fill` with sensitive:true,
|
|
2
|
+
// such as a verification code read from a mail — remembered per session and
|
|
3
|
+
// replaced by a marker wherever the session's transcript is stored: the
|
|
4
|
+
// session file (store/serialize.mjs), the crash-recovery checkpoint
|
|
5
|
+
// (manager/turn-checkpoint-journal.mjs) and the search copy
|
|
6
|
+
// (session-search/session-search-ingest.mjs), earlier records that carried
|
|
7
|
+
// the value included: marking one notifies the running turn, which rewrites
|
|
8
|
+
// them (manager/ask-turn-state.mjs). The live transcript the provider sees is
|
|
9
|
+
// left as sent (its prefix must not change); the desktop redacts the value in
|
|
10
|
+
// its own replies (browser/actions/forms.ts).
|
|
11
|
+
//
|
|
12
|
+
// A value stays known for the life of the process — as long as any plaintext
|
|
13
|
+
// copy of it (the loaded session) can still be written — and is never
|
|
14
|
+
// evicted. In text, replacement is the exact substring, longest spelling
|
|
15
|
+
// first, so a shorter value never splits a longer one. Which parts of a
|
|
16
|
+
// message are text is sensitive-projection.mjs's business.
|
|
17
|
+
//
|
|
18
|
+
// A marker is opaque to every redaction (the one written here, and the one the
|
|
19
|
+
// desktop writes for a saved login, which reaches the transcript in replies):
|
|
20
|
+
// a value is matched whole against the text and replaced unless that
|
|
21
|
+
// occurrence lies entirely inside markers already there, so redacting twice
|
|
22
|
+
// is redacting once and a value such as `e` never rewrites a marker's letters,
|
|
23
|
+
// while a value that contains or spans marker text is still replaced.
|
|
24
|
+
export const MARKER = '[redacted]';
|
|
25
|
+
/** What the desktop writes for a Computer Use saved-login password
|
|
26
|
+
* (apps/desktop/src/main/secret-markers.ts). */
|
|
27
|
+
export const SAVED_LOGIN_MARKER = '[saved login]';
|
|
28
|
+
/** Every marker a redactor writes. */
|
|
29
|
+
const MARKERS = [MARKER, SAVED_LOGIN_MARKER];
|
|
30
|
+
/** sessionId → { version, values: Set(value), spellings: string[] (longest first) } */
|
|
31
|
+
const bySession = new Map();
|
|
32
|
+
/** sessionId → Set(listener) notified synchronously when the session gains a value. */
|
|
33
|
+
const listeners = new Map();
|
|
34
|
+
|
|
35
|
+
function fillValues(call) {
|
|
36
|
+
let args = call?.arguments;
|
|
37
|
+
if (typeof args === 'string') {
|
|
38
|
+
try {
|
|
39
|
+
args = JSON.parse(args);
|
|
40
|
+
} catch {
|
|
41
|
+
return [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (call?.name !== 'browser' || args?.action !== 'fill') return [];
|
|
45
|
+
const input = args.input && typeof args.input === 'object' ? args.input : args;
|
|
46
|
+
if (input.sensitive !== true) return [];
|
|
47
|
+
const fields = Array.isArray(input.fields) ? input.fields : [];
|
|
48
|
+
return [input.text, ...fields.flatMap((field) => [field?.text, field?.value])].filter(
|
|
49
|
+
(value) => typeof value === 'string' && value.length > 0
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Remembers the sensitive values of execution calls `calls` for `sessionId`
|
|
54
|
+
* and, when one is new, tells the session's listeners before returning (so
|
|
55
|
+
* before the call runs). True when the session gained a value. */
|
|
56
|
+
export function rememberSensitiveValues(sessionId, calls) {
|
|
57
|
+
const values = (Array.isArray(calls) ? calls : []).flatMap(fillValues);
|
|
58
|
+
if (!sessionId || !values.length) return false;
|
|
59
|
+
const known = bySession.get(sessionId) ?? { version: 0, values: new Set(), spellings: [] };
|
|
60
|
+
const fresh = values.filter((value) => !known.values.has(value));
|
|
61
|
+
if (!fresh.length) return false;
|
|
62
|
+
const spellings = new Set(known.spellings);
|
|
63
|
+
for (const value of fresh) {
|
|
64
|
+
known.values.add(value);
|
|
65
|
+
// As typed, and as a JSON string spells it (an echo may keep arguments as JSON text).
|
|
66
|
+
spellings.add(value).add(JSON.stringify(value).slice(1, -1));
|
|
67
|
+
}
|
|
68
|
+
known.spellings = [...spellings].sort((a, b) => b.length - a.length);
|
|
69
|
+
known.version += 1;
|
|
70
|
+
bySession.set(sessionId, known);
|
|
71
|
+
for (const listener of [...(listeners.get(sessionId) || [])]) {
|
|
72
|
+
try {
|
|
73
|
+
listener(known.version);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
try {
|
|
76
|
+
process.stderr.write(`[sensitive-values] listener failed session=${sessionId}: ${error?.message || error}\n`);
|
|
77
|
+
} catch {
|
|
78
|
+
// stderr may be closed; the listener failure is only reported
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Calls `listener` whenever `sessionId` gains a sensitive value; returns the
|
|
86
|
+
* function that stops it. */
|
|
87
|
+
export function onSensitiveValuesMarked(sessionId, listener) {
|
|
88
|
+
if (!sessionId || typeof listener !== 'function') return () => {};
|
|
89
|
+
const set = listeners.get(sessionId) ?? new Set();
|
|
90
|
+
set.add(listener);
|
|
91
|
+
listeners.set(sessionId, set);
|
|
92
|
+
return () => {
|
|
93
|
+
set.delete(listener);
|
|
94
|
+
if (!set.size && listeners.get(sessionId) === set) listeners.delete(sessionId);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Changes whenever `sessionId` gains a value, so a stored copy made before
|
|
99
|
+
* is written again in full (store/save-delta.mjs, the turn checkpoint, the
|
|
100
|
+
* search rows). */
|
|
101
|
+
export function sensitiveValuesVersion(sessionId) {
|
|
102
|
+
return bySession.get(sessionId)?.version || 0;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** [start, end) of every marker in `text`, in order. */
|
|
106
|
+
function markerSpans(text) {
|
|
107
|
+
const spans = [];
|
|
108
|
+
for (const marker of MARKERS) {
|
|
109
|
+
for (let at = text.indexOf(marker); at >= 0; at = text.indexOf(marker, at + marker.length)) {
|
|
110
|
+
spans.push([at, at + marker.length]);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
return spans.sort((a, b) => a[0] - b[0]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** `text` with each occurrence of `spelling` replaced by MARKER, except one
|
|
117
|
+
* lying entirely inside markers. An occurrence that partly overlaps a marker
|
|
118
|
+
* replaces that marker whole, so no marker is left broken. */
|
|
119
|
+
function redactSpelling(text, spelling) {
|
|
120
|
+
const spans = markerSpans(text);
|
|
121
|
+
let next = '';
|
|
122
|
+
let copied = 0;
|
|
123
|
+
for (let at = text.indexOf(spelling); at >= 0; ) {
|
|
124
|
+
const end = at + spelling.length;
|
|
125
|
+
const touched = spans.filter(([from, to]) => from < end && at < to);
|
|
126
|
+
let reach = at;
|
|
127
|
+
for (const [from, to] of touched) {
|
|
128
|
+
if (from > reach) break;
|
|
129
|
+
reach = Math.max(reach, to);
|
|
130
|
+
}
|
|
131
|
+
if (reach >= end) {
|
|
132
|
+
at = text.indexOf(spelling, at + 1);
|
|
133
|
+
continue;
|
|
134
|
+
}
|
|
135
|
+
const from = Math.min(at, touched[0]?.[0] ?? at);
|
|
136
|
+
const to = Math.max(end, touched.at(-1)?.[1] ?? end);
|
|
137
|
+
next += text.slice(copied, from) + MARKER;
|
|
138
|
+
copied = to;
|
|
139
|
+
at = text.indexOf(spelling, to);
|
|
140
|
+
}
|
|
141
|
+
return next + text.slice(copied);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** `text` with every spelling (longest first) replaced by MARKER, passes
|
|
145
|
+
* repeated until no spelling occurs outside the markers: a replacement can
|
|
146
|
+
* join a spelling that holds marker text (`a[redacted]`) to the text before
|
|
147
|
+
* it, so one pass is not enough. The repetition ends without a bound: every
|
|
148
|
+
* replacement covers at least one character outside the markers and takes in
|
|
149
|
+
* whole every marker it touches (writing one marker, which adds no character
|
|
150
|
+
* outside), so each replacement lowers the number of characters outside the
|
|
151
|
+
* markers and a pass that changes nothing comes after at most that many.
|
|
152
|
+
* The result is stable: redacting it again changes nothing, and a later,
|
|
153
|
+
* shorter spelling cannot match inside a marker an earlier one wrote. */
|
|
154
|
+
function redactText(text, spellings) {
|
|
155
|
+
let out = text;
|
|
156
|
+
let before;
|
|
157
|
+
do {
|
|
158
|
+
before = out;
|
|
159
|
+
for (const spelling of spellings) out = redactSpelling(out, spelling);
|
|
160
|
+
} while (out !== before);
|
|
161
|
+
return out;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Text `text` with the session's sensitive values replaced; `text` itself
|
|
165
|
+
* when none occurs (or it is no string). */
|
|
166
|
+
export function redactSensitiveText(sessionId, text) {
|
|
167
|
+
const known = bySession.get(sessionId);
|
|
168
|
+
return known?.spellings.length && typeof text === 'string' ? redactText(text, known.spellings) : text;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** Whether text `text` holds one of the session's sensitive values. */
|
|
172
|
+
export function containsSensitive(sessionId, text) {
|
|
173
|
+
const known = bySession.get(sessionId);
|
|
174
|
+
return Boolean(known && typeof text === 'string' && known.spellings.some((spelling) => text.includes(spelling)));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/** Where the longest tail of `text` that is a proper prefix of one of
|
|
178
|
+
* `strings` starts; the end of `text` when there is none. */
|
|
179
|
+
function pendingStart(text, strings) {
|
|
180
|
+
let start = text.length;
|
|
181
|
+
for (const string of strings) {
|
|
182
|
+
for (let length = Math.min(string.length - 1, text.length); length > text.length - start; length -= 1) {
|
|
183
|
+
if (text.endsWith(string.slice(0, length))) {
|
|
184
|
+
start = text.length - length;
|
|
185
|
+
break;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return start;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** `start`, moved left to the start of every one of `strings` that lies
|
|
193
|
+
* across it, until none does. */
|
|
194
|
+
function coverStart(text, start, strings) {
|
|
195
|
+
for (let moved = true; moved; ) {
|
|
196
|
+
moved = false;
|
|
197
|
+
for (const string of strings) {
|
|
198
|
+
for (let at = text.indexOf(string); at >= 0 && at < start; at = text.indexOf(string, at + 1)) {
|
|
199
|
+
if (start < at + string.length) {
|
|
200
|
+
start = at;
|
|
201
|
+
moved = true;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return start;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/** How much of the redacted text `out` no later text can change: the start of
|
|
210
|
+
* the held part, `out.length` when nothing is held. Held is every part that
|
|
211
|
+
* may still be taken into a replacement:
|
|
212
|
+
* - the tail that starts a value (or a marker) and may yet complete it;
|
|
213
|
+
* - the marker such a tail starts inside, which a replacement takes in whole;
|
|
214
|
+
* - the text before the held part that would join it, once it is replaced by
|
|
215
|
+
* a marker, into a value holding marker text, as `a` before the held part
|
|
216
|
+
* does for `a[redacted]`; this repeats, as the joined part is held too. */
|
|
217
|
+
function settledEnd(out, spellings) {
|
|
218
|
+
const spans = markerSpans(out);
|
|
219
|
+
const longest = spellings[0].length;
|
|
220
|
+
let end = pendingStart(out, [...spellings, ...MARKERS]);
|
|
221
|
+
for (let moved = true; moved; ) {
|
|
222
|
+
moved = false;
|
|
223
|
+
const marker = spans.find(([start, stop]) => start < end && end < stop);
|
|
224
|
+
if (marker) {
|
|
225
|
+
end = marker[0];
|
|
226
|
+
moved = true;
|
|
227
|
+
}
|
|
228
|
+
for (let start = Math.max(0, end - longest + 1); start < end; start += 1) {
|
|
229
|
+
const part = out.slice(start, end);
|
|
230
|
+
const joins = (spelling) =>
|
|
231
|
+
spelling.length > part.length &&
|
|
232
|
+
MARKERS.some((m) => spelling.startsWith(part + m) || (part + m).startsWith(spelling));
|
|
233
|
+
if (spellings.some(joins)) {
|
|
234
|
+
end = start;
|
|
235
|
+
moved = true;
|
|
236
|
+
break;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
return end;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** Text that is still streaming, redacted, without the part that later text
|
|
244
|
+
* can still change: what of `text` can be journaled now
|
|
245
|
+
* (manager/turn-checkpoint-journal.mjs). As the text grows each result starts
|
|
246
|
+
* the next, and none shows what the finished text's redaction hides. The tail
|
|
247
|
+
* of `text` that starts a value or a marker is cut off before redacting (a
|
|
248
|
+
* replacement inside a half-written marker is undone when it is completed),
|
|
249
|
+
* with every value or marker lying across the cut; settledEnd then holds
|
|
250
|
+
* back what of the redacted rest a replacement can still reach. */
|
|
251
|
+
export function redactSettledText(sessionId, text) {
|
|
252
|
+
const known = bySession.get(sessionId);
|
|
253
|
+
if (!known?.spellings.length || typeof text !== 'string') return text;
|
|
254
|
+
const pending = [...known.spellings, ...MARKERS];
|
|
255
|
+
const out = redactText(text.slice(0, coverStart(text, pendingStart(text, pending), pending)), known.spellings);
|
|
256
|
+
return out.slice(0, settledEnd(out, known.spellings));
|
|
257
|
+
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
// side question or a compaction summary forks) and the side question in flight. It holds live
|
|
4
4
|
// provider/tool objects, so it never reaches the session record or disk.
|
|
5
5
|
import { isAgentOwner } from '../agent-owner.mjs';
|
|
6
|
+
import { armCacheWarm, cancelCacheWarm, forgetCacheWarm, noteCacheWarmActivity } from './cache-warm.mjs';
|
|
6
7
|
|
|
7
8
|
// sessionId -> { provider, model, messages, tools, opts, response }
|
|
8
9
|
const requests = new Map();
|
|
@@ -42,21 +43,27 @@ export function sideQuestionRequestSnapshot({ provider, model, messages, tools,
|
|
|
42
43
|
|
|
43
44
|
/**
|
|
44
45
|
* Record a provider request the main loop is sending as the session's latest
|
|
45
|
-
* one. Agent sessions
|
|
46
|
-
*
|
|
47
|
-
*
|
|
46
|
+
* one. Agent sessions keep nothing here (/btw is a Lead command); their
|
|
47
|
+
* request only reaches the cache warmer. The handle attaches the reply once
|
|
48
|
+
* the send proceeds, or puts the previous request back when this one fails,
|
|
49
|
+
* so only a successfully sent request is kept.
|
|
48
50
|
*/
|
|
49
51
|
export function captureSideQuestionRequest({ sessionId, sessionRef, provider, model, messages, tools, opts }) {
|
|
50
|
-
if (!sessionId || !provider
|
|
52
|
+
if (!sessionId || !provider) return null;
|
|
53
|
+
const kept = !isAgentOwner(sessionRef);
|
|
51
54
|
const previous = requests.get(sessionId);
|
|
52
55
|
const request = sideQuestionRequestSnapshot({ provider, model, messages, tools, opts });
|
|
53
|
-
requests.set(sessionId, request);
|
|
56
|
+
if (kept) requests.set(sessionId, request);
|
|
57
|
+
// A real request is session activity: it closes the session's idle gap and
|
|
58
|
+
// replaces any cache warm (cache-warm.mjs); a settled one arms the next.
|
|
59
|
+
noteCacheWarmActivity(sessionId, request);
|
|
54
60
|
return {
|
|
55
61
|
settle(response) {
|
|
56
62
|
request.response = response || null;
|
|
63
|
+
armCacheWarm(sessionId, request, response);
|
|
57
64
|
},
|
|
58
65
|
discard() {
|
|
59
|
-
if (requests.get(sessionId) !== request) return;
|
|
66
|
+
if (!kept || requests.get(sessionId) !== request) return;
|
|
60
67
|
if (previous) requests.set(sessionId, previous);
|
|
61
68
|
else requests.delete(sessionId);
|
|
62
69
|
},
|
|
@@ -80,6 +87,7 @@ export function compactionForkRequest(sessionId) {
|
|
|
80
87
|
export function retireCompactionFork(sessionId) {
|
|
81
88
|
const request = sideQuestionRequest(sessionId);
|
|
82
89
|
if (request) request.compacted = true;
|
|
90
|
+
forgetCacheWarm(sessionId);
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
/** Start the session's side question, aborting the one still in flight. */
|
|
@@ -109,5 +117,10 @@ export function cancelSideQuestion(sessionId) {
|
|
|
109
117
|
export function forgetSideQuestion(sessionId, { cancel = false } = {}) {
|
|
110
118
|
if (!sessionId) return;
|
|
111
119
|
requests.delete(sessionId);
|
|
112
|
-
|
|
120
|
+
// Clear / rewind drop the cached prefix; a close or runtime unload (daemon
|
|
121
|
+
// shutdown) keeps its durable touch for the restart re-arm.
|
|
122
|
+
if (cancel) {
|
|
123
|
+
cancelCacheWarm(sessionId);
|
|
124
|
+
cancelSideQuestion(sessionId);
|
|
125
|
+
} else forgetCacheWarm(sessionId);
|
|
113
126
|
}
|
|
@@ -98,6 +98,24 @@ function nextMainRequest(sessionId) {
|
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
/**
|
|
102
|
+
* The session's last provider request as its next turn would rebuild it from
|
|
103
|
+
* the record (a runtime restart lost the in-memory one): the next request
|
|
104
|
+
* without the reply that last request produced, stamped with the session's
|
|
105
|
+
* last send time. Null when the transcript does not end in that reply or the
|
|
106
|
+
* session has no rebuildable request (nextMainRequest).
|
|
107
|
+
*/
|
|
108
|
+
export function lastSentMainRequest(sessionId) {
|
|
109
|
+
const request = nextMainRequest(sessionId);
|
|
110
|
+
if (!request) return null;
|
|
111
|
+
let end = request.messages.length;
|
|
112
|
+
while (end > 0 && request.messages[end - 1]?.role === 'assistant') end -= 1;
|
|
113
|
+
if (end === 0 || end === request.messages.length) return null;
|
|
114
|
+
const sentAt = Number(request.opts.session?.lastProviderSendAt);
|
|
115
|
+
if (!(sentAt > 0)) return null;
|
|
116
|
+
return { ...request, messages: request.messages.slice(0, end), sentAt };
|
|
117
|
+
}
|
|
118
|
+
|
|
101
119
|
function answerText(response) {
|
|
102
120
|
if (response?.toolCalls?.length) return SIDE_QUESTION_TOOL_ATTEMPT;
|
|
103
121
|
const text = typeof response?.content === 'string' ? response.content.trim() : '';
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// A thread can live for days while its loaded skills change underneath it (an
|
|
2
|
+
// app update ships a new SKILL.md, the user edits or switches one off). Compact
|
|
3
|
+
// restores every skill from its current source; between Compacts, each turn
|
|
4
|
+
// tells the model once that a skill it holds instructions for changed, as a
|
|
5
|
+
// new runtime message after the history, never by rewriting what was sent.
|
|
6
|
+
import { createHash } from 'node:crypto';
|
|
7
|
+
import { loadConfig } from '../config.mjs';
|
|
8
|
+
import { collectSkillsCached, currentSkillDelivery } from '../context/collect.mjs';
|
|
9
|
+
import { latestSkillBodies, skillMessageText } from '../context/skill-state.mjs';
|
|
10
|
+
|
|
11
|
+
export const SKILL_UPDATE_SOURCE = 'skill-update-notice';
|
|
12
|
+
|
|
13
|
+
const UNAVAILABLE = 'unavailable';
|
|
14
|
+
// The loaded body is what the Skill tool delivers again (after an
|
|
15
|
+
// unavailable notice): its instructions are back in force.
|
|
16
|
+
const CURRENT = 'current';
|
|
17
|
+
|
|
18
|
+
function skillUpdateNotice(name, version) {
|
|
19
|
+
let text = `[mixdog-runtime] Skill "${name}" was updated since it was loaded. Reload it with the Skill tool before relying on it.`;
|
|
20
|
+
if (version === UNAVAILABLE) {
|
|
21
|
+
text = `[mixdog-runtime] Skill "${name}" is no longer available in this session (switched off, removed, or outside this project's scope). Do not rely on its instructions.`;
|
|
22
|
+
} else if (version === CURRENT) {
|
|
23
|
+
text = `[mixdog-runtime] Skill "${name}" is available again, unchanged; its loaded instructions apply again.`;
|
|
24
|
+
}
|
|
25
|
+
return { role: 'user', content: text, meta: { source: SKILL_UPDATE_SOURCE, synthetic: true, skill: name, version } };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* For every skill whose latest body is in `messages`, compare it with what the
|
|
30
|
+
* Skill tool would deliver at `cwd` now. Its state — the loaded body still
|
|
31
|
+
* current, a changed version, or unavailable — is announced whenever it
|
|
32
|
+
* differs from the last state told after that body (none told: current).
|
|
33
|
+
* So each transition notifies once: changed, switched off, and back again
|
|
34
|
+
* unchanged. A reload replaces the body and starts over. Returns the
|
|
35
|
+
* appended notices.
|
|
36
|
+
*/
|
|
37
|
+
export function appendSkillUpdateNotices(messages, cwd) {
|
|
38
|
+
const bodies = latestSkillBodies(messages);
|
|
39
|
+
if (!bodies.length) return [];
|
|
40
|
+
const config = loadConfig({ secrets: false });
|
|
41
|
+
const skills = collectSkillsCached(cwd);
|
|
42
|
+
const notices = [];
|
|
43
|
+
for (const { name, message } of bodies) {
|
|
44
|
+
const delivery = currentSkillDelivery(name, cwd, config, skills);
|
|
45
|
+
let version = UNAVAILABLE;
|
|
46
|
+
if (delivery) {
|
|
47
|
+
version =
|
|
48
|
+
skillMessageText(message.content).trimStart() === delivery.body
|
|
49
|
+
? CURRENT
|
|
50
|
+
: createHash('sha256').update(delivery.body).digest('hex').slice(0, 16);
|
|
51
|
+
}
|
|
52
|
+
const told =
|
|
53
|
+
messages
|
|
54
|
+
.slice(messages.lastIndexOf(message) + 1)
|
|
55
|
+
.findLast((m) => m?.meta?.source === SKILL_UPDATE_SOURCE && m.meta.skill === name)?.meta.version || CURRENT;
|
|
56
|
+
if (told !== version) notices.push(skillUpdateNotice(name, version));
|
|
57
|
+
}
|
|
58
|
+
messages.push(...notices);
|
|
59
|
+
return notices;
|
|
60
|
+
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { contentCarriesLiveMedia } from '../../providers/media-normalization.mjs';
|
|
2
|
+
import { publishSessionPersistence } from './session-persistence-events.mjs';
|
|
2
3
|
|
|
3
4
|
const _lastSaveError = new Map(); // id -> { message, at }
|
|
4
5
|
|
|
@@ -287,9 +288,11 @@ export function _recordSaveDrop(id, epoch = null) {
|
|
|
287
288
|
export function _clearSaveStateIfCurrent(id, epoch = null) {
|
|
288
289
|
if (!id) return false;
|
|
289
290
|
const marked = _failureEpochs.get(id);
|
|
290
|
-
|
|
291
|
-
_clearSessionSaveState(id);
|
|
292
|
-
|
|
291
|
+
const cleared = !(marked !== undefined && Number.isFinite(epoch) && epoch < marked);
|
|
292
|
+
if (cleared) _clearSessionSaveState(id);
|
|
293
|
+
// A landed write put the record on disk whether or not it clears the markers.
|
|
294
|
+
publishSessionPersistence(id, 'landed');
|
|
295
|
+
return cleared;
|
|
293
296
|
}
|
|
294
297
|
|
|
295
298
|
/** True while this process's last save attempt for `id` failed. */
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { hash } from 'node:crypto';
|
|
2
2
|
import { _messagesForDisk, _sessionForDisk } from './serialize.mjs';
|
|
3
|
+
import { sensitiveValuesVersion } from '../sensitive-values.mjs';
|
|
3
4
|
|
|
4
5
|
// ── Delta handoff to the save worker ─────────────────────────────────────────
|
|
5
6
|
// Worker.postMessage structured-clones on the CALLER's thread, so a full-
|
|
@@ -93,7 +94,7 @@ function _attemptRecord(header, refs, prints) {
|
|
|
93
94
|
*/
|
|
94
95
|
export function _attemptEvidence(attempt) {
|
|
95
96
|
if (!attempt) return null;
|
|
96
|
-
const messages = _messagesForDisk(attempt.refs);
|
|
97
|
+
const messages = _messagesForDisk(attempt.refs, attempt.header.id);
|
|
97
98
|
for (let index = 0; index < messages.length; index += 1) {
|
|
98
99
|
const print = attempt.prints[index];
|
|
99
100
|
if (print === null || _messagePrint(messages[index]) !== print) return null;
|
|
@@ -112,14 +113,17 @@ function _isPureAppend(refs, live) {
|
|
|
112
113
|
export function _buildWirePayload(id, session, forceFull = false) {
|
|
113
114
|
const live = Array.isArray(session.messages) ? session.messages : [];
|
|
114
115
|
const base = forceFull ? null : _deltaBaseline.get(id);
|
|
115
|
-
|
|
116
|
+
// A value marked sensitive since the baseline was sent must leave the
|
|
117
|
+
// messages the worker already holds too: write the whole transcript again.
|
|
118
|
+
const sensitive = sensitiveValuesVersion(id);
|
|
119
|
+
const resyncDue = !base || base.deltasSinceFull >= DELTA_FULL_RESYNC_EVERY || base.sensitive !== sensitive;
|
|
116
120
|
if (!resyncDue && _isPureAppend(base.refs, live)) {
|
|
117
121
|
// Header: full disk projection with an empty message list (strips
|
|
118
122
|
// transient aliases); tail: per-message projection of the appended
|
|
119
123
|
// suffix only. Both use the same idempotent per-message projection
|
|
120
124
|
// as a full send, so worker-side reconstruction is byte-identical.
|
|
121
125
|
const header = _sessionForDisk({ ...session, messages: [] });
|
|
122
|
-
const tailMessages = _messagesForDisk(live.slice(base.refs.length));
|
|
126
|
+
const tailMessages = _messagesForDisk(live.slice(base.refs.length), id);
|
|
123
127
|
// The EXACT payload the worker will reconstruct and write is the sent
|
|
124
128
|
// baseline + this delta. Its evidence is fingerprint-checked against
|
|
125
129
|
// what was sent, never a fresh projection of the mutable live session
|
|
@@ -127,7 +131,7 @@ export function _buildWirePayload(id, session, forceFull = false) {
|
|
|
127
131
|
// reference-identity delta). Only the tail is fingerprinted.
|
|
128
132
|
const prints = base.prints ? base.prints.concat(tailMessages.map(_messagePrint)) : null;
|
|
129
133
|
const refs = live.slice();
|
|
130
|
-
_deltaBaseline.set(id, { refs, deltasSinceFull: base.deltasSinceFull + 1, prints });
|
|
134
|
+
_deltaBaseline.set(id, { refs, deltasSinceFull: base.deltasSinceFull + 1, prints, sensitive });
|
|
131
135
|
return {
|
|
132
136
|
delta: { baseCount: base.refs.length, header, tailMessages },
|
|
133
137
|
attempt: _attemptRecord(header, refs, prints),
|
|
@@ -142,7 +146,7 @@ export function _buildWirePayload(id, session, forceFull = false) {
|
|
|
142
146
|
// DELTA_FULL_RESYNC_EVERY saves.
|
|
143
147
|
const refs = live.slice();
|
|
144
148
|
const prints = _printMessages(live, diskMessages, resyncDue ? null : base);
|
|
145
|
-
_deltaBaseline.set(id, { refs, deltasSinceFull: 0, prints });
|
|
149
|
+
_deltaBaseline.set(id, { refs, deltasSinceFull: 0, prints, sensitive });
|
|
146
150
|
// (saveSessionAsync normalizes `messages` to an array before any post.)
|
|
147
151
|
return { session: full, attempt: _attemptRecord(full, refs, prints) };
|
|
148
152
|
}
|
|
@@ -3,6 +3,7 @@ import { join } from 'node:path';
|
|
|
3
3
|
import { renameWithRetrySync } from '../../../../shared/atomic-file.mjs';
|
|
4
4
|
import { sanitizeContentForStoredHistory } from '../../providers/media-normalization.mjs';
|
|
5
5
|
import { readTopLevelLifecycleRecord, isLifecycleUnreadable } from '../lifecycle-scan.mjs';
|
|
6
|
+
import { redactSensitiveMessage } from '../sensitive-projection.mjs';
|
|
6
7
|
|
|
7
8
|
// Inline base64 images (tool screenshots included) are written as
|
|
8
9
|
// content-addressed refs, like prompt media, so session JSON stays small while
|
|
@@ -13,17 +14,17 @@ import { readTopLevelLifecycleRecord, isLifecycleUnreadable } from '../lifecycle
|
|
|
13
14
|
// Per-message disk projection shared by _sessionForDisk and the save-worker
|
|
14
15
|
// delta path (tail-only projection). Returns the SAME array reference when no
|
|
15
16
|
// message changed. Idempotent: already-sanitized content passes through
|
|
16
|
-
// unchanged, so projecting a projected message is a no-op.
|
|
17
|
-
|
|
17
|
+
// unchanged, so projecting a projected message is a no-op. A value session
|
|
18
|
+
// `sessionId` marked sensitive (sensitive-values.mjs) is stored as a marker in
|
|
19
|
+
// the message's text (sensitive-projection.mjs).
|
|
20
|
+
export function _messagesForDisk(messages, sessionId) {
|
|
18
21
|
let changed = false;
|
|
19
22
|
const out = messages.map((m) => {
|
|
20
23
|
if (!m || typeof m !== 'object') return m;
|
|
21
24
|
const content = sanitizeContentForStoredHistory(m.content);
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
}
|
|
26
|
-
return m;
|
|
25
|
+
const projected = redactSensitiveMessage(sessionId, content === m.content ? m : { ...m, content });
|
|
26
|
+
if (projected !== m) changed = true;
|
|
27
|
+
return projected;
|
|
27
28
|
});
|
|
28
29
|
return changed ? out : messages;
|
|
29
30
|
}
|
|
@@ -46,7 +47,7 @@ export function _sessionForDisk(session) {
|
|
|
46
47
|
Object.hasOwn(session, 'toolApprovalHook') ||
|
|
47
48
|
Object.hasOwn(session, '_providerPrefixGuardState'));
|
|
48
49
|
const messages = Array.isArray(session?.messages) ? session.messages : null;
|
|
49
|
-
const out = messages?.length ? _messagesForDisk(messages) : messages;
|
|
50
|
+
const out = messages?.length ? _messagesForDisk(messages, session.id) : messages;
|
|
50
51
|
if (out === messages) {
|
|
51
52
|
if (!hasTransient) return session;
|
|
52
53
|
return _withMidTurnContextAnchor(_withoutTransientFields(session), session);
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read-only notifications about a session's persistence lifecycle, for owners
|
|
3
|
+
* of derived sidecars (the compaction archive) that must wait for the session
|
|
4
|
+
* record instead of polling for it. A pure registry: it persists nothing and
|
|
5
|
+
* imports no write path.
|
|
6
|
+
*
|
|
7
|
+
* 'landed' — a save of the session reached disk (its record exists);
|
|
8
|
+
* 'closed' — the session was closed (no further save is to be expected).
|
|
9
|
+
*
|
|
10
|
+
* A hard delete is announced through the existing purge hooks
|
|
11
|
+
* (session-observers.mjs registerSessionPurgeHook).
|
|
12
|
+
*/
|
|
13
|
+
const listeners = new Set();
|
|
14
|
+
|
|
15
|
+
export function subscribeSessionPersistence(listener) {
|
|
16
|
+
if (typeof listener !== 'function') return () => {};
|
|
17
|
+
listeners.add(listener);
|
|
18
|
+
return () => listeners.delete(listener);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function publishSessionPersistence(id, event) {
|
|
22
|
+
for (const listener of [...listeners]) {
|
|
23
|
+
try {
|
|
24
|
+
listener(id, event);
|
|
25
|
+
} catch {
|
|
26
|
+
/* observers never affect persistence */
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|