ai-foreman 1.7.6 → 1.7.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +26 -1
  2. package/dist/activity.d.ts +3 -0
  3. package/dist/activity.js +31 -6
  4. package/dist/adapters/claude.d.ts +61 -6
  5. package/dist/adapters/claude.js +544 -36
  6. package/dist/adapters/codex.d.ts +28 -1
  7. package/dist/adapters/codex.js +398 -27
  8. package/dist/adapters/recovering.d.ts +13 -2
  9. package/dist/adapters/recovering.js +32 -4
  10. package/dist/adapters/sessionFailure.d.ts +18 -0
  11. package/dist/adapters/sessionFailure.js +51 -0
  12. package/dist/adapters/types.d.ts +117 -9
  13. package/dist/agentRun.d.ts +4 -1
  14. package/dist/agentRun.js +96 -9
  15. package/dist/branch/currentGuard.d.ts +38 -0
  16. package/dist/branch/currentGuard.js +71 -0
  17. package/dist/branch/planner.d.ts +1 -1
  18. package/dist/branch/planner.js +2 -7
  19. package/dist/branch/prefix.d.ts +8 -0
  20. package/dist/branch/prefix.js +26 -0
  21. package/dist/branch/resume.d.ts +2 -0
  22. package/dist/branch/resume.js +8 -0
  23. package/dist/branch/runner.d.ts +14 -2
  24. package/dist/branch/runner.js +77 -17
  25. package/dist/buildRuns.d.ts +13 -4
  26. package/dist/buildRuns.js +85 -19
  27. package/dist/cli/handoffs.d.ts +2 -0
  28. package/dist/cli/handoffs.js +74 -0
  29. package/dist/cli/start.js +1079 -43
  30. package/dist/cli/tickets.js +199 -3
  31. package/dist/continuity.d.ts +92 -0
  32. package/dist/continuity.js +415 -0
  33. package/dist/foreman.d.ts +5 -1
  34. package/dist/foreman.js +41 -6
  35. package/dist/handoffs.d.ts +69 -0
  36. package/dist/handoffs.js +546 -0
  37. package/dist/log.d.ts +1 -1
  38. package/dist/permissions/policy.d.ts +5 -1
  39. package/dist/permissions/policy.js +48 -1
  40. package/dist/qaReview.d.ts +6 -6
  41. package/dist/qaReview.js +24 -14
  42. package/dist/sessionAvailability.d.ts +15 -0
  43. package/dist/sessionAvailability.js +57 -0
  44. package/dist/sessionIdentity.d.ts +33 -0
  45. package/dist/sessionIdentity.js +132 -0
  46. package/dist/sessionLifecycle.d.ts +198 -5
  47. package/dist/sessionLifecycle.js +1373 -20
  48. package/dist/statusReporter.d.ts +84 -8
  49. package/dist/statusReporter.js +270 -14
  50. package/dist/ticketPlanning.js +8 -0
  51. package/dist/ticketPopulation.js +88 -12
  52. package/dist/tickets/commands.js +20 -12
  53. package/dist/tickets/groupReset.d.ts +25 -0
  54. package/dist/tickets/groupReset.js +279 -0
  55. package/dist/tickets/groups.d.ts +36 -0
  56. package/dist/tickets/groups.js +68 -0
  57. package/dist/tickets/importer.js +27 -3
  58. package/dist/tickets/recommendations.js +31 -7
  59. package/dist/tickets/reset.d.ts +1 -0
  60. package/dist/tickets/reset.js +1 -0
  61. package/dist/tickets/setupConfig.d.ts +1 -0
  62. package/dist/tickets/setupConfig.js +10 -2
  63. package/dist/tickets/stateDb.d.ts +34 -0
  64. package/dist/tickets/stateDb.js +207 -0
  65. package/dist/tickets/ticketLoader.js +5 -2
  66. package/dist/tickets/validate.js +4 -0
  67. package/dist/workflowDb.d.ts +145 -3
  68. package/dist/workflowDb.js +571 -10
  69. package/package.json +32 -4
@@ -17,11 +17,13 @@ import { assertEffortLevel, createRoleBuilder, makeLogPath, readOnlyPermissionCo
17
17
  import { DEFAULT_TICKET_SETUP, configuredPlanningSources, configuredAppName, defaultAppName, detectDefaultBranch, detectPackageName, ensureRafiConfigForTicketSetup, externalSources, hasTicketSetupConfig, loadTicketSetupConfig, loadTicketSetupConfigWithDefaults, localSourcePaths, mergeTicketSetup, recommendedBuildDefaults, saveTicketSetupConfig, syncTicketLimits, urlSources, } from "../tickets/setupConfig.js";
18
18
  import { fetchAndSnapshotUrl, snapshotExternalLocalFile } from "../tickets/sourceFetch.js";
19
19
  import { checkpointInterview, completeInterview, createInterviewRecord, failInterview, } from "../interviews.js";
20
- import { applyTicketPopulation, authorizeTicketRetirements, extractTicketPopulationProposal, materializeTicketPopulation, TICKET_POPULATION_PROPOSAL_END, TICKET_POPULATION_PROPOSAL_START } from "../ticketPopulation.js";
20
+ import { applyTicketPopulation, authorizeTicketRetirements, extractTicketPopulationProposal, materializeTicketPopulation, recoverTicketPublications, TICKET_POPULATION_PROPOSAL_END, TICKET_POPULATION_PROPOSAL_START } from "../ticketPopulation.js";
21
21
  import { loadTickets } from "../tickets/ticketLoader.js";
22
22
  import { resolveTicketPaths } from "../tickets/config.js";
23
23
  import { formatTicketDetails, getTicketDetails } from "../tickets/details.js";
24
- import { previewTicketReset, recoverTicketResetPublications, resetTickets } from "../tickets/reset.js";
24
+ import { applyResolvedTicketReset, previewTicketReset, recoverTicketResetPublications, resetTickets, resolveTicketResetSelection, TicketResetDependencyConflictError } from "../tickets/reset.js";
25
+ import { validateBranchPrefix } from "../branch/prefix.js";
26
+ import { listTicketGroups, previewTicketGroupRepair, repairTicketGroups } from "../tickets/groups.js";
25
27
  let nestedTicketCommandDepth = 0;
26
28
  class TicketInterviewCancelled extends Error {
27
29
  }
@@ -321,6 +323,8 @@ async function collectTicketSetupPatch(dir, opts, current) {
321
323
  populatePatch.agent_preference = parseAgentPreference(opts.agentPreference);
322
324
  if (opts.branchStrategy)
323
325
  buildPatch.branch_strategy = parseBranchStrategy(opts.branchStrategy);
326
+ if (opts.branchPrefix)
327
+ buildPatch.branch_prefix = validateBranchPrefix(opts.branchPrefix);
324
328
  if (opts.completion)
325
329
  buildPatch.completion = parseCompletionMode(opts.completion);
326
330
  if (opts.provider)
@@ -420,6 +424,18 @@ async function collectTicketSetupPatch(dir, opts, current) {
420
424
  if (isCancel(base))
421
425
  cancelTicketInterview();
422
426
  buildPatch.base_branch = String(base).trim();
427
+ const prefix = await text({ message: "Branch prefix:", initialValue: current.build.branch_prefix, defaultValue: current.build.branch_prefix, validate: (value) => {
428
+ try {
429
+ validateBranchPrefix(String(value));
430
+ return undefined;
431
+ }
432
+ catch (error) {
433
+ return error instanceof Error ? error.message : String(error);
434
+ }
435
+ } });
436
+ if (isCancel(prefix))
437
+ cancelTicketInterview();
438
+ buildPatch.branch_prefix = validateBranchPrefix(String(prefix));
423
439
  const policyMode = await select({ message: "Use one global branch strategy or choose by inferred ticket size?", initialValue: current.build.branch_policy.mode, options: [
424
440
  { value: "size", label: "Size-based: XS/S shared, M/L/XL per ticket (Recommended)" },
425
441
  { value: "global", label: "One global strategy" },
@@ -772,6 +788,22 @@ function parseResetScope(value) {
772
788
  return value;
773
789
  throw new Error("--scope must be all, completed-and-unfinished, or unfinished");
774
790
  }
791
+ function parsePositiveInteger(value, label) {
792
+ const parsed = Number.parseInt(value, 10);
793
+ if (Number.isInteger(parsed) && parsed > 0 && String(parsed) === value.trim())
794
+ return parsed;
795
+ throw new Error(`${label} must be a positive integer`);
796
+ }
797
+ function parseTicketGroupId(value) {
798
+ if (!/^TG-[1-9]\d*$/.test(value))
799
+ throw new Error("--group must use a stable ID such as TG-1");
800
+ return value;
801
+ }
802
+ function parseDeletedTicketPolicy(value) {
803
+ if (value === "ignore" || value === "restore")
804
+ return value;
805
+ throw new Error("--deleted-tickets must be ignore or restore");
806
+ }
775
807
  function parseOptionalPositiveInteger(value, label) {
776
808
  if (!value.trim())
777
809
  return null;
@@ -1091,11 +1123,100 @@ function ticketOwnershipReceipts(projectDir, commandName, _afterWrite) {
1091
1123
  }
1092
1124
  return unique(candidates).filter((path) => existsSync(join(projectDir, path))).map((path) => ({ path, category: "tickets", origin: `tickets:${commandName}` }));
1093
1125
  }
1126
+ async function runGroupResetCli(projectDir, opts) {
1127
+ const requested = opts.allGroups
1128
+ ? { kind: "all-groups" }
1129
+ : opts.recentGroups !== undefined
1130
+ ? { kind: "recent-groups", count: parsePositiveInteger(String(opts.recentGroups), "--recent-groups") }
1131
+ : opts.group !== undefined
1132
+ ? { kind: "group", groupId: parseTicketGroupId(String(opts.group)) }
1133
+ : { kind: "group-index", position: parsePositiveInteger(String(opts.groupIndex), "--group-index") };
1134
+ const explicitPolicy = opts.deletedTickets === undefined ? undefined : parseDeletedTicketPolicy(String(opts.deletedTickets));
1135
+ const initial = resolveTicketResetSelection(projectDir, requested, { deletedTickets: "ignore" });
1136
+ const missing = initial.previewRows.filter((row) => row.definitionMissing);
1137
+ if (missing.length && !explicitPolicy && (!process.stdin.isTTY || !process.stdout.isTTY)) {
1138
+ throw new Error("group reset includes deleted ticket definitions; automation must pass --deleted-tickets ignore|restore");
1139
+ }
1140
+ let policy = explicitPolicy;
1141
+ const perTicket = {};
1142
+ const restoreDependencies = [];
1143
+ if (missing.length && !policy) {
1144
+ const answer = await select({ message: `${missing.length} group member definition(s) are missing. What should reset do?`, options: [
1145
+ { value: "ignore", label: "Ignore all missing definitions (Recommended)" },
1146
+ { value: "restore", label: "Restore all from Rafi snapshots" },
1147
+ { value: "each", label: "Decide each one" },
1148
+ ] });
1149
+ if (isCancel(answer)) {
1150
+ console.log("foreman tickets: reset cancelled; nothing changed");
1151
+ return;
1152
+ }
1153
+ if (answer === "each") {
1154
+ for (const row of missing) {
1155
+ const decision = await select({ message: `${row.ticketId}: ${row.title}`, options: [
1156
+ { value: "ignore", label: "Ignore missing definition (Recommended)" }, { value: "restore", label: "Restore definition" },
1157
+ ] });
1158
+ if (isCancel(decision)) {
1159
+ console.log("foreman tickets: reset cancelled; nothing changed");
1160
+ return;
1161
+ }
1162
+ perTicket[row.ticketId] = decision;
1163
+ }
1164
+ policy = "ignore";
1165
+ }
1166
+ else
1167
+ policy = answer;
1168
+ }
1169
+ let selection;
1170
+ while (true) {
1171
+ try {
1172
+ selection = resolveTicketResetSelection(projectDir, requested, { deletedTickets: policy ?? "ignore", perTicket, restoreDependencies });
1173
+ break;
1174
+ }
1175
+ catch (error) {
1176
+ if (!(error instanceof TicketResetDependencyConflictError) || !process.stdin.isTTY || !process.stdout.isTTY)
1177
+ throw error;
1178
+ for (const conflict of error.conflicts) {
1179
+ const decision = await select({ message: `${conflict.ticketId} depends on deleted definition ${conflict.dependencyId}.`, options: [
1180
+ { value: "dependency", label: `Restore ${conflict.dependencyId} without resetting it (Recommended)` },
1181
+ { value: "skip", label: `Skip restoring ${conflict.ticketId}` },
1182
+ { value: "cancel", label: "Cancel reset" },
1183
+ ] });
1184
+ if (isCancel(decision) || decision === "cancel") {
1185
+ console.log("foreman tickets: reset cancelled; nothing changed");
1186
+ return;
1187
+ }
1188
+ if (decision === "dependency")
1189
+ restoreDependencies.push(conflict.dependencyId);
1190
+ else
1191
+ perTicket[conflict.ticketId] = "ignore";
1192
+ }
1193
+ }
1194
+ }
1195
+ console.log(`foreman tickets reset preview (${selection.groups.map((group) => group.id).join(", ") || requested.kind}):`);
1196
+ for (const row of selection.previewRows)
1197
+ console.log(` ${row.ticketId}: ${row.title} [${row.status}]${row.definitionMissing ? row.restoreDefinition ? " — restore definition and reset" : " — missing definition, skip" : " — reset active state"}`);
1198
+ for (const dependency of selection.definitionRestorations.filter((item) => item.dependencyOnly))
1199
+ console.log(` dependency ${dependency.ticketId}: restore definition only (do not reset)`);
1200
+ if (!selection.previewRows.length)
1201
+ return;
1202
+ if (!opts.yes) {
1203
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
1204
+ throw new Error("confirmation requires a TTY or --yes");
1205
+ const approved = await confirm({ message: `Apply this frozen ${selection.previewRows.length}-member reset selection?`, initialValue: false });
1206
+ if (isCancel(approved) || !approved) {
1207
+ console.log("foreman tickets: reset cancelled; nothing changed");
1208
+ return;
1209
+ }
1210
+ }
1211
+ const result = applyResolvedTicketReset(projectDir, selection, String(opts.actor ?? "user"));
1212
+ console.log(`foreman tickets: reset ${result.tickets.length} ticket(s), restored ${result.restored.length}, ignored ${result.ignoredMissing.length} missing (audit ${result.resetId})`);
1213
+ }
1094
1214
  export function buildTicketsCommand(options = {}) {
1095
1215
  const tickets = new Command("tickets").description("Manage the structured ticket tracker for a project.");
1096
1216
  tickets.hook("preAction", (_thisCommand, actionCommand) => {
1097
1217
  const commandOptions = actionCommand.opts();
1098
1218
  const projectDir = cwd(commandOptions);
1219
+ recoverTicketPublications(projectDir);
1099
1220
  recoverTicketResetPublications(projectDir);
1100
1221
  if (options.prepareOwnership && !["show", "validate", "queue"].includes(actionCommand.name())) {
1101
1222
  options.prepareOwnership(projectDir, ticketOwnershipReceipts(projectDir, actionCommand.name(), false));
@@ -1127,6 +1248,7 @@ export function buildTicketsCommand(options = {}) {
1127
1248
  .option("--url-source <urls...>", "add public HTTP(S) source URLs")
1128
1249
  .option("--agent-preference <agent>", "populate runtime preference (configured | claude | codex)")
1129
1250
  .option("--branch-strategy <strategy>", "build branch strategy default (current | batch | branch-per-ticket)")
1251
+ .option("--branch-prefix <prefix>", "prefix for Rafi-generated branches (for example team/feature)")
1130
1252
  .option("--completion <mode>", "build completion default (pr | auto-merge | direct-merge | none)")
1131
1253
  .option("--provider <provider>", "PR/MR provider default (auto | github | gitlab | local)")
1132
1254
  .option("--auto-merge-wait", "wait for dependency PR/MRs to merge before starting dependent tickets")
@@ -1162,6 +1284,7 @@ export function buildTicketsCommand(options = {}) {
1162
1284
  .option("--url-source <urls...>", "replace saved sources with public HTTP(S) URLs")
1163
1285
  .option("--agent-preference <agent>", "populate runtime preference (configured | claude | codex)")
1164
1286
  .option("--branch-strategy <strategy>", "build branch strategy default (current | batch | branch-per-ticket)")
1287
+ .option("--branch-prefix <prefix>", "prefix for Rafi-generated branches (for example team/feature)")
1165
1288
  .option("--completion <mode>", "build completion default (pr | auto-merge | direct-merge | none)")
1166
1289
  .option("--provider <provider>", "PR/MR provider default (auto | github | gitlab | local)")
1167
1290
  .option("--auto-merge-wait", "wait for dependency PR/MRs to merge before starting dependent tickets")
@@ -1269,20 +1392,93 @@ export function buildTicketsCommand(options = {}) {
1269
1392
  fail(String(err instanceof Error ? err.message : err));
1270
1393
  }
1271
1394
  });
1395
+ const groups = tickets.command("groups").description("List and repair durable ticket creation groups.");
1396
+ groups
1397
+ .command("list")
1398
+ .description("List immutable ticket creation groups newest first.")
1399
+ .option("-p, --project <dir>", "project directory (default: cwd)")
1400
+ .option("--json", "write the versioned stable JSON envelope")
1401
+ .action((opts) => {
1402
+ try {
1403
+ const envelope = listTicketGroups(cwd(opts));
1404
+ if (opts.json) {
1405
+ process.stdout.write(`${JSON.stringify(envelope, null, 2)}\n`);
1406
+ return;
1407
+ }
1408
+ if (!envelope.groups.length) {
1409
+ console.log("foreman tickets groups: no creation groups");
1410
+ return;
1411
+ }
1412
+ for (const group of envelope.groups) {
1413
+ const totals = Object.entries(group.statusTotals).map(([status, count]) => `${status}=${count}`).join(", ");
1414
+ console.log(`${group.id} index=${group.recencyPosition} origin=${group.origin} created=${group.createdAt}${group.legacy ? " legacy" : ""} ${totals}`);
1415
+ for (const member of group.members)
1416
+ console.log(` ${member.position}. ${member.ticketId} [${member.status}]${member.definitionMissing ? " — definition missing (recoverable)" : ""}`);
1417
+ for (const run of group.recoverableRuns)
1418
+ console.log(` recoverable run ${run.runId} [${run.status}] ${run.tickets.join(", ")}`);
1419
+ }
1420
+ }
1421
+ catch (error) {
1422
+ fail(String(error instanceof Error ? error.message : error));
1423
+ }
1424
+ });
1425
+ groups
1426
+ .command("repair")
1427
+ .description("Preview and group tickets created outside Rafi after the legacy migration.")
1428
+ .option("-p, --project <dir>", "project directory (default: cwd)")
1429
+ .option("-y, --yes", "approve the exact preview")
1430
+ .action(async (opts) => {
1431
+ try {
1432
+ const dir = cwd(opts);
1433
+ const preview = previewTicketGroupRepair(dir);
1434
+ console.log("foreman tickets groups repair preview:");
1435
+ console.log(preview.ticketIds.length ? ` new repair group: ${preview.ticketIds.join(", ")}` : " no ungrouped tickets");
1436
+ if (!preview.ticketIds.length)
1437
+ return;
1438
+ if (!opts.yes) {
1439
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
1440
+ throw new Error("repair approval requires a TTY or --yes");
1441
+ const approved = await confirm({ message: `Create one repair group for exactly ${preview.ticketIds.length} ticket(s)?`, initialValue: false });
1442
+ if (isCancel(approved) || !approved) {
1443
+ console.log("foreman tickets groups: repair cancelled; nothing changed");
1444
+ return;
1445
+ }
1446
+ }
1447
+ const group = repairTicketGroups(dir, preview.inputFingerprint);
1448
+ console.log(group ? `foreman tickets groups: created ${group.id} for ${group.members.map((member) => member.ticketId).join(", ")}` : "foreman tickets groups: nothing to repair");
1449
+ }
1450
+ catch (error) {
1451
+ fail(String(error instanceof Error ? error.message : error));
1452
+ }
1453
+ });
1272
1454
  tickets
1273
1455
  .command("reset [ticketId]")
1274
1456
  .description("Reset one ticket or an explicit ticket scope to pristine active state while retaining history.")
1275
1457
  .option("-p, --project <dir>", "project directory (default: cwd)")
1276
1458
  .option("--all", "open the interactive bulk reset scope chooser")
1277
1459
  .option("--scope <scope>", "bulk scope (all | completed-and-unfinished | unfinished)")
1460
+ .option("--all-groups", "reset the exact membership of every durable creation group")
1461
+ .option("--recent-groups <count>", "reset the exact membership of the newest N creation groups")
1462
+ .option("--group <TG-N>", "reset one durable creation group by stable ID")
1463
+ .option("--group-index <position>", "reset one creation group by current newest-first position")
1464
+ .option("--deleted-tickets <policy>", "missing-definition policy for automation (ignore | restore)")
1278
1465
  .option("--actor <actor>", "who requested the reset", "user")
1279
1466
  .option("-y, --yes", "confirm an explicit ticket or --scope without prompting")
1280
1467
  .action(async (ticketId, opts) => {
1281
1468
  try {
1469
+ const groupTargetCount = [opts.allGroups, opts.recentGroups, opts.group, opts.groupIndex].filter((value) => value !== undefined && value !== false).length;
1470
+ if (groupTargetCount > 0) {
1471
+ if (groupTargetCount !== 1)
1472
+ throw new Error("choose exactly one group reset target");
1473
+ if (ticketId || opts.all || opts.scope)
1474
+ throw new Error("group reset targets cannot be combined with a ticket or scope target");
1475
+ await runGroupResetCli(cwd(opts), opts);
1476
+ return;
1477
+ }
1282
1478
  if (ticketId && (opts.all || opts.scope))
1283
1479
  throw new Error("provide a ticket ID or a bulk scope, not both");
1284
1480
  if (!ticketId && !opts.all && !opts.scope)
1285
- throw new Error("provide <ticketId>, --all, or --scope <scope>");
1481
+ throw new Error("provide <ticketId>, --all, --scope <scope>, or one group target");
1286
1482
  let target = ticketId;
1287
1483
  if (opts.scope)
1288
1484
  target = parseResetScope(String(opts.scope));
@@ -0,0 +1,92 @@
1
+ import type { ContinuityDelta, ResolvedAgentSettings } from "rafi-spec";
2
+ import type { BuilderAdapter, CompactResult, ContextManagementPolicy, ContextUsage, InterruptResult, ManagedTurnDispatcher, PreparedContextManagement, ProviderSessionUsage, ProviderSettingSwitch, RuntimeFailure, TurnResult } from "./adapters/types.js";
3
+ export declare const CONTINUITY_MARKER = "RAFI_CONTINUITY_DELTA:";
4
+ export declare class ContinuityValidationError extends Error {
5
+ readonly problems: string[];
6
+ constructor(problems: string[]);
7
+ }
8
+ export declare class ContinuityRecoveryRequiredError extends Error {
9
+ readonly runId: string;
10
+ readonly role: "builder" | "qa";
11
+ constructor(runId: string, role: "builder" | "qa", message: string);
12
+ }
13
+ /**
14
+ * Exact-session loss is a host recovery boundary, never a continuity-repair
15
+ * prompt. `guidedRecovery` is true when the provider may already have received
16
+ * the frozen instruction, so a fresh successor must reconcile receipts first.
17
+ */
18
+ export declare class SessionUnavailableContinuityError extends Error {
19
+ readonly runId: string;
20
+ readonly role: "builder" | "qa";
21
+ readonly failure: RuntimeFailure;
22
+ readonly guidedRecovery: boolean;
23
+ constructor(runId: string, role: "builder" | "qa", failure: RuntimeFailure);
24
+ }
25
+ export declare function validateContinuityDelta(value: unknown): ContinuityDelta;
26
+ export declare function parseContinuityDelta(text: string): {
27
+ delta?: ContinuityDelta;
28
+ cleanText: string;
29
+ error?: ContinuityValidationError;
30
+ };
31
+ export declare function mergeContinuityDeltas(deltas: readonly ContinuityDelta[]): ContinuityDelta;
32
+ export declare function baselineContinuityDelta(nextAction?: string): ContinuityDelta;
33
+ export declare function continuityInstruction(): string;
34
+ export interface ContinuityAdapterOptions {
35
+ adapter: BuilderAdapter;
36
+ projectDir: string;
37
+ runId: string;
38
+ role: "builder" | "qa";
39
+ settings: ResolvedAgentSettings;
40
+ authoritativeStateRevision?: () => number;
41
+ /** @deprecated Use recoverWithHandoff so successor acceptance is lifecycle-managed and durable. */
42
+ createSuccessor?: (handoff: string) => Promise<BuilderAdapter>;
43
+ /**
44
+ * Production recovery path for a double-invalid delta. The callback must
45
+ * publish a versioned handoff, validate a genuinely fresh successor, and
46
+ * move the role lease before returning it.
47
+ */
48
+ recoverWithHandoff?: (input: {
49
+ reason: string;
50
+ reconstruction: string;
51
+ predecessor: BuilderAdapter;
52
+ }) => Promise<BuilderAdapter>;
53
+ /** Explicit compatibility recovery may replace a dead predecessor lease only after its first valid checkpoint. */
54
+ replaceRecoveryLeaseAfterCheckpoint?: boolean;
55
+ /** Host-owned validated Builder handoff request processing at a safe turn boundary. */
56
+ handleHandoffRequest?: (text: string, predecessor: BuilderAdapter, frozenAction: string) => Promise<BuilderAdapter | undefined>;
57
+ }
58
+ /** Enforces a durable role checkpoint after every completed provider turn. */
59
+ export declare class ContinuityAdapter implements BuilderAdapter {
60
+ private readonly options;
61
+ private adapter;
62
+ private readonly queue;
63
+ private sourcePump?;
64
+ private recoveryLeasePending;
65
+ private turnDispatcher?;
66
+ constructor(options: ContinuityAdapterOptions);
67
+ get agent(): "claude" | "codex";
68
+ sendTurn(instruction: string): Promise<TurnResult>;
69
+ sessionId(): string | undefined;
70
+ sessionRef(): import("rafi-spec").ProviderSessionRefV1 | undefined;
71
+ adoptSessionRef(ref: import("rafi-spec").ProviderSessionRefV1): void;
72
+ validateSession(): Promise<import("rafi-spec").SessionAvailabilityV1>;
73
+ compact(): Promise<CompactResult>;
74
+ prepareContextManagement(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
75
+ updateContextManagement(policy: ContextManagementPolicy): Promise<PreparedContextManagement>;
76
+ interruptTurnAtCompactionBoundary(providerEventId?: string): Promise<InterruptResult>;
77
+ installManagedTurnDispatcher(dispatcher: ManagedTurnDispatcher): void;
78
+ contextUsage(): Promise<ContextUsage | undefined>;
79
+ sessionUsage(): Promise<ProviderSessionUsage | undefined>;
80
+ switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
81
+ events(): AsyncIterable<import("./adapters/types.js").BuilderEvent>;
82
+ close(): Promise<void>;
83
+ /** Keep the observable wrapper and event stream stable while moving to an accepted successor. */
84
+ adoptValidatedSuccessor(successor: BuilderAdapter): Promise<void>;
85
+ private providerTurn;
86
+ private publish;
87
+ private moveRecoveryLeaseAfterCheckpoint;
88
+ private recordSessionUnavailable;
89
+ private reconstructionHandoff;
90
+ private revision;
91
+ private pumpEvents;
92
+ }