mixdog 1.3.0 → 1.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE.md +52 -9
- package/README.md +8 -4
- package/package.json +2 -2
- package/scripts/native-release-status.mjs +238 -0
- package/src/defaults/shipped-history.json +8 -1
- package/src/defaults/skills/pptx/scripts/qc-pages.mjs +3 -2
- package/src/defaults/skills/setup/SKILL.md +23 -1
- package/src/defaults/skills/setup/references/actions.md +54 -4
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +17 -10
- 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/output-styles/common.md +13 -8
- package/src/rules/agent-mode/AGENT_MODE.md +9 -6
- package/src/rules/agent-mode/MIXY.md +11 -6
- package/src/rules/lead/LEAD.md +2 -2
- package/src/rules/shared/10-tool-workflow.md +4 -0
- package/src/rules/shared/20-research.md +2 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/preset.mjs +5 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-dispatch/session-prep.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/agent-progress-watchdog.mjs +6 -1
- package/src/runtime/agent/orchestrator/agent-runtime/cache-strategy.mjs +114 -26
- package/src/runtime/agent/orchestrator/agent-runtime/maintenance-route.mjs +8 -0
- package/src/runtime/agent/orchestrator/agent-runtime/room-supervisor-completion.mjs +5 -4
- package/src/runtime/agent/orchestrator/agent-trace.mjs +3 -0
- package/src/runtime/agent/orchestrator/cache-break-trace.mjs +4 -0
- package/src/runtime/agent/orchestrator/config-presets.mjs +2 -0
- package/src/runtime/agent/orchestrator/config-storage.mjs +10 -3
- package/src/runtime/agent/orchestrator/config.mjs +11 -0
- package/src/runtime/agent/orchestrator/context/role-instructions.mjs +81 -16
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -4
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +80 -31
- package/src/runtime/agent/orchestrator/providers/anthropic-model-resolve.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth-client-version.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/anthropic-oauth.mjs +67 -38
- package/src/runtime/agent/orchestrator/providers/anthropic-turn-result.mjs +2 -0
- package/src/runtime/agent/orchestrator/providers/antigravity-oauth-catalog.mjs +7 -2
- package/src/runtime/agent/orchestrator/providers/antigravity-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +34 -16
- package/src/runtime/agent/orchestrator/providers/cursor-wire.mjs +31 -4
- package/src/runtime/agent/orchestrator/providers/cursor.mjs +4 -1
- package/src/runtime/agent/orchestrator/providers/custom-provider.mjs +11 -8
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-prefix-trace.mjs +76 -0
- package/src/runtime/agent/orchestrator/providers/lib/anthropic-request-utils.mjs +19 -1
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/lib/tool-call-wire-ids.mjs +65 -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-retry-profile.mjs +8 -2
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +10 -2
- 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-responses-input.mjs +3 -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/provider-error-kind.mjs +14 -2
- package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -0
- package/src/runtime/agent/orchestrator/providers/retry-executor.mjs +17 -0
- package/src/runtime/agent/orchestrator/runtime-core/config-helpers.mjs +90 -24
- package/src/runtime/agent/orchestrator/runtime-core/fs-utils.mjs +8 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-mutations.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/goal-storage.mjs +68 -22
- package/src/runtime/agent/orchestrator/runtime-core/goal-tool-exec.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/orchestration.mjs +5 -5
- package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +17 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +41 -10
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +6 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/agent-mode.mjs +0 -6
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.d.mts +1 -1
- package/src/runtime/agent/orchestrator/session/agent-room-supervisor.mjs +22 -4
- package/src/runtime/agent/orchestrator/session/agent-room.d.mts +21 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +149 -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/execution-tail.mjs +14 -11
- package/src/runtime/agent/orchestrator/session/compact/forked-summary.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/compact/runner.mjs +68 -18
- package/src/runtime/agent/orchestrator/session/compact/summary-mode.mjs +16 -0
- package/src/runtime/agent/orchestrator/session/compact/summary-route.mjs +87 -0
- package/src/runtime/agent/orchestrator/session/compact/summary.mjs +2 -0
- package/src/runtime/agent/orchestrator/session/compact/text-utils.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/compact/user-message-kind.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/compaction-archive-capture.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/context-utils.mjs +41 -10
- package/src/runtime/agent/orchestrator/session/human-gate/delegation.mjs +230 -0
- package/src/runtime/agent/orchestrator/session/human-gate/gate.mjs +156 -25
- package/src/runtime/agent/orchestrator/session/human-gate/outbound-policy.mjs +20 -0
- package/src/runtime/agent/orchestrator/session/human-gate/requests.mjs +21 -9
- package/src/runtime/agent/orchestrator/session/human-gate/run-policy.mjs +73 -0
- package/src/runtime/agent/orchestrator/session/human-gate/store.mjs +55 -27
- package/src/runtime/agent/orchestrator/session/human-gate/unattended-port.mjs +15 -0
- package/src/runtime/agent/orchestrator/session/loop/compact-policy.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/loop/fresh-context.mjs +189 -105
- package/src/runtime/agent/orchestrator/session/loop/loop-state.mjs +9 -0
- package/src/runtime/agent/orchestrator/session/loop/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +111 -9
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +11 -14
- package/src/runtime/agent/orchestrator/session/loop/steering.mjs +3 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/before-hook.mjs +38 -5
- package/src/runtime/agent/orchestrator/session/loop/tool-exec/dispatch.mjs +21 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-exec.mjs +2 -1
- package/src/runtime/agent/orchestrator/session/loop/tool-exposure-migration.mjs +12 -0
- package/src/runtime/agent/orchestrator/session/loop/tool-helpers.mjs +11 -5
- package/src/runtime/agent/orchestrator/session/loop/usage.mjs +46 -4
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-auto-effort.mjs +13 -2
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-commit.mjs +18 -8
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-execute.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-prepare.mjs +6 -0
- package/src/runtime/agent/orchestrator/session/manager/ask-turn-settle.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/manager/compaction-runner.mjs +44 -18
- package/src/runtime/agent/orchestrator/session/manager/orchestration-mode-switch-notice.mjs +99 -0
- package/src/runtime/agent/orchestrator/session/manager/owner-route-fallbacks.mjs +42 -0
- package/src/runtime/agent/orchestrator/session/manager/pending-messages.mjs +3 -9
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-file.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/pending-spool-shard.mjs +5 -3
- package/src/runtime/agent/orchestrator/session/manager/provider-replay.mjs +50 -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-lifecycle.mjs +62 -47
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +6 -8
- package/src/runtime/agent/orchestrator/session/manager/session-record.mjs +12 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint-journal.mjs +6 -15
- package/src/runtime/agent/orchestrator/session/manager/turn-checkpoint.mjs +7 -1
- package/src/runtime/agent/orchestrator/session/manager/turn-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/usage-metrics.mjs +1 -1
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/mixy-name.mjs +5 -2
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +35 -4
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +179 -16
- package/src/runtime/agent/orchestrator/session/send-with-recovery.mjs +5 -1
- package/src/runtime/agent/orchestrator/session/side-question-store.mjs +3 -1
- package/src/runtime/agent/orchestrator/session/store/canonical-reader.mjs +41 -7
- package/src/runtime/agent/orchestrator/session/store/lifecycle-barriers.mjs +10 -2
- package/src/runtime/agent/orchestrator/session/store/load-cache.mjs +14 -2
- package/src/runtime/agent/orchestrator/session/store/session-delete.mjs +15 -1
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-record.mjs +8 -3
- package/src/runtime/agent/orchestrator/session/store/sweep/sweep-row.mjs +36 -1
- package/src/runtime/agent/orchestrator/session/store-summary-catalog.mjs +4 -1
- package/src/runtime/agent/orchestrator/session/store-summary-index.mjs +40 -5
- package/src/runtime/agent/orchestrator/session/store-summary-last-message.mjs +47 -2
- package/src/runtime/agent/orchestrator/session/tail-reminder-rules.mjs +125 -0
- package/src/runtime/agent/orchestrator/session/tool-batch/finalize.mjs +4 -0
- package/src/runtime/agent/orchestrator/session/tool-result-offload.mjs +78 -43
- package/src/runtime/agent/orchestrator/session/transcript-restore/restore.mjs +49 -14
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +134 -17
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +35 -13
- package/src/runtime/agent/orchestrator/tools/builtin/atomic-write.mjs +25 -5
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/backgrounded-result.mjs +4 -3
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/completed-result.mjs +13 -14
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool/transport-artifacts.mjs +9 -0
- package/src/runtime/agent/orchestrator/tools/builtin/bash-tool.mjs +19 -14
- package/src/runtime/agent/orchestrator/tools/builtin/builtin-tools.mjs +7 -1
- package/src/runtime/agent/orchestrator/tools/builtin/lib/shell-job-records.mjs +6 -14
- package/src/runtime/agent/orchestrator/tools/builtin/pdf-test-fixtures.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/builtin/read-image.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-single-media.mjs +47 -2
- package/src/runtime/agent/orchestrator/tools/builtin/read-special-files.mjs +264 -87
- package/src/runtime/agent/orchestrator/tools/builtin/shell-analysis.mjs +10 -4
- package/src/runtime/agent/orchestrator/tools/builtin/shell-jobs.mjs +34 -21
- package/src/runtime/agent/orchestrator/tools/code-graph/constants.mjs +8 -4
- package/src/runtime/agent/orchestrator/tools/lib/shell-descendants.mjs +9 -30
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-settle.mjs +24 -12
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-spawn.mjs +3 -2
- package/src/runtime/agent/orchestrator/tools/lib/shell-run-state.mjs +6 -2
- package/src/runtime/agent/orchestrator/tools/patch/delete-quarantine.mjs +2 -1
- package/src/runtime/agent/orchestrator/tools/patch/replay-capture.mjs +4 -2
- package/src/runtime/agent/orchestrator/tools/patch/rollback-state.mjs +33 -2
- package/src/runtime/agent/orchestrator/tools/patch-manifest.json +13 -13
- package/src/runtime/agent/orchestrator/tools/shell-command.mjs +5 -3
- package/src/runtime/agent/orchestrator/tools/shell-exec-output.mjs +3 -1
- package/src/runtime/attachments/attachment-reference.mjs +155 -0
- package/src/runtime/attachments/limits.mjs +5 -1
- package/src/runtime/attachments/office-extract.mjs +3 -6
- package/src/runtime/attachments/pdf-extract.mjs +84 -10
- package/src/runtime/attachments/store.mjs +55 -30
- package/src/runtime/browser-bridge/client.mjs +73 -20
- package/src/runtime/channels/lib/runtime-paths.mjs +1 -1
- package/src/runtime/channels/lib/scheduler.mjs +14 -7
- package/src/runtime/channels/lib/state-file.mjs +15 -15
- package/src/runtime/channels/lib/voice-runtime-fetcher.mjs +14 -13
- package/src/runtime/channels/lib/webhook/relay-tunnel.mjs +22 -18
- package/src/runtime/channels/lib/webhook/session-dispatch.mjs +14 -36
- package/src/runtime/channels/lib/worker-main.mjs +3 -2
- package/src/runtime/effort-judge/compact-tokenizer.mjs +8 -2
- package/src/runtime/effort-judge/judge-client.mjs +6 -3
- package/src/runtime/effort-judge/model-install.mjs +16 -8
- package/src/runtime/effort-judge/model-manifest.json +7 -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/pg/process.mjs +8 -4
- package/src/runtime/memory/lib/project-id-resolver.mjs +5 -1
- package/src/runtime/memory/lib/runtime-fetcher.mjs +6 -1
- package/src/runtime/office/core/journal.mjs +7 -14
- package/src/runtime/office/design/library/design-library-core.mjs +4 -16
- package/src/runtime/office/pdf/ocr-engine-worker.mjs +37 -0
- package/src/runtime/office/pdf/ocr-engine.mjs +146 -0
- package/src/runtime/office/pdf/pdf-ocr.mjs +51 -21
- package/src/runtime/office/pdf/pdf-render.mjs +30 -18
- package/src/runtime/office/portable/ooxml-validator.mjs +2 -2
- package/src/runtime/office/portable/portable-soffice.mjs +4 -0
- package/src/runtime/shared/agent-kind-migration.mjs +200 -0
- package/src/runtime/shared/agent-kind.mjs +152 -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-route-config.d.mts +1 -0
- package/src/runtime/shared/agent-route-config.mjs +22 -8
- package/src/runtime/shared/agent-store.mjs +149 -0
- package/src/runtime/shared/agent-task-starts.mjs +27 -0
- package/src/runtime/shared/atomic-file.d.mts +50 -0
- package/src/runtime/shared/atomic-file.mjs +394 -132
- package/src/runtime/shared/background-tasks.mjs +7 -4
- package/src/runtime/shared/bot-hops.mjs +30 -0
- package/src/runtime/shared/bot-split-migration.mjs +457 -0
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config-patch.mjs +20 -1
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/connector-catalog.d.mts +1 -2
- package/src/runtime/shared/connector-catalog.mjs +2 -4
- package/src/runtime/shared/connector-triggers.mjs +2 -0
- 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 +7 -1
- package/src/runtime/shared/error-presentation.mjs +113 -10
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/handoff-artifacts.d.mts +20 -0
- package/src/runtime/shared/handoff-artifacts.mjs +61 -0
- package/src/runtime/shared/llm/provider-model-identities.mjs +6 -1
- package/src/runtime/shared/llm/usage-accounting.mjs +7 -1
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/model-route-fallback.mjs +79 -7
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/plain-preview.mjs +22 -17
- package/src/runtime/shared/process-lifecycle.mjs +8 -12
- package/src/runtime/shared/projects.mjs +50 -41
- package/src/runtime/shared/provider-accounts.mjs +17 -12
- package/src/runtime/shared/routine-trigger.d.mts +6 -1
- package/src/runtime/shared/routine-trigger.mjs +14 -6
- package/src/runtime/shared/run-child.d.mts +3 -0
- package/src/runtime/shared/run-child.mjs +24 -3
- package/src/runtime/shared/sensitive-path-rules.d.mts +15 -0
- package/src/runtime/shared/sensitive-path-rules.mjs +256 -0
- package/src/runtime/shared/session-baseline-store.mjs +8 -2
- package/src/runtime/shared/singleton-owner.mjs +7 -6
- package/src/runtime/shared/skill-tool-dependencies.mjs +4 -10
- package/src/runtime/shared/staged-update.mjs +4 -2
- package/src/runtime/shared/statusline/statusline-segments.mjs +4 -15
- package/src/runtime/shared/tool-execution-contract.mjs +6 -0
- package/src/runtime/shared/transcript-metadata.mjs +28 -3
- package/src/runtime/shared/transcript-writer/session-record.mjs +7 -6
- package/src/runtime/shared/turn-snapshot-store.mjs +5 -4
- package/src/runtime/shared/turn-snapshot.mjs +44 -2
- package/src/runtime/shared/usage-readout-totals.mjs +15 -13
- package/src/runtime/shared/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +46 -44
- package/src/runtime/shared/utf8-cut.mjs +10 -0
- package/src/runtime/shared/worktrees/activity-gate.mjs +269 -61
- package/src/runtime/shared/worktrees/registry.mjs +26 -26
- package/src/runtime/tidy/core-install.mjs +26 -4
- package/src/runtime/tidy/install.mjs +91 -23
- package/src/runtime/web-search/lib/config.mjs +4 -16
- package/src/session-runtime/agent-home-delivery.mjs +859 -136
- package/src/session-runtime/agent-room-delivery.mjs +167 -19
- package/src/session-runtime/agent-task-sessions.mjs +831 -69
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +58 -24
- package/src/session-runtime/boot/config.mjs +1 -0
- package/src/session-runtime/boot/facade.mjs +11 -1
- package/src/session-runtime/boot/infrastructure.mjs +14 -2
- package/src/session-runtime/boot/providers.mjs +16 -6
- package/src/session-runtime/boot/shared.mjs +12 -6
- package/src/session-runtime/boot/tools.mjs +2 -2
- package/src/session-runtime/bot-status.mjs +432 -0
- package/src/session-runtime/config-lifecycle/lead-route-sync.mjs +29 -0
- package/src/session-runtime/config-lifecycle.mjs +78 -32
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- package/src/session-runtime/handoff-attachments.mjs +593 -0
- package/src/session-runtime/held-requests.mjs +36 -33
- package/src/session-runtime/internal-tool-executor/bridge-tools.mjs +10 -1
- package/src/session-runtime/internal-tool-executor/feature-tools.mjs +2 -2
- package/src/session-runtime/internal-tool-executor/workspace-tools.mjs +45 -6
- package/src/session-runtime/lifecycle/session-catalog.mjs +12 -2
- package/src/session-runtime/lifecycle/teardown.mjs +11 -3
- package/src/session-runtime/mcp-glue/connect.mjs +100 -186
- package/src/session-runtime/mcp-glue/initial-connect-gate.mjs +57 -0
- package/src/session-runtime/mcp-glue/server-connection.mjs +71 -0
- package/src/session-runtime/media-api.mjs +5 -2
- package/src/session-runtime/model-route/route-persist.mjs +2 -2
- package/src/session-runtime/model-route/route-tuning.mjs +11 -2
- package/src/session-runtime/model-route/set-route.mjs +82 -2
- package/src/session-runtime/native-web-search.mjs +2 -2
- package/src/session-runtime/new-session-config.mjs +6 -1
- package/src/session-runtime/owned-automation-run.mjs +8 -1
- package/src/session-runtime/room-member-gate.mjs +16 -3
- package/src/session-runtime/route-resolution.mjs +21 -19
- package/src/session-runtime/route-state.mjs +1 -59
- package/src/session-runtime/routine-home-turn.mjs +5 -3
- package/src/session-runtime/runtime-bootstrap.mjs +5 -3
- package/src/session-runtime/runtime-paths.mjs +11 -5
- package/src/session-runtime/runtime-tunables.mjs +6 -0
- package/src/session-runtime/services/agent-tool/close-flow/close-all.mjs +11 -2
- package/src/session-runtime/services/agent-tool/close-flow.mjs +4 -2
- package/src/session-runtime/services/agent-tool/execute-send.mjs +1 -0
- package/src/session-runtime/services/agent-tool/execute-spawn.mjs +1 -0
- package/src/session-runtime/services/agent-tool/helpers.mjs +24 -5
- package/src/session-runtime/services/agent-tool/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +36 -24
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +4 -0
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-prep.mjs +6 -3
- package/src/session-runtime/services/agent-tool/spawn-flow/spawn-run.mjs +12 -6
- package/src/session-runtime/services/agent-tool/spawn-preset.mjs +5 -8
- package/src/session-runtime/services/agent-tool/tool-def.mjs +7 -0
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
- package/src/session-runtime/services/agent-tool.mjs +6 -0
- package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
- package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
- package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
- package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
- package/src/session-runtime/services/plugin-admin.mjs +26 -26
- package/src/session-runtime/services/provider-admin.mjs +14 -4
- package/src/session-runtime/services/schedule-access.mjs +33 -4
- package/src/session-runtime/services/seeds.mjs +4 -3
- package/src/session-runtime/services/usage-dashboard.mjs +1 -1
- package/src/session-runtime/services/usage-stats-model.mjs +2 -1
- package/src/session-runtime/session-create/materialize.mjs +2 -3
- package/src/session-runtime/session-create/session-options.mjs +15 -2
- package/src/session-runtime/settings-builtin-tools-api.mjs +63 -1
- package/src/session-runtime/settings-compaction-api.mjs +86 -39
- package/src/session-runtime/setup-tool/executor.mjs +20 -3
- package/src/session-runtime/setup-tool/extended-actions.mjs +19 -7
- package/src/session-runtime/setup-tool/settings-contract.mjs +37 -1
- package/src/session-runtime/setup-tool/tool-defs.mjs +20 -3
- package/src/session-runtime/skills-api.mjs +12 -5
- package/src/session-runtime/tool-policy/agent-profile-fold.mjs +265 -0
- package/src/session-runtime/tool-policy/mode-switch.mjs +54 -0
- package/src/session-runtime/tool-policy/pending-switches.mjs +59 -0
- package/src/session-runtime/tool-policy/policy-stage.mjs +165 -0
- package/src/session-runtime/tool-policy/session-switches.mjs +169 -0
- package/src/session-runtime/tool-policy/surface-helpers.mjs +114 -0
- package/src/session-runtime/tool-policy/workflow-overlay.mjs +145 -0
- package/src/session-runtime/tool-policy-refresh.mjs +47 -358
- package/src/session-runtime/tool-surface.mjs +5 -3
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +99 -81
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +2 -1
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +578 -301
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +113 -53
- package/src/session-runtime/workflow-agents-api/onboarding.mjs +11 -4
- package/src/session-runtime/workflow-agents-api/shared.mjs +5 -1
- package/src/session-runtime/workflow-agents-api/style-and-mode.mjs +14 -1
- package/src/session-runtime/workflow-agents-api/workflow-packs.mjs +6 -3
- package/src/session-runtime/workspace/workspace-state.mjs +7 -6
- package/src/standalone/daemon-agent-control.mjs +4 -1
- package/src/standalone/daemon-session-control.mjs +60 -11
- package/src/standalone/daemon.mjs +34 -4
- package/src/standalone/local-session-runtime.mjs +22 -14
- package/src/standalone/session-client.mjs +7 -2
- package/src/standalone/session-protocol.mjs +7 -0
- package/src/standalone/session-service/agent-tree/agent-turns.mjs +46 -3
- package/src/standalone/session-service/agent-tree.mjs +4 -3
- package/src/standalone/session-service/session-calls/session-turns.mjs +19 -0
- package/src/standalone/session-service.mjs +11 -0
- package/src/standalone/session-state-projection.mjs +19 -9
- package/src/standalone/session-tool-output.mjs +35 -9
- package/src/tui/app/maintenance-pickers/auto-clear-picker.mjs +19 -13
- package/src/tui/app/settings-picker/settings-rows.mjs +2 -2
- package/src/tui/app/slash-commands.mjs +1 -1
- package/src/tui/app/usage-context-panels/context-model.mjs +8 -2
- package/src/tui/dist/index.mjs +599 -342
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/context-state.mjs +4 -0
- package/src/tui/session/notice-surface.mjs +3 -4
- package/src/tui/session/session-api/intake/abort.mjs +19 -1
- package/src/tui/session/session-api/media.mjs +2 -1
- package/src/tui/session/session-api/routes-agent-definitions.mjs +75 -0
- package/src/tui/session/session-api/routes-write.mjs +203 -0
- package/src/tui/session/session-api/routes.mjs +23 -236
- package/src/tui/session/session-api/settings.mjs +14 -1
- package/src/tui/session/session-flow/drain.mjs +28 -3
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +8 -3
- package/src/tui/session/tool-approval.mjs +185 -20
- package/src/tui/session/transcript-intake.mjs +4 -0
- package/src/tui/session/turn-live-call.mjs +11 -3
- package/src/tui/session/turn.mjs +36 -17
- 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
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
// The credential / secret file deny list, shared by every lane that hands a
|
|
2
|
+
// local file to someone else: the desktop's remote (paired-device) file lanes
|
|
3
|
+
// (apps/desktop/src/main/remote-sensitive-paths.ts adds its registered private
|
|
4
|
+
// roots and async canonicalization) and the runtime's attachment hand-offs
|
|
5
|
+
// between bots and agents (session-runtime/handoff-attachments.mjs).
|
|
6
|
+
//
|
|
7
|
+
// Matching is on SEGMENTS of a path, lower-cased on every platform, so a
|
|
8
|
+
// denied name cannot hide behind letter case, trailing dots/spaces or an NTFS
|
|
9
|
+
// stream suffix. Callers pass the canonical (realpath) form as well as the
|
|
10
|
+
// lexical one, so a link cannot reach a denied place under an innocent name.
|
|
11
|
+
import { realpathSync } from 'node:fs';
|
|
12
|
+
import { homedir } from 'node:os';
|
|
13
|
+
import { dirname, join, resolve } from 'node:path';
|
|
14
|
+
|
|
15
|
+
/** A directory with this name anywhere on the path holds credentials. `.git`
|
|
16
|
+
* is one of them: its config carries remotes with embedded tokens and
|
|
17
|
+
* credential helpers, and its objects hold every secret ever committed. */
|
|
18
|
+
const SECRET_DIRECTORIES = new Set([
|
|
19
|
+
'.ssh',
|
|
20
|
+
'.gnupg',
|
|
21
|
+
'.aws',
|
|
22
|
+
'.azure',
|
|
23
|
+
'.kube',
|
|
24
|
+
'.mozilla',
|
|
25
|
+
'.password-store',
|
|
26
|
+
'.git',
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
/** Consecutive segments anywhere on the path that name a credential store:
|
|
30
|
+
* CLI configs, browser profiles and OS credential stores. */
|
|
31
|
+
const SECRET_SEQUENCES = [
|
|
32
|
+
// Tool credentials in the user's home.
|
|
33
|
+
['.docker', 'config.json'],
|
|
34
|
+
['.config', 'gcloud'],
|
|
35
|
+
['appdata', 'roaming', 'gcloud'],
|
|
36
|
+
['.config', 'gh'],
|
|
37
|
+
['appdata', 'roaming', 'github cli'],
|
|
38
|
+
['.config', 'git', 'credentials'],
|
|
39
|
+
['.cargo', 'credentials'],
|
|
40
|
+
['.cargo', 'credentials.toml'],
|
|
41
|
+
['.terraform.d', 'credentials.tfrc.json'],
|
|
42
|
+
['.claude', '.credentials.json'],
|
|
43
|
+
['.codex', 'auth.json'],
|
|
44
|
+
['.gemini', 'oauth_creds.json'],
|
|
45
|
+
// Browser profiles: cookies, saved logins, session tokens.
|
|
46
|
+
['google', 'chrome', 'user data'],
|
|
47
|
+
['microsoft', 'edge', 'user data'],
|
|
48
|
+
['bravesoftware', 'brave-browser'],
|
|
49
|
+
['chromium', 'user data'],
|
|
50
|
+
['vivaldi', 'user data'],
|
|
51
|
+
['opera software'],
|
|
52
|
+
['mozilla', 'firefox'],
|
|
53
|
+
['application support', 'google', 'chrome'],
|
|
54
|
+
['application support', 'microsoft edge'],
|
|
55
|
+
['application support', 'firefox'],
|
|
56
|
+
['application support', 'chromium'],
|
|
57
|
+
['application support', 'bravesoftware'],
|
|
58
|
+
['application support', 'vivaldi'],
|
|
59
|
+
['library', 'safari'],
|
|
60
|
+
['library', 'cookies'],
|
|
61
|
+
['.config', 'google-chrome'],
|
|
62
|
+
['.config', 'chromium'],
|
|
63
|
+
['.config', 'microsoft-edge'],
|
|
64
|
+
['.config', 'bravesoftware'],
|
|
65
|
+
['.config', 'vivaldi'],
|
|
66
|
+
['snap', 'firefox'],
|
|
67
|
+
['snap', 'chromium'],
|
|
68
|
+
['.var', 'app', 'org.mozilla.firefox'],
|
|
69
|
+
['.var', 'app', 'com.google.chrome'],
|
|
70
|
+
// OS credential stores.
|
|
71
|
+
['microsoft', 'credentials'],
|
|
72
|
+
['microsoft', 'protect'],
|
|
73
|
+
['microsoft', 'vault'],
|
|
74
|
+
['windows', 'system32', 'config'],
|
|
75
|
+
['library', 'keychains'],
|
|
76
|
+
['.local', 'share', 'keyrings'],
|
|
77
|
+
['.local', 'share', 'kwalletd'],
|
|
78
|
+
['etc', 'shadow'],
|
|
79
|
+
['etc', 'gshadow'],
|
|
80
|
+
['etc', 'ssh'],
|
|
81
|
+
['etc', 'ssl', 'private'],
|
|
82
|
+
];
|
|
83
|
+
|
|
84
|
+
const KEY_EXTENSIONS = new Set([
|
|
85
|
+
'.pem',
|
|
86
|
+
'.key',
|
|
87
|
+
'.p12',
|
|
88
|
+
'.pfx',
|
|
89
|
+
'.p8',
|
|
90
|
+
'.ppk',
|
|
91
|
+
'.jks',
|
|
92
|
+
'.keystore',
|
|
93
|
+
'.kdbx',
|
|
94
|
+
'.keychain',
|
|
95
|
+
'.keychain-db',
|
|
96
|
+
]);
|
|
97
|
+
const CREDENTIAL_FILES = new Set([
|
|
98
|
+
'.netrc',
|
|
99
|
+
'_netrc',
|
|
100
|
+
'.npmrc',
|
|
101
|
+
'.pypirc',
|
|
102
|
+
'.git-credentials',
|
|
103
|
+
'.pgpass',
|
|
104
|
+
'.vault-token',
|
|
105
|
+
'.htpasswd',
|
|
106
|
+
'.dockercfg',
|
|
107
|
+
'.envrc',
|
|
108
|
+
'.s3cfg',
|
|
109
|
+
'kubeconfig',
|
|
110
|
+
]);
|
|
111
|
+
/** Environment files: every `.env*` name (`.env`, `.env.local`, `.envrc`,
|
|
112
|
+
* `.environment`, and the `.env.example` templates too — a template is
|
|
113
|
+
* checked in often enough with real values that no name is exempt) and
|
|
114
|
+
* `<stage>.env`. */
|
|
115
|
+
const ENV_FILE = /^(?:\.env.*|[^.]{1,64}(?:\.[^.]{1,64}){0,3}\.env)$/;
|
|
116
|
+
/** The dotenv templates a person may still open on purpose from a paired
|
|
117
|
+
* device (the desktop's remote file lanes, `envTemplates: true`); automatic
|
|
118
|
+
* transfers between bots and agents never get this allowance. */
|
|
119
|
+
const ENV_TEMPLATES = new Set(['.env.example', '.env.sample', '.env.template', '.env.dist']);
|
|
120
|
+
/** Credential JSON a cloud or OAuth client writes or downloads:
|
|
121
|
+
* `credentials.json`, `*credentials*.json`, `service-account*.json`,
|
|
122
|
+
* `client_secret*.json`. */
|
|
123
|
+
const CREDENTIAL_JSON = /^(?:.*credential.*|service[-_ ]?account.*|client[-_]?secret.*)\.json$/;
|
|
124
|
+
/** OpenSSH private keys (`id_ed25519`, `id_rsa_work`, `id_ecdsa_sk`, any
|
|
125
|
+
* `id_<type>`): no extension, so the public `.pub` half and documents named
|
|
126
|
+
* after a key type (`id_rsa_notes.md`) are not keys. */
|
|
127
|
+
const SSH_PRIVATE_KEY = /^id_[a-z0-9]{2,16}(?:[_-][^._-]{1,32}){0,3}$/;
|
|
128
|
+
/** A copy an editor or tool leaves beside a file (`x.bak`, `x.old`, `x~`,
|
|
129
|
+
* `x.1`): it holds what the original holds. */
|
|
130
|
+
const BACKUP_SUFFIX = /(?:\.(?:bak|backup|old|orig|prev|save|sav|swp|swo|tmp|copy|\d{1,3})|~)$/;
|
|
131
|
+
const MAX_BACKUP_SUFFIXES = 3;
|
|
132
|
+
|
|
133
|
+
/** The name a backup copy was made of (`.npmrc.bak.1` → `.npmrc`). */
|
|
134
|
+
function backupOriginal(name) {
|
|
135
|
+
let original = name;
|
|
136
|
+
for (let strip = 0; strip < MAX_BACKUP_SUFFIXES; strip += 1) {
|
|
137
|
+
const next = original.replace(BACKUP_SUFFIX, '');
|
|
138
|
+
if (next === original || !next) break;
|
|
139
|
+
original = next;
|
|
140
|
+
}
|
|
141
|
+
return original;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/** A file whose own name says it holds a secret, wherever it lives: a
|
|
145
|
+
* credential file, a dotenv file, a key or keystore, or a backup of one. */
|
|
146
|
+
function secretFileName(name, { envTemplates = false } = {}) {
|
|
147
|
+
const original = backupOriginal(name);
|
|
148
|
+
if (CREDENTIAL_FILES.has(original) || CREDENTIAL_JSON.test(original)) return true;
|
|
149
|
+
if (ENV_FILE.test(original)) return !(envTemplates && ENV_TEMPLATES.has(original));
|
|
150
|
+
const dot = original.lastIndexOf('.');
|
|
151
|
+
if (dot >= 0 && KEY_EXTENSIONS.has(original.slice(dot))) return true;
|
|
152
|
+
return SSH_PRIVATE_KEY.test(original);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// Mixdog's own state. Its home and data folders also hold what tools write
|
|
156
|
+
// (tool results, shell output, media), which stay reachable; only the files
|
|
157
|
+
// and folders that carry provider tokens, API keys, pairing and bridge secrets
|
|
158
|
+
// are denied.
|
|
159
|
+
const MIXDOG_SECRET_DIRECTORIES = new Set([
|
|
160
|
+
'secrets',
|
|
161
|
+
'mcp-oauth',
|
|
162
|
+
'provider-accounts',
|
|
163
|
+
'telegram',
|
|
164
|
+
'relay-data',
|
|
165
|
+
'dev-profile',
|
|
166
|
+
]);
|
|
167
|
+
const MIXDOG_SECRET_FILE =
|
|
168
|
+
/^\.?(mixdog-config|provider-accounts|relay-hook-device|auth\.json|[a-z0-9-]+-oauth(-credentials)?\.json|[a-z0-9-]+-bridge\.json)/;
|
|
169
|
+
const MIXDOG_SECRET_EXTENSIONS = new Set(['.dpapi', '.token']);
|
|
170
|
+
|
|
171
|
+
function mixdogSecret(rest) {
|
|
172
|
+
if (rest.some((segment) => MIXDOG_SECRET_DIRECTORIES.has(segment))) return true;
|
|
173
|
+
const name = rest[rest.length - 1] ?? '';
|
|
174
|
+
const dot = name.lastIndexOf('.');
|
|
175
|
+
return MIXDOG_SECRET_FILE.test(name) || MIXDOG_SECRET_EXTENSIONS.has(dot >= 0 ? name.slice(dot) : '');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** The segments of a path as the rules compare them: no extended-path prefix,
|
|
179
|
+
* no NTFS stream suffix, no trailing dots or spaces, lower case. */
|
|
180
|
+
export function sensitivePathSegments(path) {
|
|
181
|
+
return String(path)
|
|
182
|
+
.replace(/^[\\/]{2}[?.][\\/]/, '')
|
|
183
|
+
.split(/[\\/]+/)
|
|
184
|
+
.map((segment) =>
|
|
185
|
+
segment
|
|
186
|
+
.split(':')[0]
|
|
187
|
+
.replace(/[. ]+$/, '')
|
|
188
|
+
.toLowerCase()
|
|
189
|
+
)
|
|
190
|
+
.filter(Boolean);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function hasRun(segments, run) {
|
|
194
|
+
for (let start = 0; start + run.length <= segments.length; start += 1) {
|
|
195
|
+
if (run.every((segment, offset) => segments[start + offset] === segment)) return true;
|
|
196
|
+
}
|
|
197
|
+
return false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Whether `path` is denied under `within`: scopes `{ root: segments, except:
|
|
202
|
+
* subfolder names, whole }` — Mixdog's home and data folders (`whole: false`,
|
|
203
|
+
* only their secret subset is denied) and registered private roots (`whole:
|
|
204
|
+
* true`, everything but `except`). Pass both the lexical and canonical form.
|
|
205
|
+
* `envTemplates`: a user-initiated remote open may read the dotenv
|
|
206
|
+
* templates; nothing else gets that allowance.
|
|
207
|
+
*/
|
|
208
|
+
export function matchesSensitivePath(path, within = [], { envTemplates = false } = {}) {
|
|
209
|
+
const segments = sensitivePathSegments(resolve(path));
|
|
210
|
+
const name = segments[segments.length - 1] ?? '';
|
|
211
|
+
if (segments.some((segment) => SECRET_DIRECTORIES.has(segment))) return true;
|
|
212
|
+
if (secretFileName(name, { envTemplates }) || SECRET_SEQUENCES.some((run) => hasRun(segments, run))) return true;
|
|
213
|
+
const containing = within.filter(
|
|
214
|
+
({ root }) => root.length > 0 && root.every((segment, index) => segments[index] === segment)
|
|
215
|
+
);
|
|
216
|
+
// Mixdog's home and data folders follow Mixdog's own rule even inside a
|
|
217
|
+
// registered private root: a dev or isolated profile keeps them in the app
|
|
218
|
+
// profile, and the tool output and media there stay reachable.
|
|
219
|
+
const own = containing.filter(({ whole }) => !whole);
|
|
220
|
+
if (own.length) return own.some(({ root }) => mixdogSecret(segments.slice(root.length)));
|
|
221
|
+
return containing.some(({ root, except }) => {
|
|
222
|
+
const rest = segments.slice(root.length);
|
|
223
|
+
return rest.length > 0 && !except.includes(rest[0]);
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function realOrSelf(path) {
|
|
228
|
+
try {
|
|
229
|
+
return realpathSync.native(path);
|
|
230
|
+
} catch {
|
|
231
|
+
const parent = dirname(path);
|
|
232
|
+
return parent === path ? path : join(realOrSelf(parent), path.slice(parent.length).replace(/^[\\/]+/, ''));
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/** Mixdog's home and data folders (MIXDOG_HOME / MIXDOG_DATA_DIR, plus any
|
|
237
|
+
* extra data folder the caller names) as `matchesSensitivePath` scopes. */
|
|
238
|
+
export function mixdogSensitiveScopes(extraData = []) {
|
|
239
|
+
const home = process.env.MIXDOG_HOME || join(homedir(), '.mixdog');
|
|
240
|
+
const roots = [home, process.env.MIXDOG_DATA_DIR || join(home, 'data'), ...extraData].filter(Boolean);
|
|
241
|
+
const scopes = [];
|
|
242
|
+
for (const root of roots) {
|
|
243
|
+
for (const form of new Set([resolve(root), realOrSelf(resolve(root))])) {
|
|
244
|
+
scopes.push({ root: sensitivePathSegments(form), except: [], whole: false });
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
return scopes;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** Whether `path` — its own name or what its links lead to — is a
|
|
251
|
+
* credential/secret file under the shared deny list and Mixdog's own scopes. */
|
|
252
|
+
export function isSensitiveLocalPath(path, { extraData = [] } = {}) {
|
|
253
|
+
const within = mixdogSensitiveScopes(extraData);
|
|
254
|
+
const absolute = resolve(path);
|
|
255
|
+
return matchesSensitivePath(absolute, within) || matchesSensitivePath(realOrSelf(absolute), within);
|
|
256
|
+
}
|
|
@@ -809,8 +809,14 @@ export function _unsavedBytesForTest(sessionId) {
|
|
|
809
809
|
async function readBlob(sessionId, entry) {
|
|
810
810
|
if (!entry?.blob) return undefined;
|
|
811
811
|
try {
|
|
812
|
-
const
|
|
813
|
-
|
|
812
|
+
const path = join(blobDir(sessionId), entry.blob);
|
|
813
|
+
const bytes = await readFile(path);
|
|
814
|
+
if (digest(bytes) === entry.blob) return bytes;
|
|
815
|
+
// A torn blob (unflushed write cut by a crash). Blobs are published whole
|
|
816
|
+
// and never replaced, so a mismatch is damage: drop it, or the create-only
|
|
817
|
+
// write of a later capture of the same content would skip over it.
|
|
818
|
+
await rm(path, { force: true });
|
|
819
|
+
return undefined;
|
|
814
820
|
} catch {
|
|
815
821
|
return undefined;
|
|
816
822
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { rmdirSync, rmSync, statSync } from 'node:fs';
|
|
2
2
|
import { readFile, rename, rm, rmdir, stat, writeFile } from 'node:fs/promises';
|
|
3
|
-
import {
|
|
3
|
+
import { writeJsonAtomicSync } from './atomic-file.mjs';
|
|
4
4
|
import { withFileLock, withFileLockSync } from './file-lock.mjs';
|
|
5
5
|
import { readJsonSafe } from './json-file.mjs';
|
|
6
6
|
import { isPidAlive } from './pid-liveness.mjs';
|
|
@@ -19,11 +19,12 @@ const CLAIM_LOCK_STALE_MS = 5000;
|
|
|
19
19
|
const CLAIM_LOCK_OPTS = Object.freeze({ timeoutMs: CLAIM_LOCK_TIMEOUT_MS, staleMs: CLAIM_LOCK_STALE_MS });
|
|
20
20
|
const BUSY = Symbol('claim lock busy');
|
|
21
21
|
|
|
22
|
+
// A claim or handoff is flushed before it is published. The heartbeat's async
|
|
23
|
+
// refresh below is not: it rewrites the record every few seconds, and a record
|
|
24
|
+
// a crash leaves NUL reads as "no owner" (readJsonSafe), so the next claim
|
|
25
|
+
// simply takes over.
|
|
22
26
|
function writeJsonAtomic(path, value) {
|
|
23
|
-
|
|
24
|
-
const tmp = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
25
|
-
writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`);
|
|
26
|
-
renameSync(tmp, path);
|
|
27
|
+
writeJsonAtomicSync(path, value);
|
|
27
28
|
}
|
|
28
29
|
|
|
29
30
|
// Held by another live process or this one's async holder ('busy'). Any other
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
// Explicit skill dependencies. Imported manifests are read, never rewritten.
|
|
2
|
-
import { createHash
|
|
3
|
-
import { existsSync,
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { existsSync, readFileSync, rmSync, statSync } from 'node:fs';
|
|
4
4
|
import { dirname, join, resolve } from 'node:path';
|
|
5
5
|
import { parseDocument } from 'yaml';
|
|
6
|
+
import { writeFileAtomicSync } from './atomic-file.mjs';
|
|
6
7
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
7
8
|
|
|
8
9
|
const MAX_METADATA_BYTES = 64 * 1024;
|
|
@@ -119,12 +120,5 @@ export function saveSkillToolDependencies(filePath, dependencies) {
|
|
|
119
120
|
const tools = normalizeSkillToolDependencies(dependencies, { strict: true });
|
|
120
121
|
const content = `${JSON.stringify({ dependencies: { tools } }, null, 2)}\n`;
|
|
121
122
|
if (Buffer.byteLength(content) > MAX_METADATA_BYTES) throw new Error('Skill dependency metadata is too large.');
|
|
122
|
-
|
|
123
|
-
const temporary = `${path}.${randomUUID()}.tmp`;
|
|
124
|
-
try {
|
|
125
|
-
writeFileSync(temporary, content, { encoding: 'utf8', flag: 'wx' });
|
|
126
|
-
renameSync(temporary, path);
|
|
127
|
-
} finally {
|
|
128
|
-
rmSync(temporary, { force: true });
|
|
129
|
-
}
|
|
123
|
+
writeFileAtomicSync(path, content);
|
|
130
124
|
}
|
|
@@ -50,7 +50,7 @@ import { resolvePluginData } from './plugin-paths.mjs';
|
|
|
50
50
|
import { isPidAlive as pidAlive } from './pid-liveness.mjs';
|
|
51
51
|
import { sleepSync } from './sleep.mjs';
|
|
52
52
|
import { detachedSpawnOpts, hiddenSpawnOpts } from './spawn-flags.mjs';
|
|
53
|
-
import { renameWithRetrySync } from './atomic-file.mjs';
|
|
53
|
+
import { renameWithRetrySync, writeJsonAtomicSync } from './atomic-file.mjs';
|
|
54
54
|
import {
|
|
55
55
|
isDevInstall,
|
|
56
56
|
localPackageVersion,
|
|
@@ -295,7 +295,9 @@ export async function runStagedInstall(version) {
|
|
|
295
295
|
|
|
296
296
|
if (!existsSync(join(pkgDir, 'src', 'cli.mjs'))) return { ok: false, error: 'staged cli.mjs missing' };
|
|
297
297
|
// Marker written LAST — its presence is the completion signal for the swap.
|
|
298
|
-
|
|
298
|
+
// Published whole and flushed: a NUL marker would read as "not staged" and
|
|
299
|
+
// force a full re-install.
|
|
300
|
+
writeJsonAtomicSync(markerPath(verDir), { version: v, pkgDir, stagedAt: Date.now() }, { compact: true });
|
|
299
301
|
return { ok: true, version: v, dir: pkgDir };
|
|
300
302
|
} catch (err) {
|
|
301
303
|
return { ok: false, error: err?.message || String(err) };
|
|
@@ -197,11 +197,11 @@ async function refreshShellJobsStatus(ownerPid) {
|
|
|
197
197
|
}
|
|
198
198
|
// A killed command whose processes are not confirmed gone has already
|
|
199
199
|
// reported its result, so its tool card is closed: it is listed until
|
|
200
|
-
// its owner retires the record.
|
|
201
|
-
//
|
|
200
|
+
// its owner retires the record. An incomplete observation can leave no
|
|
201
|
+
// known survivors even after the owner session closes; only the owning
|
|
202
|
+
// runtime can confirm termination, so its liveness decides, not the root's.
|
|
202
203
|
const unconfirmed = detail?.terminationUnconfirmed === true;
|
|
203
|
-
if (unconfirmed ? !
|
|
204
|
-
continue;
|
|
204
|
+
if (unconfirmed ? !isPidAlive(ownerByJob.get(id) || 0) : !(await isShellJobAlive(detail, p, dir, id))) continue;
|
|
205
205
|
// A foreground command is awaited in its own tool card; listing it here
|
|
206
206
|
// made every short command flash in and out of the shell list. Only
|
|
207
207
|
// backgrounded jobs (including promoted ones) are background work.
|
|
@@ -327,17 +327,6 @@ function summarizeShellJobs({ count, oldestMs, jobs, bySession }) {
|
|
|
327
327
|
return { count, elapsedLabel, jobs, sessions };
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
-
// A held record lives while its owner process does. Once its owner session
|
|
331
|
-
// has closed, it also needs a process left to hold: the root or a survivor
|
|
332
|
-
// its termination watch last reported. Without one it is an orphan whose
|
|
333
|
-
// processes are gone, and it resolves instead of staying unconfirmed.
|
|
334
|
-
function isHeldShellJobAlive(detail, ownerPid) {
|
|
335
|
-
if (!isPidAlive(ownerPid || 0)) return false;
|
|
336
|
-
if (detail?.ownerSessionClosed !== true) return true;
|
|
337
|
-
const survivors = Array.isArray(detail?.survivorPids) ? detail.survivorPids : [];
|
|
338
|
-
return [detail?.pid, ...survivors].some((pid) => isPidAlive(positiveInt(pid)));
|
|
339
|
-
}
|
|
340
|
-
|
|
341
330
|
async function isShellJobAlive(detail, detailPath, dir, id) {
|
|
342
331
|
const pid = positiveInt(detail?.pid);
|
|
343
332
|
if (!pid) return false;
|
|
@@ -236,6 +236,12 @@ export function isTranscriptHiddenToolItem(item) {
|
|
|
236
236
|
// stopped, and the restored transcript must keep that row
|
|
237
237
|
// (scripts/turn-checkpoint-crash-test.mjs).
|
|
238
238
|
const INTERNAL_TRANSCRIPT_INTERRUPT_RE = /^\[request interrupted by user(?: for tool use)?\]$/i;
|
|
239
|
+
// Every runtime control row written as a user turn when a turn is cut short
|
|
240
|
+
// (including the bare "[Request interrupted]" a kept Stop prompt gets). One
|
|
241
|
+
// pattern for "not real conversation": realTurnCount and the preview/title
|
|
242
|
+
// noise filter both use it.
|
|
243
|
+
export const CONTROL_MARKER_RE =
|
|
244
|
+
/^\[(?:truncated|request interrupted(?: by user(?: for tool use)?| by process restart)?)\]$/i;
|
|
239
245
|
const TRANSCRIPT_CANCELLED_STATUS_RE = /^\[request interrupted(?: by process restart)?\]$/i;
|
|
240
246
|
|
|
241
247
|
// Crash/implicit interruption markers remain in model-visible history for
|
|
@@ -53,6 +53,20 @@ export function normalizeMessageAttachments(value) {
|
|
|
53
53
|
return out;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
/**
|
|
57
|
+
* Who a user-kind row came from, as the row's own fields: the sender, the
|
|
58
|
+
* submitting device and the bot that sent it. One definition for the delivered
|
|
59
|
+
* steering row and the still-queued one, so both render the same.
|
|
60
|
+
*/
|
|
61
|
+
export function transcriptSourceFields(value) {
|
|
62
|
+
if (!value || typeof value !== 'object') return {};
|
|
63
|
+
return {
|
|
64
|
+
...(typeof value.sender === 'string' && value.sender ? { sender: value.sender } : {}),
|
|
65
|
+
...(typeof value.device === 'string' && value.device ? { device: value.device } : {}),
|
|
66
|
+
...(typeof value.fromAgent === 'string' && value.fromAgent ? { fromAgent: value.fromAgent } : {}),
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
56
70
|
/** A new stable message id (`meta.transcript.messageId`). */
|
|
57
71
|
export function newTranscriptMessageId() {
|
|
58
72
|
return `msg_${globalThis.crypto.randomUUID().replaceAll('-', '')}`;
|
|
@@ -71,14 +85,19 @@ export function persistedUserTranscriptMetadata(value) {
|
|
|
71
85
|
...(typeof value.attemptId === 'string' && value.attemptId ? { attemptId: value.attemptId } : {}),
|
|
72
86
|
...(Number.isFinite(Number(value.at)) ? { at: Number(value.at) } : {}),
|
|
73
87
|
...transcriptRouteMetadataFields(value),
|
|
74
|
-
|
|
75
|
-
//
|
|
76
|
-
...(
|
|
88
|
+
// Sender, the submitting device (host-stamped display name) and the bot
|
|
89
|
+
// that sent this message to this session (agent-task-sessions.mjs).
|
|
90
|
+
...transcriptSourceFields(value),
|
|
91
|
+
// Bot-to-bot chain position of a bot's message (session-runtime/bot-hops.mjs).
|
|
92
|
+
...(Number.isInteger(value.hop) && value.hop > 0 ? { hop: value.hop } : {}),
|
|
77
93
|
// The turn's change-record key (the outer prompt id), so a restored user
|
|
78
94
|
// row still addresses its turn's Undo/Reapply.
|
|
79
95
|
...(typeof value.checkpointId === 'string' && value.checkpointId ? { checkpointId: value.checkpointId } : {}),
|
|
80
96
|
// Every submission a merged queued batch delivered as this turn.
|
|
81
97
|
...submissionIdsField(value.submissionIds),
|
|
98
|
+
// The executions a woken batch delivered (session-flow/drain.mjs): the
|
|
99
|
+
// provenance a task session's report follows (agent-task-sessions.mjs).
|
|
100
|
+
...consumedExecutionIdsField(value.consumedExecutionIds),
|
|
82
101
|
...attachmentsField(value.attachments),
|
|
83
102
|
// A routine fire delivered as this turn (routine-trigger.mjs).
|
|
84
103
|
...routineTriggerField(value),
|
|
@@ -90,6 +109,12 @@ function submissionIdsField(value) {
|
|
|
90
109
|
return ids.length ? { submissionIds: ids } : {};
|
|
91
110
|
}
|
|
92
111
|
|
|
112
|
+
// Positions are kept: a null stands for a delivered entry that named no execution.
|
|
113
|
+
function consumedExecutionIdsField(value) {
|
|
114
|
+
if (!Array.isArray(value) || !value.length) return {};
|
|
115
|
+
return { consumedExecutionIds: value.map((id) => (typeof id === 'string' && id ? id : null)) };
|
|
116
|
+
}
|
|
117
|
+
|
|
93
118
|
function attachmentsField(value) {
|
|
94
119
|
const attachments = normalizeMessageAttachments(value);
|
|
95
120
|
return attachments.length ? { attachments } : {};
|
|
@@ -3,8 +3,7 @@
|
|
|
3
3
|
* record that names this session's transcript for discovery, plus the
|
|
4
4
|
* first-occurrence failure logger every transcript writer shares.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import { dirname } from 'node:path';
|
|
6
|
+
import { writeJsonAtomicSync } from '../atomic-file.mjs';
|
|
8
7
|
|
|
9
8
|
// Repeated-failure guard: log the first time each distinct message is seen,
|
|
10
9
|
// then stay quiet so a persistently-broken path does not flood stderr.
|
|
@@ -27,10 +26,11 @@ export function createSessionRecord({ sessionRecordPath, sessionId, resolvedCwd,
|
|
|
27
26
|
|
|
28
27
|
function write() {
|
|
29
28
|
try {
|
|
30
|
-
|
|
31
|
-
|
|
29
|
+
// Published whole and flushed: a discovery reader never meets a torn or
|
|
30
|
+
// NUL record, and every refresh rewrites it anyway.
|
|
31
|
+
writeJsonAtomicSync(
|
|
32
32
|
sessionRecordPath,
|
|
33
|
-
|
|
33
|
+
{
|
|
34
34
|
sessionId,
|
|
35
35
|
cwd: resolvedCwd,
|
|
36
36
|
transcriptPath,
|
|
@@ -38,7 +38,8 @@ export function createSessionRecord({ sessionRecordPath, sessionId, resolvedCwd,
|
|
|
38
38
|
updatedAt: Date.now(),
|
|
39
39
|
kind: 'interactive',
|
|
40
40
|
entrypoint: 'cli',
|
|
41
|
-
}
|
|
41
|
+
},
|
|
42
|
+
{ compact: true }
|
|
42
43
|
);
|
|
43
44
|
} catch (err) {
|
|
44
45
|
logOnce(err);
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
// list is what lets a SHARED worktree still revert: the baseline describes the
|
|
14
14
|
// whole tree, but only these paths may be restored from it.
|
|
15
15
|
import { createHash } from 'node:crypto';
|
|
16
|
-
import { mkdir, readFile, readdir,
|
|
16
|
+
import { mkdir, readFile, readdir, rm, stat } from 'node:fs/promises';
|
|
17
17
|
import { join, resolve } from 'node:path';
|
|
18
|
+
import { writeFileAtomicAsync } from './atomic-file.mjs';
|
|
18
19
|
import { cleanString as clean } from './clean.mjs';
|
|
19
20
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
20
21
|
|
|
@@ -173,10 +174,10 @@ async function sweepRecords() {
|
|
|
173
174
|
await sweepDirectory(recordRoot(), RECORD_TTL_MS, MAX_RECORD_FILES);
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
// Flushed before it is published: a crash must not leave a record that reads as
|
|
178
|
+
// NUL (every reader below answers a damaged record as "no record").
|
|
176
179
|
async function writeJsonAtomically(path, payload) {
|
|
177
|
-
|
|
178
|
-
await writeFile(temporary, JSON.stringify(payload), 'utf8');
|
|
179
|
-
await rename(temporary, path);
|
|
180
|
+
await writeFileAtomicAsync(path, JSON.stringify(payload));
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
/**
|
|
@@ -54,6 +54,7 @@ import {
|
|
|
54
54
|
reviewSectionTruncated,
|
|
55
55
|
} from './review-diff.mjs';
|
|
56
56
|
import { PATCH_EDIT_MAX_BYTES } from './bounded-file-read.mjs';
|
|
57
|
+
import { runGitOffThread } from './git-runner.mjs';
|
|
57
58
|
import { cleanString as clean } from './clean.mjs';
|
|
58
59
|
import { createLineDiffBudget, lineDiffHunks, unifiedHunksBody } from './bounded-line-diff.mjs';
|
|
59
60
|
|
|
@@ -1718,13 +1719,52 @@ export async function getTurnChanges(sessionId, options = {}) {
|
|
|
1718
1719
|
let status = 'missing';
|
|
1719
1720
|
if (ownerSessionId && turnChangesPending(ownerSessionId, checkpointId)) status = 'pending';
|
|
1720
1721
|
else if (record) status = record.files?.length || record.outside?.length ? 'ready' : 'none';
|
|
1721
|
-
|
|
1722
|
+
const reply = turnChangesReply({
|
|
1722
1723
|
status,
|
|
1723
1724
|
checkpointId,
|
|
1724
1725
|
record,
|
|
1725
1726
|
patch: options?.patch === true,
|
|
1726
1727
|
file: clean(options?.file) || null,
|
|
1727
1728
|
});
|
|
1729
|
+
return status === 'ready' ? withIgnoredChangeFiles(ownerSessionId, reply) : reply;
|
|
1730
|
+
}
|
|
1731
|
+
|
|
1732
|
+
// The gitignored files of each answered record revision (build output a file
|
|
1733
|
+
// tool wrote): flagged `ignored` so a viewer can leave them out of its turn
|
|
1734
|
+
// previews. Asked of Git once per revision; bounded.
|
|
1735
|
+
const IGNORED_CHANGE_CACHE_LIMIT = 200;
|
|
1736
|
+
const _ignoredChangeFiles = new Map();
|
|
1737
|
+
|
|
1738
|
+
async function ignoredChangePaths(key, root, paths) {
|
|
1739
|
+
const cached = _ignoredChangeFiles.get(key);
|
|
1740
|
+
if (cached) return cached;
|
|
1741
|
+
let ignored = new Set();
|
|
1742
|
+
try {
|
|
1743
|
+
const result = await runGitOffThread(['check-ignore', '-z', '--stdin'], {
|
|
1744
|
+
cwd: root,
|
|
1745
|
+
input: `${paths.join('\0')}\0`,
|
|
1746
|
+
timeoutMs: 10_000,
|
|
1747
|
+
maxBytes: 4 * 1024 * 1024,
|
|
1748
|
+
});
|
|
1749
|
+
// 0: some are ignored, 1: none; anything else (no repository) flags none.
|
|
1750
|
+
if (result.code === 0) ignored = new Set(result.stdout.split('\0').filter(Boolean));
|
|
1751
|
+
} catch {
|
|
1752
|
+
// No Git to ask: nothing is flagged.
|
|
1753
|
+
}
|
|
1754
|
+
_ignoredChangeFiles.set(key, ignored);
|
|
1755
|
+
while (_ignoredChangeFiles.size > IGNORED_CHANGE_CACHE_LIMIT) {
|
|
1756
|
+
_ignoredChangeFiles.delete(_ignoredChangeFiles.keys().next().value);
|
|
1757
|
+
}
|
|
1758
|
+
return ignored;
|
|
1759
|
+
}
|
|
1760
|
+
|
|
1761
|
+
async function withIgnoredChangeFiles(ownerSessionId, reply) {
|
|
1762
|
+
const paths = reply.files.map((file) => file.path).filter((path) => path && !isAbsolute(path));
|
|
1763
|
+
if (!reply.root || paths.length === 0) return reply;
|
|
1764
|
+
const key = `${ownerSessionId}\0${reply.checkpointId}\0${reply.revision}\0${reply.root}`;
|
|
1765
|
+
const ignored = await ignoredChangePaths(key, reply.root, paths);
|
|
1766
|
+
if (ignored.size === 0) return reply;
|
|
1767
|
+
return { ...reply, files: reply.files.map((file) => (ignored.has(file.path) ? { ...file, ignored: true } : file)) };
|
|
1728
1768
|
}
|
|
1729
1769
|
|
|
1730
1770
|
function serializeChangeMutation(ownerSessionId, task) {
|
|
@@ -1871,11 +1911,13 @@ async function mutateTurnChanges(sessionId, checkpointIdValue, fileValue, revisi
|
|
|
1871
1911
|
if (targets.length === 0) throw new Error(`this turn has no changes to be ${verb}`);
|
|
1872
1912
|
}
|
|
1873
1913
|
const updated = await mutateRecordFiles(ownerSessionId, record, targets, reverse);
|
|
1874
|
-
|
|
1914
|
+
const reply = turnChangesReply({
|
|
1875
1915
|
status: updated ? 'ready' : 'missing',
|
|
1876
1916
|
checkpointId,
|
|
1877
1917
|
record: withUnknownChangeStates(ownerSessionId, updated),
|
|
1878
1918
|
});
|
|
1919
|
+
// The viewer caches this reply as it caches a read: the same `ignored` flags.
|
|
1920
|
+
return updated ? withIgnoredChangeFiles(ownerSessionId, reply) : reply;
|
|
1879
1921
|
});
|
|
1880
1922
|
}
|
|
1881
1923
|
|
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
// - cache hit = cacheRead / (input + cacheRead + cacheWrite) over every call
|
|
7
7
|
// whose prompt was measured. A call that could not measure its prompt
|
|
8
8
|
// (promptKnown === false) has unknown zeros, so it adds nothing.
|
|
9
|
-
// - speed = output tokens over
|
|
10
|
-
// response
|
|
11
|
-
//
|
|
12
|
-
//
|
|
9
|
+
// - speed = output tokens over request ms (provider request start → end of the
|
|
10
|
+
// response, the wait for the first token included: the same wall time as the
|
|
11
|
+
// usage ledger's `duration_ms`) of every call with positive output and
|
|
12
|
+
// positive duration: the same eligibility as the usage ledger and the usage
|
|
13
|
+
// dialog, so the readout and the dialog agree. Records saved before this
|
|
14
|
+
// definition carry `speedOutputTokens` / `speedMs` (first visible token →
|
|
15
|
+
// end); they are kept as they are and read only while no request-timed call
|
|
16
|
+
// has landed.
|
|
13
17
|
// - total tokens = input + cacheRead + cacheWrite + output, from the same sums.
|
|
14
|
-
export const MIN_SPEED_OUTPUT_TOKENS = 20;
|
|
15
|
-
export const MIN_SPEED_MS = 500;
|
|
16
18
|
|
|
17
19
|
function num(value) {
|
|
18
20
|
const n = Number(value);
|
|
@@ -21,16 +23,16 @@ function num(value) {
|
|
|
21
23
|
|
|
22
24
|
/** The zeroed totals record. */
|
|
23
25
|
export function emptyUsageReadout() {
|
|
24
|
-
return { input: 0, cacheRead: 0, cacheWrite: 0, output: 0,
|
|
26
|
+
return { input: 0, cacheRead: 0, cacheWrite: 0, output: 0, requestOutputTokens: 0, requestMs: 0 };
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
/**
|
|
28
|
-
* Fold one `onUsageDelta` event (and the call's
|
|
30
|
+
* Fold one `onUsageDelta` event (and the call's request ms, 0 when it was
|
|
29
31
|
* not timed) into the totals; returns a new record, never mutates `totals`.
|
|
30
32
|
* Providers differ on whether input counts cache; the prompt total does not,
|
|
31
33
|
* so fresh input is the prompt less its cache read/write.
|
|
32
34
|
*/
|
|
33
|
-
export function foldUsageReadout(totals, delta = {},
|
|
35
|
+
export function foldUsageReadout(totals, delta = {}, requestMs = 0) {
|
|
34
36
|
const base = totals || emptyUsageReadout();
|
|
35
37
|
const next = { ...base };
|
|
36
38
|
if (delta.promptKnown !== false) {
|
|
@@ -47,10 +49,10 @@ export function foldUsageReadout(totals, delta = {}, generationMs = 0) {
|
|
|
47
49
|
// partial one would pass for the whole: it stays without until the session
|
|
48
50
|
// starts over.
|
|
49
51
|
if (Number.isFinite(base.output)) next.output = base.output + output;
|
|
50
|
-
const ms = num(
|
|
51
|
-
if (ms
|
|
52
|
-
next.
|
|
53
|
-
next.
|
|
52
|
+
const ms = num(requestMs);
|
|
53
|
+
if (ms > 0 && output > 0) {
|
|
54
|
+
next.requestOutputTokens = num(base.requestOutputTokens) + output;
|
|
55
|
+
next.requestMs = num(base.requestMs) + ms;
|
|
54
56
|
}
|
|
55
57
|
return next;
|
|
56
58
|
}
|