@tryinget/pi-activity-strip 0.5.0 → 0.7.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 +59 -7
- package/bin/pi-activity-strip-claude-hook.mjs +71 -0
- package/bin/pi-activity-strip.mjs +98 -64
- package/extensions/activity-strip.js +25 -21
- package/native/bin/linux-x64-gnu/artifact.json +2 -2
- package/native/bin/linux-x64-gnu/pi-activity-strip-panel +0 -0
- package/native/panel/src/app.rs +20 -20
- package/native/panel/src/card_view.rs +396 -9
- package/native/panel/src/main.rs +64 -1
- package/native/panel/src/protocol.rs +142 -0
- package/native/panel/src/runtime.rs +50 -9
- package/native/panel/src/style.css +147 -60
- package/package.json +4 -4
- package/src/client/runtime-lock.mjs +140 -0
- package/src/common/agent-identity.mjs +163 -0
- package/src/common/ak-tasks.mjs +354 -0
- package/src/common/claude-events.mjs +190 -0
- package/src/common/claude-hook-config.mjs +56 -0
- package/src/common/claude-transcript.mjs +163 -0
- package/src/common/codex-transcript.mjs +153 -0
- package/src/common/compatibility.mjs +13 -0
- package/src/common/constants.mjs +2 -0
- package/src/common/contracts.d.ts +35 -0
- package/src/common/contracts.ts +67 -1
- package/src/common/ghostty-present.mjs +100 -0
- package/src/common/ghostty-theme.mjs +149 -0
- package/src/common/niri-focus.mjs +179 -111
- package/src/common/session-cards.mjs +5 -2
- package/src/common/status-report.mjs +51 -0
- package/src/common/surface-bindings.mjs +234 -0
- package/src/common/terminal-identity.mjs +75 -12
- package/src/common/window-placement.mjs +59 -0
- package/src/common/workspace-view.mjs +161 -0
- package/src/native/agent-discovery.mjs +294 -0
- package/src/native/ak-runtime.mjs +164 -0
- package/src/native/codex-discovery.mjs +128 -0
- package/src/native/ghostty-tab-inventory.py +99 -0
- package/src/native/height-repair.mjs +154 -0
- package/src/native/main.mjs +122 -64
- package/src/native/panel-binary.mjs +56 -0
- package/src/native/panel-projection.mjs +70 -5
- package/src/native/placement.mjs +225 -0
- package/src/native/tab-inventory.mjs +188 -0
- package/src/native/theme-runtime.mjs +72 -0
- package/src/native/theme-source.mjs +197 -0
- package/src/native/workspace-events.mjs +22 -1
|
@@ -6,19 +6,33 @@
|
|
|
6
6
|
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
|
-
import {
|
|
9
|
+
import { presentGhosttySurface } from "./ghostty-present.mjs";
|
|
10
|
+
import { sessionCardId } from "./session-cards.mjs";
|
|
10
11
|
import {
|
|
11
12
|
appIdForGhosttyFamily,
|
|
12
13
|
canonicalGhosttyTerminalKey,
|
|
13
14
|
normalizeGhosttySurfaceId,
|
|
14
15
|
terminalTitleSegment,
|
|
15
16
|
} from "./terminal-identity.mjs";
|
|
17
|
+
import { resolveHiddenSurfaceWindow } from "./window-placement.mjs";
|
|
16
18
|
|
|
17
19
|
const GHOSTTY_APP_IDS = new Set(["com.mitchellh.ghostty", "com.tryinget.ghosttysidequest"]);
|
|
18
20
|
const PI_SESSION_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
19
21
|
const SESSION_PRESENCE_SOURCE = "@tryinget/pi-little-helpers/session-presence";
|
|
20
22
|
const SESSION_TITLE_TOKEN_HEX_LENGTH = 32;
|
|
21
23
|
const LEGACY_SESSION_TITLE_TOKEN_HEX_LENGTH = 8;
|
|
24
|
+
const PRESENT_VERIFY_ATTEMPTS = 4;
|
|
25
|
+
const PRESENT_VERIFY_DELAY_MS = 120;
|
|
26
|
+
|
|
27
|
+
/** @typedef {import("./window-placement.mjs").PlacementOptions} PlacementOptions */
|
|
28
|
+
/** @typedef {import("./window-placement.mjs").WindowPlacement} WindowPlacement */
|
|
29
|
+
/**
|
|
30
|
+
* @typedef {PlacementOptions & {
|
|
31
|
+
* env?: NodeJS.ProcessEnv;
|
|
32
|
+
* readFileSync?: typeof fs.readFileSync;
|
|
33
|
+
* existsSync?: typeof fs.existsSync;
|
|
34
|
+
* }} ResolveOptions
|
|
35
|
+
*/
|
|
22
36
|
|
|
23
37
|
/**
|
|
24
38
|
* Read one bounded Niri JSON list. Polling callers treat command, timeout,
|
|
@@ -167,156 +181,210 @@ export function resolveExactGhosttyWindow(windows, sessionId, session = {}) {
|
|
|
167
181
|
}
|
|
168
182
|
|
|
169
183
|
/**
|
|
170
|
-
*
|
|
171
|
-
*
|
|
172
|
-
* fail closed so the renderer never highlights a guessed session.
|
|
184
|
+
* Place a session on its window. A visible title is exact proof; a bound surface whose tab is
|
|
185
|
+
* hidden behind another tab is placed through its Ghostty host process and remembered window.
|
|
173
186
|
* @param {Array<Record<string, unknown>>} windows
|
|
174
|
-
* @param {
|
|
175
|
-
* @param {
|
|
187
|
+
* @param {string} sessionId
|
|
188
|
+
* @param {Record<string, unknown>} [session]
|
|
189
|
+
* @param {PlacementOptions} [options]
|
|
190
|
+
* @returns {WindowPlacement | null}
|
|
176
191
|
*/
|
|
177
|
-
export function
|
|
178
|
-
const
|
|
179
|
-
if (
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
if (!sessionId) return false;
|
|
184
|
-
return resolveExactGhosttyWindow(windows, sessionId, session)?.id === focusedWindow.id;
|
|
192
|
+
export function resolveSessionWindow(windows, sessionId, session = {}, options = {}) {
|
|
193
|
+
const exact = resolveExactGhosttyWindow(windows, sessionId, session);
|
|
194
|
+
if (exact) return { window: exact, placement: "title" };
|
|
195
|
+
return resolveHiddenSurfaceWindow(windows, session, {
|
|
196
|
+
...options,
|
|
197
|
+
sessionToken: shortSessionId(sessionId),
|
|
185
198
|
});
|
|
186
|
-
return matches.length === 1 ? String(matches[0]?.sessionId ?? "") || null : null;
|
|
187
199
|
}
|
|
188
200
|
|
|
189
201
|
/**
|
|
190
|
-
*
|
|
191
|
-
*
|
|
192
|
-
*
|
|
202
|
+
* A tab occupied by a coding agent other than Pi carries no Pi identity, so it is placed by the
|
|
203
|
+
* exact title the agent put on its own terminal, and otherwise by host containment and window
|
|
204
|
+
* memory. A title claimed by more than one window is ambiguous and places nothing.
|
|
193
205
|
* @param {Array<Record<string, unknown>>} windows
|
|
194
|
-
* @param {Record<string, unknown>}
|
|
195
|
-
* @param {
|
|
196
|
-
* @
|
|
206
|
+
* @param {Record<string, unknown>} session
|
|
207
|
+
* @param {PlacementOptions} [options]
|
|
208
|
+
* @returns {WindowPlacement | null}
|
|
197
209
|
*/
|
|
198
|
-
export function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (
|
|
209
|
-
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const cardIdsByWindow = new Map();
|
|
213
|
-
for (const candidate of candidates) {
|
|
214
|
-
const ids = cardIdsByWindow.get(candidate.window.id) ?? new Set();
|
|
215
|
-
ids.add(candidate.cardId);
|
|
216
|
-
cardIdsByWindow.set(candidate.window.id, ids);
|
|
217
|
-
}
|
|
218
|
-
const allowedCardByWindow = new Map();
|
|
219
|
-
for (const [windowId, ids] of cardIdsByWindow) {
|
|
220
|
-
const terminalIds = [...ids].filter((cardId) => cardId.startsWith("terminal:"));
|
|
221
|
-
if (terminalIds.length === 1) allowedCardByWindow.set(windowId, terminalIds[0]);
|
|
222
|
-
else if (ids.size === 1) allowedCardByWindow.set(windowId, [...ids][0]);
|
|
210
|
+
export function resolveAgentSessionWindow(windows, session, options = {}) {
|
|
211
|
+
const titleSuffix = String(session.titleSuffix ?? "").trim();
|
|
212
|
+
if (titleSuffix) {
|
|
213
|
+
const expectedAppId = appIdForGhosttyFamily(String(session.terminalFamily ?? ""));
|
|
214
|
+
const matches = windows.filter(
|
|
215
|
+
(window) =>
|
|
216
|
+
Number.isInteger(window?.id) &&
|
|
217
|
+
String(window?.app_id ?? "") === expectedAppId &&
|
|
218
|
+
String(window?.title ?? "").endsWith(titleSuffix),
|
|
219
|
+
);
|
|
220
|
+
if (matches.length === 1) return { window: matches[0], placement: "title" };
|
|
221
|
+
if (matches.length > 1) return null;
|
|
223
222
|
}
|
|
223
|
+
return resolveHiddenSurfaceWindow(windows, session, options);
|
|
224
|
+
}
|
|
224
225
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
226
|
+
/**
|
|
227
|
+
* Whether a session could ever be placed by a title. Activation can only confirm that a presented
|
|
228
|
+
* tab became visible for these; for anything else the compositor offers no readable proof.
|
|
229
|
+
* @param {Record<string, unknown>} session
|
|
230
|
+
* @param {ResolveOptions} [options]
|
|
231
|
+
*/
|
|
232
|
+
export function canVerifyTabVisibility(session, options = {}) {
|
|
233
|
+
if (resolvePiSessionIdentity(session, options)) return true;
|
|
234
|
+
return String(session?.titleSuffix ?? "").trim().length > 0;
|
|
235
|
+
}
|
|
233
236
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
workspace,
|
|
248
|
-
sessions: projectedSessions,
|
|
249
|
-
focusedSessionId: focusedCard ? String(focusedCard.sessionId ?? "") || null : null,
|
|
250
|
-
focusedCardId: focusedCard ? String(focusedCard.cardId ?? "") || null : null,
|
|
251
|
-
};
|
|
237
|
+
/**
|
|
238
|
+
* Place any tracked session: a Pi session by its exact Pi identity, an agent tab by its own title.
|
|
239
|
+
* @param {Array<Record<string, unknown>>} windows
|
|
240
|
+
* @param {Record<string, unknown>} session
|
|
241
|
+
* @param {ResolveOptions} [options]
|
|
242
|
+
* @returns {WindowPlacement | null}
|
|
243
|
+
*/
|
|
244
|
+
export function placeSessionWindow(windows, session, options = {}) {
|
|
245
|
+
const piSessionId = resolvePiSessionIdentity(session, options);
|
|
246
|
+
if (piSessionId) return resolveSessionWindow(windows, piSessionId, session, options);
|
|
247
|
+
return canonicalGhosttyTerminalKey(session)
|
|
248
|
+
? resolveAgentSessionWindow(windows, session, options)
|
|
249
|
+
: null;
|
|
252
250
|
}
|
|
253
251
|
|
|
254
252
|
/**
|
|
255
|
-
*
|
|
253
|
+
* Resolve the one snapshot session whose exact Ghostty window currently owns
|
|
254
|
+
* compositor focus. Missing, ambiguous, non-Ghostty, and stale identities all
|
|
255
|
+
* fail closed so the renderer never highlights a guessed session.
|
|
256
256
|
* @param {Array<Record<string, unknown>>} windows
|
|
257
|
-
* @param {Array<Record<string, unknown>>} workspaces
|
|
258
257
|
* @param {Array<Record<string, unknown>>} sessions
|
|
259
258
|
* @param {{env?: NodeJS.ProcessEnv; readFileSync?: typeof fs.readFileSync; existsSync?: typeof fs.existsSync}} [options]
|
|
260
259
|
*/
|
|
261
|
-
export function
|
|
262
|
-
const
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
Number.isInteger(workspace?.id) &&
|
|
272
|
-
(typeof workspace?.name === "string" || Number.isInteger(workspace?.idx)),
|
|
273
|
-
);
|
|
274
|
-
return matches.length === 1 ? matches[0] : null;
|
|
260
|
+
export function resolveFocusedSnapshotSessionId(windows, sessions, options = {}) {
|
|
261
|
+
const focusedWindows = windows.filter((window) => window?.is_focused === true);
|
|
262
|
+
if (focusedWindows.length !== 1) return null;
|
|
263
|
+
const focusedWindow = focusedWindows[0];
|
|
264
|
+
const matches = sessions.filter((session) => {
|
|
265
|
+
const sessionId = resolvePiSessionIdentity(session, options);
|
|
266
|
+
if (!sessionId) return false;
|
|
267
|
+
return resolveExactGhosttyWindow(windows, sessionId, session)?.id === focusedWindow.id;
|
|
268
|
+
});
|
|
269
|
+
return matches.length === 1 ? String(matches[0]?.sessionId ?? "") || null : null;
|
|
275
270
|
}
|
|
276
271
|
|
|
277
272
|
/**
|
|
273
|
+
* Focus the exact terminal behind a card. A visible tab needs only compositor focus. A hidden tab
|
|
274
|
+
* is first presented inside its proven Ghostty host process, then its window is focused, and the
|
|
275
|
+
* result counts as success only once the window title proves the tab became visible.
|
|
278
276
|
* @param {string | Record<string, unknown>} session
|
|
279
277
|
* @param {(file: string, args: string[], options: object) => Promise<{stdout?: string}>} execFileAsync
|
|
280
278
|
* @param {NodeJS.ProcessEnv} [env]
|
|
281
|
-
* @param {
|
|
279
|
+
* @param {PlacementOptions & {
|
|
280
|
+
* readFileSync?: typeof fs.readFileSync;
|
|
281
|
+
* existsSync?: typeof fs.existsSync;
|
|
282
|
+
* presentSurface?: typeof presentGhosttySurface;
|
|
283
|
+
* sleep?: (milliseconds: number) => Promise<void>;
|
|
284
|
+
* }} [options]
|
|
285
|
+
* @returns {Promise<{ok: boolean; error?: string; windowId?: number; presented?: boolean; verified?: boolean}>}
|
|
282
286
|
*/
|
|
283
287
|
export async function focusNiriSession(session, execFileAsync, env = process.env, options = {}) {
|
|
284
288
|
if (!env.NIRI_SOCKET) return { ok: false, error: "Niri is not available; focus did nothing." };
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
});
|
|
290
|
-
if (!sessionId) {
|
|
289
|
+
const record =
|
|
290
|
+
session && typeof session === "object" ? session : { sessionId: String(session ?? "") };
|
|
291
|
+
const placementOptions = { ...options, env };
|
|
292
|
+
if (!resolvePiSessionIdentity(record, placementOptions) && !canonicalGhosttyTerminalKey(record)) {
|
|
291
293
|
return {
|
|
292
294
|
ok: false,
|
|
293
295
|
error: "Exact Pi identity is unavailable; reload that Pi tab and try again.",
|
|
294
296
|
};
|
|
295
297
|
}
|
|
298
|
+
const readWindows = async () => {
|
|
299
|
+
const result = await execFileAsync("niri", ["msg", "-j", "windows"], { env });
|
|
300
|
+
const windows = JSON.parse(String(result.stdout ?? "[]"));
|
|
301
|
+
if (!Array.isArray(windows)) throw new Error("Unexpected Niri window data");
|
|
302
|
+
return windows;
|
|
303
|
+
};
|
|
296
304
|
let windows;
|
|
297
305
|
try {
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
306
|
+
windows = await readWindows();
|
|
307
|
+
} catch (error) {
|
|
308
|
+
return {
|
|
309
|
+
ok: false,
|
|
310
|
+
error:
|
|
311
|
+
error instanceof Error && error.message === "Unexpected Niri window data"
|
|
312
|
+
? "Unexpected Niri window data; focus did nothing."
|
|
313
|
+
: "Could not inspect Niri windows; focus did nothing.",
|
|
314
|
+
};
|
|
305
315
|
}
|
|
306
|
-
const
|
|
307
|
-
|
|
308
|
-
if (!target) {
|
|
316
|
+
const placed = placeSessionWindow(windows, record, placementOptions);
|
|
317
|
+
if (!placed) {
|
|
309
318
|
return {
|
|
310
319
|
ok: false,
|
|
311
|
-
error: "
|
|
320
|
+
error: "That session did not resolve to exactly one Ghostty window; focus did nothing.",
|
|
312
321
|
};
|
|
313
322
|
}
|
|
323
|
+
const target = placed.window;
|
|
324
|
+
const targetId = Number(target.id);
|
|
325
|
+
const focusWindow = () =>
|
|
326
|
+
execFileAsync("niri", ["msg", "action", "focus-window", "--id", String(targetId)], { env });
|
|
327
|
+
if (placed.placement === "title") {
|
|
328
|
+
try {
|
|
329
|
+
await focusWindow();
|
|
330
|
+
return { ok: true, windowId: targetId };
|
|
331
|
+
} catch {
|
|
332
|
+
return { ok: false, error: "Niri rejected the exact focus request; focus did nothing." };
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
const presented = await (options.presentSurface ?? presentGhosttySurface)({
|
|
337
|
+
execFileAsync,
|
|
338
|
+
env,
|
|
339
|
+
hostPid: Number(target.pid),
|
|
340
|
+
terminalFamily: record.terminalFamily,
|
|
341
|
+
surfaceId: record.terminalSurfaceId,
|
|
342
|
+
});
|
|
314
343
|
try {
|
|
315
|
-
await
|
|
316
|
-
env,
|
|
317
|
-
});
|
|
318
|
-
return { ok: true, windowId: target.id };
|
|
344
|
+
await focusWindow();
|
|
319
345
|
} catch {
|
|
320
|
-
return {
|
|
346
|
+
return {
|
|
347
|
+
ok: false,
|
|
348
|
+
windowId: targetId,
|
|
349
|
+
presented: false,
|
|
350
|
+
error: presented.ok
|
|
351
|
+
? "Presented the hidden tab, but Niri rejected the window focus request."
|
|
352
|
+
: "Niri rejected the exact focus request; focus did nothing.",
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
if (!presented.ok) {
|
|
356
|
+
return {
|
|
357
|
+
ok: false,
|
|
358
|
+
windowId: targetId,
|
|
359
|
+
presented: false,
|
|
360
|
+
error: `Focused the Ghostty window, but could not present the hidden tab (${presented.error}); select it manually.`,
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
if (!canVerifyTabVisibility(record, placementOptions)) {
|
|
364
|
+
// Nothing this session writes is readable from the compositor, so the presented tab cannot be
|
|
365
|
+
// confirmed. The action succeeded; the outcome is reported without claiming proof.
|
|
366
|
+
return { ok: true, windowId: targetId, presented: true, verified: false };
|
|
367
|
+
}
|
|
368
|
+
const sleep =
|
|
369
|
+
options.sleep ??
|
|
370
|
+
((milliseconds) => new Promise((resolve) => setTimeout(resolve, milliseconds)));
|
|
371
|
+
for (let attempt = 0; attempt < PRESENT_VERIFY_ATTEMPTS; attempt += 1) {
|
|
372
|
+
if (attempt > 0) await sleep(PRESENT_VERIFY_DELAY_MS);
|
|
373
|
+
try {
|
|
374
|
+
const current = await readWindows();
|
|
375
|
+
const confirmed = placeSessionWindow(current, record, placementOptions);
|
|
376
|
+
if (confirmed?.placement === "title" && Number(confirmed.window.id) === targetId) {
|
|
377
|
+
return { ok: true, windowId: targetId, presented: true, verified: true };
|
|
378
|
+
}
|
|
379
|
+
} catch {
|
|
380
|
+
break;
|
|
381
|
+
}
|
|
321
382
|
}
|
|
383
|
+
return {
|
|
384
|
+
ok: false,
|
|
385
|
+
windowId: targetId,
|
|
386
|
+
presented: false,
|
|
387
|
+
error:
|
|
388
|
+
"Focused the Ghostty window, but the hidden tab did not become visible; select it manually.",
|
|
389
|
+
};
|
|
322
390
|
}
|
|
@@ -54,7 +54,7 @@ export function comparePublisherRepresentatives(left, right) {
|
|
|
54
54
|
/**
|
|
55
55
|
* @param {SessionLike[]} [sessions]
|
|
56
56
|
* @param {Set<string> | null} [allowedCardIds]
|
|
57
|
-
* @returns {Array<SessionLike & {cardId: string; publisherCount: number; publisherIds: string[]; publisherRecordKeys: string[]}>}
|
|
57
|
+
* @returns {Array<SessionLike & {cardId: string; publisherCount: number; publisherIds: string[]; publisherRecordKeys: string[]; publisherSessionIds: string[]}>}
|
|
58
58
|
*/
|
|
59
59
|
export function projectSessionCards(sessions = [], allowedCardIds = null) {
|
|
60
60
|
/** @type {Map<string, SessionLike[]>} */
|
|
@@ -67,7 +67,7 @@ export function projectSessionCards(sessions = [], allowedCardIds = null) {
|
|
|
67
67
|
groups.set(cardId, group);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
/** @type {Array<SessionLike & {cardId: string; publisherCount: number; publisherIds: string[]; publisherRecordKeys: string[]}>} */
|
|
70
|
+
/** @type {Array<SessionLike & {cardId: string; publisherCount: number; publisherIds: string[]; publisherRecordKeys: string[]; publisherSessionIds: string[]}>} */
|
|
71
71
|
const cards = [];
|
|
72
72
|
for (const [cardId, group] of groups) {
|
|
73
73
|
const ordered = [...group].sort(comparePublisherRepresentatives);
|
|
@@ -79,6 +79,9 @@ export function projectSessionCards(sessions = [], allowedCardIds = null) {
|
|
|
79
79
|
publisherCount: group.length,
|
|
80
80
|
publisherIds: group.map((session) => String(session.publisherId ?? "")).filter(Boolean),
|
|
81
81
|
publisherRecordKeys: group.map(sessionRecordKey),
|
|
82
|
+
publisherSessionIds: [
|
|
83
|
+
...new Set(group.map((session) => String(session.sessionId ?? "")).filter(Boolean)),
|
|
84
|
+
],
|
|
82
85
|
});
|
|
83
86
|
}
|
|
84
87
|
return cards;
|
|
@@ -26,6 +26,57 @@ export function formatBrokerRuntimeStatus(result) {
|
|
|
26
26
|
if (typeof runtime.windowVisible === "boolean") {
|
|
27
27
|
lines.push(`surface visible: ${runtime.windowVisible ? "yes" : "no"}`);
|
|
28
28
|
}
|
|
29
|
+
if (typeof runtime.rendererCardCount === "number") {
|
|
30
|
+
const hidden =
|
|
31
|
+
typeof runtime.rendererHiddenTabCardCount === "number"
|
|
32
|
+
? ` (${runtime.rendererHiddenTabCardCount} hidden tabs)`
|
|
33
|
+
: "";
|
|
34
|
+
lines.push(`cards on focused workspace: ${runtime.rendererCardCount}${hidden}`);
|
|
35
|
+
}
|
|
36
|
+
if (typeof runtime.surfaceBindingCount === "number") {
|
|
37
|
+
lines.push(`remembered tab windows: ${runtime.surfaceBindingCount}`);
|
|
38
|
+
}
|
|
39
|
+
if (typeof runtime.agentTabCount === "number") {
|
|
40
|
+
lines.push(`agent tabs discovered: ${runtime.agentTabCount}`);
|
|
41
|
+
}
|
|
42
|
+
if (runtime.themeName) {
|
|
43
|
+
lines.push(`theme: ${runtime.themeName} (${runtime.themeScheme ?? "unknown"})`);
|
|
44
|
+
}
|
|
45
|
+
if (runtime.agentScanError) {
|
|
46
|
+
lines.push(`agent discovery error: ${runtime.agentScanError}`);
|
|
47
|
+
}
|
|
48
|
+
if (typeof runtime.heightRepairState === "string") {
|
|
49
|
+
const repaired = Number(runtime.heightRepairCount ?? 0);
|
|
50
|
+
lines.push(
|
|
51
|
+
`window height repair: ${runtime.heightRepairState}${repaired > 0 ? ` (${repaired} restored to automatic)` : ""}`,
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
if (typeof runtime.tabInventoryState === "string") {
|
|
55
|
+
const detail =
|
|
56
|
+
runtime.tabInventoryState === "ready"
|
|
57
|
+
? ` (${runtime.tabInventoryFrameCount ?? 0} Ghostty windows, ${runtime.tabInventoryTabCount ?? 0} tabs)`
|
|
58
|
+
: runtime.tabInventoryDetail
|
|
59
|
+
? ` (${runtime.tabInventoryDetail})`
|
|
60
|
+
: "";
|
|
61
|
+
lines.push(`tab inventory: ${runtime.tabInventoryState}${detail}`);
|
|
62
|
+
}
|
|
63
|
+
if (typeof runtime.akTaskState === "string") {
|
|
64
|
+
if (runtime.akTaskState === "ok") {
|
|
65
|
+
const orphaned = Number(runtime.akTaskOrphanCount ?? 0);
|
|
66
|
+
lines.push(
|
|
67
|
+
`AK task refs: ok (${runtime.akTaskClaimCount ?? 0} live claims, ${runtime.akTaskDeferredCount ?? 0} deferred${orphaned > 0 ? `, ${orphaned} orphaned claims` : ""})`,
|
|
68
|
+
);
|
|
69
|
+
} else if (runtime.akTaskState !== "disabled") {
|
|
70
|
+
lines.push(
|
|
71
|
+
`AK task refs: ${runtime.akTaskState}${runtime.akTaskError ? ` (${runtime.akTaskError})` : ""}`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
if (typeof runtime.unplacedSurfaceCount === "number" && runtime.unplacedSurfaceCount > 0) {
|
|
76
|
+
lines.push(
|
|
77
|
+
`unplaced hidden tabs: ${runtime.unplacedSurfaceCount} (placed once the tab inventory or a visible title reveals their window)`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
29
80
|
if (runtime.error) {
|
|
30
81
|
lines.push(`error: ${runtime.error}`);
|
|
31
82
|
}
|