@centralu/host-linux-x64 0.1.0-beta.38 → 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.
- package/host/bundle-info.json +2 -2
- package/host/centralu-package.json +5 -5
- package/host/main.mjs +382 -206
- package/package.json +1 -1
package/host/bundle-info.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "centralu",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.39",
|
|
4
4
|
"description": "Run, watch and steer several Claude Code and Codex CLI sessions from one window",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "ijun17",
|
|
@@ -38,9 +38,9 @@
|
|
|
38
38
|
"win32"
|
|
39
39
|
],
|
|
40
40
|
"optionalDependencies": {
|
|
41
|
-
"@centralu/darwin-arm64": "0.1.0-beta.
|
|
42
|
-
"@centralu/linux-arm64": "0.1.0-beta.
|
|
43
|
-
"@centralu/linux-x64": "0.1.0-beta.
|
|
44
|
-
"@centralu/win32-x64": "0.1.0-beta.
|
|
41
|
+
"@centralu/darwin-arm64": "0.1.0-beta.39",
|
|
42
|
+
"@centralu/linux-arm64": "0.1.0-beta.39",
|
|
43
|
+
"@centralu/linux-x64": "0.1.0-beta.39",
|
|
44
|
+
"@centralu/win32-x64": "0.1.0-beta.39"
|
|
45
45
|
}
|
|
46
46
|
}
|
package/host/main.mjs
CHANGED
|
@@ -43054,7 +43054,7 @@ 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
43056
|
// packages/protocol/src/version.generated.ts
|
|
43057
|
-
var APP_VERSION = "0.1.0-beta.
|
|
43057
|
+
var APP_VERSION = "0.1.0-beta.39";
|
|
43058
43058
|
|
|
43059
43059
|
// packages/protocol/src/brand.ts
|
|
43060
43060
|
var APP_NAME = "Centralu";
|
|
@@ -59751,7 +59751,18 @@ var NormalizedEvent = external_exports.discriminatedUnion("type", [
|
|
|
59751
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
|
|
59752
59752
|
* names a different model over time), when it says. With the window it is what `ModelWindows` keeps per model (#1376).
|
|
59753
59753
|
*/
|
|
59754
|
-
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()
|
|
59755
59766
|
}),
|
|
59756
59767
|
external_exports.object({
|
|
59757
59768
|
...base,
|
|
@@ -60551,7 +60562,16 @@ var SessionInfo = external_exports.object({
|
|
|
60551
60562
|
* tool and outlives us. So the store writes it down and reads it back (schema v17), and
|
|
60552
60563
|
* `null` here means "this session has never reported one" rather than "we forgot".
|
|
60553
60564
|
*/
|
|
60554
|
-
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)
|
|
60555
60575
|
});
|
|
60556
60576
|
var SettingsApplied = external_exports.enum(["restarted", "after_turn", "saved"]);
|
|
60557
60577
|
var UpdateSettingsResult = SessionInfo.extend({ applied: SettingsApplied.optional() });
|
|
@@ -62460,6 +62480,17 @@ var sessionMethods = {
|
|
|
62460
62480
|
params: external_exports.object({ sessionId: SessionId, seq: external_exports.number() }),
|
|
62461
62481
|
result: external_exports.object({ ok: external_exports.literal(true) })
|
|
62462
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
|
+
},
|
|
62463
62494
|
"messages.load": {
|
|
62464
62495
|
params: external_exports.object({ sessionId: SessionId, limit: external_exports.number().default(200), beforeSeq: external_exports.number().optional() }),
|
|
62465
62496
|
result: external_exports.array(StoredMessage)
|
|
@@ -63465,12 +63496,12 @@ function applySessionState(m, e, now) {
|
|
|
63465
63496
|
function nextContext(context, e) {
|
|
63466
63497
|
switch (e.type) {
|
|
63467
63498
|
case "context_update":
|
|
63468
|
-
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 } : {} };
|
|
63469
63500
|
case "conversation_reset":
|
|
63470
63501
|
return null;
|
|
63471
63502
|
case "compaction":
|
|
63472
63503
|
if (e.failed || e.after === void 0 || !context) return context;
|
|
63473
|
-
return { used: e.after,
|
|
63504
|
+
return { ...context, used: e.after, exactness: "estimate" };
|
|
63474
63505
|
default:
|
|
63475
63506
|
return context;
|
|
63476
63507
|
}
|
|
@@ -65188,6 +65219,8 @@ var METHOD_RIGHTS = {
|
|
|
65188
65219
|
"sessions.list": "everyday",
|
|
65189
65220
|
"sessions.rename": "everyday",
|
|
65190
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",
|
|
65191
65224
|
// The trash: reading and restoring run; deleting for good asks
|
|
65192
65225
|
"trash.list": "everyday",
|
|
65193
65226
|
"trash.readRun": "everyday",
|
|
@@ -103626,6 +103659,19 @@ var ModelWindows = class {
|
|
|
103626
103659
|
if (known && known.window === window2 && known.model === (resolved ?? null)) return;
|
|
103627
103660
|
this.write(key, { window: window2, model: resolved ?? null });
|
|
103628
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
|
+
}
|
|
103629
103675
|
/** The model's window, or null when no session has read one */
|
|
103630
103676
|
of(m) {
|
|
103631
103677
|
return this.read(this.key(m))?.window ?? null;
|
|
@@ -104117,6 +104163,168 @@ var SURFACE_KINDS = ["command", "message", "capability"];
|
|
|
104117
104163
|
// packages/agent-host/src/adapters/contract.ts
|
|
104118
104164
|
var INTERRUPTED_BY_TOOL = "interrupted_by_tool";
|
|
104119
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
|
+
|
|
104120
104328
|
// packages/agent-host/src/sessions/app-agents.ts
|
|
104121
104329
|
var AgentRunWait = class {
|
|
104122
104330
|
constructor(notify, sessionName, onUsage = () => {
|
|
@@ -104223,11 +104431,11 @@ var AgentRunWait = class {
|
|
|
104223
104431
|
|
|
104224
104432
|
// packages/agent-host/src/sessions/worktree-sessions.ts
|
|
104225
104433
|
import { dirname as dirname14, join as join27 } from "node:path";
|
|
104226
|
-
import { lstatSync as lstatSync8, statSync as
|
|
104434
|
+
import { lstatSync as lstatSync8, statSync as statSync9 } from "node:fs";
|
|
104227
104435
|
import { exec as exec2 } from "node:child_process";
|
|
104228
104436
|
|
|
104229
104437
|
// packages/agent-host/src/dev-services/git.ts
|
|
104230
|
-
import { isAbsolute as isAbsolute3, relative as relative2, resolve as
|
|
104438
|
+
import { isAbsolute as isAbsolute3, relative as relative2, resolve as resolve8 } from "node:path";
|
|
104231
104439
|
function git(cwd, args, trust = {}) {
|
|
104232
104440
|
return runGit(cwd, args, { allowRepoPrograms: trust.trusted === true });
|
|
104233
104441
|
}
|
|
@@ -104321,8 +104529,8 @@ function assertLexicalGitPath(cwd, path2) {
|
|
|
104321
104529
|
if (!path2 || path2.includes("\0") || path2.startsWith(":")) {
|
|
104322
104530
|
throw rpcError("Invalid git path");
|
|
104323
104531
|
}
|
|
104324
|
-
const root3 =
|
|
104325
|
-
const target = isAbsolute3(path2) ?
|
|
104532
|
+
const root3 = resolve8(cwd);
|
|
104533
|
+
const target = isAbsolute3(path2) ? resolve8(path2) : resolve8(root3, path2);
|
|
104326
104534
|
const rel = relative2(root3, target);
|
|
104327
104535
|
if (rel === "") throw rpcError("Path is the project root, not a file in it");
|
|
104328
104536
|
if (isInside(root3, target)) return rel;
|
|
@@ -104832,7 +105040,7 @@ async function prepareWorktreeDependencies(projectCwd, worktreeCwd) {
|
|
|
104832
105040
|
// packages/agent-host/src/adapters/edit-grant.ts
|
|
104833
105041
|
import { lstat as lstat7, realpath as realpath5 } from "node:fs/promises";
|
|
104834
105042
|
import { homedir as homedir4 } from "node:os";
|
|
104835
|
-
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";
|
|
104836
105044
|
var PROTECTED_NAMES = /* @__PURE__ */ new Set([
|
|
104837
105045
|
".git",
|
|
104838
105046
|
".gitconfig",
|
|
@@ -104868,7 +105076,7 @@ var PROTECTED_NAMES = /* @__PURE__ */ new Set([
|
|
|
104868
105076
|
var PROTECTED_PAIRS = [[".github", "workflows"], [".cargo", "config"], [".cargo", "config.toml"]];
|
|
104869
105077
|
async function realPathOf(path2) {
|
|
104870
105078
|
const tail = [];
|
|
104871
|
-
let at2 =
|
|
105079
|
+
let at2 = resolve9(path2);
|
|
104872
105080
|
for (; ; ) {
|
|
104873
105081
|
try {
|
|
104874
105082
|
return join26(await realpath5(at2), ...tail.reverse());
|
|
@@ -104895,7 +105103,7 @@ async function insideFolder(folder, targets) {
|
|
|
104895
105103
|
}
|
|
104896
105104
|
for (const target of targets) {
|
|
104897
105105
|
if (!target) return false;
|
|
104898
|
-
const real3 = await realPathOf(
|
|
105106
|
+
const real3 = await realPathOf(resolve9(folder, target));
|
|
104899
105107
|
if (real3 === null) return false;
|
|
104900
105108
|
const inside2 = relative3(root3, real3);
|
|
104901
105109
|
if (inside2 === "" || isAbsolute4(inside2) || inside2 === ".." || inside2.startsWith(`..${sep4}`)) return false;
|
|
@@ -105134,7 +105342,7 @@ function worktreeFolderState(path2) {
|
|
|
105134
105342
|
}
|
|
105135
105343
|
const parent = dirname14(path2);
|
|
105136
105344
|
try {
|
|
105137
|
-
if (
|
|
105345
|
+
if (statSync9(parent).isDirectory()) return "gone";
|
|
105138
105346
|
return { unreachable: `${parent} is not a folder` };
|
|
105139
105347
|
} catch (e) {
|
|
105140
105348
|
return { unreachable: `${parent} cannot be read (${e.code ?? "error"}), so the folder may be on a volume that is not mounted` };
|
|
@@ -105602,49 +105810,6 @@ function scriptLabel(raw) {
|
|
|
105602
105810
|
return plain3 || "unnamed";
|
|
105603
105811
|
}
|
|
105604
105812
|
|
|
105605
|
-
// packages/agent-host/src/sessions/turn-answer.ts
|
|
105606
|
-
function turnAnswer(messages2, opts = {}) {
|
|
105607
|
-
const parts = [];
|
|
105608
|
-
for (let i = messages2.length - 1; i >= 0; i--) {
|
|
105609
|
-
const m = messages2[i];
|
|
105610
|
-
if (opts.afterSeq !== void 0 && m.seq <= opts.afterSeq) break;
|
|
105611
|
-
if (m.role === "assistant" && m.kind === "reasoning") continue;
|
|
105612
|
-
if (m.role === "assistant" && m.kind === "text") {
|
|
105613
|
-
parts.unshift(String(m.payload.text ?? ""));
|
|
105614
|
-
continue;
|
|
105615
|
-
}
|
|
105616
|
-
break;
|
|
105617
|
-
}
|
|
105618
|
-
return parts.join("\n\n").trim();
|
|
105619
|
-
}
|
|
105620
|
-
function turnAnswerSeq(messages2, opts = {}) {
|
|
105621
|
-
let first = null;
|
|
105622
|
-
for (let i = messages2.length - 1; i >= 0; i--) {
|
|
105623
|
-
const m = messages2[i];
|
|
105624
|
-
if (opts.afterSeq !== void 0 && m.seq <= opts.afterSeq) break;
|
|
105625
|
-
if (m.role === "assistant" && m.kind === "reasoning") continue;
|
|
105626
|
-
if (m.role === "assistant" && m.kind === "text") {
|
|
105627
|
-
first = m.seq;
|
|
105628
|
-
continue;
|
|
105629
|
-
}
|
|
105630
|
-
break;
|
|
105631
|
-
}
|
|
105632
|
-
return first;
|
|
105633
|
-
}
|
|
105634
|
-
function lastResponseSeq(messages2) {
|
|
105635
|
-
let end = messages2.length;
|
|
105636
|
-
while (end > 0 && !(messages2[end - 1].role === "assistant" && messages2[end - 1].kind === "text")) end--;
|
|
105637
|
-
return turnAnswerSeq(messages2.slice(0, end));
|
|
105638
|
-
}
|
|
105639
|
-
function lastResponse(messages2) {
|
|
105640
|
-
let end = messages2.length;
|
|
105641
|
-
while (end > 0 && !(messages2[end - 1].role === "assistant" && messages2[end - 1].kind === "text")) end--;
|
|
105642
|
-
return turnAnswer(messages2.slice(0, end));
|
|
105643
|
-
}
|
|
105644
|
-
function clipPreview(text2, max) {
|
|
105645
|
-
return text2.length > max ? `${text2.slice(0, max)}\u2026` : text2;
|
|
105646
|
-
}
|
|
105647
|
-
|
|
105648
105813
|
// packages/agent-host/src/sessions/transcript-view.ts
|
|
105649
105814
|
var MEMORY_MESSAGES = 40;
|
|
105650
105815
|
var MEMORY_LINE_CHARS = 600;
|
|
@@ -106013,10 +106178,29 @@ function orchestratorToolsFor(host, orchestratorId, scopeIds, ownProject, hubVie
|
|
|
106013
106178
|
},
|
|
106014
106179
|
createSession: async (opts) => {
|
|
106015
106180
|
const all = host.store.projects.listProjects();
|
|
106016
|
-
const local = all.filter((p) => p.id === opts.project || p.name === opts.project);
|
|
106017
106181
|
const reader = linked();
|
|
106018
|
-
const
|
|
106019
|
-
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
|
+
}
|
|
106020
106204
|
if (local.length + remote.length > 1) {
|
|
106021
106205
|
const named = [
|
|
106022
106206
|
...local.map((p) => `${p.name} [${p.id}] on this computer`),
|
|
@@ -106496,8 +106680,8 @@ function askedQuestion(asker, act) {
|
|
|
106496
106680
|
}
|
|
106497
106681
|
|
|
106498
106682
|
// packages/agent-host/src/sessions/asked-reach.ts
|
|
106499
|
-
import { realpathSync as
|
|
106500
|
-
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";
|
|
106501
106685
|
var GIVES_NOTHING = /* @__PURE__ */ new Set([
|
|
106502
106686
|
"Write",
|
|
106503
106687
|
"Edit",
|
|
@@ -106558,15 +106742,15 @@ function globPaths(path2, pattern) {
|
|
|
106558
106742
|
return out.length > 0 ? out : [""];
|
|
106559
106743
|
}
|
|
106560
106744
|
function pinned(scope) {
|
|
106561
|
-
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;
|
|
106562
106746
|
}
|
|
106563
|
-
function
|
|
106564
|
-
const full =
|
|
106747
|
+
function real2(path2) {
|
|
106748
|
+
const full = resolve10(path2);
|
|
106565
106749
|
let existing = full;
|
|
106566
106750
|
const rest = [];
|
|
106567
106751
|
for (let i = 0; i < 64; i++) {
|
|
106568
106752
|
try {
|
|
106569
|
-
return join28(
|
|
106753
|
+
return join28(realpathSync4.native(existing), ...rest);
|
|
106570
106754
|
} catch {
|
|
106571
106755
|
const up2 = dirname15(existing);
|
|
106572
106756
|
if (up2 === existing) return full;
|
|
@@ -106595,10 +106779,10 @@ function outOfReach(name, scope, reach, projectOf) {
|
|
|
106595
106779
|
const mine = [reach.project, reach.projectName].filter((p) => typeof p === "string" && p !== "");
|
|
106596
106780
|
return mine.some((p) => p.toLowerCase() === asked) ? null : named(scope.project ?? "");
|
|
106597
106781
|
}
|
|
106598
|
-
const folders = reach.folders.filter((f) => isAbsolute5(f)).map(
|
|
106782
|
+
const folders = reach.folders.filter((f) => isAbsolute5(f)).map(real2);
|
|
106599
106783
|
for (const [i, path2] of scope.paths.entries()) {
|
|
106600
106784
|
if (!isAbsolute5(path2)) return path2 === "" ? name : named(path2);
|
|
106601
|
-
const there = scope.real?.[i] ??
|
|
106785
|
+
const there = scope.real?.[i] ?? real2(path2);
|
|
106602
106786
|
if (!folders.some((f) => inside(f, there))) return named(path2);
|
|
106603
106787
|
}
|
|
106604
106788
|
return null;
|
|
@@ -106736,114 +106920,6 @@ function withHubMark(settings, info) {
|
|
|
106736
106920
|
// packages/agent-host/src/sessions/queued-requests.ts
|
|
106737
106921
|
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
106738
106922
|
|
|
106739
|
-
// packages/agent-host/src/sessions/ask-project.ts
|
|
106740
|
-
import { realpathSync as realpathSync4, statSync as statSync9 } from "node:fs";
|
|
106741
|
-
import { resolve as resolve10 } from "node:path";
|
|
106742
|
-
var ASK_WAIT_MS = 24e4;
|
|
106743
|
-
var ANSWER_READ_MAX_CHARS = 1e5;
|
|
106744
|
-
var ANSWER_MAX_CHARS = 6e3;
|
|
106745
|
-
var ANSWER_HEAD_CHARS = 4500;
|
|
106746
|
-
var ANSWER_TAIL_CHARS = 1200;
|
|
106747
|
-
function taskLine(task, max = 160) {
|
|
106748
|
-
const flat = oneLineText(task);
|
|
106749
|
-
return flat.length > max ? `${flat.slice(0, max)}\u2026 (cut here; ${flat.length} characters in all)` : flat;
|
|
106750
|
-
}
|
|
106751
|
-
function askFrame(callerProject, task, asker) {
|
|
106752
|
-
const who = asker ? ` It is the session ${JSON.stringify(frameField(asker.name))}${asker.sessionId ? ` [${asker.sessionId}]` : ""}.` : "";
|
|
106753
|
-
return [
|
|
106754
|
-
`[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.]`,
|
|
106755
|
-
"",
|
|
106756
|
-
task
|
|
106757
|
-
].join("\n");
|
|
106758
|
-
}
|
|
106759
|
-
function projectChoice(p) {
|
|
106760
|
-
return `${p.name} [${p.id}] on ${p.machine ?? "this computer"}`;
|
|
106761
|
-
}
|
|
106762
|
-
function matchProjects(wanted, projects) {
|
|
106763
|
-
const firstPerMachine = (found) => {
|
|
106764
|
-
const seen = /* @__PURE__ */ new Set();
|
|
106765
|
-
return found.filter((p) => !seen.has(p.machineId) && !!seen.add(p.machineId));
|
|
106766
|
-
};
|
|
106767
|
-
const tiers = [
|
|
106768
|
-
() => projects.filter((p) => p.id === wanted),
|
|
106769
|
-
() => projects.filter((p) => p.name === wanted),
|
|
106770
|
-
() => {
|
|
106771
|
-
const slash = wanted.indexOf("/");
|
|
106772
|
-
if (slash <= 0) return [];
|
|
106773
|
-
const machine = wanted.slice(0, slash).trim().toLowerCase();
|
|
106774
|
-
const name = wanted.slice(slash + 1).trim();
|
|
106775
|
-
const there = projects.filter((p) => p.machineId !== null && (p.machineId === machine || p.machine?.toLowerCase() === machine));
|
|
106776
|
-
return there.some((p) => p.name === name) ? there.filter((p) => p.name === name) : there.filter((p) => p.name.toLowerCase() === name.toLowerCase());
|
|
106777
|
-
},
|
|
106778
|
-
() => projects.filter((p) => p.name.toLowerCase() === wanted.toLowerCase())
|
|
106779
|
-
];
|
|
106780
|
-
for (const tier of tiers) {
|
|
106781
|
-
const found = firstPerMachine(tier());
|
|
106782
|
-
if (found.length) return found;
|
|
106783
|
-
}
|
|
106784
|
-
return [];
|
|
106785
|
-
}
|
|
106786
|
-
function clipAnswer(answer, sessionName, at2) {
|
|
106787
|
-
if (answer.length <= ANSWER_MAX_CHARS) return answer;
|
|
106788
|
-
const cut2 = answer.length - ANSWER_HEAD_CHARS - ANSWER_TAIL_CHARS;
|
|
106789
|
-
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}"`;
|
|
106790
|
-
return answer.slice(0, ANSWER_HEAD_CHARS) + `
|
|
106791
|
-
\u2026[${cut2} characters cut here; ${where2}]\u2026
|
|
106792
|
-
` + answer.slice(-ANSWER_TAIL_CHARS);
|
|
106793
|
-
}
|
|
106794
|
-
function answerAt(reader, sessionId, answer) {
|
|
106795
|
-
const rows3 = reader.loadMessages(sessionId, 50);
|
|
106796
|
-
const seq = turnAnswerSeq(rows3);
|
|
106797
|
-
return seq !== null && answer && turnAnswer(rows3) === answer ? { sessionId, seq } : void 0;
|
|
106798
|
-
}
|
|
106799
|
-
function pathsIn(answer) {
|
|
106800
|
-
const found = [];
|
|
106801
|
-
const re2 = /(?:^|[\s"'`(<[])((?:\/|[A-Za-z]:\\)[^\s"'`)>\]]+)/g;
|
|
106802
|
-
for (const m of answer.matchAll(re2)) {
|
|
106803
|
-
const p = m[1].replace(/[.,;:!?]+$/, "");
|
|
106804
|
-
if (p === "/" || /^[A-Za-z]:\\?$/.test(p)) continue;
|
|
106805
|
-
if (!found.includes(p)) found.push(p);
|
|
106806
|
-
}
|
|
106807
|
-
return found;
|
|
106808
|
-
}
|
|
106809
|
-
function real2(p) {
|
|
106810
|
-
try {
|
|
106811
|
-
return realpathSync4.native(p);
|
|
106812
|
-
} catch {
|
|
106813
|
-
return null;
|
|
106814
|
-
}
|
|
106815
|
-
}
|
|
106816
|
-
function readableGrants(paths, projectRoot) {
|
|
106817
|
-
const root3 = real2(projectRoot);
|
|
106818
|
-
const granted = [];
|
|
106819
|
-
const outside = [];
|
|
106820
|
-
for (const p of paths) {
|
|
106821
|
-
const r = real2(p);
|
|
106822
|
-
if (!root3 || !r || !isInside(root3, r)) {
|
|
106823
|
-
outside.push(p);
|
|
106824
|
-
continue;
|
|
106825
|
-
}
|
|
106826
|
-
let dir = false;
|
|
106827
|
-
try {
|
|
106828
|
-
dir = statSync9(r).isDirectory();
|
|
106829
|
-
} catch {
|
|
106830
|
-
outside.push(p);
|
|
106831
|
-
continue;
|
|
106832
|
-
}
|
|
106833
|
-
if (dir && r === root3) {
|
|
106834
|
-
outside.push(p);
|
|
106835
|
-
continue;
|
|
106836
|
-
}
|
|
106837
|
-
if (!granted.includes(r)) granted.push(r);
|
|
106838
|
-
}
|
|
106839
|
-
return { granted, outside };
|
|
106840
|
-
}
|
|
106841
|
-
function underGrant(path2, grants, cwd) {
|
|
106842
|
-
const r = canonicalPath(resolve10(cwd, path2));
|
|
106843
|
-
for (const g5 of grants) if (isInside(g5, r)) return true;
|
|
106844
|
-
return false;
|
|
106845
|
-
}
|
|
106846
|
-
|
|
106847
106923
|
// packages/agent-host/src/sessions/interruptions.ts
|
|
106848
106924
|
var NETWORK_WORDS = [
|
|
106849
106925
|
/\b(ECONNREFUSED|ENOTFOUND|ECONNRESET|ETIMEDOUT|EAI_AGAIN|ENETUNREACH|EHOSTUNREACH|ENETDOWN)\b/,
|
|
@@ -119431,6 +119507,10 @@ var SessionStarts = class {
|
|
|
119431
119507
|
autoCompactFor(m, adopted) {
|
|
119432
119508
|
const { tokens } = autoCompactPlan(autoCompactOf(this.host.uiPreferences()), this.host.modelWindows, m);
|
|
119433
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
|
+
}
|
|
119434
119514
|
const kept = adopted ? this.host.modelWindows.ceilingOf(m.id) : null;
|
|
119435
119515
|
return { ...tokens === null ? {} : { autoCompactTokens: tokens }, ...kept === null ? {} : { autoCompactCeiling: kept } };
|
|
119436
119516
|
}
|
|
@@ -120323,7 +120403,15 @@ var ContextMeter = class {
|
|
|
120323
120403
|
if (used === m.context.used || now - s.at < ESTIMATE_GAP_MS) return null;
|
|
120324
120404
|
s.at = now;
|
|
120325
120405
|
s.estimated = true;
|
|
120326
|
-
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
|
+
};
|
|
120327
120415
|
}
|
|
120328
120416
|
};
|
|
120329
120417
|
|
|
@@ -120391,7 +120479,7 @@ var SessionEvents = class {
|
|
|
120391
120479
|
meter = new ContextMeter();
|
|
120392
120480
|
/** Receive event -> update metadata -> persist message -> broadcast */
|
|
120393
120481
|
onEvent(raw, from = null) {
|
|
120394
|
-
|
|
120482
|
+
let e = raw.type === "files_touched" ? this.projectRelative(raw) : raw;
|
|
120395
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") {
|
|
120396
120484
|
return;
|
|
120397
120485
|
}
|
|
@@ -120413,6 +120501,7 @@ var SessionEvents = class {
|
|
|
120413
120501
|
this.host.compactEvent(e, false);
|
|
120414
120502
|
return;
|
|
120415
120503
|
}
|
|
120504
|
+
if (e.type === "context_update" && e.exactness !== "estimate") e = this.shareOf(e);
|
|
120416
120505
|
if (e.type === "settings_changed" && e.by === "tool") {
|
|
120417
120506
|
this.host.record.recordToolSettings(e);
|
|
120418
120507
|
return;
|
|
@@ -120508,6 +120597,21 @@ var SessionEvents = class {
|
|
|
120508
120597
|
if (e.type === "background_tasks" && e.sessionId) this.host.restartPolicy.applyDeferredRestart(e.sessionId);
|
|
120509
120598
|
if (e.type === "background_tasks" && e.sessionId && this.host.children.pausedReports.has(e.sessionId)) this.host.reports.settlePausedReport(e.sessionId);
|
|
120510
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
|
+
}
|
|
120511
120615
|
/** A state hint, for storage: the core reducer's rules on the host's copy (`applySessionState`, protocol session-state.ts) */
|
|
120512
120616
|
applyStateHint(e, m) {
|
|
120513
120617
|
const from = m.state;
|
|
@@ -120560,7 +120664,6 @@ var SessionEvents = class {
|
|
|
120560
120664
|
break;
|
|
120561
120665
|
case "context_update":
|
|
120562
120666
|
m.context = nextContext(m.context, e);
|
|
120563
|
-
if (e.exactness !== "estimate") this.host.modelWindows.note(m, e.window, e.model);
|
|
120564
120667
|
break;
|
|
120565
120668
|
case "conversation_reset":
|
|
120566
120669
|
m.context = nextContext(m.context, e);
|
|
@@ -120998,7 +121101,8 @@ var SelfCompaction = class {
|
|
|
120998
121101
|
if (now - since < IDLE_COMPACT_MINUTES * 6e4) return false;
|
|
120999
121102
|
const ctx = m.context;
|
|
121000
121103
|
if (!ctx || ctx.window <= 0) return false;
|
|
121001
|
-
const window2 = this.host.window(m)
|
|
121104
|
+
const window2 = this.host.window(m);
|
|
121105
|
+
if (window2 === null || window2 <= 0) return false;
|
|
121002
121106
|
return ctx.used >= COMPACT_FLOOR_TOKENS && ctx.used >= window2 * setting.idleShare;
|
|
121003
121107
|
}
|
|
121004
121108
|
/** Starts the sweep. A host that never calls it still serves `compact_self` */
|
|
@@ -121063,7 +121167,7 @@ var ContextNudges = class {
|
|
|
121063
121167
|
}
|
|
121064
121168
|
cycle(id3) {
|
|
121065
121169
|
let c = this.cycles.get(id3);
|
|
121066
|
-
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 });
|
|
121067
121171
|
return c;
|
|
121068
121172
|
}
|
|
121069
121173
|
requested(id3, size) {
|
|
@@ -121071,25 +121175,31 @@ var ContextNudges = class {
|
|
|
121071
121175
|
const setting = this.host.setting();
|
|
121072
121176
|
const m = this.host.session(id3);
|
|
121073
121177
|
if (!m || !setting.controlled || !setting.nudge || !this.host.hasCompactTool(id3)) return;
|
|
121074
|
-
const window2 = this.host.window(m)
|
|
121075
|
-
if (window2 <= 0) return;
|
|
121178
|
+
const window2 = this.host.window(m);
|
|
121179
|
+
if (window2 === null || window2 <= 0) return;
|
|
121076
121180
|
const c = this.cycle(id3);
|
|
121077
121181
|
c.size = size;
|
|
121078
121182
|
const share = size / window2;
|
|
121079
|
-
const
|
|
121080
|
-
|
|
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);
|
|
121081
121191
|
if (crossed.length > 0) {
|
|
121082
121192
|
for (const k2 of crossed) c.told.add(k2);
|
|
121083
121193
|
c.waiting = crossed.includes("second") ? "second" : "first";
|
|
121084
121194
|
}
|
|
121085
|
-
if (c.waiting) this.deliver(id3, c, m, window2
|
|
121195
|
+
if (c.waiting) this.deliver(id3, c, m, window2);
|
|
121086
121196
|
}
|
|
121087
|
-
deliver(id3, c, m, window2
|
|
121197
|
+
deliver(id3, c, m, window2) {
|
|
121088
121198
|
const which = c.waiting;
|
|
121089
121199
|
if (!which || c.sending) return;
|
|
121090
121200
|
const percent = Math.round(c.size / window2 * 100);
|
|
121091
121201
|
const used = `${Math.round(c.size / 1e3)}k of ${Math.round(window2 / 1e3)}k tokens`;
|
|
121092
|
-
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.`;
|
|
121093
121203
|
const failed3 = (err) => {
|
|
121094
121204
|
c.waiting = null;
|
|
121095
121205
|
console.error(`[compact] ${id3.slice(0, 8)}: could not send the context notice: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -121362,6 +121472,10 @@ function compactPointOff(passed, reported, modelWindow) {
|
|
|
121362
121472
|
if (reported < expected && modelWindow !== null && modelWindow < passed) return null;
|
|
121363
121473
|
return expected;
|
|
121364
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
|
+
}
|
|
121365
121479
|
function claudeAutoCompactWindow(tokens) {
|
|
121366
121480
|
return Math.min(CLAUDE_AUTO_COMPACT_MAX, Math.max(CLAUDE_AUTO_COMPACT_MIN, Math.round(tokens) + CLAUDE_AUTO_COMPACT_BUFFER));
|
|
121367
121481
|
}
|
|
@@ -125234,8 +125348,10 @@ var ClaudeSession = class {
|
|
|
125234
125348
|
this.contextWindow = window2 > 0 ? window2 : null;
|
|
125235
125349
|
if (window2 > 0 && used >= 0) {
|
|
125236
125350
|
const model = typeof usage?.model === "string" && usage.model ? usage.model : void 0;
|
|
125237
|
-
|
|
125238
|
-
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);
|
|
125239
125355
|
}
|
|
125240
125356
|
} catch {
|
|
125241
125357
|
}
|
|
@@ -127827,6 +127943,9 @@ async function initializeAppServer(client, sessionId, adopted, emit) {
|
|
|
127827
127943
|
function autoCompactConfig(opts) {
|
|
127828
127944
|
return opts.autoCompactTokens ? { model_auto_compact_token_limit: opts.autoCompactTokens } : {};
|
|
127829
127945
|
}
|
|
127946
|
+
function codexCompactAt(opts) {
|
|
127947
|
+
return opts.autoCompactTokens && !opts.processSource?.adopt ? opts.autoCompactTokens : void 0;
|
|
127948
|
+
}
|
|
127830
127949
|
function resumeThreadParams(opts, servers, workspaceConfig) {
|
|
127831
127950
|
return {
|
|
127832
127951
|
threadId: opts.resumeExternalId,
|
|
@@ -128363,7 +128482,9 @@ var CodexSession = class {
|
|
|
128363
128482
|
/** A notification of this thread's own, as the events it maps to (`normalizeNotification`), each the way it goes out */
|
|
128364
128483
|
emitNotification(n) {
|
|
128365
128484
|
for (const said of normalizeNotification(this.sessionId, n)) {
|
|
128366
|
-
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;
|
|
128367
128488
|
if (e.type === "error" && this.inputReceipts.size) this.interrupt(new Error(e.error.message));
|
|
128368
128489
|
if (e.type === "message_image" && !e.data && e.path) {
|
|
128369
128490
|
void imageEventFromDisk(this.sessionId, e.path).then((filled2) => this.emit(filled2));
|
|
@@ -135717,16 +135838,21 @@ var AskProjectFlow = class {
|
|
|
135717
135838
|
const reader = this.host.linkedReader;
|
|
135718
135839
|
const exactHere = projects.some((p) => p.id === wanted);
|
|
135719
135840
|
const hubs2 = this.hubs;
|
|
135720
|
-
const none = { projects: [], away: [] };
|
|
135721
|
-
const [
|
|
135722
|
-
|
|
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 = () => [
|
|
135723
135845
|
...projects.map((p) => ({ id: p.id, name: p.name, machine: null, machineId: null })),
|
|
135724
135846
|
...there.projects.map((p) => ({ id: p.id, name: p.name, machine: p.machine, machineId: p.machineId })),
|
|
135725
135847
|
...fromHubs.projects.map((p) => ({ id: p.id, name: p.name, machine: p.hub, machineId: p.hubId }))
|
|
135726
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
|
+
}
|
|
135727
135854
|
const awayAll = [...there.away, ...fromHubs.away.map((a) => ({ machineId: a.hubId, machine: a.hub, why: a.why }))];
|
|
135728
135855
|
const elsewhere = !!reader || fromHubs.projects.length > 0 || fromHubs.away.length > 0;
|
|
135729
|
-
const found = matchProjects(wanted, askable);
|
|
135730
135856
|
if (found.length > 1) {
|
|
135731
135857
|
return {
|
|
135732
135858
|
ok: false,
|
|
@@ -135743,7 +135869,8 @@ var AskProjectFlow = class {
|
|
|
135743
135869
|
...there.projects.map((p) => `${p.name} (on ${p.machine}${p.away ? `, ${p.away}` : ""})`),
|
|
135744
135870
|
...fromHubs.projects.map((p) => `${p.name} (on ${p.hub}${p.away ? `, ${p.away}` : ""})`)
|
|
135745
135871
|
];
|
|
135746
|
-
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(", ")}.` : "";
|
|
135747
135874
|
return {
|
|
135748
135875
|
ok: false,
|
|
135749
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}`
|
|
@@ -140453,6 +140580,21 @@ var SessionManager = class {
|
|
|
140453
140580
|
rename(...a) {
|
|
140454
140581
|
return this.sessionSettings.rename(...a);
|
|
140455
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
|
+
}
|
|
140456
140598
|
/** `SessionSettings.markRead` (session-settings.ts), kept on the manager for its callers */
|
|
140457
140599
|
markRead(...a) {
|
|
140458
140600
|
return this.sessionSettings.markRead(...a);
|
|
@@ -143769,6 +143911,10 @@ function createRpcHandler(mgr2, adapters2, { settleDeletedProjects, deleteOutloo
|
|
|
143769
143911
|
mgr2.rename(sessionId, name);
|
|
143770
143912
|
return { ok: true };
|
|
143771
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
|
+
},
|
|
143772
143918
|
"sessions.markRead": async (p) => {
|
|
143773
143919
|
const { sessionId, seq } = RpcMethods["sessions.markRead"].params.parse(p);
|
|
143774
143920
|
mgr2.markRead(sessionId, seq);
|
|
@@ -148450,6 +148596,10 @@ var LinkedMachineConnection = class {
|
|
|
148450
148596
|
await this.settled(Math.min(left(), this.deps.attemptWaitMs ?? ATTEMPT_WAIT_MS));
|
|
148451
148597
|
this.noteCallFailure();
|
|
148452
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
|
+
}
|
|
148453
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`) */
|
|
148454
148604
|
noteCallFailure() {
|
|
148455
148605
|
if (this.reachable || this.attemptUnderWay) return;
|
|
@@ -150471,20 +150621,49 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
|
|
|
150471
150621
|
return { projects: mirrored(cause(m, err)), away: away2 };
|
|
150472
150622
|
}
|
|
150473
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
|
+
};
|
|
150474
150629
|
const needs = (m, need3) => {
|
|
150475
150630
|
if (need3.machineId === m.id) return true;
|
|
150476
150631
|
const ref = need3.ref?.trim();
|
|
150477
150632
|
if (!ref) return false;
|
|
150478
|
-
|
|
150479
|
-
const prefix = slash > 0 ? ref.slice(0, slash).trim().toLowerCase() : null;
|
|
150480
|
-
if (prefix === m.id || prefix === m.name.toLowerCase() || ref.startsWith(`${m.id}.`)) return true;
|
|
150633
|
+
if (qualifiedWith(m, ref)) return true;
|
|
150481
150634
|
return lastProjects(m).some((p) => p.id === ref || p.name.toLowerCase() === ref.toLowerCase());
|
|
150482
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
|
+
};
|
|
150483
150647
|
const everyProject = async (need3) => {
|
|
150484
150648
|
const all = [...machines()];
|
|
150485
|
-
const
|
|
150486
|
-
const
|
|
150487
|
-
|
|
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);
|
|
150664
|
+
})
|
|
150665
|
+
);
|
|
150666
|
+
return { projects: parts.flatMap((p) => p.projects), away: parts.flatMap((p) => p.away), machines: all.map((m) => ({ machine: m.name, machineId: m.id })) };
|
|
150488
150667
|
};
|
|
150489
150668
|
return {
|
|
150490
150669
|
async send(sessionId, text2, fromName, ask) {
|
|
@@ -150500,10 +150679,6 @@ function linkedSessions(machines, route = notRouted, hubName2, options = {}) {
|
|
|
150500
150679
|
if (!m) return { ok: false, error: `Not a session of a linked machine: ${sessionId}` };
|
|
150501
150680
|
return attempt(m, async () => ({ info: await route("agents.updateSettings", { sessionId, ...s, by: "orchestrator" }) }));
|
|
150502
150681
|
},
|
|
150503
|
-
async findProjects(ref) {
|
|
150504
|
-
const wanted = ref.trim();
|
|
150505
|
-
return (await everyProject({ ref: wanted })).projects.filter((p) => p.id === wanted || p.name === wanted);
|
|
150506
|
-
},
|
|
150507
150682
|
projects: everyProject,
|
|
150508
150683
|
async projectSessions(project2) {
|
|
150509
150684
|
const m = owner(project2.id);
|
|
@@ -150975,6 +151150,7 @@ var ROUTES = {
|
|
|
150975
151150
|
"attachments.save": session(),
|
|
150976
151151
|
"sessions.rename": session(),
|
|
150977
151152
|
"sessions.markRead": session(),
|
|
151153
|
+
"sessions.clearError": session(),
|
|
150978
151154
|
"messages.load": session(messages),
|
|
150979
151155
|
"messages.subagent": session(messages),
|
|
150980
151156
|
"messages.subagentTrashed": session(),
|
|
@@ -152008,7 +152184,7 @@ var handedToken = launch.tokenOnStdin ? await readHandedToken(process.stdin).cat
|
|
|
152008
152184
|
console.error(`[agent-host] the launcher did not hand over the token: ${e.message}`);
|
|
152009
152185
|
process.exit(1);
|
|
152010
152186
|
}) : launch.token;
|
|
152011
|
-
var BUILD = true ? "
|
|
152187
|
+
var BUILD = true ? "bd398a1" : "dev";
|
|
152012
152188
|
var { values } = parseArgs({
|
|
152013
152189
|
options: {
|
|
152014
152190
|
port: { type: "string", default: "5175" },
|
package/package.json
CHANGED