@tt-a1i/openpi 0.4.0 → 0.6.0
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/README.md +116 -46
- package/SETUP.md +29 -7
- package/THIRD_PARTY_NOTICES.md +16 -0
- package/assets/openpi-launch-card-v1.webp +0 -0
- package/bin/openpi.js +155 -0
- package/extensions/ai-providers/LICENSE.upstream +23 -0
- package/extensions/ai-providers/README.md +59 -0
- package/extensions/ai-providers/antigravity/credentials.ts +52 -0
- package/extensions/ai-providers/antigravity/discovery.ts +130 -0
- package/extensions/ai-providers/antigravity/google-conversion.ts +455 -0
- package/extensions/ai-providers/antigravity/models.ts +84 -0
- package/extensions/ai-providers/antigravity/oauth.ts +700 -0
- package/extensions/ai-providers/antigravity/provider.ts +1116 -0
- package/extensions/ai-providers/antigravity/routing.ts +340 -0
- package/extensions/ai-providers/antigravity/with-resolvers.d.ts +19 -0
- package/extensions/ai-providers/cursor/constants.ts +5 -0
- package/extensions/ai-providers/cursor/credentials.ts +14 -0
- package/extensions/ai-providers/cursor/discovery.ts +291 -0
- package/extensions/ai-providers/cursor/input-images.ts +106 -0
- package/extensions/ai-providers/cursor/models.ts +45 -0
- package/extensions/ai-providers/cursor/oauth.ts +263 -0
- package/extensions/ai-providers/cursor/proto.ts +1064 -0
- package/extensions/ai-providers/cursor/protobuf.ts +1171 -0
- package/extensions/ai-providers/cursor/provider.ts +1175 -0
- package/extensions/ai-providers/cursor/proxy.ts +213 -0
- package/extensions/ai-providers/cursor/with-resolvers.d.ts +12 -0
- package/extensions/ai-providers/index.ts +86 -0
- package/extensions/ai-providers/oauth-adapter.ts +81 -0
- package/extensions/ai-providers/usage.ts +10 -0
- package/extensions/background-terminals/index.ts +38 -3
- package/extensions/background-terminals/src/domain.ts +2 -0
- package/extensions/background-terminals/src/manager.ts +484 -106
- package/extensions/background-terminals/src/output.ts +33 -0
- package/extensions/background-terminals/src/prompt.ts +13 -5
- package/extensions/background-terminals/src/result-delivery.ts +47 -24
- package/extensions/clear-context/index.ts +83 -0
- package/extensions/context-pivot/index.ts +16 -6
- package/extensions/cron/index.ts +68 -27
- package/extensions/cron/schedule.ts +12 -2
- package/extensions/file-mutation-display/render.ts +17 -257
- package/extensions/file-search/src/binaries.ts +57 -41
- package/extensions/git-read/index.ts +1 -3
- package/extensions/model-info/cache-diagnostics.ts +220 -0
- package/extensions/model-info/index.ts +65 -33
- package/extensions/model-info/session-metrics.ts +96 -0
- package/extensions/plan-mode/bash-policy.ts +54 -9
- package/extensions/plan-mode/index.ts +82 -6
- package/extensions/post-edit/index.ts +16 -6
- package/extensions/sessions/git-stats.ts +258 -72
- package/extensions/sessions/index.ts +153 -86
- package/extensions/sessions/preview-cache.ts +104 -0
- package/extensions/sessions/preview-loader.ts +856 -0
- package/extensions/sessions/sessions.ts +43 -4
- package/extensions/setup/index.ts +138 -130
- package/extensions/shared/activity-status.ts +30 -0
- package/extensions/shared/agent-session-page.ts +319 -0
- package/extensions/shared/agent-tool-renderer.ts +218 -0
- package/extensions/shared/agent-transcript.ts +524 -0
- package/extensions/shared/capability-intent.ts +1 -1
- package/extensions/shared/child-session.ts +457 -21
- package/extensions/shared/completion-inbox.ts +193 -0
- package/extensions/shared/result-delivery.ts +34 -0
- package/extensions/shared/setup-config.ts +83 -34
- package/extensions/shared/setup-episode-state.ts +1 -1
- package/extensions/shared/structured-output.ts +154 -0
- package/extensions/shared/terminal-text.ts +110 -23
- package/extensions/shared/text-projection.ts +72 -15
- package/extensions/shared/tool-activity.ts +382 -0
- package/extensions/shared/tool-surface.ts +29 -2
- package/extensions/shared/transcript-viewport.ts +46 -0
- package/extensions/shared/web-observer-registry.ts +390 -0
- package/extensions/shared/worktree.ts +11 -0
- package/extensions/subagents/index.ts +313 -62
- package/extensions/subagents/navigation.ts +34 -5
- package/extensions/subagents/src/backend.ts +12 -1
- package/extensions/subagents/src/backends/pi.ts +450 -70
- package/extensions/subagents/src/domain.ts +21 -1
- package/extensions/subagents/src/manager.ts +39 -2
- package/extensions/subagents/src/prompt.ts +49 -7
- package/extensions/subagents/src/result-artifact.ts +36 -0
- package/extensions/subagents/src/result-delivery.ts +39 -14
- package/extensions/subagents/src/runtime.ts +15 -1
- package/extensions/subagents/src/ui/takeover.ts +73 -257
- package/extensions/subagents/src/ui/transcript.ts +38 -535
- package/extensions/subagents/src/ui/wait-result.ts +103 -15
- package/extensions/suggestions/src/ui.ts +10 -4
- package/extensions/tasks/index.ts +0 -3
- package/extensions/ui-customization/footer.ts +16 -45
- package/extensions/ui-customization/index.ts +0 -4
- package/extensions/user-input-fold/index.ts +42 -6
- package/extensions/web/index.ts +257 -0
- package/extensions/workflows/acceptance.ts +43 -19
- package/extensions/workflows/artifacts.ts +137 -47
- package/extensions/workflows/completion-projection.ts +459 -0
- package/extensions/workflows/coordinator.ts +8 -10
- package/extensions/workflows/dashboard.ts +175 -228
- package/extensions/workflows/handoff.ts +70 -16
- package/extensions/workflows/index.ts +501 -198
- package/extensions/workflows/journal.ts +148 -13
- package/extensions/workflows/model.ts +79 -5
- package/extensions/workflows/navigation.ts +32 -8
- package/extensions/workflows/progress-projection.ts +306 -0
- package/extensions/workflows/prompt.ts +70 -16
- package/extensions/workflows/replay-safety.ts +42 -21
- package/extensions/workflows/result-delivery.ts +214 -76
- package/extensions/workflows/retention.ts +599 -0
- package/extensions/workflows/runner.ts +389 -345
- package/extensions/workflows/sandbox-child.cjs +25 -3
- package/extensions/workflows/sandbox.ts +62 -8
- package/extensions/workflows/serialization.ts +325 -17
- package/extensions/workflows/tool-renderer.ts +22 -0
- package/extensions/workflows/transcript.ts +149 -0
- package/extensions/workspace-cleanup-guard/index.ts +54 -0
- package/extensions/workspace-cleanup-guard/workspace-provenance.ts +563 -0
- package/package.json +34 -14
- package/skills/subagents/REFERENCE.md +190 -0
- package/skills/subagents/SKILL.md +2 -1
- package/skills/workflows/REFERENCE.md +6 -4
- package/skills/workflows/SKILL.md +1 -1
- package/web/adapter/pi-adapter.ts +664 -0
- package/web/host/browser-launcher.ts +20 -0
- package/web/host/pi-coding-agent-entry.ts +162 -0
- package/web/host/static-assets.ts +4 -0
- package/web/host/terminal-status.ts +38 -0
- package/web/host/web-host.ts +1069 -0
- package/web/http-dispatcher.ts +125 -0
- package/web/protocol/types.ts +467 -0
- package/web/runtime/pi-runtime.ts +1206 -0
- package/web/runtime/types.ts +102 -0
- package/web/runtime/web-host-lease.ts +497 -0
- package/web/trace.ts +18 -0
- package/web/ui/app.js +1700 -0
- package/web/ui/index.html +142 -0
- package/web/ui/styles.css +680 -0
- package/web/vite.config.mjs +34 -0
- package/extensions/execution-convergence/active-evidence.ts +0 -129
- package/extensions/execution-convergence/index.ts +0 -442
- package/extensions/execution-convergence/workspace-provenance.ts +0 -338
- package/extensions/setup/intercom-fs-helper.cjs +0 -130
- package/extensions/setup/intercom.ts +0 -603
- package/extensions/subagents/src/backends/stub.ts +0 -303
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Takeover UI for subagents (ported from v1, rendering from the synchronous
|
|
3
3
|
* SubagentReadModel instead of live pi sessions):
|
|
4
4
|
* - SubagentDashboard: compact picker docked above the input, listing all subagents.
|
|
5
|
-
* - TakeoverView: full
|
|
6
|
-
*
|
|
5
|
+
* - TakeoverView: full read-only view of one subagent; steering remains owned
|
|
6
|
+
* by the parent model through the subagent tools.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type {
|
|
@@ -12,22 +12,15 @@ import type {
|
|
|
12
12
|
Theme,
|
|
13
13
|
} from "@earendil-works/pi-coding-agent";
|
|
14
14
|
import type { Component, Focusable, TUI } from "@earendil-works/pi-tui";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
panelFrame,
|
|
19
|
-
type ScreenHint,
|
|
20
|
-
} from "../../../shared/screen-chrome.ts";
|
|
15
|
+
import { truncateToWidth, visibleWidth } from "@earendil-works/pi-tui";
|
|
16
|
+
import { AgentSessionPage } from "../../../shared/agent-session-page.ts";
|
|
17
|
+
import { hintLine, panelFrame } from "../../../shared/screen-chrome.ts";
|
|
21
18
|
import { sanitizeTerminalText } from "../../../shared/terminal-text.ts";
|
|
22
19
|
import { formatElapsed, type SubagentSnapshot } from "../domain.ts";
|
|
23
20
|
import { formatContextUtilization } from "../../../shared/context-utilization.ts";
|
|
21
|
+
import { SPINNER_INTERVAL_MS, spinnerFrame } from "../../../shared/spinner.ts";
|
|
24
22
|
import type { SubagentReadModel } from "../manager.ts";
|
|
25
|
-
import {
|
|
26
|
-
buildTranscriptLines,
|
|
27
|
-
SPINNER_INTERVAL_MS,
|
|
28
|
-
spinnerFrame,
|
|
29
|
-
TranscriptRenderer,
|
|
30
|
-
} from "./transcript.ts";
|
|
23
|
+
import { subagentTranscriptDocument } from "./transcript.ts";
|
|
31
24
|
|
|
32
25
|
export function sanitizeSubagentDisplayLine(value: string) {
|
|
33
26
|
return sanitizeTerminalText(value).replace(/\s+/g, " ").trim();
|
|
@@ -43,7 +36,7 @@ function configuredKeys(
|
|
|
43
36
|
/**
|
|
44
37
|
* One spinner definition for the whole subagent UI: the dashboard glyph, the
|
|
45
38
|
* takeover header, and the transcript's live tools must animate in step, so the
|
|
46
|
-
* frames and their cadence live in `
|
|
39
|
+
* frames and their cadence live in `shared/spinner.ts` and are imported here.
|
|
47
40
|
*/
|
|
48
41
|
function statusGlyph(
|
|
49
42
|
snap: SubagentSnapshot,
|
|
@@ -69,6 +62,8 @@ function statusGlyph(
|
|
|
69
62
|
|
|
70
63
|
export interface TakeoverOptions {
|
|
71
64
|
readonly badge?: string;
|
|
65
|
+
/** Captured from the parent UI when this child page opens. */
|
|
66
|
+
readonly toolsExpanded?: boolean;
|
|
72
67
|
}
|
|
73
68
|
|
|
74
69
|
export async function openSubagentTakeover(
|
|
@@ -78,12 +73,24 @@ export async function openSubagentTakeover(
|
|
|
78
73
|
options?: TakeoverOptions,
|
|
79
74
|
) {
|
|
80
75
|
if (!view.get(id)) return;
|
|
76
|
+
const takeoverOptions: TakeoverOptions = {
|
|
77
|
+
...options,
|
|
78
|
+
toolsExpanded: ctx.ui.getToolsExpanded(),
|
|
79
|
+
};
|
|
81
80
|
await ctx.ui.custom<null>(
|
|
82
81
|
(tui, theme, keybindings, done) =>
|
|
83
|
-
new TakeoverView(
|
|
82
|
+
new TakeoverView(
|
|
83
|
+
tui,
|
|
84
|
+
theme,
|
|
85
|
+
keybindings,
|
|
86
|
+
id,
|
|
87
|
+
view,
|
|
88
|
+
done,
|
|
89
|
+
takeoverOptions,
|
|
90
|
+
),
|
|
84
91
|
{
|
|
85
92
|
overlay: true,
|
|
86
|
-
overlayOptions: { anchor: "
|
|
93
|
+
overlayOptions: { anchor: "top-left", width: "100%", maxHeight: "100%" },
|
|
87
94
|
},
|
|
88
95
|
);
|
|
89
96
|
}
|
|
@@ -387,33 +394,18 @@ export class SubagentDashboard implements Component {
|
|
|
387
394
|
|
|
388
395
|
// --- Takeover view ------------------------------------------------------------
|
|
389
396
|
|
|
390
|
-
const TRANSCRIPT_SCROLL_STEP = 6;
|
|
391
|
-
|
|
392
397
|
export class TakeoverView implements Component, Focusable {
|
|
393
|
-
private
|
|
394
|
-
private theme: Theme;
|
|
395
|
-
private keybindings: KeybindingsManager;
|
|
396
|
-
private id: string;
|
|
397
|
-
private view: SubagentReadModel;
|
|
398
|
-
private done: (value: null) => void;
|
|
399
|
-
private options?: TakeoverOptions;
|
|
400
|
-
|
|
401
|
-
private input = new Input();
|
|
402
|
-
private transcriptRenderer = new TranscriptRenderer();
|
|
403
|
-
/** Scroll offset in lines from the bottom of the transcript. 0 = pinned to bottom. */
|
|
404
|
-
private scrollOffset = 0;
|
|
398
|
+
private page: AgentSessionPage;
|
|
405
399
|
private unsubscribe: () => void;
|
|
406
400
|
private renderTimer?: ReturnType<typeof setTimeout>;
|
|
407
401
|
private ticker?: ReturnType<typeof setInterval>;
|
|
408
402
|
private closed = false;
|
|
409
403
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
return this._focused;
|
|
404
|
+
get focused() {
|
|
405
|
+
return this.page.focused;
|
|
413
406
|
}
|
|
414
407
|
set focused(value: boolean) {
|
|
415
|
-
this.
|
|
416
|
-
this.input.focused = value;
|
|
408
|
+
this.page.focused = value;
|
|
417
409
|
}
|
|
418
410
|
|
|
419
411
|
constructor(
|
|
@@ -425,46 +417,58 @@ export class TakeoverView implements Component, Focusable {
|
|
|
425
417
|
done: (value: null) => void,
|
|
426
418
|
options?: TakeoverOptions,
|
|
427
419
|
) {
|
|
428
|
-
this.
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
420
|
+
this.page = new AgentSessionPage(
|
|
421
|
+
tui,
|
|
422
|
+
theme,
|
|
423
|
+
keybindings,
|
|
424
|
+
{
|
|
425
|
+
getState: () => {
|
|
426
|
+
const snap = view.get(id);
|
|
427
|
+
if (!snap) return undefined;
|
|
428
|
+
return {
|
|
429
|
+
id: snap.id,
|
|
430
|
+
title: snap.title,
|
|
431
|
+
status: snap.status,
|
|
432
|
+
document: subagentTranscriptDocument(
|
|
433
|
+
snap,
|
|
434
|
+
view.getToolRenderer?.(id),
|
|
435
|
+
),
|
|
436
|
+
metadata: [
|
|
437
|
+
options?.badge,
|
|
438
|
+
snap.meta.modelLabel,
|
|
439
|
+
formatContextUtilization(snap.usage),
|
|
440
|
+
formatElapsed(snap),
|
|
441
|
+
],
|
|
442
|
+
errorText: snap.errorText,
|
|
443
|
+
};
|
|
444
|
+
},
|
|
445
|
+
close: () => this.close(done),
|
|
446
|
+
abort: () => view.requestAbort(id),
|
|
447
|
+
},
|
|
448
|
+
{ toolsExpanded: options?.toolsExpanded },
|
|
449
|
+
);
|
|
435
450
|
this.unsubscribe = view.subscribeTo(id, () => {
|
|
436
|
-
this.refreshTicker();
|
|
437
|
-
this.scheduleRender();
|
|
451
|
+
this.refreshTicker(view.get(id), tui);
|
|
452
|
+
this.scheduleRender(tui);
|
|
438
453
|
});
|
|
439
|
-
this.refreshTicker();
|
|
440
|
-
this.input.onSubmit = (value: string) => {
|
|
441
|
-
const text = value.trim();
|
|
442
|
-
if (!text) return;
|
|
443
|
-
this.input.setValue("");
|
|
444
|
-
this.view.requestSend(this.id, text);
|
|
445
|
-
this.scrollOffset = 0;
|
|
446
|
-
this.tui.requestRender();
|
|
447
|
-
};
|
|
454
|
+
this.refreshTicker(view.get(id), tui);
|
|
448
455
|
}
|
|
449
456
|
|
|
450
|
-
private snap
|
|
451
|
-
return this.view.get(this.id);
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
private refreshTicker() {
|
|
455
|
-
const interval =
|
|
456
|
-
this.snap()?.status === "running" ? SPINNER_INTERVAL_MS : 1000;
|
|
457
|
+
private refreshTicker(snap: SubagentSnapshot | undefined, tui: TUI) {
|
|
457
458
|
if (this.ticker) clearInterval(this.ticker);
|
|
458
|
-
this.ticker =
|
|
459
|
+
this.ticker = undefined;
|
|
460
|
+
if (snap?.status === "running") {
|
|
461
|
+
this.ticker = setInterval(() => tui.requestRender(), SPINNER_INTERVAL_MS);
|
|
462
|
+
}
|
|
459
463
|
}
|
|
460
464
|
|
|
461
|
-
private scheduleRender() {
|
|
465
|
+
private scheduleRender(tui: TUI) {
|
|
462
466
|
if (this.renderTimer) return;
|
|
463
467
|
// Streaming can emit an event per token. Limit terminal repaints so this
|
|
464
468
|
// view cannot starve input handling or make the child look frozen.
|
|
465
469
|
this.renderTimer = setTimeout(() => {
|
|
466
470
|
this.renderTimer = undefined;
|
|
467
|
-
if (!this.closed)
|
|
471
|
+
if (!this.closed) tui.requestRender();
|
|
468
472
|
}, 50);
|
|
469
473
|
}
|
|
470
474
|
|
|
@@ -478,8 +482,8 @@ export class TakeoverView implements Component, Focusable {
|
|
|
478
482
|
return true;
|
|
479
483
|
}
|
|
480
484
|
|
|
481
|
-
private close() {
|
|
482
|
-
if (this.cleanup())
|
|
485
|
+
private close(done: (value: null) => void) {
|
|
486
|
+
if (this.cleanup()) done(null);
|
|
483
487
|
}
|
|
484
488
|
|
|
485
489
|
dispose(): void {
|
|
@@ -487,202 +491,14 @@ export class TakeoverView implements Component, Focusable {
|
|
|
487
491
|
}
|
|
488
492
|
|
|
489
493
|
handleInput(data: string): void {
|
|
490
|
-
|
|
491
|
-
const snap = this.snap();
|
|
492
|
-
if (snap?.status === "running") this.view.requestAbort(this.id);
|
|
493
|
-
return;
|
|
494
|
-
}
|
|
495
|
-
if (
|
|
496
|
-
this.keybindings.matches(data, "app.interrupt") ||
|
|
497
|
-
this.keybindings.matches(data, "tui.select.cancel")
|
|
498
|
-
) {
|
|
499
|
-
this.close();
|
|
500
|
-
return;
|
|
501
|
-
}
|
|
502
|
-
if (this.keybindings.matches(data, "tui.editor.cursorUp")) {
|
|
503
|
-
this.scrollOffset += TRANSCRIPT_SCROLL_STEP;
|
|
504
|
-
this.tui.requestRender();
|
|
505
|
-
return;
|
|
506
|
-
}
|
|
507
|
-
if (this.keybindings.matches(data, "tui.editor.cursorDown")) {
|
|
508
|
-
this.scrollOffset = Math.max(
|
|
509
|
-
0,
|
|
510
|
-
this.scrollOffset - TRANSCRIPT_SCROLL_STEP,
|
|
511
|
-
);
|
|
512
|
-
this.tui.requestRender();
|
|
513
|
-
return;
|
|
514
|
-
}
|
|
515
|
-
if (this.keybindings.matches(data, "tui.editor.pageUp")) {
|
|
516
|
-
this.scrollOffset += this.viewportHeight();
|
|
517
|
-
this.tui.requestRender();
|
|
518
|
-
return;
|
|
519
|
-
}
|
|
520
|
-
if (this.keybindings.matches(data, "tui.editor.pageDown")) {
|
|
521
|
-
this.scrollOffset = Math.max(
|
|
522
|
-
0,
|
|
523
|
-
this.scrollOffset - this.viewportHeight(),
|
|
524
|
-
);
|
|
525
|
-
this.tui.requestRender();
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
this.input.handleInput(data);
|
|
529
|
-
this.tui.requestRender();
|
|
530
|
-
}
|
|
531
|
-
|
|
532
|
-
private viewportHeight(): number {
|
|
533
|
-
const rows = this.tui.terminal.rows || 30;
|
|
534
|
-
// Top rule, transcript rule, input, key hints, and bottom rule are five
|
|
535
|
-
// chrome rows. The overlay leaves Pi's final footer row visible.
|
|
536
|
-
return Math.max(1, rows - 6);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
private rule(width: number, left = "", right = "") {
|
|
540
|
-
const fill = "─";
|
|
541
|
-
const available = Math.max(1, width);
|
|
542
|
-
const leftWidth = visibleWidth(left);
|
|
543
|
-
const rightWidth = visibleWidth(right);
|
|
544
|
-
if (!right || leftWidth + rightWidth + 2 > available) {
|
|
545
|
-
return truncateToWidth(
|
|
546
|
-
left + fill.repeat(Math.max(0, available - leftWidth)),
|
|
547
|
-
available,
|
|
548
|
-
);
|
|
549
|
-
}
|
|
550
|
-
return (
|
|
551
|
-
left +
|
|
552
|
-
fill.repeat(Math.max(1, available - leftWidth - rightWidth)) +
|
|
553
|
-
right
|
|
554
|
-
);
|
|
494
|
+
this.page.handleInput(data);
|
|
555
495
|
}
|
|
556
496
|
|
|
557
497
|
render(width: number): string[] {
|
|
558
|
-
|
|
559
|
-
const now = Date.now();
|
|
560
|
-
const lines: string[] = [];
|
|
561
|
-
const snap = this.snap();
|
|
562
|
-
|
|
563
|
-
if (!snap) {
|
|
564
|
-
const border = theme.fg("borderAccent", "─".repeat(Math.max(1, width)));
|
|
565
|
-
lines.push(
|
|
566
|
-
border,
|
|
567
|
-
theme.fg("dim", `${this.id} is no longer tracked`),
|
|
568
|
-
border,
|
|
569
|
-
);
|
|
570
|
-
return lines;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
const title = sanitizeSubagentDisplayLine(snap.title) || snap.id;
|
|
574
|
-
const headerLeft =
|
|
575
|
-
theme.fg("borderAccent", "─ ") +
|
|
576
|
-
statusGlyph(snap, theme, now) +
|
|
577
|
-
" " +
|
|
578
|
-
theme.fg("accent", theme.bold(title)) +
|
|
579
|
-
theme.fg("borderAccent", " ");
|
|
580
|
-
const utilization = formatContextUtilization(snap.usage);
|
|
581
|
-
const metadata = [
|
|
582
|
-
...(this.options?.badge
|
|
583
|
-
? [theme.fg("muted", sanitizeSubagentDisplayLine(this.options.badge))]
|
|
584
|
-
: []),
|
|
585
|
-
theme.fg(
|
|
586
|
-
"muted",
|
|
587
|
-
sanitizeSubagentDisplayLine(snap.meta.modelLabel ?? "?") || "?",
|
|
588
|
-
),
|
|
589
|
-
...(utilization ? [theme.fg("muted", utilization)] : []),
|
|
590
|
-
theme.fg("muted", formatElapsed(snap)),
|
|
591
|
-
];
|
|
592
|
-
const dot = theme.fg("dim", " · ");
|
|
593
|
-
while (
|
|
594
|
-
metadata.length > 1 &&
|
|
595
|
-
visibleWidth(headerLeft) + visibleWidth(metadata.join(dot)) + 2 > width
|
|
596
|
-
) {
|
|
597
|
-
metadata.shift();
|
|
598
|
-
}
|
|
599
|
-
lines.push(
|
|
600
|
-
this.rule(
|
|
601
|
-
width,
|
|
602
|
-
truncateToWidth(
|
|
603
|
-
headerLeft,
|
|
604
|
-
Math.max(1, width - visibleWidth(metadata.join(dot)) - 2),
|
|
605
|
-
),
|
|
606
|
-
metadata.join(dot),
|
|
607
|
-
),
|
|
608
|
-
);
|
|
609
|
-
|
|
610
|
-
// Fixed-height transcript viewport. Errors consume a row, but scroll state
|
|
611
|
-
// is represented by the following rule so its height never changes.
|
|
612
|
-
// `now` is shared with the header glyph so both spinners show one frame.
|
|
613
|
-
const transcript = buildTranscriptLines(
|
|
614
|
-
snap,
|
|
615
|
-
width,
|
|
616
|
-
theme,
|
|
617
|
-
this.transcriptRenderer,
|
|
618
|
-
{ now },
|
|
619
|
-
);
|
|
620
|
-
const viewport = this.viewportHeight();
|
|
621
|
-
const errorRows = snap.errorText ? 1 : 0;
|
|
622
|
-
const transcriptCapacity = Math.max(1, viewport - errorRows);
|
|
623
|
-
const maxOffset = Math.max(0, transcript.length - transcriptCapacity);
|
|
624
|
-
if (this.scrollOffset > maxOffset) this.scrollOffset = maxOffset;
|
|
625
|
-
|
|
626
|
-
const body: string[] = [];
|
|
627
|
-
if (snap.errorText) {
|
|
628
|
-
body.push(
|
|
629
|
-
truncateToWidth(
|
|
630
|
-
theme.fg(
|
|
631
|
-
"error",
|
|
632
|
-
`error: ${sanitizeSubagentDisplayLine(snap.errorText)}`,
|
|
633
|
-
),
|
|
634
|
-
width,
|
|
635
|
-
),
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
|
-
const end = transcript.length - this.scrollOffset;
|
|
639
|
-
const visible = transcript.slice(
|
|
640
|
-
Math.max(0, end - Math.max(1, viewport - body.length)),
|
|
641
|
-
end,
|
|
642
|
-
);
|
|
643
|
-
if (visible.length === 0) body.push(theme.fg("dim", "waiting for output…"));
|
|
644
|
-
else body.push(...visible);
|
|
645
|
-
while (body.length < viewport) body.push("");
|
|
646
|
-
lines.push(...body.slice(0, viewport));
|
|
647
|
-
|
|
648
|
-
lines.push(
|
|
649
|
-
this.rule(
|
|
650
|
-
width,
|
|
651
|
-
theme.fg("borderAccent", "─"),
|
|
652
|
-
this.scrollOffset > 0 ? theme.fg("dim", `↓ ${this.scrollOffset}`) : "",
|
|
653
|
-
),
|
|
654
|
-
);
|
|
655
|
-
lines.push(...this.input.render(width));
|
|
656
|
-
const keys = (binding: Parameters<KeybindingsManager["getKeys"]>[0]) =>
|
|
657
|
-
configuredKeys(this.keybindings, binding);
|
|
658
|
-
const editing: ScreenHint[] = [
|
|
659
|
-
[keys("tui.input.submit"), "send"],
|
|
660
|
-
[keys("app.interrupt"), "back"],
|
|
661
|
-
[keys("app.clear"), "abort run"],
|
|
662
|
-
[
|
|
663
|
-
`${keys("tui.editor.cursorUp")}/${keys("tui.editor.cursorDown")}`,
|
|
664
|
-
"scroll",
|
|
665
|
-
],
|
|
666
|
-
];
|
|
667
|
-
// Same drop-the-page-hint fallback as before, measured on the styled line
|
|
668
|
-
// so the keys-brighter-than-labels styling cannot change what fits.
|
|
669
|
-
const full = hintLine(
|
|
670
|
-
theme,
|
|
671
|
-
[
|
|
672
|
-
...editing,
|
|
673
|
-
[`${keys("tui.editor.pageUp")}/${keys("tui.editor.pageDown")}`, "page"],
|
|
674
|
-
],
|
|
675
|
-
width,
|
|
676
|
-
);
|
|
677
|
-
lines.push(
|
|
678
|
-
visibleWidth(full) <= width ? full : hintLine(theme, editing, width),
|
|
679
|
-
);
|
|
680
|
-
lines.push(this.rule(width, theme.fg("borderAccent", "─")));
|
|
681
|
-
return lines;
|
|
498
|
+
return this.page.render(width);
|
|
682
499
|
}
|
|
683
500
|
|
|
684
501
|
invalidate(): void {
|
|
685
|
-
this.
|
|
686
|
-
this.transcriptRenderer.invalidate();
|
|
502
|
+
this.page.invalidate();
|
|
687
503
|
}
|
|
688
504
|
}
|