@oh-my-pi/pi-coding-agent 16.2.2 → 16.2.4
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 +60 -0
- package/dist/cli.js +3682 -3615
- package/dist/types/advisor/__tests__/config.test.d.ts +1 -0
- package/dist/types/advisor/advise-tool.d.ts +8 -4
- package/dist/types/advisor/config.d.ts +88 -0
- package/dist/types/advisor/index.d.ts +1 -0
- package/dist/types/advisor/transcript-recorder.d.ts +13 -2
- package/dist/types/advisor/watchdog.d.ts +20 -0
- package/dist/types/cli/update-cli.d.ts +15 -0
- package/dist/types/collab/guest.d.ts +29 -0
- package/dist/types/collab/replication-shrink.d.ts +39 -0
- package/dist/types/config/provider-globals.d.ts +7 -0
- package/dist/types/config/settings-schema.d.ts +81 -0
- package/dist/types/debug/log-viewer.d.ts +1 -0
- package/dist/types/debug/raw-sse.d.ts +1 -0
- package/dist/types/edit/hashline/diff.d.ts +0 -11
- package/dist/types/extensibility/tool-event-input.d.ts +7 -0
- package/dist/types/extensibility/utils.d.ts +12 -0
- package/dist/types/mcp/transports/index.d.ts +1 -0
- package/dist/types/mcp/transports/sse.d.ts +20 -0
- package/dist/types/memories/index.d.ts +20 -1
- package/dist/types/modes/components/advisor-config.d.ts +59 -0
- package/dist/types/modes/components/index.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +9 -1
- package/dist/types/modes/components/settings-selector.d.ts +1 -0
- package/dist/types/modes/components/status-line/component.d.ts +35 -1
- package/dist/types/modes/components/status-line/types.d.ts +13 -1
- package/dist/types/modes/controllers/command-controller.d.ts +3 -3
- package/dist/types/modes/controllers/selector-controller.d.ts +1 -0
- package/dist/types/modes/interactive-mode.d.ts +11 -4
- package/dist/types/modes/skill-command.d.ts +32 -0
- package/dist/types/modes/types.d.ts +9 -3
- package/dist/types/session/agent-session.d.ts +58 -10
- package/dist/types/session/indexed-session-storage.d.ts +7 -1
- package/dist/types/session/messages.d.ts +26 -0
- package/dist/types/session/messages.test.d.ts +1 -0
- package/dist/types/session/session-entries.d.ts +31 -3
- package/dist/types/session/session-history-format.d.ts +6 -0
- package/dist/types/session/session-loader.d.ts +9 -1
- package/dist/types/session/session-manager.d.ts +8 -7
- package/dist/types/session/session-storage.d.ts +11 -0
- package/dist/types/session/session-title-slot.d.ts +19 -0
- package/dist/types/ssh/connection-manager.d.ts +47 -0
- package/dist/types/ssh/utils.d.ts +16 -0
- package/dist/types/task/executor.d.ts +3 -16
- package/dist/types/task/render.d.ts +0 -5
- package/dist/types/task/renderer.d.ts +13 -0
- package/dist/types/task/types.d.ts +16 -0
- package/dist/types/task/yield-assembly.d.ts +28 -0
- package/dist/types/tiny/text.d.ts +8 -0
- package/dist/types/tools/render-utils.d.ts +2 -0
- package/dist/types/tools/review.d.ts +6 -4
- package/dist/types/tools/ssh.d.ts +1 -1
- package/dist/types/tools/todo.d.ts +6 -0
- package/dist/types/tools/yield.d.ts +8 -3
- package/dist/types/utils/thinking-display.d.ts +4 -0
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +242 -10
- package/src/advisor/__tests__/config.test.ts +173 -0
- package/src/advisor/advise-tool.ts +11 -6
- package/src/advisor/config.ts +256 -0
- package/src/advisor/index.ts +1 -0
- package/src/advisor/runtime.ts +12 -2
- package/src/advisor/transcript-recorder.ts +25 -2
- package/src/advisor/watchdog.ts +57 -31
- package/src/autolearn/controller.ts +13 -22
- package/src/autoresearch/index.ts +7 -2
- package/src/cli/gc-cli.ts +17 -10
- package/src/cli/update-cli.ts +254 -0
- package/src/collab/guest.ts +43 -7
- package/src/collab/host.ts +13 -8
- package/src/collab/replication-shrink.ts +111 -0
- package/src/config/model-registry.ts +80 -18
- package/src/config/provider-globals.ts +25 -0
- package/src/config/settings-schema.ts +77 -0
- package/src/debug/index.ts +32 -7
- package/src/debug/log-viewer.ts +111 -53
- package/src/debug/raw-sse.ts +68 -48
- package/src/discovery/codex.ts +13 -5
- package/src/edit/hashline/diff.ts +57 -4
- package/src/eval/__tests__/julia-prelude.test.ts +2 -2
- package/src/eval/js/shared/local-module-loader.ts +23 -1
- package/src/export/html/template.js +13 -7
- package/src/extensibility/extensions/loader.ts +5 -3
- package/src/extensibility/extensions/wrapper.ts +9 -3
- package/src/extensibility/hooks/loader.ts +3 -3
- package/src/extensibility/hooks/tool-wrapper.ts +13 -4
- package/src/extensibility/plugins/manager.ts +2 -1
- package/src/extensibility/tool-event-input.ts +23 -0
- package/src/extensibility/utils.ts +74 -0
- package/src/internal-urls/docs-index.generated.txt +1 -1
- package/src/mcp/client.ts +3 -1
- package/src/mcp/manager.ts +12 -5
- package/src/mcp/transports/index.ts +1 -0
- package/src/mcp/transports/sse.ts +377 -0
- package/src/memories/index.ts +130 -15
- package/src/memory-backend/local-backend.ts +5 -3
- package/src/modes/components/advisor-config.ts +555 -0
- package/src/modes/components/advisor-message.ts +9 -2
- package/src/modes/components/agent-hub.ts +9 -4
- package/src/modes/components/index.ts +2 -0
- package/src/modes/components/model-selector.ts +79 -48
- package/src/modes/components/settings-selector.ts +1 -0
- package/src/modes/components/status-line/component.ts +150 -5
- package/src/modes/components/status-line/segments.ts +46 -21
- package/src/modes/components/status-line/types.ts +13 -1
- package/src/modes/components/tool-execution.ts +47 -6
- package/src/modes/controllers/command-controller.ts +27 -36
- package/src/modes/controllers/event-controller.ts +113 -1
- package/src/modes/controllers/extension-ui-controller.ts +1 -1
- package/src/modes/controllers/input-controller.ts +61 -61
- package/src/modes/controllers/selector-controller.ts +100 -9
- package/src/modes/interactive-mode.ts +74 -25
- package/src/modes/skill-command.ts +116 -0
- package/src/modes/types.ts +9 -3
- package/src/modes/utils/ui-helpers.ts +41 -23
- package/src/prompts/agents/reviewer.md +11 -10
- package/src/prompts/goals/goal-todo-context.md +12 -0
- package/src/prompts/review-custom-request.md +1 -2
- package/src/prompts/review-request.md +1 -2
- package/src/prompts/system/interrupted-thinking.md +7 -0
- package/src/prompts/system/recap-user.md +9 -0
- package/src/prompts/system/subagent-system-prompt.md +8 -5
- package/src/prompts/system/subagent-yield-reminder.md +6 -5
- package/src/prompts/system/system-prompt.md +0 -1
- package/src/prompts/tools/irc.md +2 -2
- package/src/prompts/tools/read.md +2 -2
- package/src/sdk.ts +28 -24
- package/src/session/agent-session.ts +899 -429
- package/src/session/indexed-session-storage.ts +86 -13
- package/src/session/messages.test.ts +125 -0
- package/src/session/messages.ts +172 -9
- package/src/session/redis-session-storage.ts +49 -2
- package/src/session/session-entries.ts +39 -2
- package/src/session/session-history-format.ts +29 -2
- package/src/session/session-listing.ts +54 -24
- package/src/session/session-loader.ts +66 -3
- package/src/session/session-manager.ts +115 -22
- package/src/session/session-persistence.ts +95 -1
- package/src/session/session-storage.ts +36 -0
- package/src/session/session-title-slot.ts +141 -0
- package/src/session/sql-session-storage.ts +71 -11
- package/src/slash-commands/builtin-registry.ts +23 -24
- package/src/ssh/__tests__/connection-manager-args.test.ts +123 -1
- package/src/ssh/__tests__/file-transfer-posix-guard.test.ts +55 -18
- package/src/ssh/connection-manager.ts +139 -12
- package/src/ssh/file-transfer.ts +23 -18
- package/src/ssh/ssh-executor.ts +2 -13
- package/src/ssh/utils.ts +19 -0
- package/src/task/executor.ts +21 -23
- package/src/task/render.ts +162 -20
- package/src/task/renderer.ts +14 -0
- package/src/task/types.ts +17 -0
- package/src/task/yield-assembly.ts +207 -0
- package/src/tiny/text.ts +23 -0
- package/src/tools/ask.ts +55 -4
- package/src/tools/render-utils.ts +2 -0
- package/src/tools/renderers.ts +8 -2
- package/src/tools/review.ts +17 -7
- package/src/tools/ssh.ts +8 -4
- package/src/tools/todo.ts +17 -1
- package/src/tools/yield.ts +140 -31
- package/src/utils/thinking-display.ts +15 -0
- package/src/prompts/system/autolearn-nudge.md +0 -5
|
@@ -600,6 +600,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
600
600
|
"Use the terminal's default background for the status line instead of the theme's `statusLineBg`. Powerline end caps are dropped because they need a contrasting fill to bridge into the surrounding terminal.",
|
|
601
601
|
},
|
|
602
602
|
},
|
|
603
|
+
"statusLine.compactThinkingLevel": {
|
|
604
|
+
type: "boolean",
|
|
605
|
+
default: false,
|
|
606
|
+
ui: {
|
|
607
|
+
tab: "appearance",
|
|
608
|
+
group: "Status Line",
|
|
609
|
+
label: "Compact Thinking Level",
|
|
610
|
+
description:
|
|
611
|
+
"Show the thinking level as a single icon on the model name instead of a separate ` · <level>` suffix.",
|
|
612
|
+
},
|
|
613
|
+
},
|
|
603
614
|
"tools.artifactSpillThreshold": {
|
|
604
615
|
type: "number",
|
|
605
616
|
default: 50,
|
|
@@ -1633,6 +1644,35 @@ export const SETTINGS_SCHEMA = {
|
|
|
1633
1644
|
},
|
|
1634
1645
|
},
|
|
1635
1646
|
|
|
1647
|
+
"recap.enabled": {
|
|
1648
|
+
type: "boolean",
|
|
1649
|
+
default: true,
|
|
1650
|
+
ui: {
|
|
1651
|
+
tab: "interaction",
|
|
1652
|
+
group: "Notifications",
|
|
1653
|
+
label: "Idle Recap",
|
|
1654
|
+
description: "Generate a brief LLM recap of where things stand after the terminal has been idle",
|
|
1655
|
+
},
|
|
1656
|
+
},
|
|
1657
|
+
|
|
1658
|
+
"recap.idleSeconds": {
|
|
1659
|
+
type: "number",
|
|
1660
|
+
default: 240,
|
|
1661
|
+
ui: {
|
|
1662
|
+
tab: "interaction",
|
|
1663
|
+
group: "Notifications",
|
|
1664
|
+
label: "Idle Recap Delay",
|
|
1665
|
+
description: "Seconds to wait while idle before showing the recap",
|
|
1666
|
+
options: [
|
|
1667
|
+
{ value: "60", label: "1 minute" },
|
|
1668
|
+
{ value: "120", label: "2 minutes" },
|
|
1669
|
+
{ value: "240", label: "4 minutes" },
|
|
1670
|
+
{ value: "300", label: "5 minutes" },
|
|
1671
|
+
{ value: "600", label: "10 minutes" },
|
|
1672
|
+
],
|
|
1673
|
+
},
|
|
1674
|
+
},
|
|
1675
|
+
|
|
1636
1676
|
// Collab
|
|
1637
1677
|
"collab.relayUrl": {
|
|
1638
1678
|
type: "string",
|
|
@@ -1889,6 +1929,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
1889
1929
|
},
|
|
1890
1930
|
},
|
|
1891
1931
|
|
|
1932
|
+
"compaction.remoteStreamingV2Enabled": {
|
|
1933
|
+
type: "boolean",
|
|
1934
|
+
default: true,
|
|
1935
|
+
ui: {
|
|
1936
|
+
tab: "context",
|
|
1937
|
+
group: "Compaction",
|
|
1938
|
+
label: "Remote Compaction V2",
|
|
1939
|
+
description: "Use Responses streaming compaction for compatible remote compaction models",
|
|
1940
|
+
},
|
|
1941
|
+
},
|
|
1942
|
+
|
|
1892
1943
|
"compaction.reserveTokens": { type: "number", default: 16384 },
|
|
1893
1944
|
|
|
1894
1945
|
"compaction.keepRecentTokens": { type: "number", default: 20000 },
|
|
@@ -1897,6 +1948,8 @@ export const SETTINGS_SCHEMA = {
|
|
|
1897
1948
|
|
|
1898
1949
|
"compaction.remoteEndpoint": { type: "string", default: undefined },
|
|
1899
1950
|
|
|
1951
|
+
"compaction.v2RetainedMessageBudget": { type: "number", default: 64000 },
|
|
1952
|
+
|
|
1900
1953
|
// Idle compaction
|
|
1901
1954
|
"compaction.idleEnabled": {
|
|
1902
1955
|
type: "boolean",
|
|
@@ -3796,6 +3849,17 @@ export const SETTINGS_SCHEMA = {
|
|
|
3796
3849
|
},
|
|
3797
3850
|
},
|
|
3798
3851
|
|
|
3852
|
+
"title.refreshOnReplan": {
|
|
3853
|
+
type: "boolean",
|
|
3854
|
+
default: true,
|
|
3855
|
+
ui: {
|
|
3856
|
+
tab: "tasks",
|
|
3857
|
+
group: "Modes",
|
|
3858
|
+
label: "Refresh Title on Replan",
|
|
3859
|
+
description: "Refresh generated session titles after todo init replans unless the title was set by the user",
|
|
3860
|
+
},
|
|
3861
|
+
},
|
|
3862
|
+
|
|
3799
3863
|
// Delegation
|
|
3800
3864
|
"task.isolation.mode": {
|
|
3801
3865
|
type: "enum",
|
|
@@ -4850,6 +4914,8 @@ export interface CompactionSettings {
|
|
|
4850
4914
|
autoContinue: boolean;
|
|
4851
4915
|
remoteEnabled: boolean;
|
|
4852
4916
|
remoteEndpoint: string | undefined;
|
|
4917
|
+
remoteStreamingV2Enabled: boolean;
|
|
4918
|
+
v2RetainedMessageBudget: number;
|
|
4853
4919
|
idleEnabled: boolean;
|
|
4854
4920
|
idleThresholdTokens: number;
|
|
4855
4921
|
idleTimeoutSeconds: number;
|
|
@@ -4857,6 +4923,15 @@ export interface CompactionSettings {
|
|
|
4857
4923
|
dropUseless: boolean;
|
|
4858
4924
|
}
|
|
4859
4925
|
|
|
4926
|
+
export interface RecapSettings {
|
|
4927
|
+
enabled: boolean;
|
|
4928
|
+
idleSeconds: number;
|
|
4929
|
+
}
|
|
4930
|
+
|
|
4931
|
+
export interface TitleSettings {
|
|
4932
|
+
refreshOnReplan: boolean;
|
|
4933
|
+
}
|
|
4934
|
+
|
|
4860
4935
|
export interface ContextPromotionSettings {
|
|
4861
4936
|
enabled: boolean;
|
|
4862
4937
|
}
|
|
@@ -5002,6 +5077,8 @@ export interface GcSettings {
|
|
|
5002
5077
|
/** Map group prefix -> typed settings interface */
|
|
5003
5078
|
export interface GroupTypeMap {
|
|
5004
5079
|
compaction: CompactionSettings;
|
|
5080
|
+
recap: RecapSettings;
|
|
5081
|
+
title: TitleSettings;
|
|
5005
5082
|
contextPromotion: ContextPromotionSettings;
|
|
5006
5083
|
retry: RetrySettings;
|
|
5007
5084
|
memories: MemoriesSettings;
|
package/src/debug/index.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
Container,
|
|
11
11
|
isNotificationSuppressed,
|
|
12
12
|
Loader,
|
|
13
|
+
type OverlayHandle,
|
|
13
14
|
type SelectItem,
|
|
14
15
|
SelectList,
|
|
15
16
|
Spacer,
|
|
@@ -327,18 +328,29 @@ export class DebugSelectorComponent extends Container {
|
|
|
327
328
|
return;
|
|
328
329
|
}
|
|
329
330
|
|
|
331
|
+
let overlay: OverlayHandle | undefined;
|
|
332
|
+
const close = (): void => {
|
|
333
|
+
overlay?.hide();
|
|
334
|
+
overlay = undefined;
|
|
335
|
+
void this.ctx.showDebugSelector();
|
|
336
|
+
};
|
|
330
337
|
const viewer = new DebugLogViewerComponent({
|
|
331
338
|
logs,
|
|
332
339
|
terminalRows: this.ctx.ui.terminal.rows,
|
|
333
|
-
onExit:
|
|
340
|
+
onExit: close,
|
|
334
341
|
onStatus: message => this.ctx.showStatus(message, { dim: true }),
|
|
335
342
|
onError: message => this.ctx.showError(message),
|
|
336
343
|
onUpdate: () => this.ctx.ui.requestRender(),
|
|
337
344
|
logSource,
|
|
338
345
|
});
|
|
339
346
|
|
|
340
|
-
this.ctx.
|
|
341
|
-
|
|
347
|
+
overlay = this.ctx.ui.showOverlay(viewer, {
|
|
348
|
+
anchor: "top-left",
|
|
349
|
+
width: "100%",
|
|
350
|
+
maxHeight: "100%",
|
|
351
|
+
margin: 0,
|
|
352
|
+
fullscreen: true,
|
|
353
|
+
});
|
|
342
354
|
this.ctx.ui.setFocus(viewer);
|
|
343
355
|
} catch (err) {
|
|
344
356
|
this.ctx.showError(`Failed to read logs: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -348,16 +360,29 @@ export class DebugSelectorComponent extends Container {
|
|
|
348
360
|
}
|
|
349
361
|
|
|
350
362
|
async #handleViewRawSse(): Promise<void> {
|
|
351
|
-
|
|
363
|
+
let overlay: OverlayHandle | undefined;
|
|
364
|
+
let viewer: RawSseViewerComponent | undefined;
|
|
365
|
+
const close = (): void => {
|
|
366
|
+
viewer?.dispose();
|
|
367
|
+
overlay?.hide();
|
|
368
|
+
overlay = undefined;
|
|
369
|
+
void this.ctx.showDebugSelector();
|
|
370
|
+
};
|
|
371
|
+
viewer = new RawSseViewerComponent({
|
|
352
372
|
buffer: resolveRawSseDebugBuffer(this.ctx.session),
|
|
353
373
|
terminalRows: this.ctx.ui.terminal.rows,
|
|
354
|
-
onExit:
|
|
374
|
+
onExit: close,
|
|
355
375
|
onStatus: message => this.ctx.showStatus(message, { dim: true }),
|
|
356
376
|
onUpdate: () => this.ctx.ui.requestRender(),
|
|
357
377
|
});
|
|
358
378
|
|
|
359
|
-
this.ctx.
|
|
360
|
-
|
|
379
|
+
overlay = this.ctx.ui.showOverlay(viewer, {
|
|
380
|
+
anchor: "top-left",
|
|
381
|
+
width: "100%",
|
|
382
|
+
maxHeight: "100%",
|
|
383
|
+
margin: 0,
|
|
384
|
+
fullscreen: true,
|
|
385
|
+
});
|
|
361
386
|
this.ctx.ui.setFocus(viewer);
|
|
362
387
|
this.ctx.ui.requestRender();
|
|
363
388
|
}
|
package/src/debug/log-viewer.ts
CHANGED
|
@@ -3,11 +3,13 @@ import {
|
|
|
3
3
|
extractPrintableText,
|
|
4
4
|
matchesKey,
|
|
5
5
|
padding,
|
|
6
|
+
parseSgrMouse,
|
|
6
7
|
replaceTabs,
|
|
7
8
|
truncateToWidth,
|
|
8
9
|
visibleWidth,
|
|
9
10
|
} from "@oh-my-pi/pi-tui";
|
|
10
11
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
12
|
+
import { bottomBorder, divider, row, topBorder } from "../modes/components/overlay-box";
|
|
11
13
|
import { theme } from "../modes/theme/theme";
|
|
12
14
|
import { copyToClipboard } from "../utils/clipboard";
|
|
13
15
|
import {
|
|
@@ -23,6 +25,8 @@ export const LOAD_OLDER_LABEL = "### MOVE UP TO LOAD MORE...";
|
|
|
23
25
|
|
|
24
26
|
const INITIAL_LOG_CHUNK = 50;
|
|
25
27
|
const LOAD_OLDER_CHUNK = 50;
|
|
28
|
+
const MIN_LOG_VIEWER_WIDTH = 48;
|
|
29
|
+
const LOG_VIEWER_CHROME_LINES = 8;
|
|
26
30
|
|
|
27
31
|
type LogEntry = {
|
|
28
32
|
rawLine: string;
|
|
@@ -184,6 +188,31 @@ export class DebugLogViewerModel {
|
|
|
184
188
|
}
|
|
185
189
|
}
|
|
186
190
|
|
|
191
|
+
moveCursorToRow(rowIndex: number, extendSelection: boolean): boolean {
|
|
192
|
+
const selectableIndex = this.#selectableRowIndices.indexOf(rowIndex);
|
|
193
|
+
if (selectableIndex < 0) {
|
|
194
|
+
return false;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (extendSelection && this.#selectionAnchorSelectableIndex === undefined) {
|
|
198
|
+
const row = this.#getCursorRow();
|
|
199
|
+
if (row?.kind === "log") {
|
|
200
|
+
this.#selectionAnchorSelectableIndex = this.#cursorSelectableIndex;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
this.#cursorSelectableIndex = selectableIndex;
|
|
205
|
+
|
|
206
|
+
if (!extendSelection) {
|
|
207
|
+
this.#selectionAnchorSelectableIndex = undefined;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
if (this.#getCursorRow()?.kind !== "log" && !extendSelection) {
|
|
211
|
+
this.#selectionAnchorSelectableIndex = undefined;
|
|
212
|
+
}
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
|
|
187
216
|
getSelectedLogIndices(): number[] {
|
|
188
217
|
if (this.#selectableRowIndices.length === 0) {
|
|
189
218
|
return [];
|
|
@@ -484,6 +513,9 @@ export class DebugLogViewerComponent implements Component {
|
|
|
484
513
|
#scrollRowOffset = 0;
|
|
485
514
|
#statusMessage: string | undefined;
|
|
486
515
|
#loadingOlder = false;
|
|
516
|
+
#bodyRowStart = 0;
|
|
517
|
+
#bodyRowCount = 0;
|
|
518
|
+
#bodyLineToRowIndex: Array<number | undefined> = [];
|
|
487
519
|
|
|
488
520
|
constructor(options: DebugLogViewerComponentOptions) {
|
|
489
521
|
this.#logSource = options.logSource;
|
|
@@ -501,6 +533,10 @@ export class DebugLogViewerComponent implements Component {
|
|
|
501
533
|
}
|
|
502
534
|
|
|
503
535
|
handleInput(keyData: string): void {
|
|
536
|
+
if (keyData.startsWith("\x1b[<") && this.#handleMouse(keyData)) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
|
|
504
540
|
if (matchesKey(keyData, "escape") || matchesKey(keyData, "esc")) {
|
|
505
541
|
this.#onExit();
|
|
506
542
|
return;
|
|
@@ -598,59 +634,99 @@ export class DebugLogViewerComponent implements Component {
|
|
|
598
634
|
}
|
|
599
635
|
}
|
|
600
636
|
|
|
637
|
+
#handleMouse(keyData: string): boolean {
|
|
638
|
+
const event = parseSgrMouse(keyData);
|
|
639
|
+
if (!event) return false;
|
|
640
|
+
|
|
641
|
+
const overBody = event.row >= this.#bodyRowStart && event.row < this.#bodyRowStart + this.#bodyRowCount;
|
|
642
|
+
if (event.wheel !== null && overBody) {
|
|
643
|
+
this.#statusMessage = undefined;
|
|
644
|
+
const maxOffset = Math.max(0, this.#model.rows.length - this.#bodyHeight());
|
|
645
|
+
this.#scrollRowOffset = Math.max(0, Math.min(maxOffset, this.#scrollRowOffset + event.wheel * 3));
|
|
646
|
+
this.#onUpdate?.();
|
|
647
|
+
return true;
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
if (!event.leftClick || !overBody) return false;
|
|
651
|
+
const rowIndex = this.#bodyLineToRowIndex[event.row - this.#bodyRowStart];
|
|
652
|
+
if (rowIndex === undefined) return false;
|
|
653
|
+
|
|
654
|
+
const target = this.#model.rows[rowIndex];
|
|
655
|
+
if (!target || target.kind === "warning") return false;
|
|
656
|
+
this.#statusMessage = undefined;
|
|
657
|
+
this.#model.moveCursorToRow(rowIndex, false);
|
|
658
|
+
if (target.kind === "load-older") {
|
|
659
|
+
void this.#handleLoadOlder();
|
|
660
|
+
return true;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
if (this.#model.isExpanded(target.logIndex)) {
|
|
664
|
+
this.#model.collapseSelected();
|
|
665
|
+
} else {
|
|
666
|
+
this.#model.expandSelected();
|
|
667
|
+
}
|
|
668
|
+
this.#ensureCursorVisible();
|
|
669
|
+
this.#onUpdate?.();
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
672
|
+
|
|
601
673
|
invalidate(): void {
|
|
602
674
|
// no cached child state
|
|
603
675
|
}
|
|
604
676
|
|
|
605
677
|
render(width: number): readonly string[] {
|
|
606
|
-
this.#lastRenderWidth = Math.max(
|
|
678
|
+
this.#lastRenderWidth = Math.max(MIN_LOG_VIEWER_WIDTH, width);
|
|
607
679
|
this.#ensureCursorVisible();
|
|
608
680
|
|
|
609
|
-
const
|
|
681
|
+
const contentWidth = Math.max(1, this.#lastRenderWidth - 4);
|
|
610
682
|
const bodyHeight = this.#bodyHeight();
|
|
611
683
|
|
|
612
|
-
const rows = this.#renderRows(
|
|
613
|
-
|
|
684
|
+
const rows = this.#renderRows(contentWidth);
|
|
685
|
+
this.#bodyRowStart = 4;
|
|
686
|
+
this.#bodyRowCount = bodyHeight;
|
|
687
|
+
const visibleBodyLines = this.#renderVisibleBodyLines(rows, bodyHeight);
|
|
614
688
|
|
|
615
689
|
return [
|
|
616
|
-
this.#
|
|
617
|
-
|
|
618
|
-
this.#
|
|
619
|
-
|
|
620
|
-
this.#frameSeparator(innerWidth),
|
|
690
|
+
topBorder(this.#lastRenderWidth, "Recent Logs"),
|
|
691
|
+
row(this.#summaryText(), this.#lastRenderWidth),
|
|
692
|
+
row(this.#filterText(), this.#lastRenderWidth),
|
|
693
|
+
divider(this.#lastRenderWidth),
|
|
621
694
|
...visibleBodyLines,
|
|
622
|
-
|
|
623
|
-
this.#
|
|
695
|
+
divider(this.#lastRenderWidth),
|
|
696
|
+
row(this.#statusText(), this.#lastRenderWidth),
|
|
697
|
+
row(theme.fg("dim", this.#controlsText()), this.#lastRenderWidth),
|
|
698
|
+
bottomBorder(this.#lastRenderWidth),
|
|
624
699
|
];
|
|
625
700
|
}
|
|
626
701
|
|
|
627
702
|
#summaryText(): string {
|
|
628
|
-
|
|
703
|
+
const selected = this.#model.getSelectedCount();
|
|
704
|
+
const expanded = this.#model.expandedCount;
|
|
705
|
+
return `${theme.fg("muted", "showing")} ${theme.fg("accent", `${this.#model.visibleLogCount}/${this.#model.logCount}`)} ${theme.fg("muted", "selected")} ${theme.fg(selected > 0 ? "accent" : "muted", String(selected))} ${theme.fg("muted", "expanded")} ${theme.fg(expanded > 0 ? "accent" : "muted", String(expanded))}`;
|
|
629
706
|
}
|
|
630
707
|
|
|
631
708
|
#controlsText(): string {
|
|
632
|
-
return "Esc
|
|
709
|
+
return "Esc close · Ctrl+C copy · ↑/↓/wheel move · click toggle · Shift+↑/↓ select · ←/→ collapse/expand · Ctrl+A all · Ctrl+O older · Ctrl+P pid";
|
|
633
710
|
}
|
|
634
711
|
|
|
635
712
|
#filterText(): string {
|
|
636
713
|
const sanitized = replaceTabs(sanitizeText(this.#model.filterQuery));
|
|
637
|
-
const query = sanitized.length === 0 ? "" : theme.fg("accent", sanitized);
|
|
714
|
+
const query = sanitized.length === 0 ? theme.fg("muted", "type to filter") : theme.fg("accent", sanitized);
|
|
638
715
|
const pidStatus = this.#model.isProcessFilterEnabled()
|
|
639
|
-
? theme.fg("success", "pid
|
|
640
|
-
: theme.fg("muted", "pid
|
|
641
|
-
|
|
716
|
+
? theme.fg("success", "pid on")
|
|
717
|
+
: theme.fg("muted", "pid off");
|
|
718
|
+
const loading = this.#loadingOlder ? ` ${theme.fg("warning", "loading older…")}` : "";
|
|
719
|
+
return `${theme.fg("muted", "filter")} ${query} ${pidStatus}${loading}`;
|
|
642
720
|
}
|
|
643
721
|
|
|
644
722
|
#statusText(): string {
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
}
|
|
649
|
-
return base;
|
|
723
|
+
return this.#statusMessage
|
|
724
|
+
? theme.fg("success", this.#statusMessage)
|
|
725
|
+
: theme.fg("dim", "Enter loads older when highlighted; printable keys update filter");
|
|
650
726
|
}
|
|
651
727
|
|
|
652
728
|
#bodyHeight(): number {
|
|
653
|
-
return Math.max(3, this.#terminalRows -
|
|
729
|
+
return Math.max(3, (process.stdout.rows || this.#terminalRows || 24) - LOG_VIEWER_CHROME_LINES);
|
|
654
730
|
}
|
|
655
731
|
|
|
656
732
|
async #handleLoadOlder(additionalCount: number = LOAD_OLDER_CHUNK): Promise<void> {
|
|
@@ -780,26 +856,25 @@ export class DebugLogViewerComponent implements Component {
|
|
|
780
856
|
return rendered;
|
|
781
857
|
}
|
|
782
858
|
|
|
783
|
-
#renderVisibleBodyLines(
|
|
784
|
-
|
|
785
|
-
innerWidth: number,
|
|
786
|
-
bodyHeight: number,
|
|
787
|
-
): string[] {
|
|
859
|
+
#renderVisibleBodyLines(rows: Array<{ lines: string[]; rowIndex: number }>, bodyHeight: number): string[] {
|
|
860
|
+
this.#bodyLineToRowIndex = [];
|
|
788
861
|
const lines: string[] = [];
|
|
789
862
|
if (rows.length === 0) {
|
|
790
|
-
|
|
863
|
+
this.#bodyLineToRowIndex.push(undefined);
|
|
864
|
+
lines.push(row(theme.fg("muted", "no matches"), this.#lastRenderWidth));
|
|
791
865
|
}
|
|
792
866
|
for (let i = this.#scrollRowOffset; i < rows.length; i++) {
|
|
793
|
-
const
|
|
794
|
-
if (!
|
|
867
|
+
const renderedRow = rows[i];
|
|
868
|
+
if (!renderedRow) {
|
|
795
869
|
continue;
|
|
796
870
|
}
|
|
797
871
|
|
|
798
|
-
for (const line of
|
|
872
|
+
for (const line of renderedRow.lines) {
|
|
799
873
|
if (lines.length >= bodyHeight) {
|
|
800
874
|
break;
|
|
801
875
|
}
|
|
802
|
-
|
|
876
|
+
this.#bodyLineToRowIndex.push(renderedRow.rowIndex);
|
|
877
|
+
lines.push(row(line, this.#lastRenderWidth));
|
|
803
878
|
}
|
|
804
879
|
|
|
805
880
|
if (lines.length >= bodyHeight) {
|
|
@@ -808,7 +883,8 @@ export class DebugLogViewerComponent implements Component {
|
|
|
808
883
|
}
|
|
809
884
|
|
|
810
885
|
while (lines.length < bodyHeight) {
|
|
811
|
-
|
|
886
|
+
this.#bodyLineToRowIndex.push(undefined);
|
|
887
|
+
lines.push(row("", this.#lastRenderWidth));
|
|
812
888
|
}
|
|
813
889
|
|
|
814
890
|
return lines;
|
|
@@ -840,7 +916,7 @@ export class DebugLogViewerComponent implements Component {
|
|
|
840
916
|
return;
|
|
841
917
|
}
|
|
842
918
|
const bodyHeight = Math.max(1, this.#bodyHeight());
|
|
843
|
-
const innerWidth = Math.max(1, this.#lastRenderWidth -
|
|
919
|
+
const innerWidth = Math.max(1, this.#lastRenderWidth - 4);
|
|
844
920
|
|
|
845
921
|
// Scroll up: cursor is above viewport
|
|
846
922
|
if (cursorRowIndex < this.#scrollRowOffset) {
|
|
@@ -865,24 +941,6 @@ export class DebugLogViewerComponent implements Component {
|
|
|
865
941
|
}
|
|
866
942
|
}
|
|
867
943
|
|
|
868
|
-
#frameTop(innerWidth: number): string {
|
|
869
|
-
return `${theme.boxRound.topLeft}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.topRight}`;
|
|
870
|
-
}
|
|
871
|
-
|
|
872
|
-
#frameSeparator(innerWidth: number): string {
|
|
873
|
-
return `${theme.boxRound.teeRight}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.teeLeft}`;
|
|
874
|
-
}
|
|
875
|
-
|
|
876
|
-
#frameBottom(innerWidth: number): string {
|
|
877
|
-
return `${theme.boxRound.bottomLeft}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.bottomRight}`;
|
|
878
|
-
}
|
|
879
|
-
|
|
880
|
-
#frameLine(content: string, innerWidth: number): string {
|
|
881
|
-
const truncated = truncateToWidth(content, innerWidth);
|
|
882
|
-
const remaining = Math.max(0, innerWidth - visibleWidth(truncated));
|
|
883
|
-
return `${theme.boxRound.vertical}${truncated}${padding(remaining)}${theme.boxRound.vertical}`;
|
|
884
|
-
}
|
|
885
|
-
|
|
886
944
|
#copySelected() {
|
|
887
945
|
const selectedPayload = buildLogCopyPayload(this.#model.getSelectedRawLines());
|
|
888
946
|
const selected = selectedPayload.length === 0 ? [] : selectedPayload.split("\n");
|
package/src/debug/raw-sse.ts
CHANGED
|
@@ -1,13 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
type Component,
|
|
3
|
-
matchesKey,
|
|
4
|
-
padding,
|
|
5
|
-
replaceTabs,
|
|
6
|
-
ScrollView,
|
|
7
|
-
truncateToWidth,
|
|
8
|
-
visibleWidth,
|
|
9
|
-
} from "@oh-my-pi/pi-tui";
|
|
1
|
+
import { type Component, matchesKey, parseSgrMouse, replaceTabs, ScrollView, truncateToWidth } from "@oh-my-pi/pi-tui";
|
|
10
2
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
3
|
+
import { bottomBorder, divider, row, topBorder } from "../modes/components/overlay-box";
|
|
11
4
|
import { theme } from "../modes/theme/theme";
|
|
12
5
|
import { copyToClipboard } from "../utils/clipboard";
|
|
13
6
|
import {
|
|
@@ -17,8 +10,8 @@ import {
|
|
|
17
10
|
rawSseRecordLines,
|
|
18
11
|
} from "./raw-sse-buffer";
|
|
19
12
|
|
|
20
|
-
const MIN_VIEWER_WIDTH =
|
|
21
|
-
const
|
|
13
|
+
const MIN_VIEWER_WIDTH = 40;
|
|
14
|
+
const VIEWER_CHROME_LINES = 6;
|
|
22
15
|
// `data:` lines below this width render fine on a single row; anything wider gets pretty-printed
|
|
23
16
|
// across multiple `data:` lines so streamed JSON blobs stop getting clipped by `truncateToWidth`.
|
|
24
17
|
const PRETTY_PRINT_DATA_THRESHOLD = 100;
|
|
@@ -79,6 +72,8 @@ export class RawSseViewerComponent implements Component {
|
|
|
79
72
|
#followTail = true;
|
|
80
73
|
#lastRenderWidth = MIN_VIEWER_WIDTH;
|
|
81
74
|
#statusMessage: string | undefined;
|
|
75
|
+
#bodyRowStart = 0;
|
|
76
|
+
#bodyRowCount = 0;
|
|
82
77
|
// Pretty-printed wire lines keyed by `record.sequence`. Pretty-printing is
|
|
83
78
|
// the JSON.parse + JSON.stringify per `data:` line, so we cache the result —
|
|
84
79
|
// the render path runs on every keypress and from `#maxScrollOffset()`.
|
|
@@ -98,9 +93,17 @@ export class RawSseViewerComponent implements Component {
|
|
|
98
93
|
});
|
|
99
94
|
}
|
|
100
95
|
|
|
96
|
+
dispose(): void {
|
|
97
|
+
this.#unsubscribe();
|
|
98
|
+
}
|
|
99
|
+
|
|
101
100
|
handleInput(keyData: string): void {
|
|
101
|
+
if (keyData.startsWith("\x1b[<") && this.#handleMouse(keyData)) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
102
105
|
if (matchesKey(keyData, "escape") || matchesKey(keyData, "esc")) {
|
|
103
|
-
this
|
|
106
|
+
this.dispose();
|
|
104
107
|
this.#onExit();
|
|
105
108
|
return;
|
|
106
109
|
}
|
|
@@ -145,15 +148,44 @@ export class RawSseViewerComponent implements Component {
|
|
|
145
148
|
}
|
|
146
149
|
}
|
|
147
150
|
|
|
151
|
+
#handleMouse(keyData: string): boolean {
|
|
152
|
+
const event = parseSgrMouse(keyData);
|
|
153
|
+
if (!event) return false;
|
|
154
|
+
|
|
155
|
+
const overBody = event.row >= this.#bodyRowStart && event.row < this.#bodyRowStart + this.#bodyRowCount;
|
|
156
|
+
if (event.wheel !== null && overBody) {
|
|
157
|
+
this.#followTail = false;
|
|
158
|
+
this.#scrollOffset = Math.max(0, Math.min(this.#maxScrollOffset(), this.#scrollOffset + event.wheel * 3));
|
|
159
|
+
this.#onUpdate?.();
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (!event.leftClick) return false;
|
|
164
|
+
if (event.row === 1) {
|
|
165
|
+
this.#followTail = !this.#followTail;
|
|
166
|
+
this.#followIfNeeded();
|
|
167
|
+
this.#onUpdate?.();
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
if (overBody) {
|
|
171
|
+
this.#followTail = false;
|
|
172
|
+
const clickedOffset = this.#scrollOffset + event.row - this.#bodyRowStart;
|
|
173
|
+
this.#scrollOffset = Math.max(0, Math.min(this.#maxScrollOffset(), clickedOffset));
|
|
174
|
+
this.#onUpdate?.();
|
|
175
|
+
return true;
|
|
176
|
+
}
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
148
180
|
invalidate(): void {}
|
|
149
181
|
|
|
150
182
|
render(width: number): readonly string[] {
|
|
151
183
|
this.#lastRenderWidth = Math.max(MIN_VIEWER_WIDTH, width);
|
|
152
184
|
this.#followIfNeeded();
|
|
153
185
|
|
|
154
|
-
const
|
|
186
|
+
const contentWidth = Math.max(1, this.#lastRenderWidth - 4);
|
|
155
187
|
const bodyHeight = this.#bodyHeight();
|
|
156
|
-
const rawLines = this.#renderRawLines(
|
|
188
|
+
const rawLines = this.#renderRawLines(contentWidth);
|
|
157
189
|
const sv = new ScrollView(rawLines.slice(this.#scrollOffset, this.#scrollOffset + bodyHeight), {
|
|
158
190
|
height: bodyHeight,
|
|
159
191
|
scrollbar: "auto",
|
|
@@ -161,15 +193,18 @@ export class RawSseViewerComponent implements Component {
|
|
|
161
193
|
theme: { track: t => theme.fg("muted", t), thumb: t => theme.fg("accent", t) },
|
|
162
194
|
});
|
|
163
195
|
sv.setScrollOffset(this.#scrollOffset);
|
|
164
|
-
const bodyRows = sv.render(
|
|
196
|
+
const bodyRows = sv.render(contentWidth);
|
|
197
|
+
this.#bodyRowStart = 3;
|
|
198
|
+
this.#bodyRowCount = bodyHeight;
|
|
165
199
|
|
|
166
200
|
return [
|
|
167
|
-
this.#
|
|
168
|
-
|
|
169
|
-
this.#
|
|
170
|
-
...bodyRows.map(line =>
|
|
171
|
-
|
|
172
|
-
this.#
|
|
201
|
+
topBorder(this.#lastRenderWidth, "Raw Provider Stream"),
|
|
202
|
+
row(this.#summaryText(), this.#lastRenderWidth),
|
|
203
|
+
divider(this.#lastRenderWidth),
|
|
204
|
+
...bodyRows.map(line => row(line, this.#lastRenderWidth)),
|
|
205
|
+
divider(this.#lastRenderWidth),
|
|
206
|
+
row(this.#statusText(), this.#lastRenderWidth),
|
|
207
|
+
bottomBorder(this.#lastRenderWidth),
|
|
173
208
|
];
|
|
174
209
|
}
|
|
175
210
|
|
|
@@ -221,22 +256,25 @@ export class RawSseViewerComponent implements Component {
|
|
|
221
256
|
if (key < firstSequence) this.#prettyLinesCache.delete(key);
|
|
222
257
|
}
|
|
223
258
|
}
|
|
224
|
-
|
|
225
259
|
#summaryText(): string {
|
|
226
260
|
const snapshot = this.#buffer.snapshot();
|
|
227
|
-
const last = snapshot.lastUpdatedAt
|
|
228
|
-
|
|
229
|
-
|
|
261
|
+
const last = snapshot.lastUpdatedAt
|
|
262
|
+
? `${theme.fg("muted", "last")} ${theme.fg("accent", formatRawSseIsoTime(snapshot.lastUpdatedAt))}`
|
|
263
|
+
: theme.fg("muted", "waiting for first frame");
|
|
264
|
+
const follow = this.#followTail ? theme.fg("success", "follow on") : theme.fg("warning", "follow off");
|
|
265
|
+
return `${theme.fg("muted", "events")} ${theme.fg("accent", String(snapshot.totalEvents))} ${theme.fg("muted", "records")} ${theme.fg("accent", String(snapshot.records.length))} ${last} ${follow}`;
|
|
230
266
|
}
|
|
231
267
|
|
|
232
268
|
#statusText(): string {
|
|
233
|
-
|
|
269
|
+
const help = "Esc close · Ctrl+C copy raw · End follow tail · wheel scroll · click summary toggles follow";
|
|
270
|
+
return this.#statusMessage
|
|
271
|
+
? `${theme.fg("success", this.#statusMessage)} ${theme.fg("dim", help)}`
|
|
272
|
+
: theme.fg("dim", help);
|
|
234
273
|
}
|
|
235
274
|
|
|
236
275
|
#bodyHeight(): number {
|
|
237
|
-
return Math.max(3, this.#terminalRows -
|
|
276
|
+
return Math.max(3, (process.stdout.rows || this.#terminalRows || 24) - VIEWER_CHROME_LINES);
|
|
238
277
|
}
|
|
239
|
-
|
|
240
278
|
#followIfNeeded(): void {
|
|
241
279
|
if (this.#followTail) this.#scrollToTail();
|
|
242
280
|
}
|
|
@@ -246,8 +284,8 @@ export class RawSseViewerComponent implements Component {
|
|
|
246
284
|
}
|
|
247
285
|
|
|
248
286
|
#maxScrollOffset(): number {
|
|
249
|
-
const
|
|
250
|
-
return Math.max(0, this.#renderRawLines(
|
|
287
|
+
const contentWidth = Math.max(1, this.#lastRenderWidth - 4);
|
|
288
|
+
return Math.max(0, this.#renderRawLines(contentWidth).length - this.#bodyHeight());
|
|
251
289
|
}
|
|
252
290
|
|
|
253
291
|
#copyAll(): void {
|
|
@@ -271,22 +309,4 @@ export class RawSseViewerComponent implements Component {
|
|
|
271
309
|
}
|
|
272
310
|
this.#onUpdate?.();
|
|
273
311
|
}
|
|
274
|
-
|
|
275
|
-
#frameTop(innerWidth: number): string {
|
|
276
|
-
return `${theme.boxRound.topLeft}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.topRight}`;
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
#frameSeparator(innerWidth: number): string {
|
|
280
|
-
return `${theme.boxRound.teeRight}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.teeLeft}`;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
#frameBottom(innerWidth: number): string {
|
|
284
|
-
return `${theme.boxRound.bottomLeft}${theme.boxRound.horizontal.repeat(innerWidth)}${theme.boxRound.bottomRight}`;
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
#frameLine(content: string, innerWidth: number): string {
|
|
288
|
-
const truncated = truncateToWidth(content, innerWidth);
|
|
289
|
-
const remaining = Math.max(0, innerWidth - visibleWidth(truncated));
|
|
290
|
-
return `${theme.boxRound.vertical}${truncated}${padding(remaining)}${theme.boxRound.vertical}`;
|
|
291
|
-
}
|
|
292
312
|
}
|