@pi-archimedes/core 1.8.2 → 1.9.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/package.json +1 -1
- package/src/index.ts +41 -9
- package/src/startup/index.ts +55 -31
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -59,6 +59,7 @@ export function getCoreSettingsItems(config: CoreConfig): SettingItem[] {
|
|
|
59
59
|
// Module-level state for session lifecycle (shared between session_start and session_shutdown)
|
|
60
60
|
let coreRef: ListingRef | undefined;
|
|
61
61
|
let coreCtx: ExtensionContext | undefined;
|
|
62
|
+
let coreTui: TUI | undefined;
|
|
62
63
|
|
|
63
64
|
export function registerCore(pi: ExtensionAPI): void {
|
|
64
65
|
// Patch console.log for model scope capture
|
|
@@ -72,26 +73,56 @@ export function registerCore(pi: ExtensionAPI): void {
|
|
|
72
73
|
const listingRef = g["listingRef"] as ListingRef | undefined;
|
|
73
74
|
if (listingRef) { listingRef.settled = true; }
|
|
74
75
|
|
|
75
|
-
// Restore patched
|
|
76
|
-
const ORIG_ADD_CHILD = Symbol.for("splashscreen:origAddChild");
|
|
76
|
+
// Restore patched addChild to prevent closure accumulation across reloads
|
|
77
77
|
const PATCHED_LISTING = Symbol.for("splashscreen:listingPatched");
|
|
78
|
-
|
|
78
|
+
const ORIG_ADD_CHILD = Symbol.for("splashscreen:origAddChild");
|
|
79
|
+
if (coreTui) {
|
|
79
80
|
try {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
// Direct TUI children (old Pi: chatContainer was a direct child)
|
|
82
|
+
for (const child of coreTui.children) {
|
|
83
|
+
const cc = child as any;
|
|
84
|
+
if (cc[PATCHED_LISTING] && cc[ORIG_ADD_CHILD]) {
|
|
85
|
+
cc.addChild = cc[ORIG_ADD_CHILD];
|
|
86
|
+
cc[PATCHED_LISTING] = false;
|
|
87
|
+
cc[ORIG_ADD_CHILD] = undefined;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
// Nested children (Pi 0.84+: loadedResourcesContainer is inside documentContainer)
|
|
91
|
+
for (const topChild of coreTui.children) {
|
|
92
|
+
const tc = topChild as any;
|
|
93
|
+
if (!tc.children) continue;
|
|
94
|
+
for (const child of tc.children) {
|
|
83
95
|
const cc = child as any;
|
|
84
96
|
if (cc[PATCHED_LISTING] && cc[ORIG_ADD_CHILD]) {
|
|
85
|
-
|
|
97
|
+
cc.addChild = cc[ORIG_ADD_CHILD];
|
|
86
98
|
cc[PATCHED_LISTING] = false;
|
|
87
99
|
cc[ORIG_ADD_CHILD] = undefined;
|
|
88
100
|
}
|
|
89
101
|
}
|
|
90
102
|
}
|
|
91
|
-
|
|
92
|
-
|
|
103
|
+
|
|
104
|
+
// Clear animation interval and debounce timer for prompt cleanup
|
|
105
|
+
const ANIM_INTERVAL = Symbol.for("splashscreen:animInterval");
|
|
106
|
+
const DEBOUNCE_TIMER = Symbol.for("splashscreen:debounceTimer");
|
|
107
|
+
for (const child of coreTui.children) {
|
|
108
|
+
const cc = child as any;
|
|
109
|
+
if (cc[ANIM_INTERVAL]) clearInterval(cc[ANIM_INTERVAL]);
|
|
110
|
+
if (cc[DEBOUNCE_TIMER]) clearTimeout(cc[DEBOUNCE_TIMER]);
|
|
111
|
+
}
|
|
112
|
+
// Nested children (Pi 0.84+: loadedResourcesContainer is inside documentContainer)
|
|
113
|
+
for (const topChild of coreTui.children) {
|
|
114
|
+
const tc = topChild as any;
|
|
115
|
+
if (!tc.children) continue;
|
|
116
|
+
for (const child of tc.children) {
|
|
117
|
+
const cc = child as any;
|
|
118
|
+
if (cc[ANIM_INTERVAL]) clearInterval(cc[ANIM_INTERVAL]);
|
|
119
|
+
if (cc[DEBOUNCE_TIMER]) clearTimeout(cc[DEBOUNCE_TIMER]);
|
|
120
|
+
}
|
|
93
121
|
}
|
|
122
|
+
} catch {
|
|
123
|
+
/* TUI structure may have changed — ignore */
|
|
94
124
|
}
|
|
125
|
+
}
|
|
95
126
|
|
|
96
127
|
// Clear editor component override
|
|
97
128
|
if (coreCtx) { coreCtx.ui.setEditorComponent(undefined); }
|
|
@@ -116,6 +147,7 @@ export function registerCore(pi: ExtensionAPI): void {
|
|
|
116
147
|
};
|
|
117
148
|
const ref = coreRef;
|
|
118
149
|
const headerFactory = (tui: TUI, theme: Theme): Component & { dispose?(): void } => {
|
|
150
|
+
coreTui = tui; // Capture for shutdown cleanup
|
|
119
151
|
const comp: Component & { dispose?(): void } = {
|
|
120
152
|
invalidate(): void { /* no-op */ },
|
|
121
153
|
render(width: number): string[] {
|
package/src/startup/index.ts
CHANGED
|
@@ -159,22 +159,46 @@ function findChatContainer(tui: TUI): Container | undefined {
|
|
|
159
159
|
return undefined;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
+
// Pi 0.84+ moved resource sections from chatContainer to loadedResourcesContainer
|
|
163
|
+
// (child of documentContainer). Find it by locating documentContainer (the first TUI
|
|
164
|
+
// child that contains Container sub-children), then taking its second Container child.
|
|
165
|
+
//
|
|
166
|
+
// Pi 0.84+ TUI layout (regular mode, 7 top-level children):
|
|
167
|
+
// documentContainer → [headerContainer(0), loadedResourcesContainer(1), chatContainer(2)]
|
|
168
|
+
function findLoadedResourcesContainer(tui: TUI): Container | undefined {
|
|
169
|
+
// Only run this heuristic on Pi >= 0.84.0 where loadedResourcesContainer exists.
|
|
170
|
+
// On older Pi versions, return undefined so the caller falls back to findChatContainer().
|
|
171
|
+
if (compareVersions(VERSION, "0.84.0") < 0) return undefined;
|
|
172
|
+
|
|
173
|
+
// Find documentContainer: first tui child that has Container sub-children
|
|
174
|
+
const docContainer = tui.children.find(
|
|
175
|
+
(c) => c instanceof Container && c.children.some((child) => child instanceof Container)
|
|
176
|
+
) as Container | undefined;
|
|
177
|
+
if (!docContainer) return undefined;
|
|
178
|
+
|
|
179
|
+
const containerChildren = docContainer.children.filter(
|
|
180
|
+
(c): c is Container => c instanceof Container
|
|
181
|
+
);
|
|
182
|
+
// Pi 0.84+: [headerContainer(0), loadedResourcesContainer(1), chatContainer(2)]
|
|
183
|
+
// The index [1] assumption is load-bearing — tied to Pi 0.84+ initialization order.
|
|
184
|
+
if (containerChildren.length < 2) return undefined;
|
|
185
|
+
return containerChildren[1]; // loadedResourcesContainer
|
|
186
|
+
}
|
|
187
|
+
|
|
162
188
|
export function patchStartupListing(
|
|
163
189
|
tui: TUI,
|
|
164
190
|
_theme: Theme,
|
|
165
191
|
ref: ListingRef,
|
|
166
192
|
): void {
|
|
167
|
-
const
|
|
168
|
-
if (!
|
|
169
|
-
|
|
170
|
-
// the startup listing won't render but the extension continues.
|
|
171
|
-
console.warn("[archimedes] Could not find chat container — startup listing disabled. This may indicate a pi TUI structure change.");
|
|
193
|
+
const resourcesContainer = findLoadedResourcesContainer(tui) ?? findChatContainer(tui);
|
|
194
|
+
if (!resourcesContainer) {
|
|
195
|
+
console.warn("[archimedes] Could not find resources container — startup listing disabled. This may indicate a pi TUI structure change.");
|
|
172
196
|
return;
|
|
173
197
|
}
|
|
174
|
-
const
|
|
198
|
+
const rc = resourcesContainer as any;
|
|
175
199
|
|
|
176
200
|
// Always update ref + restart animation (critical for /reload)
|
|
177
|
-
|
|
201
|
+
rc[LISTING_REF] = ref;
|
|
178
202
|
ref.frame = 0;
|
|
179
203
|
ref.revealed = false;
|
|
180
204
|
ref.revealedAt = 0;
|
|
@@ -184,12 +208,12 @@ export function patchStartupListing(
|
|
|
184
208
|
delete ref.cachedWidth;
|
|
185
209
|
delete ref.maxHeaderHeight;
|
|
186
210
|
|
|
187
|
-
if (
|
|
188
|
-
if (
|
|
211
|
+
if (rc[ANIM_INTERVAL]) clearInterval(rc[ANIM_INTERVAL]);
|
|
212
|
+
if (rc[DEBOUNCE_TIMER]) clearTimeout(rc[DEBOUNCE_TIMER]);
|
|
189
213
|
|
|
190
214
|
const interval = setInterval(() => {
|
|
191
215
|
try {
|
|
192
|
-
const current: ListingRef =
|
|
216
|
+
const current: ListingRef = rc[LISTING_REF];
|
|
193
217
|
if (!current) {
|
|
194
218
|
clearInterval(interval);
|
|
195
219
|
return;
|
|
@@ -197,7 +221,7 @@ export function patchStartupListing(
|
|
|
197
221
|
current.frame++;
|
|
198
222
|
if (current.settled && current.frame >= LOGO_SETTLE_FRAME) {
|
|
199
223
|
clearInterval(interval);
|
|
200
|
-
|
|
224
|
+
rc[ANIM_INTERVAL] = null;
|
|
201
225
|
return;
|
|
202
226
|
}
|
|
203
227
|
tui.requestRender();
|
|
@@ -206,12 +230,12 @@ export function patchStartupListing(
|
|
|
206
230
|
}
|
|
207
231
|
}, 16);
|
|
208
232
|
|
|
209
|
-
|
|
233
|
+
rc[ANIM_INTERVAL] = interval;
|
|
210
234
|
|
|
211
235
|
// Fetch latest version from npm
|
|
212
236
|
fetchLatestVersion().then(v => {
|
|
213
237
|
if (v) {
|
|
214
|
-
const current: ListingRef =
|
|
238
|
+
const current: ListingRef = rc[LISTING_REF];
|
|
215
239
|
current.latestVersion = v;
|
|
216
240
|
// Invalidate cache so version updates on next render
|
|
217
241
|
delete current.cachedLines;
|
|
@@ -220,28 +244,28 @@ export function patchStartupListing(
|
|
|
220
244
|
});
|
|
221
245
|
|
|
222
246
|
// Patch clear() to intercept container rebuild
|
|
223
|
-
if (!
|
|
224
|
-
|
|
225
|
-
const origClear =
|
|
226
|
-
|
|
247
|
+
if (!rc[PATCHED_CLEAR]) {
|
|
248
|
+
rc[PATCHED_CLEAR] = true;
|
|
249
|
+
const origClear = resourcesContainer.clear.bind(resourcesContainer);
|
|
250
|
+
resourcesContainer.clear = () => {
|
|
227
251
|
return origClear();
|
|
228
252
|
};
|
|
229
253
|
}
|
|
230
254
|
|
|
231
|
-
// Only patch addChild once — the closure reads
|
|
232
|
-
if (
|
|
233
|
-
|
|
255
|
+
// Only patch addChild once — the closure reads rc[LISTING_REF] dynamically
|
|
256
|
+
if (rc[PATCHED_LISTING]) {
|
|
257
|
+
resourcesContainer.clear();
|
|
234
258
|
return;
|
|
235
259
|
}
|
|
236
|
-
|
|
260
|
+
rc[PATCHED_LISTING] = true;
|
|
237
261
|
|
|
238
|
-
const origAddChild =
|
|
239
|
-
|
|
240
|
-
|
|
262
|
+
const origAddChild = resourcesContainer.addChild.bind(resourcesContainer);
|
|
263
|
+
rc[ORIG_ADD_CHILD] = origAddChild; // Store for cleanup on shutdown
|
|
264
|
+
resourcesContainer.clear();
|
|
241
265
|
|
|
242
|
-
|
|
266
|
+
resourcesContainer.addChild = (component: Component) => {
|
|
243
267
|
try {
|
|
244
|
-
const currentRef: ListingRef =
|
|
268
|
+
const currentRef: ListingRef = rc[LISTING_REF];
|
|
245
269
|
|
|
246
270
|
if (component instanceof Text) {
|
|
247
271
|
// pi ≥0.67.6 wraps startup sections in ExpandableText; collapsed body
|
|
@@ -270,14 +294,14 @@ export function patchStartupListing(
|
|
|
270
294
|
tui.requestRender();
|
|
271
295
|
} else {
|
|
272
296
|
// Batch initial sections — reset debounce on each arrival
|
|
273
|
-
if (
|
|
274
|
-
|
|
275
|
-
const ref: ListingRef =
|
|
297
|
+
if (rc[DEBOUNCE_TIMER]) clearTimeout(rc[DEBOUNCE_TIMER]);
|
|
298
|
+
rc[DEBOUNCE_TIMER] = setTimeout(() => {
|
|
299
|
+
const ref: ListingRef = rc[LISTING_REF];
|
|
276
300
|
ref.revealed = true;
|
|
277
301
|
ref.revealedAt = ref.frame;
|
|
278
302
|
ref.scaffoldAt = ref.frame;
|
|
279
303
|
tui.requestRender();
|
|
280
|
-
|
|
304
|
+
rc[DEBOUNCE_TIMER] = null;
|
|
281
305
|
}, REVEAL_DEBOUNCE_MS);
|
|
282
306
|
}
|
|
283
307
|
|
|
@@ -297,7 +321,7 @@ export function patchStartupListing(
|
|
|
297
321
|
try {
|
|
298
322
|
origAddChild(component);
|
|
299
323
|
} catch (err) {
|
|
300
|
-
console.error("[archimedes] Error in
|
|
324
|
+
console.error("[archimedes] Error in resourcesContainer.addChild:", err);
|
|
301
325
|
}
|
|
302
326
|
} catch (err) {
|
|
303
327
|
console.error("[archimedes] Error in startup listing addChild handler:", err);
|