ai-foreman 1.7.8 → 1.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -11
- package/dist/activity.js +2 -2
- package/dist/adapters/claude.d.ts +13 -29
- package/dist/adapters/claude.js +76 -334
- package/dist/adapters/codex.d.ts +13 -18
- package/dist/adapters/codex.js +103 -195
- package/dist/adapters/recovering.d.ts +5 -6
- package/dist/adapters/recovering.js +5 -9
- package/dist/adapters/types.d.ts +24 -66
- package/dist/branch/currentGuard.d.ts +5 -5
- package/dist/branch/currentGuard.js +4 -6
- package/dist/branch/runner.d.ts +2 -0
- package/dist/branch/runner.js +2 -1
- package/dist/cli/start.js +90 -165
- package/dist/continuity.d.ts +6 -8
- package/dist/continuity.js +26 -72
- package/dist/foreman.d.ts +7 -1
- package/dist/foreman.js +25 -1
- package/dist/handoffs.js +3 -223
- package/dist/qaReview.d.ts +2 -0
- package/dist/qaReview.js +1 -4
- package/dist/sessionLifecycle.d.ts +19 -108
- package/dist/sessionLifecycle.js +310 -1024
- package/dist/statusReporter.d.ts +5 -27
- package/dist/statusReporter.js +20 -76
- package/dist/workflowDb.d.ts +10 -47
- package/dist/workflowDb.js +41 -146
- package/package.json +4 -4
package/dist/cli/start.js
CHANGED
|
@@ -37,7 +37,7 @@ import { eligibleTickets, evaluateTicketEligibility } from "../tickets/eligibili
|
|
|
37
37
|
import { BUILTIN_BRANCH_PREFIX, validateBranchPrefix } from "../branch/prefix.js";
|
|
38
38
|
import { captureCurrentWorkflowSessionIdentity, CurrentWorkflowChangedError, CurrentWorkflowGuardAdapter } from "../branch/currentGuard.js";
|
|
39
39
|
import { ContinuityAdapter, ContinuityRecoveryRequiredError, SessionUnavailableContinuityError } from "../continuity.js";
|
|
40
|
-
import { ContextCapabilityError, RoleSessionController } from "../sessionLifecycle.js";
|
|
40
|
+
import { ContextCapabilityError, RoleSessionController, ThresholdCompactionController } from "../sessionLifecycle.js";
|
|
41
41
|
import { HandoffLoopError, HandoffService, parseBuilderHandoffRequest } from "../handoffs.js";
|
|
42
42
|
import { captureWorkspaceIdentity, createProviderSessionRef, resolveUniqueSessionBinding } from "../sessionIdentity.js";
|
|
43
43
|
import { SessionUnavailableError } from "../adapters/sessionFailure.js";
|
|
@@ -625,13 +625,7 @@ export function buildStartCommand() {
|
|
|
625
625
|
fast: builderFast,
|
|
626
626
|
systemPromptAppend: roleBundle.system || undefined,
|
|
627
627
|
skills: roleBundle.skills.length > 0 ? roleBundle.skills : undefined,
|
|
628
|
-
|
|
629
|
-
role: "builder",
|
|
630
|
-
configuredThresholdPercent: thresholdOverride ?? roleDefaults.builder?.auto_compact_threshold_percent ?? 50,
|
|
631
|
-
compactMaximum: roleDefaults.builder?.compact_maximum ?? 10,
|
|
632
|
-
settingsRevision,
|
|
633
|
-
model: model ?? "default",
|
|
634
|
-
},
|
|
628
|
+
autoCompactThresholdPercent: thresholdOverride ?? roleDefaults.builder?.auto_compact_threshold_percent ?? 50,
|
|
635
629
|
};
|
|
636
630
|
const adapter = agent === "codex"
|
|
637
631
|
? new CodexAdapter(adapterOpts)
|
|
@@ -643,12 +637,12 @@ export function buildStartCommand() {
|
|
|
643
637
|
throw new SessionUnavailableError({ runtime: "codex", phase: "preflight", dispatchState: "not-sent", executable: agentExecutable ?? "codex", cwd: builderCwd, diagnostics: availability.detail ?? `Codex session ${sessionRef.sessionId} is ${availability.status}`, availability });
|
|
644
638
|
}
|
|
645
639
|
}
|
|
640
|
+
await prepareNativeAutoCompaction(adapter);
|
|
646
641
|
return !branchMode ? new CurrentWorkflowGuardAdapter(adapter, builderCwd) : adapter;
|
|
647
642
|
};
|
|
648
643
|
let activeContinuityRunId;
|
|
649
644
|
let liveStatusReporter;
|
|
650
645
|
let activeBuilderForStatus;
|
|
651
|
-
let activeBuilderContextSnapshot;
|
|
652
646
|
const resolvedContinuitySettings = (role) => role === "builder" ? {
|
|
653
647
|
role, source: roleDefaults.builder ? "project" : "provider", make: agent, model: model ?? "default",
|
|
654
648
|
reasoning: builderEffort ?? "default", fast: Boolean(builderFast), session_strategy: roleDefaults.builder?.session_strategy ?? "compact",
|
|
@@ -662,67 +656,25 @@ export function buildStartCommand() {
|
|
|
662
656
|
auto_compact_threshold_percent: roleDefaults.qa?.auto_compact_threshold_percent ?? 50,
|
|
663
657
|
compact_maximum: roleDefaults.qa?.compact_maximum ?? 10,
|
|
664
658
|
};
|
|
665
|
-
const liveRoleFields = [
|
|
666
|
-
"make", "model", "reasoning", "fast", "session_strategy", "display_session_cost",
|
|
667
|
-
"auto_compact_threshold_percent", "compact_maximum",
|
|
668
|
-
];
|
|
669
|
-
const observedRoleDefaults = {
|
|
670
|
-
builder: { ...(roleDefaults.builder ?? {}) },
|
|
671
|
-
qa: { ...(roleDefaults.qa ?? {}) },
|
|
672
|
-
};
|
|
673
|
-
const observedProjectRevision = { builder: settingsRevision, qa: settingsRevision };
|
|
674
|
-
const effectiveRoleRevision = { builder: settingsRevision, qa: settingsRevision };
|
|
675
|
-
const liveRoleOverrides = {
|
|
676
|
-
builder: new Map(),
|
|
677
|
-
qa: new Map(),
|
|
678
|
-
};
|
|
679
659
|
const liveRoleSettings = (role, base) => {
|
|
680
660
|
const live = readAgentDefaults(cwd);
|
|
681
661
|
const revision = live.revision ?? 0;
|
|
682
|
-
|
|
683
|
-
if (revision > observedProjectRevision[role]) {
|
|
684
|
-
const prior = observedRoleDefaults[role];
|
|
685
|
-
let roleChanged = false;
|
|
686
|
-
for (const field of liveRoleFields) {
|
|
687
|
-
const value = defaults?.[field];
|
|
688
|
-
if (!Object.is(value, prior[field])) {
|
|
689
|
-
liveRoleOverrides[role].set(field, value);
|
|
690
|
-
roleChanged = true;
|
|
691
|
-
}
|
|
692
|
-
}
|
|
693
|
-
const revisionDb = new WorkflowDb(cwd);
|
|
694
|
-
let targetedFields;
|
|
695
|
-
try {
|
|
696
|
-
targetedFields = revisionDb.projectSettingsRevisionTargets(revision)[role] ?? [];
|
|
697
|
-
}
|
|
698
|
-
finally {
|
|
699
|
-
revisionDb.close();
|
|
700
|
-
}
|
|
701
|
-
for (const field of targetedFields) {
|
|
702
|
-
if (liveRoleFields.includes(field)) {
|
|
703
|
-
const typedField = field;
|
|
704
|
-
liveRoleOverrides[role].set(typedField, defaults?.[typedField]);
|
|
705
|
-
}
|
|
706
|
-
}
|
|
707
|
-
observedRoleDefaults[role] = { ...(defaults ?? {}) };
|
|
708
|
-
observedProjectRevision[role] = revision;
|
|
709
|
-
if (roleChanged || targetedFields.length)
|
|
710
|
-
effectiveRoleRevision[role] = revision;
|
|
711
|
-
}
|
|
712
|
-
if (effectiveRoleRevision[role] <= base.settings_revision)
|
|
662
|
+
if (revision <= base.settings_revision)
|
|
713
663
|
return base;
|
|
714
|
-
const
|
|
664
|
+
const defaults = role === "builder" ? live.builder : live.qa;
|
|
665
|
+
return {
|
|
715
666
|
...base,
|
|
716
667
|
source: defaults ? "project" : base.source,
|
|
717
|
-
|
|
668
|
+
make: defaults?.make ?? base.make,
|
|
669
|
+
model: defaults?.model ?? base.model,
|
|
670
|
+
reasoning: defaults?.reasoning ?? base.reasoning,
|
|
671
|
+
fast: defaults?.fast ?? base.fast,
|
|
672
|
+
session_strategy: defaults?.session_strategy ?? base.session_strategy,
|
|
673
|
+
display_session_cost: defaults?.display_session_cost ?? base.display_session_cost,
|
|
674
|
+
auto_compact_threshold_percent: defaults?.auto_compact_threshold_percent ?? base.auto_compact_threshold_percent,
|
|
675
|
+
compact_maximum: defaults?.compact_maximum ?? base.compact_maximum,
|
|
676
|
+
settings_revision: revision,
|
|
718
677
|
};
|
|
719
|
-
const mutable = merged;
|
|
720
|
-
for (const [field, value] of liveRoleOverrides[role]) {
|
|
721
|
-
if (value === undefined)
|
|
722
|
-
continue;
|
|
723
|
-
mutable[field] = value;
|
|
724
|
-
}
|
|
725
|
-
return merged;
|
|
726
678
|
};
|
|
727
679
|
const createBuilderForSettings = async (builderCwd, settings) => {
|
|
728
680
|
const ready = await ensureRuntimeReadyForCommand(builderCwd, settings.make, {
|
|
@@ -747,12 +699,10 @@ export function buildStartCommand() {
|
|
|
747
699
|
fast: settings.fast,
|
|
748
700
|
systemPromptAppend: roleBundle.system || undefined,
|
|
749
701
|
skills: roleBundle.skills.length > 0 ? roleBundle.skills : undefined,
|
|
750
|
-
|
|
751
|
-
role: "builder", configuredThresholdPercent: settings.auto_compact_threshold_percent,
|
|
752
|
-
compactMaximum: settings.compact_maximum, settingsRevision: settings.settings_revision, model: settings.model,
|
|
753
|
-
},
|
|
702
|
+
autoCompactThresholdPercent: settings.auto_compact_threshold_percent,
|
|
754
703
|
};
|
|
755
704
|
const created = settings.make === "codex" ? new CodexAdapter(adapterOptions) : await ClaudeAdapter.create(adapterOptions);
|
|
705
|
+
await prepareNativeAutoCompaction(created);
|
|
756
706
|
return !branchMode ? new CurrentWorkflowGuardAdapter(created, builderCwd) : created;
|
|
757
707
|
};
|
|
758
708
|
const createQaForSettings = async (qaCwd, settings) => {
|
|
@@ -777,12 +727,11 @@ export function buildStartCommand() {
|
|
|
777
727
|
fast: settings.fast,
|
|
778
728
|
systemPromptAppend: `${roleBundle.system}\n\nYou are an independent QA reviewer. Do not edit source, tickets, configuration, or project documentation. You may run tests and create only harmless ignored caches or coverage output.`,
|
|
779
729
|
skills: roleBundle.skills.length > 0 ? roleBundle.skills : undefined,
|
|
780
|
-
|
|
781
|
-
role: "qa", configuredThresholdPercent: settings.auto_compact_threshold_percent,
|
|
782
|
-
compactMaximum: settings.compact_maximum, settingsRevision: settings.settings_revision, model: settings.model,
|
|
783
|
-
},
|
|
730
|
+
autoCompactThresholdPercent: settings.auto_compact_threshold_percent,
|
|
784
731
|
};
|
|
785
|
-
|
|
732
|
+
const created = settings.make === "codex" ? new CodexAdapter(adapterOptions) : await ClaudeAdapter.create(adapterOptions);
|
|
733
|
+
await prepareNativeAutoCompaction(created);
|
|
734
|
+
return created;
|
|
786
735
|
};
|
|
787
736
|
const createRecoveringBuilder = async (builderCwd, sessionId, sessionRef) => {
|
|
788
737
|
if (sessionId && !sessionRef) {
|
|
@@ -969,6 +918,16 @@ export function buildStartCommand() {
|
|
|
969
918
|
return continuousBuilder(transfer.successor, builderCwd, runId, input.next);
|
|
970
919
|
};
|
|
971
920
|
const createRawQa = async (qaCwd, sessionRef) => {
|
|
921
|
+
// Isolated QA conversations are intentionally disposable. Resolve live
|
|
922
|
+
// QA settings for each one so a threshold-only update applies to the
|
|
923
|
+
// very next review instead of waiting for a nonexistent session reuse.
|
|
924
|
+
const qaSettings = liveRoleSettings("qa", resolvedContinuitySettings("qa"));
|
|
925
|
+
const ready = await ensureRuntimeReadyForCommand(qaCwd, qaSettings.make, {
|
|
926
|
+
label: "live QA settings",
|
|
927
|
+
yes: true,
|
|
928
|
+
allowSwitch: false,
|
|
929
|
+
model: qaSettings.model === "default" ? undefined : qaSettings.model,
|
|
930
|
+
});
|
|
972
931
|
const qaPolicy = new PermissionPolicy(config.permissions, qaCwd);
|
|
973
932
|
const roleBundle = loadRoleBundle("qa", { projectDir: qaCwd });
|
|
974
933
|
const adapterOpts = {
|
|
@@ -976,28 +935,24 @@ export function buildStartCommand() {
|
|
|
976
935
|
configRoot: cwd,
|
|
977
936
|
sessionRole: "qa",
|
|
978
937
|
sessionStream: "qa",
|
|
979
|
-
runtimeExecutable:
|
|
938
|
+
runtimeExecutable: ready.executable,
|
|
980
939
|
runtimePhase: "qa",
|
|
981
|
-
model:
|
|
940
|
+
model: qaSettings.model === "default" ? undefined : qaSettings.model,
|
|
982
941
|
...(sessionRef ? { resumeSessionRef: sessionRef } : {}),
|
|
983
942
|
sessionGeneration: sessionRef?.generation ?? 0,
|
|
984
943
|
workspaceIdentity: captureWorkspaceIdentity(qaCwd),
|
|
985
944
|
permission: createPermissionHandler(qaPolicy, log),
|
|
986
|
-
effort:
|
|
987
|
-
fast:
|
|
945
|
+
effort: explicitEffort(qaSettings.reasoning),
|
|
946
|
+
fast: qaSettings.fast,
|
|
988
947
|
systemPromptAppend: `${roleBundle.system}\n\nYou are an independent QA reviewer. Do not edit source, tickets, configuration, or project documentation. You may run tests and create only harmless ignored caches or coverage output.`,
|
|
989
948
|
skills: roleBundle.skills.length > 0 ? roleBundle.skills : undefined,
|
|
990
|
-
|
|
991
|
-
role: "qa",
|
|
992
|
-
configuredThresholdPercent: roleDefaults.qa?.auto_compact_threshold_percent ?? 50,
|
|
993
|
-
compactMaximum: roleDefaults.qa?.compact_maximum ?? 10,
|
|
994
|
-
settingsRevision,
|
|
995
|
-
model: qaModel ?? "default",
|
|
996
|
-
},
|
|
949
|
+
autoCompactThresholdPercent: qaSettings.auto_compact_threshold_percent,
|
|
997
950
|
};
|
|
998
|
-
|
|
951
|
+
const created = qaSettings.make === "codex"
|
|
999
952
|
? new CodexAdapter(adapterOpts)
|
|
1000
953
|
: await ClaudeAdapter.create(adapterOpts);
|
|
954
|
+
await prepareNativeAutoCompaction(created);
|
|
955
|
+
return created;
|
|
1001
956
|
};
|
|
1002
957
|
const createRecoveringQa = async (qaCwd, sessionRef) => {
|
|
1003
958
|
const initial = await createRawQa(qaCwd, sessionRef);
|
|
@@ -1114,7 +1069,6 @@ export function buildStartCommand() {
|
|
|
1114
1069
|
finally {
|
|
1115
1070
|
db.close();
|
|
1116
1071
|
} },
|
|
1117
|
-
contextSnapshot: activeBuilderContextSnapshot,
|
|
1118
1072
|
});
|
|
1119
1073
|
});
|
|
1120
1074
|
};
|
|
@@ -1218,9 +1172,8 @@ export function buildStartCommand() {
|
|
|
1218
1172
|
projectDir: cwd,
|
|
1219
1173
|
runId: activeContinuityRunId,
|
|
1220
1174
|
role: "qa",
|
|
1221
|
-
requireNativeContextManagement: true,
|
|
1222
1175
|
initialSettings: qaSettings,
|
|
1223
|
-
historicalCountUncertain:
|
|
1176
|
+
historicalCountUncertain: recoveryCompactionHistoryUncertain(cwd, recoveryRecord, activeContinuityRunId, "qa"),
|
|
1224
1177
|
readSettings: () => liveRoleSettings("qa", qaSettings),
|
|
1225
1178
|
settingsBoundary: (input) => applyQaSettingsBoundary(activeContinuityRunId, qaCwd, input),
|
|
1226
1179
|
settingsAdopted: (settings, active) => liveStatusReporter?.updateState({
|
|
@@ -1231,7 +1184,6 @@ export function buildStartCommand() {
|
|
|
1231
1184
|
report: (event) => {
|
|
1232
1185
|
currentActivity()?.update(event.kind, event.detail, { provider: qaSettings.make, model: qaSettings.model });
|
|
1233
1186
|
log.write("context-lifecycle", { role: "qa", ...event, sample: event.sample });
|
|
1234
|
-
void liveStatusReporter?.tick();
|
|
1235
1187
|
},
|
|
1236
1188
|
handoff: async ({ reason, adapter: predecessor, sample, settings, compactionCount }) => {
|
|
1237
1189
|
const nativeUsage = await predecessor.sessionUsage?.().catch(() => undefined);
|
|
@@ -1252,9 +1204,21 @@ export function buildStartCommand() {
|
|
|
1252
1204
|
return decorateQa(transfer.successor, qaCwd, undefined, settings);
|
|
1253
1205
|
},
|
|
1254
1206
|
});
|
|
1255
|
-
liveStatusReporter?.updateState({ contextSnapshot: () => controller.contextSnapshot() });
|
|
1256
1207
|
return (await controller.atWorkSessionBoundary(adapter, frozenAction, strategy)).adapter;
|
|
1257
1208
|
};
|
|
1209
|
+
const observeQaNativeCompactions = async (adapter) => {
|
|
1210
|
+
if (!activeContinuityRunId)
|
|
1211
|
+
return;
|
|
1212
|
+
const qaSettings = resolvedContinuitySettings("qa");
|
|
1213
|
+
const controller = new ThresholdCompactionController({
|
|
1214
|
+
projectDir: cwd,
|
|
1215
|
+
runId: activeContinuityRunId,
|
|
1216
|
+
role: "qa",
|
|
1217
|
+
initialSettings: qaSettings,
|
|
1218
|
+
historicalCountUncertain: recoveryCompactionHistoryUncertain(cwd, recoveryRecord, activeContinuityRunId, "qa"),
|
|
1219
|
+
});
|
|
1220
|
+
await controller.observeNativeCompactions(adapter);
|
|
1221
|
+
};
|
|
1258
1222
|
const qaNonconvergence = createQaNonconvergenceHandler(cwd, Boolean(opts.yes), roleDefaults.planner);
|
|
1259
1223
|
if (branchMode) {
|
|
1260
1224
|
const ticketsConfig = loadTicketsConfig(cwd);
|
|
@@ -1356,68 +1320,12 @@ export function buildStartCommand() {
|
|
|
1356
1320
|
});
|
|
1357
1321
|
let auditBuilder;
|
|
1358
1322
|
let auditViewer;
|
|
1359
|
-
let auditCompleted = false;
|
|
1360
1323
|
const auditRunId = `audit-${stamp}`;
|
|
1361
1324
|
activeContinuityRunId = auditRunId;
|
|
1362
1325
|
try {
|
|
1363
|
-
const auditSettings = resolvedContinuitySettings("builder");
|
|
1364
|
-
const auditInstruction = buildBranchAuditInstruction(plan.nodes.map((node) => node.ticket));
|
|
1365
|
-
const auditDb = new WorkflowDb(cwd);
|
|
1366
|
-
try {
|
|
1367
|
-
auditDb.ensureRun(auditRunId);
|
|
1368
|
-
auditDb.appendContinuityEvent({
|
|
1369
|
-
runId: auditRunId, role: "host", kind: "branch_audit_initialized",
|
|
1370
|
-
payload: { frozenActionDigest: createHash("sha256").update(auditInstruction).digest("hex") },
|
|
1371
|
-
authoritativeStateRevision: auditSettings.settings_revision,
|
|
1372
|
-
});
|
|
1373
|
-
auditDb.publishContinuityCheckpoint({
|
|
1374
|
-
runId: auditRunId, role: "builder", authoritativeStateRevision: auditSettings.settings_revision,
|
|
1375
|
-
delta: {
|
|
1376
|
-
version: 1, decisions: [], constraints: [], discoveries: [], completedActions: [], evidence: [], failures: [], blockers: [],
|
|
1377
|
-
openWork: ["complete the selected-ticket dependency audit"], nextAction: "complete the selected-ticket dependency audit",
|
|
1378
|
-
},
|
|
1379
|
-
});
|
|
1380
|
-
}
|
|
1381
|
-
finally {
|
|
1382
|
-
auditDb.close();
|
|
1383
|
-
}
|
|
1384
1326
|
auditBuilder = await createBuilder(cwd);
|
|
1385
|
-
const auditController = RoleSessionController.managed({
|
|
1386
|
-
projectDir: cwd, runId: auditRunId, role: "builder", initialSettings: auditSettings,
|
|
1387
|
-
requireNativeContextManagement: true,
|
|
1388
|
-
readSettings: () => liveRoleSettings("builder", auditSettings),
|
|
1389
|
-
settingsBoundary: (input) => applyBuilderSettingsBoundary(auditRunId, cwd, input),
|
|
1390
|
-
report: (event) => log.write("context-lifecycle", { phase: "branch-audit", ...event, sample: event.sample }),
|
|
1391
|
-
handoff: async ({ reason, adapter, sample, settings, compactionCount, frozenAction }) => {
|
|
1392
|
-
const nativeUsage = await adapter.sessionUsage?.().catch(() => undefined);
|
|
1393
|
-
const transfer = await new HandoffService(cwd).transfer({
|
|
1394
|
-
runId: auditRunId, role: "builder", reason, predecessorSessionId: adapter.sessionId(), predecessorSessionRef: adapter.sessionRef?.(),
|
|
1395
|
-
roleState: { phase: "branch-audit", frozenActionDigest: createHash("sha256").update(frozenAction).digest("hex"), contextSample: sample },
|
|
1396
|
-
...(nativeUsage ? { sessionUsage: {
|
|
1397
|
-
version: 1, runId: auditRunId, role: "builder", provider: adapter.agent,
|
|
1398
|
-
providerSessionId: adapter.sessionId(), observedAt: nativeUsage.observedAt, source: nativeUsage.source,
|
|
1399
|
-
cumulativeInputTokens: nativeUsage.inputTokens, cumulativeOutputTokens: nativeUsage.outputTokens,
|
|
1400
|
-
cumulativeTotalTokens: nativeUsage.totalTokens, authoritativeCostUsd: nativeUsage.authoritativeCostUsd,
|
|
1401
|
-
} } : {}),
|
|
1402
|
-
compactionCount, compactMaximum: settings.compact_maximum,
|
|
1403
|
-
resources: [{ label: "frozen-branch-audit", content: frozenAction, authoritative: true }],
|
|
1404
|
-
}, () => createBuilderForSettings(cwd, settings));
|
|
1405
|
-
log.write("handoff-transfer", {
|
|
1406
|
-
phase: "branch-audit", reason, generation: transfer.manifest.generation,
|
|
1407
|
-
predecessorSessionId: transfer.manifest.predecessorSessionId, successorSessionId: transfer.successorSessionId,
|
|
1408
|
-
acceptanceCheckpointDigest: transfer.acceptanceCheckpointDigest,
|
|
1409
|
-
});
|
|
1410
|
-
if (adapter instanceof ContinuityAdapter) {
|
|
1411
|
-
await adapter.adoptValidatedSuccessor(transfer.successor);
|
|
1412
|
-
return adapter;
|
|
1413
|
-
}
|
|
1414
|
-
await adapter.close().catch(() => { });
|
|
1415
|
-
return continuousBuilder(transfer.successor, cwd, auditRunId, settings);
|
|
1416
|
-
},
|
|
1417
|
-
});
|
|
1418
|
-
auditBuilder = (await auditController.atSafeBoundary(auditBuilder, auditInstruction)).adapter;
|
|
1419
1327
|
auditViewer = printEvents(auditBuilder.events());
|
|
1420
|
-
const audit = await auditBuilder.sendTurn(
|
|
1328
|
+
const audit = await auditBuilder.sendTurn(buildBranchAuditInstruction(plan.nodes.map((node) => node.ticket)));
|
|
1421
1329
|
if (audit.isError)
|
|
1422
1330
|
throw new Error(audit.text);
|
|
1423
1331
|
const auditDependencies = parseAuditDependencies(audit.text);
|
|
@@ -1431,7 +1339,6 @@ export function buildStartCommand() {
|
|
|
1431
1339
|
ticketIds: selectedNewTicket ? [selectedNewTicket] : selectedStackTickets ?? deliveryRun?.remaining.slice(0, steps) ?? recoveryRecord?.tickets,
|
|
1432
1340
|
});
|
|
1433
1341
|
auditDependencyCount = auditDependencies.length;
|
|
1434
|
-
auditCompleted = true;
|
|
1435
1342
|
}
|
|
1436
1343
|
finally {
|
|
1437
1344
|
await auditBuilder?.close().catch(() => { });
|
|
@@ -1439,9 +1346,7 @@ export function buildStartCommand() {
|
|
|
1439
1346
|
const auditDb = new WorkflowDb(cwd);
|
|
1440
1347
|
try {
|
|
1441
1348
|
if (auditDb.getRun(auditRunId))
|
|
1442
|
-
auditDb.transition(auditRunId,
|
|
1443
|
-
? { status: "completed", checkpoint: "branch-audit-complete", remainingWork: {}, event: "audit_completed" }
|
|
1444
|
-
: { status: "paused", checkpoint: "branch-audit-interrupted", remainingWork: { action: "complete the selected-ticket dependency audit" }, event: "audit_interrupted" });
|
|
1349
|
+
auditDb.transition(auditRunId, { status: "completed", checkpoint: "branch-audit-complete", remainingWork: {}, event: "audit_completed" });
|
|
1445
1350
|
}
|
|
1446
1351
|
finally {
|
|
1447
1352
|
auditDb.close();
|
|
@@ -1562,8 +1467,7 @@ export function buildStartCommand() {
|
|
|
1562
1467
|
return existing;
|
|
1563
1468
|
const controller = RoleSessionController.managed({
|
|
1564
1469
|
projectDir: cwd, runId: masterRun.runId, role: "builder", initialSettings: capturedBranchBuilder,
|
|
1565
|
-
|
|
1566
|
-
historicalCountUncertain: Boolean(recoveryRecord && (recoveryRecord.legacy || !recoveryRecord.runDecisions)),
|
|
1470
|
+
historicalCountUncertain: recoveryCompactionHistoryUncertain(cwd, recoveryRecord, masterRun.runId, "builder"),
|
|
1567
1471
|
readSettings: () => liveRoleSettings("builder", capturedBranchBuilder),
|
|
1568
1472
|
settingsBoundary: (input) => applyBuilderSettingsBoundary(masterRun.runId, worktreePath, input),
|
|
1569
1473
|
settingsAdopted: (settings, active) => liveStatusReporter?.updateState({
|
|
@@ -1571,7 +1475,7 @@ export function buildStartCommand() {
|
|
|
1571
1475
|
adapter: active, settingsRevision: settings.settings_revision, displaySessionCost: settings.display_session_cost,
|
|
1572
1476
|
sessionTransition: "adopted live settings",
|
|
1573
1477
|
}),
|
|
1574
|
-
report: (event) => { currentActivity()?.update(event.kind, event.detail, { provider: capturedBranchBuilder.make, model: capturedBranchBuilder.model }); log.write("context-lifecycle", { worktreePath, ...event, sample: event.sample });
|
|
1478
|
+
report: (event) => { currentActivity()?.update(event.kind, event.detail, { provider: capturedBranchBuilder.make, model: capturedBranchBuilder.model }); log.write("context-lifecycle", { worktreePath, ...event, sample: event.sample }); },
|
|
1575
1479
|
handoff: async ({ reason, adapter, sample, settings, compactionCount, frozenAction }) => {
|
|
1576
1480
|
const nativeUsage = await adapter.sessionUsage?.().catch(() => undefined);
|
|
1577
1481
|
const transfer = await new HandoffService(cwd).transfer({
|
|
@@ -1623,7 +1527,7 @@ export function buildStartCommand() {
|
|
|
1623
1527
|
archiveDoc: ticketsConfig.paths.archiveDoc,
|
|
1624
1528
|
},
|
|
1625
1529
|
resumeSessions: resumeSessionByTicket,
|
|
1626
|
-
createBuilder:
|
|
1530
|
+
createBuilder: (builderCwd, sessionId, sessionRef) => createBuilder(builderCwd, sessionId, sessionRef),
|
|
1627
1531
|
recordBuilderSession: (session, ticketId) => { masterRun = persistBuildSession(cwd, { ...masterRun, currentTicket: ticketId }, "builder", session); },
|
|
1628
1532
|
recordQaSession: (session, ticketId) => { masterRun = persistBuildSession(cwd, { ...masterRun, currentTicket: ticketId }, "qa", session); },
|
|
1629
1533
|
onSessionUnavailable: (error) => { branchSessionFailure = error; },
|
|
@@ -1633,9 +1537,6 @@ export function buildStartCommand() {
|
|
|
1633
1537
|
observeBuilder: async (builder) => {
|
|
1634
1538
|
liveStatusReporter?.stop();
|
|
1635
1539
|
activeBuilderForStatus = () => builder;
|
|
1636
|
-
activeBuilderContextSnapshot = () => [...branchContextControllers.values()]
|
|
1637
|
-
.map((controller) => controller.contextSnapshot())
|
|
1638
|
-
.find((snapshot) => snapshot?.contextSample.providerSessionId === builder.sessionId());
|
|
1639
1540
|
const reporter = liveStatusReporter = new AgentStatusReporter({
|
|
1640
1541
|
runId: masterRun.runId, role: "builder", provider: builder.agent, model: capturedBranchBuilder.model,
|
|
1641
1542
|
reasoning: capturedBranchBuilder.reasoning, fast: capturedBranchBuilder.fast, step: Math.min(plan.nodes.length, branchContextControllers.size + 1), total: plan.nodes.length,
|
|
@@ -1653,7 +1554,6 @@ export function buildStartCommand() {
|
|
|
1653
1554
|
finally {
|
|
1654
1555
|
db.close();
|
|
1655
1556
|
} },
|
|
1656
|
-
contextSnapshot: activeBuilderContextSnapshot,
|
|
1657
1557
|
}, (line, snapshot) => {
|
|
1658
1558
|
const activity = currentActivity();
|
|
1659
1559
|
if (activity && process.stdout.isTTY)
|
|
@@ -1664,6 +1564,7 @@ export function buildStartCommand() {
|
|
|
1664
1564
|
const db = new WorkflowDb(cwd);
|
|
1665
1565
|
try {
|
|
1666
1566
|
db.recordTelemetry(masterRun.runId, snapshot);
|
|
1567
|
+
db.recordContextSample(snapshot.contextSample);
|
|
1667
1568
|
if (snapshot.sessionUsage)
|
|
1668
1569
|
db.recordSessionUsage(snapshot.sessionUsage);
|
|
1669
1570
|
}
|
|
@@ -1681,8 +1582,10 @@ export function buildStartCommand() {
|
|
|
1681
1582
|
},
|
|
1682
1583
|
qaNonconvergence,
|
|
1683
1584
|
beforeBuilderTurn: async (adapter, frozenAction, worktreePath) => (await branchContextController(worktreePath).atSafeBoundary(adapter, frozenAction)).adapter,
|
|
1585
|
+
observeBuilderNativeCompactions: async (adapter, worktreePath) => { await branchContextController(worktreePath).observeNativeCompactions(adapter); },
|
|
1684
1586
|
builderSessionBoundary: async (adapter, frozenAction, strategy, worktreePath) => (await branchContextController(worktreePath).atWorkSessionBoundary(adapter, frozenAction, strategy)).adapter,
|
|
1685
1587
|
qaSessionBoundary,
|
|
1588
|
+
observeQaNativeCompactions,
|
|
1686
1589
|
});
|
|
1687
1590
|
if (branchSessionFailure) {
|
|
1688
1591
|
const failure = branchSessionFailure;
|
|
@@ -1825,9 +1728,8 @@ export function buildStartCommand() {
|
|
|
1825
1728
|
projectDir: cwd,
|
|
1826
1729
|
runId: buildRun.runId,
|
|
1827
1730
|
role: "builder",
|
|
1828
|
-
requireNativeContextManagement: true,
|
|
1829
1731
|
initialSettings: capturedBuilder,
|
|
1830
|
-
historicalCountUncertain:
|
|
1732
|
+
historicalCountUncertain: recoveryCompactionHistoryUncertain(cwd, recoveryRecord, buildRun.runId, "builder"),
|
|
1831
1733
|
readSettings: () => liveRoleSettings("builder", capturedBuilder),
|
|
1832
1734
|
settingsBoundary: (input) => applyBuilderSettingsBoundary(buildRun.runId, cwd, input),
|
|
1833
1735
|
settingsAdopted: (settings, active) => liveStatusReporter?.updateState({
|
|
@@ -1838,7 +1740,6 @@ export function buildStartCommand() {
|
|
|
1838
1740
|
report: (event) => {
|
|
1839
1741
|
currentActivity()?.update(event.kind, event.detail, { provider: capturedBuilder.make, model: capturedBuilder.model });
|
|
1840
1742
|
log.write("context-lifecycle", { ...event, sample: event.sample });
|
|
1841
|
-
void liveStatusReporter?.tick();
|
|
1842
1743
|
},
|
|
1843
1744
|
handoff: async ({ reason, adapter, sample, settings, compactionCount, frozenAction }) => {
|
|
1844
1745
|
const nativeUsage = await adapter.sessionUsage?.().catch(() => undefined);
|
|
@@ -1868,9 +1769,7 @@ export function buildStartCommand() {
|
|
|
1868
1769
|
return continuousBuilder(transfer.successor, cwd, buildRun.runId, settings);
|
|
1869
1770
|
},
|
|
1870
1771
|
});
|
|
1871
|
-
|
|
1872
|
-
activeBuilderContextSnapshot = () => contextController.contextSnapshot();
|
|
1873
|
-
const foreman = new Foreman(builder, log, config.notifications.enabled, qaEnabled, 3, cwd, undefined, qaEnabled ? createQa : undefined, capturedQa.session_strategy, createBuilder, capturedBuilder.session_strategy, qaNonconvergence, async (adapter, frozenAction) => (await contextController.atSafeBoundary(adapter, frozenAction)).adapter, async (adapter, frozenAction, strategy) => (await contextController.atWorkSessionBoundary(adapter, frozenAction, strategy)).adapter, qaSessionBoundary);
|
|
1772
|
+
const foreman = new Foreman(builder, log, config.notifications.enabled, qaEnabled, 3, cwd, undefined, qaEnabled ? createQa : undefined, capturedQa.session_strategy, createBuilder, capturedBuilder.session_strategy, qaNonconvergence, async (adapter, frozenAction) => (await contextController.atSafeBoundary(adapter, frozenAction)).adapter, async (adapter, frozenAction, strategy) => (await contextController.atWorkSessionBoundary(adapter, frozenAction, strategy)).adapter, qaSessionBoundary, observeQaNativeCompactions, async (adapter) => { await contextController.observeNativeCompactions(adapter); });
|
|
1874
1773
|
activeBuilderForStatus = () => foreman.builderAdapter();
|
|
1875
1774
|
const statusReporter = liveStatusReporter = new AgentStatusReporter({
|
|
1876
1775
|
runId: buildRun.runId,
|
|
@@ -1891,7 +1790,6 @@ export function buildStartCommand() {
|
|
|
1891
1790
|
finally {
|
|
1892
1791
|
db.close();
|
|
1893
1792
|
} },
|
|
1894
|
-
contextSnapshot: () => contextController.contextSnapshot(),
|
|
1895
1793
|
}, (line, snapshot) => {
|
|
1896
1794
|
const activity = currentActivity();
|
|
1897
1795
|
if (activity && process.stdout.isTTY)
|
|
@@ -1902,6 +1800,7 @@ export function buildStartCommand() {
|
|
|
1902
1800
|
const db = new WorkflowDb(cwd);
|
|
1903
1801
|
try {
|
|
1904
1802
|
db.recordTelemetry(buildRun.runId, snapshot);
|
|
1803
|
+
db.recordContextSample(snapshot.contextSample);
|
|
1905
1804
|
if (snapshot.sessionUsage)
|
|
1906
1805
|
db.recordSessionUsage(snapshot.sessionUsage);
|
|
1907
1806
|
}
|
|
@@ -2054,6 +1953,19 @@ export function buildStartCommand() {
|
|
|
2054
1953
|
}
|
|
2055
1954
|
});
|
|
2056
1955
|
}
|
|
1956
|
+
async function prepareNativeAutoCompaction(adapter) {
|
|
1957
|
+
if (!adapter.prepareAutoCompaction) {
|
|
1958
|
+
await adapter.close().catch(() => { });
|
|
1959
|
+
throw new Error(`${adapter.agent} does not support provider-native automatic compaction`);
|
|
1960
|
+
}
|
|
1961
|
+
try {
|
|
1962
|
+
await adapter.prepareAutoCompaction();
|
|
1963
|
+
}
|
|
1964
|
+
catch (error) {
|
|
1965
|
+
await adapter.close().catch(() => { });
|
|
1966
|
+
throw error;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
2057
1969
|
function createQaNonconvergenceHandler(projectDir, noninteractive, planner) {
|
|
2058
1970
|
return async (context) => {
|
|
2059
1971
|
if (noninteractive || !process.stdin.isTTY || !process.stdout.isTTY)
|
|
@@ -2156,6 +2068,19 @@ function readAgentDefaults(projectDir) {
|
|
|
2156
2068
|
function explicitDefaultValue(value) {
|
|
2157
2069
|
return value && value !== "default" ? value : undefined;
|
|
2158
2070
|
}
|
|
2071
|
+
function recoveryCompactionHistoryUncertain(projectDir, recovery, runId, role) {
|
|
2072
|
+
if (!recovery)
|
|
2073
|
+
return false;
|
|
2074
|
+
if (recovery.legacy || !recovery.runDecisions || !runId)
|
|
2075
|
+
return true;
|
|
2076
|
+
const db = new WorkflowDb(projectDir);
|
|
2077
|
+
try {
|
|
2078
|
+
return db.hasUncheckpointedRoleTurn(runId, role);
|
|
2079
|
+
}
|
|
2080
|
+
finally {
|
|
2081
|
+
db.close();
|
|
2082
|
+
}
|
|
2083
|
+
}
|
|
2159
2084
|
/** Make the executable branch plan exactly match each explicit root-to-tip stack. */
|
|
2160
2085
|
export function applyExplicitStackTopology(plan, stacks, delivery, branchPrefix) {
|
|
2161
2086
|
const nodeByTicket = new Map(plan.nodes.map((node) => [node.ticket.id, node]));
|
package/dist/continuity.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { ContinuityDelta, ResolvedAgentSettings } from "rafi-spec";
|
|
2
|
-
import type { BuilderAdapter, CompactResult,
|
|
2
|
+
import type { BuilderAdapter, CompactResult, ContextUsage, NativeCompaction, ProviderSessionUsage, ProviderSettingSwitch, RuntimeFailure, TurnResult } from "./adapters/types.js";
|
|
3
3
|
export declare const CONTINUITY_MARKER = "RAFI_CONTINUITY_DELTA:";
|
|
4
4
|
export declare class ContinuityValidationError extends Error {
|
|
5
5
|
readonly problems: string[];
|
|
@@ -38,7 +38,7 @@ export interface ContinuityAdapterOptions {
|
|
|
38
38
|
role: "builder" | "qa";
|
|
39
39
|
settings: ResolvedAgentSettings;
|
|
40
40
|
authoritativeStateRevision?: () => number;
|
|
41
|
-
/**
|
|
41
|
+
/** Fresh successor creation after the same-session repair also fails. */
|
|
42
42
|
createSuccessor?: (handoff: string) => Promise<BuilderAdapter>;
|
|
43
43
|
/**
|
|
44
44
|
* Production recovery path for a double-invalid delta. The callback must
|
|
@@ -62,7 +62,6 @@ export declare class ContinuityAdapter implements BuilderAdapter {
|
|
|
62
62
|
private readonly queue;
|
|
63
63
|
private sourcePump?;
|
|
64
64
|
private recoveryLeasePending;
|
|
65
|
-
private turnDispatcher?;
|
|
66
65
|
constructor(options: ContinuityAdapterOptions);
|
|
67
66
|
get agent(): "claude" | "codex";
|
|
68
67
|
sendTurn(instruction: string): Promise<TurnResult>;
|
|
@@ -71,10 +70,10 @@ export declare class ContinuityAdapter implements BuilderAdapter {
|
|
|
71
70
|
adoptSessionRef(ref: import("rafi-spec").ProviderSessionRefV1): void;
|
|
72
71
|
validateSession(): Promise<import("rafi-spec").SessionAvailabilityV1>;
|
|
73
72
|
compact(): Promise<CompactResult>;
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
73
|
+
prepareAutoCompaction(thresholdPercent?: number): Promise<void>;
|
|
74
|
+
drainNativeCompactions(): import("./adapters/types.js").NativeCompaction[];
|
|
75
|
+
restoreNativeCompactions(compactions: NativeCompaction[]): void;
|
|
76
|
+
contextUsageAfterNativeCompaction(compaction: NativeCompaction): Promise<ContextUsage | undefined>;
|
|
78
77
|
contextUsage(): Promise<ContextUsage | undefined>;
|
|
79
78
|
sessionUsage(): Promise<ProviderSessionUsage | undefined>;
|
|
80
79
|
switchSettings(settings: ProviderSettingSwitch): Promise<CompactResult>;
|
|
@@ -82,7 +81,6 @@ export declare class ContinuityAdapter implements BuilderAdapter {
|
|
|
82
81
|
close(): Promise<void>;
|
|
83
82
|
/** Keep the observable wrapper and event stream stable while moving to an accepted successor. */
|
|
84
83
|
adoptValidatedSuccessor(successor: BuilderAdapter): Promise<void>;
|
|
85
|
-
private providerTurn;
|
|
86
84
|
private publish;
|
|
87
85
|
private moveRecoveryLeaseAfterCheckpoint;
|
|
88
86
|
private recordSessionUnavailable;
|