lunel-cli 0.1.53 → 0.1.55
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/dist/ai/codex.d.ts +7 -0
- package/dist/ai/codex.js +187 -37
- package/package.json +1 -1
package/dist/ai/codex.d.ts
CHANGED
|
@@ -58,10 +58,14 @@ export declare class CodexProvider implements AIProvider {
|
|
|
58
58
|
private ensureAssistantMessage;
|
|
59
59
|
private upsertLocalMessagePart;
|
|
60
60
|
private fetchServerThreads;
|
|
61
|
+
private refreshSessionMetadata;
|
|
62
|
+
private fetchServerThreadsByArchiveState;
|
|
61
63
|
private fetchModels;
|
|
62
64
|
private parseThreadListEntry;
|
|
63
65
|
private hasNextCursor;
|
|
64
66
|
private ingestThreadMetadata;
|
|
67
|
+
private reconcileSessionsWithServer;
|
|
68
|
+
private mergeSession;
|
|
65
69
|
private upsertSession;
|
|
66
70
|
private ensureLocalSession;
|
|
67
71
|
private resolveSessionFromPayload;
|
|
@@ -87,6 +91,7 @@ export declare class CodexProvider implements AIProvider {
|
|
|
87
91
|
private extractUpdatedAt;
|
|
88
92
|
private readTimestamp;
|
|
89
93
|
private firstString;
|
|
94
|
+
private firstStringFromSources;
|
|
90
95
|
private readArray;
|
|
91
96
|
private readString;
|
|
92
97
|
private asRecord;
|
|
@@ -102,7 +107,9 @@ export declare class CodexProvider implements AIProvider {
|
|
|
102
107
|
private renderFileChangeEntriesBody;
|
|
103
108
|
private renderUnifiedDiffBody;
|
|
104
109
|
private extractCanonicalPatch;
|
|
110
|
+
private normalizeDisplayPath;
|
|
105
111
|
private normalizedFileChangeStatus;
|
|
106
112
|
private extractToolInput;
|
|
113
|
+
private extractCommandExecutionInput;
|
|
107
114
|
private describeCompletedItemOutput;
|
|
108
115
|
}
|
package/dist/ai/codex.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// over stdin/stdout. Maps Codex's thread/turn model onto Lunel's AIProvider
|
|
3
3
|
// contract using the same thread/list + thread/read flow used by Remodex.
|
|
4
4
|
import * as crypto from "crypto";
|
|
5
|
+
import * as path from "path";
|
|
5
6
|
import { spawn } from "child_process";
|
|
6
7
|
import { createInterface } from "readline";
|
|
7
8
|
const THREAD_LIST_SOURCE_KINDS = ["cli", "vscode", "appServer", "exec", "unknown"];
|
|
@@ -61,14 +62,20 @@ export class CodexProvider {
|
|
|
61
62
|
title: threadTitle,
|
|
62
63
|
createdAt: this.extractCreatedAt(result) ?? Date.now(),
|
|
63
64
|
updatedAt: this.extractUpdatedAt(result) ?? Date.now(),
|
|
65
|
+
archived: false,
|
|
64
66
|
});
|
|
65
67
|
return { session: this.toSessionInfo(session) };
|
|
66
68
|
}
|
|
67
69
|
async listSessions() {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
const activeThreads = await this.fetchServerThreads();
|
|
71
|
+
let archivedThreads = [];
|
|
72
|
+
try {
|
|
73
|
+
archivedThreads = await this.fetchServerThreadsByArchiveState(true);
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Some Codex runtimes may not support archived thread listing.
|
|
71
77
|
}
|
|
78
|
+
this.reconcileSessionsWithServer(activeThreads, archivedThreads);
|
|
72
79
|
const sessions = Array.from(this.sessions.values())
|
|
73
80
|
.sort((a, b) => a.updatedAt - b.updatedAt)
|
|
74
81
|
.map((session) => this.toSessionInfo(session));
|
|
@@ -103,12 +110,14 @@ export class CodexProvider {
|
|
|
103
110
|
const historyMessages = this.decodeMessagesFromThreadRead(sessionId, threadObject);
|
|
104
111
|
if (historyMessages.length > 0) {
|
|
105
112
|
session.messages = historyMessages;
|
|
106
|
-
session.updatedAt = this.extractUpdatedAt(threadObject) ?? session.updatedAt;
|
|
107
|
-
session.createdAt = this.extractCreatedAt(threadObject) ?? session.createdAt;
|
|
108
|
-
const title = this.extractThreadTitle(threadObject);
|
|
109
|
-
if (title)
|
|
110
|
-
session.title = title;
|
|
111
113
|
}
|
|
114
|
+
this.upsertSession({
|
|
115
|
+
id: sessionId,
|
|
116
|
+
title: this.extractThreadTitle(threadObject),
|
|
117
|
+
createdAt: this.extractCreatedAt(threadObject) ?? session.createdAt,
|
|
118
|
+
updatedAt: this.extractUpdatedAt(threadObject) ?? session.updatedAt,
|
|
119
|
+
archived: false,
|
|
120
|
+
});
|
|
112
121
|
return { messages: session.messages };
|
|
113
122
|
}
|
|
114
123
|
async prompt(sessionId, text, model, agent) {
|
|
@@ -297,15 +306,24 @@ export class CodexProvider {
|
|
|
297
306
|
case "thread/started":
|
|
298
307
|
case "thread/name/updated":
|
|
299
308
|
if (session) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
309
|
+
this.upsertSession({
|
|
310
|
+
id: session.id,
|
|
311
|
+
title: this.extractThreadTitle(params),
|
|
312
|
+
createdAt: this.extractCreatedAt(params) ?? session.createdAt,
|
|
313
|
+
updatedAt: this.extractUpdatedAt(params) ?? Date.now(),
|
|
314
|
+
archived: false,
|
|
315
|
+
}, true);
|
|
305
316
|
}
|
|
306
317
|
return;
|
|
307
318
|
case "thread/status/changed":
|
|
308
319
|
if (session) {
|
|
320
|
+
this.upsertSession({
|
|
321
|
+
id: session.id,
|
|
322
|
+
title: this.extractThreadTitle(params),
|
|
323
|
+
createdAt: this.extractCreatedAt(params) ?? session.createdAt,
|
|
324
|
+
updatedAt: this.extractUpdatedAt(params) ?? Date.now(),
|
|
325
|
+
archived: session.archived,
|
|
326
|
+
}, true);
|
|
309
327
|
this.emitter?.({
|
|
310
328
|
type: "session.status",
|
|
311
329
|
properties: { sessionID: session.id, status: params.status ?? params },
|
|
@@ -330,6 +348,9 @@ export class CodexProvider {
|
|
|
330
348
|
session.activeTurnId = undefined;
|
|
331
349
|
session.updatedAt = Date.now();
|
|
332
350
|
this.finishAssistantTurn(session, params, method === "turn/failed");
|
|
351
|
+
this.refreshSessionMetadata(session.id).catch(() => {
|
|
352
|
+
// Best-effort metadata refresh for title/preview updates after a turn ends.
|
|
353
|
+
});
|
|
333
354
|
this.emitter?.({ type: "session.idle", properties: { sessionID: session.id } });
|
|
334
355
|
if (method === "turn/failed") {
|
|
335
356
|
const error = this.readString(this.asRecord(params.error).message) ?? "Turn failed";
|
|
@@ -605,12 +626,34 @@ export class CodexProvider {
|
|
|
605
626
|
message.time = Date.now();
|
|
606
627
|
}
|
|
607
628
|
async fetchServerThreads() {
|
|
629
|
+
return this.fetchServerThreadsByArchiveState(false);
|
|
630
|
+
}
|
|
631
|
+
async refreshSessionMetadata(sessionId) {
|
|
632
|
+
const session = this.sessions.get(sessionId);
|
|
633
|
+
const result = await this.call("thread/read", {
|
|
634
|
+
threadId: sessionId,
|
|
635
|
+
includeTurns: false,
|
|
636
|
+
});
|
|
637
|
+
const threadObject = this.extractThreadObject(result);
|
|
638
|
+
if (!threadObject || Object.keys(threadObject).length === 0) {
|
|
639
|
+
return;
|
|
640
|
+
}
|
|
641
|
+
this.upsertSession({
|
|
642
|
+
id: sessionId,
|
|
643
|
+
title: this.extractThreadTitle(threadObject),
|
|
644
|
+
createdAt: this.extractCreatedAt(threadObject) ?? session?.createdAt ?? Date.now(),
|
|
645
|
+
updatedAt: this.extractUpdatedAt(threadObject) ?? session?.updatedAt ?? Date.now(),
|
|
646
|
+
archived: false,
|
|
647
|
+
}, true);
|
|
648
|
+
}
|
|
649
|
+
async fetchServerThreadsByArchiveState(archived) {
|
|
608
650
|
const threads = [];
|
|
609
651
|
let nextCursor = null;
|
|
610
652
|
let hasRequestedFirstPage = false;
|
|
611
653
|
do {
|
|
612
654
|
const result = await this.call("thread/list", {
|
|
613
655
|
sourceKinds: THREAD_LIST_SOURCE_KINDS,
|
|
656
|
+
archived,
|
|
614
657
|
cursor: nextCursor,
|
|
615
658
|
});
|
|
616
659
|
const payload = this.asRecord(result);
|
|
@@ -622,7 +665,7 @@ export class CodexProvider {
|
|
|
622
665
|
? payload.threads
|
|
623
666
|
: [];
|
|
624
667
|
for (const entry of page) {
|
|
625
|
-
const parsed = this.parseThreadListEntry(entry);
|
|
668
|
+
const parsed = this.parseThreadListEntry(entry, archived);
|
|
626
669
|
if (parsed)
|
|
627
670
|
threads.push(parsed);
|
|
628
671
|
}
|
|
@@ -664,7 +707,7 @@ export class CodexProvider {
|
|
|
664
707
|
})
|
|
665
708
|
.filter((value) => Boolean(value));
|
|
666
709
|
}
|
|
667
|
-
parseThreadListEntry(value) {
|
|
710
|
+
parseThreadListEntry(value, archived = false) {
|
|
668
711
|
const obj = this.asRecord(value);
|
|
669
712
|
const id = this.extractThreadId(obj);
|
|
670
713
|
if (!id)
|
|
@@ -674,6 +717,7 @@ export class CodexProvider {
|
|
|
674
717
|
title: this.extractThreadTitle(obj),
|
|
675
718
|
createdAt: this.extractCreatedAt(obj),
|
|
676
719
|
updatedAt: this.extractUpdatedAt(obj),
|
|
720
|
+
archived,
|
|
677
721
|
};
|
|
678
722
|
}
|
|
679
723
|
hasNextCursor(value) {
|
|
@@ -687,7 +731,7 @@ export class CodexProvider {
|
|
|
687
731
|
const threadId = this.extractThreadId(payload);
|
|
688
732
|
if (!threadId)
|
|
689
733
|
return;
|
|
690
|
-
const title = this.extractThreadTitleFromUnknown(payload)
|
|
734
|
+
const title = this.extractThreadTitleFromUnknown(payload);
|
|
691
735
|
this.upsertSession({
|
|
692
736
|
id: threadId,
|
|
693
737
|
title,
|
|
@@ -695,22 +739,65 @@ export class CodexProvider {
|
|
|
695
739
|
updatedAt: this.extractUpdatedAt(payload) ?? Date.now(),
|
|
696
740
|
});
|
|
697
741
|
}
|
|
698
|
-
|
|
699
|
-
const
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
return existing;
|
|
742
|
+
reconcileSessionsWithServer(activeThreads, archivedThreads = []) {
|
|
743
|
+
const localSessions = this.sessions;
|
|
744
|
+
const merged = new Map();
|
|
745
|
+
for (const thread of activeThreads) {
|
|
746
|
+
const session = this.mergeSession(localSessions.get(thread.id), { ...thread, archived: false });
|
|
747
|
+
merged.set(thread.id, session);
|
|
705
748
|
}
|
|
706
|
-
const
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
749
|
+
for (const thread of archivedThreads) {
|
|
750
|
+
if (merged.has(thread.id))
|
|
751
|
+
continue;
|
|
752
|
+
const session = this.mergeSession(localSessions.get(thread.id), { ...thread, archived: true });
|
|
753
|
+
merged.set(thread.id, session);
|
|
754
|
+
}
|
|
755
|
+
for (const [id, session] of localSessions.entries()) {
|
|
756
|
+
if (!merged.has(id)) {
|
|
757
|
+
merged.set(id, session);
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
this.sessions = merged;
|
|
761
|
+
}
|
|
762
|
+
mergeSession(existing, input) {
|
|
763
|
+
if (!existing) {
|
|
764
|
+
return {
|
|
765
|
+
id: input.id,
|
|
766
|
+
title: input.title ?? "Conversation",
|
|
767
|
+
createdAt: input.createdAt ?? Date.now(),
|
|
768
|
+
updatedAt: input.updatedAt ?? Date.now(),
|
|
769
|
+
archived: input.archived ?? false,
|
|
770
|
+
messages: [],
|
|
771
|
+
};
|
|
772
|
+
}
|
|
773
|
+
existing.title = input.title ?? existing.title;
|
|
774
|
+
existing.createdAt = input.createdAt ?? existing.createdAt;
|
|
775
|
+
existing.updatedAt = input.updatedAt != null
|
|
776
|
+
? Math.max(existing.updatedAt, input.updatedAt)
|
|
777
|
+
: existing.updatedAt;
|
|
778
|
+
existing.archived = input.archived ?? existing.archived ?? false;
|
|
779
|
+
return existing;
|
|
780
|
+
}
|
|
781
|
+
upsertSession(input, emitUpdated = false) {
|
|
782
|
+
const existing = this.sessions.get(input.id);
|
|
783
|
+
const before = existing
|
|
784
|
+
? {
|
|
785
|
+
title: existing.title,
|
|
786
|
+
createdAt: existing.createdAt,
|
|
787
|
+
updatedAt: existing.updatedAt,
|
|
788
|
+
archived: existing.archived ?? false,
|
|
789
|
+
}
|
|
790
|
+
: null;
|
|
791
|
+
const session = this.mergeSession(existing, input);
|
|
713
792
|
this.sessions.set(input.id, session);
|
|
793
|
+
const changed = !before
|
|
794
|
+
|| before.title !== session.title
|
|
795
|
+
|| before.createdAt !== session.createdAt
|
|
796
|
+
|| before.updatedAt !== session.updatedAt
|
|
797
|
+
|| before.archived !== (session.archived ?? false);
|
|
798
|
+
if (emitUpdated && changed) {
|
|
799
|
+
this.emitter?.({ type: "session.updated", properties: { info: this.toSessionInfo(session) } });
|
|
800
|
+
}
|
|
714
801
|
return session;
|
|
715
802
|
}
|
|
716
803
|
ensureLocalSession(sessionId) {
|
|
@@ -802,6 +889,7 @@ export class CodexProvider {
|
|
|
802
889
|
}
|
|
803
890
|
if (type === "commandexecution" || type === "enteredreviewmode" || type === "contextcompaction") {
|
|
804
891
|
const output = this.decodeCommandExecutionItemText(itemObject, type);
|
|
892
|
+
const input = this.extractCommandExecutionInput(itemObject);
|
|
805
893
|
messages.push({
|
|
806
894
|
id: itemId,
|
|
807
895
|
role: "assistant",
|
|
@@ -810,6 +898,7 @@ export class CodexProvider {
|
|
|
810
898
|
type: "tool",
|
|
811
899
|
name: type === "commandexecution" ? "command" : "system",
|
|
812
900
|
toolName: type === "commandexecution" ? "command" : "system",
|
|
901
|
+
...(input ? { input } : {}),
|
|
813
902
|
output,
|
|
814
903
|
state: "completed",
|
|
815
904
|
sessionID: threadId,
|
|
@@ -963,10 +1052,16 @@ export class CodexProvider {
|
|
|
963
1052
|
}
|
|
964
1053
|
extractThreadTitle(payload) {
|
|
965
1054
|
const thread = this.asRecord(payload.thread);
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1055
|
+
const explicitName = this.readString(thread.name) ?? this.readString(payload.name);
|
|
1056
|
+
if (explicitName)
|
|
1057
|
+
return explicitName;
|
|
1058
|
+
const explicitTitle = this.readString(thread.title) ?? this.readString(payload.title);
|
|
1059
|
+
if (explicitTitle)
|
|
1060
|
+
return explicitTitle;
|
|
1061
|
+
const preview = this.readString(thread.preview) ?? this.readString(payload.preview);
|
|
1062
|
+
if (!preview)
|
|
1063
|
+
return undefined;
|
|
1064
|
+
return preview.charAt(0).toUpperCase() + preview.slice(1);
|
|
970
1065
|
}
|
|
971
1066
|
extractTurnId(payload) {
|
|
972
1067
|
return (this.readString(payload.turnId)
|
|
@@ -1054,6 +1149,14 @@ export class CodexProvider {
|
|
|
1054
1149
|
}
|
|
1055
1150
|
return undefined;
|
|
1056
1151
|
}
|
|
1152
|
+
firstStringFromSources(sources, keys) {
|
|
1153
|
+
for (const source of sources) {
|
|
1154
|
+
const value = this.firstString(source, keys);
|
|
1155
|
+
if (value)
|
|
1156
|
+
return value;
|
|
1157
|
+
}
|
|
1158
|
+
return undefined;
|
|
1159
|
+
}
|
|
1057
1160
|
readArray(value) {
|
|
1058
1161
|
return Array.isArray(value) ? value : [];
|
|
1059
1162
|
}
|
|
@@ -1190,12 +1293,13 @@ export class CodexProvider {
|
|
|
1190
1293
|
"old_path",
|
|
1191
1294
|
"oldPath",
|
|
1192
1295
|
"from",
|
|
1193
|
-
])
|
|
1296
|
+
]);
|
|
1297
|
+
const normalizedPath = this.normalizeDisplayPath(path) ?? "file";
|
|
1194
1298
|
const kind = this.firstString(obj, ["kind", "type", "action", "status"]) ?? "change";
|
|
1195
1299
|
const diff = this.firstString(obj, ["diff", "unified_diff", "unifiedDiff", "patch"]) ?? "";
|
|
1196
1300
|
return diff
|
|
1197
|
-
? `Path: ${
|
|
1198
|
-
: `Path: ${
|
|
1301
|
+
? `Path: ${normalizedPath}\nKind: ${kind}\n\n\`\`\`diff\n${diff}\n\`\`\``
|
|
1302
|
+
: `Path: ${normalizedPath}\nKind: ${kind}`;
|
|
1199
1303
|
})
|
|
1200
1304
|
.filter(Boolean);
|
|
1201
1305
|
return rendered.length > 0 ? rendered.join("\n\n---\n\n") : undefined;
|
|
@@ -1225,6 +1329,20 @@ export class CodexProvider {
|
|
|
1225
1329
|
.join("\n");
|
|
1226
1330
|
return patch.trim() || undefined;
|
|
1227
1331
|
}
|
|
1332
|
+
normalizeDisplayPath(rawPath) {
|
|
1333
|
+
if (!rawPath)
|
|
1334
|
+
return undefined;
|
|
1335
|
+
const trimmed = rawPath.trim();
|
|
1336
|
+
if (!trimmed)
|
|
1337
|
+
return undefined;
|
|
1338
|
+
if (!path.isAbsolute(trimmed))
|
|
1339
|
+
return trimmed;
|
|
1340
|
+
const relative = path.relative(process.cwd(), trimmed);
|
|
1341
|
+
if (!relative || relative.startsWith("..")) {
|
|
1342
|
+
return trimmed;
|
|
1343
|
+
}
|
|
1344
|
+
return relative;
|
|
1345
|
+
}
|
|
1228
1346
|
normalizedFileChangeStatus(itemObject, isCompleted) {
|
|
1229
1347
|
const status = this.readString(itemObject.status)
|
|
1230
1348
|
?? this.readString(this.asRecord(itemObject.status).type)
|
|
@@ -1235,8 +1353,40 @@ export class CodexProvider {
|
|
|
1235
1353
|
return isCompleted ? "completed" : "inProgress";
|
|
1236
1354
|
}
|
|
1237
1355
|
extractToolInput(item, params) {
|
|
1356
|
+
const normalizedType = this.normalizeStructuredType(this.readString(item.type) ?? "");
|
|
1357
|
+
if (normalizedType === "commandexecution") {
|
|
1358
|
+
return this.extractCommandExecutionInput(item, params);
|
|
1359
|
+
}
|
|
1238
1360
|
return item.input ?? item.command ?? item.path ?? item.args ?? params.command ?? params.path ?? undefined;
|
|
1239
1361
|
}
|
|
1362
|
+
extractCommandExecutionInput(item, params) {
|
|
1363
|
+
const event = this.asRecord(params?.event);
|
|
1364
|
+
const nestedItem = this.asRecord(event.item);
|
|
1365
|
+
const sources = [item, params ?? {}, event, nestedItem];
|
|
1366
|
+
const command = this.firstStringFromSources(sources, [
|
|
1367
|
+
"command",
|
|
1368
|
+
"cmd",
|
|
1369
|
+
"raw_command",
|
|
1370
|
+
"rawCommand",
|
|
1371
|
+
"invocation",
|
|
1372
|
+
"input",
|
|
1373
|
+
"fullCommand",
|
|
1374
|
+
"full_command",
|
|
1375
|
+
]);
|
|
1376
|
+
const cwd = this.firstStringFromSources(sources, [
|
|
1377
|
+
"cwd",
|
|
1378
|
+
"workdir",
|
|
1379
|
+
"workingDirectory",
|
|
1380
|
+
"working_directory",
|
|
1381
|
+
]);
|
|
1382
|
+
if (command && cwd)
|
|
1383
|
+
return { command, cwd };
|
|
1384
|
+
if (command)
|
|
1385
|
+
return { command };
|
|
1386
|
+
if (cwd)
|
|
1387
|
+
return { cwd };
|
|
1388
|
+
return undefined;
|
|
1389
|
+
}
|
|
1240
1390
|
describeCompletedItemOutput(item, params, normalizedType) {
|
|
1241
1391
|
if (normalizedType === "commandexecution") {
|
|
1242
1392
|
return this.firstString(item, ["stdout", "stderr", "text", "message", "summary"]);
|