@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.js
CHANGED
|
@@ -9741,7 +9741,7 @@ var PromptRegistry = class {
|
|
|
9741
9741
|
this.maxRecordsPerSession = options.maxRecordsPerSession ?? PROMPT_MAX_RECORDS_PER_SESSION;
|
|
9742
9742
|
}
|
|
9743
9743
|
open(draft, adapter, promptId = this.createId()) {
|
|
9744
|
-
this.
|
|
9744
|
+
this.sweepExpired(draft.sessionId);
|
|
9745
9745
|
const held = this.byId.get(promptId);
|
|
9746
9746
|
if (held && (held.prompt.state === "open" || held.prompt.state === "updated")) {
|
|
9747
9747
|
throw new Error(`Prompt id already exists: ${promptId}`);
|
|
@@ -9831,6 +9831,58 @@ var PromptRegistry = class {
|
|
|
9831
9831
|
this.publish(entry);
|
|
9832
9832
|
return copyPrompt(entry.prompt);
|
|
9833
9833
|
}
|
|
9834
|
+
/**
|
|
9835
|
+
* The provider's prompt left the screen.
|
|
9836
|
+
*
|
|
9837
|
+
* Distinct from `transition` because the detector cannot tell a teardown it
|
|
9838
|
+
* observed on its own from one OUR OWN write caused. On a screen-scraped gate
|
|
9839
|
+
* the answer keys remove the box, and pty-manager's sendKeys fires the close
|
|
9840
|
+
* synchronously from inside the write — so the close always arrives before
|
|
9841
|
+
* the answer that caused it has settled, and reporting it as a cancel failed
|
|
9842
|
+
* every answer whose bytes had already landed (#720).
|
|
9843
|
+
*
|
|
9844
|
+
* While an answer is writing, the close is deferred and the answer decides:
|
|
9845
|
+
* `resolved` if the write landed, the deferred close if it did not. Success
|
|
9846
|
+
* is the adapter's own result, never the prompt's absence from the screen —
|
|
9847
|
+
* a prompt that does not vanish when answered (a multi-select form) simply
|
|
9848
|
+
* never reaches here, and is settled by the same adapter result (#721).
|
|
9849
|
+
*
|
|
9850
|
+
* ONLY this path defers. `replaced` from a different gate taking the screen,
|
|
9851
|
+
* `unavailable` from invalidateSession and `expired` all still go through
|
|
9852
|
+
* `transition` and still win, because none of them was caused by our write.
|
|
9853
|
+
*/
|
|
9854
|
+
providerClosed(promptId, reason) {
|
|
9855
|
+
const entry = this.byId.get(promptId);
|
|
9856
|
+
if (!entry) return null;
|
|
9857
|
+
if (entry.prompt.state !== "open" && entry.prompt.state !== "updated") return null;
|
|
9858
|
+
if (entry.answering) {
|
|
9859
|
+
entry.deferredClose = reason;
|
|
9860
|
+
return null;
|
|
9861
|
+
}
|
|
9862
|
+
return this.transition(promptId, "cancelled", reason);
|
|
9863
|
+
}
|
|
9864
|
+
/**
|
|
9865
|
+
* Run a provider write that may close the prompt as a side effect, so the
|
|
9866
|
+
* close it causes is deferred rather than applied. For callers outside this
|
|
9867
|
+
* class that write without going through `answer()` — the legacy permission
|
|
9868
|
+
* route. `performAnswer` manages the same two fields directly, because it
|
|
9869
|
+
* needs the deferred value in its own control flow.
|
|
9870
|
+
*
|
|
9871
|
+
* The write's own failure needs no unwinding here: pty-manager fires the
|
|
9872
|
+
* close as the last statement of a successful sendKeys, so a throw means no
|
|
9873
|
+
* close was ever deferred and the record is still open for the caller.
|
|
9874
|
+
*/
|
|
9875
|
+
whileAnswering(promptId, write) {
|
|
9876
|
+
const entry = this.byId.get(promptId);
|
|
9877
|
+
if (!entry) return write();
|
|
9878
|
+
entry.answering = true;
|
|
9879
|
+
try {
|
|
9880
|
+
return write();
|
|
9881
|
+
} finally {
|
|
9882
|
+
entry.answering = false;
|
|
9883
|
+
entry.deferredClose = void 0;
|
|
9884
|
+
}
|
|
9885
|
+
}
|
|
9834
9886
|
invalidateSession(sessionId, reason = "session_ended") {
|
|
9835
9887
|
const transitioned = [];
|
|
9836
9888
|
for (const entry of this.bySession.get(sessionId)?.values() ?? []) {
|
|
@@ -9843,17 +9895,17 @@ var PromptRegistry = class {
|
|
|
9843
9895
|
get(promptId) {
|
|
9844
9896
|
const entry = this.byId.get(promptId);
|
|
9845
9897
|
if (!entry) return null;
|
|
9846
|
-
this.
|
|
9898
|
+
this.sweepExpired(entry.prompt.sessionId);
|
|
9847
9899
|
return this.byId.has(promptId) ? copyPrompt(entry.prompt) : null;
|
|
9848
9900
|
}
|
|
9849
9901
|
hasActionable(sessionId) {
|
|
9850
|
-
this.
|
|
9902
|
+
this.sweepExpired(sessionId);
|
|
9851
9903
|
return [...this.bySession.get(sessionId)?.values() ?? []].some(
|
|
9852
9904
|
(entry) => entry.prompt.state === "open" || entry.prompt.state === "updated"
|
|
9853
9905
|
);
|
|
9854
9906
|
}
|
|
9855
9907
|
snapshot(sessionId) {
|
|
9856
|
-
this.
|
|
9908
|
+
this.sweepExpired(sessionId);
|
|
9857
9909
|
return {
|
|
9858
9910
|
type: "prompt_snapshot",
|
|
9859
9911
|
schemaVersion: PROMPT_SCHEMA_VERSION,
|
|
@@ -9868,7 +9920,7 @@ var PromptRegistry = class {
|
|
|
9868
9920
|
for (const entry of this.byId.values()) this.clearExpiration(entry);
|
|
9869
9921
|
}
|
|
9870
9922
|
answer(sessionId, answer) {
|
|
9871
|
-
this.
|
|
9923
|
+
this.sweepExpired(sessionId);
|
|
9872
9924
|
const entry = this.byId.get(answer.promptId);
|
|
9873
9925
|
if (!entry || entry.prompt.sessionId !== sessionId) {
|
|
9874
9926
|
return Promise.resolve({ ok: false, code: "prompt_not_found" });
|
|
@@ -9908,21 +9960,33 @@ var PromptRegistry = class {
|
|
|
9908
9960
|
const responseError = this.validateResponses(prompt, answer);
|
|
9909
9961
|
if (responseError) return { ok: false, code: responseError };
|
|
9910
9962
|
if (!entry.adapter) return { ok: false, code: "prompt_unavailable" };
|
|
9963
|
+
entry.answering = true;
|
|
9911
9964
|
let adapterResult;
|
|
9965
|
+
let threw = false;
|
|
9912
9966
|
try {
|
|
9913
9967
|
adapterResult = await entry.adapter({ prompt: copyPrompt(prompt), answer });
|
|
9914
9968
|
} catch {
|
|
9915
|
-
|
|
9969
|
+
threw = true;
|
|
9970
|
+
adapterResult = { ok: false, code: "provider_error" };
|
|
9971
|
+
} finally {
|
|
9972
|
+
entry.answering = false;
|
|
9916
9973
|
}
|
|
9974
|
+
const deferred = entry.deferredClose;
|
|
9975
|
+
entry.deferredClose = void 0;
|
|
9976
|
+
const settle = (outcome) => {
|
|
9977
|
+
if (deferred) this.providerClosed(prompt.promptId, deferred);
|
|
9978
|
+
return outcome;
|
|
9979
|
+
};
|
|
9980
|
+
if (threw) return settle({ ok: false, code: "provider_error" });
|
|
9917
9981
|
if (entry.prompt.state !== "open" && entry.prompt.state !== "updated") {
|
|
9918
|
-
return { ok: false, code: terminalError(entry.prompt.state) };
|
|
9982
|
+
return settle({ ok: false, code: terminalError(entry.prompt.state) });
|
|
9919
9983
|
}
|
|
9920
9984
|
if (entry.prompt.revision !== answer.revision) {
|
|
9921
|
-
return {
|
|
9985
|
+
return settle({
|
|
9922
9986
|
ok: false,
|
|
9923
9987
|
code: "prompt_revision_mismatch",
|
|
9924
9988
|
currentRevision: entry.prompt.revision
|
|
9925
|
-
};
|
|
9989
|
+
});
|
|
9926
9990
|
}
|
|
9927
9991
|
if (!adapterResult.ok) {
|
|
9928
9992
|
if (adapterResult.terminal) {
|
|
@@ -9932,7 +9996,7 @@ var PromptRegistry = class {
|
|
|
9932
9996
|
adapterResult.terminal.reason
|
|
9933
9997
|
);
|
|
9934
9998
|
}
|
|
9935
|
-
return { ok: false, code: adapterResult.code };
|
|
9999
|
+
return settle({ ok: false, code: adapterResult.code });
|
|
9936
10000
|
}
|
|
9937
10001
|
return { ok: true, prompt: this.transition(prompt.promptId, "resolved", "answered") };
|
|
9938
10002
|
}
|
|
@@ -9998,7 +10062,7 @@ var PromptRegistry = class {
|
|
|
9998
10062
|
if (!entry) throw new Error(`Unknown prompt: ${promptId}`);
|
|
9999
10063
|
return entry;
|
|
10000
10064
|
}
|
|
10001
|
-
|
|
10065
|
+
sweepExpired(sessionId) {
|
|
10002
10066
|
const now = this.now();
|
|
10003
10067
|
const session = this.bySession.get(sessionId);
|
|
10004
10068
|
if (!session) return;
|
|
@@ -11022,19 +11086,26 @@ var SessionHandlers = class {
|
|
|
11022
11086
|
json(res, 400, { error: "Missing input field" });
|
|
11023
11087
|
return;
|
|
11024
11088
|
}
|
|
11025
|
-
this.promptRegistry.
|
|
11089
|
+
this.promptRegistry.sweepExpired(sessionId);
|
|
11026
11090
|
const openPrompt = this.pendingPermission.has(sessionId) ? "permission" : this.pendingQuestions.has(sessionId) ? "question" : null;
|
|
11027
11091
|
if (openPrompt) {
|
|
11028
|
-
this.
|
|
11029
|
-
|
|
11030
|
-
|
|
11031
|
-
|
|
11032
|
-
|
|
11092
|
+
const pendingGate = openPrompt === "permission" ? this.pendingPermission.get(sessionId) : void 0;
|
|
11093
|
+
const promptState = pendingGate?.promptId !== void 0 && this.promptRegistry.get(pendingGate.promptId)?.state === "resolved" ? "answered" : "open";
|
|
11094
|
+
this.log.info(
|
|
11095
|
+
`[input.prompt_pending] ${sessionId.slice(0, 8)} kind=${openPrompt} state=${promptState}`,
|
|
11096
|
+
{
|
|
11097
|
+
event: "input.prompt_pending",
|
|
11098
|
+
sessionId,
|
|
11099
|
+
promptKind: openPrompt,
|
|
11100
|
+
promptState
|
|
11101
|
+
}
|
|
11102
|
+
);
|
|
11033
11103
|
json(res, 409, {
|
|
11034
11104
|
ok: false,
|
|
11035
11105
|
reason: "prompt_pending",
|
|
11036
11106
|
promptKind: openPrompt,
|
|
11037
|
-
|
|
11107
|
+
promptState,
|
|
11108
|
+
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"
|
|
11038
11109
|
});
|
|
11039
11110
|
return;
|
|
11040
11111
|
}
|
|
@@ -11136,10 +11207,7 @@ var SessionHandlers = class {
|
|
|
11136
11207
|
if (gate === null) {
|
|
11137
11208
|
const prior2 = this.pendingPermission.get(sessionId);
|
|
11138
11209
|
if (!prior2) return;
|
|
11139
|
-
|
|
11140
|
-
if (prompt2?.state === "open" || prompt2?.state === "updated") {
|
|
11141
|
-
this.promptRegistry.transition(prompt2.promptId, "cancelled", "provider_closed");
|
|
11142
|
-
}
|
|
11210
|
+
if (prior2.promptId) this.promptRegistry.providerClosed(prior2.promptId, "provider_closed");
|
|
11143
11211
|
this.pendingPermission.delete(sessionId);
|
|
11144
11212
|
this.pendingPermissionKey.delete(sessionId);
|
|
11145
11213
|
this.broadcastToSession(sessionId, { type: "permission_cancelled", sessionId });
|
|
@@ -11364,7 +11432,13 @@ var SessionHandlers = class {
|
|
|
11364
11432
|
return;
|
|
11365
11433
|
}
|
|
11366
11434
|
try {
|
|
11367
|
-
this.
|
|
11435
|
+
this.promptRegistry.whileAnswering(
|
|
11436
|
+
gate.promptId ?? "",
|
|
11437
|
+
() => this.ptyManager.sendKeys(
|
|
11438
|
+
sessionId,
|
|
11439
|
+
option.answerKeys ?? permissionAnswerKeys(option.index)
|
|
11440
|
+
)
|
|
11441
|
+
);
|
|
11368
11442
|
} catch (err) {
|
|
11369
11443
|
const message = err instanceof Error ? err.message : "Failed to send answer";
|
|
11370
11444
|
json(res, 400, { ok: false, reason: message });
|