@centralu/host-linux-arm64 0.1.0-beta.19 → 0.1.0-beta.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
- "commit": "60665cc",
2
+ "commit": "c6afa2e",
3
3
  "protocolVersion": 1,
4
- "builtAt": "2026-10-12T21:22:21.807Z",
4
+ "builtAt": "2026-10-13T05:16:37.829Z",
5
5
  "runtime": "system-node",
6
6
  "platform": "linux",
7
7
  "arch": "arm64"
package/host/main.mjs CHANGED
@@ -42965,7 +42965,7 @@ import { mkdirSync as mkdirSync11, writeSync as writeSync4 } from "node:fs";
42965
42965
 
42966
42966
  // packages/protocol/src/brand.ts
42967
42967
  var APP_NAME = "Centralu";
42968
- var APP_VERSION = "0.1.0-beta.19";
42968
+ var APP_VERSION = "0.1.0-beta.20";
42969
42969
  var APP_SLUG = "centralu";
42970
42970
  var CLIENT_INFO = { name: APP_SLUG, title: APP_NAME, version: APP_VERSION };
42971
42971
  var DATA_DIR = ".centralu";
@@ -58249,6 +58249,8 @@ var AdapterCapabilities = external_exports.object({
58249
58249
  * The idle check (#297) needs that difference — a session whose background work is invisible is never called idle.
58250
58250
  */
58251
58251
  backgroundTasks: external_exports.boolean().default(false),
58252
+ /** Whether send accepts input during a running turn without interrupting it (#592). Absent is unsupported. */
58253
+ midTurnInput: external_exports.boolean().optional(),
58252
58254
  /**
58253
58255
  * Whether this tool can defer individual Centralu tool definitions until the model asks for
58254
58256
  * them. This is a serving property, not a list of tool names: the host uses it when it counts
@@ -59837,6 +59839,14 @@ var MessageImage = external_exports.discriminatedUnion("ok", [
59837
59839
  external_exports.object({ ok: external_exports.literal(true), mime: external_exports.string(), data: external_exports.string(), file: external_exports.string().optional() }),
59838
59840
  external_exports.object({ ok: external_exports.literal(false), reason: MessageImageRefusal, message: external_exports.string(), file: external_exports.string().optional() })
59839
59841
  ]);
59842
+ var AppHomeViewResult = external_exports.object({
59843
+ instanceId: external_exports.string(),
59844
+ tool: external_exports.string(),
59845
+ resourceUri: external_exports.string(),
59846
+ toolInput: external_exports.record(external_exports.string(), external_exports.unknown()),
59847
+ toolResult: external_exports.looseObject({ content: external_exports.array(external_exports.record(external_exports.string(), external_exports.unknown())) }),
59848
+ runId: external_exports.string()
59849
+ });
59840
59850
  var RpcMethods = {
59841
59851
  "agents.createSession": { params: CreateSessionParams, result: SessionInfo },
59842
59852
  "agents.send": {
@@ -60433,7 +60443,8 @@ var RpcMethods = {
60433
60443
  * tool-result).
60434
60444
  *
60435
60445
  * If there is no `home`, or that tool did not declare a screen, or the app cannot be reached
60436
- * (not trusted, or failed to start), this fails with a reason and leaves no instance behind. If
60446
+ * (not trusted, or failed to start), this fails with a reason and leaves no live screen behind. A
60447
+ * keyed open keeps only a failure reservation during the lease grace period, without holding the app. If
60437
60448
  * the app itself answered with a failure, this still opens — drawing a failure is also the
60438
60449
  * screen's job.
60439
60450
  *
@@ -60442,21 +60453,16 @@ var RpcMethods = {
60442
60453
  * machine. Absent: this host's own app, as before.
60443
60454
  */
60444
60455
  "apps.openView": {
60456
+ // openKey: one client-chosen UUID per logical open. Repeats reuse the instance and home outcome,
60457
+ // including after a planned host handover, so a lost answer never runs the home tool twice.
60445
60458
  /*
60446
60459
  * `lease` (#392, additive): the view belongs to this window's connection. Once the connection ends, the host waits
60447
60460
  * a few minutes for a window to claim it (`apps.holdViews`) and then closes it, so a reloaded or closed window does
60448
60461
  * not hold its apps open for good. Without it (an older window, or a linked machine's view) the view lives until
60449
60462
  * `apps.closeView`.
60450
60463
  */
60451
- params: external_exports.object({ appId: AppId, projectId: ProjectId.nullable(), ...machineParam, lease: external_exports.boolean().optional() }),
60452
- result: external_exports.object({
60453
- instanceId: external_exports.string(),
60454
- tool: external_exports.string(),
60455
- resourceUri: external_exports.string(),
60456
- toolInput: external_exports.record(external_exports.string(), external_exports.unknown()),
60457
- toolResult: external_exports.looseObject({ content: external_exports.array(external_exports.record(external_exports.string(), external_exports.unknown())) }),
60458
- runId: external_exports.string()
60459
- })
60464
+ params: external_exports.object({ appId: AppId, projectId: ProjectId.nullable(), ...machineParam, lease: external_exports.boolean().optional(), openKey: external_exports.string().uuid().optional() }),
60465
+ result: AppHomeViewResult
60460
60466
  },
60461
60467
  /**
60462
60468
  * Closes the pinned screen (M4 B-2). Releases the app the instance was holding on to — an app
@@ -60467,6 +60473,11 @@ var RpcMethods = {
60467
60473
  params: external_exports.object({ instanceId: external_exports.string() }),
60468
60474
  result: external_exports.object({ ok: external_exports.literal(true) })
60469
60475
  },
60476
+ /** Reclaims a keyed home open after losing its answer; never calls the home tool. */
60477
+ "apps.recoverView": {
60478
+ params: external_exports.object({ appId: AppId, projectId: ProjectId.nullable(), ...machineParam, lease: external_exports.boolean().optional(), openKey: external_exports.string().uuid() }),
60479
+ result: AppHomeViewResult
60480
+ },
60470
60481
  /**
60471
60482
  * A window claims the pinned screens it shows (#392), after every reconnect: each leased instance
60472
60483
  * named becomes this connection's again and stops waiting to be closed. `missing` lists the ids
@@ -63175,6 +63186,7 @@ var ViewHost = class _ViewHost {
63175
63186
  }
63176
63187
  opts;
63177
63188
  instances = /* @__PURE__ */ new Map();
63189
+ openingHomes = /* @__PURE__ */ new Map();
63178
63190
  origins = /* @__PURE__ */ new Map();
63179
63191
  /** Origin servers being closed, by key: one started again for the same key waits for its port to be free */
63180
63192
  closingOrigins = /* @__PURE__ */ new Map();
@@ -63212,6 +63224,42 @@ var ViewHost = class _ViewHost {
63212
63224
  this.instances.set(id2, { id: id2, app: ref, uri, doc: null, release, lease });
63213
63225
  return { instanceId: id2 };
63214
63226
  }
63227
+ /** A logical home open may run its tool once, even when its answer is lost in a swap. */
63228
+ homeOpen(app2, key, holder) {
63229
+ const inst = [...this.instances.values()].find((i) => i.home?.key === key);
63230
+ if (!inst) return null;
63231
+ if (!sameApp(inst.app, app2)) fail("This open key belongs to another app");
63232
+ if (inst.home.outcome) {
63233
+ if (holder !== void 0) this.hold([inst.id], holder);
63234
+ return Promise.resolve(inst.home.outcome);
63235
+ }
63236
+ if (inst.home.error) return Promise.reject(new Error(inst.home.error));
63237
+ const opening = this.openingHomes.get(inst.id);
63238
+ if (opening) {
63239
+ if (holder !== void 0) this.hold([inst.id], holder);
63240
+ return opening;
63241
+ }
63242
+ return Promise.reject(new Error(
63243
+ "Host restarted while the home tool was running \u2014 the app may or may not have done this; check before opening again"
63244
+ ));
63245
+ }
63246
+ rememberHome(instanceId, key, work) {
63247
+ const inst = this.instances.get(instanceId);
63248
+ inst.home = { key };
63249
+ const opening = work().then((outcome) => {
63250
+ inst.home.outcome = outcome;
63251
+ return outcome;
63252
+ }, (e) => {
63253
+ inst.home.error = e instanceof Error ? e.message : String(e);
63254
+ inst.release?.();
63255
+ inst.release = null;
63256
+ inst.lease = { holder: null, orphanSince: Date.now() };
63257
+ this.scheduleSweep();
63258
+ throw e;
63259
+ }).finally(() => this.openingHomes.delete(instanceId));
63260
+ this.openingHomes.set(instanceId, opening);
63261
+ return opening;
63262
+ }
63215
63263
  close(instanceId) {
63216
63264
  this.drop(instanceId);
63217
63265
  }
@@ -63265,7 +63313,7 @@ var ViewHost = class _ViewHost {
63265
63313
  }
63266
63314
  /** Every open instance, oldest first — what a planned hand-over records (view-handover.ts) */
63267
63315
  list() {
63268
- return [...this.instances.values()].map((i) => ({ id: i.id, app: { ...i.app }, uri: i.uri, ...i.lease ? { leased: true } : {} }));
63316
+ return [...this.instances.values()].map((i) => ({ id: i.id, app: { ...i.app }, uri: i.uri, ...i.lease ? { leased: true } : {}, ...i.home ? { home: i.home } : {} }));
63269
63317
  }
63270
63318
  /**
63271
63319
  * Opens again, **under their old ids**, the instances a previous host handed over (#280: a build
@@ -63292,8 +63340,22 @@ var ViewHost = class _ViewHost {
63292
63340
  this.log(`[agent-host] view ${v.uri} (${_ViewHost.originKey(ref)}) not restored: ${err.message}`);
63293
63341
  continue;
63294
63342
  }
63295
- const lease = v.leased === true ? { holder: null, orphanSince: now } : null;
63296
- this.instances.set(v.id, { id: v.id, app: ref, uri: v.uri, doc: null, release, lease });
63343
+ let lease = v.leased === true ? { holder: null, orphanSince: now } : null;
63344
+ let home;
63345
+ if (v.home && typeof v.home.key === "string" && RpcMethods["apps.openView"].params.shape.openKey.safeParse(v.home.key).success) {
63346
+ const outcome = RpcMethods["apps.openView"].result.safeParse(v.home.outcome);
63347
+ home = {
63348
+ key: v.home.key,
63349
+ ...outcome.success && outcome.data.instanceId === v.id ? { outcome: outcome.data } : {},
63350
+ ...typeof v.home.error === "string" ? { error: v.home.error } : {}
63351
+ };
63352
+ }
63353
+ if (home && !home.outcome) {
63354
+ release?.();
63355
+ release = null;
63356
+ lease = { holder: null, orphanSince: now };
63357
+ }
63358
+ this.instances.set(v.id, { id: v.id, app: ref, uri: v.uri, doc: null, release, lease, ...home ? { home } : {} });
63297
63359
  opened.push(v.id);
63298
63360
  }
63299
63361
  if (opened.some((id2) => this.instances.get(id2)?.lease)) this.scheduleSweep();
@@ -98104,7 +98166,7 @@ var OriginPorts = class {
98104
98166
  };
98105
98167
 
98106
98168
  // packages/agent-host/src/sessions/manager.ts
98107
- import { randomUUID as randomUUID7 } from "node:crypto";
98169
+ import { randomUUID as randomUUID9 } from "node:crypto";
98108
98170
 
98109
98171
  // packages/agent-host/src/sessions/tool-cost.ts
98110
98172
  import { createHash as createHash8 } from "node:crypto";
@@ -100183,10 +100245,11 @@ function finalAnswer(messages2) {
100183
100245
  }
100184
100246
  var AgentRunWait = class {
100185
100247
  constructor(notify, sessionName, onUsage = () => {
100186
- }) {
100248
+ }, readAnswer2) {
100187
100249
  this.notify = notify;
100188
100250
  this.sessionName = sessionName;
100189
100251
  this.onUsage = onUsage;
100252
+ this.readAnswer = readAnswer2;
100190
100253
  this.done = new Promise((resolve11, reject) => this.settle = { resolve: resolve11, reject });
100191
100254
  this.done.catch(() => {
100192
100255
  });
@@ -100194,6 +100257,7 @@ var AgentRunWait = class {
100194
100257
  notify;
100195
100258
  sessionName;
100196
100259
  onUsage;
100260
+ readAnswer;
100197
100261
  /** The person stopped that session's turn (`SessionManager.interrupt`) */
100198
100262
  stoppedByPerson = false;
100199
100263
  /** The person deleted that session — there is no session left to conclude */
@@ -100201,17 +100265,46 @@ var AgentRunWait = class {
100201
100265
  done;
100202
100266
  settle;
100203
100267
  settled = false;
100268
+ inputs = 0;
100269
+ trackedInputs = false;
100270
+ ending;
100271
+ get waitingForInputs() {
100272
+ return this.inputs > 0;
100273
+ }
100274
+ get finished() {
100275
+ return this.settled;
100276
+ }
100277
+ get tracksInputs() {
100278
+ return this.trackedInputs;
100279
+ }
100280
+ get completedAnswer() {
100281
+ return this.ending?.answer;
100282
+ }
100283
+ /** A parent's note may join this turn or run after it. Hold before send, since adapters can emit synchronously. */
100284
+ holdInput() {
100285
+ this.trackedInputs = true;
100286
+ this.inputs++;
100287
+ let released = false;
100288
+ return () => {
100289
+ if (released) return;
100290
+ released = true;
100291
+ this.inputs--;
100292
+ if (!this.inputs && this.ending) this.finish(this.ending);
100293
+ };
100294
+ }
100204
100295
  onEvent(e) {
100205
100296
  if (e.type === "usage_update") {
100206
100297
  const t = e.tokens;
100207
100298
  return this.onUsage({ input: t.inputTokens + t.cacheReadTokens + t.cacheCreationTokens, output: t.outputTokens });
100208
100299
  }
100209
100300
  if (this.settled) return;
100301
+ if (e.type === "message_delta" || e.type === "state_change" && e.state === "working") this.ending = void 0;
100210
100302
  switch (e.type) {
100211
100303
  case "turn_complete":
100212
100304
  if (this.stoppedByPerson) return this.fail(new Error("the person stopped the agent before it finished"));
100213
- this.settled = true;
100214
- return this.settle.resolve(e.output === void 0 ? {} : { output: e.output });
100305
+ this.ending = { ...e.output === void 0 ? {} : { output: e.output }, ...this.readAnswer ? { answer: this.readAnswer() } : {} };
100306
+ if (this.inputs) return;
100307
+ return this.finish(this.ending);
100215
100308
  case "error":
100216
100309
  return this.fail(new Error(this.stoppedByPerson ? "the person stopped the agent before it finished" : `the agent's turn failed: ${e.error.message}`));
100217
100310
  case "limit_reached":
@@ -100225,7 +100318,12 @@ var AgentRunWait = class {
100225
100318
  fail(err) {
100226
100319
  if (this.settled) return;
100227
100320
  this.settled = true;
100228
- this.settle.reject(err);
100321
+ this.settle.reject(this.stoppedByPerson ? new Error("the person stopped the agent before it finished") : err);
100322
+ }
100323
+ finish(ending) {
100324
+ if (this.settled) return;
100325
+ this.settled = true;
100326
+ this.settle.resolve(ending);
100229
100327
  }
100230
100328
  };
100231
100329
 
@@ -100457,6 +100555,11 @@ function agentFrame(parentName, task) {
100457
100555
  task
100458
100556
  ].join("\n");
100459
100557
  }
100558
+ function agentNoteFrame(parentName, text2) {
100559
+ return `[A note from the parent session "${parentName}" through Centralu's agent_session. These are the parent's words about your ongoing work.]
100560
+
100561
+ ${text2}`;
100562
+ }
100460
100563
  function agentBranch(task, id2, max = 40) {
100461
100564
  const slug = task.toLowerCase().normalize("NFKD").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, max).replace(/-+$/, "");
100462
100565
  return `${APP_SLUG}/${slug || "agent"}-${id2.replace(/[^a-z0-9]/gi, "").slice(0, 4).toLowerCase()}`;
@@ -100739,6 +100842,72 @@ import { exec as exec6 } from "node:child_process";
100739
100842
  // packages/agent-host/src/adapters/claude/index.ts
100740
100843
  init_sdk();
100741
100844
  import { homedir as homedir5 } from "node:os";
100845
+ import { randomUUID as randomUUID8 } from "node:crypto";
100846
+
100847
+ // packages/agent-host/src/adapters/input-receipts.ts
100848
+ import { randomUUID as randomUUID7 } from "node:crypto";
100849
+ var InputReceipts = class {
100850
+ pending = /* @__PURE__ */ new Map();
100851
+ ended = /* @__PURE__ */ new Set();
100852
+ create(observer) {
100853
+ const id2 = randomUUID7();
100854
+ let resolve11;
100855
+ let reject;
100856
+ const done = new Promise((yes, no2) => {
100857
+ resolve11 = yes;
100858
+ reject = no2;
100859
+ });
100860
+ done.catch(() => {
100861
+ });
100862
+ const receipt = { id: id2, done, cancelled: false, completed: false };
100863
+ this.pending.set(id2, { receipt, resolve: resolve11, reject, observer });
100864
+ return receipt;
100865
+ }
100866
+ get size() {
100867
+ return this.pending.size;
100868
+ }
100869
+ get ids() {
100870
+ return [...this.pending.keys()];
100871
+ }
100872
+ /** Codex's turn/start response can arrive after that turn's completion notification. */
100873
+ bind(receipt, turn) {
100874
+ const p = this.pending.get(receipt.id);
100875
+ if (!p) return;
100876
+ p.turn = turn;
100877
+ if (this.ended.has(turn)) this.finish(receipt.id);
100878
+ }
100879
+ endTurn(turn) {
100880
+ if (this.pending.size) this.ended.add(turn);
100881
+ for (const [id2, p] of this.pending) if (p.turn === turn) this.finish(id2);
100882
+ }
100883
+ /** Retries can reorder inputs; only UUIDs actually reported as consumed have completed. */
100884
+ complete(ids) {
100885
+ for (const id2 of ids) this.finish(id2);
100886
+ }
100887
+ failAll(error51) {
100888
+ const turns = [...new Set([...this.pending.values()].flatMap((p) => p.turn ? [p.turn] : []))];
100889
+ const pending = [...this.pending.values()];
100890
+ this.pending.clear();
100891
+ this.ended.clear();
100892
+ for (const p of pending) {
100893
+ p.receipt.cancelled = true;
100894
+ p.observer?.fail(error51);
100895
+ p.reject(error51);
100896
+ }
100897
+ return turns;
100898
+ }
100899
+ finish(id2) {
100900
+ const p = this.pending.get(id2);
100901
+ if (!p) return;
100902
+ this.pending.delete(id2);
100903
+ if (!this.pending.size) this.ended.clear();
100904
+ p.receipt.completed = true;
100905
+ p.observer?.complete();
100906
+ p.resolve();
100907
+ }
100908
+ };
100909
+
100910
+ // packages/agent-host/src/adapters/claude/index.ts
100742
100911
  import { execFile as execFile4 } from "node:child_process";
100743
100912
  import { promisify as promisify4 } from "node:util";
100744
100913
 
@@ -102543,6 +102712,12 @@ var ClaudeSession = class {
102543
102712
  launch;
102544
102713
  externalId = null;
102545
102714
  queue = [];
102715
+ inputReceipts = new InputReceipts();
102716
+ /** Person messages already handed to the SDK, so an interrupt receipt can report cancellations. */
102717
+ personInputs = /* @__PURE__ */ new Set();
102718
+ trackedInputKnown = false;
102719
+ /** Installed only after the initial CLI metadata confirms the protocol we have verified. */
102720
+ sendTracked;
102546
102721
  /** The CLI version this process last reported (#297) */
102547
102722
  reportedVersion = null;
102548
102723
  notify = null;
@@ -102635,11 +102810,13 @@ var ClaudeSession = class {
102635
102810
  const next = self2.queue.shift();
102636
102811
  if (next !== void 0) {
102637
102812
  self2.inTurn.push(next);
102813
+ if (!next.parentNote) self2.personInputs.add(next.uuid);
102638
102814
  yield {
102639
102815
  type: "user",
102640
102816
  parent_tool_use_id: null,
102641
102817
  session_id: self2.externalId ?? "",
102642
- message: { role: "user", content: [{ type: "text", text: next }] }
102818
+ uuid: next.uuid,
102819
+ message: { role: "user", content: [{ type: "text", text: next.text }] }
102643
102820
  };
102644
102821
  continue;
102645
102822
  }
@@ -102800,7 +102977,23 @@ var ClaudeSession = class {
102800
102977
  for await (const msg of q2) {
102801
102978
  if (this.closed) break;
102802
102979
  const m = msg;
102980
+ if (!this.turnOpen && m.user_message_uuid && !m.parent_tool_use_id && (m.type === "assistant" || m.type === "stream_event")) {
102981
+ this.turnOpen = true;
102982
+ if (!this.pending.size && !this.questions.size) this.emit({ type: "state_change", sessionId: this.sessionId, state: "working" });
102983
+ }
102803
102984
  if (m.type === "system" && m.subtype === "init" && m.session_id) this.externalId = m.session_id;
102985
+ if (m.type === "system" && m.subtype === "init" && !this.trackedInputKnown) {
102986
+ this.trackedInputKnown = true;
102987
+ const version2 = typeof m.claude_code_version === "string" ? /^(\d+)\.(\d+)\.(\d+)$/.exec(m.claude_code_version) : null;
102988
+ const supported = version2 && (Number(version2[1]) > 2 || Number(version2[1]) === 2 && (Number(version2[2]) > 1 || Number(version2[2]) === 1 && Number(version2[3]) >= 289));
102989
+ if (supported && Array.isArray(m.capabilities) && m.capabilities.includes("interrupt_receipt_v1") && m.capabilities.includes("interrupt_cancel_queued_v1")) {
102990
+ this.sendTracked = (text2, observer) => {
102991
+ const receipt = this.inputReceipts.create(observer);
102992
+ this.sendInput(text2, receipt.id, receipt);
102993
+ return receipt.done;
102994
+ };
102995
+ }
102996
+ }
102804
102997
  if (m.type === "system" && m.subtype === "init" && typeof m.claude_code_version === "string" && m.claude_code_version !== this.reportedVersion) {
102805
102998
  this.reportedVersion = m.claude_code_version;
102806
102999
  this.emit({ type: "agent_version", sessionId: this.sessionId, version: m.claude_code_version });
@@ -102814,8 +103007,23 @@ var ClaudeSession = class {
102814
103007
  if (m.type === "result") this.turnOpen = false;
102815
103008
  let events2 = this.stream.push(msg);
102816
103009
  const retrying = m.type === "result" && this.retryAfterRefreshRace(msg);
103010
+ if (m.type === "result" && !retrying) {
103011
+ const result = msg;
103012
+ for (const id2 of result.user_message_uuids ?? (result.user_message_uuid ? [result.user_message_uuid] : [])) this.personInputs.delete(id2);
103013
+ }
103014
+ let completedInputs;
103015
+ if (m.type === "result" && !retrying && this.inputReceipts.size && events2.some((e) => e.type === "turn_complete" || e.type === "error")) {
103016
+ const result = msg;
103017
+ const ids = result.user_message_uuids ?? [];
103018
+ const failure3 = events2.find((e) => e.type === "error");
103019
+ if (ids.length && failure3?.type === "error") this.cancelParentNotes(new Error(failure3.error.message));
103020
+ else if (ids.length) completedInputs = ids;
103021
+ }
102817
103022
  if (retrying) events2 = events2.filter((e) => e.type !== "error");
102818
- for (const e of events2) this.emit(e);
103023
+ for (const e of events2) {
103024
+ this.emit(e);
103025
+ if (e.type === "turn_complete" && completedInputs) this.inputReceipts.complete(completedInputs);
103026
+ }
102819
103027
  this.armScheduleLook();
102820
103028
  if (m.type === "result" && !retrying) void this.reportContext(q2);
102821
103029
  }
@@ -102984,14 +103192,18 @@ var ClaudeSession = class {
102984
103192
  }
102985
103193
  }
102986
103194
  send(text2) {
103195
+ this.sendInput(text2);
103196
+ }
103197
+ sendInput(text2, uuid3 = randomUUID8(), note) {
102987
103198
  const goal = isGoalCommand(text2);
102988
103199
  if (!this.sendGate && !(goal && this.goalCommand === void 0)) {
102989
- this.deliver(text2);
103200
+ this.deliver(text2, uuid3, !!note);
102990
103201
  return;
102991
103202
  }
102992
103203
  const gate = this.sendGate = (this.sendGate ?? this.goalKnown).then(() => {
103204
+ if (note?.cancelled) return;
102993
103205
  if (!this.closed) {
102994
- this.deliver(text2);
103206
+ this.deliver(text2, uuid3, !!note);
102995
103207
  return;
102996
103208
  }
102997
103209
  this.emit({
@@ -103008,7 +103220,7 @@ var ClaudeSession = class {
103008
103220
  if (this.sendGate === gate) this.sendGate = null;
103009
103221
  });
103010
103222
  }
103011
- deliver(text2) {
103223
+ deliver(text2, uuid3, parentNote) {
103012
103224
  if (isGoalCommand(text2)) {
103013
103225
  if (this.goalCommand !== true) {
103014
103226
  this.emit({
@@ -103023,11 +103235,12 @@ var ClaudeSession = class {
103023
103235
  }
103024
103236
  this.stream.goal.commandSent();
103025
103237
  }
103026
- this.queue.push(text2);
103238
+ const starting = !this.turnOpen;
103239
+ this.queue.push({ text: text2, uuid: uuid3, parentNote });
103027
103240
  this.turnOpen = true;
103028
103241
  this.notify?.();
103029
103242
  this.notify = null;
103030
- this.emit({ type: "state_change", sessionId: this.sessionId, state: "working" });
103243
+ if (starting) this.emit({ type: "state_change", sessionId: this.sessionId, state: "working" });
103031
103244
  }
103032
103245
  respondApproval(requestId, decision, scope, matcher) {
103033
103246
  const p = this.pending.get(requestId);
@@ -103166,7 +103379,11 @@ var ClaudeSession = class {
103166
103379
  * 2) SDK interrupt: this actually cuts the turn. We can call this method because we pass the
103167
103380
  * prompt as an async generator, in streaming input mode.
103168
103381
  */
103169
- interrupt() {
103382
+ interrupt(cancelQueued = this.inputReceipts.size > 0, reason = new Error("The parent note was stopped before it finished")) {
103383
+ this.inputReceipts.failAll(reason);
103384
+ const held3 = (cancelQueued ? this.queue.filter((input) => !input.parentNote).length : 0) + (this.retry?.texts.filter((input) => !input.parentNote).length ?? 0);
103385
+ if (cancelQueued) this.queue.length = 0;
103386
+ this.reportUndelivered(held3);
103170
103387
  this.opts.apps?.cancelAll();
103171
103388
  this.cancelRetry();
103172
103389
  for (const [id2, p] of this.pending) {
@@ -103176,7 +103393,13 @@ var ClaudeSession = class {
103176
103393
  this.pending.clear();
103177
103394
  this.releaseQuestions("Stopped by user");
103178
103395
  if (this.turnOpen) this.stream.stopped();
103179
- void this.query?.interrupt().catch((err) => {
103396
+ void this.query?.interrupt(cancelQueued ? { cancelQueued: true } : void 0).then((response) => {
103397
+ const cancelled = response?.cancelled;
103398
+ if (!Array.isArray(cancelled)) return;
103399
+ let count = 0;
103400
+ for (const id2 of cancelled) if (typeof id2 === "string" && this.personInputs.delete(id2)) count++;
103401
+ this.reportUndelivered(count);
103402
+ }).catch((err) => {
103180
103403
  this.emit({
103181
103404
  type: "error",
103182
103405
  sessionId: this.sessionId,
@@ -103185,6 +103408,34 @@ var ClaudeSession = class {
103185
103408
  });
103186
103409
  this.emit({ type: "state_change", sessionId: this.sessionId, state: "waiting_input", reason: "interrupted" });
103187
103410
  }
103411
+ /** A failed turn is already over. Cancel its parent notes without interrupting the person's next turn. */
103412
+ cancelParentNotes(reason) {
103413
+ const ids = this.inputReceipts.ids;
103414
+ this.inputReceipts.failAll(reason);
103415
+ this.queue = this.queue.filter((input) => !input.parentNote);
103416
+ if (this.retry) {
103417
+ const texts = this.retry.texts;
103418
+ texts.splice(0, texts.length, ...texts.filter((input) => !input.parentNote));
103419
+ if (!texts.length) this.cancelRetry();
103420
+ }
103421
+ const query = this.query;
103422
+ if (!query?.cancelAsyncMessage || this.personInputs.size || this.queue.length || this.retry?.texts.length || this.sendGate) return;
103423
+ for (const id2 of ids) void query.cancelAsyncMessage(id2).catch((error51) => {
103424
+ this.emit({ type: "error", sessionId: this.sessionId, error: {
103425
+ code: "internal",
103426
+ message: `Could not cancel the parent note: ${error51.message}`,
103427
+ retryable: false
103428
+ } });
103429
+ });
103430
+ }
103431
+ reportUndelivered(count) {
103432
+ if (!count) return;
103433
+ this.emit({
103434
+ type: "error",
103435
+ sessionId: this.sessionId,
103436
+ error: { code: "internal", message: `${count} message(s) were still queued when the turn stopped and were not delivered \u2014 please resend`, retryable: false }
103437
+ });
103438
+ }
103188
103439
  /**
103189
103440
  * Stops one background task (#290). Measured on a backgrounded shell: `background_tasks_changed` without it,
103190
103441
  * `task_updated` killed and `task_notification` stopped follow at once, and the stream turns that into the
@@ -103203,6 +103454,7 @@ var ClaudeSession = class {
103203
103454
  * same way; only swapping the process was missing it.
103204
103455
  */
103205
103456
  async dispose() {
103457
+ this.inputReceipts.failAll(new Error("The session closed before the parent note finished"));
103206
103458
  this.closed = true;
103207
103459
  this.notify?.();
103208
103460
  this.settleGoalKnown();
@@ -103244,6 +103496,7 @@ var ClaudeSession = class {
103244
103496
  * the process (#98) — and ends every background task the session listed (#290), for the same reason.
103245
103497
  */
103246
103498
  releaseAgents(why) {
103499
+ this.inputReceipts.failAll(new Error(why));
103247
103500
  if (this.scheduleLook) clearTimeout(this.scheduleLook);
103248
103501
  this.scheduleLook = null;
103249
103502
  for (const e of this.stream.release(why)) this.emit(e);
@@ -103391,6 +103644,8 @@ var ClaudeAdapter = class _ClaudeAdapter {
103391
103644
  processModel: "per-session",
103392
103645
  builtInSubagents: true,
103393
103646
  peerMessaging: true,
103647
+ // The query's prompt iterator reads queued user messages while the current turn continues.
103648
+ midTurnInput: true,
103394
103649
  /*
103395
103650
  * An idle session may be put to sleep (#568): a resumed process read the prompt cache as the live one did (~100% at
103396
103651
  * 2, 6 and 10 min on the 1 h lifetime, CLI 2.1.29x with haiku, #561) for +0.7 s (p50) / +1.8 s (p95) before the first
@@ -105157,6 +105412,8 @@ var CodexSession = class {
105157
105412
  * **that we ourselves started**, and flush the queue as a single turn once that turn ends.
105158
105413
  */
105159
105414
  pendingInputs = [];
105415
+ inputReceipts = new InputReceipts();
105416
+ inputGroups = /* @__PURE__ */ new Map();
105160
105417
  /** An unsteerable turn (compact/review) is running — we know because we are the ones who started it */
105161
105418
  blockingTurn = false;
105162
105419
  /**
@@ -105524,6 +105781,16 @@ var CodexSession = class {
105524
105781
  this.activityBefore = null;
105525
105782
  }
105526
105783
  this.noteAppCall(n);
105784
+ if (n.method === "item/started" || n.method === "item/completed") {
105785
+ const p = n.params ?? {};
105786
+ if (p.item?.type === "userMessage" && p.item.clientId && p.turnId) {
105787
+ const group = this.inputGroups.get(p.item.clientId);
105788
+ if (group) {
105789
+ this.inputGroups.delete(p.item.clientId);
105790
+ this.takeInputTurn({ turn: { id: p.turnId } }, group);
105791
+ }
105792
+ }
105793
+ }
105527
105794
  if (n.method === "turn/completed" && this.blockingTurn) {
105528
105795
  this.blockingTurn = false;
105529
105796
  this.flushPending();
@@ -105541,6 +105808,7 @@ var CodexSession = class {
105541
105808
  }
105542
105809
  if (n.method === "mcpServer/startupStatus/updated" && !this.firstMcpFailure(n.params)) return;
105543
105810
  for (const e of normalizeNotification(this.sessionId, n)) {
105811
+ if (e.type === "error" && this.inputReceipts.size) this.interrupt(new Error(e.error.message));
105544
105812
  if (e.type === "message_image" && !e.data && e.path) {
105545
105813
  void imageEventFromDisk(this.sessionId, e.path).then((filled) => this.emit(filled));
105546
105814
  continue;
@@ -105559,6 +105827,10 @@ var CodexSession = class {
105559
105827
  else this.activityBefore = e.activity;
105560
105828
  }
105561
105829
  this.emit(e);
105830
+ if (e.type === "turn_complete") {
105831
+ const ended = turnIdOf(n.params);
105832
+ if (ended) this.inputReceipts.endTurn(ended);
105833
+ }
105562
105834
  }
105563
105835
  this.noteSpawn(n);
105564
105836
  if (n.method === "turn/completed" && this.unlinked.size > 0) {
@@ -105730,13 +106002,21 @@ var CodexSession = class {
105730
106002
  applyRules(matchers) {
105731
106003
  this.alwaysAllow.addAll(matchers);
105732
106004
  }
106005
+ sendTracked(text2, observer) {
106006
+ const receipt = this.inputReceipts.create(observer);
106007
+ this.sendInput(text2, receipt);
106008
+ return receipt.done;
106009
+ }
105733
106010
  send(text2) {
106011
+ this.sendInput(text2);
106012
+ }
106013
+ sendInput(text2, receipt) {
105734
106014
  const nth = ++this.sendsIssued;
105735
106015
  void this.ready.then(() => {
105736
- if (nth <= this.sendsStopped) return;
106016
+ if (nth <= this.sendsStopped || receipt?.cancelled) return;
105737
106017
  if (!this.threadId) throw new Error("Thread is not ready");
105738
106018
  if (this.blockingTurn) {
105739
- this.pendingInputs.push(text2);
106019
+ this.pendingInputs.push({ text: text2, receipt });
105740
106020
  return;
105741
106021
  }
105742
106022
  if (text2.trim() === "/compact") {
@@ -105789,8 +106069,10 @@ var CodexSession = class {
105789
106069
  say2(status === "active" ? `Goal set: ${arg}` : `Goal set (${status}): ${arg}`);
105790
106070
  });
105791
106071
  }
106072
+ if (receipt) this.inputGroups.set(receipt.id, [receipt]);
105792
106073
  return this.client.request("turn/start", {
105793
106074
  threadId: this.threadId,
106075
+ ...receipt ? { clientUserMessageId: receipt.id } : {},
105794
106076
  input: [{ type: "text", text: text2 }],
105795
106077
  /*
105796
106078
  * Reasoning effort is passed per turn — this is the slot codex documents as applying
@@ -105799,8 +106081,11 @@ var CodexSession = class {
105799
106081
  */
105800
106082
  ...this.opts.effort ? { effort: this.opts.effort } : {},
105801
106083
  ...this.outputSchemaParam()
105802
- }).then((res) => this.takeStartedTurn(turnIdOf(res)));
106084
+ }).then((res) => this.takeInputTurn(res, receipt ? [receipt] : []));
105803
106085
  }).catch((e) => {
106086
+ if (receipt) this.inputGroups.delete(receipt.id);
106087
+ if (receipt?.cancelled || receipt?.completed) return;
106088
+ if (receipt) this.interrupt(e);
105804
106089
  if (this.startFailed) return;
105805
106090
  this.emit({
105806
106091
  type: "error",
@@ -105812,14 +106097,22 @@ var CodexSession = class {
105812
106097
  /** Sends the messages that were queued out as one turn — each message as its own input item (does not blur the boundary) */
105813
106098
  flushPending() {
105814
106099
  if (this.pendingInputs.length === 0 || !this.threadId) return;
105815
- const input = this.pendingInputs.map((text2) => ({ type: "text", text: text2 }));
106100
+ const pending = this.pendingInputs;
106101
+ const receipts = pending.flatMap((p) => p.receipt ? [p.receipt] : []);
106102
+ const clientId = receipts[0]?.id;
106103
+ if (clientId) this.inputGroups.set(clientId, receipts);
106104
+ const input = pending.map(({ text: text2 }) => ({ type: "text", text: text2 }));
105816
106105
  this.pendingInputs = [];
105817
106106
  void this.client.request("turn/start", {
105818
106107
  threadId: this.threadId,
106108
+ ...clientId ? { clientUserMessageId: clientId } : {},
105819
106109
  input,
105820
106110
  ...this.opts.effort ? { effort: this.opts.effort } : {},
105821
106111
  ...this.outputSchemaParam()
105822
- }).then((res) => this.takeStartedTurn(turnIdOf(res))).catch((e) => {
106112
+ }).then((res) => this.takeInputTurn(res, receipts)).catch((e) => {
106113
+ if (clientId) this.inputGroups.delete(clientId);
106114
+ if (receipts.length && receipts.every((r) => r.cancelled || r.completed)) return;
106115
+ if (this.inputReceipts.size) this.interrupt(e);
105823
106116
  this.emit({
105824
106117
  type: "error",
105825
106118
  sessionId: this.sessionId,
@@ -105827,6 +106120,25 @@ var CodexSession = class {
105827
106120
  });
105828
106121
  });
105829
106122
  }
106123
+ takeInputTurn(res, receipts) {
106124
+ if (receipts.length && receipts.every((r) => r.completed)) return;
106125
+ const id2 = turnIdOf(res);
106126
+ if (!receipts.length) {
106127
+ this.takeStartedTurn(id2);
106128
+ return;
106129
+ }
106130
+ if (!id2) {
106131
+ this.interrupt(new Error("Codex did not identify the turn accepting the parent note"));
106132
+ return;
106133
+ }
106134
+ if (receipts.some((r) => r.cancelled) && !this.endedTurns.includes(id2)) {
106135
+ void this.client.request("turn/interrupt", { threadId: this.threadId, turnId: id2 }).catch(() => {
106136
+ });
106137
+ }
106138
+ if (receipts.some((r) => r.cancelled)) return;
106139
+ this.takeStartedTurn(id2);
106140
+ for (const receipt of receipts) this.inputReceipts.bind(receipt, id2);
106141
+ }
105830
106142
  /**
105831
106143
  * Takes a running turn's id from a response, unless the notifications already named one or already ended it. The
105832
106144
  * response and the notifications race: `turn/started` can come after the response (Stop pressed very quickly), and
@@ -105894,7 +106206,14 @@ var CodexSession = class {
105894
106206
  }
105895
106207
  return out;
105896
106208
  }
105897
- interrupt() {
106209
+ interrupt(reason = new Error("The parent note was stopped before it finished")) {
106210
+ const turns = this.inputReceipts.failAll(reason);
106211
+ this.pendingInputs = this.pendingInputs.filter((p) => !p.receipt);
106212
+ for (const turnId of turns) {
106213
+ if (!this.threadId || turnId === this.turnId || this.endedTurns.includes(turnId)) continue;
106214
+ void this.client.request("turn/interrupt", { threadId: this.threadId, turnId }).catch(() => {
106215
+ });
106216
+ }
105898
106217
  this.opts.apps?.cancelAll();
105899
106218
  if (!this.threadId && this.sendsIssued > this.sendsStopped) {
105900
106219
  this.sendsStopped = this.sendsIssued;
@@ -105927,6 +106246,8 @@ var CodexSession = class {
105927
106246
  }
105928
106247
  /** The app-server is gone, and the child threads with it — each still listed ends as stopped. */
105929
106248
  releaseChildren() {
106249
+ this.inputGroups.clear();
106250
+ this.inputReceipts.failAll(new Error("The session process ended before the parent note finished"));
105930
106251
  for (const e of this.children.release("Ended with the session process")) this.emit(e);
105931
106252
  }
105932
106253
  /**
@@ -106102,6 +106423,8 @@ var CodexAdapter = class {
106102
106423
  processModel: "per-session",
106103
106424
  builtInSubagents: true,
106104
106425
  peerMessaging: false,
106426
+ // 0.160 TurnStartParams: turn/start steers an already-active regular turn; compact/review input is queued.
106427
+ midTurnInput: true,
106105
106428
  /*
106106
106429
  * Not put to sleep (#568). Waking a large thread measured 7-13 s (`thread/resume` scales with the rollout, ~13.5 s for
106107
106430
  * 550 MB), and its cache on resume is unmeasured. The memory is better won by one shared app-server per account
@@ -109950,6 +110273,8 @@ var SessionManager = class {
109950
110273
  * app-server), even though the screen said "starting next turn".
109951
110274
  */
109952
110275
  restartAfterTurn = /* @__PURE__ */ new Set();
110276
+ /** Input arriving after a note completes waits for its deferred process replacement, including disposal. */
110277
+ deferredRestarts = /* @__PURE__ */ new Map();
109953
110278
  /** A new host served a different Centralu tool prefix; replace the old agent at its safe boundary. */
109954
110279
  definitionsRestartAfterTurn = /* @__PURE__ */ new Set();
109955
110280
  /** A kept process was started with adapter settings from an older host build (#589). */
@@ -110061,7 +110386,7 @@ var SessionManager = class {
110061
110386
  throw Object.assign(new Error(`Directory not found: ${path2}`), { code: "internal" });
110062
110387
  }
110063
110388
  const existing = this.store.findProjectByPath(path2);
110064
- const id2 = existing?.id ?? randomUUID7();
110389
+ const id2 = existing?.id ?? randomUUID9();
110065
110390
  this.store.addProject({ id: id2, path: path2, name: basename5(path2) });
110066
110391
  return this.projectInfo(id2, path2);
110067
110392
  }
@@ -110589,7 +110914,7 @@ var SessionManager = class {
110589
110914
  );
110590
110915
  }
110591
110916
  }
110592
- const id2 = randomUUID7();
110917
+ const id2 = randomUUID9();
110593
110918
  let worktree = null;
110594
110919
  if (params.worktree && params.projectId) {
110595
110920
  const summary = await gitSummary(params.cwd, this.gitTrust(params.projectId));
@@ -111610,20 +111935,30 @@ var SessionManager = class {
111610
111935
  }
111611
111936
  }
111612
111937
  }
111938
+ const run = e.sessionId ? this.agentRuns.get(e.sessionId) : void 0;
111939
+ run?.onEvent(e);
111613
111940
  this.emit(seq != null ? { ...e, seq } : e);
111614
- if (endedTurn && e.sessionId) void this.reportBackIfAwaited(e.sessionId, e);
111615
- if (e.sessionId) this.agentRuns.get(e.sessionId)?.onEvent(e);
111941
+ if (endedTurn && e.sessionId && !run?.tracksInputs) void this.reportBackIfAwaited(e.sessionId, e, run?.completedAnswer);
111616
111942
  if (e.type === "approval_resolved" && e.sessionId) this.raiseHostAsks(e.sessionId);
111617
111943
  if (endedTurn && e.sessionId) this.forgetCommits(e.sessionId);
111618
- if (endedTurn && e.sessionId && this.restartAfterTurn.delete(e.sessionId)) {
111619
- const apps = this.appsRestartAfterTurn.delete(e.sessionId);
111620
- const definitions = this.definitionsRestartAfterTurn.delete(e.sessionId);
111621
- const launchSettings = this.launchSettingsRestartAfterTurn.delete(e.sessionId);
111622
- if ((apps || definitions || launchSettings || this.settingsDrifted(e.sessionId)) && this.handles.has(e.sessionId)) {
111623
- void this.restartSession(e.sessionId, launchSettings ? "launch_settings" : apps ? "thread_restart" : definitions ? "instructions" : this.restartCause(e.sessionId)).catch(() => {
111624
- });
111625
- }
111626
- }
111944
+ if (endedTurn && e.sessionId) this.applyDeferredRestart(e.sessionId);
111945
+ }
111946
+ /** A note receipt follows turn_complete: re-check the same deferred change when the last hold is released. */
111947
+ applyDeferredRestart(sessionId) {
111948
+ const session2 = this.meta.get(sessionId);
111949
+ if (!session2 || inTurn(session2.state) || this.deferredRestarts.has(sessionId) || this.agentRuns.get(sessionId)?.waitingForInputs || !this.restartAfterTurn.delete(sessionId)) return;
111950
+ const apps = this.appsRestartAfterTurn.delete(sessionId);
111951
+ const definitions = this.definitionsRestartAfterTurn.delete(sessionId);
111952
+ const launchSettings = this.launchSettingsRestartAfterTurn.delete(sessionId);
111953
+ if (!(apps || definitions || launchSettings || this.settingsDrifted(sessionId)) || !this.handles.has(sessionId)) return;
111954
+ const cause2 = launchSettings ? "launch_settings" : apps ? "thread_restart" : definitions ? "instructions" : this.restartCause(sessionId);
111955
+ const restarting = Promise.resolve().then(() => this.restartSession(sessionId, cause2)).then(() => {
111956
+ }).finally(() => {
111957
+ if (this.deferredRestarts.get(sessionId) === restarting) this.deferredRestarts.delete(sessionId);
111958
+ });
111959
+ this.deferredRestarts.set(sessionId, restarting);
111960
+ void restarting.catch(() => {
111961
+ });
111627
111962
  }
111628
111963
  /**
111629
111964
  * Notifies the orchestrator when the work it assigned finishes — **the other half of "one window".**
@@ -111637,14 +111972,14 @@ var SessionManager = class {
111637
111972
  * - Once notified, the marker is cleared. Otherwise that session would wake the orchestrator on
111638
111973
  * every turn it runs on its own afterward — which would become a loop of waking each other
111639
111974
  */
111640
- async reportBackIfAwaited(sessionId, ending) {
111975
+ async reportBackIfAwaited(sessionId, ending, answer) {
111641
111976
  const orchestratorId = this.awaitingReport.get(sessionId);
111642
111977
  if (!orchestratorId) return;
111643
111978
  this.awaitingReport.delete(sessionId);
111644
111979
  const target = this.meta.get(sessionId);
111645
111980
  if (!target || !this.meta.has(orchestratorId)) return;
111646
111981
  const project2 = target.projectId ? this.store.listProjects().find((p) => p.id === target.projectId)?.name ?? "(project no longer exists)" : "(none)";
111647
- await this.deliverReport(orchestratorId, sessionId, target.name, project2, previewOf(this.store, sessionId, 600), ending);
111982
+ await this.deliverReport(orchestratorId, sessionId, target.name, project2, answer?.slice(0, 600) ?? previewOf(this.store, sessionId, 600), ending);
111648
111983
  }
111649
111984
  /** Reports the end of a turn the orchestrator asked to hear about, as one message in its session (#166) */
111650
111985
  async deliverReport(orchestratorId, sessionId, name, project2, preview, ending) {
@@ -111980,7 +112315,9 @@ If you need more, you can read that session's recent conversation with read_sess
111980
112315
  * exposed on send(), which anything else can call — a boundary that can be flipped on by accident
111981
112316
  * is not a boundary.
111982
112317
  */
111983
- async deliver(sessionId, text2, attachments, from, relayed, fromApp, fromAppVia = "inline") {
112318
+ async deliver(sessionId, text2, attachments, from, relayed, fromApp, fromAppVia = "inline", sendInput) {
112319
+ const restarting = this.deferredRestarts.get(sessionId);
112320
+ if (restarting) await restarting;
111984
112321
  const m = this.meta.get(sessionId);
111985
112322
  if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
111986
112323
  if (!this.handles.has(sessionId)) {
@@ -112027,7 +112364,8 @@ If you need more, you can read that session's recent conversation with read_sess
112027
112364
  const adapterText = fromApp ? appMessageFrame(fromApp, text2, fromAppVia) : from && (relayed || this.directs(sessionId) && !this.directs(from.sessionId) && from.sessionId !== HUB_SENDER) ? untrustedSourceSessionNotification(from.sessionId) : attachments?.length ? `${text2}
112028
112365
 
112029
112366
  ${attachments.map((a) => `@${a.path}`).join("\n")}` : text2;
112030
- h.send(adapterText);
112367
+ if (sendInput) sendInput(h, adapterText);
112368
+ else h.send(adapterText);
112031
112369
  }
112032
112370
  /**
112033
112371
  * Switches a session's agent (claude <-> codex).
@@ -112710,6 +113048,7 @@ ${String(stderr).slice(0, 2e3)}`
112710
113048
  sessionIdle(sessionId) {
112711
113049
  const m = this.meta.get(sessionId);
112712
113050
  if (!m) throw Object.assign(new Error(`Session not found: ${sessionId}`), { code: "session_not_found" });
113051
+ if (this.agentRuns.get(sessionId)?.waitingForInputs) return { idle: false, reason: "turn" };
112713
113052
  return sessionIdle(m, {
112714
113053
  running: this.handles.has(sessionId),
112715
113054
  reportsBackground: !!this.adapters.get(m.tool)?.capabilities.backgroundTasks
@@ -112737,7 +113076,7 @@ ${String(stderr).slice(0, 2e3)}`
112737
113076
  */
112738
113077
  async sleepSession(sessionId) {
112739
113078
  const h = this.handles.get(sessionId);
112740
- if (!h || this.resuming.has(sessionId) || this.sleeping.has(sessionId) || this.leaving.has(sessionId)) return false;
113079
+ if (!h || this.resuming.has(sessionId) || this.deferredRestarts.has(sessionId) || this.sleeping.has(sessionId) || this.leaving.has(sessionId)) return false;
112741
113080
  if (!this.sessionIdle(sessionId).idle || this.waitsOn(sessionId).length > 0) return false;
112742
113081
  this.handles.delete(sessionId);
112743
113082
  this.running.delete(sessionId);
@@ -112757,12 +113096,13 @@ ${String(stderr).slice(0, 2e3)}`
112757
113096
  return true;
112758
113097
  }
112759
113098
  interrupt(sessionId) {
112760
- this.requireHandle(sessionId).interrupt();
113099
+ const handle = this.requireHandle(sessionId);
113100
+ const run = this.agentRuns.get(sessionId);
113101
+ if (run) run.stoppedByPerson = true;
113102
+ handle.interrupt();
112761
113103
  for (const d2 of this.delegations.values()) {
112762
113104
  if (d2.callerId === sessionId && (!d2.agent || (d2.waiters ?? 0) > 0)) d2.stop("the person stopped the session that asked");
112763
113105
  }
112764
- const run = this.agentRuns.get(sessionId);
112765
- if (run) run.stoppedByPerson = true;
112766
113106
  }
112767
113107
  /**
112768
113108
  * Restarts only the agent (an extension of FR-10).
@@ -113402,7 +113742,7 @@ ${String(stderr).slice(0, 2e3)}`
113402
113742
  askHost(sessionId, detail, signal) {
113403
113743
  return new Promise((resolve11) => {
113404
113744
  if (signal?.aborted) return resolve11(null);
113405
- const requestId = `${detail.kind === "project_access" ? "xp" : detail.kind === "agent_start" ? "ag" : detail.kind === "centralu_update" ? "up" : "mk"}-${randomUUID7()}`;
113745
+ const requestId = `${detail.kind === "project_access" ? "xp" : detail.kind === "agent_start" ? "ag" : detail.kind === "centralu_update" ? "up" : "mk"}-${randomUUID9()}`;
113406
113746
  this.hostAsks.set(requestId, { requestId, sessionId, detail, shown: false, resolve: resolve11 });
113407
113747
  signal?.addEventListener("abort", () => {
113408
113748
  if (!this.hostAsks.delete(requestId)) return;
@@ -113528,31 +113868,38 @@ ${String(stderr).slice(0, 2e3)}`
113528
113868
  /** Registers the wait on the delegated session's turn (the same watcher an app's agent uses, `AgentRunWait`) */
113529
113869
  startDelegation(key, callerId, sessionId, target, agent = false) {
113530
113870
  let notice;
113531
- const wait = new AgentRunWait((message) => notice = message, () => this.meta.get(sessionId)?.name ?? "the session");
113871
+ let answerAfter = this.meta.get(sessionId)?.lastSeq ?? 0;
113872
+ const wait = new AgentRunWait((message) => notice = message, () => this.meta.get(sessionId)?.name ?? "the session", void 0, () => {
113873
+ const answer = finalAnswer(this.store.loadMessages(sessionId, 50).filter((m) => m.seq > answerAfter));
113874
+ answerAfter = this.meta.get(sessionId)?.lastSeq ?? answerAfter;
113875
+ return answer;
113876
+ });
113532
113877
  const d2 = {
113533
113878
  callerId,
113534
113879
  sessionId,
113535
113880
  target,
113536
113881
  wait,
113537
- settled: false,
113882
+ get settled() {
113883
+ return wait.finished;
113884
+ },
113538
113885
  notice: () => notice,
113539
113886
  outcome: wait.done.then(
113540
- () => ({ ok: true }),
113887
+ (r) => ({ ok: true, answer: r.answer }),
113541
113888
  (e) => ({ ok: false, error: e.message })
113542
113889
  ),
113543
113890
  stop: (why) => {
113544
113891
  if (d2.settled) return;
113892
+ wait.fail(new Error(`${why}, so the delegated turn was stopped`));
113545
113893
  try {
113546
113894
  this.handles.get(sessionId)?.interrupt();
113547
113895
  } catch {
113548
113896
  }
113549
- wait.fail(new Error(`${why}, so the delegated turn was stopped`));
113550
113897
  },
113551
113898
  ...agent ? { agent: true } : {}
113552
113899
  };
113553
- void d2.outcome.then(() => {
113554
- d2.settled = true;
113900
+ void d2.outcome.then((r) => {
113555
113901
  if (this.agentRuns.get(sessionId) === wait) this.agentRuns.delete(sessionId);
113902
+ if (agent) void this.reportBackIfAwaited(sessionId, r.ok ? { type: "turn_complete", sessionId } : { type: "error", sessionId, error: { code: "internal", message: r.error, retryable: false } }, r.ok ? r.answer : void 0);
113556
113903
  });
113557
113904
  this.agentRuns.set(sessionId, wait);
113558
113905
  this.delegations.set(key, d2);
@@ -113589,7 +113936,7 @@ ${String(stderr).slice(0, 2e3)}`
113589
113936
  error: `${d2.target.name}'s session "${sessionName()}" [${d2.sessionId}] did not finish: ${r.error}. Tell the person, or ask again with a clearer task.`
113590
113937
  };
113591
113938
  }
113592
- const answer = finalAnswer(this.store.loadMessages(d2.sessionId, 50));
113939
+ const answer = r.answer ?? finalAnswer(this.store.loadMessages(d2.sessionId, 50));
113593
113940
  const { granted, outside } = readableGrants(pathsIn(answer), d2.target.path);
113594
113941
  if (granted.length) {
113595
113942
  const set3 = this.readGrants.get(d2.callerId) ?? /* @__PURE__ */ new Set();
@@ -113708,8 +114055,8 @@ ${String(stderr).slice(0, 2e3)}`
113708
114055
  *
113709
114056
  * status where it stands, without waiting (the answer, once the turn the caller gave it has ended)
113710
114057
  * wait waits on that turn up to the bound, as start_agent does
113711
- * send gives it more: refused while a turn the caller gave it runs, while it works on the person's own
113712
- * message, or when the person has since raised its preset above the caller's
114058
+ * send gives it more, including during the caller's turn on adapters that accept mid-turn input; refused
114059
+ * while it works on the person's own message or has a higher preset than the caller
113713
114060
  * stop stops the turn the caller gave it; a turn the person started there is theirs
113714
114061
  * delete cleans up a child's merged, clean worktree (`cleanUpWorktreeChild`, #76's hard gate, part 3)
113715
114062
  */
@@ -113743,26 +114090,48 @@ ${String(stderr).slice(0, 2e3)}`
113743
114090
  return { ok: true, state: "deleted", sessionId: child.id, sessionName: child.name, tool: child.tool, branch: r.branch, proof: r.proof };
113744
114091
  }
113745
114092
  case "send": {
113746
- if (d2 && !d2.settled) {
113747
- return { ok: false, error: `"${child.name}" is still working on what you gave it. Wait for it (action "wait") or stop it first.` };
113748
- }
113749
- if (d2) this.delegations.delete(key);
113750
- if (child.state === "working" || child.state === "waiting_approval" || this.agentRuns.has(child.id)) {
113751
- return { ok: false, error: `"${child.name}" is busy with something else (the person may be using it). Try again later.` };
113752
- }
113753
114093
  if (!presetWithin(child.permissionPreset, caller.permissionPreset)) {
113754
114094
  return {
113755
114095
  ok: false,
113756
114096
  error: `"${child.name}" now has more permissions (${child.permissionPreset}) than this session (${caller.permissionPreset}), so this session cannot give it work. Tell the person.`
113757
114097
  };
113758
114098
  }
114099
+ const from = { sessionId: callerId, name: labelOf(this.store, caller) };
114100
+ if (d2 && !d2.settled) {
114101
+ const text2 = agentNoteFrame(from.name, opts.text ?? "");
114102
+ if (!this.adapters.get(child.tool)?.capabilities.midTurnInput || !this.handles.get(child.id)?.sendTracked) {
114103
+ return { ok: false, error: `"${child.name}" cannot take messages while working. Wait for it (action "wait") or stop it first.` };
114104
+ }
114105
+ const releaseHold = d2.wait.holdInput();
114106
+ const release = () => {
114107
+ releaseHold();
114108
+ this.applyDeferredRestart(child.id);
114109
+ };
114110
+ try {
114111
+ await this.deliver(child.id, text2, void 0, from, false, void 0, "inline", (h, input) => {
114112
+ const fail4 = (e) => {
114113
+ d2.wait.fail(e);
114114
+ release();
114115
+ };
114116
+ void h.sendTracked(input, { complete: release, fail: fail4 }).then(release, fail4);
114117
+ });
114118
+ } catch (e) {
114119
+ release();
114120
+ return { ok: false, error: `Could not send it: ${e.message}` };
114121
+ }
114122
+ return this.agentWorking(d2, false);
114123
+ }
114124
+ if (child.state === "working" || child.state === "waiting_approval" || this.agentRuns.has(child.id) && !this.agentRuns.get(child.id).finished) {
114125
+ return { ok: false, error: `"${child.name}" is busy with something else (the person may be using it). Try again later.` };
114126
+ }
114127
+ if (d2) this.delegations.delete(key);
113759
114128
  const full = this.agentCeilingError(child.projectId ?? "");
113760
114129
  if (full) return { ok: false, error: full };
113761
114130
  const project2 = this.store.listProjects().find((p) => p.id === child.projectId);
113762
114131
  const cwd = this.store.sessionCwd(child.id) ?? child.worktree?.path ?? project2?.path ?? "";
113763
114132
  const next = this.startDelegation(key, callerId, child.id, { id: child.projectId ?? "", name: project2?.name ?? "", path: cwd }, true);
113764
114133
  try {
113765
- await this.deliver(child.id, opts.text ?? "", void 0, { sessionId: callerId, name: labelOf(this.store, caller) }, false);
114134
+ await this.deliver(child.id, agentFrame(from.name, opts.text ?? ""), void 0, from, false);
113766
114135
  } catch (e) {
113767
114136
  this.delegations.delete(key);
113768
114137
  if (this.agentRuns.get(child.id) === next.wait) this.agentRuns.delete(child.id);
@@ -113782,7 +114151,7 @@ ${String(stderr).slice(0, 2e3)}`
113782
114151
  if (!summary.isRepo) return { error: "This project is not a git repository, so it has no worktrees. Start the agent without one." };
113783
114152
  const asked = opts.branch?.trim();
113784
114153
  if (asked) return await gitValidBranchName(asked) ? { branch: asked } : { error: `Not a valid branch name: ${asked}` };
113785
- return { branch: agentBranch(opts.task, randomUUID7()) };
114154
+ return { branch: agentBranch(opts.task, randomUUID9()) };
113786
114155
  }
113787
114156
  /**
113788
114157
  * Cleans up a child's merged worktree (#76's hard gate, moved from the retired worktree manager's tool to
@@ -113951,7 +114320,7 @@ ${String(stderr).slice(0, 2e3)}`
113951
114320
  if (r === "bound") return this.agentWorking(d2);
113952
114321
  if (this.delegations.get(key) === d2) this.delegations.delete(key);
113953
114322
  if (!r.ok) return { ok: false, error: `"${name()}" [${d2.sessionId}] did not finish: ${r.error}. Tell the person, or give it a clearer task.` };
113954
- const answer = finalAnswer(this.store.loadMessages(d2.sessionId, 50));
114323
+ const answer = r.answer ?? finalAnswer(this.store.loadMessages(d2.sessionId, 50));
113955
114324
  const { granted, outside } = readableGrants(pathsIn(answer), d2.target.path);
113956
114325
  if (granted.length) {
113957
114326
  const set3 = this.readGrants.get(d2.callerId) ?? /* @__PURE__ */ new Set();
@@ -113978,7 +114347,7 @@ ${String(stderr).slice(0, 2e3)}`
113978
114347
  if (signal.aborted) return resolve11(null);
113979
114348
  const app2 = { appId: q2.app.appId, projectId: q2.app.projectId, name: q2.appName };
113980
114349
  if (q2.origin.kind === "session" && this.meta.has(q2.origin.sessionId)) {
113981
- const requestId = `cap-${randomUUID7()}`;
114350
+ const requestId = `cap-${randomUUID9()}`;
113982
114351
  const ask = {
113983
114352
  requestId,
113984
114353
  sessionId: q2.origin.sessionId,
@@ -113997,7 +114366,7 @@ ${String(stderr).slice(0, 2e3)}`
113997
114366
  return;
113998
114367
  }
113999
114368
  const origin = q2.origin.kind === "view" ? q2.origin.app : q2.app;
114000
- const id2 = `q-${randomUUID7()}`;
114369
+ const id2 = `q-${randomUUID9()}`;
114001
114370
  this.appQuestions.set(id2, {
114002
114371
  question: { id: id2, app: app2, capability: q2.capability, text: q2.text, origin: { appId: origin.appId, projectId: origin.projectId }, askedAt: q2.askedAt, expiresAt: q2.expiresAt },
114003
114372
  resolve: resolve11
@@ -115032,13 +115401,24 @@ async function stopStrays(pids, roots, selfPid = process.pid, held3 = []) {
115032
115401
  }
115033
115402
 
115034
115403
  // packages/agent-host/src/app-home-view.ts
115035
- async function openHomeView(apps, views2, ref, holder) {
115404
+ async function openHomeView(apps, views2, ref, holder, openKey) {
115405
+ const prior = openKey ? views2.homeOpen(ref, openKey, holder) : null;
115406
+ if (prior) return prior;
115036
115407
  const home = await apps.homeView(ref);
115408
+ const concurrent = openKey ? views2.homeOpen(ref, openKey, holder) : null;
115409
+ if (concurrent) return concurrent;
115037
115410
  const toolInput = {};
115038
- const out = await apps.call(ref, home.tool, toolInput, { kind: "view" });
115039
- if (!out.result) throw Object.assign(new Error(out.error ?? `The home tool "${home.tool}" did not answer`), { code: "internal" });
115040
- const { instanceId } = views2.open(ref, home.resourceUri, holder);
115041
- return { instanceId, tool: home.tool, resourceUri: home.resourceUri, toolInput, toolResult: out.result, runId: out.runId };
115411
+ const invoke = async () => {
115412
+ const out = await apps.call(ref, home.tool, toolInput, { kind: "view" });
115413
+ if (!out.result) throw Object.assign(new Error(out.error ?? `The home tool "${home.tool}" did not answer`), { code: "internal" });
115414
+ return { tool: home.tool, resourceUri: home.resourceUri, toolInput, toolResult: out.result, runId: out.runId };
115415
+ };
115416
+ if (openKey) {
115417
+ const { instanceId } = views2.open(ref, home.resourceUri, holder);
115418
+ return views2.rememberHome(instanceId, openKey, async () => ({ instanceId, ...await invoke() }));
115419
+ }
115420
+ const outcome = await invoke();
115421
+ return { ...views2.open(ref, home.resourceUri, holder), ...outcome };
115042
115422
  }
115043
115423
 
115044
115424
  // packages/agent-host/src/builder-requests.ts
@@ -115351,8 +115731,14 @@ function createRpcHandler(mgr2, adapters2, { terminals: terminals2, updates: upd
115351
115731
  return { appId: d2.app.appId, projectId: d2.app.projectId, uri: d2.uri, origin: d2.origin, resource: d2.resource };
115352
115732
  },
115353
115733
  "apps.openView": async (p, ctx) => {
115354
- const { appId, projectId, lease } = RpcMethods["apps.openView"].params.parse(p);
115355
- return openHomeView(requireExternalApps(), requireViews(), { appId, projectId }, lease && ctx ? ctx.client : void 0);
115734
+ const { appId, projectId, lease, openKey } = RpcMethods["apps.openView"].params.parse(p);
115735
+ return openHomeView(requireExternalApps(), requireViews(), { appId, projectId }, lease && ctx ? ctx.client : void 0, openKey);
115736
+ },
115737
+ "apps.recoverView": async (p, ctx) => {
115738
+ const { appId, projectId, openKey, lease } = RpcMethods["apps.recoverView"].params.parse(p);
115739
+ const prior = requireViews().homeOpen({ appId, projectId }, openKey, lease && ctx ? ctx.client : void 0);
115740
+ if (prior) return prior;
115741
+ throw new Error("The original app open could not be recovered \u2014 the home tool may or may not have run; check before opening again");
115356
115742
  },
115357
115743
  "apps.holdViews": async (p, ctx) => {
115358
115744
  const { instanceIds } = RpcMethods["apps.holdViews"].params.parse(p);
@@ -120129,6 +120515,7 @@ var ROUTES = {
120129
120515
  * link, not the window, so its views are opened without one and live until closed, as before
120130
120516
  */
120131
120517
  "apps.openView": { kind: "project", orMachine: true, result: (r, q2) => q2.openedView(r), params: ({ lease: _lease, ...p }) => p },
120518
+ "apps.recoverView": { kind: "project", orMachine: true, result: (r, q2) => q2.openedView(r), params: ({ lease: _lease, ...p }) => p },
120132
120519
  "apps.readResource": view(["projectId", "instanceId"]),
120133
120520
  "apps.invoke": view(["projectId", "instanceId"]),
120134
120521
  "apps.inlineReopen": session((r, q2) => q2.inlineView(r)),
@@ -120488,7 +120875,7 @@ function storeMirror(store2) {
120488
120875
  var start = new StartSequence();
120489
120876
  start.at("launch variables");
120490
120877
  var launch = takeLaunchEnv();
120491
- var BUILD = true ? "60665cc" : "dev";
120878
+ var BUILD = true ? "c6afa2e" : "dev";
120492
120879
  var { values } = parseArgs({
120493
120880
  options: {
120494
120881
  port: { type: "string", default: "5175" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@centralu/host-linux-arm64",
3
- "version": "0.1.0-beta.19",
3
+ "version": "0.1.0-beta.20",
4
4
  "description": "Centralu's host without the window, for Linux on arm64: what a hub installs on a linked machine",
5
5
  "license": "MIT",
6
6
  "author": "ijun17",