@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.
- package/dist/cadence +134 -20
- 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.
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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] : [],
|