pi-gentic 0.1.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 +499 -0
- package/dist/commands.d.ts +93 -0
- package/dist/commands.js +422 -0
- package/dist/config.d.ts +27 -0
- package/dist/config.js +407 -0
- package/dist/core.d.ts +19 -0
- package/dist/core.js +125 -0
- package/dist/extension.d.ts +2 -0
- package/dist/extension.js +476 -0
- package/dist/orchestrator.d.ts +277 -0
- package/dist/orchestrator.js +633 -0
- package/dist/policy.d.ts +43 -0
- package/dist/policy.js +136 -0
- package/dist/prompt.d.ts +12 -0
- package/dist/prompt.js +226 -0
- package/dist/runs.d.ts +99 -0
- package/dist/runs.js +540 -0
- package/dist/runtime.d.ts +127 -0
- package/dist/runtime.js +360 -0
- package/dist/sessions.d.ts +56 -0
- package/dist/sessions.js +487 -0
- package/dist/ui.d.ts +108 -0
- package/dist/ui.js +957 -0
- package/dist/worktrees.d.ts +1 -0
- package/dist/worktrees.js +86 -0
- package/docs/assets/error-card.png +0 -0
- package/docs/assets/load-agent.png +0 -0
- package/docs/assets/orchestration-tree.png +0 -0
- package/docs/assets/send-background.png +0 -0
- package/docs/assets/send-foreground.png +0 -0
- package/package.json +58 -0
package/dist/sessions.js
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Session identity, summaries, and tree discovery.
|
|
3
|
+
*
|
|
4
|
+
* The rest of pi-gentic can ask for sessions by human-friendly ids while this
|
|
5
|
+
* module handles paths, short ids, parent links, and runtime overlays.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, openSync, readSync, closeSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
8
|
+
import path from "node:path";
|
|
9
|
+
import { shortSessionId } from "./core.js";
|
|
10
|
+
import { getActiveState } from "./policy.js";
|
|
11
|
+
import { findRuntimeSession, listRuntimeSessions, livePath, registerLiveRuntime, } from "./runtime.js";
|
|
12
|
+
/** Resolves a full id, short id, prefix, substring, or path to one session. */
|
|
13
|
+
export function resolveSessionReference(sessions, reference) {
|
|
14
|
+
if (!reference)
|
|
15
|
+
throw new Error("sessionId is required.");
|
|
16
|
+
const query = String(reference).toLowerCase();
|
|
17
|
+
const matches = sessions.filter((session) => sessionKeys(session).some((key) => String(key).toLowerCase() === query ||
|
|
18
|
+
String(key).toLowerCase().includes(query)));
|
|
19
|
+
const unique = uniqueBy(matches, (session) => session.path ?? session.id);
|
|
20
|
+
if (unique.length === 0)
|
|
21
|
+
throw new Error(`No session matches "${reference}".`);
|
|
22
|
+
if (unique.length > 1)
|
|
23
|
+
throw new Error(`Ambiguous session reference "${reference}" matches ${unique.length} sessions.`);
|
|
24
|
+
return unique[0];
|
|
25
|
+
}
|
|
26
|
+
const persistedSummaryCache = new Map();
|
|
27
|
+
const persistedSessionListCache = new Map();
|
|
28
|
+
export function listSessionSummariesFast(sessionDir) {
|
|
29
|
+
if (!sessionDir || !existsSync(sessionDir))
|
|
30
|
+
return [];
|
|
31
|
+
return readdirSync(sessionDir)
|
|
32
|
+
.filter((name) => name.endsWith(".jsonl"))
|
|
33
|
+
.map((name) => fastSessionSummary(path.join(sessionDir, name)))
|
|
34
|
+
.filter(Boolean)
|
|
35
|
+
.sort((a, b) => modifiedTime(b) - modifiedTime(a));
|
|
36
|
+
}
|
|
37
|
+
function fastSessionSummary(filePath) {
|
|
38
|
+
try {
|
|
39
|
+
const stat = statSync(filePath);
|
|
40
|
+
const lines = readFileHead(filePath).split(/\r?\n/);
|
|
41
|
+
const header = parseLine(lines[0]);
|
|
42
|
+
if (header?.type !== "session")
|
|
43
|
+
return undefined;
|
|
44
|
+
const details = {
|
|
45
|
+
id: header.id,
|
|
46
|
+
path: filePath,
|
|
47
|
+
cwd: header.cwd,
|
|
48
|
+
parentSessionPath: header.parentSession,
|
|
49
|
+
created: header.timestamp,
|
|
50
|
+
modified: stat.mtime,
|
|
51
|
+
firstMessage: "(no messages)",
|
|
52
|
+
};
|
|
53
|
+
for (const line of lines.slice(1)) {
|
|
54
|
+
const entry = parseLine(line);
|
|
55
|
+
if (!entry)
|
|
56
|
+
continue;
|
|
57
|
+
if (entry.type === "session_info" && typeof entry.name === "string")
|
|
58
|
+
details.name = entry.name.trim() || undefined;
|
|
59
|
+
if (entry.type === "message" && entry.message?.role === "user") {
|
|
60
|
+
const text = extractText(entry.message.content);
|
|
61
|
+
if (text) {
|
|
62
|
+
details.firstMessage = cleanSessionMessage(text);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return details;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return undefined;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function readFileHead(filePath, bytes = 64 * 1024) {
|
|
74
|
+
const fd = openSync(filePath, "r");
|
|
75
|
+
try {
|
|
76
|
+
const buffer = Buffer.alloc(bytes);
|
|
77
|
+
const read = readSync(fd, buffer, 0, bytes, 0);
|
|
78
|
+
return buffer.subarray(0, read).toString("utf8");
|
|
79
|
+
}
|
|
80
|
+
finally {
|
|
81
|
+
closeSync(fd);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function parseLine(line) {
|
|
85
|
+
try {
|
|
86
|
+
return line?.trim() ? JSON.parse(line) : undefined;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return undefined;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
export async function cachedPersistedSessions(key, load, maxAgeMs = 15_000) {
|
|
93
|
+
const now = Date.now();
|
|
94
|
+
const cached = persistedSessionListCache.get(key);
|
|
95
|
+
if (cached?.sessions && now - cached.updatedAt < maxAgeMs)
|
|
96
|
+
return cached.sessions;
|
|
97
|
+
if (cached?.promise)
|
|
98
|
+
return cached.promise;
|
|
99
|
+
const promise = load()
|
|
100
|
+
.then((sessions) => {
|
|
101
|
+
persistedSessionListCache.set(key, {
|
|
102
|
+
sessions,
|
|
103
|
+
updatedAt: Date.now(),
|
|
104
|
+
promise: undefined,
|
|
105
|
+
});
|
|
106
|
+
return sessions;
|
|
107
|
+
})
|
|
108
|
+
.catch((error) => {
|
|
109
|
+
if (cached?.sessions)
|
|
110
|
+
return cached.sessions;
|
|
111
|
+
persistedSessionListCache.delete(key);
|
|
112
|
+
throw error;
|
|
113
|
+
});
|
|
114
|
+
persistedSessionListCache.set(key, {
|
|
115
|
+
sessions: cached?.sessions,
|
|
116
|
+
updatedAt: cached?.updatedAt ?? 0,
|
|
117
|
+
promise,
|
|
118
|
+
});
|
|
119
|
+
return promise;
|
|
120
|
+
}
|
|
121
|
+
export function warmPersistedSessions(key, load) {
|
|
122
|
+
void cachedPersistedSessions(key, load).catch(() => undefined);
|
|
123
|
+
}
|
|
124
|
+
export function summarizeSession(session, options = {}) {
|
|
125
|
+
const persisted = options.enrich === true
|
|
126
|
+
? readPersistedSessionSummary(session.path, session.modified)
|
|
127
|
+
: cachedPersistedSessionSummary(session.path, session.modified);
|
|
128
|
+
return {
|
|
129
|
+
id: session.id,
|
|
130
|
+
sessionId: session.id,
|
|
131
|
+
shortId: shortSessionId(session.id),
|
|
132
|
+
path: session.path,
|
|
133
|
+
parentSessionPath: session.parentSessionPath,
|
|
134
|
+
name: session.name,
|
|
135
|
+
firstMessage: persisted.firstUserMessage ?? session.firstMessage,
|
|
136
|
+
lastMessage: persisted.lastUserMessage ?? session.name ?? session.firstMessage,
|
|
137
|
+
modified: session.modified,
|
|
138
|
+
agentName: persisted.agentName,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export function enrichSessionSummary(session) {
|
|
142
|
+
return session?.path
|
|
143
|
+
? { ...session, ...summarizeSession(session, { enrich: true }) }
|
|
144
|
+
: session;
|
|
145
|
+
}
|
|
146
|
+
export function enrichSessionSummaries(sessions, limit = sessions.length) {
|
|
147
|
+
return sessions.map((session, index) => index < limit ? enrichSessionSummary(session) : session);
|
|
148
|
+
}
|
|
149
|
+
/** Orders sessions so every child appears directly under its parent. */
|
|
150
|
+
export function orderSessionTree(sessions) {
|
|
151
|
+
const byKey = sessionKeyMap(sessions);
|
|
152
|
+
const children = new Map();
|
|
153
|
+
const roots = [];
|
|
154
|
+
for (const session of sessions) {
|
|
155
|
+
const parent = parentSession(session, byKey);
|
|
156
|
+
if (!parent)
|
|
157
|
+
roots.push(session);
|
|
158
|
+
else {
|
|
159
|
+
const parentKey = primarySessionKey(parent);
|
|
160
|
+
children.set(parentKey, [...(children.get(parentKey) ?? []), session]);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const ordered = [];
|
|
164
|
+
const subtreeModified = (session) => Math.max(modifiedTime(session), ...(children.get(primarySessionKey(session)) ?? []).map(subtreeModified));
|
|
165
|
+
const sortByTreeActivity = (items) => sortSessions(items, subtreeModified);
|
|
166
|
+
const visit = (session, depth = 0, siblingIndex = 0, siblingCount = 1) => {
|
|
167
|
+
const nested = sortByTreeActivity(children.get(primarySessionKey(session)) ?? []);
|
|
168
|
+
ordered.push({
|
|
169
|
+
...session,
|
|
170
|
+
depth,
|
|
171
|
+
isLast: siblingIndex === siblingCount - 1,
|
|
172
|
+
});
|
|
173
|
+
nested.forEach((child, index) => visit(child, depth + 1, index, nested.length));
|
|
174
|
+
};
|
|
175
|
+
sortByTreeActivity(roots).forEach((root, index, sortedRoots) => visit(root, 0, index, sortedRoots.length));
|
|
176
|
+
return ordered;
|
|
177
|
+
}
|
|
178
|
+
export function treeSwitchPath(session) {
|
|
179
|
+
return session.running === true
|
|
180
|
+
? (session.livePath ?? session.path)
|
|
181
|
+
: (session.path ?? session.livePath);
|
|
182
|
+
}
|
|
183
|
+
export function sessionDiscoveryScope(sessions, currentSession, options = {}) {
|
|
184
|
+
return options.all === true
|
|
185
|
+
? sessions
|
|
186
|
+
: filterSessionNeighborhood(sessions, currentSession, options);
|
|
187
|
+
}
|
|
188
|
+
export function buildSessionTree(currentSession, persistedSessions, runtimeSessions = listRuntimeSessions(), options = {}) {
|
|
189
|
+
return orderSessionTree(mergeSessionSummaries([
|
|
190
|
+
currentSession,
|
|
191
|
+
...persistedSessions.map((session) => summarizeSession(session, options)),
|
|
192
|
+
...runtimeSessions.map(runtimeSessionSummary),
|
|
193
|
+
]));
|
|
194
|
+
}
|
|
195
|
+
export function sessionCompletionScope(sessions, currentSession, options = {}) {
|
|
196
|
+
const scoped = assignTreeDepths(sessionDiscoveryScope(sessions, currentSession ?? {}, options)).map(withRuntimeState);
|
|
197
|
+
return orderSessionCompletions(scoped, currentSession);
|
|
198
|
+
}
|
|
199
|
+
export function findSessionSummary(sessions, identity = {}) {
|
|
200
|
+
const keys = sessionKeys(identity).filter(Boolean);
|
|
201
|
+
if (keys.length === 0)
|
|
202
|
+
return undefined;
|
|
203
|
+
return sessions.find((session) => sessionKeys(session).some((key) => keys.includes(key)));
|
|
204
|
+
}
|
|
205
|
+
/** Keeps the current session plus nearby siblings and branch relatives. */
|
|
206
|
+
export function filterSessionNeighborhood(sessions, currentSession, { rx = 0, ry = 0 } = {}) {
|
|
207
|
+
if (!currentSession)
|
|
208
|
+
return sessions;
|
|
209
|
+
const currentKey = primarySessionKey(currentSession);
|
|
210
|
+
const currentIndex = sessions.findIndex((session) => sessionKeys(session).includes(currentKey));
|
|
211
|
+
const current = currentIndex === -1 ? undefined : sessions[currentIndex];
|
|
212
|
+
if (!current)
|
|
213
|
+
return sessions;
|
|
214
|
+
const byKey = sessionKeyMap(sessions);
|
|
215
|
+
const siblings = siblingGroups(sessions, byKey);
|
|
216
|
+
const currentParentKey = parentSessionKey(current, byKey);
|
|
217
|
+
const currentSiblings = siblings.get(siblingGroupKey(current, currentParentKey)) ?? [];
|
|
218
|
+
const currentSiblingIndex = currentSiblings.indexOf(current);
|
|
219
|
+
return sessions.filter((session) => {
|
|
220
|
+
if (session === current)
|
|
221
|
+
return true;
|
|
222
|
+
const verticalDistance = Math.abs(Number(session.depth ?? 0) - Number(current.depth ?? 0));
|
|
223
|
+
if (verticalDistance > ry)
|
|
224
|
+
return false;
|
|
225
|
+
if (isAncestorOrDescendant(session, current, byKey))
|
|
226
|
+
return true;
|
|
227
|
+
const parentKey = parentSessionKey(session, byKey);
|
|
228
|
+
if (parentKey !== currentParentKey)
|
|
229
|
+
return false;
|
|
230
|
+
const group = siblings.get(siblingGroupKey(session, parentKey)) ?? [];
|
|
231
|
+
const siblingIndex = group.indexOf(session);
|
|
232
|
+
return (currentSiblingIndex !== -1 &&
|
|
233
|
+
siblingIndex !== -1 &&
|
|
234
|
+
Math.abs(siblingIndex - currentSiblingIndex) <= rx);
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
export function orderSessionCompletions(sessions, currentSession) {
|
|
238
|
+
if (!currentSession)
|
|
239
|
+
return sortSessions(sessions, modifiedTime);
|
|
240
|
+
const byKey = sessionKeyMap(sessions);
|
|
241
|
+
const currentKeys = sessionKeys(currentSession);
|
|
242
|
+
const rank = (session) => {
|
|
243
|
+
const parentKey = parentSessionKey(session, byKey);
|
|
244
|
+
return parentKey && currentKeys.includes(parentKey) ? 0 : 1;
|
|
245
|
+
};
|
|
246
|
+
return [...sessions].sort((a, b) => rank(a) - rank(b) ||
|
|
247
|
+
modifiedTime(b) - modifiedTime(a) ||
|
|
248
|
+
sessionLabel(a).localeCompare(sessionLabel(b)));
|
|
249
|
+
}
|
|
250
|
+
export function mergeSessionSummaries(sessions) {
|
|
251
|
+
const byKey = new Map();
|
|
252
|
+
for (const session of sessions.filter(Boolean)) {
|
|
253
|
+
const key = session.path ?? session.sessionId ?? session.id;
|
|
254
|
+
if (!key)
|
|
255
|
+
continue;
|
|
256
|
+
byKey.set(key, { ...(byKey.get(key) ?? {}), ...session });
|
|
257
|
+
}
|
|
258
|
+
return [...byKey.values()];
|
|
259
|
+
}
|
|
260
|
+
export function currentSessionSummary(ctx) {
|
|
261
|
+
const sessionId = ctx.sessionManager.getSessionId?.();
|
|
262
|
+
const path = ctx.sessionManager.getSessionFile?.();
|
|
263
|
+
if (!sessionId && !path)
|
|
264
|
+
return undefined;
|
|
265
|
+
const state = getActiveState(ctx.sessionManager);
|
|
266
|
+
return {
|
|
267
|
+
id: sessionId,
|
|
268
|
+
sessionId,
|
|
269
|
+
shortId: sessionId ? shortSessionId(sessionId) : undefined,
|
|
270
|
+
path,
|
|
271
|
+
parentSessionPath: ctx.sessionManager.getHeader?.()?.parentSession,
|
|
272
|
+
agentName: state.agentName,
|
|
273
|
+
lastMessage: ctx.sessionManager.getSessionName?.() ?? "Current session",
|
|
274
|
+
modified: new Date().toISOString(),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
export function runtimeSessionSummary(runtime) {
|
|
278
|
+
const sessionId = runtime.session.sessionManager.getSessionId();
|
|
279
|
+
return {
|
|
280
|
+
id: sessionId,
|
|
281
|
+
sessionId,
|
|
282
|
+
shortId: shortSessionId(sessionId),
|
|
283
|
+
path: runtime.session.sessionManager.getSessionFile(),
|
|
284
|
+
parentSessionPath: runtime.parentSessionPath,
|
|
285
|
+
agentName: runtime.agentName,
|
|
286
|
+
lastMessage: runtime.lastMessage ??
|
|
287
|
+
(runtime.agentName ? `Message to ${runtime.agentName}` : "Child session"),
|
|
288
|
+
modified: runtime.lastActivityAt ?? runtime.createdAt ?? new Date().toISOString(),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/** Adds live runtime details without changing persisted session summaries. */
|
|
292
|
+
export function withRuntimeState(session) {
|
|
293
|
+
const runtime = findRuntimeSession((item) => item.session.sessionManager.getSessionId() === session.sessionId);
|
|
294
|
+
if (!runtime)
|
|
295
|
+
return session;
|
|
296
|
+
const running = runtime.session?.isStreaming === true;
|
|
297
|
+
const live = running && runtime.runtimeHost
|
|
298
|
+
? { livePath: livePath(runtime.session.sessionManager.getSessionId()) }
|
|
299
|
+
: {};
|
|
300
|
+
if (running && runtime.runtimeHost)
|
|
301
|
+
registerLiveRuntime(runtime.runtimeHost, { agentName: runtime.agentName });
|
|
302
|
+
const lastActivityAt = runtime.lastActivityAt ?? runtime.createdAt;
|
|
303
|
+
const lastActivityTime = lastActivityAt
|
|
304
|
+
? new Date(lastActivityAt).getTime()
|
|
305
|
+
: undefined;
|
|
306
|
+
return {
|
|
307
|
+
...session,
|
|
308
|
+
...live,
|
|
309
|
+
running,
|
|
310
|
+
lastActivityAt,
|
|
311
|
+
inactiveMs: lastActivityTime && Number.isFinite(lastActivityTime)
|
|
312
|
+
? Date.now() - lastActivityTime
|
|
313
|
+
: session.inactiveMs,
|
|
314
|
+
agentName: runtime.agentName ?? session.agentName,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
export function assignTreeDepths(sessions) {
|
|
318
|
+
return sessions.map((session) => ({
|
|
319
|
+
...session,
|
|
320
|
+
depth: Math.max(0, Number(session.depth ?? 0)),
|
|
321
|
+
inactiveMs: session.modified
|
|
322
|
+
? Date.now() - new Date(session.modified).getTime()
|
|
323
|
+
: 0,
|
|
324
|
+
}));
|
|
325
|
+
}
|
|
326
|
+
function cachedPersistedSessionSummary(filePath, modified) {
|
|
327
|
+
if (!filePath)
|
|
328
|
+
return {};
|
|
329
|
+
try {
|
|
330
|
+
const cacheKey = `${filePath}:${modified ?? statSync(filePath).mtimeMs}`;
|
|
331
|
+
return persistedSummaryCache.get(cacheKey) ?? {};
|
|
332
|
+
}
|
|
333
|
+
catch {
|
|
334
|
+
return {};
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
function readPersistedSessionSummary(filePath, modified) {
|
|
338
|
+
if (!filePath)
|
|
339
|
+
return {};
|
|
340
|
+
try {
|
|
341
|
+
const cacheKey = `${filePath}:${modified ?? statSync(filePath).mtimeMs}`;
|
|
342
|
+
const cached = persistedSummaryCache.get(cacheKey);
|
|
343
|
+
if (cached)
|
|
344
|
+
return cached;
|
|
345
|
+
const summary = {};
|
|
346
|
+
for (const line of readFileSync(filePath, "utf8").split(/\r?\n/)) {
|
|
347
|
+
if (!line.trim())
|
|
348
|
+
continue;
|
|
349
|
+
const entry = JSON.parse(line);
|
|
350
|
+
if (entry.type === "custom" && entry.customType === "pi-gentic:state") {
|
|
351
|
+
summary.agentName =
|
|
352
|
+
typeof entry.data?.agentName === "string" && entry.data.agentName
|
|
353
|
+
? entry.data.agentName
|
|
354
|
+
: undefined;
|
|
355
|
+
}
|
|
356
|
+
if (entry.type === "message" && entry.message?.role === "user") {
|
|
357
|
+
const text = extractText(entry.message.content);
|
|
358
|
+
if (text) {
|
|
359
|
+
summary.firstUserMessage ??= cleanSessionMessage(text);
|
|
360
|
+
summary.lastUserMessage = cleanSessionMessage(text);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
persistedSummaryCache.set(cacheKey, summary);
|
|
365
|
+
prunePersistedSummaryCache();
|
|
366
|
+
return summary;
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
return {};
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
function prunePersistedSummaryCache(maxEntries = 500) {
|
|
373
|
+
if (persistedSummaryCache.size <= maxEntries)
|
|
374
|
+
return;
|
|
375
|
+
for (const key of persistedSummaryCache.keys()) {
|
|
376
|
+
persistedSummaryCache.delete(key);
|
|
377
|
+
if (persistedSummaryCache.size <= maxEntries)
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function extractText(content) {
|
|
382
|
+
if (typeof content === "string")
|
|
383
|
+
return content;
|
|
384
|
+
if (!Array.isArray(content))
|
|
385
|
+
return "";
|
|
386
|
+
return content
|
|
387
|
+
.filter((part) => part?.type === "text")
|
|
388
|
+
.map((part) => part.text)
|
|
389
|
+
.filter(Boolean)
|
|
390
|
+
.join("\n");
|
|
391
|
+
}
|
|
392
|
+
function cleanSessionMessage(text) {
|
|
393
|
+
const match = String(text).match(/^Message from(?: \[[^\]]+\])? agent from session [^:]+:\n([\s\S]*?)(?:\nOnly your final answer will be returned\.)?$/);
|
|
394
|
+
return (match?.[1] ?? text).trim();
|
|
395
|
+
}
|
|
396
|
+
function isAncestorOrDescendant(a, b, byKey) {
|
|
397
|
+
return isAncestor(a, b, byKey) || isAncestor(b, a, byKey);
|
|
398
|
+
}
|
|
399
|
+
function isAncestor(ancestor, session, byKey) {
|
|
400
|
+
const ancestorKeys = new Set(sessionKeys(ancestor));
|
|
401
|
+
let current = session;
|
|
402
|
+
for (let guard = 0; guard < 100; guard++) {
|
|
403
|
+
const parent = parentSession(current, byKey);
|
|
404
|
+
if (!parent)
|
|
405
|
+
return false;
|
|
406
|
+
if (sessionKeys(parent).some((key) => ancestorKeys.has(key)))
|
|
407
|
+
return true;
|
|
408
|
+
current = parent;
|
|
409
|
+
}
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
function sessionKeyMap(sessions) {
|
|
413
|
+
const byKey = new Map();
|
|
414
|
+
for (const session of sessions)
|
|
415
|
+
for (const key of sessionKeys(session))
|
|
416
|
+
byKey.set(key, session);
|
|
417
|
+
return byKey;
|
|
418
|
+
}
|
|
419
|
+
function siblingGroups(sessions, byKey) {
|
|
420
|
+
const groups = new Map();
|
|
421
|
+
for (const session of sessions) {
|
|
422
|
+
const key = siblingGroupKey(session, parentSessionKey(session, byKey));
|
|
423
|
+
groups.set(key, [...(groups.get(key) ?? []), session]);
|
|
424
|
+
}
|
|
425
|
+
return groups;
|
|
426
|
+
}
|
|
427
|
+
function siblingGroupKey(session, parentKey) {
|
|
428
|
+
return `${parentKey ?? "root"}:${Number(session.depth ?? 0)}`;
|
|
429
|
+
}
|
|
430
|
+
function parentSession(session, byKey) {
|
|
431
|
+
const key = parentSessionKey(session, byKey);
|
|
432
|
+
return key ? byKey.get(key) : undefined;
|
|
433
|
+
}
|
|
434
|
+
function parentSessionKey(session, byKey) {
|
|
435
|
+
return parentKeys(session).find((key) => byKey.has(key));
|
|
436
|
+
}
|
|
437
|
+
function primarySessionKey(session) {
|
|
438
|
+
return (session.path ??
|
|
439
|
+
session.sessionId ??
|
|
440
|
+
session.id ??
|
|
441
|
+
shortSessionId(session.sessionId ?? session.id));
|
|
442
|
+
}
|
|
443
|
+
function sessionLabel(session) {
|
|
444
|
+
return String(session.lastMessage ?? session.firstMessage ?? session.name ?? session.id ?? "");
|
|
445
|
+
}
|
|
446
|
+
function sessionKeys(session) {
|
|
447
|
+
return [
|
|
448
|
+
session.path,
|
|
449
|
+
session.sessionId,
|
|
450
|
+
session.id,
|
|
451
|
+
shortSessionId(session.sessionId ?? session.id),
|
|
452
|
+
idFromPath(session.path),
|
|
453
|
+
].filter(Boolean);
|
|
454
|
+
}
|
|
455
|
+
function parentKeys(session) {
|
|
456
|
+
return [
|
|
457
|
+
session.parentSessionPath,
|
|
458
|
+
session.parentSessionId,
|
|
459
|
+
idFromPath(session.parentSessionPath),
|
|
460
|
+
shortSessionId(session.parentSessionId),
|
|
461
|
+
].filter(Boolean);
|
|
462
|
+
}
|
|
463
|
+
function idFromPath(value) {
|
|
464
|
+
const match = String(value ?? "").match(/([0-9a-f]{8,}(?:-[0-9a-f-]+)?)\.jsonl$/i);
|
|
465
|
+
return match?.[1];
|
|
466
|
+
}
|
|
467
|
+
function sortSessions(sessions, score = modifiedTime) {
|
|
468
|
+
return [...sessions].sort((a, b) => score(b) - score(a) ||
|
|
469
|
+
String(b.modified ?? "").localeCompare(String(a.modified ?? "")) ||
|
|
470
|
+
String(b.path).localeCompare(String(a.path)));
|
|
471
|
+
}
|
|
472
|
+
function modifiedTime(session) {
|
|
473
|
+
const time = new Date(session.modified ?? 0).getTime();
|
|
474
|
+
return Number.isFinite(time) ? time : 0;
|
|
475
|
+
}
|
|
476
|
+
function uniqueBy(items, keyFn) {
|
|
477
|
+
const seen = new Set();
|
|
478
|
+
const result = [];
|
|
479
|
+
for (const item of items) {
|
|
480
|
+
const key = keyFn(item);
|
|
481
|
+
if (seen.has(key))
|
|
482
|
+
continue;
|
|
483
|
+
seen.add(key);
|
|
484
|
+
result.push(item);
|
|
485
|
+
}
|
|
486
|
+
return result;
|
|
487
|
+
}
|
package/dist/ui.d.ts
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
export declare function liveCardKey(details: any): any;
|
|
2
|
+
export declare function setLiveCardDetails(details: AnyRecord, options?: AnyRecord): any;
|
|
3
|
+
export declare function getLiveCardDetails(details: any): any;
|
|
4
|
+
export declare function clearLiveCardDetails(details: any): void;
|
|
5
|
+
export declare function center(text: any, width: any): string;
|
|
6
|
+
export declare function joinWithRight(left: any, right: any, width: any): string;
|
|
7
|
+
export declare function joinWithMiddle(left: any, middle: any, right: any, width: any): string;
|
|
8
|
+
export declare function normalizeInline(text: any): string;
|
|
9
|
+
export declare function wrap(text: any, width: any): any[];
|
|
10
|
+
export declare function fit(text: any, width: any): string;
|
|
11
|
+
/** Measures terminal cell width after stripping ANSI control sequences. */
|
|
12
|
+
export declare function visibleLength(text: any): number;
|
|
13
|
+
export declare const AGENT_WIDGET_KEY = "pi-gentic-agent";
|
|
14
|
+
export declare const CARD_MESSAGE_TYPE = "pi-gentic:card";
|
|
15
|
+
export declare const LIVE_REFRESH_WIDGET_KEY = "pi-gentic-live-refresh";
|
|
16
|
+
export declare function setAgentLabel(ctx: any, agentName: any): void;
|
|
17
|
+
export declare function showCard(pi: any, text: any, details: any): void;
|
|
18
|
+
export declare function startLiveRefresh(ctx: PiContext, key?: string, options?: AnyRecord): (() => void) & {
|
|
19
|
+
refresh?: () => void;
|
|
20
|
+
};
|
|
21
|
+
export declare function styleAgentName(agentName: unknown, { bracketed }?: AnyRecord): string;
|
|
22
|
+
export declare function agentColorCode(agentName: any): number;
|
|
23
|
+
export declare const SESSION_TREE_VISIBLE_ITEMS = 12;
|
|
24
|
+
export declare function renderSessionTree(details: AnyRecord, theme: PiTheme): SessionTreeCard;
|
|
25
|
+
export declare function createSessionTreePicker(sessions: AnyRecord[], theme: PiTheme, done: (session: AnyRecord | undefined) => void, requestRender?: () => void, options?: AnyRecord): SessionTreeCard;
|
|
26
|
+
/** Interactive orchestration tree used by the orchestration-tree picker. */
|
|
27
|
+
export declare class SessionTreeCard {
|
|
28
|
+
sessions: AnyRecord[];
|
|
29
|
+
theme: PiTheme;
|
|
30
|
+
selectedIndex: number;
|
|
31
|
+
maxVisible: number;
|
|
32
|
+
onSelect?: (session: AnyRecord | undefined) => void;
|
|
33
|
+
requestRender: () => void;
|
|
34
|
+
refreshSessions?: () => Promise<AnyRecord[]> | AnyRecord[];
|
|
35
|
+
refreshing: boolean;
|
|
36
|
+
refreshIntervalMs: number;
|
|
37
|
+
repaintIntervalMs: number;
|
|
38
|
+
refreshTimer?: NodeJS.Timeout;
|
|
39
|
+
repaintTimer?: NodeJS.Timeout;
|
|
40
|
+
constructor(sessions: AnyRecord[], theme: PiTheme, options?: AnyRecord);
|
|
41
|
+
invalidate(): void;
|
|
42
|
+
dispose(): void;
|
|
43
|
+
updateSessions(sessions: AnyRecord[]): void;
|
|
44
|
+
refresh(): Promise<void>;
|
|
45
|
+
ensureRefreshTimer(): void;
|
|
46
|
+
clearRefreshTimer(): void;
|
|
47
|
+
handleInput(data: string): void;
|
|
48
|
+
render(width: number): string[];
|
|
49
|
+
lines(width: number): string[];
|
|
50
|
+
visibleRange(): {
|
|
51
|
+
start: number;
|
|
52
|
+
end: number;
|
|
53
|
+
};
|
|
54
|
+
scrollLines(start: number, end: number, width: number): string[];
|
|
55
|
+
clampedSelectedIndex(): number;
|
|
56
|
+
sessionLine(session: AnyRecord, index: number, width: number): string;
|
|
57
|
+
colorBorder(text: string): string;
|
|
58
|
+
bold(text: string): string;
|
|
59
|
+
muted(text: string): string;
|
|
60
|
+
dim(text: string): string;
|
|
61
|
+
green(text: string): string;
|
|
62
|
+
timer(text: string): string;
|
|
63
|
+
selected(text: string): string;
|
|
64
|
+
agentName(text: string): string;
|
|
65
|
+
}
|
|
66
|
+
export declare function sessionInactiveMs(session: AnyRecord): number;
|
|
67
|
+
export declare function renderAgentsCall(): InvisibleComponent;
|
|
68
|
+
/** Reuses card instances during streaming updates so live details stay smooth. */
|
|
69
|
+
export declare function renderAgentsResult(result: AnyRecord, options: AnyRecord, theme: PiTheme, context: AnyRecord): AgentsCard;
|
|
70
|
+
declare class InvisibleComponent {
|
|
71
|
+
invalidate(): void;
|
|
72
|
+
render(): any[];
|
|
73
|
+
}
|
|
74
|
+
/** Chat card renderer for load, send, status, discovery, and error results. */
|
|
75
|
+
declare class AgentsCard {
|
|
76
|
+
theme: PiTheme;
|
|
77
|
+
data: AnyRecord;
|
|
78
|
+
expanded: boolean;
|
|
79
|
+
constructor(theme: any);
|
|
80
|
+
update(data: AnyRecord, expanded: boolean): void;
|
|
81
|
+
invalidate(): void;
|
|
82
|
+
render(width: number): string[];
|
|
83
|
+
buildLines(width: number): any[];
|
|
84
|
+
header(width: number): string;
|
|
85
|
+
title(): string;
|
|
86
|
+
body(width: number): any[];
|
|
87
|
+
sessionTreeLines(width: number): string[];
|
|
88
|
+
sessionTreeLine(session: AnyRecord, index: number, width: number): string;
|
|
89
|
+
sessionMessage(session: AnyRecord): string;
|
|
90
|
+
configurationLines(width: number): string[];
|
|
91
|
+
activityLines(width: number): string[];
|
|
92
|
+
footer(width: number): string;
|
|
93
|
+
totalDurationText(): string;
|
|
94
|
+
statusIcon(): string;
|
|
95
|
+
colorBorder(text: string): string;
|
|
96
|
+
bold(text: string): string;
|
|
97
|
+
muted(text: string): string;
|
|
98
|
+
dim(text: string): string;
|
|
99
|
+
green(text: string): string;
|
|
100
|
+
red(text: string): string;
|
|
101
|
+
purple(text: string): string;
|
|
102
|
+
brightPurple(text: string): string;
|
|
103
|
+
pink(text: string): string;
|
|
104
|
+
timer(text: string): string;
|
|
105
|
+
agent(text: string): string;
|
|
106
|
+
agentName(text: string): string;
|
|
107
|
+
}
|
|
108
|
+
export {};
|