@oh-my-pi/pi-coding-agent 17.0.4 → 17.0.6
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 +209 -0
- package/dist/cli.js +3616 -3676
- package/dist/types/advisor/advise-tool.d.ts +3 -0
- package/dist/types/advisor/runtime.d.ts +7 -1
- package/dist/types/async/job-manager.d.ts +2 -0
- package/dist/types/config/model-registry.d.ts +7 -0
- package/dist/types/config/model-resolver.d.ts +8 -0
- package/dist/types/config/models-config-schema.d.ts +10 -0
- package/dist/types/config/models-config.d.ts +6 -0
- package/dist/types/dap/client.d.ts +10 -0
- package/dist/types/dap/types.d.ts +6 -5
- package/dist/types/exec/bash-executor.d.ts +1 -0
- package/dist/types/extensibility/extensions/load-errors.d.ts +3 -0
- package/dist/types/extensibility/extensions/runner.d.ts +4 -2
- package/dist/types/extensibility/legacy-pi-coding-agent-shim.d.ts +16 -0
- package/dist/types/extensibility/plugins/legacy-pi-compat.d.ts +8 -0
- package/dist/types/launch/spawn-options.d.ts +10 -0
- package/dist/types/launch/spawn-options.test.d.ts +1 -0
- package/dist/types/modes/components/status-line/component.d.ts +10 -3
- package/dist/types/modes/components/status-line/types.d.ts +3 -1
- package/dist/types/modes/components/tool-execution.d.ts +2 -0
- package/dist/types/modes/components/transcript-container.d.ts +4 -3
- package/dist/types/modes/components/tree-selector.d.ts +6 -2
- package/dist/types/modes/components/usage-row.d.ts +1 -1
- package/dist/types/modes/interactive-mode.d.ts +2 -0
- package/dist/types/modes/print-mode.d.ts +4 -0
- package/dist/types/modes/rpc/rpc-client.d.ts +1 -1
- package/dist/types/modes/rpc/rpc-frame.d.ts +9 -0
- package/dist/types/modes/rpc/rpc-input.d.ts +5 -0
- package/dist/types/modes/rpc/rpc-mode.d.ts +1 -1
- package/dist/types/modes/setup-wizard/index.d.ts +1 -1
- package/dist/types/modes/setup-wizard/scenes/model.d.ts +3 -0
- package/dist/types/modes/types.d.ts +2 -0
- package/dist/types/sdk.d.ts +2 -0
- package/dist/types/session/agent-session.d.ts +42 -6
- package/dist/types/session/messages.d.ts +6 -0
- package/dist/types/session/session-paths.d.ts +21 -4
- package/dist/types/tools/browser/aria/aria-snapshot.d.ts +17 -7
- package/dist/types/tools/browser/cmux/socket-client.d.ts +2 -0
- package/dist/types/tools/browser/tab-protocol.d.ts +2 -0
- package/dist/types/tools/browser/tab-worker.d.ts +2 -0
- package/dist/types/tools/gh.d.ts +5 -3
- package/dist/types/tools/hub/index.d.ts +1 -1
- package/dist/types/tools/hub/jobs.d.ts +1 -0
- package/dist/types/tools/hub/types.d.ts +2 -0
- package/dist/types/tools/tool-timeouts.d.ts +1 -1
- package/dist/types/tools/xdev.d.ts +5 -3
- package/dist/types/utils/git.d.ts +33 -0
- package/dist/types/vibe/__tests__/token-rate.test.d.ts +1 -0
- package/dist/types/vibe/runtime.d.ts +23 -0
- package/dist/types/web/search/providers/codex.d.ts +1 -1
- package/package.json +12 -12
- package/src/advisor/__tests__/advisor.test.ts +150 -0
- package/src/advisor/advise-tool.ts +4 -0
- package/src/advisor/runtime.ts +38 -14
- package/src/async/job-manager.ts +3 -0
- package/src/cli/bench-cli.ts +8 -2
- package/src/cli/dry-balance-cli.ts +1 -0
- package/src/config/model-registry.ts +89 -8
- package/src/config/model-resolver.ts +78 -14
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings.ts +3 -1
- package/src/dap/client.ts +168 -1
- package/src/dap/config.ts +51 -1
- package/src/dap/session.ts +575 -234
- package/src/dap/types.ts +6 -5
- package/src/discovery/agents.ts +2 -2
- package/src/exec/bash-executor.ts +68 -8
- package/src/extensibility/extensions/load-errors.ts +13 -0
- package/src/extensibility/extensions/runner.ts +6 -4
- package/src/extensibility/legacy-pi-coding-agent-shim.ts +26 -0
- package/src/extensibility/plugins/legacy-pi-compat.ts +41 -6
- package/src/launch/broker.ts +34 -31
- package/src/launch/client.ts +7 -1
- package/src/launch/spawn-options.test.ts +31 -0
- package/src/launch/spawn-options.ts +17 -0
- package/src/lsp/types.ts +5 -1
- package/src/main.ts +25 -4
- package/src/mcp/transports/stdio.test.ts +9 -1
- package/src/modes/components/ask-dialog.ts +137 -73
- package/src/modes/components/chat-transcript-builder.ts +10 -1
- package/src/modes/components/status-line/component.ts +57 -3
- package/src/modes/components/status-line/segments.ts +20 -3
- package/src/modes/components/status-line/types.ts +3 -1
- package/src/modes/components/tool-execution.ts +5 -0
- package/src/modes/components/transcript-container.ts +23 -122
- package/src/modes/components/tree-selector.ts +10 -4
- package/src/modes/components/usage-row.ts +17 -1
- package/src/modes/controllers/command-controller.ts +41 -1
- package/src/modes/controllers/event-controller.ts +7 -3
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +29 -8
- package/src/modes/interactive-mode.ts +102 -60
- package/src/modes/noninteractive-dispose.test.ts +14 -1
- package/src/modes/print-mode.ts +81 -9
- package/src/modes/rpc/rpc-client.ts +76 -35
- package/src/modes/rpc/rpc-frame.ts +156 -0
- package/src/modes/rpc/rpc-input.ts +38 -0
- package/src/modes/rpc/rpc-mode.ts +11 -4
- package/src/modes/setup-wizard/index.ts +2 -0
- package/src/modes/setup-wizard/scenes/model.ts +134 -0
- package/src/modes/setup-wizard/scenes/sign-in.ts +3 -1
- package/src/modes/types.ts +2 -0
- package/src/modes/utils/ui-helpers.ts +9 -3
- package/src/prompts/system/workflow-notice.md +89 -74
- package/src/prompts/tools/browser.md +3 -2
- package/src/prompts/tools/debug.md +2 -7
- package/src/prompts/tools/github.md +6 -1
- package/src/prompts/tools/hub.md +4 -2
- package/src/prompts/tools/task-async-contract.md +1 -0
- package/src/prompts/tools/task.md +9 -2
- package/src/sdk.ts +38 -6
- package/src/session/agent-session.ts +484 -188
- package/src/session/messages.test.ts +91 -0
- package/src/session/messages.ts +248 -110
- package/src/session/session-manager.ts +34 -3
- package/src/session/session-paths.ts +38 -9
- package/src/slash-commands/builtin-registry.ts +1 -0
- package/src/task/executor.ts +104 -33
- package/src/task/index.ts +46 -9
- package/src/task/worktree.ts +10 -0
- package/src/tools/browser/aria/aria-snapshot.ts +36 -8
- package/src/tools/browser/cmux/cmux-tab.ts +2 -1
- package/src/tools/browser/cmux/socket-client.ts +139 -3
- package/src/tools/browser/run-cancellation.ts +4 -0
- package/src/tools/browser/tab-protocol.ts +2 -0
- package/src/tools/browser/tab-supervisor.ts +21 -11
- package/src/tools/browser/tab-worker.ts +199 -33
- package/src/tools/debug.ts +3 -0
- package/src/tools/gh.ts +40 -2
- package/src/tools/hub/index.ts +4 -1
- package/src/tools/hub/jobs.ts +42 -1
- package/src/tools/hub/types.ts +2 -0
- package/src/tools/tool-timeouts.ts +1 -1
- package/src/tools/xdev.ts +11 -4
- package/src/tools/yield.ts +4 -1
- package/src/utils/git.ts +237 -0
- package/src/{modes/components/status-line → utils}/token-rate.ts +6 -0
- package/src/vibe/__tests__/token-rate.test.ts +96 -0
- package/src/vibe/runtime.ts +50 -0
- package/src/web/search/index.ts +9 -5
- package/src/web/search/providers/codex.ts +195 -99
- /package/dist/types/{modes/components/status-line → utils}/token-rate.d.ts +0 -0
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
Container,
|
|
4
4
|
type NativeScrollbackCommittedRows,
|
|
5
5
|
type NativeScrollbackLiveRegion,
|
|
6
|
-
type NativeScrollbackReplay,
|
|
7
6
|
type RenderStablePrefix,
|
|
8
7
|
type ViewportTailProvider,
|
|
9
8
|
} from "@oh-my-pi/pi-tui";
|
|
@@ -19,17 +18,11 @@ interface FinalizableBlock {
|
|
|
19
18
|
/**
|
|
20
19
|
* Monotonic content version for blocks that can still mutate *after*
|
|
21
20
|
* reporting finalized (e.g. `AssistantMessageComponent`: the inline error
|
|
22
|
-
* restored at the next turn's `agent_start`, late tool-result images).
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* fully commit to native scrollback they are dropped from the local frame
|
|
28
|
-
* (compacted) — a later mutation no longer recommits on an ordinary frame
|
|
29
|
-
* (immutable history the terminal owns; recommitting would duplicate it) and
|
|
30
|
-
* instead rehydrates on the next destructive full replay
|
|
31
|
-
* ({@link "@oh-my-pi/pi-tui".NativeScrollbackReplay}). Blocks that never
|
|
32
|
-
* mutate post-finalize simply omit the method.
|
|
21
|
+
* restored at the next turn's `agent_start`, late tool-result images). The
|
|
22
|
+
* committed-scrollback render bypass only replays a block's previous rows
|
|
23
|
+
* when the version is unchanged; without this signal a post-finalize
|
|
24
|
+
* mutation would stay invisible until a global invalidation. Blocks that
|
|
25
|
+
* never mutate post-finalize simply omit the method.
|
|
33
26
|
*/
|
|
34
27
|
getTranscriptBlockVersion?(): number;
|
|
35
28
|
/**
|
|
@@ -130,8 +123,6 @@ interface BlockSegment {
|
|
|
130
123
|
sep: number;
|
|
131
124
|
/** Whether the block reported finalized when this segment was rendered. */
|
|
132
125
|
finalized: boolean;
|
|
133
|
-
/** Safe to drop from the local frame once its rows fully commit to native scrollback: produced while finalized. */
|
|
134
|
-
compactable: boolean;
|
|
135
126
|
/** Block version observed when this segment was rendered (see {@link FinalizableBlock}). */
|
|
136
127
|
version: number | undefined;
|
|
137
128
|
}
|
|
@@ -167,12 +158,7 @@ const EMPTY_TAIL: readonly string[] = [];
|
|
|
167
158
|
*/
|
|
168
159
|
export class TranscriptContainer
|
|
169
160
|
extends Container
|
|
170
|
-
implements
|
|
171
|
-
NativeScrollbackLiveRegion,
|
|
172
|
-
NativeScrollbackCommittedRows,
|
|
173
|
-
NativeScrollbackReplay,
|
|
174
|
-
RenderStablePrefix,
|
|
175
|
-
ViewportTailProvider
|
|
161
|
+
implements NativeScrollbackLiveRegion, NativeScrollbackCommittedRows, RenderStablePrefix, ViewportTailProvider
|
|
176
162
|
{
|
|
177
163
|
// Bumped to retire every block segment at once (theme change / clear); a
|
|
178
164
|
// segment is only reused when its stored generation matches.
|
|
@@ -181,6 +167,7 @@ export class TranscriptContainer
|
|
|
181
167
|
// final: the leading finalized blocks plus the first live block's declared
|
|
182
168
|
// settled rows. TUI commits rows to native scrollback only above it.
|
|
183
169
|
#nativeScrollbackLiveRegionStart: number | undefined;
|
|
170
|
+
#nativeScrollbackLiveRegionPinned = false;
|
|
184
171
|
// Persistent assembled transcript rows. Rows before the stable floor are
|
|
185
172
|
// byte-identical to the previous render; rows at/after it were re-pushed.
|
|
186
173
|
#lines: string[] = [];
|
|
@@ -190,14 +177,6 @@ export class TranscriptContainer
|
|
|
190
177
|
// Finalized blocks wholly before this boundary are immutable on-screen history;
|
|
191
178
|
// their previous contribution can be replayed without calling render().
|
|
192
179
|
#committedRows = 0;
|
|
193
|
-
// Leading children whose rows were handed to native scrollback and dropped
|
|
194
|
-
// from the local frame. Children remain owned by the session and can be
|
|
195
|
-
// re-rendered when the TUI prepares a destructive full replay.
|
|
196
|
-
#compactedChildStart = 0;
|
|
197
|
-
// Suppresses re-compaction for the rehydrating render. The TUI feeds its old
|
|
198
|
-
// committed-row count immediately before render, so resetting that count
|
|
199
|
-
// alone cannot distinguish a replay from an ordinary update.
|
|
200
|
-
#replayPending = false;
|
|
201
180
|
// Stable-prefix floor accumulated across renders since the last
|
|
202
181
|
// getRenderStablePrefixRows() read (see RenderStablePrefix: reading
|
|
203
182
|
// consumes the report and re-bases the baseline). Out-of-band renders
|
|
@@ -213,14 +192,12 @@ export class TranscriptContainer
|
|
|
213
192
|
override clear(): void {
|
|
214
193
|
this.#generation++;
|
|
215
194
|
super.clear();
|
|
216
|
-
this.#compactedChildStart = 0;
|
|
217
195
|
this.#committedRows = 0;
|
|
218
|
-
this.#replayPending = false;
|
|
219
196
|
}
|
|
220
197
|
|
|
221
198
|
override setNativeScrollbackCommittedRows(rows: number): void {
|
|
222
199
|
this.#committedRows = Number.isFinite(rows) ? Math.max(0, Math.trunc(rows)) : 0;
|
|
223
|
-
for (let i =
|
|
200
|
+
for (let i = 0; i < this.children.length; i++) {
|
|
224
201
|
const child = this.children[i]!;
|
|
225
202
|
const segment = this.#segments[i];
|
|
226
203
|
if (segment === undefined || segment.component !== child) continue;
|
|
@@ -243,18 +220,6 @@ export class TranscriptContainer
|
|
|
243
220
|
}
|
|
244
221
|
}
|
|
245
222
|
|
|
246
|
-
override prepareNativeScrollbackReplay(): void {
|
|
247
|
-
// Replay retires the old terminal tape, so descendants may discard layout
|
|
248
|
-
// locks whose only purpose was keeping that immutable history byte-stable.
|
|
249
|
-
super.prepareNativeScrollbackReplay();
|
|
250
|
-
if (this.#compactedChildStart === 0) return;
|
|
251
|
-
this.#compactedChildStart = 0;
|
|
252
|
-
this.#replayPending = true;
|
|
253
|
-
this.#generation++;
|
|
254
|
-
this.#lines.length = 0;
|
|
255
|
-
this.#stableRowsFloor = 0;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
223
|
getRenderStablePrefixRows(): number {
|
|
259
224
|
const value = Math.min(this.#stableRowsFloor, this.#lines.length);
|
|
260
225
|
this.#stableRowsFloor = this.#lines.length;
|
|
@@ -265,6 +230,11 @@ export class TranscriptContainer
|
|
|
265
230
|
return this.#nativeScrollbackLiveRegionStart;
|
|
266
231
|
}
|
|
267
232
|
|
|
233
|
+
/** Propagates viewport pinning from the first still-mutating transcript block. */
|
|
234
|
+
isNativeScrollbackLiveRegionPinned(): boolean {
|
|
235
|
+
return this.#nativeScrollbackLiveRegionPinned;
|
|
236
|
+
}
|
|
237
|
+
|
|
268
238
|
/**
|
|
269
239
|
* Whether none of `component`'s rows (per the most recent render) have
|
|
270
240
|
* entered native scrollback. Callers that retract ephemeral blocks (IRC
|
|
@@ -275,14 +245,8 @@ export class TranscriptContainer
|
|
|
275
245
|
* committed rows and is safely removable.
|
|
276
246
|
*/
|
|
277
247
|
isBlockUncommitted(component: Component): boolean {
|
|
278
|
-
const index = this.children.indexOf(component);
|
|
279
|
-
// Compacted prefix is already committed native history and must not be
|
|
280
|
-
// retracted. Compacted slots may be sparse holes after a later re-render
|
|
281
|
-
// (render only fills from #compactedChildStart), so the loop below must
|
|
282
|
-
// skip undefined entries.
|
|
283
|
-
if (index >= 0 && index < this.#compactedChildStart) return false;
|
|
284
248
|
for (const segment of this.#segments) {
|
|
285
|
-
if (segment
|
|
249
|
+
if (segment.component !== component) continue;
|
|
286
250
|
return segment.rowCount === 0 || segment.startRow >= this.#committedRows;
|
|
287
251
|
}
|
|
288
252
|
return true;
|
|
@@ -336,7 +300,7 @@ export class TranscriptContainer
|
|
|
336
300
|
if (maxRows <= 0) return EMPTY_TAIL;
|
|
337
301
|
const collected: (readonly string[])[] = [];
|
|
338
302
|
let total = 0;
|
|
339
|
-
for (let i = this.children.length - 1; i >=
|
|
303
|
+
for (let i = this.children.length - 1; i >= 0 && total < maxRows; i--) {
|
|
340
304
|
const contribution = stripPlainBlankEdges(this.children[i]!.render(width));
|
|
341
305
|
if (contribution.length === 0) continue;
|
|
342
306
|
// One blank separator sits between this block and the (already
|
|
@@ -358,9 +322,9 @@ export class TranscriptContainer
|
|
|
358
322
|
override render(width: number): readonly string[] {
|
|
359
323
|
width = Math.max(1, width);
|
|
360
324
|
this.#nativeScrollbackLiveRegionStart = undefined;
|
|
325
|
+
this.#nativeScrollbackLiveRegionPinned = false;
|
|
361
326
|
|
|
362
327
|
const count = this.children.length;
|
|
363
|
-
if (this.#compactedChildStart > count) this.#compactedChildStart = count;
|
|
364
328
|
|
|
365
329
|
// Seal displaceable snapshots whose rows are already on the tape (per the
|
|
366
330
|
// previous frame's segments — the geometry the committed count was
|
|
@@ -370,7 +334,7 @@ export class TranscriptContainer
|
|
|
370
334
|
// frame, and every frame so a block that BECAME displaceable after its
|
|
371
335
|
// pending-preview rows committed (late result on a scrolled-off call) is
|
|
372
336
|
// caught too.
|
|
373
|
-
for (let i =
|
|
337
|
+
for (let i = 0; i < count && i < this.#segments.length; i++) {
|
|
374
338
|
const previous = this.#segments[i];
|
|
375
339
|
if (previous === undefined) continue;
|
|
376
340
|
if (previous.startRow >= this.#committedRows) break;
|
|
@@ -386,10 +350,14 @@ export class TranscriptContainer
|
|
|
386
350
|
// reaches.
|
|
387
351
|
let liveStartIndex = -1;
|
|
388
352
|
let hasLiveBlock = false;
|
|
389
|
-
for (let i =
|
|
353
|
+
for (let i = 0; i < count; i++) {
|
|
390
354
|
if (!isBlockFinalized(this.children[i]!)) {
|
|
391
355
|
liveStartIndex = i;
|
|
392
356
|
hasLiveBlock = true;
|
|
357
|
+
this.#nativeScrollbackLiveRegionPinned =
|
|
358
|
+
(
|
|
359
|
+
this.children[i] as Component & Partial<NativeScrollbackLiveRegion>
|
|
360
|
+
).isNativeScrollbackLiveRegionPinned?.() === true;
|
|
393
361
|
break;
|
|
394
362
|
}
|
|
395
363
|
}
|
|
@@ -418,7 +386,7 @@ export class TranscriptContainer
|
|
|
418
386
|
// Frame row cursor: rows emitted (reused or pushed) so far.
|
|
419
387
|
let row = 0;
|
|
420
388
|
let stableRows = 0;
|
|
421
|
-
for (let i =
|
|
389
|
+
for (let i = 0; i < count; i++) {
|
|
422
390
|
const child = this.children[i]!;
|
|
423
391
|
|
|
424
392
|
// This child's contribution: its current render with plain-blank
|
|
@@ -459,7 +427,6 @@ export class TranscriptContainer
|
|
|
459
427
|
previous.width === width &&
|
|
460
428
|
previous.generation === this.#generation);
|
|
461
429
|
const contribution = reusable ? previous.contribution : stripPlainBlankEdges(raw);
|
|
462
|
-
const compactable = finalized && previous?.finalized !== false;
|
|
463
430
|
|
|
464
431
|
// Empty (or stripped-to-nothing) children contribute nothing and never
|
|
465
432
|
// affect spacing. An empty still-live child still gates the commit
|
|
@@ -484,7 +451,6 @@ export class TranscriptContainer
|
|
|
484
451
|
rowCount: 0,
|
|
485
452
|
sep: 0,
|
|
486
453
|
finalized,
|
|
487
|
-
compactable,
|
|
488
454
|
version,
|
|
489
455
|
};
|
|
490
456
|
continue;
|
|
@@ -536,7 +502,6 @@ export class TranscriptContainer
|
|
|
536
502
|
rowCount,
|
|
537
503
|
sep,
|
|
538
504
|
finalized,
|
|
539
|
-
compactable,
|
|
540
505
|
version,
|
|
541
506
|
};
|
|
542
507
|
row += rowCount;
|
|
@@ -546,72 +511,8 @@ export class TranscriptContainer
|
|
|
546
511
|
if (lines.length !== row) lines.length = row;
|
|
547
512
|
this.#segments = segments;
|
|
548
513
|
this.#stableRowsFloor = Math.min(stableFloorBefore, stableRows, row);
|
|
549
|
-
if (this.#replayPending) {
|
|
550
|
-
this.#replayPending = false;
|
|
551
|
-
} else {
|
|
552
|
-
this.#compactCommittedPrefix();
|
|
553
|
-
}
|
|
554
514
|
return lines;
|
|
555
515
|
}
|
|
556
|
-
|
|
557
|
-
#compactCommittedPrefix(): void {
|
|
558
|
-
if (this.#committedRows <= 0 || this.#compactedChildStart >= this.children.length) return;
|
|
559
|
-
const lines = this.#lines;
|
|
560
|
-
const segments = this.#segments;
|
|
561
|
-
let dropRows = 0;
|
|
562
|
-
let dropUntil = this.#compactedChildStart;
|
|
563
|
-
for (let i = this.#compactedChildStart; i < segments.length; i++) {
|
|
564
|
-
const segment = segments[i];
|
|
565
|
-
if (segment === undefined || !segment.compactable) break;
|
|
566
|
-
const segmentEnd = segment.startRow + segment.rowCount;
|
|
567
|
-
if (segmentEnd > this.#committedRows) break;
|
|
568
|
-
dropRows = segmentEnd;
|
|
569
|
-
dropUntil = i + 1;
|
|
570
|
-
}
|
|
571
|
-
const retained = segments[dropUntil];
|
|
572
|
-
if (retained !== undefined && retained.sep > 0) {
|
|
573
|
-
const committedSeparatorRows = this.#committedRows - retained.startRow;
|
|
574
|
-
if (committedSeparatorRows <= 0) {
|
|
575
|
-
dropRows = 0;
|
|
576
|
-
dropUntil = this.#compactedChildStart;
|
|
577
|
-
} else {
|
|
578
|
-
const trim = Math.min(retained.sep, committedSeparatorRows);
|
|
579
|
-
dropRows += trim;
|
|
580
|
-
retained.sep -= trim;
|
|
581
|
-
retained.rowCount -= trim;
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
if (dropRows === 0) return;
|
|
585
|
-
|
|
586
|
-
lines.splice(0, dropRows);
|
|
587
|
-
for (let i = this.#compactedChildStart; i < dropUntil; i++) {
|
|
588
|
-
const segment = segments[i];
|
|
589
|
-
if (segment === undefined) continue;
|
|
590
|
-
segments[i] = {
|
|
591
|
-
component: segment.component,
|
|
592
|
-
rawRef: EMPTY_TAIL,
|
|
593
|
-
contribution: EMPTY_TAIL,
|
|
594
|
-
width: segment.width,
|
|
595
|
-
generation: segment.generation,
|
|
596
|
-
startRow: 0,
|
|
597
|
-
rowCount: 0,
|
|
598
|
-
sep: 0,
|
|
599
|
-
finalized: true,
|
|
600
|
-
compactable: false,
|
|
601
|
-
version: segment.version,
|
|
602
|
-
};
|
|
603
|
-
}
|
|
604
|
-
for (let i = dropUntil; i < segments.length; i++) {
|
|
605
|
-
const segment = segments[i];
|
|
606
|
-
if (segment !== undefined) segment.startRow -= dropRows;
|
|
607
|
-
}
|
|
608
|
-
this.#compactedChildStart = dropUntil;
|
|
609
|
-
this.#committedRows = Math.max(0, this.#committedRows - dropRows);
|
|
610
|
-
this.#stableRowsFloor = 0;
|
|
611
|
-
if (this.#nativeScrollbackLiveRegionStart !== undefined) {
|
|
612
|
-
this.#nativeScrollbackLiveRegionStart = Math.max(0, this.#nativeScrollbackLiveRegionStart - dropRows);
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
516
|
}
|
|
616
517
|
|
|
617
518
|
/**
|
|
@@ -66,7 +66,7 @@ class TreeList implements Component {
|
|
|
66
66
|
#activePathIds: Set<string> = new Set();
|
|
67
67
|
#lastSelectedId: string | null = null;
|
|
68
68
|
|
|
69
|
-
onSelect?: (entryId: string) => void;
|
|
69
|
+
onSelect?: (entryId: string, options: { summarize: boolean }) => void;
|
|
70
70
|
onCancel?: () => void;
|
|
71
71
|
onLabelEdit?: (entryId: string, currentLabel: string | undefined) => void;
|
|
72
72
|
|
|
@@ -792,10 +792,16 @@ class TreeList implements Component {
|
|
|
792
792
|
} else if (matchesKey(keyData, "right")) {
|
|
793
793
|
// Page down
|
|
794
794
|
this.#selectedIndex = Math.min(this.#filteredNodes.length - 1, this.#selectedIndex + this.maxVisibleLines);
|
|
795
|
+
} else if (matchesKey(keyData, "shift+enter") || matchesKey(keyData, "shift+return")) {
|
|
796
|
+
// Summarize-and-switch: fork with a branch summary without the extra prompt.
|
|
797
|
+
const selected = this.#filteredNodes[this.#selectedIndex];
|
|
798
|
+
if (selected && this.onSelect) {
|
|
799
|
+
this.onSelect(selected.node.entry.id, { summarize: true });
|
|
800
|
+
}
|
|
795
801
|
} else if (matchesKey(keyData, "enter") || matchesKey(keyData, "return") || keyData === "\n") {
|
|
796
802
|
const selected = this.#filteredNodes[this.#selectedIndex];
|
|
797
803
|
if (selected && this.onSelect) {
|
|
798
|
-
this.onSelect(selected.node.entry.id);
|
|
804
|
+
this.onSelect(selected.node.entry.id, { summarize: false });
|
|
799
805
|
}
|
|
800
806
|
} else if (matchesAppInterrupt(keyData)) {
|
|
801
807
|
if (this.#searchQuery) {
|
|
@@ -923,7 +929,7 @@ export class TreeSelectorComponent extends Container {
|
|
|
923
929
|
tree: SessionTreeNode[],
|
|
924
930
|
currentLeafId: string | null,
|
|
925
931
|
terminalHeight: number,
|
|
926
|
-
onSelect: (entryId: string) => void,
|
|
932
|
+
onSelect: (entryId: string, options: { summarize: boolean }) => void,
|
|
927
933
|
onCancel: () => void,
|
|
928
934
|
private readonly onLabelChangeCallback?: (entryId: string, label: string | undefined) => void,
|
|
929
935
|
initialFilterMode: FilterMode = "default",
|
|
@@ -948,7 +954,7 @@ export class TreeSelectorComponent extends Container {
|
|
|
948
954
|
new TruncatedText(
|
|
949
955
|
theme.fg(
|
|
950
956
|
"muted",
|
|
951
|
-
"
|
|
957
|
+
"Enter: switch. Shift+Enter: summarize & switch. Shift+L: label. Ctrl+O: filter. Alt+D/T/U/L/A: filter. Type to search",
|
|
952
958
|
),
|
|
953
959
|
0,
|
|
954
960
|
0,
|
|
@@ -6,9 +6,25 @@ import { theme } from "../../modes/theme/theme";
|
|
|
6
6
|
/** Below this the rate is nonsense (cached/instant responses yield absurd tok/s). */
|
|
7
7
|
const MIN_DURATION_MS = 100;
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
/** Local `YYYY-MM-DD HH:mm:ss` stamp for the per-turn usage row. */
|
|
10
|
+
function formatUsageTimestamp(ms: number): string {
|
|
11
|
+
const d = new Date(ms);
|
|
12
|
+
const pad = (n: number): string => String(n).padStart(2, "0");
|
|
13
|
+
const date = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
14
|
+
const time = `${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
15
|
+
return `${date} ${time}`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// `timestamp` is optional and trails the throughput args to preserve the existing
|
|
19
|
+
// (usage, durationMs, ttftMs) call contract — this function is part of the package's
|
|
20
|
+
// public export surface (./modes/components/*).
|
|
21
|
+
export function createUsageRowBlock(usage: Usage, durationMs?: number, ttftMs?: number, timestamp?: number): Container {
|
|
10
22
|
const totalInput = usage.input + usage.cacheWrite;
|
|
11
23
|
const parts: string[] = [];
|
|
24
|
+
// Lead with the turn's local wall-clock time (down to the second), log-line style.
|
|
25
|
+
if (timestamp !== undefined && Number.isFinite(timestamp) && timestamp > 0) {
|
|
26
|
+
parts.push(formatUsageTimestamp(timestamp));
|
|
27
|
+
}
|
|
12
28
|
parts.push(`${theme.icon.input} ${formatNumber(totalInput)}`);
|
|
13
29
|
parts.push(`${theme.icon.output} ${formatNumber(usage.output)}`);
|
|
14
30
|
if (usage.cacheRead > 0) {
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
14
14
|
import { formatDuration, Snowflake, sanitizeText } from "@oh-my-pi/pi-utils";
|
|
15
15
|
import { shouldEnableAppendOnlyContext } from "../../config/append-only-context-mode";
|
|
16
|
+
import { type BashResult, isPersistentShellCdCommand } from "../../exec/bash-executor";
|
|
16
17
|
import { type LoadedCustomShare, loadCustomShare } from "../../export/custom-share";
|
|
17
18
|
import { shareSession } from "../../export/share";
|
|
18
19
|
import type { CompactOptions } from "../../extensibility/extensions/types";
|
|
@@ -1100,6 +1101,12 @@ export class CommandController {
|
|
|
1100
1101
|
|
|
1101
1102
|
async handleBashCommand(command: string, excludeFromContext = false): Promise<void> {
|
|
1102
1103
|
const isDeferred = this.ctx.session.isStreaming;
|
|
1104
|
+
const shouldPersistCwd = isPersistentShellCdCommand(command);
|
|
1105
|
+
if (isDeferred && shouldPersistCwd) {
|
|
1106
|
+
this.ctx.showWarning("Wait for the current response to finish or abort it before changing directories.");
|
|
1107
|
+
return;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1103
1110
|
this.ctx.bashComponent = new BashExecutionComponent(command, this.ctx.ui, excludeFromContext);
|
|
1104
1111
|
|
|
1105
1112
|
if (isDeferred) {
|
|
@@ -1120,7 +1127,6 @@ export class CommandController {
|
|
|
1120
1127
|
},
|
|
1121
1128
|
{ excludeFromContext, useUserShell: true },
|
|
1122
1129
|
);
|
|
1123
|
-
|
|
1124
1130
|
if (this.ctx.bashComponent) {
|
|
1125
1131
|
const meta = outputMeta().truncationFromSummary(result, { direction: "tail" }).get();
|
|
1126
1132
|
this.ctx.bashComponent.setComplete(result.exitCode, result.cancelled, {
|
|
@@ -1128,6 +1134,15 @@ export class CommandController {
|
|
|
1128
1134
|
truncation: meta?.truncation,
|
|
1129
1135
|
});
|
|
1130
1136
|
}
|
|
1137
|
+
try {
|
|
1138
|
+
if (shouldPersistCwd) await this.#applyBashResultCwd(result);
|
|
1139
|
+
} catch (error) {
|
|
1140
|
+
this.ctx.showError(
|
|
1141
|
+
`Bash command completed, but OMP failed to update its working directory: ${
|
|
1142
|
+
error instanceof Error ? error.message : "Unknown error"
|
|
1143
|
+
}`,
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1131
1146
|
} catch (error) {
|
|
1132
1147
|
if (this.ctx.bashComponent) {
|
|
1133
1148
|
this.ctx.bashComponent.setComplete(undefined, false);
|
|
@@ -1139,6 +1154,31 @@ export class CommandController {
|
|
|
1139
1154
|
this.ctx.ui.requestRender();
|
|
1140
1155
|
}
|
|
1141
1156
|
|
|
1157
|
+
async #moveInteractiveCwd(resolvedPath: string): Promise<void> {
|
|
1158
|
+
await this.ctx.sessionManager.moveTo(resolvedPath);
|
|
1159
|
+
await this.ctx.applyCwdChange(resolvedPath);
|
|
1160
|
+
this.ctx.updateEditorBorderColor();
|
|
1161
|
+
await this.ctx.reloadTodos();
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
async #applyBashResultCwd(result: BashResult): Promise<void> {
|
|
1165
|
+
if (result.cancelled || result.exitCode !== 0 || !result.workingDir) return;
|
|
1166
|
+
if (!path.isAbsolute(result.workingDir)) return;
|
|
1167
|
+
|
|
1168
|
+
const resolvedPath = path.resolve(result.workingDir);
|
|
1169
|
+
if (resolvedPath === path.resolve(this.ctx.sessionManager.getCwd())) return;
|
|
1170
|
+
|
|
1171
|
+
let isDirectory = false;
|
|
1172
|
+
try {
|
|
1173
|
+
isDirectory = (await fs.stat(resolvedPath)).isDirectory();
|
|
1174
|
+
} catch {
|
|
1175
|
+
isDirectory = false;
|
|
1176
|
+
}
|
|
1177
|
+
if (!isDirectory) return;
|
|
1178
|
+
|
|
1179
|
+
await this.#moveInteractiveCwd(resolvedPath);
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1142
1182
|
async handlePythonCommand(code: string, excludeFromContext = false): Promise<void> {
|
|
1143
1183
|
const isDeferred = this.ctx.session.isStreaming;
|
|
1144
1184
|
this.ctx.pythonComponent = new EvalExecutionComponent(code, this.ctx.ui, excludeFromContext);
|
|
@@ -247,7 +247,6 @@ export class EventController {
|
|
|
247
247
|
toolCallId: string,
|
|
248
248
|
result: { content: Array<{ type: string; data?: string; mimeType?: string }> },
|
|
249
249
|
): boolean {
|
|
250
|
-
if (!settings.get("terminal.showImages")) return false;
|
|
251
250
|
const assistantComponent = this.#readToolCallAssistantComponents.get(toolCallId);
|
|
252
251
|
if (!assistantComponent) return false;
|
|
253
252
|
const images: ImageContent[] = result.content
|
|
@@ -258,7 +257,7 @@ export class EventController {
|
|
|
258
257
|
.map(content => ({ type: "image", data: content.data, mimeType: content.mimeType }));
|
|
259
258
|
if (images.length === 0) return false;
|
|
260
259
|
assistantComponent.setToolResultImages(toolCallId, images);
|
|
261
|
-
return
|
|
260
|
+
return settings.get("terminal.showImages");
|
|
262
261
|
}
|
|
263
262
|
|
|
264
263
|
#insertAfterTranscriptComponent(anchor: Component | undefined, component: Component): boolean {
|
|
@@ -913,7 +912,12 @@ export class EventController {
|
|
|
913
912
|
this.#lastAssistantComponent = lastPostToolAssistantComponent ?? this.ctx.streamingComponent;
|
|
914
913
|
if (settings.get("display.showTokenUsage") && assistantUsageIsBilled(event.message.usage)) {
|
|
915
914
|
this.ctx.chatContainer.addChild(
|
|
916
|
-
createUsageRowBlock(
|
|
915
|
+
createUsageRowBlock(
|
|
916
|
+
event.message.usage,
|
|
917
|
+
event.message.duration,
|
|
918
|
+
event.message.ttft,
|
|
919
|
+
event.message.timestamp,
|
|
920
|
+
),
|
|
917
921
|
);
|
|
918
922
|
}
|
|
919
923
|
this.ctx.streamingComponent = undefined;
|
|
@@ -771,7 +771,7 @@ export class InputController {
|
|
|
771
771
|
// While loop mode is on, every user-typed prompt becomes the new loop
|
|
772
772
|
// prompt that auto-resubmits after each yield.
|
|
773
773
|
if (this.ctx.loopModeEnabled) {
|
|
774
|
-
this.ctx.
|
|
774
|
+
this.ctx.setLoopPrompt(text);
|
|
775
775
|
}
|
|
776
776
|
|
|
777
777
|
// Queue input during compaction
|
|
@@ -442,13 +442,20 @@ export class SelectorController {
|
|
|
442
442
|
break;
|
|
443
443
|
|
|
444
444
|
// Settings with UI side effects
|
|
445
|
-
case "showImages":
|
|
445
|
+
case "terminal.showImages":
|
|
446
|
+
case "showImages": {
|
|
447
|
+
const visible = value as boolean;
|
|
446
448
|
for (const child of this.ctx.chatContainer.children) {
|
|
447
449
|
if (child instanceof ToolExecutionComponent) {
|
|
448
|
-
child.setShowImages(
|
|
450
|
+
child.setShowImages(visible);
|
|
451
|
+
} else if (child instanceof AssistantMessageComponent) {
|
|
452
|
+
child.setImagesVisible(visible);
|
|
449
453
|
}
|
|
450
454
|
}
|
|
455
|
+
if (!visible) this.ctx.ui.clearInlineImages();
|
|
456
|
+
this.ctx.ui.resetDisplay();
|
|
451
457
|
break;
|
|
458
|
+
}
|
|
452
459
|
case "hideThinkingBlock":
|
|
453
460
|
this.ctx.hideThinkingBlock = value as boolean;
|
|
454
461
|
for (const child of this.ctx.chatContainer.children) {
|
|
@@ -1130,7 +1137,7 @@ export class SelectorController {
|
|
|
1130
1137
|
tree,
|
|
1131
1138
|
realLeafId,
|
|
1132
1139
|
this.ctx.ui.terminal.rows,
|
|
1133
|
-
async entryId => {
|
|
1140
|
+
async (entryId, options) => {
|
|
1134
1141
|
// Selecting the current leaf is a no-op (already there)
|
|
1135
1142
|
if (entryId === realLeafId) {
|
|
1136
1143
|
done();
|
|
@@ -1141,13 +1148,15 @@ export class SelectorController {
|
|
|
1141
1148
|
// Ask about summarization
|
|
1142
1149
|
done(); // Close selector first
|
|
1143
1150
|
|
|
1144
|
-
// Loop until user makes a complete choice or cancels to tree
|
|
1145
|
-
|
|
1151
|
+
// Loop until user makes a complete choice or cancels to tree.
|
|
1152
|
+
// Shift+Enter in the tree selector pre-answers "Summarize" and
|
|
1153
|
+
// skips the prompt entirely.
|
|
1154
|
+
let wantsSummary = options.summarize;
|
|
1146
1155
|
let customInstructions: string | undefined;
|
|
1147
1156
|
|
|
1148
1157
|
const branchSummariesEnabled = settings.get("branchSummary.enabled");
|
|
1149
1158
|
|
|
1150
|
-
while (branchSummariesEnabled) {
|
|
1159
|
+
while (!wantsSummary && branchSummariesEnabled) {
|
|
1151
1160
|
const summaryChoice = await this.ctx.showHookSelector("Summarize branch?", [
|
|
1152
1161
|
"No summary",
|
|
1153
1162
|
"Summarize",
|
|
@@ -1476,7 +1485,14 @@ export class SelectorController {
|
|
|
1476
1485
|
// focus (#5339).
|
|
1477
1486
|
onManualCodeInput: useManualInput ? () => dialog.showManualInput(MANUAL_LOGIN_PROMPT) : undefined,
|
|
1478
1487
|
});
|
|
1479
|
-
|
|
1488
|
+
// Scope the post-login refresh to the just-authenticated provider with an
|
|
1489
|
+
// `online` strategy: the default all-provider `online-if-uncached` reuses
|
|
1490
|
+
// a fresh authoritative cache row (e.g. an empty result fetched before
|
|
1491
|
+
// login), so newly persisted credentials would never re-run discovery and
|
|
1492
|
+
// models would stay unavailable in-session (#5780). Unrelated providers
|
|
1493
|
+
// are left untouched. `refreshProvider` swallows discovery failures, so
|
|
1494
|
+
// awaiting cannot reject the login.
|
|
1495
|
+
await this.ctx.session.modelRegistry.refreshProvider(providerId, "online");
|
|
1480
1496
|
const block = new TranscriptBlock();
|
|
1481
1497
|
// Name the account (and Anthropic organization) that was stored so a
|
|
1482
1498
|
// login that lands on an unintended account/subscription is visible
|
|
@@ -1516,7 +1532,12 @@ export class SelectorController {
|
|
|
1516
1532
|
return;
|
|
1517
1533
|
}
|
|
1518
1534
|
|
|
1519
|
-
|
|
1535
|
+
// Provider-scoped online refresh so the removed credential's stale
|
|
1536
|
+
// endpoint/deployment models are invalidated deterministically; the
|
|
1537
|
+
// default all-provider `online-if-uncached` would reuse the fresh
|
|
1538
|
+
// authoritative cache row and keep showing models the credential
|
|
1539
|
+
// unlocked (#5780). Other providers are left untouched.
|
|
1540
|
+
await this.ctx.session.modelRegistry.refreshProvider(providerId, "online");
|
|
1520
1541
|
const block = new TranscriptBlock();
|
|
1521
1542
|
block.addChild(
|
|
1522
1543
|
new Text(
|