chati-dev 4.5.15 → 4.5.27

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 (124) hide show
  1. package/README.md +8 -3
  2. package/bin/chati.js +124 -66
  3. package/framework/agents/build/dev.md +5 -5
  4. package/framework/agents/deploy/devops.md +7 -7
  5. package/framework/agents/discover/brief.md +4 -4
  6. package/framework/agents/discover/brownfield-wu.md +3 -3
  7. package/framework/agents/discover/greenfield-wu.md +3 -3
  8. package/framework/agents/plan/architect.md +2 -2
  9. package/framework/agents/plan/detail.md +4 -4
  10. package/framework/agents/plan/phases.md +2 -2
  11. package/framework/agents/plan/tasks.md +2 -2
  12. package/framework/agents/plan/ux.md +2 -2
  13. package/framework/agents/quality/qa-implementation.md +11 -10
  14. package/framework/agents/quality/qa-planning.md +3 -3
  15. package/framework/agents/quality/qa-visual.md +1 -1
  16. package/framework/config.yaml +3 -3
  17. package/framework/constitution.md +18 -18
  18. package/framework/context/protocols.md +2 -2
  19. package/framework/context/quality.md +1 -1
  20. package/framework/context/root.md +4 -4
  21. package/framework/data/entity-registry.yaml +1 -1
  22. package/framework/domains/agents/orchestrator.yaml +2 -2
  23. package/framework/domains/constitution.yaml +1 -1
  24. package/framework/hooks/advance-trigger.js +4 -6
  25. package/framework/hooks/git-push-authority.js +45 -37
  26. package/framework/hooks/mode-governance.js +149 -40
  27. package/framework/hooks/model-governance.js +13 -20
  28. package/framework/hooks/prism-engine.js +74 -92
  29. package/framework/hooks/reasoning-escalator.js +23 -40
  30. package/framework/hooks/session-digest.js +12 -13
  31. package/framework/hooks/session-reader.js +224 -0
  32. package/framework/hooks/session-writer.js +195 -0
  33. package/framework/hooks/team-quality-gate.js +34 -24
  34. package/framework/i18n/en.yaml +2 -2
  35. package/framework/i18n/es.yaml +2 -2
  36. package/framework/i18n/fr.yaml +2 -2
  37. package/framework/i18n/pt.yaml +2 -2
  38. package/framework/intelligence/context-engine.md +4 -4
  39. package/framework/intelligence/memory-layer.md +1 -1
  40. package/framework/manifest.json +129 -119
  41. package/framework/manifest.sig +1 -1
  42. package/framework/orchestrator/chati-router.js +278 -24
  43. package/framework/orchestrator/chati.md +250 -68
  44. package/framework/schemas/session.schema.json +21 -1
  45. package/framework/tasks/orchestrator-deviation.md +1 -1
  46. package/framework/tasks/orchestrator-escalate.md +1 -1
  47. package/framework/tasks/orchestrator-handoff.md +6 -6
  48. package/framework/tasks/orchestrator-health.md +5 -9
  49. package/framework/tasks/orchestrator-mode-switch.md +3 -7
  50. package/framework/tasks/orchestrator-resume.md +10 -14
  51. package/framework/tasks/orchestrator-route.md +3 -3
  52. package/framework/tasks/orchestrator-spawn-terminal.md +1 -1
  53. package/framework/tasks/orchestrator-status.md +9 -9
  54. package/framework/tasks/orchestrator-suggest-mode.md +1 -1
  55. package/framework/tasks/qa-impl-consolidate.md +2 -2
  56. package/framework/tasks/qa-impl-performance-test.md +4 -4
  57. package/framework/tasks/qa-impl-regression-check.md +4 -4
  58. package/framework/tasks/qa-impl-sast-scan.md +1 -1
  59. package/framework/tasks/qa-impl-test-execute.md +1 -1
  60. package/framework/tasks/qa-impl-verdict.md +2 -2
  61. package/framework/tasks/qa-planning-consolidate.md +3 -3
  62. package/framework/tasks/qa-planning-coverage-plan.md +2 -2
  63. package/framework/tasks/qa-planning-gate-define.md +4 -4
  64. package/framework/tasks/qa-planning-risk-matrix.md +4 -4
  65. package/framework/tasks/qa-planning-test-strategy.md +2 -2
  66. package/node_modules/@chati/browser-capability/src/index.js +12 -2
  67. package/node_modules/@chati/planning/src/index.js +24 -4
  68. package/node_modules/@chati/provider-registry/src/index.js +11 -0
  69. package/node_modules/@chati/rail/src/index.js +1967 -83
  70. package/node_modules/@chati/release-lane/README.md +12 -8
  71. package/node_modules/@chati/release-lane/package.json +1 -1
  72. package/node_modules/@chati/release-lane/src/index.js +1426 -58
  73. package/node_modules/@chati/review-council/src/index.js +63 -0
  74. package/node_modules/@chati/tracking-clickup/README.md +13 -0
  75. package/node_modules/@chati/tracking-clickup/src/index.js +690 -30
  76. package/package-artifact-manifest.json +1 -0
  77. package/package-artifact-manifest.sig +1 -0
  78. package/package.json +16 -7
  79. package/scripts/verify-real-harness-e2e.js +1581 -0
  80. package/src/config/framework-adapter.js +4 -4
  81. package/src/installer/core.js +157 -56
  82. package/src/installer/manifest.js +140 -9
  83. package/src/installer/package-artifact.js +249 -0
  84. package/src/installer/templates.js +91 -23
  85. package/src/installer-v2/catalog-client.js +531 -23
  86. package/src/installer-v2/index.js +91 -34
  87. package/src/installer-v2/installation-authority.js +327 -0
  88. package/src/installer-v2/provider-executable.js +235 -0
  89. package/src/installer-v2/wizard-installation.js +1 -1
  90. package/src/intelligence/timeline.js +3 -1
  91. package/src/orchestrator/browser-runtime.js +44 -13
  92. package/src/orchestrator/cli.js +1616 -151
  93. package/src/orchestrator/clickup-projection.js +43 -1
  94. package/src/orchestrator/clickup-runtime.js +355 -39
  95. package/src/orchestrator/doctor.js +87 -4
  96. package/src/orchestrator/index.js +16 -0
  97. package/src/orchestrator/planning-runtime.js +20 -2
  98. package/src/orchestrator/rail-adjudication-evidence.js +234 -0
  99. package/src/orchestrator/rail-evidence-authority.js +147 -0
  100. package/src/orchestrator/rail-execution-evidence.js +45 -0
  101. package/src/orchestrator/rail-runtime.js +836 -56
  102. package/src/orchestrator/release-runtime.js +65 -6
  103. package/src/orchestrator/review-runtime.js +186 -41
  104. package/src/orchestrator/runtime-installation-v2.js +236 -20
  105. package/src/orchestrator/session-manager.js +1249 -52
  106. package/src/terminal/adapters/claude-adapter.js +3 -1
  107. package/src/terminal/adapters/codex-adapter.js +2 -0
  108. package/src/terminal/adapters/grok-adapter.js +7 -4
  109. package/src/terminal/handoff-parser.js +19 -1
  110. package/src/terminal/prompt-builder.js +10 -1
  111. package/src/terminal/provider-preflight.js +36 -3
  112. package/src/terminal/rail-execution-worktree.js +213 -0
  113. package/src/terminal/rail-prompts.js +169 -0
  114. package/src/terminal/rail-readonly-workspace.js +324 -0
  115. package/src/terminal/run-agent.js +434 -16
  116. package/src/terminal/run-parallel.js +5 -0
  117. package/src/terminal/run-rail-adjudication.js +323 -0
  118. package/src/terminal/run-rail-review.js +291 -0
  119. package/src/terminal/run-rail-rework.js +325 -0
  120. package/src/terminal/run-rail-task.js +380 -0
  121. package/src/terminal/run-team.js +5 -0
  122. package/src/terminal/spawner.js +1225 -77
  123. package/src/utils/schema-validator.js +5 -0
  124. package/src/wizard/index.js +3 -2
@@ -0,0 +1,380 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { randomUUID } from 'node:crypto';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { resolve } from 'node:path';
6
+ import {
7
+ buildSpawnCommand, getTerminalCompletionAuthorityReceipt,
8
+ getTerminalLaunchAuthorityReceipt, isTransientFailure, killTerminal, spawnTerminal, waitForTerminal,
9
+ } from './spawner.js';
10
+ import { checkProviderReadiness } from './provider-preflight.js';
11
+ import { getRateLimiter } from './rate-limiter.js';
12
+ import {
13
+ dispatchReadyRailTask, heartbeatRailTask, loadRailHandoff, recordRailExecutionEvidence,
14
+ relinquishRailTask, resolveRailAttemptContext,
15
+ } from '../orchestrator/rail-runtime.js';
16
+ import { parseRailExecutionEnvelope } from '../orchestrator/rail-execution-evidence.js';
17
+ import {
18
+ loadRuntimeInstallationV2, resolveRuntimeExecutableBindingV2,
19
+ } from '../orchestrator/runtime-installation-v2.js';
20
+ export { buildRailTaskPrompt } from './rail-prompts.js';
21
+
22
+ function fail(code, message, details = {}) {
23
+ const error = new Error(message);
24
+ error.code = code;
25
+ error.details = details;
26
+ throw error;
27
+ }
28
+
29
+ function exactTask({ projectDir, handoff_id, task_id }) {
30
+ const stored = loadRailHandoff({ projectDir, handoff_id });
31
+ const task = stored.handoff.tasks.find((candidate) => candidate.task_id === task_id);
32
+ if (!task) fail('TASK_NOT_FOUND', `task ${task_id} is not part of sealed handoff ${handoff_id}`);
33
+ return Object.freeze({ stored, task });
34
+ }
35
+
36
+ function terminalConfig({
37
+ projectDir, task, invocation, prompt, timeout, providerExecutableBinding, baseCommitRef,
38
+ }) {
39
+ return Object.freeze({
40
+ agent: 'rail',
41
+ taskId: task.task_id,
42
+ provider: invocation.harness_id,
43
+ providerId: invocation.provider_id,
44
+ model: invocation.model_pin.model_id,
45
+ reasoningConfiguration: invocation.model_pin.reasoning_configuration,
46
+ catalogSnapshotRef: invocation.model_pin.catalog_snapshot_ref,
47
+ providerExecutableBinding,
48
+ prompt,
49
+ workingDir: projectDir,
50
+ executionBaseCommitRef: baseCommitRef,
51
+ timeout,
52
+ writeScope: ['.'],
53
+ authorityContext: {
54
+ authority_kind: 'rail_execution',
55
+ invocation_id: invocation.invocation_id,
56
+ launch_nonce: invocation.launch_nonce,
57
+ provider_id: invocation.provider_id,
58
+ },
59
+ });
60
+ }
61
+
62
+ function assertExactTerminalRoute(config) {
63
+ const built = buildSpawnCommand(config);
64
+ if (built.actualProvider !== config.provider
65
+ || built.actualModel !== config.model
66
+ || built.actualReasoningConfiguration !== config.reasoningConfiguration) {
67
+ fail('RAIL_TERMINAL_ROUTE_MISMATCH', 'terminal adapter changed the sealed RAIL execution binding', {
68
+ expected: { provider: config.provider, model: config.model, reasoning_configuration: config.reasoningConfiguration },
69
+ actual: { provider: built.actualProvider, model: built.actualModel, reasoning_configuration: built.actualReasoningConfiguration },
70
+ });
71
+ }
72
+ return built;
73
+ }
74
+
75
+ function terminalEvidence(handle) {
76
+ const stdout = (handle.stdout || []).join('');
77
+ const stderr = (handle.stderr || []).join('');
78
+ // The journal has a stricter record limit than the terminal authority
79
+ // capture. Reject oversized RAIL output instead of truncating it: a
80
+ // truncated transcript cannot be cryptographically bound to the signed
81
+ // completion receipt.
82
+ if (Buffer.byteLength(stdout) > 1_000_000 || Buffer.byteLength(stderr) > 100_000) {
83
+ fail('RAIL_EXECUTION_OUTPUT_TOO_LARGE', 'RAIL terminal output exceeds the journal-bound evidence limit');
84
+ }
85
+ return Object.freeze({
86
+ terminal_id: handle.id,
87
+ provider: handle.provider,
88
+ model: handle.model,
89
+ reasoning_configuration: handle.reasoningConfiguration,
90
+ exit_status: handle.exitCode,
91
+ retry_count: handle.retryCount || 0,
92
+ started_at: handle.startedAt,
93
+ stdout,
94
+ stderr,
95
+ terminal_authority_receipt: getTerminalCompletionAuthorityReceipt(handle),
96
+ });
97
+ }
98
+
99
+ export function startClaimHeartbeat({ projectDir, handoff_id, attempt_id, claim_id, operator_id, lease_ttl_ms, initial_lease, activeHandle }) {
100
+ const intervalMs = Math.max(25, Math.min(60_000, Math.floor(lease_ttl_ms / 3)));
101
+ let heartbeatCount = 0;
102
+ let contentionCount = 0;
103
+ let latestLease = initial_lease;
104
+ let failure = null;
105
+ const timer = setInterval(() => {
106
+ if (failure) return;
107
+ try {
108
+ const heartbeat = heartbeatRailTask({
109
+ projectDir, handoff_id, attempt_id, claim_id, operator_id, lease_ttl_ms,
110
+ });
111
+ heartbeatCount += 1;
112
+ latestLease = heartbeat.lease;
113
+ } catch (error) {
114
+ if (error?.code === 'JOURNAL_LOCK_BUSY' && Date.now() < Date.parse(latestLease.expires_at)) {
115
+ contentionCount += 1;
116
+ return;
117
+ }
118
+ failure = error;
119
+ const handle = activeHandle();
120
+ if (handle?.process && handle.status === 'running') handle.process.kill('SIGTERM');
121
+ }
122
+ }, intervalMs);
123
+ timer.unref?.();
124
+ return Object.freeze({
125
+ stop() { clearInterval(timer); },
126
+ evidence() {
127
+ return Object.freeze({
128
+ heartbeat_count: heartbeatCount,
129
+ contention_count: contentionCount,
130
+ latest_lease: latestLease,
131
+ failure,
132
+ });
133
+ },
134
+ });
135
+ }
136
+
137
+ /**
138
+ * Production RAIL boundary. It atomically claims dependency-ready work through
139
+ * dispatchReadyRailTask and starts the exact selected CLI binding from inside
140
+ * the injected adapter. Retries reuse that same binding and never downgrade.
141
+ */
142
+ export async function executeReadyRailTask({
143
+ projectDir,
144
+ handoff_id,
145
+ task_id,
146
+ attempt_id = `attempt-${randomUUID()}`,
147
+ operator_id = 'operator://chati/runtime',
148
+ browser_request,
149
+ git_dir = projectDir,
150
+ lease_ttl_ms,
151
+ recover_expired = false,
152
+ timeout = 30 * 60 * 1000,
153
+ max_retries = 2,
154
+ } = {}) {
155
+ if (!projectDir || !handoff_id || !task_id) fail('RAIL_DISPATCH_INPUT_REQUIRED', 'projectDir, handoff_id and task_id are required');
156
+ const { stored, task } = exactTask({ projectDir, handoff_id, task_id });
157
+ const binding = task.execution_binding;
158
+ const providerExecutableBinding = resolveRuntimeExecutableBindingV2({
159
+ artifact: loadRuntimeInstallationV2(projectDir),
160
+ provider_id: binding.provider_id,
161
+ harness_id: binding.harness_id,
162
+ });
163
+ const readiness = checkProviderReadiness(binding.harness_id, {
164
+ workingDir: projectDir, executableBinding: providerExecutableBinding,
165
+ });
166
+ if (!readiness.ready) fail(readiness.code || 'PROVIDER_NOT_READY', readiness.detail || `${binding.harness_id} is not ready`);
167
+
168
+ let handle = null;
169
+ let exactConfig = null;
170
+ const adapter = Object.freeze({
171
+ harness_id: binding.harness_id,
172
+ invoke({ invocation, prompt, reserve_launch }) {
173
+ exactConfig = Object.freeze({
174
+ ...terminalConfig({
175
+ projectDir, task, invocation, prompt, timeout, providerExecutableBinding,
176
+ baseCommitRef: stored.handoff.seal.git_commit_ref,
177
+ }),
178
+ beforeAuthoritySpawn: reserve_launch,
179
+ });
180
+ assertExactTerminalRoute(exactConfig);
181
+ handle = spawnTerminal(exactConfig);
182
+ return getTerminalLaunchAuthorityReceipt(handle);
183
+ },
184
+ });
185
+
186
+ await getRateLimiter(binding.harness_id).waitForSlot();
187
+ const effectiveLeaseTtlMs = lease_ttl_ms ?? 5 * 60 * 1000;
188
+ let dispatch;
189
+ try {
190
+ dispatch = dispatchReadyRailTask({
191
+ projectDir, handoff_id, task_id, attempt_id, operator_id,
192
+ adapter, browser_request, git_dir, lease_ttl_ms: effectiveLeaseTtlMs,
193
+ recover_expired,
194
+ });
195
+ } catch (error) {
196
+ if (handle) await killTerminal(handle);
197
+ throw error;
198
+ }
199
+ if (!handle || !exactConfig) fail('RAIL_EXECUTION_NOT_STARTED', 'RAIL adapter did not start the selected harness');
200
+
201
+ const heartbeat = startClaimHeartbeat({
202
+ projectDir,
203
+ handoff_id,
204
+ attempt_id,
205
+ claim_id: dispatch.claim.claim.claim_id,
206
+ operator_id,
207
+ lease_ttl_ms: effectiveLeaseTtlMs,
208
+ initial_lease: dispatch.claim.claim.lease,
209
+ activeHandle: () => handle,
210
+ });
211
+ let claimRelinquished = false;
212
+ const relinquishClaim = (reason) => {
213
+ if (claimRelinquished) return null;
214
+ const relinquished = relinquishRailTask({
215
+ projectDir, handoff_id, attempt_id,
216
+ claim_id: dispatch.claim.claim.claim_id,
217
+ operator_id,
218
+ reason,
219
+ });
220
+ claimRelinquished = true;
221
+ return relinquished;
222
+ };
223
+ try {
224
+ let retries = 0;
225
+ const failedAttemptRefs = [];
226
+ const recordFailedAttempt = () => {
227
+ handle.retryCount = retries;
228
+ const failedExecution = terminalEvidence(handle);
229
+ const persisted = recordRailExecutionEvidence({
230
+ projectDir, handoff_id, task_id, attempt_id,
231
+ invocation: dispatch.invocation,
232
+ execution: failedExecution,
233
+ base_commit_ref: dispatch.claim.attempt.git_commit_ref,
234
+ result_commit_ref: null,
235
+ });
236
+ failedAttemptRefs.push(persisted.evidence_ref);
237
+ };
238
+ try {
239
+ await waitForTerminal(handle);
240
+ while (handle.exitCode !== 0 && retries < max_retries && isTransientFailure(handle.exitCode, [...handle.stdout, ...handle.stderr])) {
241
+ if (heartbeat.evidence().failure) break;
242
+ recordFailedAttempt();
243
+ retries += 1;
244
+ await new Promise((resolveDelay) => setTimeout(resolveDelay, 2_000 * (2 ** (retries - 1))));
245
+ if (heartbeat.evidence().failure) break;
246
+ await getRateLimiter(binding.harness_id).waitForSlot();
247
+ try {
248
+ dispatch = dispatchReadyRailTask({
249
+ projectDir, handoff_id, task_id, attempt_id, operator_id,
250
+ adapter, browser_request, git_dir, lease_ttl_ms: effectiveLeaseTtlMs,
251
+ });
252
+ } catch (error) {
253
+ if (handle) await killTerminal(handle);
254
+ throw error;
255
+ }
256
+ await waitForTerminal(handle);
257
+ }
258
+ } finally {
259
+ heartbeat.stop();
260
+ }
261
+ const heartbeatEvidence = heartbeat.evidence();
262
+ if (heartbeatEvidence.failure) fail(
263
+ 'RAIL_LEASE_HEARTBEAT_FAILED',
264
+ 'RAIL execution lost its claim lease heartbeat and was stopped for reconciliation',
265
+ { cause_code: heartbeatEvidence.failure.code, cause: heartbeatEvidence.failure.message },
266
+ );
267
+ handle.retryCount = retries;
268
+ const execution = terminalEvidence(handle);
269
+ const base_commit_ref = dispatch.claim.attempt.git_commit_ref;
270
+ const invalidOutput = execution.terminal_authority_receipt.execution_result_status === 'invalid_output';
271
+ const envelope = execution.exit_status === 0 && !invalidOutput
272
+ ? parseRailExecutionEnvelope((handle.stdout || []).join(''))
273
+ : null;
274
+ if (envelope && envelope.launch_nonce !== dispatch.invocation.launch_nonce) {
275
+ fail('RAIL_EXECUTION_LAUNCH_NONCE_MISMATCH', 'execution output does not echo the canonical launch nonce');
276
+ }
277
+ const result_commit_ref = envelope?.result_commit_ref ?? null;
278
+ const persistedExecution = recordRailExecutionEvidence({
279
+ projectDir,
280
+ handoff_id,
281
+ task_id,
282
+ attempt_id,
283
+ invocation: dispatch.invocation,
284
+ execution,
285
+ base_commit_ref,
286
+ result_commit_ref,
287
+ });
288
+ const executionSucceeded = persistedExecution.outcome === 'succeeded';
289
+ const relinquished = relinquishClaim(executionSucceeded ? 'action_complete' : 'execution_failed');
290
+ if (!executionSucceeded && execution.exit_status === 0) {
291
+ fail(
292
+ persistedExecution.failure_code || 'RAIL_EXECUTION_OUTPUT_INVALID',
293
+ 'RAIL execution completed without a valid canonical result commit',
294
+ { execution_evidence_ref: persistedExecution.evidence_ref },
295
+ );
296
+ }
297
+ return Object.freeze({
298
+ ok: executionSucceeded,
299
+ action: executionSucceeded ? 'rail_task_executed' : 'rail_task_execution_failed',
300
+ handoff_id,
301
+ task_id,
302
+ attempt_id,
303
+ claim: dispatch.claim,
304
+ resumed: dispatch.resumed,
305
+ invocation: dispatch.invocation,
306
+ launch_receipt: dispatch.receipt,
307
+ execution,
308
+ base_commit_ref,
309
+ result_commit_ref,
310
+ execution_evidence_ref: persistedExecution.evidence_ref,
311
+ failed_execution_evidence_refs: Object.freeze(failedAttemptRefs),
312
+ lease_heartbeat_count: heartbeatEvidence.heartbeat_count,
313
+ lease_contention_count: heartbeatEvidence.contention_count,
314
+ latest_lease: relinquished?.lease ?? heartbeatEvidence.latest_lease ?? dispatch.claim.claim.lease,
315
+ browser_manifest: dispatch.browser_manifest,
316
+ next_required_action: executionSucceeded ? 'independent_review' : 'rail_recover_same_attempt_after_lease_expiry',
317
+ });
318
+ } catch (error) {
319
+ try {
320
+ relinquishClaim('supervisor_error');
321
+ } catch (relinquishError) {
322
+ error.relinquish_error = {
323
+ code: relinquishError.code || 'RAIL_RELINQUISH_FAILED',
324
+ message: relinquishError.message,
325
+ };
326
+ }
327
+ throw error;
328
+ }
329
+ }
330
+
331
+ function parseArgs(argv) {
332
+ const args = {};
333
+ for (let index = 2; index < argv.length; index += 1) {
334
+ const token = argv[index];
335
+ if (!token.startsWith('--')) continue;
336
+ const value = argv[index + 1];
337
+ if (value && !value.startsWith('--')) { args[token.slice(2)] = value; index += 1; }
338
+ else args[token.slice(2)] = true;
339
+ }
340
+ return args;
341
+ }
342
+
343
+ async function main() {
344
+ const args = parseArgs(process.argv);
345
+ const attemptId = args['attempt-id'] || `attempt-${randomUUID()}`;
346
+ const projectDir = resolve(args['project-dir'] || process.cwd());
347
+ try {
348
+ const result = await executeReadyRailTask({
349
+ projectDir,
350
+ handoff_id: args['handoff-id'],
351
+ task_id: args['task-id'],
352
+ attempt_id: attemptId,
353
+ ...(args['operator-id'] ? { operator_id: args['operator-id'] } : {}),
354
+ ...(args.timeout ? { timeout: Number(args.timeout) } : {}),
355
+ });
356
+ process.stdout.write(`${JSON.stringify(result, null, 2)}\n`);
357
+ if (!result.ok) process.exitCode = 1;
358
+ } catch (error) {
359
+ let nextRequiredAction = 'rail_retry_same_attempt';
360
+ try {
361
+ resolveRailAttemptContext({ projectDir, handoff_id: args['handoff-id'], attempt_id: attemptId });
362
+ nextRequiredAction = 'rail_recover_same_attempt_after_lease_expiry';
363
+ } catch {
364
+ // No canonical claim exists, so the same attempt can be dispatched again.
365
+ }
366
+ process.stdout.write(`${JSON.stringify({
367
+ ok: false,
368
+ action: 'error',
369
+ code: error.code || 'RAIL_DISPATCH_FAILED',
370
+ error: error.message,
371
+ attempt_id: attemptId,
372
+ next_required_action: nextRequiredAction,
373
+ })}\n`);
374
+ process.exitCode = 1;
375
+ }
376
+ }
377
+
378
+ if (process.argv[1] && resolve(process.argv[1]) === resolve(fileURLToPath(import.meta.url))) {
379
+ await main();
380
+ }
@@ -27,6 +27,7 @@ import { collectResults, mergeHandoffs, buildConsolidatedHandoff } from './colle
27
27
  import { parseAgentOutput } from './handoff-parser.js';
28
28
  import { estimateTokens, COST_PER_1K } from './cost-tracker.js';
29
29
  import { getRateLimiter } from './rate-limiter.js';
30
+ import { loadRuntimeInstallationV2 } from '../orchestrator/runtime-installation-v2.js';
30
31
  import {
31
32
  readTaskList, getTeamProgress, writeMailboxMessage, computeEchoSimilarity,
32
33
  } from './team-task-list.js';
@@ -85,6 +86,10 @@ async function main() {
85
86
  const teamId = args['team-id'];
86
87
  const teamType = args['team-type'];
87
88
  const projectDir = args['project-dir'] || process.cwd();
89
+ if (loadRuntimeInstallationV2(projectDir)) {
90
+ outputError('Runtime v2 uses individually signed planning commands and RAIL. The legacy native team runner is disabled.');
91
+ process.exit(1);
92
+ }
88
93
  const previousAgent = args['previous-agent'] || null;
89
94
  const provider = args.provider || null;
90
95
  const timeout = parseInt(args.timeout, 10) || 1_800_000; // default 30 minutes