@oh-my-pi/pi-coding-agent 17.0.9 → 17.1.1
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 +78 -0
- package/README.md +1 -1
- package/dist/cli.js +4685 -4614
- 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/model-registry.d.ts +3 -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 +374 -47
- 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 +3 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +13 -0
- package/dist/types/live/attestation.d.ts +2 -0
- package/dist/types/live/controller.d.ts +48 -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 +35 -0
- package/dist/types/live/visualizer.d.ts +30 -0
- package/dist/types/lsp/client.d.ts +16 -1
- package/dist/types/mcp/config-writer.test.d.ts +1 -0
- package/dist/types/mcp/oauth-discovery.d.ts +2 -0
- package/dist/types/mcp/smithery-auth.d.ts +1 -1
- package/dist/types/mcp/smithery-auth.test.d.ts +1 -0
- package/dist/types/memory-backend/tool-names.d.ts +2 -0
- package/dist/types/modes/components/assistant-message.d.ts +1 -0
- package/dist/types/modes/components/custom-message.d.ts +1 -1
- package/dist/types/modes/components/login-dialog.d.ts +2 -2
- package/dist/types/modes/components/message-frame.d.ts +8 -4
- package/dist/types/modes/components/plan-review-overlay.d.ts +26 -0
- package/dist/types/modes/components/session-account-selector.d.ts +11 -0
- package/dist/types/modes/components/settings-defs.d.ts +7 -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/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +6 -0
- package/dist/types/modes/theme/theme.d.ts +1 -1
- package/dist/types/modes/types.d.ts +4 -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 +321 -0
- package/dist/types/session/agent-session.d.ts +135 -753
- package/dist/types/session/async-job-delivery.d.ts +28 -0
- package/dist/types/session/auth-storage.d.ts +1 -1
- 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 +51 -0
- package/dist/types/session/irc-bridge.d.ts +46 -0
- package/dist/types/session/messages.d.ts +24 -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-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 +158 -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/streaming-output.d.ts +8 -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/slash-commands/helpers/session-pin.d.ts +9 -0
- package/dist/types/stt/index.d.ts +0 -2
- package/dist/types/stt/stt-controller.d.ts +7 -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/tiny/title-client.d.ts +10 -0
- package/dist/types/tools/approval.d.ts +1 -0
- package/dist/types/tools/browser/launch.d.ts +5 -0
- package/dist/types/tools/builtin-names.d.ts +1 -1
- package/dist/types/tools/computer/protocol.d.ts +43 -0
- package/dist/types/tools/computer/supervisor.d.ts +32 -0
- package/dist/types/tools/computer/worker-entry.d.ts +1 -0
- package/dist/types/tools/computer/worker.d.ts +15 -0
- package/dist/types/tools/computer-renderer.d.ts +22 -0
- package/dist/types/tools/computer.d.ts +71 -0
- package/dist/types/tools/context.d.ts +2 -0
- package/dist/types/tools/default-renderer.d.ts +21 -0
- 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 +7 -0
- package/dist/types/tools/path-utils.d.ts +8 -0
- 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 +10 -29
- 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/utils/tools-manager.d.ts +1 -2
- 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/types.d.ts +94 -0
- package/package.json +12 -12
- package/scripts/generate-share-viewer.ts +4 -6
- 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 +3 -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/setup-cli.ts +2 -14
- package/src/cli/usage-cli.ts +10 -1
- package/src/cli.ts +8 -0
- package/src/collab/host.ts +3 -2
- package/src/commands/bench.ts +18 -4
- package/src/commands/launch.ts +4 -0
- package/src/config/model-registry.ts +16 -2
- 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 +270 -51
- package/src/config/settings.ts +81 -2
- package/src/dap/client.ts +37 -4
- package/src/edit/index.ts +49 -12
- package/src/eval/executor-base.ts +1 -0
- package/src/eval/js/executor.ts +2 -0
- package/src/eval/py/executor.ts +103 -33
- package/src/exec/bash-executor.ts +90 -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/extensions/wrapper.ts +68 -12
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +7 -1
- package/src/extensibility/plugins/legacy-pi-compat.ts +19 -1
- 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/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/attestation.ts +91 -0
- package/src/live/controller.ts +517 -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 +422 -0
- package/src/live/visualizer.ts +214 -0
- package/src/lsp/client.ts +29 -9
- package/src/lsp/index.ts +19 -4
- package/src/main.ts +8 -1
- package/src/mcp/config-writer.test.ts +43 -0
- package/src/mcp/config-writer.ts +109 -82
- package/src/mcp/oauth-discovery.ts +10 -2
- package/src/mcp/smithery-auth.test.ts +29 -0
- package/src/mcp/smithery-auth.ts +3 -2
- 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 +1 -1
- package/src/modes/components/assistant-message.ts +7 -2
- package/src/modes/components/custom-message.ts +4 -1
- package/src/modes/components/login-dialog.ts +14 -4
- package/src/modes/components/message-frame.ts +14 -8
- package/src/modes/components/plan-review-overlay.ts +350 -35
- package/src/modes/components/session-account-selector.ts +62 -0
- package/src/modes/components/settings-defs.ts +23 -1
- package/src/modes/components/settings-selector.ts +169 -1
- 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.ts +28 -112
- package/src/modes/controllers/command-controller.ts +56 -15
- 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/input-controller.ts +47 -39
- package/src/modes/controllers/live-command-controller.ts +255 -0
- package/src/modes/controllers/mcp-command-controller.ts +10 -1
- package/src/modes/controllers/omfg-controller.ts +44 -40
- package/src/modes/controllers/selector-controller.ts +80 -10
- package/src/modes/controllers/session-focus-controller.ts +6 -1
- package/src/modes/interactive-mode.ts +176 -38
- package/src/modes/runtime-init.ts +2 -0
- package/src/modes/setup-wizard/scenes/web-search.ts +17 -9
- package/src/modes/theme/theme.ts +1 -1
- package/src/modes/types.ts +4 -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/system/computer-safety.md +14 -0
- 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/computer.md +26 -0
- 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 +215 -136
- 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 +343 -0
- package/src/session/agent-session.ts +1940 -12218
- package/src/session/artifacts.ts +6 -5
- package/src/session/async-job-delivery.ts +74 -0
- package/src/session/auth-storage.ts +1 -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 +217 -0
- package/src/session/indexed-session-storage.ts +9 -2
- package/src/session/irc-bridge.ts +203 -0
- package/src/session/messages.ts +266 -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.ts +6 -3
- 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 +0 -46
- 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 +980 -0
- package/src/session/session-workspace.ts +53 -0
- package/src/session/stream-guards.ts +417 -0
- package/src/session/streaming-output.ts +52 -5
- 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 +257 -20
- package/src/slash-commands/helpers/session-pin.ts +44 -0
- package/src/slash-commands/helpers/usage-report.ts +22 -3
- package/src/stt/downloader.ts +0 -2
- package/src/stt/index.ts +0 -2
- package/src/stt/stt-controller.ts +57 -146
- package/src/system-prompt.ts +45 -17
- package/src/task/executor.ts +285 -70
- package/src/task/index.ts +98 -105
- package/src/task/output-manager.ts +25 -3
- package/src/task/persisted-revive.ts +4 -3
- package/src/task/structured-subagent.ts +1 -0
- package/src/tiny/title-client.ts +22 -0
- package/src/tools/approval.ts +55 -10
- package/src/tools/bash-interactive.ts +116 -86
- 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/builtin-names.ts +1 -0
- package/src/tools/computer/protocol.ts +28 -0
- package/src/tools/computer/supervisor.ts +258 -0
- package/src/tools/computer/worker-entry.ts +25 -0
- package/src/tools/computer/worker.ts +135 -0
- package/src/tools/computer-renderer.ts +108 -0
- package/src/tools/computer.ts +433 -0
- package/src/tools/context.ts +2 -0
- package/src/tools/default-renderer.ts +139 -0
- package/src/tools/essential-tools.ts +1 -0
- package/src/tools/image-gen.ts +20 -28
- package/src/tools/image-providers.ts +50 -0
- package/src/tools/index.ts +14 -0
- package/src/tools/path-utils.ts +49 -2
- package/src/tools/read.ts +156 -88
- package/src/tools/renderers.ts +9 -1
- 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 +121 -29
- package/src/tts/streaming-player.ts +82 -283
- package/src/tts/vocalizer.ts +18 -1
- package/src/utils/clipboard.ts +1 -30
- package/src/utils/jj.ts +125 -4
- package/src/utils/lang-from-path.ts +7 -0
- package/src/utils/mac-file-urls.applescript +37 -0
- package/src/utils/title-generator.ts +137 -1
- package/src/utils/tool-choice.ts +14 -0
- package/src/utils/tools-manager.ts +1 -19
- 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/xai.ts +7 -1
- package/src/web/search/types.ts +3 -0
- package/dist/types/stt/recorder.d.ts +0 -30
- package/dist/types/stt/transcriber.d.ts +0 -14
- package/dist/types/stt/wav.d.ts +0 -29
- package/dist/types/tts/player.d.ts +0 -32
- package/src/stt/recorder.ts +0 -551
- package/src/stt/transcriber.ts +0 -60
- package/src/stt/wav.ts +0 -173
- package/src/tts/player.ts +0 -137
|
@@ -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(" · "))];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Container, type SelectItem, SelectList, type SgrMouseEvent, Spacer, TruncatedText } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import { getSelectListTheme, theme } from "../../modes/theme/theme";
|
|
3
|
+
import type { SessionPinAccount } from "../../slash-commands/helpers/session-pin";
|
|
4
|
+
import { DynamicBorder } from "./dynamic-border";
|
|
5
|
+
|
|
6
|
+
const ACCOUNT_SELECTOR_MAX_VISIBLE = 10;
|
|
7
|
+
const ACCOUNT_LIST_ROW_OFFSET = 4;
|
|
8
|
+
|
|
9
|
+
/** Account picker opened by `/session pin` for the current model provider. */
|
|
10
|
+
export class SessionAccountSelectorComponent extends Container {
|
|
11
|
+
#selectList: SelectList;
|
|
12
|
+
|
|
13
|
+
constructor(
|
|
14
|
+
providerName: string,
|
|
15
|
+
accounts: readonly SessionPinAccount[],
|
|
16
|
+
onSelect: (account: SessionPinAccount) => void,
|
|
17
|
+
onCancel: () => void,
|
|
18
|
+
) {
|
|
19
|
+
super();
|
|
20
|
+
const accountsByValue = new Map<string, SessionPinAccount>();
|
|
21
|
+
const items: SelectItem[] = accounts.map(account => {
|
|
22
|
+
const value = String(account.credentialId);
|
|
23
|
+
accountsByValue.set(value, account);
|
|
24
|
+
return {
|
|
25
|
+
value,
|
|
26
|
+
label: account.label,
|
|
27
|
+
description: account.active ? "active for this session" : undefined,
|
|
28
|
+
};
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
this.addChild(new DynamicBorder());
|
|
32
|
+
this.addChild(new Spacer(1));
|
|
33
|
+
this.addChild(new TruncatedText(theme.bold(`Select a ${providerName} account for this session:`)));
|
|
34
|
+
this.addChild(new Spacer(1));
|
|
35
|
+
|
|
36
|
+
this.#selectList = new SelectList(
|
|
37
|
+
items,
|
|
38
|
+
Math.min(Math.max(items.length, 1), ACCOUNT_SELECTOR_MAX_VISIBLE),
|
|
39
|
+
getSelectListTheme(),
|
|
40
|
+
);
|
|
41
|
+
const activeIndex = accounts.findIndex(account => account.active);
|
|
42
|
+
if (activeIndex >= 0) this.#selectList.setSelectedIndex(activeIndex);
|
|
43
|
+
this.#selectList.onSelect = item => {
|
|
44
|
+
const account = accountsByValue.get(item.value);
|
|
45
|
+
if (account) onSelect(account);
|
|
46
|
+
};
|
|
47
|
+
this.#selectList.onCancel = onCancel;
|
|
48
|
+
this.addChild(this.#selectList);
|
|
49
|
+
this.addChild(new Spacer(1));
|
|
50
|
+
this.addChild(new DynamicBorder());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Forward keyboard navigation and cancellation when the wrapper owns focus. */
|
|
54
|
+
handleInput(keyData: string): void {
|
|
55
|
+
this.#selectList.handleInput(keyData);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Route mouse selection through the title rows into the account list. */
|
|
59
|
+
routeMouse(event: SgrMouseEvent, line: number, col: number): void {
|
|
60
|
+
this.#selectList.routeMouse(event, line - ACCOUNT_LIST_ROW_OFFSET, col);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -73,12 +73,20 @@ export interface ProviderLimitsSettingDef extends BaseSettingDef {
|
|
|
73
73
|
type: "providerLimits";
|
|
74
74
|
}
|
|
75
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
|
+
|
|
76
83
|
export type SettingDef =
|
|
77
84
|
| BooleanSettingDef
|
|
78
85
|
| EnumSettingDef
|
|
79
86
|
| SubmenuSettingDef
|
|
80
87
|
| TextInputSettingDef
|
|
81
|
-
| ProviderLimitsSettingDef
|
|
88
|
+
| ProviderLimitsSettingDef
|
|
89
|
+
| MultiSelectSettingDef;
|
|
82
90
|
|
|
83
91
|
// ═══════════════════════════════════════════════════════════════════════════
|
|
84
92
|
// Condition Functions
|
|
@@ -121,6 +129,13 @@ const CONDITIONS: Record<string, () => boolean> = {
|
|
|
121
129
|
return false;
|
|
122
130
|
}
|
|
123
131
|
},
|
|
132
|
+
usageAwareFallbackEnabled: () => {
|
|
133
|
+
try {
|
|
134
|
+
return Settings.instance.get("retry.usageAwareFallback") === true;
|
|
135
|
+
} catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
},
|
|
124
139
|
planModeEnabled: () => {
|
|
125
140
|
try {
|
|
126
141
|
return Settings.instance.get("plan.enabled");
|
|
@@ -180,6 +195,13 @@ function pathToSettingDef(path: SettingPath): SettingDef | null {
|
|
|
180
195
|
return { ...base, type: "text", secret: ui.secret === true };
|
|
181
196
|
}
|
|
182
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 };
|
|
203
|
+
}
|
|
204
|
+
|
|
183
205
|
if (schemaType === "record") {
|
|
184
206
|
return path === "providers.maxInFlightRequests"
|
|
185
207
|
? { ...base, type: "providerLimits" }
|