mixdog 1.3.1 → 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/README.md +8 -4
- package/package.json +2 -2
- package/scripts/native-release-status.mjs +238 -0
- package/src/defaults/shipped-history.json +5 -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 +50 -3
- package/src/defaults/skills/setup/references/surfaces.md +23 -3
- package/src/defaults/skills/setup-guide/SKILL.md +7 -3
- package/src/defaults/skills/visualize/SKILL.md +9 -6
- package/src/defaults/skills/visualize/references/craft.md +8 -1
- package/src/help.mjs +1 -1
- package/src/lib/keychain-cjs.cjs +4 -1
- package/src/lib/rules-builder.cjs +14 -7
- package/src/rules/agent-mode/AGENT_MODE.md +8 -5
- package/src/rules/agent-mode/MIXY.md +6 -8
- package/src/rules/lead/LEAD.md +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 +9 -2
- package/src/runtime/agent/orchestrator/mcp/agent-connectors.mjs +97 -5
- package/src/runtime/agent/orchestrator/mcp/child-tree.mjs +6 -10
- package/src/runtime/agent/orchestrator/mcp/client.mjs +7 -4
- package/src/runtime/agent/orchestrator/providers/anthropic-api-request.mjs +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-request.mjs +6 -2
- package/src/runtime/agent/orchestrator/providers/auto-effort.mjs +27 -8
- 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 +16 -0
- package/src/runtime/agent/orchestrator/providers/lib/tail-reminder-split.mjs +13 -0
- package/src/runtime/agent/orchestrator/providers/media-normalization.mjs +16 -1
- package/src/runtime/agent/orchestrator/providers/mixdog-local.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/model-proxy-provider.mjs +1 -0
- package/src/runtime/agent/orchestrator/providers/openai-codex-metadata.mjs +5 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-models.mjs +9 -5
- package/src/runtime/agent/orchestrator/providers/openai-compat-responses.mjs +9 -1
- package/src/runtime/agent/orchestrator/providers/openai-compat-wire.mjs +2 -1
- package/src/runtime/agent/orchestrator/providers/openai-direct-request.mjs +30 -3
- package/src/runtime/agent/orchestrator/providers/openai-model-capabilities.mjs +1 -1
- package/src/runtime/agent/orchestrator/providers/openai-ws.mjs +3 -1
- package/src/runtime/agent/orchestrator/providers/opencode-go-oauth.mjs +61 -8
- package/src/runtime/agent/orchestrator/providers/registry.mjs +9 -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 +4 -6
- package/src/runtime/agent/orchestrator/runtime-core/route-context-state.mjs +81 -0
- package/src/runtime/agent/orchestrator/runtime-core/session-text.mjs +2 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-catalog.mjs +12 -1
- package/src/runtime/agent/orchestrator/runtime-core/tool-defs.mjs +6 -5
- package/src/runtime/agent/orchestrator/runtime-core/workflow/agents.mjs +7 -6
- package/src/runtime/agent/orchestrator/runtime-core/workflow/context.mjs +1 -1
- package/src/runtime/agent/orchestrator/runtime-core/workflow/packs.mjs +8 -4
- package/src/runtime/agent/orchestrator/runtime-core/workflow.mjs +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 +17 -2
- package/src/runtime/agent/orchestrator/session/agent-room.mjs +133 -38
- 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 +136 -32
- 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/pre-dispatch-deny.mjs +6 -2
- package/src/runtime/agent/orchestrator/session/loop/route-fallback.mjs +5 -5
- package/src/runtime/agent/orchestrator/session/loop/send-phase.mjs +3 -0
- 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/manager/ask-turn-auto-effort.mjs +13 -2
- 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/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 +55 -27
- package/src/runtime/agent/orchestrator/session/manager/session-prompt-composition.mjs +3 -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-interruption.mjs +24 -8
- package/src/runtime/agent/orchestrator/session/manager/workflow-switch-notice.mjs +94 -13
- package/src/runtime/agent/orchestrator/session/pre-send-compact.mjs +42 -10
- package/src/runtime/agent/orchestrator/session/provider-prefix-guard.mjs +21 -3
- package/src/runtime/agent/orchestrator/session/routine-result.d.mts +18 -3
- package/src/runtime/agent/orchestrator/session/routine-result.mjs +120 -9
- 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 +35 -5
- package/src/runtime/agent/orchestrator/session/transcript-restore/user-text.mjs +42 -2
- package/src/runtime/agent/orchestrator/tools/builtin/advisory-lock.mjs +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 +49 -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 +5 -2
- package/src/runtime/effort-judge/model-install.mjs +16 -8
- 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-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-store.mjs +24 -7
- package/src/runtime/shared/agent-task-starts.mjs +27 -0
- package/src/runtime/shared/atomic-file.d.mts +50 -0
- package/src/runtime/shared/atomic-file.mjs +394 -132
- package/src/runtime/shared/background-tasks.mjs +7 -4
- package/src/runtime/shared/bot-hops.mjs +30 -0
- package/src/runtime/shared/bot-split-migration.mjs +1 -1
- package/src/runtime/shared/bounded-download.mjs +4 -2
- package/src/runtime/shared/config.mjs +39 -9
- package/src/runtime/shared/defaults-separation.mjs +6 -2
- package/src/runtime/shared/delegated-approval-host.mjs +74 -0
- package/src/runtime/shared/err-text.mjs +4 -0
- package/src/runtime/shared/error-presentation.mjs +22 -4
- package/src/runtime/shared/file-has-data.mjs +28 -0
- package/src/runtime/shared/llm/usage-ledger-import.mjs +37 -18
- package/src/runtime/shared/pending-agent-work.mjs +54 -0
- package/src/runtime/shared/process-lifecycle.mjs +8 -12
- package/src/runtime/shared/projects.mjs +50 -41
- package/src/runtime/shared/provider-accounts.mjs +17 -12
- package/src/runtime/shared/routine-trigger.d.mts +6 -1
- package/src/runtime/shared/routine-trigger.mjs +14 -6
- package/src/runtime/shared/run-child.d.mts +3 -0
- package/src/runtime/shared/run-child.mjs +24 -3
- package/src/runtime/shared/session-baseline-store.mjs +8 -2
- package/src/runtime/shared/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/user-cwd.mjs +9 -4
- package/src/runtime/shared/user-data-guard.mjs +41 -42
- 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 +555 -199
- package/src/session-runtime/agent-room-delivery.mjs +143 -36
- package/src/session-runtime/agent-task-sessions.mjs +667 -165
- package/src/session-runtime/agent-tool-routing.mjs +1 -0
- package/src/session-runtime/boot/apis.mjs +45 -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 +2 -2
- 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 +60 -55
- package/src/session-runtime/cwd-plugins/core-memory-context.mjs +2 -4
- 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 +19 -4
- 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-tuning.mjs +4 -1
- package/src/session-runtime/model-route/set-route.mjs +61 -1
- package/src/session-runtime/owned-automation-run.mjs +8 -1
- package/src/session-runtime/room-member-gate.mjs +16 -3
- package/src/session-runtime/route-resolution.mjs +16 -8
- package/src/session-runtime/route-state.mjs +1 -59
- package/src/session-runtime/routine-home-turn.mjs +5 -3
- package/src/session-runtime/runtime-bootstrap.mjs +5 -3
- package/src/session-runtime/runtime-paths.mjs +11 -5
- package/src/session-runtime/runtime-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/restart-notify.mjs +10 -0
- package/src/session-runtime/services/agent-tool/send-flow.mjs +25 -21
- package/src/session-runtime/services/agent-tool/spawn-flow/job-start.mjs +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 +8 -5
- package/src/session-runtime/services/agent-tool/worker-index/row-liveness.mjs +22 -2
- package/src/session-runtime/services/agent-tool/worker-index.mjs +4 -6
- package/src/session-runtime/services/agent-tool.mjs +6 -0
- package/src/session-runtime/services/daemon-crash-capture/daemon-exit-record.mjs +1 -1
- package/src/session-runtime/services/daemon-crash-capture/retention.mjs +4 -3
- package/src/session-runtime/services/hook-bus/config-loader.mjs +14 -5
- package/src/session-runtime/services/hook-bus/rule-file.mjs +49 -11
- package/src/session-runtime/services/plugin-admin.mjs +26 -26
- package/src/session-runtime/services/provider-admin.mjs +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 +8 -2
- package/src/session-runtime/setup-tool/settings-contract.mjs +31 -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 -369
- package/src/session-runtime/tool-surface.mjs +5 -3
- package/src/session-runtime/turn/turn-run/turn-prepare.mjs +95 -88
- package/src/session-runtime/turn/turn-run/turn-timing.mjs +4 -3
- package/src/session-runtime/turn/turn-run.mjs +2 -1
- package/src/session-runtime/workflow-agents-api/agent-connectors.mjs +54 -7
- package/src/session-runtime/workflow-agents-api/agent-editor.mjs +519 -333
- package/src/session-runtime/workflow-agents-api/agent-route.mjs +46 -46
- 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 +51 -10
- package/src/standalone/daemon.mjs +28 -10
- 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.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 +529 -333
- package/src/tui/prompt-history-store.mjs +16 -21
- package/src/tui/session/context-state.mjs +4 -0
- 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 -247
- package/src/tui/session/session-api/settings.mjs +14 -1
- package/src/tui/session/session-flow/drain.mjs +28 -3
- package/src/tui/session/session-flow/failed-turn-rewind.mjs +4 -2
- package/src/tui/session/session-flow/queue/take-entries.mjs +8 -0
- package/src/tui/session/session-flow/steering.mjs +8 -3
- package/src/tui/session/tool-approval.mjs +185 -20
- package/src/tui/session/transcript-intake.mjs +2 -0
- package/src/tui/session/turn.mjs +23 -14
- package/src/tui/transcript-tool-failures.mjs +8 -2
- package/src/vendor/statusline/src/gateway/route-meta.mjs +7 -8
- package/src/vendor/statusline/src/gateway/session-routes.mjs +6 -7
- package/src/rules/agent-mode/ROUTINE.md +0 -6
|
@@ -8,9 +8,9 @@
|
|
|
8
8
|
* Data shape: { projects: [{ name, path, addedAt, lastSelectedAt? }] }
|
|
9
9
|
*/
|
|
10
10
|
import { homedir } from 'node:os';
|
|
11
|
-
import { existsSync, mkdirSync, readFileSync,
|
|
11
|
+
import { existsSync, mkdirSync, readFileSync, statSync } from 'node:fs';
|
|
12
12
|
import { basename, isAbsolute, join, resolve } from 'node:path';
|
|
13
|
-
import {
|
|
13
|
+
import { holdsNoData, readJsonStateSync, updateJsonAtomicSync, writeFileAtomicSync } from './atomic-file.mjs';
|
|
14
14
|
|
|
15
15
|
const MIXDOG_HOME = process.env.MIXDOG_HOME || join(homedir(), '.mixdog');
|
|
16
16
|
const PROJECTS_FILE = process.env.MIXDOG_PROJECTS_FILE
|
|
@@ -30,47 +30,57 @@ function normalizeKey(absPath) {
|
|
|
30
30
|
return process.platform === 'win32' ? text.replace(/[\\/]+$/, '').toLowerCase() : text.replace(/\/+$/, '');
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
// The registry document as a store. A strict read (permission checks and
|
|
34
|
+
// mutations) drops entries without an absolute path: they can never grant
|
|
35
|
+
// anything, and the replaced file survives as projects.json.bak.
|
|
36
|
+
function normalizeStore(parsed, strict) {
|
|
37
|
+
const projects = Array.isArray(parsed?.projects) ? parsed.projects : [];
|
|
38
|
+
return {
|
|
39
|
+
projects: projects
|
|
40
|
+
.filter(
|
|
41
|
+
(entry) => entry && typeof entry.path === 'string' && entry.path.trim() && (!strict || isAbsolute(entry.path))
|
|
42
|
+
)
|
|
43
|
+
.map((entry) => ({
|
|
44
|
+
name: String(entry.name || basename(entry.path) || entry.path),
|
|
45
|
+
path: String(entry.path),
|
|
46
|
+
addedAt: Number(entry.addedAt) || 0,
|
|
47
|
+
...(Number(entry.lastSelectedAt) > 0 ? { lastSelectedAt: Number(entry.lastSelectedAt) } : {}),
|
|
48
|
+
})),
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// A damaged registry file (NUL, truncated, malformed) is kept aside as
|
|
53
|
+
// projects.json.corrupt-<ts> and the last good projects.json.bak is restored
|
|
54
|
+
// when there is one, else the registry starts empty: a damaged file never
|
|
55
|
+
// blocks adding, removing or selecting a project. Strict callers still see a
|
|
56
|
+
// real I/O failure (EACCES, EIO) as an error — an unreadable registry is not
|
|
57
|
+
// an empty one; the tolerant listing reads it as empty.
|
|
33
58
|
function readStore(strict = false) {
|
|
34
59
|
try {
|
|
35
|
-
|
|
36
|
-
const raw = readFileSync(PROJECTS_FILE, 'utf8');
|
|
37
|
-
const parsed = JSON.parse(raw);
|
|
38
|
-
if (
|
|
39
|
-
strict &&
|
|
40
|
-
(!Array.isArray(parsed?.projects) ||
|
|
41
|
-
parsed.projects.some(
|
|
42
|
-
(entry) => !entry || typeof entry.path !== 'string' || !entry.path.trim() || !isAbsolute(entry.path)
|
|
43
|
-
))
|
|
44
|
-
)
|
|
45
|
-
throw new TypeError('Project registry is invalid.');
|
|
46
|
-
const projects = Array.isArray(parsed?.projects) ? parsed.projects : [];
|
|
47
|
-
return {
|
|
48
|
-
projects: projects
|
|
49
|
-
.filter((entry) => entry && typeof entry.path === 'string' && entry.path.trim())
|
|
50
|
-
.map((entry) => ({
|
|
51
|
-
name: String(entry.name || basename(entry.path) || entry.path),
|
|
52
|
-
path: String(entry.path),
|
|
53
|
-
addedAt: Number(entry.addedAt) || 0,
|
|
54
|
-
...(Number(entry.lastSelectedAt) > 0 ? { lastSelectedAt: Number(entry.lastSelectedAt) } : {}),
|
|
55
|
-
})),
|
|
56
|
-
};
|
|
60
|
+
return normalizeStore(readJsonStateSync(PROJECTS_FILE), strict);
|
|
57
61
|
} catch (error) {
|
|
58
|
-
if (strict
|
|
62
|
+
if (strict) throw error;
|
|
59
63
|
return { projects: [] };
|
|
60
64
|
}
|
|
61
65
|
}
|
|
62
66
|
|
|
63
67
|
// Serialize the entire read/modify/write across cooperating processes, not
|
|
64
|
-
// just publication
|
|
65
|
-
//
|
|
66
|
-
//
|
|
68
|
+
// just publication: the file is read under the registry lock. Preserve
|
|
69
|
+
// synchronous return values and no-op behavior (an unchanged store is not
|
|
70
|
+
// written); failed reads, lock acquisition and writes propagate. Each write
|
|
71
|
+
// keeps the replaced version as projects.json.bak.
|
|
67
72
|
function mutateStore(mutator) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
73
|
+
let outcome;
|
|
74
|
+
updateJsonAtomicSync(
|
|
75
|
+
PROJECTS_FILE,
|
|
76
|
+
(current) => {
|
|
77
|
+
const store = normalizeStore(current, true);
|
|
78
|
+
outcome = mutator(store);
|
|
79
|
+
return outcome.changed ? store : undefined;
|
|
80
|
+
},
|
|
81
|
+
{ backup: true }
|
|
82
|
+
);
|
|
83
|
+
return outcome.value;
|
|
74
84
|
}
|
|
75
85
|
|
|
76
86
|
/**
|
|
@@ -78,7 +88,8 @@ function mutateStore(mutator) {
|
|
|
78
88
|
* selection store and memory project_id classification (resolveProjectId)
|
|
79
89
|
* agree. Rules:
|
|
80
90
|
* - Never overwrite an existing marker (user may have set 'common' or a
|
|
81
|
-
* custom id — respect it).
|
|
91
|
+
* custom id — respect it). A marker holding no data (empty or NUL, what a
|
|
92
|
+
* crash leaves) is no id: it is rewritten.
|
|
82
93
|
* - Only act when absPath is a real directory on disk; never create the
|
|
83
94
|
* project folder itself (addProject does NOT create the directory).
|
|
84
95
|
* - Marker value = provided name, else basename(absPath). Skip when the
|
|
@@ -97,14 +108,12 @@ function ensureProjectIdMarker(absPath, name) {
|
|
|
97
108
|
return; // missing / unstattable → silently skip
|
|
98
109
|
}
|
|
99
110
|
const markerPath = join(absPath, '.mixdog', 'project.id');
|
|
100
|
-
|
|
111
|
+
const present = existsSync(markerPath);
|
|
112
|
+
if (present && !holdsNoData(readFileSync(markerPath))) return; // preserve existing value
|
|
101
113
|
const value = String(name || basename(absPath) || '').trim();
|
|
102
114
|
if (!value || value.toLowerCase() === 'common') return;
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
const tmp = `${markerPath}.${process.pid}.tmp`;
|
|
106
|
-
writeFileSync(tmp, `${value}\n`, 'utf8');
|
|
107
|
-
renameSync(tmp, markerPath);
|
|
115
|
+
// A missing marker is created only if still missing (a concurrent creator's wins).
|
|
116
|
+
writeFileAtomicSync(markerPath, `${value}\n`, { createOnly: !present });
|
|
108
117
|
} catch {
|
|
109
118
|
/* best-effort marker; never throw */
|
|
110
119
|
}
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { createHash, randomUUID } from 'node:crypto';
|
|
2
|
-
import { readFileSync } from 'node:fs';
|
|
3
2
|
import { join } from 'node:path';
|
|
4
|
-
import {
|
|
3
|
+
import { readJsonStateSync, unsupportedState, updateJsonAtomic } from './atomic-file.mjs';
|
|
5
4
|
import { resolvePluginData } from './plugin-paths.mjs';
|
|
6
5
|
|
|
7
6
|
export const ACCOUNT_PROVIDERS = Object.freeze([
|
|
@@ -34,12 +33,19 @@ function requireAccountId(id) {
|
|
|
34
33
|
|
|
35
34
|
function validate(raw) {
|
|
36
35
|
if (raw == null) return { version: 1, providers: {} };
|
|
36
|
+
// A newer build's roster is left in place: reads see no accounts and writes
|
|
37
|
+
// refuse, so a downgrade never discards it.
|
|
38
|
+
if (Number.isSafeInteger(raw.version) && raw.version > 1) {
|
|
39
|
+
throw unsupportedState(`Provider account settings version ${raw.version} is newer than this build.`);
|
|
40
|
+
}
|
|
37
41
|
if (raw.version !== 1 || !raw.providers || typeof raw.providers !== 'object') {
|
|
38
42
|
throw new Error('Provider account settings are unreadable.');
|
|
39
43
|
}
|
|
40
44
|
for (const [provider, pool] of Object.entries(raw.providers)) {
|
|
41
|
-
|
|
42
|
-
|
|
45
|
+
// A provider a newer build added is carried as is: this build never reads
|
|
46
|
+
// or writes its pool, and treating it as damage would discard it.
|
|
47
|
+
if (!ACCOUNT_PROVIDERS.includes(provider)) continue;
|
|
48
|
+
if (!Array.isArray(pool?.accounts) || pool.accounts.length > MAX_ACCOUNTS) {
|
|
43
49
|
throw new Error('Invalid provider account list.');
|
|
44
50
|
}
|
|
45
51
|
const ids = pool.accounts.map((row) => requireAccountId(row.id));
|
|
@@ -48,13 +54,11 @@ function validate(raw) {
|
|
|
48
54
|
return raw;
|
|
49
55
|
}
|
|
50
56
|
|
|
57
|
+
// Every OAuth provider's init reads the roster, so a damaged one (crash
|
|
58
|
+
// leftovers, truncation, a shape this build rejects) must never throw: it is
|
|
59
|
+
// kept aside and the last good copy, which every write keeps, is restored.
|
|
51
60
|
function read(dataDir) {
|
|
52
|
-
|
|
53
|
-
return validate(parseJsonText(readFileSync(file(dataDir), 'utf8')));
|
|
54
|
-
} catch (error) {
|
|
55
|
-
if (error.code === 'ENOENT') return validate(null);
|
|
56
|
-
throw error;
|
|
57
|
-
}
|
|
61
|
+
return readJsonStateSync(file(dataDir), { validate, fallback: validate(null), secret: true });
|
|
58
62
|
}
|
|
59
63
|
|
|
60
64
|
// Labels an earlier build assigned automatically. They read as a different
|
|
@@ -106,7 +110,8 @@ export function readProviderAccountPool(provider, dataDir = resolvePluginData())
|
|
|
106
110
|
|
|
107
111
|
async function update(provider, mutate) {
|
|
108
112
|
requireAccountProvider(provider);
|
|
109
|
-
//
|
|
113
|
+
// A roster that parses yet fails validation is recovered here first; the
|
|
114
|
+
// mutator's validate would otherwise refuse every write. I/O failures throw.
|
|
110
115
|
read();
|
|
111
116
|
const result = await updateJsonAtomic(
|
|
112
117
|
file(),
|
|
@@ -117,7 +122,7 @@ async function update(provider, mutate) {
|
|
|
117
122
|
state.providers[provider] = pool;
|
|
118
123
|
return validate(state);
|
|
119
124
|
},
|
|
120
|
-
{ mode: 0o600, secret: true, fsyncDir: true }
|
|
125
|
+
{ mode: 0o600, secret: true, fsyncDir: true, backup: true }
|
|
121
126
|
);
|
|
122
127
|
return normalizePool(result.providers[provider]);
|
|
123
128
|
}
|
|
@@ -4,7 +4,12 @@ export interface RoutineTrigger {
|
|
|
4
4
|
event?: string;
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
export declare function routineTriggerHeader(input: {
|
|
7
|
+
export declare function routineTriggerHeader(input: {
|
|
8
|
+
kind: string;
|
|
9
|
+
name: string;
|
|
10
|
+
project?: string;
|
|
11
|
+
now?: Date | number;
|
|
12
|
+
}): string;
|
|
8
13
|
export declare function stripRoutineTriggerHeader(text: unknown): string;
|
|
9
14
|
export declare function normalizeRoutineTrigger(value: unknown): RoutineTrigger | null;
|
|
10
15
|
export declare function routineTriggerField(value: unknown): { routineTrigger?: RoutineTrigger };
|
|
@@ -4,18 +4,26 @@
|
|
|
4
4
|
// carries (live, persisted in meta.transcript, restored) so a chat paints it
|
|
5
5
|
// as a routine marker instead of a user bubble.
|
|
6
6
|
|
|
7
|
+
import { localTimestampParts } from './time-format.mjs';
|
|
8
|
+
|
|
7
9
|
const HEADER_PREFIX = '[Routine trigger]';
|
|
8
10
|
const KINDS = new Set(['schedule', 'webhook']);
|
|
9
11
|
|
|
10
|
-
/** The first line of a routine turn's prompt: which routine fired
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
|
|
12
|
+
/** The first line of a routine turn's prompt: which routine fired, when (local
|
|
13
|
+
* time and zone: the turn gets no other date), and how to treat the turn.
|
|
14
|
+
* It is the routine's only conduct text. Only the turn's content carries it,
|
|
15
|
+
* never the system prompt, so the thread's prompt cache is untouched. */
|
|
16
|
+
export function routineTriggerHeader({ kind, name, project = '', now = new Date() }) {
|
|
14
17
|
const what = kind === 'webhook' ? 'webhook' : 'schedule';
|
|
18
|
+
const at = localTimestampParts(now);
|
|
15
19
|
return [
|
|
16
20
|
`${HEADER_PREFIX} Your ${what} "${String(name)}" fired.`,
|
|
17
|
-
|
|
18
|
-
'
|
|
21
|
+
...(at ? [`Local time: ${at.date} ${at.time.slice(0, 5)} ${at.timeZone} (${at.offset}).`] : []),
|
|
22
|
+
'Carry out its instructions below now, yourself or by delegating.',
|
|
23
|
+
'Nobody is watching this turn and nothing asks for approval: act within your permissions.',
|
|
24
|
+
'Payments and permanent deletes are refused here: report that and suggest doing it from the chat.',
|
|
25
|
+
'If a sign-in, code or CAPTCHA blocks you, stop and say which site needs the user.',
|
|
26
|
+
'Reply in this chat in plain words, without tool names; if you handed work off, say it started — its result arrives here later.',
|
|
19
27
|
...(project ? [`Project folder: ${project}`] : []),
|
|
20
28
|
].join(' ');
|
|
21
29
|
}
|
|
@@ -8,6 +8,29 @@
|
|
|
8
8
|
// maxBytes, or the spawn error itself.
|
|
9
9
|
import { spawn } from 'node:child_process';
|
|
10
10
|
|
|
11
|
+
/** Kills `child` with everything it started. On Windows a plain kill ends only
|
|
12
|
+
* the child itself: `git` there is a launcher whose git.exe (and the alias,
|
|
13
|
+
* hook or filter it runs) would live on, still holding the output pipes. */
|
|
14
|
+
export function killChildTree(child) {
|
|
15
|
+
if (process.platform === 'win32' && child.pid && child.exitCode === null) {
|
|
16
|
+
const taskkill = `${process.env.SystemRoot || 'C:\\Windows'}\\System32\\taskkill.exe`;
|
|
17
|
+
const fallback = () => {
|
|
18
|
+
try {
|
|
19
|
+
child.kill();
|
|
20
|
+
} catch {}
|
|
21
|
+
};
|
|
22
|
+
spawn(taskkill, ['/PID', String(child.pid), '/T', '/F'], { stdio: 'ignore', windowsHide: true })
|
|
23
|
+
.once('error', fallback)
|
|
24
|
+
.once('exit', (code) => {
|
|
25
|
+
if (code !== 0) fallback();
|
|
26
|
+
});
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
child.kill();
|
|
31
|
+
} catch {}
|
|
32
|
+
}
|
|
33
|
+
|
|
11
34
|
export function runChild({ command, args, cwd, input, env, timeoutMs, maxBytes, stdoutEncoding = 'utf8' }) {
|
|
12
35
|
return new Promise((resolvePromise, rejectPromise) => {
|
|
13
36
|
const hasInput = (typeof input === 'string' || ArrayBuffer.isView(input)) && input.length > 0;
|
|
@@ -35,9 +58,7 @@ export function runChild({ command, args, cwd, input, env, timeoutMs, maxBytes,
|
|
|
35
58
|
? setTimeout(() => {
|
|
36
59
|
const error = new Error(`${command} ${args.join(' ')} timed out after ${timeoutMs}ms`);
|
|
37
60
|
error.code = 'ETIMEDOUT';
|
|
38
|
-
|
|
39
|
-
child.kill();
|
|
40
|
-
} catch {}
|
|
61
|
+
killChildTree(child);
|
|
41
62
|
finish(error);
|
|
42
63
|
}, timeoutMs)
|
|
43
64
|
: null;
|
|
@@ -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
|
|