ai-foreman 1.7.29 → 2.0.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/README.md +1 -1
- package/dist/adapters/claude.d.ts +23 -12
- package/dist/adapters/claude.js +227 -23
- package/dist/adapters/codex.d.ts +17 -7
- package/dist/adapters/codex.js +195 -17
- package/dist/adapters/recovering.d.ts +2 -0
- package/dist/adapters/recovering.js +5 -1
- package/dist/adapters/structured.d.ts +71 -0
- package/dist/adapters/structured.js +213 -0
- package/dist/adapters/types.d.ts +85 -9
- package/dist/agentRun.d.ts +2 -0
- package/dist/agentRun.js +2 -1
- package/dist/boundedTurnEvidence.d.ts +18 -0
- package/dist/boundedTurnEvidence.js +76 -0
- package/dist/branch/currentGuard.d.ts +2 -0
- package/dist/branch/currentGuard.js +2 -0
- package/dist/branch/planner.js +1 -4
- package/dist/branch/runner.d.ts +2 -1
- package/dist/branch/runner.js +10 -7
- package/dist/buildAdmission.js +4 -0
- package/dist/buildApproval.d.ts +15 -0
- package/dist/buildApproval.js +25 -0
- package/dist/buildAssignment.d.ts +1 -0
- package/dist/buildAssignment.js +82 -23
- package/dist/buildInterventions.d.ts +2 -2
- package/dist/buildInterventions.js +11 -4
- package/dist/buildLaunchGate.js +3 -2
- package/dist/builderGuidanceFollowup.js +17 -6
- package/dist/cli/manager.js +22 -20
- package/dist/cli/start.js +22 -26
- package/dist/cli/tickets.d.ts +0 -7
- package/dist/cli/tickets.js +41 -40
- package/dist/continuity.d.ts +10 -14
- package/dist/continuity.js +198 -179
- package/dist/continuityOrigin.d.ts +13 -0
- package/dist/continuityOrigin.js +91 -0
- package/dist/foreman.d.ts +10 -4
- package/dist/foreman.js +51 -193
- package/dist/graph/boundary.d.ts +1 -1
- package/dist/graph/boundary.js +1 -1
- package/dist/graph/host.js +8 -19
- package/dist/graph/manager.d.ts +1 -1
- package/dist/graph/manager.js +1 -1
- package/dist/graph/preparation.d.ts +38 -0
- package/dist/graph/preparation.js +7 -6
- package/dist/graph/semantic.d.ts +1 -0
- package/dist/graph/semantic.js +4 -1
- package/dist/graph/session.js +7 -2
- package/dist/graph/turn.d.ts +2 -3
- package/dist/graph/turn.js +28 -40
- package/dist/handoffProtocol.js +2 -2
- package/dist/handoffs.d.ts +3 -1
- package/dist/handoffs.js +90 -59
- package/dist/hostGraphApplicability.d.ts +2 -1
- package/dist/hostGraphApplicability.js +17 -19
- package/dist/hostQaAugmentation.js +21 -4
- package/dist/hostQaComparison.d.ts +21 -2
- package/dist/hostQaComparison.js +43 -29
- package/dist/hostSourcePreservation.d.ts +38 -0
- package/dist/hostSourcePreservation.js +72 -0
- package/dist/interviews.d.ts +7 -0
- package/dist/interviews.js +18 -1
- package/dist/legacyCommunicationDisplay.d.ts +2 -0
- package/dist/legacyCommunicationDisplay.js +2 -0
- package/dist/managerPacket.js +3 -3
- package/dist/markers.d.ts +4 -3
- package/dist/markers.js +4 -20
- package/dist/probeIpc.d.ts +14 -0
- package/dist/probeIpc.js +29 -0
- package/dist/qaBuildGate.d.ts +21 -0
- package/dist/qaBuildGate.js +47 -12
- package/dist/qaBuilderCoverage.js +22 -13
- package/dist/qaBuilderEvidence.d.ts +16 -1
- package/dist/qaBuilderEvidence.js +48 -8
- package/dist/qaContractCoverage.d.ts +2 -2
- package/dist/qaContractCoverage.js +4 -9
- package/dist/qaContractDelivery.js +34 -20
- package/dist/qaCoverageAssembly.d.ts +22 -0
- package/dist/qaCoverageAssembly.js +16 -0
- package/dist/qaDeliveryJournal.d.ts +2 -0
- package/dist/qaDeliveryJournal.js +19 -0
- package/dist/qaFailureDelivery.js +97 -85
- package/dist/qaHostTransformation.d.ts +26 -0
- package/dist/qaHostTransformation.js +100 -0
- package/dist/qaPreparation.d.ts +8 -20
- package/dist/qaPreparation.js +133 -248
- package/dist/qaPreparationInputs.js +19 -0
- package/dist/qaPrerequisites.d.ts +2 -1
- package/dist/qaPrerequisites.js +21 -1
- package/dist/qaProtocolV2.d.ts +15 -0
- package/dist/qaRecovery.js +21 -4
- package/dist/qaReview.js +220 -395
- package/dist/qaReviewEvidence.d.ts +7 -3
- package/dist/qaReviewEvidence.js +15 -4
- package/dist/qaRuntime.js +1 -1
- package/dist/qaSnapshot.js +71 -21
- package/dist/qaStructuredResponse.d.ts +10 -0
- package/dist/qaStructuredResponse.js +57 -0
- package/dist/qaTimeline.js +5 -3
- package/dist/qaVerificationContract.d.ts +1 -2
- package/dist/qaVerificationContract.js +24 -35
- package/dist/readinessGate.js +5 -4
- package/dist/runtimeReadiness.js +23 -15
- package/dist/statusReporter.d.ts +2 -0
- package/dist/statusReporter.js +2 -0
- package/dist/structuredCutover.d.ts +31 -0
- package/dist/structuredCutover.js +510 -0
- package/dist/structuredCutoverFiles.d.ts +10 -0
- package/dist/structuredCutoverFiles.js +181 -0
- package/dist/structuredDispatchAuthority.d.ts +22 -0
- package/dist/structuredDispatchAuthority.js +110 -0
- package/dist/structuredDispatchRecord.d.ts +29 -0
- package/dist/structuredDispatchRecord.js +36 -0
- package/dist/structuredLiveBudget.d.ts +2 -0
- package/dist/structuredLiveBudget.js +23 -0
- package/dist/structuredOperation.d.ts +18 -0
- package/dist/structuredOperation.js +257 -0
- package/dist/supervisedStart.js +35 -21
- package/dist/ticketPlanning.d.ts +1 -373
- package/dist/ticketPlanning.js +2 -26
- package/dist/ticketPopulation.d.ts +0 -3
- package/dist/ticketPopulation.js +4 -21
- package/dist/ticketPopulationPageAuthority.d.ts +13 -0
- package/dist/ticketPopulationPageAuthority.js +30 -0
- package/dist/ticketPopulationPaging.d.ts +27 -0
- package/dist/ticketPopulationPaging.js +105 -0
- package/dist/tickets/renderMarkdown.d.ts +14 -0
- package/dist/tickets/renderMarkdown.js +39 -5
- package/dist/tickets/ticketLoader.js +10 -0
- package/dist/tickets/ticketSchema.d.ts +107 -0
- package/dist/tickets/ticketSchema.js +4 -1
- package/dist/workflowDb.d.ts +100 -2
- package/dist/workflowDb.js +763 -39
- package/dist/workflowReader.d.ts +1 -0
- package/dist/workflowReader.js +9 -0
- package/package.json +15 -7
package/dist/foreman.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { GraphPurpose } from "rafi-spec";
|
|
1
|
+
import type { GraphPurpose, OutputContract } from "rafi-spec";
|
|
2
2
|
import type { BuilderAdapter, PermissionHandler, PermissionRequest, TurnResult } from "./adapters/types.js";
|
|
3
3
|
import type { PermissionPolicy } from "./permissions/policy.js";
|
|
4
4
|
import { type AnsweredProviderQuestion } from "./providerQuestions.js";
|
|
@@ -67,7 +67,7 @@ export interface ForemanNotificationOptions {
|
|
|
67
67
|
desktop: boolean;
|
|
68
68
|
terminalBell: boolean;
|
|
69
69
|
}
|
|
70
|
-
/** Drives one builder through
|
|
70
|
+
/** Drives one builder through contracted native operations. */
|
|
71
71
|
export declare class BuildInterruptedError extends Error {
|
|
72
72
|
constructor();
|
|
73
73
|
}
|
|
@@ -133,14 +133,20 @@ export declare class Foreman {
|
|
|
133
133
|
/** Send user feedback on the plan; builder responds with a revised list. Does not count toward steps. */
|
|
134
134
|
sendPreflightFeedback(feedback: string): Promise<void>;
|
|
135
135
|
/** Send one custom instruction through the same needs_input loop as a batch turn. */
|
|
136
|
-
runInstruction(instruction: string, options
|
|
136
|
+
runInstruction(instruction: string, options: {
|
|
137
137
|
completion?: InstructionCompletion;
|
|
138
|
+
output: OutputContract;
|
|
138
139
|
}): Promise<{
|
|
139
140
|
result: TurnResult;
|
|
140
141
|
status: StepStatus;
|
|
141
142
|
}>;
|
|
143
|
+
/** The sole convenience path that selects the Builder's work-report contract. */
|
|
144
|
+
runBuilderWork(instruction: string): Promise<{
|
|
145
|
+
result: TurnResult;
|
|
146
|
+
status: StepStatus;
|
|
147
|
+
}>;
|
|
142
148
|
/** Ask an already-active role session to propose choices for its reported blocker. */
|
|
143
|
-
resolveBlocker(adapter: BuilderAdapter, reason: string, mode
|
|
149
|
+
resolveBlocker(adapter: BuilderAdapter, reason: string, mode: "builder" | "qa", output: OutputContract): Promise<{
|
|
144
150
|
result: TurnResult;
|
|
145
151
|
status: StepStatus;
|
|
146
152
|
}>;
|
package/dist/foreman.js
CHANGED
|
@@ -7,12 +7,13 @@ import { renderBuildWorkContext } from "./buildWorkContext.js";
|
|
|
7
7
|
import { loadTicketSetupConfigWithDefaults } from "./tickets/setupConfig.js";
|
|
8
8
|
import { deliverBuilderGuidanceFollowup } from "./builderGuidanceFollowup.js";
|
|
9
9
|
import { graphContext, sendGraphTurn } from "./graph/turn.js";
|
|
10
|
+
import { workReportOutput, preflightOutput, qaVerdictOutput } from "rafi-spec";
|
|
11
|
+
import { operationInstruction, operationStatus, operationPayload, nativePolicy, structuredTurnErrorMessage } from "./structuredOperation.js";
|
|
10
12
|
import { qaDigest } from "./qaProtocolV2.js";
|
|
11
13
|
import { BuildControlBoundary } from "./buildInterventions.js";
|
|
12
14
|
import { buildScopeRevision } from "./buildApproval.js";
|
|
13
15
|
import { durableHumanDecision, HumanDecisionRequired, HumanDecisionCancelled, servicePendingHumanDecisions, decisionResponse } from "./humanDecision.js";
|
|
14
16
|
import { select, text, isCancel } from "@clack/prompts";
|
|
15
|
-
import { MARKER_SPEC, QA_MARKER_SPEC } from "./markers.js";
|
|
16
17
|
import { countProviderQuestions, handleProviderQuestionTool } from "./providerQuestions.js";
|
|
17
18
|
import { bestEffortDiagnostic } from "./questionTrace.js";
|
|
18
19
|
import { signalAttention } from "./notify.js";
|
|
@@ -30,22 +31,6 @@ import { join, relative } from "node:path";
|
|
|
30
31
|
import { SessionUnavailableError, recoverableBuilderTurnError, sessionUnavailableErrorFromFailure } from "./adapters/sessionFailure.js";
|
|
31
32
|
import { WorkflowDb } from "./workflowDb.js";
|
|
32
33
|
import { assertBuildAssignmentReconciled, beginBuildAssignment, BuildAssignmentRejected, captureBuildSource, finishBuildAssignment, validateBuildAssignment } from "./buildAssignment.js";
|
|
33
|
-
/** Compare Builder product state without Rafi's own tracker bookkeeping. */
|
|
34
|
-
function builderProductDigest(source, hostOwnedPaths) {
|
|
35
|
-
const ignored = new Set(hostOwnedPaths.map(path => path.replace(/\\/g, "/").replace(/^\.\//, "")));
|
|
36
|
-
const hostOwned = (path) => {
|
|
37
|
-
const normalized = path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
38
|
-
return ignored.has(normalized)
|
|
39
|
-
|| [".foreman/", ".rafi/", ".tickets/", ".codex/", ".agents/", "docs-rafi/"]
|
|
40
|
-
.some(prefix => normalized.startsWith(prefix))
|
|
41
|
-
|| [".gitignore", "AGENTS.md", "rafi-config.yaml", "foreman.yaml"].includes(normalized);
|
|
42
|
-
};
|
|
43
|
-
const inventory = source.pathInventory
|
|
44
|
-
.filter(item => !hostOwned(item.path))
|
|
45
|
-
.map(item => ({ ...item, path: item.path.replace(/\\/g, "/") }))
|
|
46
|
-
.sort((a, b) => a.path.localeCompare(b.path));
|
|
47
|
-
return createHash("sha256").update(JSON.stringify(inventory)).digest("hex");
|
|
48
|
-
}
|
|
49
34
|
/** Phrases that suggest the builder ended its turn by asking the human. */
|
|
50
35
|
const QUESTION_HINTS = [
|
|
51
36
|
"should i",
|
|
@@ -155,7 +140,7 @@ export function buildPrimer(n, trackerPath, ticketsEnabled = false, preferredTic
|
|
|
155
140
|
`\n- Foreman records completion and regenerates the tracker after independent QA passes. Do not run ticket-complete commands or mark tracker records done yourself.` +
|
|
156
141
|
`\n- When available and permitted, \`foreman tickets discover --summary "..." --rationale "..."\` can log newly discovered work and \`foreman tickets update <id> --next-action "..."\` can record optional mid-turn notes.` +
|
|
157
142
|
`\n- If a ticket CLI is unavailable, include those notes in your response; it is not an implementation blocker.` +
|
|
158
|
-
`\n-
|
|
143
|
+
`\n- The host binds your result to the assigned ticket; report completion only for that exact work.`;
|
|
159
144
|
}
|
|
160
145
|
else if (trackerPath) {
|
|
161
146
|
trackerRule = `\n- After completing each ticket or step, update its status in the ticket progress tracker at \`${trackerPath}\`, following the Standard Update Workflow documented in that file.`;
|
|
@@ -170,14 +155,14 @@ export function buildPrimer(n, trackerPath, ticketsEnabled = false, preferredTic
|
|
|
170
155
|
|
|
171
156
|
Rules:
|
|
172
157
|
- Do exactly ONE ticket or step this turn, then stop.
|
|
173
|
-
-
|
|
174
|
-
- If a tool action is denied by foreman policy, do not retry the denied form; report it
|
|
158
|
+
- Return the result using the selected native JSON output contract.
|
|
159
|
+
- If a tool action is denied by foreman policy, do not retry the denied form; report it using the blocked result. The one exception is a required test that you changed by adding a pipe, redirection, substitution, or wrapper: retry the exact required-test command without that extra shell syntax before reporting it blocked.${trackerRule}${preferredTicketRule}
|
|
175
160
|
|
|
176
161
|
This is step 1 of ${n}. Implement the next ticket or step now.`;
|
|
177
162
|
}
|
|
178
163
|
/** Instruction sent on turns 2–N. */
|
|
179
164
|
export function buildNextStepInstruction(i, n) {
|
|
180
|
-
return `Implement the next ticket or step now (exactly one) — this is step ${i} of ${n}.
|
|
165
|
+
return `Implement the next ticket or step now (exactly one) — this is step ${i} of ${n}. Return the completed work-report through the selected native JSON contract.`;
|
|
181
166
|
}
|
|
182
167
|
/** QA review turn: ask the builder to triple-check the ticket or step it just completed. */
|
|
183
168
|
export function buildQaInstruction() {
|
|
@@ -194,10 +179,7 @@ The independent QA workspace is a disposable snapshot and may have a detached HE
|
|
|
194
179
|
|
|
195
180
|
If an otherwise required test is denied only because you added a pipe, redirection, substitution, or wrapper, retry the exact required-test command without that extra shell syntax. Do not retry other denied actions.
|
|
196
181
|
|
|
197
|
-
|
|
198
|
-
If anything is off, return the required RAFI_QA_FAILURE_REPORT_START/RAFI_QA_FAILURE_REPORT_END JSON envelope immediately before STEP_STATUS: qa_fail. The report must contain every check, every blocking finding, and all nonblocking observations. Do NOT fix issues on this turn — just report them. Foreman will instruct the Builder to fix them next.
|
|
199
|
-
|
|
200
|
-
${QA_MARKER_SPEC}`;
|
|
182
|
+
Return a native qa-verdict result. Use a passed payload only when verification succeeds. A failed payload must contain the full report: every check, blocking finding, and nonblocking observation. Do not fix issues during this review; the Builder receives failures separately.`;
|
|
201
183
|
}
|
|
202
184
|
/** Follow-up turn after qa_fail: have the builder implement the listed fixes. */
|
|
203
185
|
export function buildQaFixInstruction(issues) {
|
|
@@ -205,7 +187,7 @@ export function buildQaFixInstruction(issues) {
|
|
|
205
187
|
|
|
206
188
|
${issues}
|
|
207
189
|
|
|
208
|
-
Fix every one of them now.
|
|
190
|
+
Fix every one of them now. Submit the selected native remediation result so Foreman can rerun independent QA. Verify that your fixes resolve the reported issues.`;
|
|
209
191
|
}
|
|
210
192
|
/** Pre-flight planning turn: ask the builder to list its next N steps without implementing anything. */
|
|
211
193
|
export function buildPlanningTurn(n, ticketsContent, preferredTicketId) {
|
|
@@ -215,7 +197,7 @@ export function buildPlanningTurn(n, ticketsContent, preferredTicketId) {
|
|
|
215
197
|
const preferred = preferredTicketId
|
|
216
198
|
? ` The first item must be ticket ${preferredTicketId}; this is an interrupted ticket being resumed.`
|
|
217
199
|
: "";
|
|
218
|
-
return `${header}Before we begin, list the next ${n} ticket(s) or step(s) you plan to implement, in order.${preferred} Be specific — reference ticket IDs or titles where applicable. Do not implement anything yet;
|
|
200
|
+
return `${header}Before we begin, list the next ${n} ticket(s) or step(s) you plan to implement, in order.${preferred} Be specific — reference ticket IDs or titles where applicable. Do not implement anything yet; return the ordered steps through the build-preflight response.`;
|
|
219
201
|
}
|
|
220
202
|
/** Builds the permission callback: classify, log, allow or escalate. */
|
|
221
203
|
export function createPermissionHandler(policy, log, opts = {}) {
|
|
@@ -276,11 +258,11 @@ export function createPermissionHandler(policy, log, opts = {}) {
|
|
|
276
258
|
behavior: "deny",
|
|
277
259
|
message: `Foreman policy: this action needs human approval (${verdict.reason}) ` +
|
|
278
260
|
`and was NOT performed. Do not retry this exact action. An allow-listed alternative is permitted; for project tests use a literal in-worktree cd, project-local PYTHONPATH, existing virtualenv Python, or uv run --offline --no-sync with an allowed test command. If no permitted alternative can complete the step, ` +
|
|
279
|
-
`
|
|
261
|
+
`return a native blocked result explaining the required approval: ${verdict.reason}.`,
|
|
280
262
|
};
|
|
281
263
|
};
|
|
282
264
|
}
|
|
283
|
-
/** Drives one builder through
|
|
265
|
+
/** Drives one builder through contracted native operations. */
|
|
284
266
|
export class BuildInterruptedError extends Error {
|
|
285
267
|
constructor() { super("Build interrupted; current work is preserved for recovery"); this.name = "BuildInterruptedError"; }
|
|
286
268
|
}
|
|
@@ -392,7 +374,7 @@ export class Foreman {
|
|
|
392
374
|
* The returned result and status always reflect the final (non-needs_input) turn.
|
|
393
375
|
*/
|
|
394
376
|
async doTurn(instruction) {
|
|
395
|
-
return this.
|
|
377
|
+
return this.runBuilderWork(instruction);
|
|
396
378
|
}
|
|
397
379
|
async prepareBuilderBoundary(frozenAction) {
|
|
398
380
|
if (!this.builderFactory || !this.projectDir)
|
|
@@ -440,19 +422,24 @@ export class Foreman {
|
|
|
440
422
|
this.builder = await this.builderFactory(this.projectDir);
|
|
441
423
|
this.log.write("branch-session", { role: "builder", transition: "missing-session-fresh", continuityLost: true });
|
|
442
424
|
}
|
|
443
|
-
async doTurnWith(adapter, instruction, mode = "builder", completion) {
|
|
425
|
+
async doTurnWith(adapter, instruction, mode = "builder", output, completion) {
|
|
444
426
|
return this.projectDir
|
|
445
|
-
? withGraphSessionAccess(this.projectDir, adapter, () => this.doTurnWithOwned(adapter, instruction, mode, completion))
|
|
446
|
-
: this.doTurnWithOwned(adapter, instruction, mode, completion);
|
|
427
|
+
? withGraphSessionAccess(this.projectDir, adapter, () => this.doTurnWithOwned(adapter, instruction, mode, output, completion))
|
|
428
|
+
: this.doTurnWithOwned(adapter, instruction, mode, output, completion);
|
|
447
429
|
}
|
|
448
|
-
async doTurnWithOwned(adapter, instruction, mode = "builder", completion) {
|
|
430
|
+
async doTurnWithOwned(adapter, instruction, mode = "builder", output, completion) {
|
|
431
|
+
if (completion)
|
|
432
|
+
throw new Error("Text completion predicates cannot authorize native operation results");
|
|
449
433
|
const ticket = mode === "builder" ? this.currentTicketId : undefined;
|
|
434
|
+
const statusOf = (turn) => operationStatus(turn, output, ticket);
|
|
450
435
|
const hostToolObservations = [];
|
|
451
436
|
const scopedInstruction = (text) => !ticket || text.includes(`Ticket scope: ${ticket}.`) ? text
|
|
452
|
-
: `${text}\nTicket scope: ${ticket}. Do not substitute another ticket
|
|
437
|
+
: `${text}\nTicket scope: ${ticket}. Do not substitute another ticket.`;
|
|
453
438
|
instruction = scopedInstruction(instruction);
|
|
439
|
+
instruction += `\n\n${operationInstruction(output)}`;
|
|
454
440
|
const send = async (text, policy) => {
|
|
455
441
|
this.assertNotInterrupted();
|
|
442
|
+
policy = nativePolicy(output, { ...policy, dispatchId: undefined, journalRunId: this.qaRunId });
|
|
456
443
|
const context = ticket && this.projectDir ? renderBuildWorkContext(this.ticketForQa(0, ticket), loadTicketSetupConfigWithDefaults(this.projectDir).build.validation_checklist) : "";
|
|
457
444
|
const contractContext = ticket && this.projectDir ? await ensureBuilderContract(this.projectDir, this.qaRunId, ticket, this.builderWorktree ?? this.projectDir, adapter, this.qaFactory ? (...args) => this.createOwnedQa(...args) : undefined) : "";
|
|
458
445
|
this.assertNotInterrupted();
|
|
@@ -472,8 +459,8 @@ export class Foreman {
|
|
|
472
459
|
finally {
|
|
473
460
|
stopHostObservation();
|
|
474
461
|
}
|
|
475
|
-
const parsed =
|
|
476
|
-
const applicable = parsed
|
|
462
|
+
const parsed = statusOf(response);
|
|
463
|
+
const applicable = parsed;
|
|
477
464
|
if (!this.interruptionRequested && assignment && !response.isError && !response.failure && ["done", "plan_complete"].includes(applicable.kind))
|
|
478
465
|
await collectBuilderContractCoverage(this.projectDir, assignment.runId, assignment.ticketId, assignment.worktree, adapter, assignment.operationId, submitted, response, hostToolObservations, initialSourceDigest);
|
|
479
466
|
const rejection = assignment ? finishBuildAssignment(this.projectDir, assignment, response, applicable) : undefined;
|
|
@@ -496,60 +483,16 @@ export class Foreman {
|
|
|
496
483
|
this.builder = await this.beforeBuilderTurn(this.builder, instruction);
|
|
497
484
|
adapter = this.builder;
|
|
498
485
|
}
|
|
499
|
-
const turnPolicy = { logicalActionId:
|
|
486
|
+
const turnPolicy = { logicalActionId: randomUUID() };
|
|
500
487
|
let result = await send(instruction, turnPolicy);
|
|
501
488
|
await this.observeBuilderNative(adapter);
|
|
502
|
-
let status =
|
|
503
|
-
if (!result.isError && !result.failure) {
|
|
504
|
-
const completed = completion?.(result.text);
|
|
505
|
-
if (completed)
|
|
506
|
-
return { result, status: completed };
|
|
507
|
-
}
|
|
489
|
+
let status = statusOf(result);
|
|
508
490
|
if (ticket && validateBuildAssignment(ticket, status))
|
|
509
491
|
return { result, status };
|
|
510
492
|
if (result.isError || result.failure)
|
|
511
493
|
return { result: { ...result, isError: true }, status };
|
|
512
|
-
if (status.kind === "unknown")
|
|
513
|
-
|
|
514
|
-
status = { kind: "needs_input", question: lastLine(result.text), choices: ["Continue", "Cancel"] };
|
|
515
|
-
}
|
|
516
|
-
else {
|
|
517
|
-
if (adapter === this.builder && this.beforeBuilderTurn) {
|
|
518
|
-
this.builder = await this.beforeBuilderTurn(this.builder, instruction);
|
|
519
|
-
adapter = this.builder;
|
|
520
|
-
}
|
|
521
|
-
if (this.projectDir) {
|
|
522
|
-
const budget = new WorkflowDb(this.projectDir);
|
|
523
|
-
try {
|
|
524
|
-
if (!budget.reserveRecoveryAllowance(this.qaRunId, turnPolicy.logicalActionId, "protocol-correction", budget.autonomyPolicy(this.qaRunId)?.limits.protocolCorrections ?? 1))
|
|
525
|
-
return { result, status: { kind: "blocked", reason: "shared protocol correction budget exhausted" } };
|
|
526
|
-
}
|
|
527
|
-
finally {
|
|
528
|
-
budget.close();
|
|
529
|
-
}
|
|
530
|
-
}
|
|
531
|
-
const correction = mode === "qa"
|
|
532
|
-
? "Protocol correction only: based on the review already completed, return exactly one final STEP_STATUS: qa_pass or STEP_STATUS: qa_fail marker. Do not repeat QA, tests, tools, or compaction."
|
|
533
|
-
: "Protocol correction only: based on the work already completed, return exactly one final STEP_STATUS: done, plan_complete, blocked, or needs_input marker. Do not repeat implementation, tools, or compaction.";
|
|
534
|
-
result = await send([
|
|
535
|
-
correction,
|
|
536
|
-
`The previous marker was rejected: ${JSON.stringify(status.error ?? "missing final marker")}.`,
|
|
537
|
-
"STEP_STATUS is a separate plain-text line, not JSON. Do not append braces, commas, a code fence, or commentary after it. Keep the continuity JSON on its own preceding line.",
|
|
538
|
-
...(mode === "builder" ? [
|
|
539
|
-
`Only if the assigned work is complete, the exact marker form is: STEP_STATUS: done${ticket ? ` | ticket="${ticket}"` : ""}`,
|
|
540
|
-
`If blocked, use: STEP_STATUS: blocked | ${ticket ? `ticket="${ticket}" ` : ""}reason="the actual blocker"`,
|
|
541
|
-
"Choose the status supported by the work already performed; do not invent completion.",
|
|
542
|
-
] : ["If successful, use exactly: STEP_STATUS: qa_pass", "If failed, retain the required failure report and use exactly: STEP_STATUS: qa_fail"]),
|
|
543
|
-
].join("\n"), { ...turnPolicy, responseOnly: true });
|
|
544
|
-
await this.observeBuilderNative(adapter);
|
|
545
|
-
if (result.isError || result.failure)
|
|
546
|
-
return { result: { ...result, isError: true }, status: { kind: "blocked", reason: result.text } };
|
|
547
|
-
status = parseStepStatus(result.text);
|
|
548
|
-
if (status.kind === "unknown")
|
|
549
|
-
status = { ...status, error: `protocol correction exhausted: ${status.error ?? "missing final marker"}` };
|
|
550
|
-
}
|
|
551
|
-
}
|
|
552
|
-
let blockerExplanations = 0;
|
|
494
|
+
if (status.kind === "unknown")
|
|
495
|
+
return { result: { ...result, isError: true }, status };
|
|
553
496
|
while (true) {
|
|
554
497
|
if (ticket && validateBuildAssignment(ticket, status))
|
|
555
498
|
return { result, status };
|
|
@@ -632,58 +575,11 @@ export class Foreman {
|
|
|
632
575
|
await this.observeBuilderNative(adapter);
|
|
633
576
|
if (result.isError || result.failure)
|
|
634
577
|
return { result: { ...result, isError: true }, status: { kind: "blocked", reason: result.text } };
|
|
635
|
-
status =
|
|
636
|
-
if (completion) {
|
|
637
|
-
const completed = completion(result.text);
|
|
638
|
-
if (completed)
|
|
639
|
-
return { result, status: completed };
|
|
640
|
-
}
|
|
578
|
+
status = statusOf(result);
|
|
641
579
|
if (ticket && validateBuildAssignment(ticket, status))
|
|
642
580
|
return { result, status };
|
|
643
581
|
}
|
|
644
|
-
|
|
645
|
-
break;
|
|
646
|
-
if (blockerExplanations++ >= 1)
|
|
647
|
-
break;
|
|
648
|
-
if (this.projectDir) {
|
|
649
|
-
const budget = new WorkflowDb(this.projectDir);
|
|
650
|
-
try {
|
|
651
|
-
if (!budget.reserveRecoveryAllowance(this.qaRunId, this.currentTicketId ?? "instruction", "blocker-explanation", 1))
|
|
652
|
-
break;
|
|
653
|
-
}
|
|
654
|
-
finally {
|
|
655
|
-
budget.close();
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
const reason = status.reason ?? "the agent reported an unspecified blocker";
|
|
659
|
-
this.log.write("blocked-recovery", { reason, role: adapter === this.builder ? "builder" : "qa" });
|
|
660
|
-
const recoveryInstruction = buildBlockerRecoveryInstruction(reason);
|
|
661
|
-
if (adapter === this.builder && this.beforeBuilderTurn) {
|
|
662
|
-
this.builder = await this.beforeBuilderTurn(this.builder, scopedInstruction(recoveryInstruction));
|
|
663
|
-
adapter = this.builder;
|
|
664
|
-
}
|
|
665
|
-
result = await send(recoveryInstruction, turnPolicy);
|
|
666
|
-
await this.observeBuilderNative(adapter);
|
|
667
|
-
if (result.isError || result.failure)
|
|
668
|
-
return { result: { ...result, isError: true }, status: { kind: "blocked", reason: result.text } };
|
|
669
|
-
status = parseStepStatus(result.text);
|
|
670
|
-
if (status.kind === "unknown") {
|
|
671
|
-
if (this.projectDir && this.qaRunId) {
|
|
672
|
-
const budget = new WorkflowDb(this.projectDir);
|
|
673
|
-
try {
|
|
674
|
-
if (!budget.reserveRecoveryAllowance(this.qaRunId, turnPolicy.logicalActionId, "protocol-correction", budget.autonomyPolicy(this.qaRunId)?.limits.protocolCorrections ?? 1))
|
|
675
|
-
break;
|
|
676
|
-
}
|
|
677
|
-
finally {
|
|
678
|
-
budget.close();
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
result = await send('Protocol correction only: return the blocker approaches now using exactly one final STEP_STATUS: needs_input marker with question="..." and choices="recommended (Recommended)|alternative|alternative". Do not repeat tools or implementation.', { ...turnPolicy, responseOnly: true });
|
|
682
|
-
await this.observeBuilderNative(adapter);
|
|
683
|
-
if (result.isError || result.failure)
|
|
684
|
-
return { result: { ...result, isError: true }, status: { kind: "blocked", reason: result.text } };
|
|
685
|
-
status = parseStepStatus(result.text);
|
|
686
|
-
}
|
|
582
|
+
break;
|
|
687
583
|
}
|
|
688
584
|
return { result, status };
|
|
689
585
|
}
|
|
@@ -694,7 +590,8 @@ export class Foreman {
|
|
|
694
590
|
if (this.beforeBuilderTurn)
|
|
695
591
|
this.builder = await this.beforeBuilderTurn(this.builder, instruction);
|
|
696
592
|
this.assertNotInterrupted();
|
|
697
|
-
const
|
|
593
|
+
const policy = nativePolicy(preflightOutput, { purpose: "planning", journalRunId: this.qaRunId });
|
|
594
|
+
const result = this.projectDir ? await sendGraphTurn(this.builder, `${instruction}\n\n${operationInstruction(preflightOutput)}`, graphContext(this.projectDir, this.builderWorktree ?? this.projectDir, "build-preflight", instruction), policy) : await this.builder.sendTurn(instruction, policy);
|
|
698
595
|
this.assertNotInterrupted();
|
|
699
596
|
await this.observeBuilderNative(this.builder);
|
|
700
597
|
this.log.write("preflight", {
|
|
@@ -703,9 +600,9 @@ export class Foreman {
|
|
|
703
600
|
isError: result.isError,
|
|
704
601
|
});
|
|
705
602
|
if (result.isError)
|
|
706
|
-
throw new Error(result
|
|
707
|
-
this.preflightPlan = result.
|
|
708
|
-
return
|
|
603
|
+
throw new Error(structuredTurnErrorMessage(result, "Build preflight"));
|
|
604
|
+
this.preflightPlan = operationPayload(result, preflightOutput).steps.map((step, index) => `${index + 1}. ${step.ticket ? step.ticket + ": " : ""}${step.work}`).join("\n");
|
|
605
|
+
return this.preflightPlan;
|
|
709
606
|
}
|
|
710
607
|
/** Send user feedback on the plan; builder responds with a revised list. Does not count toward steps. */
|
|
711
608
|
async sendPreflightFeedback(feedback) {
|
|
@@ -713,21 +610,26 @@ export class Foreman {
|
|
|
713
610
|
if (this.beforeBuilderTurn)
|
|
714
611
|
this.builder = await this.beforeBuilderTurn(this.builder, feedback);
|
|
715
612
|
this.assertNotInterrupted();
|
|
716
|
-
const
|
|
613
|
+
const policy = nativePolicy(preflightOutput, { purpose: "planning", journalRunId: this.qaRunId });
|
|
614
|
+
const result = this.projectDir ? await sendGraphTurn(this.builder, feedback, graphContext(this.projectDir, this.builderWorktree ?? this.projectDir, "build-preflight", feedback), policy) : await this.builder.sendTurn(feedback, policy);
|
|
717
615
|
this.assertNotInterrupted();
|
|
718
616
|
await this.observeBuilderNative(this.builder);
|
|
719
617
|
this.log.write("preflight", { feedback: true, costUsd: result.costUsd, isError: result.isError });
|
|
720
618
|
if (result.isError)
|
|
721
|
-
throw new Error(result
|
|
722
|
-
this.preflightPlan = result.
|
|
619
|
+
throw new Error(structuredTurnErrorMessage(result, "Build preflight"));
|
|
620
|
+
this.preflightPlan = operationPayload(result, preflightOutput).steps.map((step, index) => `${index + 1}. ${step.ticket ? step.ticket + ": " : ""}${step.work}`).join("\n");
|
|
723
621
|
}
|
|
724
622
|
/** Send one custom instruction through the same needs_input loop as a batch turn. */
|
|
725
|
-
async runInstruction(instruction, options
|
|
726
|
-
return this.doTurnWith(this.builder, instruction, "builder", options.completion);
|
|
623
|
+
async runInstruction(instruction, options) {
|
|
624
|
+
return this.doTurnWith(this.builder, instruction, "builder", options.output, options.completion);
|
|
625
|
+
}
|
|
626
|
+
/** The sole convenience path that selects the Builder's work-report contract. */
|
|
627
|
+
async runBuilderWork(instruction) {
|
|
628
|
+
return this.doTurnWith(this.builder, instruction, "builder", workReportOutput);
|
|
727
629
|
}
|
|
728
630
|
/** Ask an already-active role session to propose choices for its reported blocker. */
|
|
729
|
-
async resolveBlocker(adapter, reason, mode
|
|
730
|
-
return this.doTurnWith(adapter, buildBlockerRecoveryInstruction(reason), mode);
|
|
631
|
+
async resolveBlocker(adapter, reason, mode, output) {
|
|
632
|
+
return this.doTurnWith(adapter, buildBlockerRecoveryInstruction(reason), mode, output);
|
|
731
633
|
}
|
|
732
634
|
async observeBuilderNative(adapter) {
|
|
733
635
|
if (adapter === this.builder)
|
|
@@ -807,7 +709,7 @@ export class Foreman {
|
|
|
807
709
|
},
|
|
808
710
|
evidence: ({ cycle, outcome, detail, qaDiff }) => this.log.write("qa", { stepIndex, cycle, outcome, detail, qaDiff, disposable: true }),
|
|
809
711
|
onNonconvergence: this.qaNonconvergence,
|
|
810
|
-
resolveBlocked: (adapter, reason) => this.resolveBlocker(adapter, reason, "qa"),
|
|
712
|
+
resolveBlocked: (adapter, reason) => this.resolveBlocker(adapter, reason, "qa", qaVerdictOutput),
|
|
811
713
|
});
|
|
812
714
|
if (resumedRecovery) {
|
|
813
715
|
const recoveryDb = new WorkflowDb(this.projectDir);
|
|
@@ -1118,7 +1020,7 @@ export class Foreman {
|
|
|
1118
1020
|
? buildPrimer(n, trackerPath, this.ticketsEnabled, pendingTicketId)
|
|
1119
1021
|
: buildNextStepInstruction(i, n);
|
|
1120
1022
|
if (pendingTicketId)
|
|
1121
|
-
instruction += `\n\nAssigned ticket: ${pendingTicketId}. Implement exactly this ticket. Do not substitute another ticket.
|
|
1023
|
+
instruction += `\n\nAssigned ticket: ${pendingTicketId}. Implement exactly this ticket. Do not substitute another ticket. Return the native work report for this assigned ticket.`;
|
|
1122
1024
|
const continuations = answered.filter(decision => decision.interruptionId === `ticket:${pendingTicketId}`);
|
|
1123
1025
|
const continuationKeys = [];
|
|
1124
1026
|
if (continuations.length) {
|
|
@@ -1140,14 +1042,6 @@ export class Foreman {
|
|
|
1140
1042
|
}
|
|
1141
1043
|
if (i > 1)
|
|
1142
1044
|
await this.prepareBuilderBoundary(instruction);
|
|
1143
|
-
// Current-branch builds do not always populate builderWorktree. The
|
|
1144
|
-
// project directory is still the authoritative source boundary, so a
|
|
1145
|
-
// plan-only resumed turn must be compared there before QA can run.
|
|
1146
|
-
const builderSourceRoot = this.builderWorktree ?? this.projectDir;
|
|
1147
|
-
const hostOwnedSourcePaths = this.projectDir && this.ticketsEnabled
|
|
1148
|
-
? generatedTrackerDirtyPaths(loadTicketsConfig(this.projectDir).paths)
|
|
1149
|
-
: [];
|
|
1150
|
-
const sourceBeforeTurn = builderSourceRoot ? captureBuildSource(builderSourceRoot) : undefined;
|
|
1151
1045
|
this.builderWorkSessions += 1;
|
|
1152
1046
|
const turn = () => this.doTurn(instruction);
|
|
1153
1047
|
let response;
|
|
@@ -1190,41 +1084,6 @@ export class Foreman {
|
|
|
1190
1084
|
detail = `builder turn errored: ${result.text.slice(0, 200)}`;
|
|
1191
1085
|
break;
|
|
1192
1086
|
}
|
|
1193
|
-
// A resumed provider can answer the mandatory primer with a planning
|
|
1194
|
-
// response while still emitting a done marker. Do not send unchanged
|
|
1195
|
-
// source to QA or mark the ticket complete. Retry the bounded
|
|
1196
|
-
// implementation once; if it still produces no work, stop as a real
|
|
1197
|
-
// Builder blocker.
|
|
1198
|
-
if (["done", "plan_complete"].includes(status.kind) && pendingTicketId && sourceBeforeTurn && builderSourceRoot) {
|
|
1199
|
-
const sourceAfterTurn = captureBuildSource(builderSourceRoot);
|
|
1200
|
-
const beforeProductDigest = builderProductDigest(sourceBeforeTurn, hostOwnedSourcePaths);
|
|
1201
|
-
const afterProductDigest = builderProductDigest(sourceAfterTurn, hostOwnedSourcePaths);
|
|
1202
|
-
// plan_complete is explicitly a planning response. Even if the
|
|
1203
|
-
// provider touched bookkeeping while planning, never send that state
|
|
1204
|
-
// to QA as if implementation were complete.
|
|
1205
|
-
const responseIsPlanningOnly = /\bplan(?:ning)?\s+only\b|\bdo not implement(?: yet)?\b|\bawait(?:ing)? (?:approval|go-ahead)\b/i.test(result.text)
|
|
1206
|
-
&& !/\bimplemented\b|\badded\b|\bchanged\b/i.test(result.text);
|
|
1207
|
-
if (status.kind === "plan_complete" || responseIsPlanningOnly || afterProductDigest === beforeProductDigest) {
|
|
1208
|
-
this.log.write("step", { index: i, statusKind: "plan_only_without_source_change", ticket: pendingTicketId });
|
|
1209
|
-
const retry = await this.doTurn(`The previous turn did not complete implementation. Implement the assigned ticket now: ${pendingTicketId}. Inspect the current files, make the required changes and tests, then report truthful evidence with STEP_STATUS: done | ticket="${pendingTicketId}" summary="...". Do not return another plan-only response.`);
|
|
1210
|
-
result = retry.result;
|
|
1211
|
-
status = retry.status;
|
|
1212
|
-
if (result.isError) {
|
|
1213
|
-
const recoverable = recoverableBuilderTurnError(result);
|
|
1214
|
-
if (recoverable)
|
|
1215
|
-
throw recoverable;
|
|
1216
|
-
outcome = "blocked";
|
|
1217
|
-
detail = `builder implementation retry errored: ${result.text.slice(0, 200)}`;
|
|
1218
|
-
break;
|
|
1219
|
-
}
|
|
1220
|
-
const sourceAfterRetry = captureBuildSource(builderSourceRoot);
|
|
1221
|
-
if (status.kind === "plan_complete" || (status.kind === "done" && builderProductDigest(sourceAfterRetry, hostOwnedSourcePaths) === beforeProductDigest)) {
|
|
1222
|
-
outcome = "needs-human";
|
|
1223
|
-
detail = `Builder returned planning-only responses without changing product source for ${pendingTicketId}`;
|
|
1224
|
-
break;
|
|
1225
|
-
}
|
|
1226
|
-
}
|
|
1227
|
-
}
|
|
1228
1087
|
if (status.kind === "done" || status.kind === "plan_complete") {
|
|
1229
1088
|
let qaSummary;
|
|
1230
1089
|
let qaWaived = false;
|
|
@@ -1368,7 +1227,7 @@ export class Foreman {
|
|
|
1368
1227
|
? status.error
|
|
1369
1228
|
: looksLikeQuestion(result.text)
|
|
1370
1229
|
? `builder ended with a question: ${lastLine(result.text)}`
|
|
1371
|
-
: "builder did not
|
|
1230
|
+
: "builder did not submit a valid native operation result";
|
|
1372
1231
|
break;
|
|
1373
1232
|
}
|
|
1374
1233
|
if (completed >= n && this.projectDir) {
|
|
@@ -1402,8 +1261,7 @@ function buildBlockerRecoveryInstruction(reason) {
|
|
|
1402
1261
|
`You reported this blocker: ${reason}`,
|
|
1403
1262
|
"Do not end the run. Analyze the durable state and propose two or three safe, materially different approaches the user can choose from.",
|
|
1404
1263
|
"Put your recommended approach first and end its label with (Recommended). Include the important consequence or tradeoff in every choice label.",
|
|
1405
|
-
"Do not perform more implementation or QA work until the user chooses.
|
|
1406
|
-
'STEP_STATUS: needs_input | question="How should Rafi unblock this work?" choices="recommended approach and consequence (Recommended)|alternative and consequence|another alternative and consequence"',
|
|
1264
|
+
"Do not perform more implementation or QA work until the user chooses. Return a native needs-input result with the question and concrete choices.",
|
|
1407
1265
|
"After the user answers, apply that guidance in this same session. If still blocked, report blocked again so Rafi can offer a new set of approaches.",
|
|
1408
1266
|
].join("\n\n");
|
|
1409
1267
|
}
|
package/dist/graph/boundary.d.ts
CHANGED
|
@@ -11,5 +11,5 @@ export interface GraphPendingTask {
|
|
|
11
11
|
}
|
|
12
12
|
/** A completed editing assignment plus host-observed source change is required.
|
|
13
13
|
* Changed bytes alone are deliberately insufficient to establish this trigger. */
|
|
14
|
-
export declare function queueGraphMaintenance(root: string, workspace: string, logicalTaskId: string, response: string, sourceChanged: boolean): void;
|
|
14
|
+
export declare function queueGraphMaintenance(root: string, workspace: string, logicalTaskId: string, response: string | boolean, sourceChanged: boolean): void;
|
|
15
15
|
export declare function maintainGraphTask(root: string, workspace: string, logicalTaskId: string, host: BuilderAdapter): Promise<unknown>;
|
package/dist/graph/boundary.js
CHANGED
|
@@ -9,7 +9,7 @@ export const GRAPH_CHANGE_SIGNAL = "RAFI_GRAPH_MAINTENANCE: meaningful-indexed-c
|
|
|
9
9
|
/** A completed editing assignment plus host-observed source change is required.
|
|
10
10
|
* Changed bytes alone are deliberately insufficient to establish this trigger. */
|
|
11
11
|
export function queueGraphMaintenance(root, workspace, logicalTaskId, response, sourceChanged) {
|
|
12
|
-
if (!sourceChanged || !response.split(/\r?\n/).some(line => line.trim() === GRAPH_CHANGE_SIGNAL))
|
|
12
|
+
if (!sourceChanged || !(typeof response === "boolean" ? response : response.split(/\r?\n/).some(line => line.trim() === GRAPH_CHANGE_SIGNAL)))
|
|
13
13
|
return;
|
|
14
14
|
const effective = loadGraphConfig(root);
|
|
15
15
|
if (!effective.enabled || effective.config?.maintenance !== "selective")
|
package/dist/graph/host.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { semanticExtractionOutput } from "rafi-spec";
|
|
2
|
+
import { nativePolicy, operationInstruction, operationPayload, journalStructuredDispatch } from "../structuredOperation.js";
|
|
1
3
|
import { mkdirSync, mkdtempSync, rmSync } from "node:fs";
|
|
2
4
|
import { join } from "node:path";
|
|
3
5
|
import { ClaudeAdapter } from "../adapters/claude.js";
|
|
4
6
|
import { CodexAdapter } from "../adapters/codex.js";
|
|
5
7
|
import { WorkflowDb } from "../workflowDb.js";
|
|
6
|
-
import {
|
|
8
|
+
import { validateSemanticResult, semanticExchange, SEMANTIC_INSTRUCTIONS } from "./semantic.js";
|
|
7
9
|
import { canonical, confined, digest, bytesDigest } from "./util.js";
|
|
8
10
|
/** Fresh packet-only worker, using the selected host's exact runtime settings. */
|
|
9
11
|
export function graphSemanticHost(root, host, createWorker = async (options) => host.agent === "codex" ? new CodexAdapter(options) : ClaudeAdapter.create(options)) {
|
|
@@ -101,29 +103,16 @@ export function graphSemanticHost(root, host, createWorker = async (options) =>
|
|
|
101
103
|
store.put("job", id, { version: 1, state: "returned-unvalidated", operationId, requestId: request.operationId,
|
|
102
104
|
inputDigest: request.inputDigest, sessionId: worker?.sessionId(),
|
|
103
105
|
observations: observations.map(t => ({ turnId: t.turnId, usage: t.usage, costUsd: t.costAuthoritative ? t.costUsd : undefined,
|
|
104
|
-
responseDigest: bytesDigest(t.rawResponse ?? t.text), failed: Boolean(t.isError || t.failure) })) });
|
|
106
|
+
responseDigest: bytesDigest(JSON.stringify({ structured: t.structured, rawResponse: t.rawResponse ?? t.text })), failed: Boolean(t.isError || t.failure) })) });
|
|
105
107
|
};
|
|
106
|
-
const turn = await Promise.race([worker
|
|
108
|
+
const turn = await Promise.race([journalStructuredDispatch(root, worker, `${canonical(request)}\n${operationInstruction(semanticExtractionOutput)}`, nativePolicy(semanticExtractionOutput, { responseOnly: true, handback: true, logicalActionId: request.operationId }), (prompt, policy) => worker.sendTurn(prompt, policy)), stopped]);
|
|
107
109
|
recordObservation(turn);
|
|
108
110
|
check();
|
|
109
|
-
accountOutput(turn.cleanedResponse ?? turn.text);
|
|
110
111
|
if (turn.isError || turn.failure)
|
|
111
112
|
throw new Error(`Semantic provider failed: ${turn.text.slice(0, 500)}`);
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
catch (error) {
|
|
117
|
-
check();
|
|
118
|
-
store.put("job", id, { version: 1, state: "repair-intended", operationId, requestId: request.operationId, inputDigest: request.inputDigest, initialResponseDigest: bytesDigest(turn.rawResponse ?? turn.text), observations: observations.map(t => ({ turnId: t.turnId, usage: t.usage, costUsd: t.costAuthoritative ? t.costUsd : undefined })) });
|
|
119
|
-
const repaired = await Promise.race([worker.sendTurn(`Format correction only. Preserve your source observations; no tools or new extraction. Return strict JSON matching ${canonical({ operationId: request.operationId, inputDigest: request.inputDigest })}. Validation: ${String(error)}`, { responseOnly: true, handback: true, logicalActionId: request.operationId }), stopped]);
|
|
120
|
-
recordObservation(repaired);
|
|
121
|
-
check();
|
|
122
|
-
accountOutput(repaired.cleanedResponse ?? repaired.text);
|
|
123
|
-
if (repaired.isError || repaired.failure)
|
|
124
|
-
throw new Error("Semantic format correction failed; reconciliation required");
|
|
125
|
-
parsed = parseSemanticResult(repaired.cleanedResponse ?? repaired.text, request);
|
|
126
|
-
}
|
|
113
|
+
const payload = operationPayload(turn, semanticExtractionOutput);
|
|
114
|
+
accountOutput(JSON.stringify(payload));
|
|
115
|
+
const parsed = validateSemanticResult(payload, request);
|
|
127
116
|
const known = observations.every(t => t.usage?.scope === "turn-delta" && t.usage.inputTokens !== undefined && t.usage.outputTokens !== undefined);
|
|
128
117
|
const authoritative = observations.every(t => t.costAuthoritative === true);
|
|
129
118
|
store.put("job", id, { version: 1, state: "completed", operationId, sessionId: worker.sessionId(),
|
package/dist/graph/manager.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ManagerGraphRequestV3 } from "rafi-spec";
|
|
2
2
|
import type { GraphDerivedAccess } from "./derived.js";
|
|
3
|
-
export declare const MANAGER_GRAPH_GUIDANCE = "For substantial architecture/dependency questions, request graph evidence through
|
|
3
|
+
export declare const MANAGER_GRAPH_GUIDANCE = "For substantial architecture/dependency questions, request graph evidence through a native evidence-request result with evidenceType graph whose request is: {\"kind\":\"manager_graph_evidence_request\",\"version\":3,\"requestId\":\"unique-id\",\"operations\":[{\"operation\":\"query\",\"query\":\"specific source concepts\"}]}. Submit one result per turn. This shares your existing evidence lookup budget. Only the host chooses project/source scope. An optional receiptId can select previously delivered evidence belonging to the host-selected run; unavailable historical caches stay unavailable. Current-source graph evidence cannot establish a historical run's implementation or cause. Graph output never authorizes controls.";
|
|
4
4
|
export declare function parseManagerGraphRequest(text: string): ManagerGraphRequestV3 | undefined;
|
|
5
5
|
export declare function createManagerGraphEvidence(configRoot: string, runId?: string): {
|
|
6
6
|
guidance: string;
|
package/dist/graph/manager.js
CHANGED
|
@@ -4,7 +4,7 @@ import { loadGraphConfig } from "./config.js";
|
|
|
4
4
|
import { captureGraphCorpus } from "./corpus.js";
|
|
5
5
|
import { acquireGraphView, graphUnavailable, readGraph } from "./read.js";
|
|
6
6
|
import { canonical } from "./util.js";
|
|
7
|
-
export const MANAGER_GRAPH_GUIDANCE = `For substantial architecture/dependency questions, request graph evidence through
|
|
7
|
+
export const MANAGER_GRAPH_GUIDANCE = `For substantial architecture/dependency questions, request graph evidence through a native evidence-request result with evidenceType graph whose request is: {"kind":"manager_graph_evidence_request","version":3,"requestId":"unique-id","operations":[{"operation":"query","query":"specific source concepts"}]}. Submit one result per turn. This shares your existing evidence lookup budget. Only the host chooses project/source scope. An optional receiptId can select previously delivered evidence belonging to the host-selected run; unavailable historical caches stay unavailable. Current-source graph evidence cannot establish a historical run's implementation or cause. Graph output never authorizes controls.`;
|
|
8
8
|
export function parseManagerGraphRequest(text) {
|
|
9
9
|
if (Buffer.byteLength(text) > 8192 || !text.trim().startsWith("{"))
|
|
10
10
|
return undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { BuilderAdapter } from "../adapters/types.js";
|
|
2
|
+
import type { OutputContract } from "rafi-spec";
|
|
2
3
|
/** One reserved preparation operation owns all optional evidence subturns. */
|
|
3
4
|
export declare function sendPreparationGraphTurn(adapter: BuilderAdapter, instruction: string, input: {
|
|
4
5
|
phase: "planner" | "prepare" | "assess" | "challenge" | "repair";
|
|
@@ -10,8 +11,10 @@ export declare function sendPreparationGraphTurn(adapter: BuilderAdapter, instru
|
|
|
10
11
|
workId: string;
|
|
11
12
|
operationId: string;
|
|
12
13
|
remainingMs: number;
|
|
14
|
+
output?: OutputContract;
|
|
13
15
|
}): Promise<{
|
|
14
16
|
graphReceiptRefs: string[];
|
|
17
|
+
transportEvidence?: import("../adapters/types.js").RetainedTransportEvidence;
|
|
15
18
|
text: string;
|
|
16
19
|
isError: boolean;
|
|
17
20
|
numTurns: number;
|
|
@@ -29,6 +32,7 @@ export declare function sendPreparationGraphTurn(adapter: BuilderAdapter, instru
|
|
|
29
32
|
failure?: import("../adapters/types.js").RuntimeFailure;
|
|
30
33
|
turnId?: string;
|
|
31
34
|
continuityErrors?: string[];
|
|
35
|
+
continuity?: import("rafi-spec").ContinuityDelta;
|
|
32
36
|
hostInstruction?: string;
|
|
33
37
|
providerInstruction?: string;
|
|
34
38
|
rawResponse?: string;
|
|
@@ -38,4 +42,38 @@ export declare function sendPreparationGraphTurn(adapter: BuilderAdapter, instru
|
|
|
38
42
|
sessionId?: string;
|
|
39
43
|
sessionRef?: import("rafi-spec").ProviderSessionRefV1;
|
|
40
44
|
};
|
|
45
|
+
structured: import("rafi-spec").StructuredResponse;
|
|
46
|
+
selectedDispatch: import("rafi-spec").SelectedDispatchTupleV2;
|
|
47
|
+
} | {
|
|
48
|
+
graphReceiptRefs: string[];
|
|
49
|
+
transportEvidence?: import("../adapters/types.js").RetainedTransportEvidence;
|
|
50
|
+
text: string;
|
|
51
|
+
isError: boolean;
|
|
52
|
+
numTurns: number;
|
|
53
|
+
costUsd: number;
|
|
54
|
+
costAuthoritative?: boolean;
|
|
55
|
+
inputTokens?: number;
|
|
56
|
+
outputTokens?: number;
|
|
57
|
+
usage?: {
|
|
58
|
+
scope: "turn-delta" | "session-cumulative";
|
|
59
|
+
inputTokens?: number;
|
|
60
|
+
outputTokens?: number;
|
|
61
|
+
totalTokens?: number;
|
|
62
|
+
costUsd?: number;
|
|
63
|
+
};
|
|
64
|
+
failure?: import("../adapters/types.js").RuntimeFailure;
|
|
65
|
+
turnId?: string;
|
|
66
|
+
continuityErrors?: string[];
|
|
67
|
+
continuity?: import("rafi-spec").ContinuityDelta;
|
|
68
|
+
hostInstruction?: string;
|
|
69
|
+
providerInstruction?: string;
|
|
70
|
+
rawResponse?: string;
|
|
71
|
+
cleanedResponse?: string;
|
|
72
|
+
providerMetadata?: {
|
|
73
|
+
provider: "claude" | "codex";
|
|
74
|
+
sessionId?: string;
|
|
75
|
+
sessionRef?: import("rafi-spec").ProviderSessionRefV1;
|
|
76
|
+
};
|
|
77
|
+
structured?: undefined;
|
|
78
|
+
selectedDispatch?: import("rafi-spec").SelectedDispatchTupleV2;
|
|
41
79
|
}>;
|