@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.
- package/dist/cadence +524 -118
- 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.
|
|
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 =
|
|
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
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
|
|
5108
|
-
|
|
5109
|
-
|
|
5110
|
-
|
|
5111
|
-
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
5117
|
-
|
|
5118
|
-
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
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
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
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
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
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:
|
|
5637
|
-
stdout:
|
|
5638
|
-
stderr: truncateText(
|
|
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:
|
|
6065
|
+
checkpoint: checkpointPlan,
|
|
6066
|
+
route: checkpointPlan.route,
|
|
5661
6067
|
mode,
|
|
5662
6068
|
fingerprints,
|
|
5663
|
-
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:
|
|
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:
|
|
6110
|
+
route: checkpointPlan.route,
|
|
5703
6111
|
...reason ? { reason } : {},
|
|
5704
6112
|
ticketId: targetTicketId,
|
|
5705
6113
|
summary,
|
|
5706
|
-
entryCount:
|
|
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 (
|
|
5722
|
-
|
|
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 (
|
|
5725
|
-
|
|
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 (
|
|
5728
|
-
return await finishCheckpoint("noop",
|
|
6135
|
+
if (checkpointPlan.route.action === "noop") {
|
|
6136
|
+
return await finishCheckpoint("noop", checkpointPlan.route.reason ?? checkpointPlan.summary ?? "Nothing durable to record.");
|
|
5729
6137
|
}
|
|
5730
|
-
if (
|
|
5731
|
-
|
|
6138
|
+
if (checkpointPlan.route.action === "needs_human") {
|
|
6139
|
+
checkpointPlan = checkpointPlanWithRoute(checkpointPlan, {
|
|
5732
6140
|
action: "noop",
|
|
5733
|
-
confidence:
|
|
5734
|
-
reason:
|
|
6141
|
+
confidence: checkpointPlan.route.confidence,
|
|
6142
|
+
reason: checkpointPlan.route.reason ?? "Checkpoint routing was uncertain."
|
|
5735
6143
|
});
|
|
5736
|
-
return await finishCheckpoint("noop",
|
|
6144
|
+
return await finishCheckpoint("noop", checkpointPlan.route.reason);
|
|
5737
6145
|
}
|
|
5738
|
-
if (checkpointRouteRequiresIntake(
|
|
5739
|
-
if (
|
|
5740
|
-
|
|
6146
|
+
if (checkpointRouteRequiresIntake(checkpointPlan.route.action)) {
|
|
6147
|
+
if (checkpointPlan.route.confidence !== "high") {
|
|
6148
|
+
checkpointPlan = checkpointPlanWithRoute(checkpointPlan, {
|
|
5741
6149
|
action: "noop",
|
|
5742
|
-
confidence:
|
|
6150
|
+
confidence: checkpointPlan.route.confidence,
|
|
5743
6151
|
reason: "Checkpoint reroute requires high confidence."
|
|
5744
6152
|
});
|
|
5745
|
-
return await finishCheckpoint("noop",
|
|
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",
|
|
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
|
|
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 (
|
|
6226
|
+
if (checkpointPlan.files.length && targetSessionId) {
|
|
5821
6227
|
const filesByKind = new Map;
|
|
5822
|
-
for (const file of
|
|
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 ||
|
|
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:
|
|
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:
|
|
6266
|
+
currentSummary: checkpointPlan.summaryUpdate.value
|
|
5861
6267
|
}
|
|
5862
6268
|
});
|
|
5863
6269
|
}
|
|
5864
6270
|
}
|
|
5865
|
-
if (
|
|
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:
|
|
6277
|
+
summary: checkpointPlan.session.summary ?? summary,
|
|
5872
6278
|
...commandMetadata()
|
|
5873
6279
|
}
|
|
5874
6280
|
});
|
|
5875
|
-
lifecycleOperations.push(checkpointLifecycleOperation("session.ended", true, { sessionId: targetSessionId, action:
|
|
6281
|
+
lifecycleOperations.push(checkpointLifecycleOperation("session.ended", true, { sessionId: targetSessionId, action: checkpointPlan.session.action }));
|
|
5876
6282
|
}
|
|
5877
6283
|
}
|
|
5878
|
-
if (
|
|
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
|
-
|
|
5882
|
-
return await finishCheckpoint("needs_human",
|
|
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:
|
|
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:
|
|
6304
|
+
summary: checkpointPlan.session.summary ?? checkpointPlan.summaryUpdate?.value ?? summary
|
|
5899
6305
|
});
|
|
5900
6306
|
}
|
|
5901
6307
|
return await finishCheckpoint("checkpointed");
|