@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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Size-bounding for outbound WebSocket frames.
|
|
3
|
+
*
|
|
4
|
+
* A single oversized frame — typically a multi-megabyte tool result (a big
|
|
5
|
+
* `Read`/`Bash`/`Grep` output), a tool_use with a large input, or a tool
|
|
6
|
+
* result whose content array holds thousands of blocks — can exceed the
|
|
7
|
+
* socket's per-frame limit, drop the connection, and (because the client
|
|
8
|
+
* reconnects and re-requests the same history) wedge into a reconnect loop.
|
|
9
|
+
*
|
|
10
|
+
* This helper bounds any JSON-serializable payload below
|
|
11
|
+
* {@link MAX_WS_MESSAGE_BYTES} by head-truncating long strings AND
|
|
12
|
+
* head-clipping long arrays, leaving small metadata (ids, types, statuses)
|
|
13
|
+
* untouched. Both axes matter: string truncation alone leaves the cap
|
|
14
|
+
* unenforced for a payload built of many short strings. The model already
|
|
15
|
+
* received the full tool output server-side — this only bounds what the
|
|
16
|
+
* browser renders.
|
|
17
|
+
*
|
|
18
|
+
* Ported from oh-my-pi's collab replication-shrink.
|
|
19
|
+
*/
|
|
20
|
+
/** Per-frame ceiling for server → client messages. Well under Bun's 16 MB
|
|
21
|
+
* default and typical reverse-proxy WebSocket limits. */
|
|
22
|
+
export const MAX_WS_MESSAGE_BYTES = 512_000;
|
|
23
|
+
/** Target byte size for one `session_history` chunk. Below the frame cap so a
|
|
24
|
+
* batch plus its wrapper stays comfortably under {@link MAX_WS_MESSAGE_BYTES}. */
|
|
25
|
+
export const HISTORY_CHUNK_BYTES = 400_000;
|
|
26
|
+
/**
|
|
27
|
+
* Progressive shrink passes. Each tightens both the per-string cap and the
|
|
28
|
+
* per-array head limit; the loop stops at the first pass whose output fits
|
|
29
|
+
* {@link MAX_WS_MESSAGE_BYTES}. The final pass clamps every string to 64 B and
|
|
30
|
+
* every array to one element, so even pathological mixes converge.
|
|
31
|
+
*/
|
|
32
|
+
const SHRINK_PASSES = [
|
|
33
|
+
{ stringCap: 64 * 1024, arrayLimit: 256 },
|
|
34
|
+
{ stringCap: 16 * 1024, arrayLimit: 128 },
|
|
35
|
+
{ stringCap: 4 * 1024, arrayLimit: 64 },
|
|
36
|
+
{ stringCap: 1 * 1024, arrayLimit: 32 },
|
|
37
|
+
{ stringCap: 256, arrayLimit: 16 },
|
|
38
|
+
{ stringCap: 256, arrayLimit: 4 },
|
|
39
|
+
{ stringCap: 64, arrayLimit: 1 },
|
|
40
|
+
];
|
|
41
|
+
const STRING_ELISION_RESERVE = 80;
|
|
42
|
+
/**
|
|
43
|
+
* Recursively walk `value`, head-truncating any string longer than
|
|
44
|
+
* `stringCap` and head-clipping any array longer than `arrayLimit`. Returns a
|
|
45
|
+
* freshly built deep clone; short strings/numbers/booleans pass through.
|
|
46
|
+
*/
|
|
47
|
+
function shrinkWalk(value, stringCap, arrayLimit) {
|
|
48
|
+
if (typeof value === "string") {
|
|
49
|
+
if (value.length <= stringCap)
|
|
50
|
+
return value;
|
|
51
|
+
const headLen = Math.max(0, stringCap - STRING_ELISION_RESERVE);
|
|
52
|
+
return `${value.slice(0, headLen)}\n…[${value.length - headLen} chars elided]`;
|
|
53
|
+
}
|
|
54
|
+
if (Array.isArray(value)) {
|
|
55
|
+
const keep = Math.min(value.length, arrayLimit);
|
|
56
|
+
const elided = value.length - keep;
|
|
57
|
+
const out = new Array(elided > 0 ? keep + 1 : keep);
|
|
58
|
+
for (let i = 0; i < keep; i++)
|
|
59
|
+
out[i] = shrinkWalk(value[i], stringCap, arrayLimit);
|
|
60
|
+
if (elided > 0)
|
|
61
|
+
out[keep] = `…[${elided} items elided]`;
|
|
62
|
+
return out;
|
|
63
|
+
}
|
|
64
|
+
if (value && typeof value === "object") {
|
|
65
|
+
const src = value;
|
|
66
|
+
const out = {};
|
|
67
|
+
for (const k in src)
|
|
68
|
+
out[k] = shrinkWalk(src[k], stringCap, arrayLimit);
|
|
69
|
+
return out;
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Return `value` unchanged when its JSON serialization already fits
|
|
75
|
+
* {@link MAX_WS_MESSAGE_BYTES}; otherwise return a deep-cloned shadow shrunk
|
|
76
|
+
* along both string and array axes until it fits. The wire shape is preserved:
|
|
77
|
+
* only string leaves and array tails change — discriminator fields, ids, and
|
|
78
|
+
* other small metadata pass through untouched.
|
|
79
|
+
*/
|
|
80
|
+
export function shrinkForReplication(value) {
|
|
81
|
+
if (JSON.stringify(value).length <= MAX_WS_MESSAGE_BYTES)
|
|
82
|
+
return value;
|
|
83
|
+
let shrunk = value;
|
|
84
|
+
for (const pass of SHRINK_PASSES) {
|
|
85
|
+
shrunk = shrinkWalk(value, pass.stringCap, pass.arrayLimit);
|
|
86
|
+
if (JSON.stringify(shrunk).length <= MAX_WS_MESSAGE_BYTES)
|
|
87
|
+
return shrunk;
|
|
88
|
+
}
|
|
89
|
+
return shrunk;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=shrink.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shrink.js","sourceRoot":"","sources":["../../src/ws/shrink.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { AgentBackend, PermissionDecision, AskUserResult, LocationFix } from "@schlessera/brain-ui-sdk/server";
|
|
2
|
+
import type { ChatImageAttachment } from "@schlessera/brain-ui-sdk/protocol";
|
|
3
|
+
export interface QueuedFollowUp {
|
|
4
|
+
text: string;
|
|
5
|
+
attachments: ChatImageAttachment[];
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* One RUNNING session slot. `abortController`/`timeoutHandle` belong to the
|
|
9
|
+
* turn currently executing; a queued follow-up runs as the next turn in the
|
|
10
|
+
* same slot (same sessionId) after the current one resolves.
|
|
11
|
+
*/
|
|
12
|
+
export interface RunningTurn {
|
|
13
|
+
sessionId: string | null;
|
|
14
|
+
/** Host-minted id of the CURRENT turn in this slot; re-minted per queued follow-up. */
|
|
15
|
+
turnId: string;
|
|
16
|
+
providerId: string | null;
|
|
17
|
+
backend: AgentBackend;
|
|
18
|
+
abortController: AbortController;
|
|
19
|
+
timeoutHandle: ReturnType<typeof setTimeout>;
|
|
20
|
+
queue: QueuedFollowUp[];
|
|
21
|
+
cancelled: boolean;
|
|
22
|
+
}
|
|
23
|
+
export interface PendingApproval {
|
|
24
|
+
turn: RunningTurn;
|
|
25
|
+
turnId: string;
|
|
26
|
+
resolve: (decision: PermissionDecision) => void;
|
|
27
|
+
}
|
|
28
|
+
export interface PendingAskUser {
|
|
29
|
+
turn: RunningTurn;
|
|
30
|
+
turnId: string;
|
|
31
|
+
resolve: (result: AskUserResult) => void;
|
|
32
|
+
reject: (err: Error) => void;
|
|
33
|
+
}
|
|
34
|
+
export interface PendingLocation {
|
|
35
|
+
turn: RunningTurn;
|
|
36
|
+
turnId: string;
|
|
37
|
+
resolve: (result: LocationFix) => void;
|
|
38
|
+
reject: (err: Error) => void;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Mutable turn state for one ws host: the running session slots and every
|
|
42
|
+
* pending interactive round-trip. This used to be six module-level globals
|
|
43
|
+
* inside a 976-line handler; as instance state it is testable and owned by an
|
|
44
|
+
* explicit object with a defined lifecycle.
|
|
45
|
+
*/
|
|
46
|
+
export declare class TurnCoordinator {
|
|
47
|
+
readonly running: Set<RunningTurn>;
|
|
48
|
+
readonly bySession: Map<string, RunningTurn>;
|
|
49
|
+
startingSessions: number;
|
|
50
|
+
readonly pendingApprovals: Map<string, PendingApproval>;
|
|
51
|
+
readonly pendingAskUser: Map<string, PendingAskUser>;
|
|
52
|
+
readonly pendingLocation: Map<string, PendingLocation>;
|
|
53
|
+
private locationCounter;
|
|
54
|
+
nextLocationRequestId(): string;
|
|
55
|
+
/** True while any session has a running turn. */
|
|
56
|
+
isTurnActive(): boolean;
|
|
57
|
+
/** Cancel every running turn (used on shutdown). */
|
|
58
|
+
cancelAll(reason: string): boolean;
|
|
59
|
+
/** Cancel a session's current turn and drop its queued follow-ups. */
|
|
60
|
+
cancelTurn(turn: RunningTurn, reason: string): void;
|
|
61
|
+
/** Resolve/reject every pending interactive request belonging to one turn. */
|
|
62
|
+
drainPendingForTurn(turn: RunningTurn, reason: string): void;
|
|
63
|
+
/**
|
|
64
|
+
* Interactive-request ids (tool-approval / ask-user / location) must be
|
|
65
|
+
* globally unique so an inbound response resolves the correct turn's
|
|
66
|
+
* promise. The current backends mint unique ids (SDK tool-use ids,
|
|
67
|
+
* crypto.randomUUID, a host counter), so a collision across live turns means
|
|
68
|
+
* a future backend broke that invariant — fail the new request loudly rather
|
|
69
|
+
* than silently overwriting (and thus resolving) another turn's entry.
|
|
70
|
+
*/
|
|
71
|
+
collidesAcrossTurns<T extends {
|
|
72
|
+
turn: RunningTurn;
|
|
73
|
+
}>(map: Map<string, T>, id: string, turn: RunningTurn): boolean;
|
|
74
|
+
/** Test seam: abort everything and clear all turn/pending state. */
|
|
75
|
+
reset(): void;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=turns.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turns.d.ts","sourceRoot":"","sources":["../../src/ws/turns.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,YAAY,EACZ,kBAAkB,EAClB,aAAa,EACb,WAAW,EACZ,MAAM,iCAAiC,CAAC;AACzC,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,mCAAmC,CAAC;AAE7E,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,mBAAmB,EAAE,CAAC;CACpC;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,uFAAuF;IACvF,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,OAAO,EAAE,YAAY,CAAC;IACtB,eAAe,EAAE,eAAe,CAAC;IACjC,aAAa,EAAE,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC;IAC7C,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,SAAS,EAAE,OAAO,CAAC;CACpB;AAKD,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,QAAQ,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACjD;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IACzC,MAAM,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CAC9B;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,WAAW,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,IAAI,CAAC;IACvC,MAAM,EAAE,CAAC,GAAG,EAAE,KAAK,KAAK,IAAI,CAAC;CAC9B;AAED;;;;;GAKG;AACH,qBAAa,eAAe;IAC1B,QAAQ,CAAC,OAAO,mBAA0B;IAC1C,QAAQ,CAAC,SAAS,2BAAkC;IACpD,gBAAgB,SAAK;IAErB,QAAQ,CAAC,gBAAgB,+BAAsC;IAC/D,QAAQ,CAAC,cAAc,8BAAqC;IAC5D,QAAQ,CAAC,eAAe,+BAAsC;IAE9D,OAAO,CAAC,eAAe,CAAK;IAE5B,qBAAqB,IAAI,MAAM;IAI/B,iDAAiD;IACjD,YAAY,IAAI,OAAO;IAIvB,oDAAoD;IACpD,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO;IAMlC,sEAAsE;IACtE,UAAU,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAQnD,8EAA8E;IAC9E,mBAAmB,CAAC,IAAI,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,GAAG,IAAI;IAkB5D;;;;;;;OAOG;IACH,mBAAmB,CAAC,CAAC,SAAS;QAAE,IAAI,EAAE,WAAW,CAAA;KAAE,EACjD,GAAG,EAAE,GAAG,CAAC,MAAM,EAAE,CAAC,CAAC,EACnB,EAAE,EAAE,MAAM,EACV,IAAI,EAAE,WAAW,GAChB,OAAO;IAWV,oEAAoE;IACpE,KAAK,IAAI,IAAI;CAYd"}
|
package/dist/ws/turns.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mutable turn state for one ws host: the running session slots and every
|
|
3
|
+
* pending interactive round-trip. This used to be six module-level globals
|
|
4
|
+
* inside a 976-line handler; as instance state it is testable and owned by an
|
|
5
|
+
* explicit object with a defined lifecycle.
|
|
6
|
+
*/
|
|
7
|
+
export class TurnCoordinator {
|
|
8
|
+
running = new Set();
|
|
9
|
+
bySession = new Map();
|
|
10
|
+
startingSessions = 0;
|
|
11
|
+
pendingApprovals = new Map();
|
|
12
|
+
pendingAskUser = new Map();
|
|
13
|
+
pendingLocation = new Map();
|
|
14
|
+
locationCounter = 0;
|
|
15
|
+
nextLocationRequestId() {
|
|
16
|
+
return `loc-${Date.now().toString(36)}-${++this.locationCounter}`;
|
|
17
|
+
}
|
|
18
|
+
/** True while any session has a running turn. */
|
|
19
|
+
isTurnActive() {
|
|
20
|
+
return this.running.size > 0;
|
|
21
|
+
}
|
|
22
|
+
/** Cancel every running turn (used on shutdown). */
|
|
23
|
+
cancelAll(reason) {
|
|
24
|
+
if (this.running.size === 0)
|
|
25
|
+
return false;
|
|
26
|
+
for (const turn of [...this.running])
|
|
27
|
+
this.cancelTurn(turn, reason);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
/** Cancel a session's current turn and drop its queued follow-ups. */
|
|
31
|
+
cancelTurn(turn, reason) {
|
|
32
|
+
turn.cancelled = true;
|
|
33
|
+
turn.queue.length = 0;
|
|
34
|
+
clearTimeout(turn.timeoutHandle);
|
|
35
|
+
turn.abortController.abort();
|
|
36
|
+
this.drainPendingForTurn(turn, reason);
|
|
37
|
+
}
|
|
38
|
+
/** Resolve/reject every pending interactive request belonging to one turn. */
|
|
39
|
+
drainPendingForTurn(turn, reason) {
|
|
40
|
+
for (const [id, p] of this.pendingApprovals) {
|
|
41
|
+
if (p.turn !== turn)
|
|
42
|
+
continue;
|
|
43
|
+
p.resolve({ behavior: "deny", message: reason });
|
|
44
|
+
this.pendingApprovals.delete(id);
|
|
45
|
+
}
|
|
46
|
+
for (const [id, p] of this.pendingAskUser) {
|
|
47
|
+
if (p.turn !== turn)
|
|
48
|
+
continue;
|
|
49
|
+
p.reject(new Error(reason));
|
|
50
|
+
this.pendingAskUser.delete(id);
|
|
51
|
+
}
|
|
52
|
+
for (const [id, p] of this.pendingLocation) {
|
|
53
|
+
if (p.turn !== turn)
|
|
54
|
+
continue;
|
|
55
|
+
p.reject(new Error(reason));
|
|
56
|
+
this.pendingLocation.delete(id);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Interactive-request ids (tool-approval / ask-user / location) must be
|
|
61
|
+
* globally unique so an inbound response resolves the correct turn's
|
|
62
|
+
* promise. The current backends mint unique ids (SDK tool-use ids,
|
|
63
|
+
* crypto.randomUUID, a host counter), so a collision across live turns means
|
|
64
|
+
* a future backend broke that invariant — fail the new request loudly rather
|
|
65
|
+
* than silently overwriting (and thus resolving) another turn's entry.
|
|
66
|
+
*/
|
|
67
|
+
collidesAcrossTurns(map, id, turn) {
|
|
68
|
+
const existing = map.get(id);
|
|
69
|
+
if (existing && existing.turn !== turn) {
|
|
70
|
+
console.error(`[ws] interactive id collision: "${id}" is already pending for another turn`);
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
/** Test seam: abort everything and clear all turn/pending state. */
|
|
76
|
+
reset() {
|
|
77
|
+
for (const turn of [...this.running]) {
|
|
78
|
+
clearTimeout(turn.timeoutHandle);
|
|
79
|
+
turn.abortController.abort();
|
|
80
|
+
}
|
|
81
|
+
this.running.clear();
|
|
82
|
+
this.bySession.clear();
|
|
83
|
+
this.pendingApprovals.clear();
|
|
84
|
+
this.pendingAskUser.clear();
|
|
85
|
+
this.pendingLocation.clear();
|
|
86
|
+
this.startingSessions = 0;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=turns.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"turns.js","sourceRoot":"","sources":["../../src/ws/turns.ts"],"names":[],"mappings":"AAqDA;;;;;GAKG;AACH,MAAM,OAAO,eAAe;IACjB,OAAO,GAAG,IAAI,GAAG,EAAe,CAAC;IACjC,SAAS,GAAG,IAAI,GAAG,EAAuB,CAAC;IACpD,gBAAgB,GAAG,CAAC,CAAC;IAEZ,gBAAgB,GAAG,IAAI,GAAG,EAA2B,CAAC;IACtD,cAAc,GAAG,IAAI,GAAG,EAA0B,CAAC;IACnD,eAAe,GAAG,IAAI,GAAG,EAA2B,CAAC;IAEtD,eAAe,GAAG,CAAC,CAAC;IAE5B,qBAAqB;QACnB,OAAO,OAAO,IAAI,CAAC,GAAG,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,CAAC,eAAe,EAAE,CAAC;IACpE,CAAC;IAED,iDAAiD;IACjD,YAAY;QACV,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,GAAG,CAAC,CAAC;IAC/B,CAAC;IAED,oDAAoD;IACpD,SAAS,CAAC,MAAc;QACtB,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,KAAK,CAAC;YAAE,OAAO,KAAK,CAAC;QAC1C,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC;YAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;QACpE,OAAO,IAAI,CAAC;IACd,CAAC;IAED,sEAAsE;IACtE,UAAU,CAAC,IAAiB,EAAE,MAAc;QAC1C,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;QACtB,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QACjC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC,mBAAmB,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACzC,CAAC;IAED,8EAA8E;IAC9E,mBAAmB,CAAC,IAAiB,EAAE,MAAc;QACnD,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,gBAAgB,EAAE,CAAC;YAC5C,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI;gBAAE,SAAS;YAC9B,CAAC,CAAC,OAAO,CAAC,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;YACjD,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACnC,CAAC;QACD,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YAC1C,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI;gBAAE,SAAS;YAC9B,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;YAC5B,IAAI,CAAC,cAAc,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QACjC,CAAC;QACD,KAAK,MAAM,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,CAAC,eAAe,EAAE,CAAC;YAC3C,IAAI,CAAC,CAAC,IAAI,KAAK,IAAI;gBAAE,SAAS;YAC9B,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC;YAC5B,IAAI,CAAC,eAAe,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAClC,CAAC;IACH,CAAC;IAED;;;;;;;OAOG;IACH,mBAAmB,CACjB,GAAmB,EACnB,EAAU,EACV,IAAiB;QAEjB,MAAM,QAAQ,GAAG,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QAC7B,IAAI,QAAQ,IAAI,QAAQ,CAAC,IAAI,KAAK,IAAI,EAAE,CAAC;YACvC,OAAO,CAAC,KAAK,CACX,mCAAmC,EAAE,uCAAuC,CAC7E,CAAC;YACF,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,oEAAoE;IACpE,KAAK;QACH,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC;YACrC,YAAY,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;YACjC,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC/B,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,CAAC;QACvB,IAAI,CAAC,gBAAgB,CAAC,KAAK,EAAE,CAAC;QAC9B,IAAI,CAAC,cAAc,CAAC,KAAK,EAAE,CAAC;QAC5B,IAAI,CAAC,eAAe,CAAC,KAAK,EAAE,CAAC;QAC7B,IAAI,CAAC,gBAAgB,GAAG,CAAC,CAAC;IAC5B,CAAC;CACF"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
-- Chat session metadata (Claude SDK stores actual messages in JSONL)
|
|
2
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
3
|
+
id TEXT PRIMARY KEY,
|
|
4
|
+
title TEXT,
|
|
5
|
+
created_at INTEGER NOT NULL,
|
|
6
|
+
last_active_at INTEGER NOT NULL,
|
|
7
|
+
total_cost_usd REAL DEFAULT 0,
|
|
8
|
+
num_turns INTEGER DEFAULT 0
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
-- Cron job run history
|
|
12
|
+
CREATE TABLE IF NOT EXISTS cron_runs (
|
|
13
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
14
|
+
job_name TEXT NOT NULL,
|
|
15
|
+
started_at INTEGER NOT NULL,
|
|
16
|
+
finished_at INTEGER,
|
|
17
|
+
status TEXT NOT NULL CHECK (status IN ('running', 'success', 'error')),
|
|
18
|
+
error_message TEXT,
|
|
19
|
+
duration_ms INTEGER
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_cron_runs_job ON cron_runs(job_name, started_at DESC);
|
|
23
|
+
|
|
24
|
+
-- Migrations tracking
|
|
25
|
+
CREATE TABLE IF NOT EXISTS _migrations (
|
|
26
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
27
|
+
filename TEXT NOT NULL UNIQUE,
|
|
28
|
+
applied_at INTEGER NOT NULL
|
|
29
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
ALTER TABLE sessions ADD COLUMN provider_id TEXT;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
-- Which AgentBackend owns a session's transcript (e.g. "claude" | "pi").
|
|
2
|
+
-- One backend is active per deployment in v1, but the column is added now so a
|
|
3
|
+
-- later per-session-backend change needs no migration. Populated on session
|
|
4
|
+
-- creation from the active backend id.
|
|
5
|
+
ALTER TABLE sessions ADD COLUMN backend_id TEXT;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
-- Phase-5 hardening: the native Gemini backend was removed. Reconcile sessions
|
|
2
|
+
-- that were pinned to it so they resume cleanly on the default (Claude) backend
|
|
3
|
+
-- instead of dangling on a backend id that no longer resolves.
|
|
4
|
+
--
|
|
5
|
+
-- Rollback note: this rewrites data. Redeploying a Gemini-aware image does NOT
|
|
6
|
+
-- restore these ids; a rollback that needs them must reinstate them separately.
|
|
7
|
+
|
|
8
|
+
-- Clear the profile pin for any session that ran on the removed backend — those
|
|
9
|
+
-- profile ids do not exist under the default backend.
|
|
10
|
+
UPDATE sessions SET provider_id = NULL WHERE backend_id = 'gemini';
|
|
11
|
+
|
|
12
|
+
-- Point those sessions at the default backend (getBackendForSession resolves a
|
|
13
|
+
-- NULL/unknown backend_id to the default).
|
|
14
|
+
UPDATE sessions SET backend_id = NULL WHERE backend_id = 'gemini';
|
|
15
|
+
|
|
16
|
+
-- Migrate the pre-extraction Claude profile id to its renamed, byte-identical
|
|
17
|
+
-- successor so those sessions keep their pin instead of silently dropping to the
|
|
18
|
+
-- default model.
|
|
19
|
+
UPDATE sessions SET provider_id = 'claude-sonnet-4-6'
|
|
20
|
+
WHERE provider_id = 'anthropic-sonnet';
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
-- WebAuthn passkey credentials (single-user app: no user_id column).
|
|
2
|
+
-- rp_id scopes each credential to the relying party it was registered on
|
|
3
|
+
-- (localhost dev credentials and prod-domain credentials coexist).
|
|
4
|
+
CREATE TABLE passkey_credentials (
|
|
5
|
+
id TEXT PRIMARY KEY, -- base64url credential id
|
|
6
|
+
public_key BLOB NOT NULL, -- COSE public key bytes
|
|
7
|
+
counter INTEGER NOT NULL DEFAULT 0, -- signature counter (0 forever for cloud passkeys)
|
|
8
|
+
transports TEXT, -- JSON array, e.g. ["internal","hybrid"], or NULL
|
|
9
|
+
rp_id TEXT NOT NULL,
|
|
10
|
+
aaguid TEXT,
|
|
11
|
+
device_type TEXT, -- 'singleDevice' | 'multiDevice'
|
|
12
|
+
backed_up INTEGER NOT NULL DEFAULT 0,
|
|
13
|
+
label TEXT NOT NULL DEFAULT '',
|
|
14
|
+
created_at INTEGER NOT NULL,
|
|
15
|
+
last_used_at INTEGER
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
CREATE INDEX idx_passkey_credentials_rp_id ON passkey_credentials(rp_id);
|
package/package.json
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@schlessera/brain-ui-server",
|
|
3
|
+
"version": "0.4.0",
|
|
4
|
+
"description": "brain-kit chat-UI server: Hono app factory, WebSocket turn coordinator, auth (password/passkeys/tailscale/proxy), session catalog, and brain/files/voice routes",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"brain-kit",
|
|
8
|
+
"server",
|
|
9
|
+
"hono",
|
|
10
|
+
"websocket",
|
|
11
|
+
"ai-agents"
|
|
12
|
+
],
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/schlessera/brain-kit.git",
|
|
16
|
+
"directory": "packages/ui-server"
|
|
17
|
+
},
|
|
18
|
+
"homepage": "https://github.com/schlessera/brain-kit",
|
|
19
|
+
"bugs": {
|
|
20
|
+
"url": "https://github.com/schlessera/brain-kit/issues"
|
|
21
|
+
},
|
|
22
|
+
"publishConfig": {
|
|
23
|
+
"access": "public"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"bun": ">=1.3.0"
|
|
27
|
+
},
|
|
28
|
+
"sideEffects": false,
|
|
29
|
+
"type": "module",
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"bun": "./src/index.ts",
|
|
33
|
+
"types": "./dist/index.d.ts",
|
|
34
|
+
"default": "./dist/index.js"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"files": [
|
|
38
|
+
"src",
|
|
39
|
+
"dist",
|
|
40
|
+
"migrations",
|
|
41
|
+
"README.md"
|
|
42
|
+
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"test": "bun test tests",
|
|
45
|
+
"prepublishOnly": "bun ../../scripts/check-dist.ts"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@schlessera/brain-backend-claude": "0.4.0",
|
|
49
|
+
"@schlessera/brain-ui-sdk": "0.4.0",
|
|
50
|
+
"@simplewebauthn/server": "^13.3.2",
|
|
51
|
+
"hono": "^4",
|
|
52
|
+
"ignore": "^7.0.5",
|
|
53
|
+
"marked": "^18.0.4",
|
|
54
|
+
"zod": "^4.4.3"
|
|
55
|
+
},
|
|
56
|
+
"peerDependencies": {
|
|
57
|
+
"@schlessera/brain-backend-pi": "0.4.0",
|
|
58
|
+
"@types/bun": ">=1.3.0"
|
|
59
|
+
},
|
|
60
|
+
"peerDependenciesMeta": {
|
|
61
|
+
"@schlessera/brain-backend-pi": {
|
|
62
|
+
"optional": true
|
|
63
|
+
},
|
|
64
|
+
"@types/bun": {
|
|
65
|
+
"optional": true
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"devDependencies": {
|
|
69
|
+
"@types/bun": "^1.3.14",
|
|
70
|
+
"typescript": "^6.0.3"
|
|
71
|
+
}
|
|
72
|
+
}
|