pi-ui-extend 0.1.34 → 0.1.36
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 +20 -0
- package/dist/app/app.d.ts +1 -0
- package/dist/app/app.js +12 -2
- package/dist/app/commands/command-host.d.ts +1 -0
- package/dist/app/commands/command-model-actions.d.ts +1 -0
- package/dist/app/commands/command-model-actions.js +32 -0
- package/dist/app/commands/command-navigation-actions.js +3 -0
- package/dist/app/commands/command-session-actions.js +2 -0
- package/dist/app/constants.d.ts +2 -1
- package/dist/app/constants.js +6 -1
- package/dist/app/extensions/extension-actions-controller.d.ts +1 -0
- package/dist/app/extensions/extension-actions-controller.js +4 -0
- package/dist/app/input/input-controller.d.ts +5 -1
- package/dist/app/input/input-controller.js +122 -16
- package/dist/app/input/input-paste-handler.js +3 -1
- package/dist/app/input/terminal-edit-shortcuts.d.ts +21 -0
- package/dist/app/input/terminal-edit-shortcuts.js +92 -16
- package/dist/app/popup/popup-action-controller.d.ts +1 -0
- package/dist/app/popup/popup-action-controller.js +1 -0
- package/dist/app/rendering/conversation-entry-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-entry-renderer.js +1 -1
- package/dist/app/rendering/conversation-tool-renderer.d.ts +1 -0
- package/dist/app/rendering/conversation-tool-renderer.js +21 -0
- package/dist/app/rendering/conversation-viewport.d.ts +3 -0
- package/dist/app/rendering/conversation-viewport.js +41 -5
- package/dist/app/rendering/editor-layout-renderer.js +3 -2
- package/dist/app/rendering/editor-panels.js +27 -10
- package/dist/app/runtime.d.ts +1 -0
- package/dist/app/runtime.js +33 -14
- package/dist/app/session/session-event-controller.d.ts +7 -0
- package/dist/app/session/session-event-controller.js +78 -0
- package/dist/app/session/session-lifecycle-controller.d.ts +1 -0
- package/dist/app/session/session-lifecycle-controller.js +7 -0
- package/dist/app/session/tabs-controller.d.ts +1 -0
- package/dist/app/session/tabs-controller.js +4 -1
- package/dist/app/subagents/subagents-widget-controller.d.ts +10 -2
- package/dist/app/subagents/subagents-widget-controller.js +141 -70
- package/dist/app/terminal/terminal-controller.d.ts +10 -0
- package/dist/app/terminal/terminal-controller.js +91 -2
- package/dist/app/todo/todo-model.js +2 -0
- package/dist/app/todo/todo-widget-controller.d.ts +2 -0
- package/dist/app/todo/todo-widget-controller.js +17 -7
- package/dist/app/types.d.ts +4 -0
- package/dist/app/workspace/workspace-actions-controller.d.ts +1 -0
- package/dist/app/workspace/workspace-actions-controller.js +1 -0
- package/dist/bundled-extensions/question/tui.js +8 -1
- package/dist/bundled-extensions/session-title/index.js +65 -14
- package/dist/input-editor-files.js +23 -4
- package/dist/markdown-format.d.ts +4 -1
- package/dist/markdown-format.js +76 -9
- package/external/pi-tools-suite/README.md +71 -1
- package/external/pi-tools-suite/package.json +5 -5
- package/external/pi-tools-suite/src/async-subagents/commands.ts +12 -6
- package/external/pi-tools-suite/src/async-subagents/index.ts +133 -37
- package/external/pi-tools-suite/src/context-usage.ts +6 -1
- package/external/pi-tools-suite/src/dcp/commands.ts +3 -2
- package/external/pi-tools-suite/src/dcp/compress-tool.ts +9 -4
- package/external/pi-tools-suite/src/dcp/config.ts +142 -6
- package/external/pi-tools-suite/src/dcp/index.ts +20 -8
- package/external/pi-tools-suite/src/dcp/prompts.ts +17 -9
- package/external/pi-tools-suite/src/dcp/pruner-candidates.ts +59 -15
- package/external/pi-tools-suite/src/dcp/pruner-metadata.ts +6 -8
- package/external/pi-tools-suite/src/dcp/pruner-nudge.ts +3 -3
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +51 -1
- package/external/pi-tools-suite/src/glm-coding-discipline/index.ts +16 -11
- package/external/pi-tools-suite/src/model-tools/index.ts +24 -12
- package/external/pi-tools-suite/src/prompt-commands/index.ts +11 -2
- package/external/pi-tools-suite/src/telegram-mirror/index.ts +66 -27
- package/external/pi-tools-suite/src/todo/index.ts +87 -16
- package/external/pi-tools-suite/src/todo/state/store.ts +41 -10
- package/external/pi-tools-suite/src/todo/todo.ts +49 -6
- package/external/pi-tools-suite/src/tool-descriptions.ts +4 -4
- package/package.json +7 -5
|
@@ -3,34 +3,84 @@ const CONTROL_MODIFIER_FLAG = 4;
|
|
|
3
3
|
const COMMAND_MODIFIER_FLAG = 8;
|
|
4
4
|
const LOCK_MODIFIER_MASK = 64 + 128;
|
|
5
5
|
const KEY_CODE_C = 99;
|
|
6
|
+
const KEY_CODE_ENTER = 13;
|
|
7
|
+
const KEY_CODE_V = 118;
|
|
6
8
|
const KEY_CODE_Y = 121;
|
|
7
9
|
const KEY_CODE_Z = 122;
|
|
8
10
|
const CYRILLIC_SMALL_ES_CODE = 1089;
|
|
9
11
|
const CYRILLIC_CAPITAL_ES_CODE = 1057;
|
|
12
|
+
const CYRILLIC_SMALL_EM_CODE = 1084;
|
|
13
|
+
const CYRILLIC_CAPITAL_EM_CODE = 1052;
|
|
14
|
+
const KITTY_ARROW_CODEPOINTS = {
|
|
15
|
+
A: -1,
|
|
16
|
+
B: -2,
|
|
17
|
+
C: -3,
|
|
18
|
+
D: -4,
|
|
19
|
+
};
|
|
10
20
|
const KITTY_CSI_U_SEQUENCE = /^\x1b\[(\d+)(?::(\d*))?(?::(\d+))?(?:;(\d+))?(?::(\d+))?u/;
|
|
21
|
+
const KITTY_ARROW_SEQUENCE = /^\x1b\[1;(\d+)(?::(\d+))?([ABCD])/;
|
|
11
22
|
const XTERM_MODIFY_OTHER_KEYS_SEQUENCE = /^\x1b\[27;(\d+);(\d+)~/;
|
|
12
|
-
export function
|
|
23
|
+
export function parseTerminalModifiedKeySequence(input) {
|
|
13
24
|
const kitty = parseKittyCsiUSequence(input);
|
|
14
25
|
if (kitty)
|
|
15
|
-
return
|
|
26
|
+
return { kind: "key", key: kitty };
|
|
27
|
+
const kittyArrow = parseKittyArrowSequence(input);
|
|
28
|
+
if (kittyArrow)
|
|
29
|
+
return { kind: "key", key: kittyArrow };
|
|
16
30
|
const xterm = parseXtermModifyOtherKeysSequence(input);
|
|
17
31
|
if (xterm)
|
|
18
|
-
return
|
|
19
|
-
if (
|
|
32
|
+
return { kind: "key", key: xterm };
|
|
33
|
+
if (isPotentialModifiedKeyPrefix(input))
|
|
20
34
|
return { kind: "pending" };
|
|
21
35
|
return { kind: "none" };
|
|
22
36
|
}
|
|
37
|
+
export function parseTerminalEditShortcutSequence(input) {
|
|
38
|
+
const result = parseTerminalModifiedKeySequence(input);
|
|
39
|
+
if (result.kind === "pending")
|
|
40
|
+
return { kind: "pending" };
|
|
41
|
+
if (result.kind === "none")
|
|
42
|
+
return { kind: "none" };
|
|
43
|
+
return terminalEditShortcutResult(result.key);
|
|
44
|
+
}
|
|
23
45
|
export function parseTerminalInterruptSequence(input) {
|
|
24
|
-
const
|
|
25
|
-
if (
|
|
26
|
-
return { kind: "interrupt", length: kitty.length };
|
|
27
|
-
const xterm = parseXtermModifyOtherKeysSequence(input);
|
|
28
|
-
if (xterm && terminalKeyIsControlC(xterm))
|
|
29
|
-
return { kind: "interrupt", length: xterm.length };
|
|
30
|
-
if (isPotentialInterruptPrefix(input))
|
|
46
|
+
const result = parseTerminalModifiedKeySequence(input);
|
|
47
|
+
if (result.kind === "pending")
|
|
31
48
|
return { kind: "pending" };
|
|
49
|
+
if (result.kind === "key" && terminalKeyIsControlC(result.key))
|
|
50
|
+
return { kind: "interrupt", length: result.key.length };
|
|
51
|
+
if (result.kind === "key" || isPotentialInterruptPrefix(input))
|
|
52
|
+
return { kind: "none" };
|
|
32
53
|
return { kind: "none" };
|
|
33
54
|
}
|
|
55
|
+
export function terminalKeyIsShiftEnter(key) {
|
|
56
|
+
const effectiveModifier = key.modifier & ~LOCK_MODIFIER_MASK;
|
|
57
|
+
if ((effectiveModifier & SHIFT_MODIFIER_FLAG) === 0)
|
|
58
|
+
return false;
|
|
59
|
+
return terminalKeyMatchesCodepoint(key, KEY_CODE_ENTER);
|
|
60
|
+
}
|
|
61
|
+
export function terminalKeyIsClipboardImagePaste(key) {
|
|
62
|
+
const effectiveModifier = key.modifier & ~LOCK_MODIFIER_MASK;
|
|
63
|
+
if ((effectiveModifier & (CONTROL_MODIFIER_FLAG | COMMAND_MODIFIER_FLAG)) === 0)
|
|
64
|
+
return false;
|
|
65
|
+
return terminalKeyMatchesCodepoint(key, KEY_CODE_V, CYRILLIC_SMALL_EM_CODE, CYRILLIC_CAPITAL_EM_CODE);
|
|
66
|
+
}
|
|
67
|
+
export function terminalKeyShouldIgnore(key) {
|
|
68
|
+
return key.eventType === 3;
|
|
69
|
+
}
|
|
70
|
+
export function terminalKeyArrowDirection(key) {
|
|
71
|
+
const effectiveModifier = key.modifier & ~LOCK_MODIFIER_MASK;
|
|
72
|
+
if (effectiveModifier !== 0)
|
|
73
|
+
return undefined;
|
|
74
|
+
if (key.codepoint === KITTY_ARROW_CODEPOINTS.A)
|
|
75
|
+
return "up";
|
|
76
|
+
if (key.codepoint === KITTY_ARROW_CODEPOINTS.B)
|
|
77
|
+
return "down";
|
|
78
|
+
if (key.codepoint === KITTY_ARROW_CODEPOINTS.C)
|
|
79
|
+
return "right";
|
|
80
|
+
if (key.codepoint === KITTY_ARROW_CODEPOINTS.D)
|
|
81
|
+
return "left";
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
34
84
|
export function terminalEditShortcutForControlChar(char, shiftPressed) {
|
|
35
85
|
if (char === "\u001a")
|
|
36
86
|
return shiftPressed ? "redo" : "undo";
|
|
@@ -56,6 +106,24 @@ function parseKittyCsiUSequence(input) {
|
|
|
56
106
|
length: match[0].length,
|
|
57
107
|
};
|
|
58
108
|
}
|
|
109
|
+
function parseKittyArrowSequence(input) {
|
|
110
|
+
const match = KITTY_ARROW_SEQUENCE.exec(input);
|
|
111
|
+
if (!match)
|
|
112
|
+
return undefined;
|
|
113
|
+
const modifierValue = Number.parseInt(match[1] ?? "", 10);
|
|
114
|
+
const eventType = match[2] ? Number.parseInt(match[2], 10) : undefined;
|
|
115
|
+
const arrow = match[3];
|
|
116
|
+
const codepoint = arrow ? KITTY_ARROW_CODEPOINTS[arrow] : undefined;
|
|
117
|
+
if (!Number.isFinite(modifierValue) || codepoint === undefined)
|
|
118
|
+
return undefined;
|
|
119
|
+
return {
|
|
120
|
+
codepoint,
|
|
121
|
+
baseLayoutKey: undefined,
|
|
122
|
+
modifier: modifierValue - 1,
|
|
123
|
+
eventType: Number.isFinite(eventType) ? eventType : undefined,
|
|
124
|
+
length: match[0].length,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
59
127
|
function parseXtermModifyOtherKeysSequence(input) {
|
|
60
128
|
const match = XTERM_MODIFY_OTHER_KEYS_SEQUENCE.exec(input);
|
|
61
129
|
if (!match)
|
|
@@ -95,6 +163,17 @@ function interruptCodepointIsC(codepoint) {
|
|
|
95
163
|
const normalized = normalizeLetterCodepoint(codepoint);
|
|
96
164
|
return normalized === KEY_CODE_C || normalized === CYRILLIC_SMALL_ES_CODE || normalized === CYRILLIC_CAPITAL_ES_CODE;
|
|
97
165
|
}
|
|
166
|
+
function terminalKeyMatchesCodepoint(key, ...expectedCodepoints) {
|
|
167
|
+
return expectedCodepoints.some((codepoint) => keyMatchesCodepoint(key, codepoint));
|
|
168
|
+
}
|
|
169
|
+
function keyMatchesCodepoint(key, expectedCodepoint) {
|
|
170
|
+
const normalizedExpected = normalizeLetterCodepoint(expectedCodepoint);
|
|
171
|
+
const primary = normalizeLetterCodepoint(key.codepoint);
|
|
172
|
+
if (primary === normalizedExpected)
|
|
173
|
+
return true;
|
|
174
|
+
const baseLayout = key.baseLayoutKey;
|
|
175
|
+
return baseLayout !== undefined && normalizeLetterCodepoint(baseLayout) === normalizedExpected;
|
|
176
|
+
}
|
|
98
177
|
function terminalEditShortcutForKey(key, effectiveModifier) {
|
|
99
178
|
const codepoint = editShortcutCodepoint(key);
|
|
100
179
|
if (codepoint === KEY_CODE_Y)
|
|
@@ -114,16 +193,13 @@ function normalizeLetterCodepoint(codepoint) {
|
|
|
114
193
|
return codepoint + 32;
|
|
115
194
|
return codepoint;
|
|
116
195
|
}
|
|
117
|
-
function
|
|
196
|
+
function isPotentialModifiedKeyPrefix(input) {
|
|
118
197
|
if (!input.startsWith("\x1b["))
|
|
119
198
|
return false;
|
|
120
199
|
if (input.includes("u") || input.includes("~"))
|
|
121
200
|
return false;
|
|
122
201
|
const body = input.slice(2);
|
|
123
|
-
|
|
124
|
-
return false;
|
|
125
|
-
const possibleStarts = ["122", "121", "90", "27;"];
|
|
126
|
-
return possibleStarts.some((start) => start.startsWith(body) || body.startsWith(start));
|
|
202
|
+
return /^[\d:;]*$/.test(body);
|
|
127
203
|
}
|
|
128
204
|
function isPotentialInterruptPrefix(input) {
|
|
129
205
|
if (!input.startsWith("\x1b["))
|
|
@@ -7,6 +7,7 @@ import type { Entry, SlashCommand, UserMessageJumpMenuValue } from "../types.js"
|
|
|
7
7
|
import type { AppWorkspaceActionsController } from "../workspace/workspace-actions-controller.js";
|
|
8
8
|
export type AppPopupActionControllerHost = {
|
|
9
9
|
runtime(): AgentSessionRuntime | undefined;
|
|
10
|
+
awaitCurrentSessionExtensions(runtime?: AgentSessionRuntime): Promise<void>;
|
|
10
11
|
getBuiltinSlashCommands(): readonly SlashCommand[];
|
|
11
12
|
isRunning(): boolean;
|
|
12
13
|
setInput(value: string): void;
|
|
@@ -228,6 +228,7 @@ export class AppPopupActionController {
|
|
|
228
228
|
this.host.setStatus("switching session");
|
|
229
229
|
this.host.render();
|
|
230
230
|
try {
|
|
231
|
+
await this.host.awaitCurrentSessionExtensions(runtime);
|
|
231
232
|
const result = await runtime.switchSession(session.path);
|
|
232
233
|
if (result.cancelled) {
|
|
233
234
|
this.host.addEntry({ id: createId("system"), kind: "system", text: "Resume cancelled." });
|
|
@@ -14,6 +14,7 @@ export type ConversationEntryRenderOptions = {
|
|
|
14
14
|
availableThinkingLevels?: readonly string[];
|
|
15
15
|
superCompactTools?: boolean;
|
|
16
16
|
allThinkingExpanded?: boolean;
|
|
17
|
+
currentTimeMs?: number;
|
|
17
18
|
renderInlineUserMessageMenu: (entry: Extract<Entry, {
|
|
18
19
|
kind: "user";
|
|
19
20
|
}>, context: InlineUserMessageMenuContext) => RenderedLine[];
|
|
@@ -75,7 +75,7 @@ function renderAssistantLines(text, width, options) {
|
|
|
75
75
|
if (!displayText)
|
|
76
76
|
return [];
|
|
77
77
|
const { left: contentLeft, contentWidth } = horizontalPaddingLayout(width);
|
|
78
|
-
const contentLines = renderMarkdownTextLines(displayText, contentWidth, contentLeft);
|
|
78
|
+
const contentLines = renderMarkdownTextLines(displayText, contentWidth, contentLeft, { preserveWrappedWordSeparator: true });
|
|
79
79
|
if (contentLines.length === 0)
|
|
80
80
|
return [];
|
|
81
81
|
const lines = [];
|
|
@@ -8,6 +8,7 @@ export type ConversationToolRenderOptions = {
|
|
|
8
8
|
availableThinkingLevels?: readonly string[];
|
|
9
9
|
superCompactTools?: boolean;
|
|
10
10
|
allThinkingExpanded?: boolean;
|
|
11
|
+
currentTimeMs?: number;
|
|
11
12
|
};
|
|
12
13
|
export declare function renderConversationToolEntry(entry: Extract<Entry, {
|
|
13
14
|
kind: "tool";
|
|
@@ -56,9 +56,11 @@ export function renderThinkingEntry(entry, width, options) {
|
|
|
56
56
|
const headerColorOverride = entry.level
|
|
57
57
|
? thinkingLevelThemeColor(entry.level, options.colors, options.availableThinkingLevels)
|
|
58
58
|
: undefined;
|
|
59
|
+
const elapsed = thinkingElapsedText(entry, options.currentTimeMs ?? Date.now());
|
|
59
60
|
return renderToolBlock({
|
|
60
61
|
id: entry.id,
|
|
61
62
|
toolName: THINKING_TOOL_NAME,
|
|
63
|
+
...(elapsed === undefined ? {} : { headerArgs: elapsed }),
|
|
62
64
|
expanded,
|
|
63
65
|
status: entry.status,
|
|
64
66
|
isError: false,
|
|
@@ -74,6 +76,25 @@ export function renderThinkingEntry(entry, width, options) {
|
|
|
74
76
|
...(headerColorOverride === undefined ? {} : { headerColorOverride }),
|
|
75
77
|
});
|
|
76
78
|
}
|
|
79
|
+
function thinkingElapsedText(entry, currentTimeMs) {
|
|
80
|
+
if (entry.startedAt === undefined)
|
|
81
|
+
return undefined;
|
|
82
|
+
const endTimeMs = entry.finishedAt ?? currentTimeMs;
|
|
83
|
+
const elapsedMs = Math.max(0, endTimeMs - entry.startedAt);
|
|
84
|
+
return formatThinkingElapsed(elapsedMs);
|
|
85
|
+
}
|
|
86
|
+
function formatThinkingElapsed(elapsedMs) {
|
|
87
|
+
const totalSeconds = Math.max(0, Math.floor(elapsedMs / 1000));
|
|
88
|
+
const seconds = totalSeconds % 60;
|
|
89
|
+
const totalMinutes = Math.floor(totalSeconds / 60);
|
|
90
|
+
if (totalMinutes < 1)
|
|
91
|
+
return `${seconds}s`;
|
|
92
|
+
const minutes = totalMinutes % 60;
|
|
93
|
+
const hours = Math.floor(totalMinutes / 60);
|
|
94
|
+
if (hours < 1)
|
|
95
|
+
return `${minutes}m ${seconds.toString().padStart(2, "0")}s`;
|
|
96
|
+
return `${hours}h ${minutes.toString().padStart(2, "0")}m`;
|
|
97
|
+
}
|
|
77
98
|
function trimTrailingBlankLines(text) {
|
|
78
99
|
return text.replace(/(?:\r?\n[ \t]*)+$/u, "");
|
|
79
100
|
}
|
|
@@ -49,11 +49,14 @@ export declare class ConversationViewport {
|
|
|
49
49
|
private blockCacheForWidth;
|
|
50
50
|
private refreshDynamicLayoutEntries;
|
|
51
51
|
private ensureEntryMeasured;
|
|
52
|
+
private hasDynamicConversationBlock;
|
|
53
|
+
private isDynamicConversationBlock;
|
|
52
54
|
private refreshLayoutEntry;
|
|
53
55
|
private measuredLineCountForEntry;
|
|
54
56
|
private estimatedLineCountForEntry;
|
|
55
57
|
private lineCountWithGap;
|
|
56
58
|
private estimatedBlockLineCountForEntry;
|
|
59
|
+
private estimatedToolEntryLineCount;
|
|
57
60
|
private nextVisibleEntry;
|
|
58
61
|
private nextEstimatedVisibleEntry;
|
|
59
62
|
private gapAfterEntry;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { resolveToolRule } from "../../config.js";
|
|
2
|
+
import { renderToolDisplay } from "../../tool-renderers/index.js";
|
|
2
3
|
import { stringDisplayWidth } from "../../terminal-width.js";
|
|
3
4
|
import { renderConversationEntry as renderConversationEntryLines } from "./conversation-entry-renderer.js";
|
|
4
5
|
import { horizontalPaddingLayout } from "./render-text.js";
|
|
@@ -70,7 +71,7 @@ export class ConversationViewport {
|
|
|
70
71
|
+ (this.host.superCompactTools ? 1_000_000_000 : 0)
|
|
71
72
|
+ (this.host.allThinkingExpanded ? 2_000_000_000 : 0);
|
|
72
73
|
const cached = blockCache.get(entry.id);
|
|
73
|
-
const dynamic = this.
|
|
74
|
+
const dynamic = this.isDynamicConversationBlock(entry);
|
|
74
75
|
if (!dynamic && cached?.version === version)
|
|
75
76
|
return cached;
|
|
76
77
|
const availableThinkingLevels = this.host.availableThinkingLevels?.();
|
|
@@ -82,6 +83,7 @@ export class ConversationViewport {
|
|
|
82
83
|
...(availableThinkingLevels ? { availableThinkingLevels } : {}),
|
|
83
84
|
superCompactTools: Boolean(this.host.superCompactTools),
|
|
84
85
|
allThinkingExpanded: Boolean(this.host.allThinkingExpanded),
|
|
86
|
+
currentTimeMs: Date.now(),
|
|
85
87
|
renderInlineUserMessageMenu: (userEntry, context) => this.host.renderInlineUserMessageMenu(userEntry, context),
|
|
86
88
|
});
|
|
87
89
|
const block = {
|
|
@@ -156,7 +158,7 @@ export class ConversationViewport {
|
|
|
156
158
|
}
|
|
157
159
|
}
|
|
158
160
|
this.refreshDirtyLayoutEntries(layout, width);
|
|
159
|
-
if (this.
|
|
161
|
+
if (this.hasDynamicConversationBlock(layout.entries)) {
|
|
160
162
|
this.refreshDynamicLayoutEntries(layout, width);
|
|
161
163
|
}
|
|
162
164
|
return layout;
|
|
@@ -278,7 +280,7 @@ export class ConversationViewport {
|
|
|
278
280
|
}
|
|
279
281
|
refreshDynamicLayoutEntries(layout, width) {
|
|
280
282
|
for (let index = 0; index < layout.entries.length; index += 1) {
|
|
281
|
-
if (this.
|
|
283
|
+
if (this.isDynamicConversationBlock(layout.entries[index]))
|
|
282
284
|
this.refreshLayoutEntry(layout, width, index, true);
|
|
283
285
|
}
|
|
284
286
|
}
|
|
@@ -286,10 +288,17 @@ export class ConversationViewport {
|
|
|
286
288
|
const entry = layout.entries[index];
|
|
287
289
|
if (!entry)
|
|
288
290
|
return false;
|
|
289
|
-
if (layout.measuredLineCounts[index] === true && !this.
|
|
291
|
+
if (layout.measuredLineCounts[index] === true && !this.isDynamicConversationBlock(entry))
|
|
290
292
|
return false;
|
|
291
293
|
return this.refreshLayoutEntry(layout, width, index, true);
|
|
292
294
|
}
|
|
295
|
+
hasDynamicConversationBlock(entries) {
|
|
296
|
+
return this.host.hasDynamicConversationBlock?.() === true || entries.some((entry) => this.isDynamicConversationBlock(entry));
|
|
297
|
+
}
|
|
298
|
+
isDynamicConversationBlock(entry) {
|
|
299
|
+
return (entry.kind === "thinking" && entry.status === "running" && entry.startedAt !== undefined)
|
|
300
|
+
|| this.host.isDynamicConversationBlock(entry);
|
|
301
|
+
}
|
|
293
302
|
refreshLayoutEntry(layout, width, index, measure) {
|
|
294
303
|
const entry = layout.entries[index];
|
|
295
304
|
if (!entry)
|
|
@@ -348,11 +357,38 @@ export class ConversationViewport {
|
|
|
348
357
|
case "shell":
|
|
349
358
|
return estimateToolLikeLineCount("shell", entry.expanded, `${entry.output}\n${entry.status}`, width, this.host.pixConfig, this.host.superCompactTools === true, true);
|
|
350
359
|
case "tool":
|
|
351
|
-
return
|
|
360
|
+
return this.estimatedToolEntryLineCount(entry, width);
|
|
352
361
|
default:
|
|
353
362
|
return 1;
|
|
354
363
|
}
|
|
355
364
|
}
|
|
365
|
+
estimatedToolEntryLineCount(entry, width) {
|
|
366
|
+
const display = renderToolDisplay({
|
|
367
|
+
toolName: entry.toolName,
|
|
368
|
+
argsText: entry.argsText,
|
|
369
|
+
output: entry.output,
|
|
370
|
+
details: entry.details,
|
|
371
|
+
isError: entry.isError,
|
|
372
|
+
status: entry.status,
|
|
373
|
+
cwd: this.host.cwd,
|
|
374
|
+
colors: this.host.colors,
|
|
375
|
+
});
|
|
376
|
+
const toolName = display.toolName ?? entry.toolName;
|
|
377
|
+
const rule = resolveToolRule(toolName, this.host.pixConfig.toolRenderer);
|
|
378
|
+
if (rule.hidden)
|
|
379
|
+
return 0;
|
|
380
|
+
const bodyWidth = Math.max(1, width - 2);
|
|
381
|
+
if (entry.expanded)
|
|
382
|
+
return 1 + estimateWrappedLineCount(display.expandedText, bodyWidth);
|
|
383
|
+
if (rule.compactHidden || (rule.defaultExpanded === true && this.host.superCompactTools !== true))
|
|
384
|
+
return 1;
|
|
385
|
+
const body = display.collapsedBody.trimEnd();
|
|
386
|
+
if (!body || rule.previewLines === 0)
|
|
387
|
+
return 1;
|
|
388
|
+
const bodyLineCount = estimateWrappedLineCount(body, bodyWidth);
|
|
389
|
+
const previewLineCount = Math.min(rule.previewLines, bodyLineCount);
|
|
390
|
+
return this.host.superCompactTools === true ? 1 : 1 + previewLineCount;
|
|
391
|
+
}
|
|
356
392
|
nextVisibleEntry(entries, index, width) {
|
|
357
393
|
for (let nextIndex = index + 1; nextIndex < entries.length; nextIndex += 1) {
|
|
358
394
|
const nextEntry = entries[nextIndex];
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ABOVE_EDITOR_WIDGET_KEY_GROUPS, BUILT_IN_SUBAGENTS_WIDGET_KEYS,
|
|
1
|
+
import { ABOVE_EDITOR_WIDGET_KEY_GROUPS, BUILT_IN_SUBAGENTS_WIDGET_KEYS, LEGACY_TODO_WIDGET_KEYS, } from "../constants.js";
|
|
2
2
|
import { renderSubagentsPanel, renderTodoPanel } from "./editor-panels.js";
|
|
3
3
|
import { ellipsizeDisplay, horizontalPaddingLayout, padHorizontalText, sanitizeText, wrapText } from "./render-text.js";
|
|
4
4
|
import { APP_ICONS } from "../icons.js";
|
|
@@ -9,7 +9,8 @@ export class EditorLayoutRenderer {
|
|
|
9
9
|
}
|
|
10
10
|
computeLayout(width, rows) {
|
|
11
11
|
const maxAvailableInputRows = Math.max(1, rows - 4);
|
|
12
|
-
const
|
|
12
|
+
const maxComposerRows = Math.max(1, Math.min(maxAvailableInputRows, Math.floor(rows * 0.7)));
|
|
13
|
+
const renderedInput = this.renderInput(width, maxComposerRows, maxComposerRows);
|
|
13
14
|
const maxEntityRows = Math.max(0, rows - renderedInput.lines.length - 4);
|
|
14
15
|
const editorEntityWidth = inputFrameContentWidth(width);
|
|
15
16
|
const aboveEditorEntities = this.renderAboveEditorEntities(editorEntityWidth);
|
|
@@ -71,30 +71,45 @@ export function renderTodoPanel(details, expanded, width, colors) {
|
|
|
71
71
|
export function renderSubagentsPanel(state, expanded, width, colors) {
|
|
72
72
|
if (!state)
|
|
73
73
|
return [];
|
|
74
|
-
const
|
|
74
|
+
const runs = state.runs?.length
|
|
75
|
+
? state.runs
|
|
76
|
+
: [{ runDir: state.runDir, agents: state.agents, ...(state.tasks === undefined ? {} : { tasks: state.tasks }) }];
|
|
77
|
+
const activeRuns = runs
|
|
78
|
+
.map((run) => ({ ...run, activeAgents: activeSubagentStates(run.agents) }))
|
|
79
|
+
.filter((run) => run.activeAgents.length > 0);
|
|
80
|
+
const activeAgents = activeRuns.flatMap((run) => run.activeAgents);
|
|
75
81
|
if (activeAgents.length === 0)
|
|
76
82
|
return [];
|
|
77
83
|
const target = { kind: "subagents-panel" };
|
|
78
|
-
const previewById = taskPreviewMap(state.tasks);
|
|
79
|
-
const runName = subagentRunName(state.runDir);
|
|
80
84
|
const titleSuffix = state.live ? "" : " (snapshot)";
|
|
81
85
|
const stats = formatSubagentsPanelStats(activeAgents);
|
|
82
86
|
const headerText = `subagents ${expanded ? "▾" : "▸"}${stats ? ` ${stats}` : ""}${titleSuffix}`;
|
|
83
87
|
const contentWidth = Math.max(1, width);
|
|
84
88
|
if (!expanded) {
|
|
85
|
-
const
|
|
89
|
+
const runSummary = activeRuns.length === 1 ? subagentRunName(activeRuns[0]?.runDir ?? state.runDir) : `${activeRuns.length} runs`;
|
|
90
|
+
const collapsedText = `${headerText} — ${runSummary}`;
|
|
86
91
|
return [{ text: padOrTrimPlain(ellipsizeDisplay(collapsedText, contentWidth), width), colorOverride: colors.accent, target }];
|
|
87
92
|
}
|
|
88
93
|
const lines = [];
|
|
89
|
-
const
|
|
94
|
+
const flattenedRuns = activeRuns.flatMap((run) => {
|
|
95
|
+
const previewById = taskPreviewMap(run.tasks);
|
|
96
|
+
return run.activeAgents.map((agent, index) => ({
|
|
97
|
+
runDir: run.runDir,
|
|
98
|
+
agent,
|
|
99
|
+
preview: previewById.get(agent.id),
|
|
100
|
+
showRunLabel: activeRuns.length > 1 && index === 0,
|
|
101
|
+
}));
|
|
102
|
+
});
|
|
103
|
+
const visibleAgents = flattenedRuns.slice(0, SUBAGENTS_WIDGET_MAX_ROWS);
|
|
90
104
|
const rowWidth = contentWidth;
|
|
91
105
|
const now = Date.now();
|
|
92
|
-
for (const
|
|
93
|
-
const preview =
|
|
106
|
+
for (const visibleRun of visibleAgents) {
|
|
107
|
+
const { agent, preview } = visibleRun;
|
|
94
108
|
const model = subagentModelThinkingLabel(preview);
|
|
95
109
|
const task = preview?.task?.trim() || preview?.scope?.trim() || "task unavailable";
|
|
96
110
|
const icon = subagentStatusIcon(agent.status);
|
|
97
|
-
const
|
|
111
|
+
const runLabel = visibleRun.showRunLabel ? `${subagentRunName(visibleRun.runDir)} ` : "";
|
|
112
|
+
const prefix = `${runLabel}${icon} ${agent.id} ${model} `;
|
|
98
113
|
const suffix = ` ${formatElapsedSince(agent.startedAt, now)}`;
|
|
99
114
|
const taskWidth = Math.max(8, rowWidth - stringDisplayWidth(prefix) - stringDisplayWidth(suffix));
|
|
100
115
|
const taskText = ellipsizeDisplay(task, taskWidth);
|
|
@@ -102,22 +117,24 @@ export function renderSubagentsPanel(state, expanded, width, colors) {
|
|
|
102
117
|
lines.push({
|
|
103
118
|
text: padOrTrimPlain(text, width),
|
|
104
119
|
colorOverride: colors.muted,
|
|
105
|
-
segments: subagentPanelLineSegments({ text, icon, agentId: agent.id, model, taskText, prefix, status: agent.status }, colors),
|
|
120
|
+
segments: subagentPanelLineSegments({ text, icon, agentId: agent.id, model, taskText, prefix, runLabel, status: agent.status }, colors),
|
|
106
121
|
target,
|
|
107
122
|
});
|
|
108
123
|
}
|
|
109
|
-
const hidden =
|
|
124
|
+
const hidden = flattenedRuns.length - visibleAgents.length;
|
|
110
125
|
if (hidden > 0)
|
|
111
126
|
lines.push({ text: padOrTrimPlain(`+${hidden} more`, width), variant: "muted", target });
|
|
112
127
|
return lines;
|
|
113
128
|
}
|
|
114
129
|
function subagentPanelLineSegments(input, colors) {
|
|
115
130
|
const iconStart = input.text.indexOf(input.icon);
|
|
131
|
+
const runLabelStart = input.runLabel ? input.text.indexOf(input.runLabel) : -1;
|
|
116
132
|
const nameStart = input.text.indexOf(input.agentId, iconStart + input.icon.length);
|
|
117
133
|
const modelStart = input.text.indexOf(input.model, nameStart + input.agentId.length);
|
|
118
134
|
const taskStart = input.prefix.length;
|
|
119
135
|
const suffixStart = taskStart + input.taskText.length;
|
|
120
136
|
return [
|
|
137
|
+
...(runLabelStart >= 0 ? [{ start: runLabelStart, end: runLabelStart + input.runLabel.length, foreground: colors.warning }] : []),
|
|
121
138
|
{ start: iconStart, end: iconStart + input.icon.length, foreground: subagentStatusColor(input.status, colors), bold: true },
|
|
122
139
|
{ start: nameStart, end: nameStart + input.agentId.length, foreground: colors.accent, bold: true },
|
|
123
140
|
{ start: modelStart, end: modelStart + input.model.length, foreground: colors.info },
|
package/dist/app/runtime.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export declare function prioritizeBundledQuestionExtension(base: LoadExtensionsR
|
|
|
38
38
|
export type CreatePixRuntimeOptions = {
|
|
39
39
|
eventBus?: EventBus;
|
|
40
40
|
config?: PixConfig;
|
|
41
|
+
reuseServicesFrom?: AgentSessionRuntime;
|
|
41
42
|
};
|
|
42
43
|
type RuntimeSessionManagerModelState = Pick<SessionManager, "getEntries" | "getBranch">;
|
|
43
44
|
export declare function resolvePixRuntimeModelRef(options: Pick<AppOptions, "modelRef">, sessionManager: RuntimeSessionManagerModelState, config?: PixConfig): string | undefined;
|
package/dist/app/runtime.js
CHANGED
|
@@ -220,26 +220,20 @@ export function resolveSessionModelRefFromTail(entries) {
|
|
|
220
220
|
}
|
|
221
221
|
export async function createPixRuntime(options, runtimeOptions = {}) {
|
|
222
222
|
const agentDir = getAgentDir();
|
|
223
|
+
const reusableServices = reusableRuntimeServices(runtimeOptions.reuseServicesFrom, options.cwd, agentDir);
|
|
223
224
|
const createRuntime = async ({ cwd, sessionManager, sessionStartEvent }) => {
|
|
224
225
|
const config = runtimeOptions.config ?? loadPixConfig(cwd);
|
|
225
226
|
const effectiveModelRef = resolvePixRuntimeModelRef(options, sessionManager, config);
|
|
226
227
|
const parsedModel = effectiveModelRef ? parseModelRef(effectiveModelRef) : undefined;
|
|
227
228
|
const initialThinkingLevel = resolvePixRuntimeInitialThinkingLevel(options, sessionManager, config);
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
resourceLoaderOptions: {
|
|
235
|
-
...(config.ignoreContextFiles ? { noContextFiles: true } : {}),
|
|
229
|
+
const services = reusableServices && sameRuntimeServiceTarget(reusableServices, cwd, agentDir)
|
|
230
|
+
? reusableServices
|
|
231
|
+
: await createPixRuntimeServices({
|
|
232
|
+
cwd,
|
|
233
|
+
agentDir,
|
|
234
|
+
config,
|
|
236
235
|
...(runtimeOptions.eventBus === undefined ? {} : { eventBus: runtimeOptions.eventBus }),
|
|
237
|
-
|
|
238
|
-
additionalExtensionPaths: bundledExtensionPaths,
|
|
239
|
-
extensionsOverride: prioritizeBundledQuestionExtension,
|
|
240
|
-
}),
|
|
241
|
-
},
|
|
242
|
-
});
|
|
236
|
+
});
|
|
243
237
|
services.modelRegistry.refresh();
|
|
244
238
|
const model = parsedModel ? services.modelRegistry.find(parsedModel.provider, parsedModel.modelId) : undefined;
|
|
245
239
|
if (parsedModel && !model) {
|
|
@@ -285,3 +279,28 @@ export async function createPixRuntime(options, runtimeOptions = {}) {
|
|
|
285
279
|
: SessionManager.create(options.cwd),
|
|
286
280
|
});
|
|
287
281
|
}
|
|
282
|
+
async function createPixRuntimeServices(options) {
|
|
283
|
+
await ensureBundledSkillsInstalledOnce();
|
|
284
|
+
await ensurePiToolsSuiteExtensionInstalledOnce({ agentDir: options.agentDir });
|
|
285
|
+
const bundledExtensionPaths = await getBundledExtensionPathsAsync();
|
|
286
|
+
return await createAgentSessionServices({
|
|
287
|
+
cwd: options.cwd,
|
|
288
|
+
agentDir: options.agentDir,
|
|
289
|
+
resourceLoaderOptions: {
|
|
290
|
+
...(options.config.ignoreContextFiles ? { noContextFiles: true } : {}),
|
|
291
|
+
...(options.eventBus === undefined ? {} : { eventBus: options.eventBus }),
|
|
292
|
+
...(bundledExtensionPaths.length === 0 ? {} : {
|
|
293
|
+
additionalExtensionPaths: bundledExtensionPaths,
|
|
294
|
+
extensionsOverride: prioritizeBundledQuestionExtension,
|
|
295
|
+
}),
|
|
296
|
+
},
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
function reusableRuntimeServices(runtime, cwd, agentDir) {
|
|
300
|
+
const services = runtime?.services;
|
|
301
|
+
return services && sameRuntimeServiceTarget(services, cwd, agentDir) ? services : undefined;
|
|
302
|
+
}
|
|
303
|
+
function sameRuntimeServiceTarget(services, cwd, agentDir) {
|
|
304
|
+
return normalizePathForCompare(services.cwd) === normalizePathForCompare(cwd)
|
|
305
|
+
&& normalizePathForCompare(services.agentDir) === normalizePathForCompare(agentDir);
|
|
306
|
+
}
|
|
@@ -19,6 +19,7 @@ export type AppSessionEventControllerState = {
|
|
|
19
19
|
currentAssistantTextBlockContentIndex: number | undefined;
|
|
20
20
|
assistantTextBlocksByContentIndex: Map<number, string>;
|
|
21
21
|
currentThinkingEntryId: string | undefined;
|
|
22
|
+
currentThinkingEntryStartedAt: number | undefined;
|
|
22
23
|
assistantMessageClosed: boolean;
|
|
23
24
|
assistantTextBuffer: string;
|
|
24
25
|
entryRenderVersions: Map<string, number>;
|
|
@@ -74,6 +75,8 @@ export declare class AppSessionEventController {
|
|
|
74
75
|
private historyEntries;
|
|
75
76
|
private historyWindowStart;
|
|
76
77
|
private currentThinkingEntryId;
|
|
78
|
+
private currentThinkingEntryStartedAt;
|
|
79
|
+
private thinkingElapsedRenderTimer;
|
|
77
80
|
private assistantMessageClosed;
|
|
78
81
|
private assistantTextBuffer;
|
|
79
82
|
constructor(host: AppSessionEventControllerHost);
|
|
@@ -133,7 +136,11 @@ export declare class AppSessionEventController {
|
|
|
133
136
|
private appendThinkingText;
|
|
134
137
|
private finishCurrentThinkingEntry;
|
|
135
138
|
private reconcileThinkingText;
|
|
139
|
+
private syncThinkingElapsedRenderTimer;
|
|
140
|
+
private startThinkingElapsedRenderTimer;
|
|
141
|
+
private stopThinkingElapsedRenderTimer;
|
|
136
142
|
private currentThinkingLevel;
|
|
143
|
+
private reconcileAssistantTextFromFinalMessage;
|
|
137
144
|
private renderAssistantToolCallsFromMessage;
|
|
138
145
|
private upsertPendingToolCall;
|
|
139
146
|
private upsertToolEntry;
|