@oh-my-pi/pi-coding-agent 15.10.9 → 15.10.11
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 +117 -0
- package/dist/cli.js +23087 -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 +1 -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/commands/launch.d.ts +1 -1
- package/dist/types/commands/read.d.ts +1 -1
- package/dist/types/commands/usage.d.ts +25 -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 +20 -219
- package/dist/types/config/model-resolver.d.ts +16 -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 +12 -16
- package/dist/types/config/settings.d.ts +1 -1
- package/dist/types/debug/log-viewer.d.ts +1 -1
- package/dist/types/debug/raw-sse.d.ts +1 -1
- package/dist/types/debug/terminal-info.d.ts +0 -1
- 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/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +3 -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/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 +31 -26
- 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/interactive-mode.d.ts +1 -1
- 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 +2 -1
- package/dist/types/session/agent-session.d.ts +1 -1
- package/dist/types/session/auth-broker-config.d.ts +4 -0
- package/dist/types/session/session-manager.d.ts +1 -1
- package/dist/types/slash-commands/helpers/stats-dashboard.d.ts +13 -0
- package/dist/types/ssh/connection-manager.d.ts +8 -0
- package/dist/types/task/discovery.d.ts +1 -2
- package/dist/types/task/parallel.d.ts +2 -2
- package/dist/types/task/worktree.d.ts +2 -0
- package/dist/types/tiny/title-client.d.ts +1 -1
- 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/sqlite-reader.d.ts +3 -0
- package/dist/types/tools/todo.d.ts +2 -0
- package/dist/types/tui/output-block.d.ts +3 -3
- package/dist/types/utils/changelog.d.ts +8 -0
- 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/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 +1 -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 +7 -12
- package/src/cli/usage-cli.ts +603 -0
- package/src/cli-commands.ts +1 -0
- package/src/cli.ts +69 -5
- package/src/commands/complete.ts +1 -1
- package/src/commands/launch.ts +1 -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 +1 -1
- package/src/config/append-only-context-mode.ts +6 -12
- package/src/config/model-discovery.ts +554 -0
- package/src/config/model-registry.ts +308 -1025
- package/src/config/model-resolver.ts +113 -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 +18 -14
- package/src/config/settings.ts +37 -1
- 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/debug/terminal-info.ts +0 -3
- package/src/edit/diff.ts +95 -18
- package/src/edit/hashline/block-resolver.ts +20 -1
- package/src/edit/hashline/diff.ts +36 -1
- package/src/edit/hashline/execute.ts +8 -2
- 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 +2 -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 +49 -23
- package/src/eval/js/shared/runtime.ts +1 -1
- package/src/eval/py/index.ts +0 -2
- package/src/eval/py/kernel.ts +19 -0
- package/src/eval/py/runner.py +107 -3
- package/src/exec/bash-executor.ts +3 -1
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +3 -1
- package/src/extensibility/extensions/types.ts +3 -2
- package/src/extensibility/plugins/legacy-pi-compat.ts +20 -3
- 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 +10 -10
- 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 +165 -17
- package/src/mcp/json-rpc.ts +35 -5
- package/src/mcp/transports/stdio.ts +7 -1
- package/src/memories/index.ts +2 -1
- package/src/mnemopi/backend.ts +25 -3
- package/src/mnemopi/state.ts +38 -2
- package/src/modes/components/agent-dashboard.ts +10 -7
- package/src/modes/components/assistant-message.ts +19 -13
- 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 +1 -1
- 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 +66 -54
- 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 +1 -1
- package/src/modes/components/tiny-title-download-progress.ts +1 -1
- package/src/modes/components/transcript-container.ts +373 -141
- 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 +10 -3
- package/src/modes/controllers/event-controller.ts +73 -49
- package/src/modes/controllers/input-controller.ts +5 -5
- package/src/modes/controllers/mcp-command-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +1 -5
- package/src/modes/controllers/streaming-reveal.ts +85 -18
- package/src/modes/interactive-mode.ts +5 -19
- 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 +2 -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 +15 -26
- 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 +8 -10
- 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 +6 -2
- package/src/sdk.ts +23 -10
- package/src/session/agent-session.ts +44 -10
- package/src/session/auth-broker-config.ts +30 -1
- package/src/session/session-manager.ts +2 -2
- package/src/session/streaming-output.ts +23 -2
- package/src/slash-commands/builtin-registry.ts +20 -0
- package/src/slash-commands/helpers/stats-dashboard.ts +85 -0
- package/src/ssh/connection-manager.ts +27 -0
- package/src/task/commands.ts +2 -1
- package/src/task/discovery.ts +17 -24
- package/src/task/executor.ts +61 -53
- package/src/task/index.ts +137 -60
- package/src/task/parallel.ts +3 -3
- package/src/task/render.ts +2 -2
- package/src/task/worktree.ts +64 -56
- package/src/thinking.ts +2 -1
- package/src/tiny/title-client.ts +32 -14
- 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 +54 -13
- 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 +3 -9
- 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 +51 -12
- 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/file-mentions.ts +2 -1
- 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/anthropic.ts +8 -2
- 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
|
@@ -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
|
}
|
|
@@ -17,6 +17,24 @@ const TIPS: readonly string[] = tipsText
|
|
|
17
17
|
.map(line => line.trim())
|
|
18
18
|
.filter(line => line.length > 0);
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Tip chosen once per process so the pre-TUI startup splash and the in-TUI
|
|
22
|
+
* welcome screen show the same tip instead of shuffling on the swap.
|
|
23
|
+
*/
|
|
24
|
+
const PROCESS_TIP: string | undefined = TIPS.length > 0 ? TIPS[Math.floor(Math.random() * TIPS.length)] : undefined;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Fixed number of session rows in the welcome box so its height doesn't shift
|
|
28
|
+
* between the pre-TUI splash (loading placeholder) and the loaded state.
|
|
29
|
+
*/
|
|
30
|
+
export const WELCOME_SESSION_SLOTS = 4;
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Fixed number of LSP-server rows, for the same reason. Overflow is sliced so
|
|
34
|
+
* the box height is constant regardless of how many servers a project has.
|
|
35
|
+
*/
|
|
36
|
+
export const WELCOME_LSP_SLOTS = 4;
|
|
37
|
+
|
|
20
38
|
export function renderWelcomeTip(tip: string, boxWidth: number): string[] {
|
|
21
39
|
const label = "Tip: ";
|
|
22
40
|
const labelWidth = visibleWidth(label);
|
|
@@ -48,7 +66,7 @@ export interface RecentSession {
|
|
|
48
66
|
|
|
49
67
|
export interface LspServerInfo {
|
|
50
68
|
name: string;
|
|
51
|
-
status: "ready" | "error" | "connecting";
|
|
69
|
+
status: "ready" | "error" | "connecting" | "available";
|
|
52
70
|
fileTypes: string[];
|
|
53
71
|
}
|
|
54
72
|
|
|
@@ -58,18 +76,38 @@ export interface LspServerInfo {
|
|
|
58
76
|
export class WelcomeComponent implements Component {
|
|
59
77
|
#animStart: number | null = null;
|
|
60
78
|
#animTimer: ReturnType<typeof setInterval> | null = null;
|
|
61
|
-
/**
|
|
62
|
-
|
|
79
|
+
/** When set, a non-animating render shows the intro's first frame instead of the resting frame. */
|
|
80
|
+
#holdIntroFirstFrame = false;
|
|
81
|
+
/** Per-process tip so re-renders (intro, LSP updates, splash swap) don't shuffle it. */
|
|
82
|
+
readonly #tip: string | undefined = PROCESS_TIP;
|
|
83
|
+
// Render cache: the welcome box is the first transcript-area component, so
|
|
84
|
+
// returning a stable array reference keeps the whole frame prefix stable.
|
|
85
|
+
// Bypassed while the intro animation runs (every frame differs).
|
|
86
|
+
#cachedWidth = -1;
|
|
87
|
+
#cachedLines: string[] | undefined;
|
|
63
88
|
|
|
64
89
|
constructor(
|
|
65
90
|
private readonly version: string,
|
|
66
91
|
private modelName: string,
|
|
67
92
|
private providerName: string,
|
|
68
|
-
private recentSessions: RecentSession[] = [],
|
|
93
|
+
private recentSessions: RecentSession[] | null = [],
|
|
69
94
|
private lspServers: LspServerInfo[] = [],
|
|
70
95
|
) {}
|
|
71
96
|
|
|
72
|
-
invalidate(): void {
|
|
97
|
+
invalidate(): void {
|
|
98
|
+
this.#cachedWidth = -1;
|
|
99
|
+
this.#cachedLines = undefined;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Freeze the logo on the intro animation's first frame. The pre-TUI startup
|
|
104
|
+
* splash uses this so the in-TUI intro — which starts at that exact frame —
|
|
105
|
+
* picks up seamlessly from the splash's static box.
|
|
106
|
+
*/
|
|
107
|
+
holdIntroFirstFrame(): void {
|
|
108
|
+
this.#holdIntroFirstFrame = true;
|
|
109
|
+
this.invalidate();
|
|
110
|
+
}
|
|
73
111
|
|
|
74
112
|
/**
|
|
75
113
|
* Play a one-shot intro that sweeps the gradient through every phase
|
|
@@ -78,6 +116,7 @@ export class WelcomeComponent implements Component {
|
|
|
78
116
|
*/
|
|
79
117
|
playIntro(requestRender: () => void): void {
|
|
80
118
|
this.#stopAnimation();
|
|
119
|
+
this.#holdIntroFirstFrame = false;
|
|
81
120
|
this.#animStart = performance.now();
|
|
82
121
|
requestRender();
|
|
83
122
|
this.#animTimer = setInterval(() => {
|
|
@@ -95,22 +134,43 @@ export class WelcomeComponent implements Component {
|
|
|
95
134
|
this.#animTimer = null;
|
|
96
135
|
}
|
|
97
136
|
this.#animStart = null;
|
|
137
|
+
// The settled (resting) frame differs from the last intro frame.
|
|
138
|
+
this.invalidate();
|
|
98
139
|
}
|
|
99
140
|
|
|
100
141
|
setModel(modelName: string, providerName: string): void {
|
|
101
142
|
this.modelName = modelName;
|
|
102
143
|
this.providerName = providerName;
|
|
144
|
+
this.invalidate();
|
|
103
145
|
}
|
|
104
146
|
|
|
105
147
|
setRecentSessions(sessions: RecentSession[]): void {
|
|
106
148
|
this.recentSessions = sessions;
|
|
149
|
+
this.invalidate();
|
|
107
150
|
}
|
|
108
151
|
|
|
109
152
|
setLspServers(servers: LspServerInfo[]): void {
|
|
110
153
|
this.lspServers = servers;
|
|
154
|
+
this.invalidate();
|
|
111
155
|
}
|
|
112
156
|
|
|
113
|
-
render(termWidth: number): string[] {
|
|
157
|
+
render(termWidth: number): readonly string[] {
|
|
158
|
+
const animating = this.#animStart != null;
|
|
159
|
+
if (!animating && this.#cachedLines && this.#cachedWidth === termWidth) {
|
|
160
|
+
return this.#cachedLines;
|
|
161
|
+
}
|
|
162
|
+
const lines = this.#renderLines(termWidth);
|
|
163
|
+
if (animating) {
|
|
164
|
+
this.#cachedLines = undefined;
|
|
165
|
+
this.#cachedWidth = -1;
|
|
166
|
+
} else {
|
|
167
|
+
this.#cachedLines = lines;
|
|
168
|
+
this.#cachedWidth = termWidth;
|
|
169
|
+
}
|
|
170
|
+
return lines;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
#renderLines(termWidth: number): string[] {
|
|
114
174
|
// Box dimensions - responsive with max width and small-terminal support
|
|
115
175
|
const maxWidth = 100;
|
|
116
176
|
const boxWidth = Math.min(maxWidth, Math.max(0, termWidth - 2));
|
|
@@ -157,7 +217,9 @@ export class WelcomeComponent implements Component {
|
|
|
157
217
|
|
|
158
218
|
// Recent sessions content
|
|
159
219
|
const sessionLines: string[] = [];
|
|
160
|
-
if (this.recentSessions
|
|
220
|
+
if (this.recentSessions === null) {
|
|
221
|
+
sessionLines.push(` ${theme.fg("dim", "Loading…")}`);
|
|
222
|
+
} else if (this.recentSessions.length === 0) {
|
|
161
223
|
sessionLines.push(` ${theme.fg("dim", "No recent sessions")}`);
|
|
162
224
|
} else {
|
|
163
225
|
// Reserve width for the bullet prefix (" • ") and the trailing " (timeAgo)"
|
|
@@ -165,7 +227,7 @@ export class WelcomeComponent implements Component {
|
|
|
165
227
|
// absorbs whatever space is left.
|
|
166
228
|
const bulletPrefix = ` ${theme.md.bullet} `;
|
|
167
229
|
const prefixWidth = visibleWidth(bulletPrefix);
|
|
168
|
-
for (const session of this.recentSessions.slice(0,
|
|
230
|
+
for (const session of this.recentSessions.slice(0, WELCOME_SESSION_SLOTS)) {
|
|
169
231
|
const timeSuffixRaw = ` (${session.timeAgo})`;
|
|
170
232
|
const timeWidth = visibleWidth(timeSuffixRaw);
|
|
171
233
|
const nameBudget = Math.max(1, rightCol - prefixWidth - timeWidth);
|
|
@@ -176,23 +238,33 @@ export class WelcomeComponent implements Component {
|
|
|
176
238
|
);
|
|
177
239
|
}
|
|
178
240
|
}
|
|
241
|
+
// Pad to the fixed slot count so the box doesn't grow when sessions load in.
|
|
242
|
+
while (sessionLines.length < WELCOME_SESSION_SLOTS) {
|
|
243
|
+
sessionLines.push("");
|
|
244
|
+
}
|
|
179
245
|
|
|
180
246
|
// LSP servers content
|
|
181
247
|
const lspLines: string[] = [];
|
|
182
248
|
if (this.lspServers.length === 0) {
|
|
183
249
|
lspLines.push(` ${theme.fg("dim", "No LSP servers")}`);
|
|
184
250
|
} else {
|
|
185
|
-
for (const server of this.lspServers) {
|
|
251
|
+
for (const server of this.lspServers.slice(0, WELCOME_LSP_SLOTS)) {
|
|
186
252
|
const icon =
|
|
187
253
|
server.status === "ready"
|
|
188
254
|
? theme.styledSymbol("status.enabled", "success")
|
|
189
|
-
: server.status === "
|
|
190
|
-
? theme.styledSymbol("status.
|
|
191
|
-
:
|
|
255
|
+
: server.status === "available"
|
|
256
|
+
? theme.styledSymbol("status.enabled", "dim")
|
|
257
|
+
: server.status === "connecting"
|
|
258
|
+
? theme.styledSymbol("status.pending", "muted")
|
|
259
|
+
: theme.styledSymbol("status.error", "error");
|
|
192
260
|
const exts = server.fileTypes.slice(0, 3).join(" ");
|
|
193
261
|
lspLines.push(` ${icon} ${theme.fg("muted", server.name)} ${theme.fg("dim", exts)}`);
|
|
194
262
|
}
|
|
195
263
|
}
|
|
264
|
+
// Pad to the fixed slot count so the box height doesn't depend on server count.
|
|
265
|
+
while (lspLines.length < WELCOME_LSP_SLOTS) {
|
|
266
|
+
lspLines.push("");
|
|
267
|
+
}
|
|
196
268
|
|
|
197
269
|
// Right column
|
|
198
270
|
const rightLines = [
|
|
@@ -305,23 +377,12 @@ export class WelcomeComponent implements Component {
|
|
|
305
377
|
return str + padding(width - visLen);
|
|
306
378
|
}
|
|
307
379
|
|
|
308
|
-
/** Pick the logo frame for the current intro phase, or the resting frame. */
|
|
380
|
+
/** Pick the logo frame for the current intro phase, or the resting/held frame. */
|
|
309
381
|
#currentLogoFrame(): readonly string[] {
|
|
310
|
-
if (this.#animStart == null) return REST_FRAME;
|
|
382
|
+
if (this.#animStart == null) return this.#holdIntroFirstFrame ? INTRO_FIRST_FRAME : REST_FRAME;
|
|
311
383
|
const elapsed = performance.now() - this.#animStart;
|
|
312
384
|
if (elapsed >= INTRO_MS) return REST_FRAME;
|
|
313
|
-
|
|
314
|
-
const progress = elapsed / INTRO_MS;
|
|
315
|
-
const eased = 1 - (1 - progress) ** 3;
|
|
316
|
-
// Sweep backward through INTRO_SWEEPS full rotations so the gradient
|
|
317
|
-
// visibly spins multiple times. `eased == 1` → phase = 0 = resting frame.
|
|
318
|
-
const phase = ((((1 - eased) * INTRO_SWEEPS) % 1) + 1) % 1;
|
|
319
|
-
// Shine traverses the diagonal at a steady pace, decoupled from the
|
|
320
|
-
// gradient phase so the two layers parallax. Strength fades out with
|
|
321
|
-
// the same ease-out curve so the highlight is gone by the resting frame.
|
|
322
|
-
const shinePos = (((progress * INTRO_SHINE_TRAVERSALS) % 1) + 1) % 1;
|
|
323
|
-
const shineStrength = (1 - eased) ** 1.5;
|
|
324
|
-
return gradientLogo(PI_LOGO, phase, { strength: shineStrength, pos: shinePos });
|
|
385
|
+
return introLogoFrame(elapsed / INTRO_MS);
|
|
325
386
|
}
|
|
326
387
|
}
|
|
327
388
|
|
|
@@ -431,5 +492,26 @@ const INTRO_SWEEPS = 2.5;
|
|
|
431
492
|
/** Number of times the shine highlight crosses the diagonal across the intro. */
|
|
432
493
|
const INTRO_SHINE_TRAVERSALS = 3;
|
|
433
494
|
|
|
495
|
+
/**
|
|
496
|
+
* Logo frame for a normalized intro progress in [0, 1).
|
|
497
|
+
*
|
|
498
|
+
* Ease-out cubic so the spin decelerates into the resting state. The gradient
|
|
499
|
+
* sweeps backward through INTRO_SWEEPS full rotations (`eased == 1` → phase =
|
|
500
|
+
* 0 = resting frame) while the shine traverses the diagonal at a steady pace,
|
|
501
|
+
* decoupled from the gradient phase so the two layers parallax; its strength
|
|
502
|
+
* fades with the same ease-out curve so the highlight is gone by the resting
|
|
503
|
+
* frame.
|
|
504
|
+
*/
|
|
505
|
+
function introLogoFrame(progress: number): string[] {
|
|
506
|
+
const eased = 1 - (1 - progress) ** 3;
|
|
507
|
+
const phase = ((((1 - eased) * INTRO_SWEEPS) % 1) + 1) % 1;
|
|
508
|
+
const shinePos = (((progress * INTRO_SHINE_TRAVERSALS) % 1) + 1) % 1;
|
|
509
|
+
const shineStrength = (1 - eased) ** 1.5;
|
|
510
|
+
return gradientLogo(PI_LOGO, phase, { strength: shineStrength, pos: shinePos });
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
/** First intro frame, cached for splash-held renders (resize re-renders reuse it). */
|
|
514
|
+
const INTRO_FIRST_FRAME = introLogoFrame(0);
|
|
515
|
+
|
|
434
516
|
/** Resting gradient frame, cached for re-renders outside of the intro. */
|
|
435
517
|
const REST_FRAME = gradientLogo(PI_LOGO, 0);
|
|
@@ -21,6 +21,7 @@ import {
|
|
|
21
21
|
loadHindsightConfig,
|
|
22
22
|
reloadMentalModelsForSession,
|
|
23
23
|
resolveSeedsForScope,
|
|
24
|
+
seedAlreadyExists,
|
|
24
25
|
summarizeMentalModel,
|
|
25
26
|
} from "../../hindsight";
|
|
26
27
|
import { resolveMemoryBackend } from "../../memory-backend";
|
|
@@ -314,7 +315,13 @@ export class CommandController {
|
|
|
314
315
|
info += `\n${theme.bold("LSP Servers")}\n`;
|
|
315
316
|
for (const server of this.ctx.lspServers) {
|
|
316
317
|
const statusColor =
|
|
317
|
-
server.status === "ready"
|
|
318
|
+
server.status === "ready"
|
|
319
|
+
? "success"
|
|
320
|
+
: server.status === "available"
|
|
321
|
+
? "dim"
|
|
322
|
+
: server.status === "connecting"
|
|
323
|
+
? "warning"
|
|
324
|
+
: "error";
|
|
318
325
|
const statusText =
|
|
319
326
|
server.status === "error" && server.error ? `${server.status}: ${server.error}` : server.status;
|
|
320
327
|
info += `${theme.fg("dim", `${server.name}:`)} ${theme.fg(statusColor, statusText)} ${theme.fg("dim", `(${server.fileTypes.join(", ")})`)}\n`;
|
|
@@ -712,11 +719,11 @@ export class CommandController {
|
|
|
712
719
|
return;
|
|
713
720
|
}
|
|
714
721
|
const list = await state.client.listMentalModels(state.bankId, { detail: "metadata" });
|
|
715
|
-
const existing =
|
|
722
|
+
const existing = list.items ?? [];
|
|
716
723
|
let created = 0;
|
|
717
724
|
let skipped = 0;
|
|
718
725
|
for (const seed of seeds) {
|
|
719
|
-
if (
|
|
726
|
+
if (seedAlreadyExists(seed, existing)) {
|
|
720
727
|
skipped++;
|
|
721
728
|
continue;
|
|
722
729
|
}
|
|
@@ -25,6 +25,16 @@ import { StreamingRevealController } from "./streaming-reveal";
|
|
|
25
25
|
type AgentSessionEventKind = AgentSessionEvent["type"];
|
|
26
26
|
|
|
27
27
|
const IRC_MESSAGE_VISIBLE_TTL_MS = 10_000;
|
|
28
|
+
/**
|
|
29
|
+
* Concurrent IRC cards allowed in the transcript's live region. Cards land
|
|
30
|
+
* below a still-live block (a running task), where they cannot commit to
|
|
31
|
+
* native scrollback (commits are prefix-only) — every visible card inflates
|
|
32
|
+
* the live region and pushes the live block's uncommitted rows above the
|
|
33
|
+
* window top, where they are neither on screen nor in history. A swarm burst
|
|
34
|
+
* (several agents coordinating at once) must therefore stay bounded: the
|
|
35
|
+
* oldest live-region card retires as soon as a new one would exceed the cap.
|
|
36
|
+
*/
|
|
37
|
+
const MAX_LIVE_IRC_CARDS = 4;
|
|
28
38
|
|
|
29
39
|
/**
|
|
30
40
|
* Loader label shown the instant a user interrupt (Esc) is requested, kept until
|
|
@@ -36,19 +46,6 @@ const IRC_MESSAGE_VISIBLE_TTL_MS = 10_000;
|
|
|
36
46
|
*/
|
|
37
47
|
export const INTERRUPTING_WORKING_MESSAGE = "Interrupting…";
|
|
38
48
|
|
|
39
|
-
// Events that change foreground streaming state, or that reset a turn. The TUI
|
|
40
|
-
// eager native-scrollback rebuild mode is recomputed only on these so unrelated
|
|
41
|
-
// IRC/notices/status refreshes do not toggle scrollback replay policy.
|
|
42
|
-
const STREAM_RENDER_MODE_EVENTS: Record<string, true> = {
|
|
43
|
-
agent_start: true,
|
|
44
|
-
agent_end: true,
|
|
45
|
-
message_start: true,
|
|
46
|
-
message_end: true,
|
|
47
|
-
tool_execution_start: true,
|
|
48
|
-
tool_execution_update: true,
|
|
49
|
-
tool_execution_end: true,
|
|
50
|
-
};
|
|
51
|
-
|
|
52
49
|
type AgentSessionEventHandlers = {
|
|
53
50
|
[E in AgentSessionEventKind]: (event: Extract<AgentSessionEvent, { type: E }>) => Promise<void>;
|
|
54
51
|
};
|
|
@@ -65,7 +62,6 @@ export class EventController {
|
|
|
65
62
|
#renderedCustomMessages = new Set<string>();
|
|
66
63
|
#lastIntent: string | undefined = undefined;
|
|
67
64
|
#backgroundToolCallIds = new Set<string>();
|
|
68
|
-
#assistantMessageStreaming = false;
|
|
69
65
|
#agentTurnActive = false;
|
|
70
66
|
#interrupting = false;
|
|
71
67
|
#readToolCallArgs = new Map<string, Record<string, unknown>>();
|
|
@@ -78,6 +74,9 @@ export class EventController {
|
|
|
78
74
|
#pinnedErrorComponent: AssistantMessageComponent | undefined = undefined;
|
|
79
75
|
#idleCompactionTimer?: NodeJS.Timeout;
|
|
80
76
|
#ircExpiryTimers = new Map<string, NodeJS.Timeout>();
|
|
77
|
+
// Insertion-ordered IRC cards not yet retired; values are the transcript
|
|
78
|
+
// components each card contributed (see #retireIrcCard for the guard).
|
|
79
|
+
#liveIrcCards = new Map<string, Component[]>();
|
|
81
80
|
#streamingReveal: StreamingRevealController;
|
|
82
81
|
#handlers: AgentSessionEventHandlers;
|
|
83
82
|
|
|
@@ -125,6 +124,7 @@ export class EventController {
|
|
|
125
124
|
clearTimeout(timer);
|
|
126
125
|
}
|
|
127
126
|
this.#ircExpiryTimers.clear();
|
|
127
|
+
this.#liveIrcCards.clear();
|
|
128
128
|
}
|
|
129
129
|
|
|
130
130
|
#resetReadGroup(): void {
|
|
@@ -217,30 +217,6 @@ export class EventController {
|
|
|
217
217
|
|
|
218
218
|
const run = this.#handlers[event.type] as (e: AgentSessionEvent) => Promise<void>;
|
|
219
219
|
await run(event);
|
|
220
|
-
// While an assistant turn is active, visible status chrome and foreground
|
|
221
|
-
// transcript blocks can re-render after rows have entered native scrollback
|
|
222
|
-
// (idle Working loader, Markdown fences, wrapping, tool previews). Let the
|
|
223
|
-
// TUI use its foreground live-region path instead of idle deferral, which
|
|
224
|
-
// can otherwise leave the loader/status frame frozen until the next input.
|
|
225
|
-
// Background-running tools after the turn ends are excluded so late async
|
|
226
|
-
// updates keep the no-yank deferral; agent_start/agent_end bracket the
|
|
227
|
-
// foreground turn.
|
|
228
|
-
if (STREAM_RENDER_MODE_EVENTS[event.type]) {
|
|
229
|
-
this.#refreshToolRenderMode();
|
|
230
|
-
}
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
#refreshToolRenderMode(): void {
|
|
234
|
-
let foregroundToolActive = this.#agentTurnActive || this.#assistantMessageStreaming;
|
|
235
|
-
if (!foregroundToolActive) {
|
|
236
|
-
for (const toolCallId of this.ctx.pendingTools.keys()) {
|
|
237
|
-
if (!this.#backgroundToolCallIds.has(toolCallId)) {
|
|
238
|
-
foregroundToolActive = true;
|
|
239
|
-
break;
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
this.ctx.ui.setEagerNativeScrollbackRebuild(foregroundToolActive);
|
|
244
220
|
}
|
|
245
221
|
|
|
246
222
|
async #handleAgentStart(_event: Extract<AgentSessionEvent, { type: "agent_start" }>): Promise<void> {
|
|
@@ -250,7 +226,6 @@ export class EventController {
|
|
|
250
226
|
this.#readToolCallArgs.clear();
|
|
251
227
|
this.#readToolCallAssistantComponents.clear();
|
|
252
228
|
this.#resetReadGroup();
|
|
253
|
-
this.#assistantMessageStreaming = false;
|
|
254
229
|
this.#lastAssistantComponent = undefined;
|
|
255
230
|
// Restore the previous turn's inline error in the transcript before dropping
|
|
256
231
|
// the banner, so the error stays in history once the banner is gone.
|
|
@@ -267,7 +242,6 @@ export class EventController {
|
|
|
267
242
|
this.ctx.statusContainer.clear();
|
|
268
243
|
}
|
|
269
244
|
this.#cancelIdleCompaction();
|
|
270
|
-
this.#refreshToolRenderMode();
|
|
271
245
|
this.ctx.ensureLoadingAnimation();
|
|
272
246
|
this.ctx.ui.requestRender();
|
|
273
247
|
}
|
|
@@ -340,7 +314,6 @@ export class EventController {
|
|
|
340
314
|
this.ctx.addMessageToChat(event.message);
|
|
341
315
|
this.ctx.ui.requestRender();
|
|
342
316
|
} else if (event.message.role === "assistant") {
|
|
343
|
-
this.#assistantMessageStreaming = true;
|
|
344
317
|
this.#lastVisibleBlockCount = 0;
|
|
345
318
|
this.ctx.streamingComponent = new AssistantMessageComponent(
|
|
346
319
|
undefined,
|
|
@@ -365,6 +338,7 @@ export class EventController {
|
|
|
365
338
|
this.#resetReadGroup();
|
|
366
339
|
const components = this.ctx.addMessageToChat(event.message);
|
|
367
340
|
this.#scheduleIrcExpiry(signature, components);
|
|
341
|
+
this.#enforceIrcCardCap(signature);
|
|
368
342
|
this.ctx.ui.requestRender();
|
|
369
343
|
}
|
|
370
344
|
|
|
@@ -372,13 +346,47 @@ export class EventController {
|
|
|
372
346
|
if (components.length === 0 || this.#ircExpiryTimers.has(signature)) return;
|
|
373
347
|
const timer = setTimeout(() => {
|
|
374
348
|
this.#ircExpiryTimers.delete(signature);
|
|
375
|
-
|
|
376
|
-
this.ctx.chatContainer.removeChild(component);
|
|
377
|
-
}
|
|
378
|
-
this.ctx.ui.requestRender();
|
|
349
|
+
this.#retireIrcCard(signature);
|
|
379
350
|
}, IRC_MESSAGE_VISIBLE_TTL_MS);
|
|
380
351
|
timer.unref?.();
|
|
381
352
|
this.#ircExpiryTimers.set(signature, timer);
|
|
353
|
+
this.#liveIrcCards.set(signature, components);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Remove an expired/evicted IRC card — but only while it still sits below a
|
|
358
|
+
* live block, where its rows cannot have entered native scrollback. Once
|
|
359
|
+
* everything above it has finalized, its rows may already be committed;
|
|
360
|
+
* removing them then is an interior deletion of the committed prefix, which
|
|
361
|
+
* the engine can only repair by recommitting every row below the gap —
|
|
362
|
+
* exactly the duplicated-block artifact this guard exists to prevent. Such
|
|
363
|
+
* a card simply stays: it is final history, and the window scrolls past it.
|
|
364
|
+
*/
|
|
365
|
+
#retireIrcCard(signature: string): void {
|
|
366
|
+
const components = this.#liveIrcCards.get(signature);
|
|
367
|
+
this.#liveIrcCards.delete(signature);
|
|
368
|
+
if (!components) return;
|
|
369
|
+
let removed = false;
|
|
370
|
+
for (const component of components) {
|
|
371
|
+
if (!this.ctx.chatContainer.isWithinLiveRegion(component)) continue;
|
|
372
|
+
this.ctx.chatContainer.removeChild(component);
|
|
373
|
+
removed = true;
|
|
374
|
+
}
|
|
375
|
+
if (removed) this.ctx.ui.requestRender();
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** Evict oldest live-region cards beyond {@link MAX_LIVE_IRC_CARDS}. */
|
|
379
|
+
#enforceIrcCardCap(latestSignature: string): void {
|
|
380
|
+
while (this.#liveIrcCards.size > MAX_LIVE_IRC_CARDS) {
|
|
381
|
+
const oldest = this.#liveIrcCards.keys().next().value;
|
|
382
|
+
if (oldest === undefined || oldest === latestSignature) return;
|
|
383
|
+
const timer = this.#ircExpiryTimers.get(oldest);
|
|
384
|
+
if (timer) {
|
|
385
|
+
clearTimeout(timer);
|
|
386
|
+
this.#ircExpiryTimers.delete(oldest);
|
|
387
|
+
}
|
|
388
|
+
this.#retireIrcCard(oldest);
|
|
389
|
+
}
|
|
382
390
|
}
|
|
383
391
|
|
|
384
392
|
async #handleNotice(event: Extract<AgentSessionEvent, { type: "notice" }>): Promise<void> {
|
|
@@ -406,6 +414,26 @@ export class EventController {
|
|
|
406
414
|
this.#resetReadGroup();
|
|
407
415
|
this.#lastVisibleBlockCount = visibleBlockCount;
|
|
408
416
|
}
|
|
417
|
+
|
|
418
|
+
// Content blocks stream sequentially: a toolCall block can only begin
|
|
419
|
+
// after every preceding thinking/text block has closed, and the
|
|
420
|
+
// reveal's setTarget above force-completes the visible text for
|
|
421
|
+
// toolCall messages. Finalize the assistant block now instead of at
|
|
422
|
+
// message_end so the transcript's commit-safe run can extend through
|
|
423
|
+
// it into the streaming tool preview below — otherwise a long args
|
|
424
|
+
// stream (a big write/edit/eval) sits below a still-live block and
|
|
425
|
+
// can never reach native scrollback: the head of the preview is
|
|
426
|
+
// neither committed nor on screen and the transcript reads as cut.
|
|
427
|
+
// Skipped when the per-turn usage row is enabled: that row is only
|
|
428
|
+
// known at message_end and appends to this block, which would shift
|
|
429
|
+
// committed tool rows below it every turn (audit recommit →
|
|
430
|
+
// duplicated preview copies in scrollback).
|
|
431
|
+
if (
|
|
432
|
+
this.ctx.streamingMessage.content.some(content => content.type === "toolCall") &&
|
|
433
|
+
!settings.get("display.showTokenUsage")
|
|
434
|
+
) {
|
|
435
|
+
this.ctx.streamingComponent.markTranscriptBlockFinalized();
|
|
436
|
+
}
|
|
409
437
|
for (const content of this.ctx.streamingMessage.content) {
|
|
410
438
|
if (content.type !== "toolCall") continue;
|
|
411
439
|
if (content.name === "read") {
|
|
@@ -491,9 +519,6 @@ export class EventController {
|
|
|
491
519
|
|
|
492
520
|
async #handleMessageEnd(event: Extract<AgentSessionEvent, { type: "message_end" }>): Promise<void> {
|
|
493
521
|
if (event.message.role === "user") return;
|
|
494
|
-
if (event.message.role === "assistant") {
|
|
495
|
-
this.#assistantMessageStreaming = false;
|
|
496
|
-
}
|
|
497
522
|
if (this.ctx.streamingComponent && event.message.role === "assistant") {
|
|
498
523
|
this.ctx.streamingMessage = event.message;
|
|
499
524
|
this.#streamingReveal.stop();
|
|
@@ -701,7 +726,6 @@ export class EventController {
|
|
|
701
726
|
}
|
|
702
727
|
async #handleAgentEnd(_event: Extract<AgentSessionEvent, { type: "agent_end" }>): Promise<void> {
|
|
703
728
|
this.#agentTurnActive = false;
|
|
704
|
-
this.#assistantMessageStreaming = false;
|
|
705
729
|
this.#streamingReveal.stop();
|
|
706
730
|
if (this.ctx.loadingAnimation) {
|
|
707
731
|
this.ctx.loadingAnimation.stop();
|
|
@@ -2,7 +2,7 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import type { AutocompleteProvider, SlashCommand } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { $env, logger, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
5
|
-
import { getRoleInfo } from "../../config/model-
|
|
5
|
+
import { getRoleInfo } from "../../config/model-roles";
|
|
6
6
|
import { isSettingsInitialized, settings } from "../../config/settings";
|
|
7
7
|
import { renderSegmentTrack } from "../../modes/components/segment-track";
|
|
8
8
|
import { TinyTitleDownloadProgressComponent } from "../../modes/components/tiny-title-download-progress";
|
|
@@ -267,7 +267,7 @@ export class InputController {
|
|
|
267
267
|
const focused = this.ctx.ui.getFocused();
|
|
268
268
|
const target = focused && focused !== this.ctx.editor && hasPasteText(focused) ? focused : this.ctx.editor;
|
|
269
269
|
target.pasteText(text);
|
|
270
|
-
this.ctx.ui.requestRender(
|
|
270
|
+
this.ctx.ui.requestRender();
|
|
271
271
|
},
|
|
272
272
|
pasteImage: async image => {
|
|
273
273
|
// Images can only land in the main editor — when a modal Input is
|
|
@@ -755,7 +755,7 @@ export class InputController {
|
|
|
755
755
|
const dims = await this.#imageDimensions(imageData);
|
|
756
756
|
const label = dims ? `[Image #${imageNum}, ${dims.width}x${dims.height}]` : `[Image #${imageNum}]`;
|
|
757
757
|
this.ctx.editor.insertText(`${label} `);
|
|
758
|
-
this.ctx.ui.requestRender(
|
|
758
|
+
this.ctx.ui.requestRender();
|
|
759
759
|
}
|
|
760
760
|
|
|
761
761
|
/** Probe pixel dimensions for the marker label (`[Image #N, WxH]`). Returns undefined when the
|
|
@@ -801,7 +801,7 @@ export class InputController {
|
|
|
801
801
|
});
|
|
802
802
|
if (!image) {
|
|
803
803
|
this.ctx.editor.pasteText(path);
|
|
804
|
-
this.ctx.ui.requestRender(
|
|
804
|
+
this.ctx.ui.requestRender();
|
|
805
805
|
this.ctx.showStatus("Pasted path is not a supported image");
|
|
806
806
|
return;
|
|
807
807
|
}
|
|
@@ -811,7 +811,7 @@ export class InputController {
|
|
|
811
811
|
);
|
|
812
812
|
} catch (error) {
|
|
813
813
|
this.ctx.editor.pasteText(path);
|
|
814
|
-
this.ctx.ui.requestRender(
|
|
814
|
+
this.ctx.ui.requestRender();
|
|
815
815
|
this.ctx.showStatus(
|
|
816
816
|
error instanceof ImageInputTooLargeError ? error.message : "Failed to read pasted image path",
|
|
817
817
|
);
|
|
@@ -62,7 +62,7 @@ export class MCPAuthorizationLinkPrompt implements Component {
|
|
|
62
62
|
|
|
63
63
|
invalidate(): void {}
|
|
64
64
|
|
|
65
|
-
render(_width: number): string[] {
|
|
65
|
+
render(_width: number): readonly string[] {
|
|
66
66
|
const link = urlHyperlinkAlways(this.#url, "Click here to authorize");
|
|
67
67
|
return [
|
|
68
68
|
` ${theme.fg("success", "Open authorization URL:")}`,
|
|
@@ -5,8 +5,8 @@ import type { OAuthProvider } from "@oh-my-pi/pi-ai/oauth/types";
|
|
|
5
5
|
import type { Component, OverlayHandle } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { Input, Loader, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
7
7
|
import { getAgentDbPath, getProjectDir, normalizePathForComparison } from "@oh-my-pi/pi-utils";
|
|
8
|
-
import { getRoleInfo } from "../../config/model-registry";
|
|
9
8
|
import { formatModelSelectorValue } from "../../config/model-resolver";
|
|
9
|
+
import { getRoleInfo } from "../../config/model-roles";
|
|
10
10
|
import { settings } from "../../config/settings";
|
|
11
11
|
import { disableProvider, enableProvider } from "../../discovery";
|
|
12
12
|
import { clearPluginRootsAndCaches, resolveActiveProjectRegistryPath } from "../../discovery/helpers";
|
|
@@ -266,10 +266,6 @@ export class SelectorController {
|
|
|
266
266
|
this.ctx.updateEditorBorderColor();
|
|
267
267
|
break;
|
|
268
268
|
|
|
269
|
-
case "clearOnShrink":
|
|
270
|
-
this.ctx.ui.setClearOnShrink(value as boolean);
|
|
271
|
-
break;
|
|
272
|
-
|
|
273
269
|
case "autocompleteMaxVisible":
|
|
274
270
|
this.ctx.editor.setAutocompleteMaxVisible(typeof value === "number" ? value : Number(value));
|
|
275
271
|
break;
|