@centralu/win32-x64 0.1.0-beta.37 → 0.1.0-beta.39
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.
|
@@ -43053,9 +43053,11 @@ import { hostname as hostname5 } from "node:os";
|
|
|
43053
43053
|
import { dirname as dirname23, join as join52 } from "node:path";
|
|
43054
43054
|
import { mkdirSync as mkdirSync19, writeSync as writeSync3 } from "node:fs";
|
|
43055
43055
|
|
|
43056
|
+
// packages/protocol/src/version.generated.ts
|
|
43057
|
+
var APP_VERSION = "0.1.0-beta.39";
|
|
43058
|
+
|
|
43056
43059
|
// packages/protocol/src/brand.ts
|
|
43057
43060
|
var APP_NAME = "Centralu";
|
|
43058
|
-
var APP_VERSION = "0.1.0-beta.37";
|
|
43059
43061
|
var APP_SLUG = "centralu";
|
|
43060
43062
|
var CLIENT_INFO = { name: APP_SLUG, title: APP_NAME, version: APP_VERSION };
|
|
43061
43063
|
var DATA_DIR = ".centralu";
|
|
@@ -59749,7 +59751,18 @@ var NormalizedEvent = external_exports.discriminatedUnion("type", [
|
|
|
59749
59751
|
* The model the tool resolved the session's model name to (Claude's CLI: an alias such as `haiku` or the project's default
|
|
59750
59752
|
* names a different model over time), when it says. With the window it is what `ModelWindows` keeps per model (#1376).
|
|
59751
59753
|
*/
|
|
59752
|
-
model: external_exports.string().optional()
|
|
59754
|
+
model: external_exports.string().optional(),
|
|
59755
|
+
/**
|
|
59756
|
+
* (#1553) `window` is the model's full context window as the host says it, so the share is used / the model's window whatever
|
|
59757
|
+
* the tool reported (Claude's CLI reports the smaller of the model's window and the compaction point it was started with).
|
|
59758
|
+
* The host sets `window` and `capped`; `capped`: the full window is not known (an adopted process with no kept ceiling, a model
|
|
59759
|
+
* name that moved to another model), so `window` is only the tool's own reading, a floor under the model's.
|
|
59760
|
+
* `compactAt`: the size in tokens at which this session's process compacts by itself, said by the adapter of a process Centralu's
|
|
59761
|
+
* control started (Claude: the CLI's own threshold from every reading; Codex: the limit it was started with). The host drops one
|
|
59762
|
+
* that is not under `window`.
|
|
59763
|
+
*/
|
|
59764
|
+
capped: external_exports.boolean().optional(),
|
|
59765
|
+
compactAt: external_exports.number().optional()
|
|
59753
59766
|
}),
|
|
59754
59767
|
external_exports.object({
|
|
59755
59768
|
...base,
|
|
@@ -60549,7 +60562,16 @@ var SessionInfo = external_exports.object({
|
|
|
60549
60562
|
* tool and outlives us. So the store writes it down and reads it back (schema v17), and
|
|
60550
60563
|
* `null` here means "this session has never reported one" rather than "we forgot".
|
|
60551
60564
|
*/
|
|
60552
|
-
context: external_exports.object({
|
|
60565
|
+
context: external_exports.object({
|
|
60566
|
+
used: external_exports.number(),
|
|
60567
|
+
/** The model's full window when known (`capped` absent), else the tool's own reading of it (#1553, `contextShare`) */
|
|
60568
|
+
window: external_exports.number(),
|
|
60569
|
+
exactness: external_exports.enum(["exact", "estimate"]),
|
|
60570
|
+
/** `window` is a floor under the model's window, not the window itself */
|
|
60571
|
+
capped: external_exports.boolean().optional(),
|
|
60572
|
+
/** The size in tokens at which the session's process compacts by itself, when Centralu's control started it */
|
|
60573
|
+
compactAt: external_exports.number().optional()
|
|
60574
|
+
}).nullable().default(null)
|
|
60553
60575
|
});
|
|
60554
60576
|
var SettingsApplied = external_exports.enum(["restarted", "after_turn", "saved"]);
|
|
60555
60577
|
var UpdateSettingsResult = SessionInfo.extend({ applied: SettingsApplied.optional() });
|
|
@@ -62458,6 +62480,17 @@ var sessionMethods = {
|
|
|
62458
62480
|
params: external_exports.object({ sessionId: SessionId, seq: external_exports.number() }),
|
|
62459
62481
|
result: external_exports.object({ ok: external_exports.literal(true) })
|
|
62460
62482
|
},
|
|
62483
|
+
/**
|
|
62484
|
+
* Done on a session in `error` (#1549): it moves to `idle` and what the person saw is read, so the sidebar's mark and the
|
|
62485
|
+
* inbox row go. `seq` is the newest row the window had: the error is cleared only when nothing newer exists (a later error
|
|
62486
|
+
* the person has not seen stays), and rows up to `seq` are read either way. `cleared` says whether the state moved, `newer` that it stayed `error` because a newer row exists. A session
|
|
62487
|
+
* not in `error` keeps its state (its rows up to `seq` are read). Nothing is stopped, retried or continued, and the error
|
|
62488
|
+
* line stays in the conversation. The person's alone (the window): an agent's script may not hide a failure.
|
|
62489
|
+
*/
|
|
62490
|
+
"sessions.clearError": {
|
|
62491
|
+
params: external_exports.object({ sessionId: SessionId, seq: external_exports.number() }),
|
|
62492
|
+
result: external_exports.object({ ok: external_exports.literal(true), cleared: external_exports.boolean(), newer: external_exports.boolean().default(false) })
|
|
62493
|
+
},
|
|
62461
62494
|
"messages.load": {
|
|
62462
62495
|
params: external_exports.object({ sessionId: SessionId, limit: external_exports.number().default(200), beforeSeq: external_exports.number().optional() }),
|
|
62463
62496
|
result: external_exports.array(StoredMessage)
|
|
@@ -63463,12 +63496,12 @@ function applySessionState(m, e, now) {
|
|
|
63463
63496
|
function nextContext(context, e) {
|
|
63464
63497
|
switch (e.type) {
|
|
63465
63498
|
case "context_update":
|
|
63466
|
-
return { used: e.used, window: e.window, exactness: e.exactness };
|
|
63499
|
+
return { used: e.used, window: e.window, exactness: e.exactness, ...e.capped ? { capped: true } : {}, ...e.compactAt !== void 0 ? { compactAt: e.compactAt } : {} };
|
|
63467
63500
|
case "conversation_reset":
|
|
63468
63501
|
return null;
|
|
63469
63502
|
case "compaction":
|
|
63470
63503
|
if (e.failed || e.after === void 0 || !context) return context;
|
|
63471
|
-
return { used: e.after,
|
|
63504
|
+
return { ...context, used: e.after, exactness: "estimate" };
|
|
63472
63505
|
default:
|
|
63473
63506
|
return context;
|
|
63474
63507
|
}
|
|
@@ -65186,6 +65219,8 @@ var METHOD_RIGHTS = {
|
|
|
65186
65219
|
"sessions.list": "everyday",
|
|
65187
65220
|
"sessions.rename": "everyday",
|
|
65188
65221
|
"sessions.markRead": "everyday",
|
|
65222
|
+
// Hiding a failure is the person's (an agent's script may not clear what went wrong with it)
|
|
65223
|
+
"sessions.clearError": "window",
|
|
65189
65224
|
// The trash: reading and restoring run; deleting for good asks
|
|
65190
65225
|
"trash.list": "everyday",
|
|
65191
65226
|
"trash.readRun": "everyday",
|
|
@@ -103624,6 +103659,19 @@ var ModelWindows = class {
|
|
|
103624
103659
|
if (known && known.window === window2 && known.model === (resolved ?? null)) return;
|
|
103625
103660
|
this.write(key, { window: window2, model: resolved ?? null });
|
|
103626
103661
|
}
|
|
103662
|
+
/**
|
|
103663
|
+
* The window a session's context share is read against (#1553): the model's full window, whatever the process reported. Asked
|
|
103664
|
+
* right after `note` took the same reading. A reading under the process's ceiling is the model's own window; one at it is a
|
|
103665
|
+
* floor, and the model's window is then what `note` kept (the record for this model), or unknown: the reading stands in as
|
|
103666
|
+
* `capped`, which the screen says. A tool with no ceiling (Codex, a process started without the setting) reports the model's own.
|
|
103667
|
+
*/
|
|
103668
|
+
shareWindow(m, reported) {
|
|
103669
|
+
const ceiling = this.ceilings.get(m.id);
|
|
103670
|
+
const atCeiling = ceiling === "unknown" || ceiling !== void 0 && reported >= ceiling;
|
|
103671
|
+
const known = atCeiling ? this.of(m) : null;
|
|
103672
|
+
if (!atCeiling) return { window: reported, capped: false };
|
|
103673
|
+
return known === null ? { window: reported, capped: true } : { window: Math.max(known, reported), capped: false };
|
|
103674
|
+
}
|
|
103627
103675
|
/** The model's window, or null when no session has read one */
|
|
103628
103676
|
of(m) {
|
|
103629
103677
|
return this.read(this.key(m))?.window ?? null;
|
|
@@ -104106,7 +104154,7 @@ var NOT_CONNECTED = "not connected until the person connects it";
|
|
|
104106
104154
|
var NOT_CONNECTED_PHRASE = "is not connected, and nothing here connects it by itself; ask the person to connect it (Settings, Machines, Connect)";
|
|
104107
104155
|
function awayPhrase(why) {
|
|
104108
104156
|
if (why === NOT_CONNECTED) return NOT_CONNECTED_PHRASE;
|
|
104109
|
-
return why === NOT_REACHABLE ? `is ${
|
|
104157
|
+
return why === NOT_REACHABLE || why.startsWith(`${NOT_REACHABLE};`) ? `is ${why}` : `could not be asked just now (${why})`;
|
|
104110
104158
|
}
|
|
104111
104159
|
|
|
104112
104160
|
// packages/agent-host/src/adapters/contract/surface.ts
|
|
@@ -104115,6 +104163,168 @@ var SURFACE_KINDS = ["command", "message", "capability"];
|
|
|
104115
104163
|
// packages/agent-host/src/adapters/contract.ts
|
|
104116
104164
|
var INTERRUPTED_BY_TOOL = "interrupted_by_tool";
|
|
104117
104165
|
|
|
104166
|
+
// packages/agent-host/src/sessions/ask-project.ts
|
|
104167
|
+
import { realpathSync as realpathSync3, statSync as statSync8 } from "node:fs";
|
|
104168
|
+
import { resolve as resolve7 } from "node:path";
|
|
104169
|
+
|
|
104170
|
+
// packages/agent-host/src/sessions/turn-answer.ts
|
|
104171
|
+
function turnAnswer(messages2, opts = {}) {
|
|
104172
|
+
const parts = [];
|
|
104173
|
+
for (let i = messages2.length - 1; i >= 0; i--) {
|
|
104174
|
+
const m = messages2[i];
|
|
104175
|
+
if (opts.afterSeq !== void 0 && m.seq <= opts.afterSeq) break;
|
|
104176
|
+
if (m.role === "assistant" && m.kind === "reasoning") continue;
|
|
104177
|
+
if (m.role === "assistant" && m.kind === "text") {
|
|
104178
|
+
parts.unshift(String(m.payload.text ?? ""));
|
|
104179
|
+
continue;
|
|
104180
|
+
}
|
|
104181
|
+
break;
|
|
104182
|
+
}
|
|
104183
|
+
return parts.join("\n\n").trim();
|
|
104184
|
+
}
|
|
104185
|
+
function turnAnswerSeq(messages2, opts = {}) {
|
|
104186
|
+
let first = null;
|
|
104187
|
+
for (let i = messages2.length - 1; i >= 0; i--) {
|
|
104188
|
+
const m = messages2[i];
|
|
104189
|
+
if (opts.afterSeq !== void 0 && m.seq <= opts.afterSeq) break;
|
|
104190
|
+
if (m.role === "assistant" && m.kind === "reasoning") continue;
|
|
104191
|
+
if (m.role === "assistant" && m.kind === "text") {
|
|
104192
|
+
first = m.seq;
|
|
104193
|
+
continue;
|
|
104194
|
+
}
|
|
104195
|
+
break;
|
|
104196
|
+
}
|
|
104197
|
+
return first;
|
|
104198
|
+
}
|
|
104199
|
+
function lastResponseSeq(messages2) {
|
|
104200
|
+
let end = messages2.length;
|
|
104201
|
+
while (end > 0 && !(messages2[end - 1].role === "assistant" && messages2[end - 1].kind === "text")) end--;
|
|
104202
|
+
return turnAnswerSeq(messages2.slice(0, end));
|
|
104203
|
+
}
|
|
104204
|
+
function lastResponse(messages2) {
|
|
104205
|
+
let end = messages2.length;
|
|
104206
|
+
while (end > 0 && !(messages2[end - 1].role === "assistant" && messages2[end - 1].kind === "text")) end--;
|
|
104207
|
+
return turnAnswer(messages2.slice(0, end));
|
|
104208
|
+
}
|
|
104209
|
+
function clipPreview(text2, max) {
|
|
104210
|
+
return text2.length > max ? `${text2.slice(0, max)}\u2026` : text2;
|
|
104211
|
+
}
|
|
104212
|
+
|
|
104213
|
+
// packages/agent-host/src/sessions/ask-project.ts
|
|
104214
|
+
var ASK_WAIT_MS = 24e4;
|
|
104215
|
+
var ANSWER_READ_MAX_CHARS = 1e5;
|
|
104216
|
+
var ANSWER_MAX_CHARS = 6e3;
|
|
104217
|
+
var ANSWER_HEAD_CHARS = 4500;
|
|
104218
|
+
var ANSWER_TAIL_CHARS = 1200;
|
|
104219
|
+
function taskLine(task, max = 160) {
|
|
104220
|
+
const flat = oneLineText(task);
|
|
104221
|
+
return flat.length > max ? `${flat.slice(0, max)}\u2026 (cut here; ${flat.length} characters in all)` : flat;
|
|
104222
|
+
}
|
|
104223
|
+
function askFrame(callerProject, task, asker) {
|
|
104224
|
+
const who = asker ? ` It is the session ${JSON.stringify(frameField(asker.name))}${asker.sessionId ? ` [${asker.sessionId}]` : ""}.` : "";
|
|
104225
|
+
return [
|
|
104226
|
+
`[A session in the project "${frameField(callerProject)}" asks this project, through Centralu's ask_project.${who} Your final message goes back to it as the answer: keep it short, and name every file it should read by its absolute path.]`,
|
|
104227
|
+
"",
|
|
104228
|
+
task
|
|
104229
|
+
].join("\n");
|
|
104230
|
+
}
|
|
104231
|
+
function projectChoice(p) {
|
|
104232
|
+
return `${p.name} [${p.id}] on ${p.machine ?? "this computer"}`;
|
|
104233
|
+
}
|
|
104234
|
+
function matchProjects(asked, projects, o = {}) {
|
|
104235
|
+
const wanted = asked.trim();
|
|
104236
|
+
const firstPerMachine = (found) => {
|
|
104237
|
+
if (o.everyOne) return found;
|
|
104238
|
+
const seen = /* @__PURE__ */ new Set();
|
|
104239
|
+
return found.filter((p) => !seen.has(p.machineId) && !!seen.add(p.machineId));
|
|
104240
|
+
};
|
|
104241
|
+
const tiers = [
|
|
104242
|
+
() => projects.filter((p) => p.id === wanted),
|
|
104243
|
+
() => projects.filter((p) => p.name === wanted),
|
|
104244
|
+
() => {
|
|
104245
|
+
const slash = wanted.indexOf("/");
|
|
104246
|
+
if (slash <= 0) return [];
|
|
104247
|
+
const machine = wanted.slice(0, slash).trim().toLowerCase();
|
|
104248
|
+
const name = wanted.slice(slash + 1).trim();
|
|
104249
|
+
const there = projects.filter((p) => p.machineId !== null && (p.machineId === machine || p.machine?.toLowerCase() === machine));
|
|
104250
|
+
return there.some((p) => p.name === name) ? there.filter((p) => p.name === name) : there.filter((p) => p.name.toLowerCase() === name.toLowerCase());
|
|
104251
|
+
},
|
|
104252
|
+
() => projects.filter((p) => p.name.toLowerCase() === wanted.toLowerCase())
|
|
104253
|
+
];
|
|
104254
|
+
for (const tier of tiers) {
|
|
104255
|
+
const found = firstPerMachine(tier());
|
|
104256
|
+
if (found.length) return found;
|
|
104257
|
+
}
|
|
104258
|
+
return [];
|
|
104259
|
+
}
|
|
104260
|
+
function clipAnswer(answer, sessionName, at2) {
|
|
104261
|
+
if (answer.length <= ANSWER_MAX_CHARS) return answer;
|
|
104262
|
+
const cut2 = answer.length - ANSWER_HEAD_CHARS - ANSWER_TAIL_CHARS;
|
|
104263
|
+
const where2 = at2 ? `read them with read_session(sessionId="${at2.sessionId}", around=${at2.seq}, offset=${ANSWER_HEAD_CHARS})` : `the whole answer is in the session "${sessionName}"`;
|
|
104264
|
+
return answer.slice(0, ANSWER_HEAD_CHARS) + `
|
|
104265
|
+
\u2026[${cut2} characters cut here; ${where2}]\u2026
|
|
104266
|
+
` + answer.slice(-ANSWER_TAIL_CHARS);
|
|
104267
|
+
}
|
|
104268
|
+
function answerAt(reader, sessionId, answer) {
|
|
104269
|
+
const rows3 = reader.loadMessages(sessionId, 50);
|
|
104270
|
+
const seq = turnAnswerSeq(rows3);
|
|
104271
|
+
return seq !== null && answer && turnAnswer(rows3) === answer ? { sessionId, seq } : void 0;
|
|
104272
|
+
}
|
|
104273
|
+
function pathsIn(answer) {
|
|
104274
|
+
const found = [];
|
|
104275
|
+
const re2 = /(?:^|[\s"'`(<[])((?:\/|[A-Za-z]:\\)[^\s"'`)>\]]+)/g;
|
|
104276
|
+
for (const m of answer.matchAll(re2)) {
|
|
104277
|
+
const p = m[1].replace(/[.,;:!?]+$/, "");
|
|
104278
|
+
if (p === "/" || /^[A-Za-z]:\\?$/.test(p)) continue;
|
|
104279
|
+
if (!found.includes(p)) found.push(p);
|
|
104280
|
+
}
|
|
104281
|
+
return found;
|
|
104282
|
+
}
|
|
104283
|
+
function real(p) {
|
|
104284
|
+
try {
|
|
104285
|
+
return realpathSync3.native(p);
|
|
104286
|
+
} catch {
|
|
104287
|
+
return null;
|
|
104288
|
+
}
|
|
104289
|
+
}
|
|
104290
|
+
function readableGrants(paths, projectRoot) {
|
|
104291
|
+
const root3 = real(projectRoot);
|
|
104292
|
+
const granted = [];
|
|
104293
|
+
const outside = [];
|
|
104294
|
+
for (const p of paths) {
|
|
104295
|
+
const r = real(p);
|
|
104296
|
+
if (!root3 || !r || !isInside(root3, r)) {
|
|
104297
|
+
outside.push(p);
|
|
104298
|
+
continue;
|
|
104299
|
+
}
|
|
104300
|
+
let dir = false;
|
|
104301
|
+
try {
|
|
104302
|
+
dir = statSync8(r).isDirectory();
|
|
104303
|
+
} catch {
|
|
104304
|
+
outside.push(p);
|
|
104305
|
+
continue;
|
|
104306
|
+
}
|
|
104307
|
+
if (dir && r === root3) {
|
|
104308
|
+
outside.push(p);
|
|
104309
|
+
continue;
|
|
104310
|
+
}
|
|
104311
|
+
if (!granted.includes(r)) granted.push(r);
|
|
104312
|
+
}
|
|
104313
|
+
return { granted, outside };
|
|
104314
|
+
}
|
|
104315
|
+
function underGrant(path2, grants, cwd) {
|
|
104316
|
+
const r = canonicalPath(resolve7(cwd, path2));
|
|
104317
|
+
for (const g5 of grants) if (isInside(g5, r)) return true;
|
|
104318
|
+
return false;
|
|
104319
|
+
}
|
|
104320
|
+
function awayFor(wanted, away, known) {
|
|
104321
|
+
const slash = wanted.indexOf("/");
|
|
104322
|
+
const prefix = slash > 0 ? wanted.slice(0, slash).trim().toLowerCase() : null;
|
|
104323
|
+
const byId = [...known, ...away.map((a) => a.machineId)].some((id3) => wanted.startsWith(`${id3}.`));
|
|
104324
|
+
if (prefix === null && !byId) return [...away];
|
|
104325
|
+
return away.filter((a) => a.machineId === prefix || a.machine.toLowerCase() === prefix || wanted.startsWith(`${a.machineId}.`));
|
|
104326
|
+
}
|
|
104327
|
+
|
|
104118
104328
|
// packages/agent-host/src/sessions/app-agents.ts
|
|
104119
104329
|
var AgentRunWait = class {
|
|
104120
104330
|
constructor(notify, sessionName, onUsage = () => {
|
|
@@ -104221,11 +104431,11 @@ var AgentRunWait = class {
|
|
|
104221
104431
|
|
|
104222
104432
|
// packages/agent-host/src/sessions/worktree-sessions.ts
|
|
104223
104433
|
import { dirname as dirname14, join as join27 } from "node:path";
|
|
104224
|
-
import { lstatSync as lstatSync8, statSync as
|
|
104434
|
+
import { lstatSync as lstatSync8, statSync as statSync9 } from "node:fs";
|
|
104225
104435
|
import { exec as exec2 } from "node:child_process";
|
|
104226
104436
|
|
|
104227
104437
|
// packages/agent-host/src/dev-services/git.ts
|
|
104228
|
-
import { isAbsolute as isAbsolute3, relative as relative2, resolve as
|
|
104438
|
+
import { isAbsolute as isAbsolute3, relative as relative2, resolve as resolve8 } from "node:path";
|
|
104229
104439
|
function git(cwd, args, trust = {}) {
|
|
104230
104440
|
return runGit(cwd, args, { allowRepoPrograms: trust.trusted === true });
|
|
104231
104441
|
}
|
|
@@ -104319,8 +104529,8 @@ function assertLexicalGitPath(cwd, path2) {
|
|
|
104319
104529
|
if (!path2 || path2.includes("\0") || path2.startsWith(":")) {
|
|
104320
104530
|
throw rpcError("Invalid git path");
|
|
104321
104531
|
}
|
|
104322
|
-
const root3 =
|
|
104323
|
-
const target = isAbsolute3(path2) ?
|
|
104532
|
+
const root3 = resolve8(cwd);
|
|
104533
|
+
const target = isAbsolute3(path2) ? resolve8(path2) : resolve8(root3, path2);
|
|
104324
104534
|
const rel = relative2(root3, target);
|
|
104325
104535
|
if (rel === "") throw rpcError("Path is the project root, not a file in it");
|
|
104326
104536
|
if (isInside(root3, target)) return rel;
|
|
@@ -104830,7 +105040,7 @@ async function prepareWorktreeDependencies(projectCwd, worktreeCwd) {
|
|
|
104830
105040
|
// packages/agent-host/src/adapters/edit-grant.ts
|
|
104831
105041
|
import { lstat as lstat7, realpath as realpath5 } from "node:fs/promises";
|
|
104832
105042
|
import { homedir as homedir4 } from "node:os";
|
|
104833
|
-
import { dirname as dirname13, isAbsolute as isAbsolute4, join as join26, relative as relative3, resolve as
|
|
105043
|
+
import { dirname as dirname13, isAbsolute as isAbsolute4, join as join26, relative as relative3, resolve as resolve9, sep as sep4 } from "node:path";
|
|
104834
105044
|
var PROTECTED_NAMES = /* @__PURE__ */ new Set([
|
|
104835
105045
|
".git",
|
|
104836
105046
|
".gitconfig",
|
|
@@ -104866,7 +105076,7 @@ var PROTECTED_NAMES = /* @__PURE__ */ new Set([
|
|
|
104866
105076
|
var PROTECTED_PAIRS = [[".github", "workflows"], [".cargo", "config"], [".cargo", "config.toml"]];
|
|
104867
105077
|
async function realPathOf(path2) {
|
|
104868
105078
|
const tail = [];
|
|
104869
|
-
let at2 =
|
|
105079
|
+
let at2 = resolve9(path2);
|
|
104870
105080
|
for (; ; ) {
|
|
104871
105081
|
try {
|
|
104872
105082
|
return join26(await realpath5(at2), ...tail.reverse());
|
|
@@ -104893,7 +105103,7 @@ async function insideFolder(folder, targets) {
|
|
|
104893
105103
|
}
|
|
104894
105104
|
for (const target of targets) {
|
|
104895
105105
|
if (!target) return false;
|
|
104896
|
-
const real3 = await realPathOf(
|
|
105106
|
+
const real3 = await realPathOf(resolve9(folder, target));
|
|
104897
105107
|
if (real3 === null) return false;
|
|
104898
105108
|
const inside2 = relative3(root3, real3);
|
|
104899
105109
|
if (inside2 === "" || isAbsolute4(inside2) || inside2 === ".." || inside2.startsWith(`..${sep4}`)) return false;
|
|
@@ -105132,7 +105342,7 @@ function worktreeFolderState(path2) {
|
|
|
105132
105342
|
}
|
|
105133
105343
|
const parent = dirname14(path2);
|
|
105134
105344
|
try {
|
|
105135
|
-
if (
|
|
105345
|
+
if (statSync9(parent).isDirectory()) return "gone";
|
|
105136
105346
|
return { unreachable: `${parent} is not a folder` };
|
|
105137
105347
|
} catch (e) {
|
|
105138
105348
|
return { unreachable: `${parent} cannot be read (${e.code ?? "error"}), so the folder may be on a volume that is not mounted` };
|
|
@@ -105600,49 +105810,6 @@ function scriptLabel(raw) {
|
|
|
105600
105810
|
return plain3 || "unnamed";
|
|
105601
105811
|
}
|
|
105602
105812
|
|
|
105603
|
-
// packages/agent-host/src/sessions/turn-answer.ts
|
|
105604
|
-
function turnAnswer(messages2, opts = {}) {
|
|
105605
|
-
const parts = [];
|
|
105606
|
-
for (let i = messages2.length - 1; i >= 0; i--) {
|
|
105607
|
-
const m = messages2[i];
|
|
105608
|
-
if (opts.afterSeq !== void 0 && m.seq <= opts.afterSeq) break;
|
|
105609
|
-
if (m.role === "assistant" && m.kind === "reasoning") continue;
|
|
105610
|
-
if (m.role === "assistant" && m.kind === "text") {
|
|
105611
|
-
parts.unshift(String(m.payload.text ?? ""));
|
|
105612
|
-
continue;
|
|
105613
|
-
}
|
|
105614
|
-
break;
|
|
105615
|
-
}
|
|
105616
|
-
return parts.join("\n\n").trim();
|
|
105617
|
-
}
|
|
105618
|
-
function turnAnswerSeq(messages2, opts = {}) {
|
|
105619
|
-
let first = null;
|
|
105620
|
-
for (let i = messages2.length - 1; i >= 0; i--) {
|
|
105621
|
-
const m = messages2[i];
|
|
105622
|
-
if (opts.afterSeq !== void 0 && m.seq <= opts.afterSeq) break;
|
|
105623
|
-
if (m.role === "assistant" && m.kind === "reasoning") continue;
|
|
105624
|
-
if (m.role === "assistant" && m.kind === "text") {
|
|
105625
|
-
first = m.seq;
|
|
105626
|
-
continue;
|
|
105627
|
-
}
|
|
105628
|
-
break;
|
|
105629
|
-
}
|
|
105630
|
-
return first;
|
|
105631
|
-
}
|
|
105632
|
-
function lastResponseSeq(messages2) {
|
|
105633
|
-
let end = messages2.length;
|
|
105634
|
-
while (end > 0 && !(messages2[end - 1].role === "assistant" && messages2[end - 1].kind === "text")) end--;
|
|
105635
|
-
return turnAnswerSeq(messages2.slice(0, end));
|
|
105636
|
-
}
|
|
105637
|
-
function lastResponse(messages2) {
|
|
105638
|
-
let end = messages2.length;
|
|
105639
|
-
while (end > 0 && !(messages2[end - 1].role === "assistant" && messages2[end - 1].kind === "text")) end--;
|
|
105640
|
-
return turnAnswer(messages2.slice(0, end));
|
|
105641
|
-
}
|
|
105642
|
-
function clipPreview(text2, max) {
|
|
105643
|
-
return text2.length > max ? `${text2.slice(0, max)}\u2026` : text2;
|
|
105644
|
-
}
|
|
105645
|
-
|
|
105646
105813
|
// packages/agent-host/src/sessions/transcript-view.ts
|
|
105647
105814
|
var MEMORY_MESSAGES = 40;
|
|
105648
105815
|
var MEMORY_LINE_CHARS = 600;
|
|
@@ -106011,10 +106178,29 @@ function orchestratorToolsFor(host, orchestratorId, scopeIds, ownProject, hubVie
|
|
|
106011
106178
|
},
|
|
106012
106179
|
createSession: async (opts) => {
|
|
106013
106180
|
const all = host.store.projects.listProjects();
|
|
106014
|
-
const local = all.filter((p) => p.id === opts.project || p.name === opts.project);
|
|
106015
106181
|
const reader = linked();
|
|
106016
|
-
const
|
|
106017
|
-
const
|
|
106182
|
+
const wanted = (opts.project ?? "").trim();
|
|
106183
|
+
const exactHere = all.some((p) => p.id === wanted);
|
|
106184
|
+
const found = (machineProjects) => matchProjects(
|
|
106185
|
+
wanted,
|
|
106186
|
+
[
|
|
106187
|
+
...all.map((p) => ({ id: p.id, name: p.name, machine: null, machineId: null })),
|
|
106188
|
+
...machineProjects.map((p) => ({ id: p.id, name: p.name, machine: p.machine, machineId: p.machineId }))
|
|
106189
|
+
],
|
|
106190
|
+
{ everyOne: true }
|
|
106191
|
+
);
|
|
106192
|
+
let there = reader && wanted && !exactHere ? await reader.projects({ ref: wanted }) : null;
|
|
106193
|
+
let matched = found(there?.projects ?? []);
|
|
106194
|
+
if (reader && there && !matched.length) {
|
|
106195
|
+
there = await reader.projects({ ref: wanted, anyDown: true, prior: there });
|
|
106196
|
+
matched = found(there.projects);
|
|
106197
|
+
}
|
|
106198
|
+
const local = matched.flatMap((m) => all.filter((p) => m.machineId === null && p.id === m.id));
|
|
106199
|
+
const remote = matched.flatMap((m) => (there?.projects ?? []).filter((p) => m.machineId !== null && p.id === m.id));
|
|
106200
|
+
const missing = there && !matched.length ? awayFor(wanted, there.away, there.machines.map((m) => m.machineId)) : [];
|
|
106201
|
+
if (missing.length) {
|
|
106202
|
+
return { ok: false, error: `No such project: ${wanted}. Not asked, and its projects may be missing: ${missing.map((a) => `${a.machine} (${a.why})`).join(", ")}.` };
|
|
106203
|
+
}
|
|
106018
106204
|
if (local.length + remote.length > 1) {
|
|
106019
106205
|
const named = [
|
|
106020
106206
|
...local.map((p) => `${p.name} [${p.id}] on this computer`),
|
|
@@ -106494,8 +106680,8 @@ function askedQuestion(asker, act) {
|
|
|
106494
106680
|
}
|
|
106495
106681
|
|
|
106496
106682
|
// packages/agent-host/src/sessions/asked-reach.ts
|
|
106497
|
-
import { realpathSync as
|
|
106498
|
-
import { dirname as dirname15, isAbsolute as isAbsolute5, join as join28, relative as relative4, resolve as
|
|
106683
|
+
import { realpathSync as realpathSync4 } from "node:fs";
|
|
106684
|
+
import { dirname as dirname15, isAbsolute as isAbsolute5, join as join28, relative as relative4, resolve as resolve10 } from "node:path";
|
|
106499
106685
|
var GIVES_NOTHING = /* @__PURE__ */ new Set([
|
|
106500
106686
|
"Write",
|
|
106501
106687
|
"Edit",
|
|
@@ -106556,15 +106742,15 @@ function globPaths(path2, pattern) {
|
|
|
106556
106742
|
return out.length > 0 ? out : [""];
|
|
106557
106743
|
}
|
|
106558
106744
|
function pinned(scope) {
|
|
106559
|
-
return "paths" in scope ? { paths: scope.paths, real: scope.paths.map((p) => isAbsolute5(p) ?
|
|
106745
|
+
return "paths" in scope ? { paths: scope.paths, real: scope.paths.map((p) => isAbsolute5(p) ? real2(p) : p) } : scope;
|
|
106560
106746
|
}
|
|
106561
|
-
function
|
|
106562
|
-
const full =
|
|
106747
|
+
function real2(path2) {
|
|
106748
|
+
const full = resolve10(path2);
|
|
106563
106749
|
let existing = full;
|
|
106564
106750
|
const rest = [];
|
|
106565
106751
|
for (let i = 0; i < 64; i++) {
|
|
106566
106752
|
try {
|
|
106567
|
-
return join28(
|
|
106753
|
+
return join28(realpathSync4.native(existing), ...rest);
|
|
106568
106754
|
} catch {
|
|
106569
106755
|
const up2 = dirname15(existing);
|
|
106570
106756
|
if (up2 === existing) return full;
|
|
@@ -106593,10 +106779,10 @@ function outOfReach(name, scope, reach, projectOf) {
|
|
|
106593
106779
|
const mine = [reach.project, reach.projectName].filter((p) => typeof p === "string" && p !== "");
|
|
106594
106780
|
return mine.some((p) => p.toLowerCase() === asked) ? null : named(scope.project ?? "");
|
|
106595
106781
|
}
|
|
106596
|
-
const folders = reach.folders.filter((f) => isAbsolute5(f)).map(
|
|
106782
|
+
const folders = reach.folders.filter((f) => isAbsolute5(f)).map(real2);
|
|
106597
106783
|
for (const [i, path2] of scope.paths.entries()) {
|
|
106598
106784
|
if (!isAbsolute5(path2)) return path2 === "" ? name : named(path2);
|
|
106599
|
-
const there = scope.real?.[i] ??
|
|
106785
|
+
const there = scope.real?.[i] ?? real2(path2);
|
|
106600
106786
|
if (!folders.some((f) => inside(f, there))) return named(path2);
|
|
106601
106787
|
}
|
|
106602
106788
|
return null;
|
|
@@ -106734,114 +106920,6 @@ function withHubMark(settings, info) {
|
|
|
106734
106920
|
// packages/agent-host/src/sessions/queued-requests.ts
|
|
106735
106921
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
106736
106922
|
|
|
106737
|
-
// packages/agent-host/src/sessions/ask-project.ts
|
|
106738
|
-
import { realpathSync as realpathSync4, statSync as statSync9 } from "node:fs";
|
|
106739
|
-
import { resolve as resolve10 } from "node:path";
|
|
106740
|
-
var ASK_WAIT_MS = 24e4;
|
|
106741
|
-
var ANSWER_READ_MAX_CHARS = 1e5;
|
|
106742
|
-
var ANSWER_MAX_CHARS = 6e3;
|
|
106743
|
-
var ANSWER_HEAD_CHARS = 4500;
|
|
106744
|
-
var ANSWER_TAIL_CHARS = 1200;
|
|
106745
|
-
function taskLine(task, max = 160) {
|
|
106746
|
-
const flat = oneLineText(task);
|
|
106747
|
-
return flat.length > max ? `${flat.slice(0, max)}\u2026 (cut here; ${flat.length} characters in all)` : flat;
|
|
106748
|
-
}
|
|
106749
|
-
function askFrame(callerProject, task, asker) {
|
|
106750
|
-
const who = asker ? ` It is the session ${JSON.stringify(frameField(asker.name))}${asker.sessionId ? ` [${asker.sessionId}]` : ""}.` : "";
|
|
106751
|
-
return [
|
|
106752
|
-
`[A session in the project "${frameField(callerProject)}" asks this project, through Centralu's ask_project.${who} Your final message goes back to it as the answer: keep it short, and name every file it should read by its absolute path.]`,
|
|
106753
|
-
"",
|
|
106754
|
-
task
|
|
106755
|
-
].join("\n");
|
|
106756
|
-
}
|
|
106757
|
-
function projectChoice(p) {
|
|
106758
|
-
return `${p.name} [${p.id}] on ${p.machine ?? "this computer"}`;
|
|
106759
|
-
}
|
|
106760
|
-
function matchProjects(wanted, projects) {
|
|
106761
|
-
const firstPerMachine = (found) => {
|
|
106762
|
-
const seen = /* @__PURE__ */ new Set();
|
|
106763
|
-
return found.filter((p) => !seen.has(p.machineId) && !!seen.add(p.machineId));
|
|
106764
|
-
};
|
|
106765
|
-
const tiers = [
|
|
106766
|
-
() => projects.filter((p) => p.id === wanted),
|
|
106767
|
-
() => projects.filter((p) => p.name === wanted),
|
|
106768
|
-
() => {
|
|
106769
|
-
const slash = wanted.indexOf("/");
|
|
106770
|
-
if (slash <= 0) return [];
|
|
106771
|
-
const machine = wanted.slice(0, slash).trim().toLowerCase();
|
|
106772
|
-
const name = wanted.slice(slash + 1).trim();
|
|
106773
|
-
const there = projects.filter((p) => p.machineId !== null && (p.machineId === machine || p.machine?.toLowerCase() === machine));
|
|
106774
|
-
return there.some((p) => p.name === name) ? there.filter((p) => p.name === name) : there.filter((p) => p.name.toLowerCase() === name.toLowerCase());
|
|
106775
|
-
},
|
|
106776
|
-
() => projects.filter((p) => p.name.toLowerCase() === wanted.toLowerCase())
|
|
106777
|
-
];
|
|
106778
|
-
for (const tier of tiers) {
|
|
106779
|
-
const found = firstPerMachine(tier());
|
|
106780
|
-
if (found.length) return found;
|
|
106781
|
-
}
|
|
106782
|
-
return [];
|
|
106783
|
-
}
|
|
106784
|
-
function clipAnswer(answer, sessionName, at2) {
|
|
106785
|
-
if (answer.length <= ANSWER_MAX_CHARS) return answer;
|
|
106786
|
-
const cut2 = answer.length - ANSWER_HEAD_CHARS - ANSWER_TAIL_CHARS;
|
|
106787
|
-
const where2 = at2 ? `read them with read_session(sessionId="${at2.sessionId}", around=${at2.seq}, offset=${ANSWER_HEAD_CHARS})` : `the whole answer is in the session "${sessionName}"`;
|
|
106788
|
-
return answer.slice(0, ANSWER_HEAD_CHARS) + `
|
|
106789
|
-
\u2026[${cut2} characters cut here; ${where2}]\u2026
|
|
106790
|
-
` + answer.slice(-ANSWER_TAIL_CHARS);
|
|
106791
|
-
}
|
|
106792
|
-
function answerAt(reader, sessionId, answer) {
|
|
106793
|
-
const rows3 = reader.loadMessages(sessionId, 50);
|
|
106794
|
-
const seq = turnAnswerSeq(rows3);
|
|
106795
|
-
return seq !== null && answer && turnAnswer(rows3) === answer ? { sessionId, seq } : void 0;
|
|
106796
|
-
}
|
|
106797
|
-
function pathsIn(answer) {
|
|
106798
|
-
const found = [];
|
|
106799
|
-
const re2 = /(?:^|[\s"'`(<[])((?:\/|[A-Za-z]:\\)[^\s"'`)>\]]+)/g;
|
|
106800
|
-
for (const m of answer.matchAll(re2)) {
|
|
106801
|
-
const p = m[1].replace(/[.,;:!?]+$/, "");
|
|
106802
|
-
if (p === "/" || /^[A-Za-z]:\\?$/.test(p)) continue;
|
|
106803
|
-
if (!found.includes(p)) found.push(p);
|
|
106804
|
-
}
|
|
106805
|
-
return found;
|
|
106806
|
-
}
|
|
106807
|
-
function real2(p) {
|
|
106808
|
-
try {
|
|
106809
|
-
return realpathSync4.native(p);
|
|
106810
|
-
} catch {
|
|
106811
|
-
return null;
|
|
106812
|
-
}
|
|
106813
|
-
}
|
|
106814
|
-
function readableGrants(paths, projectRoot) {
|
|
106815
|
-
const root3 = real2(projectRoot);
|
|
106816
|
-
const granted = [];
|
|
106817
|
-
const outside = [];
|
|
106818
|
-
for (const p of paths) {
|
|
106819
|
-
const r = real2(p);
|
|
106820
|
-
if (!root3 || !r || !isInside(root3, r)) {
|
|
106821
|
-
outside.push(p);
|
|
106822
|
-
continue;
|
|
106823
|
-
}
|
|
106824
|
-
let dir = false;
|
|
106825
|
-
try {
|
|
106826
|
-
dir = statSync9(r).isDirectory();
|
|
106827
|
-
} catch {
|
|
106828
|
-
outside.push(p);
|
|
106829
|
-
continue;
|
|
106830
|
-
}
|
|
106831
|
-
if (dir && r === root3) {
|
|
106832
|
-
outside.push(p);
|
|
106833
|
-
continue;
|
|
106834
|
-
}
|
|
106835
|
-
if (!granted.includes(r)) granted.push(r);
|
|
106836
|
-
}
|
|
106837
|
-
return { granted, outside };
|
|
106838
|
-
}
|
|
106839
|
-
function underGrant(path2, grants, cwd) {
|
|
106840
|
-
const r = canonicalPath(resolve10(cwd, path2));
|
|
106841
|
-
for (const g5 of grants) if (isInside(g5, r)) return true;
|
|
106842
|
-
return false;
|
|
106843
|
-
}
|
|
106844
|
-
|
|
106845
106923
|
// packages/agent-host/src/sessions/interruptions.ts
|
|
106846
106924
|
var NETWORK_WORDS = [
|
|
106847
106925
|
/\b(ECONNREFUSED|ENOTFOUND|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENETUNREACH|EHOSTUNREACH|ENETDOWN)\b/,
|
|
@@ -113241,11 +113319,12 @@ var RemoteAgents = class {
|
|
|
113241
113319
|
if (!origin || !key) {
|
|
113242
113320
|
return { error: `This project has ${origin ? "a local folder as its origin remote" : "no origin remote"}, so no checkout of it on ${machine.name} can be told apart. Start the agent here, without machine.` };
|
|
113243
113321
|
}
|
|
113244
|
-
const all = await reader.projects();
|
|
113322
|
+
const all = await reader.projects({ machineId: machine.id });
|
|
113245
113323
|
const away = all.away.find((a) => a.machineId === machine.id);
|
|
113246
113324
|
if (away) {
|
|
113247
113325
|
if (away.why === NOT_CONNECTED) return { error: `${machine.name} ${awayPhrase(NOT_CONNECTED)}, so nothing was started there. Try again once it is connected.` };
|
|
113248
|
-
|
|
113326
|
+
const cannot = away.why.startsWith(`${NOT_REACHABLE};`) ? `is ${away.why}` : `cannot be reached (${away.why})`;
|
|
113327
|
+
return { error: `${machine.name} ${cannot}, so nothing was started there. Try again once its link is back.` };
|
|
113249
113328
|
}
|
|
113250
113329
|
const theirs = all.projects.filter((p) => p.machineId === machine.id);
|
|
113251
113330
|
const read = await Promise.all(theirs.map(async (p) => ({ p, r: await reader.origin(p) })));
|
|
@@ -119428,6 +119507,10 @@ var SessionStarts = class {
|
|
|
119428
119507
|
autoCompactFor(m, adopted) {
|
|
119429
119508
|
const { tokens } = autoCompactPlan(autoCompactOf(this.host.uiPreferences()), this.host.modelWindows, m);
|
|
119430
119509
|
this.host.modelWindows.started(m.id, tokens === null ? null : this.host.adapters.get(m.tool)?.windowCeilingFor?.(tokens) ?? null, adopted);
|
|
119510
|
+
if (m.context?.compactAt !== void 0) {
|
|
119511
|
+
const { compactAt: _before, ...rest } = m.context;
|
|
119512
|
+
m.context = rest;
|
|
119513
|
+
}
|
|
119431
119514
|
const kept = adopted ? this.host.modelWindows.ceilingOf(m.id) : null;
|
|
119432
119515
|
return { ...tokens === null ? {} : { autoCompactTokens: tokens }, ...kept === null ? {} : { autoCompactCeiling: kept } };
|
|
119433
119516
|
}
|
|
@@ -120320,7 +120403,15 @@ var ContextMeter = class {
|
|
|
120320
120403
|
if (used === m.context.used || now - s.at < ESTIMATE_GAP_MS) return null;
|
|
120321
120404
|
s.at = now;
|
|
120322
120405
|
s.estimated = true;
|
|
120323
|
-
return {
|
|
120406
|
+
return {
|
|
120407
|
+
type: "context_update",
|
|
120408
|
+
sessionId: m.id,
|
|
120409
|
+
used,
|
|
120410
|
+
window: m.context.window,
|
|
120411
|
+
exactness: "estimate",
|
|
120412
|
+
...m.context.capped ? { capped: true } : {},
|
|
120413
|
+
...m.context.compactAt !== void 0 ? { compactAt: m.context.compactAt } : {}
|
|
120414
|
+
};
|
|
120324
120415
|
}
|
|
120325
120416
|
};
|
|
120326
120417
|
|
|
@@ -120388,7 +120479,7 @@ var SessionEvents = class {
|
|
|
120388
120479
|
meter = new ContextMeter();
|
|
120389
120480
|
/** Receive event -> update metadata -> persist message -> broadcast */
|
|
120390
120481
|
onEvent(raw, from = null) {
|
|
120391
|
-
|
|
120482
|
+
let e = raw.type === "files_touched" ? this.projectRelative(raw) : raw;
|
|
120392
120483
|
if (from && e.sessionId && this.host.handles.get(e.sessionId) !== from && e.type !== "approval_resolved" && e.type !== "question_resolved" && e.type !== "tool_result" && e.type !== "background_tasks") {
|
|
120393
120484
|
return;
|
|
120394
120485
|
}
|
|
@@ -120410,6 +120501,7 @@ var SessionEvents = class {
|
|
|
120410
120501
|
this.host.compactEvent(e, false);
|
|
120411
120502
|
return;
|
|
120412
120503
|
}
|
|
120504
|
+
if (e.type === "context_update" && e.exactness !== "estimate") e = this.shareOf(e);
|
|
120413
120505
|
if (e.type === "settings_changed" && e.by === "tool") {
|
|
120414
120506
|
this.host.record.recordToolSettings(e);
|
|
120415
120507
|
return;
|
|
@@ -120505,6 +120597,21 @@ var SessionEvents = class {
|
|
|
120505
120597
|
if (e.type === "background_tasks" && e.sessionId) this.host.restartPolicy.applyDeferredRestart(e.sessionId);
|
|
120506
120598
|
if (e.type === "background_tasks" && e.sessionId && this.host.children.pausedReports.has(e.sessionId)) this.host.reports.settlePausedReport(e.sessionId);
|
|
120507
120599
|
}
|
|
120600
|
+
/**
|
|
120601
|
+
* The tool's reading of the context, as the rest of the host and the screens see it (#1553): `window` is the model's full window
|
|
120602
|
+
* (`ModelWindows.shareWindow`), `capped` says it is not known, `compactAt` is where the adapter says the session's process compacts. The reading
|
|
120603
|
+
* itself is what `ModelWindows` learns the model's window from, so it is noted first, against the window the tool reported. An
|
|
120604
|
+
* estimate from a request (#1492) is no reading of the model's window and carries what the last reading did (`ContextMeter`).
|
|
120605
|
+
*/
|
|
120606
|
+
shareOf(e) {
|
|
120607
|
+
const m = this.host.meta.get(e.sessionId);
|
|
120608
|
+
if (!m) return e;
|
|
120609
|
+
this.host.modelWindows.note(m, e.window, e.model);
|
|
120610
|
+
const { window: window2, capped } = this.host.modelWindows.shareWindow(m, e.window);
|
|
120611
|
+
const { compactAt: said, ...rest } = e;
|
|
120612
|
+
const compactAt = said !== void 0 && said > 0 && said < window2 ? said : null;
|
|
120613
|
+
return { ...rest, window: window2, ...capped ? { capped: true } : {}, ...compactAt !== null ? { compactAt } : {} };
|
|
120614
|
+
}
|
|
120508
120615
|
/** A state hint, for storage: the core reducer's rules on the host's copy (`applySessionState`, protocol session-state.ts) */
|
|
120509
120616
|
applyStateHint(e, m) {
|
|
120510
120617
|
const from = m.state;
|
|
@@ -120557,7 +120664,6 @@ var SessionEvents = class {
|
|
|
120557
120664
|
break;
|
|
120558
120665
|
case "context_update":
|
|
120559
120666
|
m.context = nextContext(m.context, e);
|
|
120560
|
-
if (e.exactness !== "estimate") this.host.modelWindows.note(m, e.window, e.model);
|
|
120561
120667
|
break;
|
|
120562
120668
|
case "conversation_reset":
|
|
120563
120669
|
m.context = nextContext(m.context, e);
|
|
@@ -120995,7 +121101,8 @@ var SelfCompaction = class {
|
|
|
120995
121101
|
if (now - since < IDLE_COMPACT_MINUTES * 6e4) return false;
|
|
120996
121102
|
const ctx = m.context;
|
|
120997
121103
|
if (!ctx || ctx.window <= 0) return false;
|
|
120998
|
-
const window2 = this.host.window(m)
|
|
121104
|
+
const window2 = this.host.window(m);
|
|
121105
|
+
if (window2 === null || window2 <= 0) return false;
|
|
120999
121106
|
return ctx.used >= COMPACT_FLOOR_TOKENS && ctx.used >= window2 * setting.idleShare;
|
|
121000
121107
|
}
|
|
121001
121108
|
/** Starts the sweep. A host that never calls it still serves `compact_self` */
|
|
@@ -121060,7 +121167,7 @@ var ContextNudges = class {
|
|
|
121060
121167
|
}
|
|
121061
121168
|
cycle(id3) {
|
|
121062
121169
|
let c = this.cycles.get(id3);
|
|
121063
|
-
if (!c) this.cycles.set(id3, c = { told: /* @__PURE__ */ new Set(), waiting: null, size: 0, sending: false });
|
|
121170
|
+
if (!c) this.cycles.set(id3, c = { told: /* @__PURE__ */ new Set(), waiting: null, size: 0, sending: false, point: null });
|
|
121064
121171
|
return c;
|
|
121065
121172
|
}
|
|
121066
121173
|
requested(id3, size) {
|
|
@@ -121068,25 +121175,31 @@ var ContextNudges = class {
|
|
|
121068
121175
|
const setting = this.host.setting();
|
|
121069
121176
|
const m = this.host.session(id3);
|
|
121070
121177
|
if (!m || !setting.controlled || !setting.nudge || !this.host.hasCompactTool(id3)) return;
|
|
121071
|
-
const window2 = this.host.window(m)
|
|
121072
|
-
if (window2 <= 0) return;
|
|
121178
|
+
const window2 = this.host.window(m);
|
|
121179
|
+
if (window2 === null || window2 <= 0) return;
|
|
121073
121180
|
const c = this.cycle(id3);
|
|
121074
121181
|
c.size = size;
|
|
121075
121182
|
const share = size / window2;
|
|
121076
|
-
const
|
|
121077
|
-
|
|
121183
|
+
const point = m.context?.compactAt !== void 0 && m.context.compactAt < window2 ? m.context.compactAt / window2 : null;
|
|
121184
|
+
c.point = point;
|
|
121185
|
+
const { first, second } = nudgeShares({ ...setting, share: point ?? setting.share });
|
|
121186
|
+
const shares = [
|
|
121187
|
+
["first", first],
|
|
121188
|
+
["second", second]
|
|
121189
|
+
];
|
|
121190
|
+
const crossed = shares.filter(([k2, at2]) => !c.told.has(k2) && (k2 === "first" ? at2 < (point ?? setting.share) : point !== null && at2 < point) && at2 <= share).map(([k2]) => k2);
|
|
121078
121191
|
if (crossed.length > 0) {
|
|
121079
121192
|
for (const k2 of crossed) c.told.add(k2);
|
|
121080
121193
|
c.waiting = crossed.includes("second") ? "second" : "first";
|
|
121081
121194
|
}
|
|
121082
|
-
if (c.waiting) this.deliver(id3, c, m, window2
|
|
121195
|
+
if (c.waiting) this.deliver(id3, c, m, window2);
|
|
121083
121196
|
}
|
|
121084
|
-
deliver(id3, c, m, window2
|
|
121197
|
+
deliver(id3, c, m, window2) {
|
|
121085
121198
|
const which = c.waiting;
|
|
121086
121199
|
if (!which || c.sending) return;
|
|
121087
121200
|
const percent = Math.round(c.size / window2 * 100);
|
|
121088
121201
|
const used = `${Math.round(c.size / 1e3)}k of ${Math.round(window2 / 1e3)}k tokens`;
|
|
121089
|
-
const text2 = which === "second" ? `[Centralu] Your context is now about ${percent}% full (${used}), and the tool compacts it by itself in the middle of work at ${Math.round(
|
|
121202
|
+
const text2 = which === "second" && c.point !== null ? `[Centralu] Your context is now about ${percent}% full (${used}), and the tool compacts it by itself in the middle of work at ${Math.round(c.point * 100)}%. Call compact_self when the current step is done, at the latest; do not stop what you are doing for it.` : `[Centralu] Your context is now about ${percent}% full (${used}). When this step of the work is done, call compact_self so the next step starts from a summary; do not stop what you are doing for it.`;
|
|
121090
121203
|
const failed3 = (err) => {
|
|
121091
121204
|
c.waiting = null;
|
|
121092
121205
|
console.error(`[compact] ${id3.slice(0, 8)}: could not send the context notice: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -121359,6 +121472,10 @@ function compactPointOff(passed, reported, modelWindow) {
|
|
|
121359
121472
|
if (reported < expected && modelWindow !== null && modelWindow < passed) return null;
|
|
121360
121473
|
return expected;
|
|
121361
121474
|
}
|
|
121475
|
+
function claudeCompactAt(opts, threshold) {
|
|
121476
|
+
const controlled = opts.processSource?.adopt ? opts.autoCompactCeiling !== void 0 : opts.autoCompactTokens !== void 0;
|
|
121477
|
+
return controlled && threshold > 0 ? threshold : void 0;
|
|
121478
|
+
}
|
|
121362
121479
|
function claudeAutoCompactWindow(tokens) {
|
|
121363
121480
|
return Math.min(CLAUDE_AUTO_COMPACT_MAX, Math.max(CLAUDE_AUTO_COMPACT_MIN, Math.round(tokens) + CLAUDE_AUTO_COMPACT_BUFFER));
|
|
121364
121481
|
}
|
|
@@ -125231,8 +125348,10 @@ var ClaudeSession = class {
|
|
|
125231
125348
|
this.contextWindow = window2 > 0 ? window2 : null;
|
|
125232
125349
|
if (window2 > 0 && used >= 0) {
|
|
125233
125350
|
const model = typeof usage?.model === "string" && usage.model ? usage.model : void 0;
|
|
125234
|
-
|
|
125235
|
-
this.
|
|
125351
|
+
const threshold = Number(usage?.autoCompactThreshold ?? 0);
|
|
125352
|
+
const compactAt = claudeCompactAt(this.opts, threshold);
|
|
125353
|
+
this.emit({ type: "context_update", sessionId: this.sessionId, used, window: window2, exactness: "exact", ...model ? { model } : {}, ...compactAt !== void 0 ? { compactAt } : {} });
|
|
125354
|
+
this.checkCompactPoint(threshold);
|
|
125236
125355
|
}
|
|
125237
125356
|
} catch {
|
|
125238
125357
|
}
|
|
@@ -127824,6 +127943,9 @@ async function initializeAppServer(client, sessionId, adopted, emit) {
|
|
|
127824
127943
|
function autoCompactConfig(opts) {
|
|
127825
127944
|
return opts.autoCompactTokens ? { model_auto_compact_token_limit: opts.autoCompactTokens } : {};
|
|
127826
127945
|
}
|
|
127946
|
+
function codexCompactAt(opts) {
|
|
127947
|
+
return opts.autoCompactTokens && !opts.processSource?.adopt ? opts.autoCompactTokens : void 0;
|
|
127948
|
+
}
|
|
127827
127949
|
function resumeThreadParams(opts, servers, workspaceConfig) {
|
|
127828
127950
|
return {
|
|
127829
127951
|
threadId: opts.resumeExternalId,
|
|
@@ -128360,7 +128482,9 @@ var CodexSession = class {
|
|
|
128360
128482
|
/** A notification of this thread's own, as the events it maps to (`normalizeNotification`), each the way it goes out */
|
|
128361
128483
|
emitNotification(n) {
|
|
128362
128484
|
for (const said of normalizeNotification(this.sessionId, n)) {
|
|
128363
|
-
const
|
|
128485
|
+
const sort = said.type === "compaction" ? { ...said, trigger: this.compactTurn ? "manual" : "auto" } : said;
|
|
128486
|
+
const compactAt = codexCompactAt(this.opts);
|
|
128487
|
+
const e = sort.type === "context_update" && compactAt !== void 0 ? { ...sort, compactAt } : sort;
|
|
128364
128488
|
if (e.type === "error" && this.inputReceipts.size) this.interrupt(new Error(e.error.message));
|
|
128365
128489
|
if (e.type === "message_image" && !e.data && e.path) {
|
|
128366
128490
|
void imageEventFromDisk(this.sessionId, e.path).then((filled2) => this.emit(filled2));
|
|
@@ -135714,16 +135838,21 @@ var AskProjectFlow = class {
|
|
|
135714
135838
|
const reader = this.host.linkedReader;
|
|
135715
135839
|
const exactHere = projects.some((p) => p.id === wanted);
|
|
135716
135840
|
const hubs2 = this.hubs;
|
|
135717
|
-
const none = { projects: [], away: [] };
|
|
135718
|
-
const [
|
|
135719
|
-
|
|
135841
|
+
const none = { projects: [], away: [], machines: [] };
|
|
135842
|
+
const [first, fromHubs] = (reader || hubs2) && !exactHere ? await Promise.all([reader ? reader.projects({ ref: wanted }) : none, hubs2 ? hubs2.projects() : none]) : [none, none];
|
|
135843
|
+
let there = first;
|
|
135844
|
+
const askableOf = () => [
|
|
135720
135845
|
...projects.map((p) => ({ id: p.id, name: p.name, machine: null, machineId: null })),
|
|
135721
135846
|
...there.projects.map((p) => ({ id: p.id, name: p.name, machine: p.machine, machineId: p.machineId })),
|
|
135722
135847
|
...fromHubs.projects.map((p) => ({ id: p.id, name: p.name, machine: p.hub, machineId: p.hubId }))
|
|
135723
135848
|
];
|
|
135849
|
+
let found = matchProjects(wanted, askableOf());
|
|
135850
|
+
if (!found.length && reader && !exactHere) {
|
|
135851
|
+
there = await reader.projects({ ref: wanted, anyDown: true, prior: there });
|
|
135852
|
+
found = matchProjects(wanted, askableOf());
|
|
135853
|
+
}
|
|
135724
135854
|
const awayAll = [...there.away, ...fromHubs.away.map((a) => ({ machineId: a.hubId, machine: a.hub, why: a.why }))];
|
|
135725
135855
|
const elsewhere = !!reader || fromHubs.projects.length > 0 || fromHubs.away.length > 0;
|
|
135726
|
-
const found = matchProjects(wanted, askable);
|
|
135727
135856
|
if (found.length > 1) {
|
|
135728
135857
|
return {
|
|
135729
135858
|
ok: false,
|
|
@@ -135740,7 +135869,8 @@ var AskProjectFlow = class {
|
|
|
135740
135869
|
...there.projects.map((p) => `${p.name} (on ${p.machine}${p.away ? `, ${p.away}` : ""})`),
|
|
135741
135870
|
...fromHubs.projects.map((p) => `${p.name} (on ${p.hub}${p.away ? `, ${p.away}` : ""})`)
|
|
135742
135871
|
];
|
|
135743
|
-
const
|
|
135872
|
+
const missing = awayFor(wanted, awayAll, [...there.machines.map((m) => m.machineId), ...fromHubs.projects.map((p) => p.hubId)]);
|
|
135873
|
+
const unasked = missing.length ? ` Not asked, and its projects may be missing: ${missing.map((a) => `${a.machine} (${a.why})`).join(", ")}.` : "";
|
|
135744
135874
|
return {
|
|
135745
135875
|
ok: false,
|
|
135746
135876
|
error: others.length ? `No project named "${wanted}". The other projects are: ${others.join(", ")}.${unasked}` : `No project named "${wanted}" \u2014 this is the only project in Centralu.${unasked}`
|
|
@@ -140450,6 +140580,21 @@ var SessionManager = class {
|
|
|
140450
140580
|
rename(...a) {
|
|
140451
140581
|
return this.sessionSettings.rename(...a);
|
|
140452
140582
|
}
|
|
140583
|
+
/**
|
|
140584
|
+
* Done on a session in error (#1549): the person has read why, and nothing is left to do about the mark. It goes to `idle`
|
|
140585
|
+
* (the table allows error → idle) through the statement a resume makes, so every window hears it and a new error marks the
|
|
140586
|
+
* session again. The error line stays in the conversation; nothing is stopped or sent. Its last answer is read, so a stopped
|
|
140587
|
+
* turn that waits unread in the inbox goes too.
|
|
140588
|
+
*/
|
|
140589
|
+
clearError(sessionId, seq) {
|
|
140590
|
+
const m = this.meta.get(sessionId);
|
|
140591
|
+
if (!m) throw sessionNotFound(sessionId);
|
|
140592
|
+
const newer = m.state === "error" && m.lastSeq > seq;
|
|
140593
|
+
const cleared = m.state === "error" && !newer;
|
|
140594
|
+
if (cleared) this.events.settleState(m, { type: "state_change", sessionId, state: "idle", reason: "error_cleared" });
|
|
140595
|
+
this.markRead(sessionId, seq);
|
|
140596
|
+
return { cleared, newer };
|
|
140597
|
+
}
|
|
140453
140598
|
/** `SessionSettings.markRead` (session-settings.ts), kept on the manager for its callers */
|
|
140454
140599
|
markRead(...a) {
|
|
140455
140600
|
return this.sessionSettings.markRead(...a);
|
|
@@ -143766,6 +143911,10 @@ function createRpcHandler(mgr2, adapters2, { settleDeletedProjects, deleteOutloo
|
|
|
143766
143911
|
mgr2.rename(sessionId, name);
|
|
143767
143912
|
return { ok: true };
|
|
143768
143913
|
},
|
|
143914
|
+
"sessions.clearError": async (p) => {
|
|
143915
|
+
const { sessionId, seq } = RpcMethods["sessions.clearError"].params.parse(p);
|
|
143916
|
+
return { ok: true, ...mgr2.clearError(sessionId, seq) };
|
|
143917
|
+
},
|
|
143769
143918
|
"sessions.markRead": async (p) => {
|
|
143770
143919
|
const { sessionId, seq } = RpcMethods["sessions.markRead"].params.parse(p);
|
|
143771
143920
|
mgr2.markRead(sessionId, seq);
|
|
@@ -144805,6 +144954,13 @@ var AUTO_RETRY_DELAYS_MS = [10 * 60 * 1e3, 60 * 60 * 1e3, 6 * 60 * 60 * 1e3];
|
|
|
144805
144954
|
var INSTALL_TIMEOUT_MS = 5 * 60 * 1e3;
|
|
144806
144955
|
var NPM_RETRY_DELAY_MS = 3e4;
|
|
144807
144956
|
var NPM_RETRY_WINDOW_MS = 10 * 60 * 1e3;
|
|
144957
|
+
var UNSTAMPED_VERSION = "0.0.0-dev";
|
|
144958
|
+
function installsByItself(build3, current) {
|
|
144959
|
+
return build3 !== "dev" && current !== UNSTAMPED_VERSION;
|
|
144960
|
+
}
|
|
144961
|
+
function hubUpdatesMachines(f) {
|
|
144962
|
+
return installsByItself(f.build, f.current) && f.autoApply && !f.draining;
|
|
144963
|
+
}
|
|
144808
144964
|
function existingInstalledCopy() {
|
|
144809
144965
|
const path2 = installedCopyPath();
|
|
144810
144966
|
return existsSync22(path2) ? path2 : null;
|
|
@@ -144855,6 +145011,8 @@ var UpdateService = class {
|
|
|
144855
145011
|
constructor(publish, deps = {}) {
|
|
144856
145012
|
this.publish = publish;
|
|
144857
145013
|
this.deps = {
|
|
145014
|
+
current: deps.current ?? APP_VERSION,
|
|
145015
|
+
build: deps.build ?? "",
|
|
144858
145016
|
fetchLatest: deps.fetchLatest ?? fetchLatestFromRegistry,
|
|
144859
145017
|
run: deps.run ?? runCommand,
|
|
144860
145018
|
installedCopy: deps.installedCopy ?? existingInstalledCopy,
|
|
@@ -144880,13 +145038,13 @@ var UpdateService = class {
|
|
|
144880
145038
|
/*
|
|
144881
145039
|
* The honest source for "which build is this".
|
|
144882
145040
|
*
|
|
144883
|
-
* `APP_VERSION` is what the app was compiled from
|
|
144884
|
-
*
|
|
144885
|
-
*
|
|
144886
|
-
*
|
|
144887
|
-
* enough to notice.
|
|
145041
|
+
* `APP_VERSION` is what the app was compiled from: the version a release stamped
|
|
145042
|
+
* into the build with the npm packages that carry it (#1501, `tooling/brand.test.ts`),
|
|
145043
|
+
* or the placeholder `0.0.0-dev` of a source tree. The workspace root's package.json
|
|
145044
|
+
* is not it — that one is private, has no version anyone installs, and would have
|
|
145045
|
+
* read `0.0.0` forever without ever being wrong enough to notice.
|
|
144888
145046
|
*/
|
|
144889
|
-
current:
|
|
145047
|
+
current: this.deps.current,
|
|
144890
145048
|
latest: null,
|
|
144891
145049
|
newer: false,
|
|
144892
145050
|
auto: this.deps.readAuto(),
|
|
@@ -144983,6 +145141,7 @@ var UpdateService = class {
|
|
|
144983
145141
|
*/
|
|
144984
145142
|
installIfAutomatic() {
|
|
144985
145143
|
if (!this.status.autoApply || !this.status.newer || !this.status.latest) return;
|
|
145144
|
+
if (!installsByItself(this.deps.build, this.deps.current)) return;
|
|
144986
145145
|
if (this.status.phase === "idle") {
|
|
144987
145146
|
void this.apply();
|
|
144988
145147
|
return;
|
|
@@ -145006,6 +145165,7 @@ var UpdateService = class {
|
|
|
145006
145165
|
this.retryTimer = this.deps.schedule(() => {
|
|
145007
145166
|
this.retryTimer = null;
|
|
145008
145167
|
if (this.stopped || !this.status.autoApply || this.status.phase !== "failed" || !this.status.newer || !this.status.latest) return;
|
|
145168
|
+
if (!installsByItself(this.deps.build, this.deps.current)) return;
|
|
145009
145169
|
void this.apply();
|
|
145010
145170
|
}, delay);
|
|
145011
145171
|
}
|
|
@@ -147380,8 +147540,8 @@ function failure2(target, code2, detail) {
|
|
|
147380
147540
|
case "dev_dirty":
|
|
147381
147541
|
return `The working copy on ${target} has ${detail} uncommitted change${detail === "1" ? "" : "s"}; commit or stash them, or pass --allow-dirty to build them too. Nothing was built`;
|
|
147382
147542
|
case "dev_base":
|
|
147383
|
-
if (/^0\.0\.0-dev(?:\.|$)/.test(detail))
|
|
147384
|
-
return `The working copy on ${target}
|
|
147543
|
+
if (!detail || /^0\.0\.0-dev(?:\.|$)/.test(detail))
|
|
147544
|
+
return `The working copy on ${target} has no release tag (v*) reachable from its HEAD, which a dev build takes its version from (#1501); fetch the tags there (git fetch --tags) and try again. Nothing was built`;
|
|
147385
147545
|
return `The working copy on ${target} is version ${detail || "(none)"}; a dev build is cut from a prerelease (x.y.z-beta.N)`;
|
|
147386
147546
|
case "dev_protocol": {
|
|
147387
147547
|
const [have = "?", want = "?"] = detail.split(" ");
|
|
@@ -148220,7 +148380,11 @@ var KEEPS_TRYING = "reconnecting";
|
|
|
148220
148380
|
var WAITS_FOR_PERSON = "press Connect to try again";
|
|
148221
148381
|
var NEXT_STEP = new RegExp(`; (${KEEPS_TRYING}|asking the machine again|${WAITS_FOR_PERSON})$`);
|
|
148222
148382
|
var ATTEMPT_WAIT_MS = 6e4;
|
|
148383
|
+
var CALL_WAIT_MS = 1e4;
|
|
148384
|
+
var CALL_FAILURE_KEPT_MS = 6e4;
|
|
148385
|
+
var CALL_WINDOW_MS = 15e3;
|
|
148223
148386
|
var AWAY = /* @__PURE__ */ new Set(["unreachable", "away", "not_running", "refused", "stopped"]);
|
|
148387
|
+
var CALL_RETRIES = /* @__PURE__ */ new Set(["unreachable", "away", "not_running", "refused"]);
|
|
148224
148388
|
var LinkedMachineConnection = class {
|
|
148225
148389
|
constructor(record2, tunnel, deps) {
|
|
148226
148390
|
this.record = record2;
|
|
@@ -148335,6 +148499,10 @@ var LinkedMachineConnection = class {
|
|
|
148335
148499
|
nextAttemptAt = null;
|
|
148336
148500
|
/** Calls waiting for an attempt to connect to end (`settled`) */
|
|
148337
148501
|
statusWaiters = /* @__PURE__ */ new Set();
|
|
148502
|
+
/** When a call last started an attempt (`reachForCall`), and how the attempt a call waited on ended, with when */
|
|
148503
|
+
lastCallAttemptAt = 0;
|
|
148504
|
+
callFailure = null;
|
|
148505
|
+
callFailureAt = 0;
|
|
148338
148506
|
get id() {
|
|
148339
148507
|
return this.record.id;
|
|
148340
148508
|
}
|
|
@@ -148376,7 +148544,11 @@ var LinkedMachineConnection = class {
|
|
|
148376
148544
|
}
|
|
148377
148545
|
/** Resolves once no attempt is under way, or after `waitMs` (a minute): connecting took 2 to 4 s on a LAN, a host started there adds its start */
|
|
148378
148546
|
settled(waitMs = this.deps.attemptWaitMs ?? ATTEMPT_WAIT_MS) {
|
|
148379
|
-
|
|
148547
|
+
return this.waitUntil(() => !this.attemptUnderWay, waitMs);
|
|
148548
|
+
}
|
|
148549
|
+
/** Resolves once `ready()` holds (checked at every change of status and at the end of every opening), or after `waitMs` */
|
|
148550
|
+
waitUntil(ready, waitMs) {
|
|
148551
|
+
if (ready()) return Promise.resolve();
|
|
148380
148552
|
return new Promise((resolve21) => {
|
|
148381
148553
|
const done2 = () => {
|
|
148382
148554
|
clearTimeout(timer);
|
|
@@ -148384,13 +148556,67 @@ var LinkedMachineConnection = class {
|
|
|
148384
148556
|
resolve21();
|
|
148385
148557
|
};
|
|
148386
148558
|
const waiter = () => {
|
|
148387
|
-
if (
|
|
148559
|
+
if (ready()) done2();
|
|
148388
148560
|
};
|
|
148389
148561
|
const timer = setTimeout(done2, waitMs);
|
|
148390
148562
|
timer.unref?.();
|
|
148391
148563
|
this.statusWaiters.add(waiter);
|
|
148392
148564
|
});
|
|
148393
148565
|
}
|
|
148566
|
+
/**
|
|
148567
|
+
* An agent's call needs this machine (#1532): with Auto reconnect on and the link down, it does what the person's Connect
|
|
148568
|
+
* does (`connect`, the one action that opens a link now) instead of waiting out the backoff, and waits for it a short
|
|
148569
|
+
* bound (`CALL_WAIT_MS`). The caller reads `reachable` afterwards: connected in time, the call goes through; not, it is
|
|
148570
|
+
* answered as not reachable with `whyDown`, and the backoff goes on from where it was (`keepBackoff`).
|
|
148571
|
+
*
|
|
148572
|
+
* - one attempt at a time: a call that finds one under way (the backoff's, the person's press, another call's) waits on
|
|
148573
|
+
* it and starts none; so do ten calls at once, and a press at the same moment as a call;
|
|
148574
|
+
* - calls cannot defeat the backoff: at most one attempt started by a call per machine within `CALL_WINDOW_MS`; a call
|
|
148575
|
+
* inside the window waits for nothing and is answered at once;
|
|
148576
|
+
* - with Auto reconnect off nothing is started (#1341): the call is answered as not connected, and the person is named.
|
|
148577
|
+
* Neither does it when the person disconnected the machine, the host was stopped there, an operation holds the link
|
|
148578
|
+
* or the versions differ: a Connect does not help those, or is the person's to press
|
|
148579
|
+
*/
|
|
148580
|
+
async reachForCall() {
|
|
148581
|
+
if (this.reachable) return;
|
|
148582
|
+
const until = Date.now() + (this.deps.callWaitMs ?? CALL_WAIT_MS);
|
|
148583
|
+
const left = () => Math.max(0, until - Date.now());
|
|
148584
|
+
await this.waitUntil(() => this.reachable || !this.redialing, left());
|
|
148585
|
+
if (this.reachable) return;
|
|
148586
|
+
if (this.attemptUnderWay) {
|
|
148587
|
+
await this.settled(Math.min(left(), this.deps.attemptWaitMs ?? ATTEMPT_WAIT_MS));
|
|
148588
|
+
return this.noteCallFailure();
|
|
148589
|
+
}
|
|
148590
|
+
if (this.stopped || this.parked || this.held || this.inOperation() || !this.autoReconnect || this.redialing) return;
|
|
148591
|
+
if (!CALL_RETRIES.has(this.status)) return;
|
|
148592
|
+
const now = Date.now();
|
|
148593
|
+
if (now - this.lastCallAttemptAt < (this.deps.callWindowMs ?? CALL_WINDOW_MS)) return;
|
|
148594
|
+
this.lastCallAttemptAt = now;
|
|
148595
|
+
this.connect({ start: false, keepBackoff: true });
|
|
148596
|
+
await this.settled(Math.min(left(), this.deps.attemptWaitMs ?? ATTEMPT_WAIT_MS));
|
|
148597
|
+
this.noteCallFailure();
|
|
148598
|
+
}
|
|
148599
|
+
/** The window in which a call starts at most one attempt (`reachForCall`) */
|
|
148600
|
+
get callWindowMs() {
|
|
148601
|
+
return this.deps.callWindowMs ?? CALL_WINDOW_MS;
|
|
148602
|
+
}
|
|
148603
|
+
/** How the attempt a call waited on ended when it did not connect: the machine's own words. One still going is told as it is (`whyDown`) */
|
|
148604
|
+
noteCallFailure() {
|
|
148605
|
+
if (this.reachable || this.attemptUnderWay) return;
|
|
148606
|
+
this.callFailure = this.error?.replace(NEXT_STEP, "") ?? null;
|
|
148607
|
+
this.callFailureAt = Date.now();
|
|
148608
|
+
}
|
|
148609
|
+
/**
|
|
148610
|
+
* What the answer to a call that could not take it says of the attempt to connect the machine (#1532), as a clause: that
|
|
148611
|
+
* one is still under way, or that the one a call made last failed, with the machine's own words. Null once the link is
|
|
148612
|
+
* up, and when no attempt was made or told of
|
|
148613
|
+
*/
|
|
148614
|
+
get whyDown() {
|
|
148615
|
+
if (this.reachable) return null;
|
|
148616
|
+
if (this.attemptUnderWay) return "an attempt to connect it is still under way";
|
|
148617
|
+
if (!this.callFailure || Date.now() - this.callFailureAt > CALL_FAILURE_KEPT_MS) return null;
|
|
148618
|
+
return `the attempt to connect it just now failed: ${this.callFailure}`;
|
|
148619
|
+
}
|
|
148394
148620
|
/** An install, update or other operation the person started is running: its retries are part of it (`retryLater`) */
|
|
148395
148621
|
inOperation() {
|
|
148396
148622
|
return this.held;
|
|
@@ -148526,7 +148752,9 @@ var LinkedMachineConnection = class {
|
|
|
148526
148752
|
* The person's Connect (`machines.reconnect`, #1134): opens now, forgetting the backoff, also a disconnected link and
|
|
148527
148753
|
* also with Auto reconnect off (#1341), where it is the only way to try again. It is also the person's Start
|
|
148528
148754
|
* (#1143): a host is started there even where they stopped it or the link stopped starting them. `start: false` is
|
|
148529
|
-
* the orchestrator's reconnect without its card, which opens the link and starts nothing the link would not
|
|
148755
|
+
* the orchestrator's reconnect without its card, which opens the link and starts nothing the link would not.
|
|
148756
|
+
* `keepBackoff` is an agent's call that needs the machine (`reachForCall`, #1532): it opens now too, but the backoff
|
|
148757
|
+
* goes on from the step it was at instead of starting over, so calls cannot hold it at its short steps
|
|
148530
148758
|
*/
|
|
148531
148759
|
connect(opts = {}) {
|
|
148532
148760
|
if (opts.start !== false) {
|
|
@@ -148535,7 +148763,7 @@ var LinkedMachineConnection = class {
|
|
|
148535
148763
|
this.restartsSpent = false;
|
|
148536
148764
|
}
|
|
148537
148765
|
if (this.parked) return this.start();
|
|
148538
|
-
this.reconnect();
|
|
148766
|
+
this.reconnect(opts.keepBackoff === true);
|
|
148539
148767
|
}
|
|
148540
148768
|
/**
|
|
148541
148769
|
* Closes the link and tries none until the person connects it again (#1134). The machine stays linked; its sessions
|
|
@@ -148580,9 +148808,9 @@ var LinkedMachineConnection = class {
|
|
|
148580
148808
|
this.start();
|
|
148581
148809
|
}
|
|
148582
148810
|
/** Opens again now, forgetting the backoff (`machines.reconnect`, or the person accepted the versions) */
|
|
148583
|
-
reconnect() {
|
|
148811
|
+
reconnect(keepBackoff = false) {
|
|
148584
148812
|
this.clearRetry();
|
|
148585
|
-
this.attempt = 0;
|
|
148813
|
+
if (!keepBackoff) this.attempt = 0;
|
|
148586
148814
|
const under = this.opening;
|
|
148587
148815
|
if (under) {
|
|
148588
148816
|
if (this.status === "away") this.setStatus("connecting", null);
|
|
@@ -148995,6 +149223,7 @@ var LinkedMachineConnection = class {
|
|
|
148995
149223
|
setStatus(status, error51) {
|
|
148996
149224
|
if (this.status === "connected" && status !== "connected") this.seen();
|
|
148997
149225
|
const changed = status !== this.status || error51 !== this.error;
|
|
149226
|
+
if (status === "connected") this.callFailure = null;
|
|
148998
149227
|
this.status = status;
|
|
148999
149228
|
this.error = error51;
|
|
149000
149229
|
if (changed) {
|
|
@@ -150301,9 +150530,12 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
|
|
|
150301
150530
|
for (const m of machines()) if (m.id === id3) return m;
|
|
150302
150531
|
return null;
|
|
150303
150532
|
};
|
|
150304
|
-
const away = (m) => m.waitsForPerson ? `${m.name} ${awayPhrase(NOT_CONNECTED)}, then try again` : `${m.name} is ${
|
|
150533
|
+
const away = (m) => m.waitsForPerson ? `${m.name} ${awayPhrase(NOT_CONNECTED)}, then try again` : `${m.name} is ${notReachable(m)}; try again once its link is back`;
|
|
150534
|
+
const notReachable = (m) => m.whyDown ? `${NOT_REACHABLE}; ${oneLine3(m.whyDown, 300)}` : NOT_REACHABLE;
|
|
150305
150535
|
const settle = async (m) => {
|
|
150306
|
-
if (m.
|
|
150536
|
+
if (m.reachable) return;
|
|
150537
|
+
if (m.reachForCall) await m.reachForCall();
|
|
150538
|
+
else if (m.attemptUnderWay) await m.settled?.();
|
|
150307
150539
|
};
|
|
150308
150540
|
const attempt = async (m, run4) => {
|
|
150309
150541
|
await settle(m);
|
|
@@ -150373,26 +150605,65 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
|
|
|
150373
150605
|
}
|
|
150374
150606
|
return out;
|
|
150375
150607
|
};
|
|
150376
|
-
const
|
|
150608
|
+
const projectsOf = async (m, tried = false) => {
|
|
150377
150609
|
const away2 = [];
|
|
150378
|
-
const
|
|
150379
|
-
|
|
150380
|
-
|
|
150381
|
-
|
|
150382
|
-
|
|
150383
|
-
|
|
150384
|
-
|
|
150385
|
-
|
|
150386
|
-
|
|
150387
|
-
|
|
150388
|
-
|
|
150389
|
-
|
|
150390
|
-
|
|
150391
|
-
|
|
150392
|
-
|
|
150610
|
+
const mirrored = (why) => {
|
|
150611
|
+
away2.push({ machine: m.name, machineId: m.id, why });
|
|
150612
|
+
return lastProjects(m).map((p) => ({ ...p, away: why }));
|
|
150613
|
+
};
|
|
150614
|
+
if (!m.reachable && m.attemptUnderWay && !tried) return { projects: lastProjects(m), away: away2 };
|
|
150615
|
+
if (!m.reachable) return { projects: mirrored(m.waitsForPerson ? NOT_CONNECTED : notReachable(m)), away: away2 };
|
|
150616
|
+
try {
|
|
150617
|
+
const list = await raceTimeout(m.call("projects.list", {}), lookupMs, TIMED_OUT);
|
|
150618
|
+
if (list === TIMED_OUT) throw new Error(notWithin(lookupMs));
|
|
150619
|
+
return { projects: (Array.isArray(list) ? list : []).map((raw) => projectOf(m, raw)).filter((p) => p !== null), away: away2 };
|
|
150620
|
+
} catch (err) {
|
|
150621
|
+
return { projects: mirrored(cause(m, err)), away: away2 };
|
|
150622
|
+
}
|
|
150623
|
+
};
|
|
150624
|
+
const qualifiedWith = (m, ref) => {
|
|
150625
|
+
const slash = ref.indexOf("/");
|
|
150626
|
+
const prefix = slash > 0 ? ref.slice(0, slash).trim().toLowerCase() : null;
|
|
150627
|
+
return prefix === m.id || prefix === m.name.toLowerCase() || ref.startsWith(`${m.id}.`);
|
|
150628
|
+
};
|
|
150629
|
+
const needs = (m, need3) => {
|
|
150630
|
+
if (need3.machineId === m.id) return true;
|
|
150631
|
+
const ref = need3.ref?.trim();
|
|
150632
|
+
if (!ref) return false;
|
|
150633
|
+
if (qualifiedWith(m, ref)) return true;
|
|
150634
|
+
return lastProjects(m).some((p) => p.id === ref || p.name.toLowerCase() === ref.toLowerCase());
|
|
150635
|
+
};
|
|
150636
|
+
const guessed = /* @__PURE__ */ new Map();
|
|
150637
|
+
const guess = async (m) => {
|
|
150638
|
+
const g5 = guessed.get(m.id);
|
|
150639
|
+
if (g5 && Date.now() - g5.at < (m.callWindowMs ?? 0)) return g5.wait ?? void 0;
|
|
150640
|
+
const entry = { at: Date.now(), wait: null };
|
|
150641
|
+
entry.wait = settle(m).finally(() => {
|
|
150642
|
+
entry.wait = null;
|
|
150643
|
+
});
|
|
150644
|
+
guessed.set(m.id, entry);
|
|
150645
|
+
return entry.wait;
|
|
150646
|
+
};
|
|
150647
|
+
const everyProject = async (need3) => {
|
|
150648
|
+
const all = [...machines()];
|
|
150649
|
+
const ref = need3?.ref?.trim();
|
|
150650
|
+
const bare2 = !!ref && !ref.includes("/") && !all.some((m) => qualifiedWith(m, ref));
|
|
150651
|
+
const anyDown = !!need3?.anyDown && bare2;
|
|
150652
|
+
const prior = need3?.prior;
|
|
150653
|
+
if (prior && !anyDown) return prior;
|
|
150654
|
+
const named = (m) => !!need3 && needs(m, need3);
|
|
150655
|
+
const wanted = all.filter((m) => !m.reachable && (anyDown || named(m)));
|
|
150656
|
+
const parts = await Promise.all(
|
|
150657
|
+
all.map(async (m) => {
|
|
150658
|
+
if (wanted.includes(m)) {
|
|
150659
|
+
await (anyDown && !named(m) ? guess(m) : settle(m));
|
|
150660
|
+
return projectsOf(m, true);
|
|
150661
|
+
}
|
|
150662
|
+
if (prior) return { projects: prior.projects.filter((p) => p.machineId === m.id), away: prior.away.filter((a) => a.machineId === m.id) };
|
|
150663
|
+
return projectsOf(m);
|
|
150393
150664
|
})
|
|
150394
150665
|
);
|
|
150395
|
-
return { projects:
|
|
150666
|
+
return { projects: parts.flatMap((p) => p.projects), away: parts.flatMap((p) => p.away), machines: all.map((m) => ({ machine: m.name, machineId: m.id })) };
|
|
150396
150667
|
};
|
|
150397
150668
|
return {
|
|
150398
150669
|
async send(sessionId, text2, fromName, ask) {
|
|
@@ -150408,10 +150679,6 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
|
|
|
150408
150679
|
if (!m) return { ok: false, error: `Not a session of a linked machine: ${sessionId}` };
|
|
150409
150680
|
return attempt(m, async () => ({ info: await route("agents.updateSettings", { sessionId, ...s, by: "orchestrator" }) }));
|
|
150410
150681
|
},
|
|
150411
|
-
async findProjects(ref) {
|
|
150412
|
-
const wanted = ref.trim();
|
|
150413
|
-
return (await everyProject()).projects.filter((p) => p.id === wanted || p.name === wanted);
|
|
150414
|
-
},
|
|
150415
150682
|
projects: everyProject,
|
|
150416
150683
|
async projectSessions(project2) {
|
|
150417
150684
|
const m = owner(project2.id);
|
|
@@ -150676,7 +150943,7 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
|
|
|
150676
150943
|
const m = owner(sessionId);
|
|
150677
150944
|
if (!m) return { ok: false, error: `Not a session of a linked machine: ${sessionId}` };
|
|
150678
150945
|
await settle(m);
|
|
150679
|
-
if (!m.reachable) return { ok: false, error: m.waitsForPerson ? away(m) : `${m.name} is ${
|
|
150946
|
+
if (!m.reachable) return { ok: false, error: m.waitsForPerson ? away(m) : `${m.name} is ${notReachable(m)}` };
|
|
150680
150947
|
try {
|
|
150681
150948
|
const answer = await m.call("host.lastAnswer", { sessionId: m.q.strip(sessionId, "The session"), maxChars: Math.min(maxChars, ANSWER_MAX_CHARS2) });
|
|
150682
150949
|
const r5 = HostLastAnswerResult.safeParse(answer);
|
|
@@ -150716,7 +150983,7 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
|
|
|
150716
150983
|
const m = owner(sessionId);
|
|
150717
150984
|
if (!m) return { ok: false, error: `Not a session of a linked machine: ${sessionId}` };
|
|
150718
150985
|
await settle(m);
|
|
150719
|
-
if (!m.reachable) return { ok: false, error: m.waitsForPerson ? away(m) : `${m.name} is
|
|
150986
|
+
if (!m.reachable) return { ok: false, error: m.waitsForPerson ? away(m) : `${m.name} is ${notReachable(m)}; read it again once its link is back` };
|
|
150720
150987
|
const toolMode = toolLinesOf(opts?.tools);
|
|
150721
150988
|
try {
|
|
150722
150989
|
const answer = await m.call("host.readSession", {
|
|
@@ -150883,6 +151150,7 @@ var ROUTES = {
|
|
|
150883
151150
|
"attachments.save": session(),
|
|
150884
151151
|
"sessions.rename": session(),
|
|
150885
151152
|
"sessions.markRead": session(),
|
|
151153
|
+
"sessions.clearError": session(),
|
|
150886
151154
|
"messages.load": session(messages),
|
|
150887
151155
|
"messages.subagent": session(messages),
|
|
150888
151156
|
"messages.subagentTrashed": session(),
|
|
@@ -151126,7 +151394,7 @@ var MachineUnreachableError = class extends Error {
|
|
|
151126
151394
|
retryable = true;
|
|
151127
151395
|
data;
|
|
151128
151396
|
constructor(machine) {
|
|
151129
|
-
super(`${machine.name} is not reachable right now; this works again once its link is back`);
|
|
151397
|
+
super(`${machine.name} is not reachable right now${machine.whyDown ? `; ${machine.whyDown}` : ""}; this works again once its link is back`);
|
|
151130
151398
|
this.data = { machine: machine.id, reason: "unreachable" };
|
|
151131
151399
|
}
|
|
151132
151400
|
};
|
|
@@ -151191,7 +151459,8 @@ var Router = class {
|
|
|
151191
151459
|
for (const m of this.opts.machines()) if (m.id === id3) return m;
|
|
151192
151460
|
return null;
|
|
151193
151461
|
}
|
|
151194
|
-
async forward(m, method, params) {
|
|
151462
|
+
async forward(m, method, params, ctx) {
|
|
151463
|
+
if (!m.reachable && ctx === void 0) await m.reachForCall?.();
|
|
151195
151464
|
if (!m.reachable) throw new MachineUnreachableError(m);
|
|
151196
151465
|
return m.call(method, params);
|
|
151197
151466
|
}
|
|
@@ -151201,7 +151470,7 @@ var Router = class {
|
|
|
151201
151470
|
const m2 = this.machineById(params.machine);
|
|
151202
151471
|
if (!m2) throw rpcError(`No linked machine ${params.machine}`);
|
|
151203
151472
|
const { machine: _machine2, ...rest } = params;
|
|
151204
|
-
const r5 = await this.forward(m2, method, route.params ? route.params(rest, m2.q) : rest);
|
|
151473
|
+
const r5 = await this.forward(m2, method, route.params ? route.params(rest, m2.q) : rest, ctx);
|
|
151205
151474
|
return route.result ? route.result(r5, m2.q) : r5;
|
|
151206
151475
|
}
|
|
151207
151476
|
const m = this.owner(params[key]);
|
|
@@ -151212,7 +151481,7 @@ var Router = class {
|
|
|
151212
151481
|
const done2 = this.opts.forwarding?.(method, params);
|
|
151213
151482
|
let r;
|
|
151214
151483
|
try {
|
|
151215
|
-
r = await this.forward(m, method, p);
|
|
151484
|
+
r = await this.forward(m, method, p, ctx);
|
|
151216
151485
|
} catch (e) {
|
|
151217
151486
|
done2?.(false);
|
|
151218
151487
|
throw e;
|
|
@@ -151247,7 +151516,7 @@ var Router = class {
|
|
|
151247
151516
|
const { machine: _machine, ...own } = params;
|
|
151248
151517
|
const p = route.orMachine ? own : { ...params };
|
|
151249
151518
|
for (const key of route.keys) if (typeof p[key] === "string") p[key] = target.q.strip(p[key], "The id");
|
|
151250
|
-
const r = await this.forward(target, method, p);
|
|
151519
|
+
const r = await this.forward(target, method, p, ctx);
|
|
151251
151520
|
return route.result ? route.result(r, target.q) : r;
|
|
151252
151521
|
}
|
|
151253
151522
|
async byMachine(method, params, route, ctx) {
|
|
@@ -151259,7 +151528,7 @@ var Router = class {
|
|
|
151259
151528
|
const shared = await this.sameAccountAnswer(m, method, rest, ctx);
|
|
151260
151529
|
if (shared) return route.result ? route.result(shared, m.q) : shared;
|
|
151261
151530
|
}
|
|
151262
|
-
const r = await this.forward(m, method, rest);
|
|
151531
|
+
const r = await this.forward(m, method, rest, ctx);
|
|
151263
151532
|
return route.result ? route.result(r, m.q) : r;
|
|
151264
151533
|
}
|
|
151265
151534
|
/**
|
|
@@ -151915,7 +152184,7 @@ var handedToken = launch.tokenOnStdin ? await readHandedToken(process.stdin).cat
|
|
|
151915
152184
|
console.error(`[agent-host] the launcher did not hand over the token: ${e.message}`);
|
|
151916
152185
|
process.exit(1);
|
|
151917
152186
|
}) : launch.token;
|
|
151918
|
-
var BUILD = true ? "
|
|
152187
|
+
var BUILD = true ? "bd398a1" : "dev";
|
|
151919
152188
|
var { values } = parseArgs({
|
|
151920
152189
|
options: {
|
|
151921
152190
|
port: { type: "string", default: "5175" },
|
|
@@ -152178,6 +152447,8 @@ var { auto: autoUpdateCheck, autoApply: autoApplyUpdates } = updateSwitches(stor
|
|
|
152178
152447
|
var themes = new ThemeFiles(join52(dataRoot(), "themes"), () => events.push({ type: "themes_changed" }));
|
|
152179
152448
|
await themes.start().catch((e) => console.error(`[themes] the themes folder is unavailable: ${e.message}`));
|
|
152180
152449
|
var updates = new UpdateService((status) => events.push({ type: "update_status", status }), {
|
|
152450
|
+
// A host from source never installs by itself, whatever is stored (#1501 review, `installsByItself`)
|
|
152451
|
+
build: BUILD,
|
|
152181
152452
|
// On by default when nothing has been saved yet. Since the check is read-only and every failure
|
|
152182
152453
|
// is swallowed, leaving it on costs nothing, while leaving it off traps **someone who never once
|
|
152183
152454
|
// opens settings** on an old version forever — that person is exactly the one most likely to stay
|
|
@@ -152279,7 +152550,7 @@ var links = new Links({
|
|
|
152279
152550
|
// When agents compact: this hub's setting governs the machines it links (#1376)
|
|
152280
152551
|
autoCompact: () => autoCompactOf(mgr.uiPreferences()),
|
|
152281
152552
|
// Automatic updates cover the linked machines too (#1508): one on an older release follows this hub's version; none starts mid-swap
|
|
152282
|
-
autoUpdate: () => autoApplyUpdates.read()
|
|
152553
|
+
autoUpdate: () => hubUpdatesMachines({ build: BUILD, current: APP_VERSION, autoApply: autoApplyUpdates.read(), draining: hostDrain.draining }),
|
|
152283
152554
|
reverse: reverseCalls({
|
|
152284
152555
|
allowed: (machineId) => store.machines.linkedMachineAsks(machineId),
|
|
152285
152556
|
projects: () => mgr.projectsForMachines(),
|