@love-moon/conductor-cli 0.2.39 → 0.2.40

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.
@@ -11,9 +11,15 @@ import {
11
11
  getExternalRuntimeBackendDescriptor,
12
12
  isRuntimeSupportedBackend,
13
13
  normalizeRuntimeBackendAlias,
14
+ parseCommandParts,
14
15
  resolveConfiguredRuntimeBackend,
15
16
  } from "../runtime-backends.js";
16
17
 
18
+ const LEGACY_COPILOT_CLI_ARGS = new Set(["--allow-all-paths", "--allow-all-tools"]);
19
+ const DEFAULT_COPILOT_RESUME_TIMEOUT_MS = 20_000;
20
+ const DEFAULT_COPILOT_RESUME_STOP_TIMEOUT_MS = 5_000;
21
+ const COPILOT_GITHUB_TOKEN_ENV_KEYS = ["COPILOT_GITHUB_TOKEN", "GH_TOKEN", "GITHUB_TOKEN"];
22
+
17
23
  function normalizeBackend(backend) {
18
24
  return String(backend || "").trim().toLowerCase();
19
25
  }
@@ -41,6 +47,248 @@ function resolveConfigFilePath(options = {}) {
41
47
  : path.join(resolveHomeDir(options), ".conductor", "config.yaml");
42
48
  }
43
49
 
50
+ function normalizeCopilotCliArgs(args) {
51
+ if (!Array.isArray(args)) {
52
+ return [];
53
+ }
54
+ return args.filter((item) => {
55
+ const normalized = typeof item === "string" ? item.trim().toLowerCase() : "";
56
+ return normalized && !LEGACY_COPILOT_CLI_ARGS.has(normalized);
57
+ });
58
+ }
59
+
60
+ function stripExecutableSuffix(name) {
61
+ return String(name || "")
62
+ .trim()
63
+ .toLowerCase()
64
+ .replace(/\.(cmd|bat|exe)$/i, "");
65
+ }
66
+
67
+ function isDefaultCopilotCommand(command) {
68
+ const normalized = String(command || "").trim();
69
+ if (!normalized || /[\\/]/.test(normalized)) {
70
+ return false;
71
+ }
72
+ return stripExecutableSuffix(normalized) === "copilot";
73
+ }
74
+
75
+ function isEnvironmentAssignment(token) {
76
+ return /^[A-Za-z_][A-Za-z0-9_]*=/.test(String(token || "").trim());
77
+ }
78
+
79
+ function parseEnvironmentAssignment(token) {
80
+ const normalized = String(token || "");
81
+ const index = normalized.indexOf("=");
82
+ if (index <= 0) {
83
+ return null;
84
+ }
85
+ return {
86
+ key: normalized.slice(0, index),
87
+ value: normalized.slice(index + 1),
88
+ };
89
+ }
90
+
91
+ function isEnvCommand(command) {
92
+ return stripExecutableSuffix(path.basename(String(command || ""))) === "env";
93
+ }
94
+
95
+ function isPathLikeCommand(command) {
96
+ const normalized = String(command || "").trim();
97
+ return (
98
+ normalized.startsWith(".") ||
99
+ normalized.startsWith("/") ||
100
+ normalized.includes("/") ||
101
+ normalized.includes("\\") ||
102
+ /^[A-Za-z]:[\\/]/.test(normalized)
103
+ );
104
+ }
105
+
106
+ function resolveExecutablePath(command, env = process.env) {
107
+ const normalized = String(command || "").trim();
108
+ if (!normalized) {
109
+ return "";
110
+ }
111
+ if (isPathLikeCommand(normalized)) {
112
+ return normalized;
113
+ }
114
+
115
+ const pathEnv = typeof env?.PATH === "string" ? env.PATH : process.env.PATH || "";
116
+ const pathExt =
117
+ process.platform === "win32" && !path.extname(normalized)
118
+ ? String(env?.PATHEXT || process.env.PATHEXT || ".COM;.EXE;.BAT;.CMD")
119
+ .split(";")
120
+ .filter(Boolean)
121
+ : [""];
122
+ for (const dir of pathEnv.split(path.delimiter)) {
123
+ if (!dir) {
124
+ continue;
125
+ }
126
+ for (const ext of pathExt) {
127
+ const candidate = path.join(dir, `${normalized}${ext}`);
128
+ if (fs.existsSync(candidate)) {
129
+ return candidate;
130
+ }
131
+ }
132
+ }
133
+ return "";
134
+ }
135
+
136
+ function unwrapEnvironmentCommand(command, args) {
137
+ const parts = [command, ...args].filter((item) => typeof item === "string" && item.length > 0);
138
+ const extraEnv = {};
139
+ let index = 0;
140
+
141
+ while (index < parts.length && isEnvironmentAssignment(parts[index])) {
142
+ const assignment = parseEnvironmentAssignment(parts[index]);
143
+ if (assignment) {
144
+ extraEnv[assignment.key] = assignment.value;
145
+ }
146
+ index += 1;
147
+ }
148
+
149
+ if (index > 0) {
150
+ return {
151
+ command: parts[index] || "",
152
+ args: parts.slice(index + 1),
153
+ env: extraEnv,
154
+ };
155
+ }
156
+
157
+ if (!isEnvCommand(command)) {
158
+ return { command, args, env: extraEnv };
159
+ }
160
+
161
+ index = 0;
162
+ while (index < args.length) {
163
+ const token = args[index];
164
+ if (token === "--") {
165
+ index += 1;
166
+ break;
167
+ }
168
+ if (isEnvironmentAssignment(token)) {
169
+ const assignment = parseEnvironmentAssignment(token);
170
+ if (assignment) {
171
+ extraEnv[assignment.key] = assignment.value;
172
+ }
173
+ index += 1;
174
+ continue;
175
+ }
176
+ if (String(token || "").startsWith("-")) {
177
+ return { command, args, env: extraEnv };
178
+ }
179
+ break;
180
+ }
181
+
182
+ return {
183
+ command: args[index] || "",
184
+ args: args.slice(index + 1),
185
+ env: extraEnv,
186
+ };
187
+ }
188
+
189
+ function hasOwnEnumerableKeys(value) {
190
+ return value && typeof value === "object" && Object.keys(value).length > 0;
191
+ }
192
+
193
+ function withoutCopilotGithubTokenEnv(env) {
194
+ const next = env && typeof env === "object" ? { ...env } : {};
195
+ for (const key of COPILOT_GITHUB_TOKEN_ENV_KEYS) {
196
+ delete next[key];
197
+ }
198
+ return next;
199
+ }
200
+
201
+ function resolvePositiveTimeoutMs(value, fallback) {
202
+ const n = Number(value);
203
+ return Number.isFinite(n) && n > 0 ? Math.round(n) : fallback;
204
+ }
205
+
206
+ function remainingTimeoutMs(startedAtMs, timeoutMs, message) {
207
+ const remaining = timeoutMs - (Date.now() - startedAtMs);
208
+ if (remaining <= 0) {
209
+ throw new Error(message);
210
+ }
211
+ return remaining;
212
+ }
213
+
214
+ async function withTimeout(promise, timeoutMs, message) {
215
+ let timer = null;
216
+ try {
217
+ return await Promise.race([
218
+ promise,
219
+ new Promise((_, reject) => {
220
+ timer = setTimeout(() => reject(new Error(message)), timeoutMs);
221
+ }),
222
+ ]);
223
+ } finally {
224
+ if (timer) {
225
+ clearTimeout(timer);
226
+ }
227
+ }
228
+ }
229
+
230
+ function resolveCopilotCliLaunch(commandLine, env = process.env) {
231
+ const normalized = typeof commandLine === "string" ? commandLine.trim() : "";
232
+ if (!normalized) {
233
+ return null;
234
+ }
235
+ const parsed = parseCommandParts(normalized);
236
+ const unwrapped = unwrapEnvironmentCommand(parsed.command, parsed.args);
237
+ const command = unwrapped.command;
238
+ const args = unwrapped.args;
239
+ if (!command) {
240
+ return null;
241
+ }
242
+ const cliArgs = normalizeCopilotCliArgs(args);
243
+ if (isDefaultCopilotCommand(command)) {
244
+ if (cliArgs.length === 0 && !hasOwnEnumerableKeys(unwrapped.env)) {
245
+ return null;
246
+ }
247
+ return {
248
+ cliArgs,
249
+ env: unwrapped.env,
250
+ };
251
+ }
252
+ const launchEnv = {
253
+ ...process.env,
254
+ ...env,
255
+ ...unwrapped.env,
256
+ };
257
+ const resolvedPath = resolveExecutablePath(command, launchEnv);
258
+ return {
259
+ cliPath: resolvedPath || command,
260
+ cliArgs,
261
+ env: unwrapped.env,
262
+ };
263
+ }
264
+
265
+ function normalizeEnvConfigValue(value) {
266
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
267
+ }
268
+
269
+ function proxyToEnv(envConfig) {
270
+ if (!envConfig || typeof envConfig !== "object") {
271
+ return {};
272
+ }
273
+ const env = {};
274
+ const mappings = {
275
+ http_proxy: ["HTTP_PROXY", "http_proxy"],
276
+ https_proxy: ["HTTPS_PROXY", "https_proxy"],
277
+ all_proxy: ["ALL_PROXY", "all_proxy"],
278
+ no_proxy: ["NO_PROXY", "no_proxy"],
279
+ };
280
+ for (const [key, envKeys] of Object.entries(mappings)) {
281
+ const value = envConfig[key] || envConfig[key.toUpperCase()];
282
+ if (!value) {
283
+ continue;
284
+ }
285
+ for (const envKey of envKeys) {
286
+ env[envKey] = value;
287
+ }
288
+ }
289
+ return env;
290
+ }
291
+
44
292
  export function buildResumeArgsForBackend(backend, sessionId) {
45
293
  const resumeSessionId = normalizeSessionId(sessionId);
46
294
  if (!resumeSessionId) {
@@ -87,9 +335,6 @@ export async function findSessionPath(provider, sessionId, options = {}) {
87
335
  if (normalizedProvider === "claude") {
88
336
  return findClaudeSessionPath(sessionId, options);
89
337
  }
90
- if (normalizedProvider === "copilot") {
91
- return findCopilotSessionPath(sessionId, options);
92
- }
93
338
  if (normalizedProvider === "kimi") {
94
339
  return findKimiSessionPath(sessionId, options);
95
340
  }
@@ -128,27 +373,6 @@ export async function findClaudeSessionPath(sessionId, options = {}) {
128
373
  return null;
129
374
  }
130
375
 
131
- export async function findCopilotSessionPath(sessionId, options = {}) {
132
- const normalizedSessionId = normalizeSessionId(sessionId);
133
- if (!normalizedSessionId) {
134
- return null;
135
- }
136
-
137
- const homeDir = resolveHomeDir(options);
138
- const sessionStateDir = options.copilotSessionStateDir || path.join(homeDir, ".copilot", "session-state");
139
- const directJsonlPath = path.join(sessionStateDir, `${normalizedSessionId}.jsonl`);
140
- if (await pathExists(directJsonlPath, "file")) {
141
- return directJsonlPath;
142
- }
143
-
144
- const directSessionDir = path.join(sessionStateDir, normalizedSessionId);
145
- if (await pathExists(directSessionDir, "directory")) {
146
- return directSessionDir;
147
- }
148
-
149
- return findPathByName(sessionStateDir, normalizedSessionId);
150
- }
151
-
152
376
  export async function findKimiSessionPath(sessionId, options = {}) {
153
377
  const normalizedSessionId = normalizeSessionId(sessionId);
154
378
  if (!normalizedSessionId) {
@@ -183,15 +407,43 @@ export async function resolveResumeContext(backend, sessionId, options = {}) {
183
407
  if (!normalizedSessionId) {
184
408
  throw new Error("--resume requires a session id");
185
409
  }
186
- const provider = resumeProviderForBackend(backend);
410
+ const configFilePath = resolveConfigFilePath(options);
411
+ const allowCliList =
412
+ options.allowCliList && typeof options.allowCliList === "object"
413
+ ? options.allowCliList
414
+ : await loadConfiguredAllowCliList({ ...options, configFilePath });
415
+ const lookupBackend = await resolveResumeLookupBackend(backend, {
416
+ ...options,
417
+ configFilePath,
418
+ allowCliList,
419
+ });
420
+ const provider = resumeProviderForBackend(lookupBackend || backend);
187
421
  if (!provider) {
188
- const externalContext = await resolveExternalResumeContext(backend, normalizedSessionId, options);
422
+ const externalContext = await resolveExternalResumeContext(backend, normalizedSessionId, {
423
+ ...options,
424
+ configFilePath,
425
+ allowCliList,
426
+ });
189
427
  if (externalContext) {
190
428
  return externalContext;
191
429
  }
192
430
  throw new Error(`--resume is not supported for backend "${backend}"`);
193
431
  }
194
432
 
433
+ if (provider === "copilot") {
434
+ const copilotContext = await resolveCopilotResumeContext(normalizedSessionId, {
435
+ ...options,
436
+ configFilePath,
437
+ allowCliList,
438
+ backend,
439
+ runtimeBackend: lookupBackend || provider,
440
+ });
441
+ if (!copilotContext) {
442
+ throw new Error(`Invalid --resume session id for copilot: ${normalizedSessionId}`);
443
+ }
444
+ return copilotContext;
445
+ }
446
+
195
447
  const sessionPath = await findSessionPath(provider, normalizedSessionId, options);
196
448
  if (!sessionPath) {
197
449
  throw new Error(`Invalid --resume session id for ${provider}: ${normalizedSessionId}`);
@@ -298,56 +550,6 @@ async function extractClaudeResumeCwd(sessionPath, sessionId) {
298
550
  return null;
299
551
  }
300
552
 
301
- async function extractCopilotResumeCwd(sessionPath) {
302
- let stats;
303
- try {
304
- stats = await fsp.stat(sessionPath);
305
- } catch {
306
- return null;
307
- }
308
-
309
- if (stats.isDirectory()) {
310
- const workspaceYamlPath = path.join(sessionPath, "workspace.yaml");
311
- try {
312
- const yamlContent = await fsp.readFile(workspaceYamlPath, "utf8");
313
- const parsed = yaml.load(yamlContent);
314
- const maybeCwd = parsed && typeof parsed === "object" ? parsed.cwd : null;
315
- if (typeof maybeCwd === "string" && maybeCwd.trim()) {
316
- return maybeCwd.trim();
317
- }
318
- } catch {
319
- return null;
320
- }
321
- return null;
322
- }
323
-
324
- if (!sessionPath.endsWith(".jsonl")) {
325
- return null;
326
- }
327
-
328
- const rl = readline.createInterface({
329
- input: fs.createReadStream(sessionPath),
330
- crlfDelay: Infinity,
331
- });
332
- for await (const line of rl) {
333
- const trimmed = line.trim();
334
- if (!trimmed) {
335
- continue;
336
- }
337
- let entry;
338
- try {
339
- entry = JSON.parse(trimmed);
340
- } catch {
341
- continue;
342
- }
343
- const maybeCwd = entry?.data?.context?.cwd || entry?.data?.cwd;
344
- if (typeof maybeCwd === "string" && maybeCwd.trim()) {
345
- return maybeCwd.trim();
346
- }
347
- }
348
- return null;
349
- }
350
-
351
553
  function md5Hex(value) {
352
554
  return crypto.createHash("md5").update(String(value ?? "")).digest("hex");
353
555
  }
@@ -456,21 +658,44 @@ async function loadConductorSessionRecords(options = {}) {
456
658
  return records;
457
659
  }
458
660
 
459
- async function loadConfiguredAllowCliList(options = {}) {
661
+ async function loadParsedConfigFile(options = {}) {
460
662
  const configFilePath = resolveConfigFilePath(options);
461
- let parsed = null;
462
663
  try {
463
664
  const content = await fsp.readFile(configFilePath, "utf8");
464
- parsed = yaml.load(content);
665
+ const parsed = yaml.load(content);
666
+ return parsed && typeof parsed === "object" ? parsed : null;
465
667
  } catch {
466
- return {};
668
+ return null;
467
669
  }
670
+ }
671
+
672
+ async function loadConfiguredAllowCliList(options = {}) {
673
+ const configFilePath = resolveConfigFilePath(options);
674
+ const parsed = await loadParsedConfigFile({ ...options, configFilePath });
468
675
  if (!parsed || typeof parsed !== "object" || !parsed.allow_cli_list || typeof parsed.allow_cli_list !== "object") {
469
676
  return {};
470
677
  }
471
678
  return filterRuntimeSupportedAllowCliList(parsed.allow_cli_list, { configFilePath });
472
679
  }
473
680
 
681
+ async function loadConfiguredEnvMap(options = {}) {
682
+ const parsed = await loadParsedConfigFile(options);
683
+ if (!parsed || typeof parsed !== "object" || !parsed.envs || typeof parsed.envs !== "object") {
684
+ return {};
685
+ }
686
+ const proxyEnv = proxyToEnv(parsed.envs);
687
+ const normalizedEnv = {
688
+ ...proxyEnv,
689
+ };
690
+ for (const [key, value] of Object.entries(parsed.envs)) {
691
+ const normalizedValue = normalizeEnvConfigValue(value);
692
+ if (normalizedValue !== undefined) {
693
+ normalizedEnv[key] = normalizedValue;
694
+ }
695
+ }
696
+ return normalizedEnv;
697
+ }
698
+
474
699
  async function resolveResumeLookupBackend(backend, options = {}) {
475
700
  const normalizedBackend = normalizeBackend(backend);
476
701
  if (!normalizedBackend) {
@@ -490,6 +715,195 @@ async function resolveResumeLookupBackend(backend, options = {}) {
490
715
  return normalizeRuntimeBackendAlias(normalizedBackend, { configFilePath });
491
716
  }
492
717
 
718
+ async function getCopilotSdkModule(options = {}) {
719
+ if (options.copilotSdkModule && typeof options.copilotSdkModule === "object") {
720
+ return options.copilotSdkModule;
721
+ }
722
+ return import("@github/copilot-sdk");
723
+ }
724
+
725
+ async function resolveCopilotCommandLine(options = {}) {
726
+ if (typeof options.commandLine === "string" && options.commandLine.trim()) {
727
+ return options.commandLine.trim();
728
+ }
729
+ const configFilePath = resolveConfigFilePath(options);
730
+ const allowCliList =
731
+ options.allowCliList && typeof options.allowCliList === "object"
732
+ ? options.allowCliList
733
+ : await loadConfiguredAllowCliList({ ...options, configFilePath });
734
+ const backendCandidates = [];
735
+ const pushCandidate = (backend) => {
736
+ const normalized = normalizeBackend(backend);
737
+ if (normalized && !backendCandidates.includes(normalized)) {
738
+ backendCandidates.push(normalized);
739
+ }
740
+ };
741
+ pushCandidate(options.backend);
742
+ pushCandidate(options.runtimeBackend);
743
+ pushCandidate("copilot");
744
+
745
+ for (const candidate of backendCandidates) {
746
+ const configuredBackend = await resolveConfiguredRuntimeBackend(candidate, allowCliList, {
747
+ configFilePath,
748
+ });
749
+ const commandLine =
750
+ typeof configuredBackend?.commandLine === "string" && configuredBackend.commandLine.trim()
751
+ ? configuredBackend.commandLine.trim()
752
+ : "";
753
+ if (commandLine) {
754
+ return commandLine;
755
+ }
756
+ }
757
+ return "";
758
+ }
759
+
760
+ async function buildCopilotClientOptions(options = {}) {
761
+ const clientOptions = options.copilotClientOptions && typeof options.copilotClientOptions === "object"
762
+ ? { ...options.copilotClientOptions }
763
+ : {};
764
+ const configFilePath = resolveConfigFilePath(options);
765
+ const configEnv = await loadConfiguredEnvMap({ ...options, configFilePath });
766
+ const commandLine = await resolveCopilotCommandLine(options);
767
+ const cliLaunch = resolveCopilotCliLaunch(commandLine, {
768
+ ...process.env,
769
+ ...configEnv,
770
+ ...options.env,
771
+ });
772
+ if (cliLaunch && clientOptions.cliPath === undefined && clientOptions.cliArgs === undefined && clientOptions.cliUrl === undefined) {
773
+ if (cliLaunch.cliPath !== undefined) {
774
+ clientOptions.cliPath = cliLaunch.cliPath;
775
+ }
776
+ if (cliLaunch.cliArgs !== undefined) {
777
+ clientOptions.cliArgs = cliLaunch.cliArgs;
778
+ }
779
+ }
780
+
781
+ const explicitGithubToken =
782
+ typeof clientOptions.githubToken === "string" && clientOptions.githubToken.trim()
783
+ ? clientOptions.githubToken.trim()
784
+ : typeof options.githubToken === "string" && options.githubToken.trim()
785
+ ? options.githubToken.trim()
786
+ : "";
787
+ if (clientOptions.githubToken === undefined && explicitGithubToken) {
788
+ clientOptions.githubToken = explicitGithubToken;
789
+ }
790
+ if (clientOptions.useLoggedInUser === undefined && typeof options.useLoggedInUser === "boolean") {
791
+ clientOptions.useLoggedInUser = options.useLoggedInUser;
792
+ }
793
+
794
+ let resolvedEnv;
795
+ if (clientOptions.env === undefined) {
796
+ resolvedEnv = {
797
+ ...process.env,
798
+ ...configEnv,
799
+ ...(options.env && typeof options.env === "object" ? options.env : {}),
800
+ ...(hasOwnEnumerableKeys(cliLaunch?.env) ? cliLaunch.env : {}),
801
+ };
802
+ } else if (hasOwnEnumerableKeys(cliLaunch?.env)) {
803
+ resolvedEnv = {
804
+ ...clientOptions.env,
805
+ ...cliLaunch.env,
806
+ };
807
+ } else {
808
+ resolvedEnv = { ...clientOptions.env };
809
+ }
810
+ clientOptions.env = explicitGithubToken
811
+ ? resolvedEnv
812
+ : withoutCopilotGithubTokenEnv(resolvedEnv);
813
+ if (!explicitGithubToken && clientOptions.useLoggedInUser === undefined) {
814
+ clientOptions.useLoggedInUser = true;
815
+ }
816
+ if (clientOptions.cwd === undefined) {
817
+ const cwd =
818
+ typeof options.cwd === "string" && options.cwd.trim()
819
+ ? options.cwd.trim()
820
+ : process.cwd();
821
+ clientOptions.cwd = cwd;
822
+ }
823
+ return clientOptions;
824
+ }
825
+
826
+ async function withCopilotClient(options, fn) {
827
+ const sdkModule = await getCopilotSdkModule(options);
828
+ if (!sdkModule || typeof sdkModule.CopilotClient !== "function") {
829
+ throw new Error("GitHub Copilot SDK client is unavailable");
830
+ }
831
+ const timeoutMs = resolvePositiveTimeoutMs(
832
+ options.copilotResumeTimeoutMs ?? options.timeoutMs,
833
+ DEFAULT_COPILOT_RESUME_TIMEOUT_MS,
834
+ );
835
+ const startedAtMs = Date.now();
836
+ const client = new sdkModule.CopilotClient(await buildCopilotClientOptions(options));
837
+ try {
838
+ if (typeof client.start === "function") {
839
+ const startTimeoutMs = remainingTimeoutMs(startedAtMs, timeoutMs, "copilot resume lookup timed out");
840
+ await withTimeout(
841
+ client.start(),
842
+ startTimeoutMs,
843
+ "copilot resume SDK start timed out",
844
+ );
845
+ }
846
+ const lookupTimeoutMs = remainingTimeoutMs(startedAtMs, timeoutMs, "copilot resume lookup timed out");
847
+ return await withTimeout(
848
+ fn(client),
849
+ lookupTimeoutMs,
850
+ "copilot resume lookup timed out",
851
+ );
852
+ } finally {
853
+ try {
854
+ if (typeof client.stop === "function") {
855
+ const stopTimeoutMs = resolvePositiveTimeoutMs(
856
+ options.copilotResumeStopTimeoutMs,
857
+ DEFAULT_COPILOT_RESUME_STOP_TIMEOUT_MS,
858
+ );
859
+ await withTimeout(
860
+ client.stop(),
861
+ stopTimeoutMs,
862
+ "copilot resume SDK stop timed out",
863
+ );
864
+ }
865
+ } catch {
866
+ try {
867
+ await client.forceStop?.();
868
+ } catch {
869
+ // best effort
870
+ }
871
+ }
872
+ }
873
+ }
874
+
875
+ async function resolveCopilotResumeContext(sessionId, options = {}) {
876
+ const sessionMetadata = await withCopilotClient(options, async (client) => {
877
+ const sessions = await client.listSessions();
878
+ return sessions.find((entry) => normalizeSessionId(entry?.sessionId) === sessionId) || null;
879
+ });
880
+ if (!sessionMetadata) {
881
+ return null;
882
+ }
883
+
884
+ const cwd = normalizeProjectPathCandidate(sessionMetadata?.context?.cwd);
885
+ if (!cwd) {
886
+ throw new Error(`Could not resolve workspace for copilot session ${sessionId}`);
887
+ }
888
+ if (!(await isExistingDirectory(cwd))) {
889
+ throw new Error(`Resume workspace path does not exist: ${cwd}`);
890
+ }
891
+
892
+ return {
893
+ provider: "copilot",
894
+ sessionId,
895
+ sessionPath: null,
896
+ cwd,
897
+ debugMetadata: {
898
+ cwdSource: "sdk_list_sessions",
899
+ sessionPath: null,
900
+ context: sessionMetadata?.context && typeof sessionMetadata.context === "object"
901
+ ? { ...sessionMetadata.context }
902
+ : undefined,
903
+ },
904
+ };
905
+ }
906
+
493
907
  function normalizeProjectPathCandidate(value) {
494
908
  return typeof value === "string" && value.trim() ? value.trim() : "";
495
909
  }
@@ -500,7 +914,10 @@ function normalizeConductorRecordSourcePath(value) {
500
914
 
501
915
  async function resolveExternalResumeContext(backend, sessionId, options = {}) {
502
916
  const configFilePath = resolveConfigFilePath(options);
503
- const allowCliList = await loadConfiguredAllowCliList({ ...options, configFilePath });
917
+ const allowCliList =
918
+ options.allowCliList && typeof options.allowCliList === "object"
919
+ ? options.allowCliList
920
+ : await loadConfiguredAllowCliList({ ...options, configFilePath });
504
921
  const normalizedBackend = await resolveResumeLookupBackend(backend, {
505
922
  ...options,
506
923
  configFilePath,
@@ -652,9 +1069,6 @@ async function extractResumeCwdFromSession(provider, sessionPath, sessionId, opt
652
1069
  if (provider === "claude") {
653
1070
  return extractClaudeResumeCwd(sessionPath, sessionId);
654
1071
  }
655
- if (provider === "copilot") {
656
- return extractCopilotResumeCwd(sessionPath);
657
- }
658
1072
  if (provider === "kimi") {
659
1073
  return resolveKimiResumeCwd(sessionPath, sessionId, options);
660
1074
  }
@@ -739,29 +1153,6 @@ async function findClaudeSessionEntries(projectsDir, sessionId) {
739
1153
  return entries;
740
1154
  }
741
1155
 
742
- async function findPathByName(rootDir, sessionId) {
743
- const queue = [rootDir];
744
- while (queue.length) {
745
- const current = queue.pop();
746
- let entries = [];
747
- try {
748
- entries = await fsp.readdir(current, { withFileTypes: true });
749
- } catch {
750
- continue;
751
- }
752
- for (const entry of entries) {
753
- const fullPath = path.join(current, entry.name);
754
- if (entry.name.includes(sessionId)) {
755
- return fullPath;
756
- }
757
- if (entry.isDirectory()) {
758
- queue.push(fullPath);
759
- }
760
- }
761
- }
762
- return null;
763
- }
764
-
765
1156
  async function findKimiSessionDirectory(rootDir, sessionId) {
766
1157
  let hashDirs = [];
767
1158
  try {