@jamiexiongr/panda-hub 0.1.7 → 0.1.8

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.
@@ -0,0 +1,1375 @@
1
+ import {
2
+ agentRegistrationResponseSchema,
3
+ appendSessionIndexUpdate,
4
+ areMergeableAssistantEntries,
5
+ buildTailscaleHttpsUrl,
6
+ choosePreferredAssistantEntry,
7
+ configureTailscaleServe,
8
+ createCodexLiveSessionStream,
9
+ deleteRolloutFile,
10
+ ensurePandaHubApiKey,
11
+ getOrderedWorkspaceRoots,
12
+ getPandaHubApiKeyFilePath,
13
+ getPinnedSessionIds,
14
+ getPinnedWorkspaceRoots,
15
+ getSavedWorkspaceRoots,
16
+ getWorkspaceRootLabels,
17
+ isTailscaleRunning,
18
+ isWithinWorkspaceRoot,
19
+ moveRolloutFileFromArchived,
20
+ moveRolloutFileToArchived,
21
+ normalizeWorkspacePathKey,
22
+ parseMessageContent,
23
+ phaseOneSnapshotSchema,
24
+ printTerminalQr,
25
+ readCodexGlobalState,
26
+ readPandaSessionPrefs,
27
+ readPandaThreadPrefs,
28
+ readTailscaleStatus,
29
+ resolveCliOptionValue,
30
+ resolvePandaHubApiKey,
31
+ resolveTailscalePublicationMode,
32
+ resolveTailscaleServeEnabled,
33
+ resolveTailscaleServePort,
34
+ setSessionPinned,
35
+ setWorkspaceRootLabel,
36
+ setWorkspaceRootOrder,
37
+ setWorkspaceRootPinned,
38
+ setWorkspaceRootVisibility,
39
+ sortByStoredWorkspaceOrder,
40
+ startPandaSessionService,
41
+ stripInjectedUserText,
42
+ writeCodexGlobalState,
43
+ writePandaSessionPrefs,
44
+ writePandaThreadPrefs
45
+ } from "./chunk-WFHIGEWQ.mjs";
46
+ import "./chunk-AEQMWH7D.mjs";
47
+
48
+ // packages/provider-codex/src/index.ts
49
+ import { execFileSync } from "child_process";
50
+ import { createHash } from "crypto";
51
+ import fs2 from "fs/promises";
52
+ import os3 from "os";
53
+ import path2 from "path";
54
+
55
+ // packages/provider-codex/src/rollout-monitor.ts
56
+ import fs from "fs";
57
+ import fsp from "fs/promises";
58
+ import os from "os";
59
+ import path from "path";
60
+ var WATCH_DEBOUNCE_MS = 160;
61
+ var FALLBACK_SCAN_INTERVAL_MS = 2500;
62
+ var readFirstLine = async (filePath) => {
63
+ const handle = await fsp.open(filePath, "r");
64
+ try {
65
+ const chunks = [];
66
+ let bytesReadTotal = 0;
67
+ let position = 0;
68
+ while (bytesReadTotal < 256 * 1024) {
69
+ const buffer = Buffer.alloc(8192);
70
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
71
+ if (bytesRead === 0) {
72
+ break;
73
+ }
74
+ const text = buffer.toString("utf8", 0, bytesRead);
75
+ const newlineIndex = text.indexOf("\n");
76
+ if (newlineIndex >= 0) {
77
+ chunks.push(text.slice(0, newlineIndex));
78
+ break;
79
+ }
80
+ chunks.push(text);
81
+ bytesReadTotal += bytesRead;
82
+ position += bytesRead;
83
+ }
84
+ return chunks.join("").trim();
85
+ } finally {
86
+ await handle.close();
87
+ }
88
+ };
89
+ var walkRolloutFiles = async (rootPath) => {
90
+ let entries;
91
+ try {
92
+ entries = await fsp.readdir(rootPath, { withFileTypes: true });
93
+ } catch {
94
+ return [];
95
+ }
96
+ const files = [];
97
+ for (const entry of entries) {
98
+ const fullPath = path.join(rootPath, entry.name);
99
+ if (entry.isDirectory()) {
100
+ files.push(...await walkRolloutFiles(fullPath));
101
+ continue;
102
+ }
103
+ if (entry.isFile() && fullPath.endsWith(".jsonl")) {
104
+ files.push(fullPath);
105
+ }
106
+ }
107
+ return files;
108
+ };
109
+ var readSessionIdFromRollout = async (filePath) => {
110
+ try {
111
+ const firstLine = await readFirstLine(filePath);
112
+ if (!firstLine) {
113
+ return null;
114
+ }
115
+ const parsed = JSON.parse(firstLine);
116
+ return parsed.payload?.id?.trim() || null;
117
+ } catch {
118
+ return null;
119
+ }
120
+ };
121
+ var createCodexRolloutMonitor = (options) => {
122
+ const codexHome = options?.codexHome ?? path.join(os.homedir(), ".codex");
123
+ const roots = [
124
+ path.join(codexHome, "sessions"),
125
+ path.join(codexHome, "archived_sessions")
126
+ ];
127
+ const watchedFiles = /* @__PURE__ */ new Map();
128
+ const fileSignatures = /* @__PURE__ */ new Map();
129
+ const pending = /* @__PURE__ */ new Map();
130
+ const watchers = [];
131
+ let pollTimer = null;
132
+ let stopped = false;
133
+ const emitIfKnown = async (filePath) => {
134
+ if (!filePath.endsWith(".jsonl")) {
135
+ return;
136
+ }
137
+ let sessionId = watchedFiles.get(filePath);
138
+ if (!sessionId) {
139
+ sessionId = await readSessionIdFromRollout(filePath) ?? void 0;
140
+ if (!sessionId) {
141
+ return;
142
+ }
143
+ watchedFiles.set(filePath, sessionId);
144
+ }
145
+ options?.onSessionUpdated?.({
146
+ sessionId,
147
+ filePath,
148
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
149
+ });
150
+ };
151
+ const readFileSignature = async (filePath) => {
152
+ const stat = await fsp.stat(filePath);
153
+ return `${stat.size}:${stat.mtimeMs}`;
154
+ };
155
+ const scheduleEmit = (filePath) => {
156
+ const existing = pending.get(filePath);
157
+ if (existing) {
158
+ clearTimeout(existing);
159
+ }
160
+ pending.set(
161
+ filePath,
162
+ setTimeout(async () => {
163
+ pending.delete(filePath);
164
+ try {
165
+ const stat = await fsp.stat(filePath);
166
+ if (!stat.isFile()) {
167
+ return;
168
+ }
169
+ fileSignatures.set(filePath, `${stat.size}:${stat.mtimeMs}`);
170
+ } catch {
171
+ watchedFiles.delete(filePath);
172
+ fileSignatures.delete(filePath);
173
+ return;
174
+ }
175
+ await emitIfKnown(filePath);
176
+ }, WATCH_DEBOUNCE_MS)
177
+ );
178
+ };
179
+ const seedExistingFiles = async () => {
180
+ const files = (await Promise.all(roots.map((rootPath) => walkRolloutFiles(rootPath)))).flat();
181
+ await Promise.all(
182
+ files.map(async (filePath) => {
183
+ const sessionId = await readSessionIdFromRollout(filePath);
184
+ if (sessionId) {
185
+ watchedFiles.set(filePath, sessionId);
186
+ fileSignatures.set(filePath, await readFileSignature(filePath));
187
+ }
188
+ })
189
+ );
190
+ };
191
+ const startPolling = () => {
192
+ if (pollTimer) {
193
+ return;
194
+ }
195
+ pollTimer = setInterval(async () => {
196
+ const files = (await Promise.all(roots.map((rootPath) => walkRolloutFiles(rootPath)))).flat();
197
+ for (const filePath of files) {
198
+ let nextSignature = "";
199
+ try {
200
+ nextSignature = await readFileSignature(filePath);
201
+ } catch {
202
+ watchedFiles.delete(filePath);
203
+ fileSignatures.delete(filePath);
204
+ continue;
205
+ }
206
+ if (fileSignatures.get(filePath) !== nextSignature) {
207
+ fileSignatures.set(filePath, nextSignature);
208
+ scheduleEmit(filePath);
209
+ }
210
+ }
211
+ }, FALLBACK_SCAN_INTERVAL_MS);
212
+ };
213
+ const bindWatchers = async () => {
214
+ for (const rootPath of roots) {
215
+ try {
216
+ await fsp.mkdir(rootPath, { recursive: true });
217
+ } catch {
218
+ continue;
219
+ }
220
+ try {
221
+ const watcher = fs.watch(rootPath, { recursive: true }, (_eventType, filename) => {
222
+ if (stopped) {
223
+ return;
224
+ }
225
+ if (!filename) {
226
+ return;
227
+ }
228
+ const fullPath = path.join(rootPath, filename.toString());
229
+ if (fullPath.endsWith(".jsonl")) {
230
+ scheduleEmit(fullPath);
231
+ }
232
+ });
233
+ watcher.on("error", () => {
234
+ startPolling();
235
+ });
236
+ watchers.push(watcher);
237
+ } catch {
238
+ startPolling();
239
+ }
240
+ }
241
+ };
242
+ return {
243
+ start: async () => {
244
+ await seedExistingFiles();
245
+ await bindWatchers();
246
+ },
247
+ stop: async () => {
248
+ stopped = true;
249
+ for (const timer of pending.values()) {
250
+ clearTimeout(timer);
251
+ }
252
+ pending.clear();
253
+ if (pollTimer) {
254
+ clearInterval(pollTimer);
255
+ pollTimer = null;
256
+ }
257
+ for (const watcher of watchers) {
258
+ watcher.close();
259
+ }
260
+ watchers.length = 0;
261
+ }
262
+ };
263
+ };
264
+
265
+ // packages/provider-codex/src/agent-hub-sync.ts
266
+ import os2 from "os";
267
+ var DEFAULT_HEARTBEAT_INTERVAL_MS = 15e3;
268
+ var trimToNull = (value) => {
269
+ const normalized = value?.trim() ?? "";
270
+ return normalized ? normalized : null;
271
+ };
272
+ var normalizeHostname = (value) => {
273
+ const normalized = trimToNull(value);
274
+ if (!normalized) {
275
+ return null;
276
+ }
277
+ return normalized.replace(/^\[|\]$/g, "").replace(/\.+$/, "").toLowerCase();
278
+ };
279
+ var readHostnameFromUrl = (value) => {
280
+ const normalized = trimToNull(value);
281
+ if (!normalized) {
282
+ return null;
283
+ }
284
+ try {
285
+ return normalizeHostname(new URL(normalized).hostname);
286
+ } catch {
287
+ return null;
288
+ }
289
+ };
290
+ var toWsUrl = (baseUrl) => {
291
+ const url = new URL(baseUrl);
292
+ url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
293
+ url.pathname = "/ws";
294
+ return url.toString();
295
+ };
296
+ var resolveHostAddress = () => {
297
+ const interfaces = os2.networkInterfaces();
298
+ let fallback = null;
299
+ for (const [name, entries] of Object.entries(interfaces)) {
300
+ for (const entry of entries ?? []) {
301
+ if (entry.internal || entry.family !== "IPv4") {
302
+ continue;
303
+ }
304
+ if (name.toLowerCase().includes("tailscale") || entry.address.startsWith("100.")) {
305
+ return entry.address;
306
+ }
307
+ fallback ??= entry.address;
308
+ }
309
+ }
310
+ return fallback ?? os2.hostname();
311
+ };
312
+ var resolveAgentNetworkIdentity = (options) => {
313
+ const tailscaleStatus = readTailscaleStatus();
314
+ const detectedDnsName = normalizeHostname(tailscaleStatus?.Self?.DNSName);
315
+ const detectedTailscaleIp = trimToNull(tailscaleStatus?.Self?.TailscaleIPs?.[0]);
316
+ const port = options?.port ?? 4242;
317
+ const host = readHostnameFromUrl(options?.directBaseUrl) ?? normalizeHostname(options?.tailscaleDnsName) ?? trimToNull(options?.tailscaleIp) ?? detectedDnsName ?? detectedTailscaleIp ?? resolveHostAddress();
318
+ const directBaseUrl = trimToNull(options?.directBaseUrl) ?? `http://${host}:${port}`;
319
+ const wsBaseUrl = trimToNull(options?.wsBaseUrl) ?? toWsUrl(directBaseUrl);
320
+ return {
321
+ host,
322
+ tailscaleIp: trimToNull(options?.tailscaleIp) ?? detectedTailscaleIp,
323
+ tailscaleDnsName: normalizeHostname(options?.tailscaleDnsName) ?? detectedDnsName,
324
+ directBaseUrl,
325
+ wsBaseUrl
326
+ };
327
+ };
328
+ var readLocalSnapshot = async (localBaseUrl) => {
329
+ const response = await fetch(new URL("/api/bootstrap", localBaseUrl));
330
+ if (!response.ok) {
331
+ throw new Error(`Local bootstrap request failed with ${response.status}`);
332
+ }
333
+ return phaseOneSnapshotSchema.parse(await response.json());
334
+ };
335
+ var buildAgentSyncPayload = async (snapshot, options) => {
336
+ const now = (/* @__PURE__ */ new Date()).toISOString();
337
+ const discoveredAgent = snapshot.agents[0];
338
+ const agent = {
339
+ ...discoveredAgent ?? {
340
+ id: options.agentId,
341
+ name: options.agentName,
342
+ host: options.networkIdentity.host,
343
+ tailscale_ip: options.networkIdentity.tailscaleIp,
344
+ tailscale_dns_name: options.networkIdentity.tailscaleDnsName,
345
+ direct_base_url: options.networkIdentity.directBaseUrl,
346
+ ws_base_url: options.networkIdentity.wsBaseUrl,
347
+ status: "online",
348
+ provider_availability: ["codex"],
349
+ project_count: snapshot.projects.length,
350
+ session_count: snapshot.sessions.length,
351
+ transport: options.transport,
352
+ version: options.version ?? null,
353
+ registered_at: now,
354
+ last_seen_at: now
355
+ },
356
+ id: options.agentId,
357
+ name: options.agentName,
358
+ host: options.networkIdentity.host,
359
+ tailscale_ip: options.networkIdentity.tailscaleIp,
360
+ tailscale_dns_name: options.networkIdentity.tailscaleDnsName,
361
+ direct_base_url: options.networkIdentity.directBaseUrl,
362
+ ws_base_url: options.networkIdentity.wsBaseUrl,
363
+ status: "online",
364
+ project_count: snapshot.projects.length,
365
+ session_count: snapshot.sessions.length,
366
+ transport: options.transport,
367
+ version: options.version ?? discoveredAgent?.version ?? null,
368
+ registered_at: discoveredAgent?.registered_at ?? now,
369
+ last_seen_at: now
370
+ };
371
+ return {
372
+ agent,
373
+ projects: snapshot.projects.map((project) => ({
374
+ ...project,
375
+ agent_id: options.agentId
376
+ })),
377
+ sessions: snapshot.sessions.map((session) => ({
378
+ ...session,
379
+ agent_id: options.agentId
380
+ })),
381
+ active_session_id: snapshot.active_session_id,
382
+ generated_at: snapshot.generated_at
383
+ };
384
+ };
385
+ var postHubSync = async (pathName, payload, options) => {
386
+ const response = await fetch(new URL(pathName, options.hubUrl), {
387
+ method: "POST",
388
+ headers: {
389
+ "content-type": "application/json",
390
+ ...options.hubApiKey ? { "x-panda-hub-api-key": options.hubApiKey } : {}
391
+ },
392
+ body: JSON.stringify(payload)
393
+ });
394
+ const json = await response.json().catch(() => null);
395
+ if (!response.ok) {
396
+ throw new Error(
397
+ json?.error ?? `${pathName} failed with ${response.status}`
398
+ );
399
+ }
400
+ return agentRegistrationResponseSchema.parse(json);
401
+ };
402
+ var startAgentHubSync = (options) => {
403
+ let stopped = false;
404
+ let heartbeatTimer = null;
405
+ let heartbeatIntervalMs = options.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
406
+ const clearHeartbeatTimer = () => {
407
+ if (heartbeatTimer) {
408
+ clearTimeout(heartbeatTimer);
409
+ heartbeatTimer = null;
410
+ }
411
+ };
412
+ const scheduleHeartbeat = () => {
413
+ if (stopped) {
414
+ return;
415
+ }
416
+ clearHeartbeatTimer();
417
+ heartbeatTimer = setTimeout(() => {
418
+ void sync("heartbeat");
419
+ }, heartbeatIntervalMs);
420
+ };
421
+ const sync = async (kind) => {
422
+ try {
423
+ const snapshot = await readLocalSnapshot(options.localBaseUrl);
424
+ const payload = await buildAgentSyncPayload(snapshot, options);
425
+ const response = await postHubSync(
426
+ kind === "register" ? "/api/agents/register" : "/api/agents/heartbeat",
427
+ payload,
428
+ options
429
+ );
430
+ heartbeatIntervalMs = response.heartbeat_interval_ms;
431
+ if (kind === "register") {
432
+ options.logger?.info?.(
433
+ `Registered Panda agent ${options.agentId} to hub ${options.hubUrl}`
434
+ );
435
+ }
436
+ } catch (error) {
437
+ options.logger?.warn?.(
438
+ `Unable to ${kind === "register" ? "register" : "heartbeat"} Panda agent with hub: ${error instanceof Error ? error.message : String(error)}`
439
+ );
440
+ } finally {
441
+ scheduleHeartbeat();
442
+ }
443
+ };
444
+ void sync("register");
445
+ return {
446
+ stop: () => {
447
+ stopped = true;
448
+ clearHeartbeatTimer();
449
+ }
450
+ };
451
+ };
452
+
453
+ // packages/provider-codex/src/index.ts
454
+ var ACTIVE_WINDOW_MS = 10 * 60 * 1e3;
455
+ var LIVE_WINDOW_MS = 30 * 60 * 1e3;
456
+ var IDLE_WINDOW_MS = 24 * 60 * 60 * 1e3;
457
+ var FIRST_LINE_READ_LIMIT = 256 * 1024;
458
+ var TITLE_CANDIDATE_READ_LIMIT = 128 * 1024;
459
+ var DISCOVERED_TITLE_MAX_LENGTH = 72;
460
+ var TOOL_OUTPUT_LIMIT = 1400;
461
+ var COMPLETED_STATE_TTL_MS = 3 * 60 * 1e3;
462
+ var stableId = (prefix, value) => `${prefix}-${createHash("sha1").update(value).digest("hex").slice(0, 12)}`;
463
+ var normalizePathKey = (value) => {
464
+ const normalized = path2.normalize(value.trim());
465
+ return process.platform === "win32" ? normalized.replace(/\//g, "\\").toLowerCase() : normalized;
466
+ };
467
+ var basenameFromPath = (targetPath) => {
468
+ const normalized = targetPath.replace(/[\\/]+$/, "");
469
+ return path2.basename(normalized) || normalized;
470
+ };
471
+ var pickNewestTimestamp = (...candidates) => {
472
+ let newestValue = null;
473
+ let newestTime = Number.NEGATIVE_INFINITY;
474
+ for (const candidate of candidates) {
475
+ if (typeof candidate !== "string" || !candidate.trim()) {
476
+ continue;
477
+ }
478
+ const candidateTime = new Date(candidate).getTime();
479
+ if (!Number.isFinite(candidateTime) || candidateTime <= newestTime) {
480
+ continue;
481
+ }
482
+ newestTime = candidateTime;
483
+ newestValue = candidate;
484
+ }
485
+ return newestValue;
486
+ };
487
+ var truncateText = (value, length) => {
488
+ const trimmed = value.trim();
489
+ if (trimmed.length <= length) {
490
+ return trimmed;
491
+ }
492
+ return `${trimmed.slice(0, length - 1).trimEnd()}\u2026`;
493
+ };
494
+ var normalizeConversationRole = (role) => {
495
+ if (role === "user") {
496
+ return {
497
+ kind: "user",
498
+ title: "user",
499
+ accent: "primary"
500
+ };
501
+ }
502
+ if (role === "assistant") {
503
+ return {
504
+ kind: "assistant",
505
+ title: "assistant",
506
+ accent: "secondary"
507
+ };
508
+ }
509
+ if (role === "system") {
510
+ return {
511
+ kind: "system",
512
+ title: "system",
513
+ accent: "muted"
514
+ };
515
+ }
516
+ if (role === "developer") {
517
+ return {
518
+ kind: "system",
519
+ title: "system_prompt",
520
+ accent: "muted"
521
+ };
522
+ }
523
+ return null;
524
+ };
525
+ var parseSessionContextUsage = (info, timestamp) => {
526
+ if (!info || typeof info !== "object") {
527
+ return null;
528
+ }
529
+ const usageInfo = info;
530
+ const preferredUsage = usageInfo.last_token_usage && typeof usageInfo.last_token_usage === "object" ? usageInfo.last_token_usage : usageInfo.total_token_usage;
531
+ const fallbackUsage = usageInfo.total_token_usage ?? usageInfo.last_token_usage;
532
+ const totalTokens = preferredUsage?.total_tokens ?? fallbackUsage?.total_tokens;
533
+ const totalContextWindow = usageInfo.model_context_window;
534
+ if (typeof totalTokens !== "number" || !Number.isFinite(totalTokens) || typeof totalContextWindow !== "number" || !Number.isFinite(totalContextWindow) || totalContextWindow <= 0) {
535
+ return null;
536
+ }
537
+ const usedTokens = Math.max(0, Math.round(totalTokens));
538
+ const totalWindow = Math.max(usedTokens, Math.round(totalContextWindow));
539
+ const remainingTokens = Math.max(0, totalWindow - usedTokens);
540
+ return {
541
+ used_tokens: usedTokens,
542
+ total_tokens: totalWindow,
543
+ remaining_tokens: remainingTokens,
544
+ percent_used: Math.max(0, Math.min(100, usedTokens / totalWindow * 100)),
545
+ cached_input_tokens: Math.max(
546
+ 0,
547
+ Math.round(
548
+ preferredUsage?.cached_input_tokens ?? fallbackUsage?.cached_input_tokens ?? 0
549
+ )
550
+ ),
551
+ output_tokens: Math.max(
552
+ 0,
553
+ Math.round(
554
+ preferredUsage?.output_tokens ?? fallbackUsage?.output_tokens ?? 0
555
+ )
556
+ ),
557
+ reasoning_output_tokens: Math.max(
558
+ 0,
559
+ Math.round(
560
+ preferredUsage?.reasoning_output_tokens ?? fallbackUsage?.reasoning_output_tokens ?? 0
561
+ )
562
+ ),
563
+ updated_at: timestamp
564
+ };
565
+ };
566
+ var defaultCodexHome = () => path2.join(os3.homedir(), ".codex");
567
+ var isWithinWorkspaceRoots = (cwd, workspaceRoots) => {
568
+ if (workspaceRoots.length === 0) {
569
+ return true;
570
+ }
571
+ const normalizedCwd = normalizePathKey(cwd);
572
+ return workspaceRoots.some((root) => {
573
+ if (normalizedCwd === root) {
574
+ return true;
575
+ }
576
+ const separator = process.platform === "win32" ? "\\" : "/";
577
+ return normalizedCwd.startsWith(`${root}${separator}`);
578
+ });
579
+ };
580
+ var resolveHostAddress2 = (providedHost) => {
581
+ if (providedHost) {
582
+ return providedHost;
583
+ }
584
+ const interfaces = os3.networkInterfaces();
585
+ let fallback = null;
586
+ for (const [name, entries] of Object.entries(interfaces)) {
587
+ for (const entry of entries ?? []) {
588
+ if (entry.internal || entry.family !== "IPv4") {
589
+ continue;
590
+ }
591
+ if (name.toLowerCase().includes("tailscale") || entry.address.startsWith("100.")) {
592
+ return entry.address;
593
+ }
594
+ fallback ??= entry.address;
595
+ }
596
+ }
597
+ return fallback ?? os3.hostname();
598
+ };
599
+ var readFirstLine2 = async (filePath) => {
600
+ const handle = await fs2.open(filePath, "r");
601
+ try {
602
+ const chunks = [];
603
+ let bytesReadTotal = 0;
604
+ let position = 0;
605
+ while (bytesReadTotal < FIRST_LINE_READ_LIMIT) {
606
+ const buffer = Buffer.alloc(8192);
607
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, position);
608
+ if (bytesRead === 0) {
609
+ break;
610
+ }
611
+ const text = buffer.toString("utf8", 0, bytesRead);
612
+ const newlineIndex = text.indexOf("\n");
613
+ if (newlineIndex >= 0) {
614
+ chunks.push(text.slice(0, newlineIndex));
615
+ break;
616
+ }
617
+ chunks.push(text);
618
+ bytesReadTotal += bytesRead;
619
+ position += bytesRead;
620
+ }
621
+ return chunks.join("").trim();
622
+ } finally {
623
+ await handle.close();
624
+ }
625
+ };
626
+ var readInitialChunk = async (filePath, limit) => {
627
+ const handle = await fs2.open(filePath, "r");
628
+ try {
629
+ const buffer = Buffer.alloc(limit);
630
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
631
+ return buffer.toString("utf8", 0, bytesRead);
632
+ } finally {
633
+ await handle.close();
634
+ }
635
+ };
636
+ var readJsonLines = async (filePath) => {
637
+ const content = await fs2.readFile(filePath, "utf8");
638
+ const lines = content.split(/\r?\n/).filter(Boolean);
639
+ const records = [];
640
+ for (const line of lines) {
641
+ try {
642
+ records.push(JSON.parse(line));
643
+ } catch {
644
+ continue;
645
+ }
646
+ }
647
+ return records;
648
+ };
649
+ var loadSessionIndex = async (codexHome) => {
650
+ const result = /* @__PURE__ */ new Map();
651
+ const filePath = path2.join(codexHome, "session_index.jsonl");
652
+ try {
653
+ const content = await fs2.readFile(filePath, "utf8");
654
+ for (const line of content.split(/\r?\n/).filter(Boolean)) {
655
+ try {
656
+ const parsed = JSON.parse(line);
657
+ if (parsed?.id) {
658
+ result.set(parsed.id, parsed);
659
+ }
660
+ } catch {
661
+ continue;
662
+ }
663
+ }
664
+ } catch {
665
+ return result;
666
+ }
667
+ return result;
668
+ };
669
+ var walkRolloutFiles2 = async (rootPath) => {
670
+ let entries;
671
+ try {
672
+ entries = await fs2.readdir(rootPath, { withFileTypes: true });
673
+ } catch {
674
+ return [];
675
+ }
676
+ const files = [];
677
+ for (const entry of entries) {
678
+ const fullPath = path2.join(rootPath, entry.name);
679
+ if (entry.isDirectory()) {
680
+ files.push(...await walkRolloutFiles2(fullPath));
681
+ continue;
682
+ }
683
+ if (entry.isFile() && fullPath.endsWith(".jsonl")) {
684
+ files.push(fullPath);
685
+ }
686
+ }
687
+ return files;
688
+ };
689
+ var normalizeDiscoveredTitle = (value) => {
690
+ const normalized = stripInjectedUserText(value).replace(/\s+/g, " ").trim();
691
+ if (!normalized) {
692
+ return "";
693
+ }
694
+ if (normalized.length <= DISCOVERED_TITLE_MAX_LENGTH) {
695
+ return normalized;
696
+ }
697
+ return `${normalized.slice(0, DISCOVERED_TITLE_MAX_LENGTH - 1).trimEnd()}\u2026`;
698
+ };
699
+ var extractDiscoveredUserTitleFromMessage = (payload) => {
700
+ if (!payload || typeof payload !== "object") {
701
+ return "";
702
+ }
703
+ const message = payload;
704
+ if (message.type !== "message" || message.role !== "user" || !Array.isArray(message.content)) {
705
+ return "";
706
+ }
707
+ return normalizeDiscoveredTitle(parseMessageContent(message.content).text);
708
+ };
709
+ var readDiscoveredTitleFromRollout = async (filePath) => {
710
+ try {
711
+ const content = await readInitialChunk(filePath, TITLE_CANDIDATE_READ_LIMIT);
712
+ const lines = content.split(/\r?\n/);
713
+ for (const line of lines) {
714
+ if (!line.trim()) {
715
+ continue;
716
+ }
717
+ try {
718
+ const parsed = JSON.parse(line);
719
+ if (parsed.type !== "response_item") {
720
+ continue;
721
+ }
722
+ const candidate = extractDiscoveredUserTitleFromMessage(parsed.payload);
723
+ if (candidate) {
724
+ return candidate;
725
+ }
726
+ } catch {
727
+ continue;
728
+ }
729
+ }
730
+ } catch {
731
+ return "";
732
+ }
733
+ return "";
734
+ };
735
+ var extractReasoningSummary = (summary) => {
736
+ if (!Array.isArray(summary)) {
737
+ return "";
738
+ }
739
+ return summary.map((item) => {
740
+ if (!item || typeof item !== "object") {
741
+ return "";
742
+ }
743
+ const candidate = item;
744
+ if (candidate.type === "summary_text" && typeof candidate.text === "string") {
745
+ return candidate.text.trim();
746
+ }
747
+ return "";
748
+ }).filter(Boolean).join("\n\n").trim();
749
+ };
750
+ var truncateOutput = (value, maxLength) => {
751
+ const trimmed = value.trim();
752
+ if (trimmed.length <= maxLength) {
753
+ return trimmed;
754
+ }
755
+ return `${trimmed.slice(0, maxLength - 1).trimEnd()}\u2026`;
756
+ };
757
+ var safeJsonPreview = (value) => {
758
+ if (typeof value === "string") {
759
+ return truncateOutput(value, TOOL_OUTPUT_LIMIT);
760
+ }
761
+ try {
762
+ return truncateOutput(JSON.stringify(value, null, 2), TOOL_OUTPUT_LIMIT);
763
+ } catch {
764
+ return "";
765
+ }
766
+ };
767
+ var normalizeRunState = (runState, changedAt, lastActivityAt) => {
768
+ const freshestRunningAt = Math.max(
769
+ changedAt ? new Date(changedAt).getTime() : Number.NaN,
770
+ lastActivityAt ? new Date(lastActivityAt).getTime() : Number.NaN
771
+ );
772
+ if (runState === "running" && (!Number.isFinite(freshestRunningAt) || Date.now() - freshestRunningAt > ACTIVE_WINDOW_MS)) {
773
+ return {
774
+ run_state: "idle",
775
+ run_state_changed_at: null
776
+ };
777
+ }
778
+ if (runState === "completed" && changedAt && Date.now() - new Date(changedAt).getTime() > COMPLETED_STATE_TTL_MS) {
779
+ return {
780
+ run_state: "idle",
781
+ run_state_changed_at: null
782
+ };
783
+ }
784
+ return {
785
+ run_state: runState,
786
+ run_state_changed_at: changedAt
787
+ };
788
+ };
789
+ var buildSessionCapability = (isLive, archived) => ({
790
+ can_stream_live: archived ? false : isLive,
791
+ can_send_input: !archived,
792
+ can_interrupt: !archived,
793
+ can_approve: archived ? false : isLive,
794
+ can_reject: archived ? false : isLive,
795
+ can_show_git: true,
796
+ can_show_terminal: !archived
797
+ });
798
+ var deriveSessionHealth = (updatedAt, archived) => {
799
+ if (archived) {
800
+ return "offline";
801
+ }
802
+ const diff = Date.now() - new Date(updatedAt).getTime();
803
+ if (diff <= ACTIVE_WINDOW_MS) {
804
+ return "active";
805
+ }
806
+ if (diff <= IDLE_WINDOW_MS) {
807
+ return "idle";
808
+ }
809
+ return "offline";
810
+ };
811
+ var deriveSessionMode = (updatedAt, archived) => archived ? "history-only" : Date.now() - new Date(updatedAt).getTime() <= LIVE_WINDOW_MS ? "attached-live" : "history-only";
812
+ var gitInfoCache = /* @__PURE__ */ new Map();
813
+ var readGitInfo = (cwd) => {
814
+ const cacheKey = normalizePathKey(cwd);
815
+ const cached = gitInfoCache.get(cacheKey);
816
+ if (cached) {
817
+ return cached;
818
+ }
819
+ try {
820
+ const topLevel = execFileSync(
821
+ "git",
822
+ ["-C", cwd, "rev-parse", "--show-toplevel"],
823
+ {
824
+ encoding: "utf8",
825
+ stdio: ["ignore", "pipe", "ignore"],
826
+ windowsHide: true
827
+ }
828
+ ).trim();
829
+ const branch = execFileSync(
830
+ "git",
831
+ ["-C", cwd, "rev-parse", "--abbrev-ref", "HEAD"],
832
+ {
833
+ encoding: "utf8",
834
+ stdio: ["ignore", "pipe", "ignore"],
835
+ windowsHide: true
836
+ }
837
+ ).trim();
838
+ const info = {
839
+ branch: branch || "unknown",
840
+ worktree: normalizePathKey(topLevel) === normalizePathKey(cwd) ? "default" : basenameFromPath(cwd)
841
+ };
842
+ gitInfoCache.set(cacheKey, info);
843
+ return info;
844
+ } catch {
845
+ const info = {
846
+ branch: "unknown",
847
+ worktree: "default"
848
+ };
849
+ gitInfoCache.set(cacheKey, info);
850
+ return info;
851
+ }
852
+ };
853
+ var summarizeTimeline = (entries) => {
854
+ const candidate = [...entries].reverse().find((entry) => entry.kind === "assistant" || entry.kind === "user");
855
+ return candidate ? truncateText(candidate.body, 88) : "";
856
+ };
857
+ var parseSessionSubagent = (payload) => {
858
+ if (!payload || typeof payload !== "object") {
859
+ return null;
860
+ }
861
+ const threadSpawn = payload.source && typeof payload.source === "object" ? payload.source.subagent?.thread_spawn : void 0;
862
+ const parentSessionId = typeof threadSpawn?.parent_thread_id === "string" && threadSpawn.parent_thread_id.trim() ? threadSpawn.parent_thread_id.trim() : typeof payload.forked_from_id === "string" && payload.forked_from_id.trim() ? payload.forked_from_id.trim() : "";
863
+ if (!parentSessionId) {
864
+ return null;
865
+ }
866
+ const nickname = typeof threadSpawn?.agent_nickname === "string" && threadSpawn.agent_nickname.trim() ? threadSpawn.agent_nickname.trim() : typeof payload.agent_nickname === "string" && payload.agent_nickname.trim() ? payload.agent_nickname.trim() : null;
867
+ const role = typeof threadSpawn?.agent_role === "string" && threadSpawn.agent_role.trim() ? threadSpawn.agent_role.trim() : typeof payload.agent_role === "string" && payload.agent_role.trim() ? payload.agent_role.trim() : null;
868
+ const depth = typeof threadSpawn?.depth === "number" && Number.isFinite(threadSpawn.depth) ? Math.max(0, Math.round(threadSpawn.depth)) : 1;
869
+ const rootSessionId = typeof payload.forked_from_id === "string" && payload.forked_from_id.trim() ? payload.forked_from_id.trim() : parentSessionId;
870
+ return {
871
+ parent_session_id: parentSessionId,
872
+ root_session_id: rootSessionId,
873
+ nickname,
874
+ role,
875
+ depth
876
+ };
877
+ };
878
+ var readRolloutRecord = async (filePath, sessionIndex, archived) => {
879
+ const firstLine = await readFirstLine2(filePath);
880
+ if (!firstLine) {
881
+ return null;
882
+ }
883
+ try {
884
+ const parsed = JSON.parse(firstLine);
885
+ const sessionId = parsed.payload?.id;
886
+ const cwd = parsed.payload?.cwd;
887
+ if (!sessionId || !cwd) {
888
+ return null;
889
+ }
890
+ const stat = await fs2.stat(filePath);
891
+ const indexed = sessionIndex.get(sessionId);
892
+ const indexedTitle = indexed?.thread_name?.trim() ?? "";
893
+ const updatedAt = pickNewestTimestamp(
894
+ indexed?.updated_at,
895
+ stat.mtime.toISOString(),
896
+ parsed.payload?.timestamp
897
+ ) ?? stat.mtime.toISOString();
898
+ const discoveredTitle = indexedTitle || await readDiscoveredTitleFromRollout(filePath);
899
+ return {
900
+ sessionId,
901
+ filePath,
902
+ cwd,
903
+ updatedAt,
904
+ archived,
905
+ subagent: parseSessionSubagent(parsed.payload),
906
+ title: discoveredTitle || `${basenameFromPath(cwd)} ${new Date(updatedAt).toLocaleString("zh-CN")}`
907
+ };
908
+ } catch {
909
+ return null;
910
+ }
911
+ };
912
+ var readCodexTimelineDetails = async (sessionId, options) => {
913
+ const codexHome = options?.codexHome ?? defaultCodexHome();
914
+ let filePath = options?.sessionFiles?.[sessionId];
915
+ if (!filePath) {
916
+ const sessionIndex = await loadSessionIndex(codexHome);
917
+ const [files, archivedFiles] = await Promise.all([
918
+ walkRolloutFiles2(path2.join(codexHome, "sessions")),
919
+ walkRolloutFiles2(path2.join(codexHome, "archived_sessions"))
920
+ ]);
921
+ for (const [candidate, archived] of [
922
+ ...files.map((candidate2) => [candidate2, false]),
923
+ ...archivedFiles.map((candidate2) => [candidate2, true])
924
+ ]) {
925
+ const record = await readRolloutRecord(candidate, sessionIndex, archived);
926
+ if (record?.sessionId === sessionId) {
927
+ filePath = candidate;
928
+ break;
929
+ }
930
+ }
931
+ }
932
+ if (!filePath) {
933
+ return {
934
+ entries: [],
935
+ runState: "idle",
936
+ runStateChangedAt: null,
937
+ contextUsage: null
938
+ };
939
+ }
940
+ const records = await readJsonLines(filePath);
941
+ const entries = [];
942
+ let sessionMeta = null;
943
+ let lastRunState = "idle";
944
+ let lastRunStateChangedAt = null;
945
+ let lastActivityAt = null;
946
+ let contextUsage = null;
947
+ const pushEntry = (entry) => {
948
+ const previous = entries[entries.length - 1];
949
+ if (areMergeableAssistantEntries(previous, {
950
+ ...entry,
951
+ attachments: entry.attachments ?? []
952
+ })) {
953
+ entries[entries.length - 1] = choosePreferredAssistantEntry(previous, entry);
954
+ return;
955
+ }
956
+ const nextEntry = {
957
+ id: `${sessionId}-${entries.length + 1}`,
958
+ kind: entry.kind,
959
+ title: entry.title,
960
+ body: entry.body,
961
+ timestamp: entry.timestamp,
962
+ accent: entry.accent,
963
+ attachments: entry.attachments ?? []
964
+ };
965
+ entries.push(nextEntry);
966
+ };
967
+ for (const record of records) {
968
+ if (typeof record.timestamp === "string" && record.timestamp.trim()) {
969
+ lastActivityAt = record.timestamp;
970
+ }
971
+ if (record.type === "session_meta") {
972
+ sessionMeta = {
973
+ timestamp: record.timestamp ?? record.payload?.timestamp,
974
+ cwd: record.payload?.cwd,
975
+ originator: record.payload?.originator
976
+ };
977
+ continue;
978
+ }
979
+ if (record.type === "event_msg") {
980
+ const eventType = record.payload?.type;
981
+ const eventTimestamp = record.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
982
+ if (eventType === "task_started") {
983
+ lastRunState = "running";
984
+ lastRunStateChangedAt = eventTimestamp;
985
+ pushEntry({
986
+ kind: "system",
987
+ title: "status",
988
+ body: "\u5F00\u59CB\u5904\u7406\u8BF7\u6C42",
989
+ timestamp: eventTimestamp,
990
+ accent: "muted"
991
+ });
992
+ }
993
+ if (eventType === "task_complete") {
994
+ lastRunState = "completed";
995
+ lastRunStateChangedAt = eventTimestamp;
996
+ pushEntry({
997
+ kind: "system",
998
+ title: "status",
999
+ body: "\u5904\u7406\u5B8C\u6210",
1000
+ timestamp: eventTimestamp,
1001
+ accent: "muted"
1002
+ });
1003
+ }
1004
+ if (eventType === "token_count") {
1005
+ contextUsage = parseSessionContextUsage(record.payload?.info, eventTimestamp) ?? contextUsage;
1006
+ }
1007
+ if (eventType === "context_compacted") {
1008
+ pushEntry({
1009
+ kind: "system",
1010
+ title: "context_compacted",
1011
+ body: "\u6B63\u5728\u81EA\u52A8\u538B\u7F29\u80CC\u666F\u4FE1\u606F",
1012
+ timestamp: eventTimestamp,
1013
+ accent: "muted"
1014
+ });
1015
+ }
1016
+ if (eventType === "agent_reasoning") {
1017
+ const summary = typeof record.payload?.text === "string" ? record.payload.text.trim() : "";
1018
+ if (summary) {
1019
+ pushEntry({
1020
+ kind: "thinking",
1021
+ title: "thinking",
1022
+ body: summary,
1023
+ timestamp: eventTimestamp,
1024
+ accent: "secondary"
1025
+ });
1026
+ }
1027
+ }
1028
+ if (eventType === "agent_message") {
1029
+ const message = typeof record.payload?.message === "string" ? record.payload.message.trim() : "";
1030
+ if (message) {
1031
+ pushEntry({
1032
+ kind: "assistant",
1033
+ title: typeof record.payload?.phase === "string" && record.payload.phase.trim() ? record.payload.phase.trim() : "assistant",
1034
+ body: message,
1035
+ timestamp: eventTimestamp,
1036
+ accent: "secondary"
1037
+ });
1038
+ }
1039
+ }
1040
+ continue;
1041
+ }
1042
+ if (record.type !== "response_item") {
1043
+ continue;
1044
+ }
1045
+ const responseType = record.payload?.type;
1046
+ const timestamp = record.timestamp ?? sessionMeta?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
1047
+ if (responseType === "message") {
1048
+ const role = record.payload?.role;
1049
+ const normalizedRole = normalizeConversationRole(role);
1050
+ if (!normalizedRole) {
1051
+ continue;
1052
+ }
1053
+ const messageContent = parseMessageContent(record.payload?.content);
1054
+ const displayedText = role === "user" ? stripInjectedUserText(messageContent.text) : messageContent.text.trim();
1055
+ if (!displayedText && messageContent.attachments.length === 0) {
1056
+ continue;
1057
+ }
1058
+ pushEntry({
1059
+ kind: normalizedRole.kind,
1060
+ title: normalizedRole.title,
1061
+ body: displayedText,
1062
+ timestamp,
1063
+ accent: normalizedRole.accent,
1064
+ attachments: messageContent.attachments
1065
+ });
1066
+ continue;
1067
+ }
1068
+ if (responseType === "reasoning") {
1069
+ const summary = extractReasoningSummary(record.payload?.summary);
1070
+ if (!summary) {
1071
+ continue;
1072
+ }
1073
+ pushEntry({
1074
+ kind: "thinking",
1075
+ title: "thinking",
1076
+ body: summary,
1077
+ timestamp,
1078
+ accent: "secondary"
1079
+ });
1080
+ continue;
1081
+ }
1082
+ if (responseType === "function_call") {
1083
+ const name = record.payload?.name?.trim() || "tool";
1084
+ const argumentsPreview = safeJsonPreview(record.payload?.arguments);
1085
+ pushEntry({
1086
+ kind: "tool",
1087
+ title: name,
1088
+ body: argumentsPreview || `\u8C03\u7528 ${name}`,
1089
+ timestamp,
1090
+ accent: "secondary"
1091
+ });
1092
+ continue;
1093
+ }
1094
+ if (responseType === "function_call_output") {
1095
+ const output = safeJsonPreview(record.payload?.output);
1096
+ if (!output) {
1097
+ continue;
1098
+ }
1099
+ pushEntry({
1100
+ kind: "tool",
1101
+ title: "tool-output",
1102
+ body: output,
1103
+ timestamp,
1104
+ accent: "muted"
1105
+ });
1106
+ }
1107
+ if (responseType === "custom_tool_call") {
1108
+ const name = record.payload?.name?.trim() || "tool";
1109
+ const input = typeof record.payload?.input === "string" ? record.payload.input.trim() : safeJsonPreview(record.payload?.input);
1110
+ pushEntry({
1111
+ kind: "tool",
1112
+ title: name,
1113
+ body: input || name,
1114
+ timestamp,
1115
+ accent: "secondary"
1116
+ });
1117
+ continue;
1118
+ }
1119
+ if (responseType === "custom_tool_call_output") {
1120
+ const output = typeof record.payload?.output === "string" ? record.payload.output.trim() : safeJsonPreview(record.payload?.output);
1121
+ if (!output) {
1122
+ continue;
1123
+ }
1124
+ pushEntry({
1125
+ kind: "tool",
1126
+ title: "tool-output",
1127
+ body: output,
1128
+ timestamp,
1129
+ accent: "muted"
1130
+ });
1131
+ }
1132
+ }
1133
+ if (entries.length > 0) {
1134
+ const normalizedState2 = normalizeRunState(
1135
+ lastRunState,
1136
+ lastRunStateChangedAt,
1137
+ lastActivityAt
1138
+ );
1139
+ return {
1140
+ entries,
1141
+ runState: normalizedState2.run_state,
1142
+ runStateChangedAt: normalizedState2.run_state_changed_at,
1143
+ contextUsage
1144
+ };
1145
+ }
1146
+ if (!sessionMeta) {
1147
+ const normalizedState2 = normalizeRunState(
1148
+ lastRunState,
1149
+ lastRunStateChangedAt,
1150
+ lastActivityAt
1151
+ );
1152
+ return {
1153
+ entries: [],
1154
+ runState: normalizedState2.run_state,
1155
+ runStateChangedAt: normalizedState2.run_state_changed_at,
1156
+ contextUsage
1157
+ };
1158
+ }
1159
+ const normalizedState = normalizeRunState(
1160
+ lastRunState,
1161
+ lastRunStateChangedAt,
1162
+ lastActivityAt
1163
+ );
1164
+ return {
1165
+ entries: [
1166
+ {
1167
+ id: `${sessionId}-meta`,
1168
+ kind: "system",
1169
+ title: "session",
1170
+ body: [sessionMeta.originator, sessionMeta.cwd].filter(Boolean).join(" \xB7 "),
1171
+ timestamp: sessionMeta.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
1172
+ accent: "muted",
1173
+ attachments: []
1174
+ }
1175
+ ],
1176
+ runState: normalizedState.run_state,
1177
+ runStateChangedAt: normalizedState.run_state_changed_at,
1178
+ contextUsage
1179
+ };
1180
+ };
1181
+ var readCodexTimeline = async (sessionId, options) => {
1182
+ return (await readCodexTimelineDetails(sessionId, options)).entries;
1183
+ };
1184
+ var discoverLocalCodexData = async (options = {}) => {
1185
+ const codexHome = options.codexHome ?? defaultCodexHome();
1186
+ const sessionIndex = await loadSessionIndex(codexHome);
1187
+ const [globalState, pandaThreadPrefs, pandaSessionPrefs] = await Promise.all([
1188
+ readCodexGlobalState(codexHome),
1189
+ readPandaThreadPrefs(codexHome),
1190
+ readPandaSessionPrefs(codexHome)
1191
+ ]);
1192
+ const savedWorkspaceRoots = getSavedWorkspaceRoots(globalState).map(normalizePathKey);
1193
+ const workspaceRootLabels = getWorkspaceRootLabels(globalState);
1194
+ const pinnedWorkspaceRoots = new Set(
1195
+ getPinnedWorkspaceRoots(pandaThreadPrefs).map(normalizePathKey)
1196
+ );
1197
+ const orderedWorkspaceRoots = getOrderedWorkspaceRoots(pandaThreadPrefs);
1198
+ const pinnedSessionIds = new Set(getPinnedSessionIds(pandaSessionPrefs));
1199
+ const rolloutRoot = path2.join(codexHome, "sessions");
1200
+ const archivedRolloutRoot = path2.join(codexHome, "archived_sessions");
1201
+ const [rolloutFiles, archivedRolloutFiles] = await Promise.all([
1202
+ walkRolloutFiles2(rolloutRoot),
1203
+ walkRolloutFiles2(archivedRolloutRoot)
1204
+ ]);
1205
+ const records = (await Promise.all(
1206
+ [
1207
+ ...rolloutFiles.map((filePath) => readRolloutRecord(filePath, sessionIndex, false)),
1208
+ ...archivedRolloutFiles.map((filePath) => readRolloutRecord(filePath, sessionIndex, true))
1209
+ ]
1210
+ )).filter((record) => Boolean(record)).sort((a, b) => +new Date(b.updatedAt) - +new Date(a.updatedAt));
1211
+ const maxSessions = options.maxSessions ?? records.length;
1212
+ const limitedRecords = records.slice(0, maxSessions);
1213
+ const projectMap = /* @__PURE__ */ new Map();
1214
+ const sessionFiles = {};
1215
+ const sessions = [];
1216
+ for (const record of limitedRecords) {
1217
+ const shouldShowRecord = record.archived || isWithinWorkspaceRoots(record.cwd, savedWorkspaceRoots);
1218
+ if (!shouldShowRecord) {
1219
+ continue;
1220
+ }
1221
+ sessionFiles[record.sessionId] = record.filePath;
1222
+ const projectKey = normalizePathKey(record.cwd);
1223
+ if (!projectMap.has(projectKey)) {
1224
+ const gitInfo = readGitInfo(record.cwd);
1225
+ projectMap.set(projectKey, {
1226
+ id: stableId("project", projectKey),
1227
+ agent_id: options.agentId ?? stableId("agent", options.agentName ?? os3.hostname()),
1228
+ name: basenameFromPath(record.cwd),
1229
+ display_name: workspaceRootLabels[projectKey] ?? null,
1230
+ pinned: pinnedWorkspaceRoots.has(projectKey),
1231
+ path: record.cwd,
1232
+ branch: gitInfo.branch,
1233
+ worktree: gitInfo.worktree,
1234
+ runtime_profiles: [],
1235
+ preview_url: null
1236
+ });
1237
+ }
1238
+ }
1239
+ for (const record of limitedRecords) {
1240
+ const project = projectMap.get(normalizePathKey(record.cwd));
1241
+ if (!project) {
1242
+ continue;
1243
+ }
1244
+ const mode = deriveSessionMode(record.updatedAt, record.archived);
1245
+ const timelineDetails = await readCodexTimelineDetails(record.sessionId, {
1246
+ codexHome,
1247
+ sessionFiles
1248
+ });
1249
+ sessions.push({
1250
+ id: record.sessionId,
1251
+ agent_id: project.agent_id,
1252
+ project_id: project.id,
1253
+ provider: "codex",
1254
+ archived: record.archived,
1255
+ title: record.title,
1256
+ mode,
1257
+ health: deriveSessionHealth(record.updatedAt, record.archived),
1258
+ branch: project.branch,
1259
+ worktree: project.worktree,
1260
+ summary: summarizeTimeline(timelineDetails.entries),
1261
+ latest_assistant_message: timelineDetails.entries.filter((entry) => entry.kind === "assistant").at(-1)?.body ?? null,
1262
+ last_event_at: record.updatedAt,
1263
+ pinned: pinnedSessionIds.has(record.sessionId),
1264
+ run_state: record.archived ? "idle" : timelineDetails.runState,
1265
+ run_state_changed_at: record.archived ? null : timelineDetails.runStateChangedAt,
1266
+ context_usage: timelineDetails.contextUsage,
1267
+ subagent: record.subagent,
1268
+ capability: buildSessionCapability(mode === "attached-live", record.archived)
1269
+ });
1270
+ }
1271
+ const agentId = options.agentId ?? stableId("agent", options.agentName ?? os3.hostname());
1272
+ const host = resolveHostAddress2(options.host);
1273
+ const directBaseUrl = options.directBaseUrl?.trim() || `http://${host}:4242`;
1274
+ const wsBaseUrl = options.wsBaseUrl?.trim() || directBaseUrl.replace(/^http/i, "ws").replace(/\/+$/, "") + "/ws";
1275
+ const agent = {
1276
+ id: agentId,
1277
+ name: options.agentName ?? os3.hostname(),
1278
+ host,
1279
+ tailscale_ip: options.tailscaleIp ?? (host.startsWith("100.") ? host : null),
1280
+ tailscale_dns_name: options.tailscaleDnsName ?? null,
1281
+ direct_base_url: directBaseUrl,
1282
+ ws_base_url: wsBaseUrl,
1283
+ status: "online",
1284
+ provider_availability: ["codex"],
1285
+ project_count: projectMap.size,
1286
+ session_count: sessions.length,
1287
+ transport: options.transport ?? "direct-agent",
1288
+ version: options.version ?? null,
1289
+ registered_at: options.registeredAt ?? null,
1290
+ last_seen_at: options.lastSeenAt ?? null
1291
+ };
1292
+ return {
1293
+ agent,
1294
+ projects: sortByStoredWorkspaceOrder(
1295
+ [...projectMap.values()],
1296
+ orderedWorkspaceRoots
1297
+ ),
1298
+ sessions,
1299
+ activeSessionId: sessions.find((session) => !session.archived)?.id ?? sessions[0]?.id ?? "",
1300
+ sessionFiles
1301
+ };
1302
+ };
1303
+ var CodexAdapter = class {
1304
+ constructor(options = {}) {
1305
+ this.options = options;
1306
+ }
1307
+ async discoverProjects() {
1308
+ return (await discoverLocalCodexData(this.options)).projects;
1309
+ }
1310
+ async discoverSessions() {
1311
+ return (await discoverLocalCodexData(this.options)).sessions;
1312
+ }
1313
+ async getSessionCapabilities(sessionId) {
1314
+ const sessions = await this.discoverSessions();
1315
+ return sessions.find((session) => session.id === sessionId)?.capability ?? null;
1316
+ }
1317
+ async createManagedSession(_prompt) {
1318
+ return {
1319
+ sessionId: stableId("managed", `${Date.now()}`),
1320
+ accepted: true
1321
+ };
1322
+ }
1323
+ async sendUserInput(_sessionId, _input) {
1324
+ return;
1325
+ }
1326
+ async interruptTurn(_sessionId) {
1327
+ return;
1328
+ }
1329
+ };
1330
+ export {
1331
+ CodexAdapter,
1332
+ appendSessionIndexUpdate,
1333
+ buildTailscaleHttpsUrl,
1334
+ configureTailscaleServe,
1335
+ createCodexLiveSessionStream,
1336
+ createCodexRolloutMonitor,
1337
+ deleteRolloutFile,
1338
+ discoverLocalCodexData,
1339
+ ensurePandaHubApiKey,
1340
+ getOrderedWorkspaceRoots,
1341
+ getPandaHubApiKeyFilePath,
1342
+ getPinnedSessionIds,
1343
+ getPinnedWorkspaceRoots,
1344
+ getSavedWorkspaceRoots,
1345
+ getWorkspaceRootLabels,
1346
+ isTailscaleRunning,
1347
+ isWithinWorkspaceRoot,
1348
+ moveRolloutFileFromArchived,
1349
+ moveRolloutFileToArchived,
1350
+ normalizeWorkspacePathKey,
1351
+ printTerminalQr,
1352
+ readCodexGlobalState,
1353
+ readCodexTimeline,
1354
+ readCodexTimelineDetails,
1355
+ readPandaSessionPrefs,
1356
+ readPandaThreadPrefs,
1357
+ readTailscaleStatus,
1358
+ resolveAgentNetworkIdentity,
1359
+ resolveCliOptionValue,
1360
+ resolvePandaHubApiKey,
1361
+ resolveTailscalePublicationMode,
1362
+ resolveTailscaleServeEnabled,
1363
+ resolveTailscaleServePort,
1364
+ setSessionPinned,
1365
+ setWorkspaceRootLabel,
1366
+ setWorkspaceRootOrder,
1367
+ setWorkspaceRootPinned,
1368
+ setWorkspaceRootVisibility,
1369
+ sortByStoredWorkspaceOrder,
1370
+ startAgentHubSync,
1371
+ startPandaSessionService,
1372
+ writeCodexGlobalState,
1373
+ writePandaSessionPrefs,
1374
+ writePandaThreadPrefs
1375
+ };