@centralu/win32-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.
Binary file
@@ -1,9 +1,9 @@
1
1
  {
2
- "commit": "bab6469",
2
+ "commit": "bd398a1",
3
3
  "protocolVersion": 1,
4
4
  "hostProof": 1,
5
5
  "tokenOnStdin": 1,
6
- "builtAt": "2026-10-25T19:49:20.429Z",
6
+ "builtAt": "2026-10-25T22:24:05.820Z",
7
7
  "runtime": "system-node",
8
8
  "platform": "win32",
9
9
  "arch": "x64"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "centralu",
3
- "version": "0.1.0-beta.38",
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.38",
42
- "@centralu/linux-arm64": "0.1.0-beta.38",
43
- "@centralu/linux-x64": "0.1.0-beta.38",
44
- "@centralu/win32-x64": "0.1.0-beta.38"
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
  }
@@ -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.38";
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({ used: external_exports.number(), window: external_exports.number(), exactness: external_exports.enum(["exact", "estimate"]) }).nullable().default(null)
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, window: context.window, exactness: "estimate" };
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 statSync8 } from "node:fs";
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 resolve7 } from "node:path";
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 = resolve7(cwd);
104325
- const target = isAbsolute3(path2) ? resolve7(path2) : resolve7(root3, 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 resolve8, sep as sep4 } from "node:path";
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 = resolve8(path2);
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(resolve8(folder, target));
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 (statSync8(parent).isDirectory()) return "gone";
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 exactHere = all.some((p) => p.id === opts.project);
106019
- const remote = reader && opts.project && !exactHere ? await reader.findProjects(opts.project) : [];
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 realpathSync3 } from "node:fs";
106500
- import { dirname as dirname15, isAbsolute as isAbsolute5, join as join28, relative as relative4, resolve as resolve9 } from "node:path";
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) ? real(p) : p) } : scope;
106745
+ return "paths" in scope ? { paths: scope.paths, real: scope.paths.map((p) => isAbsolute5(p) ? real2(p) : p) } : scope;
106562
106746
  }
106563
- function real(path2) {
106564
- const full = resolve9(path2);
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(realpathSync3.native(existing), ...rest);
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(real);
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] ?? real(path2);
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 { type: "context_update", sessionId: m.id, used, window: m.context.window, exactness: "estimate" };
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
- const e = raw.type === "files_touched" ? this.projectRelative(raw) : raw;
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) ?? ctx.window;
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) ?? m.context?.window ?? 0;
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 { first, second } = nudgeShares(setting);
121080
- const crossed = ["first", "second"].filter((k2) => !c.told.has(k2) && (k2 === "first" ? first : second) < setting.share && (k2 === "first" ? first : second) <= share);
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, setting);
121195
+ if (c.waiting) this.deliver(id3, c, m, window2);
121086
121196
  }
121087
- deliver(id3, c, m, window2, setting) {
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(setting.share * 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.`;
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
- this.emit({ type: "context_update", sessionId: this.sessionId, used, window: window2, exactness: "exact", ...model ? { model } : {} });
125238
- this.checkCompactPoint(Number(usage?.autoCompactThreshold ?? 0));
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 e = said.type === "compaction" ? { ...said, trigger: this.compactTurn ? "manual" : "auto" } : said;
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 [there, fromHubs] = (reader || hubs2) && !exactHere ? await Promise.all([reader ? reader.projects({ ref: wanted }) : none, hubs2 ? hubs2.projects() : none]) : [none, none];
135722
- const askable = [
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 unasked = awayAll.length ? ` Not asked, and its projects may be missing: ${awayAll.map((a) => `${a.machine} (${a.why})`).join(", ")}.` : "";
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
- const slash = ref.indexOf("/");
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 wanted = need3 ? all.filter((m) => !m.reachable && needs(m, need3)) : [];
150486
- const parts = await Promise.all(all.map(async (m) => wanted.includes(m) ? (await settle(m), projectsOf(m, true)) : projectsOf(m)));
150487
- return { projects: parts.flatMap((p) => p.projects), away: parts.flatMap((p) => p.away) };
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 ? "bab6469" : "dev";
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centralu/win32-x64",
3
- "version": "0.1.0-beta.38",
3
+ "version": "0.1.0-beta.39",
4
4
  "description": "Centralu app folder for Windows on x86-64",
5
5
  "license": "MIT",
6
6
  "author": "ijun17",