lunel-cli 0.1.116 → 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 +3 -1
- package/dist/ai/codex.js +65 -2
- 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 +8 -1
- package/dist/ai/opencode.js +108 -10
- 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;
|
|
@@ -14,6 +14,7 @@ export declare class CodexProvider implements AIProvider {
|
|
|
14
14
|
private assistantMessageIdByTurnId;
|
|
15
15
|
private partTextById;
|
|
16
16
|
private debugLog;
|
|
17
|
+
private debugHistory;
|
|
17
18
|
init(): Promise<void>;
|
|
18
19
|
destroy(): Promise<void>;
|
|
19
20
|
subscribe(emitter: AiEventEmitter): () => void;
|
|
@@ -35,6 +36,7 @@ export declare class CodexProvider implements AIProvider {
|
|
|
35
36
|
getMessages(sessionId: string): Promise<{
|
|
36
37
|
messages: MessageInfo[];
|
|
37
38
|
}>;
|
|
39
|
+
syncState(sessionIds?: string[]): Promise<AiSyncState>;
|
|
38
40
|
prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string, files?: FileAttachment[], codexOptions?: CodexPromptOptions): Promise<{
|
|
39
41
|
ack: true;
|
|
40
42
|
}>;
|
package/dist/ai/codex.js
CHANGED
|
@@ -57,6 +57,11 @@ export class CodexProvider {
|
|
|
57
57
|
const suffix = fields ? ` ${JSON.stringify(fields)}` : "";
|
|
58
58
|
console.log(`[codex] ${message}${suffix}`);
|
|
59
59
|
}
|
|
60
|
+
debugHistory(message, fields) {
|
|
61
|
+
if (!DEBUG_MODE)
|
|
62
|
+
return;
|
|
63
|
+
console.log(`[codex-history] ${message} ${JSON.stringify(fields)}`);
|
|
64
|
+
}
|
|
60
65
|
async init() {
|
|
61
66
|
if (DEBUG_MODE)
|
|
62
67
|
console.log("Starting Codex app-server...");
|
|
@@ -263,6 +268,64 @@ export class CodexProvider {
|
|
|
263
268
|
}
|
|
264
269
|
return { messages: session.messages };
|
|
265
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
|
+
}
|
|
266
329
|
async prompt(sessionId, text, model, agent, files = [], codexOptions) {
|
|
267
330
|
const session = this.ensureLocalSession(sessionId);
|
|
268
331
|
session.updatedAt = Date.now();
|
|
@@ -1337,12 +1400,12 @@ export class CodexProvider {
|
|
|
1337
1400
|
}),
|
|
1338
1401
|
};
|
|
1339
1402
|
});
|
|
1340
|
-
|
|
1403
|
+
this.debugHistory("thread/read summary", {
|
|
1341
1404
|
sessionId,
|
|
1342
1405
|
threadId: this.readString(threadObject.id) ?? null,
|
|
1343
1406
|
turnCount: turns.length,
|
|
1344
1407
|
turnSummaries,
|
|
1345
|
-
})
|
|
1408
|
+
});
|
|
1346
1409
|
}
|
|
1347
1410
|
decodeStoredToolLikePart(type, itemObject, threadId, messageId, itemId) {
|
|
1348
1411
|
if (type === "filechange" || type === "diff" || this.isFileChangeStructuredItem(type, itemObject)) {
|
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;
|
|
@@ -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;
|
|
@@ -30,6 +33,7 @@ export declare class OpenCodeProvider implements AIProvider {
|
|
|
30
33
|
getMessages(sessionId: string): Promise<{
|
|
31
34
|
messages: MessageInfo[];
|
|
32
35
|
}>;
|
|
36
|
+
syncState(sessionIds?: string[]): Promise<AiSyncState>;
|
|
33
37
|
prompt(sessionId: string, text: string, model?: ModelSelector, agent?: string, files?: FileAttachment[], codexOptions?: CodexPromptOptions): Promise<{
|
|
34
38
|
ack: true;
|
|
35
39
|
}>;
|
|
@@ -54,6 +58,9 @@ export declare class OpenCodeProvider implements AIProvider {
|
|
|
54
58
|
private sendPromptAsync;
|
|
55
59
|
private reconcileOpenCodeState;
|
|
56
60
|
private refreshBusySessionMessages;
|
|
61
|
+
private fetchSessionStatuses;
|
|
62
|
+
private listPendingPermissions;
|
|
63
|
+
private listPendingQuestions;
|
|
57
64
|
private refreshSessionsMetadata;
|
|
58
65
|
private refreshPendingPermissions;
|
|
59
66
|
private refreshPendingQuestions;
|
package/dist/ai/opencode.js
CHANGED
|
@@ -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");
|
|
@@ -378,6 +393,52 @@ export class OpenCodeProvider {
|
|
|
378
393
|
throw err;
|
|
379
394
|
}
|
|
380
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
|
+
}
|
|
381
442
|
// -------------------------------------------------------------------------
|
|
382
443
|
// Interaction
|
|
383
444
|
// -------------------------------------------------------------------------
|
|
@@ -521,13 +582,13 @@ export class OpenCodeProvider {
|
|
|
521
582
|
path: { id: this.lastActiveSessionId },
|
|
522
583
|
});
|
|
523
584
|
if (checkResp.error) {
|
|
524
|
-
|
|
585
|
+
this.debugWarn(`[sse] OpenCode session ${this.lastActiveSessionId} was garbage-collected. Notifying app.`);
|
|
525
586
|
const gcSessionId = this.lastActiveSessionId;
|
|
526
587
|
this.lastActiveSessionId = null;
|
|
527
588
|
this.emitter?.({ type: "session_gc", properties: { sessionId: gcSessionId } });
|
|
528
589
|
}
|
|
529
590
|
else {
|
|
530
|
-
|
|
591
|
+
this.debugLog(`[sse] Active session ${this.lastActiveSessionId} still valid.`);
|
|
531
592
|
}
|
|
532
593
|
}
|
|
533
594
|
if (attempt > 0) {
|
|
@@ -535,7 +596,7 @@ export class OpenCodeProvider {
|
|
|
535
596
|
}
|
|
536
597
|
const events = await this.client.event.subscribe();
|
|
537
598
|
if (attempt > 0) {
|
|
538
|
-
|
|
599
|
+
this.debugLog(`[sse] reconnected after ${attempt} attempt(s)`);
|
|
539
600
|
}
|
|
540
601
|
attempt = 0;
|
|
541
602
|
for await (const raw of events.stream) {
|
|
@@ -549,11 +610,11 @@ export class OpenCodeProvider {
|
|
|
549
610
|
? parsed.payload
|
|
550
611
|
: parsed;
|
|
551
612
|
if (!base || typeof base.type !== "string") {
|
|
552
|
-
|
|
613
|
+
this.debugWarn("[sse] Dropped malformed event:", redactSensitive(JSON.stringify(parsed).substring(0, 200)));
|
|
553
614
|
continue;
|
|
554
615
|
}
|
|
555
616
|
if (base.type !== "server.heartbeat") {
|
|
556
|
-
|
|
617
|
+
this.debugLog("[sse]", base.type);
|
|
557
618
|
}
|
|
558
619
|
const normalizedEvent = normalizeOpenCodeEvent({
|
|
559
620
|
type: base.type,
|
|
@@ -562,7 +623,7 @@ export class OpenCodeProvider {
|
|
|
562
623
|
this.trackPermissionEvent(normalizedEvent.type, normalizedEvent.properties || {});
|
|
563
624
|
this.emitter?.(normalizedEvent);
|
|
564
625
|
}
|
|
565
|
-
|
|
626
|
+
this.debugLog("[sse] Event stream ended, reconnecting...");
|
|
566
627
|
attempt++;
|
|
567
628
|
}
|
|
568
629
|
catch (err) {
|
|
@@ -570,9 +631,9 @@ export class OpenCodeProvider {
|
|
|
570
631
|
return;
|
|
571
632
|
attempt++;
|
|
572
633
|
const delay = backoffMs(attempt - 1);
|
|
573
|
-
|
|
634
|
+
this.debugError(`[sse] Stream error (attempt ${attempt}/${SSE_MAX_RETRIES}): ${err.message}. Retrying in ${delay}ms`);
|
|
574
635
|
if (attempt >= SSE_MAX_RETRIES) {
|
|
575
|
-
|
|
636
|
+
this.debugError("[sse] Max retries reached. Sending error event to app and giving up.");
|
|
576
637
|
this.emitter?.({
|
|
577
638
|
type: "sse_dead",
|
|
578
639
|
properties: { error: err.message, attempts: attempt },
|
|
@@ -668,13 +729,50 @@ export class OpenCodeProvider {
|
|
|
668
729
|
});
|
|
669
730
|
}
|
|
670
731
|
}
|
|
671
|
-
|
|
732
|
+
this.debugLog(`[sse] Re-synced messages for busy session ${sessionId} after reconnect`);
|
|
672
733
|
}
|
|
673
734
|
catch (err) {
|
|
674
|
-
|
|
735
|
+
this.debugWarn(`[sse] Failed to refresh messages for busy session ${sessionId}:`, err.message);
|
|
675
736
|
}
|
|
676
737
|
}
|
|
677
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
|
+
}
|
|
678
776
|
async refreshSessionsMetadata() {
|
|
679
777
|
const response = await this.client.session.list();
|
|
680
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;
|