@sayknow-cli/coding-agent 0.4.6 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +112 -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 +27 -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
|
@@ -41,7 +41,10 @@ When ralplan detects its own current-session state is corrupt, tampered, unreada
|
|
|
41
41
|
|
|
42
42
|
Ralplan is a planning module. It may inspect context and draft or update plan/spec/proposal artifacts, but it MUST mark those artifacts as `pending approval` unless the user has explicitly opted into execution in the current turn or via the structured approval UI. Before explicit execution approval, it MUST NOT run mutation-oriented shell commands, edit source files, commit, push, open PRs, invoke execution skills, or delegate implementation tasks.
|
|
43
43
|
|
|
44
|
-
|
|
44
|
+
Explicitly naming `ultragoal` or `team` (including `/skill:` and `skc` forms) counts as opting into execution for that skill — do not re-ask for the same consent.
|
|
45
|
+
|
|
46
|
+
Persist planning artifacts and handoffs through the ralplan CLI writer, never direct `.skc/` edits:
|
|
47
|
+
Direct `write`, `edit`, or `ast_edit` calls against `.skc/_session-{sessionid}/specs`, `.skc/_session-{sessionid}/plans`, `.skc/_session-{sessionid}/state`, or any other `.skc/` path are forbidden unless an explicit force override is active.
|
|
45
48
|
|
|
46
49
|
```bash
|
|
47
50
|
skc ralplan --write --stage <type> --stage_n <N> --artifact "markdown file path or markdown string"
|
|
@@ -62,7 +65,7 @@ RECEIPT-ONLY guideline: role agents (`planner`, `architect`, and `critic`) persi
|
|
|
62
65
|
This skill runs SKC planning in consensus mode for the provided arguments.
|
|
63
66
|
|
|
64
67
|
The consensus workflow:
|
|
65
|
-
1. **Planner** creates the initial plan and a compact **RALPLAN-DR summary** before review. Launch the Planner ONCE per run as a detached, resumable subagent (await it before the Architect) and record its returned subagent id as the run's persisted Planner id; persist the stage with `skc ralplan --write --stage planner --stage_n 1 --artifact-env SKC_RALPLAN_ARTIFACT --planner-id <id> --planner-resumable <true|false>` (see **Persisted
|
|
68
|
+
1. **Planner** creates the initial plan and a compact **RALPLAN-DR summary** before review. Launch the Planner ONCE per run as a detached, resumable subagent (await it before the Architect) and record its returned subagent id as the run's persisted Planner id; persist the stage with `skc ralplan --write --stage planner --stage_n 1 --artifact-env SKC_RALPLAN_ARTIFACT --planner-id <id> --planner-resumable <true|false>` (see **Persisted role agents** below):
|
|
66
69
|
- After persistence, return only the receipt/path plus compact planning status; do not paste the full plan markdown back to the caller unless explicitly requested.
|
|
67
70
|
- Principles (3-5)
|
|
68
71
|
- Decision Drivers (top 3)
|
|
@@ -70,19 +73,32 @@ The consensus workflow:
|
|
|
70
73
|
- If only one viable option remains, explicit invalidation rationale for alternatives
|
|
71
74
|
- Deliberate mode only: pre-mortem (3 scenarios) + expanded test plan (unit/integration/e2e/observability)
|
|
72
75
|
2. **User feedback** *(--interactive only)*: If `--interactive` is set, use the `ask` tool to present the draft plan **plus the Principles / Drivers / Options summary** before review (Proceed to review / Request changes / Skip review). Otherwise, automatically proceed to review.
|
|
73
|
-
3. **
|
|
74
|
-
-
|
|
75
|
-
|
|
76
|
-
-
|
|
77
|
-
|
|
76
|
+
3. **Review fan-out after Planner persistence**: launch the Architect and Critic ONCE per run as detached, resumable review lanes against the same immutable Planner receipt/path/sha/stage_n. Their pass-1 fan-out remains parallel when Critic is **plan-only** and does not consume Architect output (see **Persisted role agents** below).
|
|
77
|
+
- **Architect lane**: challenge architecture, surface tradeoff tensions, and enrich thin plans with synthesis or missed sub-scope. Persist with `skc ralplan --write --stage architect --stage_n <N> --artifact-env SKC_RALPLAN_ARTIFACT --architect-id <id> --architect-resumable <true|false> --lane-verdict <token> --json`, then return receipt/path plus `CLEAR`/`WATCH`/`BLOCK` and `APPROVE`/`COMMENT`/`REQUEST CHANGES`.
|
|
78
|
+
- **Plan-only Critic lane**: independently check quality, principle-option consistency, alternatives, risks, acceptance criteria, and verification; when the plan is thin, request concrete expansion rather than only defects. Persist with `skc ralplan --write --stage critic --stage_n <N> --artifact-env SKC_RALPLAN_ARTIFACT --critic-id <id> --critic-resumable <true|false> --lane-verdict <token> --json`, then return receipt/path plus `OKAY`/`ITERATE`/`REJECT`.
|
|
79
|
+
- **Sequential fallback**: if Critic must evaluate Architect findings, verdict, antithesis, tradeoffs, synthesis, status, or any Architect-produced artifact, await the Architect result before issuing that Architect-dependent Critic pass.
|
|
80
|
+
- Every Architect/Critic assignment, including each pass-2+ re-review assignment in step 5, MUST instruct the reviewer to include `--lane-verdict <token>` on its existing `skc ralplan --write`: Architect passes its Architectural Status token (`CLEAR`/`WATCH`/`BLOCK`), and Critic passes its verdict token (`OKAY`/`ITERATE`/`REJECT`). The flag is optional so legacy invocations stay valid.
|
|
81
|
+
4. **Review join gate**: before consensus, revision, reconciliation, finalization, or approval, verify both Architect and Critic receipts/verdicts exist for the same Planner artifact/pass (`path`, `sha256`, `stage_n`). A non-`CLEAR` Architect verdict, non-`APPROVE` Architect decision, or any non-`OKAY` Critic verdict routes back to Planner revision; do not finalize from only one review lane.
|
|
82
|
+
5. **Re-review loop** (max 5 iterations; **runtime-enforced**): Any non-`OKAY` Critic verdict (`ITERATE` or `REJECT`) or Architect result that is not `CLEAR`/`APPROVE` MUST run the same full closed loop. Pass 2+ resumes the SAME persisted Architect and Critic lane subagents with the mandatory re-review context bundle and runs sequentially Architect -> Critic: await the Architect result and its receipt/path before assigning Critic; Critic receives the current-pass Architect receipt/path and performs the rule-5 counter-review before consolidated feedback routes to Planner revision. From pass 2, both reviewers are bound by the five-rule ratchet: delta-only review, novelty justification, verdict monotonicity, severity scoping, and Critic counter-review of Architect scope inflation; unjustified inflation does not force a revision.
|
|
78
83
|
a. Collect Architect + Critic feedback
|
|
79
|
-
b. Revise the plan by resuming the SAME persisted Planner subagent with consolidated Architect + Critic feedback (see **Persisted
|
|
80
|
-
|
|
84
|
+
b. Revise the plan by resuming the SAME persisted Planner subagent with consolidated Architect + Critic feedback (see **Persisted role agents** below); fall back to a fresh Planner spawn only per the fallback routing table
|
|
85
|
+
|
|
86
|
+
**Re-review context bundle (pass 2+; mandatory):** Every pass-2+ Architect or Critic assignment MUST include:
|
|
87
|
+
1. the explicit review pass number `N` for that lane, stated literally as `review pass N` in the assignment text, where **N is the ordinal review pass for that lane across the entire ralplan run/re-review loop** (equivalently the opener-iteration ordinal): the review of the initial Planner artifact is `review pass 1`, the review of the first revised Planner artifact is `review pass 2`, and so on; **N never resets within an opener iteration and never resets when a new `revision` opener begins in the same run** — it increments monotonically with every review the lane performs in the run. This ordinal is a workflow counter distinct from the runtime lane budget (which counts lane writes per opener iteration, WI-5): at the default budget the two coincide numerically, but the ratchet ("from pass 2") always keys off the run-level N so normal post-revision re-reviews activate delta-only review, monotonicity, and the sequential cadence;
|
|
88
|
+
2. the current revision receipt under review (`path`, `sha256`, `stage_n`);
|
|
89
|
+
3. the prior Planner/revision artifact path that the previous pass reviewed;
|
|
90
|
+
4. the prior same-lane review artifact path (`stage-NN-architect.md` / `stage-NN-critic.md`) with its receipt fields;
|
|
91
|
+
5. the consolidated prior blockers and the revision's claimed resolutions, as orchestrator-collected pointers into those artifacts (never pasted bodies);
|
|
92
|
+
6. Critic pass-2+ only: the current-pass Architect receipt/path, awaited first per the sequential cadence, so the rule-5 counter-review is evaluable.
|
|
93
|
+
|
|
94
|
+
**The re-review context bundle remains mandatory regardless of whether a reviewer is resumed or uses a fresh-spawn fallback.** A fresh-spawn fallback always receives everything required to apply delta-only review (rule 1), novelty justification (rule 2), monotonicity (rule 3), severity scoping (rule 4), and counter-review (rule 5).
|
|
95
|
+
c. For pass 2+, resume (or fresh-spawn only per the routing table) Architect -> Critic sequentially: await the Architect result and receipt/path, then issue Critic with the mandatory context bundle, including the current-pass Architect receipt/path. Critic performs the rule-5 counter-review before consolidated feedback routes to Planner revision.
|
|
81
96
|
- Persist each Planner revision with `skc ralplan --write --stage revision --stage_n <N> --artifact-env SKC_RALPLAN_ARTIFACT --json` before re-review, then pass the receipt/path forward instead of duplicating the full revision markdown in the parent conversation.
|
|
82
|
-
d.
|
|
83
|
-
e. Repeat this loop until Critic returns `OKAY` or 5 iterations are reached
|
|
84
|
-
f. If 5 iterations are reached without `OKAY`,
|
|
85
|
-
|
|
97
|
+
d. Re-join Architect and Critic verdicts for the same revised Planner artifact/pass
|
|
98
|
+
e. Repeat this loop until Critic returns `OKAY` **and** Architect is `CLEAR`/`APPROVE` for the same Planner artifact/pass, or 5 iterations are reached
|
|
99
|
+
f. If 5 iterations are reached without Critic `OKAY` plus Architect `CLEAR`/`APPROVE`, **stop opening further planner/revision passes**. Present the best version to the user (interactive) or surface `PLANNING-STUCK` (headless). Do **not** auto-start implementation.
|
|
100
|
+
g. **Runtime budget (#3165):** native `skc ralplan --write` refuses a new `planner`/`revision` that would open consensus iteration **> max** (default **5**, overridable via `skc.ralplan.maxIterations` in project/user `.skc/settings.json`, integer 1..20). Cap uses the same iteration definition as the HUD (`planner`/`revision` openers in `index.jsonl`). Overflow exits **3**, prints operator-visible **`PLANNING-STUCK`** on stdout (and stderr detail; JSON includes `planning_stuck: true`), and still allows `architect`/`critic` within an already-opened pass plus `post-interview`/`adr`/`final` so the best plan can be escalated to `pending approval` without auto-execution. A new `--run-id` starts a fresh budget.
|
|
101
|
+
6. **Post-ralplan interview** (intent reconciliation gate): After the review join gate has both Critic `OKAY` and Architect `CLEAR`/`APPROVE` for the same Planner artifact/pass, and before the plan is finalized, reconcile the consensus plan against the user's actual intent. The goal is to make sure ralplan did not silently bake in assumptions that conflict with what the user wants.
|
|
86
102
|
a. **Collect open items** from the run: every assumption the Planner/Architect/Critic resolved by assumption rather than by stated fact, every ambiguity flagged during review, and every decision the loop made without explicit user input. Source these from the persisted `planner`/`architect`/`critic`/`revision` stage artifacts, not from memory.
|
|
87
103
|
b. **Cross-check prior context for conflicts**: glob `.skc/_session-{sessionid}/specs/deep-interview-*.md` and other prior specs/plans/context relevant by topic. For each, list points where the consensus plan contradicts, weakens, or expands beyond a previously crystallized decision, constraint, or non-goal. Cite the conflicting artifact and line/section.
|
|
88
104
|
c. **Reconcile with the user via the `ask` tool (always, regardless of `--interactive`)**: Never stop idle with plain-text prose after the consensus loop. Every reconciliation question MUST go through the `ask` tool with contextual options plus free-text.
|
|
@@ -90,8 +106,8 @@ The consensus workflow:
|
|
|
90
106
|
- If the plan is crystal clear (no open assumptions or prior-context conflicts), skip straight to the step 8 final-options `ask` instead of inventing filler questions.
|
|
91
107
|
- For every confirmed open item, embed the resolved outcome into the final plan under an **## Intent Reconciliation** section so the `pending approval` artifact records each decision; record any item the user explicitly defers as an open confirmation under that same section.
|
|
92
108
|
d. Persist the reconciliation with `skc ralplan --write --stage post-interview --stage_n <N> --artifact-env SKC_RALPLAN_ARTIFACT --json`, then return the receipt/path plus a compact status (reconciled-clean / reconciled-with-revision / open-confirmations-pending) instead of pasting the full body.
|
|
93
|
-
7. On reconciliation completion, mark the plan `pending approval` unless explicit execution approval has already been captured, persist the ADR/final plan via `skc ralplan --write --stage final --stage_n <N> --artifact-env SKC_RALPLAN_ARTIFACT`, and do not directly edit `.skc/_session-{sessionid}/plans`. Final plan must include ADR (Decision, Drivers, Alternatives considered, Why chosen, Consequences, Follow-ups) and, when present, the **## Intent Reconciliation** section.
|
|
94
|
-
8. **
|
|
109
|
+
7. On reconciliation completion, re-check the review join gate (Critic `OKAY` plus Architect `CLEAR`/`APPROVE` for the same Planner artifact/pass), mark the plan `pending approval` unless explicit execution approval has already been captured, persist the ADR/final plan via `skc ralplan --write --stage final --stage_n <N> --artifact-env SKC_RALPLAN_ARTIFACT`, and do not directly edit `.skc/_session-{sessionid}/plans`. Final plan must include ADR (Decision, Drivers, Alternatives considered, Why chosen, Consequences, Follow-ups) and, when present, the **## Intent Reconciliation** section.
|
|
110
|
+
8. **Final approval gate (with explicit-execution exception):** If the user already explicitly named an execution skill in the current turn or via the structured approval UI (`ultragoal`, `/skill:ultragoal`, `skc ultragoal`, `team`, `/skill:team`, `skc team`, or "Approve execution via ultragoal/team"), that is execution approval — skip the re-ask and proceed to step 9 with that skill. Otherwise, **always** present the finalized plan via the `ask` tool (regardless of `--interactive`) with `workflowGate: { stage: "ralplan", kind: "approval" }` on the final question so RPC/headless clients receive a `ralplan`/`approval` workflow gate, not a deep-interview question gate. Use these options:
|
|
95
111
|
- **Refine further** — re-run the consensus loop / request changes, then return here
|
|
96
112
|
- **Approve execution via ultragoal (Recommended)** — goal-tracked autonomous execution
|
|
97
113
|
- **Approve execution via team** — only when tmux-based interactive worker parallelization is required
|
|
@@ -108,36 +124,77 @@ The consensus workflow:
|
|
|
108
124
|
|
|
109
125
|
The skill tool then dispatches the execution skill same-turn and runs `skc state ralplan handoff --to <team|ultragoal> --json` in-process to atomically demote ralplan, promote the callee, and sync `.skc/_session-{sessionid}/state/skill-active-state.json`. You do not need to run the handoff verb yourself.
|
|
110
126
|
|
|
111
|
-
> **Important:**
|
|
127
|
+
> **Important:** Architect and Critic MAY run in the same parallel batch only for the plan-only Critic lane after Planner persistence (review pass 1). Pass 2+ re-reviews MUST run sequentially Architect -> Critic: await Architect before issuing Critic, pass the current-pass Architect receipt/path to Critic for the rule-5 counter-review, then apply the same review join gate before consensus.
|
|
128
|
+
|
|
129
|
+
## Consensus iteration cap (operator contract)
|
|
130
|
+
|
|
131
|
+
- Default max consensus iterations: **5** (`skc.ralplan.maxIterations`).
|
|
132
|
+
- On cap: exit code **3**, marker **`PLANNING-STUCK`** (stdout), no silent re-loop, no automatic ultragoal/team handoff. Opener budget is `max(index.jsonl openers, on-disk stage-*-{planner,revision}.md count)` so a missing/empty/malformed ledger cannot fail open after prior openers.
|
|
133
|
+
- Headless/CI: treat `PLANNING-STUCK` / exit 3 as terminal planning failure for orchestration/watchdogs.
|
|
134
|
+
- Interactive: present best existing plan via the final approval gate; residual critic findings stay as caveats.
|
|
135
|
+
- Override example (project `.skc/settings.json`):
|
|
136
|
+
|
|
137
|
+
```json
|
|
138
|
+
{
|
|
139
|
+
"skc": {
|
|
140
|
+
"ralplan": {
|
|
141
|
+
"maxIterations": 3
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
## Per-lane review budget (operator contract)
|
|
148
|
+
|
|
149
|
+
- Default: **1** Architect pass and **1** Critic pass per opener iteration.
|
|
150
|
+
- Override via `skc.ralplan.maxReviewPassesPerLane`: project `.skc/settings.json` overrides user settings; the value is an integer **1..10** registered in the public settings schema.
|
|
151
|
+
- On overflow: exit code **3** with the **`PLANNING-STUCK`** marker and lane-specific JSON/stderr detail.
|
|
152
|
+
- `post-interview`, `adr`, and `final` are always allowed.
|
|
153
|
+
- Identical re-writes dedupe without stuck-signaling — including after a crash between artifact write and ledger append: the identical retry repairs the missing ledger row and returns the dedupe receipt.
|
|
154
|
+
- A new `--run-id` starts a fresh budget.
|
|
155
|
+
- A rule-2-justified blocker routes through a Planner `revision` opener (new iteration, fresh lane budget), never a second same-iteration review pass.
|
|
156
|
+
- Override example (project `.skc/settings.json`):
|
|
157
|
+
|
|
158
|
+
```json
|
|
159
|
+
{
|
|
160
|
+
"skc": {
|
|
161
|
+
"ralplan": {
|
|
162
|
+
"maxIterations": 3,
|
|
163
|
+
"maxReviewPassesPerLane": 2
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
112
169
|
|
|
113
|
-
Follow
|
|
170
|
+
Follow this ralplan-internal consensus workflow for consensus mode details.
|
|
114
171
|
|
|
115
|
-
### Persisted
|
|
172
|
+
### Persisted role agents (consensus loop)
|
|
116
173
|
|
|
117
|
-
The Planner
|
|
174
|
+
The Planner, Architect, and Critic are **same-session persisted subagents**. Launch the Planner detached once and await it before review fan-out; Architect and Critic are also launched once per run as detached, resumable subagents in the pass-1 fan-out (parallel only for the plan-only Critic lane tied to the same Planner receipt/path/sha/stage_n). On pass 2+, resume the SAME persisted Planner with consolidated feedback and resume the SAME persisted Architect and Critic lane subagents with the mandatory re-review context bundle instead of fresh-spawning. Do NOT modify the subagent control surface; use existing `subagent` resume/steer controls only.
|
|
118
175
|
|
|
119
|
-
**Persistence boundary:**
|
|
176
|
+
**Persistence boundary:** same-parent, active-session continuity only. Resumability requires retained subagent resume metadata and a persistent parent session (in-memory parent yields `resumable:false`), not just `.skc` run-state. A terminal subagent can still resume when its retained descriptor points at a saved subagent session; after process restart, missing metadata, or failed/unavailable resume, use the fresh role/lane fallback.
|
|
120
177
|
|
|
121
|
-
**Resume routing table** (per re-review pass, when resuming
|
|
178
|
+
**Resume routing table (for every persisted role: Planner, Architect, and Critic)** (per re-review pass, when resuming that role's persisted id):
|
|
122
179
|
|
|
123
180
|
| Resume outcome | Action |
|
|
124
181
|
|---|---|
|
|
125
|
-
| `running` | `steer`/inject
|
|
126
|
-
| `queued` | retain/update the queued message or await the same id — do NOT fresh-spawn just because it is queued |
|
|
127
|
-
| `context_unavailable`, `not_found`, `no_runner`, `resume_failed` | fresh
|
|
128
|
-
| terminal (`completed`/`failed`/`cancelled`) +
|
|
182
|
+
| `running` | `steer`/inject that role's follow-up context to the same id, then await — do NOT fresh-spawn |
|
|
183
|
+
| `queued` | retain/update the queued message or `await` the same id — do NOT fresh-spawn just because it is queued |
|
|
184
|
+
| `context_unavailable`, `not_found`, `no_runner`, `resume_failed` | fresh-spawn fallback for that role/lane on that pass; record the fallback metadata. `not_found` should only mean same-session resume metadata is unavailable, not merely that a terminal live job was evicted. |
|
|
185
|
+
| terminal (`completed`/`failed`/`cancelled`) + follow-up message | resume the same id when context is available; otherwise use the fresh-spawn fallback above |
|
|
129
186
|
|
|
130
|
-
**
|
|
187
|
+
**Ratchet synergy:** a resumed Architect or Critic natively retains prior-pass context, but the re-review context bundle remains mandatory regardless so the fresh-spawn fallback remains fully functional and applies all five rules.
|
|
131
188
|
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
189
|
+
**Recording persisted-role-agent metadata** (audit/routing only — never claim `subagent list` proves resumability, since the snapshot does not expose `resumable`). Ride the matching optional flags on the role's normal `--write` for the pass:
|
|
190
|
+
|
|
191
|
+
| Role | Normal write stage | Metadata flags |
|
|
192
|
+
|---|---|---|
|
|
193
|
+
| Planner | `planner` or `revision` | `--planner-id <id> --planner-resumable <true|false>` |
|
|
194
|
+
| Architect | `architect` | `--architect-id <id> --architect-resumable <true|false>` |
|
|
195
|
+
| Critic | `critic` | `--critic-id <id> --critic-resumable <true|false>` |
|
|
139
196
|
|
|
140
|
-
|
|
197
|
+
The existing fallback flags ride the same role's normal write: `--fallback-reason <context_unavailable|not_found|no_runner|resume_failed|process_restart|missing_record>`, `--fallback-attempted-id <id>`, `--fallback-stage-n <N>`, and optional `--fallback-receipt-path <fresh-role-stage-artifact-path>`. A planner/revision write records Planner fallback metadata, an Architect write records Architect fallback metadata, and a Critic write records Critic fallback metadata. Set the matching `--*-resumable` flag to `true` only when the parent session is provably persistent; set/record `false` after an observed `context_unavailable`; otherwise omit it (unknown). Fallback flags are recorded only when a fresh-spawn fallback actually occurs: a fallback record requires `--fallback-reason` **together with** `--fallback-attempted-id` and `--fallback-stage-n` (the failed id and the pass it failed on), while `--fallback-receipt-path` is optional.
|
|
141
198
|
|
|
142
199
|
## Pre-Execution Gate
|
|
143
200
|
|
|
@@ -307,7 +307,7 @@ SKC ports team-mode concepts from `../../oh-my-codex`, not code or OMX/Codex-spe
|
|
|
307
307
|
| Startup ACK | `skc team api worker-startup-ack`, persisted as `workers/<worker>/startup-ack.json`. |
|
|
308
308
|
| Claim-safe lifecycle APIs | `claim-task`, `transition-task-status`, and `release-task-claim` with worker ownership and claim-token guards. |
|
|
309
309
|
| Delivery states and deferred pane attempts | Native notification records under `.skc/_session-{sessionid}/state/team/<team>/notifications/` with `pending`, `sent`, `queued`, `deferred`, `failed`, `delivered`, and `acknowledged` states. |
|
|
310
|
-
|
|
|
310
|
+
| Opt-in memory-guard relaunch | Lifecycle nudges remain non-destructive by default. On Linux only, a worker whose durable `memory-guard.json` explicitly enables automatic action may be checkpointed and relaunched after sustained pressure, bounded retries, current claim validation, and a continuation-safe handoff; unsupported platforms and missing authority remain advisory-only. |
|
|
311
311
|
|
|
312
312
|
Forbidden assumptions: do not copy OMX paths, Codex notify payload formats, OMX process names, or source code directly. Keep tmux as the current runtime; native split-worker TUI remains roadmap-only.
|
|
313
313
|
|
|
@@ -123,10 +123,12 @@ An active Ultragoal run must not give up on a blocker by pausing the goal and as
|
|
|
123
123
|
- **`resolvable`** — anything the agent can act on: failing tests, missing implementation, a dependency to install, an ambiguous-but-inferable detail, investigation. **Never pause.** Exhaust autonomous resolution first: investigate, `skc ultragoal steer --kind add_subgoal --title "Investigate blocker" --objective "..." --evidence "..." --rationale "..."`, delegate an `executor`, or preserve the blocker durably with `skc ultragoal checkpoint --status blocked` / `skc ultragoal record-review-blockers` and keep scheduling the next goal.
|
|
124
124
|
- **`human_blocked`** — only the user can act: credentials/secrets, a manual or physical step, an external approval/decision, access the agent lacks. Pause is the last resort and is gated.
|
|
125
125
|
|
|
126
|
-
`goal({"op":"pause"})` is **blocked at runtime** while an Ultragoal run is active unless the latest
|
|
126
|
+
`goal({"op":"pause"})` is **blocked at runtime** while an Ultragoal run is active unless the latest `blocker_classified` ledger event is `human_blocked` and a later bound clean pause terminal critic verdict is recorded for it (see [Terminal critic gate](#terminal-critic-gate)). `assertUltragoalPauseAllowed` first consumes a pre-existing give-up nudge (a durable ledger write) before it runs the read-only pause diagnostic; only `isUltragoalPauseBlocked` is a pure reader. To pause, first record the human-only classification and capture its event id, then record the terminal critic's clean bound pause verdict, and only then pause:
|
|
127
127
|
|
|
128
128
|
```sh
|
|
129
129
|
skc ultragoal classify-blocker --classification human_blocked --evidence "<the specific human-only dependency>" [--goal-id <id>]
|
|
130
|
+
skc ultragoal record-critic-verdict --terminus pause --classification-event-id <eventId> --verdict OKAY --evidence "<terminal critic evidence>"
|
|
131
|
+
goal({"op":"pause"})
|
|
130
132
|
```
|
|
131
133
|
|
|
132
134
|
Recording `--classification resolvable` is an audit note only; it never authorizes a pause. The `ask` tool stays blocked during active runs regardless of classification — record unresolved decisions as durable blockers instead of prompting.
|
|
@@ -176,28 +178,36 @@ Ultragoal execution should use SKC's bundled role-agent roster when a durable st
|
|
|
176
178
|
- Use `architect` for read-only architecture and code-review lanes, including `CLEAR` / `WATCH` / `BLOCK` status.
|
|
177
179
|
- Use `critic` for read-only plan or handoff critique before execution proceeds.
|
|
178
180
|
|
|
179
|
-
###
|
|
181
|
+
### Implementation delegation guidance
|
|
180
182
|
|
|
181
|
-
|
|
183
|
+
Direct inline implementation by the leader is the default. Delegate to `executor` subagents only when the expected diffs land in **genuinely different sub-domains, modules, or systems** — separable surfaces with independent acceptance criteria and no shared-file contention. File count or line count alone does not force delegation; a large change confined to one domain/subsystem is usually better done inline or by a single sequenced `executor`.
|
|
182
184
|
|
|
183
|
-
|
|
185
|
+
Delegation is worth it when:
|
|
184
186
|
|
|
185
|
-
-
|
|
186
|
-
-
|
|
187
|
-
-
|
|
188
|
-
- The leader has already made **2+ inline edit passes** on the same story and implementation is still materially incomplete.
|
|
187
|
+
- The story spans **multiple distinct sub-domains / modules / systems** (e.g. a CLI surface plus an unrelated runtime subsystem plus docs tooling) whose slices can proceed in parallel without coordinating on the same files.
|
|
188
|
+
- Each slice can be bounded with explicit targets and acceptance criteria that are verifiable independently of the other slices.
|
|
189
|
+
- The leader's checkpoint/verification duties would otherwise be crowded out by juggling unrelated domains inline.
|
|
189
190
|
|
|
190
|
-
|
|
191
|
+
When delegating:
|
|
191
192
|
|
|
192
|
-
-
|
|
193
|
-
-
|
|
194
|
-
-
|
|
195
|
-
- Small, atomic, single-file changes below these thresholds stay with the leader — do not over-delegate trivial work.
|
|
193
|
+
- Give each `executor` bounded targets and explicit acceptance criteria, and keep checkpoint/goal-state ownership in the leader.
|
|
194
|
+
- Parallelize only across genuinely different sub-domains/modules/systems; sequence anything with a real dependency or shared-surface overlap.
|
|
195
|
+
- Work within a single domain/subsystem stays with the leader as direct edits — do not split one cohesive change across subagents, and do not over-delegate trivial work.
|
|
196
196
|
- After integrating delegated slices, run `architect` / `critic` review lanes; worker agents never mutate `.skc/_session-{sessionid}/ultragoal` or call goal tools.
|
|
197
197
|
|
|
198
198
|
When delegating with native subagents, an await timeout only limits the leader's wait. It is not subagent failure evidence and must not be used as a cancellation reason; inspect or continue independent work, and cancel only when the subagent has actually failed, gone off-track, or become unrecoverably wrong.
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
### Subagent reuse and resumption (token efficiency)
|
|
201
|
+
|
|
202
|
+
Fresh spawns re-pay the full context ramp-up (file reads, domain orientation, contract restatement) on every delegation. When a later slice or lane targets the **same sub-domain/module/system** as a prior subagent of the same role, **resume the prior subagent instead of freshly spawning**:
|
|
203
|
+
|
|
204
|
+
- Track the subagent id per role + domain as it is created; on the next same-domain `executor` slice or same-scope `architect` review lane, resume that id and inject only the delta (new targets, new acceptance criteria, the updated frozen change set) rather than re-briefing from scratch.
|
|
205
|
+
- Reuse is domain-scoped: resume only when the prior context is an asset. A slice in a genuinely different sub-domain/module/system gets a fresh spawn — stale cross-domain context is a liability, not a saving.
|
|
206
|
+
- Resumability requires retained subagent resume metadata and a persistent parent session; use existing `subagent` resume/steer controls only. Route per attempt: `running` → steer/inject to the same id and await; `queued` → retain or await the same id; terminal (`completed`/`failed`/`cancelled`) with context available → resume the same id; `context_unavailable`, `not_found`, `no_runner`, or `resume_failed` → fresh spawn fallback for that slice.
|
|
207
|
+
- A resumed subagent is still the same worker under the same contract: it must not mutate `.skc/_session-{sessionid}/ultragoal`, call goal tools, or absorb checkpoint/goal-state ownership, and review lanes (`architect`, `critic`) stay read-only when resumed.
|
|
208
|
+
- Resumption never weakens gates: a resumed `architect` review or `executor` QA lane must still evaluate the current frozen change set on its own evidence, not rubber-stamp its earlier verdict.
|
|
209
|
+
|
|
210
|
+
If an Ultragoal request has no approved plan or consensus artifact **and** the scope genuinely needs one, run `ralplan` first and preserve its PRD, test spec, role roster, and verification guidance in the Ultragoal ledger. Skip `ralplan` for small scope: work that fits a single reviewable PR and is tied to a single domain/subsystem can proceed directly from the brief — record that judgment in the ledger instead of running a planning round. Reach for `ralplan` when the scope spans multiple domains/subsystems, needs cross-cutting sequencing, or would not fit a single PR.
|
|
201
211
|
|
|
202
212
|
The Ultragoal leader owns `.skc/_session-{sessionid}/ultragoal/goals.json` and `.skc/_session-{sessionid}/ultragoal/ledger.jsonl`. Role agents return implementation/review evidence; they do not checkpoint Ultragoal or mutate goal state.
|
|
203
213
|
|
|
@@ -205,8 +215,8 @@ The Ultragoal leader owns `.skc/_session-{sessionid}/ultragoal/goals.json` and `
|
|
|
205
215
|
|
|
206
216
|
Native subagent parallelism is a contract for bounded `executor` delegation, not a runtime scheduler and not a Team-mode rule:
|
|
207
217
|
|
|
208
|
-
- **
|
|
209
|
-
- **
|
|
218
|
+
- **Use native `executor` parallelism only** when a story's expected diffs fall in genuinely different sub-domains/modules/systems, each boundable by a per-slice coordination contract.
|
|
219
|
+
- **Default to direct leader edits** otherwise; sequence any work with real dependencies, shared-file overlap, or a single-domain footprint, and never parallelize work that lacks a safe contract.
|
|
210
220
|
- Worker agents **MUST NOT mutate `.skc/_session-{sessionid}/ultragoal`**, call goal tools, make checkpoint decisions, own integration, or own final verification. The Ultragoal leader keeps those responsibilities.
|
|
211
221
|
|
|
212
222
|
Before workers start, each per-slice coordination contract MUST name the target files/surfaces, independence assumptions, allowed coordination channel, conflict-escalation rule, expected evidence, and terminal status. Conflict or assignment changes remain leader-owned and must be auditable through durable ledger evidence.
|
|
@@ -277,7 +287,7 @@ An ultragoal story cannot be checkpointed `complete` until the active agent has
|
|
|
277
287
|
- architecture-side: system boundaries, layering, data/control flow, operational risks.
|
|
278
288
|
- product-side: user-visible behavior, acceptance criteria, edge cases, regressions.
|
|
279
289
|
- code-side: maintainability, tests, integration points, and unsafe shortcuts.
|
|
280
|
-
5. Delegate an `executor` QA/red-team lane to build and run the e2e/
|
|
290
|
+
5. Delegate an `executor` QA/red-team lane with typed `executionMode: "ultragoal-red-team"` (preferred) — or assignment text that explicitly labels Ultragoal completion QA/red-team — to build and run the e2e/red-teaming QA suite appropriate for the story. A bare `executorQa` field-name mention is not enough to activate the mode. This lane must try to break the change, not just confirm the happy path. It must start from the approved plan/spec/acceptance criteria, then user-facing contracts, and only then implementation code as supporting evidence. Plan/code mismatches are blockers, not items to paper over with implementation intent.
|
|
281
291
|
6. The executor QA/red-team lane must prove evidence by the real surface under test:
|
|
282
292
|
- GUI/web surfaces require a valid automation transcript plus a non-uniform screenshot. Bare `inlineEvidence` text or typed receipts never prove live GUI/web execution.
|
|
283
293
|
- CLI surfaces require runtime argv replay: `schemaVersion: 1`, `kind: "cli-replay"`, `replaySafe: true`, an allowlisted argv `command`, and replayed output validation. The complete field-by-field replay schema, command allowlist, and `replayExempt` audit contract are specified once in the "For CLI replay artifacts" paragraph below the quality-gate JSON; follow it exactly.
|
|
@@ -343,6 +353,46 @@ Provide one `artifactRefs` entry per live surface actually exercised, using the
|
|
|
343
353
|
|
|
344
354
|
For CLI replay artifacts, the JSON at `path` must be an object like `{"schemaVersion":1,"kind":"cli-replay","replaySafe":true,"command":["bun","-e","console.log(\"ultragoal-cli-ok\")"],"cwd":".","env":{"LC_ALL":"C"},"timeoutMs":30000,"expectedExitCode":0,"recordedStdout":"ultragoal-cli-ok\n","recordedStderr":"","invariants":[{"type":"substring","value":"ultragoal-cli-ok"},{"type":"not-substring","value":"error"}]}`. Accepted replay fields are `command` (string array), optional `cwd`, safe `env`, `timeoutMs`, `expectedExitCode`, `recordedStdout`, `recordedStderr`, `normalization`, and `invariants`. The conservative command allowlist is intentionally small: `bun --version`, `node --version`, deterministic `bun/node -e "console.log(...)"`, `npm|pnpm|yarn --version`, `npm|pnpm|yarn list`, read-only `git status|rev-parse|merge-base|diff|show|log` with safe args, and `skc read|status`. `env` must contain only safe deterministic variables, never credentials or machine/user-specific secrets. `normalization` is optional and, when provided, must be exactly the string `"default"` (the built-in normalizer already strips ANSI codes, normalizes line endings, scrubs paths, and trims trailing whitespace); object-shaped normalization is rejected. Invariants may be substring, regex, or not-substring checks; when present, they replace exact `recordedStdout` equality — without `invariants`, replayed normalized stdout must match `recordedStdout` exactly. Unsafe, non-deterministic, credentialed, interactive, or otherwise unallowlisted commands require audited `replayExempt` metadata with exact fields `reasonCode`, `reason`, `approvedBy`, and `fallbackArtifactRefs` plus a structurally valid same-surface fallback artifact. `reason` must be substantive and audited, and `approvedBy` must identify the verifier. Allowed `reasonCode` values are exactly `unsafe_side_effect`, `requires_credentials`, `requires_network`, `non_deterministic_external`, `destructive`, `interactive_only`, and `platform_unavailable`.
|
|
345
355
|
|
|
356
|
+
## Terminal critic gate
|
|
357
|
+
|
|
358
|
+
The terminal critic gate is a fail-closed, once-per-run-terminus review. It guards both terminal exits with a read-only `critic` role agent's `OKAY` verdict; it does not run per story. It is additive to, and does not change, the existing per-story `architect` review and `executor` QA/red-team lanes.
|
|
359
|
+
|
|
360
|
+
### Completion terminus
|
|
361
|
+
|
|
362
|
+
Before assembling the final-aggregate `--quality-gate-json`, the leader delegates the terminal critic. Only the final-aggregate completion checkpoint requires the additional top-level `criticReview` key; `criticReview` is tolerated but ignored on non-final checkpoints. A clean final aggregate requires `verdict: "OKAY"`, non-empty `evidence`, and an empty `blockers` array:
|
|
363
|
+
|
|
364
|
+
```json
|
|
365
|
+
{
|
|
366
|
+
"criticReview": {
|
|
367
|
+
"verdict": "OKAY",
|
|
368
|
+
"evidence": "terminal critic review of the final required-goal state",
|
|
369
|
+
"blockers": []
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Pause/blocked terminus
|
|
375
|
+
|
|
376
|
+
At a `human_blocked` terminus, the leader first runs `skc ultragoal classify-blocker --classification human_blocked` (capturing that classification's ledger `eventId`), then delegates the terminal critic and records its verdict with `skc ultragoal record-critic-verdict --terminus pause --classification-event-id <eventId>` before calling `goal({"op":"pause"})`. The pause is allowed only when a later fresh `critic_verdict` ledger receipt exists with `terminus: "pause"`, `verdict: "OKAY"`, non-empty evidence, an empty blockers array, the current `planGeneration`, and a `classificationEventId` bound to the latest `blocker_classified` event, which must be `human_blocked`. Freshness is scoped to the final required-goal state, so required-goal or steer changes stale the receipt, and a newer classification supersedes an older verdict.
|
|
377
|
+
|
|
378
|
+
The critic must verify that the `human_blocked` classification is genuine, including catching false pauses where needed resources exist locally or the asserted blocker is resolvable. A `REJECT` (or `ITERATE`) verdict refuses the terminal pause; the run keeps executing. The pause (`goal({"op":"pause"})`) is the gated terminal park-and-wait exit — a per-goal `skc ultragoal checkpoint --status blocked` remains available as non-terminal blocker bookkeeping that never signals run completion and keeps the blocker outstanding until resolved.
|
|
379
|
+
|
|
380
|
+
### Invocation and containment
|
|
381
|
+
|
|
382
|
+
At each terminus, the leader gives the read-only `critic` role agent `brief.md`, `goals.json`, `ledger.jsonl`, and the cumulative change set. For completion, invoke it before assembling the final-aggregate gate JSON. For pause, invoke it after the `human_blocked` classification and before `goal({"op":"pause"})`. The terminal critic must not spawn nested `ralplan`, `team`, `deep-interview`, or `ultragoal` workflows. This creates no interactive surface: `ask` remains blocked while an Ultragoal run is active.
|
|
383
|
+
|
|
384
|
+
On repeat terminus attempts within the same run (after an `ITERATE`/`REJECT` reopen cycle or a superseded pause classification), **resume the prior terminal-critic subagent when resumable** instead of freshly spawning one: the critic already holds `brief.md`, `goals.json`, the ledger history, and its own prior findings, so re-invocation only needs the delta (new ledger events, the updated cumulative change set, and evidence addressing the prior blockers). Resume via existing `subagent` resume/steer controls; on `context_unavailable`, `not_found`, `no_runner`, or `resume_failed` — or after a process restart — fall back to a fresh `critic` spawn with the full context bundle. A resumed terminal critic remains read-only, keeps the same containment rules, and must issue a fresh verdict against the current state — a prior `ITERATE` is never carried forward as pre-judged, and each verdict is still recorded through `skc ultragoal record-critic-verdict`.
|
|
385
|
+
|
|
386
|
+
### Non-OKAY loop and ceiling
|
|
387
|
+
|
|
388
|
+
For completion-side `ITERATE` or `REJECT`, the leader MUST first record the terminal verdict so the run-level counter observes it: `skc ultragoal record-critic-verdict --terminus completion --verdict <ITERATE|REJECT> --evidence "<critic findings>"`; then record the findings with `skc ultragoal record-review-blockers` and reopen the run. The dedicated counter ceiling is 5, independently of the give-up nudge budget, and is **RUN-LEVEL**: it counts every non-OKAY terminal-critic verdict across the whole run and all reopen cycles. On reaching that ceiling, both pause and final completion are blocked until a human or leader records `skc ultragoal record-critic-gate-override --evidence "<authorization evidence>"`. There is no automatic pause override.
|
|
389
|
+
|
|
390
|
+
This gate is always fail-closed and has no grandfathering: in-flight runs must obtain a terminal verdict when they reach a terminus.
|
|
391
|
+
|
|
392
|
+
#### Deferred / out of scope
|
|
393
|
+
|
|
394
|
+
Gating active-aggregate `goal drop` after nudge exhaustion is a known follow-up not covered by this gate; `drop` remains governed by the existing nudge discipline.
|
|
395
|
+
|
|
346
396
|
## Review mode
|
|
347
397
|
|
|
348
398
|
`skc ultragoal review` runs the same hardened gate against an already implemented PR, branch, or worktree. Use `--pr <number>` for a PR, `--branch <ref>` for a branch diff, omit both for the current worktree, and pass `--spec <path>` when a real contract exists. `--mode review-only` emits the verdict/findings without creating fix work; `--mode review-start` records review blockers for follow-up. Review mode validates the same `executorQa` shape and live-surface artifacts as `checkpoint --status complete`. A thin or derived-only contract can never clean-pass: the verdict is capped at `inconclusive: weak-contract` until a supplied spec or equivalent strong acceptance criteria are available.
|
|
@@ -372,4 +422,5 @@ The skill tool then dispatches `/skill:ralplan` or `/skill:deep-interview` same-
|
|
|
372
422
|
- Never call `goal({"op":"complete"})` unless the aggregate run or legacy per-story goal is actually complete.
|
|
373
423
|
- In aggregate mode, intermediate and final story checkpoints update durable `goals.json` state and append receipt proof to `ledger.jsonl`; the final story checkpoint creates the final aggregate receipt before the agent may call `goal({"op":"complete"})`.
|
|
374
424
|
- Completion checkpoints require `--quality-gate-json` only. Shell commands and hooks must not mutate goal state; the agent reconciles inline goal-tool state after durable completion.
|
|
425
|
+
- Final-aggregate completion additionally requires a `criticReview` `OKAY`; a `human_blocked` pause additionally requires a fresh `OKAY` `critic_verdict` receipt.
|
|
375
426
|
- Treat `ledger.jsonl` as the durable audit trail; checkpoint after every success or failure.
|
package/src/discovery/ssh.ts
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
* Priority: 5 (low, project/user config discovery)
|
|
6
6
|
*/
|
|
7
7
|
import * as path from "node:path";
|
|
8
|
-
import { getSSHConfigPath, tryParseJson } from "@sayknow-cli/utils";
|
|
8
|
+
import { getSSHConfigPath, logger, tryParseJson } from "@sayknow-cli/utils";
|
|
9
9
|
import { registerProvider } from "../capability";
|
|
10
10
|
import { readFile } from "../capability/fs";
|
|
11
11
|
import { type SSHHost, sshCapability } from "../capability/ssh";
|
|
12
12
|
import type { LoadContext, LoadResult, SourceMeta } from "../capability/types";
|
|
13
|
+
import { validateSshDestination } from "../ssh/utils";
|
|
13
14
|
import { expandTilde } from "../tools/path-utils";
|
|
14
15
|
import { createSourceMeta, expandEnvVarsDeep } from "./helpers";
|
|
15
16
|
|
|
@@ -58,6 +59,16 @@ function normalizeHost(
|
|
|
58
59
|
warnings.push(`Missing host for SSH entry: ${name}`);
|
|
59
60
|
return null;
|
|
60
61
|
}
|
|
62
|
+
const destinationError = validateSshDestination(raw.username, raw.host);
|
|
63
|
+
if (destinationError) {
|
|
64
|
+
warnings.push(`Invalid destination for SSH entry ${name}: ${destinationError}`);
|
|
65
|
+
logger.warn("Ignoring SSH entry with invalid destination", {
|
|
66
|
+
name,
|
|
67
|
+
path: source.path,
|
|
68
|
+
reason: destinationError,
|
|
69
|
+
});
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
61
72
|
|
|
62
73
|
const port = parsePort(raw.port);
|
|
63
74
|
if (raw.port !== undefined && port === undefined) {
|
package/src/edit/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@sayknow-cli/agent-core";
|
|
2
|
-
import { prompt } from "@sayknow-cli/utils";
|
|
2
|
+
import { $pickenv, prompt } from "@sayknow-cli/utils";
|
|
3
3
|
import type * as z from "zod/v4";
|
|
4
4
|
import {
|
|
5
5
|
executeHashlineSingle,
|
|
@@ -352,11 +352,8 @@ export class EditTool implements AgentTool<TInput> {
|
|
|
352
352
|
readonly #pendingDeferredFetches = new Map<string, AbortController>();
|
|
353
353
|
|
|
354
354
|
constructor(private readonly session: ToolSession) {
|
|
355
|
-
const {
|
|
356
|
-
|
|
357
|
-
PI_EDIT_FUZZY_THRESHOLD: editFuzzyThreshold = "auto",
|
|
358
|
-
PI_EDIT_VARIANT: envEditVariant = "auto",
|
|
359
|
-
} = Bun.env;
|
|
355
|
+
const { PI_EDIT_FUZZY: editFuzzy = "auto", PI_EDIT_FUZZY_THRESHOLD: editFuzzyThreshold = "auto" } = Bun.env;
|
|
356
|
+
const envEditVariant = $pickenv("SKC_EDIT_VARIANT", "PI_EDIT_VARIANT") ?? "auto";
|
|
360
357
|
|
|
361
358
|
this.#editMode = resolveConfiguredEditMode(envEditVariant);
|
|
362
359
|
this.#allowFuzzy = resolveAllowFuzzy(session, editFuzzy);
|
package/src/edit/modes/patch.ts
CHANGED
|
@@ -44,6 +44,7 @@ import {
|
|
|
44
44
|
restoreLineEndings,
|
|
45
45
|
stripBom,
|
|
46
46
|
} from "../normalize";
|
|
47
|
+
import { withEditPathMutation } from "../path-mutation-lock";
|
|
47
48
|
import { readEditFileText, serializeEditFileText } from "../read-file";
|
|
48
49
|
import type { EditToolDetails, LspBatchRequest } from "../renderer";
|
|
49
50
|
import {
|
|
@@ -93,6 +94,15 @@ export interface ApplyPatchOptions {
|
|
|
93
94
|
fuzzyThreshold?: number;
|
|
94
95
|
allowFuzzy?: boolean;
|
|
95
96
|
fs?: FileSystem;
|
|
97
|
+
/**
|
|
98
|
+
* When false, skip durable cross-process file locks (in-process path mutex
|
|
99
|
+
* still serializes). Defaults to true only for the real `defaultFileSystem`
|
|
100
|
+
* (or when `fs` is omitted). Disk-backed adapters that are not
|
|
101
|
+
* `defaultFileSystem` (notably production `LspFileSystem` via
|
|
102
|
+
* `executePatchSingle`) MUST pass `crossProcessLock: true` explicitly —
|
|
103
|
+
* object identity is not a durable-lock capability probe.
|
|
104
|
+
*/
|
|
105
|
+
crossProcessLock?: boolean;
|
|
96
106
|
}
|
|
97
107
|
|
|
98
108
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
@@ -1409,13 +1419,29 @@ function applyHunksToContent(
|
|
|
1409
1419
|
|
|
1410
1420
|
/**
|
|
1411
1421
|
* Apply a patch operation to the filesystem.
|
|
1422
|
+
*
|
|
1423
|
+
* Concurrent mutations of the same absolute path are serialized (in-process always;
|
|
1424
|
+
* cross-process file lock when using the real filesystem) so disjoint concurrent
|
|
1425
|
+
* edits cannot silently overwrite each other (#2900).
|
|
1412
1426
|
*/
|
|
1413
1427
|
export async function applyPatch(input: PatchInput, options: ApplyPatchOptions): Promise<ApplyPatchResult> {
|
|
1414
|
-
|
|
1428
|
+
const resolvePath = (p: string): string => resolveToCwd(p, options.cwd);
|
|
1429
|
+
const absolutePath = resolvePath(input.path);
|
|
1430
|
+
const mutationPaths = [absolutePath];
|
|
1431
|
+
if (input.rename) {
|
|
1432
|
+
const destPath = resolvePath(input.rename);
|
|
1433
|
+
if (destPath !== absolutePath) mutationPaths.push(destPath);
|
|
1434
|
+
}
|
|
1435
|
+
const usingDefaultFs = options.fs === undefined || options.fs === defaultFileSystem;
|
|
1436
|
+
// dryRun/preview must stay read-only: never create durable `<path>.lock` dirs
|
|
1437
|
+
// (would fail on read-only parents and mutate the FS during preview) (#2900 review).
|
|
1438
|
+
const crossProcess = options.dryRun === true ? false : (options.crossProcessLock ?? usingDefaultFs);
|
|
1439
|
+
return withEditPathMutation(mutationPaths, () => applyNormalizedPatch(input, options), { crossProcess });
|
|
1415
1440
|
}
|
|
1416
1441
|
|
|
1417
1442
|
/**
|
|
1418
1443
|
* Apply a normalized patch operation to the filesystem.
|
|
1444
|
+
* Caller must already hold path mutation rights when concurrent writers exist.
|
|
1419
1445
|
* @internal
|
|
1420
1446
|
*/
|
|
1421
1447
|
async function applyNormalizedPatch(input: PatchInput, options: ApplyPatchOptions): Promise<ApplyPatchResult> {
|
|
@@ -1514,6 +1540,12 @@ async function applyNormalizedPatch(input: PatchInput, options: ApplyPatchOption
|
|
|
1514
1540
|
const isMove = Boolean(input.rename) && destPath !== absolutePath;
|
|
1515
1541
|
|
|
1516
1542
|
if (!dryRun) {
|
|
1543
|
+
// Commit-time CAS: reject if another writer mutated the file after our
|
|
1544
|
+
// authoritative read (e.g. a process that did not take the path lock).
|
|
1545
|
+
const commitContent = await readExistingPatchFile(fs, absolutePath, input.path);
|
|
1546
|
+
if (commitContent !== originalContent) {
|
|
1547
|
+
throw new ApplyPatchError(`concurrent edit conflict: file changed since read: ${input.path}`);
|
|
1548
|
+
}
|
|
1517
1549
|
if (isMove) {
|
|
1518
1550
|
const parentDir = path.dirname(destPath);
|
|
1519
1551
|
if (parentDir && parentDir !== ".") {
|
|
@@ -1735,11 +1767,16 @@ export async function executePatchSingle(
|
|
|
1735
1767
|
|
|
1736
1768
|
const input: PatchInput = { path: resolvedPath, op, rename: resolvedRename, diff };
|
|
1737
1769
|
const patchFileSystem = new LspFileSystem(writethrough, signal, batchRequest, beginDeferredDiagnosticsForPath);
|
|
1770
|
+
// Production edit path uses LspFileSystem (disk-backed writethrough), which is
|
|
1771
|
+
// not `defaultFileSystem` by object identity. Force durable cross-process
|
|
1772
|
+
// locking so multi-process agents cannot silently race past the path mutex
|
|
1773
|
+
// (#2900 review: do not infer lock capability from FileSystem identity).
|
|
1738
1774
|
const result = await applyPatch(input, {
|
|
1739
1775
|
cwd: session.cwd,
|
|
1740
1776
|
fs: patchFileSystem,
|
|
1741
1777
|
fuzzyThreshold,
|
|
1742
1778
|
allowFuzzy,
|
|
1779
|
+
crossProcessLock: true,
|
|
1743
1780
|
});
|
|
1744
1781
|
|
|
1745
1782
|
// Post-write verification: only meaningful for in-place updates where the
|
|
@@ -55,6 +55,7 @@ void import("@sayknow-cli/natives")
|
|
|
55
55
|
// Native unavailable; fuzzy matching uses the TS fallback.
|
|
56
56
|
});
|
|
57
57
|
|
|
58
|
+
import { withEditPathMutation } from "../path-mutation-lock";
|
|
58
59
|
import { readEditFileText, serializeEditFileText } from "../read-file";
|
|
59
60
|
import type { EditToolDetails, LspBatchRequest } from "../renderer";
|
|
60
61
|
|
|
@@ -1087,6 +1088,47 @@ export async function executeReplaceSingle(
|
|
|
1087
1088
|
}
|
|
1088
1089
|
|
|
1089
1090
|
const absolutePath = resolvePlanPath(session, path);
|
|
1091
|
+
return withEditPathMutation([absolutePath], () =>
|
|
1092
|
+
executeReplaceSingleUnderLock({
|
|
1093
|
+
session,
|
|
1094
|
+
path,
|
|
1095
|
+
params,
|
|
1096
|
+
signal,
|
|
1097
|
+
batchRequest,
|
|
1098
|
+
allowFuzzy,
|
|
1099
|
+
fuzzyThreshold,
|
|
1100
|
+
writethrough,
|
|
1101
|
+
beginDeferredDiagnosticsForPath,
|
|
1102
|
+
absolutePath,
|
|
1103
|
+
old_text,
|
|
1104
|
+
new_text,
|
|
1105
|
+
all,
|
|
1106
|
+
}),
|
|
1107
|
+
);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
async function executeReplaceSingleUnderLock(
|
|
1111
|
+
options: ExecuteReplaceSingleOptions & {
|
|
1112
|
+
absolutePath: string;
|
|
1113
|
+
old_text: string;
|
|
1114
|
+
new_text: string;
|
|
1115
|
+
all: boolean | undefined;
|
|
1116
|
+
},
|
|
1117
|
+
): Promise<AgentToolResult<EditToolDetails, typeof replaceEditEntrySchema>> {
|
|
1118
|
+
const {
|
|
1119
|
+
path,
|
|
1120
|
+
signal,
|
|
1121
|
+
batchRequest,
|
|
1122
|
+
allowFuzzy,
|
|
1123
|
+
fuzzyThreshold,
|
|
1124
|
+
writethrough,
|
|
1125
|
+
beginDeferredDiagnosticsForPath,
|
|
1126
|
+
absolutePath,
|
|
1127
|
+
old_text,
|
|
1128
|
+
new_text,
|
|
1129
|
+
all,
|
|
1130
|
+
} = options;
|
|
1131
|
+
|
|
1090
1132
|
const rawContent = await readEditFileText(absolutePath, path);
|
|
1091
1133
|
const { bom, text: content } = stripBom(rawContent);
|
|
1092
1134
|
const originalEnding = detectLineEnding(content);
|