specrails-desktop 2.45.0 → 2.47.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.
Files changed (142) hide show
  1. package/client/dist/assets/{ActivityFeedPage-D3SQklu5.js → ActivityFeedPage-DSCpSOGV.js} +1 -1
  2. package/client/dist/assets/{AgentBrowserCapture-DNGxE33d.js → AgentBrowserCapture-mc1c5pgS.js} +1 -1
  3. package/client/dist/assets/AgentModeAnalyticsPane--UkamBTm.js +2 -0
  4. package/client/dist/assets/AgentModeCodePane-DJCswsvU.js +2 -0
  5. package/client/dist/assets/AgentModeJobsPane-Dw3DNFkh.js +1 -0
  6. package/client/dist/assets/AgentsPage-ChElPUL9.js +87 -0
  7. package/client/dist/assets/{AnalyticsPage-BqV6mM2O.js → AnalyticsPage-BGtkFHBp.js} +1 -1
  8. package/client/dist/assets/CodePage-BBnw3VGn.js +3 -0
  9. package/client/dist/assets/{DesktopAnalyticsPage-D6WNax3K.js → DesktopAnalyticsPage-B85tNScc.js} +1 -1
  10. package/client/dist/assets/{DocsDialog-68jKUH9f.js → DocsDialog-D4cXrOMl.js} +1 -1
  11. package/client/dist/assets/{DocsPage-BoGJgRpm.js → DocsPage-BCAbC6qy.js} +1 -1
  12. package/client/dist/assets/{ExportDropdown-B2C_XLGp.js → ExportDropdown-BwuvqS2Y.js} +1 -1
  13. package/client/dist/assets/InteractiveJobComposer-Coq-Zhk0.js +19 -0
  14. package/client/dist/assets/JobDetailModal--Mjam0Nc.js +1 -0
  15. package/client/dist/assets/JobDetailPage-B6ShWSBh.js +1 -0
  16. package/client/dist/assets/JobsPage-DHU_ep_B.js +1 -0
  17. package/client/dist/assets/{LoopBuilderPage-D21GxFmL.js → LoopBuilderPage-Byvq8Oqx.js} +2 -2
  18. package/client/dist/assets/{LoopPreviewModal-Bo3fYFxN.js → LoopPreviewModal-BMC9Mgj1.js} +1 -1
  19. package/client/dist/assets/{LoopsPage-BcrBw1cg.js → LoopsPage-CFGl-Nw8.js} +1 -1
  20. package/client/dist/assets/{MinimizedChatsContext-Dsv2eHf_.js → MinimizedChatsContext-DHjHBYqa.js} +1 -1
  21. package/client/dist/assets/{PluginsPage-BbyTmSLx.js → PluginsPage-BHygRnW6.js} +2 -2
  22. package/client/dist/assets/{ProjectSettingsDialog-B3lEXLv7.js → ProjectSettingsDialog-nspqxH-M.js} +1 -1
  23. package/client/dist/assets/{RepositoryDeliveries-ok_Wo65C.js → RepositoryDeliveries-Dq9NJOWi.js} +1 -1
  24. package/client/dist/assets/{RepositoryScopeSelector-DU6QwNaS.js → RepositoryScopeSelector-C3jKG2z0.js} +1 -1
  25. package/client/dist/assets/{ReviewPacketPage-BBCeHtgd.js → ReviewPacketPage-C9mqhi65.js} +1 -1
  26. package/client/dist/assets/{TemplatePreviewModal-CfNmxS6O.js → TemplatePreviewModal-BmwoAjOr.js} +1 -1
  27. package/client/dist/assets/{TicketDetailModalContext-DW3eQ0I7.js → TicketDetailModalContext-lYmoONrd.js} +1 -1
  28. package/client/dist/assets/{Trans-BHPMWkOw.js → Trans-C0KyPo2O.js} +1 -1
  29. package/client/dist/assets/agentRuntime-62GWdc26.js +1 -0
  30. package/client/dist/assets/agentRuntime-BN1mKAuY.js +1 -0
  31. package/client/dist/assets/agentRuntime-BNBCzQLM.js +1 -0
  32. package/client/dist/assets/agentRuntime-BQE_K1GG.js +1 -0
  33. package/client/dist/assets/agentRuntime-CCU3HCtS.js +1 -0
  34. package/client/dist/assets/agentRuntime-DT72Bpdv.js +1 -0
  35. package/client/dist/assets/agentRuntime-ZfMPeeiP.js +1 -0
  36. package/client/dist/assets/agentRuntime-g3W8Ry4d.js +1 -0
  37. package/client/dist/assets/{agents-IUOgqT5t.js → agents-BIT1UdTy.js} +1 -1
  38. package/client/dist/assets/{agents-DaYcxAr1.js → agents-C7fKfvm8.js} +1 -1
  39. package/client/dist/assets/{agents-ms7TwG16.js → agents-CST-lpYX.js} +1 -1
  40. package/client/dist/assets/{agents-Bvyi3y6R.js → agents-Cm2JYrAa.js} +1 -1
  41. package/client/dist/assets/{agents-BHp0_FQt.js → agents-DsgGMGwU.js} +1 -1
  42. package/client/dist/assets/{agents-45GbINfh.js → agents-DyI4rbHa.js} +1 -1
  43. package/client/dist/assets/{agents-rzPnqoBR.js → agents-H6tVQUNP.js} +1 -1
  44. package/client/dist/assets/{agents-DMKFStHL.js → agents-_5mJMkAE.js} +1 -1
  45. package/client/dist/assets/format-duration-NJVPOPBG.js +1 -0
  46. package/client/dist/assets/{formatDistanceToNow-Bq8iptrG.js → formatDistanceToNow-D44phX1J.js} +1 -1
  47. package/client/dist/assets/{getTimezoneOffsetInMilliseconds-BVFky3l_.js → getTimezoneOffsetInMilliseconds-EAh9LFSx.js} +1 -1
  48. package/client/dist/assets/index-Cfh9aaep.css +2 -0
  49. package/client/dist/assets/index-DgMBighL.js +76 -0
  50. package/client/dist/assets/{jira-api-zrRdZFBB.js → jira-api-BQR6G0YY.js} +1 -1
  51. package/client/dist/assets/narration-B1uGfofH.js +1 -0
  52. package/client/dist/assets/narration-BKcL4Srl.js +1 -0
  53. package/client/dist/assets/narration-BtjKyHVM.js +1 -0
  54. package/client/dist/assets/narration-CqNvgRi9.js +1 -0
  55. package/client/dist/assets/narration-DiPyj06T.js +1 -0
  56. package/client/dist/assets/narration-DrBAf8uv.js +1 -0
  57. package/client/dist/assets/narration-OyujFouL.js +1 -0
  58. package/client/dist/assets/narration-UWi_xIZ-.js +1 -0
  59. package/client/dist/assets/{project-repositories-CFRaB9UB.js → project-repositories-GRYdjlKB.js} +1 -1
  60. package/client/dist/assets/{provider-capabilities-B-wOHfLP.js → provider-capabilities-CXipwrXJ.js} +1 -1
  61. package/client/dist/assets/{settings-OOyDrZQo.js → settings-B3rWEwOx.js} +1 -1
  62. package/client/dist/assets/{settings-CNiERE-R.js → settings-BcVoiMyw.js} +1 -1
  63. package/client/dist/assets/settings-BlYvaQ7w.js +1 -0
  64. package/client/dist/assets/{settings-DJ2jl3hJ.js → settings-CD5uEBiL.js} +1 -1
  65. package/client/dist/assets/{settings-a7NIODme.js → settings-CyhS0DLJ.js} +1 -1
  66. package/client/dist/assets/{settings-DVRVybR_.js → settings-DCMFUGDs.js} +1 -1
  67. package/client/dist/assets/{settings-CrtCs5-B.js → settings-DEgcA0jd.js} +1 -1
  68. package/client/dist/assets/{settings-ChOCVjX_.js → settings-DmbYKyMJ.js} +1 -1
  69. package/client/dist/assets/{spending-lTZ-bm_y.js → spending-CN9PHM65.js} +1 -1
  70. package/client/dist/assets/{useDesktop-Duv5ddAa.js → useDesktop-iius_s5d.js} +1 -1
  71. package/client/dist/assets/{useSharedWebSocket-5mj2SMKn.js → useSharedWebSocket-D7hkZ3Ka.js} +2 -2
  72. package/client/dist/index.html +20 -19
  73. package/docs/internals/programmatic-agent-runtime.md +19 -8
  74. package/docs/plans/implementation-efficiency-artifact.json +18 -0
  75. package/docs/plans/implementation-efficiency-evaluation.json +1817 -0
  76. package/docs/plans/implementation-efficiency-evaluation.md +41 -0
  77. package/docs/plans/implementation-efficiency-progress.md +48 -0
  78. package/docs/plans/implementation-efficiency-release.md +41 -0
  79. package/docs/plans/implementation-efficiency.md +124 -0
  80. package/package.json +1 -1
  81. package/server/dist/agent-mcp-config.js +5 -0
  82. package/server/dist/agent-operator-prompt.js +2 -2
  83. package/server/dist/agent-runtime-bridge.js +69 -17
  84. package/server/dist/agent-runtime-controls-router.js +50 -0
  85. package/server/dist/agent-runtime-controls.js +123 -12
  86. package/server/dist/agent-runtime-effective-config.js +32 -0
  87. package/server/dist/agent-runtime-events.js +46 -0
  88. package/server/dist/agent-runtime-history.js +47 -0
  89. package/server/dist/agent-runtime-loader.js +90 -5
  90. package/server/dist/agent-runtime-metrics.js +122 -0
  91. package/server/dist/agent-runtime-package.js +205 -0
  92. package/server/dist/agent-runtime-repositories.js +37 -0
  93. package/server/dist/agent-runtime-settings-router.js +29 -6
  94. package/server/dist/agent-runtime-settings.js +151 -21
  95. package/server/dist/agent-runtime-settlement.js +133 -0
  96. package/server/dist/contract-refine-runner.js +7 -7
  97. package/server/dist/core-completion.js +8 -5
  98. package/server/dist/desktop-router.js +26 -0
  99. package/server/dist/job-listing.js +7 -3
  100. package/server/dist/loop-command-catalog.js +2 -2
  101. package/server/dist/loop-executors.js +6 -4
  102. package/server/dist/loop-run-manager.js +12 -4
  103. package/server/dist/mcp/tools/rails.js +1 -0
  104. package/server/dist/mcp/tools/specs.js +7 -4
  105. package/server/dist/multi-repo-execution.js +1 -0
  106. package/server/dist/project-router-jobs.js +8 -2
  107. package/server/dist/project-router-tickets.js +4 -4
  108. package/server/dist/providers/runtime.js +6 -0
  109. package/server/dist/rail-isolated-launch.js +2 -1
  110. package/server/dist/rails-router.js +29 -1
  111. package/server/dist/runtime-role-prompts-router.js +25 -0
  112. package/server/dist/schemas/agent-runtime.schema.json +25 -3
  113. package/server/dist/schemas/fixtures/runtime-efficiency-summary.v1.json +790 -0
  114. package/server/dist/worktree-overlay.js +32 -1
  115. package/client/dist/assets/AgentModeAnalyticsPane-BUvZNoj0.js +0 -2
  116. package/client/dist/assets/AgentModeCodePane-YW717UvJ.js +0 -2
  117. package/client/dist/assets/AgentModeJobsPane-Cxg_eNmd.js +0 -1
  118. package/client/dist/assets/AgentsPage-B_DCByok.js +0 -87
  119. package/client/dist/assets/CodePage-PyZKCj1B.js +0 -3
  120. package/client/dist/assets/InteractiveJobComposer-l8q-r5Yy.js +0 -19
  121. package/client/dist/assets/JobDetailModal-C-X2GI9j.js +0 -1
  122. package/client/dist/assets/JobDetailPage-C6Jdm7S9.js +0 -1
  123. package/client/dist/assets/JobsPage-DycM6Vgp.js +0 -1
  124. package/client/dist/assets/agentRuntime-14faDjWM.js +0 -1
  125. package/client/dist/assets/agentRuntime-BIokojkJ.js +0 -1
  126. package/client/dist/assets/agentRuntime-Bu4xEcRw.js +0 -1
  127. package/client/dist/assets/agentRuntime-Cz0ASixB.js +0 -1
  128. package/client/dist/assets/agentRuntime-DESdGXLV.js +0 -1
  129. package/client/dist/assets/agentRuntime-DhX5Xy5S.js +0 -1
  130. package/client/dist/assets/agentRuntime-Dpbh_gLp.js +0 -1
  131. package/client/dist/assets/agentRuntime-jS2lplFm.js +0 -1
  132. package/client/dist/assets/index-CBLYwPw6.css +0 -2
  133. package/client/dist/assets/index-DOKftX7y.js +0 -76
  134. package/client/dist/assets/narration-BA8Yw5ZL.js +0 -1
  135. package/client/dist/assets/narration-BuMFUS7Q.js +0 -1
  136. package/client/dist/assets/narration-Bv_iQQG5.js +0 -1
  137. package/client/dist/assets/narration-C6Ost9nL.js +0 -1
  138. package/client/dist/assets/narration-CIbjo2pA.js +0 -1
  139. package/client/dist/assets/narration-CNJ2-2-a.js +0 -1
  140. package/client/dist/assets/narration-OzsPJCXM.js +0 -1
  141. package/client/dist/assets/narration-zCdgiuSU.js +0 -1
  142. package/client/dist/assets/settings-DzXZCb1h.js +0 -1
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.readRuntimeEfficiency = readRuntimeEfficiency;
4
+ exports.readRuntimeEfficiencySummary = readRuntimeEfficiencySummary;
5
+ exports.applyRuntimeSelectionOrigins = applyRuntimeSelectionOrigins;
6
+ const COUNTS = ['attempts', 'measuredAttempts', 'providerCalls', 'toolCalls', 'inputTokens', 'outputTokens', 'uncachedInputTokens', 'cacheReadInputTokens', 'cacheWriteInputTokens'];
7
+ const KEYS = [...COUNTS, 'durationMs', 'agentDurationMs', 'costUsd'];
8
+ const PHASES = ['architect', 'developer', 'verify', 'reviewer', 'archive'];
9
+ function object(value) { return value !== null && typeof value === 'object' && !Array.isArray(value) ? value : null; }
10
+ function counters(value) {
11
+ const input = object(value);
12
+ if (!input)
13
+ return null;
14
+ const output = {};
15
+ for (const key of KEYS) {
16
+ const number = input[key];
17
+ if (number === null && key !== 'attempts' && key !== 'measuredAttempts') {
18
+ output[key] = null;
19
+ continue;
20
+ }
21
+ if (typeof number !== 'number' || !Number.isFinite(number) || number < 0 || (COUNTS.includes(key) && !Number.isSafeInteger(number)))
22
+ return null;
23
+ output[key] = number;
24
+ }
25
+ if (output.measuredAttempts > output.attempts)
26
+ return null;
27
+ return output;
28
+ }
29
+ /** Older Core versions omit metrics; malformed/unknown versions do not break run controls. */
30
+ function readRuntimeEfficiency(value) {
31
+ const input = object(value);
32
+ if (input?.schemaVersion !== 1 || !Array.isArray(input.phases) || input.phases.length > PHASES.length)
33
+ return undefined;
34
+ const total = counters(input.total);
35
+ if (!total)
36
+ return undefined;
37
+ const phases = [];
38
+ for (const raw of input.phases) {
39
+ const phase = object(raw), count = counters(phase);
40
+ if (!phase || !count || typeof phase.stepId !== 'string' || !PHASES.includes(phase.stepId) || phases.some(item => item.stepId === phase.stepId))
41
+ return undefined;
42
+ for (const key of ['providers', 'models'])
43
+ if (!Array.isArray(phase[key]) || phase[key].length > 50 || !phase[key].every((item) => typeof item === 'string' && item.length > 0 && item.length <= 256))
44
+ return undefined;
45
+ phases.push({ ...count, stepId: phase.stepId, providers: [...phase.providers], models: [...phase.models] });
46
+ }
47
+ return { schemaVersion: 1, total, phases };
48
+ }
49
+ function readRuntimeEfficiencySummary(value) {
50
+ const input = object(value), invocations = object(input?.invocations), checks = object(input?.checks);
51
+ if (!input || input.schemaVersion !== 1 || typeof input.runId !== 'string' || input.runId.length > 128 || typeof input.workflowVersion !== 'string' || input.workflowVersion.length > 16 || !['pending', 'validated', 'with-exceptions', 'blocked'].includes(String(input.technicalAcceptance)) || !invocations || !checks || !Array.isArray(input.roles) || input.roles.length > 3)
52
+ return undefined;
53
+ if (typeof input.archive !== 'string' || input.archive.length > 32 || typeof input.delivery !== 'string' || input.delivery.length > 32 || typeof invocations.complete !== 'boolean' || typeof checks.available !== 'boolean')
54
+ return undefined;
55
+ const count = (value, nullable = true) => (nullable && value === null) || typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
56
+ for (const key of ['total', 'fullContexts', 'incrementalContexts'])
57
+ if (!count(invocations[key], false))
58
+ return undefined;
59
+ for (const key of ['promptBytes', 'contextBytes', 'handoffBytes'])
60
+ if (!count(invocations[key]))
61
+ return undefined;
62
+ if (checks.complete !== undefined && typeof checks.complete !== 'boolean' || checks.invalidated !== undefined && !count(checks.invalidated))
63
+ return undefined;
64
+ for (const key of ['executed', 'reused', 'notRun'])
65
+ if (!count(checks[key]))
66
+ return undefined;
67
+ if (checks.durationMs !== null && (typeof checks.durationMs !== 'number' || !Number.isFinite(checks.durationMs) || checks.durationMs < 0))
68
+ return undefined;
69
+ for (const key of ['planHash', 'candidateHash'])
70
+ if (input[key] !== null && (typeof input[key] !== 'string' || !/^[a-f0-9]{64}$/.test(input[key])))
71
+ return undefined;
72
+ if (!Array.isArray(input.currentEvidenceIds) || input.currentEvidenceIds.length > 100 || !input.currentEvidenceIds.every(id => typeof id === 'string' && /^[a-f0-9]{64}$/.test(id)))
73
+ return undefined;
74
+ const kinds = object(invocations.byKind);
75
+ if (!kinds || Object.keys(kinds).length !== 5 || !['initial', 'correction', 'repair', 'deepen', 'session-fallback'].every(key => count(kinds[key], false)))
76
+ return undefined;
77
+ const roles = [];
78
+ for (const raw of input.roles) {
79
+ const role = object(raw);
80
+ if (!role || !['architect', 'developer', 'reviewer'].includes(String(role.role)) || roles.some(item => item.role === role.role))
81
+ return undefined;
82
+ const row = { role: String(role.role) };
83
+ for (const key of ['provider', 'model', 'effort', 'observedModel', 'observedEffort', 'origin', 'tier']) {
84
+ if (role[key] !== null && (typeof role[key] !== 'string' || role[key].length > 256))
85
+ return undefined;
86
+ row[key] = role[key];
87
+ }
88
+ if (!['base', 'escalation'].includes(String(row.tier)) || typeof row.origin !== 'string')
89
+ return undefined;
90
+ roles.push(row);
91
+ }
92
+ let escalations;
93
+ if (input.escalations !== undefined) {
94
+ if (!Array.isArray(input.escalations) || input.escalations.length > 16 || typeof input.escalationsTruncated !== 'boolean')
95
+ return undefined;
96
+ escalations = [];
97
+ for (const item of input.escalations) {
98
+ const raw = object(item);
99
+ if (!raw || !['architect', 'developer', 'reviewer'].includes(String(raw.role)))
100
+ return undefined;
101
+ const row = { role: String(raw.role) };
102
+ for (const key of ['attemptId', 'provider', 'model', 'effort', 'reason']) {
103
+ if (raw[key] !== null && (typeof raw[key] !== 'string' || raw[key].length > 256) || ['attemptId', 'provider'].includes(key) && typeof raw[key] !== 'string')
104
+ return undefined;
105
+ row[key] = raw[key];
106
+ }
107
+ escalations.push(row);
108
+ }
109
+ }
110
+ return { ...(escalations ? { escalations, escalationsTruncated: input.escalationsTruncated } : {}), schemaVersion: 1, currentEvidenceIds: [...input.currentEvidenceIds], planHash: input.planHash, candidateHash: input.candidateHash, runId: input.runId, workflowVersion: input.workflowVersion, technicalAcceptance: input.technicalAcceptance, archive: input.archive, delivery: input.delivery, roles,
111
+ invocations: { total: invocations.total, complete: invocations.complete, byKind: { ...kinds }, promptBytes: invocations.promptBytes, contextBytes: invocations.contextBytes, handoffBytes: invocations.handoffBytes, fullContexts: invocations.fullContexts, incrementalContexts: invocations.incrementalContexts },
112
+ checks: { ...(checks.complete === undefined ? {} : { complete: checks.complete }), ...(checks.invalidated === undefined ? {} : { invalidated: checks.invalidated }), available: checks.available, executed: checks.executed, reused: checks.reused, notRun: checks.notRun, durationMs: checks.durationMs } };
113
+ }
114
+ /** Provenance is host-owned; it never changes Core's frozen provider selection. */
115
+ function applyRuntimeSelectionOrigins(summary, selection, runId) {
116
+ const record = object(selection), origins = object(record?.origins);
117
+ if (!summary || record?.schemaVersion !== 1 || record.runId !== runId || !origins)
118
+ return summary;
119
+ if (!['architect', 'developer', 'reviewer'].every(role => ['project-role', 'default', ...(role === 'developer' ? ['explicit-launch-override'] : [])].includes(String(origins[role]))))
120
+ return summary;
121
+ return { ...summary, roles: summary.roles.map(role => ({ ...role, origin: String(origins[role.role]) })) };
122
+ }
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.runtimeEntryFingerprint = runtimeEntryFingerprint;
7
+ exports.retainAgentRuntime = retainAgentRuntime;
8
+ exports.resolveRetainedAgentRuntime = resolveRetainedAgentRuntime;
9
+ const node_crypto_1 = require("node:crypto");
10
+ const node_fs_1 = __importDefault(require("node:fs"));
11
+ const node_module_1 = require("node:module");
12
+ const node_path_1 = __importDefault(require("node:path"));
13
+ const SHIPPED = ['package.json', 'dist', 'bin', 'templates', 'schemas', 'commands', 'integration-contract.json', 'pinned-versions.json'];
14
+ const SAFE_PACKAGE = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/i;
15
+ function packageRoot(file, name) {
16
+ let directory = node_path_1.default.dirname(node_fs_1.default.realpathSync(file));
17
+ for (;;) {
18
+ const manifest = node_path_1.default.join(directory, 'package.json');
19
+ if (node_fs_1.default.existsSync(manifest)) {
20
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(manifest, 'utf8'));
21
+ if (pkg.name === (name ?? 'specrails-core'))
22
+ return directory;
23
+ }
24
+ const parent = node_path_1.default.dirname(directory);
25
+ if (directory === parent)
26
+ throw new Error('Core runtime does not resolve to an identifiable package');
27
+ directory = parent;
28
+ }
29
+ }
30
+ /** Content identity also detects same-size edits with preserved timestamps. */
31
+ function runtimeEntryFingerprint(cli) {
32
+ let root;
33
+ try {
34
+ root = packageRoot(cli);
35
+ }
36
+ catch {
37
+ return (0, node_crypto_1.createHash)('sha256').update(node_fs_1.default.readFileSync(cli)).digest('hex');
38
+ }
39
+ return treeDigest(root, SHIPPED);
40
+ }
41
+ function treeDigest(root, entries = node_fs_1.default.readdirSync(root).sort()) {
42
+ const hash = (0, node_crypto_1.createHash)('sha256');
43
+ let files = 0, bytes = 0;
44
+ const visit = (relative) => {
45
+ const file = node_path_1.default.join(root, relative), stat = node_fs_1.default.lstatSync(file);
46
+ if (stat.isSymbolicLink())
47
+ throw new Error('Retained Core package contains a symbolic link');
48
+ if (stat.isDirectory())
49
+ for (const name of node_fs_1.default.readdirSync(file).sort())
50
+ visit(node_path_1.default.posix.join(relative, name));
51
+ else if (stat.isFile()) {
52
+ if (++files > 100_000 || (bytes += stat.size) > 1024 * 1024 * 1024)
53
+ throw new Error('Core runtime package exceeds retention limits');
54
+ hash.update(JSON.stringify([relative, stat.size])).update('\0').update(node_fs_1.default.readFileSync(file)).update('\0');
55
+ }
56
+ else
57
+ throw new Error('Core runtime package contains a nonregular file');
58
+ };
59
+ for (const entry of entries)
60
+ if (node_fs_1.default.existsSync(node_path_1.default.join(root, entry)))
61
+ visit(entry);
62
+ return hash.digest('hex');
63
+ }
64
+ /** Copy only the selected package and its installed production dependency
65
+ * closure. Resolution follows npm's actual nested versions, without downloads. */
66
+ function copyInstalledPackage(source, destination) {
67
+ const installed = new Map();
68
+ const copying = new Set();
69
+ let files = 0, bytes = 0, packages = 0;
70
+ const copy = (from, to, boundary) => {
71
+ const stat = node_fs_1.default.lstatSync(from);
72
+ if (stat.isSymbolicLink()) {
73
+ const resolved = node_fs_1.default.realpathSync(from), relative = node_path_1.default.relative(boundary, resolved);
74
+ if (relative.startsWith('..') || node_path_1.default.isAbsolute(relative))
75
+ throw new Error('Core package symlink escapes its package');
76
+ copy(resolved, to, boundary);
77
+ }
78
+ else if (stat.isDirectory()) {
79
+ const real = node_fs_1.default.realpathSync(from);
80
+ if (copying.has(real))
81
+ throw new Error('Core package contains a cyclic symbolic link');
82
+ copying.add(real);
83
+ node_fs_1.default.mkdirSync(to, { recursive: true });
84
+ try {
85
+ for (const name of node_fs_1.default.readdirSync(from).sort())
86
+ if (!['node_modules', '.git', '.bin'].includes(name))
87
+ copy(node_path_1.default.join(from, name), node_path_1.default.join(to, name), boundary);
88
+ }
89
+ finally {
90
+ copying.delete(real);
91
+ }
92
+ }
93
+ else if (stat.isFile()) {
94
+ if (++files > 100_000 || (bytes += stat.size) > 1024 * 1024 * 1024)
95
+ throw new Error('Core runtime package exceeds retention limits');
96
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(to), { recursive: true });
97
+ node_fs_1.default.copyFileSync(from, to);
98
+ node_fs_1.default.chmodSync(to, stat.mode & 0o777);
99
+ }
100
+ else
101
+ throw new Error('Core package contains a nonregular file');
102
+ };
103
+ const resolveDependency = (name, from) => {
104
+ const require = (0, node_module_1.createRequire)(node_path_1.default.join(from, 'package.json'));
105
+ try {
106
+ return packageRoot(require.resolve(name + '/package.json'), name);
107
+ }
108
+ catch {
109
+ return packageRoot(require.resolve(name), name);
110
+ }
111
+ };
112
+ const install = (from, to, root = false) => {
113
+ if (++packages > 2000)
114
+ throw new Error('Core dependency closure exceeds retention limits');
115
+ if (root)
116
+ for (const entry of SHIPPED) {
117
+ if (node_fs_1.default.existsSync(node_path_1.default.join(from, entry)))
118
+ copy(node_path_1.default.join(from, entry), node_path_1.default.join(to, entry), from);
119
+ }
120
+ else
121
+ copy(from, to, from);
122
+ installed.set(node_fs_1.default.realpathSync(to), from);
123
+ const pkg = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(from, 'package.json'), 'utf8'));
124
+ for (const name of Object.keys({ ...pkg.dependencies, ...pkg.optionalDependencies }).sort()) {
125
+ if (!SAFE_PACKAGE.test(name))
126
+ throw new Error('Invalid Core package dependency');
127
+ let dependency;
128
+ try {
129
+ dependency = resolveDependency(name, from);
130
+ }
131
+ catch (error) {
132
+ if (pkg.optionalDependencies?.[name])
133
+ continue;
134
+ throw error;
135
+ }
136
+ try {
137
+ const present = resolveDependency(name, to);
138
+ if (installed.get(present) === dependency)
139
+ continue;
140
+ }
141
+ catch { /* Not copied into this resolution scope yet. */ }
142
+ install(dependency, node_path_1.default.join(to, 'node_modules', name));
143
+ }
144
+ };
145
+ install(source, destination, true);
146
+ }
147
+ function retainAgentRuntime(cli, contextPath) {
148
+ const pinFile = node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-package.json');
149
+ if (node_fs_1.default.existsSync(pinFile))
150
+ return resolveRetainedAgentRuntime(contextPath);
151
+ const realCli = node_fs_1.default.realpathSync(cli);
152
+ const root = packageRoot(realCli);
153
+ const relativeCli = node_path_1.default.relative(root, realCli);
154
+ if (relativeCli.startsWith('..') || node_path_1.default.isAbsolute(relativeCli))
155
+ throw new Error('Core CLI escapes its package');
156
+ const cache = node_path_1.default.join(node_path_1.default.dirname(node_path_1.default.dirname(contextPath)), 'runtime-packages');
157
+ node_fs_1.default.mkdirSync(cache, { recursive: true, mode: 0o700 });
158
+ const staged = node_path_1.default.join(cache, '.staged-' + (0, node_crypto_1.randomUUID)());
159
+ try {
160
+ copyInstalledPackage(root, staged);
161
+ const integrity = treeDigest(staged);
162
+ const retained = node_path_1.default.join(cache, integrity);
163
+ if (node_fs_1.default.existsSync(retained)) {
164
+ if (treeDigest(retained) !== integrity)
165
+ throw new Error('Retained Core runtime integrity changed');
166
+ }
167
+ else {
168
+ try {
169
+ node_fs_1.default.renameSync(staged, retained);
170
+ }
171
+ catch (error) {
172
+ if (!node_fs_1.default.existsSync(retained) || treeDigest(retained) !== integrity)
173
+ throw error;
174
+ }
175
+ }
176
+ const pin = { schemaVersion: 1, packageVersion: JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(root, 'package.json'), 'utf8')).version, integrity, root: retained, cli: relativeCli };
177
+ try {
178
+ node_fs_1.default.writeFileSync(pinFile, JSON.stringify(pin) + '\n', { flag: 'wx', mode: 0o600 });
179
+ }
180
+ catch (error) {
181
+ if (error.code !== 'EEXIST' || resolveRetainedAgentRuntime(contextPath) !== node_path_1.default.join(retained, relativeCli))
182
+ throw error;
183
+ }
184
+ return node_path_1.default.join(retained, relativeCli);
185
+ }
186
+ finally {
187
+ node_fs_1.default.rmSync(staged, { recursive: true, force: true });
188
+ }
189
+ }
190
+ function resolveRetainedAgentRuntime(contextPath) {
191
+ const pinFile = node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-package.json');
192
+ if (!node_fs_1.default.existsSync(pinFile))
193
+ throw new Error('Original runtime identity is unrecorded. Restore a proven original Core package before continuing this saved execution.');
194
+ const pin = JSON.parse(node_fs_1.default.readFileSync(pinFile, 'utf8'));
195
+ const expected = node_path_1.default.join(node_path_1.default.dirname(node_path_1.default.dirname(contextPath)), 'runtime-packages', pin.integrity);
196
+ if (pin.schemaVersion !== 1 || !/^[a-f0-9]{64}$/.test(pin.integrity) || pin.root !== expected || !node_fs_1.default.existsSync(expected) || node_fs_1.default.realpathSync(expected) !== node_path_1.default.resolve(expected)
197
+ || typeof pin.cli !== 'string' || node_path_1.default.isAbsolute(pin.cli) || pin.cli.split(/[\\/]/).includes('..'))
198
+ throw new Error('The original retained Core package is unavailable or invalid. Restore it before continuing.');
199
+ if (treeDigest(expected) !== pin.integrity)
200
+ throw new Error('The original retained Core package failed its integrity check. Restore it before continuing.');
201
+ const cli = node_path_1.default.join(expected, pin.cli);
202
+ if (!node_fs_1.default.statSync(cli).isFile())
203
+ throw new Error('The original Core executable is unavailable');
204
+ return cli;
205
+ }
@@ -0,0 +1,37 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toolRepositories = toolRepositories;
4
+ const node_path_1 = require("node:path");
5
+ /** Attribute only explicit paths or repository roots present in a tool action.
6
+ * Never carry a previous action's repository into an unrelated tool call. */
7
+ function toolRepositories(event, repositories, defaultCwd) {
8
+ const found = new Set();
9
+ const cwd = typeof event.cwd === 'string' && (0, node_path_1.isAbsolute)(event.cwd) ? event.cwd : defaultCwd;
10
+ const paths = Array.isArray(event.targetPaths) ? event.targetPaths.filter((p) => typeof p === 'string') : [];
11
+ if (!paths.length && typeof event.detail === 'string' && (0, node_path_1.isAbsolute)(event.detail) && !event.detail.endsWith('…'))
12
+ paths.push(event.detail);
13
+ for (const target of paths) {
14
+ const absolute = (0, node_path_1.resolve)(cwd, target);
15
+ const matches = repositories.filter(repo => {
16
+ if (!repo.path)
17
+ return false;
18
+ const suffix = (0, node_path_1.relative)(repo.path, absolute);
19
+ return suffix === '' || (!(0, node_path_1.isAbsolute)(suffix) && suffix !== '..' && !suffix.startsWith('..' + node_path_1.sep));
20
+ }).sort((a, b) => b.path.length - a.path.length);
21
+ if (matches[0])
22
+ found.add(matches[0].id);
23
+ }
24
+ // Shell activity may contain `cd /repo && ...` or address several repositories.
25
+ if (!paths.length && typeof event.detail === 'string') {
26
+ for (const repo of repositories) {
27
+ if (!repo.path)
28
+ continue;
29
+ const escaped = repo.path.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
30
+ if (new RegExp('(?:^|[\\s\\x22\\x27=])' + escaped + '(?=$|[\\s/\\\\\\x22\\x27;&])').test(event.detail))
31
+ found.add(repo.id);
32
+ }
33
+ }
34
+ if (!found.size && !paths.length && typeof event.cwd === 'string' && (0, node_path_1.isAbsolute)(event.cwd))
35
+ return toolRepositories({ targetPaths: [event.cwd] }, repositories, defaultCwd);
36
+ return repositories.filter(repo => found.has(repo.id));
37
+ }
@@ -1,16 +1,23 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.registerAgentRuntimeSettingsRoutes = registerAgentRuntimeSettingsRoutes;
4
+ const node_fs_1 = require("node:fs");
4
5
  const agent_runtime_loader_1 = require("./agent-runtime-loader");
5
6
  const agent_runtime_settings_1 = require("./agent-runtime-settings");
6
7
  const agent_runtime_verification_suggestions_1 = require("./agent-runtime-verification-suggestions");
7
8
  const project_repositories_1 = require("./project-repositories");
8
9
  function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
9
- router.get('/:projectId/agent-runtime/config', (_req, res) => {
10
+ router.get('/:projectId/agent-runtime/config', async (_req, res) => {
10
11
  try {
11
12
  const project = ctx(_req).project;
13
+ const configured = (0, node_fs_1.existsSync)((0, agent_runtime_settings_1.agentRuntimeConfigPath)(project));
12
14
  const config = (0, agent_runtime_settings_1.loadAgentRuntimeConfig)(project);
13
- res.json({ configured: config !== null, config: config ?? (0, agent_runtime_settings_1.defaultAgentRuntimeConfig)(project), runtimeAvailable: (0, agent_runtime_loader_1.findCoreAgentRuntimeEntry)() !== null });
15
+ let efficiencyAvailable = false;
16
+ try {
17
+ efficiencyAvailable = (await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).api?.capabilities?.efficientRoleExecution === 1;
18
+ }
19
+ catch { /* Ordinary settings remain readable without capability support. */ }
20
+ res.json({ configured, config, runtimeAvailable: (0, agent_runtime_loader_1.findCoreAgentRuntimeEntry)() !== null, efficiencyAvailable });
14
21
  }
15
22
  catch (err) {
16
23
  const validation = err instanceof agent_runtime_settings_1.AgentRuntimeConfigError;
@@ -30,18 +37,34 @@ function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
30
37
  res.status(500).json({ error: 'verification_suggestions_failed', message: 'Could not inspect the project repositories' });
31
38
  }
32
39
  });
40
+ router.post('/:projectId/agent-runtime/capabilities', async (req, res) => {
41
+ try {
42
+ const config = (0, agent_runtime_settings_1.validateAgentRuntimeConfig)({ ...req.body, providers: (0, agent_runtime_settings_1.loadRuntimeProviders)(), enabled: true });
43
+ const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
44
+ if (!runtime.capabilities)
45
+ throw new Error('Installed Core does not support capability introspection');
46
+ res.json(await runtime.capabilities(config));
47
+ }
48
+ catch (error) {
49
+ res.status(error instanceof agent_runtime_settings_1.AgentRuntimeConfigError ? 400 : 503).json({ error: 'runtime_capabilities_unavailable', message: error instanceof agent_runtime_settings_1.AgentRuntimeConfigError ? error.message : 'Update the paired Core runtime to inspect model capabilities' });
50
+ }
51
+ });
33
52
  router.put('/:projectId/agent-runtime/config', async (req, res) => {
34
53
  try {
35
- const config = (0, agent_runtime_settings_1.validateAgentRuntimeConfig)(req.body);
54
+ (0, agent_runtime_settings_1.validateAgentRuntimeConfig)(req.body);
55
+ const config = (0, agent_runtime_settings_1.validateAgentRuntimeConfig)({ ...req.body, enabled: true, providers: (0, agent_runtime_settings_1.loadRuntimeProviders)() });
36
56
  const runtimeAvailable = (0, agent_runtime_loader_1.findCoreAgentRuntimeEntry)() !== null;
37
57
  if (config.enabled && !runtimeAvailable) {
38
58
  res.status(503).json({ error: 'runtime_unavailable', message: 'Update Core to an installation with the agent runtime before enabling it' });
39
59
  return;
40
60
  }
41
- // Disabling must remain possible even when an installed runtime is broken.
61
+ let efficiencyAvailable = false;
42
62
  if (config.enabled && runtimeAvailable) {
43
63
  try {
44
- (await (0, agent_runtime_loader_1.loadCoreAgentRuntime)()).validateRuntimeConfig(config);
64
+ const runtime = await (0, agent_runtime_loader_1.loadCoreAgentRuntime)();
65
+ runtime.validateRuntimeConfig(config);
66
+ (0, agent_runtime_loader_1.validateRequestedRoleEfforts)(runtime, config);
67
+ efficiencyAvailable = runtime.api?.capabilities?.efficientRoleExecution === 1;
45
68
  }
46
69
  catch {
47
70
  res.status(503).json({ error: 'runtime_incompatible', message: 'The installed Core runtime could not validate this configuration; update Core and retry' });
@@ -49,7 +72,7 @@ function registerAgentRuntimeSettingsRoutes({ router, ctx }) {
49
72
  }
50
73
  }
51
74
  const saved = (0, agent_runtime_settings_1.saveAgentRuntimeConfig)(ctx(req).project, config);
52
- res.json({ configured: true, config: saved, runtimeAvailable });
75
+ res.json({ configured: true, config: saved, runtimeAvailable, efficiencyAvailable });
53
76
  }
54
77
  catch (err) {
55
78
  const validation = err instanceof agent_runtime_settings_1.AgentRuntimeConfigError;