@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
|
@@ -16,6 +16,15 @@ import type {
|
|
|
16
16
|
MCPTransport,
|
|
17
17
|
} from "../../runtime-mcp/types";
|
|
18
18
|
import { MCPExpectedFailure, toJsonRpcError } from "../../runtime-mcp/types";
|
|
19
|
+
import {
|
|
20
|
+
cancelMCPStream,
|
|
21
|
+
MCP_MAX_CONTENT_BYTES,
|
|
22
|
+
MCP_MAX_ERROR_BYTES,
|
|
23
|
+
MCP_MAX_SSE_BATCH_MESSAGES,
|
|
24
|
+
MCP_MAX_SSE_REQUEST_MESSAGES,
|
|
25
|
+
readMCPResponseText,
|
|
26
|
+
} from "../content-limits";
|
|
27
|
+
import { fetchPluginMcpRequest, isPluginMcpPublicNetworkBound } from "../plugin-network-boundary";
|
|
19
28
|
|
|
20
29
|
/**
|
|
21
30
|
* HTTP transport for MCP servers.
|
|
@@ -54,6 +63,12 @@ export class HttpTransport implements MCPTransport {
|
|
|
54
63
|
this.#connected = true;
|
|
55
64
|
}
|
|
56
65
|
|
|
66
|
+
#fetch(init: BunFetchRequestInit): Promise<Response> {
|
|
67
|
+
return isPluginMcpPublicNetworkBound(this.config)
|
|
68
|
+
? fetchPluginMcpRequest(this.config.url, init)
|
|
69
|
+
: fetch(this.config.url, init);
|
|
70
|
+
}
|
|
71
|
+
|
|
57
72
|
#trackReader(promise: Promise<void>, controller?: AbortController): void {
|
|
58
73
|
if (controller) this.#streamControllers.add(controller);
|
|
59
74
|
this.#streamReaders.add(promise);
|
|
@@ -73,6 +88,9 @@ export class HttpTransport implements MCPTransport {
|
|
|
73
88
|
if (this.#sseConnection) return;
|
|
74
89
|
|
|
75
90
|
const sseConnection = new AbortController();
|
|
91
|
+
const headerController = new AbortController();
|
|
92
|
+
const headerTimeout = this.config.timeout ?? 30000;
|
|
93
|
+
const headerTimeoutId = setTimeout(() => headerController.abort(), headerTimeout);
|
|
76
94
|
this.#sseConnection = sseConnection;
|
|
77
95
|
const headers: Record<string, string> = {
|
|
78
96
|
Accept: "text/event-stream",
|
|
@@ -85,21 +103,25 @@ export class HttpTransport implements MCPTransport {
|
|
|
85
103
|
|
|
86
104
|
let response: Response;
|
|
87
105
|
try {
|
|
88
|
-
response = await fetch(
|
|
106
|
+
response = await this.#fetch({
|
|
89
107
|
method: "GET",
|
|
90
108
|
headers,
|
|
91
|
-
signal: sseConnection.signal,
|
|
109
|
+
signal: AbortSignal.any([sseConnection.signal, headerController.signal]),
|
|
92
110
|
});
|
|
93
111
|
} catch (error) {
|
|
94
112
|
this.#sseConnection = this.#sseConnection === sseConnection ? null : this.#sseConnection;
|
|
95
|
-
if (
|
|
113
|
+
if (headerController.signal.aborted && !sseConnection.signal.aborted) {
|
|
114
|
+
this.onError?.(new Error(`SSE connection timeout after ${headerTimeout}ms`));
|
|
115
|
+
} else if (error instanceof Error && error.name !== "AbortError") {
|
|
96
116
|
this.onError?.(error);
|
|
97
117
|
}
|
|
98
118
|
return;
|
|
119
|
+
} finally {
|
|
120
|
+
clearTimeout(headerTimeoutId);
|
|
99
121
|
}
|
|
100
122
|
|
|
101
123
|
if (response.status === 405 || !response.ok || !response.body) {
|
|
102
|
-
|
|
124
|
+
cancelMCPStream(response.body);
|
|
103
125
|
this.#sseConnection = this.#sseConnection === sseConnection ? null : this.#sseConnection;
|
|
104
126
|
return;
|
|
105
127
|
}
|
|
@@ -117,8 +139,13 @@ export class HttpTransport implements MCPTransport {
|
|
|
117
139
|
}
|
|
118
140
|
async #readSSEStream(body: ReadableStream<Uint8Array>, signal: AbortSignal): Promise<void> {
|
|
119
141
|
try {
|
|
120
|
-
for await (const message of readSseJson<JsonRpcMessage>(body, signal
|
|
142
|
+
for await (const message of readSseJson<JsonRpcMessage>(body, signal, undefined, {
|
|
143
|
+
maxEventBytes: MCP_MAX_CONTENT_BYTES,
|
|
144
|
+
})) {
|
|
121
145
|
if (!this.#connected) break;
|
|
146
|
+
if (Array.isArray(message) && message.length > MCP_MAX_SSE_BATCH_MESSAGES) {
|
|
147
|
+
throw new Error("MCP SSE batch exceeds message limit");
|
|
148
|
+
}
|
|
122
149
|
this.#dispatchSSEMessage(message);
|
|
123
150
|
}
|
|
124
151
|
} catch (error) {
|
|
@@ -207,7 +234,7 @@ export class HttpTransport implements MCPTransport {
|
|
|
207
234
|
: abortController.signal;
|
|
208
235
|
|
|
209
236
|
try {
|
|
210
|
-
const response = await fetch(
|
|
237
|
+
const response = await this.#fetch({
|
|
211
238
|
method: "POST",
|
|
212
239
|
headers,
|
|
213
240
|
body: JSON.stringify(body),
|
|
@@ -221,7 +248,7 @@ export class HttpTransport implements MCPTransport {
|
|
|
221
248
|
}
|
|
222
249
|
|
|
223
250
|
if (!response.ok) {
|
|
224
|
-
const text = await response
|
|
251
|
+
const text = await readMCPResponseText(response, MCP_MAX_ERROR_BYTES, true, operationSignal);
|
|
225
252
|
const wwwAuthenticate = response.headers.get("WWW-Authenticate");
|
|
226
253
|
const mcpAuthServer = response.headers.get("Mcp-Auth-Server");
|
|
227
254
|
const authHints = [
|
|
@@ -245,7 +272,9 @@ export class HttpTransport implements MCPTransport {
|
|
|
245
272
|
if (!response.body) {
|
|
246
273
|
throw new MCPExpectedFailure();
|
|
247
274
|
}
|
|
248
|
-
const parsedResult =
|
|
275
|
+
const parsedResult = JSON.parse(
|
|
276
|
+
await readMCPResponseText(response, MCP_MAX_CONTENT_BYTES, false, operationSignal),
|
|
277
|
+
) as unknown;
|
|
249
278
|
|
|
250
279
|
if (
|
|
251
280
|
typeof parsedResult !== "object" ||
|
|
@@ -293,6 +322,7 @@ export class HttpTransport implements MCPTransport {
|
|
|
293
322
|
|
|
294
323
|
const { promise, resolve, reject } = Promise.withResolvers<T>();
|
|
295
324
|
let captured = false;
|
|
325
|
+
let messageCount = 0;
|
|
296
326
|
|
|
297
327
|
// Drain this per-request SSE response from a single iterator. Once the
|
|
298
328
|
// matching response arrives, resolve/reject and abort the reader so the
|
|
@@ -304,8 +334,20 @@ export class HttpTransport implements MCPTransport {
|
|
|
304
334
|
this.#streamControllers.add(drainController);
|
|
305
335
|
const drain = async (): Promise<void> => {
|
|
306
336
|
try {
|
|
307
|
-
for await (const raw of readSseJson<JsonRpcMessage | JsonRpcMessage[]>(
|
|
337
|
+
for await (const raw of readSseJson<JsonRpcMessage | JsonRpcMessage[]>(
|
|
338
|
+
response.body!,
|
|
339
|
+
operationSignal,
|
|
340
|
+
undefined,
|
|
341
|
+
{
|
|
342
|
+
maxEventBytes: MCP_MAX_CONTENT_BYTES,
|
|
343
|
+
maxTotalBytes: MCP_MAX_CONTENT_BYTES,
|
|
344
|
+
},
|
|
345
|
+
)) {
|
|
308
346
|
const messages = Array.isArray(raw) ? raw : [raw];
|
|
347
|
+
if (messages.length > MCP_MAX_SSE_BATCH_MESSAGES) throw new Error("MCP SSE batch exceeds message limit");
|
|
348
|
+
messageCount += messages.length;
|
|
349
|
+
if (messageCount > MCP_MAX_SSE_REQUEST_MESSAGES)
|
|
350
|
+
throw new Error("MCP SSE response exceeds message limit");
|
|
309
351
|
for (const message of messages) {
|
|
310
352
|
if (
|
|
311
353
|
!captured &&
|
|
@@ -352,7 +394,7 @@ export class HttpTransport implements MCPTransport {
|
|
|
352
394
|
} finally {
|
|
353
395
|
clearTimeout(timeoutId);
|
|
354
396
|
this.#streamControllers.delete(drainController);
|
|
355
|
-
|
|
397
|
+
cancelMCPStream(response.body);
|
|
356
398
|
}
|
|
357
399
|
};
|
|
358
400
|
|
|
@@ -388,7 +430,7 @@ export class HttpTransport implements MCPTransport {
|
|
|
388
430
|
headers["Mcp-Session-Id"] = this.#sessionId;
|
|
389
431
|
}
|
|
390
432
|
try {
|
|
391
|
-
const resp = await fetch(
|
|
433
|
+
const resp = await this.#fetch({
|
|
392
434
|
method: "POST",
|
|
393
435
|
headers,
|
|
394
436
|
body: JSON.stringify(body),
|
|
@@ -396,23 +438,23 @@ export class HttpTransport implements MCPTransport {
|
|
|
396
438
|
});
|
|
397
439
|
// Retry once on auth failure if onAuthError is wired
|
|
398
440
|
if (this.onAuthError && (resp.status === 401 || resp.status === 403)) {
|
|
399
|
-
|
|
441
|
+
cancelMCPStream(resp.body);
|
|
400
442
|
const newHeaders = await this.onAuthError();
|
|
401
443
|
if (newHeaders) {
|
|
402
444
|
this.config.headers ??= {};
|
|
403
445
|
Object.assign(this.config.headers, newHeaders);
|
|
404
446
|
Object.assign(headers, newHeaders);
|
|
405
|
-
const retry = await fetch(
|
|
447
|
+
const retry = await this.#fetch({
|
|
406
448
|
method: "POST",
|
|
407
449
|
headers,
|
|
408
450
|
body: JSON.stringify(body),
|
|
409
451
|
signal: AbortSignal.timeout(this.config.timeout ?? 30000),
|
|
410
452
|
});
|
|
411
|
-
|
|
453
|
+
cancelMCPStream(retry.body);
|
|
412
454
|
return;
|
|
413
455
|
}
|
|
414
456
|
}
|
|
415
|
-
|
|
457
|
+
cancelMCPStream(resp.body);
|
|
416
458
|
} catch {
|
|
417
459
|
// Best-effort response delivery — server may have disconnected
|
|
418
460
|
}
|
|
@@ -445,18 +487,16 @@ export class HttpTransport implements MCPTransport {
|
|
|
445
487
|
const timeoutId = setTimeout(() => abortController.abort(), timeout);
|
|
446
488
|
|
|
447
489
|
try {
|
|
448
|
-
const response = await fetch(
|
|
490
|
+
const response = await this.#fetch({
|
|
449
491
|
method: "POST",
|
|
450
492
|
headers,
|
|
451
493
|
body: JSON.stringify(body),
|
|
452
494
|
signal: abortController.signal,
|
|
453
495
|
});
|
|
454
496
|
|
|
455
|
-
clearTimeout(timeoutId);
|
|
456
|
-
|
|
457
497
|
// 202 Accepted is success for notifications
|
|
458
498
|
if (!response.ok && response.status !== 202) {
|
|
459
|
-
const text = await response.
|
|
499
|
+
const text = await readMCPResponseText(response, MCP_MAX_ERROR_BYTES, true, abortController.signal);
|
|
460
500
|
throw new Error(`HTTP ${response.status}: ${text}`);
|
|
461
501
|
}
|
|
462
502
|
|
|
@@ -472,14 +512,15 @@ export class HttpTransport implements MCPTransport {
|
|
|
472
512
|
const reader = this.#readSSEStream(response.body, AbortSignal.any(signals));
|
|
473
513
|
this.#trackReader(reader, streamController);
|
|
474
514
|
} else {
|
|
475
|
-
|
|
515
|
+
cancelMCPStream(response.body);
|
|
476
516
|
}
|
|
477
517
|
} catch (error) {
|
|
478
|
-
clearTimeout(timeoutId);
|
|
479
518
|
if (error instanceof Error && error.name === "AbortError") {
|
|
480
519
|
throw new MCPExpectedFailure(new Error(`Notify timeout after ${timeout}ms`));
|
|
481
520
|
}
|
|
482
521
|
throw error instanceof MCPExpectedFailure ? error : new MCPExpectedFailure(error);
|
|
522
|
+
} finally {
|
|
523
|
+
clearTimeout(timeoutId);
|
|
483
524
|
}
|
|
484
525
|
}
|
|
485
526
|
|
|
@@ -508,7 +549,7 @@ export class HttpTransport implements MCPTransport {
|
|
|
508
549
|
"Mcp-Session-Id": this.#sessionId,
|
|
509
550
|
};
|
|
510
551
|
|
|
511
|
-
await fetch(
|
|
552
|
+
await this.#fetch({
|
|
512
553
|
method: "DELETE",
|
|
513
554
|
headers,
|
|
514
555
|
signal: AbortSignal.timeout(timeout),
|
package/src/sdk/acp/adapter.ts
CHANGED
|
@@ -179,7 +179,14 @@ export class AcpSdkAdapter {
|
|
|
179
179
|
this.#assertGenericDisposition("global", operation);
|
|
180
180
|
if (isLifecycleOperation(operation) && !idempotencyKey)
|
|
181
181
|
throw new AcpSdkAdapterError("invalid_input", "idempotencyKey is required for lifecycle operations.");
|
|
182
|
-
|
|
182
|
+
const readinessTimeoutMs =
|
|
183
|
+
typeof input.readinessTimeoutMs === "number" && Number.isSafeInteger(input.readinessTimeoutMs)
|
|
184
|
+
? input.readinessTimeoutMs
|
|
185
|
+
: undefined;
|
|
186
|
+
return await this.#client.global(operation, input, {
|
|
187
|
+
idempotencyKey,
|
|
188
|
+
...(readinessTimeoutMs ? { timeoutMs: readinessTimeoutMs + 1_000 } : {}),
|
|
189
|
+
});
|
|
183
190
|
}
|
|
184
191
|
|
|
185
192
|
async sdkControl(params: { operation: string; input?: JsonObject }): Promise<unknown> {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export const ACP_FINAL_TEXT_LIMIT = 100_000;
|
|
2
|
+
|
|
3
|
+
export interface BoundedAcpFinalText {
|
|
4
|
+
text: string;
|
|
5
|
+
truncated: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export type AcpFinalTextResolution =
|
|
9
|
+
| { kind: "none"; final: BoundedAcpFinalText }
|
|
10
|
+
| { kind: "emit"; final: BoundedAcpFinalText; text: string }
|
|
11
|
+
| { kind: "divergent"; final: BoundedAcpFinalText };
|
|
12
|
+
|
|
13
|
+
export function boundAcpFinalText(value: string): BoundedAcpFinalText {
|
|
14
|
+
if (value.length <= ACP_FINAL_TEXT_LIMIT) return { text: value, truncated: false };
|
|
15
|
+
let text = value.slice(0, ACP_FINAL_TEXT_LIMIT);
|
|
16
|
+
const finalCodeUnit = text.charCodeAt(text.length - 1);
|
|
17
|
+
if (finalCodeUnit >= 0xd800 && finalCodeUnit <= 0xdbff) text = text.slice(0, -1);
|
|
18
|
+
return { text, truncated: true };
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function acpFinalTextFromMessage(message: unknown): BoundedAcpFinalText {
|
|
22
|
+
const content = (message as { content?: unknown } | null | undefined)?.content;
|
|
23
|
+
if (typeof content === "string") return boundAcpFinalText(content);
|
|
24
|
+
if (!Array.isArray(content)) return { text: "", truncated: false };
|
|
25
|
+
const parts: string[] = [];
|
|
26
|
+
for (const block of content) {
|
|
27
|
+
if (!block || typeof block !== "object" || (block as { type?: unknown }).type !== "text") continue;
|
|
28
|
+
const text = (block as { text?: unknown }).text;
|
|
29
|
+
if (typeof text === "string") parts.push(text);
|
|
30
|
+
}
|
|
31
|
+
return boundAcpFinalText(parts.join(""));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function resolveAcpFinalText(streamed: string, finalText: string): AcpFinalTextResolution {
|
|
35
|
+
const final = boundAcpFinalText(finalText);
|
|
36
|
+
if (!final.text || streamed === final.text || streamed.includes(final.text)) return { kind: "none", final };
|
|
37
|
+
if (!streamed) return { kind: "emit", final, text: final.text };
|
|
38
|
+
if (final.text.startsWith(streamed)) return { kind: "emit", final, text: final.text.slice(streamed.length) };
|
|
39
|
+
return { kind: "divergent", final };
|
|
40
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const ACP_MCP_REQUEST_TIMEOUT_MS = 30_000;
|
|
2
|
+
export const ACP_MCP_LIFECYCLE_TIMEOUT_MS = ACP_MCP_REQUEST_TIMEOUT_MS + 500;
|
|
3
|
+
/**
|
|
4
|
+
* Slice of the readiness budget reserved for the work that follows MCP startup
|
|
5
|
+
* (session wiring, marker publication). The ACP MCP startup ceiling is the
|
|
6
|
+
* remaining time to `semanticReadyDeadlineAt` minus this headroom, so a slow
|
|
7
|
+
* MCP handshake cannot consume the whole readiness window.
|
|
8
|
+
*/
|
|
9
|
+
export const ACP_MCP_STARTUP_HEADROOM_MS = 250;
|
|
10
|
+
|
|
11
|
+
export interface SessionLifecycleMcpStdioServer {
|
|
12
|
+
type?: "stdio";
|
|
13
|
+
name: string;
|
|
14
|
+
command: string;
|
|
15
|
+
args: string[];
|
|
16
|
+
env?: Record<string, string>;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface SessionLifecycleMcpRemoteServer {
|
|
20
|
+
type: "http" | "sse";
|
|
21
|
+
name: string;
|
|
22
|
+
url: string;
|
|
23
|
+
headers?: Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export type SessionLifecycleMcpServer = SessionLifecycleMcpStdioServer | SessionLifecycleMcpRemoteServer;
|
package/src/sdk/broker/broker.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { createHash, randomBytes } from "node:crypto";
|
|
2
|
+
import type { BigIntStats } from "node:fs";
|
|
2
3
|
import * as fs from "node:fs/promises";
|
|
3
4
|
import path from "node:path";
|
|
4
5
|
import type { NativeDirectoryTreeSnapshot } from "@sayknow-cli/natives";
|
|
6
|
+
import type { ModelProfileErrorDetails } from "../../config/model-profile-contract";
|
|
5
7
|
import {
|
|
6
8
|
type DirectoryMigrationPolicy,
|
|
7
9
|
listManagedSessionCandidates,
|
|
@@ -141,6 +143,7 @@ export type BrokerResponse =
|
|
|
141
143
|
error: {
|
|
142
144
|
code: BrokerErrorCode;
|
|
143
145
|
message: string;
|
|
146
|
+
details?: ModelProfileErrorDetails;
|
|
144
147
|
endpoint?: "unavailable";
|
|
145
148
|
cleanup?: BrokerCleanupEvidence;
|
|
146
149
|
};
|
|
@@ -362,6 +365,7 @@ type BrokerLockSnapshot = {
|
|
|
362
365
|
ownerId?: string;
|
|
363
366
|
pid: number;
|
|
364
367
|
identity: string;
|
|
368
|
+
lockIdentity: string;
|
|
365
369
|
};
|
|
366
370
|
|
|
367
371
|
const BROKER_PUBLICATION_CADENCE_MS = 5_000;
|
|
@@ -415,7 +419,7 @@ export class Broker {
|
|
|
415
419
|
#lockRecordPath(): string {
|
|
416
420
|
return path.join(this.#lock, BROKER_LOCK_RECORD);
|
|
417
421
|
}
|
|
418
|
-
#lockSnapshot(raw: string): BrokerLockSnapshot {
|
|
422
|
+
async #lockSnapshot(raw: string, lockIdentity: string): Promise<BrokerLockSnapshot> {
|
|
419
423
|
try {
|
|
420
424
|
const lock = JSON.parse(raw) as { ownerId?: unknown; pid?: unknown };
|
|
421
425
|
if (
|
|
@@ -425,32 +429,37 @@ export class Broker {
|
|
|
425
429
|
Number.isInteger(lock.pid) &&
|
|
426
430
|
lock.pid > 0
|
|
427
431
|
)
|
|
428
|
-
return { ownerId: lock.ownerId, pid: lock.pid, identity: `owner:${lock.ownerId}
|
|
432
|
+
return { ownerId: lock.ownerId, pid: lock.pid, identity: `owner:${lock.ownerId}`, lockIdentity };
|
|
429
433
|
} catch {}
|
|
430
|
-
return { pid: 0, identity: `contents:${createHash("sha256").update(raw).digest("hex")}
|
|
434
|
+
return { pid: 0, identity: `contents:${createHash("sha256").update(raw).digest("hex")}`, lockIdentity };
|
|
431
435
|
}
|
|
432
436
|
async #readLock(): Promise<BrokerLockSnapshot | null> {
|
|
437
|
+
let lock: BigIntStats;
|
|
433
438
|
try {
|
|
434
|
-
|
|
439
|
+
lock = await fs.lstat(this.#lock, { bigint: true });
|
|
435
440
|
} catch (e) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
try {
|
|
439
|
-
return this.#lockSnapshot(await fs.readFile(this.#lock, "utf8"));
|
|
440
|
-
} catch (legacyError) {
|
|
441
|
-
if ((legacyError as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
442
|
-
throw legacyError;
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
if (code !== "ENOENT") throw e;
|
|
441
|
+
if ((e as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
442
|
+
throw e;
|
|
446
443
|
}
|
|
444
|
+
const lockIdentity = `${lock.dev}:${lock.ino}`;
|
|
445
|
+
let raw: string;
|
|
447
446
|
try {
|
|
448
|
-
|
|
449
|
-
|
|
447
|
+
raw = lock.isDirectory()
|
|
448
|
+
? await fs.readFile(path.join(this.#lock, BROKER_LOCK_RECORD), "utf8")
|
|
449
|
+
: await fs.readFile(this.#lock, "utf8");
|
|
450
|
+
} catch (e) {
|
|
451
|
+
if ((e as NodeJS.ErrnoException).code !== "ENOENT") throw e;
|
|
452
|
+
raw = "";
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
const current = await fs.lstat(this.#lock, { bigint: true });
|
|
456
|
+
if (`${current.dev}:${current.ino}` !== lockIdentity || current.isDirectory() !== lock.isDirectory())
|
|
457
|
+
return null;
|
|
450
458
|
} catch (e) {
|
|
451
459
|
if ((e as NodeJS.ErrnoException).code === "ENOENT") return null;
|
|
452
460
|
throw e;
|
|
453
461
|
}
|
|
462
|
+
return this.#lockSnapshot(raw, lockIdentity);
|
|
454
463
|
}
|
|
455
464
|
async #createLock(): Promise<void> {
|
|
456
465
|
await fs.mkdir(this.#lock, { mode: 0o700 });
|
|
@@ -478,13 +487,18 @@ export class Broker {
|
|
|
478
487
|
}
|
|
479
488
|
async #reclaimStaleLock(snapshot: BrokerLockSnapshot): Promise<void> {
|
|
480
489
|
const current = await this.#readLock();
|
|
481
|
-
if (
|
|
490
|
+
if (
|
|
491
|
+
!current ||
|
|
492
|
+
current.identity !== snapshot.identity ||
|
|
493
|
+
current.lockIdentity !== snapshot.lockIdentity ||
|
|
494
|
+
(current.pid > 0 && isPidAlive(current.pid))
|
|
495
|
+
)
|
|
496
|
+
return;
|
|
482
497
|
|
|
483
|
-
//
|
|
484
|
-
// holding an old snapshot from renaming a newly-created directory lock.
|
|
498
|
+
// Snapshot validation and the deterministic instance suffix protect successor locks.
|
|
485
499
|
const tombstone = path.join(
|
|
486
500
|
path.dirname(this.#lock),
|
|
487
|
-
`.broker.lock.stale-${createHash("sha256").update(snapshot.
|
|
501
|
+
`.broker.lock.stale-${createHash("sha256").update(snapshot.lockIdentity).digest("hex")}`,
|
|
488
502
|
);
|
|
489
503
|
try {
|
|
490
504
|
await fs.rename(this.#lock, tombstone);
|
|
@@ -11,20 +11,30 @@ export function brokerIdentityPath(agentDir: string): string {
|
|
|
11
11
|
export async function getBrokerIdentityKey(agentDir: string): Promise<string> {
|
|
12
12
|
const file = brokerIdentityPath(agentDir);
|
|
13
13
|
await fs.mkdir(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
14
|
+
while (true) {
|
|
15
|
+
try {
|
|
16
|
+
const key = (await fs.readFile(file, "utf8")).trim();
|
|
17
|
+
if (/^[0-9a-f]{64}$/i.test(key)) return key;
|
|
18
|
+
throw new Error(`Invalid broker identity key at ${file}`);
|
|
19
|
+
} catch (error) {
|
|
20
|
+
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
21
|
+
}
|
|
22
|
+
|
|
20
23
|
const key = randomBytes(32).toString("hex");
|
|
24
|
+
const temporary = path.join(path.dirname(file), `.broker.identity-${randomBytes(16).toString("hex")}`);
|
|
21
25
|
try {
|
|
22
|
-
await fs.writeFile(
|
|
23
|
-
await fs.chmod(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
26
|
+
await fs.writeFile(temporary, `${key}\n`, { encoding: "utf8", mode: MODE, flag: "wx" });
|
|
27
|
+
await fs.chmod(temporary, MODE);
|
|
28
|
+
try {
|
|
29
|
+
// Link publishes a fully written key without exposing a partial target.
|
|
30
|
+
// Same-directory hard-link support is required (standard on Linux, macOS, and Windows NTFS).
|
|
31
|
+
await fs.link(temporary, file);
|
|
32
|
+
return key;
|
|
33
|
+
} catch (linkError) {
|
|
34
|
+
if ((linkError as NodeJS.ErrnoException).code !== "EEXIST") throw linkError;
|
|
35
|
+
}
|
|
36
|
+
} finally {
|
|
37
|
+
await fs.rm(temporary, { force: true });
|
|
28
38
|
}
|
|
29
39
|
}
|
|
30
40
|
}
|