@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
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Predict the user's likely next prompt after an agent turn using a smol,
|
|
3
|
+
* fast model. Native SKC port of the ghost-text prompt-suggestion behavior
|
|
4
|
+
* from Claude Code: the prediction renders as dim ghost text in the empty
|
|
5
|
+
* composer and Tab accepts it.
|
|
6
|
+
*/
|
|
7
|
+
import type { AgentMessage } from "@sayknow-cli/agent-core";
|
|
8
|
+
import { type Api, type AssistantMessage, completeSimple, type Model } from "@sayknow-cli/ai";
|
|
9
|
+
import { logger, prompt } from "@sayknow-cli/utils";
|
|
10
|
+
import type { ModelRegistry } from "../config/model-registry";
|
|
11
|
+
import { resolveRoleSelection } from "../config/model-resolver";
|
|
12
|
+
import type { Settings } from "../config/settings";
|
|
13
|
+
import suggestionSystemPrompt from "../prompts/system/prompt-suggestion-system.md" with { type: "text" };
|
|
14
|
+
|
|
15
|
+
const SUGGESTION_SYSTEM_PROMPT = prompt.render(suggestionSystemPrompt);
|
|
16
|
+
|
|
17
|
+
export const PROMPT_SUGGESTION_MAX_WORDS = 12;
|
|
18
|
+
export const PROMPT_SUGGESTION_MAX_CHARS = 100;
|
|
19
|
+
|
|
20
|
+
const SUGGESTION_MAX_TOKENS = 64;
|
|
21
|
+
const REASONING_SAFE_MAX_TOKENS = 1024;
|
|
22
|
+
const MAX_CONTEXT_MESSAGES = 12;
|
|
23
|
+
const MAX_MESSAGE_CHARS = 1500;
|
|
24
|
+
const MAX_CONTEXT_CHARS = 8000;
|
|
25
|
+
|
|
26
|
+
/** Single words a user plausibly types on their own; anything else needs >= 2 words. */
|
|
27
|
+
const SINGLE_WORD_ALLOWLIST = new Set([
|
|
28
|
+
"yes",
|
|
29
|
+
"yeah",
|
|
30
|
+
"yep",
|
|
31
|
+
"yea",
|
|
32
|
+
"yup",
|
|
33
|
+
"sure",
|
|
34
|
+
"ok",
|
|
35
|
+
"okay",
|
|
36
|
+
"push",
|
|
37
|
+
"commit",
|
|
38
|
+
"deploy",
|
|
39
|
+
"stop",
|
|
40
|
+
"continue",
|
|
41
|
+
"check",
|
|
42
|
+
"exit",
|
|
43
|
+
"quit",
|
|
44
|
+
"no",
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
/** Model responses that are meta-statements about staying silent, not suggestions. */
|
|
48
|
+
const META_TEXT_PATTERN = /\bsilence is\b|\bstay(s|ing)? silent\b/;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Strip wrapper tags and label prefixes a model sometimes adds despite being
|
|
52
|
+
* told to reply with only the suggestion.
|
|
53
|
+
*/
|
|
54
|
+
export function sanitizePromptSuggestion(raw: string): string {
|
|
55
|
+
return raw
|
|
56
|
+
.trim()
|
|
57
|
+
.replace(/^<(suggestion|response|output|answer|result)>([\s\S]*)<\/\1>$/i, (match, tag: string, inner: string) =>
|
|
58
|
+
// Keep the original when the inner text itself contains the closing
|
|
59
|
+
// tag (nested/ambiguous markup) instead of producing a mangled strip.
|
|
60
|
+
inner.includes(`</${tag.toLowerCase()}>`) || inner.includes(`</${tag.toUpperCase()}>`) ? match : inner,
|
|
61
|
+
)
|
|
62
|
+
.replace(
|
|
63
|
+
/^\s*(suggested\s+(response|reply|input|prompt)|suggestion|response|reply|answer|output|result)\s*:\s*/i,
|
|
64
|
+
"",
|
|
65
|
+
)
|
|
66
|
+
.trim();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Heuristic gate rejecting model output that would make a bad ghost-text
|
|
71
|
+
* suggestion. Returns the rejection reason, or null when the text is usable.
|
|
72
|
+
*/
|
|
73
|
+
export function suppressPromptSuggestionReason(text: string): string | null {
|
|
74
|
+
if (!text) return "empty";
|
|
75
|
+
const lower = text.toLowerCase();
|
|
76
|
+
const wordCount = text.trim().split(/\s+/).length;
|
|
77
|
+
|
|
78
|
+
if (lower === "done") return "done";
|
|
79
|
+
if (
|
|
80
|
+
lower === "nothing found" ||
|
|
81
|
+
lower === "nothing found." ||
|
|
82
|
+
lower.startsWith("nothing to suggest") ||
|
|
83
|
+
lower.startsWith("no suggestion") ||
|
|
84
|
+
META_TEXT_PATTERN.test(lower) ||
|
|
85
|
+
/^\W*silence\W*$/.test(lower)
|
|
86
|
+
) {
|
|
87
|
+
return "meta_text";
|
|
88
|
+
}
|
|
89
|
+
if (/^\(.*\)$|^\[.*\]$/.test(text)) return "meta_wrapped";
|
|
90
|
+
if (
|
|
91
|
+
lower.startsWith("api error:") ||
|
|
92
|
+
lower.startsWith("prompt is too long") ||
|
|
93
|
+
lower.startsWith("request timed out") ||
|
|
94
|
+
lower.startsWith("invalid api key")
|
|
95
|
+
) {
|
|
96
|
+
return "error_message";
|
|
97
|
+
}
|
|
98
|
+
if (/^\w+:\s/.test(text)) return "prefixed_label";
|
|
99
|
+
if (wordCount < 2 && !text.startsWith("/") && !SINGLE_WORD_ALLOWLIST.has(lower)) return "too_few_words";
|
|
100
|
+
if (wordCount > PROMPT_SUGGESTION_MAX_WORDS) return "too_many_words";
|
|
101
|
+
if (text.length >= PROMPT_SUGGESTION_MAX_CHARS) return "too_long";
|
|
102
|
+
if (/[.!?]\s+[A-Z]/.test(text)) return "multiple_sentences";
|
|
103
|
+
if (/[\n*]|\*\*/.test(text)) return "has_formatting";
|
|
104
|
+
if (
|
|
105
|
+
/thanks|thank you|looks good|sounds good|that works|that worked|that's all|nice|great|perfect|makes sense|awesome|excellent/.test(
|
|
106
|
+
lower,
|
|
107
|
+
)
|
|
108
|
+
) {
|
|
109
|
+
return "evaluative";
|
|
110
|
+
}
|
|
111
|
+
if (
|
|
112
|
+
/^(let me|i'll|i've|i'm|i can|i would|i think|i notice|here's|here is|here are|that's|this is|this will|you can|you should|you could|sure,|of course|certainly)/i.test(
|
|
113
|
+
text,
|
|
114
|
+
)
|
|
115
|
+
) {
|
|
116
|
+
return "claude_voice";
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function extractTranscriptEntry(message: AgentMessage): { role: "User" | "Assistant"; text: string } | null {
|
|
122
|
+
if (message.role === "user") {
|
|
123
|
+
const content = message.content;
|
|
124
|
+
const text =
|
|
125
|
+
typeof content === "string"
|
|
126
|
+
? content
|
|
127
|
+
: content
|
|
128
|
+
.filter(block => block.type === "text")
|
|
129
|
+
.map(block => block.text)
|
|
130
|
+
.join("\n");
|
|
131
|
+
const trimmed = text.trim();
|
|
132
|
+
return trimmed ? { role: "User", text: trimmed } : null;
|
|
133
|
+
}
|
|
134
|
+
if (message.role === "assistant") {
|
|
135
|
+
const text = (message as AssistantMessage).content
|
|
136
|
+
.filter(block => block.type === "text")
|
|
137
|
+
.map(block => block.text)
|
|
138
|
+
.join("\n")
|
|
139
|
+
.trim();
|
|
140
|
+
return text ? { role: "Assistant", text } : null;
|
|
141
|
+
}
|
|
142
|
+
return null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Build a compact recent-conversation transcript for the prediction request.
|
|
147
|
+
* Returns null when there is no user message to predict from.
|
|
148
|
+
*/
|
|
149
|
+
export function buildPromptSuggestionContext(messages: AgentMessage[]): string | null {
|
|
150
|
+
const entries: { role: "User" | "Assistant"; text: string }[] = [];
|
|
151
|
+
let totalChars = 0;
|
|
152
|
+
for (let index = messages.length - 1; index >= 0 && entries.length < MAX_CONTEXT_MESSAGES; index--) {
|
|
153
|
+
const message = messages[index];
|
|
154
|
+
if (!message) continue;
|
|
155
|
+
const entry = extractTranscriptEntry(message);
|
|
156
|
+
if (!entry) continue;
|
|
157
|
+
const text = entry.text.length > MAX_MESSAGE_CHARS ? `${entry.text.slice(0, MAX_MESSAGE_CHARS)}…` : entry.text;
|
|
158
|
+
if (totalChars + text.length > MAX_CONTEXT_CHARS && entries.length > 0) break;
|
|
159
|
+
entries.push({ role: entry.role, text });
|
|
160
|
+
totalChars += text.length;
|
|
161
|
+
}
|
|
162
|
+
if (!entries.some(entry => entry.role === "User")) return null;
|
|
163
|
+
entries.reverse();
|
|
164
|
+
const transcript = entries.map(entry => `${entry.role}: ${entry.text}`).join("\n\n");
|
|
165
|
+
return `<conversation>\n${transcript}\n</conversation>\n\nPredict the user's next message.`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function getSuggestionModel(
|
|
169
|
+
registry: ModelRegistry,
|
|
170
|
+
settings: Settings,
|
|
171
|
+
currentModel?: Model<Api>,
|
|
172
|
+
): Model<Api> | undefined {
|
|
173
|
+
const availableModels = registry.getAvailable();
|
|
174
|
+
if (availableModels.length === 0) return undefined;
|
|
175
|
+
const model = resolveRoleSelection(["default"], settings, availableModels, registry)?.model;
|
|
176
|
+
if (model) return model;
|
|
177
|
+
return currentModel;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
function extractSuggestionText(contentBlocks: AssistantMessage["content"]): string {
|
|
181
|
+
let text = "";
|
|
182
|
+
for (const content of contentBlocks) {
|
|
183
|
+
if (content.type === "text") {
|
|
184
|
+
text += content.text;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return text;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Generate a predicted next user prompt from the recent conversation.
|
|
192
|
+
* Returns null when no usable suggestion is produced (silence is the
|
|
193
|
+
* expected steady state).
|
|
194
|
+
*/
|
|
195
|
+
export async function generatePromptSuggestion(
|
|
196
|
+
messages: AgentMessage[],
|
|
197
|
+
registry: ModelRegistry,
|
|
198
|
+
settings: Settings,
|
|
199
|
+
sessionId?: string,
|
|
200
|
+
currentModel?: Model<Api>,
|
|
201
|
+
metadataResolver?: (provider: string) => Record<string, unknown> | undefined,
|
|
202
|
+
): Promise<string | null> {
|
|
203
|
+
const context = buildPromptSuggestionContext(messages);
|
|
204
|
+
if (!context) return null;
|
|
205
|
+
|
|
206
|
+
const model = getSuggestionModel(registry, settings, currentModel);
|
|
207
|
+
if (!model) {
|
|
208
|
+
logger.debug("prompt-suggestion: no model available");
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const apiKey = await registry.getApiKey(model, sessionId);
|
|
213
|
+
if (!apiKey) {
|
|
214
|
+
logger.debug("prompt-suggestion: no API key", { provider: model.provider, id: model.id });
|
|
215
|
+
return null;
|
|
216
|
+
}
|
|
217
|
+
// Resolve metadata after getApiKey so the session-sticky credential for
|
|
218
|
+
// this request is already recorded (same contract as title-generator).
|
|
219
|
+
const metadata = metadataResolver?.(model.provider);
|
|
220
|
+
|
|
221
|
+
// A suggestion is a 2-12 word task, but some reasoning backends ignore
|
|
222
|
+
// disableReasoning; reserve output room for the answer after any
|
|
223
|
+
// unavoidable thinking tokens.
|
|
224
|
+
const maxTokens = model.reasoning
|
|
225
|
+
? Math.max(SUGGESTION_MAX_TOKENS, REASONING_SAFE_MAX_TOKENS)
|
|
226
|
+
: SUGGESTION_MAX_TOKENS;
|
|
227
|
+
|
|
228
|
+
try {
|
|
229
|
+
const response = await completeSimple(
|
|
230
|
+
model,
|
|
231
|
+
{
|
|
232
|
+
systemPrompt: [SUGGESTION_SYSTEM_PROMPT],
|
|
233
|
+
messages: [{ role: "user", content: context, timestamp: Date.now() }],
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
apiKey,
|
|
237
|
+
maxTokens,
|
|
238
|
+
disableReasoning: true,
|
|
239
|
+
metadata,
|
|
240
|
+
},
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
if (response.stopReason === "error") {
|
|
244
|
+
logger.debug("prompt-suggestion: response error", {
|
|
245
|
+
model: `${model.provider}/${model.id}`,
|
|
246
|
+
errorMessage: response.errorMessage,
|
|
247
|
+
});
|
|
248
|
+
return null;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
const suggestion = sanitizePromptSuggestion(extractSuggestionText(response.content));
|
|
252
|
+
const suppressReason = suppressPromptSuggestionReason(suggestion);
|
|
253
|
+
if (suppressReason) {
|
|
254
|
+
logger.debug("prompt-suggestion: suppressed", { reason: suppressReason });
|
|
255
|
+
return null;
|
|
256
|
+
}
|
|
257
|
+
return suggestion;
|
|
258
|
+
} catch (err) {
|
|
259
|
+
logger.debug("prompt-suggestion: error", {
|
|
260
|
+
model: `${model.provider}/${model.id}`,
|
|
261
|
+
error: err instanceof Error ? err.message : String(err),
|
|
262
|
+
});
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
@@ -5,13 +5,15 @@
|
|
|
5
5
|
* .bashrc/.zshrc, which can be sourced before each command to provide a familiar
|
|
6
6
|
* shell experience.
|
|
7
7
|
*/
|
|
8
|
-
import * as fs from "node:fs";
|
|
8
|
+
import * as fs from "node:fs/promises";
|
|
9
9
|
import * as os from "node:os";
|
|
10
10
|
import * as path from "node:path";
|
|
11
11
|
import { postmortem } from "@sayknow-cli/utils";
|
|
12
12
|
|
|
13
|
-
const cachedSnapshotPaths = new Map<string, string>();
|
|
14
13
|
const SNAPSHOT_TIMEOUT_MS = 2_000;
|
|
14
|
+
const SNAPSHOT_ROOT_PREFIX = "skc-shell-snapshots-";
|
|
15
|
+
const PRIVATE_DIRECTORY_MODE = 0o700;
|
|
16
|
+
const PRIVATE_FILE_MODE = 0o600;
|
|
15
17
|
|
|
16
18
|
function sanitizeSnapshotEnv(env: Record<string, string | undefined>): Record<string, string | undefined> {
|
|
17
19
|
const sanitized = { ...env };
|
|
@@ -23,8 +25,7 @@ function sanitizeSnapshotEnv(env: Record<string, string | undefined>): Record<st
|
|
|
23
25
|
/**
|
|
24
26
|
* Get the user's shell config file path.
|
|
25
27
|
*/
|
|
26
|
-
function getShellConfigFile(shell: string): string {
|
|
27
|
-
const home = os.homedir();
|
|
28
|
+
function getShellConfigFile(shell: string, home: string): string {
|
|
28
29
|
if (shell.includes("zsh")) return path.join(home, ".zshrc");
|
|
29
30
|
if (shell.includes("bash")) return path.join(home, ".bashrc");
|
|
30
31
|
return path.join(home, ".profile");
|
|
@@ -35,8 +36,7 @@ function getShellConfigFile(shell: string): string {
|
|
|
35
36
|
* This script sources the user's rc file and extracts functions, aliases, and options.
|
|
36
37
|
* Matches Anthropic Code's snapshot generation logic.
|
|
37
38
|
*/
|
|
38
|
-
function generateSnapshotScript(shell: string, snapshotPath: string, rcFile: string): string {
|
|
39
|
-
const hasRcFile = fs.existsSync(rcFile);
|
|
39
|
+
function generateSnapshotScript(shell: string, snapshotPath: string, rcFile: string, hasRcFile: boolean): string {
|
|
40
40
|
const isZsh = shell.includes("zsh");
|
|
41
41
|
const commonToolsRegex =
|
|
42
42
|
"^(ls|dir|vdir|cat|head|tail|less|more|grep|egrep|fgrep|rg|find|fd|locate|sed|awk|perl|cp|mv|rm|mkdir|rmdir|touch|chmod|chown|ln|pwd|readlink|stat|cut|sort|uniq|xargs|tee|tr|basename|dirname)$";
|
|
@@ -79,6 +79,7 @@ echo "shopt -s expand_aliases" >> "$SNAPSHOT_FILE"
|
|
|
79
79
|
`;
|
|
80
80
|
|
|
81
81
|
return `
|
|
82
|
+
umask 077
|
|
82
83
|
SNAPSHOT_FILE='${escapedPath}'
|
|
83
84
|
|
|
84
85
|
# Source user's rc file if it exists
|
|
@@ -114,74 +115,183 @@ fi
|
|
|
114
115
|
`.trim();
|
|
115
116
|
}
|
|
116
117
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
shell: string,
|
|
123
|
-
env: Record<string, string | undefined>,
|
|
124
|
-
): Promise<string | null> {
|
|
125
|
-
const cacheKey = shell;
|
|
126
|
-
// Return cached snapshot if valid
|
|
127
|
-
const cached = cachedSnapshotPaths.get(cacheKey);
|
|
128
|
-
if (cached && fs.existsSync(cached)) {
|
|
129
|
-
return cached;
|
|
130
|
-
}
|
|
131
|
-
if (cached) {
|
|
132
|
-
cachedSnapshotPaths.delete(cacheKey);
|
|
133
|
-
}
|
|
118
|
+
interface ShellSnapshotCacheOptions {
|
|
119
|
+
tempRoot: string;
|
|
120
|
+
home: string | (() => string);
|
|
121
|
+
platform: NodeJS.Platform;
|
|
122
|
+
}
|
|
134
123
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
124
|
+
function ownedByCurrentUser(uid: number): boolean {
|
|
125
|
+
return typeof process.getuid !== "function" || uid === process.getuid();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
async function isTrustedSnapshot(snapshotPath: string): Promise<boolean> {
|
|
129
|
+
try {
|
|
130
|
+
const stat = await fs.lstat(snapshotPath);
|
|
131
|
+
return stat.isFile() && !stat.isSymbolicLink() && ownedByCurrentUser(stat.uid) && (stat.mode & 0o077) === 0;
|
|
132
|
+
} catch {
|
|
133
|
+
return false;
|
|
138
134
|
}
|
|
135
|
+
}
|
|
139
136
|
|
|
140
|
-
|
|
137
|
+
function createShellSnapshotCache(options: ShellSnapshotCacheOptions) {
|
|
138
|
+
const cachedPaths = new Map<string, string>();
|
|
139
|
+
const inFlight = new Map<string, Promise<string | null>>();
|
|
140
|
+
let rootPath: string | null = null;
|
|
141
|
+
let rootInitialization: Promise<string> | null = null;
|
|
142
|
+
let cleanupAttempt: Promise<void> | null = null;
|
|
143
|
+
let cleaningUp = false;
|
|
144
|
+
let lifecycle = 0;
|
|
141
145
|
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
146
|
+
async function initializeRoot(): Promise<string> {
|
|
147
|
+
const root = await fs.mkdtemp(path.join(options.tempRoot, SNAPSHOT_ROOT_PREFIX));
|
|
148
|
+
try {
|
|
149
|
+
await fs.chmod(root, PRIVATE_DIRECTORY_MODE);
|
|
150
|
+
const stat = await fs.lstat(root);
|
|
151
|
+
if (
|
|
152
|
+
!stat.isDirectory() ||
|
|
153
|
+
stat.isSymbolicLink() ||
|
|
154
|
+
!ownedByCurrentUser(stat.uid) ||
|
|
155
|
+
(stat.mode & 0o777) !== 0o700
|
|
156
|
+
) {
|
|
157
|
+
throw new Error("Shell snapshot root is not owner-private");
|
|
158
|
+
}
|
|
159
|
+
return root;
|
|
160
|
+
} catch (error) {
|
|
161
|
+
await fs.rm(root, { recursive: true, force: true }).catch(() => {});
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function getRoot(): Promise<string> {
|
|
167
|
+
if (rootPath) return rootPath;
|
|
168
|
+
if (rootInitialization) return await rootInitialization;
|
|
169
|
+
const attempt = initializeRoot();
|
|
170
|
+
rootInitialization = attempt;
|
|
171
|
+
try {
|
|
172
|
+
rootPath = await attempt;
|
|
173
|
+
return rootPath;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (rootInitialization === attempt) rootInitialization = null;
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
145
179
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
180
|
+
async function createSnapshot(shell: string, env: Record<string, string | undefined>): Promise<string | null> {
|
|
181
|
+
const root = await getRoot();
|
|
182
|
+
const shellName = shell.includes("zsh") ? "zsh" : shell.includes("bash") ? "bash" : "sh";
|
|
183
|
+
const snapshotPath = path.join(root, `snapshot-${shellName}-${crypto.randomUUID()}.sh`);
|
|
184
|
+
let keep = false;
|
|
185
|
+
try {
|
|
186
|
+
const handle = await fs.open(snapshotPath, "wx", PRIVATE_FILE_MODE);
|
|
187
|
+
try {
|
|
188
|
+
await handle.chmod(PRIVATE_FILE_MODE);
|
|
189
|
+
} finally {
|
|
190
|
+
await handle.close();
|
|
191
|
+
}
|
|
149
192
|
|
|
150
|
-
|
|
151
|
-
|
|
193
|
+
const home = typeof options.home === "function" ? options.home() : options.home;
|
|
194
|
+
const rcFile = getShellConfigFile(shell, home);
|
|
195
|
+
const script = generateSnapshotScript(shell, snapshotPath, rcFile, await Bun.file(rcFile).exists());
|
|
196
|
+
const snapshotEnv = sanitizeSnapshotEnv(env);
|
|
197
|
+
const spawnEnv: Record<string, string> = {};
|
|
198
|
+
for (const [key, value] of Object.entries(snapshotEnv)) {
|
|
199
|
+
if (value !== undefined) spawnEnv[key] = value;
|
|
200
|
+
}
|
|
201
|
+
const child = Bun.spawn([shell, "-c", script], {
|
|
202
|
+
env: spawnEnv,
|
|
203
|
+
stdin: "ignore",
|
|
204
|
+
stdout: "ignore",
|
|
205
|
+
stderr: "ignore",
|
|
206
|
+
timeout: SNAPSHOT_TIMEOUT_MS,
|
|
207
|
+
killSignal: "SIGKILL",
|
|
208
|
+
});
|
|
152
209
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
spawnEnv[key] = value;
|
|
210
|
+
await child.exited;
|
|
211
|
+
if (child.exitCode === 0 && (await isTrustedSnapshot(snapshotPath))) {
|
|
212
|
+
cachedPaths.set(shell, snapshotPath);
|
|
213
|
+
keep = true;
|
|
214
|
+
return snapshotPath;
|
|
159
215
|
}
|
|
216
|
+
} catch {
|
|
217
|
+
// Snapshot creation failed, proceed without it.
|
|
218
|
+
} finally {
|
|
219
|
+
if (!keep) await fs.unlink(snapshotPath).catch(() => {});
|
|
160
220
|
}
|
|
161
|
-
|
|
162
|
-
env: spawnEnv,
|
|
163
|
-
stdin: "ignore",
|
|
164
|
-
stdout: "ignore",
|
|
165
|
-
stderr: "ignore",
|
|
166
|
-
timeout: SNAPSHOT_TIMEOUT_MS,
|
|
167
|
-
killSignal: "SIGKILL",
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
await child.exited;
|
|
171
|
-
if (child.exitCode === 0 && fs.existsSync(snapshotPath)) {
|
|
172
|
-
cachedSnapshotPaths.set(cacheKey, snapshotPath);
|
|
173
|
-
return snapshotPath;
|
|
174
|
-
}
|
|
175
|
-
} catch {
|
|
176
|
-
// Snapshot creation failed, proceed without it
|
|
221
|
+
return null;
|
|
177
222
|
}
|
|
178
223
|
|
|
179
|
-
return
|
|
224
|
+
return {
|
|
225
|
+
async getOrCreateSnapshot(shell: string, env: Record<string, string | undefined>) {
|
|
226
|
+
if (options.platform === "win32" || cleaningUp) return null;
|
|
227
|
+
const currentLifecycle = lifecycle;
|
|
228
|
+
const cached = cachedPaths.get(shell);
|
|
229
|
+
if (cached && (await isTrustedSnapshot(cached))) {
|
|
230
|
+
return cleaningUp || lifecycle !== currentLifecycle ? null : cached;
|
|
231
|
+
}
|
|
232
|
+
if (cleaningUp || lifecycle !== currentLifecycle) return null;
|
|
233
|
+
if (cached) cachedPaths.delete(shell);
|
|
234
|
+
const pending = inFlight.get(shell);
|
|
235
|
+
if (pending) {
|
|
236
|
+
const snapshot = await pending;
|
|
237
|
+
return cleaningUp || lifecycle !== currentLifecycle ? null : snapshot;
|
|
238
|
+
}
|
|
239
|
+
const attempt = createSnapshot(shell, env);
|
|
240
|
+
inFlight.set(shell, attempt);
|
|
241
|
+
try {
|
|
242
|
+
const snapshot = await attempt;
|
|
243
|
+
return cleaningUp || lifecycle !== currentLifecycle ? null : snapshot;
|
|
244
|
+
} finally {
|
|
245
|
+
if (inFlight.get(shell) === attempt) inFlight.delete(shell);
|
|
246
|
+
}
|
|
247
|
+
},
|
|
248
|
+
async cleanup() {
|
|
249
|
+
if (cleanupAttempt) return await cleanupAttempt;
|
|
250
|
+
const attempt = (async () => {
|
|
251
|
+
cleaningUp = true;
|
|
252
|
+
lifecycle += 1;
|
|
253
|
+
try {
|
|
254
|
+
const pending: Promise<unknown>[] = [...inFlight.values()];
|
|
255
|
+
if (rootInitialization) pending.push(rootInitialization);
|
|
256
|
+
await Promise.allSettled(pending);
|
|
257
|
+
const root = rootPath ?? (await rootInitialization?.catch(() => null)) ?? null;
|
|
258
|
+
cachedPaths.clear();
|
|
259
|
+
inFlight.clear();
|
|
260
|
+
rootPath = null;
|
|
261
|
+
rootInitialization = null;
|
|
262
|
+
if (root) await fs.rm(root, { recursive: true, force: true });
|
|
263
|
+
} finally {
|
|
264
|
+
cleaningUp = false;
|
|
265
|
+
}
|
|
266
|
+
})();
|
|
267
|
+
cleanupAttempt = attempt;
|
|
268
|
+
try {
|
|
269
|
+
await attempt;
|
|
270
|
+
} finally {
|
|
271
|
+
if (cleanupAttempt === attempt) cleanupAttempt = null;
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
};
|
|
180
275
|
}
|
|
181
276
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
277
|
+
/** @internal Test-only factory for isolated filesystem and platform assertions. */
|
|
278
|
+
export const createShellSnapshotCacheForTesting = createShellSnapshotCache;
|
|
279
|
+
|
|
280
|
+
const processSnapshotCache = createShellSnapshotCache({
|
|
281
|
+
tempRoot: os.tmpdir(),
|
|
282
|
+
home: () => os.homedir(),
|
|
283
|
+
platform: process.platform,
|
|
187
284
|
});
|
|
285
|
+
|
|
286
|
+
/**
|
|
287
|
+
* Create a shell snapshot, caching the result.
|
|
288
|
+
* Returns the path to the snapshot file, or null if creation failed.
|
|
289
|
+
*/
|
|
290
|
+
export async function getOrCreateSnapshot(
|
|
291
|
+
shell: string,
|
|
292
|
+
env: Record<string, string | undefined>,
|
|
293
|
+
): Promise<string | null> {
|
|
294
|
+
return await processSnapshotCache.getOrCreateSnapshot(shell, env);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
postmortem.register("shell-snapshot", () => processSnapshotCache.cleanup());
|
package/src/utils/sixel.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { $
|
|
1
|
+
import { $pickenv, $pickflag } from "@sayknow-cli/utils";
|
|
2
2
|
|
|
3
3
|
const SIXEL_START_REGEX = /\x1bP(?:[0-9;]*)q/u;
|
|
4
4
|
const SIXEL_END_SEQUENCE = "\x1b\\";
|
|
@@ -14,8 +14,8 @@ const SIXEL_PLACEHOLDER_PREFIX = "__SKC_SIXEL_SEQUENCE_";
|
|
|
14
14
|
* - PI_ALLOW_SIXEL_PASSTHROUGH=1
|
|
15
15
|
*/
|
|
16
16
|
export function isSixelPassthroughEnabled(): boolean {
|
|
17
|
-
const forcedProtocol = $
|
|
18
|
-
return forcedProtocol === "sixel" && $
|
|
17
|
+
const forcedProtocol = $pickenv("SKC_FORCE_IMAGE_PROTOCOL", "PI_FORCE_IMAGE_PROTOCOL")?.trim().toLowerCase();
|
|
18
|
+
return forcedProtocol === "sixel" && $pickflag("SKC_ALLOW_SIXEL_PASSTHROUGH", "PI_ALLOW_SIXEL_PASSTHROUGH");
|
|
19
19
|
}
|
|
20
20
|
/** Returns true when the text contains a SIXEL start sequence. */
|
|
21
21
|
export function containsSixelSequence(text: string): boolean {
|
package/src/web/insane/bridge.ts
CHANGED
|
@@ -31,6 +31,7 @@ const KILL_GRACE_MS = 2_000;
|
|
|
31
31
|
|
|
32
32
|
/** Stable note prefixes — tests assert on these without depending on full stderr. */
|
|
33
33
|
export const INSANE_NOTES = {
|
|
34
|
+
securityDisabled: `insane fallback disabled: remote renderer cannot preserve validated network routing`,
|
|
34
35
|
guardBlocked: (reason: string) => `insane fallback blocked: target URL is not public HTTP(S): ${reason}`,
|
|
35
36
|
vendorMissing: `insane fallback unavailable: vendor engine missing at packages/coding-agent/vendor/insane-search`,
|
|
36
37
|
noPython: `insane fallback unavailable: python3 not found; install python3 and curl_cffi, then retry with web.insaneFallback=true`,
|
|
@@ -325,8 +326,11 @@ function mapEngineOutput(raw: EngineRawOutput, timeoutMs: number): InsaneBridgeR
|
|
|
325
326
|
* (which MUST run before this is called). Never throws; always returns a result.
|
|
326
327
|
*/
|
|
327
328
|
export async function tryInsaneFetch(url: string, options: TryInsaneFetchOptions = {}): Promise<InsaneBridgeResult> {
|
|
329
|
+
if (!options.runner) {
|
|
330
|
+
return { ok: false, reason: "disabled-security", notes: [INSANE_NOTES.securityDisabled] };
|
|
331
|
+
}
|
|
328
332
|
const prober = options.prober ?? probeInsaneDependencies;
|
|
329
|
-
const runner = options.runner
|
|
333
|
+
const runner = options.runner;
|
|
330
334
|
const limit = options.concurrencyLimit ?? DEFAULT_CONCURRENCY;
|
|
331
335
|
|
|
332
336
|
const deps = await prober();
|