@cabane/companion 0.6.18 → 0.6.20
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 +238 -649
- package/dist/runtime.js +220 -631
- package/package.json +3 -2
package/dist/cli.js
CHANGED
|
@@ -316,14 +316,14 @@ function localAgentConfig(cfg, agent) {
|
|
|
316
316
|
return map[agent.agentId] ?? map[agent.agentUsername] ?? map[`${agent.workspaceSlug}/${agent.agentUsername}`] ?? {};
|
|
317
317
|
}
|
|
318
318
|
function loadConfig() {
|
|
319
|
-
const
|
|
320
|
-
if (!existsSync(
|
|
319
|
+
const path = configPath();
|
|
320
|
+
if (!existsSync(path)) return null;
|
|
321
321
|
let raw;
|
|
322
322
|
try {
|
|
323
|
-
raw = readFileSync(
|
|
323
|
+
raw = readFileSync(path, "utf8");
|
|
324
324
|
} catch (err) {
|
|
325
325
|
throw new ConfigError(
|
|
326
|
-
`couldn't read ${
|
|
326
|
+
`couldn't read ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
327
327
|
);
|
|
328
328
|
}
|
|
329
329
|
if (raw.trim().length === 0) return null;
|
|
@@ -332,7 +332,7 @@ function loadConfig() {
|
|
|
332
332
|
parsed = JSON.parse(raw);
|
|
333
333
|
} catch (err) {
|
|
334
334
|
throw new ConfigError(
|
|
335
|
-
`${
|
|
335
|
+
`${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}`
|
|
336
336
|
);
|
|
337
337
|
}
|
|
338
338
|
const result = companionConfigSchema.safeParse(parsed);
|
|
@@ -340,22 +340,22 @@ function loadConfig() {
|
|
|
340
340
|
const agentIssue = result.error.issues.find((i) => i.path[0] === "agents");
|
|
341
341
|
if (agentIssue) {
|
|
342
342
|
throw new ConfigError(
|
|
343
|
-
`${
|
|
343
|
+
`${path}: invalid "agents" config at \`${agentIssue.path.join(".")}\` \u2014 ${agentIssue.message}. Fix the agents block in the config and retry.`
|
|
344
344
|
);
|
|
345
345
|
}
|
|
346
346
|
throw new ConfigError(
|
|
347
|
-
`${
|
|
347
|
+
`${path} is from an incompatible or older version of the companion, or was hand-edited. Run \`cabane-companion pair\` to re-pair, or \`cabane-companion logout --purge\` to reset.`
|
|
348
348
|
);
|
|
349
349
|
}
|
|
350
350
|
return result.data;
|
|
351
351
|
}
|
|
352
352
|
function loadConfigTolerant() {
|
|
353
|
-
const
|
|
353
|
+
const path = configPath();
|
|
354
354
|
const empty = {};
|
|
355
|
-
if (!existsSync(
|
|
355
|
+
if (!existsSync(path)) return { local: empty, note: null };
|
|
356
356
|
let raw;
|
|
357
357
|
try {
|
|
358
|
-
raw = readFileSync(
|
|
358
|
+
raw = readFileSync(path, "utf8");
|
|
359
359
|
} catch {
|
|
360
360
|
return { local: empty, note: null };
|
|
361
361
|
}
|
|
@@ -364,7 +364,7 @@ function loadConfigTolerant() {
|
|
|
364
364
|
try {
|
|
365
365
|
parsed = JSON.parse(raw);
|
|
366
366
|
} catch {
|
|
367
|
-
return { local: empty, note: `${
|
|
367
|
+
return { local: empty, note: `${path} was unreadable (invalid JSON) and has been reset.` };
|
|
368
368
|
}
|
|
369
369
|
const strict = companionConfigSchema.safeParse(parsed);
|
|
370
370
|
if (strict.success) {
|
|
@@ -390,24 +390,24 @@ function loadConfigTolerant() {
|
|
|
390
390
|
}
|
|
391
391
|
return {
|
|
392
392
|
local,
|
|
393
|
-
note: `the existing ${
|
|
393
|
+
note: `the existing ${path} was from an older or incompatible companion; re-pairing rewrote it.`
|
|
394
394
|
};
|
|
395
395
|
}
|
|
396
396
|
function saveConfig(cfg) {
|
|
397
|
-
const
|
|
398
|
-
mkdirSync(dirname(
|
|
397
|
+
const path = configPath();
|
|
398
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
399
399
|
try {
|
|
400
400
|
chmodSync(cabaneDir(), 448);
|
|
401
401
|
} catch {
|
|
402
402
|
}
|
|
403
|
-
const tmp = `${
|
|
403
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
404
404
|
try {
|
|
405
405
|
writeFileSync(tmp, JSON.stringify(cfg, null, 2) + "\n", { mode: 384 });
|
|
406
406
|
try {
|
|
407
407
|
chmodSync(tmp, 384);
|
|
408
408
|
} catch {
|
|
409
409
|
}
|
|
410
|
-
renameSync(tmp,
|
|
410
|
+
renameSync(tmp, path);
|
|
411
411
|
} catch (err) {
|
|
412
412
|
try {
|
|
413
413
|
rmSync(tmp, { force: true });
|
|
@@ -426,9 +426,9 @@ function requireConfig() {
|
|
|
426
426
|
return cfg;
|
|
427
427
|
}
|
|
428
428
|
function deleteConfig() {
|
|
429
|
-
const
|
|
430
|
-
if (existsSync(
|
|
431
|
-
writeFileSync(
|
|
429
|
+
const path = configPath();
|
|
430
|
+
if (existsSync(path)) {
|
|
431
|
+
writeFileSync(path, "", { mode: 384 });
|
|
432
432
|
}
|
|
433
433
|
}
|
|
434
434
|
|
|
@@ -462,8 +462,8 @@ function consoleMessageFormat(log, messageKey) {
|
|
|
462
462
|
var cached = null;
|
|
463
463
|
function getLogger() {
|
|
464
464
|
if (cached) return cached;
|
|
465
|
-
const
|
|
466
|
-
mkdirSync2(dirname2(
|
|
465
|
+
const path = companionLogPath();
|
|
466
|
+
mkdirSync2(dirname2(path), { recursive: true });
|
|
467
467
|
const streams = [];
|
|
468
468
|
if (process.env.CABANE_COMPANION_DAEMON !== "1") {
|
|
469
469
|
const consoleStream = pretty({
|
|
@@ -473,7 +473,7 @@ function getLogger() {
|
|
|
473
473
|
});
|
|
474
474
|
streams.push({ level: "info", stream: consoleStream });
|
|
475
475
|
}
|
|
476
|
-
streams.push({ level: "debug", stream: createWriteStream(
|
|
476
|
+
streams.push({ level: "debug", stream: createWriteStream(path, { flags: "a" }) });
|
|
477
477
|
cached = pino({ level: "debug" }, pino.multistream(streams));
|
|
478
478
|
return cached;
|
|
479
479
|
}
|
|
@@ -634,9 +634,9 @@ function serialize(state) {
|
|
|
634
634
|
return JSON.stringify(state, null, 2) + "\n";
|
|
635
635
|
}
|
|
636
636
|
function writeRuntimeState(state) {
|
|
637
|
-
const
|
|
637
|
+
const path = runtimePath();
|
|
638
638
|
mkdirSync3(cabaneDir(), { recursive: true });
|
|
639
|
-
writeFileSync2(
|
|
639
|
+
writeFileSync2(path, serialize(state), "utf8");
|
|
640
640
|
}
|
|
641
641
|
function acquireRuntimeState(state) {
|
|
642
642
|
const live = readLiveRuntimeState();
|
|
@@ -656,15 +656,15 @@ function acquireRuntimeState(state) {
|
|
|
656
656
|
return { acquired: true };
|
|
657
657
|
}
|
|
658
658
|
function clearRuntimeState() {
|
|
659
|
-
const
|
|
660
|
-
if (existsSync2(
|
|
659
|
+
const path = runtimePath();
|
|
660
|
+
if (existsSync2(path)) rmSync2(path, { force: true });
|
|
661
661
|
}
|
|
662
662
|
function readLiveRuntimeState() {
|
|
663
|
-
const
|
|
664
|
-
if (!existsSync2(
|
|
663
|
+
const path = runtimePath();
|
|
664
|
+
if (!existsSync2(path)) return null;
|
|
665
665
|
let parsed;
|
|
666
666
|
try {
|
|
667
|
-
parsed = JSON.parse(readFileSync2(
|
|
667
|
+
parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
668
668
|
} catch {
|
|
669
669
|
return null;
|
|
670
670
|
}
|
|
@@ -795,11 +795,11 @@ function credentialsPath() {
|
|
|
795
795
|
}
|
|
796
796
|
var credentialStoreSchema = z3.record(z3.string(), z3.string());
|
|
797
797
|
function load() {
|
|
798
|
-
const
|
|
799
|
-
if (!existsSync3(
|
|
798
|
+
const path = credentialsPath();
|
|
799
|
+
if (!existsSync3(path)) return {};
|
|
800
800
|
let raw;
|
|
801
801
|
try {
|
|
802
|
-
raw = readFileSync3(
|
|
802
|
+
raw = readFileSync3(path, "utf8");
|
|
803
803
|
} catch {
|
|
804
804
|
return {};
|
|
805
805
|
}
|
|
@@ -812,20 +812,20 @@ function load() {
|
|
|
812
812
|
}
|
|
813
813
|
}
|
|
814
814
|
function save(map) {
|
|
815
|
-
const
|
|
816
|
-
mkdirSync5(dirname3(
|
|
815
|
+
const path = credentialsPath();
|
|
816
|
+
mkdirSync5(dirname3(path), { recursive: true });
|
|
817
817
|
try {
|
|
818
818
|
chmodSync2(cabaneDir(), 448);
|
|
819
819
|
} catch {
|
|
820
820
|
}
|
|
821
|
-
const tmp = `${
|
|
821
|
+
const tmp = `${path}.${process.pid}.tmp`;
|
|
822
822
|
try {
|
|
823
823
|
writeFileSync3(tmp, JSON.stringify(map, null, 2) + "\n", { mode: 384 });
|
|
824
824
|
try {
|
|
825
825
|
chmodSync2(tmp, 384);
|
|
826
826
|
} catch {
|
|
827
827
|
}
|
|
828
|
-
renameSync2(tmp,
|
|
828
|
+
renameSync2(tmp, path);
|
|
829
829
|
} catch (err) {
|
|
830
830
|
try {
|
|
831
831
|
rmSync3(tmp, { force: true });
|
|
@@ -857,8 +857,8 @@ function pruneCredentials(keepAgentIds) {
|
|
|
857
857
|
return removed;
|
|
858
858
|
}
|
|
859
859
|
function clearCredentials() {
|
|
860
|
-
const
|
|
861
|
-
if (existsSync3(
|
|
860
|
+
const path = credentialsPath();
|
|
861
|
+
if (existsSync3(path)) writeFileSync3(path, "", { mode: 384 });
|
|
862
862
|
}
|
|
863
863
|
|
|
864
864
|
// src/commands/logout.ts
|
|
@@ -897,10 +897,10 @@ async function logout(opts = {}) {
|
|
|
897
897
|
function trimBase(baseUrl) {
|
|
898
898
|
return baseUrl.endsWith("/") ? baseUrl.slice(0, -1) : baseUrl;
|
|
899
899
|
}
|
|
900
|
-
async function postJson(baseUrl,
|
|
900
|
+
async function postJson(baseUrl, path, body) {
|
|
901
901
|
let res;
|
|
902
902
|
try {
|
|
903
|
-
res = await fetch(`${trimBase(baseUrl)}${
|
|
903
|
+
res = await fetch(`${trimBase(baseUrl)}${path}`, {
|
|
904
904
|
method: "POST",
|
|
905
905
|
headers: { "content-type": "application/json", accept: "application/json" },
|
|
906
906
|
body: JSON.stringify(body)
|
|
@@ -920,7 +920,7 @@ async function postJson(baseUrl, path3, body) {
|
|
|
920
920
|
}
|
|
921
921
|
}
|
|
922
922
|
if (res.status >= 400) {
|
|
923
|
-
if (res.status === 404 &&
|
|
923
|
+
if (res.status === 404 && path.endsWith("/code")) {
|
|
924
924
|
throw new CompanionError(
|
|
925
925
|
`no device-flow pairing endpoint at ${baseUrl}. Either that server predates \`cabane-companion pair\`, or it isn't a Cabane API origin \u2014 the hosted app is https://app.cabane.ai (pass \`--server <url>\` for your own instance).`
|
|
926
926
|
);
|
|
@@ -1458,12 +1458,12 @@ function clampLimit(raw, fallback, max = 200) {
|
|
|
1458
1458
|
if (!Number.isFinite(n) || n <= 0) return fallback;
|
|
1459
1459
|
return Math.min(Math.floor(n), max);
|
|
1460
1460
|
}
|
|
1461
|
-
function tailFile(
|
|
1462
|
-
if (!existsSync4(
|
|
1461
|
+
function tailFile(path, lines) {
|
|
1462
|
+
if (!existsSync4(path)) return [];
|
|
1463
1463
|
const MAX_BYTES = 256 * 1024;
|
|
1464
1464
|
let fd;
|
|
1465
1465
|
try {
|
|
1466
|
-
fd = openSync3(
|
|
1466
|
+
fd = openSync3(path, "r");
|
|
1467
1467
|
const size = fstatSync(fd).size;
|
|
1468
1468
|
const start2 = Math.max(0, size - MAX_BYTES);
|
|
1469
1469
|
const len = size - start2;
|
|
@@ -1574,8 +1574,8 @@ var CabaneApi = class {
|
|
|
1574
1574
|
// One HTTP attempt — no retry. Throws `ApiError` on a 4xx/5xx response and
|
|
1575
1575
|
// rethrows transport errors (fetch reject) unchanged so the caller's retry
|
|
1576
1576
|
// logic can classify them.
|
|
1577
|
-
async attempt(method,
|
|
1578
|
-
const res = await fetch(`${this.base}${
|
|
1577
|
+
async attempt(method, path, body, signal) {
|
|
1578
|
+
const res = await fetch(`${this.base}${path}`, {
|
|
1579
1579
|
method,
|
|
1580
1580
|
headers: {
|
|
1581
1581
|
Authorization: `Bearer ${this.opts.token}`,
|
|
@@ -1601,12 +1601,12 @@ var CabaneApi = class {
|
|
|
1601
1601
|
}
|
|
1602
1602
|
return parsed;
|
|
1603
1603
|
}
|
|
1604
|
-
async request(method,
|
|
1604
|
+
async request(method, path, body, opts = {}) {
|
|
1605
1605
|
const { signal, retry = false } = opts;
|
|
1606
1606
|
const maxAttempts = retry ? RETRY_BACKOFF_MS.length + 1 : 1;
|
|
1607
1607
|
for (let attempt = 1; ; attempt++) {
|
|
1608
1608
|
try {
|
|
1609
|
-
return await this.attempt(method,
|
|
1609
|
+
return await this.attempt(method, path, body, signal);
|
|
1610
1610
|
} catch (err) {
|
|
1611
1611
|
if (attempt >= maxAttempts || signal?.aborted || !isRetryable(err)) throw err;
|
|
1612
1612
|
await sleep2(RETRY_BACKOFF_MS[attempt - 1], signal);
|
|
@@ -1632,9 +1632,9 @@ var CabaneApi = class {
|
|
|
1632
1632
|
// delivers it once the API returns. `(turnId, seq)` is the server's
|
|
1633
1633
|
// idempotency key, so a replay whose original POST's fate is unknown
|
|
1634
1634
|
// converges instead of duplicating.
|
|
1635
|
-
async durableCommit(kind,
|
|
1635
|
+
async durableCommit(kind, path, body, turnId, seq, signal) {
|
|
1636
1636
|
try {
|
|
1637
|
-
await this.request("POST",
|
|
1637
|
+
await this.request("POST", path, body, {
|
|
1638
1638
|
retry: true,
|
|
1639
1639
|
...signal ? { signal } : {}
|
|
1640
1640
|
});
|
|
@@ -1643,7 +1643,7 @@ var CabaneApi = class {
|
|
|
1643
1643
|
if (!outbox) throw err;
|
|
1644
1644
|
if (signal?.aborted || isAbortError(err)) throw err;
|
|
1645
1645
|
if (!isRetryable(err)) throw err;
|
|
1646
|
-
outbox.persist({ enqueuedAt: Date.now(), turnId, seq, method: "POST", path
|
|
1646
|
+
outbox.persist({ enqueuedAt: Date.now(), turnId, seq, method: "POST", path, body, kind });
|
|
1647
1647
|
this.opts.log?.warn(
|
|
1648
1648
|
{ kind, turnId, seq, err: err instanceof Error ? err.message : String(err) },
|
|
1649
1649
|
"companion: commit queued to outbox after transient failure (will drain when the API returns)"
|
|
@@ -1795,12 +1795,12 @@ var CabaneApi = class {
|
|
|
1795
1795
|
// left best-effort: it's lower-stakes and self-heals on the next turn, so it
|
|
1796
1796
|
// stays a single-shot PATCH and is deliberately out of CT93's scope.
|
|
1797
1797
|
setActiveRun(workspaceId, conversationId, agentId, body) {
|
|
1798
|
-
const
|
|
1798
|
+
const path = `/api/workspaces/${workspaceId}/conversations/${conversationId}/participants/agents/${agentId}/active-run`;
|
|
1799
1799
|
const touchesFlag = Object.prototype.hasOwnProperty.call(body, "activeRunStartedAt");
|
|
1800
1800
|
if (touchesFlag && this.opts.outbox) {
|
|
1801
|
-
return this.durableActiveRunWrite(
|
|
1801
|
+
return this.durableActiveRunWrite(path, conversationId, agentId, body);
|
|
1802
1802
|
}
|
|
1803
|
-
return this.request("PATCH",
|
|
1803
|
+
return this.request("PATCH", path, body);
|
|
1804
1804
|
}
|
|
1805
1805
|
// CT93: send-or-enqueue for an active-run flag write, with last-writer-wins
|
|
1806
1806
|
// across the (conversation, agent) pair. Mirrors `durableCommit`, with two
|
|
@@ -1814,11 +1814,11 @@ var CabaneApi = class {
|
|
|
1814
1814
|
// later and clobber the state we just wrote (the cross-turn race: turn N's
|
|
1815
1815
|
// queued clear vs. turn N+1's live set). Combined with persist-overwrites-
|
|
1816
1816
|
// by-key, this is the full last-writer-wins guarantee.
|
|
1817
|
-
async durableActiveRunWrite(
|
|
1817
|
+
async durableActiveRunWrite(path, conversationId, agentId, body) {
|
|
1818
1818
|
const outbox = this.opts.outbox;
|
|
1819
1819
|
const key = activeRunOutboxKey(conversationId, agentId);
|
|
1820
1820
|
try {
|
|
1821
|
-
await this.request("PATCH",
|
|
1821
|
+
await this.request("PATCH", path, body, { retry: true });
|
|
1822
1822
|
outbox?.remove(key, ACTIVE_RUN_OUTBOX_SEQ);
|
|
1823
1823
|
} catch (err) {
|
|
1824
1824
|
if (!outbox) throw err;
|
|
@@ -1831,7 +1831,7 @@ var CabaneApi = class {
|
|
|
1831
1831
|
turnId: key,
|
|
1832
1832
|
seq: ACTIVE_RUN_OUTBOX_SEQ,
|
|
1833
1833
|
method: "PATCH",
|
|
1834
|
-
path
|
|
1834
|
+
path,
|
|
1835
1835
|
body,
|
|
1836
1836
|
kind: "active-run"
|
|
1837
1837
|
});
|
|
@@ -1919,8 +1919,8 @@ var CabaneApi = class {
|
|
|
1919
1919
|
// shared resolver the in-app path uses. Omitting it (older call sites) returns
|
|
1920
1920
|
// the agent default — graceful degradation, no version coupling.
|
|
1921
1921
|
getAgentSelf(conversationId) {
|
|
1922
|
-
const
|
|
1923
|
-
return this.request("GET",
|
|
1922
|
+
const path = conversationId ? `/api/agent/me?conversationId=${encodeURIComponent(conversationId)}` : `/api/agent/me`;
|
|
1923
|
+
return this.request("GET", path);
|
|
1924
1924
|
}
|
|
1925
1925
|
// The companion fetches the triggering message body by listing the
|
|
1926
1926
|
// conversation's messages and finding the one with `id === messageId`.
|
|
@@ -1975,8 +1975,8 @@ var DeviceApi = class {
|
|
|
1975
1975
|
get base() {
|
|
1976
1976
|
return this.opts.baseUrl.endsWith("/") ? this.opts.baseUrl.slice(0, -1) : this.opts.baseUrl;
|
|
1977
1977
|
}
|
|
1978
|
-
async request(method,
|
|
1979
|
-
const res = await fetch(`${this.base}${
|
|
1978
|
+
async request(method, path, body) {
|
|
1979
|
+
const res = await fetch(`${this.base}${path}`, {
|
|
1980
1980
|
method,
|
|
1981
1981
|
headers: {
|
|
1982
1982
|
Authorization: `Bearer ${this.opts.deviceToken}`,
|
|
@@ -2030,15 +2030,15 @@ function pathFor(workspaceId) {
|
|
|
2030
2030
|
return join7(cabaneDir(), "cursors", encodeURIComponent(workspaceId));
|
|
2031
2031
|
}
|
|
2032
2032
|
function readCursor(workspaceId) {
|
|
2033
|
-
const
|
|
2034
|
-
if (!existsSync5(
|
|
2035
|
-
const raw = readFileSync4(
|
|
2033
|
+
const path = pathFor(workspaceId);
|
|
2034
|
+
if (!existsSync5(path)) return null;
|
|
2035
|
+
const raw = readFileSync4(path, "utf8").trim();
|
|
2036
2036
|
return raw.length > 0 ? raw : null;
|
|
2037
2037
|
}
|
|
2038
2038
|
function writeCursor(workspaceId, eventId) {
|
|
2039
|
-
const
|
|
2039
|
+
const path = pathFor(workspaceId);
|
|
2040
2040
|
mkdirSync6(join7(cabaneDir(), "cursors"), { recursive: true });
|
|
2041
|
-
writeFileSync4(
|
|
2041
|
+
writeFileSync4(path, eventId + "\n", "utf8");
|
|
2042
2042
|
}
|
|
2043
2043
|
|
|
2044
2044
|
// src/cursor-tracker.ts
|
|
@@ -2091,10 +2091,10 @@ function pathFor2(log, workspaceId) {
|
|
|
2091
2091
|
return join8(dir(log), encodeURIComponent(workspaceId));
|
|
2092
2092
|
}
|
|
2093
2093
|
function readIds(log, workspaceId) {
|
|
2094
|
-
const
|
|
2095
|
-
if (!existsSync6(
|
|
2094
|
+
const path = pathFor2(log, workspaceId);
|
|
2095
|
+
if (!existsSync6(path)) return [];
|
|
2096
2096
|
try {
|
|
2097
|
-
return readFileSync5(
|
|
2097
|
+
return readFileSync5(path, "utf8").split("\n").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2098
2098
|
} catch {
|
|
2099
2099
|
return [];
|
|
2100
2100
|
}
|
|
@@ -2131,10 +2131,10 @@ function resumePathFor(workspaceId) {
|
|
|
2131
2131
|
}
|
|
2132
2132
|
function readResumeCounts(workspaceId) {
|
|
2133
2133
|
const out = /* @__PURE__ */ new Map();
|
|
2134
|
-
const
|
|
2135
|
-
if (!existsSync6(
|
|
2134
|
+
const path = resumePathFor(workspaceId);
|
|
2135
|
+
if (!existsSync6(path)) return out;
|
|
2136
2136
|
try {
|
|
2137
|
-
for (const line of readFileSync5(
|
|
2137
|
+
for (const line of readFileSync5(path, "utf8").split("\n")) {
|
|
2138
2138
|
const trimmed = line.trim();
|
|
2139
2139
|
if (!trimmed) continue;
|
|
2140
2140
|
const tab = trimmed.lastIndexOf(" ");
|
|
@@ -4726,6 +4726,16 @@ function readMcpError(error) {
|
|
|
4726
4726
|
}
|
|
4727
4727
|
|
|
4728
4728
|
// packages/agent-runtime/src/codex/session.ts
|
|
4729
|
+
function fingerprintPrompt(prompt) {
|
|
4730
|
+
let fnv = 2166136261;
|
|
4731
|
+
let djb = 5381;
|
|
4732
|
+
for (let i = 0; i < prompt.length; i += 1) {
|
|
4733
|
+
const code = prompt.charCodeAt(i);
|
|
4734
|
+
fnv = Math.imul(fnv ^ code, 16777619);
|
|
4735
|
+
djb = Math.imul(djb, 33) + code | 0;
|
|
4736
|
+
}
|
|
4737
|
+
return `${prompt.length}.${(fnv >>> 0).toString(36)}.${(djb >>> 0).toString(36)}`;
|
|
4738
|
+
}
|
|
4729
4739
|
function encodeSession3(state) {
|
|
4730
4740
|
return JSON.stringify(state);
|
|
4731
4741
|
}
|
|
@@ -4745,7 +4755,10 @@ function decideResume3(stored, currentCwd) {
|
|
|
4745
4755
|
if (storedCwd !== (currentCwd ?? "")) {
|
|
4746
4756
|
return { fresh: true, reason: "cwd_mismatch" };
|
|
4747
4757
|
}
|
|
4748
|
-
return {
|
|
4758
|
+
return {
|
|
4759
|
+
resume: state.threadId,
|
|
4760
|
+
promptFingerprint: typeof state.promptFingerprint === "string" ? state.promptFingerprint : null
|
|
4761
|
+
};
|
|
4749
4762
|
}
|
|
4750
4763
|
|
|
4751
4764
|
// packages/agent-runtime/src/codex/loop.ts
|
|
@@ -4768,13 +4781,20 @@ async function* decodeCodexStream(events, ctx) {
|
|
|
4768
4781
|
if (ctx.signal.aborted) return;
|
|
4769
4782
|
if (ev.type === "thread.started") {
|
|
4770
4783
|
const threadId = readThreadId(ev);
|
|
4771
|
-
if (
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4784
|
+
if (threadId && !sessionEmitted) {
|
|
4785
|
+
const state = encodeSession3({
|
|
4786
|
+
threadId,
|
|
4787
|
+
cwd: ctx.cwd ?? "",
|
|
4788
|
+
promptFingerprint: ctx.promptFingerprint ?? null
|
|
4789
|
+
});
|
|
4790
|
+
if (threadId !== ctx.resumedThreadId || state !== ctx.storedState) {
|
|
4791
|
+
sessionEmitted = true;
|
|
4792
|
+
yield {
|
|
4793
|
+
type: "session",
|
|
4794
|
+
state,
|
|
4795
|
+
...ctx.degraded ? { degraded: true } : {}
|
|
4796
|
+
};
|
|
4797
|
+
}
|
|
4778
4798
|
}
|
|
4779
4799
|
continue;
|
|
4780
4800
|
}
|
|
@@ -4897,7 +4917,7 @@ function codexToolPolicy(policy) {
|
|
|
4897
4917
|
networkAccessEnabled: policy.web
|
|
4898
4918
|
};
|
|
4899
4919
|
}
|
|
4900
|
-
var CODEX_REASONING_EFFORTS = ["
|
|
4920
|
+
var CODEX_REASONING_EFFORTS = ["low", "medium", "high", "xhigh", "max", "ultra"];
|
|
4901
4921
|
var codexDialectSchema = z11.object({
|
|
4902
4922
|
modelReasoningEffort: z11.enum(CODEX_REASONING_EFFORTS).optional()
|
|
4903
4923
|
}).loose();
|
|
@@ -4919,7 +4939,7 @@ var CABANE_MCP_SERVER3 = "cabane";
|
|
|
4919
4939
|
var TURN_CONTROL_MCP_SERVER2 = "cabane_companion";
|
|
4920
4940
|
var ACTIVE_CONVERSATION_HEADER4 = "x-cabane-active-conversation";
|
|
4921
4941
|
var ENV_ENVELOPE_KEYS = ["CABANE_ENV_TIER", "CABANE_ENV_KEY", "CABANE_ENV_BINDING"];
|
|
4922
|
-
function buildRunSpec2(req, resumeThreadId, instructionsFile = null) {
|
|
4942
|
+
function buildRunSpec2(req, resumeThreadId, instructionsFile = null, threadPromptFingerprint = null) {
|
|
4923
4943
|
const { policy, config } = req;
|
|
4924
4944
|
const directory = req.local.cwd ?? "";
|
|
4925
4945
|
const dialect = readCodexDialect(config.runtimeOptions);
|
|
@@ -4930,6 +4950,9 @@ function buildRunSpec2(req, resumeThreadId, instructionsFile = null) {
|
|
|
4930
4950
|
`[agent-runtime/codex] CT823: dispatching a subscription turn with NO concrete model (config.model=${JSON.stringify(config.model)}) \u2014 Codex will fall back to its own default. This should be unreachable post-CT823; the model resolver should have supplied a concrete house default.`
|
|
4931
4951
|
);
|
|
4932
4952
|
}
|
|
4953
|
+
const promptFingerprint = fingerprintPrompt(req.systemPrompt);
|
|
4954
|
+
const threadHasThisPrompt = resumeThreadId !== null && threadPromptFingerprint !== null && threadPromptFingerprint === promptFingerprint;
|
|
4955
|
+
const promptRidesInput = baseInstructionsFile === null && !threadHasThisPrompt;
|
|
4933
4956
|
return {
|
|
4934
4957
|
resumeThreadId,
|
|
4935
4958
|
directory,
|
|
@@ -4938,14 +4961,16 @@ function buildRunSpec2(req, resumeThreadId, instructionsFile = null) {
|
|
|
4938
4961
|
skipGitRepoCheck: true,
|
|
4939
4962
|
...dialect.modelReasoningEffort ? { modelReasoningEffort: dialect.modelReasoningEffort } : {},
|
|
4940
4963
|
baseInstructionsFile,
|
|
4941
|
-
input: buildInput(req,
|
|
4964
|
+
input: buildInput(req, promptRidesInput),
|
|
4965
|
+
promptRidesInput,
|
|
4966
|
+
promptFingerprint,
|
|
4942
4967
|
config: buildConfig(req, baseInstructionsFile)
|
|
4943
4968
|
};
|
|
4944
4969
|
}
|
|
4945
|
-
function buildInput(req,
|
|
4970
|
+
function buildInput(req, promptRidesInput) {
|
|
4946
4971
|
const userText = req.content.filter((b) => b.type === "text").map((b) => b.text).join("\n\n");
|
|
4947
4972
|
const body = userText.trim().length > 0 ? userText : req.prompt;
|
|
4948
|
-
if (
|
|
4973
|
+
if (!promptRidesInput) return body;
|
|
4949
4974
|
const system = req.systemPrompt.trim();
|
|
4950
4975
|
return system.length > 0 ? `${system}
|
|
4951
4976
|
|
|
@@ -5047,536 +5072,10 @@ function isStringRecord2(v) {
|
|
|
5047
5072
|
return !!v && typeof v === "object" && Object.values(v).every((x) => typeof x === "string");
|
|
5048
5073
|
}
|
|
5049
5074
|
|
|
5050
|
-
// node_modules/.pnpm/@openai+codex-sdk@0.146.0/node_modules/@openai/codex-sdk/dist/index.js
|
|
5051
|
-
import { promises as fs } from "fs";
|
|
5052
|
-
import os from "os";
|
|
5053
|
-
import path from "path";
|
|
5054
|
-
import { spawn as spawn6 } from "child_process";
|
|
5055
|
-
import { statSync } from "fs";
|
|
5056
|
-
import path2 from "path";
|
|
5057
|
-
import readline from "readline";
|
|
5058
|
-
import { createRequire } from "module";
|
|
5059
|
-
async function createOutputSchemaFile(schema) {
|
|
5060
|
-
if (schema === void 0) {
|
|
5061
|
-
return { cleanup: async () => {
|
|
5062
|
-
} };
|
|
5063
|
-
}
|
|
5064
|
-
if (!isJsonObject(schema)) {
|
|
5065
|
-
throw new Error("outputSchema must be a plain JSON object");
|
|
5066
|
-
}
|
|
5067
|
-
const schemaDir = await fs.mkdtemp(path.join(os.tmpdir(), "codex-output-schema-"));
|
|
5068
|
-
const schemaPath = path.join(schemaDir, "schema.json");
|
|
5069
|
-
const cleanup = async () => {
|
|
5070
|
-
try {
|
|
5071
|
-
await fs.rm(schemaDir, { recursive: true, force: true });
|
|
5072
|
-
} catch {
|
|
5073
|
-
}
|
|
5074
|
-
};
|
|
5075
|
-
try {
|
|
5076
|
-
await fs.writeFile(schemaPath, JSON.stringify(schema), "utf8");
|
|
5077
|
-
return { schemaPath, cleanup };
|
|
5078
|
-
} catch (error) {
|
|
5079
|
-
await cleanup();
|
|
5080
|
-
throw error;
|
|
5081
|
-
}
|
|
5082
|
-
}
|
|
5083
|
-
function isJsonObject(value) {
|
|
5084
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5085
|
-
}
|
|
5086
|
-
var Thread = class {
|
|
5087
|
-
_exec;
|
|
5088
|
-
_options;
|
|
5089
|
-
_id;
|
|
5090
|
-
_threadOptions;
|
|
5091
|
-
/** Returns the ID of the thread. Populated after the first turn starts. */
|
|
5092
|
-
get id() {
|
|
5093
|
-
return this._id;
|
|
5094
|
-
}
|
|
5095
|
-
/* @internal */
|
|
5096
|
-
constructor(exec, options, threadOptions, id = null) {
|
|
5097
|
-
this._exec = exec;
|
|
5098
|
-
this._options = options;
|
|
5099
|
-
this._id = id;
|
|
5100
|
-
this._threadOptions = threadOptions;
|
|
5101
|
-
}
|
|
5102
|
-
/** Provides the input to the agent and streams events as they are produced during the turn. */
|
|
5103
|
-
async runStreamed(input, turnOptions = {}) {
|
|
5104
|
-
return { events: this.runStreamedInternal(input, turnOptions) };
|
|
5105
|
-
}
|
|
5106
|
-
async *runStreamedInternal(input, turnOptions = {}) {
|
|
5107
|
-
const { schemaPath, cleanup } = await createOutputSchemaFile(turnOptions.outputSchema);
|
|
5108
|
-
const options = this._threadOptions;
|
|
5109
|
-
const { prompt, images } = normalizeInput(input);
|
|
5110
|
-
const generator = this._exec.run({
|
|
5111
|
-
input: prompt,
|
|
5112
|
-
baseUrl: this._options.baseUrl,
|
|
5113
|
-
apiKey: this._options.apiKey,
|
|
5114
|
-
threadId: this._id,
|
|
5115
|
-
images,
|
|
5116
|
-
model: options?.model,
|
|
5117
|
-
sandboxMode: options?.sandboxMode,
|
|
5118
|
-
workingDirectory: options?.workingDirectory,
|
|
5119
|
-
skipGitRepoCheck: options?.skipGitRepoCheck,
|
|
5120
|
-
outputSchemaFile: schemaPath,
|
|
5121
|
-
modelReasoningEffort: options?.modelReasoningEffort,
|
|
5122
|
-
signal: turnOptions.signal,
|
|
5123
|
-
networkAccessEnabled: options?.networkAccessEnabled,
|
|
5124
|
-
webSearchMode: options?.webSearchMode,
|
|
5125
|
-
webSearchEnabled: options?.webSearchEnabled,
|
|
5126
|
-
approvalPolicy: options?.approvalPolicy,
|
|
5127
|
-
additionalDirectories: options?.additionalDirectories
|
|
5128
|
-
});
|
|
5129
|
-
try {
|
|
5130
|
-
for await (const item of generator) {
|
|
5131
|
-
let parsed;
|
|
5132
|
-
try {
|
|
5133
|
-
parsed = JSON.parse(item);
|
|
5134
|
-
} catch (error) {
|
|
5135
|
-
throw new Error(`Failed to parse item: ${item}`, { cause: error });
|
|
5136
|
-
}
|
|
5137
|
-
if (parsed.type === "thread.started") {
|
|
5138
|
-
this._id = parsed.thread_id;
|
|
5139
|
-
} else if (parsed.type === "turn.completed") {
|
|
5140
|
-
parsed.usage.cache_write_input_tokens ??= 0;
|
|
5141
|
-
}
|
|
5142
|
-
yield parsed;
|
|
5143
|
-
}
|
|
5144
|
-
} finally {
|
|
5145
|
-
await cleanup();
|
|
5146
|
-
}
|
|
5147
|
-
}
|
|
5148
|
-
/** Provides the input to the agent and returns the completed turn. */
|
|
5149
|
-
async run(input, turnOptions = {}) {
|
|
5150
|
-
const generator = this.runStreamedInternal(input, turnOptions);
|
|
5151
|
-
const items = [];
|
|
5152
|
-
let finalResponse = "";
|
|
5153
|
-
let usage = null;
|
|
5154
|
-
let turnFailure = null;
|
|
5155
|
-
for await (const event of generator) {
|
|
5156
|
-
if (event.type === "item.completed") {
|
|
5157
|
-
if (event.item.type === "agent_message") {
|
|
5158
|
-
finalResponse = event.item.text;
|
|
5159
|
-
}
|
|
5160
|
-
items.push(event.item);
|
|
5161
|
-
} else if (event.type === "turn.completed") {
|
|
5162
|
-
usage = event.usage;
|
|
5163
|
-
} else if (event.type === "turn.failed") {
|
|
5164
|
-
turnFailure = event.error;
|
|
5165
|
-
break;
|
|
5166
|
-
}
|
|
5167
|
-
}
|
|
5168
|
-
if (turnFailure) {
|
|
5169
|
-
throw new Error(turnFailure.message);
|
|
5170
|
-
}
|
|
5171
|
-
return { items, finalResponse, usage };
|
|
5172
|
-
}
|
|
5173
|
-
};
|
|
5174
|
-
function normalizeInput(input) {
|
|
5175
|
-
if (typeof input === "string") {
|
|
5176
|
-
return { prompt: input, images: [] };
|
|
5177
|
-
}
|
|
5178
|
-
const promptParts = [];
|
|
5179
|
-
const images = [];
|
|
5180
|
-
for (const item of input) {
|
|
5181
|
-
if (item.type === "text") {
|
|
5182
|
-
promptParts.push(item.text);
|
|
5183
|
-
} else if (item.type === "local_image") {
|
|
5184
|
-
images.push(item.path);
|
|
5185
|
-
}
|
|
5186
|
-
}
|
|
5187
|
-
return { prompt: promptParts.join("\n\n"), images };
|
|
5188
|
-
}
|
|
5189
|
-
var INTERNAL_ORIGINATOR_ENV = "CODEX_INTERNAL_ORIGINATOR_OVERRIDE";
|
|
5190
|
-
var TYPESCRIPT_SDK_ORIGINATOR = "codex_sdk_ts";
|
|
5191
|
-
var CODEX_NPM_NAME = "@openai/codex";
|
|
5192
|
-
var PLATFORM_PACKAGE_BY_TARGET = {
|
|
5193
|
-
"x86_64-unknown-linux-musl": "@openai/codex-linux-x64",
|
|
5194
|
-
"aarch64-unknown-linux-musl": "@openai/codex-linux-arm64",
|
|
5195
|
-
"x86_64-apple-darwin": "@openai/codex-darwin-x64",
|
|
5196
|
-
"aarch64-apple-darwin": "@openai/codex-darwin-arm64",
|
|
5197
|
-
"x86_64-pc-windows-msvc": "@openai/codex-win32-x64",
|
|
5198
|
-
"aarch64-pc-windows-msvc": "@openai/codex-win32-arm64"
|
|
5199
|
-
};
|
|
5200
|
-
var moduleRequire = createRequire(import.meta.url);
|
|
5201
|
-
var CodexExec = class {
|
|
5202
|
-
executablePath;
|
|
5203
|
-
pathDirs;
|
|
5204
|
-
envOverride;
|
|
5205
|
-
configOverrides;
|
|
5206
|
-
constructor(executablePath = null, env, configOverrides) {
|
|
5207
|
-
if (executablePath) {
|
|
5208
|
-
this.executablePath = executablePath;
|
|
5209
|
-
this.pathDirs = [];
|
|
5210
|
-
} else {
|
|
5211
|
-
const resolved = findCodexPath();
|
|
5212
|
-
this.executablePath = resolved.executablePath;
|
|
5213
|
-
this.pathDirs = resolved.pathDirs;
|
|
5214
|
-
}
|
|
5215
|
-
this.envOverride = env;
|
|
5216
|
-
this.configOverrides = configOverrides;
|
|
5217
|
-
}
|
|
5218
|
-
async *run(args) {
|
|
5219
|
-
const commandArgs = ["exec", "--experimental-json"];
|
|
5220
|
-
if (this.configOverrides) {
|
|
5221
|
-
for (const override of serializeConfigOverrides(this.configOverrides)) {
|
|
5222
|
-
commandArgs.push("--config", override);
|
|
5223
|
-
}
|
|
5224
|
-
}
|
|
5225
|
-
if (args.baseUrl) {
|
|
5226
|
-
commandArgs.push(
|
|
5227
|
-
"--config",
|
|
5228
|
-
`openai_base_url=${toTomlValue(args.baseUrl, "openai_base_url")}`
|
|
5229
|
-
);
|
|
5230
|
-
}
|
|
5231
|
-
if (args.model) {
|
|
5232
|
-
commandArgs.push("--model", args.model);
|
|
5233
|
-
}
|
|
5234
|
-
if (args.sandboxMode) {
|
|
5235
|
-
commandArgs.push("--sandbox", args.sandboxMode);
|
|
5236
|
-
}
|
|
5237
|
-
if (args.workingDirectory) {
|
|
5238
|
-
commandArgs.push("--cd", args.workingDirectory);
|
|
5239
|
-
}
|
|
5240
|
-
if (args.additionalDirectories?.length) {
|
|
5241
|
-
for (const dir2 of args.additionalDirectories) {
|
|
5242
|
-
commandArgs.push("--add-dir", dir2);
|
|
5243
|
-
}
|
|
5244
|
-
}
|
|
5245
|
-
if (args.skipGitRepoCheck) {
|
|
5246
|
-
commandArgs.push("--skip-git-repo-check");
|
|
5247
|
-
}
|
|
5248
|
-
if (args.outputSchemaFile) {
|
|
5249
|
-
commandArgs.push("--output-schema", args.outputSchemaFile);
|
|
5250
|
-
}
|
|
5251
|
-
if (args.modelReasoningEffort) {
|
|
5252
|
-
commandArgs.push("--config", `model_reasoning_effort="${args.modelReasoningEffort}"`);
|
|
5253
|
-
}
|
|
5254
|
-
if (args.networkAccessEnabled !== void 0) {
|
|
5255
|
-
commandArgs.push(
|
|
5256
|
-
"--config",
|
|
5257
|
-
`sandbox_workspace_write.network_access=${args.networkAccessEnabled}`
|
|
5258
|
-
);
|
|
5259
|
-
}
|
|
5260
|
-
if (args.webSearchMode) {
|
|
5261
|
-
commandArgs.push("--config", `web_search="${args.webSearchMode}"`);
|
|
5262
|
-
} else if (args.webSearchEnabled === true) {
|
|
5263
|
-
commandArgs.push("--config", `web_search="live"`);
|
|
5264
|
-
} else if (args.webSearchEnabled === false) {
|
|
5265
|
-
commandArgs.push("--config", `web_search="disabled"`);
|
|
5266
|
-
}
|
|
5267
|
-
if (args.approvalPolicy) {
|
|
5268
|
-
commandArgs.push("--config", `approval_policy="${args.approvalPolicy}"`);
|
|
5269
|
-
}
|
|
5270
|
-
if (args.threadId) {
|
|
5271
|
-
commandArgs.push("resume", args.threadId);
|
|
5272
|
-
}
|
|
5273
|
-
if (args.images?.length) {
|
|
5274
|
-
for (const image of args.images) {
|
|
5275
|
-
commandArgs.push("--image", image);
|
|
5276
|
-
}
|
|
5277
|
-
}
|
|
5278
|
-
const env = {};
|
|
5279
|
-
if (this.envOverride) {
|
|
5280
|
-
Object.assign(env, this.envOverride);
|
|
5281
|
-
} else {
|
|
5282
|
-
for (const [key, value] of Object.entries(process.env)) {
|
|
5283
|
-
if (value !== void 0) {
|
|
5284
|
-
env[key] = value;
|
|
5285
|
-
}
|
|
5286
|
-
}
|
|
5287
|
-
}
|
|
5288
|
-
if (!env[INTERNAL_ORIGINATOR_ENV]) {
|
|
5289
|
-
env[INTERNAL_ORIGINATOR_ENV] = TYPESCRIPT_SDK_ORIGINATOR;
|
|
5290
|
-
}
|
|
5291
|
-
if (args.apiKey) {
|
|
5292
|
-
env.CODEX_API_KEY = args.apiKey;
|
|
5293
|
-
}
|
|
5294
|
-
if (this.pathDirs.length > 0) {
|
|
5295
|
-
prependPathDirs(env, this.pathDirs);
|
|
5296
|
-
}
|
|
5297
|
-
const child = spawn6(this.executablePath, commandArgs, {
|
|
5298
|
-
env,
|
|
5299
|
-
signal: args.signal
|
|
5300
|
-
});
|
|
5301
|
-
let spawnError = null;
|
|
5302
|
-
child.once("error", (err) => spawnError = err);
|
|
5303
|
-
if (!child.stdin) {
|
|
5304
|
-
child.kill();
|
|
5305
|
-
throw new Error("Child process has no stdin");
|
|
5306
|
-
}
|
|
5307
|
-
child.stdin.write(args.input);
|
|
5308
|
-
child.stdin.end();
|
|
5309
|
-
if (!child.stdout) {
|
|
5310
|
-
child.kill();
|
|
5311
|
-
throw new Error("Child process has no stdout");
|
|
5312
|
-
}
|
|
5313
|
-
const stderrChunks = [];
|
|
5314
|
-
if (child.stderr) {
|
|
5315
|
-
child.stderr.on("data", (data) => {
|
|
5316
|
-
stderrChunks.push(data);
|
|
5317
|
-
});
|
|
5318
|
-
}
|
|
5319
|
-
const exitPromise = new Promise(
|
|
5320
|
-
(resolve) => {
|
|
5321
|
-
child.once("exit", (code, signal) => {
|
|
5322
|
-
resolve({ code, signal });
|
|
5323
|
-
});
|
|
5324
|
-
}
|
|
5325
|
-
);
|
|
5326
|
-
const rl = readline.createInterface({
|
|
5327
|
-
input: child.stdout,
|
|
5328
|
-
crlfDelay: Infinity
|
|
5329
|
-
});
|
|
5330
|
-
try {
|
|
5331
|
-
for await (const line of rl) {
|
|
5332
|
-
yield line;
|
|
5333
|
-
}
|
|
5334
|
-
if (spawnError) throw spawnError;
|
|
5335
|
-
const { code, signal } = await exitPromise;
|
|
5336
|
-
if (code !== 0 || signal) {
|
|
5337
|
-
const stderrBuffer = Buffer.concat(stderrChunks);
|
|
5338
|
-
const detail = signal ? `signal ${signal}` : `code ${code ?? 1}`;
|
|
5339
|
-
throw new Error(`Codex Exec exited with ${detail}: ${stderrBuffer.toString("utf8")}`);
|
|
5340
|
-
}
|
|
5341
|
-
} finally {
|
|
5342
|
-
rl.close();
|
|
5343
|
-
child.removeAllListeners();
|
|
5344
|
-
try {
|
|
5345
|
-
if (!child.killed) child.kill();
|
|
5346
|
-
} catch {
|
|
5347
|
-
}
|
|
5348
|
-
}
|
|
5349
|
-
}
|
|
5350
|
-
};
|
|
5351
|
-
function serializeConfigOverrides(configOverrides) {
|
|
5352
|
-
const overrides = [];
|
|
5353
|
-
flattenConfigOverrides(configOverrides, "", overrides);
|
|
5354
|
-
return overrides;
|
|
5355
|
-
}
|
|
5356
|
-
function flattenConfigOverrides(value, prefix, overrides) {
|
|
5357
|
-
if (!isPlainObject(value)) {
|
|
5358
|
-
if (prefix) {
|
|
5359
|
-
overrides.push(`${prefix}=${toTomlValue(value, prefix)}`);
|
|
5360
|
-
return;
|
|
5361
|
-
} else {
|
|
5362
|
-
throw new Error("Codex config overrides must be a plain object");
|
|
5363
|
-
}
|
|
5364
|
-
}
|
|
5365
|
-
const entries = Object.entries(value);
|
|
5366
|
-
if (!prefix && entries.length === 0) {
|
|
5367
|
-
return;
|
|
5368
|
-
}
|
|
5369
|
-
if (prefix && entries.length === 0) {
|
|
5370
|
-
overrides.push(`${prefix}={}`);
|
|
5371
|
-
return;
|
|
5372
|
-
}
|
|
5373
|
-
for (const [key, child] of entries) {
|
|
5374
|
-
if (!key) {
|
|
5375
|
-
throw new Error("Codex config override keys must be non-empty strings");
|
|
5376
|
-
}
|
|
5377
|
-
if (child === void 0) {
|
|
5378
|
-
continue;
|
|
5379
|
-
}
|
|
5380
|
-
const path3 = prefix ? `${prefix}.${key}` : key;
|
|
5381
|
-
if (isPlainObject(child)) {
|
|
5382
|
-
flattenConfigOverrides(child, path3, overrides);
|
|
5383
|
-
} else {
|
|
5384
|
-
overrides.push(`${path3}=${toTomlValue(child, path3)}`);
|
|
5385
|
-
}
|
|
5386
|
-
}
|
|
5387
|
-
}
|
|
5388
|
-
function toTomlValue(value, path3) {
|
|
5389
|
-
if (typeof value === "string") {
|
|
5390
|
-
return JSON.stringify(value);
|
|
5391
|
-
} else if (typeof value === "number") {
|
|
5392
|
-
if (!Number.isFinite(value)) {
|
|
5393
|
-
throw new Error(`Codex config override at ${path3} must be a finite number`);
|
|
5394
|
-
}
|
|
5395
|
-
return `${value}`;
|
|
5396
|
-
} else if (typeof value === "boolean") {
|
|
5397
|
-
return value ? "true" : "false";
|
|
5398
|
-
} else if (Array.isArray(value)) {
|
|
5399
|
-
const rendered = value.map((item, index) => toTomlValue(item, `${path3}[${index}]`));
|
|
5400
|
-
return `[${rendered.join(", ")}]`;
|
|
5401
|
-
} else if (isPlainObject(value)) {
|
|
5402
|
-
const parts = [];
|
|
5403
|
-
for (const [key, child] of Object.entries(value)) {
|
|
5404
|
-
if (!key) {
|
|
5405
|
-
throw new Error("Codex config override keys must be non-empty strings");
|
|
5406
|
-
}
|
|
5407
|
-
if (child === void 0) {
|
|
5408
|
-
continue;
|
|
5409
|
-
}
|
|
5410
|
-
parts.push(`${formatTomlKey(key)} = ${toTomlValue(child, `${path3}.${key}`)}`);
|
|
5411
|
-
}
|
|
5412
|
-
return `{${parts.join(", ")}}`;
|
|
5413
|
-
} else if (value === null) {
|
|
5414
|
-
throw new Error(`Codex config override at ${path3} cannot be null`);
|
|
5415
|
-
} else {
|
|
5416
|
-
const typeName = typeof value;
|
|
5417
|
-
throw new Error(`Unsupported Codex config override value at ${path3}: ${typeName}`);
|
|
5418
|
-
}
|
|
5419
|
-
}
|
|
5420
|
-
var TOML_BARE_KEY = /^[A-Za-z0-9_-]+$/;
|
|
5421
|
-
function formatTomlKey(key) {
|
|
5422
|
-
return TOML_BARE_KEY.test(key) ? key : JSON.stringify(key);
|
|
5423
|
-
}
|
|
5424
|
-
function isPlainObject(value) {
|
|
5425
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5426
|
-
}
|
|
5427
|
-
function findCodexPath() {
|
|
5428
|
-
const { platform: platform2, arch } = process;
|
|
5429
|
-
let targetTriple = null;
|
|
5430
|
-
switch (platform2) {
|
|
5431
|
-
case "linux":
|
|
5432
|
-
case "android":
|
|
5433
|
-
switch (arch) {
|
|
5434
|
-
case "x64":
|
|
5435
|
-
targetTriple = "x86_64-unknown-linux-musl";
|
|
5436
|
-
break;
|
|
5437
|
-
case "arm64":
|
|
5438
|
-
targetTriple = "aarch64-unknown-linux-musl";
|
|
5439
|
-
break;
|
|
5440
|
-
default:
|
|
5441
|
-
break;
|
|
5442
|
-
}
|
|
5443
|
-
break;
|
|
5444
|
-
case "darwin":
|
|
5445
|
-
switch (arch) {
|
|
5446
|
-
case "x64":
|
|
5447
|
-
targetTriple = "x86_64-apple-darwin";
|
|
5448
|
-
break;
|
|
5449
|
-
case "arm64":
|
|
5450
|
-
targetTriple = "aarch64-apple-darwin";
|
|
5451
|
-
break;
|
|
5452
|
-
default:
|
|
5453
|
-
break;
|
|
5454
|
-
}
|
|
5455
|
-
break;
|
|
5456
|
-
case "win32":
|
|
5457
|
-
switch (arch) {
|
|
5458
|
-
case "x64":
|
|
5459
|
-
targetTriple = "x86_64-pc-windows-msvc";
|
|
5460
|
-
break;
|
|
5461
|
-
case "arm64":
|
|
5462
|
-
targetTriple = "aarch64-pc-windows-msvc";
|
|
5463
|
-
break;
|
|
5464
|
-
default:
|
|
5465
|
-
break;
|
|
5466
|
-
}
|
|
5467
|
-
break;
|
|
5468
|
-
default:
|
|
5469
|
-
break;
|
|
5470
|
-
}
|
|
5471
|
-
if (!targetTriple) {
|
|
5472
|
-
throw new Error(`Unsupported platform: ${platform2} (${arch})`);
|
|
5473
|
-
}
|
|
5474
|
-
const platformPackage = PLATFORM_PACKAGE_BY_TARGET[targetTriple];
|
|
5475
|
-
if (!platformPackage) {
|
|
5476
|
-
throw new Error(`Unsupported target triple: ${targetTriple}`);
|
|
5477
|
-
}
|
|
5478
|
-
let vendorRoot;
|
|
5479
|
-
try {
|
|
5480
|
-
const codexPackageJsonPath = moduleRequire.resolve(`${CODEX_NPM_NAME}/package.json`);
|
|
5481
|
-
const codexRequire = createRequire(codexPackageJsonPath);
|
|
5482
|
-
const platformPackageJsonPath = codexRequire.resolve(`${platformPackage}/package.json`);
|
|
5483
|
-
vendorRoot = path2.join(path2.dirname(platformPackageJsonPath), "vendor");
|
|
5484
|
-
} catch {
|
|
5485
|
-
throw new Error(
|
|
5486
|
-
`Unable to locate Codex CLI binaries. Ensure ${CODEX_NPM_NAME} is installed with optional dependencies.`
|
|
5487
|
-
);
|
|
5488
|
-
}
|
|
5489
|
-
const codexBinaryName = process.platform === "win32" ? "codex.exe" : "codex";
|
|
5490
|
-
const nativePackage = resolveNativePackage(vendorRoot, targetTriple, codexBinaryName);
|
|
5491
|
-
if (!nativePackage) {
|
|
5492
|
-
throw new Error(
|
|
5493
|
-
`Unable to locate Codex CLI binaries for ${targetTriple}. Ensure ${CODEX_NPM_NAME} is installed with optional dependencies.`
|
|
5494
|
-
);
|
|
5495
|
-
}
|
|
5496
|
-
return nativePackage;
|
|
5497
|
-
}
|
|
5498
|
-
function resolveNativePackage(vendorRoot, targetTriple, codexBinaryName) {
|
|
5499
|
-
const packageRoot = path2.join(vendorRoot, targetTriple);
|
|
5500
|
-
const packageBinaryPath = path2.join(packageRoot, "bin", codexBinaryName);
|
|
5501
|
-
if (isFile(packageBinaryPath) && isFile(path2.join(packageRoot, "codex-package.json"))) {
|
|
5502
|
-
return {
|
|
5503
|
-
executablePath: packageBinaryPath,
|
|
5504
|
-
pathDirs: existingDirs(path2.join(packageRoot, "codex-path"))
|
|
5505
|
-
};
|
|
5506
|
-
}
|
|
5507
|
-
const legacyBinaryPath = path2.join(packageRoot, "codex", codexBinaryName);
|
|
5508
|
-
if (isFile(legacyBinaryPath)) {
|
|
5509
|
-
return {
|
|
5510
|
-
executablePath: legacyBinaryPath,
|
|
5511
|
-
pathDirs: existingDirs(path2.join(packageRoot, "path"))
|
|
5512
|
-
};
|
|
5513
|
-
}
|
|
5514
|
-
return null;
|
|
5515
|
-
}
|
|
5516
|
-
function existingDirs(...dirs) {
|
|
5517
|
-
return dirs.filter(isDirectory);
|
|
5518
|
-
}
|
|
5519
|
-
function prependPathDirs(env, pathDirs, platform2 = process.platform) {
|
|
5520
|
-
const pathKey = pathEnvKey(env, platform2);
|
|
5521
|
-
if (platform2 === "win32") {
|
|
5522
|
-
for (const key of Object.keys(env)) {
|
|
5523
|
-
if (key.toLowerCase() === "path" && key !== pathKey) {
|
|
5524
|
-
delete env[key];
|
|
5525
|
-
}
|
|
5526
|
-
}
|
|
5527
|
-
}
|
|
5528
|
-
const existingEntries = (env[pathKey] ?? "").split(path2.delimiter).filter((entry) => entry.length > 0 && !pathDirs.includes(entry));
|
|
5529
|
-
env[pathKey] = [...pathDirs, ...existingEntries].join(path2.delimiter);
|
|
5530
|
-
}
|
|
5531
|
-
function pathEnvKey(env, platform2) {
|
|
5532
|
-
if (platform2 !== "win32") {
|
|
5533
|
-
return "PATH";
|
|
5534
|
-
}
|
|
5535
|
-
const matchingKeys = Object.keys(env).filter((key) => key.toLowerCase() === "path");
|
|
5536
|
-
return matchingKeys.includes("Path") ? "Path" : matchingKeys.at(-1) ?? "PATH";
|
|
5537
|
-
}
|
|
5538
|
-
function isFile(filePath) {
|
|
5539
|
-
try {
|
|
5540
|
-
return statSync(filePath).isFile();
|
|
5541
|
-
} catch {
|
|
5542
|
-
return false;
|
|
5543
|
-
}
|
|
5544
|
-
}
|
|
5545
|
-
function isDirectory(filePath) {
|
|
5546
|
-
try {
|
|
5547
|
-
return statSync(filePath).isDirectory();
|
|
5548
|
-
} catch {
|
|
5549
|
-
return false;
|
|
5550
|
-
}
|
|
5551
|
-
}
|
|
5552
|
-
var Codex = class {
|
|
5553
|
-
exec;
|
|
5554
|
-
options;
|
|
5555
|
-
constructor(options = {}) {
|
|
5556
|
-
const { codexPathOverride, env, config } = options;
|
|
5557
|
-
this.exec = new CodexExec(codexPathOverride, env, config);
|
|
5558
|
-
this.options = options;
|
|
5559
|
-
}
|
|
5560
|
-
/**
|
|
5561
|
-
* Starts a new conversation with an agent.
|
|
5562
|
-
* @returns A new thread instance.
|
|
5563
|
-
*/
|
|
5564
|
-
startThread(options = {}) {
|
|
5565
|
-
return new Thread(this.exec, this.options, options);
|
|
5566
|
-
}
|
|
5567
|
-
/**
|
|
5568
|
-
* Resumes a conversation with an agent based on the thread id.
|
|
5569
|
-
* Threads are persisted in ~/.codex/sessions.
|
|
5570
|
-
*
|
|
5571
|
-
* @param id The id of the thread to resume.
|
|
5572
|
-
* @returns A new thread instance.
|
|
5573
|
-
*/
|
|
5574
|
-
resumeThread(id, options = {}) {
|
|
5575
|
-
return new Thread(this.exec, this.options, options, id);
|
|
5576
|
-
}
|
|
5577
|
-
};
|
|
5578
|
-
|
|
5579
5075
|
// packages/agent-runtime/src/codex/transport.ts
|
|
5076
|
+
import {
|
|
5077
|
+
Codex
|
|
5078
|
+
} from "@openai/codex-sdk";
|
|
5580
5079
|
function buildSdkThreadOptions(spec) {
|
|
5581
5080
|
return {
|
|
5582
5081
|
...spec.model ? { model: spec.model } : {},
|
|
@@ -5630,6 +5129,7 @@ function createCodexAdapter(deps = {}) {
|
|
|
5630
5129
|
const directory = req.local.cwd ?? "";
|
|
5631
5130
|
const decision = decideResume3(req.session, directory);
|
|
5632
5131
|
const resumeThreadId = "resume" in decision ? decision.resume : null;
|
|
5132
|
+
const threadPromptFingerprint = "resume" in decision ? decision.promptFingerprint : null;
|
|
5633
5133
|
if ("fresh" in decision && decision.reason === "cwd_mismatch") {
|
|
5634
5134
|
deps.onWarn?.(
|
|
5635
5135
|
"codex adapter: stored session directory no longer matches the current environment \u2014 starting a fresh thread (SJ527-analogue guard)",
|
|
@@ -5655,7 +5155,12 @@ function createCodexAdapter(deps = {}) {
|
|
|
5655
5155
|
}
|
|
5656
5156
|
}
|
|
5657
5157
|
try {
|
|
5658
|
-
const spec = buildRunSpec2(
|
|
5158
|
+
const spec = buildRunSpec2(
|
|
5159
|
+
req,
|
|
5160
|
+
resumeThreadId,
|
|
5161
|
+
instructions?.path ?? null,
|
|
5162
|
+
threadPromptFingerprint
|
|
5163
|
+
);
|
|
5659
5164
|
const result = await transport.run(spec, signal);
|
|
5660
5165
|
if (signal.aborted) return;
|
|
5661
5166
|
yield* decodeCodexStream(result.events, {
|
|
@@ -5663,6 +5168,14 @@ function createCodexAdapter(deps = {}) {
|
|
|
5663
5168
|
cwd: req.local.cwd,
|
|
5664
5169
|
resumedThreadId: resumeThreadId,
|
|
5665
5170
|
degraded,
|
|
5171
|
+
// CT1075: which prompt the thread carries once this turn lands. If we sent
|
|
5172
|
+
// one, it's that one — a later copy is what the model reads, so a changed
|
|
5173
|
+
// prompt supersedes the older copy still sitting above it. If we withheld
|
|
5174
|
+
// it, the thread still carries whatever it did before. Read off the spec
|
|
5175
|
+
// rather than recomputed, so the recorded fact can't disagree with what
|
|
5176
|
+
// was actually sent.
|
|
5177
|
+
promptFingerprint: spec.promptRidesInput ? spec.promptFingerprint : threadPromptFingerprint,
|
|
5178
|
+
storedState: req.session,
|
|
5666
5179
|
// CT601: Codex reports no model in-stream, so record what the thread was
|
|
5667
5180
|
// started with (null on the default token). Same for the reasoning effort.
|
|
5668
5181
|
resolvedModel: spec.model,
|
|
@@ -5697,9 +5210,10 @@ var COMPANION_POLICY2 = {
|
|
|
5697
5210
|
uiPrompts: "never"
|
|
5698
5211
|
};
|
|
5699
5212
|
var DIR2 = "/env/here";
|
|
5213
|
+
var PROMPT = "system";
|
|
5700
5214
|
function makeRequest3(overrides = {}) {
|
|
5701
5215
|
return {
|
|
5702
|
-
systemPrompt:
|
|
5216
|
+
systemPrompt: PROMPT,
|
|
5703
5217
|
prompt: "hi there",
|
|
5704
5218
|
content: [{ type: "text", text: "hi there" }],
|
|
5705
5219
|
// CT601: the resolved model + reasoning effort ride the terminal `result` off
|
|
@@ -5742,9 +5256,9 @@ var errorItem = (id, message) => ({
|
|
|
5742
5256
|
type: "item.completed",
|
|
5743
5257
|
item: { id, type: "error", message }
|
|
5744
5258
|
});
|
|
5745
|
-
var sessionEvent3 = (threadId, cwd = DIR2, degraded = false) => ({
|
|
5259
|
+
var sessionEvent3 = (threadId, cwd = DIR2, degraded = false, promptFingerprint = fingerprintPrompt(PROMPT)) => ({
|
|
5746
5260
|
type: "session",
|
|
5747
|
-
state: encodeSession3({ threadId, cwd }),
|
|
5261
|
+
state: encodeSession3({ threadId, cwd, promptFingerprint }),
|
|
5748
5262
|
...degraded ? { degraded: true } : {}
|
|
5749
5263
|
});
|
|
5750
5264
|
var CODEX_CONFORMANCE_FIXTURES = [
|
|
@@ -6202,23 +5716,91 @@ var CODEX_CONFORMANCE_FIXTURES = [
|
|
|
6202
5716
|
{
|
|
6203
5717
|
// Session resume: the stored state matches the current directory, so the adapter
|
|
6204
5718
|
// resumes its thread id and `thread.started` echoes the SAME id — NO session
|
|
6205
|
-
// event fires (the id didn't change). This is what
|
|
5719
|
+
// event fires (the id didn't change AND the state is unchanged). This is what
|
|
5720
|
+
// carries cross-turn memory.
|
|
5721
|
+
//
|
|
5722
|
+
// CT1075: the stored state also says the thread already holds the composed
|
|
5723
|
+
// prompt, so this turn withholds the preamble and the state it would write is
|
|
5724
|
+
// byte-identical to the stored one. The silence is now proof of BOTH: an id that
|
|
5725
|
+
// didn't change and a prompt that didn't need re-sending.
|
|
6206
5726
|
name: "session resume (no re-emit on echo)",
|
|
6207
|
-
request: makeRequest3({
|
|
5727
|
+
request: makeRequest3({
|
|
5728
|
+
session: encodeSession3({
|
|
5729
|
+
threadId: "th1",
|
|
5730
|
+
cwd: DIR2,
|
|
5731
|
+
promptFingerprint: fingerprintPrompt(PROMPT)
|
|
5732
|
+
})
|
|
5733
|
+
}),
|
|
6208
5734
|
nativeStream: [threadStarted("th1"), agentMessage("m1", "Back again."), turnCompleted()],
|
|
6209
5735
|
expected: [
|
|
6210
5736
|
{ type: "text", body: "Back again.", terminal: true },
|
|
6211
5737
|
{ type: "result", ok: true }
|
|
6212
5738
|
]
|
|
6213
5739
|
},
|
|
5740
|
+
{
|
|
5741
|
+
// CT1075: a resumed thread the platform's state does NOT vouch for — a state
|
|
5742
|
+
// written before CT1075 existed, so it carries no `promptFingerprint` field. The
|
|
5743
|
+
// adapter reads the absence as "we can't say which prompt this thread holds",
|
|
5744
|
+
// sends the full prompt exactly as it did pre-CT1075, and re-emits the state with
|
|
5745
|
+
// the fingerprint recorded — on the SAME thread id, which pre-CT1075 emitted
|
|
5746
|
+
// nothing at all. That re-emit is the upgrade path: without it a long-lived
|
|
5747
|
+
// thread would re-send the whole prompt forever.
|
|
5748
|
+
//
|
|
5749
|
+
// This is the fixture that fails if the static half is withheld on a thread we
|
|
5750
|
+
// have no evidence about.
|
|
5751
|
+
name: "session resume with a pre-CT1075 state re-emits with the prompt fingerprint",
|
|
5752
|
+
request: makeRequest3({
|
|
5753
|
+
session: JSON.stringify({ threadId: "th1", cwd: DIR2 })
|
|
5754
|
+
}),
|
|
5755
|
+
nativeStream: [threadStarted("th1"), agentMessage("m1", "Back again."), turnCompleted()],
|
|
5756
|
+
expected: [
|
|
5757
|
+
sessionEvent3("th1"),
|
|
5758
|
+
{ type: "text", body: "Back again.", terminal: true },
|
|
5759
|
+
{ type: "result", ok: true }
|
|
5760
|
+
]
|
|
5761
|
+
},
|
|
5762
|
+
{
|
|
5763
|
+
// CT1075 (review finding): the thread carries a DIFFERENT prompt than the one
|
|
5764
|
+
// this turn composed — a charter edit, a renamed agent, a redeployed prompt
|
|
5765
|
+
// body. The stored fingerprint doesn't match, so the new prompt is sent exactly
|
|
5766
|
+
// as on a fresh thread and the state is re-recorded against it.
|
|
5767
|
+
//
|
|
5768
|
+
// This is the fixture that fails if a stale prompt is treated as good enough. A
|
|
5769
|
+
// boolean flag would have passed the resume fixture above and silently withheld
|
|
5770
|
+
// every future charter edit from this thread.
|
|
5771
|
+
name: "session resume with a stale prompt fingerprint re-sends the prompt",
|
|
5772
|
+
request: makeRequest3({
|
|
5773
|
+
session: encodeSession3({
|
|
5774
|
+
threadId: "th1",
|
|
5775
|
+
cwd: DIR2,
|
|
5776
|
+
promptFingerprint: fingerprintPrompt("an older composed prompt")
|
|
5777
|
+
})
|
|
5778
|
+
}),
|
|
5779
|
+
nativeStream: [threadStarted("th1"), agentMessage("m1", "Back again."), turnCompleted()],
|
|
5780
|
+
expected: [
|
|
5781
|
+
sessionEvent3("th1"),
|
|
5782
|
+
{ type: "text", body: "Back again.", terminal: true },
|
|
5783
|
+
{ type: "result", ok: true }
|
|
5784
|
+
]
|
|
5785
|
+
},
|
|
6214
5786
|
{
|
|
6215
5787
|
// Directory-mismatch degrade (the SJ527 analogue): the stored state was created
|
|
6216
5788
|
// under a DIFFERENT directory, so the adapter refuses to resume and starts
|
|
6217
5789
|
// fresh — `thread.started` reports a NEW id, and the emitted session event
|
|
6218
5790
|
// records the CURRENT directory with `degraded: true` so the host rewinds the
|
|
6219
5791
|
// catch-up mark.
|
|
5792
|
+
//
|
|
5793
|
+
// CT1075: the refused resume means the new thread is EMPTY, so the full prompt
|
|
5794
|
+
// goes in even though the stored state vouched for a copy — of a thread we're
|
|
5795
|
+
// no longer in. The emitted state records the fresh thread's own answer.
|
|
6220
5796
|
name: "session directory-mismatch degrades to fresh",
|
|
6221
|
-
request: makeRequest3({
|
|
5797
|
+
request: makeRequest3({
|
|
5798
|
+
session: encodeSession3({
|
|
5799
|
+
threadId: "old",
|
|
5800
|
+
cwd: "/env/gone",
|
|
5801
|
+
promptFingerprint: fingerprintPrompt(PROMPT)
|
|
5802
|
+
})
|
|
5803
|
+
}),
|
|
6222
5804
|
nativeStream: [
|
|
6223
5805
|
threadStarted(NEW_THREAD_ID),
|
|
6224
5806
|
agentMessage("m1", "Fresh start."),
|
|
@@ -6285,7 +5867,7 @@ var ConnectorHealthStore = class {
|
|
|
6285
5867
|
|
|
6286
5868
|
// src/dispatcher.ts
|
|
6287
5869
|
import { randomUUID } from "crypto";
|
|
6288
|
-
import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync10, statSync
|
|
5870
|
+
import { appendFileSync as appendFileSync2, existsSync as existsSync9, mkdirSync as mkdirSync10, statSync } from "fs";
|
|
6289
5871
|
import { join as join13 } from "path";
|
|
6290
5872
|
|
|
6291
5873
|
// src/summon.ts
|
|
@@ -6584,9 +6166,16 @@ function buildCompanionTurnRequest(params) {
|
|
|
6584
6166
|
...t.workspaceToolSurface ? { workspaceToolSurface: t.workspaceToolSurface } : {},
|
|
6585
6167
|
// CT714: mount the turn-control surface ONLY when a real turn token backs
|
|
6586
6168
|
// this turn — the surface admits `turn_token` auth exclusively, so a
|
|
6587
|
-
// PAT-fallback bearer
|
|
6588
|
-
//
|
|
6589
|
-
//
|
|
6169
|
+
// PAT-fallback bearer would be rejected there. Absent it, external adapters
|
|
6170
|
+
// simply don't mount it that turn.
|
|
6171
|
+
//
|
|
6172
|
+
// CT1074: that fallback is now an OLD-API case only. The server mints a token
|
|
6173
|
+
// for every turn it dispatched, so "no token" no longer means "no human behind
|
|
6174
|
+
// the turn" — it means the API predates this change. The condition is right as
|
|
6175
|
+
// it stands and stays where the failure is honest (a rejected bearer is worse
|
|
6176
|
+
// than an unmounted server); what was wrong was upstream, where an undelegated
|
|
6177
|
+
// turn minted nothing and this silently dropped all five verbs on Codex and
|
|
6178
|
+
// opencode with no refusal anyone could see.
|
|
6590
6179
|
...params.turnToken ? { turnControlUrl: turnControlMcpUrl(params.baseUrl) } : {}
|
|
6591
6180
|
},
|
|
6592
6181
|
local: {
|
|
@@ -6616,10 +6205,10 @@ import { join as join9 } from "path";
|
|
|
6616
6205
|
var PREFIX = "cabane-codex-instructions-";
|
|
6617
6206
|
async function writeCodexInstructionsFile(contents) {
|
|
6618
6207
|
const dir2 = await mkdtemp(join9(tmpdir(), PREFIX));
|
|
6619
|
-
const
|
|
6620
|
-
await writeFile(
|
|
6208
|
+
const path = join9(dir2, "instructions.md");
|
|
6209
|
+
await writeFile(path, contents, { encoding: "utf8", mode: 384 });
|
|
6621
6210
|
return {
|
|
6622
|
-
path
|
|
6211
|
+
path,
|
|
6623
6212
|
cleanup: async () => {
|
|
6624
6213
|
await rm(dir2, { recursive: true, force: true });
|
|
6625
6214
|
}
|
|
@@ -6639,10 +6228,10 @@ function pathFor3(workspaceId, conversationId, agentId) {
|
|
|
6639
6228
|
return join10(conversationDir(workspaceId, conversationId), `${encodeURIComponent(agentId)}.json`);
|
|
6640
6229
|
}
|
|
6641
6230
|
function readPrepared(workspaceId, conversationId, agentId) {
|
|
6642
|
-
const
|
|
6643
|
-
if (!existsSync7(
|
|
6231
|
+
const path = pathFor3(workspaceId, conversationId, agentId);
|
|
6232
|
+
if (!existsSync7(path)) return null;
|
|
6644
6233
|
try {
|
|
6645
|
-
const parsed = JSON.parse(readFileSync6(
|
|
6234
|
+
const parsed = JSON.parse(readFileSync6(path, "utf8"));
|
|
6646
6235
|
if (parsed && typeof parsed.cwd === "string" && parsed.cwd.length > 0) {
|
|
6647
6236
|
return {
|
|
6648
6237
|
cwd: parsed.cwd,
|
|
@@ -6676,14 +6265,14 @@ function secretsPath() {
|
|
|
6676
6265
|
}
|
|
6677
6266
|
var secretStoreSchema = z13.record(z13.string(), z13.string());
|
|
6678
6267
|
function loadSecretStore() {
|
|
6679
|
-
const
|
|
6680
|
-
if (!existsSync8(
|
|
6268
|
+
const path = secretsPath();
|
|
6269
|
+
if (!existsSync8(path)) return makeStore({});
|
|
6681
6270
|
let raw;
|
|
6682
6271
|
try {
|
|
6683
|
-
raw = readFileSync7(
|
|
6272
|
+
raw = readFileSync7(path, "utf8");
|
|
6684
6273
|
} catch (err) {
|
|
6685
6274
|
throw new ConfigError(
|
|
6686
|
-
`couldn't read ${
|
|
6275
|
+
`couldn't read ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
6687
6276
|
);
|
|
6688
6277
|
}
|
|
6689
6278
|
if (raw.trim().length === 0) return makeStore({});
|
|
@@ -6692,13 +6281,13 @@ function loadSecretStore() {
|
|
|
6692
6281
|
parsed = JSON.parse(raw);
|
|
6693
6282
|
} catch (err) {
|
|
6694
6283
|
throw new ConfigError(
|
|
6695
|
-
`${
|
|
6284
|
+
`${path} is not valid JSON: ${err instanceof Error ? err.message : String(err)}. It must be a flat object of "NAME": "value" secret pairs.`
|
|
6696
6285
|
);
|
|
6697
6286
|
}
|
|
6698
6287
|
const result = secretStoreSchema.safeParse(parsed);
|
|
6699
6288
|
if (!result.success) {
|
|
6700
6289
|
throw new ConfigError(
|
|
6701
|
-
`${
|
|
6290
|
+
`${path} must be a flat object mapping secret names to string values (e.g. { "GITHUB_TOKEN": "ghp_\u2026" }).`
|
|
6702
6291
|
);
|
|
6703
6292
|
}
|
|
6704
6293
|
return makeStore(result.data);
|
|
@@ -7134,7 +6723,7 @@ function checkoutState(cwd) {
|
|
|
7134
6723
|
if (!existsSync9(gitPath)) return { ok: false, reason: `${cwd} holds no git metadata` };
|
|
7135
6724
|
let stat;
|
|
7136
6725
|
try {
|
|
7137
|
-
stat =
|
|
6726
|
+
stat = statSync(gitPath);
|
|
7138
6727
|
} catch (error) {
|
|
7139
6728
|
return { ok: false, reason: `${gitPath} is unreadable (${error.message})` };
|
|
7140
6729
|
}
|
|
@@ -8565,8 +8154,8 @@ function sleep3(ms) {
|
|
|
8565
8154
|
}
|
|
8566
8155
|
|
|
8567
8156
|
// src/version.ts
|
|
8568
|
-
import { createRequire
|
|
8569
|
-
var pkg =
|
|
8157
|
+
import { createRequire } from "module";
|
|
8158
|
+
var pkg = createRequire(import.meta.url)("../package.json");
|
|
8570
8159
|
var COMPANION_VERSION = pkg.version;
|
|
8571
8160
|
|
|
8572
8161
|
// src/supervisor.ts
|
|
@@ -9370,9 +8959,9 @@ var CompanionSupervisor = class {
|
|
|
9370
8959
|
};
|
|
9371
8960
|
function defaultReexec() {
|
|
9372
8961
|
clearRuntimeState();
|
|
9373
|
-
void import("child_process").then(({ spawn:
|
|
8962
|
+
void import("child_process").then(({ spawn: spawn6 }) => {
|
|
9374
8963
|
try {
|
|
9375
|
-
const child =
|
|
8964
|
+
const child = spawn6(process.execPath, process.argv.slice(1), {
|
|
9376
8965
|
stdio: "inherit",
|
|
9377
8966
|
detached: false
|
|
9378
8967
|
});
|
|
@@ -9456,8 +9045,8 @@ function recordCrash(rec2) {
|
|
|
9456
9045
|
}
|
|
9457
9046
|
function clearCrash() {
|
|
9458
9047
|
try {
|
|
9459
|
-
const
|
|
9460
|
-
if (existsSync11(
|
|
9048
|
+
const path = crashMarkerPath();
|
|
9049
|
+
if (existsSync11(path)) rmSync7(path, { force: true });
|
|
9461
9050
|
} catch {
|
|
9462
9051
|
}
|
|
9463
9052
|
}
|
|
@@ -9956,11 +9545,11 @@ function printList(dir2) {
|
|
|
9956
9545
|
"render one: cabane-companion transcript <file> (or `--last` for the newest)\n"
|
|
9957
9546
|
);
|
|
9958
9547
|
}
|
|
9959
|
-
function peek(
|
|
9548
|
+
function peek(path) {
|
|
9960
9549
|
let meta;
|
|
9961
9550
|
let outcome;
|
|
9962
9551
|
try {
|
|
9963
|
-
for (const line of readFileSync10(
|
|
9552
|
+
for (const line of readFileSync10(path, "utf8").split("\n")) {
|
|
9964
9553
|
if (!line.trim()) continue;
|
|
9965
9554
|
const o = safeParse(line);
|
|
9966
9555
|
const t = str2(rec(o)?.type);
|
|
@@ -9990,13 +9579,13 @@ function resolveTarget(dir2, target) {
|
|
|
9990
9579
|
` + matches.slice(0, 10).map((m) => ` ${m}`).join("\n")
|
|
9991
9580
|
);
|
|
9992
9581
|
}
|
|
9993
|
-
function renderFile(
|
|
9582
|
+
function renderFile(path) {
|
|
9994
9583
|
let content;
|
|
9995
9584
|
try {
|
|
9996
|
-
content = readFileSync10(
|
|
9585
|
+
content = readFileSync10(path, "utf8");
|
|
9997
9586
|
} catch (err) {
|
|
9998
9587
|
throw new CompanionError(
|
|
9999
|
-
`couldn't read ${
|
|
9588
|
+
`couldn't read ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
10000
9589
|
);
|
|
10001
9590
|
}
|
|
10002
9591
|
return renderTranscript(content.split("\n"));
|