lunel-cli 0.1.117 → 0.1.118
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 +2 -1
- package/dist/ai/codex.js +58 -0
- package/dist/ai/index.d.ts +18 -0
- package/dist/ai/index.js +45 -0
- package/dist/ai/interface.d.ts +14 -0
- package/dist/ai/opencode.d.ts +5 -1
- package/dist/ai/opencode.js +83 -0
- package/dist/index.js +56 -0
- package/package.json +1 -1
package/dist/ai/codex.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AIProvider, AiEventEmitter, CodexPromptOptions, FileAttachment, ModelSelector, MessageInfo, ProviderInfo, SessionInfo, ShareInfo } from "./interface.js";
|
|
1
|
+
import type { AIProvider, AiEventEmitter, CodexPromptOptions, AiSyncState, FileAttachment, ModelSelector, MessageInfo, ProviderInfo, SessionInfo, ShareInfo } from "./interface.js";
|
|
2
2
|
export declare class CodexProvider implements AIProvider {
|
|
3
3
|
private proc;
|
|
4
4
|
private shuttingDown;
|
|
@@ -36,6 +36,7 @@ export declare class CodexProvider implements AIProvider {
|
|
|
36
36
|
getMessages(sessionId: string): Promise<{
|
|
37
37
|
messages: MessageInfo[];
|
|
38
38
|
}>;
|
|
39
|
+
syncState(sessionIds?: string[]): Promise<AiSyncState>;
|
|
39
40
|
prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string, files?: FileAttachment[], codexOptions?: CodexPromptOptions): Promise<{
|
|
40
41
|
ack: true;
|
|
41
42
|
}>;
|
package/dist/ai/codex.js
CHANGED
|
@@ -268,6 +268,64 @@ export class CodexProvider {
|
|
|
268
268
|
}
|
|
269
269
|
return { messages: session.messages };
|
|
270
270
|
}
|
|
271
|
+
async syncState(sessionIds = []) {
|
|
272
|
+
await this.listSessions().catch(() => ({ sessions: [] }));
|
|
273
|
+
const messageSessionIds = new Set(sessionIds);
|
|
274
|
+
for (const session of this.sessions.values()) {
|
|
275
|
+
if (session.activeTurnId)
|
|
276
|
+
messageSessionIds.add(session.id);
|
|
277
|
+
}
|
|
278
|
+
for (const pending of this.pendingPermissionRequestIds.values()) {
|
|
279
|
+
if (pending.sessionId)
|
|
280
|
+
messageSessionIds.add(pending.sessionId);
|
|
281
|
+
}
|
|
282
|
+
for (const pending of this.pendingQuestionRequestIds.values()) {
|
|
283
|
+
if (pending.sessionId)
|
|
284
|
+
messageSessionIds.add(pending.sessionId);
|
|
285
|
+
}
|
|
286
|
+
const messages = {};
|
|
287
|
+
const statuses = [];
|
|
288
|
+
await Promise.allSettled(Array.from(messageSessionIds).map(async (sessionId) => {
|
|
289
|
+
const response = await this.getMessages(sessionId);
|
|
290
|
+
messages[sessionId] = response.messages;
|
|
291
|
+
const session = this.sessions.get(sessionId);
|
|
292
|
+
if (!session)
|
|
293
|
+
return;
|
|
294
|
+
const activeTurnId = await this.resolveInFlightTurnId(sessionId).catch(() => undefined);
|
|
295
|
+
session.activeTurnId = activeTurnId;
|
|
296
|
+
if (activeTurnId) {
|
|
297
|
+
statuses.push({
|
|
298
|
+
sessionID: sessionId,
|
|
299
|
+
status: { type: "running", activeTurnId },
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}));
|
|
303
|
+
return {
|
|
304
|
+
sessions: Array.from(this.sessions.values()).map((session) => this.toSessionInfo(session)),
|
|
305
|
+
statuses,
|
|
306
|
+
messages,
|
|
307
|
+
pendingPermissions: Array.from(this.pendingPermissionRequestIds.entries()).map(([id, pending]) => ({
|
|
308
|
+
id,
|
|
309
|
+
sessionID: pending.sessionId,
|
|
310
|
+
messageID: pending.messageId,
|
|
311
|
+
callID: pending.callId,
|
|
312
|
+
type: pending.method,
|
|
313
|
+
title: pending.method,
|
|
314
|
+
metadata: { method: pending.method },
|
|
315
|
+
})),
|
|
316
|
+
pendingQuestions: Array.from(this.pendingQuestionRequestIds.entries()).map(([id, pending]) => ({
|
|
317
|
+
id,
|
|
318
|
+
sessionID: pending.sessionId,
|
|
319
|
+
questions: [],
|
|
320
|
+
tool: {
|
|
321
|
+
...(pending.messageId ? { messageID: pending.messageId } : {}),
|
|
322
|
+
...(pending.callId ? { callID: pending.callId } : {}),
|
|
323
|
+
},
|
|
324
|
+
})),
|
|
325
|
+
statusAuthoritative: true,
|
|
326
|
+
generatedAt: Date.now(),
|
|
327
|
+
};
|
|
328
|
+
}
|
|
271
329
|
async prompt(sessionId, text, model, agent, files = [], codexOptions) {
|
|
272
330
|
const session = this.ensureLocalSession(sessionId);
|
|
273
331
|
session.updatedAt = Date.now();
|
package/dist/ai/index.d.ts
CHANGED
|
@@ -14,6 +14,24 @@ export declare class AiManager {
|
|
|
14
14
|
backend: AiBackend;
|
|
15
15
|
}>;
|
|
16
16
|
}>;
|
|
17
|
+
syncState(sessionIds?: Partial<Record<AiBackend, string[]>>): Promise<{
|
|
18
|
+
sessions: Array<Record<string, unknown> & {
|
|
19
|
+
backend: AiBackend;
|
|
20
|
+
}>;
|
|
21
|
+
statuses: Array<Record<string, unknown> & {
|
|
22
|
+
backend: AiBackend;
|
|
23
|
+
}>;
|
|
24
|
+
messages: Record<string, unknown>;
|
|
25
|
+
pendingPermissions: Array<Record<string, unknown> & {
|
|
26
|
+
backend: AiBackend;
|
|
27
|
+
}>;
|
|
28
|
+
pendingQuestions: Array<Record<string, unknown> & {
|
|
29
|
+
backend: AiBackend;
|
|
30
|
+
}>;
|
|
31
|
+
statusAuthoritativeByBackend: Partial<Record<AiBackend, boolean>>;
|
|
32
|
+
syncedBackends: AiBackend[];
|
|
33
|
+
generatedAt: number;
|
|
34
|
+
}>;
|
|
17
35
|
createSession(backend: AiBackend, title?: string): Promise<{
|
|
18
36
|
session: import("./interface.js").SessionInfo;
|
|
19
37
|
}>;
|
package/dist/ai/index.js
CHANGED
|
@@ -68,6 +68,51 @@ export class AiManager {
|
|
|
68
68
|
const sessions = results.flatMap((r) => (r.status === "fulfilled" ? r.value : []));
|
|
69
69
|
return { sessions };
|
|
70
70
|
}
|
|
71
|
+
async syncState(sessionIds = {}) {
|
|
72
|
+
const results = await Promise.allSettled(this._available.map(async (backend) => {
|
|
73
|
+
const provider = this._providers[backend];
|
|
74
|
+
const state = provider.syncState
|
|
75
|
+
? await provider.syncState(sessionIds[backend])
|
|
76
|
+
: {
|
|
77
|
+
sessions: (await provider.listSessions()).sessions,
|
|
78
|
+
statuses: [],
|
|
79
|
+
messages: {},
|
|
80
|
+
generatedAt: Date.now(),
|
|
81
|
+
};
|
|
82
|
+
return { backend, state };
|
|
83
|
+
}));
|
|
84
|
+
const sessions = [];
|
|
85
|
+
const statuses = [];
|
|
86
|
+
const messages = {};
|
|
87
|
+
const pendingPermissions = [];
|
|
88
|
+
const pendingQuestions = [];
|
|
89
|
+
const statusAuthoritativeByBackend = {};
|
|
90
|
+
const syncedBackends = [];
|
|
91
|
+
for (const result of results) {
|
|
92
|
+
if (result.status !== "fulfilled")
|
|
93
|
+
continue;
|
|
94
|
+
const { backend, state } = result.value;
|
|
95
|
+
syncedBackends.push(backend);
|
|
96
|
+
statusAuthoritativeByBackend[backend] = state.statusAuthoritative !== false;
|
|
97
|
+
sessions.push(...(state.sessions ?? []).map((session) => ({ ...session, backend })));
|
|
98
|
+
statuses.push(...(state.statuses ?? []).map((status) => ({ ...status, backend })));
|
|
99
|
+
for (const [sessionId, value] of Object.entries(state.messages ?? {})) {
|
|
100
|
+
messages[`${backend}:${sessionId}`] = value;
|
|
101
|
+
}
|
|
102
|
+
pendingPermissions.push(...(state.pendingPermissions ?? []).map((permission) => ({ ...permission, backend })));
|
|
103
|
+
pendingQuestions.push(...(state.pendingQuestions ?? []).map((question) => ({ ...question, backend })));
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
sessions,
|
|
107
|
+
statuses,
|
|
108
|
+
messages,
|
|
109
|
+
pendingPermissions,
|
|
110
|
+
pendingQuestions,
|
|
111
|
+
statusAuthoritativeByBackend,
|
|
112
|
+
syncedBackends,
|
|
113
|
+
generatedAt: Date.now(),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
71
116
|
// Session management — all require explicit backend
|
|
72
117
|
createSession(backend, title) { return this.get(backend).createSession(title); }
|
|
73
118
|
getSession(backend, id) { return this.get(backend).getSession(id); }
|
package/dist/ai/interface.d.ts
CHANGED
|
@@ -35,6 +35,19 @@ export interface ProviderInfo {
|
|
|
35
35
|
default: Record<string, string>;
|
|
36
36
|
[key: string]: unknown;
|
|
37
37
|
}
|
|
38
|
+
export interface AiSessionStatus {
|
|
39
|
+
sessionID: string;
|
|
40
|
+
status: Record<string, unknown> | string;
|
|
41
|
+
}
|
|
42
|
+
export interface AiSyncState {
|
|
43
|
+
sessions: unknown[];
|
|
44
|
+
statuses: AiSessionStatus[];
|
|
45
|
+
messages: Record<string, MessageInfo[]>;
|
|
46
|
+
pendingPermissions?: Record<string, unknown>[];
|
|
47
|
+
pendingQuestions?: Record<string, unknown>[];
|
|
48
|
+
statusAuthoritative?: boolean;
|
|
49
|
+
generatedAt: number;
|
|
50
|
+
}
|
|
38
51
|
/**
|
|
39
52
|
* Every AI backend (OpenCode, Codex, …) implements this interface.
|
|
40
53
|
* Method names map 1-to-1 with the "ai" namespace actions in index.ts.
|
|
@@ -67,6 +80,7 @@ export interface AIProvider {
|
|
|
67
80
|
getMessages(sessionId: string): Promise<{
|
|
68
81
|
messages: MessageInfo[];
|
|
69
82
|
}>;
|
|
83
|
+
syncState?(sessionIds?: string[]): Promise<AiSyncState>;
|
|
70
84
|
prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string, files?: FileAttachment[], codexOptions?: CodexPromptOptions): Promise<{
|
|
71
85
|
ack: true;
|
|
72
86
|
}>;
|
package/dist/ai/opencode.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { AIProvider, AiEventEmitter, CodexPromptOptions, FileAttachment, ModelSelector, MessageInfo, ProviderInfo, SessionInfo, ShareInfo } from "./interface.js";
|
|
1
|
+
import type { AIProvider, AiEventEmitter, CodexPromptOptions, AiSyncState, FileAttachment, ModelSelector, MessageInfo, ProviderInfo, SessionInfo, ShareInfo } from "./interface.js";
|
|
2
2
|
export declare class OpenCodeProvider implements AIProvider {
|
|
3
3
|
private client;
|
|
4
4
|
private server;
|
|
@@ -33,6 +33,7 @@ export declare class OpenCodeProvider implements AIProvider {
|
|
|
33
33
|
getMessages(sessionId: string): Promise<{
|
|
34
34
|
messages: MessageInfo[];
|
|
35
35
|
}>;
|
|
36
|
+
syncState(sessionIds?: string[]): Promise<AiSyncState>;
|
|
36
37
|
prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string, files?: FileAttachment[], codexOptions?: CodexPromptOptions): Promise<{
|
|
37
38
|
ack: true;
|
|
38
39
|
}>;
|
|
@@ -57,6 +58,9 @@ export declare class OpenCodeProvider implements AIProvider {
|
|
|
57
58
|
private sendPromptAsync;
|
|
58
59
|
private reconcileOpenCodeState;
|
|
59
60
|
private refreshBusySessionMessages;
|
|
61
|
+
private fetchSessionStatuses;
|
|
62
|
+
private listPendingPermissions;
|
|
63
|
+
private listPendingQuestions;
|
|
60
64
|
private refreshSessionsMetadata;
|
|
61
65
|
private refreshPendingPermissions;
|
|
62
66
|
private refreshPendingQuestions;
|
package/dist/ai/opencode.js
CHANGED
|
@@ -393,6 +393,52 @@ export class OpenCodeProvider {
|
|
|
393
393
|
throw err;
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
|
+
async syncState(sessionIds = []) {
|
|
397
|
+
const [sessionsResult, statusesResult, permissionsResult, questionsResult] = await Promise.allSettled([
|
|
398
|
+
this.listSessions(),
|
|
399
|
+
this.fetchSessionStatuses(),
|
|
400
|
+
this.listPendingPermissions(),
|
|
401
|
+
this.listPendingQuestions(),
|
|
402
|
+
]);
|
|
403
|
+
const sessions = sessionsResult.status === "fulfilled"
|
|
404
|
+
? (sessionsResult.value.sessions ?? [])
|
|
405
|
+
: [];
|
|
406
|
+
const statuses = statusesResult.status === "fulfilled" ? statusesResult.value : [];
|
|
407
|
+
const pendingPermissions = permissionsResult.status === "fulfilled" ? permissionsResult.value : [];
|
|
408
|
+
const pendingQuestions = questionsResult.status === "fulfilled" ? questionsResult.value : [];
|
|
409
|
+
const messageSessionIds = new Set(sessionIds);
|
|
410
|
+
for (const entry of statuses) {
|
|
411
|
+
const statusObj = typeof entry.status === "object" && entry.status !== null ? entry.status : {};
|
|
412
|
+
const statusType = typeof statusObj.type === "string" ? statusObj.type.toLowerCase() : String(entry.status ?? "").toLowerCase();
|
|
413
|
+
if (statusType === "busy" || statusType === "running" || statusType === "working" || statusType === "retry") {
|
|
414
|
+
messageSessionIds.add(entry.sessionID);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
for (const permission of pendingPermissions) {
|
|
418
|
+
const sessionID = this.readString(permission.sessionID) ?? this.readString(permission.sessionId);
|
|
419
|
+
if (sessionID)
|
|
420
|
+
messageSessionIds.add(sessionID);
|
|
421
|
+
}
|
|
422
|
+
for (const question of pendingQuestions) {
|
|
423
|
+
const sessionID = this.readString(question.sessionID) ?? this.readString(question.sessionId);
|
|
424
|
+
if (sessionID)
|
|
425
|
+
messageSessionIds.add(sessionID);
|
|
426
|
+
}
|
|
427
|
+
const messages = {};
|
|
428
|
+
await Promise.allSettled(Array.from(messageSessionIds).map(async (sessionId) => {
|
|
429
|
+
const response = await this.getMessages(sessionId);
|
|
430
|
+
messages[sessionId] = response.messages;
|
|
431
|
+
}));
|
|
432
|
+
return {
|
|
433
|
+
sessions,
|
|
434
|
+
statuses,
|
|
435
|
+
messages,
|
|
436
|
+
pendingPermissions,
|
|
437
|
+
pendingQuestions,
|
|
438
|
+
statusAuthoritative: statusesResult.status === "fulfilled",
|
|
439
|
+
generatedAt: Date.now(),
|
|
440
|
+
};
|
|
441
|
+
}
|
|
396
442
|
// -------------------------------------------------------------------------
|
|
397
443
|
// Interaction
|
|
398
444
|
// -------------------------------------------------------------------------
|
|
@@ -690,6 +736,43 @@ export class OpenCodeProvider {
|
|
|
690
736
|
}
|
|
691
737
|
}
|
|
692
738
|
}
|
|
739
|
+
async fetchSessionStatuses() {
|
|
740
|
+
const payload = await this.fetchOpenCodeJson("/session/status", { method: "GET" });
|
|
741
|
+
if (!payload || typeof payload !== "object")
|
|
742
|
+
return [];
|
|
743
|
+
return Object.entries(payload).map(([sessionID, status]) => ({
|
|
744
|
+
sessionID,
|
|
745
|
+
status: status,
|
|
746
|
+
}));
|
|
747
|
+
}
|
|
748
|
+
async listPendingPermissions() {
|
|
749
|
+
const permissionApi = this.client?.permission;
|
|
750
|
+
if (!permissionApi?.list)
|
|
751
|
+
return [];
|
|
752
|
+
const response = await permissionApi.list();
|
|
753
|
+
const data = Array.isArray(response.data) ? response.data : [];
|
|
754
|
+
return data
|
|
755
|
+
.map((entry) => normalizePermissionProperties(this.asRecord(entry)))
|
|
756
|
+
.filter((entry) => !!this.readString(entry.id));
|
|
757
|
+
}
|
|
758
|
+
async listPendingQuestions() {
|
|
759
|
+
const data = await this.fetchOpenCodeJson("/question", { method: "GET" });
|
|
760
|
+
const questions = Array.isArray(data) ? data : [];
|
|
761
|
+
return questions
|
|
762
|
+
.flatMap((entry) => {
|
|
763
|
+
const question = this.asRecord(entry);
|
|
764
|
+
const id = this.readString(question.id);
|
|
765
|
+
const sessionID = this.readString(question.sessionID) ?? this.readString(question.sessionId);
|
|
766
|
+
if (!id || !sessionID)
|
|
767
|
+
return [];
|
|
768
|
+
return [{
|
|
769
|
+
id,
|
|
770
|
+
sessionID,
|
|
771
|
+
questions: Array.isArray(question.questions) ? question.questions : [],
|
|
772
|
+
tool: typeof question.tool === "object" && question.tool !== null ? question.tool : undefined,
|
|
773
|
+
}];
|
|
774
|
+
});
|
|
775
|
+
}
|
|
693
776
|
async refreshSessionsMetadata() {
|
|
694
777
|
const response = await this.client.session.list();
|
|
695
778
|
const sessions = Array.isArray(response.data) ? response.data : [];
|
package/dist/index.js
CHANGED
|
@@ -398,6 +398,51 @@ async function handleFsLs(payload) {
|
|
|
398
398
|
}
|
|
399
399
|
return { path: reqPath, entries: result };
|
|
400
400
|
}
|
|
401
|
+
async function handleFsSearchFiles(payload) {
|
|
402
|
+
const reqPath = payload.path || ".";
|
|
403
|
+
const query = typeof payload.query === "string" ? payload.query.trim().toLowerCase() : "";
|
|
404
|
+
const maxResults = Math.max(1, Math.min(payload.maxResults || 10, 10));
|
|
405
|
+
const safePath = assertSafePath(reqPath);
|
|
406
|
+
const rootIgnore = await loadGitignore(ROOT_DIR);
|
|
407
|
+
const matches = [];
|
|
408
|
+
async function searchDir(dirPath, relativePath, ig) {
|
|
409
|
+
if (matches.length >= maxResults)
|
|
410
|
+
return;
|
|
411
|
+
const localIgnore = ignore().add(ig);
|
|
412
|
+
try {
|
|
413
|
+
const localGitignorePath = path.join(dirPath, ".gitignore");
|
|
414
|
+
const content = await fs.readFile(localGitignorePath, "utf-8");
|
|
415
|
+
localIgnore.add(content);
|
|
416
|
+
}
|
|
417
|
+
catch {
|
|
418
|
+
// No local .gitignore
|
|
419
|
+
}
|
|
420
|
+
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
421
|
+
for (const entry of entries) {
|
|
422
|
+
if (matches.length >= maxResults)
|
|
423
|
+
break;
|
|
424
|
+
const relPath = relativePath ? `${relativePath}/${entry.name}` : entry.name;
|
|
425
|
+
const checkPath = entry.isDirectory() ? `${relPath}/` : relPath;
|
|
426
|
+
if (localIgnore.ignores(checkPath))
|
|
427
|
+
continue;
|
|
428
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
429
|
+
if (entry.isDirectory()) {
|
|
430
|
+
await searchDir(fullPath, relPath, localIgnore);
|
|
431
|
+
}
|
|
432
|
+
else if (entry.isFile() && relPath.toLowerCase().includes(query)) {
|
|
433
|
+
matches.push({ path: relPath });
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
const stat = await fs.stat(safePath);
|
|
438
|
+
if (stat.isDirectory()) {
|
|
439
|
+
await searchDir(safePath, reqPath === "." ? "" : reqPath, rootIgnore);
|
|
440
|
+
}
|
|
441
|
+
else if (stat.isFile() && reqPath.toLowerCase().includes(query)) {
|
|
442
|
+
matches.push({ path: reqPath });
|
|
443
|
+
}
|
|
444
|
+
return { path: reqPath, query, maxResults, files: matches };
|
|
445
|
+
}
|
|
401
446
|
async function handleFsStat(payload) {
|
|
402
447
|
const reqPath = payload.path;
|
|
403
448
|
if (!reqPath)
|
|
@@ -2473,6 +2518,9 @@ async function processMessage(message) {
|
|
|
2473
2518
|
case "ls":
|
|
2474
2519
|
result = await handleFsLs(payload);
|
|
2475
2520
|
break;
|
|
2521
|
+
case "searchFiles":
|
|
2522
|
+
result = await handleFsSearchFiles(payload);
|
|
2523
|
+
break;
|
|
2476
2524
|
case "stat":
|
|
2477
2525
|
result = await handleFsStat(payload);
|
|
2478
2526
|
break;
|
|
@@ -2668,6 +2716,14 @@ async function processMessage(message) {
|
|
|
2668
2716
|
case "listSessions":
|
|
2669
2717
|
result = await aiManager.listAllSessions();
|
|
2670
2718
|
break;
|
|
2719
|
+
case "syncState": {
|
|
2720
|
+
const rawSessionIds = payload.sessionIds;
|
|
2721
|
+
result = await aiManager.syncState({
|
|
2722
|
+
opencode: Array.isArray(rawSessionIds?.opencode) ? rawSessionIds.opencode.filter((id) => typeof id === "string") : undefined,
|
|
2723
|
+
codex: Array.isArray(rawSessionIds?.codex) ? rawSessionIds.codex.filter((id) => typeof id === "string") : undefined,
|
|
2724
|
+
});
|
|
2725
|
+
break;
|
|
2726
|
+
}
|
|
2671
2727
|
case "getSession":
|
|
2672
2728
|
result = await aiManager.getSession(backend, payload.id);
|
|
2673
2729
|
break;
|