ai-foreman 1.7.13 → 1.7.15
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 +33 -3
- package/dist/adapters/claude.d.ts +5 -0
- package/dist/adapters/claude.js +124 -16
- package/dist/adapters/codex.d.ts +2 -0
- package/dist/adapters/codex.js +32 -8
- package/dist/adapters/recovering.d.ts +2 -0
- package/dist/adapters/recovering.js +6 -0
- package/dist/adapters/types.d.ts +22 -0
- package/dist/agentRun.js +24 -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/runner.d.ts +3 -2
- package/dist/branch/runner.js +277 -75
- package/dist/buildRuns.d.ts +1 -0
- package/dist/buildRuns.js +33 -3
- package/dist/cli/start.js +314 -86
- package/dist/cli/state.d.ts +4 -0
- package/dist/cli/state.js +69 -0
- package/dist/continuity.d.ts +4 -0
- package/dist/continuity.js +26 -3
- package/dist/foreman.d.ts +22 -4
- package/dist/foreman.js +105 -19
- package/dist/handoffs.d.ts +5 -2
- package/dist/handoffs.js +93 -12
- package/dist/index.js +2 -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 +15 -3
- package/dist/qaRecovery.js +477 -83
- package/dist/qaReview.d.ts +42 -17
- package/dist/qaReview.js +1216 -457
- package/dist/qaRuntime.d.ts +14 -0
- package/dist/qaRuntime.js +44 -0
- package/dist/qaSnapshot.d.ts +34 -0
- package/dist/qaSnapshot.js +302 -34
- 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/workflowDb.d.ts +245 -2
- package/dist/workflowDb.js +647 -6
- package/package.json +15 -3
package/README.md
CHANGED
|
@@ -70,6 +70,7 @@ ai-foreman doctor ./my-project
|
|
|
70
70
|
ai-foreman tickets init --project ./my-project --app-name "My App"
|
|
71
71
|
ai-foreman tickets populate --project ./my-project --agent codex --model gpt-5.5 --effort xhigh
|
|
72
72
|
ai-foreman start ./my-project --agent codex --model gpt-5.5 --effort xhigh --steps 5
|
|
73
|
+
ai-foreman state export ./my-project --output rafi-state.rafi.gz
|
|
73
74
|
```
|
|
74
75
|
|
|
75
76
|
What each part does:
|
|
@@ -89,6 +90,20 @@ What each part does:
|
|
|
89
90
|
- ticket files
|
|
90
91
|
- folders or globs containing planning notes
|
|
91
92
|
|
|
93
|
+
## Move Local State Between Machines
|
|
94
|
+
|
|
95
|
+
Use `ai-foreman state export` for a sequential handoff from one checkout to another:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
ai-foreman state export ./my-project --output rafi-state.rafi.gz
|
|
99
|
+
ai-foreman state inspect rafi-state.rafi.gz
|
|
100
|
+
ai-foreman state import ./matching-checkout rafi-state.rafi.gz
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The bundle includes the local tracker database, delivery state, workflow/recovery databases, compiled role bundles, source snapshots, interview recovery records, and diagnostic run/recovery files. It does not include source code, provider sessions, or live worktrees. Export/import refuse dirty Git checkouts, active workflow leases, incompatible branch/HEAD state, and divergent transfer lineage. Import asks before replacing existing local state unless `--yes` is supplied, keeps a backup under `.rafi/state-transfer-backups/`, and rolls back failed imports.
|
|
104
|
+
|
|
105
|
+
Provider sessions are historical after import. Resume with `rafi build:resume <project> --fresh-with-handoff` or the equivalent guided recovery path when exact session recovery is unavailable.
|
|
106
|
+
|
|
92
107
|
## Primary Options
|
|
93
108
|
|
|
94
109
|
### `ai-foreman start`
|
|
@@ -148,7 +163,9 @@ With the saved `current` strategy, Foreman works in the active branch while the
|
|
|
148
163
|
|
|
149
164
|
The current terminal status includes the role/provider, activity, truthful context state, successful compaction count, and handoff generation. At the Builder threshold, Foreman settles the in-flight action, verifies native compaction plus a fresh provider usage sample, and resumes the frozen action. The configured maximum defaults to ten successful compactions per location-scoped provider session; the next crossing uses a validated fresh handoff instead of exceeding it. Disposable QA snapshots never reuse a prior QA conversation in a new `/tmp/rafi-qa-*` worktree; they transfer cumulative state through an accepted handoff.
|
|
150
165
|
|
|
151
|
-
Failed QA reviews use the versioned `RAFI_QA_FAILURE_REPORT_START` / `RAFI_QA_FAILURE_REPORT_END` JSON contract. Foreman validates the report before Builder remediation and
|
|
166
|
+
Failed QA reviews use the versioned `RAFI_QA_FAILURE_REPORT_START` / `RAFI_QA_FAILURE_REPORT_END` JSON contract. Foreman validates the report before Builder remediation, namespaces finding IDs by run, ticket, and review, and durably indexes the exact report, remediation response, and bounded fix summary by digest. A malformed failing report receives two correction-only turns in its original session, then a validated fresh successor performs one complete source-bound review followed by up to two correction-only turns. Any later operator-requested fresh successor performs one complete source-bound review followed by five usable correction-only turns.
|
|
167
|
+
|
|
168
|
+
Owner-only recovery packets under `.foreman/qa-report-recovery/` bind the review to a full frozen source digest: commit, porcelain status, staged and unstaged bytes, and untracked content. Every fresh attempt captures and compares that complete state. Drift appends the new state and forces a complete review; correction-only reconstruction is never applied to changed source. Packet revisions and the authoritative WorkflowDb recovery head advance together. A successor must have a location-scoped identity, accept a handoff containing the complete resource inventory (purpose, byte count, digest, and recovery requirement), and acknowledge the copied packet before reporting. Paused recovery resumes with `rafi build:resume <project> --run <run-id> --ticket <ticket-id> --fresh-with-handoff`.
|
|
152
169
|
|
|
153
170
|
### `ai-foreman manager`
|
|
154
171
|
|
|
@@ -497,6 +514,11 @@ ai-foreman start ./my-project --steps 5 --no-qa
|
|
|
497
514
|
# Resume the latest session / a specific session
|
|
498
515
|
ai-foreman start ./my-project --steps 5 --continue
|
|
499
516
|
ai-foreman start ./my-project --steps 5 --resume <session-id>
|
|
517
|
+
|
|
518
|
+
# Export, inspect, and import local Rafi state
|
|
519
|
+
ai-foreman state export ./my-project --output rafi-state.rafi.gz
|
|
520
|
+
ai-foreman state inspect rafi-state.rafi.gz
|
|
521
|
+
ai-foreman state import ./matching-checkout rafi-state.rafi.gz
|
|
500
522
|
```
|
|
501
523
|
|
|
502
524
|
## Ticket Lifecycle Commands
|
|
@@ -558,16 +580,24 @@ STEP_STATUS: needs_input | question="Which storage backend?" choices="SQLite|Pos
|
|
|
558
580
|
|
|
559
581
|
Provider-native `AskUserQuestion` prompts preserve their existing input/output behavior. Hosts may also observe a question only after the user successfully supplies a non-empty answer; cancelled, denied, malformed, and empty prompts do not emit answered-question telemetry. Rafi uses this boundary to recognize machine-shaped exhaustive-planning exchanges without treating generic questions as grill-me coverage.
|
|
560
582
|
|
|
561
|
-
When QA is enabled, Foreman
|
|
583
|
+
When QA is enabled, Foreman creates an independent read-only review snapshot. A pass ends with:
|
|
562
584
|
|
|
563
585
|
```txt
|
|
564
586
|
STEP_STATUS: qa_pass | summary="tests pass and acceptance criteria are met"
|
|
587
|
+
```
|
|
588
|
+
|
|
589
|
+
Failure uses a structured report immediately before the status marker (the short `issues` field is only a human synopsis and fallback input):
|
|
590
|
+
|
|
591
|
+
```txt
|
|
592
|
+
RAFI_QA_FAILURE_REPORT_START
|
|
593
|
+
{"version":1,"summary":"missing empty-config coverage","checks_run":[{"check":"tests","command":"pnpm test","outcome":"failed","evidence":"empty-config case is absent"}],"findings":[{"id":"F1","requirement":"empty config is handled","locations":["src/config.ts","test/config.test.ts"],"problem":"no regression case exists","evidence":"test search found no empty-config case","expected":"the suite covers empty config","fix_direction":"add the missing case and repair behavior if needed","verification":["pnpm test"]}],"observations":[]}
|
|
594
|
+
RAFI_QA_FAILURE_REPORT_END
|
|
565
595
|
STEP_STATUS: qa_fail | issues="missing test for empty config"
|
|
566
596
|
```
|
|
567
597
|
|
|
568
598
|
If QA fails:
|
|
569
599
|
|
|
570
|
-
- Foreman sends
|
|
600
|
+
- Foreman sends the Builder the complete validated report, ticket requirements, current source digest, and prior report/remediation history.
|
|
571
601
|
- Foreman reruns QA.
|
|
572
602
|
- QA turns do not count against `--steps`.
|
|
573
603
|
|
|
@@ -50,6 +50,9 @@ export declare class ClaudeAdapter implements BuilderAdapter {
|
|
|
50
50
|
private readonly pumpDone;
|
|
51
51
|
private _sessionId?;
|
|
52
52
|
private _sessionRef?;
|
|
53
|
+
private readonly sessionIdentityReady;
|
|
54
|
+
private resolveSessionIdentity;
|
|
55
|
+
private rejectSessionIdentity;
|
|
53
56
|
private readonly stderrChunks;
|
|
54
57
|
private turnSignals;
|
|
55
58
|
private structuredError?;
|
|
@@ -82,6 +85,7 @@ export declare class ClaudeAdapter implements BuilderAdapter {
|
|
|
82
85
|
private observeToolResults;
|
|
83
86
|
sessionId(): string | undefined;
|
|
84
87
|
sessionRef(): ProviderSessionRefV1 | undefined;
|
|
88
|
+
prepareSession(timeoutMs?: number): Promise<ProviderSessionRefV1>;
|
|
85
89
|
adoptSessionRef(ref: ProviderSessionRefV1): void;
|
|
86
90
|
validateSession(): Promise<SessionAvailabilityV1>;
|
|
87
91
|
compact(): Promise<CompactResult>;
|
|
@@ -95,6 +99,7 @@ export declare class ClaudeAdapter implements BuilderAdapter {
|
|
|
95
99
|
switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
|
|
96
100
|
events(): AsyncIterable<BuilderEvent>;
|
|
97
101
|
close(): Promise<void>;
|
|
102
|
+
private failSessionIdentity;
|
|
98
103
|
private observeSession;
|
|
99
104
|
private settlePending;
|
|
100
105
|
private streamFailureResult;
|
package/dist/adapters/claude.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import { dirname, join } from "node:path";
|
|
2
3
|
/** Lazy-load the Claude Agent SDK. Throws an actionable error if not installed. */
|
|
3
4
|
export async function requireClaudeSDK() {
|
|
4
5
|
try {
|
|
@@ -22,25 +23,42 @@ import { SessionUnavailableError, sessionUnavailableResult } from "./sessionFail
|
|
|
22
23
|
* `canUseTool` is omitted here — it's a closure that the constructor adds.
|
|
23
24
|
*/
|
|
24
25
|
export function buildClaudeQueryOptions(opts) {
|
|
26
|
+
const qaReadOnly = opts.sessionRole === "qa" || opts.sandboxMode === "read-only";
|
|
25
27
|
const base = {
|
|
26
28
|
cwd: opts.cwd,
|
|
27
29
|
pathToClaudeCodeExecutable: opts.runtimeExecutable,
|
|
28
|
-
env: { ...process.env },
|
|
30
|
+
env: qaReadOnly ? qaEnvironment(process.env, opts.cwd) : { ...process.env },
|
|
29
31
|
model: opts.model,
|
|
30
32
|
resume: opts.resumeSessionRef?.sessionId ?? opts.resumeSessionId,
|
|
31
|
-
permissionMode: "acceptEdits",
|
|
33
|
+
permissionMode: qaReadOnly ? "default" : "acceptEdits",
|
|
32
34
|
effort: opts.effort,
|
|
33
35
|
extraArgs: opts.fast ? { fast: null } : undefined,
|
|
34
|
-
settingSources: ["user", "project", "local"],
|
|
36
|
+
settingSources: qaReadOnly ? [] : ["user", "project", "local"],
|
|
37
|
+
...(qaReadOnly ? { disallowedTools: ["Write", "Edit", "NotebookEdit"] } : {}),
|
|
38
|
+
...(qaReadOnly ? { sandbox: {
|
|
39
|
+
enabled: true,
|
|
40
|
+
failIfUnavailable: true,
|
|
41
|
+
autoAllowBashIfSandboxed: true,
|
|
42
|
+
allowUnsandboxedCommands: false,
|
|
43
|
+
filesystem: { allowWrite: [join(dirname(opts.cwd), "scratch")], denyWrite: [opts.cwd, ...(opts.configRoot ? [opts.configRoot] : [])] },
|
|
44
|
+
} } : {}),
|
|
35
45
|
};
|
|
36
|
-
|
|
37
|
-
|
|
46
|
+
const exactSkills = opts.preloadedSkillContent?.length
|
|
47
|
+
? ["# Preloaded Skills", "Use the following skills for this run.", ...opts.preloadedSkillContent.map((skill) => skill.content)].join("\n\n")
|
|
48
|
+
: undefined;
|
|
49
|
+
const systemAppend = [opts.systemPromptAppend, exactSkills].filter((part) => Boolean(part)).join("\n\n");
|
|
50
|
+
if (systemAppend) {
|
|
51
|
+
base.systemPrompt = { type: "preset", preset: "claude_code", append: systemAppend };
|
|
38
52
|
}
|
|
39
53
|
if (opts.skills !== undefined) {
|
|
40
54
|
base.skills = opts.skills;
|
|
41
55
|
}
|
|
42
56
|
return base;
|
|
43
57
|
}
|
|
58
|
+
function qaEnvironment(source, cwd) {
|
|
59
|
+
const allowed = ["PATH", "HOME", "USER", "LOGNAME", "SHELL", "TMPDIR", "LANG", "LC_ALL", "TERM", "NO_COLOR", "FORCE_COLOR", "ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN", "HTTPS_PROXY", "HTTP_PROXY", "NO_PROXY", "SSL_CERT_FILE", "SSL_CERT_DIR", "NODE_EXTRA_CA_CERTS"];
|
|
60
|
+
return { ...Object.fromEntries(allowed.flatMap((key) => source[key] === undefined ? [] : [[key, source[key]]])), TMPDIR: join(dirname(cwd), "scratch") };
|
|
61
|
+
}
|
|
44
62
|
export async function probeClaudeSession(ref, input) {
|
|
45
63
|
const now = input.now ?? new Date();
|
|
46
64
|
const local = validateProviderSessionScope(ref, {
|
|
@@ -148,6 +166,9 @@ export class ClaudeAdapter {
|
|
|
148
166
|
pumpDone;
|
|
149
167
|
_sessionId;
|
|
150
168
|
_sessionRef;
|
|
169
|
+
sessionIdentityReady;
|
|
170
|
+
resolveSessionIdentity;
|
|
171
|
+
rejectSessionIdentity;
|
|
151
172
|
stderrChunks = [];
|
|
152
173
|
turnSignals = [];
|
|
153
174
|
structuredError;
|
|
@@ -210,11 +231,28 @@ export class ClaudeAdapter {
|
|
|
210
231
|
this.opts = opts;
|
|
211
232
|
this._sessionId = opts.resumeSessionRef?.sessionId ?? opts.resumeSessionId;
|
|
212
233
|
this._sessionRef = opts.resumeSessionRef;
|
|
234
|
+
this.sessionIdentityReady = new Promise((resolve, reject) => {
|
|
235
|
+
this.resolveSessionIdentity = resolve;
|
|
236
|
+
this.rejectSessionIdentity = reject;
|
|
237
|
+
});
|
|
238
|
+
void this.sessionIdentityReady.catch(() => { });
|
|
213
239
|
this.query = query({
|
|
214
240
|
prompt: this.inbox,
|
|
215
241
|
options: {
|
|
216
242
|
...buildClaudeQueryOptions(opts),
|
|
217
243
|
abortController: this.abort,
|
|
244
|
+
hooks: { SessionStart: [{ hooks: [async (input) => {
|
|
245
|
+
if (input.hook_event_name !== "SessionStart" || input.agent_id)
|
|
246
|
+
return {};
|
|
247
|
+
try {
|
|
248
|
+
this.observeSession(input.session_id, input.cwd);
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
this.failSessionIdentity(error instanceof Error ? error : new Error(String(error)));
|
|
252
|
+
throw error;
|
|
253
|
+
}
|
|
254
|
+
return {};
|
|
255
|
+
}] }] },
|
|
218
256
|
stderr: (data) => this.captureStderr(data),
|
|
219
257
|
canUseTool: async (toolName, input, requestOptions = {}) => {
|
|
220
258
|
const decision = await opts.permission({
|
|
@@ -251,6 +289,7 @@ export class ClaudeAdapter {
|
|
|
251
289
|
const message = normalizeRuntimeErrorText("claude", rawMessage, null, "builder stream");
|
|
252
290
|
this.eventQueue.push({ kind: "error", message });
|
|
253
291
|
const result = this.streamFailureResult(message, isMissingClaudeSession(rawMessage));
|
|
292
|
+
this.rejectSessionIdentity(new Error(message));
|
|
254
293
|
this.terminalResult = result;
|
|
255
294
|
this.settlePending(result);
|
|
256
295
|
}
|
|
@@ -265,13 +304,14 @@ export class ClaudeAdapter {
|
|
|
265
304
|
}
|
|
266
305
|
if (!this.closed && !this.terminalResult)
|
|
267
306
|
this.terminalResult = this.streamFailureResult("Claude stream ended without a result", Boolean(this.opts.resumeSessionRef ?? this.opts.resumeSessionId));
|
|
307
|
+
if (!this._sessionRef)
|
|
308
|
+
this.rejectSessionIdentity(new Error("Claude stream ended before exposing a scoped session identity"));
|
|
268
309
|
this.eventQueue.close();
|
|
269
310
|
}
|
|
270
311
|
}
|
|
271
312
|
handle(msg) {
|
|
272
313
|
if ("session_id" in msg && typeof msg.session_id === "string") {
|
|
273
|
-
this.
|
|
274
|
-
this.observeSession(msg.session_id);
|
|
314
|
+
this.observeSession(msg.session_id, "cwd" in msg && typeof msg.cwd === "string" ? msg.cwd : undefined);
|
|
275
315
|
}
|
|
276
316
|
if (msg.type === "assistant") {
|
|
277
317
|
if (msg.error) {
|
|
@@ -279,6 +319,7 @@ export class ClaudeAdapter {
|
|
|
279
319
|
this.turnSignals.push(`assistant error: ${msg.error}`);
|
|
280
320
|
}
|
|
281
321
|
for (const block of msg.message.content) {
|
|
322
|
+
this.eventQueue.push({ kind: "provider-item", provider: "claude", lifecycle: "completed", itemType: String(block.type ?? "unknown"), payload: structuredClone(block), payloadCompleteness: "complete", providerTurnId: this.activeProviderTurnId, eventId: randomUUID(), observedAt: new Date().toISOString() });
|
|
282
323
|
if (block.type === "text" && block.text) {
|
|
283
324
|
this.eventQueue.push({ kind: "text", text: block.text, byteCount: Buffer.byteLength(block.text), digest: createHash("sha256").update(block.text).digest("hex"), eventId: randomUUID(), observedAt: new Date().toISOString() });
|
|
284
325
|
this.opts.observer?.signal(true);
|
|
@@ -497,6 +538,7 @@ export class ClaudeAdapter {
|
|
|
497
538
|
for (const block of body.content) {
|
|
498
539
|
if (block.type !== "tool_result")
|
|
499
540
|
continue;
|
|
541
|
+
this.eventQueue.push({ kind: "provider-item", provider: "claude", lifecycle: "completed", itemType: "tool_result", payload: structuredClone(block), payloadCompleteness: "complete", providerTurnId: this.activeProviderTurnId, eventId: randomUUID(), observedAt: new Date().toISOString() });
|
|
500
542
|
const callId = typeof block.tool_use_id === "string" ? block.tool_use_id : undefined;
|
|
501
543
|
if (!callId)
|
|
502
544
|
continue;
|
|
@@ -517,6 +559,37 @@ export class ClaudeAdapter {
|
|
|
517
559
|
return this._sessionId;
|
|
518
560
|
}
|
|
519
561
|
sessionRef() { return this._sessionRef; }
|
|
562
|
+
async prepareSession(timeoutMs = 30_000) {
|
|
563
|
+
if (this.closed || this.streamEnded || this.terminalResult)
|
|
564
|
+
throw new Error(this.terminalResult?.text ?? "Claude session is closed");
|
|
565
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0)
|
|
566
|
+
throw new Error("Claude session initialization timeout must be positive");
|
|
567
|
+
let timer;
|
|
568
|
+
let aborted;
|
|
569
|
+
try {
|
|
570
|
+
const interrupted = new Promise((_resolve, reject) => {
|
|
571
|
+
aborted = () => reject(new Error(this.terminalResult?.text ?? "Claude session closed during initialization"));
|
|
572
|
+
this.abort.signal.addEventListener("abort", aborted, { once: true });
|
|
573
|
+
if (this.abort.signal.aborted)
|
|
574
|
+
aborted();
|
|
575
|
+
timer = setTimeout(() => reject(new Error(`Claude did not expose a scoped session identity within ${timeoutMs}ms`)), timeoutMs);
|
|
576
|
+
});
|
|
577
|
+
const [, ref] = await Promise.race([Promise.all([this.query.initializationResult(), this.sessionIdentityReady]), interrupted]);
|
|
578
|
+
if (!ref.validatedAt)
|
|
579
|
+
throw new Error("Claude session identity was not provider-validated during initialization");
|
|
580
|
+
return ref;
|
|
581
|
+
}
|
|
582
|
+
catch (error) {
|
|
583
|
+
this.failSessionIdentity(error instanceof Error ? error : new Error(String(error)));
|
|
584
|
+
throw error;
|
|
585
|
+
}
|
|
586
|
+
finally {
|
|
587
|
+
if (timer)
|
|
588
|
+
clearTimeout(timer);
|
|
589
|
+
if (aborted)
|
|
590
|
+
this.abort.signal.removeEventListener("abort", aborted);
|
|
591
|
+
}
|
|
592
|
+
}
|
|
520
593
|
adoptSessionRef(ref) {
|
|
521
594
|
if (ref.provider !== "claude" || ref.sessionId !== this._sessionId)
|
|
522
595
|
throw new Error("cannot adopt a session reference for a different Claude conversation");
|
|
@@ -639,6 +712,8 @@ export class ClaudeAdapter {
|
|
|
639
712
|
if (this.closed)
|
|
640
713
|
return;
|
|
641
714
|
this.closed = true;
|
|
715
|
+
this.rejectSessionIdentity(new Error("Claude session closed during initialization"));
|
|
716
|
+
this.abort.abort();
|
|
642
717
|
this.inbox.close();
|
|
643
718
|
try {
|
|
644
719
|
await this.query.interrupt();
|
|
@@ -646,18 +721,49 @@ export class ClaudeAdapter {
|
|
|
646
721
|
catch {
|
|
647
722
|
// interrupt is best-effort — ignore if no turn is active
|
|
648
723
|
}
|
|
649
|
-
this.abort.abort();
|
|
650
724
|
await this.pumpDone.catch(() => { });
|
|
651
725
|
}
|
|
652
|
-
|
|
653
|
-
|
|
726
|
+
failSessionIdentity(error) {
|
|
727
|
+
this.rejectSessionIdentity(error);
|
|
728
|
+
const result = sessionUnavailableResult(new SessionUnavailableError({
|
|
729
|
+
runtime: "claude", phase: this.pending ? "turn" : "preflight", dispatchState: this.pending ? "unknown" : "not-sent",
|
|
730
|
+
executable: this.opts.runtimeExecutable ?? "claude", cwd: this.opts.cwd, diagnostics: error.message,
|
|
731
|
+
availability: { version: 1, status: "unavailable", checkedAt: new Date().toISOString(), reason: "attach-failed", detail: error.message, ...(this._sessionRef ? { sessionRef: this._sessionRef } : {}) },
|
|
732
|
+
}));
|
|
733
|
+
this.terminalResult = result;
|
|
734
|
+
this.settlePending(result);
|
|
735
|
+
this.inbox.close();
|
|
736
|
+
this.abort.abort();
|
|
737
|
+
}
|
|
738
|
+
observeSession(sessionId, observedCwd) {
|
|
739
|
+
const expectedId = this._sessionRef?.sessionId ?? this._sessionId;
|
|
740
|
+
if (!sessionId.trim() || (expectedId && expectedId !== sessionId)) {
|
|
741
|
+
const error = new Error(`Claude initialized session ${sessionId || "without an ID"} instead of requested session ${expectedId ?? "a nonempty ID"}`);
|
|
742
|
+
this.failSessionIdentity(error);
|
|
743
|
+
throw error;
|
|
744
|
+
}
|
|
745
|
+
if (observedCwd !== undefined && canonicalSessionPath(observedCwd) !== canonicalSessionPath(this.opts.cwd)) {
|
|
746
|
+
const error = new Error(`Claude session cwd ${observedCwd} does not match ${this.opts.cwd}`);
|
|
747
|
+
this.failSessionIdentity(error);
|
|
748
|
+
throw error;
|
|
749
|
+
}
|
|
750
|
+
this._sessionId = sessionId;
|
|
751
|
+
if (this._sessionRef?.validatedAt) {
|
|
752
|
+
this.resolveSessionIdentity(this._sessionRef);
|
|
654
753
|
return;
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
source: "observed",
|
|
660
|
-
|
|
754
|
+
}
|
|
755
|
+
const now = new Date().toISOString();
|
|
756
|
+
const validatedAt = observedCwd !== undefined || this.opts.sessionRole !== "qa" ? now : undefined;
|
|
757
|
+
this._sessionRef = this._sessionRef
|
|
758
|
+
? { ...this._sessionRef, source: "observed", ...(validatedAt ? { validatedAt } : {}) }
|
|
759
|
+
: createProviderSessionRef({
|
|
760
|
+
provider: "claude", sessionId, cwd: this.opts.cwd, configRoot: this.opts.configRoot ?? this.opts.cwd,
|
|
761
|
+
role: this.opts.sessionRole, stream: this.opts.sessionStream, generation: this.opts.sessionGeneration,
|
|
762
|
+
workspaceIdentity: this.opts.workspaceIdentity, ticketId: this.opts.ticketId, deliveryUnitId: this.opts.deliveryUnitId,
|
|
763
|
+
source: "observed", validatedAt,
|
|
764
|
+
});
|
|
765
|
+
if (this._sessionRef.validatedAt)
|
|
766
|
+
this.resolveSessionIdentity(this._sessionRef);
|
|
661
767
|
}
|
|
662
768
|
settlePending(result, emit = true) {
|
|
663
769
|
const pending = this.pending;
|
|
@@ -721,6 +827,8 @@ function activityPhase(phase) {
|
|
|
721
827
|
return "planning uninstall";
|
|
722
828
|
if (phase === "manager")
|
|
723
829
|
return "analyzing diagnostics";
|
|
830
|
+
if (phase === "discovery")
|
|
831
|
+
return "discovering project context";
|
|
724
832
|
return "building";
|
|
725
833
|
}
|
|
726
834
|
function formatClaudeFailure(failure) {
|
package/dist/adapters/codex.d.ts
CHANGED
|
@@ -48,6 +48,7 @@ export declare class CodexAdapter implements BuilderAdapter {
|
|
|
48
48
|
compact(): Promise<CompactResult>;
|
|
49
49
|
private compactInternal;
|
|
50
50
|
prepareAutoCompaction(thresholdPercent?: number | undefined): Promise<NativeAutoCompactionPolicy | void>;
|
|
51
|
+
requiresAutoCompactionSetupTurn(): boolean;
|
|
51
52
|
autoCompactionPolicy(): NativeAutoCompactionPolicy | undefined;
|
|
52
53
|
drainNativeCompactions(): NativeCompaction[];
|
|
53
54
|
restoreNativeCompactions(compactions: NativeCompaction[]): void;
|
|
@@ -57,6 +58,7 @@ export declare class CodexAdapter implements BuilderAdapter {
|
|
|
57
58
|
switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
|
|
58
59
|
sessionId(): string | undefined;
|
|
59
60
|
sessionRef(): ProviderSessionRefV1 | undefined;
|
|
61
|
+
prepareSession(): Promise<ProviderSessionRefV1>;
|
|
60
62
|
adoptSessionRef(ref: ProviderSessionRefV1): void;
|
|
61
63
|
validateSession(): Promise<SessionAvailabilityV1>;
|
|
62
64
|
events(): AsyncIterable<BuilderEvent>;
|
package/dist/adapters/codex.js
CHANGED
|
@@ -248,6 +248,8 @@ export class CodexAdapter {
|
|
|
248
248
|
// setup turn, then restart the app server with the provider-native ceiling
|
|
249
249
|
// before any Builder or QA work is sent.
|
|
250
250
|
if (!this.usage?.maximum) {
|
|
251
|
+
if (this.opts.allowAutoCompactionSetupTurn === false)
|
|
252
|
+
return;
|
|
251
253
|
const toolsBefore = this.observedToolCalls;
|
|
252
254
|
const setup = await this.sendTurnInternal("Rafi internal initialization only. Do not call tools or modify files. Reply briefly that the context is ready.");
|
|
253
255
|
if (setup.isError)
|
|
@@ -272,6 +274,9 @@ export class CodexAdapter {
|
|
|
272
274
|
};
|
|
273
275
|
return this.preparedAutoCompactionPolicy;
|
|
274
276
|
}
|
|
277
|
+
requiresAutoCompactionSetupTurn() {
|
|
278
|
+
return this.opts.autoCompactThresholdPercent !== undefined && !this.autoCompactionPrepared && !this.usage?.maximum;
|
|
279
|
+
}
|
|
275
280
|
autoCompactionPolicy() { return this.preparedAutoCompactionPolicy; }
|
|
276
281
|
drainNativeCompactions() {
|
|
277
282
|
const pending = this.nativeCompactions;
|
|
@@ -300,6 +305,14 @@ export class CodexAdapter {
|
|
|
300
305
|
}
|
|
301
306
|
sessionId() { return this._sessionId; }
|
|
302
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
|
+
}
|
|
303
316
|
adoptSessionRef(ref) {
|
|
304
317
|
if (ref.provider !== "codex" || ref.sessionId !== this._sessionId)
|
|
305
318
|
throw new Error("cannot adopt a session reference for a different Codex thread");
|
|
@@ -395,20 +408,22 @@ export class CodexAdapter {
|
|
|
395
408
|
availability,
|
|
396
409
|
});
|
|
397
410
|
}
|
|
398
|
-
|
|
399
|
-
if (!this._sessionId)
|
|
411
|
+
if (!returnedSessionId?.trim())
|
|
400
412
|
throw new Error(`${method} did not return a thread ID`);
|
|
401
413
|
const providerCwd = typeof thread?.cwd === "string" ? canonicalSessionPath(thread.cwd) : undefined;
|
|
402
|
-
|
|
403
|
-
|
|
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 };
|
|
404
417
|
this.sessionAvailability = availability;
|
|
405
418
|
throw new SessionUnavailableError({ runtime: "codex", phase: "attach", dispatchState: "not-sent", executable: this.opts.runtimeExecutable ?? "codex", cwd: this.opts.cwd, diagnostics: availability.detail, availability });
|
|
406
419
|
}
|
|
420
|
+
this._sessionId = returnedSessionId;
|
|
407
421
|
if (!this._sessionRef) {
|
|
408
422
|
this._sessionRef = createProviderSessionRef({
|
|
409
423
|
provider: "codex", sessionId: this._sessionId, cwd: providerCwd ?? this.opts.cwd, configRoot: this.opts.configRoot ?? this.opts.cwd,
|
|
410
424
|
role: this.opts.sessionRole, stream: this.opts.sessionStream, generation: this.opts.sessionGeneration,
|
|
411
425
|
workspaceIdentity: this.opts.workspaceIdentity, ticketId: this.opts.ticketId, deliveryUnitId: this.opts.deliveryUnitId,
|
|
426
|
+
validatedAt: new Date().toISOString(),
|
|
412
427
|
});
|
|
413
428
|
}
|
|
414
429
|
else {
|
|
@@ -476,6 +491,8 @@ export class CodexAdapter {
|
|
|
476
491
|
const params = message.params ?? {};
|
|
477
492
|
if (message.method === "item/started") {
|
|
478
493
|
const item = params.item;
|
|
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() });
|
|
479
496
|
if (isCodexToolItem(item)) {
|
|
480
497
|
this.observedToolCalls += 1;
|
|
481
498
|
const callId = codexItemId(item) ?? randomUUID();
|
|
@@ -493,6 +510,8 @@ export class CodexAdapter {
|
|
|
493
510
|
}
|
|
494
511
|
else if (message.method === "item/completed") {
|
|
495
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() });
|
|
496
515
|
if (isCodexToolItem(item)) {
|
|
497
516
|
const callId = codexItemId(item);
|
|
498
517
|
const spanId = callId ? this.toolSpans.get(callId) : undefined;
|
|
@@ -643,6 +662,9 @@ export class CodexAdapter {
|
|
|
643
662
|
child.kill("SIGTERM");
|
|
644
663
|
}
|
|
645
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
|
+
}
|
|
646
668
|
if (!this.opts.skills?.length)
|
|
647
669
|
return undefined;
|
|
648
670
|
const blocks = this.opts.skills.map((skill) => loadSkillMarkdown(this.opts.cwd, skill)).filter((block) => Boolean(block));
|
|
@@ -672,6 +694,8 @@ function activityPhase(phase) {
|
|
|
672
694
|
return "planning uninstall";
|
|
673
695
|
if (phase === "manager")
|
|
674
696
|
return "analyzing diagnostics";
|
|
697
|
+
if (phase === "discovery")
|
|
698
|
+
return "discovering project context";
|
|
675
699
|
return "building";
|
|
676
700
|
}
|
|
677
701
|
function codexItemActivity(item, completed = false) {
|
|
@@ -699,16 +723,16 @@ function isCodexToolItem(item) {
|
|
|
699
723
|
}
|
|
700
724
|
function codexItemId(item) { const value = item?.id ?? item?.itemId ?? item?.callId; return typeof value === "string" && value ? value : undefined; }
|
|
701
725
|
function codexToolName(item) { return String(item?.tool ?? item?.name ?? item?.type ?? "tool"); }
|
|
702
|
-
function
|
|
703
|
-
|
|
726
|
+
function exactProviderItem(item) { return structuredClone(item); }
|
|
727
|
+
function exactToolInput(item) { return item ? exactProviderItem(item) : {}; }
|
|
704
728
|
function boundedToolInput(item) { if (!item)
|
|
705
729
|
return {}; return Object.fromEntries(Object.entries(exactToolInput(item)).map(([key, value]) => [key, typeof value === "string" ? value.slice(0, 1000) : value])); }
|
|
706
730
|
function codexOutputSummary(item) { const value = item?.aggregatedOutput ?? item?.output ?? item?.stdout; if (typeof value !== "string" || !value)
|
|
707
731
|
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) }; }
|
|
708
732
|
function finiteNumber(value) { const result = Number(value); return Number.isFinite(result) ? result : undefined; }
|
|
709
733
|
function loadSkillMarkdown(cwd, skill) {
|
|
710
|
-
const projectPath = join(cwd, ".agents", "skills", skill, "SKILL.md");
|
|
711
|
-
if (
|
|
734
|
+
const projectPath = [join(cwd, ".codex", "skills", skill, "SKILL.md"), join(cwd, ".agents", "skills", skill, "SKILL.md")].find(existsSync);
|
|
735
|
+
if (projectPath)
|
|
712
736
|
return `## ${skill}\n${readFileSync(projectPath, "utf8").trim()}`;
|
|
713
737
|
try {
|
|
714
738
|
const bundled = loadSkill(skill);
|
|
@@ -28,10 +28,12 @@ export declare class RecoveringAdapter implements BuilderAdapter {
|
|
|
28
28
|
sendTurn(text: string): Promise<TurnResult>;
|
|
29
29
|
sessionId(): string | undefined;
|
|
30
30
|
sessionRef(): ProviderSessionRefV1 | undefined;
|
|
31
|
+
prepareSession(): Promise<ProviderSessionRefV1>;
|
|
31
32
|
adoptSessionRef(ref: ProviderSessionRefV1): void;
|
|
32
33
|
validateSession(): Promise<SessionAvailabilityV1>;
|
|
33
34
|
compact(): Promise<CompactResult>;
|
|
34
35
|
prepareAutoCompaction(thresholdPercent?: number): Promise<NativeAutoCompactionPolicy | void>;
|
|
36
|
+
requiresAutoCompactionSetupTurn(): boolean;
|
|
35
37
|
autoCompactionPolicy(): NativeAutoCompactionPolicy | undefined;
|
|
36
38
|
drainNativeCompactions(): import("./types.js").NativeCompaction[];
|
|
37
39
|
restoreNativeCompactions(compactions: NativeCompaction[]): void;
|
|
@@ -72,12 +72,18 @@ export class RecoveringAdapter {
|
|
|
72
72
|
return this.adapter.sessionId();
|
|
73
73
|
}
|
|
74
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
|
+
}
|
|
75
80
|
adoptSessionRef(ref) { this.adapter.adoptSessionRef?.(ref); }
|
|
76
81
|
validateSession() {
|
|
77
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" });
|
|
78
83
|
}
|
|
79
84
|
compact() { return this.adapter.compact?.() ?? Promise.resolve({ ok: false, error: "native compaction unavailable" }); }
|
|
80
85
|
prepareAutoCompaction(thresholdPercent) { return this.adapter.prepareAutoCompaction?.(thresholdPercent) ?? Promise.resolve(); }
|
|
86
|
+
requiresAutoCompactionSetupTurn() { return this.adapter.requiresAutoCompactionSetupTurn?.() ?? false; }
|
|
81
87
|
autoCompactionPolicy() { return this.adapter.autoCompactionPolicy?.(); }
|
|
82
88
|
drainNativeCompactions() { return this.adapter.drainNativeCompactions?.() ?? []; }
|
|
83
89
|
restoreNativeCompactions(compactions) { this.adapter.restoreNativeCompactions?.(compactions); }
|
package/dist/adapters/types.d.ts
CHANGED
|
@@ -106,6 +106,14 @@ export type BuilderEvent = ({
|
|
|
106
106
|
rawOutputBytes?: number;
|
|
107
107
|
completionKnown?: boolean;
|
|
108
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;
|
|
109
117
|
} | {
|
|
110
118
|
kind: "activity";
|
|
111
119
|
state: string;
|
|
@@ -227,8 +235,15 @@ export interface BuilderAdapterOptions {
|
|
|
227
235
|
systemPromptAppend?: string;
|
|
228
236
|
/** Skill names to preload for this session (Claude: lazy-loaded; Codex: flattened). */
|
|
229
237
|
skills?: string[];
|
|
238
|
+
/** Exact host-frozen skill text injected into the provider instruction. */
|
|
239
|
+
preloadedSkillContent?: Array<{
|
|
240
|
+
name: string;
|
|
241
|
+
content: string;
|
|
242
|
+
}>;
|
|
230
243
|
/** Provider-native context ceiling, as a percentage of that provider's model window. */
|
|
231
244
|
autoCompactThresholdPercent?: number;
|
|
245
|
+
/** QA forbids an unjournaled model turn merely to discover the context window. */
|
|
246
|
+
allowAutoCompactionSetupTurn?: boolean;
|
|
232
247
|
/** Raw-adapter observability; wrapper adapters must not persist re-emitted events. */
|
|
233
248
|
observer?: RunObserver;
|
|
234
249
|
}
|
|
@@ -240,6 +255,11 @@ export interface BuilderAdapter {
|
|
|
240
255
|
sessionId(): string | undefined;
|
|
241
256
|
/** Current location-scoped session reference, once known. */
|
|
242
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>;
|
|
243
263
|
/** Host-only metadata promotion after a validated handoff is accepted. */
|
|
244
264
|
adoptSessionRef?(ref: ProviderSessionRefV1): void;
|
|
245
265
|
/** Attach/probe a requested exact session without starting a provider turn. */
|
|
@@ -252,6 +272,8 @@ export interface BuilderAdapter {
|
|
|
252
272
|
* tool-heavy first turn is protected too.
|
|
253
273
|
*/
|
|
254
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;
|
|
255
277
|
/** The last provider-native automatic-compaction policy verified on this transport. */
|
|
256
278
|
autoCompactionPolicy?(): NativeAutoCompactionPolicy | undefined;
|
|
257
279
|
/** Consume provider-native compactions observed since the prior drain. */
|
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 ",
|
|
@@ -244,6 +262,8 @@ function phaseForRole(role) {
|
|
|
244
262
|
return "uninstaller";
|
|
245
263
|
if (role === "manager")
|
|
246
264
|
return "manager";
|
|
265
|
+
if (role === "discovery")
|
|
266
|
+
return "discovery";
|
|
247
267
|
return "builder";
|
|
248
268
|
}
|
|
249
269
|
export function readRoleDefaultsForExecution(projectDir, role) {
|
|
@@ -339,7 +359,7 @@ export async function runRoleInstruction(opts) {
|
|
|
339
359
|
}
|
|
340
360
|
}
|
|
341
361
|
function configurableRole(role) {
|
|
342
|
-
return ["builder", "qa", "planner", "ticket-maker", "uninstaller", "manager"].includes(role)
|
|
362
|
+
return ["builder", "qa", "planner", "ticket-maker", "uninstaller", "manager", "discovery"].includes(role)
|
|
343
363
|
? role
|
|
344
364
|
: "builder";
|
|
345
365
|
}
|
|
@@ -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;
|