pi-ui-extend 0.1.33 → 0.1.35
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.js +4 -2
- package/dist/app/constants.d.ts +2 -1
- package/dist/app/constants.js +6 -1
- package/dist/app/icons.js +1 -1
- package/dist/app/input/input-controller.d.ts +4 -1
- package/dist/app/input/input-controller.js +95 -16
- package/dist/app/input/input-paste-handler.js +3 -1
- package/dist/app/input/terminal-edit-shortcuts.d.ts +20 -0
- package/dist/app/input/terminal-edit-shortcuts.js +50 -16
- package/dist/app/model/model-usage-status.d.ts +2 -1
- package/dist/app/model/model-usage-status.js +33 -25
- 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/rendering/status-line-renderer.d.ts +1 -0
- package/dist/app/rendering/status-line-renderer.js +15 -1
- 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 +110 -7
- package/dist/app/session/tabs-controller.js +3 -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/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 +3 -3
- 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 +37 -56
- package/package.json +7 -5
|
@@ -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 },
|
|
@@ -71,6 +71,7 @@ export declare class StatusLineRenderer {
|
|
|
71
71
|
private pushVoiceWidgetSegment;
|
|
72
72
|
private pushWorkspaceSegments;
|
|
73
73
|
private pushModelUsageSegments;
|
|
74
|
+
private modelUsageHasWarning;
|
|
74
75
|
private modelUsageResetLength;
|
|
75
76
|
private pushSegment;
|
|
76
77
|
private pushContextBarSegments;
|
|
@@ -491,11 +491,25 @@ export class StatusLineRenderer {
|
|
|
491
491
|
fill: color,
|
|
492
492
|
track: this.host.theme.colors.statusDotBase,
|
|
493
493
|
}, MODEL_USAGE_PROGRESS_BAR_WIDTH));
|
|
494
|
-
const
|
|
494
|
+
const defaultResetStart = barStart + MODEL_USAGE_PROGRESS_BAR_WIDTH + 1;
|
|
495
|
+
const warningStart = this.modelUsageHasWarning(modelUsageLabel, defaultResetStart - labelStart)
|
|
496
|
+
? defaultResetStart
|
|
497
|
+
: undefined;
|
|
498
|
+
const resetStart = warningStart === undefined
|
|
499
|
+
? defaultResetStart
|
|
500
|
+
: warningStart + APP_ICONS.alert.length + 1;
|
|
501
|
+
if (warningStart !== undefined) {
|
|
502
|
+
this.pushSegment(segments, warningStart, APP_ICONS.alert.length, this.host.theme.colors.warning);
|
|
503
|
+
}
|
|
495
504
|
const resetLength = this.modelUsageResetLength(modelUsageLabel, resetStart - labelStart);
|
|
496
505
|
this.pushSegment(segments, resetStart, resetLength, this.host.theme.colors.muted);
|
|
497
506
|
}
|
|
498
507
|
}
|
|
508
|
+
modelUsageHasWarning(modelUsageLabel, localStart) {
|
|
509
|
+
if (localStart < 0 || localStart >= modelUsageLabel.length)
|
|
510
|
+
return false;
|
|
511
|
+
return modelUsageLabel.startsWith(APP_ICONS.alert, localStart);
|
|
512
|
+
}
|
|
499
513
|
modelUsageResetLength(modelUsageLabel, localStart) {
|
|
500
514
|
if (localStart < 0 || localStart >= modelUsageLabel.length)
|
|
501
515
|
return 0;
|
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;
|
|
@@ -26,6 +26,8 @@ export class AppSessionEventController {
|
|
|
26
26
|
historyEntries = [];
|
|
27
27
|
historyWindowStart = 0;
|
|
28
28
|
currentThinkingEntryId;
|
|
29
|
+
currentThinkingEntryStartedAt;
|
|
30
|
+
thinkingElapsedRenderTimer;
|
|
29
31
|
assistantMessageClosed = false;
|
|
30
32
|
assistantTextBuffer = "";
|
|
31
33
|
constructor(host) {
|
|
@@ -44,6 +46,7 @@ export class AppSessionEventController {
|
|
|
44
46
|
currentAssistantTextBlockContentIndex: this.currentAssistantTextBlockContentIndex,
|
|
45
47
|
assistantTextBlocksByContentIndex: new Map(this.assistantTextBlocksByContentIndex),
|
|
46
48
|
currentThinkingEntryId: this.currentThinkingEntryId,
|
|
49
|
+
currentThinkingEntryStartedAt: this.currentThinkingEntryStartedAt,
|
|
47
50
|
assistantMessageClosed: this.assistantMessageClosed,
|
|
48
51
|
assistantTextBuffer: this.assistantTextBuffer,
|
|
49
52
|
entryRenderVersions: new Map(this.entryRenderVersions),
|
|
@@ -71,6 +74,8 @@ export class AppSessionEventController {
|
|
|
71
74
|
for (const [key, value] of state.assistantTextBlocksByContentIndex)
|
|
72
75
|
this.assistantTextBlocksByContentIndex.set(key, value);
|
|
73
76
|
this.currentThinkingEntryId = state.currentThinkingEntryId;
|
|
77
|
+
this.currentThinkingEntryStartedAt = state.currentThinkingEntryStartedAt;
|
|
78
|
+
this.syncThinkingElapsedRenderTimer();
|
|
74
79
|
this.assistantMessageClosed = state.assistantMessageClosed;
|
|
75
80
|
this.assistantTextBuffer = state.assistantTextBuffer;
|
|
76
81
|
this.entryRenderVersions.clear();
|
|
@@ -94,6 +99,8 @@ export class AppSessionEventController {
|
|
|
94
99
|
this.assistantTextBlocksByContentIndex.clear();
|
|
95
100
|
this.finalizedToolCallContentIndexes.clear();
|
|
96
101
|
this.currentThinkingEntryId = undefined;
|
|
102
|
+
this.currentThinkingEntryStartedAt = undefined;
|
|
103
|
+
this.stopThinkingElapsedRenderTimer();
|
|
97
104
|
this.assistantMessageClosed = false;
|
|
98
105
|
this.assistantTextBuffer = "";
|
|
99
106
|
this.olderHistoryLoader = undefined;
|
|
@@ -195,7 +202,7 @@ export class AppSessionEventController {
|
|
|
195
202
|
this.host.updateQueuedMessageStatus();
|
|
196
203
|
break;
|
|
197
204
|
case "message_update":
|
|
198
|
-
this.handleMessageUpdate(event
|
|
205
|
+
this.handleMessageUpdate(event);
|
|
199
206
|
break;
|
|
200
207
|
case "tool_execution_start":
|
|
201
208
|
this.finishCurrentThinkingEntry();
|
|
@@ -506,7 +513,8 @@ export class AppSessionEventController {
|
|
|
506
513
|
return;
|
|
507
514
|
this.host.recordWorkspaceMutationForUserEntry(prepared.userEntryId, mutation);
|
|
508
515
|
}
|
|
509
|
-
handleMessageUpdate(
|
|
516
|
+
handleMessageUpdate(event) {
|
|
517
|
+
const assistantEvent = event.assistantMessageEvent;
|
|
510
518
|
if (this.assistantMessageClosed && assistantEvent.type !== "done")
|
|
511
519
|
return;
|
|
512
520
|
this.assistantMessageClosed = false;
|
|
@@ -522,7 +530,13 @@ export class AppSessionEventController {
|
|
|
522
530
|
case "text_delta":
|
|
523
531
|
this.finishCurrentThinkingEntry();
|
|
524
532
|
this.host.setSessionActivity("running");
|
|
525
|
-
|
|
533
|
+
{
|
|
534
|
+
const snapshotText = assistantTextSnapshotForContentIndex(event.message, assistantEvent.partial, assistantEvent.contentIndex);
|
|
535
|
+
if (snapshotText === undefined)
|
|
536
|
+
this.appendAssistantText(assistantEvent.delta, assistantEvent.contentIndex);
|
|
537
|
+
else
|
|
538
|
+
this.reconcileAssistantTextBlock(snapshotText, assistantEvent.contentIndex, { keepOpen: true });
|
|
539
|
+
}
|
|
526
540
|
break;
|
|
527
541
|
case "text_end":
|
|
528
542
|
this.finishCurrentThinkingEntry();
|
|
@@ -556,6 +570,7 @@ export class AppSessionEventController {
|
|
|
556
570
|
this.handleToolCallStreamUpdate(assistantEvent.contentIndex, assistantEvent.partial, assistantEvent.toolCall);
|
|
557
571
|
break;
|
|
558
572
|
case "done":
|
|
573
|
+
this.reconcileAssistantTextFromFinalMessage(assistantEvent.message);
|
|
559
574
|
this.renderAssistantToolCallsFromMessage(assistantEvent.message);
|
|
560
575
|
this.finishCurrentThinkingEntry();
|
|
561
576
|
this.flushAssistantTextBuffer(true);
|
|
@@ -616,7 +631,7 @@ export class AppSessionEventController {
|
|
|
616
631
|
entry.text += visibleText;
|
|
617
632
|
this.touchEntry(entry);
|
|
618
633
|
}
|
|
619
|
-
reconcileAssistantTextBlock(content, contentIndex) {
|
|
634
|
+
reconcileAssistantTextBlock(content, contentIndex, options = {}) {
|
|
620
635
|
this.flushAssistantTextBuffer(true);
|
|
621
636
|
const hasVisibleTextBeforeBlock = this.hasVisibleTextBeforeCurrentAssistantBlock();
|
|
622
637
|
// C.11: normalise CRLF in the final block content (see appendAssistantText).
|
|
@@ -667,9 +682,16 @@ export class AppSessionEventController {
|
|
|
667
682
|
this.currentAssistantEntryId = entry.id;
|
|
668
683
|
if (contentIndex !== undefined)
|
|
669
684
|
this.assistantTextBlocksByContentIndex.set(contentIndex, visibleText);
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
685
|
+
if (options.keepOpen) {
|
|
686
|
+
this.currentAssistantTextBlockEntryId = entry.id;
|
|
687
|
+
this.currentAssistantTextBlockStartLength = startLength;
|
|
688
|
+
this.currentAssistantTextBlockContentIndex = contentIndex;
|
|
689
|
+
}
|
|
690
|
+
else {
|
|
691
|
+
this.currentAssistantTextBlockEntryId = undefined;
|
|
692
|
+
this.currentAssistantTextBlockStartLength = undefined;
|
|
693
|
+
this.currentAssistantTextBlockContentIndex = undefined;
|
|
694
|
+
}
|
|
673
695
|
this.assistantTextBuffer = "";
|
|
674
696
|
}
|
|
675
697
|
ensureAssistantTextBlockStarted(entry) {
|
|
@@ -740,12 +762,15 @@ export class AppSessionEventController {
|
|
|
740
762
|
: undefined;
|
|
741
763
|
if (!entry || entry.kind !== "thinking") {
|
|
742
764
|
const level = this.currentThinkingLevel();
|
|
765
|
+
const startedAt = this.currentThinkingEntryStartedAt ?? Date.now();
|
|
766
|
+
this.currentThinkingEntryStartedAt = startedAt;
|
|
743
767
|
entry = {
|
|
744
768
|
id: createId("thinking"),
|
|
745
769
|
kind: "thinking",
|
|
746
770
|
text: "",
|
|
747
771
|
expanded: this.host.toolDefaultExpanded(THINKING_TOOL_NAME),
|
|
748
772
|
...(level === undefined ? {} : { level }),
|
|
773
|
+
startedAt,
|
|
749
774
|
status: "running",
|
|
750
775
|
};
|
|
751
776
|
this.addEntry(entry);
|
|
@@ -756,17 +781,24 @@ export class AppSessionEventController {
|
|
|
756
781
|
delete entry.level;
|
|
757
782
|
else
|
|
758
783
|
entry.level = level;
|
|
784
|
+
entry.startedAt ??= this.currentThinkingEntryStartedAt ?? Date.now();
|
|
785
|
+
this.currentThinkingEntryStartedAt = entry.startedAt;
|
|
786
|
+
delete entry.finishedAt;
|
|
759
787
|
entry.status = "running";
|
|
760
788
|
entry.text += delta;
|
|
789
|
+
this.startThinkingElapsedRenderTimer();
|
|
761
790
|
this.touchEntry(entry);
|
|
762
791
|
}
|
|
763
792
|
finishCurrentThinkingEntry() {
|
|
764
793
|
const entry = this.currentThinkingEntryId ? this.findEntry(this.currentThinkingEntryId) : undefined;
|
|
765
794
|
if (entry?.kind === "thinking" && entry.status !== "done") {
|
|
766
795
|
entry.status = "done";
|
|
796
|
+
entry.finishedAt ??= Date.now();
|
|
767
797
|
this.touchEntry(entry);
|
|
768
798
|
}
|
|
769
799
|
this.currentThinkingEntryId = undefined;
|
|
800
|
+
this.currentThinkingEntryStartedAt = undefined;
|
|
801
|
+
this.stopThinkingElapsedRenderTimer();
|
|
770
802
|
}
|
|
771
803
|
reconcileThinkingText(content) {
|
|
772
804
|
let entry = this.currentThinkingEntryId
|
|
@@ -774,12 +806,15 @@ export class AppSessionEventController {
|
|
|
774
806
|
: undefined;
|
|
775
807
|
if (!entry || entry.kind !== "thinking") {
|
|
776
808
|
const level = this.currentThinkingLevel();
|
|
809
|
+
const startedAt = this.currentThinkingEntryStartedAt ?? Date.now();
|
|
810
|
+
this.currentThinkingEntryStartedAt = startedAt;
|
|
777
811
|
entry = {
|
|
778
812
|
id: createId("thinking"),
|
|
779
813
|
kind: "thinking",
|
|
780
814
|
text: "",
|
|
781
815
|
expanded: this.host.toolDefaultExpanded(THINKING_TOOL_NAME),
|
|
782
816
|
...(level === undefined ? {} : { level }),
|
|
817
|
+
startedAt,
|
|
783
818
|
status: "running",
|
|
784
819
|
};
|
|
785
820
|
this.addEntry(entry);
|
|
@@ -792,13 +827,63 @@ export class AppSessionEventController {
|
|
|
792
827
|
delete entry.level;
|
|
793
828
|
else
|
|
794
829
|
entry.level = level;
|
|
830
|
+
entry.startedAt ??= this.currentThinkingEntryStartedAt ?? Date.now();
|
|
831
|
+
this.currentThinkingEntryStartedAt = entry.startedAt;
|
|
832
|
+
delete entry.finishedAt;
|
|
795
833
|
entry.status = "running";
|
|
834
|
+
this.startThinkingElapsedRenderTimer();
|
|
796
835
|
this.touchEntry(entry);
|
|
797
836
|
}
|
|
798
837
|
}
|
|
838
|
+
syncThinkingElapsedRenderTimer() {
|
|
839
|
+
const entry = this.currentThinkingEntryId ? this.findEntry(this.currentThinkingEntryId) : undefined;
|
|
840
|
+
if (entry?.kind === "thinking" && entry.status === "running" && entry.startedAt !== undefined) {
|
|
841
|
+
this.startThinkingElapsedRenderTimer();
|
|
842
|
+
return;
|
|
843
|
+
}
|
|
844
|
+
this.stopThinkingElapsedRenderTimer();
|
|
845
|
+
}
|
|
846
|
+
startThinkingElapsedRenderTimer() {
|
|
847
|
+
if (this.thinkingElapsedRenderTimer)
|
|
848
|
+
return;
|
|
849
|
+
this.thinkingElapsedRenderTimer = setInterval(() => {
|
|
850
|
+
if (!this.host.isRunning()) {
|
|
851
|
+
this.stopThinkingElapsedRenderTimer();
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
this.host.scheduleRender();
|
|
855
|
+
}, 1000);
|
|
856
|
+
this.thinkingElapsedRenderTimer.unref?.();
|
|
857
|
+
}
|
|
858
|
+
stopThinkingElapsedRenderTimer() {
|
|
859
|
+
if (!this.thinkingElapsedRenderTimer)
|
|
860
|
+
return;
|
|
861
|
+
clearInterval(this.thinkingElapsedRenderTimer);
|
|
862
|
+
this.thinkingElapsedRenderTimer = undefined;
|
|
863
|
+
}
|
|
799
864
|
currentThinkingLevel() {
|
|
800
865
|
return this.host.runtime()?.session.thinkingLevel;
|
|
801
866
|
}
|
|
867
|
+
reconcileAssistantTextFromFinalMessage(message) {
|
|
868
|
+
const openContentIndex = this.currentAssistantTextBlockContentIndex;
|
|
869
|
+
if (openContentIndex !== undefined) {
|
|
870
|
+
const content = assistantTextContentAt(message, openContentIndex);
|
|
871
|
+
if (content !== undefined)
|
|
872
|
+
this.reconcileAssistantTextBlock(content, openContentIndex);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
const textBlocks = assistantTextContents(message);
|
|
876
|
+
if (textBlocks.length !== 1)
|
|
877
|
+
return;
|
|
878
|
+
const entry = this.currentAssistantEntryId ? this.findEntry(this.currentAssistantEntryId) : undefined;
|
|
879
|
+
if (entry?.kind !== "assistant")
|
|
880
|
+
return;
|
|
881
|
+
const visibleText = assistantStreamVisibleTextForCompleteBlock(textBlocks[0] ?? "", false);
|
|
882
|
+
if (!visibleText || entry.text === visibleText)
|
|
883
|
+
return;
|
|
884
|
+
entry.text = visibleText;
|
|
885
|
+
this.touchEntry(entry);
|
|
886
|
+
}
|
|
802
887
|
renderAssistantToolCallsFromMessage(message) {
|
|
803
888
|
if (!isRecord(message) || !Array.isArray(message.content))
|
|
804
889
|
return;
|
|
@@ -879,6 +964,8 @@ export class AppSessionEventController {
|
|
|
879
964
|
this.currentAssistantTextBlockStartLength = undefined;
|
|
880
965
|
this.currentAssistantTextBlockContentIndex = undefined;
|
|
881
966
|
this.currentThinkingEntryId = undefined;
|
|
967
|
+
this.currentThinkingEntryStartedAt = undefined;
|
|
968
|
+
this.stopThinkingElapsedRenderTimer();
|
|
882
969
|
this.assistantTextBuffer = "";
|
|
883
970
|
this.assistantTextBlocksByContentIndex.clear();
|
|
884
971
|
this.finalizedToolCallContentIndexes.clear();
|
|
@@ -891,6 +978,22 @@ function partialToolCallAt(partial, contentIndex) {
|
|
|
891
978
|
const block = partial.content[contentIndex];
|
|
892
979
|
return isRecord(block) && block.type === "toolCall" ? block : undefined;
|
|
893
980
|
}
|
|
981
|
+
function assistantTextSnapshotForContentIndex(message, partial, contentIndex) {
|
|
982
|
+
if (contentIndex === undefined)
|
|
983
|
+
return undefined;
|
|
984
|
+
return assistantTextContentAt(message, contentIndex) ?? assistantTextContentAt(partial, contentIndex);
|
|
985
|
+
}
|
|
986
|
+
function assistantTextContentAt(value, contentIndex) {
|
|
987
|
+
if (!isRecord(value) || !Array.isArray(value.content))
|
|
988
|
+
return undefined;
|
|
989
|
+
const block = value.content[contentIndex];
|
|
990
|
+
return isRecord(block) && block.type === "text" && typeof block.text === "string" ? block.text : undefined;
|
|
991
|
+
}
|
|
992
|
+
function assistantTextContents(value) {
|
|
993
|
+
if (!isRecord(value) || !Array.isArray(value.content))
|
|
994
|
+
return [];
|
|
995
|
+
return value.content.flatMap((block) => (isRecord(block) && block.type === "text" && typeof block.text === "string" ? [block.text] : []));
|
|
996
|
+
}
|
|
894
997
|
function assistantStreamVisibleTextForCompleteBlock(text, hasVisibleTextBeforeBlock) {
|
|
895
998
|
let buffer = text;
|
|
896
999
|
let visibleText = "";
|
|
@@ -641,7 +641,9 @@ export class AppTabsController {
|
|
|
641
641
|
void this.saveTabs();
|
|
642
642
|
this.scheduleTabPrewarm();
|
|
643
643
|
const cachedView = this.sessionViewsByTabId.get(target.id);
|
|
644
|
-
|
|
644
|
+
const cachedViewNeedsHistoryReload = this.tabIdsNeedingHistoryReload.has(target.id)
|
|
645
|
+
&& this.sessionActivity(targetRuntime.session) !== "running";
|
|
646
|
+
if (cachedView && this.host.restoreSessionView && !cachedViewNeedsHistoryReload) {
|
|
645
647
|
this.host.restoreSessionView(cachedView);
|
|
646
648
|
this.restoreDeferredUserMessages(target.id);
|
|
647
649
|
this.host.setSessionStatus(targetRuntime.session);
|
|
@@ -13,7 +13,9 @@ export declare class AppSubagentsWidgetController {
|
|
|
13
13
|
private pollTimer;
|
|
14
14
|
private pollInFlight;
|
|
15
15
|
private currentRunDir;
|
|
16
|
+
private currentRunDirs;
|
|
16
17
|
private state;
|
|
18
|
+
private readonly runFreshnessByRunDir;
|
|
17
19
|
private readonly taskPreviewsByRunDir;
|
|
18
20
|
private readonly snapshotByRunDir;
|
|
19
21
|
private refreshGeneration;
|
|
@@ -32,10 +34,16 @@ export declare class AppSubagentsWidgetController {
|
|
|
32
34
|
private schedulePoll;
|
|
33
35
|
private poll;
|
|
34
36
|
private refreshFromFiles;
|
|
35
|
-
private
|
|
37
|
+
private findActiveRegistryRunDirsForCurrentSession;
|
|
36
38
|
private registryRunDirsNewestFirst;
|
|
37
|
-
private clearMissingRunAndMaybeSelectReplacement;
|
|
38
39
|
private clearCachedRun;
|
|
40
|
+
private buildStateFromRuns;
|
|
41
|
+
private orderRuns;
|
|
42
|
+
private orderRunDirs;
|
|
43
|
+
private rememberRunFreshness;
|
|
44
|
+
private runFreshness;
|
|
45
|
+
private runFreshnessFromAgents;
|
|
46
|
+
private parseRunTimestamp;
|
|
39
47
|
private updateState;
|
|
40
48
|
private isCurrentGeneration;
|
|
41
49
|
private shouldContinuePolling;
|