@jpcasabiancai/cayenne 0.5.13 → 0.5.14
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/cli.js +310 -49
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -1586,7 +1586,7 @@ var init_documents = __esm({
|
|
|
1586
1586
|
|
|
1587
1587
|
// ../../packages/shared/src/protocol/events.ts
|
|
1588
1588
|
import { z as z14 } from "zod";
|
|
1589
|
-
var SessionStatus, SessionEvent, SESSION_EVENT_NAME, sessionTopic, runnerTopic, terminalTopic, activityFeedTopic;
|
|
1589
|
+
var SessionStatus, RenewalReason, SessionEvent, SESSION_EVENT_NAME, sessionTopic, runnerTopic, terminalTopic, activityFeedTopic;
|
|
1590
1590
|
var init_events = __esm({
|
|
1591
1591
|
"../../packages/shared/src/protocol/events.ts"() {
|
|
1592
1592
|
"use strict";
|
|
@@ -1600,6 +1600,13 @@ var init_events = __esm({
|
|
|
1600
1600
|
"error",
|
|
1601
1601
|
"archived"
|
|
1602
1602
|
]);
|
|
1603
|
+
RenewalReason = z14.enum([
|
|
1604
|
+
"autopilot_threshold",
|
|
1605
|
+
"threshold",
|
|
1606
|
+
"visibility",
|
|
1607
|
+
"manual",
|
|
1608
|
+
"compaction"
|
|
1609
|
+
]);
|
|
1603
1610
|
SessionEvent = z14.discriminatedUnion("type", [
|
|
1604
1611
|
z14.object({
|
|
1605
1612
|
type: z14.literal("session.status"),
|
|
@@ -1654,7 +1661,16 @@ var init_events = __esm({
|
|
|
1654
1661
|
segment: z14.number().int().nonnegative(),
|
|
1655
1662
|
fromTokens: z14.number().int().optional(),
|
|
1656
1663
|
toTokens: z14.number().int().optional(),
|
|
1657
|
-
error: z14.string().optional()
|
|
1664
|
+
error: z14.string().optional(),
|
|
1665
|
+
// Added 2026-10-23 (Auto Pilot 03, D62); absent from runners ≤ 0.5.13.
|
|
1666
|
+
reason: RenewalReason.optional()
|
|
1667
|
+
}),
|
|
1668
|
+
// Auto Pilot 03 (D62), protocol §3: sent once after `renewal { phase: 'done' }`, naming the new
|
|
1669
|
+
// segment `n + 1` and why it renewed.
|
|
1670
|
+
z14.object({
|
|
1671
|
+
type: z14.literal("session.renewed"),
|
|
1672
|
+
segment: z14.number().int().min(1),
|
|
1673
|
+
reason: RenewalReason
|
|
1658
1674
|
})
|
|
1659
1675
|
]);
|
|
1660
1676
|
SESSION_EVENT_NAME = "evt";
|
|
@@ -2594,7 +2610,7 @@ var init_registry = __esm({
|
|
|
2594
2610
|
"use strict";
|
|
2595
2611
|
REDACTED = "[REDACTED]";
|
|
2596
2612
|
MIN_SECRET_LENGTH = 16;
|
|
2597
|
-
GITHUB_TOKEN_SHAPE = /(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,
|
|
2613
|
+
GITHUB_TOKEN_SHAPE = /(?:ghs_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+|(?:gh[pousr]_|github_pat_)[A-Za-z0-9_]{20,})/;
|
|
2598
2614
|
GITHUB_TOKEN_SHAPES = new RegExp(GITHUB_TOKEN_SHAPE.source, "g");
|
|
2599
2615
|
secrets = createSecretRegistry();
|
|
2600
2616
|
isError = (value) => value instanceof Error || types.isNativeError(value);
|
|
@@ -3469,7 +3485,7 @@ function statusSummary(porcelain) {
|
|
|
3469
3485
|
if (lines.length > MAX_STATUS_PATHS) out.push(` \u2026 ${lines.length - MAX_STATUS_PATHS} more`);
|
|
3470
3486
|
return out;
|
|
3471
3487
|
}
|
|
3472
|
-
function buildAppendix(git, todos, docs, claims) {
|
|
3488
|
+
function buildAppendix(git, todos, docs, claims, extra = []) {
|
|
3473
3489
|
const out = [APPENDIX_HEADING];
|
|
3474
3490
|
if (git) {
|
|
3475
3491
|
out.push(`Branch: ${git.branch === null ? "(detached or none)" : field(git.branch, CAP_PATH)}`);
|
|
@@ -3504,7 +3520,9 @@ function buildAppendix(git, todos, docs, claims) {
|
|
|
3504
3520
|
} else {
|
|
3505
3521
|
out.push("Active claims: none");
|
|
3506
3522
|
}
|
|
3507
|
-
|
|
3523
|
+
const tail = extra.map((section2) => `
|
|
3524
|
+
${section2}`).join("");
|
|
3525
|
+
return `${truncateHead(out.join("\n"), MAX_APPENDIX_TOKENS - estimateTokens(tail))}${tail}`;
|
|
3508
3526
|
}
|
|
3509
3527
|
function captureHandoff(turn, clock, timeoutMs = HANDOFF_TIMEOUT_MS) {
|
|
3510
3528
|
return new Promise((resolve7) => {
|
|
@@ -10052,7 +10070,7 @@ var init_token_client = __esm({
|
|
|
10052
10070
|
"src/repos/token-client.ts"() {
|
|
10053
10071
|
"use strict";
|
|
10054
10072
|
init_src();
|
|
10055
|
-
TOKEN_SHAPE = /^[A-Za-z0-9_]{20,
|
|
10073
|
+
TOKEN_SHAPE = /^[A-Za-z0-9_.-]{20,}$/;
|
|
10056
10074
|
DEFAULT_TIMEOUT_MS = 9e4;
|
|
10057
10075
|
}
|
|
10058
10076
|
});
|
|
@@ -10335,6 +10353,49 @@ var init_attachments2 = __esm({
|
|
|
10335
10353
|
}
|
|
10336
10354
|
});
|
|
10337
10355
|
|
|
10356
|
+
// src/sessions/autopilot-runs.ts
|
|
10357
|
+
function autopilotRunsReader(db, log = logger) {
|
|
10358
|
+
const running = (sessionId, columns) => db.from("autopilot_runs").select(columns).eq("session_id", sessionId).eq("status", "running").maybeSingle();
|
|
10359
|
+
const failed6 = (sessionId, err) => {
|
|
10360
|
+
const e = err;
|
|
10361
|
+
log.warn({ sessionId, code: e?.code, err: e?.message }, "autopilot_runs read failed");
|
|
10362
|
+
};
|
|
10363
|
+
return {
|
|
10364
|
+
isAutopilotRunning: async (sessionId) => {
|
|
10365
|
+
try {
|
|
10366
|
+
const { data, error } = await running(sessionId, "id");
|
|
10367
|
+
if (error) throw error;
|
|
10368
|
+
return data !== null;
|
|
10369
|
+
} catch (err) {
|
|
10370
|
+
failed6(sessionId, err);
|
|
10371
|
+
return false;
|
|
10372
|
+
}
|
|
10373
|
+
},
|
|
10374
|
+
/** Null too for a run `ownerId`, the session's owner, did not start. */
|
|
10375
|
+
autopilotRun: async (sessionId, ownerId) => {
|
|
10376
|
+
try {
|
|
10377
|
+
const { data, error } = await running(sessionId, "objective, done_when, owner_id");
|
|
10378
|
+
if (error) throw error;
|
|
10379
|
+
if (!data || data.owner_id !== ownerId) return null;
|
|
10380
|
+
return {
|
|
10381
|
+
objective: data.objective,
|
|
10382
|
+
doneWhen: data.done_when,
|
|
10383
|
+
unmetDoneWhen: data.done_when
|
|
10384
|
+
};
|
|
10385
|
+
} catch (err) {
|
|
10386
|
+
failed6(sessionId, err);
|
|
10387
|
+
return null;
|
|
10388
|
+
}
|
|
10389
|
+
}
|
|
10390
|
+
};
|
|
10391
|
+
}
|
|
10392
|
+
var init_autopilot_runs = __esm({
|
|
10393
|
+
"src/sessions/autopilot-runs.ts"() {
|
|
10394
|
+
"use strict";
|
|
10395
|
+
init_log();
|
|
10396
|
+
}
|
|
10397
|
+
});
|
|
10398
|
+
|
|
10338
10399
|
// src/fs/list.ts
|
|
10339
10400
|
import { readdirSync as readdirSync3, realpathSync as realpathSync12, statSync as statSync7 } from "fs";
|
|
10340
10401
|
import { join as join22, relative as relative7, sep as sep10 } from "path";
|
|
@@ -10851,9 +10912,14 @@ var init_format = __esm({
|
|
|
10851
10912
|
});
|
|
10852
10913
|
|
|
10853
10914
|
// src/renewal/threshold.ts
|
|
10854
|
-
function renewThreshold(
|
|
10855
|
-
const {
|
|
10856
|
-
if (
|
|
10915
|
+
function renewThreshold(s) {
|
|
10916
|
+
const { window, workspaceThreshold } = s;
|
|
10917
|
+
if (window === null || !Number.isFinite(window) || window <= 0) return null;
|
|
10918
|
+
if (s.autopilot) {
|
|
10919
|
+
const t2 = Math.floor(AUTOPILOT_WINDOW_SHARE * window);
|
|
10920
|
+
return t2 > 0 ? t2 : null;
|
|
10921
|
+
}
|
|
10922
|
+
if (!Number.isFinite(workspaceThreshold)) return null;
|
|
10857
10923
|
const t = Math.min(Math.floor(workspaceThreshold), Math.floor(WINDOW_SHARE * window));
|
|
10858
10924
|
return t < MIN_RENEW_THRESHOLD ? null : t;
|
|
10859
10925
|
}
|
|
@@ -10873,12 +10939,13 @@ function autoCompactWindowFor(window) {
|
|
|
10873
10939
|
const above = Math.floor(WINDOW_SHARE * w) + AUTOCOMPACT_BUFFER + AUTOCOMPACT_MARGIN;
|
|
10874
10940
|
return Math.min(w, Math.max(Math.floor(0.9 * w), above));
|
|
10875
10941
|
}
|
|
10876
|
-
var MIN_RENEW_THRESHOLD, WINDOW_SHARE, KNOWN_CONTEXT_WINDOWS, AUTOCOMPACT_BUFFER, AUTOCOMPACT_MARGIN;
|
|
10942
|
+
var MIN_RENEW_THRESHOLD, WINDOW_SHARE, AUTOPILOT_WINDOW_SHARE, KNOWN_CONTEXT_WINDOWS, AUTOCOMPACT_BUFFER, AUTOCOMPACT_MARGIN;
|
|
10877
10943
|
var init_threshold = __esm({
|
|
10878
10944
|
"src/renewal/threshold.ts"() {
|
|
10879
10945
|
"use strict";
|
|
10880
10946
|
MIN_RENEW_THRESHOLD = 1e5;
|
|
10881
10947
|
WINDOW_SHARE = 0.75;
|
|
10948
|
+
AUTOPILOT_WINDOW_SHARE = 0.4;
|
|
10882
10949
|
KNOWN_CONTEXT_WINDOWS = {
|
|
10883
10950
|
"claude-opus-5-5": 1e6
|
|
10884
10951
|
};
|
|
@@ -10888,13 +10955,26 @@ var init_threshold = __esm({
|
|
|
10888
10955
|
});
|
|
10889
10956
|
|
|
10890
10957
|
// src/renewal/rotation.ts
|
|
10958
|
+
function memberContinuation(lastMemberPrompt, mode) {
|
|
10959
|
+
if (lastMemberPrompt === null) return null;
|
|
10960
|
+
return { text: continuationText(lastMemberPrompt), auto: AUTO_CONTINUE_MODES.has(mode) };
|
|
10961
|
+
}
|
|
10891
10962
|
function handoffBlock(segment, handoff) {
|
|
10892
|
-
const frame = (
|
|
10963
|
+
const frame = (body) => `${HANDOFF_PREAMBLE}
|
|
10893
10964
|
<handoff segment="${segment}">
|
|
10894
|
-
${
|
|
10965
|
+
${body}
|
|
10895
10966
|
</handoff>`;
|
|
10896
|
-
const
|
|
10897
|
-
|
|
10967
|
+
const max = HANDOFF_MAX_TOKENS - estimateTokens(frame(""));
|
|
10968
|
+
const text3 = escapeAngles(handoff);
|
|
10969
|
+
const at = text3.lastIndexOf(`
|
|
10970
|
+
|
|
10971
|
+
${APPENDIX_HEADING}
|
|
10972
|
+
`);
|
|
10973
|
+
const appendix = at < 0 ? "" : text3.slice(at);
|
|
10974
|
+
if (at < 0 || estimateTokens(appendix) > 2 * MAX_APPENDIX_TOKENS) {
|
|
10975
|
+
return frame(truncateHead(text3, max));
|
|
10976
|
+
}
|
|
10977
|
+
return frame(`${truncateHead(text3.slice(0, at), max - estimateTokens(appendix))}${appendix}`);
|
|
10898
10978
|
}
|
|
10899
10979
|
function continuationText(lastMemberPrompt) {
|
|
10900
10980
|
const quoted3 = lastMemberPrompt.replace(/[«»]/g, '"');
|
|
@@ -10911,12 +10991,12 @@ function buildFirstInput(args) {
|
|
|
10911
10991
|
${args.queuedPrompt}`, send: "now" };
|
|
10912
10992
|
}
|
|
10913
10993
|
if (args.queuedPrompt !== null) return { text: block, send: "withNextPrompt" };
|
|
10914
|
-
if (args.endedOnNudge && args.
|
|
10994
|
+
if (args.endedOnNudge && args.continuation !== null) {
|
|
10915
10995
|
return {
|
|
10916
10996
|
text: `${block}
|
|
10917
10997
|
|
|
10918
|
-
${
|
|
10919
|
-
send:
|
|
10998
|
+
${args.continuation.text}`,
|
|
10999
|
+
send: args.continuation.auto ? "now" : "continue"
|
|
10920
11000
|
};
|
|
10921
11001
|
}
|
|
10922
11002
|
return { text: block, send: "withNextPrompt" };
|
|
@@ -10946,6 +11026,8 @@ var init_rotation = __esm({
|
|
|
10946
11026
|
cost = 0;
|
|
10947
11027
|
handoff = null;
|
|
10948
11028
|
reason = "renewal";
|
|
11029
|
+
why = "threshold";
|
|
11030
|
+
plainRetry = false;
|
|
10949
11031
|
/** Why a pending `requestRenew` was made: `manual` unless the runner asked for itself. */
|
|
10950
11032
|
requested = "manual";
|
|
10951
11033
|
constructor(options) {
|
|
@@ -10962,6 +11044,27 @@ var init_rotation = __esm({
|
|
|
10962
11044
|
get enabled() {
|
|
10963
11045
|
return this.threshold !== null;
|
|
10964
11046
|
}
|
|
11047
|
+
/**
|
|
11048
|
+
* What decided the current (or last) renewal. A plain retry after a failure keeps it; a
|
|
11049
|
+
* `requestRenew` that is due at that turn end names its own.
|
|
11050
|
+
*/
|
|
11051
|
+
get cause() {
|
|
11052
|
+
return this.why;
|
|
11053
|
+
}
|
|
11054
|
+
/** The current renewal is the one retry of a failed one, not asked for again (`cause` kept). */
|
|
11055
|
+
get retrying() {
|
|
11056
|
+
return this.plainRetry;
|
|
11057
|
+
}
|
|
11058
|
+
/**
|
|
11059
|
+
* The threshold changed between turns (an Auto Pilot run started or stopped, Auto Pilot 03).
|
|
11060
|
+
* The next `midTurn` and `turnEnd` go by it, except while `nudged`: the threshold that nudged
|
|
11061
|
+
* stays until that turn end renews, since the model was already told the chat will. Returns
|
|
11062
|
+
* the threshold in effect.
|
|
11063
|
+
*/
|
|
11064
|
+
setThreshold(threshold) {
|
|
11065
|
+
if (this.current !== "nudged") this.threshold = threshold;
|
|
11066
|
+
return this.threshold;
|
|
11067
|
+
}
|
|
10965
11068
|
/** The mode changed (a `setMode` update or a mode command): the continuation follows it. */
|
|
10966
11069
|
setMode(mode) {
|
|
10967
11070
|
this.mode = mode;
|
|
@@ -11014,11 +11117,17 @@ var init_rotation = __esm({
|
|
|
11014
11117
|
due = this.manual || this.current === "nudged" || input.tokens >= this.threshold;
|
|
11015
11118
|
}
|
|
11016
11119
|
if (!due) return [];
|
|
11120
|
+
this.plainRetry = this.current === "failed" && !this.manual;
|
|
11017
11121
|
if (this.current === "failed") {
|
|
11018
11122
|
this.retryArmed = false;
|
|
11123
|
+
if (this.manual) {
|
|
11124
|
+
this.reason = this.requested;
|
|
11125
|
+
this.why = this.requested === "manual" ? "manual" : "visibility";
|
|
11126
|
+
}
|
|
11019
11127
|
} else {
|
|
11020
11128
|
this.endedOnNudge = this.current === "nudged";
|
|
11021
11129
|
this.reason = this.manual ? this.requested : "renewal";
|
|
11130
|
+
this.why = !this.manual ? "threshold" : this.requested === "manual" ? "manual" : "visibility";
|
|
11022
11131
|
}
|
|
11023
11132
|
this.manual = false;
|
|
11024
11133
|
this.current = "handoff";
|
|
@@ -11068,9 +11177,8 @@ var init_rotation = __esm({
|
|
|
11068
11177
|
segment: n,
|
|
11069
11178
|
handoff: this.handoff.text,
|
|
11070
11179
|
queuedPrompt: input.queuedPrompt,
|
|
11071
|
-
|
|
11072
|
-
endedOnNudge: this.endedOnNudge
|
|
11073
|
-
mode: this.mode
|
|
11180
|
+
continuation: input.autopilotContinuation ? { text: input.autopilotContinuation, auto: true } : memberContinuation(input.lastMemberPrompt, this.mode),
|
|
11181
|
+
endedOnNudge: this.endedOnNudge
|
|
11074
11182
|
});
|
|
11075
11183
|
const actions = [
|
|
11076
11184
|
{
|
|
@@ -14908,6 +15016,50 @@ var init_materialise = __esm({
|
|
|
14908
15016
|
}
|
|
14909
15017
|
});
|
|
14910
15018
|
|
|
15019
|
+
// src/sessions/autopilot-renewal.ts
|
|
15020
|
+
function autopilotAppendix(run) {
|
|
15021
|
+
const met = run.doneWhen.length - run.unmetDoneWhen.length;
|
|
15022
|
+
const out = [
|
|
15023
|
+
AUTOPILOT_HEADING,
|
|
15024
|
+
`Objective: ${field(run.objective, CAP_OBJECTIVE)}`,
|
|
15025
|
+
`Done-when items met: ${Math.max(0, met)} of ${run.doneWhen.length}`
|
|
15026
|
+
];
|
|
15027
|
+
if (run.unmetDoneWhen.length) {
|
|
15028
|
+
out.push("Done when (not yet met):");
|
|
15029
|
+
for (const item of run.unmetDoneWhen) out.push(`- ${field(item, CAP_ITEM)}`);
|
|
15030
|
+
} else {
|
|
15031
|
+
out.push("Done when (not yet met): none");
|
|
15032
|
+
}
|
|
15033
|
+
const text3 = out.join("\n");
|
|
15034
|
+
if (text3.length <= MAX_AUTOPILOT_APPENDIX) return text3;
|
|
15035
|
+
const head = text3.slice(0, MAX_AUTOPILOT_APPENDIX - 2);
|
|
15036
|
+
return `${head.slice(0, head.lastIndexOf("\n"))}
|
|
15037
|
+
\u2026`;
|
|
15038
|
+
}
|
|
15039
|
+
function autopilotContinuation(run) {
|
|
15040
|
+
let clean3 = run.objective.replace(CONTROL, " ").replace(/\s+/g, " ").trim().replace(/[«»]/g, '"');
|
|
15041
|
+
if (clean3.length > CAP_OBJECTIVE) clean3 = `${clean3.slice(0, CAP_OBJECTIVE - 1)}\u2026`;
|
|
15042
|
+
const quoted3 = escapeAngles(clean3);
|
|
15043
|
+
return `Continue toward the objective the member set for Auto Pilot: \xAB${quoted3}\xBB. The handoff above is reference.`;
|
|
15044
|
+
}
|
|
15045
|
+
function renewalReason(p) {
|
|
15046
|
+
if (p.manual) return "manual";
|
|
15047
|
+
if (p.visibility) return "visibility";
|
|
15048
|
+
if (p.compacted) return "compaction";
|
|
15049
|
+
return p.autopilot ? "autopilot_threshold" : "threshold";
|
|
15050
|
+
}
|
|
15051
|
+
var AUTOPILOT_HEADING, MAX_AUTOPILOT_APPENDIX, CAP_OBJECTIVE, CAP_ITEM;
|
|
15052
|
+
var init_autopilot_renewal = __esm({
|
|
15053
|
+
"src/sessions/autopilot-renewal.ts"() {
|
|
15054
|
+
"use strict";
|
|
15055
|
+
init_handoff();
|
|
15056
|
+
AUTOPILOT_HEADING = "## Auto Pilot";
|
|
15057
|
+
MAX_AUTOPILOT_APPENDIX = 2400;
|
|
15058
|
+
CAP_OBJECTIVE = 800;
|
|
15059
|
+
CAP_ITEM = 240;
|
|
15060
|
+
}
|
|
15061
|
+
});
|
|
15062
|
+
|
|
14911
15063
|
// src/sessions/managed-git-guard.ts
|
|
14912
15064
|
import { realpathSync as realpathSync15 } from "fs";
|
|
14913
15065
|
import { isAbsolute as isAbsolute18, relative as relative11, resolve as resolve4, sep as sep15 } from "path";
|
|
@@ -15684,6 +15836,7 @@ var init_manager = __esm({
|
|
|
15684
15836
|
init_registry();
|
|
15685
15837
|
init_materialise();
|
|
15686
15838
|
init_attachments2();
|
|
15839
|
+
init_autopilot_renewal();
|
|
15687
15840
|
init_managed_git_guard();
|
|
15688
15841
|
init_relay();
|
|
15689
15842
|
init_repo_settings();
|
|
@@ -15748,6 +15901,8 @@ var init_manager = __esm({
|
|
|
15748
15901
|
pendingBlock: null,
|
|
15749
15902
|
manualPending: false,
|
|
15750
15903
|
lastPrompt: null,
|
|
15904
|
+
autopilot: false,
|
|
15905
|
+
cause: null,
|
|
15751
15906
|
todos: null
|
|
15752
15907
|
});
|
|
15753
15908
|
scopeOf2 = (v) => v === "private" || v === "workspace" ? v : null;
|
|
@@ -15904,7 +16059,9 @@ var init_manager = __esm({
|
|
|
15904
16059
|
try {
|
|
15905
16060
|
this.assertNotRemoving(cwd);
|
|
15906
16061
|
} catch (err) {
|
|
15907
|
-
|
|
16062
|
+
const failure2 = this.giveBack(repo?.creds, err);
|
|
16063
|
+
await this.recordStartFailure(sessionId);
|
|
16064
|
+
throw failure2;
|
|
15908
16065
|
}
|
|
15909
16066
|
const started = this.markStarting(cwd, sessionId);
|
|
15910
16067
|
const introspect = this.deps.introspect ?? ((dir, at) => introspectRepo(
|
|
@@ -15958,7 +16115,9 @@ var init_manager = __esm({
|
|
|
15958
16115
|
);
|
|
15959
16116
|
} catch (err) {
|
|
15960
16117
|
this.discardSkills(sessionId);
|
|
15961
|
-
|
|
16118
|
+
const failure2 = this.giveBack(repo?.creds, err);
|
|
16119
|
+
await this.recordStartFailure(sessionId);
|
|
16120
|
+
throw failure2;
|
|
15962
16121
|
} finally {
|
|
15963
16122
|
started();
|
|
15964
16123
|
}
|
|
@@ -16128,9 +16287,18 @@ var init_manager = __esm({
|
|
|
16128
16287
|
repo: { ownerId, repositoryId: repository.id, creds }
|
|
16129
16288
|
};
|
|
16130
16289
|
} catch (err) {
|
|
16131
|
-
|
|
16290
|
+
const failure2 = this.giveBack(creds, err);
|
|
16291
|
+
await this.recordStartFailure(sessionId);
|
|
16292
|
+
throw failure2;
|
|
16132
16293
|
}
|
|
16133
16294
|
}
|
|
16295
|
+
/** A failed first start has no live channel; persist its status without ending a live retry. */
|
|
16296
|
+
async recordStartFailure(sessionId) {
|
|
16297
|
+
if (this.live.has(sessionId)) return;
|
|
16298
|
+
await this.updateSession(sessionId, { status: "error" }).catch(
|
|
16299
|
+
(err) => this.deps.log.warn({ err, sessionId }, "failed startup status could not be recorded")
|
|
16300
|
+
);
|
|
16301
|
+
}
|
|
16134
16302
|
/**
|
|
16135
16303
|
* A repository chat's recorded worktree, proven as a resume needs it (§2.9): this runner's record,
|
|
16136
16304
|
* still the same worktree, shaped `<root>/<owner>/<name>/.cayenne/worktrees/chat-<hex>` for this
|
|
@@ -16962,14 +17130,19 @@ var init_manager = __esm({
|
|
|
16962
17130
|
* turns. Returns `{ scheduled: true }` at once and never waits for the renewal, so a
|
|
16963
17131
|
* `session.permission.respond` or `session.interrupt` queued behind it in the session lane runs
|
|
16964
17132
|
* while the renewal waits for the turn to end (§2.1). Progress goes out as `renewal` events.
|
|
16965
|
-
* Refused when
|
|
16966
|
-
* renewed (Codex: no hook can deny the handoff turn's tools; a backend
|
|
17133
|
+
* Refused when no threshold applies (below 100,000 tokens outside an Auto Pilot run), and for a
|
|
17134
|
+
* session that cannot be renewed (Codex: no hook can deny the handoff turn's tools; a backend
|
|
17135
|
+
* without 0042).
|
|
16967
17136
|
*/
|
|
16968
17137
|
async requestRenewal(sessionId, createdBy) {
|
|
16969
17138
|
const live = await this.ensureLive(sessionId, createdBy);
|
|
16970
17139
|
this.assertCodexOwner(sessionId, createdBy);
|
|
16971
17140
|
const r = live.renewal;
|
|
16972
17141
|
if (!this.renewable(live)) throw new Error(RENEWAL_NOT_AVAILABLE);
|
|
17142
|
+
if (r.machine && r.window !== null && !r.busy) {
|
|
17143
|
+
r.autopilot = await this.autopilotRunning(sessionId);
|
|
17144
|
+
await this.ensureMachine(sessionId, live, r.window);
|
|
17145
|
+
}
|
|
16973
17146
|
const machine = r.machine;
|
|
16974
17147
|
if (machine && !machine.enabled) throw new Error(RENEWAL_NOT_AVAILABLE);
|
|
16975
17148
|
if (r.busy) return { scheduled: true };
|
|
@@ -19294,8 +19467,9 @@ var init_manager = __esm({
|
|
|
19294
19467
|
* failed turn, a pending permission), so they are never left waiting for a turn that won't come.
|
|
19295
19468
|
*/
|
|
19296
19469
|
async turnEndRenewal(sessionId, live, r) {
|
|
19297
|
-
const usage = await this.measure(sessionId, live);
|
|
19298
19470
|
const rn = live.renewal;
|
|
19471
|
+
if (this.renewable(live)) rn.autopilot = await this.autopilotRunning(sessionId);
|
|
19472
|
+
const usage = await this.measure(sessionId, live);
|
|
19299
19473
|
await this.noteScope(sessionId, live);
|
|
19300
19474
|
if (rn.busy || live.stopping || live.dropping) return [];
|
|
19301
19475
|
if (live.turns.owed > 0) return [];
|
|
@@ -19461,10 +19635,14 @@ var init_manager = __esm({
|
|
|
19461
19635
|
async ensureMachine(sessionId, live, window) {
|
|
19462
19636
|
const rn = live.renewal;
|
|
19463
19637
|
if (rn.segment === null) return;
|
|
19464
|
-
if (rn.machine && (rn.window === window || rn.machine.state !== "idle")) return;
|
|
19465
19638
|
rn.workspaceThreshold ??= await this.workspaceThreshold();
|
|
19639
|
+
const subject = { autopilot: rn.autopilot, workspaceThreshold: rn.workspaceThreshold };
|
|
19640
|
+
if (rn.machine && rn.window !== null && (rn.window === window || rn.machine.state !== "idle")) {
|
|
19641
|
+
rn.threshold = rn.machine.setThreshold(renewThreshold({ ...subject, window: rn.window }));
|
|
19642
|
+
return;
|
|
19643
|
+
}
|
|
19466
19644
|
rn.window = window;
|
|
19467
|
-
rn.threshold = renewThreshold({
|
|
19645
|
+
rn.threshold = renewThreshold({ ...subject, window });
|
|
19468
19646
|
rn.machine = new RenewalMachine({
|
|
19469
19647
|
segment: rn.segment,
|
|
19470
19648
|
threshold: rn.threshold,
|
|
@@ -19477,15 +19655,55 @@ var init_manager = __esm({
|
|
|
19477
19655
|
if (rn.machine.enabled) {
|
|
19478
19656
|
rn.machine.requestRenew("manual");
|
|
19479
19657
|
} else {
|
|
19480
|
-
await this.renewalEvent(
|
|
19481
|
-
|
|
19482
|
-
phase: "failed",
|
|
19483
|
-
|
|
19484
|
-
|
|
19485
|
-
});
|
|
19658
|
+
await this.renewalEvent(
|
|
19659
|
+
sessionId,
|
|
19660
|
+
{ kind: "event", phase: "failed", segment: rn.segment, error: RENEWAL_NOT_AVAILABLE },
|
|
19661
|
+
"manual"
|
|
19662
|
+
);
|
|
19486
19663
|
}
|
|
19487
19664
|
}
|
|
19488
19665
|
}
|
|
19666
|
+
/**
|
|
19667
|
+
* `ManagerDeps.isAutopilotRunning`; false when absent, when it rejects (logged), and when the
|
|
19668
|
+
* runner no longer serves the session's owner: a run's continuation is a turn no command is
|
|
19669
|
+
* behind, so it must not outlive the owner's place on the roster.
|
|
19670
|
+
*/
|
|
19671
|
+
async autopilotRunning(sessionId) {
|
|
19672
|
+
const ask2 = this.deps.isAutopilotRunning;
|
|
19673
|
+
if (!ask2) return false;
|
|
19674
|
+
try {
|
|
19675
|
+
return await ask2(sessionId) === true && await this.servedOwner(sessionId) !== null;
|
|
19676
|
+
} catch (err) {
|
|
19677
|
+
this.deps.log.warn(
|
|
19678
|
+
{ sessionId, err: err instanceof Error ? err.message : String(err) },
|
|
19679
|
+
"reading the Auto Pilot run failed; renewing as without one"
|
|
19680
|
+
);
|
|
19681
|
+
return false;
|
|
19682
|
+
}
|
|
19683
|
+
}
|
|
19684
|
+
/**
|
|
19685
|
+
* `ManagerDeps.autopilotRun`; null when absent, and when it rejects (logged with the error only,
|
|
19686
|
+
* never the run's text).
|
|
19687
|
+
*/
|
|
19688
|
+
async autopilotBrief(sessionId) {
|
|
19689
|
+
const read2 = this.deps.autopilotRun;
|
|
19690
|
+
if (!read2) return null;
|
|
19691
|
+
try {
|
|
19692
|
+
const owner2 = await this.servedOwner(sessionId);
|
|
19693
|
+
return owner2 === null ? null : await read2(sessionId, owner2);
|
|
19694
|
+
} catch (err) {
|
|
19695
|
+
this.deps.log.warn(
|
|
19696
|
+
{ sessionId, err: err instanceof Error ? err.message : String(err) },
|
|
19697
|
+
"reading the Auto Pilot run failed; renewing as without one"
|
|
19698
|
+
);
|
|
19699
|
+
return null;
|
|
19700
|
+
}
|
|
19701
|
+
}
|
|
19702
|
+
/** The session's owner while this runner serves them, else null. Throws when unreadable. */
|
|
19703
|
+
async servedOwner(sessionId) {
|
|
19704
|
+
const owner2 = await this.sessionOwner(sessionId);
|
|
19705
|
+
return this.serves(owner2) ? owner2 : null;
|
|
19706
|
+
}
|
|
19489
19707
|
/** `workspace_settings.renewal_threshold_tokens`; the column default when it cannot be read. */
|
|
19490
19708
|
async workspaceThreshold() {
|
|
19491
19709
|
const { data, error } = await this.deps.db.from("workspace_settings").select("renewal_threshold_tokens").eq("workspace_id", this.deps.workspaceId).maybeSingle();
|
|
@@ -19519,15 +19737,37 @@ var init_manager = __esm({
|
|
|
19519
19737
|
rn.busy = true;
|
|
19520
19738
|
try {
|
|
19521
19739
|
if (!machine || n === null) return;
|
|
19740
|
+
if (!machine.retrying || !rn.cause) {
|
|
19741
|
+
const cause = machine.cause;
|
|
19742
|
+
rn.cause = {
|
|
19743
|
+
autopilot: rn.autopilot,
|
|
19744
|
+
reason: renewalReason({
|
|
19745
|
+
autopilot: rn.autopilot,
|
|
19746
|
+
manual: cause === "manual",
|
|
19747
|
+
visibility: cause === "visibility",
|
|
19748
|
+
compacted: false
|
|
19749
|
+
})
|
|
19750
|
+
};
|
|
19751
|
+
}
|
|
19752
|
+
const reason = rn.cause.reason;
|
|
19522
19753
|
let prompt = null;
|
|
19523
19754
|
for (const a of start2) {
|
|
19524
|
-
if (a.kind === "event") await this.renewalEvent(sessionId, a);
|
|
19755
|
+
if (a.kind === "event") await this.renewalEvent(sessionId, a, reason);
|
|
19525
19756
|
else if (a.kind === "runHandoffTurn") prompt = a.prompt;
|
|
19526
19757
|
}
|
|
19527
19758
|
if (prompt === null) return;
|
|
19528
19759
|
const result = await this.runHandoffTurn(live, prompt);
|
|
19529
19760
|
if (gone2()) return;
|
|
19530
|
-
const
|
|
19761
|
+
const autopilot = rn.cause.autopilot;
|
|
19762
|
+
let run = autopilot ? await this.autopilotBrief(sessionId) : null;
|
|
19763
|
+
if (gone2()) return;
|
|
19764
|
+
const handoff = await this.handoffFor(
|
|
19765
|
+
sessionId,
|
|
19766
|
+
live,
|
|
19767
|
+
n,
|
|
19768
|
+
result,
|
|
19769
|
+
run ? [autopilotAppendix(run)] : []
|
|
19770
|
+
);
|
|
19531
19771
|
try {
|
|
19532
19772
|
for (const a of machine.handoffCaptured(handoff)) {
|
|
19533
19773
|
if (a.kind === "saveHandoff") {
|
|
@@ -19537,7 +19777,7 @@ var init_manager = __esm({
|
|
|
19537
19777
|
() => this.deps.db.from("chat_segments").update({ handoff: a.handoff, handoff_source: a.handoffSource }).eq("session_id", sessionId).eq("idx", a.segment)
|
|
19538
19778
|
);
|
|
19539
19779
|
} else if (a.kind === "event") {
|
|
19540
|
-
await this.renewalEvent(sessionId, a);
|
|
19780
|
+
await this.renewalEvent(sessionId, a, reason);
|
|
19541
19781
|
} else if (a.kind === "startQuery") {
|
|
19542
19782
|
next = await this.startRenewedQuery(sessionId, live);
|
|
19543
19783
|
}
|
|
@@ -19546,10 +19786,13 @@ var init_manager = __esm({
|
|
|
19546
19786
|
const providerSessionId = next?.providerSessionId;
|
|
19547
19787
|
if (!next || !providerSessionId) throw new Error("the new query has no session id");
|
|
19548
19788
|
const first = rn.queued[0] ?? null;
|
|
19789
|
+
if (run && !first && !await this.autopilotRunning(sessionId)) run = null;
|
|
19790
|
+
if (gone2()) return;
|
|
19549
19791
|
const actions = machine.queryStarted({
|
|
19550
19792
|
providerSessionId,
|
|
19551
19793
|
queuedPrompt: first?.text ?? null,
|
|
19552
|
-
lastMemberPrompt: rn.lastPrompt
|
|
19794
|
+
lastMemberPrompt: autopilot ? null : rn.lastPrompt,
|
|
19795
|
+
autopilotContinuation: run ? this.secrets.redact(autopilotContinuation(run)) : null
|
|
19553
19796
|
});
|
|
19554
19797
|
await this.rotateSegment(sessionId, live, n, handoff.text, actions, next, first);
|
|
19555
19798
|
} catch (err) {
|
|
@@ -19617,12 +19860,12 @@ var init_manager = __esm({
|
|
|
19617
19860
|
* before the memory pipeline) or the segment's last messages; plus the deterministic appendix.
|
|
19618
19861
|
* Scrubbed (D50) before it is stored and before it becomes segment `n + 1`'s first input.
|
|
19619
19862
|
*/
|
|
19620
|
-
async handoffFor(sessionId, live, n, result) {
|
|
19863
|
+
async handoffFor(sessionId, live, n, result, extra = []) {
|
|
19621
19864
|
const fromModel = result.kind === "ok" && estimateTokens(result.text.trim()) >= MIN_HANDOFF_TOKENS;
|
|
19622
19865
|
const messages = fromModel ? [] : await this.segmentMessages(sessionId, n);
|
|
19623
19866
|
const ceiling = live.roots.find((root) => isAtOrBelow3(live.cwd, root)) ?? live.cwd;
|
|
19624
19867
|
const git = await this.appendixGitState(live.cwd, ceiling).catch(() => null);
|
|
19625
|
-
const appendix = buildAppendix(git, live.renewal.todos, [], []);
|
|
19868
|
+
const appendix = buildAppendix(git, live.renewal.todos, [], [], extra);
|
|
19626
19869
|
const handoff = resolveHandoff({ result, digest: null, messages, appendix });
|
|
19627
19870
|
return { ...handoff, text: this.secrets.redact(handoff.text) };
|
|
19628
19871
|
}
|
|
@@ -19770,7 +20013,8 @@ var init_manager = __esm({
|
|
|
19770
20013
|
case "event":
|
|
19771
20014
|
await this.renewalEvent(
|
|
19772
20015
|
sessionId,
|
|
19773
|
-
a.phase === "done" ? { ...a, toTokens: await this.firstReading(next) } : a
|
|
20016
|
+
a.phase === "done" ? { ...a, toTokens: await this.firstReading(next) } : a,
|
|
20017
|
+
live.renewal.cause?.reason
|
|
19774
20018
|
);
|
|
19775
20019
|
break;
|
|
19776
20020
|
default:
|
|
@@ -20044,7 +20288,8 @@ ${text3}`;
|
|
|
20044
20288
|
if (machine.state !== "failed" && live.renewal.machine === machine) live.renewal.machine = null;
|
|
20045
20289
|
for (const a of actions) {
|
|
20046
20290
|
if (a.kind === "clearHandoff") await this.clearHandoff(sessionId, a.segment);
|
|
20047
|
-
else if (a.kind === "event")
|
|
20291
|
+
else if (a.kind === "event")
|
|
20292
|
+
await this.renewalEvent(sessionId, a, live.renewal.cause?.reason);
|
|
20048
20293
|
}
|
|
20049
20294
|
if (live.renewal.scopeRenewalOwed && live.renewal.queued.length > 0) {
|
|
20050
20295
|
try {
|
|
@@ -20095,16 +20340,23 @@ ${text3}`;
|
|
|
20095
20340
|
throw new Error(`PROVIDER_ERROR: ${what} failed: ${error.message}`);
|
|
20096
20341
|
}
|
|
20097
20342
|
}
|
|
20098
|
-
/**
|
|
20099
|
-
|
|
20343
|
+
/**
|
|
20344
|
+
* A machine `event` action as a `renewal` session event (protocol §3), carrying the renewal's
|
|
20345
|
+
* `reason` when it is known; `done` is followed by one `session.renewed` naming it.
|
|
20346
|
+
*/
|
|
20347
|
+
async renewalEvent(sessionId, a, reason) {
|
|
20100
20348
|
await this.broadcast(sessionId, {
|
|
20101
20349
|
type: "renewal",
|
|
20102
20350
|
phase: a.phase,
|
|
20103
20351
|
segment: a.segment,
|
|
20104
20352
|
...a.fromTokens === void 0 ? {} : { fromTokens: a.fromTokens },
|
|
20105
20353
|
...a.toTokens === void 0 ? {} : { toTokens: a.toTokens },
|
|
20106
|
-
...a.error === void 0 ? {} : { error: this.cap(a.error) }
|
|
20354
|
+
...a.error === void 0 ? {} : { error: this.cap(a.error) },
|
|
20355
|
+
...reason === void 0 ? {} : { reason }
|
|
20107
20356
|
});
|
|
20357
|
+
if (a.phase === "done" && reason !== void 0) {
|
|
20358
|
+
await this.broadcast(sessionId, { type: "session.renewed", segment: a.segment, reason });
|
|
20359
|
+
}
|
|
20108
20360
|
}
|
|
20109
20361
|
/**
|
|
20110
20362
|
* Expire a request the session settled itself, and say so when this write is what settled the
|
|
@@ -22423,7 +22675,9 @@ async function compose(cfg, lock, host, preflight) {
|
|
|
22423
22675
|
publishWrites: makePublishWrites(db),
|
|
22424
22676
|
poll: prPoll,
|
|
22425
22677
|
gh: runGh
|
|
22426
|
-
}
|
|
22678
|
+
},
|
|
22679
|
+
// Auto Pilot 03: an active run renews at 40% of the window and continues on its own.
|
|
22680
|
+
...autopilotRunsReader(db, logger)
|
|
22427
22681
|
});
|
|
22428
22682
|
let ownerServed = liveRoster.serves(identity.userId);
|
|
22429
22683
|
let ownerTransition = Promise.resolve();
|
|
@@ -22813,6 +23067,7 @@ var init_start = __esm({
|
|
|
22813
23067
|
init_roster();
|
|
22814
23068
|
init_registry();
|
|
22815
23069
|
init_attachments2();
|
|
23070
|
+
init_autopilot_runs();
|
|
22816
23071
|
init_handlers();
|
|
22817
23072
|
init_manager();
|
|
22818
23073
|
init_repo_sessions();
|
|
@@ -23182,7 +23437,9 @@ var isInside = (dir, path) => {
|
|
|
23182
23437
|
};
|
|
23183
23438
|
function isTemporary(path, tmpDir = tmpdir()) {
|
|
23184
23439
|
const roots = [resolve(tmpDir), realOrSpelled(tmpDir)];
|
|
23185
|
-
return [resolve(path), realOrSpelled(path)].some(
|
|
23440
|
+
return [resolve(path), realOrSpelled(path)].some(
|
|
23441
|
+
(p) => p.split(sep).includes("cmux-cli-shims") || roots.some((root) => isInside(root, p))
|
|
23442
|
+
);
|
|
23186
23443
|
}
|
|
23187
23444
|
async function resolveClaude(exec4, tmpDir) {
|
|
23188
23445
|
const candidates = await whichAll(exec4, "claude");
|
|
@@ -23199,8 +23456,9 @@ Put the claude CLI itself on PATH, or run this from another terminal: https://cl
|
|
|
23199
23456
|
"claude CLI not found on PATH. The runner spawns it to run sessions.\nInstall it first: https://claude.com/claude-code"
|
|
23200
23457
|
);
|
|
23201
23458
|
}
|
|
23202
|
-
function buildPathEnv(nodePath, claudePath) {
|
|
23459
|
+
function buildPathEnv(nodePath, claudePath, codexPath) {
|
|
23203
23460
|
const dirs = ["/usr/bin", "/bin", "/usr/sbin", "/sbin", dirname2(nodePath), dirname2(claudePath)];
|
|
23461
|
+
if (codexPath) dirs.push(dirname2(codexPath));
|
|
23204
23462
|
return [...new Set(dirs)].join(":");
|
|
23205
23463
|
}
|
|
23206
23464
|
function readInstalledVersion(appDir, pkg) {
|
|
@@ -23222,6 +23480,9 @@ function readInstallRecord(cayenneHome2) {
|
|
|
23222
23480
|
}
|
|
23223
23481
|
async function installApp(deps) {
|
|
23224
23482
|
const claudePath = await resolveClaude(deps.exec, deps.tmpDir ?? tmpdir());
|
|
23483
|
+
const codexPath = (await whichAll(deps.exec, "codex")).find(
|
|
23484
|
+
(path) => !isTemporary(path, deps.tmpDir ?? tmpdir())
|
|
23485
|
+
);
|
|
23225
23486
|
const appDir = join6(deps.cayenneHome, "app");
|
|
23226
23487
|
mkdirSync3(appDir, { recursive: true, mode: 448 });
|
|
23227
23488
|
const install = await deps.exec("npm", [
|
|
@@ -23246,7 +23507,7 @@ async function installApp(deps) {
|
|
|
23246
23507
|
claudePath,
|
|
23247
23508
|
cliPath,
|
|
23248
23509
|
appDir,
|
|
23249
|
-
pathEnv: buildPathEnv(nodePath, claudePath),
|
|
23510
|
+
pathEnv: buildPathEnv(nodePath, claudePath, codexPath),
|
|
23250
23511
|
installedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
23251
23512
|
};
|
|
23252
23513
|
const binDir = join6(deps.cayenneHome, "bin");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jpcasabiancai/cayenne",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.14",
|
|
4
4
|
"files": [
|
|
5
5
|
"dist/cli.js"
|
|
6
6
|
],
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"test": "vitest run"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@anthropic-ai/claude-agent-sdk": "0.3.
|
|
36
|
+
"@anthropic-ai/claude-agent-sdk": "0.3.312",
|
|
37
37
|
"@anthropic-ai/sdk": "0.123.0",
|
|
38
38
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
39
39
|
"@supabase/supabase-js": "2.115.0",
|