@threadbase-sh/streamer 1.70.1 → 1.70.2

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/index.cjs CHANGED
@@ -9877,6 +9877,58 @@ var PromptRegistry = class {
9877
9877
  this.publish(entry);
9878
9878
  return copyPrompt(entry.prompt);
9879
9879
  }
9880
+ /**
9881
+ * The provider's prompt left the screen.
9882
+ *
9883
+ * Distinct from `transition` because the detector cannot tell a teardown it
9884
+ * observed on its own from one OUR OWN write caused. On a screen-scraped gate
9885
+ * the answer keys remove the box, and pty-manager's sendKeys fires the close
9886
+ * synchronously from inside the write — so the close always arrives before
9887
+ * the answer that caused it has settled, and reporting it as a cancel failed
9888
+ * every answer whose bytes had already landed (#720).
9889
+ *
9890
+ * While an answer is writing, the close is deferred and the answer decides:
9891
+ * `resolved` if the write landed, the deferred close if it did not. Success
9892
+ * is the adapter's own result, never the prompt's absence from the screen —
9893
+ * a prompt that does not vanish when answered (a multi-select form) simply
9894
+ * never reaches here, and is settled by the same adapter result (#721).
9895
+ *
9896
+ * ONLY this path defers. `replaced` from a different gate taking the screen,
9897
+ * `unavailable` from invalidateSession and `expired` all still go through
9898
+ * `transition` and still win, because none of them was caused by our write.
9899
+ */
9900
+ providerClosed(promptId, reason) {
9901
+ const entry = this.byId.get(promptId);
9902
+ if (!entry) return null;
9903
+ if (entry.prompt.state !== "open" && entry.prompt.state !== "updated") return null;
9904
+ if (entry.answering) {
9905
+ entry.deferredClose = reason;
9906
+ return null;
9907
+ }
9908
+ return this.transition(promptId, "cancelled", reason);
9909
+ }
9910
+ /**
9911
+ * Run a provider write that may close the prompt as a side effect, so the
9912
+ * close it causes is deferred rather than applied. For callers outside this
9913
+ * class that write without going through `answer()` — the legacy permission
9914
+ * route. `performAnswer` manages the same two fields directly, because it
9915
+ * needs the deferred value in its own control flow.
9916
+ *
9917
+ * The write's own failure needs no unwinding here: pty-manager fires the
9918
+ * close as the last statement of a successful sendKeys, so a throw means no
9919
+ * close was ever deferred and the record is still open for the caller.
9920
+ */
9921
+ whileAnswering(promptId, write) {
9922
+ const entry = this.byId.get(promptId);
9923
+ if (!entry) return write();
9924
+ entry.answering = true;
9925
+ try {
9926
+ return write();
9927
+ } finally {
9928
+ entry.answering = false;
9929
+ entry.deferredClose = void 0;
9930
+ }
9931
+ }
9880
9932
  invalidateSession(sessionId, reason = "session_ended") {
9881
9933
  const transitioned = [];
9882
9934
  for (const entry of this.bySession.get(sessionId)?.values() ?? []) {
@@ -9954,21 +10006,33 @@ var PromptRegistry = class {
9954
10006
  const responseError = this.validateResponses(prompt, answer);
9955
10007
  if (responseError) return { ok: false, code: responseError };
9956
10008
  if (!entry.adapter) return { ok: false, code: "prompt_unavailable" };
10009
+ entry.answering = true;
9957
10010
  let adapterResult;
10011
+ let threw = false;
9958
10012
  try {
9959
10013
  adapterResult = await entry.adapter({ prompt: copyPrompt(prompt), answer });
9960
10014
  } catch {
9961
- return { ok: false, code: "provider_error" };
10015
+ threw = true;
10016
+ adapterResult = { ok: false, code: "provider_error" };
10017
+ } finally {
10018
+ entry.answering = false;
9962
10019
  }
10020
+ const deferred = entry.deferredClose;
10021
+ entry.deferredClose = void 0;
10022
+ const settle = (outcome) => {
10023
+ if (deferred) this.providerClosed(prompt.promptId, deferred);
10024
+ return outcome;
10025
+ };
10026
+ if (threw) return settle({ ok: false, code: "provider_error" });
9963
10027
  if (entry.prompt.state !== "open" && entry.prompt.state !== "updated") {
9964
- return { ok: false, code: terminalError(entry.prompt.state) };
10028
+ return settle({ ok: false, code: terminalError(entry.prompt.state) });
9965
10029
  }
9966
10030
  if (entry.prompt.revision !== answer.revision) {
9967
- return {
10031
+ return settle({
9968
10032
  ok: false,
9969
10033
  code: "prompt_revision_mismatch",
9970
10034
  currentRevision: entry.prompt.revision
9971
- };
10035
+ });
9972
10036
  }
9973
10037
  if (!adapterResult.ok) {
9974
10038
  if (adapterResult.terminal) {
@@ -9978,7 +10042,7 @@ var PromptRegistry = class {
9978
10042
  adapterResult.terminal.reason
9979
10043
  );
9980
10044
  }
9981
- return { ok: false, code: adapterResult.code };
10045
+ return settle({ ok: false, code: adapterResult.code });
9982
10046
  }
9983
10047
  return { ok: true, prompt: this.transition(prompt.promptId, "resolved", "answered") };
9984
10048
  }
@@ -11189,10 +11253,7 @@ var SessionHandlers = class {
11189
11253
  if (gate === null) {
11190
11254
  const prior2 = this.pendingPermission.get(sessionId);
11191
11255
  if (!prior2) return;
11192
- const prompt2 = prior2.promptId ? this.promptRegistry.get(prior2.promptId) : null;
11193
- if (prompt2?.state === "open" || prompt2?.state === "updated") {
11194
- this.promptRegistry.transition(prompt2.promptId, "cancelled", "provider_closed");
11195
- }
11256
+ if (prior2.promptId) this.promptRegistry.providerClosed(prior2.promptId, "provider_closed");
11196
11257
  this.pendingPermission.delete(sessionId);
11197
11258
  this.pendingPermissionKey.delete(sessionId);
11198
11259
  this.broadcastToSession(sessionId, { type: "permission_cancelled", sessionId });
@@ -11417,7 +11478,13 @@ var SessionHandlers = class {
11417
11478
  return;
11418
11479
  }
11419
11480
  try {
11420
- this.ptyManager.sendKeys(sessionId, option.answerKeys ?? permissionAnswerKeys(option.index));
11481
+ this.promptRegistry.whileAnswering(
11482
+ gate.promptId ?? "",
11483
+ () => this.ptyManager.sendKeys(
11484
+ sessionId,
11485
+ option.answerKeys ?? permissionAnswerKeys(option.index)
11486
+ )
11487
+ );
11421
11488
  } catch (err) {
11422
11489
  const message = err instanceof Error ? err.message : "Failed to send answer";
11423
11490
  json(res, 400, { ok: false, reason: message });