openhorse 0.2.21 → 0.2.23
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/dist/cli.js +25 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +189 -49
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/parser.d.ts.map +1 -1
- package/dist/commands/parser.js +8 -3
- package/dist/commands/parser.js.map +1 -1
- package/dist/commands/types.d.ts +2 -0
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js.map +1 -1
- package/dist/core/cost-tracker.d.ts +45 -5
- package/dist/core/cost-tracker.d.ts.map +1 -1
- package/dist/core/cost-tracker.js +78 -13
- package/dist/core/cost-tracker.js.map +1 -1
- package/dist/framework/index.d.ts +1 -1
- package/dist/framework/index.d.ts.map +1 -1
- package/dist/framework/index.js.map +1 -1
- package/dist/framework/query.d.ts +25 -0
- package/dist/framework/query.d.ts.map +1 -1
- package/dist/framework/query.js +196 -49
- package/dist/framework/query.js.map +1 -1
- package/dist/framework/store.d.ts +6 -1
- package/dist/framework/store.d.ts.map +1 -1
- package/dist/framework/store.js +10 -1
- package/dist/framework/store.js.map +1 -1
- package/dist/harness/capsule.d.ts +1 -0
- package/dist/harness/capsule.d.ts.map +1 -1
- package/dist/harness/capsule.js +38 -9
- package/dist/harness/capsule.js.map +1 -1
- package/dist/harness/contract.d.ts +2 -0
- package/dist/harness/contract.d.ts.map +1 -1
- package/dist/harness/contract.js +78 -14
- package/dist/harness/contract.js.map +1 -1
- package/dist/harness/index.d.ts +2 -2
- package/dist/harness/index.d.ts.map +1 -1
- package/dist/harness/index.js +4 -1
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/ledger.d.ts.map +1 -1
- package/dist/harness/ledger.js +6 -2
- package/dist/harness/ledger.js.map +1 -1
- package/dist/harness/state.d.ts.map +1 -1
- package/dist/harness/state.js +37 -7
- package/dist/harness/state.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/ink-ui/components/StatusLine.d.ts.map +1 -1
- package/dist/ink-ui/components/StatusLine.js +0 -2
- package/dist/ink-ui/components/StatusLine.js.map +1 -1
- package/dist/ink-ui/screens/ReplScreen.d.ts.map +1 -1
- package/dist/ink-ui/screens/ReplScreen.js +12 -5
- package/dist/ink-ui/screens/ReplScreen.js.map +1 -1
- package/dist/runtime/agent-runtime-controller.d.ts.map +1 -1
- package/dist/runtime/agent-runtime-controller.js +11 -1
- package/dist/runtime/agent-runtime-controller.js.map +1 -1
- package/dist/runtime/aggregate-tool-presenter.d.ts +13 -0
- package/dist/runtime/aggregate-tool-presenter.d.ts.map +1 -0
- package/dist/runtime/aggregate-tool-presenter.js +44 -0
- package/dist/runtime/aggregate-tool-presenter.js.map +1 -0
- package/dist/runtime/chat-controller.d.ts +9 -2
- package/dist/runtime/chat-controller.d.ts.map +1 -1
- package/dist/runtime/chat-controller.js +345 -90
- package/dist/runtime/chat-controller.js.map +1 -1
- package/dist/runtime/rich-text/ansi-parser.d.ts +1 -1
- package/dist/runtime/rich-text/ansi-parser.d.ts.map +1 -1
- package/dist/runtime/rich-text/ansi-parser.js +93 -55
- package/dist/runtime/rich-text/ansi-parser.js.map +1 -1
- package/dist/runtime/rich-text/layout.d.ts +2 -11
- package/dist/runtime/rich-text/layout.d.ts.map +1 -1
- package/dist/runtime/rich-text/layout.js +280 -245
- package/dist/runtime/rich-text/layout.js.map +1 -1
- package/dist/runtime/rich-text/markdown-parser.js +31 -24
- package/dist/runtime/rich-text/markdown-parser.js.map +1 -1
- package/dist/runtime/rich-text/types.d.ts +1 -1
- package/dist/runtime/rich-text/types.d.ts.map +1 -1
- package/dist/runtime/subagents/production.d.ts.map +1 -1
- package/dist/runtime/subagents/production.js +28 -3
- package/dist/runtime/subagents/production.js.map +1 -1
- package/dist/runtime/subagents/result-parser.d.ts.map +1 -1
- package/dist/runtime/subagents/result-parser.js +54 -40
- package/dist/runtime/subagents/result-parser.js.map +1 -1
- package/dist/runtime/subagents/types.d.ts +2 -0
- package/dist/runtime/subagents/types.d.ts.map +1 -1
- package/dist/runtime/subagents/types.js +5 -1
- package/dist/runtime/subagents/types.js.map +1 -1
- package/dist/runtime/tool-detail-repository.d.ts +45 -0
- package/dist/runtime/tool-detail-repository.d.ts.map +1 -0
- package/dist/runtime/tool-detail-repository.js +131 -0
- package/dist/runtime/tool-detail-repository.js.map +1 -0
- package/dist/runtime/tool-output-presentation.d.ts +89 -0
- package/dist/runtime/tool-output-presentation.d.ts.map +1 -0
- package/dist/runtime/tool-output-presentation.js +329 -0
- package/dist/runtime/tool-output-presentation.js.map +1 -0
- package/dist/runtime/ui-events.d.ts +17 -0
- package/dist/runtime/ui-events.d.ts.map +1 -1
- package/dist/runtime/ui-events.js.map +1 -1
- package/dist/runtime/ui-view-model.d.ts +9 -0
- package/dist/runtime/ui-view-model.d.ts.map +1 -1
- package/dist/runtime/ui-view-model.js +59 -37
- package/dist/runtime/ui-view-model.js.map +1 -1
- package/dist/services/compact/auto-compact.d.ts +15 -2
- package/dist/services/compact/auto-compact.d.ts.map +1 -1
- package/dist/services/compact/auto-compact.js +56 -24
- package/dist/services/compact/auto-compact.js.map +1 -1
- package/dist/services/compact/compact.d.ts +2 -0
- package/dist/services/compact/compact.d.ts.map +1 -1
- package/dist/services/compact/compact.js +22 -5
- package/dist/services/compact/compact.js.map +1 -1
- package/dist/services/compact/coordinator.d.ts +26 -0
- package/dist/services/compact/coordinator.d.ts.map +1 -0
- package/dist/services/compact/coordinator.js +44 -0
- package/dist/services/compact/coordinator.js.map +1 -0
- package/dist/services/compact/index.d.ts +1 -0
- package/dist/services/compact/index.d.ts.map +1 -1
- package/dist/services/compact/index.js +3 -1
- package/dist/services/compact/index.js.map +1 -1
- package/dist/services/compact/summary-generator.d.ts +6 -2
- package/dist/services/compact/summary-generator.d.ts.map +1 -1
- package/dist/services/compact/summary-generator.js +73 -31
- package/dist/services/compact/summary-generator.js.map +1 -1
- package/dist/services/config-dir.d.ts +11 -1
- package/dist/services/config-dir.d.ts.map +1 -1
- package/dist/services/config-dir.js +19 -2
- package/dist/services/config-dir.js.map +1 -1
- package/dist/services/config.d.ts +4 -2
- package/dist/services/config.d.ts.map +1 -1
- package/dist/services/config.js +40 -0
- package/dist/services/config.js.map +1 -1
- package/dist/services/doctor.d.ts +2 -0
- package/dist/services/doctor.d.ts.map +1 -1
- package/dist/services/doctor.js +8 -1
- package/dist/services/doctor.js.map +1 -1
- package/dist/services/global-config.d.ts +10 -0
- package/dist/services/global-config.d.ts.map +1 -1
- package/dist/services/global-config.js.map +1 -1
- package/dist/services/llm.d.ts +21 -4
- package/dist/services/llm.d.ts.map +1 -1
- package/dist/services/llm.js +66 -8
- package/dist/services/llm.js.map +1 -1
- package/dist/services/model-context.d.ts +36 -0
- package/dist/services/model-context.d.ts.map +1 -1
- package/dist/services/model-context.js +245 -32
- package/dist/services/model-context.js.map +1 -1
- package/dist/services/redaction.js +1 -1
- package/dist/services/redaction.js.map +1 -1
- package/dist/services/session-storage.d.ts +42 -1
- package/dist/services/session-storage.d.ts.map +1 -1
- package/dist/services/session-storage.js +175 -54
- package/dist/services/session-storage.js.map +1 -1
- package/dist/services/storage-maintenance.d.ts.map +1 -1
- package/dist/services/storage-maintenance.js +3 -2
- package/dist/services/storage-maintenance.js.map +1 -1
- package/dist/services/usage-state.d.ts +55 -5
- package/dist/services/usage-state.d.ts.map +1 -1
- package/dist/services/usage-state.js +185 -59
- package/dist/services/usage-state.js.map +1 -1
- package/dist/services/verification-profile.d.ts.map +1 -1
- package/dist/services/verification-profile.js +3 -2
- package/dist/services/verification-profile.js.map +1 -1
- package/dist/services/workspace-diff.d.ts.map +1 -1
- package/dist/services/workspace-diff.js +34 -3
- package/dist/services/workspace-diff.js.map +1 -1
- package/dist/services/yaml-config.js +1 -1
- package/dist/skills/runtime.d.ts +9 -5
- package/dist/skills/runtime.d.ts.map +1 -1
- package/dist/skills/runtime.js +157 -12
- package/dist/skills/runtime.js.map +1 -1
- package/dist/terminal-ui/editor.d.ts.map +1 -1
- package/dist/terminal-ui/editor.js +14 -3
- package/dist/terminal-ui/editor.js.map +1 -1
- package/dist/terminal-ui/launch.d.ts +21 -0
- package/dist/terminal-ui/launch.d.ts.map +1 -1
- package/dist/terminal-ui/launch.js +203 -45
- package/dist/terminal-ui/launch.js.map +1 -1
- package/dist/terminal-ui/output-queue.d.ts +43 -0
- package/dist/terminal-ui/output-queue.d.ts.map +1 -0
- package/dist/terminal-ui/output-queue.js +139 -0
- package/dist/terminal-ui/output-queue.js.map +1 -0
- package/dist/terminal-ui/raw-editor.d.ts +42 -0
- package/dist/terminal-ui/raw-editor.d.ts.map +1 -1
- package/dist/terminal-ui/raw-editor.js +337 -41
- package/dist/terminal-ui/raw-editor.js.map +1 -1
- package/dist/tools/bash_security.d.ts.map +1 -1
- package/dist/tools/bash_security.js +14 -5
- package/dist/tools/bash_security.js.map +1 -1
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +176 -73
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/plan.d.ts.map +1 -1
- package/dist/tools/plan.js +7 -0
- package/dist/tools/plan.js.map +1 -1
- package/dist/tools/web.d.ts +10 -0
- package/dist/tools/web.d.ts.map +1 -1
- package/dist/tools/web.js +81 -1
- package/dist/tools/web.js.map +1 -1
- package/dist/tui-core/input-parser.d.ts +9 -2
- package/dist/tui-core/input-parser.d.ts.map +1 -1
- package/dist/tui-core/input-parser.js +57 -3
- package/dist/tui-core/input-parser.js.map +1 -1
- package/dist/tui-core/style.d.ts +16 -0
- package/dist/tui-core/style.d.ts.map +1 -1
- package/dist/tui-core/style.js +22 -1
- package/dist/tui-core/style.js.map +1 -1
- package/dist/tui-ui/inline-surface.d.ts +69 -12
- package/dist/tui-ui/inline-surface.d.ts.map +1 -1
- package/dist/tui-ui/inline-surface.js +366 -94
- package/dist/tui-ui/inline-surface.js.map +1 -1
- package/dist/tui-ui/input-ownership.d.ts +46 -0
- package/dist/tui-ui/input-ownership.d.ts.map +1 -0
- package/dist/tui-ui/input-ownership.js +74 -0
- package/dist/tui-ui/input-ownership.js.map +1 -0
- package/dist/tui-ui/launch.d.ts +2 -0
- package/dist/tui-ui/launch.d.ts.map +1 -1
- package/dist/tui-ui/launch.js +289 -43
- package/dist/tui-ui/launch.js.map +1 -1
- package/dist/tui-ui/layout.d.ts +52 -2
- package/dist/tui-ui/layout.d.ts.map +1 -1
- package/dist/tui-ui/layout.js +230 -76
- package/dist/tui-ui/layout.js.map +1 -1
- package/dist/tui-ui/runner.d.ts +101 -8
- package/dist/tui-ui/runner.d.ts.map +1 -1
- package/dist/tui-ui/runner.js +843 -51
- package/dist/tui-ui/runner.js.map +1 -1
- package/dist/tui-ui/state.d.ts +70 -4
- package/dist/tui-ui/state.d.ts.map +1 -1
- package/dist/tui-ui/state.js +251 -20
- package/dist/tui-ui/state.js.map +1 -1
- package/dist/tui-ui/theme-profile.d.ts +25 -0
- package/dist/tui-ui/theme-profile.d.ts.map +1 -0
- package/dist/tui-ui/theme-profile.js +130 -0
- package/dist/tui-ui/theme-profile.js.map +1 -0
- package/dist/tui-ui/theme.d.ts +13 -0
- package/dist/tui-ui/theme.d.ts.map +1 -0
- package/dist/tui-ui/theme.js +20 -0
- package/dist/tui-ui/theme.js.map +1 -0
- package/dist/tui-ui/transcript-cache.d.ts +5 -3
- package/dist/tui-ui/transcript-cache.d.ts.map +1 -1
- package/dist/tui-ui/transcript-cache.js +16 -9
- package/dist/tui-ui/transcript-cache.js.map +1 -1
- package/dist/tui-ui/transcript-inspector-layout.d.ts +9 -0
- package/dist/tui-ui/transcript-inspector-layout.d.ts.map +1 -0
- package/dist/tui-ui/transcript-inspector-layout.js +97 -0
- package/dist/tui-ui/transcript-inspector-layout.js.map +1 -0
- package/dist/tui-ui/transcript-inspector-surface.d.ts +18 -0
- package/dist/tui-ui/transcript-inspector-surface.d.ts.map +1 -0
- package/dist/tui-ui/transcript-inspector-surface.js +88 -0
- package/dist/tui-ui/transcript-inspector-surface.js.map +1 -0
- package/dist/tui-ui/transcript-inspector.d.ts +35 -0
- package/dist/tui-ui/transcript-inspector.d.ts.map +1 -0
- package/dist/tui-ui/transcript-inspector.js +128 -0
- package/dist/tui-ui/transcript-inspector.js.map +1 -0
- package/dist/tui-ui/transcript-layout.d.ts +22 -0
- package/dist/tui-ui/transcript-layout.d.ts.map +1 -0
- package/dist/tui-ui/transcript-layout.js +439 -0
- package/dist/tui-ui/transcript-layout.js.map +1 -0
- package/dist/ui/status-bar.d.ts +1 -1
- package/dist/ui/status-bar.d.ts.map +1 -1
- package/dist/ui/status-bar.js +2 -15
- package/dist/ui/status-bar.js.map +1 -1
- package/dist/ui-v2/components/shell.d.ts.map +1 -1
- package/dist/ui-v2/components/shell.js +0 -8
- package/dist/ui-v2/components/shell.js.map +1 -1
- package/package.json +1 -1
package/dist/tui-ui/runner.js
CHANGED
|
@@ -3,12 +3,21 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.TuiRunner = void 0;
|
|
4
4
|
const frame_1 = require("../tui-core/frame");
|
|
5
5
|
const input_parser_1 = require("../tui-core/input-parser");
|
|
6
|
-
const terminal_writer_1 = require("../tui-core/terminal-writer");
|
|
7
6
|
const commands_1 = require("../commands");
|
|
8
7
|
const layout_1 = require("./layout");
|
|
9
8
|
const pickers_1 = require("./pickers");
|
|
10
9
|
const render_scheduler_1 = require("./render-scheduler");
|
|
11
10
|
const state_1 = require("./state");
|
|
11
|
+
const input_ownership_1 = require("./input-ownership");
|
|
12
|
+
const transcript_inspector_1 = require("./transcript-inspector");
|
|
13
|
+
const transcript_inspector_layout_1 = require("./transcript-inspector-layout");
|
|
14
|
+
const transcript_cache_1 = require("./transcript-cache");
|
|
15
|
+
const transcript_layout_1 = require("./transcript-layout");
|
|
16
|
+
const theme_1 = require("./theme");
|
|
17
|
+
const history_1 = require("../runtime/composer/history");
|
|
18
|
+
const promises_1 = require("fs/promises");
|
|
19
|
+
const os_1 = require("os");
|
|
20
|
+
const path_1 = require("path");
|
|
12
21
|
/** Actions that should use 'stream' priority (FPS-capped). */
|
|
13
22
|
const STREAM_ACTIONS = new Set([
|
|
14
23
|
'updateTranscript',
|
|
@@ -22,21 +31,40 @@ class TuiRunner {
|
|
|
22
31
|
constructor(options) {
|
|
23
32
|
this.options = options;
|
|
24
33
|
this.parser = new input_parser_1.TuiInputParser();
|
|
34
|
+
this.inputOwnership = new input_ownership_1.TuiInputOwnershipController();
|
|
35
|
+
this.transcriptCache = new transcript_cache_1.TranscriptLayoutCache();
|
|
36
|
+
this.history = history_1.initialHistoryState;
|
|
37
|
+
this.surfaceFailed = false;
|
|
38
|
+
this.inspectorReady = false;
|
|
39
|
+
this.inspectorSearchActive = false;
|
|
40
|
+
this.modalTransition = Promise.resolve();
|
|
41
|
+
this.commitSequence = 0;
|
|
42
|
+
this.commitInFlight = false;
|
|
43
|
+
this.commitMismatchRetries = 0;
|
|
25
44
|
this.state = state_1.initialTuiUiState;
|
|
26
45
|
this.lastFrame = null;
|
|
27
|
-
this.
|
|
28
|
-
this.
|
|
46
|
+
this.surfaceResizePending = false;
|
|
47
|
+
this.resizeEpoch = 0;
|
|
48
|
+
this.surfaceResizeGeneration = 0;
|
|
49
|
+
this.counters = {
|
|
50
|
+
layoutCount: 0,
|
|
51
|
+
paintCount: 0,
|
|
52
|
+
changedRows: 0,
|
|
53
|
+
commitCount: 0,
|
|
54
|
+
};
|
|
29
55
|
this.width = options.width;
|
|
30
56
|
this.height = options.height;
|
|
31
|
-
this.
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
57
|
+
this.surface = options.surface ?? null;
|
|
58
|
+
this.theme = (0, theme_1.resolveTuiTheme)(options.theme);
|
|
59
|
+
this.themeId = options.themeId ?? theme_1.DEFAULT_TUI_THEME_ID;
|
|
60
|
+
this.inspectorSurface = options.inspectorSurface ?? null;
|
|
61
|
+
this.inspectorController = options.detailRepository
|
|
62
|
+
? new transcript_inspector_1.TranscriptInspectorController(options.detailRepository, options.cwd ?? process.cwd())
|
|
63
|
+
: null;
|
|
64
|
+
this.scheduler = (0, render_scheduler_1.createTuiRenderScheduler)(() => this.renderLive(), options.schedulerDeps);
|
|
38
65
|
this.events = (0, state_1.createTuiUiEventSink)(action => this.dispatch(action));
|
|
39
|
-
|
|
66
|
+
// Initial render: paint the live region immediately.
|
|
67
|
+
this.renderLive();
|
|
40
68
|
}
|
|
41
69
|
getState() {
|
|
42
70
|
return this.state;
|
|
@@ -44,61 +72,285 @@ class TuiRunner {
|
|
|
44
72
|
getLastFrame() {
|
|
45
73
|
return this.lastFrame;
|
|
46
74
|
}
|
|
47
|
-
getLastRenderResult() {
|
|
48
|
-
return this.lastRenderResult;
|
|
49
|
-
}
|
|
50
75
|
getVisibleRows() {
|
|
51
76
|
return this.lastFrame ? (0, frame_1.renderFrameRows)(this.lastFrame) : [];
|
|
52
77
|
}
|
|
78
|
+
/** Get the scheduler for external lifecycle management (flush, stop). */
|
|
79
|
+
getScheduler() {
|
|
80
|
+
return this.scheduler;
|
|
81
|
+
}
|
|
82
|
+
/** Stop stale-width paints as soon as the terminal reports SIGWINCH. */
|
|
83
|
+
beginResize(width = this.width) {
|
|
84
|
+
if (this.state.inspector)
|
|
85
|
+
return;
|
|
86
|
+
if (!this.surface || this.surfaceFailed)
|
|
87
|
+
return;
|
|
88
|
+
this.surfaceResizePending = true;
|
|
89
|
+
this.resizeEpoch += 1;
|
|
90
|
+
this.surfaceResizeGeneration = this.surface.beginResize(width);
|
|
91
|
+
}
|
|
53
92
|
resize(width, height) {
|
|
93
|
+
if (this.state.inspector) {
|
|
94
|
+
this.width = width;
|
|
95
|
+
this.height = height;
|
|
96
|
+
this.paintInspector();
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
this.beginResize(width);
|
|
100
|
+
const resizeEpoch = this.resizeEpoch;
|
|
101
|
+
const surfaceResizeGeneration = this.surfaceResizeGeneration;
|
|
54
102
|
this.width = width;
|
|
55
103
|
this.height = height;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
this.
|
|
104
|
+
// Invalidate transcript cache on resize so committed entries are re-laid-out
|
|
105
|
+
// at the new width.
|
|
106
|
+
this.transcriptCache.invalidate(this.state.transcriptGeneration, this.transcriptWidth, this.themeId);
|
|
107
|
+
if (this.surface && !this.surfaceFailed) {
|
|
108
|
+
// Surface resize is async (serialized FIFO); fire-and-forget is safe
|
|
109
|
+
// because subsequent renders go through the surface queue too.
|
|
110
|
+
void this.surface
|
|
111
|
+
.resize(width, height, () => {
|
|
112
|
+
const frame = this.buildLiveFrame(width);
|
|
113
|
+
this.lastFrame = frame;
|
|
114
|
+
this.counters.layoutCount += 1;
|
|
115
|
+
this.counters.paintCount += 1;
|
|
116
|
+
return frame;
|
|
117
|
+
}, surfaceResizeGeneration)
|
|
118
|
+
.then(() => {
|
|
119
|
+
if (resizeEpoch !== this.resizeEpoch)
|
|
120
|
+
return;
|
|
121
|
+
this.surfaceResizePending = false;
|
|
122
|
+
this.tryCommit(this.state, true);
|
|
123
|
+
this.scheduler.request('immediate');
|
|
124
|
+
this.scheduler.flush();
|
|
125
|
+
})
|
|
126
|
+
.catch(error => this.handleSurfaceError(error));
|
|
127
|
+
return;
|
|
63
128
|
}
|
|
129
|
+
this.scheduler.request('immediate');
|
|
130
|
+
this.scheduler.flush();
|
|
64
131
|
}
|
|
65
132
|
dispatch(action) {
|
|
133
|
+
const prevState = this.state;
|
|
66
134
|
this.state = (0, state_1.tuiUiReducer)(this.state, action);
|
|
67
|
-
if (
|
|
68
|
-
|
|
69
|
-
this.scheduler.request(priority);
|
|
70
|
-
}
|
|
71
|
-
else {
|
|
72
|
-
this.render();
|
|
135
|
+
if (action.type === 'setToolOutputViewMode' && action.mode !== prevState.toolOutputViewMode) {
|
|
136
|
+
this.transcriptCache.clear();
|
|
73
137
|
}
|
|
138
|
+
// Check if any transcript entries became committable (finalized).
|
|
139
|
+
this.tryCommit(prevState);
|
|
140
|
+
const priority = STREAM_ACTIONS.has(action.type) ? 'stream' : 'immediate';
|
|
141
|
+
this.scheduler.request(priority);
|
|
74
142
|
}
|
|
75
143
|
feedInput(chunk) {
|
|
76
|
-
const
|
|
77
|
-
if ((0, input_parser_1.isLikelyUnbracketedMultilinePaste)(raw)) {
|
|
78
|
-
const event = { type: 'paste', value: (0, input_parser_1.normalizePastedText)(raw) };
|
|
79
|
-
this.applyInputEvent(event);
|
|
80
|
-
return [event];
|
|
81
|
-
}
|
|
82
|
-
const events = this.parser.feed(chunk);
|
|
144
|
+
const events = this.parser.feed(chunk, { detectUnbracketedMultilinePaste: true });
|
|
83
145
|
for (const event of events) {
|
|
84
146
|
this.applyInputEvent(event);
|
|
85
147
|
}
|
|
86
148
|
return events;
|
|
87
149
|
}
|
|
88
|
-
|
|
150
|
+
/**
|
|
151
|
+
* Render the full frame (legacy path for tests without surface).
|
|
152
|
+
* Uses renderTuiUiFrame which includes both static and live transcript.
|
|
153
|
+
*/
|
|
154
|
+
renderFullFrame() {
|
|
89
155
|
this.counters.layoutCount += 1;
|
|
90
156
|
const frame = (0, layout_1.renderTuiUiFrame)(this.state, {
|
|
91
157
|
width: this.width,
|
|
92
158
|
height: this.height,
|
|
159
|
+
...this.transcriptLayoutOptions(),
|
|
93
160
|
});
|
|
94
|
-
const result = this.writer.render(frame);
|
|
95
161
|
this.lastFrame = frame;
|
|
96
|
-
this.lastRenderResult = result;
|
|
97
162
|
this.counters.paintCount += 1;
|
|
98
|
-
|
|
99
|
-
|
|
163
|
+
return frame;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Render the live region via InlineTerminalSurface.
|
|
167
|
+
* When surface is available, only ephemeral content (live transcript, overlay,
|
|
168
|
+
* status, prompt) is painted into the live region.
|
|
169
|
+
* When no surface (test mode), renders the full frame including committed
|
|
170
|
+
* transcript so tests can inspect complete content via getLastFrame().
|
|
171
|
+
*/
|
|
172
|
+
renderLive() {
|
|
173
|
+
if (this.state.inspector) {
|
|
174
|
+
this.paintInspector();
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (this.surface && !this.surfaceFailed) {
|
|
178
|
+
if (this.surfaceResizePending)
|
|
179
|
+
return;
|
|
180
|
+
this.counters.layoutCount += 1;
|
|
181
|
+
// Production path: render only the live region for inline surface.
|
|
182
|
+
const frame = this.buildLiveFrame(this.width);
|
|
183
|
+
this.lastFrame = frame;
|
|
184
|
+
this.counters.paintCount += 1;
|
|
185
|
+
// Surface renderLive is async (serialized FIFO), but for production use
|
|
186
|
+
// we fire-and-forget because the next render will also go through the queue.
|
|
187
|
+
void this.surface.renderLive(frame).catch(error => this.handleSurfaceError(error));
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
this.counters.layoutCount += 1;
|
|
191
|
+
// Test path (no surface): render the full frame so getLastFrame() /
|
|
192
|
+
// getVisibleRows() return complete content including committed transcript.
|
|
193
|
+
const frame = (0, layout_1.renderTuiUiFrame)(this.state, {
|
|
194
|
+
width: this.width,
|
|
195
|
+
height: this.height,
|
|
196
|
+
...this.transcriptLayoutOptions(),
|
|
197
|
+
});
|
|
198
|
+
this.lastFrame = frame;
|
|
199
|
+
this.counters.paintCount += 1;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
/** Build a compact live-region frame that grows only for active content. */
|
|
203
|
+
buildLiveFrame(terminalWidth) {
|
|
204
|
+
const width = this.surface
|
|
205
|
+
? Math.max(1, Math.floor(terminalWidth) - 1)
|
|
206
|
+
: Math.max(1, Math.floor(terminalWidth));
|
|
207
|
+
const height = this.surface
|
|
208
|
+
? (0, layout_1.measureTuiLiveFrameHeight)(this.state, width, this.surface.getLiveBandRows(), this.transcriptLayoutOptions())
|
|
209
|
+
: this.height - 1;
|
|
210
|
+
return (0, layout_1.renderTuiLiveFrame)(this.state, {
|
|
211
|
+
width,
|
|
212
|
+
height,
|
|
213
|
+
...this.transcriptLayoutOptions(),
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Try to commit any newly-finalized transcript entries to scrollback.
|
|
218
|
+
* Called after each dispatch. Only commits entries that are committable
|
|
219
|
+
* but not yet queued.
|
|
220
|
+
*/
|
|
221
|
+
tryCommit(prevState, force = false) {
|
|
222
|
+
if (!this.surface || this.surfaceResizePending || this.state.inspector || this.commitInFlight)
|
|
223
|
+
return;
|
|
224
|
+
const prevCommittable = prevState.committableTranscriptCount;
|
|
225
|
+
const currCommittable = this.state.committableTranscriptCount;
|
|
226
|
+
const generationChanged = this.state.transcriptGeneration !== prevState.transcriptGeneration;
|
|
227
|
+
if (!force && !generationChanged && currCommittable <= prevCommittable)
|
|
228
|
+
return;
|
|
229
|
+
if (currCommittable <= this.state.queuedTranscriptCount)
|
|
230
|
+
return;
|
|
231
|
+
// Gather entries to commit: from queued boundary to new committable boundary.
|
|
232
|
+
const entriesToCommit = (0, state_1.pendingCommitRecords)(this.state);
|
|
233
|
+
if (entriesToCommit.length === 0)
|
|
234
|
+
return;
|
|
235
|
+
// Advance the queued boundary so we don't double-commit.
|
|
236
|
+
this.state = (0, state_1.markTranscriptQueued)(this.state, entriesToCommit.length);
|
|
237
|
+
// Build the commit batch.
|
|
238
|
+
const committedEntries = entriesToCommit.map(entry => ({
|
|
239
|
+
displayKey: entry.id,
|
|
240
|
+
rows: this.layoutTranscriptRecord(entry, this.transcriptWidth),
|
|
241
|
+
}));
|
|
242
|
+
const batch = {
|
|
243
|
+
batchId: `tui-commit-${++this.commitSequence}`,
|
|
244
|
+
generation: this.state.transcriptGeneration,
|
|
245
|
+
reason: 'finalize',
|
|
246
|
+
entries: committedEntries,
|
|
247
|
+
};
|
|
248
|
+
// The LiveFrameProvider ensures the live frame is rebuilt after commit
|
|
249
|
+
// with the latest state (where committed entries are no longer in the
|
|
250
|
+
// live region).
|
|
251
|
+
const generation = batch.generation;
|
|
252
|
+
const getLatestLiveFrame = () => this.buildLiveFrame(this.width);
|
|
253
|
+
this.commitInFlight = true;
|
|
254
|
+
let continueCommit = true;
|
|
255
|
+
// Surface commit is async (serialized FIFO). Fire-and-forget: the
|
|
256
|
+
// commit's internal getLatestLiveFrame already rebuilds the live region,
|
|
257
|
+
// so no additional scheduler paint is needed here.
|
|
258
|
+
void this.surface
|
|
259
|
+
.commit(batch, getLatestLiveFrame)
|
|
260
|
+
.then(result => {
|
|
261
|
+
if (result.batchId !== batch.batchId ||
|
|
262
|
+
result.generation !== generation ||
|
|
263
|
+
result.committedEntries !== entriesToCommit.length) {
|
|
264
|
+
if (this.state.transcriptGeneration === generation) {
|
|
265
|
+
this.state = {
|
|
266
|
+
...this.state,
|
|
267
|
+
queuedTranscriptCount: Math.max(0, this.state.queuedTranscriptCount - entriesToCommit.length),
|
|
268
|
+
};
|
|
269
|
+
this.commitMismatchRetries += 1;
|
|
270
|
+
if (this.commitMismatchRetries > 1) {
|
|
271
|
+
continueCommit = false;
|
|
272
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, {
|
|
273
|
+
type: 'setStatus',
|
|
274
|
+
message: 'TUI transcript acknowledgement mismatch; retained content for retry.',
|
|
275
|
+
});
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
void this.surface?.forceRedraw(this.buildLiveFrame(this.width));
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
const acknowledgement = (0, state_1.acknowledgeTranscriptCommit)(this.state, {
|
|
282
|
+
generation,
|
|
283
|
+
recordIds: result.displayKeys ?? [],
|
|
284
|
+
});
|
|
285
|
+
if (!acknowledgement.accepted) {
|
|
286
|
+
if (this.state.transcriptGeneration === generation) {
|
|
287
|
+
this.state = {
|
|
288
|
+
...this.state,
|
|
289
|
+
queuedTranscriptCount: Math.max(0, this.state.queuedTranscriptCount - entriesToCommit.length),
|
|
290
|
+
};
|
|
291
|
+
this.commitMismatchRetries += 1;
|
|
292
|
+
if (this.commitMismatchRetries > 1) {
|
|
293
|
+
continueCommit = false;
|
|
294
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, {
|
|
295
|
+
type: 'setStatus',
|
|
296
|
+
message: 'TUI transcript acknowledgement mismatch; retained content for retry.',
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
void this.surface?.forceRedraw(this.buildLiveFrame(this.width));
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
this.state = acknowledgement.state;
|
|
304
|
+
this.commitMismatchRetries = 0;
|
|
305
|
+
this.counters.commitCount += 1;
|
|
306
|
+
})
|
|
307
|
+
.catch(error => this.handleSurfaceError(error))
|
|
308
|
+
.finally(() => {
|
|
309
|
+
this.commitInFlight = false;
|
|
310
|
+
if (!this.surfaceFailed && continueCommit)
|
|
311
|
+
this.tryCommit(this.state, true);
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
/** Layout a transcript record once per revision/width/theme combination. */
|
|
315
|
+
layoutTranscriptRecord(entry, width) {
|
|
316
|
+
const cached = this.transcriptCache.get(entry.id, entry.revision, this.state.transcriptGeneration, width, this.themeId);
|
|
317
|
+
if (cached)
|
|
318
|
+
return cached;
|
|
319
|
+
const rows = (0, transcript_layout_1.layoutTranscriptEntry)(entry, {
|
|
320
|
+
width,
|
|
321
|
+
theme: this.theme,
|
|
322
|
+
toolOutputMode: this.state.toolOutputViewMode,
|
|
323
|
+
});
|
|
324
|
+
this.transcriptCache.set(entry.id, entry.revision, rows, this.state.transcriptGeneration, width, this.themeId);
|
|
325
|
+
return rows;
|
|
326
|
+
}
|
|
327
|
+
get transcriptWidth() {
|
|
328
|
+
return this.surface ? Math.max(1, this.width - 1) : Math.max(1, this.width);
|
|
329
|
+
}
|
|
330
|
+
transcriptLayoutOptions() {
|
|
331
|
+
return {
|
|
332
|
+
transcriptWidth: this.transcriptWidth,
|
|
333
|
+
theme: this.theme,
|
|
334
|
+
toolOutputMode: this.state.toolOutputViewMode,
|
|
335
|
+
layoutTranscriptRecord: (entry, width) => this.layoutTranscriptRecord(entry, width),
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
handleSurfaceError(error) {
|
|
339
|
+
if (this.surfaceFailed)
|
|
340
|
+
return;
|
|
341
|
+
this.surfaceFailed = true;
|
|
342
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
343
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, {
|
|
344
|
+
type: 'setStatus',
|
|
345
|
+
message: `TUI output error: ${message}`,
|
|
346
|
+
});
|
|
347
|
+
this.options.onSurfaceError?.(error);
|
|
100
348
|
}
|
|
101
349
|
applyInputEvent(event) {
|
|
350
|
+
if (this.state.inspector) {
|
|
351
|
+
this.applyInspectorInput(event);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
102
354
|
if (this.state.overlay?.type === 'permission') {
|
|
103
355
|
if (event.type === 'text') {
|
|
104
356
|
const answer = event.value.trim().toLowerCase();
|
|
@@ -143,10 +395,16 @@ class TuiRunner {
|
|
|
143
395
|
this.dispatch({ type: 'moveOverlaySelection', delta: 1 });
|
|
144
396
|
return;
|
|
145
397
|
case 'pageup':
|
|
146
|
-
this.dispatch({
|
|
398
|
+
this.dispatch({
|
|
399
|
+
type: 'moveOverlaySelection',
|
|
400
|
+
delta: -Math.max(1, overlay.request.maxVisibleItems ?? 10),
|
|
401
|
+
});
|
|
147
402
|
return;
|
|
148
403
|
case 'pagedown':
|
|
149
|
-
this.dispatch({
|
|
404
|
+
this.dispatch({
|
|
405
|
+
type: 'moveOverlaySelection',
|
|
406
|
+
delta: Math.max(1, overlay.request.maxVisibleItems ?? 10),
|
|
407
|
+
});
|
|
150
408
|
return;
|
|
151
409
|
case 'enter':
|
|
152
410
|
this.submitPrompt({ allowEmpty: true });
|
|
@@ -192,6 +450,11 @@ class TuiRunner {
|
|
|
192
450
|
this.completeCommand(overlay.items[overlay.selectedIndex], false);
|
|
193
451
|
return;
|
|
194
452
|
case 'enter':
|
|
453
|
+
if (/^\/(?:tool-output|redraw)(?:\s|$)/u.test(this.state.prompt.value.trim())) {
|
|
454
|
+
this.dispatch({ type: 'closeOverlay' });
|
|
455
|
+
this.submitPrompt();
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
195
458
|
this.completeCommand(overlay.items[overlay.selectedIndex], true);
|
|
196
459
|
return;
|
|
197
460
|
case 'escape':
|
|
@@ -250,9 +513,18 @@ class TuiRunner {
|
|
|
250
513
|
return;
|
|
251
514
|
}
|
|
252
515
|
switch (key) {
|
|
516
|
+
case 'ctrl+o':
|
|
517
|
+
this.openToolInspector();
|
|
518
|
+
return;
|
|
519
|
+
case 'ctrl+l':
|
|
520
|
+
this.forceOwnedRedraw();
|
|
521
|
+
return;
|
|
253
522
|
case 'enter':
|
|
254
523
|
this.submitPrompt();
|
|
255
524
|
return;
|
|
525
|
+
case 'newline':
|
|
526
|
+
this.updatePrompt(insertAtCursor(value, cursor, '\n'));
|
|
527
|
+
return;
|
|
256
528
|
case 'backspace':
|
|
257
529
|
this.updatePrompt(deleteBeforeCursor(value, cursor));
|
|
258
530
|
return;
|
|
@@ -266,10 +538,10 @@ class TuiRunner {
|
|
|
266
538
|
this.dispatch({ type: 'setPrompt', value, cursor: nextBoundary(value, cursor) });
|
|
267
539
|
return;
|
|
268
540
|
case 'home':
|
|
269
|
-
this.
|
|
541
|
+
this.moveCursorLineHome();
|
|
270
542
|
return;
|
|
271
543
|
case 'end':
|
|
272
|
-
this.
|
|
544
|
+
this.moveCursorLineEnd();
|
|
273
545
|
return;
|
|
274
546
|
case 'ctrl+u':
|
|
275
547
|
this.dispatch({ type: 'setPrompt', value: '', cursor: 0 });
|
|
@@ -293,21 +565,97 @@ class TuiRunner {
|
|
|
293
565
|
}
|
|
294
566
|
return;
|
|
295
567
|
case 'up':
|
|
296
|
-
|
|
297
|
-
return;
|
|
298
|
-
case 'pageup':
|
|
299
|
-
this.dispatch({ type: 'scrollTranscript', delta: Math.max(1, this.height - 5) });
|
|
568
|
+
this.moveCursorUpOrHistory();
|
|
300
569
|
return;
|
|
301
|
-
case '
|
|
302
|
-
this.
|
|
570
|
+
case 'down':
|
|
571
|
+
this.moveCursorDownOrHistory();
|
|
303
572
|
return;
|
|
304
573
|
}
|
|
305
574
|
}
|
|
575
|
+
/** Navigate to previous history entry. */
|
|
576
|
+
historyBack() {
|
|
577
|
+
if (this.state.overlay)
|
|
578
|
+
return; // overlay handles up/down itself
|
|
579
|
+
const { value } = this.state.prompt;
|
|
580
|
+
const next = (0, history_1.historyPrevious)(this.history, value);
|
|
581
|
+
this.history = next;
|
|
582
|
+
const displayValue = (0, history_1.historyCurrentValue)(next, value);
|
|
583
|
+
this.dispatch({ type: 'setPrompt', value: displayValue, cursor: displayValue.length });
|
|
584
|
+
}
|
|
585
|
+
/** Navigate to next history entry (or back to draft). */
|
|
586
|
+
historyForward() {
|
|
587
|
+
if (this.state.overlay)
|
|
588
|
+
return;
|
|
589
|
+
const { value } = this.state.prompt;
|
|
590
|
+
const next = (0, history_1.historyNext)(this.history);
|
|
591
|
+
this.history = next;
|
|
592
|
+
const displayValue = (0, history_1.historyCurrentValue)(next, value);
|
|
593
|
+
this.dispatch({ type: 'setPrompt', value: displayValue, cursor: displayValue.length });
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Up in a multi-line prompt moves the cursor to the previous visual line;
|
|
597
|
+
* only at the first line does it fall back to command history (matching
|
|
598
|
+
* shell/editor behaviour). Single-line prompts behave exactly as before.
|
|
599
|
+
*/
|
|
600
|
+
moveCursorUpOrHistory() {
|
|
601
|
+
if (this.state.overlay)
|
|
602
|
+
return;
|
|
603
|
+
const { value, cursor } = this.state.prompt;
|
|
604
|
+
const { line, col } = lineColOfCursor(value, cursor);
|
|
605
|
+
if (line > 0) {
|
|
606
|
+
this.dispatch({ type: 'setPrompt', value, cursor: cursorOfLineCol(value, line - 1, col) });
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
this.historyBack();
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
/**
|
|
613
|
+
* Down in a multi-line prompt moves the cursor to the next visual line;
|
|
614
|
+
* only at the last line does it fall back to command history.
|
|
615
|
+
*/
|
|
616
|
+
moveCursorDownOrHistory() {
|
|
617
|
+
if (this.state.overlay)
|
|
618
|
+
return;
|
|
619
|
+
const { value, cursor } = this.state.prompt;
|
|
620
|
+
const lines = value.split('\n');
|
|
621
|
+
const { line, col } = lineColOfCursor(value, cursor);
|
|
622
|
+
if (line < lines.length - 1) {
|
|
623
|
+
this.dispatch({ type: 'setPrompt', value, cursor: cursorOfLineCol(value, line + 1, col) });
|
|
624
|
+
}
|
|
625
|
+
else {
|
|
626
|
+
this.historyForward();
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
/** Home: move to the start of the current visual line (not whole buffer). */
|
|
630
|
+
moveCursorLineHome() {
|
|
631
|
+
if (this.state.overlay)
|
|
632
|
+
return;
|
|
633
|
+
const { value, cursor } = this.state.prompt;
|
|
634
|
+
const { line } = lineColOfCursor(value, cursor);
|
|
635
|
+
this.dispatch({ type: 'setPrompt', value, cursor: cursorOfLineCol(value, line, 0) });
|
|
636
|
+
}
|
|
637
|
+
/** End: move to the end of the current visual line (not whole buffer). */
|
|
638
|
+
moveCursorLineEnd() {
|
|
639
|
+
if (this.state.overlay)
|
|
640
|
+
return;
|
|
641
|
+
const { value, cursor } = this.state.prompt;
|
|
642
|
+
const { line } = lineColOfCursor(value, cursor);
|
|
643
|
+
const lines = value.split('\n');
|
|
644
|
+
this.dispatch({
|
|
645
|
+
type: 'setPrompt',
|
|
646
|
+
value,
|
|
647
|
+
cursor: cursorOfLineCol(value, line, lines[line].length),
|
|
648
|
+
});
|
|
649
|
+
}
|
|
306
650
|
submitPrompt(options = {}) {
|
|
307
651
|
const input = this.state.prompt.value;
|
|
308
652
|
if (!input.trim() && !options.allowEmpty)
|
|
309
653
|
return;
|
|
654
|
+
// Push to history before clearing.
|
|
655
|
+
this.history = (0, history_1.pushHistoryEntry)(this.history, input);
|
|
310
656
|
this.dispatch({ type: 'setPrompt', value: '', cursor: 0 });
|
|
657
|
+
if (this.handleLocalCommand(input))
|
|
658
|
+
return;
|
|
311
659
|
try {
|
|
312
660
|
const submission = this.options.onSubmit?.(input);
|
|
313
661
|
if (submission) {
|
|
@@ -321,6 +669,42 @@ class TuiRunner {
|
|
|
321
669
|
reportSubmitFailure() {
|
|
322
670
|
this.events.append({ role: 'error', content: 'Input submission failed.' });
|
|
323
671
|
}
|
|
672
|
+
handleLocalCommand(input) {
|
|
673
|
+
const command = input.trim();
|
|
674
|
+
if (command === '/redraw') {
|
|
675
|
+
this.forceOwnedRedraw();
|
|
676
|
+
return true;
|
|
677
|
+
}
|
|
678
|
+
const match = command.match(/^\/tool-output(?:\s+(adaptive|collapsed|full))?$/u);
|
|
679
|
+
if (match) {
|
|
680
|
+
const mode = match[1];
|
|
681
|
+
if (mode)
|
|
682
|
+
this.dispatch({ type: 'setToolOutputViewMode', mode });
|
|
683
|
+
this.dispatch({
|
|
684
|
+
type: 'setStatus',
|
|
685
|
+
message: `tool output: ${mode ?? this.state.toolOutputViewMode}`,
|
|
686
|
+
});
|
|
687
|
+
return true;
|
|
688
|
+
}
|
|
689
|
+
if (command.startsWith('/tool-output')) {
|
|
690
|
+
this.dispatch({
|
|
691
|
+
type: 'setStatus',
|
|
692
|
+
message: 'Usage: /tool-output adaptive|collapsed|full',
|
|
693
|
+
});
|
|
694
|
+
return true;
|
|
695
|
+
}
|
|
696
|
+
return false;
|
|
697
|
+
}
|
|
698
|
+
forceOwnedRedraw() {
|
|
699
|
+
if (this.surface && !this.surfaceFailed) {
|
|
700
|
+
void this.surface
|
|
701
|
+
.forceRedraw(this.buildLiveFrame(this.width))
|
|
702
|
+
.catch(error => this.handleSurfaceError(error));
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
this.lastFrame = null;
|
|
706
|
+
this.scheduler.request('immediate');
|
|
707
|
+
}
|
|
324
708
|
updatePrompt(next) {
|
|
325
709
|
this.dispatch({ type: 'setPrompt', value: next.value, cursor: next.cursor });
|
|
326
710
|
this.syncPromptOverlay(next.value);
|
|
@@ -333,7 +717,7 @@ class TuiRunner {
|
|
|
333
717
|
this.dispatch({
|
|
334
718
|
type: 'showCommandPalette',
|
|
335
719
|
query: value.slice(1),
|
|
336
|
-
items: (
|
|
720
|
+
items: visibleTuiCommandItems(value),
|
|
337
721
|
});
|
|
338
722
|
return;
|
|
339
723
|
}
|
|
@@ -355,7 +739,8 @@ class TuiRunner {
|
|
|
355
739
|
if (!item)
|
|
356
740
|
return;
|
|
357
741
|
const command = (0, commands_1.getCommands)().find(candidate => candidate.name === item.value);
|
|
358
|
-
const needsArgs =
|
|
742
|
+
const needsArgs = item.value === 'tool-output' ||
|
|
743
|
+
Boolean(command?.argumentHint || command?.params?.some(param => param.required));
|
|
359
744
|
const value = `/${item.value}${needsArgs ? ' ' : ''}`;
|
|
360
745
|
const promptAlreadyMatchesCommand = this.state.prompt.value.trim() === `/${item.value}`;
|
|
361
746
|
const nextValue = promptAlreadyMatchesCommand ? `/${item.value}` : value;
|
|
@@ -382,6 +767,366 @@ class TuiRunner {
|
|
|
382
767
|
this.dispatch({ type: 'closeOverlay' });
|
|
383
768
|
void this.options.onPermissionDecision?.(overlay.request.id, approved);
|
|
384
769
|
}
|
|
770
|
+
/** Restore any temporary Inspector terminal ownership before shutdown. */
|
|
771
|
+
async closeModalSurface() {
|
|
772
|
+
if (this.state.inspector || this.inspectorSurface?.isMounted) {
|
|
773
|
+
this.closeToolInspector();
|
|
774
|
+
}
|
|
775
|
+
await this.modalTransition;
|
|
776
|
+
}
|
|
777
|
+
/** Wait until the current Inspector ownership transition is complete. */
|
|
778
|
+
async waitForModalSurface() {
|
|
779
|
+
await this.modalTransition;
|
|
780
|
+
}
|
|
781
|
+
/** Flush every finalized transcript batch, including batches queued by acknowledgements. */
|
|
782
|
+
async flushTranscriptCommits() {
|
|
783
|
+
if (!this.surface || this.surfaceFailed)
|
|
784
|
+
return;
|
|
785
|
+
for (let attempt = 0; attempt < 1024; attempt += 1) {
|
|
786
|
+
await this.surface.whenIdle();
|
|
787
|
+
await Promise.resolve();
|
|
788
|
+
if (!this.commitInFlight && (0, state_1.pendingCommitRecords)(this.state).length === 0)
|
|
789
|
+
return;
|
|
790
|
+
if (!this.commitInFlight)
|
|
791
|
+
this.tryCommit(this.state, true);
|
|
792
|
+
}
|
|
793
|
+
throw new Error('TUI transcript commits did not settle');
|
|
794
|
+
}
|
|
795
|
+
openToolInspector() {
|
|
796
|
+
if (this.state.inspector || !this.inspectorController || !this.inspectorSurface)
|
|
797
|
+
return;
|
|
798
|
+
this.inputOwnership.capture('inspector', {
|
|
799
|
+
value: this.state.prompt.value,
|
|
800
|
+
cursor: this.state.prompt.cursor,
|
|
801
|
+
parserState: this.parser.getState(),
|
|
802
|
+
});
|
|
803
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'openToolInspector' });
|
|
804
|
+
this.inspectorReady = false;
|
|
805
|
+
this.inspectorSearchActive = false;
|
|
806
|
+
this.modalTransition = this.modalTransition
|
|
807
|
+
.then(async () => {
|
|
808
|
+
await this.surface?.suspend();
|
|
809
|
+
this.inspectorReady = true;
|
|
810
|
+
await this.inspectorSurface.mount(this.buildInspectorFrame());
|
|
811
|
+
})
|
|
812
|
+
.catch(error => this.handleSurfaceError(error));
|
|
813
|
+
}
|
|
814
|
+
closeToolInspector() {
|
|
815
|
+
if (!this.state.inspector && !this.inspectorSurface?.isMounted)
|
|
816
|
+
return;
|
|
817
|
+
this.inspectorController?.cancel();
|
|
818
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'closeToolInspector' });
|
|
819
|
+
const snapshot = this.inputOwnership.restore();
|
|
820
|
+
if (snapshot) {
|
|
821
|
+
this.parser.setState(snapshot.parserState);
|
|
822
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, {
|
|
823
|
+
type: 'setPrompt',
|
|
824
|
+
value: snapshot.value,
|
|
825
|
+
cursor: snapshot.cursor,
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
this.inspectorReady = false;
|
|
829
|
+
this.inspectorSearchActive = false;
|
|
830
|
+
this.modalTransition = this.modalTransition
|
|
831
|
+
.then(async () => {
|
|
832
|
+
await this.inspectorSurface?.unmount();
|
|
833
|
+
if (this.surface) {
|
|
834
|
+
await this.surface.restore(() => this.buildLiveFrame(this.width), this.width, this.height);
|
|
835
|
+
}
|
|
836
|
+
this.scheduler.request('immediate');
|
|
837
|
+
})
|
|
838
|
+
.catch(error => this.handleSurfaceError(error));
|
|
839
|
+
}
|
|
840
|
+
applyInspectorInput(event) {
|
|
841
|
+
if (event.type === 'paste')
|
|
842
|
+
return;
|
|
843
|
+
if (event.type === 'text') {
|
|
844
|
+
if (!this.inspectorSearchActive) {
|
|
845
|
+
if (event.value === 'j') {
|
|
846
|
+
this.moveInspectorSelection(1);
|
|
847
|
+
return;
|
|
848
|
+
}
|
|
849
|
+
if (event.value === 'k') {
|
|
850
|
+
this.moveInspectorSelection(-1);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (event.value === ' ') {
|
|
854
|
+
void this.loadSelectedInspectorDetail(false);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
if (event.value === 'g') {
|
|
858
|
+
this.moveInspectorSelectionTo('first');
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
if (event.value === 'G') {
|
|
862
|
+
this.moveInspectorSelectionTo('last');
|
|
863
|
+
return;
|
|
864
|
+
}
|
|
865
|
+
if (event.value === 'n' && this.state.inspector?.searchQuery) {
|
|
866
|
+
this.moveInspectorSelection(1, true);
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
if (event.value === 'N' && this.state.inspector?.searchQuery) {
|
|
870
|
+
this.moveInspectorSelection(-1, true);
|
|
871
|
+
return;
|
|
872
|
+
}
|
|
873
|
+
if (event.value === 'v') {
|
|
874
|
+
this.openSelectedInspectorDetailInEditor();
|
|
875
|
+
return;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
if (!this.inspectorSearchActive && (event.value === 'q' || event.value === 'Q')) {
|
|
879
|
+
this.closeToolInspector();
|
|
880
|
+
return;
|
|
881
|
+
}
|
|
882
|
+
if (!this.inspectorSearchActive && event.value === '/') {
|
|
883
|
+
this.inspectorSearchActive = true;
|
|
884
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'setToolInspectorSearch', query: '' });
|
|
885
|
+
this.paintInspector();
|
|
886
|
+
return;
|
|
887
|
+
}
|
|
888
|
+
if (!this.inspectorSearchActive && event.value === '[') {
|
|
889
|
+
this.exportInspectorResults();
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
if (this.inspectorSearchActive) {
|
|
893
|
+
const query = `${this.state.inspector?.searchQuery ?? ''}${event.value}`;
|
|
894
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'setToolInspectorSearch', query });
|
|
895
|
+
this.paintInspector();
|
|
896
|
+
}
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
switch (event.key) {
|
|
900
|
+
case 'ctrl+o':
|
|
901
|
+
case 'escape':
|
|
902
|
+
this.closeToolInspector();
|
|
903
|
+
return;
|
|
904
|
+
case 'ctrl+c':
|
|
905
|
+
this.closeToolInspector();
|
|
906
|
+
return;
|
|
907
|
+
case 'up':
|
|
908
|
+
this.moveInspectorSelection(-1);
|
|
909
|
+
return;
|
|
910
|
+
case 'down':
|
|
911
|
+
this.moveInspectorSelection(1);
|
|
912
|
+
return;
|
|
913
|
+
case 'pageup':
|
|
914
|
+
case 'ctrl+u':
|
|
915
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'scrollToolInspector', delta: -10 });
|
|
916
|
+
this.paintInspector();
|
|
917
|
+
return;
|
|
918
|
+
case 'pagedown':
|
|
919
|
+
case 'ctrl+d':
|
|
920
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'scrollToolInspector', delta: 10 });
|
|
921
|
+
void this.loadSelectedInspectorDetail(true);
|
|
922
|
+
return;
|
|
923
|
+
case 'home':
|
|
924
|
+
this.moveInspectorSelectionTo('first');
|
|
925
|
+
return;
|
|
926
|
+
case 'end':
|
|
927
|
+
this.moveInspectorSelectionTo('last');
|
|
928
|
+
return;
|
|
929
|
+
case 'backspace': {
|
|
930
|
+
if (!this.inspectorSearchActive)
|
|
931
|
+
return;
|
|
932
|
+
const query = this.state.inspector?.searchQuery ?? '';
|
|
933
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, {
|
|
934
|
+
type: 'setToolInspectorSearch',
|
|
935
|
+
query: query.slice(0, -1),
|
|
936
|
+
});
|
|
937
|
+
this.paintInspector();
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
case 'enter':
|
|
941
|
+
if (this.inspectorSearchActive) {
|
|
942
|
+
this.inspectorSearchActive = false;
|
|
943
|
+
this.paintInspector();
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
void this.loadSelectedInspectorDetail(false);
|
|
947
|
+
return;
|
|
948
|
+
case 'ctrl+e':
|
|
949
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'toggleAllToolInspectorEntries' });
|
|
950
|
+
this.paintInspector();
|
|
951
|
+
return;
|
|
952
|
+
case 'ctrl+l':
|
|
953
|
+
this.paintInspector();
|
|
954
|
+
return;
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
async loadSelectedInspectorDetail(append) {
|
|
958
|
+
const inspector = this.state.inspector;
|
|
959
|
+
if (!inspector || !this.inspectorController)
|
|
960
|
+
return;
|
|
961
|
+
const view = this.inspectorController.view(this.state.recentToolDetails, inspector);
|
|
962
|
+
const selected = view.selected;
|
|
963
|
+
if (!selected)
|
|
964
|
+
return;
|
|
965
|
+
const expanded = inspector.expandedCallIds.includes(selected.callId);
|
|
966
|
+
if (!append && expanded) {
|
|
967
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, {
|
|
968
|
+
type: 'toggleToolInspectorEntry',
|
|
969
|
+
callId: selected.callId,
|
|
970
|
+
});
|
|
971
|
+
this.paintInspector();
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
if (!expanded) {
|
|
975
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, {
|
|
976
|
+
type: 'toggleToolInspectorEntry',
|
|
977
|
+
callId: selected.callId,
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
this.paintInspector();
|
|
981
|
+
await this.inspectorController.load(selected, append && view.detail?.nextOffsetBytes !== undefined);
|
|
982
|
+
if (this.state.inspector)
|
|
983
|
+
this.paintInspector();
|
|
984
|
+
}
|
|
985
|
+
moveInspectorSelection(delta, wrap = false) {
|
|
986
|
+
const inspector = this.state.inspector;
|
|
987
|
+
if (!inspector || !this.inspectorController)
|
|
988
|
+
return;
|
|
989
|
+
const view = this.inspectorController.view(this.state.recentToolDetails, inspector);
|
|
990
|
+
if (view.entries.length === 0)
|
|
991
|
+
return;
|
|
992
|
+
const target = wrap
|
|
993
|
+
? (view.selectedIndex + delta + view.entries.length) % view.entries.length
|
|
994
|
+
: Math.max(0, Math.min(view.entries.length - 1, view.selectedIndex + delta));
|
|
995
|
+
this.inspectorController.cancel();
|
|
996
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, { type: 'setToolInspectorSelection', index: target });
|
|
997
|
+
this.paintInspector();
|
|
998
|
+
}
|
|
999
|
+
moveInspectorSelectionTo(edge) {
|
|
1000
|
+
const inspector = this.state.inspector;
|
|
1001
|
+
if (!inspector || !this.inspectorController)
|
|
1002
|
+
return;
|
|
1003
|
+
const view = this.inspectorController.view(this.state.recentToolDetails, inspector);
|
|
1004
|
+
if (view.entries.length === 0)
|
|
1005
|
+
return;
|
|
1006
|
+
this.inspectorController.cancel();
|
|
1007
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, {
|
|
1008
|
+
type: 'setToolInspectorSelection',
|
|
1009
|
+
index: edge === 'first' ? 0 : view.entries.length - 1,
|
|
1010
|
+
});
|
|
1011
|
+
this.paintInspector();
|
|
1012
|
+
}
|
|
1013
|
+
exportInspectorResults() {
|
|
1014
|
+
const inspector = this.state.inspector;
|
|
1015
|
+
if (!inspector || !this.inspectorController)
|
|
1016
|
+
return;
|
|
1017
|
+
const entries = this.inspectorController.view(this.state.recentToolDetails, inspector).entries;
|
|
1018
|
+
this.closeToolInspector();
|
|
1019
|
+
this.modalTransition = this.modalTransition
|
|
1020
|
+
.then(async () => {
|
|
1021
|
+
if (!this.surface) {
|
|
1022
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, {
|
|
1023
|
+
type: 'setStatus',
|
|
1024
|
+
message: 'Inspector export requires an inline terminal surface.',
|
|
1025
|
+
});
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
for (const entry of entries) {
|
|
1029
|
+
let pageIndex = 0;
|
|
1030
|
+
for await (const page of this.inspectorController.readPages(entry)) {
|
|
1031
|
+
const heading = pageIndex === 0 ? `#${entry.sequence} ${entry.toolName}\n` : '';
|
|
1032
|
+
const rows = (0, transcript_layout_1.layoutTranscriptEntry)({
|
|
1033
|
+
role: 'tool',
|
|
1034
|
+
content: `${heading}${page.content}`,
|
|
1035
|
+
}, {
|
|
1036
|
+
width: this.transcriptWidth,
|
|
1037
|
+
theme: this.theme,
|
|
1038
|
+
toolOutputMode: 'full',
|
|
1039
|
+
});
|
|
1040
|
+
await this.surface.commit({
|
|
1041
|
+
batchId: `tui-inspector-export-${++this.commitSequence}`,
|
|
1042
|
+
generation: this.state.transcriptGeneration,
|
|
1043
|
+
reason: 'append',
|
|
1044
|
+
entries: [
|
|
1045
|
+
{
|
|
1046
|
+
displayKey: `inspector-export-${entry.callId}-${pageIndex}`,
|
|
1047
|
+
rows,
|
|
1048
|
+
},
|
|
1049
|
+
],
|
|
1050
|
+
}, () => this.buildLiveFrame(this.width));
|
|
1051
|
+
pageIndex += 1;
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, {
|
|
1055
|
+
type: 'setStatus',
|
|
1056
|
+
message: `Exported ${entries.length} tool result${entries.length === 1 ? '' : 's'} to scrollback.`,
|
|
1057
|
+
});
|
|
1058
|
+
this.scheduler.request('immediate');
|
|
1059
|
+
})
|
|
1060
|
+
.catch(error => this.handleSurfaceError(error));
|
|
1061
|
+
}
|
|
1062
|
+
openSelectedInspectorDetailInEditor() {
|
|
1063
|
+
const inspector = this.state.inspector;
|
|
1064
|
+
if (!inspector || !this.inspectorController)
|
|
1065
|
+
return;
|
|
1066
|
+
const selected = this.inspectorController.view(this.state.recentToolDetails, inspector).selected;
|
|
1067
|
+
if (!selected)
|
|
1068
|
+
return;
|
|
1069
|
+
if (!this.options.onOpenExternalEditor) {
|
|
1070
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, {
|
|
1071
|
+
type: 'setStatus',
|
|
1072
|
+
message: 'External editor is unavailable in this TUI environment.',
|
|
1073
|
+
});
|
|
1074
|
+
this.paintInspector();
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
this.closeToolInspector();
|
|
1078
|
+
this.modalTransition = this.modalTransition
|
|
1079
|
+
.then(async () => {
|
|
1080
|
+
const directory = await (0, promises_1.mkdtemp)((0, path_1.join)((0, os_1.tmpdir)(), 'openhorse-tool-'));
|
|
1081
|
+
const filePath = (0, path_1.join)(directory, `${selected.sequence}-${safeFileName(selected.toolName)}.txt`);
|
|
1082
|
+
try {
|
|
1083
|
+
await this.inspectorController.writeDetailToFile(selected, filePath);
|
|
1084
|
+
await this.options.onOpenExternalEditor(filePath);
|
|
1085
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, {
|
|
1086
|
+
type: 'setStatus',
|
|
1087
|
+
message: `Viewed #${selected.sequence} ${selected.toolName} in external editor.`,
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
finally {
|
|
1091
|
+
await (0, promises_1.rm)(directory, { recursive: true, force: true });
|
|
1092
|
+
}
|
|
1093
|
+
this.forceOwnedRedraw();
|
|
1094
|
+
})
|
|
1095
|
+
.catch(error => {
|
|
1096
|
+
this.state = (0, state_1.tuiUiReducer)(this.state, {
|
|
1097
|
+
type: 'setStatus',
|
|
1098
|
+
message: `External editor failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1099
|
+
});
|
|
1100
|
+
this.forceOwnedRedraw();
|
|
1101
|
+
});
|
|
1102
|
+
}
|
|
1103
|
+
paintInspector() {
|
|
1104
|
+
if (!this.state.inspector ||
|
|
1105
|
+
!this.inspectorController ||
|
|
1106
|
+
!this.inspectorSurface ||
|
|
1107
|
+
!this.inspectorReady)
|
|
1108
|
+
return;
|
|
1109
|
+
const frame = this.buildInspectorFrame();
|
|
1110
|
+
this.lastFrame = frame;
|
|
1111
|
+
void this.inspectorSurface.paint(frame).catch(error => this.handleSurfaceError(error));
|
|
1112
|
+
}
|
|
1113
|
+
buildInspectorFrame() {
|
|
1114
|
+
const inspector = this.state.inspector;
|
|
1115
|
+
const view = inspector && this.inspectorController
|
|
1116
|
+
? this.inspectorController.view(this.state.recentToolDetails, inspector)
|
|
1117
|
+
: {
|
|
1118
|
+
entries: [],
|
|
1119
|
+
selectedIndex: 0,
|
|
1120
|
+
expandedCallIds: [],
|
|
1121
|
+
searchQuery: '',
|
|
1122
|
+
detailOffset: 0,
|
|
1123
|
+
};
|
|
1124
|
+
return (0, transcript_inspector_layout_1.renderTranscriptInspectorFrame)(view, {
|
|
1125
|
+
width: this.width,
|
|
1126
|
+
height: this.height,
|
|
1127
|
+
theme: this.theme,
|
|
1128
|
+
});
|
|
1129
|
+
}
|
|
385
1130
|
}
|
|
386
1131
|
exports.TuiRunner = TuiRunner;
|
|
387
1132
|
function insertAtCursor(value, cursor, text) {
|
|
@@ -391,6 +1136,28 @@ function insertAtCursor(value, cursor, text) {
|
|
|
391
1136
|
cursor: safeCursor + text.length,
|
|
392
1137
|
};
|
|
393
1138
|
}
|
|
1139
|
+
function visibleTuiCommandItems(input) {
|
|
1140
|
+
const query = input.startsWith('/')
|
|
1141
|
+
? input.slice(1).trim().split(/\s+/u, 1)[0].toLowerCase()
|
|
1142
|
+
: '';
|
|
1143
|
+
const local = [
|
|
1144
|
+
{
|
|
1145
|
+
value: 'tool-output',
|
|
1146
|
+
label: '/tool-output',
|
|
1147
|
+
description: 'Set TUI tool output mode',
|
|
1148
|
+
},
|
|
1149
|
+
{
|
|
1150
|
+
value: 'redraw',
|
|
1151
|
+
label: '/redraw',
|
|
1152
|
+
description: 'Redraw the TUI-owned live region',
|
|
1153
|
+
},
|
|
1154
|
+
].filter(item => item.value.includes(query));
|
|
1155
|
+
const localNames = new Set(local.map(item => item.value));
|
|
1156
|
+
return [...local, ...(0, pickers_1.visibleCommandItems)(input).filter(item => !localNames.has(item.value))];
|
|
1157
|
+
}
|
|
1158
|
+
function safeFileName(value) {
|
|
1159
|
+
return value.replace(/[^a-zA-Z0-9._-]+/gu, '-').replace(/^-+|-+$/gu, '') || 'tool';
|
|
1160
|
+
}
|
|
394
1161
|
function deleteBeforeCursor(value, cursor) {
|
|
395
1162
|
const safeCursor = clampCursor(value, cursor);
|
|
396
1163
|
if (safeCursor === 0)
|
|
@@ -460,4 +1227,29 @@ function clampCursor(value, cursor) {
|
|
|
460
1227
|
return value.length;
|
|
461
1228
|
return Math.min(Math.max(0, Math.floor(cursor)), value.length);
|
|
462
1229
|
}
|
|
1230
|
+
/** Map an absolute character cursor to {line, col} within a multi-line value. */
|
|
1231
|
+
function lineColOfCursor(value, cursor) {
|
|
1232
|
+
const lines = value.split('\n');
|
|
1233
|
+
const safeCursor = clampCursor(value, cursor);
|
|
1234
|
+
let offset = 0;
|
|
1235
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1236
|
+
const lineLen = lines[i].length;
|
|
1237
|
+
if (safeCursor <= offset + lineLen) {
|
|
1238
|
+
return { line: i, col: safeCursor - offset };
|
|
1239
|
+
}
|
|
1240
|
+
offset += lineLen + 1; // +1 for the newline separator
|
|
1241
|
+
}
|
|
1242
|
+
const last = lines.length - 1;
|
|
1243
|
+
return { line: last, col: lines[last].length };
|
|
1244
|
+
}
|
|
1245
|
+
/** Map a {line, col} back to an absolute character cursor within a multi-line value. */
|
|
1246
|
+
function cursorOfLineCol(value, line, col) {
|
|
1247
|
+
const lines = value.split('\n');
|
|
1248
|
+
const safeLine = Math.max(0, Math.min(line, lines.length - 1));
|
|
1249
|
+
let offset = 0;
|
|
1250
|
+
for (let i = 0; i < safeLine; i++) {
|
|
1251
|
+
offset += lines[i].length + 1;
|
|
1252
|
+
}
|
|
1253
|
+
return offset + Math.max(0, Math.min(col, lines[safeLine].length));
|
|
1254
|
+
}
|
|
463
1255
|
//# sourceMappingURL=runner.js.map
|