@oh-my-pi/pi-coding-agent 15.10.10 → 15.10.12
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 +142 -7
- package/dist/cli.js +23108 -0
- package/dist/tokenizers.linux-x64-gnu-xcjh3jwk.node +0 -0
- package/dist/types/async/job-manager.d.ts +18 -0
- package/dist/types/cli/args.d.ts +2 -1
- package/dist/types/cli/dry-balance-cli.d.ts +1 -1
- package/dist/types/cli/gallery-cli.d.ts +1 -1
- package/dist/types/cli/gallery-fixtures/types.d.ts +1 -1
- package/dist/types/cli/usage-cli.d.ts +72 -0
- package/dist/types/cli-commands.d.ts +12 -0
- package/dist/types/commands/launch.d.ts +5 -1
- package/dist/types/commands/read.d.ts +1 -1
- package/dist/types/commands/usage.d.ts +25 -0
- package/dist/types/config/api-key-resolver.d.ts +3 -0
- package/dist/types/config/append-only-context-mode.d.ts +2 -1
- package/dist/types/config/model-discovery.d.ts +55 -0
- package/dist/types/config/model-registry.d.ts +8 -219
- package/dist/types/config/model-resolver.d.ts +34 -10
- package/dist/types/config/model-roles.d.ts +28 -0
- package/dist/types/config/models-config-schema.d.ts +523 -42
- package/dist/types/config/models-config.d.ts +385 -0
- package/dist/types/config/settings-schema.d.ts +41 -8
- package/dist/types/config/settings.d.ts +8 -1
- package/dist/types/debug/log-viewer.d.ts +1 -1
- package/dist/types/debug/raw-sse.d.ts +1 -1
- package/dist/types/edit/hashline/noop-loop-guard.d.ts +72 -0
- package/dist/types/eval/backend.d.ts +0 -2
- package/dist/types/eval/idle-timeout.d.ts +0 -4
- package/dist/types/eval/js/shared/rewrite-imports.d.ts +6 -6
- package/dist/types/eval/py/executor.d.ts +5 -0
- package/dist/types/eval/py/kernel.d.ts +6 -1
- package/dist/types/eval/py/runtime.d.ts +9 -0
- package/dist/types/exec/bash-executor.d.ts +2 -0
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/runner.d.ts +3 -2
- package/dist/types/extensibility/extensions/types.d.ts +6 -3
- package/dist/types/hindsight/mental-models.d.ts +17 -8
- package/dist/types/internal-urls/artifact-protocol.d.ts +2 -2
- package/dist/types/internal-urls/types.d.ts +1 -1
- package/dist/types/lsp/edits.d.ts +9 -0
- package/dist/types/lsp/index.d.ts +2 -2
- package/dist/types/lsp/types.d.ts +2 -0
- package/dist/types/lsp/utils.d.ts +3 -0
- package/dist/types/mcp/json-rpc.d.ts +5 -0
- package/dist/types/memory-backend/index.d.ts +1 -0
- package/dist/types/memory-backend/runtime.d.ts +4 -0
- package/dist/types/memory-backend/types.d.ts +66 -1
- package/dist/types/mnemopi/state.d.ts +11 -1
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -1
- package/dist/types/modes/components/assistant-message.d.ts +3 -1
- package/dist/types/modes/components/bash-execution.d.ts +1 -1
- package/dist/types/modes/components/copy-selector.d.ts +1 -1
- package/dist/types/modes/components/dynamic-border.d.ts +1 -1
- package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -1
- package/dist/types/modes/components/extensions/extension-list.d.ts +1 -1
- package/dist/types/modes/components/extensions/inspector-panel.d.ts +1 -1
- package/dist/types/modes/components/footer.d.ts +1 -1
- package/dist/types/modes/components/hook-editor.d.ts +5 -0
- package/dist/types/modes/components/hook-input.d.ts +4 -0
- package/dist/types/modes/components/hook-selector.d.ts +1 -1
- package/dist/types/modes/components/model-selector.d.ts +1 -1
- package/dist/types/modes/components/plan-review-overlay.d.ts +1 -1
- package/dist/types/modes/components/session-observer-overlay.d.ts +1 -1
- package/dist/types/modes/components/session-selector.d.ts +1 -1
- package/dist/types/modes/components/status-line/component.d.ts +1 -1
- package/dist/types/modes/components/tiny-title-download-progress.d.ts +1 -1
- package/dist/types/modes/components/transcript-container.d.ts +25 -6
- package/dist/types/modes/components/tree-selector.d.ts +1 -1
- package/dist/types/modes/components/user-message-selector.d.ts +1 -1
- package/dist/types/modes/components/user-message.d.ts +2 -1
- package/dist/types/modes/components/visual-truncate.d.ts +1 -1
- package/dist/types/modes/components/welcome.d.ts +19 -3
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/streaming-reveal.d.ts +1 -1
- package/dist/types/modes/index.d.ts +3 -3
- package/dist/types/modes/interactive-mode.d.ts +8 -3
- package/dist/types/modes/oauth-manual-input.d.ts +7 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +39 -2
- package/dist/types/modes/rpc/rpc-mode.d.ts +31 -2
- package/dist/types/modes/rpc/rpc-subagents.d.ts +24 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +75 -1
- package/dist/types/modes/setup-wizard/index.d.ts +5 -1
- package/dist/types/modes/setup-wizard/lazy.d.ts +2 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/modes/setup-wizard/wizard-overlay.d.ts +1 -1
- package/dist/types/modes/types.d.ts +4 -1
- package/dist/types/secrets/index.d.ts +1 -1
- package/dist/types/secrets/obfuscator.d.ts +8 -2
- package/dist/types/session/agent-session.d.ts +15 -3
- package/dist/types/session/auth-broker-config.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +1 -1
- package/dist/types/session/streaming-output.d.ts +23 -0
- package/dist/types/slash-commands/acp-builtins.d.ts +16 -0
- package/dist/types/slash-commands/builtin-registry.d.ts +1 -0
- package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
- package/dist/types/slash-commands/types.d.ts +1 -1
- package/dist/types/ssh/connection-manager.d.ts +8 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +1 -0
- package/dist/types/task/index.d.ts +2 -2
- package/dist/types/task/parallel.d.ts +2 -2
- package/dist/types/task/types.d.ts +8 -0
- package/dist/types/task/worktree.d.ts +2 -0
- package/dist/types/thinking.d.ts +4 -0
- package/dist/types/tiny/title-client.d.ts +11 -0
- package/dist/types/tiny/title-protocol.d.ts +1 -0
- package/dist/types/tools/ask.d.ts +4 -0
- package/dist/types/tools/conflict-detect.d.ts +16 -0
- package/dist/types/tools/github-cache.d.ts +7 -0
- package/dist/types/tools/index.d.ts +6 -0
- package/dist/types/tools/sqlite-reader.d.ts +3 -0
- package/dist/types/tui/output-block.d.ts +3 -3
- package/dist/types/utils/changelog.d.ts +8 -0
- package/dist/types/utils/git.d.ts +15 -2
- package/dist/types/utils/title-generator.d.ts +3 -2
- package/dist/types/web/scrapers/readthedocs.d.ts +3 -0
- package/dist/types/web/scrapers/types.d.ts +12 -0
- package/dist/types/web/search/providers/codex.d.ts +1 -1
- package/dist/types/web/search/providers/gemini.d.ts +1 -1
- package/examples/extensions/tools.ts +5 -4
- package/package.json +14 -11
- package/scripts/build-binary.ts +18 -23
- package/scripts/bundle-dist.ts +81 -0
- package/scripts/{dev-launch → omp} +1 -1
- package/scripts/{dev-launch-preload.ts → omp.ts} +1 -1
- package/src/async/job-manager.ts +57 -3
- package/src/auto-thinking/classifier.ts +1 -0
- package/src/autoresearch/dashboard.ts +1 -1
- package/src/autoresearch/prompt-setup.md +6 -6
- package/src/autoresearch/prompt.md +6 -6
- package/src/capability/fs.ts +10 -0
- package/src/cli/args.ts +4 -1
- package/src/cli/auth-gateway-cli.ts +1 -3
- package/src/cli/dry-balance-cli.ts +1 -1
- package/src/cli/gallery-cli.ts +1 -1
- package/src/cli/gallery-fixtures/fs.ts +1 -1
- package/src/cli/gallery-fixtures/types.ts +5 -1
- package/src/cli/list-models.ts +2 -1
- package/src/cli/usage-cli.ts +603 -0
- package/src/cli-commands.ts +30 -0
- package/src/cli.ts +76 -13
- package/src/commands/complete.ts +1 -1
- package/src/commands/launch.ts +5 -1
- package/src/commands/read.ts +6 -3
- package/src/commands/usage.ts +35 -0
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/model-selection.ts +4 -3
- package/src/config/api-key-resolver.ts +8 -6
- package/src/config/append-only-context-mode.ts +6 -12
- package/src/config/model-discovery.ts +554 -0
- package/src/config/model-registry.ts +320 -1041
- package/src/config/model-resolver.ts +173 -156
- package/src/config/model-roles.ts +74 -0
- package/src/config/models-config-schema.ts +57 -8
- package/src/config/models-config.ts +129 -0
- package/src/config/settings-schema.ts +61 -19
- package/src/config/settings.ts +98 -4
- package/src/dap/client.ts +124 -37
- package/src/dap/session.ts +259 -158
- package/src/debug/log-viewer.ts +1 -1
- package/src/debug/raw-sse.ts +1 -1
- package/src/edit/diff.ts +47 -3
- package/src/edit/hashline/block-resolver.ts +20 -1
- package/src/edit/hashline/diff.ts +36 -1
- package/src/edit/hashline/execute.ts +47 -4
- package/src/edit/hashline/noop-loop-guard.ts +99 -0
- package/src/edit/index.ts +16 -1
- package/src/edit/modes/patch.ts +52 -0
- package/src/edit/modes/replace.ts +56 -22
- package/src/edit/notebook.ts +22 -2
- package/src/edit/renderer.ts +36 -10
- package/src/eval/__tests__/completion-bridge.test.ts +1 -1
- package/src/eval/backend.ts +0 -2
- package/src/eval/completion-bridge.ts +3 -1
- package/src/eval/idle-timeout.ts +2 -9
- package/src/eval/js/context-manager.ts +6 -8
- package/src/eval/js/executor.ts +6 -2
- package/src/eval/js/index.ts +0 -2
- package/src/eval/js/shared/helpers.ts +5 -6
- package/src/eval/js/shared/local-module-loader.ts +1 -1
- package/src/eval/js/shared/prelude.txt +62 -1
- package/src/eval/js/shared/rewrite-imports.ts +40 -22
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/py/executor.ts +29 -7
- package/src/eval/py/index.ts +6 -3
- package/src/eval/py/kernel.ts +43 -4
- package/src/eval/py/runner.py +107 -3
- package/src/eval/py/runtime.ts +37 -0
- package/src/exec/bash-executor.ts +85 -4
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +3 -1
- package/src/extensibility/extensions/get-commands-handler.ts +2 -1
- package/src/extensibility/extensions/runner.ts +6 -1
- package/src/extensibility/extensions/types.ts +6 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
- package/src/hindsight/bank.ts +17 -2
- package/src/hindsight/mental-models.ts +59 -12
- package/src/hindsight/state.ts +6 -1
- package/src/internal-urls/artifact-protocol.ts +11 -2
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/internal-urls/issue-pr-protocol.ts +12 -5
- package/src/internal-urls/router.ts +1 -1
- package/src/internal-urls/types.ts +1 -1
- package/src/lib/xai-http.ts +1 -1
- package/src/lsp/client.ts +118 -38
- package/src/lsp/clients/biome-client.ts +101 -39
- package/src/lsp/edits.ts +143 -95
- package/src/lsp/index.ts +31 -22
- package/src/lsp/render.ts +1 -1
- package/src/lsp/types.ts +2 -0
- package/src/lsp/utils.ts +28 -10
- package/src/main.ts +183 -23
- package/src/mcp/json-rpc.ts +35 -5
- package/src/mcp/transports/stdio.ts +7 -1
- package/src/memories/index.ts +4 -1
- package/src/memory-backend/index.ts +1 -0
- package/src/memory-backend/local-backend.ts +9 -0
- package/src/memory-backend/off-backend.ts +9 -0
- package/src/memory-backend/runtime.ts +66 -0
- package/src/memory-backend/types.ts +81 -1
- package/src/mnemopi/backend.ts +176 -7
- package/src/mnemopi/state.ts +38 -2
- package/src/modes/acp/acp-agent.ts +119 -11
- package/src/modes/components/agent-dashboard.ts +10 -7
- package/src/modes/components/assistant-message.ts +32 -28
- package/src/modes/components/bash-execution.ts +1 -1
- package/src/modes/components/copy-selector.ts +1 -1
- package/src/modes/components/diff.ts +13 -2
- package/src/modes/components/dynamic-border.ts +12 -3
- package/src/modes/components/extensions/extension-dashboard.ts +8 -5
- package/src/modes/components/extensions/extension-list.ts +1 -1
- package/src/modes/components/extensions/inspector-panel.ts +1 -1
- package/src/modes/components/footer.ts +4 -2
- package/src/modes/components/history-search.ts +1 -1
- package/src/modes/components/hook-editor.ts +8 -0
- package/src/modes/components/hook-input.ts +8 -0
- package/src/modes/components/hook-selector.ts +2 -2
- package/src/modes/components/model-selector.ts +4 -2
- package/src/modes/components/plan-review-overlay.ts +1 -1
- package/src/modes/components/session-observer-overlay.ts +2 -2
- package/src/modes/components/session-selector.ts +1 -1
- package/src/modes/components/settings-selector.ts +5 -1
- package/src/modes/components/status-line/component.ts +119 -35
- package/src/modes/components/tiny-title-download-progress.ts +1 -1
- package/src/modes/components/transcript-container.ts +258 -53
- package/src/modes/components/tree-selector.ts +3 -3
- package/src/modes/components/user-message-selector.ts +1 -1
- package/src/modes/components/user-message.ts +17 -5
- package/src/modes/components/visual-truncate.ts +1 -1
- package/src/modes/components/welcome.ts +108 -26
- package/src/modes/controllers/command-controller.ts +11 -4
- package/src/modes/controllers/event-controller.ts +73 -4
- package/src/modes/controllers/input-controller.ts +2 -1
- package/src/modes/controllers/mcp-command-controller.ts +39 -4
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/controllers/streaming-reveal.ts +85 -18
- package/src/modes/index.ts +3 -21
- package/src/modes/interactive-mode.ts +42 -18
- package/src/modes/oauth-manual-input.ts +30 -3
- package/src/modes/rpc/rpc-client.ts +154 -3
- package/src/modes/rpc/rpc-mode.ts +97 -12
- package/src/modes/rpc/rpc-subagents.ts +265 -0
- package/src/modes/rpc/rpc-types.ts +81 -1
- package/src/modes/setup-wizard/index.ts +12 -2
- package/src/modes/setup-wizard/lazy.ts +16 -0
- package/src/modes/setup-wizard/scenes/glyph.ts +1 -1
- package/src/modes/setup-wizard/scenes/providers.ts +1 -1
- package/src/modes/setup-wizard/scenes/sign-in.ts +1 -1
- package/src/modes/setup-wizard/scenes/theme.ts +1 -1
- package/src/modes/setup-wizard/scenes/types.ts +1 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +1 -1
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/types.ts +4 -1
- package/src/prompts/agents/explore.md +2 -2
- package/src/prompts/agents/librarian.md +1 -2
- package/src/prompts/agents/oracle.md +1 -1
- package/src/prompts/agents/plan.md +5 -5
- package/src/prompts/agents/task.md +5 -5
- package/src/prompts/ci-green-request.md +5 -7
- package/src/prompts/goals/goal-budget-limit.md +2 -2
- package/src/prompts/goals/goal-continuation.md +4 -4
- package/src/prompts/goals/goal-mode-active.md +1 -1
- package/src/prompts/memories/read-path.md +1 -1
- package/src/prompts/memories/stage_one_system.md +2 -2
- package/src/prompts/review-custom-request.md +1 -1
- package/src/prompts/system/agent-creation-architect.md +2 -2
- package/src/prompts/system/auto-continue.md +1 -1
- package/src/prompts/system/background-tan-dispatch.md +1 -1
- package/src/prompts/system/btw-user.md +2 -2
- package/src/prompts/system/commit-message-system.md +13 -1
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/eager-todo.md +2 -2
- package/src/prompts/system/irc-incoming.md +1 -1
- package/src/prompts/system/manual-continue.md +1 -1
- package/src/prompts/system/omfg-user.md +3 -4
- package/src/prompts/system/orchestrate-notice.md +9 -9
- package/src/prompts/system/plan-mode-active.md +4 -4
- package/src/prompts/system/plan-mode-subagent.md +4 -5
- package/src/prompts/system/plan-mode-tool-decision-reminder.md +1 -1
- package/src/prompts/system/project-prompt.md +2 -2
- package/src/prompts/system/subagent-system-prompt.md +4 -4
- package/src/prompts/system/system-prompt.md +13 -24
- package/src/prompts/system/title-system.md +2 -2
- package/src/prompts/system/ttsr-tool-reminder.md +1 -1
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/ast-edit.md +1 -1
- package/src/prompts/tools/ast-grep.md +2 -2
- package/src/prompts/tools/bash.md +5 -7
- package/src/prompts/tools/browser.md +7 -7
- package/src/prompts/tools/debug.md +1 -1
- package/src/prompts/tools/eval.md +3 -3
- package/src/prompts/tools/find.md +0 -1
- package/src/prompts/tools/github.md +8 -7
- package/src/prompts/tools/goal.md +1 -1
- package/src/prompts/tools/image-gen.md +1 -1
- package/src/prompts/tools/inspect-image-system.md +1 -1
- package/src/prompts/tools/irc.md +15 -15
- package/src/prompts/tools/lsp.md +2 -2
- package/src/prompts/tools/patch.md +2 -2
- package/src/prompts/tools/read.md +3 -4
- package/src/prompts/tools/recall.md +1 -1
- package/src/prompts/tools/reflect.md +1 -1
- package/src/prompts/tools/render-mermaid.md +2 -2
- package/src/prompts/tools/replace.md +4 -10
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search-tool-bm25.md +1 -9
- package/src/prompts/tools/search.md +0 -1
- package/src/prompts/tools/ssh.md +0 -4
- package/src/prompts/tools/task.md +2 -3
- package/src/prompts/tools/todo.md +1 -1
- package/src/sdk.ts +31 -11
- package/src/secrets/index.ts +8 -1
- package/src/secrets/obfuscator.ts +39 -18
- package/src/session/agent-session.ts +223 -64
- package/src/session/auth-broker-config.ts +30 -1
- package/src/session/session-manager.ts +2 -2
- package/src/session/streaming-output.ts +188 -11
- package/src/slash-commands/acp-builtins.ts +24 -0
- package/src/slash-commands/builtin-registry.ts +40 -0
- package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
- package/src/slash-commands/types.ts +1 -1
- package/src/ssh/connection-manager.ts +27 -0
- package/src/system-prompt.ts +14 -0
- package/src/task/commands.ts +2 -1
- package/src/task/executor.ts +74 -65
- package/src/task/index.ts +146 -68
- package/src/task/parallel.ts +3 -3
- package/src/task/render.ts +20 -5
- package/src/task/types.ts +9 -0
- package/src/task/worktree.ts +64 -56
- package/src/thinking.ts +9 -1
- package/src/tiny/title-client.ts +60 -16
- package/src/tiny/title-protocol.ts +1 -1
- package/src/tiny/worker.ts +6 -4
- package/src/tools/archive-reader.ts +30 -2
- package/src/tools/ask.ts +104 -21
- package/src/tools/ast-edit.ts +25 -5
- package/src/tools/auto-generated-guard.ts +20 -3
- package/src/tools/bash-interactive.ts +27 -7
- package/src/tools/bash.ts +100 -18
- package/src/tools/browser/launch.ts +11 -2
- package/src/tools/browser/readable.ts +19 -2
- package/src/tools/browser/registry.ts +4 -1
- package/src/tools/browser/render.ts +2 -2
- package/src/tools/browser/tab-supervisor.ts +55 -16
- package/src/tools/conflict-detect.ts +50 -4
- package/src/tools/debug.ts +1 -1
- package/src/tools/eval-render.ts +5 -5
- package/src/tools/eval.ts +0 -2
- package/src/tools/fetch.ts +33 -10
- package/src/tools/gh-cache-invalidation.ts +63 -8
- package/src/tools/gh-renderer.ts +1 -1
- package/src/tools/gh.ts +172 -29
- package/src/tools/github-cache.ts +70 -6
- package/src/tools/image-gen.ts +14 -13
- package/src/tools/index.ts +13 -1
- package/src/tools/inspect-image.ts +1 -0
- package/src/tools/irc.ts +5 -1
- package/src/tools/job.ts +1 -1
- package/src/tools/read.ts +202 -61
- package/src/tools/render-utils.ts +3 -3
- package/src/tools/resolve.ts +1 -1
- package/src/tools/search.ts +92 -29
- package/src/tools/sqlite-reader.ts +17 -5
- package/src/tools/ssh.ts +8 -8
- package/src/tools/todo.ts +38 -8
- package/src/tools/write.ts +118 -18
- package/src/tui/output-block.ts +4 -4
- package/src/utils/changelog.ts +27 -1
- package/src/utils/commit-message-generator.ts +1 -0
- package/src/utils/file-mentions.ts +2 -1
- package/src/utils/git.ts +267 -13
- package/src/utils/title-generator.ts +24 -5
- package/src/web/scrapers/arxiv.ts +1 -1
- package/src/web/scrapers/go-pkg.ts +1 -1
- package/src/web/scrapers/iacr.ts +1 -1
- package/src/web/scrapers/readthedocs.ts +1 -1
- package/src/web/scrapers/twitter.ts +2 -1
- package/src/web/scrapers/types.ts +87 -8
- package/src/web/scrapers/wikipedia.ts +1 -1
- package/src/web/scrapers/youtube.ts +6 -1
- package/src/web/search/index.ts +1 -1
- package/src/web/search/providers/codex.ts +2 -1
- package/src/web/search/providers/gemini.ts +2 -3
- package/src/web/search/render.ts +8 -6
- package/dist/types/config/model-equivalence.d.ts +0 -24
- package/dist/types/config/model-id-affixes.d.ts +0 -12
- package/dist/types/config/model-provider-priority.d.ts +0 -1
- package/dist/types/exec/idle-timeout-watchdog.d.ts +0 -18
- package/src/config/model-equivalence.ts +0 -875
- package/src/config/model-id-affixes.ts +0 -81
- package/src/config/model-provider-priority.ts +0 -56
- package/src/exec/idle-timeout-watchdog.ts +0 -126
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Component, Container, type NativeScrollbackLiveRegion } from "@oh-my-pi/pi-tui";
|
|
1
|
+
import { type Component, Container, type NativeScrollbackLiveRegion, type RenderStablePrefix } from "@oh-my-pi/pi-tui";
|
|
2
2
|
|
|
3
3
|
const kSnapshot = Symbol("transcript.liveDiffSnapshot");
|
|
4
4
|
|
|
@@ -10,7 +10,7 @@ const kSnapshot = Symbol("transcript.liveDiffSnapshot");
|
|
|
10
10
|
*/
|
|
11
11
|
interface LiveDiffSnapshot {
|
|
12
12
|
width: number;
|
|
13
|
-
lines: string[];
|
|
13
|
+
lines: readonly string[];
|
|
14
14
|
generation: number;
|
|
15
15
|
appendOnly: boolean;
|
|
16
16
|
/**
|
|
@@ -27,6 +27,12 @@ interface LiveDiffSnapshot {
|
|
|
27
27
|
stablePrefixLength: number;
|
|
28
28
|
candidatePrefixLength: number;
|
|
29
29
|
candidatePrefixAge: number;
|
|
30
|
+
/**
|
|
31
|
+
* Topmost row index ever observed rewritten in place (see
|
|
32
|
+
* {@link deriveLiveCommitState}): the stable-prefix ratchet never promotes
|
|
33
|
+
* rows at/after it. `Infinity` until the first rewrite.
|
|
34
|
+
*/
|
|
35
|
+
rewriteFloor: number;
|
|
30
36
|
}
|
|
31
37
|
|
|
32
38
|
interface SnapshotCarrier {
|
|
@@ -60,7 +66,7 @@ function isPlainBlank(line: string): boolean {
|
|
|
60
66
|
// Strip leading/trailing plain-blank rows so each block contributes only its
|
|
61
67
|
// visible body; the container owns the gaps between blocks. Returns the input
|
|
62
68
|
// array unchanged when there is nothing to trim (no allocation on the hot path).
|
|
63
|
-
function stripPlainBlankEdges(lines: string[]): string[] {
|
|
69
|
+
function stripPlainBlankEdges(lines: readonly string[]): readonly string[] {
|
|
64
70
|
let start = 0;
|
|
65
71
|
let end = lines.length;
|
|
66
72
|
while (start < end && isPlainBlank(lines[start]!)) start++;
|
|
@@ -68,12 +74,35 @@ function stripPlainBlankEdges(lines: string[]): string[] {
|
|
|
68
74
|
return start === 0 && end === lines.length ? lines : lines.slice(start, end);
|
|
69
75
|
}
|
|
70
76
|
|
|
77
|
+
/**
|
|
78
|
+
* One block's recorded contribution to the assembled transcript: the raw array
|
|
79
|
+
* reference its render() returned, the stripped contribution derived from it,
|
|
80
|
+
* and where those rows landed. Reference-compared on the next render — per the
|
|
81
|
+
* Component render contract, an identical raw reference proves the block's
|
|
82
|
+
* rows are byte-identical, so the stripped contribution and the assembled rows
|
|
83
|
+
* can be reused without re-deriving anything.
|
|
84
|
+
*/
|
|
85
|
+
interface BlockSegment {
|
|
86
|
+
component: Component;
|
|
87
|
+
rawRef: readonly string[];
|
|
88
|
+
contribution: readonly string[];
|
|
89
|
+
width: number;
|
|
90
|
+
/** Frame row of this block's first emitted row (the separator when present). */
|
|
91
|
+
startRow: number;
|
|
92
|
+
/** Rows emitted: separator + contribution (0 for empty contributions). */
|
|
93
|
+
rowCount: number;
|
|
94
|
+
sep: number;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const EMPTY_SEGMENTS: BlockSegment[] = [];
|
|
98
|
+
|
|
71
99
|
interface LiveCommitState {
|
|
72
100
|
appendOnly: boolean;
|
|
73
101
|
volatileCooldown: number;
|
|
74
102
|
stablePrefixLength: number;
|
|
75
103
|
candidatePrefixLength: number;
|
|
76
104
|
candidatePrefixAge: number;
|
|
105
|
+
rewriteFloor: number;
|
|
77
106
|
safeLength: number;
|
|
78
107
|
}
|
|
79
108
|
|
|
@@ -106,6 +135,22 @@ const VOLATILE_REARM_FRAMES = 30;
|
|
|
106
135
|
*/
|
|
107
136
|
const STABLE_PREFIX_COMMIT_FRAMES = 30;
|
|
108
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Rows at a live block's tail treated as the volatile streaming edge. Real
|
|
140
|
+
* streaming is not strictly append-only at the bottom: the in-flight markdown
|
|
141
|
+
* paragraph re-wraps as words arrive (rewriting its last 1-2 visual rows), an
|
|
142
|
+
* unclosed token (`**bold`, a half-streamed link) re-renders when its closer
|
|
143
|
+
* arrives, and a wrap-shrink moves the last word onto a new row. Divergence
|
|
144
|
+
* confined to this zone is clean growth, and the zone itself is held back
|
|
145
|
+
* from the offered commit boundary — so a tolerated rewrite can never touch a
|
|
146
|
+
* row the engine may have committed. Width 4 covers the observed shapes (≤2
|
|
147
|
+
* rows) with margin for wide glyphs and multi-row token spans; the cost is
|
|
148
|
+
* only that the last 4 rows of a live block commit at finalization instead of
|
|
149
|
+
* mid-stream, which is invisible (they are on screen — the viewport is always
|
|
150
|
+
* taller than the holdback).
|
|
151
|
+
*/
|
|
152
|
+
const TAIL_VOLATILITY_ROWS = 4;
|
|
153
|
+
|
|
109
154
|
/**
|
|
110
155
|
* Visible-content form of a row: SGR/OSC bytes and trailing pad spaces are
|
|
111
156
|
* write framing, not content. A styled line's closing escape moves when the
|
|
@@ -124,6 +169,15 @@ function rowsVisiblyEqual(prev: string, cur: string): boolean {
|
|
|
124
169
|
return prev === cur || normalizeRow(prev) === normalizeRow(cur);
|
|
125
170
|
}
|
|
126
171
|
|
|
172
|
+
/**
|
|
173
|
+
* Whether `cur` is `prev` grown in place: the visible content of `prev` is a
|
|
174
|
+
* strict-or-equal prefix of `cur`'s (token streaming appending to the cursor
|
|
175
|
+
* row). Escape placement and pad drift are ignored, same as rowsVisiblyEqual.
|
|
176
|
+
*/
|
|
177
|
+
function rowVisiblyGrew(prev: string, cur: string): boolean {
|
|
178
|
+
return normalizeRow(cur).startsWith(normalizeRow(prev));
|
|
179
|
+
}
|
|
180
|
+
|
|
127
181
|
function hasValidSnapshot(
|
|
128
182
|
snapshot: LiveDiffSnapshot | undefined,
|
|
129
183
|
width: number,
|
|
@@ -132,14 +186,14 @@ function hasValidSnapshot(
|
|
|
132
186
|
return snapshot !== undefined && snapshot.generation === generation && snapshot.width === width;
|
|
133
187
|
}
|
|
134
188
|
|
|
135
|
-
function commonPrefixLength(prev: string[], cur: string[]): number {
|
|
189
|
+
function commonPrefixLength(prev: readonly string[], cur: readonly string[]): number {
|
|
136
190
|
const limit = Math.min(prev.length, cur.length);
|
|
137
191
|
let i = 0;
|
|
138
192
|
while (i < limit && rowsVisiblyEqual(prev[i]!, cur[i]!)) i++;
|
|
139
193
|
return i;
|
|
140
194
|
}
|
|
141
195
|
|
|
142
|
-
function commonSuffixLength(prev: string[], cur: string[], prefixLength: number): number {
|
|
196
|
+
function commonSuffixLength(prev: readonly string[], cur: readonly string[], prefixLength: number): number {
|
|
143
197
|
const limit = Math.min(prev.length - prefixLength, cur.length - prefixLength);
|
|
144
198
|
let i = 0;
|
|
145
199
|
while (i < limit && rowsVisiblyEqual(prev[prev.length - 1 - i]!, cur[cur.length - 1 - i]!)) i++;
|
|
@@ -148,7 +202,7 @@ function commonSuffixLength(prev: string[], cur: string[], prefixLength: number)
|
|
|
148
202
|
|
|
149
203
|
function deriveLiveCommitState(
|
|
150
204
|
previous: LiveDiffSnapshot | undefined,
|
|
151
|
-
current: string[],
|
|
205
|
+
current: readonly string[],
|
|
152
206
|
width: number,
|
|
153
207
|
generation: number,
|
|
154
208
|
): LiveCommitState {
|
|
@@ -157,12 +211,15 @@ function deriveLiveCommitState(
|
|
|
157
211
|
let stablePrefixLength = 0;
|
|
158
212
|
let candidatePrefixLength = 0;
|
|
159
213
|
let candidatePrefixAge = 0;
|
|
214
|
+
let rewriteFloor = Number.POSITIVE_INFINITY;
|
|
215
|
+
let trailingRowGrowth = false;
|
|
160
216
|
if (hasValidSnapshot(previous, width, generation)) {
|
|
161
217
|
appendOnly = previous.appendOnly;
|
|
162
218
|
volatileCooldown = previous.volatileCooldown;
|
|
163
219
|
stablePrefixLength = previous.stablePrefixLength;
|
|
164
220
|
candidatePrefixLength = previous.candidatePrefixLength;
|
|
165
221
|
candidatePrefixAge = previous.candidatePrefixAge;
|
|
222
|
+
rewriteFloor = previous.rewriteFloor;
|
|
166
223
|
|
|
167
224
|
const prefixLength = commonPrefixLength(previous.lines, current);
|
|
168
225
|
const staticRender = prefixLength === previous.lines.length && prefixLength === current.length;
|
|
@@ -170,32 +227,50 @@ function deriveLiveCommitState(
|
|
|
170
227
|
if (!staticRender) {
|
|
171
228
|
const suffixLength = commonSuffixLength(previous.lines, current, prefixLength);
|
|
172
229
|
// Append-only growth never rewrites a row that may already have scrolled
|
|
173
|
-
// into native scrollback; it only grows the block at/near its tail.
|
|
174
|
-
// shapes qualify:
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
//
|
|
179
|
-
//
|
|
180
|
-
//
|
|
181
|
-
//
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
230
|
+
// into native scrollback; it only grows the block at/near its tail. Two
|
|
231
|
+
// shapes qualify:
|
|
232
|
+
// - a pure insertion that preserves every previous row across a
|
|
233
|
+
// matching prefix + suffix (a bottom append, or an insertion above
|
|
234
|
+
// stable trailing chrome like a streaming tool's footer/border);
|
|
235
|
+
// - a rewrite whose divergence BEGINS inside the trailing
|
|
236
|
+
// TAIL_VOLATILITY_ROWS of the previous render — the streaming edge:
|
|
237
|
+
// the in-flight paragraph re-wrapping as words arrive (its last 1-2
|
|
238
|
+
// visual rows), an unclosed markdown token (`**bold`) re-rendering
|
|
239
|
+
// when its closer streams in, a wrap-shrink pushing the last word
|
|
240
|
+
// onto an appended row. That zone is held back from `safeLength`
|
|
241
|
+
// below, so a tolerated rewrite can never touch a row that was
|
|
242
|
+
// offered for commit.
|
|
243
|
+
// The anchor matters: the gap must START in the tail zone, not merely
|
|
244
|
+
// be small — a one-row ticker mid-block with stable rows beneath it
|
|
245
|
+
// would otherwise classify clean, get offered past, and rewrite
|
|
246
|
+
// committed rows on every tick. Any deeper divergent row means the
|
|
247
|
+
// block re-laid-out committed-candidate content — a rewrite, which
|
|
248
|
+
// suspends commits until the block re-earns append-only.
|
|
185
249
|
const preservedEveryRow = prefixLength + suffixLength >= previous.lines.length;
|
|
186
|
-
|
|
187
|
-
if (
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
250
|
+
const tailConfined = preservedEveryRow || prefixLength >= previous.lines.length - TAIL_VOLATILITY_ROWS;
|
|
251
|
+
if (tailConfined && current.length >= previous.lines.length) {
|
|
252
|
+
// Strict trailing-row growth: every previous row except the last
|
|
253
|
+
// is visibly unchanged and the last grew in place as a visible
|
|
254
|
+
// prefix, with no rows appended — a line accumulating tokens.
|
|
255
|
+
// The sole divergent row is the block's physical last row, which
|
|
256
|
+
// the engine's window floor never commits while it stays last
|
|
257
|
+
// (chunkTo ≤ windowTop ≤ last row index), so the volatile-tail
|
|
258
|
+
// holdback below is unnecessary: the whole body is offerable and
|
|
259
|
+
// the block's scrolled-off head reaches native scrollback.
|
|
260
|
+
trailingRowGrowth =
|
|
261
|
+
current.length === previous.lines.length &&
|
|
262
|
+
prefixLength === previous.lines.length - 1 &&
|
|
263
|
+
rowVisiblyGrew(previous.lines[prefixLength]!, current[prefixLength]!);
|
|
198
264
|
if (volatileCooldown === 0) appendOnly = true;
|
|
265
|
+
// Clean growth inserts/rewrites rows at the divergence; a floor
|
|
266
|
+
// inside the preserved suffix travels down with it, a floor at or
|
|
267
|
+
// above the divergent zone stays put (conservative: a stale floor
|
|
268
|
+
// index can only point at an earlier row, never a later one).
|
|
269
|
+
const delta = current.length - previous.lines.length;
|
|
270
|
+
if (delta > 0 && Number.isFinite(rewriteFloor)) {
|
|
271
|
+
const suffixStart = Math.max(prefixLength, previous.lines.length - suffixLength);
|
|
272
|
+
if (rewriteFloor >= suffixStart) rewriteFloor += delta;
|
|
273
|
+
}
|
|
199
274
|
} else {
|
|
200
275
|
cleanFrame = false;
|
|
201
276
|
appendOnly = false;
|
|
@@ -210,10 +285,23 @@ function deriveLiveCommitState(
|
|
|
210
285
|
// promotion means every promoted row stayed identical for the whole
|
|
211
286
|
// window (row r is inside frame i's common prefix iff r < p_i, so
|
|
212
287
|
// r < min(p) holds for every frame of the window). A row settling
|
|
213
|
-
// mid-window promotes at most two windows later.
|
|
214
|
-
//
|
|
215
|
-
// audit owns any rows that already committed (recommit, never loss).
|
|
288
|
+
// mid-window promotes at most two windows later. The engine audit owns
|
|
289
|
+
// any promoted rows that already committed (recommit, never loss).
|
|
216
290
|
if (prefixLength < stablePrefixLength) {
|
|
291
|
+
// A divergence inside the promoted run is the ratchet's proof of
|
|
292
|
+
// over-promotion: this row was visibly stable for a full window,
|
|
293
|
+
// got promoted (and likely committed), and then mutated anyway — a
|
|
294
|
+
// slow ticker (an agent row's tool/cost counter, a growing progress
|
|
295
|
+
// tree), not settling content. It will mutate again, and every
|
|
296
|
+
// promote→mutate cycle makes the engine audit recommit, spraying a
|
|
297
|
+
// stale snapshot of the block into native scrollback. Floor the
|
|
298
|
+
// ratchet at the divergence permanently: rows above it may still
|
|
299
|
+
// promote, rows at/below it never re-promote while the block lives.
|
|
300
|
+
// One-off re-layouts before any promotion (a call→result frame
|
|
301
|
+
// transition, a codespan finalizing) never hit this branch, and the
|
|
302
|
+
// append-only re-arm path commits the full block regardless of the
|
|
303
|
+
// floor.
|
|
304
|
+
rewriteFloor = Math.min(rewriteFloor, prefixLength);
|
|
217
305
|
stablePrefixLength = prefixLength;
|
|
218
306
|
candidatePrefixLength = prefixLength;
|
|
219
307
|
candidatePrefixAge = 0;
|
|
@@ -222,7 +310,15 @@ function deriveLiveCommitState(
|
|
|
222
310
|
candidatePrefixAge === 0 ? prefixLength : Math.min(candidatePrefixLength, prefixLength);
|
|
223
311
|
candidatePrefixAge++;
|
|
224
312
|
if (candidatePrefixAge >= STABLE_PREFIX_COMMIT_FRAMES) {
|
|
225
|
-
|
|
313
|
+
// Cap at the volatile-tail holdback: a long static stretch would
|
|
314
|
+
// otherwise promote the streaming edge itself (min prefix == full
|
|
315
|
+
// length), and the next chunk's tail re-wrap would then rewrite
|
|
316
|
+
// offered rows.
|
|
317
|
+
stablePrefixLength = Math.min(
|
|
318
|
+
candidatePrefixLength,
|
|
319
|
+
rewriteFloor,
|
|
320
|
+
Math.max(0, current.length - TAIL_VOLATILITY_ROWS),
|
|
321
|
+
);
|
|
226
322
|
candidatePrefixLength = prefixLength;
|
|
227
323
|
candidatePrefixAge = 0;
|
|
228
324
|
}
|
|
@@ -235,16 +331,25 @@ function deriveLiveCommitState(
|
|
|
235
331
|
stablePrefixLength,
|
|
236
332
|
candidatePrefixLength,
|
|
237
333
|
candidatePrefixAge,
|
|
238
|
-
|
|
239
|
-
//
|
|
240
|
-
|
|
334
|
+
rewriteFloor,
|
|
335
|
+
// A clean-streaming block's body is committable up to the volatile-tail
|
|
336
|
+
// holdback (the streaming edge is never offered, so its tolerated
|
|
337
|
+
// rewrites can never touch committed rows); otherwise the settled head
|
|
338
|
+
// still is — only the volatile tail stays deferred. Strict in-place
|
|
339
|
+
// growth of the trailing row skips the holdback: its only mutable row
|
|
340
|
+
// is the block's last, which cannot commit while it remains last.
|
|
341
|
+
safeLength: appendOnly
|
|
342
|
+
? trailingRowGrowth
|
|
343
|
+
? current.length
|
|
344
|
+
: Math.max(stablePrefixLength, current.length - TAIL_VOLATILITY_ROWS, 0)
|
|
345
|
+
: stablePrefixLength,
|
|
241
346
|
};
|
|
242
347
|
}
|
|
243
348
|
|
|
244
349
|
/**
|
|
245
|
-
* Transcript container that
|
|
246
|
-
* reports the live-region seam (`NativeScrollbackLiveRegion`) that gates
|
|
247
|
-
* engine's append-only scrollback commits.
|
|
350
|
+
* Transcript container that renders every block's current content each frame
|
|
351
|
+
* and reports the live-region seam (`NativeScrollbackLiveRegion`) that gates
|
|
352
|
+
* the engine's append-only scrollback commits.
|
|
248
353
|
*
|
|
249
354
|
* The engine never rewrites committed history: rows above the seam that have
|
|
250
355
|
* entered the tape keep whatever bytes they were committed with ("let the
|
|
@@ -255,8 +360,16 @@ function deriveLiveCommitState(
|
|
|
255
360
|
* their rows do not enter history while they can still change; a streaming
|
|
256
361
|
* block whose render grows append-only deepens the seam through its settled
|
|
257
362
|
* head so a long reply's scrolled-off rows still reach scrollback mid-stream.
|
|
363
|
+
*
|
|
364
|
+
* Assembly is incremental: the returned array is persistent and mutated in
|
|
365
|
+
* place. Each block's render is still called every frame, but a block whose
|
|
366
|
+
* render returned the same array reference at an unchanged offset reuses its
|
|
367
|
+
* previously assembled rows; the array is truncated and re-pushed only from
|
|
368
|
+
* the first divergent block. The leading byte-identical row count is reported
|
|
369
|
+
* through {@link RenderStablePrefix} so the engine can skip marker scanning,
|
|
370
|
+
* line preparation, and the committed-prefix audit for those rows.
|
|
258
371
|
*/
|
|
259
|
-
export class TranscriptContainer extends Container implements NativeScrollbackLiveRegion {
|
|
372
|
+
export class TranscriptContainer extends Container implements NativeScrollbackLiveRegion, RenderStablePrefix {
|
|
260
373
|
// Bumped to retire every block's diff snapshot at once (theme change /
|
|
261
374
|
// clear); a snapshot is only honored when its stored generation matches.
|
|
262
375
|
#generation = 0;
|
|
@@ -272,7 +385,16 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
272
385
|
// until it re-earns append-only via VOLATILE_REARM_FRAMES clean frames;
|
|
273
386
|
// the engine then backfills the stalled gap.
|
|
274
387
|
#nativeScrollbackCommitSafeEnd: number | undefined;
|
|
275
|
-
|
|
388
|
+
// Persistent assembled transcript rows. Rows before the stable floor are
|
|
389
|
+
// byte-identical to the previous render; rows at/after it were re-pushed.
|
|
390
|
+
#lines: string[] = [];
|
|
391
|
+
#segments: BlockSegment[] = EMPTY_SEGMENTS;
|
|
392
|
+
#renderWidth = -1;
|
|
393
|
+
// Stable-prefix floor accumulated across renders since the last
|
|
394
|
+
// getRenderStablePrefixRows() read (see RenderStablePrefix: reading
|
|
395
|
+
// consumes the report and re-bases the baseline). Out-of-band renders
|
|
396
|
+
// between engine frames lower it; they can never inflate it.
|
|
397
|
+
#stableRowsFloor = 0;
|
|
276
398
|
override invalidate(): void {
|
|
277
399
|
// Theme/global invalidation: retire every diff snapshot so stale styling
|
|
278
400
|
// is not diffed against the recolored render.
|
|
@@ -285,6 +407,12 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
285
407
|
super.clear();
|
|
286
408
|
}
|
|
287
409
|
|
|
410
|
+
getRenderStablePrefixRows(): number {
|
|
411
|
+
const value = Math.min(this.#stableRowsFloor, this.#lines.length);
|
|
412
|
+
this.#stableRowsFloor = this.#lines.length;
|
|
413
|
+
return value;
|
|
414
|
+
}
|
|
415
|
+
|
|
288
416
|
getNativeScrollbackLiveRegionStart(): number | undefined {
|
|
289
417
|
return this.#nativeScrollbackLiveRegionStart;
|
|
290
418
|
}
|
|
@@ -293,7 +421,25 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
293
421
|
return this.#nativeScrollbackCommitSafeEnd;
|
|
294
422
|
}
|
|
295
423
|
|
|
296
|
-
|
|
424
|
+
/**
|
|
425
|
+
* Whether `component` sits below a still-mutating block — i.e. inside the
|
|
426
|
+
* live region, where its rows cannot have been committed to native
|
|
427
|
+
* scrollback yet (commits are prefix-only and stop at the first
|
|
428
|
+
* still-live block). Callers that retract ephemeral blocks (IRC cards)
|
|
429
|
+
* must check this: removing a block whose rows may already be in history
|
|
430
|
+
* is an interior deletion of the committed prefix, which the engine can
|
|
431
|
+
* only repair by recommitting everything below it — duplication.
|
|
432
|
+
*/
|
|
433
|
+
isWithinLiveRegion(component: Component): boolean {
|
|
434
|
+
const index = this.children.indexOf(component);
|
|
435
|
+
if (index < 0) return false;
|
|
436
|
+
for (let i = 0; i < index; i++) {
|
|
437
|
+
if (!isBlockFinalized(this.children[i]!)) return true;
|
|
438
|
+
}
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
override render(width: number): readonly string[] {
|
|
297
443
|
width = Math.max(1, width);
|
|
298
444
|
this.#nativeScrollbackLiveRegionStart = undefined;
|
|
299
445
|
this.#nativeScrollbackCommitSafeEnd = undefined;
|
|
@@ -314,7 +460,27 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
314
460
|
}
|
|
315
461
|
}
|
|
316
462
|
|
|
317
|
-
const lines
|
|
463
|
+
const lines = this.#lines;
|
|
464
|
+
const previousSegments = this.#segments;
|
|
465
|
+
const segments: BlockSegment[] = new Array(count);
|
|
466
|
+
// Poisoned until the walk completes: a block render throwing mid-walk
|
|
467
|
+
// leaves the persistent array half-rebuilt, and the next render must
|
|
468
|
+
// not trust stale segments against it. Restored at the end.
|
|
469
|
+
this.#segments = EMPTY_SEGMENTS;
|
|
470
|
+
const stableFloorBefore = this.#stableRowsFloor;
|
|
471
|
+
this.#stableRowsFloor = 0;
|
|
472
|
+
// Stability requires the same width and, per segment, the same block at
|
|
473
|
+
// the same offset returning the same array reference. The first
|
|
474
|
+
// divergence truncates the persistent array there; everything after
|
|
475
|
+
// re-pushes.
|
|
476
|
+
let chainStable = this.#renderWidth === width;
|
|
477
|
+
this.#renderWidth = width;
|
|
478
|
+
// Entry-unstable (width change): the divergence truncation inside the
|
|
479
|
+
// loop only fires on a stable→unstable transition, so reset the
|
|
480
|
+
// persistent array here to keep the `!chainStable ⇒ lines.length === row`
|
|
481
|
+
// invariant — otherwise re-pushed rows land after the stale frame.
|
|
482
|
+
if (!chainStable) lines.length = 0;
|
|
483
|
+
|
|
318
484
|
// Tracks whether we are still inside the leading run of commit-safe live
|
|
319
485
|
// blocks. The first still-live volatile block closes it, but rendering
|
|
320
486
|
// continues so lower blocks remain visible.
|
|
@@ -323,6 +489,9 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
323
489
|
// liveStartIndex; empty leading blocks (or a separator) must not claim it
|
|
324
490
|
// early.
|
|
325
491
|
let liveRecorded = false;
|
|
492
|
+
// Frame row cursor: rows emitted (reused or pushed) so far.
|
|
493
|
+
let row = 0;
|
|
494
|
+
let stableRows = 0;
|
|
326
495
|
for (let i = 0; i < count; i++) {
|
|
327
496
|
const child = this.children[i]! as Component & SnapshotCarrier;
|
|
328
497
|
|
|
@@ -331,10 +500,20 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
331
500
|
// Always the latest content — committed history keeps whatever bytes
|
|
332
501
|
// it was written with, but the window must reflect the present state
|
|
333
502
|
// (late tool results, post-finalize re-layouts, expand toggles).
|
|
503
|
+
// A block whose render returned the same array reference reuses the
|
|
504
|
+
// previously stripped contribution (same ref ⇒ identical rows).
|
|
334
505
|
const previousSnapshot = child[kSnapshot];
|
|
335
|
-
const
|
|
506
|
+
const raw = child.render(width);
|
|
507
|
+
const previous = previousSegments[i];
|
|
508
|
+
const reusable =
|
|
509
|
+
previous !== undefined &&
|
|
510
|
+
previous.component === child &&
|
|
511
|
+
previous.rawRef === raw &&
|
|
512
|
+
previous.width === width;
|
|
513
|
+
const contribution = reusable ? previous.contribution : stripPlainBlankEdges(raw);
|
|
514
|
+
const finalized = isBlockFinalized(child);
|
|
336
515
|
let liveCommitState: LiveCommitState | undefined;
|
|
337
|
-
if (i >= liveStartIndex && !
|
|
516
|
+
if (i >= liveStartIndex && !finalized) {
|
|
338
517
|
liveCommitState = deriveLiveCommitState(previousSnapshot, contribution, width, this.#generation);
|
|
339
518
|
}
|
|
340
519
|
// Cache the latest contribution as the next frame's diff input.
|
|
@@ -347,6 +526,7 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
347
526
|
stablePrefixLength: liveCommitState?.stablePrefixLength ?? 0,
|
|
348
527
|
candidatePrefixLength: liveCommitState?.candidatePrefixLength ?? 0,
|
|
349
528
|
candidatePrefixAge: liveCommitState?.candidatePrefixAge ?? 0,
|
|
529
|
+
rewriteFloor: liveCommitState?.rewriteFloor ?? Number.POSITIVE_INFINITY,
|
|
350
530
|
};
|
|
351
531
|
|
|
352
532
|
// Empty (or stripped-to-nothing) children contribute nothing and never
|
|
@@ -354,29 +534,46 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
354
534
|
// still closes the commit-safe run: if it later gains rows, it pushes
|
|
355
535
|
// everything below it.
|
|
356
536
|
if (contribution.length === 0) {
|
|
357
|
-
if (i >= liveStartIndex && commitSafeOpen && !
|
|
537
|
+
if (i >= liveStartIndex && commitSafeOpen && !finalized) commitSafeOpen = false;
|
|
538
|
+
if (chainStable && !(reusable && previous.rowCount === 0 && previous.startRow === row)) {
|
|
539
|
+
chainStable = false;
|
|
540
|
+
lines.length = row;
|
|
541
|
+
}
|
|
542
|
+
if (chainStable) stableRows = row;
|
|
543
|
+
segments[i] = { component: child, rawRef: raw, contribution, width, startRow: row, rowCount: 0, sep: 0 };
|
|
358
544
|
continue;
|
|
359
545
|
}
|
|
360
546
|
|
|
361
547
|
// Every block is separated from preceding visible content by exactly one
|
|
362
548
|
// blank row — skipped when it opens the transcript or the prior row is
|
|
363
549
|
// already a plain blank (a fragment's own trailing pad), never doubling.
|
|
364
|
-
|
|
550
|
+
// `lines[row - 1]` is valid in both modes: reused rows are still present
|
|
551
|
+
// in the persistent array, re-pushed rows were just written.
|
|
552
|
+
const sep = row > 0 && !isPlainBlank(lines[row - 1]!) ? 1 : 0;
|
|
365
553
|
|
|
366
554
|
// The separator before the first live block stays in the committed
|
|
367
555
|
// prefix (it is deterministic once the prior block's body is settled),
|
|
368
556
|
// so the live region begins at the block's first content row.
|
|
369
557
|
if (!liveRecorded && i >= liveStartIndex) {
|
|
370
|
-
this.#nativeScrollbackLiveRegionStart =
|
|
558
|
+
this.#nativeScrollbackLiveRegionStart = row + sep;
|
|
371
559
|
liveRecorded = true;
|
|
372
560
|
}
|
|
373
561
|
|
|
374
|
-
|
|
375
|
-
const
|
|
376
|
-
|
|
562
|
+
const rowCount = sep + contribution.length;
|
|
563
|
+
const stable = chainStable && reusable && previous.startRow === row && previous.sep === sep;
|
|
564
|
+
if (stable) {
|
|
565
|
+
stableRows = row + rowCount;
|
|
566
|
+
} else {
|
|
567
|
+
if (chainStable) {
|
|
568
|
+
chainStable = false;
|
|
569
|
+
lines.length = row;
|
|
570
|
+
}
|
|
571
|
+
if (sep) lines.push("");
|
|
572
|
+
for (let j = 0; j < contribution.length; j++) lines.push(contribution[j]!);
|
|
573
|
+
}
|
|
377
574
|
|
|
575
|
+
const blockStart = row + sep;
|
|
378
576
|
if (i >= liveStartIndex && commitSafeOpen) {
|
|
379
|
-
const finalized = isBlockFinalized(child);
|
|
380
577
|
const safeLength = finalized ? contribution.length : (liveCommitState?.safeLength ?? 0);
|
|
381
578
|
if (safeLength > 0) {
|
|
382
579
|
this.#nativeScrollbackCommitSafeEnd = blockStart + safeLength;
|
|
@@ -386,7 +583,15 @@ export class TranscriptContainer extends Container implements NativeScrollbackLi
|
|
|
386
583
|
// rows around as it grows, so the run closes there.
|
|
387
584
|
if (!(finalized && safeLength >= contribution.length)) commitSafeOpen = false;
|
|
388
585
|
}
|
|
586
|
+
|
|
587
|
+
segments[i] = { component: child, rawRef: raw, contribution, width, startRow: row, rowCount, sep };
|
|
588
|
+
row += rowCount;
|
|
389
589
|
}
|
|
590
|
+
// Trailing shrink: blocks removed from the tail leave stale rows behind
|
|
591
|
+
// when every surviving segment was reused.
|
|
592
|
+
if (lines.length !== row) lines.length = row;
|
|
593
|
+
this.#segments = segments;
|
|
594
|
+
this.#stableRowsFloor = Math.min(stableFloorBefore, stableRows, row);
|
|
390
595
|
return lines;
|
|
391
596
|
}
|
|
392
597
|
}
|
|
@@ -438,7 +438,7 @@ class TreeList implements Component {
|
|
|
438
438
|
}
|
|
439
439
|
}
|
|
440
440
|
|
|
441
|
-
render(width: number): string[] {
|
|
441
|
+
render(width: number): readonly string[] {
|
|
442
442
|
const lines: string[] = [];
|
|
443
443
|
|
|
444
444
|
if (this.#filteredNodes.length === 0) {
|
|
@@ -835,7 +835,7 @@ class SearchLine implements Component {
|
|
|
835
835
|
|
|
836
836
|
invalidate(): void {}
|
|
837
837
|
|
|
838
|
-
render(width: number): string[] {
|
|
838
|
+
render(width: number): readonly string[] {
|
|
839
839
|
const query = this.treeList.getSearchQuery();
|
|
840
840
|
if (query) {
|
|
841
841
|
return [truncateToWidth(` ${theme.fg("muted", "Search:")} ${theme.fg("accent", query)}`, width)];
|
|
@@ -864,7 +864,7 @@ class LabelInput implements Component {
|
|
|
864
864
|
|
|
865
865
|
invalidate(): void {}
|
|
866
866
|
|
|
867
|
-
render(width: number): string[] {
|
|
867
|
+
render(width: number): readonly string[] {
|
|
868
868
|
const lines: string[] = [];
|
|
869
869
|
const indent = " ";
|
|
870
870
|
const availableWidth = width - indent.length;
|
|
@@ -12,6 +12,13 @@ const OSC133_ZONE_FINAL = "\x1b]133;C\x07";
|
|
|
12
12
|
* Component that renders a user message
|
|
13
13
|
*/
|
|
14
14
|
export class UserMessageComponent extends Container {
|
|
15
|
+
// Memoized OSC 133 zone wrapping keyed on the underlying container render
|
|
16
|
+
// (same source ref ⇒ identical rows ⇒ reuse the wrapped copy). Keeps this
|
|
17
|
+
// component reference-stable for the transcript's incremental assembly and
|
|
18
|
+
// never mutates the container's cached array.
|
|
19
|
+
#zoneSource: readonly string[] | undefined;
|
|
20
|
+
#zoneLines: string[] | undefined;
|
|
21
|
+
|
|
15
22
|
constructor(text: string, synthetic = false, imageLinks?: readonly (string | undefined)[]) {
|
|
16
23
|
super();
|
|
17
24
|
const bgColor = (value: string) => theme.bg("userMessageBg", value);
|
|
@@ -41,14 +48,19 @@ export class UserMessageComponent extends Container {
|
|
|
41
48
|
);
|
|
42
49
|
}
|
|
43
50
|
|
|
44
|
-
override render(width: number): string[] {
|
|
51
|
+
override render(width: number): readonly string[] {
|
|
45
52
|
const lines = super.render(width);
|
|
46
53
|
if (lines.length === 0) {
|
|
47
54
|
return lines;
|
|
48
55
|
}
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
56
|
+
if (this.#zoneSource === lines && this.#zoneLines !== undefined) {
|
|
57
|
+
return this.#zoneLines;
|
|
58
|
+
}
|
|
59
|
+
const wrapped = lines.slice();
|
|
60
|
+
wrapped[0] = OSC133_ZONE_START + wrapped[0];
|
|
61
|
+
wrapped[wrapped.length - 1] = wrapped[wrapped.length - 1] + OSC133_ZONE_END + OSC133_ZONE_FINAL;
|
|
62
|
+
this.#zoneSource = lines;
|
|
63
|
+
this.#zoneLines = wrapped;
|
|
64
|
+
return wrapped;
|
|
53
65
|
}
|
|
54
66
|
}
|
|
@@ -6,7 +6,7 @@ import { Text } from "@oh-my-pi/pi-tui";
|
|
|
6
6
|
|
|
7
7
|
export interface VisualTruncateResult {
|
|
8
8
|
/** The visual lines to display */
|
|
9
|
-
visualLines: string[];
|
|
9
|
+
visualLines: readonly string[];
|
|
10
10
|
/** Number of visual lines that were skipped (hidden) */
|
|
11
11
|
skippedCount: number;
|
|
12
12
|
}
|