@love-moon/ai-sdk 0.2.37 → 0.2.39
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/client.d.ts +4 -1
- package/dist/client.js +14 -2
- package/dist/external-provider-registry.js +37 -1
- package/dist/providers/claude-agent-sdk-session.d.ts +1 -1
- package/dist/providers/claude-agent-sdk-session.js +2 -1
- package/dist/providers/codex-app-server-session.d.ts +1 -1
- package/dist/providers/codex-app-server-session.js +3 -1
- package/dist/providers/kimi-cli-session.d.ts +1 -1
- package/dist/providers/kimi-cli-session.js +3 -1
- package/dist/providers/opencode-sdk-session.d.ts +1 -1
- package/dist/providers/opencode-sdk-session.js +3 -1
- package/dist/resume.d.ts +26 -0
- package/dist/resume.js +380 -0
- package/dist/tui-session.d.ts +153 -0
- package/dist/tui-session.js +941 -0
- package/dist/worker.js +39 -32
- package/package.json +2 -2
package/dist/client.d.ts
CHANGED
|
@@ -55,10 +55,12 @@ export class RemoteAiSession extends EventEmitter<[never]> {
|
|
|
55
55
|
setSessionReplyTarget(replyTo: any): void;
|
|
56
56
|
ensureSessionInfo(): Promise<any>;
|
|
57
57
|
getSessionUsageSummary(): Promise<any>;
|
|
58
|
+
interruptCurrentTurn(): Promise<any>;
|
|
58
59
|
runTurn(promptText: any, options?: {}): Promise<any>;
|
|
59
60
|
close(): Promise<void>;
|
|
60
|
-
callWorker(method: any, args?: any[], { progressHandler }?: {
|
|
61
|
+
callWorker(method: any, args?: any[], { progressHandler, messageType }?: {
|
|
61
62
|
progressHandler?: null | undefined;
|
|
63
|
+
messageType?: string | undefined;
|
|
62
64
|
}): Promise<any>;
|
|
63
65
|
handleWorkerStderr(line: any): void;
|
|
64
66
|
handleWorkerLine(line: any): void;
|
|
@@ -104,6 +106,7 @@ declare class LocalAiSessionProxy extends EventEmitter<[never]> {
|
|
|
104
106
|
setSessionReplyTarget(replyTarget: any): void;
|
|
105
107
|
ensureSessionInfo(): Promise<any>;
|
|
106
108
|
getSessionUsageSummary(): Promise<any>;
|
|
109
|
+
interruptCurrentTurn(): Promise<any>;
|
|
107
110
|
runTurn(promptText: any, options?: {}): Promise<any>;
|
|
108
111
|
close(): Promise<void>;
|
|
109
112
|
}
|
package/dist/client.js
CHANGED
|
@@ -168,6 +168,11 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
168
168
|
async getSessionUsageSummary() {
|
|
169
169
|
return this.callWorker("getSessionUsageSummary", []);
|
|
170
170
|
}
|
|
171
|
+
async interruptCurrentTurn() {
|
|
172
|
+
return this.callWorker("interruptCurrentTurn", [], {
|
|
173
|
+
messageType: "control",
|
|
174
|
+
});
|
|
175
|
+
}
|
|
171
176
|
async runTurn(promptText, options = {}) {
|
|
172
177
|
const { onProgress, ...restOptions } = options || {};
|
|
173
178
|
return this.callWorker("runTurn", [promptText, restOptions], {
|
|
@@ -214,7 +219,7 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
214
219
|
await this.exitPromise;
|
|
215
220
|
}
|
|
216
221
|
}
|
|
217
|
-
async callWorker(method, args = [], { progressHandler = null } = {}) {
|
|
222
|
+
async callWorker(method, args = [], { progressHandler = null, messageType = "request" } = {}) {
|
|
218
223
|
if (this.closed) {
|
|
219
224
|
throw createSessionClosedError();
|
|
220
225
|
}
|
|
@@ -224,7 +229,7 @@ export class RemoteAiSession extends EventEmitter {
|
|
|
224
229
|
}
|
|
225
230
|
const requestId = this.nextRequestId++;
|
|
226
231
|
const payload = {
|
|
227
|
-
type:
|
|
232
|
+
type: messageType,
|
|
228
233
|
id: requestId,
|
|
229
234
|
method,
|
|
230
235
|
args,
|
|
@@ -566,6 +571,13 @@ class LocalAiSessionProxy extends EventEmitter {
|
|
|
566
571
|
const session = await this.readyPromise;
|
|
567
572
|
return await session.getSessionUsageSummary();
|
|
568
573
|
}
|
|
574
|
+
async interruptCurrentTurn() {
|
|
575
|
+
const session = await this.readyPromise;
|
|
576
|
+
if (typeof session.interruptCurrentTurn === "function") {
|
|
577
|
+
return await session.interruptCurrentTurn();
|
|
578
|
+
}
|
|
579
|
+
return false;
|
|
580
|
+
}
|
|
569
581
|
async runTurn(promptText, options = {}) {
|
|
570
582
|
const session = await this.readyPromise;
|
|
571
583
|
return await session.runTurn(promptText, options);
|
|
@@ -15,13 +15,49 @@ function appendProviderModulePaths(parts, value) {
|
|
|
15
15
|
if (!raw) {
|
|
16
16
|
return;
|
|
17
17
|
}
|
|
18
|
-
for (const item of raw
|
|
18
|
+
for (const item of splitProviderModulePathString(raw)) {
|
|
19
19
|
const normalized = item.trim();
|
|
20
20
|
if (normalized) {
|
|
21
21
|
parts.push(normalized);
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
+
function looksLikeProviderModulePath(value) {
|
|
26
|
+
const normalized = String(value || "").trim();
|
|
27
|
+
if (!normalized) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return (normalized.startsWith("/") ||
|
|
31
|
+
normalized.startsWith("./") ||
|
|
32
|
+
normalized.startsWith("../") ||
|
|
33
|
+
normalized.startsWith("~/") ||
|
|
34
|
+
normalized.startsWith("file:") ||
|
|
35
|
+
normalized.includes("/") ||
|
|
36
|
+
normalized.includes("\\") ||
|
|
37
|
+
/\.[cm]?[jt]sx?$/i.test(normalized) ||
|
|
38
|
+
/^[A-Za-z]:[\\/]/.test(normalized));
|
|
39
|
+
}
|
|
40
|
+
function splitProviderModulePathString(raw) {
|
|
41
|
+
const normalized = String(raw || "").trim();
|
|
42
|
+
if (!normalized) {
|
|
43
|
+
return [];
|
|
44
|
+
}
|
|
45
|
+
const platformParts = normalized
|
|
46
|
+
.split(path.delimiter)
|
|
47
|
+
.map((item) => item.trim())
|
|
48
|
+
.filter(Boolean);
|
|
49
|
+
if (platformParts.length > 1 || !normalized.includes(",")) {
|
|
50
|
+
return platformParts;
|
|
51
|
+
}
|
|
52
|
+
const commaParts = normalized
|
|
53
|
+
.split(",")
|
|
54
|
+
.map((item) => item.trim())
|
|
55
|
+
.filter(Boolean);
|
|
56
|
+
if (commaParts.length > 1 && commaParts.every(looksLikeProviderModulePath)) {
|
|
57
|
+
return commaParts;
|
|
58
|
+
}
|
|
59
|
+
return platformParts;
|
|
60
|
+
}
|
|
25
61
|
function listProviderModulePathsFromValue(rawValue) {
|
|
26
62
|
const parts = [];
|
|
27
63
|
appendProviderModulePaths(parts, rawValue);
|
|
@@ -121,7 +121,7 @@ export class ClaudeAgentSdkSession extends EventEmitter<[never]> {
|
|
|
121
121
|
handleSdkMessage(message: any, currentTurn: any, { onProgress }: {
|
|
122
122
|
onProgress: any;
|
|
123
123
|
}): Promise<void>;
|
|
124
|
-
interruptCurrentTurn(): Promise<
|
|
124
|
+
interruptCurrentTurn(): Promise<boolean>;
|
|
125
125
|
runTurn(promptText: any, { useInitialImages, onProgress }?: {
|
|
126
126
|
useInitialImages?: boolean | undefined;
|
|
127
127
|
onProgress?: null | undefined;
|
|
@@ -680,7 +680,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
680
680
|
async interruptCurrentTurn() {
|
|
681
681
|
const currentTurn = this.currentTurn;
|
|
682
682
|
if (!currentTurn) {
|
|
683
|
-
return;
|
|
683
|
+
return false;
|
|
684
684
|
}
|
|
685
685
|
try {
|
|
686
686
|
await currentTurn.query?.interrupt?.();
|
|
@@ -700,6 +700,7 @@ export class ClaudeAgentSdkSession extends EventEmitter {
|
|
|
700
700
|
catch {
|
|
701
701
|
// best effort
|
|
702
702
|
}
|
|
703
|
+
return true;
|
|
703
704
|
}
|
|
704
705
|
async runTurn(promptText, { useInitialImages = false, onProgress = null } = {}) {
|
|
705
706
|
if (this.closeRequested) {
|
|
@@ -149,7 +149,7 @@ export class CodexAppServerSession extends EventEmitter<[never]> {
|
|
|
149
149
|
handleTransportFailure(error: any): void;
|
|
150
150
|
handleTransportExit(payload: any): void;
|
|
151
151
|
maybeEmitAuthRequired(error: any): void;
|
|
152
|
-
interruptCurrentTurn(): Promise<
|
|
152
|
+
interruptCurrentTurn(): Promise<boolean>;
|
|
153
153
|
runTurn(promptText: any, { useInitialImages }?: {
|
|
154
154
|
useInitialImages?: boolean | undefined;
|
|
155
155
|
}): Promise<{
|
|
@@ -851,17 +851,19 @@ export class CodexAppServerSession extends EventEmitter {
|
|
|
851
851
|
async interruptCurrentTurn() {
|
|
852
852
|
const currentTurn = this.currentTurn;
|
|
853
853
|
if (!currentTurn || !this.sessionId || !currentTurn.turnId) {
|
|
854
|
-
return;
|
|
854
|
+
return false;
|
|
855
855
|
}
|
|
856
856
|
try {
|
|
857
857
|
await this.transport.request("turn/interrupt", {
|
|
858
858
|
threadId: this.sessionId,
|
|
859
859
|
turnId: currentTurn.turnId,
|
|
860
860
|
});
|
|
861
|
+
return true;
|
|
861
862
|
}
|
|
862
863
|
catch {
|
|
863
864
|
// best effort
|
|
864
865
|
}
|
|
866
|
+
return false;
|
|
865
867
|
}
|
|
866
868
|
async runTurn(promptText, { useInitialImages = false } = {}) {
|
|
867
869
|
if (this.closeRequested) {
|
|
@@ -143,7 +143,7 @@ export class KimiCliSession extends EventEmitter<[never]> {
|
|
|
143
143
|
handleWireEvent(type: any, payload: any): Promise<void>;
|
|
144
144
|
handleTransportFailure(error: any): void;
|
|
145
145
|
handleTransportExit(payload: any): void;
|
|
146
|
-
interruptCurrentTurn(): Promise<
|
|
146
|
+
interruptCurrentTurn(): Promise<boolean>;
|
|
147
147
|
runTurn(promptText: any, { useInitialImages, onProgress }?: {
|
|
148
148
|
useInitialImages?: boolean | undefined;
|
|
149
149
|
onProgress?: null | undefined;
|
|
@@ -812,14 +812,16 @@ export class KimiCliSession extends EventEmitter {
|
|
|
812
812
|
}
|
|
813
813
|
async interruptCurrentTurn() {
|
|
814
814
|
if (!this.currentTurn) {
|
|
815
|
-
return;
|
|
815
|
+
return false;
|
|
816
816
|
}
|
|
817
817
|
try {
|
|
818
818
|
await this.transport.request("cancel", {});
|
|
819
|
+
return true;
|
|
819
820
|
}
|
|
820
821
|
catch {
|
|
821
822
|
// best effort
|
|
822
823
|
}
|
|
824
|
+
return false;
|
|
823
825
|
}
|
|
824
826
|
async runTurn(promptText, { useInitialImages = false, onProgress = null } = {}) {
|
|
825
827
|
if (this.closeRequested || this.closed) {
|
|
@@ -163,7 +163,7 @@ export class OpencodeSdkSession extends EventEmitter<[never]> {
|
|
|
163
163
|
handleOpencodeEvent(event: any): Promise<void>;
|
|
164
164
|
handleTransportFailure(error: any): void;
|
|
165
165
|
handleTransportExit(payload: any): void;
|
|
166
|
-
interruptCurrentTurn(): Promise<
|
|
166
|
+
interruptCurrentTurn(): Promise<boolean>;
|
|
167
167
|
runTurn(promptText: any, { useInitialImages, onProgress }?: {
|
|
168
168
|
useInitialImages?: boolean | undefined;
|
|
169
169
|
onProgress?: null | undefined;
|
|
@@ -1119,7 +1119,7 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
1119
1119
|
async interruptCurrentTurn() {
|
|
1120
1120
|
const currentTurn = this.currentTurn;
|
|
1121
1121
|
if (!currentTurn || !this.client?.session || !this.sessionId) {
|
|
1122
|
-
return;
|
|
1122
|
+
return false;
|
|
1123
1123
|
}
|
|
1124
1124
|
try {
|
|
1125
1125
|
currentTurn.abortController?.abort?.();
|
|
@@ -1129,10 +1129,12 @@ export class OpencodeSdkSession extends EventEmitter {
|
|
|
1129
1129
|
}
|
|
1130
1130
|
try {
|
|
1131
1131
|
await this.client.session.abort({ sessionID: this.sessionId }, { throwOnError: true, responseStyle: "data" });
|
|
1132
|
+
return true;
|
|
1132
1133
|
}
|
|
1133
1134
|
catch {
|
|
1134
1135
|
// best effort
|
|
1135
1136
|
}
|
|
1137
|
+
return true;
|
|
1136
1138
|
}
|
|
1137
1139
|
async runTurn(promptText, { useInitialImages = false, onProgress = null } = {}) {
|
|
1138
1140
|
if (this.closeRequested) {
|
package/dist/resume.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export function resumeProviderForBackend(backend: any): "codex" | "claude" | "copilot" | null;
|
|
2
|
+
export function findSessionPath(provider: any, sessionId: any, options?: {}): Promise<any>;
|
|
3
|
+
export function findCodexSessionPath(sessionId: any, options?: {}): Promise<string | null>;
|
|
4
|
+
export function findClaudeSessionPath(sessionId: any, options?: {}): Promise<any>;
|
|
5
|
+
export function findCopilotSessionPath(sessionId: any, options?: {}): Promise<string | null>;
|
|
6
|
+
export function resolveSessionRunDirectory(sessionPath: any): Promise<string>;
|
|
7
|
+
export function inspectResumeTarget(backend: any, sessionId: any, options?: {}): Promise<{
|
|
8
|
+
provider: string;
|
|
9
|
+
sessionId: string;
|
|
10
|
+
sessionPath: any;
|
|
11
|
+
cwd: string;
|
|
12
|
+
debugMetadata: {
|
|
13
|
+
cwdSource: string;
|
|
14
|
+
sessionPath: any;
|
|
15
|
+
};
|
|
16
|
+
}>;
|
|
17
|
+
export function resolveResumeContext(backend: any, sessionId: any, options?: {}): Promise<{
|
|
18
|
+
provider: string;
|
|
19
|
+
sessionId: string;
|
|
20
|
+
sessionPath: any;
|
|
21
|
+
cwd: string;
|
|
22
|
+
debugMetadata: {
|
|
23
|
+
cwdSource: string;
|
|
24
|
+
sessionPath: any;
|
|
25
|
+
};
|
|
26
|
+
}>;
|
package/dist/resume.js
ADDED
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { promises as fsp } from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import readline from "node:readline";
|
|
6
|
+
import yaml from "js-yaml";
|
|
7
|
+
function normalizeBackend(backend) {
|
|
8
|
+
return String(backend || "").trim().toLowerCase();
|
|
9
|
+
}
|
|
10
|
+
function resolveHomeDir(options) {
|
|
11
|
+
if (options?.homeDir) {
|
|
12
|
+
return options.homeDir;
|
|
13
|
+
}
|
|
14
|
+
return os.homedir();
|
|
15
|
+
}
|
|
16
|
+
function normalizeSessionId(sessionId) {
|
|
17
|
+
return typeof sessionId === "string" ? sessionId.trim() : "";
|
|
18
|
+
}
|
|
19
|
+
export function resumeProviderForBackend(backend) {
|
|
20
|
+
const normalizedBackend = normalizeBackend(backend);
|
|
21
|
+
if (normalizedBackend === "codex" || normalizedBackend === "code") {
|
|
22
|
+
return "codex";
|
|
23
|
+
}
|
|
24
|
+
if (normalizedBackend === "claude" || normalizedBackend === "claude-code") {
|
|
25
|
+
return "claude";
|
|
26
|
+
}
|
|
27
|
+
if (normalizedBackend === "copilot") {
|
|
28
|
+
return "copilot";
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
export async function findSessionPath(provider, sessionId, options = {}) {
|
|
33
|
+
const normalizedProvider = String(provider || "").trim().toLowerCase();
|
|
34
|
+
if (normalizedProvider === "codex") {
|
|
35
|
+
return findCodexSessionPath(sessionId, options);
|
|
36
|
+
}
|
|
37
|
+
if (normalizedProvider === "claude") {
|
|
38
|
+
return findClaudeSessionPath(sessionId, options);
|
|
39
|
+
}
|
|
40
|
+
if (normalizedProvider === "copilot") {
|
|
41
|
+
return findCopilotSessionPath(sessionId, options);
|
|
42
|
+
}
|
|
43
|
+
throw new Error(`Unsupported provider: ${provider}`);
|
|
44
|
+
}
|
|
45
|
+
export async function findCodexSessionPath(sessionId, options = {}) {
|
|
46
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
47
|
+
if (!normalizedSessionId) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
const homeDir = resolveHomeDir(options);
|
|
51
|
+
const sessionsDir = options.codexSessionsDir || path.join(homeDir, ".codex", "sessions");
|
|
52
|
+
return findCodexSessionFile(sessionsDir, normalizedSessionId);
|
|
53
|
+
}
|
|
54
|
+
export async function findClaudeSessionPath(sessionId, options = {}) {
|
|
55
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
56
|
+
if (!normalizedSessionId) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
const homeDir = resolveHomeDir(options);
|
|
60
|
+
const projectsDir = options.claudeProjectsDir || path.join(homeDir, ".claude", "projects");
|
|
61
|
+
const sessionEntries = await findClaudeSessionEntries(projectsDir, normalizedSessionId);
|
|
62
|
+
if (sessionEntries.length > 0) {
|
|
63
|
+
return sessionEntries[0]?.source || null;
|
|
64
|
+
}
|
|
65
|
+
const tasksDir = options.claudeTasksDir || path.join(homeDir, ".claude", "tasks");
|
|
66
|
+
const directTaskDir = path.join(tasksDir, normalizedSessionId);
|
|
67
|
+
if (await pathExists(directTaskDir, "directory")) {
|
|
68
|
+
return directTaskDir;
|
|
69
|
+
}
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
export async function findCopilotSessionPath(sessionId, options = {}) {
|
|
73
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
74
|
+
if (!normalizedSessionId) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
const homeDir = resolveHomeDir(options);
|
|
78
|
+
const sessionStateDir = options.copilotSessionStateDir || path.join(homeDir, ".copilot", "session-state");
|
|
79
|
+
const directJsonlPath = path.join(sessionStateDir, `${normalizedSessionId}.jsonl`);
|
|
80
|
+
if (await pathExists(directJsonlPath, "file")) {
|
|
81
|
+
return directJsonlPath;
|
|
82
|
+
}
|
|
83
|
+
const directSessionDir = path.join(sessionStateDir, normalizedSessionId);
|
|
84
|
+
if (await pathExists(directSessionDir, "directory")) {
|
|
85
|
+
return directSessionDir;
|
|
86
|
+
}
|
|
87
|
+
return findPathByName(sessionStateDir, normalizedSessionId);
|
|
88
|
+
}
|
|
89
|
+
export async function resolveSessionRunDirectory(sessionPath) {
|
|
90
|
+
const normalizedPath = typeof sessionPath === "string" ? sessionPath.trim() : "";
|
|
91
|
+
if (!normalizedPath) {
|
|
92
|
+
throw new Error("Invalid session path");
|
|
93
|
+
}
|
|
94
|
+
let stats;
|
|
95
|
+
try {
|
|
96
|
+
stats = await fsp.stat(normalizedPath);
|
|
97
|
+
}
|
|
98
|
+
catch {
|
|
99
|
+
throw new Error(`Session path does not exist: ${normalizedPath}`);
|
|
100
|
+
}
|
|
101
|
+
return stats.isDirectory() ? normalizedPath : path.dirname(normalizedPath);
|
|
102
|
+
}
|
|
103
|
+
export async function inspectResumeTarget(backend, sessionId, options = {}) {
|
|
104
|
+
return resolveResumeContext(backend, sessionId, options);
|
|
105
|
+
}
|
|
106
|
+
export async function resolveResumeContext(backend, sessionId, options = {}) {
|
|
107
|
+
const normalizedSessionId = normalizeSessionId(sessionId);
|
|
108
|
+
if (!normalizedSessionId) {
|
|
109
|
+
throw new Error("--resume requires a session id");
|
|
110
|
+
}
|
|
111
|
+
const provider = resumeProviderForBackend(backend);
|
|
112
|
+
if (!provider) {
|
|
113
|
+
throw new Error(`--resume is not supported for backend "${backend}"`);
|
|
114
|
+
}
|
|
115
|
+
const sessionPath = await findSessionPath(provider, normalizedSessionId, options);
|
|
116
|
+
if (!sessionPath) {
|
|
117
|
+
throw new Error(`Invalid --resume session id for ${provider}: ${normalizedSessionId}`);
|
|
118
|
+
}
|
|
119
|
+
const cwdFromSession = await extractResumeCwdFromSession(provider, sessionPath, normalizedSessionId);
|
|
120
|
+
const fallbackCwd = await resolveSessionRunDirectory(sessionPath);
|
|
121
|
+
const cwd = cwdFromSession || fallbackCwd;
|
|
122
|
+
if (!(await isExistingDirectory(cwd))) {
|
|
123
|
+
throw new Error(`Resume workspace path does not exist: ${cwd}`);
|
|
124
|
+
}
|
|
125
|
+
return {
|
|
126
|
+
provider,
|
|
127
|
+
sessionId: normalizedSessionId,
|
|
128
|
+
sessionPath,
|
|
129
|
+
cwd,
|
|
130
|
+
debugMetadata: {
|
|
131
|
+
cwdSource: cwdFromSession ? "session" : "session_path",
|
|
132
|
+
sessionPath,
|
|
133
|
+
},
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
async function isExistingDirectory(targetPath) {
|
|
137
|
+
const normalizedPath = typeof targetPath === "string" ? targetPath.trim() : "";
|
|
138
|
+
if (!normalizedPath) {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
try {
|
|
142
|
+
const stats = await fsp.stat(normalizedPath);
|
|
143
|
+
return stats.isDirectory();
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
async function extractCodexResumeCwd(sessionPath) {
|
|
150
|
+
if (!sessionPath.endsWith(".jsonl")) {
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
const rl = readline.createInterface({
|
|
154
|
+
input: fs.createReadStream(sessionPath),
|
|
155
|
+
crlfDelay: Infinity,
|
|
156
|
+
});
|
|
157
|
+
for await (const line of rl) {
|
|
158
|
+
const trimmed = line.trim();
|
|
159
|
+
if (!trimmed) {
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
let entry;
|
|
163
|
+
try {
|
|
164
|
+
entry = JSON.parse(trimmed);
|
|
165
|
+
}
|
|
166
|
+
catch {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const maybeCwd = entry?.type === "session_meta" ? entry?.payload?.cwd : null;
|
|
170
|
+
if (typeof maybeCwd === "string" && maybeCwd.trim()) {
|
|
171
|
+
return maybeCwd.trim();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
async function extractClaudeResumeCwd(sessionPath, sessionId) {
|
|
177
|
+
if (!sessionPath.endsWith(".jsonl")) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
const rl = readline.createInterface({
|
|
181
|
+
input: fs.createReadStream(sessionPath),
|
|
182
|
+
crlfDelay: Infinity,
|
|
183
|
+
});
|
|
184
|
+
for await (const line of rl) {
|
|
185
|
+
const trimmed = line.trim();
|
|
186
|
+
if (!trimmed) {
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
let entry;
|
|
190
|
+
try {
|
|
191
|
+
entry = JSON.parse(trimmed);
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
const idMatches = String(entry?.sessionId || "").trim() === sessionId;
|
|
197
|
+
const maybeCwd = entry?.cwd;
|
|
198
|
+
if (idMatches && typeof maybeCwd === "string" && maybeCwd.trim()) {
|
|
199
|
+
return maybeCwd.trim();
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return null;
|
|
203
|
+
}
|
|
204
|
+
async function extractCopilotResumeCwd(sessionPath) {
|
|
205
|
+
let stats;
|
|
206
|
+
try {
|
|
207
|
+
stats = await fsp.stat(sessionPath);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
if (stats.isDirectory()) {
|
|
213
|
+
const workspaceYamlPath = path.join(sessionPath, "workspace.yaml");
|
|
214
|
+
try {
|
|
215
|
+
const yamlContent = await fsp.readFile(workspaceYamlPath, "utf8");
|
|
216
|
+
const parsed = yaml.load(yamlContent);
|
|
217
|
+
const maybeCwd = parsed && typeof parsed === "object" ? parsed.cwd : null;
|
|
218
|
+
if (typeof maybeCwd === "string" && maybeCwd.trim()) {
|
|
219
|
+
return maybeCwd.trim();
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
catch {
|
|
223
|
+
return null;
|
|
224
|
+
}
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
if (!sessionPath.endsWith(".jsonl")) {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
const rl = readline.createInterface({
|
|
231
|
+
input: fs.createReadStream(sessionPath),
|
|
232
|
+
crlfDelay: Infinity,
|
|
233
|
+
});
|
|
234
|
+
for await (const line of rl) {
|
|
235
|
+
const trimmed = line.trim();
|
|
236
|
+
if (!trimmed) {
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
let entry;
|
|
240
|
+
try {
|
|
241
|
+
entry = JSON.parse(trimmed);
|
|
242
|
+
}
|
|
243
|
+
catch {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const maybeCwd = entry?.data?.context?.cwd || entry?.data?.cwd;
|
|
247
|
+
if (typeof maybeCwd === "string" && maybeCwd.trim()) {
|
|
248
|
+
return maybeCwd.trim();
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}
|
|
253
|
+
async function extractResumeCwdFromSession(provider, sessionPath, sessionId) {
|
|
254
|
+
if (provider === "codex") {
|
|
255
|
+
return extractCodexResumeCwd(sessionPath);
|
|
256
|
+
}
|
|
257
|
+
if (provider === "claude") {
|
|
258
|
+
return extractClaudeResumeCwd(sessionPath, sessionId);
|
|
259
|
+
}
|
|
260
|
+
if (provider === "copilot") {
|
|
261
|
+
return extractCopilotResumeCwd(sessionPath);
|
|
262
|
+
}
|
|
263
|
+
return null;
|
|
264
|
+
}
|
|
265
|
+
async function findCodexSessionFile(rootDir, sessionId) {
|
|
266
|
+
const queue = [rootDir];
|
|
267
|
+
while (queue.length) {
|
|
268
|
+
const current = queue.pop();
|
|
269
|
+
let entries = [];
|
|
270
|
+
try {
|
|
271
|
+
entries = await fsp.readdir(current, { withFileTypes: true });
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
for (const entry of entries) {
|
|
277
|
+
const fullPath = path.join(current, entry.name);
|
|
278
|
+
if (entry.isDirectory()) {
|
|
279
|
+
queue.push(fullPath);
|
|
280
|
+
}
|
|
281
|
+
else if (entry.isFile() &&
|
|
282
|
+
entry.name.includes(sessionId) &&
|
|
283
|
+
entry.name.endsWith(".jsonl")) {
|
|
284
|
+
return fullPath;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
async function findClaudeSessionEntries(projectsDir, sessionId) {
|
|
291
|
+
const entries = [];
|
|
292
|
+
let projectDirs = [];
|
|
293
|
+
try {
|
|
294
|
+
projectDirs = await fsp.readdir(projectsDir, { withFileTypes: true });
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return entries;
|
|
298
|
+
}
|
|
299
|
+
for (const projectDir of projectDirs) {
|
|
300
|
+
if (!projectDir.isDirectory()) {
|
|
301
|
+
continue;
|
|
302
|
+
}
|
|
303
|
+
const projectPath = path.join(projectsDir, projectDir.name);
|
|
304
|
+
let files = [];
|
|
305
|
+
try {
|
|
306
|
+
files = await fsp.readdir(projectPath, { withFileTypes: true });
|
|
307
|
+
}
|
|
308
|
+
catch {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
for (const file of files) {
|
|
312
|
+
if (!file.isFile()) {
|
|
313
|
+
continue;
|
|
314
|
+
}
|
|
315
|
+
if (!file.name.endsWith(".jsonl") || file.name.startsWith("agent-")) {
|
|
316
|
+
continue;
|
|
317
|
+
}
|
|
318
|
+
const filePath = path.join(projectPath, file.name);
|
|
319
|
+
const rl = readline.createInterface({
|
|
320
|
+
input: fs.createReadStream(filePath),
|
|
321
|
+
crlfDelay: Infinity,
|
|
322
|
+
});
|
|
323
|
+
for await (const line of rl) {
|
|
324
|
+
const trimmed = line.trim();
|
|
325
|
+
if (!trimmed) {
|
|
326
|
+
continue;
|
|
327
|
+
}
|
|
328
|
+
let entry;
|
|
329
|
+
try {
|
|
330
|
+
entry = JSON.parse(trimmed);
|
|
331
|
+
}
|
|
332
|
+
catch {
|
|
333
|
+
continue;
|
|
334
|
+
}
|
|
335
|
+
if (entry.sessionId === sessionId) {
|
|
336
|
+
entries.push({ ...entry, source: filePath });
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
return entries;
|
|
342
|
+
}
|
|
343
|
+
async function findPathByName(rootDir, sessionId) {
|
|
344
|
+
const queue = [rootDir];
|
|
345
|
+
while (queue.length) {
|
|
346
|
+
const current = queue.pop();
|
|
347
|
+
let entries = [];
|
|
348
|
+
try {
|
|
349
|
+
entries = await fsp.readdir(current, { withFileTypes: true });
|
|
350
|
+
}
|
|
351
|
+
catch {
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
for (const entry of entries) {
|
|
355
|
+
const fullPath = path.join(current, entry.name);
|
|
356
|
+
if (entry.name.includes(sessionId)) {
|
|
357
|
+
return fullPath;
|
|
358
|
+
}
|
|
359
|
+
if (entry.isDirectory()) {
|
|
360
|
+
queue.push(fullPath);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
async function pathExists(targetPath, expectedType) {
|
|
367
|
+
try {
|
|
368
|
+
const stats = await fsp.stat(targetPath);
|
|
369
|
+
if (expectedType === "file") {
|
|
370
|
+
return stats.isFile();
|
|
371
|
+
}
|
|
372
|
+
if (expectedType === "directory") {
|
|
373
|
+
return stats.isDirectory();
|
|
374
|
+
}
|
|
375
|
+
return true;
|
|
376
|
+
}
|
|
377
|
+
catch {
|
|
378
|
+
return false;
|
|
379
|
+
}
|
|
380
|
+
}
|