@wahack/pi-coding-agent 15.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +10031 -0
- package/README.md +36 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +104 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/extensions/README.md +142 -0
- package/examples/extensions/api-demo.ts +79 -0
- package/examples/extensions/chalk-logger.ts +25 -0
- package/examples/extensions/hello.ts +31 -0
- package/examples/extensions/pirate.ts +43 -0
- package/examples/extensions/plan-mode.ts +549 -0
- package/examples/extensions/reload-runtime.ts +38 -0
- package/examples/extensions/thinking-note.ts +13 -0
- package/examples/extensions/tools.ts +145 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +17 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +48 -0
- package/examples/hooks/confirm-destructive.ts +58 -0
- package/examples/hooks/custom-compaction.ts +115 -0
- package/examples/hooks/dirty-repo-guard.ts +51 -0
- package/examples/hooks/file-trigger.ts +40 -0
- package/examples/hooks/git-checkpoint.ts +52 -0
- package/examples/hooks/handoff.ts +149 -0
- package/examples/hooks/permission-gate.ts +33 -0
- package/examples/hooks/protected-paths.ts +29 -0
- package/examples/hooks/qna.ts +118 -0
- package/examples/hooks/status-line.ts +39 -0
- package/examples/sdk/01-minimal.ts +21 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +46 -0
- package/examples/sdk/04-skills.ts +43 -0
- package/examples/sdk/06-extensions.ts +82 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +35 -0
- package/examples/sdk/08-prompt-templates.ts +41 -0
- package/examples/sdk/08-slash-commands.ts +46 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +54 -0
- package/examples/sdk/11-sessions.ts +47 -0
- package/examples/sdk/12-redis-sessions.ts +54 -0
- package/examples/sdk/13-sql-sessions.ts +61 -0
- package/examples/sdk/README.md +172 -0
- package/package.json +554 -0
- package/scripts/build-binary.ts +100 -0
- package/scripts/bundle-dist.ts +90 -0
- package/scripts/format-prompts.ts +68 -0
- package/scripts/generate-docs-index.ts +40 -0
- package/scripts/generate-template.ts +33 -0
- package/scripts/omp +42 -0
- package/scripts/omp.ts +19 -0
- package/src/async/index.ts +1 -0
- package/src/async/job-manager.ts +625 -0
- package/src/auto-thinking/classifier.ts +185 -0
- package/src/autoresearch/command-resume.md +14 -0
- package/src/autoresearch/dashboard.ts +436 -0
- package/src/autoresearch/git.ts +319 -0
- package/src/autoresearch/helpers.ts +218 -0
- package/src/autoresearch/index.ts +536 -0
- package/src/autoresearch/prompt-setup.md +43 -0
- package/src/autoresearch/prompt.md +103 -0
- package/src/autoresearch/resume-message.md +10 -0
- package/src/autoresearch/state.ts +273 -0
- package/src/autoresearch/storage.ts +699 -0
- package/src/autoresearch/tools/init-experiment.ts +272 -0
- package/src/autoresearch/tools/log-experiment.ts +524 -0
- package/src/autoresearch/tools/run-experiment.ts +407 -0
- package/src/autoresearch/tools/update-notes.ts +109 -0
- package/src/autoresearch/types.ts +168 -0
- package/src/bun-imports.d.ts +28 -0
- package/src/capability/context-file.ts +44 -0
- package/src/capability/extension-module.ts +34 -0
- package/src/capability/extension.ts +47 -0
- package/src/capability/fs.ts +117 -0
- package/src/capability/hook.ts +40 -0
- package/src/capability/index.ts +436 -0
- package/src/capability/instruction.ts +37 -0
- package/src/capability/mcp.ts +74 -0
- package/src/capability/prompt.ts +35 -0
- package/src/capability/rule-buckets.ts +66 -0
- package/src/capability/rule.ts +261 -0
- package/src/capability/settings.ts +34 -0
- package/src/capability/skill.ts +63 -0
- package/src/capability/slash-command.ts +40 -0
- package/src/capability/ssh.ts +41 -0
- package/src/capability/system-prompt.ts +34 -0
- package/src/capability/tool.ts +38 -0
- package/src/capability/types.ts +168 -0
- package/src/cli/agents-cli.ts +138 -0
- package/src/cli/args.ts +340 -0
- package/src/cli/auth-broker-cli.ts +895 -0
- package/src/cli/auth-gateway-cli.ts +611 -0
- package/src/cli/classify-install-target.ts +76 -0
- package/src/cli/claude-trace-cli.ts +795 -0
- package/src/cli/commands/init-xdg.ts +27 -0
- package/src/cli/completion-gen.ts +550 -0
- package/src/cli/config-cli.ts +418 -0
- package/src/cli/dry-balance-cli.ts +856 -0
- package/src/cli/extension-flags.ts +48 -0
- package/src/cli/file-processor.ts +133 -0
- package/src/cli/gallery-cli.ts +230 -0
- package/src/cli/gallery-fixtures/agentic.ts +407 -0
- package/src/cli/gallery-fixtures/codeintel.ts +187 -0
- package/src/cli/gallery-fixtures/edit.ts +194 -0
- package/src/cli/gallery-fixtures/fs.ts +220 -0
- package/src/cli/gallery-fixtures/index.ts +40 -0
- package/src/cli/gallery-fixtures/interaction.ts +49 -0
- package/src/cli/gallery-fixtures/memory.ts +81 -0
- package/src/cli/gallery-fixtures/misc.ts +250 -0
- package/src/cli/gallery-fixtures/search.ts +213 -0
- package/src/cli/gallery-fixtures/shell.ts +167 -0
- package/src/cli/gallery-fixtures/types.ts +57 -0
- package/src/cli/gallery-fixtures/web.ts +158 -0
- package/src/cli/gallery-screenshot.ts +279 -0
- package/src/cli/grep-cli.ts +160 -0
- package/src/cli/grievances-cli.ts +256 -0
- package/src/cli/initial-message.ts +58 -0
- package/src/cli/list-models.ts +194 -0
- package/src/cli/plugin-cli.ts +996 -0
- package/src/cli/read-cli.ts +57 -0
- package/src/cli/session-picker.ts +79 -0
- package/src/cli/setup-cli.ts +231 -0
- package/src/cli/shell-cli.ts +176 -0
- package/src/cli/ssh-cli.ts +179 -0
- package/src/cli/startup-cwd.ts +68 -0
- package/src/cli/stats-cli.ts +238 -0
- package/src/cli/tiny-models-cli.ts +127 -0
- package/src/cli/update-cli.ts +611 -0
- package/src/cli/usage-cli.ts +603 -0
- package/src/cli/web-search-cli.ts +132 -0
- package/src/cli/worktree-cli.ts +291 -0
- package/src/cli-commands.ts +79 -0
- package/src/cli.ts +200 -0
- package/src/commands/acp.ts +24 -0
- package/src/commands/agents.ts +57 -0
- package/src/commands/auth-broker.ts +99 -0
- package/src/commands/auth-gateway.ts +69 -0
- package/src/commands/commit.ts +46 -0
- package/src/commands/complete.ts +66 -0
- package/src/commands/completions.ts +60 -0
- package/src/commands/config.ts +51 -0
- package/src/commands/dry-balance.ts +43 -0
- package/src/commands/gallery.ts +52 -0
- package/src/commands/grep.ts +48 -0
- package/src/commands/grievances.ts +51 -0
- package/src/commands/install.ts +107 -0
- package/src/commands/launch.ts +169 -0
- package/src/commands/plugin.ts +78 -0
- package/src/commands/read.ts +38 -0
- package/src/commands/setup.ts +67 -0
- package/src/commands/shell.ts +29 -0
- package/src/commands/ssh.ts +60 -0
- package/src/commands/stats.ts +29 -0
- package/src/commands/tiny-models.ts +36 -0
- package/src/commands/update.ts +21 -0
- package/src/commands/usage.ts +35 -0
- package/src/commands/web-search.ts +42 -0
- package/src/commands/worktree.ts +56 -0
- package/src/commit/agentic/agent.ts +317 -0
- package/src/commit/agentic/fallback.ts +96 -0
- package/src/commit/agentic/index.ts +355 -0
- package/src/commit/agentic/prompts/analyze-file.md +22 -0
- package/src/commit/agentic/prompts/session-user.md +25 -0
- package/src/commit/agentic/prompts/split-confirm.md +1 -0
- package/src/commit/agentic/prompts/system.md +38 -0
- package/src/commit/agentic/state.ts +60 -0
- package/src/commit/agentic/tools/analyze-file.ts +146 -0
- package/src/commit/agentic/tools/git-file-diff.ts +191 -0
- package/src/commit/agentic/tools/git-hunk.ts +50 -0
- package/src/commit/agentic/tools/git-overview.ts +81 -0
- package/src/commit/agentic/tools/index.ts +54 -0
- package/src/commit/agentic/tools/propose-changelog.ts +144 -0
- package/src/commit/agentic/tools/propose-commit.ts +109 -0
- package/src/commit/agentic/tools/recent-commits.ts +81 -0
- package/src/commit/agentic/tools/schemas.ts +23 -0
- package/src/commit/agentic/tools/split-commit.ts +245 -0
- package/src/commit/agentic/topo-sort.ts +44 -0
- package/src/commit/agentic/trivial.ts +51 -0
- package/src/commit/agentic/validation.ts +183 -0
- package/src/commit/analysis/conventional.ts +64 -0
- package/src/commit/analysis/index.ts +4 -0
- package/src/commit/analysis/scope.ts +242 -0
- package/src/commit/analysis/summary.ts +105 -0
- package/src/commit/analysis/validation.ts +66 -0
- package/src/commit/changelog/detect.ts +40 -0
- package/src/commit/changelog/generate.ts +97 -0
- package/src/commit/changelog/index.ts +234 -0
- package/src/commit/changelog/parse.ts +44 -0
- package/src/commit/cli.ts +85 -0
- package/src/commit/git/diff.ts +148 -0
- package/src/commit/index.ts +5 -0
- package/src/commit/map-reduce/index.ts +69 -0
- package/src/commit/map-reduce/map-phase.ts +193 -0
- package/src/commit/map-reduce/reduce-phase.ts +49 -0
- package/src/commit/map-reduce/utils.ts +9 -0
- package/src/commit/message.ts +11 -0
- package/src/commit/model-selection.ts +92 -0
- package/src/commit/pipeline.ts +243 -0
- package/src/commit/prompts/analysis-system.md +148 -0
- package/src/commit/prompts/analysis-user.md +38 -0
- package/src/commit/prompts/changelog-system.md +50 -0
- package/src/commit/prompts/changelog-user.md +18 -0
- package/src/commit/prompts/file-observer-system.md +24 -0
- package/src/commit/prompts/file-observer-user.md +8 -0
- package/src/commit/prompts/reduce-system.md +50 -0
- package/src/commit/prompts/reduce-user.md +17 -0
- package/src/commit/prompts/summary-retry.md +3 -0
- package/src/commit/prompts/summary-system.md +38 -0
- package/src/commit/prompts/summary-user.md +13 -0
- package/src/commit/prompts/types-description.md +2 -0
- package/src/commit/shared-llm.ts +77 -0
- package/src/commit/types.ts +118 -0
- package/src/commit/utils/exclusions.ts +42 -0
- package/src/commit/utils.ts +58 -0
- package/src/config/api-key-resolver.ts +60 -0
- package/src/config/append-only-context-mode.ts +31 -0
- package/src/config/config-file.ts +317 -0
- package/src/config/file-lock.ts +164 -0
- package/src/config/keybindings.ts +628 -0
- package/src/config/mcp-schema.json +230 -0
- package/src/config/model-discovery.ts +554 -0
- package/src/config/model-registry.ts +2090 -0
- package/src/config/model-resolver.ts +1502 -0
- package/src/config/model-roles.ts +74 -0
- package/src/config/models-config-schema.ts +226 -0
- package/src/config/models-config.ts +129 -0
- package/src/config/prompt-templates.ts +185 -0
- package/src/config/resolve-config-value.ts +94 -0
- package/src/config/settings-schema.ts +3530 -0
- package/src/config/settings.ts +1178 -0
- package/src/config.ts +242 -0
- package/src/cursor.ts +340 -0
- package/src/dap/client.ts +760 -0
- package/src/dap/config.ts +189 -0
- package/src/dap/defaults.json +212 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1441 -0
- package/src/dap/types.ts +610 -0
- package/src/debug/index.ts +515 -0
- package/src/debug/log-formatting.ts +58 -0
- package/src/debug/log-viewer.ts +908 -0
- package/src/debug/profiler.ts +162 -0
- package/src/debug/protocol-probe.ts +267 -0
- package/src/debug/raw-sse-buffer.ts +273 -0
- package/src/debug/raw-sse.ts +292 -0
- package/src/debug/report-bundle.ts +374 -0
- package/src/debug/system-info.ts +111 -0
- package/src/debug/terminal-info.ts +124 -0
- package/src/discovery/agents-md.ts +67 -0
- package/src/discovery/agents.ts +230 -0
- package/src/discovery/at-imports.ts +273 -0
- package/src/discovery/builtin-defaults.ts +39 -0
- package/src/discovery/builtin-rules/index.ts +54 -0
- package/src/discovery/builtin-rules/rs-box-leak.md +48 -0
- package/src/discovery/builtin-rules/rs-future-prelude.md +23 -0
- package/src/discovery/builtin-rules/rs-lazylock.md +51 -0
- package/src/discovery/builtin-rules/rs-match-ergonomics.md +67 -0
- package/src/discovery/builtin-rules/rs-parking-lot.md +44 -0
- package/src/discovery/builtin-rules/rs-result-type.md +19 -0
- package/src/discovery/builtin-rules/ts-bare-catch.md +38 -0
- package/src/discovery/builtin-rules/ts-import-type.md +42 -0
- package/src/discovery/builtin-rules/ts-no-any.md +56 -0
- package/src/discovery/builtin-rules/ts-no-deprecated-leftovers.md +44 -0
- package/src/discovery/builtin-rules/ts-no-dynamic-import.md +39 -0
- package/src/discovery/builtin-rules/ts-no-return-type.md +45 -0
- package/src/discovery/builtin-rules/ts-no-test-timers.md +55 -0
- package/src/discovery/builtin-rules/ts-no-tiny-functions.md +51 -0
- package/src/discovery/builtin-rules/ts-promise-with-resolvers.md +65 -0
- package/src/discovery/builtin-rules/ts-redundant-clear-guard.md +75 -0
- package/src/discovery/builtin-rules/ts-set-map.md +28 -0
- package/src/discovery/builtin.ts +906 -0
- package/src/discovery/claude-plugins.ts +386 -0
- package/src/discovery/claude.ts +584 -0
- package/src/discovery/cline.ts +83 -0
- package/src/discovery/codex.ts +522 -0
- package/src/discovery/cursor.ts +220 -0
- package/src/discovery/gemini.ts +383 -0
- package/src/discovery/github.ts +154 -0
- package/src/discovery/helpers.ts +1016 -0
- package/src/discovery/index.ts +81 -0
- package/src/discovery/mcp-json.ts +171 -0
- package/src/discovery/omp-extension-roots.ts +190 -0
- package/src/discovery/omp-plugins.ts +383 -0
- package/src/discovery/opencode.ts +398 -0
- package/src/discovery/plugin-dir-roots.ts +28 -0
- package/src/discovery/ssh.ts +153 -0
- package/src/discovery/substitute-plugin-root.ts +29 -0
- package/src/discovery/vscode.ts +105 -0
- package/src/discovery/windsurf.ts +147 -0
- package/src/edit/apply-patch/index.ts +87 -0
- package/src/edit/apply-patch/parser.ts +174 -0
- package/src/edit/diff.ts +999 -0
- package/src/edit/file-snapshot-store.ts +91 -0
- package/src/edit/hashline/block-resolver.ts +33 -0
- package/src/edit/hashline/diff.ts +290 -0
- package/src/edit/hashline/execute.ts +242 -0
- package/src/edit/hashline/filesystem.ts +130 -0
- package/src/edit/hashline/index.ts +5 -0
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/edit/hashline/params.ts +18 -0
- package/src/edit/index.ts +571 -0
- package/src/edit/modes/apply-patch.lark +19 -0
- package/src/edit/modes/apply-patch.ts +53 -0
- package/src/edit/modes/patch.ts +1891 -0
- package/src/edit/modes/replace.ts +1137 -0
- package/src/edit/normalize.ts +345 -0
- package/src/edit/notebook.ts +242 -0
- package/src/edit/read-file.ts +25 -0
- package/src/edit/renderer.ts +769 -0
- package/src/edit/streaming.ts +517 -0
- package/src/eval/__tests__/agent-bridge.test.ts +708 -0
- package/src/eval/__tests__/bridge-timeout.test.ts +64 -0
- package/src/eval/__tests__/budget-bridge.test.ts +69 -0
- package/src/eval/__tests__/completion-bridge.test.ts +412 -0
- package/src/eval/__tests__/helpers-local-roots.test.ts +58 -0
- package/src/eval/__tests__/idle-timeout.test.ts +80 -0
- package/src/eval/__tests__/js-context-manager.test.ts +241 -0
- package/src/eval/__tests__/kernel-spawn.test.ts +103 -0
- package/src/eval/agent-bridge.ts +319 -0
- package/src/eval/backend.ts +71 -0
- package/src/eval/bridge-timeout.ts +44 -0
- package/src/eval/budget-bridge.ts +48 -0
- package/src/eval/completion-bridge.ts +207 -0
- package/src/eval/concurrency-bridge.ts +34 -0
- package/src/eval/idle-timeout.ts +91 -0
- package/src/eval/index.ts +4 -0
- package/src/eval/js/context-manager.ts +502 -0
- package/src/eval/js/executor.ts +173 -0
- package/src/eval/js/index.ts +51 -0
- package/src/eval/js/shared/helpers.ts +283 -0
- package/src/eval/js/shared/indirect-eval.ts +30 -0
- package/src/eval/js/shared/local-module-loader.ts +342 -0
- package/src/eval/js/shared/prelude.ts +2 -0
- package/src/eval/js/shared/prelude.txt +246 -0
- package/src/eval/js/shared/rewrite-imports.ts +532 -0
- package/src/eval/js/shared/runtime.ts +352 -0
- package/src/eval/js/shared/types.ts +18 -0
- package/src/eval/js/tool-bridge.ts +162 -0
- package/src/eval/js/worker-core.ts +132 -0
- package/src/eval/js/worker-entry.ts +30 -0
- package/src/eval/js/worker-protocol.ts +47 -0
- package/src/eval/py/__tests__/prelude.test.ts +19 -0
- package/src/eval/py/display.ts +71 -0
- package/src/eval/py/executor.ts +742 -0
- package/src/eval/py/index.ts +68 -0
- package/src/eval/py/kernel.ts +748 -0
- package/src/eval/py/prelude.py +658 -0
- package/src/eval/py/prelude.ts +3 -0
- package/src/eval/py/runner.py +1133 -0
- package/src/eval/py/runtime.ts +276 -0
- package/src/eval/py/spawn-options.ts +126 -0
- package/src/eval/py/tool-bridge.ts +182 -0
- package/src/eval/session-id.ts +8 -0
- package/src/eval/types.ts +48 -0
- package/src/exa/index.ts +2 -0
- package/src/exa/mcp-client.ts +370 -0
- package/src/exa/types.ts +69 -0
- package/src/exec/bash-executor.ts +419 -0
- package/src/exec/exec.ts +53 -0
- package/src/exec/non-interactive-env.ts +48 -0
- package/src/export/custom-share.ts +65 -0
- package/src/export/html/index.ts +164 -0
- package/src/export/html/template.css +1051 -0
- package/src/export/html/template.generated.ts +2 -0
- package/src/export/html/template.html +46 -0
- package/src/export/html/template.js +2271 -0
- package/src/export/html/template.macro.ts +25 -0
- package/src/export/html/vendor/highlight.min.js +1213 -0
- package/src/export/html/vendor/marked.min.js +6 -0
- package/src/export/ttsr.ts +583 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +54 -0
- package/src/extensibility/custom-commands/bundled/review/index.ts +489 -0
- package/src/extensibility/custom-commands/index.ts +2 -0
- package/src/extensibility/custom-commands/loader.ts +238 -0
- package/src/extensibility/custom-commands/types.ts +113 -0
- package/src/extensibility/custom-tools/index.ts +7 -0
- package/src/extensibility/custom-tools/loader.ts +269 -0
- package/src/extensibility/custom-tools/types.ts +270 -0
- package/src/extensibility/custom-tools/wrapper.ts +47 -0
- package/src/extensibility/extensions/compact-handler.ts +40 -0
- package/src/extensibility/extensions/get-commands-handler.ts +78 -0
- package/src/extensibility/extensions/index.ts +16 -0
- package/src/extensibility/extensions/loader.ts +572 -0
- package/src/extensibility/extensions/runner.ts +922 -0
- package/src/extensibility/extensions/types.ts +1322 -0
- package/src/extensibility/extensions/wrapper.ts +223 -0
- package/src/extensibility/hooks/index.ts +5 -0
- package/src/extensibility/hooks/loader.ts +257 -0
- package/src/extensibility/hooks/runner.ts +425 -0
- package/src/extensibility/hooks/tool-wrapper.ts +107 -0
- package/src/extensibility/hooks/types.ts +606 -0
- package/src/extensibility/legacy-pi-ai-shim.ts +24 -0
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +15 -0
- package/src/extensibility/plugins/doctor.ts +65 -0
- package/src/extensibility/plugins/git-url.ts +367 -0
- package/src/extensibility/plugins/index.ts +9 -0
- package/src/extensibility/plugins/installer.ts +192 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +682 -0
- package/src/extensibility/plugins/loader.ts +313 -0
- package/src/extensibility/plugins/manager.ts +827 -0
- package/src/extensibility/plugins/marketplace/cache.ts +136 -0
- package/src/extensibility/plugins/marketplace/fetcher.ts +317 -0
- package/src/extensibility/plugins/marketplace/index.ts +6 -0
- package/src/extensibility/plugins/marketplace/manager.ts +770 -0
- package/src/extensibility/plugins/marketplace/registry.ts +196 -0
- package/src/extensibility/plugins/marketplace/source-resolver.ts +147 -0
- package/src/extensibility/plugins/marketplace/types.ts +191 -0
- package/src/extensibility/plugins/marketplace-auto-update.ts +49 -0
- package/src/extensibility/plugins/parser.ts +105 -0
- package/src/extensibility/plugins/types.ts +194 -0
- package/src/extensibility/shared-events.ts +343 -0
- package/src/extensibility/skills.ts +312 -0
- package/src/extensibility/slash-commands.ts +227 -0
- package/src/extensibility/tool-proxy.ts +25 -0
- package/src/extensibility/typebox.ts +418 -0
- package/src/extensibility/utils.ts +44 -0
- package/src/goals/index.ts +3 -0
- package/src/goals/runtime.ts +528 -0
- package/src/goals/state.ts +37 -0
- package/src/goals/tools/goal-tool.ts +251 -0
- package/src/hindsight/backend.ts +354 -0
- package/src/hindsight/bank.ts +156 -0
- package/src/hindsight/client.ts +598 -0
- package/src/hindsight/config.ts +175 -0
- package/src/hindsight/content.ts +210 -0
- package/src/hindsight/index.ts +8 -0
- package/src/hindsight/mental-models.ts +429 -0
- package/src/hindsight/seeds.json +32 -0
- package/src/hindsight/state.ts +488 -0
- package/src/hindsight/transcript.ts +71 -0
- package/src/index.ts +59 -0
- package/src/internal-urls/agent-protocol.ts +146 -0
- package/src/internal-urls/artifact-protocol.ts +107 -0
- package/src/internal-urls/docs-index.generated.ts +106 -0
- package/src/internal-urls/history-protocol.ts +113 -0
- package/src/internal-urls/index.ts +25 -0
- package/src/internal-urls/issue-pr-protocol.ts +584 -0
- package/src/internal-urls/json-query.ts +126 -0
- package/src/internal-urls/local-protocol.ts +287 -0
- package/src/internal-urls/mcp-protocol.ts +151 -0
- package/src/internal-urls/memory-protocol.ts +169 -0
- package/src/internal-urls/omp-protocol.ts +93 -0
- package/src/internal-urls/parse.ts +72 -0
- package/src/internal-urls/registry-helpers.ts +25 -0
- package/src/internal-urls/router.ts +105 -0
- package/src/internal-urls/rule-protocol.ts +45 -0
- package/src/internal-urls/skill-protocol.ts +96 -0
- package/src/internal-urls/types.ts +152 -0
- package/src/internal-urls/vault-protocol.ts +936 -0
- package/src/irc/bus.ts +292 -0
- package/src/lib/xai-http.ts +124 -0
- package/src/lsp/client.ts +1193 -0
- package/src/lsp/clients/biome-client.ts +264 -0
- package/src/lsp/clients/index.ts +50 -0
- package/src/lsp/clients/lsp-linter-client.ts +93 -0
- package/src/lsp/clients/swiftlint-client.ts +120 -0
- package/src/lsp/config.ts +502 -0
- package/src/lsp/defaults.json +493 -0
- package/src/lsp/diagnostics-ledger.ts +51 -0
- package/src/lsp/edits.ts +267 -0
- package/src/lsp/index.ts +2477 -0
- package/src/lsp/lspmux.ts +233 -0
- package/src/lsp/render.ts +694 -0
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +455 -0
- package/src/lsp/utils.ts +718 -0
- package/src/main.ts +1325 -0
- package/src/mcp/client.ts +484 -0
- package/src/mcp/config-writer.ts +225 -0
- package/src/mcp/config.ts +365 -0
- package/src/mcp/index.ts +29 -0
- package/src/mcp/json-rpc.ts +122 -0
- package/src/mcp/loader.ts +124 -0
- package/src/mcp/manager.ts +1275 -0
- package/src/mcp/oauth-discovery.ts +442 -0
- package/src/mcp/oauth-flow.ts +442 -0
- package/src/mcp/render.ts +132 -0
- package/src/mcp/smithery-auth.ts +104 -0
- package/src/mcp/smithery-connect.ts +145 -0
- package/src/mcp/smithery-registry.ts +477 -0
- package/src/mcp/timeout.ts +59 -0
- package/src/mcp/tool-bridge.ts +426 -0
- package/src/mcp/tool-cache.ts +117 -0
- package/src/mcp/transports/http.ts +519 -0
- package/src/mcp/transports/index.ts +6 -0
- package/src/mcp/transports/stdio.ts +528 -0
- package/src/mcp/types.ts +423 -0
- package/src/memories/index.ts +1150 -0
- package/src/memories/storage.ts +577 -0
- package/src/memory-backend/index.ts +18 -0
- package/src/memory-backend/local-backend.ts +39 -0
- package/src/memory-backend/off-backend.ts +25 -0
- package/src/memory-backend/resolve.ts +25 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/types.ts +166 -0
- package/src/mnemopi/backend.ts +547 -0
- package/src/mnemopi/config.ts +160 -0
- package/src/mnemopi/index.ts +3 -0
- package/src/mnemopi/state.ts +584 -0
- package/src/modes/acp/acp-agent.ts +2407 -0
- package/src/modes/acp/acp-client-bridge.ts +154 -0
- package/src/modes/acp/acp-event-mapper.ts +929 -0
- package/src/modes/acp/acp-mode.ts +23 -0
- package/src/modes/acp/index.ts +2 -0
- package/src/modes/acp/terminal-auth.ts +37 -0
- package/src/modes/components/agent-dashboard.ts +1206 -0
- package/src/modes/components/agent-hub.ts +1071 -0
- package/src/modes/components/assistant-message.ts +307 -0
- package/src/modes/components/bash-execution.ts +220 -0
- package/src/modes/components/bordered-loader.ts +41 -0
- package/src/modes/components/branch-summary-message.ts +45 -0
- package/src/modes/components/btw-panel.ts +104 -0
- package/src/modes/components/chat-block.ts +111 -0
- package/src/modes/components/compaction-summary-message.ts +87 -0
- package/src/modes/components/copy-selector.ts +206 -0
- package/src/modes/components/countdown-timer.ts +75 -0
- package/src/modes/components/custom-editor.ts +398 -0
- package/src/modes/components/custom-message.ts +63 -0
- package/src/modes/components/diff.ts +277 -0
- package/src/modes/components/dynamic-border.ts +34 -0
- package/src/modes/components/error-banner.ts +33 -0
- package/src/modes/components/eval-execution.ts +158 -0
- package/src/modes/components/execution-shared.ts +101 -0
- package/src/modes/components/extensions/extension-dashboard.ts +399 -0
- package/src/modes/components/extensions/extension-list.ts +502 -0
- package/src/modes/components/extensions/index.ts +9 -0
- package/src/modes/components/extensions/inspector-panel.ts +317 -0
- package/src/modes/components/extensions/state-manager.ts +627 -0
- package/src/modes/components/extensions/types.ts +186 -0
- package/src/modes/components/footer.ts +274 -0
- package/src/modes/components/history-search.ts +280 -0
- package/src/modes/components/hook-editor.ts +167 -0
- package/src/modes/components/hook-input.ts +87 -0
- package/src/modes/components/hook-message.ts +66 -0
- package/src/modes/components/hook-selector.ts +660 -0
- package/src/modes/components/index.ts +38 -0
- package/src/modes/components/keybinding-hints.ts +65 -0
- package/src/modes/components/late-diagnostics-message.ts +60 -0
- package/src/modes/components/login-dialog.ts +164 -0
- package/src/modes/components/mcp-add-wizard.ts +1340 -0
- package/src/modes/components/message-frame.ts +88 -0
- package/src/modes/components/model-selector.ts +1271 -0
- package/src/modes/components/oauth-selector.ts +368 -0
- package/src/modes/components/omfg-panel.ts +141 -0
- package/src/modes/components/overlay-box.ts +108 -0
- package/src/modes/components/plan-review-overlay.ts +820 -0
- package/src/modes/components/plan-toc.ts +138 -0
- package/src/modes/components/plugin-selector.ts +95 -0
- package/src/modes/components/plugin-settings.ts +722 -0
- package/src/modes/components/queue-mode-selector.ts +56 -0
- package/src/modes/components/read-tool-group.ts +670 -0
- package/src/modes/components/segment-track.ts +52 -0
- package/src/modes/components/session-selector.ts +625 -0
- package/src/modes/components/settings-defs.ts +189 -0
- package/src/modes/components/settings-selector.ts +651 -0
- package/src/modes/components/show-images-selector.ts +45 -0
- package/src/modes/components/skill-message.ts +89 -0
- package/src/modes/components/status-line/component.ts +869 -0
- package/src/modes/components/status-line/context-thresholds.ts +79 -0
- package/src/modes/components/status-line/git-utils.ts +42 -0
- package/src/modes/components/status-line/index.ts +5 -0
- package/src/modes/components/status-line/presets.ts +106 -0
- package/src/modes/components/status-line/segments.ts +584 -0
- package/src/modes/components/status-line/separators.ts +55 -0
- package/src/modes/components/status-line/token-rate.ts +66 -0
- package/src/modes/components/status-line/types.ts +108 -0
- package/src/modes/components/theme-selector.ts +63 -0
- package/src/modes/components/thinking-selector.ts +52 -0
- package/src/modes/components/tiny-title-download-progress.ts +90 -0
- package/src/modes/components/tips.txt +19 -0
- package/src/modes/components/todo-reminder.ts +38 -0
- package/src/modes/components/tool-execution.ts +1024 -0
- package/src/modes/components/transcript-container.ts +608 -0
- package/src/modes/components/tree-selector.ts +978 -0
- package/src/modes/components/ttsr-notification.ts +122 -0
- package/src/modes/components/user-message-selector.ts +227 -0
- package/src/modes/components/user-message.ts +66 -0
- package/src/modes/components/visual-truncate.ts +63 -0
- package/src/modes/components/welcome.ts +493 -0
- package/src/modes/controllers/btw-controller.ts +105 -0
- package/src/modes/controllers/command-controller-shared.ts +109 -0
- package/src/modes/controllers/command-controller.ts +1566 -0
- package/src/modes/controllers/event-controller.ts +1054 -0
- package/src/modes/controllers/extension-ui-controller.ts +886 -0
- package/src/modes/controllers/input-controller.ts +1073 -0
- package/src/modes/controllers/mcp-command-controller.ts +2017 -0
- package/src/modes/controllers/omfg-controller.ts +283 -0
- package/src/modes/controllers/omfg-rule.ts +647 -0
- package/src/modes/controllers/selector-controller.ts +1108 -0
- package/src/modes/controllers/ssh-command-controller.ts +384 -0
- package/src/modes/controllers/streaming-reveal.ts +279 -0
- package/src/modes/controllers/tan-command-controller.ts +173 -0
- package/src/modes/controllers/todo-command-controller.ts +485 -0
- package/src/modes/data/emojis.json +1 -0
- package/src/modes/emoji-autocomplete.ts +285 -0
- package/src/modes/gradient-highlight.ts +87 -0
- package/src/modes/image-references.ts +117 -0
- package/src/modes/index.ts +17 -0
- package/src/modes/interactive-mode.ts +3370 -0
- package/src/modes/internal-url-autocomplete.ts +143 -0
- package/src/modes/loop-limit.ts +140 -0
- package/src/modes/magic-keywords.ts +20 -0
- package/src/modes/markdown-prose.ts +247 -0
- package/src/modes/oauth-manual-input.ts +69 -0
- package/src/modes/orchestrate.ts +42 -0
- package/src/modes/print-mode.ts +126 -0
- package/src/modes/prompt-action-autocomplete.ts +260 -0
- package/src/modes/rpc/host-tools.ts +186 -0
- package/src/modes/rpc/host-uris.ts +235 -0
- package/src/modes/rpc/rpc-client.ts +963 -0
- package/src/modes/rpc/rpc-mode.ts +947 -0
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +458 -0
- package/src/modes/runtime-init.ts +116 -0
- package/src/modes/session-observer-registry.ts +146 -0
- package/src/modes/setup-version.ts +11 -0
- package/src/modes/setup-wizard/index.ts +99 -0
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +96 -0
- package/src/modes/setup-wizard/scenes/outro.ts +35 -0
- package/src/modes/setup-wizard/scenes/providers.ts +69 -0
- package/src/modes/setup-wizard/scenes/sign-in.ts +205 -0
- package/src/modes/setup-wizard/scenes/splash.ts +201 -0
- package/src/modes/setup-wizard/scenes/theme.ts +299 -0
- package/src/modes/setup-wizard/scenes/types.ts +48 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +129 -0
- package/src/modes/setup-wizard/wizard-overlay.ts +275 -0
- package/src/modes/shared.ts +47 -0
- package/src/modes/theme/dark.json +95 -0
- package/src/modes/theme/defaults/alabaster.json +93 -0
- package/src/modes/theme/defaults/amethyst.json +96 -0
- package/src/modes/theme/defaults/anthracite.json +93 -0
- package/src/modes/theme/defaults/basalt.json +91 -0
- package/src/modes/theme/defaults/birch.json +95 -0
- package/src/modes/theme/defaults/dark-abyss.json +91 -0
- package/src/modes/theme/defaults/dark-arctic.json +104 -0
- package/src/modes/theme/defaults/dark-aurora.json +95 -0
- package/src/modes/theme/defaults/dark-catppuccin.json +107 -0
- package/src/modes/theme/defaults/dark-cavern.json +91 -0
- package/src/modes/theme/defaults/dark-copper.json +95 -0
- package/src/modes/theme/defaults/dark-cosmos.json +90 -0
- package/src/modes/theme/defaults/dark-cyberpunk.json +102 -0
- package/src/modes/theme/defaults/dark-dracula.json +98 -0
- package/src/modes/theme/defaults/dark-eclipse.json +91 -0
- package/src/modes/theme/defaults/dark-ember.json +95 -0
- package/src/modes/theme/defaults/dark-equinox.json +90 -0
- package/src/modes/theme/defaults/dark-forest.json +96 -0
- package/src/modes/theme/defaults/dark-github.json +105 -0
- package/src/modes/theme/defaults/dark-gruvbox.json +112 -0
- package/src/modes/theme/defaults/dark-lavender.json +95 -0
- package/src/modes/theme/defaults/dark-lunar.json +89 -0
- package/src/modes/theme/defaults/dark-midnight.json +95 -0
- package/src/modes/theme/defaults/dark-monochrome.json +94 -0
- package/src/modes/theme/defaults/dark-monokai.json +98 -0
- package/src/modes/theme/defaults/dark-nebula.json +90 -0
- package/src/modes/theme/defaults/dark-nord.json +97 -0
- package/src/modes/theme/defaults/dark-ocean.json +101 -0
- package/src/modes/theme/defaults/dark-one.json +100 -0
- package/src/modes/theme/defaults/dark-poimandres.json +142 -0
- package/src/modes/theme/defaults/dark-rainforest.json +91 -0
- package/src/modes/theme/defaults/dark-reef.json +91 -0
- package/src/modes/theme/defaults/dark-retro.json +92 -0
- package/src/modes/theme/defaults/dark-rose-pine.json +96 -0
- package/src/modes/theme/defaults/dark-sakura.json +95 -0
- package/src/modes/theme/defaults/dark-slate.json +95 -0
- package/src/modes/theme/defaults/dark-solarized.json +97 -0
- package/src/modes/theme/defaults/dark-solstice.json +90 -0
- package/src/modes/theme/defaults/dark-starfall.json +91 -0
- package/src/modes/theme/defaults/dark-sunset.json +99 -0
- package/src/modes/theme/defaults/dark-swamp.json +90 -0
- package/src/modes/theme/defaults/dark-synthwave.json +103 -0
- package/src/modes/theme/defaults/dark-taiga.json +91 -0
- package/src/modes/theme/defaults/dark-terminal.json +95 -0
- package/src/modes/theme/defaults/dark-tokyo-night.json +101 -0
- package/src/modes/theme/defaults/dark-tundra.json +91 -0
- package/src/modes/theme/defaults/dark-twilight.json +91 -0
- package/src/modes/theme/defaults/dark-volcanic.json +91 -0
- package/src/modes/theme/defaults/graphite.json +92 -0
- package/src/modes/theme/defaults/index.ts +199 -0
- package/src/modes/theme/defaults/light-arctic.json +107 -0
- package/src/modes/theme/defaults/light-aurora-day.json +91 -0
- package/src/modes/theme/defaults/light-canyon.json +91 -0
- package/src/modes/theme/defaults/light-catppuccin.json +106 -0
- package/src/modes/theme/defaults/light-cirrus.json +90 -0
- package/src/modes/theme/defaults/light-coral.json +95 -0
- package/src/modes/theme/defaults/light-cyberpunk.json +96 -0
- package/src/modes/theme/defaults/light-dawn.json +90 -0
- package/src/modes/theme/defaults/light-dunes.json +91 -0
- package/src/modes/theme/defaults/light-eucalyptus.json +95 -0
- package/src/modes/theme/defaults/light-forest.json +100 -0
- package/src/modes/theme/defaults/light-frost.json +95 -0
- package/src/modes/theme/defaults/light-github.json +115 -0
- package/src/modes/theme/defaults/light-glacier.json +91 -0
- package/src/modes/theme/defaults/light-gruvbox.json +108 -0
- package/src/modes/theme/defaults/light-haze.json +90 -0
- package/src/modes/theme/defaults/light-honeycomb.json +95 -0
- package/src/modes/theme/defaults/light-lagoon.json +91 -0
- package/src/modes/theme/defaults/light-lavender.json +95 -0
- package/src/modes/theme/defaults/light-meadow.json +91 -0
- package/src/modes/theme/defaults/light-mint.json +95 -0
- package/src/modes/theme/defaults/light-monochrome.json +101 -0
- package/src/modes/theme/defaults/light-ocean.json +99 -0
- package/src/modes/theme/defaults/light-one.json +99 -0
- package/src/modes/theme/defaults/light-opal.json +91 -0
- package/src/modes/theme/defaults/light-orchard.json +91 -0
- package/src/modes/theme/defaults/light-paper.json +95 -0
- package/src/modes/theme/defaults/light-poimandres.json +142 -0
- package/src/modes/theme/defaults/light-prism.json +90 -0
- package/src/modes/theme/defaults/light-retro.json +98 -0
- package/src/modes/theme/defaults/light-sand.json +95 -0
- package/src/modes/theme/defaults/light-savanna.json +91 -0
- package/src/modes/theme/defaults/light-solarized.json +102 -0
- package/src/modes/theme/defaults/light-soleil.json +90 -0
- package/src/modes/theme/defaults/light-sunset.json +99 -0
- package/src/modes/theme/defaults/light-synthwave.json +98 -0
- package/src/modes/theme/defaults/light-tokyo-night.json +111 -0
- package/src/modes/theme/defaults/light-wetland.json +91 -0
- package/src/modes/theme/defaults/light-zenith.json +89 -0
- package/src/modes/theme/defaults/limestone.json +94 -0
- package/src/modes/theme/defaults/mahogany.json +97 -0
- package/src/modes/theme/defaults/marble.json +93 -0
- package/src/modes/theme/defaults/obsidian.json +91 -0
- package/src/modes/theme/defaults/onyx.json +91 -0
- package/src/modes/theme/defaults/pearl.json +93 -0
- package/src/modes/theme/defaults/porcelain.json +91 -0
- package/src/modes/theme/defaults/quartz.json +96 -0
- package/src/modes/theme/defaults/sandstone.json +95 -0
- package/src/modes/theme/defaults/titanium.json +90 -0
- package/src/modes/theme/light.json +93 -0
- package/src/modes/theme/mermaid-cache.ts +29 -0
- package/src/modes/theme/shimmer.ts +235 -0
- package/src/modes/theme/theme-schema.json +459 -0
- package/src/modes/theme/theme.ts +2676 -0
- package/src/modes/turn-budget.ts +31 -0
- package/src/modes/types.ts +359 -0
- package/src/modes/ultrathink.ts +41 -0
- package/src/modes/utils/context-usage.ts +339 -0
- package/src/modes/utils/copy-targets.ts +360 -0
- package/src/modes/utils/hotkeys-markdown.ts +61 -0
- package/src/modes/utils/keybinding-matchers.ts +51 -0
- package/src/modes/utils/tools-markdown.ts +27 -0
- package/src/modes/utils/ui-helpers.ts +801 -0
- package/src/modes/workflow.ts +42 -0
- package/src/plan-mode/approved-plan.ts +186 -0
- package/src/plan-mode/plan-handoff.ts +37 -0
- package/src/plan-mode/plan-protection.ts +31 -0
- package/src/plan-mode/state.ts +6 -0
- package/src/priority.json +41 -0
- package/src/prompts/agents/designer.md +66 -0
- package/src/prompts/agents/explore.md +58 -0
- package/src/prompts/agents/frontmatter.md +11 -0
- package/src/prompts/agents/init.md +33 -0
- package/src/prompts/agents/librarian.md +119 -0
- package/src/prompts/agents/oracle.md +55 -0
- package/src/prompts/agents/plan.md +48 -0
- package/src/prompts/agents/reviewer.md +140 -0
- package/src/prompts/agents/task.md +16 -0
- package/src/prompts/ci-green-request.md +36 -0
- package/src/prompts/dry-balance-bench.md +8 -0
- package/src/prompts/goals/goal-budget-limit.md +16 -0
- package/src/prompts/goals/goal-continuation.md +28 -0
- package/src/prompts/goals/goal-mode-active.md +23 -0
- package/src/prompts/memories/consolidation.md +30 -0
- package/src/prompts/memories/read-path.md +11 -0
- package/src/prompts/memories/stage_one_input.md +6 -0
- package/src/prompts/memories/stage_one_system.md +21 -0
- package/src/prompts/review-custom-request.md +22 -0
- package/src/prompts/review-headless-request.md +16 -0
- package/src/prompts/review-request.md +69 -0
- package/src/prompts/steering/user-interjection.md +10 -0
- package/src/prompts/system/agent-creation-architect.md +50 -0
- package/src/prompts/system/agent-creation-user.md +6 -0
- package/src/prompts/system/auto-continue.md +1 -0
- package/src/prompts/system/auto-thinking-difficulty-local.md +14 -0
- package/src/prompts/system/auto-thinking-difficulty.md +12 -0
- package/src/prompts/system/background-tan-dispatch.md +8 -0
- package/src/prompts/system/btw-user.md +8 -0
- package/src/prompts/system/commit-message-system.md +14 -0
- package/src/prompts/system/custom-system-prompt.md +64 -0
- package/src/prompts/system/eager-todo.md +13 -0
- package/src/prompts/system/empty-stop-retry.md +6 -0
- package/src/prompts/system/irc-incoming.md +7 -0
- package/src/prompts/system/manual-continue.md +7 -0
- package/src/prompts/system/memory-consolidation-system.md +8 -0
- package/src/prompts/system/memory-extraction-system.md +26 -0
- package/src/prompts/system/omfg-user.md +50 -0
- package/src/prompts/system/orchestrate-notice.md +40 -0
- package/src/prompts/system/plan-mode-active.md +109 -0
- package/src/prompts/system/plan-mode-approved.md +25 -0
- package/src/prompts/system/plan-mode-compact-instructions.md +16 -0
- package/src/prompts/system/plan-mode-reference.md +11 -0
- package/src/prompts/system/plan-mode-subagent.md +33 -0
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +9 -0
- package/src/prompts/system/project-prompt.md +52 -0
- package/src/prompts/system/subagent-system-prompt.md +64 -0
- package/src/prompts/system/subagent-user-prompt.md +3 -0
- package/src/prompts/system/subagent-yield-reminder.md +12 -0
- package/src/prompts/system/system-prompt.md +258 -0
- package/src/prompts/system/tiny-title-system.md +8 -0
- package/src/prompts/system/title-system.md +16 -0
- package/src/prompts/system/ttsr-interrupt.md +7 -0
- package/src/prompts/system/ttsr-tool-reminder.md +5 -0
- package/src/prompts/system/ultrathink-notice.md +3 -0
- package/src/prompts/system/web-search.md +25 -0
- package/src/prompts/system/workflow-notice.md +70 -0
- package/src/prompts/tools/apply-patch.md +65 -0
- package/src/prompts/tools/ask.md +30 -0
- package/src/prompts/tools/ast-edit.md +39 -0
- package/src/prompts/tools/ast-grep.md +42 -0
- package/src/prompts/tools/async-result.md +8 -0
- package/src/prompts/tools/bash.md +46 -0
- package/src/prompts/tools/browser.md +73 -0
- package/src/prompts/tools/checkpoint.md +16 -0
- package/src/prompts/tools/debug.md +34 -0
- package/src/prompts/tools/eval.md +92 -0
- package/src/prompts/tools/find.md +36 -0
- package/src/prompts/tools/github.md +21 -0
- package/src/prompts/tools/goal.md +18 -0
- package/src/prompts/tools/image-gen.md +7 -0
- package/src/prompts/tools/inspect-image-system.md +20 -0
- package/src/prompts/tools/inspect-image.md +32 -0
- package/src/prompts/tools/irc.md +59 -0
- package/src/prompts/tools/job.md +19 -0
- package/src/prompts/tools/lsp-late-diagnostic.md +8 -0
- package/src/prompts/tools/lsp.md +42 -0
- package/src/prompts/tools/memory-edit.md +8 -0
- package/src/prompts/tools/patch.md +70 -0
- package/src/prompts/tools/read.md +84 -0
- package/src/prompts/tools/recall.md +5 -0
- package/src/prompts/tools/reflect.md +5 -0
- package/src/prompts/tools/render-mermaid.md +9 -0
- package/src/prompts/tools/replace.md +30 -0
- package/src/prompts/tools/resolve.md +9 -0
- package/src/prompts/tools/retain.md +6 -0
- package/src/prompts/tools/rewind.md +13 -0
- package/src/prompts/tools/search-tool-bm25.md +32 -0
- package/src/prompts/tools/search.md +24 -0
- package/src/prompts/tools/ssh.md +31 -0
- package/src/prompts/tools/task-summary.md +17 -0
- package/src/prompts/tools/task.md +88 -0
- package/src/prompts/tools/todo.md +62 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +14 -0
- package/src/registry/agent-lifecycle.ts +218 -0
- package/src/registry/agent-registry.ts +151 -0
- package/src/sdk.ts +2558 -0
- package/src/secrets/index.ts +123 -0
- package/src/secrets/obfuscator.ts +298 -0
- package/src/secrets/regex.ts +21 -0
- package/src/session/agent-session.ts +10121 -0
- package/src/session/agent-storage.ts +455 -0
- package/src/session/artifacts.ts +135 -0
- package/src/session/auth-broker-config.ts +131 -0
- package/src/session/auth-storage.ts +29 -0
- package/src/session/blob-store.ts +255 -0
- package/src/session/client-bridge.ts +85 -0
- package/src/session/history-storage.ts +348 -0
- package/src/session/indexed-session-storage.ts +430 -0
- package/src/session/messages.ts +541 -0
- package/src/session/redis-session-storage.ts +170 -0
- package/src/session/session-dump-format.ts +209 -0
- package/src/session/session-history-format.ts +246 -0
- package/src/session/session-manager.ts +3676 -0
- package/src/session/session-storage.ts +529 -0
- package/src/session/shake-types.ts +43 -0
- package/src/session/sql-session-storage.ts +314 -0
- package/src/session/streaming-output.ts +1330 -0
- package/src/session/tool-choice-queue.ts +213 -0
- package/src/session/yield-queue.ts +173 -0
- package/src/slash-commands/acp-builtins.ts +70 -0
- package/src/slash-commands/builtin-registry.ts +1798 -0
- package/src/slash-commands/helpers/context-report.ts +39 -0
- package/src/slash-commands/helpers/format.ts +46 -0
- package/src/slash-commands/helpers/marketplace-manager.ts +25 -0
- package/src/slash-commands/helpers/mcp.ts +532 -0
- package/src/slash-commands/helpers/parse.ts +85 -0
- package/src/slash-commands/helpers/ssh.ts +195 -0
- package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
- package/src/slash-commands/helpers/todo.ts +279 -0
- package/src/slash-commands/helpers/usage-report.ts +95 -0
- package/src/slash-commands/marketplace-install-parser.ts +99 -0
- package/src/slash-commands/types.ts +135 -0
- package/src/ssh/config-writer.ts +183 -0
- package/src/ssh/connection-manager.ts +509 -0
- package/src/ssh/ssh-executor.ts +189 -0
- package/src/ssh/sshfs-mount.ts +140 -0
- package/src/ssh/utils.ts +8 -0
- package/src/stt/downloader.ts +71 -0
- package/src/stt/index.ts +3 -0
- package/src/stt/recorder.ts +351 -0
- package/src/stt/setup.ts +52 -0
- package/src/stt/stt-controller.ts +160 -0
- package/src/stt/transcribe.py +70 -0
- package/src/stt/transcriber.ts +91 -0
- package/src/stubs/natives/index.ts +814 -0
- package/src/stubs/natives/package.json +7 -0
- package/src/stubs/tui/index.ts +282 -0
- package/src/stubs/tui/package.json +7 -0
- package/src/system-prompt.ts +611 -0
- package/src/task/agents.ts +167 -0
- package/src/task/commands.ts +132 -0
- package/src/task/discovery.ts +122 -0
- package/src/task/executor.ts +2133 -0
- package/src/task/index.ts +1419 -0
- package/src/task/name-generator.ts +1577 -0
- package/src/task/omp-command.ts +26 -0
- package/src/task/output-manager.ts +88 -0
- package/src/task/parallel.ts +116 -0
- package/src/task/render.ts +1381 -0
- package/src/task/repair-args.ts +129 -0
- package/src/task/subprocess-tool-registry.ts +88 -0
- package/src/task/types.ts +336 -0
- package/src/task/worktree.ts +514 -0
- package/src/telemetry-export.ts +144 -0
- package/src/thinking.ts +167 -0
- package/src/tiny/compiled-runtime.ts +179 -0
- package/src/tiny/device.ts +111 -0
- package/src/tiny/dtype.ts +101 -0
- package/src/tiny/models.ts +242 -0
- package/src/tiny/text.ts +165 -0
- package/src/tiny/title-client.ts +543 -0
- package/src/tiny/title-protocol.ts +56 -0
- package/src/tiny/worker.ts +568 -0
- package/src/tool-discovery/mode.ts +24 -0
- package/src/tool-discovery/tool-index.ts +256 -0
- package/src/tools/approval.ts +189 -0
- package/src/tools/archive-reader.ts +721 -0
- package/src/tools/ask.ts +928 -0
- package/src/tools/ast-edit.ts +642 -0
- package/src/tools/ast-grep.ts +452 -0
- package/src/tools/auto-generated-guard.ts +322 -0
- package/src/tools/bash-command-fixup.ts +37 -0
- package/src/tools/bash-interactive.ts +408 -0
- package/src/tools/bash-interceptor.ts +67 -0
- package/src/tools/bash-pty-selection.ts +14 -0
- package/src/tools/bash-skill-urls.ts +248 -0
- package/src/tools/bash.ts +1386 -0
- package/src/tools/browser/attach.ts +175 -0
- package/src/tools/browser/launch.ts +660 -0
- package/src/tools/browser/readable.ts +112 -0
- package/src/tools/browser/registry.ts +197 -0
- package/src/tools/browser/render.ts +216 -0
- package/src/tools/browser/tab-protocol.ts +105 -0
- package/src/tools/browser/tab-supervisor.ts +628 -0
- package/src/tools/browser/tab-worker-entry.ts +21 -0
- package/src/tools/browser/tab-worker.ts +1226 -0
- package/src/tools/browser.ts +343 -0
- package/src/tools/checkpoint.ts +136 -0
- package/src/tools/conflict-detect.ts +718 -0
- package/src/tools/context.ts +39 -0
- package/src/tools/debug.ts +1067 -0
- package/src/tools/eval-backends.ts +27 -0
- package/src/tools/eval-render.ts +752 -0
- package/src/tools/eval.ts +577 -0
- package/src/tools/fetch.ts +1926 -0
- package/src/tools/file-recorder.ts +35 -0
- package/src/tools/find.ts +609 -0
- package/src/tools/fs-cache-invalidation.ts +28 -0
- package/src/tools/gh-cache-invalidation.ts +255 -0
- package/src/tools/gh-format.ts +12 -0
- package/src/tools/gh-renderer.ts +481 -0
- package/src/tools/gh.ts +3720 -0
- package/src/tools/github-cache.ts +637 -0
- package/src/tools/grouped-file-output.ts +210 -0
- package/src/tools/image-gen.ts +1517 -0
- package/src/tools/index.ts +599 -0
- package/src/tools/inspect-image-renderer.ts +132 -0
- package/src/tools/inspect-image.ts +174 -0
- package/src/tools/irc.ts +723 -0
- package/src/tools/job.ts +557 -0
- package/src/tools/json-tree.ts +243 -0
- package/src/tools/jtd-to-json-schema.ts +219 -0
- package/src/tools/jtd-to-typescript.ts +136 -0
- package/src/tools/jtd-utils.ts +102 -0
- package/src/tools/list-limit.ts +40 -0
- package/src/tools/match-line-format.ts +20 -0
- package/src/tools/memory-edit.ts +59 -0
- package/src/tools/memory-recall.ts +100 -0
- package/src/tools/memory-reflect.ts +88 -0
- package/src/tools/memory-render.ts +202 -0
- package/src/tools/memory-retain.ts +91 -0
- package/src/tools/output-meta.ts +754 -0
- package/src/tools/output-schema-validator.ts +132 -0
- package/src/tools/path-utils.ts +1054 -0
- package/src/tools/plan-mode-guard.ts +108 -0
- package/src/tools/puppeteer/00_stealth_tampering.txt +63 -0
- package/src/tools/puppeteer/01_stealth_activity.txt +20 -0
- package/src/tools/puppeteer/02_stealth_hairline.txt +11 -0
- package/src/tools/puppeteer/03_stealth_botd.txt +384 -0
- package/src/tools/puppeteer/04_stealth_iframe.txt +81 -0
- package/src/tools/puppeteer/05_stealth_webgl.txt +75 -0
- package/src/tools/puppeteer/06_stealth_screen.txt +72 -0
- package/src/tools/puppeteer/07_stealth_fonts.txt +97 -0
- package/src/tools/puppeteer/08_stealth_audio.txt +51 -0
- package/src/tools/puppeteer/09_stealth_locale.txt +46 -0
- package/src/tools/puppeteer/10_stealth_plugins.txt +206 -0
- package/src/tools/puppeteer/11_stealth_hardware.txt +8 -0
- package/src/tools/puppeteer/12_stealth_codecs.txt +40 -0
- package/src/tools/puppeteer/13_stealth_worker.txt +74 -0
- package/src/tools/read.ts +2929 -0
- package/src/tools/render-mermaid.ts +69 -0
- package/src/tools/render-utils.ts +838 -0
- package/src/tools/renderers.ts +77 -0
- package/src/tools/report-tool-issue.ts +534 -0
- package/src/tools/resolve.ts +276 -0
- package/src/tools/review.ts +253 -0
- package/src/tools/search-tool-bm25.ts +351 -0
- package/src/tools/search.ts +1580 -0
- package/src/tools/sqlite-reader.ts +828 -0
- package/src/tools/ssh.ts +349 -0
- package/src/tools/todo.ts +982 -0
- package/src/tools/tool-errors.ts +62 -0
- package/src/tools/tool-result.ts +94 -0
- package/src/tools/tool-timeouts.ts +30 -0
- package/src/tools/tts.ts +133 -0
- package/src/tools/write.ts +1217 -0
- package/src/tools/yield.ts +269 -0
- package/src/tui/code-cell.ts +216 -0
- package/src/tui/file-list.ts +55 -0
- package/src/tui/hyperlink.ts +175 -0
- package/src/tui/index.ts +12 -0
- package/src/tui/output-block.ts +240 -0
- package/src/tui/status-line.ts +54 -0
- package/src/tui/tree-list.ts +84 -0
- package/src/tui/types.ts +15 -0
- package/src/tui/utils.ts +103 -0
- package/src/utils/block-context.ts +312 -0
- package/src/utils/changelog.ts +132 -0
- package/src/utils/clipboard.ts +193 -0
- package/src/utils/command-args.ts +76 -0
- package/src/utils/commit-message-generator.ts +151 -0
- package/src/utils/edit-mode.ts +41 -0
- package/src/utils/enhanced-paste.ts +230 -0
- package/src/utils/event-bus.ts +33 -0
- package/src/utils/external-editor.ts +65 -0
- package/src/utils/file-display-mode.ts +45 -0
- package/src/utils/file-mentions.ts +281 -0
- package/src/utils/git.ts +1833 -0
- package/src/utils/image-loading.ts +132 -0
- package/src/utils/image-resize.ts +309 -0
- package/src/utils/jj.ts +248 -0
- package/src/utils/lang-from-path.ts +239 -0
- package/src/utils/markit.ts +89 -0
- package/src/utils/open.ts +55 -0
- package/src/utils/session-color.ts +68 -0
- package/src/utils/shell-snapshot.ts +187 -0
- package/src/utils/sixel.ts +69 -0
- package/src/utils/title-generator.ts +373 -0
- package/src/utils/tool-choice.ts +33 -0
- package/src/utils/tools-manager.ts +363 -0
- package/src/web/kagi.ts +305 -0
- package/src/web/parallel.ts +353 -0
- package/src/web/scrapers/artifacthub.ts +207 -0
- package/src/web/scrapers/arxiv.ts +83 -0
- package/src/web/scrapers/aur.ts +162 -0
- package/src/web/scrapers/biorxiv.ts +133 -0
- package/src/web/scrapers/bluesky.ts +262 -0
- package/src/web/scrapers/brew.ts +172 -0
- package/src/web/scrapers/cheatsh.ts +68 -0
- package/src/web/scrapers/chocolatey.ts +196 -0
- package/src/web/scrapers/choosealicense.ts +95 -0
- package/src/web/scrapers/cisa-kev.ts +87 -0
- package/src/web/scrapers/clojars.ts +154 -0
- package/src/web/scrapers/coingecko.ts +177 -0
- package/src/web/scrapers/crates-io.ts +97 -0
- package/src/web/scrapers/crossref.ts +136 -0
- package/src/web/scrapers/devto.ts +147 -0
- package/src/web/scrapers/discogs.ts +306 -0
- package/src/web/scrapers/discourse.ts +197 -0
- package/src/web/scrapers/dockerhub.ts +138 -0
- package/src/web/scrapers/docs-rs.ts +653 -0
- package/src/web/scrapers/fdroid.ts +134 -0
- package/src/web/scrapers/firefox-addons.ts +191 -0
- package/src/web/scrapers/flathub.ts +223 -0
- package/src/web/scrapers/github-gist.ts +58 -0
- package/src/web/scrapers/github.ts +704 -0
- package/src/web/scrapers/gitlab.ts +401 -0
- package/src/web/scrapers/go-pkg.ts +266 -0
- package/src/web/scrapers/hackage.ts +140 -0
- package/src/web/scrapers/hackernews.ts +189 -0
- package/src/web/scrapers/hex.ts +105 -0
- package/src/web/scrapers/huggingface.ts +321 -0
- package/src/web/scrapers/iacr.ts +89 -0
- package/src/web/scrapers/index.ts +252 -0
- package/src/web/scrapers/jetbrains-marketplace.ts +159 -0
- package/src/web/scrapers/lemmy.ts +203 -0
- package/src/web/scrapers/lobsters.ts +175 -0
- package/src/web/scrapers/mastodon.ts +292 -0
- package/src/web/scrapers/maven.ts +138 -0
- package/src/web/scrapers/mdn.ts +173 -0
- package/src/web/scrapers/metacpan.ts +222 -0
- package/src/web/scrapers/musicbrainz.ts +250 -0
- package/src/web/scrapers/npm.ts +98 -0
- package/src/web/scrapers/nuget.ts +183 -0
- package/src/web/scrapers/nvd.ts +222 -0
- package/src/web/scrapers/ollama.ts +239 -0
- package/src/web/scrapers/open-vsx.ts +106 -0
- package/src/web/scrapers/opencorporates.ts +292 -0
- package/src/web/scrapers/openlibrary.ts +336 -0
- package/src/web/scrapers/orcid.ts +286 -0
- package/src/web/scrapers/osv.ts +176 -0
- package/src/web/scrapers/packagist.ts +160 -0
- package/src/web/scrapers/pub-dev.ts +143 -0
- package/src/web/scrapers/pubmed.ts +211 -0
- package/src/web/scrapers/pypi.ts +112 -0
- package/src/web/scrapers/rawg.ts +110 -0
- package/src/web/scrapers/readthedocs.ts +120 -0
- package/src/web/scrapers/reddit.ts +95 -0
- package/src/web/scrapers/repology.ts +251 -0
- package/src/web/scrapers/rfc.ts +201 -0
- package/src/web/scrapers/rubygems.ts +103 -0
- package/src/web/scrapers/searchcode.ts +189 -0
- package/src/web/scrapers/sec-edgar.ts +261 -0
- package/src/web/scrapers/semantic-scholar.ts +171 -0
- package/src/web/scrapers/snapcraft.ts +187 -0
- package/src/web/scrapers/sourcegraph.ts +336 -0
- package/src/web/scrapers/spdx.ts +108 -0
- package/src/web/scrapers/spotify.ts +198 -0
- package/src/web/scrapers/stackoverflow.ts +120 -0
- package/src/web/scrapers/terraform.ts +277 -0
- package/src/web/scrapers/tldr.ts +47 -0
- package/src/web/scrapers/twitter.ts +94 -0
- package/src/web/scrapers/types.ts +397 -0
- package/src/web/scrapers/utils.ts +109 -0
- package/src/web/scrapers/vimeo.ts +133 -0
- package/src/web/scrapers/vscode-marketplace.ts +187 -0
- package/src/web/scrapers/w3c.ts +156 -0
- package/src/web/scrapers/wikidata.ts +344 -0
- package/src/web/scrapers/wikipedia.ts +84 -0
- package/src/web/scrapers/youtube.ts +325 -0
- package/src/web/search/index.ts +292 -0
- package/src/web/search/provider.ts +157 -0
- package/src/web/search/providers/anthropic.ts +318 -0
- package/src/web/search/providers/base.ts +89 -0
- package/src/web/search/providers/brave.ts +152 -0
- package/src/web/search/providers/codex.ts +591 -0
- package/src/web/search/providers/exa.ts +400 -0
- package/src/web/search/providers/gemini.ts +460 -0
- package/src/web/search/providers/jina.ts +111 -0
- package/src/web/search/providers/kagi.ts +86 -0
- package/src/web/search/providers/kimi.ts +196 -0
- package/src/web/search/providers/parallel.ts +225 -0
- package/src/web/search/providers/perplexity.ts +730 -0
- package/src/web/search/providers/searxng.ts +313 -0
- package/src/web/search/providers/synthetic.ts +114 -0
- package/src/web/search/providers/tavily.ts +176 -0
- package/src/web/search/providers/utils.ts +128 -0
- package/src/web/search/providers/zai.ts +333 -0
- package/src/web/search/render.ts +262 -0
- package/src/web/search/types.ts +482 -0
- package/src/web/search/utils.ts +17 -0
- package/src/workspace-tree.ts +286 -0
|
@@ -0,0 +1,1330 @@
|
|
|
1
|
+
import type { AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
|
+
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import { formatBytes } from "../tools/render-utils";
|
|
4
|
+
import { sanitizeWithOptionalSixelPassthrough } from "../utils/sixel";
|
|
5
|
+
|
|
6
|
+
// =============================================================================
|
|
7
|
+
// Constants
|
|
8
|
+
// =============================================================================
|
|
9
|
+
|
|
10
|
+
export const DEFAULT_MAX_LINES = 3000;
|
|
11
|
+
export const DEFAULT_MAX_BYTES = 50 * 1024; // 50KB
|
|
12
|
+
export const DEFAULT_MAX_COLUMN = 512; // Max chars per grep match line
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Default artifact-on-disk cap for {@link OutputSink}.
|
|
16
|
+
*
|
|
17
|
+
* `0` means unbounded: by default, `artifact://<id>` references preserve the
|
|
18
|
+
* complete raw stream instead of a capped head/tail sample.
|
|
19
|
+
*/
|
|
20
|
+
export const ARTIFACT_DEFAULT_MAX_BYTES = 0;
|
|
21
|
+
/** Default head budget; the remainder becomes the rolling tail window. */
|
|
22
|
+
export const ARTIFACT_DEFAULT_HEAD_BYTES = 3 * 1024 * 1024; // 3 MiB
|
|
23
|
+
|
|
24
|
+
const NL = "\n";
|
|
25
|
+
const ELLIPSIS = "…";
|
|
26
|
+
|
|
27
|
+
// =============================================================================
|
|
28
|
+
// Interfaces
|
|
29
|
+
// =============================================================================
|
|
30
|
+
|
|
31
|
+
export interface OutputSummary {
|
|
32
|
+
output: string;
|
|
33
|
+
truncated: boolean;
|
|
34
|
+
totalLines: number;
|
|
35
|
+
totalBytes: number;
|
|
36
|
+
outputLines: number;
|
|
37
|
+
outputBytes: number;
|
|
38
|
+
/** Bytes elided from the middle when head-retain mode is active. */
|
|
39
|
+
elidedBytes?: number;
|
|
40
|
+
/** Lines elided from the middle when head-retain mode is active. */
|
|
41
|
+
elidedLines?: number;
|
|
42
|
+
/** Bytes dropped by the per-line column cap (sum across all lines). */
|
|
43
|
+
columnDroppedBytes?: number;
|
|
44
|
+
/** Number of distinct lines that hit the per-line column cap. */
|
|
45
|
+
columnTruncatedLines?: number;
|
|
46
|
+
/** Artifact ID for internal URL access (artifact://<id>) when truncated */
|
|
47
|
+
artifactId?: string;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface OutputSinkOptions {
|
|
51
|
+
artifactPath?: string;
|
|
52
|
+
artifactId?: string;
|
|
53
|
+
/** Tail buffer budget (bytes). Default DEFAULT_MAX_BYTES. */
|
|
54
|
+
spillThreshold?: number;
|
|
55
|
+
/**
|
|
56
|
+
* When > 0, the sink keeps the first `headBytes` of output in addition to
|
|
57
|
+
* the rolling tail window. Output between the two windows is elided
|
|
58
|
+
* (middle elision). Default 0 = tail-only behavior.
|
|
59
|
+
*/
|
|
60
|
+
headBytes?: number;
|
|
61
|
+
/**
|
|
62
|
+
* Per-line byte cap. When > 0, lines wider than `maxColumns` bytes are
|
|
63
|
+
* truncated with an ellipsis at write time; remaining bytes up to the next
|
|
64
|
+
* `\n` are dropped. Cap state persists across chunks so split-mid-line
|
|
65
|
+
* writes still respect the budget. Default 0 = no per-line cap.
|
|
66
|
+
*/
|
|
67
|
+
maxColumns?: number;
|
|
68
|
+
onChunk?: (chunk: string) => void;
|
|
69
|
+
/** Minimum ms between onChunk calls. 0 = every chunk (default). */
|
|
70
|
+
chunkThrottleMs?: number;
|
|
71
|
+
/**
|
|
72
|
+
* Optional cap on bytes written to the artifact-on-disk file. When the cap
|
|
73
|
+
* is hit, the head window is preserved verbatim and subsequent output feeds
|
|
74
|
+
* a rolling tail window; on close, the sink writes a single
|
|
75
|
+
* `[ARTIFACT TRUNCATED: …]` notice between them. Default
|
|
76
|
+
* {@link ARTIFACT_DEFAULT_MAX_BYTES} (unbounded).
|
|
77
|
+
*/
|
|
78
|
+
artifactMaxBytes?: number;
|
|
79
|
+
/**
|
|
80
|
+
* Bytes reserved for the head window of the capped artifact file. The
|
|
81
|
+
* tail window receives `artifactMaxBytes - artifactHeadBytes`. Default
|
|
82
|
+
* {@link ARTIFACT_DEFAULT_HEAD_BYTES}; clamped to `[0, artifactMaxBytes]`.
|
|
83
|
+
*/
|
|
84
|
+
artifactHeadBytes?: number;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface TruncationResult {
|
|
88
|
+
content: string;
|
|
89
|
+
truncated?: boolean;
|
|
90
|
+
truncatedBy?: "lines" | "bytes" | "middle";
|
|
91
|
+
totalLines: number;
|
|
92
|
+
totalBytes: number;
|
|
93
|
+
outputLines?: number;
|
|
94
|
+
outputBytes?: number;
|
|
95
|
+
/** Bytes elided from the middle (truncateMiddle only). */
|
|
96
|
+
elidedBytes?: number;
|
|
97
|
+
/** Lines elided from the middle (truncateMiddle only). */
|
|
98
|
+
elidedLines?: number;
|
|
99
|
+
lastLinePartial?: boolean;
|
|
100
|
+
firstLineExceedsLimit?: boolean;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export interface TruncationOptions {
|
|
104
|
+
/** Maximum number of lines (default: 3000) */
|
|
105
|
+
maxLines?: number;
|
|
106
|
+
/** Maximum number of bytes (default: 50KB) */
|
|
107
|
+
maxBytes?: number;
|
|
108
|
+
/**
|
|
109
|
+
* For `truncateMiddle`: bytes reserved for the head window. The tail
|
|
110
|
+
* window receives `maxBytes - maxHeadBytes`. Default `floor(maxBytes/2)`.
|
|
111
|
+
*/
|
|
112
|
+
maxHeadBytes?: number;
|
|
113
|
+
/**
|
|
114
|
+
* For `truncateMiddle`: lines reserved for the head window. The tail
|
|
115
|
+
* window receives `maxLines - maxHeadLines`. Default `floor(maxLines/2)`.
|
|
116
|
+
*/
|
|
117
|
+
maxHeadLines?: number;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Result from byte-level truncation helpers. */
|
|
121
|
+
export interface ByteTruncationResult {
|
|
122
|
+
text: string;
|
|
123
|
+
bytes: number;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
export interface TailTruncationNoticeOptions {
|
|
127
|
+
fullOutputPath?: string;
|
|
128
|
+
originalContent?: string;
|
|
129
|
+
suffix?: string;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export interface HeadTruncationNoticeOptions {
|
|
133
|
+
startLine?: number;
|
|
134
|
+
totalFileLines?: number;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// =============================================================================
|
|
138
|
+
// Internal low-level helpers
|
|
139
|
+
// =============================================================================
|
|
140
|
+
|
|
141
|
+
/** Count newline characters via native substring search. */
|
|
142
|
+
function countNewlines(text: string): number {
|
|
143
|
+
let count = 0;
|
|
144
|
+
let pos = text.indexOf(NL);
|
|
145
|
+
while (pos !== -1) {
|
|
146
|
+
count++;
|
|
147
|
+
pos = text.indexOf(NL, pos + 1);
|
|
148
|
+
}
|
|
149
|
+
return count;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/** Zero-copy view of a Uint8Array as a Buffer (copies only if already a Buffer). */
|
|
153
|
+
function asBuffer(data: Uint8Array): Buffer {
|
|
154
|
+
return Buffer.isBuffer(data) ? (data as Buffer) : Buffer.from(data.buffer, data.byteOffset, data.byteLength);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/** Advance past UTF-8 continuation bytes (10xxxxxx) to a leading byte. */
|
|
158
|
+
function findUtf8BoundaryForward(buf: Buffer, pos: number): number {
|
|
159
|
+
let i = Math.max(0, pos);
|
|
160
|
+
while (i < buf.length && (buf[i] & 0xc0) === 0x80) i++;
|
|
161
|
+
return i;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** Retreat past UTF-8 continuation bytes to land on a leading byte. */
|
|
165
|
+
function findUtf8BoundaryBackward(buf: Buffer, cut: number): number {
|
|
166
|
+
let i = Math.min(buf.length, Math.max(0, cut));
|
|
167
|
+
// If the cut is at end-of-buffer, it's already a valid boundary.
|
|
168
|
+
if (i >= buf.length) return buf.length;
|
|
169
|
+
while (i > 0 && (buf[i] & 0xc0) === 0x80) i--;
|
|
170
|
+
return i;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// =============================================================================
|
|
174
|
+
// Byte-level truncation (windowed encoding)
|
|
175
|
+
// =============================================================================
|
|
176
|
+
|
|
177
|
+
function truncateBytesWindowed(
|
|
178
|
+
data: string | Uint8Array,
|
|
179
|
+
maxBytesRaw: number,
|
|
180
|
+
mode: "head" | "tail",
|
|
181
|
+
): ByteTruncationResult {
|
|
182
|
+
const maxBytes = maxBytesRaw;
|
|
183
|
+
if (maxBytes === 0) return { text: "", bytes: 0 };
|
|
184
|
+
|
|
185
|
+
// --------------------------
|
|
186
|
+
// String path (windowed)
|
|
187
|
+
// --------------------------
|
|
188
|
+
if (typeof data === "string") {
|
|
189
|
+
// Fast non-truncation check only when it *might* fit.
|
|
190
|
+
if (data.length <= maxBytes) {
|
|
191
|
+
const len = Buffer.byteLength(data, "utf-8");
|
|
192
|
+
if (len <= maxBytes) return { text: data, bytes: len };
|
|
193
|
+
// else: multibyte-heavy string; fall through to truncation using full string as window.
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const window =
|
|
197
|
+
mode === "head"
|
|
198
|
+
? data.substring(0, Math.min(data.length, maxBytes))
|
|
199
|
+
: data.substring(Math.max(0, data.length - maxBytes));
|
|
200
|
+
|
|
201
|
+
const buf = Buffer.from(window, "utf-8");
|
|
202
|
+
|
|
203
|
+
if (mode === "head") {
|
|
204
|
+
const end = findUtf8BoundaryBackward(buf, maxBytes);
|
|
205
|
+
if (end <= 0) return { text: "", bytes: 0 };
|
|
206
|
+
const slice = buf.subarray(0, end);
|
|
207
|
+
return { text: slice.toString("utf-8"), bytes: slice.length };
|
|
208
|
+
} else {
|
|
209
|
+
const startAt = Math.max(0, buf.length - maxBytes);
|
|
210
|
+
const start = findUtf8BoundaryForward(buf, startAt);
|
|
211
|
+
const slice = buf.subarray(start);
|
|
212
|
+
return { text: slice.toString("utf-8"), bytes: slice.length };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// --------------------------
|
|
217
|
+
// Uint8Array / Buffer path
|
|
218
|
+
// --------------------------
|
|
219
|
+
const buf = asBuffer(data);
|
|
220
|
+
if (buf.length <= maxBytes) return { text: buf.toString("utf-8"), bytes: buf.length };
|
|
221
|
+
|
|
222
|
+
if (mode === "head") {
|
|
223
|
+
const end = findUtf8BoundaryBackward(buf, maxBytes);
|
|
224
|
+
if (end <= 0) return { text: "", bytes: 0 };
|
|
225
|
+
const slice = buf.subarray(0, end);
|
|
226
|
+
return { text: slice.toString("utf-8"), bytes: slice.length };
|
|
227
|
+
} else {
|
|
228
|
+
const startAt = buf.length - maxBytes;
|
|
229
|
+
const start = findUtf8BoundaryForward(buf, startAt);
|
|
230
|
+
const slice = buf.subarray(start);
|
|
231
|
+
return { text: slice.toString("utf-8"), bytes: slice.length };
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Truncate a string/buffer to fit within a byte limit, keeping the tail.
|
|
237
|
+
* Handles multi-byte UTF-8 boundaries correctly.
|
|
238
|
+
*/
|
|
239
|
+
export function truncateTailBytes(data: string | Uint8Array, maxBytes: number): ByteTruncationResult {
|
|
240
|
+
return truncateBytesWindowed(data, maxBytes, "tail");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Truncate a string/buffer to fit within a byte limit, keeping the head.
|
|
245
|
+
* Handles multi-byte UTF-8 boundaries correctly.
|
|
246
|
+
*/
|
|
247
|
+
export function truncateHeadBytes(data: string | Uint8Array, maxBytes: number): ByteTruncationResult {
|
|
248
|
+
return truncateBytesWindowed(data, maxBytes, "head");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// =============================================================================
|
|
252
|
+
// Line-level utilities
|
|
253
|
+
// =============================================================================
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Truncate a single line to max characters, appending '…' if truncated.
|
|
257
|
+
*/
|
|
258
|
+
export function truncateLine(
|
|
259
|
+
line: string,
|
|
260
|
+
maxChars: number = DEFAULT_MAX_COLUMN,
|
|
261
|
+
): { text: string; wasTruncated: boolean } {
|
|
262
|
+
if (line.length <= maxChars) return { text: line, wasTruncated: false };
|
|
263
|
+
return { text: `${line.slice(0, maxChars)}…`, wasTruncated: true };
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// =============================================================================
|
|
267
|
+
// Content truncation (line + byte aware, no full Buffer allocation)
|
|
268
|
+
// =============================================================================
|
|
269
|
+
|
|
270
|
+
/** Shared helper to build a no-truncation result. */
|
|
271
|
+
export function noTruncResult(content: string, totalLines?: number, totalBytes?: number): TruncationResult {
|
|
272
|
+
if (totalLines == null) totalLines = countNewlines(content) + 1;
|
|
273
|
+
if (totalBytes == null) totalBytes = Buffer.byteLength(content, "utf-8");
|
|
274
|
+
return { content, totalLines, totalBytes };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Truncate content from the head (keep first N lines/bytes).
|
|
279
|
+
* Never returns partial lines. If the first line exceeds the byte limit,
|
|
280
|
+
* returns empty content with firstLineExceedsLimit=true.
|
|
281
|
+
*
|
|
282
|
+
* This implementation avoids Buffer.from(content) for the whole input.
|
|
283
|
+
* It only computes UTF-8 byteLength for candidate lines that can still fit.
|
|
284
|
+
*/
|
|
285
|
+
export function truncateHead(content: string, options: TruncationOptions = {}): TruncationResult {
|
|
286
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
287
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
288
|
+
|
|
289
|
+
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
290
|
+
const totalLines = countNewlines(content) + 1;
|
|
291
|
+
|
|
292
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
293
|
+
return noTruncResult(content, totalLines, totalBytes);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
let includedLines = 0;
|
|
297
|
+
let bytesUsed = 0;
|
|
298
|
+
let cutIndex = 0; // char index where we cut (exclusive)
|
|
299
|
+
let cursor = 0;
|
|
300
|
+
|
|
301
|
+
let truncatedBy: "lines" | "bytes" = "lines";
|
|
302
|
+
|
|
303
|
+
while (includedLines < maxLines) {
|
|
304
|
+
const nl = content.indexOf(NL, cursor);
|
|
305
|
+
const lineEnd = nl === -1 ? content.length : nl;
|
|
306
|
+
|
|
307
|
+
const sepBytes = includedLines > 0 ? 1 : 0;
|
|
308
|
+
const remaining = maxBytes - bytesUsed - sepBytes;
|
|
309
|
+
|
|
310
|
+
// No room even for separators / bytes.
|
|
311
|
+
if (remaining < 0) {
|
|
312
|
+
truncatedBy = "bytes";
|
|
313
|
+
break;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Fast reject huge lines without slicing/encoding:
|
|
317
|
+
// UTF-8 bytes >= UTF-16 code units, so if code units exceed remaining, bytes must exceed too.
|
|
318
|
+
const lineCodeUnits = lineEnd - cursor;
|
|
319
|
+
if (lineCodeUnits > remaining) {
|
|
320
|
+
truncatedBy = "bytes";
|
|
321
|
+
if (includedLines === 0) {
|
|
322
|
+
return {
|
|
323
|
+
content: "",
|
|
324
|
+
truncated: true,
|
|
325
|
+
truncatedBy: "bytes",
|
|
326
|
+
totalLines,
|
|
327
|
+
totalBytes,
|
|
328
|
+
outputLines: 0,
|
|
329
|
+
outputBytes: 0,
|
|
330
|
+
lastLinePartial: false,
|
|
331
|
+
firstLineExceedsLimit: true,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
// Small slice (bounded by remaining <= maxBytes) for exact UTF-8 byte count.
|
|
338
|
+
const lineText = content.slice(cursor, lineEnd);
|
|
339
|
+
const lineBytes = Buffer.byteLength(lineText, "utf-8");
|
|
340
|
+
|
|
341
|
+
if (lineBytes > remaining) {
|
|
342
|
+
truncatedBy = "bytes";
|
|
343
|
+
if (includedLines === 0) {
|
|
344
|
+
return {
|
|
345
|
+
content: "",
|
|
346
|
+
truncated: true,
|
|
347
|
+
truncatedBy: "bytes",
|
|
348
|
+
totalLines,
|
|
349
|
+
totalBytes,
|
|
350
|
+
outputLines: 0,
|
|
351
|
+
outputBytes: 0,
|
|
352
|
+
lastLinePartial: false,
|
|
353
|
+
firstLineExceedsLimit: true,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Include the line (join semantics: no trailing newline after the last included line).
|
|
360
|
+
bytesUsed += sepBytes + lineBytes;
|
|
361
|
+
includedLines++;
|
|
362
|
+
|
|
363
|
+
cutIndex = nl === -1 ? content.length : nl; // exclude the newline after the last included line
|
|
364
|
+
if (nl === -1) break;
|
|
365
|
+
cursor = nl + 1;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (includedLines >= maxLines && bytesUsed <= maxBytes) truncatedBy = "lines";
|
|
369
|
+
|
|
370
|
+
return {
|
|
371
|
+
content: content.slice(0, cutIndex),
|
|
372
|
+
truncated: true,
|
|
373
|
+
truncatedBy,
|
|
374
|
+
totalLines,
|
|
375
|
+
totalBytes,
|
|
376
|
+
outputLines: includedLines,
|
|
377
|
+
outputBytes: bytesUsed,
|
|
378
|
+
lastLinePartial: false,
|
|
379
|
+
firstLineExceedsLimit: false,
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Truncate content from the tail (keep last N lines/bytes).
|
|
385
|
+
* May return a partial first line if the last line exceeds the byte limit.
|
|
386
|
+
*
|
|
387
|
+
* Also avoids Buffer.from(content) for the whole input.
|
|
388
|
+
*/
|
|
389
|
+
export function truncateTail(content: string, options: TruncationOptions = {}): TruncationResult {
|
|
390
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
391
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
392
|
+
|
|
393
|
+
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
394
|
+
const totalLines = countNewlines(content) + 1;
|
|
395
|
+
|
|
396
|
+
if (totalLines <= maxLines && totalBytes <= maxBytes) {
|
|
397
|
+
return noTruncResult(content, totalLines, totalBytes);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
let includedLines = 0;
|
|
401
|
+
let bytesUsed = 0;
|
|
402
|
+
let startIndex = content.length; // char index where output starts
|
|
403
|
+
let end = content.length; // char index where current line ends (exclusive)
|
|
404
|
+
|
|
405
|
+
let truncatedBy: "lines" | "bytes" = "lines";
|
|
406
|
+
|
|
407
|
+
while (includedLines < maxLines) {
|
|
408
|
+
const nl = content.lastIndexOf(NL, end - 1);
|
|
409
|
+
const lineStart = nl === -1 ? 0 : nl + 1;
|
|
410
|
+
|
|
411
|
+
const sepBytes = includedLines > 0 ? 1 : 0;
|
|
412
|
+
const remaining = maxBytes - bytesUsed - sepBytes;
|
|
413
|
+
|
|
414
|
+
if (remaining < 0) {
|
|
415
|
+
truncatedBy = "bytes";
|
|
416
|
+
break;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const lineCodeUnits = end - lineStart;
|
|
420
|
+
|
|
421
|
+
// Fast reject huge line without slicing/encoding.
|
|
422
|
+
if (lineCodeUnits > remaining) {
|
|
423
|
+
truncatedBy = "bytes";
|
|
424
|
+
if (includedLines === 0) {
|
|
425
|
+
// Window the line substring to avoid materializing a giant string.
|
|
426
|
+
const windowStart = Math.max(lineStart, end - maxBytes);
|
|
427
|
+
const window = content.substring(windowStart, end);
|
|
428
|
+
const tail = truncateTailBytes(window, maxBytes);
|
|
429
|
+
return {
|
|
430
|
+
content: tail.text,
|
|
431
|
+
truncated: true,
|
|
432
|
+
truncatedBy: "bytes",
|
|
433
|
+
totalLines,
|
|
434
|
+
totalBytes,
|
|
435
|
+
outputLines: 1,
|
|
436
|
+
outputBytes: tail.bytes,
|
|
437
|
+
lastLinePartial: true,
|
|
438
|
+
firstLineExceedsLimit: false,
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
const lineText = content.slice(lineStart, end);
|
|
445
|
+
const lineBytes = Buffer.byteLength(lineText, "utf-8");
|
|
446
|
+
|
|
447
|
+
if (lineBytes > remaining) {
|
|
448
|
+
truncatedBy = "bytes";
|
|
449
|
+
if (includedLines === 0) {
|
|
450
|
+
const tail = truncateTailBytes(lineText, maxBytes);
|
|
451
|
+
return {
|
|
452
|
+
content: tail.text,
|
|
453
|
+
truncated: true,
|
|
454
|
+
truncatedBy: "bytes",
|
|
455
|
+
totalLines,
|
|
456
|
+
totalBytes,
|
|
457
|
+
outputLines: 1,
|
|
458
|
+
outputBytes: tail.bytes,
|
|
459
|
+
lastLinePartial: true,
|
|
460
|
+
firstLineExceedsLimit: false,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
break;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
bytesUsed += sepBytes + lineBytes;
|
|
467
|
+
includedLines++;
|
|
468
|
+
startIndex = lineStart;
|
|
469
|
+
|
|
470
|
+
if (nl === -1) break;
|
|
471
|
+
end = nl; // exclude the newline itself; it'll be accounted as sepBytes in the next iteration
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
if (includedLines >= maxLines && bytesUsed <= maxBytes) truncatedBy = "lines";
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
content: content.slice(startIndex),
|
|
478
|
+
truncated: true,
|
|
479
|
+
truncatedBy,
|
|
480
|
+
totalLines,
|
|
481
|
+
totalBytes,
|
|
482
|
+
outputLines: includedLines,
|
|
483
|
+
outputBytes: bytesUsed,
|
|
484
|
+
lastLinePartial: false,
|
|
485
|
+
firstLineExceedsLimit: false,
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// =============================================================================
|
|
490
|
+
// Middle elision (keep head + tail, drop middle)
|
|
491
|
+
// =============================================================================
|
|
492
|
+
|
|
493
|
+
/**
|
|
494
|
+
* Format the inline marker substituted for the elided middle region.
|
|
495
|
+
* Returned without surrounding newlines so callers can position it freely.
|
|
496
|
+
*/
|
|
497
|
+
export function formatMiddleElisionMarker(elidedLines: number, elidedBytes: number): string {
|
|
498
|
+
const linesPart = `${elidedLines.toLocaleString()} line${elidedLines === 1 ? "" : "s"}`;
|
|
499
|
+
return `[… ${linesPart} elided (${formatBytes(elidedBytes)}) …]`;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Truncate content keeping a head window and a tail window, eliding the middle.
|
|
504
|
+
*
|
|
505
|
+
* The combined output is `<head>\n<marker>\n<tail>` when truncation is needed.
|
|
506
|
+
* `maxHeadBytes` defaults to `floor(maxBytes / 2)`; the tail receives the
|
|
507
|
+
* remainder. Falls back to `truncateTail` / `truncateHead` if either side's
|
|
508
|
+
* budget is empty or the content already fits.
|
|
509
|
+
*/
|
|
510
|
+
export function truncateMiddle(content: string, options: TruncationOptions = {}): TruncationResult {
|
|
511
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
512
|
+
const maxLines = options.maxLines ?? DEFAULT_MAX_LINES;
|
|
513
|
+
const headBytes = options.maxHeadBytes ?? Math.floor(maxBytes / 2);
|
|
514
|
+
const tailBytes = Math.max(0, maxBytes - headBytes);
|
|
515
|
+
const headLines = options.maxHeadLines ?? Math.max(1, Math.floor(maxLines / 2));
|
|
516
|
+
const tailLines = Math.max(0, maxLines - headLines);
|
|
517
|
+
|
|
518
|
+
const totalBytes = Buffer.byteLength(content, "utf-8");
|
|
519
|
+
const totalLines = countNewlines(content) + 1;
|
|
520
|
+
|
|
521
|
+
if (totalBytes <= maxBytes && totalLines <= maxLines) {
|
|
522
|
+
return noTruncResult(content, totalLines, totalBytes);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
// Degenerate budgets → fall back to one-sided truncation.
|
|
526
|
+
if (headBytes <= 0 || headLines <= 0) {
|
|
527
|
+
return truncateTail(content, { maxBytes: tailBytes || maxBytes, maxLines: tailLines || maxLines });
|
|
528
|
+
}
|
|
529
|
+
if (tailBytes <= 0 || tailLines <= 0) {
|
|
530
|
+
return truncateHead(content, { maxBytes: headBytes, maxLines: headLines });
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
const head = truncateHead(content, { maxBytes: headBytes, maxLines: headLines });
|
|
534
|
+
const tail = truncateTail(content, { maxBytes: tailBytes, maxLines: tailLines });
|
|
535
|
+
|
|
536
|
+
const headLinesKept = head.outputLines ?? 0;
|
|
537
|
+
const tailLinesKept = tail.outputLines ?? 0;
|
|
538
|
+
const headBytesKept = head.outputBytes ?? Buffer.byteLength(head.content, "utf-8");
|
|
539
|
+
const tailBytesKept = tail.outputBytes ?? Buffer.byteLength(tail.content, "utf-8");
|
|
540
|
+
|
|
541
|
+
// Head unusable (first line exceeds budget) → tail-only.
|
|
542
|
+
if (headLinesKept === 0 || head.firstLineExceedsLimit) return tail;
|
|
543
|
+
// Tail unusable → head-only.
|
|
544
|
+
if (tailLinesKept === 0) return head;
|
|
545
|
+
// Windows overlap → no meaningful elision; return content untruncated.
|
|
546
|
+
if (headLinesKept + tailLinesKept >= totalLines) {
|
|
547
|
+
return noTruncResult(content, totalLines, totalBytes);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const elidedLines = totalLines - headLinesKept - tailLinesKept;
|
|
551
|
+
// `totalBytes - headBytesKept - tailBytesKept` includes newline separators
|
|
552
|
+
// between the kept windows and the elided region; close enough for a notice.
|
|
553
|
+
const elidedBytes = Math.max(0, totalBytes - headBytesKept - tailBytesKept);
|
|
554
|
+
const marker = formatMiddleElisionMarker(elidedLines, elidedBytes);
|
|
555
|
+
const composed = `${head.content}\n${marker}\n${tail.content}`;
|
|
556
|
+
const markerBytes = Buffer.byteLength(marker, "utf-8");
|
|
557
|
+
|
|
558
|
+
return {
|
|
559
|
+
content: composed,
|
|
560
|
+
truncated: true,
|
|
561
|
+
truncatedBy: "middle",
|
|
562
|
+
totalLines,
|
|
563
|
+
totalBytes,
|
|
564
|
+
outputLines: headLinesKept + tailLinesKept + 1,
|
|
565
|
+
outputBytes: headBytesKept + tailBytesKept + markerBytes + 2,
|
|
566
|
+
elidedLines,
|
|
567
|
+
elidedBytes,
|
|
568
|
+
lastLinePartial: tail.lastLinePartial,
|
|
569
|
+
firstLineExceedsLimit: false,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
// =============================================================================
|
|
574
|
+
// Inline byte cap — final defense at the tool-result boundary
|
|
575
|
+
// =============================================================================
|
|
576
|
+
|
|
577
|
+
/** Options for {@link enforceInlineByteCap}. */
|
|
578
|
+
export interface InlineByteCapOptions {
|
|
579
|
+
/** Inline byte budget. Defaults to {@link DEFAULT_MAX_BYTES}. */
|
|
580
|
+
maxBytes?: number;
|
|
581
|
+
/** What the text is, for the elision marker (e.g. "bash output"). */
|
|
582
|
+
label: string;
|
|
583
|
+
/**
|
|
584
|
+
* Persist the full text as a session artifact. When an artifact id is
|
|
585
|
+
* returned, a `[raw output: artifact://<id>]` footer is appended so the
|
|
586
|
+
* elided bytes stay recoverable.
|
|
587
|
+
*/
|
|
588
|
+
saveArtifact?: (full: string) => string | undefined | Promise<string | undefined>;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
/** Drop the partial last line of a head window (keep it if there is no newline at all). */
|
|
592
|
+
function trimHeadToLineBoundary(text: string): string {
|
|
593
|
+
const idx = text.lastIndexOf(NL);
|
|
594
|
+
return idx > 0 ? text.substring(0, idx) : text;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
/** Drop the partial first line of a tail window (keep it if there is no newline at all). */
|
|
598
|
+
function trimTailToLineBoundary(text: string): string {
|
|
599
|
+
const idx = text.indexOf(NL);
|
|
600
|
+
if (idx < 0 || idx === text.length - 1) return text;
|
|
601
|
+
return text.substring(idx + 1);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
/**
|
|
605
|
+
* Final-defense inline size guard for tool results.
|
|
606
|
+
*
|
|
607
|
+
* No-op when `text` fits within `maxBytes` (the common path). Otherwise keeps
|
|
608
|
+
* ~60% of the budget from the head and ~25% from the tail — cut on line
|
|
609
|
+
* boundaries, never splitting a multi-byte UTF-8 sequence — with an elision
|
|
610
|
+
* marker between. The remaining ~15% is slack for the marker and the optional
|
|
611
|
+
* `[raw output: artifact://<id>]` footer, so the result stays under `maxBytes`.
|
|
612
|
+
*/
|
|
613
|
+
export async function enforceInlineByteCap(text: string, options: InlineByteCapOptions): Promise<string> {
|
|
614
|
+
const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
|
|
615
|
+
if (maxBytes <= 0) return text;
|
|
616
|
+
const totalBytes = Buffer.byteLength(text, "utf-8");
|
|
617
|
+
if (totalBytes <= maxBytes) return text;
|
|
618
|
+
|
|
619
|
+
const head = trimHeadToLineBoundary(truncateHeadBytes(text, Math.floor(maxBytes * 0.6)).text);
|
|
620
|
+
const tail = trimTailToLineBoundary(truncateTailBytes(text, Math.floor(maxBytes * 0.25)).text);
|
|
621
|
+
const elidedBytes = Math.max(0, totalBytes - Buffer.byteLength(head, "utf-8") - Buffer.byteLength(tail, "utf-8"));
|
|
622
|
+
const marker = `[… elided ${elidedBytes} bytes of ${options.label} …]`;
|
|
623
|
+
let composed = `${head}\n${marker}\n${tail}`;
|
|
624
|
+
|
|
625
|
+
const artifactId = await options.saveArtifact?.(text);
|
|
626
|
+
if (artifactId) {
|
|
627
|
+
const sep = composed.endsWith(NL) ? "" : NL;
|
|
628
|
+
composed += `${sep}[raw output: artifact://${artifactId}]`;
|
|
629
|
+
}
|
|
630
|
+
return composed;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
// =============================================================================
|
|
634
|
+
// TailBuffer — ring-style tail buffer with lazy joining
|
|
635
|
+
// =============================================================================
|
|
636
|
+
|
|
637
|
+
const MAX_PENDING = 10;
|
|
638
|
+
|
|
639
|
+
export class TailBuffer {
|
|
640
|
+
#pending: string[] = [];
|
|
641
|
+
#pos = 0; // byte count of the currently-held tail (after trims)
|
|
642
|
+
|
|
643
|
+
constructor(readonly maxBytes: number) {}
|
|
644
|
+
|
|
645
|
+
append(text: string): void {
|
|
646
|
+
if (!text) return;
|
|
647
|
+
|
|
648
|
+
const max = this.maxBytes;
|
|
649
|
+
if (max === 0) {
|
|
650
|
+
this.#pending.length = 0;
|
|
651
|
+
this.#pos = 0;
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
const n = Buffer.byteLength(text, "utf-8");
|
|
656
|
+
|
|
657
|
+
// If the incoming chunk alone is >= budget, it fully dominates the tail.
|
|
658
|
+
if (n >= max) {
|
|
659
|
+
const { text: t, bytes } = truncateTailBytes(text, max);
|
|
660
|
+
this.#pending[0] = t;
|
|
661
|
+
this.#pending.length = 1;
|
|
662
|
+
this.#pos = bytes;
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
this.#pos += n;
|
|
667
|
+
|
|
668
|
+
if (this.#pending.length === 0) {
|
|
669
|
+
this.#pending[0] = text;
|
|
670
|
+
this.#pending.length = 1;
|
|
671
|
+
} else {
|
|
672
|
+
this.#pending.push(text);
|
|
673
|
+
if (this.#pending.length > MAX_PENDING) this.#compact();
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Trim when we exceed 2× budget to amortize cost.
|
|
677
|
+
if (this.#pos > max * 2) this.#trimTo(max);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
text(): string {
|
|
681
|
+
const max = this.maxBytes;
|
|
682
|
+
this.#trimTo(max);
|
|
683
|
+
return this.#flush();
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
bytes(): number {
|
|
687
|
+
const max = this.maxBytes;
|
|
688
|
+
this.#trimTo(max);
|
|
689
|
+
return this.#pos;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// -- private ---------------------------------------------------------------
|
|
693
|
+
|
|
694
|
+
#compact(): void {
|
|
695
|
+
this.#pending[0] = this.#pending.join("");
|
|
696
|
+
this.#pending.length = 1;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
#flush(): string {
|
|
700
|
+
if (this.#pending.length === 0) return "";
|
|
701
|
+
if (this.#pending.length > 1) this.#compact();
|
|
702
|
+
return this.#pending[0];
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
#trimTo(max: number): void {
|
|
706
|
+
if (max === 0) {
|
|
707
|
+
this.#pending.length = 0;
|
|
708
|
+
this.#pos = 0;
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
if (this.#pos <= max) return;
|
|
712
|
+
|
|
713
|
+
const joined = this.#flush();
|
|
714
|
+
const { text, bytes } = truncateTailBytes(joined, max);
|
|
715
|
+
this.#pos = bytes;
|
|
716
|
+
this.#pending[0] = text;
|
|
717
|
+
this.#pending.length = 1;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
// =============================================================================
|
|
722
|
+
// OutputSink — line-buffered output with file spill support
|
|
723
|
+
// =============================================================================
|
|
724
|
+
|
|
725
|
+
export class OutputSink {
|
|
726
|
+
#buffer = "";
|
|
727
|
+
#bufferBytes = 0;
|
|
728
|
+
#head = "";
|
|
729
|
+
#headBytes = 0;
|
|
730
|
+
#headLines = 0; // newline count inside #head
|
|
731
|
+
#headRetentionDisabled = false;
|
|
732
|
+
#totalLines = 0; // newline count
|
|
733
|
+
#totalBytes = 0;
|
|
734
|
+
#sawData = false;
|
|
735
|
+
#truncated = false;
|
|
736
|
+
#lastChunkTime = 0;
|
|
737
|
+
#pendingChunk = "";
|
|
738
|
+
|
|
739
|
+
// Per-line column cap streaming state (persists across `push` calls so a
|
|
740
|
+
// long line split across chunks still trips the same trigger).
|
|
741
|
+
#currentLineBytes = 0;
|
|
742
|
+
#columnEllipsisAdded = false;
|
|
743
|
+
#columnDroppedBytes = 0;
|
|
744
|
+
#columnTruncatedLines = 0;
|
|
745
|
+
#file?: {
|
|
746
|
+
path: string;
|
|
747
|
+
artifactId?: string;
|
|
748
|
+
sink: Bun.FileSink;
|
|
749
|
+
};
|
|
750
|
+
|
|
751
|
+
// Queue of chunks waiting for the file sink to be created.
|
|
752
|
+
#pendingFileWrites?: string[];
|
|
753
|
+
#fileReady = false;
|
|
754
|
+
|
|
755
|
+
readonly #artifactPath?: string;
|
|
756
|
+
readonly #artifactId?: string;
|
|
757
|
+
readonly #spillThreshold: number;
|
|
758
|
+
readonly #headLimit: number;
|
|
759
|
+
readonly #onChunk?: (chunk: string) => void;
|
|
760
|
+
readonly #chunkThrottleMs: number;
|
|
761
|
+
readonly #maxColumns: number;
|
|
762
|
+
|
|
763
|
+
// Optional artifact-on-disk cap. When `#artifactMaxBytes > 0` the file sink
|
|
764
|
+
// owns a head budget + a rolling tail buffer; once the head is closed,
|
|
765
|
+
// subsequent chunks are diverted into `#artifactTailRing` (bounded by
|
|
766
|
+
// `#artifactTailBudget`). On `dump()` the tail is flushed back to the sink
|
|
767
|
+
// behind a `[ARTIFACT TRUNCATED: …]` notice. The default cap is disabled so
|
|
768
|
+
// advertised `artifact://<id>` captures are lossless.
|
|
769
|
+
readonly #artifactMaxBytes: number;
|
|
770
|
+
readonly #artifactHeadBudget: number;
|
|
771
|
+
readonly #artifactTailBudget: number;
|
|
772
|
+
#artifactHeadBytesWritten = 0;
|
|
773
|
+
#artifactHeadClosed = false;
|
|
774
|
+
#artifactTailRing = "";
|
|
775
|
+
#artifactTailRingBytes = 0;
|
|
776
|
+
#artifactTailIncomingBytes = 0;
|
|
777
|
+
|
|
778
|
+
constructor(options?: OutputSinkOptions) {
|
|
779
|
+
const {
|
|
780
|
+
artifactPath,
|
|
781
|
+
artifactId,
|
|
782
|
+
spillThreshold = DEFAULT_MAX_BYTES,
|
|
783
|
+
headBytes = 0,
|
|
784
|
+
maxColumns = 0,
|
|
785
|
+
onChunk,
|
|
786
|
+
chunkThrottleMs = 0,
|
|
787
|
+
artifactMaxBytes = ARTIFACT_DEFAULT_MAX_BYTES,
|
|
788
|
+
artifactHeadBytes = ARTIFACT_DEFAULT_HEAD_BYTES,
|
|
789
|
+
} = options ?? {};
|
|
790
|
+
this.#artifactPath = artifactPath;
|
|
791
|
+
this.#artifactId = artifactId;
|
|
792
|
+
this.#spillThreshold = spillThreshold;
|
|
793
|
+
this.#headLimit = Math.max(0, headBytes);
|
|
794
|
+
this.#maxColumns = Math.max(0, maxColumns);
|
|
795
|
+
this.#onChunk = onChunk;
|
|
796
|
+
this.#chunkThrottleMs = chunkThrottleMs;
|
|
797
|
+
this.#artifactMaxBytes = Math.max(0, artifactMaxBytes);
|
|
798
|
+
this.#artifactHeadBudget = Math.max(0, Math.min(artifactHeadBytes, this.#artifactMaxBytes));
|
|
799
|
+
this.#artifactTailBudget = Math.max(0, this.#artifactMaxBytes - this.#artifactHeadBudget);
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
/**
|
|
803
|
+
* Push a chunk of output. The buffer management and onChunk callback run
|
|
804
|
+
* synchronously. File sink writes are deferred and serialized internally.
|
|
805
|
+
*/
|
|
806
|
+
push(chunk: string): void {
|
|
807
|
+
chunk = sanitizeWithOptionalSixelPassthrough(chunk, sanitizeText);
|
|
808
|
+
|
|
809
|
+
// Throttled onChunk: coalesce chunks arriving inside the throttle window
|
|
810
|
+
// and flush the buffered concatenation on the next eligible tick (plus a
|
|
811
|
+
// final flush in dump()) so the preview never has silent gaps.
|
|
812
|
+
// Live preview gets the raw (pre-cap) chunk so the TUI never lags behind
|
|
813
|
+
// what reached the sink — the column cap is for the persisted LLM view.
|
|
814
|
+
if (this.#onChunk) {
|
|
815
|
+
const now = Date.now();
|
|
816
|
+
if (now - this.#lastChunkTime >= this.#chunkThrottleMs) {
|
|
817
|
+
this.#lastChunkTime = now;
|
|
818
|
+
const merged = this.#pendingChunk + chunk;
|
|
819
|
+
this.#pendingChunk = "";
|
|
820
|
+
this.#onChunk(merged);
|
|
821
|
+
} else {
|
|
822
|
+
this.#pendingChunk += chunk;
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
const rawBytes = Buffer.byteLength(chunk, "utf-8");
|
|
827
|
+
this.#totalBytes += rawBytes;
|
|
828
|
+
|
|
829
|
+
if (chunk.length > 0) {
|
|
830
|
+
this.#sawData = true;
|
|
831
|
+
this.#totalLines += countNewlines(chunk);
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
// Per-line column cap. State persists across chunks so a mid-line split
|
|
835
|
+
// still respects the budget. Operates on the sanitized chunk; the cap is
|
|
836
|
+
// applied before head/tail accounting but after artifact mirroring decides.
|
|
837
|
+
const capped = this.#maxColumns > 0 ? this.#applyColumnCap(chunk) : chunk;
|
|
838
|
+
const cappedBytes = capped === chunk ? rawBytes : Buffer.byteLength(capped, "utf-8");
|
|
839
|
+
const cappedThisChunk = cappedBytes < rawBytes;
|
|
840
|
+
if (cappedThisChunk) this.#truncated = true;
|
|
841
|
+
|
|
842
|
+
// Mirror RAW chunk to the artifact file so the on-disk record is the full
|
|
843
|
+
// uncapped stream. Mirror triggers on: in-memory overflow OR this chunk's
|
|
844
|
+
// column cap dropped bytes (otherwise we'd lose data) OR file already open.
|
|
845
|
+
if (this.#artifactPath && (this.#file != null || cappedThisChunk || this.#willOverflow(cappedBytes))) {
|
|
846
|
+
this.#writeToFile(chunk);
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
if (cappedBytes === 0) return;
|
|
850
|
+
|
|
851
|
+
// Head retention: drain the (capped) chunk into #head until the budget is
|
|
852
|
+
// exhausted, then forward any leftover to the tail buffer.
|
|
853
|
+
let tailChunk = capped;
|
|
854
|
+
let tailBytes = cappedBytes;
|
|
855
|
+
if (this.#headLimit > 0 && !this.#headRetentionDisabled && this.#headBytes < this.#headLimit) {
|
|
856
|
+
const room = this.#headLimit - this.#headBytes;
|
|
857
|
+
if (cappedBytes <= room) {
|
|
858
|
+
this.#head += capped;
|
|
859
|
+
this.#headBytes += cappedBytes;
|
|
860
|
+
this.#headLines += countNewlines(capped);
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
// Split: head takes a UTF-8-safe prefix; remainder flows to tail.
|
|
864
|
+
const headSlice = truncateHeadBytes(capped, room);
|
|
865
|
+
if (headSlice.bytes > 0) {
|
|
866
|
+
this.#head += headSlice.text;
|
|
867
|
+
this.#headBytes += headSlice.bytes;
|
|
868
|
+
this.#headLines += countNewlines(headSlice.text);
|
|
869
|
+
tailChunk = capped.substring(headSlice.text.length);
|
|
870
|
+
tailBytes = cappedBytes - headSlice.bytes;
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
this.#pushTail(tailChunk, tailBytes);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* Apply the per-line byte cap to `chunk`, dropping bytes that would push the
|
|
879
|
+
* current line beyond `#maxColumns`. Emits a single `…` once a line trips the
|
|
880
|
+
* cap; subsequent bytes are skipped until the next `\n`. State persists
|
|
881
|
+
* across calls so a long line split across chunks still produces one marker.
|
|
882
|
+
*/
|
|
883
|
+
#applyColumnCap(chunk: string): string {
|
|
884
|
+
if (chunk.length === 0) return chunk;
|
|
885
|
+
const max = this.#maxColumns;
|
|
886
|
+
const parts: string[] = [];
|
|
887
|
+
let cursor = 0;
|
|
888
|
+
while (cursor < chunk.length) {
|
|
889
|
+
const nlIdx = chunk.indexOf(NL, cursor);
|
|
890
|
+
const segEnd = nlIdx === -1 ? chunk.length : nlIdx;
|
|
891
|
+
if (segEnd > cursor) {
|
|
892
|
+
const segment = chunk.substring(cursor, segEnd);
|
|
893
|
+
if (this.#columnEllipsisAdded) {
|
|
894
|
+
// Past the cap; drop until newline.
|
|
895
|
+
this.#columnDroppedBytes += Buffer.byteLength(segment, "utf-8");
|
|
896
|
+
} else {
|
|
897
|
+
const segBytes = Buffer.byteLength(segment, "utf-8");
|
|
898
|
+
const remaining = max - this.#currentLineBytes;
|
|
899
|
+
if (segBytes <= remaining) {
|
|
900
|
+
parts.push(segment);
|
|
901
|
+
this.#currentLineBytes += segBytes;
|
|
902
|
+
} else {
|
|
903
|
+
// First overflow on this line: keep what fits, append ellipsis,
|
|
904
|
+
// arm the skip-until-newline flag.
|
|
905
|
+
const ellipsisBytes = 3; // "…" in UTF-8
|
|
906
|
+
const headRoom = Math.max(0, remaining - ellipsisBytes);
|
|
907
|
+
let kept = "";
|
|
908
|
+
let keptBytes = 0;
|
|
909
|
+
if (headRoom > 0) {
|
|
910
|
+
const sliced = truncateHeadBytes(segment, headRoom);
|
|
911
|
+
kept = sliced.text;
|
|
912
|
+
keptBytes = sliced.bytes;
|
|
913
|
+
parts.push(kept);
|
|
914
|
+
}
|
|
915
|
+
parts.push(ELLIPSIS);
|
|
916
|
+
this.#columnDroppedBytes += segBytes - keptBytes;
|
|
917
|
+
this.#columnTruncatedLines++;
|
|
918
|
+
this.#currentLineBytes += keptBytes + ellipsisBytes;
|
|
919
|
+
this.#columnEllipsisAdded = true;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
if (nlIdx === -1) break;
|
|
924
|
+
parts.push(NL);
|
|
925
|
+
this.#currentLineBytes = 0;
|
|
926
|
+
this.#columnEllipsisAdded = false;
|
|
927
|
+
cursor = nlIdx + 1;
|
|
928
|
+
}
|
|
929
|
+
return parts.join("");
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
#willOverflow(dataBytes: number): boolean {
|
|
933
|
+
// Triggers file mirroring as soon as the next chunk would push us over
|
|
934
|
+
// the tail budget (head retention does not change spill-to-artifact).
|
|
935
|
+
return this.#bufferBytes + dataBytes > this.#spillThreshold;
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
#pushTail(chunk: string, dataBytes: number): void {
|
|
939
|
+
if (dataBytes === 0) return;
|
|
940
|
+
|
|
941
|
+
const threshold = this.#spillThreshold;
|
|
942
|
+
const willOverflow = this.#bufferBytes + dataBytes > threshold;
|
|
943
|
+
|
|
944
|
+
if (!willOverflow) {
|
|
945
|
+
this.#buffer += chunk;
|
|
946
|
+
this.#bufferBytes += dataBytes;
|
|
947
|
+
return;
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
// Overflow: keep only a tail window in memory.
|
|
951
|
+
this.#truncated = true;
|
|
952
|
+
|
|
953
|
+
// Avoid creating a giant intermediate string when chunk alone dominates.
|
|
954
|
+
if (dataBytes >= threshold) {
|
|
955
|
+
const { text, bytes } = truncateTailBytes(chunk, threshold);
|
|
956
|
+
this.#buffer = text;
|
|
957
|
+
this.#bufferBytes = bytes;
|
|
958
|
+
} else {
|
|
959
|
+
// Intermediate size is bounded (<= threshold + dataBytes), safe to concat.
|
|
960
|
+
this.#buffer += chunk;
|
|
961
|
+
this.#bufferBytes += dataBytes;
|
|
962
|
+
|
|
963
|
+
const { text, bytes } = truncateTailBytes(this.#buffer, threshold);
|
|
964
|
+
this.#buffer = text;
|
|
965
|
+
this.#bufferBytes = bytes;
|
|
966
|
+
}
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
/**
|
|
970
|
+
* Write a chunk to the artifact file. Handles the async file sink creation
|
|
971
|
+
* by queuing writes until the sink is ready, then draining synchronously.
|
|
972
|
+
* Once the sink is up, every byte flows through {@link #emitToSink} which
|
|
973
|
+
* owns the head + tail cap so artifacts cannot grow beyond
|
|
974
|
+
* `#artifactMaxBytes` on disk.
|
|
975
|
+
*/
|
|
976
|
+
#writeToFile(chunk: string): void {
|
|
977
|
+
if (this.#fileReady && this.#file) {
|
|
978
|
+
this.#emitToSink(chunk);
|
|
979
|
+
return;
|
|
980
|
+
}
|
|
981
|
+
// File sink not yet created — queue this chunk and kick off creation.
|
|
982
|
+
// The queue is bounded only by how many chunks arrive before the open
|
|
983
|
+
// resolves (typically <2). The cap is enforced on drain.
|
|
984
|
+
if (!this.#pendingFileWrites) {
|
|
985
|
+
this.#pendingFileWrites = [chunk];
|
|
986
|
+
void this.#createFileSink();
|
|
987
|
+
} else {
|
|
988
|
+
this.#pendingFileWrites.push(chunk);
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
/**
|
|
993
|
+
* Cap-aware sink writer. Bytes flow into the head window verbatim until the
|
|
994
|
+
* budget is exhausted; subsequent bytes are diverted into a rolling tail
|
|
995
|
+
* ring, evicted from the front so total RAM stays bounded by
|
|
996
|
+
* `#artifactTailBudget`. `dump()` replays the ring behind a single notice
|
|
997
|
+
* line before closing the sink.
|
|
998
|
+
*
|
|
999
|
+
* When the cap is disabled (`#artifactMaxBytes === 0`) this collapses to a
|
|
1000
|
+
* straight pass-through, preserving the historical "stream everything"
|
|
1001
|
+
* contract.
|
|
1002
|
+
*/
|
|
1003
|
+
#emitToSink(chunk: string): void {
|
|
1004
|
+
if (!this.#file || chunk.length === 0) return;
|
|
1005
|
+
if (this.#artifactMaxBytes === 0) {
|
|
1006
|
+
this.#file.sink.write(chunk);
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
const chunkBytes = Buffer.byteLength(chunk, "utf-8");
|
|
1010
|
+
const room = this.#artifactHeadClosed ? 0 : this.#artifactHeadBudget - this.#artifactHeadBytesWritten;
|
|
1011
|
+
if (room >= chunkBytes) {
|
|
1012
|
+
this.#file.sink.write(chunk);
|
|
1013
|
+
this.#artifactHeadBytesWritten += chunkBytes;
|
|
1014
|
+
return;
|
|
1015
|
+
}
|
|
1016
|
+
let overflow = chunk;
|
|
1017
|
+
if (room > 0) {
|
|
1018
|
+
const headSlice = truncateHeadBytes(chunk, room);
|
|
1019
|
+
if (headSlice.bytes > 0) {
|
|
1020
|
+
this.#file.sink.write(headSlice.text);
|
|
1021
|
+
this.#artifactHeadBytesWritten += headSlice.bytes;
|
|
1022
|
+
}
|
|
1023
|
+
// Even when UTF-8 boundary safety leaves a few bytes of nominal room,
|
|
1024
|
+
// this chunk has already overflowed the head window. Close it now so a
|
|
1025
|
+
// later small ASCII chunk cannot be written before this overflow tail.
|
|
1026
|
+
this.#artifactHeadClosed = true;
|
|
1027
|
+
overflow = chunk.substring(headSlice.text.length);
|
|
1028
|
+
}
|
|
1029
|
+
if (overflow.length === 0 || this.#artifactTailBudget === 0) {
|
|
1030
|
+
// No tail budget: count the dropped bytes so the notice reflects them.
|
|
1031
|
+
if (overflow.length > 0) {
|
|
1032
|
+
this.#artifactTailIncomingBytes += Buffer.byteLength(overflow, "utf-8");
|
|
1033
|
+
}
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
this.#pushArtifactTail(overflow);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
#pushArtifactTail(chunk: string): void {
|
|
1040
|
+
const chunkBytes = Buffer.byteLength(chunk, "utf-8");
|
|
1041
|
+
this.#artifactTailIncomingBytes += chunkBytes;
|
|
1042
|
+
const budget = this.#artifactTailBudget;
|
|
1043
|
+
if (chunkBytes >= budget) {
|
|
1044
|
+
// Chunk alone dominates — keep only its tail slice.
|
|
1045
|
+
const { text, bytes } = truncateTailBytes(chunk, budget);
|
|
1046
|
+
this.#artifactTailRing = text;
|
|
1047
|
+
this.#artifactTailRingBytes = bytes;
|
|
1048
|
+
return;
|
|
1049
|
+
}
|
|
1050
|
+
this.#artifactTailRing += chunk;
|
|
1051
|
+
this.#artifactTailRingBytes += chunkBytes;
|
|
1052
|
+
if (this.#artifactTailRingBytes > budget) {
|
|
1053
|
+
const { text, bytes } = truncateTailBytes(this.#artifactTailRing, budget);
|
|
1054
|
+
this.#artifactTailRing = text;
|
|
1055
|
+
this.#artifactTailRingBytes = bytes;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
|
|
1059
|
+
async #createFileSink(): Promise<void> {
|
|
1060
|
+
if (!this.#artifactPath || this.#fileReady) return;
|
|
1061
|
+
try {
|
|
1062
|
+
const sink = Bun.file(this.#artifactPath).writer();
|
|
1063
|
+
this.#file = { path: this.#artifactPath, artifactId: this.#artifactId, sink };
|
|
1064
|
+
this.#fileReady = true;
|
|
1065
|
+
|
|
1066
|
+
// Head-retained bytes precede the rolling tail buffer in the capture.
|
|
1067
|
+
// Route through #emitToSink so they count against the artifact head
|
|
1068
|
+
// budget — a direct sink.write would let them escape the cap.
|
|
1069
|
+
if (this.#head.length > 0) {
|
|
1070
|
+
this.#emitToSink(this.#head);
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
// Flush existing buffer to file BEFORE it gets trimmed further.
|
|
1074
|
+
if (this.#buffer.length > 0) {
|
|
1075
|
+
this.#emitToSink(this.#buffer);
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// Drain any chunks that arrived while the sink was being created.
|
|
1079
|
+
if (this.#pendingFileWrites) {
|
|
1080
|
+
for (const pending of this.#pendingFileWrites) {
|
|
1081
|
+
this.#emitToSink(pending);
|
|
1082
|
+
}
|
|
1083
|
+
this.#pendingFileWrites = undefined;
|
|
1084
|
+
}
|
|
1085
|
+
} catch {
|
|
1086
|
+
try {
|
|
1087
|
+
await this.#file?.sink?.end();
|
|
1088
|
+
} catch {
|
|
1089
|
+
/* ignore */
|
|
1090
|
+
}
|
|
1091
|
+
this.#file = undefined;
|
|
1092
|
+
this.#pendingFileWrites = undefined;
|
|
1093
|
+
this.#fileReady = false;
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
|
|
1097
|
+
createInput(): WritableStream<Uint8Array | string> {
|
|
1098
|
+
const dec = new TextDecoder("utf-8", { ignoreBOM: true });
|
|
1099
|
+
const finalize = () => {
|
|
1100
|
+
this.push(dec.decode());
|
|
1101
|
+
};
|
|
1102
|
+
return new WritableStream({
|
|
1103
|
+
write: chunk => {
|
|
1104
|
+
this.push(typeof chunk === "string" ? chunk : dec.decode(chunk, { stream: true }));
|
|
1105
|
+
},
|
|
1106
|
+
close: finalize,
|
|
1107
|
+
abort: finalize,
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* Replace the in-memory buffer with the given text. Used when an upstream
|
|
1113
|
+
* minimizer rewrites the captured output after the raw bytes have already
|
|
1114
|
+
* been streamed.
|
|
1115
|
+
*
|
|
1116
|
+
* After this call the buffer is authoritative: streaming counters realign
|
|
1117
|
+
* to the replacement, the retained head window is cleared, and head
|
|
1118
|
+
* retention is disabled so subsequent `push()` calls append directly to the
|
|
1119
|
+
* tail buffer instead of repopulating the (now meaningless) head window
|
|
1120
|
+
* — which would otherwise reorder content and trip the middle-elision
|
|
1121
|
+
* branch in `dump()` against stale totals.
|
|
1122
|
+
*/
|
|
1123
|
+
replace(text: string): void {
|
|
1124
|
+
this.#buffer = text;
|
|
1125
|
+
this.#bufferBytes = Buffer.byteLength(text, "utf-8");
|
|
1126
|
+
this.#head = "";
|
|
1127
|
+
this.#headBytes = 0;
|
|
1128
|
+
this.#headLines = 0;
|
|
1129
|
+
this.#headRetentionDisabled = true;
|
|
1130
|
+
this.#totalBytes = this.#bufferBytes;
|
|
1131
|
+
this.#totalLines = countNewlines(text);
|
|
1132
|
+
this.#sawData = text.length > 0;
|
|
1133
|
+
this.#truncated = false;
|
|
1134
|
+
this.#currentLineBytes = 0;
|
|
1135
|
+
this.#columnEllipsisAdded = false;
|
|
1136
|
+
this.#columnDroppedBytes = 0;
|
|
1137
|
+
this.#columnTruncatedLines = 0;
|
|
1138
|
+
this.#pendingChunk = "";
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
/**
|
|
1142
|
+
* Replay the rolling tail ring back into the artifact sink. When bytes
|
|
1143
|
+
* were actually dropped from the middle (the head budget was exhausted
|
|
1144
|
+
* *and* the tail ring evicted), a single `[ARTIFACT TRUNCATED: …]`
|
|
1145
|
+
* notice is injected between head and tail so a reader of
|
|
1146
|
+
* `artifact://<id>` understands the gap. When the total stream simply
|
|
1147
|
+
* spilled past the head budget but still fits below `artifactMaxBytes`,
|
|
1148
|
+
* `droppedBytes` is zero — head + tail together are the verbatim stream
|
|
1149
|
+
* and the notice is suppressed so we don't corrupt the artifact with a
|
|
1150
|
+
* misleading "0 B elided" marker (PR #2083 review by codex).
|
|
1151
|
+
*
|
|
1152
|
+
* No-op when the cap was never hit at all (head budget never exhausted,
|
|
1153
|
+
* tail ring empty).
|
|
1154
|
+
*/
|
|
1155
|
+
#flushArtifactTailIfCapped(): void {
|
|
1156
|
+
if (!this.#file) return;
|
|
1157
|
+
if (this.#artifactMaxBytes === 0) return;
|
|
1158
|
+
const tailBytes = this.#artifactTailRingBytes;
|
|
1159
|
+
const droppedBytes = Math.max(0, this.#artifactTailIncomingBytes - tailBytes);
|
|
1160
|
+
if (tailBytes === 0 && droppedBytes === 0) return;
|
|
1161
|
+
|
|
1162
|
+
if (droppedBytes > 0) {
|
|
1163
|
+
const headWritten = this.#artifactHeadBytesWritten;
|
|
1164
|
+
const totalCapped = headWritten + this.#artifactTailIncomingBytes;
|
|
1165
|
+
const headSep = headWritten > 0 ? "\n" : "";
|
|
1166
|
+
const tailSep = tailBytes > 0 && !this.#artifactTailRing.startsWith("\n") ? "\n" : "";
|
|
1167
|
+
const notice =
|
|
1168
|
+
`${headSep}[ARTIFACT TRUNCATED: kept first ${formatBytes(headWritten)} + last ${formatBytes(tailBytes)} ` +
|
|
1169
|
+
`of ${formatBytes(totalCapped)}; ${formatBytes(droppedBytes)} elided from the middle]${tailSep}`;
|
|
1170
|
+
this.#file.sink.write(notice);
|
|
1171
|
+
}
|
|
1172
|
+
if (tailBytes > 0) {
|
|
1173
|
+
this.#file.sink.write(this.#artifactTailRing);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
async dump(notice?: string): Promise<OutputSummary> {
|
|
1178
|
+
const noticeLine = notice ? `[${notice}]\n` : "";
|
|
1179
|
+
|
|
1180
|
+
// Flush any chunk still held back by the throttle so the live preview
|
|
1181
|
+
// ends with the complete stream.
|
|
1182
|
+
if (this.#onChunk && this.#pendingChunk.length > 0) {
|
|
1183
|
+
const pending = this.#pendingChunk;
|
|
1184
|
+
this.#pendingChunk = "";
|
|
1185
|
+
this.#onChunk(pending);
|
|
1186
|
+
}
|
|
1187
|
+
const totalLines = this.#sawData ? this.#totalLines + 1 : 0;
|
|
1188
|
+
|
|
1189
|
+
if (this.#file) {
|
|
1190
|
+
this.#flushArtifactTailIfCapped();
|
|
1191
|
+
await this.#file.sink.end();
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// Compose the visible output. With head retention, splice head + marker
|
|
1195
|
+
// + tail when content was elided. Otherwise return the rolling buffer.
|
|
1196
|
+
const headBytes = this.#headBytes;
|
|
1197
|
+
const tailBuf = this.#buffer;
|
|
1198
|
+
const tailBytes = this.#bufferBytes;
|
|
1199
|
+
const headLines = this.#headLines + (headBytes > 0 && !this.#head.endsWith("\n") ? 1 : 0);
|
|
1200
|
+
const tailLines = tailBuf.length > 0 ? countNewlines(tailBuf) + 1 : 0;
|
|
1201
|
+
|
|
1202
|
+
// Bytes that survived the column cap. Middle elision operates on these,
|
|
1203
|
+
// so column-dropped bytes don't inflate the "elided from middle" count.
|
|
1204
|
+
const effectiveTotalBytes = Math.max(0, this.#totalBytes - this.#columnDroppedBytes);
|
|
1205
|
+
|
|
1206
|
+
let body: string;
|
|
1207
|
+
let outputBytes: number;
|
|
1208
|
+
let outputLines: number;
|
|
1209
|
+
let elidedBytes: number | undefined;
|
|
1210
|
+
let elidedLines: number | undefined;
|
|
1211
|
+
|
|
1212
|
+
if (headBytes > 0 && effectiveTotalBytes > headBytes + tailBytes) {
|
|
1213
|
+
// Middle was elided. Emit head + marker + tail.
|
|
1214
|
+
elidedBytes = Math.max(0, effectiveTotalBytes - headBytes - tailBytes);
|
|
1215
|
+
elidedLines = Math.max(0, totalLines - headLines - tailLines);
|
|
1216
|
+
const marker = formatMiddleElisionMarker(elidedLines, elidedBytes);
|
|
1217
|
+
const markerBytes = Buffer.byteLength(marker, "utf-8");
|
|
1218
|
+
const headSep = this.#head.endsWith("\n") ? "" : "\n";
|
|
1219
|
+
const tailSep = tailBuf.startsWith("\n") ? "" : "\n";
|
|
1220
|
+
body = `${this.#head}${headSep}${marker}${tailSep}${tailBuf}`;
|
|
1221
|
+
outputBytes =
|
|
1222
|
+
headBytes +
|
|
1223
|
+
markerBytes +
|
|
1224
|
+
tailBytes +
|
|
1225
|
+
Buffer.byteLength(headSep, "utf-8") +
|
|
1226
|
+
Buffer.byteLength(tailSep, "utf-8");
|
|
1227
|
+
outputLines = headLines + 1 + tailLines;
|
|
1228
|
+
this.#truncated = true;
|
|
1229
|
+
} else if (headBytes > 0) {
|
|
1230
|
+
// Head + tail combine into the full buffered output (no overlap or elision).
|
|
1231
|
+
body = `${this.#head}${tailBuf}`;
|
|
1232
|
+
outputBytes = headBytes + tailBytes;
|
|
1233
|
+
outputLines = body.length > 0 ? countNewlines(body) + 1 : 0;
|
|
1234
|
+
} else {
|
|
1235
|
+
body = tailBuf;
|
|
1236
|
+
outputBytes = tailBytes;
|
|
1237
|
+
outputLines = tailLines;
|
|
1238
|
+
}
|
|
1239
|
+
|
|
1240
|
+
return {
|
|
1241
|
+
output: `${noticeLine}${body}`,
|
|
1242
|
+
truncated: this.#truncated,
|
|
1243
|
+
totalLines,
|
|
1244
|
+
totalBytes: this.#totalBytes,
|
|
1245
|
+
outputLines,
|
|
1246
|
+
outputBytes,
|
|
1247
|
+
elidedBytes,
|
|
1248
|
+
elidedLines,
|
|
1249
|
+
columnDroppedBytes: this.#columnDroppedBytes > 0 ? this.#columnDroppedBytes : undefined,
|
|
1250
|
+
columnTruncatedLines: this.#columnTruncatedLines > 0 ? this.#columnTruncatedLines : undefined,
|
|
1251
|
+
artifactId: this.#file?.artifactId,
|
|
1252
|
+
};
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
// =============================================================================
|
|
1257
|
+
// Truncation notice formatting
|
|
1258
|
+
// =============================================================================
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* Format a truncation notice for tail-truncated output (bash, python, ssh).
|
|
1262
|
+
* Returns empty string if not truncated.
|
|
1263
|
+
*/
|
|
1264
|
+
export function formatTailTruncationNotice(
|
|
1265
|
+
truncation: TruncationResult,
|
|
1266
|
+
options: TailTruncationNoticeOptions = {},
|
|
1267
|
+
): string {
|
|
1268
|
+
if (!truncation.truncated) return "";
|
|
1269
|
+
|
|
1270
|
+
const { fullOutputPath, originalContent, suffix = "" } = options;
|
|
1271
|
+
const startLine = truncation.totalLines - (truncation.outputLines ?? truncation.totalLines) + 1;
|
|
1272
|
+
const endLine = truncation.totalLines;
|
|
1273
|
+
const fullOutputPart = fullOutputPath ? `. Full output: ${fullOutputPath}` : "";
|
|
1274
|
+
|
|
1275
|
+
let notice: string;
|
|
1276
|
+
if (truncation.lastLinePartial) {
|
|
1277
|
+
let lastLineSizePart = "";
|
|
1278
|
+
if (originalContent) {
|
|
1279
|
+
const lastNl = originalContent.lastIndexOf(NL);
|
|
1280
|
+
const lastLine = lastNl === -1 ? originalContent : originalContent.substring(lastNl + 1);
|
|
1281
|
+
lastLineSizePart = ` (line is ${formatBytes(Buffer.byteLength(lastLine, "utf-8"))})`;
|
|
1282
|
+
}
|
|
1283
|
+
notice = `[Showing last ${formatBytes(truncation.outputBytes ?? truncation.totalBytes)} of line ${endLine}${lastLineSizePart}${fullOutputPart}${suffix}]`;
|
|
1284
|
+
} else {
|
|
1285
|
+
notice = `[Showing lines ${startLine}-${endLine} of ${truncation.totalLines}${fullOutputPart}${suffix}]`;
|
|
1286
|
+
}
|
|
1287
|
+
|
|
1288
|
+
return `\n\n${notice}`;
|
|
1289
|
+
}
|
|
1290
|
+
|
|
1291
|
+
/**
|
|
1292
|
+
* Format a truncation notice for head-truncated output (read tool).
|
|
1293
|
+
* Returns empty string if not truncated.
|
|
1294
|
+
*/
|
|
1295
|
+
export function formatHeadTruncationNotice(
|
|
1296
|
+
truncation: TruncationResult,
|
|
1297
|
+
options: HeadTruncationNoticeOptions = {},
|
|
1298
|
+
): string {
|
|
1299
|
+
if (!truncation.truncated) return "";
|
|
1300
|
+
|
|
1301
|
+
const startLineDisplay = options.startLine ?? 1;
|
|
1302
|
+
const totalFileLines = options.totalFileLines ?? truncation.totalLines;
|
|
1303
|
+
const endLineDisplay = startLineDisplay + (truncation.outputLines ?? truncation.totalLines) - 1;
|
|
1304
|
+
const nextOffset = endLineDisplay + 1;
|
|
1305
|
+
const notice = `[Showing lines ${startLineDisplay}-${endLineDisplay} of ${totalFileLines}. Use :${nextOffset} to continue]`;
|
|
1306
|
+
return `\n\n${notice}`;
|
|
1307
|
+
}
|
|
1308
|
+
|
|
1309
|
+
// =============================================================================
|
|
1310
|
+
// Streaming tail update helper (shared by bash/ssh tools)
|
|
1311
|
+
// =============================================================================
|
|
1312
|
+
|
|
1313
|
+
/**
|
|
1314
|
+
* Build an onChunk handler that appends to a TailBuffer and emits a streaming
|
|
1315
|
+
* update (when `onUpdate` is defined) with the buffer's current text.
|
|
1316
|
+
*/
|
|
1317
|
+
export function streamTailUpdates<TDetails, TInput = unknown>(
|
|
1318
|
+
tailBuffer: TailBuffer,
|
|
1319
|
+
onUpdate: AgentToolUpdateCallback<TDetails, TInput> | undefined,
|
|
1320
|
+
): (chunk: string) => void {
|
|
1321
|
+
return chunk => {
|
|
1322
|
+
tailBuffer.append(chunk);
|
|
1323
|
+
if (onUpdate) {
|
|
1324
|
+
onUpdate({
|
|
1325
|
+
content: [{ type: "text", text: tailBuffer.text() }],
|
|
1326
|
+
details: {} as TDetails,
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
}
|