@threadbase-sh/streamer 1.69.6 → 1.70.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -116376,7 +116376,7 @@ var require_query = __commonJS({
116376
116376
  if (typeof this.text !== "string" && typeof this.name !== "string") {
116377
116377
  return new Error("A query must have either text or a name. Supplying neither is unsupported.");
116378
116378
  }
116379
- const previous = connection.parsedStatements[this.name];
116379
+ const previous = connection.parsedStatements[this.name] || connection.submittedNamedStatements[this.name];
116380
116380
  if (this.text && previous && this.text !== previous) {
116381
116381
  return new Error(`Prepared statements must be unique - '${this.name}' was used for a different statement`);
116382
116382
  }
@@ -116396,7 +116396,7 @@ var require_query = __commonJS({
116396
116396
  return null;
116397
116397
  }
116398
116398
  hasBeenParsed(connection) {
116399
- return this.name && connection.parsedStatements[this.name];
116399
+ return this.name && (connection.parsedStatements[this.name] || connection.submittedNamedStatements[this.name]);
116400
116400
  }
116401
116401
  handlePortalSuspended(connection) {
116402
116402
  this._getRows(connection, this.rows);
@@ -116420,6 +116420,9 @@ var require_query = __commonJS({
116420
116420
  name: this.name,
116421
116421
  types: this.types
116422
116422
  });
116423
+ if (this.name) {
116424
+ connection.submittedNamedStatements[this.name] = this.text;
116425
+ }
116423
116426
  }
116424
116427
  try {
116425
116428
  connection.bind({
@@ -117208,7 +117211,7 @@ var require_parser3 = __commonJS({
117208
117211
  const parameterCount = reader.int16();
117209
117212
  const message = new messages_1.ParameterDescriptionMessage(LATEINIT_LENGTH, parameterCount);
117210
117213
  for (let i = 0; i < parameterCount; i++) {
117211
- message.dataTypeIDs[i] = reader.int32();
117214
+ message.dataTypeIDs[i] = reader.uint32();
117212
117215
  }
117213
117216
  return message;
117214
117217
  };
@@ -117430,6 +117433,7 @@ var require_connection2 = __commonJS({
117430
117433
  this._keepAlive = config2.keepAlive;
117431
117434
  this._keepAliveInitialDelayMillis = config2.keepAliveInitialDelayMillis;
117432
117435
  this.parsedStatements = {};
117436
+ this.submittedNamedStatements = {};
117433
117437
  this.ssl = config2.ssl || false;
117434
117438
  this.sslNegotiation = config2.sslNegotiation || "postgres";
117435
117439
  this._ending = false;
@@ -117987,6 +117991,8 @@ var require_client3 = __commonJS({
117987
117991
  encoding: this.connectionParameters.client_encoding || "utf8"
117988
117992
  });
117989
117993
  this._queryQueue = [];
117994
+ this._sentQueryQueue = [];
117995
+ this.pipeline = Boolean(c.pipeline);
117990
117996
  this.binary = c.binary || defaults2.binary;
117991
117997
  this.processID = null;
117992
117998
  this.secretKey = null;
@@ -118021,6 +118027,8 @@ var require_client3 = __commonJS({
118021
118027
  enqueueError(activeQuery);
118022
118028
  this._activeQuery = null;
118023
118029
  }
118030
+ this._sentQueryQueue.forEach(enqueueError);
118031
+ this._sentQueryQueue.length = 0;
118024
118032
  this._queryQueue.forEach(enqueueError);
118025
118033
  this._queryQueue.length = 0;
118026
118034
  }
@@ -118263,6 +118271,9 @@ var require_client3 = __commonJS({
118263
118271
  return;
118264
118272
  }
118265
118273
  this._activeQuery = null;
118274
+ if (activeQuery.name) {
118275
+ delete this.connection.submittedNamedStatements[activeQuery.name];
118276
+ }
118266
118277
  activeQuery.handleError(msg, this.connection);
118267
118278
  }
118268
118279
  _handleRowDescription(msg) {
@@ -118319,6 +118330,7 @@ var require_client3 = __commonJS({
118319
118330
  }
118320
118331
  if (activeQuery.name) {
118321
118332
  this.connection.parsedStatements[activeQuery.name] = activeQuery.text;
118333
+ delete this.connection.submittedNamedStatements[activeQuery.name];
118322
118334
  }
118323
118335
  }
118324
118336
  _handleCopyInResponse(msg) {
@@ -118385,6 +118397,9 @@ var require_client3 = __commonJS({
118385
118397
  });
118386
118398
  } else if (client._queryQueue.indexOf(query) !== -1) {
118387
118399
  client._queryQueue.splice(client._queryQueue.indexOf(query), 1);
118400
+ } else if (client._sentQueryQueue.indexOf(query) !== -1) {
118401
+ query.callback = () => {
118402
+ };
118388
118403
  }
118389
118404
  }
118390
118405
  setTypeParser(oid, format, parseFn) {
@@ -118403,6 +118418,10 @@ var require_client3 = __commonJS({
118403
118418
  return utils.escapeLiteral(str);
118404
118419
  }
118405
118420
  _pulseQueryQueue() {
118421
+ if (this.pipeline) {
118422
+ this._pulsePipelinedQueryQueue();
118423
+ return;
118424
+ }
118406
118425
  if (this.readyForQuery === true) {
118407
118426
  this._activeQuery = this._queryQueue.shift();
118408
118427
  const activeQuery = this._getActiveQuery();
@@ -118423,6 +118442,30 @@ var require_client3 = __commonJS({
118423
118442
  }
118424
118443
  }
118425
118444
  }
118445
+ _pulsePipelinedQueryQueue() {
118446
+ if (!this._connected || !this._queryable) {
118447
+ return;
118448
+ }
118449
+ while (this._queryQueue.length > 0) {
118450
+ const query = this._queryQueue.shift();
118451
+ this.hasExecuted = true;
118452
+ const queryError = query.submit(this.connection);
118453
+ if (queryError) {
118454
+ process.nextTick(() => {
118455
+ query.handleError(queryError, this.connection);
118456
+ });
118457
+ continue;
118458
+ }
118459
+ this._sentQueryQueue.push(query);
118460
+ }
118461
+ if (this.readyForQuery && !this._activeQuery && this._sentQueryQueue.length > 0) {
118462
+ this._activeQuery = this._sentQueryQueue.shift();
118463
+ this.readyForQuery = false;
118464
+ }
118465
+ if (!this._activeQuery && this._sentQueryQueue.length === 0 && this._queryQueue.length === 0 && this.hasExecuted) {
118466
+ this.emit("drain");
118467
+ }
118468
+ }
118426
118469
  query(config2, values, callback) {
118427
118470
  let query;
118428
118471
  let result;
@@ -118466,6 +118509,9 @@ var require_client3 = __commonJS({
118466
118509
  const index = this._queryQueue.indexOf(query);
118467
118510
  if (index > -1) {
118468
118511
  this._queryQueue.splice(index, 1);
118512
+ } else if (this.pipeline) {
118513
+ this.connection.stream.destroy();
118514
+ return;
118469
118515
  }
118470
118516
  this._pulseQueryQueue();
118471
118517
  }, readTimeout);
@@ -118492,7 +118538,7 @@ var require_client3 = __commonJS({
118492
118538
  });
118493
118539
  return result;
118494
118540
  }
118495
- if (this._queryQueue.length > 0) {
118541
+ if (this._queryQueue.length > 0 && !this.pipeline) {
118496
118542
  queryQueueLengthDeprecationNotice();
118497
118543
  }
118498
118544
  this._queryQueue.push(query);
@@ -118518,7 +118564,11 @@ var require_client3 = __commonJS({
118518
118564
  return this._Promise.resolve();
118519
118565
  }
118520
118566
  }
118521
- if (this._getActiveQuery() || !this._queryable) {
118567
+ if (!this._queryable) {
118568
+ this.connection.stream.destroy();
118569
+ } else if (this.pipeline && (this._getActiveQuery() || this._sentQueryQueue.length > 0 || this._queryQueue.length > 0)) {
118570
+ this.once("drain", () => this.connection.end());
118571
+ } else if (this._getActiveQuery()) {
118522
118572
  this.connection.stream.destroy();
118523
118573
  } else {
118524
118574
  this.connection.end();
@@ -119008,7 +119058,7 @@ var require_query2 = __commonJS({
119008
119058
  sourceFunction: "routine"
119009
119059
  };
119010
119060
  NativeQuery.prototype.handleError = function(err) {
119011
- const fields = this.native.pq.resultErrorFields();
119061
+ const fields = this.native && this.native.pq.resultErrorFields();
119012
119062
  if (fields) {
119013
119063
  for (const key in fields) {
119014
119064
  const normalizedFieldName = errorFieldMap[key] || key;
@@ -119142,6 +119192,8 @@ var require_client4 = __commonJS({
119142
119192
  this._connecting = false;
119143
119193
  this._connected = false;
119144
119194
  this._queryable = true;
119195
+ this.pipeline = Boolean(config2.pipeline);
119196
+ this._pipelineInFlight = false;
119145
119197
  const cp = this.connectionParameters = new ConnectionParameters(config2);
119146
119198
  if (config2.nativeConnectionString) cp.nativeConnectionString = config2.nativeConnectionString;
119147
119199
  this.user = cp.user;
@@ -119285,7 +119337,7 @@ var require_client4 = __commonJS({
119285
119337
  });
119286
119338
  return result;
119287
119339
  }
119288
- if (this._queryQueue.length > 0) {
119340
+ if (this._queryQueue.length > 0 && !this.pipeline) {
119289
119341
  queryQueueLengthDeprecationNotice();
119290
119342
  }
119291
119343
  this._queryQueue.push(query);
@@ -119307,14 +119359,21 @@ var require_client4 = __commonJS({
119307
119359
  cb2 = (err) => err ? reject(err) : resolve4();
119308
119360
  });
119309
119361
  }
119310
- this.native.end(function() {
119311
- self2._connected = false;
119312
- self2._errorAllQueries(new Error("Connection terminated"));
119313
- process.nextTick(() => {
119314
- self2.emit("end");
119315
- if (cb2) cb2();
119362
+ const doEnd = function() {
119363
+ self2.native.end(function() {
119364
+ self2._connected = false;
119365
+ self2._errorAllQueries(new Error("Connection terminated"));
119366
+ process.nextTick(() => {
119367
+ self2.emit("end");
119368
+ if (cb2) cb2();
119369
+ });
119316
119370
  });
119317
- });
119371
+ };
119372
+ if (this.pipeline && (this._pipelineInFlight || this._queryQueue.length > 0)) {
119373
+ this.once("drain", doEnd);
119374
+ } else {
119375
+ doEnd();
119376
+ }
119318
119377
  return result;
119319
119378
  };
119320
119379
  Client2.prototype._hasActiveQuery = function() {
@@ -119324,6 +119383,9 @@ var require_client4 = __commonJS({
119324
119383
  if (!this._connected) {
119325
119384
  return;
119326
119385
  }
119386
+ if (this.pipeline && !initialConnection) {
119387
+ return this._pulsePipelinedQueryQueue();
119388
+ }
119327
119389
  if (this._hasActiveQuery()) {
119328
119390
  return;
119329
119391
  }
@@ -119341,6 +119403,69 @@ var require_client4 = __commonJS({
119341
119403
  self2._pulseQueryQueue();
119342
119404
  });
119343
119405
  };
119406
+ Client2.prototype._pulsePipelinedQueryQueue = function() {
119407
+ if (!this._connected || this._pipelineInFlight) {
119408
+ return;
119409
+ }
119410
+ if (this._queryQueue.length === 0) {
119411
+ if (this.hasExecuted) {
119412
+ this.emit("drain");
119413
+ }
119414
+ return;
119415
+ }
119416
+ this._pipelineInFlight = true;
119417
+ const self2 = this;
119418
+ const queries = [];
119419
+ const nativeQueries = [];
119420
+ const utils = require_utils5();
119421
+ while (this._queryQueue.length > 0) {
119422
+ const query = this._queryQueue.shift();
119423
+ this.hasExecuted = true;
119424
+ nativeQueries.push(query);
119425
+ const values = query.values ? query.values.map(utils.prepareValue) : null;
119426
+ const pipelineEntry = { text: query.text, name: query.name };
119427
+ if (values) {
119428
+ pipelineEntry.values = values;
119429
+ }
119430
+ if (query.name && this.namedQueries[query.name]) {
119431
+ pipelineEntry._alreadyPrepared = true;
119432
+ }
119433
+ queries.push(pipelineEntry);
119434
+ }
119435
+ this.native.pipeline(queries, function(err, results) {
119436
+ self2._pipelineInFlight = false;
119437
+ if (err) {
119438
+ for (let i = 0; i < nativeQueries.length; i++) {
119439
+ const q2 = nativeQueries[i];
119440
+ q2.native = self2.native;
119441
+ q2.handleError(err);
119442
+ }
119443
+ self2._pulsePipelinedQueryQueue();
119444
+ return;
119445
+ }
119446
+ for (let i = 0; i < nativeQueries.length; i++) {
119447
+ const q2 = nativeQueries[i];
119448
+ const r = results[i];
119449
+ q2.native = self2.native;
119450
+ if (r.err) {
119451
+ q2.handleError(r.err);
119452
+ } else {
119453
+ if (q2.name) {
119454
+ self2.namedQueries[q2.name] = q2.text;
119455
+ }
119456
+ q2.state = "end";
119457
+ q2.emit("end", r.result);
119458
+ if (q2.callback) {
119459
+ q2.callback(null, r.result);
119460
+ }
119461
+ }
119462
+ setImmediate(function() {
119463
+ q2.emit("_done");
119464
+ });
119465
+ }
119466
+ self2._pulsePipelinedQueryQueue();
119467
+ });
119468
+ };
119344
119469
  Client2.prototype.cancel = function(query) {
119345
119470
  if (this._activeQuery === query) {
119346
119471
  this.native.cancel(function() {
@@ -120610,7 +120735,7 @@ var PTY_HOST_PROTOCOL_VERSION, SESSION_DATE_FIELDS, LineDecoder;
120610
120735
  var init_protocol = __esm({
120611
120736
  "src/pty-host/protocol.ts"() {
120612
120737
  "use strict";
120613
- PTY_HOST_PROTOCOL_VERSION = 3;
120738
+ PTY_HOST_PROTOCOL_VERSION = 4;
120614
120739
  SESSION_DATE_FIELDS = [
120615
120740
  "startedAt",
120616
120741
  "completedAt",
@@ -120690,8 +120815,12 @@ var init_remote_session_runner = __esm({
120690
120815
  }
120691
120816
  throw new PtyHostProtocolMismatchError(status.protocolVersion, PTY_HOST_PROTOCOL_VERSION);
120692
120817
  }
120693
- await runner.request({ type: "subscribe" });
120818
+ const subscribed = await runner.request({ type: "subscribe" });
120694
120819
  runner.refreshMirror(status);
120820
+ runner.restorePromptSnapshots({
120821
+ ...status,
120822
+ promptSnapshots: subscribed.promptSnapshots ?? status.promptSnapshots
120823
+ });
120695
120824
  return runner;
120696
120825
  }
120697
120826
  constructor(transport, options) {
@@ -120784,6 +120913,19 @@ var init_remote_session_runner = __esm({
120784
120913
  this.pids.set(session.id, entry.pid);
120785
120914
  }
120786
120915
  }
120916
+ restorePromptSnapshots(status) {
120917
+ for (const snapshot of status.promptSnapshots ?? []) {
120918
+ if (snapshot.kind === "permission") {
120919
+ this.options.onPermissionChange?.(snapshot.sessionId, snapshot.gate, snapshot.occurrenceId);
120920
+ } else {
120921
+ this.options.onLiveQuestion?.(
120922
+ snapshot.sessionId,
120923
+ snapshot.questions,
120924
+ snapshot.occurrenceId
120925
+ );
120926
+ }
120927
+ }
120928
+ }
120787
120929
  async heartbeat(state, timeoutMs = HOST_HEARTBEAT_REQUEST_TIMEOUT_MS) {
120788
120930
  await this.request({ type: "heartbeat", ...state }, timeoutMs);
120789
120931
  }
@@ -120838,13 +120980,13 @@ var init_remote_session_runner = __esm({
120838
120980
  break;
120839
120981
  }
120840
120982
  case "permission-change":
120841
- this.options.onPermissionChange?.(event.sessionId, event.gate);
120983
+ this.options.onPermissionChange?.(event.sessionId, event.gate, event.occurrenceId);
120842
120984
  break;
120843
120985
  case "phase-change":
120844
120986
  this.options.onPhaseChange?.(event.sessionId, event.phase);
120845
120987
  break;
120846
120988
  case "live-question":
120847
- this.options.onLiveQuestion?.(event.sessionId, event.questions);
120989
+ this.options.onLiveQuestion?.(event.sessionId, event.questions, event.occurrenceId);
120848
120990
  break;
120849
120991
  case "live-question-gone":
120850
120992
  this.options.onLiveQuestionGone?.(event.sessionId);
@@ -128466,12 +128608,15 @@ __export(host_exports, {
128466
128608
  HOST_ORPHAN_SWEEP_MS: () => HOST_ORPHAN_SWEEP_MS,
128467
128609
  SessionHost: () => SessionHost
128468
128610
  });
128469
- var HOST_IDLE_SWEEP_MS, HOST_IDLE_AFTER_MS, HOST_HEARTBEAT_TIMEOUT_MS, HOST_ORPHAN_SWEEP_MS, SessionHost;
128611
+ var import_node_crypto8, HOST_IDLE_SWEEP_MS, HOST_IDLE_AFTER_MS, HOST_HEARTBEAT_TIMEOUT_MS, HOST_ORPHAN_SWEEP_MS, SessionHost;
128470
128612
  var init_host = __esm({
128471
128613
  "src/pty-host/host.ts"() {
128472
128614
  "use strict";
128615
+ import_node_crypto8 = require("crypto");
128473
128616
  init_live_session_manager();
128474
128617
  init_logger();
128618
+ init_detectPermissionGate();
128619
+ init_detectQuestionFromScreen();
128475
128620
  init_protocol();
128476
128621
  HOST_IDLE_SWEEP_MS = 5 * 60 * 1e3;
128477
128622
  HOST_IDLE_AFTER_MS = 6 * 60 * 60 * 1e3;
@@ -128493,6 +128638,7 @@ var init_host = __esm({
128493
128638
  orphanTimer = null;
128494
128639
  onOrphaned;
128495
128640
  orphaned = false;
128641
+ promptSnapshots = /* @__PURE__ */ new Map();
128496
128642
  constructor(options = {}) {
128497
128643
  this.log = options.logger ?? getLogger("pty-host");
128498
128644
  this.idleAfterMs = options.idleAfterMs ?? HOST_IDLE_AFTER_MS;
@@ -128505,12 +128651,55 @@ var init_host = __esm({
128505
128651
  this.lastAgentChunkAt.set(sessionId, Date.now());
128506
128652
  this.emit({ type: "event", event: "output", sessionId, data });
128507
128653
  },
128508
- onStatusChange: (session) => this.emit({ type: "event", event: "status-change", session }),
128654
+ onStatusChange: (session) => {
128655
+ if (session.status === "idle") this.promptSnapshots.delete(session.id);
128656
+ this.emit({ type: "event", event: "status-change", session });
128657
+ },
128509
128658
  onReady: (session) => this.emit({ type: "event", event: "ready", session }),
128510
- onPermissionChange: (sessionId, gate) => this.emit({ type: "event", event: "permission-change", sessionId, gate }),
128659
+ onPermissionChange: (sessionId, gate) => {
128660
+ const prior = this.promptSnapshots.get(sessionId);
128661
+ if (gate === null) {
128662
+ this.promptSnapshots.delete(sessionId);
128663
+ this.emit({
128664
+ type: "event",
128665
+ event: "permission-change",
128666
+ sessionId,
128667
+ gate,
128668
+ ...prior ? { occurrenceId: prior.occurrenceId } : {}
128669
+ });
128670
+ return;
128671
+ }
128672
+ const occurrenceId = prior?.kind === "permission" && permissionGateKey(prior.gate) === permissionGateKey(gate) ? prior.occurrenceId : (0, import_node_crypto8.randomUUID)();
128673
+ this.promptSnapshots.set(sessionId, {
128674
+ kind: "permission",
128675
+ sessionId,
128676
+ occurrenceId,
128677
+ gate
128678
+ });
128679
+ this.emit({
128680
+ type: "event",
128681
+ event: "permission-change",
128682
+ sessionId,
128683
+ gate,
128684
+ occurrenceId
128685
+ });
128686
+ },
128511
128687
  onPhaseChange: (sessionId, phase) => this.emit({ type: "event", event: "phase-change", sessionId, phase }),
128512
- onLiveQuestion: (sessionId, questions) => this.emit({ type: "event", event: "live-question", sessionId, questions }),
128513
- onLiveQuestionGone: (sessionId) => this.emit({ type: "event", event: "live-question-gone", sessionId }),
128688
+ onLiveQuestion: (sessionId, questions) => {
128689
+ const prior = this.promptSnapshots.get(sessionId);
128690
+ const occurrenceId = prior?.kind === "question" && questionContentKey(prior.questions) === questionContentKey(questions) ? prior.occurrenceId : (0, import_node_crypto8.randomUUID)();
128691
+ this.promptSnapshots.set(sessionId, {
128692
+ kind: "question",
128693
+ sessionId,
128694
+ occurrenceId,
128695
+ questions
128696
+ });
128697
+ this.emit({ type: "event", event: "live-question", sessionId, questions, occurrenceId });
128698
+ },
128699
+ onLiveQuestionGone: (sessionId) => {
128700
+ this.promptSnapshots.delete(sessionId);
128701
+ this.emit({ type: "event", event: "live-question-gone", sessionId });
128702
+ },
128514
128703
  onUserMessage: (sessionId, text, ts2) => this.emit({ type: "event", event: "user-message", sessionId, text, ts: ts2 })
128515
128704
  });
128516
128705
  this.idleTimer = setInterval(() => this.reapIdle(), options.idleSweepMs ?? HOST_IDLE_SWEEP_MS);
@@ -128579,11 +128768,12 @@ var init_host = __esm({
128579
128768
  event: "pty_host.streamer_subscribed",
128580
128769
  subscribers: this.subscribers.size
128581
128770
  });
128582
- return {};
128771
+ return { promptSnapshots: [...this.promptSnapshots.values()] };
128583
128772
  case "status":
128584
128773
  return {
128585
128774
  protocolVersion: PTY_HOST_PROTOCOL_VERSION,
128586
- sessions: this.runner.listSessions().map((session) => this.toHostSession(session))
128775
+ sessions: this.runner.listSessions().map((session) => this.toHostSession(session)),
128776
+ promptSnapshots: [...this.promptSnapshots.values()]
128587
128777
  };
128588
128778
  case "heartbeat":
128589
128779
  this.heartbeatLeases.set(transport, Date.now());
@@ -128641,6 +128831,7 @@ var init_host = __esm({
128641
128831
  }
128642
128832
  forget(sessionId) {
128643
128833
  this.lastAgentChunkAt.delete(sessionId);
128834
+ this.promptSnapshots.delete(sessionId);
128644
128835
  }
128645
128836
  /**
128646
128837
  * Release PTYs whose agent has been silent past the threshold.
@@ -132718,13 +132909,13 @@ var checkOptionalParameter = (path2) => {
132718
132909
  if (segment !== "" && !/\:/.test(segment)) {
132719
132910
  basePath += "/" + segment;
132720
132911
  } else if (/\:/.test(segment)) {
132721
- if (/\?/.test(segment)) {
132912
+ if (segment.charCodeAt(segment.length - 1) === 63) {
132722
132913
  if (results.length === 0 && basePath === "") {
132723
132914
  results.push("/");
132724
132915
  } else {
132725
132916
  results.push(basePath);
132726
132917
  }
132727
- const optionalSegment = segment.replace("?", "");
132918
+ const optionalSegment = segment.slice(0, -1);
132728
132919
  basePath += "/" + optionalSegment;
132729
132920
  results.push(basePath);
132730
132921
  } else {
@@ -132734,18 +132925,16 @@ var checkOptionalParameter = (path2) => {
132734
132925
  });
132735
132926
  return results.filter((v2, i, a) => a.indexOf(v2) === i);
132736
132927
  };
132928
+ var tryDecodeURIComponent = (str) => str.indexOf("%") !== -1 ? tryDecode(str, decodeURIComponent_) : str;
132737
132929
  var _decodeURI = (value) => {
132738
- if (!/[%+]/.test(value)) {
132739
- return value;
132740
- }
132741
132930
  if (value.indexOf("+") !== -1) {
132742
132931
  value = value.replace(/\+/g, " ");
132743
132932
  }
132744
- return value.indexOf("%") !== -1 ? tryDecode(value, decodeURIComponent_) : value;
132933
+ return tryDecodeURIComponent(value);
132745
132934
  };
132746
132935
  var _getQueryParam = (url2, key, multiple) => {
132747
132936
  let encoded;
132748
- if (!multiple && key && !/[%+]/.test(key)) {
132937
+ if (!multiple && key && key.indexOf("%") === -1 && key.indexOf("+") === -1) {
132749
132938
  let keyIndex2 = url2.indexOf("?", 8);
132750
132939
  if (keyIndex2 === -1) {
132751
132940
  return void 0;
@@ -132817,7 +133006,6 @@ var getQueryParams = (url2, key) => {
132817
133006
  var decodeURIComponent_ = decodeURIComponent;
132818
133007
 
132819
133008
  // node_modules/hono/dist/request.js
132820
- var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
132821
133009
  var HonoRequest = class {
132822
133010
  /**
132823
133011
  * `.raw` can get the raw Request object.
@@ -132856,7 +133044,6 @@ var HonoRequest = class {
132856
133044
  this.raw = request;
132857
133045
  this.path = path2;
132858
133046
  this.#matchResult = matchResult;
132859
- this.#validatedData = {};
132860
133047
  }
132861
133048
  param(key) {
132862
133049
  return key ? this.#getDecodedParam(key) : this.#getAllDecodedParams();
@@ -132864,7 +133051,7 @@ var HonoRequest = class {
132864
133051
  #getDecodedParam(key) {
132865
133052
  const paramKey = this.#matchResult[0][this.routeIndex][1][key];
132866
133053
  const param = this.#getParamValue(paramKey);
132867
- return param && /\%/.test(param) ? tryDecodeURIComponent(param) : param;
133054
+ return param && tryDecodeURIComponent(param);
132868
133055
  }
132869
133056
  #getAllDecodedParams() {
132870
133057
  const decoded = {};
@@ -132872,7 +133059,7 @@ var HonoRequest = class {
132872
133059
  for (const key of keys) {
132873
133060
  const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
132874
133061
  if (value !== void 0) {
132875
- decoded[key] = /\%/.test(value) ? tryDecodeURIComponent(value) : value;
133062
+ decoded[key] = tryDecodeURIComponent(value);
132876
133063
  }
132877
133064
  }
132878
133065
  return decoded;
@@ -132905,8 +133092,7 @@ var HonoRequest = class {
132905
133092
  if (cachedBody) {
132906
133093
  return cachedBody;
132907
133094
  }
132908
- const anyCachedKey = Object.keys(bodyCache)[0];
132909
- if (anyCachedKey) {
133095
+ for (const anyCachedKey in bodyCache) {
132910
133096
  return bodyCache[anyCachedKey].then((body) => {
132911
133097
  if (anyCachedKey === "json") {
132912
133098
  body = JSON.stringify(body);
@@ -133009,10 +133195,11 @@ var HonoRequest = class {
133009
133195
  * @param data - The validated data to add.
133010
133196
  */
133011
133197
  addValidatedData(target, data) {
133012
- this.#validatedData[target] = data;
133198
+ ;
133199
+ (this.#validatedData ??= {})[target] = data;
133013
133200
  }
133014
133201
  valid(target) {
133015
- return this.#validatedData[target];
133202
+ return this.#validatedData?.[target];
133016
133203
  }
133017
133204
  /**
133018
133205
  * `.url()` can get the request url strings.
@@ -133343,6 +133530,10 @@ var Context = class {
133343
133530
  * c.header('X-Message', 'Hello!')
133344
133531
  * c.header('Content-Type', 'text/plain')
133345
133532
  *
133533
+ * // Append multiple headers using the append option (e.g. Vary)
133534
+ * c.header('Vary', 'Accept-Encoding', { append: true })
133535
+ * c.header('Vary', 'User-Agent', { append: true })
133536
+ *
133346
133537
  * return c.body('Thank you for coming')
133347
133538
  * })
133348
133539
  * ```
@@ -133414,11 +133605,11 @@ var Context = class {
133414
133605
  return Object.fromEntries(this.#var);
133415
133606
  }
133416
133607
  #newResponse(data, arg, headers) {
133417
- const responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders ?? new Headers();
133418
- if (typeof arg === "object" && "headers" in arg) {
133419
- const argHeaders = arg.headers instanceof Headers ? arg.headers : new Headers(arg.headers);
133420
- for (const [key, value] of argHeaders) {
133421
- if (key.toLowerCase() === "set-cookie") {
133608
+ let responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders;
133609
+ if (typeof arg === "object" && arg.headers) {
133610
+ responseHeaders ??= new Headers();
133611
+ for (const [key, value] of new Headers(arg.headers)) {
133612
+ if (key === "set-cookie") {
133422
133613
  responseHeaders.append(key, value);
133423
133614
  } else {
133424
133615
  responseHeaders.set(key, value);
@@ -133426,19 +133617,34 @@ var Context = class {
133426
133617
  }
133427
133618
  }
133428
133619
  if (headers) {
133429
- for (const [k2, v2] of Object.entries(headers)) {
133430
- if (typeof v2 === "string") {
133431
- responseHeaders.set(k2, v2);
133432
- } else {
133433
- responseHeaders.delete(k2);
133434
- for (const v22 of v2) {
133435
- responseHeaders.append(k2, v22);
133620
+ if (!responseHeaders) {
133621
+ let count = 0;
133622
+ for (const k2 in headers) {
133623
+ if (++count > 1 || typeof headers[k2] !== "string") {
133624
+ responseHeaders = new Headers();
133625
+ break;
133626
+ }
133627
+ }
133628
+ }
133629
+ if (responseHeaders) {
133630
+ for (const k2 in headers) {
133631
+ const v2 = headers[k2];
133632
+ if (typeof v2 === "string") {
133633
+ responseHeaders.set(k2, v2);
133634
+ } else {
133635
+ responseHeaders.delete(k2);
133636
+ for (const v22 of v2) {
133637
+ responseHeaders.append(k2, v22);
133638
+ }
133436
133639
  }
133437
133640
  }
133438
133641
  }
133439
133642
  }
133440
133643
  const status = typeof arg === "number" ? arg : arg?.status ?? this.#status;
133441
- return createResponseInstance(data, { status, headers: responseHeaders });
133644
+ return createResponseInstance(data, {
133645
+ status,
133646
+ headers: responseHeaders ?? headers
133647
+ });
133442
133648
  }
133443
133649
  newResponse = (...args) => this.#newResponse(...args);
133444
133650
  /**
@@ -133551,7 +133757,7 @@ var Context = class {
133551
133757
  // node_modules/hono/dist/router.js
133552
133758
  var METHOD_NAME_ALL = "ALL";
133553
133759
  var METHOD_NAME_ALL_LOWERCASE = "all";
133554
- var METHODS = ["get", "post", "put", "delete", "options", "patch"];
133760
+ var METHODS = ["get", "post", "put", "delete", "options", "patch", "query"];
133555
133761
  var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is already built.";
133556
133762
  var UnsupportedPathError = class extends Error {
133557
133763
  };
@@ -133578,6 +133784,7 @@ var Hono = class _Hono {
133578
133784
  delete;
133579
133785
  options;
133580
133786
  patch;
133787
+ query;
133581
133788
  all;
133582
133789
  on;
133583
133790
  use;
@@ -133877,8 +134084,8 @@ var Hono = class _Hono {
133877
134084
  * @see {@link https://hono.dev/docs/api/hono#fetch}
133878
134085
  *
133879
134086
  * @param {Request} request - request Object of request
133880
- * @param {Env} Env - env Object
133881
- * @param {ExecutionContext} - context of execution
134087
+ * @param {Env} env - env Object
134088
+ * @param {ExecutionContext} executionCtx - context of execution
133882
134089
  * @returns {Response | Promise<Response>} response of request
133883
134090
  *
133884
134091
  */
@@ -133970,7 +134177,7 @@ function compareKey(a, b2) {
133970
134177
  return 1;
133971
134178
  }
133972
134179
  if (a === ONLY_WILDCARD_REG_EXP_STR || a === TAIL_WILDCARD_REG_EXP_STR) {
133973
- return 1;
134180
+ return b2 === TAIL_WILDCARD_REG_EXP_STR ? -1 : 1;
133974
134181
  } else if (b2 === ONLY_WILDCARD_REG_EXP_STR || b2 === TAIL_WILDCARD_REG_EXP_STR) {
133975
134182
  return -1;
133976
134183
  }
@@ -133982,76 +134189,75 @@ function compareKey(a, b2) {
133982
134189
  return a.length === b2.length ? a < b2 ? -1 : 1 : b2.length - a.length;
133983
134190
  }
133984
134191
  var Node = class _Node {
134192
+ // handler index of a dynamic path, or -1 for a static path terminal
133985
134193
  #index;
133986
134194
  #varIndex;
133987
134195
  #children = /* @__PURE__ */ Object.create(null);
133988
- insert(tokens, index, paramMap, context, pathErrorCheckOnly) {
133989
- if (tokens.length === 0) {
133990
- if (this.#index !== void 0) {
133991
- throw PATH_ERROR;
133992
- }
133993
- if (pathErrorCheckOnly) {
133994
- return;
133995
- }
133996
- this.#index = index;
133997
- return;
133998
- }
133999
- const [token, ...restTokens] = tokens;
134000
- const pattern = token === "*" ? restTokens.length === 0 ? ["", "", ONLY_WILDCARD_REG_EXP_STR] : ["", "", LABEL_REG_EXP_STR] : token === "/*" ? ["", "", TAIL_WILDCARD_REG_EXP_STR] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
134001
- let node;
134002
- if (pattern) {
134003
- const name = pattern[1];
134004
- let regexpStr = pattern[2] || LABEL_REG_EXP_STR;
134005
- if (name && pattern[2]) {
134006
- if (regexpStr === ".*") {
134007
- throw PATH_ERROR;
134008
- }
134009
- regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:");
134010
- if (/\((?!\?:)/.test(regexpStr)) {
134011
- throw PATH_ERROR;
134012
- }
134013
- }
134014
- node = this.#children[regexpStr];
134015
- if (!node) {
134016
- if (Object.keys(this.#children).some(
134017
- (k2) => k2 !== ONLY_WILDCARD_REG_EXP_STR && k2 !== TAIL_WILDCARD_REG_EXP_STR
134018
- )) {
134019
- throw PATH_ERROR;
134020
- }
134021
- if (pathErrorCheckOnly) {
134022
- return;
134196
+ insert(tokens, index, paramMap, context, isStatic) {
134197
+ let node = this;
134198
+ for (let i = 0, len = tokens.length; i < len; i++) {
134199
+ const token = tokens[i];
134200
+ const pattern = token.length === 1 ? token === "*" ? i === len - 1 ? ["", "", ONLY_WILDCARD_REG_EXP_STR] : ["", "", LABEL_REG_EXP_STR] : null : token === "/*" ? ["", "", TAIL_WILDCARD_REG_EXP_STR] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
134201
+ let nextNode;
134202
+ if (pattern) {
134203
+ const name = pattern[1];
134204
+ let regexpStr = pattern[2] || LABEL_REG_EXP_STR;
134205
+ if (name && pattern[2]) {
134206
+ if (regexpStr === ".*") {
134207
+ throw PATH_ERROR;
134208
+ }
134209
+ regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:");
134210
+ if (/\((?!\?:)/.test(regexpStr)) {
134211
+ throw PATH_ERROR;
134212
+ }
134213
+ if (regexpStr.length === 1 && regExpMetaChars.has(regexpStr)) {
134214
+ throw PATH_ERROR;
134215
+ }
134216
+ }
134217
+ nextNode = node.#children[regexpStr];
134218
+ if (!nextNode) {
134219
+ if (regexpStr !== ONLY_WILDCARD_REG_EXP_STR && regexpStr !== TAIL_WILDCARD_REG_EXP_STR) {
134220
+ for (const k2 in node.#children) {
134221
+ if (
134222
+ // a single-char pattern coexists with single-char literals as a literal does
134223
+ (regexpStr.length > 1 || k2.length > 1) && k2 !== ONLY_WILDCARD_REG_EXP_STR && k2 !== TAIL_WILDCARD_REG_EXP_STR
134224
+ ) {
134225
+ throw PATH_ERROR;
134226
+ }
134227
+ }
134228
+ }
134229
+ nextNode = node.#children[regexpStr] = new _Node();
134023
134230
  }
134024
- node = this.#children[regexpStr] = new _Node();
134025
134231
  if (name !== "") {
134026
- node.#varIndex = context.varIndex++;
134232
+ nextNode.#varIndex ??= context.varIndex++;
134233
+ paramMap.push([name, nextNode.#varIndex]);
134027
134234
  }
134028
- }
134029
- if (!pathErrorCheckOnly && name !== "") {
134030
- paramMap.push([name, node.#varIndex]);
134031
- }
134032
- } else {
134033
- node = this.#children[token];
134034
- if (!node) {
134035
- if (Object.keys(this.#children).some(
134036
- (k2) => k2.length > 1 && k2 !== ONLY_WILDCARD_REG_EXP_STR && k2 !== TAIL_WILDCARD_REG_EXP_STR
134037
- )) {
134038
- throw PATH_ERROR;
134039
- }
134040
- if (pathErrorCheckOnly) {
134041
- return;
134235
+ } else {
134236
+ nextNode = node.#children[token];
134237
+ if (!nextNode) {
134238
+ for (const k2 in node.#children) {
134239
+ if (k2.length > 1 && k2 !== ONLY_WILDCARD_REG_EXP_STR && k2 !== TAIL_WILDCARD_REG_EXP_STR) {
134240
+ throw PATH_ERROR;
134241
+ }
134242
+ }
134243
+ nextNode = node.#children[token] = new _Node();
134042
134244
  }
134043
- node = this.#children[token] = new _Node();
134044
134245
  }
134246
+ node = nextNode;
134247
+ }
134248
+ if (node.#index !== void 0) {
134249
+ throw PATH_ERROR;
134045
134250
  }
134046
- node.insert(restTokens, index, paramMap, context, pathErrorCheckOnly);
134251
+ node.#index = isStatic ? -1 : index;
134047
134252
  }
134048
134253
  buildRegExpStr() {
134049
134254
  const childKeys = Object.keys(this.#children).sort(compareKey);
134050
134255
  const strList = childKeys.map((k2) => {
134051
134256
  const c = this.#children[k2];
134052
- return (typeof c.#varIndex === "number" ? `(${k2})@${c.#varIndex}` : regExpMetaChars.has(k2) ? `\\${k2}` : k2) + c.buildRegExpStr();
134053
- });
134054
- if (typeof this.#index === "number") {
134257
+ const childStr = c.buildRegExpStr();
134258
+ return childStr === "" ? "" : (typeof c.#varIndex === "number" ? `(${k2})@${c.#varIndex}` : regExpMetaChars.has(k2) ? `\\${k2}` : k2) + childStr;
134259
+ }).filter(Boolean);
134260
+ if (typeof this.#index === "number" && this.#index !== -1) {
134055
134261
  strList.unshift(`#${this.#index}`);
134056
134262
  }
134057
134263
  if (strList.length === 0) {
@@ -134068,12 +134274,20 @@ var Node = class _Node {
134068
134274
  var Trie = class {
134069
134275
  #context = { varIndex: 0 };
134070
134276
  #root = new Node();
134071
- insert(path2, index, pathErrorCheckOnly) {
134277
+ #index = 0;
134278
+ // dynamic path -> [handler index, param assoc]; static paths are not registered
134279
+ paths = /* @__PURE__ */ Object.create(null);
134280
+ insert(path2, isStatic) {
134281
+ if (isStatic) {
134282
+ this.#root.insert(path2.split(""), 0, [], this.#context, true);
134283
+ return;
134284
+ }
134072
134285
  const paramAssoc = [];
134073
134286
  const groups = [];
134287
+ let markedPath = path2;
134074
134288
  for (let i = 0; ; ) {
134075
134289
  let replaced = false;
134076
- path2 = path2.replace(/\{[^}]+\}/g, (m2) => {
134290
+ markedPath = markedPath.replace(/\{[^}]+\}/g, (m2) => {
134077
134291
  const mark = `@\\${i}`;
134078
134292
  groups[i] = [mark, m2];
134079
134293
  i++;
@@ -134084,7 +134298,7 @@ var Trie = class {
134084
134298
  break;
134085
134299
  }
134086
134300
  }
134087
- const tokens = path2.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
134301
+ const tokens = markedPath.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
134088
134302
  for (let i = groups.length - 1; i >= 0; i--) {
134089
134303
  const [mark] = groups[i];
134090
134304
  for (let j2 = tokens.length - 1; j2 >= 0; j2--) {
@@ -134094,8 +134308,8 @@ var Trie = class {
134094
134308
  }
134095
134309
  }
134096
134310
  }
134097
- this.#root.insert(tokens, index, paramAssoc, this.#context, pathErrorCheckOnly);
134098
- return paramAssoc;
134311
+ this.#root.insert(tokens, this.#index, paramAssoc, this.#context, false);
134312
+ this.paths[path2] = [this.#index++, paramAssoc];
134099
134313
  }
134100
134314
  buildRegExp() {
134101
134315
  let regexp = this.#root.buildRegExpStr();
@@ -134121,7 +134335,6 @@ var Trie = class {
134121
134335
  };
134122
134336
 
134123
134337
  // node_modules/hono/dist/router/reg-exp-router/router.js
134124
- var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)];
134125
134338
  var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
134126
134339
  function buildWildcardRegExp(path2) {
134127
134340
  return wildcardRegExpCache[path2] ??= new RegExp(
@@ -134134,63 +134347,6 @@ function buildWildcardRegExp(path2) {
134134
134347
  function clearWildcardRegExpCache() {
134135
134348
  wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
134136
134349
  }
134137
- function buildMatcherFromPreprocessedRoutes(routes) {
134138
- const trie = new Trie();
134139
- const handlerData = [];
134140
- if (routes.length === 0) {
134141
- return nullMatcher;
134142
- }
134143
- const routesWithStaticPathFlag = routes.map(
134144
- (route) => [!/\*|\/:/.test(route[0]), ...route]
134145
- ).sort(
134146
- ([isStaticA, pathA], [isStaticB, pathB]) => isStaticA ? 1 : isStaticB ? -1 : pathA.length - pathB.length
134147
- );
134148
- const staticMap = /* @__PURE__ */ Object.create(null);
134149
- for (let i = 0, j2 = -1, len = routesWithStaticPathFlag.length; i < len; i++) {
134150
- const [pathErrorCheckOnly, path2, handlers] = routesWithStaticPathFlag[i];
134151
- if (pathErrorCheckOnly) {
134152
- staticMap[path2] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam];
134153
- } else {
134154
- j2++;
134155
- }
134156
- let paramAssoc;
134157
- try {
134158
- paramAssoc = trie.insert(path2, j2, pathErrorCheckOnly);
134159
- } catch (e) {
134160
- throw e === PATH_ERROR ? new UnsupportedPathError(path2) : e;
134161
- }
134162
- if (pathErrorCheckOnly) {
134163
- continue;
134164
- }
134165
- handlerData[j2] = handlers.map(([h, paramCount]) => {
134166
- const paramIndexMap = /* @__PURE__ */ Object.create(null);
134167
- paramCount -= 1;
134168
- for (; paramCount >= 0; paramCount--) {
134169
- const [key, value] = paramAssoc[paramCount];
134170
- paramIndexMap[key] = value;
134171
- }
134172
- return [h, paramIndexMap];
134173
- });
134174
- }
134175
- const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
134176
- for (let i = 0, len = handlerData.length; i < len; i++) {
134177
- for (let j2 = 0, len2 = handlerData[i].length; j2 < len2; j2++) {
134178
- const map2 = handlerData[i][j2]?.[1];
134179
- if (!map2) {
134180
- continue;
134181
- }
134182
- const keys = Object.keys(map2);
134183
- for (let k2 = 0, len3 = keys.length; k2 < len3; k2++) {
134184
- map2[keys[k2]] = paramReplacementMap[map2[keys[k2]]];
134185
- }
134186
- }
134187
- }
134188
- const handlerMap = [];
134189
- for (const i in indexReplacementMap) {
134190
- handlerMap[i] = handlerData[indexReplacementMap[i]];
134191
- }
134192
- return [regexp, handlerMap, staticMap];
134193
- }
134194
134350
  function findMiddleware(middleware, path2) {
134195
134351
  if (!middleware) {
134196
134352
  return void 0;
@@ -134206,9 +134362,18 @@ var RegExpRouter = class {
134206
134362
  name = "RegExpRouter";
134207
134363
  #middleware;
134208
134364
  #routes;
134365
+ #tries;
134209
134366
  constructor() {
134210
134367
  this.#middleware = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
134211
134368
  this.#routes = { [METHOD_NAME_ALL]: /* @__PURE__ */ Object.create(null) };
134369
+ this.#tries = { [METHOD_NAME_ALL]: new Trie() };
134370
+ }
134371
+ #insertPath(method, path2) {
134372
+ try {
134373
+ this.#tries[method].insert(path2, !/\*|\/:/.test(path2));
134374
+ } catch (e) {
134375
+ throw e === PATH_ERROR ? new UnsupportedPathError(path2) : e;
134376
+ }
134212
134377
  }
134213
134378
  add(method, path2, handler) {
134214
134379
  const middleware = this.#middleware;
@@ -134217,11 +134382,12 @@ var RegExpRouter = class {
134217
134382
  throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
134218
134383
  }
134219
134384
  if (!middleware[method]) {
134220
- ;
134385
+ this.#tries[method] = new Trie();
134221
134386
  [middleware, routes].forEach((handlerMap) => {
134222
134387
  handlerMap[method] = /* @__PURE__ */ Object.create(null);
134223
134388
  Object.keys(handlerMap[METHOD_NAME_ALL]).forEach((p2) => {
134224
134389
  handlerMap[method][p2] = [...handlerMap[METHOD_NAME_ALL][p2]];
134390
+ this.#insertPath(method, p2);
134225
134391
  });
134226
134392
  });
134227
134393
  }
@@ -134231,13 +134397,12 @@ var RegExpRouter = class {
134231
134397
  const paramCount = (path2.match(/\/:/g) || []).length;
134232
134398
  if (/\*$/.test(path2)) {
134233
134399
  const re2 = buildWildcardRegExp(path2);
134234
- if (method === METHOD_NAME_ALL) {
134235
- Object.keys(middleware).forEach((m2) => {
134236
- middleware[m2][path2] ||= findMiddleware(middleware[m2], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || [];
134237
- });
134238
- } else {
134239
- middleware[method][path2] ||= findMiddleware(middleware[method], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || [];
134240
- }
134400
+ Object.keys(middleware).forEach((m2) => {
134401
+ if ((method === METHOD_NAME_ALL || method === m2) && !middleware[m2][path2]) {
134402
+ this.#insertPath(m2, path2);
134403
+ middleware[m2][path2] = findMiddleware(middleware[m2], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || [];
134404
+ }
134405
+ });
134241
134406
  Object.keys(middleware).forEach((m2) => {
134242
134407
  if (method === METHOD_NAME_ALL || method === m2) {
134243
134408
  Object.keys(middleware[m2]).forEach((p2) => {
@@ -134259,9 +134424,12 @@ var RegExpRouter = class {
134259
134424
  const path22 = paths[i];
134260
134425
  Object.keys(routes).forEach((m2) => {
134261
134426
  if (method === METHOD_NAME_ALL || method === m2) {
134262
- routes[m2][path22] ||= [
134263
- ...findMiddleware(middleware[m2], path22) || findMiddleware(middleware[METHOD_NAME_ALL], path22) || []
134264
- ];
134427
+ if (!routes[m2][path22]) {
134428
+ this.#insertPath(m2, path22);
134429
+ routes[m2][path22] = [
134430
+ ...findMiddleware(middleware[m2], path22) || findMiddleware(middleware[METHOD_NAME_ALL], path22) || []
134431
+ ];
134432
+ }
134265
134433
  routes[m2][path22].push([handler, paramCount - len + i + 1]);
134266
134434
  }
134267
134435
  });
@@ -134273,29 +134441,54 @@ var RegExpRouter = class {
134273
134441
  Object.keys(this.#routes).concat(Object.keys(this.#middleware)).forEach((method) => {
134274
134442
  matchers[method] ||= this.#buildMatcher(method);
134275
134443
  });
134276
- this.#middleware = this.#routes = void 0;
134444
+ this.#middleware = this.#routes = this.#tries = void 0;
134277
134445
  clearWildcardRegExpCache();
134278
134446
  return matchers;
134279
134447
  }
134280
134448
  #buildMatcher(method) {
134281
- const routes = [];
134282
- let hasOwnRoute = method === METHOD_NAME_ALL;
134283
- [this.#middleware, this.#routes].forEach((r) => {
134284
- const ownRoute = r[method] ? Object.keys(r[method]).map((path2) => [path2, r[method][path2]]) : [];
134285
- if (ownRoute.length !== 0) {
134286
- hasOwnRoute ||= true;
134287
- routes.push(...ownRoute);
134288
- } else if (method !== METHOD_NAME_ALL) {
134289
- routes.push(
134290
- ...Object.keys(r[METHOD_NAME_ALL]).map((path2) => [path2, r[METHOD_NAME_ALL][path2]])
134291
- );
134449
+ const middleware = this.#middleware[method];
134450
+ const routes = this.#routes[method];
134451
+ const trie = this.#tries[method];
134452
+ const staticMap = /* @__PURE__ */ Object.create(null);
134453
+ const handlerData = [];
134454
+ [middleware, routes].forEach((r) => {
134455
+ for (const path2 in r) {
134456
+ const handlers = r[path2];
134457
+ const pathData = trie.paths[path2];
134458
+ if (!pathData) {
134459
+ staticMap[path2] = [handlers.map(([h]) => [h, /* @__PURE__ */ Object.create(null)]), emptyParam];
134460
+ continue;
134461
+ }
134462
+ const paramAssoc = pathData[1];
134463
+ handlerData[pathData[0]] = handlers.map(([h, paramCount]) => {
134464
+ const paramIndexMap = /* @__PURE__ */ Object.create(null);
134465
+ paramCount -= 1;
134466
+ for (; paramCount >= 0; paramCount--) {
134467
+ const [key, value] = paramAssoc[paramCount];
134468
+ paramIndexMap[key] = value;
134469
+ }
134470
+ return [h, paramIndexMap];
134471
+ });
134292
134472
  }
134293
134473
  });
134294
- if (!hasOwnRoute) {
134295
- return null;
134296
- } else {
134297
- return buildMatcherFromPreprocessedRoutes(routes);
134474
+ const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
134475
+ for (let i = 0, len = handlerData.length; i < len; i++) {
134476
+ for (let j2 = 0, len2 = handlerData[i].length; j2 < len2; j2++) {
134477
+ const map2 = handlerData[i][j2]?.[1];
134478
+ if (!map2) {
134479
+ continue;
134480
+ }
134481
+ const keys = Object.keys(map2);
134482
+ for (let k2 = 0, len3 = keys.length; k2 < len3; k2++) {
134483
+ map2[keys[k2]] = paramReplacementMap[map2[keys[k2]]];
134484
+ }
134485
+ }
134486
+ }
134487
+ const handlerMap = [];
134488
+ for (const i in indexReplacementMap) {
134489
+ handlerMap[i] = handlerData[indexReplacementMap[i]];
134298
134490
  }
134491
+ return [regexp, handlerMap, staticMap];
134299
134492
  }
134300
134493
  };
134301
134494
 
@@ -134356,76 +134549,51 @@ var SmartRouter = class {
134356
134549
 
134357
134550
  // node_modules/hono/dist/router/trie-router/node.js
134358
134551
  var emptyParams = /* @__PURE__ */ Object.create(null);
134359
- var hasChildren = (children) => {
134360
- for (const _2 in children) {
134361
- return true;
134362
- }
134363
- return false;
134364
- };
134552
+ var order = 0;
134365
134553
  var Node2 = class _Node2 {
134366
- #methods;
134367
- #children;
134368
- #patterns;
134369
- #order = 0;
134554
+ #methods = [];
134555
+ #children = /* @__PURE__ */ Object.create(null);
134556
+ #patterns = [];
134557
+ #pattern;
134370
134558
  #params = emptyParams;
134371
- constructor(method, handler, children) {
134372
- this.#children = children || /* @__PURE__ */ Object.create(null);
134373
- this.#methods = [];
134374
- if (method && handler) {
134375
- const m2 = /* @__PURE__ */ Object.create(null);
134376
- m2[method] = { handler, possibleKeys: [], score: 0 };
134377
- this.#methods = [m2];
134378
- }
134379
- this.#patterns = [];
134380
- }
134381
134559
  insert(method, path2, handler) {
134382
- this.#order = ++this.#order;
134383
134560
  let curNode = this;
134384
134561
  const parts = splitRoutingPath(path2);
134385
- const possibleKeys = [];
134386
- for (let i = 0, len = parts.length; i < len; i++) {
134387
- const p2 = parts[i];
134388
- const nextP = parts[i + 1];
134389
- const pattern = getPattern(p2, nextP);
134390
- const key = Array.isArray(pattern) ? pattern[0] : p2;
134391
- if (key in curNode.#children) {
134392
- curNode = curNode.#children[key];
134393
- if (pattern) {
134394
- possibleKeys.push(pattern[1]);
134395
- }
134396
- continue;
134562
+ const possibleKeys = /* @__PURE__ */ new Set();
134563
+ let i = 0;
134564
+ for (const p2 of parts) {
134565
+ const nextP = parts[++i];
134566
+ const pattern = getPattern(p2, nextP) || (nextP === void 0 && p2 && p2.indexOf("*") === p2.length - 1 ? p2 : null);
134567
+ const isParam = Array.isArray(pattern);
134568
+ const key = isParam ? pattern[0] : pattern || p2;
134569
+ const child = curNode.#children[key] ||= new _Node2();
134570
+ if (pattern && !child.#pattern) {
134571
+ child.#pattern = pattern;
134572
+ curNode.#patterns.push(child);
134397
134573
  }
134398
- curNode.#children[key] = new _Node2();
134399
- if (pattern) {
134400
- curNode.#patterns.push(pattern);
134401
- possibleKeys.push(pattern[1]);
134574
+ curNode = child;
134575
+ if (isParam) {
134576
+ possibleKeys.add(pattern[1]);
134402
134577
  }
134403
- curNode = curNode.#children[key];
134404
134578
  }
134405
134579
  curNode.#methods.push({
134406
134580
  [method]: {
134407
134581
  handler,
134408
- possibleKeys: possibleKeys.filter((v2, i, a) => a.indexOf(v2) === i),
134409
- score: this.#order
134582
+ possibleKeys: [...possibleKeys],
134583
+ score: ++order
134410
134584
  }
134411
134585
  });
134412
- return curNode;
134413
134586
  }
134414
134587
  #pushHandlerSets(handlerSets, node, method, nodeParams, params) {
134415
134588
  for (let i = 0, len = node.#methods.length; i < len; i++) {
134416
134589
  const m2 = node.#methods[i];
134417
134590
  const handlerSet = m2[method] || m2[METHOD_NAME_ALL];
134418
- const processedSet = {};
134419
- if (handlerSet !== void 0) {
134591
+ if (handlerSet) {
134420
134592
  handlerSet.params = /* @__PURE__ */ Object.create(null);
134421
134593
  handlerSets.push(handlerSet);
134422
- if (nodeParams !== emptyParams || params && params !== emptyParams) {
134423
- for (let i2 = 0, len2 = handlerSet.possibleKeys.length; i2 < len2; i2++) {
134424
- const key = handlerSet.possibleKeys[i2];
134425
- const processed = processedSet[handlerSet.score];
134426
- handlerSet.params[key] = params?.[key] && !processed ? params[key] : nodeParams[key] ?? params?.[key];
134427
- processedSet[handlerSet.score] = true;
134428
- }
134594
+ for (let i2 = 0, len2 = handlerSet.possibleKeys.length; i2 < len2; i2++) {
134595
+ const key = handlerSet.possibleKeys[i2];
134596
+ handlerSet.params[key] = params?.[key] && !i2 ? params[key] : nodeParams[key] ?? params?.[key];
134429
134597
  }
134430
134598
  }
134431
134599
  }
@@ -134457,33 +134625,33 @@ var Node2 = class _Node2 {
134457
134625
  tempNodes.push(nextNode);
134458
134626
  }
134459
134627
  }
134460
- for (let k2 = 0, len3 = node.#patterns.length; k2 < len3; k2++) {
134461
- const pattern = node.#patterns[k2];
134628
+ for (const child of node.#patterns) {
134629
+ const pattern = child.#pattern;
134462
134630
  const params = node.#params === emptyParams ? {} : { ...node.#params };
134463
- if (pattern === "*") {
134464
- const astNode = node.#children["*"];
134465
- if (astNode) {
134466
- this.#pushHandlerSets(handlerSets, astNode, method, node.#params);
134467
- astNode.#params = params;
134468
- tempNodes.push(astNode);
134631
+ if (typeof pattern === "string") {
134632
+ if (pattern === "*" || part.startsWith(pattern.slice(0, -1))) {
134633
+ this.#pushHandlerSets(handlerSets, child, method, node.#params);
134634
+ if (pattern === "*") {
134635
+ child.#params = params;
134636
+ tempNodes.push(child);
134637
+ }
134469
134638
  }
134470
134639
  continue;
134471
134640
  }
134472
- const [key, name, matcher] = pattern;
134473
- if (!part && !(matcher instanceof RegExp)) {
134641
+ const [, name, matcher] = pattern;
134642
+ if (!part && matcher === true) {
134474
134643
  continue;
134475
134644
  }
134476
- const child = node.#children[key];
134477
- if (matcher instanceof RegExp) {
134478
- if (partOffsets === null) {
134479
- partOffsets = new Array(len);
134645
+ if (matcher !== true) {
134646
+ if (!partOffsets) {
134647
+ partOffsets = [];
134480
134648
  let offset = path2[0] === "/" ? 1 : 0;
134481
134649
  for (let p2 = 0; p2 < len; p2++) {
134482
134650
  partOffsets[p2] = offset;
134483
134651
  offset += parts[p2].length + 1;
134484
134652
  }
134485
134653
  }
134486
- const restPathString = path2.substring(partOffsets[i]);
134654
+ const restPathString = path2.slice(partOffsets[i]);
134487
134655
  const m2 = matcher.exec(restPathString);
134488
134656
  if (m2) {
134489
134657
  params[name] = m2[0];
@@ -134497,11 +134665,12 @@ var Node2 = class _Node2 {
134497
134665
  params
134498
134666
  );
134499
134667
  }
134500
- if (hasChildren(child.#children)) {
134668
+ for (const _2 in child.#children) {
134501
134669
  child.#params = params;
134502
- const componentCount = m2[0].match(/\//)?.length ?? 0;
134670
+ const componentCount = m2[0].match(/\//g)?.length ?? 0;
134503
134671
  const targetCurNodes = curNodesQueue[componentCount] ||= [];
134504
134672
  targetCurNodes.push(child);
134673
+ break;
134505
134674
  }
134506
134675
  continue;
134507
134676
  }
@@ -134529,7 +134698,7 @@ var Node2 = class _Node2 {
134529
134698
  const shifted = curNodesQueue.shift();
134530
134699
  curNodes = shifted ? tempNodes.concat(shifted) : tempNodes;
134531
134700
  }
134532
- if (handlerSets.length > 1) {
134701
+ if (handlerSets[1]) {
134533
134702
  handlerSets.sort((a, b2) => {
134534
134703
  return a.score - b2.score;
134535
134704
  });
@@ -134541,19 +134710,11 @@ var Node2 = class _Node2 {
134541
134710
  // node_modules/hono/dist/router/trie-router/router.js
134542
134711
  var TrieRouter = class {
134543
134712
  name = "TrieRouter";
134544
- #node;
134545
- constructor() {
134546
- this.#node = new Node2();
134547
- }
134713
+ #node = new Node2();
134548
134714
  add(method, path2, handler) {
134549
- const results = checkOptionalParameter(path2);
134550
- if (results) {
134551
- for (let i = 0, len = results.length; i < len; i++) {
134552
- this.#node.insert(method, results[i], handler);
134553
- }
134554
- return;
134715
+ for (const result of checkOptionalParameter(path2) || [path2]) {
134716
+ this.#node.insert(method, result, handler);
134555
134717
  }
134556
- this.#node.insert(method, path2, handler);
134557
134718
  }
134558
134719
  match(method, path2) {
134559
134720
  return this.#node.search(method, path2);
@@ -136057,7 +136218,13 @@ var createMiscRoutes = (deps) => {
136057
136218
  // the box is starved. Additive capability flag only — live readings stay
136058
136219
  // off this polled endpoint. Absent means an older server that never
136059
136220
  // samples. Informational: pressure never holds, kills, or refuses sessions.
136060
- hostPressure: true
136221
+ hostPressure: true,
136222
+ // Provider-neutral prompt contract: normalized prompt events, opaque ids
136223
+ // and the atomic /prompt/answer route. A prompt_snapshot on subscribe
136224
+ // carries RETAINED prompts, terminal ones included — render on `state`,
136225
+ // not on presence — and an answer retry after that retention window is
136226
+ // answered 404 prompt_not_found rather than the recorded outcome.
136227
+ promptContract: { schemaVersion: 1, atomicAnswer: true }
136061
136228
  });
136062
136229
  });
136063
136230
  app.get("/api/profiles", (c) => c.json([]));
@@ -136555,6 +136722,10 @@ var createSessionRoutes = (deps) => {
136555
136722
  await deps.handleSendAnswer(c.req.param("id"), c.env.incoming, c.env.outgoing);
136556
136723
  return alreadyHandled6();
136557
136724
  });
136725
+ app.post("/:id/prompt/answer", async (c) => {
136726
+ await deps.handlePromptAnswer(c.req.param("id"), c.env.incoming, c.env.outgoing);
136727
+ return alreadyHandled6();
136728
+ });
136558
136729
  app.post("/:id/permission/answer", async (c) => {
136559
136730
  await deps.handlePermissionAnswer(c.req.param("id"), c.env.incoming, c.env.outgoing);
136560
136731
  return alreadyHandled6();
@@ -140738,10 +140909,10 @@ function watch(paths, options = {}) {
140738
140909
  var chokidar_default = { watch, FSWatcher };
140739
140910
 
140740
140911
  // node_modules/@threadbase-sh/scanner/dist/index.js
140741
- function applySort(metas, order) {
140912
+ function applySort(metas, order2) {
140742
140913
  const out = [...metas];
140743
140914
  const tie = (a, b2) => a.id.localeCompare(b2.id);
140744
- switch (order) {
140915
+ switch (order2) {
140745
140916
  case "recent":
140746
140917
  out.sort((a, b2) => b2.timestamp.localeCompare(a.timestamp) || tie(a, b2));
140747
140918
  break;
@@ -146251,7 +146422,7 @@ function parseSessionListQuery(url2) {
146251
146422
  if (!VALID_ORDERS.includes(orderRaw)) {
146252
146423
  return { error: `order must be asc or desc` };
146253
146424
  }
146254
- const order = orderRaw;
146425
+ const order2 = orderRaw;
146255
146426
  const statusRaw = url2.searchParams.get("status");
146256
146427
  let status;
146257
146428
  if (statusRaw) {
@@ -146264,7 +146435,7 @@ function parseSessionListQuery(url2) {
146264
146435
  status = parts;
146265
146436
  }
146266
146437
  const cursor = url2.searchParams.get("cursor") ?? void 0;
146267
- return { query: { limit, sortBy, order, status, cursor } };
146438
+ return { query: { limit, sortBy, order: order2, status, cursor } };
146268
146439
  }
146269
146440
  function readBody2(req) {
146270
146441
  return new Promise((resolve4, reject) => {
@@ -147672,6 +147843,522 @@ async function readGitBranch2(dir) {
147672
147843
  // src/api/handlers/sessions.handlers.ts
147673
147844
  init_providers();
147674
147845
  init_pty_shared();
147846
+
147847
+ // src/schemas/prompt.schema.ts
147848
+ init_zod();
147849
+ var PROMPT_SCHEMA_VERSION = 1;
147850
+ var OpaqueIdSchema = external_exports.string().trim().min(1).max(200);
147851
+ var MeaningfulStringSchema = external_exports.string().trim().min(1);
147852
+ var PromptOptionSchema = external_exports.object({
147853
+ optionId: OpaqueIdSchema,
147854
+ label: MeaningfulStringSchema,
147855
+ description: external_exports.string().optional(),
147856
+ preview: external_exports.string().optional()
147857
+ });
147858
+ var PromptQuestionSchema = external_exports.object({
147859
+ questionId: OpaqueIdSchema,
147860
+ text: MeaningfulStringSchema,
147861
+ header: external_exports.string().optional(),
147862
+ inputMode: external_exports.enum(["single", "multi", "text"]),
147863
+ options: external_exports.array(PromptOptionSchema),
147864
+ allowOther: external_exports.boolean(),
147865
+ secret: external_exports.union([external_exports.boolean(), external_exports.literal("unknown")])
147866
+ }).superRefine((question, ctx) => {
147867
+ const optionIds = question.options.map((option) => option.optionId);
147868
+ if (new Set(optionIds).size !== optionIds.length) {
147869
+ ctx.addIssue({
147870
+ code: "custom",
147871
+ message: "optionId values must be unique",
147872
+ path: ["options"]
147873
+ });
147874
+ }
147875
+ if (question.inputMode === "text" && question.options.length !== 0) {
147876
+ ctx.addIssue({
147877
+ code: "custom",
147878
+ message: "text questions cannot carry options",
147879
+ path: ["options"]
147880
+ });
147881
+ }
147882
+ if (question.inputMode !== "text" && question.options.length === 0) {
147883
+ ctx.addIssue({
147884
+ code: "custom",
147885
+ message: "select questions require options",
147886
+ path: ["options"]
147887
+ });
147888
+ }
147889
+ });
147890
+ var TERMINAL_PROMPT_STATES = /* @__PURE__ */ new Set(["resolved", "cancelled", "expired", "unavailable"]);
147891
+ var PromptSchema = external_exports.object({
147892
+ schemaVersion: external_exports.literal(PROMPT_SCHEMA_VERSION),
147893
+ sessionId: OpaqueIdSchema,
147894
+ promptId: OpaqueIdSchema,
147895
+ revision: external_exports.number().int().positive(),
147896
+ state: external_exports.enum(["open", "updated", "resolved", "cancelled", "expired", "unavailable"]),
147897
+ terminalReason: MeaningfulStringSchema.optional(),
147898
+ intent: external_exports.enum(["approval", "question"]),
147899
+ title: external_exports.string().optional(),
147900
+ message: external_exports.string().optional(),
147901
+ detail: external_exports.string().optional(),
147902
+ questions: external_exports.array(PromptQuestionSchema).min(1),
147903
+ answerRequirement: external_exports.enum(["blocking", "non_blocking", "unknown"]),
147904
+ expiresAt: external_exports.string().datetime({ offset: true }).nullable(),
147905
+ provenance: external_exports.object({
147906
+ source: external_exports.enum(["provider", "screen", "transcript", "synthetic"]),
147907
+ confidence: external_exports.enum(["authoritative", "inferred"])
147908
+ })
147909
+ }).superRefine((prompt, ctx) => {
147910
+ if (![prompt.title, prompt.message, prompt.detail].some((value) => value?.trim())) {
147911
+ ctx.addIssue({
147912
+ code: "custom",
147913
+ message: "prompt requires a meaningful title, message, or detail",
147914
+ path: ["message"]
147915
+ });
147916
+ }
147917
+ const questionIds = prompt.questions.map((question) => question.questionId);
147918
+ if (new Set(questionIds).size !== questionIds.length) {
147919
+ ctx.addIssue({
147920
+ code: "custom",
147921
+ message: "questionId values must be unique",
147922
+ path: ["questions"]
147923
+ });
147924
+ }
147925
+ const optionIds = prompt.questions.flatMap(
147926
+ (question) => question.options.map((option) => option.optionId)
147927
+ );
147928
+ if (new Set(optionIds).size !== optionIds.length) {
147929
+ ctx.addIssue({
147930
+ code: "custom",
147931
+ message: "optionId values must be unique within a prompt",
147932
+ path: ["questions"]
147933
+ });
147934
+ }
147935
+ const terminal = TERMINAL_PROMPT_STATES.has(prompt.state);
147936
+ if (terminal !== (prompt.terminalReason !== void 0)) {
147937
+ ctx.addIssue({
147938
+ code: "custom",
147939
+ message: terminal ? "terminal prompts require terminalReason" : "actionable prompts cannot carry terminalReason",
147940
+ path: ["terminalReason"]
147941
+ });
147942
+ }
147943
+ });
147944
+ var OptionResponseSchema = external_exports.object({
147945
+ questionId: OpaqueIdSchema,
147946
+ optionIds: external_exports.array(OpaqueIdSchema).min(1).refine((ids) => new Set(ids).size === ids.length, "optionIds must be unique"),
147947
+ text: external_exports.never().optional()
147948
+ });
147949
+ var TextResponseSchema = external_exports.object({
147950
+ questionId: OpaqueIdSchema,
147951
+ text: external_exports.string(),
147952
+ optionIds: external_exports.never().optional()
147953
+ });
147954
+ var PromptResponseSchema = external_exports.union([OptionResponseSchema, TextResponseSchema]);
147955
+ var PromptAnswerSchema = external_exports.object({
147956
+ promptId: OpaqueIdSchema,
147957
+ revision: external_exports.number().int().positive(),
147958
+ responses: external_exports.array(PromptResponseSchema).min(1),
147959
+ idempotencyKey: OpaqueIdSchema
147960
+ }).superRefine((answer, ctx) => {
147961
+ const questionIds = answer.responses.map((response) => response.questionId);
147962
+ if (new Set(questionIds).size !== questionIds.length) {
147963
+ ctx.addIssue({
147964
+ code: "custom",
147965
+ message: "each questionId can be answered only once",
147966
+ path: ["responses"]
147967
+ });
147968
+ }
147969
+ });
147970
+
147971
+ // src/services/prompts/promptRegistry.ts
147972
+ var import_node_crypto5 = require("crypto");
147973
+ var PROMPT_TERMINAL_RETENTION_MS = 10 * 60 * 1e3;
147974
+ var PROMPT_MAX_RECORDS_PER_SESSION = 200;
147975
+ var MAX_TIMEOUT_MS = 2147483647;
147976
+ function copyPrompt(prompt) {
147977
+ return {
147978
+ ...prompt,
147979
+ questions: prompt.questions.map((question) => ({
147980
+ ...question,
147981
+ options: question.options.map((option) => ({ ...option }))
147982
+ })),
147983
+ provenance: { ...prompt.provenance }
147984
+ };
147985
+ }
147986
+ function terminalError(state) {
147987
+ switch (state) {
147988
+ case "resolved":
147989
+ return "already_resolved";
147990
+ case "expired":
147991
+ return "prompt_expired";
147992
+ case "cancelled":
147993
+ return "prompt_cancelled";
147994
+ case "unavailable":
147995
+ return "prompt_unavailable";
147996
+ }
147997
+ }
147998
+ var PromptRegistry = class {
147999
+ bySession = /* @__PURE__ */ new Map();
148000
+ byId = /* @__PURE__ */ new Map();
148001
+ sequences = /* @__PURE__ */ new Map();
148002
+ createId;
148003
+ emit;
148004
+ onExpire;
148005
+ now;
148006
+ terminalRetentionMs;
148007
+ maxRecordsPerSession;
148008
+ constructor(options = {}) {
148009
+ this.createId = options.createId ?? import_node_crypto5.randomUUID;
148010
+ this.emit = options.emit;
148011
+ this.onExpire = options.onExpire;
148012
+ this.now = options.now ?? Date.now;
148013
+ this.terminalRetentionMs = options.terminalRetentionMs ?? PROMPT_TERMINAL_RETENTION_MS;
148014
+ this.maxRecordsPerSession = options.maxRecordsPerSession ?? PROMPT_MAX_RECORDS_PER_SESSION;
148015
+ }
148016
+ open(draft, adapter, promptId = this.createId()) {
148017
+ this.sweepExpired(draft.sessionId);
148018
+ const held = this.byId.get(promptId);
148019
+ if (held && (held.prompt.state === "open" || held.prompt.state === "updated")) {
148020
+ throw new Error(`Prompt id already exists: ${promptId}`);
148021
+ }
148022
+ const id = held ? this.createId() : promptId;
148023
+ const prompt = PromptSchema.parse({
148024
+ ...draft,
148025
+ schemaVersion: PROMPT_SCHEMA_VERSION,
148026
+ promptId: id,
148027
+ revision: 1,
148028
+ state: "open",
148029
+ questions: draft.questions.map((question) => ({
148030
+ ...question,
148031
+ questionId: this.createId(),
148032
+ options: question.options.map((option) => ({ ...option, optionId: this.createId() }))
148033
+ })),
148034
+ provenance: { ...draft.provenance }
148035
+ });
148036
+ const entry = {
148037
+ prompt,
148038
+ adapter,
148039
+ queue: Promise.resolve(),
148040
+ inFlight: /* @__PURE__ */ new Map(),
148041
+ outcomes: /* @__PURE__ */ new Map()
148042
+ };
148043
+ const session = this.bySession.get(prompt.sessionId) ?? /* @__PURE__ */ new Map();
148044
+ session.set(prompt.promptId, entry);
148045
+ this.bySession.set(prompt.sessionId, session);
148046
+ this.byId.set(prompt.promptId, entry);
148047
+ this.publish(entry);
148048
+ this.scheduleExpiration(entry);
148049
+ this.enforceCap(prompt.sessionId);
148050
+ return copyPrompt(prompt);
148051
+ }
148052
+ update(promptId, draft, adapter) {
148053
+ const entry = this.requireEntry(promptId);
148054
+ this.expireIfDue(entry, this.now());
148055
+ if (entry.prompt.sessionId !== draft.sessionId) throw new Error("Prompt session cannot change");
148056
+ if (entry.prompt.state !== "open" && entry.prompt.state !== "updated") {
148057
+ throw new Error(`Cannot update terminal prompt ${promptId}`);
148058
+ }
148059
+ if (entry.prompt.questions.length !== draft.questions.length) {
148060
+ throw new Error("Prompt question cardinality cannot change during an update");
148061
+ }
148062
+ const questions = draft.questions.map((question, questionIndex) => {
148063
+ const prior = entry.prompt.questions[questionIndex];
148064
+ if (prior.options.length !== question.options.length) {
148065
+ throw new Error("Prompt option cardinality cannot change during an update");
148066
+ }
148067
+ return {
148068
+ ...question,
148069
+ questionId: prior.questionId,
148070
+ options: question.options.map((option, optionIndex) => ({
148071
+ ...option,
148072
+ optionId: prior.options[optionIndex].optionId
148073
+ }))
148074
+ };
148075
+ });
148076
+ const prompt = PromptSchema.parse({
148077
+ ...draft,
148078
+ schemaVersion: PROMPT_SCHEMA_VERSION,
148079
+ promptId,
148080
+ revision: entry.prompt.revision + 1,
148081
+ state: "updated",
148082
+ questions,
148083
+ provenance: { ...draft.provenance }
148084
+ });
148085
+ entry.prompt = prompt;
148086
+ if (adapter) entry.adapter = adapter;
148087
+ this.publish(entry);
148088
+ this.scheduleExpiration(entry);
148089
+ return copyPrompt(entry.prompt);
148090
+ }
148091
+ transition(promptId, state, reason) {
148092
+ const entry = this.requireEntry(promptId);
148093
+ if (entry.prompt.state !== "open" && entry.prompt.state !== "updated") {
148094
+ throw new Error(`Cannot transition terminal prompt ${promptId}`);
148095
+ }
148096
+ entry.prompt = {
148097
+ ...entry.prompt,
148098
+ revision: entry.prompt.revision + 1,
148099
+ state,
148100
+ terminalReason: reason
148101
+ };
148102
+ entry.terminalAt = this.now();
148103
+ this.clearExpiration(entry);
148104
+ this.publish(entry);
148105
+ return copyPrompt(entry.prompt);
148106
+ }
148107
+ invalidateSession(sessionId, reason = "session_ended") {
148108
+ const transitioned = [];
148109
+ for (const entry of this.bySession.get(sessionId)?.values() ?? []) {
148110
+ if (entry.prompt.state === "open" || entry.prompt.state === "updated") {
148111
+ transitioned.push(this.transition(entry.prompt.promptId, "unavailable", reason));
148112
+ }
148113
+ }
148114
+ return transitioned;
148115
+ }
148116
+ get(promptId) {
148117
+ const entry = this.byId.get(promptId);
148118
+ if (!entry) return null;
148119
+ this.sweepExpired(entry.prompt.sessionId);
148120
+ return this.byId.has(promptId) ? copyPrompt(entry.prompt) : null;
148121
+ }
148122
+ hasActionable(sessionId) {
148123
+ this.sweepExpired(sessionId);
148124
+ return [...this.bySession.get(sessionId)?.values() ?? []].some(
148125
+ (entry) => entry.prompt.state === "open" || entry.prompt.state === "updated"
148126
+ );
148127
+ }
148128
+ snapshot(sessionId) {
148129
+ this.sweepExpired(sessionId);
148130
+ return {
148131
+ type: "prompt_snapshot",
148132
+ schemaVersion: PROMPT_SCHEMA_VERSION,
148133
+ sessionId,
148134
+ sequence: this.sequences.get(sessionId) ?? 0,
148135
+ prompts: [...this.bySession.get(sessionId)?.values() ?? []].map(
148136
+ (entry) => copyPrompt(entry.prompt)
148137
+ )
148138
+ };
148139
+ }
148140
+ dispose() {
148141
+ for (const entry of this.byId.values()) this.clearExpiration(entry);
148142
+ }
148143
+ answer(sessionId, answer) {
148144
+ this.sweepExpired(sessionId);
148145
+ const entry = this.byId.get(answer.promptId);
148146
+ if (!entry || entry.prompt.sessionId !== sessionId) {
148147
+ return Promise.resolve({ ok: false, code: "prompt_not_found" });
148148
+ }
148149
+ this.pruneOutcomes(entry);
148150
+ const recorded = entry.outcomes.get(answer.idempotencyKey);
148151
+ if (recorded) return Promise.resolve(recorded.outcome);
148152
+ const pending = entry.inFlight.get(answer.idempotencyKey);
148153
+ if (pending) return pending;
148154
+ const task = entry.queue.then(() => this.performAnswer(entry, answer));
148155
+ entry.queue = task.then(
148156
+ () => void 0,
148157
+ () => void 0
148158
+ );
148159
+ entry.inFlight.set(answer.idempotencyKey, task);
148160
+ void task.then((outcome) => {
148161
+ entry.inFlight.delete(answer.idempotencyKey);
148162
+ entry.outcomes.set(answer.idempotencyKey, { at: this.now(), outcome });
148163
+ });
148164
+ return task;
148165
+ }
148166
+ async performAnswer(entry, answer) {
148167
+ const prompt = entry.prompt;
148168
+ if (prompt.state !== "open" && prompt.state !== "updated") {
148169
+ return { ok: false, code: terminalError(prompt.state) };
148170
+ }
148171
+ if (this.expireIfDue(entry, this.now())) {
148172
+ return { ok: false, code: "prompt_expired" };
148173
+ }
148174
+ if (prompt.revision !== answer.revision) {
148175
+ return {
148176
+ ok: false,
148177
+ code: "prompt_revision_mismatch",
148178
+ currentRevision: prompt.revision
148179
+ };
148180
+ }
148181
+ const responseError = this.validateResponses(prompt, answer);
148182
+ if (responseError) return { ok: false, code: responseError };
148183
+ if (!entry.adapter) return { ok: false, code: "prompt_unavailable" };
148184
+ let adapterResult;
148185
+ try {
148186
+ adapterResult = await entry.adapter({ prompt: copyPrompt(prompt), answer });
148187
+ } catch {
148188
+ return { ok: false, code: "provider_error" };
148189
+ }
148190
+ if (entry.prompt.state !== "open" && entry.prompt.state !== "updated") {
148191
+ return { ok: false, code: terminalError(entry.prompt.state) };
148192
+ }
148193
+ if (entry.prompt.revision !== answer.revision) {
148194
+ return {
148195
+ ok: false,
148196
+ code: "prompt_revision_mismatch",
148197
+ currentRevision: entry.prompt.revision
148198
+ };
148199
+ }
148200
+ if (!adapterResult.ok) {
148201
+ if (adapterResult.terminal) {
148202
+ this.transition(
148203
+ prompt.promptId,
148204
+ adapterResult.terminal.state,
148205
+ adapterResult.terminal.reason
148206
+ );
148207
+ }
148208
+ return { ok: false, code: adapterResult.code };
148209
+ }
148210
+ return { ok: true, prompt: this.transition(prompt.promptId, "resolved", "answered") };
148211
+ }
148212
+ validateResponses(prompt, answer) {
148213
+ const questions = new Map(prompt.questions.map((question) => [question.questionId, question]));
148214
+ for (const response of answer.responses) {
148215
+ if (!questions.has(response.questionId)) return "unknown_question";
148216
+ }
148217
+ if (answer.responses.length !== prompt.questions.length) return "incomplete_answer";
148218
+ const responses = new Map(answer.responses.map((response) => [response.questionId, response]));
148219
+ for (const question of prompt.questions) {
148220
+ const response = responses.get(question.questionId);
148221
+ if (!response) return "incomplete_answer";
148222
+ if (question.inputMode === "text") {
148223
+ if (typeof response.text !== "string") return "incomplete_answer";
148224
+ continue;
148225
+ }
148226
+ const optionIds = response.optionIds;
148227
+ if (!optionIds) return "incomplete_answer";
148228
+ if (question.inputMode === "single" && optionIds.length !== 1) {
148229
+ return "unsupported_prompt_shape";
148230
+ }
148231
+ const known = new Set(question.options.map((option) => option.optionId));
148232
+ if (optionIds.some((optionId) => !known.has(optionId))) return "unknown_option";
148233
+ }
148234
+ return null;
148235
+ }
148236
+ publish(entry) {
148237
+ const sessionId = entry.prompt.sessionId;
148238
+ const sequence = (this.sequences.get(sessionId) ?? 0) + 1;
148239
+ this.sequences.set(sessionId, sequence);
148240
+ this.emit?.({
148241
+ type: "prompt_event",
148242
+ sessionId,
148243
+ sequence,
148244
+ prompt: copyPrompt(entry.prompt)
148245
+ });
148246
+ }
148247
+ scheduleExpiration(entry) {
148248
+ this.clearExpiration(entry);
148249
+ const expiresAt = entry.prompt.expiresAt;
148250
+ if (expiresAt === null) return;
148251
+ const delay = Math.min(MAX_TIMEOUT_MS, Math.max(0, Date.parse(expiresAt) - this.now()));
148252
+ entry.expiryTimer = setTimeout(() => {
148253
+ entry.expiryTimer = void 0;
148254
+ if (!this.expireIfDue(entry, this.now())) this.scheduleExpiration(entry);
148255
+ }, delay);
148256
+ entry.expiryTimer.unref?.();
148257
+ }
148258
+ clearExpiration(entry) {
148259
+ if (entry.expiryTimer) clearTimeout(entry.expiryTimer);
148260
+ entry.expiryTimer = void 0;
148261
+ }
148262
+ expireIfDue(entry, now) {
148263
+ if (entry.prompt.state !== "open" && entry.prompt.state !== "updated") return false;
148264
+ if (entry.prompt.expiresAt === null || now < Date.parse(entry.prompt.expiresAt)) return false;
148265
+ const expired = this.transition(entry.prompt.promptId, "expired", "deadline_elapsed");
148266
+ this.onExpire?.(expired);
148267
+ return true;
148268
+ }
148269
+ requireEntry(promptId) {
148270
+ const entry = this.byId.get(promptId);
148271
+ if (!entry) throw new Error(`Unknown prompt: ${promptId}`);
148272
+ return entry;
148273
+ }
148274
+ sweepExpired(sessionId) {
148275
+ const now = this.now();
148276
+ const session = this.bySession.get(sessionId);
148277
+ if (!session) return;
148278
+ for (const [promptId, entry] of session) {
148279
+ this.expireIfDue(entry, now);
148280
+ if (entry.terminalAt !== void 0 && now - entry.terminalAt > this.terminalRetentionMs) {
148281
+ this.clearExpiration(entry);
148282
+ session.delete(promptId);
148283
+ this.byId.delete(promptId);
148284
+ }
148285
+ }
148286
+ if (session.size === 0) this.bySession.delete(sessionId);
148287
+ }
148288
+ enforceCap(sessionId) {
148289
+ const session = this.bySession.get(sessionId);
148290
+ if (!session || session.size <= this.maxRecordsPerSession) return;
148291
+ const terminal = [...session.entries()].filter(([, entry]) => entry.terminalAt !== void 0).sort((a, b2) => (a[1].terminalAt ?? 0) - (b2[1].terminalAt ?? 0));
148292
+ while (session.size > this.maxRecordsPerSession && terminal.length > 0) {
148293
+ const [promptId, entry] = terminal.shift();
148294
+ this.clearExpiration(entry);
148295
+ session.delete(promptId);
148296
+ this.byId.delete(promptId);
148297
+ }
148298
+ }
148299
+ pruneOutcomes(entry) {
148300
+ const now = this.now();
148301
+ for (const [key, recorded] of entry.outcomes) {
148302
+ if (now - recorded.at > this.terminalRetentionMs) entry.outcomes.delete(key);
148303
+ }
148304
+ }
148305
+ };
148306
+
148307
+ // src/services/prompts/ptyPromptAdapter.ts
148308
+ function permissionPromptDraft(sessionId, gate) {
148309
+ if (!gate) throw new Error("Cannot normalize an absent permission gate");
148310
+ const message = gate.prompt?.trim() || "Approval required";
148311
+ return {
148312
+ sessionId,
148313
+ intent: "approval",
148314
+ title: "Approval",
148315
+ message,
148316
+ ...gate.detail?.trim() ? { detail: gate.detail } : {},
148317
+ questions: [
148318
+ {
148319
+ text: message,
148320
+ header: "Approval",
148321
+ inputMode: "single",
148322
+ options: gate.options.map((option) => ({ label: option.label })),
148323
+ allowOther: false,
148324
+ secret: "unknown"
148325
+ }
148326
+ ],
148327
+ answerRequirement: "unknown",
148328
+ expiresAt: null,
148329
+ provenance: { source: "screen", confidence: "inferred" }
148330
+ };
148331
+ }
148332
+ function questionPromptDraft(sessionId, questions, source) {
148333
+ const first = questions[0];
148334
+ if (!first) throw new Error("Cannot normalize an empty question list");
148335
+ return {
148336
+ sessionId,
148337
+ intent: "question",
148338
+ ...first.header.trim() ? { title: first.header } : {},
148339
+ message: first.question,
148340
+ questions: questions.map((question) => ({
148341
+ text: question.question,
148342
+ ...question.header.trim() ? { header: question.header } : {},
148343
+ inputMode: question.multiSelect ? "multi" : "single",
148344
+ options: question.options.map((option) => ({
148345
+ label: option.label,
148346
+ ...option.description ? { description: option.description } : {},
148347
+ ...option.preview ? { preview: option.preview } : {}
148348
+ })),
148349
+ allowOther: false,
148350
+ secret: "unknown"
148351
+ })),
148352
+ answerRequirement: "unknown",
148353
+ expiresAt: null,
148354
+ provenance: {
148355
+ source,
148356
+ confidence: source === "transcript" ? "authoritative" : "inferred"
148357
+ }
148358
+ };
148359
+ }
148360
+
148361
+ // src/api/handlers/sessions.handlers.ts
147675
148362
  init_codexScreen();
147676
148363
  init_detectPermissionGate();
147677
148364
  init_detectQuestionFromScreen();
@@ -148041,6 +148728,21 @@ function codexSessionActiveBody(outcome) {
148041
148728
  ...outcome.ownerSource != null && { ownerSource: outcome.ownerSource }
148042
148729
  };
148043
148730
  }
148731
+ function promptAnswerStatus(code) {
148732
+ switch (code) {
148733
+ case "prompt_not_found":
148734
+ return 404;
148735
+ case "provider_error":
148736
+ return 502;
148737
+ case "unknown_question":
148738
+ case "unknown_option":
148739
+ case "incomplete_answer":
148740
+ case "unsupported_prompt_shape":
148741
+ return 400;
148742
+ default:
148743
+ return 409;
148744
+ }
148745
+ }
148044
148746
  var SessionHandlers = class {
148045
148747
  constructor(deps) {
148046
148748
  this.deps = deps;
@@ -148079,6 +148781,10 @@ var SessionHandlers = class {
148079
148781
  get pendingQuestions() {
148080
148782
  return this.deps.pendingQuestions;
148081
148783
  }
148784
+ get promptRegistry() {
148785
+ if (!this.deps.promptRegistry) this.deps.promptRegistry = new PromptRegistry();
148786
+ return this.deps.promptRegistry;
148787
+ }
148082
148788
  get pendingQuestionKey() {
148083
148789
  return this.deps.pendingQuestionKey;
148084
148790
  }
@@ -148597,18 +149303,26 @@ var SessionHandlers = class {
148597
149303
  json2(res, 400, { error: "Missing input field" });
148598
149304
  return;
148599
149305
  }
149306
+ this.promptRegistry.sweepExpired(sessionId);
148600
149307
  const openPrompt = this.pendingPermission.has(sessionId) ? "permission" : this.pendingQuestions.has(sessionId) ? "question" : null;
148601
149308
  if (openPrompt) {
148602
- this.log.info(`[input.prompt_pending] ${sessionId.slice(0, 8)} kind=${openPrompt}`, {
148603
- event: "input.prompt_pending",
148604
- sessionId,
148605
- promptKind: openPrompt
148606
- });
149309
+ const pendingGate = openPrompt === "permission" ? this.pendingPermission.get(sessionId) : void 0;
149310
+ const promptState = pendingGate?.promptId !== void 0 && this.promptRegistry.get(pendingGate.promptId)?.state === "resolved" ? "answered" : "open";
149311
+ this.log.info(
149312
+ `[input.prompt_pending] ${sessionId.slice(0, 8)} kind=${openPrompt} state=${promptState}`,
149313
+ {
149314
+ event: "input.prompt_pending",
149315
+ sessionId,
149316
+ promptKind: openPrompt,
149317
+ promptState
149318
+ }
149319
+ );
148607
149320
  json2(res, 409, {
148608
149321
  ok: false,
148609
149322
  reason: "prompt_pending",
148610
149323
  promptKind: openPrompt,
148611
- error: "A prompt is waiting for an answer; answer or dismiss it before sending text"
149324
+ promptState,
149325
+ error: promptState === "answered" ? "Your answer was sent; wait for the prompt to close before sending text" : "A prompt is waiting for an answer; answer or dismiss it before sending text"
148612
149326
  });
148613
149327
  return;
148614
149328
  }
@@ -148652,30 +149366,99 @@ var SessionHandlers = class {
148652
149366
  // the later JSONL flush of the same question is de-duped. We synthesize a
148653
149367
  // screen-scoped toolUseId; the JSONL path overwrites pendingQuestions with the
148654
149368
  // real toolUseId when it lands, so answering works once JSONL catches up.
148655
- handleLiveQuestion(sessionId, questions) {
149369
+ handleLiveQuestion(sessionId, questions, occurrenceId) {
148656
149370
  const key = questionContentKey(questions);
148657
149371
  if (this.pendingQuestionKey.get(sessionId) === key) return;
148658
149372
  const toolUseId = `screen:${sessionId}:${key.length}`;
148659
- this.pendingQuestions.set(sessionId, { toolUseId, questions, origin: "pty" });
149373
+ const prior = this.pendingQuestions.get(sessionId);
149374
+ const priorPrompt = prior ? this.promptRegistry.get(prior.promptId) : null;
149375
+ if (priorPrompt?.state === "open" || priorPrompt?.state === "updated") {
149376
+ this.promptRegistry.transition(priorPrompt.promptId, "cancelled", "replaced");
149377
+ }
149378
+ const prompt = this.promptRegistry.open(
149379
+ questionPromptDraft(sessionId, questions, "screen"),
149380
+ this.questionAnswerAdapter(sessionId),
149381
+ occurrenceId
149382
+ );
149383
+ this.pendingQuestions.set(sessionId, {
149384
+ toolUseId,
149385
+ questions,
149386
+ origin: "pty",
149387
+ promptId: prompt.promptId
149388
+ });
148660
149389
  this.pendingQuestionKey.set(sessionId, key);
148661
149390
  this.broadcastToSession(sessionId, { type: "question", sessionId, toolUseId, questions });
148662
149391
  }
149392
+ handleJsonlQuestion(sessionId, toolUseId, questions, origin) {
149393
+ const prior = this.pendingQuestions.get(sessionId);
149394
+ const sameQuestion = prior !== void 0 && questionContentKey(prior.questions) === questionContentKey(questions);
149395
+ let prompt;
149396
+ if (sameQuestion) {
149397
+ const current = this.promptRegistry.get(prior.promptId);
149398
+ prompt = current?.provenance.source === "transcript" ? current : this.promptRegistry.update(
149399
+ prior.promptId,
149400
+ questionPromptDraft(sessionId, questions, "transcript"),
149401
+ this.questionAnswerAdapter(sessionId)
149402
+ );
149403
+ } else {
149404
+ const priorPrompt = prior ? this.promptRegistry.get(prior.promptId) : null;
149405
+ if (priorPrompt?.state === "open" || priorPrompt?.state === "updated") {
149406
+ this.promptRegistry.transition(priorPrompt.promptId, "cancelled", "replaced");
149407
+ }
149408
+ prompt = this.promptRegistry.open(
149409
+ questionPromptDraft(sessionId, questions, "transcript"),
149410
+ this.questionAnswerAdapter(sessionId)
149411
+ );
149412
+ }
149413
+ this.pendingQuestions.set(sessionId, {
149414
+ toolUseId,
149415
+ questions,
149416
+ origin,
149417
+ promptId: prompt.promptId
149418
+ });
149419
+ }
148663
149420
  // Permission gate opened/closed (OSC 777 + scraped options). Broadcasts the
148664
149421
  // additive `permission` / `permission_cancelled` events. Mobile answers by
148665
149422
  // sending the chosen option index via /input { keys } (e.g. "2\r").
148666
- handlePermissionChange(sessionId, gate) {
149423
+ handlePermissionChange(sessionId, gate, occurrenceId) {
148667
149424
  if (gate === null) {
148668
- if (!this.pendingPermission.has(sessionId)) return;
149425
+ const prior2 = this.pendingPermission.get(sessionId);
149426
+ if (!prior2) return;
149427
+ const prompt2 = prior2.promptId ? this.promptRegistry.get(prior2.promptId) : null;
149428
+ if (prompt2?.state === "open" || prompt2?.state === "updated") {
149429
+ this.promptRegistry.transition(prompt2.promptId, "cancelled", "provider_closed");
149430
+ }
148669
149431
  this.pendingPermission.delete(sessionId);
148670
149432
  this.pendingPermissionKey.delete(sessionId);
148671
149433
  this.broadcastToSession(sessionId, { type: "permission_cancelled", sessionId });
148672
149434
  return;
148673
149435
  }
148674
149436
  const key = permissionContentKey(gate);
148675
- if (this.pendingPermissionKey.get(sessionId) === key) return;
148676
149437
  const prior = this.pendingPermission.get(sessionId);
148677
- const gateId = prior && permissionGateKey(prior) === permissionGateKey(gate) ? prior.gateId : (0, import_crypto9.randomUUID)();
148678
- this.pendingPermission.set(sessionId, { ...gate, gateId });
149438
+ const priorPromptId = prior?.promptId;
149439
+ if (this.pendingPermissionKey.get(sessionId) === key && (occurrenceId === void 0 || prior?.occurrenceId === occurrenceId)) {
149440
+ return;
149441
+ }
149442
+ const samePrompt = prior && priorPromptId !== void 0 && permissionGateKey(prior) === permissionGateKey(gate) && (occurrenceId === void 0 || prior.occurrenceId === occurrenceId);
149443
+ if (prior && !samePrompt) {
149444
+ const priorPrompt = prior.promptId ? this.promptRegistry.get(prior.promptId) : null;
149445
+ if (priorPrompt?.state === "open" || priorPrompt?.state === "updated") {
149446
+ this.promptRegistry.transition(priorPrompt.promptId, "cancelled", "replaced");
149447
+ }
149448
+ }
149449
+ const prompt = gate.options.length === 0 ? null : samePrompt ? this.promptRegistry.get(priorPromptId) : this.promptRegistry.open(
149450
+ permissionPromptDraft(sessionId, gate),
149451
+ this.permissionAnswerAdapter(sessionId),
149452
+ occurrenceId
149453
+ );
149454
+ if (samePrompt && !prompt) throw new Error("Pending permission prompt disappeared");
149455
+ const gateId = prompt?.promptId ?? occurrenceId ?? prior?.gateId ?? (0, import_crypto9.randomUUID)();
149456
+ this.pendingPermission.set(sessionId, {
149457
+ ...gate,
149458
+ gateId,
149459
+ ...prompt ? { promptId: prompt.promptId } : {},
149460
+ ...occurrenceId !== void 0 ? { occurrenceId } : {}
149461
+ });
148679
149462
  this.pendingPermissionKey.set(sessionId, key);
148680
149463
  const subscriberCount = this.sessionSubscribers.get(sessionId)?.size ?? 0;
148681
149464
  this.log.info(
@@ -148693,6 +149476,107 @@ var SessionHandlers = class {
148693
149476
  gateId
148694
149477
  });
148695
149478
  }
149479
+ permissionAnswerAdapter(sessionId) {
149480
+ return async ({ prompt, answer }) => {
149481
+ const gate = this.pendingPermission.get(sessionId);
149482
+ if (!gate || gate.promptId !== prompt.promptId) {
149483
+ return {
149484
+ ok: false,
149485
+ code: "prompt_unavailable",
149486
+ terminal: { state: "unavailable", reason: "provider_prompt_missing" }
149487
+ };
149488
+ }
149489
+ const response = answer.responses[0];
149490
+ const selectedId = response?.optionIds?.[0];
149491
+ const selectedIndex = prompt.questions[0]?.options.findIndex(
149492
+ (option2) => option2.optionId === selectedId
149493
+ );
149494
+ if (selectedIndex === void 0 || selectedIndex < 0) {
149495
+ return { ok: false, code: "unknown_option" };
149496
+ }
149497
+ const option = gate.options[selectedIndex];
149498
+ if (!option) return { ok: false, code: "unknown_option" };
149499
+ const provider = this.sessionStore.getManaged(sessionId)?.provider;
149500
+ if (provider !== CODEX_CLI_PROVIDER && !await this.permissionGateStillOpen(sessionId, permissionGateKey(gate))) {
149501
+ return {
149502
+ ok: false,
149503
+ code: "prompt_cancelled",
149504
+ terminal: { state: "cancelled", reason: "provider_closed" }
149505
+ };
149506
+ }
149507
+ if (this.pendingPermission.get(sessionId)?.promptId !== prompt.promptId) {
149508
+ return { ok: false, code: "prompt_cancelled" };
149509
+ }
149510
+ try {
149511
+ this.ptyManager.sendKeys(
149512
+ sessionId,
149513
+ option.answerKeys ?? permissionAnswerKeys(option.index)
149514
+ );
149515
+ } catch {
149516
+ return { ok: false, code: "provider_error" };
149517
+ }
149518
+ return { ok: true };
149519
+ };
149520
+ }
149521
+ questionAnswerAdapter(sessionId) {
149522
+ return async ({ prompt, answer }) => {
149523
+ const pending = this.pendingQuestions.get(sessionId);
149524
+ if (!pending || pending.promptId !== prompt.promptId) {
149525
+ return {
149526
+ ok: false,
149527
+ code: "prompt_unavailable",
149528
+ terminal: { state: "unavailable", reason: "provider_prompt_missing" }
149529
+ };
149530
+ }
149531
+ const answers = {};
149532
+ for (const question of prompt.questions) {
149533
+ const response = answer.responses.find((item) => item.questionId === question.questionId);
149534
+ if (!response?.optionIds) return { ok: false, code: "unsupported_prompt_shape" };
149535
+ answers[question.text] = response.optionIds.map((optionId) => {
149536
+ const option = question.options.find((item) => item.optionId === optionId);
149537
+ return option?.label ?? "";
149538
+ });
149539
+ }
149540
+ const resolution = resolveAnswer(pending, {
149541
+ toolUseId: pending.toolUseId,
149542
+ answers
149543
+ });
149544
+ if (!resolution.ok) {
149545
+ const code = resolution.reason === "unknown_option" || resolution.reason === "incomplete_answer" || resolution.reason === "unsupported_prompt_shape" ? resolution.reason : "prompt_unavailable";
149546
+ return { ok: false, code };
149547
+ }
149548
+ if (!await this.questionMenuStillOpen(sessionId)) {
149549
+ this.pendingQuestions.delete(sessionId);
149550
+ this.pendingQuestionKey.delete(sessionId);
149551
+ this.broadcastToSession(sessionId, {
149552
+ type: "question_cancelled",
149553
+ sessionId,
149554
+ toolUseId: pending.toolUseId
149555
+ });
149556
+ return {
149557
+ ok: false,
149558
+ code: "prompt_cancelled",
149559
+ terminal: { state: "cancelled", reason: "provider_closed" }
149560
+ };
149561
+ }
149562
+ if (this.pendingQuestions.get(sessionId)?.promptId !== prompt.promptId) {
149563
+ return { ok: false, code: "prompt_cancelled" };
149564
+ }
149565
+ try {
149566
+ this.ptyManager.sendKeys(sessionId, resolution.keys);
149567
+ } catch {
149568
+ return { ok: false, code: "provider_error" };
149569
+ }
149570
+ this.pendingQuestions.delete(sessionId);
149571
+ this.pendingQuestionKey.delete(sessionId);
149572
+ this.broadcastToSession(sessionId, {
149573
+ type: "question_cancelled",
149574
+ sessionId,
149575
+ toolUseId: pending.toolUseId
149576
+ });
149577
+ return { ok: true };
149578
+ };
149579
+ }
148696
149580
  /**
148697
149581
  * Answer a permission gate — the validated counterpart of POST /:id/input.
148698
149582
  *
@@ -148728,6 +149612,11 @@ var SessionHandlers = class {
148728
149612
  return;
148729
149613
  }
148730
149614
  const gateClosed = () => {
149615
+ const pending = this.pendingPermission.get(sessionId);
149616
+ const prompt = pending?.promptId ? this.promptRegistry.get(pending.promptId) : null;
149617
+ if (prompt?.state === "open" || prompt?.state === "updated") {
149618
+ this.promptRegistry.transition(prompt.promptId, "cancelled", "provider_closed");
149619
+ }
148731
149620
  this.pendingPermission.delete(sessionId);
148732
149621
  this.pendingPermissionKey.delete(sessionId);
148733
149622
  this.broadcastToSession(sessionId, { type: "permission_cancelled", sessionId });
@@ -148769,6 +149658,10 @@ var SessionHandlers = class {
148769
149658
  json2(res, 400, { ok: false, reason: message });
148770
149659
  return;
148771
149660
  }
149661
+ const normalized = gate.promptId ? this.promptRegistry.get(gate.promptId) : null;
149662
+ if (normalized?.state === "open" || normalized?.state === "updated") {
149663
+ this.promptRegistry.transition(normalized.promptId, "resolved", "answered_legacy");
149664
+ }
148772
149665
  json2(res, 200, { ok: true });
148773
149666
  }
148774
149667
  /**
@@ -148814,6 +149707,10 @@ var SessionHandlers = class {
148814
149707
  }
148815
149708
  const toolUseId = pending?.toolUseId ?? "";
148816
149709
  if (!await this.questionMenuStillOpen(sessionId)) {
149710
+ const prompt = this.promptRegistry.get(pending?.promptId ?? "");
149711
+ if (prompt?.state === "open" || prompt?.state === "updated") {
149712
+ this.promptRegistry.transition(prompt.promptId, "cancelled", "provider_closed");
149713
+ }
148817
149714
  this.pendingQuestions.delete(sessionId);
148818
149715
  this.pendingQuestionKey.delete(sessionId);
148819
149716
  this.broadcastToSession(sessionId, { type: "question_cancelled", sessionId, toolUseId });
@@ -148827,10 +149724,41 @@ var SessionHandlers = class {
148827
149724
  json2(res, 400, { ok: false, reason: message });
148828
149725
  return;
148829
149726
  }
149727
+ const normalized = this.promptRegistry.get(pending?.promptId ?? "");
149728
+ if (normalized?.state === "open" || normalized?.state === "updated") {
149729
+ this.promptRegistry.transition(normalized.promptId, "resolved", "answered_legacy");
149730
+ }
148830
149731
  this.pendingQuestions.delete(sessionId);
148831
149732
  this.broadcastToSession(sessionId, { type: "question_cancelled", sessionId, toolUseId });
148832
149733
  json2(res, 200, { ok: true });
148833
149734
  }
149735
+ /**
149736
+ * Answer a normalized prompt by its opaque ids.
149737
+ *
149738
+ * Refusals are keyed by `code` — the stable machine taxonomy of the prompt
149739
+ * contract. The released legacy routes (`/answer`, `/permission/answer`) key
149740
+ * theirs by `reason` and keep doing so; a client reads whichever key belongs
149741
+ * to the route it called, and the two vocabularies are not merged.
149742
+ *
149743
+ * Status follows the same split as the legacy routes: a malformed or
149744
+ * unanswerable *request* is 400, a prompt whose *state* refuses the answer is
149745
+ * 409. A retry after PROMPT_TERMINAL_RETENTION_MS answers 404
149746
+ * `prompt_not_found`, not the recorded outcome — the record it would replay
149747
+ * is gone by then.
149748
+ */
149749
+ async handlePromptAnswer(sessionId, req, res) {
149750
+ const parsed = PromptAnswerSchema.safeParse(await readBody2(req));
149751
+ if (!parsed.success) {
149752
+ json2(res, 400, { ok: false, code: "invalid_prompt_answer" });
149753
+ return;
149754
+ }
149755
+ const outcome = await this.promptRegistry.answer(sessionId, parsed.data);
149756
+ if (outcome.ok) {
149757
+ json2(res, 200, outcome);
149758
+ return;
149759
+ }
149760
+ json2(res, promptAnswerStatus(outcome.code), outcome);
149761
+ }
148834
149762
  // Best-effort: a session we don't own a PTY for, or one that raced away
148835
149763
  // mid-read, is not ours to veto — say yes and let the write decide.
148836
149764
  async questionMenuStillOpen(sessionId) {
@@ -151175,6 +152103,27 @@ init_capabilities();
151175
152103
  function wsAllows(principal, required2) {
151176
152104
  return principal === null || hasCapability(principal, required2);
151177
152105
  }
152106
+ function clearExpiredPendingPrompt(deps, prompt) {
152107
+ const permission = deps.pendingPermission.get(prompt.sessionId);
152108
+ if (permission?.promptId === prompt.promptId) {
152109
+ deps.pendingPermission.delete(prompt.sessionId);
152110
+ deps.pendingPermissionKey.delete(prompt.sessionId);
152111
+ deps.wsHub.broadcastToClients(deps.sessionSubscribers.get(prompt.sessionId) ?? [], {
152112
+ type: "permission_cancelled",
152113
+ sessionId: prompt.sessionId
152114
+ });
152115
+ }
152116
+ const question = deps.pendingQuestions.get(prompt.sessionId);
152117
+ if (question?.promptId === prompt.promptId) {
152118
+ deps.pendingQuestions.delete(prompt.sessionId);
152119
+ deps.pendingQuestionKey.delete(prompt.sessionId);
152120
+ deps.wsHub.broadcastToClients(deps.sessionSubscribers.get(prompt.sessionId) ?? [], {
152121
+ type: "question_cancelled",
152122
+ sessionId: prompt.sessionId,
152123
+ toolUseId: question.toolUseId
152124
+ });
152125
+ }
152126
+ }
151178
152127
  function createConversationWatcherEvents(deps) {
151179
152128
  return {
151180
152129
  onNewLineSpans: (filePath, spans, readFrom, endOffset) => {
@@ -151297,11 +152246,11 @@ function createLiveSessionOptions(deps) {
151297
152246
  ts: ts2
151298
152247
  });
151299
152248
  },
151300
- onPermissionChange: (sessionId, gate) => {
151301
- deps.sessionHandlers().handlePermissionChange(sessionId, gate);
152249
+ onPermissionChange: (sessionId, gate, occurrenceId) => {
152250
+ deps.sessionHandlers().handlePermissionChange(sessionId, gate, occurrenceId);
151302
152251
  },
151303
- onLiveQuestion: (sessionId, questions) => {
151304
- deps.sessionHandlers().handleLiveQuestion(sessionId, questions);
152252
+ onLiveQuestion: (sessionId, questions, occurrenceId) => {
152253
+ deps.sessionHandlers().handleLiveQuestion(sessionId, questions, occurrenceId);
151305
152254
  },
151306
152255
  onLiveQuestionGone: (sessionId) => {
151307
152256
  deps.pendingQuestionKey.delete(sessionId);
@@ -151382,10 +152331,11 @@ function createLiveSessionOptions(deps) {
151382
152331
  if (filePath) {
151383
152332
  deps.fileWatcher.unwatch(filePath);
151384
152333
  deps.sessionFileMap.delete(session.id);
151385
- deps.cancelPendingQuestion(session.id);
151386
152334
  }
152335
+ deps.cancelPendingQuestion(session.id);
151387
152336
  deps.pendingPermission.delete(session.id);
151388
152337
  deps.pendingPermissionKey.delete(session.id);
152338
+ deps.promptRegistry.invalidateSession(session.id, "session_ended");
151389
152339
  deps.contendedSessions.delete(session.id);
151390
152340
  deps.rememberSelfPtyEnded(session.id);
151391
152341
  }
@@ -151440,6 +152390,7 @@ function createApiDeps(deps) {
151440
152390
  handleGetOutput: (id, res) => deps.sessionHandlers.handleGetOutput(id, res),
151441
152391
  handleSendInput: (id, req, res) => deps.sessionHandlers.handleSendInput(id, req, res),
151442
152392
  handleSendAnswer: (id, req, res) => deps.sessionHandlers.handleSendAnswer(id, req, res),
152393
+ handlePromptAnswer: (id, req, res) => deps.sessionHandlers.handlePromptAnswer(id, req, res),
151443
152394
  handlePermissionAnswer: (id, req, res) => deps.sessionHandlers.handlePermissionAnswer(id, req, res),
151444
152395
  handleCancel: (id, res) => deps.sessionHandlers.handleCancel(id, res),
151445
152396
  handleStopSession: (id, res) => deps.sessionHandlers.handleStopSession(id, res),
@@ -151498,6 +152449,9 @@ function createApiDeps(deps) {
151498
152449
  return;
151499
152450
  }
151500
152451
  deps.addSessionSubscriber(msg.sessionId, ws2);
152452
+ if (deps.promptRegistry) {
152453
+ ws2.send(JSON.stringify(deps.promptRegistry.snapshot(msg.sessionId)));
152454
+ }
151501
152455
  if (deps.ptyManager.hasSession(msg.sessionId)) {
151502
152456
  const lines = await deps.ptyManager.getOutputLines(msg.sessionId, REPLAY_MAX_LINES);
151503
152457
  const userMessages = deps.ptyManager.getInputHistory(msg.sessionId);
@@ -153727,16 +154681,16 @@ function compareValues(a, b2) {
153727
154681
  if (typeof a === "number" && typeof b2 === "number") return a - b2;
153728
154682
  return String(a).localeCompare(String(b2));
153729
154683
  }
153730
- function makeComparator(key, order) {
153731
- const dir = order === "asc" ? 1 : -1;
154684
+ function makeComparator(key, order2) {
154685
+ const dir = order2 === "asc" ? 1 : -1;
153732
154686
  return (a, b2) => {
153733
154687
  const cmp = compareValues(getSortValue(a, key), getSortValue(b2, key)) * dir;
153734
154688
  if (cmp !== 0) return cmp;
153735
154689
  return a.id.localeCompare(b2.id);
153736
154690
  };
153737
154691
  }
153738
- function findCursorBoundary(sorted, cursor, key, order) {
153739
- const dir = order === "asc" ? 1 : -1;
154692
+ function findCursorBoundary(sorted, cursor, key, order2) {
154693
+ const dir = order2 === "asc" ? 1 : -1;
153740
154694
  for (let i = 0; i < sorted.length; i++) {
153741
154695
  const item = sorted[i];
153742
154696
  const cmp = compareValues(getSortValue(item, key), cursor.k) * dir;
@@ -154312,6 +155266,7 @@ var StreamerServer = class {
154312
155266
  // repaint of the same gate doesn't re-broadcast on every tick. Cleared
154313
155267
  // alongside pendingPermission.
154314
155268
  pendingPermissionKey = /* @__PURE__ */ new Map();
155269
+ promptRegistry;
154315
155270
  // Scanner lifecycle, freshness state and the cache↔disk reconcile.
154316
155271
  scannerManager;
154317
155272
  // Binds a live session to the JSONL/rollout its provider writes.
@@ -154564,6 +155519,20 @@ var StreamerServer = class {
154564
155519
  this.browserCors = config2.browserCors ?? loadBrowserCors();
154565
155520
  this.sessionStore = new SessionStore();
154566
155521
  this.wsHub = new WSHub();
155522
+ this.promptRegistry = new PromptRegistry({
155523
+ emit: (event) => this.wsHub.broadcastToClients(this.sessionSubscribers.get(event.sessionId) ?? [], event),
155524
+ onExpire: (prompt) => clearExpiredPendingPrompt(
155525
+ {
155526
+ pendingPermission: this.pendingPermission,
155527
+ pendingPermissionKey: this.pendingPermissionKey,
155528
+ pendingQuestions: this.pendingQuestions,
155529
+ pendingQuestionKey: this.pendingQuestionKey,
155530
+ sessionSubscribers: this.sessionSubscribers,
155531
+ wsHub: this.wsHub
155532
+ },
155533
+ prompt
155534
+ )
155535
+ });
154567
155536
  this.fileWatcher = new ConversationWatcher(
154568
155537
  createConversationWatcherEvents({
154569
155538
  sessionFileMap: this.sessionFileMap,
@@ -154607,6 +155576,7 @@ var StreamerServer = class {
154607
155576
  pendingQuestionKey: this.pendingQuestionKey,
154608
155577
  pendingPermission: this.pendingPermission,
154609
155578
  pendingPermissionKey: this.pendingPermissionKey,
155579
+ promptRegistry: this.promptRegistry,
154610
155580
  contendedSessions: this.contendedSessions,
154611
155581
  // Thunks, not values: sessionHandlers is constructed below, the
154612
155582
  // registry repo and the push notifiers are bound during listen(), and
@@ -154697,6 +155667,7 @@ var StreamerServer = class {
154697
155667
  sessionStatusBus: this.sessionStatusBus,
154698
155668
  sessionFileMap: this.sessionFileMap,
154699
155669
  pendingQuestions: this.pendingQuestions,
155670
+ promptRegistry: this.promptRegistry,
154700
155671
  pendingQuestionKey: this.pendingQuestionKey,
154701
155672
  pendingPermission: this.pendingPermission,
154702
155673
  pendingPermissionKey: this.pendingPermissionKey,
@@ -154797,6 +155768,7 @@ var StreamerServer = class {
154797
155768
  terminalSeq: this.terminalSeq,
154798
155769
  pendingPermission: this.pendingPermission,
154799
155770
  pendingQuestions: this.pendingQuestions,
155771
+ promptRegistry: this.promptRegistry,
154800
155772
  agentClient,
154801
155773
  conversationWriter,
154802
155774
  agentConfig
@@ -155577,6 +156549,7 @@ var StreamerServer = class {
155577
156549
  if (!this.ptyManager.isRemote()) this.ptyManager.dispose();
155578
156550
  this.fileWatcher.dispose();
155579
156551
  this.externalTails.clear();
156552
+ this.promptRegistry.dispose();
155580
156553
  this.wsHub.dispose();
155581
156554
  this.pairTokens.dispose();
155582
156555
  this.liveActivityRenewal?.stop();
@@ -156165,7 +157138,7 @@ var StreamerServer = class {
156165
157138
  for (const p2 of pending) {
156166
157139
  if (contended || foreignVsPty(p2.questions)) continue;
156167
157140
  const origin = priorPtyKey !== null && questionContentKey(p2.questions) === priorPtyKey ? "pty" : "jsonl";
156168
- this.pendingQuestions.set(sessionId, { ...p2, origin });
157141
+ this.sessionHandlers.handleJsonlQuestion(sessionId, p2.toolUseId, p2.questions, origin);
156169
157142
  const t = setTimeout(() => {
156170
157143
  if (this.pendingQuestions.get(sessionId)?.toolUseId === p2.toolUseId) {
156171
157144
  this.cancelPendingQuestion(sessionId);
@@ -156191,6 +157164,10 @@ var StreamerServer = class {
156191
157164
  if (!pq) return;
156192
157165
  this.pendingQuestions.delete(sessionId);
156193
157166
  this.pendingQuestionKey.delete(sessionId);
157167
+ const prompt = this.promptRegistry.get(pq.promptId);
157168
+ if (prompt?.state === "open" || prompt?.state === "updated") {
157169
+ this.promptRegistry.transition(pq.promptId, "cancelled", "provider_closed");
157170
+ }
156194
157171
  this.wsHub.broadcastToClients(this.sessionSubscribers.get(sessionId) ?? [], {
156195
157172
  type: "question_cancelled",
156196
157173
  sessionId,
@@ -156526,7 +157503,7 @@ function isBrewInstall(scriptPath = process.argv[1] ?? "") {
156526
157503
  }
156527
157504
 
156528
157505
  // src/updater/download.ts
156529
- var import_node_crypto5 = require("crypto");
157506
+ var import_node_crypto6 = require("crypto");
156530
157507
  var import_node_fs10 = require("fs");
156531
157508
  var import_node_path11 = require("path");
156532
157509
  var import_promises17 = require("stream/promises");
@@ -156593,7 +157570,7 @@ async function downloadAndVerify(opts) {
156593
157570
  if (!res.ok || !res.body) {
156594
157571
  throw new Error(`Failed to download ${artifact.filename}: ${res.status} ${res.statusText}`);
156595
157572
  }
156596
- const hash2 = (0, import_node_crypto5.createHash)("sha256");
157573
+ const hash2 = (0, import_node_crypto6.createHash)("sha256");
156597
157574
  const out = (0, import_node_fs10.createWriteStream)(targetPath);
156598
157575
  let bytes = 0;
156599
157576
  const measured = new TransformStream({
@@ -156898,7 +157875,7 @@ var import_node_path18 = require("path");
156898
157875
  var import_path38 = __toESM(require("path"), 1);
156899
157876
  var import_node_fs14 = __toESM(require("fs"), 1);
156900
157877
  var import_node_assert = __toESM(require("assert"), 1);
156901
- var import_node_crypto6 = require("crypto");
157878
+ var import_node_crypto7 = require("crypto");
156902
157879
  var import_node_fs15 = __toESM(require("fs"), 1);
156903
157880
  var import_node_path19 = __toESM(require("path"), 1);
156904
157881
  var import_fs43 = __toESM(require("fs"), 1);
@@ -159313,7 +160290,7 @@ var Te = fo === "win32";
159313
160290
  var uo = 1024;
159314
160291
  var mo = (s3, t) => {
159315
160292
  if (!Te) return import_node_fs15.default.unlink(s3, t);
159316
- let e = s3 + ".DELETE." + (0, import_node_crypto6.randomBytes)(16).toString("hex");
160293
+ let e = s3 + ".DELETE." + (0, import_node_crypto7.randomBytes)(16).toString("hex");
159317
160294
  import_node_fs15.default.rename(s3, e, (i) => {
159318
160295
  if (i) return t(i);
159319
160296
  import_node_fs15.default.unlink(e, t);
@@ -159321,7 +160298,7 @@ var mo = (s3, t) => {
159321
160298
  };
159322
160299
  var po = (s3) => {
159323
160300
  if (!Te) return import_node_fs15.default.unlinkSync(s3);
159324
- let t = s3 + ".DELETE." + (0, import_node_crypto6.randomBytes)(16).toString("hex");
160301
+ let t = s3 + ".DELETE." + (0, import_node_crypto7.randomBytes)(16).toString("hex");
159325
160302
  import_node_fs15.default.renameSync(s3, t), import_node_fs15.default.unlinkSync(t);
159326
160303
  };
159327
160304
  var vr = (s3, t, e) => s3 !== void 0 && s3 === s3 >>> 0 ? s3 : t !== void 0 && t === t >>> 0 ? t : e;