lunel-cli 0.1.115 → 0.1.117

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.
@@ -13,6 +13,8 @@ export declare class CodexProvider implements AIProvider {
13
13
  private pendingQuestionRequestIds;
14
14
  private assistantMessageIdByTurnId;
15
15
  private partTextById;
16
+ private debugLog;
17
+ private debugHistory;
16
18
  init(): Promise<void>;
17
19
  destroy(): Promise<void>;
18
20
  subscribe(emitter: AiEventEmitter): () => void;
@@ -87,6 +89,7 @@ export declare class CodexProvider implements AIProvider {
87
89
  private resolveSessionFromPayload;
88
90
  private resolveInFlightTurnId;
89
91
  private decodeMessagesFromThreadRead;
92
+ private logThreadReadSummary;
90
93
  private decodeStoredToolLikePart;
91
94
  private describeStoredToolName;
92
95
  private extractStoredToolInput;
package/dist/ai/codex.js CHANGED
@@ -51,6 +51,17 @@ export class CodexProvider {
51
51
  pendingQuestionRequestIds = new Map();
52
52
  assistantMessageIdByTurnId = new Map();
53
53
  partTextById = new Map();
54
+ debugLog(message, fields) {
55
+ if (!DEBUG_MODE)
56
+ return;
57
+ const suffix = fields ? ` ${JSON.stringify(fields)}` : "";
58
+ console.log(`[codex] ${message}${suffix}`);
59
+ }
60
+ debugHistory(message, fields) {
61
+ if (!DEBUG_MODE)
62
+ return;
63
+ console.log(`[codex-history] ${message} ${JSON.stringify(fields)}`);
64
+ }
54
65
  async init() {
55
66
  if (DEBUG_MODE)
56
67
  console.log("Starting Codex app-server...");
@@ -100,7 +111,10 @@ export class CodexProvider {
100
111
  };
101
112
  }
102
113
  async createSession(title) {
103
- const result = await this.call("thread/start", { cwd: process.cwd() });
114
+ const result = await this.call("thread/start", {
115
+ cwd: process.cwd(),
116
+ persistExtendedHistory: true,
117
+ });
104
118
  const threadObject = this.extractThreadObject(result);
105
119
  const threadId = this.extractThreadId(threadObject);
106
120
  if (!threadId) {
@@ -207,6 +221,11 @@ export class CodexProvider {
207
221
  }
208
222
  async getMessages(sessionId) {
209
223
  const session = this.ensureLocalSession(sessionId);
224
+ this.debugLog("getMessages start", {
225
+ sessionId,
226
+ existingMessageCount: session.messages.length,
227
+ existingPartCount: session.messages.reduce((sum, message) => sum + (message.parts?.length || 0), 0),
228
+ });
210
229
  const result = await this.call("thread/read", {
211
230
  threadId: session.id,
212
231
  includeTurns: true,
@@ -215,7 +234,16 @@ export class CodexProvider {
215
234
  if (!threadObject) {
216
235
  return { messages: session.messages };
217
236
  }
237
+ this.logThreadReadSummary(sessionId, threadObject);
218
238
  const historyMessages = this.decodeMessagesFromThreadRead(sessionId, threadObject);
239
+ this.debugLog("getMessages decoded thread", {
240
+ sessionId,
241
+ turnCount: this.readArray(threadObject.turns).length,
242
+ decodedMessageCount: historyMessages.length,
243
+ decodedPartCount: historyMessages.reduce((sum, message) => sum + (message.parts?.length || 0), 0),
244
+ decodedRoles: historyMessages.map((message) => message.role),
245
+ decodedPartTypes: historyMessages.flatMap((message) => (message.parts || []).map((part) => String(this.asRecord(part).type ?? "unknown"))),
246
+ });
219
247
  if (historyMessages.length > 0) {
220
248
  session.messages = historyMessages;
221
249
  }
@@ -1107,7 +1135,10 @@ export class CodexProvider {
1107
1135
  return;
1108
1136
  }
1109
1137
  const session = this.sessions.get(threadId);
1110
- const params = { threadId };
1138
+ const params = {
1139
+ threadId,
1140
+ persistExtendedHistory: true,
1141
+ };
1111
1142
  if (session?.cwd) {
1112
1143
  params.cwd = session.cwd;
1113
1144
  }
@@ -1174,9 +1205,11 @@ export class CodexProvider {
1174
1205
  const assistantParts = [];
1175
1206
  let assistantMessageId;
1176
1207
  let assistantTimestamp = turnTime;
1208
+ const turnItemTypes = [];
1177
1209
  for (const item of items) {
1178
1210
  const itemObject = this.asRecord(item);
1179
1211
  const type = this.normalizedItemType(this.readString(itemObject.type) ?? "");
1212
+ turnItemTypes.push(type || "unknown");
1180
1213
  const itemId = this.readString(itemObject.id) ?? crypto.randomUUID();
1181
1214
  const timestamp = this.extractUpdatedAt(itemObject) ?? this.extractCreatedAt(itemObject) ?? (turnTime + orderOffset++);
1182
1215
  if (type === "usermessage") {
@@ -1239,6 +1272,7 @@ export class CodexProvider {
1239
1272
  || type === "mcptoolcall"
1240
1273
  || type === "dynamictoolcall"
1241
1274
  || type === "collabtoolcall"
1275
+ || type === "collabagenttoolcall"
1242
1276
  || type === "websearch"
1243
1277
  || type === "imageview") {
1244
1278
  assistantMessageId = assistantMessageId ?? (turnId ? `assistant:${turnId}` : itemId);
@@ -1268,9 +1302,53 @@ export class CodexProvider {
1268
1302
  this.assistantMessageIdByTurnId.set(turnId, resolvedAssistantMessageId);
1269
1303
  }
1270
1304
  }
1305
+ this.debugLog("decoded stored turn", {
1306
+ threadId,
1307
+ turnId: turnId ?? null,
1308
+ itemCount: items.length,
1309
+ itemTypes: turnItemTypes,
1310
+ emittedUserMessages: messages.filter((message) => message.role === "user").length,
1311
+ emittedAssistantParts: assistantParts.length,
1312
+ });
1271
1313
  }
1272
1314
  return messages;
1273
1315
  }
1316
+ logThreadReadSummary(sessionId, threadObject) {
1317
+ const turns = this.readArray(threadObject.turns);
1318
+ const turnSummaries = turns.map((turn, index) => {
1319
+ const turnObject = this.asRecord(turn);
1320
+ const items = this.readArray(turnObject.items);
1321
+ return {
1322
+ index,
1323
+ turnId: this.readString(turnObject.id) ?? null,
1324
+ status: this.readString(turnObject.status) ?? this.readString(this.asRecord(turnObject.status).type) ?? null,
1325
+ itemCount: items.length,
1326
+ itemTypes: items.map((item) => this.normalizedItemType(this.readString(this.asRecord(item).type) ?? "") || "unknown"),
1327
+ itemSummaries: items.map((item) => {
1328
+ const itemObject = this.asRecord(item);
1329
+ const type = this.normalizedItemType(this.readString(itemObject.type) ?? "") || "unknown";
1330
+ return {
1331
+ id: this.readString(itemObject.id) ?? null,
1332
+ type,
1333
+ keys: Object.keys(itemObject).sort(),
1334
+ textPreview: this.firstString(itemObject, ["text", "summary", "message", "query", "path", "tool", "command"])?.slice(0, 120) ?? null,
1335
+ hasAggregatedOutput: typeof itemObject.aggregatedOutput === "string" && itemObject.aggregatedOutput.length > 0,
1336
+ aggregatedOutputLength: typeof itemObject.aggregatedOutput === "string" ? itemObject.aggregatedOutput.length : 0,
1337
+ changesCount: Array.isArray(itemObject.changes) ? itemObject.changes.length : 0,
1338
+ hasResult: itemObject.result != null,
1339
+ hasContentItems: Array.isArray(itemObject.contentItems) && itemObject.contentItems.length > 0,
1340
+ status: this.readString(itemObject.status) ?? null,
1341
+ };
1342
+ }),
1343
+ };
1344
+ });
1345
+ this.debugHistory("thread/read summary", {
1346
+ sessionId,
1347
+ threadId: this.readString(threadObject.id) ?? null,
1348
+ turnCount: turns.length,
1349
+ turnSummaries,
1350
+ });
1351
+ }
1274
1352
  decodeStoredToolLikePart(type, itemObject, threadId, messageId, itemId) {
1275
1353
  if (type === "filechange" || type === "diff" || this.isFileChangeStructuredItem(type, itemObject)) {
1276
1354
  const output = this.decodeFileLikeItemText(itemObject) ?? this.describeCompletedItemOutput(itemObject, itemObject, type) ?? "File changes";
@@ -1312,6 +1390,9 @@ export class CodexProvider {
1312
1390
  if (type === "imageview") {
1313
1391
  return "image-view";
1314
1392
  }
1393
+ if (type === "collabtoolcall" || type === "collabagenttoolcall") {
1394
+ return "agent";
1395
+ }
1315
1396
  if (type === "enteredreviewmode") {
1316
1397
  return "review";
1317
1398
  }
@@ -8,6 +8,9 @@ export declare class OpenCodeProvider implements AIProvider {
8
8
  private emitter;
9
9
  private knownPendingPermissionIds;
10
10
  private knownPendingQuestionIds;
11
+ private debugLog;
12
+ private debugWarn;
13
+ private debugError;
11
14
  init(): Promise<void>;
12
15
  destroy(): Promise<void>;
13
16
  subscribe(emitter: AiEventEmitter): () => void;
@@ -262,6 +262,21 @@ export class OpenCodeProvider {
262
262
  emitter = null;
263
263
  knownPendingPermissionIds = new Set();
264
264
  knownPendingQuestionIds = new Set();
265
+ debugLog(message, ...args) {
266
+ if (!VERBOSE_AI_LOGS)
267
+ return;
268
+ console.log(message, ...args);
269
+ }
270
+ debugWarn(message, ...args) {
271
+ if (!VERBOSE_AI_LOGS)
272
+ return;
273
+ console.warn(message, ...args);
274
+ }
275
+ debugError(message, ...args) {
276
+ if (!VERBOSE_AI_LOGS)
277
+ return;
278
+ console.error(message, ...args);
279
+ }
265
280
  async init() {
266
281
  const opencodeUsername = "lunel";
267
282
  const opencodePassword = crypto.randomBytes(32).toString("base64url");
@@ -521,13 +536,13 @@ export class OpenCodeProvider {
521
536
  path: { id: this.lastActiveSessionId },
522
537
  });
523
538
  if (checkResp.error) {
524
- console.warn(`[sse] OpenCode session ${this.lastActiveSessionId} was garbage-collected. Notifying app.`);
539
+ this.debugWarn(`[sse] OpenCode session ${this.lastActiveSessionId} was garbage-collected. Notifying app.`);
525
540
  const gcSessionId = this.lastActiveSessionId;
526
541
  this.lastActiveSessionId = null;
527
542
  this.emitter?.({ type: "session_gc", properties: { sessionId: gcSessionId } });
528
543
  }
529
544
  else {
530
- console.log(`[sse] Active session ${this.lastActiveSessionId} still valid.`);
545
+ this.debugLog(`[sse] Active session ${this.lastActiveSessionId} still valid.`);
531
546
  }
532
547
  }
533
548
  if (attempt > 0) {
@@ -535,7 +550,7 @@ export class OpenCodeProvider {
535
550
  }
536
551
  const events = await this.client.event.subscribe();
537
552
  if (attempt > 0) {
538
- console.log(`[sse] reconnected after ${attempt} attempt(s)`);
553
+ this.debugLog(`[sse] reconnected after ${attempt} attempt(s)`);
539
554
  }
540
555
  attempt = 0;
541
556
  for await (const raw of events.stream) {
@@ -549,11 +564,11 @@ export class OpenCodeProvider {
549
564
  ? parsed.payload
550
565
  : parsed;
551
566
  if (!base || typeof base.type !== "string") {
552
- console.warn("[sse] Dropped malformed event:", redactSensitive(JSON.stringify(parsed).substring(0, 200)));
567
+ this.debugWarn("[sse] Dropped malformed event:", redactSensitive(JSON.stringify(parsed).substring(0, 200)));
553
568
  continue;
554
569
  }
555
570
  if (base.type !== "server.heartbeat") {
556
- console.log("[sse]", base.type);
571
+ this.debugLog("[sse]", base.type);
557
572
  }
558
573
  const normalizedEvent = normalizeOpenCodeEvent({
559
574
  type: base.type,
@@ -562,7 +577,7 @@ export class OpenCodeProvider {
562
577
  this.trackPermissionEvent(normalizedEvent.type, normalizedEvent.properties || {});
563
578
  this.emitter?.(normalizedEvent);
564
579
  }
565
- console.log("[sse] Event stream ended, reconnecting...");
580
+ this.debugLog("[sse] Event stream ended, reconnecting...");
566
581
  attempt++;
567
582
  }
568
583
  catch (err) {
@@ -570,9 +585,9 @@ export class OpenCodeProvider {
570
585
  return;
571
586
  attempt++;
572
587
  const delay = backoffMs(attempt - 1);
573
- console.error(`[sse] Stream error (attempt ${attempt}/${SSE_MAX_RETRIES}): ${err.message}. Retrying in ${delay}ms`);
588
+ this.debugError(`[sse] Stream error (attempt ${attempt}/${SSE_MAX_RETRIES}): ${err.message}. Retrying in ${delay}ms`);
574
589
  if (attempt >= SSE_MAX_RETRIES) {
575
- console.error("[sse] Max retries reached. Sending error event to app and giving up.");
590
+ this.debugError("[sse] Max retries reached. Sending error event to app and giving up.");
576
591
  this.emitter?.({
577
592
  type: "sse_dead",
578
593
  properties: { error: err.message, attempts: attempt },
@@ -668,10 +683,10 @@ export class OpenCodeProvider {
668
683
  });
669
684
  }
670
685
  }
671
- console.log(`[sse] Re-synced messages for busy session ${sessionId} after reconnect`);
686
+ this.debugLog(`[sse] Re-synced messages for busy session ${sessionId} after reconnect`);
672
687
  }
673
688
  catch (err) {
674
- console.warn(`[sse] Failed to refresh messages for busy session ${sessionId}:`, err.message);
689
+ this.debugWarn(`[sse] Failed to refresh messages for busy session ${sessionId}:`, err.message);
675
690
  }
676
691
  }
677
692
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lunel-cli",
3
- "version": "0.1.115",
3
+ "version": "0.1.117",
4
4
  "author": [
5
5
  {
6
6
  "name": "Soham Bharambe",