@trycadence/cli 0.1.19-dev.0 → 0.1.21-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 +134 -20
  2. package/package.json +1 -1
package/dist/cadence CHANGED
@@ -1521,7 +1521,7 @@ import { createInterface } from "readline/promises";
1521
1521
  // package.json
1522
1522
  var package_default = {
1523
1523
  name: "@trycadence/cli",
1524
- version: "0.1.19-dev.0",
1524
+ version: "0.1.21-dev.0",
1525
1525
  private: false,
1526
1526
  type: "module",
1527
1527
  bin: {
@@ -3358,9 +3358,38 @@ async function writeAgentCheckpointAuditFile(parsed, options, audit) {
3358
3358
  `);
3359
3359
  return filePath;
3360
3360
  }
3361
+ async function readAgentLoopLockHolder(lockPath) {
3362
+ try {
3363
+ const text = await readFile(join(lockPath, "holder.json"), "utf8");
3364
+ const parsed = JSON.parse(text);
3365
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
3366
+ } catch {
3367
+ return null;
3368
+ }
3369
+ }
3370
+ function lockHolderPid(holder) {
3371
+ const pid = holder?.pid;
3372
+ return typeof pid === "number" && Number.isInteger(pid) && pid > 0 ? pid : null;
3373
+ }
3374
+ function processIsRunning(pid) {
3375
+ try {
3376
+ process.kill(pid, 0);
3377
+ return true;
3378
+ } catch (error) {
3379
+ if (error && typeof error === "object" && "code" in error && error.code === "ESRCH") {
3380
+ return false;
3381
+ }
3382
+ return true;
3383
+ }
3384
+ }
3361
3385
  async function removeStaleAgentLoopLock(lockPath) {
3362
3386
  try {
3363
3387
  const lockStats = await stat(lockPath);
3388
+ const holderPid = lockHolderPid(await readAgentLoopLockHolder(lockPath));
3389
+ if (holderPid !== null && !processIsRunning(holderPid)) {
3390
+ await rm(lockPath, { recursive: true, force: true });
3391
+ return true;
3392
+ }
3364
3393
  if (Date.now() - lockStats.mtimeMs < defaultCheckpointWorkerTimeoutMs) {
3365
3394
  return false;
3366
3395
  }
@@ -3605,18 +3634,6 @@ function shouldSkipForCooldown(state, cooldownSeconds) {
3605
3634
  const lastCheckpointMs = new Date(state.lastCheckpointAt).getTime();
3606
3635
  return !Number.isNaN(lastCheckpointMs) && Date.now() - lastCheckpointMs < cooldownSeconds * 1000;
3607
3636
  }
3608
- function synthesizeAgentEventFromSession(agentSessionKeyValue, session, options) {
3609
- return {
3610
- source: session.source,
3611
- event: "checkpoint",
3612
- workspacePath: options.cwd ?? process.cwd(),
3613
- occurredAt: new Date().toISOString(),
3614
- agentSessionKey: agentSessionKeyValue,
3615
- ...session.lastAssistantMessage ? { lastAssistantMessage: session.lastAssistantMessage } : {},
3616
- ...session.recentTurns?.length ? { recentTurns: session.recentTurns } : {},
3617
- payloadKeys: []
3618
- };
3619
- }
3620
3637
  var checkpointRouteActions = ["current", "noop", "intake_create", "intake_attach", "switch_existing", "needs_human"];
3621
3638
  var checkpointConfidenceLevels = ["low", "medium", "high"];
3622
3639
  var checkpointSessionActions = ["keep", "handoff", "end", "complete_ticket"];
@@ -4255,6 +4272,9 @@ function buildRoutePrompt(input) {
4255
4272
  "You are generating a compact Cadence dogfood routing plan for an agent-run worker.",
4256
4273
  "The model judges; the Cadence CLI validates and executes. Return JSON only. Do not call tools to mutate Cadence yourself.",
4257
4274
  "Decide where this agent session belongs before checkpoint memory is written.",
4275
+ "First decide whether the latest request is directed at this source agent to perform work, or whether this is a meta/utility task about quoted conversation content.",
4276
+ "If the transcript asks this agent to generate a title, branch name, label, summary, classification, or other metadata for quoted user text, treat the quoted work as reference material, not active work for this agent.",
4277
+ "For those meta/utility cases, return route.action noop with entries [] even when the quoted text describes durable repo work.",
4258
4278
  "Do not include raw diffs, raw transcripts, terminal logs, tool streams, secrets, file contents, or model reasoning in server-bound fields.",
4259
4279
  '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
4280
  "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.",
@@ -4291,6 +4311,8 @@ function buildRouteCandidatePrompt(input) {
4291
4311
  "You are resolving Cadence agent-run routing after intake returned possible existing work.",
4292
4312
  "Intake is retrieval, not the final decision. The model judges; the Cadence CLI validates and executes. Return JSON only.",
4293
4313
  "Choose whether this agent session should create a new ticket, attach/switch to one concrete candidate ticket, keep the current context, or no-op.",
4314
+ "Before choosing a candidate, decide whether the latest request is directed at this source agent to perform work, or whether this is a meta/utility task about quoted conversation content.",
4315
+ "If the transcript asks this agent to generate a title, branch name, label, summary, classification, or other metadata for quoted user text, return noop and do not attach/create/switch based on the quoted work.",
4294
4316
  "Use intake_create when the recent request is durable and the candidates are weak, adjacent, completed, or not concrete matches.",
4295
4317
  "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
4318
  "Use current only when the saved/current Cadence context clearly fits the recent work.",
@@ -4925,6 +4947,11 @@ async function runAgentRunIngestStop(parsed, options, config, meta) {
4925
4947
  ...normalized.lastAssistantMessage ? { lastAssistantMessage: normalized.lastAssistantMessage } : {},
4926
4948
  ...recentTurns ? { recentTurns } : {}
4927
4949
  };
4950
+ let observedEventFile;
4951
+ const ensureObservedEventFile = async () => {
4952
+ observedEventFile ??= await writeAgentEventFile(parsed, options, normalized);
4953
+ return observedEventFile;
4954
+ };
4928
4955
  const countedState = {
4929
4956
  ...state,
4930
4957
  sessions: {
@@ -4949,7 +4976,7 @@ async function runAgentRunIngestStop(parsed, options, config, meta) {
4949
4976
  };
4950
4977
  }
4951
4978
  if (!savedCadenceContext?.ticketId) {
4952
- const eventFile2 = await writeAgentEventFile(parsed, options, normalized);
4979
+ const eventFile2 = await ensureObservedEventFile();
4953
4980
  const lockPath2 = agentLoopLockPath(parsed, options, normalized.agentSessionKey);
4954
4981
  const workerArgs2 = [
4955
4982
  "agent-run",
@@ -5051,13 +5078,24 @@ async function runAgentRunIngestStop(parsed, options, config, meta) {
5051
5078
  };
5052
5079
  }
5053
5080
  if (nextCount < threshold) {
5054
- await writeAgentLoopState(parsed, options, countedState);
5081
+ const eventFile2 = await ensureObservedEventFile();
5082
+ await writeAgentLoopState(parsed, options, {
5083
+ ...state,
5084
+ sessions: {
5085
+ ...state.sessions,
5086
+ [normalized.agentSessionKey]: {
5087
+ ...observedSession,
5088
+ lastEventFile: eventFile2
5089
+ }
5090
+ }
5091
+ });
5055
5092
  const data2 = {
5056
5093
  action: duplicateTurn ? "updated" : "counted",
5057
5094
  ...duplicateTurn ? { reason: "duplicate_turn" } : {},
5058
5095
  agentSessionKey: normalized.agentSessionKey,
5059
5096
  stopCount: nextCount,
5060
- threshold
5097
+ threshold,
5098
+ eventFile: eventFile2
5061
5099
  };
5062
5100
  return {
5063
5101
  stdout: parsed.flags.json ? formatJson(successEnvelope(data2, meta)) : `${JSON.stringify(data2, null, 2)}
@@ -5119,7 +5157,7 @@ async function runAgentRunIngestStop(parsed, options, config, meta) {
5119
5157
  exitCode: 0
5120
5158
  };
5121
5159
  }
5122
- const eventFile = await writeAgentEventFile(parsed, options, normalized);
5160
+ const eventFile = await ensureObservedEventFile();
5123
5161
  const lockPath = agentLoopLockPath(parsed, options, normalized.agentSessionKey);
5124
5162
  const workerArgs = [
5125
5163
  "agent-run",
@@ -5259,9 +5297,35 @@ async function runAgentRunRoute(parsed, options, config, meta) {
5259
5297
  });
5260
5298
  }
5261
5299
  const savedContext = sessionState.cadenceContext;
5262
- const currentContext = savedContext?.ticketId ? undefined : await readCurrentCadenceContext(client, projectId);
5263
5300
  const eventFile = parsed.options["event-file"] ?? sessionState.lastEventFile;
5264
- const event = eventFile ? tryParseJsonObject(await readFile(eventFile, "utf8"), eventFile) : synthesizeAgentEventFromSession(agentSessionKeyValue, sessionState, options);
5301
+ if (!eventFile || !existsSync(eventFile)) {
5302
+ await writeAgentLoopState(parsed, options, {
5303
+ ...state,
5304
+ sessions: {
5305
+ ...state.sessions,
5306
+ [agentSessionKeyValue]: {
5307
+ ...sessionState,
5308
+ stopCount: 0,
5309
+ threshold: defaultCheckpointThresholdValue(),
5310
+ lastAction: "skipped",
5311
+ lastReason: "no_event_file"
5312
+ }
5313
+ }
5314
+ });
5315
+ const data = {
5316
+ action: "skipped",
5317
+ reason: "no_event_file",
5318
+ agentSessionKey: agentSessionKeyValue
5319
+ };
5320
+ return {
5321
+ stdout: parsed.flags.json ? formatJson(successEnvelope(data, meta)) : `${JSON.stringify(data, null, 2)}
5322
+ `,
5323
+ stderr: "",
5324
+ exitCode: 0
5325
+ };
5326
+ }
5327
+ const event = tryParseJsonObject(await readFile(eventFile, "utf8"), eventFile);
5328
+ const currentContext = savedContext?.ticketId ? undefined : await readCurrentCadenceContext(client, projectId);
5265
5329
  const checkpointSettings = await resolveCheckpointSettings(parsed, options);
5266
5330
  const gitStatus = gitOutput(["status", "--short"], options);
5267
5331
  const gitDiffStat = gitOutput(["diff", "--stat", "origin/dev..."], options);
@@ -5715,7 +5779,36 @@ async function runAgentRunCheckpoint(parsed, options, config, meta) {
5715
5779
  };
5716
5780
  }
5717
5781
  const eventFile = parsed.options["event-file"] ?? sessionState.lastEventFile;
5718
- const event = eventFile ? tryParseJsonObject(await readFile(eventFile, "utf8"), eventFile) : synthesizeAgentEventFromSession(agentSessionKeyValue, sessionState, options);
5782
+ if (!eventFile || !existsSync(eventFile)) {
5783
+ await writeAgentLoopState(parsed, options, {
5784
+ ...state,
5785
+ sessions: {
5786
+ ...state.sessions,
5787
+ [agentSessionKeyValue]: {
5788
+ ...sessionState,
5789
+ stopCount: 0,
5790
+ threshold: defaultCheckpointThresholdValue(),
5791
+ lastAction: "skipped",
5792
+ lastReason: "no_event_file"
5793
+ }
5794
+ }
5795
+ });
5796
+ const data = {
5797
+ action: "skipped",
5798
+ reason: "no_event_file",
5799
+ agentSessionKey: agentSessionKeyValue,
5800
+ ...ticketId ? { ticketId } : {},
5801
+ ...sessionId ? { sessionId } : {},
5802
+ ...changesetId ? { changesetId } : {}
5803
+ };
5804
+ return {
5805
+ stdout: parsed.flags.json ? formatJson(successEnvelope(data, meta)) : `${JSON.stringify(data, null, 2)}
5806
+ `,
5807
+ stderr: "",
5808
+ exitCode: 0
5809
+ };
5810
+ }
5811
+ const event = tryParseJsonObject(await readFile(eventFile, "utf8"), eventFile);
5719
5812
  const mode = checkpointModeForCommand(parsed);
5720
5813
  const logKind = parseWorkLogEntryKind(parsed.options["log-kind"] ?? "note");
5721
5814
  const updateSummary = parseBooleanOption(parsed.options["update-summary"], false);
@@ -6332,7 +6425,25 @@ async function runAgentRunSweep(parsed, options, config, meta) {
6332
6425
  for (const staleSession of staleSessions) {
6333
6426
  const existingSession = nextState.sessions[staleSession.agentSessionKey];
6334
6427
  const hasContext = Boolean(existingSession?.cadenceContext?.ticketId);
6428
+ const eventFile = existingSession?.lastEventFile;
6335
6429
  if (existingSession) {
6430
+ if (!eventFile || !existsSync(eventFile)) {
6431
+ nextState = {
6432
+ ...nextState,
6433
+ sessions: {
6434
+ ...nextState.sessions,
6435
+ [staleSession.agentSessionKey]: {
6436
+ ...existingSession,
6437
+ stopCount: 0,
6438
+ threshold: defaultCheckpointThresholdValue(),
6439
+ lastAction: "skipped",
6440
+ lastReason: "no_event_file"
6441
+ }
6442
+ }
6443
+ };
6444
+ await writeAgentLoopState(parsed, options, nextState);
6445
+ continue;
6446
+ }
6336
6447
  nextState = {
6337
6448
  ...nextState,
6338
6449
  sessions: {
@@ -6345,6 +6456,7 @@ async function runAgentRunSweep(parsed, options, config, meta) {
6345
6456
  };
6346
6457
  await writeAgentLoopState(parsed, options, nextState);
6347
6458
  }
6459
+ const realEventFile = eventFile;
6348
6460
  await spawnAgentRunCheckpoint([
6349
6461
  "agent-run",
6350
6462
  hasContext ? "checkpoint" : "route",
@@ -6352,6 +6464,8 @@ async function runAgentRunSweep(parsed, options, config, meta) {
6352
6464
  staleSession.agentSessionKey,
6353
6465
  "--reason",
6354
6466
  hasContext ? "idle" : "missing_context",
6467
+ "--event-file",
6468
+ realEventFile,
6355
6469
  "--lock",
6356
6470
  agentLoopLockPath(parsed, options, staleSession.agentSessionKey),
6357
6471
  ...parsed.flags.project ? ["--project", parsed.flags.project] : [],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@trycadence/cli",
3
- "version": "0.1.19-dev.0",
3
+ "version": "0.1.21-dev.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "bin": {