@trycadence/cli 0.1.16-dev.0 → 0.1.19-dev.0

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 (2) hide show
  1. package/dist/cadence +524 -118
  2. package/package.json +1 -1
package/dist/cadence CHANGED
@@ -1488,6 +1488,7 @@ function createCadenceClient(options = {}) {
1488
1488
  current: (input) => rpc.sessions.current.query(input),
1489
1489
  leases: {
1490
1490
  create: (input) => rpc.sessions.leases.create.mutate(input),
1491
+ renew: (input) => rpc.sessions.leases.renew.mutate(input),
1491
1492
  release: (input) => rpc.sessions.leases.release.mutate(input)
1492
1493
  }
1493
1494
  },
@@ -1520,7 +1521,7 @@ import { createInterface } from "readline/promises";
1520
1521
  // package.json
1521
1522
  var package_default = {
1522
1523
  name: "@trycadence/cli",
1523
- version: "0.1.16-dev.0",
1524
+ version: "0.1.19-dev.0",
1524
1525
  private: false,
1525
1526
  type: "module",
1526
1527
  bin: {
@@ -1560,8 +1561,9 @@ var defaultLeaseTtlSeconds = 15 * 60;
1560
1561
  var defaultCliApiBaseUrl = "https://cadenceapi.deploy.lvl8studios.com";
1561
1562
  var defaultCliWebBaseUrl = "https://cadence.deploy.lvl8studios.com";
1562
1563
  var defaultCheckpointThreshold = 3;
1563
- var defaultCheckpointCooldownSeconds = 10 * 60;
1564
+ var defaultCheckpointCooldownSeconds = 0;
1564
1565
  var defaultCheckpointWorkerTimeoutMs = 10 * 60 * 1000;
1566
+ var defaultCheckpointWorkerMaxAttempts = 3;
1565
1567
  var defaultHookCommand = `/bin/sh -lc 'root=$(git rev-parse --show-toplevel 2>/dev/null) || exit 0; cd "$root" || exit 0; [ -f .cadence/config.json ] || [ -f .cadence/config.local.json ] || exit 0; exec cadence agent-run ingest-stop --source codex --event stop >/dev/null'`;
1566
1568
  var agentLoopSuppressEnv = "CADENCE_AGENT_EVENT_SUPPRESS";
1567
1569
  var credentialRefreshSkewMs = 60 * 1000;
@@ -3090,16 +3092,6 @@ function normalizeGenericAgentEvent(input, base) {
3090
3092
  function agentSessionKey(source, agentSessionId) {
3091
3093
  return `${source}:${stableHash(agentSessionId)}`;
3092
3094
  }
3093
- function checkpointWorkLogMetadata(settings, mode, tokenAccounting) {
3094
- return {
3095
- checkpoint: {
3096
- ...mode ? { mode } : {},
3097
- provider: settings.provider,
3098
- ...settings.model ? { model: settings.model } : {},
3099
- ...tokenAccounting ? { tokenAccounting } : {}
3100
- }
3101
- };
3102
- }
3103
3095
  function defaultAgentLoopState() {
3104
3096
  return {
3105
3097
  version: 2,
@@ -3258,6 +3250,11 @@ function readAgentLoopSessions(rawSessions) {
3258
3250
  ...typeof record.lastCheckpointMode === "string" && agentRunMemoryModes.includes(record.lastCheckpointMode) ? { lastCheckpointMode: record.lastCheckpointMode } : {},
3259
3251
  ...readAgentRunCoverage(record.lastCheckpointCoverage),
3260
3252
  ...typeof record.previousCheckpointSummary === "string" ? { previousCheckpointSummary: record.previousCheckpointSummary } : {},
3253
+ ...typeof record.displayTitle === "string" ? { displayTitle: record.displayTitle } : {},
3254
+ ...typeof record.displayTitleUpdatedAt === "string" ? { displayTitleUpdatedAt: record.displayTitleUpdatedAt } : {},
3255
+ ...record.displayTitleSource === "route" ? { displayTitleSource: "route" } : {},
3256
+ ...typeof record.displayTitleConfidence === "string" && checkpointConfidenceLevels.includes(record.displayTitleConfidence) ? { displayTitleConfidence: record.displayTitleConfidence } : {},
3257
+ ...typeof record.displayTitleReason === "string" ? { displayTitleReason: record.displayTitleReason } : {},
3261
3258
  ...typeof record.lastCheckpointAuditFile === "string" ? { lastCheckpointAuditFile: record.lastCheckpointAuditFile } : {}
3262
3259
  };
3263
3260
  }
@@ -3312,6 +3309,8 @@ function readAgentSessionCadenceContext(record) {
3312
3309
  ...typeof contextRecord.ticketId === "string" ? { ticketId: contextRecord.ticketId } : {},
3313
3310
  ...typeof contextRecord.sessionId === "string" ? { sessionId: contextRecord.sessionId } : {},
3314
3311
  ...typeof contextRecord.changesetId === "string" ? { changesetId: contextRecord.changesetId } : {},
3312
+ ...typeof contextRecord.leaseId === "string" ? { leaseId: contextRecord.leaseId } : {},
3313
+ ...typeof contextRecord.leaseExpiresAt === "string" ? { leaseExpiresAt: contextRecord.leaseExpiresAt } : {},
3315
3314
  ...typeof contextRecord.capturedAt === "string" ? { capturedAt: contextRecord.capturedAt } : {}
3316
3315
  };
3317
3316
  return cadenceContext.ticketId || cadenceContext.sessionId || cadenceContext.changesetId ? { cadenceContext } : {};
@@ -3505,6 +3504,8 @@ function activeSessionFromCurrent(current) {
3505
3504
  ...sessionId ? { id: sessionId } : {},
3506
3505
  ticketId: leaseRecord.ticketId,
3507
3506
  ...typeof leaseRecord.changesetId === "string" ? { changesetId: leaseRecord.changesetId } : typeof sessionRecord.changesetId === "string" ? { changesetId: sessionRecord.changesetId } : {},
3507
+ ...typeof leaseRecord.id === "string" ? { leaseId: leaseRecord.id } : {},
3508
+ ...typeof leaseRecord.expiresAt === "string" ? { leaseExpiresAt: leaseRecord.expiresAt } : {},
3508
3509
  status: "active"
3509
3510
  };
3510
3511
  }
@@ -3529,6 +3530,8 @@ function cadenceContextFromCurrent(current) {
3529
3530
  const ticketId = typeof activeSession.ticketId === "string" ? activeSession.ticketId : undefined;
3530
3531
  const sessionId = typeof activeSession.id === "string" ? activeSession.id : undefined;
3531
3532
  const changesetId = typeof activeSession.changesetId === "string" ? activeSession.changesetId : undefined;
3533
+ const leaseId = typeof activeSession.leaseId === "string" ? activeSession.leaseId : undefined;
3534
+ const leaseExpiresAt2 = typeof activeSession.leaseExpiresAt === "string" ? activeSession.leaseExpiresAt : undefined;
3532
3535
  if (!ticketId && !sessionId && !changesetId) {
3533
3536
  return;
3534
3537
  }
@@ -3536,9 +3539,41 @@ function cadenceContextFromCurrent(current) {
3536
3539
  ...ticketId ? { ticketId } : {},
3537
3540
  ...sessionId ? { sessionId } : {},
3538
3541
  ...changesetId ? { changesetId } : {},
3542
+ ...leaseId ? { leaseId } : {},
3543
+ ...leaseExpiresAt2 ? { leaseExpiresAt: leaseExpiresAt2 } : {},
3539
3544
  capturedAt: new Date().toISOString()
3540
3545
  };
3541
3546
  }
3547
+ function leaseExpiresAtFromResponse(value) {
3548
+ return value && typeof value === "object" && "expiresAt" in value && typeof value.expiresAt === "string" ? value.expiresAt : undefined;
3549
+ }
3550
+ async function renewAgentSessionLease(client, projectId, context) {
3551
+ if (!context?.leaseId) {
3552
+ return context;
3553
+ }
3554
+ const expiresAt = leaseExpiresAt(defaultLeaseTtlSeconds);
3555
+ const renewed = await client.sessions.leases.renew({
3556
+ projectId,
3557
+ leaseId: context.leaseId,
3558
+ lease: {
3559
+ expiresAt,
3560
+ ...commandMetadata()
3561
+ }
3562
+ });
3563
+ return {
3564
+ ...context,
3565
+ leaseId: objectStringId(renewed) ?? context.leaseId,
3566
+ leaseExpiresAt: leaseExpiresAtFromResponse(renewed) ?? expiresAt,
3567
+ capturedAt: new Date().toISOString()
3568
+ };
3569
+ }
3570
+ async function renewAgentSessionLeaseBestEffort(client, projectId, context) {
3571
+ try {
3572
+ return await renewAgentSessionLease(client, projectId, context);
3573
+ } catch {
3574
+ return context;
3575
+ }
3576
+ }
3542
3577
  async function readCurrentCadenceContext(client, projectId) {
3543
3578
  const current = await client.sessions.current({
3544
3579
  projectId,
@@ -3561,6 +3596,9 @@ function fingerprintForCheckpoint(event, options) {
3561
3596
  }));
3562
3597
  }
3563
3598
  function shouldSkipForCooldown(state, cooldownSeconds) {
3599
+ if (cooldownSeconds <= 0) {
3600
+ return false;
3601
+ }
3564
3602
  if (!state.lastCheckpointAt) {
3565
3603
  return false;
3566
3604
  }
@@ -3788,6 +3826,43 @@ function parseCheckpointPlanSession(value) {
3788
3826
  ...reason ? { reason } : {}
3789
3827
  };
3790
3828
  }
3829
+ function cleanAgentSessionTitleText(value) {
3830
+ const cleaned = value.replace(/\s+/g, " ").trim();
3831
+ if (!cleaned) {
3832
+ return;
3833
+ }
3834
+ const normalized = cleaned.toLowerCase();
3835
+ const genericTitles = new Set(["agent session", "session", "work session", "cadence session", "current session"]);
3836
+ if (genericTitles.has(normalized)) {
3837
+ return;
3838
+ }
3839
+ return cleaned.length > 80 ? cleaned.slice(0, 77).trimEnd() + "..." : cleaned;
3840
+ }
3841
+ function agentSessionTitleString(record, key) {
3842
+ const value = record[key];
3843
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
3844
+ }
3845
+ function parseAgentSessionTitle(value) {
3846
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3847
+ return;
3848
+ }
3849
+ const record = value;
3850
+ const text = agentSessionTitleString(record, "text") ?? agentSessionTitleString(record, "title") ?? agentSessionTitleString(record, "label");
3851
+ const cleanedText = text ? cleanAgentSessionTitleText(text) : undefined;
3852
+ if (!cleanedText) {
3853
+ return;
3854
+ }
3855
+ const confidenceValue = agentSessionTitleString(record, "confidence") ?? "medium";
3856
+ if (!checkpointConfidenceLevels.includes(confidenceValue)) {
3857
+ return;
3858
+ }
3859
+ const reason = agentSessionTitleString(record, "reason")?.replace(/\s+/g, " ").trim();
3860
+ return {
3861
+ text: cleanedText,
3862
+ confidence: confidenceValue,
3863
+ ...reason ? { reason: reason.length > 300 ? `${reason.slice(0, 297).trimEnd()}...` : reason } : {}
3864
+ };
3865
+ }
3791
3866
  function parseCheckpointPlanFiles(value) {
3792
3867
  if (!Array.isArray(value)) {
3793
3868
  return [];
@@ -3845,6 +3920,33 @@ function safeAutomaticRoutePlan(plan, hasCurrentContext, reason) {
3845
3920
  function routePlanAllowsLifecycle(plan) {
3846
3921
  return checkpointRouteRequiresIntake(plan.route.action) && plan.route.confidence === "high";
3847
3922
  }
3923
+ function planWithCandidateSelection(original, selected, currentTicketId, candidateTicketIds) {
3924
+ let nextPlan = selected;
3925
+ if (nextPlan.route.action === "needs_human") {
3926
+ nextPlan = safeAutomaticRoutePlan(nextPlan, Boolean(currentTicketId), nextPlan.route.reason ?? "Candidate routing was uncertain.");
3927
+ } else if (checkpointRouteRequiresIntake(nextPlan.route.action) && nextPlan.route.confidence !== "high") {
3928
+ nextPlan = safeAutomaticRoutePlan(nextPlan, Boolean(currentTicketId), "Candidate routing requires high confidence.");
3929
+ } else if ((nextPlan.route.action === "intake_attach" || nextPlan.route.action === "switch_existing") && !nextPlan.route.targetTicketId) {
3930
+ nextPlan = safeAutomaticRoutePlan(nextPlan, Boolean(currentTicketId), "Candidate routing to existing work requires a target ticket id.");
3931
+ } else if ((nextPlan.route.action === "intake_attach" || nextPlan.route.action === "switch_existing") && nextPlan.route.targetTicketId && !candidateTicketIds.has(nextPlan.route.targetTicketId)) {
3932
+ nextPlan = safeAutomaticRoutePlan(nextPlan, Boolean(currentTicketId), "Candidate routing selected a ticket outside the intake candidate list.");
3933
+ } else if (nextPlan.route.action === "current" && !currentTicketId) {
3934
+ nextPlan = checkpointPlanWithRoute(nextPlan, {
3935
+ action: "noop",
3936
+ confidence: nextPlan.route.confidence,
3937
+ reason: nextPlan.route.reason ?? "No current Cadence context exists."
3938
+ });
3939
+ }
3940
+ const summary = nextPlan.summary ?? original.summary;
3941
+ return {
3942
+ ...nextPlan,
3943
+ ...summary ? { summary } : {},
3944
+ ...nextPlan.sessionTitle ?? original.sessionTitle ? { sessionTitle: nextPlan.sessionTitle ?? original.sessionTitle } : {},
3945
+ entries: nextPlan.entries.length ? nextPlan.entries : original.entries,
3946
+ files: nextPlan.files.length ? nextPlan.files : original.files,
3947
+ validationWarnings: [...original.validationWarnings, ...nextPlan.validationWarnings]
3948
+ };
3949
+ }
3848
3950
  function parseCheckpointPlanRecord(record, rawText, fallbackKind) {
3849
3951
  if (!("route" in record)) {
3850
3952
  const legacy = parseCheckpointRecord(record, rawText, fallbackKind);
@@ -3868,12 +3970,14 @@ function parseCheckpointPlanRecord(record, rawText, fallbackKind) {
3868
3970
  const entries = Array.isArray(record.entries) ? record.entries.map(parseCheckpointPlanEntry) : [];
3869
3971
  const summary = checkpointPlanString(record, "summary");
3870
3972
  const summaryUpdate = parseCheckpointPlanSummaryUpdate(record.summaryUpdate ?? record.currentSummary);
3973
+ const sessionTitle = parseAgentSessionTitle(record.sessionTitle ?? record.displayTitle);
3871
3974
  const session = parseCheckpointPlanSession(record.session);
3872
3975
  return normalizeCheckpointPlan({
3873
3976
  ...summary ? { summary } : {},
3874
3977
  route,
3875
3978
  entries,
3876
3979
  ...summaryUpdate ? { summaryUpdate } : {},
3980
+ ...sessionTitle ? { sessionTitle } : {},
3877
3981
  ...session ? { session } : {},
3878
3982
  files: parseCheckpointPlanFiles(record.files),
3879
3983
  legacy: false,
@@ -3925,6 +4029,62 @@ function parseCheckpointPlanJson(raw, fallbackKind) {
3925
4029
  function checkpointRouteRequiresIntake(action) {
3926
4030
  return action === "intake_create" || action === "intake_attach" || action === "switch_existing";
3927
4031
  }
4032
+ function intakeCandidates(intake) {
4033
+ if (!intake || typeof intake !== "object" || Array.isArray(intake)) {
4034
+ return [];
4035
+ }
4036
+ const candidates = intake.candidates;
4037
+ return Array.isArray(candidates) ? candidates.filter((candidate) => candidate && typeof candidate === "object" && !Array.isArray(candidate)) : [];
4038
+ }
4039
+ function intakeCandidateTicketId(candidate) {
4040
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
4041
+ return;
4042
+ }
4043
+ const record = candidate;
4044
+ const nestedTicket = record.ticket && typeof record.ticket === "object" && !Array.isArray(record.ticket) ? record.ticket : null;
4045
+ const value = record.ticketId ?? nestedTicket?.id ?? record.id;
4046
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
4047
+ }
4048
+ function intakeCandidateTicketIds(intake) {
4049
+ return new Set(intakeCandidates(intake).map(intakeCandidateTicketId).filter((id) => Boolean(id)));
4050
+ }
4051
+ function compactIntakeCandidate(candidate, index) {
4052
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
4053
+ return { index };
4054
+ }
4055
+ const record = candidate;
4056
+ const ticket = record.ticket && typeof record.ticket === "object" && !Array.isArray(record.ticket) ? record.ticket : {};
4057
+ const ticketId = intakeCandidateTicketId(candidate);
4058
+ const title = record.title ?? ticket.title;
4059
+ const status = record.status ?? ticket.status;
4060
+ const summary = record.summary ?? ticket.currentSummary ?? ticket.summary;
4061
+ return {
4062
+ index,
4063
+ ...ticketId ? { ticketId } : {},
4064
+ ...typeof title === "string" ? { title: truncateText(title, 200) } : {},
4065
+ ...typeof status === "string" ? { status } : {},
4066
+ ...typeof summary === "string" ? { summary: truncateText(summary, 600) } : {},
4067
+ ...typeof record.classification === "string" ? { classification: record.classification } : {},
4068
+ ...typeof record.reason === "string" ? { reason: truncateText(record.reason, 300) } : {},
4069
+ ...typeof record.score === "number" ? { score: record.score } : {},
4070
+ ...typeof record.confidence === "string" ? { confidence: record.confidence } : {}
4071
+ };
4072
+ }
4073
+ function compactIntakeResultForRouteSelection(intake) {
4074
+ if (!intake || typeof intake !== "object" || Array.isArray(intake)) {
4075
+ return null;
4076
+ }
4077
+ const record = intake;
4078
+ return {
4079
+ ...typeof record.id === "string" ? { id: record.id } : {},
4080
+ ...typeof record.status === "string" ? { status: record.status } : {},
4081
+ ...typeof record.classification === "string" ? { classification: record.classification } : {},
4082
+ ...typeof record.recommendedAction === "string" ? { recommendedAction: record.recommendedAction } : {},
4083
+ ...typeof record.attachedTicketId === "string" ? { attachedTicketId: record.attachedTicketId } : {},
4084
+ ...typeof record.convertedTicketId === "string" ? { convertedTicketId: record.convertedTicketId } : {},
4085
+ candidates: intakeCandidates(intake).map(compactIntakeCandidate)
4086
+ };
4087
+ }
3928
4088
  function checkpointPlanCompletionAllowed(event, summary) {
3929
4089
  const text = [
3930
4090
  summary,
@@ -4096,7 +4256,8 @@ function buildRoutePrompt(input) {
4096
4256
  "The model judges; the Cadence CLI validates and executes. Return JSON only. Do not call tools to mutate Cadence yourself.",
4097
4257
  "Decide where this agent session belongs before checkpoint memory is written.",
4098
4258
  "Do not include raw diffs, raw transcripts, terminal logs, tool streams, secrets, file contents, or model reasoning in server-bound fields.",
4099
- 'Return this sparse JSON shape: {"summary":"short routing summary","route":{"action":"current|noop|intake_create|intake_attach|switch_existing","confidence":"low|medium|high","reason":"short reason","request":"natural language intake request when intake is needed","targetTicketId":"optional existing ticket id"},"entries":[{"kind":"intent|decision|rationale|action|verification|blocker|correction|note","summary":"short entry summary","body":"safe Cadence work-log body"}],"files":[{"path":"relative/path.ts","kind":"added|modified|deleted|renamed|unknown"}]}',
4259
+ 'Return this sparse JSON shape: {"summary":"short routing summary","sessionTitle":{"text":"short GUI session title","confidence":"low|medium|high","reason":"why this title names the routed focus"},"route":{"action":"current|noop|intake_create|intake_attach|switch_existing","confidence":"low|medium|high","reason":"short reason","request":"natural language intake request when intake is needed","targetTicketId":"optional existing ticket id"},"entries":[{"kind":"intent|decision|rationale|action|verification|blocker|correction|note","summary":"short entry summary","body":"safe Cadence work-log body"}],"files":[{"path":"relative/path.ts","kind":"added|modified|deleted|renamed|unknown"}]}',
4260
+ "Generate sessionTitle only when the routed focus is durable enough to name a GUI row. Omit it for noop/filler. Keep it under 80 characters and do not reuse the latest checkpoint summary as the title.",
4100
4261
  "Use noop for filler, acknowledgements, setup chatter, or anything not durable enough for Cadence.",
4101
4262
  "Use current only when an existing Cadence context clearly fits the recent work.",
4102
4263
  "Use intake_create for a clearly durable new task without a good existing ticket.",
@@ -4121,6 +4282,35 @@ ${truncateText(input.changedFiles, 2000)}` : "Changed files: unavailable"
4121
4282
  ].filter(Boolean).join(`
4122
4283
  `);
4123
4284
  }
4285
+ function buildRouteCandidatePrompt(input) {
4286
+ const recentTurns = checkpointRecentTurns(input.event);
4287
+ const recentTurnsText = recentTurns.length ? formatCheckpointRecentTurns(recentTurns) : "";
4288
+ const intake = compactIntakeResultForRouteSelection(input.intakeResult);
4289
+ const contextText = input.savedContext?.ticketId ? `Saved Cadence context: ticket ${input.savedContext.ticketId}${input.savedContext.sessionId ? `, session ${input.savedContext.sessionId}` : ""}${input.savedContext.changesetId ? `, ChangeSet ${input.savedContext.changesetId}` : ""}` : input.currentContext?.ticketId ? `No saved agent-session context. Current active Cadence context: ticket ${input.currentContext.ticketId}${input.currentContext.sessionId ? `, session ${input.currentContext.sessionId}` : ""}${input.currentContext.changesetId ? `, ChangeSet ${input.currentContext.changesetId}` : ""}` : "No saved agent-session context and no active Cadence context was found.";
4290
+ return [
4291
+ "You are resolving Cadence agent-run routing after intake returned possible existing work.",
4292
+ "Intake is retrieval, not the final decision. The model judges; the Cadence CLI validates and executes. Return JSON only.",
4293
+ "Choose whether this agent session should create a new ticket, attach/switch to one concrete candidate ticket, keep the current context, or no-op.",
4294
+ "Use intake_create when the recent request is durable and the candidates are weak, adjacent, completed, or not concrete matches.",
4295
+ "Use intake_attach or switch_existing only when one candidate ticket is clearly the same work. You must include targetTicketId from the candidate list.",
4296
+ "Use current only when the saved/current Cadence context clearly fits the recent work.",
4297
+ "Use noop only for filler or if the work is not durable enough for Cadence.",
4298
+ "Do not include raw transcripts, raw diffs, terminal logs, tool streams, secrets, file contents, or model reasoning in server-bound fields.",
4299
+ 'Return this sparse JSON shape: {"summary":"short routing summary","sessionTitle":{"text":"short GUI session title","confidence":"low|medium|high","reason":"why this title names the selected routed focus"},"route":{"action":"current|noop|intake_create|intake_attach|switch_existing","confidence":"low|medium|high","reason":"short reason","request":"natural language intake request when creating","targetTicketId":"required for attach/switch"},"entries":[{"kind":"intent|decision|rationale|action|verification|blocker|correction|note","summary":"short entry summary","body":"safe Cadence work-log body"}],"files":[{"path":"relative/path.ts","kind":"added|modified|deleted|renamed|unknown"}]}',
4300
+ "The final candidate plan owns sessionTitle. Generate it only for the selected durable focus, keep it under 80 characters, and do not reuse the latest checkpoint summary as the title.",
4301
+ "Prefer preserving the initial plan's entries unless they are wrong for the selected ticket.",
4302
+ "",
4303
+ contextText,
4304
+ `Initial route plan:
4305
+ ${truncateText(JSON.stringify(input.initialPlan), 4000)}`,
4306
+ `Intake result and candidates:
4307
+ ${truncateText(JSON.stringify(intake), 6000)}`,
4308
+ recentTurnsText ? `Recent user/assistant turns (most recent 3, local routing context only):
4309
+ ${recentTurnsText}` : input.event.lastAssistantMessage ? `Last assistant message:
4310
+ ${truncateText(input.event.lastAssistantMessage, 3000)}` : "Recent turns: unavailable"
4311
+ ].filter(Boolean).join(`
4312
+ `);
4313
+ }
4124
4314
  function agentRunFingerprint(value) {
4125
4315
  return stableHash(JSON.stringify(value));
4126
4316
  }
@@ -4713,6 +4903,9 @@ async function runAgentRunIngestStop(parsed, options, config, meta) {
4713
4903
  try {
4714
4904
  const client = await createClient(config, options);
4715
4905
  cadenceContext = await readCurrentCadenceContext(client, projectId) ?? cadenceContext;
4906
+ if (!duplicateTurn) {
4907
+ cadenceContext = await renewAgentSessionLeaseBestEffort(client, projectId, cadenceContext);
4908
+ }
4716
4909
  } catch {}
4717
4910
  }
4718
4911
  const observedSession = {
@@ -5033,6 +5226,27 @@ async function runAgentRunIngestStop(parsed, options, config, meta) {
5033
5226
  function objectStringId(value) {
5034
5227
  return value && typeof value === "object" && "id" in value && typeof value.id === "string" ? value.id : undefined;
5035
5228
  }
5229
+ function objectStringTitle(value) {
5230
+ return value && typeof value === "object" && "title" in value && typeof value.title === "string" ? cleanAgentSessionTitleText(value.title) : undefined;
5231
+ }
5232
+ function routeActionAllowsDisplayTitle(action, plan) {
5233
+ return action === "routed" || action === "switched" || action === "current" && plan.route.confidence === "high";
5234
+ }
5235
+ function routeDisplayTitle(action, plan, selectedTicket) {
5236
+ if (!routeActionAllowsDisplayTitle(action, plan)) {
5237
+ return;
5238
+ }
5239
+ const fallbackTitle = objectStringTitle(selectedTicket);
5240
+ const text = plan.sessionTitle?.text ?? fallbackTitle;
5241
+ if (!text) {
5242
+ return;
5243
+ }
5244
+ return {
5245
+ text,
5246
+ confidence: plan.sessionTitle?.confidence ?? plan.route.confidence,
5247
+ reason: plan.sessionTitle?.reason ?? plan.route.reason ?? (fallbackTitle ? "Recent route selected this ticket." : undefined)
5248
+ };
5249
+ }
5036
5250
  async function runAgentRunRoute(parsed, options, config, meta) {
5037
5251
  const projectId = requireProjectId(config);
5038
5252
  const client = await createClient(config, options);
@@ -5097,37 +5311,41 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5097
5311
  }
5098
5312
  try {
5099
5313
  const codexCommand = parsed.options["codex-command"] ?? "codex";
5100
- const codexStartedAtMs = Date.now();
5101
5314
  const codexCwd = options.cwd ?? process.cwd();
5102
- const codexArgs = [
5103
- "exec",
5104
- ...checkpointSettings.model ? ["-m", checkpointSettings.model] : [],
5105
- "--disable",
5106
- "hooks",
5107
- "--sandbox",
5108
- "read-only",
5109
- "-C",
5110
- codexCwd,
5111
- prompt
5112
- ];
5113
- const codex = runLocalCommand(codexCommand, codexArgs, options, {
5114
- cwd: codexCwd,
5115
- env: {
5116
- [agentLoopSuppressEnv]: "1",
5117
- CADENCE_HOOK_SUPPRESS: "1"
5118
- },
5119
- timeoutMs: defaultCheckpointWorkerTimeoutMs
5120
- });
5121
- const codexSessionTranscript = readCodexSessionTranscript(findCodexSessionFileCreatedAfter(options, codexStartedAtMs, codexCwd));
5122
- const tokenAccounting = buildCheckpointTokenAccounting(event, prompt, codexSessionTranscript?.tokenUsage);
5123
- if (codex.status !== 0 || codex.error) {
5124
- throw new CliError("AGENT_RUN_ROUTE_FAILED", "Codex route generation failed.", {
5125
- status: codex.status,
5126
- stderr: truncateText(codex.stderr, 2000),
5127
- error: codex.error?.message
5315
+ const runRouteWorker = (workerPrompt, failureMessage) => {
5316
+ const codexStartedAtMs = Date.now();
5317
+ const codexArgs = [
5318
+ "exec",
5319
+ ...checkpointSettings.model ? ["-m", checkpointSettings.model] : [],
5320
+ "--disable",
5321
+ "hooks",
5322
+ "--sandbox",
5323
+ "read-only",
5324
+ "-C",
5325
+ codexCwd,
5326
+ workerPrompt
5327
+ ];
5328
+ const codex = runLocalCommand(codexCommand, codexArgs, options, {
5329
+ cwd: codexCwd,
5330
+ env: {
5331
+ [agentLoopSuppressEnv]: "1",
5332
+ CADENCE_HOOK_SUPPRESS: "1"
5333
+ },
5334
+ timeoutMs: defaultCheckpointWorkerTimeoutMs
5128
5335
  });
5129
- }
5130
- let routePlan = parseCheckpointPlanJson(codex.stdout, "intent");
5336
+ const codexSessionTranscript = readCodexSessionTranscript(findCodexSessionFileCreatedAfter(options, codexStartedAtMs, codexCwd));
5337
+ const tokenAccounting2 = buildCheckpointTokenAccounting(event, workerPrompt, codexSessionTranscript?.tokenUsage);
5338
+ if (codex.status !== 0 || codex.error) {
5339
+ throw new CliError("AGENT_RUN_ROUTE_FAILED", failureMessage, {
5340
+ status: codex.status,
5341
+ stderr: truncateText(codex.stderr, 2000),
5342
+ error: codex.error?.message
5343
+ });
5344
+ }
5345
+ return { codex, codexSessionTranscript, tokenAccounting: tokenAccounting2 };
5346
+ };
5347
+ const initialRouteWorker = runRouteWorker(prompt, "Codex route generation failed.");
5348
+ let routePlan = parseCheckpointPlanJson(initialRouteWorker.codex.stdout, "intent");
5131
5349
  const currentTicketId = savedContext?.ticketId ?? currentContext?.ticketId;
5132
5350
  const currentSessionId = savedContext?.sessionId ?? currentContext?.sessionId;
5133
5351
  const currentChangesetId = savedContext?.changesetId ?? currentContext?.changesetId;
@@ -5149,22 +5367,26 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5149
5367
  let targetTicketId = currentTicketId;
5150
5368
  let targetSessionId = currentSessionId;
5151
5369
  let targetChangesetId = currentChangesetId;
5370
+ let targetLeaseId = savedContext?.leaseId ?? currentContext?.leaseId;
5371
+ let targetLeaseExpiresAt = savedContext?.leaseExpiresAt ?? currentContext?.leaseExpiresAt;
5152
5372
  let intakeResult;
5153
5373
  let selectedTicket;
5154
5374
  let summary = routePlan.summary ?? routePlan.entries[0]?.summary ?? routePlan.route.reason ?? "Agent run route";
5375
+ let tokenAccounting = initialRouteWorker.tokenAccounting;
5155
5376
  const modelAudit = {
5156
5377
  provider: checkpointSettings.provider,
5157
5378
  command: codexCommand,
5158
5379
  ...checkpointSettings.model ? { model: checkpointSettings.model } : {},
5159
- status: codex.status,
5160
- stdout: codex.stdout.trim(),
5161
- stderr: truncateText(codex.stderr.trim(), 2000),
5380
+ status: initialRouteWorker.codex.status,
5381
+ stdout: initialRouteWorker.codex.stdout.trim(),
5382
+ stderr: truncateText(initialRouteWorker.codex.stderr.trim(), 2000),
5162
5383
  tokenAccounting,
5163
- ...codexSessionTranscript?.tokenUsage ? { tokenUsage: codexSessionTranscript.tokenUsage } : {},
5164
- ...codexSessionTranscript ? { sessionTranscript: codexSessionTranscript } : {}
5384
+ ...initialRouteWorker.codexSessionTranscript?.tokenUsage ? { tokenUsage: initialRouteWorker.codexSessionTranscript.tokenUsage } : {},
5385
+ ...initialRouteWorker.codexSessionTranscript ? { sessionTranscript: initialRouteWorker.codexSessionTranscript } : {}
5165
5386
  };
5166
5387
  const finishRoute = async (action, reason) => {
5167
5388
  const checkedAt = new Date().toISOString();
5389
+ const displayTitle = routeDisplayTitle(action, routePlan, selectedTicket);
5168
5390
  const auditFile = await writeAgentCheckpointAuditFile(parsed, options, {
5169
5391
  version: 1,
5170
5392
  mode: "route",
@@ -5176,12 +5398,15 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5176
5398
  ...targetTicketId ? { ticketId: targetTicketId } : {},
5177
5399
  ...targetSessionId ? { sessionId: targetSessionId } : {},
5178
5400
  ...targetChangesetId ? { changesetId: targetChangesetId } : {},
5401
+ ...targetLeaseId ? { leaseId: targetLeaseId } : {},
5402
+ ...targetLeaseExpiresAt ? { leaseExpiresAt: targetLeaseExpiresAt } : {},
5179
5403
  ...eventFile ? { eventFile } : {},
5180
5404
  event,
5181
5405
  prompt,
5182
5406
  model: modelAudit,
5183
5407
  checkpoint: routePlan,
5184
5408
  route: routePlan.route,
5409
+ ...displayTitle ? { sessionTitle: displayTitle } : {},
5185
5410
  intakeResult,
5186
5411
  selectedTicket,
5187
5412
  lifecycleOperations,
@@ -5203,11 +5428,20 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5203
5428
  ticketId: targetTicketId,
5204
5429
  ...targetSessionId ? { sessionId: targetSessionId } : {},
5205
5430
  ...targetChangesetId ? { changesetId: targetChangesetId } : {},
5431
+ ...targetLeaseId ? { leaseId: targetLeaseId } : {},
5432
+ ...targetLeaseExpiresAt ? { leaseExpiresAt: targetLeaseExpiresAt } : {},
5206
5433
  capturedAt: checkedAt
5207
5434
  }
5208
5435
  } : {},
5209
5436
  lastCheckpointAt: checkedAt,
5210
5437
  lastCheckpointMode: "checkpoint",
5438
+ ...displayTitle ? {
5439
+ displayTitle: displayTitle.text,
5440
+ displayTitleUpdatedAt: checkedAt,
5441
+ displayTitleSource: "route",
5442
+ displayTitleConfidence: displayTitle.confidence,
5443
+ ...displayTitle.reason ? { displayTitleReason: displayTitle.reason } : {}
5444
+ } : {},
5211
5445
  lastCheckpointAuditFile: auditFile
5212
5446
  };
5213
5447
  await writeAgentLoopState(parsed, options, {
@@ -5223,6 +5457,7 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5223
5457
  ...reason ? { reason } : {},
5224
5458
  ...targetTicketId ? { ticketId: targetTicketId } : {},
5225
5459
  summary,
5460
+ ...displayTitle ? { sessionTitle: displayTitle } : {},
5226
5461
  entryCount: routePlan.entries.length,
5227
5462
  mode: "route",
5228
5463
  auditFile,
@@ -5253,7 +5488,39 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5253
5488
  }
5254
5489
  });
5255
5490
  lifecycleOperations.push(checkpointLifecycleOperation("intake.created", true, { request: intakeRequest, result: intakeResult }));
5256
- if (checkpointHasConflictingIntakeResult(intakeResult, routePlan)) {
5491
+ if (intakeCandidates(intakeResult).length > 0) {
5492
+ const candidatePrompt = buildRouteCandidatePrompt({
5493
+ event,
5494
+ initialPlan: routePlan,
5495
+ intakeResult,
5496
+ ...savedContext ? { savedContext } : {},
5497
+ ...currentContext ? { currentContext } : {}
5498
+ });
5499
+ const candidateWorker = runRouteWorker(candidatePrompt, "Codex route candidate selection failed.");
5500
+ const candidatePlan = parseCheckpointPlanJson(candidateWorker.codex.stdout, "intent");
5501
+ routePlan = planWithCandidateSelection(routePlan, candidatePlan, currentTicketId, intakeCandidateTicketIds(intakeResult));
5502
+ summary = routePlan.summary ?? routePlan.entries[0]?.summary ?? routePlan.route.reason ?? summary;
5503
+ tokenAccounting = candidateWorker.tokenAccounting;
5504
+ modelAudit.candidateSelection = {
5505
+ prompt: candidatePrompt,
5506
+ status: candidateWorker.codex.status,
5507
+ stdout: candidateWorker.codex.stdout.trim(),
5508
+ stderr: truncateText(candidateWorker.codex.stderr.trim(), 2000),
5509
+ tokenAccounting: candidateWorker.tokenAccounting,
5510
+ ...candidateWorker.codexSessionTranscript?.tokenUsage ? { tokenUsage: candidateWorker.codexSessionTranscript.tokenUsage } : {},
5511
+ ...candidateWorker.codexSessionTranscript ? { sessionTranscript: candidateWorker.codexSessionTranscript } : {}
5512
+ };
5513
+ if (routePlan.route.action === "noop") {
5514
+ return await finishRoute("noop", routePlan.route.reason ?? routePlan.summary ?? "Candidate routing produced no durable route.");
5515
+ }
5516
+ if (routePlan.route.action === "current") {
5517
+ return await finishRoute("current", routePlan.route.reason ?? "Candidate routing kept current Cadence context.");
5518
+ }
5519
+ if (!routePlanAllowsLifecycle(routePlan)) {
5520
+ routePlan = safeAutomaticRoutePlan(routePlan, Boolean(currentTicketId), routePlan.route.reason ?? "Candidate routing did not request an executable automatic action.");
5521
+ return await finishRoute(routePlan.route.action, routePlan.route.reason);
5522
+ }
5523
+ } else if (checkpointHasConflictingIntakeResult(intakeResult, routePlan)) {
5257
5524
  routePlan = safeAutomaticRoutePlan(routePlan, Boolean(currentTicketId), "Intake returned conflicting duplicate, overlap, or completed-before candidates.");
5258
5525
  return await finishRoute(routePlan.route.action, routePlan.route.reason);
5259
5526
  }
@@ -5330,6 +5597,8 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5330
5597
  ...commandMetadata()
5331
5598
  }
5332
5599
  });
5600
+ targetLeaseId = objectStringId(lease);
5601
+ targetLeaseExpiresAt = leaseExpiresAtFromResponse(lease);
5333
5602
  lifecycleOperations.push(checkpointLifecycleOperation("lease.claimed", true, { ticketId: targetTicketId, sessionId: targetSessionId, lease }));
5334
5603
  }
5335
5604
  if (targetSessionId) {
@@ -5351,7 +5620,6 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5351
5620
  lifecycleOperations.push(checkpointLifecycleOperation("changeset.linked", false, { error: error instanceof Error ? error.message : String(error) }));
5352
5621
  }
5353
5622
  }
5354
- const routeMetadata = checkpointWorkLogMetadata(checkpointSettings, "route", tokenAccounting);
5355
5623
  for (const entry of routePlan.entries) {
5356
5624
  const logEntry = {
5357
5625
  entryKind: entry.kind,
@@ -5359,7 +5627,6 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5359
5627
  summary: entry.summary ?? checkpointEntrySummary(entry.body),
5360
5628
  ...targetSessionId ? { sessionId: targetSessionId } : {},
5361
5629
  ...targetChangesetId ? { changesetId: targetChangesetId } : {},
5362
- metadata: routeMetadata,
5363
5630
  ...commandMetadata()
5364
5631
  };
5365
5632
  await client.tickets.log({
@@ -5421,6 +5688,8 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5421
5688
  const ticketId = parsed.options.ticket ?? savedContext?.ticketId ?? currentContext?.ticketId;
5422
5689
  const sessionId = parsed.options.session ?? savedContext?.sessionId ?? currentContext?.sessionId;
5423
5690
  const changesetId = parsed.options.changeset ?? savedContext?.changesetId ?? currentContext?.changesetId;
5691
+ const leaseId = savedContext?.leaseId ?? currentContext?.leaseId;
5692
+ const leaseExpiresAtValue = savedContext?.leaseExpiresAt ?? currentContext?.leaseExpiresAt;
5424
5693
  if (!ticketId) {
5425
5694
  await writeAgentLoopState(parsed, options, {
5426
5695
  ...state,
@@ -5581,7 +5850,6 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5581
5850
  }
5582
5851
  try {
5583
5852
  const codexCommand = parsed.options["codex-command"] ?? "codex";
5584
- const codexStartedAtMs = Date.now();
5585
5853
  const codexCwd = options.cwd ?? process.cwd();
5586
5854
  const codexArgs = [
5587
5855
  "exec",
@@ -5594,37 +5862,172 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5594
5862
  codexCwd,
5595
5863
  prompt
5596
5864
  ];
5597
- const codex = runLocalCommand(codexCommand, codexArgs, options, {
5598
- cwd: codexCwd,
5599
- env: {
5600
- [agentLoopSuppressEnv]: "1",
5601
- CADENCE_HOOK_SUPPRESS: "1"
5602
- },
5603
- timeoutMs: defaultCheckpointWorkerTimeoutMs
5604
- });
5605
- const codexSessionTranscript = readCodexSessionTranscript(findCodexSessionFileCreatedAfter(options, codexStartedAtMs, codexCwd));
5606
- const tokenAccounting = buildCheckpointTokenAccounting(event, prompt, codexSessionTranscript?.tokenUsage);
5607
- if (codex.status !== 0 || codex.error) {
5608
- throw new CliError("AGENT_RUN_CHECKPOINT_FAILED", "Codex checkpoint generation failed.", {
5609
- status: codex.status,
5610
- stderr: truncateText(codex.stderr, 2000),
5611
- error: codex.error?.message
5865
+ const generationAttempts = [];
5866
+ let codex = null;
5867
+ let codexSessionTranscript = null;
5868
+ let tokenAccounting = promptTokenAccounting;
5869
+ let checkpoint = null;
5870
+ let reductionResult = null;
5871
+ let lastFailureReason = "Codex checkpoint generation failed.";
5872
+ for (let attempt = 1;attempt <= defaultCheckpointWorkerMaxAttempts; attempt += 1) {
5873
+ const codexStartedAtMs = Date.now();
5874
+ codex = runLocalCommand(codexCommand, codexArgs, options, {
5875
+ cwd: codexCwd,
5876
+ env: {
5877
+ [agentLoopSuppressEnv]: "1",
5878
+ CADENCE_HOOK_SUPPRESS: "1"
5879
+ },
5880
+ timeoutMs: defaultCheckpointWorkerTimeoutMs
5612
5881
  });
5882
+ codexSessionTranscript = readCodexSessionTranscript(findCodexSessionFileCreatedAfter(options, codexStartedAtMs, codexCwd));
5883
+ tokenAccounting = buildCheckpointTokenAccounting(event, prompt, codexSessionTranscript?.tokenUsage);
5884
+ if (codex.status !== 0 || codex.error) {
5885
+ lastFailureReason = codex.error?.message ?? `Codex exited with status ${codex.status ?? "unknown"}.`;
5886
+ generationAttempts.push({
5887
+ attempt,
5888
+ success: false,
5889
+ status: codex.status,
5890
+ stderr: truncateText(codex.stderr.trim(), 2000),
5891
+ ...codex.error ? { error: codex.error.message } : {}
5892
+ });
5893
+ continue;
5894
+ }
5895
+ try {
5896
+ let parsedCheckpoint = parseCheckpointPlanJson(codex.stdout, logKind);
5897
+ if (mode === "closeout") {
5898
+ parsedCheckpoint = applyCloseoutSessionDefaults(parsedCheckpoint, closeoutSessionAction, completeTicket);
5899
+ }
5900
+ const parsedReductionResult = reduceCheckpointEntries(parsedCheckpoint.entries, mode);
5901
+ checkpoint = {
5902
+ ...parsedCheckpoint,
5903
+ entries: parsedReductionResult.entries
5904
+ };
5905
+ reductionResult = parsedReductionResult;
5906
+ generationAttempts.push({
5907
+ attempt,
5908
+ success: true,
5909
+ status: codex.status
5910
+ });
5911
+ break;
5912
+ } catch (error) {
5913
+ if (error instanceof CliError && error.code === "AGENT_RUN_CHECKPOINT_INVALID_PLAN") {
5914
+ throw error;
5915
+ }
5916
+ lastFailureReason = error instanceof Error ? error.message : String(error);
5917
+ generationAttempts.push({
5918
+ attempt,
5919
+ success: false,
5920
+ status: codex.status,
5921
+ error: lastFailureReason,
5922
+ stdout: truncateText(codex.stdout.trim(), 2000),
5923
+ stderr: truncateText(codex.stderr.trim(), 2000)
5924
+ });
5925
+ }
5613
5926
  }
5614
- let checkpoint = parseCheckpointPlanJson(codex.stdout, logKind);
5615
- if (mode === "closeout") {
5616
- checkpoint = applyCloseoutSessionDefaults(checkpoint, closeoutSessionAction, completeTicket);
5927
+ if (!codex || !checkpoint || !reductionResult) {
5928
+ const failedCodex = codex ?? { status: null, stdout: "", stderr: "" };
5929
+ const checkedAt = new Date().toISOString();
5930
+ const summary2 = `Checkpoint worker failed after ${generationAttempts.length} ${generationAttempts.length === 1 ? "attempt" : "attempts"}.`;
5931
+ const route = {
5932
+ action: "failed",
5933
+ confidence: "high",
5934
+ reason: lastFailureReason
5935
+ };
5936
+ const auditFile = await writeAgentCheckpointAuditFile(parsed, options, {
5937
+ version: 1,
5938
+ action: "failed",
5939
+ reason: lastFailureReason,
5940
+ createdAt: checkedAt,
5941
+ localOnly: true,
5942
+ localOnlyReason: "Raw hook context, checkpoint prompts, and model output stay on this machine; failed checkpoint attempts are recorded locally for UI visibility and retry diagnosis.",
5943
+ agentSessionKey: agentSessionKeyValue,
5944
+ ticketId,
5945
+ ...sessionId ? { sessionId } : {},
5946
+ ...changesetId ? { changesetId } : {},
5947
+ ...eventFile ? { eventFile } : {},
5948
+ event,
5949
+ prompt,
5950
+ model: {
5951
+ provider: checkpointSettings.provider,
5952
+ command: codexCommand,
5953
+ ...checkpointSettings.model ? { model: checkpointSettings.model } : {},
5954
+ status: failedCodex.status,
5955
+ stderr: truncateText(failedCodex.stderr.trim(), 2000),
5956
+ stdout: truncateText(failedCodex.stdout.trim(), 2000),
5957
+ ...failedCodex.error ? { error: failedCodex.error.message } : {},
5958
+ tokenAccounting,
5959
+ attempts: generationAttempts,
5960
+ ...codexSessionTranscript?.tokenUsage ? { tokenUsage: codexSessionTranscript.tokenUsage } : {},
5961
+ ...codexSessionTranscript ? { sessionTranscript: codexSessionTranscript } : {}
5962
+ },
5963
+ checkpointSettings,
5964
+ route,
5965
+ mode,
5966
+ fingerprints,
5967
+ lifecycleOperations: [],
5968
+ cadenceWrites: [],
5969
+ summary: summary2,
5970
+ entryCount: 0,
5971
+ needsHuman: true
5972
+ });
5973
+ await writeAgentLoopState(parsed, options, {
5974
+ ...state,
5975
+ sessions: {
5976
+ ...state.sessions,
5977
+ [agentSessionKeyValue]: {
5978
+ ...sessionState,
5979
+ stopCount: 0,
5980
+ threshold: defaultCheckpointThresholdValue(),
5981
+ previousCheckpointSummary: summary2,
5982
+ lastAction: "failed",
5983
+ lastReason: lastFailureReason,
5984
+ ...eventFile ? { lastEventFile: eventFile } : {},
5985
+ cadenceContext: {
5986
+ ticketId,
5987
+ ...sessionId ? { sessionId } : {},
5988
+ ...changesetId ? { changesetId } : {},
5989
+ ...leaseId ? { leaseId } : {},
5990
+ ...leaseExpiresAtValue ? { leaseExpiresAt: leaseExpiresAtValue } : {},
5991
+ capturedAt: checkedAt
5992
+ },
5993
+ lastCheckpointAt: checkedAt,
5994
+ lastCheckpointMode: mode,
5995
+ lastCheckpointFingerprints: fingerprints,
5996
+ lastCheckpointAuditFile: auditFile
5997
+ }
5998
+ }
5999
+ });
6000
+ const data = {
6001
+ action: "failed",
6002
+ reason: lastFailureReason,
6003
+ ticketId,
6004
+ summary: summary2,
6005
+ entryCount: 0,
6006
+ mode,
6007
+ auditFile,
6008
+ agentSessionKey: agentSessionKeyValue,
6009
+ attempts: generationAttempts.length,
6010
+ ...sessionId ? { sessionId } : {},
6011
+ ...changesetId ? { changesetId } : {},
6012
+ needsHuman: true
6013
+ };
6014
+ return {
6015
+ stdout: parsed.flags.json ? formatJson(successEnvelope(data, meta)) : `${JSON.stringify(data, null, 2)}
6016
+ `,
6017
+ stderr: "",
6018
+ exitCode: 0
6019
+ };
5617
6020
  }
5618
- const reductionResult = reduceCheckpointEntries(checkpoint.entries, mode);
5619
- checkpoint = {
5620
- ...checkpoint,
5621
- entries: reductionResult.entries
5622
- };
5623
- const coverage = buildAgentRunCoverage(checkpoint, mode);
5624
- let summary = checkpoint.summary ?? checkpoint.entries[0]?.summary ?? checkpoint.route.reason ?? "Agent run checkpoint";
6021
+ const completedCodex = codex;
6022
+ let checkpointPlan = checkpoint;
6023
+ const checkpointReductionResult = reductionResult;
6024
+ const coverage = buildAgentRunCoverage(checkpointPlan, mode);
6025
+ let summary = checkpointPlan.summary ?? checkpointPlan.entries[0]?.summary ?? checkpointPlan.route.reason ?? "Agent run checkpoint";
5625
6026
  let targetTicketId = ticketId;
5626
6027
  let targetSessionId = sessionId;
5627
6028
  let targetChangesetId = changesetId;
6029
+ let targetLeaseId = leaseId;
6030
+ let targetLeaseExpiresAt = leaseExpiresAtValue;
5628
6031
  const cadenceWrites = [];
5629
6032
  const lifecycleOperations = [];
5630
6033
  let intakeResult;
@@ -5633,10 +6036,11 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5633
6036
  provider: checkpointSettings.provider,
5634
6037
  command: codexCommand,
5635
6038
  ...checkpointSettings.model ? { model: checkpointSettings.model } : {},
5636
- status: codex.status,
5637
- stdout: codex.stdout.trim(),
5638
- stderr: truncateText(codex.stderr.trim(), 2000),
6039
+ status: completedCodex.status,
6040
+ stdout: completedCodex.stdout.trim(),
6041
+ stderr: truncateText(completedCodex.stderr.trim(), 2000),
5639
6042
  tokenAccounting,
6043
+ attempts: generationAttempts,
5640
6044
  ...codexSessionTranscript?.tokenUsage ? { tokenUsage: codexSessionTranscript.tokenUsage } : {},
5641
6045
  ...codexSessionTranscript ? { sessionTranscript: codexSessionTranscript } : {}
5642
6046
  };
@@ -5652,15 +6056,17 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5652
6056
  ticketId: targetTicketId,
5653
6057
  ...targetSessionId ? { sessionId: targetSessionId } : {},
5654
6058
  ...targetChangesetId ? { changesetId: targetChangesetId } : {},
6059
+ ...targetLeaseId ? { leaseId: targetLeaseId } : {},
6060
+ ...targetLeaseExpiresAt ? { leaseExpiresAt: targetLeaseExpiresAt } : {},
5655
6061
  ...eventFile ? { eventFile } : {},
5656
6062
  event,
5657
6063
  prompt,
5658
6064
  model: modelAudit,
5659
- checkpoint,
5660
- route: checkpoint.route,
6065
+ checkpoint: checkpointPlan,
6066
+ route: checkpointPlan.route,
5661
6067
  mode,
5662
6068
  fingerprints,
5663
- reduction: reductionResult.reduction,
6069
+ reduction: checkpointReductionResult.reduction,
5664
6070
  ...mode === "closeout" ? { coverage } : {},
5665
6071
  ...intakeResult ? { intakeResult } : {},
5666
6072
  ...selectedTicket ? { selectedTicket } : {},
@@ -5668,7 +6074,7 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5668
6074
  cadenceWrites,
5669
6075
  summary,
5670
6076
  ...reason ? { reason } : {},
5671
- entryCount: checkpoint.entries.length,
6077
+ entryCount: checkpointPlan.entries.length,
5672
6078
  needsHuman: action === "needs_human"
5673
6079
  });
5674
6080
  await writeAgentLoopState(parsed, options, {
@@ -5687,6 +6093,8 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5687
6093
  ticketId: targetTicketId,
5688
6094
  ...targetSessionId ? { sessionId: targetSessionId } : {},
5689
6095
  ...targetChangesetId ? { changesetId: targetChangesetId } : {},
6096
+ ...targetLeaseId ? { leaseId: targetLeaseId } : {},
6097
+ ...targetLeaseExpiresAt ? { leaseExpiresAt: targetLeaseExpiresAt } : {},
5690
6098
  capturedAt: checkedAt
5691
6099
  },
5692
6100
  lastCheckpointAt: checkedAt,
@@ -5699,11 +6107,11 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5699
6107
  });
5700
6108
  const data = {
5701
6109
  action,
5702
- route: checkpoint.route,
6110
+ route: checkpointPlan.route,
5703
6111
  ...reason ? { reason } : {},
5704
6112
  ticketId: targetTicketId,
5705
6113
  summary,
5706
- entryCount: checkpoint.entries.length,
6114
+ entryCount: checkpointPlan.entries.length,
5707
6115
  mode,
5708
6116
  auditFile,
5709
6117
  agentSessionKey: agentSessionKeyValue,
@@ -5718,31 +6126,31 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5718
6126
  exitCode: 0
5719
6127
  };
5720
6128
  };
5721
- if (checkpoint.session?.action === "complete_ticket" && mode === "closeout" && !completeTicket) {
5722
- checkpoint = checkpointPlanNeedsHuman(checkpoint, "Closeout ticket completion requires --complete-ticket true.");
6129
+ if (checkpointPlan.session?.action === "complete_ticket" && mode === "closeout" && !completeTicket) {
6130
+ checkpointPlan = checkpointPlanNeedsHuman(checkpointPlan, "Closeout ticket completion requires --complete-ticket true.");
5723
6131
  }
5724
- if (checkpoint.session?.action === "complete_ticket" && !checkpointPlanCompletionAllowed(event, summary)) {
5725
- checkpoint = checkpointPlanNeedsHuman(checkpoint, "Completion requires explicit recent completion, push, PR, merge, clean branch, or verification intent.");
6132
+ if (checkpointPlan.session?.action === "complete_ticket" && !checkpointPlanCompletionAllowed(event, summary)) {
6133
+ checkpointPlan = checkpointPlanNeedsHuman(checkpointPlan, "Completion requires explicit recent completion, push, PR, merge, clean branch, or verification intent.");
5726
6134
  }
5727
- if (checkpoint.route.action === "noop") {
5728
- return await finishCheckpoint("noop", checkpoint.route.reason ?? checkpoint.summary ?? "Nothing durable to record.");
6135
+ if (checkpointPlan.route.action === "noop") {
6136
+ return await finishCheckpoint("noop", checkpointPlan.route.reason ?? checkpointPlan.summary ?? "Nothing durable to record.");
5729
6137
  }
5730
- if (checkpoint.route.action === "needs_human") {
5731
- checkpoint = checkpointPlanWithRoute(checkpoint, {
6138
+ if (checkpointPlan.route.action === "needs_human") {
6139
+ checkpointPlan = checkpointPlanWithRoute(checkpointPlan, {
5732
6140
  action: "noop",
5733
- confidence: checkpoint.route.confidence,
5734
- reason: checkpoint.route.reason ?? "Checkpoint routing was uncertain."
6141
+ confidence: checkpointPlan.route.confidence,
6142
+ reason: checkpointPlan.route.reason ?? "Checkpoint routing was uncertain."
5735
6143
  });
5736
- return await finishCheckpoint("noop", checkpoint.route.reason);
6144
+ return await finishCheckpoint("noop", checkpointPlan.route.reason);
5737
6145
  }
5738
- if (checkpointRouteRequiresIntake(checkpoint.route.action)) {
5739
- if (checkpoint.route.confidence !== "high") {
5740
- checkpoint = checkpointPlanWithRoute(checkpoint, {
6146
+ if (checkpointRouteRequiresIntake(checkpointPlan.route.action)) {
6147
+ if (checkpointPlan.route.confidence !== "high") {
6148
+ checkpointPlan = checkpointPlanWithRoute(checkpointPlan, {
5741
6149
  action: "noop",
5742
- confidence: checkpoint.route.confidence,
6150
+ confidence: checkpointPlan.route.confidence,
5743
6151
  reason: "Checkpoint reroute requires high confidence."
5744
6152
  });
5745
- return await finishCheckpoint("noop", checkpoint.route.reason);
6153
+ return await finishCheckpoint("noop", checkpointPlan.route.reason);
5746
6154
  }
5747
6155
  const routeArgs = [
5748
6156
  "agent-run",
@@ -5755,12 +6163,11 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5755
6163
  ...parsed.flags.project ? ["--project", parsed.flags.project] : [],
5756
6164
  ...parsed.flags.server ? ["--server", parsed.flags.server] : []
5757
6165
  ];
5758
- const rerouteResult = await finishCheckpoint("reroute", checkpoint.route.reason ?? "Checkpoint delegated routing to agent-run route.");
6166
+ const rerouteResult = await finishCheckpoint("reroute", checkpointPlan.route.reason ?? "Checkpoint delegated routing to agent-run route.");
5759
6167
  await spawnAgentRunWorker(routeArgs, options);
5760
6168
  return rerouteResult;
5761
6169
  }
5762
- const checkpointMetadata = checkpointWorkLogMetadata(checkpointSettings, mode, tokenAccounting);
5763
- for (const entry of checkpoint.entries) {
6170
+ for (const entry of checkpointPlan.entries) {
5764
6171
  const logEntry = {
5765
6172
  entryKind: entry.kind,
5766
6173
  body: entry.body,
@@ -5769,7 +6176,6 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5769
6176
  ...entry.parentEntryId ? { parentEntryId: entry.parentEntryId } : {},
5770
6177
  ...targetSessionId ? { sessionId: targetSessionId } : {},
5771
6178
  ...targetChangesetId ? { changesetId: targetChangesetId } : {},
5772
- metadata: checkpointMetadata,
5773
6179
  ...commandMetadata()
5774
6180
  };
5775
6181
  try {
@@ -5817,9 +6223,9 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5817
6223
  });
5818
6224
  }
5819
6225
  }
5820
- if (checkpoint.files.length && targetSessionId) {
6226
+ if (checkpointPlan.files.length && targetSessionId) {
5821
6227
  const filesByKind = new Map;
5822
- for (const file of checkpoint.files) {
6228
+ for (const file of checkpointPlan.files) {
5823
6229
  filesByKind.set(file.kind, [...filesByKind.get(file.kind) ?? [], file.path]);
5824
6230
  }
5825
6231
  for (const [kind, paths] of filesByKind) {
@@ -5839,7 +6245,7 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5839
6245
  lifecycleOperations.push(checkpointLifecycleOperation("session.files_attached", true, { sessionId: targetSessionId, kind, files: paths }));
5840
6246
  }
5841
6247
  }
5842
- if ((updateSummary || checkpoint.summaryUpdate?.update) && checkpoint.summaryUpdate?.value) {
6248
+ if ((updateSummary || checkpointPlan.summaryUpdate?.update) && checkpointPlan.summaryUpdate?.value) {
5843
6249
  const ticket = await client.tickets.get({ projectId, ticketId: targetTicketId });
5844
6250
  if (typeof ticket.projectionVersion === "number") {
5845
6251
  await client.tickets.update({
@@ -5847,7 +6253,7 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5847
6253
  ticketId: targetTicketId,
5848
6254
  ifVersion: ticket.projectionVersion,
5849
6255
  ticket: {
5850
- currentSummary: checkpoint.summaryUpdate.value,
6256
+ currentSummary: checkpointPlan.summaryUpdate.value,
5851
6257
  ...commandMetadata()
5852
6258
  }
5853
6259
  });
@@ -5857,36 +6263,36 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5857
6263
  ticketId: targetTicketId,
5858
6264
  ifVersion: ticket.projectionVersion,
5859
6265
  ticket: {
5860
- currentSummary: checkpoint.summaryUpdate.value
6266
+ currentSummary: checkpointPlan.summaryUpdate.value
5861
6267
  }
5862
6268
  });
5863
6269
  }
5864
6270
  }
5865
- if (checkpoint.session?.action === "handoff" || checkpoint.session?.action === "end" || checkpoint.session?.action === "complete_ticket") {
6271
+ if (checkpointPlan.session?.action === "handoff" || checkpointPlan.session?.action === "end" || checkpointPlan.session?.action === "complete_ticket") {
5866
6272
  if (targetSessionId) {
5867
6273
  await client.sessions.end({
5868
6274
  projectId,
5869
6275
  sessionId: targetSessionId,
5870
6276
  session: {
5871
- summary: checkpoint.session.summary ?? summary,
6277
+ summary: checkpointPlan.session.summary ?? summary,
5872
6278
  ...commandMetadata()
5873
6279
  }
5874
6280
  });
5875
- lifecycleOperations.push(checkpointLifecycleOperation("session.ended", true, { sessionId: targetSessionId, action: checkpoint.session.action }));
6281
+ lifecycleOperations.push(checkpointLifecycleOperation("session.ended", true, { sessionId: targetSessionId, action: checkpointPlan.session.action }));
5876
6282
  }
5877
6283
  }
5878
- if (checkpoint.session?.action === "complete_ticket") {
6284
+ if (checkpointPlan.session?.action === "complete_ticket") {
5879
6285
  const latestTicket = await client.tickets.get({ projectId, ticketId: targetTicketId });
5880
6286
  if (typeof latestTicket.projectionVersion !== "number") {
5881
- checkpoint = checkpointPlanNeedsHuman(checkpoint, "Ticket completion requires a latest ticket projection version.");
5882
- return await finishCheckpoint("needs_human", checkpoint.route.reason);
6287
+ checkpointPlan = checkpointPlanNeedsHuman(checkpointPlan, "Ticket completion requires a latest ticket projection version.");
6288
+ return await finishCheckpoint("needs_human", checkpointPlan.route.reason);
5883
6289
  }
5884
6290
  await client.tickets.complete({
5885
6291
  projectId,
5886
6292
  ticketId: targetTicketId,
5887
6293
  ifVersion: latestTicket.projectionVersion,
5888
6294
  completion: {
5889
- currentSummary: checkpoint.session.summary ?? checkpoint.summaryUpdate?.value ?? summary,
6295
+ currentSummary: checkpointPlan.session.summary ?? checkpointPlan.summaryUpdate?.value ?? summary,
5890
6296
  ...commandMetadata()
5891
6297
  }
5892
6298
  });
@@ -5895,7 +6301,7 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5895
6301
  success: true,
5896
6302
  ticketId: targetTicketId,
5897
6303
  ifVersion: latestTicket.projectionVersion,
5898
- summary: checkpoint.session.summary ?? checkpoint.summaryUpdate?.value ?? summary
6304
+ summary: checkpointPlan.session.summary ?? checkpointPlan.summaryUpdate?.value ?? summary
5899
6305
  });
5900
6306
  }
5901
6307
  return await finishCheckpoint("checkpointed");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trycadence/cli",
3
- "version": "0.1.16-dev.0",
3
+ "version": "0.1.19-dev.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {