botmux 3.2.0 → 3.2.1

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.
Files changed (169) hide show
  1. package/README.en.md +1 -0
  2. package/README.md +1 -0
  3. package/dist/adapters/cli/codex.d.ts.map +1 -1
  4. package/dist/adapters/cli/codex.js +13 -1
  5. package/dist/adapters/cli/codex.js.map +1 -1
  6. package/dist/adapters/cli/traex.d.ts.map +1 -1
  7. package/dist/adapters/cli/traex.js +11 -1
  8. package/dist/adapters/cli/traex.js.map +1 -1
  9. package/dist/adapters/cli/types.d.ts +6 -0
  10. package/dist/adapters/cli/types.d.ts.map +1 -1
  11. package/dist/bot-registry.d.ts +7 -0
  12. package/dist/bot-registry.d.ts.map +1 -1
  13. package/dist/bot-registry.js +1 -0
  14. package/dist/bot-registry.js.map +1 -1
  15. package/dist/cli/daemon-lifecycle-env.d.ts +11 -0
  16. package/dist/cli/daemon-lifecycle-env.d.ts.map +1 -0
  17. package/dist/cli/daemon-lifecycle-env.js +25 -0
  18. package/dist/cli/daemon-lifecycle-env.js.map +1 -0
  19. package/dist/cli.d.ts.map +1 -1
  20. package/dist/cli.js +11 -4
  21. package/dist/cli.js.map +1 -1
  22. package/dist/codex-rpc-engine.d.ts +110 -0
  23. package/dist/codex-rpc-engine.d.ts.map +1 -0
  24. package/dist/codex-rpc-engine.js +496 -0
  25. package/dist/codex-rpc-engine.js.map +1 -0
  26. package/dist/codex-rpc-lifecycle.d.ts +157 -0
  27. package/dist/codex-rpc-lifecycle.d.ts.map +1 -0
  28. package/dist/codex-rpc-lifecycle.js +210 -0
  29. package/dist/codex-rpc-lifecycle.js.map +1 -0
  30. package/dist/config.d.ts +1 -0
  31. package/dist/config.d.ts.map +1 -1
  32. package/dist/config.js +14 -2
  33. package/dist/config.js.map +1 -1
  34. package/dist/core/attachment-path.d.ts +3 -0
  35. package/dist/core/attachment-path.d.ts.map +1 -0
  36. package/dist/core/attachment-path.js +8 -0
  37. package/dist/core/attachment-path.js.map +1 -0
  38. package/dist/core/command-handler.d.ts.map +1 -1
  39. package/dist/core/command-handler.js +6 -1
  40. package/dist/core/command-handler.js.map +1 -1
  41. package/dist/core/dashboard-images.d.ts +29 -0
  42. package/dist/core/dashboard-images.d.ts.map +1 -0
  43. package/dist/core/dashboard-images.js +113 -0
  44. package/dist/core/dashboard-images.js.map +1 -0
  45. package/dist/core/dashboard-ipc-server.d.ts.map +1 -1
  46. package/dist/core/dashboard-ipc-server.js +13 -1
  47. package/dist/core/dashboard-ipc-server.js.map +1 -1
  48. package/dist/core/github-auth.d.ts +5 -0
  49. package/dist/core/github-auth.d.ts.map +1 -1
  50. package/dist/core/github-auth.js +38 -0
  51. package/dist/core/github-auth.js.map +1 -1
  52. package/dist/core/maintenance.d.ts +1 -0
  53. package/dist/core/maintenance.d.ts.map +1 -1
  54. package/dist/core/maintenance.js +9 -1
  55. package/dist/core/maintenance.js.map +1 -1
  56. package/dist/core/session-create.d.ts +10 -0
  57. package/dist/core/session-create.d.ts.map +1 -1
  58. package/dist/core/session-create.js +21 -0
  59. package/dist/core/session-create.js.map +1 -1
  60. package/dist/core/session-manager.d.ts +4 -1
  61. package/dist/core/session-manager.d.ts.map +1 -1
  62. package/dist/core/session-manager.js +25 -13
  63. package/dist/core/session-manager.js.map +1 -1
  64. package/dist/core/trigger-session.d.ts +3 -0
  65. package/dist/core/trigger-session.d.ts.map +1 -1
  66. package/dist/core/trigger-session.js +17 -3
  67. package/dist/core/trigger-session.js.map +1 -1
  68. package/dist/core/worker-pool.d.ts.map +1 -1
  69. package/dist/core/worker-pool.js +2 -0
  70. package/dist/core/worker-pool.js.map +1 -1
  71. package/dist/daemon.d.ts.map +1 -1
  72. package/dist/daemon.js +19 -3
  73. package/dist/daemon.js.map +1 -1
  74. package/dist/dashboard/connector-api.d.ts.map +1 -1
  75. package/dist/dashboard/connector-api.js +19 -0
  76. package/dist/dashboard/connector-api.js.map +1 -1
  77. package/dist/dashboard/settings-write-applier.d.ts +2 -1
  78. package/dist/dashboard/settings-write-applier.d.ts.map +1 -1
  79. package/dist/dashboard/settings-write-applier.js +6 -0
  80. package/dist/dashboard/settings-write-applier.js.map +1 -1
  81. package/dist/dashboard/web/connectors-page.d.ts.map +1 -1
  82. package/dist/dashboard/web/connectors-page.js +23 -2
  83. package/dist/dashboard/web/connectors-page.js.map +1 -1
  84. package/dist/dashboard/web/create-session-composer.d.ts +35 -0
  85. package/dist/dashboard/web/create-session-composer.d.ts.map +1 -0
  86. package/dist/dashboard/web/create-session-composer.js +70 -0
  87. package/dist/dashboard/web/create-session-composer.js.map +1 -0
  88. package/dist/dashboard/web/i18n.d.ts.map +1 -1
  89. package/dist/dashboard/web/i18n.js +65 -5
  90. package/dist/dashboard/web/i18n.js.map +1 -1
  91. package/dist/dashboard/web/sessions-page.d.ts.map +1 -1
  92. package/dist/dashboard/web/sessions-page.js +147 -1
  93. package/dist/dashboard/web/sessions-page.js.map +1 -1
  94. package/dist/dashboard/web/settings-page.d.ts.map +1 -1
  95. package/dist/dashboard/web/settings-page.js +2 -1
  96. package/dist/dashboard/web/settings-page.js.map +1 -1
  97. package/dist/dashboard/web/skills-page.d.ts.map +1 -1
  98. package/dist/dashboard/web/skills-page.js +5 -2
  99. package/dist/dashboard/web/skills-page.js.map +1 -1
  100. package/dist/dashboard/webhook-routes.d.ts +4 -0
  101. package/dist/dashboard/webhook-routes.d.ts.map +1 -1
  102. package/dist/dashboard/webhook-routes.js +19 -0
  103. package/dist/dashboard/webhook-routes.js.map +1 -1
  104. package/dist/dashboard-web/app.js +1 -1
  105. package/dist/dashboard-web/chunks/{bot-defaults-page-4PM4T6QQ.js → bot-defaults-page-O37SAQD5.js} +1 -1
  106. package/dist/dashboard-web/chunks/{chunk-RXVA6XW3.js → chunk-6QHMLMIM.js} +6 -6
  107. package/dist/dashboard-web/chunks/{chunk-IKCZ677K.js → chunk-B42PCL5P.js} +1 -1
  108. package/dist/dashboard-web/chunks/{chunk-3PMN55Z6.js → chunk-FYALHCPG.js} +1 -1
  109. package/dist/dashboard-web/chunks/{chunk-AIDN2WZB.js → chunk-O2DPQL7C.js} +1 -1
  110. package/dist/dashboard-web/chunks/connectors-page-DZBQ42MH.js +1 -0
  111. package/dist/dashboard-web/chunks/{groups-page-TAU2UIVS.js → groups-page-3IEGRZKQ.js} +1 -1
  112. package/dist/dashboard-web/chunks/{insights-page-PJ2XVM6T.js → insights-page-X3ADRON3.js} +1 -1
  113. package/dist/dashboard-web/chunks/{monitor-room-D6445HJO.js → monitor-room-IV36RGQY.js} +1 -1
  114. package/dist/dashboard-web/chunks/{monitoring-page-GUHMCKSO.js → monitoring-page-ADFKVON4.js} +1 -1
  115. package/dist/dashboard-web/chunks/{office-page-6K6U2MS5.js → office-page-CALAOWNS.js} +1 -1
  116. package/dist/dashboard-web/chunks/{overview-page-USDYU7U5.js → overview-page-SLMGLK3M.js} +1 -1
  117. package/dist/dashboard-web/chunks/{roles-page-O4OUA7ZH.js → roles-page-L7JJ4FFV.js} +1 -1
  118. package/dist/dashboard-web/chunks/{schedules-page-LNIBXJSZ.js → schedules-page-Z3672POL.js} +1 -1
  119. package/dist/dashboard-web/chunks/sessions-page-OKJ5YWUZ.js +3 -0
  120. package/dist/dashboard-web/chunks/settings-page-P6MI2ZUE.js +2 -0
  121. package/dist/dashboard-web/chunks/{skills-page-M5KYNXBA.js → skills-page-FT7BVZ6X.js} +4 -4
  122. package/dist/dashboard-web/chunks/{team-federation-page-GT7UFQMN.js → team-federation-page-DEYCPUFB.js} +1 -1
  123. package/dist/dashboard-web/chunks/{v3-page-UYZIRJ2C.js → v3-page-4IOWREZG.js} +1 -1
  124. package/dist/dashboard-web/chunks/{whiteboards-page-WAR77DQE.js → whiteboards-page-2VMBETZH.js} +1 -1
  125. package/dist/dashboard-web/style.css +191 -0
  126. package/dist/dashboard.js +33 -6
  127. package/dist/dashboard.js.map +1 -1
  128. package/dist/global-config.d.ts +7 -0
  129. package/dist/global-config.d.ts.map +1 -1
  130. package/dist/global-config.js +2 -0
  131. package/dist/global-config.js.map +1 -1
  132. package/dist/im/lark/ask-card.d.ts.map +1 -1
  133. package/dist/im/lark/ask-card.js +52 -4
  134. package/dist/im/lark/ask-card.js.map +1 -1
  135. package/dist/im/lark/card-handler.d.ts +2 -0
  136. package/dist/im/lark/card-handler.d.ts.map +1 -1
  137. package/dist/im/lark/card-handler.js +19 -2
  138. package/dist/im/lark/card-handler.js.map +1 -1
  139. package/dist/im/lark/event-dispatcher.d.ts +3 -0
  140. package/dist/im/lark/event-dispatcher.d.ts.map +1 -1
  141. package/dist/im/lark/event-dispatcher.js +706 -693
  142. package/dist/im/lark/event-dispatcher.js.map +1 -1
  143. package/dist/im/lark/grant-pending.d.ts +8 -4
  144. package/dist/im/lark/grant-pending.d.ts.map +1 -1
  145. package/dist/im/lark/grant-pending.js +13 -6
  146. package/dist/im/lark/grant-pending.js.map +1 -1
  147. package/dist/services/connector-store.d.ts +8 -0
  148. package/dist/services/connector-store.d.ts.map +1 -1
  149. package/dist/services/connector-store.js.map +1 -1
  150. package/dist/services/session-store.d.ts +2 -1
  151. package/dist/services/session-store.d.ts.map +1 -1
  152. package/dist/services/session-store.js +60 -2
  153. package/dist/services/session-store.js.map +1 -1
  154. package/dist/services/skill-registry-store.d.ts +16 -0
  155. package/dist/services/skill-registry-store.d.ts.map +1 -1
  156. package/dist/services/skill-registry-store.js +343 -47
  157. package/dist/services/skill-registry-store.js.map +1 -1
  158. package/dist/services/trigger-types.d.ts +5 -0
  159. package/dist/services/trigger-types.d.ts.map +1 -1
  160. package/dist/services/trigger-types.js +12 -0
  161. package/dist/services/trigger-types.js.map +1 -1
  162. package/dist/types.d.ts +13 -2
  163. package/dist/types.d.ts.map +1 -1
  164. package/dist/worker.js +440 -34
  165. package/dist/worker.js.map +1 -1
  166. package/package.json +1 -1
  167. package/dist/dashboard-web/chunks/connectors-page-2MKF4YTN.js +0 -1
  168. package/dist/dashboard-web/chunks/sessions-page-B6U6TCFF.js +0 -3
  169. package/dist/dashboard-web/chunks/settings-page-PXLRFF7I.js +0 -2
@@ -1,10 +1,11 @@
1
- import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, rmSync, statSync } from 'node:fs';
1
+ import { closeSync, cpSync, existsSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readSync, readdirSync, realpathSync, rmSync, statSync } from 'node:fs';
2
2
  import { execFile, execFileSync } from 'node:child_process';
3
3
  import { createHash } from 'node:crypto';
4
4
  import { basename, dirname, isAbsolute, join, relative, resolve } from 'node:path';
5
5
  import { promisify } from 'node:util';
6
6
  import { atomicWriteFileSync } from '../utils/atomic-write.js';
7
7
  import { withFileLock, withFileLockSync } from '../utils/file-lock.js';
8
+ import { githubGitAuthEnv } from '../core/github-auth.js';
8
9
  import { loadSkillPackage } from '../core/skills/package.js';
9
10
  import { skillRegistryPath, skillSourcesDir, skillStoreDir } from '../core/skills/registry-paths.js';
10
11
  import { assertAllowedGitProtocol, assertNoGitUrlCredentials, assertSafeGitRef, assertSafeGitSkillPath, githubToGitUrl, redactGitUrlCredentials } from '../core/skills/sources.js';
@@ -30,6 +31,79 @@ function writeSkillRegistry(registry) {
30
31
  mkdirSync(dirname(skillRegistryPath()), { recursive: true });
31
32
  atomicWriteFileSync(skillRegistryPath(), JSON.stringify(registry, null, 2) + '\n', { mode: 0o600 });
32
33
  }
34
+ function shebangRuntime(path) {
35
+ const buffer = Buffer.alloc(256);
36
+ let fd;
37
+ try {
38
+ fd = openSync(path, 'r');
39
+ const length = readSync(fd, buffer, 0, buffer.length, 0);
40
+ const line = buffer.subarray(0, length).toString('utf8').split(/\r?\n/, 1)[0];
41
+ const match = /^#!\s*(\S+)(?:\s+(\S+))?/.exec(line);
42
+ if (!match)
43
+ return undefined;
44
+ const command = basename(match[1]);
45
+ return command === 'env' && match[2] ? basename(match[2]) : command;
46
+ }
47
+ catch {
48
+ return undefined;
49
+ }
50
+ finally {
51
+ if (fd !== undefined)
52
+ closeSync(fd);
53
+ }
54
+ }
55
+ /** Static, non-executing install audit. Paths are relative to the Skill root;
56
+ * source URLs and absolute host paths are deliberately excluded from logs. */
57
+ export function buildSkillInstallAuditSummary(pkg) {
58
+ let files = 0;
59
+ let directories = 0;
60
+ let symlinks = 0;
61
+ let bytes = 0;
62
+ const executablePaths = [];
63
+ const runtimes = new Set();
64
+ const pending = [''];
65
+ while (pending.length > 0) {
66
+ const relDir = pending.pop();
67
+ const absDir = relDir ? join(pkg.rootDir, relDir) : pkg.rootDir;
68
+ for (const entry of readdirSync(absDir, { withFileTypes: true })) {
69
+ const rel = (relDir ? join(relDir, entry.name) : entry.name).replace(/\\/g, '/');
70
+ const abs = join(pkg.rootDir, rel);
71
+ if (entry.isSymbolicLink()) {
72
+ symlinks++;
73
+ continue;
74
+ }
75
+ if (entry.isDirectory()) {
76
+ directories++;
77
+ pending.push(rel);
78
+ continue;
79
+ }
80
+ if (!entry.isFile())
81
+ continue;
82
+ files++;
83
+ const stat = lstatSync(abs);
84
+ bytes += stat.size;
85
+ if ((stat.mode & 0o111) !== 0) {
86
+ executablePaths.push(rel);
87
+ runtimes.add(shebangRuntime(abs) ?? 'native');
88
+ }
89
+ }
90
+ }
91
+ executablePaths.sort();
92
+ const commit = pkg.source.type === 'git' || pkg.source.type === 'github' ? pkg.source.commit : undefined;
93
+ return {
94
+ name: pkg.name,
95
+ sourceType: pkg.source.type,
96
+ ...(commit ? { commit } : {}),
97
+ ...(pkg.version ? { version: pkg.version } : {}),
98
+ files,
99
+ directories,
100
+ symlinks,
101
+ bytes,
102
+ executables: executablePaths.slice(0, 32),
103
+ executablesTruncated: executablePaths.length > 32,
104
+ runtimes: [...runtimes].sort(),
105
+ };
106
+ }
33
107
  export function installLocalSkill(dir, opts) {
34
108
  const sourceDir = resolve(dir);
35
109
  const provisional = loadSkillPackage(sourceDir, {
@@ -288,34 +362,62 @@ function formatGitFailure(args, err) {
288
362
  // transport ever reached this layer, git itself refuses anything outside the
289
363
  // allowlist. GIT_TERMINAL_PROMPT=0 also keeps a private repo from hanging on an
290
364
  // interactive credential prompt instead of failing fast.
291
- function gitEnv() {
365
+ function isGithubHttpsUrl(raw) {
366
+ if (!raw)
367
+ return false;
368
+ try {
369
+ const url = new URL(raw);
370
+ return url.protocol === 'https:' && (url.hostname === 'github.com' || url.hostname === 'www.github.com');
371
+ }
372
+ catch {
373
+ return false;
374
+ }
375
+ }
376
+ function githubSshUrl(raw) {
377
+ if (!isGithubHttpsUrl(raw))
378
+ return null;
379
+ try {
380
+ const url = new URL(raw);
381
+ const path = url.pathname.replace(/^\/+/, '');
382
+ return path ? `git@github.com:${path}` : null;
383
+ }
384
+ catch {
385
+ return null;
386
+ }
387
+ }
388
+ function isGitAuthenticationFailure(err) {
389
+ const message = err instanceof Error ? err.message : String(err);
390
+ return /authentication failed|could not read username|terminal prompts disabled|repository not found|unauthor|\b401\b|\b403\b/i.test(message);
391
+ }
392
+ function gitEnv(extraEnv) {
292
393
  return {
293
394
  ...process.env,
294
395
  GIT_ALLOW_PROTOCOL: 'https:http:ssh:git:file',
295
396
  GIT_TERMINAL_PROMPT: '0',
397
+ ...extraEnv,
296
398
  };
297
399
  }
298
- function git(args, cwd) {
400
+ function git(args, cwd, extraEnv) {
299
401
  try {
300
402
  return execFileSync('git', args, {
301
403
  cwd,
302
404
  encoding: 'utf-8',
303
405
  stdio: ['ignore', 'pipe', 'pipe'],
304
406
  timeout: gitTimeoutMs(),
305
- env: gitEnv(),
407
+ env: gitEnv(extraEnv),
306
408
  }).trim();
307
409
  }
308
410
  catch (err) {
309
411
  throw formatGitFailure(args, err);
310
412
  }
311
413
  }
312
- async function gitAsync(args, cwd) {
414
+ async function gitAsync(args, cwd, extraEnv) {
313
415
  try {
314
416
  const result = await execFileAsync('git', args, {
315
417
  cwd,
316
418
  encoding: 'utf-8',
317
419
  timeout: gitTimeoutMs(),
318
- env: gitEnv(),
420
+ env: gitEnv(extraEnv),
319
421
  });
320
422
  return String(result.stdout ?? '').trim();
321
423
  }
@@ -323,16 +425,144 @@ async function gitAsync(args, cwd) {
323
425
  throw formatGitFailure(args, err);
324
426
  }
325
427
  }
428
+ function gitErrorMessage(error) {
429
+ return error instanceof Error ? error.message : String(error);
430
+ }
431
+ function gitFallbackFailure(httpsError, anonymousError, sshError) {
432
+ return new Error([
433
+ gitErrorMessage(httpsError),
434
+ ...(anonymousError && anonymousError !== httpsError
435
+ ? [`anonymous HTTPS retry failed: ${gitErrorMessage(anonymousError)}`]
436
+ : []),
437
+ ...(sshError ? [`ssh fallback failed: ${gitErrorMessage(sshError)}`] : []),
438
+ ].join('; '));
439
+ }
440
+ /**
441
+ * For GitHub HTTPS, try the botmux-resolved token first. If that credential is
442
+ * stale, retry the same operation without botmux's header so public repos and
443
+ * system credential helpers still work, then use SSH only after the anonymous
444
+ * HTTPS attempt also proves to be an authentication failure.
445
+ */
446
+ function withGithubHttpsFallback(url, attemptHttps, attemptSsh) {
447
+ const sshUrl = githubSshUrl(url);
448
+ const authEnv = sshUrl ? githubGitAuthEnv() : {};
449
+ const hasBotmuxAuth = Object.keys(authEnv).length > 0;
450
+ try {
451
+ return attemptHttps(hasBotmuxAuth ? authEnv : undefined);
452
+ }
453
+ catch (httpsError) {
454
+ if (!sshUrl || !isGitAuthenticationFailure(httpsError))
455
+ throw httpsError;
456
+ let anonymousError = httpsError;
457
+ if (hasBotmuxAuth) {
458
+ try {
459
+ return attemptHttps();
460
+ }
461
+ catch (error) {
462
+ anonymousError = error;
463
+ if (!isGitAuthenticationFailure(error)) {
464
+ throw gitFallbackFailure(httpsError, anonymousError);
465
+ }
466
+ }
467
+ }
468
+ try {
469
+ return attemptSsh(sshUrl);
470
+ }
471
+ catch (sshError) {
472
+ throw gitFallbackFailure(httpsError, anonymousError, sshError);
473
+ }
474
+ }
475
+ }
476
+ async function withGithubHttpsFallbackAsync(url, attemptHttps, attemptSsh) {
477
+ const sshUrl = githubSshUrl(url);
478
+ const authEnv = sshUrl ? githubGitAuthEnv() : {};
479
+ const hasBotmuxAuth = Object.keys(authEnv).length > 0;
480
+ try {
481
+ return await attemptHttps(hasBotmuxAuth ? authEnv : undefined);
482
+ }
483
+ catch (httpsError) {
484
+ if (!sshUrl || !isGitAuthenticationFailure(httpsError))
485
+ throw httpsError;
486
+ let anonymousError = httpsError;
487
+ if (hasBotmuxAuth) {
488
+ try {
489
+ return await attemptHttps();
490
+ }
491
+ catch (error) {
492
+ anonymousError = error;
493
+ if (!isGitAuthenticationFailure(error)) {
494
+ throw gitFallbackFailure(httpsError, anonymousError);
495
+ }
496
+ }
497
+ }
498
+ try {
499
+ return await attemptSsh(sshUrl);
500
+ }
501
+ catch (sshError) {
502
+ throw gitFallbackFailure(httpsError, anonymousError, sshError);
503
+ }
504
+ }
505
+ }
506
+ function cloneGitSource(url, dir) {
507
+ withGithubHttpsFallback(url, authEnv => {
508
+ rmSync(dir, { recursive: true, force: true });
509
+ git(['clone', '--', url, dir], skillSourcesDir(), authEnv);
510
+ }, sshUrl => {
511
+ rmSync(dir, { recursive: true, force: true });
512
+ git(['clone', '--', sshUrl, dir], skillSourcesDir());
513
+ // The cache identity and registry keep the canonical HTTPS source. Future
514
+ // updates re-evaluate HTTPS credentials before falling back to SSH again.
515
+ git(['remote', 'set-url', 'origin', url], dir);
516
+ });
517
+ }
518
+ async function cloneGitSourceAsync(url, dir) {
519
+ await withGithubHttpsFallbackAsync(url, async (authEnv) => {
520
+ rmSync(dir, { recursive: true, force: true });
521
+ await gitAsync(['clone', '--', url, dir], skillSourcesDir(), authEnv);
522
+ }, async (sshUrl) => {
523
+ rmSync(dir, { recursive: true, force: true });
524
+ await gitAsync(['clone', '--', sshUrl, dir], skillSourcesDir());
525
+ await gitAsync(['remote', 'set-url', 'origin', url], dir);
526
+ });
527
+ }
528
+ function fetchGitSource(url, dir, args) {
529
+ if (isGithubHttpsUrl(url))
530
+ git(['remote', 'set-url', 'origin', url], dir);
531
+ return withGithubHttpsFallback(url, authEnv => git(args, dir, authEnv), sshUrl => {
532
+ git(['remote', 'set-url', 'origin', sshUrl], dir);
533
+ try {
534
+ return git(args, dir);
535
+ }
536
+ finally {
537
+ git(['remote', 'set-url', 'origin', url], dir);
538
+ }
539
+ });
540
+ }
541
+ async function fetchGitSourceAsync(url, dir, args) {
542
+ if (isGithubHttpsUrl(url))
543
+ await gitAsync(['remote', 'set-url', 'origin', url], dir);
544
+ return withGithubHttpsFallbackAsync(url, authEnv => gitAsync(args, dir, authEnv), async (sshUrl) => {
545
+ await gitAsync(['remote', 'set-url', 'origin', sshUrl], dir);
546
+ try {
547
+ return await gitAsync(args, dir);
548
+ }
549
+ finally {
550
+ await gitAsync(['remote', 'set-url', 'origin', url], dir);
551
+ }
552
+ });
553
+ }
326
554
  function ensureGitSource(url) {
327
555
  assertNoGitUrlCredentials(url);
328
556
  assertAllowedGitProtocol(url);
329
557
  const dir = join(skillSourcesDir(), sourceId(url));
330
558
  mkdirSync(skillSourcesDir(), { recursive: true });
331
559
  if (existsSync(join(dir, '.git'))) {
332
- git(['fetch', '--tags', '--prune'], dir);
560
+ fetchGitSource(url, dir, ['fetch', '--tags', '--prune']);
333
561
  }
334
562
  else {
335
- git(['clone', '--', url, dir]);
563
+ // Never inherit the daemon's cwd for clone: a daemon can outlive the
564
+ // checkout it was launched from, leaving process.cwd() deleted.
565
+ cloneGitSource(url, dir);
336
566
  }
337
567
  return dir;
338
568
  }
@@ -341,7 +571,7 @@ function checkoutGitSource(url, refValue) {
341
571
  const sourceDir = ensureGitSource(url);
342
572
  const ref = refValue ?? 'HEAD';
343
573
  if (ref === 'HEAD') {
344
- git(['fetch', 'origin', 'HEAD'], sourceDir);
574
+ fetchGitSource(url, sourceDir, ['fetch', 'origin', 'HEAD']);
345
575
  git(['checkout', 'FETCH_HEAD'], sourceDir);
346
576
  }
347
577
  else {
@@ -355,10 +585,12 @@ async function ensureGitSourceAsync(url) {
355
585
  const dir = join(skillSourcesDir(), sourceId(url));
356
586
  mkdirSync(skillSourcesDir(), { recursive: true });
357
587
  if (existsSync(join(dir, '.git'))) {
358
- await gitAsync(['fetch', '--tags', '--prune'], dir);
588
+ await fetchGitSourceAsync(url, dir, ['fetch', '--tags', '--prune']);
359
589
  }
360
590
  else {
361
- await gitAsync(['clone', '--', url, dir]);
591
+ // See the synchronous path above: dashboard jobs must also be independent
592
+ // from a stale/deleted daemon launch directory.
593
+ await cloneGitSourceAsync(url, dir);
362
594
  }
363
595
  return dir;
364
596
  }
@@ -367,7 +599,7 @@ async function checkoutGitSourceAsync(url, refValue) {
367
599
  const sourceDir = await ensureGitSourceAsync(url);
368
600
  const ref = refValue ?? 'HEAD';
369
601
  if (ref === 'HEAD') {
370
- await gitAsync(['fetch', 'origin', 'HEAD'], sourceDir);
602
+ await fetchGitSourceAsync(url, sourceDir, ['fetch', 'origin', 'HEAD']);
371
603
  await gitAsync(['checkout', 'FETCH_HEAD'], sourceDir);
372
604
  }
373
605
  else {
@@ -375,31 +607,89 @@ async function checkoutGitSourceAsync(url, refValue) {
375
607
  }
376
608
  return { sourceDir, ref, commit: await gitAsync(['rev-parse', 'HEAD'], sourceDir) };
377
609
  }
378
- export function discoverGitSkillCandidates(opts) {
379
- return withGitSourceLockSync(opts.url, () => {
380
- const { sourceDir, commit } = checkoutGitSource(opts.url, opts.ref);
381
- if (opts.path) {
382
- const candidate = candidateFromDir(sourceDir, gitSkillDir(sourceDir, opts.path));
383
- return { commit, skills: candidate ? [candidate] : [] };
610
+ function discoverCheckedOutGitSource(sourceDir, commit, opts) {
611
+ if (opts.path) {
612
+ const candidate = candidateFromDir(sourceDir, gitSkillDir(sourceDir, opts.path));
613
+ return { commit, skills: candidate ? [candidate] : [] };
614
+ }
615
+ return {
616
+ commit,
617
+ skills: discoverLocalSkillCandidates(sourceDir, { fullDepth: opts.fullDepth }).skills,
618
+ };
619
+ }
620
+ function checkoutTemporaryGitSource(url, refValue) {
621
+ assertNoGitUrlCredentials(url);
622
+ assertAllowedGitProtocol(url);
623
+ assertSafeGitRef(refValue);
624
+ mkdirSync(skillSourcesDir(), { recursive: true });
625
+ const staging = mkdtempSync(join(skillSourcesDir(), '.discover-'));
626
+ const sourceDir = join(staging, 'repo');
627
+ try {
628
+ cloneGitSource(url, sourceDir);
629
+ const ref = refValue ?? 'HEAD';
630
+ if (ref === 'HEAD') {
631
+ fetchGitSource(url, sourceDir, ['fetch', 'origin', 'HEAD']);
632
+ git(['checkout', 'FETCH_HEAD'], sourceDir);
633
+ }
634
+ else {
635
+ git(['checkout', ref], sourceDir);
384
636
  }
385
637
  return {
386
- commit,
387
- skills: discoverLocalSkillCandidates(sourceDir, { fullDepth: opts.fullDepth }).skills,
638
+ sourceDir,
639
+ commit: git(['rev-parse', 'HEAD'], sourceDir),
640
+ cleanup: () => rmSync(staging, { recursive: true, force: true }),
388
641
  };
389
- });
642
+ }
643
+ catch (error) {
644
+ rmSync(staging, { recursive: true, force: true });
645
+ throw error;
646
+ }
390
647
  }
391
- export async function discoverGitSkillCandidatesAsync(opts) {
392
- return withGitSourceLock(opts.url, async () => {
393
- const { sourceDir, commit } = await checkoutGitSourceAsync(opts.url, opts.ref);
394
- if (opts.path) {
395
- const candidate = candidateFromDir(sourceDir, gitSkillDir(sourceDir, opts.path));
396
- return { commit, skills: candidate ? [candidate] : [] };
648
+ async function checkoutTemporaryGitSourceAsync(url, refValue) {
649
+ assertNoGitUrlCredentials(url);
650
+ assertAllowedGitProtocol(url);
651
+ assertSafeGitRef(refValue);
652
+ mkdirSync(skillSourcesDir(), { recursive: true });
653
+ const staging = mkdtempSync(join(skillSourcesDir(), '.discover-'));
654
+ const sourceDir = join(staging, 'repo');
655
+ try {
656
+ await cloneGitSourceAsync(url, sourceDir);
657
+ const ref = refValue ?? 'HEAD';
658
+ if (ref === 'HEAD') {
659
+ await fetchGitSourceAsync(url, sourceDir, ['fetch', 'origin', 'HEAD']);
660
+ await gitAsync(['checkout', 'FETCH_HEAD'], sourceDir);
661
+ }
662
+ else {
663
+ await gitAsync(['checkout', ref], sourceDir);
397
664
  }
398
665
  return {
399
- commit,
400
- skills: discoverLocalSkillCandidates(sourceDir, { fullDepth: opts.fullDepth }).skills,
666
+ sourceDir,
667
+ commit: await gitAsync(['rev-parse', 'HEAD'], sourceDir),
668
+ cleanup: () => rmSync(staging, { recursive: true, force: true }),
401
669
  };
402
- });
670
+ }
671
+ catch (error) {
672
+ rmSync(staging, { recursive: true, force: true });
673
+ throw error;
674
+ }
675
+ }
676
+ export function discoverGitSkillCandidates(opts) {
677
+ const checkout = checkoutTemporaryGitSource(opts.url, opts.ref);
678
+ try {
679
+ return discoverCheckedOutGitSource(checkout.sourceDir, checkout.commit, opts);
680
+ }
681
+ finally {
682
+ checkout.cleanup();
683
+ }
684
+ }
685
+ export async function discoverGitSkillCandidatesAsync(opts) {
686
+ const checkout = await checkoutTemporaryGitSourceAsync(opts.url, opts.ref);
687
+ try {
688
+ return discoverCheckedOutGitSource(checkout.sourceDir, checkout.commit, opts);
689
+ }
690
+ finally {
691
+ checkout.cleanup();
692
+ }
403
693
  }
404
694
  export function installGitSkill(opts) {
405
695
  return withGitSourceLockSync(opts.url, () => installGitSkillLocked(opts));
@@ -457,28 +747,34 @@ function sourceOverrideForCandidate(source, candidate) {
457
747
  return source;
458
748
  }
459
749
  export function installGitSkillsFromSource(opts) {
460
- const discovery = discoverGitSkillCandidates({ url: opts.url, ref: opts.ref, fullDepth: opts.fullDepth });
461
- const selected = selectDiscoveredSkills(discovery.skills, opts);
462
- return selected.map(candidate => installGitSkill({
463
- url: opts.url,
464
- path: candidate.path,
465
- ref: opts.ref,
466
- sourceOverride: sourceOverrideForCandidate(opts.sourceOverride, candidate),
467
- }));
468
- }
469
- export async function installGitSkillsFromSourceAsync(opts) {
470
- const discovery = await discoverGitSkillCandidatesAsync({ url: opts.url, ref: opts.ref, fullDepth: opts.fullDepth });
471
- const selected = selectDiscoveredSkills(discovery.skills, opts);
472
- const installed = [];
473
- for (const candidate of selected) {
474
- installed.push(await installGitSkillAsync({
750
+ return withGitSourceLockSync(opts.url, () => {
751
+ const checkout = checkoutGitSource(opts.url, opts.ref);
752
+ const discovery = discoverCheckedOutGitSource(checkout.sourceDir, checkout.commit, { fullDepth: opts.fullDepth });
753
+ const selected = selectDiscoveredSkills(discovery.skills, opts);
754
+ return selected.map(candidate => installGitSkillLocked({
475
755
  url: opts.url,
476
756
  path: candidate.path,
477
757
  ref: opts.ref,
478
758
  sourceOverride: sourceOverrideForCandidate(opts.sourceOverride, candidate),
479
759
  }));
480
- }
481
- return installed;
760
+ });
761
+ }
762
+ export async function installGitSkillsFromSourceAsync(opts) {
763
+ return withGitSourceLock(opts.url, async () => {
764
+ const checkout = await checkoutGitSourceAsync(opts.url, opts.ref);
765
+ const discovery = discoverCheckedOutGitSource(checkout.sourceDir, checkout.commit, { fullDepth: opts.fullDepth });
766
+ const selected = selectDiscoveredSkills(discovery.skills, opts);
767
+ const installed = [];
768
+ for (const candidate of selected) {
769
+ installed.push(await installGitSkillAsyncLocked({
770
+ url: opts.url,
771
+ path: candidate.path,
772
+ ref: opts.ref,
773
+ sourceOverride: sourceOverrideForCandidate(opts.sourceOverride, candidate),
774
+ }));
775
+ }
776
+ return installed;
777
+ });
482
778
  }
483
779
  // --- agentbuddy (external CLI) skill source ---------------------------------
484
780
  //