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.
- package/client/dist/assets/{ActivityFeedPage-D3SQklu5.js → ActivityFeedPage-DSCpSOGV.js} +1 -1
- package/client/dist/assets/{AgentBrowserCapture-DNGxE33d.js → AgentBrowserCapture-mc1c5pgS.js} +1 -1
- package/client/dist/assets/AgentModeAnalyticsPane--UkamBTm.js +2 -0
- package/client/dist/assets/AgentModeCodePane-DJCswsvU.js +2 -0
- package/client/dist/assets/AgentModeJobsPane-Dw3DNFkh.js +1 -0
- package/client/dist/assets/AgentsPage-ChElPUL9.js +87 -0
- package/client/dist/assets/{AnalyticsPage-BqV6mM2O.js → AnalyticsPage-BGtkFHBp.js} +1 -1
- package/client/dist/assets/CodePage-BBnw3VGn.js +3 -0
- package/client/dist/assets/{DesktopAnalyticsPage-D6WNax3K.js → DesktopAnalyticsPage-B85tNScc.js} +1 -1
- package/client/dist/assets/{DocsDialog-68jKUH9f.js → DocsDialog-D4cXrOMl.js} +1 -1
- package/client/dist/assets/{DocsPage-BoGJgRpm.js → DocsPage-BCAbC6qy.js} +1 -1
- package/client/dist/assets/{ExportDropdown-B2C_XLGp.js → ExportDropdown-BwuvqS2Y.js} +1 -1
- package/client/dist/assets/InteractiveJobComposer-Coq-Zhk0.js +19 -0
- package/client/dist/assets/JobDetailModal--Mjam0Nc.js +1 -0
- package/client/dist/assets/JobDetailPage-B6ShWSBh.js +1 -0
- package/client/dist/assets/JobsPage-DHU_ep_B.js +1 -0
- package/client/dist/assets/{LoopBuilderPage-D21GxFmL.js → LoopBuilderPage-Byvq8Oqx.js} +2 -2
- package/client/dist/assets/{LoopPreviewModal-Bo3fYFxN.js → LoopPreviewModal-BMC9Mgj1.js} +1 -1
- package/client/dist/assets/{LoopsPage-BcrBw1cg.js → LoopsPage-CFGl-Nw8.js} +1 -1
- package/client/dist/assets/{MinimizedChatsContext-Dsv2eHf_.js → MinimizedChatsContext-DHjHBYqa.js} +1 -1
- package/client/dist/assets/{PluginsPage-BbyTmSLx.js → PluginsPage-BHygRnW6.js} +2 -2
- package/client/dist/assets/{ProjectSettingsDialog-B3lEXLv7.js → ProjectSettingsDialog-nspqxH-M.js} +1 -1
- package/client/dist/assets/{RepositoryDeliveries-ok_Wo65C.js → RepositoryDeliveries-Dq9NJOWi.js} +1 -1
- package/client/dist/assets/{RepositoryScopeSelector-DU6QwNaS.js → RepositoryScopeSelector-C3jKG2z0.js} +1 -1
- package/client/dist/assets/{ReviewPacketPage-BBCeHtgd.js → ReviewPacketPage-C9mqhi65.js} +1 -1
- package/client/dist/assets/{TemplatePreviewModal-CfNmxS6O.js → TemplatePreviewModal-BmwoAjOr.js} +1 -1
- package/client/dist/assets/{TicketDetailModalContext-DW3eQ0I7.js → TicketDetailModalContext-lYmoONrd.js} +1 -1
- package/client/dist/assets/{Trans-BHPMWkOw.js → Trans-C0KyPo2O.js} +1 -1
- package/client/dist/assets/agentRuntime-62GWdc26.js +1 -0
- package/client/dist/assets/agentRuntime-BN1mKAuY.js +1 -0
- package/client/dist/assets/agentRuntime-BNBCzQLM.js +1 -0
- package/client/dist/assets/agentRuntime-BQE_K1GG.js +1 -0
- package/client/dist/assets/agentRuntime-CCU3HCtS.js +1 -0
- package/client/dist/assets/agentRuntime-DT72Bpdv.js +1 -0
- package/client/dist/assets/agentRuntime-ZfMPeeiP.js +1 -0
- package/client/dist/assets/agentRuntime-g3W8Ry4d.js +1 -0
- package/client/dist/assets/{agents-IUOgqT5t.js → agents-BIT1UdTy.js} +1 -1
- package/client/dist/assets/{agents-DaYcxAr1.js → agents-C7fKfvm8.js} +1 -1
- package/client/dist/assets/{agents-ms7TwG16.js → agents-CST-lpYX.js} +1 -1
- package/client/dist/assets/{agents-Bvyi3y6R.js → agents-Cm2JYrAa.js} +1 -1
- package/client/dist/assets/{agents-BHp0_FQt.js → agents-DsgGMGwU.js} +1 -1
- package/client/dist/assets/{agents-45GbINfh.js → agents-DyI4rbHa.js} +1 -1
- package/client/dist/assets/{agents-rzPnqoBR.js → agents-H6tVQUNP.js} +1 -1
- package/client/dist/assets/{agents-DMKFStHL.js → agents-_5mJMkAE.js} +1 -1
- package/client/dist/assets/format-duration-NJVPOPBG.js +1 -0
- package/client/dist/assets/{formatDistanceToNow-Bq8iptrG.js → formatDistanceToNow-D44phX1J.js} +1 -1
- package/client/dist/assets/{getTimezoneOffsetInMilliseconds-BVFky3l_.js → getTimezoneOffsetInMilliseconds-EAh9LFSx.js} +1 -1
- package/client/dist/assets/index-Cfh9aaep.css +2 -0
- package/client/dist/assets/index-DgMBighL.js +76 -0
- package/client/dist/assets/{jira-api-zrRdZFBB.js → jira-api-BQR6G0YY.js} +1 -1
- package/client/dist/assets/narration-B1uGfofH.js +1 -0
- package/client/dist/assets/narration-BKcL4Srl.js +1 -0
- package/client/dist/assets/narration-BtjKyHVM.js +1 -0
- package/client/dist/assets/narration-CqNvgRi9.js +1 -0
- package/client/dist/assets/narration-DiPyj06T.js +1 -0
- package/client/dist/assets/narration-DrBAf8uv.js +1 -0
- package/client/dist/assets/narration-OyujFouL.js +1 -0
- package/client/dist/assets/narration-UWi_xIZ-.js +1 -0
- package/client/dist/assets/{project-repositories-CFRaB9UB.js → project-repositories-GRYdjlKB.js} +1 -1
- package/client/dist/assets/{provider-capabilities-B-wOHfLP.js → provider-capabilities-CXipwrXJ.js} +1 -1
- package/client/dist/assets/{settings-OOyDrZQo.js → settings-B3rWEwOx.js} +1 -1
- package/client/dist/assets/{settings-CNiERE-R.js → settings-BcVoiMyw.js} +1 -1
- package/client/dist/assets/settings-BlYvaQ7w.js +1 -0
- package/client/dist/assets/{settings-DJ2jl3hJ.js → settings-CD5uEBiL.js} +1 -1
- package/client/dist/assets/{settings-a7NIODme.js → settings-CyhS0DLJ.js} +1 -1
- package/client/dist/assets/{settings-DVRVybR_.js → settings-DCMFUGDs.js} +1 -1
- package/client/dist/assets/{settings-CrtCs5-B.js → settings-DEgcA0jd.js} +1 -1
- package/client/dist/assets/{settings-ChOCVjX_.js → settings-DmbYKyMJ.js} +1 -1
- package/client/dist/assets/{spending-lTZ-bm_y.js → spending-CN9PHM65.js} +1 -1
- package/client/dist/assets/{useDesktop-Duv5ddAa.js → useDesktop-iius_s5d.js} +1 -1
- package/client/dist/assets/{useSharedWebSocket-5mj2SMKn.js → useSharedWebSocket-D7hkZ3Ka.js} +2 -2
- package/client/dist/index.html +20 -19
- package/docs/internals/programmatic-agent-runtime.md +19 -8
- package/docs/plans/implementation-efficiency-artifact.json +18 -0
- package/docs/plans/implementation-efficiency-evaluation.json +1817 -0
- package/docs/plans/implementation-efficiency-evaluation.md +41 -0
- package/docs/plans/implementation-efficiency-progress.md +48 -0
- package/docs/plans/implementation-efficiency-release.md +41 -0
- package/docs/plans/implementation-efficiency.md +124 -0
- package/package.json +1 -1
- package/server/dist/agent-mcp-config.js +5 -0
- package/server/dist/agent-operator-prompt.js +2 -2
- package/server/dist/agent-runtime-bridge.js +69 -17
- package/server/dist/agent-runtime-controls-router.js +50 -0
- package/server/dist/agent-runtime-controls.js +123 -12
- package/server/dist/agent-runtime-effective-config.js +32 -0
- package/server/dist/agent-runtime-events.js +46 -0
- package/server/dist/agent-runtime-history.js +47 -0
- package/server/dist/agent-runtime-loader.js +90 -5
- package/server/dist/agent-runtime-metrics.js +122 -0
- package/server/dist/agent-runtime-package.js +205 -0
- package/server/dist/agent-runtime-repositories.js +37 -0
- package/server/dist/agent-runtime-settings-router.js +29 -6
- package/server/dist/agent-runtime-settings.js +151 -21
- package/server/dist/agent-runtime-settlement.js +133 -0
- package/server/dist/contract-refine-runner.js +7 -7
- package/server/dist/core-completion.js +8 -5
- package/server/dist/desktop-router.js +26 -0
- package/server/dist/job-listing.js +7 -3
- package/server/dist/loop-command-catalog.js +2 -2
- package/server/dist/loop-executors.js +6 -4
- package/server/dist/loop-run-manager.js +12 -4
- package/server/dist/mcp/tools/rails.js +1 -0
- package/server/dist/mcp/tools/specs.js +7 -4
- package/server/dist/multi-repo-execution.js +1 -0
- package/server/dist/project-router-jobs.js +8 -2
- package/server/dist/project-router-tickets.js +4 -4
- package/server/dist/providers/runtime.js +6 -0
- package/server/dist/rail-isolated-launch.js +2 -1
- package/server/dist/rails-router.js +29 -1
- package/server/dist/runtime-role-prompts-router.js +25 -0
- package/server/dist/schemas/agent-runtime.schema.json +25 -3
- package/server/dist/schemas/fixtures/runtime-efficiency-summary.v1.json +790 -0
- package/server/dist/worktree-overlay.js +32 -1
- package/client/dist/assets/AgentModeAnalyticsPane-BUvZNoj0.js +0 -2
- package/client/dist/assets/AgentModeCodePane-YW717UvJ.js +0 -2
- package/client/dist/assets/AgentModeJobsPane-Cxg_eNmd.js +0 -1
- package/client/dist/assets/AgentsPage-B_DCByok.js +0 -87
- package/client/dist/assets/CodePage-PyZKCj1B.js +0 -3
- package/client/dist/assets/InteractiveJobComposer-l8q-r5Yy.js +0 -19
- package/client/dist/assets/JobDetailModal-C-X2GI9j.js +0 -1
- package/client/dist/assets/JobDetailPage-C6Jdm7S9.js +0 -1
- package/client/dist/assets/JobsPage-DycM6Vgp.js +0 -1
- package/client/dist/assets/agentRuntime-14faDjWM.js +0 -1
- package/client/dist/assets/agentRuntime-BIokojkJ.js +0 -1
- package/client/dist/assets/agentRuntime-Bu4xEcRw.js +0 -1
- package/client/dist/assets/agentRuntime-Cz0ASixB.js +0 -1
- package/client/dist/assets/agentRuntime-DESdGXLV.js +0 -1
- package/client/dist/assets/agentRuntime-DhX5Xy5S.js +0 -1
- package/client/dist/assets/agentRuntime-Dpbh_gLp.js +0 -1
- package/client/dist/assets/agentRuntime-jS2lplFm.js +0 -1
- package/client/dist/assets/index-CBLYwPw6.css +0 -2
- package/client/dist/assets/index-DOKftX7y.js +0 -76
- package/client/dist/assets/narration-BA8Yw5ZL.js +0 -1
- package/client/dist/assets/narration-BuMFUS7Q.js +0 -1
- package/client/dist/assets/narration-Bv_iQQG5.js +0 -1
- package/client/dist/assets/narration-C6Ost9nL.js +0 -1
- package/client/dist/assets/narration-CIbjo2pA.js +0 -1
- package/client/dist/assets/narration-CNJ2-2-a.js +0 -1
- package/client/dist/assets/narration-OzsPJCXM.js +0 -1
- package/client/dist/assets/narration-zCdgiuSU.js +0 -1
- package/client/dist/assets/settings-DzXZCb1h.js +0 -1
|
@@ -6,12 +6,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.AgentRuntimeControls = exports.RuntimeControlError = void 0;
|
|
7
7
|
exports.validateRuntimeResumeInput = validateRuntimeResumeInput;
|
|
8
8
|
exports.readAgentRuntimeStatus = readAgentRuntimeStatus;
|
|
9
|
+
const agent_runtime_events_1 = require("./agent-runtime-events");
|
|
10
|
+
const agent_runtime_history_1 = require("./agent-runtime-history");
|
|
11
|
+
const agent_runtime_settlement_1 = require("./agent-runtime-settlement");
|
|
12
|
+
const agent_runtime_metrics_1 = require("./agent-runtime-metrics");
|
|
9
13
|
const node_child_process_1 = require("node:child_process");
|
|
10
14
|
const node_crypto_1 = require("node:crypto");
|
|
11
15
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
12
16
|
const node_path_1 = __importDefault(require("node:path"));
|
|
13
17
|
const node_util_1 = require("node:util");
|
|
14
|
-
const
|
|
18
|
+
const agent_runtime_package_1 = require("./agent-runtime-package");
|
|
15
19
|
const agent_runtime_bridge_1 = require("./agent-runtime-bridge");
|
|
16
20
|
const core_node_runtime_1 = require("./core-node-runtime");
|
|
17
21
|
const win_spawn_1 = require("./util/win-spawn");
|
|
@@ -54,7 +58,13 @@ function openQuestion(state) {
|
|
|
54
58
|
}
|
|
55
59
|
/** Status is a read-only Core CLI operation. It never invokes a provider. */
|
|
56
60
|
async function readAgentRuntimeStatus(contextPath, cwd, env) {
|
|
57
|
-
|
|
61
|
+
let cli;
|
|
62
|
+
try {
|
|
63
|
+
cli = (0, agent_runtime_package_1.resolveRetainedAgentRuntime)(contextPath);
|
|
64
|
+
}
|
|
65
|
+
catch (error) {
|
|
66
|
+
throw new RuntimeControlError(409, 'original_runtime_unavailable', error instanceof Error ? error.message : 'The original runtime package is unavailable');
|
|
67
|
+
}
|
|
58
68
|
if (!cli)
|
|
59
69
|
throw new RuntimeControlError(503, 'runtime_unavailable', 'Update Core to inspect or resume agent runtime executions');
|
|
60
70
|
const { stdout } = await (0, node_util_1.promisify)(node_child_process_1.execFile)((0, core_node_runtime_1.resolveCoreNodeRuntime)(), [cli, 'status', '--context', contextPath, '--compact'], {
|
|
@@ -63,22 +73,29 @@ async function readAgentRuntimeStatus(contextPath, cwd, env) {
|
|
|
63
73
|
const result = JSON.parse(stdout);
|
|
64
74
|
if (result.type !== 'runtime-status' || result.state === undefined)
|
|
65
75
|
throw new Error('Core returned an invalid runtime status');
|
|
66
|
-
|
|
76
|
+
let selection;
|
|
77
|
+
try {
|
|
78
|
+
selection = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-selection.json'), 'utf8'));
|
|
79
|
+
}
|
|
80
|
+
catch { /* Original hosts may not record selection origins. */ }
|
|
81
|
+
return result.state ? { ...result.state, efficiencySummary: (0, agent_runtime_metrics_1.applyRuntimeSelectionOrigins)((0, agent_runtime_metrics_1.readRuntimeEfficiencySummary)(result.efficiencySummary), selection, result.state.runId) } : null;
|
|
67
82
|
}
|
|
68
83
|
/** One controller per ProjectContext; Core's durable lease remains the final
|
|
69
84
|
* cross-process guard. Resume never constructs a new context or worktree. */
|
|
70
85
|
class AgentRuntimeControls {
|
|
71
86
|
ctx;
|
|
72
87
|
dependencies;
|
|
88
|
+
isActive(runId) { return this.active.has(runId); }
|
|
89
|
+
activeRunIds() { return [...this.active.keys()]; }
|
|
73
90
|
active = new Map();
|
|
74
91
|
errors = new Map();
|
|
75
92
|
statusCache = new Map();
|
|
76
93
|
disposed = false;
|
|
77
|
-
constructor(ctx, dependencies = { status: readAgentRuntimeStatus, execute: agent_runtime_bridge_1.runAgentRuntimeInvocation, kill: win_spawn_1.treeKillSafe }) {
|
|
94
|
+
constructor(ctx, dependencies = { status: readAgentRuntimeStatus, execute: agent_runtime_bridge_1.runAgentRuntimeInvocation, kill: win_spawn_1.treeKillSafe, settle: agent_runtime_settlement_1.settleRuntimeContinuation }) {
|
|
78
95
|
this.ctx = ctx;
|
|
79
96
|
this.dependencies = dependencies;
|
|
80
97
|
}
|
|
81
|
-
context(runId) {
|
|
98
|
+
context(runId, historical = false) {
|
|
82
99
|
if (!SAFE_ID.test(runId))
|
|
83
100
|
throw new RuntimeControlError(400, 'invalid_run_id', 'Invalid runtime run ID');
|
|
84
101
|
const execution = (0, workspace_resolution_1.resolveProjectExecution)(this.ctx.project);
|
|
@@ -94,7 +111,7 @@ class AgentRuntimeControls {
|
|
|
94
111
|
if (frozen.runId !== runId || node_fs_1.default.realpathSync(frozen.backlogRoot) !== backlogRoot || !Array.isArray(frozen.repositories) || !frozen.repositories.length || !frozen.repositories.some((repository) => repository.path === frozen.artifactRoot))
|
|
95
112
|
throw new Error();
|
|
96
113
|
for (const root of [frozen.artifactRoot, ...frozen.repositories.map((repository) => repository.path)]) {
|
|
97
|
-
if (!node_path_1.default.isAbsolute(root) || node_fs_1.default.realpathSync(root) !== root || !node_fs_1.default.statSync(root).isDirectory())
|
|
114
|
+
if (!node_path_1.default.isAbsolute(root) || (!historical && (node_fs_1.default.realpathSync(root) !== root || !node_fs_1.default.statSync(root).isDirectory())))
|
|
98
115
|
throw new Error();
|
|
99
116
|
}
|
|
100
117
|
}
|
|
@@ -117,6 +134,21 @@ class AgentRuntimeControls {
|
|
|
117
134
|
Object.assign(env, host.env, { SPECRAILS_EXECUTION_CONTEXT: file });
|
|
118
135
|
return { file, frozen, cwd: host.cwd, env };
|
|
119
136
|
}
|
|
137
|
+
async evidence(runId, query) {
|
|
138
|
+
const { file, frozen, env } = this.context(runId, true);
|
|
139
|
+
const flags = [];
|
|
140
|
+
for (const [key, value] of Object.entries(query)) {
|
|
141
|
+
if (!['id', 'section', 'sourceId', 'cursor', 'limit'].includes(key) || typeof value !== 'string' || value.length > 1024)
|
|
142
|
+
throw new RuntimeControlError(400, 'invalid_evidence_query', 'Invalid evidence query');
|
|
143
|
+
flags.push('--' + (key === 'sourceId' ? 'source-id' : key), value);
|
|
144
|
+
}
|
|
145
|
+
const cli = (0, agent_runtime_package_1.resolveRetainedAgentRuntime)(file);
|
|
146
|
+
const { stdout } = await (0, node_util_1.promisify)(node_child_process_1.execFile)((0, core_node_runtime_1.resolveCoreNodeRuntime)(), [cli, 'evidence', '--context', file, ...flags], { cwd: frozen.backlogRoot, env: (0, win_spawn_1.windowsSpawnEnv)(env), windowsHide: true, timeout: 15000, maxBuffer: 4 * 1024 * 1024, encoding: 'utf8' });
|
|
147
|
+
const result = JSON.parse(stdout);
|
|
148
|
+
if (result.schemaVersion !== 1 || typeof result.available !== 'boolean' || typeof result.truncated !== 'boolean')
|
|
149
|
+
throw new RuntimeControlError(503, 'evidence_unavailable', 'Core evidence is unavailable or incompatible');
|
|
150
|
+
return result;
|
|
151
|
+
}
|
|
120
152
|
async list() {
|
|
121
153
|
const directory = node_path_1.default.join((0, workspace_resolution_1.resolveProjectExecution)(this.ctx.project).specrailsDir, 'pipeline');
|
|
122
154
|
if (!node_fs_1.default.existsSync(directory))
|
|
@@ -136,7 +168,7 @@ class AgentRuntimeControls {
|
|
|
136
168
|
// key, keeping idle settings panes from spawning CLI processes repeatedly.
|
|
137
169
|
const checkpoint = node_path_1.default.join(node_path_1.default.dirname(file), 'agent-workflow', runId, 'checkpoint.json');
|
|
138
170
|
const stat = node_fs_1.default.existsSync(checkpoint) ? node_fs_1.default.statSync(checkpoint) : null;
|
|
139
|
-
const fingerprint = stat ?
|
|
171
|
+
const fingerprint = stat ? (0, node_crypto_1.createHash)('sha256').update(node_fs_1.default.readFileSync(checkpoint)).digest('hex') : null;
|
|
140
172
|
const cached = this.statusCache.get(runId);
|
|
141
173
|
const state = fingerprint && cached?.fingerprint === fingerprint ? cached.state : await this.dependencies.status(file, cwd, env);
|
|
142
174
|
if (!state || state.runId !== runId)
|
|
@@ -145,13 +177,28 @@ class AgentRuntimeControls {
|
|
|
145
177
|
this.statusCache.set(runId, { fingerprint, state });
|
|
146
178
|
const parent = (0, loop_runs_store_1.getLoopRun)(this.ctx.db, runId);
|
|
147
179
|
const active = this.active.has(runId) || parent?.status === 'running' || parent?.status === 'paused';
|
|
180
|
+
let projection = null;
|
|
181
|
+
try {
|
|
182
|
+
projection = (0, agent_runtime_history_1.readRuntimeHistory)(file);
|
|
183
|
+
}
|
|
184
|
+
catch { /* Invalid advisory history cannot replace live state. */ }
|
|
185
|
+
const superseding = !active && projection && ['failed', 'cancelled', 'running'].includes(projection.status) && Date.parse(projection.updatedAt) > Date.parse(state.updatedAt ?? '1970-01-01') ? projection : null;
|
|
148
186
|
const recoverableSteps = Object.entries(state.steps).filter(([, step]) => ['running', 'interrupted'].includes(step.status)).map(([id]) => id);
|
|
149
|
-
return { runId, status: state.status === 'running' && !active ? 'interrupted' : state.status, nextStep: state.nextStep,
|
|
150
|
-
updatedAt: state.updatedAt, error: this.errors.get(runId) ?? state.error, pendingApproval: state.pendingApproval,
|
|
187
|
+
return { runId, status: superseding ? (superseding.status === 'running' ? 'interrupted' : superseding.status) : state.status === 'running' && !active ? 'interrupted' : state.status, nextStep: state.nextStep,
|
|
188
|
+
updatedAt: superseding?.updatedAt ?? state.updatedAt, error: this.errors.get(runId) ?? superseding?.error ?? state.error, pendingApproval: state.pendingApproval,
|
|
151
189
|
traceId: state.traceId, pendingQuestion: openQuestion(state),
|
|
190
|
+
metrics: (0, agent_runtime_metrics_1.readRuntimeEfficiency)(superseding ? superseding.metrics : state.metrics), efficiencySummary: (0, agent_runtime_metrics_1.readRuntimeEfficiencySummary)(superseding ? superseding.efficiencySummary : state.efficiencySummary),
|
|
191
|
+
canSettle: !superseding && !active && state.status === 'succeeded' && this.ctx.db.prepare('SELECT status FROM jobs WHERE id = ?').get(runId)?.status !== 'completed',
|
|
152
192
|
recoverableSteps, active, canCancel: this.active.has(runId), canResume: !active && parent?.status === 'completed' && state.status !== 'succeeded' };
|
|
153
193
|
}
|
|
154
194
|
catch (error) {
|
|
195
|
+
try {
|
|
196
|
+
const { file } = this.context(runId, true);
|
|
197
|
+
const historical = (0, agent_runtime_history_1.readRuntimeHistory)(file);
|
|
198
|
+
if (historical)
|
|
199
|
+
return { ...historical, runId, status: historical.status === 'running' ? 'interrupted' : historical.status, historical: true, active: this.active.has(runId), canResume: false, canSettle: false, canCancel: this.active.has(runId), recoverableSteps: [], error: error instanceof RuntimeControlError ? error.message : 'Historical result; live runtime verification is unavailable' };
|
|
200
|
+
}
|
|
201
|
+
catch { /* No trustworthy historical projection is available. */ }
|
|
155
202
|
return { runId, status: 'unavailable', nextStep: null, active: this.active.has(runId), canResume: false, canCancel: this.active.has(runId), recoverableSteps: [], error: error instanceof RuntimeControlError ? error.message : 'Could not inspect the saved runtime execution' };
|
|
156
203
|
}
|
|
157
204
|
}
|
|
@@ -161,6 +208,8 @@ class AgentRuntimeControls {
|
|
|
161
208
|
const parent = (0, loop_runs_store_1.getLoopRun)(this.ctx.db, runId);
|
|
162
209
|
if (!parent || parent.status !== 'completed' || this.active.has(runId))
|
|
163
210
|
throw new RuntimeControlError(409, 'runtime_run_active', 'Wait for the original Desktop execution to settle before resuming');
|
|
211
|
+
if (parent.rail_index != null && [...(this.ctx.railLoopRuns?.values() ?? []), ...(this.ctx.railJobs?.values() ?? [])].some(meta => meta.railIndex === parent.rail_index))
|
|
212
|
+
throw new RuntimeControlError(409, 'runtime_rail_active', 'Wait for the implementation card to finish its active job before resuming');
|
|
164
213
|
const context = this.context(runId);
|
|
165
214
|
// Reserve before awaiting status so concurrent resume requests cannot race.
|
|
166
215
|
const active = { cancelled: false };
|
|
@@ -172,6 +221,8 @@ class AgentRuntimeControls {
|
|
|
172
221
|
const state = await this.dependencies.status(context.file, context.cwd, context.env);
|
|
173
222
|
if (!state || state.runId !== runId || state.status === 'succeeded')
|
|
174
223
|
throw new RuntimeControlError(409, 'runtime_not_resumable', 'This execution has no resumable workflow');
|
|
224
|
+
if (parent.rail_index != null && [...(this.ctx.railLoopRuns?.values() ?? []), ...(this.ctx.railJobs?.values() ?? [])].some(meta => meta.railIndex === parent.rail_index))
|
|
225
|
+
throw new RuntimeControlError(409, 'runtime_rail_active', 'The implementation card became active while checking the saved execution');
|
|
175
226
|
// A pending question resumes the architect only with the operator's answer.
|
|
176
227
|
if (openQuestion(state) && !input.answer)
|
|
177
228
|
throw new RuntimeControlError(400, 'answer_required', 'This execution is waiting for an answer to the architect\'s question');
|
|
@@ -206,7 +257,20 @@ class AgentRuntimeControls {
|
|
|
206
257
|
(0, db_1.appendEvent)(this.ctx.db, runId, ++sequence, { event_type: 'log', source, payload: JSON.stringify({ line: line.replace(/\n$/, '') }) });
|
|
207
258
|
}
|
|
208
259
|
catch { /* events table best-effort */ }
|
|
260
|
+
try {
|
|
261
|
+
this.ctx.broadcast?.({ type: 'log', source, line, timestamp: new Date().toISOString(), processId: runId });
|
|
262
|
+
}
|
|
263
|
+
catch { /* persisted log remains authoritative */ }
|
|
264
|
+
};
|
|
265
|
+
if (parent.rail_index != null)
|
|
266
|
+
this.ctx.railLoopRuns?.set(runId, { railIndex: parent.rail_index, ticketIds, requiresTerminalIntent: true });
|
|
267
|
+
const publishActivity = (running) => {
|
|
268
|
+
try {
|
|
269
|
+
this.ctx.broadcast?.({ type: 'runtime.continuation', projectId: this.ctx.project.id, jobId: runId, railIndex: parent.rail_index, active: running });
|
|
270
|
+
}
|
|
271
|
+
catch { /* durable log remains available */ }
|
|
209
272
|
};
|
|
273
|
+
publishActivity(true);
|
|
210
274
|
const requested = [...(input.approve?.length ? ['approve ' + input.approve.join(',')] : []), ...(input.recover?.length ? ['recover ' + input.recover.join(',')] : []), ...(input.invalidate?.length ? ['invalidate ' + input.invalidate.join(',')] : []), ...(input.answer ? ['answered question'] : [])];
|
|
211
275
|
logLine(`[runtime] continuation started from phase ${state.nextStep ?? 'end'}${requested.length ? ' (' + requested.join('; ') + ')' : ''}; the original worktree and frozen scope are reused`);
|
|
212
276
|
void this.dependencies.execute({
|
|
@@ -223,10 +287,20 @@ class AgentRuntimeControls {
|
|
|
223
287
|
eventType = parsed.type;
|
|
224
288
|
}
|
|
225
289
|
catch { /* Keep raw diagnostics available to recovery. */ }
|
|
290
|
+
if (eventType === 'runtime-efficiency-event') {
|
|
291
|
+
const validated = (0, agent_runtime_events_1.runtimeEfficiencyEventLine)(line, runId);
|
|
292
|
+
if (!validated || (0, agent_runtime_events_1.isRecordedRuntimeEfficiencyEvent)(this.ctx.db, runId, validated))
|
|
293
|
+
return;
|
|
294
|
+
line = validated;
|
|
295
|
+
}
|
|
226
296
|
this.ctx.db.transaction(() => {
|
|
227
297
|
(0, db_1.appendEvent)(this.ctx.db, runId, ++sequence, { event_type: eventType, source: 'stdout', payload: line });
|
|
228
298
|
(0, loop_runs_store_1.updateLoopStepActivityCheckpoint)(this.ctx.db, runId, stepKey, undefined, Date.now());
|
|
229
299
|
})();
|
|
300
|
+
try {
|
|
301
|
+
this.ctx.broadcast?.({ type: 'event', jobId: runId, event_type: eventType, source: 'stdout', payload: line, seq: sequence, timestamp: new Date().toISOString() });
|
|
302
|
+
}
|
|
303
|
+
catch { /* persisted event remains authoritative */ }
|
|
230
304
|
},
|
|
231
305
|
onSpawn: (child) => {
|
|
232
306
|
active.child = child;
|
|
@@ -235,7 +309,7 @@ class AgentRuntimeControls {
|
|
|
235
309
|
else if (active.cancelled)
|
|
236
310
|
this.cancel(runId);
|
|
237
311
|
},
|
|
238
|
-
}).then((result) => {
|
|
312
|
+
}).then(async (result) => {
|
|
239
313
|
if (this.disposed)
|
|
240
314
|
return;
|
|
241
315
|
logLine(result.failed ? `[runtime] continuation ended: ${result.errorText ?? 'failed'}` : '[runtime] continuation completed', result.failed ? 'stderr' : 'stdout');
|
|
@@ -243,14 +317,51 @@ class AgentRuntimeControls {
|
|
|
243
317
|
(0, loop_run_manager_1.recoverOrphanLoopStepAccounting)(this.ctx.db, new Date().toISOString(), runId);
|
|
244
318
|
if (result.errorText)
|
|
245
319
|
this.errors.set(runId, result.errorText);
|
|
246
|
-
|
|
247
|
-
|
|
320
|
+
if (!result.failed && !active.cancelled)
|
|
321
|
+
await this.dependencies.settle?.({ db: this.ctx.db, projectId: this.ctx.project.id, runId, contextPath: context.file, cwd: context.cwd, env: context.env, broadcast: this.ctx.broadcast });
|
|
322
|
+
}).catch(() => {
|
|
323
|
+
if (this.disposed)
|
|
324
|
+
return;
|
|
325
|
+
const message = 'Runtime continuation failed. Inspect the saved phase state before retrying.';
|
|
326
|
+
this.errors.set(runId, message);
|
|
327
|
+
logLine(`[runtime] ${message}`, 'stderr');
|
|
328
|
+
})
|
|
329
|
+
.finally(() => {
|
|
330
|
+
clearTimeout(active.forceKillTimer);
|
|
331
|
+
this.active.delete(runId);
|
|
332
|
+
this.ctx.railLoopRuns?.delete(runId);
|
|
333
|
+
this.statusCache.delete(runId);
|
|
334
|
+
publishActivity(false);
|
|
335
|
+
});
|
|
248
336
|
}
|
|
249
337
|
catch (error) {
|
|
250
338
|
this.active.delete(runId);
|
|
339
|
+
this.ctx.railLoopRuns?.delete(runId);
|
|
340
|
+
try {
|
|
341
|
+
this.ctx.broadcast?.({ type: 'runtime.continuation', projectId: this.ctx.project.id, jobId: runId, railIndex: parent.rail_index, active: false });
|
|
342
|
+
}
|
|
343
|
+
catch { /* admission failed */ }
|
|
251
344
|
throw error;
|
|
252
345
|
}
|
|
253
346
|
}
|
|
347
|
+
async settle(runId) {
|
|
348
|
+
if (this.active.has(runId) || (0, loop_runs_store_1.getLoopRun)(this.ctx.db, runId)?.status !== 'completed')
|
|
349
|
+
throw new RuntimeControlError(409, 'runtime_run_active', 'Wait for the execution to settle');
|
|
350
|
+
const context = this.context(runId);
|
|
351
|
+
this.active.set(runId, { cancelled: false });
|
|
352
|
+
try {
|
|
353
|
+
await (this.dependencies.settle ?? agent_runtime_settlement_1.settleRuntimeContinuation)({ db: this.ctx.db, projectId: this.ctx.project.id, runId, contextPath: context.file, cwd: context.cwd, env: context.env, broadcast: this.ctx.broadcast });
|
|
354
|
+
this.errors.delete(runId);
|
|
355
|
+
}
|
|
356
|
+
catch (error) {
|
|
357
|
+
throw new RuntimeControlError(409, 'runtime_settlement_blocked', error instanceof Error ? error.message : 'Could not prepare the recovered delivery');
|
|
358
|
+
}
|
|
359
|
+
finally {
|
|
360
|
+
this.active.delete(runId);
|
|
361
|
+
this.statusCache.delete(runId);
|
|
362
|
+
this.ctx.broadcast?.({ type: 'runtime.continuation', projectId: this.ctx.project.id, jobId: runId, railIndex: (0, loop_runs_store_1.getLoopRun)(this.ctx.db, runId)?.rail_index ?? null, active: false });
|
|
363
|
+
}
|
|
364
|
+
}
|
|
254
365
|
cancel(runId) {
|
|
255
366
|
const active = this.active.get(runId);
|
|
256
367
|
if (!active)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveEffectiveRuntimeConfig = resolveEffectiveRuntimeConfig;
|
|
4
|
+
const providers_1 = require("./providers");
|
|
5
|
+
/** A pure admission resolver; only deliberate launch intent overrides a project role. */
|
|
6
|
+
function resolveEffectiveRuntimeConfig(input, options) {
|
|
7
|
+
const config = structuredClone(input);
|
|
8
|
+
const override = options.providerOverride;
|
|
9
|
+
if (override) {
|
|
10
|
+
const provider = config.providers.find(entry => entry.id === override.provider);
|
|
11
|
+
if (!provider)
|
|
12
|
+
throw new Error(`Selected runtime provider is not configured: ${override.provider}`);
|
|
13
|
+
for (const role of ['architect', 'developer', 'reviewer']) {
|
|
14
|
+
const current = config.agents[role];
|
|
15
|
+
const changedProvider = current.provider !== provider.id;
|
|
16
|
+
const changedModel = override.model !== undefined && override.model !== current.model;
|
|
17
|
+
config.agents[role] = {
|
|
18
|
+
...current, provider: provider.id,
|
|
19
|
+
model: override.model ?? (changedProvider ? undefined : current.model),
|
|
20
|
+
effort: override.effort ?? (changedProvider || changedModel ? undefined : current.effort),
|
|
21
|
+
escalation: changedProvider || changedModel ? undefined : current.escalation,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
for (const agent of Object.values(config.agents)) {
|
|
26
|
+
const provider = config.providers.find(entry => entry.id === agent.provider);
|
|
27
|
+
if (!agent.model && provider?.kind === 'cli')
|
|
28
|
+
agent.model = (0, providers_1.getAdapter)(provider.cli).defaultModel();
|
|
29
|
+
}
|
|
30
|
+
config.verification = config.verification.filter(check => options.repositoryIds.includes(check.repositoryId));
|
|
31
|
+
return { config, origins: { architect: override ? 'explicit-launch-override' : options.source, developer: override ? 'explicit-launch-override' : options.source, reviewer: override ? 'explicit-launch-override' : options.source } };
|
|
32
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runtimeEfficiencyEventLine = runtimeEfficiencyEventLine;
|
|
4
|
+
exports.isRecordedRuntimeEfficiencyEvent = isRecordedRuntimeEfficiencyEvent;
|
|
5
|
+
/** Efficiency events are advisory measurements, never workflow or delivery authority. */
|
|
6
|
+
function runtimeEfficiencyEventLine(line, runId) {
|
|
7
|
+
if (line.length > 16384)
|
|
8
|
+
return null;
|
|
9
|
+
let event;
|
|
10
|
+
try {
|
|
11
|
+
event = JSON.parse(line);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (event?.type !== 'runtime-efficiency-event' || event.schemaVersion !== 1 || event.runId !== runId || typeof event.eventId !== 'string' || !event.eventId.startsWith(runId + ':') || event.eventId.length > 256 || typeof event.attemptId !== 'string' || event.attemptId.length > 256 || !['role-context', 'role-route', 'check-started', 'check-finished', 'check-reused', 'check-invalidated'].includes(String(event.kind)))
|
|
17
|
+
return null;
|
|
18
|
+
if (!event.payload || typeof event.payload !== 'object' || Array.isArray(event.payload))
|
|
19
|
+
return null;
|
|
20
|
+
const raw = event.payload, payload = {};
|
|
21
|
+
for (const key of ['invocationId', 'provider', 'model', 'requestedEffort', 'kind', 'tier', 'routeReason', 'contextMode', 'status', 'executionId', 'repositoryId', 'checkId', 'label', 'reason']) {
|
|
22
|
+
const value = raw[key];
|
|
23
|
+
if (value === undefined)
|
|
24
|
+
continue;
|
|
25
|
+
if (value !== null && (typeof value !== 'string' || value.length > 256))
|
|
26
|
+
return null;
|
|
27
|
+
payload[key] = value;
|
|
28
|
+
}
|
|
29
|
+
for (const key of ['ordinal', 'promptBytes', 'contextBytes', 'handoffBytes', 'durationMs', 'toolCalls']) {
|
|
30
|
+
if (raw[key] === undefined)
|
|
31
|
+
continue;
|
|
32
|
+
if (typeof raw[key] !== 'number' || !Number.isFinite(raw[key]) || raw[key] < 0)
|
|
33
|
+
return null;
|
|
34
|
+
payload[key] = raw[key];
|
|
35
|
+
}
|
|
36
|
+
if (raw.exitCode !== undefined) {
|
|
37
|
+
if (typeof raw.exitCode !== 'number' || !Number.isSafeInteger(raw.exitCode))
|
|
38
|
+
return null;
|
|
39
|
+
payload.exitCode = raw.exitCode;
|
|
40
|
+
}
|
|
41
|
+
return JSON.stringify({ type: event.type, schemaVersion: 1, eventId: event.eventId, runId, attemptId: event.attemptId, kind: event.kind, payload });
|
|
42
|
+
}
|
|
43
|
+
function isRecordedRuntimeEfficiencyEvent(db, runId, line) {
|
|
44
|
+
const event = JSON.parse(line);
|
|
45
|
+
return Boolean(db.prepare("SELECT 1 FROM events WHERE job_id = ? AND event_type = 'runtime-efficiency-event' AND CASE WHEN json_valid(payload) THEN json_extract(payload, '$.eventId') END = ? LIMIT 1").get(runId, event.eventId));
|
|
46
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
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.writeRuntimeHistory = writeRuntimeHistory;
|
|
7
|
+
exports.readRuntimeHistory = readRuntimeHistory;
|
|
8
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const node_crypto_1 = require("node:crypto");
|
|
11
|
+
const agent_runtime_metrics_1 = require("./agent-runtime-metrics");
|
|
12
|
+
const hash = (value) => (0, node_crypto_1.createHash)('sha256').update(value).digest('hex');
|
|
13
|
+
/** A replaceable projection, not an authority to resume, accept or deliver code. */
|
|
14
|
+
function writeRuntimeHistory(contextPath, input) {
|
|
15
|
+
const contextText = node_fs_1.default.readFileSync(contextPath, 'utf8'), context = JSON.parse(contextText);
|
|
16
|
+
let selection;
|
|
17
|
+
try {
|
|
18
|
+
selection = JSON.parse(node_fs_1.default.readFileSync(node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-selection.json'), 'utf8'));
|
|
19
|
+
}
|
|
20
|
+
catch { /* Old hosts have no explicit provenance. */ }
|
|
21
|
+
const result = { runId: context.runId, status: typeof input.status === 'string' ? input.status : 'failed', nextStep: typeof input.nextStep === 'string' ? input.nextStep : null,
|
|
22
|
+
updatedAt: new Date().toISOString(), ...(typeof input.error === 'string' ? { error: input.error.slice(0, 4000) } : {}),
|
|
23
|
+
metrics: (0, agent_runtime_metrics_1.readRuntimeEfficiency)(input.metrics), efficiencySummary: (0, agent_runtime_metrics_1.applyRuntimeSelectionOrigins)((0, agent_runtime_metrics_1.readRuntimeEfficiencySummary)(input.efficiencySummary), selection, context.runId), steps: {} };
|
|
24
|
+
const payload = JSON.stringify({ schemaVersion: 1, contextHash: hash(contextText), result });
|
|
25
|
+
const file = node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-history.json'), temporary = file + '.' + (0, node_crypto_1.randomUUID)() + '.tmp';
|
|
26
|
+
try {
|
|
27
|
+
node_fs_1.default.writeFileSync(temporary, JSON.stringify({ payload, integrity: hash(payload) }), { mode: 0o600, flag: 'wx' });
|
|
28
|
+
node_fs_1.default.renameSync(temporary, file);
|
|
29
|
+
}
|
|
30
|
+
finally {
|
|
31
|
+
node_fs_1.default.rmSync(temporary, { force: true });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function readRuntimeHistory(contextPath) {
|
|
35
|
+
const file = node_path_1.default.join(node_path_1.default.dirname(contextPath), 'desktop-runtime-history.json');
|
|
36
|
+
if (!node_fs_1.default.existsSync(file))
|
|
37
|
+
return null;
|
|
38
|
+
if (node_fs_1.default.lstatSync(file).isSymbolicLink() || node_fs_1.default.statSync(file).size > 128 * 1024)
|
|
39
|
+
throw new Error('Historical runtime projection is invalid');
|
|
40
|
+
const envelope = JSON.parse(node_fs_1.default.readFileSync(file, 'utf8'));
|
|
41
|
+
if (typeof envelope.payload !== 'string' || hash(envelope.payload) !== envelope.integrity)
|
|
42
|
+
throw new Error('Historical runtime projection integrity failed');
|
|
43
|
+
const value = JSON.parse(envelope.payload), contextText = node_fs_1.default.readFileSync(contextPath, 'utf8');
|
|
44
|
+
if (value.schemaVersion !== 1 || value.contextHash !== hash(contextText) || value.result?.runId !== JSON.parse(contextText).runId || !['running', 'succeeded', 'paused', 'failed', 'blocked', 'cancelled'].includes(value.result.status))
|
|
45
|
+
throw new Error('Historical runtime projection scope changed');
|
|
46
|
+
return { ...value.result, metrics: (0, agent_runtime_metrics_1.readRuntimeEfficiency)(value.result.metrics), efficiencySummary: (0, agent_runtime_metrics_1.readRuntimeEfficiencySummary)(value.result.efficiencySummary), steps: {} };
|
|
47
|
+
}
|
|
@@ -3,6 +3,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.findCoreAgentRuntimeEntry = findCoreAgentRuntimeEntry;
|
|
4
4
|
exports.resetCoreAgentRuntimeApiCache = resetCoreAgentRuntimeApiCache;
|
|
5
5
|
exports.loadCoreAgentRuntime = loadCoreAgentRuntime;
|
|
6
|
+
exports.requireRuntimeCapabilities = requireRuntimeCapabilities;
|
|
7
|
+
exports.validateRoleCapabilities = validateRoleCapabilities;
|
|
8
|
+
exports.validateRequestedRoleEfforts = validateRequestedRoleEfforts;
|
|
6
9
|
exports.findCoreAgentRuntimeCli = findCoreAgentRuntimeCli;
|
|
7
10
|
const node_fs_1 = require("node:fs");
|
|
8
11
|
const node_child_process_1 = require("node:child_process");
|
|
@@ -11,6 +14,7 @@ const node_path_1 = require("node:path");
|
|
|
11
14
|
const core_runtime_1 = require("./core-runtime");
|
|
12
15
|
const core_node_runtime_1 = require("./core-node-runtime");
|
|
13
16
|
const win_spawn_1 = require("./util/win-spawn");
|
|
17
|
+
const agent_runtime_package_1 = require("./agent-runtime-package");
|
|
14
18
|
/** An explicit or bundled installation is authoritative: never silently replace
|
|
15
19
|
* a broken production bundle with a developer's checkout. */
|
|
16
20
|
function findCoreAgentRuntimeEntry() {
|
|
@@ -42,7 +46,7 @@ function findCoreAgentRuntimeEntry() {
|
|
|
42
46
|
}
|
|
43
47
|
return null;
|
|
44
48
|
}
|
|
45
|
-
/** The api probe is stable for
|
|
49
|
+
/** The api probe is stable for executable package content; settings saves need not respawn it.
|
|
46
50
|
* Validation stays uncached because its answer depends on the submitted configuration. */
|
|
47
51
|
const apiProbeCache = new Map();
|
|
48
52
|
function resetCoreAgentRuntimeApiCache() { apiProbeCache.clear(); }
|
|
@@ -57,18 +61,42 @@ async function loadCoreAgentRuntime() {
|
|
|
57
61
|
cwd: (0, node_path_1.dirname)(cli), env: (0, win_spawn_1.windowsSpawnEnv)(process.env), input, encoding: 'utf8',
|
|
58
62
|
timeout: 15_000, maxBuffer: 2 * 1024 * 1024, windowsHide: true, stdio: ['pipe', 'pipe', 'pipe'],
|
|
59
63
|
});
|
|
60
|
-
const
|
|
64
|
+
const fingerprint = (0, agent_runtime_package_1.runtimeEntryFingerprint)(cli);
|
|
61
65
|
const cached = apiProbeCache.get(cli);
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
let api = cached?.fingerprint === fingerprint ? cached.api : undefined;
|
|
67
|
+
if (!api) {
|
|
68
|
+
api = JSON.parse(invoke(['api']));
|
|
64
69
|
if (api.type !== 'runtime-api' || api.apiVersion !== 1) {
|
|
65
70
|
throw new Error('Incompatible Core agent runtime API; expected version 1. Update the paired Core bundle.');
|
|
66
71
|
}
|
|
67
|
-
|
|
72
|
+
if (api.capabilities !== undefined && (!api.capabilities || typeof api.capabilities !== 'object' || Array.isArray(api.capabilities) || Object.values(api.capabilities).some(value => !Number.isSafeInteger(value) || value < 1)))
|
|
73
|
+
throw new Error('Core returned malformed runtime capabilities');
|
|
74
|
+
if (api.workflowVersions !== undefined && (!Array.isArray(api.workflowVersions) || !api.workflowVersions.every(value => typeof value === 'string' && /^\d+$/.test(value))))
|
|
75
|
+
throw new Error('Core returned malformed workflow versions');
|
|
76
|
+
if (api.runtimeIdentity !== undefined) {
|
|
77
|
+
const identity = api.runtimeIdentity;
|
|
78
|
+
if (!identity || identity.apiVersion !== 1 || !['packageVersion', 'workflowVersion', 'instructionsVersion', 'packageIntegrity'].every(key => typeof identity[key] === 'string') || !/^sha256:[a-f0-9]{64}$/.test(identity.packageIntegrity))
|
|
79
|
+
throw new Error('Core returned malformed runtime identity');
|
|
80
|
+
}
|
|
81
|
+
apiProbeCache.set(cli, { fingerprint, api });
|
|
68
82
|
}
|
|
69
83
|
return {
|
|
70
84
|
RUNTIME_API_VERSION: 1,
|
|
85
|
+
api,
|
|
86
|
+
capabilities(input) {
|
|
87
|
+
if (api.capabilities?.efficientRoleExecution !== 1)
|
|
88
|
+
throw new Error('Installed Core does not support role capability introspection. Update the paired Core package.');
|
|
89
|
+
const result = JSON.parse(invoke(['capabilities', '--stdin'], JSON.stringify(input)));
|
|
90
|
+
return { ...validateRoleCapabilities(result, input), runtimeIdentity: api.runtimeIdentity };
|
|
91
|
+
},
|
|
92
|
+
rolePromptDefaults() {
|
|
93
|
+
const result = JSON.parse(invoke(['prompts']));
|
|
94
|
+
if (result.type !== 'runtime-role-prompts' || !['architect', 'developer', 'reviewer'].every(role => typeof result.defaults?.[role] === 'string' && result.defaults[role].trim()))
|
|
95
|
+
throw new Error('Core role prompt catalog is unavailable. Update the paired Core bundle.');
|
|
96
|
+
return result.defaults;
|
|
97
|
+
},
|
|
71
98
|
validateRuntimeConfig(input) {
|
|
99
|
+
requireRuntimeCapabilities(api, input);
|
|
72
100
|
const result = JSON.parse(invoke(['validate', '--stdin'], JSON.stringify(input)));
|
|
73
101
|
if (result.type !== 'runtime-config-valid')
|
|
74
102
|
throw new Error('Core did not validate the runtime configuration');
|
|
@@ -76,6 +104,63 @@ async function loadCoreAgentRuntime() {
|
|
|
76
104
|
},
|
|
77
105
|
};
|
|
78
106
|
}
|
|
107
|
+
function requireRuntimeCapabilities(api, input) {
|
|
108
|
+
if (!input || typeof input !== 'object' || Array.isArray(input))
|
|
109
|
+
return;
|
|
110
|
+
const config = input;
|
|
111
|
+
if ((config.efficiency !== undefined || Object.values(config.agents ?? {}).some(role => role?.effort !== undefined || role?.escalation !== undefined)) && api.capabilities?.efficientRoleExecution !== 1)
|
|
112
|
+
throw new Error('Requested role efficiency policy is unsupported by this Core runtime. Update the paired Core package.');
|
|
113
|
+
if (Array.isArray(config.verification) && config.verification.some(check => check?.policy !== undefined || check?.key !== undefined || check?.label !== undefined) && api.capabilities?.reproducibleVerification !== 1)
|
|
114
|
+
throw new Error('Requested verification policy is unsupported by this Core runtime. Update the paired Core package.');
|
|
115
|
+
}
|
|
116
|
+
/** Validate transport evidence against the exact submitted role selections. */
|
|
117
|
+
function validateRoleCapabilities(value, input) {
|
|
118
|
+
const invalid = () => { throw new Error('Core returned malformed role capabilities'); };
|
|
119
|
+
const object = (value) => value && typeof value === 'object' && !Array.isArray(value) ? value : invalid();
|
|
120
|
+
const result = object(value), config = object(input);
|
|
121
|
+
const choices = Object.entries(object(config.agents));
|
|
122
|
+
if (result.type !== 'runtime-capabilities' || result.schemaVersion !== 1 || !Array.isArray(result.roles) || result.roles.length > 6)
|
|
123
|
+
return invalid();
|
|
124
|
+
const expected = choices.flatMap(([role, value]) => {
|
|
125
|
+
const selected = object(value);
|
|
126
|
+
return [['base', selected], ...(selected.escalation ? [['escalation', object(selected.escalation)]] : [])].map(([tier, choice]) => {
|
|
127
|
+
const model = choice;
|
|
128
|
+
return { role, tier: String(tier), provider: selected.provider, model: model.model ?? null, requestedEffort: model.effort ?? null };
|
|
129
|
+
});
|
|
130
|
+
});
|
|
131
|
+
if (expected.length !== result.roles.length || expected.length < 3)
|
|
132
|
+
return invalid();
|
|
133
|
+
const seen = new Set();
|
|
134
|
+
const roles = result.roles.map(value => {
|
|
135
|
+
const row = object(value);
|
|
136
|
+
const selected = expected.find(item => Object.entries(item).every(([key, value]) => row[key] === value));
|
|
137
|
+
if (!selected)
|
|
138
|
+
return invalid();
|
|
139
|
+
const key = String(row.role) + ':' + String(row.tier);
|
|
140
|
+
if (seen.has(key))
|
|
141
|
+
return invalid();
|
|
142
|
+
seen.add(key);
|
|
143
|
+
if (typeof row.transport !== 'string' || !row.transport || row.transport.length > 128 || !['supported', 'unsupported', 'unknown'].includes(String(row.continuation)) || !['supported', 'unsupported', 'unknown'].includes(String(row.effortSupport)) || typeof row.observedModel !== 'boolean' || typeof row.observedEffort !== 'boolean')
|
|
144
|
+
return invalid();
|
|
145
|
+
if (row.supportedEfforts !== null && (!Array.isArray(row.supportedEfforts) || row.supportedEfforts.length > 32 || !row.supportedEfforts.every((level) => typeof level === 'string' && /^[a-z][a-z0-9_-]{0,31}$/.test(level))))
|
|
146
|
+
return invalid();
|
|
147
|
+
const levels = row.supportedEfforts;
|
|
148
|
+
if (row.effortSupport === 'supported' && !levels?.length || row.effortSupport === 'unknown' && levels !== null || row.effortSupport === 'unsupported' && levels?.length !== 0)
|
|
149
|
+
return invalid();
|
|
150
|
+
return { ...selected, transport: row.transport, continuation: row.continuation, effortSupport: row.effortSupport, supportedEfforts: levels, observedModel: row.observedModel, observedEffort: row.observedEffort };
|
|
151
|
+
});
|
|
152
|
+
return { type: 'runtime-capabilities', schemaVersion: 1, roles };
|
|
153
|
+
}
|
|
154
|
+
function validateRequestedRoleEfforts(runtime, config) {
|
|
155
|
+
if (!Object.values(config.agents).some(role => role.effort !== undefined || role.escalation?.effort !== undefined))
|
|
156
|
+
return;
|
|
157
|
+
if (!runtime.capabilities)
|
|
158
|
+
throw new Error('Installed Core cannot validate requested role effort');
|
|
159
|
+
const result = validateRoleCapabilities(runtime.capabilities(config), config);
|
|
160
|
+
for (const row of result.roles)
|
|
161
|
+
if (row.requestedEffort !== null && (row.effortSupport !== 'supported' || !row.supportedEfforts?.includes(String(row.requestedEffort))))
|
|
162
|
+
throw new Error(`${row.role} (${row.tier}): effort '${row.requestedEffort}' is not confirmed for ${row.transport}. Select provider default or a supported effort.`);
|
|
163
|
+
}
|
|
79
164
|
function findCoreAgentRuntimeCli() {
|
|
80
165
|
const entry = findCoreAgentRuntimeEntry();
|
|
81
166
|
if (!entry)
|