@centralu/host-linux-x64 0.1.0-beta.47 → 0.1.0-beta.48
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/centralu-keeper +0 -0
- package/host/bundle-info.json +2 -2
- package/host/centralu-package.json +5 -5
- package/host/main.mjs +1605 -1034
- package/package.json +1 -1
package/host/main.mjs
CHANGED
|
@@ -43050,7 +43050,7 @@ import { dirname as dirname26, join as join59 } from "node:path";
|
|
|
43050
43050
|
import { mkdirSync as mkdirSync21, readFileSync as readFileSync27, writeSync as writeSync4 } from "node:fs";
|
|
43051
43051
|
|
|
43052
43052
|
// packages/protocol/src/version.generated.ts
|
|
43053
|
-
var APP_VERSION = "0.1.0-beta.
|
|
43053
|
+
var APP_VERSION = "0.1.0-beta.48";
|
|
43054
43054
|
|
|
43055
43055
|
// packages/protocol/src/brand.ts
|
|
43056
43056
|
var APP_NAME = "Centralu";
|
|
@@ -57761,7 +57761,8 @@ var SessionState = external_exports.enum([
|
|
|
57761
57761
|
"error"
|
|
57762
57762
|
]);
|
|
57763
57763
|
var SessionActivity = external_exports.enum(["compacting", "reviewing", "retrying"]);
|
|
57764
|
-
var InterruptionReason = external_exports.enum(["network", "quit", "interrupted", "reconnecting"]).catch("interrupted");
|
|
57764
|
+
var InterruptionReason = external_exports.enum(["network", "quit", "shutdown", "interrupted", "reconnecting"]).catch("interrupted");
|
|
57765
|
+
var SHUTDOWN_WORDS = "the computer shut down, restarted or signed out";
|
|
57765
57766
|
var Interruption = external_exports.object({
|
|
57766
57767
|
reason: InterruptionReason,
|
|
57767
57768
|
/** When it happened (ms); for `reconnecting`, when the reconnecting began */
|
|
@@ -59671,6 +59672,8 @@ var TurnWho = external_exports.object({
|
|
|
59671
59672
|
by: external_exports.enum(["person", "work"]),
|
|
59672
59673
|
wrote: external_exports.literal(true).optional(),
|
|
59673
59674
|
added: external_exports.literal(true).optional(),
|
|
59675
|
+
/** An update of Centralu cut this turn and Centralu continued it (#1966): the report of its end says so */
|
|
59676
|
+
cut: external_exports.literal(true).optional(),
|
|
59674
59677
|
compaction: external_exports.literal(true).optional(),
|
|
59675
59678
|
since: external_exports.number().int().optional()
|
|
59676
59679
|
});
|
|
@@ -61807,6 +61810,12 @@ var hostParams = {
|
|
|
61807
61810
|
* says it in its hellos and sends `host_handover`, so a hub's row can say it. `handover: null` clears it. Additive
|
|
61808
61811
|
*/
|
|
61809
61812
|
"host.handover": external_exports.object({ handover: HostHandover.nullable() }),
|
|
61813
|
+
/**
|
|
61814
|
+
* The system is ending (a shutdown, restart or sign-out), told by the window on a machine where the host has no window of its
|
|
61815
|
+
* own to hear it: Windows, whose desktop window runs `centralu serve --window` and cannot write to a host it did not start
|
|
61816
|
+
* (#1970, #1397). `cancelled`: the end announced before was called off. Additive
|
|
61817
|
+
*/
|
|
61818
|
+
"host.systemEnding": external_exports.object({ cancelled: external_exports.boolean().optional() }),
|
|
61810
61819
|
/**
|
|
61811
61820
|
* A hub linked to this host says who it is and whether this host's sessions may ask its projects (#550, the reverse
|
|
61812
61821
|
* direction, docs/plans/remote-hub.md §12.7). Sent by the hub at every connect and when its switch for this machine
|
|
@@ -61878,6 +61887,7 @@ var hostResults = {
|
|
|
61878
61887
|
external_exports.object({ ok: external_exports.literal(false), busy: HostActivity, calls: external_exports.number().int() })
|
|
61879
61888
|
]),
|
|
61880
61889
|
"host.handover": external_exports.object({ ok: external_exports.literal(true) }),
|
|
61890
|
+
"host.systemEnding": external_exports.object({ ok: external_exports.literal(true) }),
|
|
61881
61891
|
"host.hubOffer": external_exports.object({ ok: external_exports.literal(true) })
|
|
61882
61892
|
};
|
|
61883
61893
|
|
|
@@ -63454,6 +63464,12 @@ var settingsParams = {
|
|
|
63454
63464
|
* like any other file and gets a fresh id if the name is taken.
|
|
63455
63465
|
*/
|
|
63456
63466
|
"themes.import": external_exports.object({ path: external_exports.string() }),
|
|
63467
|
+
/**
|
|
63468
|
+
* Takes a theme file out of the folder, without losing it: the file moves to the folder's `.removed/`, where the
|
|
63469
|
+
* theme list does not look (an OS trash is the shell's, `themes.resolve`; this is the host's own, for a caller with no shell, the orchestrator).
|
|
63470
|
+
* A side that showed it falls back to its preset (the caller's to set, as Settings does).
|
|
63471
|
+
*/
|
|
63472
|
+
"themes.remove": external_exports.object({ id: ThemeId }),
|
|
63457
63473
|
/**
|
|
63458
63474
|
* The absolute path of a theme file, for the shell to trash or reveal (the same two-step as
|
|
63459
63475
|
* `fs.resolve`: the host knows the folder, the shell knows the OS).
|
|
@@ -63474,6 +63490,7 @@ var settingsResults = {
|
|
|
63474
63490
|
"themes.list": external_exports.array(ThemeFileEntry),
|
|
63475
63491
|
"themes.save": ThemeFileEntry,
|
|
63476
63492
|
"themes.import": ThemeFileEntry,
|
|
63493
|
+
"themes.remove": external_exports.object({ removed: external_exports.boolean(), keptAt: external_exports.string().nullable() }),
|
|
63477
63494
|
"themes.resolve": external_exports.object({ path: external_exports.string() }),
|
|
63478
63495
|
"approvals.rules": external_exports.array(
|
|
63479
63496
|
external_exports.object({
|
|
@@ -65587,6 +65604,13 @@ function logonFromGroups(stdout) {
|
|
|
65587
65604
|
if (sids.has("S-1-5-6")) return "service";
|
|
65588
65605
|
return null;
|
|
65589
65606
|
}
|
|
65607
|
+
function systemStoppingQueries(platform = HOST_PLATFORM) {
|
|
65608
|
+
if (platform !== "linux") return null;
|
|
65609
|
+
return [
|
|
65610
|
+
["systemctl", "is-system-running"],
|
|
65611
|
+
["systemctl", "--user", "is-system-running"]
|
|
65612
|
+
];
|
|
65613
|
+
}
|
|
65590
65614
|
|
|
65591
65615
|
// packages/agent-host/src/dev-services/path-guard.ts
|
|
65592
65616
|
import { lstatSync as lstatSync2, realpathSync, statSync } from "node:fs";
|
|
@@ -66409,6 +66433,8 @@ var METHOD_RIGHTS = {
|
|
|
66409
66433
|
* launcher says it with the limited credential and no full one crosses a port for it; `host.stop` does far more
|
|
66410
66434
|
*/
|
|
66411
66435
|
"host.handover": "everyday",
|
|
66436
|
+
/** The window telling the serve host the system is ending (#1970): said with the window's full credential. An everyday one could plant a false end that relabels a real crash as the system's (#1397) */
|
|
66437
|
+
"host.systemEnding": "window",
|
|
66412
66438
|
"host.hubOffer": "link",
|
|
66413
66439
|
// The window's own state, preferences, themes and approval rules
|
|
66414
66440
|
"workspace.save": "window",
|
|
@@ -66421,6 +66447,7 @@ var METHOD_RIGHTS = {
|
|
|
66421
66447
|
"themes.list": "everyday",
|
|
66422
66448
|
"themes.save": "everyday",
|
|
66423
66449
|
"themes.import": "everyday",
|
|
66450
|
+
"themes.remove": "everyday",
|
|
66424
66451
|
"themes.resolve": "everyday",
|
|
66425
66452
|
"approvals.rules": "everyday"
|
|
66426
66453
|
};
|
|
@@ -67544,7 +67571,7 @@ var HostServer = class _HostServer {
|
|
|
67544
67571
|
return;
|
|
67545
67572
|
}
|
|
67546
67573
|
if (refusal2 !== null) {
|
|
67547
|
-
console.error(`[agent-host] refused ${JSON.stringify(method)} to a connection holding the limited credential (${refusal2.rights})`);
|
|
67574
|
+
console.error(`[agent-host] refused ${JSON.stringify(method)} to a connection holding the limited credential (${refusal2.rights})${method === "host.systemEnding" ? ": a launcher older than this host sends the system-ending notice with it, so that end is not noted until the launcher is updated (#1970)" : ""}`);
|
|
67548
67575
|
return this.sendError(ws2, id3, { code: "internal", message: refusal2.message, retryable: false, data: { refused: "rights", rights: refusal2.rights } });
|
|
67549
67576
|
}
|
|
67550
67577
|
}
|
|
@@ -106570,6 +106597,8 @@ function matchProjects(asked, projects, o = {}) {
|
|
|
106570
106597
|
return [];
|
|
106571
106598
|
}
|
|
106572
106599
|
var NO_WORDS = "(its turn ended without saying anything: read_session shows what it did)";
|
|
106600
|
+
var MACHINE_UNREACHABLE = "(not read: its machine could not be reached)";
|
|
106601
|
+
var IN_PAUSE_REPORT = "(in the paused report before this one)";
|
|
106573
106602
|
function clipAnswer(answer, sessionName, at2) {
|
|
106574
106603
|
if (answer.length <= ANSWER_MAX_CHARS) return answer;
|
|
106575
106604
|
const cut3 = answer.length - ANSWER_HEAD_CHARS - ANSWER_TAIL_CHARS;
|
|
@@ -106825,9 +106854,9 @@ async function gitStatusFiles(cwd, trust = {}) {
|
|
|
106825
106854
|
if (entry.startsWith("1 ") || entry.startsWith("2 ")) {
|
|
106826
106855
|
const parts = entry.split(" ");
|
|
106827
106856
|
const xy = parts[1] ?? "..";
|
|
106828
|
-
const
|
|
106829
|
-
const path2 = parts.slice(
|
|
106830
|
-
if (
|
|
106857
|
+
const rename8 = entry.startsWith("2 ");
|
|
106858
|
+
const path2 = parts.slice(rename8 ? 9 : 8).join(" ");
|
|
106859
|
+
if (rename8) i++;
|
|
106831
106860
|
if (!path2) continue;
|
|
106832
106861
|
const [x, y] = [xy[0] ?? ".", xy[1] ?? "."];
|
|
106833
106862
|
if (x !== ".") out.push({ path: path2, staged: true, status: mapStatus(x) });
|
|
@@ -108238,6 +108267,264 @@ function givenAgentName(name) {
|
|
|
108238
108267
|
return flat ? oneLineText(flat, 40) : void 0;
|
|
108239
108268
|
}
|
|
108240
108269
|
|
|
108270
|
+
// packages/agent-host/src/sessions/handoff-heirs.ts
|
|
108271
|
+
function liveHeirOf(store2, sessionId, isLive) {
|
|
108272
|
+
const seen = /* @__PURE__ */ new Set([sessionId]);
|
|
108273
|
+
let generation = [sessionId];
|
|
108274
|
+
while (generation.length) {
|
|
108275
|
+
const heirs = generation.flatMap((at2) => store2.handoffs.handoffHeirs(at2)).filter((h) => !seen.has(h.sessionId));
|
|
108276
|
+
for (const h of heirs) seen.add(h.sessionId);
|
|
108277
|
+
const live2 = heirs.find((h) => !h.trashed && isLive(h.sessionId));
|
|
108278
|
+
if (live2) return live2.sessionId;
|
|
108279
|
+
generation = heirs.map((h) => h.sessionId);
|
|
108280
|
+
}
|
|
108281
|
+
return null;
|
|
108282
|
+
}
|
|
108283
|
+
|
|
108284
|
+
// packages/agent-host/src/sessions/interruptions.ts
|
|
108285
|
+
var NETWORK_WORDS = [
|
|
108286
|
+
/\b(ECONNREFUSED|ENOTFOUND|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENETUNREACH|EHOSTUNREACH|ENETDOWN)\b/,
|
|
108287
|
+
/API Error: (No response from API|Can't reach the API server|Connection (refused|lost|reset|error|dropped)|Request timed out|No internet route|fetch failed|Unable to connect to API(?!: SSL))/i,
|
|
108288
|
+
/computer (went to sleep|was asleep)/i,
|
|
108289
|
+
/stream disconnected before completion/i,
|
|
108290
|
+
/failed to lookup address information/i,
|
|
108291
|
+
/Connection failed: error sending request/i,
|
|
108292
|
+
/waiting for network/i
|
|
108293
|
+
];
|
|
108294
|
+
function failureSentence(message) {
|
|
108295
|
+
return message.split(/(?:^|[.\s])stderr:/)[0].trim();
|
|
108296
|
+
}
|
|
108297
|
+
function isNetworkFailure(message) {
|
|
108298
|
+
const sentence = failureSentence(message);
|
|
108299
|
+
return NETWORK_WORDS.some((w) => w.test(sentence));
|
|
108300
|
+
}
|
|
108301
|
+
function interruptionAfter(e, now, endedTurn) {
|
|
108302
|
+
switch (e.type) {
|
|
108303
|
+
case "error":
|
|
108304
|
+
if (!e.sessionId || !endedTurn || e.error.code === "adapter_crashed") return void 0;
|
|
108305
|
+
return isNetworkFailure(e.error.message) ? { reason: "network", at: now, detail: failureSentence(e.error.message).slice(0, 600) } : null;
|
|
108306
|
+
case "turn_complete":
|
|
108307
|
+
return null;
|
|
108308
|
+
case "state_change":
|
|
108309
|
+
if (e.state === "working") return null;
|
|
108310
|
+
if (e.state === "waiting_input" && e.reason === INTERRUPTED_BY_TOOL) return { reason: "interrupted", at: now };
|
|
108311
|
+
if (e.state === "waiting_input" && e.reason === "interrupted") return null;
|
|
108312
|
+
return void 0;
|
|
108313
|
+
default:
|
|
108314
|
+
return void 0;
|
|
108315
|
+
}
|
|
108316
|
+
}
|
|
108317
|
+
var RECONNECTING_FLAG_MS = 5 * 6e4;
|
|
108318
|
+
function interruptionWords(i) {
|
|
108319
|
+
switch (i.reason) {
|
|
108320
|
+
case "network":
|
|
108321
|
+
return "stopped: network error";
|
|
108322
|
+
case "quit":
|
|
108323
|
+
return "stopped: Centralu quit during its turn";
|
|
108324
|
+
case "shutdown":
|
|
108325
|
+
return `stopped: ${SHUTDOWN_WORDS} during its turn`;
|
|
108326
|
+
case "interrupted":
|
|
108327
|
+
return "stopped: its tool interrupted the turn";
|
|
108328
|
+
case "reconnecting":
|
|
108329
|
+
return `reconnecting to its API for ${Math.max(1, Math.round((Date.now() - i.at) / 6e4))} min`;
|
|
108330
|
+
}
|
|
108331
|
+
}
|
|
108332
|
+
var wentWithItsProcess = (i) => i?.reason === "quit" || i?.reason === "shutdown";
|
|
108333
|
+
var UPDATE_CONTINUE_LINE = "Centralu was updated during this turn, which stopped it; Centralu continued it on the new version.";
|
|
108334
|
+
var UPDATE_TASKS_LINE = "Centralu was updated, which stopped background work of this session; Centralu told the agent so it can start again what it needs.";
|
|
108335
|
+
|
|
108336
|
+
// packages/agent-host/src/sessions/tool-answers.ts
|
|
108337
|
+
function trustedJsonText(value) {
|
|
108338
|
+
return JSON.stringify(value).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
108339
|
+
}
|
|
108340
|
+
function plainQuoted(value, max = 60) {
|
|
108341
|
+
const cut3 = value.length > max ? `${value.slice(0, max)}\u2026` : value;
|
|
108342
|
+
return JSON.stringify(cut3).replace(/[\p{Cc}\p{Cf}\u2028\u2029]/gu, (c) => {
|
|
108343
|
+
const code2 = c.codePointAt(0);
|
|
108344
|
+
return code2 > 65535 ? `\\u{${code2.toString(16)}}` : `\\u${code2.toString(16).padStart(4, "0")}`;
|
|
108345
|
+
});
|
|
108346
|
+
}
|
|
108347
|
+
var READER_LIST_CAP = 40;
|
|
108348
|
+
async function listSessionsText(tools, caller, project2) {
|
|
108349
|
+
const all = await tools.listSessions(project2 ? { project: project2 } : void 0);
|
|
108350
|
+
if (all.length === 0) {
|
|
108351
|
+
if (project2) return { text: `No session in a project called ${frameField(project2)}. Leave project out to list every one.` };
|
|
108352
|
+
return { text: caller.profile === "reader" ? "There are no other sessions." : "There are no sessions under management." };
|
|
108353
|
+
}
|
|
108354
|
+
const list2 = caller.profile === "reader" ? all.slice(0, READER_LIST_CAP) : all;
|
|
108355
|
+
const more = all.length - list2.length;
|
|
108356
|
+
return {
|
|
108357
|
+
text: list2.map(
|
|
108358
|
+
(s) => (
|
|
108359
|
+
// A linked machine's session says where it runs (#82 phase 4), and why the row is only what was last known
|
|
108360
|
+
// Names on one line (#838): a session's name is set by rename or an auto-name, and a line break in it made a row of its own
|
|
108361
|
+
`- ${frameField(s.name)} [${s.sessionId}] \xB7 project ${frameField(s.project)}${s.machine ? ` on ${frameField(s.machine)}` : ""} \xB7 ${s.tool} \xB7 ${s.waitingOn === "question" ? "waiting on a question" : s.state}` + (s.away ? ` \xB7 ${s.away}` : "") + // A turn cut short by the network or a quit looks like any session at rest (#698)
|
|
108362
|
+
(s.interruption ? ` \xB7 ${interruptionWords(s.interruption)}` : "") + // If merge status is not shown, the manager keeps assigning work to a finished branch (#69, dogfooding)
|
|
108363
|
+
(s.merged ? " \xB7 merged" : "") + // PR status (#76 stage 3) — assigning new work to a branch awaiting review pollutes the PR
|
|
108364
|
+
(s.pr ? ` \xB7 PR #${s.pr.number}(${s.pr.state})` : "") + (s.lastActive ? ` \xB7 last ${s.lastActive}` : "") + (s.preview ? `
|
|
108365
|
+
recent (JSON): ${trustedJsonText(s.preview)}` : "")
|
|
108366
|
+
)
|
|
108367
|
+
).join("\n") + (more > 0 ? `
|
|
108368
|
+
${more} more; pass project to narrow.` : "")
|
|
108369
|
+
};
|
|
108370
|
+
}
|
|
108371
|
+
function cardsText(cards) {
|
|
108372
|
+
return cards.map(
|
|
108373
|
+
(c) => c.kind === "question" ? `card ${c.card} (question), as JSON: ${trustedJsonText(JSON.stringify(c.questions.map((q2) => ({ question: q2.question, header: q2.header, options: q2.options.map((o) => o.label), ...q2.options.some((o) => o.description) ? { descriptions: q2.options.map((o) => o.description) } : {}, multiSelect: q2.multiSelect }))))}` : `card ${c.card} (approval${c.answerable ? ", yours to allow or deny" : ", only the person can answer it"}), as JSON: ${trustedJsonText(JSON.stringify(c.detail))}`
|
|
108374
|
+
).join("\n");
|
|
108375
|
+
}
|
|
108376
|
+
function progressText(p) {
|
|
108377
|
+
if (!p) return "";
|
|
108378
|
+
return ` It last recorded something ${p.quietFor} ago${p.lastStep ? `; its last step, as JSON: ${JSON.stringify(p.lastStep)}` : ""}.`;
|
|
108379
|
+
}
|
|
108380
|
+
function busyText(b2, call2) {
|
|
108381
|
+
const what = {
|
|
108382
|
+
note: "add it to the running turn as a note; that turn's answer stays its owner's, and you read the reply to your note",
|
|
108383
|
+
queue: "hold it and deliver it as a new turn when the running one ends; you are told when that turn ends",
|
|
108384
|
+
fork: "start a sub-session from a copy of its conversation now, linked to you, in a worktree of its own (fork_read: read-only work in its folder)"
|
|
108385
|
+
};
|
|
108386
|
+
const lines = b2.choices.map((c) => `- ${c.choice}: ${c.available ? `${what[c.choice]}. ${call2(c.choice === "fork" ? "fork" : c.choice)}` : `not available: ${c.why}`}`);
|
|
108387
|
+
const ahead = b2.ahead ? ` ${b2.ahead} request${b2.ahead === 1 ? " is" : "s are"} queued there already.` : "";
|
|
108388
|
+
return `"${b2.sessionName}" [${b2.sessionId}] is busy (${b2.sessionState}) with a turn that is not yours, so nothing was sent.${progressText(b2.progress)}${ahead} Choose:
|
|
108389
|
+
${lines.join("\n")}`;
|
|
108390
|
+
}
|
|
108391
|
+
function queuedText(q2) {
|
|
108392
|
+
const instead = q2.lowMemory ? `Not forked: memory is low (${q2.lowMemory}), so it was queued instead, which starts no new process. ` : "";
|
|
108393
|
+
return `${instead}Queued for "${q2.sessionName}" [${q2.sessionId}] as ${q2.queueId}, number ${q2.position} in its queue: it goes in as a new turn when the running one ends, and you are told when that turn ends.`;
|
|
108394
|
+
}
|
|
108395
|
+
var ANSWER_FORM = 'Answer a question with its option label, or your own words; one line per question; several choices of one question separated by ";". The person may answer first; the first answer counts.';
|
|
108396
|
+
function askProjectText(r) {
|
|
108397
|
+
if (!r.ok) return { text: r.error, isError: true };
|
|
108398
|
+
const where2 = `the session "${r.sessionName}" [${r.sessionId}] in ${r.project}`;
|
|
108399
|
+
if (r.state === "busy") return { text: busyText(r, (c) => `ask_project with the same task and ifBusy "${c}".`) };
|
|
108400
|
+
if (r.state === "queued") return { text: `${queuedText(r)} Call ask_project with project "${r.project}", session "${r.sessionId}" and no task to see where it stands.` };
|
|
108401
|
+
if (r.state === "asking") {
|
|
108402
|
+
return {
|
|
108403
|
+
text: `${where2} is waiting on you in the task you gave it:
|
|
108404
|
+
${cardsText(r.cards)}
|
|
108405
|
+
Answer with agent_session(sessionId="${r.sessionId}", action="answer", card=\u2026, text=\u2026). ${ANSWER_FORM} Then call ask_project with project "${r.project}", session "${r.sessionId}" and no task to wait for the answer.`
|
|
108406
|
+
};
|
|
108407
|
+
}
|
|
108408
|
+
if (r.state === "working") {
|
|
108409
|
+
return {
|
|
108410
|
+
text: `${r.project} is still working on it in ${where2}${r.notice ? ` \u2014 ${r.notice}` : ""}.${progressText(r.progress)} To keep waiting, call ask_project again with project "${r.project}", session "${r.sessionId}" and no task; the work goes on meanwhile.`
|
|
108411
|
+
};
|
|
108412
|
+
}
|
|
108413
|
+
const lines = [
|
|
108414
|
+
r.paused ? `${r.project} has not finished (from ${where2}): ${r.paused} What it said so far, as JSON:` : `${r.project} answered (from ${where2}), as JSON:`,
|
|
108415
|
+
trustedJsonText(r.answer || "(no final text)")
|
|
108416
|
+
];
|
|
108417
|
+
if (r.readable.length) lines.push(`You can read these now: ${r.readable.join(", ")}`);
|
|
108418
|
+
if (r.outside.length) lines.push(`Named outside ${r.project}'s folder (not opened for you): ${r.outside.join(", ")}`);
|
|
108419
|
+
return { text: lines.join("\n") };
|
|
108420
|
+
}
|
|
108421
|
+
var NO_POLLING = "Do not check on it with status meanwhile. With nothing else to do, wait is the one call that blocks until the end:";
|
|
108422
|
+
var NEW_ROLE_NEW_AGENT = "For another kind of work (say, editing files after read-only research), start a new agent instead, with the task written in full: it starts without this one's context, and this one's name and role came from its first task.";
|
|
108423
|
+
function cleanUpLine(sessionId, call2 = `agent_session(sessionId="${sessionId}", action="delete")`) {
|
|
108424
|
+
return `Archive it once its answer is used: ${call2}. Keep it only if more work on the same thing is coming soon, and archive it when that is done. Work not on the trunk is kept in the session archive.`;
|
|
108425
|
+
}
|
|
108426
|
+
function agentListText(r) {
|
|
108427
|
+
if (!r.ok) return { text: r.error, isError: true };
|
|
108428
|
+
const rows3 = r.agents;
|
|
108429
|
+
if (rows3.length === 0) return { text: "No session start_agent started from here is left." };
|
|
108430
|
+
const finished = rows3.filter((r5) => r5.state === "finished").length;
|
|
108431
|
+
const lines = rows3.map(
|
|
108432
|
+
(r5) => `- ${frameField(r5.name)} [${r5.sessionId}] \xB7 ${r5.tool} \xB7 ${r5.state}${r5.asleep ? " (asleep)" : ""}` + (r5.machine ? ` \xB7 on ${frameField(r5.machine)}` : "") + (r5.worktree ? ` \xB7 worktree branch ${frameField(r5.worktree.branch)}${r5.worktree.unmerged ? ", not merged: a delete keeps its work in the session archive" : ", merged"}` : "") + (r5.lastActive ? ` \xB7 last ${r5.lastActive}` : "")
|
|
108433
|
+
);
|
|
108434
|
+
return {
|
|
108435
|
+
text: `Sessions you started (${rows3.length}, ${finished} finished):
|
|
108436
|
+
${lines.join("\n")}
|
|
108437
|
+
` + (finished ? 'Delete each finished one whose answer you have used: agent_session(sessionId=\u2026, action="delete").' : "None is finished yet.")
|
|
108438
|
+
};
|
|
108439
|
+
}
|
|
108440
|
+
function agentRunText(r) {
|
|
108441
|
+
if (!r.ok) return { text: r.error, isError: true };
|
|
108442
|
+
if (r.state === "pending" && r.lowMemory) {
|
|
108443
|
+
return {
|
|
108444
|
+
text: `queued: low memory. No session yet: starting ${r.tool} is allowed and waits for memory (${r.lowMemory.available} free, ${r.lowMemory.floor} needed; request ${r.sessionId}). It starts when memory frees up, and you will be told when its turn ends; if memory stays low for ${r.lowMemory.maxWaitMinutes} minutes it gives up and you will be told. Carry on with other work, and do not do this with your own subagents instead. ${NO_POLLING} agent_session(sessionId="${r.sessionId}", action="wait") waits, action="stop" withdraws it.`
|
|
108445
|
+
};
|
|
108446
|
+
}
|
|
108447
|
+
if (r.state === "pending") {
|
|
108448
|
+
return {
|
|
108449
|
+
text: `No session yet: starting ${r.tool} is waiting for the person's answer on the card in your session (request ${r.sessionId}). Carry on with other work, and do not do this with your own subagents instead. If they allow it, the session starts and you will be told when its turn ends; if they refuse or it is withdrawn, you will be told. ${NO_POLLING} agent_session(sessionId="${r.sessionId}", action="wait") waits for the answer, action="stop" withdraws the request.`
|
|
108450
|
+
};
|
|
108451
|
+
}
|
|
108452
|
+
const who = `the ${r.tool} session "${r.sessionName}" [${r.sessionId}]` + (r.via === "fork" ? `, a fork of [${r.forkedFrom}]` : "");
|
|
108453
|
+
const out = agentStateText(r, who);
|
|
108454
|
+
return r.via === "note" ? { ...out, text: `Added as a note to the running turn. ${out.text}` } : out;
|
|
108455
|
+
}
|
|
108456
|
+
function agentStateText(r, who) {
|
|
108457
|
+
switch (r.state) {
|
|
108458
|
+
case "working":
|
|
108459
|
+
return {
|
|
108460
|
+
text: `${who} is working on it${r.notice ? ` \u2014 ${r.notice}` : ""}.${progressText(r.progress)} ` + (r.reported ? `You will be told when it ends. ${NO_POLLING} ` : "") + `agent_session(sessionId="${r.sessionId}", action="wait") waits for its answer, action="stop" stops it.`
|
|
108461
|
+
};
|
|
108462
|
+
case "asking":
|
|
108463
|
+
return {
|
|
108464
|
+
text: `${who} is waiting on you in the turn you gave it:
|
|
108465
|
+
${cardsText(r.cards)}
|
|
108466
|
+
agent_session(sessionId="${r.sessionId}", action="answer", card=\u2026, text=\u2026) answers it. ${ANSWER_FORM}`
|
|
108467
|
+
};
|
|
108468
|
+
case "busy":
|
|
108469
|
+
return { text: busyText(r, (c) => `agent_session(sessionId="${r.sessionId}", action="send", text=\u2026, ifBusy="${c}").`) };
|
|
108470
|
+
case "queued":
|
|
108471
|
+
return { text: `${queuedText(r)} agent_session(sessionId="${r.sessionId}", action="stop") withdraws it.` };
|
|
108472
|
+
case "answered":
|
|
108473
|
+
return { text: `Answered card ${r.card} in ${who}; it goes on. agent_session(sessionId="${r.sessionId}", action="wait") waits for its answer.` };
|
|
108474
|
+
case "stopped":
|
|
108475
|
+
if (!r.sessionName) return { text: `Withdrew request ${r.sessionId}: its card is gone and no session was started.` };
|
|
108476
|
+
return { text: `Stopped ${who}.${r.notice ? ` ${r.notice}` : ""}` };
|
|
108477
|
+
case "deleted":
|
|
108478
|
+
if (r.branch === void 0) return { text: `Moved ${who} to the session archive. Tell the person in one line.` };
|
|
108479
|
+
if (r.note) return { text: `Moved ${who} to the session archive. ${r.note} Tell the person in one line.` };
|
|
108480
|
+
return {
|
|
108481
|
+
text: (r.warning ? `Moved ${who} to the session archive. Warning: ${r.warning} ` : `Cleaned up ${who}: its worktree and branch "${r.branch}" were removed and the session was archived. `) + (r.setAside.changedFiles || r.setAside.commits ? `${r.setAside.changedFiles} uncommitted files, ${r.setAside.commits} commits not on the trunk, kept in the session archive. ` : "Nothing needed setting aside. ") + "Tell the person in one line."
|
|
108482
|
+
};
|
|
108483
|
+
case "at_rest":
|
|
108484
|
+
return {
|
|
108485
|
+
text: `${who} is not working on anything you gave it (${r.sessionState}${r.interruption ? `, ${interruptionWords(r.interruption)}` : ""}). Its last answer, as JSON:
|
|
108486
|
+
${trustedJsonText(r.answer || "(no final text)")}
|
|
108487
|
+
` + (r.notice ? `${r.notice}
|
|
108488
|
+
` : "") + `agent_session(sessionId="${r.sessionId}", action="send", text=\u2026) gives it more. ${NEW_ROLE_NEW_AGENT}` + (r.cleanUp ? `
|
|
108489
|
+
${cleanUpLine(r.sessionId)}` : "")
|
|
108490
|
+
};
|
|
108491
|
+
case "done": {
|
|
108492
|
+
const lines = [`${who} answered, as JSON:`, trustedJsonText(r.answer || "(no final text)")];
|
|
108493
|
+
if (r.notice) lines.push(r.notice);
|
|
108494
|
+
if (r.readable.length) lines.push(`You can read these now: ${r.readable.join(", ")}`);
|
|
108495
|
+
if (r.outside.length) lines.push(`Named outside its folder (not opened for you): ${r.outside.join(", ")}`);
|
|
108496
|
+
lines.push(`agent_session(sessionId="${r.sessionId}", action="send", text=\u2026) continues with it. ${NEW_ROLE_NEW_AGENT}`);
|
|
108497
|
+
if (r.cleanUp) lines.push(cleanUpLine(r.sessionId));
|
|
108498
|
+
return { text: lines.join("\n") };
|
|
108499
|
+
}
|
|
108500
|
+
}
|
|
108501
|
+
}
|
|
108502
|
+
var plural = (n, one2, many = `${one2}s`) => `${n} ${n === 1 ? one2 : many}`;
|
|
108503
|
+
|
|
108504
|
+
// packages/agent-host/src/sessions/stale-session-id.ts
|
|
108505
|
+
function staleSessionHint(host, id3, visible, seesAll = false, doing = "act") {
|
|
108506
|
+
if (host.meta.has(id3)) return "";
|
|
108507
|
+
const live2 = [...host.meta.values()].filter((s) => s.kind !== "orchestrator" && visible(s));
|
|
108508
|
+
const found = host.store.trash.trashedSession(id3);
|
|
108509
|
+
const trashed = found && (seesAll || live2.some((s) => s.projectId === found.record.projectId)) ? found : null;
|
|
108510
|
+
const heirId = liveHeirOf(host.store, id3, (h) => {
|
|
108511
|
+
const s = host.meta.get(h);
|
|
108512
|
+
return !!s && s.kind !== "orchestrator" && visible(s);
|
|
108513
|
+
});
|
|
108514
|
+
const heir = heirId ? host.meta.get(heirId) : void 0;
|
|
108515
|
+
if (!trashed && !heir) return "";
|
|
108516
|
+
const named = trashed ? live2.filter((s) => s.name === trashed.session.name && s.projectId === trashed.record.projectId && s !== heir) : [];
|
|
108517
|
+
const label = (s) => `${plainQuoted(s.name, 80)} (id ${s.id})`;
|
|
108518
|
+
const parts = [];
|
|
108519
|
+
if (trashed) parts.push(`That id was the session ${plainQuoted(trashed.session.name, 80)}, deleted since (it is in the archive).`);
|
|
108520
|
+
else parts.push("That id was a session that has been replaced by a handoff.");
|
|
108521
|
+
if (heir) parts.push(`The live session that took over from it: ${label(heir)}.`);
|
|
108522
|
+
if (named.length) parts.push(`Live in the same project with the same name: ${named.map(label).join(", ")}.`);
|
|
108523
|
+
const then2 = doing === "send" ? "Nothing was sent; send to the one you meant, by its id." : doing === "read" ? "Nothing was read; read the one you meant, by its id." : "Nothing was done; retry the same call with the id of the live session you meant.";
|
|
108524
|
+
parts.push(heir || named.length ? then2 : "No live session carries its name; list the sessions to find the one you meant.");
|
|
108525
|
+
return " " + parts.join(" ");
|
|
108526
|
+
}
|
|
108527
|
+
|
|
108241
108528
|
// packages/agent-host/src/mail/script-label.ts
|
|
108242
108529
|
var LABEL_MAX = 40;
|
|
108243
108530
|
var NOT_IN_A_LABEL = /[^\p{L}\p{N} ._:@/+-]/gu;
|
|
@@ -108533,8 +108820,9 @@ function orchestratorToolsFor(host, orchestratorId, scopeIds, ownProject, hubVie
|
|
|
108533
108820
|
const trusted = trustedIds();
|
|
108534
108821
|
return (id3) => !trusted.has(id3);
|
|
108535
108822
|
};
|
|
108536
|
-
const readError = (id3) => ownProject && wide() ? `Not a session you can read: ${id3}` :
|
|
108537
|
-
const scopeError = (id3
|
|
108823
|
+
const readError = (id3) => (ownProject && wide() ? `Not a session you can read: ${id3}` : scopeText(id3)) + staleSessionHint(host, id3, readable, central, "read");
|
|
108824
|
+
const scopeError = (id3, doing = "act") => scopeText(id3) + staleSessionHint(host, id3, inScope, central, doing);
|
|
108825
|
+
const scopeText = (id3) => scopeIds !== void 0 ? `Not a member of this coordinating session: ${id3}` : ownProject ? `Not a session in this project: ${id3}` : hubView ? `Not a session this machine shares: ${id3}` : `Not a session this app manages: ${id3}`;
|
|
108538
108826
|
const sendLinked = async (reader, sessionId, text3, reportBack) => {
|
|
108539
108827
|
const earlier = host.children.linkedReports.get(sessionId);
|
|
108540
108828
|
const mine = reportBack ? { orchestratorId, busy: false, instruction: text3 } : void 0;
|
|
@@ -108855,7 +109143,7 @@ function orchestratorToolsFor(host, orchestratorId, scopeIds, ownProject, hubVie
|
|
|
108855
109143
|
return sendLinked(remote, sessionId, text3, reportBack);
|
|
108856
109144
|
}
|
|
108857
109145
|
const target = host.meta.get(sessionId);
|
|
108858
|
-
if (!target || !inScope(target)) return { ok: false, error: scopeError(sessionId) };
|
|
109146
|
+
if (!target || !inScope(target)) return { ok: false, error: scopeError(sessionId, "send") };
|
|
108859
109147
|
if (reportBack) {
|
|
108860
109148
|
const refused2 = host.refusesDelegation(orchestratorId, { sessionId, projectId: target.projectId }, { tool: "send_to_session", target: target.name || target.id, text: text3 });
|
|
108861
109149
|
if (refused2) return { ok: false, error: refused2 };
|
|
@@ -109291,55 +109579,6 @@ function withHubMark(settings, info) {
|
|
|
109291
109579
|
// packages/agent-host/src/sessions/queued-requests.ts
|
|
109292
109580
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
109293
109581
|
|
|
109294
|
-
// packages/agent-host/src/sessions/interruptions.ts
|
|
109295
|
-
var NETWORK_WORDS = [
|
|
109296
|
-
/\b(ECONNREFUSED|ENOTFOUND|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENETUNREACH|EHOSTUNREACH|ENETDOWN)\b/,
|
|
109297
|
-
/API Error: (No response from API|Can't reach the API server|Connection (refused|lost|reset|error|dropped)|Request timed out|No internet route|fetch failed|Unable to connect to API(?!: SSL))/i,
|
|
109298
|
-
/computer (went to sleep|was asleep)/i,
|
|
109299
|
-
/stream disconnected before completion/i,
|
|
109300
|
-
/failed to lookup address information/i,
|
|
109301
|
-
/Connection failed: error sending request/i,
|
|
109302
|
-
/waiting for network/i
|
|
109303
|
-
];
|
|
109304
|
-
function failureSentence(message) {
|
|
109305
|
-
return message.split(/(?:^|[.\s])stderr:/)[0].trim();
|
|
109306
|
-
}
|
|
109307
|
-
function isNetworkFailure(message) {
|
|
109308
|
-
const sentence = failureSentence(message);
|
|
109309
|
-
return NETWORK_WORDS.some((w) => w.test(sentence));
|
|
109310
|
-
}
|
|
109311
|
-
function interruptionAfter(e, now, endedTurn) {
|
|
109312
|
-
switch (e.type) {
|
|
109313
|
-
case "error":
|
|
109314
|
-
if (!e.sessionId || !endedTurn || e.error.code === "adapter_crashed") return void 0;
|
|
109315
|
-
return isNetworkFailure(e.error.message) ? { reason: "network", at: now, detail: failureSentence(e.error.message).slice(0, 600) } : null;
|
|
109316
|
-
case "turn_complete":
|
|
109317
|
-
return null;
|
|
109318
|
-
case "state_change":
|
|
109319
|
-
if (e.state === "working") return null;
|
|
109320
|
-
if (e.state === "waiting_input" && e.reason === INTERRUPTED_BY_TOOL) return { reason: "interrupted", at: now };
|
|
109321
|
-
if (e.state === "waiting_input" && e.reason === "interrupted") return null;
|
|
109322
|
-
return void 0;
|
|
109323
|
-
default:
|
|
109324
|
-
return void 0;
|
|
109325
|
-
}
|
|
109326
|
-
}
|
|
109327
|
-
var RECONNECTING_FLAG_MS = 5 * 6e4;
|
|
109328
|
-
function interruptionWords(i) {
|
|
109329
|
-
switch (i.reason) {
|
|
109330
|
-
case "network":
|
|
109331
|
-
return "stopped: network error";
|
|
109332
|
-
case "quit":
|
|
109333
|
-
return "stopped: Centralu quit during its turn";
|
|
109334
|
-
case "interrupted":
|
|
109335
|
-
return "stopped: its tool interrupted the turn";
|
|
109336
|
-
case "reconnecting":
|
|
109337
|
-
return `reconnecting to its API for ${Math.max(1, Math.round((Date.now() - i.at) / 6e4))} min`;
|
|
109338
|
-
}
|
|
109339
|
-
}
|
|
109340
|
-
var UPDATE_CONTINUE_LINE = "Centralu was updated during this turn, which stopped it; Centralu continued it on the new version.";
|
|
109341
|
-
var UPDATE_TASKS_LINE = "Centralu was updated, which stopped background work of this session; Centralu told the agent so it can start again what it needs.";
|
|
109342
|
-
|
|
109343
109582
|
// packages/agent-host/src/sessions/error-kind.ts
|
|
109344
109583
|
var USAGE_LIMIT = /usage limit|rate.?limit|\b429\b|quota|limit reached|hit your limit/i;
|
|
109345
109584
|
var OVERLOADED = /overload|\b529\b|\b503\b|capacity|temporarily unavailable/i;
|
|
@@ -109610,174 +109849,6 @@ ${reason}`
|
|
|
109610
109849
|
// packages/agent-host/src/sessions/delegated-cards.ts
|
|
109611
109850
|
import { createHash as createHash9 } from "node:crypto";
|
|
109612
109851
|
|
|
109613
|
-
// packages/agent-host/src/sessions/tool-answers.ts
|
|
109614
|
-
function trustedJsonText(value) {
|
|
109615
|
-
return JSON.stringify(value).replace(/\u2028/g, "\\u2028").replace(/\u2029/g, "\\u2029");
|
|
109616
|
-
}
|
|
109617
|
-
function plainQuoted(value, max = 60) {
|
|
109618
|
-
const cut3 = value.length > max ? `${value.slice(0, max)}\u2026` : value;
|
|
109619
|
-
return JSON.stringify(cut3).replace(/[\p{Cc}\p{Cf}\u2028\u2029]/gu, (c) => {
|
|
109620
|
-
const code2 = c.codePointAt(0);
|
|
109621
|
-
return code2 > 65535 ? `\\u{${code2.toString(16)}}` : `\\u${code2.toString(16).padStart(4, "0")}`;
|
|
109622
|
-
});
|
|
109623
|
-
}
|
|
109624
|
-
var READER_LIST_CAP = 40;
|
|
109625
|
-
async function listSessionsText(tools, caller, project2) {
|
|
109626
|
-
const all = await tools.listSessions(project2 ? { project: project2 } : void 0);
|
|
109627
|
-
if (all.length === 0) {
|
|
109628
|
-
if (project2) return { text: `No session in a project called ${frameField(project2)}. Leave project out to list every one.` };
|
|
109629
|
-
return { text: caller.profile === "reader" ? "There are no other sessions." : "There are no sessions under management." };
|
|
109630
|
-
}
|
|
109631
|
-
const list2 = caller.profile === "reader" ? all.slice(0, READER_LIST_CAP) : all;
|
|
109632
|
-
const more = all.length - list2.length;
|
|
109633
|
-
return {
|
|
109634
|
-
text: list2.map(
|
|
109635
|
-
(s) => (
|
|
109636
|
-
// A linked machine's session says where it runs (#82 phase 4), and why the row is only what was last known
|
|
109637
|
-
// Names on one line (#838): a session's name is set by rename or an auto-name, and a line break in it made a row of its own
|
|
109638
|
-
`- ${frameField(s.name)} [${s.sessionId}] \xB7 project ${frameField(s.project)}${s.machine ? ` on ${frameField(s.machine)}` : ""} \xB7 ${s.tool} \xB7 ${s.waitingOn === "question" ? "waiting on a question" : s.state}` + (s.away ? ` \xB7 ${s.away}` : "") + // A turn cut short by the network or a quit looks like any session at rest (#698)
|
|
109639
|
-
(s.interruption ? ` \xB7 ${interruptionWords(s.interruption)}` : "") + // If merge status is not shown, the manager keeps assigning work to a finished branch (#69, dogfooding)
|
|
109640
|
-
(s.merged ? " \xB7 merged" : "") + // PR status (#76 stage 3) — assigning new work to a branch awaiting review pollutes the PR
|
|
109641
|
-
(s.pr ? ` \xB7 PR #${s.pr.number}(${s.pr.state})` : "") + (s.lastActive ? ` \xB7 last ${s.lastActive}` : "") + (s.preview ? `
|
|
109642
|
-
recent (JSON): ${trustedJsonText(s.preview)}` : "")
|
|
109643
|
-
)
|
|
109644
|
-
).join("\n") + (more > 0 ? `
|
|
109645
|
-
${more} more; pass project to narrow.` : "")
|
|
109646
|
-
};
|
|
109647
|
-
}
|
|
109648
|
-
function cardsText(cards) {
|
|
109649
|
-
return cards.map(
|
|
109650
|
-
(c) => c.kind === "question" ? `card ${c.card} (question), as JSON: ${trustedJsonText(JSON.stringify(c.questions.map((q2) => ({ question: q2.question, header: q2.header, options: q2.options.map((o) => o.label), ...q2.options.some((o) => o.description) ? { descriptions: q2.options.map((o) => o.description) } : {}, multiSelect: q2.multiSelect }))))}` : `card ${c.card} (approval${c.answerable ? ", yours to allow or deny" : ", only the person can answer it"}), as JSON: ${trustedJsonText(JSON.stringify(c.detail))}`
|
|
109651
|
-
).join("\n");
|
|
109652
|
-
}
|
|
109653
|
-
function progressText(p) {
|
|
109654
|
-
if (!p) return "";
|
|
109655
|
-
return ` It last recorded something ${p.quietFor} ago${p.lastStep ? `; its last step, as JSON: ${JSON.stringify(p.lastStep)}` : ""}.`;
|
|
109656
|
-
}
|
|
109657
|
-
function busyText(b2, call2) {
|
|
109658
|
-
const what = {
|
|
109659
|
-
note: "add it to the running turn as a note; that turn's answer stays its owner's, and you read the reply to your note",
|
|
109660
|
-
queue: "hold it and deliver it as a new turn when the running one ends; you are told when that turn ends",
|
|
109661
|
-
fork: "start a sub-session from a copy of its conversation now, linked to you, in a worktree of its own (fork_read: read-only work in its folder)"
|
|
109662
|
-
};
|
|
109663
|
-
const lines = b2.choices.map((c) => `- ${c.choice}: ${c.available ? `${what[c.choice]}. ${call2(c.choice === "fork" ? "fork" : c.choice)}` : `not available: ${c.why}`}`);
|
|
109664
|
-
const ahead = b2.ahead ? ` ${b2.ahead} request${b2.ahead === 1 ? " is" : "s are"} queued there already.` : "";
|
|
109665
|
-
return `"${b2.sessionName}" [${b2.sessionId}] is busy (${b2.sessionState}) with a turn that is not yours, so nothing was sent.${progressText(b2.progress)}${ahead} Choose:
|
|
109666
|
-
${lines.join("\n")}`;
|
|
109667
|
-
}
|
|
109668
|
-
function queuedText(q2) {
|
|
109669
|
-
const instead = q2.lowMemory ? `Not forked: memory is low (${q2.lowMemory}), so it was queued instead, which starts no new process. ` : "";
|
|
109670
|
-
return `${instead}Queued for "${q2.sessionName}" [${q2.sessionId}] as ${q2.queueId}, number ${q2.position} in its queue: it goes in as a new turn when the running one ends, and you are told when that turn ends.`;
|
|
109671
|
-
}
|
|
109672
|
-
var ANSWER_FORM = 'Answer a question with its option label, or your own words; one line per question; several choices of one question separated by ";". The person may answer first; the first answer counts.';
|
|
109673
|
-
function askProjectText(r) {
|
|
109674
|
-
if (!r.ok) return { text: r.error, isError: true };
|
|
109675
|
-
const where2 = `the session "${r.sessionName}" [${r.sessionId}] in ${r.project}`;
|
|
109676
|
-
if (r.state === "busy") return { text: busyText(r, (c) => `ask_project with the same task and ifBusy "${c}".`) };
|
|
109677
|
-
if (r.state === "queued") return { text: `${queuedText(r)} Call ask_project with project "${r.project}", session "${r.sessionId}" and no task to see where it stands.` };
|
|
109678
|
-
if (r.state === "asking") {
|
|
109679
|
-
return {
|
|
109680
|
-
text: `${where2} is waiting on you in the task you gave it:
|
|
109681
|
-
${cardsText(r.cards)}
|
|
109682
|
-
Answer with agent_session(sessionId="${r.sessionId}", action="answer", card=\u2026, text=\u2026). ${ANSWER_FORM} Then call ask_project with project "${r.project}", session "${r.sessionId}" and no task to wait for the answer.`
|
|
109683
|
-
};
|
|
109684
|
-
}
|
|
109685
|
-
if (r.state === "working") {
|
|
109686
|
-
return {
|
|
109687
|
-
text: `${r.project} is still working on it in ${where2}${r.notice ? ` \u2014 ${r.notice}` : ""}.${progressText(r.progress)} To keep waiting, call ask_project again with project "${r.project}", session "${r.sessionId}" and no task; the work goes on meanwhile.`
|
|
109688
|
-
};
|
|
109689
|
-
}
|
|
109690
|
-
const lines = [
|
|
109691
|
-
r.paused ? `${r.project} has not finished (from ${where2}): ${r.paused} What it said so far, as JSON:` : `${r.project} answered (from ${where2}), as JSON:`,
|
|
109692
|
-
trustedJsonText(r.answer || "(no final text)")
|
|
109693
|
-
];
|
|
109694
|
-
if (r.readable.length) lines.push(`You can read these now: ${r.readable.join(", ")}`);
|
|
109695
|
-
if (r.outside.length) lines.push(`Named outside ${r.project}'s folder (not opened for you): ${r.outside.join(", ")}`);
|
|
109696
|
-
return { text: lines.join("\n") };
|
|
109697
|
-
}
|
|
109698
|
-
var NO_POLLING = "Do not check on it with status meanwhile. With nothing else to do, wait is the one call that blocks until the end:";
|
|
109699
|
-
var NEW_ROLE_NEW_AGENT = "For another kind of work (say, editing files after read-only research), start a new agent instead, with the task written in full: it starts without this one's context, and this one's name and role came from its first task.";
|
|
109700
|
-
function cleanUpLine(sessionId, call2 = `agent_session(sessionId="${sessionId}", action="delete")`) {
|
|
109701
|
-
return `Archive it once its answer is used: ${call2}. Keep it only if more work on the same thing is coming soon, and archive it when that is done. Work not on the trunk is kept in the session archive.`;
|
|
109702
|
-
}
|
|
109703
|
-
function agentListText(r) {
|
|
109704
|
-
if (!r.ok) return { text: r.error, isError: true };
|
|
109705
|
-
const rows3 = r.agents;
|
|
109706
|
-
if (rows3.length === 0) return { text: "No session start_agent started from here is left." };
|
|
109707
|
-
const finished = rows3.filter((r5) => r5.state === "finished").length;
|
|
109708
|
-
const lines = rows3.map(
|
|
109709
|
-
(r5) => `- ${frameField(r5.name)} [${r5.sessionId}] \xB7 ${r5.tool} \xB7 ${r5.state}${r5.asleep ? " (asleep)" : ""}` + (r5.machine ? ` \xB7 on ${frameField(r5.machine)}` : "") + (r5.worktree ? ` \xB7 worktree branch ${frameField(r5.worktree.branch)}${r5.worktree.unmerged ? ", not merged: a delete keeps its work in the session archive" : ", merged"}` : "") + (r5.lastActive ? ` \xB7 last ${r5.lastActive}` : "")
|
|
109710
|
-
);
|
|
109711
|
-
return {
|
|
109712
|
-
text: `Sessions you started (${rows3.length}, ${finished} finished):
|
|
109713
|
-
${lines.join("\n")}
|
|
109714
|
-
` + (finished ? 'Delete each finished one whose answer you have used: agent_session(sessionId=\u2026, action="delete").' : "None is finished yet.")
|
|
109715
|
-
};
|
|
109716
|
-
}
|
|
109717
|
-
function agentRunText(r) {
|
|
109718
|
-
if (!r.ok) return { text: r.error, isError: true };
|
|
109719
|
-
if (r.state === "pending" && r.lowMemory) {
|
|
109720
|
-
return {
|
|
109721
|
-
text: `queued: low memory. No session yet: starting ${r.tool} is allowed and waits for memory (${r.lowMemory.available} free, ${r.lowMemory.floor} needed; request ${r.sessionId}). It starts when memory frees up, and you will be told when its turn ends; if memory stays low for ${r.lowMemory.maxWaitMinutes} minutes it gives up and you will be told. Carry on with other work, and do not do this with your own subagents instead. ${NO_POLLING} agent_session(sessionId="${r.sessionId}", action="wait") waits, action="stop" withdraws it.`
|
|
109722
|
-
};
|
|
109723
|
-
}
|
|
109724
|
-
if (r.state === "pending") {
|
|
109725
|
-
return {
|
|
109726
|
-
text: `No session yet: starting ${r.tool} is waiting for the person's answer on the card in your session (request ${r.sessionId}). Carry on with other work, and do not do this with your own subagents instead. If they allow it, the session starts and you will be told when its turn ends; if they refuse or it is withdrawn, you will be told. ${NO_POLLING} agent_session(sessionId="${r.sessionId}", action="wait") waits for the answer, action="stop" withdraws the request.`
|
|
109727
|
-
};
|
|
109728
|
-
}
|
|
109729
|
-
const who = `the ${r.tool} session "${r.sessionName}" [${r.sessionId}]` + (r.via === "fork" ? `, a fork of [${r.forkedFrom}]` : "");
|
|
109730
|
-
const out = agentStateText(r, who);
|
|
109731
|
-
return r.via === "note" ? { ...out, text: `Added as a note to the running turn. ${out.text}` } : out;
|
|
109732
|
-
}
|
|
109733
|
-
function agentStateText(r, who) {
|
|
109734
|
-
switch (r.state) {
|
|
109735
|
-
case "working":
|
|
109736
|
-
return {
|
|
109737
|
-
text: `${who} is working on it${r.notice ? ` \u2014 ${r.notice}` : ""}.${progressText(r.progress)} ` + (r.reported ? `You will be told when it ends. ${NO_POLLING} ` : "") + `agent_session(sessionId="${r.sessionId}", action="wait") waits for its answer, action="stop" stops it.`
|
|
109738
|
-
};
|
|
109739
|
-
case "asking":
|
|
109740
|
-
return {
|
|
109741
|
-
text: `${who} is waiting on you in the turn you gave it:
|
|
109742
|
-
${cardsText(r.cards)}
|
|
109743
|
-
agent_session(sessionId="${r.sessionId}", action="answer", card=\u2026, text=\u2026) answers it. ${ANSWER_FORM}`
|
|
109744
|
-
};
|
|
109745
|
-
case "busy":
|
|
109746
|
-
return { text: busyText(r, (c) => `agent_session(sessionId="${r.sessionId}", action="send", text=\u2026, ifBusy="${c}").`) };
|
|
109747
|
-
case "queued":
|
|
109748
|
-
return { text: `${queuedText(r)} agent_session(sessionId="${r.sessionId}", action="stop") withdraws it.` };
|
|
109749
|
-
case "answered":
|
|
109750
|
-
return { text: `Answered card ${r.card} in ${who}; it goes on. agent_session(sessionId="${r.sessionId}", action="wait") waits for its answer.` };
|
|
109751
|
-
case "stopped":
|
|
109752
|
-
if (!r.sessionName) return { text: `Withdrew request ${r.sessionId}: its card is gone and no session was started.` };
|
|
109753
|
-
return { text: `Stopped ${who}.${r.notice ? ` ${r.notice}` : ""}` };
|
|
109754
|
-
case "deleted":
|
|
109755
|
-
if (r.branch === void 0) return { text: `Moved ${who} to the session archive. Tell the person in one line.` };
|
|
109756
|
-
if (r.note) return { text: `Moved ${who} to the session archive. ${r.note} Tell the person in one line.` };
|
|
109757
|
-
return {
|
|
109758
|
-
text: (r.warning ? `Moved ${who} to the session archive. Warning: ${r.warning} ` : `Cleaned up ${who}: its worktree and branch "${r.branch}" were removed and the session was archived. `) + (r.setAside.changedFiles || r.setAside.commits ? `${r.setAside.changedFiles} uncommitted files, ${r.setAside.commits} commits not on the trunk, kept in the session archive. ` : "Nothing needed setting aside. ") + "Tell the person in one line."
|
|
109759
|
-
};
|
|
109760
|
-
case "at_rest":
|
|
109761
|
-
return {
|
|
109762
|
-
text: `${who} is not working on anything you gave it (${r.sessionState}${r.interruption ? `, ${interruptionWords(r.interruption)}` : ""}). Its last answer, as JSON:
|
|
109763
|
-
${trustedJsonText(r.answer || "(no final text)")}
|
|
109764
|
-
` + (r.notice ? `${r.notice}
|
|
109765
|
-
` : "") + `agent_session(sessionId="${r.sessionId}", action="send", text=\u2026) gives it more. ${NEW_ROLE_NEW_AGENT}` + (r.cleanUp ? `
|
|
109766
|
-
${cleanUpLine(r.sessionId)}` : "")
|
|
109767
|
-
};
|
|
109768
|
-
case "done": {
|
|
109769
|
-
const lines = [`${who} answered, as JSON:`, trustedJsonText(r.answer || "(no final text)")];
|
|
109770
|
-
if (r.notice) lines.push(r.notice);
|
|
109771
|
-
if (r.readable.length) lines.push(`You can read these now: ${r.readable.join(", ")}`);
|
|
109772
|
-
if (r.outside.length) lines.push(`Named outside its folder (not opened for you): ${r.outside.join(", ")}`);
|
|
109773
|
-
lines.push(`agent_session(sessionId="${r.sessionId}", action="send", text=\u2026) continues with it. ${NEW_ROLE_NEW_AGENT}`);
|
|
109774
|
-
if (r.cleanUp) lines.push(cleanUpLine(r.sessionId));
|
|
109775
|
-
return { text: lines.join("\n") };
|
|
109776
|
-
}
|
|
109777
|
-
}
|
|
109778
|
-
}
|
|
109779
|
-
var plural = (n, one2, many = `${one2}s`) => `${n} ${n === 1 ? one2 : many}`;
|
|
109780
|
-
|
|
109781
109852
|
// packages/agent-host/src/sessions/app-guide.ts
|
|
109782
109853
|
var APP_GUIDE_TOPICS = ["overview", "sessions", "orchestrator", "apps", "approvals", "settings", "updates"];
|
|
109783
109854
|
var STATIC = {
|
|
@@ -110129,8 +110200,8 @@ var ORCHESTRATOR_TOOLS = [
|
|
|
110129
110200
|
*/
|
|
110130
110201
|
{
|
|
110131
110202
|
name: "get_preferences",
|
|
110132
|
-
description: "Reads the person's settings: theme and the themes to choose from, text size, fonts, line height, the notification switches, idle sleep, automatic updates.",
|
|
110133
|
-
schema: external_exports.object({})
|
|
110203
|
+
description: "Reads the person's settings: theme and the themes to choose from (theme files with their problems), text size, fonts, line height, the notification switches, idle sleep, automatic updates, the models for started agents.",
|
|
110204
|
+
schema: external_exports.object({ themeFile: external_exports.string().optional().describe("A theme file id: shows that file in full (its tokens and problems), to edit it with set_preferences themeFile.") }).strict()
|
|
110134
110205
|
},
|
|
110135
110206
|
{
|
|
110136
110207
|
name: "set_preferences",
|
|
@@ -110149,6 +110220,21 @@ var ORCHESTRATOR_TOOLS = [
|
|
|
110149
110220
|
fold: external_exports.array(external_exports.string()).max(50).optional().describe("Sessions whose children to fold away in the sidebar."),
|
|
110150
110221
|
unfold: external_exports.array(external_exports.string()).max(50).optional().describe("Sessions whose children to show again.")
|
|
110151
110222
|
}).strict().optional().describe("Which sidebar groups of children are folded; the rest stay as they are."),
|
|
110223
|
+
agentModels: external_exports.record(
|
|
110224
|
+
external_exports.string(),
|
|
110225
|
+
external_exports.object({
|
|
110226
|
+
light: external_exports.object({ model: external_exports.string().min(1), effort: external_exports.string().optional() }).nullable().optional(),
|
|
110227
|
+
standard: external_exports.object({ model: external_exports.string().min(1), effort: external_exports.string().optional() }).nullable().optional(),
|
|
110228
|
+
heavy: external_exports.object({ model: external_exports.string().min(1), effort: external_exports.string().optional() }).nullable().optional()
|
|
110229
|
+
}).strict()
|
|
110230
|
+
).optional().describe("The model (and effort) start_agent runs when a caller names a difficulty, per tool then light, standard or heavy; null clears one. The rest stay."),
|
|
110231
|
+
themeFile: external_exports.object({
|
|
110232
|
+
id: external_exports.string().optional().describe("An existing theme file to replace; leave out to make a new file named after the theme."),
|
|
110233
|
+
name: external_exports.string().describe("The theme name, 1 to 80 characters."),
|
|
110234
|
+
base: external_exports.enum(["dark", "light"]),
|
|
110235
|
+
tokens: external_exports.record(external_exports.string(), external_exports.string()).describe("Token key to CSS value (get_preferences themeFile shows the keys); a token left out keeps its preset value.")
|
|
110236
|
+
}).strict().optional().describe("Writes a theme file as Settings does; pick it afterwards with theme (its id or name)."),
|
|
110237
|
+
deleteThemeFile: external_exports.string().optional().describe("The id of a theme file to remove (kept in the themes folder under .removed); a side that showed it goes back to its preset."),
|
|
110152
110238
|
notifications: external_exports.object({
|
|
110153
110239
|
approval: external_exports.boolean().optional().describe("Notify when an agent is blocked on an approval."),
|
|
110154
110240
|
done: external_exports.boolean().optional().describe("Notify when a session finishes out of sight."),
|
|
@@ -111314,6 +111400,310 @@ function movedMessage(m, folded = false) {
|
|
|
111314
111400
|
return { handle: m.messageId, name: m.firstLine, state, can, message: true, ...folded ? { folded: true } : {} };
|
|
111315
111401
|
}
|
|
111316
111402
|
|
|
111403
|
+
// packages/agent-host/src/mail/rules.ts
|
|
111404
|
+
var REFUSALS = [
|
|
111405
|
+
"bad_address",
|
|
111406
|
+
"not_local",
|
|
111407
|
+
"not_found",
|
|
111408
|
+
"not_participant",
|
|
111409
|
+
"thread_done",
|
|
111410
|
+
"kind_not_allowed",
|
|
111411
|
+
"field_not_allowed",
|
|
111412
|
+
"not_allowed",
|
|
111413
|
+
"empty",
|
|
111414
|
+
"too_long",
|
|
111415
|
+
"too_many_blockers",
|
|
111416
|
+
"blockers_stand",
|
|
111417
|
+
"no_blocker",
|
|
111418
|
+
"bad_follows",
|
|
111419
|
+
"no_mailbox",
|
|
111420
|
+
"rate_limited",
|
|
111421
|
+
"bad_value",
|
|
111422
|
+
"policy",
|
|
111423
|
+
// mail without threads (docs/plans/mailbox.md §15, S9)
|
|
111424
|
+
"gone",
|
|
111425
|
+
"ambiguous",
|
|
111426
|
+
"unknown",
|
|
111427
|
+
"elsewhere",
|
|
111428
|
+
"unknown_message",
|
|
111429
|
+
"own_message",
|
|
111430
|
+
"recipients",
|
|
111431
|
+
"not_yours",
|
|
111432
|
+
"pair_rate",
|
|
111433
|
+
"repeat",
|
|
111434
|
+
"ask_rate",
|
|
111435
|
+
"orchestrator_asks",
|
|
111436
|
+
"removed",
|
|
111437
|
+
"untrusted_project"
|
|
111438
|
+
];
|
|
111439
|
+
function refuse4(reason, message, more = {}) {
|
|
111440
|
+
return rpcError(message, "refused", { retryable: false, data: { reason, ...more } });
|
|
111441
|
+
}
|
|
111442
|
+
function refusalReason(err) {
|
|
111443
|
+
const reason = asObj(asObj(err).data).reason;
|
|
111444
|
+
return REFUSALS.find((r) => r === reason);
|
|
111445
|
+
}
|
|
111446
|
+
function bodyLimit(sender) {
|
|
111447
|
+
return sender === "app" ? 4e3 : 16e3;
|
|
111448
|
+
}
|
|
111449
|
+
function postsPerMinute(sender) {
|
|
111450
|
+
return sender === "session" ? 30 : sender === "app" ? 10 : Infinity;
|
|
111451
|
+
}
|
|
111452
|
+
var ASKS_PER_HOUR = 5;
|
|
111453
|
+
var REPEAT_WINDOW_MS = 10 * 6e4;
|
|
111454
|
+
function wakes(sender, m) {
|
|
111455
|
+
if (m.kind === "event") return false;
|
|
111456
|
+
if (sender === "centralu" && m.wake === "no") return false;
|
|
111457
|
+
if (sender !== "app") return true;
|
|
111458
|
+
if (m.wake === "no") return false;
|
|
111459
|
+
if (m.kind === "notice") return m.needsOwner || m.wake === "yes";
|
|
111460
|
+
return true;
|
|
111461
|
+
}
|
|
111462
|
+
function needsReceiver(m) {
|
|
111463
|
+
return m.sender === PERSON || m.kind === "task" || m.kind === "report" || m.needsReply || m.facts.needsOwner === true;
|
|
111464
|
+
}
|
|
111465
|
+
var HOLD_QUESTION_MS = 5 * 6e4;
|
|
111466
|
+
var HOLD_OTHER_MS = 15 * 6e4;
|
|
111467
|
+
var DEBOUNCE_MS = 2e3;
|
|
111468
|
+
function dueAt(now, urgent, fromPerson) {
|
|
111469
|
+
return urgent || fromPerson ? now : now + DEBOUNCE_MS;
|
|
111470
|
+
}
|
|
111471
|
+
var MINUTE_MS = 6e4;
|
|
111472
|
+
var HOUR_MS = 60 * MINUTE_MS;
|
|
111473
|
+
var READ_ITEMS = 10;
|
|
111474
|
+
var READ_CHARS = 2e4;
|
|
111475
|
+
var READ_CHAIN = 9;
|
|
111476
|
+
var PAIR_NEW_PER_HOUR = 5;
|
|
111477
|
+
var MAX_RECIPIENTS = 10;
|
|
111478
|
+
var HOLD_UNTRUSTED_OUT = true;
|
|
111479
|
+
var MAX_DEPTH = 4;
|
|
111480
|
+
var HOST_MAIL_TURNS_PER_HOUR = 120;
|
|
111481
|
+
var HOST_SHARE = 10;
|
|
111482
|
+
var PAIR_TURNS_PER_HOUR = 20;
|
|
111483
|
+
var REMINDER = {
|
|
111484
|
+
/** After a session came to rest, before the first reminder */
|
|
111485
|
+
graceMs: 2 * 6e4,
|
|
111486
|
+
/** After a turn the person started */
|
|
111487
|
+
personQuietMs: 10 * 6e4,
|
|
111488
|
+
/** After the first reminder's turn ended; also the wait after the second before the row is released */
|
|
111489
|
+
againMs: 20 * 6e4,
|
|
111490
|
+
perRow: 2,
|
|
111491
|
+
/** Between two follow-up wakes of one session (reminders and notices to a sender together) */
|
|
111492
|
+
gapMs: 20 * 6e4,
|
|
111493
|
+
perDay: 6,
|
|
111494
|
+
/** A session asleep longer than this since its last turn is not woken for a follow-up (its prompt cache has expired) */
|
|
111495
|
+
warmMs: 50 * 6e4,
|
|
111496
|
+
/** After delivery a needsReply row is released: the one 2-hour constant, which also bounds a cover (D23, D25) */
|
|
111497
|
+
unansweredAfterMs: 2 * HOUR_MS
|
|
111498
|
+
};
|
|
111499
|
+
var REFUSAL_TEXT = {
|
|
111500
|
+
gone: (address) => `${address} is gone: it is in the session archive or deleted.`,
|
|
111501
|
+
no_mailbox: () => "that session cannot read mail: only an ordinary session in a project, or the orchestrator, has the mail tool",
|
|
111502
|
+
elsewhere: (address) => `${address} is on another computer, and mail does not reach other computers yet; use agent_session there.`,
|
|
111503
|
+
unknown_message: (id3) => `there is no message ${id3} you can read; check the id with mail(action="inbox").`,
|
|
111504
|
+
own_message: (id3) => `${id3} is your own message; say who it is for with to="\u2026".`,
|
|
111505
|
+
recipients: () => `at most ${MAX_RECIPIENTS} recipients.`,
|
|
111506
|
+
not_allowed: () => "that is not a party that takes mail",
|
|
111507
|
+
withheld: (id3) => `${id3} is a request: its words are shown in the turn it starts, and it is marked done there.`,
|
|
111508
|
+
not_yours: (id3) => `you were not sent ${id3} and did not send it.`,
|
|
111509
|
+
pair_rate: (name) => `you started ${PAIR_NEW_PER_HOUR} conversations with ${name} in the last hour; answer one of its messages instead, or wait.`,
|
|
111510
|
+
repeat: (name) => `you sent the same text to ${name} less than ${REPEAT_WINDOW_MS / 6e4} minutes ago.`,
|
|
111511
|
+
ask_rate: () => `your project's sessions asked the orchestrator ${ASKS_PER_HOUR} times in the last hour; wait, or write to the person.`,
|
|
111512
|
+
orchestrator_asks: () => "the orchestrator runs on a tool that cannot ask the person for each step of a request yet, so a session cannot ask it: write to the person instead",
|
|
111513
|
+
untrusted_project: (project2, target) => `your project ${project2} is not trusted, so work you give outside it (to ${target}) waits for the person to trust ${project2}. Nothing was started. Centralu shows the person how, and tells you when ${project2} is trusted; give it again then.`
|
|
111514
|
+
};
|
|
111515
|
+
var ADOPTED_TASK = "Work given before this version of Centralu wrote it down as mail. The conversation of the session that does it shows what was asked.";
|
|
111516
|
+
var TASK_CUT = "\n[Centralu: cut here. The full task is in the session's conversation (read_session).]";
|
|
111517
|
+
function cutTask(text3, limit) {
|
|
111518
|
+
const task = text3.trim();
|
|
111519
|
+
return task.length > limit ? task.slice(0, limit - TASK_CUT.length) + TASK_CUT : task;
|
|
111520
|
+
}
|
|
111521
|
+
function firstLineOf(text3, max = Infinity) {
|
|
111522
|
+
return oneLineText(text3.split(/\r\n|[\n\r]/)[0] ?? "", max);
|
|
111523
|
+
}
|
|
111524
|
+
function wakesMessage(m) {
|
|
111525
|
+
return m.facts.quiet !== true;
|
|
111526
|
+
}
|
|
111527
|
+
function quietAnswer(m, target, receiver) {
|
|
111528
|
+
return m.kind === "message" && !m.needsReply && !m.urgent && !receiver.request && target.kind === "message" && !target.needsReply && parseAddress(m.sender)?.kind === "session" && m.sender !== receiver.parent && !receiver.owesTask;
|
|
111529
|
+
}
|
|
111530
|
+
function depthOf2(cause2, about, recipients) {
|
|
111531
|
+
if (!cause2 || cause2.sender === PERSON) return 0;
|
|
111532
|
+
const followUp = cause2.kind === "notice" && (cause2.facts.reminder !== void 0 || cause2.facts.unanswered !== void 0);
|
|
111533
|
+
if (cause2.kind !== "message" && !followUp) return 0;
|
|
111534
|
+
const base2 = followUp ? Math.max(0, ...about.map((m) => m.depth)) : cause2.depth;
|
|
111535
|
+
const back = !followUp ? [cause2.sender] : cause2.facts.reminder !== void 0 ? about.map((m) => m.sender) : (cause2.facts.unanswered ?? []).map((u) => u.recipient);
|
|
111536
|
+
return recipients.every((r) => back.includes(r)) ? base2 : base2 + 1;
|
|
111537
|
+
}
|
|
111538
|
+
function pickWork(candidates, now) {
|
|
111539
|
+
const classOf = /* @__PURE__ */ new Map();
|
|
111540
|
+
for (const c of candidates) {
|
|
111541
|
+
const hold = c.needsReply ? HOLD_QUESTION_MS : HOLD_OTHER_MS;
|
|
111542
|
+
const k2 = c.decides || now - c.dueAt > hold ? 1 : c.needsReply ? 2 : 3;
|
|
111543
|
+
const was = classOf.get(c.key);
|
|
111544
|
+
if (was === void 0 || k2 < was) classOf.set(c.key, k2);
|
|
111545
|
+
}
|
|
111546
|
+
let best = null;
|
|
111547
|
+
let bestClass = 4;
|
|
111548
|
+
for (const [key, k2] of classOf) {
|
|
111549
|
+
if (k2 < bestClass) {
|
|
111550
|
+
best = key;
|
|
111551
|
+
bestClass = k2;
|
|
111552
|
+
}
|
|
111553
|
+
}
|
|
111554
|
+
return best;
|
|
111555
|
+
}
|
|
111556
|
+
function flowsDownTo(f) {
|
|
111557
|
+
if (f.fromPerson) return true;
|
|
111558
|
+
if (!f.fromSession || f.toOrchestrator) return false;
|
|
111559
|
+
return f.kind === "task" || f.kind === "message" && f.fromParent;
|
|
111560
|
+
}
|
|
111561
|
+
|
|
111562
|
+
// packages/agent-host/src/mail/delegation.ts
|
|
111563
|
+
function delegationRecorder(mail, turns) {
|
|
111564
|
+
return (callerId, childId, origin, task) => {
|
|
111565
|
+
const written = mail.task(mail.sessionAddress(callerId), mail.sessionAddress(childId), { text: task, origin: delegationOrigin(origin), delivered: true });
|
|
111566
|
+
turns?.attach(childId, written.id, callerId);
|
|
111567
|
+
};
|
|
111568
|
+
}
|
|
111569
|
+
function delegationOrigin(origin) {
|
|
111570
|
+
return origin === "start_agent" || origin === "agent_session" || origin === "send_to_session" || origin === "create_session" || origin === "ask_project" ? origin : "send_to_session";
|
|
111571
|
+
}
|
|
111572
|
+
function personWorkRecorder(mail) {
|
|
111573
|
+
return (sessionId, text3, fromScript) => {
|
|
111574
|
+
mail.task(PERSON, mail.sessionAddress(sessionId), { text: text3, origin: fromScript ? "script" : "create_session", delivered: true });
|
|
111575
|
+
};
|
|
111576
|
+
}
|
|
111577
|
+
function personOwes(mail, records) {
|
|
111578
|
+
return (sessionId) => records.newestTask(PERSON, mail.sessionAddress(sessionId)) !== void 0;
|
|
111579
|
+
}
|
|
111580
|
+
function personReportRecorder(mail, records, waitingOn = () => 0) {
|
|
111581
|
+
return (sessionId, draft) => {
|
|
111582
|
+
const task = records.newestTask(PERSON, mail.sessionAddress(sessionId));
|
|
111583
|
+
if (!task) return false;
|
|
111584
|
+
const owed = draft.then === "review" && draft.outcome === "finished" ? waitingOn(sessionId) : 0;
|
|
111585
|
+
try {
|
|
111586
|
+
mail.report(task.id, turnReport(owed > 0 ? { ...draft, outcome: "paused", then: "running" } : draft, owed));
|
|
111587
|
+
return true;
|
|
111588
|
+
} catch (e) {
|
|
111589
|
+
if (refusalReason(e) !== void 0) return false;
|
|
111590
|
+
throw e;
|
|
111591
|
+
}
|
|
111592
|
+
};
|
|
111593
|
+
}
|
|
111594
|
+
var TURN_ENDED = "[Centralu] The session ended its turn. Its last response says whether the work is done.";
|
|
111595
|
+
var ANSWER_KEPT = 600;
|
|
111596
|
+
function answerRecorder(mail, records, turns, waitingOn = () => 0) {
|
|
111597
|
+
return (callerId, childId, how) => {
|
|
111598
|
+
const owner = mail.sessionAddress(callerId);
|
|
111599
|
+
const child = mail.sessionAddress(childId);
|
|
111600
|
+
const now = Date.now();
|
|
111601
|
+
const reports = records.unreadFor(owner).filter((p) => p.message.kind === "report" && reportOnTaskTo(records, p.message, owner, child)).map((p) => p.message.id);
|
|
111602
|
+
records.markDelivered(owner, reports, now, null);
|
|
111603
|
+
records.markRead(owner, reports, now);
|
|
111604
|
+
const task = records.newestTask(owner, child);
|
|
111605
|
+
if (!how || !task) return;
|
|
111606
|
+
const read = `read_session(sessionId="${childId}")`;
|
|
111607
|
+
const waiting = how.ok ? waitingOn(callerId, childId) : 0;
|
|
111608
|
+
const startedAt = turns?.latest(childId)?.startedAt;
|
|
111609
|
+
const written = mail.report(task.id, {
|
|
111610
|
+
...startedAt !== void 0 ? { startedAt } : {},
|
|
111611
|
+
...waiting > 0 ? { outcome: "waiting", waitingOn: waiting } : { outcome: how.ok ? "finished" : "failed" },
|
|
111612
|
+
body: how.ok ? `${TURN_ENDED}
|
|
111613
|
+
id: ${childId}
|
|
111614
|
+
|
|
111615
|
+
Its last response is that session's own words, so it is not repeated here: ${read} reads it.` : `[Centralu] The assigned work did not finish.
|
|
111616
|
+
id: ${childId}
|
|
111617
|
+
|
|
111618
|
+
Its tool's error, by kind: ${how.error}. Its words are its tool's, not Centralu's, so they are not repeated here: ${read} shows them.`,
|
|
111619
|
+
...how.ok ? { answer: how.text.length > ANSWER_KEPT ? `${how.text.slice(0, ANSWER_KEPT)}\u2026` : how.text } : { error: how.error }
|
|
111620
|
+
});
|
|
111621
|
+
if ("message" in written) {
|
|
111622
|
+
records.markDelivered(owner, [written.message.id], now, null);
|
|
111623
|
+
records.markRead(owner, [written.message.id], now);
|
|
111624
|
+
}
|
|
111625
|
+
};
|
|
111626
|
+
}
|
|
111627
|
+
function reportOnTaskTo(records, report, owner, child) {
|
|
111628
|
+
const task = report.replyTo === null ? void 0 : records.message(report.replyTo);
|
|
111629
|
+
return task?.kind === "task" && task.sender === owner && records.recipient(task.id, child) !== void 0;
|
|
111630
|
+
}
|
|
111631
|
+
function turnRecorder(mail, records, turns) {
|
|
111632
|
+
return (sessionId, turn) => {
|
|
111633
|
+
const child = mail.sessionAddress(sessionId);
|
|
111634
|
+
const taskId = turn.ownerId && (turn.by === "work" || turn.added) ? records.newestTask(mail.sessionAddress(turn.ownerId), child)?.id ?? null : null;
|
|
111635
|
+
const personWork = () => turn.ownerId ? null : records.newestTask(PERSON, child)?.id ?? null;
|
|
111636
|
+
const record2 = (by, messageId, owner) => turn.again ? turns.restate(sessionId, by, messageId, owner) : turns.beginOther(sessionId, by, messageId, owner);
|
|
111637
|
+
if (turn.by === "person") record2("person", null);
|
|
111638
|
+
else if (turn.added) record2("person", taskId, turn.ownerId);
|
|
111639
|
+
else if (turn.source === "report") record2("report", null);
|
|
111640
|
+
else if (turn.source === "app") record2("app", null);
|
|
111641
|
+
else {
|
|
111642
|
+
const person = taskId === null ? personWork() : null;
|
|
111643
|
+
record2(turn.cli && person === null ? "cli" : "caller", taskId ?? person, turn.ownerId);
|
|
111644
|
+
}
|
|
111645
|
+
};
|
|
111646
|
+
}
|
|
111647
|
+
function outcomeOf(draft) {
|
|
111648
|
+
if (draft.then === "dropped") return { outcome: "stopped", reason: "taken_over" };
|
|
111649
|
+
const word = draft.outcome;
|
|
111650
|
+
const outcome = word === "finished" || word === "settled" ? "finished" : word === "failed" || word === "limit_reached" ? "failed" : "stopped";
|
|
111651
|
+
return { outcome, ...word !== outcome ? { reason: word } : {} };
|
|
111652
|
+
}
|
|
111653
|
+
function turnReport(draft, waitingOn) {
|
|
111654
|
+
const base2 = { body: draft.body, ...draft.answer ? { answer: draft.answer } : {}, ...draft.error ? { error: draft.error } : {}, ...draft.personNote ? { personNote: true } : {} };
|
|
111655
|
+
if (draft.then === "running" && draft.paused) return { ...base2, outcome: "paused", paused: draft.paused };
|
|
111656
|
+
if (draft.then === "running") return { ...base2, outcome: "waiting", waitingOn: Math.max(1, waitingOn) };
|
|
111657
|
+
return { ...base2, ...outcomeOf(draft) };
|
|
111658
|
+
}
|
|
111659
|
+
function askedReportBody(draft) {
|
|
111660
|
+
return draft.outcome === "finished" ? "[Centralu] The orchestrator ended its turn on your request. What it sent you in reply is its answer; if it sent nothing, it had none to give. Its other words in that turn are not passed on." : `[Centralu] The orchestrator's turn on your request did not finish (${draft.outcome}). What it sent you in reply, if anything, has reached you. Ask again, or ask the person.`;
|
|
111661
|
+
}
|
|
111662
|
+
function reportedWork(records, turns, owner, child, childId) {
|
|
111663
|
+
const turnWork = turns.latest(childId)?.messageId;
|
|
111664
|
+
const work = turnWork == null ? void 0 : records.message(turnWork);
|
|
111665
|
+
if (work && work.sender === owner && records.recipient(work.id, child)) {
|
|
111666
|
+
if (work.kind === "task") return { work, request: false };
|
|
111667
|
+
if (work.kind === "message") return { work, request: true };
|
|
111668
|
+
}
|
|
111669
|
+
const task = records.newestTask(owner, child);
|
|
111670
|
+
return task && { work: task, request: false };
|
|
111671
|
+
}
|
|
111672
|
+
function reportRecorder(mail, records, turns, waitingOn = () => 0) {
|
|
111673
|
+
return (ownerId, childId, draft) => {
|
|
111674
|
+
const owner = mail.sessionAddress(ownerId);
|
|
111675
|
+
const child = mail.sessionAddress(childId);
|
|
111676
|
+
const found = reportedWork(records, turns, owner, child, childId);
|
|
111677
|
+
if (!found) return "none";
|
|
111678
|
+
if (draft.archiving) return "closed";
|
|
111679
|
+
const { work, request } = found;
|
|
111680
|
+
let written;
|
|
111681
|
+
try {
|
|
111682
|
+
const { answer: _answer, ...unanswered2 } = draft;
|
|
111683
|
+
const startedAt = turns.latest(childId)?.startedAt;
|
|
111684
|
+
const report = request ? { ...turnReport(unanswered2, 0), body: askedReportBody(draft) } : turnReport(draft, waitingOn(ownerId, childId));
|
|
111685
|
+
written = mail.report(work.id, startedAt === void 0 ? report : { ...report, startedAt });
|
|
111686
|
+
} catch (e) {
|
|
111687
|
+
if (refusalReason(e) !== void 0) return "none";
|
|
111688
|
+
throw e;
|
|
111689
|
+
}
|
|
111690
|
+
const seq = turns.latest(childId)?.seq;
|
|
111691
|
+
if (request && seq !== void 0 && draft.then !== "running") mail.requestTurnEnded(child, owner, seq);
|
|
111692
|
+
if ("settledAlready" in written || written.duplicate) return "closed";
|
|
111693
|
+
if (draft.delivery === "wake") return "mailed";
|
|
111694
|
+
if (!written.first && !draft.ownerInTurn) return "closed";
|
|
111695
|
+
const reportId = written.message.id;
|
|
111696
|
+
return {
|
|
111697
|
+
keep: true,
|
|
111698
|
+
settle: () => {
|
|
111699
|
+
const now = Date.now();
|
|
111700
|
+
records.markDelivered(owner, [reportId], now, null);
|
|
111701
|
+
records.markRead(owner, [reportId], now);
|
|
111702
|
+
}
|
|
111703
|
+
};
|
|
111704
|
+
};
|
|
111705
|
+
}
|
|
111706
|
+
|
|
111317
111707
|
// packages/agent-host/src/sessions/child-ownership.ts
|
|
111318
111708
|
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
111319
111709
|
|
|
@@ -111416,6 +111806,8 @@ var ChildOwnership = class {
|
|
|
111416
111806
|
starters = /* @__PURE__ */ new Map();
|
|
111417
111807
|
/** Turns the tool started by itself, no message behind them (`turnStarted`, #1576); memory only, a swap forgets it */
|
|
111418
111808
|
cliTurns = /* @__PURE__ */ new Set();
|
|
111809
|
+
/** Sessions whose running turn an update cut and Centralu continued (#1966): the turn's end carries it, once. Memory only */
|
|
111810
|
+
continuedCuts = /* @__PURE__ */ new Set();
|
|
111419
111811
|
lastBy = /* @__PURE__ */ new Map();
|
|
111420
111812
|
/** The messages given each session's tool that no turn has taken yet (#1639), oldest first; read back from the rows once per host (`held`) */
|
|
111421
111813
|
entries = /* @__PURE__ */ new Map();
|
|
@@ -112004,6 +112396,7 @@ var ChildOwnership = class {
|
|
|
112004
112396
|
this.starters.delete(sessionId);
|
|
112005
112397
|
this.cliTurns.delete(sessionId);
|
|
112006
112398
|
this.takenOver.delete(sessionId);
|
|
112399
|
+
if (this.continuedCuts.delete(sessionId)) starter.cut = true;
|
|
112007
112400
|
if (cut3) this.dropInputOrigins(sessionId);
|
|
112008
112401
|
this.noteTurnEnd(sessionId);
|
|
112009
112402
|
this.spendCarried(sessionId);
|
|
@@ -112047,6 +112440,14 @@ var ChildOwnership = class {
|
|
|
112047
112440
|
lastTurnBy(sessionId) {
|
|
112048
112441
|
return this.lastBy.get(sessionId);
|
|
112049
112442
|
}
|
|
112443
|
+
/** The turn running was cut by an update and continued (#1966): its report says so in one line */
|
|
112444
|
+
noteContinuedCut(sessionId) {
|
|
112445
|
+
this.continuedCuts.add(sessionId);
|
|
112446
|
+
}
|
|
112447
|
+
/** The Continue did not go in after all: the turn's end is a stopped turn's, not a continued one's */
|
|
112448
|
+
dropContinuedCut(sessionId) {
|
|
112449
|
+
this.continuedCuts.delete(sessionId);
|
|
112450
|
+
}
|
|
112050
112451
|
/** Whose the running turn is, when one is recorded */
|
|
112051
112452
|
starterOf(sessionId) {
|
|
112052
112453
|
return this.starters.get(sessionId);
|
|
@@ -112584,7 +112985,7 @@ ${machineLine(machine)}` };
|
|
|
112584
112985
|
var isFromTheFuture = (at2, now) => at2 > now;
|
|
112585
112986
|
|
|
112586
112987
|
// packages/agent-host/src/dev-services/store/usage-history.ts
|
|
112587
|
-
var
|
|
112988
|
+
var HOUR_MS2 = 60 * 6e4;
|
|
112588
112989
|
var BUCKET_COLUMNS = `hour, session_id AS sessionId, project_id AS projectId, tool, model, machine, requests, input,
|
|
112589
112990
|
cache_read AS cacheRead, cache_write AS cacheWrite, cache_write_5m AS cacheWrite5m, cache_write_1h AS cacheWrite1h,
|
|
112590
112991
|
output, rebuilds, rebuild_tokens AS rebuildTokens, causes`;
|
|
@@ -112723,7 +113124,7 @@ function mergeCauses(a, b2) {
|
|
|
112723
113124
|
var OTHERS_KEY = "*";
|
|
112724
113125
|
var MAX_KEYS = 50;
|
|
112725
113126
|
var NO_PROJECT_KEY = "none";
|
|
112726
|
-
var
|
|
113127
|
+
var HOUR_MS3 = 60 * 6e4;
|
|
112727
113128
|
function dayStart(ms3) {
|
|
112728
113129
|
const d2 = new Date(ms3);
|
|
112729
113130
|
d2.setHours(0, 0, 0, 0);
|
|
@@ -112827,7 +113228,7 @@ ${r.scope ?? ""}`;
|
|
|
112827
113228
|
}
|
|
112828
113229
|
function usageHistory(usage, p, names) {
|
|
112829
113230
|
const firstHour = usage.firstHour(p.tool);
|
|
112830
|
-
const fromHour = Math.floor(p.from /
|
|
113231
|
+
const fromHour = Math.floor(p.from / HOUR_MS3) * HOUR_MS3;
|
|
112831
113232
|
return {
|
|
112832
113233
|
by: p.by,
|
|
112833
113234
|
readings: readingSeries(usage.readings(p.from, p.to, p.tool)),
|
|
@@ -113102,9 +113503,9 @@ Pass a machine's name as machine to create_project or add_project.` };
|
|
|
113102
113503
|
remove_app: ({ app: app2, project: project2 }, call2) => appUpkeepCommand({ action: "remove", app: app2, ...trimmed(project2) ? { project: trimmed(project2) } : {} }, call2),
|
|
113103
113504
|
share_app: ({ app: app2, project: project2, shared }, call2) => trustCommand({ action: "share_app", app: app2, ...trimmed(project2) ? { project: trimmed(project2) } : {}, shared }, call2),
|
|
113104
113505
|
// The preferences (#1219): the flow writes each answer; set_preferences names what it did not find to change
|
|
113105
|
-
get_preferences: (
|
|
113506
|
+
get_preferences: (args, call2) => preferencesCommand({ action: "get", ...trimmed(args.themeFile) ? { themeFile: trimmed(args.themeFile) } : {} }, call2),
|
|
113106
113507
|
apply_agent_versions: (_args, call2) => preferencesCommand({ action: "apply_agent_versions" }, call2),
|
|
113107
|
-
set_preferences: (changes, call2) => Object.values(changes).some((v) => v !== void 0 && !(typeof v === "object" && Object.values(v).every((x) => x === void 0))) ? preferencesCommand({ action: "set", changes }, call2) : { text: "Nothing to change: give at least one of theme, textSize, bodyFont, codeFont, lineHeight, idleSleepMinutes, autoUpdate, moveIdleSessionsToNewAgentCli, sendWithModifierEnter, foldedGroups, notifications, autoCompact.", isError: true },
|
|
113508
|
+
set_preferences: (changes, call2) => Object.values(changes).some((v) => v !== void 0 && !(typeof v === "object" && Object.values(v).every((x) => x === void 0))) ? preferencesCommand({ action: "set", changes }, call2) : { text: "Nothing to change: give at least one of theme, textSize, bodyFont, codeFont, lineHeight, idleSleepMinutes, autoUpdate, moveIdleSessionsToNewAgentCli, sendWithModifierEnter, foldedGroups, notifications, autoCompact, agentModels, themeFile, deleteThemeFile.", isError: true },
|
|
113108
113509
|
async handoff_session(args, { tools, caller, signal }) {
|
|
113109
113510
|
const sessionId = args.sessionId?.trim() ?? "";
|
|
113110
113511
|
const waitFor = trimmed(args.waitFor);
|
|
@@ -114818,7 +115219,7 @@ var AgentSessionActions = class {
|
|
|
114818
115219
|
const seat = caller?.projectId ? "project" : caller?.kind === "orchestrator" ? "orchestrator" : null;
|
|
114819
115220
|
const owns = seat === "orchestrator" ? child?.askedBy === callerId : startedHere || !deletingWithoutWorktree && this.host.autoBoundaryOpen(callerId, "session ownership");
|
|
114820
115221
|
if (!caller || !seat || !child || !owns || child.projectId !== caller.projectId) {
|
|
114821
|
-
return { ok: false, error: `${opts.sessionId} is not a session this session started. agent_session works only on sessions start_agent started here
|
|
115222
|
+
return { ok: false, error: `${opts.sessionId} is not a session this session started. agent_session works only on sessions start_agent started here.${child ? "" : staleSessionHint(this.host, opts.sessionId, (s) => s.projectId === caller?.projectId && s.id !== callerId && (seat !== "orchestrator" || s.askedBy === callerId))}` };
|
|
114822
115223
|
}
|
|
114823
115224
|
if (opts.sessionId === callerId) return this.flow.agentAtRest(child);
|
|
114824
115225
|
if (opts.action === "answer") return this.host.answerRoutedCard(callerId, child.id, opts.card, opts.text ?? "");
|
|
@@ -115093,8 +115494,6 @@ var RemoteAgents = class {
|
|
|
115093
115494
|
toldDown = /* @__PURE__ */ new Map();
|
|
115094
115495
|
/** The machines whose link this hub's own update or rollback holds, until it is back (#1618, `linkChanged`) */
|
|
115095
115496
|
updateHolds = /* @__PURE__ */ new Set();
|
|
115096
|
-
/** The turns an update of their machine cut whose callers were told it goes on (#1618), by session and when it was taken up: once each */
|
|
115097
|
-
toldCut = /* @__PURE__ */ new Set();
|
|
115098
115497
|
/** The cards of a caller's turn its caller was told of, once each (`<session>\0<turn>\0<request>`, #676) */
|
|
115099
115498
|
toldCards = /* @__PURE__ */ new Set();
|
|
115100
115499
|
/** Whose each child's turn is, as settled in this host (`Whose`); one not here is read from its record (`whoseOf`) */
|
|
@@ -115232,7 +115631,7 @@ var RemoteAgents = class {
|
|
|
115232
115631
|
if (!reader || this.pending.has(rec.sessionId)) return;
|
|
115233
115632
|
await this.verify(rec, reader);
|
|
115234
115633
|
const report = this.host.children.linkedReports.get(rec.sessionId);
|
|
115235
|
-
if (!report || report.orchestratorId !== rec.callerId || report.paused || !this.gateNow(rec).owned) return;
|
|
115634
|
+
if (!report || report.orchestratorId !== rec.callerId || report.paused && !report.busy || !this.gateNow(rec).owned) return;
|
|
115236
115635
|
const now = await reader.read(rec.sessionId, 1);
|
|
115237
115636
|
if (!now.ok || this.host.children.linkedReports.get(rec.sessionId) !== report || !this.gateNow(rec).owned) return;
|
|
115238
115637
|
this.deferred.delete(rec.sessionId);
|
|
@@ -116103,39 +116502,15 @@ Reason, by kind: ${errorKind(why2)} (its words are an error's, kept in this noti
|
|
|
116103
116502
|
const held3 = this.deferred.delete(rec.sessionId);
|
|
116104
116503
|
if (this.whoseOf(rec) === "unknown" && !this.pending.has(rec.sessionId)) await this.verify(rec, reader);
|
|
116105
116504
|
const report = this.host.children.linkedReports.get(rec.sessionId);
|
|
116106
|
-
if (!report || report.orchestratorId !== rec.callerId || report.paused || !this.gateNow(rec).owned) continue;
|
|
116505
|
+
if (!report || report.orchestratorId !== rec.callerId || report.paused && !report.busy || !this.gateNow(rec).owned) continue;
|
|
116107
116506
|
if (!report.busy && !missed && !held3) continue;
|
|
116108
116507
|
const now = await reader.read(rec.sessionId, 1);
|
|
116109
116508
|
if (!now.ok || this.host.children.linkedReports.get(rec.sessionId) !== report || !this.gateNow(rec).owned) continue;
|
|
116110
116509
|
if (inTurn(now.state ?? "idle")) {
|
|
116111
116510
|
report.busy = true;
|
|
116112
|
-
this.tellContinued(rec);
|
|
116113
116511
|
} else this.host.settleLinkedTurn(rec.sessionId);
|
|
116114
116512
|
}
|
|
116115
116513
|
}
|
|
116116
|
-
/**
|
|
116117
|
-
* A child whose turn an update of its machine cut, found in a turn again once the link is back: the host that update
|
|
116118
|
-
* started continued it (#1618). Its caller is told so once, and that the report it is owed is that turn's end, so it
|
|
116119
|
-
* neither waits on a link that came back nor continues the child by hand a second time.
|
|
116120
|
-
*
|
|
116121
|
-
* Whatever the order of the Continue and the reconnect (#1626 review): the machine's host names each turn it continues
|
|
116122
|
-
* from its start on, before it answers any hello, not once the Continue is in (`SessionManager.updateContinued`), so a
|
|
116123
|
-
* link back while the Continue is still on its way is told it too
|
|
116124
|
-
*/
|
|
116125
|
-
tellContinued(rec) {
|
|
116126
|
-
const at2 = this.host.linkedReader?.continuedAfterUpdate?.(rec.sessionId);
|
|
116127
|
-
if (at2 == null || this.toldCut.has(`${rec.sessionId}@${at2}`)) return;
|
|
116128
|
-
this.toldCut.add(`${rec.sessionId}@${at2}`);
|
|
116129
|
-
const rest = "and Centralu continued it there on the new version. Its report comes when that turn ends; nothing is needed from you now.";
|
|
116130
|
-
void this.host.tellSession(
|
|
116131
|
-
rec.callerId,
|
|
116132
|
-
`[Centralu] An update of Centralu on its machine cut the turn "${rec.name}" [${rec.sessionId}] was running there, ${rest}`,
|
|
116133
|
-
{ sessionId: rec.sessionId, name: rec.name },
|
|
116134
|
-
true,
|
|
116135
|
-
// What the model reads (#1186): all of it but the name, which is not Centralu's words (#120)
|
|
116136
|
-
`[Centralu] An update of Centralu on its machine cut the turn the session [${rec.sessionId}] was running there, ${rest}`
|
|
116137
|
-
);
|
|
116138
|
-
}
|
|
116139
116514
|
/**
|
|
116140
116515
|
* Whether a message the child took was sent by this hub over this link (#1038 review): the machine records a hub's
|
|
116141
116516
|
* instruction from `HUB_SENDER`, which reaches the hub qualified with that machine's id. Another machine's prefix is no
|
|
@@ -116430,13 +116805,13 @@ var NOT_READ = "(not read: the session's last turn was the person's; read_sessio
|
|
|
116430
116805
|
function outcomeWord(work, stop2, ending) {
|
|
116431
116806
|
if (work && "tookOver" in work) return "took_over";
|
|
116432
116807
|
if (work && "swapped" in work) return "swapped";
|
|
116433
|
-
if (work && "resumed" in work) return "resumed";
|
|
116434
116808
|
if (work && ("machineRemoved" in work || "linkLost" in work)) return "stopped";
|
|
116435
116809
|
if (work && ("paused" in work || "waitingOn" in work)) return "paused";
|
|
116436
116810
|
if (work && "settled" in work) return "settled";
|
|
116437
116811
|
if (stop2?.reason === "network") return "network";
|
|
116438
116812
|
if (stop2?.reason === "interrupted") return "interrupted";
|
|
116439
116813
|
if (stop2?.reason === "quit") return "quit";
|
|
116814
|
+
if (stop2?.reason === "shutdown") return "shutdown";
|
|
116440
116815
|
if (ending.type === "error") return "failed";
|
|
116441
116816
|
if (ending.type === "limit_reached") return "limit_reached";
|
|
116442
116817
|
if (ending.type === "turn_complete") return "finished";
|
|
@@ -116456,6 +116831,8 @@ function personReportLine(outcome, tasks) {
|
|
|
116456
116831
|
return "[Centralu] The session you started stopped before finishing: its tool interrupted the turn.";
|
|
116457
116832
|
case "quit":
|
|
116458
116833
|
return "[Centralu] The session you started stopped before finishing: Centralu quit or restarted during its turn.";
|
|
116834
|
+
case "shutdown":
|
|
116835
|
+
return `[Centralu] The session you started stopped before finishing: ${SHUTDOWN_WORDS} during its turn.`;
|
|
116459
116836
|
case "paused":
|
|
116460
116837
|
return `[Centralu] The session you started paused: its turn ended while its own background work is still running (${tasks} task${tasks === 1 ? "" : "s"}).`;
|
|
116461
116838
|
default:
|
|
@@ -116481,17 +116858,17 @@ function machineRemovedReportLine(machine, paused, name = STORED) {
|
|
|
116481
116858
|
function swappedReportLine() {
|
|
116482
116859
|
return "[Centralu] The assigned work's session ended a turn while Centralu was restarting, so that end was not followed. It is most likely the end of the work you gave it; check its last response before relying on it.\n";
|
|
116483
116860
|
}
|
|
116484
|
-
function resumedReportLine(owed) {
|
|
116485
|
-
return "[Centralu] The assigned work's turn was cut short by an update of Centralu on its machine, and Centralu continued it there on the new version. " + (owed ? "Its report follows when that turn ends; this is not it, and nothing is needed from you now.\n" : "Its end is not reported to you; read the session to follow it.\n");
|
|
116486
|
-
}
|
|
116487
116861
|
function tookOverReportLine(paused, name = STORED) {
|
|
116488
116862
|
return "[Centralu] The person took this session over; its later turns are theirs, and none of them will be reported to you. " + (paused.length ? `The work you assigned had paused on its own background work (${name.tasks(paused)}), and no final report follows.
|
|
116489
116863
|
` : "The work you assigned was still running, and no report of it follows.\n");
|
|
116490
116864
|
}
|
|
116491
116865
|
function personPartLine(turn) {
|
|
116492
|
-
|
|
116493
|
-
if (turn?.
|
|
116494
|
-
|
|
116866
|
+
const cut3 = turn?.cut ? "An update of Centralu cut this turn and Centralu continued it.\n" : "";
|
|
116867
|
+
if (turn?.added) return `${cut3}The person added an instruction to this task during this turn.
|
|
116868
|
+
`;
|
|
116869
|
+
if (turn?.wrote) return `${cut3}The person wrote in this session during this turn.
|
|
116870
|
+
`;
|
|
116871
|
+
return cut3;
|
|
116495
116872
|
}
|
|
116496
116873
|
function settledReportLine(tasks, name = STORED) {
|
|
116497
116874
|
const clean = tasks.every((t) => t.status === "completed");
|
|
@@ -116677,26 +117054,12 @@ var ChildReports = class {
|
|
|
116677
117054
|
const asker = requester ?? m?.askedBy ?? void 0;
|
|
116678
117055
|
if (!m || !asker || asker === sessionId || !stop2 || stop2.reason === "reconnecting" || this.host.handingOver) return;
|
|
116679
117056
|
const askerInfo = this.host.meta.get(asker);
|
|
116680
|
-
if (!askerInfo || askerInfo.interruption
|
|
117057
|
+
if (!askerInfo || wentWithItsProcess(askerInfo.interruption)) return;
|
|
116681
117058
|
console.error(`[sessions] ${sessionId.slice(0, 8)} stopped (${stop2.reason}) in a turn no report was owed for; telling ${asker.slice(0, 8)}, which started it`);
|
|
116682
117059
|
const { name, project: project2 } = this.reportedAs(m);
|
|
116683
117060
|
const { preview, seq } = this.stoppedTurnAnswer(sessionId);
|
|
116684
117061
|
await this.deliverReport(asker, sessionId, name, project2, preview, { type: stop2.reason === "network" ? "error" : "state_change" }, void 0, seq);
|
|
116685
117062
|
}
|
|
116686
|
-
/**
|
|
116687
|
-
* A turn of this session that a linked hub's update cut, and this host continued (#1618): its requester hears it once,
|
|
116688
|
-
* so it does not read the session's stop as the end of the work, nor continue it a second time by hand. The report it is
|
|
116689
|
-
* owed, if one is, stays owed: the continued turn's end is it, so it is neither lost nor sent twice
|
|
116690
|
-
*/
|
|
116691
|
-
async tellCutByUpdate(sessionId, requester) {
|
|
116692
|
-
const m = this.host.meta.get(sessionId);
|
|
116693
|
-
if (!m || !this.host.meta.has(requester) || this.host.handingOver) return;
|
|
116694
|
-
const { name, project: project2 } = this.reportedAs(m);
|
|
116695
|
-
const owed = this.host.children.awaitingReport.get(sessionId) === requester;
|
|
116696
|
-
console.error(`[sessions] ${sessionId.slice(0, 8)}: an update cut its turn and it was continued; telling ${requester.slice(0, 8)}`);
|
|
116697
|
-
const { preview, seq } = this.stoppedTurnAnswer(sessionId);
|
|
116698
|
-
await this.deliverReport(requester, sessionId, name, project2, preview, { type: "state_change" }, { resumed: true, owed }, seq);
|
|
116699
|
-
}
|
|
116700
117063
|
/**
|
|
116701
117064
|
* The words of the turn a stop cut (a quit, the network, an update), as a notice of it quotes them: those said so far, or none, never the turn
|
|
116702
117065
|
* before's (#1604, #1946). A turn not seen to begin (one an older build ran) reads as the last response does
|
|
@@ -116758,13 +117121,14 @@ ${read} shows what the person and the session did since.`;
|
|
|
116758
117121
|
/** Reports the end of a turn the orchestrator asked to hear about, as one message in its session (#166) */
|
|
116759
117122
|
async deliverReport(orchestratorId, sessionId, name, project2, preview, ending, work, answerSeq, part) {
|
|
116760
117123
|
const stop2 = this.host.meta.get(sessionId)?.interruption;
|
|
116761
|
-
const cutShort = stop2?.reason === "network" || stop2?.reason === "interrupted" || stop2
|
|
116762
|
-
const outcomeOf3 = (name2) => work && "tookOver" in work ? tookOverReportLine(work.paused, name2) : work && "swapped" in work ? swappedReportLine() : work && "
|
|
117124
|
+
const cutShort = stop2?.reason === "network" || stop2?.reason === "interrupted" || wentWithItsProcess(stop2);
|
|
117125
|
+
const outcomeOf3 = (name2) => work && "tookOver" in work ? tookOverReportLine(work.paused, name2) : work && "swapped" in work ? swappedReportLine() : work && "machineRemoved" in work ? machineRemovedReportLine(work.machineRemoved, work.paused, name2) : work && "linkLost" in work ? linkLostReportLine(work.linkLost, work.paused, name2) : work && "paused" in work ? pausedReportLine(work.paused, work.stoppable ? this.agentCall(orchestratorId, { action: "stop" }) : void 0, name2) : work && "settled" in work ? settledReportLine(work.settled, name2) : work && "waitingOn" in work ? waitingReportLine(work.waitingOn) : stop2?.reason === "network" ? `[Centralu] The assigned work stopped before finishing: a network error ended its turn.
|
|
116763
117126
|
` : stop2?.reason === "interrupted" ? `[Centralu] The assigned work stopped before finishing: its tool interrupted the turn.
|
|
116764
117127
|
` : stop2?.reason === "quit" ? `[Centralu] The assigned work stopped before finishing: Centralu quit or restarted during its turn.
|
|
117128
|
+
` : stop2?.reason === "shutdown" ? `[Centralu] The assigned work stopped before finishing: ${SHUTDOWN_WORDS} during its turn.
|
|
116765
117129
|
` : ending.type === "error" ? `[Centralu] The assigned work failed.
|
|
116766
117130
|
` : ending.type === "limit_reached" ? `[Centralu] The assigned work stopped after hitting a usage limit.
|
|
116767
|
-
` : ending.type === "turn_complete" ?
|
|
117131
|
+
` : ending.type === "turn_complete" ? `${TURN_ENDED}
|
|
116768
117132
|
` : `[Centralu] The assigned work stopped before finishing.
|
|
116769
117133
|
`;
|
|
116770
117134
|
const outcome = outcomeOf3(STORED) + personPartLine(part);
|
|
@@ -116777,23 +117141,24 @@ ${read} shows what the person and the session did since.`;
|
|
|
116777
117141
|
const cleanUp = !work && !stop2 && ending.type === "turn_complete" ? this.cleanUpWords(orchestratorId, sessionId) : "";
|
|
116778
117142
|
const read = `read_session(sessionId="${sessionId}"${answerSeq !== void 0 ? `, around=${answerSeq}` : ""})`;
|
|
116779
117143
|
const kind = message ? stop2?.reason === "network" ? "network" : errorKind(message) : void 0;
|
|
116780
|
-
const answered = preview !== "" && preview !== NOT_READ;
|
|
117144
|
+
const answered = preview !== "" && preview !== NOT_READ && preview !== MACHINE_UNREACHABLE && preview !== IN_PAUSE_REPORT;
|
|
117145
|
+
const asRead = !!work && ("linkLost" in work || "machineRemoved" in work) && work.asRead === true;
|
|
117146
|
+
const asReadWords = asRead ? ", as it was last read there, before its machine went out of reach" : "";
|
|
116781
117147
|
const forModel = (inThread) => outcomeOf3(FOR_MODEL) + personPartLine(part) + `id: ${sessionId}
|
|
116782
117148
|
|
|
116783
117149
|
` + (kind ? `Its tool's error, by kind: ${kind}. Its words are its tool's, not Centralu's, so they are not repeated here: ${read} shows them.
|
|
116784
117150
|
|
|
116785
|
-
` : "") + resume + (inThread && answered ? `Its last response is that session's own words: it is the message above in this thread, and ${read} reads more.` : `Its last response is that session's own words, so it is not repeated here: ${read} reads it${preview ? "" : " (it had none)"}.`) + cleanUp;
|
|
117151
|
+
` : "") + resume + (inThread && answered ? `Its last response${asReadWords} is that session's own words: it is the message above in this thread, and ${read} reads more.` : preview === MACHINE_UNREACHABLE ? `Its last response could not be read: its machine could not be reached. ${read} reads it once that machine is back.` : `Its last response${asReadWords} is that session's own words, so it is not repeated here: ${read} reads it${preview ? "" : " (it had none)"}.`) + cleanUp;
|
|
116786
117152
|
const stored = outcome + `Session: ${frameField(name)}
|
|
116787
117153
|
id: ${sessionId}
|
|
116788
117154
|
Project: ${frameField(project2)}
|
|
116789
117155
|
|
|
116790
|
-
` + failure3 + resume + `Last response:
|
|
117156
|
+
` + failure3 + resume + `Last response${asReadWords}:
|
|
116791
117157
|
${preview || "(no content)"}
|
|
116792
117158
|
|
|
116793
117159
|
If you need more, you can read that session's recent conversation with read_session.` + cleanUp;
|
|
116794
117160
|
const holding = !!work && "paused" in work && !("tookOver" in work) && !("machineRemoved" in work) && !("linkLost" in work);
|
|
116795
117161
|
const handedOn = !!work && "waitingOn" in work;
|
|
116796
|
-
const goesOn = !!work && "resumed" in work;
|
|
116797
117162
|
const reported = this.toMailbox(orchestratorId, sessionId, {
|
|
116798
117163
|
outcome: outcomeWord(work, stop2, ending),
|
|
116799
117164
|
body: forModel(true),
|
|
@@ -116801,7 +117166,7 @@ If you need more, you can read that session's recent conversation with read_sess
|
|
|
116801
117166
|
...kind ? { error: kindWord(kind) } : {},
|
|
116802
117167
|
...holding ? { paused: work.paused.length } : {},
|
|
116803
117168
|
...part?.wrote || part?.added ? { personNote: true } : {},
|
|
116804
|
-
then: holding || handedOn
|
|
117169
|
+
then: holding || handedOn ? "running" : "review"
|
|
116805
117170
|
});
|
|
116806
117171
|
if (reported === "mailed" || reported === "closed") return;
|
|
116807
117172
|
await this.tellSession(
|
|
@@ -116828,7 +117193,7 @@ If you need more, you can read that session's recent conversation with read_sess
|
|
|
116828
117193
|
const others = ownAgents(this.host, parentId).filter((s) => s.id !== childId && agentState(this.host, s) === "finished").length;
|
|
116829
117194
|
return `
|
|
116830
117195
|
${cleanUpLine(childId, this.agentCall(parentId, { sessionId: childId, action: "delete" }))}` + (others ? `
|
|
116831
|
-
${others} other
|
|
117196
|
+
${others} other session${others === 1 ? "" : "s"} you started ${others === 1 ? "has" : "have"} ended ${others === 1 ? "its turn" : "their turns"} and remain${others === 1 ? "s" : ""}: ${this.agentCall(parentId, { action: "list" })} shows them.` : "");
|
|
116832
117197
|
}
|
|
116833
117198
|
/**
|
|
116834
117199
|
* An agent_session call a report names, in the form its receiver calls it (#850): the orchestrator reaches agent_session
|
|
@@ -116895,6 +117260,10 @@ sourceSessionId: ${from.sessionId}`;
|
|
|
116895
117260
|
var HELD_NOTICES_MAX = 20;
|
|
116896
117261
|
|
|
116897
117262
|
// packages/agent-host/src/sessions/interruption-watch.ts
|
|
117263
|
+
var LOST_TURN_LINE = {
|
|
117264
|
+
quit: "Centralu quit or restarted while this turn was running, so the turn stopped before it finished. Send a message to continue.",
|
|
117265
|
+
shutdown: `This turn stopped before it finished: ${SHUTDOWN_WORDS} while it was running. Send a message to continue.`
|
|
117266
|
+
};
|
|
116898
117267
|
var InterruptionWatch = class {
|
|
116899
117268
|
constructor(host) {
|
|
116900
117269
|
this.host = host;
|
|
@@ -116940,19 +117309,20 @@ var InterruptionWatch = class {
|
|
|
116940
117309
|
}
|
|
116941
117310
|
/**
|
|
116942
117311
|
* The turn went with its process (#698): a start found the session mid-turn with no process left (a full quit, a
|
|
116943
|
-
* reboot, a crash the keeper did not survive), or a kept process could not be re-attached.
|
|
117312
|
+
* reboot, a crash the keeper did not survive), or a kept process could not be re-attached. `shutdown`: the start found the
|
|
117313
|
+
* last host recorded the system ending (a shutdown, restart or sign-out, #1397), which is said so rather than as a quit. The session is reset to
|
|
116944
117314
|
* `idle`, which used to leave no mark at all: the conversation simply stopped. A line now says so, and the
|
|
116945
117315
|
* interruption puts the session in the inbox with a way to continue.
|
|
116946
117316
|
*/
|
|
116947
|
-
|
|
116948
|
-
m.interruption = { reason
|
|
117317
|
+
markTurnLost(m, announce, reason = "quit") {
|
|
117318
|
+
m.interruption = { reason, at: Date.now() };
|
|
116949
117319
|
const notice = {
|
|
116950
117320
|
type: "notice",
|
|
116951
117321
|
sessionId: m.id,
|
|
116952
117322
|
level: "warning",
|
|
116953
117323
|
from: "Centralu",
|
|
116954
117324
|
label: "stopped",
|
|
116955
|
-
text:
|
|
117325
|
+
text: LOST_TURN_LINE[reason]
|
|
116956
117326
|
};
|
|
116957
117327
|
const seq = this.host.record.persistMessage(notice, m);
|
|
116958
117328
|
if (!announce) return;
|
|
@@ -117247,11 +117617,150 @@ var MergeChecks = class {
|
|
|
117247
117617
|
}
|
|
117248
117618
|
};
|
|
117249
117619
|
|
|
117620
|
+
// packages/agent-host/src/themes.ts
|
|
117621
|
+
import { randomBytes as randomBytes7 } from "node:crypto";
|
|
117622
|
+
import { watch as watch3 } from "node:fs";
|
|
117623
|
+
import { mkdir as mkdir4, readdir as readdir7, readFile as readFile4, rename as rename4, stat as stat7 } from "node:fs/promises";
|
|
117624
|
+
import { basename as basename8, join as join31 } from "node:path";
|
|
117625
|
+
var MAX_FILE_BYTES = 256 * 1024;
|
|
117626
|
+
var SETTLE_MS = 100;
|
|
117627
|
+
function themeSlug(name) {
|
|
117628
|
+
return name.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "theme";
|
|
117629
|
+
}
|
|
117630
|
+
var REMOVED_DIR = ".removed";
|
|
117631
|
+
var ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,59}$/;
|
|
117632
|
+
var ThemeFiles = class {
|
|
117633
|
+
constructor(dir, onChange) {
|
|
117634
|
+
this.dir = dir;
|
|
117635
|
+
this.onChange = onChange;
|
|
117636
|
+
}
|
|
117637
|
+
dir;
|
|
117638
|
+
onChange;
|
|
117639
|
+
watcher = null;
|
|
117640
|
+
timer = null;
|
|
117641
|
+
/** Makes the folder, writes the schema next to the themes, and starts watching */
|
|
117642
|
+
async start() {
|
|
117643
|
+
await mkdir4(this.dir, { recursive: true });
|
|
117644
|
+
await this.writeSchema();
|
|
117645
|
+
try {
|
|
117646
|
+
this.watcher = watch3(this.dir, (_event, name) => {
|
|
117647
|
+
if (name && !String(name).endsWith(".json")) return;
|
|
117648
|
+
if (name === THEME_SCHEMA_FILE) return;
|
|
117649
|
+
if (this.timer) clearTimeout(this.timer);
|
|
117650
|
+
this.timer = setTimeout(() => {
|
|
117651
|
+
this.timer = null;
|
|
117652
|
+
this.onChange();
|
|
117653
|
+
}, SETTLE_MS);
|
|
117654
|
+
});
|
|
117655
|
+
this.watcher.on("error", () => this.stopWatching());
|
|
117656
|
+
} catch {
|
|
117657
|
+
}
|
|
117658
|
+
}
|
|
117659
|
+
close() {
|
|
117660
|
+
this.stopWatching();
|
|
117661
|
+
if (this.timer) clearTimeout(this.timer);
|
|
117662
|
+
this.timer = null;
|
|
117663
|
+
}
|
|
117664
|
+
stopWatching() {
|
|
117665
|
+
this.watcher?.close();
|
|
117666
|
+
this.watcher = null;
|
|
117667
|
+
}
|
|
117668
|
+
/**
|
|
117669
|
+
* The schema an editor uses for completion, written only when it differs — a build that adds
|
|
117670
|
+
* a token updates it, and an unchanged one does not touch the folder (or wake the watcher).
|
|
117671
|
+
*/
|
|
117672
|
+
async writeSchema() {
|
|
117673
|
+
const path2 = join31(this.dir, THEME_SCHEMA_FILE);
|
|
117674
|
+
const text3 = `${JSON.stringify(themeFileJsonSchema(), null, 2)}
|
|
117675
|
+
`;
|
|
117676
|
+
const current = await readFile4(path2, "utf8").catch(() => null);
|
|
117677
|
+
if (current !== text3) writeFileWhole(realOs().files, path2, text3);
|
|
117678
|
+
}
|
|
117679
|
+
/** Every `<id>.json` in the folder, read and checked. A file that cannot be read is listed with the reason. */
|
|
117680
|
+
async list() {
|
|
117681
|
+
const names = await readdir7(this.dir).catch(() => []);
|
|
117682
|
+
const entries = [];
|
|
117683
|
+
const ids = names.filter((name) => name.endsWith(".json") && name !== THEME_SCHEMA_FILE).map((name) => name.slice(0, -".json".length)).filter((id3) => ID_PATTERN.test(id3)).sort();
|
|
117684
|
+
for (const id3 of ids) entries.push(await this.read(id3));
|
|
117685
|
+
return entries;
|
|
117686
|
+
}
|
|
117687
|
+
async read(id3) {
|
|
117688
|
+
const path2 = this.pathOf(id3);
|
|
117689
|
+
try {
|
|
117690
|
+
const info = await stat7(path2);
|
|
117691
|
+
if (info.size > MAX_FILE_BYTES) {
|
|
117692
|
+
return { id: id3, path: path2, name: id3, base: "dark", tokens: {}, problems: ["The file is too large to be a theme."], broken: true };
|
|
117693
|
+
}
|
|
117694
|
+
return parseThemeFile(id3, path2, await readFile4(path2, "utf8"));
|
|
117695
|
+
} catch (e) {
|
|
117696
|
+
return { id: id3, path: path2, name: id3, base: "dark", tokens: {}, problems: [`Could not read the file: ${e.message}`], broken: true };
|
|
117697
|
+
}
|
|
117698
|
+
}
|
|
117699
|
+
/** Writes a theme. `id` null makes a new file named after the theme (`My theme` → `my-theme.json`). */
|
|
117700
|
+
async save(id3, content) {
|
|
117701
|
+
const text3 = formatThemeFile(content);
|
|
117702
|
+
if (Buffer.byteLength(text3) > MAX_FILE_BYTES) throw rpcError("The theme is too large to be a theme file.");
|
|
117703
|
+
const target = id3 ?? await this.freeId(content.name);
|
|
117704
|
+
await mkdir4(this.dir, { recursive: true });
|
|
117705
|
+
const names = await readdir7(this.dir).catch(() => []);
|
|
117706
|
+
const clash = names.find((n) => n !== `${target}.json` && n.toLowerCase() === `${target}.json`);
|
|
117707
|
+
if (clash) throw rpcError(`The themes folder has ${clash}, which the list does not show (ids are lowercase); rename or remove it first.`);
|
|
117708
|
+
writeFileWhole(realOs().files, this.pathOf(target), text3);
|
|
117709
|
+
return this.read(target);
|
|
117710
|
+
}
|
|
117711
|
+
/**
|
|
117712
|
+
* Takes a theme file out of the list without destroying it: it moves to `.removed/<id>-<time>-<random>.json`, a place the list
|
|
117713
|
+
* does not look, so a mistaken removal is one move back. The host has no OS trash (Settings' button reaches it through the
|
|
117714
|
+
* shell), so these stay until the person clears the folder. Where it went is returned; null when there was no such file.
|
|
117715
|
+
*/
|
|
117716
|
+
async remove(id3) {
|
|
117717
|
+
const from = this.pathOf(id3);
|
|
117718
|
+
if (!await stat7(from).then(() => true, () => false)) return null;
|
|
117719
|
+
const kept = join31(this.dir, REMOVED_DIR);
|
|
117720
|
+
await mkdir4(kept, { recursive: true });
|
|
117721
|
+
const to = join31(kept, `${id3}-${Date.now()}-${randomBytes7(3).toString("hex")}.json`);
|
|
117722
|
+
await rename4(from, to);
|
|
117723
|
+
return to;
|
|
117724
|
+
}
|
|
117725
|
+
/** Copies a file from elsewhere in, under a fresh id if its name is taken. Refuses what does not read as a theme. */
|
|
117726
|
+
async importFrom(source) {
|
|
117727
|
+
const info = await stat7(source);
|
|
117728
|
+
if (!info.isFile()) throw rpcError("That is not a file.");
|
|
117729
|
+
if (info.size > MAX_FILE_BYTES) throw rpcError("The file is too large to be a theme.");
|
|
117730
|
+
const text3 = await readFile4(source, "utf8");
|
|
117731
|
+
const parsed = parseThemeFile("import", source, text3);
|
|
117732
|
+
if (parsed.broken) throw rpcError(parsed.problems[0] ?? "Not a theme file.");
|
|
117733
|
+
const fromName = basename8(source).replace(/\.json$/i, "");
|
|
117734
|
+
const id3 = await this.freeId(parsed.name !== "import" ? parsed.name : fromName);
|
|
117735
|
+
writeFileWhole(realOs().files, this.pathOf(id3), text3);
|
|
117736
|
+
return this.read(id3);
|
|
117737
|
+
}
|
|
117738
|
+
/** The absolute path for an id, for the shell to trash or reveal. Refuses anything that is not an id. */
|
|
117739
|
+
pathOf(id3) {
|
|
117740
|
+
if (!ID_PATTERN.test(id3)) throw rpcError(`Not a theme id: ${id3}`);
|
|
117741
|
+
return join31(this.dir, `${id3}.json`);
|
|
117742
|
+
}
|
|
117743
|
+
async freeId(name) {
|
|
117744
|
+
const base2 = themeSlug(name);
|
|
117745
|
+
for (let n = 1; n < 1e3; n++) {
|
|
117746
|
+
const id3 = n === 1 ? base2 : `${base2}-${n}`;
|
|
117747
|
+
const taken = await stat7(this.pathOf(id3)).then(
|
|
117748
|
+
() => true,
|
|
117749
|
+
() => false
|
|
117750
|
+
);
|
|
117751
|
+
if (!taken) return id3;
|
|
117752
|
+
}
|
|
117753
|
+
return `${base2}-${randomBytes7(4).toString("hex")}`;
|
|
117754
|
+
}
|
|
117755
|
+
};
|
|
117756
|
+
|
|
117250
117757
|
// packages/agent-host/src/sessions/window-rpc.ts
|
|
117251
117758
|
var WINDOW_METHODS = [
|
|
117252
117759
|
"prefs.get",
|
|
117253
117760
|
"prefs.set",
|
|
117254
117761
|
"themes.list",
|
|
117762
|
+
"themes.save",
|
|
117763
|
+
"themes.remove",
|
|
117255
117764
|
"agents.sleepPolicy",
|
|
117256
117765
|
"agents.setSleepPolicy",
|
|
117257
117766
|
"updates.status",
|
|
@@ -117311,6 +117820,7 @@ async function callWindow(rpc, method, params) {
|
|
|
117311
117820
|
// packages/agent-host/src/sessions/preferences-flow.ts
|
|
117312
117821
|
var HIDDEN2 = /[\p{Cc}\p{Cf}\u2028\u2029]/u;
|
|
117313
117822
|
var UPDATE_CARD = 'Turn on "Automatic updates"? From then on this computer checks for a newer Centralu, installs it by itself as soon as one is published, and the desktop window relaunches into it when nothing is busy, without asking again. You can turn it off in Settings, Updates.';
|
|
117823
|
+
var THEME_FILE_MAX_BYTES = 256 * 1024;
|
|
117314
117824
|
var FOLDED_GROUPS_MAX = 500;
|
|
117315
117825
|
var SLEEP_CHOICES = 'a whole number of minutes, or "never"';
|
|
117316
117826
|
function autoCompactText(c) {
|
|
@@ -117332,7 +117842,7 @@ var PreferencesFlow = class {
|
|
|
117332
117842
|
const rpc = this.rpc;
|
|
117333
117843
|
if (!rpc) return { ok: false, error: "This host has no window calls to make." };
|
|
117334
117844
|
try {
|
|
117335
|
-
if (req.action === "get") return await this.get(rpc);
|
|
117845
|
+
if (req.action === "get") return await this.get(rpc, req.themeFile);
|
|
117336
117846
|
if (req.action === "apply_agent_versions") return await this.applyVersions(rpc);
|
|
117337
117847
|
return await this.set(rpc, sessionId, req.changes, signal);
|
|
117338
117848
|
} catch (e) {
|
|
@@ -117340,7 +117850,7 @@ var PreferencesFlow = class {
|
|
|
117340
117850
|
}
|
|
117341
117851
|
}
|
|
117342
117852
|
/** Every setting Settings shows, one line each; a part the host cannot read says so, and the rest still answers */
|
|
117343
|
-
async get(rpc) {
|
|
117853
|
+
async get(rpc, themeFile) {
|
|
117344
117854
|
const [prefs, themes2, sleep4, updates2, versions] = await Promise.allSettled([
|
|
117345
117855
|
callWindow(rpc, "prefs.get", {}),
|
|
117346
117856
|
callWindow(rpc, "themes.list", {}),
|
|
@@ -117360,9 +117870,11 @@ var PreferencesFlow = class {
|
|
|
117360
117870
|
"Appearance",
|
|
117361
117871
|
`- Theme mode: ${p.themeMode}; dark theme ${named(p.themeDark)}; light theme ${named(p.themeLight)}`,
|
|
117362
117872
|
`- Themes to choose from (set_preferences theme): ${choices.map((c) => `${plainQuoted(c.name)} [${c.id}] (${c.base}${c.file ? ", a theme file" : ""})`).join(", ")}`,
|
|
117873
|
+
...themes2.status === "fulfilled" ? themeFileLines(themes2.value, themeFile) : [],
|
|
117363
117874
|
`- Text size: ${p.textSize} (${TEXT_SIZES.join(", ")}; 1 is the default)`,
|
|
117364
117875
|
`- Body font: ${p.bodyFont ? plainQuoted(p.bodyFont, 200) : "the app's own"}; code font: ${p.codeFont ? plainQuoted(p.codeFont, 200) : "the app's own"}`,
|
|
117365
117876
|
`- Line height: ${p.lineHeight} (compact, normal, relaxed)`,
|
|
117877
|
+
`- Models for the agents a session starts (set_preferences agentModels): ${agentModelsText(p.agentModels)}`,
|
|
117366
117878
|
"Notifications (set_preferences notifications)",
|
|
117367
117879
|
`- Approval needed: ${onOff(notify.approval)}; a session finishes out of sight: ${onOff(notify.done)}; even while the app is focused: ${onOff(notify.whenFocused)}; sound: ${onOff(notify.sound)}; turning the approval one off, or the sound while it notifies, asks the person`,
|
|
117368
117880
|
"Auto compact (set_preferences autoCompact)",
|
|
@@ -117383,12 +117895,19 @@ var PreferencesFlow = class {
|
|
|
117383
117895
|
async set(rpc, sessionId, changes, signal) {
|
|
117384
117896
|
const patch = {};
|
|
117385
117897
|
let theme = null;
|
|
117898
|
+
const done2 = [];
|
|
117899
|
+
const failed3 = [];
|
|
117900
|
+
const modelWarnings = [];
|
|
117901
|
+
const file2 = changes.themeFile;
|
|
117902
|
+
if (file2 !== void 0) {
|
|
117903
|
+
const bad = themeFileRefusal(file2);
|
|
117904
|
+
if (bad) return { ok: false, error: bad };
|
|
117905
|
+
}
|
|
117386
117906
|
if (changes.theme !== void 0) {
|
|
117387
117907
|
const found = await this.theme(rpc, changes.theme);
|
|
117388
|
-
|
|
117389
|
-
|
|
117390
|
-
|
|
117391
|
-
patch[found.base === "dark" ? "themeDark" : "themeLight"] = found.id;
|
|
117908
|
+
const wanted = changes.theme.trim().toLowerCase();
|
|
117909
|
+
const madeHere = file2 !== void 0 && (file2.name.trim().toLowerCase() === wanted || await this.idOfFile(rpc, file2) === wanted);
|
|
117910
|
+
if (typeof found === "string" && !madeHere) return { ok: false, error: found };
|
|
117392
117911
|
}
|
|
117393
117912
|
if (changes.textSize !== void 0) {
|
|
117394
117913
|
if (!Number.isFinite(changes.textSize)) return { ok: false, error: `textSize is a number: one of ${TEXT_SIZES.join(", ")}.` };
|
|
@@ -117400,6 +117919,12 @@ var PreferencesFlow = class {
|
|
|
117400
117919
|
if (HIDDEN2.test(value)) return { ok: false, error: `${key} has a control or formatting character in it; give a font family as plain text.` };
|
|
117401
117920
|
patch[key] = value.trim();
|
|
117402
117921
|
}
|
|
117922
|
+
if (changes.agentModels !== void 0) {
|
|
117923
|
+
const merged = await this.mergedAgentModels(rpc, changes.agentModels);
|
|
117924
|
+
if (typeof merged === "string") return { ok: false, error: merged };
|
|
117925
|
+
patch.agentModels = merged.models;
|
|
117926
|
+
modelWarnings.push(...merged.warnings);
|
|
117927
|
+
}
|
|
117403
117928
|
if (changes.lineHeight !== void 0) patch.lineHeight = changes.lineHeight;
|
|
117404
117929
|
if (changes.sendWithModifierEnter !== void 0) patch.sendWithModifierEnter = changes.sendWithModifierEnter;
|
|
117405
117930
|
if (changes.foldedGroups !== void 0) {
|
|
@@ -117408,13 +117933,35 @@ var PreferencesFlow = class {
|
|
|
117408
117933
|
patch.foldedGroups = keys;
|
|
117409
117934
|
}
|
|
117410
117935
|
const parsed = UiPreferencesPatch.safeParse(patch);
|
|
117411
|
-
if (!parsed.success) return { ok: false, error:
|
|
117936
|
+
if (!parsed.success) return { ok: false, error: `${issuesOf(parsed.error)}.` };
|
|
117412
117937
|
const sleep4 = changes.idleSleepMinutes;
|
|
117413
117938
|
if (sleep4 !== void 0 && sleep4 !== "never" && !(Number.isInteger(sleep4) && sleep4 > 0)) {
|
|
117414
117939
|
return { ok: false, error: `idleSleepMinutes is ${SLEEP_CHOICES}.` };
|
|
117415
117940
|
}
|
|
117416
|
-
|
|
117417
|
-
|
|
117941
|
+
if (file2 !== void 0) {
|
|
117942
|
+
try {
|
|
117943
|
+
const saved = await callWindow(rpc, "themes.save", { id: file2.id?.trim() || null, content: { name: file2.name.trim(), base: file2.base, tokens: file2.tokens } });
|
|
117944
|
+
done2.push(themeFileWritten(saved));
|
|
117945
|
+
} catch (e) {
|
|
117946
|
+
return { ok: false, error: errorMessage(e) };
|
|
117947
|
+
}
|
|
117948
|
+
}
|
|
117949
|
+
let toApply = parsed.data;
|
|
117950
|
+
if (changes.theme !== void 0) {
|
|
117951
|
+
const found = await this.theme(rpc, changes.theme);
|
|
117952
|
+
if (typeof found === "string") failed3.push(`Theme: not picked. ${found}`);
|
|
117953
|
+
else {
|
|
117954
|
+
theme = found;
|
|
117955
|
+
patch.themeMode = found.base;
|
|
117956
|
+
patch[found.base === "dark" ? "themeDark" : "themeLight"] = found.id;
|
|
117957
|
+
const again = UiPreferencesPatch.safeParse(patch);
|
|
117958
|
+
if (again.success) toApply = again.data;
|
|
117959
|
+
else {
|
|
117960
|
+
theme = null;
|
|
117961
|
+
failed3.push(`Theme: not picked. ${issuesOf(again.error)}.`);
|
|
117962
|
+
}
|
|
117963
|
+
}
|
|
117964
|
+
}
|
|
117418
117965
|
const attempt = async (what, run4) => {
|
|
117419
117966
|
try {
|
|
117420
117967
|
done2.push(await run4());
|
|
@@ -117424,8 +117971,8 @@ var PreferencesFlow = class {
|
|
|
117424
117971
|
};
|
|
117425
117972
|
if (Object.keys(patch).length) {
|
|
117426
117973
|
await attempt("Appearance", async () => {
|
|
117427
|
-
const now = await callWindow(rpc, "prefs.set", { patch:
|
|
117428
|
-
return appearanceText(now, patch, theme);
|
|
117974
|
+
const now = await callWindow(rpc, "prefs.set", { patch: toApply });
|
|
117975
|
+
return [appearanceText(now, patch, theme), ...modelWarnings].join(" ");
|
|
117429
117976
|
});
|
|
117430
117977
|
}
|
|
117431
117978
|
const switches = Object.fromEntries(Object.entries(changes.notifications ?? {}).filter(([, v]) => v !== void 0));
|
|
@@ -117461,7 +118008,7 @@ var PreferencesFlow = class {
|
|
|
117461
118008
|
if (Object.keys(compact).length) {
|
|
117462
118009
|
await attempt("Auto compact", async () => {
|
|
117463
118010
|
const checked2 = UiPreferencesPatch.safeParse({ autoCompact: compact });
|
|
117464
|
-
if (!checked2.success) throw new Error(
|
|
118011
|
+
if (!checked2.success) throw new Error(`${issuesOf(checked2.error)}.`);
|
|
117465
118012
|
const now = autoCompactOf(await callWindow(rpc, "prefs.set", { patch: checked2.data }));
|
|
117466
118013
|
return `Now: ${autoCompactText(now)}. It reaches a running session when its process next starts.`;
|
|
117467
118014
|
});
|
|
@@ -117513,7 +118060,21 @@ var PreferencesFlow = class {
|
|
|
117513
118060
|
return `Moving idle sessions to a newly installed agent CLI is ${onOff(now.autoApply)}.`;
|
|
117514
118061
|
});
|
|
117515
118062
|
}
|
|
117516
|
-
if (
|
|
118063
|
+
if (changes.deleteThemeFile !== void 0) {
|
|
118064
|
+
const id3 = changes.deleteThemeFile.trim();
|
|
118065
|
+
await attempt("Theme file", async () => {
|
|
118066
|
+
if (!ThemeId.safeParse(id3).success) throw new Error(`${plainQuoted(id3)} is not a theme file id; get_preferences lists them.`);
|
|
118067
|
+
const { removed, keptAt } = await callWindow(rpc, "themes.remove", { id: id3 });
|
|
118068
|
+
if (!removed) throw new Error(`No theme file [${id3}]; get_preferences lists them.`);
|
|
118069
|
+
const now = await callWindow(rpc, "prefs.get", {});
|
|
118070
|
+
const back = {};
|
|
118071
|
+
if (now.themeDark === id3) back.themeDark = "dark";
|
|
118072
|
+
if (now.themeLight === id3) back.themeLight = "light";
|
|
118073
|
+
if (Object.keys(back).length) await callWindow(rpc, "prefs.set", { patch: back });
|
|
118074
|
+
return `Removed the theme file [${id3}]; it is kept at ${plainQuoted(keptAt ?? "", 300)} (move it back to restore it).${Object.keys(back).length ? " A side that showed it is back on its preset." : ""}`;
|
|
118075
|
+
});
|
|
118076
|
+
}
|
|
118077
|
+
if (!done2.length) return { ok: false, error: failed3.join(" ") };
|
|
117517
118078
|
return { ok: true, text: [...done2, ...failed3.length ? [`Not changed: ${failed3.join(" ")}`] : []].join("\n") };
|
|
117518
118079
|
}
|
|
117519
118080
|
/**
|
|
@@ -117536,6 +118097,58 @@ var PreferencesFlow = class {
|
|
|
117536
118097
|
const kept = now.filter((key) => !drop.has(key) && !add2.includes(key));
|
|
117537
118098
|
return [...kept, ...add2].slice(-FOLDED_GROUPS_MAX);
|
|
117538
118099
|
}
|
|
118100
|
+
/**
|
|
118101
|
+
* The saved models with this call's changes merged in (the patch carries the whole map), or the sentence that says what is
|
|
118102
|
+
* wrong. The map steers every difficulty-based start_agent in every project, so the tool must be an agent CLI on this
|
|
118103
|
+
* computer. A model or effort is free text, as Settings' picker takes with "Other…", also when the tool lists its models
|
|
118104
|
+
* (a newer id may not be in the list yet); one the list lacks is saved as typed and said so in a warning. Clearing a
|
|
118105
|
+
* difficulty that was never set changes nothing and leaves no empty entry.
|
|
118106
|
+
*/
|
|
118107
|
+
async mergedAgentModels(rpc, change) {
|
|
118108
|
+
const now = (await callWindow(rpc, "prefs.get", {})).agentModels ?? {};
|
|
118109
|
+
const known = (await callWindow(rpc, "agents.detect", {})).map((s) => s.name);
|
|
118110
|
+
const out = { ...now };
|
|
118111
|
+
const warnings = [];
|
|
118112
|
+
for (const [tool, levels] of Object.entries(change)) {
|
|
118113
|
+
if (!known.includes(tool)) return `agentModels: ${plainQuoted(tool, 80)} is not an agent CLI on this computer; agent_status lists them (${known.join(", ") || "none"}).`;
|
|
118114
|
+
const own = { ...out[tool] };
|
|
118115
|
+
let catalog;
|
|
118116
|
+
for (const [level2, choice] of Object.entries(levels)) {
|
|
118117
|
+
if (choice === null) delete own[level2];
|
|
118118
|
+
else if (choice !== void 0) {
|
|
118119
|
+
const model = choice.model.trim();
|
|
118120
|
+
const effort = choice.effort?.trim();
|
|
118121
|
+
if (!model) return "agentModels: a model is empty; give its id as plain text.";
|
|
118122
|
+
if (HIDDEN2.test(model) || HIDDEN2.test(effort ?? "")) return "agentModels has a control or formatting character in a model or effort; give plain text.";
|
|
118123
|
+
if (catalog === void 0) {
|
|
118124
|
+
const r = await callWindow(rpc, "agents.models", { tool }).catch(() => null);
|
|
118125
|
+
catalog = r?.supported && r.models.length ? r.models : null;
|
|
118126
|
+
}
|
|
118127
|
+
if (catalog) {
|
|
118128
|
+
const m = catalog.find((x) => x.id === model);
|
|
118129
|
+
if (!m) warnings.push(`Note: ${plainQuoted(model, 200)} is not in ${tool}'s current list (agent_models); saved as typed.`);
|
|
118130
|
+
else if (effort && m.efforts.length && !m.efforts.includes(effort)) warnings.push(`Note: ${plainQuoted(effort, 80)} is not in the current efforts of ${plainQuoted(model, 200)} (${m.efforts.map((e) => plainQuoted(e, 40)).join(", ")}); saved as typed.`);
|
|
118131
|
+
}
|
|
118132
|
+
own[level2] = effort ? { model, effort } : { model };
|
|
118133
|
+
}
|
|
118134
|
+
}
|
|
118135
|
+
if (Object.keys(own).length) out[tool] = own;
|
|
118136
|
+
else delete out[tool];
|
|
118137
|
+
}
|
|
118138
|
+
return { models: out, warnings };
|
|
118139
|
+
}
|
|
118140
|
+
/** The id a theme file will be written under: the one named, else the one `themes.save` files its name under among the ids taken now */
|
|
118141
|
+
async idOfFile(rpc, file2) {
|
|
118142
|
+
const named = file2.id?.trim().toLowerCase();
|
|
118143
|
+
if (named) return named;
|
|
118144
|
+
const taken = new Set((await callWindow(rpc, "themes.list", {})).map((f) => f.id));
|
|
118145
|
+
const base2 = themeSlug(file2.name.trim());
|
|
118146
|
+
for (let n = 1; n < 1e3; n++) {
|
|
118147
|
+
const id3 = n === 1 ? base2 : `${base2}-${n}`;
|
|
118148
|
+
if (!taken.has(id3)) return id3;
|
|
118149
|
+
}
|
|
118150
|
+
return base2;
|
|
118151
|
+
}
|
|
117539
118152
|
/** The session header's action: idle sessions on an older agent CLI restart on the installed one, and the busy ones are named */
|
|
117540
118153
|
async applyVersions(rpc) {
|
|
117541
118154
|
const r = await callWindow(rpc, "agents.applyVersions", { keepQuiet: true });
|
|
@@ -117570,6 +118183,44 @@ function automatic(s) {
|
|
|
117570
118183
|
if (!s.auto && !s.autoApply) return "off";
|
|
117571
118184
|
return s.auto ? "off (checking is on, installing is off)" : "off (installing is on, checking is off)";
|
|
117572
118185
|
}
|
|
118186
|
+
function themeFileRefusal(file2) {
|
|
118187
|
+
if (file2.id !== void 0 && file2.id.trim() && !ThemeId.safeParse(file2.id.trim()).success) {
|
|
118188
|
+
return `themeFile.id ${plainQuoted(file2.id)} is not a theme id: lowercase letters, digits and dashes, up to 60. Leave it out for a new file.`;
|
|
118189
|
+
}
|
|
118190
|
+
const content = ThemeFileContent.safeParse({ name: file2.name.trim(), base: file2.base, tokens: file2.tokens });
|
|
118191
|
+
if (!content.success) return `themeFile: ${issuesOf(content.error)}.`;
|
|
118192
|
+
if (HIDDEN2.test(file2.name) || Object.entries(file2.tokens).some(([k2, v]) => HIDDEN2.test(k2) || HIDDEN2.test(v))) {
|
|
118193
|
+
return "themeFile has a control or formatting character in its name or tokens; give plain text.";
|
|
118194
|
+
}
|
|
118195
|
+
if (Buffer.byteLength(formatThemeFile(content.data)) > THEME_FILE_MAX_BYTES) return "themeFile is too large to be a theme file; leave out the tokens you do not change.";
|
|
118196
|
+
return null;
|
|
118197
|
+
}
|
|
118198
|
+
function themeFileWritten(f) {
|
|
118199
|
+
const problems = f.problems.length ? ` Problems the reader found, skipped when applied: ${f.problems.map((x) => plainQuoted(x, 200)).join(" ")}` : "";
|
|
118200
|
+
return `Wrote the theme file ${plainQuoted(f.name)} [${f.id}] (${f.base}, ${Object.keys(f.tokens).length} tokens).${problems}`;
|
|
118201
|
+
}
|
|
118202
|
+
function themeFileLines(files2, one2) {
|
|
118203
|
+
const state = (f) => f.broken ? ", unreadable" : f.problems.length ? `, ${f.problems.length} problems` : "";
|
|
118204
|
+
const lines = [
|
|
118205
|
+
files2.length ? `- Theme files (set_preferences themeFile writes one, deleteThemeFile removes one): ${files2.map((f) => `${plainQuoted(f.name)} [${f.id}] (${f.base}, ${Object.keys(f.tokens).length} tokens${state(f)})`).join(", ")}` : "- Theme files: none (set_preferences themeFile writes one)"
|
|
118206
|
+
];
|
|
118207
|
+
if (one2) {
|
|
118208
|
+
const f = files2.find((x) => x.id === one2.trim());
|
|
118209
|
+
if (!f) lines.push(`- Theme file ${plainQuoted(one2)}: no such file`);
|
|
118210
|
+
else {
|
|
118211
|
+
lines.push(`- Theme file ${plainQuoted(f.name)} [${f.id}], ${f.base}${f.problems.length ? `; problems: ${f.problems.map((x) => plainQuoted(x, 200)).join(" ")}` : ""}`);
|
|
118212
|
+
lines.push(...Object.entries(f.tokens).map(([k2, v]) => ` ${k2}: ${plainQuoted(v, 400)}`));
|
|
118213
|
+
lines.push(` Token keys to set: ${THEME_TOKENS.map((t) => t.key).join(", ")}`);
|
|
118214
|
+
}
|
|
118215
|
+
}
|
|
118216
|
+
return lines;
|
|
118217
|
+
}
|
|
118218
|
+
function agentModelsText(models) {
|
|
118219
|
+
const parts = Object.entries(models ?? {}).flatMap(
|
|
118220
|
+
([tool, levels]) => Object.entries(levels).flatMap(([level2, c]) => c ? [`${plainQuoted(tool, 80)} ${level2}: ${plainQuoted(c.model, 200)}${c.effort ? ` (${plainQuoted(c.effort, 80)})` : ""}`] : [])
|
|
118221
|
+
);
|
|
118222
|
+
return parts.length ? parts.join("; ") : "none chosen (each project keeps its last choice)";
|
|
118223
|
+
}
|
|
117573
118224
|
function themeChoices(files2) {
|
|
117574
118225
|
return [
|
|
117575
118226
|
...THEME_PRESETS.map((p) => ({ id: p.id, name: p.name, base: p.base, file: false })),
|
|
@@ -117587,6 +118238,7 @@ function appearanceText(now, patch, theme) {
|
|
|
117587
118238
|
if ("codeFont" in patch) parts.push(`code font ${now.codeFont ? plainQuoted(now.codeFont, 200) : "the app's own"}`);
|
|
117588
118239
|
if ("lineHeight" in patch) parts.push(`line height ${now.lineHeight}`);
|
|
117589
118240
|
if ("sendWithModifierEnter" in patch) parts.push(`send with ${now.sendWithModifierEnter ? "Ctrl+Enter (Enter breaks the line)" : "Enter"}`);
|
|
118241
|
+
if ("agentModels" in patch) parts.push(`models for started agents: ${agentModelsText(now.agentModels)}`);
|
|
117590
118242
|
if ("foldedGroups" in patch) parts.push(`${now.foldedGroups.length} sidebar ${now.foldedGroups.length === 1 ? "group" : "groups"} folded`);
|
|
117591
118243
|
return `Now: ${parts.join("; ")}.`;
|
|
117592
118244
|
}
|
|
@@ -118552,7 +119204,7 @@ var HousekeepingFlow = class {
|
|
|
118552
119204
|
import { randomUUID as randomUUID18 } from "node:crypto";
|
|
118553
119205
|
import { existsSync as existsSync15, realpathSync as realpathSync5, rmSync as rmSync4, statSync as statSync13 } from "node:fs";
|
|
118554
119206
|
import { homedir as homedir7 } from "node:os";
|
|
118555
|
-
import { basename as
|
|
119207
|
+
import { basename as basename9, isAbsolute as isAbsolute5, join as join32, resolve as resolve11 } from "node:path";
|
|
118556
119208
|
|
|
118557
119209
|
// packages/agent-host/src/dev-services/project-folder.ts
|
|
118558
119210
|
import { lstatSync as lstatSync12, mkdirSync as mkdirSync16, statSync as statSync12 } from "node:fs";
|
|
@@ -118565,18 +119217,18 @@ function projectsHome(platform = process.platform, home = homedir6(), env = proc
|
|
|
118565
119217
|
if (platform === "win32") return path.win32.join(env.USERPROFILE || home, PROJECTS_DIR);
|
|
118566
119218
|
return path.posix.join(home, PROJECTS_DIR);
|
|
118567
119219
|
}
|
|
118568
|
-
function
|
|
119220
|
+
function refuse5(message) {
|
|
118569
119221
|
throw rpcError(message);
|
|
118570
119222
|
}
|
|
118571
119223
|
function projectFolderTarget(spec, home, platform = process.platform) {
|
|
118572
119224
|
const p = pathFor(platform);
|
|
118573
119225
|
const name = spec.name.trim();
|
|
118574
119226
|
const nameError = folderNameError(name, platform === "win32");
|
|
118575
|
-
if (nameError)
|
|
119227
|
+
if (nameError) refuse5(nameError);
|
|
118576
119228
|
const given = spec.parent?.trim();
|
|
118577
|
-
if (given && !p.isAbsolute(given))
|
|
119229
|
+
if (given && !p.isAbsolute(given)) refuse5(`The location has to be a full path (${platform === "win32" ? "C:\\Users\\me\\code" : "/home/me/code"}).`);
|
|
118578
119230
|
const base2 = given || home;
|
|
118579
|
-
if (!base2)
|
|
119231
|
+
if (!base2) refuse5("There is no default location here \u2014 give a parent folder.");
|
|
118580
119232
|
const parent = p.resolve(base2);
|
|
118581
119233
|
const isDefault = !given;
|
|
118582
119234
|
if (!isDefault) {
|
|
@@ -118584,9 +119236,9 @@ function projectFolderTarget(spec, home, platform = process.platform) {
|
|
|
118584
119236
|
try {
|
|
118585
119237
|
dir = statSync12(parent).isDirectory();
|
|
118586
119238
|
} catch {
|
|
118587
|
-
|
|
119239
|
+
refuse5(`There is no folder at ${parent}.`);
|
|
118588
119240
|
}
|
|
118589
|
-
if (!dir)
|
|
119241
|
+
if (!dir) refuse5(`${parent} is a file, not a folder.`);
|
|
118590
119242
|
}
|
|
118591
119243
|
const target = p.join(parent, name);
|
|
118592
119244
|
let taken = false;
|
|
@@ -118595,7 +119247,7 @@ function projectFolderTarget(spec, home, platform = process.platform) {
|
|
|
118595
119247
|
taken = true;
|
|
118596
119248
|
} catch {
|
|
118597
119249
|
}
|
|
118598
|
-
if (taken)
|
|
119250
|
+
if (taken) refuse5(`Something named "${name}" is already in ${parent} \u2014 choose another name.`);
|
|
118599
119251
|
return { name, parent, path: target, isDefault };
|
|
118600
119252
|
}
|
|
118601
119253
|
function makeProjectFolder(target) {
|
|
@@ -118604,9 +119256,9 @@ function makeProjectFolder(target) {
|
|
|
118604
119256
|
mkdirSync16(target.path);
|
|
118605
119257
|
} catch (e) {
|
|
118606
119258
|
const code2 = execFailure(e).code;
|
|
118607
|
-
if (code2 === "EEXIST")
|
|
118608
|
-
if (code2 === "ENOENT")
|
|
118609
|
-
|
|
119259
|
+
if (code2 === "EEXIST") refuse5(`Something named "${target.name}" is already in ${target.parent} \u2014 choose another name.`);
|
|
119260
|
+
if (code2 === "ENOENT") refuse5(`There is no folder at ${target.parent}.`);
|
|
119261
|
+
refuse5(`Could not make ${target.path}: ${errorMessage(e)}`);
|
|
118610
119262
|
}
|
|
118611
119263
|
}
|
|
118612
119264
|
|
|
@@ -118629,7 +119281,7 @@ var ProjectCreation = class {
|
|
|
118629
119281
|
const existing = this.projectAt(spelled2);
|
|
118630
119282
|
const at2 = existing?.path ?? spelled2;
|
|
118631
119283
|
const id3 = existing?.id ?? randomUUID18();
|
|
118632
|
-
this.host.store.projects.addProject({ id: id3, path: at2, name:
|
|
119284
|
+
this.host.store.projects.addProject({ id: id3, path: at2, name: basename9(at2), ...opts.trusted ? { trusted: true } : {} });
|
|
118633
119285
|
return this.host.projectInfo(id3, at2);
|
|
118634
119286
|
}
|
|
118635
119287
|
/**
|
|
@@ -118651,7 +119303,7 @@ var ProjectCreation = class {
|
|
|
118651
119303
|
*/
|
|
118652
119304
|
async inspectFolder(given) {
|
|
118653
119305
|
const typed = given.trim();
|
|
118654
|
-
const expanded = /^~(?=$|[\\/])/.test(typed) ?
|
|
119306
|
+
const expanded = /^~(?=$|[\\/])/.test(typed) ? join32(homedir7(), typed.slice(1)) : typed;
|
|
118655
119307
|
if (!expanded || !isAbsolute5(expanded)) throw rpcError(`Give the folder's full path: ${JSON.stringify(given)} is not one`);
|
|
118656
119308
|
const path2 = resolve11(expanded).replace(/^[a-z]:/, (drive) => drive.toUpperCase());
|
|
118657
119309
|
let found;
|
|
@@ -118664,7 +119316,7 @@ var ProjectCreation = class {
|
|
|
118664
119316
|
const git2 = folder ? await isRepo(path2).catch(() => false) : false;
|
|
118665
119317
|
const origin = git2 ? await gitOriginUrl(path2, { trusted: false }) : null;
|
|
118666
119318
|
const kept = folder ? this.projectAt(path2) : void 0;
|
|
118667
|
-
const project2 = kept ? { id: kept.id, name: this.host.store.projects.listProjects().find((p) => p.id === kept.id)?.name ??
|
|
119319
|
+
const project2 = kept ? { id: kept.id, name: this.host.store.projects.listProjects().find((p) => p.id === kept.id)?.name ?? basename9(kept.path) } : null;
|
|
118668
119320
|
return { path: path2, exists: found !== null, folder, git: git2, origin, project: project2 };
|
|
118669
119321
|
}
|
|
118670
119322
|
/**
|
|
@@ -120459,27 +121111,27 @@ var SessionTools = class {
|
|
|
120459
121111
|
|
|
120460
121112
|
// packages/agent-host/src/dev-services/handoff-notes.ts
|
|
120461
121113
|
import { mkdirSync as mkdirSync17, writeFileSync as writeFileSync8 } from "node:fs";
|
|
120462
|
-
import { mkdir as
|
|
120463
|
-
import { join as
|
|
120464
|
-
var root = () =>
|
|
121114
|
+
import { mkdir as mkdir5, readdir as readdir8, writeFile as writeFile4 } from "node:fs/promises";
|
|
121115
|
+
import { join as join33 } from "node:path";
|
|
121116
|
+
var root = () => join33(dataRoot(), "handoff");
|
|
120465
121117
|
function handoffNoteDir(projectId) {
|
|
120466
121118
|
if (!isProjectId(projectId)) {
|
|
120467
121119
|
throw rpcError(`Not a project id: ${projectId}`);
|
|
120468
121120
|
}
|
|
120469
|
-
return
|
|
121121
|
+
return join33(root(), projectId);
|
|
120470
121122
|
}
|
|
120471
121123
|
function handoffNotePath(projectId, sessionId) {
|
|
120472
121124
|
if (!isSessionId(sessionId)) {
|
|
120473
121125
|
throw rpcError(`Not a session id: ${sessionId}`);
|
|
120474
121126
|
}
|
|
120475
|
-
return
|
|
121127
|
+
return join33(handoffNoteDir(projectId), `${sessionId}.md`);
|
|
120476
121128
|
}
|
|
120477
121129
|
async function handoffNoteBytes(projectId, sessionId) {
|
|
120478
121130
|
return dataBytes(handoffNotePath(projectId, sessionId));
|
|
120479
121131
|
}
|
|
120480
121132
|
async function writeHandoffNote(projectId, sessionId, text3) {
|
|
120481
121133
|
const path2 = handoffNotePath(projectId, sessionId);
|
|
120482
|
-
await
|
|
121134
|
+
await mkdir5(handoffNoteDir(projectId), { recursive: true });
|
|
120483
121135
|
await removeData(path2);
|
|
120484
121136
|
await writeFile4(path2, text3, "utf8");
|
|
120485
121137
|
return path2;
|
|
@@ -120494,7 +121146,7 @@ function writeHandoffNoteSync(projectId, sessionId, text3) {
|
|
|
120494
121146
|
async function sweepHandoffNotes(owned, projectId) {
|
|
120495
121147
|
let dirs;
|
|
120496
121148
|
try {
|
|
120497
|
-
dirs = projectId ? [projectId] : (await
|
|
121149
|
+
dirs = projectId ? [projectId] : (await readdir8(root(), { withFileTypes: true })).filter((d2) => d2.isDirectory()).map((d2) => d2.name);
|
|
120498
121150
|
} catch {
|
|
120499
121151
|
return;
|
|
120500
121152
|
}
|
|
@@ -120505,12 +121157,12 @@ async function sweepHandoffNotes(owned, projectId) {
|
|
|
120505
121157
|
} catch {
|
|
120506
121158
|
continue;
|
|
120507
121159
|
}
|
|
120508
|
-
const entries = await
|
|
121160
|
+
const entries = await readdir8(dir, { withFileTypes: true }).catch(() => []);
|
|
120509
121161
|
for (const e of entries) {
|
|
120510
121162
|
if (!e.isFile() || !e.name.endsWith(".md")) continue;
|
|
120511
121163
|
const owner = e.name.slice(0, -".md".length);
|
|
120512
121164
|
if (owned(owner)) continue;
|
|
120513
|
-
await removeData(
|
|
121165
|
+
await removeData(join33(dir, e.name)).catch(() => {
|
|
120514
121166
|
});
|
|
120515
121167
|
}
|
|
120516
121168
|
}
|
|
@@ -120808,20 +121460,12 @@ var HandoffNotes = class {
|
|
|
120808
121460
|
* first, as its children did; the nearest generation with a live one wins.
|
|
120809
121461
|
*/
|
|
120810
121462
|
handoffHeir(sessionId) {
|
|
120811
|
-
const seen = /* @__PURE__ */ new Set([sessionId]);
|
|
120812
|
-
let generation = [sessionId];
|
|
120813
121463
|
try {
|
|
120814
|
-
|
|
120815
|
-
const heirs = generation.flatMap((at2) => this.host.store.handoffs.handoffHeirs(at2)).filter((h) => !seen.has(h.sessionId));
|
|
120816
|
-
for (const h of heirs) seen.add(h.sessionId);
|
|
120817
|
-
const live2 = heirs.find((h) => !h.trashed && this.host.meta.has(h.sessionId) && !this.host.trashing.has(h.sessionId));
|
|
120818
|
-
if (live2) return live2.sessionId;
|
|
120819
|
-
generation = heirs.map((h) => h.sessionId);
|
|
120820
|
-
}
|
|
121464
|
+
return liveHeirOf(this.host.store, sessionId, (id3) => this.host.meta.has(id3) && !this.host.trashing.has(id3));
|
|
120821
121465
|
} catch (e) {
|
|
120822
121466
|
console.error(`[agent-host] no handoff heir read for ${sessionId.slice(0, 8)}: ${e.message}`);
|
|
121467
|
+
return null;
|
|
120823
121468
|
}
|
|
120824
|
-
return null;
|
|
120825
121469
|
}
|
|
120826
121470
|
/**
|
|
120827
121471
|
* The note folder a successor reads from (#142) — only a project session that inherited a handoff
|
|
@@ -120960,11 +121604,11 @@ ${note}` : note;
|
|
|
120960
121604
|
};
|
|
120961
121605
|
|
|
120962
121606
|
// packages/agent-host/src/mail/ids.ts
|
|
120963
|
-
import { randomBytes as
|
|
121607
|
+
import { randomBytes as randomBytes8 } from "node:crypto";
|
|
120964
121608
|
var CROCKFORD = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
120965
121609
|
var ID_RE = /^([a-z0-9][a-z0-9-]*)\.(t|m)_([0-9A-HJKMNP-TV-Z]{26})$/;
|
|
120966
121610
|
var UlidMaker = class {
|
|
120967
|
-
constructor(random =
|
|
121611
|
+
constructor(random = randomBytes8) {
|
|
120968
121612
|
this.random = random;
|
|
120969
121613
|
}
|
|
120970
121614
|
random;
|
|
@@ -121972,7 +122616,7 @@ function linksOf(raw) {
|
|
|
121972
122616
|
function stateOf(word) {
|
|
121973
122617
|
return THREAD_STATES.find((x) => x === word) ?? "unknown";
|
|
121974
122618
|
}
|
|
121975
|
-
function
|
|
122619
|
+
function outcomeOf2(word) {
|
|
121976
122620
|
if (word === null) return null;
|
|
121977
122621
|
return THREAD_OUTCOMES.find((x) => x === word) ?? "unknown";
|
|
121978
122622
|
}
|
|
@@ -121986,7 +122630,7 @@ var toThread = (r) => ({
|
|
|
121986
122630
|
parentId: r.parent_id,
|
|
121987
122631
|
follows: readJsonList(r.follows, external_exports.string()),
|
|
121988
122632
|
state: stateOf(r.state),
|
|
121989
|
-
outcome:
|
|
122633
|
+
outcome: outcomeOf2(r.outcome),
|
|
121990
122634
|
owner: r.owner,
|
|
121991
122635
|
assignee: r.assignee,
|
|
121992
122636
|
next: r.next_step,
|
|
@@ -123465,165 +124109,6 @@ function attachCommitSessions(commits, records, nameOf3) {
|
|
|
123465
124109
|
});
|
|
123466
124110
|
}
|
|
123467
124111
|
|
|
123468
|
-
// packages/agent-host/src/mail/rules.ts
|
|
123469
|
-
var REFUSALS = [
|
|
123470
|
-
"bad_address",
|
|
123471
|
-
"not_local",
|
|
123472
|
-
"not_found",
|
|
123473
|
-
"not_participant",
|
|
123474
|
-
"thread_done",
|
|
123475
|
-
"kind_not_allowed",
|
|
123476
|
-
"field_not_allowed",
|
|
123477
|
-
"not_allowed",
|
|
123478
|
-
"empty",
|
|
123479
|
-
"too_long",
|
|
123480
|
-
"too_many_blockers",
|
|
123481
|
-
"blockers_stand",
|
|
123482
|
-
"no_blocker",
|
|
123483
|
-
"bad_follows",
|
|
123484
|
-
"no_mailbox",
|
|
123485
|
-
"rate_limited",
|
|
123486
|
-
"bad_value",
|
|
123487
|
-
"policy",
|
|
123488
|
-
// mail without threads (docs/plans/mailbox.md §15, S9)
|
|
123489
|
-
"gone",
|
|
123490
|
-
"ambiguous",
|
|
123491
|
-
"unknown",
|
|
123492
|
-
"elsewhere",
|
|
123493
|
-
"unknown_message",
|
|
123494
|
-
"own_message",
|
|
123495
|
-
"recipients",
|
|
123496
|
-
"not_yours",
|
|
123497
|
-
"pair_rate",
|
|
123498
|
-
"repeat",
|
|
123499
|
-
"ask_rate",
|
|
123500
|
-
"orchestrator_asks",
|
|
123501
|
-
"removed",
|
|
123502
|
-
"untrusted_project"
|
|
123503
|
-
];
|
|
123504
|
-
function refuse5(reason, message, more = {}) {
|
|
123505
|
-
return rpcError(message, "refused", { retryable: false, data: { reason, ...more } });
|
|
123506
|
-
}
|
|
123507
|
-
function refusalReason(err) {
|
|
123508
|
-
const reason = asObj(asObj(err).data).reason;
|
|
123509
|
-
return REFUSALS.find((r) => r === reason);
|
|
123510
|
-
}
|
|
123511
|
-
function bodyLimit(sender) {
|
|
123512
|
-
return sender === "app" ? 4e3 : 16e3;
|
|
123513
|
-
}
|
|
123514
|
-
function postsPerMinute(sender) {
|
|
123515
|
-
return sender === "session" ? 30 : sender === "app" ? 10 : Infinity;
|
|
123516
|
-
}
|
|
123517
|
-
var ASKS_PER_HOUR = 5;
|
|
123518
|
-
var REPEAT_WINDOW_MS = 10 * 6e4;
|
|
123519
|
-
function wakes(sender, m) {
|
|
123520
|
-
if (m.kind === "event") return false;
|
|
123521
|
-
if (sender === "centralu" && m.wake === "no") return false;
|
|
123522
|
-
if (sender !== "app") return true;
|
|
123523
|
-
if (m.wake === "no") return false;
|
|
123524
|
-
if (m.kind === "notice") return m.needsOwner || m.wake === "yes";
|
|
123525
|
-
return true;
|
|
123526
|
-
}
|
|
123527
|
-
function needsReceiver(m) {
|
|
123528
|
-
return m.sender === PERSON || m.kind === "task" || m.kind === "report" || m.needsReply || m.facts.needsOwner === true;
|
|
123529
|
-
}
|
|
123530
|
-
var HOLD_QUESTION_MS = 5 * 6e4;
|
|
123531
|
-
var HOLD_OTHER_MS = 15 * 6e4;
|
|
123532
|
-
var DEBOUNCE_MS = 2e3;
|
|
123533
|
-
function dueAt(now, urgent, fromPerson) {
|
|
123534
|
-
return urgent || fromPerson ? now : now + DEBOUNCE_MS;
|
|
123535
|
-
}
|
|
123536
|
-
var MINUTE_MS = 6e4;
|
|
123537
|
-
var HOUR_MS3 = 60 * MINUTE_MS;
|
|
123538
|
-
var READ_ITEMS = 10;
|
|
123539
|
-
var READ_CHARS = 2e4;
|
|
123540
|
-
var READ_CHAIN = 9;
|
|
123541
|
-
var PAIR_NEW_PER_HOUR = 5;
|
|
123542
|
-
var MAX_RECIPIENTS = 10;
|
|
123543
|
-
var HOLD_UNTRUSTED_OUT = true;
|
|
123544
|
-
var MAX_DEPTH = 4;
|
|
123545
|
-
var HOST_MAIL_TURNS_PER_HOUR = 120;
|
|
123546
|
-
var HOST_SHARE = 10;
|
|
123547
|
-
var PAIR_TURNS_PER_HOUR = 20;
|
|
123548
|
-
var REMINDER = {
|
|
123549
|
-
/** After a session came to rest, before the first reminder */
|
|
123550
|
-
graceMs: 2 * 6e4,
|
|
123551
|
-
/** After a turn the person started */
|
|
123552
|
-
personQuietMs: 10 * 6e4,
|
|
123553
|
-
/** After the first reminder's turn ended; also the wait after the second before the row is released */
|
|
123554
|
-
againMs: 20 * 6e4,
|
|
123555
|
-
perRow: 2,
|
|
123556
|
-
/** Between two follow-up wakes of one session (reminders and notices to a sender together) */
|
|
123557
|
-
gapMs: 20 * 6e4,
|
|
123558
|
-
perDay: 6,
|
|
123559
|
-
/** A session asleep longer than this since its last turn is not woken for a follow-up (its prompt cache has expired) */
|
|
123560
|
-
warmMs: 50 * 6e4,
|
|
123561
|
-
/** After delivery a needsReply row is released: the one 2-hour constant, which also bounds a cover (D23, D25) */
|
|
123562
|
-
unansweredAfterMs: 2 * HOUR_MS3
|
|
123563
|
-
};
|
|
123564
|
-
var REFUSAL_TEXT = {
|
|
123565
|
-
gone: (address) => `${address} is gone: it is in the session archive or deleted.`,
|
|
123566
|
-
no_mailbox: () => "that session cannot read mail: only an ordinary session in a project, or the orchestrator, has the mail tool",
|
|
123567
|
-
elsewhere: (address) => `${address} is on another computer, and mail does not reach other computers yet; use agent_session there.`,
|
|
123568
|
-
unknown_message: (id3) => `there is no message ${id3} you can read; check the id with mail(action="inbox").`,
|
|
123569
|
-
own_message: (id3) => `${id3} is your own message; say who it is for with to="\u2026".`,
|
|
123570
|
-
recipients: () => `at most ${MAX_RECIPIENTS} recipients.`,
|
|
123571
|
-
not_allowed: () => "that is not a party that takes mail",
|
|
123572
|
-
withheld: (id3) => `${id3} is a request: its words are shown in the turn it starts, and it is marked done there.`,
|
|
123573
|
-
not_yours: (id3) => `you were not sent ${id3} and did not send it.`,
|
|
123574
|
-
pair_rate: (name) => `you started ${PAIR_NEW_PER_HOUR} conversations with ${name} in the last hour; answer one of its messages instead, or wait.`,
|
|
123575
|
-
repeat: (name) => `you sent the same text to ${name} less than ${REPEAT_WINDOW_MS / 6e4} minutes ago.`,
|
|
123576
|
-
ask_rate: () => `your project's sessions asked the orchestrator ${ASKS_PER_HOUR} times in the last hour; wait, or write to the person.`,
|
|
123577
|
-
orchestrator_asks: () => "the orchestrator runs on a tool that cannot ask the person for each step of a request yet, so a session cannot ask it: write to the person instead",
|
|
123578
|
-
untrusted_project: (project2, target) => `your project ${project2} is not trusted, so work you give outside it (to ${target}) waits for the person to trust ${project2}. Nothing was started. Centralu shows the person how, and tells you when ${project2} is trusted; give it again then.`
|
|
123579
|
-
};
|
|
123580
|
-
var ADOPTED_TASK = "Work given before this version of Centralu wrote it down as mail. The conversation of the session that does it shows what was asked.";
|
|
123581
|
-
var TASK_CUT = "\n[Centralu: cut here. The full task is in the session's conversation (read_session).]";
|
|
123582
|
-
function cutTask(text3, limit) {
|
|
123583
|
-
const task = text3.trim();
|
|
123584
|
-
return task.length > limit ? task.slice(0, limit - TASK_CUT.length) + TASK_CUT : task;
|
|
123585
|
-
}
|
|
123586
|
-
function firstLineOf(text3, max = Infinity) {
|
|
123587
|
-
return oneLineText(text3.split(/\r\n|[\n\r]/)[0] ?? "", max);
|
|
123588
|
-
}
|
|
123589
|
-
function wakesMessage(m) {
|
|
123590
|
-
return m.facts.quiet !== true;
|
|
123591
|
-
}
|
|
123592
|
-
function quietAnswer(m, target, receiver) {
|
|
123593
|
-
return m.kind === "message" && !m.needsReply && !m.urgent && !receiver.request && target.kind === "message" && !target.needsReply && parseAddress(m.sender)?.kind === "session" && m.sender !== receiver.parent && !receiver.owesTask;
|
|
123594
|
-
}
|
|
123595
|
-
function depthOf2(cause2, about, recipients) {
|
|
123596
|
-
if (!cause2 || cause2.sender === PERSON) return 0;
|
|
123597
|
-
const followUp = cause2.kind === "notice" && (cause2.facts.reminder !== void 0 || cause2.facts.unanswered !== void 0);
|
|
123598
|
-
if (cause2.kind !== "message" && !followUp) return 0;
|
|
123599
|
-
const base2 = followUp ? Math.max(0, ...about.map((m) => m.depth)) : cause2.depth;
|
|
123600
|
-
const back = !followUp ? [cause2.sender] : cause2.facts.reminder !== void 0 ? about.map((m) => m.sender) : (cause2.facts.unanswered ?? []).map((u) => u.recipient);
|
|
123601
|
-
return recipients.every((r) => back.includes(r)) ? base2 : base2 + 1;
|
|
123602
|
-
}
|
|
123603
|
-
function pickWork(candidates, now) {
|
|
123604
|
-
const classOf = /* @__PURE__ */ new Map();
|
|
123605
|
-
for (const c of candidates) {
|
|
123606
|
-
const hold = c.needsReply ? HOLD_QUESTION_MS : HOLD_OTHER_MS;
|
|
123607
|
-
const k2 = c.decides || now - c.dueAt > hold ? 1 : c.needsReply ? 2 : 3;
|
|
123608
|
-
const was = classOf.get(c.key);
|
|
123609
|
-
if (was === void 0 || k2 < was) classOf.set(c.key, k2);
|
|
123610
|
-
}
|
|
123611
|
-
let best = null;
|
|
123612
|
-
let bestClass = 4;
|
|
123613
|
-
for (const [key, k2] of classOf) {
|
|
123614
|
-
if (k2 < bestClass) {
|
|
123615
|
-
best = key;
|
|
123616
|
-
bestClass = k2;
|
|
123617
|
-
}
|
|
123618
|
-
}
|
|
123619
|
-
return best;
|
|
123620
|
-
}
|
|
123621
|
-
function flowsDownTo(f) {
|
|
123622
|
-
if (f.fromPerson) return true;
|
|
123623
|
-
if (!f.fromSession || f.toOrchestrator) return false;
|
|
123624
|
-
return f.kind === "task" || f.kind === "message" && f.fromParent;
|
|
123625
|
-
}
|
|
123626
|
-
|
|
123627
124112
|
// packages/agent-host/src/mail/delivery.ts
|
|
123628
124113
|
var MailNotAtRest = class extends Error {
|
|
123629
124114
|
constructor() {
|
|
@@ -124012,7 +124497,7 @@ var MailDelivery = class {
|
|
|
124012
124497
|
*/
|
|
124013
124498
|
overHostCap(recipient, sessionId, drained, now) {
|
|
124014
124499
|
if (!this.turns) return 0;
|
|
124015
|
-
const since = now -
|
|
124500
|
+
const since = now - HOUR_MS;
|
|
124016
124501
|
if (this.turns.mailStartedSince(since) < this.o.hostCap || this.turns.mailStartedSince(since, sessionId) < this.o.hostShare) return 0;
|
|
124017
124502
|
if (drained.messageIds.some((id3) => {
|
|
124018
124503
|
const m = this.records.message(id3);
|
|
@@ -124021,9 +124506,9 @@ var MailDelivery = class {
|
|
|
124021
124506
|
this.service.notice(PERSON, {
|
|
124022
124507
|
body: `Mail between sessions started ${this.o.hostCap} turns in the last hour; more wait until the hour has room.`,
|
|
124023
124508
|
facts: { hostCap: true },
|
|
124024
|
-
dedupeKey: `hostcap:${PERSON}:${Math.floor(now /
|
|
124509
|
+
dedupeKey: `hostcap:${PERSON}:${Math.floor(now / HOUR_MS) * HOUR_MS}`
|
|
124025
124510
|
});
|
|
124026
|
-
const until = (this.records.oldestMailStartedSince(since, sessionId) ?? now) +
|
|
124511
|
+
const until = (this.records.oldestMailStartedSince(since, sessionId) ?? now) + HOUR_MS;
|
|
124027
124512
|
if (this.capUntil.get(recipient) !== until) console.error(`[mail] ${recipient}: the host has had ${this.o.hostCap} mail-started turns this hour; its mail waits`);
|
|
124028
124513
|
this.capUntil.set(recipient, until);
|
|
124029
124514
|
return Math.max(1e3, until - now);
|
|
@@ -125253,16 +125738,16 @@ var CardIds = class {
|
|
|
125253
125738
|
|
|
125254
125739
|
// packages/agent-host/src/adapters/frames/recorder.ts
|
|
125255
125740
|
import { appendFileSync as appendFileSync2, existsSync as existsSync16, mkdirSync as mkdirSync18 } from "node:fs";
|
|
125256
|
-
import { join as
|
|
125741
|
+
import { join as join34 } from "node:path";
|
|
125257
125742
|
var processes = /* @__PURE__ */ new Map();
|
|
125258
125743
|
function frameRecorder(sessionId, env = process.env) {
|
|
125259
125744
|
const dir = env.CC_RECORD_FRAMES;
|
|
125260
125745
|
if (!dir) return void 0;
|
|
125261
125746
|
mkdirSync18(dir, { recursive: true });
|
|
125262
125747
|
let n = (processes.get(sessionId) ?? 0) + 1;
|
|
125263
|
-
while (existsSync16(
|
|
125748
|
+
while (existsSync16(join34(dir, `${sessionId}.${n}.jsonl`))) n++;
|
|
125264
125749
|
processes.set(sessionId, n);
|
|
125265
|
-
const file2 =
|
|
125750
|
+
const file2 = join34(dir, `${sessionId}.${n}.jsonl`);
|
|
125266
125751
|
const start3 = Date.now();
|
|
125267
125752
|
const write = (entry) => {
|
|
125268
125753
|
try {
|
|
@@ -125711,7 +126196,7 @@ var CoreSession = class {
|
|
|
125711
126196
|
};
|
|
125712
126197
|
|
|
125713
126198
|
// packages/agent-host/src/adapters/acp/session.ts
|
|
125714
|
-
import { isAbsolute as isAbsolute6, join as
|
|
126199
|
+
import { isAbsolute as isAbsolute6, join as join35 } from "node:path";
|
|
125715
126200
|
|
|
125716
126201
|
// packages/agent-host/src/adapters/always-allow.ts
|
|
125717
126202
|
var AlwaysAllowRules = class {
|
|
@@ -125853,7 +126338,7 @@ var UnmappedTypes = class {
|
|
|
125853
126338
|
};
|
|
125854
126339
|
|
|
125855
126340
|
// packages/agent-host/src/adapters/acp/connection.ts
|
|
125856
|
-
import { randomBytes as
|
|
126341
|
+
import { randomBytes as randomBytes9 } from "node:crypto";
|
|
125857
126342
|
var RpcError = class extends Error {
|
|
125858
126343
|
constructor(code2, message, data) {
|
|
125859
126344
|
super(message);
|
|
@@ -125898,7 +126383,7 @@ var AcpConnection = class {
|
|
|
125898
126383
|
* String ids with a per-connection prefix: a host that adopts a process the previous host was talking to can
|
|
125899
126384
|
* receive answers to that host's requests (§4.4), and plain counters on both would have collided.
|
|
125900
126385
|
*/
|
|
125901
|
-
idPrefix = `c${
|
|
126386
|
+
idPrefix = `c${randomBytes9(3).toString("hex")}-`;
|
|
125902
126387
|
nextId = 1;
|
|
125903
126388
|
pending = new PendingRequests();
|
|
125904
126389
|
closed = false;
|
|
@@ -127108,7 +127593,7 @@ var AcpWire = class {
|
|
|
127108
127593
|
ownTool: Boolean(this.deps.centraluToolOf?.(call2)),
|
|
127109
127594
|
auto: this.preset === "auto",
|
|
127110
127595
|
alwaysAllowKey: alwaysAllowKey(detail),
|
|
127111
|
-
edit: edit ? { folder: this.opts.cwd, paths: editPaths(call2).map((p) => isAbsolute6(p) ? p :
|
|
127596
|
+
edit: edit ? { folder: this.opts.cwd, paths: editPaths(call2).map((p) => isAbsolute6(p) ? p : join35(this.opts.cwd, p)) } : null
|
|
127112
127597
|
};
|
|
127113
127598
|
}
|
|
127114
127599
|
respondApproval(requestId, decision, matcher2) {
|
|
@@ -127265,10 +127750,10 @@ var ACP_CAPABILITIES = {
|
|
|
127265
127750
|
// packages/agent-host/src/adapters/antigravity/agent-files.ts
|
|
127266
127751
|
import { spawn as spawnChild } from "node:child_process";
|
|
127267
127752
|
import { createHash as createHash11 } from "node:crypto";
|
|
127268
|
-
import { closeSync as closeSync5, createReadStream as createReadStream3, createWriteStream, existsSync as existsSync18, fstatSync as fstatSync4, openSync as openSync5, readdirSync as readdirSync11, readFileSync as readFileSync12, readSync as readSync4, watch as
|
|
127269
|
-
import { chmod as chmod3, mkdir as
|
|
127753
|
+
import { closeSync as closeSync5, createReadStream as createReadStream3, createWriteStream, existsSync as existsSync18, fstatSync as fstatSync4, openSync as openSync5, readdirSync as readdirSync11, readFileSync as readFileSync12, readSync as readSync4, watch as watch4 } from "node:fs";
|
|
127754
|
+
import { chmod as chmod3, mkdir as mkdir6, mkdtemp as mkdtemp3, readdir as readdir9, readFile as readFile5, rename as rename5, rm as rm6, statfs, writeFile as writeFile5 } from "node:fs/promises";
|
|
127270
127755
|
import { homedir as homedir8 } from "node:os";
|
|
127271
|
-
import { join as
|
|
127756
|
+
import { join as join37 } from "node:path";
|
|
127272
127757
|
import { Transform } from "node:stream";
|
|
127273
127758
|
import { pipeline } from "node:stream/promises";
|
|
127274
127759
|
import { createInflateRaw } from "node:zlib";
|
|
@@ -127276,7 +127761,7 @@ import { createInflateRaw } from "node:zlib";
|
|
|
127276
127761
|
// packages/agent-host/src/dev-services/instance-lock.ts
|
|
127277
127762
|
import { execFileSync as execFileSync3 } from "node:child_process";
|
|
127278
127763
|
import { readFileSync as readFileSync11, unlinkSync as unlinkSync3, writeFileSync as writeFileSync9 } from "node:fs";
|
|
127279
|
-
import { dirname as dirname16, join as
|
|
127764
|
+
import { dirname as dirname16, join as join36 } from "node:path";
|
|
127280
127765
|
import Database2 from "better-sqlite3";
|
|
127281
127766
|
var OWNERSHIP_FILE = "host-ownership.sqlite";
|
|
127282
127767
|
var held = /* @__PURE__ */ new Set();
|
|
@@ -127317,7 +127802,7 @@ function parseHolder(raw) {
|
|
|
127317
127802
|
function acquireInstanceLock(dbPath2, startOf = processStartTime, describe4 = {}, now = Date.now) {
|
|
127318
127803
|
if (dbPath2 === ":memory:") return { ok: true, release: () => {
|
|
127319
127804
|
} };
|
|
127320
|
-
const lockPath =
|
|
127805
|
+
const lockPath = join36(dirname16(dbPath2), "host.lock");
|
|
127321
127806
|
const readHolder = () => {
|
|
127322
127807
|
try {
|
|
127323
127808
|
return parseHolder(readFileSync11(lockPath, "utf8"));
|
|
@@ -127325,7 +127810,7 @@ function acquireInstanceLock(dbPath2, startOf = processStartTime, describe4 = {}
|
|
|
127325
127810
|
return null;
|
|
127326
127811
|
}
|
|
127327
127812
|
};
|
|
127328
|
-
const owner = takeOwnership(
|
|
127813
|
+
const owner = takeOwnership(join36(dirname16(dbPath2), OWNERSHIP_FILE));
|
|
127329
127814
|
if (!owner) {
|
|
127330
127815
|
const holder = readHolder();
|
|
127331
127816
|
const pid = holder && Number.isInteger(holder.pid) && holder.pid > 0 ? holder.pid : null;
|
|
@@ -127450,26 +127935,26 @@ function agyPlatformKey(platform = HOST_PLATFORM, arch = process.arch) {
|
|
|
127450
127935
|
return null;
|
|
127451
127936
|
}
|
|
127452
127937
|
function agyHome(dataDir, version2) {
|
|
127453
|
-
const root3 =
|
|
127454
|
-
return { root: root3, versionDir:
|
|
127938
|
+
const root3 = join37(dataDir, "tools", "antigravity");
|
|
127939
|
+
return { root: root3, versionDir: join37(root3, version2), geminiHome: join37(root3, "gemini-home") };
|
|
127455
127940
|
}
|
|
127456
127941
|
function agyProgram(home, archive) {
|
|
127457
|
-
return
|
|
127942
|
+
return join37(home.versionDir, archive.command);
|
|
127458
127943
|
}
|
|
127459
127944
|
var INSTALL_MARKER = "install.json";
|
|
127460
127945
|
function isInstalled(home, archive) {
|
|
127461
127946
|
try {
|
|
127462
|
-
const marker = asObj(JSON.parse(readFileSync12(
|
|
127947
|
+
const marker = asObj(JSON.parse(readFileSync12(join37(home.versionDir, INSTALL_MARKER), "utf8")));
|
|
127463
127948
|
if (marker.sha256 !== archive.sha256) return false;
|
|
127464
|
-
return archive.files.every((f) => existsSync18(
|
|
127949
|
+
return archive.files.every((f) => existsSync18(join37(home.versionDir, f)));
|
|
127465
127950
|
} catch {
|
|
127466
127951
|
return false;
|
|
127467
127952
|
}
|
|
127468
127953
|
}
|
|
127469
127954
|
function hasSignIn(home) {
|
|
127470
|
-
const dir =
|
|
127955
|
+
const dir = join37(home.geminiHome, "antigravity-acp");
|
|
127471
127956
|
try {
|
|
127472
|
-
const settings = asObj(JSON.parse(readFileSync12(
|
|
127957
|
+
const settings = asObj(JSON.parse(readFileSync12(join37(dir, "settings.json"), "utf8")));
|
|
127473
127958
|
const auth = asObj(settings.auth);
|
|
127474
127959
|
return typeof auth.type === "string" && auth.type.length > 0;
|
|
127475
127960
|
} catch {
|
|
@@ -127529,22 +128014,22 @@ async function installAgy(home, archive, version2, opts) {
|
|
|
127529
128014
|
const io2 = opts.io ?? realInstallIo;
|
|
127530
128015
|
const { signal } = opts;
|
|
127531
128016
|
if (isInstalled(home, archive)) return;
|
|
127532
|
-
await
|
|
128017
|
+
await mkdir6(home.root, { recursive: true });
|
|
127533
128018
|
await removeStale(home.root, ".install-");
|
|
127534
128019
|
const free = await io2.freeBytes(home.root);
|
|
127535
128020
|
if (free !== null && free < AGY_INSTALL_SPACE_BYTES) {
|
|
127536
128021
|
throw new InstallFailure("disk_space", `Antigravity needs about ${mb(AGY_INSTALL_SPACE_BYTES)} MB free to install; ${mb(free)} MB are free`);
|
|
127537
128022
|
}
|
|
127538
|
-
const work = await mkdtemp3(
|
|
128023
|
+
const work = await mkdtemp3(join37(home.root, `.install-${ownerTag()}-`));
|
|
127539
128024
|
try {
|
|
127540
|
-
const zip =
|
|
128025
|
+
const zip = join37(work, "archive.zip");
|
|
127541
128026
|
const sha = await download(io2, archive.url, zip, signal, opts.onProgress);
|
|
127542
128027
|
if (sha !== archive.sha256) throw new InstallFailure("checksum", `The downloaded archive does not match the version Centralu pins (sha256 ${sha.slice(0, 12)}\u2026)`);
|
|
127543
128028
|
cancelled(signal);
|
|
127544
|
-
const pkg =
|
|
128029
|
+
const pkg = join37(work, "pkg");
|
|
127545
128030
|
await unpack(zip, archive, pkg, signal, opts.platform ?? HOST_PLATFORM);
|
|
127546
128031
|
await rm6(zip, { force: true });
|
|
127547
|
-
await writeFile5(
|
|
128032
|
+
await writeFile5(join37(pkg, INSTALL_MARKER), `${JSON.stringify({ version: version2, sha256: archive.sha256, url: archive.url })}
|
|
127548
128033
|
`);
|
|
127549
128034
|
cancelled(signal);
|
|
127550
128035
|
if (isInstalled(home, archive)) return;
|
|
@@ -127607,12 +128092,12 @@ async function unpack(zip, archive, dir, signal, platform) {
|
|
|
127607
128092
|
if (names.length !== want.length || names.some((n, i) => n !== want[i])) {
|
|
127608
128093
|
throw new InstallFailure("unpack", `The archive holds ${names.join(", ") || "nothing"}, not the files the release names (${want.join(", ")})`);
|
|
127609
128094
|
}
|
|
127610
|
-
await
|
|
128095
|
+
await mkdir6(dir, { recursive: true });
|
|
127611
128096
|
for (const e of entries) {
|
|
127612
128097
|
if (e.kind !== "file" || !/^[\w.-]+$/.test(e.name)) throw new InstallFailure("unpack", `The archive's entry ${e.name} is not a plain file`);
|
|
127613
128098
|
if (e.method !== 0 && e.method !== 8) throw new InstallFailure("unpack", `The archive's entry ${e.name} uses compression method ${e.method}`);
|
|
127614
128099
|
cancelled(signal);
|
|
127615
|
-
const path2 =
|
|
128100
|
+
const path2 = join37(dir, e.name);
|
|
127616
128101
|
await unpackEntry(zip, e, starts.get(e) ?? 0, path2, signal);
|
|
127617
128102
|
if (platform !== "win32") await chmod3(path2, 493);
|
|
127618
128103
|
}
|
|
@@ -127662,9 +128147,9 @@ async function removeLeftovers(home) {
|
|
|
127662
128147
|
await removeStale(home.root, ".sign-in-");
|
|
127663
128148
|
}
|
|
127664
128149
|
async function removeStale(root3, prefix) {
|
|
127665
|
-
const names = await
|
|
128150
|
+
const names = await readdir9(root3).catch(() => []);
|
|
127666
128151
|
const stale = names.filter((n) => n.startsWith(prefix) && ownerGone(n.slice(prefix.length)));
|
|
127667
|
-
await Promise.all(stale.map((n) => rm6(
|
|
128152
|
+
await Promise.all(stale.map((n) => rm6(join37(root3, n), { recursive: true, force: true }).catch(() => {
|
|
127668
128153
|
})));
|
|
127669
128154
|
}
|
|
127670
128155
|
var ownStart;
|
|
@@ -127695,10 +128180,10 @@ function processAlive(pid) {
|
|
|
127695
128180
|
}
|
|
127696
128181
|
}
|
|
127697
128182
|
async function removeOtherVersions(home) {
|
|
127698
|
-
const names = await
|
|
128183
|
+
const names = await readdir9(home.root).catch(() => []);
|
|
127699
128184
|
for (const n of names) {
|
|
127700
128185
|
if (!/^\d+\.\d+\.\d+/.test(n)) continue;
|
|
127701
|
-
const dir =
|
|
128186
|
+
const dir = join37(home.root, n);
|
|
127702
128187
|
if (dir === home.versionDir) continue;
|
|
127703
128188
|
await rm6(dir, { recursive: true, force: true }).catch(() => {
|
|
127704
128189
|
});
|
|
@@ -127707,7 +128192,7 @@ async function removeOtherVersions(home) {
|
|
|
127707
128192
|
async function renameHeld(from, to) {
|
|
127708
128193
|
for (let attempt = 1; ; attempt++) {
|
|
127709
128194
|
try {
|
|
127710
|
-
await
|
|
128195
|
+
await rename5(from, to);
|
|
127711
128196
|
return;
|
|
127712
128197
|
} catch (err) {
|
|
127713
128198
|
const code2 = err instanceof Error ? Reflect.get(err, "code") : void 0;
|
|
@@ -127718,21 +128203,21 @@ async function renameHeld(from, to) {
|
|
|
127718
128203
|
}
|
|
127719
128204
|
var mb = (bytes2) => Math.round(bytes2 / (1024 * 1024));
|
|
127720
128205
|
async function browserFolder(home, platform = HOST_PLATFORM, node = process.execPath) {
|
|
127721
|
-
await
|
|
128206
|
+
await mkdir6(home.root, { recursive: true });
|
|
127722
128207
|
await removeStale(home.root, ".sign-in-");
|
|
127723
|
-
const dir = await mkdtemp3(
|
|
128208
|
+
const dir = await mkdtemp3(join37(home.root, `.sign-in-${ownerTag()}-`));
|
|
127724
128209
|
await chmod3(dir, 448).catch(() => {
|
|
127725
128210
|
});
|
|
127726
128211
|
let browser;
|
|
127727
128212
|
if (platform === "win32") {
|
|
127728
|
-
const script =
|
|
128213
|
+
const script = join37(dir, "open-url.mjs");
|
|
127729
128214
|
await writeFile5(script, WINDOWS_HELPER);
|
|
127730
128215
|
browser = windowsBrowserCommand(node, script);
|
|
127731
128216
|
} else {
|
|
127732
|
-
const helper =
|
|
127733
|
-
const part = `${shQuote(
|
|
128217
|
+
const helper = join37(dir, "open-url");
|
|
128218
|
+
const part = `${shQuote(join37(dir, "part-"))}"$$"`;
|
|
127734
128219
|
await writeFile5(helper, `#!/bin/sh
|
|
127735
|
-
printf '%s\\n' "$1" > ${part} && mv ${part} ${shQuote(
|
|
128220
|
+
printf '%s\\n' "$1" > ${part} && mv ${part} ${shQuote(join37(dir, "url-"))}"$$"
|
|
127736
128221
|
exit 0
|
|
127737
128222
|
`, { mode: 448 });
|
|
127738
128223
|
await chmod3(helper, 448);
|
|
@@ -127752,8 +128237,8 @@ exit 0
|
|
|
127752
128237
|
}
|
|
127753
128238
|
for (const name of names) {
|
|
127754
128239
|
taken.add(name);
|
|
127755
|
-
const path2 =
|
|
127756
|
-
void
|
|
128240
|
+
const path2 = join37(dir, name);
|
|
128241
|
+
void readFile5(path2, "utf8").then(
|
|
127757
128242
|
async (text3) => {
|
|
127758
128243
|
await rm6(path2, { force: true }).catch(() => {
|
|
127759
128244
|
});
|
|
@@ -127765,7 +128250,7 @@ exit 0
|
|
|
127765
128250
|
}
|
|
127766
128251
|
};
|
|
127767
128252
|
try {
|
|
127768
|
-
watcher =
|
|
128253
|
+
watcher = watch4(dir, () => look());
|
|
127769
128254
|
watcher.on("error", () => {
|
|
127770
128255
|
});
|
|
127771
128256
|
} catch {
|
|
@@ -128295,10 +128780,10 @@ var AntigravityAdapter = class extends AcpAdapter {
|
|
|
128295
128780
|
|
|
128296
128781
|
// packages/agent-host/src/adapters/claude/index.ts
|
|
128297
128782
|
import { tmpdir as tmpdir4 } from "node:os";
|
|
128298
|
-
import { mkdtemp as mkdtemp4, readdir as
|
|
128299
|
-
import { join as
|
|
128783
|
+
import { mkdtemp as mkdtemp4, readdir as readdir12, rm as rm9, stat as stat11 } from "node:fs/promises";
|
|
128784
|
+
import { join as join45 } from "node:path";
|
|
128300
128785
|
import { randomUUID as randomUUID23 } from "node:crypto";
|
|
128301
|
-
import { execFile as
|
|
128786
|
+
import { execFile as execFile6 } from "node:child_process";
|
|
128302
128787
|
import { promisify as promisify4 } from "node:util";
|
|
128303
128788
|
|
|
128304
128789
|
// packages/agent-host/src/cli-version.ts
|
|
@@ -128651,13 +129136,28 @@ function claudeQueryOptions({ sessionId, opts, executablePath, servers, permissi
|
|
|
128651
129136
|
}
|
|
128652
129137
|
|
|
128653
129138
|
// packages/agent-host/src/adapters/claude/exe-link.ts
|
|
128654
|
-
import {
|
|
129139
|
+
import { execFile as execFile5 } from "node:child_process";
|
|
129140
|
+
import { randomBytes as randomBytes10 } from "node:crypto";
|
|
128655
129141
|
import { closeSync as closeSync7, copyFileSync as copyFileSync4, linkSync as linkSync2, mkdirSync as mkdirSync20, openSync as openSync7, readdirSync as readdirSync13, readFileSync as readFileSync15, readSync as readSync6, renameSync as renameSync11, rmSync as rmSync6, statSync as statSync15 } from "node:fs";
|
|
128656
|
-
import {
|
|
129142
|
+
import { createRequire } from "node:module";
|
|
129143
|
+
import { basename as basename12, dirname as dirname20, join as join42 } from "node:path";
|
|
128657
129144
|
var PLACEHOLDER_MAX_BYTES = 1e6;
|
|
129145
|
+
var INSTALLER_TIMEOUT_MS = 3e4;
|
|
129146
|
+
var VERSION_TIMEOUT_MS = 1e4;
|
|
128658
129147
|
var ClaudePlaceholderError = class extends Error {
|
|
128659
129148
|
name = "ClaudePlaceholderError";
|
|
128660
129149
|
};
|
|
129150
|
+
function runNode(file2, args, cwd, timeoutMs) {
|
|
129151
|
+
return new Promise((resolve20) => {
|
|
129152
|
+
const child = execFile5(file2, args, { cwd, timeout: timeoutMs, windowsHide: true, maxBuffer: 1 << 20, encoding: "utf8" }, (err, stdout, stderr) => {
|
|
129153
|
+
const output = `${stdout}${stderr}`.trim();
|
|
129154
|
+
if (!err) return resolve20({ status: 0, output });
|
|
129155
|
+
if (err.killed) return resolve20({ status: null, output: `it did not finish within ${Math.round(timeoutMs / 1e3)} seconds` });
|
|
129156
|
+
resolve20(typeof err.code === "number" ? { status: err.code, output } : { status: null, output: err.message });
|
|
129157
|
+
});
|
|
129158
|
+
child.stdin?.end();
|
|
129159
|
+
});
|
|
129160
|
+
}
|
|
128661
129161
|
var realExeFs = {
|
|
128662
129162
|
stat: (p) => statSync15(p),
|
|
128663
129163
|
head: (p, n) => {
|
|
@@ -128677,9 +129177,33 @@ var realExeFs = {
|
|
|
128677
129177
|
list: (d2) => readdirSync13(d2),
|
|
128678
129178
|
mkdir: (d2) => void mkdirSync20(d2, { recursive: true }),
|
|
128679
129179
|
removeDir: (d2) => rmSync6(d2, { recursive: true, force: true }),
|
|
128680
|
-
removeFile: (p) => rmSync6(p, { force: true })
|
|
129180
|
+
removeFile: (p) => rmSync6(p, { force: true }),
|
|
129181
|
+
runInstaller: (installer, timeoutMs) => runNode(process.execPath, [installer], dirname20(installer), timeoutMs),
|
|
129182
|
+
/*
|
|
129183
|
+
* Where `install.cjs` takes the program from: the platform package its `package.json` lists, resolved
|
|
129184
|
+
* from the package's own folder. Only Windows repairs, so the platform is win32 and the file claude.exe.
|
|
129185
|
+
*/
|
|
129186
|
+
nativeSize: (installer) => {
|
|
129187
|
+
try {
|
|
129188
|
+
const pkg = createRequire(installer).resolve(`@anthropic-ai/claude-code-win32-${process.arch}/package.json`);
|
|
129189
|
+
return statSync15(join42(dirname20(pkg), "claude.exe")).size;
|
|
129190
|
+
} catch {
|
|
129191
|
+
return null;
|
|
129192
|
+
}
|
|
129193
|
+
},
|
|
129194
|
+
runProgram: async (path2, args, timeoutMs) => ({ status: (await runNode(path2, args, void 0, timeoutMs)).status })
|
|
128681
129195
|
};
|
|
128682
|
-
function
|
|
129196
|
+
function installerFor(exe, fs3 = realExeFs) {
|
|
129197
|
+
if (basename12(dirname20(exe)).toLowerCase() !== "bin") return null;
|
|
129198
|
+
const installer = join42(dirname20(dirname20(exe)), "install.cjs");
|
|
129199
|
+
try {
|
|
129200
|
+
fs3.stat(installer);
|
|
129201
|
+
return installer;
|
|
129202
|
+
} catch {
|
|
129203
|
+
return null;
|
|
129204
|
+
}
|
|
129205
|
+
}
|
|
129206
|
+
function placeholderProblem(exe, size2, fs3 = realExeFs, tried = null) {
|
|
128683
129207
|
if (size2 >= PLACEHOLDER_MAX_BYTES) return null;
|
|
128684
129208
|
let head;
|
|
128685
129209
|
try {
|
|
@@ -128688,26 +129212,23 @@ function placeholderProblem(exe, size2, fs3 = realExeFs) {
|
|
|
128688
129212
|
return null;
|
|
128689
129213
|
}
|
|
128690
129214
|
if (head.toString("latin1") === "MZ") return null;
|
|
128691
|
-
const pkg = dirname20(dirname20(exe));
|
|
128692
|
-
const installer = basename11(dirname20(exe)).toLowerCase() === "bin" ? join41(pkg, "install.cjs") : null;
|
|
128693
|
-
let canRerun = false;
|
|
128694
|
-
if (installer) {
|
|
128695
|
-
try {
|
|
128696
|
-
fs3.stat(installer);
|
|
128697
|
-
canRerun = true;
|
|
128698
|
-
} catch {
|
|
128699
|
-
}
|
|
128700
|
-
}
|
|
128701
129215
|
return [
|
|
128702
129216
|
`Claude Code is not installed completely: ${exe} is a ${size2}-byte placeholder, not the Claude program (about 250 MB).`,
|
|
128703
129217
|
"Claude Code's setup step puts the real program there after npm installs the package; it did not finish, usually because a running Claude Code held the file during an install or update.",
|
|
128704
|
-
|
|
129218
|
+
...tried ? [tried] : [],
|
|
129219
|
+
fixAdvice(installerFor(exe, fs3))
|
|
128705
129220
|
].join(" ");
|
|
128706
129221
|
}
|
|
129222
|
+
function fixAdvice(installer) {
|
|
129223
|
+
return installer ? `To fix it, close every Claude Code (Centralu's Claude sessions, terminals, editors) and run \`node "${installer}"\`, or reinstall with \`npm i -g @anthropic-ai/claude-code\` while no Claude Code is running.` : "To fix it, close every Claude Code (Centralu's Claude sessions, terminals, editors) and reinstall it with `npm i -g @anthropic-ai/claude-code`.";
|
|
129224
|
+
}
|
|
129225
|
+
function unprovenProblem(exe, size2, why2, tried, installer) {
|
|
129226
|
+
return [`Claude Code is not installed completely: ${exe} is ${size2} bytes and ${why2}.`, tried, fixAdvice(installer)].join(" ");
|
|
129227
|
+
}
|
|
128707
129228
|
function claudeKey(exe, stat12, fs3 = realExeFs) {
|
|
128708
129229
|
let version2 = null;
|
|
128709
129230
|
try {
|
|
128710
|
-
const pkg = JSON.parse(fs3.read(
|
|
129231
|
+
const pkg = JSON.parse(fs3.read(join42(dirname20(dirname20(exe)), "package.json")));
|
|
128711
129232
|
if (pkg.name === "@anthropic-ai/claude-code" && typeof pkg.version === "string" && pkg.version) version2 = pkg.version;
|
|
128712
129233
|
} catch {
|
|
128713
129234
|
}
|
|
@@ -128727,19 +129248,31 @@ var ClaudeLinks = class {
|
|
|
128727
129248
|
fs;
|
|
128728
129249
|
log;
|
|
128729
129250
|
uses = /* @__PURE__ */ new Map();
|
|
129251
|
+
/** The setup step of each package this host has started, running or done: once per host start, never in a loop */
|
|
129252
|
+
repairs = /* @__PURE__ */ new Map();
|
|
129253
|
+
/** What the setup step did, once it ended: what the message adds about it, and the program it left that is not proven */
|
|
129254
|
+
outcomes = /* @__PURE__ */ new Map();
|
|
128730
129255
|
/** The key new sessions start from: the last one prepared */
|
|
128731
129256
|
current = null;
|
|
128732
129257
|
get enabled() {
|
|
128733
129258
|
return windowsStart(this.opts.platform);
|
|
128734
129259
|
}
|
|
128735
129260
|
base() {
|
|
128736
|
-
return
|
|
129261
|
+
return join42(this.opts.root(), "tools", "claude");
|
|
129262
|
+
}
|
|
129263
|
+
/**
|
|
129264
|
+
* `prepare`, after repairing npm's placeholder if that is what `exe` is. Every start path awaits this,
|
|
129265
|
+
* so the setup step runs beside the host's loop, never in it.
|
|
129266
|
+
*/
|
|
129267
|
+
async ensure(exe) {
|
|
129268
|
+
await this.repair(exe);
|
|
129269
|
+
return this.prepare(exe);
|
|
128737
129270
|
}
|
|
128738
129271
|
/**
|
|
128739
129272
|
* What to start for the `claude.exe` found at `exe`. Off Windows, or for anything but an `.exe`
|
|
128740
129273
|
* (a `cli.js` through Node), `exe` itself.
|
|
128741
129274
|
*
|
|
128742
|
-
* @throws ClaudePlaceholderError when the file is npm's placeholder
|
|
129275
|
+
* @throws ClaudePlaceholderError when the file is npm's placeholder, or a setup step left a file that is not proven to be the program
|
|
128743
129276
|
*/
|
|
128744
129277
|
prepare(exe) {
|
|
128745
129278
|
const direct = { path: exe, key: null };
|
|
@@ -128750,11 +129283,15 @@ var ClaudeLinks = class {
|
|
|
128750
129283
|
} catch {
|
|
128751
129284
|
return direct;
|
|
128752
129285
|
}
|
|
128753
|
-
const
|
|
129286
|
+
const installer = installerFor(exe, this.fs);
|
|
129287
|
+
const outcome = installer ? this.outcomes.get(installer) : void 0;
|
|
129288
|
+
let problem3 = placeholderProblem(exe, stat12.size, this.fs, outcome?.note ?? null);
|
|
129289
|
+
const bad = outcome?.unproven;
|
|
129290
|
+
if (!problem3 && installer && outcome && bad && bad.size === stat12.size && bad.mtimeMs === stat12.mtimeMs) problem3 = unprovenProblem(exe, stat12.size, bad.why, outcome.note, installer);
|
|
128754
129291
|
if (problem3) throw new ClaudePlaceholderError(problem3);
|
|
128755
129292
|
const key = claudeKey(exe, stat12, this.fs);
|
|
128756
|
-
const dir =
|
|
128757
|
-
const target =
|
|
129293
|
+
const dir = join42(this.base(), key);
|
|
129294
|
+
const target = join42(dir, "claude.exe");
|
|
128758
129295
|
const ready = () => {
|
|
128759
129296
|
try {
|
|
128760
129297
|
return this.fs.stat(target).size === stat12.size;
|
|
@@ -128763,7 +129300,7 @@ var ClaudeLinks = class {
|
|
|
128763
129300
|
}
|
|
128764
129301
|
};
|
|
128765
129302
|
if (ready()) return this.use(key, target);
|
|
128766
|
-
const tmp =
|
|
129303
|
+
const tmp = join42(dir, `claude-${randomBytes10(4).toString("hex")}.tmp`);
|
|
128767
129304
|
try {
|
|
128768
129305
|
this.fs.mkdir(dir);
|
|
128769
129306
|
try {
|
|
@@ -128788,6 +129325,74 @@ var ClaudeLinks = class {
|
|
|
128788
129325
|
return direct;
|
|
128789
129326
|
}
|
|
128790
129327
|
}
|
|
129328
|
+
/**
|
|
129329
|
+
* Runs the package's own setup step, once per package per host start, when `exe` is npm's placeholder
|
|
129330
|
+
* and `install.cjs` is beside it (the step that was meant to put the program there; nothing else runs
|
|
129331
|
+
* and nothing is fetched). Starts that arrive meanwhile wait for the same run. Never stops a process:
|
|
129332
|
+
* a file a running Claude Code holds stays a placeholder and the message says so. Never rejects.
|
|
129333
|
+
*/
|
|
129334
|
+
repair(exe) {
|
|
129335
|
+
if (!this.enabled || !/\.exe$/i.test(exe)) return Promise.resolve();
|
|
129336
|
+
let size2;
|
|
129337
|
+
try {
|
|
129338
|
+
size2 = this.fs.stat(exe).size;
|
|
129339
|
+
} catch {
|
|
129340
|
+
return Promise.resolve();
|
|
129341
|
+
}
|
|
129342
|
+
if (placeholderProblem(exe, size2, this.fs) === null) return Promise.resolve();
|
|
129343
|
+
const installer = installerFor(exe, this.fs);
|
|
129344
|
+
if (!installer) return Promise.resolve();
|
|
129345
|
+
let run4 = this.repairs.get(installer);
|
|
129346
|
+
if (!run4) {
|
|
129347
|
+
run4 = this.runSetupStep(exe, installer).catch((err) => {
|
|
129348
|
+
this.log(`[claude] the setup step could not be run: ${code(err)}`);
|
|
129349
|
+
this.outcomes.set(installer, { note: `Centralu tried to run that setup step (\`node "${installer}"\`) and could not: ${code(err)}.`, unproven: null });
|
|
129350
|
+
});
|
|
129351
|
+
this.repairs.set(installer, run4);
|
|
129352
|
+
}
|
|
129353
|
+
return run4;
|
|
129354
|
+
}
|
|
129355
|
+
async runSetupStep(exe, installer) {
|
|
129356
|
+
this.log(`[claude] ${exe} is npm's placeholder; running Claude Code's own setup step, ${installer}`);
|
|
129357
|
+
const run4 = await this.fs.runInstaller(installer, this.opts.installerTimeoutMs ?? INSTALLER_TIMEOUT_MS);
|
|
129358
|
+
const step = `Centralu ran that setup step itself (\`node "${installer}"\`)`;
|
|
129359
|
+
const verdict = await this.proven(exe, installer);
|
|
129360
|
+
if (verdict.ok) {
|
|
129361
|
+
this.log(`[claude] repaired ${exe}`);
|
|
129362
|
+
this.outcomes.set(installer, { note: `Centralu already ran that setup step once since it started (\`node "${installer}"\`) and does not run it again.`, unproven: null });
|
|
129363
|
+
return;
|
|
129364
|
+
}
|
|
129365
|
+
const ran = run4.status === null ? run4.output : run4.status === 0 ? "" : `exit code ${run4.status}: ${firstLine3(run4.output)}`;
|
|
129366
|
+
if (verdict.stat) {
|
|
129367
|
+
this.log(`[claude] the setup step left ${verdict.stat.size} bytes at ${exe} that are not proven to be the program (${verdict.why})${ran ? `; ${ran}` : ""}`);
|
|
129368
|
+
this.outcomes.set(installer, { note: `${step} and it did not finish the copy${ran ? `: ${ran}` : ""}.`, unproven: { ...verdict.stat, why: verdict.why } });
|
|
129369
|
+
} else if (/\b(EBUSY|EPERM|EACCES)\b/.test(run4.output)) {
|
|
129370
|
+
this.log(`[claude] the setup step could not replace ${exe}; a running program holds it: ${firstLine3(run4.output)}`);
|
|
129371
|
+
this.outcomes.set(installer, { note: `${step} and the file is still a placeholder: it could not be replaced, usually because a running Claude Code held it.`, unproven: null });
|
|
129372
|
+
} else {
|
|
129373
|
+
const why2 = ran || firstLine3(run4.output) || "it left the placeholder in place";
|
|
129374
|
+
this.log(`[claude] the setup step did not repair ${exe}: ${why2}`);
|
|
129375
|
+
this.outcomes.set(installer, { note: `${step} and it did not repair the file: ${why2}.`, unproven: null });
|
|
129376
|
+
}
|
|
129377
|
+
}
|
|
129378
|
+
/**
|
|
129379
|
+
* Whether the file at `exe` is the program. Not "large enough": a copy cut by the time limit is large.
|
|
129380
|
+
* It must be the size of the platform package's program it was made from; where that cannot be found,
|
|
129381
|
+
* it must run `--version` and exit 0.
|
|
129382
|
+
*/
|
|
129383
|
+
async proven(exe, installer) {
|
|
129384
|
+
let stat12;
|
|
129385
|
+
try {
|
|
129386
|
+
stat12 = this.fs.stat(exe);
|
|
129387
|
+
} catch {
|
|
129388
|
+
return { ok: false, why: "the file is gone" };
|
|
129389
|
+
}
|
|
129390
|
+
if (placeholderProblem(exe, stat12.size, this.fs) !== null) return { ok: false, why: "it is still the placeholder" };
|
|
129391
|
+
const native = this.fs.nativeSize(installer);
|
|
129392
|
+
if (native !== null) return stat12.size === native ? { ok: true } : { ok: false, stat: stat12, why: `not the ${native} bytes of the Claude program it was copied from` };
|
|
129393
|
+
const version2 = await this.fs.runProgram(exe, ["--version"], VERSION_TIMEOUT_MS);
|
|
129394
|
+
return version2.status === 0 ? { ok: true } : { ok: false, stat: stat12, why: "it does not run (`--version` failed)" };
|
|
129395
|
+
}
|
|
128791
129396
|
use(key, path2) {
|
|
128792
129397
|
if (key !== this.current) {
|
|
128793
129398
|
this.current = key;
|
|
@@ -128823,7 +129428,7 @@ var ClaudeLinks = class {
|
|
|
128823
129428
|
for (const name of names) {
|
|
128824
129429
|
if (name === this.current || this.uses.has(name)) continue;
|
|
128825
129430
|
try {
|
|
128826
|
-
this.fs.removeDir(
|
|
129431
|
+
this.fs.removeDir(join42(this.base(), name));
|
|
128827
129432
|
} catch {
|
|
128828
129433
|
}
|
|
128829
129434
|
}
|
|
@@ -128837,6 +129442,10 @@ var ClaudeLinks = class {
|
|
|
128837
129442
|
}
|
|
128838
129443
|
}
|
|
128839
129444
|
};
|
|
129445
|
+
function firstLine3(text3) {
|
|
129446
|
+
const line = text3.split(/\r?\n/).find((l) => l.trim())?.trim() ?? "";
|
|
129447
|
+
return line.length > 240 ? `${line.slice(0, 240)}...` : line;
|
|
129448
|
+
}
|
|
128840
129449
|
function code(err) {
|
|
128841
129450
|
return err?.code ?? err?.message ?? String(err);
|
|
128842
129451
|
}
|
|
@@ -128960,7 +129569,7 @@ function contentBlocks(m) {
|
|
|
128960
129569
|
}
|
|
128961
129570
|
|
|
128962
129571
|
// packages/agent-host/src/adapters/images.ts
|
|
128963
|
-
import { readFile as
|
|
129572
|
+
import { readFile as readFile7, stat as stat9 } from "node:fs/promises";
|
|
128964
129573
|
var IMAGE_EXTENSIONS = /* @__PURE__ */ new Set(["png", "jpg", "jpeg", "gif", "webp", "bmp"]);
|
|
128965
129574
|
var startsWith = (buf, bytes2, at2 = 0) => bytes2.every((b2, i) => buf[at2 + i] === b2);
|
|
128966
129575
|
function gifReachesTrailer(buf) {
|
|
@@ -129041,9 +129650,9 @@ async function imageEventFromDisk(sessionId, path2, maxBytes = IMAGE_MAX_BYTES)
|
|
|
129041
129650
|
const file2 = path2.split(/[\\/]/).pop() ?? path2;
|
|
129042
129651
|
const ext2 = file2.includes(".") ? file2.slice(file2.lastIndexOf(".") + 1).toLowerCase() : "";
|
|
129043
129652
|
try {
|
|
129044
|
-
const s = await
|
|
129653
|
+
const s = await stat9(path2);
|
|
129045
129654
|
if (s.size > maxBytes) return fail4(tooLarge(s.size));
|
|
129046
|
-
const buf = await
|
|
129655
|
+
const buf = await readFile7(path2);
|
|
129047
129656
|
const seen = judgeImage(buf);
|
|
129048
129657
|
if (!seen) return fail4(!ext2 ? "the file is not a picture" : IMAGE_EXTENSIONS.has(ext2) ? `the file is not a valid .${ext2} image` : `the .${ext2} format cannot be shown`);
|
|
129049
129658
|
if ("note" in seen) return fail4(seen.note);
|
|
@@ -129060,12 +129669,13 @@ function commandRunOf(name, input) {
|
|
|
129060
129669
|
const run4 = centraluRunCommand(input);
|
|
129061
129670
|
return run4 ? { name: name.slice(0, -"centralu_run".length) + run4.command, input: run4.args } : null;
|
|
129062
129671
|
}
|
|
129672
|
+
var SHELL_TOOLS = /* @__PURE__ */ new Set(["Bash", "PowerShell"]);
|
|
129063
129673
|
function toolSummary(name, input) {
|
|
129064
129674
|
const run4 = commandRunOf(name, input);
|
|
129065
129675
|
if (run4) return toolSummary(run4.name, run4.input);
|
|
129066
129676
|
const paths = [];
|
|
129067
129677
|
let title = name;
|
|
129068
|
-
if (name
|
|
129678
|
+
if (SHELL_TOOLS.has(name)) title = str(input.command, name);
|
|
129069
129679
|
else if (FILE_EDIT_TOOLS.has(name) || name === "Read") {
|
|
129070
129680
|
const p = str(input.file_path ?? input.notebook_path);
|
|
129071
129681
|
if (p) paths.push(p);
|
|
@@ -129120,7 +129730,7 @@ ${content.replace(/\n$/, "")}` : "" };
|
|
|
129120
129730
|
${diff}` : diff };
|
|
129121
129731
|
}
|
|
129122
129732
|
function approvalDetail(name, input, cwd, isNew) {
|
|
129123
|
-
if (name
|
|
129733
|
+
if (SHELL_TOOLS.has(name)) return { kind: "command", command: str(input.command), cwd, ...input.dangerouslyDisableSandbox === true ? { unsandboxed: true } : {} };
|
|
129124
129734
|
if (FILE_EDIT_TOOLS.has(name)) {
|
|
129125
129735
|
const change = claudeFileChange(name, input, isNew);
|
|
129126
129736
|
return fileEditDetail([change.diff ? change : { ...change, diff: JSON.stringify(input) }]);
|
|
@@ -130452,16 +131062,16 @@ var ClaudeStreamNormalizer = class {
|
|
|
130452
131062
|
|
|
130453
131063
|
// packages/agent-host/src/adapters/claude/lsp-plugins.ts
|
|
130454
131064
|
init_sdk();
|
|
130455
|
-
import { access as access3, readFile as
|
|
131065
|
+
import { access as access3, readFile as readFile8 } from "node:fs/promises";
|
|
130456
131066
|
import { homedir as homedir10 } from "node:os";
|
|
130457
|
-
import { join as
|
|
131067
|
+
import { join as join43 } from "node:path";
|
|
130458
131068
|
var KNOWN_LSP_PLUGINS = ["typescript-lsp@claude-plugins-official"];
|
|
130459
131069
|
function claudeConfigDir(env = process.env, home = homedir10()) {
|
|
130460
|
-
return env.CLAUDE_CONFIG_DIR ||
|
|
131070
|
+
return env.CLAUDE_CONFIG_DIR || join43(home, ".claude");
|
|
130461
131071
|
}
|
|
130462
131072
|
async function readJson2(path2) {
|
|
130463
131073
|
try {
|
|
130464
|
-
return JSON.parse(await
|
|
131074
|
+
return JSON.parse(await readFile8(path2, "utf8"));
|
|
130465
131075
|
} catch {
|
|
130466
131076
|
return void 0;
|
|
130467
131077
|
}
|
|
@@ -130472,22 +131082,22 @@ async function declaresLspServers(id3, configDir) {
|
|
|
130472
131082
|
if (at2 <= 0) return false;
|
|
130473
131083
|
const name = id3.slice(0, at2);
|
|
130474
131084
|
const marketplace = id3.slice(at2 + 1);
|
|
130475
|
-
const installed = await readJson2(
|
|
131085
|
+
const installed = await readJson2(join43(configDir, "plugins", "installed_plugins.json"));
|
|
130476
131086
|
const entries = isObj(installed) && isObj(installed.plugins) ? installed.plugins[id3] : void 0;
|
|
130477
131087
|
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
130478
131088
|
const path2 = isObj(entry) && typeof entry.installPath === "string" ? entry.installPath : null;
|
|
130479
131089
|
if (!path2) continue;
|
|
130480
|
-
if (declares(await readJson2(
|
|
131090
|
+
if (declares(await readJson2(join43(path2, ".claude-plugin", "plugin.json")))) return true;
|
|
130481
131091
|
try {
|
|
130482
|
-
await access3(
|
|
131092
|
+
await access3(join43(path2, ".lsp.json"));
|
|
130483
131093
|
return true;
|
|
130484
131094
|
} catch {
|
|
130485
131095
|
}
|
|
130486
131096
|
}
|
|
130487
|
-
const known = await readJson2(
|
|
131097
|
+
const known = await readJson2(join43(configDir, "plugins", "known_marketplaces.json"));
|
|
130488
131098
|
const place = isObj(known) && isObj(known[marketplace]) ? known[marketplace].installLocation : void 0;
|
|
130489
131099
|
if (typeof place !== "string") return false;
|
|
130490
|
-
const listing = await readJson2(
|
|
131100
|
+
const listing = await readJson2(join43(place, ".claude-plugin", "marketplace.json"));
|
|
130491
131101
|
const plugins = isObj(listing) && Array.isArray(listing.plugins) ? listing.plugins : [];
|
|
130492
131102
|
return plugins.some((p) => isObj(p) && p.name === name && declares(p));
|
|
130493
131103
|
}
|
|
@@ -130850,8 +131460,8 @@ var MidTurnLedger = class {
|
|
|
130850
131460
|
import { resolve as resolve17 } from "node:path";
|
|
130851
131461
|
|
|
130852
131462
|
// packages/agent-host/src/adapters/claude/new-file.ts
|
|
130853
|
-
import { stat as
|
|
130854
|
-
async function newFile(path2, statOf =
|
|
131463
|
+
import { stat as stat10 } from "node:fs/promises";
|
|
131464
|
+
async function newFile(path2, statOf = stat10) {
|
|
130855
131465
|
const found = statOf(path2).then(
|
|
130856
131466
|
() => false,
|
|
130857
131467
|
(err) => err.code === "ENOENT" ? true : void 0
|
|
@@ -131378,7 +131988,7 @@ function tracksInputs(m) {
|
|
|
131378
131988
|
|
|
131379
131989
|
// packages/agent-host/src/adapters/claude/surface.ts
|
|
131380
131990
|
import { readFileSync as readFileSync16 } from "node:fs";
|
|
131381
|
-
import { join as
|
|
131991
|
+
import { join as join44 } from "node:path";
|
|
131382
131992
|
|
|
131383
131993
|
// packages/agent-host/src/adapters/surface-drift.ts
|
|
131384
131994
|
function compareSurface(surface, limitations, baseline) {
|
|
@@ -131871,8 +132481,8 @@ var CLAUDE_SURFACE = {
|
|
|
131871
132481
|
async gather(env) {
|
|
131872
132482
|
const sdk = await env.packageDir(SDK);
|
|
131873
132483
|
return {
|
|
131874
|
-
"package.json": readFileSync16(
|
|
131875
|
-
"sdk.d.ts": readFileSync16(
|
|
132484
|
+
"package.json": readFileSync16(join44(sdk, "package.json"), "utf8"),
|
|
132485
|
+
"sdk.d.ts": readFileSync16(join44(sdk, "sdk.d.ts"), "utf8"),
|
|
131876
132486
|
"claude --version": await env.run("claude", ["--version"]),
|
|
131877
132487
|
"claude --help": await env.run("claude", ["--help"])
|
|
131878
132488
|
};
|
|
@@ -132075,7 +132685,7 @@ var ClaudeSession = class {
|
|
|
132075
132685
|
const preset = this.opts.permissionPreset;
|
|
132076
132686
|
this.cards.editAskRule = preset === "edits" || preset === "normal" ? await editAskRuleExists(this.opts) : true;
|
|
132077
132687
|
const exe = this.launch.executable();
|
|
132078
|
-
const run4 = exe ? this.launch.links.
|
|
132688
|
+
const run4 = exe ? await this.launch.links.ensure(exe) : null;
|
|
132079
132689
|
this.servers.load();
|
|
132080
132690
|
const servers = this.servers.mcpServers();
|
|
132081
132691
|
const permissionOptions = await permissionOptionsFor(this.opts);
|
|
@@ -132092,7 +132702,7 @@ var ClaudeSession = class {
|
|
|
132092
132702
|
servers,
|
|
132093
132703
|
permissionOptions,
|
|
132094
132704
|
codeIntelligence,
|
|
132095
|
-
spawn: (o) => this.opts.processSource ? this.spawnProcess(o) : this.proc = spawnLocalAgent(o),
|
|
132705
|
+
spawn: (o) => this.opts.processSource ? this.spawnProcess(o) : this.proc = tapSessionProcess(spawnLocalAgent(o), this.sessionId),
|
|
132096
132706
|
canUseTool: (toolName, toolInput, options) => this.cards.canUseTool(preset, toolName, toolInput, options),
|
|
132097
132707
|
hooks: requestTurnHooks(this.opts, () => this.servers.orchestratorServer !== null)
|
|
132098
132708
|
})
|
|
@@ -132271,7 +132881,7 @@ var ClaudeSession = class {
|
|
|
132271
132881
|
const src = this.opts.processSource;
|
|
132272
132882
|
const adopt = src.adopt && !this.adopted ? src.adopt.process : null;
|
|
132273
132883
|
this.adopted = true;
|
|
132274
|
-
this.proc = adopt ?? src.spawn({ command: o.command, args: o.args, cwd: o.cwd, env: o.env });
|
|
132884
|
+
this.proc = tapSessionProcess(adopt ?? src.spawn({ command: o.command, args: o.args, cwd: o.cwd, env: o.env }), this.sessionId);
|
|
132275
132885
|
return this.proc;
|
|
132276
132886
|
}
|
|
132277
132887
|
/**
|
|
@@ -132569,7 +133179,7 @@ var ClaudeSession = class {
|
|
|
132569
133179
|
};
|
|
132570
133180
|
|
|
132571
133181
|
// packages/agent-host/src/adapters/claude/index.ts
|
|
132572
|
-
var exec4 = promisify4(
|
|
133182
|
+
var exec4 = promisify4(execFile6);
|
|
132573
133183
|
var PROBE_USAGE_MS = 2e4;
|
|
132574
133184
|
var PROBE_USAGE_RETRY_MS = 500;
|
|
132575
133185
|
var ACCOUNT_CACHE_MS = 6e4;
|
|
@@ -132597,10 +133207,10 @@ async function claudeLoggedIn(bin) {
|
|
|
132597
133207
|
async function sweepProbeDirs(now = Date.now()) {
|
|
132598
133208
|
try {
|
|
132599
133209
|
const root3 = tmpdir4();
|
|
132600
|
-
for (const name of await
|
|
133210
|
+
for (const name of await readdir12(root3)) {
|
|
132601
133211
|
if (!name.startsWith(PROBE_DIR_PREFIX)) continue;
|
|
132602
|
-
const path2 =
|
|
132603
|
-
const old = await
|
|
133212
|
+
const path2 = join45(root3, name);
|
|
133213
|
+
const old = await stat11(path2).then((s) => now - s.mtimeMs > PROBE_DIR_STALE_MS, () => false);
|
|
132604
133214
|
if (old) await rm9(path2, { recursive: true, force: true }).catch(() => {
|
|
132605
133215
|
});
|
|
132606
133216
|
}
|
|
@@ -132708,7 +133318,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
132708
133318
|
try {
|
|
132709
133319
|
let launch2;
|
|
132710
133320
|
try {
|
|
132711
|
-
launch2 = this.statusLaunch();
|
|
133321
|
+
launch2 = await this.statusLaunch();
|
|
132712
133322
|
} catch (err) {
|
|
132713
133323
|
if (!(err instanceof ClaudePlaceholderError)) throw err;
|
|
132714
133324
|
return { tool: "claude", installed: false, loggedIn: false, detail: err.message };
|
|
@@ -132791,7 +133401,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
132791
133401
|
this.swept = true;
|
|
132792
133402
|
await sweepProbeDirs();
|
|
132793
133403
|
}
|
|
132794
|
-
const dir = await mkdtemp4(
|
|
133404
|
+
const dir = await mkdtemp4(join45(tmpdir4(), PROBE_DIR_PREFIX));
|
|
132795
133405
|
const sessionId = `usage-probe-${randomUUID23()}`;
|
|
132796
133406
|
_ClaudeAdapter.probeIds.add(sessionId);
|
|
132797
133407
|
let probe = null;
|
|
@@ -132829,7 +133439,7 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
132829
133439
|
if (this.accountCache && Date.now() - this.accountCache.at < ACCOUNT_CACHE_MS) return this.accountCache.value;
|
|
132830
133440
|
let value;
|
|
132831
133441
|
try {
|
|
132832
|
-
const status = await claudeAuthStatus(this.statusLaunch());
|
|
133442
|
+
const status = await claudeAuthStatus(await this.statusLaunch());
|
|
132833
133443
|
const flag = status?.loggedIn;
|
|
132834
133444
|
value = { loggedIn: typeof flag === "boolean" ? flag : null, fingerprint: accountFingerprint(status) };
|
|
132835
133445
|
} catch {
|
|
@@ -132840,9 +133450,9 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
132840
133450
|
}
|
|
132841
133451
|
accountCache = null;
|
|
132842
133452
|
/** The program the sign-in is asked of: what sessions start (on Windows the link), else the one on PATH */
|
|
132843
|
-
statusLaunch() {
|
|
133453
|
+
async statusLaunch() {
|
|
132844
133454
|
const exe = this.launch.windows ? this.launch.executable() : null;
|
|
132845
|
-
if (exe && /\.exe$/i.test(exe)) return { command: this.launch.links.
|
|
133455
|
+
if (exe && /\.exe$/i.test(exe)) return { command: (await this.launch.links.ensure(exe)).path, args: [] };
|
|
132846
133456
|
return launchFor2(toolCommand("claude"));
|
|
132847
133457
|
}
|
|
132848
133458
|
async listModels() {
|
|
@@ -132875,13 +133485,13 @@ var ClaudeAdapter = class _ClaudeAdapter {
|
|
|
132875
133485
|
};
|
|
132876
133486
|
|
|
132877
133487
|
// packages/agent-host/src/adapters/codex/index.ts
|
|
132878
|
-
import { execFile as
|
|
133488
|
+
import { execFile as execFile7 } from "node:child_process";
|
|
132879
133489
|
import { existsSync as existsSync21 } from "node:fs";
|
|
132880
|
-
import { join as
|
|
133490
|
+
import { join as join49 } from "node:path";
|
|
132881
133491
|
import { promisify as promisify5 } from "node:util";
|
|
132882
133492
|
|
|
132883
133493
|
// packages/agent-host/src/adapters/codex/client.ts
|
|
132884
|
-
import { randomBytes as
|
|
133494
|
+
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
132885
133495
|
|
|
132886
133496
|
// packages/agent-host/src/adapters/codex/native.ts
|
|
132887
133497
|
import { existsSync as existsSync20, readFileSync as readFileSync17, realpathSync as realpathSync8 } from "node:fs";
|
|
@@ -133010,7 +133620,7 @@ var CodexClient = class {
|
|
|
133010
133620
|
* with plain counters both hosts would have sent `1`, `2`, … and an old answer would resolve a new
|
|
133011
133621
|
* request. codex echoes string ids back as given.
|
|
133012
133622
|
*/
|
|
133013
|
-
idPrefix = `${
|
|
133623
|
+
idPrefix = `${randomBytes11(3).toString("hex")}-`;
|
|
133014
133624
|
pending = new PendingRequests();
|
|
133015
133625
|
closed = false;
|
|
133016
133626
|
/** A flag to call onExit only once (both 'error' and 'exit' can fire) */
|
|
@@ -133107,24 +133717,24 @@ async function withCodexClient(opts, fn2) {
|
|
|
133107
133717
|
|
|
133108
133718
|
// packages/agent-host/src/adapters/codex/home.ts
|
|
133109
133719
|
import { homedir as homedir11 } from "node:os";
|
|
133110
|
-
import { join as
|
|
133720
|
+
import { join as join46 } from "node:path";
|
|
133111
133721
|
function codexHome(env = process.env, home = homedir11()) {
|
|
133112
133722
|
const custom2 = env.CODEX_HOME?.trim();
|
|
133113
|
-
return custom2 ? custom2 :
|
|
133723
|
+
return custom2 ? custom2 : join46(home, ".codex");
|
|
133114
133724
|
}
|
|
133115
133725
|
|
|
133116
133726
|
// packages/agent-host/src/adapters/codex/rollout.ts
|
|
133117
133727
|
import { createReadStream as createReadStream5 } from "node:fs";
|
|
133118
|
-
import { readdir as
|
|
133119
|
-
import { join as
|
|
133728
|
+
import { readdir as readdir13 } from "node:fs/promises";
|
|
133729
|
+
import { join as join47 } from "node:path";
|
|
133120
133730
|
var MIN_SUMMARY_CHARS = 200;
|
|
133121
|
-
async function findRolloutPath(threadId, sessionsDir =
|
|
133731
|
+
async function findRolloutPath(threadId, sessionsDir = join47(codexHome(), "sessions")) {
|
|
133122
133732
|
try {
|
|
133123
133733
|
const suffix = `-${threadId}.jsonl`;
|
|
133124
|
-
const names = await
|
|
133734
|
+
const names = await readdir13(sessionsDir, { recursive: true });
|
|
133125
133735
|
const hits = names.filter((n) => String(n).endsWith(suffix)).sort();
|
|
133126
133736
|
const hit = hits[hits.length - 1];
|
|
133127
|
-
return hit ?
|
|
133737
|
+
return hit ? join47(sessionsDir, String(hit)) : null;
|
|
133128
133738
|
} catch {
|
|
133129
133739
|
return null;
|
|
133130
133740
|
}
|
|
@@ -133602,7 +134212,7 @@ function normalizeNotification(sessionId, n) {
|
|
|
133602
134212
|
* exactly the lie we are trying to fix. The same rule the Claude adapter uses for `result`.
|
|
133603
134213
|
*
|
|
133604
134214
|
* An interrupted turn is not a finished one either (#698). It went out as `turn_complete`, so a turn Codex cut
|
|
133605
|
-
* short told the orchestrator that gave it
|
|
134215
|
+
* short told the orchestrator that gave it that the work was done ("finished", as the report then read). It ends as the person's Stop ends a turn,
|
|
133606
134216
|
* `waiting_input` with `interrupted`; the adapter tells a Stop of ours from an interrupt it did not ask for
|
|
133607
134217
|
* (`INTERRUPTED_BY_TOOL`).
|
|
133608
134218
|
*/
|
|
@@ -135627,7 +136237,7 @@ var CodexSession = class {
|
|
|
135627
136237
|
|
|
135628
136238
|
// packages/agent-host/src/adapters/codex/surface.ts
|
|
135629
136239
|
import { readFileSync as readFileSync18 } from "node:fs";
|
|
135630
|
-
import { join as
|
|
136240
|
+
import { join as join48 } from "node:path";
|
|
135631
136241
|
|
|
135632
136242
|
// packages/agent-host/src/adapters/codex/surface-baseline.ts
|
|
135633
136243
|
var SURFACE_BASELINE2 = {
|
|
@@ -135945,7 +136555,7 @@ var CODEX_SURFACE = {
|
|
|
135945
136555
|
"codex --version": await env.run("codex", ["--version"]),
|
|
135946
136556
|
"codex --help": await env.run("codex", ["--help"])
|
|
135947
136557
|
};
|
|
135948
|
-
for (const f of FILES) inputs[f] = readFileSync18(
|
|
136558
|
+
for (const f of FILES) inputs[f] = readFileSync18(join48(out, f), "utf8");
|
|
135949
136559
|
return inputs;
|
|
135950
136560
|
},
|
|
135951
136561
|
read: readCodexSurface,
|
|
@@ -135954,7 +136564,7 @@ var CODEX_SURFACE = {
|
|
|
135954
136564
|
};
|
|
135955
136565
|
|
|
135956
136566
|
// packages/agent-host/src/adapters/codex/index.ts
|
|
135957
|
-
var exec5 = promisify5(
|
|
136567
|
+
var exec5 = promisify5(execFile7);
|
|
135958
136568
|
var CODEX_VERBOSITIES = ["low", "medium", "high"];
|
|
135959
136569
|
var CodexAdapter = class {
|
|
135960
136570
|
tool = "codex";
|
|
@@ -136013,7 +136623,7 @@ var CodexAdapter = class {
|
|
|
136013
136623
|
const launch2 = launchFor2(path2 ?? toolCommand("codex"));
|
|
136014
136624
|
const { stdout } = await exec5(launch2.command, [...launch2.args, "--version"], { timeout: 5e3, windowsHide: true });
|
|
136015
136625
|
const version2 = `${stdout.trim()} \xB7 ${path2 ?? "PATH"}`;
|
|
136016
|
-
const loggedIn = existsSync21(
|
|
136626
|
+
const loggedIn = existsSync21(join49(codexHome(), "auth.json"));
|
|
136017
136627
|
return {
|
|
136018
136628
|
tool: "codex",
|
|
136019
136629
|
installed: true,
|
|
@@ -136116,12 +136726,12 @@ var CodexAdapter = class {
|
|
|
136116
136726
|
};
|
|
136117
136727
|
|
|
136118
136728
|
// packages/agent-host/src/adapters/cursor-agent/index.ts
|
|
136119
|
-
import { execFile as
|
|
136729
|
+
import { execFile as execFile9 } from "node:child_process";
|
|
136120
136730
|
import { promisify as promisify7 } from "node:util";
|
|
136121
136731
|
|
|
136122
136732
|
// packages/agent-host/src/adapters/cursor-agent/install.ts
|
|
136123
136733
|
import { existsSync as existsSync22, readdirSync as readdirSync14, realpathSync as realpathSync10 } from "node:fs";
|
|
136124
|
-
import { dirname as dirname22, join as
|
|
136734
|
+
import { dirname as dirname22, join as join50, win32 as win327 } from "node:path";
|
|
136125
136735
|
var realFs = {
|
|
136126
136736
|
exists: existsSync22,
|
|
136127
136737
|
list: (dir) => {
|
|
@@ -136173,15 +136783,15 @@ function findCursor(env = process.env, platform, fs3 = realFs) {
|
|
|
136173
136783
|
const real3 = fs3.real(found);
|
|
136174
136784
|
const dir = dirname22(real3);
|
|
136175
136785
|
const version2 = dir.split(/[\\/]/).pop() ?? "";
|
|
136176
|
-
if (VERSION.test(version2) && fs3.exists(
|
|
136786
|
+
if (VERSION.test(version2) && fs3.exists(join50(dir, "index.js"))) return { version: version2, command: real3, args: [], env: {} };
|
|
136177
136787
|
}
|
|
136178
136788
|
return null;
|
|
136179
136789
|
}
|
|
136180
136790
|
|
|
136181
136791
|
// packages/agent-host/src/adapters/cursor-agent/surface.ts
|
|
136182
|
-
import { execFile as
|
|
136792
|
+
import { execFile as execFile8 } from "node:child_process";
|
|
136183
136793
|
import { readdirSync as readdirSync15, readFileSync as readFileSync19 } from "node:fs";
|
|
136184
|
-
import { dirname as dirname23, join as
|
|
136794
|
+
import { dirname as dirname23, join as join51 } from "node:path";
|
|
136185
136795
|
import { promisify as promisify6 } from "node:util";
|
|
136186
136796
|
|
|
136187
136797
|
// packages/agent-host/src/adapters/cursor-agent/surface-baseline.ts
|
|
@@ -136241,7 +136851,7 @@ var SURFACE_BASELINE3 = {
|
|
|
136241
136851
|
};
|
|
136242
136852
|
|
|
136243
136853
|
// packages/agent-host/src/adapters/cursor-agent/surface.ts
|
|
136244
|
-
var exec6 = promisify6(
|
|
136854
|
+
var exec6 = promisify6(execFile8);
|
|
136245
136855
|
async function gather2() {
|
|
136246
136856
|
const install = findCursor();
|
|
136247
136857
|
if (!install) throw new Error("Cursor CLI is not installed on this machine (https://cursor.com/install)");
|
|
@@ -136251,7 +136861,7 @@ async function gather2() {
|
|
|
136251
136861
|
const dir = dirname23(install.args[0] ?? install.command);
|
|
136252
136862
|
const names = /* @__PURE__ */ new Set();
|
|
136253
136863
|
for (const f of readdirSync15(dir).filter((n) => n.endsWith(".js"))) {
|
|
136254
|
-
const text3 = readFileSync19(
|
|
136864
|
+
const text3 = readFileSync19(join51(dir, f), "utf8");
|
|
136255
136865
|
if (!text3.includes("session/request_permission") && !text3.includes("cursor_login")) continue;
|
|
136256
136866
|
for (const m of text3.matchAll(/sessionUpdate:"([a-z_]+)"|"(cursor\/[a-z_]+)"/g)) {
|
|
136257
136867
|
const name = m[1] ?? m[2];
|
|
@@ -136435,7 +137045,7 @@ function cursorUsageReader(run4, now = Date.now) {
|
|
|
136435
137045
|
}
|
|
136436
137046
|
|
|
136437
137047
|
// packages/agent-host/src/adapters/cursor-agent/index.ts
|
|
136438
|
-
var exec7 = promisify7(
|
|
137048
|
+
var exec7 = promisify7(execFile9);
|
|
136439
137049
|
var CAPABILITIES2 = {
|
|
136440
137050
|
...ACP_CAPABILITIES,
|
|
136441
137051
|
contextUsage: "none",
|
|
@@ -136499,24 +137109,24 @@ var CursorAgentAdapter = class extends AcpAdapter {
|
|
|
136499
137109
|
};
|
|
136500
137110
|
|
|
136501
137111
|
// packages/agent-host/src/adapters/opencode/index.ts
|
|
136502
|
-
import { execFile as
|
|
137112
|
+
import { execFile as execFile11 } from "node:child_process";
|
|
136503
137113
|
import { promisify as promisify9 } from "node:util";
|
|
136504
137114
|
|
|
136505
137115
|
// packages/agent-host/src/adapters/opencode/config-files.ts
|
|
136506
137116
|
import { existsSync as existsSync23, readdirSync as readdirSync16, readFileSync as readFileSync20 } from "node:fs";
|
|
136507
137117
|
import { homedir as homedir15 } from "node:os";
|
|
136508
|
-
import { dirname as dirname24, extname as extname3, join as
|
|
137118
|
+
import { dirname as dirname24, extname as extname3, join as join52, relative as relative6 } from "node:path";
|
|
136509
137119
|
function readOpenCodeConfigs(cwd, env, home = homedir15()) {
|
|
136510
|
-
const global2 =
|
|
137120
|
+
const global2 = join52(env.XDG_CONFIG_HOME || join52(home, ".config"), "opencode");
|
|
136511
137121
|
const project2 = !env.OPENCODE_DISABLE_PROJECT_CONFIG;
|
|
136512
137122
|
const upward = project2 ? foldersUp(cwd) : [];
|
|
136513
|
-
const dotFolders = [...upward.map((d2) =>
|
|
137123
|
+
const dotFolders = [...upward.map((d2) => join52(d2, ".opencode")), join52(home, ".opencode"), ...env.OPENCODE_CONFIG_DIR ? [env.OPENCODE_CONFIG_DIR] : []];
|
|
136514
137124
|
const files2 = [
|
|
136515
|
-
...["config.json", "opencode.json", "opencode.jsonc"].map((f) =>
|
|
137125
|
+
...["config.json", "opencode.json", "opencode.jsonc"].map((f) => join52(global2, f)),
|
|
136516
137126
|
...env.OPENCODE_CONFIG ? [env.OPENCODE_CONFIG] : [],
|
|
136517
|
-
...upward.flatMap((d2) => CONFIG_NAMES.map((f) =>
|
|
136518
|
-
...dotFolders.flatMap((d2) => CONFIG_NAMES.map((f) =>
|
|
136519
|
-
...CONFIG_NAMES.map((f) =>
|
|
137127
|
+
...upward.flatMap((d2) => CONFIG_NAMES.map((f) => join52(d2, f))),
|
|
137128
|
+
...dotFolders.flatMap((d2) => CONFIG_NAMES.map((f) => join52(d2, f))),
|
|
137129
|
+
...CONFIG_NAMES.map((f) => join52(managedFolder(env), f))
|
|
136520
137130
|
];
|
|
136521
137131
|
return {
|
|
136522
137132
|
layers: files2.map(readConfig).filter((c) => c !== null),
|
|
@@ -136528,7 +137138,7 @@ function foldersUp(cwd) {
|
|
|
136528
137138
|
const out = [];
|
|
136529
137139
|
for (let d2 = cwd; ; d2 = dirname24(d2)) {
|
|
136530
137140
|
out.push(d2);
|
|
136531
|
-
if (existsSync23(
|
|
137141
|
+
if (existsSync23(join52(d2, ".git")) || dirname24(d2) === d2) return out;
|
|
136532
137142
|
}
|
|
136533
137143
|
}
|
|
136534
137144
|
function managedFolder(env) {
|
|
@@ -136570,13 +137180,13 @@ function stringEnd(text3, start3) {
|
|
|
136570
137180
|
return text3.length;
|
|
136571
137181
|
}
|
|
136572
137182
|
function agentNames(folder) {
|
|
136573
|
-
const named = (sub, deep) => markdown(
|
|
137183
|
+
const named = (sub, deep) => markdown(join52(folder, sub), deep).map((f) => relative6(join52(folder, sub), f).replaceAll("\\", "/").slice(0, -extname3(f).length));
|
|
136574
137184
|
return [...named("agent", true), ...named("agents", true), ...named("mode", false), ...named("modes", false)];
|
|
136575
137185
|
}
|
|
136576
137186
|
function markdown(folder, deep) {
|
|
136577
137187
|
try {
|
|
136578
137188
|
return readdirSync16(folder, { withFileTypes: true }).flatMap((e) => {
|
|
136579
|
-
const path2 =
|
|
137189
|
+
const path2 = join52(folder, e.name);
|
|
136580
137190
|
if (e.isDirectory()) return deep ? markdown(path2, deep) : [];
|
|
136581
137191
|
return e.name.endsWith(".md") ? [path2] : [];
|
|
136582
137192
|
});
|
|
@@ -136705,7 +137315,7 @@ function openCodeRefusal(line, conversation) {
|
|
|
136705
137315
|
}
|
|
136706
137316
|
|
|
136707
137317
|
// packages/agent-host/src/adapters/opencode/surface.ts
|
|
136708
|
-
import { execFile as
|
|
137318
|
+
import { execFile as execFile10 } from "node:child_process";
|
|
136709
137319
|
import { readFileSync as readFileSync21 } from "node:fs";
|
|
136710
137320
|
import { promisify as promisify8 } from "node:util";
|
|
136711
137321
|
|
|
@@ -136762,7 +137372,7 @@ var SURFACE_BASELINE4 = {
|
|
|
136762
137372
|
};
|
|
136763
137373
|
|
|
136764
137374
|
// packages/agent-host/src/adapters/opencode/surface.ts
|
|
136765
|
-
var exec8 = promisify8(
|
|
137375
|
+
var exec8 = promisify8(execFile10);
|
|
136766
137376
|
async function gather3() {
|
|
136767
137377
|
const install = findOpenCode();
|
|
136768
137378
|
if (!install) throw new Error("OpenCode is not installed on this machine (npm i -g opencode-ai)");
|
|
@@ -136905,7 +137515,7 @@ function openCodeUsageReader(run4, now = Date.now) {
|
|
|
136905
137515
|
}
|
|
136906
137516
|
|
|
136907
137517
|
// packages/agent-host/src/adapters/opencode/index.ts
|
|
136908
|
-
var exec9 = promisify9(
|
|
137518
|
+
var exec9 = promisify9(execFile11);
|
|
136909
137519
|
var CAPABILITIES3 = {
|
|
136910
137520
|
...ACP_CAPABILITIES
|
|
136911
137521
|
};
|
|
@@ -137078,7 +137688,7 @@ var ApprovalRules = class {
|
|
|
137078
137688
|
};
|
|
137079
137689
|
|
|
137080
137690
|
// packages/agent-host/src/sessions/project-registry.ts
|
|
137081
|
-
import { basename as
|
|
137691
|
+
import { basename as basename13 } from "node:path";
|
|
137082
137692
|
var ProjectRegistry = class {
|
|
137083
137693
|
constructor(host) {
|
|
137084
137694
|
this.host = host;
|
|
@@ -137132,7 +137742,7 @@ var ProjectRegistry = class {
|
|
|
137132
137742
|
return {
|
|
137133
137743
|
id: id3,
|
|
137134
137744
|
path: path2,
|
|
137135
|
-
name:
|
|
137745
|
+
name: basename13(path2),
|
|
137136
137746
|
defaultTool: this.host.firstTool(stored),
|
|
137137
137747
|
// Trust (M4, decision 3) — reads the same column as the app runtime. The trust toggle on screen shows
|
|
137138
137748
|
// this value
|
|
@@ -137401,11 +138011,11 @@ var SessionLayout = class {
|
|
|
137401
138011
|
// packages/agent-host/src/sessions/linked-reports.ts
|
|
137402
138012
|
var ADD_NEXT_MS = 15e3;
|
|
137403
138013
|
function partOf(turn) {
|
|
137404
|
-
return turn.wrote || turn.added ? { ...turn.wrote ? { wrote: true } : {}, ...turn.added ? { added: true } : {} } : void 0;
|
|
138014
|
+
return turn.wrote || turn.added || turn.cut ? { ...turn.wrote ? { wrote: true } : {}, ...turn.added ? { added: true } : {}, ...turn.cut ? { cut: true } : {} } : void 0;
|
|
137405
138015
|
}
|
|
137406
138016
|
function saidTurn(raw) {
|
|
137407
138017
|
if (typeof raw !== "object" || raw === null || !("by" in raw) || raw.by !== "person" && raw.by !== "work") return void 0;
|
|
137408
|
-
return { by: raw.by, ..."wrote" in raw && raw.wrote === true ? { wrote: true } : {}, ..."added" in raw && raw.added === true ? { added: true } : {} };
|
|
138018
|
+
return { by: raw.by, ..."wrote" in raw && raw.wrote === true ? { wrote: true } : {}, ..."added" in raw && raw.added === true ? { added: true } : {}, ..."cut" in raw && raw.cut === true ? { cut: true } : {} };
|
|
137409
138019
|
}
|
|
137410
138020
|
function fromHub(from) {
|
|
137411
138021
|
const id3 = from?.sessionId;
|
|
@@ -137493,7 +138103,7 @@ var LinkedReports = class {
|
|
|
137493
138103
|
wait.paused = [...held3, ...running2.filter((t) => !held3.some((h) => h.id === t.id))];
|
|
137494
138104
|
wait.busy = false;
|
|
137495
138105
|
this.host.children.setLinkedReport(sessionId, wait);
|
|
137496
|
-
if (!told) void this.report(sessionId, wait.orchestratorId, e, { paused: running2, stoppable: false }, void 0, partOf(turn));
|
|
138106
|
+
if (!told) void this.report(sessionId, wait.orchestratorId, e, { paused: running2, stoppable: false }, void 0, partOf(turn), false, wait);
|
|
137497
138107
|
return;
|
|
137498
138108
|
}
|
|
137499
138109
|
this.drop(sessionId);
|
|
@@ -137688,7 +138298,7 @@ var LinkedReports = class {
|
|
|
137688
138298
|
continue;
|
|
137689
138299
|
}
|
|
137690
138300
|
this.drop(sessionId);
|
|
137691
|
-
void this.report(sessionId, wait.orchestratorId, { type: "turn_complete" }, { linkLost: name, paused: wait.paused });
|
|
138301
|
+
void this.report(sessionId, wait.orchestratorId, { type: "turn_complete" }, { linkLost: name, paused: wait.paused }, void 0, void 0, false, wait);
|
|
137692
138302
|
}
|
|
137693
138303
|
for (const sessionId of [...this.tasks.keys()]) if (sessionId.startsWith(prefix)) this.forget(sessionId);
|
|
137694
138304
|
}
|
|
@@ -137724,23 +138334,25 @@ var LinkedReports = class {
|
|
|
137724
138334
|
if (!sessionId.startsWith(prefix)) continue;
|
|
137725
138335
|
const about = this.about.get(sessionId);
|
|
137726
138336
|
this.drop(sessionId);
|
|
137727
|
-
void this.report(sessionId, wait.orchestratorId, { type: "turn_complete" }, { machineRemoved: name, ...wait.paused ? { paused: wait.paused } : {} }, about);
|
|
138337
|
+
void this.report(sessionId, wait.orchestratorId, { type: "turn_complete" }, { machineRemoved: name, ...wait.paused ? { paused: wait.paused } : {} }, about, void 0, false, wait);
|
|
137728
138338
|
}
|
|
137729
138339
|
for (const sessionId of [...this.tasks.keys()]) if (sessionId.startsWith(prefix)) this.forget(sessionId);
|
|
137730
138340
|
this.machineNames.delete(machineId);
|
|
137731
138341
|
}
|
|
137732
|
-
async report(sessionId, orchestratorId, ending, work, known, part, theirs = false) {
|
|
138342
|
+
async report(sessionId, orchestratorId, ending, work, known, part, theirs = false, wait) {
|
|
137733
138343
|
const reader = this.host.linkedReader;
|
|
137734
138344
|
if (!reader || !this.host.hasSession(orchestratorId)) return;
|
|
137735
138345
|
const about = reader.describe(sessionId) ?? known ?? this.about.get(sessionId) ?? { name: sessionId, project: "(unknown)" };
|
|
137736
138346
|
const unreachable = work && ("linkLost" in work || "machineRemoved" in work);
|
|
138347
|
+
const kept = unreachable && work && "paused" in work && work.paused ? wait?.read : void 0;
|
|
137737
138348
|
if (!unreachable && this.gate(sessionId) !== "deliver") return;
|
|
137738
138349
|
const tookOver = !!work && "tookOver" in work || theirs;
|
|
137739
138350
|
const since = turnSince(ending);
|
|
137740
138351
|
const words = unreachable || tookOver ? null : await reader.lastWords(sessionId, 600, { turn: true, ...since !== void 0 ? { since } : {} }).catch((e) => ({ ok: false, error: e.message }));
|
|
137741
|
-
const preview = theirs && !(work && "tookOver" in work) ? "(not read: the last turn there was the person's; read_session shows it)" : tookOver ? "(not read: the conversation there is the person's now; read_session shows it)" : !words ? work && "paused" in work && work.paused ?
|
|
137742
|
-
|
|
137743
|
-
|
|
138352
|
+
const preview = theirs && !(work && "tookOver" in work) ? "(not read: the last turn there was the person's; read_session shows it)" : tookOver ? "(not read: the conversation there is the person's now; read_session shows it)" : !words ? kept ? clipPreview(kept.preview, 600) : work && "paused" in work && work.paused ? IN_PAUSE_REPORT : MACHINE_UNREACHABLE : words.ok ? words.text === "" ? NO_WORDS : words.text : `(its last response could not be read there: ${words.error})`;
|
|
138353
|
+
if (wait && words?.ok && words.text !== "" && work && "paused" in work && "stoppable" in work) wait.read = { preview: words.text, seq: words.seq ?? void 0 };
|
|
138354
|
+
const seq = words?.ok ? words.seq ?? void 0 : kept?.seq;
|
|
138355
|
+
await this.host.deliverReport(orchestratorId, sessionId, about.name, about.project, preview, ending, kept && unreachable ? { ...work, asRead: true } : work, seq, part);
|
|
137744
138356
|
}
|
|
137745
138357
|
};
|
|
137746
138358
|
|
|
@@ -138627,7 +139239,7 @@ var UsageRecorder = class {
|
|
|
138627
139239
|
}
|
|
138628
139240
|
/** Counts one request in its hour's bucket, in memory until the next flush */
|
|
138629
139241
|
request(src, r, counted, at2 = this.now()) {
|
|
138630
|
-
const hour = Math.floor(at2 /
|
|
139242
|
+
const hour = Math.floor(at2 / HOUR_MS2) * HOUR_MS2;
|
|
138631
139243
|
const projectId = src.projectId ?? "";
|
|
138632
139244
|
const model = r.model || src.model || "";
|
|
138633
139245
|
const id3 = [hour, src.sessionId, projectId, src.tool, model].join("\n");
|
|
@@ -138697,10 +139309,10 @@ var UsageRecorder = class {
|
|
|
138697
139309
|
/** Drops what is older than the keeping time. Once a day at most unless `force` */
|
|
138698
139310
|
prune(force = false) {
|
|
138699
139311
|
const now = this.now();
|
|
138700
|
-
if (!force && now - this.lastPrune < 24 *
|
|
139312
|
+
if (!force && now - this.lastPrune < 24 * HOUR_MS2) return;
|
|
138701
139313
|
this.lastPrune = now;
|
|
138702
139314
|
try {
|
|
138703
|
-
this.usage.prune(now - KEEP_DAYS * 24 *
|
|
139315
|
+
this.usage.prune(now - KEEP_DAYS * 24 * HOUR_MS2);
|
|
138704
139316
|
} catch (err) {
|
|
138705
139317
|
console.error(`[usage] could not prune: ${errorMessage(err)}`);
|
|
138706
139318
|
}
|
|
@@ -138747,7 +139359,7 @@ var UsageRecorder = class {
|
|
|
138747
139359
|
|
|
138748
139360
|
// packages/agent-host/src/sessions/manager.ts
|
|
138749
139361
|
import { homedir as homedir17 } from "node:os";
|
|
138750
|
-
import { join as
|
|
139362
|
+
import { join as join55 } from "node:path";
|
|
138751
139363
|
|
|
138752
139364
|
// packages/agent-host/src/dev-services/store.ts
|
|
138753
139365
|
import Database4 from "better-sqlite3";
|
|
@@ -143445,6 +144057,105 @@ var Store = class extends MigrationRunner {
|
|
|
143445
144057
|
};
|
|
143446
144058
|
var WAL_SIZE_LIMIT = 64 * 1024 * 1024;
|
|
143447
144059
|
|
|
144060
|
+
// packages/agent-host/src/lifecycle/system-end.ts
|
|
144061
|
+
var SYSTEM_END_FILE = "system-end.json";
|
|
144062
|
+
var SystemEndNotice = class {
|
|
144063
|
+
constructor(o) {
|
|
144064
|
+
this.o = o;
|
|
144065
|
+
}
|
|
144066
|
+
o;
|
|
144067
|
+
at = null;
|
|
144068
|
+
/** Which notice an expiry belongs to: a later one is not called off by an earlier one's timer */
|
|
144069
|
+
notices = 0;
|
|
144070
|
+
closed = false;
|
|
144071
|
+
/** The system said it is ending, at `at` */
|
|
144072
|
+
notice(at2 = (this.o.now ?? Date.now)()) {
|
|
144073
|
+
if (this.at !== null) return;
|
|
144074
|
+
this.at = at2;
|
|
144075
|
+
const n = ++this.notices;
|
|
144076
|
+
this.flush();
|
|
144077
|
+
const setTimer = this.o.setTimer ?? ((f, ms3) => void setTimeout(f, ms3).unref());
|
|
144078
|
+
setTimer(() => {
|
|
144079
|
+
if (this.notices !== n || this.at === null) return;
|
|
144080
|
+
this.o.outlived?.();
|
|
144081
|
+
this.cancel();
|
|
144082
|
+
}, SYSTEM_END_TAIL_MS);
|
|
144083
|
+
}
|
|
144084
|
+
/** The end was called off (Windows: another app refused it) */
|
|
144085
|
+
cancel() {
|
|
144086
|
+
if (this.at === null) return;
|
|
144087
|
+
this.at = null;
|
|
144088
|
+
this.flush();
|
|
144089
|
+
}
|
|
144090
|
+
close() {
|
|
144091
|
+
this.closed = true;
|
|
144092
|
+
}
|
|
144093
|
+
flush() {
|
|
144094
|
+
if (!this.closed) this.o.write(this.at === null ? null : { at: this.at });
|
|
144095
|
+
}
|
|
144096
|
+
};
|
|
144097
|
+
function parseSystemEnd(text3) {
|
|
144098
|
+
let r;
|
|
144099
|
+
try {
|
|
144100
|
+
r = JSON.parse(text3);
|
|
144101
|
+
} catch {
|
|
144102
|
+
return null;
|
|
144103
|
+
}
|
|
144104
|
+
if (typeof r !== "object" || r === null || !("v" in r) || r.v !== 1 || !("at" in r) || typeof r.at !== "number") return null;
|
|
144105
|
+
return { at: r.at };
|
|
144106
|
+
}
|
|
144107
|
+
var systemEndText = (end) => `${JSON.stringify({ v: 1, at: end.at })}
|
|
144108
|
+
`;
|
|
144109
|
+
var SYSTEM_END_LEAD_MS = 3e4;
|
|
144110
|
+
var SYSTEM_END_TAIL_MS = 2 * 6e4;
|
|
144111
|
+
var inSystemEnd = (end, ts) => !!end && ts >= end.at - SYSTEM_END_LEAD_MS && ts <= end.at + SYSTEM_END_TAIL_MS;
|
|
144112
|
+
var cutBySystemEnd = (end, lastTs) => !!end && (lastTs === void 0 || lastTs <= end.at + SYSTEM_END_TAIL_MS);
|
|
144113
|
+
function errorCodeOf(payload) {
|
|
144114
|
+
if (typeof payload !== "object" || payload === null || !("type" in payload) || payload.type !== "error") return void 0;
|
|
144115
|
+
const error51 = "error" in payload ? payload.error : void 0;
|
|
144116
|
+
return typeof error51 === "object" && error51 !== null && "code" in error51 && typeof error51.code === "string" ? error51.code : "";
|
|
144117
|
+
}
|
|
144118
|
+
function crashedInSystemEnd(s, rows3, end) {
|
|
144119
|
+
if (s.state !== "error" || !end) return false;
|
|
144120
|
+
const last = rows3.findLast((r) => {
|
|
144121
|
+
const code2 = errorCodeOf(r.payload);
|
|
144122
|
+
return code2 !== void 0 && code2 !== "internal";
|
|
144123
|
+
});
|
|
144124
|
+
return !!last && errorCodeOf(last.payload) === "adapter_crashed" && inSystemEnd(end, last.ts);
|
|
144125
|
+
}
|
|
144126
|
+
function askSystemStopping(o = {}) {
|
|
144127
|
+
const queries = (o.queries === void 0 ? systemStoppingQueries() : o.queries) ?? [];
|
|
144128
|
+
const children = /* @__PURE__ */ new Set();
|
|
144129
|
+
const end = () => {
|
|
144130
|
+
for (const child of children) child.kill("SIGKILL");
|
|
144131
|
+
children.clear();
|
|
144132
|
+
};
|
|
144133
|
+
if (queries.length === 0) return { stopping: null, end };
|
|
144134
|
+
const timeoutMs = o.timeoutMs ?? 1e3;
|
|
144135
|
+
const run4 = o.run ?? ((command, args, options) => realOs().processes.run(command, args, options));
|
|
144136
|
+
const stopping = new Promise((resolve20) => {
|
|
144137
|
+
let left = queries.length;
|
|
144138
|
+
const answered = (yes) => {
|
|
144139
|
+
if (yes) resolve20(true);
|
|
144140
|
+
else if (--left === 0) resolve20(false);
|
|
144141
|
+
};
|
|
144142
|
+
for (const [command, ...args] of queries) {
|
|
144143
|
+
if (command === void 0) {
|
|
144144
|
+
answered(false);
|
|
144145
|
+
continue;
|
|
144146
|
+
}
|
|
144147
|
+
run4(command, args, { timeoutMs, onChild: (child) => void children.add(child) }).then(
|
|
144148
|
+
(r) => answered(String(r.stdout).trim() === "stopping"),
|
|
144149
|
+
() => answered(false)
|
|
144150
|
+
);
|
|
144151
|
+
}
|
|
144152
|
+
setTimeout(() => resolve20(false), timeoutMs).unref();
|
|
144153
|
+
});
|
|
144154
|
+
stopping.finally(end).catch(() => {
|
|
144155
|
+
});
|
|
144156
|
+
return { stopping, end };
|
|
144157
|
+
}
|
|
144158
|
+
|
|
143448
144159
|
// packages/agent-host/src/lifecycle/update-ended.ts
|
|
143449
144160
|
var ENDED_MAX_AGE_MS = 10 * 6e4;
|
|
143450
144161
|
var MAX_ENTRIES2 = 100;
|
|
@@ -143841,8 +144552,8 @@ async function gitBranchPr(projectCwd, branch, signal, timeoutMs = OK2.timeout)
|
|
|
143841
144552
|
}
|
|
143842
144553
|
|
|
143843
144554
|
// packages/agent-host/src/dev-services/file-search.ts
|
|
143844
|
-
import { readdir as
|
|
143845
|
-
import { join as
|
|
144555
|
+
import { readdir as readdir14 } from "node:fs/promises";
|
|
144556
|
+
import { join as join53 } from "node:path";
|
|
143846
144557
|
var MAX_FILES2 = 2e4;
|
|
143847
144558
|
var TTL_MS2 = 15e3;
|
|
143848
144559
|
var WALK_DEPTH = 6;
|
|
@@ -143872,7 +144583,7 @@ async function walk(root3) {
|
|
|
143872
144583
|
const { dir, rel, depth } = next;
|
|
143873
144584
|
let entries;
|
|
143874
144585
|
try {
|
|
143875
|
-
entries = await
|
|
144586
|
+
entries = await readdir14(dir, { withFileTypes: true });
|
|
143876
144587
|
} catch {
|
|
143877
144588
|
continue;
|
|
143878
144589
|
}
|
|
@@ -143880,7 +144591,7 @@ async function walk(root3) {
|
|
|
143880
144591
|
if (e.name.startsWith(".") && e.name !== ".env") continue;
|
|
143881
144592
|
if (e.isDirectory()) {
|
|
143882
144593
|
if (SKIP.has(e.name) || depth >= WALK_DEPTH) continue;
|
|
143883
|
-
queue.push({ dir:
|
|
144594
|
+
queue.push({ dir: join53(dir, e.name), rel: wireJoin(rel, e.name), depth: depth + 1 });
|
|
143884
144595
|
} else {
|
|
143885
144596
|
out.push(wireJoin(rel, e.name).normalize("NFC"));
|
|
143886
144597
|
if (out.length >= MAX_FILES2) break;
|
|
@@ -145065,14 +145776,14 @@ async function readMessageImage(abs, max = IMAGE_PREVIEW_MAX_BYTES) {
|
|
|
145065
145776
|
}
|
|
145066
145777
|
|
|
145067
145778
|
// packages/agent-host/src/dev-services/attachments.ts
|
|
145068
|
-
import { lstat as lstat11, mkdir as
|
|
145069
|
-
import { extname as extname4, join as
|
|
145070
|
-
var root2 = () =>
|
|
145779
|
+
import { lstat as lstat11, mkdir as mkdir9, readdir as readdir15, rm as rm10, writeFile as writeFile6 } from "node:fs/promises";
|
|
145780
|
+
import { extname as extname4, join as join54 } from "node:path";
|
|
145781
|
+
var root2 = () => join54(dataRoot(), "attachments");
|
|
145071
145782
|
function sessionDir(sessionId) {
|
|
145072
145783
|
if (!isSessionId(sessionId)) {
|
|
145073
145784
|
throw rpcError(`Not a session id: ${sessionId}`);
|
|
145074
145785
|
}
|
|
145075
|
-
return
|
|
145786
|
+
return join54(root2(), sessionId);
|
|
145076
145787
|
}
|
|
145077
145788
|
var EXT = {
|
|
145078
145789
|
"image/png": ".png",
|
|
@@ -145082,9 +145793,9 @@ var EXT = {
|
|
|
145082
145793
|
};
|
|
145083
145794
|
async function saveAttachment(sessionId, name, mime, dataBase64) {
|
|
145084
145795
|
const dir = sessionDir(sessionId);
|
|
145085
|
-
await
|
|
145796
|
+
await mkdir9(dir, { recursive: true });
|
|
145086
145797
|
const ext2 = extname4(name) || EXT[mime] || "";
|
|
145087
|
-
const file2 =
|
|
145798
|
+
const file2 = join54(dir, `${Date.now()}-${Math.random().toString(36).slice(2, 8)}${ext2}`);
|
|
145088
145799
|
const buf = Buffer.from(dataBase64, "base64");
|
|
145089
145800
|
await writeFile6(file2, buf);
|
|
145090
145801
|
return { kind: mime.startsWith("image/") ? "image" : "file", path: file2, name, mime, bytes: buf.length };
|
|
@@ -145100,15 +145811,15 @@ async function sweepAttachments(maxBytes = ATTACHMENTS_MAX_BYTES) {
|
|
|
145100
145811
|
const files2 = [];
|
|
145101
145812
|
let dirs;
|
|
145102
145813
|
try {
|
|
145103
|
-
dirs = await
|
|
145814
|
+
dirs = await readdir15(root2());
|
|
145104
145815
|
} catch {
|
|
145105
145816
|
return 0;
|
|
145106
145817
|
}
|
|
145107
145818
|
for (const d2 of dirs) {
|
|
145108
|
-
const dir =
|
|
145109
|
-
const names = await
|
|
145819
|
+
const dir = join54(root2(), d2);
|
|
145820
|
+
const names = await readdir15(dir).catch(() => []);
|
|
145110
145821
|
for (const name of names) {
|
|
145111
|
-
const p =
|
|
145822
|
+
const p = join54(dir, name);
|
|
145112
145823
|
const s = await lstat11(p).catch(() => null);
|
|
145113
145824
|
if (s?.isFile()) files2.push({ path: p, size: s.size, mtime: s.mtimeMs });
|
|
145114
145825
|
}
|
|
@@ -146061,7 +146772,7 @@ async function gitStatusForApp(summary, files2) {
|
|
|
146061
146772
|
}
|
|
146062
146773
|
|
|
146063
146774
|
// packages/agent-host/src/sessions/session-trash.ts
|
|
146064
|
-
import { basename as
|
|
146775
|
+
import { basename as basename14 } from "node:path";
|
|
146065
146776
|
import { rm as rm11 } from "node:fs/promises";
|
|
146066
146777
|
import { existsSync as existsSync26, statSync as statSync16 } from "node:fs";
|
|
146067
146778
|
var SessionTrash = class {
|
|
@@ -146438,7 +147149,7 @@ var SessionTrash = class {
|
|
|
146438
147149
|
this.host.store.projects.addProject({
|
|
146439
147150
|
id: record2.projectId,
|
|
146440
147151
|
path: record2.projectPath,
|
|
146441
|
-
name: record2.projectName ??
|
|
147152
|
+
name: record2.projectName ?? basename14(record2.projectPath)
|
|
146442
147153
|
});
|
|
146443
147154
|
projectId = record2.projectId;
|
|
146444
147155
|
registered = await this.host.projectInfo(projectId, record2.projectPath);
|
|
@@ -146811,7 +147522,7 @@ function stands(o, proto) {
|
|
|
146811
147522
|
return Object.getPrototypeOf(o) === proto;
|
|
146812
147523
|
}
|
|
146813
147524
|
var SessionManager = class {
|
|
146814
|
-
constructor(store2, adapters2, emit, endpoint, worktreeRoot =
|
|
147525
|
+
constructor(store2, adapters2, emit, endpoint, worktreeRoot = join55(homedir17(), DATA_DIR, "worktrees"), keeperOpts = {}) {
|
|
146815
147526
|
this.store = store2;
|
|
146816
147527
|
this.adapters = adapters2;
|
|
146817
147528
|
this.emit = emit;
|
|
@@ -146943,16 +147654,21 @@ var SessionManager = class {
|
|
|
146943
147654
|
const cut3 = turnCut(s) && !keeperOpts.keptSessions?.has(s.id);
|
|
146944
147655
|
const goesOn = cut3 && !!lists?.cut(s.id, s.lastSeq);
|
|
146945
147656
|
const stale = cut3 && !goesOn;
|
|
146946
|
-
const
|
|
147657
|
+
const end = keeperOpts.systemEnd;
|
|
147658
|
+
const recent = end && (stale || s.state === "error") ? store2.messages.loadMessages(s.id, 20) : [];
|
|
147659
|
+
const crashedAtEnd = !cut3 && crashedInSystemEnd(s, recent, end);
|
|
147660
|
+
const lost = stale || crashedAtEnd;
|
|
147661
|
+
const bySystem = crashedAtEnd || stale && cutBySystemEnd(end, recent.at(-1)?.ts);
|
|
147662
|
+
const fixed = lost ? { ...s, state: "idle", waitingSince: null } : goesOn ? { ...s, state: "working", waitingSince: null } : s;
|
|
146947
147663
|
this.meta.set(s.id, withHubMark(store2.settings, fixed));
|
|
146948
|
-
if (
|
|
146949
|
-
console.error(`[agent-host] stale state reset: ${s.id.slice(0, 8)} ${s.state} -> idle`);
|
|
146950
|
-
this.interruptions.
|
|
147664
|
+
if (lost) {
|
|
147665
|
+
console.error(`[agent-host] stale state reset: ${s.id.slice(0, 8)} ${s.state} -> idle${bySystem ? " (the system was ending)" : ""}`);
|
|
147666
|
+
this.interruptions.markTurnLost(fixed, false, bySystem ? "shutdown" : "quit");
|
|
146951
147667
|
store2.sessions.upsertSession(fixed);
|
|
146952
147668
|
cutTurns2.set(s.id, this.children.processGone(s.id));
|
|
146953
147669
|
stoppedByQuit.push(fixed);
|
|
146954
147670
|
}
|
|
146955
|
-
const stopped =
|
|
147671
|
+
const stopped = lost ? null : lists?.stoppedOf(s.id, s.lastSeq) ?? null;
|
|
146956
147672
|
if (stopped) stoppedBy.set(s.id, stopped);
|
|
146957
147673
|
if (goesOn) {
|
|
146958
147674
|
console.error(`[agent-host] ${s.id.slice(0, 8)}: an update of this machine ended its turn; continuing it`);
|
|
@@ -146974,8 +147690,8 @@ var SessionManager = class {
|
|
|
146974
147690
|
}
|
|
146975
147691
|
const cutIds = new Set(cutByUpdate.map((s) => s.id));
|
|
146976
147692
|
this.updateCuts = [
|
|
146977
|
-
...cutByUpdate.map((s) => ({ sessionId: s.id,
|
|
146978
|
-
...[...stoppedBy].filter(([id3]) => !cutIds.has(id3)).map(([id3, stopped]) => ({ sessionId: id3,
|
|
147693
|
+
...cutByUpdate.map((s) => ({ sessionId: s.id, turn: true, stopped: stoppedBy.get(s.id) ?? null })),
|
|
147694
|
+
...[...stoppedBy].filter(([id3]) => !cutIds.has(id3)).map(([id3, stopped]) => ({ sessionId: id3, turn: false, stopped }))
|
|
146979
147695
|
];
|
|
146980
147696
|
this.cardRests.restore((id3) => !!keeperOpts.keptSessions?.has(id3) && this.meta.get(id3)?.state === "waiting_approval");
|
|
146981
147697
|
for (const m of this.children.restoreReports(this.meta, goingOn)) this.quitNotices.push({ sessionId: m.sessionId, requester: m.callerSessionId, swapped: true });
|
|
@@ -147167,8 +147883,8 @@ var SessionManager = class {
|
|
|
147167
147883
|
/** Sessions this start found mid-turn with no process: the person is told of the ones whose work is theirs (§8.1), once the mailbox is wired */
|
|
147168
147884
|
quitPersonWork = [];
|
|
147169
147885
|
/**
|
|
147170
|
-
* Sessions an update's stop left, to tell once the start is done (#1618, `continueCutTurns`): a turn it cut is continued (`turn
|
|
147171
|
-
*
|
|
147886
|
+
* Sessions an update's stop left, to tell once the start is done (#1618, `continueCutTurns`): a turn it cut is continued (`turn`; its end
|
|
147887
|
+
* says so, #1966); a session at rest whose tasks it stopped is woken for the note alone (#1902). `stopped`: what else it ended there
|
|
147172
147888
|
*/
|
|
147173
147889
|
updateCuts = [];
|
|
147174
147890
|
/**
|
|
@@ -147584,7 +148300,7 @@ var SessionManager = class {
|
|
|
147584
148300
|
turnLostInSwap(m) {
|
|
147585
148301
|
const change = { type: "state_change", sessionId: m.id, state: "idle", reason: "reattach_failed" };
|
|
147586
148302
|
this.events.applyStateHint(change, m);
|
|
147587
|
-
this.interruptions.
|
|
148303
|
+
this.interruptions.markTurnLost(m, true);
|
|
147588
148304
|
this.store.sessions.upsertSession(m);
|
|
147589
148305
|
this.emit(change);
|
|
147590
148306
|
const turnBy = this.children.turnEnded(m.id, null, true);
|
|
@@ -147612,14 +148328,16 @@ var SessionManager = class {
|
|
|
147612
148328
|
}
|
|
147613
148329
|
m.agentVersion = k2.version ?? null;
|
|
147614
148330
|
const r = await this.resumeSession(k2.sessionId, k2.process).catch((err) => ({ resumed: false, reason: err.message }));
|
|
147615
|
-
|
|
148331
|
+
let gone = Promise.resolve();
|
|
148332
|
+
if (!r.resumed && !this.lettingGo()) gone = this.endKept(k2.process);
|
|
147616
148333
|
const adopted = r.resumed ? this.handles.get(k2.sessionId) : void 0;
|
|
147617
|
-
if (adopted?.takesOverTurn === false && inTurn(m.state)) this.
|
|
148334
|
+
if (adopted?.takesOverTurn === false && inTurn(m.state)) await this.continueLostTurn(m);
|
|
147618
148335
|
if (this.children.queued.has(k2.sessionId)) this.queue.scheduleDrain(k2.sessionId);
|
|
147619
148336
|
if (!r.resumed) {
|
|
147620
148337
|
console.error(`[agent-host] could not re-attach ${k2.sessionId.slice(0, 8)}: ${r.reason ?? "unknown"}`);
|
|
147621
148338
|
if (inTurn(m.state)) {
|
|
147622
|
-
|
|
148339
|
+
await gone;
|
|
148340
|
+
await this.continueLostTurn(m);
|
|
147623
148341
|
} else if (this.children.pausedReports.has(m.id)) {
|
|
147624
148342
|
void this.reports.reportBackIfAwaited(m.id, { type: "state_change", sessionId: m.id, state: "idle" });
|
|
147625
148343
|
}
|
|
@@ -147634,9 +148352,8 @@ var SessionManager = class {
|
|
|
147634
148352
|
* Continues each turn a linked hub's update cut (#1618, owner 2026-10-26): the update no longer waits for the turns on a
|
|
147635
148353
|
* machine, so the host it starts takes them up again, once. The session was left `working` by the start; it is woken
|
|
147636
148354
|
* and given Centralu's note through Centralu's own channel (`deliverMail`: no message of the person's, so the turn stays
|
|
147637
|
-
* whose it was and its report, if one is owed, is still that turn's end). Its requester
|
|
147638
|
-
*
|
|
147639
|
-
* (`tellAskerOfInterruption`). A session that cannot be continued (its tool cannot resume it, it is gone there) ends
|
|
148355
|
+
* whose it was and its report, if one is owed, is still that turn's end). Its requester is told nothing of the cut: the
|
|
148356
|
+
* report of the continued turn's end says so (#1966). A session that cannot be continued (its tool cannot resume it, it is gone there) ends
|
|
147640
148357
|
* as a kept process that could not be re-attached does (`turnLostInSwap`): idle, marked stopped, its report or its
|
|
147641
148358
|
* requester told. Each is taken once: the list is emptied first, and its id leaves the file once its Continue was sent
|
|
147642
148359
|
* or its loss reported (`UpdateResumeLists.done`), not before, so a host stopped in between leaves it for the next.
|
|
@@ -147649,9 +148366,8 @@ var SessionManager = class {
|
|
|
147649
148366
|
async continueCutTurns() {
|
|
147650
148367
|
const cuts = this.updateCuts;
|
|
147651
148368
|
this.updateCuts = [];
|
|
147652
|
-
const continued = new Set(cuts.filter((c) => c.turn).map((c) => c.sessionId));
|
|
147653
148369
|
await Promise.all(
|
|
147654
|
-
cuts.map(async ({ sessionId,
|
|
148370
|
+
cuts.map(async ({ sessionId, turn, stopped }) => {
|
|
147655
148371
|
const m = this.meta.get(sessionId);
|
|
147656
148372
|
if (this.disposing || this.handingOver) return;
|
|
147657
148373
|
if (!m || turn && !inTurn(m.state)) {
|
|
@@ -147660,13 +148376,11 @@ var SessionManager = class {
|
|
|
147660
148376
|
this.updateLists?.done(sessionId);
|
|
147661
148377
|
return;
|
|
147662
148378
|
}
|
|
148379
|
+
if (turn) this.children.noteContinuedCut(sessionId);
|
|
147663
148380
|
try {
|
|
147664
|
-
await this.
|
|
147665
|
-
sessionId,
|
|
147666
|
-
{ text: updateNote(turn, stopped), quiet: false, line: turn ? UPDATE_CONTINUE_LINE : UPDATE_TASKS_LINE, label: "update", skipLine: () => this.disposing && this.stoppingForUpdate },
|
|
147667
|
-
{ leaving: () => this.handingOver || this.disposing, atRest: () => true }
|
|
147668
|
-
);
|
|
148381
|
+
await this.sendUpdateNote(sessionId, turn, stopped);
|
|
147669
148382
|
} catch (e) {
|
|
148383
|
+
this.children.dropContinuedCut(sessionId);
|
|
147670
148384
|
if (this.disposing || this.handingOver || this.meta.get(sessionId) !== m) return;
|
|
147671
148385
|
this.restartPolicy.continuingTurns.delete(sessionId);
|
|
147672
148386
|
if (!turn) {
|
|
@@ -147683,11 +148397,50 @@ var SessionManager = class {
|
|
|
147683
148397
|
if (this.disposing || this.handingOver) return;
|
|
147684
148398
|
this.restartPolicy.continuingTurns.delete(sessionId);
|
|
147685
148399
|
this.updateLists?.done(sessionId);
|
|
147686
|
-
if (requester && !continued.has(requester)) void this.reports.tellCutByUpdate(sessionId, requester);
|
|
147687
148400
|
})
|
|
147688
148401
|
);
|
|
147689
148402
|
this.tellQuitNotices(() => true);
|
|
147690
148403
|
}
|
|
148404
|
+
/**
|
|
148405
|
+
* Centralu's note to the agent after an update or a swap stopped it (#1618, #1966): the Continue of a cut turn, or the tasks the stop ended
|
|
148406
|
+
* for a session at rest. The one door of the update's continue and of the swap's (`adoptKept`). Not sent once this host is leaving (a second
|
|
148407
|
+
* update's stop while the session wakes): the host after it sends the one
|
|
148408
|
+
*/
|
|
148409
|
+
sendUpdateNote(sessionId, turn, stopped) {
|
|
148410
|
+
return this.events.deliverMail(
|
|
148411
|
+
sessionId,
|
|
148412
|
+
{ text: updateNote(turn, stopped), quiet: false, line: turn ? UPDATE_CONTINUE_LINE : UPDATE_TASKS_LINE, label: "update", skipLine: () => this.disposing && this.stoppingForUpdate },
|
|
148413
|
+
{ leaving: () => this.handingOver || this.disposing, atRest: () => true }
|
|
148414
|
+
);
|
|
148415
|
+
}
|
|
148416
|
+
/**
|
|
148417
|
+
* A turn a host swap cut that its tool cannot take up again (ACP reopens the conversation in a new process; a process that could not be
|
|
148418
|
+
* re-attached): continued like one an update cut, with the same Continue (#1966). Only a turn Centralu itself cut. One that cannot be
|
|
148419
|
+
* continued ends as it did: stopped, its report or its requester told (`turnLostInSwap`)
|
|
148420
|
+
*/
|
|
148421
|
+
async continueLostTurn(m) {
|
|
148422
|
+
this.restartPolicy.continuingTurns.add(m.id);
|
|
148423
|
+
this.children.noteContinuedCut(m.id);
|
|
148424
|
+
try {
|
|
148425
|
+
await this.sendUpdateNote(m.id, true, null);
|
|
148426
|
+
} catch (e) {
|
|
148427
|
+
this.children.dropContinuedCut(m.id);
|
|
148428
|
+
if (this.disposing || this.handingOver || this.meta.get(m.id) !== m) return;
|
|
148429
|
+
console.error(`[agent-host] ${m.id.slice(0, 8)}: the turn a swap ended could not be continued (${errorMessage(e)})`);
|
|
148430
|
+
this.turnLostInSwap(m);
|
|
148431
|
+
} finally {
|
|
148432
|
+
this.restartPolicy.continuingTurns.delete(m.id);
|
|
148433
|
+
}
|
|
148434
|
+
}
|
|
148435
|
+
/** Stops a kept process nobody can address; resolves when it has exited, or after a bound (then it is killed) */
|
|
148436
|
+
endKept(p) {
|
|
148437
|
+
if (p.exitCode !== null || p.signalCode) return Promise.resolve();
|
|
148438
|
+
const exited = new Promise((r) => p.once("exit", () => r(true)));
|
|
148439
|
+
p.kill("SIGTERM");
|
|
148440
|
+
return raceTimeout(exited, this.keeperOpts.exitWaitMs ?? 5e3, false).then((done2) => {
|
|
148441
|
+
if (!done2) p.kill("SIGKILL");
|
|
148442
|
+
});
|
|
148443
|
+
}
|
|
147691
148444
|
/** A turn the start took up that is not continued after all: the hello stops naming it (#1618) */
|
|
147692
148445
|
notContinued(sessionId) {
|
|
147693
148446
|
this.continuedAfterUpdate = this.continuedAfterUpdate.filter((c) => c.sessionId !== sessionId);
|
|
@@ -148910,7 +149663,7 @@ var MailPerson = class {
|
|
|
148910
149663
|
/** A chain with its messages, which the person reads: what is returned is read for them. An old thread's id names the chain moved from it */
|
|
148911
149664
|
thread(threadId, opts = {}) {
|
|
148912
149665
|
const root3 = this.rootsOf(threadId)[0];
|
|
148913
|
-
if (root3 === void 0) throw
|
|
149666
|
+
if (root3 === void 0) throw refuse4("unknown_message", `There is no mail ${threadId} kept.`);
|
|
148914
149667
|
const all = this.shown(root3);
|
|
148915
149668
|
let page;
|
|
148916
149669
|
if (opts.after !== void 0) page = all.filter((s) => s.seq > (opts.after ?? 0)).slice(0, opts.all ? void 0 : THREAD_PAGE);
|
|
@@ -148952,7 +149705,7 @@ var MailPerson = class {
|
|
|
148952
149705
|
const theirs = open6 ?? toPerson.at(-1);
|
|
148953
149706
|
const mine = chain.findLast((m) => m.sender === PERSON && this.records.recipients(m.id).some((r) => this.here(r.recipient)));
|
|
148954
149707
|
const target = theirs ?? mine;
|
|
148955
|
-
if (!target) throw
|
|
149708
|
+
if (!target) throw refuse4("unknown_message", `There is no message in ${threadId} to answer.`);
|
|
148956
149709
|
const to = target === theirs ? [target.sender] : this.records.recipients(target.id).flatMap((r) => this.here(r.recipient) ? [r.recipient] : []);
|
|
148957
149710
|
const sent = this.service.send(PERSON, { to, text: text3, replyTo: target.id, needsReply: kind === "question" });
|
|
148958
149711
|
const at2 = this.shown(target.rootId).find((s) => s.m.id === sent.message.id)?.seq ?? 1;
|
|
@@ -148972,7 +149725,7 @@ var MailPerson = class {
|
|
|
148972
149725
|
close(threadId, outcome = "finished") {
|
|
148973
149726
|
const roots = this.rootsOf(threadId);
|
|
148974
149727
|
const root3 = roots[0];
|
|
148975
|
-
if (root3 === void 0) throw
|
|
149728
|
+
if (root3 === void 0) throw refuse4("unknown_message", `There is no mail ${threadId} kept.`);
|
|
148976
149729
|
const now = Date.now();
|
|
148977
149730
|
for (const r of roots) {
|
|
148978
149731
|
const chain = this.chainOf(r);
|
|
@@ -149010,7 +149763,7 @@ var MailPerson = class {
|
|
|
149010
149763
|
if (action === "dismiss") return { changed: this.service.dismissHeld(recipient, messageIds, PERSON_HOLDS) };
|
|
149011
149764
|
const projects = new Set(messageIds.flatMap((id3) => this.records.recipient(id3, recipient)?.held === "policy" ? this.records.message(id3)?.senderProject ?? [] : []));
|
|
149012
149765
|
const still = [...projects].find((p) => this.host.untrusted(p));
|
|
149013
|
-
if (still !== void 0) throw
|
|
149766
|
+
if (still !== void 0) throw refuse4("untrusted_project", `${this.host.projectLabel(still)} is not trusted: what its sessions send out of it waits until you trust the project, and then goes in order.`);
|
|
149014
149767
|
const policy = messageIds.filter((id3) => this.records.recipient(id3, recipient)?.held === "policy");
|
|
149015
149768
|
for (const p of projects) this.trusted(p);
|
|
149016
149769
|
const letGo = policy.filter((id3) => this.records.recipient(id3, recipient)?.held !== "policy").length;
|
|
@@ -149244,151 +149997,6 @@ function askedBy(host, turns, orchestratorId) {
|
|
|
149244
149997
|
return { messageId: turn.messageId, sessionId: asker.sessionId, turn: turn.turnSeq, ...askerFacts(host, asker.sessionId), cites: host.cites(turn.messageId) };
|
|
149245
149998
|
}
|
|
149246
149999
|
|
|
149247
|
-
// packages/agent-host/src/mail/delegation.ts
|
|
149248
|
-
function delegationRecorder(mail, turns) {
|
|
149249
|
-
return (callerId, childId, origin, task) => {
|
|
149250
|
-
const written = mail.task(mail.sessionAddress(callerId), mail.sessionAddress(childId), { text: task, origin: delegationOrigin(origin), delivered: true });
|
|
149251
|
-
turns?.attach(childId, written.id, callerId);
|
|
149252
|
-
};
|
|
149253
|
-
}
|
|
149254
|
-
function delegationOrigin(origin) {
|
|
149255
|
-
return origin === "start_agent" || origin === "agent_session" || origin === "send_to_session" || origin === "create_session" || origin === "ask_project" ? origin : "send_to_session";
|
|
149256
|
-
}
|
|
149257
|
-
function personWorkRecorder(mail) {
|
|
149258
|
-
return (sessionId, text3, fromScript) => {
|
|
149259
|
-
mail.task(PERSON, mail.sessionAddress(sessionId), { text: text3, origin: fromScript ? "script" : "create_session", delivered: true });
|
|
149260
|
-
};
|
|
149261
|
-
}
|
|
149262
|
-
function personOwes(mail, records) {
|
|
149263
|
-
return (sessionId) => records.newestTask(PERSON, mail.sessionAddress(sessionId)) !== void 0;
|
|
149264
|
-
}
|
|
149265
|
-
function personReportRecorder(mail, records, waitingOn = () => 0) {
|
|
149266
|
-
return (sessionId, draft) => {
|
|
149267
|
-
const task = records.newestTask(PERSON, mail.sessionAddress(sessionId));
|
|
149268
|
-
if (!task) return false;
|
|
149269
|
-
const owed = draft.then === "review" && draft.outcome === "finished" ? waitingOn(sessionId) : 0;
|
|
149270
|
-
try {
|
|
149271
|
-
mail.report(task.id, turnReport(owed > 0 ? { ...draft, outcome: "paused", then: "running" } : draft, owed));
|
|
149272
|
-
return true;
|
|
149273
|
-
} catch (e) {
|
|
149274
|
-
if (refusalReason(e) !== void 0) return false;
|
|
149275
|
-
throw e;
|
|
149276
|
-
}
|
|
149277
|
-
};
|
|
149278
|
-
}
|
|
149279
|
-
var ANSWER_KEPT = 600;
|
|
149280
|
-
function answerRecorder(mail, records, turns, waitingOn = () => 0) {
|
|
149281
|
-
return (callerId, childId, how) => {
|
|
149282
|
-
const owner = mail.sessionAddress(callerId);
|
|
149283
|
-
const child = mail.sessionAddress(childId);
|
|
149284
|
-
const now = Date.now();
|
|
149285
|
-
const reports = records.unreadFor(owner).filter((p) => p.message.kind === "report" && reportOnTaskTo(records, p.message, owner, child)).map((p) => p.message.id);
|
|
149286
|
-
records.markDelivered(owner, reports, now, null);
|
|
149287
|
-
records.markRead(owner, reports, now);
|
|
149288
|
-
const task = records.newestTask(owner, child);
|
|
149289
|
-
if (!how || !task) return;
|
|
149290
|
-
const read = `read_session(sessionId="${childId}")`;
|
|
149291
|
-
const waiting = how.ok ? waitingOn(callerId, childId) : 0;
|
|
149292
|
-
const startedAt = turns?.latest(childId)?.startedAt;
|
|
149293
|
-
const written = mail.report(task.id, {
|
|
149294
|
-
...startedAt !== void 0 ? { startedAt } : {},
|
|
149295
|
-
...waiting > 0 ? { outcome: "waiting", waitingOn: waiting } : { outcome: how.ok ? "finished" : "failed" },
|
|
149296
|
-
body: how.ok ? `[Centralu] The assigned work finished.
|
|
149297
|
-
id: ${childId}
|
|
149298
|
-
|
|
149299
|
-
Its last response is that session's own words, so it is not repeated here: ${read} reads it.` : `[Centralu] The assigned work did not finish.
|
|
149300
|
-
id: ${childId}
|
|
149301
|
-
|
|
149302
|
-
Its tool's error, by kind: ${how.error}. Its words are its tool's, not Centralu's, so they are not repeated here: ${read} shows them.`,
|
|
149303
|
-
...how.ok ? { answer: how.text.length > ANSWER_KEPT ? `${how.text.slice(0, ANSWER_KEPT)}\u2026` : how.text } : { error: how.error }
|
|
149304
|
-
});
|
|
149305
|
-
if ("message" in written) {
|
|
149306
|
-
records.markDelivered(owner, [written.message.id], now, null);
|
|
149307
|
-
records.markRead(owner, [written.message.id], now);
|
|
149308
|
-
}
|
|
149309
|
-
};
|
|
149310
|
-
}
|
|
149311
|
-
function reportOnTaskTo(records, report, owner, child) {
|
|
149312
|
-
const task = report.replyTo === null ? void 0 : records.message(report.replyTo);
|
|
149313
|
-
return task?.kind === "task" && task.sender === owner && records.recipient(task.id, child) !== void 0;
|
|
149314
|
-
}
|
|
149315
|
-
function turnRecorder(mail, records, turns) {
|
|
149316
|
-
return (sessionId, turn) => {
|
|
149317
|
-
const child = mail.sessionAddress(sessionId);
|
|
149318
|
-
const taskId = turn.ownerId && (turn.by === "work" || turn.added) ? records.newestTask(mail.sessionAddress(turn.ownerId), child)?.id ?? null : null;
|
|
149319
|
-
const personWork = () => turn.ownerId ? null : records.newestTask(PERSON, child)?.id ?? null;
|
|
149320
|
-
const record2 = (by, messageId, owner) => turn.again ? turns.restate(sessionId, by, messageId, owner) : turns.beginOther(sessionId, by, messageId, owner);
|
|
149321
|
-
if (turn.by === "person") record2("person", null);
|
|
149322
|
-
else if (turn.added) record2("person", taskId, turn.ownerId);
|
|
149323
|
-
else if (turn.source === "report") record2("report", null);
|
|
149324
|
-
else if (turn.source === "app") record2("app", null);
|
|
149325
|
-
else {
|
|
149326
|
-
const person = taskId === null ? personWork() : null;
|
|
149327
|
-
record2(turn.cli && person === null ? "cli" : "caller", taskId ?? person, turn.ownerId);
|
|
149328
|
-
}
|
|
149329
|
-
};
|
|
149330
|
-
}
|
|
149331
|
-
function outcomeOf2(draft) {
|
|
149332
|
-
if (draft.then === "dropped") return { outcome: "stopped", reason: "taken_over" };
|
|
149333
|
-
const word = draft.outcome;
|
|
149334
|
-
const outcome = word === "finished" || word === "settled" ? "finished" : word === "failed" || word === "limit_reached" ? "failed" : "stopped";
|
|
149335
|
-
return { outcome, ...word !== outcome ? { reason: word } : {} };
|
|
149336
|
-
}
|
|
149337
|
-
function turnReport(draft, waitingOn) {
|
|
149338
|
-
const base2 = { body: draft.body, ...draft.answer ? { answer: draft.answer } : {}, ...draft.error ? { error: draft.error } : {}, ...draft.personNote ? { personNote: true } : {} };
|
|
149339
|
-
if (draft.then === "running" && draft.paused) return { ...base2, outcome: "paused", paused: draft.paused };
|
|
149340
|
-
if (draft.then === "running") return { ...base2, outcome: "waiting", waitingOn: Math.max(1, waitingOn) };
|
|
149341
|
-
return { ...base2, ...outcomeOf2(draft) };
|
|
149342
|
-
}
|
|
149343
|
-
function askedReportBody(draft) {
|
|
149344
|
-
return draft.outcome === "finished" ? "[Centralu] The orchestrator ended its turn on your request. What it sent you in reply is its answer; if it sent nothing, it had none to give. Its other words in that turn are not passed on." : `[Centralu] The orchestrator's turn on your request did not finish (${draft.outcome}). What it sent you in reply, if anything, has reached you. Ask again, or ask the person.`;
|
|
149345
|
-
}
|
|
149346
|
-
function reportedWork(records, turns, owner, child, childId) {
|
|
149347
|
-
const turnWork = turns.latest(childId)?.messageId;
|
|
149348
|
-
const work = turnWork == null ? void 0 : records.message(turnWork);
|
|
149349
|
-
if (work && work.sender === owner && records.recipient(work.id, child)) {
|
|
149350
|
-
if (work.kind === "task") return { work, request: false };
|
|
149351
|
-
if (work.kind === "message") return { work, request: true };
|
|
149352
|
-
}
|
|
149353
|
-
const task = records.newestTask(owner, child);
|
|
149354
|
-
return task && { work: task, request: false };
|
|
149355
|
-
}
|
|
149356
|
-
function reportRecorder(mail, records, turns, waitingOn = () => 0) {
|
|
149357
|
-
return (ownerId, childId, draft) => {
|
|
149358
|
-
const owner = mail.sessionAddress(ownerId);
|
|
149359
|
-
const child = mail.sessionAddress(childId);
|
|
149360
|
-
const found = reportedWork(records, turns, owner, child, childId);
|
|
149361
|
-
if (!found) return "none";
|
|
149362
|
-
if (draft.archiving) return "closed";
|
|
149363
|
-
if (draft.outcome === "resumed") return "none";
|
|
149364
|
-
const { work, request } = found;
|
|
149365
|
-
let written;
|
|
149366
|
-
try {
|
|
149367
|
-
const { answer: _answer, ...unanswered2 } = draft;
|
|
149368
|
-
const startedAt = turns.latest(childId)?.startedAt;
|
|
149369
|
-
const report = request ? { ...turnReport(unanswered2, 0), body: askedReportBody(draft) } : turnReport(draft, waitingOn(ownerId, childId));
|
|
149370
|
-
written = mail.report(work.id, startedAt === void 0 ? report : { ...report, startedAt });
|
|
149371
|
-
} catch (e) {
|
|
149372
|
-
if (refusalReason(e) !== void 0) return "none";
|
|
149373
|
-
throw e;
|
|
149374
|
-
}
|
|
149375
|
-
const seq = turns.latest(childId)?.seq;
|
|
149376
|
-
if (request && seq !== void 0 && draft.then !== "running") mail.requestTurnEnded(child, owner, seq);
|
|
149377
|
-
if ("settledAlready" in written || written.duplicate) return "closed";
|
|
149378
|
-
if (draft.delivery === "wake") return "mailed";
|
|
149379
|
-
if (!written.first && !draft.ownerInTurn) return "closed";
|
|
149380
|
-
const reportId = written.message.id;
|
|
149381
|
-
return {
|
|
149382
|
-
keep: true,
|
|
149383
|
-
settle: () => {
|
|
149384
|
-
const now = Date.now();
|
|
149385
|
-
records.markDelivered(owner, [reportId], now, null);
|
|
149386
|
-
records.markRead(owner, [reportId], now);
|
|
149387
|
-
}
|
|
149388
|
-
};
|
|
149389
|
-
};
|
|
149390
|
-
}
|
|
149391
|
-
|
|
149392
150000
|
// packages/agent-host/src/mail/messages.ts
|
|
149393
150001
|
import { createHash as createHash13 } from "node:crypto";
|
|
149394
150002
|
var CITE_MS = 30 * 6e4;
|
|
@@ -149446,18 +150054,18 @@ var MessageService = class {
|
|
|
149446
150054
|
*/
|
|
149447
150055
|
send(sender, input) {
|
|
149448
150056
|
const from = parseAddress(sender);
|
|
149449
|
-
if (!from || from.kind === "centralu") throw
|
|
150057
|
+
if (!from || from.kind === "centralu") throw refuse4("not_allowed", "Centralu sends with notice and report");
|
|
149450
150058
|
const cls = from.kind;
|
|
149451
150059
|
const text3 = this.checkText(cls, input.text);
|
|
149452
|
-
if (input.urgent === true && cls !== "session" && cls !== "person") throw
|
|
150060
|
+
if (input.urgent === true && cls !== "session" && cls !== "person") throw refuse4("field_not_allowed", "only a session or the person marks mail urgent");
|
|
149453
150061
|
const target = input.replyTo === void 0 ? void 0 : this.readable(sender, input.replyTo);
|
|
149454
150062
|
let to = [...new Set(input.to)];
|
|
149455
150063
|
if (to.length === 0) {
|
|
149456
|
-
if (!target) throw
|
|
149457
|
-
if (target.sender === sender) throw
|
|
150064
|
+
if (!target) throw refuse4("bad_value", "a message needs at least one recipient");
|
|
150065
|
+
if (target.sender === sender) throw refuse4("own_message", REFUSAL_TEXT.own_message(target.id));
|
|
149458
150066
|
to = [target.sender];
|
|
149459
150067
|
}
|
|
149460
|
-
if (to.length > MAX_RECIPIENTS) throw
|
|
150068
|
+
if (to.length > MAX_RECIPIENTS) throw refuse4("recipients", REFUSAL_TEXT.recipients());
|
|
149461
150069
|
const causeId = this.causeOf(sender);
|
|
149462
150070
|
const cause2 = causeId === null ? null : this.records.message(causeId) ?? null;
|
|
149463
150071
|
const clarifies = cls === "session" && cause2?.kind === "task" && to.length === 1 && to[0] === cause2.sender ? cause2.id : void 0;
|
|
@@ -149466,10 +150074,10 @@ var MessageService = class {
|
|
|
149466
150074
|
const requests2 = to.map((r) => this.isRequest({ sender, kind: "message", replyTo: target?.id ?? null, facts: { clarifies } }, r));
|
|
149467
150075
|
for (const [i, r] of to.entries()) {
|
|
149468
150076
|
const party = parseAddress(r);
|
|
149469
|
-
if (!party) throw
|
|
149470
|
-
if (r === sender) throw
|
|
150077
|
+
if (!party) throw refuse4("bad_address", `Not a mail address: ${JSON.stringify(r)}`);
|
|
150078
|
+
if (r === sender) throw refuse4("bad_value", "a message is for someone else");
|
|
149471
150079
|
const why2 = this.reach.mayWrite(sender, r, requests2[i]);
|
|
149472
|
-
if (why2 !== null) throw
|
|
150080
|
+
if (why2 !== null) throw refuse4(why2, this.refusalText(why2, r));
|
|
149473
150081
|
}
|
|
149474
150082
|
return this.run(() => {
|
|
149475
150083
|
const now = this.now();
|
|
@@ -149514,9 +150122,9 @@ var MessageService = class {
|
|
|
149514
150122
|
/** Work a delegation tool gave: a task from `sender` to `recipient`, whose turn Centralu reports (D2, D7). Written delivered when the old path delivered its words */
|
|
149515
150123
|
task(sender, recipient, input) {
|
|
149516
150124
|
const from = parseAddress(sender);
|
|
149517
|
-
if (!from || from.kind === "centralu") throw
|
|
150125
|
+
if (!from || from.kind === "centralu") throw refuse4("not_allowed", "Centralu sends with notice and report");
|
|
149518
150126
|
const text3 = cutTask(input.text, bodyLimit(from.kind));
|
|
149519
|
-
if (text3 === "") throw
|
|
150127
|
+
if (text3 === "") throw refuse4("empty", "a task says something");
|
|
149520
150128
|
return this.run(() => {
|
|
149521
150129
|
const now = this.now();
|
|
149522
150130
|
return this.put(
|
|
@@ -149529,12 +150137,12 @@ var MessageService = class {
|
|
|
149529
150137
|
refused(callerId, input) {
|
|
149530
150138
|
const to = this.sessionAddress(callerId);
|
|
149531
150139
|
const now = this.now();
|
|
149532
|
-
const
|
|
150140
|
+
const firstLine4 = oneLineText(input.firstLine, 120);
|
|
149533
150141
|
this.run(
|
|
149534
150142
|
() => this.centralu(to, {
|
|
149535
150143
|
kind: "notice",
|
|
149536
150144
|
body: REFUSAL_TEXT.untrusted_project(this.reach.projectLabel(input.projectId), input.target),
|
|
149537
|
-
facts: { refused: { ...input, firstLine:
|
|
150145
|
+
facts: { refused: { ...input, firstLine: firstLine4 } },
|
|
149538
150146
|
dedupeKey: `refused:${to}:${hash16([input.tool, input.target, input.firstLine])}:${Math.floor(now / MINUTE_MS)}`,
|
|
149539
150147
|
delivered: true
|
|
149540
150148
|
})
|
|
@@ -149549,7 +150157,7 @@ var MessageService = class {
|
|
|
149549
150157
|
return this.run(() => {
|
|
149550
150158
|
const task = this.records.message(taskId);
|
|
149551
150159
|
const row = task && (task.kind === "task" || task.kind === "message") ? this.records.recipients(taskId).find((r) => task.kind === "task" || this.isRequest(task, r.recipient)) : void 0;
|
|
149552
|
-
if (!task || !row) throw
|
|
150160
|
+
if (!task || !row) throw refuse4("unknown_message", REFUSAL_TEXT.unknown_message(taskId));
|
|
149553
150161
|
if (row.settled !== null && task.kind === "task") return { settledAlready: true };
|
|
149554
150162
|
const now = this.now();
|
|
149555
150163
|
const pause = input.outcome === "paused" || input.outcome === "waiting";
|
|
@@ -149603,11 +150211,11 @@ var MessageService = class {
|
|
|
149603
150211
|
done(by, id3) {
|
|
149604
150212
|
return this.run(() => {
|
|
149605
150213
|
const m = this.records.message(id3);
|
|
149606
|
-
if (!m) throw
|
|
150214
|
+
if (!m) throw refuse4("unknown_message", REFUSAL_TEXT.unknown_message(id3));
|
|
149607
150215
|
const mine = m.sender === by;
|
|
149608
150216
|
const row = mine ? void 0 : this.records.recipient(id3, by);
|
|
149609
|
-
if (!mine && !row) throw
|
|
149610
|
-
if (!mine && this.isRequest(m, by) && m.sender !== this.opts.turn?.(by)?.asker) throw
|
|
150217
|
+
if (!mine && !row) throw refuse4("not_yours", REFUSAL_TEXT.not_yours(id3));
|
|
150218
|
+
if (!mine && this.isRequest(m, by) && m.sender !== this.opts.turn?.(by)?.asker) throw refuse4("not_allowed", REFUSAL_TEXT.withheld(id3));
|
|
149611
150219
|
if (!m.needsReply && (m.kind !== "task" || !mine)) return { settled: 0, how: "unwatched" };
|
|
149612
150220
|
if (mine) {
|
|
149613
150221
|
const n = this.records.settle(id3, "all", "withdrawn", this.now());
|
|
@@ -149642,7 +150250,7 @@ var MessageService = class {
|
|
|
149642
150250
|
}
|
|
149643
150251
|
const id3 = opts.id ?? opts.before ?? "";
|
|
149644
150252
|
const base2 = opts.id === void 0 ? this.records.message(id3) : this.readable(reader, id3);
|
|
149645
|
-
if (!base2) throw
|
|
150253
|
+
if (!base2) throw refuse4("unknown_message", REFUSAL_TEXT.unknown_message(id3));
|
|
149646
150254
|
const chain = this.records.chain(id3, READ_CHAIN);
|
|
149647
150255
|
const oldest = chain.at(-1) ?? base2;
|
|
149648
150256
|
const missing = oldest.replyTo !== null && chain.length < READ_CHAIN && this.records.message(oldest.replyTo) === void 0;
|
|
@@ -150008,7 +150616,7 @@ var MessageService = class {
|
|
|
150008
150616
|
rows3
|
|
150009
150617
|
);
|
|
150010
150618
|
} catch (e) {
|
|
150011
|
-
if (e instanceof MailTargetMissing) throw
|
|
150619
|
+
if (e instanceof MailTargetMissing) throw refuse4("unknown_message", REFUSAL_TEXT.unknown_message(e.replyTo));
|
|
150012
150620
|
throw e;
|
|
150013
150621
|
}
|
|
150014
150622
|
if (!stored.duplicate) this.changed(stored, rows3.map((r) => r.recipient));
|
|
@@ -150029,9 +150637,9 @@ var MessageService = class {
|
|
|
150029
150637
|
);
|
|
150030
150638
|
}
|
|
150031
150639
|
checkText(cls, text3) {
|
|
150032
|
-
if (typeof text3 !== "string") throw
|
|
150033
|
-
if (text3.trim() === "") throw
|
|
150034
|
-
if (text3.length > bodyLimit(cls)) throw
|
|
150640
|
+
if (typeof text3 !== "string") throw refuse4("bad_value", "a body is text");
|
|
150641
|
+
if (text3.trim() === "") throw refuse4("empty", "a post says something");
|
|
150642
|
+
if (text3.length > bodyLimit(cls)) throw refuse4("too_long", `a body from ${cls === "app" ? "an app" : "this sender"} is at most ${bodyLimit(cls)} characters`);
|
|
150035
150643
|
return text3;
|
|
150036
150644
|
}
|
|
150037
150645
|
refusalText(why2, to) {
|
|
@@ -150048,7 +150656,7 @@ var MessageService = class {
|
|
|
150048
150656
|
readable(reader, id3) {
|
|
150049
150657
|
const m = this.records.message(id3);
|
|
150050
150658
|
const row = m && m.sender !== reader ? this.records.recipient(id3, reader) : void 0;
|
|
150051
|
-
if (!m || m.sender !== reader && (!row || isHidden(row))) throw
|
|
150659
|
+
if (!m || m.sender !== reader && (!row || isHidden(row))) throw refuse4("unknown_message", REFUSAL_TEXT.unknown_message(id3));
|
|
150052
150660
|
return m;
|
|
150053
150661
|
}
|
|
150054
150662
|
/** Whether `target` is a clarification of a task `owner` gave, and the answer goes to the one who asked (§9.7) */
|
|
@@ -150074,18 +150682,18 @@ var MessageService = class {
|
|
|
150074
150682
|
/** The guards of a send, over the store so a restart does not reset them (§7.5) */
|
|
150075
150683
|
guards(sender, cls, to, text3, target, requests2, now) {
|
|
150076
150684
|
for (const r of to) {
|
|
150077
|
-
if ((cls === "session" || cls === "app") && this.records.sameBody(sender, r, text3, now - REPEAT_WINDOW_MS)) throw
|
|
150685
|
+
if ((cls === "session" || cls === "app") && this.records.sameBody(sender, r, text3, now - REPEAT_WINDOW_MS)) throw refuse4("repeat", REFUSAL_TEXT.repeat(this.reach.labelOf(r)));
|
|
150078
150686
|
}
|
|
150079
150687
|
const limit = postsPerMinute(cls);
|
|
150080
150688
|
if (limit !== Infinity && this.records.postsSince(sender, now - MINUTE_MS) >= limit) {
|
|
150081
|
-
throw
|
|
150689
|
+
throw refuse4("rate_limited", `at most ${limit} posts a minute from ${cls === "app" ? "an app" : "a session"}: wait a minute`);
|
|
150082
150690
|
}
|
|
150083
150691
|
for (const [i, r] of to.entries()) {
|
|
150084
|
-
if (target === void 0 && cls === "session" && parseAddress(r)?.kind === "session" && this.records.newSince(sender, r, now -
|
|
150085
|
-
throw
|
|
150692
|
+
if (target === void 0 && cls === "session" && parseAddress(r)?.kind === "session" && this.records.newSince(sender, r, now - HOUR_MS) >= PAIR_NEW_PER_HOUR) {
|
|
150693
|
+
throw refuse4("pair_rate", REFUSAL_TEXT.pair_rate(this.reach.labelOf(r)));
|
|
150086
150694
|
}
|
|
150087
150695
|
if (requests2[i]) {
|
|
150088
|
-
if (this.records.requestsSince(this.reach.askGroupMembers(sender), r, now -
|
|
150696
|
+
if (this.records.requestsSince(this.reach.askGroupMembers(sender), r, now - HOUR_MS) >= ASKS_PER_HOUR) throw refuse4("ask_rate", REFUSAL_TEXT.ask_rate());
|
|
150089
150697
|
}
|
|
150090
150698
|
}
|
|
150091
150699
|
}
|
|
@@ -150197,7 +150805,8 @@ var MessageService = class {
|
|
|
150197
150805
|
...answered?.sender === recipient ? { answers: answered.id } : {},
|
|
150198
150806
|
...typeof m.facts.clarifies === "string" ? { clarifies: m.facts.clarifies } : {},
|
|
150199
150807
|
...typeof about === "string" && parseAddress(about) ? { about } : {},
|
|
150200
|
-
|
|
150808
|
+
// The stored word `finished` is the host's knowledge that a turn ended, not that the work is done (#1902): the model reads it as that
|
|
150809
|
+
...m.facts.outcome ? { outcome: m.facts.outcome === "finished" ? "turn_ended" : m.facts.outcome } : {},
|
|
150201
150810
|
...m.facts.error ? { error: m.facts.error } : {},
|
|
150202
150811
|
...m.facts.reason ? { reason: m.facts.reason } : {},
|
|
150203
150812
|
...m.facts.paused !== void 0 ? { paused: m.facts.paused } : {},
|
|
@@ -150319,7 +150928,7 @@ var MailTool = class {
|
|
|
150319
150928
|
run(sessionId, args) {
|
|
150320
150929
|
try {
|
|
150321
150930
|
const me2 = this.host.caller(sessionId);
|
|
150322
|
-
if (!me2) throw
|
|
150931
|
+
if (!me2) throw refuse4("gone", "this session is not here");
|
|
150323
150932
|
return { text: this.dispatch(sessionId, me2, args, mailCalls(this.host.mailArgs(sessionId), this.host.form(sessionId))) };
|
|
150324
150933
|
} catch (e) {
|
|
150325
150934
|
const reason = refusalReason(e);
|
|
@@ -150346,7 +150955,7 @@ var MailTool = class {
|
|
|
150346
150955
|
}
|
|
150347
150956
|
case "update":
|
|
150348
150957
|
case "close":
|
|
150349
|
-
throw
|
|
150958
|
+
throw refuse4(
|
|
150350
150959
|
"removed",
|
|
150351
150960
|
`Centralu's mail has no threads any more, so "${a.action}" is gone. Answer a message with ${calls.answer("<message id>")}, and mark one that needs nothing with ${calls.done("<message id>")}.`
|
|
150352
150961
|
);
|
|
@@ -150361,7 +150970,7 @@ var MailTool = class {
|
|
|
150361
150970
|
const id3 = thread.trim();
|
|
150362
150971
|
if (parseMailId(id3)?.kind !== "thread") return id3;
|
|
150363
150972
|
const newest = this.records.newestLegacyTo(id3, me2);
|
|
150364
|
-
if (!newest) throw
|
|
150973
|
+
if (!newest) throw refuse4("unknown_message", `there is no message of thread ${id3} you were sent; check the id with mail(action="inbox").`);
|
|
150365
150974
|
return newest.id;
|
|
150366
150975
|
}
|
|
150367
150976
|
/**
|
|
@@ -150373,14 +150982,14 @@ var MailTool = class {
|
|
|
150373
150982
|
if (t === PERSON) return { address: PERSON };
|
|
150374
150983
|
if (t.toLowerCase() === "orchestrator") {
|
|
150375
150984
|
const id3 = this.host.orchestratorId();
|
|
150376
|
-
if (!id3) throw
|
|
150985
|
+
if (!id3) throw refuse4("no_mailbox", "there is no orchestrator to ask yet: write to the person instead");
|
|
150377
150986
|
return { address: this.service.sessionAddress(id3), via: "orchestrator" };
|
|
150378
150987
|
}
|
|
150379
150988
|
if (t.toLowerCase() === "parent") {
|
|
150380
|
-
if (!me2.askedBy) throw
|
|
150989
|
+
if (!me2.askedBy) throw refuse4("unknown", 'this session was not started by another session; write to "orchestrator" or "person".');
|
|
150381
150990
|
return { address: this.service.sessionAddress(me2.askedBy) };
|
|
150382
150991
|
}
|
|
150383
|
-
if (t === sessionId) throw
|
|
150992
|
+
if (t === sessionId) throw refuse4("bad_value", "a message is for someone else");
|
|
150384
150993
|
return { address: t.includes(":") ? t : this.service.sessionAddress(t) };
|
|
150385
150994
|
}
|
|
150386
150995
|
/** `send`'s and `reply`'s answer (S4's lines, in this tool's calls) */
|
|
@@ -150639,7 +151248,7 @@ var MailTurns = class {
|
|
|
150639
151248
|
else if (outcome === "acted") this.empty.delete(sessionId);
|
|
150640
151249
|
}
|
|
150641
151250
|
recent(sessionId) {
|
|
150642
|
-
const since = this.now() -
|
|
151251
|
+
const since = this.now() - HOUR_MS;
|
|
150643
151252
|
return (this.tokens.get(sessionId) ?? []).filter((t) => t.at > since);
|
|
150644
151253
|
}
|
|
150645
151254
|
/** Tokens the session's mail-started turns read in the last hour, the open one included */
|
|
@@ -150664,7 +151273,7 @@ var MailTurns = class {
|
|
|
150664
151273
|
* counted since the latest of an hour ago, the person's last word to either (a turn of theirs or a message), and the pair's Resume (§7.5)
|
|
150665
151274
|
*/
|
|
150666
151275
|
pairLoopSpent(a, b2) {
|
|
150667
|
-
const since = Math.max(this.now() -
|
|
151276
|
+
const since = Math.max(this.now() - HOUR_MS, this.lastPersonWord(a), this.lastPersonWord(b2), this.pairFrom.get(pairKey(a, b2)) ?? 0);
|
|
150668
151277
|
return this.records.pairTurnsSince(a, b2, since) > this.o.turnsPerHour;
|
|
150669
151278
|
}
|
|
150670
151279
|
/** The person pressed Resume on a pair that was paused: its turns are counted from now */
|
|
@@ -150867,8 +151476,8 @@ function refusesDelegation(service, reach, callerId, target, call2) {
|
|
|
150867
151476
|
}
|
|
150868
151477
|
|
|
150869
151478
|
// packages/agent-host/src/sessions/memory-gate.ts
|
|
150870
|
-
import { execFile as
|
|
150871
|
-
import { readFile as
|
|
151479
|
+
import { execFile as execFile12 } from "node:child_process";
|
|
151480
|
+
import { readFile as readFile9 } from "node:fs/promises";
|
|
150872
151481
|
import { freemem } from "node:os";
|
|
150873
151482
|
import { promisify as promisify10 } from "node:util";
|
|
150874
151483
|
var MEMORY_FLOOR_BYTES = 2 * 1024 ** 3;
|
|
@@ -150890,14 +151499,14 @@ function parseMemInfo(text3) {
|
|
|
150890
151499
|
const m = /^MemAvailable:\s+(\d+)\s*kB\s*$/m.exec(text3);
|
|
150891
151500
|
return m ? Number(m[1]) * 1024 : null;
|
|
150892
151501
|
}
|
|
150893
|
-
var run = promisify10(
|
|
151502
|
+
var run = promisify10(execFile12);
|
|
150894
151503
|
async function readAvailableMemory(deps = {}) {
|
|
150895
151504
|
const source = deps.source ?? availableMemorySource();
|
|
150896
151505
|
const free = deps.free ?? freemem;
|
|
150897
151506
|
if (source.kind === "freemem") return free();
|
|
150898
151507
|
const what = source.kind === "command" ? source.file : source.path;
|
|
150899
151508
|
try {
|
|
150900
|
-
const value = source.kind === "command" ? parseVmStat((await (deps.exec ?? ((f, a) => run(f, a, { timeout: 5e3, windowsHide: true })))(source.file, source.args)).stdout) : parseMemInfo(await (deps.readText ?? ((p) =>
|
|
151509
|
+
const value = source.kind === "command" ? parseVmStat((await (deps.exec ?? ((f, a) => run(f, a, { timeout: 5e3, windowsHide: true })))(source.file, source.args)).stdout) : parseMemInfo(await (deps.readText ?? ((p) => readFile9(p, "utf8")))(source.path));
|
|
150901
151510
|
if (value !== null) return value;
|
|
150902
151511
|
console.error(`[memory-gate] could not parse ${what}; letting agent starts through`);
|
|
150903
151512
|
} catch (e) {
|
|
@@ -150991,126 +151600,12 @@ var MemoryGate = class {
|
|
|
150991
151600
|
// packages/agent-host/src/rpc.ts
|
|
150992
151601
|
import { relative as relative8 } from "node:path";
|
|
150993
151602
|
|
|
150994
|
-
// packages/agent-host/src/themes.ts
|
|
150995
|
-
import { randomBytes as randomBytes11 } from "node:crypto";
|
|
150996
|
-
import { watch as watch4 } from "node:fs";
|
|
150997
|
-
import { mkdir as mkdir9, readdir as readdir15, readFile as readFile9, stat as stat11 } from "node:fs/promises";
|
|
150998
|
-
import { basename as basename14, join as join55 } from "node:path";
|
|
150999
|
-
var MAX_FILE_BYTES = 256 * 1024;
|
|
151000
|
-
var SETTLE_MS = 100;
|
|
151001
|
-
var ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,59}$/;
|
|
151002
|
-
var ThemeFiles = class {
|
|
151003
|
-
constructor(dir, onChange) {
|
|
151004
|
-
this.dir = dir;
|
|
151005
|
-
this.onChange = onChange;
|
|
151006
|
-
}
|
|
151007
|
-
dir;
|
|
151008
|
-
onChange;
|
|
151009
|
-
watcher = null;
|
|
151010
|
-
timer = null;
|
|
151011
|
-
/** Makes the folder, writes the schema next to the themes, and starts watching */
|
|
151012
|
-
async start() {
|
|
151013
|
-
await mkdir9(this.dir, { recursive: true });
|
|
151014
|
-
await this.writeSchema();
|
|
151015
|
-
try {
|
|
151016
|
-
this.watcher = watch4(this.dir, (_event, name) => {
|
|
151017
|
-
if (name && !String(name).endsWith(".json")) return;
|
|
151018
|
-
if (name === THEME_SCHEMA_FILE) return;
|
|
151019
|
-
if (this.timer) clearTimeout(this.timer);
|
|
151020
|
-
this.timer = setTimeout(() => {
|
|
151021
|
-
this.timer = null;
|
|
151022
|
-
this.onChange();
|
|
151023
|
-
}, SETTLE_MS);
|
|
151024
|
-
});
|
|
151025
|
-
this.watcher.on("error", () => this.stopWatching());
|
|
151026
|
-
} catch {
|
|
151027
|
-
}
|
|
151028
|
-
}
|
|
151029
|
-
close() {
|
|
151030
|
-
this.stopWatching();
|
|
151031
|
-
if (this.timer) clearTimeout(this.timer);
|
|
151032
|
-
this.timer = null;
|
|
151033
|
-
}
|
|
151034
|
-
stopWatching() {
|
|
151035
|
-
this.watcher?.close();
|
|
151036
|
-
this.watcher = null;
|
|
151037
|
-
}
|
|
151038
|
-
/**
|
|
151039
|
-
* The schema an editor uses for completion, written only when it differs — a build that adds
|
|
151040
|
-
* a token updates it, and an unchanged one does not touch the folder (or wake the watcher).
|
|
151041
|
-
*/
|
|
151042
|
-
async writeSchema() {
|
|
151043
|
-
const path2 = join55(this.dir, THEME_SCHEMA_FILE);
|
|
151044
|
-
const text3 = `${JSON.stringify(themeFileJsonSchema(), null, 2)}
|
|
151045
|
-
`;
|
|
151046
|
-
const current = await readFile9(path2, "utf8").catch(() => null);
|
|
151047
|
-
if (current !== text3) writeFileWhole(realOs().files, path2, text3);
|
|
151048
|
-
}
|
|
151049
|
-
/** Every `<id>.json` in the folder, read and checked. A file that cannot be read is listed with the reason. */
|
|
151050
|
-
async list() {
|
|
151051
|
-
const names = await readdir15(this.dir).catch(() => []);
|
|
151052
|
-
const entries = [];
|
|
151053
|
-
const ids = names.filter((name) => name.endsWith(".json") && name !== THEME_SCHEMA_FILE).map((name) => name.slice(0, -".json".length)).filter((id3) => ID_PATTERN.test(id3)).sort();
|
|
151054
|
-
for (const id3 of ids) entries.push(await this.read(id3));
|
|
151055
|
-
return entries;
|
|
151056
|
-
}
|
|
151057
|
-
async read(id3) {
|
|
151058
|
-
const path2 = this.pathOf(id3);
|
|
151059
|
-
try {
|
|
151060
|
-
const info = await stat11(path2);
|
|
151061
|
-
if (info.size > MAX_FILE_BYTES) {
|
|
151062
|
-
return { id: id3, path: path2, name: id3, base: "dark", tokens: {}, problems: ["The file is too large to be a theme."], broken: true };
|
|
151063
|
-
}
|
|
151064
|
-
return parseThemeFile(id3, path2, await readFile9(path2, "utf8"));
|
|
151065
|
-
} catch (e) {
|
|
151066
|
-
return { id: id3, path: path2, name: id3, base: "dark", tokens: {}, problems: [`Could not read the file: ${e.message}`], broken: true };
|
|
151067
|
-
}
|
|
151068
|
-
}
|
|
151069
|
-
/** Writes a theme. `id` null makes a new file named after the theme (`My theme` → `my-theme.json`). */
|
|
151070
|
-
async save(id3, content) {
|
|
151071
|
-
const target = id3 ?? await this.freeId(content.name);
|
|
151072
|
-
await mkdir9(this.dir, { recursive: true });
|
|
151073
|
-
writeFileWhole(realOs().files, this.pathOf(target), formatThemeFile(content));
|
|
151074
|
-
return this.read(target);
|
|
151075
|
-
}
|
|
151076
|
-
/** Copies a file from elsewhere in, under a fresh id if its name is taken. Refuses what does not read as a theme. */
|
|
151077
|
-
async importFrom(source) {
|
|
151078
|
-
const info = await stat11(source);
|
|
151079
|
-
if (!info.isFile()) throw rpcError("That is not a file.");
|
|
151080
|
-
if (info.size > MAX_FILE_BYTES) throw rpcError("The file is too large to be a theme.");
|
|
151081
|
-
const text3 = await readFile9(source, "utf8");
|
|
151082
|
-
const parsed = parseThemeFile("import", source, text3);
|
|
151083
|
-
if (parsed.broken) throw rpcError(parsed.problems[0] ?? "Not a theme file.");
|
|
151084
|
-
const fromName = basename14(source).replace(/\.json$/i, "");
|
|
151085
|
-
const id3 = await this.freeId(parsed.name !== "import" ? parsed.name : fromName);
|
|
151086
|
-
writeFileWhole(realOs().files, this.pathOf(id3), text3);
|
|
151087
|
-
return this.read(id3);
|
|
151088
|
-
}
|
|
151089
|
-
/** The absolute path for an id, for the shell to trash or reveal. Refuses anything that is not an id. */
|
|
151090
|
-
pathOf(id3) {
|
|
151091
|
-
if (!ID_PATTERN.test(id3)) throw rpcError(`Not a theme id: ${id3}`);
|
|
151092
|
-
return join55(this.dir, `${id3}.json`);
|
|
151093
|
-
}
|
|
151094
|
-
async freeId(name) {
|
|
151095
|
-
const base2 = name.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48) || "theme";
|
|
151096
|
-
for (let n = 1; n < 1e3; n++) {
|
|
151097
|
-
const id3 = n === 1 ? base2 : `${base2}-${n}`;
|
|
151098
|
-
const taken = await stat11(this.pathOf(id3)).then(
|
|
151099
|
-
() => true,
|
|
151100
|
-
() => false
|
|
151101
|
-
);
|
|
151102
|
-
if (!taken) return id3;
|
|
151103
|
-
}
|
|
151104
|
-
return `${base2}-${randomBytes11(4).toString("hex")}`;
|
|
151105
|
-
}
|
|
151106
|
-
};
|
|
151107
|
-
|
|
151108
151603
|
// packages/agent-host/src/dev-services/strays.ts
|
|
151109
|
-
import { execFile as
|
|
151604
|
+
import { execFile as execFile13 } from "node:child_process";
|
|
151110
151605
|
import { realpathSync as realpathSync11 } from "node:fs";
|
|
151111
151606
|
import { promisify as promisify11 } from "node:util";
|
|
151112
151607
|
import { userInfo as userInfo2 } from "node:os";
|
|
151113
|
-
var exec10 = promisify11(
|
|
151608
|
+
var exec10 = promisify11(execFile13);
|
|
151114
151609
|
function parsePsRows(out) {
|
|
151115
151610
|
const rows3 = [];
|
|
151116
151611
|
for (const line of out.split("\n")) {
|
|
@@ -151382,7 +151877,7 @@ var toInfo = (h) => ({
|
|
|
151382
151877
|
history: h.history(),
|
|
151383
151878
|
alive: h.alive
|
|
151384
151879
|
});
|
|
151385
|
-
function createRpcHandler(mgr2, adapters2, { settleDeletedProjects, deleteOutlook, terminals: terminals2, updates: updates2, commands, externalApps: externalApps2, views: views2, inlineViews: inlineViews2, heldPids, themes: themes2, agentVersions: agentVersions2, sleeper: sleeper2, machines, stopHost, stopGate, handover: handover2, activity: activity2, updateApply: updateApply2, hubs: hubs2, mail, requests: requests2, serveCredentials } = {}) {
|
|
151880
|
+
function createRpcHandler(mgr2, adapters2, { settleDeletedProjects, deleteOutlook, terminals: terminals2, updates: updates2, commands, externalApps: externalApps2, views: views2, inlineViews: inlineViews2, heldPids, themes: themes2, agentVersions: agentVersions2, sleeper: sleeper2, machines, stopHost, stopGate, handover: handover2, systemEnding: systemEnding2, activity: activity2, updateApply: updateApply2, hubs: hubs2, mail, requests: requests2, serveCredentials } = {}) {
|
|
151386
151881
|
const reportUiCrash = createUiCrashLog((line) => console.error(line));
|
|
151387
151882
|
const requireThemes = () => {
|
|
151388
151883
|
if (!themes2) throw rpcError("Theme files are unavailable");
|
|
@@ -151470,6 +151965,10 @@ function createRpcHandler(mgr2, adapters2, { settleDeletedProjects, deleteOutloo
|
|
|
151470
151965
|
requireTerminals().close(RpcMethods["terminal.close"].params.parse(p).terminalId);
|
|
151471
151966
|
return { ok: true };
|
|
151472
151967
|
},
|
|
151968
|
+
"themes.remove": async (p) => {
|
|
151969
|
+
const keptAt = await requireThemes().remove(RpcMethods["themes.remove"].params.parse(p).id);
|
|
151970
|
+
return { removed: keptAt !== null, keptAt };
|
|
151971
|
+
},
|
|
151473
151972
|
"themes.import": async (p) => requireThemes().importFrom(RpcMethods["themes.import"].params.parse(p).path),
|
|
151474
151973
|
"themes.resolve": async (p) => ({ path: requireThemes().pathOf(RpcMethods["themes.resolve"].params.parse(p).id) }),
|
|
151475
151974
|
"machines.add": async (p) => {
|
|
@@ -152072,7 +152571,8 @@ function createRpcHandler(mgr2, adapters2, { settleDeletedProjects, deleteOutloo
|
|
|
152072
152571
|
if (updateApply2 && ctx) updateApply2.report(ctx.client, r);
|
|
152073
152572
|
return {};
|
|
152074
152573
|
},
|
|
152075
|
-
"host.handover": async ({ handover: h }) => ack(handover2?.(h))
|
|
152574
|
+
"host.handover": async ({ handover: h }) => ack(handover2?.(h)),
|
|
152575
|
+
"host.systemEnding": async ({ cancelled: cancelled2 }) => ack(systemEnding2?.(cancelled2 === true))
|
|
152076
152576
|
};
|
|
152077
152577
|
const routes = { ...raw, ...handlers };
|
|
152078
152578
|
return async (method, params, ctx) => {
|
|
@@ -152327,7 +152827,7 @@ function broadcastAppRuns(send, windowMs = APP_CHANGE_WINDOW_MS) {
|
|
|
152327
152827
|
var sameCause = (a, b2) => a !== null && b2 !== null && JSON.stringify(a) === JSON.stringify(b2);
|
|
152328
152828
|
|
|
152329
152829
|
// packages/agent-host/src/dev-services/terminal.ts
|
|
152330
|
-
import { createRequire } from "node:module";
|
|
152830
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
152331
152831
|
import { existsSync as existsSync27 } from "node:fs";
|
|
152332
152832
|
|
|
152333
152833
|
// packages/agent-host/src/dev-services/pty-slot.ts
|
|
@@ -152482,7 +152982,7 @@ function warnInternals(what) {
|
|
|
152482
152982
|
}
|
|
152483
152983
|
|
|
152484
152984
|
// packages/agent-host/src/dev-services/terminal.ts
|
|
152485
|
-
var require2 =
|
|
152985
|
+
var require2 = createRequire2(import.meta.url);
|
|
152486
152986
|
var TerminalService = class {
|
|
152487
152987
|
constructor(emit, ptys) {
|
|
152488
152988
|
this.emit = emit;
|
|
@@ -152675,8 +153175,8 @@ function shellPath() {
|
|
|
152675
153175
|
}
|
|
152676
153176
|
|
|
152677
153177
|
// packages/agent-host/src/dev-services/commands.ts
|
|
152678
|
-
import { createRequire as
|
|
152679
|
-
var require3 =
|
|
153178
|
+
import { createRequire as createRequire3 } from "node:module";
|
|
153179
|
+
var require3 = createRequire3(import.meta.url);
|
|
152680
153180
|
var CommandRunner = class {
|
|
152681
153181
|
constructor(emit, ptys) {
|
|
152682
153182
|
this.emit = emit;
|
|
@@ -152824,7 +153324,7 @@ var CommandRunner = class {
|
|
|
152824
153324
|
};
|
|
152825
153325
|
|
|
152826
153326
|
// packages/agent-host/src/updates.ts
|
|
152827
|
-
import { execFile as
|
|
153327
|
+
import { execFile as execFile14, execFileSync as execFileSync4 } from "node:child_process";
|
|
152828
153328
|
import { existsSync as existsSync28, readFileSync as readFileSync23 } from "node:fs";
|
|
152829
153329
|
import { dirname as dirname25, join as join56 } from "node:path";
|
|
152830
153330
|
var REGISTRY_URL = `https://registry.npmjs.org/${APP_SLUG}/latest`;
|
|
@@ -152847,7 +153347,7 @@ function hubUpdatesMachines(f) {
|
|
|
152847
153347
|
async function npmGlobalPackage() {
|
|
152848
153348
|
const cmd = commandFor("npm", ["root", "-g"]);
|
|
152849
153349
|
const out = await new Promise((done2) => {
|
|
152850
|
-
|
|
153350
|
+
execFile14(cmd.file, cmd.args, { timeout: 6e4, windowsHide: true }, (err, stdout) => done2(err ? "" : String(stdout)));
|
|
152851
153351
|
});
|
|
152852
153352
|
const root3 = out.trim().split(/\r?\n/).at(-1)?.trim();
|
|
152853
153353
|
if (!root3) return null;
|
|
@@ -152922,7 +153422,7 @@ function failureLine(e) {
|
|
|
152922
153422
|
function runCommand(file2, args, resolve20) {
|
|
152923
153423
|
const cmd = commandFor(file2, args, resolve20);
|
|
152924
153424
|
return new Promise((done2, reject) => {
|
|
152925
|
-
|
|
153425
|
+
execFile14(cmd.file, cmd.args, { timeout: INSTALL_TIMEOUT_MS, windowsHide: true }, (err, _stdout, stderr) => {
|
|
152926
153426
|
if (!err) return done2();
|
|
152927
153427
|
if (err.code === "ENOENT") {
|
|
152928
153428
|
return reject(new Error(`${file2} was not found on PATH (${err.message})`));
|
|
@@ -154369,7 +154869,14 @@ var Shutdown = class {
|
|
|
154369
154869
|
if (this.ending) return this.ending;
|
|
154370
154870
|
if (this.stopping) return this.stopping.catch(() => {
|
|
154371
154871
|
});
|
|
154372
|
-
|
|
154872
|
+
this.mode = mode;
|
|
154873
|
+
let before;
|
|
154874
|
+
try {
|
|
154875
|
+
before = this.o.beforeLeave?.()?.catch(() => {
|
|
154876
|
+
});
|
|
154877
|
+
} catch {
|
|
154878
|
+
}
|
|
154879
|
+
const stopped = before ? before.then(() => this.stopServices(mode, handOver2)) : this.stopServices(mode, handOver2);
|
|
154373
154880
|
const setTimer = this.o.setTimer ?? ((f, ms3) => void setTimeout(f, ms3).unref());
|
|
154374
154881
|
setTimer(() => this.force(`did not finish within ${this.o.boundMs ?? SHUTDOWN_BOUND_MS}ms`), this.o.boundMs ?? SHUTDOWN_BOUND_MS);
|
|
154375
154882
|
this.ending = stopped.catch((err) => {
|
|
@@ -154496,6 +155003,29 @@ function markQuit(dataDir, now = Date.now()) {
|
|
|
154496
155003
|
} catch {
|
|
154497
155004
|
}
|
|
154498
155005
|
}
|
|
155006
|
+
function writeSystemEnd(dataDir) {
|
|
155007
|
+
return (end) => {
|
|
155008
|
+
try {
|
|
155009
|
+
if (end) writeFileSync10(join57(dataDir, SYSTEM_END_FILE), systemEndText(end));
|
|
155010
|
+
else rmSync8(join57(dataDir, SYSTEM_END_FILE), { force: true });
|
|
155011
|
+
} catch {
|
|
155012
|
+
}
|
|
155013
|
+
};
|
|
155014
|
+
}
|
|
155015
|
+
function takeSystemEnd(dataDir) {
|
|
155016
|
+
const path2 = join57(dataDir, SYSTEM_END_FILE);
|
|
155017
|
+
let text3;
|
|
155018
|
+
try {
|
|
155019
|
+
text3 = readFileSync24(path2, "utf8");
|
|
155020
|
+
} catch {
|
|
155021
|
+
return null;
|
|
155022
|
+
}
|
|
155023
|
+
try {
|
|
155024
|
+
rmSync8(path2, { force: true });
|
|
155025
|
+
} catch {
|
|
155026
|
+
}
|
|
155027
|
+
return parseSystemEnd(text3);
|
|
155028
|
+
}
|
|
154499
155029
|
function updateResumeFolder(dataDir) {
|
|
154500
155030
|
const NAME3 = /^update-resume\.[0-9a-f-]{36}\.json$/;
|
|
154501
155031
|
return {
|
|
@@ -157283,10 +157813,6 @@ var LinkedMachineConnection = class {
|
|
|
157283
157813
|
continuesCutTurns() {
|
|
157284
157814
|
return this.updateContinued !== null;
|
|
157285
157815
|
}
|
|
157286
|
-
/** When the remote host took up this session's turn (its own id) after an update cut it (#1618); null when it did not */
|
|
157287
|
-
continuedAfterUpdate(sessionId) {
|
|
157288
|
-
return this.updateContinued?.get(sessionId) ?? null;
|
|
157289
|
-
}
|
|
157290
157816
|
info() {
|
|
157291
157817
|
return {
|
|
157292
157818
|
id: this.record.id,
|
|
@@ -159278,10 +159804,6 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
|
|
|
159278
159804
|
continuesCutTurns(machineId) {
|
|
159279
159805
|
return machineById(machineId)?.continuesCutTurns?.() === true;
|
|
159280
159806
|
},
|
|
159281
|
-
continuedAfterUpdate(sessionId) {
|
|
159282
|
-
const at2 = qualifiedMachine(machines(), sessionId);
|
|
159283
|
-
return at2?.machine.continuedAfterUpdate?.(at2.id) ?? null;
|
|
159284
|
-
},
|
|
159285
159807
|
async backgroundTasks(machineId) {
|
|
159286
159808
|
const m = machineById(machineId);
|
|
159287
159809
|
if (!m || !m.reachable) return null;
|
|
@@ -159712,6 +160234,7 @@ var ROUTES = {
|
|
|
159712
160234
|
"themes.list": HUB,
|
|
159713
160235
|
"themes.save": HUB,
|
|
159714
160236
|
"themes.import": HUB,
|
|
160237
|
+
"themes.remove": HUB,
|
|
159715
160238
|
"themes.resolve": HUB,
|
|
159716
160239
|
"updates.status": HUB,
|
|
159717
160240
|
"updates.setAuto": HUB,
|
|
@@ -159736,6 +160259,8 @@ var ROUTES = {
|
|
|
159736
160259
|
"host.stop": HUB,
|
|
159737
160260
|
// A sign-in's serve on this machine telling its own host about the hand-over (#1224); never through a link
|
|
159738
160261
|
"host.handover": HUB,
|
|
160262
|
+
// The window of this machine telling its own host the system is ending (#1970); never through a link
|
|
160263
|
+
"host.systemEnding": HUB,
|
|
159739
160264
|
"machines.acceptVersions": HUB,
|
|
159740
160265
|
"machines.install": HUB,
|
|
159741
160266
|
"machines.update": HUB,
|
|
@@ -160313,7 +160838,7 @@ function reverseCalls(o) {
|
|
|
160313
160838
|
}
|
|
160314
160839
|
|
|
160315
160840
|
// packages/agent-host/src/lifecycle/work-job.ts
|
|
160316
|
-
import { execFile as
|
|
160841
|
+
import { execFile as execFile15 } from "node:child_process";
|
|
160317
160842
|
import { promisify as promisify12 } from "node:util";
|
|
160318
160843
|
var WORK_JOB_LIMITS = 8192;
|
|
160319
160844
|
var JOIN_SCRIPT = (pid, limits) => `
|
|
@@ -160354,7 +160879,7 @@ public static class CentraluWorkJob {
|
|
|
160354
160879
|
'@
|
|
160355
160880
|
[CentraluWorkJob]::Join(${pid}, ${limits})
|
|
160356
160881
|
`;
|
|
160357
|
-
var run3 = promisify12(
|
|
160882
|
+
var run3 = promisify12(execFile15);
|
|
160358
160883
|
var REAL_OS2 = {
|
|
160359
160884
|
needed: endsWorkWithJob(),
|
|
160360
160885
|
powershell: async (script) => {
|
|
@@ -160387,7 +160912,7 @@ function workJobLine(job) {
|
|
|
160387
160912
|
// packages/agent-host/src/dev-services/broker-pty.ts
|
|
160388
160913
|
import { spawn as startProcess } from "node:child_process";
|
|
160389
160914
|
import { existsSync as existsSync30 } from "node:fs";
|
|
160390
|
-
import { createRequire as
|
|
160915
|
+
import { createRequire as createRequire4 } from "node:module";
|
|
160391
160916
|
import { createInterface as createInterface2 } from "node:readline";
|
|
160392
160917
|
import { fileURLToPath as fileURLToPath7 } from "node:url";
|
|
160393
160918
|
function parseBrokerEvent(line) {
|
|
@@ -160563,7 +161088,7 @@ function openInHost(url2) {
|
|
|
160563
161088
|
} catch {
|
|
160564
161089
|
}
|
|
160565
161090
|
}
|
|
160566
|
-
var require4 =
|
|
161091
|
+
var require4 = createRequire4(import.meta.url);
|
|
160567
161092
|
function loadNodePty() {
|
|
160568
161093
|
const loaded = require4("node-pty");
|
|
160569
161094
|
if (!isPtyModule(loaded)) throw new Error("node-pty did not load as a terminal module");
|
|
@@ -160589,7 +161114,7 @@ function brokerPtyModule(broker, fallback = loadNodePty) {
|
|
|
160589
161114
|
}
|
|
160590
161115
|
|
|
160591
161116
|
// packages/agent-host/src/elevation.ts
|
|
160592
|
-
import { execFile as
|
|
161117
|
+
import { execFile as execFile16 } from "node:child_process";
|
|
160593
161118
|
function detectToken(o = {}) {
|
|
160594
161119
|
const source = elevationSource(o.platform ?? HOST_PLATFORM, o.env ?? process.env);
|
|
160595
161120
|
if (source.kind === "uid") {
|
|
@@ -160597,7 +161122,7 @@ function detectToken(o = {}) {
|
|
|
160597
161122
|
return Promise.resolve({ elevated: getuid ? getuid() === 0 : null, logon: null });
|
|
160598
161123
|
}
|
|
160599
161124
|
const run4 = o.run ?? ((file2, args) => new Promise(
|
|
160600
|
-
(resolve20, reject) =>
|
|
161125
|
+
(resolve20, reject) => execFile16(file2, args, { windowsHide: true, timeout: 1e4, encoding: "utf8" }, (err, stdout) => err ? reject(err) : resolve20(stdout))
|
|
160601
161126
|
));
|
|
160602
161127
|
return run4(source.file, source.args).then(
|
|
160603
161128
|
(out) => ({ elevated: elevatedFromGroups(out), logon: logonFromGroups(out) }),
|
|
@@ -160622,7 +161147,7 @@ var handedToken = launch.tokenOnStdin ? await readHandedToken(process.stdin).cat
|
|
|
160622
161147
|
console.error(`[agent-host] the launcher did not hand over the token: ${e.message}`);
|
|
160623
161148
|
process.exit(1);
|
|
160624
161149
|
}) : launch.token;
|
|
160625
|
-
var BUILD = true ? "
|
|
161150
|
+
var BUILD = true ? "eafd282" : "dev";
|
|
160626
161151
|
var { values } = parseArgs({
|
|
160627
161152
|
options: {
|
|
160628
161153
|
port: { type: "string", default: "5175" },
|
|
@@ -160694,6 +161219,30 @@ if (!lock.ok) {
|
|
|
160694
161219
|
}
|
|
160695
161220
|
var ownership = lock;
|
|
160696
161221
|
process.on("exit", ownership.release);
|
|
161222
|
+
var systemEnd = dbPath === ":memory:" ? null : takeSystemEnd(dirname26(dbPath));
|
|
161223
|
+
if (systemEnd) console.error(`[agent-host] the last host was told the system was ending, at ${new Date(systemEnd.at).toISOString()}`);
|
|
161224
|
+
var systemEnding = new SystemEndNotice({
|
|
161225
|
+
write: dbPath === ":memory:" ? () => {
|
|
161226
|
+
} : writeSystemEnd(dirname26(dbPath)),
|
|
161227
|
+
outlived: () => console.error("[agent-host] still running well after the system said it was ending: it did not end")
|
|
161228
|
+
});
|
|
161229
|
+
var systemQuestion = null;
|
|
161230
|
+
var endSystemQuestion = () => {
|
|
161231
|
+
};
|
|
161232
|
+
process.on("exit", () => endSystemQuestion());
|
|
161233
|
+
var askSystem = () => {
|
|
161234
|
+
if (systemQuestion) return systemQuestion;
|
|
161235
|
+
const at2 = Date.now();
|
|
161236
|
+
const q2 = askSystemStopping();
|
|
161237
|
+
if (!q2.stopping) return void 0;
|
|
161238
|
+
endSystemQuestion = q2.end;
|
|
161239
|
+
systemQuestion = q2.stopping.then((stopping) => {
|
|
161240
|
+
if (!stopping) return;
|
|
161241
|
+
console.error("[agent-host] leaving as the system is stopping");
|
|
161242
|
+
systemEnding.notice(at2);
|
|
161243
|
+
});
|
|
161244
|
+
return systemQuestion;
|
|
161245
|
+
};
|
|
160697
161246
|
if (dbPath !== ":memory:") clearStoppedMark(dirname26(dbPath));
|
|
160698
161247
|
var updateResume = dbPath === ":memory:" ? void 0 : updateResumeFolder(dirname26(dbPath));
|
|
160699
161248
|
start2.at("hold signals");
|
|
@@ -160730,7 +161279,12 @@ var mgr = new SessionManager(
|
|
|
160730
161279
|
(sessionId) => bridgeAddress(launch.frontDoor, port, token, sessionId),
|
|
160731
161280
|
// Worktrees are created next to the data folder — dev and the packaged app never touch each other's worktrees
|
|
160732
161281
|
join59(dirname26(dbPath), "worktrees"),
|
|
160733
|
-
{
|
|
161282
|
+
{
|
|
161283
|
+
...held2 ? { processes: held2.processes, keptSessions: held2.kept.sessionIds } : {},
|
|
161284
|
+
...updateResume ? { updateResume } : {},
|
|
161285
|
+
// The last host recorded that the system was ending (#1397)
|
|
161286
|
+
...systemEnd ? { systemEnd } : {}
|
|
161287
|
+
}
|
|
160734
161288
|
);
|
|
160735
161289
|
mgr.useProjectsHome(() => projectsHome());
|
|
160736
161290
|
mgr.useMemoryGate(new MemoryGate());
|
|
@@ -160895,6 +161449,16 @@ var stopFromRequest = (o, by = "centralu serve --stop") => {
|
|
|
160895
161449
|
else stopAsked = true;
|
|
160896
161450
|
};
|
|
160897
161451
|
if (control && launch.startedBy === "app") control.on("stop", (m) => stopFromRequest({ forUpdate: m.forUpdate === true }, "the window"));
|
|
161452
|
+
var systemEndingTold = (cancelled2) => {
|
|
161453
|
+
if (cancelled2) {
|
|
161454
|
+
console.error("[agent-host] the system is no longer ending");
|
|
161455
|
+
systemEnding.cancel();
|
|
161456
|
+
} else {
|
|
161457
|
+
console.error("[agent-host] the system is ending");
|
|
161458
|
+
systemEnding.notice();
|
|
161459
|
+
}
|
|
161460
|
+
};
|
|
161461
|
+
control?.on("system-ending", (m) => systemEndingTold(m.cancelled === true));
|
|
160898
161462
|
var hubName = () => hostname5().replace(/\.local$/i, "") || "a linked computer";
|
|
160899
161463
|
var links = new Links(linkDepsFor(store, {
|
|
160900
161464
|
hub: { version: APP_VERSION, protocolVersion: PROTOCOL_VERSION, dev: BUILD === "dev" },
|
|
@@ -160980,6 +161544,7 @@ var router = new Router({
|
|
|
160980
161544
|
// `host.stop {ifIdle}`: the check and the refusal of new calls in one step (#1224, lifecycle/stop-gate.ts)
|
|
160981
161545
|
stopGate: launch.startedByServe && !launch.underKeeper ? createStopGate({ drain: hostDrain, activity: () => activityCounts(activity()) }) : void 0,
|
|
160982
161546
|
// A sign-in's serve says it waits to take over, or gave up (#1224): kept for every hello, and told to who is connected
|
|
161547
|
+
systemEnding: systemEndingTold,
|
|
160983
161548
|
handover: (h) => {
|
|
160984
161549
|
handover = h;
|
|
160985
161550
|
server.broadcast({ type: "host_handover", handover: h });
|
|
@@ -161100,7 +161665,9 @@ var shutdown = new Shutdown({
|
|
|
161100
161665
|
log: (line) => console.error(line),
|
|
161101
161666
|
stopLog,
|
|
161102
161667
|
exit: (code2) => process.exit(code2),
|
|
161103
|
-
pid: process.pid
|
|
161668
|
+
pid: process.pid,
|
|
161669
|
+
// Whether the system is ending, asked as the leave begins, whichever way it began (#1397). Nothing where it cannot say (`undefined`)
|
|
161670
|
+
beforeLeave: () => askSystem()
|
|
161104
161671
|
});
|
|
161105
161672
|
start2.at("listen");
|
|
161106
161673
|
await raceTimeout(elevation, 3e3, void 0);
|
|
@@ -161181,7 +161748,11 @@ if (control && launch.underKeeper) {
|
|
|
161181
161748
|
return shutdown.stopServices(mode, true);
|
|
161182
161749
|
},
|
|
161183
161750
|
keepsAgents: KEEPS_AGENTS_ACROSS_SWAP && held2 !== null,
|
|
161184
|
-
|
|
161751
|
+
// The data folder is the next host's from here: nothing this one learns later is written in it
|
|
161752
|
+
release: () => {
|
|
161753
|
+
systemEnding.close();
|
|
161754
|
+
ownership.release();
|
|
161755
|
+
},
|
|
161185
161756
|
write: (line) => void writeSync4(1, `${line}
|
|
161186
161757
|
`),
|
|
161187
161758
|
log: (line) => console.error(line),
|