refactor(gsd): unify sidecar mini-loop into main dispatch path (#1617)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
7564901868
commit
70cf14f72d
1 changed files with 79 additions and 182 deletions
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
import { importExtensionModule, type ExtensionAPI, type ExtensionContext } from "@gsd/pi-coding-agent";
|
||||
|
||||
import type { AutoSession } from "./auto/session.js";
|
||||
import type { AutoSession, SidecarItem } from "./auto/session.js";
|
||||
import { NEW_SESSION_TIMEOUT_MS } from "./auto/session.js";
|
||||
import type { GSDPreferences } from "./preferences.js";
|
||||
import type { SessionLockStatus } from "./session-lock.js";
|
||||
|
|
@ -694,6 +694,18 @@ export async function autoLoop(
|
|||
// ── Blanket try/catch: one bad iteration must not kill the session
|
||||
const prefs = deps.loadEffectiveGSDPreferences()?.preferences;
|
||||
|
||||
// ── Check sidecar queue before deriveState ──
|
||||
let sidecarItem: SidecarItem | undefined;
|
||||
if (s.sidecarQueue.length > 0) {
|
||||
sidecarItem = s.sidecarQueue.shift()!;
|
||||
debugLog("autoLoop", {
|
||||
phase: "sidecar-dequeue",
|
||||
kind: sidecarItem.kind,
|
||||
unitType: sidecarItem.unitType,
|
||||
unitId: sidecarItem.unitId,
|
||||
});
|
||||
}
|
||||
|
||||
const sessionLockBase = deps.lockBase();
|
||||
if (sessionLockBase) {
|
||||
const lockStatus = deps.validateSessionLock(sessionLockBase);
|
||||
|
|
@ -714,6 +726,17 @@ export async function autoLoop(
|
|||
}
|
||||
}
|
||||
|
||||
// Variables shared between the sidecar and normal paths
|
||||
let unitType: string;
|
||||
let unitId: string;
|
||||
let prompt: string;
|
||||
let pauseAfterUatDispatch = false;
|
||||
let state: GSDState;
|
||||
let mid: string | undefined;
|
||||
let midTitle: string | undefined;
|
||||
let observabilityIssues: unknown[] = [];
|
||||
|
||||
if (!sidecarItem) {
|
||||
// ── Phase 1: Pre-dispatch ───────────────────────────────────────────
|
||||
|
||||
// Resource version guard
|
||||
|
|
@ -764,10 +787,10 @@ export async function autoLoop(
|
|||
}
|
||||
|
||||
// Derive state
|
||||
let state = await deps.deriveState(s.basePath);
|
||||
state = await deps.deriveState(s.basePath);
|
||||
deps.syncCmuxSidebar(prefs, state);
|
||||
let mid = state.activeMilestone?.id;
|
||||
let midTitle = state.activeMilestone?.title;
|
||||
mid = state.activeMilestone?.id;
|
||||
midTitle = state.activeMilestone?.title;
|
||||
debugLog("autoLoop", {
|
||||
phase: "state-derived",
|
||||
iteration,
|
||||
|
|
@ -1130,10 +1153,10 @@ export async function autoLoop(
|
|||
continue;
|
||||
}
|
||||
|
||||
let unitType = dispatchResult.unitType;
|
||||
let unitId = dispatchResult.unitId;
|
||||
let prompt = dispatchResult.prompt;
|
||||
const pauseAfterUatDispatch = dispatchResult.pauseAfterDispatch ?? false;
|
||||
unitType = dispatchResult.unitType;
|
||||
unitId = dispatchResult.unitId;
|
||||
prompt = dispatchResult.prompt;
|
||||
pauseAfterUatDispatch = dispatchResult.pauseAfterDispatch ?? false;
|
||||
|
||||
// ── Sliding-window stuck detection with graduated recovery ──
|
||||
const derivedKey = `${unitType}/${unitId}`;
|
||||
|
|
@ -1250,13 +1273,27 @@ export async function autoLoop(
|
|||
break;
|
||||
}
|
||||
|
||||
const observabilityIssues = await deps.collectObservabilityWarnings(
|
||||
observabilityIssues = await deps.collectObservabilityWarnings(
|
||||
ctx,
|
||||
s.basePath,
|
||||
unitType,
|
||||
unitId,
|
||||
);
|
||||
|
||||
// Derive state for shared use in execution phase
|
||||
// (state, mid, midTitle already set above)
|
||||
|
||||
} else {
|
||||
// ── Sidecar path: use values from the sidecar item directly ──
|
||||
unitType = sidecarItem.unitType;
|
||||
unitId = sidecarItem.unitId;
|
||||
prompt = sidecarItem.prompt;
|
||||
// Derive minimal state for progress widget / execution context
|
||||
state = await deps.deriveState(s.basePath);
|
||||
mid = state.activeMilestone?.id;
|
||||
midTitle = state.activeMilestone?.title;
|
||||
}
|
||||
|
||||
// ── Phase 4: Unit execution ─────────────────────────────────────────
|
||||
|
||||
debugLog("autoLoop", {
|
||||
|
|
@ -1371,7 +1408,7 @@ export async function autoLoop(
|
|||
);
|
||||
}
|
||||
|
||||
// Select and apply model (with tier escalation on retry)
|
||||
// Select and apply model (with tier escalation on retry — normal units only)
|
||||
const modelResult = await deps.selectAndApplyModel(
|
||||
ctx,
|
||||
pi,
|
||||
|
|
@ -1381,7 +1418,7 @@ export async function autoLoop(
|
|||
prefs,
|
||||
s.verbose,
|
||||
s.autoModeStartModel,
|
||||
{ isRetry, previousTier },
|
||||
sidecarItem ? undefined : { isRetry, previousTier },
|
||||
);
|
||||
s.currentUnitRouting =
|
||||
modelResult.routing as AutoSession["currentUnitRouting"];
|
||||
|
|
@ -1532,7 +1569,13 @@ export async function autoLoop(
|
|||
};
|
||||
|
||||
// Pre-verification processing (commit, doctor, state rebuild, etc.)
|
||||
const preResult = await deps.postUnitPreVerification(postUnitCtx);
|
||||
// Sidecar items use lightweight pre-verification opts
|
||||
const preVerificationOpts: PreVerificationOpts | undefined = sidecarItem
|
||||
? sidecarItem.kind === "hook"
|
||||
? { skipSettleDelay: true, skipDoctor: true, skipStateRebuild: true, skipWorktreeSync: true }
|
||||
: { skipSettleDelay: true, skipStateRebuild: true }
|
||||
: undefined;
|
||||
const preResult = await deps.postUnitPreVerification(postUnitCtx, preVerificationOpts);
|
||||
if (preResult === "dispatched") {
|
||||
debugLog("autoLoop", {
|
||||
phase: "exit",
|
||||
|
|
@ -1551,22 +1594,32 @@ export async function autoLoop(
|
|||
break;
|
||||
}
|
||||
|
||||
// Verification gate — the loop handles retries via s.pendingVerificationRetry
|
||||
const verificationResult = await deps.runPostUnitVerification(
|
||||
{ s, ctx, pi },
|
||||
deps.pauseAuto,
|
||||
);
|
||||
// Verification gate
|
||||
// Hook sidecar items skip verification entirely.
|
||||
// Non-hook sidecar items run verification but skip retries (just continue).
|
||||
const skipVerification = sidecarItem?.kind === "hook";
|
||||
if (!skipVerification) {
|
||||
const verificationResult = await deps.runPostUnitVerification(
|
||||
{ s, ctx, pi },
|
||||
deps.pauseAuto,
|
||||
);
|
||||
|
||||
if (verificationResult === "pause") {
|
||||
debugLog("autoLoop", { phase: "exit", reason: "verification-pause" });
|
||||
break;
|
||||
}
|
||||
if (verificationResult === "pause") {
|
||||
debugLog("autoLoop", { phase: "exit", reason: "verification-pause" });
|
||||
break;
|
||||
}
|
||||
|
||||
if (verificationResult === "retry") {
|
||||
// s.pendingVerificationRetry was set by runPostUnitVerification.
|
||||
// Continue the loop — next iteration will inject the retry context into the prompt.
|
||||
debugLog("autoLoop", { phase: "verification-retry", iteration });
|
||||
continue;
|
||||
if (verificationResult === "retry") {
|
||||
if (sidecarItem) {
|
||||
// Sidecar verification retries are skipped — just continue
|
||||
debugLog("autoLoop", { phase: "sidecar-verification-retry-skipped", iteration });
|
||||
} else {
|
||||
// s.pendingVerificationRetry was set by runPostUnitVerification.
|
||||
// Continue the loop — next iteration will inject the retry context into the prompt.
|
||||
debugLog("autoLoop", { phase: "verification-retry", iteration });
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Post-verification processing (DB dual-write, hooks, triage, quick-tasks)
|
||||
|
|
@ -1586,162 +1639,6 @@ export async function autoLoop(
|
|||
break;
|
||||
}
|
||||
|
||||
// ── Sidecar drain: dispatch enqueued hooks/triage/quick-tasks ──
|
||||
let sidecarBroke = false;
|
||||
while (s.sidecarQueue.length > 0 && s.active) {
|
||||
const item = s.sidecarQueue.shift()!;
|
||||
debugLog("autoLoop", {
|
||||
phase: "sidecar-dequeue",
|
||||
kind: item.kind,
|
||||
unitType: item.unitType,
|
||||
unitId: item.unitId,
|
||||
});
|
||||
|
||||
// Set up as current unit
|
||||
const sidecarStartedAt = Date.now();
|
||||
s.currentUnit = {
|
||||
type: item.unitType,
|
||||
id: item.unitId,
|
||||
startedAt: sidecarStartedAt,
|
||||
};
|
||||
deps.writeUnitRuntimeRecord(
|
||||
s.basePath,
|
||||
item.unitType,
|
||||
item.unitId,
|
||||
sidecarStartedAt,
|
||||
{
|
||||
phase: "dispatched",
|
||||
wrapupWarningSent: false,
|
||||
timeoutAt: null,
|
||||
lastProgressAt: sidecarStartedAt,
|
||||
progressCount: 0,
|
||||
lastProgressKind: "dispatch",
|
||||
},
|
||||
);
|
||||
|
||||
// Model selection (handles hook model override)
|
||||
await deps.selectAndApplyModel(
|
||||
ctx,
|
||||
pi,
|
||||
item.unitType,
|
||||
item.unitId,
|
||||
s.basePath,
|
||||
prefs,
|
||||
s.verbose,
|
||||
s.autoModeStartModel,
|
||||
);
|
||||
|
||||
// Supervision
|
||||
deps.clearUnitTimeout();
|
||||
deps.startUnitSupervision({
|
||||
s,
|
||||
ctx,
|
||||
pi,
|
||||
unitType: item.unitType,
|
||||
unitId: item.unitId,
|
||||
prefs,
|
||||
buildSnapshotOpts: () =>
|
||||
deps.buildSnapshotOpts(item.unitType, item.unitId),
|
||||
buildRecoveryContext: () => ({}),
|
||||
pauseAuto: deps.pauseAuto,
|
||||
});
|
||||
|
||||
// Write lock
|
||||
const sidecarSessionFile = deps.getSessionFile(ctx);
|
||||
deps.writeLock(
|
||||
deps.lockBase(),
|
||||
item.unitType,
|
||||
item.unitId,
|
||||
s.completedUnits.length,
|
||||
sidecarSessionFile,
|
||||
);
|
||||
|
||||
// Execute via standard runUnit
|
||||
const sidecarResult = await runUnit(
|
||||
ctx,
|
||||
pi,
|
||||
s,
|
||||
item.unitType,
|
||||
item.unitId,
|
||||
item.prompt,
|
||||
);
|
||||
deps.clearUnitTimeout();
|
||||
|
||||
if (sidecarResult.status === "cancelled") {
|
||||
ctx.ui.notify(
|
||||
`Sidecar unit ${item.unitType} ${item.unitId} session cancelled. Stopping.`,
|
||||
"warning",
|
||||
);
|
||||
await deps.stopAuto(ctx, pi, "Sidecar session creation failed");
|
||||
sidecarBroke = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Immediate closeout for sidecar unit
|
||||
await deps.closeoutUnit(
|
||||
ctx,
|
||||
s.basePath,
|
||||
item.unitType,
|
||||
item.unitId,
|
||||
sidecarStartedAt,
|
||||
deps.buildSnapshotOpts(item.unitType, item.unitId),
|
||||
);
|
||||
|
||||
// Run pre-verification for the sidecar unit (lightweight path)
|
||||
const sidecarPreOpts: PreVerificationOpts = item.kind === "hook"
|
||||
? { skipSettleDelay: true, skipDoctor: true, skipStateRebuild: true, skipWorktreeSync: true }
|
||||
: { skipSettleDelay: true, skipStateRebuild: true };
|
||||
const sidecarPreResult =
|
||||
await deps.postUnitPreVerification(postUnitCtx, sidecarPreOpts);
|
||||
if (sidecarPreResult === "dispatched") {
|
||||
// Pre-verification caused stop/pause
|
||||
debugLog("autoLoop", {
|
||||
phase: "exit",
|
||||
reason: "sidecar-pre-verification-stop",
|
||||
});
|
||||
sidecarBroke = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Verification gate for non-hook sidecar units (triage, quick-tasks)
|
||||
// Hook units are lightweight and don't need verification.
|
||||
if (item.kind !== "hook") {
|
||||
const sidecarVerification = await deps.runPostUnitVerification(
|
||||
{ s, ctx, pi },
|
||||
deps.pauseAuto,
|
||||
);
|
||||
if (sidecarVerification === "pause") {
|
||||
debugLog("autoLoop", {
|
||||
phase: "exit",
|
||||
reason: "sidecar-verification-pause",
|
||||
});
|
||||
sidecarBroke = true;
|
||||
break;
|
||||
}
|
||||
// "retry" for sidecars — skip retry, just continue (sidecar retries are not worth the complexity)
|
||||
}
|
||||
|
||||
// Post-verification (may enqueue more sidecar items)
|
||||
const sidecarPostResult =
|
||||
await deps.postUnitPostVerification(postUnitCtx);
|
||||
if (sidecarPostResult === "stopped") {
|
||||
debugLog("autoLoop", { phase: "exit", reason: "sidecar-stopped" });
|
||||
sidecarBroke = true;
|
||||
break;
|
||||
}
|
||||
if (sidecarPostResult === "step-wizard") {
|
||||
debugLog("autoLoop", {
|
||||
phase: "exit",
|
||||
reason: "sidecar-step-wizard",
|
||||
});
|
||||
sidecarBroke = true;
|
||||
break;
|
||||
}
|
||||
// "continue" — loop checks sidecarQueue again
|
||||
}
|
||||
|
||||
if (sidecarBroke) break;
|
||||
|
||||
consecutiveErrors = 0; // Iteration completed successfully
|
||||
debugLog("autoLoop", { phase: "iteration-complete", iteration });
|
||||
} catch (loopErr) {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue