lunel-cli 0.1.114 → 0.1.116
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 +18 -2
- package/dist/ai/codex.js +524 -90
- package/dist/ai/index.d.ts +3 -0
- package/dist/ai/index.js +1 -0
- package/dist/ai/interface.d.ts +4 -0
- package/dist/ai/opencode.d.ts +5 -1
- package/dist/ai/opencode.js +317 -34
- package/dist/index.js +125 -6
- package/package.json +1 -1
package/dist/ai/index.d.ts
CHANGED
|
@@ -23,6 +23,9 @@ export declare class AiManager {
|
|
|
23
23
|
deleteSession(backend: AiBackend, id: string): Promise<{
|
|
24
24
|
deleted: boolean;
|
|
25
25
|
}>;
|
|
26
|
+
renameSession(backend: AiBackend, id: string, title: string): Promise<{
|
|
27
|
+
session: import("./interface.js").SessionInfo;
|
|
28
|
+
}>;
|
|
26
29
|
getMessages(backend: AiBackend, sessionId: string): Promise<{
|
|
27
30
|
messages: import("./interface.js").MessageInfo[];
|
|
28
31
|
}>;
|
package/dist/ai/index.js
CHANGED
|
@@ -72,6 +72,7 @@ export class AiManager {
|
|
|
72
72
|
createSession(backend, title) { return this.get(backend).createSession(title); }
|
|
73
73
|
getSession(backend, id) { return this.get(backend).getSession(id); }
|
|
74
74
|
deleteSession(backend, id) { return this.get(backend).deleteSession(id); }
|
|
75
|
+
renameSession(backend, id, title) { return this.get(backend).renameSession(id, title); }
|
|
75
76
|
getMessages(backend, sessionId) { return this.get(backend).getMessages(sessionId); }
|
|
76
77
|
prompt(backend, sessionId, text, model, agent, files, codexOptions) {
|
|
77
78
|
this.get(backend).setActiveSession?.(sessionId);
|
package/dist/ai/interface.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface ModelSelector {
|
|
|
10
10
|
export interface CodexPromptOptions {
|
|
11
11
|
reasoningEffort?: "low" | "medium" | "high";
|
|
12
12
|
speed?: "fast" | "balanced" | "quality";
|
|
13
|
+
permissionMode?: "default" | "full-access";
|
|
13
14
|
}
|
|
14
15
|
export interface FileAttachment {
|
|
15
16
|
type: "file";
|
|
@@ -60,6 +61,9 @@ export interface AIProvider {
|
|
|
60
61
|
deleteSession(id: string): Promise<{
|
|
61
62
|
deleted: boolean;
|
|
62
63
|
}>;
|
|
64
|
+
renameSession(id: string, title: string): Promise<{
|
|
65
|
+
session: SessionInfo;
|
|
66
|
+
}>;
|
|
63
67
|
getMessages(sessionId: string): Promise<{
|
|
64
68
|
messages: MessageInfo[];
|
|
65
69
|
}>;
|
package/dist/ai/opencode.d.ts
CHANGED
|
@@ -24,6 +24,9 @@ export declare class OpenCodeProvider implements AIProvider {
|
|
|
24
24
|
deleteSession(id: string): Promise<{
|
|
25
25
|
deleted: boolean;
|
|
26
26
|
}>;
|
|
27
|
+
renameSession(id: string, title: string): Promise<{
|
|
28
|
+
session: SessionInfo;
|
|
29
|
+
}>;
|
|
27
30
|
getMessages(sessionId: string): Promise<{
|
|
28
31
|
messages: MessageInfo[];
|
|
29
32
|
}>;
|
|
@@ -50,12 +53,13 @@ export declare class OpenCodeProvider implements AIProvider {
|
|
|
50
53
|
private runSseLoop;
|
|
51
54
|
private sendPromptAsync;
|
|
52
55
|
private reconcileOpenCodeState;
|
|
56
|
+
private refreshBusySessionMessages;
|
|
53
57
|
private refreshSessionsMetadata;
|
|
54
58
|
private refreshPendingPermissions;
|
|
55
59
|
private refreshPendingQuestions;
|
|
56
60
|
private fetchOpenCodeJson;
|
|
57
61
|
private refreshSessionStatuses;
|
|
58
62
|
private trackPermissionEvent;
|
|
59
|
-
private asRecord;
|
|
60
63
|
private readString;
|
|
64
|
+
private asRecord;
|
|
61
65
|
}
|
package/dist/ai/opencode.js
CHANGED
|
@@ -23,6 +23,236 @@ function requireData(response, label) {
|
|
|
23
23
|
}
|
|
24
24
|
return response.data;
|
|
25
25
|
}
|
|
26
|
+
function asRecord(value) {
|
|
27
|
+
return value && typeof value === "object" ? value : {};
|
|
28
|
+
}
|
|
29
|
+
function readString(value) {
|
|
30
|
+
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
31
|
+
}
|
|
32
|
+
function normalizeToolOutput(output, metadata) {
|
|
33
|
+
const attachments = Array.isArray(metadata.attachments) ? metadata.attachments : [];
|
|
34
|
+
if (attachments.length === 0)
|
|
35
|
+
return output;
|
|
36
|
+
const attachmentLines = attachments
|
|
37
|
+
.map((entry) => {
|
|
38
|
+
const file = asRecord(entry);
|
|
39
|
+
const filename = readString(file.filename)
|
|
40
|
+
?? readString(file.path)
|
|
41
|
+
?? readString(file.url)
|
|
42
|
+
?? "attachment";
|
|
43
|
+
return `- ${filename}`;
|
|
44
|
+
})
|
|
45
|
+
.filter((line) => line.trim().length > 0);
|
|
46
|
+
if (attachmentLines.length === 0)
|
|
47
|
+
return output;
|
|
48
|
+
if (!output.trim()) {
|
|
49
|
+
return `Attachments:\n${attachmentLines.join("\n")}`;
|
|
50
|
+
}
|
|
51
|
+
return `${output}\n\nAttachments:\n${attachmentLines.join("\n")}`;
|
|
52
|
+
}
|
|
53
|
+
function buildPatchSummary(part) {
|
|
54
|
+
const hash = readString(part.hash);
|
|
55
|
+
const files = Array.isArray(part.files)
|
|
56
|
+
? part.files.map((value) => String(value)).filter((value) => value.trim().length > 0)
|
|
57
|
+
: [];
|
|
58
|
+
const lines = [];
|
|
59
|
+
if (hash)
|
|
60
|
+
lines.push(`Patch hash: ${hash}`);
|
|
61
|
+
if (files.length > 0) {
|
|
62
|
+
lines.push("Files:");
|
|
63
|
+
for (const file of files)
|
|
64
|
+
lines.push(`- ${file}`);
|
|
65
|
+
}
|
|
66
|
+
return lines.join("\n");
|
|
67
|
+
}
|
|
68
|
+
function normalizeOpenCodePart(part) {
|
|
69
|
+
const raw = asRecord(part);
|
|
70
|
+
const type = readString(raw.type);
|
|
71
|
+
if (!type)
|
|
72
|
+
return raw;
|
|
73
|
+
if (type === "tool") {
|
|
74
|
+
const tool = readString(raw.tool) ?? "tool";
|
|
75
|
+
const state = asRecord(raw.state);
|
|
76
|
+
const status = readString(state.status) ?? "running";
|
|
77
|
+
const metadata = asRecord(state.metadata ?? raw.metadata);
|
|
78
|
+
const normalized = {
|
|
79
|
+
...raw,
|
|
80
|
+
type: "tool",
|
|
81
|
+
toolName: tool,
|
|
82
|
+
name: tool,
|
|
83
|
+
state: status,
|
|
84
|
+
input: asRecord(state.input),
|
|
85
|
+
metadata,
|
|
86
|
+
};
|
|
87
|
+
const title = readString(state.title);
|
|
88
|
+
if (title)
|
|
89
|
+
normalized.title = title;
|
|
90
|
+
const rawText = readString(state.raw);
|
|
91
|
+
if (rawText)
|
|
92
|
+
normalized.raw = rawText;
|
|
93
|
+
const time = asRecord(state.time);
|
|
94
|
+
if (Object.keys(time).length > 0) {
|
|
95
|
+
normalized.time = time;
|
|
96
|
+
}
|
|
97
|
+
if (status === "completed") {
|
|
98
|
+
const output = typeof state.output === "string" ? state.output : "";
|
|
99
|
+
normalized.output = normalizeToolOutput(output, {
|
|
100
|
+
...metadata,
|
|
101
|
+
attachments: state.attachments,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
else if (status === "error") {
|
|
105
|
+
normalized.error = readString(state.error) ?? "Tool failed";
|
|
106
|
+
const errorMessage = readString(state.error);
|
|
107
|
+
if (errorMessage)
|
|
108
|
+
normalized.output = errorMessage;
|
|
109
|
+
}
|
|
110
|
+
const attachments = Array.isArray(state.attachments) ? state.attachments : [];
|
|
111
|
+
if (attachments.length > 0) {
|
|
112
|
+
normalized.attachments = attachments.map((entry) => normalizeOpenCodePart(entry));
|
|
113
|
+
}
|
|
114
|
+
return normalized;
|
|
115
|
+
}
|
|
116
|
+
if (type === "step-start") {
|
|
117
|
+
const snapshot = readString(raw.snapshot);
|
|
118
|
+
return {
|
|
119
|
+
...raw,
|
|
120
|
+
type: "step-start",
|
|
121
|
+
title: snapshot ? `Step started · ${snapshot}` : "Step started",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (type === "step-finish") {
|
|
125
|
+
const reason = readString(raw.reason);
|
|
126
|
+
return {
|
|
127
|
+
...raw,
|
|
128
|
+
type: "step-finish",
|
|
129
|
+
title: reason ? `Step finished · ${reason}` : "Step finished",
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (type === "patch") {
|
|
133
|
+
return {
|
|
134
|
+
...raw,
|
|
135
|
+
type: "file-change",
|
|
136
|
+
title: "File changes",
|
|
137
|
+
output: buildPatchSummary(raw),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (type === "subtask") {
|
|
141
|
+
return {
|
|
142
|
+
...raw,
|
|
143
|
+
type: "tool",
|
|
144
|
+
toolName: "subtask",
|
|
145
|
+
name: "subtask",
|
|
146
|
+
state: "completed",
|
|
147
|
+
input: {
|
|
148
|
+
prompt: readString(raw.prompt) ?? "",
|
|
149
|
+
description: readString(raw.description) ?? "",
|
|
150
|
+
agent: readString(raw.agent) ?? "",
|
|
151
|
+
...(readString(raw.command) ? { command: readString(raw.command) } : {}),
|
|
152
|
+
},
|
|
153
|
+
output: readString(raw.description) ?? readString(raw.prompt) ?? "Subtask requested",
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (type === "agent") {
|
|
157
|
+
const name = readString(raw.name) ?? "Agent";
|
|
158
|
+
return {
|
|
159
|
+
...raw,
|
|
160
|
+
type: "step-start",
|
|
161
|
+
title: `Agent · ${name}`,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
if (type === "retry") {
|
|
165
|
+
const attempt = raw.attempt;
|
|
166
|
+
const error = asRecord(raw.error);
|
|
167
|
+
const message = readString(error.message) ?? "Retry requested";
|
|
168
|
+
return {
|
|
169
|
+
...raw,
|
|
170
|
+
type: "tool",
|
|
171
|
+
toolName: "retry",
|
|
172
|
+
name: "retry",
|
|
173
|
+
state: "error",
|
|
174
|
+
input: {
|
|
175
|
+
attempt,
|
|
176
|
+
},
|
|
177
|
+
error: message,
|
|
178
|
+
output: message,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (type === "compaction") {
|
|
182
|
+
const auto = raw.auto === true;
|
|
183
|
+
const overflow = raw.overflow === true;
|
|
184
|
+
return {
|
|
185
|
+
...raw,
|
|
186
|
+
type: "step-start",
|
|
187
|
+
title: `Context compacted${auto ? " · auto" : ""}${overflow ? " · overflow" : ""}`,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
if (type === "snapshot") {
|
|
191
|
+
return {
|
|
192
|
+
...raw,
|
|
193
|
+
type: "step-start",
|
|
194
|
+
title: "Workspace snapshot",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
return raw;
|
|
198
|
+
}
|
|
199
|
+
function normalizeOpenCodeMessage(message) {
|
|
200
|
+
return {
|
|
201
|
+
id: message.info.id,
|
|
202
|
+
role: message.info.role,
|
|
203
|
+
parts: (message.parts || []).map((part) => normalizeOpenCodePart(part)),
|
|
204
|
+
time: message.info.time,
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
function normalizePermissionProperties(properties) {
|
|
208
|
+
const tool = asRecord(properties.tool);
|
|
209
|
+
const metadata = properties.metadata && typeof properties.metadata === "object"
|
|
210
|
+
? properties.metadata
|
|
211
|
+
: properties;
|
|
212
|
+
return {
|
|
213
|
+
id: readString(properties.id),
|
|
214
|
+
sessionID: readString(properties.sessionID) ?? readString(properties.sessionId),
|
|
215
|
+
messageID: readString(properties.messageID) ?? readString(tool.messageID),
|
|
216
|
+
callID: readString(properties.callID) ?? readString(tool.callID),
|
|
217
|
+
type: readString(properties.type) ?? readString(properties.permission) ?? "permission",
|
|
218
|
+
title: readString(properties.title)
|
|
219
|
+
?? readString(properties.permission)
|
|
220
|
+
?? "Permission requested",
|
|
221
|
+
metadata,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function normalizeOpenCodeEvent(event) {
|
|
225
|
+
const { type, properties } = event;
|
|
226
|
+
if (type === "message.part.updated") {
|
|
227
|
+
return {
|
|
228
|
+
type,
|
|
229
|
+
properties: {
|
|
230
|
+
...properties,
|
|
231
|
+
part: normalizeOpenCodePart(properties.part),
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
if (type === "permission.updated" || type === "permission.asked") {
|
|
236
|
+
return {
|
|
237
|
+
type: "permission.updated",
|
|
238
|
+
properties: normalizePermissionProperties(properties),
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
if (type === "permission.replied") {
|
|
242
|
+
return {
|
|
243
|
+
type: "permission.replied",
|
|
244
|
+
properties: {
|
|
245
|
+
sessionID: readString(properties.sessionID) ?? readString(properties.sessionId),
|
|
246
|
+
permissionId: readString(properties.permissionID)
|
|
247
|
+
?? readString(properties.requestID)
|
|
248
|
+
?? readString(properties.permissionId)
|
|
249
|
+
?? readString(properties.id),
|
|
250
|
+
response: readString(properties.response) ?? readString(properties.reply),
|
|
251
|
+
},
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
return event;
|
|
255
|
+
}
|
|
26
256
|
export class OpenCodeProvider {
|
|
27
257
|
client = null;
|
|
28
258
|
server = null;
|
|
@@ -111,7 +341,23 @@ export class OpenCodeProvider {
|
|
|
111
341
|
}
|
|
112
342
|
async deleteSession(id) {
|
|
113
343
|
const response = await this.client.session.delete({ path: { id } });
|
|
114
|
-
|
|
344
|
+
const raw = response;
|
|
345
|
+
if (raw.error) {
|
|
346
|
+
const errMsg = typeof raw.error === "string"
|
|
347
|
+
? raw.error
|
|
348
|
+
: JSON.stringify(raw.error);
|
|
349
|
+
throw new Error(errMsg);
|
|
350
|
+
}
|
|
351
|
+
// Treat any non-error delete response as success. Some SDK/runtime combos
|
|
352
|
+
// return inconsistent boolean payloads despite successful deletion.
|
|
353
|
+
return { deleted: true };
|
|
354
|
+
}
|
|
355
|
+
async renameSession(id, title) {
|
|
356
|
+
const response = await this.client.session.update({
|
|
357
|
+
path: { id },
|
|
358
|
+
body: { title },
|
|
359
|
+
});
|
|
360
|
+
return { session: requireData(response, "session.update") };
|
|
115
361
|
}
|
|
116
362
|
// -------------------------------------------------------------------------
|
|
117
363
|
// Messages
|
|
@@ -122,12 +368,7 @@ export class OpenCodeProvider {
|
|
|
122
368
|
try {
|
|
123
369
|
const response = await this.client.session.messages({ path: { id: sessionId } });
|
|
124
370
|
const raw = requireData(response, "session.messages");
|
|
125
|
-
const messages = raw.map((m) => (
|
|
126
|
-
id: m.info.id,
|
|
127
|
-
role: m.info.role,
|
|
128
|
-
parts: m.parts || [],
|
|
129
|
-
time: m.info.time,
|
|
130
|
-
}));
|
|
371
|
+
const messages = raw.map((m) => normalizeOpenCodeMessage(m));
|
|
131
372
|
if (VERBOSE_AI_LOGS)
|
|
132
373
|
console.log("[ai] getMessages returned", messages.length, "messages");
|
|
133
374
|
return { messages };
|
|
@@ -314,10 +555,15 @@ export class OpenCodeProvider {
|
|
|
314
555
|
if (base.type !== "server.heartbeat") {
|
|
315
556
|
console.log("[sse]", base.type);
|
|
316
557
|
}
|
|
317
|
-
|
|
318
|
-
|
|
558
|
+
const normalizedEvent = normalizeOpenCodeEvent({
|
|
559
|
+
type: base.type,
|
|
560
|
+
properties: base.properties || {},
|
|
561
|
+
});
|
|
562
|
+
this.trackPermissionEvent(normalizedEvent.type, normalizedEvent.properties || {});
|
|
563
|
+
this.emitter?.(normalizedEvent);
|
|
319
564
|
}
|
|
320
565
|
console.log("[sse] Event stream ended, reconnecting...");
|
|
566
|
+
attempt++;
|
|
321
567
|
}
|
|
322
568
|
catch (err) {
|
|
323
569
|
if (this.shuttingDown)
|
|
@@ -379,6 +625,55 @@ export class OpenCodeProvider {
|
|
|
379
625
|
this.refreshPendingQuestions(),
|
|
380
626
|
this.refreshSessionStatuses(),
|
|
381
627
|
]);
|
|
628
|
+
await this.refreshBusySessionMessages();
|
|
629
|
+
}
|
|
630
|
+
async refreshBusySessionMessages() {
|
|
631
|
+
const server = this.server;
|
|
632
|
+
const authHeader = this.authHeader;
|
|
633
|
+
if (!server || !authHeader)
|
|
634
|
+
return;
|
|
635
|
+
const statusUrl = new URL("/session/status", server.url);
|
|
636
|
+
const statusResp = await fetch(statusUrl, {
|
|
637
|
+
headers: { Authorization: authHeader, accept: "application/json" },
|
|
638
|
+
}).catch(() => null);
|
|
639
|
+
if (!statusResp?.ok)
|
|
640
|
+
return;
|
|
641
|
+
const payload = await statusResp.json().catch(() => null);
|
|
642
|
+
if (!payload || typeof payload !== "object")
|
|
643
|
+
return;
|
|
644
|
+
for (const [sessionId, status] of Object.entries(payload)) {
|
|
645
|
+
const statusObj = status;
|
|
646
|
+
const statusType = typeof statusObj?.type === "string" ? statusObj.type.toLowerCase() : "";
|
|
647
|
+
if (statusType !== "busy")
|
|
648
|
+
continue;
|
|
649
|
+
try {
|
|
650
|
+
const response = await this.client.session.messages({ path: { id: sessionId } });
|
|
651
|
+
const raw = Array.isArray(response.data) ? response.data : [];
|
|
652
|
+
for (const m of raw) {
|
|
653
|
+
const msgObj = this.asRecord(m);
|
|
654
|
+
const info = this.asRecord(msgObj.info);
|
|
655
|
+
const parts = Array.isArray(msgObj.parts) ? msgObj.parts : [];
|
|
656
|
+
const msgId = this.readString(info.id);
|
|
657
|
+
if (!msgId)
|
|
658
|
+
continue;
|
|
659
|
+
this.emitter?.({ type: "message.updated", properties: { info } });
|
|
660
|
+
for (const part of parts) {
|
|
661
|
+
const partObj = normalizeOpenCodePart(part);
|
|
662
|
+
this.emitter?.({
|
|
663
|
+
type: "message.part.updated",
|
|
664
|
+
properties: {
|
|
665
|
+
part: { ...partObj, sessionID: sessionId, messageID: msgId },
|
|
666
|
+
message: { sessionID: sessionId, id: msgId, role: info.role },
|
|
667
|
+
},
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
console.log(`[sse] Re-synced messages for busy session ${sessionId} after reconnect`);
|
|
672
|
+
}
|
|
673
|
+
catch (err) {
|
|
674
|
+
console.warn(`[sse] Failed to refresh messages for busy session ${sessionId}:`, err.message);
|
|
675
|
+
}
|
|
676
|
+
}
|
|
382
677
|
}
|
|
383
678
|
async refreshSessionsMetadata() {
|
|
384
679
|
const response = await this.client.session.list();
|
|
@@ -414,19 +709,7 @@ export class OpenCodeProvider {
|
|
|
414
709
|
this.knownPendingPermissionIds.add(id);
|
|
415
710
|
this.emitter?.({
|
|
416
711
|
type: "permission.updated",
|
|
417
|
-
properties:
|
|
418
|
-
id,
|
|
419
|
-
sessionID: this.readString(permission.sessionID) ?? this.readString(permission.sessionId),
|
|
420
|
-
messageID: this.readString(this.asRecord(permission.tool).messageID),
|
|
421
|
-
callID: this.readString(this.asRecord(permission.tool).callID),
|
|
422
|
-
type: this.readString(permission.permission) ?? "permission",
|
|
423
|
-
title: this.readString(permission.title)
|
|
424
|
-
?? this.readString(permission.permission)
|
|
425
|
-
?? "Permission requested",
|
|
426
|
-
metadata: permission.metadata && typeof permission.metadata === "object"
|
|
427
|
-
? permission.metadata
|
|
428
|
-
: permission,
|
|
429
|
-
},
|
|
712
|
+
properties: normalizePermissionProperties(permission),
|
|
430
713
|
});
|
|
431
714
|
}
|
|
432
715
|
for (const id of Array.from(this.knownPendingPermissionIds)) {
|
|
@@ -530,40 +813,40 @@ export class OpenCodeProvider {
|
|
|
530
813
|
}
|
|
531
814
|
trackPermissionEvent(type, properties) {
|
|
532
815
|
if (type === "permission.updated") {
|
|
533
|
-
const id =
|
|
816
|
+
const id = readString(properties.id);
|
|
534
817
|
if (id) {
|
|
535
818
|
this.knownPendingPermissionIds.add(id);
|
|
536
819
|
}
|
|
537
820
|
return;
|
|
538
821
|
}
|
|
539
822
|
if (type === "permission.replied") {
|
|
540
|
-
const id =
|
|
541
|
-
??
|
|
542
|
-
??
|
|
823
|
+
const id = readString(properties.permissionId)
|
|
824
|
+
?? readString(properties.requestID)
|
|
825
|
+
?? readString(properties.id);
|
|
543
826
|
if (id) {
|
|
544
827
|
this.knownPendingPermissionIds.delete(id);
|
|
545
828
|
}
|
|
546
829
|
}
|
|
547
830
|
if (type === "question.asked") {
|
|
548
|
-
const id =
|
|
831
|
+
const id = readString(properties.id);
|
|
549
832
|
if (id) {
|
|
550
833
|
this.knownPendingQuestionIds.add(id);
|
|
551
834
|
}
|
|
552
835
|
return;
|
|
553
836
|
}
|
|
554
837
|
if (type === "question.replied" || type === "question.rejected") {
|
|
555
|
-
const id =
|
|
556
|
-
??
|
|
557
|
-
??
|
|
838
|
+
const id = readString(properties.requestID)
|
|
839
|
+
?? readString(properties.questionId)
|
|
840
|
+
?? readString(properties.id);
|
|
558
841
|
if (id) {
|
|
559
842
|
this.knownPendingQuestionIds.delete(id);
|
|
560
843
|
}
|
|
561
844
|
}
|
|
562
845
|
}
|
|
563
|
-
asRecord(value) {
|
|
564
|
-
return value && typeof value === "object" ? value : {};
|
|
565
|
-
}
|
|
566
846
|
readString(value) {
|
|
567
|
-
return
|
|
847
|
+
return readString(value);
|
|
848
|
+
}
|
|
849
|
+
asRecord(value) {
|
|
850
|
+
return asRecord(value);
|
|
568
851
|
}
|
|
569
852
|
}
|
package/dist/index.js
CHANGED
|
@@ -31,6 +31,10 @@ const __require = createRequire(import.meta.url);
|
|
|
31
31
|
const VERSION = __require("../package.json").version;
|
|
32
32
|
const VERBOSE_AI_LOGS = process.env.LUNEL_DEBUG_AI === "1";
|
|
33
33
|
const PTY_RELEASE_BASE_URL = "https://github.com/lunel-dev/lunel/releases/download/v0";
|
|
34
|
+
const AI_RUNTIME_INSTALL_CANDIDATES = {
|
|
35
|
+
opencode: ["opencode-ai", "@opencode-ai/cli", "opencode"],
|
|
36
|
+
codex: ["@openai/codex", "codex"],
|
|
37
|
+
};
|
|
34
38
|
const PTY_RELEASES = {
|
|
35
39
|
"linux:x64": {
|
|
36
40
|
fileName: "lunel-pty-linux-x8664-0",
|
|
@@ -529,6 +533,34 @@ async function loadGitignore(dirPath) {
|
|
|
529
533
|
}
|
|
530
534
|
return ig;
|
|
531
535
|
}
|
|
536
|
+
const KNOWN_BINARY_EXTENSIONS = new Set([
|
|
537
|
+
".png", ".jpg", ".jpeg", ".gif", ".bmp", ".webp", ".avif", ".ico", ".icns", ".heic", ".heif", ".tiff", ".tif",
|
|
538
|
+
".psd", ".ai", ".eps",
|
|
539
|
+
".mp3", ".wav", ".ogg", ".m4a", ".flac", ".aac",
|
|
540
|
+
".mp4", ".mov", ".avi", ".mkv", ".webm", ".wmv", ".m4v",
|
|
541
|
+
".pdf", ".zip", ".gz", ".tgz", ".bz2", ".xz", ".7z", ".rar", ".tar",
|
|
542
|
+
".exe", ".dll", ".so", ".dylib", ".bin", ".class", ".o", ".obj", ".a", ".lib",
|
|
543
|
+
".ttf", ".otf", ".woff", ".woff2", ".eot",
|
|
544
|
+
]);
|
|
545
|
+
function isLikelyBinaryContent(content) {
|
|
546
|
+
if (content.length === 0)
|
|
547
|
+
return false;
|
|
548
|
+
const sample = content.subarray(0, Math.min(content.length, 8192));
|
|
549
|
+
let suspicious = 0;
|
|
550
|
+
for (const byte of sample) {
|
|
551
|
+
if (byte === 0)
|
|
552
|
+
return true; // Null bytes strongly indicate binary data.
|
|
553
|
+
if (byte < 7 || (byte > 13 && byte < 32))
|
|
554
|
+
suspicious += 1;
|
|
555
|
+
}
|
|
556
|
+
return suspicious / sample.length > 0.3;
|
|
557
|
+
}
|
|
558
|
+
function shouldSkipAsBinary(filePath, content) {
|
|
559
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
560
|
+
if (KNOWN_BINARY_EXTENSIONS.has(ext))
|
|
561
|
+
return true;
|
|
562
|
+
return isLikelyBinaryContent(content);
|
|
563
|
+
}
|
|
532
564
|
async function handleFsGrep(payload) {
|
|
533
565
|
const reqPath = payload.path || ".";
|
|
534
566
|
const pattern = payload.pattern;
|
|
@@ -553,12 +585,12 @@ async function handleFsGrep(payload) {
|
|
|
553
585
|
if (matches.length >= maxResults)
|
|
554
586
|
return;
|
|
555
587
|
try {
|
|
556
|
-
const
|
|
557
|
-
if (
|
|
588
|
+
const rawContent = await fs.readFile(filePath);
|
|
589
|
+
if (shouldSkipAsBinary(relativePath, rawContent)) {
|
|
558
590
|
regex.lastIndex = 0;
|
|
559
591
|
return;
|
|
560
592
|
}
|
|
561
|
-
const content =
|
|
593
|
+
const content = rawContent.toString("utf-8");
|
|
562
594
|
const lines = content.split("\n");
|
|
563
595
|
for (let i = 0; i < lines.length && matches.length < maxResults; i++) {
|
|
564
596
|
if (regex.test(lines[i])) {
|
|
@@ -927,9 +959,20 @@ async function handleGitDiscard(payload) {
|
|
|
927
959
|
await runGit(["clean", "-fd"]);
|
|
928
960
|
}
|
|
929
961
|
else if (paths && paths.length > 0) {
|
|
930
|
-
const
|
|
931
|
-
|
|
932
|
-
|
|
962
|
+
for (const filePath of paths) {
|
|
963
|
+
const tracked = await runGit(["ls-files", "--error-unmatch", "--", filePath]);
|
|
964
|
+
if (tracked.code === 0) {
|
|
965
|
+
const result = await runGit(["checkout", "--", filePath]);
|
|
966
|
+
if (result.code !== 0) {
|
|
967
|
+
throw Object.assign(new Error(result.stderr || `git checkout failed for ${filePath}`), { code: "EGIT" });
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
const cleanResult = await runGit(["clean", "-fd", "--", filePath]);
|
|
972
|
+
if (cleanResult.code !== 0) {
|
|
973
|
+
throw Object.assign(new Error(cleanResult.stderr || `git clean failed for ${filePath}`), { code: "EGIT" });
|
|
974
|
+
}
|
|
975
|
+
}
|
|
933
976
|
}
|
|
934
977
|
}
|
|
935
978
|
return {};
|
|
@@ -2631,6 +2674,9 @@ async function processMessage(message) {
|
|
|
2631
2674
|
case "deleteSession":
|
|
2632
2675
|
result = await aiManager.deleteSession(backend, payload.id);
|
|
2633
2676
|
break;
|
|
2677
|
+
case "renameSession":
|
|
2678
|
+
result = await aiManager.renameSession(backend, payload.id, payload.title);
|
|
2679
|
+
break;
|
|
2634
2680
|
case "getMessages":
|
|
2635
2681
|
result = await aiManager.getMessages(backend, payload.id);
|
|
2636
2682
|
break;
|
|
@@ -2945,6 +2991,78 @@ function displaySavedSessionNotice() {
|
|
|
2945
2991
|
console.log(`${red}${border}${reset}`);
|
|
2946
2992
|
console.log("");
|
|
2947
2993
|
}
|
|
2994
|
+
function isCommandAvailable(command) {
|
|
2995
|
+
const probe = spawnSync(command, ["--version"], {
|
|
2996
|
+
stdio: "ignore",
|
|
2997
|
+
shell: process.platform === "win32",
|
|
2998
|
+
});
|
|
2999
|
+
const err = probe.error;
|
|
3000
|
+
if (err && (err.code === "ENOENT" || err.code === "ENOTDIR")) {
|
|
3001
|
+
return false;
|
|
3002
|
+
}
|
|
3003
|
+
return !err;
|
|
3004
|
+
}
|
|
3005
|
+
function askYesNo(question, defaultValue = false) {
|
|
3006
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY)
|
|
3007
|
+
return Promise.resolve(false);
|
|
3008
|
+
return new Promise((resolve) => {
|
|
3009
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
3010
|
+
const suffix = defaultValue ? " [Y/n] " : " [y/N] ";
|
|
3011
|
+
rl.question(`${question}${suffix}`, (answer) => {
|
|
3012
|
+
rl.close();
|
|
3013
|
+
const normalized = answer.trim().toLowerCase();
|
|
3014
|
+
if (!normalized) {
|
|
3015
|
+
resolve(defaultValue);
|
|
3016
|
+
return;
|
|
3017
|
+
}
|
|
3018
|
+
resolve(normalized === "y" || normalized === "yes");
|
|
3019
|
+
});
|
|
3020
|
+
});
|
|
3021
|
+
}
|
|
3022
|
+
function installLatestNpmPackage(pkg) {
|
|
3023
|
+
const npmCommand = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
3024
|
+
const result = spawnSync(npmCommand, ["install", "-g", `${pkg}@latest`], {
|
|
3025
|
+
stdio: "inherit",
|
|
3026
|
+
shell: process.platform === "win32",
|
|
3027
|
+
env: process.env,
|
|
3028
|
+
});
|
|
3029
|
+
return !result.error && result.status === 0;
|
|
3030
|
+
}
|
|
3031
|
+
async function ensureAiCliRuntimes() {
|
|
3032
|
+
const missingBackends = Object.keys(AI_RUNTIME_INSTALL_CANDIDATES)
|
|
3033
|
+
.filter((backend) => !isCommandAvailable(backend));
|
|
3034
|
+
if (missingBackends.length === 0)
|
|
3035
|
+
return;
|
|
3036
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
3037
|
+
console.warn(`[ai] Missing runtimes: ${missingBackends.join(", ")}. Run in an interactive shell to install them.`);
|
|
3038
|
+
return;
|
|
3039
|
+
}
|
|
3040
|
+
const installPrompt = `Missing AI runtimes (${missingBackends.join(", ")}). Install latest versions now?`;
|
|
3041
|
+
const approved = await askYesNo(installPrompt, false);
|
|
3042
|
+
if (!approved) {
|
|
3043
|
+
console.warn("[ai] Skipping AI runtime installation.");
|
|
3044
|
+
return;
|
|
3045
|
+
}
|
|
3046
|
+
for (const backend of missingBackends) {
|
|
3047
|
+
if (isCommandAvailable(backend))
|
|
3048
|
+
continue;
|
|
3049
|
+
const candidates = AI_RUNTIME_INSTALL_CANDIDATES[backend];
|
|
3050
|
+
let installed = false;
|
|
3051
|
+
for (const pkg of candidates) {
|
|
3052
|
+
console.log(`[ai] Installing ${backend} via npm package ${pkg}@latest...`);
|
|
3053
|
+
if (!installLatestNpmPackage(pkg))
|
|
3054
|
+
continue;
|
|
3055
|
+
if (isCommandAvailable(backend)) {
|
|
3056
|
+
installed = true;
|
|
3057
|
+
console.log(`[ai] ${backend} installed successfully.`);
|
|
3058
|
+
break;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
if (!installed) {
|
|
3062
|
+
console.warn(`[ai] Failed to install ${backend}. You can install it manually and restart the CLI.`);
|
|
3063
|
+
}
|
|
3064
|
+
}
|
|
3065
|
+
}
|
|
2948
3066
|
function gracefulShutdown() {
|
|
2949
3067
|
shuttingDown = true;
|
|
2950
3068
|
console.log("\nShutting down...");
|
|
@@ -3125,6 +3243,7 @@ async function main() {
|
|
|
3125
3243
|
else {
|
|
3126
3244
|
debugLog(`PTY runtime unsupported on ${os.platform()}/${os.arch()}. Skipping prefetch.\n`);
|
|
3127
3245
|
}
|
|
3246
|
+
await ensureAiCliRuntimes();
|
|
3128
3247
|
// Start AI backends in the background so missing or slow AI runtimes never
|
|
3129
3248
|
// block QR/session startup for the rest of the CLI.
|
|
3130
3249
|
startAiManagerInBackground();
|