@sayknow-cli/coding-agent 0.4.7 → 0.5.1
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/CHANGELOG.md +122 -0
- package/dist/types/capability/index.d.ts +10 -20
- package/dist/types/capability/types.d.ts +3 -0
- package/dist/types/cli/args.d.ts +0 -2
- package/dist/types/cli/read-cli.d.ts +1 -0
- package/dist/types/cli.d.ts +9 -1
- package/dist/types/commands/daemon.d.ts +2 -2
- package/dist/types/commands/deep-interview.d.ts +9 -0
- package/dist/types/commands/harness.d.ts +22 -0
- package/dist/types/commands/read.d.ts +6 -0
- package/dist/types/commands/sdk.d.ts +20 -2
- package/dist/types/config/keybindings.d.ts +34 -4
- package/dist/types/config/model-profile-contract.d.ts +40 -0
- package/dist/types/config/model-profiles.d.ts +2 -3
- package/dist/types/config/model-registry.d.ts +5 -0
- package/dist/types/config/provider-auth-health.d.ts +14 -0
- package/dist/types/config/provider-ranking.d.ts +59 -0
- package/dist/types/config/settings-schema.d.ts +156 -10
- package/dist/types/config/settings.d.ts +3 -0
- package/dist/types/edit/modes/patch.d.ts +13 -0
- package/dist/types/edit/path-mutation-lock.d.ts +15 -0
- package/dist/types/eval/py/executor.d.ts +1 -1
- package/dist/types/eval/py/runner-artifact.d.ts +8 -0
- package/dist/types/eval/py/tool-bridge.d.ts +1 -2
- package/dist/types/extensibility/extensions/types.d.ts +43 -2
- package/dist/types/extensibility/shared-events.d.ts +6 -0
- package/dist/types/internal-urls/local-root-gc.d.ts +2 -0
- package/dist/types/main.d.ts +33 -2
- package/dist/types/memories/index.d.ts +2 -0
- package/dist/types/modes/acp/acp-agent.d.ts +22 -9
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/btw-panel.d.ts +1 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +41 -0
- package/dist/types/modes/components/queue-pane.d.ts +8 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +6 -0
- package/dist/types/modes/components/read-tool-group.d.ts +4 -4
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +2 -1
- package/dist/types/modes/components/session-observer-overlay.d.ts +4 -0
- package/dist/types/modes/components/settings-selector.d.ts +2 -0
- package/dist/types/modes/components/tool-execution.d.ts +8 -0
- package/dist/types/modes/components/tool-status-header.d.ts +4 -2
- package/dist/types/modes/components/welcome.d.ts +5 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +5 -0
- package/dist/types/modes/controllers/command-controller.d.ts +2 -0
- package/dist/types/modes/controllers/event-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +13 -2
- package/dist/types/modes/irc-observation-ledger.d.ts +17 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +6 -1
- package/dist/types/modes/prompt-suggestion-controller.d.ts +35 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +2 -2
- package/dist/types/modes/types.d.ts +6 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +2 -1
- package/dist/types/modes/utils/ui-helpers.d.ts +16 -0
- package/dist/types/runtime/memory-domain.d.ts +9 -0
- package/dist/types/runtime/memory-guard-contract.d.ts +71 -0
- package/dist/types/runtime/memory-guard.d.ts +39 -0
- package/dist/types/runtime/memory-limit.d.ts +11 -0
- package/dist/types/runtime-mcp/content-limits.d.ts +7 -0
- package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +1 -1
- package/dist/types/runtime-mcp/manager.d.ts +9 -1
- package/dist/types/runtime-mcp/oauth-flow.d.ts +0 -5
- package/dist/types/runtime-mcp/plugin-network-boundary.d.ts +14 -0
- package/dist/types/runtime-mcp/redaction.d.ts +2 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +5 -0
- package/dist/types/sdk/acp/final-text.d.ts +19 -0
- package/dist/types/sdk/acp/mcp.d.ts +23 -0
- package/dist/types/sdk/broker/broker.d.ts +2 -0
- package/dist/types/sdk/broker/lifecycle.d.ts +13 -0
- package/dist/types/sdk/bus/chat-daemon-control.d.ts +9 -1
- package/dist/types/sdk/bus/config-commands.d.ts +2 -2
- package/dist/types/sdk/bus/config.d.ts +2 -0
- package/dist/types/sdk/bus/control-drain-lease.d.ts +52 -0
- package/dist/types/sdk/bus/conversation-store.d.ts +1 -0
- package/dist/types/sdk/bus/daemon-paths.d.ts +1 -0
- package/dist/types/sdk/bus/index.d.ts +52 -4
- package/dist/types/sdk/bus/kind-aware-reconciliation.d.ts +37 -0
- package/dist/types/sdk/bus/lifecycle-commands.d.ts +5 -4
- package/dist/types/sdk/bus/notification-orchestration.d.ts +1 -0
- package/dist/types/sdk/bus/notification-service.d.ts +34 -0
- package/dist/types/sdk/bus/operator-runtime.d.ts +1 -0
- package/dist/types/sdk/bus/prompt-reconciliation.d.ts +106 -0
- package/dist/types/sdk/bus/rate-limit-pool.d.ts +2 -0
- package/dist/types/sdk/bus/reconciliation-store.d.ts +65 -0
- package/dist/types/sdk/bus/telegram-daemon-contract.d.ts +33 -9
- package/dist/types/sdk/bus/telegram-daemon-control.d.ts +5 -1
- package/dist/types/sdk/bus/telegram-daemon.d.ts +286 -12
- package/dist/types/sdk/bus/telegram-reference.d.ts +2 -0
- package/dist/types/sdk/bus/topic-registry.d.ts +88 -2
- package/dist/types/sdk/client/discovery.d.ts +2 -0
- package/dist/types/sdk/client/liveness.d.ts +7 -0
- package/dist/types/sdk/host/control/operations.d.ts +3 -2
- package/dist/types/sdk/host/host.d.ts +3 -1
- package/dist/types/sdk/host/query/handlers.d.ts +14 -0
- package/dist/types/sdk/index.d.ts +2 -0
- package/dist/types/sdk/lifecycle-session.d.ts +10 -1
- package/dist/types/sdk/prompt-status.d.ts +88 -0
- package/dist/types/sdk/session-directory.d.ts +1 -1
- package/dist/types/sdk/session.d.ts +7 -0
- package/dist/types/sdk/startup-capability.d.ts +9 -0
- package/dist/types/sdk/transport/auth-preface.d.ts +9 -0
- package/dist/types/sdk/transport/index.d.ts +12 -0
- package/dist/types/sdk/transport/relay.d.ts +43 -0
- package/dist/types/sdk/transport/serve-cli.d.ts +4 -0
- package/dist/types/sdk/transport/socket.d.ts +7 -0
- package/dist/types/sdk/transport/stdio.d.ts +3 -0
- package/dist/types/session/agent-session.d.ts +83 -24
- package/dist/types/session/btw-contract.d.ts +16 -0
- package/dist/types/session/fallback-chain-controller.d.ts +18 -2
- package/dist/types/session/internal/managed-session-scope.d.ts +70 -10
- package/dist/types/session/internal/managed-session-storage.d.ts +34 -12
- package/dist/types/session/internal/native-publish-outcome.d.ts +48 -0
- package/dist/types/session/memory-guard-checkpoint-participant.d.ts +69 -0
- package/dist/types/session/session-manager.d.ts +89 -18
- package/dist/types/session/session-storage.d.ts +31 -0
- package/dist/types/session/streaming-output.d.ts +60 -5
- package/dist/types/setup/credential-import.d.ts +5 -1
- package/dist/types/setup/provider-onboarding.d.ts +2 -0
- package/dist/types/skc-runtime/deep-interview-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/deep-interview-stage.d.ts +37 -0
- package/dist/types/skc-runtime/gc-runtime.d.ts +1 -1
- package/dist/types/skc-runtime/linux-proc.d.ts +22 -14
- package/dist/types/skc-runtime/memory-guard-owner-claims.d.ts +47 -0
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +112 -1
- package/dist/types/skc-runtime/repository-binding.d.ts +63 -0
- package/dist/types/skc-runtime/state-runtime.d.ts +3 -1
- package/dist/types/skc-runtime/state-writer.d.ts +5 -1
- package/dist/types/skc-runtime/team-launch.d.ts +1 -1
- package/dist/types/skc-runtime/team-runtime.d.ts +9 -2
- package/dist/types/skc-runtime/team-store.d.ts +12 -0
- package/dist/types/skc-runtime/team-worker-memory-guard.d.ts +92 -0
- package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +7 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +6 -5
- package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +28 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +43 -5
- package/dist/types/skill-state/workflow-hud.d.ts +3 -0
- package/dist/types/ssh/utils.d.ts +1 -0
- package/dist/types/task/discovery.d.ts +3 -1
- package/dist/types/task/executor.d.ts +6 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/task/provider-retry-status.d.ts +13 -0
- package/dist/types/task/receipt.d.ts +2 -0
- package/dist/types/task/types.d.ts +133 -0
- package/dist/types/task/ultragoal-redteam-activation.d.ts +33 -0
- package/dist/types/tools/browser/launch.d.ts +7 -0
- package/dist/types/tools/fetch.d.ts +22 -9
- package/dist/types/tools/image-gen.d.ts +63 -1
- package/dist/types/tools/index.d.ts +7 -1
- package/dist/types/tools/output-meta.d.ts +21 -1
- package/dist/types/tools/read-internals.d.ts +12 -0
- package/dist/types/tools/read.d.ts +45 -1
- package/dist/types/tools/resource-gc.d.ts +23 -0
- package/dist/types/tools/skill-discovery.d.ts +7 -0
- package/dist/types/tools/sqlite-reader.d.ts +7 -0
- package/dist/types/tools/tool-result.d.ts +7 -1
- package/dist/types/utils/prompt-suggestion.d.ts +33 -0
- package/dist/types/utils/shell-snapshot.d.ts +12 -0
- package/dist/types/web/insane/bridge.d.ts +1 -0
- package/dist/types/web/insane/url-guard.d.ts +17 -0
- package/package.json +8 -12
- package/scripts/benchmark-sticky-viewport-pr1.ts +239 -0
- package/scripts/capture-platform-shortcut-labels-showcase.ts +342 -0
- package/scripts/capture-sticky-viewport-showcase.ts +292 -0
- package/scripts/compile-args.ts +0 -1
- package/scripts/dogfood-repository-binding.ts +209 -0
- package/scripts/generate-docs-index.ts +4 -1
- package/scripts/generate-sdk-operation-inventory.ts +9 -0
- package/scripts/run-test-manifest.ts +6 -4
- package/scripts/verify-skc-sdk-canonicalization.ts +122 -18
- package/scripts/verify-sticky-viewport-showcase.ts +611 -0
- package/src/capability/index.ts +58 -42
- package/src/capability/types.ts +3 -0
- package/src/cli/args.ts +16 -100
- package/src/cli/fast-help.ts +1 -1
- package/src/cli/mcp-cli.ts +4 -21
- package/src/cli/notify-cli.ts +6 -38
- package/src/cli/read-cli.ts +2 -1
- package/src/cli/setup-cli.ts +10 -4
- package/src/cli.ts +133 -11
- package/src/commands/daemon.ts +22 -5
- package/src/commands/deep-interview.ts +26 -1
- package/src/commands/harness.ts +35 -12
- package/src/commands/read.ts +11 -2
- package/src/commands/sdk.ts +134 -17
- package/src/commands/team.ts +1 -1
- package/src/commit/agentic/index.ts +3 -3
- package/src/commit/map-reduce/index.ts +2 -2
- package/src/config/keybindings.ts +158 -25
- package/src/config/model-profile-contract.ts +174 -0
- package/src/config/model-profiles.ts +51 -10
- package/src/config/model-registry.ts +24 -4
- package/src/config/model-resolver.ts +2 -2
- package/src/config/provider-auth-health.ts +42 -0
- package/src/config/provider-ranking.ts +119 -0
- package/src/config/settings-schema.ts +157 -10
- package/src/config/settings.ts +254 -108
- package/src/coordinator-mcp/model-preset.ts +21 -74
- package/src/defaults/skc/skills/deep-interview/SKILL.md +147 -24
- package/src/defaults/skc/skills/ralplan/SKILL.md +91 -34
- package/src/defaults/skc/skills/team/SKILL.md +1 -1
- package/src/defaults/skc/skills/ultragoal/SKILL.md +68 -17
- package/src/discovery/ssh.ts +12 -1
- package/src/edit/index.ts +3 -6
- package/src/edit/modes/patch.ts +38 -1
- package/src/edit/modes/replace.ts +42 -0
- package/src/edit/path-mutation-lock.ts +112 -0
- package/src/eval/js/tool-bridge.ts +1 -1
- package/src/eval/py/executor.ts +37 -13
- package/src/eval/py/kernel.ts +1 -21
- package/src/eval/py/prelude.py +2 -2
- package/src/eval/py/runner-artifact.ts +84 -0
- package/src/eval/py/tool-bridge.ts +34 -18
- package/src/extensibility/extensions/runner.ts +4 -0
- package/src/extensibility/extensions/types.ts +42 -4
- package/src/extensibility/plugins/marketplace/fetcher.ts +191 -7
- package/src/extensibility/shared-events.ts +5 -0
- package/src/extensibility/skc-plugins/runtime-adapters.ts +2 -1
- package/src/hooks/native-skill-hook.ts +17 -4
- package/src/internal-urls/docs-index.generated.ts +17 -16
- package/src/internal-urls/local-protocol.ts +87 -25
- package/src/internal-urls/local-root-gc.ts +127 -0
- package/src/internal-urls/mcp-protocol.ts +73 -11
- package/src/main.ts +120 -42
- package/src/memories/index.ts +10 -0
- package/src/modes/DESIGN.md +52 -0
- package/src/modes/acp/acp-agent.ts +647 -83
- package/src/modes/acp/acp-event-mapper.ts +106 -12
- package/src/modes/acp/acp-mode.ts +39 -3
- package/src/modes/action-registry.ts +1 -1
- package/src/modes/components/assistant-message.ts +123 -22
- package/src/modes/components/btw-panel.ts +82 -16
- package/src/modes/components/irc-sidebar.ts +176 -62
- package/src/modes/components/model-selector.ts +61 -4
- package/src/modes/components/notifications-settings-editor.ts +1 -1
- package/src/modes/components/oauth-selector.ts +42 -12
- package/src/modes/components/plan-preview-overlay.ts +1 -1
- package/src/modes/components/queue-pane.ts +55 -9
- package/src/modes/components/queued-message-selector.ts +53 -10
- package/src/modes/components/read-tool-group.ts +82 -14
- package/src/modes/components/runtime-mcp-add-wizard.ts +5 -0
- package/src/modes/components/sayknow-pet-widget.ts +20 -8
- package/src/modes/components/session-observer-overlay.ts +187 -32
- package/src/modes/components/settings-selector.ts +60 -18
- package/src/modes/components/tool-execution.ts +138 -27
- package/src/modes/components/tool-status-header.ts +26 -8
- package/src/modes/components/welcome.ts +50 -23
- package/src/modes/controllers/btw-controller.ts +119 -36
- package/src/modes/controllers/command-controller.ts +101 -54
- package/src/modes/controllers/event-controller.ts +119 -27
- package/src/modes/controllers/extension-ui-controller.ts +23 -12
- package/src/modes/controllers/input-controller.ts +100 -8
- package/src/modes/controllers/runtime-mcp-command-controller.ts +29 -6
- package/src/modes/controllers/selector-controller.ts +4 -1
- package/src/modes/interactive-mode.ts +201 -60
- package/src/modes/irc-observation-ledger.ts +71 -15
- package/src/modes/prompt-action-autocomplete.ts +32 -13
- package/src/modes/prompt-suggestion-controller.ts +96 -0
- package/src/modes/runtime-init.ts +20 -2
- package/src/modes/shared/agent-wire/command-dispatch.ts +3 -3
- package/src/modes/shared/agent-wire/workflow-gate-broker.ts +2 -2
- package/src/modes/types.ts +6 -0
- package/src/modes/utils/hotkeys-markdown.ts +64 -36
- package/src/modes/utils/ui-helpers.ts +99 -4
- package/src/prompts/agent-fragments/restricted-bash.md +1 -1
- package/src/prompts/agents/architect.md +17 -3
- package/src/prompts/agents/critic.md +17 -2
- package/src/prompts/agents/executor.md +1 -1
- package/src/prompts/agents/planner.md +8 -1
- package/src/prompts/system/btw-user.md +3 -8
- package/src/prompts/system/prompt-suggestion-system.md +32 -0
- package/src/prompts/system/system-prompt.md +6 -3
- package/src/prompts/tools/cron.md +3 -1
- package/src/prompts/tools/read.md +4 -2
- package/src/prompts/tools/skill-discovery.md +1 -0
- package/src/runtime/memory-domain.ts +66 -0
- package/src/runtime/memory-guard-contract.ts +73 -0
- package/src/runtime/memory-guard.ts +292 -0
- package/src/runtime/memory-limit.ts +50 -0
- package/src/runtime-mcp/client.ts +41 -55
- package/src/runtime-mcp/content-limits.ts +71 -0
- package/src/runtime-mcp/discoverable-tool-metadata.ts +1 -1
- package/src/runtime-mcp/json-rpc.ts +28 -11
- package/src/runtime-mcp/manager.ts +77 -59
- package/src/runtime-mcp/oauth-flow.ts +0 -72
- package/src/runtime-mcp/plugin-network-boundary.ts +101 -0
- package/src/runtime-mcp/redaction.ts +30 -0
- package/src/runtime-mcp/smithery-auth.ts +17 -2
- package/src/runtime-mcp/smithery-connect.ts +11 -1
- package/src/runtime-mcp/transports/http.ts +63 -22
- package/src/sdk/acp/adapter.ts +8 -1
- package/src/sdk/acp/final-text.ts +40 -0
- package/src/sdk/acp/mcp.ts +26 -0
- package/src/sdk/broker/broker.ts +34 -20
- package/src/sdk/broker/identity.ts +22 -12
- package/src/sdk/broker/lifecycle.ts +293 -43
- package/src/sdk/broker/session-index.ts +11 -1
- package/src/sdk/bus/chat-daemon-cli.ts +2 -2
- package/src/sdk/bus/chat-daemon-control.ts +22 -5
- package/src/sdk/bus/config-commands.ts +2 -2
- package/src/sdk/bus/config.ts +10 -2
- package/src/sdk/bus/control-drain-lease.ts +104 -0
- package/src/sdk/bus/conversation-store.ts +40 -6
- package/src/sdk/bus/daemon-paths.ts +2 -0
- package/src/sdk/bus/index.ts +793 -121
- package/src/sdk/bus/kind-aware-reconciliation.ts +242 -0
- package/src/sdk/bus/lifecycle-commands.ts +63 -10
- package/src/sdk/bus/lifecycle-control-runtime.ts +11 -2
- package/src/sdk/bus/lifecycle-orchestrator.ts +32 -1
- package/src/sdk/bus/notification-orchestration.ts +12 -0
- package/src/sdk/bus/notification-service.ts +87 -25
- package/src/sdk/bus/operator-runtime.ts +31 -5
- package/src/sdk/bus/prompt-reconciliation.ts +240 -0
- package/src/sdk/bus/rate-limit-pool.ts +8 -0
- package/src/sdk/bus/recent-activity.ts +90 -26
- package/src/sdk/bus/reconciliation-store.ts +224 -0
- package/src/sdk/bus/telegram-cli.ts +6 -13
- package/src/sdk/bus/telegram-daemon-cli.ts +8 -3
- package/src/sdk/bus/telegram-daemon-contract.ts +34 -9
- package/src/sdk/bus/telegram-daemon-control.ts +161 -40
- package/src/sdk/bus/telegram-daemon.ts +3649 -593
- package/src/sdk/bus/telegram-reference.ts +20 -2
- package/src/sdk/bus/topic-registry.ts +451 -12
- package/src/sdk/cli/session-cli.ts +15 -2
- package/src/sdk/client/discovery.ts +19 -3
- package/src/sdk/client/liveness.ts +49 -0
- package/src/sdk/host/control/dispatch.ts +8 -2
- package/src/sdk/host/control/operations.ts +3 -2
- package/src/sdk/host/host.ts +27 -9
- package/src/sdk/host/query/handlers.ts +125 -5
- package/src/sdk/index.ts +12 -0
- package/src/sdk/lifecycle-session.ts +19 -3
- package/src/sdk/mcp/server.ts +46 -18
- package/src/sdk/prompt-status.ts +97 -0
- package/src/sdk/protocol/operation-inventory.generated.json +145 -0
- package/src/sdk/protocol/operation-registry.ts +24 -4
- package/src/sdk/session-directory.ts +5 -1
- package/src/sdk/session.ts +69 -8
- package/src/sdk/startup-capability.ts +15 -1
- package/src/sdk/transport/auth-preface.ts +82 -0
- package/src/sdk/transport/index.ts +18 -0
- package/src/sdk/transport/relay.ts +252 -0
- package/src/sdk/transport/serve-cli.ts +105 -0
- package/src/sdk/transport/socket.ts +197 -0
- package/src/sdk/transport/stdio.ts +17 -0
- package/src/session/agent-session.ts +1112 -314
- package/src/session/blob-store.ts +25 -7
- package/src/session/btw-contract.ts +59 -0
- package/src/session/fallback-chain-controller.ts +47 -8
- package/src/session/internal/managed-session-scope.ts +1426 -259
- package/src/session/internal/managed-session-storage.ts +414 -238
- package/src/session/internal/native-publish-outcome.ts +271 -0
- package/src/session/memory-guard-checkpoint-participant.ts +131 -0
- package/src/session/session-manager.ts +2079 -469
- package/src/session/session-storage.ts +151 -11
- package/src/session/streaming-output.ts +262 -43
- package/src/setup/credential-import.ts +28 -3
- package/src/setup/provider-onboarding.ts +49 -5
- package/src/setup/provider-presets.json +2 -1
- package/src/skc-runtime/deep-interview-runtime.ts +303 -36
- package/src/skc-runtime/deep-interview-stage.ts +1016 -0
- package/src/skc-runtime/deep-interview-state.ts +8 -5
- package/src/skc-runtime/gc-render.ts +1 -0
- package/src/skc-runtime/gc-runtime.ts +11 -1
- package/src/skc-runtime/linux-proc.ts +70 -36
- package/src/skc-runtime/managed-owner-supervisor.ts +83 -4
- package/src/skc-runtime/memory-guard-owner-claims.ts +389 -0
- package/src/skc-runtime/ralplan-runtime.ts +1057 -75
- package/src/skc-runtime/repository-binding.ts +267 -0
- package/src/skc-runtime/session-state-sidecar.ts +25 -9
- package/src/skc-runtime/state-runtime.ts +50 -12
- package/src/skc-runtime/state-writer.ts +68 -56
- package/src/skc-runtime/team-launch.ts +34 -5
- package/src/skc-runtime/team-runtime.ts +41 -1
- package/src/skc-runtime/team-store.ts +32 -0
- package/src/skc-runtime/team-worker-memory-guard.ts +363 -0
- package/src/skc-runtime/team-workers.ts +1 -0
- package/src/skc-runtime/tmux-common.ts +13 -2
- package/src/skc-runtime/tmux-owner-isolation.ts +20 -1
- package/src/skc-runtime/tmux-sessions.ts +45 -6
- package/src/skc-runtime/ultragoal-guard.ts +93 -15
- package/src/skc-runtime/ultragoal-receipt-freshness.ts +123 -0
- package/src/skc-runtime/ultragoal-runtime.ts +469 -38
- package/src/skc-runtime/workflow-manifest.generated.json +195 -5
- package/src/skc-runtime/workflow-manifest.ts +67 -3
- package/src/skill-state/workflow-hud.ts +25 -2
- package/src/skill-state/workflow-mutation-guard.ts +104 -12
- package/src/slash-commands/builtin-registry.ts +1 -1
- package/src/ssh/utils.ts +14 -0
- package/src/task/discovery.ts +8 -2
- package/src/task/executor.ts +165 -47
- package/src/task/index.ts +123 -15
- package/src/task/provider-retry-status.ts +88 -0
- package/src/task/receipt.ts +3 -0
- package/src/task/render.ts +45 -26
- package/src/task/skc-command.ts +2 -2
- package/src/task/types.ts +55 -10
- package/src/task/ultragoal-redteam-activation.ts +75 -0
- package/src/tools/ask.ts +1 -1
- package/src/tools/ast-edit.ts +2 -2
- package/src/tools/bash-allowed-prefixes.ts +45 -1
- package/src/tools/bash-pty-selection.ts +2 -2
- package/src/tools/browser/launch.ts +41 -6
- package/src/tools/browser/screenshot-format.ts +6 -1
- package/src/tools/cron.ts +1 -1
- package/src/tools/fetch.ts +127 -154
- package/src/tools/image-gen.ts +476 -23
- package/src/tools/index.ts +7 -1
- package/src/tools/output-meta.ts +176 -9
- package/src/tools/path-utils.ts +5 -1
- package/src/tools/read-internals.ts +44 -0
- package/src/tools/read.ts +1544 -310
- package/src/tools/resource-gc.ts +665 -125
- package/src/tools/skill-discovery.ts +39 -3
- package/src/tools/sqlite-reader.ts +63 -5
- package/src/tools/subagent-render.ts +74 -16
- package/src/tools/subagent.ts +2 -0
- package/src/tools/tool-result.ts +14 -1
- package/src/utils/edit-mode.ts +2 -2
- package/src/utils/pasted-image-path.ts +15 -6
- package/src/utils/prompt-suggestion.ts +265 -0
- package/src/utils/shell-snapshot.ts +173 -63
- package/src/utils/sixel.ts +3 -3
- package/src/web/insane/bridge.ts +5 -1
- package/src/web/insane/url-guard.ts +140 -27
- package/src/web/scrapers/docs-rs.ts +30 -5
- package/src/web/scrapers/types.ts +19 -49
- package/src/web/scrapers/utils.ts +12 -26
- package/src/web/search/providers/codex.ts +1 -1
- package/src/workflow/workflow-intent-diff.ts +4 -1
package/src/config/settings.ts
CHANGED
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import * as fs from "node:fs";
|
|
14
14
|
import * as os from "node:os";
|
|
15
15
|
import * as path from "node:path";
|
|
16
|
+
import * as util from "node:util";
|
|
16
17
|
import {
|
|
17
18
|
getAgentDbPath,
|
|
18
19
|
getAgentDir,
|
|
@@ -43,6 +44,7 @@ import {
|
|
|
43
44
|
atomicYamlPathHash,
|
|
44
45
|
type CasReceipt,
|
|
45
46
|
deleteByPath,
|
|
47
|
+
enqueueAtomicYamlOperation,
|
|
46
48
|
reserveAtomicYamlUpdateSlot,
|
|
47
49
|
setByPath,
|
|
48
50
|
} from "./atomic-yaml-patch";
|
|
@@ -364,6 +366,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
364
366
|
/** Pending debounced ordinary save; its queue slot is reserved immediately. */
|
|
365
367
|
#saveTimer?: NodeJS.Timeout;
|
|
366
368
|
#savePromise?: Promise<void>;
|
|
369
|
+
#changeListeners = new Set<(path: SettingPath) => void>();
|
|
367
370
|
#pendingSaveSlot?: PendingSaveSlot;
|
|
368
371
|
|
|
369
372
|
/** Legacy fallback migration warnings emitted once per settings instance. */
|
|
@@ -374,6 +377,8 @@ export class Settings implements NotificationSettingsReader {
|
|
|
374
377
|
/** A newer config schema must never be rewritten by legacy migrations. */
|
|
375
378
|
#futureSchemaVersion = false;
|
|
376
379
|
#hasMalformedConfigRoot = false;
|
|
380
|
+
/** YAML syntax was unrecoverable, so the loaded defaults are read-only until config.yml is repaired. */
|
|
381
|
+
#hasRecoveredConfigSyntax = false;
|
|
377
382
|
#hasInvalidNotificationConfiguration = false;
|
|
378
383
|
#notificationValidationGeneration = 0;
|
|
379
384
|
/** Notification subtree fingerprint from the last raw durable config read. */
|
|
@@ -385,7 +390,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
385
390
|
private constructor(options: SettingsOptions = {}) {
|
|
386
391
|
this.#cwd = path.normalize(options.cwd ?? getProjectDir());
|
|
387
392
|
this.#agentDir = path.normalize(options.agentDir ?? getAgentDir());
|
|
388
|
-
this.#configPath = options.inMemory ? null : path.
|
|
393
|
+
this.#configPath = options.inMemory ? null : path.resolve(this.#agentDir, "config.yml");
|
|
389
394
|
this.#persist = !options.inMemory;
|
|
390
395
|
|
|
391
396
|
if (options.overrides) {
|
|
@@ -523,6 +528,16 @@ export class Settings implements NotificationSettingsReader {
|
|
|
523
528
|
return structuredClone(this.#schemaReport);
|
|
524
529
|
}
|
|
525
530
|
|
|
531
|
+
onChanged(listener: (path: SettingPath) => void): () => void {
|
|
532
|
+
this.#changeListeners.add(listener);
|
|
533
|
+
return () => this.#changeListeners.delete(listener);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/** Whether durable settings mutations are permitted for the loaded configuration. */
|
|
537
|
+
canWriteDurableConfig(): boolean {
|
|
538
|
+
return !this.#persist || !this.#hasRecoveredConfigSyntax;
|
|
539
|
+
}
|
|
540
|
+
|
|
526
541
|
/**
|
|
527
542
|
* Set a setting value (sync).
|
|
528
543
|
* Updates global settings and reserves its background persistence slot before
|
|
@@ -533,6 +548,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
533
548
|
this.unset(path);
|
|
534
549
|
return;
|
|
535
550
|
}
|
|
551
|
+
this.#assertDurableConfigWritable();
|
|
536
552
|
this.#set(path, value, true);
|
|
537
553
|
}
|
|
538
554
|
|
|
@@ -556,6 +572,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
556
572
|
|
|
557
573
|
const hook = SETTING_HOOKS[path];
|
|
558
574
|
if (hook) hook(value, prev);
|
|
575
|
+
for (const listener of this.#changeListeners) listener(path);
|
|
559
576
|
}
|
|
560
577
|
|
|
561
578
|
/**
|
|
@@ -563,6 +580,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
563
580
|
* `undefined` value. Defaults/project settings become visible immediately.
|
|
564
581
|
*/
|
|
565
582
|
unset<P extends SettingPath>(path: P): void {
|
|
583
|
+
this.#assertDurableConfigWritable();
|
|
566
584
|
const prev = this.get(path);
|
|
567
585
|
const patch: SettingsPatch = {
|
|
568
586
|
path,
|
|
@@ -580,6 +598,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
580
598
|
|
|
581
599
|
const hook = SETTING_HOOKS[path];
|
|
582
600
|
if (hook) hook(this.get(path), prev);
|
|
601
|
+
for (const listener of this.#changeListeners) listener(path);
|
|
583
602
|
}
|
|
584
603
|
|
|
585
604
|
/**
|
|
@@ -587,6 +606,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
587
606
|
* {@link set}, canonical state and hooks change only after the rename succeeds.
|
|
588
607
|
*/
|
|
589
608
|
async commitAtomicBatch(patches: readonly SettingsAtomicPatch[]): Promise<CasReceipt> {
|
|
609
|
+
this.#assertDurableConfigWritable();
|
|
590
610
|
if (!this.#persist || !this.#configPath) {
|
|
591
611
|
const notificationValidationGuard = this.#notificationValidationRestoreGuard();
|
|
592
612
|
const changes = new Map<string, { before: unknown; beforeHash: string; afterHash: string }>();
|
|
@@ -687,13 +707,16 @@ export class Settings implements NotificationSettingsReader {
|
|
|
687
707
|
}));
|
|
688
708
|
for (const entry of revisions) this.#pathRevisions.set(entry.patch.path, entry.revision);
|
|
689
709
|
|
|
710
|
+
const commit = applyAtomicYamlPatches(this.#configPath, durablePatches, {
|
|
711
|
+
validateRoot: (root, currentPatches) =>
|
|
712
|
+
this.#rejectAtomicNotificationRepairForMalformedRoot(currentPatches, root),
|
|
713
|
+
onRestored: restoredPatches =>
|
|
714
|
+
this.#applyRestoredDurableBatch(revisions, restoredPatches, notificationValidationGuard),
|
|
715
|
+
});
|
|
716
|
+
const failureRefresh = this.#reserveAtomicFailureRefresh(commit);
|
|
690
717
|
try {
|
|
691
|
-
const receipt = await
|
|
692
|
-
|
|
693
|
-
this.#rejectAtomicNotificationRepairForMalformedRoot(currentPatches, root),
|
|
694
|
-
onRestored: restoredPatches =>
|
|
695
|
-
this.#applyRestoredDurableBatch(revisions, restoredPatches, notificationValidationGuard),
|
|
696
|
-
});
|
|
718
|
+
const receipt = await commit;
|
|
719
|
+
await failureRefresh;
|
|
697
720
|
const appliedNotificationMutation = this.#applyDurableBatch(revisions);
|
|
698
721
|
this.#recordNotificationValidationBatchApply(notificationValidationGuard, appliedNotificationMutation);
|
|
699
722
|
return receipt;
|
|
@@ -704,6 +727,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
704
727
|
else this.#pathRevisions.set(entry.patch.path, entry.previousRevision);
|
|
705
728
|
}
|
|
706
729
|
}
|
|
730
|
+
await failureRefresh;
|
|
707
731
|
if (this.#modified.size > 0 && !this.#pendingSaveSlot) this.#queueSave();
|
|
708
732
|
throw error;
|
|
709
733
|
}
|
|
@@ -715,6 +739,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
715
739
|
current: Readonly<RawSettings>,
|
|
716
740
|
) => Promise<readonly SettingsAtomicPatch[]> | readonly SettingsAtomicPatch[],
|
|
717
741
|
): Promise<CasReceipt> {
|
|
742
|
+
this.#assertDurableConfigWritable();
|
|
718
743
|
if (!this.#persist || !this.#configPath) {
|
|
719
744
|
const patches = await buildPatches(structuredClone(this.#global));
|
|
720
745
|
return this.commitAtomicBatch(patches);
|
|
@@ -723,38 +748,41 @@ export class Settings implements NotificationSettingsReader {
|
|
|
723
748
|
this.#releasePendingSaveSlot();
|
|
724
749
|
let revisions: DurableBatchRevision[] = [];
|
|
725
750
|
const notificationValidationGuard = this.#notificationValidationRestoreGuard();
|
|
751
|
+
const commit = applyAtomicYamlPatchesWithCurrent(
|
|
752
|
+
this.#configPath,
|
|
753
|
+
async current => {
|
|
754
|
+
const patches = await buildPatches(structuredClone(current));
|
|
755
|
+
const durablePatches: AtomicYamlPatch[] = patches.map(patch => {
|
|
756
|
+
if (!isAtomicSettingsPath(patch.path)) {
|
|
757
|
+
throw new Error(`Unknown setting path for atomic batch: ${patch.path}`);
|
|
758
|
+
}
|
|
759
|
+
if (patch.op === "unset") return { path: patch.path, op: "unset" };
|
|
760
|
+
if (patch.value === undefined) {
|
|
761
|
+
throw new TypeError(
|
|
762
|
+
`Settings set patch for ${patch.path} cannot carry undefined; use unset instead.`,
|
|
763
|
+
);
|
|
764
|
+
}
|
|
765
|
+
return { path: patch.path, op: "set", value: structuredClone(patch.value) };
|
|
766
|
+
});
|
|
767
|
+
revisions = durablePatches.map(patch => ({
|
|
768
|
+
patch,
|
|
769
|
+
revision: ++this.#nextRevision,
|
|
770
|
+
previousRevision: this.#pathRevisions.get(patch.path),
|
|
771
|
+
}));
|
|
772
|
+
for (const entry of revisions) this.#pathRevisions.set(entry.patch.path, entry.revision);
|
|
773
|
+
return durablePatches;
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
validateRoot: (root, currentPatches) =>
|
|
777
|
+
this.#rejectAtomicNotificationRepairForMalformedRoot(currentPatches, root),
|
|
778
|
+
onRestored: restoredPatches =>
|
|
779
|
+
this.#applyRestoredDurableBatch(revisions, restoredPatches, notificationValidationGuard),
|
|
780
|
+
},
|
|
781
|
+
);
|
|
782
|
+
const failureRefresh = this.#reserveAtomicFailureRefresh(commit);
|
|
726
783
|
try {
|
|
727
|
-
const receipt = await
|
|
728
|
-
|
|
729
|
-
async current => {
|
|
730
|
-
const patches = await buildPatches(structuredClone(current));
|
|
731
|
-
const durablePatches: AtomicYamlPatch[] = patches.map(patch => {
|
|
732
|
-
if (!isAtomicSettingsPath(patch.path)) {
|
|
733
|
-
throw new Error(`Unknown setting path for atomic batch: ${patch.path}`);
|
|
734
|
-
}
|
|
735
|
-
if (patch.op === "unset") return { path: patch.path, op: "unset" };
|
|
736
|
-
if (patch.value === undefined) {
|
|
737
|
-
throw new TypeError(
|
|
738
|
-
`Settings set patch for ${patch.path} cannot carry undefined; use unset instead.`,
|
|
739
|
-
);
|
|
740
|
-
}
|
|
741
|
-
return { path: patch.path, op: "set", value: structuredClone(patch.value) };
|
|
742
|
-
});
|
|
743
|
-
revisions = durablePatches.map(patch => ({
|
|
744
|
-
patch,
|
|
745
|
-
revision: ++this.#nextRevision,
|
|
746
|
-
previousRevision: this.#pathRevisions.get(patch.path),
|
|
747
|
-
}));
|
|
748
|
-
for (const entry of revisions) this.#pathRevisions.set(entry.patch.path, entry.revision);
|
|
749
|
-
return durablePatches;
|
|
750
|
-
},
|
|
751
|
-
{
|
|
752
|
-
validateRoot: (root, currentPatches) =>
|
|
753
|
-
this.#rejectAtomicNotificationRepairForMalformedRoot(currentPatches, root),
|
|
754
|
-
onRestored: restoredPatches =>
|
|
755
|
-
this.#applyRestoredDurableBatch(revisions, restoredPatches, notificationValidationGuard),
|
|
756
|
-
},
|
|
757
|
-
);
|
|
784
|
+
const receipt = await commit;
|
|
785
|
+
await failureRefresh;
|
|
758
786
|
const appliedNotificationMutation = this.#applyDurableBatch(revisions);
|
|
759
787
|
this.#recordNotificationValidationBatchApply(notificationValidationGuard, appliedNotificationMutation);
|
|
760
788
|
return receipt;
|
|
@@ -765,6 +793,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
765
793
|
else this.#pathRevisions.set(entry.patch.path, entry.previousRevision);
|
|
766
794
|
}
|
|
767
795
|
}
|
|
796
|
+
await failureRefresh;
|
|
768
797
|
if (this.#modified.size > 0 && !this.#pendingSaveSlot) this.#queueSave();
|
|
769
798
|
throw error;
|
|
770
799
|
}
|
|
@@ -819,6 +848,15 @@ export class Settings implements NotificationSettingsReader {
|
|
|
819
848
|
}
|
|
820
849
|
}
|
|
821
850
|
await this.#refreshDurableSettings();
|
|
851
|
+
if (this.#modified.size > 0 && !this.#pendingSaveSlot) {
|
|
852
|
+
this.#queueSave();
|
|
853
|
+
this.#releasePendingSaveSlot();
|
|
854
|
+
try {
|
|
855
|
+
await this.#savePromise;
|
|
856
|
+
} catch {
|
|
857
|
+
// Keep dirty state for a later explicit flush or mutation.
|
|
858
|
+
}
|
|
859
|
+
}
|
|
822
860
|
}
|
|
823
861
|
|
|
824
862
|
/** Like {@link flush}, but reports a durable save failure to the caller. */
|
|
@@ -826,8 +864,20 @@ export class Settings implements NotificationSettingsReader {
|
|
|
826
864
|
this.#releasePendingSaveSlot();
|
|
827
865
|
if (this.#modified.size > 0 && !this.#pendingSaveSlot) this.#queueSave();
|
|
828
866
|
this.#releasePendingSaveSlot();
|
|
829
|
-
|
|
867
|
+
let saveError: unknown;
|
|
868
|
+
try {
|
|
869
|
+
await this.#savePromise;
|
|
870
|
+
} catch (error) {
|
|
871
|
+
saveError = error;
|
|
872
|
+
}
|
|
830
873
|
await this.#refreshDurableSettings();
|
|
874
|
+
if (this.#modified.size > 0 && !this.#pendingSaveSlot) {
|
|
875
|
+
this.#queueSave();
|
|
876
|
+
this.#releasePendingSaveSlot();
|
|
877
|
+
await this.#savePromise;
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
if (saveError !== undefined) throw saveError;
|
|
831
881
|
}
|
|
832
882
|
|
|
833
883
|
async cloneForCwd(cwd: string): Promise<Settings> {
|
|
@@ -841,15 +891,23 @@ export class Settings implements NotificationSettingsReader {
|
|
|
841
891
|
inMemory: !this.#persist,
|
|
842
892
|
});
|
|
843
893
|
cloned.#storage = this.#storage;
|
|
894
|
+
cloned.#schemaReport = structuredClone(this.#schemaReport);
|
|
895
|
+
cloned.#schemaMigrationPending = this.#schemaMigrationPending;
|
|
844
896
|
cloned.#futureSchemaVersion = this.#futureSchemaVersion;
|
|
845
|
-
|
|
897
|
+
cloned.#hasMalformedConfigRoot = this.#hasMalformedConfigRoot;
|
|
898
|
+
cloned.#hasRecoveredConfigSyntax = this.#hasRecoveredConfigSyntax;
|
|
899
|
+
cloned.#hasInvalidNotificationConfiguration = this.#hasInvalidNotificationConfiguration;
|
|
900
|
+
cloned.#notificationValidationGeneration = this.#notificationValidationGeneration;
|
|
846
901
|
cloned.#global = structuredClone(this.#global);
|
|
847
902
|
cloned.#rawNotificationConfig = structuredClone(this.#rawNotificationConfig);
|
|
848
903
|
cloned.#durableRawNotificationConfig = structuredClone(this.#durableRawNotificationConfig);
|
|
849
904
|
cloned.#durableNotificationFingerprint = this.#durableNotificationFingerprint;
|
|
850
905
|
cloned.#project = this.#persist ? await cloned.#loadProjectSettings() : structuredClone(this.#project);
|
|
851
906
|
cloned.#overrides = structuredClone(this.#overrides);
|
|
852
|
-
|
|
907
|
+
if (cloned.#hasRecoveredConfigSyntax) {
|
|
908
|
+
cloned.#sanitizeModelSelectorRecords();
|
|
909
|
+
cloned.#rebuildMerged();
|
|
910
|
+
} else await cloned.#normalizeAfterLoad();
|
|
853
911
|
cloned.#fireAllHooks();
|
|
854
912
|
return cloned;
|
|
855
913
|
}
|
|
@@ -941,6 +999,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
941
999
|
}
|
|
942
1000
|
|
|
943
1001
|
setGlobalModelRole(role: ModelRole | string, modelId: ModelSelectorValue | undefined): void {
|
|
1002
|
+
this.#assertDurableConfigWritable();
|
|
944
1003
|
const revision = ++this.#nextRevision;
|
|
945
1004
|
const patch: SettingsPatch = {
|
|
946
1005
|
path: "modelRoles",
|
|
@@ -974,13 +1033,27 @@ export class Settings implements NotificationSettingsReader {
|
|
|
974
1033
|
}
|
|
975
1034
|
|
|
976
1035
|
#replaceGlobalWithDurable(current: RawSettings): void {
|
|
1036
|
+
const previous = new Map<SettingPath, unknown>();
|
|
1037
|
+
for (const settingPath of Object.keys(SETTINGS_SCHEMA) as SettingPath[]) {
|
|
1038
|
+
previous.set(settingPath, structuredClone(this.get(settingPath)));
|
|
1039
|
+
}
|
|
977
1040
|
this.#global = current;
|
|
978
1041
|
for (const patch of this.#pendingPatchesInGenerationOrder()) {
|
|
979
1042
|
applySettingsPatch(this.#global, { ...patch, value: structuredClone(patch.value) });
|
|
980
|
-
this.#
|
|
1043
|
+
if (this.#rawNotificationConfig !== undefined) {
|
|
1044
|
+
this.#applyNotificationMutationToRaw(patch.path, patch.value);
|
|
1045
|
+
}
|
|
981
1046
|
}
|
|
982
1047
|
this.#rebuildMerged();
|
|
983
1048
|
this.#recomputeNotificationValidationFromRaw();
|
|
1049
|
+
for (const settingPath of Object.keys(SETTINGS_SCHEMA) as SettingPath[]) {
|
|
1050
|
+
const previousValue = previous.get(settingPath);
|
|
1051
|
+
const nextValue = this.get(settingPath);
|
|
1052
|
+
if (util.isDeepStrictEqual(previousValue, nextValue)) continue;
|
|
1053
|
+
const hook = SETTING_HOOKS[settingPath];
|
|
1054
|
+
if (hook) hook(nextValue, previousValue);
|
|
1055
|
+
for (const listener of this.#changeListeners) listener(settingPath);
|
|
1056
|
+
}
|
|
984
1057
|
}
|
|
985
1058
|
/**
|
|
986
1059
|
* Set an agent model override while keeping any live runtime override aligned.
|
|
@@ -1074,60 +1147,97 @@ export class Settings implements NotificationSettingsReader {
|
|
|
1074
1147
|
}
|
|
1075
1148
|
}
|
|
1076
1149
|
|
|
1077
|
-
|
|
1150
|
+
#resetYamlLoadState(): void {
|
|
1078
1151
|
this.#hasMalformedConfigRoot = false;
|
|
1152
|
+
this.#hasRecoveredConfigSyntax = false;
|
|
1079
1153
|
this.#hasInvalidNotificationConfiguration = false;
|
|
1154
|
+
this.#schemaReport = { issues: [], valid: true };
|
|
1155
|
+
this.#schemaMigrationPending = false;
|
|
1156
|
+
this.#futureSchemaVersion = false;
|
|
1080
1157
|
this.#captureRawNotificationConfig({});
|
|
1081
|
-
|
|
1082
|
-
const content = await Bun.file(filePath).text();
|
|
1083
|
-
const parsed = YAML.parse(content);
|
|
1084
|
-
if (parsed === undefined) return {};
|
|
1085
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1086
|
-
this.#hasMalformedConfigRoot = true;
|
|
1087
|
-
this.#captureRawNotificationConfig(undefined);
|
|
1088
|
-
return {};
|
|
1089
|
-
}
|
|
1090
|
-
const parsedRaw = parsed as RawSettings;
|
|
1091
|
-
if (filePath === this.#configPath) this.#captureRawNotificationConfig(parsedRaw);
|
|
1092
|
-
if (filePath === this.#configPath) {
|
|
1093
|
-
try {
|
|
1094
|
-
parseNotificationSettingsSnapshot(parsedRaw);
|
|
1095
|
-
} catch (error) {
|
|
1096
|
-
if (!(error instanceof Error) || error.message !== "skc_notify_daemon_invalid_configuration")
|
|
1097
|
-
throw error;
|
|
1098
|
-
this.#hasInvalidNotificationConfiguration = true;
|
|
1099
|
-
}
|
|
1100
|
-
}
|
|
1101
|
-
this.#futureSchemaVersion =
|
|
1102
|
-
filePath === this.#configPath &&
|
|
1103
|
-
typeof parsedRaw.configSchemaVersion === "number" &&
|
|
1104
|
-
parsedRaw.configSchemaVersion > CONFIG_SCHEMA_VERSION;
|
|
1158
|
+
}
|
|
1105
1159
|
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
) {
|
|
1111
|
-
this.#schemaMigrationPending = true;
|
|
1112
|
-
}
|
|
1113
|
-
const migrated = this.#migrateRawSettings(parsedRaw);
|
|
1114
|
-
const reconciled = reconcileSettingsSchema(migrated);
|
|
1115
|
-
if (typeof configSchemaVersion === "number" && configSchemaVersion > CONFIG_SCHEMA_VERSION) {
|
|
1116
|
-
reconciled.report.issues.push({
|
|
1117
|
-
path: "configSchemaVersion",
|
|
1118
|
-
kind: "pending-migration",
|
|
1119
|
-
detail: `Configuration requires schema version ${configSchemaVersion}.`,
|
|
1120
|
-
});
|
|
1121
|
-
}
|
|
1122
|
-
this.#schemaReport = reconciled.report;
|
|
1123
|
-
return reconciled.settings;
|
|
1160
|
+
async #loadYaml(filePath: string): Promise<RawSettings> {
|
|
1161
|
+
let content: string;
|
|
1162
|
+
try {
|
|
1163
|
+
content = await Bun.file(filePath).text();
|
|
1124
1164
|
} catch (error) {
|
|
1125
1165
|
if (isEnoent(error)) {
|
|
1126
|
-
this.#
|
|
1166
|
+
this.#resetYamlLoadState();
|
|
1127
1167
|
return {};
|
|
1128
1168
|
}
|
|
1129
1169
|
throw error;
|
|
1130
1170
|
}
|
|
1171
|
+
this.#resetYamlLoadState();
|
|
1172
|
+
if (content.trim() === "") return {};
|
|
1173
|
+
let parsed: unknown;
|
|
1174
|
+
try {
|
|
1175
|
+
parsed = YAML.parse(content);
|
|
1176
|
+
} catch {
|
|
1177
|
+
this.#hasRecoveredConfigSyntax = true;
|
|
1178
|
+
this.#hasMalformedConfigRoot = true;
|
|
1179
|
+
this.#schemaReport = {
|
|
1180
|
+
valid: false,
|
|
1181
|
+
issues: [
|
|
1182
|
+
{
|
|
1183
|
+
path: "config.yml",
|
|
1184
|
+
kind: "invalid",
|
|
1185
|
+
detail: "Configuration YAML syntax is invalid; repair config.yml before changing settings.",
|
|
1186
|
+
},
|
|
1187
|
+
],
|
|
1188
|
+
};
|
|
1189
|
+
this.#captureRawNotificationConfig(undefined);
|
|
1190
|
+
return {};
|
|
1191
|
+
}
|
|
1192
|
+
if (parsed === undefined) return {};
|
|
1193
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
1194
|
+
this.#hasMalformedConfigRoot = true;
|
|
1195
|
+
this.#schemaReport = {
|
|
1196
|
+
valid: false,
|
|
1197
|
+
issues: [
|
|
1198
|
+
{
|
|
1199
|
+
path: "config.yml",
|
|
1200
|
+
kind: "invalid",
|
|
1201
|
+
detail: "Configuration root must be a YAML mapping.",
|
|
1202
|
+
},
|
|
1203
|
+
],
|
|
1204
|
+
};
|
|
1205
|
+
this.#captureRawNotificationConfig(undefined);
|
|
1206
|
+
return {};
|
|
1207
|
+
}
|
|
1208
|
+
const parsedRaw = parsed as RawSettings;
|
|
1209
|
+
if (filePath === this.#configPath) this.#captureRawNotificationConfig(parsedRaw);
|
|
1210
|
+
if (filePath === this.#configPath) {
|
|
1211
|
+
try {
|
|
1212
|
+
parseNotificationSettingsSnapshot(parsedRaw);
|
|
1213
|
+
} catch (error) {
|
|
1214
|
+
if (!(error instanceof Error) || error.message !== "skc_notify_daemon_invalid_configuration") throw error;
|
|
1215
|
+
this.#hasInvalidNotificationConfiguration = true;
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
this.#futureSchemaVersion =
|
|
1219
|
+
filePath === this.#configPath &&
|
|
1220
|
+
typeof parsedRaw.configSchemaVersion === "number" &&
|
|
1221
|
+
parsedRaw.configSchemaVersion > CONFIG_SCHEMA_VERSION;
|
|
1222
|
+
|
|
1223
|
+
const configSchemaVersion = parsedRaw.configSchemaVersion;
|
|
1224
|
+
if (
|
|
1225
|
+
filePath === this.#configPath &&
|
|
1226
|
+
(typeof configSchemaVersion !== "number" || configSchemaVersion < CONFIG_SCHEMA_VERSION)
|
|
1227
|
+
) {
|
|
1228
|
+
this.#schemaMigrationPending = true;
|
|
1229
|
+
}
|
|
1230
|
+
const migrated = this.#migrateRawSettings(parsedRaw);
|
|
1231
|
+
const reconciled = reconcileSettingsSchema(migrated);
|
|
1232
|
+
if (typeof configSchemaVersion === "number" && configSchemaVersion > CONFIG_SCHEMA_VERSION) {
|
|
1233
|
+
reconciled.report.issues.push({
|
|
1234
|
+
path: "configSchemaVersion",
|
|
1235
|
+
kind: "pending-migration",
|
|
1236
|
+
detail: `Configuration requires schema version ${configSchemaVersion}.`,
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
this.#schemaReport = reconciled.report;
|
|
1240
|
+
return reconciled.settings;
|
|
1131
1241
|
}
|
|
1132
1242
|
|
|
1133
1243
|
async #loadProjectSettings(): Promise<RawSettings> {
|
|
@@ -1520,7 +1630,7 @@ export class Settings implements NotificationSettingsReader {
|
|
|
1520
1630
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1521
1631
|
|
|
1522
1632
|
#queueSave(): void {
|
|
1523
|
-
if (!this.#persist || !this.#configPath) return;
|
|
1633
|
+
if (!this.#persist || !this.#configPath || this.#hasRecoveredConfigSyntax) return;
|
|
1524
1634
|
|
|
1525
1635
|
const currentSlot = this.#pendingSaveSlot;
|
|
1526
1636
|
if (currentSlot && !currentSlot.captured && !currentSlot.released) {
|
|
@@ -1587,25 +1697,33 @@ export class Settings implements NotificationSettingsReader {
|
|
|
1587
1697
|
this.#recomputeNotificationValidationFromRaw();
|
|
1588
1698
|
},
|
|
1589
1699
|
};
|
|
1590
|
-
})
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
}
|
|
1598
|
-
if (durableBeforeWrite) {
|
|
1599
|
-
this.#global = durableBeforeWrite;
|
|
1600
|
-
this.#captureRawNotificationConfig(durableBeforeWrite);
|
|
1601
|
-
for (const patch of this.#pendingPatchesInGenerationOrder()) {
|
|
1602
|
-
applySettingsPatch(this.#global, { ...patch, value: structuredClone(patch.value) });
|
|
1603
|
-
this.#applyNotificationMutationToRaw(patch.path, patch.value);
|
|
1700
|
+
})
|
|
1701
|
+
.then(() => undefined)
|
|
1702
|
+
.catch(async error => {
|
|
1703
|
+
logger.warn("Settings: background save failed", { error: String(error) });
|
|
1704
|
+
for (const patch of captured) {
|
|
1705
|
+
const key = settingsPatchKey(patch);
|
|
1706
|
+
if (this.#modified.get(key)?.generation === patch.generation) this.#modified.set(key, patch);
|
|
1604
1707
|
}
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1708
|
+
if (durableBeforeWrite) {
|
|
1709
|
+
this.#global = durableBeforeWrite;
|
|
1710
|
+
this.#captureRawNotificationConfig(durableBeforeWrite);
|
|
1711
|
+
for (const patch of this.#pendingPatchesInGenerationOrder()) {
|
|
1712
|
+
applySettingsPatch(this.#global, { ...patch, value: structuredClone(patch.value) });
|
|
1713
|
+
this.#applyNotificationMutationToRaw(patch.path, patch.value);
|
|
1714
|
+
}
|
|
1715
|
+
this.#rebuildMerged();
|
|
1716
|
+
this.#recomputeNotificationValidationFromRaw();
|
|
1717
|
+
}
|
|
1718
|
+
try {
|
|
1719
|
+
await this.#refreshDurableSettings();
|
|
1720
|
+
} catch (refreshError) {
|
|
1721
|
+
logger.warn("Settings: refresh after background save failure failed", { error: String(refreshError) });
|
|
1722
|
+
}
|
|
1723
|
+
throw error;
|
|
1724
|
+
});
|
|
1725
|
+
this.#savePromise = save;
|
|
1726
|
+
void save.catch(() => {});
|
|
1609
1727
|
this.#armSaveTimer(slot);
|
|
1610
1728
|
}
|
|
1611
1729
|
|
|
@@ -1709,13 +1827,41 @@ export class Settings implements NotificationSettingsReader {
|
|
|
1709
1827
|
return applicable.some(patch => isNotificationSettingsPath(patch.path));
|
|
1710
1828
|
}
|
|
1711
1829
|
|
|
1712
|
-
|
|
1713
|
-
if (!this.#persist || !this.#configPath) return;
|
|
1830
|
+
#reserveAtomicFailureRefresh(commit: Promise<unknown>): Promise<void> {
|
|
1831
|
+
if (!this.#persist || !this.#configPath) return Promise.resolve();
|
|
1832
|
+
return enqueueAtomicYamlOperation(this.#configPath, async canonicalPath => {
|
|
1833
|
+
try {
|
|
1834
|
+
await commit;
|
|
1835
|
+
return;
|
|
1836
|
+
} catch {
|
|
1837
|
+
// The original commit error remains authoritative. Recovery failures
|
|
1838
|
+
// are diagnostic only and must not replace it.
|
|
1839
|
+
}
|
|
1840
|
+
try {
|
|
1841
|
+
await this.#refreshDurableSettingsUnderQueue(canonicalPath);
|
|
1842
|
+
} catch (refreshError) {
|
|
1843
|
+
logger.warn("Settings: refresh after atomic batch failure failed", { error: String(refreshError) });
|
|
1844
|
+
}
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1847
|
+
async #refreshDurableSettingsUnderQueue(canonicalPath: string): Promise<void> {
|
|
1714
1848
|
const previousFingerprint = this.#durableNotificationFingerprint;
|
|
1715
|
-
const current = await this.#loadYaml(
|
|
1849
|
+
const current = await this.#loadYaml(canonicalPath);
|
|
1716
1850
|
if (previousFingerprint !== this.#durableNotificationFingerprint) this.#notificationValidationGeneration++;
|
|
1717
1851
|
this.#replaceGlobalWithDurable(current);
|
|
1718
1852
|
}
|
|
1853
|
+
async #refreshDurableSettings(): Promise<void> {
|
|
1854
|
+
if (!this.#persist || !this.#configPath) return;
|
|
1855
|
+
await enqueueAtomicYamlOperation(this.#configPath, canonicalPath =>
|
|
1856
|
+
this.#refreshDurableSettingsUnderQueue(canonicalPath),
|
|
1857
|
+
);
|
|
1858
|
+
}
|
|
1859
|
+
#assertDurableConfigWritable(): void {
|
|
1860
|
+
if (this.canWriteDurableConfig()) return;
|
|
1861
|
+
throw new Error(
|
|
1862
|
+
"Cannot change settings while config.yml has invalid YAML syntax. Repair config.yml and reload settings.",
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1719
1865
|
|
|
1720
1866
|
// ─────────────────────────────────────────────────────────────────────────
|
|
1721
1867
|
// Utilities
|