@schlessera/brain-ui-server 0.4.0
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/LICENSE +21 -0
- package/README.md +65 -0
- package/dist/agent/backend.d.ts +30 -0
- package/dist/agent/backend.d.ts.map +1 -0
- package/dist/agent/backend.js +226 -0
- package/dist/agent/backend.js.map +1 -0
- package/dist/app.d.ts +27 -0
- package/dist/app.d.ts.map +1 -0
- package/dist/app.js +116 -0
- package/dist/app.js.map +1 -0
- package/dist/brain/client.d.ts +43 -0
- package/dist/brain/client.d.ts.map +1 -0
- package/dist/brain/client.js +154 -0
- package/dist/brain/client.js.map +1 -0
- package/dist/brain/types.d.ts +41 -0
- package/dist/brain/types.d.ts.map +1 -0
- package/dist/brain/types.js +2 -0
- package/dist/brain/types.js.map +1 -0
- package/dist/cron/scheduler.d.ts +9 -0
- package/dist/cron/scheduler.d.ts.map +1 -0
- package/dist/cron/scheduler.js +58 -0
- package/dist/cron/scheduler.js.map +1 -0
- package/dist/db/client.d.ts +10 -0
- package/dist/db/client.d.ts.map +1 -0
- package/dist/db/client.js +73 -0
- package/dist/db/client.js.map +1 -0
- package/dist/files/walker.d.ts +63 -0
- package/dist/files/walker.d.ts.map +1 -0
- package/dist/files/walker.js +416 -0
- package/dist/files/walker.js.map +1 -0
- package/dist/index.d.ts +18 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +59 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +246 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/passkeys.d.ts +36 -0
- package/dist/middleware/passkeys.d.ts.map +1 -0
- package/dist/middleware/passkeys.js +427 -0
- package/dist/middleware/passkeys.js.map +1 -0
- package/dist/middleware/tailscale.d.ts +31 -0
- package/dist/middleware/tailscale.d.ts.map +1 -0
- package/dist/middleware/tailscale.js +64 -0
- package/dist/middleware/tailscale.js.map +1 -0
- package/dist/render/template.d.ts +7 -0
- package/dist/render/template.d.ts.map +1 -0
- package/dist/render/template.js +112 -0
- package/dist/render/template.js.map +1 -0
- package/dist/routes/brain.d.ts +155 -0
- package/dist/routes/brain.d.ts.map +1 -0
- package/dist/routes/brain.js +250 -0
- package/dist/routes/brain.js.map +1 -0
- package/dist/routes/files.d.ts +89 -0
- package/dist/routes/files.d.ts.map +1 -0
- package/dist/routes/files.js +91 -0
- package/dist/routes/files.js.map +1 -0
- package/dist/routes/health.d.ts +37 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +27 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/providers.d.ts +33 -0
- package/dist/routes/providers.d.ts.map +1 -0
- package/dist/routes/providers.js +9 -0
- package/dist/routes/providers.js.map +1 -0
- package/dist/routes/render.d.ts +25 -0
- package/dist/routes/render.d.ts.map +1 -0
- package/dist/routes/render.js +47 -0
- package/dist/routes/render.js.map +1 -0
- package/dist/routes/sessions.d.ts +79 -0
- package/dist/routes/sessions.d.ts.map +1 -0
- package/dist/routes/sessions.js +37 -0
- package/dist/routes/sessions.js.map +1 -0
- package/dist/routes/voice.d.ts +92 -0
- package/dist/routes/voice.d.ts.map +1 -0
- package/dist/routes/voice.js +64 -0
- package/dist/routes/voice.js.map +1 -0
- package/dist/voice/deepgram-token.d.ts +5 -0
- package/dist/voice/deepgram-token.d.ts.map +1 -0
- package/dist/voice/deepgram-token.js +63 -0
- package/dist/voice/deepgram-token.js.map +1 -0
- package/dist/voice/keyterm-builder.d.ts +18 -0
- package/dist/voice/keyterm-builder.d.ts.map +1 -0
- package/dist/voice/keyterm-builder.js +391 -0
- package/dist/voice/keyterm-builder.js.map +1 -0
- package/dist/voice/speech-providers.d.ts +25 -0
- package/dist/voice/speech-providers.d.ts.map +1 -0
- package/dist/voice/speech-providers.js +104 -0
- package/dist/voice/speech-providers.js.map +1 -0
- package/dist/ws/attachments.d.ts +10 -0
- package/dist/ws/attachments.d.ts.map +1 -0
- package/dist/ws/attachments.js +63 -0
- package/dist/ws/attachments.js.map +1 -0
- package/dist/ws/bridge.d.ts +7 -0
- package/dist/ws/bridge.d.ts.map +1 -0
- package/dist/ws/bridge.js +86 -0
- package/dist/ws/bridge.js.map +1 -0
- package/dist/ws/clients.d.ts +20 -0
- package/dist/ws/clients.d.ts.map +1 -0
- package/dist/ws/clients.js +48 -0
- package/dist/ws/clients.js.map +1 -0
- package/dist/ws/connection.d.ts +8 -0
- package/dist/ws/connection.d.ts.map +1 -0
- package/dist/ws/connection.js +116 -0
- package/dist/ws/connection.js.map +1 -0
- package/dist/ws/dispatch.d.ts +5 -0
- package/dist/ws/dispatch.d.ts.map +1 -0
- package/dist/ws/dispatch.js +139 -0
- package/dist/ws/dispatch.js.map +1 -0
- package/dist/ws/frames.d.ts +17 -0
- package/dist/ws/frames.d.ts.map +1 -0
- package/dist/ws/frames.js +37 -0
- package/dist/ws/frames.js.map +1 -0
- package/dist/ws/handler.d.ts +34 -0
- package/dist/ws/handler.d.ts.map +1 -0
- package/dist/ws/handler.js +33 -0
- package/dist/ws/handler.js.map +1 -0
- package/dist/ws/history.d.ts +17 -0
- package/dist/ws/history.d.ts.map +1 -0
- package/dist/ws/history.js +45 -0
- package/dist/ws/history.js.map +1 -0
- package/dist/ws/host.d.ts +40 -0
- package/dist/ws/host.d.ts.map +1 -0
- package/dist/ws/host.js +49 -0
- package/dist/ws/host.js.map +1 -0
- package/dist/ws/routing.d.ts +12 -0
- package/dist/ws/routing.d.ts.map +1 -0
- package/dist/ws/routing.js +31 -0
- package/dist/ws/routing.js.map +1 -0
- package/dist/ws/run-session.d.ts +17 -0
- package/dist/ws/run-session.d.ts.map +1 -0
- package/dist/ws/run-session.js +160 -0
- package/dist/ws/run-session.js.map +1 -0
- package/dist/ws/session-catalog.d.ts +25 -0
- package/dist/ws/session-catalog.d.ts.map +1 -0
- package/dist/ws/session-catalog.js +45 -0
- package/dist/ws/session-catalog.js.map +1 -0
- package/dist/ws/shrink.d.ts +34 -0
- package/dist/ws/shrink.d.ts.map +1 -0
- package/dist/ws/shrink.js +91 -0
- package/dist/ws/shrink.js.map +1 -0
- package/dist/ws/turns.d.ts +77 -0
- package/dist/ws/turns.d.ts.map +1 -0
- package/dist/ws/turns.js +89 -0
- package/dist/ws/turns.js.map +1 -0
- package/migrations/001_initial.sql +29 -0
- package/migrations/002_session_provider.sql +1 -0
- package/migrations/003_session_backend.sql +5 -0
- package/migrations/004_drop_gemini_backend.sql +20 -0
- package/migrations/005_passkey_credentials.sql +18 -0
- package/package.json +72 -0
- package/src/agent/backend.ts +308 -0
- package/src/app.ts +159 -0
- package/src/brain/client.ts +186 -0
- package/src/brain/types.ts +44 -0
- package/src/cron/scheduler.ts +80 -0
- package/src/db/client.ts +85 -0
- package/src/files/walker.ts +418 -0
- package/src/index.ts +39 -0
- package/src/middleware/auth.ts +321 -0
- package/src/middleware/passkeys.ts +510 -0
- package/src/middleware/tailscale.ts +81 -0
- package/src/render/template.ts +124 -0
- package/src/routes/brain.ts +305 -0
- package/src/routes/files.ts +99 -0
- package/src/routes/health.ts +29 -0
- package/src/routes/providers.ts +9 -0
- package/src/routes/render.ts +68 -0
- package/src/routes/sessions.ts +49 -0
- package/src/routes/voice.ts +80 -0
- package/src/voice/deepgram-token.ts +80 -0
- package/src/voice/keyterm-builder.ts +426 -0
- package/src/voice/speech-providers.ts +111 -0
- package/src/ws/attachments.ts +86 -0
- package/src/ws/bridge.ts +139 -0
- package/src/ws/clients.ts +57 -0
- package/src/ws/connection.ts +132 -0
- package/src/ws/dispatch.ts +162 -0
- package/src/ws/frames.ts +42 -0
- package/src/ws/handler.ts +50 -0
- package/src/ws/history.ts +51 -0
- package/src/ws/host.ts +69 -0
- package/src/ws/routing.ts +43 -0
- package/src/ws/run-session.ts +192 -0
- package/src/ws/session-catalog.ts +97 -0
- package/src/ws/shrink.ts +95 -0
- package/src/ws/turns.ts +151 -0
package/src/ws/turns.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AgentBackend,
|
|
3
|
+
PermissionDecision,
|
|
4
|
+
AskUserResult,
|
|
5
|
+
LocationFix,
|
|
6
|
+
} from "@schlessera/brain-ui-sdk/server";
|
|
7
|
+
import type { ChatImageAttachment } from "@schlessera/brain-ui-sdk/protocol";
|
|
8
|
+
|
|
9
|
+
export interface QueuedFollowUp {
|
|
10
|
+
text: string;
|
|
11
|
+
attachments: ChatImageAttachment[];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* One RUNNING session slot. `abortController`/`timeoutHandle` belong to the
|
|
16
|
+
* turn currently executing; a queued follow-up runs as the next turn in the
|
|
17
|
+
* same slot (same sessionId) after the current one resolves.
|
|
18
|
+
*/
|
|
19
|
+
export interface RunningTurn {
|
|
20
|
+
sessionId: string | null; // null until session_info resolves it (new session)
|
|
21
|
+
/** Host-minted id of the CURRENT turn in this slot; re-minted per queued follow-up. */
|
|
22
|
+
turnId: string;
|
|
23
|
+
providerId: string | null;
|
|
24
|
+
backend: AgentBackend;
|
|
25
|
+
abortController: AbortController;
|
|
26
|
+
timeoutHandle: ReturnType<typeof setTimeout>;
|
|
27
|
+
queue: QueuedFollowUp[];
|
|
28
|
+
cancelled: boolean;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Pending interactive requests are tagged with their turn so a per-session
|
|
32
|
+
// cancel/disconnect can drain only that session's pending entries, and with
|
|
33
|
+
// the turnId that raised them (the slot's id mutates across queued follow-ups).
|
|
34
|
+
export interface PendingApproval {
|
|
35
|
+
turn: RunningTurn;
|
|
36
|
+
turnId: string;
|
|
37
|
+
resolve: (decision: PermissionDecision) => void;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface PendingAskUser {
|
|
41
|
+
turn: RunningTurn;
|
|
42
|
+
turnId: string;
|
|
43
|
+
resolve: (result: AskUserResult) => void;
|
|
44
|
+
reject: (err: Error) => void;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface PendingLocation {
|
|
48
|
+
turn: RunningTurn;
|
|
49
|
+
turnId: string;
|
|
50
|
+
resolve: (result: LocationFix) => void;
|
|
51
|
+
reject: (err: Error) => void;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Mutable turn state for one ws host: the running session slots and every
|
|
56
|
+
* pending interactive round-trip. This used to be six module-level globals
|
|
57
|
+
* inside a 976-line handler; as instance state it is testable and owned by an
|
|
58
|
+
* explicit object with a defined lifecycle.
|
|
59
|
+
*/
|
|
60
|
+
export class TurnCoordinator {
|
|
61
|
+
readonly running = new Set<RunningTurn>();
|
|
62
|
+
readonly bySession = new Map<string, RunningTurn>();
|
|
63
|
+
startingSessions = 0;
|
|
64
|
+
|
|
65
|
+
readonly pendingApprovals = new Map<string, PendingApproval>();
|
|
66
|
+
readonly pendingAskUser = new Map<string, PendingAskUser>();
|
|
67
|
+
readonly pendingLocation = new Map<string, PendingLocation>();
|
|
68
|
+
|
|
69
|
+
private locationCounter = 0;
|
|
70
|
+
|
|
71
|
+
nextLocationRequestId(): string {
|
|
72
|
+
return `loc-${Date.now().toString(36)}-${++this.locationCounter}`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** True while any session has a running turn. */
|
|
76
|
+
isTurnActive(): boolean {
|
|
77
|
+
return this.running.size > 0;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Cancel every running turn (used on shutdown). */
|
|
81
|
+
cancelAll(reason: string): boolean {
|
|
82
|
+
if (this.running.size === 0) return false;
|
|
83
|
+
for (const turn of [...this.running]) this.cancelTurn(turn, reason);
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Cancel a session's current turn and drop its queued follow-ups. */
|
|
88
|
+
cancelTurn(turn: RunningTurn, reason: string): void {
|
|
89
|
+
turn.cancelled = true;
|
|
90
|
+
turn.queue.length = 0;
|
|
91
|
+
clearTimeout(turn.timeoutHandle);
|
|
92
|
+
turn.abortController.abort();
|
|
93
|
+
this.drainPendingForTurn(turn, reason);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Resolve/reject every pending interactive request belonging to one turn. */
|
|
97
|
+
drainPendingForTurn(turn: RunningTurn, reason: string): void {
|
|
98
|
+
for (const [id, p] of this.pendingApprovals) {
|
|
99
|
+
if (p.turn !== turn) continue;
|
|
100
|
+
p.resolve({ behavior: "deny", message: reason });
|
|
101
|
+
this.pendingApprovals.delete(id);
|
|
102
|
+
}
|
|
103
|
+
for (const [id, p] of this.pendingAskUser) {
|
|
104
|
+
if (p.turn !== turn) continue;
|
|
105
|
+
p.reject(new Error(reason));
|
|
106
|
+
this.pendingAskUser.delete(id);
|
|
107
|
+
}
|
|
108
|
+
for (const [id, p] of this.pendingLocation) {
|
|
109
|
+
if (p.turn !== turn) continue;
|
|
110
|
+
p.reject(new Error(reason));
|
|
111
|
+
this.pendingLocation.delete(id);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Interactive-request ids (tool-approval / ask-user / location) must be
|
|
117
|
+
* globally unique so an inbound response resolves the correct turn's
|
|
118
|
+
* promise. The current backends mint unique ids (SDK tool-use ids,
|
|
119
|
+
* crypto.randomUUID, a host counter), so a collision across live turns means
|
|
120
|
+
* a future backend broke that invariant — fail the new request loudly rather
|
|
121
|
+
* than silently overwriting (and thus resolving) another turn's entry.
|
|
122
|
+
*/
|
|
123
|
+
collidesAcrossTurns<T extends { turn: RunningTurn }>(
|
|
124
|
+
map: Map<string, T>,
|
|
125
|
+
id: string,
|
|
126
|
+
turn: RunningTurn
|
|
127
|
+
): boolean {
|
|
128
|
+
const existing = map.get(id);
|
|
129
|
+
if (existing && existing.turn !== turn) {
|
|
130
|
+
console.error(
|
|
131
|
+
`[ws] interactive id collision: "${id}" is already pending for another turn`
|
|
132
|
+
);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Test seam: abort everything and clear all turn/pending state. */
|
|
139
|
+
reset(): void {
|
|
140
|
+
for (const turn of [...this.running]) {
|
|
141
|
+
clearTimeout(turn.timeoutHandle);
|
|
142
|
+
turn.abortController.abort();
|
|
143
|
+
}
|
|
144
|
+
this.running.clear();
|
|
145
|
+
this.bySession.clear();
|
|
146
|
+
this.pendingApprovals.clear();
|
|
147
|
+
this.pendingAskUser.clear();
|
|
148
|
+
this.pendingLocation.clear();
|
|
149
|
+
this.startingSessions = 0;
|
|
150
|
+
}
|
|
151
|
+
}
|