@aemonge-dev/pi-stardeck 0.5.0 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -2
- package/docs/architecture.md +8 -5
- package/licenses/README.md +9 -7
- package/package.json +2 -2
- 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 +7 -6
- package/packages/status/package.json +1 -1
- package/src/health.ts +0 -1
- package/src/index.ts +3 -4
- package/licenses/pi-fancy-footer-LICENSE +0 -21
- package/packages/pi-fancy-footer-full-palette/LICENSE +0 -21
- package/packages/pi-fancy-footer-full-palette/LOCAL-CHANGES.md +0 -41
- package/packages/pi-fancy-footer-full-palette/README.md +0 -468
- package/packages/pi-fancy-footer-full-palette/package.json +0 -52
- package/packages/pi-fancy-footer-full-palette/src/api.ts +0 -204
- package/packages/pi-fancy-footer-full-palette/src/config-editor.ts +0 -634
- package/packages/pi-fancy-footer-full-palette/src/config.ts +0 -852
- package/packages/pi-fancy-footer-full-palette/src/data-widgets.ts +0 -259
- package/packages/pi-fancy-footer-full-palette/src/git.ts +0 -130
- package/packages/pi-fancy-footer-full-palette/src/index.ts +0 -479
- package/packages/pi-fancy-footer-full-palette/src/layout.ts +0 -309
- package/packages/pi-fancy-footer-full-palette/src/provider-status.ts +0 -1466
- package/packages/pi-fancy-footer-full-palette/src/render.ts +0 -1061
- package/packages/pi-fancy-footer-full-palette/src/shared.ts +0 -903
|
@@ -1,479 +0,0 @@
|
|
|
1
|
-
import { createRequire } from "node:module";
|
|
2
|
-
import {
|
|
3
|
-
type ExtensionAPI,
|
|
4
|
-
type ExtensionContext,
|
|
5
|
-
} from "@earendil-works/pi-coding-agent";
|
|
6
|
-
import {
|
|
7
|
-
DEFAULT_FOOTER_CONFIG,
|
|
8
|
-
EMPTY_GIT_INFO,
|
|
9
|
-
MAX_FOOTER_REFRESH_MS,
|
|
10
|
-
MAX_PROVIDER_STATUS_REFRESH_MS,
|
|
11
|
-
MIN_FOOTER_REFRESH_MS,
|
|
12
|
-
MIN_PROVIDER_STATUS_REFRESH_MS,
|
|
13
|
-
clampInt,
|
|
14
|
-
type FooterConfigSnapshot,
|
|
15
|
-
type ProviderStatusSnapshot,
|
|
16
|
-
type SessionUsageMetrics,
|
|
17
|
-
} from "./shared.ts";
|
|
18
|
-
import {
|
|
19
|
-
cloneFooterConfig,
|
|
20
|
-
getFooterConfigPath,
|
|
21
|
-
hasFooterConfigError,
|
|
22
|
-
loadFooterConfig,
|
|
23
|
-
writeFooterConfigSnapshot,
|
|
24
|
-
} from "./config.ts";
|
|
25
|
-
import { collectGitInfo } from "./git.ts";
|
|
26
|
-
import {
|
|
27
|
-
FANCY_FOOTER_PROTOCOL_VERSION,
|
|
28
|
-
FANCY_FOOTER_READY_CHANNEL,
|
|
29
|
-
FANCY_FOOTER_TELEMETRY_CHANNEL,
|
|
30
|
-
FANCY_FOOTER_WIDGET_CHANNEL,
|
|
31
|
-
PI_STATUS_SOURCE_CHANNEL,
|
|
32
|
-
type FancyFooterReadyMessage,
|
|
33
|
-
type FancyFooterStatusSourceMessage,
|
|
34
|
-
type FancyFooterTelemetryMessage,
|
|
35
|
-
} from "./api.ts";
|
|
36
|
-
import {
|
|
37
|
-
createMicrotaskCoalescer,
|
|
38
|
-
FancyFooterDataWidgetStore,
|
|
39
|
-
type NormalizedFancyFooterDataWidget,
|
|
40
|
-
} from "./data-widgets.ts";
|
|
41
|
-
import { openFooterConfigEditor } from "./config-editor.ts";
|
|
42
|
-
import { collectSessionUsageMetrics, renderFooterLines } from "./render.ts";
|
|
43
|
-
import {
|
|
44
|
-
collectProviderStatus,
|
|
45
|
-
isProviderStatusRelevantToModel,
|
|
46
|
-
projectProviderStatusForModel,
|
|
47
|
-
providerStatusQuotaWindows,
|
|
48
|
-
updateProviderStatusFromHeaders,
|
|
49
|
-
} from "./provider-status.ts";
|
|
50
|
-
|
|
51
|
-
interface ActiveFooterControls {
|
|
52
|
-
requestRender: () => void;
|
|
53
|
-
reschedule: () => void;
|
|
54
|
-
rescheduleProviderStatus: () => void;
|
|
55
|
-
updateProviderStatus: (status: ProviderStatusSnapshot) => void;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const PACKAGE_VERSION = (
|
|
59
|
-
createRequire(import.meta.url)("../package.json") as { version: string }
|
|
60
|
-
).version;
|
|
61
|
-
|
|
62
|
-
export type FancyFooterSurface = "footer" | "telemetry";
|
|
63
|
-
|
|
64
|
-
export interface FancyFooterOptions {
|
|
65
|
-
surface?: FancyFooterSurface;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
export default function (pi: ExtensionAPI, options: FancyFooterOptions = {}) {
|
|
69
|
-
const telemetryOnly = options.surface === "telemetry";
|
|
70
|
-
let footerConfig: FooterConfigSnapshot = {
|
|
71
|
-
refreshMs: DEFAULT_FOOTER_CONFIG.refreshMs,
|
|
72
|
-
iconFamily: DEFAULT_FOOTER_CONFIG.iconFamily,
|
|
73
|
-
gaugeStyle: DEFAULT_FOOTER_CONFIG.gaugeStyle,
|
|
74
|
-
gaugeWidth: DEFAULT_FOOTER_CONFIG.gaugeWidth,
|
|
75
|
-
gaugeColors: { ...DEFAULT_FOOTER_CONFIG.gaugeColors },
|
|
76
|
-
defaultTextColor: DEFAULT_FOOTER_CONFIG.defaultTextColor,
|
|
77
|
-
defaultIconColor: DEFAULT_FOOTER_CONFIG.defaultIconColor,
|
|
78
|
-
providerStatus: { ...DEFAULT_FOOTER_CONFIG.providerStatus },
|
|
79
|
-
widgets: { ...DEFAULT_FOOTER_CONFIG.widgets },
|
|
80
|
-
extensionWidgets: { ...DEFAULT_FOOTER_CONFIG.extensionWidgets },
|
|
81
|
-
};
|
|
82
|
-
const dataWidgets = new FancyFooterDataWidgetStore();
|
|
83
|
-
let extensionWidgets: NormalizedFancyFooterDataWidget[] = [];
|
|
84
|
-
|
|
85
|
-
let activeFooterControls: ActiveFooterControls | undefined;
|
|
86
|
-
let footerInstanceId = 0;
|
|
87
|
-
let telemetryContext: ExtensionContext | undefined;
|
|
88
|
-
let telemetryProviderStatuses = new Map<string, ProviderStatusSnapshot>();
|
|
89
|
-
let telemetryRefreshTimer: ReturnType<typeof setTimeout> | undefined;
|
|
90
|
-
let telemetryGeneration = 0;
|
|
91
|
-
|
|
92
|
-
const invalidateActiveFooter = () => {
|
|
93
|
-
footerInstanceId += 1;
|
|
94
|
-
activeFooterControls = undefined;
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
const requestDataWidgetRender = createMicrotaskCoalescer(() => {
|
|
98
|
-
activeFooterControls?.requestRender();
|
|
99
|
-
});
|
|
100
|
-
const refreshDataWidgets = () => {
|
|
101
|
-
extensionWidgets = dataWidgets.values();
|
|
102
|
-
requestDataWidgetRender();
|
|
103
|
-
};
|
|
104
|
-
const publishReady = () => {
|
|
105
|
-
const message: FancyFooterReadyMessage = {
|
|
106
|
-
protocol: FANCY_FOOTER_PROTOCOL_VERSION,
|
|
107
|
-
version: PACKAGE_VERSION,
|
|
108
|
-
};
|
|
109
|
-
pi.events.emit(FANCY_FOOTER_READY_CHANNEL, message);
|
|
110
|
-
};
|
|
111
|
-
|
|
112
|
-
const telemetryQuotaPercent = (): number | undefined => {
|
|
113
|
-
const model = telemetryContext?.model;
|
|
114
|
-
const percentages: number[] = [];
|
|
115
|
-
for (const snapshot of telemetryProviderStatuses.values()) {
|
|
116
|
-
if (!isProviderStatusRelevantToModel(snapshot.provider, model)) continue;
|
|
117
|
-
const projected = projectProviderStatusForModel(snapshot, model);
|
|
118
|
-
for (const window of [projected.primary, projected.secondary]) {
|
|
119
|
-
if (!window || window.usageUnknown) continue;
|
|
120
|
-
percentages.push(window.usedPercent);
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
return percentages.length > 0 ? Math.max(...percentages) : undefined;
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const publishTelemetry = () => {
|
|
127
|
-
if (!telemetryContext) return;
|
|
128
|
-
const totalCost = collectSessionUsageMetrics(telemetryContext).totalCost;
|
|
129
|
-
const quotaPercent = telemetryQuotaPercent();
|
|
130
|
-
const quotaWindows = providerStatusQuotaWindows(
|
|
131
|
-
telemetryProviderStatuses.values(),
|
|
132
|
-
telemetryContext?.model,
|
|
133
|
-
);
|
|
134
|
-
const message: FancyFooterTelemetryMessage = {
|
|
135
|
-
protocol: FANCY_FOOTER_PROTOCOL_VERSION,
|
|
136
|
-
type: "snapshot",
|
|
137
|
-
totalCost,
|
|
138
|
-
...(quotaPercent === undefined ? {} : { quotaPercent }),
|
|
139
|
-
...(quotaWindows.length > 0 ? { quotaWindows } : {}),
|
|
140
|
-
};
|
|
141
|
-
pi.events.emit(FANCY_FOOTER_TELEMETRY_CHANNEL, message);
|
|
142
|
-
|
|
143
|
-
const conditions: FancyFooterStatusSourceMessage["conditions"] = [];
|
|
144
|
-
if (hasFooterConfigError()) {
|
|
145
|
-
conditions.push({
|
|
146
|
-
id: "config",
|
|
147
|
-
severity: "error",
|
|
148
|
-
summary: "Footer configuration is invalid; defaults are active",
|
|
149
|
-
});
|
|
150
|
-
}
|
|
151
|
-
for (const snapshot of telemetryProviderStatuses.values()) {
|
|
152
|
-
if (!snapshot.error) continue;
|
|
153
|
-
const retained =
|
|
154
|
-
snapshot.source === "cache" &&
|
|
155
|
-
(snapshot.primary !== undefined || snapshot.secondary !== undefined);
|
|
156
|
-
conditions.push({
|
|
157
|
-
id: `provider-status.${snapshot.provider}`,
|
|
158
|
-
severity: retained ? "warning" : "error",
|
|
159
|
-
summary: retained
|
|
160
|
-
? `${snapshot.provider} quota refresh failed; cached status is active`
|
|
161
|
-
: `${snapshot.provider} quota status is unavailable`,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
const health: FancyFooterStatusSourceMessage = {
|
|
165
|
-
protocol: FANCY_FOOTER_PROTOCOL_VERSION,
|
|
166
|
-
type: "snapshot",
|
|
167
|
-
source: "fancy-footer",
|
|
168
|
-
conditions,
|
|
169
|
-
};
|
|
170
|
-
pi.events.emit(PI_STATUS_SOURCE_CHANNEL, health);
|
|
171
|
-
};
|
|
172
|
-
|
|
173
|
-
const stopTelemetryProvider = () => {
|
|
174
|
-
telemetryGeneration += 1;
|
|
175
|
-
telemetryContext = undefined;
|
|
176
|
-
telemetryProviderStatuses.clear();
|
|
177
|
-
if (telemetryRefreshTimer) clearTimeout(telemetryRefreshTimer);
|
|
178
|
-
telemetryRefreshTimer = undefined;
|
|
179
|
-
};
|
|
180
|
-
|
|
181
|
-
const installTelemetryProvider = (ctx: ExtensionContext) => {
|
|
182
|
-
stopTelemetryProvider();
|
|
183
|
-
telemetryContext = ctx;
|
|
184
|
-
footerConfig = loadFooterConfig();
|
|
185
|
-
const generation = telemetryGeneration;
|
|
186
|
-
|
|
187
|
-
const refresh = async () => {
|
|
188
|
-
if (!telemetryContext || generation !== telemetryGeneration) return;
|
|
189
|
-
const next = await collectProviderStatus(pi, footerConfig.providerStatus);
|
|
190
|
-
if (!telemetryContext || generation !== telemetryGeneration) return;
|
|
191
|
-
telemetryProviderStatuses = new Map(
|
|
192
|
-
next.map((snapshot) => [snapshot.provider, snapshot]),
|
|
193
|
-
);
|
|
194
|
-
publishTelemetry();
|
|
195
|
-
};
|
|
196
|
-
const schedule = () => {
|
|
197
|
-
if (!telemetryContext || generation !== telemetryGeneration) return;
|
|
198
|
-
if (telemetryRefreshTimer) clearTimeout(telemetryRefreshTimer);
|
|
199
|
-
const refreshMs = clampInt(
|
|
200
|
-
footerConfig.providerStatus.refreshMs,
|
|
201
|
-
MIN_PROVIDER_STATUS_REFRESH_MS,
|
|
202
|
-
MAX_PROVIDER_STATUS_REFRESH_MS,
|
|
203
|
-
);
|
|
204
|
-
telemetryRefreshTimer = setTimeout(() => {
|
|
205
|
-
void refresh().finally(schedule);
|
|
206
|
-
}, refreshMs);
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
publishTelemetry();
|
|
210
|
-
void refresh();
|
|
211
|
-
schedule();
|
|
212
|
-
};
|
|
213
|
-
|
|
214
|
-
const stopDataWidgetListener = pi.events.on(FANCY_FOOTER_WIDGET_CHANNEL, (raw) => {
|
|
215
|
-
if (dataWidgets.apply(raw)) refreshDataWidgets();
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
const installFooter = (ctx: ExtensionContext) => {
|
|
219
|
-
if (!ctx.hasUI) return;
|
|
220
|
-
|
|
221
|
-
footerConfig = loadFooterConfig();
|
|
222
|
-
|
|
223
|
-
ctx.ui.setFooter((tui, theme, footerData) => {
|
|
224
|
-
const instanceId = ++footerInstanceId;
|
|
225
|
-
const fallbackThinkingLevel = pi.getThinkingLevel();
|
|
226
|
-
let currentGit = { ...EMPTY_GIT_INFO };
|
|
227
|
-
let providerStatuses = new Map<string, ProviderStatusSnapshot>();
|
|
228
|
-
let usageMetrics: SessionUsageMetrics = collectSessionUsageMetrics(ctx);
|
|
229
|
-
let refreshing = false;
|
|
230
|
-
let refreshQueued = false;
|
|
231
|
-
let providerStatusRefreshing = false;
|
|
232
|
-
let providerStatusRefreshQueued = false;
|
|
233
|
-
let providerStatusRefreshAt = 0;
|
|
234
|
-
let disposed = false;
|
|
235
|
-
let refreshTimer: ReturnType<typeof setTimeout> | undefined;
|
|
236
|
-
let providerStatusTimer: ReturnType<typeof setTimeout> | undefined;
|
|
237
|
-
|
|
238
|
-
const isActiveFooter = () => !disposed && instanceId === footerInstanceId;
|
|
239
|
-
|
|
240
|
-
const requestRender = () => {
|
|
241
|
-
if (!isActiveFooter()) return;
|
|
242
|
-
tui.requestRender();
|
|
243
|
-
};
|
|
244
|
-
|
|
245
|
-
const isProviderStatusWidgetEnabled = () =>
|
|
246
|
-
footerConfig.widgets["provider-status"]?.enabled !== false;
|
|
247
|
-
|
|
248
|
-
const refreshProviderStatus = async (force = false) => {
|
|
249
|
-
if (!isActiveFooter() || !isProviderStatusWidgetEnabled()) return;
|
|
250
|
-
const now = Date.now();
|
|
251
|
-
const refreshMs = clampInt(
|
|
252
|
-
footerConfig.providerStatus.refreshMs,
|
|
253
|
-
MIN_PROVIDER_STATUS_REFRESH_MS,
|
|
254
|
-
MAX_PROVIDER_STATUS_REFRESH_MS,
|
|
255
|
-
);
|
|
256
|
-
if (!force && providerStatusRefreshAt + refreshMs > now) return;
|
|
257
|
-
if (providerStatusRefreshing) {
|
|
258
|
-
providerStatusRefreshQueued = true;
|
|
259
|
-
return;
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
providerStatusRefreshing = true;
|
|
263
|
-
try {
|
|
264
|
-
do {
|
|
265
|
-
providerStatusRefreshQueued = false;
|
|
266
|
-
if (!isActiveFooter() || !isProviderStatusWidgetEnabled()) {
|
|
267
|
-
continue;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
providerStatusRefreshAt = Date.now();
|
|
271
|
-
const next = await collectProviderStatus(pi, footerConfig.providerStatus);
|
|
272
|
-
if (!isActiveFooter()) return;
|
|
273
|
-
providerStatuses = new Map(
|
|
274
|
-
next.map((snapshot) => [snapshot.provider, snapshot]),
|
|
275
|
-
);
|
|
276
|
-
requestRender();
|
|
277
|
-
} while (isActiveFooter() && providerStatusRefreshQueued);
|
|
278
|
-
} finally {
|
|
279
|
-
providerStatusRefreshing = false;
|
|
280
|
-
}
|
|
281
|
-
};
|
|
282
|
-
|
|
283
|
-
const refreshGit = async () => {
|
|
284
|
-
if (!isActiveFooter()) return;
|
|
285
|
-
if (refreshing) {
|
|
286
|
-
refreshQueued = true;
|
|
287
|
-
return;
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
refreshing = true;
|
|
291
|
-
try {
|
|
292
|
-
do {
|
|
293
|
-
refreshQueued = false;
|
|
294
|
-
if (!isActiveFooter()) continue;
|
|
295
|
-
|
|
296
|
-
footerConfig = loadFooterConfig();
|
|
297
|
-
usageMetrics = collectSessionUsageMetrics(ctx);
|
|
298
|
-
|
|
299
|
-
const git = await collectGitInfo(pi, ctx.cwd);
|
|
300
|
-
if (!isActiveFooter()) return;
|
|
301
|
-
currentGit = git;
|
|
302
|
-
requestRender();
|
|
303
|
-
} while (isActiveFooter() && refreshQueued);
|
|
304
|
-
} finally {
|
|
305
|
-
refreshing = false;
|
|
306
|
-
}
|
|
307
|
-
};
|
|
308
|
-
|
|
309
|
-
const scheduleRefresh = () => {
|
|
310
|
-
if (!isActiveFooter()) return;
|
|
311
|
-
if (refreshTimer) clearTimeout(refreshTimer);
|
|
312
|
-
|
|
313
|
-
const refreshMs = clampInt(
|
|
314
|
-
footerConfig.refreshMs,
|
|
315
|
-
MIN_FOOTER_REFRESH_MS,
|
|
316
|
-
MAX_FOOTER_REFRESH_MS,
|
|
317
|
-
);
|
|
318
|
-
refreshTimer = setTimeout(() => {
|
|
319
|
-
if (!isActiveFooter()) return;
|
|
320
|
-
void refreshGit().finally(() => {
|
|
321
|
-
scheduleRefresh();
|
|
322
|
-
});
|
|
323
|
-
}, refreshMs);
|
|
324
|
-
};
|
|
325
|
-
|
|
326
|
-
const scheduleProviderStatusRefresh = () => {
|
|
327
|
-
if (!isActiveFooter()) return;
|
|
328
|
-
if (providerStatusTimer) clearTimeout(providerStatusTimer);
|
|
329
|
-
|
|
330
|
-
const refreshMs = clampInt(
|
|
331
|
-
footerConfig.providerStatus.refreshMs,
|
|
332
|
-
MIN_PROVIDER_STATUS_REFRESH_MS,
|
|
333
|
-
MAX_PROVIDER_STATUS_REFRESH_MS,
|
|
334
|
-
);
|
|
335
|
-
providerStatusTimer = setTimeout(() => {
|
|
336
|
-
if (!isActiveFooter()) return;
|
|
337
|
-
void refreshProviderStatus().finally(() => {
|
|
338
|
-
scheduleProviderStatusRefresh();
|
|
339
|
-
});
|
|
340
|
-
}, refreshMs);
|
|
341
|
-
};
|
|
342
|
-
|
|
343
|
-
const onBranchChange = footerData.onBranchChange(() => {
|
|
344
|
-
if (!isActiveFooter()) return;
|
|
345
|
-
usageMetrics = collectSessionUsageMetrics(ctx);
|
|
346
|
-
requestRender();
|
|
347
|
-
void refreshGit();
|
|
348
|
-
});
|
|
349
|
-
|
|
350
|
-
activeFooterControls = {
|
|
351
|
-
requestRender,
|
|
352
|
-
reschedule: scheduleRefresh,
|
|
353
|
-
rescheduleProviderStatus: scheduleProviderStatusRefresh,
|
|
354
|
-
updateProviderStatus: (status) => {
|
|
355
|
-
if (!isActiveFooter()) return;
|
|
356
|
-
providerStatuses.set(status.provider, status);
|
|
357
|
-
requestRender();
|
|
358
|
-
},
|
|
359
|
-
};
|
|
360
|
-
|
|
361
|
-
void refreshGit();
|
|
362
|
-
void refreshProviderStatus(true);
|
|
363
|
-
scheduleRefresh();
|
|
364
|
-
scheduleProviderStatusRefresh();
|
|
365
|
-
|
|
366
|
-
return {
|
|
367
|
-
invalidate() {},
|
|
368
|
-
dispose() {
|
|
369
|
-
disposed = true;
|
|
370
|
-
onBranchChange();
|
|
371
|
-
if (refreshTimer) clearTimeout(refreshTimer);
|
|
372
|
-
if (providerStatusTimer) clearTimeout(providerStatusTimer);
|
|
373
|
-
if (activeFooterControls?.requestRender === requestRender) {
|
|
374
|
-
activeFooterControls = undefined;
|
|
375
|
-
}
|
|
376
|
-
},
|
|
377
|
-
render(width: number): string[] {
|
|
378
|
-
if (!isActiveFooter()) return ["", ""];
|
|
379
|
-
|
|
380
|
-
return renderFooterLines(
|
|
381
|
-
width,
|
|
382
|
-
ctx,
|
|
383
|
-
currentGit,
|
|
384
|
-
fallbackThinkingLevel,
|
|
385
|
-
theme,
|
|
386
|
-
usageMetrics,
|
|
387
|
-
footerConfig,
|
|
388
|
-
extensionWidgets,
|
|
389
|
-
Array.from(providerStatuses.values()).sort((a, b) =>
|
|
390
|
-
a.provider.localeCompare(b.provider),
|
|
391
|
-
),
|
|
392
|
-
);
|
|
393
|
-
},
|
|
394
|
-
};
|
|
395
|
-
});
|
|
396
|
-
};
|
|
397
|
-
|
|
398
|
-
pi.registerCommand("fancy-footer", {
|
|
399
|
-
description: "Configure the fancy footer.",
|
|
400
|
-
handler: async (_args, ctx) => {
|
|
401
|
-
const configPath = getFooterConfigPath();
|
|
402
|
-
|
|
403
|
-
if (!ctx.hasUI) {
|
|
404
|
-
ctx.ui.notify("/fancy-footer requires interactive UI mode", "warning");
|
|
405
|
-
return;
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
const draft = cloneFooterConfig(loadFooterConfig());
|
|
409
|
-
const applyDraft = () => {
|
|
410
|
-
try {
|
|
411
|
-
writeFooterConfigSnapshot(draft);
|
|
412
|
-
footerConfig = loadFooterConfig();
|
|
413
|
-
activeFooterControls?.reschedule();
|
|
414
|
-
activeFooterControls?.rescheduleProviderStatus();
|
|
415
|
-
activeFooterControls?.requestRender();
|
|
416
|
-
} catch (error) {
|
|
417
|
-
const msg = error instanceof Error ? error.message : String(error);
|
|
418
|
-
ctx.ui.notify(`Failed to save config: ${msg}`, "error");
|
|
419
|
-
}
|
|
420
|
-
};
|
|
421
|
-
|
|
422
|
-
await openFooterConfigEditor({
|
|
423
|
-
ctx,
|
|
424
|
-
configPath,
|
|
425
|
-
draft,
|
|
426
|
-
extensionWidgets,
|
|
427
|
-
applyDraft,
|
|
428
|
-
});
|
|
429
|
-
},
|
|
430
|
-
});
|
|
431
|
-
|
|
432
|
-
pi.on("after_provider_response", async (event) => {
|
|
433
|
-
if (footerConfig.widgets["provider-status"]?.enabled === false) return;
|
|
434
|
-
|
|
435
|
-
const updated = await updateProviderStatusFromHeaders(
|
|
436
|
-
event.headers ?? {},
|
|
437
|
-
footerConfig.providerStatus,
|
|
438
|
-
);
|
|
439
|
-
for (const snapshot of updated) {
|
|
440
|
-
activeFooterControls?.updateProviderStatus(snapshot);
|
|
441
|
-
if (telemetryOnly) telemetryProviderStatuses.set(snapshot.provider, snapshot);
|
|
442
|
-
}
|
|
443
|
-
if (telemetryOnly && updated.length > 0) publishTelemetry();
|
|
444
|
-
});
|
|
445
|
-
|
|
446
|
-
pi.on("model_select", () => {
|
|
447
|
-
activeFooterControls?.requestRender();
|
|
448
|
-
if (telemetryOnly) publishTelemetry();
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
pi.on("thinking_level_select", () => {
|
|
452
|
-
activeFooterControls?.requestRender();
|
|
453
|
-
});
|
|
454
|
-
|
|
455
|
-
pi.on("session_compact", () => {
|
|
456
|
-
activeFooterControls?.requestRender();
|
|
457
|
-
if (telemetryOnly) publishTelemetry();
|
|
458
|
-
});
|
|
459
|
-
|
|
460
|
-
pi.on("message_end", () => {
|
|
461
|
-
if (telemetryOnly) publishTelemetry();
|
|
462
|
-
});
|
|
463
|
-
|
|
464
|
-
pi.on("session_shutdown", async () => {
|
|
465
|
-
stopDataWidgetListener();
|
|
466
|
-
stopTelemetryProvider();
|
|
467
|
-
invalidateActiveFooter();
|
|
468
|
-
if (dataWidgets.clear()) extensionWidgets = [];
|
|
469
|
-
});
|
|
470
|
-
|
|
471
|
-
pi.on("session_start", async (_event, ctx) => {
|
|
472
|
-
if (dataWidgets.clear()) extensionWidgets = [];
|
|
473
|
-
if (telemetryOnly) installTelemetryProvider(ctx);
|
|
474
|
-
else installFooter(ctx);
|
|
475
|
-
publishReady();
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
publishReady();
|
|
479
|
-
}
|