@mrciphersmith/keryx 0.3.66 → 0.3.67

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.
Files changed (3) hide show
  1. package/README.md +10 -2
  2. package/dist/cli.js +387 -96
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1053,8 +1053,16 @@ once to HTML and once to plain text, so a reply is never dropped. `keryx remote
1053
1053
  format-sample` prints the sample in every mode with no network, and `/channels`
1054
1054
  shows the mode and the last fallback; see
1055
1055
  [how replies look in Telegram](docs/docs/guides/drive-keryx-remotely.md#how-replies-look-in-telegram).
1056
- This has been verified against a fake Bot API only; no run against real Telegram has
1057
- been done yet. Setup and limits:
1056
+ An approval question is confirmed by the shell: after you press Allow or Deny,
1057
+ `serve` waits five seconds for the shell to say it applied the answer to the question
1058
+ it was waiting on, then edits the message to "Allowed by ... at ..." or "Denied by ... at ...".
1059
+ If the shell was no longer waiting (the question had timed out or the session had
1060
+ reconnected) the message says "Not applied" and that the answer changed nothing. If the shell does
1061
+ not answer in time the message says the answer was sent to the shell, **not
1062
+ confirmed**: the shell did not say in time that it received the answer (a shell that never got an Allow lets its own question time out as a denial; one that got it late still applies it); while
1063
+ that is so the shell shows an "Approvals not confirmed" count in `/remote-control`
1064
+ and in the sidebar row. This has been verified against a fake Bot API only; no run
1065
+ against real Telegram has been done yet. Setup and limits:
1058
1066
  [drive keryx remotely](docs/docs/guides/drive-keryx-remotely.md#remote-control-from-telegram).
1059
1067
 
1060
1068
  ## CI integration
package/dist/cli.js CHANGED
@@ -20107,7 +20107,7 @@ var package_default;
20107
20107
  var init_package = __esm(() => {
20108
20108
  package_default = {
20109
20109
  name: "@mrciphersmith/keryx",
20110
- version: "0.3.66",
20110
+ version: "0.3.67",
20111
20111
  description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
20112
20112
  private: false,
20113
20113
  publishConfig: {
@@ -114789,6 +114789,9 @@ function channelsRoutePath(route) {
114789
114789
  function isSessionId(value) {
114790
114790
  return typeof value === "string" && SESSION_ID_PATTERN.test(value);
114791
114791
  }
114792
+ function isApprovalId(value) {
114793
+ return typeof value === "string" && APPROVAL_ID_PATTERN.test(value);
114794
+ }
114792
114795
  function approvalCallbackData(approvalId, decision) {
114793
114796
  return `ap:${approvalId}:${decision}`;
114794
114797
  }
@@ -114861,7 +114864,7 @@ class SseParser {
114861
114864
  return frames;
114862
114865
  }
114863
114866
  }
114864
- var REMOTE_API_PREFIX = "/v1/remote", REMOTE_SCHEMA_VERSION = "1.0.0", REMOTE_ROUTE_METHODS, CHANNELS_ROUTE_METHODS, MAX_REMOTE_BODY_BYTES, MAX_PROJECT_CHARS = 256, MAX_APPROVAL_PROMPT_CHARS = 3000, MAX_KEYBOARD_ROWS = 8, MAX_KEYBOARD_BUTTONS_PER_ROW = 8, MAX_BUTTON_TEXT_CHARS = 64, MAX_CALLBACK_DATA_BYTES = 64, DEFAULT_APPROVAL_TIMEOUT_MS, MAX_APPROVAL_TIMEOUT_MS, MIN_APPROVAL_TIMEOUT_MS = 100, DEFAULT_KEEPALIVE_MS = 15000, DEFAULT_ACK_TIMEOUT_MS = 30000, SESSION_ID_PATTERN, APPROVAL_CALLBACK_PATTERN, RESERVED_CALLBACK_PREFIX = "ap:", CHOICE_CALLBACK_PATTERN, RESERVED_CHOICE_PREFIX = "pk:", MAX_PROMPT_BUTTONS = 24, MESSAGE_STATES, SSE_KEEPALIVE_FRAME = `: keepalive
114867
+ var REMOTE_API_PREFIX = "/v1/remote", REMOTE_SCHEMA_VERSION = "1.0.0", REMOTE_ROUTE_METHODS, CHANNELS_ROUTE_METHODS, MAX_REMOTE_BODY_BYTES, MAX_PROJECT_CHARS = 256, MAX_APPROVAL_PROMPT_CHARS = 3000, MAX_KEYBOARD_ROWS = 8, MAX_KEYBOARD_BUTTONS_PER_ROW = 8, MAX_BUTTON_TEXT_CHARS = 64, MAX_CALLBACK_DATA_BYTES = 64, DEFAULT_APPROVAL_TIMEOUT_MS, MAX_APPROVAL_TIMEOUT_MS, MIN_APPROVAL_TIMEOUT_MS = 100, DEFAULT_APPROVAL_ACK_MS = 5000, DEFAULT_KEEPALIVE_MS = 15000, DEFAULT_ACK_TIMEOUT_MS = 30000, SESSION_ID_PATTERN, APPROVAL_ID_PATTERN, APPROVAL_CALLBACK_PATTERN, RESERVED_CALLBACK_PREFIX = "ap:", CHOICE_CALLBACK_PATTERN, RESERVED_CHOICE_PREFIX = "pk:", MAX_PROMPT_BUTTONS = 24, MESSAGE_STATES, SSE_KEEPALIVE_FRAME = `: keepalive
114865
114868
 
114866
114869
  `;
114867
114870
  var init_protocol2 = __esm(() => {
@@ -114874,6 +114877,7 @@ var init_protocol2 = __esm(() => {
114874
114877
  prompt: "POST",
114875
114878
  state: "POST",
114876
114879
  ack: "POST",
114880
+ "approval-ack": "POST",
114877
114881
  stream: "GET"
114878
114882
  };
114879
114883
  CHANNELS_ROUTE_METHODS = {
@@ -114889,6 +114893,7 @@ var init_protocol2 = __esm(() => {
114889
114893
  DEFAULT_APPROVAL_TIMEOUT_MS = 5 * 60000;
114890
114894
  MAX_APPROVAL_TIMEOUT_MS = 60 * 60000;
114891
114895
  SESSION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
114896
+ APPROVAL_ID_PATTERN = /^ap[0-9a-f]{12}$/;
114892
114897
  APPROVAL_CALLBACK_PATTERN = /^ap:(ap[0-9a-f]{12}):(allow|deny)$/;
114893
114898
  CHOICE_CALLBACK_PATTERN = /^pk:(pk[0-9a-f]{12}):(\d{1,3})$/;
114894
114899
  MESSAGE_STATES = ["reading", "working", "done", "failed"];
@@ -115253,6 +115258,10 @@ class RemoteClient {
115253
115258
  inboundChain = Promise.resolve();
115254
115259
  waiters = new Map;
115255
115260
  earlyDecisions = new Map;
115261
+ seenApprovals = new Map;
115262
+ earlyDecisionMs;
115263
+ unackedApprovals = new Map;
115264
+ lingerTimers = new Set;
115256
115265
  choiceWaiters = new Map;
115257
115266
  earlyChoices = new Map;
115258
115267
  firstResult;
@@ -115269,10 +115278,20 @@ class RemoteClient {
115269
115278
  this.sleep = options.sleep ?? abortableSleep2;
115270
115279
  this.requestTimeoutMs = options.requestTimeoutMs ?? DEFAULT_REQUEST_TIMEOUT_MS;
115271
115280
  this.isAlive = options.isAlive ?? ownProcessIsAlive;
115281
+ this.earlyDecisionMs = options.earlyDecisionMs ?? DEFAULT_EARLY_DECISION_MS;
115272
115282
  }
115273
115283
  get connected() {
115274
115284
  return this.streamOpen && !this.stopped;
115275
115285
  }
115286
+ get unconfirmedApprovals() {
115287
+ const cutoff = Date.now() - UNCONFIRMED_APPROVAL_LINGER_MS;
115288
+ for (const [id, at] of [...this.unackedApprovals.entries()]) {
115289
+ if (at < cutoff) {
115290
+ this.unackedApprovals.delete(id);
115291
+ }
115292
+ }
115293
+ return this.unackedApprovals.size;
115294
+ }
115276
115295
  start() {
115277
115296
  if (this.started) {
115278
115297
  return Promise.resolve({ ok: false, code: "already-started", message: "this client was already started", retrying: false });
@@ -115323,6 +115342,10 @@ class RemoteClient {
115323
115342
  }
115324
115343
  this.lifetime.abort();
115325
115344
  this.streamAbort?.abort();
115345
+ for (const timer of this.lingerTimers) {
115346
+ clearTimeout(timer);
115347
+ }
115348
+ this.lingerTimers.clear();
115326
115349
  this.failApprovals();
115327
115350
  this.firstResult?.({ ok: false, code: "stopped", message: "the client was stopped before it connected", retrying: false });
115328
115351
  this.firstResult = undefined;
@@ -115356,8 +115379,10 @@ class RemoteClient {
115356
115379
  }
115357
115380
  const early = this.earlyDecisions.get(approvalId);
115358
115381
  if (early !== undefined) {
115382
+ clearTimeout(early.timer);
115359
115383
  this.earlyDecisions.delete(approvalId);
115360
- return early;
115384
+ this.settleApproval(approvalId, early.decision, true);
115385
+ return early.decision;
115361
115386
  }
115362
115387
  if (!this.connected) {
115363
115388
  return "deny";
@@ -115599,8 +115624,7 @@ class RemoteClient {
115599
115624
  return;
115600
115625
  });
115601
115626
  } else if (event === "approval") {
115602
- const approval = parsed;
115603
- this.resolveApproval(approval.approvalId, approval.decision);
115627
+ this.handleApprovalFrame(parsed);
115604
115628
  } else if (event === "choice") {
115605
115629
  const choice = parsed;
115606
115630
  this.resolveChoice(choice.promptId, choice.index);
@@ -115645,16 +115669,115 @@ class RemoteClient {
115645
115669
  await this.post("ack", { sessionId: this.options.sessionId, updateId });
115646
115670
  } catch {}
115647
115671
  }
115672
+ handleApprovalFrame(approval) {
115673
+ const approvalId = approval?.approvalId;
115674
+ if (!isApprovalId(approvalId) || approval.decision !== "allow" && approval.decision !== "deny") {
115675
+ return;
115676
+ }
115677
+ const decision = approval.decision;
115678
+ const seen = this.seenApprovals.get(approvalId);
115679
+ if (seen === "pending") {
115680
+ return;
115681
+ }
115682
+ this.countUnconfirmed(approvalId);
115683
+ if (seen !== undefined) {
115684
+ this.ackApproval(approvalId, seen === "applied");
115685
+ return;
115686
+ }
115687
+ if (this.waiters.has(approvalId)) {
115688
+ this.settleApproval(approvalId, decision, true, () => this.resolveApproval(approvalId, decision));
115689
+ return;
115690
+ }
115691
+ this.rememberSeen(approvalId, "pending");
115692
+ this.parkEarlyDecision(approvalId, decision);
115693
+ }
115694
+ rememberSeen(approvalId, outcome) {
115695
+ this.seenApprovals.delete(approvalId);
115696
+ this.seenApprovals.set(approvalId, outcome);
115697
+ if (this.seenApprovals.size > MAX_SEEN_APPROVALS) {
115698
+ const oldest = this.seenApprovals.keys().next().value;
115699
+ if (oldest !== undefined) {
115700
+ this.seenApprovals.delete(oldest);
115701
+ }
115702
+ }
115703
+ }
115704
+ countUnconfirmed(approvalId) {
115705
+ this.unackedApprovals.delete(approvalId);
115706
+ this.unackedApprovals.set(approvalId, Date.now());
115707
+ this.notifyUnconfirmed();
115708
+ while (this.unackedApprovals.size > MAX_UNCONFIRMED_APPROVALS) {
115709
+ const oldest = this.unackedApprovals.keys().next().value;
115710
+ if (oldest === undefined) {
115711
+ break;
115712
+ }
115713
+ this.unackedApprovals.delete(oldest);
115714
+ }
115715
+ }
115716
+ settleApproval(approvalId, decision, applied, apply) {
115717
+ this.rememberSeen(approvalId, applied ? "applied" : "not-applied");
115718
+ try {
115719
+ this.options.onApprovalFrame?.({ approvalId, decision, applied });
115720
+ } catch {}
115721
+ apply?.();
115722
+ this.ackApproval(approvalId, applied);
115723
+ }
115724
+ parkEarlyDecision(approvalId, decision) {
115725
+ while (this.earlyDecisions.size >= MAX_EARLY_DECISIONS) {
115726
+ const oldest = this.earlyDecisions.keys().next().value;
115727
+ if (oldest === undefined) {
115728
+ break;
115729
+ }
115730
+ this.dropEarlyDecision(oldest);
115731
+ }
115732
+ const timer = setTimeout(() => this.dropEarlyDecision(approvalId), this.earlyDecisionMs);
115733
+ timer.unref?.();
115734
+ this.earlyDecisions.set(approvalId, { decision, timer });
115735
+ }
115736
+ dropEarlyDecision(approvalId) {
115737
+ const early = this.earlyDecisions.get(approvalId);
115738
+ if (early === undefined) {
115739
+ return;
115740
+ }
115741
+ clearTimeout(early.timer);
115742
+ this.earlyDecisions.delete(approvalId);
115743
+ if (!this.stopped) {
115744
+ this.settleApproval(approvalId, early.decision, false);
115745
+ }
115746
+ }
115747
+ async ackApproval(approvalId, applied) {
115748
+ let confirmed = false;
115749
+ try {
115750
+ const response = await this.post("approval-ack", { sessionId: this.options.sessionId, approvalId, applied });
115751
+ confirmed = response.ok;
115752
+ } catch {}
115753
+ if (this.stopped) {
115754
+ return;
115755
+ }
115756
+ if (confirmed) {
115757
+ this.unackedApprovals.delete(approvalId);
115758
+ this.notifyUnconfirmed();
115759
+ return;
115760
+ }
115761
+ this.lingerTimers.add(this.scheduleUnref(() => {
115762
+ this.notifyUnconfirmed();
115763
+ }, UNCONFIRMED_APPROVAL_LINGER_MS + 50));
115764
+ }
115765
+ notifyUnconfirmed() {
115766
+ try {
115767
+ this.options.onUnconfirmedChange?.();
115768
+ } catch {}
115769
+ }
115770
+ scheduleUnref(fn, ms) {
115771
+ const timer = setTimeout(() => {
115772
+ this.lingerTimers.delete(timer);
115773
+ fn();
115774
+ }, ms);
115775
+ timer.unref?.();
115776
+ return timer;
115777
+ }
115648
115778
  resolveApproval(approvalId, decision) {
115649
115779
  const waiter = this.waiters.get(approvalId);
115650
115780
  if (waiter === undefined) {
115651
- if (this.earlyDecisions.size >= MAX_EARLY_DECISIONS) {
115652
- const oldest = this.earlyDecisions.keys().next().value;
115653
- if (oldest !== undefined) {
115654
- this.earlyDecisions.delete(oldest);
115655
- }
115656
- }
115657
- this.earlyDecisions.set(approvalId, decision);
115658
115781
  return;
115659
115782
  }
115660
115783
  clearTimeout(waiter.timer);
@@ -115691,11 +115814,13 @@ class RemoteClient {
115691
115814
  this.waiters.delete(id);
115692
115815
  waiter.resolve("deny");
115693
115816
  }
115694
- this.earlyDecisions.clear();
115817
+ for (const id of [...this.earlyDecisions.keys()]) {
115818
+ this.dropEarlyDecision(id);
115819
+ }
115695
115820
  this.failChoices();
115696
115821
  }
115697
115822
  }
115698
- var UNVERIFIED_SERVE_MESSAGE = "the program answering on keryx serve's port did not prove it is the keryx serve this shell trusts; its answer was ignored. Restart `keryx serve` (an older serve cannot prove itself; update keryx on both sides)", FatalClientError, TransientClientError, DEFAULT_BACKOFF_INITIAL_MS = 250, DEFAULT_BACKOFF_MAX_MS = 15000, DEFAULT_REQUEST_TIMEOUT_MS = 1e4, MAX_EARLY_DECISIONS = 32, MAX_COMPLETED_IDS = 256;
115823
+ var UNVERIFIED_SERVE_MESSAGE = "the program answering on keryx serve's port did not prove it is the keryx serve this shell trusts; its answer was ignored. Restart `keryx serve` (an older serve cannot prove itself; update keryx on both sides)", FatalClientError, TransientClientError, DEFAULT_BACKOFF_INITIAL_MS = 250, DEFAULT_BACKOFF_MAX_MS = 15000, DEFAULT_REQUEST_TIMEOUT_MS = 1e4, MAX_EARLY_DECISIONS = 32, DEFAULT_EARLY_DECISION_MS = 2000, MAX_SEEN_APPROVALS = 256, UNCONFIRMED_APPROVAL_LINGER_MS = 60000, MAX_UNCONFIRMED_APPROVALS = 64, MAX_COMPLETED_IDS = 256;
115699
115824
  var init_client3 = __esm(() => {
115700
115825
  init_serve_config();
115701
115826
  init_lease();
@@ -116375,10 +116500,12 @@ class RemoteBridge {
116375
116500
  }
116376
116501
  const online = this.clientConnected && client.connected;
116377
116502
  const heartbeat = client.lastHeartbeatAt;
116503
+ const unconfirmed = client.unconfirmedApprovals ?? 0;
116378
116504
  return {
116379
116505
  state: online ? "on" : "offline",
116380
116506
  ...client.name !== undefined ? { name: client.name } : {},
116381
116507
  ...heartbeat !== undefined ? { heartbeatAgeMs: Math.max(0, this.now() - heartbeat) } : {},
116508
+ ...unconfirmed > 0 ? { unconfirmedApprovals: unconfirmed } : {},
116382
116509
  events: [...this.events]
116383
116510
  };
116384
116511
  }
@@ -116405,7 +116532,9 @@ class RemoteBridge {
116405
116532
  ...name !== undefined ? { name } : {},
116406
116533
  ...this.options.dir !== undefined ? { dir: this.options.dir } : {},
116407
116534
  onLine: (text, meta) => this.accept(text, meta),
116408
- onStatus: (status) => this.onStatus(status)
116535
+ onStatus: (status) => this.onStatus(status),
116536
+ onApprovalFrame: (frame) => this.host.notice(frame.applied ? `\u25C7 approval ${frame.approvalId} ${frame.decision === "allow" ? "allowed" : "denied"} from Telegram` : `\u25C7 approval ${frame.approvalId}: ${frame.decision === "allow" ? "an allow" : "a deny"} from Telegram arrived but nothing here was waiting for it; not applied`),
116537
+ onUnconfirmedChange: () => this.host.onChange?.()
116409
116538
  };
116410
116539
  const client = this.options.makeClient !== undefined ? this.options.makeClient(clientOptions) : new RemoteClient(clientOptions);
116411
116540
  this.client = client;
@@ -118034,6 +118163,10 @@ function formatRemoteStatusLines(status) {
118034
118163
  lines.push(`Last heartbeat: ${status.heartbeatAgeMs === undefined ? "none yet" : `${formatAge(status.heartbeatAgeMs)} ago`}`);
118035
118164
  if (status.state === "offline")
118036
118165
  lines.push("Serve is not reachable. The shell keeps retrying; nothing is lost from Telegram.");
118166
+ if (status.unconfirmedApprovals !== undefined && status.unconfirmedApprovals > 0) {
118167
+ const n = status.unconfirmedApprovals;
118168
+ lines.push(`Approvals not confirmed: ${n}. The topic shows ${n === 1 ? "it" : "them"} as "not confirmed" until serve takes the shell's ack.`);
118169
+ }
118037
118170
  lines.push(`Turn it off with ${REMOTE_CONTROL_COMMAND} off, or press o here. Rename: ${REMOTE_CONTROL_COMMAND} off, then ${REMOTE_CONTROL_COMMAND} <name>.`);
118038
118171
  return lines;
118039
118172
  }
@@ -118154,7 +118287,11 @@ function projectRemoteRow(status, width) {
118154
118287
  return { text: fit3("off", width), role: "muted", action: "open" };
118155
118288
  if (status.state === "offline")
118156
118289
  return { text: fit3("offline", width), role: "attention", action: "open" };
118157
- return { text: fit3(status.name ?? "connecting", width), role: "ok", action: "open" };
118290
+ const topic = status.name ?? "connecting";
118291
+ const unconfirmed = status.unconfirmedApprovals ?? 0;
118292
+ if (unconfirmed > 0)
118293
+ return { text: fit3(`${topic} \xB7 ${unconfirmed} unconfirmed`, width), role: "attention", action: "open" };
118294
+ return { text: fit3(topic, width), role: "ok", action: "open" };
118158
118295
  }
118159
118296
  function mountRemotePanel(otui, renderer, parent, options) {
118160
118297
  const core = otui;
@@ -121375,7 +121512,7 @@ var init_serve_turn_store = __esm(() => {
121375
121512
  import { randomUUID as randomUUID34 } from "crypto";
121376
121513
  import { existsSync as existsSync45, readdirSync as readdirSync10 } from "fs";
121377
121514
  import path261 from "path";
121378
- function isApprovalId(value) {
121515
+ function isApprovalId2(value) {
121379
121516
  return ID_PATTERN2.test(value);
121380
121517
  }
121381
121518
  function root(dir) {
@@ -121401,7 +121538,7 @@ function appendEvidence(event, dir) {
121401
121538
  appendOwnerOnlyLine(ledgerFile(dir), JSON.stringify(event));
121402
121539
  }
121403
121540
  function createApproval(input, dir, now = new Date) {
121404
- if (!isApprovalId(input.approvalId)) {
121541
+ if (!isApprovalId2(input.approvalId)) {
121405
121542
  throw new Error("approvalId is not an approval id");
121406
121543
  }
121407
121544
  ensureKeryxSubdir(["approvals"], dir);
@@ -121468,7 +121605,7 @@ function parseResolution(text) {
121468
121605
  }
121469
121606
  }
121470
121607
  function readRaw(id, dir) {
121471
- if (!isApprovalId(id)) {
121608
+ if (!isApprovalId2(id)) {
121472
121609
  return { ok: false, reason: "not-an-approval-id" };
121473
121610
  }
121474
121611
  const read = readConfigFile(requestFile(id, dir));
@@ -121572,7 +121709,7 @@ function approvalIds(dir) {
121572
121709
  if (!existsSync45(base)) {
121573
121710
  return [];
121574
121711
  }
121575
- return readdirSync10(base).filter((name) => name.endsWith(".json") && !name.endsWith(".resolution.json")).map((name) => name.slice(0, -".json".length)).filter(isApprovalId);
121712
+ return readdirSync10(base).filter((name) => name.endsWith(".json") && !name.endsWith(".resolution.json")).map((name) => name.slice(0, -".json".length)).filter(isApprovalId2);
121576
121713
  }
121577
121714
  function listApprovals(dir, options = {}) {
121578
121715
  const views = [];
@@ -141950,6 +142087,98 @@ function drainIdleMainQueue(items, options) {
141950
142087
  return true;
141951
142088
  }
141952
142089
 
142090
+ // src/tui/remote-queue-wiring.ts
142091
+ function createRemoteQueueWiring(deps) {
142092
+ let pendingEdit;
142093
+ return {
142094
+ removeMainQueue(index) {
142095
+ const queue = deps.getQueue();
142096
+ if (index < 0 || index >= queue.length)
142097
+ return;
142098
+ const removed = queue[index];
142099
+ if (removed?.source === TG_SOURCE) {
142100
+ deps.getBridge()?.queuedLineRemoved(removed.question);
142101
+ }
142102
+ deps.setQueue(removeMainQueueItem(queue, index));
142103
+ deps.paint();
142104
+ },
142105
+ editMainQueue(index) {
142106
+ const edited = editMainQueueItem(deps.getQueue(), index);
142107
+ if (edited === undefined)
142108
+ return;
142109
+ deps.setQueue(edited.rest);
142110
+ pendingEdit = pendingQueueEditFor(edited.removed, index);
142111
+ deps.composer.setText(edited.text);
142112
+ deps.composer.focus();
142113
+ deps.paint();
142114
+ },
142115
+ requeuePendingEdit(line, displayLine) {
142116
+ if (pendingEdit === undefined)
142117
+ return false;
142118
+ const edit = pendingEdit;
142119
+ pendingEdit = undefined;
142120
+ deps.setQueue(reinsertEditedMainQueueItem(deps.getQueue(), edit, line, displayLine));
142121
+ deps.paint();
142122
+ return true;
142123
+ },
142124
+ hasPendingEdit: () => pendingEdit !== undefined,
142125
+ stopRemoteForSessionSwitch() {
142126
+ const bridge = deps.getBridge();
142127
+ if (bridge?.keepingTopic === true) {
142128
+ bridge.sessionLeaving();
142129
+ return;
142130
+ }
142131
+ if (bridge?.active !== true)
142132
+ return;
142133
+ const closing = bridge.disable();
142134
+ deps.say(`\u25C7 remote control is turning off: the session changed, and its Telegram topic is being deleted. Turn it on again with /remote-control <name>.
142135
+ `);
142136
+ closing.then(() => deps.say(`\u25C7 remote control off: the topic is deleted.
142137
+ `));
142138
+ },
142139
+ dropQueuedTelegramLines() {
142140
+ const { kept, dropped } = dropQueuedBySource(deps.getQueue(), TG_SOURCE);
142141
+ if (dropped.length === 0)
142142
+ return [];
142143
+ deps.setQueue(kept);
142144
+ deps.paint();
142145
+ return dropped.map((item) => item.question);
142146
+ },
142147
+ enqueueTelegramLine(text) {
142148
+ const queue = deps.getQueue();
142149
+ queue.push({ id: deps.nextId(), question: text, displayQuestion: deps.summarize(text), source: TG_SOURCE });
142150
+ deps.paint();
142151
+ deps.say(`\u25C7 a line from Telegram is queued as q${queue.length}.
142152
+ `);
142153
+ },
142154
+ runTelegramLine(text) {
142155
+ deps.runLine(text, "operator", TG_SOURCE);
142156
+ },
142157
+ runQueued(item) {
142158
+ deps.runLine(item.question, "operator", item.source);
142159
+ },
142160
+ turnStarted(source) {
142161
+ deps.getBridge()?.turnStarted(source);
142162
+ },
142163
+ assistantText(text) {
142164
+ deps.getBridge()?.assistantText(text);
142165
+ },
142166
+ wrapOnToolCall(base) {
142167
+ return (...args) => {
142168
+ deps.getBridge()?.toolCall();
142169
+ base?.(...args);
142170
+ };
142171
+ },
142172
+ turnSettled(outcome) {
142173
+ const bridge = deps.getBridge();
142174
+ return bridge === undefined ? Promise.resolve() : bridge.turnSettled({ failed: outcome.failed || outcome.aborted });
142175
+ }
142176
+ };
142177
+ }
142178
+ var init_remote_queue_wiring = __esm(() => {
142179
+ init_shell_bridge();
142180
+ });
142181
+
141953
142182
  // src/tui/worker-fleet.ts
141954
142183
  function clip4(s, max) {
141955
142184
  if (s.length <= max) {
@@ -151763,17 +151992,31 @@ async function launchTuiAgentShell(opts) {
151763
151992
  guardCollector.onToolResult(name, toolResult);
151764
151993
  baseOnToolResultForGuard?.(name, toolResult);
151765
151994
  };
151995
+ const remoteQueue = createRemoteQueueWiring({
151996
+ getBridge: () => remoteBridge,
151997
+ getQueue: () => mainQueue,
151998
+ setQueue: (next) => {
151999
+ mainQueue = next;
152000
+ },
152001
+ nextId: () => `mq${mainQueueSeq++}`,
152002
+ summarize: summarizeSubmittedLine,
152003
+ paint: () => paintMainQueue(),
152004
+ composer: {
152005
+ setText: (text) => {
152006
+ input.value = text;
152007
+ },
152008
+ focus: () => input.focus()
152009
+ },
152010
+ say: (text) => io.onSystem?.(text),
152011
+ runLine: (line, origin, source) => runLine(line, origin, source)
152012
+ });
151766
152013
  const baseOnAssistantTextForGuard = io.onAssistantText?.bind(io);
151767
152014
  io.onAssistantText = (text) => {
151768
152015
  guardCollector.onAssistantText(text);
151769
- remoteBridge?.assistantText(text);
152016
+ remoteQueue.assistantText(text);
151770
152017
  baseOnAssistantTextForGuard?.(text);
151771
152018
  };
151772
- const baseOnToolCallForRemote = io.onToolCall?.bind(io);
151773
- io.onToolCall = (name, toolInput) => {
151774
- remoteBridge?.toolCall();
151775
- baseOnToolCallForRemote?.(name, toolInput);
151776
- };
152019
+ io.onToolCall = remoteQueue.wrapOnToolCall(io.onToolCall?.bind(io));
151777
152020
  const refreshGuardSidebar = () => {
151778
152021
  clearTranscriptChildren(sbGuard);
151779
152022
  if (!guardEnabled)
@@ -152419,19 +152662,7 @@ async function launchTuiAgentShell(opts) {
152419
152662
  deps = { ...deps, onContextCompaction };
152420
152663
  liveDeps = deps;
152421
152664
  let splash = undefined;
152422
- const stopRemoteForSessionSwitch = () => {
152423
- if (remoteBridge?.keepingTopic === true) {
152424
- remoteBridge.sessionLeaving();
152425
- return;
152426
- }
152427
- if (remoteBridge?.active !== true)
152428
- return;
152429
- const closing = remoteBridge.disable();
152430
- io.onSystem?.(`\u25C7 remote control is turning off: the session changed, and its Telegram topic is being deleted. Turn it on again with /remote-control <name>.
152431
- `);
152432
- closing.then(() => io.onSystem?.(`\u25C7 remote control off: the topic is deleted.
152433
- `));
152434
- };
152665
+ const stopRemoteForSessionSwitch = () => remoteQueue.stopRemoteForSessionSwitch();
152435
152666
  const applyOpened = (opened, previewHistory) => {
152436
152667
  if (opened.history.length > 0) {
152437
152668
  splash?.removeIfShown();
@@ -153567,7 +153798,6 @@ ${formatThemeList(getThemeId())}`);
153567
153798
  const sideQueue = [];
153568
153799
  let mainQueue = [];
153569
153800
  let mainQueueSeq = 0;
153570
- let pendingQueueEdit;
153571
153801
  const forceHandoff = createForegroundForceHandoff();
153572
153802
  let sideWorkerRunning = false;
153573
153803
  let sideClearTimeout;
@@ -153675,26 +153905,8 @@ ${formatThemeList(getThemeId())}`);
153675
153905
  fleet.remove("agent:queue");
153676
153906
  }
153677
153907
  };
153678
- const removeMainQueue = (index) => {
153679
- if (index < 0 || index >= mainQueue.length)
153680
- return;
153681
- const removedItem = mainQueue[index];
153682
- if (removedItem?.source === TG_SOURCE) {
153683
- remoteBridge?.queuedLineRemoved(removedItem.question);
153684
- }
153685
- mainQueue = removeMainQueueItem(mainQueue, index);
153686
- paintMainQueue();
153687
- };
153688
- const editMainQueue = (index) => {
153689
- const edited = editMainQueueItem(mainQueue, index);
153690
- if (edited === undefined)
153691
- return;
153692
- mainQueue = edited.rest;
153693
- pendingQueueEdit = pendingQueueEditFor(edited.removed, index);
153694
- input.value = edited.text;
153695
- input.focus();
153696
- paintMainQueue();
153697
- };
153908
+ const removeMainQueue = (index) => remoteQueue.removeMainQueue(index);
153909
+ const editMainQueue = (index) => remoteQueue.editMainQueue(index);
153698
153910
  const forceMainQueue = (index) => {
153699
153911
  const item = mainQueue[index];
153700
153912
  if (item === undefined)
@@ -153709,7 +153921,7 @@ ${formatThemeList(getThemeId())}`);
153709
153921
  forceForegroundQueueItem(foregroundOperation, forceHandoff, item, {
153710
153922
  announce: () => io.onSystem?.(`\u25C7 main turn interrupted \u2014 q${index + 1} will run next.
153711
153923
  `),
153712
- run: (next) => runLine(next.question, "operator", next.source)
153924
+ run: (next) => remoteQueue.runQueued(next)
153713
153925
  });
153714
153926
  };
153715
153927
  const handleQueueNavKey = (key) => {
@@ -154118,11 +154330,7 @@ ${formatThemeList(getThemeId())}`);
154118
154330
  case "not-a-command":
154119
154331
  break;
154120
154332
  }
154121
- if (pendingQueueEdit !== undefined) {
154122
- const edit = pendingQueueEdit;
154123
- pendingQueueEdit = undefined;
154124
- mainQueue = reinsertEditedMainQueueItem(mainQueue, edit, line, displayLine);
154125
- paintMainQueue();
154333
+ if (remoteQueue.requeuePendingEdit(line, displayLine)) {
154126
154334
  return;
154127
154335
  }
154128
154336
  if (leaseView()?.held() === true) {
@@ -154163,7 +154371,7 @@ ${formatThemeList(getThemeId())}`);
154163
154371
  takeForced: () => forceHandoff.takeNext(),
154164
154372
  dispatch: (next) => {
154165
154373
  paintMainQueue();
154166
- runLine(next.question, "operator", next.source);
154374
+ remoteQueue.runQueued(next);
154167
154375
  }
154168
154376
  });
154169
154377
  } else {
@@ -154939,7 +155147,7 @@ ${formatThemeList(getThemeId())}`);
154939
155147
  const next = forceHandoff.takeNext() ?? mainQueue.shift();
154940
155148
  paintMainQueue();
154941
155149
  if (next !== undefined)
154942
- runLine(next.question, "operator", next.source);
155150
+ remoteQueue.runQueued(next);
154943
155151
  }
154944
155152
  }
154945
155153
  });
@@ -154951,7 +155159,7 @@ ${formatThemeList(getThemeId())}`);
154951
155159
  sessions.clear();
154952
155160
  deps.resetSubagentBudget?.();
154953
155161
  const operation = foregroundOperation.begin();
154954
- remoteBridge?.turnStarted(source);
155162
+ remoteQueue.turnStarted(source);
154955
155163
  blockIo.resetReasoningLiveState();
154956
155164
  setMainAgent("running", "waiting");
154957
155165
  startBusy("waiting for model");
@@ -155046,7 +155254,7 @@ ${formatThemeList(getThemeId())}`);
155046
155254
  ...slateSession !== undefined ? { slateSession, pruneArchive: true } : {}
155047
155255
  }).finally(() => {
155048
155256
  foregroundOperation.settle(operation);
155049
- remoteBridge?.turnSettled({ failed: turnFailed || turnSignal.aborted });
155257
+ remoteQueue.turnSettled({ failed: turnFailed, aborted: turnSignal.aborted });
155050
155258
  if (foregroundOperation.isDisposed)
155051
155259
  return;
155052
155260
  refreshRewindSidebar();
@@ -155099,7 +155307,7 @@ ${formatThemeList(getThemeId())}`);
155099
155307
  const next = forceHandoff.takeNext() ?? mainQueue.shift();
155100
155308
  paintMainQueue();
155101
155309
  if (next !== undefined) {
155102
- runLine(next.question, "operator", next.source);
155310
+ remoteQueue.runQueued(next);
155103
155311
  } else {
155104
155312
  busWakeController?.onSettle();
155105
155313
  }
@@ -155143,7 +155351,7 @@ ${formatThemeList(getThemeId())}`);
155143
155351
  const drained = forceHandoff.takeNext() ?? mainQueue.shift();
155144
155352
  paintMainQueue();
155145
155353
  if (drained !== undefined)
155146
- runLine(drained.question, "operator", drained.source);
155354
+ remoteQueue.runQueued(drained);
155147
155355
  }
155148
155356
  });
155149
155357
  const captureShellOutput = async (run) => {
@@ -155255,14 +155463,8 @@ Turn: ${chrome.isBusy() ? "running" : "idle"} \xB7 queue: ${mainQueue.length} wa
155255
155463
  sessionId: () => liveSession.summary.id,
155256
155464
  project: () => sessionCwd,
155257
155465
  isBusy: () => chrome.isBusy() || foregroundOperation.isActive || forceHandoff.isAwaitingSettlement || mainQueue.length > 0 || leaseView()?.held() === true,
155258
- runLine: (text) => runLine(text, "operator", TG_SOURCE),
155259
- enqueue: (text) => {
155260
- const id = `mq${mainQueueSeq++}`;
155261
- mainQueue.push({ id, question: text, displayQuestion: summarizeSubmittedLine(text), source: TG_SOURCE });
155262
- paintMainQueue();
155263
- io.onSystem?.(`\u25C7 a line from Telegram is queued as q${mainQueue.length}.
155264
- `);
155265
- },
155466
+ runLine: (text) => remoteQueue.runTelegramLine(text),
155467
+ enqueue: (text) => remoteQueue.enqueueTelegramLine(text),
155266
155468
  notice: (text) => io.onSystem?.(`${text}
155267
155469
  `),
155268
155470
  cancelTurn: () => foregroundOperation.cancel("remote run time limit"),
@@ -155280,14 +155482,7 @@ Turn: ${chrome.isBusy() ? "running" : "idle"} \xB7 queue: ${mainQueue.length} wa
155280
155482
  liveSession = recordRemoteOff(liveSession);
155281
155483
  } catch {}
155282
155484
  },
155283
- dropQueuedTelegramLines: () => {
155284
- const { kept, dropped } = dropQueuedBySource(mainQueue, TG_SOURCE);
155285
- if (dropped.length === 0)
155286
- return [];
155287
- mainQueue = kept;
155288
- paintMainQueue();
155289
- return dropped.map((item) => item.question);
155290
- },
155485
+ dropQueuedTelegramLines: () => remoteQueue.dropQueuedTelegramLines(),
155291
155486
  runCommand: runRemoteCommand,
155292
155487
  busyRefusal: (line) => {
155293
155488
  const command = findAgentCommand(line, "agent");
@@ -155514,6 +155709,7 @@ var init_tui_shell = __esm(() => {
155514
155709
  init_composer_choice();
155515
155710
  init_filter_list();
155516
155711
  init_shell_chrome();
155712
+ init_remote_queue_wiring();
155517
155713
  init_queue_nav();
155518
155714
  init_subagent_inspector();
155519
155715
  init_subagent_session();
@@ -192911,7 +193107,7 @@ function listApprovalsRoute(request, ctx) {
192911
193107
  });
192912
193108
  }
192913
193109
  async function answerApprovalRoute(request, approvalId, ctx) {
192914
- if (!isApprovalId(approvalId)) {
193110
+ if (!isApprovalId2(approvalId)) {
192915
193111
  return approvalNotFound();
192916
193112
  }
192917
193113
  const declared = Number(request.headers.get("content-length") ?? "0");
@@ -193178,6 +193374,7 @@ var init_serve_server = __esm(() => {
193178
193374
  ["prompt", "POST"],
193179
193375
  ["state", "POST"],
193180
193376
  ["ack", "POST"],
193377
+ ["approval-ack", "POST"],
193181
193378
  ["stream", "GET"],
193182
193379
  ["channels-status", "GET"],
193183
193380
  ["channels-pair", "POST"],
@@ -196665,6 +196862,7 @@ class RemoteHttpSurface {
196665
196862
  timers;
196666
196863
  keepaliveMs;
196667
196864
  ackTimeoutMs;
196865
+ approvalAckMs;
196668
196866
  newApprovalId;
196669
196867
  newPromptId;
196670
196868
  streams = new Map;
@@ -196673,6 +196871,8 @@ class RemoteHttpSurface {
196673
196871
  bound = new Map;
196674
196872
  pendingAcks = new Map;
196675
196873
  approvals = new Map;
196874
+ awaiting = new Map;
196875
+ decided = new Map;
196676
196876
  choices = new Map;
196677
196877
  finished = new Map;
196678
196878
  constructor(options) {
@@ -196681,6 +196881,7 @@ class RemoteHttpSurface {
196681
196881
  this.timers = options.timers ?? realTimers;
196682
196882
  this.keepaliveMs = options.keepaliveMs ?? DEFAULT_KEEPALIVE_MS;
196683
196883
  this.ackTimeoutMs = options.ackTimeoutMs ?? DEFAULT_ACK_TIMEOUT_MS;
196884
+ this.approvalAckMs = options.approvalAckMs ?? DEFAULT_APPROVAL_ACK_MS;
196684
196885
  this.newApprovalId = options.approvalIds ?? (() => `ap${randomBytes15(6).toString("hex")}`);
196685
196886
  this.newPromptId = options.promptIds ?? (() => `pk${randomBytes15(6).toString("hex")}`);
196686
196887
  this.consumer = {
@@ -196747,6 +196948,8 @@ class RemoteHttpSurface {
196747
196948
  return this.messageState(hub, request);
196748
196949
  case "ack":
196749
196950
  return this.ack(hub, request);
196951
+ case "approval-ack":
196952
+ return this.approvalAck(hub, request);
196750
196953
  case "stream":
196751
196954
  return this.openStream(hub, request, io);
196752
196955
  }
@@ -196765,6 +196968,9 @@ class RemoteHttpSurface {
196765
196968
  for (const id of [...this.approvals.keys()]) {
196766
196969
  this.dropApproval(id);
196767
196970
  }
196971
+ for (const id of [...this.awaiting.keys()]) {
196972
+ this.dropAwaiting(id);
196973
+ }
196768
196974
  for (const id of [...this.choices.keys()]) {
196769
196975
  this.dropChoice(id);
196770
196976
  }
@@ -197088,6 +197294,48 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
197088
197294
  this.complete(value.sessionId, value.updateId);
197089
197295
  return ok2({ acknowledged: true });
197090
197296
  }
197297
+ async approvalAck(hub, request) {
197298
+ const body = await readJsonBody(request);
197299
+ if (!body.ok) {
197300
+ return body.response;
197301
+ }
197302
+ const value = body.value;
197303
+ if (!isSessionId(value.sessionId)) {
197304
+ return invalid2("sessionId must be 1 to 64 characters of letters, digits, '-' and '_'.");
197305
+ }
197306
+ if (!isApprovalId(value.approvalId)) {
197307
+ return invalid2("approvalId must be 'ap' and 12 lowercase hex characters.");
197308
+ }
197309
+ if (!hub.hasSession(value.sessionId)) {
197310
+ return fail4(404, "unknown-session", "This session is not registered for remote control; register again.");
197311
+ }
197312
+ if (value.applied !== undefined && typeof value.applied !== "boolean") {
197313
+ return invalid2("applied must be true or false when present.");
197314
+ }
197315
+ const sessionId = value.sessionId;
197316
+ const id = value.approvalId;
197317
+ const waiting = this.awaiting.get(id);
197318
+ if (waiting !== undefined && waiting.sessionId === sessionId) {
197319
+ this.dropAwaiting(id);
197320
+ this.rememberDecided(id, sessionId);
197321
+ if (value.applied === false) {
197322
+ this.finishApproval(id, sessionId, waiting.messageId, waiting.text, {
197323
+ result: `Not applied at ${waiting.sentAt}: the shell was no longer waiting for this question, so the ${waiting.decision === "allow" ? "Allow" : "Deny"} from ${waiting.who} changed nothing.`,
197324
+ shortReply: "Not applied: the shell was no longer waiting for this question."
197325
+ });
197326
+ return ok2({ acknowledged: true });
197327
+ }
197328
+ this.finishApproval(id, sessionId, waiting.messageId, waiting.text, {
197329
+ result: waiting.decision === "allow" ? `Allowed by ${waiting.who} at ${waiting.sentAt}.` : `Denied by ${waiting.who} at ${waiting.sentAt}.`,
197330
+ shortReply: waiting.decision === "allow" ? "Approval granted." : "Approval denied."
197331
+ });
197332
+ return ok2({ acknowledged: true });
197333
+ }
197334
+ if (this.decided.get(id) === sessionId) {
197335
+ return ok2({ acknowledged: true });
197336
+ }
197337
+ return fail4(404, "unknown-approval", "No approval with that id was sent to this session.");
197338
+ }
197091
197339
  openStream(hub, request, io) {
197092
197340
  const sessionId = new URL(request.url).searchParams.get("sessionId");
197093
197341
  if (!isSessionId(sessionId)) {
@@ -197244,10 +197492,14 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
197244
197492
  });
197245
197493
  return;
197246
197494
  }
197247
- const who = `user ${callback.fromId}`;
197248
- this.finishApproval(id, sessionId, messageId, entry.text, {
197249
- result: approval.decision === "allow" ? `Allowed by ${who} at ${when}.` : `Denied by ${who} at ${when}.`,
197250
- shortReply: approval.decision === "allow" ? "Approval granted." : "Approval denied."
197495
+ this.awaiting.set(id, {
197496
+ sessionId,
197497
+ decision: approval.decision,
197498
+ who: `user ${callback.fromId}`,
197499
+ sentAt: when,
197500
+ ...messageId === undefined ? {} : { messageId },
197501
+ ...entry.text === undefined ? {} : { text: entry.text },
197502
+ timer: this.timers.setTimeout(() => this.giveUpOnAck(id), this.approvalAckMs)
197251
197503
  });
197252
197504
  return;
197253
197505
  }
@@ -197255,6 +197507,9 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
197255
197507
  this.expireApproval(id);
197256
197508
  return;
197257
197509
  }
197510
+ if (this.awaiting.has(id)) {
197511
+ return;
197512
+ }
197258
197513
  const done = this.finished.get(id);
197259
197514
  if (done !== undefined) {
197260
197515
  if (done.sessionId === sessionId) {
@@ -197275,7 +197530,7 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
197275
197530
  }
197276
197531
  }
197277
197532
  carriesLivePrompt(messageId) {
197278
- return [...this.approvals.values(), ...this.choices.values()].some((live) => live.messageId === messageId);
197533
+ return [...this.approvals.values(), ...this.awaiting.values(), ...this.choices.values()].some((live) => live.messageId === messageId);
197279
197534
  }
197280
197535
  pressChoice(sessionId, callback, choice, stream) {
197281
197536
  const id = choice.promptId;
@@ -197358,7 +197613,7 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
197358
197613
  });
197359
197614
  }
197360
197615
  onApprovalSent(approvalId, sessionId, messageId, text) {
197361
- const live = this.approvals.get(approvalId);
197616
+ const live = this.approvals.get(approvalId) ?? this.awaiting.get(approvalId);
197362
197617
  if (live !== undefined) {
197363
197618
  live.messageId = messageId;
197364
197619
  live.text = text;
@@ -197411,6 +197666,37 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
197411
197666
  this.timers.clearTimeout(entry.timer);
197412
197667
  this.approvals.delete(approvalId);
197413
197668
  }
197669
+ dropAwaiting(approvalId) {
197670
+ const entry = this.awaiting.get(approvalId);
197671
+ if (entry === undefined) {
197672
+ return;
197673
+ }
197674
+ this.timers.clearTimeout(entry.timer);
197675
+ this.awaiting.delete(approvalId);
197676
+ }
197677
+ rememberDecided(approvalId, sessionId) {
197678
+ this.decided.delete(approvalId);
197679
+ this.decided.set(approvalId, sessionId);
197680
+ while (this.decided.size > MAX_DECIDED_APPROVALS) {
197681
+ const oldest = this.decided.keys().next().value;
197682
+ if (oldest === undefined) {
197683
+ break;
197684
+ }
197685
+ this.decided.delete(oldest);
197686
+ }
197687
+ }
197688
+ giveUpOnAck(approvalId) {
197689
+ const entry = this.awaiting.get(approvalId);
197690
+ if (entry === undefined) {
197691
+ return;
197692
+ }
197693
+ this.dropAwaiting(approvalId);
197694
+ this.rememberDecided(approvalId, entry.sessionId);
197695
+ this.finishApproval(approvalId, entry.sessionId, entry.messageId, entry.text, {
197696
+ result: `Sent to the shell at ${entry.sentAt}, not confirmed; the shell denies by itself if it did not receive it.`,
197697
+ shortReply: "Sent to the shell, not confirmed; it denies by itself if it did not receive it."
197698
+ });
197699
+ }
197414
197700
  expireApproval(approvalId) {
197415
197701
  const entry = this.approvals.get(approvalId);
197416
197702
  if (entry === undefined) {
@@ -197447,6 +197733,11 @@ ${asCodeBlock(redactSensitiveText(value.prompt))}`, {
197447
197733
  }
197448
197734
  }
197449
197735
  }
197736
+ for (const [id, entry] of [...this.awaiting.entries()]) {
197737
+ if (entry.sessionId === stream.sessionId && !this.closed) {
197738
+ this.giveUpOnAck(id);
197739
+ }
197740
+ }
197450
197741
  for (const [id, entry] of [...this.choices.entries()]) {
197451
197742
  if (entry.sessionId === stream.sessionId) {
197452
197743
  this.dropChoice(id);
@@ -197472,7 +197763,7 @@ function asCodeBlock(text) {
197472
197763
  ${safe}
197473
197764
  ${fence}`;
197474
197765
  }
197475
- var MAX_REPLY_CHARS = 20000, MAX_PENDING_APPROVALS_PER_SESSION = 8, MAX_PENDING_CHOICES_PER_SESSION = 8, MAX_REMEMBERED_IDS_PER_SESSION = 512, MAX_PROJECT_NAME_CHARS = 128, CONTROL_CHARS2, JSON_HEADERS2, MAX_FINISHED_PROMPTS = 128, MAX_SETTLED_TEXT_CHARS = 3600, MAX_BACKTICK_RUN = 8;
197766
+ var MAX_REPLY_CHARS = 20000, MAX_PENDING_APPROVALS_PER_SESSION = 8, MAX_PENDING_CHOICES_PER_SESSION = 8, MAX_REMEMBERED_IDS_PER_SESSION = 512, MAX_PROJECT_NAME_CHARS = 128, CONTROL_CHARS2, JSON_HEADERS2, MAX_FINISHED_PROMPTS = 128, MAX_DECIDED_APPROVALS = 256, MAX_SETTLED_TEXT_CHARS = 3600, MAX_BACKTICK_RUN = 8;
197476
197767
  var init_http_surface = __esm(() => {
197477
197768
  init_service2();
197478
197769
  init_hub();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.3.66",
3
+ "version": "0.3.67",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {