pi-one-ui 0.1.1
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/LICENSE +21 -0
- package/README.md +106 -0
- package/extensions/app/config/renderer.ts +452 -0
- package/extensions/app/config/shell.ts +2484 -0
- package/extensions/app/ownership.ts +18 -0
- package/extensions/app/panel.ts +533 -0
- package/extensions/app/presets.ts +36 -0
- package/extensions/features/agent-summary/core.ts +194 -0
- package/extensions/features/agent-summary/index.ts +37 -0
- package/extensions/features/compact-thinking.ts +1262 -0
- package/extensions/features/context.ts +857 -0
- package/extensions/features/reference/index.ts +643 -0
- package/extensions/features/reference/session.ts +278 -0
- package/extensions/features/reference/subagent.ts +218 -0
- package/extensions/features/shell/aliases.ts +28 -0
- package/extensions/features/shell/flush-docked-bash.ts +39 -0
- package/extensions/features/shell/startup-header.ts +357 -0
- package/extensions/features/shell/working-message.ts +226 -0
- package/extensions/index.ts +52 -0
- package/extensions/shell/accent-rail-editor.ts +112 -0
- package/extensions/shell/accent-rail-layout-patch.ts +530 -0
- package/extensions/shell/completion-menu.ts +186 -0
- package/extensions/shell/editor-metadata-format.ts +287 -0
- package/extensions/shell/editor-transfer.ts +73 -0
- package/extensions/shell/extension-status.ts +102 -0
- package/extensions/shell/footer-format.ts +388 -0
- package/extensions/shell/footer-layout.ts +144 -0
- package/extensions/shell/footer.ts +1035 -0
- package/extensions/shell/format.ts +577 -0
- package/extensions/shell/git.ts +434 -0
- package/extensions/shell/icons.ts +261 -0
- package/extensions/shell/index.ts +1659 -0
- package/extensions/shell/interaction-summary.ts +1194 -0
- package/extensions/shell/live-context.ts +82 -0
- package/extensions/shell/minimalist-editor.ts +548 -0
- package/extensions/shell/package-version.ts +675 -0
- package/extensions/shell/project-refresh.ts +130 -0
- package/extensions/shell/project-state.ts +60 -0
- package/extensions/shell/prototype-patch-registry.ts +183 -0
- package/extensions/shell/runtime.ts +915 -0
- package/extensions/shell/selector-border.ts +109 -0
- package/extensions/shell/session-lifecycle.ts +60 -0
- package/extensions/shell/settings-command.ts +2144 -0
- package/extensions/shell/settings-previews.ts +137 -0
- package/extensions/shell/state.ts +83 -0
- package/extensions/shell/style.ts +480 -0
- package/extensions/shell/telemetry.ts +90 -0
- package/extensions/shell/ui.ts +1326 -0
- package/extensions/shell/user-message-osc.ts +279 -0
- package/extensions/shell/user-message-styles.ts +266 -0
- package/extensions/shell/user-message.ts +226 -0
- package/extensions/shell/working-line-messages.ts +24 -0
- package/extensions/shell/working-line-spinners.ts +54 -0
- package/extensions/shell/working-line.ts +1603 -0
- package/extensions/tools/ansi-color.ts +44 -0
- package/extensions/tools/ansi-text.ts +52 -0
- package/extensions/tools/component-tree.ts +37 -0
- package/extensions/tools/format.ts +28 -0
- package/extensions/tools/fullscreen-detect.ts +11 -0
- package/extensions/tools/patch-keys.ts +158 -0
- package/extensions/tools/sgr-mouse.ts +59 -0
- package/extensions/tools/tool-loading-icon.ts +20 -0
- package/extensions/tools/tool-result-sanitize.ts +27 -0
- package/extensions/transcript/index.ts +65 -0
- package/extensions/transcript/renderer/compact-mode.ts +1443 -0
- package/extensions/transcript/renderer/default-mode.ts +735 -0
- package/extensions/transcript/renderer/index.ts +286 -0
- package/extensions/transcript/renderer/markdown-enhance.ts +325 -0
- package/extensions/transcript/renderer/mouse/hover.ts +175 -0
- package/extensions/transcript/renderer/mouse/interaction.ts +1016 -0
- package/extensions/transcript/renderer/mouse/layout.ts +207 -0
- package/extensions/transcript/renderer/mouse/packets.ts +96 -0
- package/extensions/transcript/renderer/mouse/scroll.ts +211 -0
- package/extensions/transcript/renderer/tool/diff/ATTRIBUTION.md +28 -0
- package/extensions/transcript/renderer/tool/diff/ansi-utils.ts +128 -0
- package/extensions/transcript/renderer/tool/diff/diff-component.ts +112 -0
- package/extensions/transcript/renderer/tool/diff/diff-edit-render.ts +255 -0
- package/extensions/transcript/renderer/tool/diff/diff-header.ts +184 -0
- package/extensions/transcript/renderer/tool/diff/diff-highlight.ts +131 -0
- package/extensions/transcript/renderer/tool/diff/diff-inline.ts +350 -0
- package/extensions/transcript/renderer/tool/diff/diff-layout.ts +870 -0
- package/extensions/transcript/renderer/tool/diff/diff-limits.ts +189 -0
- package/extensions/transcript/renderer/tool/diff/diff-palette.ts +436 -0
- package/extensions/transcript/renderer/tool/diff/diff-parse.ts +536 -0
- package/extensions/transcript/renderer/tool/diff/diff-presentation.ts +79 -0
- package/extensions/transcript/renderer/tool/diff/diff-renderer.ts +29 -0
- package/extensions/transcript/renderer/tool/diff/diff-text.ts +57 -0
- package/extensions/transcript/renderer/tool/diff/diff-write-render.ts +629 -0
- package/extensions/transcript/renderer/tool/diff/index.ts +147 -0
- package/extensions/transcript/renderer/tool/diff/line-width-safety.ts +100 -0
- package/extensions/transcript/renderer/tool/diff/shiki-highlight.ts +104 -0
- package/extensions/transcript/renderer/tool/diff/write-execution.ts +124 -0
- package/extensions/transcript/renderer/tool/grouping.ts +619 -0
- package/extensions/transcript/renderer/tool/message-display.ts +183 -0
- package/extensions/transcript/renderer/tool/names.ts +195 -0
- package/extensions/transcript/renderer/tool/result.ts +842 -0
- package/extensions/transcript/renderer/tool/show-more-hint.ts +42 -0
- package/extensions/transcript/renderer/transcript-refresh.ts +79 -0
- package/package.json +58 -0
- package/themes/cc-dark.json +91 -0
- package/themes/cc-light.json +91 -0
|
@@ -0,0 +1,1659 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import type {
|
|
4
|
+
ExtensionAPI,
|
|
5
|
+
ExtensionContext,
|
|
6
|
+
KeybindingsManager,
|
|
7
|
+
Theme,
|
|
8
|
+
} from "@earendil-works/pi-coding-agent";
|
|
9
|
+
import type { EditorTheme, TUI } from "@earendil-works/pi-tui";
|
|
10
|
+
import {
|
|
11
|
+
type AccentRailLayoutPatchDiagnostic,
|
|
12
|
+
installHostAccentRailLayoutPatch,
|
|
13
|
+
markAccentRailLayoutEditor,
|
|
14
|
+
retainAccentRailLayoutPatchInstallation,
|
|
15
|
+
} from "./accent-rail-layout-patch.ts";
|
|
16
|
+
import {
|
|
17
|
+
type AccentRailEditorStyleConfig,
|
|
18
|
+
type ContextStyle,
|
|
19
|
+
type EditorComponentConfig,
|
|
20
|
+
type ExtensionStatusColorMode,
|
|
21
|
+
type ExtensionStatusPlacement,
|
|
22
|
+
ensureConfigExists,
|
|
23
|
+
FOOTER_FORMAT_ALIASES,
|
|
24
|
+
type FooterComponentConfig,
|
|
25
|
+
type FooterSegmentsConfig,
|
|
26
|
+
type GitBranchConfig,
|
|
27
|
+
type GitCommitConfig,
|
|
28
|
+
type GitMetricsConfig,
|
|
29
|
+
hasUnsupportedComponentStyle,
|
|
30
|
+
type IconMode,
|
|
31
|
+
loadConfig,
|
|
32
|
+
type MinimalistConfig,
|
|
33
|
+
type PathDisplayConfig,
|
|
34
|
+
type PolishedCopyFriendlyEditorStyleConfig,
|
|
35
|
+
type PolishedEditorStyleConfig,
|
|
36
|
+
type PolishedTuiConfig,
|
|
37
|
+
type SelectorBordersComponentConfig,
|
|
38
|
+
type SeparatorStyle,
|
|
39
|
+
saveAccentRailEditorStylePatch,
|
|
40
|
+
saveEditorComponentPatch,
|
|
41
|
+
saveExtensionStatusColorMode,
|
|
42
|
+
saveExtensionStatusDefaultPlacement,
|
|
43
|
+
saveExtensionStatusPlacement,
|
|
44
|
+
saveFooterComponentPatch,
|
|
45
|
+
saveIconsModePatch,
|
|
46
|
+
saveMinimalistEditorStylePatch,
|
|
47
|
+
savePolishedCopyFriendlyEditorStylePatch,
|
|
48
|
+
savePolishedEditorStylePatch,
|
|
49
|
+
saveSelectorBordersComponentPatch,
|
|
50
|
+
saveStarshipFooterStylePatch,
|
|
51
|
+
saveUserMessagesComponentPatch,
|
|
52
|
+
saveWorkingLineComponentPatch,
|
|
53
|
+
type UserMessagesComponentConfig,
|
|
54
|
+
type WorkingLineComponentPatch,
|
|
55
|
+
type ZentuiConfig,
|
|
56
|
+
} from "../app/config/shell.ts";
|
|
57
|
+
import {
|
|
58
|
+
type EditorTransferFailureReason,
|
|
59
|
+
replaceEditorComponentWithExpandedText,
|
|
60
|
+
} from "./editor-transfer.ts";
|
|
61
|
+
import { installFooter, installHiddenFooter } from "./footer.ts";
|
|
62
|
+
import {
|
|
63
|
+
collectFooterFormatReferences,
|
|
64
|
+
parseFooterFormat,
|
|
65
|
+
} from "./footer-format.ts";
|
|
66
|
+
import {
|
|
67
|
+
buildSessionDurationLabel,
|
|
68
|
+
invalidateUsageTotalsCache,
|
|
69
|
+
} from "./format.ts";
|
|
70
|
+
import { emptyGitStatus, readGitStatus } from "./git.ts";
|
|
71
|
+
import {
|
|
72
|
+
InteractionMetricsTracker,
|
|
73
|
+
renderTurnSummaryEntry,
|
|
74
|
+
TURN_SUMMARY_ENTRY_TYPE,
|
|
75
|
+
} from "./interaction-summary.ts";
|
|
76
|
+
import { LiveContextController } from "./live-context.ts";
|
|
77
|
+
import { readPackageVersionResult } from "./package-version.ts";
|
|
78
|
+
import {
|
|
79
|
+
createProjectRefreshScheduler,
|
|
80
|
+
type ProjectRefreshRun,
|
|
81
|
+
type ScheduleProjectRefreshOptions,
|
|
82
|
+
type StopProjectRefreshInterval,
|
|
83
|
+
startProjectRefreshInterval,
|
|
84
|
+
} from "./project-refresh.ts";
|
|
85
|
+
import { applyProjectRefreshToState } from "./project-state.ts";
|
|
86
|
+
import { readRuntimeInfo } from "./runtime.ts";
|
|
87
|
+
import {
|
|
88
|
+
installSelectorBorderStyle,
|
|
89
|
+
removeSelectorBorderStyle,
|
|
90
|
+
} from "./selector-border.ts";
|
|
91
|
+
import { SessionLifecycle } from "./session-lifecycle.ts";
|
|
92
|
+
import { registerShellSettingsCommand } from "./settings-command.ts";
|
|
93
|
+
import {
|
|
94
|
+
createInitialState,
|
|
95
|
+
type FooterState,
|
|
96
|
+
modelLabelFor,
|
|
97
|
+
syncState,
|
|
98
|
+
} from "./state.ts";
|
|
99
|
+
import { resolveFooterTelemetry } from "./telemetry.ts";
|
|
100
|
+
import { PolishedEditor, WrappedPolishedEditor } from "./ui.ts";
|
|
101
|
+
import {
|
|
102
|
+
installUserMessageStyle,
|
|
103
|
+
removeUserMessageStyle,
|
|
104
|
+
} from "./user-message.ts";
|
|
105
|
+
import {
|
|
106
|
+
AgentDurationClock,
|
|
107
|
+
snapshotWorkingLineHighStyle,
|
|
108
|
+
WorkingLineController,
|
|
109
|
+
} from "./working-line.ts";
|
|
110
|
+
|
|
111
|
+
const ZENTUI_EDITOR_FACTORY = Symbol.for("pi-zentui.editor-factory");
|
|
112
|
+
const ZENTUI_EDITOR_BASE_FACTORY = Symbol.for("pi-zentui.editor-base-factory");
|
|
113
|
+
const ZENTUI_EDITOR_OWNER = Symbol.for("pi-zentui.editor-owner");
|
|
114
|
+
const ZENTUI_FOOTER_OWNER = Symbol.for("pi-zentui.footer-owner");
|
|
115
|
+
|
|
116
|
+
type EditorFactory = NonNullable<
|
|
117
|
+
Parameters<ExtensionContext["ui"]["setEditorComponent"]>[0]
|
|
118
|
+
>;
|
|
119
|
+
|
|
120
|
+
type ZentuiEditorFactory = EditorFactory & {
|
|
121
|
+
[ZENTUI_EDITOR_FACTORY]?: true;
|
|
122
|
+
[ZENTUI_EDITOR_BASE_FACTORY]?: EditorFactory;
|
|
123
|
+
[ZENTUI_EDITOR_OWNER]?: symbol;
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
type ApplyUiResult = {
|
|
127
|
+
editorBlocked: boolean;
|
|
128
|
+
editorReason?: string;
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
type EditorChangeResult = { ok: true } | { ok: false; reason: string };
|
|
132
|
+
|
|
133
|
+
type EditorInstallMode = "none" | "standalone" | "wrapper";
|
|
134
|
+
type InstalledFooterKind = "starship" | "hidden";
|
|
135
|
+
|
|
136
|
+
function editorTransferFailureMessage(
|
|
137
|
+
reason: EditorTransferFailureReason,
|
|
138
|
+
): string {
|
|
139
|
+
switch (reason) {
|
|
140
|
+
case "unsupported-transfer-api":
|
|
141
|
+
return "this Pi version cannot safely transfer expanded editor text; reload Pi to apply this change";
|
|
142
|
+
case "editor-factory-snapshot-failed":
|
|
143
|
+
return "the current editor factory could not be read safely; reload Pi to apply this change";
|
|
144
|
+
case "editor-text-snapshot-failed":
|
|
145
|
+
return "expanded editor text could not be read safely; reload Pi to apply this change";
|
|
146
|
+
case "editor-text-preparation-failed":
|
|
147
|
+
return "expanded editor text could not be prepared safely; reload Pi to apply this change";
|
|
148
|
+
case "editor-replacement-failed-with-rollback":
|
|
149
|
+
return "the editor replacement failed; the previous factory was reapplied, but editor instance identity is not guaranteed";
|
|
150
|
+
case "editor-replacement-rollback-failed":
|
|
151
|
+
return "the editor replacement and previous-factory rollback both failed; reload Pi before editing";
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function isZentuiEditorFactory(factory: EditorFactory | undefined): boolean {
|
|
156
|
+
return Boolean(
|
|
157
|
+
(factory as ZentuiEditorFactory | undefined)?.[ZENTUI_EDITOR_FACTORY],
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function getZentuiEditorBaseFactory(
|
|
162
|
+
factory: EditorFactory | undefined,
|
|
163
|
+
): EditorFactory | undefined {
|
|
164
|
+
return (factory as ZentuiEditorFactory | undefined)?.[
|
|
165
|
+
ZENTUI_EDITOR_BASE_FACTORY
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export function activeFooterReferences(config: ZentuiConfig): Set<string> {
|
|
170
|
+
const starship = config.components.footer.styles.starship;
|
|
171
|
+
const references = starship.format
|
|
172
|
+
? collectFooterFormatReferences(
|
|
173
|
+
parseFooterFormat(starship.format),
|
|
174
|
+
FOOTER_FORMAT_ALIASES,
|
|
175
|
+
)
|
|
176
|
+
: new Set<string>([
|
|
177
|
+
...(starship.segments.sessionName ? ["session_name"] : []),
|
|
178
|
+
...(starship.segments.runtime ? ["runtime"] : []),
|
|
179
|
+
...(starship.segments.gitCommit ? ["git_commit"] : []),
|
|
180
|
+
...(starship.segments.gitMetrics ? ["git_metrics"] : []),
|
|
181
|
+
...(starship.segments.packageVersion ? ["package"] : []),
|
|
182
|
+
...(starship.segments.sessionDuration ? ["session_duration"] : []),
|
|
183
|
+
...(starship.segments.time ? ["time"] : []),
|
|
184
|
+
]);
|
|
185
|
+
if (starship.responsive) {
|
|
186
|
+
for (const name of collectFooterFormatReferences(
|
|
187
|
+
parseFooterFormat(starship.compactFormat),
|
|
188
|
+
FOOTER_FORMAT_ALIASES,
|
|
189
|
+
)) {
|
|
190
|
+
references.add(name);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
return references;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function findRepositoryRoot(cwd: string): string | undefined {
|
|
197
|
+
let current = resolve(cwd);
|
|
198
|
+
while (true) {
|
|
199
|
+
if (existsSync(join(current, ".git"))) return current;
|
|
200
|
+
const parent = dirname(current);
|
|
201
|
+
if (parent === current) return undefined;
|
|
202
|
+
current = parent;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function isTuiContext(ctx: ExtensionContext): boolean {
|
|
207
|
+
try {
|
|
208
|
+
const mode = (ctx as ExtensionContext & { mode?: string }).mode;
|
|
209
|
+
return ctx.hasUI && (mode === undefined || mode === "tui");
|
|
210
|
+
} catch {
|
|
211
|
+
return false;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export type ShellRuntimeController = {
|
|
216
|
+
setEditorComponent: (
|
|
217
|
+
patch: Partial<EditorComponentConfig>,
|
|
218
|
+
ctx: ExtensionContext,
|
|
219
|
+
) => { applied: boolean; reason?: string };
|
|
220
|
+
setUserMessagesComponent: (
|
|
221
|
+
patch: Partial<UserMessagesComponentConfig>,
|
|
222
|
+
ctx: ExtensionContext,
|
|
223
|
+
) => void;
|
|
224
|
+
setWorkingLineComponent: (
|
|
225
|
+
patch: WorkingLineComponentPatch,
|
|
226
|
+
ctx: ExtensionContext,
|
|
227
|
+
) => { applied: boolean; reason?: string };
|
|
228
|
+
setFooterComponent: (
|
|
229
|
+
patch: Partial<FooterComponentConfig>,
|
|
230
|
+
ctx: ExtensionContext,
|
|
231
|
+
) => void;
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
export type ShellExtensionOptions = {
|
|
235
|
+
/** Expose the live shell reconciler to the unified settings panel. */
|
|
236
|
+
/** Keep legacy standalone registration disabled in the unified package. */
|
|
237
|
+
registerCommand?: boolean;
|
|
238
|
+
onRuntimeController?: (controller: ShellRuntimeController) => void;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
export default function (
|
|
242
|
+
pi: ExtensionAPI,
|
|
243
|
+
options: ShellExtensionOptions = {},
|
|
244
|
+
) {
|
|
245
|
+
const state: FooterState = createInitialState(emptyGitStatus());
|
|
246
|
+
const sessionLifecycle = new SessionLifecycle();
|
|
247
|
+
const editorOwnerToken = Symbol("zentui-editor-owner");
|
|
248
|
+
|
|
249
|
+
let currentConfig: PolishedTuiConfig = loadConfig();
|
|
250
|
+
// Keep the capability guard defensive for hosts with incomplete extension APIs.
|
|
251
|
+
if (typeof pi.registerEntryRenderer === "function") {
|
|
252
|
+
pi.registerEntryRenderer(TURN_SUMMARY_ENTRY_TYPE, (entry, options, theme) =>
|
|
253
|
+
renderTurnSummaryEntry(
|
|
254
|
+
entry,
|
|
255
|
+
{
|
|
256
|
+
...options,
|
|
257
|
+
colorSource: currentConfig.components.workingLine.colorSource,
|
|
258
|
+
workingLineHigh: currentConfig.colors.workingLineHigh,
|
|
259
|
+
},
|
|
260
|
+
theme,
|
|
261
|
+
),
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
let activeTheme: Theme | undefined;
|
|
265
|
+
let requestFooterRender: (() => void) | undefined;
|
|
266
|
+
let requestEditorRender: (() => void) | undefined;
|
|
267
|
+
let getActiveExtensionStatuses: () => ReadonlyMap<string, string> = () =>
|
|
268
|
+
new Map();
|
|
269
|
+
let stopRefreshInterval: StopProjectRefreshInterval = () => {};
|
|
270
|
+
let cleanupUserMessageStyle: () => void = () => {};
|
|
271
|
+
let userMessageStyleInstalled = false;
|
|
272
|
+
let cleanupSelectorBorderStyle: () => void = () => {};
|
|
273
|
+
let selectorBorderStyleInstalled = false;
|
|
274
|
+
let installedFooterKind: InstalledFooterKind | undefined;
|
|
275
|
+
let installedFooterToken: symbol | undefined;
|
|
276
|
+
let editorInstalled = false;
|
|
277
|
+
let editorInstallMode: EditorInstallMode = "none";
|
|
278
|
+
let installedEditorFactory: EditorFactory | undefined;
|
|
279
|
+
let wrappedEditorFactory: EditorFactory | undefined;
|
|
280
|
+
let stopSessionTimer: () => void = () => {};
|
|
281
|
+
let stopMinimalistDurationUpdates: () => void = () => {};
|
|
282
|
+
let minimalistDurationUpdatesActive = false;
|
|
283
|
+
let minimalistDecorationActive = false;
|
|
284
|
+
let sessionTimerRequirements = "";
|
|
285
|
+
let lastDurationLabel = "";
|
|
286
|
+
let lastProjectCwd: string | undefined;
|
|
287
|
+
let requestedProjectCwd: string | undefined;
|
|
288
|
+
const agentDurationClock = new AgentDurationClock();
|
|
289
|
+
const interactionMetrics = new InteractionMetricsTracker();
|
|
290
|
+
let agentRunActive = false;
|
|
291
|
+
let minimalistProjectRoot: string | undefined;
|
|
292
|
+
let projectRefreshActive = false;
|
|
293
|
+
let activeTuiContext: ExtensionContext | undefined;
|
|
294
|
+
let cleanupAccentRailLayoutPatch: () => void = () => {};
|
|
295
|
+
let accentRailLayoutPatchInstallSerial = 0;
|
|
296
|
+
|
|
297
|
+
const recordAccentRailLayoutPatchDiagnostic = (
|
|
298
|
+
diagnostic: AccentRailLayoutPatchDiagnostic,
|
|
299
|
+
version?: string,
|
|
300
|
+
) => {
|
|
301
|
+
if (process.env.ZENTUI_DEBUG === "1") {
|
|
302
|
+
console.error(
|
|
303
|
+
`[zentui] Accent Rail fullscreen layout patch: ${diagnostic}${version ? ` (Pi TUI ${version})` : ""}`,
|
|
304
|
+
);
|
|
305
|
+
}
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const isOwnedEditorFactory = (factory: EditorFactory | undefined) =>
|
|
309
|
+
(factory as ZentuiEditorFactory | undefined)?.[ZENTUI_EDITOR_OWNER] ===
|
|
310
|
+
editorOwnerToken;
|
|
311
|
+
const effectiveEditorEnabled = () =>
|
|
312
|
+
currentConfig.components.editor.enabled &&
|
|
313
|
+
!hasUnsupportedComponentStyle(currentConfig, "editor");
|
|
314
|
+
const effectiveUserMessagesEnabled = () =>
|
|
315
|
+
currentConfig.components.userMessages.enabled &&
|
|
316
|
+
!hasUnsupportedComponentStyle(currentConfig, "userMessages");
|
|
317
|
+
const effectiveSelectorBordersEnabled = () =>
|
|
318
|
+
currentConfig.components.selectorBorders.enabled &&
|
|
319
|
+
!hasUnsupportedComponentStyle(currentConfig, "selectorBorders");
|
|
320
|
+
const effectiveFooterStyle = () =>
|
|
321
|
+
hasUnsupportedComponentStyle(currentConfig, "footer")
|
|
322
|
+
? ("native" as const)
|
|
323
|
+
: currentConfig.components.footer.style;
|
|
324
|
+
|
|
325
|
+
const ownsInstalledEditorFactory = () => {
|
|
326
|
+
if (
|
|
327
|
+
!sessionLifecycle.isCurrent() ||
|
|
328
|
+
!editorInstalled ||
|
|
329
|
+
!installedEditorFactory ||
|
|
330
|
+
!activeTuiContext
|
|
331
|
+
) {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
return (
|
|
336
|
+
isOwnedEditorFactory(installedEditorFactory) &&
|
|
337
|
+
activeTuiContext.ui.getEditorComponent() === installedEditorFactory
|
|
338
|
+
);
|
|
339
|
+
} catch {
|
|
340
|
+
return false;
|
|
341
|
+
}
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const refresh = () => {
|
|
345
|
+
if (!sessionLifecycle.isCurrent()) return;
|
|
346
|
+
requestFooterRender?.();
|
|
347
|
+
requestEditorRender?.();
|
|
348
|
+
};
|
|
349
|
+
const liveContext = new LiveContextController(sessionLifecycle, refresh);
|
|
350
|
+
const getActiveTheme = () => activeTheme;
|
|
351
|
+
const getCurrentConfig = () => currentConfig;
|
|
352
|
+
const workingLine = new WorkingLineController(
|
|
353
|
+
getCurrentConfig,
|
|
354
|
+
() => activeTheme as Theme,
|
|
355
|
+
agentDurationClock,
|
|
356
|
+
Math.random,
|
|
357
|
+
Date.now,
|
|
358
|
+
() => interactionMetrics.currentThought(),
|
|
359
|
+
);
|
|
360
|
+
const getContextWindow = (ctx: ExtensionContext): number | undefined =>
|
|
361
|
+
ctx.model?.contextWindow ?? ctx.getContextUsage()?.contextWindow;
|
|
362
|
+
const getContextPercent = (ctx: ExtensionContext): number | undefined => {
|
|
363
|
+
const usage = ctx.getContextUsage();
|
|
364
|
+
const contextWindow = getContextWindow(ctx);
|
|
365
|
+
const live = liveContext.get();
|
|
366
|
+
return live && contextWindow && contextWindow > 0
|
|
367
|
+
? (live.tokens / contextWindow) * 100
|
|
368
|
+
: (usage?.percent ?? undefined);
|
|
369
|
+
};
|
|
370
|
+
const getAgentDurationMs = () => agentDurationClock.elapsedMs();
|
|
371
|
+
const getThinkingLevel = () =>
|
|
372
|
+
sessionLifecycle.isCurrent() ? pi.getThinkingLevel() : ("off" as const);
|
|
373
|
+
const syncFooterState = (ctx: ExtensionContext) =>
|
|
374
|
+
syncState(
|
|
375
|
+
state,
|
|
376
|
+
ctx,
|
|
377
|
+
currentConfig.icons.cacheHit,
|
|
378
|
+
resolveFooterTelemetry(ctx),
|
|
379
|
+
);
|
|
380
|
+
const ownsInstalledFooter = () =>
|
|
381
|
+
Boolean(
|
|
382
|
+
activeTuiContext &&
|
|
383
|
+
installedFooterToken &&
|
|
384
|
+
ctxFooterOwner(activeTuiContext) === installedFooterToken,
|
|
385
|
+
);
|
|
386
|
+
const installedFooterReferences = () =>
|
|
387
|
+
installedFooterKind === "starship" && ownsInstalledFooter()
|
|
388
|
+
? activeFooterReferences(currentConfig)
|
|
389
|
+
: new Set<string>();
|
|
390
|
+
|
|
391
|
+
type ProjectRefreshTarget = { cwd: string; generation: number };
|
|
392
|
+
const refreshProjectState = async (
|
|
393
|
+
{ cwd, generation }: ProjectRefreshTarget,
|
|
394
|
+
run: ProjectRefreshRun,
|
|
395
|
+
) => {
|
|
396
|
+
if (!run.isCurrent() || !sessionLifecycle.isCurrent(generation)) return;
|
|
397
|
+
const starship = currentConfig.components.footer.styles.starship;
|
|
398
|
+
const gitCommitConfig = starship.gitCommit;
|
|
399
|
+
const gitMetricsConfig = starship.gitMetrics;
|
|
400
|
+
const references = installedFooterReferences();
|
|
401
|
+
const wantExactTag =
|
|
402
|
+
(references.has("git_commit") && gitCommitConfig.showTag) ||
|
|
403
|
+
references.has("git_tag");
|
|
404
|
+
const wantMetrics =
|
|
405
|
+
references.has("git_metrics") ||
|
|
406
|
+
references.has("git_added") ||
|
|
407
|
+
references.has("git_deleted");
|
|
408
|
+
const wantPackage =
|
|
409
|
+
references.has("package") || references.has("package_version");
|
|
410
|
+
const wantRuntime = references.has("runtime");
|
|
411
|
+
const [git, runtime, packageVersion] = await Promise.all([
|
|
412
|
+
readGitStatus(cwd, {
|
|
413
|
+
readExactTag: wantExactTag,
|
|
414
|
+
readMetrics: wantMetrics,
|
|
415
|
+
ignoreSubmodules: gitMetricsConfig.ignoreSubmodules,
|
|
416
|
+
}),
|
|
417
|
+
wantRuntime
|
|
418
|
+
? readRuntimeInfo(cwd)
|
|
419
|
+
: Promise.resolve({ kind: "ok" as const, runtime: undefined }),
|
|
420
|
+
wantPackage
|
|
421
|
+
? readPackageVersionResult(cwd)
|
|
422
|
+
: Promise.resolve({ kind: "ok" as const, result: null }),
|
|
423
|
+
]);
|
|
424
|
+
if (
|
|
425
|
+
!run.isCurrent() ||
|
|
426
|
+
!sessionLifecycle.isCurrent(generation) ||
|
|
427
|
+
requestedProjectCwd !== cwd
|
|
428
|
+
) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
minimalistProjectRoot =
|
|
432
|
+
git.kind === "ok" ? findRepositoryRoot(cwd) : undefined;
|
|
433
|
+
lastProjectCwd = applyProjectRefreshToState(state, {
|
|
434
|
+
cwd,
|
|
435
|
+
previousCwd: lastProjectCwd,
|
|
436
|
+
git,
|
|
437
|
+
runtime,
|
|
438
|
+
packageVersion,
|
|
439
|
+
});
|
|
440
|
+
};
|
|
441
|
+
|
|
442
|
+
const projectRefreshScheduler = createProjectRefreshScheduler(
|
|
443
|
+
refreshProjectState,
|
|
444
|
+
refresh,
|
|
445
|
+
);
|
|
446
|
+
const scheduleProjectRefresh = (
|
|
447
|
+
ctx: ExtensionContext,
|
|
448
|
+
options?: ScheduleProjectRefreshOptions,
|
|
449
|
+
) => {
|
|
450
|
+
const generation = sessionLifecycle.currentGeneration();
|
|
451
|
+
if (!sessionLifecycle.isCurrent(generation)) return;
|
|
452
|
+
const cwd = ctx.cwd;
|
|
453
|
+
requestedProjectCwd = cwd;
|
|
454
|
+
projectRefreshScheduler.schedule({ cwd, generation }, options);
|
|
455
|
+
};
|
|
456
|
+
|
|
457
|
+
const minimalistProjectRequired = () => {
|
|
458
|
+
const editor = currentConfig.components.editor;
|
|
459
|
+
const minimalist = editor.styles.minimalist;
|
|
460
|
+
return (
|
|
461
|
+
effectiveEditorEnabled() &&
|
|
462
|
+
ownsInstalledEditorFactory() &&
|
|
463
|
+
editor.style === "minimalist" &&
|
|
464
|
+
(minimalist.showGit || minimalist.pathDisplay === "project")
|
|
465
|
+
);
|
|
466
|
+
};
|
|
467
|
+
|
|
468
|
+
const needsProjectRefresh = () =>
|
|
469
|
+
(installedFooterKind === "starship" && ownsInstalledFooter()) ||
|
|
470
|
+
minimalistProjectRequired();
|
|
471
|
+
|
|
472
|
+
const stopProjectRefresh = () => {
|
|
473
|
+
stopRefreshInterval();
|
|
474
|
+
stopRefreshInterval = () => {};
|
|
475
|
+
projectRefreshScheduler.stop();
|
|
476
|
+
projectRefreshActive = false;
|
|
477
|
+
};
|
|
478
|
+
|
|
479
|
+
const reconcileProjectRefresh = (ctx: ExtensionContext, force = false) => {
|
|
480
|
+
if (!sessionLifecycle.isCurrent() || !needsProjectRefresh()) {
|
|
481
|
+
stopProjectRefresh();
|
|
482
|
+
return;
|
|
483
|
+
}
|
|
484
|
+
const activated = !projectRefreshActive;
|
|
485
|
+
if (activated) {
|
|
486
|
+
stopRefreshInterval = startProjectRefreshInterval(
|
|
487
|
+
currentConfig.projectRefreshIntervalMs,
|
|
488
|
+
() => {
|
|
489
|
+
if (editorInstalled && !ownsInstalledEditorFactory()) {
|
|
490
|
+
reconcileObservedEditorOwnership(ctx);
|
|
491
|
+
}
|
|
492
|
+
if (!needsProjectRefresh()) {
|
|
493
|
+
stopProjectRefresh();
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
scheduleProjectRefresh(ctx);
|
|
497
|
+
},
|
|
498
|
+
);
|
|
499
|
+
projectRefreshActive = true;
|
|
500
|
+
}
|
|
501
|
+
if (force && !activated) projectRefreshScheduler.invalidate();
|
|
502
|
+
if (force || activated) scheduleProjectRefresh(ctx, { force: true });
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
const refreshInteractiveState = (ctx: ExtensionContext, project = false) => {
|
|
506
|
+
if (!sessionLifecycle.isCurrent() || !ctx.hasUI) return;
|
|
507
|
+
if (editorInstalled && !ownsInstalledEditorFactory())
|
|
508
|
+
reconcileObservedEditorOwnership(ctx);
|
|
509
|
+
syncFooterState(ctx);
|
|
510
|
+
if (project && needsProjectRefresh()) scheduleProjectRefresh(ctx);
|
|
511
|
+
refresh();
|
|
512
|
+
};
|
|
513
|
+
|
|
514
|
+
const reconcileSessionTimer = () => {
|
|
515
|
+
const references = installedFooterReferences();
|
|
516
|
+
const needsTime = references.has("time");
|
|
517
|
+
const needsDuration = references.has("session_duration");
|
|
518
|
+
const nextRequirements =
|
|
519
|
+
needsTime || needsDuration ? `${needsTime}:${needsDuration}` : "";
|
|
520
|
+
if (
|
|
521
|
+
!sessionLifecycle.isCurrent() ||
|
|
522
|
+
installedFooterKind !== "starship" ||
|
|
523
|
+
!ownsInstalledFooter() ||
|
|
524
|
+
!nextRequirements
|
|
525
|
+
) {
|
|
526
|
+
stopSessionTimer();
|
|
527
|
+
sessionTimerRequirements = "";
|
|
528
|
+
lastDurationLabel = "";
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
if (sessionTimerRequirements === nextRequirements) return;
|
|
532
|
+
|
|
533
|
+
stopSessionTimer();
|
|
534
|
+
sessionTimerRequirements = nextRequirements;
|
|
535
|
+
lastDurationLabel = "";
|
|
536
|
+
const timer = setInterval(() => {
|
|
537
|
+
if (!sessionLifecycle.isCurrent()) return;
|
|
538
|
+
if (needsTime) {
|
|
539
|
+
refresh();
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
const label = state.sessionStartEpoch
|
|
543
|
+
? buildSessionDurationLabel(state.sessionStartEpoch)
|
|
544
|
+
: "";
|
|
545
|
+
if (label === lastDurationLabel) return;
|
|
546
|
+
lastDurationLabel = label;
|
|
547
|
+
refresh();
|
|
548
|
+
}, 1000);
|
|
549
|
+
stopSessionTimer = () => {
|
|
550
|
+
clearInterval(timer);
|
|
551
|
+
sessionTimerRequirements = "";
|
|
552
|
+
stopSessionTimer = () => {};
|
|
553
|
+
};
|
|
554
|
+
};
|
|
555
|
+
|
|
556
|
+
const reconcileAgentTimer = () => {
|
|
557
|
+
const needed =
|
|
558
|
+
sessionLifecycle.isCurrent() &&
|
|
559
|
+
agentRunActive &&
|
|
560
|
+
agentDurationClock.isActive() &&
|
|
561
|
+
minimalistDecorationActive &&
|
|
562
|
+
effectiveEditorEnabled() &&
|
|
563
|
+
ownsInstalledEditorFactory() &&
|
|
564
|
+
currentConfig.components.editor.style === "minimalist" &&
|
|
565
|
+
currentConfig.components.editor.styles.minimalist.showTimer;
|
|
566
|
+
if (!needed) {
|
|
567
|
+
stopMinimalistDurationUpdates();
|
|
568
|
+
stopMinimalistDurationUpdates = () => {};
|
|
569
|
+
minimalistDurationUpdatesActive = false;
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
if (minimalistDurationUpdatesActive) return;
|
|
573
|
+
minimalistDurationUpdatesActive = true;
|
|
574
|
+
stopMinimalistDurationUpdates = agentDurationClock.subscribe(() => {
|
|
575
|
+
const ctx = activeTuiContext;
|
|
576
|
+
if (ctx && editorInstalled && !ownsInstalledEditorFactory()) {
|
|
577
|
+
reconcileObservedEditorOwnership(ctx);
|
|
578
|
+
}
|
|
579
|
+
reconcileAgentTimer();
|
|
580
|
+
if (minimalistDurationUpdatesActive) refresh();
|
|
581
|
+
});
|
|
582
|
+
};
|
|
583
|
+
|
|
584
|
+
const setMinimalistDecorationActive = (active: boolean) => {
|
|
585
|
+
const next =
|
|
586
|
+
sessionLifecycle.isCurrent() && active && ownsInstalledEditorFactory();
|
|
587
|
+
if (minimalistDecorationActive === next) return;
|
|
588
|
+
minimalistDecorationActive = next;
|
|
589
|
+
reconcileAgentTimer();
|
|
590
|
+
};
|
|
591
|
+
|
|
592
|
+
const startAgentTurn = (interactionStarted: boolean) => {
|
|
593
|
+
agentRunActive = true;
|
|
594
|
+
if (interactionStarted) agentDurationClock.start();
|
|
595
|
+
reconcileAgentTimer();
|
|
596
|
+
refresh();
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
const pauseAgentRun = () => {
|
|
600
|
+
agentRunActive = false;
|
|
601
|
+
reconcileAgentTimer();
|
|
602
|
+
refresh();
|
|
603
|
+
};
|
|
604
|
+
|
|
605
|
+
const settleAgentTurn = (nextStartedAt?: number) => {
|
|
606
|
+
agentRunActive = nextStartedAt !== undefined;
|
|
607
|
+
if (nextStartedAt === undefined) agentDurationClock.finish();
|
|
608
|
+
else agentDurationClock.start(nextStartedAt);
|
|
609
|
+
reconcileAgentTimer();
|
|
610
|
+
refresh();
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
const resetAgentTimer = () => {
|
|
614
|
+
stopMinimalistDurationUpdates();
|
|
615
|
+
stopMinimalistDurationUpdates = () => {};
|
|
616
|
+
minimalistDurationUpdatesActive = false;
|
|
617
|
+
agentRunActive = false;
|
|
618
|
+
agentDurationClock.reset();
|
|
619
|
+
};
|
|
620
|
+
|
|
621
|
+
const sameReferences = (left: Set<string>, right: Set<string>) =>
|
|
622
|
+
left.size === right.size && [...left].every((name) => right.has(name));
|
|
623
|
+
|
|
624
|
+
const applyFooterDependencyConfigChange = (
|
|
625
|
+
ctx: ExtensionContext,
|
|
626
|
+
save: () => PolishedTuiConfig,
|
|
627
|
+
) => {
|
|
628
|
+
const before = activeFooterReferences(currentConfig);
|
|
629
|
+
const nextConfig = save();
|
|
630
|
+
const after = activeFooterReferences(nextConfig);
|
|
631
|
+
currentConfig = nextConfig;
|
|
632
|
+
if (sameReferences(before, after)) return;
|
|
633
|
+
reconcileSessionTimer();
|
|
634
|
+
reconcileProjectRefresh(ctx, true);
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
const installUserMessages = () => {
|
|
638
|
+
if (userMessageStyleInstalled) return;
|
|
639
|
+
let cleanup: (() => void) | undefined;
|
|
640
|
+
try {
|
|
641
|
+
cleanup = installUserMessageStyle(getActiveTheme, getCurrentConfig);
|
|
642
|
+
cleanupUserMessageStyle = cleanup;
|
|
643
|
+
userMessageStyleInstalled = true;
|
|
644
|
+
} catch {
|
|
645
|
+
try {
|
|
646
|
+
cleanup?.();
|
|
647
|
+
} catch {
|
|
648
|
+
// Best effort: the installer is locally transactional.
|
|
649
|
+
}
|
|
650
|
+
cleanupUserMessageStyle = () => {};
|
|
651
|
+
userMessageStyleInstalled = false;
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
|
|
655
|
+
const uninstallUserMessages = () => {
|
|
656
|
+
try {
|
|
657
|
+
cleanupUserMessageStyle();
|
|
658
|
+
} catch {
|
|
659
|
+
// Best effort cleanup.
|
|
660
|
+
} finally {
|
|
661
|
+
cleanupUserMessageStyle = () => {};
|
|
662
|
+
userMessageStyleInstalled = false;
|
|
663
|
+
}
|
|
664
|
+
};
|
|
665
|
+
|
|
666
|
+
const reconcileUserMessages = () => {
|
|
667
|
+
if (effectiveUserMessagesEnabled()) installUserMessages();
|
|
668
|
+
else uninstallUserMessages();
|
|
669
|
+
};
|
|
670
|
+
|
|
671
|
+
const installSelectorBorders = () => {
|
|
672
|
+
if (selectorBorderStyleInstalled) return;
|
|
673
|
+
let cleanup: (() => void) | undefined;
|
|
674
|
+
try {
|
|
675
|
+
cleanup = installSelectorBorderStyle(getActiveTheme, getCurrentConfig);
|
|
676
|
+
cleanupSelectorBorderStyle = cleanup;
|
|
677
|
+
selectorBorderStyleInstalled = true;
|
|
678
|
+
} catch {
|
|
679
|
+
try {
|
|
680
|
+
cleanup?.();
|
|
681
|
+
} catch {
|
|
682
|
+
// Best effort: the installer is locally transactional.
|
|
683
|
+
}
|
|
684
|
+
cleanupSelectorBorderStyle = () => {};
|
|
685
|
+
selectorBorderStyleInstalled = false;
|
|
686
|
+
}
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
const uninstallSelectorBorders = () => {
|
|
690
|
+
try {
|
|
691
|
+
cleanupSelectorBorderStyle();
|
|
692
|
+
} catch {
|
|
693
|
+
// Best effort cleanup.
|
|
694
|
+
} finally {
|
|
695
|
+
cleanupSelectorBorderStyle = () => {};
|
|
696
|
+
selectorBorderStyleInstalled = false;
|
|
697
|
+
}
|
|
698
|
+
};
|
|
699
|
+
|
|
700
|
+
const reconcileSelectorBorders = () => {
|
|
701
|
+
const selectors = currentConfig.components.selectorBorders;
|
|
702
|
+
if (effectiveSelectorBordersEnabled() && selectors.style === "zentui") {
|
|
703
|
+
installSelectorBorders();
|
|
704
|
+
} else uninstallSelectorBorders();
|
|
705
|
+
};
|
|
706
|
+
|
|
707
|
+
const clearEditorOwnership = () => {
|
|
708
|
+
setMinimalistDecorationActive(false);
|
|
709
|
+
requestEditorRender = undefined;
|
|
710
|
+
wrappedEditorFactory = undefined;
|
|
711
|
+
installedEditorFactory = undefined;
|
|
712
|
+
editorInstallMode = "none";
|
|
713
|
+
editorInstalled = false;
|
|
714
|
+
};
|
|
715
|
+
|
|
716
|
+
const trackZentuiEditorFactory = (factory: EditorFactory): boolean => {
|
|
717
|
+
if (!isOwnedEditorFactory(factory)) return false;
|
|
718
|
+
const baseFactory = getZentuiEditorBaseFactory(factory);
|
|
719
|
+
wrappedEditorFactory = baseFactory;
|
|
720
|
+
installedEditorFactory = factory;
|
|
721
|
+
editorInstallMode = baseFactory ? "wrapper" : "standalone";
|
|
722
|
+
editorInstalled = true;
|
|
723
|
+
return true;
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
const observeEditorFactory = (
|
|
727
|
+
ctx: ExtensionContext,
|
|
728
|
+
): { known: true; factory: EditorFactory | undefined } | { known: false } => {
|
|
729
|
+
try {
|
|
730
|
+
return { known: true, factory: ctx.ui.getEditorComponent() };
|
|
731
|
+
} catch {
|
|
732
|
+
return { known: false };
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
|
|
736
|
+
const reconcileObservedEditorOwnership = (ctx: ExtensionContext) => {
|
|
737
|
+
const observed = observeEditorFactory(ctx);
|
|
738
|
+
if (!observed.known) return observed;
|
|
739
|
+
if (observed.factory && isOwnedEditorFactory(observed.factory)) {
|
|
740
|
+
trackZentuiEditorFactory(observed.factory);
|
|
741
|
+
} else {
|
|
742
|
+
clearEditorOwnership();
|
|
743
|
+
reconcileProjectRefresh(ctx);
|
|
744
|
+
}
|
|
745
|
+
return observed;
|
|
746
|
+
};
|
|
747
|
+
|
|
748
|
+
const accentRailLayoutActive = () =>
|
|
749
|
+
sessionLifecycle.isCurrent() &&
|
|
750
|
+
ownsInstalledEditorFactory() &&
|
|
751
|
+
effectiveEditorEnabled() &&
|
|
752
|
+
currentConfig.components.editor.style === "accent-rail";
|
|
753
|
+
|
|
754
|
+
const markOwnedAccentRailEditor = <T extends object>(editor: T): T => {
|
|
755
|
+
markAccentRailLayoutEditor(
|
|
756
|
+
editor,
|
|
757
|
+
editorOwnerToken,
|
|
758
|
+
accentRailLayoutActive,
|
|
759
|
+
);
|
|
760
|
+
return editor;
|
|
761
|
+
};
|
|
762
|
+
|
|
763
|
+
const makeEditorFactory = (ctx: ExtensionContext): ZentuiEditorFactory => {
|
|
764
|
+
const sessionTheme = ctx.ui.theme;
|
|
765
|
+
const factory = ((
|
|
766
|
+
tui: TUI,
|
|
767
|
+
theme: EditorTheme,
|
|
768
|
+
keybindings: KeybindingsManager,
|
|
769
|
+
) => {
|
|
770
|
+
requestEditorRender = () => tui.requestRender();
|
|
771
|
+
return markOwnedAccentRailEditor(
|
|
772
|
+
new PolishedEditor(
|
|
773
|
+
tui,
|
|
774
|
+
theme,
|
|
775
|
+
keybindings,
|
|
776
|
+
sessionTheme,
|
|
777
|
+
getCurrentConfig,
|
|
778
|
+
() => ({
|
|
779
|
+
modelLabel: modelLabelFor(
|
|
780
|
+
state,
|
|
781
|
+
currentConfig.components.editor.modelLabel,
|
|
782
|
+
),
|
|
783
|
+
modelId: state.modelId,
|
|
784
|
+
modelName: state.modelName,
|
|
785
|
+
providerLabel: state.providerLabel,
|
|
786
|
+
sessionName: ctx.sessionManager.getSessionName() ?? "",
|
|
787
|
+
}),
|
|
788
|
+
getThinkingLevel,
|
|
789
|
+
() => ({
|
|
790
|
+
cwd: ctx.cwd,
|
|
791
|
+
projectRoot: minimalistProjectRoot,
|
|
792
|
+
branch: state.branch,
|
|
793
|
+
dirty: state.dirty,
|
|
794
|
+
ahead: state.ahead,
|
|
795
|
+
behind: state.behind,
|
|
796
|
+
costLabel: state.costLabel,
|
|
797
|
+
modelLabel: modelLabelFor(
|
|
798
|
+
state,
|
|
799
|
+
currentConfig.components.editor.modelLabel,
|
|
800
|
+
),
|
|
801
|
+
thinkingLevel: getThinkingLevel(),
|
|
802
|
+
contextPercent: getContextPercent(ctx),
|
|
803
|
+
contextWindow: getContextWindow(ctx),
|
|
804
|
+
sessionName: ctx.sessionManager.getSessionName() ?? "",
|
|
805
|
+
agentDurationMs: getAgentDurationMs(),
|
|
806
|
+
agentActive: agentRunActive,
|
|
807
|
+
}),
|
|
808
|
+
setMinimalistDecorationActive,
|
|
809
|
+
),
|
|
810
|
+
);
|
|
811
|
+
}) as ZentuiEditorFactory;
|
|
812
|
+
factory[ZENTUI_EDITOR_FACTORY] = true;
|
|
813
|
+
factory[ZENTUI_EDITOR_OWNER] = editorOwnerToken;
|
|
814
|
+
return factory;
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
const makeWrappedEditorFactory = (
|
|
818
|
+
ctx: ExtensionContext,
|
|
819
|
+
baseFactory: EditorFactory,
|
|
820
|
+
): ZentuiEditorFactory => {
|
|
821
|
+
const sessionTheme = ctx.ui.theme;
|
|
822
|
+
const factory = ((
|
|
823
|
+
tui: TUI,
|
|
824
|
+
theme: EditorTheme,
|
|
825
|
+
keybindings: KeybindingsManager,
|
|
826
|
+
) => {
|
|
827
|
+
requestEditorRender = () => tui.requestRender();
|
|
828
|
+
return markOwnedAccentRailEditor(
|
|
829
|
+
new WrappedPolishedEditor(
|
|
830
|
+
baseFactory(tui, theme, keybindings),
|
|
831
|
+
sessionTheme,
|
|
832
|
+
getCurrentConfig,
|
|
833
|
+
() => ({
|
|
834
|
+
modelLabel: modelLabelFor(
|
|
835
|
+
state,
|
|
836
|
+
currentConfig.components.editor.modelLabel,
|
|
837
|
+
),
|
|
838
|
+
modelId: state.modelId,
|
|
839
|
+
modelName: state.modelName,
|
|
840
|
+
providerLabel: state.providerLabel,
|
|
841
|
+
sessionName: ctx.sessionManager.getSessionName() ?? "",
|
|
842
|
+
}),
|
|
843
|
+
getThinkingLevel,
|
|
844
|
+
() => ({
|
|
845
|
+
cwd: ctx.cwd,
|
|
846
|
+
projectRoot: minimalistProjectRoot,
|
|
847
|
+
branch: state.branch,
|
|
848
|
+
dirty: state.dirty,
|
|
849
|
+
ahead: state.ahead,
|
|
850
|
+
behind: state.behind,
|
|
851
|
+
costLabel: state.costLabel,
|
|
852
|
+
modelLabel: modelLabelFor(
|
|
853
|
+
state,
|
|
854
|
+
currentConfig.components.editor.modelLabel,
|
|
855
|
+
),
|
|
856
|
+
thinkingLevel: getThinkingLevel(),
|
|
857
|
+
contextPercent: getContextPercent(ctx),
|
|
858
|
+
contextWindow: getContextWindow(ctx),
|
|
859
|
+
sessionName: ctx.sessionManager.getSessionName() ?? "",
|
|
860
|
+
agentDurationMs: getAgentDurationMs(),
|
|
861
|
+
agentActive: agentRunActive,
|
|
862
|
+
}),
|
|
863
|
+
setMinimalistDecorationActive,
|
|
864
|
+
),
|
|
865
|
+
);
|
|
866
|
+
}) as ZentuiEditorFactory;
|
|
867
|
+
factory[ZENTUI_EDITOR_FACTORY] = true;
|
|
868
|
+
factory[ZENTUI_EDITOR_OWNER] = editorOwnerToken;
|
|
869
|
+
factory[ZENTUI_EDITOR_BASE_FACTORY] = baseFactory;
|
|
870
|
+
return factory;
|
|
871
|
+
};
|
|
872
|
+
|
|
873
|
+
const replaceEditor = (
|
|
874
|
+
ctx: ExtensionContext,
|
|
875
|
+
factory: EditorFactory | undefined,
|
|
876
|
+
): EditorChangeResult => {
|
|
877
|
+
const result = replaceEditorComponentWithExpandedText(ctx.ui, factory);
|
|
878
|
+
return result.ok
|
|
879
|
+
? result
|
|
880
|
+
: { ok: false, reason: editorTransferFailureMessage(result.reason) };
|
|
881
|
+
};
|
|
882
|
+
|
|
883
|
+
const installEditor = (ctx: ExtensionContext): EditorChangeResult => {
|
|
884
|
+
const currentFactory = ctx.ui.getEditorComponent();
|
|
885
|
+
if (currentFactory && currentFactory === installedEditorFactory) {
|
|
886
|
+
editorInstalled = true;
|
|
887
|
+
return { ok: true };
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
const currentZentuiBaseFactory = getZentuiEditorBaseFactory(currentFactory);
|
|
891
|
+
const baseFactory =
|
|
892
|
+
currentZentuiBaseFactory ??
|
|
893
|
+
(currentFactory && !isZentuiEditorFactory(currentFactory)
|
|
894
|
+
? currentFactory
|
|
895
|
+
: undefined);
|
|
896
|
+
const nextFactory = baseFactory
|
|
897
|
+
? makeWrappedEditorFactory(ctx, baseFactory)
|
|
898
|
+
: makeEditorFactory(ctx);
|
|
899
|
+
const replacement = replaceEditor(ctx, nextFactory);
|
|
900
|
+
if (!replacement.ok) return replacement;
|
|
901
|
+
|
|
902
|
+
trackZentuiEditorFactory(nextFactory);
|
|
903
|
+
return { ok: true };
|
|
904
|
+
};
|
|
905
|
+
|
|
906
|
+
const uninstallEditor = (
|
|
907
|
+
ctx: ExtensionContext,
|
|
908
|
+
options: { allowStaleZentui?: boolean } = {},
|
|
909
|
+
): EditorChangeResult => {
|
|
910
|
+
const observed = observeEditorFactory(ctx);
|
|
911
|
+
if (!observed.known) {
|
|
912
|
+
return {
|
|
913
|
+
ok: false,
|
|
914
|
+
reason:
|
|
915
|
+
"the current editor factory could not be observed safely; reload Pi to apply this change",
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
const currentFactory = observed.factory;
|
|
919
|
+
if (!currentFactory || !isZentuiEditorFactory(currentFactory)) {
|
|
920
|
+
clearEditorOwnership();
|
|
921
|
+
return { ok: true };
|
|
922
|
+
}
|
|
923
|
+
if (!isOwnedEditorFactory(currentFactory) && !options.allowStaleZentui) {
|
|
924
|
+
clearEditorOwnership();
|
|
925
|
+
return { ok: true };
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
const replacement = replaceEditor(
|
|
929
|
+
ctx,
|
|
930
|
+
getZentuiEditorBaseFactory(currentFactory) ??
|
|
931
|
+
(editorInstallMode === "wrapper" && wrappedEditorFactory
|
|
932
|
+
? wrappedEditorFactory
|
|
933
|
+
: undefined),
|
|
934
|
+
);
|
|
935
|
+
if (!replacement.ok) return replacement;
|
|
936
|
+
|
|
937
|
+
clearEditorOwnership();
|
|
938
|
+
return { ok: true };
|
|
939
|
+
};
|
|
940
|
+
|
|
941
|
+
const ctxFooterOwner = (ctx: ExtensionContext): unknown =>
|
|
942
|
+
(ctx.ui as unknown as Record<PropertyKey, unknown>)[ZENTUI_FOOTER_OWNER];
|
|
943
|
+
|
|
944
|
+
const setStatusLineOwnership = (
|
|
945
|
+
ctx: ExtensionContext,
|
|
946
|
+
token: symbol | undefined,
|
|
947
|
+
) => {
|
|
948
|
+
const ui = ctx.ui as unknown as Record<PropertyKey, unknown>;
|
|
949
|
+
try {
|
|
950
|
+
if (token) ui[ZENTUI_FOOTER_OWNER] = token;
|
|
951
|
+
else delete ui[ZENTUI_FOOTER_OWNER];
|
|
952
|
+
} catch {
|
|
953
|
+
// Failure to mark ownership intentionally prevents Native from restoring it.
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
const ownsStatusLine = (ctx: ExtensionContext) =>
|
|
958
|
+
installedFooterToken !== undefined &&
|
|
959
|
+
ctxFooterOwner(ctx) === installedFooterToken;
|
|
960
|
+
|
|
961
|
+
const clearFooterOwnership = (ctx: ExtensionContext, token: symbol) => {
|
|
962
|
+
if (installedFooterToken !== token) return;
|
|
963
|
+
installedFooterKind = undefined;
|
|
964
|
+
installedFooterToken = undefined;
|
|
965
|
+
if (ctxFooterOwner(ctx) === token) setStatusLineOwnership(ctx, undefined);
|
|
966
|
+
requestFooterRender = undefined;
|
|
967
|
+
getActiveExtensionStatuses = () => new Map();
|
|
968
|
+
stopSessionTimer();
|
|
969
|
+
if (sessionLifecycle.isCurrent()) reconcileProjectRefresh(ctx, true);
|
|
970
|
+
};
|
|
971
|
+
|
|
972
|
+
type FooterBookkeepingSnapshot = {
|
|
973
|
+
token: symbol | undefined;
|
|
974
|
+
requestRender: (() => void) | undefined;
|
|
975
|
+
getExtensionStatuses: () => ReadonlyMap<string, string>;
|
|
976
|
+
};
|
|
977
|
+
|
|
978
|
+
const snapshotFooterBookkeeping = (
|
|
979
|
+
ctx: ExtensionContext,
|
|
980
|
+
): FooterBookkeepingSnapshot => ({
|
|
981
|
+
token: ownsStatusLine(ctx) ? installedFooterToken : undefined,
|
|
982
|
+
requestRender: requestFooterRender,
|
|
983
|
+
getExtensionStatuses: getActiveExtensionStatuses,
|
|
984
|
+
});
|
|
985
|
+
|
|
986
|
+
const resetFailedFooterInstallation = (
|
|
987
|
+
ctx: ExtensionContext,
|
|
988
|
+
token: symbol,
|
|
989
|
+
previous: FooterBookkeepingSnapshot,
|
|
990
|
+
) => {
|
|
991
|
+
// Pi has no transactional Footer replacement API. If a live replacement
|
|
992
|
+
// fails while retaining our predecessor, preserve that Footer's callbacks
|
|
993
|
+
// and timer. Otherwise clear only local bookkeeping; never issue a
|
|
994
|
+
// destructive setFooter(undefined) rollback.
|
|
995
|
+
if (
|
|
996
|
+
previous.token !== undefined &&
|
|
997
|
+
installedFooterToken === previous.token &&
|
|
998
|
+
ctxFooterOwner(ctx) === previous.token
|
|
999
|
+
) {
|
|
1000
|
+
requestFooterRender = previous.requestRender;
|
|
1001
|
+
getActiveExtensionStatuses = previous.getExtensionStatuses;
|
|
1002
|
+
return;
|
|
1003
|
+
}
|
|
1004
|
+
clearFooterOwnership(ctx, token);
|
|
1005
|
+
requestFooterRender = undefined;
|
|
1006
|
+
getActiveExtensionStatuses = () => new Map();
|
|
1007
|
+
stopSessionTimer();
|
|
1008
|
+
};
|
|
1009
|
+
|
|
1010
|
+
const installStatusLine = (ctx: ExtensionContext) => {
|
|
1011
|
+
if (installedFooterKind === "starship" && ownsStatusLine(ctx)) return;
|
|
1012
|
+
const token = Symbol("zentui-starship-footer");
|
|
1013
|
+
const previous = snapshotFooterBookkeeping(ctx);
|
|
1014
|
+
try {
|
|
1015
|
+
installFooter(ctx, state, getCurrentConfig, {
|
|
1016
|
+
setRequestRender: (fn) => {
|
|
1017
|
+
requestFooterRender = fn;
|
|
1018
|
+
},
|
|
1019
|
+
scheduleProjectRefresh,
|
|
1020
|
+
setExtensionStatusesGetter(fn) {
|
|
1021
|
+
getActiveExtensionStatuses = fn ?? (() => new Map());
|
|
1022
|
+
},
|
|
1023
|
+
getLiveContext: () => liveContext.get(),
|
|
1024
|
+
onDispose: () => clearFooterOwnership(ctx, token),
|
|
1025
|
+
});
|
|
1026
|
+
installedFooterKind = "starship";
|
|
1027
|
+
installedFooterToken = token;
|
|
1028
|
+
setStatusLineOwnership(ctx, token);
|
|
1029
|
+
refresh();
|
|
1030
|
+
reconcileSessionTimer();
|
|
1031
|
+
} catch {
|
|
1032
|
+
resetFailedFooterInstallation(ctx, token, previous);
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
|
|
1036
|
+
const installHiddenStatusLine = (ctx: ExtensionContext) => {
|
|
1037
|
+
if (installedFooterKind === "hidden" && ownsStatusLine(ctx)) return;
|
|
1038
|
+
const token = Symbol("zentui-hidden-footer");
|
|
1039
|
+
const previous = snapshotFooterBookkeeping(ctx);
|
|
1040
|
+
try {
|
|
1041
|
+
installHiddenFooter(ctx, () => clearFooterOwnership(ctx, token));
|
|
1042
|
+
installedFooterKind = "hidden";
|
|
1043
|
+
installedFooterToken = token;
|
|
1044
|
+
setStatusLineOwnership(ctx, token);
|
|
1045
|
+
requestFooterRender = undefined;
|
|
1046
|
+
getActiveExtensionStatuses = () => new Map();
|
|
1047
|
+
stopSessionTimer();
|
|
1048
|
+
} catch {
|
|
1049
|
+
resetFailedFooterInstallation(ctx, token, previous);
|
|
1050
|
+
}
|
|
1051
|
+
};
|
|
1052
|
+
|
|
1053
|
+
const uninstallStatusLine = (
|
|
1054
|
+
ctx: ExtensionContext,
|
|
1055
|
+
options: { forceLocalCleanup?: boolean } = {},
|
|
1056
|
+
) => {
|
|
1057
|
+
const ownedToken = ownsStatusLine(ctx) ? installedFooterToken : undefined;
|
|
1058
|
+
if (!ownedToken) return;
|
|
1059
|
+
try {
|
|
1060
|
+
ctx.ui.setFooter(undefined);
|
|
1061
|
+
} catch {
|
|
1062
|
+
// A live transition must preserve an owned Footer that Pi retained.
|
|
1063
|
+
// Shutdown cannot keep local callbacks alive, so it clears bookkeeping.
|
|
1064
|
+
if (!options.forceLocalCleanup) return;
|
|
1065
|
+
}
|
|
1066
|
+
clearFooterOwnership(ctx, ownedToken);
|
|
1067
|
+
};
|
|
1068
|
+
|
|
1069
|
+
const reconcileFooter = (ctx: ExtensionContext) => {
|
|
1070
|
+
switch (effectiveFooterStyle()) {
|
|
1071
|
+
case "starship":
|
|
1072
|
+
installStatusLine(ctx);
|
|
1073
|
+
break;
|
|
1074
|
+
case "hidden":
|
|
1075
|
+
installHiddenStatusLine(ctx);
|
|
1076
|
+
break;
|
|
1077
|
+
case "native":
|
|
1078
|
+
uninstallStatusLine(ctx);
|
|
1079
|
+
break;
|
|
1080
|
+
}
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
const reconcileEditor = (
|
|
1084
|
+
ctx: ExtensionContext,
|
|
1085
|
+
options: { allowStaleZentui?: boolean } = {},
|
|
1086
|
+
): EditorChangeResult | undefined => {
|
|
1087
|
+
try {
|
|
1088
|
+
if (effectiveEditorEnabled()) {
|
|
1089
|
+
const currentFactory = ctx.ui.getEditorComponent();
|
|
1090
|
+
if (
|
|
1091
|
+
isZentuiEditorFactory(currentFactory) &&
|
|
1092
|
+
!isOwnedEditorFactory(currentFactory) &&
|
|
1093
|
+
!options.allowStaleZentui
|
|
1094
|
+
) {
|
|
1095
|
+
clearEditorOwnership();
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
const editorMissingOrReplaced =
|
|
1099
|
+
!editorInstalled || !isOwnedEditorFactory(currentFactory);
|
|
1100
|
+
if (editorMissingOrReplaced) return installEditor(ctx);
|
|
1101
|
+
} else {
|
|
1102
|
+
const currentFactory = ctx.ui.getEditorComponent();
|
|
1103
|
+
if (editorInstalled || isOwnedEditorFactory(currentFactory))
|
|
1104
|
+
return uninstallEditor(ctx);
|
|
1105
|
+
}
|
|
1106
|
+
} catch {
|
|
1107
|
+
return {
|
|
1108
|
+
ok: false,
|
|
1109
|
+
reason:
|
|
1110
|
+
"the editor could not be reconciled safely; reload Pi to apply this change",
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
};
|
|
1114
|
+
|
|
1115
|
+
const applyConfiguredUi = (
|
|
1116
|
+
ctx: ExtensionContext,
|
|
1117
|
+
options: { allowStaleZentui?: boolean } = {},
|
|
1118
|
+
): ApplyUiResult => {
|
|
1119
|
+
const result: ApplyUiResult = { editorBlocked: false };
|
|
1120
|
+
if (!isTuiContext(ctx)) return result;
|
|
1121
|
+
activeTheme = ctx.ui.theme;
|
|
1122
|
+
|
|
1123
|
+
const editorChange = reconcileEditor(ctx, options);
|
|
1124
|
+
if (editorChange && !editorChange.ok) {
|
|
1125
|
+
result.editorBlocked = true;
|
|
1126
|
+
result.editorReason = editorChange.reason;
|
|
1127
|
+
}
|
|
1128
|
+
reconcileUserMessages();
|
|
1129
|
+
reconcileSelectorBorders();
|
|
1130
|
+
reconcileFooter(ctx);
|
|
1131
|
+
reconcileProjectRefresh(ctx);
|
|
1132
|
+
reconcileSessionTimer();
|
|
1133
|
+
reconcileAgentTimer();
|
|
1134
|
+
return result;
|
|
1135
|
+
};
|
|
1136
|
+
|
|
1137
|
+
const installUi = (ctx: ExtensionContext) => {
|
|
1138
|
+
if (!isTuiContext(ctx)) return;
|
|
1139
|
+
activeTuiContext = ctx;
|
|
1140
|
+
activeTheme = ctx.ui.theme;
|
|
1141
|
+
const staleFooterOwner = ctxFooterOwner(ctx);
|
|
1142
|
+
if (typeof staleFooterOwner === "symbol")
|
|
1143
|
+
installedFooterToken = staleFooterOwner;
|
|
1144
|
+
ensureConfigExists();
|
|
1145
|
+
currentConfig = loadConfig();
|
|
1146
|
+
syncFooterState(ctx);
|
|
1147
|
+
stopProjectRefresh();
|
|
1148
|
+
|
|
1149
|
+
uninstallUserMessages();
|
|
1150
|
+
uninstallSelectorBorders();
|
|
1151
|
+
try {
|
|
1152
|
+
removeUserMessageStyle();
|
|
1153
|
+
} catch {
|
|
1154
|
+
// Startup alone may supersede a stale registration from an earlier reload.
|
|
1155
|
+
}
|
|
1156
|
+
try {
|
|
1157
|
+
removeSelectorBorderStyle();
|
|
1158
|
+
} catch {
|
|
1159
|
+
// Startup alone may supersede a stale registration from an earlier reload.
|
|
1160
|
+
}
|
|
1161
|
+
uninstallStatusLine(ctx);
|
|
1162
|
+
if (effectiveEditorEnabled()) clearEditorOwnership();
|
|
1163
|
+
else {
|
|
1164
|
+
try {
|
|
1165
|
+
uninstallEditor(ctx, { allowStaleZentui: true });
|
|
1166
|
+
} catch {
|
|
1167
|
+
// Reconciliation below retries observable stale ownership.
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
applyConfiguredUi(ctx, { allowStaleZentui: true });
|
|
1172
|
+
refresh();
|
|
1173
|
+
};
|
|
1174
|
+
|
|
1175
|
+
const scheduleEditorReconciliation = (ctx: ExtensionContext) => {
|
|
1176
|
+
sessionLifecycle.defer(() => {
|
|
1177
|
+
if (!isTuiContext(ctx) || !effectiveEditorEnabled()) return;
|
|
1178
|
+
const observed = observeEditorFactory(ctx);
|
|
1179
|
+
if (!observed.known) return;
|
|
1180
|
+
if (observed.factory === installedEditorFactory) {
|
|
1181
|
+
reconcileProjectRefresh(ctx);
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
if (!observed.factory || !isOwnedEditorFactory(observed.factory)) {
|
|
1185
|
+
clearEditorOwnership();
|
|
1186
|
+
reconcileProjectRefresh(ctx);
|
|
1187
|
+
refresh();
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
trackZentuiEditorFactory(observed.factory);
|
|
1191
|
+
reconcileProjectRefresh(ctx);
|
|
1192
|
+
refresh();
|
|
1193
|
+
});
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
const cleanupUi = (ctx?: ExtensionContext) => {
|
|
1197
|
+
if (!ctx || !sessionLifecycle.isCurrent()) return;
|
|
1198
|
+
sessionLifecycle.shutdown();
|
|
1199
|
+
stopSessionTimer();
|
|
1200
|
+
resetAgentTimer();
|
|
1201
|
+
stopProjectRefresh();
|
|
1202
|
+
|
|
1203
|
+
let retainedEditorOwnership = false;
|
|
1204
|
+
if (isTuiContext(ctx)) {
|
|
1205
|
+
uninstallStatusLine(ctx, { forceLocalCleanup: true });
|
|
1206
|
+
try {
|
|
1207
|
+
const before = observeEditorFactory(ctx);
|
|
1208
|
+
if (before.known) {
|
|
1209
|
+
const currentFactory = before.factory;
|
|
1210
|
+
if (currentFactory && isOwnedEditorFactory(currentFactory)) {
|
|
1211
|
+
replaceEditor(
|
|
1212
|
+
ctx,
|
|
1213
|
+
getZentuiEditorBaseFactory(currentFactory) ??
|
|
1214
|
+
(editorInstallMode === "wrapper" && wrappedEditorFactory
|
|
1215
|
+
? wrappedEditorFactory
|
|
1216
|
+
: undefined),
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
const after = observeEditorFactory(ctx);
|
|
1221
|
+
if (
|
|
1222
|
+
after.known &&
|
|
1223
|
+
after.factory &&
|
|
1224
|
+
isOwnedEditorFactory(after.factory)
|
|
1225
|
+
) {
|
|
1226
|
+
trackZentuiEditorFactory(after.factory);
|
|
1227
|
+
retainedEditorOwnership = true;
|
|
1228
|
+
}
|
|
1229
|
+
} catch {
|
|
1230
|
+
// Continue cleaning independent surfaces.
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
if (!retainedEditorOwnership) clearEditorOwnership();
|
|
1234
|
+
accentRailLayoutPatchInstallSerial += 1;
|
|
1235
|
+
cleanupAccentRailLayoutPatch();
|
|
1236
|
+
cleanupAccentRailLayoutPatch = () => {};
|
|
1237
|
+
uninstallUserMessages();
|
|
1238
|
+
uninstallSelectorBorders();
|
|
1239
|
+
installedFooterKind = undefined;
|
|
1240
|
+
installedFooterToken = undefined;
|
|
1241
|
+
requestFooterRender = undefined;
|
|
1242
|
+
requestEditorRender = undefined;
|
|
1243
|
+
getActiveExtensionStatuses = () => new Map();
|
|
1244
|
+
activeTheme = undefined;
|
|
1245
|
+
activeTuiContext = undefined;
|
|
1246
|
+
};
|
|
1247
|
+
|
|
1248
|
+
const syncInteractiveState = (_event: unknown, ctx: ExtensionContext) => {
|
|
1249
|
+
refreshInteractiveState(ctx);
|
|
1250
|
+
};
|
|
1251
|
+
const syncInteractiveAndProjectState = (
|
|
1252
|
+
_event: unknown,
|
|
1253
|
+
ctx: ExtensionContext,
|
|
1254
|
+
) => {
|
|
1255
|
+
refreshInteractiveState(ctx, true);
|
|
1256
|
+
};
|
|
1257
|
+
|
|
1258
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
1259
|
+
const lifecycleGeneration = sessionLifecycle.start();
|
|
1260
|
+
const layoutInstallSerial = ++accentRailLayoutPatchInstallSerial;
|
|
1261
|
+
cleanupAccentRailLayoutPatch();
|
|
1262
|
+
cleanupAccentRailLayoutPatch = () => {};
|
|
1263
|
+
if (isTuiContext(ctx)) {
|
|
1264
|
+
const layoutPatchRetention =
|
|
1265
|
+
await retainAccentRailLayoutPatchInstallation(
|
|
1266
|
+
() => installHostAccentRailLayoutPatch(editorOwnerToken),
|
|
1267
|
+
() =>
|
|
1268
|
+
sessionLifecycle.isCurrent(lifecycleGeneration) &&
|
|
1269
|
+
layoutInstallSerial === accentRailLayoutPatchInstallSerial,
|
|
1270
|
+
(layoutPatch) => {
|
|
1271
|
+
cleanupAccentRailLayoutPatch = layoutPatch.cleanup;
|
|
1272
|
+
recordAccentRailLayoutPatchDiagnostic(
|
|
1273
|
+
layoutPatch.diagnostic,
|
|
1274
|
+
layoutPatch.version,
|
|
1275
|
+
);
|
|
1276
|
+
},
|
|
1277
|
+
);
|
|
1278
|
+
if (layoutPatchRetention === "stale") return;
|
|
1279
|
+
if (layoutPatchRetention === "failed") {
|
|
1280
|
+
recordAccentRailLayoutPatchDiagnostic("host-module-unavailable");
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
if (!sessionLifecycle.isCurrent(lifecycleGeneration)) return;
|
|
1284
|
+
liveContext.clear();
|
|
1285
|
+
interactionMetrics.shutdown();
|
|
1286
|
+
state.sessionStartEpoch = Date.now();
|
|
1287
|
+
invalidateUsageTotalsCache();
|
|
1288
|
+
resetAgentTimer();
|
|
1289
|
+
lastProjectCwd = undefined;
|
|
1290
|
+
requestedProjectCwd = undefined;
|
|
1291
|
+
minimalistProjectRoot = undefined;
|
|
1292
|
+
installUi(ctx);
|
|
1293
|
+
workingLine.startSession(ctx);
|
|
1294
|
+
scheduleEditorReconciliation(ctx);
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
const setEditorComponent = (
|
|
1298
|
+
patch: Partial<EditorComponentConfig>,
|
|
1299
|
+
ctx: ExtensionContext,
|
|
1300
|
+
) => {
|
|
1301
|
+
currentConfig = saveEditorComponentPatch(patch);
|
|
1302
|
+
let result: EditorChangeResult | undefined;
|
|
1303
|
+
if (patch.enabled !== undefined && isTuiContext(ctx))
|
|
1304
|
+
result = reconcileEditor(ctx);
|
|
1305
|
+
if (patch.style !== undefined && patch.style !== "minimalist") {
|
|
1306
|
+
setMinimalistDecorationActive(false);
|
|
1307
|
+
}
|
|
1308
|
+
if (patch.modelLabel !== undefined) syncFooterState(ctx);
|
|
1309
|
+
reconcileProjectRefresh(ctx);
|
|
1310
|
+
reconcileAgentTimer();
|
|
1311
|
+
refresh();
|
|
1312
|
+
return {
|
|
1313
|
+
applied: !result || result.ok,
|
|
1314
|
+
reason: result && !result.ok ? result.reason : undefined,
|
|
1315
|
+
};
|
|
1316
|
+
};
|
|
1317
|
+
const setUserMessagesComponent = (
|
|
1318
|
+
patch: Partial<UserMessagesComponentConfig>,
|
|
1319
|
+
_ctx: ExtensionContext,
|
|
1320
|
+
) => {
|
|
1321
|
+
currentConfig = saveUserMessagesComponentPatch(patch);
|
|
1322
|
+
if (patch.enabled !== undefined || patch.style !== undefined)
|
|
1323
|
+
reconcileUserMessages();
|
|
1324
|
+
refresh();
|
|
1325
|
+
};
|
|
1326
|
+
const setWorkingLineComponent = (
|
|
1327
|
+
patch: WorkingLineComponentPatch,
|
|
1328
|
+
ctx: ExtensionContext,
|
|
1329
|
+
) => {
|
|
1330
|
+
currentConfig = saveWorkingLineComponentPatch(patch);
|
|
1331
|
+
return workingLine.reconcile(ctx);
|
|
1332
|
+
};
|
|
1333
|
+
const setFooterComponent = (
|
|
1334
|
+
patch: Partial<FooterComponentConfig>,
|
|
1335
|
+
ctx: ExtensionContext,
|
|
1336
|
+
) => {
|
|
1337
|
+
const previousStyle = effectiveFooterStyle();
|
|
1338
|
+
currentConfig = saveFooterComponentPatch(patch);
|
|
1339
|
+
const styleChanged = effectiveFooterStyle() !== previousStyle;
|
|
1340
|
+
if (patch.style !== undefined) reconcileFooter(ctx);
|
|
1341
|
+
if (patch.modelLabel !== undefined) syncFooterState(ctx);
|
|
1342
|
+
reconcileProjectRefresh(ctx, styleChanged);
|
|
1343
|
+
reconcileSessionTimer();
|
|
1344
|
+
refresh();
|
|
1345
|
+
};
|
|
1346
|
+
options.onRuntimeController?.({
|
|
1347
|
+
setEditorComponent,
|
|
1348
|
+
setUserMessagesComponent,
|
|
1349
|
+
setWorkingLineComponent,
|
|
1350
|
+
setFooterComponent,
|
|
1351
|
+
});
|
|
1352
|
+
|
|
1353
|
+
if (options.registerCommand !== false)
|
|
1354
|
+
registerShellSettingsCommand(pi, {
|
|
1355
|
+
sessionLifecycle,
|
|
1356
|
+
getConfig: getCurrentConfig,
|
|
1357
|
+
setEditorComponent(
|
|
1358
|
+
patch: Partial<EditorComponentConfig>,
|
|
1359
|
+
ctx: ExtensionContext,
|
|
1360
|
+
) {
|
|
1361
|
+
currentConfig = saveEditorComponentPatch(patch);
|
|
1362
|
+
let result: EditorChangeResult | undefined;
|
|
1363
|
+
if (patch.enabled !== undefined && isTuiContext(ctx)) {
|
|
1364
|
+
result = reconcileEditor(ctx);
|
|
1365
|
+
}
|
|
1366
|
+
if (patch.style !== undefined && patch.style !== "minimalist") {
|
|
1367
|
+
setMinimalistDecorationActive(false);
|
|
1368
|
+
}
|
|
1369
|
+
if (patch.modelLabel !== undefined) syncFooterState(ctx);
|
|
1370
|
+
reconcileProjectRefresh(ctx);
|
|
1371
|
+
reconcileAgentTimer();
|
|
1372
|
+
refresh();
|
|
1373
|
+
return {
|
|
1374
|
+
applied: !result || result.ok,
|
|
1375
|
+
reason: result && !result.ok ? result.reason : undefined,
|
|
1376
|
+
};
|
|
1377
|
+
},
|
|
1378
|
+
setPolished(
|
|
1379
|
+
patch: Partial<PolishedEditorStyleConfig>,
|
|
1380
|
+
_ctx: ExtensionContext,
|
|
1381
|
+
) {
|
|
1382
|
+
currentConfig = savePolishedEditorStylePatch(patch);
|
|
1383
|
+
refresh();
|
|
1384
|
+
},
|
|
1385
|
+
setPolishedCopyFriendly(
|
|
1386
|
+
patch: Partial<PolishedCopyFriendlyEditorStyleConfig>,
|
|
1387
|
+
_ctx: ExtensionContext,
|
|
1388
|
+
) {
|
|
1389
|
+
currentConfig = savePolishedCopyFriendlyEditorStylePatch(patch);
|
|
1390
|
+
refresh();
|
|
1391
|
+
},
|
|
1392
|
+
setAccentRail(
|
|
1393
|
+
patch: Partial<AccentRailEditorStyleConfig>,
|
|
1394
|
+
_ctx: ExtensionContext,
|
|
1395
|
+
) {
|
|
1396
|
+
currentConfig = saveAccentRailEditorStylePatch(patch);
|
|
1397
|
+
refresh();
|
|
1398
|
+
},
|
|
1399
|
+
setMinimalist(patch: Partial<MinimalistConfig>, ctx: ExtensionContext) {
|
|
1400
|
+
currentConfig = saveMinimalistEditorStylePatch(patch);
|
|
1401
|
+
reconcileAgentTimer();
|
|
1402
|
+
reconcileProjectRefresh(
|
|
1403
|
+
ctx,
|
|
1404
|
+
patch.pathDisplay !== undefined || patch.showGit !== undefined,
|
|
1405
|
+
);
|
|
1406
|
+
refresh();
|
|
1407
|
+
},
|
|
1408
|
+
setUserMessagesComponent(
|
|
1409
|
+
patch: Partial<UserMessagesComponentConfig>,
|
|
1410
|
+
_ctx: ExtensionContext,
|
|
1411
|
+
) {
|
|
1412
|
+
currentConfig = saveUserMessagesComponentPatch(patch);
|
|
1413
|
+
if (patch.enabled !== undefined || patch.style !== undefined)
|
|
1414
|
+
reconcileUserMessages();
|
|
1415
|
+
refresh();
|
|
1416
|
+
},
|
|
1417
|
+
setWorkingLineComponent(
|
|
1418
|
+
patch: WorkingLineComponentPatch,
|
|
1419
|
+
ctx: ExtensionContext,
|
|
1420
|
+
) {
|
|
1421
|
+
currentConfig = saveWorkingLineComponentPatch(patch);
|
|
1422
|
+
return workingLine.reconcile(ctx);
|
|
1423
|
+
},
|
|
1424
|
+
setSelectorBordersComponent(
|
|
1425
|
+
patch: Partial<SelectorBordersComponentConfig>,
|
|
1426
|
+
_ctx: ExtensionContext,
|
|
1427
|
+
) {
|
|
1428
|
+
currentConfig = saveSelectorBordersComponentPatch(patch);
|
|
1429
|
+
if (patch.enabled !== undefined || patch.style !== undefined)
|
|
1430
|
+
reconcileSelectorBorders();
|
|
1431
|
+
refresh();
|
|
1432
|
+
},
|
|
1433
|
+
setFooterComponent(
|
|
1434
|
+
patch: Partial<FooterComponentConfig>,
|
|
1435
|
+
ctx: ExtensionContext,
|
|
1436
|
+
) {
|
|
1437
|
+
const previousStyle = effectiveFooterStyle();
|
|
1438
|
+
currentConfig = saveFooterComponentPatch(patch);
|
|
1439
|
+
const styleChanged = effectiveFooterStyle() !== previousStyle;
|
|
1440
|
+
if (patch.style !== undefined) reconcileFooter(ctx);
|
|
1441
|
+
if (patch.modelLabel !== undefined) syncFooterState(ctx);
|
|
1442
|
+
reconcileProjectRefresh(ctx, styleChanged);
|
|
1443
|
+
reconcileSessionTimer();
|
|
1444
|
+
refresh();
|
|
1445
|
+
},
|
|
1446
|
+
setFooterSegments(
|
|
1447
|
+
patch: Partial<FooterSegmentsConfig>,
|
|
1448
|
+
ctx: ExtensionContext,
|
|
1449
|
+
) {
|
|
1450
|
+
applyFooterDependencyConfigChange(ctx, () =>
|
|
1451
|
+
saveStarshipFooterStylePatch({
|
|
1452
|
+
segments: patch as FooterSegmentsConfig,
|
|
1453
|
+
}),
|
|
1454
|
+
);
|
|
1455
|
+
},
|
|
1456
|
+
setFooterFormat(value: string, ctx: ExtensionContext) {
|
|
1457
|
+
applyFooterDependencyConfigChange(ctx, () =>
|
|
1458
|
+
saveStarshipFooterStylePatch({ format: value }),
|
|
1459
|
+
);
|
|
1460
|
+
},
|
|
1461
|
+
setResponsiveFooter(
|
|
1462
|
+
patch: Partial<
|
|
1463
|
+
Pick<PolishedTuiConfig, "responsiveFooter" | "compactFooterMaxLines">
|
|
1464
|
+
>,
|
|
1465
|
+
ctx: ExtensionContext,
|
|
1466
|
+
) {
|
|
1467
|
+
applyFooterDependencyConfigChange(ctx, () =>
|
|
1468
|
+
saveStarshipFooterStylePatch({
|
|
1469
|
+
...(patch.responsiveFooter === undefined
|
|
1470
|
+
? {}
|
|
1471
|
+
: { responsive: patch.responsiveFooter }),
|
|
1472
|
+
...(patch.compactFooterMaxLines === undefined
|
|
1473
|
+
? {}
|
|
1474
|
+
: { compactMaxLines: patch.compactFooterMaxLines }),
|
|
1475
|
+
}),
|
|
1476
|
+
);
|
|
1477
|
+
},
|
|
1478
|
+
setIconMode(mode: IconMode) {
|
|
1479
|
+
currentConfig = saveIconsModePatch(mode);
|
|
1480
|
+
},
|
|
1481
|
+
setContextStyle(style: ContextStyle) {
|
|
1482
|
+
currentConfig = saveStarshipFooterStylePatch({ contextStyle: style });
|
|
1483
|
+
},
|
|
1484
|
+
setSeparator(separator: SeparatorStyle) {
|
|
1485
|
+
currentConfig = saveStarshipFooterStylePatch({ separator });
|
|
1486
|
+
},
|
|
1487
|
+
setPathDisplay(patch: Partial<PathDisplayConfig>) {
|
|
1488
|
+
currentConfig = saveStarshipFooterStylePatch({
|
|
1489
|
+
pathDisplay: patch as PathDisplayConfig,
|
|
1490
|
+
});
|
|
1491
|
+
},
|
|
1492
|
+
setGitBranch(patch: Partial<GitBranchConfig>) {
|
|
1493
|
+
currentConfig = saveStarshipFooterStylePatch({
|
|
1494
|
+
gitBranch: patch as GitBranchConfig,
|
|
1495
|
+
});
|
|
1496
|
+
},
|
|
1497
|
+
setGitCommit(
|
|
1498
|
+
patch: Partial<Pick<GitCommitConfig, "onlyDetached" | "showTag">>,
|
|
1499
|
+
ctx: ExtensionContext,
|
|
1500
|
+
) {
|
|
1501
|
+
currentConfig = saveStarshipFooterStylePatch({
|
|
1502
|
+
gitCommit: patch as GitCommitConfig,
|
|
1503
|
+
});
|
|
1504
|
+
if (patch.showTag !== undefined) reconcileProjectRefresh(ctx, true);
|
|
1505
|
+
},
|
|
1506
|
+
setGitMetrics(patch: Partial<GitMetricsConfig>, ctx: ExtensionContext) {
|
|
1507
|
+
currentConfig = saveStarshipFooterStylePatch({
|
|
1508
|
+
gitMetrics: patch as GitMetricsConfig,
|
|
1509
|
+
});
|
|
1510
|
+
if (patch.ignoreSubmodules !== undefined)
|
|
1511
|
+
reconcileProjectRefresh(ctx, true);
|
|
1512
|
+
},
|
|
1513
|
+
getActiveExtensionStatuses() {
|
|
1514
|
+
return getActiveExtensionStatuses();
|
|
1515
|
+
},
|
|
1516
|
+
setExtensionStatusDefaultPlacement(placement: ExtensionStatusPlacement) {
|
|
1517
|
+
currentConfig = saveExtensionStatusDefaultPlacement(placement);
|
|
1518
|
+
},
|
|
1519
|
+
setExtensionStatusPlacement(
|
|
1520
|
+
key: string,
|
|
1521
|
+
placement: ExtensionStatusPlacement,
|
|
1522
|
+
) {
|
|
1523
|
+
currentConfig = saveExtensionStatusPlacement(key, placement);
|
|
1524
|
+
},
|
|
1525
|
+
setExtensionStatusColorMode(
|
|
1526
|
+
key: string,
|
|
1527
|
+
colorMode: ExtensionStatusColorMode,
|
|
1528
|
+
) {
|
|
1529
|
+
currentConfig = saveExtensionStatusColorMode(key, colorMode);
|
|
1530
|
+
},
|
|
1531
|
+
requestRender() {
|
|
1532
|
+
refresh();
|
|
1533
|
+
},
|
|
1534
|
+
});
|
|
1535
|
+
|
|
1536
|
+
pi.on("session_shutdown", async (_event, ctx) => {
|
|
1537
|
+
liveContext.clear();
|
|
1538
|
+
interactionMetrics.shutdown();
|
|
1539
|
+
workingLine.dispose(ctx);
|
|
1540
|
+
cleanupUi(ctx);
|
|
1541
|
+
});
|
|
1542
|
+
|
|
1543
|
+
const syncInteractiveAndProjectStateWithUsage = (
|
|
1544
|
+
_event: unknown,
|
|
1545
|
+
ctx: ExtensionContext,
|
|
1546
|
+
) => {
|
|
1547
|
+
invalidateUsageTotalsCache();
|
|
1548
|
+
refreshInteractiveState(ctx, true);
|
|
1549
|
+
};
|
|
1550
|
+
|
|
1551
|
+
pi.on("agent_start", (event, ctx) => {
|
|
1552
|
+
liveContext.clear();
|
|
1553
|
+
const { interactionStarted } = interactionMetrics.agentStart();
|
|
1554
|
+
startAgentTurn(interactionStarted);
|
|
1555
|
+
workingLine.startAgent(ctx);
|
|
1556
|
+
syncInteractiveState(event, ctx);
|
|
1557
|
+
});
|
|
1558
|
+
pi.on("turn_start", (_event, ctx) => {
|
|
1559
|
+
interactionMetrics.turnStart();
|
|
1560
|
+
workingLine.startTurn(ctx);
|
|
1561
|
+
});
|
|
1562
|
+
pi.on("agent_end", (event, ctx) => {
|
|
1563
|
+
liveContext.clear();
|
|
1564
|
+
const displayTokens = interactionMetrics.currentDisplayTokens();
|
|
1565
|
+
interactionMetrics.agentEnd();
|
|
1566
|
+
pauseAgentRun();
|
|
1567
|
+
workingLine.finishAgent(ctx);
|
|
1568
|
+
workingLine.updateMetrics(
|
|
1569
|
+
displayTokens,
|
|
1570
|
+
interactionMetrics.currentThought(),
|
|
1571
|
+
ctx,
|
|
1572
|
+
);
|
|
1573
|
+
// Reconcile once more after Pi has persisted the assistant message.
|
|
1574
|
+
syncInteractiveAndProjectStateWithUsage(event, ctx);
|
|
1575
|
+
});
|
|
1576
|
+
pi.on("model_select", (event, ctx) => {
|
|
1577
|
+
liveContext.clear();
|
|
1578
|
+
syncInteractiveState(event, ctx);
|
|
1579
|
+
});
|
|
1580
|
+
pi.on("thinking_level_select", syncInteractiveState);
|
|
1581
|
+
pi.on("session_info_changed", syncInteractiveState);
|
|
1582
|
+
pi.on("message_update", (event, ctx) => {
|
|
1583
|
+
liveContext.update(event.message);
|
|
1584
|
+
const metrics = interactionMetrics.messageUpdate(
|
|
1585
|
+
event.message,
|
|
1586
|
+
"assistantMessageEvent" in event
|
|
1587
|
+
? event.assistantMessageEvent
|
|
1588
|
+
: undefined,
|
|
1589
|
+
);
|
|
1590
|
+
if (metrics.usageChanged || metrics.thoughtChanged) {
|
|
1591
|
+
workingLine.updateMetrics(
|
|
1592
|
+
metrics.displayTokens,
|
|
1593
|
+
interactionMetrics.currentThought(),
|
|
1594
|
+
ctx,
|
|
1595
|
+
);
|
|
1596
|
+
}
|
|
1597
|
+
});
|
|
1598
|
+
pi.on("message_end", (event, ctx) => {
|
|
1599
|
+
const result = interactionMetrics.messageEnd(event.message);
|
|
1600
|
+
if (result.status === "accepted") {
|
|
1601
|
+
workingLine.updateMetrics(
|
|
1602
|
+
result.displayTokens,
|
|
1603
|
+
interactionMetrics.currentThought(),
|
|
1604
|
+
ctx,
|
|
1605
|
+
);
|
|
1606
|
+
}
|
|
1607
|
+
// Pi notifies extensions before persisting a successful message, so retain its live
|
|
1608
|
+
// context until agent_end; accepted failed messages clear immediately instead of showing
|
|
1609
|
+
// stale usage. Rejected and duplicate finals are not authoritative.
|
|
1610
|
+
if (
|
|
1611
|
+
result.status === "accepted" &&
|
|
1612
|
+
event.message.role === "assistant" &&
|
|
1613
|
+
(event.message.stopReason === "error" ||
|
|
1614
|
+
event.message.stopReason === "aborted")
|
|
1615
|
+
) {
|
|
1616
|
+
liveContext.clear();
|
|
1617
|
+
}
|
|
1618
|
+
syncInteractiveAndProjectStateWithUsage(event, ctx);
|
|
1619
|
+
});
|
|
1620
|
+
pi.on("agent_settled", (_event, ctx) => {
|
|
1621
|
+
const settled = interactionMetrics.settle(ctx.isIdle());
|
|
1622
|
+
if (!settled) return;
|
|
1623
|
+
settleAgentTurn(settled.nextStartedAt);
|
|
1624
|
+
workingLine.settle(settled.nextTokens, settled.nextThought, ctx);
|
|
1625
|
+
const config = currentConfig.components.workingLine;
|
|
1626
|
+
if (config.enabled && config.turnSummary) {
|
|
1627
|
+
try {
|
|
1628
|
+
pi.appendEntry(TURN_SUMMARY_ENTRY_TYPE, {
|
|
1629
|
+
version: 3,
|
|
1630
|
+
...settled.summary,
|
|
1631
|
+
stylePrefix: snapshotWorkingLineHighStyle(
|
|
1632
|
+
ctx.ui.theme,
|
|
1633
|
+
config,
|
|
1634
|
+
currentConfig.colors,
|
|
1635
|
+
),
|
|
1636
|
+
});
|
|
1637
|
+
} catch {
|
|
1638
|
+
// A transcript persistence failure must not break settlement cleanup.
|
|
1639
|
+
}
|
|
1640
|
+
}
|
|
1641
|
+
});
|
|
1642
|
+
pi.on("tool_execution_start", (event, ctx) => {
|
|
1643
|
+
liveContext.clear();
|
|
1644
|
+
workingLine.startTool(event.toolCallId, event.toolName, ctx);
|
|
1645
|
+
syncInteractiveState(event, ctx);
|
|
1646
|
+
});
|
|
1647
|
+
pi.on("tool_execution_end", (event, ctx) => {
|
|
1648
|
+
workingLine.finishTool(event.toolCallId, ctx);
|
|
1649
|
+
syncInteractiveAndProjectState(event, ctx);
|
|
1650
|
+
});
|
|
1651
|
+
pi.on("session_compact", (event, ctx) => {
|
|
1652
|
+
liveContext.clear();
|
|
1653
|
+
syncInteractiveAndProjectStateWithUsage(event, ctx);
|
|
1654
|
+
});
|
|
1655
|
+
pi.on("session_tree", (event, ctx) => {
|
|
1656
|
+
liveContext.clear();
|
|
1657
|
+
syncInteractiveAndProjectStateWithUsage(event, ctx);
|
|
1658
|
+
});
|
|
1659
|
+
}
|