@threadbase-sh/streamer 1.70.0 → 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/api/handlers/sessions.handlers.d.ts +3 -17
- package/dist/api/handlers/sessions.handlers.d.ts.map +1 -1
- package/dist/cli.cjs +97 -23
- package/dist/cli.cjs.map +1 -1
- package/dist/index.cjs +97 -23
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +97 -23
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/services/prompts/promptRegistry.d.ts +34 -1
- package/dist/services/prompts/promptRegistry.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -9787,7 +9787,7 @@ var PromptRegistry = class {
|
|
|
9787
9787
|
this.maxRecordsPerSession = options.maxRecordsPerSession ?? PROMPT_MAX_RECORDS_PER_SESSION;
|
|
9788
9788
|
}
|
|
9789
9789
|
open(draft, adapter, promptId = this.createId()) {
|
|
9790
|
-
this.
|
|
9790
|
+
this.sweepExpired(draft.sessionId);
|
|
9791
9791
|
const held = this.byId.get(promptId);
|
|
9792
9792
|
if (held && (held.prompt.state === "open" || held.prompt.state === "updated")) {
|
|
9793
9793
|
throw new Error(`Prompt id already exists: ${promptId}`);
|
|
@@ -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() ?? []) {
|
|
@@ -9889,17 +9941,17 @@ var PromptRegistry = class {
|
|
|
9889
9941
|
get(promptId) {
|
|
9890
9942
|
const entry = this.byId.get(promptId);
|
|
9891
9943
|
if (!entry) return null;
|
|
9892
|
-
this.
|
|
9944
|
+
this.sweepExpired(entry.prompt.sessionId);
|
|
9893
9945
|
return this.byId.has(promptId) ? copyPrompt(entry.prompt) : null;
|
|
9894
9946
|
}
|
|
9895
9947
|
hasActionable(sessionId) {
|
|
9896
|
-
this.
|
|
9948
|
+
this.sweepExpired(sessionId);
|
|
9897
9949
|
return [...this.bySession.get(sessionId)?.values() ?? []].some(
|
|
9898
9950
|
(entry) => entry.prompt.state === "open" || entry.prompt.state === "updated"
|
|
9899
9951
|
);
|
|
9900
9952
|
}
|
|
9901
9953
|
snapshot(sessionId) {
|
|
9902
|
-
this.
|
|
9954
|
+
this.sweepExpired(sessionId);
|
|
9903
9955
|
return {
|
|
9904
9956
|
type: "prompt_snapshot",
|
|
9905
9957
|
schemaVersion: PROMPT_SCHEMA_VERSION,
|
|
@@ -9914,7 +9966,7 @@ var PromptRegistry = class {
|
|
|
9914
9966
|
for (const entry of this.byId.values()) this.clearExpiration(entry);
|
|
9915
9967
|
}
|
|
9916
9968
|
answer(sessionId, answer) {
|
|
9917
|
-
this.
|
|
9969
|
+
this.sweepExpired(sessionId);
|
|
9918
9970
|
const entry = this.byId.get(answer.promptId);
|
|
9919
9971
|
if (!entry || entry.prompt.sessionId !== sessionId) {
|
|
9920
9972
|
return Promise.resolve({ ok: false, code: "prompt_not_found" });
|
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -10044,7 +10108,7 @@ var PromptRegistry = class {
|
|
|
10044
10108
|
if (!entry) throw new Error(`Unknown prompt: ${promptId}`);
|
|
10045
10109
|
return entry;
|
|
10046
10110
|
}
|
|
10047
|
-
|
|
10111
|
+
sweepExpired(sessionId) {
|
|
10048
10112
|
const now = this.now();
|
|
10049
10113
|
const session = this.bySession.get(sessionId);
|
|
10050
10114
|
if (!session) return;
|
|
@@ -11068,19 +11132,26 @@ var SessionHandlers = class {
|
|
|
11068
11132
|
json(res, 400, { error: "Missing input field" });
|
|
11069
11133
|
return;
|
|
11070
11134
|
}
|
|
11071
|
-
this.promptRegistry.
|
|
11135
|
+
this.promptRegistry.sweepExpired(sessionId);
|
|
11072
11136
|
const openPrompt = this.pendingPermission.has(sessionId) ? "permission" : this.pendingQuestions.has(sessionId) ? "question" : null;
|
|
11073
11137
|
if (openPrompt) {
|
|
11074
|
-
this.
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11138
|
+
const pendingGate = openPrompt === "permission" ? this.pendingPermission.get(sessionId) : void 0;
|
|
11139
|
+
const promptState = pendingGate?.promptId !== void 0 && this.promptRegistry.get(pendingGate.promptId)?.state === "resolved" ? "answered" : "open";
|
|
11140
|
+
this.log.info(
|
|
11141
|
+
`[input.prompt_pending] ${sessionId.slice(0, 8)} kind=${openPrompt} state=${promptState}`,
|
|
11142
|
+
{
|
|
11143
|
+
event: "input.prompt_pending",
|
|
11144
|
+
sessionId,
|
|
11145
|
+
promptKind: openPrompt,
|
|
11146
|
+
promptState
|
|
11147
|
+
}
|
|
11148
|
+
);
|
|
11079
11149
|
json(res, 409, {
|
|
11080
11150
|
ok: false,
|
|
11081
11151
|
reason: "prompt_pending",
|
|
11082
11152
|
promptKind: openPrompt,
|
|
11083
|
-
|
|
11153
|
+
promptState,
|
|
11154
|
+
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"
|
|
11084
11155
|
});
|
|
11085
11156
|
return;
|
|
11086
11157
|
}
|
|
@@ -11182,10 +11253,7 @@ var SessionHandlers = class {
|
|
|
11182
11253
|
if (gate === null) {
|
|
11183
11254
|
const prior2 = this.pendingPermission.get(sessionId);
|
|
11184
11255
|
if (!prior2) return;
|
|
11185
|
-
|
|
11186
|
-
if (prompt2?.state === "open" || prompt2?.state === "updated") {
|
|
11187
|
-
this.promptRegistry.transition(prompt2.promptId, "cancelled", "provider_closed");
|
|
11188
|
-
}
|
|
11256
|
+
if (prior2.promptId) this.promptRegistry.providerClosed(prior2.promptId, "provider_closed");
|
|
11189
11257
|
this.pendingPermission.delete(sessionId);
|
|
11190
11258
|
this.pendingPermissionKey.delete(sessionId);
|
|
11191
11259
|
this.broadcastToSession(sessionId, { type: "permission_cancelled", sessionId });
|
|
@@ -11410,7 +11478,13 @@ var SessionHandlers = class {
|
|
|
11410
11478
|
return;
|
|
11411
11479
|
}
|
|
11412
11480
|
try {
|
|
11413
|
-
this.
|
|
11481
|
+
this.promptRegistry.whileAnswering(
|
|
11482
|
+
gate.promptId ?? "",
|
|
11483
|
+
() => this.ptyManager.sendKeys(
|
|
11484
|
+
sessionId,
|
|
11485
|
+
option.answerKeys ?? permissionAnswerKeys(option.index)
|
|
11486
|
+
)
|
|
11487
|
+
);
|
|
11414
11488
|
} catch (err) {
|
|
11415
11489
|
const message = err instanceof Error ? err.message : "Failed to send answer";
|
|
11416
11490
|
json(res, 400, { ok: false, reason: message });
|