@oh-my-pi/pi-coding-agent 17.0.8 → 17.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +87 -1
- package/README.md +1 -1
- package/dist/cli.js +5495 -5021
- package/dist/types/async/job-manager.d.ts +37 -1
- package/dist/types/cli/args.d.ts +2 -0
- package/dist/types/cli/bench-cli.d.ts +41 -0
- package/dist/types/commands/bench.d.ts +15 -3
- package/dist/types/commands/launch.d.ts +4 -0
- package/dist/types/config/__tests__/model-registry.test.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +30 -1
- package/dist/types/config/models-config-schema.d.ts +18 -6
- package/dist/types/config/models-config.d.ts +14 -5
- package/dist/types/config/provider-globals.d.ts +2 -2
- package/dist/types/config/service-tier.d.ts +5 -1
- package/dist/types/config/settings-schema.d.ts +350 -50
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/dap/client.d.ts +20 -0
- package/dist/types/exec/bash-executor.d.ts +38 -0
- package/dist/types/exec/direnv.d.ts +33 -0
- package/dist/types/export/html/args.d.ts +15 -0
- package/dist/types/export/html/index.d.ts +11 -26
- package/dist/types/export/html/web-palette.d.ts +55 -116
- package/dist/types/extensibility/extensions/loader.d.ts +3 -0
- package/dist/types/extensibility/extensions/types.d.ts +17 -2
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +1 -0
- package/dist/types/extensibility/legacy-pi-tui-shim.d.ts +10 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +14 -1
- package/dist/types/internal-urls/registry-helpers.d.ts +11 -0
- package/dist/types/live/controller.d.ts +40 -0
- package/dist/types/live/protocol.d.ts +85 -0
- package/dist/types/live/protocol.test.d.ts +1 -0
- package/dist/types/live/transport.d.ts +48 -0
- package/dist/types/live/visualizer.d.ts +33 -0
- package/dist/types/lsp/client.d.ts +18 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/manager.d.ts +6 -2
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/render.d.ts +2 -2
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
- package/dist/types/mcp/tool-bridge.d.ts +7 -3
- package/dist/types/mcp/types.d.ts +6 -0
- package/dist/types/memory-backend/tool-names.d.ts +2 -0
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/oauth-selector.d.ts +8 -0
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/settings-defs.d.ts +8 -1
- package/dist/types/modes/components/status-line/component.jj-cache.test.d.ts +1 -0
- package/dist/types/modes/components/tool-execution.d.ts +9 -1
- package/dist/types/modes/controllers/command-controller.d.ts +1 -1
- package/dist/types/modes/controllers/event-controller.d.ts +6 -1
- package/dist/types/modes/controllers/live-command-controller.d.ts +14 -0
- package/dist/types/modes/controllers/mcp-command-controller.d.ts +3 -0
- package/dist/types/modes/interactive-mode.d.ts +5 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +10 -1
- package/dist/types/modes/rpc/rpc-frame.d.ts +16 -0
- package/dist/types/modes/rpc/rpc-messages.d.ts +26 -0
- package/dist/types/modes/rpc/rpc-types.d.ts +40 -0
- package/dist/types/modes/setup-wizard/scenes/sign-in.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/types.d.ts +9 -1
- package/dist/types/modes/setup-wizard/scenes/web-search.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/context-usage.d.ts +12 -2
- package/dist/types/plan-mode/plan-files.d.ts +10 -0
- package/dist/types/registry/agent-lifecycle.d.ts +21 -9
- package/dist/types/registry/agent-registry.d.ts +11 -4
- package/dist/types/sdk.d.ts +15 -1
- package/dist/types/secrets/index.d.ts +15 -2
- package/dist/types/secrets/obfuscator.d.ts +113 -15
- package/dist/types/session/acp-permission-gate.d.ts +19 -0
- package/dist/types/session/agent-session-events.d.ts +84 -0
- package/dist/types/session/agent-session-types.d.ts +314 -0
- package/dist/types/session/agent-session.d.ts +120 -750
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/bash-runner.d.ts +75 -0
- package/dist/types/session/blob-store.d.ts +11 -1
- package/dist/types/session/blob-store.test.d.ts +1 -0
- package/dist/types/session/checkpoint-entries.d.ts +28 -0
- package/dist/types/session/eval-runner.d.ts +49 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +22 -0
- package/dist/types/session/model-controls.d.ts +180 -0
- package/dist/types/session/prewalk.d.ts +46 -0
- package/dist/types/session/queued-messages.d.ts +22 -0
- package/dist/types/session/redis-session-storage.d.ts +1 -0
- package/dist/types/session/retry-fallback-chains.d.ts +74 -0
- package/dist/types/session/role-models.d.ts +15 -0
- package/dist/types/session/session-advisors.d.ts +228 -0
- package/dist/types/session/session-entries.d.ts +8 -0
- package/dist/types/session/session-handoff.d.ts +70 -0
- package/dist/types/session/session-history-format.d.ts +6 -1
- package/dist/types/session/session-loader.d.ts +6 -4
- package/dist/types/session/session-maintenance.d.ts +246 -0
- package/dist/types/session/session-manager.d.ts +41 -0
- package/dist/types/session/session-memory.d.ts +56 -0
- package/dist/types/session/session-provider-boundary.d.ts +62 -0
- package/dist/types/session/session-stats.d.ts +48 -0
- package/dist/types/session/session-tools.d.ts +143 -0
- package/dist/types/session/session-workspace.d.ts +32 -0
- package/dist/types/session/stream-guards.d.ts +44 -0
- package/dist/types/session/todo-tracker.d.ts +56 -0
- package/dist/types/session/ttsr-coordinator.d.ts +51 -0
- package/dist/types/session/turn-recovery.d.ts +225 -0
- package/dist/types/system-prompt.d.ts +2 -0
- package/dist/types/task/executor.d.ts +4 -1
- package/dist/types/task/output-manager.d.ts +3 -1
- package/dist/types/task/types.d.ts +14 -0
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/hub/jobs.d.ts +1 -1
- package/dist/types/tools/image-gen.d.ts +4 -4
- package/dist/types/tools/image-providers.d.ts +38 -0
- package/dist/types/tools/index.d.ts +8 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- package/dist/types/tools/report-tool-issue.d.ts +24 -9
- package/dist/types/tools/todo.d.ts +10 -4
- package/dist/types/tools/xdev.d.ts +5 -1
- package/dist/types/tts/streaming-player.d.ts +14 -0
- package/dist/types/tts/vocalizer.d.ts +5 -0
- package/dist/types/utils/jj.d.ts +29 -0
- package/dist/types/utils/lang-from-path.d.ts +1 -0
- package/dist/types/utils/title-generator.d.ts +30 -0
- package/dist/types/vibe/runtime.d.ts +36 -8
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +15 -5
- package/dist/types/web/search/providers/firecrawl.d.ts +9 -0
- package/dist/types/web/search/types.d.ts +95 -1
- package/package.json +13 -12
- package/scripts/generate-share-viewer.ts +4 -6
- package/scripts/legacy-pi-virtual-module.ts +1 -1
- package/src/advisor/__tests__/advisor.test.ts +643 -0
- package/src/advisor/runtime.ts +144 -43
- package/src/async/job-manager.ts +90 -2
- package/src/cli/args.ts +2 -0
- package/src/cli/auth-gateway-cli.ts +18 -3
- package/src/cli/bench-cli.ts +395 -29
- package/src/cli/flag-tables.ts +3 -0
- package/src/cli/gc-cli.ts +2 -2
- package/src/cli/grievances-cli.ts +2 -2
- package/src/cli/usage-cli.ts +11 -2
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/__tests__/model-registry.test.ts +147 -0
- package/src/config/model-registry.ts +50 -2
- package/src/config/model-resolver.ts +0 -1
- package/src/config/models-config-schema.ts +15 -4
- package/src/config/provider-globals.ts +9 -9
- package/src/config/service-tier.ts +26 -1
- package/src/config/settings-schema.ts +250 -54
- package/src/config/settings.ts +81 -2
- package/src/dap/client.ts +37 -4
- package/src/edit/index.ts +49 -12
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +89 -6
- package/src/exec/direnv.ts +145 -0
- package/src/export/html/args.ts +20 -0
- package/src/export/html/index.ts +45 -47
- package/src/export/html/template.css +19 -1
- package/src/export/html/template.html +6 -0
- package/src/export/html/template.js +21 -0
- package/src/export/html/tool-views.generated.js +31 -31
- package/src/export/html/web-palette.ts +116 -132
- package/src/export/share.ts +249 -49
- package/src/extensibility/extensions/loader.ts +29 -1
- package/src/extensibility/extensions/runner.ts +6 -0
- package/src/extensibility/extensions/types.ts +33 -2
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +1 -0
- package/src/extensibility/legacy-pi-tui-shim.ts +10 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +870 -311
- package/src/hindsight/state.ts +64 -6
- package/src/internal-urls/local-protocol.ts +2 -1
- package/src/internal-urls/memory-protocol.ts +2 -2
- package/src/internal-urls/registry-helpers.ts +40 -0
- package/src/internal-urls/skill-protocol.ts +2 -2
- package/src/internal-urls/ssh-protocol.ts +2 -1
- package/src/internal-urls/vault-protocol.ts +2 -1
- package/src/live/audio-worklet.txt +59 -0
- package/src/live/browser-runtime.txt +221 -0
- package/src/live/controller.ts +464 -0
- package/src/live/prompts/agent-final-message.md +3 -0
- package/src/live/prompts/live-instructions.md +23 -0
- package/src/live/protocol.test.ts +140 -0
- package/src/live/protocol.ts +233 -0
- package/src/live/transport.ts +490 -0
- package/src/live/visualizer.ts +234 -0
- package/src/lsp/client.ts +52 -9
- package/src/lsp/clients/biome-client.ts +3 -4
- package/src/lsp/index.ts +44 -11
- package/src/main.ts +9 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/manager.ts +39 -8
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/render.ts +94 -35
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- package/src/mcp/tool-bridge.ts +107 -11
- package/src/mcp/types.ts +7 -0
- package/src/memories/index.ts +40 -20
- package/src/memory-backend/tool-names.ts +2 -0
- package/src/mnemopi/backend.ts +24 -7
- package/src/modes/acp/acp-agent.ts +56 -10
- package/src/modes/acp/acp-event-mapper.ts +8 -1
- package/src/modes/components/agent-hub.ts +27 -10
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/oauth-selector.ts +24 -7
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/settings-defs.ts +28 -3
- package/src/modes/components/settings-selector.ts +178 -6
- package/src/modes/components/status-line/component.jj-cache.test.ts +229 -0
- package/src/modes/components/status-line/component.ts +123 -2
- package/src/modes/components/tool-execution.test.ts +63 -2
- package/src/modes/components/tool-execution.ts +11 -2
- package/src/modes/controllers/command-controller.ts +70 -22
- package/src/modes/controllers/event-controller.ts +111 -16
- package/src/modes/controllers/extension-ui-controller.test.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +20 -4
- package/src/modes/controllers/live-command-controller.ts +178 -0
- package/src/modes/controllers/mcp-command-controller.ts +80 -41
- package/src/modes/controllers/omfg-controller.ts +44 -40
- package/src/modes/controllers/selector-controller.ts +18 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +160 -38
- package/src/modes/rpc/rpc-client.ts +94 -3
- package/src/modes/rpc/rpc-frame.ts +164 -4
- package/src/modes/rpc/rpc-messages.ts +127 -0
- package/src/modes/rpc/rpc-mode.ts +79 -7
- package/src/modes/rpc/rpc-types.ts +34 -2
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/model.ts +5 -7
- package/src/modes/setup-wizard/scenes/providers.ts +3 -2
- package/src/modes/setup-wizard/scenes/sign-in.ts +8 -2
- package/src/modes/setup-wizard/scenes/theme.ts +13 -3
- package/src/modes/setup-wizard/scenes/types.ts +9 -1
- package/src/modes/setup-wizard/scenes/web-search.ts +23 -10
- package/src/modes/setup-wizard/wizard-overlay.ts +1 -1
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +3 -1
- package/src/modes/utils/context-usage.ts +23 -7
- package/src/modes/utils/ui-helpers.ts +10 -5
- package/src/plan-mode/plan-files.ts +40 -0
- package/src/prompts/bench/cache-prefix-chunk.md +1 -0
- package/src/prompts/bench/cache-prefix.md +3 -0
- package/src/prompts/bench/cache-suffix.md +1 -0
- package/src/prompts/goals/guided-goal-system.md +24 -3
- package/src/prompts/system/custom-system-prompt.md +1 -1
- package/src/prompts/system/project-prompt.md +8 -1
- package/src/prompts/system/subagent-async-pending.md +6 -0
- package/src/prompts/system/system-prompt.md +1 -1
- package/src/prompts/system/workflow-notice.md +48 -40
- package/src/prompts/system/xdev-mount-notice.md +4 -0
- package/src/prompts/tools/task.md +12 -2
- package/src/prompts/tools/todo.md +3 -1
- package/src/registry/agent-lifecycle.ts +116 -46
- package/src/registry/agent-registry.ts +35 -8
- package/src/registry/persisted-agents.ts +26 -2
- package/src/sdk.ts +255 -139
- package/src/secrets/index.ts +127 -4
- package/src/secrets/obfuscator.ts +2328 -105
- package/src/session/acp-permission-gate.ts +165 -0
- package/src/session/agent-session-error-log.test.ts +1 -1
- package/src/session/agent-session-events.ts +66 -0
- package/src/session/agent-session-types.ts +334 -0
- package/src/session/agent-session.ts +1910 -12213
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/bash-runner.ts +326 -0
- package/src/session/blob-store.test.ts +56 -0
- package/src/session/blob-store.ts +18 -2
- package/src/session/checkpoint-entries.ts +81 -0
- package/src/session/eval-runner.ts +212 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +263 -1
- package/src/session/model-controls.ts +714 -0
- package/src/session/prewalk.ts +252 -0
- package/src/session/queued-messages.ts +93 -0
- package/src/session/redis-session-storage.ts +51 -11
- package/src/session/retry-fallback-chains.ts +455 -0
- package/src/session/role-models.ts +85 -0
- package/src/session/session-advisors.ts +1679 -0
- package/src/session/session-context.test.ts +224 -1
- package/src/session/session-context.ts +47 -5
- package/src/session/session-entries.ts +8 -0
- package/src/session/session-handoff.ts +308 -0
- package/src/session/session-history-format.ts +20 -9
- package/src/session/session-loader.ts +10 -51
- package/src/session/session-maintenance.ts +2983 -0
- package/src/session/session-manager.ts +390 -30
- package/src/session/session-memory.ts +222 -0
- package/src/session/session-provider-boundary.ts +307 -0
- package/src/session/session-stats.ts +293 -0
- package/src/session/session-tools.ts +943 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/todo-tracker.ts +380 -0
- package/src/session/ttsr-coordinator.ts +496 -0
- package/src/session/turn-recovery.ts +1629 -0
- package/src/slash-commands/builtin-registry.ts +92 -11
- package/src/slash-commands/helpers/usage-report.ts +25 -4
- package/src/system-prompt.ts +41 -17
- package/src/task/executor.ts +288 -70
- package/src/task/index.ts +148 -111
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +4 -1
- package/src/task/types.ts +16 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +26 -0
- package/src/tools/bash-skill-urls.ts +28 -2
- package/src/tools/bash.ts +339 -123
- package/src/tools/browser/launch.ts +9 -1
- package/src/tools/browser/tab-supervisor.ts +8 -5
- package/src/tools/browser.ts +94 -47
- package/src/tools/hub/index.ts +1 -1
- package/src/tools/hub/jobs.ts +67 -8
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +12 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +7 -1
- package/src/tools/report-tool-issue.ts +79 -28
- package/src/tools/todo.ts +101 -11
- package/src/tools/vibe.ts +1 -2
- package/src/tools/write.ts +51 -1
- package/src/tools/xdev.ts +67 -3
- package/src/tts/streaming-player.ts +62 -4
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/vibe/runtime.ts +852 -72
- package/src/web/search/index.ts +3 -6
- package/src/web/search/provider.ts +31 -16
- package/src/web/search/providers/firecrawl.ts +46 -13
- package/src/web/search/providers/xai.ts +7 -1
- package/src/web/search/types.ts +8 -1
|
@@ -60,6 +60,8 @@ export class OAuthSelectorComponent extends Container {
|
|
|
60
60
|
/** First provider index of the visible ScrollView window (last #updateList). */
|
|
61
61
|
#scrollStart = 0;
|
|
62
62
|
#visibleCount = 0;
|
|
63
|
+
/** Visible list window, shrunk by {@link setMaxHeight} on short screens. */
|
|
64
|
+
#maxVisible = OAUTH_SELECTOR_MAX_VISIBLE;
|
|
63
65
|
#mode: "login" | "logout";
|
|
64
66
|
#authStorage: AuthStorage;
|
|
65
67
|
#onSelectCallback: (providerId: string) => void;
|
|
@@ -111,6 +113,24 @@ export class OAuthSelectorComponent extends Container {
|
|
|
111
113
|
this.#validationGeneration += 1;
|
|
112
114
|
this.#stopSpinner();
|
|
113
115
|
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Fit the selector into `lines` rendered rows by shrinking the visible list
|
|
119
|
+
* window (the window is centered on the selection, so the selected row is
|
|
120
|
+
* always visible at any height). Prefers keeping the full chrome — borders,
|
|
121
|
+
* spacers, title, search status — but sacrifices the trailing spacer/border
|
|
122
|
+
* (clipped by the host) before dropping below three visible rows.
|
|
123
|
+
*/
|
|
124
|
+
setMaxHeight(lines: number): void {
|
|
125
|
+
// Above the rows: LIST_ROW_OFFSET; below: search status + spacer + border.
|
|
126
|
+
const strict = lines - LIST_ROW_OFFSET - 3;
|
|
127
|
+
// Keeps only the rows + search status inside `lines`.
|
|
128
|
+
const relaxed = lines - LIST_ROW_OFFSET - 1;
|
|
129
|
+
const rows = Math.min(OAUTH_SELECTOR_MAX_VISIBLE, Math.max(1, strict, Math.min(relaxed, 3)));
|
|
130
|
+
if (rows === this.#maxVisible) return;
|
|
131
|
+
this.#maxVisible = rows;
|
|
132
|
+
this.#updateList();
|
|
133
|
+
}
|
|
114
134
|
#hasSelectableAuth(providerId: string): boolean {
|
|
115
135
|
return this.#mode === "logout" ? this.#authStorage.has(providerId) : this.#authStorage.hasAuth(providerId);
|
|
116
136
|
}
|
|
@@ -227,7 +247,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
227
247
|
}
|
|
228
248
|
|
|
229
249
|
#isSearchEnabled(): boolean {
|
|
230
|
-
return this.#allProviders.length >
|
|
250
|
+
return this.#allProviders.length > this.#maxVisible;
|
|
231
251
|
}
|
|
232
252
|
|
|
233
253
|
#shouldRenderSearchStatus(): boolean {
|
|
@@ -286,7 +306,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
286
306
|
this.#listContainer.clear();
|
|
287
307
|
|
|
288
308
|
const total = this.#filteredProviders.length;
|
|
289
|
-
const maxVisible =
|
|
309
|
+
const maxVisible = this.#maxVisible;
|
|
290
310
|
const startIndex =
|
|
291
311
|
total <= maxVisible
|
|
292
312
|
? 0
|
|
@@ -381,7 +401,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
381
401
|
// Page up - jump up by one visible page
|
|
382
402
|
else if (matchesKey(keyData, "pageUp")) {
|
|
383
403
|
if (this.#filteredProviders.length > 0) {
|
|
384
|
-
this.#selectedIndex = Math.max(0, this.#selectedIndex -
|
|
404
|
+
this.#selectedIndex = Math.max(0, this.#selectedIndex - this.#maxVisible);
|
|
385
405
|
}
|
|
386
406
|
this.#statusMessage = undefined;
|
|
387
407
|
this.#updateList();
|
|
@@ -389,10 +409,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
389
409
|
// Page down - jump down by one visible page
|
|
390
410
|
else if (matchesKey(keyData, "pageDown")) {
|
|
391
411
|
if (this.#filteredProviders.length > 0) {
|
|
392
|
-
this.#selectedIndex = Math.min(
|
|
393
|
-
this.#filteredProviders.length - 1,
|
|
394
|
-
this.#selectedIndex + OAUTH_SELECTOR_MAX_VISIBLE,
|
|
395
|
-
);
|
|
412
|
+
this.#selectedIndex = Math.min(this.#filteredProviders.length - 1, this.#selectedIndex + this.#maxVisible);
|
|
396
413
|
}
|
|
397
414
|
this.#statusMessage = undefined;
|
|
398
415
|
this.#updateList();
|
|
@@ -23,11 +23,14 @@ import {
|
|
|
23
23
|
Markdown,
|
|
24
24
|
type MarkdownTheme,
|
|
25
25
|
matchesKey,
|
|
26
|
+
replaceTabs,
|
|
26
27
|
routeSgrMouseInput,
|
|
27
28
|
ScrollView,
|
|
28
29
|
truncateToWidth,
|
|
29
30
|
visibleWidth,
|
|
30
31
|
} from "@oh-my-pi/pi-tui";
|
|
32
|
+
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
33
|
+
import { sanitizeStatusText } from "../shared";
|
|
31
34
|
import { getMarkdownTheme, theme } from "../theme/theme";
|
|
32
35
|
import {
|
|
33
36
|
matchesAppExternalEditor,
|
|
@@ -58,22 +61,59 @@ const MIN_BODY_ROWS = 3;
|
|
|
58
61
|
const SIDEBAR_MIN_HEADINGS = 2;
|
|
59
62
|
const SIDEBAR_MIN_TOTAL_WIDTH = 64;
|
|
60
63
|
const SIDEBAR_MIN_BODY_WIDTH = 40;
|
|
64
|
+
/** Persisted line-context cap; render-time captions clamp again to the viewport. */
|
|
65
|
+
const MAX_ANNOTATION_CONTEXT_WIDTH = 120;
|
|
61
66
|
|
|
62
67
|
type Focus = "toc" | "body" | "actions";
|
|
63
68
|
|
|
69
|
+
type AnnotationTarget = { kind: "section" } | { kind: "line"; row: number; context: string; contextTruncated: boolean };
|
|
70
|
+
|
|
71
|
+
interface OverlayAnnotation {
|
|
72
|
+
note: string;
|
|
73
|
+
target: AnnotationTarget;
|
|
74
|
+
}
|
|
75
|
+
|
|
64
76
|
interface OverlaySection {
|
|
65
77
|
level: number;
|
|
66
78
|
title: string;
|
|
67
79
|
raw: string;
|
|
68
80
|
md: Markdown;
|
|
69
|
-
annotations:
|
|
81
|
+
annotations: OverlayAnnotation[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface LineAnchorContext {
|
|
85
|
+
text: string;
|
|
86
|
+
truncated: boolean;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
interface BodyRowAnchor {
|
|
90
|
+
sectionIndex: number;
|
|
91
|
+
row: number;
|
|
92
|
+
context: string;
|
|
93
|
+
contextTruncated: boolean;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Serializable annotations retained by the plan-review owner between overlays. */
|
|
97
|
+
export interface PlanReviewAnnotationState {
|
|
98
|
+
annotations: Array<{
|
|
99
|
+
section: {
|
|
100
|
+
index: number;
|
|
101
|
+
title: string;
|
|
102
|
+
/** Heading ancestry from the document root, when emitted by this overlay. */
|
|
103
|
+
path?: string[];
|
|
104
|
+
/** Hash of the section source, used to reject ambiguous moved headings. */
|
|
105
|
+
contentHash?: string;
|
|
106
|
+
};
|
|
107
|
+
target: { kind: "section" } | { kind: "line"; row: number; context: string; contextTruncated?: boolean };
|
|
108
|
+
note: string;
|
|
109
|
+
}>;
|
|
70
110
|
}
|
|
71
111
|
|
|
72
112
|
/** Undo snapshot: joined plan text, annotations aligned by section, and the
|
|
73
113
|
* accumulated deleted-section feedback at the time of the snapshot. */
|
|
74
114
|
interface UndoEntry {
|
|
75
115
|
text: string;
|
|
76
|
-
annotations:
|
|
116
|
+
annotations: OverlayAnnotation[][];
|
|
77
117
|
deleted: string[];
|
|
78
118
|
}
|
|
79
119
|
|
|
@@ -92,6 +132,8 @@ export interface PlanReviewOverlayCallbacks {
|
|
|
92
132
|
onPlanEdited?: (content: string) => void;
|
|
93
133
|
/** Invoked with the Refine feedback markdown whenever annotations change. */
|
|
94
134
|
onFeedbackChange?: (feedback: string) => void;
|
|
135
|
+
/** Invoked with a serializable annotation snapshot whenever annotations change. */
|
|
136
|
+
onAnnotationStateChange?: (state: PlanReviewAnnotationState) => void;
|
|
95
137
|
}
|
|
96
138
|
|
|
97
139
|
export interface PlanReviewOverlayOptions {
|
|
@@ -108,6 +150,8 @@ export interface PlanReviewOverlayOptions {
|
|
|
108
150
|
slider?: HookSelectorSlider;
|
|
109
151
|
/** Display label for the external-editor key, surfaced in the footer help. */
|
|
110
152
|
externalEditorLabel?: string;
|
|
153
|
+
/** Serializable annotations restored into this overlay instance. */
|
|
154
|
+
annotationState?: PlanReviewAnnotationState;
|
|
111
155
|
}
|
|
112
156
|
|
|
113
157
|
/** Default trailing footer hint when the caller supplies none. */
|
|
@@ -121,6 +165,8 @@ export class PlanReviewOverlay implements Component {
|
|
|
121
165
|
/** Shallowest level among ToC entries, used to flatten indentation. */
|
|
122
166
|
#tocBaseLevel = 1;
|
|
123
167
|
#sectionOffsets: number[] = [];
|
|
168
|
+
/** Rendered body row to underlying plan row; callouts retain their owner's anchor. */
|
|
169
|
+
#bodyRowAnchors: BodyRowAnchor[] = [];
|
|
124
170
|
#undo: UndoEntry[] = [];
|
|
125
171
|
/** Titles of sections deleted in the overlay, surfaced as Refine feedback. */
|
|
126
172
|
#deleted: string[] = [];
|
|
@@ -162,6 +208,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
162
208
|
#committedLabel: string | undefined;
|
|
163
209
|
#annotating = false;
|
|
164
210
|
#input: Input;
|
|
211
|
+
#annotationTarget: BodyRowAnchor | { sectionIndex: number; row: null; context: null } | undefined;
|
|
165
212
|
|
|
166
213
|
constructor(
|
|
167
214
|
planContent: string,
|
|
@@ -194,6 +241,10 @@ export class PlanReviewOverlay implements Component {
|
|
|
194
241
|
this.#input.onSubmit = value => this.#submitAnnotation(value);
|
|
195
242
|
this.#input.onEscape = () => this.#exitAnnotate();
|
|
196
243
|
this.#setSections(planContent);
|
|
244
|
+
this.#restoreAnnotationState(options.annotationState);
|
|
245
|
+
if (Array.isArray(options.annotationState?.annotations) && options.annotationState.annotations.length > 0) {
|
|
246
|
+
this.#recomputeFeedback();
|
|
247
|
+
}
|
|
197
248
|
}
|
|
198
249
|
|
|
199
250
|
invalidate(): void {
|
|
@@ -204,7 +255,9 @@ export class PlanReviewOverlay implements Component {
|
|
|
204
255
|
* reset scroll/focus so the operator starts at the top. Does not emit
|
|
205
256
|
* `onPlanEdited` (the editor round-trip already persisted the file). */
|
|
206
257
|
setPlanContent(planContent: string): void {
|
|
258
|
+
const annotations = this.#annotationState();
|
|
207
259
|
this.#setSections(planContent);
|
|
260
|
+
this.#restoreAnnotationState(annotations);
|
|
208
261
|
this.#scrollView.scrollToTop();
|
|
209
262
|
this.#scrollProgress = 0;
|
|
210
263
|
this.#tocCursor = 0;
|
|
@@ -220,11 +273,148 @@ export class PlanReviewOverlay implements Component {
|
|
|
220
273
|
title: section.title,
|
|
221
274
|
raw: section.raw,
|
|
222
275
|
md: new Markdown(section.raw, 1, 0, this.#mdTheme),
|
|
223
|
-
annotations: []
|
|
276
|
+
annotations: [],
|
|
224
277
|
}));
|
|
225
278
|
this.#rebuildToc();
|
|
226
279
|
this.#tocCursor = Math.min(this.#tocCursor, Math.max(0, this.#toc.length - 1));
|
|
227
280
|
}
|
|
281
|
+
#cloneAnnotation(annotation: OverlayAnnotation): OverlayAnnotation {
|
|
282
|
+
return {
|
|
283
|
+
note: annotation.note,
|
|
284
|
+
target:
|
|
285
|
+
annotation.target.kind === "section"
|
|
286
|
+
? { kind: "section" }
|
|
287
|
+
: {
|
|
288
|
+
kind: "line",
|
|
289
|
+
row: annotation.target.row,
|
|
290
|
+
context: annotation.target.context,
|
|
291
|
+
contextTruncated: annotation.target.contextTruncated,
|
|
292
|
+
},
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
#sectionPaths(): string[][] {
|
|
297
|
+
const stack: Array<{ level: number; title: string }> = [];
|
|
298
|
+
return this.#sections.map(section => {
|
|
299
|
+
if (section.level < 1) return [];
|
|
300
|
+
while (stack.length > 0 && stack[stack.length - 1]!.level >= section.level) stack.pop();
|
|
301
|
+
stack.push({ level: section.level, title: section.title });
|
|
302
|
+
return stack.map(entry => entry.title);
|
|
303
|
+
});
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
#sectionContentHash(section: OverlaySection): string {
|
|
307
|
+
return `${section.raw.length}:${Bun.hash(section.raw).toString(16)}`;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
#annotationState(): PlanReviewAnnotationState {
|
|
311
|
+
const annotations: PlanReviewAnnotationState["annotations"] = [];
|
|
312
|
+
const sectionPaths = this.#sectionPaths();
|
|
313
|
+
for (let sectionIndex = 0; sectionIndex < this.#sections.length; sectionIndex++) {
|
|
314
|
+
const section = this.#sections[sectionIndex]!;
|
|
315
|
+
for (const annotation of section.annotations) {
|
|
316
|
+
annotations.push({
|
|
317
|
+
section: {
|
|
318
|
+
index: sectionIndex,
|
|
319
|
+
title: section.title,
|
|
320
|
+
path: sectionPaths[sectionIndex]!,
|
|
321
|
+
contentHash: this.#sectionContentHash(section),
|
|
322
|
+
},
|
|
323
|
+
target:
|
|
324
|
+
annotation.target.kind === "section"
|
|
325
|
+
? { kind: "section" }
|
|
326
|
+
: {
|
|
327
|
+
kind: "line",
|
|
328
|
+
row: annotation.target.row,
|
|
329
|
+
context: annotation.target.context,
|
|
330
|
+
contextTruncated: annotation.target.contextTruncated,
|
|
331
|
+
},
|
|
332
|
+
note: annotation.note,
|
|
333
|
+
});
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return { annotations };
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
#restoreAnnotationState(state: PlanReviewAnnotationState | undefined): void {
|
|
340
|
+
if (!state || !Array.isArray(state.annotations)) return;
|
|
341
|
+
const sectionPaths = this.#sectionPaths();
|
|
342
|
+
const contentHashes = this.#sections.map(section => this.#sectionContentHash(section));
|
|
343
|
+
for (const entry of state.annotations) {
|
|
344
|
+
if (
|
|
345
|
+
!entry ||
|
|
346
|
+
typeof entry.note !== "string" ||
|
|
347
|
+
!entry.section ||
|
|
348
|
+
!entry.target ||
|
|
349
|
+
typeof entry.section.title !== "string"
|
|
350
|
+
) {
|
|
351
|
+
continue;
|
|
352
|
+
}
|
|
353
|
+
const note = entry.note.trim();
|
|
354
|
+
if (!note) continue;
|
|
355
|
+
const storedIndex = Number.isInteger(entry.section.index) ? entry.section.index : -1;
|
|
356
|
+
const storedPath = entry.section.path;
|
|
357
|
+
let matchingSections: number[];
|
|
358
|
+
if (
|
|
359
|
+
Array.isArray(storedPath) &&
|
|
360
|
+
storedPath.every(segment => typeof segment === "string") &&
|
|
361
|
+
typeof entry.section.contentHash === "string"
|
|
362
|
+
) {
|
|
363
|
+
matchingSections = [];
|
|
364
|
+
for (let i = 0; i < this.#sections.length; i++) {
|
|
365
|
+
const path = sectionPaths[i]!;
|
|
366
|
+
if (
|
|
367
|
+
this.#sections[i]!.title === entry.section.title &&
|
|
368
|
+
contentHashes[i] === entry.section.contentHash &&
|
|
369
|
+
path.length === storedPath.length &&
|
|
370
|
+
path.every((segment, pathIndex) => segment === storedPath[pathIndex])
|
|
371
|
+
) {
|
|
372
|
+
matchingSections.push(i);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
} else {
|
|
376
|
+
matchingSections =
|
|
377
|
+
storedIndex >= 0 &&
|
|
378
|
+
storedIndex < this.#sections.length &&
|
|
379
|
+
this.#sections[storedIndex]!.title === entry.section.title
|
|
380
|
+
? [storedIndex]
|
|
381
|
+
: [];
|
|
382
|
+
}
|
|
383
|
+
if (matchingSections.length === 0) continue;
|
|
384
|
+
const sectionIndex = matchingSections.reduce((best, candidate) =>
|
|
385
|
+
Math.abs(candidate - storedIndex) < Math.abs(best - storedIndex) ? candidate : best,
|
|
386
|
+
);
|
|
387
|
+
const section = this.#sections[sectionIndex]!;
|
|
388
|
+
if (entry.target.kind === "section") {
|
|
389
|
+
if (section.level >= 1) section.annotations.push({ note, target: { kind: "section" } });
|
|
390
|
+
continue;
|
|
391
|
+
}
|
|
392
|
+
if (
|
|
393
|
+
entry.target.kind !== "line" ||
|
|
394
|
+
!Number.isFinite(entry.target.row) ||
|
|
395
|
+
typeof entry.target.context !== "string"
|
|
396
|
+
) {
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
const contexts = section.md.render(MAX_ANNOTATION_CONTEXT_WIDTH).map(line => this.#lineContext(line));
|
|
400
|
+
const row = this.#resolveLineRow(
|
|
401
|
+
entry.target.row,
|
|
402
|
+
{ text: entry.target.context, truncated: entry.target.contextTruncated === true },
|
|
403
|
+
contexts,
|
|
404
|
+
);
|
|
405
|
+
if (row < 0) continue;
|
|
406
|
+
const context = contexts[row]!;
|
|
407
|
+
section.annotations.push({
|
|
408
|
+
note,
|
|
409
|
+
target: {
|
|
410
|
+
kind: "line",
|
|
411
|
+
row,
|
|
412
|
+
context: context.text,
|
|
413
|
+
contextTruncated: context.truncated,
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
}
|
|
228
418
|
|
|
229
419
|
#rebuildToc(): void {
|
|
230
420
|
const headings: number[] = [];
|
|
@@ -443,6 +633,10 @@ export class PlanReviewOverlay implements Component {
|
|
|
443
633
|
}
|
|
444
634
|
|
|
445
635
|
#handleBody(data: string): void {
|
|
636
|
+
if (data === "a") {
|
|
637
|
+
this.#startBodyAnnotate();
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
446
640
|
if (matchesKey(data, "left") || matchesKey(data, "h")) {
|
|
447
641
|
if (this.#sidebarShown) this.#setFocus("toc");
|
|
448
642
|
return;
|
|
@@ -529,7 +723,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
529
723
|
return;
|
|
530
724
|
}
|
|
531
725
|
if (data === "a") {
|
|
532
|
-
this.#
|
|
726
|
+
this.#startSectionAnnotate();
|
|
533
727
|
return;
|
|
534
728
|
}
|
|
535
729
|
if (data === "u") {
|
|
@@ -577,7 +771,9 @@ export class PlanReviewOverlay implements Component {
|
|
|
577
771
|
#pushUndo(): void {
|
|
578
772
|
this.#undo.push({
|
|
579
773
|
text: joinPlanSections(this.#sections),
|
|
580
|
-
annotations: this.#sections.map(section =>
|
|
774
|
+
annotations: this.#sections.map(section =>
|
|
775
|
+
section.annotations.map(annotation => this.#cloneAnnotation(annotation)),
|
|
776
|
+
),
|
|
581
777
|
deleted: [...this.#deleted],
|
|
582
778
|
});
|
|
583
779
|
}
|
|
@@ -607,7 +803,8 @@ export class PlanReviewOverlay implements Component {
|
|
|
607
803
|
if (!entry) return;
|
|
608
804
|
this.#setSections(entry.text);
|
|
609
805
|
for (let i = 0; i < this.#sections.length; i++) {
|
|
610
|
-
this.#sections[i]!.annotations =
|
|
806
|
+
this.#sections[i]!.annotations =
|
|
807
|
+
entry.annotations[i]?.map(annotation => this.#cloneAnnotation(annotation)) ?? [];
|
|
611
808
|
}
|
|
612
809
|
this.#deleted = [...entry.deleted];
|
|
613
810
|
this.#tocCursor = Math.min(this.#tocCursor, Math.max(0, this.#toc.length - 1));
|
|
@@ -616,8 +813,21 @@ export class PlanReviewOverlay implements Component {
|
|
|
616
813
|
this.#recomputeFeedback();
|
|
617
814
|
}
|
|
618
815
|
|
|
619
|
-
#
|
|
620
|
-
|
|
816
|
+
#startSectionAnnotate(): void {
|
|
817
|
+
const sectionIndex = this.#toc[this.#tocCursor];
|
|
818
|
+
if (sectionIndex === undefined) return;
|
|
819
|
+
this.#startAnnotate({ sectionIndex, row: null, context: null });
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
#startBodyAnnotate(): void {
|
|
823
|
+
const maxRow = this.#bodyRowAnchors.length - 1;
|
|
824
|
+
if (maxRow < 0) return;
|
|
825
|
+
const topRow = Math.max(0, Math.min(maxRow, Math.floor(this.#scrollView.getScrollOffset())));
|
|
826
|
+
this.#startAnnotate(this.#bodyRowAnchors[topRow]!);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
#startAnnotate(target: BodyRowAnchor | { sectionIndex: number; row: null; context: null }): void {
|
|
830
|
+
this.#annotationTarget = target;
|
|
621
831
|
this.#annotating = true;
|
|
622
832
|
this.#input.setValue("");
|
|
623
833
|
}
|
|
@@ -625,10 +835,23 @@ export class PlanReviewOverlay implements Component {
|
|
|
625
835
|
#submitAnnotation(value: string): void {
|
|
626
836
|
this.#annotating = false;
|
|
627
837
|
const note = value.trim();
|
|
628
|
-
const
|
|
629
|
-
|
|
838
|
+
const target = this.#annotationTarget;
|
|
839
|
+
this.#annotationTarget = undefined;
|
|
840
|
+
const section = target ? this.#sections[target.sectionIndex] : undefined;
|
|
841
|
+
if (note && section && target) {
|
|
630
842
|
this.#pushUndo();
|
|
631
|
-
|
|
843
|
+
section.annotations.push({
|
|
844
|
+
note,
|
|
845
|
+
target:
|
|
846
|
+
target.row === null
|
|
847
|
+
? { kind: "section" }
|
|
848
|
+
: {
|
|
849
|
+
kind: "line",
|
|
850
|
+
row: target.row,
|
|
851
|
+
context: target.context,
|
|
852
|
+
contextTruncated: target.contextTruncated,
|
|
853
|
+
},
|
|
854
|
+
});
|
|
632
855
|
this.#recomputeFeedback();
|
|
633
856
|
}
|
|
634
857
|
this.#input.setValue("");
|
|
@@ -636,11 +859,13 @@ export class PlanReviewOverlay implements Component {
|
|
|
636
859
|
|
|
637
860
|
#exitAnnotate(): void {
|
|
638
861
|
this.#annotating = false;
|
|
862
|
+
this.#annotationTarget = undefined;
|
|
639
863
|
this.#input.setValue("");
|
|
640
864
|
}
|
|
641
865
|
|
|
642
866
|
#recomputeFeedback(): void {
|
|
643
|
-
|
|
867
|
+
this.callbacks.onAnnotationStateChange?.(this.#annotationState());
|
|
868
|
+
const annotated = this.#sections.filter(section => section.annotations.length > 0);
|
|
644
869
|
if (annotated.length === 0 && this.#deleted.length === 0) {
|
|
645
870
|
this.callbacks.onFeedbackChange?.("");
|
|
646
871
|
return;
|
|
@@ -651,8 +876,11 @@ export class PlanReviewOverlay implements Component {
|
|
|
651
876
|
for (const title of this.#deleted) feedback += `- ${title}\n`;
|
|
652
877
|
}
|
|
653
878
|
for (const section of annotated) {
|
|
654
|
-
feedback += `\n## ${section.title}\n`;
|
|
655
|
-
for (const
|
|
879
|
+
feedback += `\n## ${section.title || "Plan preamble"}\n`;
|
|
880
|
+
for (const annotation of section.annotations) {
|
|
881
|
+
if (annotation.target.kind === "line") feedback += `> Line: ${annotation.target.context}\n`;
|
|
882
|
+
feedback += this.#formatAnnotationFeedback(annotation.note);
|
|
883
|
+
}
|
|
656
884
|
}
|
|
657
885
|
this.callbacks.onFeedbackChange?.(feedback);
|
|
658
886
|
}
|
|
@@ -717,7 +945,7 @@ export class PlanReviewOverlay implements Component {
|
|
|
717
945
|
parts.push("↑↓ section", "⏎ open", "a annotate", "d delete", "u undo");
|
|
718
946
|
break;
|
|
719
947
|
case "body":
|
|
720
|
-
parts.push("↑↓ scroll", "⇧ faster", "pgup/pgdn", "g/G ends");
|
|
948
|
+
parts.push("↑↓ scroll", "⇧ faster", "pgup/pgdn", "g/G ends", "a annotate");
|
|
721
949
|
break;
|
|
722
950
|
}
|
|
723
951
|
if (this.callbacks.onCopyPlan) parts.push("c copy");
|
|
@@ -744,35 +972,113 @@ export class PlanReviewOverlay implements Component {
|
|
|
744
972
|
if (maxOffset > 0) this.#scrollView.setScrollOffset(Math.round(this.#scrollProgress * maxOffset));
|
|
745
973
|
}
|
|
746
974
|
|
|
747
|
-
/** Build the concatenated body lines and
|
|
975
|
+
/** Build the concatenated body lines and map each rendered row back to a plan anchor. */
|
|
748
976
|
#buildBody(bodyContentWidth: number): string[] {
|
|
749
977
|
const lines: string[] = [];
|
|
978
|
+
const anchors: BodyRowAnchor[] = [];
|
|
750
979
|
const offsets: number[] = new Array(this.#sections.length);
|
|
751
|
-
for (let
|
|
752
|
-
const section = this.#sections[
|
|
753
|
-
offsets[
|
|
980
|
+
for (let sectionIndex = 0; sectionIndex < this.#sections.length; sectionIndex++) {
|
|
981
|
+
const section = this.#sections[sectionIndex]!;
|
|
982
|
+
offsets[sectionIndex] = lines.length;
|
|
754
983
|
const rendered = section.md.render(bodyContentWidth);
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
984
|
+
const contexts = rendered.map(line => this.#lineContext(line));
|
|
985
|
+
for (let row = 0; row < rendered.length; row++) {
|
|
986
|
+
const context = contexts[row]!;
|
|
987
|
+
const anchor = {
|
|
988
|
+
sectionIndex,
|
|
989
|
+
row,
|
|
990
|
+
context: context.text,
|
|
991
|
+
contextTruncated: context.truncated,
|
|
992
|
+
};
|
|
993
|
+
lines.push(rendered[row]!);
|
|
994
|
+
anchors.push(anchor);
|
|
995
|
+
for (const annotation of section.annotations) {
|
|
996
|
+
const annotationRow =
|
|
997
|
+
annotation.target.kind === "section"
|
|
998
|
+
? 0
|
|
999
|
+
: this.#resolveLineRow(
|
|
1000
|
+
annotation.target.row,
|
|
1001
|
+
{
|
|
1002
|
+
text: annotation.target.context,
|
|
1003
|
+
truncated: annotation.target.contextTruncated,
|
|
1004
|
+
},
|
|
1005
|
+
contexts,
|
|
1006
|
+
);
|
|
1007
|
+
if (annotationRow === row) {
|
|
1008
|
+
this.#appendAnnotationCallout(lines, anchors, annotation.note, anchor, bodyContentWidth);
|
|
765
1009
|
}
|
|
766
1010
|
}
|
|
767
|
-
for (let k = 1; k < rendered.length; k++) lines.push(rendered[k]!);
|
|
768
|
-
} else {
|
|
769
|
-
for (const line of rendered) lines.push(line);
|
|
770
1011
|
}
|
|
771
1012
|
}
|
|
772
1013
|
this.#sectionOffsets = offsets;
|
|
1014
|
+
this.#bodyRowAnchors = anchors;
|
|
773
1015
|
return lines;
|
|
774
1016
|
}
|
|
775
1017
|
|
|
1018
|
+
#lineContext(line: string): LineAnchorContext {
|
|
1019
|
+
const sanitized = sanitizeStatusText(line);
|
|
1020
|
+
const truncated = visibleWidth(sanitized) > MAX_ANNOTATION_CONTEXT_WIDTH;
|
|
1021
|
+
const text = truncateToWidth(sanitized, MAX_ANNOTATION_CONTEXT_WIDTH, Ellipsis.Unicode);
|
|
1022
|
+
return { text: text || "(blank line)", truncated };
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
#resolveLineRow(
|
|
1026
|
+
storedRow: number,
|
|
1027
|
+
storedContext: LineAnchorContext,
|
|
1028
|
+
contexts: readonly LineAnchorContext[],
|
|
1029
|
+
): number {
|
|
1030
|
+
if (contexts.length === 0) return -1;
|
|
1031
|
+
const targetRow = Math.max(0, Math.floor(storedRow));
|
|
1032
|
+
const normalize = (context: LineAnchorContext): string => {
|
|
1033
|
+
const normalized = sanitizeStatusText(context.text).replace(/\s+/g, " ").trim();
|
|
1034
|
+
return context.truncated && normalized.endsWith("…") ? normalized.slice(0, -1) : normalized;
|
|
1035
|
+
};
|
|
1036
|
+
const normalizedStoredContext = normalize(storedContext);
|
|
1037
|
+
if (!normalizedStoredContext) return -1;
|
|
1038
|
+
let best = -1;
|
|
1039
|
+
let bestDistance = Number.POSITIVE_INFINITY;
|
|
1040
|
+
for (let row = 0; row < contexts.length; row++) {
|
|
1041
|
+
const normalizedContext = normalize(contexts[row]!);
|
|
1042
|
+
if (
|
|
1043
|
+
normalizedContext !== normalizedStoredContext &&
|
|
1044
|
+
!normalizedContext.includes(normalizedStoredContext) &&
|
|
1045
|
+
!normalizedStoredContext.includes(normalizedContext)
|
|
1046
|
+
) {
|
|
1047
|
+
continue;
|
|
1048
|
+
}
|
|
1049
|
+
const distance = Math.abs(row - targetRow);
|
|
1050
|
+
if (distance < bestDistance) {
|
|
1051
|
+
best = row;
|
|
1052
|
+
bestDistance = distance;
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return best;
|
|
1056
|
+
}
|
|
1057
|
+
|
|
1058
|
+
#appendAnnotationCallout(
|
|
1059
|
+
lines: string[],
|
|
1060
|
+
anchors: BodyRowAnchor[],
|
|
1061
|
+
note: string,
|
|
1062
|
+
anchor: BodyRowAnchor,
|
|
1063
|
+
bodyContentWidth: number,
|
|
1064
|
+
): void {
|
|
1065
|
+
const noteLines = note.split(/\r?\n/);
|
|
1066
|
+
for (let i = 0; i < noteLines.length; i++) {
|
|
1067
|
+
const prefix =
|
|
1068
|
+
i === 0
|
|
1069
|
+
? `${theme.fg("warning", "▎ ")}${theme.fg("dim", "note: ")}`
|
|
1070
|
+
: `${theme.fg("warning", "▎ ")}${theme.fg("dim", " ")}`;
|
|
1071
|
+
const available = Math.max(0, bodyContentWidth - visibleWidth(prefix));
|
|
1072
|
+
const displayLine = truncateToWidth(
|
|
1073
|
+
replaceTabs(sanitizeText(noteLines[i] ?? "")),
|
|
1074
|
+
available,
|
|
1075
|
+
Ellipsis.Unicode,
|
|
1076
|
+
);
|
|
1077
|
+
lines.push(truncateToWidth(`${prefix}${theme.fg("accent", displayLine)}`, bodyContentWidth));
|
|
1078
|
+
anchors.push(anchor);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
|
|
776
1082
|
#sidebarWidthFor(width: number): number {
|
|
777
1083
|
return Math.max(18, Math.min(30, Math.round(width * 0.24)));
|
|
778
1084
|
}
|
|
@@ -832,9 +1138,18 @@ export class PlanReviewOverlay implements Component {
|
|
|
832
1138
|
|
|
833
1139
|
#renderFooterLines(innerWidth: number): string[] {
|
|
834
1140
|
if (this.#annotating) {
|
|
835
|
-
const
|
|
836
|
-
const
|
|
837
|
-
const
|
|
1141
|
+
const target = this.#annotationTarget;
|
|
1142
|
+
const section = target ? this.#sections[target.sectionIndex] : undefined;
|
|
1143
|
+
const title = sanitizeStatusText(section?.title || "Plan preamble");
|
|
1144
|
+
const location =
|
|
1145
|
+
target?.row === null
|
|
1146
|
+
? `‹${title}›`
|
|
1147
|
+
: `‹${title}› · ${truncateToWidth(target?.context ?? "", Math.max(1, innerWidth - 16), Ellipsis.Unicode)}`;
|
|
1148
|
+
const caption = truncateToWidth(
|
|
1149
|
+
`${theme.fg("dim", "Annotate")} ${theme.fg("accent", location)}`,
|
|
1150
|
+
innerWidth,
|
|
1151
|
+
Ellipsis.Unicode,
|
|
1152
|
+
);
|
|
838
1153
|
const hintParts = ["enter save", "esc cancel"];
|
|
839
1154
|
if (this.#externalEditorLabel) hintParts.push(`${this.#externalEditorLabel} editor`);
|
|
840
1155
|
return [caption, this.#input.render(innerWidth)[0] ?? "", theme.fg("dim", hintParts.join(" · "))];
|
|
@@ -66,18 +66,27 @@ export interface SubmenuSettingDef extends BaseSettingDef {
|
|
|
66
66
|
|
|
67
67
|
export interface TextInputSettingDef extends BaseSettingDef {
|
|
68
68
|
type: "text";
|
|
69
|
+
secret: boolean;
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
export interface ProviderLimitsSettingDef extends BaseSettingDef {
|
|
72
73
|
type: "providerLimits";
|
|
73
74
|
}
|
|
74
75
|
|
|
76
|
+
/** Array-of-enum setting edited as a toggle list; `ordered` lists render positions and support reordering. */
|
|
77
|
+
export interface MultiSelectSettingDef extends BaseSettingDef {
|
|
78
|
+
type: "multiselect";
|
|
79
|
+
options: OptionList;
|
|
80
|
+
ordered: boolean;
|
|
81
|
+
}
|
|
82
|
+
|
|
75
83
|
export type SettingDef =
|
|
76
84
|
| BooleanSettingDef
|
|
77
85
|
| EnumSettingDef
|
|
78
86
|
| SubmenuSettingDef
|
|
79
87
|
| TextInputSettingDef
|
|
80
|
-
| ProviderLimitsSettingDef
|
|
88
|
+
| ProviderLimitsSettingDef
|
|
89
|
+
| MultiSelectSettingDef;
|
|
81
90
|
|
|
82
91
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
83
92
|
// Condition Functions
|
|
@@ -120,6 +129,13 @@ const CONDITIONS: Record<string, () => boolean> = {
|
|
|
120
129
|
return false;
|
|
121
130
|
}
|
|
122
131
|
},
|
|
132
|
+
usageAwareFallbackEnabled: () => {
|
|
133
|
+
try {
|
|
134
|
+
return Settings.instance.get("retry.usageAwareFallback") === true;
|
|
135
|
+
} catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
},
|
|
123
139
|
planModeEnabled: () => {
|
|
124
140
|
try {
|
|
125
141
|
return Settings.instance.get("plan.enabled");
|
|
@@ -176,11 +192,20 @@ function pathToSettingDef(path: SettingPath): SettingDef | null {
|
|
|
176
192
|
if (options) {
|
|
177
193
|
return { ...base, type: "submenu", options };
|
|
178
194
|
}
|
|
179
|
-
return { ...base, type: "text" };
|
|
195
|
+
return { ...base, type: "text", secret: ui.secret === true };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (schemaType === "array") {
|
|
199
|
+
// Arrays without declared options stay config-file only (free-form lists
|
|
200
|
+
// like extension paths have no finite choice set to toggle).
|
|
201
|
+
if (!options || options === "runtime") return null;
|
|
202
|
+
return { ...base, type: "multiselect", options, ordered: ui.ordered === true };
|
|
180
203
|
}
|
|
181
204
|
|
|
182
205
|
if (schemaType === "record") {
|
|
183
|
-
return path === "providers.maxInFlightRequests"
|
|
206
|
+
return path === "providers.maxInFlightRequests"
|
|
207
|
+
? { ...base, type: "providerLimits" }
|
|
208
|
+
: { ...base, type: "text", secret: false };
|
|
184
209
|
}
|
|
185
210
|
|
|
186
211
|
return null;
|