@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.
- package/dist/cadence +464 -118
- 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.
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
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
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
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
|
|
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";
|
|
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:
|
|
5637
|
-
stdout:
|
|
5638
|
-
stderr: truncateText(
|
|
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:
|
|
6007
|
+
checkpoint: checkpointPlan,
|
|
6008
|
+
route: checkpointPlan.route,
|
|
5661
6009
|
mode,
|
|
5662
6010
|
fingerprints,
|
|
5663
|
-
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:
|
|
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:
|
|
6050
|
+
route: checkpointPlan.route,
|
|
5703
6051
|
...reason ? { reason } : {},
|
|
5704
6052
|
ticketId: targetTicketId,
|
|
5705
6053
|
summary,
|
|
5706
|
-
entryCount:
|
|
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 (
|
|
5722
|
-
|
|
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 (
|
|
5725
|
-
|
|
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 (
|
|
5728
|
-
return await finishCheckpoint("noop",
|
|
6075
|
+
if (checkpointPlan.route.action === "noop") {
|
|
6076
|
+
return await finishCheckpoint("noop", checkpointPlan.route.reason ?? checkpointPlan.summary ?? "Nothing durable to record.");
|
|
5729
6077
|
}
|
|
5730
|
-
if (
|
|
5731
|
-
|
|
6078
|
+
if (checkpointPlan.route.action === "needs_human") {
|
|
6079
|
+
checkpointPlan = checkpointPlanWithRoute(checkpointPlan, {
|
|
5732
6080
|
action: "noop",
|
|
5733
|
-
confidence:
|
|
5734
|
-
reason:
|
|
6081
|
+
confidence: checkpointPlan.route.confidence,
|
|
6082
|
+
reason: checkpointPlan.route.reason ?? "Checkpoint routing was uncertain."
|
|
5735
6083
|
});
|
|
5736
|
-
return await finishCheckpoint("noop",
|
|
6084
|
+
return await finishCheckpoint("noop", checkpointPlan.route.reason);
|
|
5737
6085
|
}
|
|
5738
|
-
if (checkpointRouteRequiresIntake(
|
|
5739
|
-
if (
|
|
5740
|
-
|
|
6086
|
+
if (checkpointRouteRequiresIntake(checkpointPlan.route.action)) {
|
|
6087
|
+
if (checkpointPlan.route.confidence !== "high") {
|
|
6088
|
+
checkpointPlan = checkpointPlanWithRoute(checkpointPlan, {
|
|
5741
6089
|
action: "noop",
|
|
5742
|
-
confidence:
|
|
6090
|
+
confidence: checkpointPlan.route.confidence,
|
|
5743
6091
|
reason: "Checkpoint reroute requires high confidence."
|
|
5744
6092
|
});
|
|
5745
|
-
return await finishCheckpoint("noop",
|
|
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",
|
|
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
|
|
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 (
|
|
6166
|
+
if (checkpointPlan.files.length && targetSessionId) {
|
|
5821
6167
|
const filesByKind = new Map;
|
|
5822
|
-
for (const file of
|
|
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 ||
|
|
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:
|
|
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:
|
|
6206
|
+
currentSummary: checkpointPlan.summaryUpdate.value
|
|
5861
6207
|
}
|
|
5862
6208
|
});
|
|
5863
6209
|
}
|
|
5864
6210
|
}
|
|
5865
|
-
if (
|
|
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:
|
|
6217
|
+
summary: checkpointPlan.session.summary ?? summary,
|
|
5872
6218
|
...commandMetadata()
|
|
5873
6219
|
}
|
|
5874
6220
|
});
|
|
5875
|
-
lifecycleOperations.push(checkpointLifecycleOperation("session.ended", true, { sessionId: targetSessionId, action:
|
|
6221
|
+
lifecycleOperations.push(checkpointLifecycleOperation("session.ended", true, { sessionId: targetSessionId, action: checkpointPlan.session.action }));
|
|
5876
6222
|
}
|
|
5877
6223
|
}
|
|
5878
|
-
if (
|
|
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
|
-
|
|
5882
|
-
return await finishCheckpoint("needs_human",
|
|
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:
|
|
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:
|
|
6244
|
+
summary: checkpointPlan.session.summary ?? checkpointPlan.summaryUpdate?.value ?? summary
|
|
5899
6245
|
});
|
|
5900
6246
|
}
|
|
5901
6247
|
return await finishCheckpoint("checkpointed");
|