@linxiraos/pi-tui 1.2.1 → 1.3.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 +245 -224
- package/README.md +36 -0
- package/THIRD-PARTY-NOTICES.txt +55 -5
- package/dist/types/app-keybindings.d.ts +18 -8
- package/dist/types/apps/git/diff-pane.d.ts +5 -0
- package/dist/types/apps/git/git-tui.d.ts +16 -2
- package/dist/types/apps/git/state.d.ts +11 -0
- package/dist/types/autocomplete.d.ts +2 -0
- package/dist/types/charts/chart-svg.d.ts +7 -13
- package/dist/types/chat/advisor-message.d.ts +4 -1
- package/dist/types/chat/assistant-message.d.ts +9 -4
- package/dist/types/chat/figure-markdown.d.ts +7 -2
- package/dist/types/chat/messages.d.ts +3 -1
- package/dist/types/chat/read-tool-group.d.ts +2 -2
- package/dist/types/chat/table-chart.d.ts +11 -15
- package/dist/types/chat/tool-execution.d.ts +32 -20
- package/dist/types/chat/user-message.d.ts +3 -2
- package/dist/types/chrome/countdown-timer.d.ts +3 -2
- package/dist/types/chrome/diff.d.ts +2 -3
- package/dist/types/chrome/status-notice.d.ts +2 -0
- package/dist/types/chrome/transcript-container.d.ts +2 -0
- package/dist/types/components/image.d.ts +2 -0
- package/dist/types/components/markdown.d.ts +49 -3
- package/dist/types/components/text.d.ts +2 -0
- package/dist/types/debug-server.d.ts +2 -0
- package/dist/types/keybinding-matchers.d.ts +6 -0
- package/dist/types/multiplexers/tmux.d.ts +1 -0
- package/dist/types/multiplexers/types.d.ts +6 -0
- package/dist/types/native/backend.d.ts +5 -4
- package/dist/types/native/blobs.d.ts +24 -12
- package/dist/types/overlays/agent-hub-projection.d.ts +10 -5
- package/dist/types/overlays/annotation-overlay.d.ts +19 -0
- package/dist/types/overlays/approval-dialog.d.ts +79 -0
- package/dist/types/overlays/ask-dialog.d.ts +11 -6
- package/dist/types/overlays/btw-history-panel.d.ts +2 -6
- package/dist/types/overlays/hook-editor.d.ts +7 -2
- package/dist/types/overlays/hook-input.d.ts +12 -4
- package/dist/types/overlays/job-tree.d.ts +50 -0
- package/dist/types/overlays/jobs-panel.d.ts +25 -11
- package/dist/types/overlays/login-dialog.d.ts +8 -3
- package/dist/types/overlays/mcp-add-wizard.d.ts +9 -1
- package/dist/types/overlays/model-browser.d.ts +6 -1
- package/dist/types/overlays/model-picker.d.ts +2 -0
- package/dist/types/overlays/pause-screen.d.ts +5 -0
- package/dist/types/overlays/plan-review-overlay.d.ts +4 -0
- package/dist/types/overlays/report-panel.d.ts +5 -1
- package/dist/types/overlays/reset-usage-selector.d.ts +7 -5
- package/dist/types/overlays/session-account-selector.d.ts +4 -0
- package/dist/types/overlays/session-info-overlay.d.ts +7 -3
- package/dist/types/overlays/session-selector.d.ts +3 -1
- package/dist/types/overlays/usage-dashboard.d.ts +6 -6
- package/dist/types/prompt/composer.d.ts +23 -20
- package/dist/types/prompt/custom-editor.d.ts +20 -10
- package/dist/types/prompt/image-references.d.ts +22 -0
- package/dist/types/prompt/internal-url-autocomplete.d.ts +1 -0
- package/dist/types/prompt/macos-spelling.d.ts +2 -1
- package/dist/types/prompt/prose-gate.d.ts +3 -0
- package/dist/types/prompt/queued-messages.d.ts +1 -1
- package/dist/types/prompt/typing-word.d.ts +25 -0
- package/dist/types/prompt/welcome.d.ts +26 -6
- package/dist/types/render/hanging-wrap.d.ts +7 -0
- package/dist/types/render/hyperlink.d.ts +2 -0
- package/dist/types/render/render-utils.d.ts +9 -1
- package/dist/types/render/utils.d.ts +1 -1
- package/dist/types/setup/scenes/splash.d.ts +6 -5
- package/dist/types/status-line/context-usage.d.ts +4 -1
- package/dist/types/status-line/host.d.ts +6 -4
- package/dist/types/status-line/startup.d.ts +6 -6
- package/dist/types/status-line/types.d.ts +3 -0
- package/dist/types/stdin-buffer.d.ts +7 -0
- package/dist/types/terminal-capabilities.d.ts +16 -1
- package/dist/types/terminal-multiplexer.d.ts +7 -0
- package/dist/types/terminal.d.ts +8 -4
- package/dist/types/theme/symbols.d.ts +1 -1
- package/dist/types/theme/theme.d.ts +11 -0
- package/dist/types/thinking.d.ts +5 -0
- package/dist/types/tmux.d.ts +39 -4
- package/dist/types/tools/ast-edit.d.ts +2 -2
- package/dist/types/tools/ast-grep.d.ts +2 -2
- package/dist/types/tools/bash.d.ts +6 -1
- package/dist/types/tools/edit.d.ts +2 -0
- package/dist/types/tools/eval.d.ts +53 -4
- package/dist/types/tools/find.d.ts +14 -4
- package/dist/types/tools/glob.d.ts +3 -3
- package/dist/types/tools/grep.d.ts +2 -2
- package/dist/types/tools/grouped-file-output.d.ts +9 -4
- package/dist/types/tools/native-view.d.ts +1 -1
- package/dist/types/tools/read.d.ts +32 -10
- package/dist/types/tools/renderer.d.ts +2 -0
- package/dist/types/tools/task.d.ts +31 -0
- package/dist/types/tools/web-search-types.d.ts +1 -0
- package/dist/types/tools/write.d.ts +2 -0
- package/dist/types/tui.d.ts +20 -3
- package/package.json +10 -10
- package/src/app-keybindings.d.ts +18 -8
- package/src/app-keybindings.ts +20 -8
- package/src/apps/autoresearch-dashboard.ts +444 -27
- package/src/apps/debug/raw-sse.ts +2 -2
- package/src/apps/git/diff-pane.ts +39 -41
- package/src/apps/git/git-tui.ts +80 -14
- package/src/apps/git/sidebar.ts +10 -1
- package/src/apps/git/state.ts +12 -0
- package/src/autocomplete.d.ts +2 -0
- package/src/autocomplete.ts +21 -19
- package/src/bracketed-paste.ts +9 -2
- package/src/charts/chart-svg.d.ts +7 -13
- package/src/charts/chart-svg.ts +19 -22
- package/src/charts/table-data.ts +17 -6
- package/src/chat/advisor-message.d.ts +4 -1
- package/src/chat/advisor-message.ts +77 -24
- package/src/chat/assistant-message.d.ts +9 -4
- package/src/chat/assistant-message.ts +127 -31
- package/src/chat/bash-execution.ts +7 -2
- package/src/chat/figure-markdown.d.ts +7 -2
- package/src/chat/figure-markdown.ts +10 -3
- package/src/chat/image-loading.ts +9 -6
- package/src/chat/messages.d.ts +3 -1
- package/src/chat/messages.ts +4 -1
- package/src/chat/read-tool-group.d.ts +2 -2
- package/src/chat/read-tool-group.ts +97 -42
- package/src/chat/svg-figure.ts +3 -1
- package/src/chat/svg-source.ts +6 -3
- package/src/chat/table-chart.d.ts +11 -15
- package/src/chat/table-chart.ts +24 -57
- package/src/chat/tool-execution.d.ts +32 -20
- package/src/chat/tool-execution.ts +103 -36
- package/src/chat/user-message.d.ts +3 -2
- package/src/chat/user-message.ts +54 -34
- package/src/chrome/countdown-timer.ts +3 -3
- package/src/chrome/diff.d.ts +2 -3
- package/src/chrome/diff.ts +4 -9
- package/src/chrome/status-notice.ts +5 -0
- package/src/chrome/transcript-container.d.ts +2 -0
- package/src/chrome/transcript-container.ts +212 -28
- package/src/components/editor.ts +12 -12
- package/src/components/image.d.ts +2 -0
- package/src/components/image.ts +54 -20
- package/src/components/markdown.d.ts +49 -3
- package/src/components/markdown.ts +482 -12
- package/src/components/settings-list.ts +2 -4
- package/src/components/text.d.ts +2 -0
- package/src/components/text.ts +11 -1
- package/src/debug-server.d.ts +2 -0
- package/src/debug-server.ts +51 -69
- package/src/hotkeys-markdown.ts +2 -0
- package/src/keybinding-matchers.d.ts +6 -0
- package/src/keybinding-matchers.ts +19 -0
- package/src/multiplexers/tmux.d.ts +1 -0
- package/src/multiplexers/tmux.ts +3 -0
- package/src/multiplexers/types.d.ts +6 -0
- package/src/multiplexers/types.ts +6 -0
- package/src/native/backend.d.ts +5 -4
- package/src/native/backend.ts +13 -5
- package/src/native/blobs.d.ts +24 -12
- package/src/native/blobs.ts +56 -7
- package/src/native/reconcile.ts +43 -21
- package/src/overlays/advisor-config.ts +259 -8
- package/src/overlays/agent-hub-projection.d.ts +10 -5
- package/src/overlays/agent-hub-projection.ts +63 -12
- package/src/overlays/annotation-overlay.ts +422 -168
- package/src/overlays/approval-dialog.ts +389 -0
- package/src/overlays/ask-dialog.ts +244 -105
- package/src/overlays/btw-history-panel.ts +208 -147
- package/src/overlays/extensions/extension-list.ts +4 -6
- package/src/overlays/hook-editor.ts +90 -32
- package/src/overlays/hook-input.ts +105 -19
- package/src/overlays/job-tree.ts +177 -0
- package/src/overlays/jobs-panel.ts +222 -142
- package/src/overlays/login-dialog.ts +201 -91
- package/src/overlays/mcp-add-wizard.ts +562 -279
- package/src/overlays/model-browser.ts +32 -7
- package/src/overlays/model-hub.ts +4 -10
- package/src/overlays/model-picker.ts +58 -21
- package/src/overlays/pause-screen.ts +81 -23
- package/src/overlays/plan-review-overlay.ts +16 -0
- package/src/overlays/plugin-selector.ts +17 -1
- package/src/overlays/queue-mode-selector.ts +10 -0
- package/src/overlays/report-panel.ts +6 -2
- package/src/overlays/reset-usage-selector.ts +130 -21
- package/src/overlays/session-account-selector.ts +36 -0
- package/src/overlays/session-info-overlay.ts +199 -76
- package/src/overlays/session-selector.ts +15 -10
- package/src/overlays/settings-selector.ts +3 -0
- package/src/overlays/show-images-selector.ts +1 -0
- package/src/overlays/snapcompact-shape-preview.ts +13 -6
- package/src/overlays/theme-selector.ts +6 -1
- package/src/overlays/thinking-selector.ts +8 -4
- package/src/overlays/tree-selector.ts +117 -2
- package/src/overlays/usage-dashboard.ts +498 -144
- package/src/prompt/composer-cache.ts +4 -1
- package/src/prompt/composer.ts +200 -74
- package/src/prompt/custom-editor.ts +168 -65
- package/src/prompt/image-references.d.ts +22 -0
- package/src/prompt/image-references.ts +79 -2
- package/src/prompt/internal-url-autocomplete.ts +1 -0
- package/src/prompt/macos-spelling.ts +28 -5
- package/src/prompt/prose-gate.ts +3 -0
- package/src/prompt/queued-messages.ts +9 -2
- package/src/prompt/tips.txt +1 -2
- package/src/prompt/typing-word.ts +59 -0
- package/src/prompt/welcome.ts +97 -45
- package/src/render/code-cell.ts +18 -5
- package/src/render/hanging-wrap.d.ts +7 -0
- package/src/render/hanging-wrap.ts +58 -0
- package/src/render/hyperlink.d.ts +2 -0
- package/src/render/hyperlink.ts +5 -0
- package/src/render/render-utils.d.ts +9 -1
- package/src/render/render-utils.ts +16 -2
- package/src/render/utils.d.ts +1 -1
- package/src/render/utils.ts +1 -1
- package/src/render/width-aware-text.ts +6 -5
- package/src/setup/scenes/splash.ts +22 -8
- package/src/status-line/component.ts +79 -11
- package/src/status-line/context-usage.ts +176 -73
- package/src/status-line/footer.ts +18 -35
- package/src/status-line/host.ts +6 -1
- package/src/status-line/segments.ts +16 -5
- package/src/status-line/startup.ts +6 -6
- package/src/status-line/types.ts +1 -0
- package/src/stdin-buffer.d.ts +7 -0
- package/src/stdin-buffer.ts +300 -62
- package/src/terminal-capabilities.d.ts +16 -1
- package/src/terminal-capabilities.ts +66 -28
- package/src/terminal-multiplexer.d.ts +7 -0
- package/src/terminal-multiplexer.ts +14 -0
- package/src/terminal.d.ts +8 -4
- package/src/terminal.ts +190 -63
- package/src/theme/symbols.d.ts +1 -1
- package/src/theme/symbols.ts +1 -0
- package/src/theme/theme-class.ts +1 -0
- package/src/theme/theme.d.ts +11 -0
- package/src/theme/theme.ts +46 -14
- package/src/thinking.d.ts +5 -0
- package/src/thinking.ts +9 -1
- package/src/tmux.d.ts +39 -4
- package/src/tmux.ts +110 -16
- package/src/tools/ask.ts +20 -9
- package/src/tools/ast-edit.d.ts +2 -2
- package/src/tools/ast-edit.ts +30 -7
- package/src/tools/ast-grep.d.ts +2 -2
- package/src/tools/ast-grep.ts +39 -35
- package/src/tools/bash.d.ts +6 -1
- package/src/tools/bash.ts +15 -9
- package/src/tools/debug.ts +2 -1
- package/src/tools/default-renderer.ts +3 -2
- package/src/tools/edit.d.ts +2 -0
- package/src/tools/edit.ts +100 -21
- package/src/tools/eval.d.ts +53 -4
- package/src/tools/eval.ts +173 -137
- package/src/tools/find.d.ts +14 -4
- package/src/tools/find.ts +71 -18
- package/src/tools/glob.d.ts +3 -3
- package/src/tools/glob.ts +51 -17
- package/src/tools/grep.d.ts +2 -2
- package/src/tools/grep.ts +99 -22
- package/src/tools/grouped-file-output.d.ts +9 -4
- package/src/tools/grouped-file-output.ts +21 -6
- package/src/tools/lsp.ts +30 -18
- package/src/tools/mcp.ts +2 -2
- package/src/tools/native-view.d.ts +1 -1
- package/src/tools/native-view.ts +2 -2
- package/src/tools/read.d.ts +32 -10
- package/src/tools/read.ts +89 -43
- package/src/tools/renderer.d.ts +2 -0
- package/src/tools/renderer.ts +2 -0
- package/src/tools/task.d.ts +31 -0
- package/src/tools/task.ts +40 -7
- package/src/tools/todo.ts +3 -2
- package/src/tools/wait.ts +16 -30
- package/src/tools/web-search-types.d.ts +1 -0
- package/src/tools/web-search-types.ts +1 -0
- package/src/tools/web-search.ts +2 -1
- package/src/tools/write.d.ts +2 -0
- package/src/tools/write.ts +36 -12
- package/src/tools/yield.ts +2 -2
- package/src/tui.d.ts +20 -3
- package/src/tui.ts +160 -43
- package/src/utils.ts +5 -3
- package/dist/types/tools/eval-format/code-mode-declarations.d.ts +0 -5
- package/src/tools/eval-format/code-mode-declarations.ts +0 -61
package/src/debug-server.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface TuiDebugTree {
|
|
|
22
22
|
export declare class TuiDebugServer {
|
|
23
23
|
#private;
|
|
24
24
|
constructor(tui: TUI, path: string);
|
|
25
|
+
/** Resolves true once the socket accepts connections, false if listen failed or the server stopped first. */
|
|
26
|
+
get ready(): Promise<boolean>;
|
|
25
27
|
start(): void;
|
|
26
28
|
stop(): void;
|
|
27
29
|
}
|
package/src/debug-server.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { createServer, type Server, type Socket } from "node:net";
|
|
1
|
+
import { type IpcConnection, IpcServer } from "@linxiraos/pi-natives";
|
|
3
2
|
import { type Component, isFocusable, type OverlayOptions, type TUI } from "./tui";
|
|
4
3
|
import { plainText } from "./native/spans";
|
|
5
4
|
|
|
@@ -209,72 +208,60 @@ function mouseSequence(x: number, y: number, action: string): string {
|
|
|
209
208
|
export class TuiDebugServer {
|
|
210
209
|
readonly #tui: TUI;
|
|
211
210
|
readonly #path: string;
|
|
212
|
-
#server:
|
|
213
|
-
#
|
|
211
|
+
#server: IpcServer | undefined;
|
|
212
|
+
#listening: Promise<boolean> | undefined;
|
|
213
|
+
#stopped = false;
|
|
214
|
+
#connections = new Set<IpcConnection>();
|
|
214
215
|
|
|
215
216
|
constructor(tui: TUI, path: string) {
|
|
216
217
|
this.#tui = tui;
|
|
217
218
|
this.#path = path;
|
|
218
219
|
}
|
|
219
220
|
|
|
221
|
+
/** Resolves true once the socket accepts connections, false if listen failed or the server stopped first. */
|
|
222
|
+
get ready(): Promise<boolean> {
|
|
223
|
+
return this.#listening ?? Promise.resolve(false);
|
|
224
|
+
}
|
|
225
|
+
|
|
220
226
|
start(): void {
|
|
221
|
-
if (this.#
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
} catch {
|
|
235
|
-
this.#server = undefined;
|
|
236
|
-
server.close();
|
|
237
|
-
}
|
|
227
|
+
if (this.#listening !== undefined) return;
|
|
228
|
+
this.#stopped = false;
|
|
229
|
+
this.#listening = IpcServer.listen(this.#path, { framing: "lines" }, connection => this.#accept(connection)).then(
|
|
230
|
+
server => {
|
|
231
|
+
if (this.#stopped) {
|
|
232
|
+
void server.close().catch(() => {});
|
|
233
|
+
return false;
|
|
234
|
+
}
|
|
235
|
+
this.#server = server;
|
|
236
|
+
return true;
|
|
237
|
+
},
|
|
238
|
+
() => false, // A live peer or unusable path disables the debug socket; the host application is unaffected.
|
|
239
|
+
);
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
stop(): void {
|
|
243
|
+
this.#stopped = true;
|
|
241
244
|
const server = this.#server;
|
|
242
245
|
this.#server = undefined;
|
|
243
|
-
|
|
244
|
-
this.#
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
} catch {
|
|
248
|
-
// A failed or already-closed listener still needs its stale path removed.
|
|
249
|
-
}
|
|
250
|
-
try {
|
|
251
|
-
if (existsSync(this.#path)) unlinkSync(this.#path);
|
|
252
|
-
} catch {
|
|
253
|
-
// Teardown must not affect the host application.
|
|
254
|
-
}
|
|
246
|
+
this.#listening = undefined;
|
|
247
|
+
for (const connection of this.#connections) connection.destroy();
|
|
248
|
+
this.#connections.clear();
|
|
249
|
+
void server?.close().catch(() => {});
|
|
255
250
|
}
|
|
256
251
|
|
|
257
|
-
#accept(
|
|
258
|
-
this.#
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
if (line.length > 0) this.#handleLine(socket, line);
|
|
268
|
-
newline = buffer.indexOf("\n");
|
|
269
|
-
}
|
|
270
|
-
});
|
|
271
|
-
socket.on("error", error => {
|
|
272
|
-
void error;
|
|
273
|
-
});
|
|
274
|
-
socket.on("close", () => this.#sockets.delete(socket));
|
|
252
|
+
#accept(connection: IpcConnection): void {
|
|
253
|
+
if (this.#stopped) {
|
|
254
|
+
connection.destroy();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
this.#connections.add(connection);
|
|
258
|
+
connection.start(
|
|
259
|
+
line => void this.#handleLine(connection, line),
|
|
260
|
+
() => this.#connections.delete(connection),
|
|
261
|
+
);
|
|
275
262
|
}
|
|
276
263
|
|
|
277
|
-
#handleLine(
|
|
264
|
+
async #handleLine(connection: IpcConnection, line: string): Promise<void> {
|
|
278
265
|
let request: DebugRequest;
|
|
279
266
|
try {
|
|
280
267
|
const parsed: unknown = JSON.parse(line);
|
|
@@ -282,36 +269,31 @@ export class TuiDebugServer {
|
|
|
282
269
|
throw new Error("request must be an object");
|
|
283
270
|
request = parsed as DebugRequest;
|
|
284
271
|
} catch (error) {
|
|
285
|
-
this.#write(
|
|
272
|
+
this.#write(connection, { ok: false, error: `malformed JSON: ${errorMessage(error)}` });
|
|
286
273
|
return;
|
|
287
274
|
}
|
|
275
|
+
let response: DebugResponse;
|
|
288
276
|
try {
|
|
289
|
-
|
|
290
|
-
if (request.op === "quit" && response.ok) {
|
|
291
|
-
this.#write(socket, response, () => {
|
|
292
|
-
this.#tui.stop();
|
|
293
|
-
process.exit(0);
|
|
294
|
-
});
|
|
295
|
-
return;
|
|
296
|
-
}
|
|
297
|
-
this.#write(socket, response);
|
|
277
|
+
response = this.#dispatch(request);
|
|
298
278
|
} catch (error) {
|
|
299
|
-
|
|
279
|
+
response = { ok: false, error: errorMessage(error) };
|
|
280
|
+
}
|
|
281
|
+
this.#write(connection, response);
|
|
282
|
+
if (request.op === "quit" && response.ok) {
|
|
283
|
+
await connection.drained();
|
|
284
|
+
this.#tui.stop();
|
|
285
|
+
process.exit(0);
|
|
300
286
|
}
|
|
301
287
|
}
|
|
302
288
|
|
|
303
|
-
#write(
|
|
289
|
+
#write(connection: IpcConnection, response: DebugResponse): void {
|
|
304
290
|
let line: string;
|
|
305
291
|
try {
|
|
306
292
|
line = JSON.stringify(response);
|
|
307
293
|
} catch (error) {
|
|
308
294
|
line = JSON.stringify({ ok: false, error: errorMessage(error) });
|
|
309
295
|
}
|
|
310
|
-
|
|
311
|
-
socket.write(`${line}\n`, flushed);
|
|
312
|
-
} catch {
|
|
313
|
-
// Broken clients cannot affect the TUI.
|
|
314
|
-
}
|
|
296
|
+
connection.send(line);
|
|
315
297
|
}
|
|
316
298
|
|
|
317
299
|
#dispatch(request: DebugRequest): DebugResponse {
|
package/src/hotkeys-markdown.ts
CHANGED
|
@@ -135,6 +135,8 @@ function hotkeyGroups(bindings: HotkeysMarkdownBindings): HotkeyGroup[] {
|
|
|
135
135
|
{ keys: [act("app.history.search")], action: "Search prompt history" },
|
|
136
136
|
{ keys: [act("app.tools.expand")], action: "Toggle tool output expansion" },
|
|
137
137
|
{ keys: [act("app.tools.toggleVisibility")], action: "Toggle tool activity visibility" },
|
|
138
|
+
{ keys: [act("app.ask.hide")], action: "Hide/show a pending ask question" },
|
|
139
|
+
{ keys: [act("app.approval.approve")], action: "Approve a pending tool call" },
|
|
138
140
|
{ keys: [act("app.thinking.toggle")], action: "Toggle thinking block visibility" },
|
|
139
141
|
{ keys: [act("app.editor.external")], action: "Edit message in external editor" },
|
|
140
142
|
{ keys: [act("app.retry")], action: "Retry last failed assistant turn" },
|
|
@@ -9,6 +9,8 @@ import type { KeyId } from "./app-keybindings";
|
|
|
9
9
|
export declare function matchesAppInterrupt(data: string): boolean;
|
|
10
10
|
/** Match `app.tools.expand`, falling back to Ctrl+O when the action is unbound. */
|
|
11
11
|
export declare function matchesAppToolsExpand(data: string): boolean;
|
|
12
|
+
/** Effective tools-expand key: the first bound key, or Ctrl+O when unbound. */
|
|
13
|
+
export declare function appToolsExpandKey(): KeyId;
|
|
12
14
|
/** Match the generic selector cancel keybinding. */
|
|
13
15
|
export declare function matchesSelectCancel(data: string): boolean;
|
|
14
16
|
/** Match the generic selector up-navigation keybinding. */
|
|
@@ -36,3 +38,7 @@ export declare function matchesAppExternalEditor(data: string): boolean;
|
|
|
36
38
|
* effective follow-up binding.
|
|
37
39
|
*/
|
|
38
40
|
export declare function matchesAppFollowUp(data: string): boolean;
|
|
41
|
+
/** Effective tool-approval keys: the `app.approval.approve` bindings, or Ctrl+Enter/Ctrl+Q when unbound. */
|
|
42
|
+
export declare function appApproveKeys(): readonly KeyId[];
|
|
43
|
+
/** Match the tool-approval key, recognizing modifier-tagged LF as Ctrl+Enter like {@link matchesAppFollowUp}. */
|
|
44
|
+
export declare function matchesAppApprove(data: string): boolean;
|
|
@@ -28,6 +28,11 @@ export function matchesAppToolsExpand(data: string): boolean {
|
|
|
28
28
|
return matchesKey(data, "ctrl+o");
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
/** Effective tools-expand key: the first bound key, or Ctrl+O when unbound. */
|
|
32
|
+
export function appToolsExpandKey(): KeyId {
|
|
33
|
+
return getKeybindings().getKeys("app.tools.expand")[0] ?? "ctrl+o";
|
|
34
|
+
}
|
|
35
|
+
|
|
31
36
|
/** Match the generic selector cancel keybinding. */
|
|
32
37
|
export function matchesSelectCancel(data: string): boolean {
|
|
33
38
|
return getKeybindings().matches(data, "tui.select.cancel");
|
|
@@ -101,3 +106,17 @@ export function matchesAppFollowUp(data: string): boolean {
|
|
|
101
106
|
}
|
|
102
107
|
return matchesEffectiveKeys(data, ["ctrl+enter", "ctrl+q"]);
|
|
103
108
|
}
|
|
109
|
+
|
|
110
|
+
/** Approve-key defaults when `app.approval.approve` is unbound: approval must stay reachable by keyboard. */
|
|
111
|
+
const APPROVE_DEFAULT_KEYS: readonly KeyId[] = ["ctrl+enter", "ctrl+q"];
|
|
112
|
+
|
|
113
|
+
/** Effective tool-approval keys: the `app.approval.approve` bindings, or Ctrl+Enter/Ctrl+Q when unbound. */
|
|
114
|
+
export function appApproveKeys(): readonly KeyId[] {
|
|
115
|
+
const keys = getKeybindings().getKeys("app.approval.approve");
|
|
116
|
+
return keys.length > 0 ? keys : APPROVE_DEFAULT_KEYS;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Match the tool-approval key, recognizing modifier-tagged LF as Ctrl+Enter like {@link matchesAppFollowUp}. */
|
|
120
|
+
export function matchesAppApprove(data: string): boolean {
|
|
121
|
+
return matchesEffectiveKeys(data, appApproveKeys());
|
|
122
|
+
}
|
|
@@ -22,6 +22,7 @@ export declare const tmuxMultiplexer: {
|
|
|
22
22
|
readonly passthrough: typeof wrapTmuxPassthrough;
|
|
23
23
|
readonly settleMs: 100;
|
|
24
24
|
};
|
|
25
|
+
readonly gatesBracketedPaste: true;
|
|
25
26
|
readonly expandsRepPadding: true;
|
|
26
27
|
readonly expiresSynchronizedOutput: true;
|
|
27
28
|
readonly altRestoreEndsSynchronizedOutput: true;
|
package/src/multiplexers/tmux.ts
CHANGED
|
@@ -32,6 +32,9 @@ export const tmuxMultiplexer = {
|
|
|
32
32
|
// An explicit appearance refresh passes OSC 11 to the outer terminal, then
|
|
33
33
|
// re-reads tmux's cache once it has consumed the reply.
|
|
34
34
|
backgroundColorCache: { passthrough: wrapTmuxPassthrough, settleMs: 100 },
|
|
35
|
+
// tmux drops paste markers for a pane whose own DEC 2004 flag another
|
|
36
|
+
// writer cleared, and only answers DECRQM 2004 since 3.6.
|
|
37
|
+
gatesBracketedPaste: true,
|
|
35
38
|
expandsRepPadding: true,
|
|
36
39
|
// tmux expires synchronized output after one second.
|
|
37
40
|
expiresSynchronizedOutput: true,
|
|
@@ -73,6 +73,12 @@ export interface TerminalMultiplexerModule<Id extends string = string> {
|
|
|
73
73
|
* session, and an unrecognized report does not turn it off.
|
|
74
74
|
*/
|
|
75
75
|
readonly honorsSynchronizedOutput?: boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Tracks DEC 2004 per pane and forwards paste markers only while it is set,
|
|
78
|
+
* yet may leave DECRQM 2004 unanswered, so bracketed paste is reasserted
|
|
79
|
+
* without waiting for a DECRPM confirmation.
|
|
80
|
+
*/
|
|
81
|
+
readonly gatesBracketedPaste?: boolean;
|
|
76
82
|
/**
|
|
77
83
|
* The session does not reveal whether its client renders graphics while
|
|
78
84
|
* outer-terminal identity variables leak in, so images and Kitty
|
|
@@ -83,6 +83,12 @@ export interface TerminalMultiplexerModule<Id extends string = string> {
|
|
|
83
83
|
* session, and an unrecognized report does not turn it off.
|
|
84
84
|
*/
|
|
85
85
|
readonly honorsSynchronizedOutput?: boolean;
|
|
86
|
+
/**
|
|
87
|
+
* Tracks DEC 2004 per pane and forwards paste markers only while it is set,
|
|
88
|
+
* yet may leave DECRQM 2004 unanswered, so bracketed paste is reasserted
|
|
89
|
+
* without waiting for a DECRPM confirmation.
|
|
90
|
+
*/
|
|
91
|
+
readonly gatesBracketedPaste?: boolean;
|
|
86
92
|
/**
|
|
87
93
|
* The session does not reveal whether its client renders graphics while
|
|
88
94
|
* outer-terminal identity variables leak in, so images and Kitty
|
package/src/native/backend.d.ts
CHANGED
|
@@ -47,10 +47,11 @@ export interface NativeBackendOptions {
|
|
|
47
47
|
}
|
|
48
48
|
/**
|
|
49
49
|
* The reply a v1 terminal is assumed to give before its real `hello` arrives
|
|
50
|
-
* (the `TERM_PROGRAM=tern` optimistic start): the whole vocabulary
|
|
51
|
-
* default APC limit and credits, the terminal's
|
|
52
|
-
* already detected, full motion, and `blobs`, so a
|
|
53
|
-
* are asked about rather than resent before the
|
|
50
|
+
* (the `TERM_PROGRAM=tern` optimistic start): the whole vocabulary except
|
|
51
|
+
* {@link UNASSUMED_KINDS}, the default APC limit and credits, the terminal's
|
|
52
|
+
* width, the appearance omp already detected, full motion, and `blobs`, so a
|
|
53
|
+
* resumed session's images are asked about rather than resent before the
|
|
54
|
+
* reply comes.
|
|
54
55
|
*/
|
|
55
56
|
export declare function assumedTspHello(terminal: Terminal): TspHello;
|
|
56
57
|
export declare class NativeBackend {
|
package/src/native/backend.ts
CHANGED
|
@@ -202,12 +202,20 @@ function sameSet(a: ReadonlySet<string>, b: ReadonlySet<string>): boolean {
|
|
|
202
202
|
return true;
|
|
203
203
|
}
|
|
204
204
|
|
|
205
|
+
/**
|
|
206
|
+
* Kinds newer than some Tern builds that answer `TERM_PROGRAM=tern`: used only
|
|
207
|
+
* once the real `hello` lists them, so the optimistic start never emits a node
|
|
208
|
+
* an older terminal cannot draw (`media`: files Tern loads by path).
|
|
209
|
+
*/
|
|
210
|
+
const UNASSUMED_KINDS: Partial<Record<TspKind, true>> = { media: true };
|
|
211
|
+
|
|
205
212
|
/**
|
|
206
213
|
* The reply a v1 terminal is assumed to give before its real `hello` arrives
|
|
207
|
-
* (the `TERM_PROGRAM=tern` optimistic start): the whole vocabulary
|
|
208
|
-
* default APC limit and credits, the terminal's
|
|
209
|
-
* already detected, full motion, and `blobs`, so a
|
|
210
|
-
* are asked about rather than resent before the
|
|
214
|
+
* (the `TERM_PROGRAM=tern` optimistic start): the whole vocabulary except
|
|
215
|
+
* {@link UNASSUMED_KINDS}, the default APC limit and credits, the terminal's
|
|
216
|
+
* width, the appearance omp already detected, full motion, and `blobs`, so a
|
|
217
|
+
* resumed session's images are asked about rather than resent before the
|
|
218
|
+
* reply comes.
|
|
211
219
|
*/
|
|
212
220
|
export function assumedTspHello(terminal: Terminal): TspHello {
|
|
213
221
|
const appearance = terminal.appearance;
|
|
@@ -215,7 +223,7 @@ export function assumedTspHello(terminal: Terminal): TspHello {
|
|
|
215
223
|
r: "hello",
|
|
216
224
|
v: TSP_VERSION,
|
|
217
225
|
term: "tern",
|
|
218
|
-
kinds: TSP_KINDS,
|
|
226
|
+
kinds: TSP_KINDS.filter(kind => !UNASSUMED_KINDS[kind]),
|
|
219
227
|
features: ["blobs"],
|
|
220
228
|
apc: TSP_DEFAULT_APC_LIMIT,
|
|
221
229
|
credits: TSP_DEFAULT_CREDITS,
|
package/src/native/blobs.d.ts
CHANGED
|
@@ -1,14 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Content-addressed binary blobs (images) for `image` nodes.
|
|
3
|
-
*
|
|
4
|
-
* `describe()` registers the bytes and puts the returned id in `image.p.blob`;
|
|
5
|
-
* the native backend delivers each referenced blob once per terminal
|
|
6
|
-
* connection (asking the terminal first which it holds, or through its blob
|
|
7
|
-
* cache), else uploads it with verb `b` before the frame that first
|
|
8
|
-
* references it.
|
|
9
|
-
*/
|
|
10
1
|
import type { TspProps } from "@linxiraos/pi-wire";
|
|
11
|
-
import type { NativeNode } from "./node";
|
|
2
|
+
import type { NativeChild, NativeNode } from "./node";
|
|
12
3
|
export interface NativeBlob {
|
|
13
4
|
readonly id: string;
|
|
14
5
|
readonly mime: string;
|
|
@@ -26,8 +17,29 @@ export declare function nativeImageNode(bytes: Uint8Array, mime: string, p?: Omi
|
|
|
26
17
|
* so callers cache the node per payload ({@link NativeImageCache}).
|
|
27
18
|
*/
|
|
28
19
|
export declare function base64ImageNode(data: string, mimeType: string, p?: Omit<TspProps<"image">, "blob" | "builtin" | "w" | "h">, key?: string): NativeNode;
|
|
29
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* Props that make an `image` node open the file at `filePath` (as a `file://` URL, the TSP
|
|
22
|
+
* open-target form): a click zooms the image in the terminal, and the file opens from its
|
|
23
|
+
* context menu. Empty without a file.
|
|
24
|
+
*/
|
|
25
|
+
export declare function openableImageProps(filePath: string | undefined): Pick<TspProps<"image">, "href" | "actions">;
|
|
26
|
+
/**
|
|
27
|
+
* `image` nodes for base64 payloads, one per slot key, rebuilt only when the slot's payload
|
|
28
|
+
* or the link policy changes. Each opens its file (`sourcePath`, else a blob copy).
|
|
29
|
+
*/
|
|
30
30
|
export declare class NativeImageCache {
|
|
31
31
|
#private;
|
|
32
|
-
get(key: string, data: string, mimeType: string): NativeNode;
|
|
32
|
+
get(key: string, data: string, mimeType: string, sourcePath?: string): NativeNode;
|
|
33
33
|
}
|
|
34
|
+
/** The disk file a tool result's image was read from (`read` details `mediaPath`), when it has one. */
|
|
35
|
+
export declare function readMediaPath(details: unknown): string | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* A `media` node the host loads by path, capped at the inline image preview
|
|
38
|
+
* height. Callers check `cx.supports("media")` first.
|
|
39
|
+
*/
|
|
40
|
+
export declare function nativeMediaNode(path: string, key: string): NativeNode;
|
|
41
|
+
/** Blob ids of the `image` nodes and paths of the `media` nodes under `children` (described nodes only). */
|
|
42
|
+
export declare function shownMedia(children: readonly NativeChild[] | undefined): {
|
|
43
|
+
blobs: Set<string>;
|
|
44
|
+
paths: Set<string>;
|
|
45
|
+
};
|
package/src/native/blobs.ts
CHANGED
|
@@ -7,10 +7,13 @@
|
|
|
7
7
|
* cache), else uploads it with verb `b` before the frame that first
|
|
8
8
|
* references it.
|
|
9
9
|
*/
|
|
10
|
+
import * as url from "node:url";
|
|
10
11
|
import type { TspProps } from "@linxiraos/pi-wire";
|
|
12
|
+
import { resolveImageOptions } from "../render/render-utils";
|
|
11
13
|
import { getImageDimensions } from "../terminal-capabilities";
|
|
12
14
|
import { node } from "./describe";
|
|
13
|
-
import
|
|
15
|
+
import { imageLinksEnabled, transcriptImageLink } from "../prompt/image-references";
|
|
16
|
+
import type { NativeChild, NativeNode } from "./node";
|
|
14
17
|
|
|
15
18
|
export interface NativeBlob {
|
|
16
19
|
readonly id: string;
|
|
@@ -91,15 +94,61 @@ export function base64ImageNode(
|
|
|
91
94
|
);
|
|
92
95
|
}
|
|
93
96
|
|
|
94
|
-
/**
|
|
97
|
+
/**
|
|
98
|
+
* Props that make an `image` node open the file at `filePath` (as a `file://` URL, the TSP
|
|
99
|
+
* open-target form): a click zooms the image in the terminal, and the file opens from its
|
|
100
|
+
* context menu. Empty without a file.
|
|
101
|
+
*/
|
|
102
|
+
export function openableImageProps(filePath: string | undefined): Pick<TspProps<"image">, "href" | "actions"> {
|
|
103
|
+
return filePath ? { href: url.pathToFileURL(filePath).href, actions: { menu: ["open"] } } : {};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* `image` nodes for base64 payloads, one per slot key, rebuilt only when the slot's payload
|
|
108
|
+
* or the link policy changes. Each opens its file (`sourcePath`, else a blob copy).
|
|
109
|
+
*/
|
|
95
110
|
export class NativeImageCache {
|
|
96
|
-
#entries = new Map<string, { data: string; node: NativeNode }>();
|
|
111
|
+
#entries = new Map<string, { data: string; linked: boolean; node: NativeNode }>();
|
|
97
112
|
|
|
98
|
-
get(key: string, data: string, mimeType: string): NativeNode {
|
|
113
|
+
get(key: string, data: string, mimeType: string, sourcePath?: string): NativeNode {
|
|
114
|
+
const linked = imageLinksEnabled();
|
|
99
115
|
const cached = this.#entries.get(key);
|
|
100
|
-
if (cached?.data === data) return cached.node;
|
|
101
|
-
const
|
|
102
|
-
|
|
116
|
+
if (cached?.data === data && cached.linked === linked) return cached.node;
|
|
117
|
+
const open = openableImageProps(transcriptImageLink({ data, mimeType }, sourcePath));
|
|
118
|
+
const image = base64ImageNode(data, mimeType, { alt: mimeType, ...open }, key);
|
|
119
|
+
this.#entries.set(key, { data, linked, node: image });
|
|
103
120
|
return image;
|
|
104
121
|
}
|
|
105
122
|
}
|
|
123
|
+
|
|
124
|
+
/** The disk file a tool result's image was read from (`read` details `mediaPath`), when it has one. */
|
|
125
|
+
export function readMediaPath(details: unknown): string | undefined {
|
|
126
|
+
if (typeof details !== "object" || details === null) return undefined;
|
|
127
|
+
const mediaPath = (details as { mediaPath?: unknown }).mediaPath;
|
|
128
|
+
return typeof mediaPath === "string" && mediaPath.length > 0 ? mediaPath : undefined;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* A `media` node the host loads by path, capped at the inline image preview
|
|
133
|
+
* height. Callers check `cx.supports("media")` first.
|
|
134
|
+
*/
|
|
135
|
+
export function nativeMediaNode(path: string, key: string): NativeNode {
|
|
136
|
+
const maxRows = resolveImageOptions().maxHeightCells;
|
|
137
|
+
return node("media", { path, max: maxRows && maxRows > 0 ? { h: `${maxRows}lines` } : undefined }, undefined, key);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/** Blob ids of the `image` nodes and paths of the `media` nodes under `children` (described nodes only). */
|
|
141
|
+
export function shownMedia(children: readonly NativeChild[] | undefined): { blobs: Set<string>; paths: Set<string> } {
|
|
142
|
+
const shown = { blobs: new Set<string>(), paths: new Set<string>() };
|
|
143
|
+
const visit = (list: readonly NativeChild[] | undefined): void => {
|
|
144
|
+
for (const child of list ?? []) {
|
|
145
|
+
if (!("k" in child)) continue;
|
|
146
|
+
const p = child.p as { blob?: unknown; path?: unknown } | undefined;
|
|
147
|
+
if (child.k === "image" && typeof p?.blob === "string") shown.blobs.add(p.blob);
|
|
148
|
+
else if (child.k === "media" && typeof p?.path === "string") shown.paths.add(p.path);
|
|
149
|
+
visit(child.c);
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
visit(children);
|
|
153
|
+
return shown;
|
|
154
|
+
}
|
package/src/native/reconcile.ts
CHANGED
|
@@ -405,13 +405,31 @@ export class Reconciler {
|
|
|
405
405
|
return this.#findEditorIn(node, state.id, "", state.owner, depth);
|
|
406
406
|
}
|
|
407
407
|
|
|
408
|
-
|
|
408
|
+
/**
|
|
409
|
+
* The first editor under `node`. An `overlay`/`picker` child outside a hoisted
|
|
410
|
+
* sheet is hoisted into `layer` under its `^` keypath (see {@link #entries}), so
|
|
411
|
+
* a field in a sheet a dock component describes is found under that id.
|
|
412
|
+
*/
|
|
413
|
+
#findEditorIn(
|
|
414
|
+
node: NativeNode,
|
|
415
|
+
id: string,
|
|
416
|
+
keypath: string,
|
|
417
|
+
owner: Owner,
|
|
418
|
+
depth: number,
|
|
419
|
+
hoist = true,
|
|
420
|
+
): string | null {
|
|
409
421
|
if (node.k === "editor" || node.k === "input") return id;
|
|
410
|
-
for (const entry of this.#entries(node.c, keypath, owner,
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
422
|
+
for (const entry of this.#entries(node.c, keypath, owner, false, null)) {
|
|
423
|
+
let found: string | null;
|
|
424
|
+
if (entry.comp !== undefined) {
|
|
425
|
+
found = this.#findEditorOf(entry.comp, depth + 1);
|
|
426
|
+
} else if (hoist && (entry.node.k === "overlay" || entry.node.k === "picker")) {
|
|
427
|
+
const path = entry.keypath;
|
|
428
|
+
const portalPath = keypath === "" ? `^${path}` : `${keypath}/^${path.slice(keypath.length + 1)}`;
|
|
429
|
+
found = this.#findEditorIn(entry.node, nodeId(owner.base, portalPath), portalPath, owner, depth, false);
|
|
430
|
+
} else {
|
|
431
|
+
found = this.#findEditorIn(entry.node, entry.id, entry.keypath, owner, depth, hoist);
|
|
432
|
+
}
|
|
415
433
|
if (found !== null) return found;
|
|
416
434
|
}
|
|
417
435
|
return null;
|
|
@@ -682,24 +700,28 @@ export class Reconciler {
|
|
|
682
700
|
this.#ops.push(["text", id, "append", newText.slice(oldText.length)]);
|
|
683
701
|
return;
|
|
684
702
|
}
|
|
685
|
-
|
|
703
|
+
// A streaming `md` node rewrites only its tail (the speculatively closed span it ends in), so an
|
|
704
|
+
// edit past a shared head splices too instead of resending the whole text.
|
|
705
|
+
if ((kind === "editor" || kind === "input" || kind === "md") && oldText.length > 0) {
|
|
686
706
|
const limit = Math.min(oldText.length, newText.length);
|
|
687
707
|
let prefix = 0;
|
|
688
708
|
while (prefix < limit && oldText.charCodeAt(prefix) === newText.charCodeAt(prefix)) prefix++;
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
709
|
+
if (kind !== "md" || prefix > 0) {
|
|
710
|
+
let suffix = 0;
|
|
711
|
+
while (
|
|
712
|
+
suffix < limit - prefix &&
|
|
713
|
+
oldText.charCodeAt(oldText.length - 1 - suffix) === newText.charCodeAt(newText.length - 1 - suffix)
|
|
714
|
+
)
|
|
715
|
+
suffix++;
|
|
716
|
+
this.#ops.push([
|
|
717
|
+
"splice",
|
|
718
|
+
id,
|
|
719
|
+
prefix,
|
|
720
|
+
oldText.length - prefix - suffix,
|
|
721
|
+
newText.slice(prefix, newText.length - suffix),
|
|
722
|
+
]);
|
|
723
|
+
return;
|
|
724
|
+
}
|
|
703
725
|
}
|
|
704
726
|
this.#ops.push(["text", id, "replace", newText]);
|
|
705
727
|
}
|