@trycadence/cli 0.1.16-dev.0 → 0.1.18-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 +464 -118
  2. package/package.json +1 -1
package/dist/cadence CHANGED
@@ -1520,7 +1520,7 @@ import { createInterface } from "readline/promises";
1520
1520
  // package.json
1521
1521
  var package_default = {
1522
1522
  name: "@trycadence/cli",
1523
- version: "0.1.16-dev.0",
1523
+ version: "0.1.18-dev.0",
1524
1524
  private: false,
1525
1525
  type: "module",
1526
1526
  bin: {
@@ -1560,8 +1560,9 @@ var defaultLeaseTtlSeconds = 15 * 60;
1560
1560
  var defaultCliApiBaseUrl = "https://cadenceapi.deploy.lvl8studios.com";
1561
1561
  var defaultCliWebBaseUrl = "https://cadence.deploy.lvl8studios.com";
1562
1562
  var defaultCheckpointThreshold = 3;
1563
- var defaultCheckpointCooldownSeconds = 10 * 60;
1563
+ var defaultCheckpointCooldownSeconds = 0;
1564
1564
  var defaultCheckpointWorkerTimeoutMs = 10 * 60 * 1000;
1565
+ var defaultCheckpointWorkerMaxAttempts = 3;
1565
1566
  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
1567
  var agentLoopSuppressEnv = "CADENCE_AGENT_EVENT_SUPPRESS";
1567
1568
  var credentialRefreshSkewMs = 60 * 1000;
@@ -3090,16 +3091,6 @@ function normalizeGenericAgentEvent(input, base) {
3090
3091
  function agentSessionKey(source, agentSessionId) {
3091
3092
  return `${source}:${stableHash(agentSessionId)}`;
3092
3093
  }
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
3094
  function defaultAgentLoopState() {
3104
3095
  return {
3105
3096
  version: 2,
@@ -3258,6 +3249,11 @@ function readAgentLoopSessions(rawSessions) {
3258
3249
  ...typeof record.lastCheckpointMode === "string" && agentRunMemoryModes.includes(record.lastCheckpointMode) ? { lastCheckpointMode: record.lastCheckpointMode } : {},
3259
3250
  ...readAgentRunCoverage(record.lastCheckpointCoverage),
3260
3251
  ...typeof record.previousCheckpointSummary === "string" ? { previousCheckpointSummary: record.previousCheckpointSummary } : {},
3252
+ ...typeof record.displayTitle === "string" ? { displayTitle: record.displayTitle } : {},
3253
+ ...typeof record.displayTitleUpdatedAt === "string" ? { displayTitleUpdatedAt: record.displayTitleUpdatedAt } : {},
3254
+ ...record.displayTitleSource === "route" ? { displayTitleSource: "route" } : {},
3255
+ ...typeof record.displayTitleConfidence === "string" && checkpointConfidenceLevels.includes(record.displayTitleConfidence) ? { displayTitleConfidence: record.displayTitleConfidence } : {},
3256
+ ...typeof record.displayTitleReason === "string" ? { displayTitleReason: record.displayTitleReason } : {},
3261
3257
  ...typeof record.lastCheckpointAuditFile === "string" ? { lastCheckpointAuditFile: record.lastCheckpointAuditFile } : {}
3262
3258
  };
3263
3259
  }
@@ -3561,6 +3557,9 @@ function fingerprintForCheckpoint(event, options) {
3561
3557
  }));
3562
3558
  }
3563
3559
  function shouldSkipForCooldown(state, cooldownSeconds) {
3560
+ if (cooldownSeconds <= 0) {
3561
+ return false;
3562
+ }
3564
3563
  if (!state.lastCheckpointAt) {
3565
3564
  return false;
3566
3565
  }
@@ -3788,6 +3787,43 @@ function parseCheckpointPlanSession(value) {
3788
3787
  ...reason ? { reason } : {}
3789
3788
  };
3790
3789
  }
3790
+ function cleanAgentSessionTitleText(value) {
3791
+ const cleaned = value.replace(/\s+/g, " ").trim();
3792
+ if (!cleaned) {
3793
+ return;
3794
+ }
3795
+ const normalized = cleaned.toLowerCase();
3796
+ const genericTitles = new Set(["agent session", "session", "work session", "cadence session", "current session"]);
3797
+ if (genericTitles.has(normalized)) {
3798
+ return;
3799
+ }
3800
+ return cleaned.length > 80 ? cleaned.slice(0, 77).trimEnd() + "..." : cleaned;
3801
+ }
3802
+ function agentSessionTitleString(record, key) {
3803
+ const value = record[key];
3804
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
3805
+ }
3806
+ function parseAgentSessionTitle(value) {
3807
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
3808
+ return;
3809
+ }
3810
+ const record = value;
3811
+ const text = agentSessionTitleString(record, "text") ?? agentSessionTitleString(record, "title") ?? agentSessionTitleString(record, "label");
3812
+ const cleanedText = text ? cleanAgentSessionTitleText(text) : undefined;
3813
+ if (!cleanedText) {
3814
+ return;
3815
+ }
3816
+ const confidenceValue = agentSessionTitleString(record, "confidence") ?? "medium";
3817
+ if (!checkpointConfidenceLevels.includes(confidenceValue)) {
3818
+ return;
3819
+ }
3820
+ const reason = agentSessionTitleString(record, "reason")?.replace(/\s+/g, " ").trim();
3821
+ return {
3822
+ text: cleanedText,
3823
+ confidence: confidenceValue,
3824
+ ...reason ? { reason: reason.length > 300 ? `${reason.slice(0, 297).trimEnd()}...` : reason } : {}
3825
+ };
3826
+ }
3791
3827
  function parseCheckpointPlanFiles(value) {
3792
3828
  if (!Array.isArray(value)) {
3793
3829
  return [];
@@ -3845,6 +3881,33 @@ function safeAutomaticRoutePlan(plan, hasCurrentContext, reason) {
3845
3881
  function routePlanAllowsLifecycle(plan) {
3846
3882
  return checkpointRouteRequiresIntake(plan.route.action) && plan.route.confidence === "high";
3847
3883
  }
3884
+ function planWithCandidateSelection(original, selected, currentTicketId, candidateTicketIds) {
3885
+ let nextPlan = selected;
3886
+ if (nextPlan.route.action === "needs_human") {
3887
+ nextPlan = safeAutomaticRoutePlan(nextPlan, Boolean(currentTicketId), nextPlan.route.reason ?? "Candidate routing was uncertain.");
3888
+ } else if (checkpointRouteRequiresIntake(nextPlan.route.action) && nextPlan.route.confidence !== "high") {
3889
+ nextPlan = safeAutomaticRoutePlan(nextPlan, Boolean(currentTicketId), "Candidate routing requires high confidence.");
3890
+ } else if ((nextPlan.route.action === "intake_attach" || nextPlan.route.action === "switch_existing") && !nextPlan.route.targetTicketId) {
3891
+ nextPlan = safeAutomaticRoutePlan(nextPlan, Boolean(currentTicketId), "Candidate routing to existing work requires a target ticket id.");
3892
+ } else if ((nextPlan.route.action === "intake_attach" || nextPlan.route.action === "switch_existing") && nextPlan.route.targetTicketId && !candidateTicketIds.has(nextPlan.route.targetTicketId)) {
3893
+ nextPlan = safeAutomaticRoutePlan(nextPlan, Boolean(currentTicketId), "Candidate routing selected a ticket outside the intake candidate list.");
3894
+ } else if (nextPlan.route.action === "current" && !currentTicketId) {
3895
+ nextPlan = checkpointPlanWithRoute(nextPlan, {
3896
+ action: "noop",
3897
+ confidence: nextPlan.route.confidence,
3898
+ reason: nextPlan.route.reason ?? "No current Cadence context exists."
3899
+ });
3900
+ }
3901
+ const summary = nextPlan.summary ?? original.summary;
3902
+ return {
3903
+ ...nextPlan,
3904
+ ...summary ? { summary } : {},
3905
+ ...nextPlan.sessionTitle ?? original.sessionTitle ? { sessionTitle: nextPlan.sessionTitle ?? original.sessionTitle } : {},
3906
+ entries: nextPlan.entries.length ? nextPlan.entries : original.entries,
3907
+ files: nextPlan.files.length ? nextPlan.files : original.files,
3908
+ validationWarnings: [...original.validationWarnings, ...nextPlan.validationWarnings]
3909
+ };
3910
+ }
3848
3911
  function parseCheckpointPlanRecord(record, rawText, fallbackKind) {
3849
3912
  if (!("route" in record)) {
3850
3913
  const legacy = parseCheckpointRecord(record, rawText, fallbackKind);
@@ -3868,12 +3931,14 @@ function parseCheckpointPlanRecord(record, rawText, fallbackKind) {
3868
3931
  const entries = Array.isArray(record.entries) ? record.entries.map(parseCheckpointPlanEntry) : [];
3869
3932
  const summary = checkpointPlanString(record, "summary");
3870
3933
  const summaryUpdate = parseCheckpointPlanSummaryUpdate(record.summaryUpdate ?? record.currentSummary);
3934
+ const sessionTitle = parseAgentSessionTitle(record.sessionTitle ?? record.displayTitle);
3871
3935
  const session = parseCheckpointPlanSession(record.session);
3872
3936
  return normalizeCheckpointPlan({
3873
3937
  ...summary ? { summary } : {},
3874
3938
  route,
3875
3939
  entries,
3876
3940
  ...summaryUpdate ? { summaryUpdate } : {},
3941
+ ...sessionTitle ? { sessionTitle } : {},
3877
3942
  ...session ? { session } : {},
3878
3943
  files: parseCheckpointPlanFiles(record.files),
3879
3944
  legacy: false,
@@ -3925,6 +3990,62 @@ function parseCheckpointPlanJson(raw, fallbackKind) {
3925
3990
  function checkpointRouteRequiresIntake(action) {
3926
3991
  return action === "intake_create" || action === "intake_attach" || action === "switch_existing";
3927
3992
  }
3993
+ function intakeCandidates(intake) {
3994
+ if (!intake || typeof intake !== "object" || Array.isArray(intake)) {
3995
+ return [];
3996
+ }
3997
+ const candidates = intake.candidates;
3998
+ return Array.isArray(candidates) ? candidates.filter((candidate) => candidate && typeof candidate === "object" && !Array.isArray(candidate)) : [];
3999
+ }
4000
+ function intakeCandidateTicketId(candidate) {
4001
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
4002
+ return;
4003
+ }
4004
+ const record = candidate;
4005
+ const nestedTicket = record.ticket && typeof record.ticket === "object" && !Array.isArray(record.ticket) ? record.ticket : null;
4006
+ const value = record.ticketId ?? nestedTicket?.id ?? record.id;
4007
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
4008
+ }
4009
+ function intakeCandidateTicketIds(intake) {
4010
+ return new Set(intakeCandidates(intake).map(intakeCandidateTicketId).filter((id) => Boolean(id)));
4011
+ }
4012
+ function compactIntakeCandidate(candidate, index) {
4013
+ if (!candidate || typeof candidate !== "object" || Array.isArray(candidate)) {
4014
+ return { index };
4015
+ }
4016
+ const record = candidate;
4017
+ const ticket = record.ticket && typeof record.ticket === "object" && !Array.isArray(record.ticket) ? record.ticket : {};
4018
+ const ticketId = intakeCandidateTicketId(candidate);
4019
+ const title = record.title ?? ticket.title;
4020
+ const status = record.status ?? ticket.status;
4021
+ const summary = record.summary ?? ticket.currentSummary ?? ticket.summary;
4022
+ return {
4023
+ index,
4024
+ ...ticketId ? { ticketId } : {},
4025
+ ...typeof title === "string" ? { title: truncateText(title, 200) } : {},
4026
+ ...typeof status === "string" ? { status } : {},
4027
+ ...typeof summary === "string" ? { summary: truncateText(summary, 600) } : {},
4028
+ ...typeof record.classification === "string" ? { classification: record.classification } : {},
4029
+ ...typeof record.reason === "string" ? { reason: truncateText(record.reason, 300) } : {},
4030
+ ...typeof record.score === "number" ? { score: record.score } : {},
4031
+ ...typeof record.confidence === "string" ? { confidence: record.confidence } : {}
4032
+ };
4033
+ }
4034
+ function compactIntakeResultForRouteSelection(intake) {
4035
+ if (!intake || typeof intake !== "object" || Array.isArray(intake)) {
4036
+ return null;
4037
+ }
4038
+ const record = intake;
4039
+ return {
4040
+ ...typeof record.id === "string" ? { id: record.id } : {},
4041
+ ...typeof record.status === "string" ? { status: record.status } : {},
4042
+ ...typeof record.classification === "string" ? { classification: record.classification } : {},
4043
+ ...typeof record.recommendedAction === "string" ? { recommendedAction: record.recommendedAction } : {},
4044
+ ...typeof record.attachedTicketId === "string" ? { attachedTicketId: record.attachedTicketId } : {},
4045
+ ...typeof record.convertedTicketId === "string" ? { convertedTicketId: record.convertedTicketId } : {},
4046
+ candidates: intakeCandidates(intake).map(compactIntakeCandidate)
4047
+ };
4048
+ }
3928
4049
  function checkpointPlanCompletionAllowed(event, summary) {
3929
4050
  const text = [
3930
4051
  summary,
@@ -4096,7 +4217,8 @@ function buildRoutePrompt(input) {
4096
4217
  "The model judges; the Cadence CLI validates and executes. Return JSON only. Do not call tools to mutate Cadence yourself.",
4097
4218
  "Decide where this agent session belongs before checkpoint memory is written.",
4098
4219
  "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"}]}',
4220
+ '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"}]}',
4221
+ "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
4222
  "Use noop for filler, acknowledgements, setup chatter, or anything not durable enough for Cadence.",
4101
4223
  "Use current only when an existing Cadence context clearly fits the recent work.",
4102
4224
  "Use intake_create for a clearly durable new task without a good existing ticket.",
@@ -4121,6 +4243,35 @@ ${truncateText(input.changedFiles, 2000)}` : "Changed files: unavailable"
4121
4243
  ].filter(Boolean).join(`
4122
4244
  `);
4123
4245
  }
4246
+ function buildRouteCandidatePrompt(input) {
4247
+ const recentTurns = checkpointRecentTurns(input.event);
4248
+ const recentTurnsText = recentTurns.length ? formatCheckpointRecentTurns(recentTurns) : "";
4249
+ const intake = compactIntakeResultForRouteSelection(input.intakeResult);
4250
+ 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.";
4251
+ return [
4252
+ "You are resolving Cadence agent-run routing after intake returned possible existing work.",
4253
+ "Intake is retrieval, not the final decision. The model judges; the Cadence CLI validates and executes. Return JSON only.",
4254
+ "Choose whether this agent session should create a new ticket, attach/switch to one concrete candidate ticket, keep the current context, or no-op.",
4255
+ "Use intake_create when the recent request is durable and the candidates are weak, adjacent, completed, or not concrete matches.",
4256
+ "Use intake_attach or switch_existing only when one candidate ticket is clearly the same work. You must include targetTicketId from the candidate list.",
4257
+ "Use current only when the saved/current Cadence context clearly fits the recent work.",
4258
+ "Use noop only for filler or if the work is not durable enough for Cadence.",
4259
+ "Do not include raw transcripts, raw diffs, terminal logs, tool streams, secrets, file contents, or model reasoning in server-bound fields.",
4260
+ '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"}]}',
4261
+ "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.",
4262
+ "Prefer preserving the initial plan's entries unless they are wrong for the selected ticket.",
4263
+ "",
4264
+ contextText,
4265
+ `Initial route plan:
4266
+ ${truncateText(JSON.stringify(input.initialPlan), 4000)}`,
4267
+ `Intake result and candidates:
4268
+ ${truncateText(JSON.stringify(intake), 6000)}`,
4269
+ recentTurnsText ? `Recent user/assistant turns (most recent 3, local routing context only):
4270
+ ${recentTurnsText}` : input.event.lastAssistantMessage ? `Last assistant message:
4271
+ ${truncateText(input.event.lastAssistantMessage, 3000)}` : "Recent turns: unavailable"
4272
+ ].filter(Boolean).join(`
4273
+ `);
4274
+ }
4124
4275
  function agentRunFingerprint(value) {
4125
4276
  return stableHash(JSON.stringify(value));
4126
4277
  }
@@ -5033,6 +5184,27 @@ async function runAgentRunIngestStop(parsed, options, config, meta) {
5033
5184
  function objectStringId(value) {
5034
5185
  return value && typeof value === "object" && "id" in value && typeof value.id === "string" ? value.id : undefined;
5035
5186
  }
5187
+ function objectStringTitle(value) {
5188
+ return value && typeof value === "object" && "title" in value && typeof value.title === "string" ? cleanAgentSessionTitleText(value.title) : undefined;
5189
+ }
5190
+ function routeActionAllowsDisplayTitle(action, plan) {
5191
+ return action === "routed" || action === "switched" || action === "current" && plan.route.confidence === "high";
5192
+ }
5193
+ function routeDisplayTitle(action, plan, selectedTicket) {
5194
+ if (!routeActionAllowsDisplayTitle(action, plan)) {
5195
+ return;
5196
+ }
5197
+ const fallbackTitle = objectStringTitle(selectedTicket);
5198
+ const text = plan.sessionTitle?.text ?? fallbackTitle;
5199
+ if (!text) {
5200
+ return;
5201
+ }
5202
+ return {
5203
+ text,
5204
+ confidence: plan.sessionTitle?.confidence ?? plan.route.confidence,
5205
+ reason: plan.sessionTitle?.reason ?? plan.route.reason ?? (fallbackTitle ? "Recent route selected this ticket." : undefined)
5206
+ };
5207
+ }
5036
5208
  async function runAgentRunRoute(parsed, options, config, meta) {
5037
5209
  const projectId = requireProjectId(config);
5038
5210
  const client = await createClient(config, options);
@@ -5097,37 +5269,41 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5097
5269
  }
5098
5270
  try {
5099
5271
  const codexCommand = parsed.options["codex-command"] ?? "codex";
5100
- const codexStartedAtMs = Date.now();
5101
5272
  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
5273
+ const runRouteWorker = (workerPrompt, failureMessage) => {
5274
+ const codexStartedAtMs = Date.now();
5275
+ const codexArgs = [
5276
+ "exec",
5277
+ ...checkpointSettings.model ? ["-m", checkpointSettings.model] : [],
5278
+ "--disable",
5279
+ "hooks",
5280
+ "--sandbox",
5281
+ "read-only",
5282
+ "-C",
5283
+ codexCwd,
5284
+ workerPrompt
5285
+ ];
5286
+ const codex = runLocalCommand(codexCommand, codexArgs, options, {
5287
+ cwd: codexCwd,
5288
+ env: {
5289
+ [agentLoopSuppressEnv]: "1",
5290
+ CADENCE_HOOK_SUPPRESS: "1"
5291
+ },
5292
+ timeoutMs: defaultCheckpointWorkerTimeoutMs
5128
5293
  });
5129
- }
5130
- let routePlan = parseCheckpointPlanJson(codex.stdout, "intent");
5294
+ const codexSessionTranscript = readCodexSessionTranscript(findCodexSessionFileCreatedAfter(options, codexStartedAtMs, codexCwd));
5295
+ const tokenAccounting2 = buildCheckpointTokenAccounting(event, workerPrompt, codexSessionTranscript?.tokenUsage);
5296
+ if (codex.status !== 0 || codex.error) {
5297
+ throw new CliError("AGENT_RUN_ROUTE_FAILED", failureMessage, {
5298
+ status: codex.status,
5299
+ stderr: truncateText(codex.stderr, 2000),
5300
+ error: codex.error?.message
5301
+ });
5302
+ }
5303
+ return { codex, codexSessionTranscript, tokenAccounting: tokenAccounting2 };
5304
+ };
5305
+ const initialRouteWorker = runRouteWorker(prompt, "Codex route generation failed.");
5306
+ let routePlan = parseCheckpointPlanJson(initialRouteWorker.codex.stdout, "intent");
5131
5307
  const currentTicketId = savedContext?.ticketId ?? currentContext?.ticketId;
5132
5308
  const currentSessionId = savedContext?.sessionId ?? currentContext?.sessionId;
5133
5309
  const currentChangesetId = savedContext?.changesetId ?? currentContext?.changesetId;
@@ -5152,19 +5328,21 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5152
5328
  let intakeResult;
5153
5329
  let selectedTicket;
5154
5330
  let summary = routePlan.summary ?? routePlan.entries[0]?.summary ?? routePlan.route.reason ?? "Agent run route";
5331
+ let tokenAccounting = initialRouteWorker.tokenAccounting;
5155
5332
  const modelAudit = {
5156
5333
  provider: checkpointSettings.provider,
5157
5334
  command: codexCommand,
5158
5335
  ...checkpointSettings.model ? { model: checkpointSettings.model } : {},
5159
- status: codex.status,
5160
- stdout: codex.stdout.trim(),
5161
- stderr: truncateText(codex.stderr.trim(), 2000),
5336
+ status: initialRouteWorker.codex.status,
5337
+ stdout: initialRouteWorker.codex.stdout.trim(),
5338
+ stderr: truncateText(initialRouteWorker.codex.stderr.trim(), 2000),
5162
5339
  tokenAccounting,
5163
- ...codexSessionTranscript?.tokenUsage ? { tokenUsage: codexSessionTranscript.tokenUsage } : {},
5164
- ...codexSessionTranscript ? { sessionTranscript: codexSessionTranscript } : {}
5340
+ ...initialRouteWorker.codexSessionTranscript?.tokenUsage ? { tokenUsage: initialRouteWorker.codexSessionTranscript.tokenUsage } : {},
5341
+ ...initialRouteWorker.codexSessionTranscript ? { sessionTranscript: initialRouteWorker.codexSessionTranscript } : {}
5165
5342
  };
5166
5343
  const finishRoute = async (action, reason) => {
5167
5344
  const checkedAt = new Date().toISOString();
5345
+ const displayTitle = routeDisplayTitle(action, routePlan, selectedTicket);
5168
5346
  const auditFile = await writeAgentCheckpointAuditFile(parsed, options, {
5169
5347
  version: 1,
5170
5348
  mode: "route",
@@ -5182,6 +5360,7 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5182
5360
  model: modelAudit,
5183
5361
  checkpoint: routePlan,
5184
5362
  route: routePlan.route,
5363
+ ...displayTitle ? { sessionTitle: displayTitle } : {},
5185
5364
  intakeResult,
5186
5365
  selectedTicket,
5187
5366
  lifecycleOperations,
@@ -5208,6 +5387,13 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5208
5387
  } : {},
5209
5388
  lastCheckpointAt: checkedAt,
5210
5389
  lastCheckpointMode: "checkpoint",
5390
+ ...displayTitle ? {
5391
+ displayTitle: displayTitle.text,
5392
+ displayTitleUpdatedAt: checkedAt,
5393
+ displayTitleSource: "route",
5394
+ displayTitleConfidence: displayTitle.confidence,
5395
+ ...displayTitle.reason ? { displayTitleReason: displayTitle.reason } : {}
5396
+ } : {},
5211
5397
  lastCheckpointAuditFile: auditFile
5212
5398
  };
5213
5399
  await writeAgentLoopState(parsed, options, {
@@ -5223,6 +5409,7 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5223
5409
  ...reason ? { reason } : {},
5224
5410
  ...targetTicketId ? { ticketId: targetTicketId } : {},
5225
5411
  summary,
5412
+ ...displayTitle ? { sessionTitle: displayTitle } : {},
5226
5413
  entryCount: routePlan.entries.length,
5227
5414
  mode: "route",
5228
5415
  auditFile,
@@ -5253,7 +5440,39 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5253
5440
  }
5254
5441
  });
5255
5442
  lifecycleOperations.push(checkpointLifecycleOperation("intake.created", true, { request: intakeRequest, result: intakeResult }));
5256
- if (checkpointHasConflictingIntakeResult(intakeResult, routePlan)) {
5443
+ if (intakeCandidates(intakeResult).length > 0) {
5444
+ const candidatePrompt = buildRouteCandidatePrompt({
5445
+ event,
5446
+ initialPlan: routePlan,
5447
+ intakeResult,
5448
+ ...savedContext ? { savedContext } : {},
5449
+ ...currentContext ? { currentContext } : {}
5450
+ });
5451
+ const candidateWorker = runRouteWorker(candidatePrompt, "Codex route candidate selection failed.");
5452
+ const candidatePlan = parseCheckpointPlanJson(candidateWorker.codex.stdout, "intent");
5453
+ routePlan = planWithCandidateSelection(routePlan, candidatePlan, currentTicketId, intakeCandidateTicketIds(intakeResult));
5454
+ summary = routePlan.summary ?? routePlan.entries[0]?.summary ?? routePlan.route.reason ?? summary;
5455
+ tokenAccounting = candidateWorker.tokenAccounting;
5456
+ modelAudit.candidateSelection = {
5457
+ prompt: candidatePrompt,
5458
+ status: candidateWorker.codex.status,
5459
+ stdout: candidateWorker.codex.stdout.trim(),
5460
+ stderr: truncateText(candidateWorker.codex.stderr.trim(), 2000),
5461
+ tokenAccounting: candidateWorker.tokenAccounting,
5462
+ ...candidateWorker.codexSessionTranscript?.tokenUsage ? { tokenUsage: candidateWorker.codexSessionTranscript.tokenUsage } : {},
5463
+ ...candidateWorker.codexSessionTranscript ? { sessionTranscript: candidateWorker.codexSessionTranscript } : {}
5464
+ };
5465
+ if (routePlan.route.action === "noop") {
5466
+ return await finishRoute("noop", routePlan.route.reason ?? routePlan.summary ?? "Candidate routing produced no durable route.");
5467
+ }
5468
+ if (routePlan.route.action === "current") {
5469
+ return await finishRoute("current", routePlan.route.reason ?? "Candidate routing kept current Cadence context.");
5470
+ }
5471
+ if (!routePlanAllowsLifecycle(routePlan)) {
5472
+ routePlan = safeAutomaticRoutePlan(routePlan, Boolean(currentTicketId), routePlan.route.reason ?? "Candidate routing did not request an executable automatic action.");
5473
+ return await finishRoute(routePlan.route.action, routePlan.route.reason);
5474
+ }
5475
+ } else if (checkpointHasConflictingIntakeResult(intakeResult, routePlan)) {
5257
5476
  routePlan = safeAutomaticRoutePlan(routePlan, Boolean(currentTicketId), "Intake returned conflicting duplicate, overlap, or completed-before candidates.");
5258
5477
  return await finishRoute(routePlan.route.action, routePlan.route.reason);
5259
5478
  }
@@ -5351,7 +5570,6 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5351
5570
  lifecycleOperations.push(checkpointLifecycleOperation("changeset.linked", false, { error: error instanceof Error ? error.message : String(error) }));
5352
5571
  }
5353
5572
  }
5354
- const routeMetadata = checkpointWorkLogMetadata(checkpointSettings, "route", tokenAccounting);
5355
5573
  for (const entry of routePlan.entries) {
5356
5574
  const logEntry = {
5357
5575
  entryKind: entry.kind,
@@ -5359,7 +5577,6 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5359
5577
  summary: entry.summary ?? checkpointEntrySummary(entry.body),
5360
5578
  ...targetSessionId ? { sessionId: targetSessionId } : {},
5361
5579
  ...targetChangesetId ? { changesetId: targetChangesetId } : {},
5362
- metadata: routeMetadata,
5363
5580
  ...commandMetadata()
5364
5581
  };
5365
5582
  await client.tickets.log({
@@ -5581,7 +5798,6 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5581
5798
  }
5582
5799
  try {
5583
5800
  const codexCommand = parsed.options["codex-command"] ?? "codex";
5584
- const codexStartedAtMs = Date.now();
5585
5801
  const codexCwd = options.cwd ?? process.cwd();
5586
5802
  const codexArgs = [
5587
5803
  "exec",
@@ -5594,34 +5810,165 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5594
5810
  codexCwd,
5595
5811
  prompt
5596
5812
  ];
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
5813
+ const generationAttempts = [];
5814
+ let codex = null;
5815
+ let codexSessionTranscript = null;
5816
+ let tokenAccounting = promptTokenAccounting;
5817
+ let checkpoint = null;
5818
+ let reductionResult = null;
5819
+ let lastFailureReason = "Codex checkpoint generation failed.";
5820
+ for (let attempt = 1;attempt <= defaultCheckpointWorkerMaxAttempts; attempt += 1) {
5821
+ const codexStartedAtMs = Date.now();
5822
+ codex = runLocalCommand(codexCommand, codexArgs, options, {
5823
+ cwd: codexCwd,
5824
+ env: {
5825
+ [agentLoopSuppressEnv]: "1",
5826
+ CADENCE_HOOK_SUPPRESS: "1"
5827
+ },
5828
+ timeoutMs: defaultCheckpointWorkerTimeoutMs
5612
5829
  });
5830
+ codexSessionTranscript = readCodexSessionTranscript(findCodexSessionFileCreatedAfter(options, codexStartedAtMs, codexCwd));
5831
+ tokenAccounting = buildCheckpointTokenAccounting(event, prompt, codexSessionTranscript?.tokenUsage);
5832
+ if (codex.status !== 0 || codex.error) {
5833
+ lastFailureReason = codex.error?.message ?? `Codex exited with status ${codex.status ?? "unknown"}.`;
5834
+ generationAttempts.push({
5835
+ attempt,
5836
+ success: false,
5837
+ status: codex.status,
5838
+ stderr: truncateText(codex.stderr.trim(), 2000),
5839
+ ...codex.error ? { error: codex.error.message } : {}
5840
+ });
5841
+ continue;
5842
+ }
5843
+ try {
5844
+ let parsedCheckpoint = parseCheckpointPlanJson(codex.stdout, logKind);
5845
+ if (mode === "closeout") {
5846
+ parsedCheckpoint = applyCloseoutSessionDefaults(parsedCheckpoint, closeoutSessionAction, completeTicket);
5847
+ }
5848
+ const parsedReductionResult = reduceCheckpointEntries(parsedCheckpoint.entries, mode);
5849
+ checkpoint = {
5850
+ ...parsedCheckpoint,
5851
+ entries: parsedReductionResult.entries
5852
+ };
5853
+ reductionResult = parsedReductionResult;
5854
+ generationAttempts.push({
5855
+ attempt,
5856
+ success: true,
5857
+ status: codex.status
5858
+ });
5859
+ break;
5860
+ } catch (error) {
5861
+ if (error instanceof CliError && error.code === "AGENT_RUN_CHECKPOINT_INVALID_PLAN") {
5862
+ throw error;
5863
+ }
5864
+ lastFailureReason = error instanceof Error ? error.message : String(error);
5865
+ generationAttempts.push({
5866
+ attempt,
5867
+ success: false,
5868
+ status: codex.status,
5869
+ error: lastFailureReason,
5870
+ stdout: truncateText(codex.stdout.trim(), 2000),
5871
+ stderr: truncateText(codex.stderr.trim(), 2000)
5872
+ });
5873
+ }
5613
5874
  }
5614
- let checkpoint = parseCheckpointPlanJson(codex.stdout, logKind);
5615
- if (mode === "closeout") {
5616
- checkpoint = applyCloseoutSessionDefaults(checkpoint, closeoutSessionAction, completeTicket);
5875
+ if (!codex || !checkpoint || !reductionResult) {
5876
+ const failedCodex = codex ?? { status: null, stdout: "", stderr: "" };
5877
+ const checkedAt = new Date().toISOString();
5878
+ const summary2 = `Checkpoint worker failed after ${generationAttempts.length} ${generationAttempts.length === 1 ? "attempt" : "attempts"}.`;
5879
+ const route = {
5880
+ action: "failed",
5881
+ confidence: "high",
5882
+ reason: lastFailureReason
5883
+ };
5884
+ const auditFile = await writeAgentCheckpointAuditFile(parsed, options, {
5885
+ version: 1,
5886
+ action: "failed",
5887
+ reason: lastFailureReason,
5888
+ createdAt: checkedAt,
5889
+ localOnly: true,
5890
+ 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.",
5891
+ agentSessionKey: agentSessionKeyValue,
5892
+ ticketId,
5893
+ ...sessionId ? { sessionId } : {},
5894
+ ...changesetId ? { changesetId } : {},
5895
+ ...eventFile ? { eventFile } : {},
5896
+ event,
5897
+ prompt,
5898
+ model: {
5899
+ provider: checkpointSettings.provider,
5900
+ command: codexCommand,
5901
+ ...checkpointSettings.model ? { model: checkpointSettings.model } : {},
5902
+ status: failedCodex.status,
5903
+ stderr: truncateText(failedCodex.stderr.trim(), 2000),
5904
+ stdout: truncateText(failedCodex.stdout.trim(), 2000),
5905
+ ...failedCodex.error ? { error: failedCodex.error.message } : {},
5906
+ tokenAccounting,
5907
+ attempts: generationAttempts,
5908
+ ...codexSessionTranscript?.tokenUsage ? { tokenUsage: codexSessionTranscript.tokenUsage } : {},
5909
+ ...codexSessionTranscript ? { sessionTranscript: codexSessionTranscript } : {}
5910
+ },
5911
+ checkpointSettings,
5912
+ route,
5913
+ mode,
5914
+ fingerprints,
5915
+ lifecycleOperations: [],
5916
+ cadenceWrites: [],
5917
+ summary: summary2,
5918
+ entryCount: 0,
5919
+ needsHuman: true
5920
+ });
5921
+ await writeAgentLoopState(parsed, options, {
5922
+ ...state,
5923
+ sessions: {
5924
+ ...state.sessions,
5925
+ [agentSessionKeyValue]: {
5926
+ ...sessionState,
5927
+ stopCount: 0,
5928
+ threshold: defaultCheckpointThresholdValue(),
5929
+ previousCheckpointSummary: summary2,
5930
+ lastAction: "failed",
5931
+ lastReason: lastFailureReason,
5932
+ ...eventFile ? { lastEventFile: eventFile } : {},
5933
+ cadenceContext: {
5934
+ ticketId,
5935
+ ...sessionId ? { sessionId } : {},
5936
+ ...changesetId ? { changesetId } : {},
5937
+ capturedAt: checkedAt
5938
+ },
5939
+ lastCheckpointAt: checkedAt,
5940
+ lastCheckpointMode: mode,
5941
+ lastCheckpointFingerprints: fingerprints,
5942
+ lastCheckpointAuditFile: auditFile
5943
+ }
5944
+ }
5945
+ });
5946
+ const data = {
5947
+ action: "failed",
5948
+ reason: lastFailureReason,
5949
+ ticketId,
5950
+ summary: summary2,
5951
+ entryCount: 0,
5952
+ mode,
5953
+ auditFile,
5954
+ agentSessionKey: agentSessionKeyValue,
5955
+ attempts: generationAttempts.length,
5956
+ ...sessionId ? { sessionId } : {},
5957
+ ...changesetId ? { changesetId } : {},
5958
+ needsHuman: true
5959
+ };
5960
+ return {
5961
+ stdout: parsed.flags.json ? formatJson(successEnvelope(data, meta)) : `${JSON.stringify(data, null, 2)}
5962
+ `,
5963
+ stderr: "",
5964
+ exitCode: 0
5965
+ };
5617
5966
  }
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";
5967
+ const completedCodex = codex;
5968
+ let checkpointPlan = checkpoint;
5969
+ const checkpointReductionResult = reductionResult;
5970
+ const coverage = buildAgentRunCoverage(checkpointPlan, mode);
5971
+ let summary = checkpointPlan.summary ?? checkpointPlan.entries[0]?.summary ?? checkpointPlan.route.reason ?? "Agent run checkpoint";
5625
5972
  let targetTicketId = ticketId;
5626
5973
  let targetSessionId = sessionId;
5627
5974
  let targetChangesetId = changesetId;
@@ -5633,10 +5980,11 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5633
5980
  provider: checkpointSettings.provider,
5634
5981
  command: codexCommand,
5635
5982
  ...checkpointSettings.model ? { model: checkpointSettings.model } : {},
5636
- status: codex.status,
5637
- stdout: codex.stdout.trim(),
5638
- stderr: truncateText(codex.stderr.trim(), 2000),
5983
+ status: completedCodex.status,
5984
+ stdout: completedCodex.stdout.trim(),
5985
+ stderr: truncateText(completedCodex.stderr.trim(), 2000),
5639
5986
  tokenAccounting,
5987
+ attempts: generationAttempts,
5640
5988
  ...codexSessionTranscript?.tokenUsage ? { tokenUsage: codexSessionTranscript.tokenUsage } : {},
5641
5989
  ...codexSessionTranscript ? { sessionTranscript: codexSessionTranscript } : {}
5642
5990
  };
@@ -5656,11 +6004,11 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5656
6004
  event,
5657
6005
  prompt,
5658
6006
  model: modelAudit,
5659
- checkpoint,
5660
- route: checkpoint.route,
6007
+ checkpoint: checkpointPlan,
6008
+ route: checkpointPlan.route,
5661
6009
  mode,
5662
6010
  fingerprints,
5663
- reduction: reductionResult.reduction,
6011
+ reduction: checkpointReductionResult.reduction,
5664
6012
  ...mode === "closeout" ? { coverage } : {},
5665
6013
  ...intakeResult ? { intakeResult } : {},
5666
6014
  ...selectedTicket ? { selectedTicket } : {},
@@ -5668,7 +6016,7 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5668
6016
  cadenceWrites,
5669
6017
  summary,
5670
6018
  ...reason ? { reason } : {},
5671
- entryCount: checkpoint.entries.length,
6019
+ entryCount: checkpointPlan.entries.length,
5672
6020
  needsHuman: action === "needs_human"
5673
6021
  });
5674
6022
  await writeAgentLoopState(parsed, options, {
@@ -5699,11 +6047,11 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5699
6047
  });
5700
6048
  const data = {
5701
6049
  action,
5702
- route: checkpoint.route,
6050
+ route: checkpointPlan.route,
5703
6051
  ...reason ? { reason } : {},
5704
6052
  ticketId: targetTicketId,
5705
6053
  summary,
5706
- entryCount: checkpoint.entries.length,
6054
+ entryCount: checkpointPlan.entries.length,
5707
6055
  mode,
5708
6056
  auditFile,
5709
6057
  agentSessionKey: agentSessionKeyValue,
@@ -5718,31 +6066,31 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5718
6066
  exitCode: 0
5719
6067
  };
5720
6068
  };
5721
- if (checkpoint.session?.action === "complete_ticket" && mode === "closeout" && !completeTicket) {
5722
- checkpoint = checkpointPlanNeedsHuman(checkpoint, "Closeout ticket completion requires --complete-ticket true.");
6069
+ if (checkpointPlan.session?.action === "complete_ticket" && mode === "closeout" && !completeTicket) {
6070
+ checkpointPlan = checkpointPlanNeedsHuman(checkpointPlan, "Closeout ticket completion requires --complete-ticket true.");
5723
6071
  }
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.");
6072
+ if (checkpointPlan.session?.action === "complete_ticket" && !checkpointPlanCompletionAllowed(event, summary)) {
6073
+ checkpointPlan = checkpointPlanNeedsHuman(checkpointPlan, "Completion requires explicit recent completion, push, PR, merge, clean branch, or verification intent.");
5726
6074
  }
5727
- if (checkpoint.route.action === "noop") {
5728
- return await finishCheckpoint("noop", checkpoint.route.reason ?? checkpoint.summary ?? "Nothing durable to record.");
6075
+ if (checkpointPlan.route.action === "noop") {
6076
+ return await finishCheckpoint("noop", checkpointPlan.route.reason ?? checkpointPlan.summary ?? "Nothing durable to record.");
5729
6077
  }
5730
- if (checkpoint.route.action === "needs_human") {
5731
- checkpoint = checkpointPlanWithRoute(checkpoint, {
6078
+ if (checkpointPlan.route.action === "needs_human") {
6079
+ checkpointPlan = checkpointPlanWithRoute(checkpointPlan, {
5732
6080
  action: "noop",
5733
- confidence: checkpoint.route.confidence,
5734
- reason: checkpoint.route.reason ?? "Checkpoint routing was uncertain."
6081
+ confidence: checkpointPlan.route.confidence,
6082
+ reason: checkpointPlan.route.reason ?? "Checkpoint routing was uncertain."
5735
6083
  });
5736
- return await finishCheckpoint("noop", checkpoint.route.reason);
6084
+ return await finishCheckpoint("noop", checkpointPlan.route.reason);
5737
6085
  }
5738
- if (checkpointRouteRequiresIntake(checkpoint.route.action)) {
5739
- if (checkpoint.route.confidence !== "high") {
5740
- checkpoint = checkpointPlanWithRoute(checkpoint, {
6086
+ if (checkpointRouteRequiresIntake(checkpointPlan.route.action)) {
6087
+ if (checkpointPlan.route.confidence !== "high") {
6088
+ checkpointPlan = checkpointPlanWithRoute(checkpointPlan, {
5741
6089
  action: "noop",
5742
- confidence: checkpoint.route.confidence,
6090
+ confidence: checkpointPlan.route.confidence,
5743
6091
  reason: "Checkpoint reroute requires high confidence."
5744
6092
  });
5745
- return await finishCheckpoint("noop", checkpoint.route.reason);
6093
+ return await finishCheckpoint("noop", checkpointPlan.route.reason);
5746
6094
  }
5747
6095
  const routeArgs = [
5748
6096
  "agent-run",
@@ -5755,12 +6103,11 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5755
6103
  ...parsed.flags.project ? ["--project", parsed.flags.project] : [],
5756
6104
  ...parsed.flags.server ? ["--server", parsed.flags.server] : []
5757
6105
  ];
5758
- const rerouteResult = await finishCheckpoint("reroute", checkpoint.route.reason ?? "Checkpoint delegated routing to agent-run route.");
6106
+ const rerouteResult = await finishCheckpoint("reroute", checkpointPlan.route.reason ?? "Checkpoint delegated routing to agent-run route.");
5759
6107
  await spawnAgentRunWorker(routeArgs, options);
5760
6108
  return rerouteResult;
5761
6109
  }
5762
- const checkpointMetadata = checkpointWorkLogMetadata(checkpointSettings, mode, tokenAccounting);
5763
- for (const entry of checkpoint.entries) {
6110
+ for (const entry of checkpointPlan.entries) {
5764
6111
  const logEntry = {
5765
6112
  entryKind: entry.kind,
5766
6113
  body: entry.body,
@@ -5769,7 +6116,6 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5769
6116
  ...entry.parentEntryId ? { parentEntryId: entry.parentEntryId } : {},
5770
6117
  ...targetSessionId ? { sessionId: targetSessionId } : {},
5771
6118
  ...targetChangesetId ? { changesetId: targetChangesetId } : {},
5772
- metadata: checkpointMetadata,
5773
6119
  ...commandMetadata()
5774
6120
  };
5775
6121
  try {
@@ -5817,9 +6163,9 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5817
6163
  });
5818
6164
  }
5819
6165
  }
5820
- if (checkpoint.files.length && targetSessionId) {
6166
+ if (checkpointPlan.files.length && targetSessionId) {
5821
6167
  const filesByKind = new Map;
5822
- for (const file of checkpoint.files) {
6168
+ for (const file of checkpointPlan.files) {
5823
6169
  filesByKind.set(file.kind, [...filesByKind.get(file.kind) ?? [], file.path]);
5824
6170
  }
5825
6171
  for (const [kind, paths] of filesByKind) {
@@ -5839,7 +6185,7 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5839
6185
  lifecycleOperations.push(checkpointLifecycleOperation("session.files_attached", true, { sessionId: targetSessionId, kind, files: paths }));
5840
6186
  }
5841
6187
  }
5842
- if ((updateSummary || checkpoint.summaryUpdate?.update) && checkpoint.summaryUpdate?.value) {
6188
+ if ((updateSummary || checkpointPlan.summaryUpdate?.update) && checkpointPlan.summaryUpdate?.value) {
5843
6189
  const ticket = await client.tickets.get({ projectId, ticketId: targetTicketId });
5844
6190
  if (typeof ticket.projectionVersion === "number") {
5845
6191
  await client.tickets.update({
@@ -5847,7 +6193,7 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5847
6193
  ticketId: targetTicketId,
5848
6194
  ifVersion: ticket.projectionVersion,
5849
6195
  ticket: {
5850
- currentSummary: checkpoint.summaryUpdate.value,
6196
+ currentSummary: checkpointPlan.summaryUpdate.value,
5851
6197
  ...commandMetadata()
5852
6198
  }
5853
6199
  });
@@ -5857,36 +6203,36 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5857
6203
  ticketId: targetTicketId,
5858
6204
  ifVersion: ticket.projectionVersion,
5859
6205
  ticket: {
5860
- currentSummary: checkpoint.summaryUpdate.value
6206
+ currentSummary: checkpointPlan.summaryUpdate.value
5861
6207
  }
5862
6208
  });
5863
6209
  }
5864
6210
  }
5865
- if (checkpoint.session?.action === "handoff" || checkpoint.session?.action === "end" || checkpoint.session?.action === "complete_ticket") {
6211
+ if (checkpointPlan.session?.action === "handoff" || checkpointPlan.session?.action === "end" || checkpointPlan.session?.action === "complete_ticket") {
5866
6212
  if (targetSessionId) {
5867
6213
  await client.sessions.end({
5868
6214
  projectId,
5869
6215
  sessionId: targetSessionId,
5870
6216
  session: {
5871
- summary: checkpoint.session.summary ?? summary,
6217
+ summary: checkpointPlan.session.summary ?? summary,
5872
6218
  ...commandMetadata()
5873
6219
  }
5874
6220
  });
5875
- lifecycleOperations.push(checkpointLifecycleOperation("session.ended", true, { sessionId: targetSessionId, action: checkpoint.session.action }));
6221
+ lifecycleOperations.push(checkpointLifecycleOperation("session.ended", true, { sessionId: targetSessionId, action: checkpointPlan.session.action }));
5876
6222
  }
5877
6223
  }
5878
- if (checkpoint.session?.action === "complete_ticket") {
6224
+ if (checkpointPlan.session?.action === "complete_ticket") {
5879
6225
  const latestTicket = await client.tickets.get({ projectId, ticketId: targetTicketId });
5880
6226
  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);
6227
+ checkpointPlan = checkpointPlanNeedsHuman(checkpointPlan, "Ticket completion requires a latest ticket projection version.");
6228
+ return await finishCheckpoint("needs_human", checkpointPlan.route.reason);
5883
6229
  }
5884
6230
  await client.tickets.complete({
5885
6231
  projectId,
5886
6232
  ticketId: targetTicketId,
5887
6233
  ifVersion: latestTicket.projectionVersion,
5888
6234
  completion: {
5889
- currentSummary: checkpoint.session.summary ?? checkpoint.summaryUpdate?.value ?? summary,
6235
+ currentSummary: checkpointPlan.session.summary ?? checkpointPlan.summaryUpdate?.value ?? summary,
5890
6236
  ...commandMetadata()
5891
6237
  }
5892
6238
  });
@@ -5895,7 +6241,7 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5895
6241
  success: true,
5896
6242
  ticketId: targetTicketId,
5897
6243
  ifVersion: latestTicket.projectionVersion,
5898
- summary: checkpoint.session.summary ?? checkpoint.summaryUpdate?.value ?? summary
6244
+ summary: checkpointPlan.session.summary ?? checkpointPlan.summaryUpdate?.value ?? summary
5899
6245
  });
5900
6246
  }
5901
6247
  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.18-dev.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {