ai-foreman 1.7.12 → 1.7.14
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 +47 -2
- package/dist/activity.d.ts +5 -1
- package/dist/activity.js +1 -1
- package/dist/adapters/claude.d.ts +12 -0
- package/dist/adapters/claude.js +255 -23
- package/dist/adapters/codex.d.ts +7 -0
- package/dist/adapters/codex.js +149 -17
- package/dist/adapters/recovering.d.ts +4 -0
- package/dist/adapters/recovering.js +17 -1
- package/dist/adapters/types.d.ts +69 -1
- package/dist/agentRun.d.ts +2 -0
- package/dist/agentRun.js +28 -4
- package/dist/branch/currentGuard.d.ts +2 -0
- package/dist/branch/currentGuard.js +9 -0
- package/dist/branch/finalization.d.ts +22 -0
- package/dist/branch/finalization.js +120 -0
- package/dist/branch/git.js +7 -2
- package/dist/branch/resume.js +11 -1
- package/dist/branch/runner.d.ts +12 -3
- package/dist/branch/runner.js +381 -125
- package/dist/buildRuns.d.ts +7 -2
- package/dist/buildRuns.js +109 -35
- package/dist/cli/doctor.d.ts +7 -0
- package/dist/cli/doctor.js +84 -1
- package/dist/cli/manager.d.ts +17 -0
- package/dist/cli/manager.js +135 -0
- package/dist/cli/recovery.d.ts +4 -0
- package/dist/cli/recovery.js +80 -0
- package/dist/cli/start.js +543 -79
- package/dist/cli/state.d.ts +4 -0
- package/dist/cli/state.js +69 -0
- package/dist/cli/status.js +13 -2
- package/dist/cli/tickets.js +26 -8
- package/dist/config.d.ts +2 -0
- package/dist/config.js +23 -0
- package/dist/continuity.d.ts +4 -0
- package/dist/continuity.js +38 -10
- package/dist/diagnostics.d.ts +25 -0
- package/dist/diagnostics.js +428 -0
- package/dist/foreman.d.ts +34 -6
- package/dist/foreman.js +164 -90
- package/dist/handoffs.d.ts +10 -3
- package/dist/handoffs.js +94 -10
- package/dist/index.js +8 -0
- package/dist/log.d.ts +2 -2
- package/dist/log.js +4 -1
- package/dist/managerPacket.d.ts +25 -0
- package/dist/managerPacket.js +83 -0
- package/dist/markers.d.ts +1 -1
- package/dist/markers.js +4 -1
- package/dist/observability.d.ts +248 -0
- package/dist/observability.js +1018 -0
- package/dist/planChanges.d.ts +4 -0
- package/dist/planChanges.js +16 -0
- package/dist/projectDiagnostics.d.ts +27 -0
- package/dist/projectDiagnostics.js +407 -0
- package/dist/qaFailureDelivery.d.ts +100 -0
- package/dist/qaFailureDelivery.js +570 -0
- package/dist/qaProtocolV2.d.ts +233 -0
- package/dist/qaProtocolV2.js +114 -0
- package/dist/qaRecovery.d.ts +152 -0
- package/dist/qaRecovery.js +989 -0
- package/dist/qaReview.d.ts +153 -14
- package/dist/qaReview.js +1747 -71
- package/dist/qaRuntime.d.ts +14 -0
- package/dist/qaRuntime.js +44 -0
- package/dist/qaSnapshot.d.ts +65 -5
- package/dist/qaSnapshot.js +473 -130
- package/dist/recoveryPolicy.d.ts +37 -0
- package/dist/recoveryPolicy.js +251 -0
- package/dist/roles.js +1 -0
- package/dist/stateTransfer.d.ts +57 -0
- package/dist/stateTransfer.js +644 -0
- package/dist/statusReporter.d.ts +2 -0
- package/dist/statusReporter.js +6 -0
- package/dist/supervisor.d.ts +40 -0
- package/dist/supervisor.js +102 -0
- package/dist/ticketHistoryReader.d.ts +19 -0
- package/dist/ticketHistoryReader.js +58 -0
- package/dist/tickets/details.d.ts +8 -0
- package/dist/tickets/details.js +59 -11
- package/dist/tickets/output.d.ts +5 -0
- package/dist/tickets/output.js +73 -0
- package/dist/tickets/stateDb.d.ts +1 -0
- package/dist/tickets/stateDb.js +5 -0
- package/dist/workflowDb.d.ts +275 -3
- package/dist/workflowDb.js +839 -29
- package/dist/workflowReader.d.ts +42 -0
- package/dist/workflowReader.js +171 -0
- package/foreman.yaml +9 -0
- package/package.json +63 -3
package/dist/adapters/codex.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
3
|
import { existsSync, readFileSync } from "node:fs";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { createInterface } from "node:readline";
|
|
@@ -51,6 +52,9 @@ export class CodexAdapter {
|
|
|
51
52
|
nativeCompactionSequence = 0;
|
|
52
53
|
manualCompactionInFlight = false;
|
|
53
54
|
observedToolCalls = 0;
|
|
55
|
+
activeProviderTurnId;
|
|
56
|
+
activeProviderTurnSpanId;
|
|
57
|
+
toolSpans = new Map();
|
|
54
58
|
constructor(opts) {
|
|
55
59
|
this.opts = opts;
|
|
56
60
|
this._sessionId = opts.resumeSessionRef?.sessionId ?? opts.resumeSessionId;
|
|
@@ -82,13 +86,45 @@ export class CodexAdapter {
|
|
|
82
86
|
return args;
|
|
83
87
|
}
|
|
84
88
|
async sendTurn(instruction) {
|
|
85
|
-
|
|
89
|
+
const turnId = randomUUID();
|
|
90
|
+
this.activeProviderTurnId = turnId;
|
|
91
|
+
const observer = this.opts.observer;
|
|
92
|
+
if (!observer) {
|
|
93
|
+
try {
|
|
94
|
+
return await withActivityPhase(`Codex ${activityPhase(this.opts.runtimePhase)}`, () => this.sendTurnInternal(instruction));
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
this.activeProviderTurnId = undefined;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const context = this.observationContext();
|
|
101
|
+
const spanId = observer.store.startSpan(context, { spanId: turnId, kind: "provider_turn", name: `Codex ${activityPhase(this.opts.runtimePhase)}`, providerTurnId: turnId, attributes: { provider: "codex" } });
|
|
102
|
+
this.activeProviderTurnSpanId = spanId;
|
|
103
|
+
observer.store.updateCurrentState({ runId: observer.runId, role: context.role ?? "host", stream: context.stream ?? "codex", executionId: observer.executionId, ticketId: context.ticketId, deliveryUnitId: context.deliveryUnitId, providerSessionId: context.providerSessionId, phase: "provider turn", activeSpanId: spanId, activeSpanKind: "provider_turn", lastSemanticProgressAt: new Date().toISOString() });
|
|
104
|
+
try {
|
|
105
|
+
const result = await withActivityPhase(`Codex ${activityPhase(this.opts.runtimePhase)}`, () => this.sendTurnInternal(instruction));
|
|
106
|
+
observer.store.finishSpan(spanId, { outcome: result.isError ? "failed" : "completed", attributes: { usage: result.usage } });
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
observer.store.finishSpan(spanId, { outcome: "failed", attributes: { error: String(error).slice(0, 500) } });
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
finally {
|
|
114
|
+
for (const [callId, toolSpanId] of this.toolSpans) {
|
|
115
|
+
observer.store.finishSpan(toolSpanId, { outcome: "unknown", completionKnown: false });
|
|
116
|
+
this.toolSpans.delete(callId);
|
|
117
|
+
}
|
|
118
|
+
this.activeProviderTurnId = undefined;
|
|
119
|
+
this.activeProviderTurnSpanId = undefined;
|
|
120
|
+
}
|
|
86
121
|
}
|
|
87
122
|
async sendTurnInternal(instruction) {
|
|
88
123
|
if (this.closed)
|
|
89
124
|
throw new Error("builder is closed");
|
|
90
125
|
this.eventQueue.push({ kind: "activity", state: "starting Codex turn", provider: "codex", model: this.opts.model });
|
|
91
126
|
let turnStartDispatched = false;
|
|
127
|
+
const providerInstruction = this.buildInstruction(instruction);
|
|
92
128
|
try {
|
|
93
129
|
await this.ensureThread();
|
|
94
130
|
this.activeText = [];
|
|
@@ -97,7 +133,7 @@ export class CodexAdapter {
|
|
|
97
133
|
turnStartDispatched = true;
|
|
98
134
|
await this.request("turn/start", {
|
|
99
135
|
threadId: this._sessionId,
|
|
100
|
-
input: [{ type: "text", text:
|
|
136
|
+
input: [{ type: "text", text: providerInstruction, text_elements: [] }],
|
|
101
137
|
cwd: this.opts.cwd,
|
|
102
138
|
model: this.opts.model ?? null,
|
|
103
139
|
effort: this.opts.effort ?? (this.opts.fast ? "low" : null),
|
|
@@ -110,16 +146,27 @@ export class CodexAdapter {
|
|
|
110
146
|
const result = {
|
|
111
147
|
text: failed ? normalizeRuntimeErrorText("codex", String(error?.message ?? text), null, "app-server turn") : text,
|
|
112
148
|
isError: failed, numTurns: 1, costUsd: 0, costAuthoritative: false,
|
|
149
|
+
turnId: this.activeProviderTurnId, hostInstruction: instruction, providerInstruction,
|
|
150
|
+
rawResponse: text, cleanedResponse: failed ? normalizeRuntimeErrorText("codex", String(error?.message ?? text), null, "app-server turn") : text,
|
|
151
|
+
providerMetadata: { provider: "codex", sessionId: this._sessionId, sessionRef: this._sessionRef },
|
|
113
152
|
...(this.lastTurnTokens ?? {}),
|
|
114
153
|
};
|
|
115
|
-
|
|
154
|
+
result.usage = { scope: "turn-delta", inputTokens: result.inputTokens, outputTokens: result.outputTokens,
|
|
155
|
+
...(result.inputTokens !== undefined || result.outputTokens !== undefined ? { totalTokens: (result.inputTokens ?? 0) + (result.outputTokens ?? 0) } : {}) };
|
|
156
|
+
this.eventQueue.push({ kind: "turn-complete", result, turnId: result.turnId });
|
|
116
157
|
return result;
|
|
117
158
|
}
|
|
118
159
|
catch (error) {
|
|
119
160
|
if (error instanceof SessionUnavailableError) {
|
|
120
161
|
const result = sessionUnavailableResult(error);
|
|
121
162
|
this.eventQueue.push({ kind: "error", message: result.text });
|
|
122
|
-
|
|
163
|
+
result.turnId = this.activeProviderTurnId;
|
|
164
|
+
result.hostInstruction = instruction;
|
|
165
|
+
result.providerInstruction = providerInstruction;
|
|
166
|
+
result.rawResponse = result.text;
|
|
167
|
+
result.cleanedResponse = result.text;
|
|
168
|
+
result.providerMetadata = { provider: "codex", sessionId: this._sessionId, sessionRef: this._sessionRef };
|
|
169
|
+
this.eventQueue.push({ kind: "turn-complete", result, turnId: result.turnId });
|
|
123
170
|
return result;
|
|
124
171
|
}
|
|
125
172
|
const detail = [error instanceof Error ? error.message : String(error), this.stderr].filter(Boolean).join("\n");
|
|
@@ -134,11 +181,22 @@ export class CodexAdapter {
|
|
|
134
181
|
}))
|
|
135
182
|
: { text, isError: true, numTurns: 1, costUsd: 0, costAuthoritative: false };
|
|
136
183
|
this.eventQueue.push({ kind: "error", message: text });
|
|
137
|
-
|
|
184
|
+
result.turnId = this.activeProviderTurnId;
|
|
185
|
+
result.hostInstruction = instruction;
|
|
186
|
+
result.providerInstruction = providerInstruction;
|
|
187
|
+
result.rawResponse = result.text;
|
|
188
|
+
result.cleanedResponse = result.text;
|
|
189
|
+
result.providerMetadata = { provider: "codex", sessionId: this._sessionId, sessionRef: this._sessionRef };
|
|
190
|
+
this.eventQueue.push({ kind: "turn-complete", result, turnId: result.turnId });
|
|
138
191
|
return result;
|
|
139
192
|
}
|
|
140
193
|
}
|
|
141
194
|
async compact() {
|
|
195
|
+
return this.opts.observer
|
|
196
|
+
? this.opts.observer.span("compaction", "Codex context compaction", () => this.compactInternal())
|
|
197
|
+
: this.compactInternal();
|
|
198
|
+
}
|
|
199
|
+
async compactInternal() {
|
|
142
200
|
try {
|
|
143
201
|
await this.ensureThread();
|
|
144
202
|
this.eventQueue.push({ kind: "session-transition", transition: "compacting" });
|
|
@@ -190,6 +248,8 @@ export class CodexAdapter {
|
|
|
190
248
|
// setup turn, then restart the app server with the provider-native ceiling
|
|
191
249
|
// before any Builder or QA work is sent.
|
|
192
250
|
if (!this.usage?.maximum) {
|
|
251
|
+
if (this.opts.allowAutoCompactionSetupTurn === false)
|
|
252
|
+
return;
|
|
193
253
|
const toolsBefore = this.observedToolCalls;
|
|
194
254
|
const setup = await this.sendTurnInternal("Rafi internal initialization only. Do not call tools or modify files. Reply briefly that the context is ready.");
|
|
195
255
|
if (setup.isError)
|
|
@@ -214,6 +274,9 @@ export class CodexAdapter {
|
|
|
214
274
|
};
|
|
215
275
|
return this.preparedAutoCompactionPolicy;
|
|
216
276
|
}
|
|
277
|
+
requiresAutoCompactionSetupTurn() {
|
|
278
|
+
return this.opts.autoCompactThresholdPercent !== undefined && !this.autoCompactionPrepared && !this.usage?.maximum;
|
|
279
|
+
}
|
|
217
280
|
autoCompactionPolicy() { return this.preparedAutoCompactionPolicy; }
|
|
218
281
|
drainNativeCompactions() {
|
|
219
282
|
const pending = this.nativeCompactions;
|
|
@@ -242,6 +305,14 @@ export class CodexAdapter {
|
|
|
242
305
|
}
|
|
243
306
|
sessionId() { return this._sessionId; }
|
|
244
307
|
sessionRef() { return this._sessionRef; }
|
|
308
|
+
async prepareSession() {
|
|
309
|
+
await this.ensureThread();
|
|
310
|
+
if (!this._sessionRef)
|
|
311
|
+
throw new Error("Codex did not expose a scoped thread identity during session preparation");
|
|
312
|
+
if (!this._sessionRef.validatedAt)
|
|
313
|
+
this._sessionRef = { ...this._sessionRef, validatedAt: new Date().toISOString() };
|
|
314
|
+
return this._sessionRef;
|
|
315
|
+
}
|
|
245
316
|
adoptSessionRef(ref) {
|
|
246
317
|
if (ref.provider !== "codex" || ref.sessionId !== this._sessionId)
|
|
247
318
|
throw new Error("cannot adopt a session reference for a different Codex thread");
|
|
@@ -337,20 +408,22 @@ export class CodexAdapter {
|
|
|
337
408
|
availability,
|
|
338
409
|
});
|
|
339
410
|
}
|
|
340
|
-
|
|
341
|
-
if (!this._sessionId)
|
|
411
|
+
if (!returnedSessionId?.trim())
|
|
342
412
|
throw new Error(`${method} did not return a thread ID`);
|
|
343
413
|
const providerCwd = typeof thread?.cwd === "string" ? canonicalSessionPath(thread.cwd) : undefined;
|
|
344
|
-
|
|
345
|
-
|
|
414
|
+
const expectedCwd = canonicalSessionPath(this._sessionRef?.cwd ?? this.opts.cwd);
|
|
415
|
+
if ((providerCwd && providerCwd !== expectedCwd) || (!providerCwd && this.opts.sessionRole === "qa")) {
|
|
416
|
+
const availability = { version: 1, status: "unavailable", checkedAt: new Date().toISOString(), reason: "cwd-mismatch", observedCwd: providerCwd, detail: `Codex thread cwd ${providerCwd ?? "was not returned"} does not match ${expectedCwd}`, sessionRef: this._sessionRef };
|
|
346
417
|
this.sessionAvailability = availability;
|
|
347
418
|
throw new SessionUnavailableError({ runtime: "codex", phase: "attach", dispatchState: "not-sent", executable: this.opts.runtimeExecutable ?? "codex", cwd: this.opts.cwd, diagnostics: availability.detail, availability });
|
|
348
419
|
}
|
|
420
|
+
this._sessionId = returnedSessionId;
|
|
349
421
|
if (!this._sessionRef) {
|
|
350
422
|
this._sessionRef = createProviderSessionRef({
|
|
351
423
|
provider: "codex", sessionId: this._sessionId, cwd: providerCwd ?? this.opts.cwd, configRoot: this.opts.configRoot ?? this.opts.cwd,
|
|
352
424
|
role: this.opts.sessionRole, stream: this.opts.sessionStream, generation: this.opts.sessionGeneration,
|
|
353
425
|
workspaceIdentity: this.opts.workspaceIdentity, ticketId: this.opts.ticketId, deliveryUnitId: this.opts.deliveryUnitId,
|
|
426
|
+
validatedAt: new Date().toISOString(),
|
|
354
427
|
});
|
|
355
428
|
}
|
|
356
429
|
else {
|
|
@@ -418,20 +491,47 @@ export class CodexAdapter {
|
|
|
418
491
|
const params = message.params ?? {};
|
|
419
492
|
if (message.method === "item/started") {
|
|
420
493
|
const item = params.item;
|
|
421
|
-
if (
|
|
494
|
+
if (item)
|
|
495
|
+
this.eventQueue.push({ kind: "provider-item", provider: "codex", lifecycle: "started", itemType: String(item.type ?? "unknown"), payload: exactProviderItem(item), payloadCompleteness: "complete", providerTurnId: this.activeProviderTurnId, eventId: randomUUID(), observedAt: new Date().toISOString() });
|
|
496
|
+
if (isCodexToolItem(item)) {
|
|
422
497
|
this.observedToolCalls += 1;
|
|
498
|
+
const callId = codexItemId(item) ?? randomUUID();
|
|
499
|
+
const name = codexToolName(item);
|
|
500
|
+
this.eventQueue.push({ kind: "tool", name, input: exactToolInput(item), inputCompleteness: "complete", lifecycle: "started", callId, completionKnown: false, providerTurnId: this.activeProviderTurnId, eventId: randomUUID(), observedAt: new Date().toISOString() });
|
|
501
|
+
if (this.opts.observer) {
|
|
502
|
+
const spanId = this.opts.observer.store.startSpan(this.observationContext(), { kind: "tool", name, providerTurnId: this.activeProviderTurnId, attributes: { callId, input: boundedToolInput(item) } });
|
|
503
|
+
this.toolSpans.set(callId, spanId);
|
|
504
|
+
this.opts.observer.signal(true);
|
|
505
|
+
}
|
|
506
|
+
}
|
|
423
507
|
const activity = codexItemActivity(item);
|
|
424
508
|
if (activity)
|
|
425
509
|
this.eventQueue.push({ kind: "activity", provider: "codex", ...activity });
|
|
426
510
|
}
|
|
427
511
|
else if (message.method === "item/completed") {
|
|
428
512
|
const item = params.item;
|
|
513
|
+
if (item)
|
|
514
|
+
this.eventQueue.push({ kind: "provider-item", provider: "codex", lifecycle: "completed", itemType: String(item.type ?? "unknown"), payload: exactProviderItem(item), payloadCompleteness: "complete", providerTurnId: this.activeProviderTurnId, eventId: randomUUID(), observedAt: new Date().toISOString() });
|
|
515
|
+
if (isCodexToolItem(item)) {
|
|
516
|
+
const callId = codexItemId(item);
|
|
517
|
+
const spanId = callId ? this.toolSpans.get(callId) : undefined;
|
|
518
|
+
const status = typeof item?.status === "string" ? item.status : undefined;
|
|
519
|
+
const exitCode = finiteNumber(item?.exitCode ?? item?.exit_code);
|
|
520
|
+
const durationMs = finiteNumber(item?.durationMs ?? item?.duration_ms);
|
|
521
|
+
const output = codexOutputSummary(item);
|
|
522
|
+
this.eventQueue.push({ kind: "tool", name: codexToolName(item), input: exactToolInput(item), inputCompleteness: "complete", lifecycle: "completed", callId, status, durationMs, exitCode, outputSummary: output?.summary, outputDigest: output?.digest, outputCompleteness: output?.completeness ?? "unavailable", rawOutputBytes: output?.bytes, completionKnown: true, providerTurnId: this.activeProviderTurnId, eventId: randomUUID(), observedAt: new Date().toISOString() });
|
|
523
|
+
if (spanId && this.opts.observer) {
|
|
524
|
+
this.opts.observer.store.finishSpan(spanId, { outcome: status ?? (exitCode === undefined || exitCode === 0 ? "completed" : "failed"), completionKnown: true, attributes: { callId, providerDurationMs: durationMs, exitCode, outputSummary: output?.summary, outputDigest: output?.digest } });
|
|
525
|
+
this.toolSpans.delete(callId);
|
|
526
|
+
}
|
|
527
|
+
this.opts.observer?.signal(true);
|
|
528
|
+
}
|
|
429
529
|
if (item?.type === "agentMessage" && typeof item.text === "string") {
|
|
430
530
|
this.activeText.push(item.text);
|
|
431
|
-
this.eventQueue.push({ kind: "text", text: item.text });
|
|
531
|
+
this.eventQueue.push({ kind: "text", text: item.text, byteCount: Buffer.byteLength(item.text), digest: createHash("sha256").update(item.text).digest("hex"), eventId: randomUUID(), observedAt: new Date().toISOString() });
|
|
532
|
+
this.opts.observer?.signal(true);
|
|
432
533
|
}
|
|
433
|
-
else if (item?.type === "commandExecution") {
|
|
434
|
-
this.eventQueue.push({ kind: "tool", name: "command_execution", input: { command: item.command } });
|
|
534
|
+
else if (item?.type === "commandExecution") { /* tool completion was emitted above */
|
|
435
535
|
}
|
|
436
536
|
else if (item?.type === "contextCompaction" && !this.manualCompactionInFlight) {
|
|
437
537
|
// Completion is the provider's authoritative confirmation. Token-usage
|
|
@@ -485,8 +585,18 @@ export class CodexAdapter {
|
|
|
485
585
|
else if (message.method === "error") {
|
|
486
586
|
const error = params.error;
|
|
487
587
|
const reason = String(error?.message ?? params.message ?? "Codex app-server error");
|
|
488
|
-
if (params.willRetry === true)
|
|
489
|
-
|
|
588
|
+
if (params.willRetry === true) {
|
|
589
|
+
const retryId = randomUUID();
|
|
590
|
+
this.eventQueue.push(this.opts.observer
|
|
591
|
+
? { kind: "retry", provider: "codex", reason, managedBy: "provider", retryId, providerTurnId: this.activeProviderTurnId, eventId: retryId, observedAt: new Date().toISOString() }
|
|
592
|
+
: { kind: "retry", provider: "codex", reason, managedBy: "provider" });
|
|
593
|
+
if (this.opts.observer) {
|
|
594
|
+
const retrySpanId = this.opts.observer.store.startSpan(this.observationContext(), { spanId: retryId, kind: "retry", name: "Codex provider retry", providerTurnId: this.activeProviderTurnId,
|
|
595
|
+
attributes: { provider: "codex", reasonDigest: createHash("sha256").update(reason).digest("hex") } });
|
|
596
|
+
this.opts.observer.store.recordEvent(this.observationContext(), "retry", { eventId: retryId, spanId: retrySpanId, severity: "warning", attributes: { provider: "codex", reasonDigest: createHash("sha256").update(reason).digest("hex") } });
|
|
597
|
+
this.opts.observer.store.finishSpan(retrySpanId, { outcome: "reported" });
|
|
598
|
+
}
|
|
599
|
+
}
|
|
490
600
|
else
|
|
491
601
|
this.eventQueue.push({ kind: "error", message: reason });
|
|
492
602
|
}
|
|
@@ -496,6 +606,14 @@ export class CodexAdapter {
|
|
|
496
606
|
waiter.predicate(params) ? waiter.resolve(params) : remaining.push(waiter);
|
|
497
607
|
this.notificationWaiters.set(message.method, remaining);
|
|
498
608
|
}
|
|
609
|
+
observationContext() {
|
|
610
|
+
const observer = this.opts.observer;
|
|
611
|
+
const inherited = observer.context();
|
|
612
|
+
return { ...inherited, runId: observer.runId, executionId: observer.executionId, role: this.opts.sessionRole ?? inherited.role ?? "builder",
|
|
613
|
+
stream: this.opts.sessionStream ?? inherited.stream ?? this.opts.sessionRole ?? "builder", providerSessionId: this._sessionId,
|
|
614
|
+
ticketId: this.opts.ticketId ?? inherited.ticketId, deliveryUnitId: this.opts.deliveryUnitId ?? inherited.deliveryUnitId,
|
|
615
|
+
parentSpanId: this.activeProviderTurnSpanId ?? inherited.parentSpanId, providerTurnId: this.activeProviderTurnId ?? inherited.providerTurnId };
|
|
616
|
+
}
|
|
499
617
|
waitFor(method, predicate, timeoutMs) {
|
|
500
618
|
return new Promise((resolve, reject) => {
|
|
501
619
|
let timeout;
|
|
@@ -544,6 +662,9 @@ export class CodexAdapter {
|
|
|
544
662
|
child.kill("SIGTERM");
|
|
545
663
|
}
|
|
546
664
|
buildSkillsAppendix() {
|
|
665
|
+
if (this.opts.preloadedSkillContent?.length) {
|
|
666
|
+
return ["# Preloaded Skills", "Use the following skills for this run.", ...this.opts.preloadedSkillContent.map((skill) => skill.content)].join("\n\n");
|
|
667
|
+
}
|
|
547
668
|
if (!this.opts.skills?.length)
|
|
548
669
|
return undefined;
|
|
549
670
|
const blocks = this.opts.skills.map((skill) => loadSkillMarkdown(this.opts.cwd, skill)).filter((block) => Boolean(block));
|
|
@@ -571,6 +692,8 @@ function activityPhase(phase) {
|
|
|
571
692
|
return "reviewing with QA";
|
|
572
693
|
if (phase === "uninstaller")
|
|
573
694
|
return "planning uninstall";
|
|
695
|
+
if (phase === "manager")
|
|
696
|
+
return "analyzing diagnostics";
|
|
574
697
|
return "building";
|
|
575
698
|
}
|
|
576
699
|
function codexItemActivity(item, completed = false) {
|
|
@@ -596,9 +719,18 @@ function codexItemActivity(item, completed = false) {
|
|
|
596
719
|
function isCodexToolItem(item) {
|
|
597
720
|
return item?.type === "commandExecution" || item?.type === "mcpToolCall" || item?.type === "dynamicToolCall" || item?.type === "webSearch" || item?.type === "fileChange";
|
|
598
721
|
}
|
|
722
|
+
function codexItemId(item) { const value = item?.id ?? item?.itemId ?? item?.callId; return typeof value === "string" && value ? value : undefined; }
|
|
723
|
+
function codexToolName(item) { return String(item?.tool ?? item?.name ?? item?.type ?? "tool"); }
|
|
724
|
+
function exactProviderItem(item) { return structuredClone(item); }
|
|
725
|
+
function exactToolInput(item) { return item ? exactProviderItem(item) : {}; }
|
|
726
|
+
function boundedToolInput(item) { if (!item)
|
|
727
|
+
return {}; return Object.fromEntries(Object.entries(exactToolInput(item)).map(([key, value]) => [key, typeof value === "string" ? value.slice(0, 1000) : value])); }
|
|
728
|
+
function codexOutputSummary(item) { const value = item?.aggregatedOutput ?? item?.output ?? item?.stdout; if (typeof value !== "string" || !value)
|
|
729
|
+
return undefined; const sanitized = value.replace(/\b(?:token|password|secret|api[_-]?key)\s*[:=]\s*\S+/gi, "$1=[REDACTED]"); const providerTruncated = item?.outputTruncated === true || item?.truncated === true; return { summary: sanitized.slice(0, 1000), digest: createHash("sha256").update(value).digest("hex"), completeness: providerTruncated ? "truncated" : "complete", bytes: Buffer.byteLength(value) }; }
|
|
730
|
+
function finiteNumber(value) { const result = Number(value); return Number.isFinite(result) ? result : undefined; }
|
|
599
731
|
function loadSkillMarkdown(cwd, skill) {
|
|
600
|
-
const projectPath = join(cwd, ".agents", "skills", skill, "SKILL.md");
|
|
601
|
-
if (
|
|
732
|
+
const projectPath = [join(cwd, ".codex", "skills", skill, "SKILL.md"), join(cwd, ".agents", "skills", skill, "SKILL.md")].find(existsSync);
|
|
733
|
+
if (projectPath)
|
|
602
734
|
return `## ${skill}\n${readFileSync(projectPath, "utf8").trim()}`;
|
|
603
735
|
try {
|
|
604
736
|
const bundled = loadSkill(skill);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentRuntime } from "../runtimeAuth.js";
|
|
2
2
|
import type { BuilderAdapter, BuilderEvent, CompactResult, ContextUsage, NativeAutoCompactionPolicy, NativeCompaction, ProviderSessionUsage, TurnResult } from "./types.js";
|
|
3
3
|
import type { ProviderSessionRefV1, SessionAvailabilityV1 } from "rafi-spec";
|
|
4
|
+
import type { RunObserver } from "../observability.js";
|
|
4
5
|
export type TurnRecoveryChoice = "retry" | "switch" | "cancel";
|
|
5
6
|
export interface RecoveringAdapterOptions {
|
|
6
7
|
initial: BuilderAdapter;
|
|
@@ -12,6 +13,7 @@ export interface RecoveringAdapterOptions {
|
|
|
12
13
|
choose?: (result: TurnResult, runtime: AgentRuntime, otherRuntime: AgentRuntime) => Promise<TurnRecoveryChoice>;
|
|
13
14
|
onRuntimeChange?: (runtime: AgentRuntime) => void;
|
|
14
15
|
onSessionRef?: (ref: ProviderSessionRefV1) => void;
|
|
16
|
+
observer?: RunObserver;
|
|
15
17
|
}
|
|
16
18
|
/** Adds interactive recovery around structured runtime turn failures. */
|
|
17
19
|
export declare class RecoveringAdapter implements BuilderAdapter {
|
|
@@ -26,10 +28,12 @@ export declare class RecoveringAdapter implements BuilderAdapter {
|
|
|
26
28
|
sendTurn(text: string): Promise<TurnResult>;
|
|
27
29
|
sessionId(): string | undefined;
|
|
28
30
|
sessionRef(): ProviderSessionRefV1 | undefined;
|
|
31
|
+
prepareSession(): Promise<ProviderSessionRefV1>;
|
|
29
32
|
adoptSessionRef(ref: ProviderSessionRefV1): void;
|
|
30
33
|
validateSession(): Promise<SessionAvailabilityV1>;
|
|
31
34
|
compact(): Promise<CompactResult>;
|
|
32
35
|
prepareAutoCompaction(thresholdPercent?: number): Promise<NativeAutoCompactionPolicy | void>;
|
|
36
|
+
requiresAutoCompactionSetupTurn(): boolean;
|
|
33
37
|
autoCompactionPolicy(): NativeAutoCompactionPolicy | undefined;
|
|
34
38
|
drainNativeCompactions(): import("./types.js").NativeCompaction[];
|
|
35
39
|
restoreNativeCompactions(compactions: NativeCompaction[]): void;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { isCancel, log, select } from "@clack/prompts";
|
|
2
2
|
import { AsyncQueue } from "../util/asyncQueue.js";
|
|
3
3
|
import { pauseActivityForInput, reportBuilderEvent } from "../activity.js";
|
|
4
|
+
import { randomUUID } from "node:crypto";
|
|
4
5
|
/** Adds interactive recovery around structured runtime turn failures. */
|
|
5
6
|
export class RecoveringAdapter {
|
|
6
7
|
opts;
|
|
@@ -39,7 +40,9 @@ export class RecoveringAdapter {
|
|
|
39
40
|
const otherRuntime = this.runtime === "claude" ? "codex" : "claude";
|
|
40
41
|
const choice = this.opts.choose
|
|
41
42
|
? await this.opts.choose(result, this.runtime, otherRuntime)
|
|
42
|
-
: await
|
|
43
|
+
: await (this.opts.observer
|
|
44
|
+
? this.opts.observer.span("user_wait", "runtime recovery choice", () => pauseActivityForInput(() => promptTurnRecovery(result, this.opts.label, this.runtime, otherRuntime, this.opts.allowSwitch)))
|
|
45
|
+
: pauseActivityForInput(() => promptTurnRecovery(result, this.opts.label, this.runtime, otherRuntime, this.opts.allowSwitch)));
|
|
43
46
|
if (choice === "cancel")
|
|
44
47
|
return result;
|
|
45
48
|
if (choice === "switch" && !this.opts.allowSwitch)
|
|
@@ -49,6 +52,13 @@ export class RecoveringAdapter {
|
|
|
49
52
|
: { kind: "activity", provider: otherRuntime, state: `switching to ${otherRuntime}`, detail: "starting a fresh provider session" };
|
|
50
53
|
this.eventQueue.push(recoveryEvent);
|
|
51
54
|
reportBuilderEvent(recoveryEvent);
|
|
55
|
+
if (choice === "retry" && this.opts.observer) {
|
|
56
|
+
const retryId = randomUUID();
|
|
57
|
+
const context = this.opts.observer.context();
|
|
58
|
+
const spanId = this.opts.observer.store.startSpan(context, { spanId: retryId, kind: "retry", name: "Rafi recovery retry", attributes: { provider: this.runtime, managedBy: "rafi" } });
|
|
59
|
+
this.opts.observer.store.recordEvent(context, "retry", { eventId: retryId, spanId, severity: "warning", attributes: { provider: this.runtime, managedBy: "rafi" } });
|
|
60
|
+
this.opts.observer.store.finishSpan(spanId, { outcome: "scheduled" });
|
|
61
|
+
}
|
|
52
62
|
const nextRuntime = choice === "switch" ? otherRuntime : this.runtime;
|
|
53
63
|
const resumeSessionId = choice === "retry" ? this.adapter.sessionId() : undefined;
|
|
54
64
|
const resumeSessionRef = choice === "retry" ? this.adapter.sessionRef?.() : undefined;
|
|
@@ -62,12 +72,18 @@ export class RecoveringAdapter {
|
|
|
62
72
|
return this.adapter.sessionId();
|
|
63
73
|
}
|
|
64
74
|
sessionRef() { return this.adapter.sessionRef?.(); }
|
|
75
|
+
prepareSession() {
|
|
76
|
+
if (!this.adapter.prepareSession)
|
|
77
|
+
return Promise.reject(new Error("wrapped adapter cannot establish a provider session without a work turn"));
|
|
78
|
+
return this.adapter.prepareSession();
|
|
79
|
+
}
|
|
65
80
|
adoptSessionRef(ref) { this.adapter.adoptSessionRef?.(ref); }
|
|
66
81
|
validateSession() {
|
|
67
82
|
return this.adapter.validateSession?.() ?? Promise.resolve({ version: 1, status: "unknown", checkedAt: new Date().toISOString(), reason: "legacy-unscoped", detail: "wrapped adapter does not expose scoped session validation" });
|
|
68
83
|
}
|
|
69
84
|
compact() { return this.adapter.compact?.() ?? Promise.resolve({ ok: false, error: "native compaction unavailable" }); }
|
|
70
85
|
prepareAutoCompaction(thresholdPercent) { return this.adapter.prepareAutoCompaction?.(thresholdPercent) ?? Promise.resolve(); }
|
|
86
|
+
requiresAutoCompactionSetupTurn() { return this.adapter.requiresAutoCompactionSetupTurn?.() ?? false; }
|
|
71
87
|
autoCompactionPolicy() { return this.adapter.autoCompactionPolicy?.(); }
|
|
72
88
|
drainNativeCompactions() { return this.adapter.drainNativeCompactions?.() ?? []; }
|
|
73
89
|
restoreNativeCompactions(compactions) { this.adapter.restoreNativeCompactions?.(compactions); }
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Agent-agnostic interface over a coding agent ("builder").
|
|
3
3
|
*/
|
|
4
4
|
import type { ConfigurableAgentRole, ProviderSessionRefV1, RuntimeProbeCategory, RuntimeProbePhase, SessionAvailabilityV1 } from "rafi-spec";
|
|
5
|
+
import type { RunObserver } from "../observability.js";
|
|
5
6
|
/** A permission request surfaced by a builder before it runs a tool. */
|
|
6
7
|
export interface PermissionRequest {
|
|
7
8
|
toolName: string;
|
|
@@ -58,16 +59,61 @@ export interface TurnResult {
|
|
|
58
59
|
costAuthoritative?: boolean;
|
|
59
60
|
inputTokens?: number;
|
|
60
61
|
outputTokens?: number;
|
|
62
|
+
/** Explicit counter semantics. Compatibility counters above must not be summed without this scope. */
|
|
63
|
+
usage?: {
|
|
64
|
+
scope: "turn-delta" | "session-cumulative";
|
|
65
|
+
inputTokens?: number;
|
|
66
|
+
outputTokens?: number;
|
|
67
|
+
totalTokens?: number;
|
|
68
|
+
costUsd?: number;
|
|
69
|
+
};
|
|
61
70
|
failure?: RuntimeFailure;
|
|
71
|
+
/** Stable host/provider correlation ID for this turn. */
|
|
72
|
+
turnId?: string;
|
|
73
|
+
/** Exact instruction requested by the immediate host caller. */
|
|
74
|
+
hostInstruction?: string;
|
|
75
|
+
/** Exact instruction dispatched to the provider after all wrappers. */
|
|
76
|
+
providerInstruction?: string;
|
|
77
|
+
/** Exact provider response before continuity or contract cleanup. */
|
|
78
|
+
rawResponse?: string;
|
|
79
|
+
/** Effective response consumed by the host contract parser. */
|
|
80
|
+
cleanedResponse?: string;
|
|
81
|
+
providerMetadata?: {
|
|
82
|
+
provider: "claude" | "codex";
|
|
83
|
+
sessionId?: string;
|
|
84
|
+
sessionRef?: ProviderSessionRefV1;
|
|
85
|
+
};
|
|
62
86
|
}
|
|
63
87
|
/** Observability events emitted while a builder works. */
|
|
64
|
-
export type BuilderEvent = {
|
|
88
|
+
export type BuilderEvent = ({
|
|
65
89
|
kind: "text";
|
|
66
90
|
text: string;
|
|
91
|
+
byteCount?: number;
|
|
92
|
+
digest?: string;
|
|
67
93
|
} | {
|
|
68
94
|
kind: "tool";
|
|
69
95
|
name: string;
|
|
70
96
|
input: unknown;
|
|
97
|
+
inputCompleteness?: "complete" | "truncated" | "unavailable";
|
|
98
|
+
lifecycle?: "started" | "progress" | "completed";
|
|
99
|
+
callId?: string;
|
|
100
|
+
status?: string;
|
|
101
|
+
durationMs?: number;
|
|
102
|
+
exitCode?: number;
|
|
103
|
+
outputSummary?: string;
|
|
104
|
+
outputDigest?: string;
|
|
105
|
+
outputCompleteness?: "complete" | "truncated" | "unavailable";
|
|
106
|
+
rawOutputBytes?: number;
|
|
107
|
+
completionKnown?: boolean;
|
|
108
|
+
providerTurnId?: string;
|
|
109
|
+
} | {
|
|
110
|
+
kind: "provider-item";
|
|
111
|
+
provider: "claude" | "codex";
|
|
112
|
+
lifecycle: "started" | "completed";
|
|
113
|
+
itemType: string;
|
|
114
|
+
payload: Record<string, unknown>;
|
|
115
|
+
payloadCompleteness: "complete";
|
|
116
|
+
providerTurnId?: string;
|
|
71
117
|
} | {
|
|
72
118
|
kind: "activity";
|
|
73
119
|
state: string;
|
|
@@ -83,9 +129,12 @@ export type BuilderEvent = {
|
|
|
83
129
|
maximum?: number;
|
|
84
130
|
delayMs?: number;
|
|
85
131
|
managedBy: "provider" | "rafi";
|
|
132
|
+
retryId?: string;
|
|
133
|
+
providerTurnId?: string;
|
|
86
134
|
} | {
|
|
87
135
|
kind: "turn-complete";
|
|
88
136
|
result: TurnResult;
|
|
137
|
+
turnId?: string;
|
|
89
138
|
} | {
|
|
90
139
|
kind: "session-transition";
|
|
91
140
|
transition: "started" | "resumed" | "compacting" | "compacted" | "fresh-fallback";
|
|
@@ -100,6 +149,9 @@ export type BuilderEvent = {
|
|
|
100
149
|
} | {
|
|
101
150
|
kind: "error";
|
|
102
151
|
message: string;
|
|
152
|
+
}) & {
|
|
153
|
+
eventId?: string;
|
|
154
|
+
observedAt?: string;
|
|
103
155
|
};
|
|
104
156
|
export interface ContextUsage {
|
|
105
157
|
used: number;
|
|
@@ -183,8 +235,17 @@ export interface BuilderAdapterOptions {
|
|
|
183
235
|
systemPromptAppend?: string;
|
|
184
236
|
/** Skill names to preload for this session (Claude: lazy-loaded; Codex: flattened). */
|
|
185
237
|
skills?: string[];
|
|
238
|
+
/** Exact host-frozen skill text injected into the provider instruction. */
|
|
239
|
+
preloadedSkillContent?: Array<{
|
|
240
|
+
name: string;
|
|
241
|
+
content: string;
|
|
242
|
+
}>;
|
|
186
243
|
/** Provider-native context ceiling, as a percentage of that provider's model window. */
|
|
187
244
|
autoCompactThresholdPercent?: number;
|
|
245
|
+
/** QA forbids an unjournaled model turn merely to discover the context window. */
|
|
246
|
+
allowAutoCompactionSetupTurn?: boolean;
|
|
247
|
+
/** Raw-adapter observability; wrapper adapters must not persist re-emitted events. */
|
|
248
|
+
observer?: RunObserver;
|
|
188
249
|
}
|
|
189
250
|
export interface BuilderAdapter {
|
|
190
251
|
readonly agent: "claude" | "codex";
|
|
@@ -194,6 +255,11 @@ export interface BuilderAdapter {
|
|
|
194
255
|
sessionId(): string | undefined;
|
|
195
256
|
/** Current location-scoped session reference, once known. */
|
|
196
257
|
sessionRef?(): ProviderSessionRefV1 | undefined;
|
|
258
|
+
/**
|
|
259
|
+
* Establish and observe the exact provider session without dispatching a
|
|
260
|
+
* role/work turn. Durable QA calls this before binding its first turn.
|
|
261
|
+
*/
|
|
262
|
+
prepareSession?(): Promise<ProviderSessionRefV1>;
|
|
197
263
|
/** Host-only metadata promotion after a validated handoff is accepted. */
|
|
198
264
|
adoptSessionRef?(ref: ProviderSessionRefV1): void;
|
|
199
265
|
/** Attach/probe a requested exact session without starting a provider turn. */
|
|
@@ -206,6 +272,8 @@ export interface BuilderAdapter {
|
|
|
206
272
|
* tool-heavy first turn is protected too.
|
|
207
273
|
*/
|
|
208
274
|
prepareAutoCompaction?(thresholdPercent?: number): Promise<NativeAutoCompactionPolicy | void>;
|
|
275
|
+
/** True only when a provider needs one observable, tool-free turn to learn its context window. */
|
|
276
|
+
requiresAutoCompactionSetupTurn?(): boolean;
|
|
209
277
|
/** The last provider-native automatic-compaction policy verified on this transport. */
|
|
210
278
|
autoCompactionPolicy?(): NativeAutoCompactionPolicy | undefined;
|
|
211
279
|
/** Consume provider-native compactions observed since the prior drain. */
|
package/dist/agentRun.d.ts
CHANGED
|
@@ -22,6 +22,8 @@ export interface RoleBuilderOptions {
|
|
|
22
22
|
permissionConfig?: PermissionConfig;
|
|
23
23
|
extraSkills?: string[];
|
|
24
24
|
sandboxMode?: BuilderAdapterOptions["sandboxMode"];
|
|
25
|
+
/** Disable recovery-session persistence for isolated, non-recoverable roles such as Manager. */
|
|
26
|
+
persistSessionBindings?: boolean;
|
|
25
27
|
resumeSessionId?: string;
|
|
26
28
|
/** Location-scoped exact session. Raw IDs are resolved through durable project bindings or treated as legacy candidates. */
|
|
27
29
|
resumeSessionRef?: ProviderSessionRefV1;
|
package/dist/agentRun.js
CHANGED
|
@@ -47,6 +47,22 @@ export function readOnlyPermissionConfig() {
|
|
|
47
47
|
"git show --stat",
|
|
48
48
|
"git ls-files",
|
|
49
49
|
"git grep",
|
|
50
|
+
"npm test",
|
|
51
|
+
"npm run test",
|
|
52
|
+
"npm run typecheck",
|
|
53
|
+
"npm run lint",
|
|
54
|
+
"npm run check",
|
|
55
|
+
"pnpm test",
|
|
56
|
+
"pnpm run test",
|
|
57
|
+
"pnpm typecheck",
|
|
58
|
+
"pnpm lint",
|
|
59
|
+
"pnpm --filter ",
|
|
60
|
+
"yarn test",
|
|
61
|
+
"pytest",
|
|
62
|
+
"python -m pytest",
|
|
63
|
+
"python3 -m pytest",
|
|
64
|
+
"cargo test",
|
|
65
|
+
"go test",
|
|
50
66
|
],
|
|
51
67
|
escalateBash: [
|
|
52
68
|
...DEFAULT_CONFIG.permissions.escalateBash,
|
|
@@ -60,10 +76,12 @@ export function readOnlyPermissionConfig() {
|
|
|
60
76
|
"git restore",
|
|
61
77
|
"git stash",
|
|
62
78
|
"npm install",
|
|
63
|
-
"npm run",
|
|
64
79
|
"npm ci",
|
|
65
|
-
"pnpm ",
|
|
66
|
-
"
|
|
80
|
+
"pnpm install",
|
|
81
|
+
"pnpm add",
|
|
82
|
+
"pnpm remove",
|
|
83
|
+
"yarn add",
|
|
84
|
+
"yarn install",
|
|
67
85
|
"mkdir ",
|
|
68
86
|
"touch ",
|
|
69
87
|
"mv ",
|
|
@@ -199,6 +217,8 @@ export async function createRoleBuilder(opts) {
|
|
|
199
217
|
};
|
|
200
218
|
const initial = await makeAdapter(runtime, opts.resumeSessionId, requestedResumeRef);
|
|
201
219
|
const persistSessionRef = (ref) => {
|
|
220
|
+
if (opts.persistSessionBindings === false)
|
|
221
|
+
return;
|
|
202
222
|
const bindingDb = new WorkflowDb(opts.projectDir);
|
|
203
223
|
try {
|
|
204
224
|
bindingDb.recordProviderSessionBinding(ref);
|
|
@@ -240,6 +260,8 @@ function phaseForRole(role) {
|
|
|
240
260
|
return "qa";
|
|
241
261
|
if (role === "uninstaller")
|
|
242
262
|
return "uninstaller";
|
|
263
|
+
if (role === "manager")
|
|
264
|
+
return "manager";
|
|
243
265
|
return "builder";
|
|
244
266
|
}
|
|
245
267
|
export function readRoleDefaultsForExecution(projectDir, role) {
|
|
@@ -253,6 +275,8 @@ export function readRoleDefaultsForExecution(projectDir, role) {
|
|
|
253
275
|
: undefined;
|
|
254
276
|
if (!saved)
|
|
255
277
|
return undefined;
|
|
278
|
+
if (role === "manager")
|
|
279
|
+
return saved;
|
|
256
280
|
if (isFullyInitializedTracker(projectDir))
|
|
257
281
|
return saved;
|
|
258
282
|
const { make: _pendingMake, ...active } = saved;
|
|
@@ -333,7 +357,7 @@ export async function runRoleInstruction(opts) {
|
|
|
333
357
|
}
|
|
334
358
|
}
|
|
335
359
|
function configurableRole(role) {
|
|
336
|
-
return ["builder", "qa", "planner", "ticket-maker", "uninstaller"].includes(role)
|
|
360
|
+
return ["builder", "qa", "planner", "ticket-maker", "uninstaller", "manager"].includes(role)
|
|
337
361
|
? role
|
|
338
362
|
: "builder";
|
|
339
363
|
}
|
|
@@ -23,10 +23,12 @@ export declare class CurrentWorkflowGuardAdapter implements BuilderAdapter {
|
|
|
23
23
|
sendTurn(text: string): Promise<TurnResult>;
|
|
24
24
|
sessionId(): string | undefined;
|
|
25
25
|
sessionRef(): ProviderSessionRefV1 | undefined;
|
|
26
|
+
prepareSession(): Promise<ProviderSessionRefV1>;
|
|
26
27
|
adoptSessionRef(ref: ProviderSessionRefV1): void;
|
|
27
28
|
validateSession(): Promise<SessionAvailabilityV1>;
|
|
28
29
|
compact(): Promise<CompactResult>;
|
|
29
30
|
prepareAutoCompaction(thresholdPercent?: number): Promise<NativeAutoCompactionPolicy | void>;
|
|
31
|
+
requiresAutoCompactionSetupTurn(): boolean;
|
|
30
32
|
autoCompactionPolicy(): NativeAutoCompactionPolicy | undefined;
|
|
31
33
|
drainNativeCompactions(): import("../adapters/types.js").NativeCompaction[];
|
|
32
34
|
restoreNativeCompactions(compactions: NativeCompaction[]): void;
|
|
@@ -45,10 +45,19 @@ export class CurrentWorkflowGuardAdapter {
|
|
|
45
45
|
}
|
|
46
46
|
sessionId() { return this.adapter.sessionId(); }
|
|
47
47
|
sessionRef() { return this.adapter.sessionRef?.(); }
|
|
48
|
+
async prepareSession() {
|
|
49
|
+
assertCurrentWorkflowIdentity(this.cwd, this.expected);
|
|
50
|
+
if (!this.adapter.prepareSession)
|
|
51
|
+
throw new Error("wrapped adapter cannot establish a provider session without a work turn");
|
|
52
|
+
const ref = await this.adapter.prepareSession();
|
|
53
|
+
assertCurrentWorkflowIdentity(this.cwd, this.expected);
|
|
54
|
+
return ref;
|
|
55
|
+
}
|
|
48
56
|
adoptSessionRef(ref) { this.adapter.adoptSessionRef?.(ref); }
|
|
49
57
|
validateSession() { assertCurrentWorkflowIdentity(this.cwd, this.expected); return this.adapter.validateSession?.() ?? Promise.resolve({ version: 1, status: "unknown", checkedAt: new Date().toISOString(), reason: "legacy-unscoped" }); }
|
|
50
58
|
async compact() { assertCurrentWorkflowIdentity(this.cwd, this.expected); const result = await (this.adapter.compact?.() ?? Promise.resolve({ ok: false, error: "native compaction unavailable" })); assertCurrentWorkflowIdentity(this.cwd, this.expected); return result; }
|
|
51
59
|
prepareAutoCompaction(thresholdPercent) { return this.adapter.prepareAutoCompaction?.(thresholdPercent) ?? Promise.resolve(); }
|
|
60
|
+
requiresAutoCompactionSetupTurn() { return this.adapter.requiresAutoCompactionSetupTurn?.() ?? false; }
|
|
52
61
|
autoCompactionPolicy() { return this.adapter.autoCompactionPolicy?.(); }
|
|
53
62
|
drainNativeCompactions() { return this.adapter.drainNativeCompactions?.() ?? []; }
|
|
54
63
|
restoreNativeCompactions(compactions) { this.adapter.restoreNativeCompactions?.(compactions); }
|