specrails-desktop 2.59.2 → 2.60.0
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/client/dist/assets/{ActivityFeedPage-vAY9C71W.js → ActivityFeedPage-3J7fR8yB.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-CpwjGXQm.js → AgentBrowserCapture-D4a3wYM1.js} +1 -1
- package/client/dist/assets/{AgentModeAnalyticsPane-BHeoJVxQ.js → AgentModeAnalyticsPane-Dps30tk9.js} +2 -2
- package/client/dist/assets/{AgentModeCodePane-B0Nf3YkN.js → AgentModeCodePane-CvLimey8.js} +2 -2
- package/client/dist/assets/{AgentModeJobsPane-CpbIItY_.js → AgentModeJobsPane-25tQlLpF.js} +1 -1
- package/client/dist/assets/{AgentsPage-BVJAFXn3.js → AgentsPage-CUDp_sfw.js} +2 -2
- package/client/dist/assets/{AnalyticsPage-DDWees-K.js → AnalyticsPage-DXWMGn8b.js} +1 -1
- package/client/dist/assets/{CodePage-CzV5tkvS.js → CodePage-D981zRHy.js} +1 -1
- package/client/dist/assets/{DesktopAnalyticsPage-DfRHNK7i.js → DesktopAnalyticsPage-BkFBq0E6.js} +1 -1
- package/client/dist/assets/{DocsDialog-BxduBYbh.js → DocsDialog-BL7uSbIJ.js} +1 -1
- package/client/dist/assets/{DocsPage-DOVeRJn_.js → DocsPage-CsDLbmZX.js} +1 -1
- package/client/dist/assets/{ExportDropdown-BnKVV8UK.js → ExportDropdown-B4Olg1Fk.js} +1 -1
- package/client/dist/assets/{InteractiveJobComposer-Da9dvM-0.js → InteractiveJobComposer-Z6fl4791.js} +2 -2
- package/client/dist/assets/JobDetailModal-DS_Owxs6.js +1 -0
- package/client/dist/assets/JobDetailPage-1TUQ9vlE.js +1 -0
- package/client/dist/assets/{JobsPage-0vdlbNik.js → JobsPage-BSrl8P7u.js} +1 -1
- package/client/dist/assets/LoopBuilderPage-Dh5uk2Qy.js +1 -0
- package/client/dist/assets/{LoopPreviewModal-Df-S8NGf.js → LoopPreviewModal-CH1r-KhU.js} +1 -1
- package/client/dist/assets/{LoopWindowSurface-sdb7QbSo.js → LoopWindowSurface-CiZdI6aw.js} +1 -1
- package/client/dist/assets/{LoopsPage-CDfCC9Px.js → LoopsPage-BwFujdGi.js} +1 -1
- package/client/dist/assets/{MinimizedChatsContext-DKlPf54Y.js → MinimizedChatsContext-BD2bsRBe.js} +1 -1
- package/client/dist/assets/PluginsPage-B8f49Mvn.js +2 -0
- package/client/dist/assets/{ProjectSettingsDialog-DOkjT7xv.js → ProjectSettingsDialog-D7q-O6h3.js} +1 -1
- package/client/dist/assets/{RepositoryDeliveries-CJVpOUpZ.js → RepositoryDeliveries-NNorcGzT.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-BeM-2gIZ.js → RepositoryScopeSelector-D-jb4bI_.js} +1 -1
- package/client/dist/assets/{ReviewPacketPage-EBCim2w_.js → ReviewPacketPage-DpZSzF-Q.js} +1 -1
- package/client/dist/assets/{TemplatePreviewModal-1-92UGwN.js → TemplatePreviewModal-5CZdAZ0G.js} +1 -1
- package/client/dist/assets/{TicketDetailModalContext-CkVXuHxU.js → TicketDetailModalContext-BHmtj1As.js} +1 -1
- package/client/dist/assets/{Trans-CzhPjnjB.js → Trans-wmTUDVyC.js} +1 -1
- package/client/dist/assets/{agentRuntime-TXPQowKJ.js → agentRuntime-7Ydcvr6P.js} +1 -1
- package/client/dist/assets/{agentRuntime-Bpx3yWHK.js → agentRuntime-BHVw9orA.js} +1 -1
- package/client/dist/assets/{agentRuntime-15Pkk6we.js → agentRuntime-DCPDaQcV.js} +1 -1
- package/client/dist/assets/{agentRuntime-C78n13To.js → agentRuntime-DKTw4Ajg.js} +1 -1
- package/client/dist/assets/{agentRuntime-CCI7XF4c.js → agentRuntime-DVhij_i_.js} +1 -1
- package/client/dist/assets/{agentRuntime-Ddqvp2bP.js → agentRuntime-Doss-T8o.js} +1 -1
- package/client/dist/assets/{agentRuntime-CKDHe2-a.js → agentRuntime-eZSMUlkM.js} +1 -1
- package/client/dist/assets/{agentRuntime-hqZRy08E.js → agentRuntime-qSSU7fQk.js} +1 -1
- package/client/dist/assets/agents-B6ZsnARo.js +1 -0
- package/client/dist/assets/agents-BORdhe0K.js +1 -0
- package/client/dist/assets/agents-C6aq9Oqs.js +1 -0
- package/client/dist/assets/agents-Cbsc4bcX.js +1 -0
- package/client/dist/assets/agents-D0uYR6hq.js +1 -0
- package/client/dist/assets/agents-Doe1sHY5.js +1 -0
- package/client/dist/assets/agents-DyNi_I9m.js +1 -0
- package/client/dist/assets/agents-StKxiTg0.js +1 -0
- package/client/dist/assets/agentstudio-7cYCXn-L.js +1 -0
- package/client/dist/assets/{agentstudio-QdAJIDef.js → agentstudio-BHWQh8G3.js} +1 -1
- package/client/dist/assets/agentstudio-BjnWHyRs.js +1 -0
- package/client/dist/assets/{agentstudio-fLXpRGIi.js → agentstudio-BkV8vcK4.js} +1 -1
- package/client/dist/assets/{agentstudio-BWAKeGgB.js → agentstudio-BzugqB_N.js} +1 -1
- package/client/dist/assets/{agentstudio-Cja8Sjcp.js → agentstudio-CWbp7KvH.js} +1 -1
- package/client/dist/assets/agentstudio-DG6plf5o.js +1 -0
- package/client/dist/assets/agentstudio-DkZVF-Cc.js +1 -0
- package/client/dist/assets/{formatDistanceToNow-DlQaXc7c.js → formatDistanceToNow-Dax4zqPF.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-DxUFGcVq.js → getTimezoneOffsetInMilliseconds-dpIo42JZ.js} +1 -1
- package/client/dist/assets/{index-Czov72g7.js → index-g8S62tpS.js} +7 -7
- package/client/dist/assets/jira-B6OYw4CJ.js +1 -0
- package/client/dist/assets/jira-BTF6RaOe.js +1 -0
- package/client/dist/assets/jira-CEetIB2q.js +1 -0
- package/client/dist/assets/jira-CIzsZLw5.js +1 -0
- package/client/dist/assets/{jira-BOoaUXbb.js → jira-Cq6yvN4q.js} +1 -1
- package/client/dist/assets/jira-Daw9tmpI.js +1 -0
- package/client/dist/assets/jira-FXr7u2lh.js +1 -0
- package/client/dist/assets/jira-LaNQn7yl.js +1 -0
- package/client/dist/assets/{jira-api-CbJvbRYx.js → jira-api-CLrIvXFm.js} +1 -1
- package/client/dist/assets/{jobs-DZAbh1m2.js → jobs-ARwD85B9.js} +1 -1
- package/client/dist/assets/{jobs-BD47Ov11.js → jobs-BMF1w4WT.js} +1 -1
- package/client/dist/assets/{jobs-tWjcDF_6.js → jobs-CBfzGLwm.js} +1 -1
- package/client/dist/assets/{jobs-CSrRx5lD.js → jobs-CPjiluBY.js} +1 -1
- package/client/dist/assets/{jobs-epP7gt8W.js → jobs-CQQd58pY.js} +1 -1
- package/client/dist/assets/{jobs-1G3wodhl.js → jobs-De3EqaO0.js} +1 -1
- package/client/dist/assets/{jobs-BLOoVRRl.js → jobs-Du9-Ei9a.js} +1 -1
- package/client/dist/assets/{jobs-B4pu8TdF.js → jobs-qMHbnxwC.js} +1 -1
- package/client/dist/assets/{project-repositories-BkBD3rxx.js → project-repositories-BkyT8AjU.js} +1 -1
- package/client/dist/assets/{spending-CYotH-9N.js → spending-Oov7p52A.js} +1 -1
- package/client/dist/assets/{useDesktop-DpxVGfUC.js → useDesktop-DpiS8Uov.js} +1 -1
- package/client/dist/assets/{useRuntimeRuns-CfGRbhr9.js → useRuntimeRuns-DBm1mul0.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-C17TGqT8.js → useSharedWebSocket-BZxKaVNT.js} +2 -2
- package/client/dist/index.html +19 -19
- package/docs/getting-started.md +2 -2
- package/docs/guide/de/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/de/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/de/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/de/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/en/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/en/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/en/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/en/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/es/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/es/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/es/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/es/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/fr/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/fr/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/fr/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/fr/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/it/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/it/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/it/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/it/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/ja/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/ja/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/ja/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/ja/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/pt/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/pt/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/pt/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/pt/agents/4-custom-agents-catalog.md +3 -3
- package/docs/guide/zh/agents/1-meet-the-agents.md +1 -1
- package/docs/guide/zh/agents/2-profiles-and-the-balanced-default.md +1 -1
- package/docs/guide/zh/agents/3-customizing-models-per-agent.md +1 -1
- package/docs/guide/zh/agents/4-custom-agents-catalog.md +3 -3
- package/docs/internals/adding-a-provider.md +7 -6
- package/docs/internals/configuration.md +45 -0
- package/docs/internals/core-runtime-updates.md +19 -0
- package/docs/internals/loop-step-log-explorer.md +27 -0
- package/docs/internals/mission-rail-cards.md +7 -0
- package/docs/internals/profiles.md +17 -10
- package/docs/internals/review-packet.md +1 -1
- package/docs/internals/safe-pr-review-flow.md +9 -0
- package/docs/internals/source-map.md +6 -2
- package/docs/jira-integration-plan.md +7 -4
- package/docs/running-pipelines.md +2 -2
- package/package.json +1 -1
- package/server/dist/core-compat.js +7 -3
- package/server/dist/core-execution.js +5 -18
- package/server/dist/jira/jira-db.js +33 -6
- package/server/dist/jira/jira-materializer.js +11 -9
- package/server/dist/jira/jira-status-resolver.js +64 -22
- package/server/dist/jira/jira-sync-manager.js +40 -1
- package/server/dist/jira-router.js +8 -41
- package/server/dist/mcp/guide.js +4 -3
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-bridge.js +22 -70
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-controls.js +7 -4
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-settings.js +24 -1
- package/server/dist/modules/agent-runtime/runtime/agent-runtime-verification-log.js +252 -0
- package/server/dist/modules/agents/runtime/profiles-router.js +74 -36
- package/server/dist/modules/delivery/runtime/rail-relaunch.js +10 -2
- package/server/dist/modules/execution/runtime/queue-manager.js +7 -9
- package/server/dist/modules/loops/runtime/loop-agents.js +2 -5
- package/server/dist/modules/loops/runtime/loop-command-catalog.js +5 -5
- package/server/dist/modules/loops/runtime/loop-definition.js +1 -1
- package/server/dist/modules/loops/runtime/loop-executors.js +0 -2
- package/server/dist/modules/loops/runtime/loop-implement-recipe.js +5 -4
- package/server/dist/modules/loops/runtime/loop-run-manager.js +5 -3
- package/server/dist/modules/loops/runtime/loops-router.js +3 -1
- package/server/dist/path-resolver.js +41 -20
- package/server/dist/project-env.js +22 -6
- package/server/dist/providers/gemini-agent-ack.js +13 -10
- package/server/dist/setup-manager.js +28 -43
- package/server/dist/util/checkout-path.js +22 -0
- package/server/dist/workspace-manager.js +13 -71
- package/server/dist/worktree-node-modules.js +70 -34
- package/client/dist/assets/JobDetailModal-DhwKbZGe.js +0 -1
- package/client/dist/assets/JobDetailPage-MtPE-E_1.js +0 -1
- package/client/dist/assets/LoopBuilderPage-DCUHozXh.js +0 -1
- package/client/dist/assets/PluginsPage-iL0G3rQQ.js +0 -2
- package/client/dist/assets/agents-CA5eT1d0.js +0 -1
- package/client/dist/assets/agents-CAgy-5uS.js +0 -1
- package/client/dist/assets/agents-CNm6Qzlv.js +0 -1
- package/client/dist/assets/agents-CTwbx-fb.js +0 -1
- package/client/dist/assets/agents-DEGV1Vao.js +0 -1
- package/client/dist/assets/agents-HsDF1aQU.js +0 -1
- package/client/dist/assets/agents-IQjdA-z9.js +0 -1
- package/client/dist/assets/agents-RkGoteXh.js +0 -1
- package/client/dist/assets/agentstudio-B2CTtQgZ.js +0 -1
- package/client/dist/assets/agentstudio-DtfPcr99.js +0 -1
- package/client/dist/assets/agentstudio-X70BKf6u.js +0 -1
- package/client/dist/assets/agentstudio-qXKqVnWS.js +0 -1
- package/client/dist/assets/jira-BNZu8f9O.js +0 -1
- package/client/dist/assets/jira-BYVN4L2u.js +0 -1
- package/client/dist/assets/jira-CKJg3HZd.js +0 -1
- package/client/dist/assets/jira-DDDsZ4ub.js +0 -1
- package/client/dist/assets/jira-DXwYjVE4.js +0 -1
- package/client/dist/assets/jira-Dn6U2nRW.js +0 -1
- package/client/dist/assets/jira-j7hVolR5.js +0 -1
- package/server/dist/modules/loops/runtime/loop-agent-defaults.json +0 -6
|
@@ -12,6 +12,7 @@ exports.parseLoginShellOutput = parseLoginShellOutput;
|
|
|
12
12
|
exports.augmentPathFromLoginShell = augmentPathFromLoginShell;
|
|
13
13
|
exports.parseLoginShellEnv = parseLoginShellEnv;
|
|
14
14
|
exports.augmentEnvFromLoginShell = augmentEnvFromLoginShell;
|
|
15
|
+
exports.readEnvFromLoginShellSync = readEnvFromLoginShellSync;
|
|
15
16
|
exports.augmentEnvFromLoginShellSync = augmentEnvFromLoginShellSync;
|
|
16
17
|
exports.augmentAuthEnvFromLoginShell = augmentAuthEnvFromLoginShell;
|
|
17
18
|
exports.getPathDiagnostic = getPathDiagnostic;
|
|
@@ -372,6 +373,17 @@ function parseLoginShellOutput(stdout) {
|
|
|
372
373
|
return null;
|
|
373
374
|
return stdout.slice(start, end);
|
|
374
375
|
}
|
|
376
|
+
/** GUI launches may omit SHELL even when the account uses zsh/bash. */
|
|
377
|
+
function resolveLoginShell(env = process.env) {
|
|
378
|
+
if (env.SHELL)
|
|
379
|
+
return env.SHELL;
|
|
380
|
+
try {
|
|
381
|
+
return os_1.default.userInfo().shell || '/bin/sh';
|
|
382
|
+
}
|
|
383
|
+
catch {
|
|
384
|
+
return '/bin/sh';
|
|
385
|
+
}
|
|
386
|
+
}
|
|
375
387
|
/**
|
|
376
388
|
* Spawn the user's login shell once and merge any additional PATH segments
|
|
377
389
|
* it exposes (Volta/nvm/fnm/asdf shims) into `process.env.PATH`. Async,
|
|
@@ -398,7 +410,7 @@ async function augmentPathFromLoginShell(opts = {}) {
|
|
|
398
410
|
}
|
|
399
411
|
const spawnFn = opts.spawnFn ?? child_process_1.spawn;
|
|
400
412
|
const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
|
|
401
|
-
const shell =
|
|
413
|
+
const shell = resolveLoginShell();
|
|
402
414
|
const command = `printf "${PATH_BEGIN}%s${PATH_END}" "$PATH"`;
|
|
403
415
|
const status = await new Promise((resolve) => {
|
|
404
416
|
let child;
|
|
@@ -474,7 +486,6 @@ exports.AUTH_ENV_VARS = [
|
|
|
474
486
|
'GOOGLE_APPLICATION_CREDENTIALS',
|
|
475
487
|
];
|
|
476
488
|
const LOGIN_SHELL_ENV_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
477
|
-
const loginShellEnvProbeCache = new Set();
|
|
478
489
|
function normalizeLoginShellEnvNames(names) {
|
|
479
490
|
const out = [];
|
|
480
491
|
for (const raw of names) {
|
|
@@ -528,7 +539,7 @@ async function augmentEnvFromLoginShell(names, opts = {}) {
|
|
|
528
539
|
return;
|
|
529
540
|
const spawnFn = opts.spawnFn ?? child_process_1.spawn;
|
|
530
541
|
const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
|
|
531
|
-
const shell =
|
|
542
|
+
const shell = resolveLoginShell();
|
|
532
543
|
const command = buildLoginShellEnvCommand(wanted);
|
|
533
544
|
await new Promise((resolve) => {
|
|
534
545
|
let child;
|
|
@@ -569,42 +580,53 @@ async function augmentEnvFromLoginShell(names, opts = {}) {
|
|
|
569
580
|
});
|
|
570
581
|
});
|
|
571
582
|
}
|
|
572
|
-
/**
|
|
573
|
-
*
|
|
574
|
-
|
|
575
|
-
function augmentEnvFromLoginShellSync(names, opts = {}) {
|
|
583
|
+
/** Read missing configured names without mutating the caller's or global env.
|
|
584
|
+
* Callers own any cache so project credentials cannot leak across scopes. */
|
|
585
|
+
function readEnvFromLoginShellSync(names, opts = {}) {
|
|
576
586
|
if (process.platform === 'win32')
|
|
577
|
-
return;
|
|
587
|
+
return {};
|
|
578
588
|
if (process.env.NODE_ENV === 'test' || process.env.VITEST === 'true')
|
|
579
|
-
return;
|
|
580
|
-
const
|
|
589
|
+
return {};
|
|
590
|
+
const env = opts.env ?? process.env;
|
|
591
|
+
const wanted = normalizeLoginShellEnvNames(names).filter((name) => !env[name]);
|
|
581
592
|
if (wanted.length === 0)
|
|
582
|
-
return;
|
|
583
|
-
for (const name of wanted)
|
|
584
|
-
loginShellEnvProbeCache.add(name);
|
|
593
|
+
return {};
|
|
585
594
|
const spawnSyncFn = opts.spawnSyncFn ?? child_process_1.spawnSync;
|
|
586
595
|
const timeoutMs = opts.timeoutMs ?? LOGIN_SHELL_TIMEOUT_MS;
|
|
587
|
-
const shell =
|
|
596
|
+
const shell = resolveLoginShell(env);
|
|
588
597
|
const command = buildLoginShellEnvCommand(wanted);
|
|
589
598
|
let res;
|
|
590
599
|
try {
|
|
591
600
|
res = spawnSyncFn(shell, ['-l', '-i', '-c', command], {
|
|
601
|
+
env,
|
|
592
602
|
encoding: 'utf8',
|
|
593
603
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
594
604
|
timeout: timeoutMs,
|
|
605
|
+
maxBuffer: 1024 * 1024,
|
|
595
606
|
});
|
|
596
607
|
}
|
|
597
608
|
catch {
|
|
598
|
-
return;
|
|
609
|
+
return {};
|
|
599
610
|
}
|
|
600
|
-
if (res.error)
|
|
601
|
-
return;
|
|
611
|
+
if (res.error || res.status !== 0)
|
|
612
|
+
return {};
|
|
602
613
|
const stdout = typeof res.stdout === 'string' ? res.stdout : res.stdout?.toString('utf8') ?? '';
|
|
603
614
|
const recovered = parseLoginShellEnv(stdout);
|
|
615
|
+
const out = {};
|
|
604
616
|
for (const key of wanted) {
|
|
605
617
|
const val = recovered[key];
|
|
606
|
-
if (val
|
|
607
|
-
|
|
618
|
+
if (val)
|
|
619
|
+
out[key] = val;
|
|
620
|
+
}
|
|
621
|
+
return out;
|
|
622
|
+
}
|
|
623
|
+
/** Compatibility backfill for callers that intentionally augment process.env.
|
|
624
|
+
* Failed probes remain retryable; successful names are already present. */
|
|
625
|
+
function augmentEnvFromLoginShellSync(names, opts = {}) {
|
|
626
|
+
const recovered = readEnvFromLoginShellSync(names, opts);
|
|
627
|
+
for (const [key, value] of Object.entries(recovered)) {
|
|
628
|
+
if (value && !process.env[key])
|
|
629
|
+
process.env[key] = value;
|
|
608
630
|
}
|
|
609
631
|
}
|
|
610
632
|
/** Startup backfill for the built-in provider auth variables. */
|
|
@@ -647,5 +669,4 @@ function __resetPathResolverForTest() {
|
|
|
647
669
|
diagnostic = { pathSegments: [], pathSources: [], loginShellStatus: 'skipped' };
|
|
648
670
|
warnedLoginShell = false;
|
|
649
671
|
bundledRuntimesActive = false;
|
|
650
|
-
loginShellEnvProbeCache.clear();
|
|
651
672
|
}
|
|
@@ -4,21 +4,37 @@ exports.resolveWorktreeEnvPassthrough = resolveWorktreeEnvPassthrough;
|
|
|
4
4
|
exports.applyWorktreeEnvPassthrough = applyWorktreeEnvPassthrough;
|
|
5
5
|
const db_1 = require("./db");
|
|
6
6
|
const path_resolver_1 = require("./path-resolver");
|
|
7
|
+
// Values stay in the owning project's memory, never process.env or SQLite.
|
|
8
|
+
// Both successful and missing lookups expire so profiles can recover/rotate.
|
|
9
|
+
const LOGIN_SHELL_CACHE_MS = 30_000;
|
|
10
|
+
const shellValues = new WeakMap();
|
|
11
|
+
function recoverProjectEnv(db, names) {
|
|
12
|
+
const missing = names.filter(name => !process.env[name]);
|
|
13
|
+
if (!missing.length) {
|
|
14
|
+
shellValues.delete(db);
|
|
15
|
+
return {};
|
|
16
|
+
}
|
|
17
|
+
const identity = JSON.stringify([names, missing, process.env.SHELL, process.env.HOME, process.env.ZDOTDIR]);
|
|
18
|
+
const cached = shellValues.get(db);
|
|
19
|
+
if (cached?.identity === identity && Date.now() < cached.expiresAt)
|
|
20
|
+
return cached.env;
|
|
21
|
+
const env = (0, path_resolver_1.readEnvFromLoginShellSync)(missing);
|
|
22
|
+
shellValues.set(db, { identity, expiresAt: Date.now() + LOGIN_SHELL_CACHE_MS, env });
|
|
23
|
+
return env;
|
|
24
|
+
}
|
|
7
25
|
/** Resolve the per-project env passthrough overlay for a spawn.
|
|
8
26
|
*
|
|
9
27
|
* The project setting stores names only. Values are read from `sourceEnv` at
|
|
10
28
|
* spawn time so credentials can rotate without touching SQLite. When the
|
|
11
|
-
* source is `process.env`, missing configured names get
|
|
12
|
-
* recovery
|
|
13
|
-
* blocking the run.
|
|
29
|
+
* source is `process.env`, missing configured names get bounded login-shell
|
|
30
|
+
* recovery with a short, project-owned cache. Unresolved names remain absent.
|
|
14
31
|
*/
|
|
15
32
|
function resolveWorktreeEnvPassthrough(db, sourceEnv = process.env) {
|
|
16
33
|
const out = {};
|
|
17
34
|
const names = (0, db_1.getProjectSettings)(db).worktreeEnvPassthrough;
|
|
18
|
-
|
|
19
|
-
(0, path_resolver_1.augmentEnvFromLoginShellSync)(names);
|
|
35
|
+
const recovered = sourceEnv === process.env ? recoverProjectEnv(db, names) : {};
|
|
20
36
|
for (const name of names) {
|
|
21
|
-
const value = sourceEnv[name];
|
|
37
|
+
const value = sourceEnv[name] || recovered[name] || sourceEnv[name];
|
|
22
38
|
if (value !== undefined)
|
|
23
39
|
out[name] = value;
|
|
24
40
|
}
|
|
@@ -1,18 +1,20 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
// Gemini headless subagent pre-acknowledgment.
|
|
2
|
+
// Gemini headless subagent pre-acknowledgment (custom agents only).
|
|
3
3
|
//
|
|
4
4
|
// gemini 0.46+ DISCOVERS `<project>/.gemini/agents/*.md` but only ENABLES a
|
|
5
5
|
// project's custom subagents after an interactive "New Agents Discovered →
|
|
6
6
|
// Acknowledge and Enable" prompt. That prompt never fires in headless
|
|
7
7
|
// (`gemini -p`) spawns — which is how the desktop runs every rail — so
|
|
8
|
-
// `invoke_agent
|
|
9
|
-
//
|
|
10
|
-
// architect/developer/reviewer
|
|
8
|
+
// `invoke_agent custom-<name>` returns "Subagent not found" and the role that
|
|
9
|
+
// delegates to it silently falls back to inline work. The baseline roles
|
|
10
|
+
// (architect/developer/reviewer) are defined by the specrails-core runtime and
|
|
11
|
+
// are not `.gemini/agents` files, so only the project's `custom-*` agents need
|
|
12
|
+
// this acknowledgment.
|
|
11
13
|
//
|
|
12
14
|
// specrails-core writes the acknowledgment file at install time; this is the
|
|
13
15
|
// defence-in-depth copy the desktop runs right before a gemini rail spawn, so a
|
|
14
|
-
// project
|
|
15
|
-
//
|
|
16
|
+
// project whose custom agents changed since install is still trusted headless.
|
|
17
|
+
// The file gemini reads is
|
|
16
18
|
// `~/.gemini/acknowledgments/agents.json`, shaped
|
|
17
19
|
// { [projectRoot]: { [agentName]: <sha256-hex of the agent .md file> } }
|
|
18
20
|
// where the hash is sha256 of the FULL agent markdown file (verified empirically
|
|
@@ -29,10 +31,11 @@ function ackFilePath() {
|
|
|
29
31
|
return (0, path_1.join)((0, os_1.homedir)(), '.gemini', 'acknowledgments', 'agents.json');
|
|
30
32
|
}
|
|
31
33
|
/**
|
|
32
|
-
* Pre-acknowledge every `<projectPath>/.gemini/agents/*.md`
|
|
33
|
-
* in headless mode. No-op when the project
|
|
34
|
-
* agent files. The `projectPath` is the key
|
|
35
|
-
* root), matching what `specrails-core`
|
|
34
|
+
* Pre-acknowledge every `<projectPath>/.gemini/agents/*.md` (the project's
|
|
35
|
+
* custom agents) so gemini loads them in headless mode. No-op when the project
|
|
36
|
+
* has no `.gemini/agents` dir or no agent files. The `projectPath` is the key
|
|
37
|
+
* gemini uses (the spawn cwd / repo root), matching what `specrails-core`
|
|
38
|
+
* writes at install.
|
|
36
39
|
*/
|
|
37
40
|
function acknowledgeGeminiProjectAgents(projectPath) {
|
|
38
41
|
const agentsDir = (0, path_1.join)(projectPath, '.gemini', 'agents');
|
|
@@ -186,7 +186,6 @@ exports.CHECKPOINTS = [
|
|
|
186
186
|
{ key: 'agent_selection', name: 'Agent selection' },
|
|
187
187
|
{ key: 'codebase_analysis', name: 'Codebase analysis' },
|
|
188
188
|
{ key: 'vpc_discovery', name: 'VPC discovery' },
|
|
189
|
-
{ key: 'agent_generation', name: 'Agent generation' },
|
|
190
189
|
{ key: 'persona_synthesis', name: 'Persona synthesis' },
|
|
191
190
|
{ key: 'command_generation', name: 'Command generation' },
|
|
192
191
|
];
|
|
@@ -204,10 +203,7 @@ function checkFilesystem(projectPath, provider = 'claude') {
|
|
|
204
203
|
(0, fs_1.existsSync)((0, path_1.join)(projectPath, '.specrails-version'));
|
|
205
204
|
const hasSetupTemplates = (0, fs_1.existsSync)((0, path_1.join)(projectPath, '.specrails', 'setup-templates')) ||
|
|
206
205
|
(0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'setup-templates'));
|
|
207
|
-
const hasRules = (0, fs_1.existsSync)((0, path_1.join)(projectPath, dir, 'rules')) &&
|
|
208
|
-
hasFiles((0, path_1.join)(projectPath, dir, 'rules'), /\.md$/);
|
|
209
206
|
const hasPersonas = artifactState.hasPersonas;
|
|
210
|
-
const hasAgents = artifactState.hasAgents;
|
|
211
207
|
const hasCommands = artifactState.hasCommands;
|
|
212
208
|
const instructionCandidates = [(0, path_1.join)(projectPath, adapter.instructionsFilename)];
|
|
213
209
|
// Older Core releases sometimes placed a top-level instructions filename
|
|
@@ -227,22 +223,15 @@ function checkFilesystem(projectPath, provider = 'claude') {
|
|
|
227
223
|
base_install: hasBaseInstall,
|
|
228
224
|
// Back-compat filesystem signals from older cores are translated to the
|
|
229
225
|
// current integration-contract checkpoint keys.
|
|
230
|
-
agent_selection: hasInstallConfig || hasAgentConfig || hasBacklogConfig ||
|
|
226
|
+
agent_selection: hasInstallConfig || hasAgentConfig || hasBacklogConfig || hasCommands,
|
|
231
227
|
codebase_analysis: hasBaseInstall && (hasInstructions || hasSetupTemplates),
|
|
232
228
|
vpc_discovery: hasPersonas,
|
|
233
229
|
persona_synthesis: hasPersonas,
|
|
234
|
-
|
|
235
|
-
|
|
230
|
+
// Role agents are runtime-defined by specrails-core; only the workflow
|
|
231
|
+
// commands (and provider skills) prove a complete installation.
|
|
232
|
+
command_generation: hasCommands,
|
|
236
233
|
};
|
|
237
234
|
}
|
|
238
|
-
function hasFiles(dir, pattern) {
|
|
239
|
-
try {
|
|
240
|
-
return (0, fs_1.readdirSync)(dir).some((f) => pattern.test(f));
|
|
241
|
-
}
|
|
242
|
-
catch {
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
235
|
// ─── Enrich.md content resolver (shared by start + resume enrich paths) ─────
|
|
247
236
|
/**
|
|
248
237
|
* Reads the enrich command's body from the project's specrails dir, falling
|
|
@@ -286,9 +275,6 @@ function detectCheckpointFromText(text) {
|
|
|
286
275
|
if (/phase\s*3|configuration|agent\s*selection|backlog\s*provider/i.test(text)) {
|
|
287
276
|
hits.push({ key: 'agent_selection', detail: 'Configuring agents...' });
|
|
288
277
|
}
|
|
289
|
-
if (/generating\s*all\s*files|writing.*agent|sr-architect|sr-developer|sr-reviewer/i.test(text)) {
|
|
290
|
-
hits.push({ key: 'agent_generation', detail: 'Generating agents...' });
|
|
291
|
-
}
|
|
292
278
|
if (/command\s*selection|installing.*commands|\.claude\/commands\/(sr|specrails)/i.test(text)) {
|
|
293
279
|
hits.push({ key: 'command_generation', detail: 'Configuring commands...' });
|
|
294
280
|
}
|
|
@@ -301,7 +287,7 @@ function detectCheckpointFromText(text) {
|
|
|
301
287
|
hits.push({ key: 'config_written' });
|
|
302
288
|
}
|
|
303
289
|
if (/installing\s*specrails|phase\s*2\s*&\s*3|placing\s*agents/i.test(text)) {
|
|
304
|
-
hits.push({ key: '
|
|
290
|
+
hits.push({ key: 'command_generation', detail: 'Installing specrails artefacts...' });
|
|
305
291
|
}
|
|
306
292
|
if (/writing\s*manifest|wrote\s+.*specrails-manifest/i.test(text)) {
|
|
307
293
|
hits.push({ key: 'command_generation' });
|
|
@@ -315,16 +301,8 @@ function detectCheckpointFromText(text) {
|
|
|
315
301
|
if (text.includes('/agents/personas/') && text.includes('.md')) {
|
|
316
302
|
hits.push({ key: 'persona_synthesis', detail: 'Writing personas...' });
|
|
317
303
|
}
|
|
318
|
-
//
|
|
319
|
-
|
|
320
|
-
hits.push({ key: 'agent_generation', detail: 'Writing agents...' });
|
|
321
|
-
}
|
|
322
|
-
// Provider-native rail skills. Kimi's upstream discovery contract scans only
|
|
323
|
-
// direct children of `.kimi-code/skills`, so its roles are top-level
|
|
324
|
-
// `.kimi-code/skills/sr-*/SKILL.md` entries. Codex may also use `rails/`.
|
|
325
|
-
if (/\.(?:codex|kimi-code)\/skills\/(?:rails\/)?sr-[^/]+\/SKILL\.md/.test(text)) {
|
|
326
|
-
hits.push({ key: 'agent_generation', detail: 'Writing agent skills...' });
|
|
327
|
-
}
|
|
304
|
+
// Role agents (`sr-*`) are no longer installed files: Core defines them in
|
|
305
|
+
// its runtime, so role paths carry no checkpoint signal.
|
|
328
306
|
if ((text.includes('/commands/sr/') || text.includes('/commands/specrails/')) && text.includes('.md')) {
|
|
329
307
|
hits.push({ key: 'command_generation', detail: 'Writing commands...' });
|
|
330
308
|
}
|
|
@@ -423,10 +401,11 @@ function computeSummary(projectPath, tier, provider = 'claude') {
|
|
|
423
401
|
if (provider === 'codex' || provider === 'kimi') {
|
|
424
402
|
// Codex/Kimi layout: every artefact ships as a SKILL under the provider's
|
|
425
403
|
// `skills/` root.
|
|
426
|
-
// - agents =
|
|
427
|
-
//
|
|
428
|
-
//
|
|
429
|
-
//
|
|
404
|
+
// - agents = custom roles only: codex `skills/rails/custom-*/SKILL.md`,
|
|
405
|
+
// kimi direct children `skills/custom-*/SKILL.md` (upstream
|
|
406
|
+
// Kimi discovery is intentionally one level deep). Baseline
|
|
407
|
+
// `sr-*` roles are runtime-defined and never counted; stale
|
|
408
|
+
// `sr-*` skills from older Cores are ignored.
|
|
430
409
|
// - opsxCommands = `skills/openspec-*/SKILL.md`
|
|
431
410
|
// - specrailsCommands = everything else under `skills/` (ported claude
|
|
432
411
|
// slash commands like propose-spec, explore-spec, retry, doctor,
|
|
@@ -444,7 +423,7 @@ function computeSummary(projectPath, tier, provider = 'claude') {
|
|
|
444
423
|
const personasDir = (0, path_1.join)(railsDir, entry);
|
|
445
424
|
personas = (0, fs_1.readdirSync)(personasDir).filter((persona) => (0, fs_1.existsSync)((0, path_1.join)(personasDir, persona, 'SKILL.md'))).length;
|
|
446
425
|
}
|
|
447
|
-
else if ((0, fs_1.existsSync)((0, path_1.join)(railsDir, entry, 'SKILL.md'))) {
|
|
426
|
+
else if (/^custom-/.test(entry) && (0, fs_1.existsSync)((0, path_1.join)(railsDir, entry, 'SKILL.md'))) {
|
|
448
427
|
agents++;
|
|
449
428
|
}
|
|
450
429
|
}
|
|
@@ -456,6 +435,8 @@ function computeSummary(projectPath, tier, provider = 'claude') {
|
|
|
456
435
|
if (!(0, fs_1.existsSync)((0, path_1.join)(skillsDir, entry, 'SKILL.md')))
|
|
457
436
|
continue;
|
|
458
437
|
if (/^sr-/.test(entry))
|
|
438
|
+
continue;
|
|
439
|
+
else if (/^custom-/.test(entry))
|
|
459
440
|
agents++;
|
|
460
441
|
else if (/^openspec-/.test(entry))
|
|
461
442
|
opsxCommands++;
|
|
@@ -473,7 +454,8 @@ function computeSummary(projectPath, tier, provider = 'claude') {
|
|
|
473
454
|
}
|
|
474
455
|
}
|
|
475
456
|
else {
|
|
476
|
-
// Claude / Gemini layout:
|
|
457
|
+
// Claude / Gemini layout: custom roles at `<dir>/agents/custom-*.md`
|
|
458
|
+
// (baseline roles are runtime-defined, stale `sr-*` files are ignored), slash commands
|
|
477
459
|
// at `<dir>/commands/{specrails,opsx}/*.<ext>`. Gemini installs into
|
|
478
460
|
// `.gemini/` and its commands are TOML (`.gemini/commands/specrails/*.toml`);
|
|
479
461
|
// claude installs into `.claude/` with Markdown commands. Without the
|
|
@@ -484,7 +466,7 @@ function computeSummary(projectPath, tier, provider = 'claude') {
|
|
|
484
466
|
const agentsDir = (0, path_1.join)(projectPath, dir, 'agents');
|
|
485
467
|
if ((0, fs_1.existsSync)(agentsDir)) {
|
|
486
468
|
const files = (0, fs_1.readdirSync)(agentsDir);
|
|
487
|
-
agents = files.filter((f) => /^
|
|
469
|
+
agents = files.filter((f) => /^custom-.*\.md$/.test(f)).length;
|
|
488
470
|
const personasDir = (0, path_1.join)(agentsDir, 'personas');
|
|
489
471
|
if ((0, fs_1.existsSync)(personasDir)) {
|
|
490
472
|
personas = (0, fs_1.readdirSync)(personasDir).filter((f) => f.endsWith('.md')).length;
|
|
@@ -515,6 +497,8 @@ function computeSummary(projectPath, tier, provider = 'claude') {
|
|
|
515
497
|
}
|
|
516
498
|
/**
|
|
517
499
|
* Resolve completion against the selected provider's real filesystem layout.
|
|
500
|
+
* Completion is decided by the workflow commands Core places; role agents are
|
|
501
|
+
* runtime-defined and never required.
|
|
518
502
|
* A legacy `.claude` fallback remains intentional only for Codex/Gemini:
|
|
519
503
|
* older Core releases scaffolded Claude artefacts for those selections.
|
|
520
504
|
* Kimi never had such a released legacy Core target; accepting a mixed
|
|
@@ -529,14 +513,13 @@ function setupArtifactState(projectPath, tier, provider) {
|
|
|
529
513
|
: nativeSummary;
|
|
530
514
|
const nativeHasCommands = nativeSummary.specrailsCommands + nativeSummary.opsxCommands > 0;
|
|
531
515
|
const legacyHasCommands = legacySummary.specrailsCommands + legacySummary.opsxCommands > 0;
|
|
532
|
-
const nativeComplete =
|
|
533
|
-
const legacyComplete =
|
|
516
|
+
const nativeComplete = nativeHasCommands;
|
|
517
|
+
const legacyComplete = legacyHasCommands;
|
|
534
518
|
const selected = nativeComplete || !legacyComplete ? nativeSummary : legacySummary;
|
|
535
519
|
return {
|
|
536
520
|
// Preserve the actual selected provider for UI labels even when the
|
|
537
521
|
// artefacts came from the legacy Claude compatibility tree.
|
|
538
522
|
summary: { ...selected, provider },
|
|
539
|
-
hasAgents: nativeSummary.agents > 0 || legacySummary.agents > 0,
|
|
540
523
|
hasCommands: nativeHasCommands || legacyHasCommands,
|
|
541
524
|
hasPersonas: nativeSummary.personas > 0 || legacySummary.personas > 0,
|
|
542
525
|
complete: nativeComplete || legacyComplete,
|
|
@@ -1026,7 +1009,9 @@ class SetupManager {
|
|
|
1026
1009
|
const validation = validateInstalledCore(root);
|
|
1027
1010
|
const selectedProvider = provider ?? this._projectProviders.get(projectId) ?? 'claude';
|
|
1028
1011
|
const summary = computeSummary(root, 'quick', selectedProvider);
|
|
1029
|
-
|
|
1012
|
+
// Role agents are runtime-defined: a Core install is complete when its
|
|
1013
|
+
// workflow commands are in place, regardless of custom role count.
|
|
1014
|
+
if (!validation.ok || !installedVersion || summary.specrailsCommands < 1) {
|
|
1030
1015
|
this._broadcast({ type: 'setup_error', projectId, error: 'Core 5 uses deterministic installation. Run project installation to repair its framework; enrichment is no longer supported.' });
|
|
1031
1016
|
return true;
|
|
1032
1017
|
}
|
|
@@ -1315,9 +1300,9 @@ class SetupManager {
|
|
|
1315
1300
|
case 'tool-use': {
|
|
1316
1301
|
this._broadcast({ type: 'setup_log', projectId, line: `[tool] ${ev.name}`, stream: 'stdout' });
|
|
1317
1302
|
// Tool inputs commonly mention the paths being written — feed the
|
|
1318
|
-
// input preview into the checkpoint detector so writes to
|
|
1319
|
-
//
|
|
1320
|
-
// the checkpoint state immediately.
|
|
1303
|
+
// input preview into the checkpoint detector so writes to workflow
|
|
1304
|
+
// commands (`commands/specrails/*.md`) or provider command skills
|
|
1305
|
+
// advance the checkpoint state immediately.
|
|
1321
1306
|
const hits = detectCheckpointFromText(ev.inputPreview);
|
|
1322
1307
|
for (const hit of hits) {
|
|
1323
1308
|
this._advanceCheckpoint(projectId, hit.key, hit.detail);
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.checkoutSubdirectory = checkoutSubdirectory;
|
|
4
|
+
const node_fs_1 = require("node:fs");
|
|
5
|
+
const node_path_1 = require("node:path");
|
|
6
|
+
/** Where a directory sits inside its Git checkout. Handles worktree .git files
|
|
7
|
+
* as well as directories; root and non-Git directories both return ''. */
|
|
8
|
+
function checkoutSubdirectory(directory) {
|
|
9
|
+
let start;
|
|
10
|
+
try {
|
|
11
|
+
start = (0, node_fs_1.realpathSync)(directory);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return '';
|
|
15
|
+
}
|
|
16
|
+
for (let current = start;; current = (0, node_path_1.dirname)(current)) {
|
|
17
|
+
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(current, '.git')))
|
|
18
|
+
return (0, node_path_1.relative)(current, start).split(node_path_1.sep).join('/');
|
|
19
|
+
if ((0, node_path_1.dirname)(current) === current)
|
|
20
|
+
return '';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -3,7 +3,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.ensureFrameworkAgents = ensureFrameworkAgents;
|
|
7
6
|
exports.ensureFrameworkCommandSubtrees = ensureFrameworkCommandSubtrees;
|
|
8
7
|
exports.workspacePathFor = workspacePathFor;
|
|
9
8
|
exports.ensureWorkspace = ensureWorkspace;
|
|
@@ -16,63 +15,6 @@ const framework_manager_1 = require("./framework-manager");
|
|
|
16
15
|
const framework_migration_1 = require("./framework-migration");
|
|
17
16
|
const artifact_registry_1 = require("./artifact-registry");
|
|
18
17
|
const agent_mcp_config_1 = require("./agent-mcp-config");
|
|
19
|
-
/**
|
|
20
|
-
* Windows repair: ensure the workspace's `<providerDir>/agents` holds the
|
|
21
|
-
* framework's `sr-*` agent definitions, copied from the REAL versioned framework
|
|
22
|
-
* dir (`~/.specrails/framework/<version>/<providerDir>/agents`).
|
|
23
|
-
*
|
|
24
|
-
* Why: on Windows the `framework/current` JUNCTION can be untraversable by Node's
|
|
25
|
-
* `fs` ("UNKNOWN: scandir" / "untrusted mount point"); the bundled core's
|
|
26
|
-
* `assemble` sources the agents THROUGH `current`, so `linkAgentFiles` reads
|
|
27
|
-
* nothing and the workspace ends up with NO `sr-*` agents — the implement
|
|
28
|
-
* pipeline then has no sub-agents to delegate to (architect/developer/reviewer)
|
|
29
|
-
* and silently runs everything inline. The versioned dir is a real, traversable
|
|
30
|
-
* directory, so we read it directly. (The proper fix lives in core's `assemble`;
|
|
31
|
-
* this repairs already-broken installs without waiting for a core republish, and
|
|
32
|
-
* mirrors exactly what the fixed core does on Windows — copy from the version
|
|
33
|
-
* dir.) `assemble` never re-runs on a rail spawn, so a workspace broken at setup
|
|
34
|
-
* stays broken; this runs per rail spawn to self-heal it.
|
|
35
|
-
*
|
|
36
|
-
* NO-OP on POSIX (per-file symlinks already populate the workspace correctly;
|
|
37
|
-
* byte-identical). Additive (never touches user `custom-*.md`) + idempotent.
|
|
38
|
-
* Returns the number of agents copied.
|
|
39
|
-
*/
|
|
40
|
-
function ensureFrameworkAgents(workspaceDir, providerDir, home) {
|
|
41
|
-
(0, core_update_state_1.assertWorkspaceCoreReady)(workspaceDir, home);
|
|
42
|
-
if (process.platform !== 'win32')
|
|
43
|
-
return 0;
|
|
44
|
-
const root = (0, framework_manager_1.frameworkRoot)(home);
|
|
45
|
-
const version = (0, framework_manager_1.readCurrentFrameworkVersion)(home);
|
|
46
|
-
if (!version)
|
|
47
|
-
return 0;
|
|
48
|
-
const src = path_1.default.join(root, version, providerDir, 'agents');
|
|
49
|
-
let entries;
|
|
50
|
-
try {
|
|
51
|
-
entries = fs_1.default.readdirSync(src);
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return 0; // framework agents not materialized for this provider
|
|
55
|
-
}
|
|
56
|
-
const dest = path_1.default.join(workspaceDir, providerDir, 'agents');
|
|
57
|
-
let copied = 0;
|
|
58
|
-
for (const name of entries) {
|
|
59
|
-
// Framework agents only; never clobber a user/plugin `custom-*.md`.
|
|
60
|
-
if (!name.endsWith('.md') || name.startsWith('custom-'))
|
|
61
|
-
continue;
|
|
62
|
-
const destFile = path_1.default.join(dest, name);
|
|
63
|
-
if (fs_1.default.existsSync(destFile))
|
|
64
|
-
continue;
|
|
65
|
-
try {
|
|
66
|
-
fs_1.default.mkdirSync(dest, { recursive: true });
|
|
67
|
-
fs_1.default.copyFileSync(path_1.default.join(src, name), destFile);
|
|
68
|
-
copied += 1;
|
|
69
|
-
}
|
|
70
|
-
catch {
|
|
71
|
-
/* best-effort per file — one failure must never abort the rail spawn */
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
return copied;
|
|
75
|
-
}
|
|
76
18
|
/** Whole-directory framework links for the legacy provider layouts. */
|
|
77
19
|
const DIR_LINKED_SUBTREES = ['commands', 'skills', 'rules'];
|
|
78
20
|
/**
|
|
@@ -80,8 +22,9 @@ const DIR_LINKED_SUBTREES = ['commands', 'skills', 'rules'];
|
|
|
80
22
|
*
|
|
81
23
|
* - `rules/` and `specrails/` are whole-directory framework links.
|
|
82
24
|
* - `skills/` is a real merged directory. Core links only its framework-owned
|
|
83
|
-
* `specrails-*` workflow
|
|
84
|
-
* other user-owned direct-child skills can
|
|
25
|
+
* `specrails-*` workflow children (older Cores also linked `sr-*` role
|
|
26
|
+
* skills) so OpenSpec, custom, and other user-owned direct-child skills can
|
|
27
|
+
* coexist.
|
|
85
28
|
*
|
|
86
29
|
* The Windows repair must mirror that ownership boundary exactly. Replacing the
|
|
87
30
|
* Kimi `skills/` root would destroy user state; merely seeing entries in that
|
|
@@ -172,21 +115,20 @@ function repairMissingCommands(src, dest) {
|
|
|
172
115
|
}
|
|
173
116
|
}
|
|
174
117
|
/**
|
|
175
|
-
* Windows repair for provider-owned framework links
|
|
176
|
-
* `ensureFrameworkAgents`.
|
|
118
|
+
* Windows repair for provider-owned framework links.
|
|
177
119
|
*
|
|
178
|
-
* Why
|
|
179
|
-
*
|
|
180
|
-
*
|
|
181
|
-
*
|
|
182
|
-
*
|
|
183
|
-
* unreadable and the workspace has NO `/specrails:*` commands at all — the claude
|
|
184
|
-
* CLI then reports `Unknown command: /specrails:implement`. This replaces an
|
|
120
|
+
* Why: `commands`/`skills`/`rules` are a single dir-symlink INTO
|
|
121
|
+
* `current/<provider>/<subtree>`; when the `current` junction is untraversable
|
|
122
|
+
* by the sidecar (the documented Windows failure), that link is unreadable and
|
|
123
|
+
* the workspace has NO `/specrails:*` commands at all — the claude CLI then
|
|
124
|
+
* reports `Unknown command: /specrails:implement`. This replaces an
|
|
185
125
|
* unreadable/missing link with a REAL recursively-copied directory read straight
|
|
186
|
-
* from the versioned framework dir (never through `current`).
|
|
126
|
+
* from the versioned framework dir (never through `current`). Role agents need
|
|
127
|
+
* no repair: specrails-core defines them in its runtime, not as workspace files.
|
|
128
|
+
* `assemble` never re-runs on a rail spawn, so this runs per spawn to self-heal.
|
|
187
129
|
*
|
|
188
130
|
* Kimi needs an additional per-child repair because its `skills/` root is a real
|
|
189
|
-
* merged directory. Only `specrails-*` and `sr-*` children are framework-owned;
|
|
131
|
+
* merged directory. Only `specrails-*` (and legacy `sr-*`) children are framework-owned;
|
|
190
132
|
* `openspec-*`, `custom-*`, and unknown children are never copied, replaced, or
|
|
191
133
|
* removed.
|
|
192
134
|
*
|