@aemonge-dev/pi-stardeck 0.0.0-stage → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +240 -2
- package/demo/pi-stardeck.tape +59 -0
- package/docs/architecture.md +106 -0
- package/docs/assets/pi-stardeck.gif +0 -0
- package/docs/development.md +128 -0
- package/licenses/README.md +19 -0
- package/licenses/pi-vim-LICENSE +21 -0
- package/package.json +81 -4
- package/packages/footer/README.md +15 -0
- package/packages/footer/index.ts +37 -0
- package/packages/footer/package.json +32 -0
- package/packages/footer/src/protocol.ts +10 -0
- package/packages/footer/src/quota.ts +26 -0
- package/packages/footer/src/telemetry.ts +59 -0
- package/packages/pi-stardeck/README.md +220 -0
- package/packages/pi-stardeck/index.ts +691 -0
- package/packages/pi-stardeck/package.json +32 -0
- package/packages/pi-stardeck/src/activity-age.ts +184 -0
- package/packages/pi-stardeck/src/capabilities.ts +105 -0
- package/packages/pi-stardeck/src/deck.ts +807 -0
- package/packages/pi-stardeck/src/gauge.ts +1323 -0
- package/packages/pi-stardeck/src/git-refresh.ts +5 -0
- package/packages/pi-stardeck/src/process-resources.ts +428 -0
- package/packages/pi-stardeck/src/title-state.ts +195 -0
- package/packages/pi-stardeck/src/title.ts +443 -0
- package/packages/pi-vim-top-border/LICENSE +21 -0
- package/packages/pi-vim-top-border/LOCAL-CHANGES.md +71 -0
- package/packages/pi-vim-top-border/README.md +577 -0
- package/packages/pi-vim-top-border/clipboard-mirror.ts +451 -0
- package/packages/pi-vim-top-border/clipboard-policy.ts +23 -0
- package/packages/pi-vim-top-border/cursor-shape.ts +131 -0
- package/packages/pi-vim-top-border/index.ts +4195 -0
- package/packages/pi-vim-top-border/input-keys.ts +49 -0
- package/packages/pi-vim-top-border/mode-change-command.ts +120 -0
- package/packages/pi-vim-top-border/mode-colors.ts +97 -0
- package/packages/pi-vim-top-border/mode-label.ts +62 -0
- package/packages/pi-vim-top-border/motions.ts +297 -0
- package/packages/pi-vim-top-border/package.json +47 -0
- package/packages/pi-vim-top-border/prompt-external-editor.ts +152 -0
- package/packages/pi-vim-top-border/prompt-rail.ts +334 -0
- package/packages/pi-vim-top-border/prompt-status.ts +131 -0
- package/packages/pi-vim-top-border/settings.ts +287 -0
- package/packages/pi-vim-top-border/text-objects.ts +389 -0
- package/packages/pi-vim-top-border/types.ts +51 -0
- package/packages/pi-vim-top-border/visual.ts +65 -0
- package/packages/pi-vim-top-border/word-boundary-cache.ts +196 -0
- package/packages/status/README.md +398 -0
- package/packages/status/index.ts +1717 -0
- package/packages/status/package.json +33 -0
- package/packages/status/src/active-work.ts +235 -0
- package/packages/status/src/config.ts +218 -0
- package/packages/status/src/diagnostics.ts +238 -0
- package/packages/status/src/goal-block.ts +377 -0
- package/packages/status/src/goal.ts +170 -0
- package/packages/status/src/ledger.ts +101 -0
- package/packages/status/src/openspec.ts +394 -0
- package/packages/status/src/orchestration.ts +237 -0
- package/packages/status/src/publisher.ts +1294 -0
- package/packages/status/src/reducer.ts +190 -0
- package/packages/status/src/runtime-runs.ts +267 -0
- package/packages/status/src/state.ts +178 -0
- package/packages/status/src/title-activity.ts +224 -0
- package/packages/status/src/title-state.ts +218 -0
- package/packages/status/src/turn-cost.ts +63 -0
- package/packages/status/src/types.ts +228 -0
- package/src/deck-surface.ts +46 -0
- package/src/health.ts +227 -0
- package/src/index.ts +27 -0
- package/src/pi-status.ts +334 -0
|
@@ -0,0 +1,691 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionAPI,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
Theme,
|
|
5
|
+
} from '@earendil-works/pi-coding-agent';
|
|
6
|
+
import { truncateToWidth } from '@earendil-works/pi-tui';
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
buildAgentFooterWidgets,
|
|
10
|
+
buildPromptStatusSnapshot,
|
|
11
|
+
COMPACTION_WIDGET_ID,
|
|
12
|
+
CONTEXT_WIDGET_ID,
|
|
13
|
+
countCompactions,
|
|
14
|
+
ENVIRONMENT_WIDGET_ID,
|
|
15
|
+
FANCY_FOOTER_READY_CHANNEL,
|
|
16
|
+
FOOTER_TELEMETRY_CHANNEL,
|
|
17
|
+
FANCY_FOOTER_WIDGET_CHANNEL,
|
|
18
|
+
formatFooterBranch,
|
|
19
|
+
formatFooterPath,
|
|
20
|
+
GALACTICA_HEADER_CHANNEL,
|
|
21
|
+
GIT_BRANCH_WIDGET_ID,
|
|
22
|
+
GIT_CONFLICT_WIDGET_ID,
|
|
23
|
+
GIT_SCOPE_SEPARATOR_WIDGET_ID,
|
|
24
|
+
GIT_MODIFIED_WIDGET_ID,
|
|
25
|
+
GIT_STAGED_WIDGET_ID,
|
|
26
|
+
GIT_UNTRACKED_WIDGET_ID,
|
|
27
|
+
type FooterTelemetrySnapshot,
|
|
28
|
+
type GitStatusSummary,
|
|
29
|
+
type HeaderSnapshot,
|
|
30
|
+
LEGACY_GIT_WIDGET_ID,
|
|
31
|
+
LOCATION_WIDGET_ID,
|
|
32
|
+
normalizeContextPercent,
|
|
33
|
+
parseFooterTelemetry,
|
|
34
|
+
parseGitBranch,
|
|
35
|
+
parseGitStatus,
|
|
36
|
+
parseHeaderSnapshot,
|
|
37
|
+
parsePiStatusSnapshot,
|
|
38
|
+
PI_NATIVE_STATUS_CHANNEL,
|
|
39
|
+
PI_STATUS_SNAPSHOT_CHANNEL,
|
|
40
|
+
PROMPT_STATUS_CHANNEL,
|
|
41
|
+
REASONING_WIDGET_ID,
|
|
42
|
+
SCOPE_SEPARATOR_WIDGET_ID,
|
|
43
|
+
runtimeHeaderWork,
|
|
44
|
+
parseVimMode,
|
|
45
|
+
VIM_MODE_CHANNEL,
|
|
46
|
+
type PiStatusSnapshot,
|
|
47
|
+
type VimMode,
|
|
48
|
+
} from './src/gauge.ts';
|
|
49
|
+
import {
|
|
50
|
+
buildRuntimeFooterWidgets,
|
|
51
|
+
formatRailChronometer,
|
|
52
|
+
RUNTIME_SCOPE_SEPARATOR_WIDGET_ID,
|
|
53
|
+
RUNTIME_STATUS_WIDGET_ID,
|
|
54
|
+
} from './src/activity-age.ts';
|
|
55
|
+
import {
|
|
56
|
+
buildCapabilityFooterWidget,
|
|
57
|
+
CAPABILITY_WIDGET_ID,
|
|
58
|
+
LSP_STATUS_EVENT,
|
|
59
|
+
MCP_STATUS_EVENT,
|
|
60
|
+
parseLspCapability,
|
|
61
|
+
parseMcpCapability,
|
|
62
|
+
type LspCapability,
|
|
63
|
+
type McpCapability,
|
|
64
|
+
} from './src/capabilities.ts';
|
|
65
|
+
import { shouldRefreshGitAfterTool } from './src/git-refresh.ts';
|
|
66
|
+
import {
|
|
67
|
+
buildResourceFooterWidgets,
|
|
68
|
+
buildResourceTelemetry,
|
|
69
|
+
calculateCpuPercent,
|
|
70
|
+
CPU_WIDGET_ID,
|
|
71
|
+
MEMORY_WIDGET_ID,
|
|
72
|
+
RESOURCE_REFRESH_MS,
|
|
73
|
+
sampleProcessTreeResources,
|
|
74
|
+
updateCpuSmoothing,
|
|
75
|
+
type CpuSmoothingState,
|
|
76
|
+
type ProcessTreeSample,
|
|
77
|
+
type ResourceTelemetry,
|
|
78
|
+
} from './src/process-resources.ts';
|
|
79
|
+
import { renderHeaderDeck } from './src/deck.ts';
|
|
80
|
+
import { layoutLifecycleTitle, renderLifecycleTitleSections } from './src/title.ts';
|
|
81
|
+
|
|
82
|
+
const WIDGET_KEY = 'galactica.work-identity';
|
|
83
|
+
const PI_STATUS_SOURCE_CHANNEL = 'pi-stardeck:status-source/v1';
|
|
84
|
+
const EMPTY_GIT: GitStatusSummary = {
|
|
85
|
+
staged: 0,
|
|
86
|
+
modified: 0,
|
|
87
|
+
untracked: 0,
|
|
88
|
+
conflicts: 0,
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
export interface ContextHeaderModeRail {
|
|
92
|
+
plain: string;
|
|
93
|
+
styled: string;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export interface ContextHeaderDeckSurface {
|
|
97
|
+
setRenderer(
|
|
98
|
+
renderer:
|
|
99
|
+
| ((width: number, theme: Theme, modeRail?: ContextHeaderModeRail) => string[])
|
|
100
|
+
| null,
|
|
101
|
+
): void;
|
|
102
|
+
requestRender(): void;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export interface ContextHeaderOptions {
|
|
106
|
+
surface?: 'legacy' | 'deck' | 'editor-deck';
|
|
107
|
+
deckSurface?: ContextHeaderDeckSurface;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function installEmptyDeckFooter(
|
|
111
|
+
ctx: ExtensionContext,
|
|
112
|
+
publishStatuses: (statuses: Array<{ key: string; text: string }>) => void = () => {},
|
|
113
|
+
publishAvailability: (available: boolean) => void = () => {},
|
|
114
|
+
): () => void {
|
|
115
|
+
ctx.ui.setFooter((_tui, _theme, footerData) => {
|
|
116
|
+
let lastSignature = '';
|
|
117
|
+
let lastAvailability: boolean | undefined;
|
|
118
|
+
return {
|
|
119
|
+
render(): string[] {
|
|
120
|
+
let statuses: Array<{ key: string; text: string }> = [];
|
|
121
|
+
try {
|
|
122
|
+
const getStatuses = (
|
|
123
|
+
footerData as {
|
|
124
|
+
getExtensionStatuses?: () => ReadonlyMap<string, string>;
|
|
125
|
+
}
|
|
126
|
+
).getExtensionStatuses;
|
|
127
|
+
if (typeof getStatuses !== 'function') throw new Error('unavailable');
|
|
128
|
+
statuses = [...getStatuses.call(footerData)]
|
|
129
|
+
.filter(
|
|
130
|
+
(entry): entry is [string, string] =>
|
|
131
|
+
typeof entry[0] === 'string' &&
|
|
132
|
+
typeof entry[1] === 'string' &&
|
|
133
|
+
entry[1].trim() !== '',
|
|
134
|
+
)
|
|
135
|
+
.map(([key, text]) => ({ key, text }))
|
|
136
|
+
.sort((left, right) => left.key.localeCompare(right.key));
|
|
137
|
+
if (lastAvailability !== true) publishAvailability(true);
|
|
138
|
+
lastAvailability = true;
|
|
139
|
+
} catch {
|
|
140
|
+
if (lastAvailability !== false) publishAvailability(false);
|
|
141
|
+
lastAvailability = false;
|
|
142
|
+
}
|
|
143
|
+
const signature = JSON.stringify(statuses);
|
|
144
|
+
if (signature !== lastSignature) {
|
|
145
|
+
lastSignature = signature;
|
|
146
|
+
publishStatuses(statuses);
|
|
147
|
+
}
|
|
148
|
+
return [];
|
|
149
|
+
},
|
|
150
|
+
invalidate() {},
|
|
151
|
+
};
|
|
152
|
+
});
|
|
153
|
+
return () => ctx.ui.setFooter(undefined);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export default function galacticaContextHeader(
|
|
157
|
+
pi: ExtensionAPI,
|
|
158
|
+
options: ContextHeaderOptions = {},
|
|
159
|
+
): void {
|
|
160
|
+
const deckMode = options.surface === 'deck' || options.surface === 'editor-deck';
|
|
161
|
+
const editorDeckSurface =
|
|
162
|
+
options.surface === 'editor-deck' ? options.deckSurface : undefined;
|
|
163
|
+
let clearWidget: (() => void) | undefined;
|
|
164
|
+
let clearFooter: (() => void) | undefined;
|
|
165
|
+
let requestRender: (() => void) | undefined;
|
|
166
|
+
let branch = '';
|
|
167
|
+
let gitAvailable = false;
|
|
168
|
+
let gitStatus: GitStatusSummary = { ...EMPTY_GIT };
|
|
169
|
+
let header: HeaderSnapshot | null = null;
|
|
170
|
+
let agentBusy = false;
|
|
171
|
+
let lastObservedActivityAt: number | null = null;
|
|
172
|
+
let lastActivityAgeSignature = '';
|
|
173
|
+
let activityAgeTimer: ReturnType<typeof setInterval> | undefined;
|
|
174
|
+
let sessionGeneration = 0;
|
|
175
|
+
let activeCwd = '';
|
|
176
|
+
let activeCtx: ExtensionContext | undefined;
|
|
177
|
+
let gitRefreshInFlight = false;
|
|
178
|
+
let gitRefreshQueued = false;
|
|
179
|
+
let resourceTimer: ReturnType<typeof setInterval> | undefined;
|
|
180
|
+
let resourceRefreshInFlight = false;
|
|
181
|
+
let previousResourceSample: ProcessTreeSample | undefined;
|
|
182
|
+
let cpuSmoothing: CpuSmoothingState | undefined;
|
|
183
|
+
let latestResourceTelemetry: ResourceTelemetry | undefined;
|
|
184
|
+
let lastResourceSignature = '';
|
|
185
|
+
let lspCapability: LspCapability | null = null;
|
|
186
|
+
let mcpCapability: McpCapability | null = null;
|
|
187
|
+
let footerTelemetry: FooterTelemetrySnapshot | undefined;
|
|
188
|
+
let piStatus: PiStatusSnapshot | undefined;
|
|
189
|
+
let nativeStatusBridgeAvailable = true;
|
|
190
|
+
let vimMode: VimMode = 'insert';
|
|
191
|
+
let lastCapabilitySignature = '';
|
|
192
|
+
|
|
193
|
+
const currentHeader = () => header;
|
|
194
|
+
|
|
195
|
+
const publishPromptStatus = () => {
|
|
196
|
+
if (deckMode) return;
|
|
197
|
+
pi.events.emit(
|
|
198
|
+
PROMPT_STATUS_CHANNEL,
|
|
199
|
+
buildPromptStatusSnapshot(currentHeader(), branch, gitStatus),
|
|
200
|
+
);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const publishFooter = () => {
|
|
204
|
+
if (deckMode) return;
|
|
205
|
+
const ctx = activeCtx;
|
|
206
|
+
if (!ctx) return;
|
|
207
|
+
const usage = ctx.getContextUsage();
|
|
208
|
+
const contextPercent = normalizeContextPercent(
|
|
209
|
+
usage?.percent,
|
|
210
|
+
usage?.tokens,
|
|
211
|
+
usage?.contextWindow ?? ctx.model?.contextWindow,
|
|
212
|
+
);
|
|
213
|
+
const thinking = pi.getThinkingLevel();
|
|
214
|
+
for (const message of buildAgentFooterWidgets({
|
|
215
|
+
cwd: formatFooterPath(activeCwd || ctx.cwd, process.env.HOME),
|
|
216
|
+
devbox: process.env.DEVBOX_PROJECT_ROOT !== undefined,
|
|
217
|
+
branch: formatFooterBranch(branch),
|
|
218
|
+
git: gitStatus,
|
|
219
|
+
gitAvailable,
|
|
220
|
+
model: ctx.model?.name || ctx.model?.id || 'no model',
|
|
221
|
+
...(thinking && thinking !== 'off' ? { thinking } : {}),
|
|
222
|
+
contextPercent,
|
|
223
|
+
compactionCount: countCompactions(ctx.sessionManager.getBranch()),
|
|
224
|
+
})) {
|
|
225
|
+
pi.events.emit(FANCY_FOOTER_WIDGET_CHANNEL, message);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
const publishLatestResourceFooter = () => {
|
|
230
|
+
if (deckMode || !latestResourceTelemetry) return;
|
|
231
|
+
for (const message of buildResourceFooterWidgets(latestResourceTelemetry)) {
|
|
232
|
+
pi.events.emit(FANCY_FOOTER_WIDGET_CHANNEL, message);
|
|
233
|
+
}
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
const publishCapabilityFooter = (force = false) => {
|
|
237
|
+
if (deckMode) return;
|
|
238
|
+
const signature = [mcpCapability?.healthy ?? '', mcpCapability?.total ?? ''].join(
|
|
239
|
+
':',
|
|
240
|
+
);
|
|
241
|
+
if (!force && signature === lastCapabilitySignature) return;
|
|
242
|
+
lastCapabilitySignature = signature;
|
|
243
|
+
pi.events.emit(
|
|
244
|
+
FANCY_FOOTER_WIDGET_CHANNEL,
|
|
245
|
+
buildCapabilityFooterWidget(mcpCapability),
|
|
246
|
+
);
|
|
247
|
+
};
|
|
248
|
+
|
|
249
|
+
const refreshResources = async (): Promise<void> => {
|
|
250
|
+
if (!activeCtx || resourceRefreshInFlight) return;
|
|
251
|
+
resourceRefreshInFlight = true;
|
|
252
|
+
const generation = sessionGeneration;
|
|
253
|
+
try {
|
|
254
|
+
const sample = await sampleProcessTreeResources();
|
|
255
|
+
if (!activeCtx || generation !== sessionGeneration) return;
|
|
256
|
+
const rawCpuPercent = calculateCpuPercent(previousResourceSample, sample);
|
|
257
|
+
previousResourceSample = sample;
|
|
258
|
+
cpuSmoothing = updateCpuSmoothing(cpuSmoothing, rawCpuPercent);
|
|
259
|
+
const telemetry = buildResourceTelemetry(cpuSmoothing, sample.memoryBytes);
|
|
260
|
+
const signature = [
|
|
261
|
+
Math.round(telemetry.cpuPercent * 10),
|
|
262
|
+
telemetry.memoryBytes,
|
|
263
|
+
telemetry.cpuWarning,
|
|
264
|
+
telemetry.memoryWarning,
|
|
265
|
+
].join(':');
|
|
266
|
+
latestResourceTelemetry = telemetry;
|
|
267
|
+
if (signature === lastResourceSignature) return;
|
|
268
|
+
lastResourceSignature = signature;
|
|
269
|
+
if (deckMode) requestRender?.();
|
|
270
|
+
else publishLatestResourceFooter();
|
|
271
|
+
} finally {
|
|
272
|
+
resourceRefreshInFlight = false;
|
|
273
|
+
}
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const removeFooterWidgets = () => {
|
|
277
|
+
for (const id of [
|
|
278
|
+
LOCATION_WIDGET_ID,
|
|
279
|
+
LEGACY_GIT_WIDGET_ID,
|
|
280
|
+
GIT_BRANCH_WIDGET_ID,
|
|
281
|
+
GIT_SCOPE_SEPARATOR_WIDGET_ID,
|
|
282
|
+
GIT_STAGED_WIDGET_ID,
|
|
283
|
+
GIT_MODIFIED_WIDGET_ID,
|
|
284
|
+
GIT_UNTRACKED_WIDGET_ID,
|
|
285
|
+
GIT_CONFLICT_WIDGET_ID,
|
|
286
|
+
ENVIRONMENT_WIDGET_ID,
|
|
287
|
+
REASONING_WIDGET_ID,
|
|
288
|
+
CONTEXT_WIDGET_ID,
|
|
289
|
+
COMPACTION_WIDGET_ID,
|
|
290
|
+
CPU_WIDGET_ID,
|
|
291
|
+
MEMORY_WIDGET_ID,
|
|
292
|
+
RUNTIME_STATUS_WIDGET_ID,
|
|
293
|
+
RUNTIME_SCOPE_SEPARATOR_WIDGET_ID,
|
|
294
|
+
SCOPE_SEPARATOR_WIDGET_ID,
|
|
295
|
+
CAPABILITY_WIDGET_ID,
|
|
296
|
+
]) {
|
|
297
|
+
pi.events.emit(FANCY_FOOTER_WIDGET_CHANNEL, {
|
|
298
|
+
protocol: 1,
|
|
299
|
+
type: 'remove',
|
|
300
|
+
id,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
const currentActivityAge = (): number | null =>
|
|
306
|
+
agentBusy && lastObservedActivityAt !== null
|
|
307
|
+
? Math.max(0, Date.now() - lastObservedActivityAt)
|
|
308
|
+
: null;
|
|
309
|
+
|
|
310
|
+
const publishRuntimeFooter = () => {
|
|
311
|
+
if (deckMode) return;
|
|
312
|
+
const activityAge = currentActivityAge();
|
|
313
|
+
const status = formatRailChronometer(activityAge);
|
|
314
|
+
const active = activityAge !== null;
|
|
315
|
+
const signature = `${active ? 'active' : 'idle'}:${status}`;
|
|
316
|
+
if (signature === lastActivityAgeSignature) return;
|
|
317
|
+
lastActivityAgeSignature = signature;
|
|
318
|
+
for (const message of buildRuntimeFooterWidgets(status, active)) {
|
|
319
|
+
pi.events.emit(FANCY_FOOTER_WIDGET_CHANNEL, message);
|
|
320
|
+
}
|
|
321
|
+
};
|
|
322
|
+
|
|
323
|
+
const publishCapabilityHealth = () => {
|
|
324
|
+
const conditions: Array<{
|
|
325
|
+
id: string;
|
|
326
|
+
severity: 'warning';
|
|
327
|
+
summary: string;
|
|
328
|
+
}> = [];
|
|
329
|
+
if (!nativeStatusBridgeAvailable) {
|
|
330
|
+
conditions.push({
|
|
331
|
+
id: 'native-status-api',
|
|
332
|
+
severity: 'warning',
|
|
333
|
+
summary: 'Pi native extension statuses are unavailable',
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
if (mcpCapability && mcpCapability.healthy < mcpCapability.total) {
|
|
337
|
+
conditions.push({
|
|
338
|
+
id: 'mcp',
|
|
339
|
+
severity: 'warning',
|
|
340
|
+
summary: `${mcpCapability.total - mcpCapability.healthy} MCP servers need attention`,
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
if (lspCapability && lspCapability.healthy < lspCapability.total) {
|
|
344
|
+
conditions.push({
|
|
345
|
+
id: 'lsp',
|
|
346
|
+
severity: 'warning',
|
|
347
|
+
summary: `${lspCapability.total - lspCapability.healthy} LSP servers need attention`,
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
pi.events.emit(PI_STATUS_SOURCE_CHANNEL, {
|
|
351
|
+
protocol: 1,
|
|
352
|
+
type: 'snapshot',
|
|
353
|
+
source: 'galactica-context-header',
|
|
354
|
+
conditions,
|
|
355
|
+
});
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
const stopReady = pi.events.on(FANCY_FOOTER_READY_CHANNEL, () => {
|
|
359
|
+
publishFooter();
|
|
360
|
+
publishLatestResourceFooter();
|
|
361
|
+
publishRuntimeFooter();
|
|
362
|
+
publishCapabilityFooter(true);
|
|
363
|
+
});
|
|
364
|
+
const stopLspStatus = pi.events.on(LSP_STATUS_EVENT, (raw) => {
|
|
365
|
+
lspCapability = parseLspCapability(raw);
|
|
366
|
+
requestRender?.();
|
|
367
|
+
publishCapabilityHealth();
|
|
368
|
+
});
|
|
369
|
+
const stopMcpStatus = pi.events.on(MCP_STATUS_EVENT, (raw) => {
|
|
370
|
+
mcpCapability = parseMcpCapability(raw);
|
|
371
|
+
requestRender?.();
|
|
372
|
+
publishCapabilityFooter();
|
|
373
|
+
publishCapabilityHealth();
|
|
374
|
+
});
|
|
375
|
+
const stopFooterTelemetry = pi.events.on(FOOTER_TELEMETRY_CHANNEL, (raw) => {
|
|
376
|
+
const next = parseFooterTelemetry(raw);
|
|
377
|
+
if (!next) return;
|
|
378
|
+
footerTelemetry = next;
|
|
379
|
+
requestRender?.();
|
|
380
|
+
});
|
|
381
|
+
const stopPiStatus = pi.events.on(PI_STATUS_SNAPSHOT_CHANNEL, (raw) => {
|
|
382
|
+
const next = parsePiStatusSnapshot(raw);
|
|
383
|
+
if (!next) return;
|
|
384
|
+
piStatus = next;
|
|
385
|
+
requestRender?.();
|
|
386
|
+
});
|
|
387
|
+
const stopVimMode = pi.events.on(VIM_MODE_CHANNEL, (raw) => {
|
|
388
|
+
const next = parseVimMode(raw);
|
|
389
|
+
if (!next) return;
|
|
390
|
+
vimMode = next;
|
|
391
|
+
requestRender?.();
|
|
392
|
+
});
|
|
393
|
+
const stopHeader = pi.events.on(GALACTICA_HEADER_CHANNEL, (raw) => {
|
|
394
|
+
header = parseHeaderSnapshot(raw);
|
|
395
|
+
requestRender?.();
|
|
396
|
+
publishPromptStatus();
|
|
397
|
+
publishRuntimeFooter();
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
const markObservedActivity = () => {
|
|
401
|
+
lastObservedActivityAt = Date.now();
|
|
402
|
+
lastActivityAgeSignature = '';
|
|
403
|
+
requestRender?.();
|
|
404
|
+
publishRuntimeFooter();
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const clearObservedActivity = () => {
|
|
408
|
+
lastObservedActivityAt = null;
|
|
409
|
+
lastActivityAgeSignature = '';
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const refreshActivityAge = () => {
|
|
413
|
+
if (currentActivityAge() === null) return;
|
|
414
|
+
if (deckMode) requestRender?.();
|
|
415
|
+
else publishRuntimeFooter();
|
|
416
|
+
};
|
|
417
|
+
|
|
418
|
+
pi.on('before_agent_start', (_event, ctx) => {
|
|
419
|
+
if (ctx.mode !== 'tui') return;
|
|
420
|
+
agentBusy = true;
|
|
421
|
+
markObservedActivity();
|
|
422
|
+
publishPromptStatus();
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
pi.on('turn_start', markObservedActivity);
|
|
426
|
+
pi.on('before_provider_request', markObservedActivity);
|
|
427
|
+
pi.on('after_provider_response', markObservedActivity);
|
|
428
|
+
|
|
429
|
+
pi.on('message_start', (event) => {
|
|
430
|
+
if (event.message.role === 'assistant') markObservedActivity();
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
pi.on('message_update', (event) => {
|
|
434
|
+
if (event.message.role === 'assistant') markObservedActivity();
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
pi.on('tool_execution_start', markObservedActivity);
|
|
438
|
+
pi.on('tool_execution_update', markObservedActivity);
|
|
439
|
+
|
|
440
|
+
const runGit = async (args: string[], cwd: string) => {
|
|
441
|
+
try {
|
|
442
|
+
return await pi.exec('git', args, { cwd, timeout: 2_000 });
|
|
443
|
+
} catch {
|
|
444
|
+
return undefined;
|
|
445
|
+
}
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
const refreshGit = async (): Promise<void> => {
|
|
449
|
+
if (!activeCwd) return;
|
|
450
|
+
if (gitRefreshInFlight) {
|
|
451
|
+
gitRefreshQueued = true;
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
gitRefreshInFlight = true;
|
|
456
|
+
try {
|
|
457
|
+
do {
|
|
458
|
+
gitRefreshQueued = false;
|
|
459
|
+
const generation = sessionGeneration;
|
|
460
|
+
const cwd = activeCwd;
|
|
461
|
+
const [branchResult, statusResult] = await Promise.all([
|
|
462
|
+
runGit(['symbolic-ref', '--short', 'HEAD'], cwd),
|
|
463
|
+
runGit(['status', '--porcelain=v1', '--untracked-files=all'], cwd),
|
|
464
|
+
]);
|
|
465
|
+
if (generation !== sessionGeneration || cwd !== activeCwd) continue;
|
|
466
|
+
|
|
467
|
+
gitAvailable = statusResult?.code === 0;
|
|
468
|
+
branch =
|
|
469
|
+
branchResult?.code === 0
|
|
470
|
+
? parseGitBranch(String(branchResult.stdout ?? ''))
|
|
471
|
+
: '';
|
|
472
|
+
gitStatus = gitAvailable
|
|
473
|
+
? parseGitStatus(String(statusResult?.stdout ?? ''))
|
|
474
|
+
: { ...EMPTY_GIT };
|
|
475
|
+
if (deckMode) requestRender?.();
|
|
476
|
+
else {
|
|
477
|
+
publishPromptStatus();
|
|
478
|
+
publishFooter();
|
|
479
|
+
}
|
|
480
|
+
} while (gitRefreshQueued && activeCwd);
|
|
481
|
+
} finally {
|
|
482
|
+
gitRefreshInFlight = false;
|
|
483
|
+
}
|
|
484
|
+
};
|
|
485
|
+
|
|
486
|
+
pi.on('session_start', (_event, ctx) => {
|
|
487
|
+
clearWidget?.();
|
|
488
|
+
clearFooter?.();
|
|
489
|
+
clearFooter = undefined;
|
|
490
|
+
if (activityAgeTimer) clearInterval(activityAgeTimer);
|
|
491
|
+
if (resourceTimer) clearInterval(resourceTimer);
|
|
492
|
+
activityAgeTimer = undefined;
|
|
493
|
+
resourceTimer = undefined;
|
|
494
|
+
++sessionGeneration;
|
|
495
|
+
activeCtx = ctx;
|
|
496
|
+
activeCwd = '';
|
|
497
|
+
branch = '';
|
|
498
|
+
gitAvailable = false;
|
|
499
|
+
gitStatus = { ...EMPTY_GIT };
|
|
500
|
+
header = null;
|
|
501
|
+
agentBusy = false;
|
|
502
|
+
lspCapability = null;
|
|
503
|
+
mcpCapability = null;
|
|
504
|
+
footerTelemetry = undefined;
|
|
505
|
+
piStatus = undefined;
|
|
506
|
+
nativeStatusBridgeAvailable = true;
|
|
507
|
+
vimMode = 'insert';
|
|
508
|
+
resourceRefreshInFlight = false;
|
|
509
|
+
previousResourceSample = undefined;
|
|
510
|
+
cpuSmoothing = undefined;
|
|
511
|
+
latestResourceTelemetry = undefined;
|
|
512
|
+
lastResourceSignature = '';
|
|
513
|
+
lastCapabilitySignature = '';
|
|
514
|
+
clearObservedActivity();
|
|
515
|
+
publishFooter();
|
|
516
|
+
publishPromptStatus();
|
|
517
|
+
publishRuntimeFooter();
|
|
518
|
+
publishCapabilityFooter();
|
|
519
|
+
publishCapabilityHealth();
|
|
520
|
+
if (ctx.mode === 'tui') {
|
|
521
|
+
ctx.ui.setWorkingVisible(false);
|
|
522
|
+
activityAgeTimer = setInterval(refreshActivityAge, 50);
|
|
523
|
+
}
|
|
524
|
+
if (!ctx.hasUI) return;
|
|
525
|
+
if (deckMode) {
|
|
526
|
+
clearFooter = installEmptyDeckFooter(
|
|
527
|
+
ctx,
|
|
528
|
+
(statuses) => {
|
|
529
|
+
pi.events.emit(PI_NATIVE_STATUS_CHANNEL, {
|
|
530
|
+
protocol: 1,
|
|
531
|
+
type: 'snapshot',
|
|
532
|
+
statuses,
|
|
533
|
+
});
|
|
534
|
+
},
|
|
535
|
+
(available) => {
|
|
536
|
+
nativeStatusBridgeAvailable = available;
|
|
537
|
+
publishCapabilityHealth();
|
|
538
|
+
},
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
void refreshResources();
|
|
542
|
+
resourceTimer = setInterval(() => void refreshResources(), RESOURCE_REFRESH_MS);
|
|
543
|
+
activeCwd = ctx.cwd;
|
|
544
|
+
void refreshGit();
|
|
545
|
+
|
|
546
|
+
const renderDeck = (width: number, theme: Theme): string[] => {
|
|
547
|
+
if (width <= 0) return [];
|
|
548
|
+
const usage = ctx.getContextUsage();
|
|
549
|
+
const contextWindow = usage?.contextWindow ?? ctx.model?.contextWindow;
|
|
550
|
+
const hasContext =
|
|
551
|
+
typeof usage?.percent === 'number' || typeof usage?.tokens === 'number';
|
|
552
|
+
return renderHeaderDeck(
|
|
553
|
+
{
|
|
554
|
+
elapsedMs: currentActivityAge(),
|
|
555
|
+
header: currentHeader(),
|
|
556
|
+
cwd: formatFooterPath(activeCwd || ctx.cwd, process.env.HOME),
|
|
557
|
+
devbox: process.env.DEVBOX_PROJECT_ROOT !== undefined,
|
|
558
|
+
branch: formatFooterBranch(branch, 80),
|
|
559
|
+
gitAvailable,
|
|
560
|
+
git: gitStatus,
|
|
561
|
+
model: ctx.model?.name || ctx.model?.id || 'no model',
|
|
562
|
+
thinking: pi.getThinkingLevel(),
|
|
563
|
+
...(hasContext
|
|
564
|
+
? {
|
|
565
|
+
contextPercent: normalizeContextPercent(
|
|
566
|
+
usage?.percent,
|
|
567
|
+
usage?.tokens,
|
|
568
|
+
contextWindow,
|
|
569
|
+
),
|
|
570
|
+
}
|
|
571
|
+
: {}),
|
|
572
|
+
...(typeof usage?.tokens === 'number' ? { contextTokens: usage.tokens } : {}),
|
|
573
|
+
...(typeof contextWindow === 'number' ? { contextWindow } : {}),
|
|
574
|
+
compactionCount: countCompactions(ctx.sessionManager.getBranch()),
|
|
575
|
+
...(latestResourceTelemetry ? { resources: latestResourceTelemetry } : {}),
|
|
576
|
+
...(footerTelemetry ? { footerTelemetry } : {}),
|
|
577
|
+
...(piStatus ? { piStatus } : {}),
|
|
578
|
+
lsp: lspCapability,
|
|
579
|
+
mcp: mcpCapability,
|
|
580
|
+
mode: vimMode,
|
|
581
|
+
},
|
|
582
|
+
width,
|
|
583
|
+
theme,
|
|
584
|
+
);
|
|
585
|
+
};
|
|
586
|
+
|
|
587
|
+
if (editorDeckSurface) {
|
|
588
|
+
requestRender = () => editorDeckSurface.requestRender();
|
|
589
|
+
editorDeckSurface.setRenderer(renderDeck);
|
|
590
|
+
clearWidget = () => {
|
|
591
|
+
requestRender = undefined;
|
|
592
|
+
editorDeckSurface.setRenderer(null);
|
|
593
|
+
};
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
ctx.ui.setWidget(
|
|
598
|
+
WIDGET_KEY,
|
|
599
|
+
(tui, theme) => {
|
|
600
|
+
requestRender = () => tui.requestRender();
|
|
601
|
+
return {
|
|
602
|
+
render(width: number): string[] {
|
|
603
|
+
if (deckMode) return renderDeck(width, theme);
|
|
604
|
+
const { work, idle } = runtimeHeaderWork(currentHeader()?.work, agentBusy);
|
|
605
|
+
const layout = layoutLifecycleTitle(work, width, agentBusy);
|
|
606
|
+
const line = renderLifecycleTitleSections(
|
|
607
|
+
layout,
|
|
608
|
+
work.lifecycle,
|
|
609
|
+
idle,
|
|
610
|
+
theme,
|
|
611
|
+
);
|
|
612
|
+
return [truncateToWidth(line, width, '…')];
|
|
613
|
+
},
|
|
614
|
+
invalidate() {},
|
|
615
|
+
};
|
|
616
|
+
},
|
|
617
|
+
{ placement: 'aboveEditor' },
|
|
618
|
+
);
|
|
619
|
+
|
|
620
|
+
clearWidget = () => {
|
|
621
|
+
requestRender = undefined;
|
|
622
|
+
ctx.ui.setWidget(WIDGET_KEY, undefined);
|
|
623
|
+
};
|
|
624
|
+
});
|
|
625
|
+
|
|
626
|
+
pi.on('model_select', () => {
|
|
627
|
+
publishFooter();
|
|
628
|
+
requestRender?.();
|
|
629
|
+
});
|
|
630
|
+
|
|
631
|
+
pi.on('thinking_level_select', () => {
|
|
632
|
+
if (deckMode) requestRender?.();
|
|
633
|
+
else publishFooter();
|
|
634
|
+
});
|
|
635
|
+
pi.on('message_end', (event) => {
|
|
636
|
+
if (event.message.role === 'assistant') markObservedActivity();
|
|
637
|
+
publishFooter();
|
|
638
|
+
});
|
|
639
|
+
pi.on('session_compact', () => {
|
|
640
|
+
if (deckMode) requestRender?.();
|
|
641
|
+
else publishFooter();
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
pi.on('tool_execution_end', (event) => {
|
|
645
|
+
markObservedActivity();
|
|
646
|
+
if (shouldRefreshGitAfterTool(event.toolName, event.isError)) {
|
|
647
|
+
void refreshGit();
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
|
|
651
|
+
pi.on('agent_settled', () => {
|
|
652
|
+
agentBusy = false;
|
|
653
|
+
clearObservedActivity();
|
|
654
|
+
requestRender?.();
|
|
655
|
+
publishPromptStatus();
|
|
656
|
+
publishFooter();
|
|
657
|
+
publishRuntimeFooter();
|
|
658
|
+
void refreshGit();
|
|
659
|
+
});
|
|
660
|
+
|
|
661
|
+
pi.on('session_shutdown', () => {
|
|
662
|
+
sessionGeneration += 1;
|
|
663
|
+
activeCwd = '';
|
|
664
|
+
activeCtx = undefined;
|
|
665
|
+
gitAvailable = false;
|
|
666
|
+
agentBusy = false;
|
|
667
|
+
clearObservedActivity();
|
|
668
|
+
if (activityAgeTimer) clearInterval(activityAgeTimer);
|
|
669
|
+
if (resourceTimer) clearInterval(resourceTimer);
|
|
670
|
+
activityAgeTimer = undefined;
|
|
671
|
+
resourceTimer = undefined;
|
|
672
|
+
resourceRefreshInFlight = false;
|
|
673
|
+
previousResourceSample = undefined;
|
|
674
|
+
cpuSmoothing = undefined;
|
|
675
|
+
latestResourceTelemetry = undefined;
|
|
676
|
+
lastResourceSignature = '';
|
|
677
|
+
gitRefreshQueued = false;
|
|
678
|
+
clearWidget?.();
|
|
679
|
+
clearWidget = undefined;
|
|
680
|
+
clearFooter?.();
|
|
681
|
+
clearFooter = undefined;
|
|
682
|
+
removeFooterWidgets();
|
|
683
|
+
stopHeader();
|
|
684
|
+
stopReady();
|
|
685
|
+
stopLspStatus();
|
|
686
|
+
stopMcpStatus();
|
|
687
|
+
stopPiStatus();
|
|
688
|
+
stopFooterTelemetry();
|
|
689
|
+
stopVimMode();
|
|
690
|
+
});
|
|
691
|
+
}
|