chati-dev 4.5.27 → 4.5.29
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/bin/chati.js +151 -123
- package/framework/agents/build/dev.md +4 -4
- package/framework/agents/deploy/devops.md +3 -3
- package/framework/agents/discover/brief.md +18 -6
- package/framework/agents/discover/brownfield-wu.md +3 -3
- package/framework/agents/discover/greenfield-wu.md +4 -4
- package/framework/agents/plan/architect.md +2 -2
- package/framework/agents/plan/detail.md +4 -4
- package/framework/agents/plan/phases.md +3 -3
- package/framework/agents/plan/tasks.md +47 -19
- package/framework/agents/plan/ux-brand-architect.md +1 -1
- package/framework/agents/plan/ux.md +26 -2
- package/framework/agents/quality/qa-implementation.md +8 -8
- package/framework/agents/quality/qa-planning.md +58 -4
- package/framework/agents/quality/qa-visual.md +4 -4
- package/framework/config.yaml +2 -2
- package/framework/constitution.md +12 -1
- package/framework/context/governance.md +2 -1
- package/framework/context/root.md +3 -1
- package/framework/domains/constitution.yaml +1 -1
- package/framework/domains/global.yaml +1 -1
- package/framework/domains/workflows/standard-flow.yaml +7 -8
- package/framework/hooks/advance-trigger.js +3 -2
- package/framework/hooks/brief-validator.js +1 -1
- package/framework/hooks/post-dev.js +1 -1
- package/framework/hooks/team-quality-gate.js +9 -2
- package/framework/i18n/en.yaml +1 -0
- package/framework/i18n/es.yaml +1 -0
- package/framework/i18n/fr.yaml +1 -0
- package/framework/i18n/pt.yaml +1 -0
- package/framework/manifest.json +83 -83
- package/framework/manifest.sig +1 -1
- package/framework/orchestrator/chati-router.js +14 -6
- package/framework/orchestrator/chati.md +61 -23
- package/framework/rules/human-writing-style.md +7 -0
- package/framework/schemas/session.schema.json +25 -2
- package/framework/schemas/task.schema.json +26 -0
- package/framework/tasks/orchestrator-handoff.md +10 -10
- package/framework/tasks/orchestrator-resume.md +3 -3
- package/framework/workflows/brownfield-fullstack.yaml +6 -3
- package/framework/workflows/brownfield-service.yaml +5 -3
- package/framework/workflows/brownfield-ui.yaml +5 -3
- package/framework/workflows/greenfield-fullstack.yaml +6 -3
- package/framework/workflows/standard-flow.yaml +8 -3
- package/node_modules/@chati/core/native/browser-proof-wire.h +29 -0
- package/node_modules/@chati/core/native/darwin-browser-listener.c +36 -0
- package/node_modules/@chati/core/native/darwin-browser-listener.h +32 -0
- package/node_modules/@chati/core/native/darwin-browser-proof.h +86 -0
- package/node_modules/@chati/core/native/darwin-code-identity.h +35 -0
- package/node_modules/@chati/core/native/darwin-launch-messages.h +37 -0
- package/node_modules/@chati/core/native/darwin-launcher.c +312 -0
- package/node_modules/@chati/core/native/descriptor-fs.c +407 -0
- package/node_modules/@chati/core/native/execution-admission.h +59 -0
- package/node_modules/@chati/core/native/execution-gate.c +41 -0
- package/node_modules/@chati/core/native/linux-process.c +41 -0
- package/node_modules/@chati/core/native/prebuilds/darwin-arm64/darwin-launcher +0 -0
- package/node_modules/@chati/core/native/prebuilds/darwin-arm64/descriptor-fs.node +0 -0
- package/node_modules/@chati/core/native/prebuilds/darwin-arm64/execution-gate +0 -0
- package/node_modules/@chati/core/native/prebuilds/linux-x64/descriptor-fs.node +0 -0
- package/node_modules/@chati/core/native/prebuilds/linux-x64/execution-gate +0 -0
- package/node_modules/@chati/core/native/process-info-pipe.c +91 -0
- package/node_modules/@chati/core/package.json +2 -1
- package/node_modules/@chati/core/scripts/build-native.js +36 -0
- package/node_modules/@chati/core/src/darwin-browser-proof.js +87 -0
- package/node_modules/@chati/core/src/darwin-host-boot.js +58 -0
- package/node_modules/@chati/core/src/darwin-process.js +79 -0
- package/node_modules/@chati/core/src/darwin-supervisor-channels.js +82 -0
- package/node_modules/@chati/core/src/darwin-terminal-receipt.js +111 -0
- package/node_modules/@chati/core/src/descriptor-fs.js +222 -0
- package/node_modules/@chati/core/src/execution-gate.js +8 -0
- package/node_modules/@chati/core/src/host-boot-marker.js +45 -0
- package/node_modules/@chati/core/src/index.js +18 -0
- package/node_modules/@chati/core/src/linux-host-boot.js +218 -0
- package/node_modules/@chati/core/src/linux-process.js +133 -0
- package/node_modules/@chati/planning/src/browser-verification-plan.js +71 -0
- package/node_modules/@chati/planning/src/index.js +33 -0
- package/node_modules/@chati/planning/src/planning-material.js +64 -0
- package/node_modules/@chati/planning/src/verification-plan.js +57 -0
- package/node_modules/@chati/rail/package.json +1 -0
- package/node_modules/@chati/rail/src/execution-restoration.js +92 -0
- package/node_modules/@chati/rail/src/index.js +812 -167
- package/node_modules/@chati/rail/src/provider-processing.js +118 -0
- package/node_modules/@chati/rail/src/terminal-containment.js +23 -0
- package/node_modules/@chati/release-lane/src/index.js +40 -43
- package/node_modules/@chati/review-council/src/index.js +223 -0
- package/node_modules/@chati/tracking-clickup/src/index.js +67 -58
- package/package-artifact-manifest.json +1 -1
- package/package-artifact-manifest.sig +1 -1
- package/package.json +3 -2
- package/scripts/verify-real-harness-e2e.js +20 -8
- package/src/installer/core.js +147 -107
- package/src/installer/package-artifact.js +7 -0
- package/src/installer/preflight.js +13 -0
- package/src/installer/provider-overlay.js +1 -2
- package/src/installer/templates.js +58 -0
- package/src/installer-v2/catalog-client.js +31 -1
- package/src/installer-v2/clickup-connection-storage.js +92 -0
- package/src/installer-v2/clickup-mcp-client.js +155 -0
- package/src/installer-v2/clickup-oauth-loopback.js +96 -0
- package/src/installer-v2/clickup-oauth.js +233 -0
- package/src/installer-v2/index.js +141 -15
- package/src/installer-v2/installation-authority.js +98 -4
- package/src/installer-v2/provider-executable.js +137 -20
- package/src/installer-v2/wizard-installation.js +11 -0
- package/src/orchestrator/agent-selector.js +39 -1
- package/src/orchestrator/browser-artifact-store.js +131 -0
- package/src/orchestrator/cli.js +762 -205
- package/src/orchestrator/clickup-mcp-observer.js +226 -0
- package/src/orchestrator/clickup-runtime.js +45 -24
- package/src/orchestrator/council-gate-runtime.js +903 -0
- package/src/orchestrator/evidence-authority-enrollment.js +75 -0
- package/src/orchestrator/evidence-authority-request.js +46 -0
- package/src/orchestrator/handoff-engine.js +4 -11
- package/src/orchestrator/pipeline-manager.js +42 -4
- package/src/orchestrator/planning-contract-revision.js +711 -0
- package/src/orchestrator/planning-reconciliation.js +302 -0
- package/src/orchestrator/planning-runtime.js +476 -29
- package/src/orchestrator/rail-adjudication-evidence.js +15 -24
- package/src/orchestrator/rail-human-effort-authority.js +46 -0
- package/src/orchestrator/rail-human-effort.js +149 -0
- package/src/orchestrator/rail-runtime.js +291 -47
- package/src/orchestrator/rail-verification.js +278 -0
- package/src/orchestrator/review-runtime.js +37 -2
- package/src/orchestrator/runtime-installation-v2.js +143 -24
- package/src/orchestrator/session-manager.js +1457 -29
- package/src/tasks/handoff.js +57 -33
- package/src/terminal/adapters/claude-adapter.js +3 -0
- package/src/terminal/adapters/codex-adapter.js +13 -0
- package/src/terminal/adapters/grok-adapter.js +1 -0
- package/src/terminal/agent-attempt-budget.js +35 -0
- package/src/terminal/browser-endpoint-proxy.js +174 -0
- package/src/terminal/browser-observer-driver.js +94 -0
- package/src/terminal/browser-observer-supervisor.js +73 -0
- package/src/terminal/browser-observer.js +95 -0
- package/src/terminal/browser-process-facts.js +75 -0
- package/src/terminal/browser-runtime.js +131 -0
- package/src/terminal/darwin-authority-policy.js +213 -0
- package/src/terminal/darwin-browser-application.js +116 -0
- package/src/terminal/darwin-browser-observer.js +57 -0
- package/src/terminal/darwin-execution.js +185 -0
- package/src/terminal/executable-snapshot.js +202 -0
- package/src/terminal/handoff-parser.js +48 -2
- package/src/terminal/isolation.js +14 -13
- package/src/terminal/prompt-builder.js +176 -15
- package/src/terminal/provider-preflight.js +47 -27
- package/src/terminal/rail-execution-worktree.js +22 -6
- package/src/terminal/rail-process-observer.js +10 -0
- package/src/terminal/rail-prompts.js +21 -3
- package/src/terminal/rail-readonly-workspace.js +54 -5
- package/src/terminal/rail-worktree-checkpoint.js +315 -0
- package/src/terminal/rail-worktree-restoration.js +485 -0
- package/src/terminal/run-agent.js +164 -42
- package/src/terminal/run-council-gate.js +338 -0
- package/src/terminal/run-planning-reconciliation.js +49 -0
- package/src/terminal/run-rail-adjudication.js +29 -7
- package/src/terminal/run-rail-recovery.js +97 -0
- package/src/terminal/run-rail-review.js +36 -15
- package/src/terminal/run-rail-rework.js +26 -15
- package/src/terminal/run-rail-task.js +22 -9
- package/src/terminal/runtime-native-helper.js +85 -0
- package/src/terminal/spawner.js +899 -142
- package/src/terminal/verification-process-facts.js +49 -0
- package/src/upgrade/entrypoints.js +68 -0
- package/src/upgrade/generation.js +194 -0
- package/src/upgrade/maintenance.js +79 -0
- package/src/upgrade/paths.js +81 -0
- package/src/upgrade/upgrade.js +267 -0
- package/src/utils/execution-role.js +15 -0
- package/src/utils/handoff-paths.js +59 -0
- package/src/wizard/i18n.js +1 -0
- package/src/wizard/index.js +17 -5
- package/src/wizard/questions.js +24 -12
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { withUpgradeMaintenance } from '../upgrade/maintenance.js';
|
|
2
3
|
/**
|
|
3
4
|
* CLI runner for single-agent terminal execution.
|
|
4
5
|
*
|
|
@@ -18,9 +19,12 @@ import { randomUUID } from 'node:crypto';
|
|
|
18
19
|
import { closeSync, constants, existsSync, fstatSync, lstatSync, openSync, readSync, realpathSync, rmSync } from 'fs';
|
|
19
20
|
import { isAbsolute, relative, resolve } from 'path';
|
|
20
21
|
import { buildAgentPrompt } from './prompt-builder.js';
|
|
21
|
-
import { isTransientFailure, spawnTerminalWithRetry } from './spawner.js';
|
|
22
|
+
import { getTerminalAttemptDiagnostics, isTransientFailure, spawnTerminalWithRetry } from './spawner.js';
|
|
22
23
|
import { getWriteScope } from './isolation.js';
|
|
23
|
-
import { parseAgentOutput } from './handoff-parser.js';
|
|
24
|
+
import { normalizePlanningQaOutcome, parseAgentOutput } from './handoff-parser.js';
|
|
25
|
+
import { getQaThreshold } from '../orchestrator/pipeline-manager.js';
|
|
26
|
+
import { pinPlanningContractRevisionInputs, pinPlanningQaSubject } from '../orchestrator/council-gate-runtime.js';
|
|
27
|
+
import { AGENT_RETRY_DELAY_MS, createAgentAttemptBudget, parseAgentTimeout } from './agent-attempt-budget.js';
|
|
24
28
|
import { checkProviderReadiness } from './provider-preflight.js';
|
|
25
29
|
import { createCostTracker } from './cost-tracker.js';
|
|
26
30
|
import { getRateLimiter } from './rate-limiter.js';
|
|
@@ -30,7 +34,7 @@ import { getTelemetryConfig, isEnabled as isTelemetryEnabled } from '../telemetr
|
|
|
30
34
|
import {
|
|
31
35
|
recordPendingInteraction, clearPendingInteraction,
|
|
32
36
|
claimPendingInteraction, releasePendingInteraction,
|
|
33
|
-
loadSession, startAgentExecution, holdAgentExecutionForAdvance, releaseAgentExecution,
|
|
37
|
+
loadSession, startAgentExecution, holdAgentExecutionForAdvance, releaseAgentExecution, validateInitialTaskContext,
|
|
34
38
|
} from '../orchestrator/session-manager.js';
|
|
35
39
|
import { recordEvent, EventType } from '../intelligence/timeline.js';
|
|
36
40
|
import {
|
|
@@ -92,7 +96,11 @@ function parseArgs(argv) {
|
|
|
92
96
|
|
|
93
97
|
async function main() {
|
|
94
98
|
const args = parseArgs(process.argv);
|
|
99
|
+
const projectDir = args['project-dir'] || process.cwd();
|
|
100
|
+
return withUpgradeMaintenance(projectDir, () => runUnderMaintenance(args, projectDir));
|
|
101
|
+
}
|
|
95
102
|
|
|
103
|
+
async function runUnderMaintenance(args, projectDir) {
|
|
96
104
|
// Validate required args
|
|
97
105
|
if (!args.agent) {
|
|
98
106
|
outputError('Missing required argument: --agent');
|
|
@@ -103,8 +111,14 @@ async function main() {
|
|
|
103
111
|
process.exit(1);
|
|
104
112
|
}
|
|
105
113
|
|
|
106
|
-
|
|
107
|
-
|
|
114
|
+
let timeout;
|
|
115
|
+
try { timeout = parseAgentTimeout(args.timeout, args.agent); }
|
|
116
|
+
catch (error) {
|
|
117
|
+
outputResult({ status: 'error', code: error.code, error: error.message, agent: args.agent });
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
let executionDeadline = null;
|
|
121
|
+
let contentReviewContext = null;
|
|
108
122
|
|
|
109
123
|
const runtimeRoute = validateRuntimeCommand(projectDir, args);
|
|
110
124
|
if (!runtimeRoute.ok) {
|
|
@@ -128,6 +142,10 @@ async function main() {
|
|
|
128
142
|
const execution = startAgentExecution(projectDir, {
|
|
129
143
|
agent: args.agent,
|
|
130
144
|
claimId: args['execution-claim-id'] || null,
|
|
145
|
+
taskId: args['task-id'],
|
|
146
|
+
binding: { provider: args.provider || null, provider_id: args['provider-id'] || null,
|
|
147
|
+
model: args.model || null, reasoning_configuration: args['reasoning-configuration'] || null,
|
|
148
|
+
catalog_snapshot_ref: args['catalog-snapshot-ref'] || null },
|
|
131
149
|
});
|
|
132
150
|
if (!execution.saved || !execution.started) {
|
|
133
151
|
outputResult({
|
|
@@ -139,6 +157,8 @@ async function main() {
|
|
|
139
157
|
});
|
|
140
158
|
process.exit(1);
|
|
141
159
|
}
|
|
160
|
+
executionDeadline = execution.expiresAt;
|
|
161
|
+
contentReviewContext = execution.contentReviewContext;
|
|
142
162
|
process.once('exit', () => {
|
|
143
163
|
releaseAgentExecution(projectDir, {
|
|
144
164
|
agent: args.agent,
|
|
@@ -159,11 +179,21 @@ async function main() {
|
|
|
159
179
|
process.exit(1);
|
|
160
180
|
}
|
|
161
181
|
if (responseFile.response !== null) args['additional-context'] = responseFile.response;
|
|
182
|
+
// Extra task context belongs to this already validated fresh v2 claim. It
|
|
183
|
+
// is not an answer to a pending question and cannot authenticate a relay.
|
|
184
|
+
const freshTaskContext = Boolean(runtimeRoute.artifact && !args['interaction-id']);
|
|
185
|
+
if (freshTaskContext) {
|
|
186
|
+
try { validateInitialTaskContext(args['additional-context']); }
|
|
187
|
+
catch (error) {
|
|
188
|
+
outputResult({ status: 'error', code: error.code, error: error.message, agent: args.agent });
|
|
189
|
+
process.exit(1);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
162
192
|
|
|
163
193
|
// A session awaiting user input is a local state gate, not a provider
|
|
164
194
|
// preflight concern. Check it before looking for a CLI so a missing or
|
|
165
195
|
// empty relay response cannot be masked by a host-specific CLI absence.
|
|
166
|
-
if (typeof args['additional-context'] !== 'string' || !args['additional-context'].trim()) {
|
|
196
|
+
if (freshTaskContext || typeof args['additional-context'] !== 'string' || !args['additional-context'].trim()) {
|
|
167
197
|
const pendingGuard = claimPendingInteraction(projectDir, {
|
|
168
198
|
interactionId: args['interaction-id'] || null,
|
|
169
199
|
agent: args.agent,
|
|
@@ -173,7 +203,7 @@ async function main() {
|
|
|
173
203
|
model: args.model || null,
|
|
174
204
|
reasoningConfiguration: args['reasoning-configuration'] || null,
|
|
175
205
|
catalogSnapshotRef: args['catalog-snapshot-ref'] || null,
|
|
176
|
-
response: args['additional-context'],
|
|
206
|
+
response: freshTaskContext ? undefined : args['additional-context'],
|
|
177
207
|
reasoningEscalation: args['reasoning-escalation'] === 'true',
|
|
178
208
|
});
|
|
179
209
|
if (!pendingGuard.saved || !pendingGuard.allowed) {
|
|
@@ -208,28 +238,32 @@ async function main() {
|
|
|
208
238
|
process.exit(1);
|
|
209
239
|
}
|
|
210
240
|
|
|
211
|
-
|
|
241
|
+
const promptConfig = {
|
|
242
|
+
agent: args.agent, taskId: args['task-id'], projectDir,
|
|
243
|
+
previousAgent: args['previous-agent'] || null, workflow: args.workflow || null,
|
|
244
|
+
sessionState, additionalContext: args['additional-context'] || null,
|
|
245
|
+
provider: args.provider || null, model: args.model || null,
|
|
246
|
+
providerId: args['provider-id'] || null,
|
|
247
|
+
reasoningConfiguration: args['reasoning-configuration'] || null,
|
|
248
|
+
catalogSnapshotRef: args['catalog-snapshot-ref'] || null,
|
|
249
|
+
interactionId: args['interaction-id'] || null,
|
|
250
|
+
strictProvider: args['strict-provider'] === 'true',
|
|
251
|
+
executionClaimId: args['execution-claim-id'] || null,
|
|
252
|
+
contentReviewContext,
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
// Local preparation resolves the route, but never injects unclaimed history.
|
|
212
256
|
let promptResult;
|
|
213
257
|
try {
|
|
214
|
-
promptResult = buildAgentPrompt(
|
|
215
|
-
agent: args.agent,
|
|
216
|
-
taskId: args['task-id'],
|
|
217
|
-
projectDir,
|
|
218
|
-
previousAgent: args['previous-agent'] || null,
|
|
219
|
-
workflow: args.workflow || null,
|
|
220
|
-
sessionState,
|
|
221
|
-
additionalContext: args['additional-context'] || null,
|
|
222
|
-
provider: args.provider || null,
|
|
223
|
-
model: args.model || null,
|
|
224
|
-
strictProvider: args['strict-provider'] === 'true',
|
|
225
|
-
});
|
|
258
|
+
promptResult = buildAgentPrompt(promptConfig);
|
|
226
259
|
} catch (err) {
|
|
227
260
|
const relayState = releasePendingForFailedRelay(projectDir, args);
|
|
228
261
|
if (!relayState.saved) {
|
|
229
262
|
outputResult({ status: 'error', code: 'SESSION_WRITE_FAILED', error: relayState.error, cause: err.message });
|
|
230
263
|
process.exit(1);
|
|
231
264
|
}
|
|
232
|
-
|
|
265
|
+
outputResult({ status: 'error', code: err.code || 'PROMPT_BUILD_FAILED',
|
|
266
|
+
error: `Failed to build prompt: ${err.message}`, agent: args.agent });
|
|
233
267
|
process.exit(1);
|
|
234
268
|
}
|
|
235
269
|
|
|
@@ -270,6 +304,7 @@ async function main() {
|
|
|
270
304
|
|
|
271
305
|
// Reserve the relay only after local preparation and rate limiting. The
|
|
272
306
|
// lease therefore covers provider execution, not unbounded queue time.
|
|
307
|
+
const relayDeadline = new Date(Date.now() + (timeout * 2) + 5 * 60 * 1000).toISOString();
|
|
273
308
|
const pendingClaim = claimPendingInteraction(projectDir, {
|
|
274
309
|
interactionId: args['interaction-id'] || null,
|
|
275
310
|
agent: args.agent,
|
|
@@ -279,9 +314,9 @@ async function main() {
|
|
|
279
314
|
model: args.model || null,
|
|
280
315
|
reasoningConfiguration: args['reasoning-configuration'] || null,
|
|
281
316
|
catalogSnapshotRef: args['catalog-snapshot-ref'] || null,
|
|
282
|
-
response: args['additional-context'],
|
|
317
|
+
response: freshTaskContext ? undefined : args['additional-context'],
|
|
283
318
|
reasoningEscalation: args['reasoning-escalation'] === 'true',
|
|
284
|
-
leaseExpiresAt:
|
|
319
|
+
leaseExpiresAt: relayDeadline,
|
|
285
320
|
});
|
|
286
321
|
if (!pendingClaim.saved || !pendingClaim.allowed) {
|
|
287
322
|
cleanupRejectedResponseFile(responseFile.filePath, pendingClaim);
|
|
@@ -295,12 +330,43 @@ async function main() {
|
|
|
295
330
|
process.exit(1);
|
|
296
331
|
}
|
|
297
332
|
if (pendingClaim.claimed) args['relay-claim-id'] = pendingClaim.claimId;
|
|
333
|
+
if (pendingClaim.claimed) {
|
|
334
|
+
try {
|
|
335
|
+
// The claim atomically validates the entire binding and snapshots its
|
|
336
|
+
// conversation. A pre-claim session read is not relay authority.
|
|
337
|
+
promptResult = buildAgentPrompt({ ...promptConfig, relayContext: pendingClaim.relayContext,
|
|
338
|
+
executionClaimId: pendingClaim.claimId, contentReviewContext: pendingClaim.contentReviewContext });
|
|
339
|
+
} catch (error) {
|
|
340
|
+
const released = releasePendingForFailedRelay(projectDir, args);
|
|
341
|
+
outputResult({ status: 'error', code: released.saved ? (error.code || 'RELAY_PROMPT_INVALID') : 'SESSION_WRITE_FAILED',
|
|
342
|
+
error: released.saved ? error.message : released.error, agent: args.agent });
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
298
346
|
|
|
299
347
|
// Spawn the agent terminal
|
|
300
348
|
const startTime = Date.now();
|
|
301
349
|
let handle;
|
|
350
|
+
let retryBudgetExhausted = false;
|
|
351
|
+
let qaSubject;
|
|
302
352
|
|
|
303
353
|
try {
|
|
354
|
+
const recoveryContext = pendingClaim.claimed ? pendingClaim.contentReviewContext : contentReviewContext;
|
|
355
|
+
const revalidation = recoveryContext?.planning_reconciliation || recoveryContext?.council_revalidation;
|
|
356
|
+
const contractRevision = recoveryContext?.planning_contract_revision;
|
|
357
|
+
if (contractRevision) {
|
|
358
|
+
// The executing owner may replace only its own output. Keep every other
|
|
359
|
+
// Planning input descriptor frozen across retries, interaction and hold.
|
|
360
|
+
qaSubject = pinPlanningContractRevisionInputs(projectDir, args.agent, contractRevision.execution_subject);
|
|
361
|
+
process.once('exit', () => qaSubject.close());
|
|
362
|
+
} else if (revalidation) {
|
|
363
|
+
// Retain the same input descriptors for the entire provider lifetime,
|
|
364
|
+
// including retries and final persistence. Hashes at two endpoints alone
|
|
365
|
+
// cannot detect a writer changing and restoring an input during review.
|
|
366
|
+
qaSubject = pinPlanningQaSubject(projectDir, revalidation.artifacts, { inputsOnly: true, excludeQaReport: true });
|
|
367
|
+
process.once('exit', () => qaSubject.close());
|
|
368
|
+
}
|
|
369
|
+
const budget = createAgentAttemptBudget(timeout, pendingClaim.claimed ? relayDeadline : executionDeadline);
|
|
304
370
|
const spawnConfig = {
|
|
305
371
|
agent: args.agent,
|
|
306
372
|
taskId: args['task-id'],
|
|
@@ -326,22 +392,32 @@ async function main() {
|
|
|
326
392
|
};
|
|
327
393
|
handle = await spawnTerminalWithRetry(spawnConfig, {
|
|
328
394
|
maxRetries: 1,
|
|
329
|
-
baseDelay:
|
|
395
|
+
baseDelay: AGENT_RETRY_DELAY_MS,
|
|
396
|
+
// This also runs after backoff, so a scheduler pause cannot turn an
|
|
397
|
+
// earlier admission decision into an expired claim launching a child.
|
|
398
|
+
beforeAttempt: () => { budget.assertCanStart(); qaSubject?.assertCurrent(); },
|
|
330
399
|
// The orchestrator must execute the routed model exactly. A silent
|
|
331
400
|
// downgrade would make the persisted binding and cost record false.
|
|
332
401
|
enableModelFallback: false,
|
|
333
402
|
shouldRetry(exitCode, output) {
|
|
334
403
|
const combined = Array.isArray(output) ? output.join('') : String(output || '');
|
|
335
|
-
|
|
404
|
+
const retryable = isTransientFailure(exitCode, combined) || /not logged in|not authenticated/i.test(combined);
|
|
405
|
+
if (retryable && !budget.canStart(AGENT_RETRY_DELAY_MS)) {
|
|
406
|
+
retryBudgetExhausted = true;
|
|
407
|
+
return false;
|
|
408
|
+
}
|
|
409
|
+
return retryable;
|
|
336
410
|
},
|
|
337
411
|
});
|
|
412
|
+
qaSubject?.assertCurrent();
|
|
338
413
|
} catch (err) {
|
|
339
414
|
const relayState = releasePendingForFailedRelay(projectDir, args);
|
|
415
|
+
const diagnostic = { elapsed: Date.now() - startTime, execution_diagnostics: getTerminalAttemptDiagnostics(err) };
|
|
340
416
|
if (!relayState.saved) {
|
|
341
|
-
outputResult({ status: 'error', code: 'SESSION_WRITE_FAILED', error: relayState.error, cause: err.message });
|
|
417
|
+
outputResult({ status: 'error', code: 'SESSION_WRITE_FAILED', error: relayState.error, cause: err.message, ...diagnostic });
|
|
342
418
|
process.exit(1);
|
|
343
419
|
}
|
|
344
|
-
|
|
420
|
+
outputResult({ status: 'error', code: err.code || 'TERMINAL_SPAWN_FAILED', error: `Failed to spawn terminal: ${err.message}`, agent: args.agent, ...diagnostic });
|
|
345
421
|
process.exit(1);
|
|
346
422
|
}
|
|
347
423
|
|
|
@@ -399,7 +475,7 @@ async function main() {
|
|
|
399
475
|
await flushAndSend(projectDir);
|
|
400
476
|
|
|
401
477
|
// Parse the handoff from stdout
|
|
402
|
-
const parsed = parseAgentOutput(stdout);
|
|
478
|
+
const parsed = parseAgentOutput(stdout, { strict: args.agent === 'qa-planning' });
|
|
403
479
|
const recovered = recoverInteractiveHandoff(args.agent, stdout, handle.exitCode);
|
|
404
480
|
|
|
405
481
|
if (handle.exitCode !== 0 && /not logged in|not authenticated/i.test(providerOutput)) {
|
|
@@ -415,6 +491,8 @@ async function main() {
|
|
|
415
491
|
provider: spawnProvider,
|
|
416
492
|
exitCode: handle.exitCode,
|
|
417
493
|
retryCount: handle.retryCount || 0,
|
|
494
|
+
elapsed,
|
|
495
|
+
execution_diagnostics: getTerminalAttemptDiagnostics(handle),
|
|
418
496
|
});
|
|
419
497
|
process.exit(1);
|
|
420
498
|
}
|
|
@@ -438,25 +516,58 @@ async function main() {
|
|
|
438
516
|
exitCode: handle.exitCode,
|
|
439
517
|
retryCount: handle.retryCount || 0,
|
|
440
518
|
agent: args.agent,
|
|
519
|
+
elapsed,
|
|
520
|
+
execution_diagnostics: getTerminalAttemptDiagnostics(handle),
|
|
521
|
+
...(retryBudgetExhausted ? { retry_suppressed: 'AGENT_EXECUTION_BUDGET_EXHAUSTED' } : {}),
|
|
441
522
|
});
|
|
442
523
|
process.exit(1);
|
|
443
524
|
}
|
|
444
525
|
|
|
445
526
|
if (parsed.found && parsed.valid) {
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
527
|
+
if (!['complete', 'needs_input'].includes(parsed.handoff.status)) {
|
|
528
|
+
// A zero OS exit is not a successful agent result. Keep the pending
|
|
529
|
+
// question and exact response recoverable, just as for process failure.
|
|
530
|
+
const relayState = releasePendingForFailedRelay(projectDir, args);
|
|
531
|
+
if (!relayState.saved) {
|
|
532
|
+
outputResult({ status: 'error', code: 'SESSION_WRITE_FAILED', error: relayState.error, cause: 'AGENT_RESULT_INCOMPLETE' });
|
|
533
|
+
process.exit(1);
|
|
534
|
+
}
|
|
535
|
+
outputResult({ status: 'error', code: 'AGENT_RESULT_INCOMPLETE',
|
|
536
|
+
error: `Agent returned ${parsed.handoff.status}; no successful result was held.`,
|
|
537
|
+
agent: args.agent, provider: spawnProvider, model: promptResult.model,
|
|
538
|
+
exitCode: handle.exitCode, handoff: parsed.handoff, elapsed, costEstimate });
|
|
449
539
|
process.exit(1);
|
|
450
540
|
}
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
541
|
+
let qaResult;
|
|
542
|
+
if (args.agent === 'qa-planning' && parsed.handoff.status === 'complete') {
|
|
543
|
+
const normalized = normalizePlanningQaOutcome(parsed.handoff, getQaThreshold(args.agent, projectDir));
|
|
544
|
+
if (!normalized.ok) {
|
|
545
|
+
const released = releasePendingForFailedRelay(projectDir, args);
|
|
546
|
+
outputResult({ status: 'error', code: released.saved ? normalized.code : 'SESSION_WRITE_FAILED',
|
|
547
|
+
error: released.saved ? normalized.error : released.error, agent: args.agent });
|
|
458
548
|
process.exit(1);
|
|
459
549
|
}
|
|
550
|
+
qaResult = normalized.outcome;
|
|
551
|
+
}
|
|
552
|
+
const persistence = parsed.handoff.status === 'complete'
|
|
553
|
+
? holdAgentExecutionForAdvance(projectDir, {
|
|
554
|
+
agent: args.agent, taskId: args['task-id'], resultStatus: parsed.handoff.status,
|
|
555
|
+
interactionId: args['interaction-id'] || null,
|
|
556
|
+
claimId: args['interaction-id'] ? args['relay-claim-id'] : (args['execution-claim-id'] || null),
|
|
557
|
+
binding: { provider: spawnProvider, provider_id: args['provider-id'] || null, model: promptResult.model,
|
|
558
|
+
reasoning_configuration: args['reasoning-configuration'] || null,
|
|
559
|
+
catalog_snapshot_ref: args['catalog-snapshot-ref'] || null },
|
|
560
|
+
...(qaResult ? { qaResult } : {}),
|
|
561
|
+
...(qaSubject ? { verifySubject: () => qaSubject.assertCurrent() } : {}),
|
|
562
|
+
})
|
|
563
|
+
: persistInteractiveState(projectDir, args, parsed.handoff, promptResult.model, spawnProvider);
|
|
564
|
+
if (!persistence.saved) {
|
|
565
|
+
outputResult({ status: 'error', code: persistence.code || 'SESSION_WRITE_FAILED', error: persistence.error, agent: args.agent });
|
|
566
|
+
process.exit(1);
|
|
567
|
+
}
|
|
568
|
+
if (parsed.handoff.status === 'complete' && !persistence.held) {
|
|
569
|
+
outputResult({ status: 'error', code: persistence.code || 'SESSION_WRITE_FAILED', error: persistence.error, agent: args.agent });
|
|
570
|
+
process.exit(1);
|
|
460
571
|
}
|
|
461
572
|
cleanupResponseFile(responseFile.filePath);
|
|
462
573
|
outputResult({
|
|
@@ -517,6 +628,9 @@ async function main() {
|
|
|
517
628
|
elapsed,
|
|
518
629
|
costEstimate,
|
|
519
630
|
});
|
|
631
|
+
// The provider's zero exit does not satisfy the handoff contract. Keep
|
|
632
|
+
// the relay/response recoverable, but report runner failure to its caller.
|
|
633
|
+
process.exit(1);
|
|
520
634
|
}
|
|
521
635
|
|
|
522
636
|
process.exit(handle.exitCode === 0 ? 0 : 1);
|
|
@@ -554,11 +668,18 @@ function persistInteractiveState(projectDir, args, handoff, model, provider) {
|
|
|
554
668
|
model,
|
|
555
669
|
reasoningConfiguration: args['reasoning-configuration'] || null,
|
|
556
670
|
catalogSnapshotRef: args['catalog-snapshot-ref'] || null,
|
|
557
|
-
advanceRelay: Boolean(args['
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
671
|
+
advanceRelay: Boolean(args['interaction-id'] && args['relay-claim-id']),
|
|
672
|
+
initialTaskContext: args['interaction-id'] ? undefined : args['additional-context'],
|
|
673
|
+
interactionId: args['interaction-id'] || null,
|
|
674
|
+
claimId: args['relay-claim-id'] || null,
|
|
561
675
|
});
|
|
676
|
+
if (!result.saved) {
|
|
677
|
+
// Keep the original response recoverable on a rejected/failed commit.
|
|
678
|
+
// Exact claim ownership prevents this release from touching a newer relay.
|
|
679
|
+
releasePendingForFailedRelay(projectDir, args);
|
|
680
|
+
return result;
|
|
681
|
+
}
|
|
682
|
+
recordEvent(projectDir, EventType.AGENT_WAITING_FOR_INPUT, args.agent, { task_id: args['task-id'] });
|
|
562
683
|
return result;
|
|
563
684
|
}
|
|
564
685
|
return clearPendingInteraction(projectDir, {
|
|
@@ -566,6 +687,7 @@ function persistInteractiveState(projectDir, args, handoff, model, provider) {
|
|
|
566
687
|
taskId: args['task-id'],
|
|
567
688
|
interactionId: args['interaction-id'] || null,
|
|
568
689
|
claimId: args['relay-claim-id'] || null,
|
|
690
|
+
requireClaim: Boolean(args['interaction-id']),
|
|
569
691
|
});
|
|
570
692
|
}
|
|
571
693
|
|