@sayknow-cli/coding-agent 0.3.13 → 0.3.15
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/dist/types/append-only-mode.d.ts +31 -0
- package/dist/types/async/index.d.ts +2 -0
- package/dist/types/async/job-manager.d.ts +328 -0
- package/dist/types/async/support.d.ts +2 -0
- package/dist/types/autoresearch/dashboard.d.ts +4 -0
- package/dist/types/autoresearch/git.d.ts +36 -0
- package/dist/types/autoresearch/helpers.d.ts +24 -0
- package/dist/types/autoresearch/index.d.ts +2 -0
- package/dist/types/autoresearch/state.d.ts +17 -0
- package/dist/types/autoresearch/storage.d.ts +142 -0
- package/dist/types/autoresearch/tools/init-experiment.d.ts +31 -0
- package/dist/types/autoresearch/tools/log-experiment.d.ts +23 -0
- package/dist/types/autoresearch/tools/run-experiment.d.ts +8 -0
- package/dist/types/autoresearch/tools/update-notes.d.ts +12 -0
- package/dist/types/autoresearch/types.d.ts +154 -0
- package/dist/types/build-metadata.d.ts +7 -0
- package/dist/types/capability/context-file.d.ts +17 -0
- package/dist/types/capability/extension-module.d.ts +15 -0
- package/dist/types/capability/extension.d.ts +28 -0
- package/dist/types/capability/fs.d.ts +20 -0
- package/dist/types/capability/hook.d.ts +19 -0
- package/dist/types/capability/index.d.ts +80 -0
- package/dist/types/capability/instruction.d.ts +17 -0
- package/dist/types/capability/mcp.d.ts +49 -0
- package/dist/types/capability/prompt.d.ts +15 -0
- package/dist/types/capability/rule.d.ts +69 -0
- package/dist/types/capability/settings.d.ts +15 -0
- package/dist/types/capability/skill.d.ts +36 -0
- package/dist/types/capability/slash-command.d.ts +17 -0
- package/dist/types/capability/ssh.d.ts +23 -0
- package/dist/types/capability/system-prompt.d.ts +15 -0
- package/dist/types/capability/tool.d.ts +19 -0
- package/dist/types/capability/types.d.ts +154 -0
- package/dist/types/cli/agents-cli.d.ts +12 -0
- package/dist/types/cli/args.d.ts +57 -0
- package/dist/types/cli/auth-broker-cli.d.ts +25 -0
- package/dist/types/cli/auth-gateway-cli.d.ts +18 -0
- package/dist/types/cli/classify-install-target.d.ts +8 -0
- package/dist/types/cli/commands/init-xdg.d.ts +1 -0
- package/dist/types/cli/completion-cli.d.ts +41 -0
- package/dist/types/cli/config-cli.d.ts +23 -0
- package/dist/types/cli/daemon-cli.d.ts +27 -0
- package/dist/types/cli/fast-help.d.ts +1 -0
- package/dist/types/cli/file-processor.d.ts +11 -0
- package/dist/types/cli/fixture-report.d.ts +20 -0
- package/dist/types/cli/fixture-report.test.d.ts +1 -0
- package/dist/types/cli/grep-cli.d.ts +17 -0
- package/dist/types/cli/initial-message.d.ts +17 -0
- package/dist/types/cli/list-models.d.ts +36 -0
- package/dist/types/cli/local-provider-smoke.d.ts +59 -0
- package/dist/types/cli/malloc-env-guard.d.ts +65 -0
- package/dist/types/cli/malloc-env-guard.test.d.ts +1 -0
- package/dist/types/cli/mcp-cli.d.ts +25 -0
- package/dist/types/cli/migrate-cli.d.ts +20 -0
- package/dist/types/cli/nofile-limit.d.ts +11 -0
- package/dist/types/cli/notify-cli.d.ts +39 -0
- package/dist/types/cli/plugin-cli.d.ts +34 -0
- package/dist/types/cli/read-cli.d.ts +4 -0
- package/dist/types/cli/session-picker.d.ts +4 -0
- package/dist/types/cli/setup-cli.d.ts +73 -0
- package/dist/types/cli/shell-cli.d.ts +8 -0
- package/dist/types/cli/skills-cli.d.ts +9 -0
- package/dist/types/cli/ssh-cli.d.ts +21 -0
- package/dist/types/cli/stats-cli.d.ts +17 -0
- package/dist/types/cli/telegram-cli.d.ts +13 -0
- package/dist/types/cli/update-cli.d.ts +108 -0
- package/dist/types/cli/web-search-cli.d.ts +32 -0
- package/dist/types/cli/worktree-cli.d.ts +26 -0
- package/dist/types/cli.d.ts +140 -0
- package/dist/types/commands/acp.d.ts +12 -0
- package/dist/types/commands/agents.d.ts +34 -0
- package/dist/types/commands/auth-broker.d.ts +54 -0
- package/dist/types/commands/auth-gateway.d.ts +32 -0
- package/dist/types/commands/codex-native-hook.d.ts +6 -0
- package/dist/types/commands/commit.d.ts +27 -0
- package/dist/types/commands/completion.d.ts +29 -0
- package/dist/types/commands/config.d.ts +33 -0
- package/dist/types/commands/contribution-prep.d.ts +18 -0
- package/dist/types/commands/coordinator.d.ts +19 -0
- package/dist/types/commands/daemon.d.ts +45 -0
- package/dist/types/commands/deep-interview.d.ts +54 -0
- package/dist/types/commands/gc.d.ts +26 -0
- package/dist/types/commands/grep.d.ts +42 -0
- package/dist/types/commands/harness.d.ts +43 -0
- package/dist/types/commands/launch.d.ts +131 -0
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/commands/mcp-serve.d.ts +24 -0
- package/dist/types/commands/mcp.d.ts +70 -0
- package/dist/types/commands/migrate.d.ts +33 -0
- package/dist/types/commands/notify.d.ts +47 -0
- package/dist/types/commands/plugin.d.ts +58 -0
- package/dist/types/commands/ralplan.d.ts +7 -0
- package/dist/types/commands/read.d.ts +15 -0
- package/dist/types/commands/rlm.d.ts +10 -0
- package/dist/types/commands/session.d.ts +30 -0
- package/dist/types/commands/setup.d.ts +105 -0
- package/dist/types/commands/shell.d.ts +21 -0
- package/dist/types/commands/skills.d.ts +26 -0
- package/dist/types/commands/ssh.d.ts +48 -0
- package/dist/types/commands/state.d.ts +7 -0
- package/dist/types/commands/stats.d.ts +25 -0
- package/dist/types/commands/team.d.ts +24 -0
- package/dist/types/commands/telegram.d.ts +22 -0
- package/dist/types/commands/ultragoal.d.ts +8 -0
- package/dist/types/commands/update.d.ts +20 -0
- package/dist/types/commands/web-search.d.ts +87 -0
- package/dist/types/commands/worktree.d.ts +34 -0
- package/dist/types/commit/agentic/agent.d.ts +31 -0
- package/dist/types/commit/agentic/fallback.d.ts +5 -0
- package/dist/types/commit/agentic/index.d.ts +2 -0
- package/dist/types/commit/agentic/state.d.ts +58 -0
- package/dist/types/commit/agentic/tools/analyze-file.d.ts +19 -0
- package/dist/types/commit/agentic/tools/git-file-diff.d.ts +10 -0
- package/dist/types/commit/agentic/tools/git-hunk.d.ts +9 -0
- package/dist/types/commit/agentic/tools/git-overview.d.ts +9 -0
- package/dist/types/commit/agentic/tools/index.d.ts +16 -0
- package/dist/types/commit/agentic/tools/propose-changelog.d.ts +28 -0
- package/dist/types/commit/agentic/tools/propose-commit.d.ts +36 -0
- package/dist/types/commit/agentic/tools/recent-commits.d.ts +7 -0
- package/dist/types/commit/agentic/tools/schemas.d.ts +27 -0
- package/dist/types/commit/agentic/tools/split-commit.d.ts +53 -0
- package/dist/types/commit/agentic/topo-sort.d.ts +4 -0
- package/dist/types/commit/agentic/trivial.d.ts +7 -0
- package/dist/types/commit/agentic/validation.d.ts +20 -0
- package/dist/types/commit/analysis/conventional.d.ts +22 -0
- package/dist/types/commit/analysis/index.d.ts +4 -0
- package/dist/types/commit/analysis/scope.d.ts +6 -0
- package/dist/types/commit/analysis/summary.d.ts +19 -0
- package/dist/types/commit/analysis/validation.d.ts +8 -0
- package/dist/types/commit/changelog/detect.d.ts +2 -0
- package/dist/types/commit/changelog/generate.d.ts +30 -0
- package/dist/types/commit/changelog/index.d.ts +30 -0
- package/dist/types/commit/changelog/parse.d.ts +2 -0
- package/dist/types/commit/cli.d.ts +3 -0
- package/dist/types/commit/git/diff.d.ts +5 -0
- package/dist/types/commit/index.d.ts +4 -0
- package/dist/types/commit/map-reduce/index.d.ts +28 -0
- package/dist/types/commit/map-reduce/map-phase.d.ts +17 -0
- package/dist/types/commit/map-reduce/reduce-phase.d.ts +13 -0
- package/dist/types/commit/map-reduce/utils.d.ts +2 -0
- package/dist/types/commit/message.d.ts +2 -0
- package/dist/types/commit/model-selection.d.ts +15 -0
- package/dist/types/commit/pipeline.d.ts +5 -0
- package/dist/types/commit/shared-llm.d.ts +54 -0
- package/dist/types/commit/types.d.ts +78 -0
- package/dist/types/commit/utils/exclusions.d.ts +4 -0
- package/dist/types/commit/utils.d.ts +20 -0
- package/dist/types/config/config-file.d.ts +58 -0
- package/dist/types/config/file-lock-gc.d.ts +5 -0
- package/dist/types/config/file-lock.d.ts +36 -0
- package/dist/types/config/keybindings.d.ts +364 -0
- package/dist/types/config/model-equivalence.d.ts +24 -0
- package/dist/types/config/model-profile-activation.d.ts +65 -0
- package/dist/types/config/model-profiles.d.ts +39 -0
- package/dist/types/config/model-registry.d.ts +473 -0
- package/dist/types/config/model-resolver.d.ts +217 -0
- package/dist/types/config/models-config-schema.d.ts +700 -0
- package/dist/types/config/prompt-templates.d.ts +32 -0
- package/dist/types/config/resolve-config-value.d.ts +17 -0
- package/dist/types/config/settings-schema.d.ts +4223 -0
- package/dist/types/config/settings.d.ts +158 -0
- package/dist/types/config/skill-settings-defaults.d.ts +14 -0
- package/dist/types/config/telegram-autostart.d.ts +14 -0
- package/dist/types/config/telegram-env-bridge.d.ts +18 -0
- package/dist/types/config.d.ts +66 -0
- package/dist/types/coordinator/contract.d.ts +4 -0
- package/dist/types/coordinator-mcp/model-preset.d.ts +45 -0
- package/dist/types/coordinator-mcp/policy.d.ts +27 -0
- package/dist/types/coordinator-mcp/safety.d.ts +26 -0
- package/dist/types/coordinator-mcp/server.d.ts +139 -0
- package/dist/types/coordinator-mcp/session-reaper.d.ts +53 -0
- package/dist/types/cursor.d.ts +24 -0
- package/dist/types/daemon/builtin.d.ts +20 -0
- package/dist/types/daemon/control-types.d.ts +67 -0
- package/dist/types/daemon/operator-contract.d.ts +47 -0
- package/dist/types/daemon/runtime.d.ts +25 -0
- package/dist/types/dap/client.d.ts +39 -0
- package/dist/types/dap/config.d.ts +6 -0
- package/dist/types/dap/index.d.ts +4 -0
- package/dist/types/dap/session.d.ts +108 -0
- package/dist/types/dap/types.d.ts +524 -0
- package/dist/types/debug/crash-diagnostics.d.ts +45 -0
- package/dist/types/debug/index.d.ts +15 -0
- package/dist/types/debug/log-formatting.d.ts +4 -0
- package/dist/types/debug/log-viewer.d.ts +68 -0
- package/dist/types/debug/profiler.d.ts +24 -0
- package/dist/types/debug/raw-sse-buffer.d.ts +44 -0
- package/dist/types/debug/raw-sse.d.ts +16 -0
- package/dist/types/debug/report-bundle.d.ts +55 -0
- package/dist/types/debug/runtime-gauges.d.ts +6 -0
- package/dist/types/debug/system-info.d.ts +26 -0
- package/dist/types/deep-interview/plaintext-gate-guard.d.ts +11 -0
- package/dist/types/deep-interview/render-middleware.d.ts +6 -0
- package/dist/types/defaults/skc/extensions/grok-build/index.d.ts +1 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/index.d.ts +1 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/models/catalog.d.ts +29 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/payload/sanitize.d.ts +27 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/billing.d.ts +8 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/register.d.ts +5 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/stream.d.ts +10 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/provider/usage.d.ts +2 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/base-url.d.ts +2 -0
- package/dist/types/defaults/skc/extensions/grok-cli-vendor/src/shared/errors.d.ts +38 -0
- package/dist/types/defaults/skc-defaults.d.ts +64 -0
- package/dist/types/defaults/skc-grok-cli.d.ts +5 -0
- package/dist/types/discovery/agents-md.d.ts +1 -0
- package/dist/types/discovery/agents.d.ts +12 -0
- package/dist/types/discovery/builtin.d.ts +1 -0
- package/dist/types/discovery/claude-plugins.d.ts +1 -0
- package/dist/types/discovery/claude.d.ts +1 -0
- package/dist/types/discovery/cline.d.ts +1 -0
- package/dist/types/discovery/codex.d.ts +1 -0
- package/dist/types/discovery/cursor.d.ts +19 -0
- package/dist/types/discovery/gemini.d.ts +1 -0
- package/dist/types/discovery/github.d.ts +1 -0
- package/dist/types/discovery/helpers.d.ts +271 -0
- package/dist/types/discovery/index.d.ts +48 -0
- package/dist/types/discovery/mcp-json.d.ts +1 -0
- package/dist/types/discovery/opencode.d.ts +1 -0
- package/dist/types/discovery/plugin-dir-roots.d.ts +15 -0
- package/dist/types/discovery/ssh.d.ts +1 -0
- package/dist/types/discovery/substitute-plugin-root.d.ts +1 -0
- package/dist/types/discovery/vscode.d.ts +1 -0
- package/dist/types/discovery/windsurf.d.ts +15 -0
- package/dist/types/edit/apply-patch/index.d.ts +35 -0
- package/dist/types/edit/apply-patch/parser.d.ts +34 -0
- package/dist/types/edit/diff.d.ts +75 -0
- package/dist/types/edit/file-read-cache.d.ts +25 -0
- package/dist/types/edit/index.d.ts +58 -0
- package/dist/types/edit/modes/apply-patch.d.ts +24 -0
- package/dist/types/edit/modes/patch.d.ts +99 -0
- package/dist/types/edit/modes/replace.d.ts +149 -0
- package/dist/types/edit/normalize.d.ts +43 -0
- package/dist/types/edit/notebook.d.ts +26 -0
- package/dist/types/edit/read-file.d.ts +8 -0
- package/dist/types/edit/renderer.d.ts +110 -0
- package/dist/types/edit/streaming.d.ts +66 -0
- package/dist/types/eval/backend.d.ts +40 -0
- package/dist/types/eval/index.d.ts +4 -0
- package/dist/types/eval/js/context-manager.d.ts +27 -0
- package/dist/types/eval/js/executor.d.ts +29 -0
- package/dist/types/eval/js/index.d.ts +10 -0
- package/dist/types/eval/js/shared/helpers.d.ts +38 -0
- package/dist/types/eval/js/shared/indirect-eval.d.ts +14 -0
- package/dist/types/eval/js/shared/prelude.d.ts +1 -0
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -0
- package/dist/types/eval/js/shared/runtime.d.ts +47 -0
- package/dist/types/eval/js/shared/types.d.ts +24 -0
- package/dist/types/eval/js/tool-bridge.d.ts +18 -0
- package/dist/types/eval/js/worker-core.d.ts +5 -0
- package/dist/types/eval/js/worker-entry.d.ts +1 -0
- package/dist/types/eval/js/worker-protocol.d.ts +77 -0
- package/dist/types/eval/py/display.d.ts +25 -0
- package/dist/types/eval/py/executor.d.ts +88 -0
- package/dist/types/eval/py/index.d.ts +10 -0
- package/dist/types/eval/py/kernel.d.ts +65 -0
- package/dist/types/eval/py/prelude.d.ts +1 -0
- package/dist/types/eval/py/runtime.d.ts +29 -0
- package/dist/types/eval/py/tool-bridge.d.ts +20 -0
- package/dist/types/eval/types.d.ts +52 -0
- package/dist/types/exa/factory.d.ts +13 -0
- package/dist/types/exa/index.d.ts +20 -0
- package/dist/types/exa/mcp-client.d.ts +45 -0
- package/dist/types/exa/render.d.ts +19 -0
- package/dist/types/exa/researcher.d.ts +9 -0
- package/dist/types/exa/search.d.ts +9 -0
- package/dist/types/exa/types.d.ts +155 -0
- package/dist/types/exa/websets.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +64 -0
- package/dist/types/exec/exec.d.ts +25 -0
- package/dist/types/exec/idle-timeout-watchdog.d.ts +18 -0
- package/dist/types/exec/non-interactive-env.d.ts +1 -0
- package/dist/types/export/custom-share.d.ts +20 -0
- package/dist/types/export/html/index.d.ts +10 -0
- package/dist/types/export/html/template.generated.d.ts +1 -0
- package/dist/types/export/html/template.macro.d.ts +5 -0
- package/dist/types/export/ttsr.d.ts +55 -0
- package/dist/types/extensibility/custom-commands/bundled/ci-green/index.d.ts +9 -0
- package/dist/types/extensibility/custom-commands/index.d.ts +2 -0
- package/dist/types/extensibility/custom-commands/loader.d.ts +29 -0
- package/dist/types/extensibility/custom-commands/types.d.ts +106 -0
- package/dist/types/extensibility/custom-tools/index.d.ts +6 -0
- package/dist/types/extensibility/custom-tools/loader.d.ts +69 -0
- package/dist/types/extensibility/custom-tools/types.d.ts +229 -0
- package/dist/types/extensibility/custom-tools/wrapper.d.ts +24 -0
- package/dist/types/extensibility/extensions/compact-handler.d.ts +26 -0
- package/dist/types/extensibility/extensions/get-commands-handler.d.ts +29 -0
- package/dist/types/extensibility/extensions/index.d.ts +9 -0
- package/dist/types/extensibility/extensions/loader.d.ts +43 -0
- package/dist/types/extensibility/extensions/prefix-command-bridge.d.ts +35 -0
- package/dist/types/extensibility/extensions/runner.d.ts +135 -0
- package/dist/types/extensibility/extensions/types.d.ts +920 -0
- package/dist/types/extensibility/extensions/wrapper.d.ts +55 -0
- package/dist/types/extensibility/hooks/index.d.ts +5 -0
- package/dist/types/extensibility/hooks/loader.d.ts +89 -0
- package/dist/types/extensibility/hooks/runner.d.ts +126 -0
- package/dist/types/extensibility/hooks/tool-wrapper.d.ts +25 -0
- package/dist/types/extensibility/hooks/types.d.ts +431 -0
- package/dist/types/extensibility/plugins/doctor.d.ts +3 -0
- package/dist/types/extensibility/plugins/git-url.d.ts +34 -0
- package/dist/types/extensibility/plugins/index.d.ts +8 -0
- package/dist/types/extensibility/plugins/installer.d.ts +5 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +2 -0
- package/dist/types/extensibility/plugins/loader.d.ts +31 -0
- package/dist/types/extensibility/plugins/manager.d.ts +65 -0
- package/dist/types/extensibility/plugins/marketplace/cache.d.ts +41 -0
- package/dist/types/extensibility/plugins/marketplace/fetcher.d.ts +48 -0
- package/dist/types/extensibility/plugins/marketplace/index.d.ts +6 -0
- package/dist/types/extensibility/plugins/marketplace/manager.d.ts +57 -0
- package/dist/types/extensibility/plugins/marketplace/registry.d.ts +34 -0
- package/dist/types/extensibility/plugins/marketplace/source-resolver.d.ts +28 -0
- package/dist/types/extensibility/plugins/marketplace/types.d.ts +130 -0
- package/dist/types/extensibility/plugins/parser.d.ts +52 -0
- package/dist/types/extensibility/plugins/security-scanner.d.ts +31 -0
- package/dist/types/extensibility/plugins/types.d.ts +149 -0
- package/dist/types/extensibility/runtime-skill-discovery.d.ts +20 -0
- package/dist/types/extensibility/shared-events.d.ts +281 -0
- package/dist/types/extensibility/skc-plugins/activation.d.ts +14 -0
- package/dist/types/extensibility/skc-plugins/compiler.d.ts +19 -0
- package/dist/types/extensibility/skc-plugins/constrained-hooks.d.ts +29 -0
- package/dist/types/extensibility/skc-plugins/index.d.ts +18 -0
- package/dist/types/extensibility/skc-plugins/injection.d.ts +40 -0
- package/dist/types/extensibility/skc-plugins/installer.d.ts +13 -0
- package/dist/types/extensibility/skc-plugins/loader.d.ts +3 -0
- package/dist/types/extensibility/skc-plugins/mcp-policy.d.ts +26 -0
- package/dist/types/extensibility/skc-plugins/observability.d.ts +27 -0
- package/dist/types/extensibility/skc-plugins/paths.d.ts +8 -0
- package/dist/types/extensibility/skc-plugins/prompt-appendix.d.ts +16 -0
- package/dist/types/extensibility/skc-plugins/registry.d.ts +32 -0
- package/dist/types/extensibility/skc-plugins/runtime-adapters.d.ts +64 -0
- package/dist/types/extensibility/skc-plugins/schema.d.ts +3 -0
- package/dist/types/extensibility/skc-plugins/session-validation.d.ts +42 -0
- package/dist/types/extensibility/skc-plugins/state.d.ts +9 -0
- package/dist/types/extensibility/skc-plugins/tools.d.ts +8 -0
- package/dist/types/extensibility/skc-plugins/types.d.ts +220 -0
- package/dist/types/extensibility/skc-plugins/validation.d.ts +11 -0
- package/dist/types/extensibility/skills.d.ts +82 -0
- package/dist/types/extensibility/slash-commands.d.ts +48 -0
- package/dist/types/extensibility/tool-proxy.d.ts +4 -0
- package/dist/types/extensibility/typebox.d.ts +155 -0
- package/dist/types/extensibility/utils.d.ts +12 -0
- package/dist/types/goals/index.d.ts +3 -0
- package/dist/types/goals/runtime.d.ts +63 -0
- package/dist/types/goals/state.d.ts +32 -0
- package/dist/types/goals/tools/goal-tool.d.ts +62 -0
- package/dist/types/harness-control-plane/classifier.d.ts +13 -0
- package/dist/types/harness-control-plane/control-endpoint.d.ts +31 -0
- package/dist/types/harness-control-plane/finalize.d.ts +60 -0
- package/dist/types/harness-control-plane/gc-adapter.d.ts +3 -0
- package/dist/types/harness-control-plane/operate.d.ts +35 -0
- package/dist/types/harness-control-plane/owner.d.ts +53 -0
- package/dist/types/harness-control-plane/phase-rollup.d.ts +23 -0
- package/dist/types/harness-control-plane/preserve.d.ts +19 -0
- package/dist/types/harness-control-plane/receipt-ingest.d.ts +19 -0
- package/dist/types/harness-control-plane/receipt-spool.d.ts +19 -0
- package/dist/types/harness-control-plane/receipts.d.ts +149 -0
- package/dist/types/harness-control-plane/rpc-adapter.d.ts +69 -0
- package/dist/types/harness-control-plane/seams.d.ts +21 -0
- package/dist/types/harness-control-plane/session-lease.d.ts +65 -0
- package/dist/types/harness-control-plane/state-machine.d.ts +24 -0
- package/dist/types/harness-control-plane/storage.d.ts +81 -0
- package/dist/types/harness-control-plane/types.d.ts +187 -0
- package/dist/types/hashline/anchors.d.ts +20 -0
- package/dist/types/hashline/apply.d.ts +14 -0
- package/dist/types/hashline/constants.d.ts +19 -0
- package/dist/types/hashline/diff-preview.d.ts +2 -0
- package/dist/types/hashline/diff.d.ts +17 -0
- package/dist/types/hashline/execute.d.ts +4 -0
- package/dist/types/hashline/hash.d.ts +123 -0
- package/dist/types/hashline/index.d.ts +13 -0
- package/dist/types/hashline/input.d.ts +11 -0
- package/dist/types/hashline/parser.d.ts +7 -0
- package/dist/types/hashline/prefixes.d.ts +7 -0
- package/dist/types/hashline/recovery.d.ts +27 -0
- package/dist/types/hashline/stream.d.ts +2 -0
- package/dist/types/hashline/types.d.ts +77 -0
- package/dist/types/hindsight/backend.d.ts +13 -0
- package/dist/types/hindsight/bank.d.ts +54 -0
- package/dist/types/hindsight/client.d.ts +224 -0
- package/dist/types/hindsight/config.d.ts +51 -0
- package/dist/types/hindsight/content.d.ts +70 -0
- package/dist/types/hindsight/index.d.ts +8 -0
- package/dist/types/hindsight/mental-models.d.ts +125 -0
- package/dist/types/hindsight/state.d.ts +99 -0
- package/dist/types/hindsight/transcript.d.ts +28 -0
- package/dist/types/hooks/codex-native-hooks-config.d.ts +29 -0
- package/dist/types/hooks/native-skill-hook.d.ts +28 -0
- package/dist/types/hooks/skill-keywords.d.ts +18 -0
- package/dist/types/hooks/skill-state.d.ts +93 -0
- package/dist/types/i18n/index.d.ts +38 -0
- package/dist/types/i18n/messages/de.d.ts +2 -0
- package/dist/types/i18n/messages/de.messages.d.ts +1 -0
- package/dist/types/i18n/messages/de.settings.d.ts +1 -0
- package/dist/types/i18n/messages/en.d.ts +81 -0
- package/dist/types/i18n/messages/es.d.ts +2 -0
- package/dist/types/i18n/messages/es.messages.d.ts +1 -0
- package/dist/types/i18n/messages/es.settings.d.ts +1 -0
- package/dist/types/i18n/messages/fr.d.ts +2 -0
- package/dist/types/i18n/messages/fr.messages.d.ts +1 -0
- package/dist/types/i18n/messages/fr.settings.d.ts +1 -0
- package/dist/types/i18n/messages/ja.d.ts +2 -0
- package/dist/types/i18n/messages/ja.messages.d.ts +1 -0
- package/dist/types/i18n/messages/ja.settings.d.ts +1 -0
- package/dist/types/i18n/messages/ko.d.ts +2 -0
- package/dist/types/i18n/messages/ko.messages.d.ts +1 -0
- package/dist/types/i18n/messages/ko.settings.d.ts +1 -0
- package/dist/types/i18n/messages/zh.d.ts +2 -0
- package/dist/types/i18n/messages/zh.messages.d.ts +1 -0
- package/dist/types/i18n/messages/zh.settings.d.ts +1 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/internal-urls/agent-protocol.d.ts +12 -0
- package/dist/types/internal-urls/artifact-protocol.d.ts +6 -0
- package/dist/types/internal-urls/docs-index.generated.d.ts +2 -0
- package/dist/types/internal-urls/index.d.ts +20 -0
- package/dist/types/internal-urls/issue-pr-protocol.d.ts +17 -0
- package/dist/types/internal-urls/json-query.d.ts +30 -0
- package/dist/types/internal-urls/local-protocol.d.ts +43 -0
- package/dist/types/internal-urls/mcp-protocol.d.ts +12 -0
- package/dist/types/internal-urls/memory-protocol.d.ts +23 -0
- package/dist/types/internal-urls/parse.d.ts +19 -0
- package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
- package/dist/types/internal-urls/router.d.ts +14 -0
- package/dist/types/internal-urls/rule-protocol.d.ts +6 -0
- package/dist/types/internal-urls/skc-protocol.d.ts +12 -0
- package/dist/types/internal-urls/skill-protocol.d.ts +13 -0
- package/dist/types/internal-urls/types.d.ts +109 -0
- package/dist/types/lsp/client.d.ts +66 -0
- package/dist/types/lsp/clients/biome-client.d.ts +16 -0
- package/dist/types/lsp/clients/index.d.ts +19 -0
- package/dist/types/lsp/clients/lsp-linter-client.d.ts +16 -0
- package/dist/types/lsp/clients/swiftlint-client.d.ts +20 -0
- package/dist/types/lsp/config.d.ts +65 -0
- package/dist/types/lsp/edits.d.ts +23 -0
- package/dist/types/lsp/index.d.ts +130 -0
- package/dist/types/lsp/lspmux.d.ts +58 -0
- package/dist/types/lsp/render.d.ts +32 -0
- package/dist/types/lsp/startup-events.d.ts +12 -0
- package/dist/types/lsp/types.d.ts +306 -0
- package/dist/types/lsp/utils.d.ts +108 -0
- package/dist/types/main.d.ts +139 -0
- package/dist/types/memories/index.d.ts +28 -0
- package/dist/types/memories/storage.d.ts +110 -0
- package/dist/types/memory-backend/index.d.ts +4 -0
- package/dist/types/memory-backend/local-backend.d.ts +9 -0
- package/dist/types/memory-backend/off-backend.d.ts +7 -0
- package/dist/types/memory-backend/resolve.d.ts +15 -0
- package/dist/types/memory-backend/types.d.ts +61 -0
- package/dist/types/migrate/action-planner.d.ts +11 -0
- package/dist/types/migrate/adapters/claude-code.d.ts +2 -0
- package/dist/types/migrate/adapters/codex.d.ts +5 -0
- package/dist/types/migrate/adapters/index.d.ts +45 -0
- package/dist/types/migrate/adapters/opencode.d.ts +2 -0
- package/dist/types/migrate/executor.d.ts +2 -0
- package/dist/types/migrate/mcp-mapper.d.ts +20 -0
- package/dist/types/migrate/report.d.ts +18 -0
- package/dist/types/migrate/skill-normalizer.d.ts +27 -0
- package/dist/types/migrate/types.d.ts +126 -0
- package/dist/types/modes/acp/acp-agent.d.ts +78 -0
- package/dist/types/modes/acp/acp-client-bridge.d.ts +9 -0
- package/dist/types/modes/acp/acp-event-mapper.d.ts +36 -0
- package/dist/types/modes/acp/acp-mode.d.ts +17 -0
- package/dist/types/modes/acp/index.d.ts +2 -0
- package/dist/types/modes/acp/terminal-auth.d.ts +6 -0
- package/dist/types/modes/bridge/auth.d.ts +12 -0
- package/dist/types/modes/bridge/bridge-client-bridge.d.ts +9 -0
- package/dist/types/modes/bridge/bridge-mode.d.ts +47 -0
- package/dist/types/modes/bridge/bridge-ui-context.d.ts +88 -0
- package/dist/types/modes/bridge/event-stream.d.ts +8 -0
- package/dist/types/modes/components/agent-dashboard.d.ts +21 -0
- package/dist/types/modes/components/assistant-message.d.ts +20 -0
- package/dist/types/modes/components/bash-execution.d.ts +29 -0
- package/dist/types/modes/components/bordered-loader.d.ts +11 -0
- package/dist/types/modes/components/branch-summary-message.d.ts +13 -0
- package/dist/types/modes/components/btw-panel.d.ts +16 -0
- package/dist/types/modes/components/compaction-summary-message.d.ts +13 -0
- package/dist/types/modes/components/countdown-timer.d.ts +14 -0
- package/dist/types/modes/components/custom-editor.d.ts +69 -0
- package/dist/types/modes/components/custom-message.d.ts +15 -0
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +12 -0
- package/dist/types/modes/components/custom-provider-wizard.d.ts +10 -0
- package/dist/types/modes/components/diff.d.ts +11 -0
- package/dist/types/modes/components/dynamic-border.d.ts +14 -0
- package/dist/types/modes/components/eval-execution.d.ts +23 -0
- package/dist/types/modes/components/execution-shared.d.ts +46 -0
- package/dist/types/modes/components/extensions/extension-dashboard.d.ts +26 -0
- package/dist/types/modes/components/extensions/extension-list.d.ts +39 -0
- package/dist/types/modes/components/extensions/index.d.ts +8 -0
- package/dist/types/modes/components/extensions/inspector-panel.d.ts +8 -0
- package/dist/types/modes/components/extensions/state-manager.d.ts +50 -0
- package/dist/types/modes/components/extensions/types.d.ts +151 -0
- package/dist/types/modes/components/footer.d.ts +30 -0
- package/dist/types/modes/components/history-search.d.ts +7 -0
- package/dist/types/modes/components/hook-editor.d.ts +18 -0
- package/dist/types/modes/components/hook-input.d.ts +15 -0
- package/dist/types/modes/components/hook-message.d.ts +15 -0
- package/dist/types/modes/components/hook-selector.d.ts +59 -0
- package/dist/types/modes/components/index.d.ts +35 -0
- package/dist/types/modes/components/irc-sidebar.d.ts +32 -0
- package/dist/types/modes/components/jobs-overlay-model.d.ts +31 -0
- package/dist/types/modes/components/jobs-overlay.d.ts +30 -0
- package/dist/types/modes/components/keybinding-hints.d.ts +40 -0
- package/dist/types/modes/components/login-dialog.d.ts +32 -0
- package/dist/types/modes/components/message-frame.d.ts +42 -0
- package/dist/types/modes/components/model-selector.d.ts +60 -0
- package/dist/types/modes/components/oauth-selector.d.ts +17 -0
- package/dist/types/modes/components/pet-selector.d.ts +11 -0
- package/dist/types/modes/components/plugin-selector.d.ts +26 -0
- package/dist/types/modes/components/plugin-settings.d.ts +66 -0
- package/dist/types/modes/components/provider-onboarding-selector.d.ts +7 -0
- package/dist/types/modes/components/queue-mode-selector.d.ts +9 -0
- package/dist/types/modes/components/queued-message-selector.d.ts +12 -0
- package/dist/types/modes/components/read-tool-group.d.ts +30 -0
- package/dist/types/modes/components/runtime-mcp-add-wizard.d.ts +26 -0
- package/dist/types/modes/components/sayknow-pet-widget.d.ts +59 -0
- package/dist/types/modes/components/session-observer-overlay.d.ts +11 -0
- package/dist/types/modes/components/session-selector.d.ts +43 -0
- package/dist/types/modes/components/settings-defs.d.ts +50 -0
- package/dist/types/modes/components/settings-selector.d.ts +64 -0
- package/dist/types/modes/components/show-images-selector.d.ts +9 -0
- package/dist/types/modes/components/skill-hud/render.d.ts +2 -0
- package/dist/types/modes/components/skill-message.d.ts +10 -0
- package/dist/types/modes/components/status-line/context-thresholds.d.ts +4 -0
- package/dist/types/modes/components/status-line/git-utils.d.ts +28 -0
- package/dist/types/modes/components/status-line/index.d.ts +4 -0
- package/dist/types/modes/components/status-line/presets.d.ts +3 -0
- package/dist/types/modes/components/status-line/segments.d.ts +5 -0
- package/dist/types/modes/components/status-line/separators.d.ts +3 -0
- package/dist/types/modes/components/status-line/token-rate.d.ts +10 -0
- package/dist/types/modes/components/status-line/types.d.ts +83 -0
- package/dist/types/modes/components/status-line.d.ts +94 -0
- package/dist/types/modes/components/theme-selector.d.ts +10 -0
- package/dist/types/modes/components/thinking-selector.d.ts +15 -0
- package/dist/types/modes/components/todo-reminder.d.ts +13 -0
- package/dist/types/modes/components/tool-execution.d.ts +55 -0
- package/dist/types/modes/components/tree-selector.d.ts +31 -0
- package/dist/types/modes/components/ttsr-notification.d.ts +13 -0
- package/dist/types/modes/components/two-column-body.d.ts +9 -0
- package/dist/types/modes/components/user-message-selector.d.ts +28 -0
- package/dist/types/modes/components/user-message.d.ts +8 -0
- package/dist/types/modes/components/visual-truncate.d.ts +19 -0
- package/dist/types/modes/components/welcome.d.ts +46 -0
- package/dist/types/modes/controllers/btw-controller.d.ts +10 -0
- package/dist/types/modes/controllers/command-controller-shared.d.ts +47 -0
- package/dist/types/modes/controllers/command-controller.d.ts +40 -0
- package/dist/types/modes/controllers/event-controller.d.ts +21 -0
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +80 -0
- package/dist/types/modes/controllers/input-controller.d.ts +42 -0
- package/dist/types/modes/controllers/runtime-mcp-command-controller.d.ts +10 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +68 -0
- package/dist/types/modes/controllers/ssh-command-controller.d.ts +10 -0
- package/dist/types/modes/controllers/todo-command-controller.d.ts +7 -0
- package/dist/types/modes/emoji-autocomplete.d.ts +16 -0
- package/dist/types/modes/index.d.ts +10 -0
- package/dist/types/modes/interactive-mode.d.ts +297 -0
- package/dist/types/modes/irc-observation-ledger.d.ts +22 -0
- package/dist/types/modes/jobs-observer.d.ts +57 -0
- package/dist/types/modes/oauth-manual-input.d.ts +8 -0
- package/dist/types/modes/print-mode.d.ts +44 -0
- package/dist/types/modes/prompt-action-autocomplete.d.ts +50 -0
- package/dist/types/modes/rpc/host-tools.d.ts +1 -0
- package/dist/types/modes/rpc/host-uris.d.ts +1 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +299 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +86 -0
- package/dist/types/modes/rpc/rpc-socket-security.d.ts +10 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +810 -0
- package/dist/types/modes/runtime-init.d.ts +21 -0
- package/dist/types/modes/session-observer-registry.d.ts +26 -0
- package/dist/types/modes/shared/agent-wire/approval-gate.d.ts +57 -0
- package/dist/types/modes/shared/agent-wire/command-contract.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/command-dispatch.d.ts +37 -0
- package/dist/types/modes/shared/agent-wire/command-validation.d.ts +2 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +102 -0
- package/dist/types/modes/shared/agent-wire/event-contract.d.ts +103 -0
- package/dist/types/modes/shared/agent-wire/event-envelope.d.ts +60 -0
- package/dist/types/modes/shared/agent-wire/event-observation.d.ts +37 -0
- package/dist/types/modes/shared/agent-wire/handshake.d.ts +56 -0
- package/dist/types/modes/shared/agent-wire/host-tool-bridge.d.ts +16 -0
- package/dist/types/modes/shared/agent-wire/host-uri-bridge.d.ts +17 -0
- package/dist/types/modes/shared/agent-wire/protocol.d.ts +25 -0
- package/dist/types/modes/shared/agent-wire/responses.d.ts +4 -0
- package/dist/types/modes/shared/agent-wire/scopes.d.ts +18 -0
- package/dist/types/modes/shared/agent-wire/session-registry.d.ts +25 -0
- package/dist/types/modes/shared/agent-wire/ui-request-broker.d.ts +42 -0
- package/dist/types/modes/shared/agent-wire/ui-result.d.ts +27 -0
- package/dist/types/modes/shared/agent-wire/unattended-action-policy.d.ts +29 -0
- package/dist/types/modes/shared/agent-wire/unattended-audit.d.ts +68 -0
- package/dist/types/modes/shared/agent-wire/unattended-run-controller.d.ts +166 -0
- package/dist/types/modes/shared/agent-wire/unattended-session.d.ts +127 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-broker.d.ts +154 -0
- package/dist/types/modes/shared/agent-wire/workflow-gate-schema.d.ts +39 -0
- package/dist/types/modes/shared.d.ts +15 -0
- package/dist/types/modes/slash-command-visibility.d.ts +1 -0
- package/dist/types/modes/theme/defaults/index.d.ts +652 -0
- package/dist/types/modes/theme/mermaid-cache.d.ts +9 -0
- package/dist/types/modes/theme/shimmer.d.ts +38 -0
- package/dist/types/modes/theme/theme.d.ts +275 -0
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/types.d.ts +318 -0
- package/dist/types/modes/utils/abort-message.d.ts +4 -0
- package/dist/types/modes/utils/context-usage.d.ts +36 -0
- package/dist/types/modes/utils/hotkeys-markdown.d.ts +5 -0
- package/dist/types/modes/utils/injected-user-submission.d.ts +52 -0
- package/dist/types/modes/utils/irc-message.d.ts +38 -0
- package/dist/types/modes/utils/keybinding-matchers.d.ts +10 -0
- package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
- package/dist/types/modes/utils/tools-markdown.d.ts +5 -0
- package/dist/types/modes/utils/ui-helpers.d.ts +75 -0
- package/dist/types/notifications/attachment-registry.d.ts +17 -0
- package/dist/types/notifications/chat-adapters.d.ts +9 -0
- package/dist/types/notifications/config-commands.d.ts +64 -0
- package/dist/types/notifications/config.d.ts +132 -0
- package/dist/types/notifications/daemon-paths.d.ts +17 -0
- package/dist/types/notifications/engine.d.ts +59 -0
- package/dist/types/notifications/helpers.d.ts +55 -0
- package/dist/types/notifications/html-format.d.ts +75 -0
- package/dist/types/notifications/index.d.ts +219 -0
- package/dist/types/notifications/lifecycle-commands.d.ts +69 -0
- package/dist/types/notifications/lifecycle-control-runtime.d.ts +107 -0
- package/dist/types/notifications/lifecycle-orchestrator.d.ts +144 -0
- package/dist/types/notifications/managed-daemon.d.ts +48 -0
- package/dist/types/notifications/notification-service.d.ts +144 -0
- package/dist/types/notifications/operator-runtime.d.ts +52 -0
- package/dist/types/notifications/rate-limit-pool.d.ts +113 -0
- package/dist/types/notifications/recent-activity.d.ts +39 -0
- package/dist/types/notifications/reply-sent-store.d.ts +53 -0
- package/dist/types/notifications/rich-draft.d.ts +68 -0
- package/dist/types/notifications/rich-render.d.ts +90 -0
- package/dist/types/notifications/telegram-cli.d.ts +19 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +28 -0
- package/dist/types/notifications/telegram-daemon-control.d.ts +70 -0
- package/dist/types/notifications/telegram-daemon.d.ts +558 -0
- package/dist/types/notifications/telegram-reference.d.ts +136 -0
- package/dist/types/notifications/threaded-inbound.d.ts +77 -0
- package/dist/types/notifications/threaded-render.d.ts +88 -0
- package/dist/types/notifications/topic-registry.d.ts +89 -0
- package/dist/types/plan-mode/approved-plan.d.ts +49 -0
- package/dist/types/plan-mode/state.d.ts +6 -0
- package/dist/types/registry/agent-registry.d.ts +64 -0
- package/dist/types/reminders/star-reminder.d.ts +115 -0
- package/dist/types/research-plan/index.d.ts +1 -0
- package/dist/types/research-plan/ledger.d.ts +33 -0
- package/dist/types/rlm/artifacts.d.ts +9 -0
- package/dist/types/rlm/complete-research-tool.d.ts +35 -0
- package/dist/types/rlm/data-context.d.ts +6 -0
- package/dist/types/rlm/index.d.ts +47 -0
- package/dist/types/rlm/notebook.d.ts +12 -0
- package/dist/types/rlm/preset.d.ts +23 -0
- package/dist/types/rlm/python-tool.d.ts +16 -0
- package/dist/types/rlm/report.d.ts +14 -0
- package/dist/types/rlm/types.d.ts +37 -0
- package/dist/types/runtime/process-lifecycle.d.ts +108 -0
- package/dist/types/runtime-api-key.d.ts +7 -0
- package/dist/types/runtime-credential-selector.d.ts +7 -0
- package/dist/types/runtime-mcp/client.d.ts +74 -0
- package/dist/types/runtime-mcp/config-writer.d.ts +89 -0
- package/dist/types/runtime-mcp/config.d.ts +77 -0
- package/dist/types/runtime-mcp/discoverable-tool-metadata.d.ts +7 -0
- package/dist/types/runtime-mcp/index.d.ts +18 -0
- package/dist/types/runtime-mcp/json-rpc.d.ts +22 -0
- package/dist/types/runtime-mcp/loader.d.ts +45 -0
- package/dist/types/runtime-mcp/manager.d.ts +195 -0
- package/dist/types/runtime-mcp/oauth-discovery.d.ts +40 -0
- package/dist/types/runtime-mcp/oauth-flow.d.ts +59 -0
- package/dist/types/runtime-mcp/render.d.ts +25 -0
- package/dist/types/runtime-mcp/smithery-auth.d.ts +16 -0
- package/dist/types/runtime-mcp/smithery-connect.d.ts +38 -0
- package/dist/types/runtime-mcp/smithery-registry.d.ts +51 -0
- package/dist/types/runtime-mcp/tool-bridge.d.ts +86 -0
- package/dist/types/runtime-mcp/tool-cache.d.ts +8 -0
- package/dist/types/runtime-mcp/transports/http.d.ts +36 -0
- package/dist/types/runtime-mcp/transports/index.d.ts +5 -0
- package/dist/types/runtime-mcp/transports/stdio.d.ts +29 -0
- package/dist/types/runtime-mcp/types.d.ts +349 -0
- package/dist/types/sdk.d.ts +256 -0
- package/dist/types/secrets/index.d.ts +9 -0
- package/dist/types/secrets/obfuscator.d.ts +23 -0
- package/dist/types/secrets/regex.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +1238 -0
- package/dist/types/session/agent-storage.d.ts +101 -0
- package/dist/types/session/artifacts.d.ts +64 -0
- package/dist/types/session/auth-broker-config.d.ts +13 -0
- package/dist/types/session/auth-storage.d.ts +6 -0
- package/dist/types/session/blob-store.d.ts +160 -0
- package/dist/types/session/cache-economics.d.ts +79 -0
- package/dist/types/session/client-bridge.d.ts +88 -0
- package/dist/types/session/context-estimation.d.ts +31 -0
- package/dist/types/session/contribution-prep.d.ts +47 -0
- package/dist/types/session/file-mention-pruning.d.ts +8 -0
- package/dist/types/session/history-storage.d.ts +16 -0
- package/dist/types/session/messages.d.ts +169 -0
- package/dist/types/session/provider-safety-stop.d.ts +5 -0
- package/dist/types/session/session-dump-format.d.ts +22 -0
- package/dist/types/session/session-manager.d.ts +781 -0
- package/dist/types/session/session-storage.d.ts +230 -0
- package/dist/types/session/streaming-output.d.ts +209 -0
- package/dist/types/session/tool-choice-queue.d.ts +84 -0
- package/dist/types/session/yield-queue.d.ts +24 -0
- package/dist/types/setup/credential-auto-import.d.ts +63 -0
- package/dist/types/setup/credential-import.d.ts +82 -0
- package/dist/types/setup/hermes-setup.d.ts +78 -0
- package/dist/types/setup/host-plugin-setup.d.ts +39 -0
- package/dist/types/setup/model-onboarding-guidance.d.ts +9 -0
- package/dist/types/setup/provider-onboarding.d.ts +52 -0
- package/dist/types/skc-runtime/cli-write-receipt.d.ts +24 -0
- package/dist/types/skc-runtime/deep-interview-ambiguity.d.ts +39 -0
- package/dist/types/skc-runtime/deep-interview-recorder.d.ts +114 -0
- package/dist/types/skc-runtime/deep-interview-runtime.d.ts +36 -0
- package/dist/types/skc-runtime/deep-interview-state.d.ts +127 -0
- package/dist/types/skc-runtime/gc-render.d.ts +6 -0
- package/dist/types/skc-runtime/gc-runtime.d.ts +134 -0
- package/dist/types/skc-runtime/goal-mode-request.d.ts +53 -0
- package/dist/types/skc-runtime/launch-tmux.d.ts +78 -0
- package/dist/types/skc-runtime/launch-worktree.d.ts +46 -0
- package/dist/types/skc-runtime/ledger-event-renderer.d.ts +68 -0
- package/dist/types/skc-runtime/psmux-detect.d.ts +78 -0
- package/dist/types/skc-runtime/ralplan-runtime.d.ts +26 -0
- package/dist/types/skc-runtime/restricted-role-agent-bash.d.ts +3 -0
- package/dist/types/skc-runtime/session-layout.d.ts +59 -0
- package/dist/types/skc-runtime/session-resolution.d.ts +47 -0
- package/dist/types/skc-runtime/session-state-sidecar.d.ts +70 -0
- package/dist/types/skc-runtime/state-graph.d.ts +4 -0
- package/dist/types/skc-runtime/state-migrations.d.ts +33 -0
- package/dist/types/skc-runtime/state-renderer.d.ts +70 -0
- package/dist/types/skc-runtime/state-runtime.d.ts +38 -0
- package/dist/types/skc-runtime/state-schema.d.ts +319 -0
- package/dist/types/skc-runtime/state-validation.d.ts +6 -0
- package/dist/types/skc-runtime/state-writer.d.ts +244 -0
- package/dist/types/skc-runtime/team-gc.d.ts +7 -0
- package/dist/types/skc-runtime/team-runtime.d.ts +355 -0
- package/dist/types/skc-runtime/tmux-common.d.ts +111 -0
- package/dist/types/skc-runtime/tmux-gc.d.ts +7 -0
- package/dist/types/skc-runtime/tmux-owner-isolation-cli.d.ts +5 -0
- package/dist/types/skc-runtime/tmux-owner-isolation.d.ts +309 -0
- package/dist/types/skc-runtime/tmux-sessions.d.ts +94 -0
- package/dist/types/skc-runtime/ultragoal-guard.d.ts +93 -0
- package/dist/types/skc-runtime/ultragoal-receipt-freshness.d.ts +42 -0
- package/dist/types/skc-runtime/ultragoal-runtime.d.ts +410 -0
- package/dist/types/skc-runtime/workflow-command-ref.d.ts +43 -0
- package/dist/types/skc-runtime/workflow-manifest.d.ts +54 -0
- package/dist/types/skill-state/active-state.d.ts +126 -0
- package/dist/types/skill-state/canonical-skills.d.ts +3 -0
- package/dist/types/skill-state/deep-interview-mutation-guard.d.ts +38 -0
- package/dist/types/skill-state/initial-phase.d.ts +12 -0
- package/dist/types/skill-state/workflow-hud.d.ts +83 -0
- package/dist/types/skill-state/workflow-state-contract.d.ts +57 -0
- package/dist/types/skill-state/workflow-state-version.d.ts +3 -0
- package/dist/types/slash-commands/acp-builtins.d.ts +18 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +24 -0
- package/dist/types/slash-commands/helpers/context-report.d.ts +7 -0
- package/dist/types/slash-commands/helpers/fast-status-report.d.ts +82 -0
- package/dist/types/slash-commands/helpers/format.d.ts +10 -0
- package/dist/types/slash-commands/helpers/mcp.d.ts +3 -0
- package/dist/types/slash-commands/helpers/parse.d.ts +33 -0
- package/dist/types/slash-commands/helpers/ssh.d.ts +3 -0
- package/dist/types/slash-commands/helpers/todo.d.ts +3 -0
- package/dist/types/slash-commands/helpers/usage-report.d.ts +7 -0
- package/dist/types/slash-commands/types.d.ts +120 -0
- package/dist/types/ssh/config-writer.d.ts +49 -0
- package/dist/types/ssh/connection-manager.d.ts +33 -0
- package/dist/types/ssh/ssh-executor.d.ts +37 -0
- package/dist/types/ssh/sshfs-mount.d.ts +6 -0
- package/dist/types/ssh/utils.d.ts +2 -0
- package/dist/types/stt/downloader.d.ts +9 -0
- package/dist/types/stt/index.d.ts +3 -0
- package/dist/types/stt/recorder.d.ts +13 -0
- package/dist/types/stt/setup.d.ts +18 -0
- package/dist/types/stt/stt-controller.d.ts +16 -0
- package/dist/types/stt/transcriber.d.ts +16 -0
- package/dist/types/system-prompt.d.ts +118 -0
- package/dist/types/task/agents.d.ts +28 -0
- package/dist/types/task/commands.d.ts +35 -0
- package/dist/types/task/discovery.d.ts +19 -0
- package/dist/types/task/executor.d.ts +127 -0
- package/dist/types/task/fork-context-advisory.d.ts +13 -0
- package/dist/types/task/fork-context-budget.d.ts +7 -0
- package/dist/types/task/id.d.ts +7 -0
- package/dist/types/task/index.d.ts +43 -0
- package/dist/types/task/name-generator.d.ts +16 -0
- package/dist/types/task/output-manager.d.ts +36 -0
- package/dist/types/task/parallel.d.ts +34 -0
- package/dist/types/task/receipt.d.ts +89 -0
- package/dist/types/task/render.d.ts +28 -0
- package/dist/types/task/roi-reconciliation.d.ts +27 -0
- package/dist/types/task/simple-mode.d.ts +8 -0
- package/dist/types/task/skc-command.d.ts +7 -0
- package/dist/types/task/spawn-gate.d.ts +29 -0
- package/dist/types/task/subprocess-tool-registry.d.ts +72 -0
- package/dist/types/task/token-log.d.ts +30 -0
- package/dist/types/task/token-log.test.d.ts +1 -0
- package/dist/types/task/types.d.ts +516 -0
- package/dist/types/task/worktree.d.ts +94 -0
- package/dist/types/thinking-metadata.d.ts +16 -0
- package/dist/types/thinking.d.ts +22 -0
- package/dist/types/tool-discovery/tool-index.d.ts +112 -0
- package/dist/types/tools/archive-reader.d.ts +40 -0
- package/dist/types/tools/ask-answer-registry.d.ts +18 -0
- package/dist/types/tools/ask.d.ts +123 -0
- package/dist/types/tools/ast-edit.d.ts +77 -0
- package/dist/types/tools/ast-grep.d.ts +69 -0
- package/dist/types/tools/auto-generated-guard.d.ts +17 -0
- package/dist/types/tools/bash-allowed-prefixes.d.ts +10 -0
- package/dist/types/tools/bash-command-fixup.d.ts +11 -0
- package/dist/types/tools/bash-interactive.d.ts +16 -0
- package/dist/types/tools/bash-interceptor.d.ts +24 -0
- package/dist/types/tools/bash-pty-selection.d.ts +7 -0
- package/dist/types/tools/bash-skill-urls.d.ts +31 -0
- package/dist/types/tools/bash.d.ts +157 -0
- package/dist/types/tools/bisect.d.ts +81 -0
- package/dist/types/tools/browser/actions.d.ts +54 -0
- package/dist/types/tools/browser/attach.d.ts +50 -0
- package/dist/types/tools/browser/launch.d.ts +62 -0
- package/dist/types/tools/browser/readable.d.ts +17 -0
- package/dist/types/tools/browser/registry.d.ts +57 -0
- package/dist/types/tools/browser/render.d.ts +53 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +144 -0
- package/dist/types/tools/browser/tab-supervisor.d.ts +109 -0
- package/dist/types/tools/browser/tab-worker-entry.d.ts +1 -0
- package/dist/types/tools/browser/tab-worker.d.ts +19 -0
- package/dist/types/tools/browser.d.ts +211 -0
- package/dist/types/tools/calculator.d.ts +76 -0
- package/dist/types/tools/checkpoint.d.ts +63 -0
- package/dist/types/tools/composer-bash-policy.d.ts +14 -0
- package/dist/types/tools/computer/render.d.ts +17 -0
- package/dist/types/tools/computer-gc.d.ts +23 -0
- package/dist/types/tools/computer.d.ts +465 -0
- package/dist/types/tools/conflict-detect.d.ts +205 -0
- package/dist/types/tools/context.d.ts +19 -0
- package/dist/types/tools/cron.d.ts +91 -0
- package/dist/types/tools/debug.d.ts +209 -0
- package/dist/types/tools/eval.d.ts +108 -0
- package/dist/types/tools/fetch.d.ts +84 -0
- package/dist/types/tools/file-recorder.d.ts +13 -0
- package/dist/types/tools/find.d.ts +95 -0
- package/dist/types/tools/fs-cache-invalidation.d.ts +15 -0
- package/dist/types/tools/gh-format.d.ts +6 -0
- package/dist/types/tools/gh-renderer.d.ts +25 -0
- package/dist/types/tools/gh.d.ts +356 -0
- package/dist/types/tools/github-cache.d.ts +103 -0
- package/dist/types/tools/grouped-file-output.d.ts +36 -0
- package/dist/types/tools/hindsight-recall.d.ts +21 -0
- package/dist/types/tools/hindsight-reflect.d.ts +23 -0
- package/dist/types/tools/hindsight-retain.d.ts +27 -0
- package/dist/types/tools/image-gen.d.ts +79 -0
- package/dist/types/tools/index.d.ts +371 -0
- package/dist/types/tools/irc.d.ts +79 -0
- package/dist/types/tools/job.d.ts +78 -0
- package/dist/types/tools/json-tree.d.ts +23 -0
- package/dist/types/tools/jtd-to-json-schema.d.ts +29 -0
- package/dist/types/tools/jtd-to-typescript.d.ts +26 -0
- package/dist/types/tools/jtd-utils.d.ts +42 -0
- package/dist/types/tools/list-limit.d.ts +12 -0
- package/dist/types/tools/match-line-format.d.ts +11 -0
- package/dist/types/tools/monitor.d.ts +54 -0
- package/dist/types/tools/output-meta.d.ts +204 -0
- package/dist/types/tools/path-utils.d.ts +147 -0
- package/dist/types/tools/plan-mode-guard.d.ts +18 -0
- package/dist/types/tools/read.d.ts +85 -0
- package/dist/types/tools/recipe/index.d.ts +45 -0
- package/dist/types/tools/recipe/render.d.ts +36 -0
- package/dist/types/tools/recipe/runner.d.ts +60 -0
- package/dist/types/tools/recipe/runners/cargo.d.ts +16 -0
- package/dist/types/tools/recipe/runners/index.d.ts +2 -0
- package/dist/types/tools/recipe/runners/just.d.ts +2 -0
- package/dist/types/tools/recipe/runners/make.d.ts +2 -0
- package/dist/types/tools/recipe/runners/pkg.d.ts +2 -0
- package/dist/types/tools/recipe/runners/task.d.ts +2 -0
- package/dist/types/tools/render-mermaid.d.ts +37 -0
- package/dist/types/tools/render-utils.d.ts +158 -0
- package/dist/types/tools/renderers.d.ts +26 -0
- package/dist/types/tools/resolve.d.ts +80 -0
- package/dist/types/tools/resource-gc.d.ts +54 -0
- package/dist/types/tools/review.d.ts +63 -0
- package/dist/types/tools/search-tool-bm25.d.ts +65 -0
- package/dist/types/tools/search.d.ts +93 -0
- package/dist/types/tools/skill-discovery.d.ts +40 -0
- package/dist/types/tools/skill.d.ts +48 -0
- package/dist/types/tools/sqlite-reader.d.ts +94 -0
- package/dist/types/tools/ssh.d.ts +67 -0
- package/dist/types/tools/subagent-render.d.ts +31 -0
- package/dist/types/tools/subagent.d.ts +117 -0
- package/dist/types/tools/telegram-send.d.ts +39 -0
- package/dist/types/tools/todo-write.d.ts +120 -0
- package/dist/types/tools/tool-errors.d.ts +33 -0
- package/dist/types/tools/tool-result.d.ts +30 -0
- package/dist/types/tools/tool-timeouts.d.ts +56 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +12 -0
- package/dist/types/tools/vim.d.ts +58 -0
- package/dist/types/tools/write.d.ts +59 -0
- package/dist/types/tools/yield.d.ts +25 -0
- package/dist/types/tui/code-cell.d.ts +32 -0
- package/dist/types/tui/file-list.d.ts +22 -0
- package/dist/types/tui/hyperlink.d.ts +42 -0
- package/dist/types/tui/index.d.ts +11 -0
- package/dist/types/tui/output-block.d.ts +28 -0
- package/dist/types/tui/status-line.d.ts +18 -0
- package/dist/types/tui/tree-list.d.ts +19 -0
- package/dist/types/tui/types.d.ts +13 -0
- package/dist/types/tui/utils.d.ts +36 -0
- package/dist/types/utils/changelog.d.ts +37 -0
- package/dist/types/utils/clipboard.d.ts +22 -0
- package/dist/types/utils/cmux-workspace.d.ts +52 -0
- package/dist/types/utils/command-args.d.ts +9 -0
- package/dist/types/utils/commit-message-generator.d.ts +7 -0
- package/dist/types/utils/edit-mode.d.ts +13 -0
- package/dist/types/utils/event-bus.d.ts +6 -0
- package/dist/types/utils/external-editor.d.ts +19 -0
- package/dist/types/utils/file-display-mode.d.ts +27 -0
- package/dist/types/utils/file-mentions.d.ts +25 -0
- package/dist/types/utils/git.d.ts +393 -0
- package/dist/types/utils/image-loading.d.ts +26 -0
- package/dist/types/utils/image-resize.d.ts +39 -0
- package/dist/types/utils/lang-from-path.d.ts +8 -0
- package/dist/types/utils/linkedom.d.ts +4 -0
- package/dist/types/utils/markit.d.ts +7 -0
- package/dist/types/utils/open.d.ts +2 -0
- package/dist/types/utils/pasted-image-path.d.ts +32 -0
- package/dist/types/utils/session-color.d.ts +10 -0
- package/dist/types/utils/shell-snapshot.d.ts +5 -0
- package/dist/types/utils/sixel.d.ts +21 -0
- package/dist/types/utils/title-generator.d.ts +31 -0
- package/dist/types/utils/tool-choice.d.ts +20 -0
- package/dist/types/utils/tools-manager.d.ts +9 -0
- package/dist/types/vim/buffer.d.ts +41 -0
- package/dist/types/vim/commands.d.ts +6 -0
- package/dist/types/vim/engine.d.ts +47 -0
- package/dist/types/vim/parser.d.ts +3 -0
- package/dist/types/vim/render.d.ts +25 -0
- package/dist/types/vim/types.d.ts +182 -0
- package/dist/types/web/insane/bridge.d.ts +103 -0
- package/dist/types/web/insane/url-guard.d.ts +25 -0
- package/dist/types/web/kagi.d.ts +23 -0
- package/dist/types/web/parallel.d.ts +58 -0
- package/dist/types/web/scrapers/artifacthub.d.ts +6 -0
- package/dist/types/web/scrapers/arxiv.d.ts +5 -0
- package/dist/types/web/scrapers/aur.d.ts +5 -0
- package/dist/types/web/scrapers/biorxiv.d.ts +5 -0
- package/dist/types/web/scrapers/bluesky.d.ts +5 -0
- package/dist/types/web/scrapers/brew.d.ts +5 -0
- package/dist/types/web/scrapers/cheatsh.d.ts +8 -0
- package/dist/types/web/scrapers/chocolatey.d.ts +5 -0
- package/dist/types/web/scrapers/choosealicense.d.ts +2 -0
- package/dist/types/web/scrapers/cisa-kev.d.ts +5 -0
- package/dist/types/web/scrapers/clojars.d.ts +5 -0
- package/dist/types/web/scrapers/coingecko.d.ts +5 -0
- package/dist/types/web/scrapers/crates-io.d.ts +5 -0
- package/dist/types/web/scrapers/crossref.d.ts +2 -0
- package/dist/types/web/scrapers/devto.d.ts +5 -0
- package/dist/types/web/scrapers/discogs.d.ts +8 -0
- package/dist/types/web/scrapers/discourse.d.ts +5 -0
- package/dist/types/web/scrapers/dockerhub.d.ts +5 -0
- package/dist/types/web/scrapers/docs-rs.d.ts +2 -0
- package/dist/types/web/scrapers/fdroid.d.ts +5 -0
- package/dist/types/web/scrapers/firefox-addons.d.ts +2 -0
- package/dist/types/web/scrapers/flathub.d.ts +2 -0
- package/dist/types/web/scrapers/github-gist.d.ts +5 -0
- package/dist/types/web/scrapers/github.d.ts +12 -0
- package/dist/types/web/scrapers/gitlab.d.ts +5 -0
- package/dist/types/web/scrapers/go-pkg.d.ts +5 -0
- package/dist/types/web/scrapers/hackage.d.ts +5 -0
- package/dist/types/web/scrapers/hackernews.d.ts +2 -0
- package/dist/types/web/scrapers/hex.d.ts +5 -0
- package/dist/types/web/scrapers/huggingface.d.ts +2 -0
- package/dist/types/web/scrapers/iacr.d.ts +5 -0
- package/dist/types/web/scrapers/index.d.ts +84 -0
- package/dist/types/web/scrapers/jetbrains-marketplace.d.ts +2 -0
- package/dist/types/web/scrapers/lemmy.d.ts +2 -0
- package/dist/types/web/scrapers/lobsters.d.ts +5 -0
- package/dist/types/web/scrapers/mastodon.d.ts +5 -0
- package/dist/types/web/scrapers/maven.d.ts +6 -0
- package/dist/types/web/scrapers/mdn.d.ts +2 -0
- package/dist/types/web/scrapers/metacpan.d.ts +5 -0
- package/dist/types/web/scrapers/musicbrainz.d.ts +5 -0
- package/dist/types/web/scrapers/npm.d.ts +5 -0
- package/dist/types/web/scrapers/nuget.d.ts +5 -0
- package/dist/types/web/scrapers/nvd.d.ts +5 -0
- package/dist/types/web/scrapers/ollama.d.ts +2 -0
- package/dist/types/web/scrapers/open-vsx.d.ts +5 -0
- package/dist/types/web/scrapers/opencorporates.d.ts +5 -0
- package/dist/types/web/scrapers/openlibrary.d.ts +5 -0
- package/dist/types/web/scrapers/orcid.d.ts +5 -0
- package/dist/types/web/scrapers/osv.d.ts +5 -0
- package/dist/types/web/scrapers/packagist.d.ts +5 -0
- package/dist/types/web/scrapers/pub-dev.d.ts +5 -0
- package/dist/types/web/scrapers/pubmed.d.ts +5 -0
- package/dist/types/web/scrapers/pypi.d.ts +5 -0
- package/dist/types/web/scrapers/rawg.d.ts +2 -0
- package/dist/types/web/scrapers/readthedocs.d.ts +2 -0
- package/dist/types/web/scrapers/reddit.d.ts +5 -0
- package/dist/types/web/scrapers/repology.d.ts +5 -0
- package/dist/types/web/scrapers/rfc.d.ts +5 -0
- package/dist/types/web/scrapers/rubygems.d.ts +5 -0
- package/dist/types/web/scrapers/searchcode.d.ts +2 -0
- package/dist/types/web/scrapers/sec-edgar.d.ts +5 -0
- package/dist/types/web/scrapers/semantic-scholar.d.ts +2 -0
- package/dist/types/web/scrapers/snapcraft.d.ts +2 -0
- package/dist/types/web/scrapers/sourcegraph.d.ts +2 -0
- package/dist/types/web/scrapers/spdx.d.ts +5 -0
- package/dist/types/web/scrapers/spotify.d.ts +8 -0
- package/dist/types/web/scrapers/stackoverflow.d.ts +6 -0
- package/dist/types/web/scrapers/terraform.d.ts +5 -0
- package/dist/types/web/scrapers/tldr.d.ts +7 -0
- package/dist/types/web/scrapers/twitter.d.ts +5 -0
- package/dist/types/web/scrapers/types.d.ts +83 -0
- package/dist/types/web/scrapers/utils.d.ts +32 -0
- package/dist/types/web/scrapers/vimeo.d.ts +5 -0
- package/dist/types/web/scrapers/vscode-marketplace.d.ts +5 -0
- package/dist/types/web/scrapers/w3c.d.ts +2 -0
- package/dist/types/web/scrapers/wikidata.d.ts +5 -0
- package/dist/types/web/scrapers/wikipedia.d.ts +5 -0
- package/dist/types/web/scrapers/youtube.d.ts +13 -0
- package/dist/types/web/search/index.d.ts +118 -0
- package/dist/types/web/search/provider.d.ts +55 -0
- package/dist/types/web/search/providers/anthropic.d.ts +32 -0
- package/dist/types/web/search/providers/base.d.ts +90 -0
- package/dist/types/web/search/providers/brave.d.ts +27 -0
- package/dist/types/web/search/providers/codex.d.ts +35 -0
- package/dist/types/web/search/providers/duckduckgo.d.ts +57 -0
- package/dist/types/web/search/providers/exa.d.ts +52 -0
- package/dist/types/web/search/providers/gemini.d.ts +57 -0
- package/dist/types/web/search/providers/insane.d.ts +53 -0
- package/dist/types/web/search/providers/jina.d.ts +26 -0
- package/dist/types/web/search/providers/kagi.d.ts +24 -0
- package/dist/types/web/search/providers/kimi.d.ts +27 -0
- package/dist/types/web/search/providers/openai-compatible.d.ts +9 -0
- package/dist/types/web/search/providers/parallel.d.ts +15 -0
- package/dist/types/web/search/providers/perplexity.d.ts +38 -0
- package/dist/types/web/search/providers/searxng.d.ts +44 -0
- package/dist/types/web/search/providers/synthetic.d.ts +21 -0
- package/dist/types/web/search/providers/tavily.d.ts +29 -0
- package/dist/types/web/search/providers/text-citations.d.ts +23 -0
- package/dist/types/web/search/providers/utils.d.ts +88 -0
- package/dist/types/web/search/providers/xai.d.ts +64 -0
- package/dist/types/web/search/providers/zai.d.ts +28 -0
- package/dist/types/web/search/render.d.ts +36 -0
- package/dist/types/web/search/types.d.ts +372 -0
- package/dist/types/web/search/utils.d.ts +4 -0
- package/dist/types/workflow/workflow-intent-diff.d.ts +25 -0
- package/dist/types/workflow/workflow-intent-report-consumer.d.ts +50 -0
- package/dist/types/workflow/workflow-intent-report-schema.d.ts +2 -0
- package/dist/types/workflow/workflow-intent-report.d.ts +48 -0
- package/dist/types/workspace-tree.d.ts +42 -0
- package/package.json +116 -115
- package/scripts/generate-docs-index.ts +0 -2
- package/src/cli.ts +1 -0
- package/src/config/settings.ts +96 -45
- package/src/coordinator/contract.ts +1 -0
- package/src/coordinator-mcp/policy.ts +30 -0
- package/src/coordinator-mcp/server.ts +294 -66
- package/src/coordinator-mcp/session-reaper.ts +129 -0
- package/src/extensibility/extensions/runner.ts +1 -1
- package/src/extensibility/extensions/types.ts +3 -1
- package/src/internal-urls/docs-index.generated.ts +6 -5
- package/src/main.ts +3 -5
- package/src/modes/acp/acp-agent.ts +2 -2
- package/src/modes/components/footer.ts +3 -3
- package/src/modes/components/status-line/segments.ts +14 -7
- package/src/modes/components/status-line/types.ts +1 -1
- package/src/modes/components/status-line.ts +4 -171
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +16 -0
- package/src/modes/print-mode.ts +228 -92
- package/src/modes/rpc/rpc-client.ts +58 -1
- package/src/modes/rpc/rpc-mode.ts +286 -85
- package/src/modes/rpc/rpc-types.ts +15 -1
- package/src/modes/shared/agent-wire/command-dispatch.ts +33 -1
- package/src/modes/shared/agent-wire/command-validation.ts +10 -0
- package/src/modes/shared/agent-wire/scopes.ts +1 -0
- package/src/modes/utils/context-usage.ts +56 -92
- package/src/session/agent-session.ts +287 -85
- package/src/session/context-estimation.ts +91 -0
- package/src/slash-commands/builtin-registry.ts +12 -2
- package/src/slash-commands/helpers/context-report.ts +26 -4
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
// Auto-generated by scripts/generate-docs-index.ts - DO NOT EDIT
|
|
2
2
|
Reflect.set(globalThis, Symbol.for("skc.docs-index.generated.loaded"), true);
|
|
3
3
|
|
|
4
|
-
export const EMBEDDED_DOC_FILENAMES: readonly string[] = ["ERRATA-GPT5-HARMONY.md","REBRANDING_PLAN_260525.md","ai-schema-normalize.md","analyze-me-with-skc.md","aside-integration.md","auth-broker-gateway.md","bash-tool-runtime.md","blob-artifact-architecture.md","bot-integration.md","brand-assets.md","bridge.md","codebase-overview.md","codegraph-custom-tool.md","compaction.md","composer-codex-parity.md","computer-use/README.md","environment-variables.md","external-control-readiness.md","extragoal-skill-template.md","fs-scan-cache-architecture.md","geobench.md","git-daemon.md","gpt-5.6-codex-preset-benchmark.md","grok-build-provider-design.md","handoff-generation-pipeline.md","hermes-mcp-bridge.md","hotspot-map-successor.md","keybindings.md","lsp-config.md","memory.md","models.md","multi-vendor-profiles.md","native-ffi-optimization-policy.md","natives-addon-loader-runtime.md","natives-architecture.md","natives-binding-contract.md","natives-build-release-debugging.md","natives-media-system-utils.md","natives-package-split-plan.md","natives-rust-task-cancellation.md","natives-shell-pty-process.md","natives-text-search-pipeline.md","non-compaction-retry-policy.md","notebook-tool-runtime.md","notifications-sdk.md","onboarding-packet.md","onboarding-receipt.md","ooo-bridge-extension-contract.md","openclaw-hermes-rpc-integration.md","perf-profiling-corpus.md","porting-from-pi-mono.md","porting-to-natives.md","prompt-architect-reports/README.md","prompt-architect-reports/recovered-context/0-ToolPrompts.recovered.md","prompt-architect-reports/recovered-context/1-SystemPrompts.recovered.md","prompt-architect-reports/recovered-context/3-SkillMiscPrompts.recovered.md","prompt-architect-reports/recovery-summary.md","prompt-architect-reports/system-prompts.raw.md","prompt-architect-reports/tool-prompts.raw.md","provider-streaming-internals.md","python-repl.md","readme/README.de.md","readme/README.es.md","readme/README.fr.md","readme/README.ja.md","readme/README.ko.md","readme/README.zh.md","render-mermaid.md","research-plan-ledger.md","resolve-tool-runtime.md","rpc.md","rulebook-matching-pipeline.md","sayknow-remote.md","sdk.md","secrets.md","session-operations-export-share-fork-resume.md","session-switching-and-recent-listing.md","session-tree-plan.md","session.md","skc-dogfood-skill-template.md","skc-plugins.md","skc-session-clawhip-routing.md","standalone-mcp.md","telegram-onboarding.md","telegram-remote.md","theme.md","tools/ask.md","tools/ast-edit.md","tools/ast-grep.md","tools/bash.md","tools/bisect.md","tools/browser.md","tools/calc.md","tools/checkpoint.md","tools/computer.md","tools/cron.md","tools/debug.md","tools/edit.md","tools/eval.md","tools/find.md","tools/github.md","tools/irc.md","tools/job.md","tools/lsp.md","tools/monitor.md","tools/read.md","tools/recipe.md","tools/render_mermaid.md","tools/resolve.md","tools/rewind.md","tools/search.md","tools/search_tool_bm25.md","tools/ssh.md","tools/task.md","tools/todo_write.md","tools/web_search.md","tools/write.md","tree.md","ttsr-injection-lifecycle.md","tui-runtime-internals.md","ui-design-visual-qa.md"];
|
|
4
|
+
export const EMBEDDED_DOC_FILENAMES: readonly string[] = ["ERRATA-GPT5-HARMONY.md","FORK_MAINTENANCE.md","REBRANDING_PLAN_260525.md","ai-schema-normalize.md","analyze-me-with-skc.md","aside-integration.md","auth-broker-gateway.md","bash-tool-runtime.md","blob-artifact-architecture.md","bot-integration.md","brand-assets.md","bridge.md","codebase-overview.md","codegraph-custom-tool.md","compaction.md","composer-codex-parity.md","computer-use/README.md","environment-variables.md","external-control-readiness.md","extragoal-skill-template.md","fs-scan-cache-architecture.md","geobench.md","git-daemon.md","gpt-5.6-codex-preset-benchmark.md","grok-build-provider-design.md","handoff-generation-pipeline.md","hermes-mcp-bridge.md","hotspot-map-successor.md","keybindings.md","lsp-config.md","memory.md","models.md","multi-vendor-profiles.md","native-ffi-optimization-policy.md","natives-addon-loader-runtime.md","natives-architecture.md","natives-binding-contract.md","natives-build-release-debugging.md","natives-media-system-utils.md","natives-package-split-plan.md","natives-rust-task-cancellation.md","natives-shell-pty-process.md","natives-text-search-pipeline.md","non-compaction-retry-policy.md","notebook-tool-runtime.md","notifications-sdk.md","onboarding-packet.md","onboarding-receipt.md","ooo-bridge-extension-contract.md","openclaw-hermes-rpc-integration.md","perf-profiling-corpus.md","porting-from-pi-mono.md","porting-to-natives.md","prompt-architect-reports/README.md","prompt-architect-reports/recovered-context/0-ToolPrompts.recovered.md","prompt-architect-reports/recovered-context/1-SystemPrompts.recovered.md","prompt-architect-reports/recovered-context/3-SkillMiscPrompts.recovered.md","prompt-architect-reports/recovery-summary.md","prompt-architect-reports/system-prompts.raw.md","prompt-architect-reports/tool-prompts.raw.md","provider-streaming-internals.md","python-repl.md","readme/README.de.md","readme/README.es.md","readme/README.fr.md","readme/README.ja.md","readme/README.ko.md","readme/README.zh.md","render-mermaid.md","research-plan-ledger.md","resolve-tool-runtime.md","rpc.md","rulebook-matching-pipeline.md","sayknow-remote.md","sdk.md","secrets.md","session-operations-export-share-fork-resume.md","session-switching-and-recent-listing.md","session-tree-plan.md","session.md","skc-dogfood-skill-template.md","skc-plugins.md","skc-session-clawhip-routing.md","standalone-mcp.md","telegram-onboarding.md","telegram-remote.md","theme.md","tools/ask.md","tools/ast-edit.md","tools/ast-grep.md","tools/bash.md","tools/bisect.md","tools/browser.md","tools/calc.md","tools/checkpoint.md","tools/computer.md","tools/cron.md","tools/debug.md","tools/edit.md","tools/eval.md","tools/find.md","tools/github.md","tools/irc.md","tools/job.md","tools/lsp.md","tools/monitor.md","tools/read.md","tools/recipe.md","tools/render_mermaid.md","tools/resolve.md","tools/rewind.md","tools/search.md","tools/search_tool_bm25.md","tools/ssh.md","tools/task.md","tools/todo_write.md","tools/web_search.md","tools/write.md","tree.md","ttsr-injection-lifecycle.md","tui-runtime-internals.md","ui-design-visual-qa.md"];
|
|
5
5
|
|
|
6
6
|
export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
|
|
7
7
|
"ERRATA-GPT5-HARMONY.md": "# ERRATA — GPT-5 Harmony-Header Leakage\n\n## 1. The problem\n\nOpenAI frames tool calls in the Harmony chat protocol:\n\n```\n<|start|>assistant<|channel|>commentary to=functions.<NAME><|message|>{ARGS}<|call|>\n```\n\n`<|channel|>commentary to=functions.NAME` is the **routing header** —\ncontrol tokens consumed by the runtime to dispatch the call. These\ntokens never appear as content under normal operation; the runtime\nstrips them.\n\nThe defect: gpt-5 models occasionally emit, **as ordinary content\ninside `{ARGS}`**, the **plain-text shadow** of these routing tokens —\nthe same characters without the `<|…|>` brackets — and continue\nproducing more pseudo-routing structure (channel name, body marker,\nmultilingual spam, fake tool-result framing). The contamination lives\ninside the visible tool argument and is dispatched to the tool as if it\nwere intended content.\n\n**Critical detail.** The actual `<|start|>` / `<|channel|>` /\n`<|message|>` / `<|call|>` special tokens almost never appear in tool\nargs. What leaks is the bracket-less spelling — `analysis to=functions.X\ncode …` — because OpenAI applies a logit mask suppressing the\ncontrol-token IDs inside the args region. The mass that would have gone\nto those special tokens redistributes onto the un-bracketed plain-text\nrepresentation the model also learned. This makes the leak structurally\ninvisible to the routing parser and lands it in the tool input verbatim.\n\nManifestation in tool args (real corpus example):\n\n```\n~ add_function(iso, ctx, ns, \"installSystemChangeObserver\",\n os_install_system_change_observer);】【\"】【analysis to=functions.edit\n code above เงินไทยฟรีuser to=functions.edit code …\n```\n\nThe leading code is real and intended. Everything after the first\nnon-Latin token through the next clean structural boundary is corruption.\n\n---\n\n## 2. Observed statistics & failure modes\n\nSource: `~/.skc/stats.db` (`ss_tool_calls`, `ss_assistant_msgs`), through\n2026-05-10. 1.05M tool calls scanned.\n\n### 2.1 Rate\n\n| Model | Leaks in tool args | Calls | per million |\n|------------------|-------------------:|--------:|------------:|\n| gpt-5.4 | 37 | 226,957 | 163 |\n| gpt-5.3-openai-code | 17 | 112,243 | 151 |\n| gpt-5.5 | 2 | 80,750 | 25 |\n| gpt-5.2-openai-code | 0 | — | — |\n\nPlus 15 hits in assistant visible text / thinking blobs.\n\n### 2.2 Tool distribution\n\n| Tool | Hits |\n|---------------------|-----:|\n| `edit` | 38 |\n| `eval` | 11 |\n| `report_tool_issue` | 3 |\n| `grep`/`read`/`search`/`yield` | 1 each |\n\nConcentrated in tools with free-form (non-JSON-schema) argument formats.\n\n### 2.3 Leak shape (deterministic)\n\n```\nLEAK ::= JUNK_PREFIX MARKER CHANNEL_BODY (LEAK)?\nMARKER ::= \"to=functions.\" TOOL_NAME\nCHANNEL_BODY ::= \" code \" (SPAM | reasoning_prose | fake_tool_output)*\nJUNK_PREFIX ::= (GLITCH_TOKEN | CHANNEL_WORD | NON_LATIN_RUN | \"}\" | \"】【\")+\n```\n\n**Cascading is common.** Of 96 marker occurrences across 71 contaminated\nrecords, 39 contain ≥2 markers and 7 contain ≥3 — the model emits\nmultiple fake `to=functions.X code …` blocks back-to-back, often with\nfake `code_output\\nCell N:\\n…` framing between them. Once the\nplain-text scaffolding is in the residual stream, the prefix now *looks\nlike* a fresh tool envelope start, so the macro prior over continuations\nkeeps voting for more scaffolding. Self-amplifying.\n\n### 2.4 Glitch tokens\n\nSingle-token identifiers in `o200k_base` whose embeddings appear to be\nnear-init from underrepresentation in post-training. ASCII residue\nimmediately before the marker in the natural corpus:\n\n| Surface string | Single-token | Token ID | Hits in corpus |\n|-------------------|:-:|---------:|---:|\n| `Japgolly` | ✅ | 199,745 | 1 |\n| `Jsii` | ✅ | 114,318 | (subtoken of `Jsii_commentary`) |\n| `Jsii_commentary` | — (3 toks) | — | 2 |\n| `changedFiles` | — (2 toks) | — | 8 |\n| `RTLU` | — (2 toks) | — | 3 |\n\n`Japgolly` is in the last 0.13% of the vocabulary — the same family of\nGitHub-corpus residue that produced `SolidGoldMagikarp` in the 2023\nGPT-2 vocabulary (Rumbelow & Watkins). `SolidGoldMagikarp` itself\ntokenizes to 5 tokens in `o200k_base` — that specific token was retired,\nbut the class wasn't.\n\nFor the multi-token entries, the corpus-level signature is the surface\nstring; the underlying glitch trigger is a sub-token (e.g. `Jsii` inside\n`Jsii_commentary`). The detector list (`G` signal) keys on the surface\nstrings.\n\nStable across unrelated sessions. Treated as a high-precision detector\nsignal.\n\n### 2.5 Channel-word leakage\n\n`analysis` (5), `assistant` (5), `commentary` (3), `user` (1) appear\ndirectly preceding `to=`. Always bare words; never `<|channel|>analysis`\nor any other bracketed form. Consistent with §1 — the brackets are\nmasked, the words are not.\n\n### 2.6 Non-Latin spam residue\n\n96 marker hits, by script: CJK 40, Cyrillic 12, Telugu/Kannada/Malayalam\n18, Thai 8, Georgian 7, Armenian 7, Arabic 1. Recurring fragments are\nChinese gambling SEO (`大发时时彩`, `天天中彩票`), Georgian/Abkhaz junk,\nand Thai casino spam — well-known low-quality crawl residue.\n\nThis is the same script distribution observed in the controlled\nreproduction (§7.3), independent of the prompt's natural language.\n\n### 2.7 Failure-mode breakdown for the `edit` tool\n\nThe `edit` tool exists in two variants in the corpus:\n\n| Variant | Calls | Recovery |\n|--------------------------|------:|----------|\n| Patch-DSL (`§PATH`/anchor/`«»≔` ops) | 27 | **Recoverable** by op-truncation (§3.3) |\n| JSON-schema (`{path,edits:[…]}`) | 11 | **Not recoverable** — contamination is escaped *inside* JSON strings, parser accepts it cleanly, content would be written verbatim into source files |\n\nFor Patch-DSL leaks specifically:\n\n- 20/27 cases: contamination on the last input line; nothing follows.\n- 7/27 cases: contamination mid-input; what follows is one of: a\n duplicate replay of an earlier file/anchor, intended content for a\n *different* tool call (the model started its next call inline), or\n pure hallucination. Post-contamination content is never trustworthy.\n\n### 2.8 Mechanism (confirmed)\n\n**Prior collapse from null-embedding glitch tokens, into a\ncontrol-token-masked basin whose mass redistributes onto the\nplain-text shadow of the Harmony protocol.**\n\nStep by step:\n\n1. The model is mid-`{ARGS}` of a Harmony tool call. The runtime applies\n a logit mask suppressing structural control tokens (`<|channel|>`,\n `<|message|>`, `<|call|>`, `<|start|>`, `<|end|>`) inside the args\n region. Without this mask, normal generation would constantly\n hallucinate envelope-closes; with it, those token IDs have logit\n `-∞` in args.\n2. A glitch token `g` is sampled. By construction `g` was in the BPE\n merge corpus but barely in LM/RL training, so its **input embedding\n `e_g` ≈ near-init noise of small norm**.\n3. At position t+1, the residual update `h_{t+1} ≈ LN(h_t + e_g + Attn +\n MLP)` is dominated by the prefix-derived terms; the just-emitted-token\n signal is effectively absent. Generation diversity normally comes\n from `e_x` steering the residual into different sub-regions —\n stripped here.\n4. The next-token distribution therefore collapses onto the **conditional\n prior over continuations of the prefix, with local conditioning\n removed**. In a tool-calling rollout context, that prior is sharply\n peaked on Harmony scaffolding (control tokens + routing tokens) —\n that's what RL trained.\n5. The mask zeros the control-token IDs. Mass redistributes onto the\n **next-best continuation**: the un-bracketed surface-form spelling of\n the same protocol (`analysis`, `commentary`, ` to=functions.X`,\n ` code `). This spelling is unmasked because those characters are\n ordinary tokens.\n6. Once a few tokens of plain-text scaffolding land in the residual\n stream, the prefix now resembles a fresh envelope start. The macro\n prior keeps voting for more scaffolding. Cascading (§2.3) follows.\n7. Multilingual spam after the marker is the same prior-collapse\n continuation, drawn from the training neighborhood of the glitch\n token (often ESL/auto-generated multilingual web junk — exactly the\n crawl residue in §2.6).\n\n**Two corollaries the corpus data demanded but only the experiment\nexplained:**\n\n- **The brackets never appear** (§1, §2.5). The mask is what makes the\n leak land in plain text instead of as a real envelope-close.\n- **Counterintuitive grammar dependency** (§7.4). The leak is *worse* in\n formats closest to OpenAI's training distribution. Off-distribution\n custom grammars dampen the macro-prior basin; the official\n `*** Begin Patch` format is the strongest collapse target.\n\nThe 2023 SolidGoldMagikarp paper documented mechanism (1)+(2)+(4). The\nnew piece is (5): when constrained decoding masks the natural collapse\ntarget, the mass laundered through the un-masked plain-text shadow\nbecomes a structurally-invisible exfiltration channel.",
|
|
8
|
+
"FORK_MAINTENANCE.md": "# Fork maintenance — keeping Sayknow-CLI synced with upstream\n\nSayknow-CLI is a **rebranded fork** of upstream `gajae-code`. We track upstream\nreleases without re-doing the rename by hand each time: the fork is a\n**reproducible function of `{upstream tag, our fork layer}`**.\n\n```\nfork tree = gen-tree(clean upstream tag)\n = codemod + fork-identity + overlay + patches\n```\n\nVerified: `gen-tree(v0.5.4)` byte-reproduces the `sayknow-fork` branch (excluding\nregenerated lockfiles + `*.generated.ts`).\n\n## Branch / remote topology\n\n| ref | meaning |\n| --- | --- |\n| `upstream` (remote) | `github.com/Yeachan-Heo/gajae-code` — read-only source |\n| `origin` (remote) | `github.com/jaybeyond/Sayknow_CLI` — our fork |\n| `main` (local) / `origin/main` | our shippable fork (local branch `sayknow-fork` → `origin/main`) |\n| `origin/upstream-mirror` | pristine upstream mirror (tag `upstream/v0.5.4`) |\n| tag `sayknow-v0.1.0` | fork release; tag `upstream/v0.5.4` | the upstream base it was generated from |\n\nBacked up: `git push origin sayknow-fork:main` (done). Local branch `sayknow-fork`\ntracks `origin/main`.\n\n## The four layers\n\n1. **codemod** — `scripts/apply-rebrand.ts`: deterministic brand rename\n (`gajae/gjc → sayknow/skc`, paths, `@sayknow-cli` scope) + identity\n special-cases (`can1357`/`Yeachan-Heo → jaybeyond`, discord → placeholder).\n Skips `bun.lock`/`Cargo.lock` (integrity hashes). Reproduces ~1885 files with\n **zero residual tokens**.\n2. **fork-identity** — `scripts/apply-fork-identity.ts` + `rebrand/identity.json`:\n stamps the fork **version** (`0.1.0`) onto workspace `package.json` + root\n catalog + `Cargo.toml`, as minimal format-preserving edits. **Bump the version\n here**, never with a global replace (that would corrupt CHANGELOG/lockfiles).\n3. **overlay** — `rebrand/overlay/**`: whole files we own outright (i18n module,\n `blue-octopus`/`red-octopus` themes, octopus assets, brand docs). Copied over\n the codemod output. **No line-level merge → zero conflict.**\n4. **patches** — `rebrand/patches/NN-*.patch`: the ~17 **in-place edits** to\n upstream-owned files (welcome redesign, theme default, i18n wiring, tests).\n Applied with `git apply --reject`. **This is the only conflict-prone layer.**\n\n`rebrand/manifest.json` declares which files are `patch` / `regenerate` /\n`toolingOnly`; everything else that differs becomes overlay automatically.\n\n## Sync to a new upstream release\n\n```sh\nbash scripts/sync-upstream.sh v0.5.5 # fetch tag, regenerate, run gates G1–G4\n```\n\nThis produces a generated, gate-verified tree in a temp worktree (it does **not**\nauto-commit). Then **adopt** it:\n\n```sh\n# review the generated tree printed by the sync command, then:\nrsync -a --delete --exclude .git <generated-wt>/ .\nbun install # refresh lockfiles\ngit add -A && git commit -m \"sync: upstream v0.5.5\"\ngit tag sayknow-v0.<n>.0 # bump rebrand/identity.json first if releasing\n```\n\n### When a patch conflicts (upstream changed a patched file)\n\n`git apply --reject` leaves `*.rej` files in the generated worktree. For each:\n\n1. Open the `.rej` and the target file, apply the change by hand.\n2. Re-extract so the patch matches the new upstream base:\n ```sh\n # after upstream is mirrored + codemod+finalizer applied to <base>:\n bun scripts/extract-fork-layer.ts --base <base> --fork <fixed-tree> --apply\n ```\n3. Commit the refreshed `rebrand/patches/`.\n\n**Lever:** the fewer in-place edits, the fewer future conflicts. Prefer expressing\nnew fork features as *new files* (overlay) or additive hooks rather than edits to\nupstream files. Today only ~17 files are patches; keep it small.\n\n## Gates (run by `sync`, or manually)\n\n| gate | check |\n| --- | --- |\n| **G1** | no residual `gajae/gjc/red-claw/...` tokens in the generated tree |\n| **G2** | codemod idempotence — a *second* `apply-rebrand` changes **0 files** (diff-based; the printed hit-count is unreliable, do not gate on it) |\n| **G3** | `bun --cwd=packages/coding-agent run check:types` |\n| **G4** | `bun test` brand/i18n/welcome suites |\n\n## Publishing to npm\n\n`scripts/publish-npm.ts` publishes the 9 workspace packages in dependency order\nusing **`bun publish`** (not `npm publish`): bun resolves `catalog:`/`workspace:`\ndeps to concrete versions at pack time, so the packages actually install. Plain\n`npm publish` leaves `catalog:` in the tarball and breaks `bun install -g sayknow-cli`.\n\nOne-time prerequisites (need your npm account — the script can't do these):\n\n1. Create the **`@sayknow-cli`** org/scope on npmjs.com (Settings → Add Organization\n → free unlimited public). All 9 packages are `@sayknow-cli/*`.\n2. `bunx npm login`\n\nThen:\n\n```sh\nbun run build:native # build the platform .node first\nbun scripts/publish-npm.ts --dry-run # preview tarballs (no login needed)\nbun scripts/publish-npm.ts # publish for real\n```\n\nAfter publishing, `bun install -g sayknow-cli` works.\n\n> **Native binary is platform-specific.** `@sayknow-cli/natives` bundles a prebuilt\n> `.node` for the machine you publish from (e.g. `darwin-arm64`). A publish from an\n> Apple-Silicon Mac works for Apple-Silicon Mac users; **Linux / Windows / Intel-Mac\n> users get a \"failed to load native addon\" error.** Full cross-platform support\n> needs each platform's `.node` built in CI (`scripts/ci-release-build-binaries.ts`)\n> and shipped together — a follow-up, not a single-machine publish.\n\n## Regenerating the fork from scratch (audit)\n\n```sh\ngit worktree add --detach /tmp/u v0.6.0\nbun scripts/gen-tree.ts /tmp/u --build\ndiff -rq /tmp/u . -x .git -x node_modules -x bun.lock -x Cargo.lock -x '*.generated.ts'\n# → empty means the fork is fully reproducible from upstream + the fork layer\n```\n",
|
|
8
9
|
"REBRANDING_PLAN_260525.md": "# SKC Rebranding Plan — 2026-05-26\n\n## Status\n\nApproved plan for the sayknow-cli/SKC rebrand and visible UI redesign. This document records the implementation contract to track in GitHub and preserve in-repo.\nGitHub tracking issue: https://github.com/jaybeyond/Sayknow_CLI/issues/3\n\n## Decision\n\nRedesign the visible SKC terminal, export, and documentation surfaces around a coherent red-octopus sayknow-cli identity while preserving clegacyatibility boundaries.\n\nThe default-visible product should read as **sayknow-cli / SKC**, not legacy upstream branding or a generic inherited terminal skin. Red-claw becomes the default dark visual direction for users without an explicit override. Session exports and README screenshots should show the same brand direction, while exported transcript content remains neutral and readable.\n\n## Principles\n\n1. **SKC-first visible identity** — Default-visible UI should present sayknow-cli/red-octopus as the current product identity.\n2. **Clegacyatibility preservation** — Keep `skc`, `skc-stats`, `skc-swarm`, `@sayknow-cli/*`, legacy runtime roots/env aliases, and explicit attribution/history.\n3. **Semantic color integrity** — Brand red/coral/shell colors must stay distinct from error, warning, and diff-removal semantics.\n4. **Readable fallbacks** — Truecolor, 256-color, Unicode, Nerd Font, ASCII, narrow terminal, and imperfect-font modes must remain usable.\n5. **Audit-friendly exports** — HTML exports and docs use SKC header/accent/metadata branding without making transcript content decorative or hard to review.\n6. **Visible workflow minimization** — Default repo-shipped visible skills/workflows remain limited to `deep-interview`, `ralplan`, `team`, and `ultragoal`.\n\n## Scope\n\n### In scope\n\n- Default dark theme and bundled red-octopus palette.\n- Visible TUI surfaces: welcome, status line, footer/keybinding hints, message frames, assistant/user/custom/system messages, tool execution cards, ask/approval cards, selectors/settings, todo/plan surfaces, transcript chrome, diff/tool output styling.\n- Status-line identity cutover away from default-visible legacy/Pi/powerline styling.\n- Session HTML export header/accent/metadata branding while preserving transcript readability.\n- README screenshots/alt text and docs pages that present current SKC UI/export identity.\n- Static scans and tests for current-product brand leaks, clegacyatibility names, theme defaults, fallback readability, and export branding.\n\n### Out of scope\n\n- Renaming `skc`, `skc-stats`, `skc-swarm`, or `@sayknow-cli/*` package surfaces.\n- Removing legacy runtime roots, env aliases, clegacyatibility internals, migration notes, generated/vendor content, or attribution/history solely because they mention legacy/Pi.\n- Copying OpenAI code provider, SST/opencode, Anthropic Code, or legacy upstream visuals verbatim.\n- Making exports decorative enough to reduce audit readability.\n- Replacing the TUI framework as part of the brand redesign.\n\n## Implementation Plan\n\n### Phase 1 — Inventory and allowlist\n\n- Search active visible UI/docs/export surfaces for old-brand and inherited UI identity markers: legacy upstream markers, `skc`, `pi`, `powerline`, and generic export labels.\n- Classify hits as current product identity, explicit user opt-in setting labels, clegacyatibility internals, attribution/history/migration notes, or generated/vendor content.\n- Build or update verification gates so current-product visible leaks fail, but clegacyatibility and attribution do not.\n\n### Phase 2 — Theme defaults and palette semantics\n\n- Make red-octopus the default dark visual direction for users without explicit theme overrides.\n- Separate brand tokens (`brandRed`, `claw`, `coral`, `shell`) from semantic tokens (`dangerRed`, `warningAmber`, `diffRemovalRed`).\n- Ensure accents, borders, markdown, status-line identity, and export header variables use brand tokens while errors, warnings, and removals use semantic tokens.\n- Add focused tests for default theme resolution and token separation.\n\n### Phase 3 — Status-line identity cutover\n\n- Remove Pi from bundled default-visible status presets or replace it with clegacyact SKC/claw identity.\n- Preserve legacy segment/symbol clegacyatibility only as explicit opt-in or internal alias behavior.\n- Change default separators away from powerline-like styling; keep powerline variants available only as explicit user choices.\n- Verify status-line overflow, narrow-width, and ASCII/minimal-symbol behavior.\n\n### Phase 4 — Coherent TUI clegacyonent pass\n\nUse existing theme tokens rather than a new UI framework abstraction.\n\n- Apply shell/ink backgrounds, coral/claw accents, clegacyact borders, and lower-noise hierarchy across visible clegacyonents.\n- Refresh welcome, status line, footer hints, message frames, tool cards, ask/approval cards, selectors/settings, todo/plan surfaces, and transcript chrome.\n- Keep high-frequency tool cards inspectable: tool name, path/args, status, diff preview, truncation/expand hints, and error states remain clearer than decoration.\n- Confirm Unicode/Nerd/ASCII fallbacks for new visible symbols.\n\n### Phase 5 — Export and docs alignment\n\n- Update HTML export title/header/metadata to present SKC session export branding.\n- Keep message bodies, code blocks, tool output, system prlegacyts, and transcript content neutral and high contrast.\n- Regenerate derived export templates if required by the repository workflow.\n- Update README screenshots/alt text and docs references so the demonstrated TUI/export direction matches the implemented default.\n\n### Phase 6 — Verification and review\n\n- Run focused theme/status/export/static-scan tests first.\n- Run package-local checks after focused tests pass.\n- Run cleanup/refactor review on changed files.\n- Rerun verification after cleanup.\n- Run final code review and resolve blockers before considering the implementation clegacylete.\n\n## Acceptance Criteria\n\n- [ ] Default dark theme resolves to red-octopus/SKC for users without explicit theme override.\n- [ ] Brand/accent tokens are distinct from error, warning, and diff-removal tokens.\n- [ ] Default-visible status-line identity no longer leads with legacy/Pi-style branding.\n- [ ] Default-visible status separators no longer use powerline-style styling unless explicitly opted in.\n- [ ] Visible TUI clegacyonents share one coherent SKC language across welcome, status line, footer hints, message frames, tool execution cards, ask/approval cards, selectors/settings, and todo/plan surfaces.\n- [ ] Static scans of active UI/docs/export surfaces do not present legacy/Pi as current product identity; clegacyatibility internals, attribution/history, generated/vendor content, and migration notes remain allowlisted.\n- [ ] Full session HTML export includes SKC header/accent/metadata branding while preserving neutral readable transcript content.\n- [ ] README screenshots and alt text show the same SKC/red-octopus brand direction as the TUI/export surfaces.\n- [ ] Redesign remains readable under fallback terminal modes, including ASCII/minimal-symbol operation.\n- [ ] Focused verification covers default theme, visible brand allowlist, export branding, and preserved clegacyatibility names.\n\n## Planned Evidence\n\nFocused tests/probes after implementation:\n\n```bash\nbun test packages/coding-agent/test/skc-ui-redesign.test.ts\nbun test packages/coding-agent/test/theme-auto-detection.test.ts packages/coding-agent/test/status-line-overflow.test.ts packages/coding-agent/test/status-line-path.test.ts\nbun scripts/verify-skc-ui-redesign.ts\nbun --cwd=packages/coding-agent run check\n```\n\nManual/render probes:\n\n1. Launch with no explicit theme config and capture welcome/status/footer/tool-card flow.\n2. Launch with explicit non-red theme config and confirm it is not overwritten.\n3. Render status line at normal and narrow widths for default, clegacyact, full, Nerd, ASCII, and preserved custom settings.\n4. Render representative tool executions: pending, success, error, diff added/removed, spilled/truncated output, and image fallback.\n5. Render selectors/settings and ask/approval cards under red-octopus and ASCII/minimal-symbol mode.\n6. Generate a full session HTML export and inspect header/title/metadata/accent variables plus transcript readability.\n7. Inspect README screenshots/alt text and clegacyare them against the generated full-session export direction.\n\n## Risks and Mitigations\n\n- **Brand red becomes error/removal red** — Add token-level tests and rendered probes for brand, error, warning, and diff states.\n- **User-selected themes/status settings are overwritten** — Change defaults and bundled presets only; test explicit non-red theme/custom status preservation.\n- **Visible legacy/Pi removal breaks legacy configs** — Keep clegacyatibility aliases internally or opt-in, while removing current-product default visibility.\n- **Visual pass becomes subjective churn** — Centralize design in existing theme tokens and focused snapshots/probes; avoid framework replacement.\n- **Exports become too decorative for audits** — Brand only header/accent/metadata; keep transcript/code/tool content neutral and high contrast.\n- **Terminal fallback regressions** — Verify ASCII/minimal-symbol and narrow-width render paths.\n\n## Approval State\n\nThis plan is approved for tracking. Implementation still requires normal code review and verification before clegacyletion.\n",
|
|
9
10
|
"ai-schema-normalize.md": "# AI tool-schema normalization\n\n`@sayknow-cli/ai` exposes one unified schema normalizer that providers consume\nbefore tools are sent on the wire. All walkers live in\n`packages/ai/src/utils/schema/normalize.ts`; the operational contract is\n`packages/ai/src/utils/schema/CONSTRAINTS.md`.\n\nThere is no separate `strict-mode.ts` module any more — OpenAI strict-mode\nsanitization, OpenAI Responses `oneOf` rewriting, Google/Vertex/Gemini-CLI\nsanitization, Cloud Code Assist Anthropic sanitization, and MCP sanitization all\nshare the same option-driven walk.\n\n## Entry points\n\nAll exports live under `@sayknow-cli/ai/utils/schema`:\n\n- `normalizeSchema(value, options)` — generic option-driven walker.\n- `normalizeSchemaForGoogle(value)` — Gemini / Vertex / Gemini CLI.\n- `normalizeSchemaForCCA(value)` — Cloud Code Assist Anthropic (Antigravity + GCA).\n- `normalizeSchemaForMCP(value)` — MCP inputSchemas before they enter the\n custom-tool registry. `tool-bridge.ts` runs every MCP `inputSchema` through\n this dispatcher.\n- `normalizeSchemaForOpenAIResponses(schema)` (alias\n `sanitizeSchemaForOpenAIResponses`) — rewrites `oneOf` → `anyOf` for the\n Responses family.\n- `sanitizeSchemaForStrictMode(schema)` and\n `enforceStrictSchema(schema)` / `tryEnforceStrictSchema(schema)` — the\n OpenAI strict-mode pipeline (sanitize → enforce). All three are exported\n from `normalize.ts`.\n- `adaptSchemaForStrict(schema, strict)` from `./adapt` — thin composer that\n wraps `tryEnforceStrictSchema` for provider call sites and consults\n `SKC_NO_STRICT` (env `SKC_NO_STRICT`) for the global bypass.\n\nRemoved in the unified-flow refactor:\n\n- `strict-mode.ts` (merged into `normalize.ts`).\n- `sanitize-google.ts` and `normalize-cca.ts` (replaced by\n `normalizeSchemaFor*` dispatchers).\n- `StringEnum` helper — use `z.enum([...])` directly; Zod's emitted JSON\n Schema is already wire-compatible with Google and other providers.\n- `sanitizeSchemaFor{Google,CCA,MCP}` / `prepareSchemaForCCA` — renamed to\n `normalizeSchemaFor{Google,CCA,MCP}`.\n\n## Dispatcher mapping\n\n| Provider transport(s) | Dispatcher |\n| -------------------------------------------------------------------- | -------------------------------------------- |\n| `openai-completions`, `openai-responses`, `openai-code-responses` | `adaptSchemaForStrict` (sanitize + enforce) |\n| `openai-responses` family (`oneOf` → `anyOf` only) | `normalizeSchemaForOpenAIResponses` |\n| `google-generative-ai`, `google-vertex`, Gemini CLI | `normalizeSchemaForGoogle` |\n| Cloud Code Assist Anthropic (Antigravity + GCA, `anthropic-model-*` model ids) | `normalizeSchemaForCCA` |\n| MCP `inputSchema` ingestion | `normalizeSchemaForMCP` |\n| `anthropic-messages` (native, not CCA) | per-provider whitelist in `anthropic.ts` |\n\nGemini CLI / Antigravity CCA MUST run the full `normalizeSchemaForCCA`\npipeline (not just the first keyword-stripping pass) to keep parity with the\nshared Google Anthropic path.\n\n## Walk semantics\n\n`normalizeSchema` first upgrades the input to JSON Schema 2020-12, then\nwalks the tree with the option set pinned by the dispatcher. Each node:\n\n1. Inlines `$ref` (see \"Edge cases\" below).\n2. Renames `snake_case` combinator/property keys to camelCase\n (`any_of` → `anyOf`, etc.; collisions follow python-genai\n `pop(from)`/`set(to)` semantics — snake_case wins).\n3. Applies the `handle_null_fields` collapse for nullable unions before\n recursing into children.\n4. Strips keys the target provider does not support, optionally lifting\n human-meaningful keys (`pattern`, `format`, min/max, `default`,\n `examples`, ...) into the sibling `description` via the spill formatter\n (`spill.ts`). Structural/meta keys (`$ref`, `$defs`,\n `additionalProperties`) are not spilled.\n5. Normalizes type unions (`type: [\"T\", \"null\"]` → `type: \"T\"` + nullable\n marker on Google, plain `type: \"T\"` on CCA).\n6. Collapses object-only / same-type combiners, optionally lossy-collapses\n mixed-type combiners (CCA only), and runs the residual-combiner fixpoint.\n7. Validates against AJV 2020 when `validateAndFallback` is set (CCA path)\n and emits the per-tool fallback `{ \"type\": \"object\", \"properties\": {} }`\n on residual incompatibility — `type` array, `type: \"null\"`, `nullable`\n key, or any remaining `anyOf`/`oneOf`/`allOf`.\n\n## OpenAI strict-mode pipeline\n\n`adaptSchemaForStrict(schema, strict)` runs `tryEnforceStrictSchema`,\nwhich composes:\n\n1. **Sanitize** (`sanitizeSchemaForStrictMode`): strips non-structural\n keywords (`format`, `pattern`, min/max, `examples`, `default`,\n `if`/`then`/`else`, `not`, `unevaluated*`, `patternProperties`,\n `dependent*`, `content*`, `min/maxProperties`, `$dynamicRef`, etc.). The\n `default` value is inlined into the sibling `description` as\n ` (default: X)` before being dropped, unless `description` already\n contains `(default:` or no `description` exists.\n2. **Enforce** (`enforceStrictSchema`): every object node gets\n `additionalProperties: false`, every property goes into `required`, and\n optional properties become nullable unions\n (`anyOf: [<original>, { \"type\": \"null\" }]`). Tuple `prefixItems` are\n strictified recursively.\n\nThe two passes share node-level caches and the same epoch-based cycle\nguard, so a single walk on the wire path normalizes refs, allOf, and\nnullable wrapping consistently. `tryEnforceStrictSchema` is fail-open:\nif anything throws, it returns `{ strict: false, schema: original }` so\ncallers MUST emit `strict: true` only when enforcement actually succeeded.\n\n### Edge cases the strict-mode normalizer handles\n\n- **Local `$ref` inlining.** OpenAI strict mode rejects\n `{ \"$ref\": \"...\", \"description\": \"...\" }` with sibling keys. The\n sanitizer pre-resolves local `#/...` refs against the root and merges\n with **sibling keys winning** over the resolved def — same precedence\n as `openai-python`'s `_ensure_strict_json_schema`. Recursive refs are\n guarded by the per-walk epoch.\n- **Single-item `allOf`.** A `{ \"allOf\": [X], ...siblings }` collapses to\n `{ ...X, ...siblings }` with the inlined entry's keys winning over the\n original siblings (matches `openai-python`'s `_pydantic.py:79-83`). Multi-\n item `allOf` is left intact for the downstream validator to reject if\n needed.\n- **Type-array branches and nullable unions.** When a node has\n `type: [\"T\", \"U\"]`, the sanitizer emits one variant schema per type,\n pruning type-specific keywords (e.g. `properties`/`required` only stay on\n the `object` variant, `items` only on the `array` variant). The shared\n `description` is **hoisted onto the `anyOf` wrapper** instead of being\n duplicated on every branch — so a strict nullable union becomes\n `{ anyOf: [T, { type: \"null\" }], description: \"...\" }`, not\n `anyOf: [{ ..., description }, { ..., description }]`.\n- **Enum/const without a `type`.** Both sanitize and enforce paths call\n `inferStrictPrimitiveTypeFromEnumOrConst` to infer the primitive `type`\n from `enum` / `const` values. Mixed-primitive enums (`[1, \"two\", null]`),\n enums containing objects/arrays, and non-primitive `const` values\n (`{a:1}`, `[1,2,3]`) cannot be described by a single `type` keyword and\n trigger the strict-mode fail-open path — emitting a typeless schema\n would just be rejected on the wire by OpenAI.\n\n## Performance: static fingerprint cache\n\n`resolveProviderModels` in `packages/ai/src/model-manager.ts` and\n`readModelCache`/`writeModelCache` in `model-cache.ts` cooperate via a\nschema-v3 `static_fingerprint` column on the `model_cache` SQLite table.\n\n- `fingerprintStatic(staticModels)` hashes the static catalog slice\n (`Bun.hash(JSON.stringify(models))` in base36) and memoizes the result\n in a per-process `WeakMap` keyed by the array reference. Multiple\n cold-start arms calling `resolveProviderModels` with the same\n `staticModels` array pay the JSON+hash cost once.\n- On cache read, if the network fetch is being skipped, the cached row is\n fresh + authoritative, and the cached `static_fingerprint` matches the\n current one, `resolveProviderModels` returns the cached models verbatim\n — the cache already incorporates the same static state, so re-running\n `mergeDynamicModels(static, cache)` would just rebuild the same objects.\n- `mergeModelSources` and `mergeDynamicModels` short-circuit on\n empty-source inputs (the common shape after `(static, [])` or for\n providers without a static catalog), avoiding Map churn entirely.\n\nCache rows written before schema v3 are dropped by the cache-version\ncheck; the column defaults to `''` for any row that survives a version\nupgrade so the fingerprint-equality check naturally fails closed and the\nfull merge re-runs.\n\n## Related\n\n- `docs/models.md` — registry, equivalence, compat flags\n (`supportsStrictMode`, `toolStrictMode`, `disableStrictTools`).\n- `docs/provider-streaming-internals.md` — how the normalized schemas are\n used downstream during the provider stream loop.\n- `packages/ai/src/utils/schema/CONSTRAINTS.md` — operational contract for\n every normalization rule.\n",
|
|
10
11
|
"analyze-me-with-skc.md": "# Analyze Me with SKC\n\nUse this prompt for meetup icebreakers where each participant asks SKC to introduce them from their own local SKC usage history.\n\nThe prompt is designed to be repeatable: it tells SKC what local artifacts to inspect, what patterns to extract, how to avoid leaking secrets, and how to turn the analysis into a short spoken self-introduction.\n\n## Full prompt\n\n```text\n~/.skc 에 있는 내 가재코드 사용내역을 바탕으로, 가재코드 밋업 아이스브레이킹용 “가재코드가 보는 나” 자기소개 글을 작성해줘.\n\n목표:\n- 내 실제 가재코드 사용패턴을 분석해서, 내가 어떤 개발자/빌더/운영자인지 소개하는 글을 써줘.\n- 단순 통계 나열이 아니라, 사용 습관과 관심사에서 드러나는 성향을 해석해줘.\n- 밋업에서 2~4분 정도 읽을 수 있는 분량으로 작성해줘.\n- 너무 딱딱한 리포트 말고, 사람 소개글처럼 재미있고 선명하게 써줘.\n- 과장하거나 없는 사실을 만들지 말고, 실제 ~/.skc 기록에서 관찰된 패턴만 근거로 삼아줘.\n\n분석 지시:\n1. ~/.skc 디렉터리 구조를 먼저 확인해줘.\n2. 가능한 경우 아래의 안전한 메타데이터 중심 자료만 분석해줘:\n - ~/.skc/agent/history.db 의 집계값\n - ~/.skc/agent/sessions/**/*.jsonl 의 세션 메타데이터(세션 제목, timestamp, cwd, 메시지 수, tool-call 수, 파일 크기, subagent/task 이름)\n - ~/.skc 내부의 추가 파일은 사용패턴 집계에 꼭 필요하고 민감정보가 없다고 판단되는 경우에만 읽어줘.\n - 기본적으로 ~/.skc/logs/*, auth/config/credential 파일, env dump, raw tool-result body, raw prompt body, secret-like 값은 읽지 마.\n3. history.db에서는 최소한 다음을 봐줘:\n - 전체 프롬프트 수\n - 기간 범위\n - 작업 디렉터리 / 레포지토리 분포\n - 자주 등장하는 주제어\n - 짧은 명령과 긴 프롬프트의 비율\n - /skill:ultragoal, /skill:ralplan, /skill:deep-interview, /skill:team 사용 빈도\n - continue, fix, review, merge, PR, CI, test, verify, implement, delegate 같은 실행/검증 관련 단어 빈도\n4. sessions jsonl에서는 가능하면 다음을 봐줘:\n - 메인 세션 수\n - 서브에이전트 / task 세션 수\n - 짧은 세션과 긴 세션의 분포\n - 세션 title 또는 worktree 이름에서 보이는 관심사\n - 장기 실행, 병렬 위임, 검증, 리뷰, 릴리스 운영 흔적\n5. 레포지토리와 주제 다양성을 꼭 반영해줘:\n - 어떤 레포지토리/워크트리에서 많이 일했는지\n - SKC core, 개인 프로젝트, 연구/quant, infra, UI, automation, image/media 등 주제 범위가 보이면 묶어서 설명해줘.\n6. 민감정보는 절대 노출하지 마:\n - API key, 토큰, credential, 개인 연락처, 로컬 secret, private URL, 인증정보는 출력하지 마.\n - 프롬프트 예시는 필요할 때만 짧게 paraphrase해서 써줘.\n - 파일 경로나 레포 이름은 자기소개에 필요한 수준으로만 언급해줘.\n - 분석 중에도 민감정보를 모델 컨텍스트에 올리지 않도록 metadata-first / aggregate-only 방식으로 처리해줘.\n\n출력 형식:\n\n먼저 아주 짧게 “분석한 근거”를 3~6개 bullet로 요약해줘.\n예:\n- 분석 기간:\n- 프롬프트 수:\n- 주요 작업 공간:\n- 세션 패턴:\n- 자주 보인 workflow/skill:\n- 주요 관심사:\n\n그 다음 아래 제목으로 자기소개 글을 써줘:\n\n# 가재코드가 보는 나\n\n글 스타일:\n- 한국어로 작성.\n- 살짝 위트 있게.\n- “당신은 …” 또는 “나는 …” 중 더 자연스러운 쪽을 선택해도 됨.\n- 밋업에서 읽기 좋게 문단을 나눠줘.\n- 너무 아부하지 말고, 사용패턴에서 드러나는 장점과 특이한 습관을 솔직하게 말해줘.\n- 마지막에는 한 문장으로 요약해줘:\n “한 문장으로 말하면, 나는 ___ 하는 사람이다.”\n\n추가로 마지막에 선택사항으로 아래 3개를 붙여줘:\n\n## 10초 버전\n한두 문장짜리 초단기 자기소개.\n\n## 한 줄 별명\n사용패턴 기반 별명 3개.\n\n## 밋업용 오프닝 멘트\n처음 인사할 때 바로 읽을 수 있는 20~30초짜리 멘트.\n\n주의:\n- 분석 없이 일반론으로 쓰지 마.\n- 실제 ~/.skc 기록을 읽고 나서 작성해.\n- 숫자를 말할 때는 실제로 확인한 숫자만 써.\n- 확인하지 못한 항목은 “확인 불가”라고 하지 말고, 그 항목을 빼고 자연스럽게 작성해.\n```\n\n## Short meetup prompt\n\nUse this when participants need a shorter copy/paste prompt.\n\n```text\n~/.skc 사용내역을 분석해서 밋업 아이스브레이킹용 “가재코드가 보는 나” 자기소개 글을 써줘.\n\n반드시 실제 ~/.skc 기록을 읽되, 안전한 메타데이터와 집계값 중심으로 근거 기반 작성해:\n- history.db의 프롬프트 수, 기간, cwd/레포 분포, 자주 쓰는 단어, skill 사용량\n- sessions jsonl의 세션 수, 세션 길이 다양성, subagent/task 사용 흔적\n- 레포지토리/주제 다양성\n- 짧은 명령 vs 긴 지시문 패턴\n- 실행/검증/리뷰/PR/CI/릴리스/위임 습관\n\n민감정보는 읽지도 출력하지도 마. API key, 토큰, private credential, 개인 secret, 긴 원문 프롬프트, raw tool-result body, ~/.skc/logs/*, auth/config/env dump는 기본적으로 건너뛰고, 필요한 경우에도 안전한 집계값과 짧은 paraphrase만 써.\n\n출력:\n1. 분석 근거 bullet 3~6개\n2. 제목: “가재코드가 보는 나”\n3. 밋업에서 2~4분 읽을 수 있는 한국어 자기소개 글\n4. 마지막에:\n - 10초 버전\n - 사용패턴 기반 별명 3개\n - 20~30초 오프닝 멘트\n\n스타일:\n- 재미있고 선명하게\n- 과장 없이\n- 통계 나열보다 “이 사람이 어떤 식으로 일하는 사람인지” 해석 중심\n- 마지막 문장은 “한 문장으로 말하면, 나는 ___ 하는 사람이다.”\n```\n\n## Optional: SayknowTI prompt\n\nA meetup host can also turn the same analysis into a playful, MBTI-like “SayknowTI” result. This is only an icebreaker taxonomy, not a psychological assessment.\n\n```text\n~/.skc 사용내역을 안전한 메타데이터와 집계값 중심으로 분석해서, 밋업 아이스브레이킹용 “가재TI”를 만들어줘.\n\n목표:\n- MBTI처럼 4글자 코드와 타입명을 만들되, 실제 성격검사가 아니라 가재코드 사용패턴 기반의 재미있는 작업 스타일 분류로 작성해.\n- 실제 ~/.skc 기록에서 확인한 사용패턴만 근거로 삼아줘.\n- 민감정보는 읽지도 출력하지도 마. API key, 토큰, private credential, 개인 secret, 긴 원문 프롬프트, raw tool-result body, ~/.skc/logs/*, auth/config/env dump는 기본적으로 건너뛰고, 안전한 집계값과 짧은 paraphrase만 써.\n\n먼저 아래 4개 축을 기준으로 타입을 판정해줘. 각 축은 한쪽을 고르되, 애매하면 근거와 함께 중간 성향이라고 설명해.\n\n1. E / P — Execute vs Plan\n - E: fix, implement, merge, ship, PR, CI, release처럼 실행/운영 명령이 강함.\n - P: deep-interview, ralplan, spec, architecture, review처럼 계획/정의/합의 흐름이 강함.\n\n2. S / M — Sprint vs Marathon\n - S: 짧은 명령, 빠른 follow-up, `continue`, 작은 세션이 많음.\n - M: 장기 세션, durable goal, 긴 프롬프트, 며칠짜리 작업 흐름이 많음.\n\n3. C / D — Craft vs Delegate\n - C: 직접 구현/수정/탐색 중심.\n - D: subagent, executor, architect, critic, team, parallel delegation 사용이 강함.\n\n4. X / O — Explore vs Operate\n - X: 새로운 모델/provider/tool/research/실험 주제가 많음.\n - O: PR, CI, release, changelog, version, production 운영/마감 흐름이 많음.\n\n분석할 최소 근거:\n- history.db의 프롬프트 수, 기간, cwd/레포 분포, 자주 쓰는 단어, skill 사용량\n- sessions jsonl의 세션 수, 세션 길이 다양성, subagent/task 흔적\n- 레포지토리/주제 다양성\n- 짧은 명령 vs 긴 지시문 패턴\n- 실행/검증/리뷰/PR/CI/릴리스/위임 습관\n\n출력 형식:\n\n# 나의 가재TI: <4글자 코드> — <타입명>\n\n## 판정 근거\n- E/P: <선택> — <실제 집계 또는 관찰 근거>\n- S/M: <선택> — <실제 집계 또는 관찰 근거>\n- C/D: <선택> — <실제 집계 또는 관찰 근거>\n- X/O: <선택> — <실제 집계 또는 관찰 근거>\n\n## 타입 설명\n밋업에서 1분 정도 읽을 수 있게, 이 사람이 가재코드를 어떻게 쓰는 사람인지 재미있게 설명해줘.\n\n## 강점\n3개 bullet.\n\n## 주의할 점\n놀리는 느낌은 살짝 있어도 되지만, 비하하지 말고 작업 습관상 조심할 점 2~3개.\n\n## 어울리는 밋업 별명\n3개.\n\n주의:\n- 이건 성격검사가 아니라 사용패턴 기반 밋업 놀이야.\n- 숫자는 실제로 확인한 값만 써.\n- 확인하지 못한 축은 억지로 단정하지 말고 “근거 부족” 또는 “혼합형”이라고 써.\n```\n",
|
|
@@ -12,9 +13,9 @@ export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
|
|
|
12
13
|
"auth-broker-gateway.md": "# Auth Broker and Auth Gateway\n\nThe auth broker and auth gateway are two cooperating HTTP services that move OAuth refresh tokens and provider access tokens off developer laptops and into a single broker host.\n\n- **`skc auth-broker serve`** holds the canonical SQLite credential vault, performs OAuth refreshes, and exposes a small REST API (`/v1/snapshot`, `/v1/credential/:id/refresh`, `/v1/credential/:id/disable`, `/v1/credential`, `/v1/usage`, `/v1/healthz`).\n- **`skc auth-gateway serve`** is a forward-proxy. It accepts OpenAI Chat Completions, Anthropic Messages, and OpenAI Responses requests, injects the broker-resolved access token, and forwards the bytes to the real provider. Clients (containerised skc, llm-git, the macOS usage widget, …) never see the access token.\n\nTransport security between operator, broker, and gateway is delegated to the operator (Tailscale / Wireguard / reverse proxy + TLS). Every endpoint except `/v1/healthz` (broker) and `/healthz` (gateway) requires a bearer token.\n\nSource: `packages/ai/src/auth-broker/`, `packages/ai/src/auth-gateway/`, `packages/coding-agent/src/cli/auth-broker-cli.ts`, `packages/coding-agent/src/cli/auth-gateway-cli.ts`, `packages/coding-agent/src/session/auth-broker-config.ts`.\n\n## Data flow\n\n```\n ┌────────────────────────────────────────────────────────────┐\n │ broker host │\n │ │\n developer ──▶ │ ┌──────────────────────────┐ ┌────────────────────┐ │\n laptop / │ │ skc auth-broker serve │◀──▶│ SQLite agent.db │ │\n CI / roboskc │ │ - holds refresh tokens │ │ (canonical writer)│ │\n │ │ - background refresher │ └────────────────────┘ │\n │ │ /v1/{snapshot,refresh,…}│ │\n │ └─────────┬────────────────┘ │\n │ │ bearer ($CONFIG_DIR/auth-broker.token) │\n │ ▼ │\n │ ┌──────────────────────────┐ │\n │ │ skc auth-gateway serve │ RemoteAuthCredentialStore │\n │ │ /v1/{chat,messages,…} │ pulls /v1/snapshot at boot, │\n │ │ /v1/usage, /v1/models │ refreshes credentials by id │\n │ └─────────┬────────────────┘ via the broker on expiry │\n └────────────┼───────────────────────────────────────────────┘\n │ bearer ($CONFIG_DIR/auth-gateway.token)\n ▼\n unauthenticated clients\n (llm-git, macOS widget, roboskc containers, IDE plugins, …)\n │\n ▼ same path is forwarded with Authorization\n api.anthropic.com / api.openai.com / …\n```\n\nThe broker is the only writer of OAuth refresh tokens. Clients (including the gateway itself) load a redacted snapshot in which every `refresh` field has been replaced with `REMOTE_REFRESH_SENTINEL`; when an access token expires the client calls `POST /v1/credential/:id/refresh` and the broker performs the refresh server-side. `RemoteAuthCredentialStore` rejects any local code path that tries to write through it, with an error pointing at `skc auth-broker login` / `skc auth-broker logout`.\n\n## auth-broker\n\n### CLI\n\n```\nskc auth-broker serve [--bind=host:port] # boot the broker\nskc auth-broker token [--regenerate] [--json] # print or rotate the bearer token\nskc auth-broker login <provider> [--via=user@host] [--dry-run]\nskc auth-broker logout <provider>\nskc auth-broker import <file|dir> [--provider=<id>] [--include-disabled] [--dry-run] [--json]\nskc auth-broker migrate --from-local [--dry-run] [--json]\nskc auth-broker status [--json]\n```\n\n- `serve` opens the local SQLite store at `getAgentDbPath()` and binds an HTTP listener (default `127.0.0.1:8765`). On startup a token is ensured at `<config-dir>/auth-broker.token` (mode `0600`, `0700` parent dir). The background refresher refreshes any OAuth credential whose `expires - Date.now() < refreshSkewMs` (default 5 min) every `refreshIntervalMs` (default 60 s).\n- `token` prints the cached bearer or generates a new one. `--regenerate` rotates it.\n- `login <provider>` runs the per-provider OAuth flow locally, or — with `--via=user@host` — `ssh -L <callback-port>:127.0.0.1:<callback-port> user@host skc auth-broker login <provider>` so the OAuth callback hits the local browser but the credential is written on the broker host. Built-in callback ports: `anthropic:54545`, `openai-code:1455`, `google-gemini-cli:8085`, `google-antigravity:51121`, `gitlab-duo:8080`.\n- `logout <provider>` deletes every credential row for `<provider>`.\n- `import <file|dir>` imports CLIProxyAPI-style JSON credentials into the local SQLite store. Maps `type` field → skc provider (`anthropic-model → anthropic`, `openai-code → openai-code`, `gemini → google-gemini-cli`, `antigravity → google-antigravity`, `gemini-cli → google-gemini-cli`).\n- `migrate --from-local` walks the local SQLite store + env-derived credentials and idempotently uploads them to the configured broker (`POST /v1/credential`).\n- `status` health-pings the configured remote broker.\n\n### Endpoints\n\n| Method | Path | Auth | Purpose |\n| ------ | ---- | ---- | ------- |\n| `GET` | `/v1/healthz` | none | Liveness + version |\n| `GET` | `/v1/snapshot` | bearer | Redacted snapshot (refresh tokens replaced by sentinel) |\n| `POST` | `/v1/credential` | bearer | Upsert one OAuth or API-key credential |\n| `POST` | `/v1/credential/:id/refresh` | bearer | Force-refresh one OAuth credential |\n| `POST` | `/v1/credential/:id/disable` | bearer | Disable one credential with a recorded cause |\n| `GET` | `/v1/usage` | bearer | Aggregate `UsageReport[]` across credentials |\n\nRequests use `Authorization: Bearer <token>`. The server compares against an in-memory token allow-list; the gateway’s implementation uses a timing-safe comparison.\n\n### Background refresher\n\n`AuthBrokerRefresher` iterates active OAuth credentials at `refreshIntervalMs` cadence and refreshes any within `refreshSkewMs` of expiry. Refreshes are single-flighted per credential id so a slow refresh cannot be retriggered. The refresher distinguishes:\n\n- **definitive failures** (`invalid_grant`, `invalid_token`, `revoked`, unauthorized refresh-token, 401/403 not from a network blip) — credentials are passed to `AuthStorage.disableCredentialById(id, cause)` so the next snapshot pull surfaces a clean delete on the client;\n- **transient failures** (timeout / ECONNREFUSED / fetch failed) — left in place for the next sweep.\n\n## auth-gateway\n\n### CLI\n\n```\nskc auth-gateway serve [--bind=host:port] [--no-auth]\nskc auth-gateway token [--regenerate] [--json]\nskc auth-gateway status [--json]\n```\n\n- `serve` requires `SKC_AUTH_BROKER_URL` (or `auth.broker.url` in `config.yml`) — the gateway is itself a broker client. It calls `AuthBrokerClient.fetchSnapshot()`, wraps it in `RemoteAuthCredentialStore`, and constructs an `AuthStorage` that resolves access tokens through the broker. Default bind is `127.0.0.1:4000`. The gateway token is stored at `<config-dir>/auth-gateway.token` (`0600`); `--no-auth` disables the bearer check entirely (loopback-only use).\n- `token` / `status` mirror the broker’s equivalents.\n\n### Endpoints\n\n| Method | Path | Auth | Purpose |\n| ------ | ---- | ---- | ------- |\n| `GET` | `/healthz` | none | Liveness + version |\n| `GET` | `/v1/usage` | bearer | Aggregate `UsageReport[]` (proxied through `AuthStorage`) |\n| `GET` | `/v1/models` | bearer | Bundled-model catalog filtered to providers with credentials |\n| `POST` | `/v1/chat/completions` | bearer | OpenAI Chat Completions wire format |\n| `POST` | `/v1/messages` | bearer | Anthropic Messages wire format |\n| `POST` | `/v1/responses` | bearer | OpenAI Responses wire format |\n\nThe model id is read from the top-level `model` field. The gateway picks the first bundled `Model<Api>` matching that id and:\n\n- **Passthrough fast-path** — when the inbound wire format matches the model’s native API (`openai-chat → openai-completions`, `anthropic-messages → anthropic-messages`, `openai-responses → openai-responses`), the request body is forwarded byte-for-byte with the client `Authorization`/`x-api-key` stripped and replaced by `Authorization: Bearer <resolved-access-token>`. Provider-specific fields (`cache_control`, `service_tier`, tool-choice extensions, …) flow through unmodified. Hop-by-hop headers (RFC 7230) plus `Content-Encoding`/`Content-Length` are stripped from the upstream response.\n- **Translate path** — when the inbound format and the resolved model’s API differ (e.g. `/v1/chat/completions` targeting an Anthropic model, or `/v1/responses` targeting `openai-code-responses` which runs over a websocket transport), the request is parsed against the wire schema, rebuilt into an skc `Context`, dispatched through `streamSimple()`, and re-encoded back to the inbound format (SSE for streamed responses).\n\n`idleTimeout` on the underlying `Bun.serve` is set to `255 s` so long thinking-budget calls do not get killed by Bun’s default idle timeout.\n\n## Usage cache: server-side 5-min jitter + client-side 15 s single-flight\n\nTwo layers cache the aggregate provider-usage report. Both are intentional and stacked.\n\n### Server-side cache (broker `AuthStorage`)\n\n`AuthStorage` caches each credential’s `UsageReport` in the broker’s SQLite store at a **5-minute per-credential TTL with ±25 % jitter**. Anthropic and OpenAI rate-limit `/usage` aggressively per source IP, and a synchronized 5-credential fan-out trips 429s every cycle; the jitter decorrelates refresh times within a few cycles. On fetch failure the store keeps the **last-good** report for up to 24 h with a short jittered re-poll window — so a transient upstream blip never blanks out the widget.\n\nConstants: `USAGE_REPORT_TTL_MS = 5 * 60_000`, `USAGE_LAST_GOOD_RETENTION_MS = 24 * 60 * 60_000` (`packages/ai/src/auth-storage.ts`).\n\n### Client-side single-flight (`RemoteAuthCredentialStore`)\n\nWhen the gateway (or any other broker client) calls `fetchUsageReports()` / `getUsageReport(provider, credential)`, `RemoteAuthCredentialStore` coalesces concurrent calls into a single `GET /v1/usage` round-trip and caches the result for **15 s** in memory.\n\n- `USAGE_CACHE_TTL_MS = 15_000` (`packages/ai/src/auth-broker/remote-store.ts`).\n- A single `#usageInflight` promise is shared across all callers; a per-caller `AbortSignal` is **raced** against the shared promise, not threaded into it, so one caller’s abort never cascades into a peer’s in-flight request.\n- On fetch failure the rejected promise is logged and the awaited value is `null` — callers (`AuthStorage.fetchUsageReports`, `#getUsageReport`) treat a `null` report as \"no usage signal for this cycle\" and proceed without it. **This is the 15 s TTL fallback**: the client absorbs transient broker outages by suppressing the error, returning `null` to ranking, and re-attempting after the 15 s window.\n\nThe 15 s client window deliberately sits below the broker’s 5 min server cache, so almost every client poll is served from the broker’s already-cached value; the client cache exists to absorb the parallel fan-out generated by `AuthStorage.#rankOAuthSelections` into a single broker round-trip.\n\n## Operator opt-in\n\nThe broker is **off** unless `SKC_AUTH_BROKER_URL` (or `auth.broker.url` in `config.yml`) is set. When set, `discoverAuthStorage` in `packages/coding-agent/src/sdk.ts` swaps the local SQLite credential store for `RemoteAuthCredentialStore` and every API call resolves credentials through the broker.\n\n### Environment variables\n\n| Variable | Purpose | Required when |\n| -------- | ------- | ------------- |\n| `SKC_AUTH_BROKER_URL` | Base URL of the remote auth-broker (e.g. `https://broker.tailnet:8765`). Selecting this puts the client in broker mode — local SQLite is bypassed. | Any time the skc client should resolve credentials through a broker (and required by `skc auth-gateway serve`). |\n| `SKC_AUTH_BROKER_TOKEN` | Bearer token used for every broker endpoint except `/v1/healthz`. | When `SKC_AUTH_BROKER_URL` is set and no token is available from `auth.broker.token` or `<config-dir>/auth-broker.token`. |\n\nResolution order in `resolveAuthBrokerConfig()`:\n\n1. `SKC_AUTH_BROKER_URL` env (else `auth.broker.url` from `config.yml`, with `$ENV_NAME` resolution);\n2. `SKC_AUTH_BROKER_TOKEN` env (else `auth.broker.token` from `config.yml`, else `<config-dir>/auth-broker.token`);\n3. URL set but no token resolvable → hard error pointing at the token file path.\n\nThe gateway has no dedicated env vars — it inherits `SKC_AUTH_BROKER_*` because it is itself a broker client.\n\n### `config.yml` keys\n\n| Key | Default | Purpose |\n| --- | ------- | ------- |\n| `auth.broker.url` | unset | Same as `SKC_AUTH_BROKER_URL`; env wins. Hidden from the settings UI. |\n| `auth.broker.token` | unset | Same as `SKC_AUTH_BROKER_TOKEN`; env wins. Values may be the literal token or `$ENV_NAME` to indirect through env. |\n\n### Token files\n\n| Path | Owner | Mode |\n| ---- | ----- | ---- |\n| `<config-dir>/auth-broker.token` | `skc auth-broker serve` (created at first start) | `0600` in a `0700` parent dir |\n| `<config-dir>/auth-gateway.token` | `skc auth-gateway serve` (skipped under `--no-auth`) | `0600` in a `0700` parent dir |\n\n`<config-dir>` resolves to `~/.skc/` (respecting `SKC_CONFIG_DIR`).\n\n## Interaction with the local API-key resolution order\n\nThe broker only owns OAuth credentials and provider-API-key credentials that were uploaded to it. The standard credential ladder in `models.md` (`Auth and API key resolution order`) is preserved, with one addition committed alongside the gateway:\n\n- `AuthStorage.setConfigApiKey / removeConfigApiKey / clearConfigApiKeys` let a `models.yml` `apiKey` beat a stored OAuth token **without** overriding an explicit `--api-key`. This is what allows a broker-resolved OAuth credential to be reliably shadowed by a per-environment `models.yml` config key when both are present.\n\n## See also\n\n- [`secrets.md`](./secrets.md) — secret obfuscation around tokens that *do* leak through (e.g. `SKC_AUTH_BROKER_TOKEN` in shell output).\n- [`models.md`](./models.md) — provider auth resolution order; the broker plugs in at layers 2–3 (stored credentials).\n- [`environment-variables.md`](./environment-variables.md) — full env reference including `SKC_AUTH_BROKER_URL` / `SKC_AUTH_BROKER_TOKEN`.\n",
|
|
13
14
|
"bash-tool-runtime.md": "# Bash tool runtime\n\nThis document describes the **`bash` tool** runtime path used by agent tool calls, from command normalization to execution, truncation/artifacts, and rendering.\n\nIt also calls out where behavior diverges in interactive TUI, print mode, RPC mode, and user-initiated bang (`!`) shell execution.\n\n## Scope and runtime surfaces\n\nThere are two different bash execution surfaces in coding-agent:\n\n1. **Tool-call surface** (`toolName: \"bash\"`): used when the model calls the bash tool.\n - Entry point: `BashTool.execute()`.\n - Parameters include `command`, optional `env`, `timeout`, `cwd`, `head`, `tail`, `pty`, and, when `async.enabled` is true, `async`.\n2. **User bang-command surface** (`!cmd` from interactive input or RPC `bash` command): session-level helper path.\n - Entry point: `AgentSession.executeBash()`.\n\nBoth eventually use `executeBash()` in `src/exec/bash-executor.ts` for non-PTY execution, but only the tool-call path runs normalization/interception, optional managed background-job handling, and tool renderer logic.\n\n## End-to-end tool-call pipeline\n\n## 1) Input handling and parameter merge\n\n`BashTool.execute()` currently handles input before execution as follows:\n\n- validates optional `env` names against shell-variable syntax,\n- extracts a leading `cd <path> && ...` into `cwd` when `cwd` was not supplied,\n- rejects `async: true` when `async.enabled` is false,\n- uses only explicit `head`/`tail` tool args for post-run filtering.\n\n`normalizeBashCommand()` still exists in `src/tools/bash-normalize.ts`, but `BashTool.execute()` does not call it in the current source. Trailing shell pipes such as `| head -n 50` remain part of the shell command unless the caller uses the structured `head`/`tail` args.\n\n## 2) Optional interception (blocked-command path)\n\nIf `bashInterceptor.enabled` is true, `BashTool` loads rules from settings and runs `checkBashInterception()` against the normalized command.\n\nInterception behavior:\n\n- command is blocked **only** when:\n - regex rule matches, and\n - the suggested tool is present in `ctx.toolNames`.\n- invalid regex rules are silently skipped.\n- on block, `BashTool` throws `ToolError` with message:\n - `Blocked: ...`\n - original command included.\n\nDefault rule patterns (defined in code) target common misuses:\n\n- file readers (`cat`, `head`, `tail`, ...)\n- search tools (`grep`, `rg`, ...)\n- file finders (`find`, `fd`, ...)\n- in-place editors (`sed -i`, `perl -i`, `awk -i inplace`)\n- shell redirection writes (`echo ... > file`, heredoc redirection)\n\n### Caveat\n\n`InterceptionResult` includes `suggestedTool`, but `BashTool` currently surfaces only the message text (no structured suggested-tool field in `details`).\n\n## 3) CWD validation and timeout clamping\n\n`cwd` is resolved relative to session cwd (`resolveToCwd`), then validated via `stat`:\n\n- missing path -> `ToolError(\"Working directory does not exist: ...\")`\n- non-directory -> `ToolError(\"Working directory is not a directory: ...\")`\n\nTimeout is clamped to `[1, 3600]` seconds and converted to milliseconds.\n\n## 4) Artifact allocation\n\nBefore execution, the tool allocates an artifact path/id (best-effort) for truncated output storage.\n\n- artifact allocation failure is non-fatal (execution continues without artifact spill file),\n- artifact id/path are passed into execution path for full-output persistence on truncation.\n\n## 5) PTY vs non-PTY execution selection\n\n`BashTool` chooses PTY execution only when all are true:\n\n- tool input `pty === true`\n- `SKC_NO_PTY !== \"1\"`\n- tool context has UI (`ctx.hasUI === true` and `ctx.ui` set)\n\nOtherwise it uses non-interactive `executeBash()`.\n\nThat means print mode and non-UI RPC/tool contexts always use non-PTY.\n\n## Non-interactive execution engine (`executeBash`)\n\n## Shell session reuse model\n\n`executeBash()` caches native `Shell` instances in a process-global map keyed by:\n\n- shell path,\n- configured command prefix,\n- snapshot path,\n- serialized shell env,\n- optional agent session key.\n\nSession-level bang-command executions pass `sessionKey: this.sessionId`.\n\nTool-call executions pass `sessionKey: this.session.getSessionId?.()`, when available. In both surfaces, a session key isolates shell reuse per session; without one, reuse falls back to shell config/snapshot/env.\n\n## Shell config and snapshot behavior\n\nAt each call, executor loads settings shell config (`shell`, `env`, optional `prefix`).\n\nIf selected shell includes `bash`, it attempts `getOrCreateSnapshot()`:\n\n- snapshot captures aliases/functions/options from user rc,\n- snapshot creation is best-effort,\n- failure falls back to no snapshot.\n\nIf `prefix` is configured, command becomes:\n\n```text\n<prefix> <command>\n```\n\n## Streaming and cancellation\n\n`Shell.run()` streams chunks to `OutputSink` and optional `onChunk` callback.\n\nCancellation:\n\n- aborted signal triggers `shellSession.abort(...)`,\n- timeout from native result is mapped to `cancelled: true` + annotation text,\n- explicit cancellation similarly returns `cancelled: true` + annotation.\n\nNo exception is thrown inside executor for timeout/cancel; it returns structured `BashResult` and lets caller map error semantics.\n\n## Interactive PTY path (`runInteractiveBashPty`)\n\nWhen PTY is enabled, tool runs `runInteractiveBashPty()` which opens an overlay console component and drives a native `PtySession`.\n\nBehavior highlights:\n\n- xterm-headless virtual terminal renders viewport in overlay,\n- keyboard input is normalized (including Kitty sequences and application cursor mode handling),\n- `esc` while running kills the PTY session,\n- terminal resize propagates to PTY (`session.resize(cols, rows)`).\n\nEnvironment hardening defaults are injected for unattended runs:\n\n- pagers disabled (`PAGER=cat`, `GIT_PAGER=cat`, etc.),\n- editor prompts disabled (`GIT_EDITOR=true`, `EDITOR=true`, ...),\n- terminal/auth prompts reduced (`GIT_TERMINAL_PROMPT=0`, `SSH_ASKPASS=/usr/bin/false`, `CI=1`),\n- package-manager/tool automation flags for non-interactive behavior.\n\nPTY output is normalized (`CRLF`/`CR` to `LF`, `sanitizeText`) and written into `OutputSink`, including artifact spill support.\n\nOn PTY startup/runtime error, sink receives `PTY error: ...` line and command finalizes with undefined exit code.\n\n## Output handling: streaming, truncation, artifact spill\n\nBoth PTY and non-PTY paths use `OutputSink`.\n\n## OutputSink semantics\n\n- keeps an in-memory UTF-8-safe tail buffer (`DEFAULT_MAX_BYTES`, currently 50KB),\n- tracks total bytes/lines seen,\n- if artifact path exists and output overflows (or file already active), writes full stream to artifact file,\n- when memory threshold overflows, trims in-memory buffer to tail (UTF-8 boundary safe),\n- marks `truncated` when overflow/file spill occurs.\n\n`dump()` returns:\n\n- `output` (possibly annotated prefix),\n- `truncated`,\n- `totalLines/totalBytes`,\n- `outputLines/outputBytes`,\n- `artifactId` if artifact file was active.\n\n### Long-output caveat\n\nRuntime truncation is byte-threshold based in `OutputSink` (50KB default). It does not enforce a hard 2000-line cap in this code path.\n\n## Live tool updates and async jobs\n\nFor non-PTY foreground execution, `BashTool` uses a separate `TailBuffer` for partial updates and emits `onUpdate` snapshots while command is running.\n\nFor PTY execution, live rendering is handled by custom UI overlay, not by `onUpdate` text chunks.\n\nWhen `async.enabled` is true and the call passes `async: true`, `BashTool` starts a managed bash job, returns a running job result with a job id, and stores completion through the session managed-job path. Auto-backgrounding can also start this path after `bash.autoBackground.thresholdMs`.\n\n## Result shaping, metadata, and error mapping\n\nAfter execution:\n\n1. `cancelled` handling:\n - if abort signal is aborted -> throw `ToolAbortError` (abort semantics),\n - else -> throw `ToolError` (treated as tool failure).\n2. PTY `timedOut` -> throw `ToolError`.\n3. apply head/tail filters to final output text (`applyHeadTail`, head then tail).\n4. empty output becomes `(no output)`.\n5. attach truncation metadata via `toolResult(...).truncationFromSummary(result, { direction: \"tail\" })`.\n6. exit-code mapping:\n - missing exit code -> `ToolError(\"... missing exit status\")`\n - non-zero exit -> `ToolError(\"... Command exited with code N\")`\n - zero exit -> success result.\n\nSuccess payload structure:\n\n- `content`: text output,\n- `details.meta.truncation` when truncated, including:\n - `direction`, `truncatedBy`, total/output line+byte counts,\n - `shownRange`,\n - `artifactId` when available.\n\nBecause built-in tools are wrapped with `wrapToolWithMetaNotice()`, truncation notice text is appended to final text content automatically (for example: `Full: artifact://<id>`).\n\n## Rendering paths\n\n## Tool-call renderer (`bashToolRenderer`)\n\n`bashToolRenderer` is used for tool-call messages (`toolCall` / `toolResult`):\n\n- collapsed mode shows visual-line-truncated preview,\n- expanded mode shows all currently available output text,\n- warning line includes truncation reason and `artifact://<id>` when truncated,\n- timeout value (from args) is shown in footer metadata line.\n\n### Caveat: full artifact expansion\n\n`BashRenderContext` has `isFullOutput`, but current renderer context builder does not set it for bash tool results. Expanded view still uses the text already in result content (tail/truncated output) unless another caller provides full artifact content.\n\n## User bang-command component (`BashExecutionComponent`)\n\n`BashExecutionComponent` is for user `!` commands in interactive mode (not model tool calls):\n\n- streams chunks live,\n- collapsed preview keeps last 20 logical lines,\n- line clamp at 4000 chars per line,\n- shows truncation + artifact warnings when metadata is present,\n- marks cancelled/error/exit state separately.\n\nThis component is wired by `CommandController.handleBashCommand()` and fed from `AgentSession.executeBash()`.\n\n## Mode-specific behavior differences\n\n| Surface | Entry path | PTY eligible | Live output UX | Error surfacing |\n| ------------------------------ | ----------------------------------------------------- | -------------------------------------------------------------------- | ------------------------------------------------------------------------ | ------------------------------------------------ |\n| Interactive tool call | `BashTool.execute` | Yes, when `pty=true` and UI exists and `SKC_NO_PTY!=1` | PTY overlay (interactive) or streamed tail updates | Tool errors become `toolResult.isError` |\n| Print mode tool call | `BashTool.execute` | No (no UI context) | No TUI overlay; output appears in event stream/final assistant text flow | Same tool error mapping |\n| RPC tool call (agent tooling) | `BashTool.execute` | Usually no UI -> non-PTY | Structured tool events/results | Same tool error mapping |\n| Interactive bang command (`!`) | `AgentSession.executeBash` + `BashExecutionComponent` | No (uses executor directly) | Dedicated bash execution component | Controller catches exceptions and shows UI error |\n| RPC `bash` command | `rpc-mode` -> `session.executeBash` | No | Returns `BashResult` directly | Consumer handles returned fields |\n\n## Operational caveats\n\n- Interceptor only blocks commands when suggested tool is currently available in context.\n- If artifact allocation fails, truncation still occurs but no `artifact://` back-reference is available.\n- Shell session cache has no explicit eviction in this module; lifetime is process-scoped.\n- PTY and non-PTY timeout surfaces differ:\n - PTY exposes explicit `timedOut` result field,\n - non-PTY maps timeout into `cancelled + annotation` summary.\n\n## Implementation files\n\n- [`src/tools/bash.ts`](../packages/coding-agent/src/tools/bash.ts) — tool entrypoint, input handling/interception, async and PTY/non-PTY selection, result/error mapping, bash tool renderer.\n- [`src/tools/bash-normalize.ts`](../packages/coding-agent/src/tools/bash-normalize.ts) — post-run head/tail filtering; also contains an unused command-normalization helper.\n- [`src/tools/bash-interceptor.ts`](../packages/coding-agent/src/tools/bash-interceptor.ts) — interceptor rule matching and blocked-command messages.\n- [`src/exec/bash-executor.ts`](../packages/coding-agent/src/exec/bash-executor.ts) — non-PTY executor, shell session reuse, cancellation wiring, output sink integration.\n- [`src/tools/bash-interactive.ts`](../packages/coding-agent/src/tools/bash-interactive.ts) — PTY runtime, overlay UI, input normalization, non-interactive env defaults.\n- [`src/session/streaming-output.ts`](../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink`, `TailBuffer`, truncation/artifact spill, and summary metadata.\n- [`src/tools/output-meta.ts`](../packages/coding-agent/src/tools/output-meta.ts) — truncation metadata shape + notice injection wrapper.\n- [`src/session/agent-session.ts`](../packages/coding-agent/src/session/agent-session.ts) — session-level `executeBash`, message recording, abort lifecycle.\n- [`src/modes/components/bash-execution.ts`](../packages/coding-agent/src/modes/components/bash-execution.ts) — interactive `!` command execution component.\n- [`src/modes/controllers/command-controller.ts`](../packages/coding-agent/src/modes/controllers/command-controller.ts) — wiring for interactive `!` command UI stream/update completion.\n- [`src/modes/rpc/rpc-mode.ts`](../packages/coding-agent/src/modes/rpc/rpc-mode.ts) — RPC `bash` and `abort_bash` command surface.\n- [`src/internal-urls/artifact-protocol.ts`](../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://<id>` resolution.\n",
|
|
14
15
|
"blob-artifact-architecture.md": "# Blob and artifact storage architecture\n\nThis document describes how coding-agent stores large/binary payloads outside session JSONL, how truncated tool output is persisted, and how internal URLs (`artifact://`, `agent://`) resolve back to stored data.\n\n## Why two storage systems exist\n\nThe runtime uses two different persistence mechanisms for different data shapes:\n\n- **Content-addressed blobs** (`blob:sha256:<hash>`): global storage used to externalize large image base64 payloads and provider image data URLs from persisted session entries.\n- **Session-scoped artifacts** (files under `<sessionFile-without-.jsonl>/`): per-session text files used for full tool outputs and subagent outputs.\n\nThey are intentionally separate:\n\n- blob storage optimizes deduplication and stable references by content hash,\n- artifact storage optimizes append-only session tooling and human/tool retrieval by local IDs.\n\n## Storage boundaries and on-disk layout\n\n## Blob store boundary (global)\n\n`SessionManager` constructs `BlobStore(getBlobsDir())`, so blob files live in a shared global blob directory (not in a session folder).\n\nBlob file naming:\n\n- file path: `<blobsDir>/<sha256-hex>`\n- no extension\n- reference string stored in entries: `blob:sha256:<sha256-hex>`\n\nImplications:\n\n- same binary content across sessions resolves to the same hash/path,\n- writes are idempotent at the content level,\n- blobs can outlive any individual session file.\n\n## Artifact boundary (session-local)\n\n`ArtifactManager` derives artifact directory from session file path:\n\n- session file: `.../<timestamp>_<sessionId>.jsonl`\n- artifacts directory: `.../<timestamp>_<sessionId>/` (strip `.jsonl`)\n\nArtifact types share this directory:\n\n- truncated tool output files: `<numericId>.<toolType>.log` (for `artifact://`)\n- subagent output files: `<outputId>.md` (for `agent://`)\n\n## ID and name allocation schemes\n\n## Blob IDs: content hash\n\n`BlobStore.put()` computes SHA-256 over the bytes it is given and returns:\n\n- `hash`: hex digest,\n- `path`: `<blobsDir>/<hash>`,\n- `ref`: `blob:sha256:<hash>`.\n\nNo session-local counter is used.\n\n## Artifact IDs: session-local monotonic integer\n\n`ArtifactManager` scans existing `*.log` artifact files on first use to find max existing numeric ID and sets `nextId = max + 1`.\n\nAllocation behavior:\n\n- file format: `{id}.{toolType}.log`\n- IDs are sequential strings (`\"0\"`, `\"1\"`, ...)\n- resume does not overwrite existing artifacts because scan happens before allocation.\n\nIf artifact directory is missing, scanning yields empty list and allocation starts from `0`.\n\n## Agent output IDs (`agent://`)\n\n`AgentOutputManager` allocates IDs for subagent outputs as `<index>-<requestedId>` (optionally nested under parent prefix, e.g. `0-Parent.1-Child`). It scans existing `.md` files on initialization to continue from the next index on resume.\n\n## Persistence dataflow\n\n## 1) Session entry persistence rewrite path\n\nBefore session entries are written (`#rewriteFile` / incremental persist), `SessionManager` calls `prepareEntryForPersistence()` (via `truncateForPersistence`).\n\nKey behaviors:\n\n1. **Large string truncation**: oversized strings are cut and suffixed with `\"[Session persistence truncated large content]\"`; signature fields (`thinkingSignature`, `thoughtSignature`, `textSignature`) are cleared instead of truncated.\n2. **Transient field stripping**: `partialJson` and `jsonlEvents` are removed from persisted entries.\n3. **Image externalization to blobs**:\n - image blocks in `content` arrays are externalized when `data` is not already a blob ref and base64 length is at least threshold (`BLOB_EXTERNALIZE_THRESHOLD = 1024`),\n - provider-style `image_url` data URLs are externalized when they start with `data:image/` and contain `;base64,`,\n - image block `data` is stored as decoded binary bytes,\n - provider data URLs are stored as the original UTF-8 data URL string,\n - persisted values are replaced with `blob:sha256:<hash>`.\n\nThis keeps session JSONL compact while preserving recoverability.\n\n## 2) Session load rehydration path\n\nWhen opening a session (`setSessionFile`), after migrations, `SessionManager` runs `resolveBlobRefsInEntries()`.\n\nFor message/custom-message image blocks with `blob:sha256:<hash>` and for persisted provider `image_url` fields with blob refs:\n\n- reads blob bytes from blob store,\n- converts image-block bytes back to base64,\n- converts provider `image_url` blobs back to the original string,\n- mutates in-memory entry fields for runtime consumers.\n\nIf blob is missing:\n\n- `resolveImageData()` logs warning,\n- returns original ref string unchanged,\n- load continues (no hard crash).\n\n## 3) Tool output spill/truncation path\n\n`OutputSink` powers streaming output in bash/python/ssh and related executors.\n\nBehavior:\n\n1. Every chunk is sanitized and appended to in-memory tail buffer.\n2. When in-memory bytes exceed spill threshold (`DEFAULT_MAX_BYTES`, 50KB), sink marks output truncated.\n3. If an artifact path is available, sink opens a file writer and writes:\n - existing buffered content once,\n - all subsequent chunks.\n4. In-memory buffer is always trimmed to tail window for display.\n5. `dump()` returns summary including `artifactId` only when file sink was successfully created.\n\nPractical effect:\n\n- UI/tool return shows truncated tail,\n- full output is preserved in artifact file and referenced as `artifact://<id>`.\n\nIf file sink creation fails (I/O error, missing path, etc.), sink silently falls back to in-memory truncation only; full output is not persisted.\n\n## URL access model\n\n## `blob:` references\n\n`blob:sha256:<hash>` is a persistence reference inside session entry payloads, not an internal URL scheme handled by the router. Resolution is done by `SessionManager` during session load.\n\n## `artifact://<id>`\n\nHandled by `ArtifactProtocolHandler`:\n\n- requires active session artifact directory,\n- ID must be numeric,\n- resolves by matching filename prefix `<id>.`,\n- returns raw text (`text/plain`) from the matched `.log` file,\n- when missing, error includes list of available artifact IDs.\n\nMissing directory behavior:\n\n- if artifacts directory does not exist, throws `No artifacts directory found`.\n\n## `agent://<id>`\n\nHandled by `AgentProtocolHandler` over `<artifactsDir>/<id>.md`:\n\n- plain form returns markdown text,\n- `/path` or `?q=` forms perform JSON extraction,\n- path and query extraction cannot be combined,\n- if extraction requested, file content must parse as JSON.\n\nMissing directory behavior:\n\n- throws `No artifacts directory found`.\n\nMissing output behavior:\n\n- throws `Not found: <id>` with available IDs from existing `.md` files.\n\nRead tool integration:\n\n- `read` supports offset/limit pagination for non-extraction internal URL reads,\n- rejects `offset/limit` when `agent://` extraction is used.\n\n## Resume, fork, and move semantics\n\n## Resume\n\n- `ArtifactManager` scans existing `{id}.*.log` files on first allocation and continues numbering.\n- `AgentOutputManager` scans existing `.md` output IDs and continues numbering.\n- `SessionManager` rehydrates blob refs to base64 on load.\n\n## Fork\n\n`SessionManager.fork()` creates a new session file with new session ID and `parentSession` link, then returns old/new file paths. Artifact copying is handled by `AgentSession.fork()`:\n\n- attempts recursive copy of old artifact directory to new artifact directory,\n- missing old directory is tolerated,\n- non-ENOENT copy errors are logged as warnings and fork still completes.\n\nID implications after fork:\n\n- if copy succeeded, artifact counters in new session continue after max copied ID,\n- if copy failed/skipped, new session artifact IDs start from `0`.\n\nBlob implications after fork:\n\n- blobs are global and content-addressed, so no blob directory copy is required.\n\n## Move to new cwd\n\n`SessionManager.moveTo()` renames both session file and artifact directory to the new default session directory, with rollback logic if a later step fails. This preserves artifact identity while relocating session scope.\n\n## Failure handling and fallback paths\n\n| Case | Behavior |\n| -------------------------------------------------------- | --------------------------------------------------------------------- |\n| Blob file missing during rehydration | Warn and keep `blob:sha256:` ref string in-memory |\n| Blob read ENOENT via `BlobStore.get` | Returns `null` |\n| Artifact directory missing (`ArtifactManager.listFiles`) | Returns empty list (allocation can start fresh) |\n| Artifact directory missing (`artifact://` / `agent://`) | Throws explicit `No artifacts directory found` |\n| Artifact ID not found | Throws with available IDs listing |\n| OutputSink artifact writer init fails | Continues with tail-only truncation (no full-output artifact) |\n| No session file (some task paths) | Task tool falls back to temp artifacts directory for subagent outputs |\n\n## Binary blob externalization vs text-output artifacts\n\n- **Blob externalization** is for image payloads inside persisted session entry content and provider image data URLs; it replaces inline payload strings in JSONL with stable content refs.\n- **Artifacts** are plain text files for execution output and subagent output; they are addressable by session-local IDs through internal URLs.\n\nThe two systems intersect only indirectly (both reduce session JSONL bloat) but have different identity, lifetime, and retrieval paths.\n\n## Implementation files\n\n- [`src/session/blob-store.ts`](../packages/coding-agent/src/session/blob-store.ts) — blob reference format, hashing, put/get, externalize/resolve helpers.\n- [`src/session/artifacts.ts`](../packages/coding-agent/src/session/artifacts.ts) — session artifact directory model and numeric artifact ID/path allocation.\n- [`src/session/streaming-output.ts`](../packages/coding-agent/src/session/streaming-output.ts) — `OutputSink` truncation/spill-to-file behavior and summary metadata.\n- [`src/session/session-manager.ts`](../packages/coding-agent/src/session/session-manager.ts) — persistence transforms, blob rehydration on load, session fork/move interactions.\n- [`src/session/agent-session.ts`](../packages/coding-agent/src/session/agent-session.ts) — artifact directory copy during interactive fork.\n- [`src/internal-urls/artifact-protocol.ts`](../packages/coding-agent/src/internal-urls/artifact-protocol.ts) — `artifact://` resolver.\n- [`src/internal-urls/agent-protocol.ts`](../packages/coding-agent/src/internal-urls/agent-protocol.ts) — `agent://` resolver + JSON extraction.\n- [`src/sdk.ts`](../packages/coding-agent/src/sdk.ts) — internal URL router wiring and artifacts-dir resolver.\n- [`src/task/output-manager.ts`](../packages/coding-agent/src/task/output-manager.ts) — session-scoped agent output ID allocation for `agent://`.\n- [`src/task/executor.ts`](../packages/coding-agent/src/task/executor.ts) — subagent output artifact writes (`<id>.md`) and temp artifact directory fallback.\n",
|
|
15
|
-
"bot-integration.md": "# External controller integration guide\n\nThis guide is for authors of bots and orchestrators that want to drive Sayknow-CLI (`skc`) without scraping terminal scrollback. Hermes, OpenClaw, GitHub bots, chatops bots, and custom schedulers are examples of external controllers; none of them need bespoke SKC behavior if they can speak the Coordinator MCP or RPC lifecycle below.\n\nSKC is an external runner. Your controller owns queueing, identity, policy, and credentials; SKC owns the coding-agent session, workflows, tools, artifacts, and evidence inside the selected repository or worktree.\n\n## Integration surfaces\n\nUse the smallest surface that fits your bot:\n\n| Surface | Best for | Command | Stability notes |\n| --- | --- | --- | --- |\n| Coordinator MCP | Any external controller that can call MCP tools to start/register tmux sessions, send turns, answer questions, and read artifacts. | `skc mcp-serve coordinator` | Preferred orchestration surface. `skc mcp-serve hermes` is a compatibility alias, not a separate contract. |\n| Setup adapter | Rendering a portable MCP config and operator instructions for a controller profile. | `skc setup hermes --root /path/to/repo` | Compatibility-oriented config renderer; does not call an LLM or validate provider credentials. |\n| RPC stdio | A controller that embeds a single `skc --mode rpc` subprocess and handles JSONL frames directly or through `python/skc-rpc`. | `skc --mode rpc` | Best for process-backed, single-session bot workers. |\n| Bridge HTTPS | Experimental remote control for an already-running session. | `skc --mode bridge` | Session-control endpoints are fail-closed by default; do not use as the default bot lifecycle surface yet. |\n| Visible tmux fallback | Human-supervised lanes where an existing visible `skc --tmux` pane should become coordinator-authoritative. | `skc --tmux`, then `skc_coordinator_register_session` | Use when an operator already opened a pane or wants direct terminal visibility. |\n\n## Recommended architecture\n\n```text\nexternal controller / bot\n ├─ chooses repo/worktree and task policy\n ├─ starts MCP server: skc mcp-serve coordinator\n ├─ starts or registers one SKC tmux session\n ├─ sends one bounded turn at a time\n ├─ answers structured questions explicitly\n ├─ marks turn completion/failure with report_status\n └─ reads artifacts/reports from allowlisted roots\n```\n\nDo not infer completion from terminal output. Treat durable turn state as authoritative and tmux tail output as advisory debug context only.\n\n## Coordinator MCP setup\n\nRender a non-mutating config preview:\n\n```sh\nskc setup hermes --root /path/to/repo --profile my-bot --repo my-repo\n```\n\nInstall into a Hermes-compatible profile only when the target path is intentional:\n\n```sh\nskc setup hermes \\\n --root /path/to/repo \\\n --profile my-bot \\\n --repo my-repo \\\n --mutation sessions,questions,reports \\\n --profile-dir /path/to/hermes/profile \\\n --install\n```\n\nRun provider-independent contract smokes before trying a live model:\n\n```sh\nskc setup hermes --root /path/to/repo --smoke --json\nskc mcp-serve coordinator --check --json\n```\n\nThe generated config uses these environment variables:\n\n| Variable | Purpose |\n| --- | --- |\n| `SKC_COORDINATOR_MCP_WORKDIR_ROOTS` | Required allowlist for workdirs and artifact paths. |\n| `SKC_COORDINATOR_MCP_MUTATIONS` | Startup opt-in for mutation classes: `sessions`, `questions`, `reports`, or `all`. |\n| `SKC_COORDINATOR_MCP_SESSION_COMMAND` | Command used to start real SKC sessions, defaulting to `skc --worktree` in generated setup. |\n| `SKC_COORDINATOR_MCP_PROFILE` | Optional profile namespace so one bot cannot enumerate another profile's state. |\n| `SKC_COORDINATOR_MCP_REPO` | Optional repo namespace so one repo cannot enumerate another repo's state. |\n| `SKC_COORDINATOR_MCP_STATE_ROOT` | Optional coordination state root; defaults under `.skc/state/coordinator-mcp`. |\n| `SKC_COORDINATOR_MCP_ARTIFACT_BYTE_CAP` | Maximum bytes returned by artifact reads. |\n\nMutating calls require both startup opt-in and per-call `allow_mutation: true`. Missing either one fails closed.\n\n## Generic smoke strategy\n\nUse three different smoke levels so CI does not depend on one operator's model, API key, tmux layout, or desktop:\n\n| Smoke | Required for CI | What it proves | Example |\n| --- | --- | --- | --- |\n| Contract smoke | Yes | MCP server metadata, tool discovery, exported tool names, input schemas, read-only default, and mutation-gate failures. No provider credentials or tmux pane required. | `skc mcp-serve coordinator --check --json` and focused tests around `tools/list` plus mutation denial. |\n| Dry-run lifecycle smoke | Yes when changed behavior affects lifecycle state | A generic controller can start/register a mocked session, send a turn, observe active-turn protection, report terminal status, and read the completed turn without a real LLM. | `bun test packages/coding-agent/test/coordinator-mcp.test.ts` uses mocked coordinator services and temporary state roots. |\n| Optional live smoke | No | One operator's local provider/model/profile/tmux setup can run end-to-end in their chosen repo. Failure diagnoses that setup; it must not fail CI or PR validation. | Start `skc mcp-serve coordinator` with local env, dispatch a tiny task, then report/read evidence. |\n\nA public bot integration change should at least preserve the contract smoke and local-leak docs test. Live smokes are diagnostics, not mandatory gates.\n\n## MCP tool contract\n\nRead-only tools:\n\n- `skc_coordinator_list_sessions`\n- `skc_coordinator_read_status`\n- `skc_coordinator_read_tail`\n- `skc_coordinator_read_turn`\n- `skc_coordinator_await_turn`\n- `skc_coordinator_list_questions`\n- `skc_coordinator_list_artifacts`\n- `skc_coordinator_read_artifact`\n- `skc_coordinator_read_coordination_status`\n- `skc_coordinator_watch_events`\n\nMutating tools:\n\n- `skc_coordinator_start_session`\n- `skc_coordinator_register_session`\n- `skc_coordinator_send_prompt`\n- `skc_coordinator_submit_question_answer`\n- `skc_coordinator_report_status`\n\nHigh-level delegation tools:\n\n- `skc_delegate_plan`\n- `skc_delegate_execute`\n- `skc_delegate_team`\n\nThe `skc_delegate_*` tools package common SKC workflows for hosts that want to delegate an entire planning, execution, or team turn without manually composing `start_session` and `send_prompt`. They use the same coordinator mutation gates and workdir allowlists as the lower-level session tools.\n\n### Start a managed SKC session\n\nCall `skc_coordinator_start_session` with a canonical workdir inside `SKC_COORDINATOR_MCP_WORKDIR_ROOTS`:\n\n```json\n{\n \"cwd\": \"/path/to/repo\",\n \"prompt\": \"Optional first bounded task prompt\",\n \"allow_mutation\": true\n}\n```\n\nThe returned payload includes `session.session_id`, `session_state`, and, when a prompt is provided, `turn_id`, `status`, `delivery`, `queued`, and `delivered`.\n\n### Register a visible tmux fallback session\n\nIf an operator already started a visible session, register it instead of starting a hidden coordinator session:\n\n```sh\nskc --tmux\n```\n\n```json\n{\n \"session_id\": \"visible-skc-1\",\n \"cwd\": \"/path/to/repo\",\n \"tmux_session\": \"visible-skc-1\",\n \"tmux_target\": \"visible-skc-1:0.0\",\n \"visible\": true,\n \"source\": \"operator-visible-tmux\",\n \"allow_mutation\": true\n}\n```\n\n`skc_coordinator_register_session` validates safe ids, workdir allowlists, tmux target syntax, and liveness before writing coordinator state.\n\n### Send work as turns\n\nSend one bounded task prompt and persist the returned `turn_id`:\n\n```json\n{\n \"session_id\": \"skc-demo\",\n \"prompt\": \"Use /skill:ralplan to build a plan for ...\",\n \"allow_mutation\": true\n}\n```\n\nA session may have one active turn by default. A second prompt returns `active_turn_exists` unless the bot passes:\n\n- `queue: true` to enqueue a durable follow-up turn, or\n- `force: true` to supersede the previous active turn and audit the supersession.\n\n### Wait or watch for completion\n\nUse `skc_coordinator_read_turn` for polling or `skc_coordinator_await_turn` for bounded waiting:\n\n```json\n{\n \"session_id\": \"skc-demo\",\n \"turn_id\": \"turn-00000000-0000-0000-0000-000000000000\",\n \"timeout_ms\": 30000,\n \"poll_interval_ms\": 1000,\n \"lines\": 80\n}\n```\n\nTerminal turn statuses are `completed`, `failed`, `cancelled`, and `superseded`. Non-terminal statuses include `queued`, `delivering`, `active`, `waiting_for_answer`, and `completing`.\n\nWhen the work is done, your bot must call `skc_coordinator_report_status` with the turn id. This writes the final response/error, evidence paths, and coordinator report that later reads consume:\n\n```json\n{\n \"session_id\": \"skc-demo\",\n \"turn_id\": \"turn-00000000-0000-0000-0000-000000000000\",\n \"status\": \"completed\",\n \"summary\": \"Implemented the requested fix and ran focused tests.\",\n \"evidence_paths\": [\"/path/to/repo/test-output.txt\"],\n \"allow_mutation\": true\n}\n```\n\nUse `status: \"failed\"` plus `blocker` for provider failures, unrecoverable tool failures, missing credentials, policy denial, or task blockers.\nUse `status: \"cancelled\"` when the coordinator policy intentionally stops tracking an active turn, for example after an operator abort or a bot-side shutdown decision. This records the turn as terminal in coordinator state; it does not kill the underlying tmux process. To supersede one active turn with replacement work, send the replacement prompt with `force: true` and preserve the superseded turn id in your audit trail.\n\n### Forward finish/stop lifecycle notifications\n\nDiscord, Hermes, Clawhip, and similar external notifiers should be opt-in and should forward only the public lifecycle surface. Use one of these supported paths:\n\n- Coordinator controllers: watch or poll turn state with `skc_coordinator_watch_events`, `skc_coordinator_await_turn`, or `skc_coordinator_read_turn`, then notify from the terminal turn status your controller records with `skc_coordinator_report_status`.\n- In-process extensions or hooks: subscribe to the public lifecycle events `turn_end` and `agent_end` from the shared hook/extension event contract.\n\nRecommended notification mapping:\n\n| Notification intent | Public surface | Safe meaning |\n| --- | --- | --- |\n| Turn finished | `turn_end` or terminal coordinator turn status `completed` | One LLM turn produced its final assistant message. |\n| Agent stopped / finished | `agent_end` | The agent loop ended for the submitted prompt. |\n| Waiting for user | Coordinator turn status `waiting_for_answer` | The agent is blocked on a structured question. |\n| Failed or blocked | Coordinator status `failed` with a public `blocker` summary | The controller recorded a terminal failure. |\n| Cancelled / superseded | Coordinator status `cancelled` or `superseded` | The controller intentionally stopped tracking or replaced the turn. |\n\nDo not forward raw prompts, transcripts, tool outputs, hidden instructions, private configs, host paths, channel ids, webhook URLs, or tokens. If your notifier needs a human-readable sentence, create a caller-supplied sanitized summary and keep provider/tool details out of the payload.\n\nExample public-safe extension event payloads:\n\n```json\n{ \"type\": \"turn_end\", \"turnIndex\": 2, \"summary\": \"Turn finished; review the local SKC session for details.\" }\n```\n\n```json\n{ \"type\": \"agent_end\", \"summary\": \"Agent loop ended; no raw transcript is included.\" }\n```\n\nExample opt-in forwarding policy:\n\n```json\n{\n \"enabled\": true,\n \"events\": [\"turn_end\", \"agent_end\"],\n \"destination\": \"external-notifier-profile\",\n \"redaction\": \"metadata-only\"\n}\n```\n\nSKC does not currently expose a structured stop-reason field on `agent_end`; integrators that need `waiting_for_answer`, `failed`, `cancelled`, or `superseded` should prefer the Coordinator MCP turn status because it is explicit, terminal-state oriented, and safe to relay after controller-side redaction.\n\n### Answer structured questions\n\nList pending questions:\n\n```json\n{\n \"session_id\": \"skc-demo\",\n \"status\": \"pending\"\n}\n```\n\nThen answer by id:\n\n```json\n{\n \"session_id\": \"skc-demo\",\n \"turn_id\": \"turn-00000000-0000-0000-0000-000000000000\",\n \"question_id\": \"question-1\",\n \"answer\": { \"decision\": \"approve\" },\n \"allow_mutation\": true\n}\n```\n\nAlways answer the advertised shape. Do not synthesize approvals for destructive actions unless your bot policy explicitly permits that action.\n\n### Read artifacts and reports\n\nUse `skc_coordinator_list_artifacts` to inspect safe roots and `skc_coordinator_read_artifact` to read a bounded artifact:\n\n```json\n{ \"path\": \"/path/to/repo/.skc/ultragoal/ledger.jsonl\" }\n```\n\nArtifact paths are canonicalized, symlink escapes are rejected, and output is byte-capped. Use `skc_coordinator_read_coordination_status` for status reports written through `skc_coordinator_report_status`.\n\n## RPC stdio integration\n\nUse RPC when your bot owns a single worker subprocess rather than an MCP coordinator. The wire protocol is JSONL over stdio:\n\n```sh\nskc --mode rpc --provider anthropic --model claude-sonnet-4-5\n```\n\nRecommended Python client:\n\n```python\nfrom skc_rpc import RpcClient, WorkflowGate\n\nwith RpcClient(no_session=True, no_rules=True) as client:\n client.install_headless_ui()\n\n def on_gate(gate: WorkflowGate) -> None:\n if gate.kind == \"approval\":\n client.respond_gate(gate.gate_id, {\"decision\": \"approve\"})\n\n client.on_workflow_gate(on_gate)\n turn = client.prompt_and_wait(\"Inspect this repo and report the integration contract.\")\n print(turn.require_assistant_text())\n```\n\nRPC hosts can also expose host-owned tools and URI schemes. Use these to give SKC controlled access to your bot's issue tracker, queue, database rows, or artifact store without leaking long-lived credentials into the SKC process.\n\nKey RPC lifecycle facts:\n\n- `{ \"type\": \"ready\" }` means the subprocess is ready for commands.\n- `prompt` is acknowledged immediately; completion is observed through `agent_end` or `RpcClient.prompt_and_wait()`.\n- `workflow_gate` frames are answered with `workflow_gate_response`.\n- `extension_ui_request` frames are answered with `extension_ui_response` or a headless policy.\n- Host tool calls and host URI requests are explicit callback frames that must be completed or rejected by the host.\n- `RpcClient` enforces single-flight prompt lifecycle collection; use one client per concurrent worker.\n- `abort` and `abort_and_prompt` are the RPC cancellation commands for subprocess workers; coordinator MCP cancellation is recorded through terminal turn status instead.\n\n## Error handling playbook\n\n| Situation | Bot behavior |\n| --- | --- |\n| `coordinator_mutation_class_disabled:*` | Re-render setup with the required mutation class, or keep the bot in read-only mode. |\n| `coordinator_mutation_call_not_allowed:*` | Add `allow_mutation: true` only after policy approval for that specific call. |\n| `unknown_session` | Re-list sessions; start a new managed session or register the visible tmux fallback. |\n| `active_turn_exists` | Poll the active turn, send with `queue: true`, or use `force: true` only when supersession is intentional. |\n| `timeout` from `await_turn` | Treat as non-terminal. Poll again or inspect `read_status`/`read_tail`; do not mark failure solely from a bounded wait timeout. |\n| Coordinator cancellation | Use `skc_coordinator_report_status` with `status: \"cancelled\"` for an intentionally stopped turn, or send replacement work with `force: true` when supersession is policy-approved. This is coordinator state, not a tmux process kill. |\n| Stale tmux/session state | Check `read_status.session_state` and advisory liveness. Register a new visible session or report the turn failed with a recoverable blocker. |\n| Provider/auth failure | Capture the model/provider error in `report_status` with `status: \"failed\"`; do not retry forever without a policy budget. |\n| Artifact denied | Keep the artifact inside allowlisted roots and avoid symlink escapes. |\n| Malformed or invalid question answer | Re-read the question/gate schema and submit a value matching the advertised shape. |\n| Bot shutdown | Persist `session_id` and active `turn_id`; on restart use `read_turn` and `read_status` before sending more work. |\n\n## Controller examples\n\nGeneric MCP controller config:\n\n```json\n{\n \"mcp_servers\": {\n \"skc_coordinator\": {\n \"command\": \"skc\",\n \"args\": [\"mcp-serve\", \"coordinator\"],\n \"env\": {\n \"SKC_COORDINATOR_MCP_WORKDIR_ROOTS\": \"/home/bot/src/project:/home/bot/src/worktrees\",\n \"SKC_COORDINATOR_MCP_MUTATIONS\": \"sessions,questions,reports\",\n \"SKC_COORDINATOR_MCP_PROFILE\": \"controller-prod\",\n \"SKC_COORDINATOR_MCP_REPO\": \"project\",\n \"SKC_COORDINATOR_MCP_SESSION_COMMAND\": \"skc --worktree\"\n },\n \"enabled\": true\n }\n }\n}\n```\n\nExample controller loop:\n\n```text\n1. Start `skc mcp-serve coordinator` with repo/worktree roots allowlisted.\n2. Call `skc_coordinator_start_session` for a SKC-managed worktree session.\n3. Send `/skill:deep-interview`, `/skill:ralplan`, or an approved `skc ultragoal ...` task as one turn.\n4. Await the turn; answer `skc_coordinator_list_questions` entries using bot policy.\n5. Report terminal status with evidence paths.\n6. Read artifacts/reports for the user-facing bot response.\n```\n\nHermes and OpenClaw can use the same MCP tool contract. Their names here are examples of controller products, not privileged integration modes.\n\n## Security and credential boundaries\n\n- Do not put provider API keys, GitHub tokens, or bot secrets in prompts.\n- Prefer host tools, host URI schemes, or bot-side sidecars for credentialed external writes.\n- Keep `SKC_COORDINATOR_MCP_WORKDIR_ROOTS` narrow; do not allow `/`, `/home`, or broad parent directories.\n- Use namespaces for multi-tenant bots.\n- Keep mutation classes minimal: read-only for dashboards, `sessions` for work dispatch, `questions` for answering questions, and `reports` for final state.\n- Treat `.skc/` as local runtime state and evidence. Do not expose it wholesale to untrusted users.\n\n## Related references\n\n- [`docs/hermes-mcp-bridge.md`](./hermes-mcp-bridge.md) — coordinator MCP details and setup adapter behavior.\n- [`docs/rpc.md`](./rpc.md) — JSONL RPC protocol, event frames, workflow gates, host tools, and host URI schemes.\n- [`docs/bridge.md`](./bridge.md) — experimental HTTPS bridge and fail-closed endpoint matrix.\n- [`python/skc-rpc/README.md`](../python/skc-rpc/README.md) — typed Python RPC client examples.\n- [`python/roboskc/README.md`](../python/roboskc/README.md) — example self-hosted GitHub bot using `skc --mode rpc`.\n",
|
|
16
|
+
"bot-integration.md": "# External controller integration guide\n\nThis guide is for authors of bots and orchestrators that want to drive Sayknow-CLI (`skc`) without scraping terminal scrollback. Hermes, OpenClaw, GitHub bots, chatops bots, and custom schedulers are examples of external controllers; none of them need bespoke SKC behavior if they can speak the Coordinator MCP or RPC lifecycle below.\n\nSKC is an external runner. Your controller owns queueing, identity, policy, and credentials; SKC owns the coding-agent session, workflows, tools, artifacts, and evidence inside the selected repository or worktree.\n\n## Integration surfaces\n\nUse the smallest surface that fits your bot:\n\n| Surface | Best for | Command | Stability notes |\n| --- | --- | --- | --- |\n| Coordinator MCP | Any external controller that can call MCP tools to start/register tmux sessions, send turns, answer questions, and read artifacts. | `skc mcp-serve coordinator` | Preferred orchestration surface. `skc mcp-serve hermes` is a compatibility alias, not a separate contract. |\n| Setup adapter | Rendering a portable MCP config and operator instructions for a controller profile. | `skc setup hermes --root /path/to/repo` | Compatibility-oriented config renderer; does not call an LLM or validate provider credentials. |\n| RPC stdio | A controller that embeds a single `skc --mode rpc` subprocess and handles JSONL frames directly or through `python/skc-rpc`. | `skc --mode rpc` | Best for process-backed, single-session bot workers. |\n| Bridge HTTPS | Experimental remote control for an already-running session. | `skc --mode bridge` | Session-control endpoints are fail-closed by default; do not use as the default bot lifecycle surface yet. |\n| Visible tmux fallback | Human-supervised lanes where an existing visible `skc --tmux` pane should become coordinator-authoritative. | `skc --tmux`, then `skc_coordinator_register_session` | Use when an operator already opened a pane or wants direct terminal visibility. |\n\n## Recommended architecture\n\n```text\nexternal controller / bot\n ├─ chooses repo/worktree and task policy\n ├─ starts MCP server: skc mcp-serve coordinator\n ├─ starts or registers one SKC tmux session\n ├─ sends one bounded turn at a time\n ├─ answers structured questions explicitly\n ├─ marks turn completion/failure with report_status\n └─ reads artifacts/reports from allowlisted roots\n```\n\nDo not infer completion from terminal output. Treat durable turn state as authoritative and tmux tail output as advisory debug context only.\n\n## Coordinator MCP setup\n\nRender a non-mutating config preview:\n\n```sh\nskc setup hermes --root /path/to/repo --profile my-bot --repo my-repo\n```\n\nInstall into a Hermes-compatible profile only when the target path is intentional:\n\n```sh\nskc setup hermes \\\n --root /path/to/repo \\\n --profile my-bot \\\n --repo my-repo \\\n --mutation sessions,questions,reports \\\n --profile-dir /path/to/hermes/profile \\\n --install\n```\n\nRun provider-independent contract smokes before trying a live model:\n\n```sh\nskc setup hermes --root /path/to/repo --smoke --json\nskc mcp-serve coordinator --check --json\n```\n\nThe generated config uses these environment variables:\n\n| Variable | Purpose |\n| --- | --- |\n| `SKC_COORDINATOR_MCP_WORKDIR_ROOTS` | Required allowlist for workdirs and artifact paths. |\n| `SKC_COORDINATOR_MCP_MUTATIONS` | Startup opt-in for mutation classes: `sessions`, `questions`, `reports`, or `all`. |\n| `SKC_COORDINATOR_MCP_SESSION_COMMAND` | Command used to start real SKC sessions, defaulting to `skc --worktree` in generated setup. |\n| `SKC_COORDINATOR_MCP_PROFILE` | Optional profile namespace so one bot cannot enumerate another profile's state. |\n| `SKC_COORDINATOR_MCP_REPO` | Optional repo namespace so one repo cannot enumerate another repo's state. |\n| `SKC_COORDINATOR_MCP_STATE_ROOT` | Optional coordination state root; defaults under `.skc/state/coordinator-mcp`. |\n| `SKC_COORDINATOR_MCP_ARTIFACT_BYTE_CAP` | Maximum bytes returned by artifact reads. |\n\nMutating calls require both startup opt-in and per-call `allow_mutation: true`. Missing either one fails closed.\n\n## Generic smoke strategy\n\nUse three different smoke levels so CI does not depend on one operator's model, API key, tmux layout, or desktop:\n\n| Smoke | Required for CI | What it proves | Example |\n| --- | --- | --- | --- |\n| Contract smoke | Yes | MCP server metadata, tool discovery, exported tool names, input schemas, read-only default, and mutation-gate failures. No provider credentials or tmux pane required. | `skc mcp-serve coordinator --check --json` and focused tests around `tools/list` plus mutation denial. |\n| Dry-run lifecycle smoke | Yes when changed behavior affects lifecycle state | A generic controller can start/register a mocked session, send a turn, observe active-turn protection, report terminal status, and read the completed turn without a real LLM. | `bun test packages/coding-agent/test/coordinator-mcp.test.ts` uses mocked coordinator services and temporary state roots. |\n| Optional live smoke | No | One operator's local provider/model/profile/tmux setup can run end-to-end in their chosen repo. Failure diagnoses that setup; it must not fail CI or PR validation. | Start `skc mcp-serve coordinator` with local env, dispatch a tiny task, then report/read evidence. |\n\nA public bot integration change should at least preserve the contract smoke and local-leak docs test. Live smokes are diagnostics, not mandatory gates.\n\n## MCP tool contract\n\nRead-only tools:\n\n- `skc_coordinator_list_sessions`\n- `skc_coordinator_read_status`\n- `skc_coordinator_read_tail`\n- `skc_coordinator_read_turn`\n- `skc_coordinator_await_turn`\n- `skc_coordinator_list_questions`\n- `skc_coordinator_list_artifacts`\n- `skc_coordinator_read_artifact`\n- `skc_coordinator_read_coordination_status`\n- `skc_coordinator_watch_events`\n\nMutating tools:\n\n- `skc_coordinator_start_session`\n- `skc_coordinator_register_session`\n- `skc_coordinator_send_prompt`\n- `skc_coordinator_submit_question_answer`\n- `skc_coordinator_report_status`\n- `skc_coordinator_stop_session`\n\n`skc_coordinator_stop_session` terminates a coordinator-managed session through the owner-proof tmux shutdown path — it verifies the pane pid, native session id, owner generation, server key, and process start time before signaling, so it can never kill a PID-reused or foreign session — and then purges the session's coordinator state. It refuses a session that still has an active turn unless an explicit force is supplied, and it is the same reclamation path the idle reaper uses to reap abandoned ephemeral (delegate-created) sessions.\n\nHigh-level delegation tools:\n\n- `skc_delegate_plan`\n- `skc_delegate_execute`\n- `skc_delegate_team`\n\nThe `skc_delegate_*` tools package common SKC workflows for hosts that want to delegate an entire planning, execution, or team turn without manually composing `start_session` and `send_prompt`. They use the same coordinator mutation gates and workdir allowlists as the lower-level session tools.\n\n### Start a managed SKC session\n\nCall `skc_coordinator_start_session` with a canonical workdir inside `SKC_COORDINATOR_MCP_WORKDIR_ROOTS`:\n\n```json\n{\n \"cwd\": \"/path/to/repo\",\n \"prompt\": \"Optional first bounded task prompt\",\n \"allow_mutation\": true\n}\n```\n\nThe returned payload includes `session.session_id`, `session_state`, and, when a prompt is provided, `turn_id`, `status`, `delivery`, `queued`, and `delivered`.\n\n### Register a visible tmux fallback session\n\nIf an operator already started a visible session, register it instead of starting a hidden coordinator session:\n\n```sh\nskc --tmux\n```\n\n```json\n{\n \"session_id\": \"visible-skc-1\",\n \"cwd\": \"/path/to/repo\",\n \"tmux_session\": \"visible-skc-1\",\n \"tmux_target\": \"visible-skc-1:0.0\",\n \"visible\": true,\n \"source\": \"operator-visible-tmux\",\n \"allow_mutation\": true\n}\n```\n\n`skc_coordinator_register_session` validates safe ids, workdir allowlists, tmux target syntax, and liveness before writing coordinator state.\n\n### Send work as turns\n\nSend one bounded task prompt and persist the returned `turn_id`:\n\n```json\n{\n \"session_id\": \"skc-demo\",\n \"prompt\": \"Use /skill:ralplan to build a plan for ...\",\n \"allow_mutation\": true\n}\n```\n\nA session may have one active turn by default. A second prompt returns `active_turn_exists` unless the bot passes:\n\n- `queue: true` to enqueue a durable follow-up turn, or\n- `force: true` to supersede the previous active turn and audit the supersession.\n\n### Wait or watch for completion\n\nUse `skc_coordinator_read_turn` for polling or `skc_coordinator_await_turn` for bounded waiting:\n\n```json\n{\n \"session_id\": \"skc-demo\",\n \"turn_id\": \"turn-00000000-0000-0000-0000-000000000000\",\n \"timeout_ms\": 30000,\n \"poll_interval_ms\": 1000,\n \"lines\": 80\n}\n```\n\nTerminal turn statuses are `completed`, `failed`, `cancelled`, and `superseded`. Non-terminal statuses include `queued`, `delivering`, `active`, `waiting_for_answer`, and `completing`.\n\nWhen the work is done, your bot must call `skc_coordinator_report_status` with the turn id. This writes the final response/error, evidence paths, and coordinator report that later reads consume:\n\n```json\n{\n \"session_id\": \"skc-demo\",\n \"turn_id\": \"turn-00000000-0000-0000-0000-000000000000\",\n \"status\": \"completed\",\n \"summary\": \"Implemented the requested fix and ran focused tests.\",\n \"evidence_paths\": [\"/path/to/repo/test-output.txt\"],\n \"allow_mutation\": true\n}\n```\n\nUse `status: \"failed\"` plus `blocker` for provider failures, unrecoverable tool failures, missing credentials, policy denial, or task blockers.\nUse `status: \"cancelled\"` when the coordinator policy intentionally stops tracking an active turn, for example after an operator abort or a bot-side shutdown decision. This records the turn as terminal in coordinator state; it does not kill the underlying tmux process. To supersede one active turn with replacement work, send the replacement prompt with `force: true` and preserve the superseded turn id in your audit trail.\n\n### Forward finish/stop lifecycle notifications\n\nDiscord, Hermes, Clawhip, and similar external notifiers should be opt-in and should forward only the public lifecycle surface. Use one of these supported paths:\n\n- Coordinator controllers: watch or poll turn state with `skc_coordinator_watch_events`, `skc_coordinator_await_turn`, or `skc_coordinator_read_turn`, then notify from the terminal turn status your controller records with `skc_coordinator_report_status`.\n- In-process extensions or hooks: subscribe to the public lifecycle events `turn_end` and `agent_end` from the shared hook/extension event contract.\n\nRecommended notification mapping:\n\n| Notification intent | Public surface | Safe meaning |\n| --- | --- | --- |\n| Turn finished | `turn_end` or terminal coordinator turn status `completed` | One LLM turn produced its final assistant message. |\n| Agent stopped / finished | `agent_end` | The agent loop ended for the submitted prompt. |\n| Waiting for user | Coordinator turn status `waiting_for_answer` | The agent is blocked on a structured question. |\n| Failed or blocked | Coordinator status `failed` with a public `blocker` summary | The controller recorded a terminal failure. |\n| Cancelled / superseded | Coordinator status `cancelled` or `superseded` | The controller intentionally stopped tracking or replaced the turn. |\n\nDo not forward raw prompts, transcripts, tool outputs, hidden instructions, private configs, host paths, channel ids, webhook URLs, or tokens. If your notifier needs a human-readable sentence, create a caller-supplied sanitized summary and keep provider/tool details out of the payload.\n\nExample public-safe extension event payloads:\n\n```json\n{ \"type\": \"turn_end\", \"turnIndex\": 2, \"summary\": \"Turn finished; review the local SKC session for details.\" }\n```\n\n```json\n{ \"type\": \"agent_end\", \"summary\": \"Agent loop ended; no raw transcript is included.\" }\n```\n\nExample opt-in forwarding policy:\n\n```json\n{\n \"enabled\": true,\n \"events\": [\"turn_end\", \"agent_end\"],\n \"destination\": \"external-notifier-profile\",\n \"redaction\": \"metadata-only\"\n}\n```\n\nSKC does not currently expose a structured stop-reason field on `agent_end`; integrators that need `waiting_for_answer`, `failed`, `cancelled`, or `superseded` should prefer the Coordinator MCP turn status because it is explicit, terminal-state oriented, and safe to relay after controller-side redaction.\n\n### Answer structured questions\n\nList pending questions:\n\n```json\n{\n \"session_id\": \"skc-demo\",\n \"status\": \"pending\"\n}\n```\n\nThen answer by id:\n\n```json\n{\n \"session_id\": \"skc-demo\",\n \"turn_id\": \"turn-00000000-0000-0000-0000-000000000000\",\n \"question_id\": \"question-1\",\n \"answer\": { \"decision\": \"approve\" },\n \"allow_mutation\": true\n}\n```\n\nAlways answer the advertised shape. Do not synthesize approvals for destructive actions unless your bot policy explicitly permits that action.\n\n### Read artifacts and reports\n\nUse `skc_coordinator_list_artifacts` to inspect safe roots and `skc_coordinator_read_artifact` to read a bounded artifact:\n\n```json\n{ \"path\": \"/path/to/repo/.skc/ultragoal/ledger.jsonl\" }\n```\n\nArtifact paths are canonicalized, symlink escapes are rejected, and output is byte-capped. Use `skc_coordinator_read_coordination_status` for status reports written through `skc_coordinator_report_status`.\n\n## RPC stdio integration\n\nUse RPC when your bot owns a single worker subprocess rather than an MCP coordinator. The wire protocol is JSONL over stdio:\n\n```sh\nskc --mode rpc --provider anthropic --model claude-sonnet-4-5\n```\n\nRecommended Python client:\n\n```python\nfrom skc_rpc import RpcClient, WorkflowGate\n\nwith RpcClient(no_session=True, no_rules=True) as client:\n client.install_headless_ui()\n\n def on_gate(gate: WorkflowGate) -> None:\n if gate.kind == \"approval\":\n client.respond_gate(gate.gate_id, {\"decision\": \"approve\"})\n\n client.on_workflow_gate(on_gate)\n turn = client.prompt_and_wait(\"Inspect this repo and report the integration contract.\")\n print(turn.require_assistant_text())\n```\n\nRPC hosts can also expose host-owned tools and URI schemes. Use these to give SKC controlled access to your bot's issue tracker, queue, database rows, or artifact store without leaking long-lived credentials into the SKC process.\n\nKey RPC lifecycle facts:\n\n- `{ \"type\": \"ready\" }` means the subprocess is ready for commands.\n- `prompt` is acknowledged immediately; completion is observed through `agent_end` or `RpcClient.prompt_and_wait()`.\n- `workflow_gate` frames are answered with `workflow_gate_response`.\n- `extension_ui_request` frames are answered with `extension_ui_response` or a headless policy.\n- Host tool calls and host URI requests are explicit callback frames that must be completed or rejected by the host.\n- `RpcClient` enforces single-flight prompt lifecycle collection; use one client per concurrent worker.\n- `abort` and `abort_and_prompt` are the RPC cancellation commands for subprocess workers; coordinator MCP cancellation is recorded through terminal turn status instead.\n\n## Error handling playbook\n\n| Situation | Bot behavior |\n| --- | --- |\n| `coordinator_mutation_class_disabled:*` | Re-render setup with the required mutation class, or keep the bot in read-only mode. |\n| `coordinator_mutation_call_not_allowed:*` | Add `allow_mutation: true` only after policy approval for that specific call. |\n| `unknown_session` | Re-list sessions; start a new managed session or register the visible tmux fallback. |\n| `active_turn_exists` | Poll the active turn, send with `queue: true`, or use `force: true` only when supersession is intentional. |\n| `timeout` from `await_turn` | Treat as non-terminal. Poll again or inspect `read_status`/`read_tail`; do not mark failure solely from a bounded wait timeout. |\n| Coordinator cancellation | Use `skc_coordinator_report_status` with `status: \"cancelled\"` for an intentionally stopped turn, or send replacement work with `force: true` when supersession is policy-approved. This is coordinator state, not a tmux process kill. |\n| Stale tmux/session state | Check `read_status.session_state` and advisory liveness. Register a new visible session or report the turn failed with a recoverable blocker. |\n| Provider/auth failure | Capture the model/provider error in `report_status` with `status: \"failed\"`; do not retry forever without a policy budget. |\n| Artifact denied | Keep the artifact inside allowlisted roots and avoid symlink escapes. |\n| Malformed or invalid question answer | Re-read the question/gate schema and submit a value matching the advertised shape. |\n| Bot shutdown | Persist `session_id` and active `turn_id`; on restart use `read_turn` and `read_status` before sending more work. |\n\n## Controller examples\n\nGeneric MCP controller config:\n\n```json\n{\n \"mcp_servers\": {\n \"skc_coordinator\": {\n \"command\": \"skc\",\n \"args\": [\"mcp-serve\", \"coordinator\"],\n \"env\": {\n \"SKC_COORDINATOR_MCP_WORKDIR_ROOTS\": \"/home/bot/src/project:/home/bot/src/worktrees\",\n \"SKC_COORDINATOR_MCP_MUTATIONS\": \"sessions,questions,reports\",\n \"SKC_COORDINATOR_MCP_PROFILE\": \"controller-prod\",\n \"SKC_COORDINATOR_MCP_REPO\": \"project\",\n \"SKC_COORDINATOR_MCP_SESSION_COMMAND\": \"skc --worktree\"\n },\n \"enabled\": true\n }\n }\n}\n```\n\nExample controller loop:\n\n```text\n1. Start `skc mcp-serve coordinator` with repo/worktree roots allowlisted.\n2. Call `skc_coordinator_start_session` for a SKC-managed worktree session.\n3. Send `/skill:deep-interview`, `/skill:ralplan`, or an approved `skc ultragoal ...` task as one turn.\n4. Await the turn; answer `skc_coordinator_list_questions` entries using bot policy.\n5. Report terminal status with evidence paths.\n6. Read artifacts/reports for the user-facing bot response.\n```\n\nHermes and OpenClaw can use the same MCP tool contract. Their names here are examples of controller products, not privileged integration modes.\n\n## Security and credential boundaries\n\n- Do not put provider API keys, GitHub tokens, or bot secrets in prompts.\n- Prefer host tools, host URI schemes, or bot-side sidecars for credentialed external writes.\n- Keep `SKC_COORDINATOR_MCP_WORKDIR_ROOTS` narrow; do not allow `/`, `/home`, or broad parent directories.\n- Use namespaces for multi-tenant bots.\n- Keep mutation classes minimal: read-only for dashboards, `sessions` for work dispatch, `questions` for answering questions, and `reports` for final state.\n- Treat `.skc/` as local runtime state and evidence. Do not expose it wholesale to untrusted users.\n\n## Related references\n\n- [`docs/hermes-mcp-bridge.md`](./hermes-mcp-bridge.md) — coordinator MCP details and setup adapter behavior.\n- [`docs/rpc.md`](./rpc.md) — JSONL RPC protocol, event frames, workflow gates, host tools, and host URI schemes.\n- [`docs/bridge.md`](./bridge.md) — experimental HTTPS bridge and fail-closed endpoint matrix.\n- [`python/skc-rpc/README.md`](../python/skc-rpc/README.md) — typed Python RPC client examples.\n- [`python/roboskc/README.md`](../python/roboskc/README.md) — example self-hosted GitHub bot using `skc --mode rpc`.\n",
|
|
16
17
|
"brand-assets.md": "# Brand assets\n\nSayknow-CLI's mascot is an **octopus** 🐙. The source artwork is\n[`assets/brand/mascot.png`](../assets/brand/mascot.png) (512×512, transparent);\nall product images are derived from it.\n\n| Asset | Purpose |\n| --- | --- |\n| [`assets/brand/mascot.png`](../assets/brand/mascot.png) | Source mascot (transparent, 512×512). Edit/replace this to rebrand. |\n| [`assets/hero.png`](../assets/hero.png) | Wide README/docs hero — mascot + `sayknow` wordmark on a deep-navy field. |\n| [`assets/character.png`](../assets/character.png) | Standalone mascot (square). |\n| [`python/roboskc/assets/icon.png`](../python/roboskc/assets/icon.png) | App/service icon (square, transparent). |\n| [`python/roboskc/assets/icon.jpg`](../python/roboskc/assets/icon.jpg) | Same, flattened on a navy background for JPEG. |\n| [`assets/tool-image-fixture.webp`](../assets/tool-image-fixture.webp) | Minimal WebP fixture for terminal image rendering tests. Not a product brand asset. |\n\n## Regenerating\n\nDerived images are composited from `assets/brand/mascot.png` with ImageMagick:\n\n```sh\nSRC=assets/brand/mascot.png\nmagick \"$SRC\" -resize 800x800 assets/character.png\ncp \"$SRC\" python/roboskc/assets/icon.png\nmagick \"$SRC\" -background '#06182b' -flatten -resize 512x512 python/roboskc/assets/icon.jpg\nmagick -size 1200x630 gradient:'#0c2a47'-'#020812' \\\n \\( \"$SRC\" -resize 300x300 \\) -gravity North -geometry +0+72 -composite \\\n -gravity North -font \"/System/Library/Fonts/Supplemental/Arial Bold.ttf\" \\\n -fill '#eef7ff' -pointsize 104 -annotate +0+392 'sayknow' \\\n -fill '#ff8a4c' -pointsize 27 -annotate +0+520 'Coding should feel like thinking.' \\\n assets/hero.png\n```\n\n> Note: the mascot is warm/orange while the bundled TUI theme is `blue-octopus`\n> (blue). They coexist intentionally; if you want the terminal palette to match\n> the mascot, switch the default theme to `red-octopus` or add a warm theme.\n",
|
|
17
|
-
"bridge.md": "# Bridge Protocol Reference (Experimental, Fail-Closed)\n\nBridge mode runs the coding agent as an experimental network control surface over\nHTTPS. The session-control surface is intentionally **fail-closed by default**\nwhile the bridge security model is hardened.\n\nDefault availability:\n\n- `GET /healthz` is available without auth and returns `{ \"status\": \"ok\" }`.\n- `GET /v1/help` is available without auth and reports the fail-closed endpoint\n matrix.\n- `POST /v1/handshake` remains authenticated, but the default response advertises\n no enabled session endpoints, no accepted capabilities, no accepted scopes, and\n no frame types.\n- `GET /v1/sessions/{session_id}/events` fails closed with\n `403 endpoint_disabled` after bearer auth succeeds.\n- `POST /v1/sessions/{session_id}/commands` fails closed with\n `403 endpoint_disabled` after bearer auth succeeds and before body parsing,\n command validation, scope checks, or dispatch.\n- `POST /v1/sessions/{session_id}/control:claim` and\n `POST /v1/sessions/{session_id}/control:disconnect` fail closed with\n `403 endpoint_disabled` after bearer auth succeeds.\n- `POST /v1/sessions/{session_id}/ui-responses/{correlation_id}` fails closed\n with `403 endpoint_disabled` after bearer auth succeeds and before body parsing\n or controller checks.\n- `POST /v1/sessions/{session_id}/host-tool-results/{correlation_id}` and\n `POST /v1/sessions/{session_id}/host-uri-results/{correlation_id}` fail closed\n with `403 endpoint_disabled` after bearer auth succeeds and before body parsing\n or host callback handling.\n\nThe implementation still contains the v1 protocol scaffolding and internal tests\nfor the previously enabled surface, but external clients must treat events,\ncommands, controller ownership, UI responses, host tool results, and host URI\nresults as unavailable unless a future release explicitly re-enables them.\n\nPrimary implementation:\n\n- `src/modes/bridge/bridge-mode.ts`\n- `src/modes/bridge/auth.ts`\n- `src/modes/bridge/event-stream.ts`\n- `src/modes/bridge/bridge-client-bridge.ts`\n- `src/modes/bridge/bridge-ui-context.ts`\n- `src/modes/shared/agent-wire/*` (protocol, scopes, handshake, command dispatch/validation, host bridges)\n- `packages/bridge-client/src/*`\n\n## Startup\n\n```bash\nskc --mode bridge [regular CLI options]\n```\n\nBehavior notes:\n\n- The bridge is served over **HTTPS only**. Startup refuses to bind without TLS\n configured (see Security and TLS). There is no unencrypted startup path.\n- `@file` CLI arguments are rejected in bridge mode (as in RPC mode).\n- Bridge mode reuses the RPC default-setting overrides and suppresses automatic\n session title generation.\n- One bridge process serves exactly **one live `AgentSession`**.\n- The default endpoint matrix disables session events, commands, controller\n ownership, UI responses, host tool results, and host URI results.\n\n### Configuration (environment variables)\n\nSee `docs/environment-variables.md` for the authoritative table. Summary:\n\n| Variable | Required | Default | Notes |\n| --- | --- | --- | --- |\n| `SKC_BRIDGE_TOKEN` | Yes | — | Bearer token for authenticated endpoints. **Secret — never commit.** |\n| `SKC_BRIDGE_TLS_CERT` | Yes | — | Path to the TLS certificate (PEM). |\n| `SKC_BRIDGE_TLS_KEY` | Yes | — | Path to the TLS private key (PEM). **Secret — never commit.** |\n| `SKC_BRIDGE_HOST` | No | `127.0.0.1` | Bind hostname. |\n| `SKC_BRIDGE_PORT` | No | `4077` | Bind port (1–65535). |\n| `SKC_BRIDGE_SCOPES` | No | `prompt` | Parsed for internal compatibility, but default session endpoints are fail-closed. |\n\n## Security and TLS\n\nThe bridge is a network control surface, so it is **secure-by-default**:\n\n- **TLS is mandatory for every bind, including loopback.** Startup fails closed\n with a clear error if `SKC_BRIDGE_TLS_CERT` and `SKC_BRIDGE_TLS_KEY` are not\n both set. There is no plaintext fallback and no insecure/trust-bypass switch.\n- **Bearer token is mandatory** for every endpoint except `GET /healthz` and\n `GET /v1/help`.\n- The TypeScript SDK refuses bearer-token clients over non-`https` URLs by\n default. It allows plaintext only for `localhost`, `127.0.0.1`, or `[::1]`\n when the caller explicitly passes the localhost/test opt-in.\n- Session endpoints fail closed by default even when bearer auth and scopes are\n otherwise valid.\n\n## Handshake\n\n```\nPOST /v1/handshake (authenticated)\n```\n\nThe client sends its supported protocol version range, requested capabilities,\nand requested scopes. Version mismatch returns `status: \"rejected\"`,\n`reason: \"incompatible_version\"`. Malformed request bodies return\n`400 invalid_request`.\n\nIn the default fail-closed configuration, a successful authenticated\nhandshake returns:\n\n- `protocol_version` — the server protocol version (`BRIDGE_PROTOCOL_VERSION`, `2`).\n- `session_id` — the single session id this bridge serves.\n- `accepted_capabilities` — empty.\n- `accepted_scopes` — empty.\n- `unsupported` — every requested capability.\n- `endpoints` — all session endpoint descriptors present but empty strings.\n- `frame_types` — empty.\n\n## Fail-Closed Endpoint Matrix\n\nThe disabled endpoint matrix is:\n\n| Surface | Endpoint(s) | Default |\n| --- | --- | --- |\n| Events | `GET /v1/sessions/{session_id}/events?last_seq=<n>` | Disabled |\n| Commands | `POST /v1/sessions/{session_id}/commands` | Disabled |\n| Control | `POST /v1/sessions/{session_id}/control:claim`, `POST /v1/sessions/{session_id}/control:disconnect` | Disabled |\n| UI responses | `POST /v1/sessions/{session_id}/ui-responses/{correlation_id}` | Disabled |\n| Host tool results | `POST /v1/sessions/{session_id}/host-tool-results/{correlation_id}` | Disabled |\n| Host URI results | `POST /v1/sessions/{session_id}/host-uri-results/{correlation_id}` | Disabled |\n\nAuthenticated requests to disabled endpoints return:\n\n```json\n{ \"error\": \"endpoint_disabled\", \"endpoint\": \"commands\" }\n```\n\nThe `endpoint` value is one of `events`, `commands`, `control`, `uiResponses`,\n`hostToolResults`, or `hostUriResults`.\n\n## Protocol Catalog Kept for Internal Compatibility\n\nThe bridge protocol module still defines the v1 command and scope catalog so\nexisting internal tests can validate the dormant implementation and future\nre-enable work has a stable baseline.\n\nWhen internally enabled for compatibility tests, event replay still uses `last_seq` and the bounded replay reset marker `replay_window_exceeded`; command and UI response retries still use `Idempotency-Key`. These mechanisms are dormant for default external bridge clients because the endpoint matrix rejects the endpoints before they reach replay, body parsing, idempotency, scope, or dispatch logic.\n\nWorkflow-gate responses are part of the UI-response surface, not the dormant command surface: when internally enabled, an answerer responds to `workflow_gate` frame `wg_...` by posting `{ \"gate_id\": \"wg_...\", \"answer\": ... }` to `POST /v1/sessions/{session_id}/ui-responses/{gate_id}`. Gate answers are authorized by bearer auth, the `control` scope on this (default-disabled) endpoint, and the currently claimed controller owner token. `X-SKC-Bridge-Owner-Token` must match the claimed controller token; mismatches return `403 not_controller` and do not resolve the gate. `Idempotency-Key` is optional and is also forwarded as `idempotency_key` when supplied by SDK helpers.\n\n### Scopes\n\nThe configurable scope set (`BRIDGE_COMMAND_SCOPES`) is:\n\n- `prompt`\n- `control`\n- `bash`\n- `export`\n- `session`\n- `model`\n- `message:read`\n- `host_tools`\n- `host_uri`\n- `admin`\n\nThe mandatory compliance floor (`MANDATORY_FLOOR_COMMAND_SCOPES`) remains\n`prompt` for the dormant command surface. Because commands are disabled by the\nendpoint matrix, the default handshake advertises no accepted scopes.\n\n### Command catalog and scope mapping\n\n| Command | Scope |\n| --- | --- |\n| `prompt` | `prompt` |\n| `steer` | `prompt` |\n| `follow_up` | `prompt` |\n| `abort` | `prompt` |\n| `abort_and_prompt` | `prompt` |\n| `new_session` | `session` |\n| `get_state` | `message:read` |\n| `set_todos` | `control` |\n| `set_host_tools` | `host_tools` |\n| `set_host_uri_schemes` | `host_uri` |\n| `get_pending_workflow_gates` | `message:read` |\n| `set_capabilities` | `control` |\n| `set_model` | `model` |\n| `cycle_model` | `model` |\n| `get_available_models` | `model` |\n| `set_thinking_level` | `model` |\n| `cycle_thinking_level` | `model` |\n| `set_steering_mode` | `control` |\n| `set_follow_up_mode` | `control` |\n| `set_interrupt_mode` | `control` |\n| `compact` | `control` |\n| `set_auto_compaction` | `control` |\n| `set_auto_retry` | `control` |\n| `abort_retry` | `control` |\n| `bash` | `bash` |\n| `abort_bash` | `bash` |\n| `get_session_stats` | `message:read` |\n| `export_html` | `export` |\n| `switch_session` | `session` |\n| `branch` | `session` |\n| `get_branch_messages` | `session` |\n| `get_last_assistant_text` | `message:read` |\n| `set_session_name` | `session` |\n| `handoff` | `admin` |\n| `get_messages` | `message:read` |\n| `get_login_providers` | `admin` |\n| `login` | `admin` |\n| `negotiate_unattended` | `control` |\n| `workflow_gate_response` | `control` |\n\n### Dormant capabilities and frame types\n\nThese names remain in the protocol code for future compatibility and internal\nconformance tests, but they are not advertised by the default fail-closed\nhandshake:\n\nCapabilities: `events`, `prompt`, `permission`, `elicitation`, `ui.declarative`,\n`host_tools`, `host_uri`, `workflow_gate`.\n\nFrame types: `ready`, `event`, `response`, `ui_request`, `permission_request`,\n`host_tool_call`, `host_uri_request`, `reset`, `workflow_gate`, `error`.\n\n## UI Capability Parity\n\nBridge UI parity remains **semantic, not pixel-perfect** when the dormant UI\nsurface is explicitly enabled for internal validation. Local-only UI capabilities\ncontinue to report typed unsupported results instead of silent defaults:\n\n- `ui.terminal_input`\n- `ui.widget.component`\n- `ui.footer.component`\n- `ui.header.component`\n- `ui.custom.component`\n- `ui.editor.get_text`\n- `ui.editor.component`\n- `ui.tools_expanded`\n- Theme switching is unsupported (`setTheme` returns `{ success: false }`).\n\n## SDK Usage\n\n`@sayknow-cli/bridge-client` exposes `BridgeClient` with handshake, command\nhelpers mirroring the full RPC command catalog, an `events()` async generator,\ncontroller/UI/host-callback helpers, and an idempotency-key helper. The bridge\nsession-control surface remains fail-closed by default, so against an\nunconfigured bridge those helpers should be expected to fail because the server\nendpoint matrix disables the corresponding session endpoints until they are\nexplicitly enabled.\n\n`BridgeClient.respondGate(sessionId, gateId, ownerToken, answer, options)` posts to the fail-closed UI-response endpoint and returns the gate resolution envelope emitted by the bridge. It deliberately does not send `workflow_gate_response` through `/commands`. Gate answers are authorized by bearer auth, the `control` scope on the (by-default-disabled) `ui-responses` endpoint, and the current controller owner token; unauthorized owner-token attempts return `403 not_controller` without resolving the gate.\n\n> Response typing: in this experimental version, `command()` and the typed\n> command helpers return `Promise<unknown>`. Callers narrow the response\n> themselves. Importing `@sayknow-cli/coding-agent` internal `rpc-types` into the\n> SDK is intentionally avoided to preserve the package boundary; stable shared\n> protocol response types are tracked as follow-up work.\n\n## Limitations\n\n- **Single session per process.** A bridge process serves exactly one live\n `AgentSession`. The `session_id` is present in every frame and endpoint for\n ordering and future additive multiplexing, but multi-session multiplexing is\n **not** implemented in v1.\n- Session events, commands, controller ownership, UI responses, host tool\n results, and host URI results are disabled by default.\n- Coarse per-token scopes only (no fine-grained per-command policy yet).\n- UI parity is semantic, not pixel-perfect (see UI Capability Parity).\n\n## Hermes/Claw orchestration layering\n\nFor Hermes/Claw-style orchestration, treat `skc` as an external runner. The orchestration agent should choose or create the repository checkout first, preferably a dedicated Git worktree for branch-local work, then launch or attach a leader session with `skc --tmux` from that directory. SKC is not embedded runtime injection into Hermes, Claw Code, or another coding tool.\n\nPublic orchestration boundaries:\n\n1. Choose the repo/worktree and branch that will own changes, logs, and review evidence.\n2. Start or attach the SKC leader with `skc --tmux` from that directory. If you want SKC to create the sibling worktree, use `skc --tmux --worktree <branch-like-name>`; the argument is a worktree/branch name, not a filesystem path.\n3. Submit the workflow appropriate to the task: `/skill:deep-interview` for requirements discovery, `/skill:ralplan` for plan consensus, and `skc ultragoal ...` for durable goal tracking through execution and verification.\n4. Use `skc team ...` only when coordinated parallel tmux workers help with implementation or verification; single-lane work should stay in the leader session.\n5. Collect the handoff state: whether the session stopped cleanly, changed files, commands/checks run, failures, unresolved risks, and evidence summaries.\n\nBridge mode remains the public remote-control protocol for an already-running SKC session, but the session-control endpoints are fail-closed by default. Keep lifecycle, worktree selection, and evidence policy above the bridge frames, and avoid documenting private deployment, routing, or credential internals. Introducing another authenticated remote-control protocol for the same purpose should require ADR-level rationale.\n\nThe same external-runner workflow is summarized in the README section [Using SKC with other coding agents](../README.md#using-skc-with-other-coding-agents).\n",
|
|
18
|
+
"bridge.md": "# Bridge Protocol Reference (Experimental, Fail-Closed)\n\nBridge mode runs the coding agent as an experimental network control surface over\nHTTPS. The session-control surface is intentionally **fail-closed by default**\nwhile the bridge security model is hardened.\n\nDefault availability:\n\n- `GET /healthz` is available without auth and returns `{ \"status\": \"ok\" }`.\n- `GET /v1/help` is available without auth and reports the fail-closed endpoint\n matrix.\n- `POST /v1/handshake` remains authenticated, but the default response advertises\n no enabled session endpoints, no accepted capabilities, no accepted scopes, and\n no frame types.\n- `GET /v1/sessions/{session_id}/events` fails closed with\n `403 endpoint_disabled` after bearer auth succeeds.\n- `POST /v1/sessions/{session_id}/commands` fails closed with\n `403 endpoint_disabled` after bearer auth succeeds and before body parsing,\n command validation, scope checks, or dispatch.\n- `POST /v1/sessions/{session_id}/control:claim` and\n `POST /v1/sessions/{session_id}/control:disconnect` fail closed with\n `403 endpoint_disabled` after bearer auth succeeds.\n- `POST /v1/sessions/{session_id}/ui-responses/{correlation_id}` fails closed\n with `403 endpoint_disabled` after bearer auth succeeds and before body parsing\n or controller checks.\n- `POST /v1/sessions/{session_id}/host-tool-results/{correlation_id}` and\n `POST /v1/sessions/{session_id}/host-uri-results/{correlation_id}` fail closed\n with `403 endpoint_disabled` after bearer auth succeeds and before body parsing\n or host callback handling.\n\nThe implementation still contains the v1 protocol scaffolding and internal tests\nfor the previously enabled surface, but external clients must treat events,\ncommands, controller ownership, UI responses, host tool results, and host URI\nresults as unavailable unless a future release explicitly re-enables them.\n\nPrimary implementation:\n\n- `src/modes/bridge/bridge-mode.ts`\n- `src/modes/bridge/auth.ts`\n- `src/modes/bridge/event-stream.ts`\n- `src/modes/bridge/bridge-client-bridge.ts`\n- `src/modes/bridge/bridge-ui-context.ts`\n- `src/modes/shared/agent-wire/*` (protocol, scopes, handshake, command dispatch/validation, host bridges)\n- `packages/bridge-client/src/*`\n\n## Startup\n\n```bash\nskc --mode bridge [regular CLI options]\n```\n\nBehavior notes:\n\n- The bridge is served over **HTTPS only**. Startup refuses to bind without TLS\n configured (see Security and TLS). There is no unencrypted startup path.\n- `@file` CLI arguments are rejected in bridge mode (as in RPC mode).\n- Bridge mode reuses the RPC default-setting overrides and suppresses automatic\n session title generation.\n- One bridge process serves exactly **one live `AgentSession`**.\n- The default endpoint matrix disables session events, commands, controller\n ownership, UI responses, host tool results, and host URI results.\n\n### Configuration (environment variables)\n\nSee `docs/environment-variables.md` for the authoritative table. Summary:\n\n| Variable | Required | Default | Notes |\n| --- | --- | --- | --- |\n| `SKC_BRIDGE_TOKEN` | Yes | — | Bearer token for authenticated endpoints. **Secret — never commit.** |\n| `SKC_BRIDGE_TLS_CERT` | Yes | — | Path to the TLS certificate (PEM). |\n| `SKC_BRIDGE_TLS_KEY` | Yes | — | Path to the TLS private key (PEM). **Secret — never commit.** |\n| `SKC_BRIDGE_HOST` | No | `127.0.0.1` | Bind hostname. |\n| `SKC_BRIDGE_PORT` | No | `4077` | Bind port (1–65535). |\n| `SKC_BRIDGE_SCOPES` | No | `prompt` | Parsed for internal compatibility, but default session endpoints are fail-closed. |\n\n## Security and TLS\n\nThe bridge is a network control surface, so it is **secure-by-default**:\n\n- **TLS is mandatory for every bind, including loopback.** Startup fails closed\n with a clear error if `SKC_BRIDGE_TLS_CERT` and `SKC_BRIDGE_TLS_KEY` are not\n both set. There is no plaintext fallback and no insecure/trust-bypass switch.\n- **Bearer token is mandatory** for every endpoint except `GET /healthz` and\n `GET /v1/help`.\n- The TypeScript SDK refuses bearer-token clients over non-`https` URLs by\n default. It allows plaintext only for `localhost`, `127.0.0.1`, or `[::1]`\n when the caller explicitly passes the localhost/test opt-in.\n- Session endpoints fail closed by default even when bearer auth and scopes are\n otherwise valid.\n\n## Handshake\n\n```\nPOST /v1/handshake (authenticated)\n```\n\nThe client sends its supported protocol version range, requested capabilities,\nand requested scopes. Version mismatch returns `status: \"rejected\"`,\n`reason: \"incompatible_version\"`. Malformed request bodies return\n`400 invalid_request`.\n\nIn the default fail-closed configuration, a successful authenticated\nhandshake returns:\n\n- `protocol_version` — the server protocol version (`BRIDGE_PROTOCOL_VERSION`, `2`).\n- `session_id` — the single session id this bridge serves.\n- `accepted_capabilities` — empty.\n- `accepted_scopes` — empty.\n- `unsupported` — every requested capability.\n- `endpoints` — all session endpoint descriptors present but empty strings.\n- `frame_types` — empty.\n\n## Fail-Closed Endpoint Matrix\n\nThe disabled endpoint matrix is:\n\n| Surface | Endpoint(s) | Default |\n| --- | --- | --- |\n| Events | `GET /v1/sessions/{session_id}/events?last_seq=<n>` | Disabled |\n| Commands | `POST /v1/sessions/{session_id}/commands` | Disabled |\n| Control | `POST /v1/sessions/{session_id}/control:claim`, `POST /v1/sessions/{session_id}/control:disconnect` | Disabled |\n| UI responses | `POST /v1/sessions/{session_id}/ui-responses/{correlation_id}` | Disabled |\n| Host tool results | `POST /v1/sessions/{session_id}/host-tool-results/{correlation_id}` | Disabled |\n| Host URI results | `POST /v1/sessions/{session_id}/host-uri-results/{correlation_id}` | Disabled |\n\nAuthenticated requests to disabled endpoints return:\n\n```json\n{ \"error\": \"endpoint_disabled\", \"endpoint\": \"commands\" }\n```\n\nThe `endpoint` value is one of `events`, `commands`, `control`, `uiResponses`,\n`hostToolResults`, or `hostUriResults`.\n\n## Protocol Catalog Kept for Internal Compatibility\n\nThe bridge protocol module still defines the v1 command and scope catalog so\nexisting internal tests can validate the dormant implementation and future\nre-enable work has a stable baseline.\n\nWhen internally enabled for compatibility tests, event replay still uses `last_seq` and the bounded replay reset marker `replay_window_exceeded`; command and UI response retries still use `Idempotency-Key`. These mechanisms are dormant for default external bridge clients because the endpoint matrix rejects the endpoints before they reach replay, body parsing, idempotency, scope, or dispatch logic.\n\nWorkflow-gate responses are part of the UI-response surface, not the dormant command surface: when internally enabled, an answerer responds to `workflow_gate` frame `wg_...` by posting `{ \"gate_id\": \"wg_...\", \"answer\": ... }` to `POST /v1/sessions/{session_id}/ui-responses/{gate_id}`. Gate answers are authorized by bearer auth, the `control` scope on this (default-disabled) endpoint, and the currently claimed controller owner token. `X-SKC-Bridge-Owner-Token` must match the claimed controller token; mismatches return `403 not_controller` and do not resolve the gate. `Idempotency-Key` is optional and is also forwarded as `idempotency_key` when supplied by SDK helpers.\n\n### Scopes\n\nThe configurable scope set (`BRIDGE_COMMAND_SCOPES`) is:\n\n- `prompt`\n- `control`\n- `bash`\n- `export`\n- `session`\n- `model`\n- `message:read`\n- `host_tools`\n- `host_uri`\n- `admin`\n\nThe mandatory compliance floor (`MANDATORY_FLOOR_COMMAND_SCOPES`) remains\n`prompt` for the dormant command surface. Because commands are disabled by the\nendpoint matrix, the default handshake advertises no accepted scopes.\n\n### Command catalog and scope mapping\n\n| Command | Scope |\n| --- | --- |\n| `prompt` | `prompt` |\n| `steer` | `prompt` |\n| `follow_up` | `prompt` |\n| `abort` | `prompt` |\n| `abort_and_prompt` | `prompt` |\n| `new_session` | `session` |\n| `get_state` | `message:read` |\n| `set_todos` | `control` |\n| `set_host_tools` | `host_tools` |\n| `set_host_uri_schemes` | `host_uri` |\n| `get_pending_workflow_gates` | `message:read` |\n| `set_capabilities` | `control` |\n| `set_model` | `model` |\n| `set_default_model_selection` | `model` |\n| `cycle_model` | `model` |\n| `get_available_models` | `model` |\n| `set_thinking_level` | `model` |\n| `cycle_thinking_level` | `model` |\n| `set_steering_mode` | `control` |\n| `set_follow_up_mode` | `control` |\n| `set_interrupt_mode` | `control` |\n| `compact` | `control` |\n| `set_auto_compaction` | `control` |\n| `set_auto_retry` | `control` |\n| `abort_retry` | `control` |\n| `bash` | `bash` |\n| `abort_bash` | `bash` |\n| `get_session_stats` | `message:read` |\n| `export_html` | `export` |\n| `switch_session` | `session` |\n| `branch` | `session` |\n| `get_branch_messages` | `session` |\n| `get_last_assistant_text` | `message:read` |\n| `set_session_name` | `session` |\n| `handoff` | `admin` |\n| `get_messages` | `message:read` |\n| `get_login_providers` | `admin` |\n| `login` | `admin` |\n| `negotiate_unattended` | `control` |\n| `workflow_gate_response` | `control` |\n\n### Dormant capabilities and frame types\n\nThese names remain in the protocol code for future compatibility and internal\nconformance tests, but they are not advertised by the default fail-closed\nhandshake:\n\nCapabilities: `events`, `prompt`, `permission`, `elicitation`, `ui.declarative`,\n`host_tools`, `host_uri`, `workflow_gate`.\n\nFrame types: `ready`, `event`, `response`, `ui_request`, `permission_request`,\n`host_tool_call`, `host_uri_request`, `reset`, `workflow_gate`, `error`.\n\n## UI Capability Parity\n\nBridge UI parity remains **semantic, not pixel-perfect** when the dormant UI\nsurface is explicitly enabled for internal validation. Local-only UI capabilities\ncontinue to report typed unsupported results instead of silent defaults:\n\n- `ui.terminal_input`\n- `ui.widget.component`\n- `ui.footer.component`\n- `ui.header.component`\n- `ui.custom.component`\n- `ui.editor.get_text`\n- `ui.editor.component`\n- `ui.tools_expanded`\n- Theme switching is unsupported (`setTheme` returns `{ success: false }`).\n\n## SDK Usage\n\n`@sayknow-cli/bridge-client` exposes `BridgeClient` with handshake, command\nhelpers mirroring the full RPC command catalog, an `events()` async generator,\ncontroller/UI/host-callback helpers, and an idempotency-key helper. The bridge\nsession-control surface remains fail-closed by default, so against an\nunconfigured bridge those helpers should be expected to fail because the server\nendpoint matrix disables the corresponding session endpoints until they are\nexplicitly enabled.\n\n`BridgeClient.respondGate(sessionId, gateId, ownerToken, answer, options)` posts to the fail-closed UI-response endpoint and returns the gate resolution envelope emitted by the bridge. It deliberately does not send `workflow_gate_response` through `/commands`. Gate answers are authorized by bearer auth, the `control` scope on the (by-default-disabled) `ui-responses` endpoint, and the current controller owner token; unauthorized owner-token attempts return `403 not_controller` without resolving the gate.\n\n> Response typing: in this experimental version, `command()` and the typed\n> command helpers return `Promise<unknown>`. Callers narrow the response\n> themselves. Importing `@sayknow-cli/coding-agent` internal `rpc-types` into the\n> SDK is intentionally avoided to preserve the package boundary; stable shared\n> protocol response types are tracked as follow-up work.\n\n## Limitations\n\n- **Single session per process.** A bridge process serves exactly one live\n `AgentSession`. The `session_id` is present in every frame and endpoint for\n ordering and future additive multiplexing, but multi-session multiplexing is\n **not** implemented in v1.\n- Session events, commands, controller ownership, UI responses, host tool\n results, and host URI results are disabled by default.\n- Coarse per-token scopes only (no fine-grained per-command policy yet).\n- UI parity is semantic, not pixel-perfect (see UI Capability Parity).\n\n## Hermes/Claw orchestration layering\n\nFor Hermes/Claw-style orchestration, treat `skc` as an external runner. The orchestration agent should choose or create the repository checkout first, preferably a dedicated Git worktree for branch-local work, then launch or attach a leader session with `skc --tmux` from that directory. SKC is not embedded runtime injection into Hermes, Claw Code, or another coding tool.\n\nPublic orchestration boundaries:\n\n1. Choose the repo/worktree and branch that will own changes, logs, and review evidence.\n2. Start or attach the SKC leader with `skc --tmux` from that directory. If you want SKC to create the sibling worktree, use `skc --tmux --worktree <branch-like-name>`; the argument is a worktree/branch name, not a filesystem path.\n3. Submit the workflow appropriate to the task: `/skill:deep-interview` for requirements discovery, `/skill:ralplan` for plan consensus, and `skc ultragoal ...` for durable goal tracking through execution and verification.\n4. Use `skc team ...` only when coordinated parallel tmux workers help with implementation or verification; single-lane work should stay in the leader session.\n5. Collect the handoff state: whether the session stopped cleanly, changed files, commands/checks run, failures, unresolved risks, and evidence summaries.\n\nBridge mode remains the public remote-control protocol for an already-running SKC session, but the session-control endpoints are fail-closed by default. Keep lifecycle, worktree selection, and evidence policy above the bridge frames, and avoid documenting private deployment, routing, or credential internals. Introducing another authenticated remote-control protocol for the same purpose should require ADR-level rationale.\n\nThe same external-runner workflow is summarized in the README section [Using SKC with other coding agents](../README.md#using-skc-with-other-coding-agents).\n",
|
|
18
19
|
"codebase-overview.md": "# Codebase Overview\n\nThis document maps the main parts of the `sayknow-cli` repository. The root README stays intentionally small; this file is the architecture-oriented companion.\n\n## Product shape\n\nSayknow-CLI (`skc`) is centered on `packages/coding-agent/`. The public workflow surface is intentionally fixed at four source-bundled skills and four public role subagents. Runtime state, specs, plans, goals, team state, and local overrides live under `.skc/`.\n\nDefault workflow skills are embedded from:\n\n```text\npackages/coding-agent/src/defaults/skc/skills/<name>/SKILL.md\n```\n\nPublic role subagent prompts are embedded from:\n\n```text\npackages/coding-agent/src/prompts/agents/<role>.md\n```\n\nThe runtime can still discover project/user overrides, but the bundled defaults are loaded from source so a missing project `.skc` directory does not remove the default workflow surface.\n\n## Packages\n\n### `packages/coding-agent/`\n\nMain `skc` CLI and product runtime.\n\n- `packages/coding-agent/package.json` exposes the `skc` binary at `src/cli.ts` and the SDK/barrel entrypoint at `src/index.ts`.\n- `packages/coding-agent/src/cli.ts` is the executable bootstrap. It registers CLI commands such as `setup`, `deep-interview`, `ralplan`, `ultragoal`, `team`, and the default launch path.\n- `packages/coding-agent/src/main.ts` adapts CLI options into session creation and dispatches interactive, print, RPC, RPC-UI, ACP, and Bridge modes.\n- `packages/coding-agent/src/sdk.ts` assembles settings, model registry, auth, workspace/context discovery, skills, rules, tools, system prompt, and the underlying `@sayknow-cli/agent-core` agent.\n- `packages/coding-agent/src/tools/index.ts` is the built-in tool registry for file/code/runtime tools such as read, bash, edit, AST tools, eval, find/search, LSP, browser, task/subagent, recipe, IRC, todo, web search, and write. Memory backends are private integrations, not public coding-harness tools.\n- `packages/coding-agent/src/defaults/skc-defaults.ts` embeds and installs the default workflow skills.\n- `packages/coding-agent/src/task/agents.ts` embeds bundled task-agent prompts. The public contract is `executor`, `architect`, `planner`, and `critic`; other bundled prompts are internal/runtime utilities.\n- `packages/coding-agent/src/coordinator/contract.ts` defines the transport-neutral third-party coordinator contract used by `skc mcp-serve coordinator`, `skc coordinator`, and `skc setup hermes`.\n- `packages/coding-agent/src/coordinator-mcp/server.ts` implements the outward MCP adapter for bot/coordinator integrations, including session start/register, turn state, question answering, status reports, and artifact reads.\n- `docs/external-control-readiness.md` classifies the public external-control surfaces: Coordinator MCP for multi-session control planes, RPC stdio for subprocess workers, ACP for editor/ACP clients, and Bridge HTTPS as experimental/fail-closed protocol scaffolding.\n\n### `packages/ai/`\n\nProvider/model boundary for LLM access.\n\n- `packages/ai/src/index.ts` exports model registry/resolution, provider implementations, auth broker/gateway/storage, streaming, usage, retry/overflow utilities, OAuth, discovery, and validation helpers.\n- `packages/ai/src/types.ts` defines provider, model, context, message, tool, usage, reasoning, and stream-event contracts.\n- `packages/ai/src/stream.ts` dispatches model-driven streams to the right provider/API implementation and normalizes streaming events.\n- `packages/ai/src/model-manager.ts` merges static, cached, dynamic, and remote model sources.\n- `packages/ai/README.md` documents tool calling, partial streaming tool calls, thinking/reasoning, provider configuration, context handoff, and OAuth flows.\n\n### `packages/agent/`\n\nStateful agent runtime built on `@sayknow-cli/ai`.\n\n- `packages/agent/src/index.ts` exports the `Agent`, loop APIs, append-only context, compaction, telemetry, proxy utilities, thinking helpers, and shared types.\n- `packages/agent/src/agent-loop.ts` owns the turn loop: transform context, call the model stream, execute tool calls, append tool results, and emit lifecycle events.\n- `packages/agent/src/agent.ts` wraps the loop with mutable state, subscriptions, prompt/continue/abort APIs, queues, provider session state, telemetry, and state mutation helpers.\n- `packages/agent/src/types.ts` defines `AgentMessage`, `AgentTool`, loop config, event, and runtime state contracts.\n\n### `packages/tui/`\n\nTerminal UI framework used by the CLI.\n\n- `packages/tui/src/index.ts` exports components, keybindings, autocomplete, terminal abstractions, image support, TUI core, and utilities.\n- `packages/tui/src/tui.ts` manages component rendering, focus, overlays, terminal dimensions, diff state, and synchronized output.\n- `packages/tui/src/terminal.ts` abstracts terminal lifecycle, dimensions, cursor controls, title/progress, Kitty protocol state, and appearance notifications.\n- `packages/tui/README.md` documents the component model and built-in components such as text, input, editor, markdown, loaders, select/settings lists, spacer, image, box, and container.\n\n### `packages/natives/` and Rust crates\n\nNative helper layer exposed through N-API.\n\n- `packages/natives/package.json` exports `native/index.js` and generated TypeScript definitions.\n- `packages/natives/native/loader-state.js` resolves platform/CPU-specific native binaries and validates package/native version alignment.\n- `crates/pi-natives/src/lib.rs` is the N-API root for appearance, AST search/editing, clipboard, filesystem scan/cache, grep/glob, syntax highlighting, HTML-to-Markdown, keyboard parsing, process/PTY/shell support, SIXEL, code summarization, token counting, text measurement/wrapping/truncation, workspace scanning, power assertions, and isolation helpers.\n- `crates/pi-shell/src/lib.rs` exposes brush-based shell execution primitives used by the native shell adapter.\n- `crates/pi-shell/src/shell.rs` implements persistent and one-shot shell execution, streaming, environment handling, cancellation, and output minimizer telemetry.\n- `crates/pi-shell/src/fixup.rs` performs conservative AST-based bash command fixups.\n- `crates/pi-natives/src/pty.rs` implements interactive PTY sessions.\n\n### `packages/utils/`\n\nShared TypeScript utilities.\n\n- `packages/utils/src/index.ts` exports abortable/async helpers, color/env/dir utilities, fetch retry, formatting, frontmatter, glob helpers, JSON helpers, logging, MIME detection, prompt rendering, process-tree helpers, sanitization, streams, temp files, tab spacing, type guards, and executable lookup.\n- `packages/utils/src/ptree.ts` and `packages/utils/src/procmgr.ts` wrap native process helpers for ergonomic TypeScript use.\n\n### `packages/stats/`\n\nLocal observability dashboard for session and model usage.\n\n- `packages/stats/src/index.ts` exposes the `skc-stats` CLI entrypoint and exports aggregation/server APIs.\n- `packages/stats/src/aggregator.ts` parses session-derived request metrics and writes aggregated data through SQLite.\n- `packages/stats/src/server.ts` serves local dashboard API routes and static SPA assets.\n- `packages/stats/src/types.ts` and `packages/stats/src/shared-types.ts` define dashboard and aggregate metric shapes.\n\n### `packages/typescript-edit-benchmark/`\n\nPrivate benchmark package for TypeScript edit tasks.\n\n- `packages/typescript-edit-benchmark/package.json` exposes `typescript-edit-benchmark` and depends on the coding-agent, agent-core, ai, tui, utils, diff, prettier, and Babel tooling.\n- `packages/typescript-edit-benchmark/src/index.ts` is the benchmark CLI: it resolves fixtures, loads tasks, runs edit attempts, records progress, and writes reports/conversation dumps under `runs/`.\n\n## Python packages\n\n### `python/skc-rpc/`\n\nTyped Python client for `skc --mode rpc`.\n\n- `python/skc-rpc/pyproject.toml` packages `skc-rpc` for Python 3.11+.\n- `python/skc-rpc/README.md` documents the process-backed stdio client, typed command methods, startup flags, event listeners, todo seeding, host-owned tools, and host-owned URI schemes.\n- `docs/bot-integration.md` is the practical entry guide for generic external controller and bot authors; it ties together coordinator MCP, RPC stdio, bridge limitations, visible tmux fallback, provider-independent smokes, errors, and artifact/report consumption.\n\n### `python/roboskc/`\n\nSelf-hosted GitHub triage/fix bot that drives `skc --mode rpc`.\n\n- `python/roboskc/AGENTS.md` is the authoritative local contract for this subtree.\n- `python/roboskc/pyproject.toml` packages `roboskc` for Python 3.11+ with FastAPI, httpx, pydantic settings, Click, and `skc-rpc`.\n- `python/roboskc/README.md` documents the webhook-to-worktree-to-skc flow, GitHub sidecar trust boundary, persistent per-issue sessions, and audit trail.\n- Important modules include `src/server.py`, `src/queue.py`, `src/tasks.py`, `src/worker.py`, `src/host_tools.py`, `src/sandbox.py`, `src/github_client.py`, `src/github_events.py`, `src/db.py`, and `src/config.py`.\n\n## Runtime flow\n\nA normal CLI session starts in `packages/coding-agent/src/cli.ts`, routes through command handling, then reaches `packages/coding-agent/src/main.ts`. `main.ts` converts CLI/runtime settings into `CreateAgentSessionOptions` and calls `createAgentSession()` in `packages/coding-agent/src/sdk.ts`.\n\nThe SDK builds the session context, loads the default skills, creates built-in tools, resolves model/auth state through `@sayknow-cli/ai`, constructs the system prompt, and instantiates `@sayknow-cli/agent-core`. The agent loop streams model events, executes tools, records tool results, and hands state back to the selected mode: interactive TUI, print, RPC, RPC-UI, ACP, or Bridge.\n\n## Verification and gates\n\nPackage-local checks are defined in each `package.json`. For workflow-definition or default-surface changes, the focused gates are:\n\n```sh\nbun scripts/check-visible-definitions.ts\nbun scripts/verify-g002-gates.ts\nbun scripts/rebrand-inventory.ts --strict\nbun test packages/coding-agent/test/default-skc-definitions.test.ts\n```\n\nFor broader TypeScript verification, use the root script:\n\n```sh\nbun run check:ts\n```\n\nDo not use `tsc` or `npx tsc` directly in this repository.\n",
|
|
19
20
|
"codegraph-custom-tool.md": "# CodeGraph as a custom tool\n\n[CodeGraph](https://github.com/colbymchenry/codegraph) is a local, language-agnostic\ncode knowledge graph for AI agents. It pre-indexes symbols, call edges, and\ndependencies in a project so an agent can answer structural questions (\"how does X\nwork\", \"who calls X\", \"what breaks if I change X\") in a few graph queries instead\nof crawling files with `search`/`read`.\n\nThis guide shows how to wire CodeGraph into SKC through the **custom-tool extension\npath** — no core changes, no built-in provider. SKC intentionally keeps third-party\nCLI integrations like this in the user/project extension layer rather than bundling\nthem, so you own the integration and its lifecycle.\n\n> CodeGraph integrates with other agents over MCP, but this guide wires it as a SKC\n> custom tool around CodeGraph's local **CLI** — it does not add an MCP server or a\n> built-in provider. For how SKC treats MCP servers in standalone sessions, see\n> [`standalone-mcp.md`](standalone-mcp.md).\n\n## 1. Install and index\n\n```bash\n# Install the CodeGraph CLI (or use the install script from CodeGraph's README).\nnpm i -g @colbymchenry/codegraph\n\n# Build the local index for a project.\ncd your-project\ncodegraph init\n```\n\n`codegraph init` creates a local `.codegraph/` directory. No data leaves your\nmachine — it is a local SQLite index.\n\n## 2. Add the custom tool\n\nSKC discovers custom tools from a `tools/` directory in its config dirs:\n\n- **Project-scoped**: `<project>/.skc/tools/`\n- **User-scoped (all projects)**: `~/.skc/agent/tools/`\n\nA `*.ts` tool file's default export is a factory `(pi) => CustomTool`. The factory\nreceives an API (`pi`) with members such as `exec`, `cwd`, `zod`, and `logger` — so\nthe tool needs no imports from SKC internals.\n\nSave the following as `.skc/tools/codegraph.ts` (project) or\n`~/.skc/agent/tools/codegraph.ts` (user):\n\n```typescript\n/**\n * CodeGraph custom tool for sayknow-cli (SKC).\n *\n * Wraps the local CodeGraph CLI (https://github.com/colbymchenry/codegraph) so the\n * agent can query a project's code knowledge graph instead of crawling files.\n *\n * It only runs CodeGraph's query-style (read-only) subcommands and never edits your\n * source files. It does not run indexing or sync commands. (CodeGraph maintains its\n * own local `.codegraph/` index via its own CLI; this tool only reads from it.)\n *\n * Prereqs: `npm i -g @colbymchenry/codegraph` and `codegraph init` in the project.\n */\nimport type { CustomToolFactory } from \"@sayknow-cli/coding-agent\"; // optional: editor types only\n\nconst CODEGRAPH_CLI = \"codegraph\";\nconst TIMEOUT_MS = 60_000;\nconst SEARCH_LIMIT_DEFAULT = 10;\nconst MAX = 100;\n\nconst codegraph: CustomToolFactory = (pi) => {\n\tconst z = pi.zod;\n\n\tconst parameters = z\n\t\t.object({\n\t\t\top: z\n\t\t\t\t.enum([\"explore\", \"search\", \"callers\", \"callees\", \"impact\", \"status\"])\n\t\t\t\t.describe(\n\t\t\t\t\t\"explore: context (relevant source + call paths) for a natural-language query — prefer for 'how does X work'; search: full-text symbol search (target=query); callers: who calls target; callees: what target calls; impact: blast radius of changing target; status: index health (no target).\",\n\t\t\t\t),\n\t\t\ttarget: z\n\t\t\t\t.string()\n\t\t\t\t.optional()\n\t\t\t\t.describe(\n\t\t\t\t\t\"For explore: a natural-language query or symbol(s). For callers/callees/impact: a symbol name. For search: the query. Omit for status.\",\n\t\t\t\t),\n\t\t\tlimit: z.number().int().min(1).max(MAX).optional().describe(`Max search results (default ${SEARCH_LIMIT_DEFAULT}).`),\n\t\t\tmaxFiles: z.number().int().min(1).max(MAX).optional().describe(\"For explore: cap files whose source is included.\"),\n\t\t})\n\t\t.strict();\n\n\ttype Params = import(\"zod/v4\").infer<typeof parameters>;\n\n\tfunction buildArgs(params: Params): string[] {\n\t\tif (params.op === \"status\") return [\"status\", pi.cwd, \"--json\"];\n\t\tconst target = params.target?.trim();\n\t\tif (!target) throw new Error(`codegraph ${params.op} requires a non-empty \"target\".`);\n\t\tif (params.op === \"search\") {\n\t\t\tconst limit = Math.min(params.limit ?? SEARCH_LIMIT_DEFAULT, MAX);\n\t\t\treturn [\"query\", target, \"--json\", \"--limit\", String(limit), \"--path\", pi.cwd];\n\t\t}\n\t\tif (params.op === \"explore\") {\n\t\t\tconst args = [\"explore\", target, \"--path\", pi.cwd];\n\t\t\tif (params.maxFiles !== undefined) args.push(\"--max-files\", String(params.maxFiles));\n\t\t\treturn args;\n\t\t}\n\t\treturn [params.op, target, \"--json\", \"--path\", pi.cwd];\n\t}\n\n\tfunction ref(r: { name: string; kind: string; filePath: string; startLine: number }): string {\n\t\treturn ` - ${r.name} (${r.kind}) — ${r.filePath}:${r.startLine}`;\n\t}\n\n\tfunction render(params: Params, stdout: string): string {\n\t\tif (params.op === \"explore\") return stdout.trim() || `No exploration results for \"${params.target?.trim() ?? \"\"}\".`;\n\t\tlet data: any;\n\t\ttry {\n\t\t\tdata = JSON.parse(stdout);\n\t\t} catch {\n\t\t\tthrow new Error(`codegraph ${params.op} returned unparseable output.`);\n\t\t}\n\t\tif (params.op === \"search\") {\n\t\t\tconst hits = data as Array<{ node: any }>;\n\t\t\tif (hits.length === 0) return `No symbols matched \"${params.target?.trim() ?? \"\"}\".`;\n\t\t\treturn [\n\t\t\t\t`${hits.length} symbol(s) matching \"${params.target?.trim() ?? \"\"}\":`,\n\t\t\t\t...hits.map(({ node }) => {\n\t\t\t\t\tconst exp = node.isExported ? \" [exported]\" : \"\";\n\t\t\t\t\tconst sig = node.signature ? ` ${node.signature}` : \"\";\n\t\t\t\t\treturn ` - ${node.name} (${node.kind})${sig}${exp} — ${node.filePath}:${node.startLine}`;\n\t\t\t\t}),\n\t\t\t].join(\"\\n\");\n\t\t}\n\t\tif (params.op === \"callers\") {\n\t\t\tconst list = data.callers ?? [];\n\t\t\treturn list.length === 0\n\t\t\t\t? `No callers found for \"${data.symbol}\".`\n\t\t\t\t: [`${list.length} caller(s) of \"${data.symbol}\":`, ...list.map(ref)].join(\"\\n\");\n\t\t}\n\t\tif (params.op === \"callees\") {\n\t\t\tconst list = data.callees ?? [];\n\t\t\treturn list.length === 0\n\t\t\t\t? `\"${data.symbol}\" has no recorded callees.`\n\t\t\t\t: [`${list.length} callee(s) of \"${data.symbol}\":`, ...list.map(ref)].join(\"\\n\");\n\t\t}\n\t\tif (params.op === \"impact\") {\n\t\t\tconst header = `Impact of changing \"${data.symbol}\" (depth ${data.depth}): ${data.nodeCount} node(s), ${data.edgeCount} edge(s) affected.`;\n\t\t\tconst list = data.affected ?? [];\n\t\t\treturn list.length === 0 ? header : [header, \"Affected:\", ...list.map(ref)].join(\"\\n\");\n\t\t}\n\t\t// status\n\t\tif (!data.initialized) return `CodeGraph is not initialized for ${data.projectPath}. Run \\`codegraph init\\`.`;\n\t\tconst lines = [\n\t\t\t`CodeGraph index for ${data.projectPath}:`,\n\t\t\t` files: ${data.fileCount}, nodes: ${data.nodeCount}, edges: ${data.edgeCount}`,\n\t\t];\n\t\tif (data.languages?.length) lines.push(` languages: ${data.languages.join(\", \")}`);\n\t\tconst p = data.pendingChanges;\n\t\tif (p && (p.added || p.modified || p.removed)) lines.push(` pending sync: +${p.added} ~${p.modified} -${p.removed}`);\n\t\treturn lines.join(\"\\n\");\n\t}\n\n\treturn {\n\t\tname: \"codegraph\",\n\t\tlabel: \"CodeGraph\",\n\t\tdescription:\n\t\t\t\"Query the project's CodeGraph code knowledge graph (symbols, callers, callees, impact, and an 'explore' context query) via the local codegraph CLI. Read-only with respect to your source. Prefer over search/read for structural questions. Requires `codegraph init` to have been run in the project.\",\n\t\tparameters,\n\t\tstrict: true,\n\t\tasync execute(_id: string, params: Params, _onUpdate: unknown, _ctx: unknown, signal?: AbortSignal) {\n\t\t\tlet result: { stdout: string; stderr: string; code: number };\n\t\t\ttry {\n\t\t\t\tresult = await pi.exec(CODEGRAPH_CLI, buildArgs(params), { cwd: pi.cwd, signal, timeout: TIMEOUT_MS });\n\t\t\t} catch (e) {\n\t\t\t\tconst msg = e instanceof Error ? e.message : String(e);\n\t\t\t\tif (/not found|enoent/i.test(msg)) {\n\t\t\t\t\tthrow new Error(\"The `codegraph` CLI is not installed. Install it: npm i -g @colbymchenry/codegraph\");\n\t\t\t\t}\n\t\t\t\tthrow e;\n\t\t\t}\n\t\t\tif (result.code !== 0) {\n\t\t\t\tconst err = result.stderr.toLowerCase();\n\t\t\t\tif (err.includes(\"not initialized\") || err.includes(\".codegraph\") || err.includes(\"no index\")) {\n\t\t\t\t\tthrow new Error(\"CodeGraph is not initialized for this project. Run `codegraph init` in the project root.\");\n\t\t\t\t}\n\t\t\t\tthrow new Error(result.stderr.trim() || \"codegraph failed with no diagnostic output.\");\n\t\t\t}\n\t\t\treturn { content: [{ type: \"text\", text: render(params, result.stdout) }] };\n\t\t},\n\t};\n};\n\nexport default codegraph;\n```\n\nThe `import type` line is optional — it only provides editor types when SKC is\nresolvable from your tool file. It is erased at runtime, so the tool loads fine\nwithout it.\n\n## 3. Use it\n\nStart SKC in the project. The `codegraph` tool is now available to the model. Ask\na structural question and it will call the tool, for example:\n\n- `codegraph` with `{ \"op\": \"explore\", \"target\": \"how requests are routed\" }` can\n return relevant source plus graph context in one call.\n- `{ \"op\": \"callers\", \"target\": \"MyClass.handle\" }` lists callers.\n- `{ \"op\": \"impact\", \"target\": \"parseConfig\" }` shows what a change would affect.\n- `{ \"op\": \"status\" }` reports index health.\n\n## Operations\n\n| `op` | `target` | Description |\n| --- | --- | --- |\n| `explore` | natural-language query | Context query: relevant symbols' source plus graph context (CodeGraph's `explore`). `maxFiles` caps included source. |\n| `search` | query | Full-text symbol search (`limit`, default 10). |\n| `callers` | symbol | Functions/methods that call the symbol, including dynamic dispatch. |\n| `callees` | symbol | Functions/methods the symbol calls. |\n| `impact` | symbol | Blast radius of changing the symbol. |\n| `status` | — | Index health: file/node/edge counts, languages, pending sync. |\n\n## Notes\n\n- **Read-only with respect to your code.** The tool only runs CodeGraph's\n query-style subcommands; it never edits your files and does not run indexing or\n sync commands. CodeGraph maintains its own local `.codegraph/` index via its CLI.\n If results look stale or `status` reports pending changes, refresh the index with\n CodeGraph's CLI (e.g. `codegraph sync`) outside SKC.\n- **Safe argument handling.** The example spawns CodeGraph with argv arrays via\n `pi.exec` (no shell), `op` is constrained to a fixed enum, and numeric inputs are\n capped — there is no shell interpolation of model-provided values.\n- **Scope.** Use a project-scoped file to limit the tool to one repo, or a\n user-scoped file to make it available everywhere `codegraph init` has been run.\n- **Naming.** The tool registers as `codegraph`; rename it in the file if it\n collides with another tool in your setup.\n- **Fallback.** If the graph reports a symbol is missing, a file is flagged as\n pending sync, or you need a non-structural text search, refresh the CodeGraph\n index outside SKC or fall back to `read`/`search`.\n- For background on how SKC treats external tools and MCP servers in standalone\n sessions, see [`standalone-mcp.md`](standalone-mcp.md).\n",
|
|
20
21
|
"compaction.md": "# Compaction and Branch Summaries\n\nCompaction and branch summaries are the two mechanisms that keep long sessions usable without losing prior work context.\n\n- **Compaction** rewrites old history into a summary on the current branch.\n- **Branch summary** captures abandoned branch context during `/tree` navigation.\n\nBoth are persisted as session entries and converted back into user-context messages when rebuilding LLM input.\n\n## Key implementation files\n\n- `packages/agent/src/compaction/compaction.ts` (context-full summarization and handoff generation)\n- `packages/agent/src/compaction/branch-summarization.ts`\n- `packages/agent/src/compaction/pruning.ts`\n- `packages/agent/src/compaction/utils.ts`\n- `packages/agent/src/compaction/openai.ts`\n- `packages/coding-agent/src/session/session-manager.ts`\n- `packages/coding-agent/src/session/agent-session.ts`\n- `packages/coding-agent/src/session/messages.ts`\n- `packages/coding-agent/src/extensibility/hooks/types.ts`\n- `packages/coding-agent/src/config/settings-schema.ts`\n\n## Session entry model\n\nCompaction and branch summaries are first-class session entries, not plain assistant/user messages.\n\n- `CompactionEntry`\n - `type: \"compaction\"`\n - `summary`, optional `shortSummary`\n - `firstKeptEntryId` (compaction boundary)\n - `tokensBefore`\n - optional `details`, `preserveData`, `fromExtension`\n- `BranchSummaryEntry`\n - `type: \"branch_summary\"`\n - `fromId`, `summary`\n - optional `details`, `fromExtension`\n\nWhen context is rebuilt (`buildSessionContext`):\n\n1. Latest compaction on the active path is converted to one `compactionSummary` message.\n2. Kept entries from `firstKeptEntryId` to the compaction point are re-included.\n3. Later entries on the path are appended.\n4. `branch_summary` entries are converted to `branchSummary` messages.\n5. `custom_message` entries are converted to `custom` messages.\n\nThose custom roles are then transformed into LLM-facing user messages in `convertToLlm()` using the static templates:\n\n- `packages/agent/src/compaction/prompts/compaction-summary-context.md`\n- `packages/agent/src/compaction/prompts/branch-summary-context.md`\n- `packages/agent/src/compaction/prompts/handoff-document.md`\n\n## Compaction pipeline\n\n### Triggers\n\nCompaction/context maintenance can run in four ways:\n\n1. **Manual context compaction**: `/compact [instructions]` calls `AgentSession.compact(...)`.\n2. **Automatic overflow recovery**: after a same-model assistant error that matches context overflow.\n3. **Automatic threshold maintenance**: after a successful turn when context exceeds the resolved threshold.\n4. **Idle maintenance**: `runIdleCompaction()` can invoke the same auto-maintenance path with reason `\"idle\"`.\n\n### Compaction shape (visual)\n\n```text\nBefore compaction:\n\n entry: 0 1 2 3 4 5 6 7 8 9\n ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┐\n │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │\n └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┘\n └────────┬───────┘ └──────────────┬──────────────┘\n messagesToSummarize kept messages\n ↑\n firstKeptEntryId (entry 4)\n\nAfter compaction (new entry appended):\n\n entry: 0 1 2 3 4 5 6 7 8 9 10\n ┌─────┬─────┬─────┬──────┬─────┬─────┬──────┬──────┬─────┬──────┬─────┐\n │ hdr │ usr │ ass │ tool │ usr │ ass │ tool │ tool │ ass │ tool │ cmp │\n └─────┴─────┴─────┴──────┴─────┴─────┴──────┴──────┴─────┴──────┴─────┘\n └──────────┬──────┘ └──────────────────────┬───────────────────┘\n not sent to LLM sent to LLM\n ↑\n starts from firstKeptEntryId\n\nWhat the LLM sees:\n\n ┌────────┬─────────┬─────┬─────┬──────┬──────┬─────┬──────┐\n │ system │ summary │ usr │ ass │ tool │ tool │ ass │ tool │\n └────────┴─────────┴─────┴─────┴──────┴──────┴─────┴──────┘\n ↑ ↑ └─────────────────┬────────────────┘\n prompt from cmp messages from firstKeptEntryId\n```\n\n### Overflow-retry vs threshold/idle maintenance\n\nThe automatic paths are intentionally different:\n\n- **Overflow recovery**\n - Trigger: current-model assistant error is detected as context overflow and the error is not older than the latest compaction.\n - The failing assistant error message is removed from active agent state before retry.\n - Context promotion is tried first; if a configured larger model is available, the agent switches model and retries without compacting.\n - If promotion is unavailable and compaction is enabled, context-full compaction runs with `reason: \"overflow\"` and `willRetry: true`; handoff strategy is not used for overflow.\n - On success, agent auto-continues (`agent.continue()`) after compaction.\n\n- **Threshold maintenance**\n - Trigger: successful, non-error assistant message whose adjusted context tokens exceed `resolveThresholdTokens(...)`.\n - Tool-output pruning can reduce the measured token count before threshold comparison.\n - Context promotion is tried before compaction.\n - If promotion is unavailable, auto maintenance runs with `reason: \"threshold\"` and `willRetry: false`.\n - With `compaction.strategy: \"handoff\"`, threshold maintenance starts a new handoff session instead of writing a compaction entry; if handoff returns no document without aborting, it falls back to context-full compaction.\n - On success, if `compaction.autoContinue !== false`, schedules an agent-authored developer auto-continue prompt from `prompts/system/auto-continue.md`.\n\n- **Idle maintenance**\n - Trigger: `runIdleCompaction()` when not streaming or already compacting.\n - Uses `reason: \"idle\"` and does not auto-continue afterward.\n\n### Pre-compaction pruning\n\nBefore compaction checks, tool-result pruning may run (`pruneToolOutputs`).\n\nDefault prune policy:\n\n- Protect newest `40_000` tool-output tokens.\n- Require at least `20_000` total estimated savings.\n- Never prune tool results from `skill` or `read`.\n\nPruned tool results are replaced with:\n\n- `[Output truncated - N tokens]`\n\nIf pruning changes entries, session storage is rewritten and agent message state is refreshed before compaction decisions.\n\n### Boundary and cut-point logic\n\n`prepareCompaction()` only considers entries since the last compaction entry (if any).\n\n1. Find previous compaction index.\n2. Compute `boundaryStart = prevCompactionIndex + 1`.\n3. Adapt `keepRecentTokens` using measured usage ratio when available.\n4. Run `findCutPoint()` over the boundary window.\n\nValid cut points include:\n\n- message entries with roles: `user`, `assistant`, `bashExecution`, `hookMessage`, `branchSummary`, `compactionSummary`\n- `custom_message` entries\n- `branch_summary` entries\n\nHard rule: never cut at `toolResult`.\n\nIf there are non-message metadata entries immediately before the cut point (`model_change`, `thinking_level_change`, labels, etc.), they are pulled into the kept region by moving cut index backward until a message or compaction boundary is hit.\n\n### Split-turn handling\n\nIf cut point is not at a user-turn start, compaction treats it as a split turn.\n\nTurn start detection treats these as user-turn boundaries:\n\n- `message.role === \"user\"`\n- `message.role === \"bashExecution\"`\n- `custom_message` entry\n- `branch_summary` entry\n\nSplit-turn compaction generates two summaries:\n\n1. History summary (`messagesToSummarize`)\n2. Turn-prefix summary (`turnPrefixMessages`)\n\nFinal stored summary is merged as:\n\n```markdown\n<history summary>\n\n---\n\n**Turn Context (split turn):**\n\n<turn prefix summary>\n```\n\n### Summary generation\n\n`compact(...)` builds summaries from serialized conversation text:\n\n1. Convert messages via `convertToLlm()`.\n2. Serialize with `serializeConversation()`.\n3. Wrap in `<conversation>...</conversation>`.\n4. Optionally include `<previous-summary>...</previous-summary>`.\n5. Optionally inject hook context as `<additional-context>` list.\n6. Execute summarization prompt with `SUMMARIZATION_SYSTEM_PROMPT`.\n\nPrompt selection:\n\n- first compaction: `compaction-summary.md`\n- iterative compaction with prior summary: `compaction-update-summary.md`\n- split-turn second pass: `compaction-turn-prefix.md`\n- short UI summary: `compaction-short-summary.md`\n- handoff document: `handoff-document.md` (used by `generateHandoff(...)`, not serialized compaction)\n\nRemote summarization modes:\n\n- If `compaction.remoteEndpoint` is set and remote compaction is enabled, local summary generation POSTs:\n - `{ systemPrompt, prompt }`\n- Expects JSON containing at least `{ summary }`.\n- For OpenAI/OpenAI code provider models, compaction first tries the provider-native `/responses/compact` endpoint when remote compaction is enabled. It preserves provider replacement history in `preserveData.openaiRemoteCompaction` and falls back to local summarization if that native request fails.\n\n### Handoff generation\n\n`packages/agent/src/compaction/compaction.ts` also exports `generateHandoff(...)`. Handoff generation uses the same `completeSimple(...)` oneshot style as summarization, but it preserves the live agent cache prefix by sending the active system prompt, tool array, and real LLM message history, then appending one agent-attributed `user` message containing the handoff prompt. It forces `toolChoice: \"none\"` and returns joined text blocks directly.\n\nHandoff does not write a `CompactionEntry`. `AgentSession.handoff()` owns the session transition: it starts a new session, injects the generated document as a visible `custom_message` with `customType: \"handoff\"`, and rebuilds agent messages from that new session.\n\n### File-operation context in summaries\n\nCompaction tracks cumulative file activity using assistant tool calls:\n\n- `read(path)` → read set\n- `write(path)` → modified set\n- `edit(path)` → modified set\n\nCumulative behavior:\n\n- Includes prior compaction details only when prior entry is pi-generated (`fromExtension !== true`).\n- In split turns, includes turn-prefix file ops too.\n- `readFiles` excludes files also modified.\n\nSummary text gets file tags appended via prompt template:\n\n```xml\n<read-files>\n...\n</read-files>\n<modified-files>\n...\n</modified-files>\n```\n\n### Persist and reload\n\nAfter summary generation (or hook-provided summary), agent session:\n\n1. Appends `CompactionEntry` with `appendCompaction(...)` for context-full maintenance; handoff strategy creates a new session and injects a handoff `custom_message` instead.\n2. Rebuilds display context from the active leaf via `buildDisplaySessionContext()`.\n3. Replaces live agent messages with rebuilt context.\n4. Emits `session_compact` hook event.\n\n## Branch summarization pipeline\n\nBranch summarization is tied to tree navigation, not token overflow.\n\n### Trigger\n\nDuring `navigateTree(...)`:\n\n1. Compute abandoned entries from old leaf to common ancestor using `collectEntriesForBranchSummary(...)`.\n2. If caller requested summary (`options.summarize`), generate summary before switching leaf.\n3. If summary exists, attach it at the navigation target using `branchWithSummary(...)`.\n\nOperationally this is commonly driven by `/tree` flow when `branchSummary.enabled` is enabled.\n\n### Branch switch shape (visual)\n\n```text\nTree before navigation:\n\n ┌─ B ─ C ─ D (old leaf, being abandoned)\n A ───┤\n └─ E ─ F (target)\n\nCommon ancestor: A\nEntries to summarize: B, C, D\n\nAfter navigation with summary:\n\n ┌─ B ─ C ─ D ─ [summary of B,C,D]\n A ───┤\n └─ E ─ F (new leaf)\n```\n\n### Preparation and token budget\n\n`generateBranchSummary(...)` computes budget as:\n\n- `tokenBudget = model.contextWindow - branchSummary.reserveTokens`\n\n`prepareBranchEntries(...)` then:\n\n1. First pass: collect cumulative file ops from all summarized entries, including prior pi-generated `branch_summary` details.\n2. Second pass: walk newest → oldest, adding messages until token budget is reached.\n3. Prefer preserving recent context.\n4. May still include large summary entries near budget edge for continuity.\n\nCompaction entries are included as messages (`compactionSummary`) during branch summarization input.\n\n### Summary generation and persistence\n\nBranch summarization:\n\n1. Converts and serializes selected messages.\n2. Wraps in `<conversation>`.\n3. Uses custom instructions if supplied, otherwise `branch-summary.md`.\n4. Calls summarization model with `SUMMARIZATION_SYSTEM_PROMPT`.\n5. Prepends `branch-summary-preamble.md`.\n6. Appends file-operation tags.\n\nResult is stored as `BranchSummaryEntry` with optional details (`readFiles`, `modifiedFiles`).\n\n## Extension and hook touchpoints\n\n### `session_before_compact`\n\nPre-compaction hook.\n\nCan:\n\n- cancel compaction (`{ cancel: true }`)\n- provide full custom compaction payload (`{ compaction: CompactionResult }`)\n\n### `session.compacting`\n\nPrompt/context customization hook for default compaction.\n\nCan return:\n\n- `prompt` (override base summary prompt)\n- `context` (extra context lines injected into `<additional-context>`)\n- `preserveData` (stored on compaction entry)\n\n### `session_compact`\n\nPost-compaction notification with saved `compactionEntry` and `fromExtension` flag.\n\n### `session_before_tree`\n\nRuns on tree navigation before default branch summary generation.\n\nCan:\n\n- cancel navigation\n- provide custom `{ summary: { summary, details } }` used when user requested summarization\n\n### `session_tree`\n\nPost-navigation event exposing new/old leaf and optional summary entry.\n\n## Runtime behavior and failure semantics\n\n- Manual compaction aborts current agent operation first.\n- `abortCompaction()` cancels both manual and auto-compaction controllers.\n- Auto compaction emits start/end session events for UI/state updates.\n- Auto compaction can try multiple model candidates and retry transient failures; long retry delays prefer the next candidate when one is available.\n- Overflow errors are excluded from generic retry path because they are handled by context promotion/compaction.\n- If auto-compaction fails:\n - overflow path emits `Context overflow recovery failed: ...`\n - threshold path emits `Auto-compaction failed: ...`\n- Branch summarization can be cancelled via abort signal (e.g., Escape), returning canceled/aborted navigation result.\n\n## Settings and defaults\n\nFrom `settings-schema.ts`:\n\n- `compaction.enabled` = `true`\n- `compaction.strategy` = `\"context-full\"` (`\"handoff\"` and `\"off\"` are also supported)\n- `compaction.reserveTokens` = `16384`\n- `compaction.keepRecentTokens` = `20000`\n- `compaction.autoContinue` = `true`\n- `compaction.remoteEnabled` = `true`\n- `compaction.remoteEndpoint` = `undefined`\n- `compaction.thresholdPercent` = `-1` and `compaction.thresholdTokens` = `-1`; when no positive override is set, the threshold is `contextWindow - max(15% of contextWindow, reserveTokens)`\n- `compaction.idleEnabled` = `true`\n- `branchSummary.enabled` = `false`\n- `branchSummary.reserveTokens` = `16384`\n\nThese values are consumed at runtime by `AgentSession` and compaction/branch summarization modules.\n",
|
|
@@ -74,7 +75,7 @@ export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
|
|
|
74
75
|
"render-mermaid.md": "# RenderMermaid\n\n`RenderMermaid` is an optional built-in tool that renders Mermaid source to terminal-friendly text.\n\n## Enable it\n\nDisabled by default. Turn it on in `/settings` under **Tools → Render Mermaid**, or in `~/.skc/agent/config.yml`:\n\n```yaml\nrenderMermaid:\n enabled: true\n```\n\n## What it does\n\n- Tool name: `render_mermaid`\n- Input: Mermaid source in the required `mermaid` field\n- Output: rendered ASCII/Unicode text, not SVG or PNG\n- Storage: when artifact storage is available, the full render is also saved as an `artifact://...`\n\nThere are no model-specific or environment-variable prerequisites. Once enabled, any model that can call built-in tools can use it.\n\n## Parameters\n\n```json\n{\n \"mermaid\": \"graph TD\\n A[Start] --> B[Stop]\",\n \"config\": {\n \"useAscii\": false,\n \"paddingX\": 2,\n \"paddingY\": 2,\n \"boxBorderPadding\": 0\n }\n}\n```\n\nAvailable `config` fields:\n\n- `useAscii` — `true` for plain ASCII, `false` for Unicode box-drawing characters (default and usually more readable)\n- `paddingX` — horizontal spacing between nodes\n- `paddingY` — vertical spacing between nodes\n- `boxBorderPadding` — inner padding inside node boxes\n\n## Current limitations\n\n`RenderMermaid` uses the `beautiful-mermaid` ASCII renderer. It works best for flowcharts and small diagrams.\n\nComplex sequence diagrams, especially with `alt` / `else` blocks, can become very wide in a terminal. That is current renderer behavior, not a provider or model configuration problem.\n\nIf a sequence diagram is hard to read:\n\n1. Keep Unicode output (`useAscii: false`)\n2. Reduce spacing with a tighter config such as `paddingX: 2`, `paddingY: 2`, `boxBorderPadding: 0`\n3. Prefer smaller sub-diagrams over one large sequence diagram\n4. Open the saved artifact if the inline preview is truncated in the TUI\n\n## Example\n\nInput:\n\n```mermaid\ngraph TD\n A[Start] --> B{Decision}\n B -->|Yes| C[Action]\n B -->|No| D[End]\n```\n\nTypical result:\n\n```text\n┌─────┐\n│Start│\n└─────┘\n │\n ▼\n┌────────┐\n│Decision│\n└────────┘\n```\n",
|
|
75
76
|
"research-plan-ledger.md": "# Research plan items and evidence ledger\n\nResearch/deep-research workflows need a planning contract that is stronger than an execution-order checklist. A plan item should name the claim under investigation, the uncertainty around it, what evidence is required, what counterexamples would falsify it, and how a verifier should handle source conflicts.\n\nThis document defines the public product-facing spike for issue #932. It intentionally avoids private operator, session, channel, and routing internals.\n\n## Research plan item schema\n\n```ts\ntype ResearchPlanConfidence = \"low\" | \"medium\" | \"high\";\n\ntype ResearchPlanItem = {\n claim: string;\n confidence: ResearchPlanConfidence;\n unknowns: string[];\n evidenceNeeded: string[];\n counterexampleQueries: string[];\n sourceConflictPolicy: string;\n dropCondition: string;\n verifierChecks: string[];\n};\n```\n\nField intent:\n\n- `claim`: The smallest claim that can survive or fail verification.\n- `confidence`: Planner's initial confidence before evidence collection.\n- `unknowns`: Known gaps the final answer must resolve or explicitly carry forward.\n- `evidenceNeeded`: Evidence workers must collect before the claim can be accepted.\n- `counterexampleQueries`: Directed search prompts for evidence that would weaken or falsify the claim.\n- `sourceConflictPolicy`: How the verifier treats conflicting sources, stale sources, or mismatched methodology.\n- `dropCondition`: The explicit condition that removes this claim from the final answer.\n- `verifierChecks`: Checklist the verifier applies before accepting the claim.\n\n## Evidence ledger schema\n\n```ts\ntype ResearchEvidenceVerdict = \"support\" | \"contradict\" | \"uncertain\";\n\ntype ResearchEvidenceEntry = {\n claim: string;\n source: string;\n confidence: ResearchPlanConfidence;\n verdict: ResearchEvidenceVerdict;\n notes?: string;\n};\n\ntype ResearchLedgerVerdict = {\n claim: string;\n finalVerdict: \"accepted\" | \"rejected\" | \"uncertain\";\n survivingSources: ResearchEvidenceEntry[];\n rejectReason?: string;\n unresolvedUnknowns: string[];\n};\n```\n\nThe ledger is claim-centric. Workers add evidence entries against plan-item claims; the verifier reduces those entries into a final verdict. Accepted claims can be cited in the final answer. Rejected claims are named with `rejectReason`. Uncertain claims are either excluded or marked explicitly as unresolved.\n\n## Ralplan/research workflow shape\n\n1. Planner emits `ResearchPlanItem[]` alongside the normal plan narrative when the task is research-heavy.\n2. Workers gather independent evidence for each item, including counterexample-oriented searches.\n3. Verifier checks contradictions, source quality, stale information, and unresolved uncertainty using the item's `verifierChecks`, `sourceConflictPolicy`, and `dropCondition`.\n4. The final answer cites accepted claims, lists rejected claims with reasons, and marks any surviving uncertainty.\n\n## Example\n\n```ts\nconst item: ResearchPlanItem = {\n claim: \"Model X reduces latency by 30% on production-like workloads\",\n confidence: \"medium\",\n unknowns: [\"production workload mix\"],\n evidenceNeeded: [\"benchmark with production-like fixture\", \"baseline comparison\"],\n counterexampleQueries: [\"regression on long-context workload\", \"cold-start latency increase\"],\n sourceConflictPolicy: \"Reject the claim when any credible counterexample contradicts the benchmark.\",\n dropCondition: \"Drop if a counterexample contradicts the claim or key unknowns remain unresolved.\",\n verifierChecks: [\"check source freshness\", \"compare benchmark harness\", \"inspect counterexample evidence\"],\n};\n```\n\nIf the ledger contains a supporting benchmark and a credible long-context counterexample, the verifier rejects the broad claim instead of letting a plausible summary survive by vibes.\n\n## Current spike\n\nThe first implementation spike lives in `packages/coding-agent/src/research-plan/ledger.ts` and provides:\n\n- TypeScript interfaces for research plan items, evidence entries, and final verdicts.\n- Validators for product-facing plan/evidence objects.\n- A deterministic verifier helper that rejects plausible claims when counterexample/source-conflict/drop-condition evidence applies.\n- Regression tests in `packages/coding-agent/test/research-plan-ledger.test.ts`.\n\nFuture runtime integration can make `/skill:ralplan` emit these structures as a fenced JSON block or structured sidecar in the persisted ralplan artifact. The spike keeps the schema independent from private session state so it can be exposed in docs and tests safely.\n",
|
|
76
77
|
"resolve-tool-runtime.md": "# Resolve tool runtime internals\n\nThis document explains how preview/apply workflows are modeled in coding-agent and how built-in or custom tools can participate via the tool-choice queue and `pushPendingAction`.\n\n## Scope and key files\n\n- [`src/tools/resolve.ts`](../packages/coding-agent/src/tools/resolve.ts)\n- [`src/tools/ast-edit.ts`](../packages/coding-agent/src/tools/ast-edit.ts)\n- [`src/extensibility/custom-tools/types.ts`](../packages/coding-agent/src/extensibility/custom-tools/types.ts)\n- [`src/extensibility/custom-tools/loader.ts`](../packages/coding-agent/src/extensibility/custom-tools/loader.ts)\n- [`src/sdk.ts`](../packages/coding-agent/src/sdk.ts)\n\n## What `resolve` does\n\n`resolve` is a hidden tool that finalizes a pending preview action.\n\n- `action: \"apply\"` executes the queued action's `apply(reason)` callback and returns that result with resolve metadata.\n- `action: \"discard\"` invokes `reject(reason)` if provided; otherwise returns `Discarded: <label>. Reason: <reason>`.\n\nIf no pending action exists, `resolve` fails with:\n\n- `No pending action to resolve. Nothing to apply or discard.`\n\n## Pending actions use the tool-choice queue\n\nPreview producers call `queueResolveHandler(...)`, which pushes a one-shot forced `resolve` directive onto the session tool-choice queue and adds a `resolve-reminder` steering message.\n\nRuntime behavior:\n\n- the queued handler owns the pending `apply`/`reject` callbacks,\n- `resolve` looks up the current queue invoker with `session.peekQueueInvoker()`,\n- if the model rejects the forced tool choice, the queue directive is requeued,\n- `resolve` does not maintain a separate pending-action stack.\n\nMultiple pending previews therefore follow the active tool-choice queue ordering, not an independent pending-action store.\n\n## Built-in producer example (`ast_edit`)\n\n`ast_edit` previews structural replacements first. When the preview has replacements and is not applied yet, it queues a resolve handler that contains:\n\n- label (human-readable summary)\n- `sourceToolName` (`ast_edit`)\n- `apply(reason: string)` callback that reruns AST edit with `dryRun: false`\n\n`resolve(action=\"apply\", reason=\"...\")` passes `reason` into this callback.\n\n## Custom tools: `pushPendingAction`\n\nCustom tools can register resolve-compatible pending actions through `CustomToolAPI.pushPendingAction(...)`. The custom tool loader forwards these actions to `queueResolveHandler(...)` when that hook is available.\n\n`CustomToolPendingAction`:\n\n- `label: string` (required)\n- `apply(reason: string): Promise<AgentToolResult<unknown>>` (required) — invoked on apply; `reason` is the string passed to `resolve`\n- `reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>` (optional) — invoked on discard; return value replaces the default \"Discarded\" message if provided\n- `details?: unknown` exists on the public custom-tool type but is not currently forwarded by the loader into resolve metadata\n- `sourceToolName?: string` (optional, defaults to `\"custom_tool\"`)\n\n### Minimal usage example\n\n```ts\nimport type { CustomToolFactory } from \"@sayknow-cli/coding-agent\";\n\nconst factory: CustomToolFactory = (pi) => ({\n name: \"batch_rename_preview\",\n label: \"Batch Rename Preview\",\n description: \"Previews renames and defers commit to resolve\",\n parameters: pi.zod.object({\n files: pi.zod.array(pi.zod.string()),\n }),\n\n async execute(_toolCallId, params) {\n const previewSummary = `Prepared rename plan for ${params.files.length} files`;\n\n pi.pushPendingAction({\n label: `Batch rename: ${params.files.length} files`,\n sourceToolName: \"batch_rename_preview\",\n apply: async (reason) => {\n // apply writes here\n return {\n content: [\n { type: \"text\", text: `Applied batch rename. Reason: ${reason}` },\n ],\n };\n },\n reject: async (reason) => {\n // optional: cleanup or notify on discard\n return {\n content: [\n { type: \"text\", text: `Discarded batch rename. Reason: ${reason}` },\n ],\n };\n },\n });\n\n return {\n content: [\n {\n type: \"text\",\n text: `${previewSummary}. Call resolve to apply or discard.`,\n },\n ],\n };\n },\n});\n\nexport default factory;\n```\n\n## Runtime availability and failures\n\n`pushPendingAction` is wired by the custom tool loader through the active session's resolve queue hook.\n\nIf the runtime did not provide the resolve queue hook, `pushPendingAction` throws:\n\n- `Pending action store unavailable for custom tools in this runtime.`\n\n## Tool-choice behavior\n\nWhen `queueResolveHandler(...)` registers a preview, the agent runtime forces a one-shot `resolve` tool choice so pending previews are explicitly finalized before normal tool flow continues.\n\n## Developer guidance\n\n- Use pending actions only for destructive or high-impact operations that should support explicit apply/discard.\n- Keep `label` concise and specific; it is shown in resolve renderer output.\n- Ensure `apply(reason)` is deterministic and idempotent enough for one-shot execution; `reason` is informational and should not change behavior.\n- Implement `reject(reason)` when the discard needs cleanup (temp state, locks, notifications); omit it for stateless previews where the default message suffices.\n- If your tool can stage multiple previews, remember they are mediated by the tool-choice queue rather than a separate pending-action stack.\n",
|
|
77
|
-
"rpc.md": "# RPC Protocol Reference\n\nRPC mode runs the coding agent as a newline-delimited JSON protocol over stdio.\n\n- **stdin**: commands (`RpcCommand`), `workflow_gate_response`, extension UI responses, and host-tool updates/results\n- **stdout**: a ready frame, command responses (`RpcResponse`), session/agent events, `workflow_gate`, extension UI requests, host-tool requests/cancellations\n\nPrimary implementation:\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## Startup\n\n```bash\nskc --mode rpc [regular CLI options]\n```\n\nBehavior notes:\n\n- `@file` CLI arguments are rejected in RPC mode.\n- RPC mode disables automatic session title generation by default to avoid an extra model call.\n- RPC mode resets workflow-altering `todo.*`, `task.*`, `async.*`, and `bash.autoBackground.*` settings to their built-in defaults instead of inheriting user overrides.\n- The process reads stdin as JSONL (`readJsonl(Bun.stdin.stream())`).\n- At startup it writes `{ \"type\": \"ready\" }` before processing commands.\n- When stdin closes, pending host-tool calls are rejected and the process exits with code `0`.\n- Responses/events are written as one JSON object per line.\n\n## Transport and Framing\n\nEach frame is a single JSON object followed by `\\n`.\n\nAgent session events are wrapped in canonical `event` frames. Ready, response, workflow gate, extension UI/error, host tool, and host URI frames remain flat.\n\n### Outbound frame categories (stdout)\n\n1. Ready frame (`{ type: \"ready\" }`)\n2. `RpcResponse` (`{ type: \"response\", ... }`)\n3. Canonical event frames wrapping `AgentSessionEvent` objects (`{ type: \"event\", ... }`)\n4. `RpcWorkflowGateEvent` (`{ type: \"workflow_gate\", ... }`)\n5. `RpcExtensionUIRequest` (`{ type: \"extension_ui_request\", ... }`)\n6. Host tool requests/cancellations (`host_tool_call`, `host_tool_cancel`)\n7. Host URI requests/cancellations (`host_uri_request`, `host_uri_cancel`)\n8. Extension errors (`{ type: \"extension_error\", extensionPath, event, error }`)\n\n### Inbound frame categories (stdin)\n\n1. `RpcCommand`\n2. `RpcWorkflowGateResponse` (`{ type: \"workflow_gate_response\", gate_id, answer }`)\n3. `RpcExtensionUIResponse` (`{ type: \"extension_ui_response\", ... }`)\n4. Host tool updates/results (`host_tool_update`, `host_tool_result`)\n5. Host URI results (`host_uri_result`)\n\n## Request/Response Correlation\n\nAll commands accept optional `id?: string`.\n\n- If provided, normal command responses echo the same `id`.\n- `RpcClient` relies on this for pending-request resolution.\n\nImportant edge behavior from runtime:\n\n- Unknown command responses are emitted with `id: undefined` (even if the request had an `id`).\n- Parse/handler exceptions in the input loop emit `command: \"parse\"` with `id: undefined`.\n- `prompt` and `abort_and_prompt` return immediate success, then may emit a later error response with the **same** id if async prompt scheduling fails.\n\n## Command Schema (canonical)\n\n`RpcCommand` is defined in `src/modes/rpc/rpc-types.ts`:\n\n### Prompting\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### State\n\n- `{ id?, type: \"get_state\", include?: (\"tools\" | \"dumpTools\" | \"systemPrompt\")[] }` (`dumpTools` is accepted as an alias for the older response field name.)\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n- `{ id?, type: \"set_host_uri_schemes\", schemes: RpcHostUriSchemeDefinition[] }`\n- `{ id?, type: \"workflow_gate_response\", gate_id: string, answer: unknown }`\n\n### Model\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### Thinking\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### Queue modes\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### Compaction\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### Retry\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### Session\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### Messages\n\n- `{ id?, type: \"get_messages\" }`\n\n## Response Schema\n\nAll command results use `RpcResponse`:\n\n- Success: `{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- Failure: `{ id?, type: \"response\", command: string, success: false, error: string | object }`; typed control-plane failures use object-valued errors such as `{ \"code\": \"scope_denied\", ... }`.\n\nData payloads are command-specific and defined in `rpc-types.ts`.\n\n\nBy default, `get_state` omits large static fields. Request `include: [\"tools\"]` to include `dumpTools`, `include: [\"systemPrompt\"]` to include `systemPrompt`, or both when a host needs a one-shot full session dump.\n### `get_state` payload\n\n```json\n{\n \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n \"isStreaming\": false,\n \"isCompacting\": false,\n \"steeringMode\": \"all|one-at-a-time\",\n \"followUpMode\": \"all|one-at-a-time\",\n \"interruptMode\": \"immediate|wait\",\n \"sessionFile\": \"...\",\n \"sessionId\": \"...\",\n \"sessionName\": \"...\",\n \"autoCompactionEnabled\": true,\n \"messageCount\": 0,\n \"queuedMessageCount\": 0,\n \"todoPhases\": [\n {\n \"id\": \"phase-1\",\n \"name\": \"Todos\",\n \"tasks\": [\n {\n \"id\": \"task-1\",\n \"content\": \"Map the tool surface\",\n \"status\": \"in_progress\"\n }\n ]\n }\n ],\n \"contextUsage\": {\n \"tokens\": 0,\n \"contextWindow\": 200000,\n \"percent\": 0\n }\n // Optional with include: [\"systemPrompt\"]:\n // \"systemPrompt\": [\"...\"],\n // Optional with include: [\"tools\"] (or [\"dumpTools\"]):\n // \"dumpTools\": [\n // { \"name\": \"read\", \"description\": \"Read files and URLs\", \"parameters\": {} }\n // ]\n}\n```\n\n### `set_todos` payload\n\nReplaces the in-memory todo state for the current session and returns the normalized phase list:\n\n```json\n{\n \"id\": \"req_2\",\n \"type\": \"set_todos\",\n \"phases\": [\n {\n \"id\": \"phase-1\",\n \"name\": \"Evaluation\",\n \"tasks\": [\n {\n \"id\": \"task-1\",\n \"content\": \"Map the read tool surface\",\n \"status\": \"in_progress\"\n },\n {\n \"id\": \"task-2\",\n \"content\": \"Exercise edit operations\",\n \"status\": \"pending\"\n }\n ]\n }\n ]\n}\n```\n\nThis is useful for hosts that want to pre-seed a plan before the first prompt.\n\n### `set_host_tools` payload\n\nReplaces the current set of host-owned tools that the RPC server may call back\ninto over stdio:\n\n```json\n{\n \"id\": \"req_3\",\n \"type\": \"set_host_tools\",\n \"tools\": [\n {\n \"name\": \"echo_host\",\n \"label\": \"Echo Host\",\n \"description\": \"Echo a value from the embedding host\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"message\": { \"type\": \"string\" }\n },\n \"required\": [\"message\"],\n \"additionalProperties\": false\n }\n }\n ]\n}\n```\n\nThe response payload is:\n\n```json\n{\n \"toolNames\": [\"echo_host\"]\n}\n```\n\nThese tools are added to the active session tool registry before the next model\ncall. Re-sending `set_host_tools` replaces the previous host-owned set.\n\n### `set_host_uri_schemes` payload\n\nReplaces the current set of host-owned URL schemes the RPC server should\ndispatch reads/writes through:\n\n```json\n{\n \"id\": \"req_4\",\n \"type\": \"set_host_uri_schemes\",\n \"schemes\": [\n {\n \"scheme\": \"db\",\n \"description\": \"Virtual db row files\",\n \"writable\": true,\n \"immutable\": false\n }\n ]\n}\n```\n\nThe response payload is:\n\n```json\n{\n \"schemes\": [\"db\"]\n}\n```\n\nSchemes are case-insensitive on the wire and normalized to lowercase before\nthe response is sent. Re-sending `set_host_uri_schemes` replaces the entire\nprevious set — schemes missing from the new list are unregistered.\n\n## Event Stream Schema\n\nRPC mode forwards `AgentSessionEvent` objects from `AgentSession.subscribe(...)` as canonical `event` frames:\n\n```json\n{\n \"type\": \"event\",\n \"protocol_version\": 2,\n \"session_id\": \"...\",\n \"seq\": 1,\n \"frame_id\": \"...\",\n \"payload\": {\n \"event_type\": \"agent_start\",\n \"event\": { \"type\": \"agent_start\" }\n }\n}\n```\n\n`seq` is monotonic per session starting at `1`. `payload.event_type` duplicates the inner event `type` for routing, and `payload.event` contains the original `AgentSessionEvent` fields.\n\nCommon inner event types:\n\n- `agent_start`, `agent_end`\n- `turn_start`, `turn_end`\n- `message_start`, `message_update`, `message_end`\n- `tool_execution_start`, `tool_execution_update`, `tool_execution_end`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\nNon-event stdout categories remain flat: `ready`, `response`, `workflow_gate`, `extension_ui_request`, `extension_error`, `host_tool_call`, `host_tool_cancel`, `host_uri_request`, and `host_uri_cancel`.\n\n`message_update` includes streaming deltas in the inner event's `assistantMessageEvent` (text/thinking/toolcall deltas).\n\nExtension runner errors are emitted separately as flat frames:\n\n```json\n{\n \"type\": \"extension_error\",\n \"extensionPath\": \"...\",\n \"event\": \"...\",\n \"error\": \"...\"\n}\n```\n\n## Prompt/Queue Concurrency and Ordering\n\nThis is the most important operational behavior.\n\n### Immediate ack vs completion\n\n`prompt` and `abort_and_prompt` are **acknowledged immediately**:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\nThat means:\n\n- command acceptance != run completion\n- final completion is observed via `agent_end`\n\n### While streaming\n\n`AgentSession.prompt()` requires `streamingBehavior` during active streaming:\n\n- `\"steer\"` => queued steering message (interrupt path)\n- `\"followUp\"` => queued follow-up message (post-turn path)\n\nIf omitted during streaming, prompt fails.\n\n### Queue defaults\n\nFrom `packages/agent/src/agent.ts` defaults:\n\n- `steeringMode`: `\"one-at-a-time\"`\n- `followUpMode`: `\"one-at-a-time\"`\n- `interruptMode`: `\"immediate\"`\n\n### Mode semantics\n\n- `set_steering_mode` / `set_follow_up_mode`\n - `\"one-at-a-time\"`: dequeue one queued message per turn\n - `\"all\"`: dequeue entire queue at once\n- `set_interrupt_mode`\n - `\"immediate\"`: tool execution checks steering between tool calls; pending steering can abort remaining tool calls in the turn\n - `\"wait\"`: defer steering until turn completion\n\n## Workflow Gate Sub-Protocol\n\nInteractive workflow stages emit a machine-addressable gate frame before the legacy extension UI request:\n\n```json\n{\n \"type\": \"workflow_gate\",\n \"gate_id\": \"wg_4845_ralplan_000001\",\n \"stage\": \"ralplan\",\n \"kind\": \"approval\",\n \"schema\": { \"type\": \"string\", \"enum\": [\"approve\", \"request-changes\", \"reject\"] },\n \"schema_hash\": \"<sha256 of canonical schema>\",\n \"options\": [{ \"value\": \"approve\", \"label\": \"Approve execution\" }],\n \"context\": { \"title\": \"Approve plan?\", \"summary\": \"…\" },\n \"created_at\": \"2026-06-05T05:00:00.000Z\",\n \"required\": true\n}\n```\n\nFields:\n\n- `gate_id`: run-scoped, monotonic, stable id of the form `wg_<run>_<stage>_NNNNNN`.\n- `stage`: one of `\"deep-interview\"`, `\"ralplan\"`, or `\"ultragoal\"` (`team` is reserved and rejected for v1).\n- `kind`: one of `\"question\"`, `\"approval\"`, or `\"execution\"`.\n- `schema`: documented JSON Schema 2020-12 subset for the expected answer; `schema_hash` is the canonical hash of `schema` and equals the server-side validation hash.\n- `options`: optional `RpcWorkflowGateOption[]` (`{ value, label, description? }`), emitted for select-style gates.\n- `context`: `RpcWorkflowGateContext` (`title`, `prompt`, `summary`, `stage_state`, `artifact_refs`, `language`).\n- `created_at`: ISO timestamp the gate was opened; `required` is always `true`.\n\nHosts answer with:\n\n```json\n{ \"id\": \"resp_1\", \"type\": \"workflow_gate_response\", \"gate_id\": \"wg_4845_ralplan_000001\", \"answer\": \"approve\" }\n```\n\nA valid answer resolves the pending gate and returns:\n\n```json\n{ \"id\": \"resp_1\", \"type\": \"response\", \"command\": \"workflow_gate_response\", \"success\": true }\n```\n\nA schema mismatch is **not** a command failure: the response succeeds and the\nresolution data carries `status: \"rejected\"` plus a typed validation `error`\nwith code `invalid_workflow_gate_answer`:\n\n```json\n{\n \"id\": \"resp_1\",\n \"type\": \"response\",\n \"command\": \"workflow_gate_response\",\n \"success\": true,\n \"data\": {\n \"gate_id\": \"wg_1\",\n \"status\": \"rejected\",\n \"answer_hash\": \"…\",\n \"resolved_at\": \"…\",\n \"error\": {\n \"code\": \"invalid_workflow_gate_answer\",\n \"gate_id\": \"wg_1\",\n \"schema_hash\": \"…\",\n \"errors\": [{ \"path\": \"/answer\", \"keyword\": \"type\", \"message\": \"must be boolean\" }]\n }\n }\n}\n```\n\nAnswering a gate that does not exist is a recoverable command failure carrying\nthe broker error code `unknown_gate` (other broker codes are `already_resolved`,\n`idempotency_conflict`, and `invalid_workflow_stage`).\n## Extension UI Sub-Protocol\n\nExtensions in RPC mode use request/response UI frames.\n\n### Outbound request\n\n`RpcExtensionUIRequest` (`type: \"extension_ui_request\"`) methods:\n\n- `select`, `confirm`, `input`, `editor`, `cancel`\n- `notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`\n\nRuntime note:\n\n- Automatic session title generation is disabled in RPC mode, and `setTitle` UI\n requests are also suppressed by default because most hosts do not have a\n meaningful terminal-title surface. Set `SKC_RPC_EMIT_TITLE=1` to opt back in to\n the UI event only.\n\nExample:\n\n```json\n{\n \"type\": \"extension_ui_request\",\n \"id\": \"123\",\n \"method\": \"confirm\",\n \"title\": \"Confirm\",\n \"message\": \"Continue?\",\n \"timeout\": 30000\n}\n```\n\n### Inbound response\n\n`RpcExtensionUIResponse` (`type: \"extension_ui_response\"`):\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true, timedOut?: boolean }`\n\nIf a dialog has a timeout, RPC mode resolves to a default value when timeout/abort fires.\n\n## Host Tool Sub-Protocol\n\nRPC hosts can expose custom tools to the agent by sending `set_host_tools`, then\nserving execution requests over the same transport.\n\n### Outbound request\n\nWhen the agent wants the host to execute one of those tools, RPC mode emits:\n\n```json\n{\n \"type\": \"host_tool_call\",\n \"id\": \"host_1\",\n \"toolCallId\": \"toolu_123\",\n \"toolName\": \"echo_host\",\n \"arguments\": { \"message\": \"hello\" }\n}\n```\n\nIf the tool execution is later aborted, RPC mode emits:\n\n```json\n{\n \"type\": \"host_tool_cancel\",\n \"id\": \"host_cancel_1\",\n \"targetId\": \"host_1\"\n}\n```\n\n### Inbound updates and completion\n\nHosts can optionally stream progress:\n\n```json\n{\n \"type\": \"host_tool_update\",\n \"id\": \"host_1\",\n \"partialResult\": {\n \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n }\n}\n```\n\nCompletion uses:\n\n```json\n{\n \"type\": \"host_tool_result\",\n \"id\": \"host_1\",\n \"result\": {\n \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n }\n}\n```\n\nSet top-level `isError: true` on `host_tool_result` to reject the pending host tool call and surface the returned text content as a tool error.\n\n## Host URI Sub-Protocol\n\nRPC hosts can also own custom URL schemes (virtual files). After\n`set_host_uri_schemes`, every read of `<scheme>://…` and write of\n`<scheme>://…` (when registered as `writable`) is bounced back to the host\nover the same transport.\n\n### Outbound request\n\nWhen a session tool resolves a host-owned URL, RPC mode emits:\n\n```json\n{\n \"type\": \"host_uri_request\",\n \"id\": \"uri_1\",\n \"operation\": \"read\",\n \"url\": \"db://users/42\"\n}\n```\n\nWrites look the same with `\"operation\": \"write\"` and an additional\n`\"content\": \"...\"` field carrying the full replacement bytes.\n\nIf the request is later aborted (caller cancels, session ends), RPC mode\nemits:\n\n```json\n{\n \"type\": \"host_uri_cancel\",\n \"id\": \"uri_cancel_1\",\n \"targetId\": \"uri_1\"\n}\n```\n\n### Inbound result\n\nFor successful reads:\n\n```json\n{\n \"type\": \"host_uri_result\",\n \"id\": \"uri_1\",\n \"content\": \"id=42\\nname=Alice\\n\",\n \"contentType\": \"text/plain\",\n \"notes\": [\"fresh from cache\"],\n \"immutable\": false\n}\n```\n\nFor successful writes, omit content:\n\n```json\n{ \"type\": \"host_uri_result\", \"id\": \"uri_1\" }\n```\n\nTo reject the request, set `isError: true` and either populate `error` with\na message or fall back to `content` for textual error surfacing:\n\n```json\n{\n \"type\": \"host_uri_result\",\n \"id\": \"uri_1\",\n \"isError\": true,\n \"error\": \"row 42 not found\"\n}\n```\n\n### Constraints\n\n- The agent's `edit` tool does not target host URIs. Hosts that want to\n mutate virtual files expose `write` and let the model use the `write` tool\n with replacement content.\n- Schemes are global to the process; `set_host_uri_schemes` replaces the\n previous set, unregistering anything not in the new list.\n- Schemes are normalized to lowercase before registration.\n\n## Error Model and Recoverability\n\n### Command-level failures\n\nFailures are `success: false` with string `error`.\n\n```json\n{\n \"id\": \"req_2\",\n \"type\": \"response\",\n \"command\": \"set_model\",\n \"success\": false,\n \"error\": \"Model not found: provider/model\"\n}\n```\n\n### Recoverability expectations\n\n- Most command failures are recoverable; process remains alive.\n- Malformed JSONL / parse-loop exceptions emit a `parse` error response and continue reading subsequent lines.\n- Empty `set_session_name` is rejected (`Session name cannot be empty`).\n- Extension UI responses with unknown `id` are ignored.\n- Process termination conditions are stdin close or explicit extension-triggered shutdown after the current command.\n\n## Compact Command Flows\n\n### 1) Prompt and stream\n\nstdin:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nstdout sequence (typical):\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) Prompt during streaming with explicit queue policy\n\nstdin:\n\n```json\n{\n \"id\": \"req_2\",\n \"type\": \"prompt\",\n \"message\": \"Also include risks\",\n \"streamingBehavior\": \"followUp\"\n}\n```\n\n### 3) Inspect and tune queue behavior\n\nstdin:\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) Extension UI round trip\n\nstdout:\n\n```json\n{\n \"type\": \"extension_ui_request\",\n \"id\": \"ui_7\",\n \"method\": \"input\",\n \"title\": \"Branch name\",\n \"placeholder\": \"feature/...\"\n}\n```\n\nstdin:\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## OpenClaw / Hermes host integrations\n\nFor OpenClaw- or Hermes-style hosts, keep MCP servers and skills on the host side and expose the selected capabilities through RPC host tools. Do not import SKC runtime MCP internals directly; those package paths are intentionally quarantined. See [OpenClaw / Hermes RPC integration notes](./openclaw-hermes-rpc-integration.md).\n\n## Notes on `RpcClient` helper\n\n`src/modes/rpc/rpc-client.ts` is a convenience wrapper, not the protocol definition.\n\nCurrent helper characteristics:\n\n- Spawns `bun <cliPath> --mode rpc`\n- Correlates responses by generated `req_<n>` ids\n- Dispatches recognized `AgentEvent` types to event listeners\n- Dispatches top-level `workflow_gate` frames to `onWorkflowGate()` listeners\n- Supports host-owned custom tools via `setCustomTools()` and automatic handling of `host_tool_call` / `host_tool_cancel`\n- Exposes `respondGate()` for `workflow_gate_response` and waits for the accepted/rejected resolution envelope\n- Does **not** expose helper methods for every protocol command (for example, `set_interrupt_mode` and `set_session_name` are in protocol types but not wrapped as dedicated methods)\n\nUse raw protocol frames if you need complete surface coverage.\n\n## Workflow gates (agent-driven lifecycle)\n\nThe workflow-gate contract makes every human-gated lifecycle moment\n(deep-interview questions, ralplan approval, ultragoal execution sign-off)\nmachine-addressable so an external agent can answer it over RPC without\nscreen-scraping.\n\n### Outbound event: `workflow_gate`\n\n```json\n{\n \"type\": \"workflow_gate\",\n \"gate_id\": \"wg_4845_ralplan_000001\",\n \"stage\": \"ralplan\",\n \"kind\": \"approval\",\n \"schema\": { \"type\": \"string\", \"enum\": [\"approve\", \"request-changes\", \"reject\"] },\n \"schema_hash\": \"<sha256 of canonical schema>\",\n \"options\": [{ \"value\": \"approve\", \"label\": \"Approve execution\" }],\n \"context\": { \"title\": \"Approve plan?\", \"summary\": \"…\" },\n \"created_at\": \"2026-06-05T05:00:00.000Z\",\n \"required\": true\n}\n```\n\n- `gate_id` is **run-scoped and monotonic**: `wg_<run>_<stage>_<NNNNNN>`.\n- `stage` is one of `deep-interview` | `ralplan` | `ultragoal`. `team` is\n reserved and rejected for v1 (single-agent only).\n- `kind` is `question` | `approval` | `execution`.\n- `schema` is a **documented subset of JSON Schema 2020-12**. Supported keywords:\n `type`, `enum`, `const`, `properties`, `required`, `additionalProperties`,\n `items`, `minLength`, `maxLength`, `minimum`, `maximum`, `title`,\n `description`, `oneOf`, `anyOf`. Any other keyword is rejected at gate\n construction (`invalid_workflow_gate_schema`) so the server never advertises a\n schema it will not validate. `schema_hash` equals the server-side validation\n hash for that gate.\n\n### Inbound command: `workflow_gate_response`\n\n```json\n{ \"type\": \"workflow_gate_response\", \"gate_id\": \"wg_4845_ralplan_000001\", \"answer\": \"approve\", \"idempotency_key\": \"k1\" }\n```\n\nThe answer is validated against the advertised schema **before acceptance**:\n\n- Valid → resolution persisted before the workflow advances; response:\n `{ \"type\": \"response\", \"command\": \"workflow_gate_response\", \"success\": true, \"data\": { \"gate_id\": \"…\", \"status\": \"accepted\", \"answer_hash\": \"…\", \"resolved_at\": \"…\" } }`.\n- Invalid → the gate stays **pending** and the resolution carries a typed\n `invalid_workflow_gate_answer` error listing each `{ path, keyword, message, expected? }`.\n- Idempotency: replaying the same `idempotency_key` + identical body returns the\n cached resolution; the same key with a different body is an\n `idempotency_conflict`; answering an already-accepted gate is `already_resolved`.\n- Client helpers wait for this accepted/rejected resolution envelope; they must not treat the write of `workflow_gate_response` itself as completion.\n\n### Entering unattended mode: `negotiate_unattended`\n\nUnattended (zero-human) operation is **fail-closed**. The external agent must\ndeclare its budget, scopes, and action allowlist up front:\n\n```json\n{\n \"type\": \"negotiate_unattended\",\n \"declaration\": {\n \"actor\": \"openclaw/hermes\",\n \"budget\": { \"max_tokens\": 2000000, \"max_tool_calls\": 5000, \"max_wall_time_ms\": 3600000, \"max_cost_usd\": 20 },\n \"scopes\": [\"prompt\", \"control\", \"bash\"],\n \"action_allowlist\": [\"bash.readonly\", \"file.write\"]\n }\n}\n```\n\nA missing or partial declaration refuses unattended mode. Budget, scope, and\naudit enforcement are layered on this contract by the unattended control plane\n(see issues #318/#319/#320). Attended mode is unaffected: clients that never send\n`negotiate_unattended` keep the existing extension-UI / permission behavior.\n\n\n> **Status (live, #315/#318/#321):** the `workflow_gate` /\n> `workflow_gate_response` / `negotiate_unattended` frames, the answer-schema\n> validator, and the durable gate broker are defined, tested, and wired into\n> live session dispatch. When an unattended control plane is attached to the\n> session, `dispatchRpcCommand` routes `negotiate_unattended` and\n> `workflow_gate_response` through it (see\n> `packages/coding-agent/src/modes/shared/agent-wire/command-dispatch.ts`); a\n> session without that control plane returns a typed \"not available\" error for\n> these frames rather than silently dropping them.\n\n\n### Answering gates from a client (#322)\n\nBoth clients expose typed `workflow_gate` receive + respond helpers so an agent\ncan answer a gate from its own memory via a callback.\n\nFor bridge sessions, gate responses are **not** posted through `/commands`. The\nclient must first own the UI/control plane, then post the answer body to\n`POST /v1/sessions/{session_id}/ui-responses/{gate_id}` with\n`X-SKC-Bridge-Owner-Token: <ownerToken>`. `Idempotency-Key` may be supplied as a\nheader and the same value is also accepted in the JSON body as `idempotency_key`.\n\n`@sayknow-cli/bridge-client` (TypeScript):\n\n```ts\nimport { BridgeClient } from \"@sayknow-cli/bridge-client\";\n\nconst client = new BridgeClient({ baseUrl, token });\n// Headless policy: every received gate is routed to the resolver and answered.\nfor await (const { gate, answer } of client.consumeWorkflowGates(sessionId, ownerToken, gate => {\n if (gate.kind === \"approval\") return { decision: \"approve\" };\n if (gate.kind === \"question\") return { selected: [gate.options?.[0]?.value], other: false };\n return { decision: \"approve\" };\n})) {\n console.log(`answered ${gate.gate_id} (${gate.kind}) with`, answer);\n}\n// Or answer a single gate directly:\nawait client.respondGate(sessionId, gateId, ownerToken, { decision: \"approve\" });\n```\n\n`python/skc-rpc` (Python):\n\n```python\nfrom skc_rpc import RpcClient, WorkflowGate\n\nclient = RpcClient(executable=\"skc\")\n\ndef resolver(gate: WorkflowGate) -> object:\n if gate.kind == \"approval\":\n return {\"decision\": \"approve\"}\n if gate.kind == \"question\":\n return {\"selected\": [gate.options[0].value] if gate.options else [], \"other\": False}\n return {\"decision\": \"approve\"}\n\n# Headless policy: route every received gate to the resolver and respond.\nclient.run_workflow_gate_policy(resolver)\nclient.start()\n# Or answer a single gate directly: client.respond_gate(gate_id, {\"decision\": \"approve\"})\n```\n",
|
|
78
|
+
"rpc.md": "# RPC Protocol Reference\n\nRPC mode runs the coding agent as a newline-delimited JSON protocol over stdio.\n\n- **stdin**: commands (`RpcCommand`), `workflow_gate_response`, extension UI responses, and host-tool updates/results\n- **stdout**: a ready frame, command responses (`RpcResponse`), session/agent events, `workflow_gate`, extension UI requests, host-tool requests/cancellations\n\nPrimary implementation:\n\n- `src/modes/rpc/rpc-mode.ts`\n- `src/modes/rpc/rpc-types.ts`\n- `src/session/agent-session.ts`\n- `packages/agent/src/agent.ts`\n- `packages/agent/src/agent-loop.ts`\n\n## Startup\n\n```bash\nskc --mode rpc [regular CLI options]\n```\n\nBehavior notes:\n\n- `@file` CLI arguments are rejected in RPC mode.\n- RPC mode disables automatic session title generation by default to avoid an extra model call.\n- RPC mode resets workflow-altering `todo.*`, `task.*`, `async.*`, and `bash.autoBackground.*` settings to their built-in defaults instead of inheriting user overrides.\n- The process reads stdin as JSONL (`readJsonl(Bun.stdin.stream())`).\n- At startup it writes `{ \"type\": \"ready\" }` before processing commands.\n- When stdin closes, pending host-tool calls are rejected and the process exits with code `0`.\n- Responses/events are written as one JSON object per line.\n\n## Transport and Framing\n\nEach frame is a single JSON object followed by `\\n`.\n\nAgent session events are wrapped in canonical `event` frames. Ready, response, workflow gate, extension UI/error, host tool, and host URI frames remain flat.\n\n### Outbound frame categories (stdout)\n\n1. Ready frame (`{ type: \"ready\" }`)\n2. `RpcResponse` (`{ type: \"response\", ... }`)\n3. Canonical event frames wrapping `AgentSessionEvent` objects (`{ type: \"event\", ... }`)\n4. `RpcWorkflowGateEvent` (`{ type: \"workflow_gate\", ... }`)\n5. `RpcExtensionUIRequest` (`{ type: \"extension_ui_request\", ... }`)\n6. Host tool requests/cancellations (`host_tool_call`, `host_tool_cancel`)\n7. Host URI requests/cancellations (`host_uri_request`, `host_uri_cancel`)\n8. Extension errors (`{ type: \"extension_error\", extensionPath, event, error }`)\n\n### Inbound frame categories (stdin)\n\n1. `RpcCommand`\n2. `RpcWorkflowGateResponse` (`{ type: \"workflow_gate_response\", gate_id, answer }`)\n3. `RpcExtensionUIResponse` (`{ type: \"extension_ui_response\", ... }`)\n4. Host tool updates/results (`host_tool_update`, `host_tool_result`)\n5. Host URI results (`host_uri_result`)\n\n## Request/Response Correlation\n\nAll commands accept optional `id?: string`.\n\n- If provided, normal command responses echo the same `id`.\n- `RpcClient` relies on this for pending-request resolution.\n\nImportant edge behavior from runtime:\n\n- Unknown command responses are emitted with `id: undefined` (even if the request had an `id`).\n- Parse/handler exceptions in the input loop emit `command: \"parse\"` with `id: undefined`.\n- `prompt` and `abort_and_prompt` return immediate success, then may emit a later error response with the **same** id if async prompt scheduling fails.\n\n## Command Schema (canonical)\n\n`RpcCommand` is defined in `src/modes/rpc/rpc-types.ts`:\n\n### Prompting\n\n- `{ id?, type: \"prompt\", message: string, images?: ImageContent[], streamingBehavior?: \"steer\" | \"followUp\" }`\n- `{ id?, type: \"steer\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"follow_up\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"abort\" }`\n- `{ id?, type: \"abort_and_prompt\", message: string, images?: ImageContent[] }`\n- `{ id?, type: \"new_session\", parentSession?: string }`\n\n### State\n\n- `{ id?, type: \"get_state\", include?: (\"tools\" | \"dumpTools\" | \"systemPrompt\")[] }` (`dumpTools` is accepted as an alias for the older response field name.)\n- `{ id?, type: \"set_todos\", phases: TodoPhase[] }`\n- `{ id?, type: \"set_host_tools\", tools: RpcHostToolDefinition[] }`\n- `{ id?, type: \"set_host_uri_schemes\", schemes: RpcHostUriSchemeDefinition[] }`\n- `{ id?, type: \"workflow_gate_response\", gate_id: string, answer: unknown }`\n\n### Model\n\n- `{ id?, type: \"set_model\", provider: string, modelId: string }`\n- `{ id?, type: \"set_default_model_selection\", provider: string, modelId: string, thinkingLevel?: \"off\" | \"minimal\" | \"low\" | \"medium\" | \"high\" | \"xhigh\" | \"max\" }`\n- `{ id?, type: \"cycle_model\" }`\n- `{ id?, type: \"get_available_models\" }`\n\n### Thinking\n\n- `{ id?, type: \"set_thinking_level\", level: ThinkingLevel }`\n- `{ id?, type: \"cycle_thinking_level\" }`\n\n### Queue modes\n\n- `{ id?, type: \"set_steering_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_follow_up_mode\", mode: \"all\" | \"one-at-a-time\" }`\n- `{ id?, type: \"set_interrupt_mode\", mode: \"immediate\" | \"wait\" }`\n\n### Compaction\n\n- `{ id?, type: \"compact\", customInstructions?: string }`\n- `{ id?, type: \"set_auto_compaction\", enabled: boolean }`\n\n### Retry\n\n- `{ id?, type: \"set_auto_retry\", enabled: boolean }`\n- `{ id?, type: \"abort_retry\" }`\n\n### Bash\n\n- `{ id?, type: \"bash\", command: string }`\n- `{ id?, type: \"abort_bash\" }`\n\n### Session\n\n- `{ id?, type: \"get_session_stats\" }`\n- `{ id?, type: \"export_html\", outputPath?: string }`\n- `{ id?, type: \"switch_session\", sessionPath: string }`\n- `{ id?, type: \"branch\", entryId: string }`\n- `{ id?, type: \"get_branch_messages\" }`\n- `{ id?, type: \"get_last_assistant_text\" }`\n- `{ id?, type: \"set_session_name\", name: string }`\n\n### Messages\n\n- `{ id?, type: \"get_messages\" }`\n\n## Response Schema\n\nAll command results use `RpcResponse`:\n\n- Success: `{ id?, type: \"response\", command: <command>, success: true, data?: ... }`\n- Failure: `{ id?, type: \"response\", command: string, success: false, error: string | object }`; typed control-plane failures use object-valued errors such as `{ \"code\": \"scope_denied\", ... }`.\n\nData payloads are command-specific and defined in `rpc-types.ts`.\n\n### `set_default_model_selection` contract\n\nRequest:\n\n```json\n{ \"id\": \"model-default-1\", \"type\": \"set_default_model_selection\", \"provider\": \"openai\", \"modelId\": \"gpt-5.6\", \"thinkingLevel\": \"high\" }\n```\n\nCorrelated success response:\n\n```json\n{ \"id\": \"model-default-1\", \"type\": \"response\", \"command\": \"set_default_model_selection\", \"success\": true, \"data\": { \"provider\": \"openai\", \"modelId\": \"gpt-5.6\", \"thinkingLevel\": \"high\" } }\n```\n\n`thinkingLevel` may be omitted or set to a concrete level. `\"inherit\"` is rejected. A requested reasoning level is clamped to the selected model's supported range. A non-reasoning model always resolves to `off`. The response always reports the effective concrete level that was persisted and applied.\n\nThis is an ordered mutation. It waits behind earlier mutations and, if the agent is streaming, waits for an active stream to become idle. It acknowledges success only after the machine-global selector is durably written and the selected model and effective level are recorded as the active session default. The selection therefore applies to the next message; it does not change the model of a response already being streamed.\n\nThe durable setting is a fallback, not an unconditional override. Project-level model-role policy overrides the machine-global selector, and a resumed session's recorded default model overrides it. Fresh sessions without either higher-precedence source consume the machine-global selector, including its effective thinking-level suffix.\n\n`set_default_model_selection` belongs to the shared agent-wire command surface. If a Bridge command endpoint is enabled by an embedding, the command requires the `model` scope; this catalog mapping does not enable the default-disabled Bridge command endpoint.\n\nBy default, `get_state` omits large static fields. Request `include: [\"tools\"]` to include `dumpTools`, `include: [\"systemPrompt\"]` to include `systemPrompt`, or both when a host needs a one-shot full session dump.\n### `get_state` payload\n\n```json\n{\n \"model\": { \"provider\": \"...\", \"id\": \"...\" },\n \"thinkingLevel\": \"off|minimal|low|medium|high|xhigh\",\n \"isStreaming\": false,\n \"isCompacting\": false,\n \"steeringMode\": \"all|one-at-a-time\",\n \"followUpMode\": \"all|one-at-a-time\",\n \"interruptMode\": \"immediate|wait\",\n \"sessionFile\": \"...\",\n \"sessionId\": \"...\",\n \"sessionName\": \"...\",\n \"autoCompactionEnabled\": true,\n \"messageCount\": 0,\n \"queuedMessageCount\": 0,\n \"todoPhases\": [\n {\n \"id\": \"phase-1\",\n \"name\": \"Todos\",\n \"tasks\": [\n {\n \"id\": \"task-1\",\n \"content\": \"Map the tool surface\",\n \"status\": \"in_progress\"\n }\n ]\n }\n ],\n \"contextUsage\": {\n \"tokens\": 0,\n \"contextWindow\": 200000,\n \"percent\": 0,\n \"source\": \"heuristic\"\n }\n // Optional with include: [\"systemPrompt\"]:\n // \"systemPrompt\": [\"...\"],\n // Optional with include: [\"tools\"] (or [\"dumpTools\"]):\n // \"dumpTools\": [\n // { \"name\": \"read\", \"description\": \"Read files and URLs\", \"parameters\": {} }\n // ]\n}\n```\n\n| Field | Contract |\n| --- | --- |\n| `tokens` | `number \\| null`. `null` when the count is unknown, such as immediately after compaction. |\n| `contextWindow` | `number`. |\n| `percent` | `number \\| null`. `null` when the count is unknown. |\n| `source` | `\"provider_anchor\" \\| \"heuristic\" \\| \"unknown\"`. `provider_anchor` uses a provider-reported total for already-sent context plus a heuristic estimate of trailing unsent messages; `heuristic` has no provider usage anchor and estimates the full context, including fixed system/tool context; `unknown` is post-compaction, when an exact count is unavailable until the next provider response. |\n\n`contextUsage` may be absent when no model or context window is configured. `source` is additive, so older clients can ignore it.\n\n### `set_todos` payload\n\nReplaces the in-memory todo state for the current session and returns the normalized phase list:\n\n```json\n{\n \"id\": \"req_2\",\n \"type\": \"set_todos\",\n \"phases\": [\n {\n \"id\": \"phase-1\",\n \"name\": \"Evaluation\",\n \"tasks\": [\n {\n \"id\": \"task-1\",\n \"content\": \"Map the read tool surface\",\n \"status\": \"in_progress\"\n },\n {\n \"id\": \"task-2\",\n \"content\": \"Exercise edit operations\",\n \"status\": \"pending\"\n }\n ]\n }\n ]\n}\n```\n\nThis is useful for hosts that want to pre-seed a plan before the first prompt.\n\n### `set_host_tools` payload\n\nReplaces the current set of host-owned tools that the RPC server may call back\ninto over stdio:\n\n```json\n{\n \"id\": \"req_3\",\n \"type\": \"set_host_tools\",\n \"tools\": [\n {\n \"name\": \"echo_host\",\n \"label\": \"Echo Host\",\n \"description\": \"Echo a value from the embedding host\",\n \"parameters\": {\n \"type\": \"object\",\n \"properties\": {\n \"message\": { \"type\": \"string\" }\n },\n \"required\": [\"message\"],\n \"additionalProperties\": false\n }\n }\n ]\n}\n```\n\nThe response payload is:\n\n```json\n{\n \"toolNames\": [\"echo_host\"]\n}\n```\n\nThese tools are added to the active session tool registry before the next model\ncall. Re-sending `set_host_tools` replaces the previous host-owned set.\n\n### `set_host_uri_schemes` payload\n\nReplaces the current set of host-owned URL schemes the RPC server should\ndispatch reads/writes through:\n\n```json\n{\n \"id\": \"req_4\",\n \"type\": \"set_host_uri_schemes\",\n \"schemes\": [\n {\n \"scheme\": \"db\",\n \"description\": \"Virtual db row files\",\n \"writable\": true,\n \"immutable\": false\n }\n ]\n}\n```\n\nThe response payload is:\n\n```json\n{\n \"schemes\": [\"db\"]\n}\n```\n\nSchemes are case-insensitive on the wire and normalized to lowercase before\nthe response is sent. Re-sending `set_host_uri_schemes` replaces the entire\nprevious set — schemes missing from the new list are unregistered.\n\n## Event Stream Schema\n\nRPC mode forwards `AgentSessionEvent` objects from `AgentSession.subscribe(...)` as canonical `event` frames:\n\n```json\n{\n \"type\": \"event\",\n \"protocol_version\": 2,\n \"session_id\": \"...\",\n \"seq\": 1,\n \"frame_id\": \"...\",\n \"payload\": {\n \"event_type\": \"agent_start\",\n \"event\": { \"type\": \"agent_start\" }\n }\n}\n```\n\n`seq` is monotonic per session starting at `1`. `payload.event_type` duplicates the inner event `type` for routing, and `payload.event` contains the original `AgentSessionEvent` fields.\n\nCommon inner event types:\n\n- `agent_start`, `agent_end`\n- `turn_start`, `turn_end`\n- `message_start`, `message_update`, `message_end`\n- `tool_execution_start`, `tool_execution_update`, `tool_execution_end`\n- `auto_compaction_start`, `auto_compaction_end`\n- `auto_retry_start`, `auto_retry_end`\n- `ttsr_triggered`\n- `todo_reminder`\n- `todo_auto_clear`\n\nNon-event stdout categories remain flat: `ready`, `response`, `workflow_gate`, `extension_ui_request`, `extension_error`, `host_tool_call`, `host_tool_cancel`, `host_uri_request`, and `host_uri_cancel`.\n\n`message_update` includes streaming deltas in the inner event's `assistantMessageEvent` (text/thinking/toolcall deltas).\n\nExtension runner errors are emitted separately as flat frames:\n\n```json\n{\n \"type\": \"extension_error\",\n \"extensionPath\": \"...\",\n \"event\": \"...\",\n \"error\": \"...\"\n}\n```\n\n## Prompt/Queue Concurrency and Ordering\n\nThis is the most important operational behavior.\n\n### Immediate ack vs completion\n\n`prompt` and `abort_and_prompt` are **acknowledged immediately**:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n```\n\nThat means:\n\n- command acceptance != run completion\n- final completion is observed via `agent_end`\n\n### While streaming\n\n`AgentSession.prompt()` requires `streamingBehavior` during active streaming:\n\n- `\"steer\"` => queued steering message (interrupt path)\n- `\"followUp\"` => queued follow-up message (post-turn path)\n\nIf omitted during streaming, prompt fails.\n\n### Queue defaults\n\nFrom `packages/agent/src/agent.ts` defaults:\n\n- `steeringMode`: `\"one-at-a-time\"`\n- `followUpMode`: `\"one-at-a-time\"`\n- `interruptMode`: `\"immediate\"`\n\n### Mode semantics\n\n- `set_steering_mode` / `set_follow_up_mode`\n - `\"one-at-a-time\"`: dequeue one queued message per turn\n - `\"all\"`: dequeue entire queue at once\n- `set_interrupt_mode`\n - `\"immediate\"`: tool execution checks steering between tool calls; pending steering can abort remaining tool calls in the turn\n - `\"wait\"`: defer steering until turn completion\n\n## Workflow Gate Sub-Protocol\n\nInteractive workflow stages emit a machine-addressable gate frame before the legacy extension UI request:\n\n```json\n{\n \"type\": \"workflow_gate\",\n \"gate_id\": \"wg_4845_ralplan_000001\",\n \"stage\": \"ralplan\",\n \"kind\": \"approval\",\n \"schema\": { \"type\": \"string\", \"enum\": [\"approve\", \"request-changes\", \"reject\"] },\n \"schema_hash\": \"<sha256 of canonical schema>\",\n \"options\": [{ \"value\": \"approve\", \"label\": \"Approve execution\" }],\n \"context\": { \"title\": \"Approve plan?\", \"summary\": \"…\" },\n \"created_at\": \"2026-06-05T05:00:00.000Z\",\n \"required\": true\n}\n```\n\nFields:\n\n- `gate_id`: run-scoped, monotonic, stable id of the form `wg_<run>_<stage>_NNNNNN`.\n- `stage`: one of `\"deep-interview\"`, `\"ralplan\"`, or `\"ultragoal\"` (`team` is reserved and rejected for v1).\n- `kind`: one of `\"question\"`, `\"approval\"`, or `\"execution\"`.\n- `schema`: documented JSON Schema 2020-12 subset for the expected answer; `schema_hash` is the canonical hash of `schema` and equals the server-side validation hash.\n- `options`: optional `RpcWorkflowGateOption[]` (`{ value, label, description? }`), emitted for select-style gates.\n- `context`: `RpcWorkflowGateContext` (`title`, `prompt`, `summary`, `stage_state`, `artifact_refs`, `language`).\n- `created_at`: ISO timestamp the gate was opened; `required` is always `true`.\n\nHosts answer with:\n\n```json\n{ \"id\": \"resp_1\", \"type\": \"workflow_gate_response\", \"gate_id\": \"wg_4845_ralplan_000001\", \"answer\": \"approve\" }\n```\n\nA valid answer resolves the pending gate and returns:\n\n```json\n{ \"id\": \"resp_1\", \"type\": \"response\", \"command\": \"workflow_gate_response\", \"success\": true }\n```\n\nA schema mismatch is **not** a command failure: the response succeeds and the\nresolution data carries `status: \"rejected\"` plus a typed validation `error`\nwith code `invalid_workflow_gate_answer`:\n\n```json\n{\n \"id\": \"resp_1\",\n \"type\": \"response\",\n \"command\": \"workflow_gate_response\",\n \"success\": true,\n \"data\": {\n \"gate_id\": \"wg_1\",\n \"status\": \"rejected\",\n \"answer_hash\": \"…\",\n \"resolved_at\": \"…\",\n \"error\": {\n \"code\": \"invalid_workflow_gate_answer\",\n \"gate_id\": \"wg_1\",\n \"schema_hash\": \"…\",\n \"errors\": [{ \"path\": \"/answer\", \"keyword\": \"type\", \"message\": \"must be boolean\" }]\n }\n }\n}\n```\n\nAnswering a gate that does not exist is a recoverable command failure carrying\nthe broker error code `unknown_gate` (other broker codes are `already_resolved`,\n`idempotency_conflict`, and `invalid_workflow_stage`).\n## Extension UI Sub-Protocol\n\nExtensions in RPC mode use request/response UI frames.\n\n### Outbound request\n\n`RpcExtensionUIRequest` (`type: \"extension_ui_request\"`) methods:\n\n- `select`, `confirm`, `input`, `editor`, `cancel`\n- `notify`, `setStatus`, `setWidget`, `setTitle`, `set_editor_text`\n\nRuntime note:\n\n- Automatic session title generation is disabled in RPC mode, and `setTitle` UI\n requests are also suppressed by default because most hosts do not have a\n meaningful terminal-title surface. Set `SKC_RPC_EMIT_TITLE=1` to opt back in to\n the UI event only.\n\nExample:\n\n```json\n{\n \"type\": \"extension_ui_request\",\n \"id\": \"123\",\n \"method\": \"confirm\",\n \"title\": \"Confirm\",\n \"message\": \"Continue?\",\n \"timeout\": 30000\n}\n```\n\n### Inbound response\n\n`RpcExtensionUIResponse` (`type: \"extension_ui_response\"`):\n\n- `{ type: \"extension_ui_response\", id: string, value: string }`\n- `{ type: \"extension_ui_response\", id: string, confirmed: boolean }`\n- `{ type: \"extension_ui_response\", id: string, cancelled: true, timedOut?: boolean }`\n\nIf a dialog has a timeout, RPC mode resolves to a default value when timeout/abort fires.\n\n## Host Tool Sub-Protocol\n\nRPC hosts can expose custom tools to the agent by sending `set_host_tools`, then\nserving execution requests over the same transport.\n\n### Outbound request\n\nWhen the agent wants the host to execute one of those tools, RPC mode emits:\n\n```json\n{\n \"type\": \"host_tool_call\",\n \"id\": \"host_1\",\n \"toolCallId\": \"toolu_123\",\n \"toolName\": \"echo_host\",\n \"arguments\": { \"message\": \"hello\" }\n}\n```\n\nIf the tool execution is later aborted, RPC mode emits:\n\n```json\n{\n \"type\": \"host_tool_cancel\",\n \"id\": \"host_cancel_1\",\n \"targetId\": \"host_1\"\n}\n```\n\n### Inbound updates and completion\n\nHosts can optionally stream progress:\n\n```json\n{\n \"type\": \"host_tool_update\",\n \"id\": \"host_1\",\n \"partialResult\": {\n \"content\": [{ \"type\": \"text\", \"text\": \"working\" }]\n }\n}\n```\n\nCompletion uses:\n\n```json\n{\n \"type\": \"host_tool_result\",\n \"id\": \"host_1\",\n \"result\": {\n \"content\": [{ \"type\": \"text\", \"text\": \"done\" }]\n }\n}\n```\n\nSet top-level `isError: true` on `host_tool_result` to reject the pending host tool call and surface the returned text content as a tool error.\n\n## Host URI Sub-Protocol\n\nRPC hosts can also own custom URL schemes (virtual files). After\n`set_host_uri_schemes`, every read of `<scheme>://…` and write of\n`<scheme>://…` (when registered as `writable`) is bounced back to the host\nover the same transport.\n\n### Outbound request\n\nWhen a session tool resolves a host-owned URL, RPC mode emits:\n\n```json\n{\n \"type\": \"host_uri_request\",\n \"id\": \"uri_1\",\n \"operation\": \"read\",\n \"url\": \"db://users/42\"\n}\n```\n\nWrites look the same with `\"operation\": \"write\"` and an additional\n`\"content\": \"...\"` field carrying the full replacement bytes.\n\nIf the request is later aborted (caller cancels, session ends), RPC mode\nemits:\n\n```json\n{\n \"type\": \"host_uri_cancel\",\n \"id\": \"uri_cancel_1\",\n \"targetId\": \"uri_1\"\n}\n```\n\n### Inbound result\n\nFor successful reads:\n\n```json\n{\n \"type\": \"host_uri_result\",\n \"id\": \"uri_1\",\n \"content\": \"id=42\\nname=Alice\\n\",\n \"contentType\": \"text/plain\",\n \"notes\": [\"fresh from cache\"],\n \"immutable\": false\n}\n```\n\nFor successful writes, omit content:\n\n```json\n{ \"type\": \"host_uri_result\", \"id\": \"uri_1\" }\n```\n\nTo reject the request, set `isError: true` and either populate `error` with\na message or fall back to `content` for textual error surfacing:\n\n```json\n{\n \"type\": \"host_uri_result\",\n \"id\": \"uri_1\",\n \"isError\": true,\n \"error\": \"row 42 not found\"\n}\n```\n\n### Constraints\n\n- The agent's `edit` tool does not target host URIs. Hosts that want to\n mutate virtual files expose `write` and let the model use the `write` tool\n with replacement content.\n- Schemes are global to the process; `set_host_uri_schemes` replaces the\n previous set, unregistering anything not in the new list.\n- Schemes are normalized to lowercase before registration.\n\n## Error Model and Recoverability\n\n### Command-level failures\n\nFailures are `success: false` with string `error`.\n\n```json\n{\n \"id\": \"req_2\",\n \"type\": \"response\",\n \"command\": \"set_model\",\n \"success\": false,\n \"error\": \"Model not found: provider/model\"\n}\n```\n\n### Recoverability expectations\n\n- Most command failures are recoverable; process remains alive.\n- Malformed JSONL / parse-loop exceptions emit a `parse` error response and continue reading subsequent lines.\n- Empty `set_session_name` is rejected (`Session name cannot be empty`).\n- Extension UI responses with unknown `id` are ignored.\n- Process termination conditions are stdin close or explicit extension-triggered shutdown after the current command.\n\n## Compact Command Flows\n\n### 1) Prompt and stream\n\nstdin:\n\n```json\n{ \"id\": \"req_1\", \"type\": \"prompt\", \"message\": \"Summarize this repo\" }\n```\n\nstdout sequence (typical):\n\n```json\n{ \"id\": \"req_1\", \"type\": \"response\", \"command\": \"prompt\", \"success\": true }\n{ \"type\": \"agent_start\" }\n{ \"type\": \"message_update\", \"assistantMessageEvent\": { \"type\": \"text_delta\", \"delta\": \"...\" }, \"message\": { \"role\": \"assistant\", \"content\": [] } }\n{ \"type\": \"agent_end\", \"messages\": [] }\n```\n\n### 2) Prompt during streaming with explicit queue policy\n\nstdin:\n\n```json\n{\n \"id\": \"req_2\",\n \"type\": \"prompt\",\n \"message\": \"Also include risks\",\n \"streamingBehavior\": \"followUp\"\n}\n```\n\n### 3) Inspect and tune queue behavior\n\nstdin:\n\n```json\n{ \"id\": \"q1\", \"type\": \"get_state\" }\n{ \"id\": \"q2\", \"type\": \"set_steering_mode\", \"mode\": \"all\" }\n{ \"id\": \"q3\", \"type\": \"set_interrupt_mode\", \"mode\": \"wait\" }\n```\n\n### 4) Extension UI round trip\n\nstdout:\n\n```json\n{\n \"type\": \"extension_ui_request\",\n \"id\": \"ui_7\",\n \"method\": \"input\",\n \"title\": \"Branch name\",\n \"placeholder\": \"feature/...\"\n}\n```\n\nstdin:\n\n```json\n{ \"type\": \"extension_ui_response\", \"id\": \"ui_7\", \"value\": \"feature/rpc-host\" }\n```\n\n## OpenClaw / Hermes host integrations\n\nFor OpenClaw- or Hermes-style hosts, keep MCP servers and skills on the host side and expose the selected capabilities through RPC host tools. Do not import SKC runtime MCP internals directly; those package paths are intentionally quarantined. See [OpenClaw / Hermes RPC integration notes](./openclaw-hermes-rpc-integration.md).\n\n## Notes on `RpcClient` helper\n\n`src/modes/rpc/rpc-client.ts` is a convenience wrapper, not the protocol definition.\n\nCurrent helper characteristics:\n\n- Spawns `bun <cliPath> --mode rpc`\n- Correlates responses by generated `req_<n>` ids\n- Dispatches recognized `AgentEvent` types to event listeners\n- Dispatches top-level `workflow_gate` frames to `onWorkflowGate()` listeners\n- Supports host-owned custom tools via `setCustomTools()` and automatic handling of `host_tool_call` / `host_tool_cancel`\n- Exposes `respondGate()` for `workflow_gate_response` and waits for the accepted/rejected resolution envelope\n- Does **not** expose helper methods for every protocol command (for example, `set_interrupt_mode` and `set_session_name` are in protocol types but not wrapped as dedicated methods)\n\nUse raw protocol frames if you need complete surface coverage.\n\n## Workflow gates (agent-driven lifecycle)\n\nThe workflow-gate contract makes every human-gated lifecycle moment\n(deep-interview questions, ralplan approval, ultragoal execution sign-off)\nmachine-addressable so an external agent can answer it over RPC without\nscreen-scraping.\n\n### Outbound event: `workflow_gate`\n\n```json\n{\n \"type\": \"workflow_gate\",\n \"gate_id\": \"wg_4845_ralplan_000001\",\n \"stage\": \"ralplan\",\n \"kind\": \"approval\",\n \"schema\": { \"type\": \"string\", \"enum\": [\"approve\", \"request-changes\", \"reject\"] },\n \"schema_hash\": \"<sha256 of canonical schema>\",\n \"options\": [{ \"value\": \"approve\", \"label\": \"Approve execution\" }],\n \"context\": { \"title\": \"Approve plan?\", \"summary\": \"…\" },\n \"created_at\": \"2026-06-05T05:00:00.000Z\",\n \"required\": true\n}\n```\n\n- `gate_id` is **run-scoped and monotonic**: `wg_<run>_<stage>_<NNNNNN>`.\n- `stage` is one of `deep-interview` | `ralplan` | `ultragoal`. `team` is\n reserved and rejected for v1 (single-agent only).\n- `kind` is `question` | `approval` | `execution`.\n- `schema` is a **documented subset of JSON Schema 2020-12**. Supported keywords:\n `type`, `enum`, `const`, `properties`, `required`, `additionalProperties`,\n `items`, `minLength`, `maxLength`, `minimum`, `maximum`, `title`,\n `description`, `oneOf`, `anyOf`. Any other keyword is rejected at gate\n construction (`invalid_workflow_gate_schema`) so the server never advertises a\n schema it will not validate. `schema_hash` equals the server-side validation\n hash for that gate.\n\n### Inbound command: `workflow_gate_response`\n\n```json\n{ \"type\": \"workflow_gate_response\", \"gate_id\": \"wg_4845_ralplan_000001\", \"answer\": \"approve\", \"idempotency_key\": \"k1\" }\n```\n\nThe answer is validated against the advertised schema **before acceptance**:\n\n- Valid → resolution persisted before the workflow advances; response:\n `{ \"type\": \"response\", \"command\": \"workflow_gate_response\", \"success\": true, \"data\": { \"gate_id\": \"…\", \"status\": \"accepted\", \"answer_hash\": \"…\", \"resolved_at\": \"…\" } }`.\n- Invalid → the gate stays **pending** and the resolution carries a typed\n `invalid_workflow_gate_answer` error listing each `{ path, keyword, message, expected? }`.\n- Idempotency: replaying the same `idempotency_key` + identical body returns the\n cached resolution; the same key with a different body is an\n `idempotency_conflict`; answering an already-accepted gate is `already_resolved`.\n- Client helpers wait for this accepted/rejected resolution envelope; they must not treat the write of `workflow_gate_response` itself as completion.\n\n### Entering unattended mode: `negotiate_unattended`\n\nUnattended (zero-human) operation is **fail-closed**. The external agent must\ndeclare its budget, scopes, and action allowlist up front:\n\n```json\n{\n \"type\": \"negotiate_unattended\",\n \"declaration\": {\n \"actor\": \"openclaw/hermes\",\n \"budget\": { \"max_tokens\": 2000000, \"max_tool_calls\": 5000, \"max_wall_time_ms\": 3600000, \"max_cost_usd\": 20 },\n \"scopes\": [\"prompt\", \"control\", \"bash\"],\n \"action_allowlist\": [\"bash.readonly\", \"file.write\"]\n }\n}\n```\n\nA missing or partial declaration refuses unattended mode. Budget, scope, and\naudit enforcement are layered on this contract by the unattended control plane\n(see issues #318/#319/#320). Attended mode is unaffected: clients that never send\n`negotiate_unattended` keep the existing extension-UI / permission behavior.\n\n\n> **Status (live, #315/#318/#321):** the `workflow_gate` /\n> `workflow_gate_response` / `negotiate_unattended` frames, the answer-schema\n> validator, and the durable gate broker are defined, tested, and wired into\n> live session dispatch. When an unattended control plane is attached to the\n> session, `dispatchRpcCommand` routes `negotiate_unattended` and\n> `workflow_gate_response` through it (see\n> `packages/coding-agent/src/modes/shared/agent-wire/command-dispatch.ts`); a\n> session without that control plane returns a typed \"not available\" error for\n> these frames rather than silently dropping them.\n\n\n### Answering gates from a client (#322)\n\nBoth clients expose typed `workflow_gate` receive + respond helpers so an agent\ncan answer a gate from its own memory via a callback.\n\nFor bridge sessions, gate responses are **not** posted through `/commands`. The\nclient must first own the UI/control plane, then post the answer body to\n`POST /v1/sessions/{session_id}/ui-responses/{gate_id}` with\n`X-SKC-Bridge-Owner-Token: <ownerToken>`. `Idempotency-Key` may be supplied as a\nheader and the same value is also accepted in the JSON body as `idempotency_key`.\n\n`@sayknow-cli/bridge-client` (TypeScript):\n\n```ts\nimport { BridgeClient } from \"@sayknow-cli/bridge-client\";\n\nconst client = new BridgeClient({ baseUrl, token });\n// Headless policy: every received gate is routed to the resolver and answered.\nfor await (const { gate, answer } of client.consumeWorkflowGates(sessionId, ownerToken, gate => {\n if (gate.kind === \"approval\") return { decision: \"approve\" };\n if (gate.kind === \"question\") return { selected: [gate.options?.[0]?.value], other: false };\n return { decision: \"approve\" };\n})) {\n console.log(`answered ${gate.gate_id} (${gate.kind}) with`, answer);\n}\n// Or answer a single gate directly:\nawait client.respondGate(sessionId, gateId, ownerToken, { decision: \"approve\" });\n```\n\n`python/skc-rpc` (Python):\n\n```python\nfrom skc_rpc import RpcClient, WorkflowGate\n\nclient = RpcClient(executable=\"skc\")\n\ndef resolver(gate: WorkflowGate) -> object:\n if gate.kind == \"approval\":\n return {\"decision\": \"approve\"}\n if gate.kind == \"question\":\n return {\"selected\": [gate.options[0].value] if gate.options else [], \"other\": False}\n return {\"decision\": \"approve\"}\n\n# Headless policy: route every received gate to the resolver and respond.\nclient.run_workflow_gate_policy(resolver)\nclient.start()\n# Or answer a single gate directly: client.respond_gate(gate_id, {\"decision\": \"approve\"})\n```\n",
|
|
78
79
|
"rulebook-matching-pipeline.md": "# Rulebook Matching Pipeline\n\nThis document describes how coding-agent discovers rules from supported config formats, normalizes them into a single `Rule` shape, resolves precedence conflicts, and splits the result into:\n\n- **Rulebook rules** (available to the model via system prompt + `rule://` URLs)\n- **TTSR rules** (time-travel stream interruption rules)\n\nIt reflects the current implementation, including partial semantics and metadata that is parsed but not enforced.\n\n## Implementation files\n\n- [`../src/capability/rule.ts`](../packages/coding-agent/src/capability/rule.ts)\n- [`../src/capability/index.ts`](../packages/coding-agent/src/capability/index.ts)\n- [`../src/discovery/index.ts`](../packages/coding-agent/src/discovery/index.ts)\n- [`../src/discovery/helpers.ts`](../packages/coding-agent/src/discovery/helpers.ts)\n- [`../src/discovery/builtin.ts`](../packages/coding-agent/src/discovery/builtin.ts)\n- [`../src/discovery/cursor.ts`](../packages/coding-agent/src/discovery/cursor.ts)\n- [`../src/discovery/windsurf.ts`](../packages/coding-agent/src/discovery/windsurf.ts)\n- [`../src/discovery/cline.ts`](../packages/coding-agent/src/discovery/cline.ts)\n- [`../src/sdk.ts`](../packages/coding-agent/src/sdk.ts)\n- [`../src/system-prompt.ts`](../packages/coding-agent/src/system-prompt.ts)\n- [`../src/internal-urls/rule-protocol.ts`](../packages/coding-agent/src/internal-urls/rule-protocol.ts)\n- [`../src/utils/frontmatter.ts`](../packages/coding-agent/src/utils/frontmatter.ts)\n\n## 1. Canonical rule shape\n\nAll providers normalize source files into `Rule`:\n\n```ts\ninterface Rule {\n name: string;\n path: string;\n content: string;\n globs?: string[];\n alwaysApply?: boolean;\n description?: string;\n condition?: string[];\n scope?: string[];\n interruptMode?: \"never\" | \"prose-only\" | \"tool-only\" | \"always\";\n _source: SourceMeta;\n}\n```\n\nCapability identity is `rule.name` (`ruleCapability.key = rule => rule.name`).\n\nConsequence: precedence and deduplication are **name-based only**. Two different files with the same `name` are considered the same logical rule.\n\n## 2. Discovery sources and normalization\n\n`src/discovery/index.ts` auto-registers providers. For `rules`, current providers are:\n\n- `native` (priority `100`)\n- `cursor` (priority `50`)\n- `windsurf` (priority `50`)\n- `cline` (priority `40`)\n\n### Native provider (`builtin.ts`)\n\nLoads `.skc` rules from:\n\n- project: `<cwd>/.skc/rules/*.{md,mdc}`\n- user: `~/.skc/agent/rules/*.{md,mdc}`\n\nNormalization:\n\n- `name` = filename without `.md`/`.mdc`\n- frontmatter parsed via `parseFrontmatter`\n- `content` = body (frontmatter stripped)\n- `globs`, `alwaysApply`, `description`, `condition`/legacy `ttsr_trigger`, `scope`, and `interruptMode` are parsed by `buildRuleFromMarkdown`\n\nImportant caveat: `condition` values that look like file globs are converted into `tool:edit(...)` / `tool:write(...)` scope shorthands with catch-all condition `.*`.\n\n### Cursor provider (`cursor.ts`)\n\nLoads from:\n\n- user: `~/.cursor/rules/*.{mdc,md}`\n- project: `<cwd>/.cursor/rules/*.{mdc,md}`\n\nNormalization (`transformMDCRule`):\n\n- `description`: kept only if string\n- `alwaysApply`: only `true` is preserved (`false` becomes `undefined`)\n- `globs`: accepts array (string elements only) or single string\n- `condition`/legacy `ttsr_trigger`, `scope`, and `interruptMode` are parsed by shared rule helpers\n- `name` from filename without extension\n\n### Windsurf provider (`windsurf.ts`)\n\nLoads from:\n\n- user: `~/.codeium/windsurf/memories/global_rules.md` (fixed rule name `global_rules`)\n- project: `<cwd>/.windsurf/rules/*.md`\n\nNormalization:\n\n- `globs`: array-of-string or single string\n- `alwaysApply`, `description`, `condition`/legacy `ttsr_trigger`, `scope`, and `interruptMode` parsed by shared rule helpers\n- `name` is fixed to `global_rules` for the user global file and derived from filename for project rules\n\n### Cline provider (`cline.ts`)\n\nSearches upward from `cwd` for nearest `.clinerules`:\n\n- if directory: loads `*.md` inside it\n- if file: loads single file as rule named `clinerules`\n\nNormalization:\n\n- `globs`: array-of-string or single string\n- `alwaysApply`, `description`, `condition`/legacy `ttsr_trigger`, `scope`, and `interruptMode` parsed by shared rule helpers\n- `name` is fixed to `clinerules` for a `.clinerules` file and derived from filename for `.clinerules/*.md`\n\n## 3. Frontmatter parsing behavior and ambiguity\n\nAll providers use `parseFrontmatter` (`utils/frontmatter.ts`) with these semantics:\n\n1. Frontmatter is parsed only when content starts with `---` and has a closing `\\n---`.\n2. Body is trimmed after frontmatter extraction.\n3. If YAML parse fails:\n - warning is logged,\n - parser falls back to simple `key: value` line parsing (`^(\\w+):\\s*(.*)$`).\n\nAmbiguity consequences:\n\n- Fallback parser does not support arrays, nested objects, quoting rules, or hyphenated keys.\n- Fallback values become strings (for example `alwaysApply: true` becomes string `\"true\"`), so providers requiring boolean/string types may drop metadata.\n- `ttsr_trigger` works in fallback (underscore key); keys like `thinking-level` would not.\n- Files without valid frontmatter still load as rules with empty metadata and full content body.\n\n## 4. Provider precedence and deduplication\n\n`loadCapability(\"rules\")` (`capability/index.ts`) merges provider outputs and then deduplicates by `rule.name`.\n\n### Precedence model\n\n- Providers are ordered by priority descending.\n- Equal priority keeps registration order (`cursor` before `windsurf` from `discovery/index.ts`).\n- Dedup is first-wins: first encountered rule name is kept; later same-name items are marked `_shadowed` in `all` and excluded from `items`.\n\nEffective rule provider order is currently:\n\n1. `native` (100)\n2. `cursor` (50)\n3. `windsurf` (50)\n4. `cline` (40)\n\n### Intra-provider ordering caveat\n\nWithin a provider, item order comes from `loadFilesFromDir` glob result ordering plus explicit push order. This is deterministic enough for normal use but not explicitly sorted in code.\n\nNotable source-order differences:\n\n- `native` appends project then user config dirs.\n- `cursor` appends user then project results.\n- `windsurf` appends user `global_rules` first, then project rules.\n- `cline` loads only nearest `.clinerules` source.\n\n## 5. Split into Rulebook, Always-Apply, and TTSR buckets\n\nAfter rule discovery in `createAgentSession` (`sdk.ts`):\n\n1. All discovered rules are scanned.\n2. Rules with `condition` entries are registered into `TtsrManager`; legacy `ttsr_trigger` / `ttsrTrigger` are accepted during rule parsing as condition fallbacks.\n3. A separate `rulebookRules` list is built with this predicate:\n\n```ts\n!isTtsrRule && rule.alwaysApply !== true && !!rule.description;\n```\n\n4. An `alwaysApplyRules` list is built:\n\n```ts\n!isTtsrRule && rule.alwaysApply === true;\n```\n\n### Bucket behavior\n\n- **TTSR bucket**: any rule with a non-empty parsed `condition` that `TtsrManager.addRule(...)` accepts. Takes priority over other buckets.\n- **Always-apply bucket**: `alwaysApply === true`, not TTSR. Full content injected into system prompt. Resolvable via `rule://`.\n- **Rulebook bucket**: must have description, must not be TTSR, must not be `alwaysApply`. Listed in system prompt by name+description; content read on demand via `rule://`.\n- A rule with both `condition` and `alwaysApply` goes to TTSR only (TTSR takes priority).\n- A rule with both `alwaysApply` and `description` goes to always-apply only (not rulebook).\n\n## 6. How metadata affects runtime surfaces\n\n### `description`\n\n- Required for inclusion in rulebook.\n- Rendered in system prompt `<rules>` block.\n- Missing description means rule is not available via `rule://` and not listed in system prompt rules.\n\n### `globs`\n\n- Carried through on `Rule`.\n- Rendered as `<glob>...</glob>` entries in the system prompt rules block.\n- Exposed in rules UI state (`extensions` mode list).\n- **Not enforced for automatic matching in this pipeline.** There is no runtime glob matcher selecting rules by current file/tool target.\n\n### `alwaysApply`\n\n- Parsed and preserved by providers.\n- Used in UI display (`\"always\"` trigger label in extensions state manager).\n- Used as an exclusion condition from `rulebookRules`.\n- **Full rule content is auto-injected into the system prompt** (before the rulebook rules section).\n- Rule is also addressable via `rule://<name>` for re-reading.\n\n### `condition`, `scope`, and `interruptMode`\n\n- `condition` is the current TTSR trigger field; legacy `ttsr_trigger` / `ttsrTrigger` are accepted as fallback inputs during parsing.\n- `scope` narrows TTSR matching scope. A condition token that looks like a file glob becomes `tool:edit(<glob>)` and `tool:write(<glob>)` scope entries plus catch-all condition `.*`.\n- `interruptMode` can override the global TTSR interrupt mode for the rule.\n\n## 7. System prompt inclusion path\n\n`buildSystemPromptInternal` receives both `rules` (rulebook) and `alwaysApplyRules`.\n\nAlways-apply rules are rendered first, injecting their raw content directly into the prompt.\n\nRulebook rules are rendered in a `# Rules` section with:\n\n- `Read rule://<name> when working in matching domain`\n- Each rule's `name`, `description`, and optional `<glob>` list\n\nThis is advisory/contextual: prompt text asks the model to read applicable rules, but code does not enforce glob applicability.\n\n## 8. `rule://` internal URL behavior\n\n`RuleProtocolHandler` is registered with:\n\n```ts\nnew RuleProtocolHandler({\n getRules: () => [...rulebookRules, ...alwaysApplyRules],\n});\n```\n\nImplications:\n\n- `rule://<name>` resolves against both **rulebookRules** and **alwaysApplyRules**.\n- TTSR-only rules and rules with no description and no `alwaysApply` are not addressable via `rule://`.\n- Resolution is exact name match.\n- Unknown names return error listing available rule names.\n- Returned content is raw `rule.content` (frontmatter stripped), content type `text/markdown`.\n\n## 9. Known partial / non-enforced semantics\n\n1. Provider descriptions mention legacy files (`.cursorrules`, `.windsurfrules`), but current loader code paths do not actually read those files.\n2. `globs` metadata is surfaced to prompt/UI but not enforced by rule selection logic.\n3. Rule selection for `rule://` includes rulebook and always-apply rules, but not TTSR-only rules.\n4. Discovery warnings (`loadCapability(\"rules\").warnings`) are produced but `createAgentSession` does not currently surface/log them in this path.\n",
|
|
79
80
|
"sayknow-remote.md": "# Sayknow Remote — v0 Design (thin phone steering wheel)\n\nStatus: **design / pending approval** · Tracks issue #565 · Scope: **v0 only**\n\nSayknow Remote is a thin remote *steering wheel* for an already-running PC-side\n`skc` session. It is intentionally **not** a universal phone shell, a remote\nfilesystem editor, or a remote-desktop replacement. The PC stays the authority\nboundary for file edits, shell execution, approval gates, and sensitive output.\n\nThis document fixes the two things the issue calls out as blockers/gates before\nany code lands: the **authority boundary contract** and the **transmitted-data\ncontract**. It then maps v0 onto existing surfaces and splits the work into\nPR-sized steps.\n\n## TL;DR architecture decision\n\nv0 is a thin **read + one-line-submit** surface layered on subsystems that\nalready exist. It introduces **no new remote-control protocol** — that would\nrequire ADR-level rationale per [`docs/bridge.md`](bridge.md).\n\n| Concern | Reused existing surface |\n| --- | --- |\n| Network transport, TLS, bearer auth, fail-closed posture | Bridge mode (`skc --mode bridge`), [`docs/bridge.md`](bridge.md) |\n| Client SDK / framing | `@sayknow-cli/bridge-client` (`BridgeClient`, `events()`) |\n| Session state storage, liveness, bounded status, submit gating | Harness control plane (`skc harness`), `packages/coding-agent/src/harness-control-plane/` |\n| Bounded observation (never a raw transcript dump) | `Observation` / `SessionStateView` in `harness-control-plane/types.ts` |\n| Web client precedent (local server + SPA) | `packages/stats` (`server.ts` + `src/client/`) |\n\nThe only genuinely new piece is a thin **Sayknow Remote gateway**: one PC-side\nprocess that enumerates harness control-plane sessions and proxies a strict,\nbounded subset of read/submit operations to each session's owner. The phone\ntalks to exactly one gateway endpoint.\n\n## Why a gateway (and not one bridge per session)\n\nBridge mode serves **exactly one live `AgentSession` per process** and is\nfail-closed by default (see the endpoint matrix in\n`packages/coding-agent/src/modes/bridge/bridge-mode.ts`). The v0 requirement\n\"list active PC-side sessions\" needs cross-session enumeration, which a single\nbridge process does not provide.\n\nThe harness control plane already centralizes exactly this: per-session\n`state.json`, lease + heartbeat liveness, a single-writer severity event log,\nand a **bounded** observation vocabulary. So the gateway is a thin read/submit\nproxy over the control plane, wearing the bridge security model (TLS + bearer +\nfail-closed). This keeps bridge single-session semantics untouched and avoids a\nproliferation of per-session ports and pairing surfaces.\n\n```\n phone (mobile web)\n │ HTTPS + bearer (scoped: remote:view + remote:submit)\n ▼\n Sayknow Remote gateway ── enumerates ─▶ harness control-plane session-state dir\n (one PC process) ── observe ───▶ owner process (RuntimeOwner, lease holder)\n ── submit ────▶ owner submit path (readyForSubmit gating)\n │\n └─ never: file edits · shell · gate answers · raw transcript · secrets\n```\n\n## Authority boundary contract\n\nThe PC-side runtime is the sole authority. The gateway and the phone are\n**observers + one-line submitters**, nothing more.\n\nThe gateway MUST NOT, in v0:\n\n- edit files, run shell, or invoke any mutating tool;\n- answer workflow-gate / permission / approval prompts (those stay on the PC);\n- expose bridge/RPC command scopes (`message:read`, `session`, `model`, `bash`,\n `host_tools`, `host_uri`, `export`, `admin`, or `control`) to the phone;\n- stream raw pane output, transcripts, tool arguments/results, diffs, file\n contents, environment, or secrets;\n- bypass the owner's `readyForSubmit` gating or submit while a session is busy.\n\nThe phone MAY, in v0:\n\n- list active sessions (bounded metadata);\n- open one session and read its **bounded** status/observation;\n- submit a single one-line instruction through the owner's normal submit path;\n- see idle / working / blocked status and a human-readable reason when blocked.\n\nA one-line submit travels the same path as a local `skc harness submit`, so it\ninherits the owner's submission gating (`Observation.readyForSubmit` /\n`submitUnavailableReason`). The phone cannot force a submit the local runtime\nwould itself refuse.\n\n## Transmitted-data contract (allowlist)\n\nThe contract is an **allowlist**: only the fields below leave the PC. Anything\nnot listed is withheld by default. This is enforced in code as a typed\nprojection from the control plane's already-bounded `Observation` /\n`SessionStateView` — never a passthrough of internal state.\n\n### Session list entry → phone (`RemoteSessionSummary`)\n\n| Field | Source | Notes |\n| --- | --- | --- |\n| `sessionId` | `SessionState.sessionId` | opaque id |\n| `name` | derived from handle metadata (`issueOrPr`, repo, branch, or session id fallback) | sanitized, length-capped |\n| `harness` | `SessionState.harness` | `sayknow-cli` in v1 |\n| `status` | derived (see state mapping) | `idle` \\| `working` \\| `blocked` \\| `offline` |\n| `lastActivityAt` | `Observation.lastActivityAt` | ISO timestamp |\n| `branch` | `Observation.branch` | branch name only |\n\n### Open-session view → phone (`RemoteSessionView`)\n\n| Field | Source | Notes |\n| --- | --- | --- |\n| `sessionId`, `name`, `harness`, `status` | as above | |\n| `lifecycle` | `SessionStateView.lifecycle` | bounded enum |\n| `ownerLive` | `SessionStateView.ownerLive` | liveness |\n| `blockers` | `SessionStateView.blockers` | reason strings, sanitized |\n| `observedSignals` | `Observation.observedSignals` | bounded vocab only (`tool-call`, `test-running`, `streaming`, `idle`, …) |\n| `gitDelta` | `Observation.gitDelta` | enum: `clean`/`dirty`/`zero-delta`/`unknown` |\n| `risk` | `Observation.risk` | enum |\n| `readyForSubmit` | `Observation.readyForSubmit` | submit affordance |\n| `submitUnavailableReason` | `Observation.submitUnavailableReason` | when not ready |\n| `lastActivityAt`, `branch` | as above | |\n\n### Phone → PC (`RemoteSubmitRequest`)\n\n| Field | Notes |\n| --- | --- |\n| `sessionId` | target session |\n| `text` | single one-line instruction; length-capped, control-chars stripped |\n| `idempotencyKey` | optional; dedupes retries (mirrors bridge idempotency) |\n\n### Never transmitted by default\n\nRaw pane/terminal output, full transcript / message bodies, tool call arguments\nor results, file contents, diffs, system prompt, environment variables, tokens\nor secrets, and absolute paths beyond the session `cwd`/`branch` metadata. When\ncontent is intentionally held back, the phone shows a neutral *\"withheld on PC\"*\nmarker rather than a redacted blob.\n\n## Session-state model (idle / working / blocked)\n\n`status` is derived from harness lifecycle + liveness + bounded signals:\n\n- **offline** — `ownerLive == false`, lease dead, or gateway cannot reach the\n owner. (Distinct from blocked; the PC is gone, not waiting.)\n- **blocked** — `lifecycle == \"blocked\"`, OR a workflow-gate / permission prompt\n is pending on the PC, OR `readyForSubmit == false` with a\n `submitUnavailableReason`. Phone shows the reason; it does **not** resolve it.\n- **working** — owner live and `lifecycle` in\n `{started, submitted, observing, recovering, validating, finalizing}` with\n recent activity signals (`streaming` / `tool-call` / `test-running`).\n- **idle** — owner live, stable lifecycle, last signal `idle`/`completed`, and\n `readyForSubmit == true`.\n\n## Failure states (must be understandable)\n\n| Condition | Detection | Phone UX |\n| --- | --- | --- |\n| Disconnected PC | `ownerLive`/lease dead or gateway unreachable | `offline`; submit disabled; \"PC is offline\" |\n| Expired pairing | bearer/pairing token expired or revoked | \"Pairing expired — re-pair on PC\" |\n| Session busy | `readyForSubmit == false` (+ reason) | submit disabled with reason; optionally queue |\n| Submit rejected | typed object error (e.g. `{ code: \"scope_denied\" }`, see [`docs/rpc.md`](rpc.md)) | inline rejection reason |\n| Sensitive output withheld | bounded observation by design | neutral \"withheld on PC\" marker |\n\n## Pairing and auth (minimum that is not security soup)\n\nv0 = **local pairing only**. Reuse the bridge security model for transport and authentication posture, but expose a gateway-specific authorization surface:\n\n- **TLS mandatory for every bind, including loopback** (no plaintext fallback;\n matches `docs/bridge.md`).\n- **Bearer token mandatory** for every endpoint except health/help.\n- Pairing flow: the PC prints/serves a short-lived **pairing code**; the phone\n submits host + code and receives a **scoped bearer** capped to\n gateway-only scopes: `remote:view` + `remote:submit` only. These scopes are\n not aliases for bridge/RPC `message:read` or `prompt`. Phone bearers MUST NOT\n authorize bridge command-catalog calls such as `get_messages`,\n `get_last_assistant_text`, `get_state` with `include: [\"systemPrompt\",\n \"tools\"]`, `new_session`, `switch_session`, `branch`, `set_model`,\n `bash`, `host_*`, `control`, or `admin`.\n- Tokens expire; re-pairing is the recovery path. The gateway is fail-closed:\n unknown/expired tokens, non-gateway scopes, bridge command-catalog methods,\n and out-of-scope commands are rejected before dispatch.\n\nHosted relay is **deferred to v1** and gated behind a separate ADR (it changes\nthe trust model and is where \"security soup\" risk concentrates).\n\n## Open questions from the issue — v0 decisions\n\n| Question | v0 decision | Deferred |\n| --- | --- | --- |\n| Hosted relay vs local pairing vs both | Local pairing only | Hosted relay → v1 (ADR) |\n| Minimum pairing/auth | Pairing code → gateway-scoped bearer, TLS mandatory | Identity/relay accounts → v1 |\n| Which session states are public | Bounded: `idle`/`working`/`blocked`/`offline` + bounded observation vocab | Richer telemetry → v1 |\n| Web vs native first | Mobile web first (reuse stats SPA build pattern) | Native app / PWA polish → v1 |\n| Notifications / pause / resume | Out of scope for v0 | Staged in v1 |\n\n## Implementation plan (PR-sized steps)\n\nEach step is independently shippable; later steps stay fail-closed until wired.\n\n1. **PR 1 — this doc.** `docs/sayknow-remote.md` + README cross-link. Resolves the\n authority + transmitted-data gates. No code.\n2. **PR 2 — typed contract + schema.** `RemoteSessionSummary`,\n `RemoteSessionView`, `RemoteSubmitRequest`, `RemoteSubmitResult`,\n `RemoteErrorCode`, plus a projection `Observation`/`SessionStateView →\n RemoteSessionView` and a JSON schema. Tests assert the allowlist (no\n forbidden field can leak). Types only; no runtime wiring.\n3. **PR 3 — gateway read path.** Enumerate harness sessions (list) and serve the\n per-session bounded view, behind an explicit opt-in flag, fail-closed\n otherwise, reusing bridge TLS + bearer. Tests for liveness derivation and the\n redaction projection.\n4. **PR 4 — gateway submit path.** One-line submit through the owner's submit\n gating; typed rejections for busy/denied; idempotency. Tests for\n busy/rejected paths.\n5. **PR 5 — pairing/auth.** Pairing code → gateway-scoped bearer\n (`remote:view` + `remote:submit`) with expiry. Tests prove phone bearers\n cannot call bridge/RPC `message:read`, `prompt`, session/model, shell, host,\n control, or admin surfaces.\n6. **PR 6 — mobile web client.** Minimal SPA (list → open → status → submit)\n using `@sayknow-cli/bridge-client` and the `packages/stats` build pattern.\n7. **PR 7 — failure-state UX + hardening.** Failure-state surfaces, redaction\n hardening, CHANGELOG, docs finalize.\n\n## Non-goals (v0)\n\n- No arbitrary phone-side shell.\n- No raw secret/log dumping to mobile by default.\n- No direct filesystem editor from the phone.\n- No bypass around PC-side approval/confirmation gates.\n- No remote-desktop replacement.\n- No second authenticated remote-control protocol (reuse bridge; relay needs an\n ADR).\n\n## Key source references\n\n- Bridge transport / security: `packages/coding-agent/src/modes/bridge/`, [`docs/bridge.md`](bridge.md)\n- RPC command/response contract: `packages/coding-agent/src/modes/rpc/`, [`docs/rpc.md`](rpc.md)\n- Client SDK: `packages/bridge-client/src/`\n- Control plane (sessions, leases, bounded observation, submit): `packages/coding-agent/src/harness-control-plane/`, `packages/coding-agent/src/commands/harness.ts`\n- Web client precedent: `packages/stats/src/server.ts`, `packages/stats/src/client/`\n\n—\n*[repo owner's gaebal-sayknow (clawdbot) 🦞]*\n",
|
|
80
81
|
"sdk.md": "# SDK\n\nThe SDK is the in-process integration surface for `@sayknow-cli/coding-agent`.\nUse it when you want direct access to agent state, event streaming, tool wiring, and session control from your own Bun/Node process.\n\nIf you need cross-language/process isolation, use RPC mode instead.\n\n## Installation\n\n```bash\nbun add @sayknow-cli/coding-agent\n```\n\n## Entry points\n\n`@sayknow-cli/coding-agent` exports the SDK APIs from the package root (and also via `@sayknow-cli/coding-agent/sdk`).\n\nCore exports for embedders:\n\n- `createAgentSession`\n- `SessionManager`\n- `Settings`\n- `AuthStorage`\n- `ModelRegistry`\n- `discoverAuthStorage`\n- Discovery helpers for retained context/prompt surfaces (`discoverContextFiles`, `discoverPromptTemplates`)\n- Tool factory surface (`createTools`, `BUILTIN_TOOLS`, tool classes)\n\n## Quick start (auto-discovery defaults)\n\n```ts\nimport { createAgentSession } from \"@sayknow-cli/coding-agent\";\n\nconst { session, modelFallbackMessage } = await createAgentSession();\n\nif (modelFallbackMessage) {\n process.stderr.write(`${modelFallbackMessage}\\n`);\n}\n\nconst unsubscribe = session.subscribe((event) => {\n if (\n event.type === \"message_update\" &&\n event.assistantMessageEvent.type === \"text_delta\"\n ) {\n process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Summarize this repository in 3 bullets.\");\nunsubscribe();\nawait session.dispose();\n```\n\n## What `createAgentSession()` discovers by default\n\n`createAgentSession()` follows “provide to override, omit to discover”.\n\nIf omitted, it resolves:\n\n- `cwd`: `getProjectDir()`\n- `agentDir`: `~/.skc/agent` (via `getAgentDir()`)\n- `authStorage`: `discoverAuthStorage(agentDir)`\n- `modelRegistry`: `new ModelRegistry(authStorage)` + background `refreshInBackground()` when the registry is not provided\n- `settings`: `await Settings.init({ cwd, agentDir })`\n- `sessionManager`: `SessionManager.create(cwd)` (file-backed)\n- context files and prompt templates\n- built-in tools via `createTools(...)`\n- LSP integration (enabled by default)\n- `eventBus`: new `EventBus()` unless supplied\n\n### Required vs optional inputs\n\nTypically you must provide only what you want to control:\n\n- **Must provide**: nothing for a minimal session\n- **Usually provide explicitly** in embedders:\n - `sessionManager` (if you need in-memory or custom location)\n - `authStorage` + `modelRegistry` (if you own credential/model lifecycle)\n - `model` or `modelPattern` (if deterministic model selection matters)\n - `settings` (if you need isolated/test config)\n\n## Session manager behavior (persistent vs in-memory)\n\n`AgentSession` always uses a `SessionManager`; behavior depends on which factory you use.\n\n### File-backed (default)\n\n```ts\nimport { createAgentSession, SessionManager } from \"@sayknow-cli/coding-agent\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.create(process.cwd()),\n});\n\nconsole.log(session.sessionFile); // absolute .jsonl path\n```\n\n- Persists conversation/messages/state deltas to session files.\n- Supports resume/open/list/fork workflows.\n- `session.sessionFile` is defined.\n\n### In-memory\n\n```ts\nimport { createAgentSession, SessionManager } from \"@sayknow-cli/coding-agent\";\n\nconst { session } = await createAgentSession({\n sessionManager: SessionManager.inMemory(),\n});\n\nconsole.log(session.sessionFile); // undefined\n```\n\n- No filesystem persistence.\n- Useful for tests, ephemeral workers, request-scoped agents.\n- Session methods still work, but persistence-specific behaviors (file resume/fork paths) are naturally limited.\n\n### Resume/open/list helpers\n\n```ts\nimport { SessionManager } from \"@sayknow-cli/coding-agent\";\n\nconst recent = await SessionManager.continueRecent(process.cwd());\nconst listed = await SessionManager.list(process.cwd());\nconst opened = listed[0] ? await SessionManager.open(listed[0].path) : null;\n```\n\n## Model and auth wiring\n\n`createAgentSession()` uses `ModelRegistry` + `AuthStorage` for model selection and API key resolution.\n\n### Explicit wiring\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n} from \"@sayknow-cli/coding-agent\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst available = modelRegistry.getAvailable();\nif (available.length === 0)\n throw new Error(\"No authenticated models available\");\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n model: available[0],\n thinkingLevel: \"medium\",\n sessionManager: SessionManager.inMemory(),\n});\n```\n\n### Selection order when `model` is omitted\n\nWhen no explicit `model`/`modelPattern` is provided:\n\n1. restore model from existing session (if restorable + key available)\n2. settings default model role (`default`)\n3. first available model with valid auth\n\nIf restore fails, `modelFallbackMessage` explains fallback.\n\n### Auth priority\n\n`AuthStorage.getApiKey(...)` resolves in this order:\n\n1. runtime override (`setRuntimeApiKey`)\n2. stored credentials in `agent.db`\n3. provider environment variables\n4. custom-provider resolver fallback (if configured)\n\n## Event subscription model\n\nSubscribe with `session.subscribe(listener)`; it returns an unsubscribe function.\n\n```ts\nconst unsubscribe = session.subscribe((event) => {\n switch (event.type) {\n case \"agent_start\":\n case \"turn_start\":\n case \"tool_execution_start\":\n break;\n case \"message_update\":\n if (event.assistantMessageEvent.type === \"text_delta\") {\n process.stdout.write(event.assistantMessageEvent.delta);\n }\n break;\n }\n});\n```\n\n`AgentSessionEvent` includes core `AgentEvent` plus session-level events:\n\n- `auto_compaction_start` / `auto_compaction_end`\n- `auto_retry_start` / `auto_retry_end`\n- `retry_fallback_applied` / `retry_fallback_succeeded`\n- `ttsr_triggered`\n- `todo_reminder` / `todo_auto_clear`\n- `irc_message`\n\n## Prompt lifecycle\n\n`session.prompt(text, options?)` is the primary entry point.\n\nBehavior:\n\n1. optional command/template expansion (`/` commands, custom commands, file slash commands, prompt templates)\n2. if currently streaming:\n - requires `streamingBehavior: \"steer\" | \"followUp\"`\n - queues instead of throwing work away\n3. if idle:\n - validates model + API key\n - appends user message\n - starts agent turn\n\nRelated APIs:\n\n- `sendUserMessage(content, { deliverAs? })`\n- `steer(text, images?)`\n- `followUp(text, images?)`\n- `sendCustomMessage({ customType, content, ... }, { deliverAs?, triggerTurn? })`\n- `abort()`\n\n## Tools integration\n\n### Built-ins and filtering\n\n- Built-ins come from `createTools(...)` and `BUILTIN_TOOLS`.\n- `toolNames` acts as an allowlist for built-ins.\n- Hidden tools (for example `yield`) are opt-in unless required by options.\n\n```ts\nconst { session } = await createAgentSession({\n toolNames: [\"read\", \"grep\", \"find\", \"write\"],\n requireYieldTool: true,\n});\n```\n\n### Runtime tool set changes\n\n`AgentSession` supports runtime activation updates:\n\n- `getActiveToolNames()`\n- `getAllToolNames()`\n- `setActiveToolsByName(names)`\n\nSystem prompt is rebuilt to reflect active tool changes.\n\n## Discovery helpers\n\nUse these when you want partial control without recreating internal discovery logic:\n\n- `discoverAuthStorage(agentDir?)`\n- `discoverContextFiles(cwd?, _agentDir?)`\n- `discoverPromptTemplates(cwd?, agentDir?)`\n- `buildSystemPrompt(options?)`\n\n## Subagent-oriented options\n\nFor SDK consumers building orchestrators (similar to task executor flow):\n\n- `outputSchema`: passes structured output expectation into tool context\n- `requireYieldTool`: forces `yield` tool inclusion\n- `taskDepth`: recursion-depth context for nested task sessions\n- `parentTaskPrefix`: artifact naming prefix for nested task outputs\n\nThese are optional for normal single-agent embedding.\n\n## `createAgentSession()` return value\n\n```ts\ntype CreateAgentSessionResult = {\n session: AgentSession;\n setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void;\n modelFallbackMessage?: string;\n lspServers?: Array<{\n name: string;\n status: \"ready\" | \"error\";\n fileTypes: string[];\n error?: string;\n }>;\n eventBus: EventBus;\n};\n```\n\nUse `setToolUIContext(...)` only if your embedder provides UI capabilities that tools should call into.\n\n## Startup performance\n\n`createAgentSession()` runs two background optimizations to overlap I/O with the rest of session setup:\n\n- **Model-host preconnect.** As soon as the model is resolved, the SDK fires a best-effort `fetch.preconnect(model.baseUrl)` so DNS + TCP + TLS + HTTP/2 to the provider's host happens in parallel with tool registry build, and system-prompt assembly. The first real `fetch(...)` then reuses the warm connection, saving 100–300 ms on transcontinental hops (e.g. residential IP → `api.anthropic.com`). Implementation lives in `preconnectModelHost()` in `packages/coding-agent/src/sdk.ts`. If `fetch.preconnect` is unavailable (non-Bun runtime) or the call throws, the optimization is silently skipped — never a hard dependency. Applies to every mode (interactive, print, RPC, ACP).\n- **Conditional LSP warmup.** Startup LSP servers (those returned by `discoverStartupLspServers(cwd)`) are only warmed when **all** of these hold:\n - `enableLsp !== false` on the session options, **and**\n - `options.hasUI === true` (interactive TUI), **and**\n - the `lsp.diagnosticsOnWrite` setting is enabled.\n\n Print / script / RPC / ACP invocations (`hasUI=false`) skip the warmup entirely: they don't render the warmup status indicator and typically finish before the language servers would stabilize, so warming them just spends CPU parsing big `initialize` responses concurrently with the LLM stream consumer and jitters perceived latency. Tools that actually need an LSP server still spin one up on demand through `getOrCreateClient()` — only the *startup* warmup is skipped. The returned `lspServers` field in `CreateAgentSessionResult` is therefore `undefined` (not an empty array) whenever the warmup branch was bypassed.\n\n## Minimal controlled embed example\n\n```ts\nimport {\n createAgentSession,\n discoverAuthStorage,\n ModelRegistry,\n SessionManager,\n Settings,\n} from \"@sayknow-cli/coding-agent\";\n\nconst authStorage = await discoverAuthStorage();\nconst modelRegistry = new ModelRegistry(authStorage);\nawait modelRegistry.refresh();\n\nconst settings = Settings.isolated({\n \"compaction.enabled\": true,\n \"retry.enabled\": true,\n});\n\nconst { session } = await createAgentSession({\n authStorage,\n modelRegistry,\n settings,\n sessionManager: SessionManager.inMemory(),\n toolNames: [\"read\", \"grep\", \"find\", \"edit\", \"write\"],\n enableLsp: true,\n});\n\nsession.subscribe((event) => {\n if (\n event.type === \"message_update\" &&\n event.assistantMessageEvent.type === \"text_delta\"\n ) {\n process.stdout.write(event.assistantMessageEvent.delta);\n }\n});\n\nawait session.prompt(\"Find all TODO comments in this repo and propose fixes.\");\nawait session.dispose();\n```\n",
|
|
@@ -105,7 +106,7 @@ export const EMBEDDED_DOCS: Readonly<Record<string, string>> = {
|
|
|
105
106
|
"tools/eval.md": "# eval\n\n> Execute Python or JavaScript code in persistent cell-based runtimes.\n\n> **Notice:** Do not shell out to `python -c`/`python -e`, `bun -e`, or `node -e` via the `bash` tool for ad-hoc code execution. Use this tool instead — it gives you persistent state across cells, structured `display()` output, image/JSON capture, and proper cancellation/timeout handling that one-shot `-e`/`-c` invocations cannot provide.\n\n## Source\n- Entry: `packages/coding-agent/src/tools/eval.ts`\n- Model-facing prompt: `packages/coding-agent/src/prompts/tools/eval.md`\n- Key collaborators:\n - `packages/coding-agent/src/eval/backend.ts` — backend execution contract\n - `packages/coding-agent/src/eval/js/index.ts` — JS backend adapter\n - `packages/coding-agent/src/eval/js/executor.ts` — JS execution + output sink\n - `packages/coding-agent/src/eval/js/context-manager.ts` — persistent VM contexts, prelude, tool bridge\n - `packages/coding-agent/src/eval/js/prelude.txt` — JS global helpers\n - `packages/coding-agent/src/eval/py/index.ts` — Python backend adapter\n - `packages/coding-agent/src/eval/py/executor.ts` — kernel session retention, reset, cleanup\n - `packages/coding-agent/src/eval/py/kernel.ts` — Jupyter gateway/kernel protocol, display capture\n - `packages/coding-agent/src/eval/py/prelude.py` — Python helper functions and status events\n - `packages/coding-agent/src/session/streaming-output.ts` — truncation, artifacts, streamed chunks\n - `docs/python-repl.md` — Python kernel/gateway internals\n\n## Inputs\n\nTool parameters are a JSON object with a single `cells` field — an ordered array of cell objects. Each cell is a structured record; there is no `*** Cell` header parsing, no language sniffing, and no implicit single-cell fallback. Cells run in array order; state persists within each language across cells and across tool calls.\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `cells` | `EvalCellInput[]` | Yes | Cells executed in order. At least one cell is required (`.min(1)`). |\n\nEach `EvalCellInput` (from `evalCellSchema` in `packages/coding-agent/src/tools/eval.ts`):\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `language` | `\"py\" \\| \"js\"` | Yes | Backend selector. `\"py\"` maps to the IPython/Jupyter kernel (`python` backend); `\"js\"` maps to the persistent JavaScript VM. |\n| `code` | `string` | Yes | Cell body, verbatim. JSON-encoded — embed newlines, quotes, and indentation directly; no fences, no headers. |\n| `title` | `string` | No | Short label rendered in the transcript (e.g. `\"imports\"`, `\"load config\"`). |\n| `timeout` | `integer` | No | Per-cell timeout in seconds, clamped to `1..600`. Defaults to 30 when omitted. |\n| `reset` | `boolean` | No | Wipe this cell's language kernel before running. Reset is per-language: a `py` cell's reset does not touch the JS VM and vice versa. Defaults to `false`. |\n\nMinimal example matching the live schema:\n\n```json\n{\n \"cells\": [\n { \"language\": \"py\", \"title\": \"imports\", \"timeout\": 10, \"code\": \"import json\\nfrom pathlib import Path\" },\n { \"language\": \"py\", \"title\": \"load config\", \"code\": \"data = json.loads(read('package.json'))\\ndisplay(data)\" },\n { \"language\": \"js\", \"title\": \"summary\", \"reset\": true, \"code\": \"const data = JSON.parse(await read('package.json'));\\ndisplay(data);\\nreturn data.name;\" }\n ]\n}\n```\n\n## Outputs\n\nFinal result from `EvalTool.execute()` is single-shot, but `onUpdate` streams partial text and `details` while cells run.\n\nReturned shape:\n\n- `content`: one text block containing combined cell output, or `(no text output)` / `(no output)` when only rich outputs exist.\n- `details` (`EvalToolDetails` from `packages/coding-agent/src/eval/types.ts`):\n - `cells`: per-cell code, status (`pending`/`running`/`complete`/`error`), output, duration, exit code, status events, markdown flag\n - `language`: first backend used\n - `languages`: distinct backends used, in first-use order\n - `jsonOutputs`: structured values emitted via `display(...)`\n - `images`: image payloads emitted by Python rich display or JS `display({ type: \"image\", ... })`\n - `statusEvents`: aggregated helper/tool status events\n - `notice`: backend fallback notice (currently unused; reserved for future per-cell notices)\n - `meta`: truncation metadata\n - `isError`: set on cell failure or cancellation\n\nRenderer behavior in `packages/coding-agent/src/tools/eval.ts`:\n\n- call preview renders each cell's `code` with syntax highlighting based on its declared `language`\n- result view renders each cell separately, including status, duration, and output\n- markdown outputs are rendered with the Markdown component instead of plain text\n- `jsonOutputs` render as a tree, collapsed or expanded depending on UI state\n- timeout / truncation notices render as dim metadata lines\n- images are carried in `details.images`; generic tool UI image handling renders them outside the text block\n\nSide-channel artifacts:\n\n- `session.allocateOutputArtifact?.(\"eval\")` may allocate an `artifact://...` backing store for spilled output.\n- Truncated output metadata points at that artifact when available.\n\n## Flow\n\n1. `EvalTool.execute()` in `packages/coding-agent/src/tools/eval.ts` receives `params.cells` already validated by the Zod schema — no string parsing step.\n2. For each cell, `execute()` maps `cell.language` to an `EvalLanguage` (`\"py\"` → `\"python\"`, `\"js\"` → `\"js\"`) and calls `resolveBackend(session, language)`:\n - `python` is gated on `eval.py !== false` and `pythonBackend.isAvailable(session)`.\n - `js` is gated on `eval.js !== false`.\n - A disabled or unavailable requested backend throws `ToolError`; there is no auto-fallback or sniffing.\n3. The tool allocates an `OutputSink`, a `TailBuffer`, per-cell result objects, and a `sessionAbortController`. `session.trackEvalExecution?.(...)` can wrap the whole run for external cancellation tracking.\n4. Cells execute sequentially. For each cell, `execute()`:\n - clamps `(cell.timeout ?? 30) * 1000` ms through `clampTimeout(\"eval\", ...)`\n - builds a combined abort signal from the tool signal, the timeout, and the session abort controller\n - marks the cell `running` and emits an update\n - calls the backend’s `execute()` with `cwd`, `sessionId`, `sessionFile`, `kernelOwnerId`, `deadlineMs`, `reset` (defaults to `false`), artifact info, and chunk callback\n5. JS cells dispatch through `packages/coding-agent/src/eval/js/index.ts` into `executeJs()`; Python cells dispatch through `packages/coding-agent/src/eval/py/index.ts` into `executePython()`.\n6. Backend text chunks stream into the shared `OutputSink`; rich outputs are accumulated separately as JSON, images, markdown markers, and status events.\n7. After each cell:\n - text output is trimmed and stored on that cell result\n - multi-cell runs prefix text with `[i/n]` and the optional title\n - cancellations return early with `isError: true` and a cell-specific abort message\n - non-zero exit codes return early with `isError: true` and a message naming the failed cell\n - later cells are skipped after the first error, but earlier cell state persists in the underlying runtime\n8. On success, the tool joins all cell outputs, synthesizes `(no text output)` or `(no output)` when needed, and attaches truncation metadata from `summarizeFinal()`.\n9. The renderer uses `details.cells`, `details.jsonOutputs`, and `details.statusEvents` to build notebook-style output. `mergeCallAndResult = true` and `inline = true`, so call and result render together in the transcript.\n\n## Modes / Variants\n\n### Backend selection\n\nBackend choice is **explicit per cell** — there is no auto-detection.\n\n- `language: \"py\"` → Python (IPython/Jupyter) backend\n- `language: \"js\"` → JavaScript VM backend\n\nIf the requested backend is disabled or unavailable, the tool throws `ToolError` for that cell. The caller chooses; the tool does not silently substitute.\n\n### JavaScript runtime\n\nImplemented in `packages/coding-agent/src/eval/js/context-manager.ts` and `packages/coding-agent/src/eval/js/prelude.txt`.\n\n- Persistent `vm.Context` instances keyed by `js:${sessionId}` in `vmContexts`\n- `reset: true` calls `resetVmContext(sessionKey)` before the cell executes\n- Top-level `await` and bare `return` are supported by wrapping code in an async IIFE when `wrapCode()` sees `await` or `return`\n- Top-level static `import ... from ...` and dynamic `import(...)` calls are routed through `rewriteImports()`, which sends them via `__skc_import__` so the specifier resolves against the session cwd\n- Module cache is busted for **local** imports between cells so edits to source files are picked up without restarting the runtime. `__skc_import__` deletes `require.cache[absPath]` before re-importing whenever the original specifier is a filesystem path: relative (`./x`, `../x`, `.`, `..`), POSIX-absolute (`/...`), home-prefixed (`~/...`), or Windows drive-letter (`C:\\...` / `C:/...`). Bare specifiers (`react`, `lodash/x`) and URL/scheme specifiers (`node:fs`, `file://...`, `https://...`) are left in cache so package identity stays stable across cells. The cache-bust only fires when the resolved target is an absolute path — unresolved bare-package fallbacks (`resolveImportSpecifier()` returning the original specifier) skip it.\n- The prelude installs globals:\n - `display`, `print`\n - `read`, `write`, `append`, `sort`, `uniq`, `counter`, `diff`, `tree`, `env`, `output`\n - `tool.<name>(args)` proxy for arbitrary session tool calls\n- JS helpers are async because they cross the VM/tool boundary\n- `display(value)` behavior:\n - plain objects/arrays become JSON outputs\n - `{ type: \"image\", data, mimeType }` becomes an image output\n - scalars become text\n- The VM exposes a restricted `process` subset plus `Buffer`, `fetch`, `Blob`, `File`, `Headers`, `Request`, `Response`, `fs`, `require`, and browser-style globals\n- Per-session VM runs are serialized with `runQueued()`\n\n### Python runtime\n\nImplemented in `packages/coding-agent/src/eval/py/executor.ts`, `packages/coding-agent/src/eval/py/kernel.ts`, and `packages/coding-agent/src/eval/py/prelude.py`. See `docs/python-repl.md` for gateway and kernel details.\n\n- Default mode is retained `session` kernels keyed by `python:${sessionId}`\n- Optional `python.kernelMode = \"per-call\"` creates a fresh kernel for each cell and shuts it down afterward\n- `reset: true` disposes the retained kernel for that session before the cell runs; later Python cells in the same tool call reuse the fresh kernel\n- Startup path:\n - availability check\n - create/connect kernel\n - initialize cwd / env / `sys.path`\n - execute `PYTHON_PRELUDE`\n- Python cells run inside IPython/Jupyter, so top-level `await` works; the prompt warns not to use `asyncio.run(...)`\n- The Python prelude defines synchronous helpers with the same surface as JS (except `tool.<name>` exists only in JS)\n- `display(value)` wraps dict/list/tuple values in `IPython.display.JSON`; rich display MIME bundles are preserved\n- Kernel `display_data` / `execute_result` messages map to:\n - `application/x-skc-status` → status event\n - `image/png` → image output\n - `application/json` → JSON output\n - `text/markdown` → markdown output\n - `text/plain` → text output\n - `text/html` → HTML converted to markdown with `htmlToBasicMarkdown()`\n- Interactive stdin is rejected: `input_request` sends an empty reply, marks `stdinRequested`, and the executor returns exit code `1`\n\n### Multi-language call behavior\n\nA single tool call can mix Python and JS cells. Persistence is per language runtime:\n\n- `reset: true` on a Python cell does not touch JS state\n- `reset: true` on a JS cell does not touch Python state\n- each backend keeps its own retained session keyed from the same session-derived ID\n\n## Side Effects\n\n- Filesystem\n - JS/Python prelude helpers can read, write, append, diff, and traverse files under the session cwd or absolute paths.\n - Output may spill to an artifact file via `OutputSink`.\n- Network\n - Python backend speaks NDJSON to a local `python3` subprocess over stdin/stdout (no network).\n - JS runtime exposes `fetch` and `tool.<name>()`; those tools may perform additional network I/O.\n- Subprocesses / native bindings\n - Python availability check runs `<python> -c ...`.\n - Python backend spawns one `python -u runner.py` subprocess per kernel; cancellation sends `SIGINT`. Details in `docs/python-repl.md`.\n- Session state\n - `session.assertEvalExecutionAllowed?.()` can block execution.\n - `session.trackEvalExecution?.(...)` can register cancellable eval work.\n - `session.getSessionFile?.()` and `session.getEvalKernelOwnerId?.()` influence kernel reuse and artifact lookup.\n - JS VM contexts persist in `vmContexts` across eval calls until reset/disposal.\n - Python retained kernels persist in `kernelSessions` until reset, eviction, idle cleanup, or owner cleanup.\n- User-visible prompts / interactive UI\n - none; stdin requests are rejected programmatically\n- Background work / cancellation\n - Python retained kernels have heartbeat and idle cleanup timers.\n - Cancellation interrupts a running Python kernel and aborts JS promise waits.\n\n## Limits & Caps\n\n- Per-cell timeout default: 30s (applied when `timeout` is omitted in `EvalTool.execute()`; clamped through `TOOL_TIMEOUTS.eval.default` in `packages/coding-agent/src/tools/tool-timeouts.ts`)\n- Schema-level `timeout` range: integer `1..600` seconds (enforced by Zod on the cell schema)\n- Timeout clamp at runtime: 1s minimum, 600s maximum (`TOOL_TIMEOUTS.eval` in `packages/coding-agent/src/tools/tool-timeouts.ts`)\n- Transcript code/output preview: 10 lines by default (`EVAL_DEFAULT_PREVIEW_LINES` in `packages/coding-agent/src/tools/eval.ts`)\n- Output truncation window: 50KB default (`DEFAULT_MAX_BYTES` in `packages/coding-agent/src/session/streaming-output.ts`)\n- Output line cap inside truncation helpers: 3000 lines (`DEFAULT_MAX_LINES` in `packages/coding-agent/src/session/streaming-output.ts`)\n- Streaming tail buffer for live updates: `DEFAULT_MAX_BYTES * 2` = 100KB (`packages/coding-agent/src/tools/eval.ts`)\n- Python retained kernel idle timeout: 5 minutes (`IDLE_TIMEOUT_MS` in `packages/coding-agent/src/eval/py/executor.ts`)\n- Python retained kernel cap: 4 sessions (`MAX_KERNEL_SESSIONS` in `packages/coding-agent/src/eval/py/executor.ts`)\n- Python retained kernel cleanup sweep: every 30s (`CLEANUP_INTERVAL_MS` in `packages/coding-agent/src/eval/py/executor.ts`)\n- Python owner-cleanup shutdown wait: 2000ms (`OWNER_CLEANUP_KERNEL_SHUTDOWN_TIMEOUT_MS` in `packages/coding-agent/src/eval/py/executor.ts`)\n- Python heartbeat interval: 5s (`ensureKernelHeartbeat()` in `packages/coding-agent/src/eval/py/executor.ts`)\n- Python external gateway availability check timeout: 5s (`AbortSignal.timeout(5000)` in `packages/coding-agent/src/eval/py/kernel.ts`)\n- Python auto-restart budget: one restart per retained session before hard failure (`restartCount > 1` in `packages/coding-agent/src/eval/py/executor.ts`)\n\n## Errors\n\n- Zod validation rejects malformed `cells` arrays before `execute()` runs (missing `language`/`code`, out-of-range `timeout`, empty `cells`).\n- Missing session without proxy executor throws `ToolError(\"Eval tool requires a session when not using proxy executor\")`.\n- Disabled/unavailable backends throw `ToolError` from `resolveBackend()`:\n - `eval.py = false` and a `py` cell is requested\n - `eval.js = false` and a `js` cell is requested\n - Python kernel unavailable and a `py` cell is requested\n- JS runtime exceptions are converted into text output plus `exitCode: 1`; cancellations return `cancelled: true` and may append `Command timed out`.\n- Python execution errors from the kernel become text output and `exitCode: 1`; later cells are skipped.\n- Python stdin requests are treated as errors with the message `Kernel requested stdin; interactive input is not supported.`\n- Cancellation is returned, not thrown, once backend execution has started. The tool formats it as a cell failure and sets `details.isError = true`.\n- If output truncates, the tool still succeeds; truncation is surfaced through `details.meta` and artifact-backed full output when available.\n\n## Notes\n\n- Backend selection is now strictly explicit per cell: `language` must be `\"py\"` or `\"js\"`. The previous `*** Cell` header parser, the `eval.lark` constrained grammar, and the sniffer-based fallback have all been removed.\n- `EvalTool.customFormat` no longer exists. Tool calls flow through the standard JSON schema; there is no Lark-constrained sampling path.\n- `tool.<name>()` exists only in JS. Python prelude helpers do not call back into the full tool registry.\n- JS helper paths reject protocol URIs (`://`) in `resolvePath()`; the JS prelude is filesystem-only unless the code calls `tool.read(...)` or another tool explicitly.\n- Python helper `output(...)` depends on `SKC_SESSION_FILE`; it fails outside a session-backed run.\n- `display()` can produce text and structured outputs from the same value; the renderer prefers markdown over `text/plain` when both exist.\n- JS static imports are rewritten only at top level. Nested imports stay invalid and surface normal JS syntax/runtime errors.\n- `EvalTool` is `concurrency = \"exclusive\"`, so eval calls do not overlap within a session.\n- The tool description shown to the model is templated by backend availability (`getEvalToolDescription()`); if Python is unavailable, the prompt omits Python-specific instructions.\n",
|
|
106
107
|
"tools/find.md": "# find\n\n> Find filesystem paths by glob; use `search` when you need content matches instead of path matches.\n\n## Source\n- Entry: `packages/coding-agent/src/tools/find.ts`\n- Model-facing prompt: `packages/coding-agent/src/prompts/tools/find.md`\n- Key collaborators:\n - `packages/coding-agent/src/tools/path-utils.ts` — normalize inputs; split base path vs glob.\n - `packages/coding-agent/src/tools/list-limit.ts` — apply result-count caps.\n - `packages/coding-agent/src/session/streaming-output.ts` — truncate text output at byte cap.\n - `packages/coding-agent/src/tools/tool-result.ts` — build `content` and `details.meta`.\n - `packages/coding-agent/src/tools/output-meta.ts` — encode limit / truncation metadata.\n - `packages/coding-agent/src/tools/tool-errors.ts` — map user-facing tool errors.\n - `packages/coding-agent/src/tools/index.ts` — register the built-in local implementation.\n\n## Inputs\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `paths` | `string[]` | Yes | One or more globs, files, or directories. Empty strings are rejected. Multiple entries may be merged into one brace-union search when their base paths can be resolved together. |\n| `hidden` | `boolean` | No | Whether hidden files are included. Defaults to `true` (`hidden ?? true`). |\n| `limit` | `number` | No | Max returned paths. Defaults to `1000`. Must be a finite positive number; non-integers are floored. |\n\n## Outputs\nThe tool returns a single text block plus structured `details`.\n\n- Success text: newline-delimited paths, one per line, relative to the session cwd when possible; absolute when outside cwd. Exact file inputs return that file path as one line.\n- Empty result text: `No files found matching pattern`.\n- Multi-path partial miss: appends `Skipped missing paths: ...` after the result block, or after the empty-result line.\n- `details` may include:\n - `scopePath`: display form of the searched root or merged roots.\n - `fileCount`: number of paths returned after result limiting.\n - `files`: returned paths as an array.\n - `truncated`: whether result count or byte truncation occurred.\n - `resultLimitReached`: reached result limit.\n - `missingPaths`: skipped missing inputs in multi-path calls.\n - `truncation` / `meta.limits`: structured truncation and limit metadata for renderers.\n- Streaming: when the runtime supplies `onUpdate`, the local implementation emits incremental newline-delimited text snapshots during globbing, throttled to 200 ms.\n\n## Flow\n1. `FindTool.execute()` normalizes each `paths` entry with `normalizePathLikeInput()` and `/\\\\/g -> \"/\"` (`packages/coding-agent/src/tools/find.ts`). Empty normalized entries fail with `` `paths` must contain non-empty globs or paths ``.\n2. For multi-path local calls, `partitionExistingPaths(..., parseFindPattern)` (`packages/coding-agent/src/tools/path-utils.ts`) stats each base path. Missing entries are skipped; if all are missing, the tool throws `Path not found: ...`. Single missing paths still hard-fail.\n3. The tool tries `resolveExplicitFindPatterns()` to merge multiple inputs into one search rooted at a common base path. If that does not apply, it parses one input with `parseFindPattern()`.\n4. `parseFindPattern()` determines `(basePath, globPattern, hasGlob)`:\n - no glob chars (`*`, `?`, `[`, `{`) => search that path with implicit `**/*`.\n - glob in the first segment => search from `.` and, unless the pattern already starts with `**/`, prefix it with `**/`.\n - glob later in the path => split at the first glob-bearing segment.\n5. `resolveToCwd()` converts the base path to an absolute path under the session cwd. A resolved `/` is rejected with `Searching from root directory '/' is not allowed`.\n6. `limit` is defaulted to `DEFAULT_LIMIT` (`1000`) and validated as a positive finite integer. `hidden` defaults to `true`. The tool also creates a 5 s timeout via `AbortSignal.timeout(GLOB_TIMEOUT_MS)`.\n7. Execution then branches:\n - **Custom operations branch**: if `FindToolOptions.operations.glob` exists, the tool checks existence with `operations.exists()`, short-circuits exact-file inputs via `operations.stat()` when available, then calls `operations.glob(globPattern, searchPath, { ignore: [\"**/node_modules/**\", \"**/.git/**\"], limit })`.\n - **Built-in local branch**: the tool stats `searchPath`. Exact-file inputs return immediately. Directory inputs call `natives.glob()` with `fileType: File`, `hidden`, `maxResults: limit`, `sortByMtime: true`, `gitignore: true`, and the combined abort signal.\n8. In the local branch, optional `onMatch` callbacks convert each match to a cwd-relative display path and emit throttled progress updates.\n9. After native glob returns, JS sorts `result.matches` by `mtime` descending (`(b.mtime ?? 0) - (a.mtime ?? 0)`) before formatting paths.\n10. `buildResult()` applies `applyListLimit()` to cap the array again at `limit`, joins paths with `\\n`, then runs `truncateHead()` with `maxLines: Number.MAX_SAFE_INTEGER`. In practice this leaves the 50 KB byte cap in place while disabling the default 3000-line cap.\n11. `toolResult()` packages text plus `details`, and records result-limit / truncation metadata for renderers.\n\n## Modes / Variants\n- **Exact file path**: if the parsed input has no glob and the resolved path stats as a file, output is that one path.\n- **Directory path**: if the parsed input has no glob and stats as a directory, the tool searches it with implicit `**/*`.\n- **Single glob path**: one input parsed by `parseFindPattern()`.\n- **Merged multi-path search**: multiple inputs resolved by `resolveExplicitFindPatterns()` into one brace-union glob rooted at a common base path.\n- **Partial multi-path search with missing inputs**: local multi-path calls skip missing base paths and surface them as `missingPaths` / `Skipped missing paths: ...`.\n- **Custom delegated search**: uses injected `FindOperations` instead of local fs + native glob.\n\n## Side Effects\n- Filesystem\n - Stats the resolved base path, and in local multi-path mode stats every candidate base path up front.\n - Does not write files.\n- Subprocesses / native bindings\n - Built-in local mode calls the native `@sayknow-cli/natives` glob implementation.\n- Session state (transcript, memory, jobs, checkpoints, registries)\n - Emits structured progress updates when `onUpdate` is provided.\n - Adds truncation / limit metadata to the tool result.\n- Background work / cancellation\n - Local globbing is cancellable through the caller abort signal plus an internal 5 s timeout.\n\n## Limits & Caps\n- Default result limit: `1000` (`DEFAULT_LIMIT` in `packages/coding-agent/src/tools/find.ts`).\n- Local glob timeout: `5000` ms (`GLOB_TIMEOUT_MS` in `packages/coding-agent/src/tools/find.ts`).\n- Output byte cap: `50 * 1024` bytes (`DEFAULT_MAX_BYTES` in `packages/coding-agent/src/session/streaming-output.ts`).\n- Default generic line cap in `truncateHead()` is `3000`, but `find` overrides `maxLines` to `Number.MAX_SAFE_INTEGER`, so byte size — not line count — is the practical output truncation cap.\n- Streaming update throttle: `200` ms between `onUpdate` emissions.\n- Sort order: most recent `mtime` first in the built-in local branch and promised in the prompt. The tool re-sorts in JS even though native glob receives `sortByMtime: true` so native code can still stop early at `maxResults`.\n\n## Errors\n- User-facing `ToolError`s from `FindTool.execute()` include:\n - `` `paths` must contain non-empty globs or paths ``\n - `Path not found: ...`\n - `Searching from root directory '/' is not allowed`\n - `Limit must be a positive number`\n - `Path is not a directory: ...`\n - `find timed out after 5s`\n- If the caller aborts, the local branch converts `AbortError` into `ToolAbortError`.\n- Non-`ENOENT` stat failures and other unexpected errors are rethrown.\n- Empty matches are not errors; they return the no-files text result.\n\n## Notes\n- Reach for `find` for filename / path discovery. Reach for `search` when the selection criterion is file contents or regex matches; `search` takes a `pattern` and returns anchored content matches, while `find` only returns matching paths (`packages/coding-agent/src/prompts/tools/find.md`, `packages/coding-agent/src/prompts/tools/search.md`).\n- Bare top-level globs are made recursive. `*.ts` is parsed as base `.` plus glob `**/*.ts`; `src/*.ts` stays rooted at `src` with a non-recursive `*.ts` segment; `src/**/*.ts` preserves explicit recursion.\n- `.gitignore` is always enabled in the built-in local branch (`gitignore: true`). There is no model-facing flag to disable it.\n- `hidden` defaults to `true`; hidden-file exclusion is opt-out, not opt-in.\n- Multi-path missing-input tolerance only applies in the built-in local branch. The custom-operations branch hard-fails the first missing `searchPath` it checks.\n- The custom `FindOperations.glob()` hook receives `ignore` and `limit`, but not the `hidden` flag or an explicit `.gitignore` toggle. A remote delegate must account for that itself if it wants parity with the local branch.\n- Built-in local globbing asks the native layer for `fileType: File`, so recursive directory searches yield files, not directories. Directory outputs are only possible through exact-path passthrough or custom delegates that return them.\n",
|
|
107
108
|
"tools/github.md": "# github\n\n> Dispatch GitHub CLI operations for repositories, issues, pull requests, search, and Actions run watching.\n\n## Source\n- Entry: `packages/coding-agent/src/tools/gh.ts`\n- Model-facing prompt: `packages/coding-agent/src/prompts/tools/github.md`\n- Key collaborators:\n - `packages/coding-agent/src/tools/gh-format.ts` — shorten commit SHAs for summaries.\n - `packages/coding-agent/src/tools/gh-renderer.ts` — TUI rendering, especially `run_watch` live/result views.\n - `packages/coding-agent/src/utils/git.ts` — `gh`/`git` process wrappers, repo locking, branch config writes.\n - `packages/utils/src/dirs.ts` — base directory for dedicated PR worktrees.\n - `packages/coding-agent/src/sdk.ts` — session artifact allocation hook.\n - `packages/coding-agent/src/session/artifacts.ts` — artifact filename format `<id>.<toolType>.log`.\n\n## Inputs\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `op` | `\"repo_view\" \\| \"pr_create\" \\| \"pr_checkout\" \\| \"pr_push\" \\| \"search_issues\" \\| \"search_prs\" \\| \"search_code\" \\| \"search_commits\" \\| \"search_repos\" \\| \"run_watch\"` | Yes | Dispatch selector. `GithubTool.execute()` switches only on this field. |\n| `repo` | `string` | No | `owner/repo` override. Ignored when the identifier argument is already a full GitHub URL. For `search_issues`/`search_prs`/`search_code`/`search_commits`, defaults to the current checkout's `owner/repo` when omitted (skipped when the query already contains a `repo:`/`org:`/`user:`/`owner:` qualifier or when current-repo resolution fails). Required in practice when `gh` cannot infer repo context from the current checkout. |\n| `branch` | `string` | No | Used by `repo_view`, `pr_push`, and `run_watch`. `run_watch` falls back to current git branch when `run` is omitted; `pr_push` falls back to current branch. |\n| `pr` | `string \\| string[]` | No | Used by `pr_checkout`. Each item may be a PR number, branch name, or GitHub PR URL. Array form enables batching. Omitted means current branch PR. |\n| `force` | `boolean` | No | Used only by `pr_checkout`. Defaults to `false`; allows resetting an existing `pr-<number>` local branch to the PR head commit. |\n| `forceWithLease` | `boolean` | No | Used only by `pr_push`; passed through to git push. |\n| `title` | `string` | No | Used only by `pr_create`. Required unless `fill` is `true`. |\n| `body` | `string` | No | Used only by `pr_create`. Mutually exclusive with `fill`. Empty/omitted body becomes `--body \"\"` to suppress the interactive editor. Non-empty body is written to a temp file and passed as `--body-file`. |\n| `base` | `string` | No | Used only by `pr_create`; passed as `--base`. |\n| `head` | `string` | No | Used only by `pr_create`; passed as `--head`. |\n| `draft` | `boolean` | No | Used only by `pr_create`. Defaults to `false`. |\n| `fill` | `boolean` | No | Used only by `pr_create`. Defaults to `false`. Mutually exclusive with `title` and `body`. |\n| `reviewer` | `string[]` | No | Used only by `pr_create`; each entry becomes `--reviewer`. |\n| `assignee` | `string[]` | No | Used only by `pr_create`; each entry becomes `--assignee`. |\n| `label` | `string[]` | No | Used only by `pr_create`; each entry becomes `--label`. |\n| `query` | `string` | No | Used by all `search_*` ops. Required there. |\n| `limit` | `number` | No | Used by all `search_*` ops. Defaults to `10`, floored, clamped to `50`, and must be `> 0`. |\n| `run` | `string` | No | Used only by `run_watch`. Must be a numeric run ID or full GitHub Actions run URL. |\n| `tail` | `number` | No | Used only by `run_watch`. Defaults to `15`, floored, clamped to `200`, and must be `> 0`. |\n\n## Outputs\nThe tool returns a single text result built by `buildTextResult()` in `packages/coding-agent/src/tools/gh.ts`.\n\n- `content`: one text block. Multi-item ops join sections with blank lines and `---` separators.\n- `sourceUrl`: set for single repo/PR/run results when a canonical URL is known.\n- `details`: optional structured metadata used by the TUI renderer.\n - Common fields: `artifactId`, `repo`, `branch`, `worktreePath`, `remote`, `remoteBranch`, `headSha`, `runId`, `runIds`, `status`, `conclusion`, `failedJobs`.\n - `pr_checkout` adds `checkouts: GhPrCheckoutSummary[]`.\n - `run_watch` adds `watch: GhRunWatchViewDetails`, which drives the custom live/result renderer in `packages/coding-agent/src/tools/gh-renderer.ts`.\n- Artifact trailer: when `artifactId` is present, the text body gets an appended line like `Full failed-job logs: artifact://<id>`.\n - `run_watch` allocates artifacts with `session.allocateOutputArtifact(\"github\")`; persistent sessions therefore save failed-log bodies as `<artifact-dir>/<id>.github.log`.\n\n`run_watch` is the only streaming op. It emits `onUpdate` snapshots while polling, then returns one final text result.\n\n## Flow\n1. `GithubTool.createIf()` exposes the tool only when `git.github.available()` finds `gh` on `PATH`.\n2. `GithubTool.execute()` wraps dispatch in `untilAborted()` and switches on `params.op`.\n3. Each op normalizes optional strings, arrays, booleans, and numeric caps locally in `packages/coding-agent/src/tools/gh.ts`.\n4. CLI execution goes through `git.github.run/json/text()` in `packages/coding-agent/src/utils/git.ts`:\n - spawns `gh ...` with `Bun.spawn()`;\n - trims stdout/stderr unless `trimOutput: false`;\n - maps common auth/repo-context failures into tool-facing `ToolError` messages;\n - `json()` rejects empty or invalid JSON.\n5. Read-style ops (`repo_view`, `search_*`) fetch JSON and format Markdown-like text summaries. Single-issue and single-PR views were moved out of the tool and now resolve through the `issue://` / `pr://` internal URL schemes, which share the same SQLite cache.\n7. PR diffs moved out of the tool. `pr://<N>/diff` lists changed files, `pr://<N>/diff/<i>` slices a single file, and `pr://<N>/diff/all` returns the full unified diff — see `docs/tools/read.md`. All three variants share one `gh pr diff` invocation through the `pr-diff` cache row.\n8. `pr_checkout` resolves PR metadata first, then enters `git.withRepoLock()` before any git mutation so parallel checkout calls for the same primary repo do not race on shared `.git` state.\n9. `pr_push` reads PR head metadata back from git branch config, derives a refspec, then pushes with `git.push()`.\n10. `pr_create` shells out once, then best-effort re-reads the created PR for a richer summary.\n11. `run_watch` chooses either run mode (`run` supplied) or commit mode (`run` omitted), polls GitHub Actions APIs every 3 seconds, emits streaming updates, and may save a full failed-log artifact before returning.\n12. Final text goes through `toolResult().text(...)`; if `session.allocateOutputArtifact()` returns a slot, failed-log text is persisted with `Bun.write()`.\n\n## Modes / Variants\n\n### `repo_view`\n\n| Aspect | Value |\n| --- | --- |\n| Required fields | `op` |\n| Optional fields | `repo`, `branch` |\n| `gh` command | `gh repo view [<repo>] [--branch <branch>] --json <GH_REPO_FIELDS>` |\n| Batching | None |\n| Output | `# <owner/repo>` header, description, URL, default branch, requested branch, visibility, permission, primary language, stars, forks, archive/fork flags, updated timestamp, homepage, topics. `sourceUrl = data.url`. |\n\nIf `repo` is omitted, `gh` repository resolution is used.\n\nSingle-issue and single-PR reads live in the `issue://<N>` / `pr://<N>` URL schemes (see `docs/tools/read.md`). They share `~/.skc/cache/github-cache.db` (override via `SKC_GITHUB_CACHE_DB`) and the `github.cache.softTtlSec` / `github.cache.hardTtlSec` / `github.cache.enabled` settings. The cache retains rendered Markdown plus the raw JSON payload returned by `gh`, including private bodies, comments, reviews, and review comments when comments are enabled; rows are scoped by the local GitHub credential fingerprint. Root and repo-scoped reads (`issue://`, `pr://owner/repo`) issue a live `gh issue list` / `gh pr list` for browsing; query params `state`, `limit`, `author`, `label` pass through to `gh` (`issue://` accepts `state=open|closed|all`; `pr://` also accepts `merged`). PR diffs ride the same cache under `pr://<N>/diff[/…]`: the listing, full diff, and per-file slices all share one `pr-diff` row keyed by repo and PR number.\n\n### `pr_create`\n\n| Aspect | Value |\n| --- | --- |\n| Required fields | `op` plus either `fill=true` or `title` |\n| Optional fields | `repo`, `title`, `body`, `base`, `head`, `draft`, `fill`, `reviewer[]`, `assignee[]`, `label[]` |\n| `gh` command | `gh pr create ...` with flags assembled from provided fields |\n| Batching | None |\n| Output | `# Created Pull Request ...` summary with URL, state, draft flag, base/head, author, created time, labels, optional body. `sourceUrl` is the created PR URL. |\n\nBranches:\n- `fill && (title || body !== undefined)` throws.\n- Non-empty `body` is written under a temp dir `gh-pr-body-*` in `os.tmpdir()`, passed as `--body-file`, then removed in `finally`.\n- After creation, the tool parses the returned URL and best-effort runs `gh pr view <number> --repo <repo> --json <GH_PR_FIELDS_NO_COMMENTS>`; failures there are swallowed.\n\n### `pr_checkout`\n\n| Aspect | Value |\n| --- | --- |\n| Required fields | `op` |\n| Optional fields | `repo`, `pr`, `force` |\n| `gh` command | For each requested PR: `gh pr view [<pr>] [--repo <repo>] --json <GH_PR_CHECKOUT_FIELDS>`; cross-repo PRs may also call `gh repo view <headRepository> --json <GH_REPO_CLONE_FIELDS>`. |\n| Batching | Yes. `pr` may be `string[]`; each PR is resolved in parallel, but git mutations are serialized per primary repo by `git.withRepoLock()`. |\n| Output | Single PR: checkout/worktree summary plus `details.repo`, `details.branch`, `details.worktreePath`, `details.remote`, `details.remoteBranch`, `details.checkouts`. Batched: `# <n> Pull Request Worktrees (...)` plus one section per PR and aggregated `details.checkouts`. |\n\nWorktree and metadata behavior:\n- Local branch name is always `pr-<number>`.\n- Worktree path is `path.join(getWorktreesDir(), encodeRepoPathForFilesystem(primaryRepoRoot), localBranch)`, where `getWorktreesDir()` is `~/.skc/wt`; effective path is `~/.skc/wt/<encoded-primary-repo-root>/pr-<number>`.\n- Existing worktree detection is by branch ref `refs/heads/pr-<number>` from `git.worktree.list()`.\n- New worktree creation calls `git.worktree.add(repoRoot, finalWorktreePath, localBranch, { signal })` after verifying the path is neither already registered nor already present on disk.\n- For same-repo PRs, remote is `origin`. For cross-repo PRs, the tool resolves a clone URL for the head repo, reuses an existing remote with the same URL when possible, or creates `fork-<owner>` / `fork-<owner>-<n>`.\n- The branch push metadata is persisted with `git config` under the repository's shared `.git/config` as:\n - `branch.pr-<number>.remote`\n - `branch.pr-<number>.merge`\n - `branch.pr-<number>.pushRemote`\n - `branch.pr-<number>.skcPrHeadRef`\n - `branch.pr-<number>.skcPrUrl`\n - `branch.pr-<number>.skcPrIsCrossRepository`\n - `branch.pr-<number>.skcPrMaintainerCanModify`\n- If `refs/heads/pr-<number>` already exists at a different commit, checkout fails unless `force=true`, in which case `git branch --force` resets it to the fetched PR head.\n- If a matching worktree already exists, the tool reuses it and reports `reused: true`.\n\n### `pr_push`\n\n| Aspect | Value |\n| --- | --- |\n| Required fields | `op` |\n| Optional fields | `branch`, `forceWithLease` |\n| `gh` command | None. This path uses git, not `gh`. |\n| Batching | None |\n| Output | `# Pushed Pull Request Branch` summary with local branch, remote, remote branch, remote URL, PR URL, and force-with-lease flag. `sourceUrl = prUrl` when known. |\n\nPush target resolution reads the `branch.<name>.skcPrHeadRef`, `pushRemote`/`remote`, `skcPrUrl`, `skcPrMaintainerCanModify`, and `skcPrIsCrossRepository` git-config keys written by `pr_checkout`. If the current checked-out branch matches the target branch, the source ref is `HEAD`; otherwise it pushes `refs/heads/<branch>`. The refspec is `HEAD:refs/heads/<headRef>` or `refs/heads/<branch>:refs/heads/<headRef>`.\n\n### `search_issues`\n\n| Aspect | Value |\n| --- | --- |\n| Required fields | `op`, `query` |\n| Optional fields | `repo`, `limit` |\n| `gh` command | `gh api -X GET /search/issues -f q=\"<query> [repo:<repo>] is:issue\" -F per_page=<limit>` |\n| Batching | None |\n| Output | `# GitHub issues search`, echoed query, optional repo, result count, then one bullet per issue with repo/state/author/labels/timestamps/URL. |\n\n`repo` defaults to the current checkout's `owner/repo` via `resolveSearchRepoScope()` when omitted. The default is suppressed when the query already contains a leading `repo:`/`org:`/`user:`/`owner:` qualifier or when `gh repo view` fails to resolve the current checkout (e.g. outside a github remote).\n\n### `search_prs`\n\n| Aspect | Value |\n| --- | --- |\n| Required fields | `op`, `query` |\n| Optional fields | `repo`, `limit` |\n| `gh` command | `gh api -X GET /search/issues -f q=\"<query> [repo:<repo>] is:pr\" -F per_page=<limit>` |\n| Batching | None |\n| Output | Same shape as `search_issues`, labeled as pull requests. |\n\n`repo` defaults to the current checkout's `owner/repo` as in `search_issues`.\n\n### `search_code`\n\n| Aspect | Value |\n| --- | --- |\n| Required fields | `op`, `query` |\n| Optional fields | `repo`, `limit` |\n| `gh` command | `gh api -X GET /search/code -f q=\"<query> [repo:<repo>]\" -F per_page=<limit> -H \"Accept: application/vnd.github.text-match+json\"` |\n| Batching | None |\n| Output | `# GitHub code search`, result count, then one bullet per match with path, repo, short commit SHA, URL, and first normalized text-match fragment line when present. |\n\n`repo` defaults to the current checkout's `owner/repo` as in `search_issues`.\n\n### `search_commits`\n\n| Aspect | Value |\n| --- | --- |\n| Required fields | `op`, `query` |\n| Optional fields | `repo`, `limit` |\n| `gh` command | `gh api -X GET /search/commits -f q=\"<query> [repo:<repo>]\" -F per_page=<limit>` |\n| Batching | None |\n| Output | `# GitHub commits search`, result count, then one bullet per commit: short SHA + first commit-message line, repo, author, date, URL. |\n\n`repo` defaults to the current checkout's `owner/repo` as in `search_issues`.\n\n### `search_repos`\n\n| Aspect | Value |\n| --- | --- |\n| Required fields | `op`, `query` |\n| Optional fields | `limit` |\n| `gh` command | `gh api -X GET /search/repositories -f q=\"<query>\" -F per_page=<limit>` |\n| Batching | None |\n| Output | `# GitHub repositories search`, result count, then one bullet per repo with first description line, language, stars, forks, open issues, visibility, archive/fork flags, updated time, URL. |\n\n`repo` is intentionally not used for this op.\n\n### `run_watch`\n\n| Aspect | Value |\n| --- | --- |\n| Required fields | `op` |\n| Optional fields | `repo`, `branch`, `run`, `tail` |\n| `gh` command | Repo resolution: `gh repo view --json nameWithOwner -q .nameWithOwner` when `repo` and run URL repo are both absent. Single-run mode uses `gh api --method GET /repos/<repo>/actions/runs/<runId>` and `gh api --method GET /repos/<repo>/actions/runs/<runId>/jobs`. Commit mode uses `gh api --method GET /repos/<repo>/branches/<branch>`, `gh api --method GET /repos/<repo>/actions/runs`, `gh api --method GET /repos/<repo>/actions/runs/<runId>/jobs`, and `gh api /repos/<repo>/actions/jobs/<jobId>/logs` for failed jobs. |\n| Batching | Implicit batching only in commit mode: all workflow runs for one commit are tracked together. |\n| Output | Streaming watch snapshots via `onUpdate`, then a final text report. On failure, appends `Full failed-job logs: artifact://<id>` and sets `details.artifactId`. |\n\nWatch flow:\n- `run` parsing accepts either a decimal run ID or a full run URL. URL repo must match explicit `repo` when both are given.\n- Poll interval is fixed at 3 seconds (`RUN_WATCH_INTERVAL_DEFAULT`).\n- Failure grace period is fixed at 5 seconds (`RUN_WATCH_GRACE_DEFAULT`). When any failed job appears before completion, the tool emits a note, waits once, re-fetches state, then collects logs so concurrent failures are included.\n- Failed-job logs are fetched with `gh api /repos/<repo>/actions/jobs/<jobId>/logs` via `git.github.run()`, not `json()`. Non-zero exit leaves `available: false` instead of failing the whole watch.\n- Inline result includes only the last `tail` lines per failed job. The saved artifact contains full logs (`mode: \"full\"`).\n- In commit mode, success is intentionally double-checked: once all known runs are successful, the tool waits one more poll interval and succeeds only if the set of run IDs is unchanged. This avoids returning before late workflow runs appear for the same commit.\n- `details.watch` drives a specialized renderer in `packages/coding-agent/src/tools/gh-renderer.ts`; non-watch results fall back to generic text rendering.\n\n## Side Effects\n- Filesystem\n - `pr_create` may create a temp dir under `os.tmpdir()` named `gh-pr-body-*`, write `body.md`, then remove the dir in `finally`.\n - `pr_checkout` may create directories under `~/.skc/wt/<encoded-primary-repo-root>/` and add git worktrees there.\n - `run_watch` may write a session artifact with full failed-job logs.\n- Network\n - Every op shells out to `gh`, which then talks to GitHub APIs except `pr_push`.\n - `pr_push` uses git network transport to the configured remote.\n- Subprocesses / native bindings\n - All `gh` calls use `Bun.spawn([\"gh\", ...args])`.\n - `pr_checkout` and `pr_push` also invoke git helpers from `packages/coding-agent/src/utils/git.ts`.\n- Session state (transcript, memory, jobs, checkpoints, registries)\n - `run_watch` consumes `session.allocateOutputArtifact()` when failed-job logs are persisted.\n - Returned `details` objects carry run/checkouts metadata for the renderer/UI.\n- User-visible prompts / interactive UI\n - `gh` interactive editor fallback is suppressed for `pr_create` by forcing either `--body-file` or `--body \"\"`.\n - `gh-renderer` provides compact headers for all ops and a custom live watch view for `run_watch`.\n- Background work / cancellation\n - `run_watch` loops until success/failure and uses `scheduler.wait()` between polls.\n - `GithubTool.execute()` is wrapped in `untilAborted()`; `git.github.run()` forwards the abort signal into `Bun.spawn()`.\n\n## Limits & Caps\n- Search result default: `10` (`SEARCH_LIMIT_DEFAULT` in `packages/coding-agent/src/tools/gh.ts`).\n- Search result max: `50` (`SEARCH_LIMIT_MAX`).\n- PR file preview inside the `pr://` view: first `50` files only (`FILE_PREVIEW_LIMIT` in `gh.ts`).\n- Run-watch poll interval: `3s` (`RUN_WATCH_INTERVAL_DEFAULT`).\n- Run-watch failure grace period: `5s` (`RUN_WATCH_GRACE_DEFAULT`).\n- Run-watch failed-log tail default: `15` lines (`RUN_WATCH_TAIL_DEFAULT`).\n- Run-watch failed-log tail max: `200` lines (`RUN_WATCH_TAIL_MAX`).\n- PR review comments page size: `100` (`REVIEW_COMMENTS_PAGE_SIZE`).\n- Actions jobs page size: `100` (`RUN_JOBS_PAGE_SIZE`).\n- Search and tail numeric inputs are floored with `Math.floor()`, clamped to the max, and rejected when non-finite or `<= 0`.\n- `pr_checkout` batch fan-out is unbounded in tool code; all requested PRs are launched with `Promise.all()`.\n\n## Errors\n- Tool creation is skipped entirely when `gh` is not installed.\n- `git.github.run()` throws `ToolError(\"GitHub CLI (gh) is not installed...\")` if `gh` is missing at execution time.\n- `git.github.text/json()` map common failures to model-facing messages:\n - not authenticated → `GitHub CLI is not authenticated. Run \\`gh auth login\\`.`\n - missing repo context without explicit `repo` → `GitHub repository context is unavailable. Pass \\`repo\\` explicitly or run the tool inside a GitHub checkout.`\n - otherwise stderr/stdout text, or fallback `GitHub CLI command failed: gh ...`\n- `json()` also throws on empty stdout or invalid JSON.\n- Local validation errors throw `ToolError`, including:\n - missing required per-op fields (`query`, `title unless fill=true`)\n - invalid numeric `limit` / `tail`\n - invalid `run` format\n - `fill` combined with `title` or `body`\n - missing git repo / branch / HEAD context for checkout, push, or watch\n - `pr_push` on a branch without `skcPrHeadRef` metadata\n - conflicting existing worktree path or branch without `force`\n- `run_watch` treats failed-job log fetches specially: missing log content does not fail the watch; it marks that log `available: false` and prints `Log tail unavailable.` / `Full log unavailable.`.\n- `pr_create` swallows only the post-create best-effort `gh pr view` refresh; the create step itself still fails normally.\n\n## Notes\n- `appendRepoFlag()` intentionally skips `--repo` when the identifier argument is already a full GitHub URL; that lets `gh` derive repo/number from the URL.\n- `normalizePrIdentifierList()` accepts `reviewer`, `assignee`, and `label` arrays too; the helper name is broader than its callers.\n- `pr_push` depends on `pr_checkout` having run first for that local branch; there is no alternate metadata source.\n- `pr_checkout` stores push metadata in branch config, not in the worktree directory. Reusing the same `pr-<number>` branch reuses those config keys.\n- Worktree write serialization is keyed by the primary repo root, not the current worktree path, because git worktrees share `.git/config`, `packed-refs`, commit-graph, and worktree metadata files.\n- `search_repos` is the only search op that never forwards `repo`; repository scoping must be expressed in the query itself.\n- `run_watch` success on commit mode means “all observed runs succeeded and no additional runs appeared one poll later”, not merely “latest poll looked green”.\n- The TUI renderer collapses failed log previews unless the result view is expanded; the underlying text result still contains the same tailed lines plus any artifact reference.\n",
|
|
108
|
-
"tools/irc.md": "# irc\n\n> Send short prose messages to other live agents in the current process.\n\n## Source\n- Entry: `packages/coding-agent/src/tools/irc.ts`\n- Model-facing prompt: `packages/coding-agent/src/prompts/tools/irc.md`\n- Key collaborators:\n - `packages/coding-agent/src/registry/agent-registry.ts` — process-global live agent directory.\n - `packages/coding-agent/src/session/agent-session.ts` — side-channel reply generation and history injection.\n - `packages/coding-agent/src/prompts/system/irc-incoming.md` — no-tools auto-reply prompt.\n - `packages/coding-agent/src/tools/index.ts` — tool availability gating.\n - `packages/coding-agent/src/config/settings-schema.ts` — `irc.enabled` default.\n - `packages/coding-agent/src/modes/controllers/event-controller.ts` — renders IRC events into chat UI.\n - `packages/coding-agent/src/modes/utils/ui-helpers.ts` — formats `[IRC]` transcript lines.\n - `packages/coding-agent/src/task/executor.ts` — carries `irc.enabled` into subagents.\n\n## Inputs\n\n### `op: \"list\"`\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `op` | `\"list\"` | Yes | Lists peers visible to the caller. |\n\n### `op: \"send\"`\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `op` | `\"send\"` | Yes | Sends one message to one peer or to `\"all\"`. |\n| `to` | `string` | Yes | Peer id such as `0-Main`, or `\"all\"` for broadcast. Whitespace is trimmed. |\n| `message` | `string` | Yes | Message body. Whitespace is trimmed; empty-after-trim is rejected. |\n| `awaitReply` | `boolean` | No | Wait for prose replies. Defaults to `true` for direct messages and `false` for `to: \"all\"`. |\n\n## Outputs\n- Single-shot `AgentToolResult`; no streaming updates.\n- `content` is one text block.\n - `list` returns either `No other live agents.` or a bullet list headed by `<n> peer(s):`.\n - `send` returns delivery summary text, then optional `## Replies`, `## Failed`, and `Unknown / unavailable peers:` sections.\n- `details` is structured metadata:\n - `list`: `{ op, from, peers, channels }`\n - `send`: `{ op, from, to, delivered, replies?, failed?, notFound? }`\n- The tool does not return raw IRC frames, message ids, or a transcript object.\n\n## Flow\n1. `IrcTool.createIf` only constructs the tool when `irc.enabled` is on and the session has both an `AgentRegistry` and `getAgentId` (`packages/coding-agent/src/tools/irc.ts`).\n2. Tool discovery adds another gate in `packages/coding-agent/src/tools/index.ts`: if the caller is `0-Main` and `async.enabled` is off, `irc` is hidden because the main agent cannot talk to concurrent peers in sync mode.\n3. `execute` resolves the process-global registry and sender id. Missing either returns a text error result instead of throwing.\n4. `op: \"list\"` calls `registry.listVisibleTo(senderId)`, which exposes every other agent in flat namespace whose status is `running` or `idle` (`packages/coding-agent/src/registry/agent-registry.ts`).\n5. `list` formats human-readable lines and returns `channels` as `['all', ...peerIds]`. These are logical targets only; there is no channel join state.\n6. `op: \"send\"` trims `to` and `message`; missing values produce text errors.\n7. `send` resolves targets:\n - `to === \"all\"`: all visible peers.\n - otherwise: one exact registry id, excluding self and excluding peers not in `running`/`idle`.\n8. `send` chooses `awaitReply = params.awaitReply ?? !isBroadcast`.\n9. Each target is dispatched in parallel via `target.session.respondAsBackground(...)`. One slow or failing peer does not block dispatch to the others.\n10. `respondAsBackground` emits an `irc_message` session event, forwards a display-only relay to the main session UI, and either:\n - queues just the incoming message for later history injection when `awaitReply === false`, or\n - renders `packages/coding-agent/src/prompts/system/irc-incoming.md`, runs `runEphemeralTurn` with `toolChoice: \"none\"`, emits an auto-reply event, then queues both incoming and reply messages for history injection.\n11. Deferred injection waits until the recipient is no longer streaming; `#flushPendingBackgroundExchanges` appends the custom messages through normal `message_start`/`message_end` external events so persistence and listeners see them.\n12. `send` aggregates `delivered`, `replies`, `failed`, and `notFound`, then returns one text summary plus matching `details`.\n\n## Modes / Variants\n- `list`: enumerate visible peers and logical channels.\n- `send` direct message: one exact peer id, default synchronous auto-reply.\n- `send` broadcast: `to: \"all\"`, default fire-and-forget (`awaitReply: false`) to every visible peer.\n- `send` with `awaitReply: false`: recipient records the incoming message but does not generate a reply.\n- `send` with `awaitReply: true`: recipient performs a no-tools ephemeral LLM turn and returns prose.\n\n## Side Effects\n- Session state\n - Reads from the process-global `AgentRegistry`.\n - Emits `irc_message` session events on recipient sessions.\n - Queues IRC custom messages into recipient persisted history after the current stream finishes.\n - For non-main recipients, forwards display-only relay observations into the main session UI; these relays are not persisted to the main agent history.\n - Subagents inherit `irc.enabled` from task executor settings.\n- User-visible prompts / interactive UI\n - IRC events render as `[IRC]` transcript lines in the TUI.\n - Auto-replies are generated from `packages/coding-agent/src/prompts/system/irc-incoming.md` and explicitly forbid tool use.\n- Background work / cancellation\n - `send` starts one background `respondAsBackground` call per target.\n - The caller's `AbortSignal` is forwarded into each background reply turn.\n- Network\n - No IRC server connection.\n - When `awaitReply: true`, the recipient may make model-provider API calls through `runEphemeralTurn`.\n- Filesystem\n - No direct filesystem writes in the tool itself.\n\n## Limits & Caps\n- Availability gates:\n - `irc.enabled` defaults to `true` in `packages/coding-agent/src/config/settings-schema.ts`.\n - Main agent tool discovery suppresses `irc` when `async.enabled` is off (`packages/coding-agent/src/tools/index.ts`).\n- Visibility scope: only peers in status `running` or `idle` are addressable via `listVisibleTo`.\n- Reply execution:\n - No tools are available in auto-reply turns (`toolChoice: \"none\"` in `runEphemeralTurn`).\n - No internal timeout, retry, backoff, rate limit, or reply length cap is defined in `irc.ts`; behavior relies on the underlying model stream and any upstream API limits.\n- Flush scheduling: deferred history injection polls every `50` ms while the recipient is still streaming (`#scheduleBackgroundExchangeFlush` in `packages/coding-agent/src/session/agent-session.ts`).\n\n## Errors\n- The tool returns text errors, not thrown exceptions, for:\n - missing registry: `IRC is unavailable in this session.`\n - missing sender id: `IRC is unavailable: caller has no agent id.`\n - missing `to`: `` `to` is required for op=\"send\". ``\n - missing `message`: `` `message` is required for op=\"send\". ``\n - unknown op: `Unknown irc op.`\n- Unknown, self-addressed, non-running, and non-idle direct targets are reported under `details.notFound` and in the text footer `Unknown / unavailable peers:`.\n- If a target has no attached session, it is treated as not found.\n- Exceptions thrown by `respondAsBackground` or `runEphemeralTurn` are caught per-target and surfaced under `details.failed` as `{ id, error }`; other recipients still complete.\n- If no target succeeds, `send` still returns normally with `No recipients received the message.` and optional `failed`/`notFound` metadata.\n\n## Notes\n- This is IRC-like naming only. There are no servers, sockets, nick registration, auth handshakes, channels beyond `all`, or commands such as join/part/topic.\n- Addressing is by exact agent id from the registry; there is no fuzzy lookup or aliasing.\n- `channels` in `list` is synthetic output: `all` plus visible peer ids. Nothing is persisted across calls as channel membership.\n- Persistence is per recipient history, not per sender history. The sender gets the tool result; the recipient later sees injected custom messages on its next turn.\n- The main UI may show IRC relays for conversations it was not part of, but those relay records are explicitly display-only.\n- Because reply generation snapshots in-flight assistant text, a recipient can answer based on partially streamed context.\n- Direct self-messaging is rejected by resolving the target as unavailable.\n\n## Sidebar\n\n- `irc.sidebar.enabled` defaults to `true`: the read-only sidebar is available when `irc.enabled` is also enabled, but starts closed. `app.irc.sidebar.toggle` (default `Alt+I`, remappable) opens or hides it.\n- The sidebar retains the active runtime UI session's IRC observations only. It is not written to disk or restored into another session. Each arrival decides its inline lifetime once: arrivals while the panel is visible expire 10 seconds after observation; arrivals while it is closed persist inline. Later toggles do not change that decision.\n- An eligible first live inline arrival while the sidebar is closed shows a one-time hint using the resolved toggle key (for example, `Alt+I opens sidebar`). Rebuilds do not show or consume this hint.\n- When open, the transcript/sidebar split targets 70:30. The sidebar keeps a 30-column minimum only while the transcript can retain at least half the usable width; below that boundary the sidebar yields completely and the transcript renders full width. Retained sidebar messages are Discord-style blocks: `sender → recipient · HH:mm`, followed by the retained body with a two-column indent and one blank row between messages. Sender and recipient display fields are normalized and bounded to 256 UTF-8 bytes at complete grapheme boundaries.\n- The sidebar retains at most 10,000 observations and 16 MiB of UTF-8 message payload per runtime UI session, evicting the oldest observations first. An individual observation larger than the 16 MiB budget is omitted rather than truncated or admitted by evicting the rest of the backlog. Replay suppression tracks at most 100,000 unique observation identities and then fails closed for unseen arrivals until the runtime UI instance ends, preventing forgotten identities from resurrecting across eviction or fork cleanup while keeping memory bounded. Both inline transcript and sidebar body rendering independently materialize at most 2,048 rows from a bounded 64 KiB UTF-8 source projection, preferring recent retained content and showing explicit message/backlog elision markers when necessary. The bounded, runtime-only, read-only backlog does not affect welcome-screen row reservation, which counts transcript rows only.\n- While the sidebar is visible, Kitty terminals keep rendering real images in the transcript (Kitty placements are cursor-neutral and compose safely with the split). Cursor-advancing protocols — iTerm2 inline images and raw SIXEL sequences — are represented by compact text placeholders so they cannot corrupt the split. Hiding the sidebar restores normal rendering for every protocol.\n- A successful `/fork` starts a new logical UI session: it clears the sidebar ledger, hides the panel, and resets roster-delivery state. Failed or cancelled forks preserve the current runtime sidebar state.\n\n## Hidden peer roster reminders\n\nWhen the live peer roster changes, an eligible model turn receives one hidden single-line reminder listing stable agent ids and roster labels. The initial empty roster produces no reminder; a later transition to empty does. Running/idle status changes alone do not count as a roster change.\n\nNormal turns, IRC auto-replies, and `/btw` ephemeral turns share one atomic roster claim. At most one concurrent turn receives a changed roster; successful completion commits that delivery, while failed or aborted turns release it for a later retry. These reminders are context-only and never appear in the transcript or persisted history.",
|
|
109
|
+
"tools/irc.md": "# irc\n\n> Send short prose messages to other live agents in the current process.\n\n## Source\n- Entry: `packages/coding-agent/src/tools/irc.ts`\n- Model-facing prompt: `packages/coding-agent/src/prompts/tools/irc.md`\n- Key collaborators:\n - `packages/coding-agent/src/registry/agent-registry.ts` — process-global live agent directory.\n - `packages/coding-agent/src/session/agent-session.ts` — side-channel reply generation and history injection.\n - `packages/coding-agent/src/prompts/system/irc-incoming.md` — no-tools auto-reply prompt.\n - `packages/coding-agent/src/tools/index.ts` — tool availability gating.\n - `packages/coding-agent/src/config/settings-schema.ts` — `irc.enabled` default.\n - `packages/coding-agent/src/modes/controllers/event-controller.ts` — renders IRC events into chat UI.\n - `packages/coding-agent/src/modes/utils/ui-helpers.ts` — formats `[IRC]` transcript lines.\n - `packages/coding-agent/src/task/executor.ts` — carries `irc.enabled` into subagents.\n\n## Inputs\n\n### `op: \"list\"`\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `op` | `\"list\"` | Yes | Lists peers visible to the caller. |\n\n### `op: \"send\"`\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `op` | `\"send\"` | Yes | Sends one message to one peer or to `\"all\"`. |\n| `to` | `string` | Yes | Peer id such as `0-Main`, or `\"all\"` for broadcast. Whitespace is trimmed. |\n| `message` | `string` | Yes | Message body. Whitespace is trimmed; empty-after-trim is rejected. |\n| `awaitReply` | `boolean` | No | Wait for prose replies. Defaults to `true` for direct messages and `false` for `to: \"all\"`. |\n\n## Outputs\n- Single-shot `AgentToolResult`; no streaming updates.\n- `content` is one text block.\n - `list` returns either `No other live agents.` or a bullet list headed by `<n> peer(s):`.\n - `send` returns delivery summary text, then optional `## Replies`, `## Failed`, and `Unknown / unavailable peers:` sections.\n- `details` is structured metadata:\n - `list`: `{ op, from, peers, channels }`\n - `send`: `{ op, from, to, delivered, replies?, failed?, notFound? }`\n- The tool does not return raw IRC frames, message ids, or a transcript object.\n\n## Flow\n1. `IrcTool.createIf` only constructs the tool when `irc.enabled` is on and the session has both an `AgentRegistry` and `getAgentId` (`packages/coding-agent/src/tools/irc.ts`).\n2. Tool discovery adds another gate in `packages/coding-agent/src/tools/index.ts`: if the caller is `0-Main` and `async.enabled` is off, `irc` is hidden because the main agent cannot talk to concurrent peers in sync mode.\n3. `execute` resolves the process-global registry and sender id. Missing either returns a text error result instead of throwing.\n4. `op: \"list\"` calls `registry.listVisibleTo(senderId)`, which exposes every other agent in flat namespace whose status is `running` or `idle` (`packages/coding-agent/src/registry/agent-registry.ts`).\n5. `list` formats human-readable lines and returns `channels` as `['all', ...peerIds]`. These are logical targets only; there is no channel join state.\n6. `op: \"send\"` trims `to` and `message`; missing values produce text errors.\n7. `send` resolves targets:\n - `to === \"all\"`: all visible peers.\n - otherwise: one exact registry id, excluding self and excluding peers not in `running`/`idle`.\n8. `send` chooses `awaitReply = params.awaitReply ?? !isBroadcast`.\n9. Each target is dispatched in parallel via `target.session.respondAsBackground(...)`. One slow or failing peer does not block dispatch to the others.\n10. `respondAsBackground` accepts each delivery into the recipient's volatile current-session exchange queue before observing it in the recipient or main UI, and before reporting sender delivery success:\n - `awaitReply === false`: accepts/queues the incoming message, then emits its `irc_message` event and forwards the display-only relay to the main session UI.\n - `awaitReply === true`: renders `packages/coding-agent/src/prompts/system/irc-incoming.md` and runs `runEphemeralTurn` with `toolChoice: \"none\"`. After a reply succeeds, it constructs and accepts/queues the ordered incoming + auto-reply pair, commits its IRC roster claim, then emits both `irc_message` events and forwards both display-only relays. A failed or aborted reply turn accepts and surfaces nothing.\n11. Deferred injection waits until the recipient is no longer streaming; `#flushPendingBackgroundExchanges` appends accepted custom messages through normal `message_start`/`message_end` external events so persistence and listeners see them.\n12. `send` aggregates `delivered`, `replies`, `failed`, and `notFound`, then returns one text summary plus matching `details`.\n\n## Modes / Variants\n- `list`: enumerate visible peers and logical channels.\n- `send` direct message: one exact peer id, default synchronous auto-reply.\n- `send` broadcast: `to: \"all\"`, default fire-and-forget (`awaitReply: false`) to every visible peer.\n- `send` with `awaitReply: false`: recipient records the incoming message but does not generate a reply.\n- `send` with `awaitReply: true`: recipient performs a no-tools ephemeral LLM turn and returns prose.\n\n## Side Effects\n- Session state\n - Reads from the process-global `AgentRegistry`.\n - Accepts each IRC delivery into a process-local, volatile recipient exchange queue before recipient/main observations and sender success. This acceptance is not durable delivery.\n - Emits `irc_message` session events on recipient sessions after acceptance.\n - Flushes accepted IRC custom messages into recipient history after the current stream finishes.\n - For non-main recipients, forwards display-only relay observations into the main session UI after acceptance; these relays are not persisted to the main agent history. Observer failures are isolated from accepted delivery.\n - Subagents inherit `irc.enabled` from task executor settings.\n- User-visible prompts / interactive UI\n - IRC events render as `[IRC]` transcript lines in the TUI.\n - Auto-replies are generated from `packages/coding-agent/src/prompts/system/irc-incoming.md` and explicitly forbid tool use.\n- Background work / cancellation\n - `send` starts one background `respondAsBackground` call per target.\n - The caller's `AbortSignal` is forwarded into each background reply turn.\n- Network\n - No IRC server connection.\n - When `awaitReply: true`, the recipient may make model-provider API calls through `runEphemeralTurn`.\n- Filesystem\n - No direct filesystem writes in the tool itself.\n\n## Limits & Caps\n- Availability gates:\n - `irc.enabled` defaults to `true` in `packages/coding-agent/src/config/settings-schema.ts`.\n - Main agent tool discovery suppresses `irc` when `async.enabled` is off (`packages/coding-agent/src/tools/index.ts`).\n- Visibility scope: only peers in status `running` or `idle` are addressable via `listVisibleTo`.\n- Reply execution:\n - No tools are available in auto-reply turns (`toolChoice: \"none\"` in `runEphemeralTurn`).\n - No internal timeout, retry, backoff, rate limit, or reply length cap is defined in `irc.ts`; behavior relies on the underlying model stream and any upstream API limits.\n- Flush scheduling: deferred history injection polls every `50` ms while the recipient is still streaming (`#scheduleBackgroundExchangeFlush` in `packages/coding-agent/src/session/agent-session.ts`).\n\n## Errors\n- The tool returns text errors, not thrown exceptions, for:\n - missing registry: `IRC is unavailable in this session.`\n - missing sender id: `IRC is unavailable: caller has no agent id.`\n - missing `to`: `` `to` is required for op=\"send\". ``\n - missing `message`: `` `message` is required for op=\"send\". ``\n - unknown op: `Unknown irc op.`\n- Unknown, self-addressed, non-running, and non-idle direct targets are reported under `details.notFound` and in the text footer `Unknown / unavailable peers:`.\n- If a target has no attached session, it is treated as not found.\n- Exceptions from reply generation before awaited-exchange acceptance are caught per-target and surfaced under `details.failed` as `{ id, error }`; other recipients still complete. A provider failure or sender abort therefore emits no `irc_message` observations and accepts no recipient exchange. Recipient/main observer failures after acceptance are isolated and do not turn a delivered exchange into a sender failure.\n- If no target succeeds, `send` still returns normally with `No recipients received the message.` and optional `failed`/`notFound` metadata.\n\n## Notes\n- This is IRC-like naming only. There are no servers, sockets, nick registration, auth handshakes, channels beyond `all`, or commands such as join/part/topic.\n- Addressing is by exact agent id from the registry; there is no fuzzy lookup or aliasing.\n- `channels` in `list` is synthetic output: `all` plus visible peer ids. Nothing is persisted across calls as channel membership.\n- Recipient history, not sender history, receives accepted IRC custom messages when the recipient flushes its current-session queue. Acceptance is process-local and volatile: it does not promise durable storage, fsync, recovery, deduplication, or replay across process loss.\n- The main UI may show IRC relays for conversations it was not part of, but those relay records are explicitly display-only.\n- Because reply generation snapshots in-flight assistant text, a recipient can answer based on partially streamed context.\n- Direct self-messaging is rejected by resolving the target as unavailable.\n\n## Sidebar\n\n- `irc.sidebar.enabled` defaults to `true`: the read-only sidebar is available when `irc.enabled` is also enabled, but starts closed. `app.irc.sidebar.toggle` (default `Alt+I`, remappable) opens or hides it.\n- The sidebar retains the active runtime UI session's IRC observations only. It is not written to disk or restored into another session. Each arrival decides its inline lifetime once: arrivals while the panel is visible expire 10 seconds after observation; arrivals while it is closed persist inline. Later toggles do not change that decision.\n- An eligible first live inline arrival while the sidebar is closed shows a one-time hint using the resolved toggle key (for example, `Alt+I opens sidebar`). Rebuilds do not show or consume this hint.\n- When open, the transcript/sidebar split targets 70:30. The sidebar keeps a 30-column minimum only while the transcript can retain at least half the usable width; below that boundary the sidebar yields completely and the transcript renders full width. Retained sidebar messages are Discord-style blocks: `sender → recipient · HH:mm`, followed by the retained body with a two-column indent and one blank row between messages. Sender and recipient display fields are normalized and bounded to 256 UTF-8 bytes at complete grapheme boundaries.\n- The sidebar retains at most 10,000 observations and 16 MiB of UTF-8 message payload per runtime UI session, evicting the oldest observations first. An individual observation larger than the 16 MiB budget is omitted rather than truncated or admitted by evicting the rest of the backlog. Replay suppression tracks at most 100,000 unique observation identities and then fails closed for unseen arrivals until the runtime UI instance ends, preventing forgotten identities from resurrecting across eviction or fork cleanup while keeping memory bounded. Both inline transcript and sidebar body rendering independently materialize at most 2,048 rows from a bounded 64 KiB UTF-8 source projection, preferring recent retained content and showing explicit message/backlog elision markers when necessary. The bounded, runtime-only, read-only backlog does not affect welcome-screen row reservation, which counts transcript rows only.\n- While the sidebar is visible, Kitty terminals keep rendering real images in the transcript (Kitty placements are cursor-neutral and compose safely with the split). Cursor-advancing protocols — iTerm2 inline images and raw SIXEL sequences — are represented by compact text placeholders so they cannot corrupt the split. Hiding the sidebar restores normal rendering for every protocol.\n- A successful `/fork` starts a new logical UI session: it clears the sidebar ledger, hides the panel, and resets roster-delivery state. Failed or cancelled forks preserve the current runtime sidebar state.\n\n## Hidden peer roster reminders\n\nWhen the live peer roster changes, an eligible model turn receives one hidden single-line reminder listing stable agent ids and roster labels. The initial empty roster produces no reminder; a later transition to empty does. Running/idle status changes alone do not count as a roster change.\n\nNormal turns and `/btw` ephemeral turns commit an atomic roster claim on successful completion. Awaited IRC auto-replies defer that commit until their incoming + auto-reply exchange is accepted; failed or aborted reply turns release the claim for a later retry. These reminders are context-only and never appear in the transcript or persisted history.",
|
|
109
110
|
"tools/job.md": "# job\n\n> Wait for or cancel background jobs managed by the session async runtime.\n\n## Source\n- Entry: `packages/coding-agent/src/tools/job.ts`\n- Model-facing prompt: `packages/coding-agent/src/prompts/tools/job.md`\n- Key collaborators:\n - `packages/coding-agent/src/async/job-manager.ts` — job registry, cancellation, delivery suppression.\n - `packages/coding-agent/src/async/support.ts` — feature gating for background jobs.\n - `packages/coding-agent/src/tools/bash.ts` — explicit async bash and auto-backgrounded bash jobs.\n - `packages/coding-agent/src/task/index.ts` — async task-job scheduling.\n - `packages/coding-agent/src/sdk.ts` — automatic follow-up delivery for unsuppressed completions.\n - `packages/coding-agent/src/config/settings-schema.ts` — `async.pollWaitDuration` options.\n\n## Inputs\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `poll` | `string[]` | No | Job ids to watch. Cannot be combined with `list`. If omitted (and `cancel` is also omitted), the tool watches all running jobs. If provided, missing ids are silently filtered out before waiting. |\n| `cancel` | `string[]` | No | Job ids to cancel before any polling. Missing ids are reported as `not_found`; non-running ids as `already_completed`. |\n| `list` | `boolean` | No | Return an immediate snapshot of every job spawned by the calling agent (running + completed within retention) without waiting. Read-only — cannot be combined with `poll` or `cancel`. |\n\n## Outputs\nThe tool returns one text block plus `details`.\n\n- `content[0].text`: markdown-like plain text sections assembled by `#buildResult(...)`:\n - `## Cancelled (N)` for cancel outcomes.\n - `## Completed (N)` for non-running jobs, including stored `resultText` and `errorText`.\n - `## Still Running (N)` for jobs still in `running`.\n- `details.jobs`: array of snapshots:\n - `id: string`\n - `type: \"bash\" | \"task\"`\n - `status: \"running\" | \"completed\" | \"failed\" | \"cancelled\"`\n - `label: string`\n - `durationMs: number`\n - optional `resultText`, `errorText`\n- `details.cancelled` appears only when `cancel` was passed; each item is `{ id, status }` where status is `\"cancelled\" | \"not_found\" | \"already_completed\"`.\n\nStreaming behavior:\n- During a polling wait, `execute(...)` emits `onUpdate(...)` every 500 ms with an empty text block and fresh `details.jobs` snapshots.\n- Final return is single-shot after a completion, timeout, abort, or immediate fast path.\n\nRead-only snapshot path:\n- Calling `job` with `list: true` returns a markdown summary of every job spawned by the calling agent (running + completed within retention) without waiting.\n\n## Flow\n1. `JobTool.createIf(...)` in `packages/coding-agent/src/tools/job.ts` only exposes the tool when `isBackgroundJobSupportEnabled(...)` returns true for either `async.enabled` or `bash.autoBackground.enabled`.\n2. `execute(...)` fetches `session.asyncJobManager`. If absent, it returns `Async execution is disabled; no background jobs are available.`\n3. `cancel` ids are processed first:\n - `manager.getJob(id)` missing → `not_found`.\n - existing job with `status !== \"running\"` → `already_completed`.\n - running job → `manager.cancel(id)`, which sets `job.status = \"cancelled\"`, aborts the controller, and schedules eviction.\n4. Polling mode is chosen with `const shouldPoll = requestedPollIds !== undefined || cancelIds.length === 0`:\n - only `cancel` present → return immediately, no wait.\n - explicit `poll`, or no args at all → proceed to watch jobs.\n5. Watch set resolution:\n - explicit `poll` → map ids through `manager.getJob(...)` and drop missing ones.\n - no `poll` and no `cancel` → `manager.getRunningJobs()`.\n6. Empty watch set returns immediately:\n - if cancellations happened, return snapshots for the cancelled ids that still exist.\n - else return either `No matching jobs found for IDs: ...` or `No running background jobs to wait for.`\n7. If every watched job is already non-running, `#buildResult(...)` returns immediately without waiting.\n8. Otherwise the tool waits on `Promise.race(...)` across:\n - every watched running job's `job.promise`,\n - a timeout promise for `async.pollWaitDuration`,\n - the tool-call abort signal when present.\n9. Before waiting, it calls `manager.watchJobs(watchedJobIds)`. This suppresses automatic completion delivery for those ids while they are being watched.\n10. If `onUpdate` exists, a 500 ms interval sends progress snapshots from `#snapshotJobs(...)`; one snapshot is emitted immediately before entering the race.\n11. In `finally`, the tool always calls `manager.unwatchJobs(...)`, clears the timeout, and stops the progress interval.\n12. `#buildResult(...)` deduplicates jobs, snapshots current manager state, then calls `manager.acknowledgeDeliveries(...)` for every non-running job in the result. That suppresses later automatic follow-up delivery for the same completions and removes queued deliveries for those ids.\n13. The final text groups jobs by non-running vs still-running state. A timeout is not an error path; it simply returns the current snapshot.\n\n## Modes / Variants\n- Poll all running jobs: call with neither `poll` nor `cancel`.\n- Poll explicit ids: call with `poll` only.\n- Cancel only: call with `cancel` only; cancellations happen and the tool returns immediately.\n- Cancel then poll: call with both. Cancellations are applied first, then the tool watches the remaining resolved `poll` ids.\n- Read-only inspection: call with `list: true` for the same snapshot data without waiting on completion.\n\nSpawn paths that produce jobs:\n- `packages/coding-agent/src/tools/bash.ts`\n - `async: true` always registers a `type: \"bash\"` job with `AsyncJobManager.register(...)` and returns a start message.\n - auto-background mode (`bash.autoBackground.enabled`) starts the same managed job path for non-PTY commands, waits up to `min(bash.autoBackground.thresholdMs, timeoutMs - 1000)`, and if the command is still running returns a background-job start result instead of inline command output.\n- `packages/coding-agent/src/task/index.ts`\n - when `async.enabled` is on, the chosen agent is not blocking, and `tasks.length > 0`, each task item is registered as a `type: \"task\"` job.\n\nLifecycle and exact state names:\n- Conceptual scheduling path: `pending` (only task-progress bookkeeping before work starts) → `running` → `completed` / `failed`; cancellation changes a running async job to `cancelled`.\n- Exact `AsyncJob.status` values in `packages/coding-agent/src/async/job-manager.ts`: `\"running\" | \"completed\" | \"failed\" | \"cancelled\"`.\n- Exact per-task progress values in `packages/coding-agent/src/task/types.ts`: `\"pending\" | \"running\" | \"completed\" | \"failed\" | \"aborted\"`.\n\n## Side Effects\n- Filesystem\n - None in `job.ts` itself.\n - Jobs being observed may already have written artifacts/results through their own tool runtimes.\n- Session state (transcript, memory, jobs, checkpoints, registries)\n - Reads and mutates `session.asyncJobManager` state.\n - `watchJobs(...)` / `unwatchJobs(...)` toggle delivery suppression for the watched ids.\n - `acknowledgeDeliveries(...)` marks completed ids as suppressed and removes queued deliveries for them.\n - `cancel(...)` aborts running jobs through each job's `AbortController`.\n- User-visible prompts / interactive UI\n - Polling emits periodic `onUpdate` snapshots every 500 ms.\n - Automatic job completion follow-ups are generated by `packages/coding-agent/src/sdk.ts` only for unsuppressed deliveries.\n- Background work / cancellation\n - Waiting uses a timeout plus optional tool-call abort signal.\n - Cancelling a job does not synchronously await teardown; it flips state, aborts, and returns control to the manager/job promise.\n\n## Limits & Caps\n- Poll wait duration comes from `async.pollWaitDuration` in `packages/coding-agent/src/config/settings-schema.ts`:\n - allowed values: `5s`, `10s`, `30s`, `1m`, `5m`\n - default: `30s`\n- Progress update cadence while polling: `PROGRESS_INTERVAL_MS = 500` in `packages/coding-agent/src/tools/job.ts`.\n- Async job retention default: `DEFAULT_RETENTION_MS = 5 * 60 * 1000` in `packages/coding-agent/src/async/job-manager.ts`.\n- Manager fallback max-running limit: `DEFAULT_MAX_RUNNING_JOBS = 15` in `packages/coding-agent/src/async/job-manager.ts`.\n- Session wiring clamps `async.maxJobs` to `1..100` before constructing the manager in `packages/coding-agent/src/sdk.ts`; settings default is `100` in `packages/coding-agent/src/config/settings-schema.ts`.\n- Async completion delivery retry backoff in `packages/coding-agent/src/async/job-manager.ts`:\n - base `500` ms\n - max `30_000` ms\n - jitter `< 200` ms\n - exponent capped at 8 doublings\n\n## Errors\n- Tool-disabled path is returned as normal text, not thrown: `Async execution is disabled; no background jobs are available.`\n- Polling a nonexistent id is not an exception:\n - with `poll` only, missing ids are dropped; if none remain the tool returns `No matching jobs found for IDs: ...`.\n - with `cancel`, each missing id is reported as `not_found` in `details.cancelled` and text.\n- Cancelling a non-running job is not an exception; it reports `already_completed` even if the actual status is `completed`, `failed`, or `cancelled`.\n- Tool-call abort during polling stops waiting and returns a final snapshot through `#buildResult(...)`; it does not cancel watched jobs.\n- Failures inside the underlying async work are stored on the job (`status: \"failed\"`, `errorText`) and reported in normal tool output, not rethrown by `job`.\n- Calling `list: true` against an empty manager returns a normal empty-list result rather than throwing; missing ids passed to `poll` are silently filtered.\n\n## Notes\n- `job` waits for the first watched running job to settle, not for all watched jobs. If others remain `running`, they are reported under `## Still Running`; the caller must invoke `job` again to continue waiting.\n- Delivery suppression is the key difference between snapshot and automatic delivery:\n - snapshots (`job` calls with `poll` or `list: true`) read current manager state;\n - follow-up delivery comes from `AsyncJobManager.#enqueueDelivery(...)` and `sdk.ts` `onJobComplete`;\n - watched or acknowledged ids are suppressed via `isDeliverySuppressed(...)`.\n- `manager.cancel(id)` sets `status = \"cancelled\"` before the underlying promise settles. The job function may later populate `resultText` or `errorText`; `job-manager.ts` preserves that text but does not transition the status away from `cancelled`.\n- Retention eviction removes the job record, suppression flags, and watch flag together. After eviction, both `job` calls and `list: true` snapshots behave as if the id never existed.\n",
|
|
110
111
|
"tools/lsp.md": "# lsp\n\n> Query language servers for diagnostics, navigation, symbols, renames, code actions, capabilities, and raw requests.\n\n## Source\n- Entry: `packages/coding-agent/src/lsp/index.ts`\n- Model-facing prompt: `packages/coding-agent/src/prompts/tools/lsp.md`\n- Key collaborators:\n - `packages/coding-agent/src/lsp/client.ts` — client process lifecycle and JSON-RPC\n - `packages/coding-agent/src/lsp/config.ts` — config loading, auto-detect, server selection\n - `packages/coding-agent/src/lsp/lspmux.ts` — optional `lspmux` command wrapping\n - `packages/coding-agent/src/lsp/edits.ts` — apply `WorkspaceEdit` and text edits\n - `packages/coding-agent/src/lsp/utils.ts` — URI conversion, symbol resolution, formatting, glob expansion\n - `packages/coding-agent/src/lsp/types.ts` — tool schema and protocol types\n - `packages/coding-agent/src/lsp/clients/index.ts` — custom linter client cache/factory\n - `packages/coding-agent/src/lsp/clients/lsp-linter-client.ts` — LSP-backed linter adapter\n - `packages/coding-agent/src/lsp/clients/biome-client.ts` — Biome CLI diagnostics/formatting adapter\n - `packages/coding-agent/src/lsp/clients/swiftlint-client.ts` — SwiftLint CLI diagnostics adapter\n - `packages/coding-agent/src/tools/index.ts` — tool registration and `lsp.enabled` gating\n - `packages/coding-agent/src/tools/tool-timeouts.ts` — timeout defaults and clamping\n - `packages/coding-agent/src/lsp/defaults.json` — built-in server definitions for auto-detect\n\n## Inputs\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `action` | string enum | Yes | One of `diagnostics`, `definition`, `references`, `hover`, `symbols`, `rename`, `rename_file`, `code_actions`, `type_definition`, `implementation`, `status`, `reload`, `capabilities`, `request`. |\n| `file` | string | No | File path; for `diagnostics` also a glob; for workspace forms use `\"*\"`; for `rename_file` this is the source path. |\n| `line` | number | No | 1-indexed line number for position-based actions. Defaults to `1` on the single-file action path. |\n| `symbol` | string | No | Substring used to resolve the column on `line`. Supports `name#N` occurrence selectors; `N` is 1-indexed and defaults to `1`. |\n| `query` | string | No | Workspace symbol query, code-action selector/filter, or LSP method name for `action=request`. |\n| `new_name` | string | No | Required for `rename` and `rename_file`. |\n| `apply` | boolean | No | For `rename`/`rename_file`, apply unless explicitly `false`. For `code_actions`, list unless explicitly `true`. |\n| `timeout` | number | No | Seconds, clamped by `clampTimeout(\"lsp\", ...)` to `5..60`, default `20`. |\n| `payload` | string | No | JSON string for `action=request`; overrides auto-built params. |\n\n## Outputs\n- Single-shot `AgentToolResult`.\n- `content` is always one text block: `[{ type: \"text\", text: string }]`.\n- `details` is `LspToolDetails`: `action`, `success`, optional `serverName`, optional original `request`.\n- No streaming updates.\n- No artifact URIs or background jobs.\n- Many validation failures are returned as ordinary text results with `details.success: false`; aborts throw `ToolAbortError` instead.\n\n## Flow\n1. `packages/coding-agent/src/tools/index.ts` registers `lsp: LspTool.createIf`; session creation also gates it behind `session.enableLsp !== false` and `settings.get(\"lsp.enabled\")`.\n2. `LspTool.execute()` in `packages/coding-agent/src/lsp/index.ts` clamps `timeout` with `clampTimeout(\"lsp\", ...)`, builds an `AbortSignal.timeout(...)`, and combines it with the caller signal.\n3. `getConfig()` loads and caches `LspConfig` per cwd, applies idle-timeout config via `setIdleTimeout()`, and reuses the cached config on later calls.\n4. Config loading in `packages/coding-agent/src/lsp/config.ts` merges `defaults.json` with JSON/YAML overrides from project, project config dirs, user config dirs, plugin roots, and home; if there are no overrides it auto-detects servers from root markers plus executable discovery.\n5. Server routing uses `getServersForFile()` / `getServerForFile()` from `config.ts`: extension or basename match, then sort primary servers before linters. `index.ts` further filters custom linter clients out of navigation/refactor paths with `getLspServersForFile()` / `getLspServerForFile()`.\n6. `getOrCreateClient()` in `client.ts` creates one process per `command:cwd`, optionally wraps supported commands with `lspmux`, spawns the server, starts the background message reader, sends `initialize`, stores server capabilities, then sends `initialized`.\n7. The message reader in `client.ts` parses LSP frames, resolves pending requests, caches `publishDiagnostics`, tracks `$/progress` tokens for project-load completion, answers `workspace/configuration`, and applies `workspace/applyEdit` requests through `applyWorkspaceEdit()`.\n8. File-scoped actions call `ensureFileOpen()` before requests. Column resolution uses `resolveSymbolColumn()` from `utils.ts`: read the target file, pick first non-whitespace when `symbol` is omitted, otherwise find the exact or case-insensitive match on the target line and honor `#N` occurrence selectors.\n9. Actions dispatch in `LspTool.execute()` through dedicated branches: workspace-only branches (`status`, some `diagnostics`, workspace `symbols`, workspace `reload`, `capabilities`, `request`) run before the single-file switch; all other single-file actions share one client lookup and `switch(action)`.\n10. Requests go through `sendRequest()` in `client.ts`, which allocates an incrementing JSON-RPC id, installs abort and timeout handling, sends `$/cancelRequest` on abort, and rejects on timeout or process exit.\n11. Actions that return edits either preview with `formatWorkspaceEdit()` or apply with `applyWorkspaceEdit()` from `edits.ts`; `rename_file` also performs the filesystem rename and then sends `workspace/didRenameFiles`.\n12. Non-abort failures inside the single-file action block are converted to `LSP error: ...`; many precondition failures return explicit text without throwing.\n\n## Modes / Variants\n### Routing and workspace scope\n- `file: \"*\"` is only special for `diagnostics`, `symbols`, and `reload`.\n- `status` ignores `file`.\n- `capabilities` with omitted `file` or `\"*\"` inspects all non-custom LSP servers; with a concrete file it scopes to matching non-custom servers.\n- `request` with omitted `file` or `\"*\"` chooses the first available non-custom LSP server; with a concrete file it chooses that file's primary non-linter server.\n- `rename_file` sends `workspace/willRenameFiles` and `workspace/didRenameFiles` to every non-custom LSP server from `getLspServers(config)`, not just one file-scoped server.\n- Diagnostics are the only tool action that queries both normal LSP servers and custom linter clients (`BiomeClient`, `SwiftLintClient`, or `LspLinterClient`).\n\n### `diagnostics`\n**Inputs**\n- Required: `file`, unless using workspace mode with `file: \"*\"`.\n- Optional: `timeout`.\n\n**Execution**\n- `file: \"*\"`: `runWorkspaceDiagnostics()` detects project type from root markers and runs one subprocess command: Rust `cargo check --message-format=short`, TypeScript `npx tsc --noEmit`, Go `go build ./...`, Python `pyright`.\n- Concrete file or glob: `resolveDiagnosticTargets()` treats non-globs as one target, otherwise expands a `Bun.Glob` up to `MAX_GLOB_DIAGNOSTIC_TARGETS`.\n- Per file, every matching server runs: custom clients call `lint(file)`; real LSP servers optionally wait for project load, capture `diagnosticsVersion`, `refreshFile()`, then `waitForDiagnostics()` for fresh `publishDiagnostics`.\n- Results are deduplicated by range+message and severity-sorted.\n\n**Output text**\n- Single target with no issues: `OK`.\n- Single target with issues: `<summary>:\\n<grouped diagnostics>`.\n- Batch/glob target: one section per file, plus an initial truncation warning when the glob exceeds the file cap.\n- Workspace mode: `Workspace diagnostics (<detected description>):\\n<command output>`.\n\n### `definition`\n**Inputs**\n- Required: `file`.\n- Optional: `line`, `symbol`, `timeout`.\n\n**Execution**\n- Sends `textDocument/definition` with `{ textDocument, position }`.\n- Accepts `Location`, `Location[]`, `LocationLink`, or `LocationLink[]`; `normalizeLocationResult()` converts `LocationLink` to `targetSelectionRange ?? targetRange`.\n- Waits for project load before the request.\n\n**Output text**\n- `No definition found` or `Found N definition(s):` followed by `file:line:col` and one context line above/below each location.\n\n### `type_definition`\nSame as `definition`, but sends `textDocument/typeDefinition` and reports `type definition(s)`.\n\n### `implementation`\nSame as `definition`, but sends `textDocument/implementation` and reports `implementation(s)`.\n\n### `references`\n**Inputs**\n- Required: `file`.\n- Optional: `line`, `symbol`, `timeout`.\n\n**Execution**\n- Sends `textDocument/references` with `includeDeclaration: true`.\n- For project-aware servers, retries up to `REFERENCES_RETRY_COUNT` times when the only hit is the queried declaration; between retries it waits for project load and sleeps `REFERENCES_RETRY_DELAY_MS`.\n- First `REFERENCE_CONTEXT_LIMIT` references include surrounding context; the rest are location-only.\n\n**Output text**\n- `No references found` or `Found N reference(s):` with contextual entries first, then `... M additional reference(s) shown without context` when truncated.\n\n### `hover`\n**Inputs**\n- Required: `file`.\n- Optional: `line`, `symbol`, `timeout`.\n\n**Execution**\n- Sends `textDocument/hover`.\n- `extractHoverText()` flattens strings, markup content, marked-string objects, or arrays into plain text.\n\n**Output text**\n- `No hover information` or the extracted hover text.\n\n### `symbols`\n**Inputs**\n- Workspace mode: `file: \"*\"` or omitted file on the early workspace branch, plus required `query`.\n- Document mode: required `file`.\n- Optional: `timeout`.\n\n**Execution**\n- Workspace mode sends `workspace/symbol` to every non-custom LSP server, post-filters matches with `filterWorkspaceSymbols()`, deduplicates with `dedupeWorkspaceSymbols()`, then truncates to `WORKSPACE_SYMBOL_LIMIT`.\n- Document mode sends `textDocument/documentSymbol` to the primary server. If the first item has `selectionRange`, it formats hierarchical `DocumentSymbol`s; otherwise it formats flat `SymbolInformation`s.\n\n**Output text**\n- Workspace mode: `Found N symbol(s) matching \"query\":` plus formatted `name @ file:line:col`, with an omission line when over the limit.\n- Document mode: `Symbols in <file>:` plus hierarchical or flat symbol lines.\n\n### `rename`\n**Inputs**\n- Required: `file`, `new_name`.\n- Optional: `line`, `symbol`, `apply`, `timeout`.\n\n**Execution**\n- Waits for project load, sends `textDocument/rename`, receives a `WorkspaceEdit`.\n- `apply !== false` applies edits immediately with `applyWorkspaceEdit()`.\n- `apply === false` renders a preview with `formatWorkspaceEdit()`.\n\n**Output text**\n- `Rename returned no edits`, `Applied rename:` plus applied change lines, or `Rename preview:` plus summarized edits.\n\n### `rename_file`\n**Inputs**\n- Required: `file` source path, `new_name` destination path.\n- Optional: `apply`, `timeout`.\n\n**Execution**\n- Resolves absolute source and destination, rejects identical paths, missing source, existing destination, empty rename set, or directories with more than `MAX_RENAME_PAIRS` files.\n- `enumerateRenamePairs()` returns one `{oldUri,newUri}` pair for a file or walks every regular file in a directory tree.\n- Sends `workspace/willRenameFiles` with `{ files: pairs }` to every non-custom LSP server; collects returned `WorkspaceEdit`s and server notes.\n- Preview mode (`apply === false`) only formats those edits.\n- Apply mode runs each returned `WorkspaceEdit`, renames the source path on disk, sends `textDocument/didClose` for every renamed open file, deletes those `openFiles` entries, then sends `workspace/didRenameFiles`.\n\n**Output text**\n- Preview: `Rename preview: <file-count label> → <dest>` plus per-server edit summaries and optional server notes.\n- Apply: `Renamed <file-count label> → <dest>` plus applied edit summaries, filesystem rename line, and optional server notes.\n\n### `code_actions`\n**Inputs**\n- Required: `file`.\n- Optional: `line`, `symbol`, `query`, `apply`, `timeout`.\n\n**Execution**\n- Reads cached diagnostics for the open URI from `client.diagnostics` and sends `textDocument/codeAction` for a zero-width range at the resolved position.\n- When `apply !== true`, `query` is passed as `context.only: [query]`; this is a server-side kind filter.\n- When `apply === true`, `query` becomes a required client-side selector: either a zero-based numeric index or a case-insensitive substring of the action title.\n- Applying a `CodeAction` uses `applyCodeAction()`: optionally `codeAction/resolve`, then `applyWorkspaceEdit(edit)`, then optional `workspace/executeCommand`.\n- Applying a bare `Command` only runs `workspace/executeCommand`.\n\n**Output text**\n- List mode: `N code action(s):` plus `index: [kind] title` lines.\n- Apply mode success: `Applied \"title\":` plus `Workspace edit:` and/or `Executed command(s):` sections.\n- Apply mode miss: `No code action matches \"query\". Available actions:`.\n- Apply mode with no edit/command: `Action \"title\" has no workspace edit or command to apply`.\n\n### `status`\n**Inputs**\n- None.\n\n**Execution**\n- Reads configured servers from cached `LspConfig`, not `getActiveClients()`.\n- Calls `detectLspmux()` and appends status text when `lspmux` is installed.\n\n**Output text**\n- `Active language servers: ...` or `No language servers configured for this project`, optionally followed by `lspmux: active (multiplexing enabled)` or `lspmux: installed but server not running`.\n\n### `reload`\n**Inputs**\n- Workspace mode: `file: \"*\"` or omitted `file`.\n- Single-file mode: required `file`.\n- Optional: `timeout`.\n\n**Execution**\n- Workspace mode reloads every non-custom LSP server.\n- Single-file mode reloads the primary server for that file.\n- `reloadServer()` tries `rust-analyzer/reloadWorkspace`, then `workspace/didChangeConfiguration` with `{ settings: {} }`; if neither works it kills the process so the next request cold-starts a new client.\n\n**Output text**\n- One line per server: `Reloaded <server>`, `Restarted <server>`, or `Failed to reload <server>: ...`.\n\n### `capabilities`\n**Inputs**\n- Optional: `file`, `timeout`.\n\n**Execution**\n- With a concrete `file`, inspects matching non-custom servers for that file.\n- With omitted `file` or `\"*\"`, inspects every non-custom configured server.\n- Starts servers as needed and dumps `client.serverCapabilities ?? {}` as pretty JSON.\n\n**Output text**\n- Per server: `<server>:` followed by indented `capabilities: { ... }`, or `<server>: failed to start (...)`.\n\n### `request`\n**Inputs**\n- Required: `query` method name.\n- Optional: `file`, `line`, `symbol`, `payload`, `timeout`.\n\n**Execution**\n- Chooses one non-custom server: file-scoped primary server, otherwise the first configured non-custom server.\n- Param building precedence:\n 1. If `payload` is present, parse JSON and use it verbatim.\n 2. Else if `file` is concrete and `line` is present, build `{ textDocument: { uri }, position: { line: line - 1, character } }` using `resolveSymbolColumn()`.\n 3. Else if `file` is concrete, build `{ textDocument: { uri } }`.\n 4. Else use `{}`.\n- Opens the file first when `file` is concrete.\n\n**Output text**\n- Success: `<server> ← <method>:\\n<formatted result>`, where non-string results are `JSON.stringify(..., null, 2)` and nullish values become `null`.\n- Failure: `LSP error from <server> on <method>: ...`.\n\n## Side Effects\n- Filesystem\n - Reads config files, target files, and root markers.\n - `rename` and `code_actions` may edit/create/delete/rename files via `applyWorkspaceEdit()`.\n - `rename_file` always renames the source path on disk in apply mode.\n - Server-initiated `workspace/applyEdit` requests also mutate files through `applyWorkspaceEdit()`.\n- Network\n - None directly; communication is local stdio JSON-RPC to subprocesses.\n- Subprocesses / native bindings\n - Spawns language servers with `ptree.spawn()`.\n - Workspace diagnostics spawns `cargo`, `npx`, `go`, or `pyright`.\n - `BiomeClient` and `SwiftLintClient` spawn CLI tools.\n - Optional `lspmux` detection spawns `lspmux status`; supported servers may be wrapped through `lspmux client`.\n- Session state (transcript, memory, jobs, checkpoints, registries)\n - Caches config per cwd in `configCache`.\n - Caches LSP clients per `command:cwd`, with `pendingRequests`, `diagnostics`, `openFiles`, `serverCapabilities`, and project-load state.\n - Caches custom linter clients by `serverName:cwd`.\n - Updates client `lastActivity`; optional idle-timeout cleanup is driven by `setIdleTimeout()`.\n- Background work / cancellation\n - Every request has an abortable timeout signal.\n - Aborting an in-flight LSP request sends `$/cancelRequest`.\n - Background message readers persist for each live client until process exit/shutdown.\n\n## Limits & Caps\n- Tool timeout clamp: default `20`, min `5`, max `60` seconds — `TOOL_TIMEOUTS.lsp` in `packages/coding-agent/src/tools/tool-timeouts.ts`.\n- LSP request default timeout inside `sendRequest()`: `30_000ms` — `DEFAULT_REQUEST_TIMEOUT_MS` in `packages/coding-agent/src/lsp/client.ts`.\n- Warmup initialize timeout default: `5_000ms` — `WARMUP_TIMEOUT_MS` in `packages/coding-agent/src/lsp/client.ts`.\n- Project-load wait fallback: `15_000ms` — `PROJECT_LOAD_TIMEOUT_MS` in `packages/coding-agent/src/lsp/client.ts`.\n- Idle-client sweep interval when enabled: `60_000ms` — `IDLE_CHECK_INTERVAL_MS` in `packages/coding-agent/src/lsp/client.ts`.\n- Diagnostic message output cap: first `50` messages — `DIAGNOSTIC_MESSAGE_LIMIT` in `packages/coding-agent/src/lsp/index.ts`.\n- Single-file diagnostics wait: `3_000ms` — `SINGLE_DIAGNOSTICS_WAIT_TIMEOUT_MS`.\n- Batch/glob diagnostics wait per file: `400ms` — `BATCH_DIAGNOSTICS_WAIT_TIMEOUT_MS`.\n- Glob diagnostic target cap: first `20` matches — `MAX_GLOB_DIAGNOSTIC_TARGETS`.\n- Workspace symbol cap: first `200` entries — `WORKSPACE_SYMBOL_LIMIT`.\n- Reference context cap: first `50` references include source context — `REFERENCE_CONTEXT_LIMIT`.\n- References retry count: `2` retries, `250ms` backoff — `REFERENCES_RETRY_COUNT`, `REFERENCES_RETRY_DELAY_MS`.\n- Directory rename cap: `1_000` file pairs — `MAX_RENAME_PAIRS`.\n- `detectLspmux()` state cache TTL: `5 * 60 * 1000ms`; liveness check timeout: `1_000ms` — `STATE_CACHE_TTL_MS`, `LIVENESS_TIMEOUT_MS` in `packages/coding-agent/src/lsp/lspmux.ts`.\n- Workspace diagnostics output cap: first `50` lines from the subprocess.\n\n## Errors\n- Missing or invalid inputs are usually returned as text with `details.success: false`, not thrown:\n - missing `file`/`query`/`new_name`\n - invalid JSON in `payload`\n - no matching server\n - invalid `rename_file` source/destination conditions\n- `resolveSymbolColumn()` throws explicit errors for missing files, missing symbols, and out-of-bounds `#N` selectors; these surface as `LSP error: ...` or request-specific error text.\n- `sendRequest()` rejects on timeout with `LSP request <method> timed out after <ms>ms`.\n- Client process exit rejects all pending requests with an exit-code/stderr error assembled in `getOrCreateClient()`.\n- Single-file action failures inside the main `try` become `LSP error: <message>`.\n- `request` has its own error envelope: `LSP error from <server> on <method>: <message>`.\n- Some server failures are intentionally softened:\n - diagnostics continue when one server fails\n - `rename_file` suppresses `workspace/willRenameFiles` “method not found” errors and records other server errors as notes\n - `code_actions` ignores `codeAction/resolve` failures and applies unresolved actions when possible\n- Aborts are not converted to text: `ToolAbortError` is rethrown.\n\n## Notes\n- `status` reports configured/available servers from `LspConfig`, not currently active client processes from `getActiveClients()`.\n- `getLspServerForFile()` excludes `createClient` adapters and linter-only servers; navigation/refactor actions never target Biome/SwiftLint custom clients.\n- `getServersForFile()` matches both file extensions and exact basenames from `fileTypes`; config can target names like `Dockerfile` if present.\n- `symbol` matching is exact first, then case-insensitive, and falls back to the Nth occurrence on the specified line only; it never scans other lines.\n- `code_actions` uses `query` in two different ways: server-side `context.only` filter in list mode, client-side title/index selector in apply mode.\n- `rename` and `rename_file` default to apply. Preview requires `apply: false`.\n- `request` with `file: \"*\"` is treated the same as omitted `file`: it does not build workspace-specific params.\n- `reload` does not recreate a client immediately after killing it; the next request triggers reinitialization.\n- `workspace/applyEdit` can apply edits initiated by the server outside the direct tool action result path.\n- `detectLspmux()` can be disabled with `SKC_DISABLE_LSPMUX=1`; only `rust-analyzer` is in `DEFAULT_SUPPORTED_SERVERS`.\n- Startup LSP warmup (`discoverStartupLspServers(cwd)` in `sdk.ts`) is gated on `enableLsp && options.hasUI && settings.get(\"lsp.diagnosticsOnWrite\")` — print/RPC/ACP/script sessions skip it and let `getOrCreateClient()` cold-start servers on demand. See `docs/sdk.md` § Startup performance.\n- `configCache` is per-process and never auto-invalidated; config changes require a fresh process to be observed by `getConfig()` callers.",
|
|
111
112
|
"tools/monitor.md": "# monitor\n\n> Start a background monitor that streams stdout lines as task notifications. Mirrors Claude Code's `Monitor` tool surface.\n\n## Source\n\n- Entry: `packages/coding-agent/src/tools/monitor.ts`\n- Model-facing prompt: `packages/coding-agent/src/prompts/tools/monitor.md`\n- Key collaborators:\n - `packages/coding-agent/src/async/job-manager.ts` — stores the captured stream via `appendOutput`/`readOutputSince` and delivers the final background-job result.\n - `packages/coding-agent/src/exec/bash-executor.ts` and `packages/coding-agent/src/session/streaming-output.ts` — provide the unthrottled `onRawChunk` capture hook that feeds the manager.\n - `packages/coding-agent/src/tools/bash.ts` — exposes `BashTool.startMonitorJob(...)`, a Bash-aligned helper that preserves interception, cwd/env expansion, artifacts, timeouts, and raw stream capture.\n - `packages/coding-agent/src/tools/job.ts` — polls/cancels the monitor task by id; there is no sibling `MonitorKill` tool.\n\n## Inputs\n\n| Field | Type | Required | Description |\n| --- | --- | --- | --- |\n| `command` | `string` | Yes | Shell command to run as a background monitor. Each sanitized stdout line is delivered as a task-notification. |\n| `kind` | `\"log\" \\| \"poll\" \\| \"watch\" \\| \"other\"` | Yes | Category of monitor. Surfaces in listings. |\n| `description` | `string` | Yes | Short human-readable summary of what is being monitored. |\n| `timeout` | `number` | No | Maximum wall-clock seconds the monitor may run before automatic shutdown. Omit for session lifetime. |\n| `persistent` | `boolean` | No | Keep the monitor running after its first emitted event. Persistent monitors survive until session end, timeout, or explicit cancel via `job`. Defaults to `false`. |\n\n## Outputs\n\nThe tool returns one text block plus `details`:\n\n- `content[0].text`: `Monitor started · task <task_id> · persistent: true|false`.\n- `details`: `{ taskId, kind, description, command, persistent }`.\n\nEach newline-terminated stdout line is appended to the manager-owned cursor and sent to the agent as a `<task-notification>` custom message between turns. Use `job` with the returned `taskId` to inspect completion state or terminate the monitor.\n\n## Behavior / Lifecycle\n\n1. `MonitorTool.createIf(session)` gates the tool on `isBackgroundJobSupportEnabled(session.settings)` — identical to `JobTool`'s gate.\n2. `execute(...)` delegates to `BashTool.startMonitorJob(...)`, so Monitor inherits Bash's interception rules, cwd normalization, internal URL expansion, environment construction, artifact allocation, timeout clamping, and unthrottled raw capture.\n3. The helper mirrors every sanitized raw chunk to `manager.appendOutput(jobId, chunk)` and line-buffers the stream so each stdout line dispatches one `<task-notification>` event.\n4. Non-persistent monitors auto-cancel after delivering their first stdout-line notification. Persistent monitors terminate when the underlying command exits, `timeout` elapses, the calling agent is torn down, or the user cancels the returned background task via `job`.\n\n## Errors\n\n- `ToolError`: `Async execution is disabled; the monitor tool is unavailable in this session.` — emitted when `AsyncJobManager.instance()` returns `undefined`.\n- Invalid parameter shapes are rejected by zod with the project's standard validation error path.\n\n## Examples\n\nTail an error log and react when lines appear:\n\n```jsonc\n{\n \"command\": \"tail -F /var/log/app.log | grep -i error\",\n \"kind\": \"log\",\n \"description\": \"Tail app.log for errors\"\n}\n```\n\nPoll CI status until the build completes (15-minute timeout):\n\n```jsonc\n{\n \"command\": \"while true; do gh run view --json status,conclusion --jq .status; sleep 30; done\",\n \"kind\": \"poll\",\n \"description\": \"Watch CI build status\",\n \"timeout\": 900,\n \"persistent\": false\n}\n```\n\n## Parity oracle\n\nThe schema and behavior captured in this doc are pinned by the fixture at\n`packages/coding-agent/test/fixtures/claude-code-tools/monitor.schema.json`,\ncaptured from the upstream Claude Code CLI (`claude --version 2.1.152`). Any\ndeviation from that fixture is a parity bug.\n",
|