@orkestrel/toolbox 0.0.21 → 0.0.22

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.
@@ -3,7 +3,7 @@ let _orkestrel_terminal = require("@orkestrel/terminal");
3
3
  let _orkestrel_form = require("@orkestrel/form");
4
4
  let _orkestrel_contract = require("@orkestrel/contract");
5
5
  let _orkestrel_server = require("@orkestrel/server");
6
- //#region src/server/constants.ts
6
+ // src/server/constants.ts
7
7
  /**
8
8
  * Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}
9
9
  * mounts its GET (SSE) and POST (answer) routes under, `/terminals/:name`.
@@ -15,8 +15,7 @@ var TERMINAL_ROUTES_PATH = "/terminals/:name";
15
15
  * keeping intermediary proxies from timing out an otherwise-idle stream.
16
16
  */
17
17
  var TERMINAL_KEEPALIVE_MS = 15e3;
18
- //#endregion
19
- //#region src/server/terminals/TerminalConnection.ts
18
+ // src/server/terminals/TerminalConnection.ts
20
19
  /**
21
20
  * Owns one terminal SSE connection's replay, subscriptions, keepalive, and teardown.
22
21
  *
@@ -128,8 +127,7 @@ var TerminalConnection = class {
128
127
  this.#stream.end();
129
128
  }
130
129
  };
131
- //#endregion
132
- //#region src/server/terminals/TerminalBridge.ts
130
+ // src/server/terminals/TerminalBridge.ts
133
131
  /**
134
132
  * Bridges a terminal manager onto the wire — owns the shared route options, the token gate, and
135
133
  * the bound GET stream and POST answer handlers.
@@ -202,13 +200,8 @@ var TerminalBridge = class {
202
200
  if (!this.#authorized(request)) return new Response(null, { status: 401 });
203
201
  const name = context.params.name;
204
202
  if (name === void 0 || this.#manager.terminal(name) === void 0) return new Response(null, { status: 404 });
205
- let bytes;
206
- try {
207
- bytes = await (0, _orkestrel_server.collectRequestBody)(request, Math.max(1, this.#limit));
208
- } catch (error) {
209
- if ((0, _orkestrel_contract.isInstance)(error, _orkestrel_server.ContentTooLargeError)) return new Response(null, { status: 413 });
210
- throw error;
211
- }
203
+ const bytes = await this.#readBody(request);
204
+ if (bytes instanceof Response) return bytes;
212
205
  if (bytes.byteLength > 0 && bytes.byteLength > this.#limit) return new Response(null, { status: 413 });
213
206
  const body = (0, _orkestrel_contract.parseJSON)(new TextDecoder().decode(bytes));
214
207
  if (body === void 0) return new Response(null, { status: 400 });
@@ -218,9 +211,16 @@ var TerminalBridge = class {
218
211
  if (result.error.reason === "target") return Response.json(result, { status: 404 });
219
212
  return Response.json(result, { status: 422 });
220
213
  }
214
+ async #readBody(request) {
215
+ try {
216
+ return await (0, _orkestrel_server.collectRequestBody)(request, Math.max(1, this.#limit));
217
+ } catch (error) {
218
+ if ((0, _orkestrel_contract.isInstance)(error, _orkestrel_server.ContentTooLargeError)) return new Response(null, { status: 413 });
219
+ throw error;
220
+ }
221
+ }
221
222
  };
222
- //#endregion
223
- //#region src/server/factories.ts
223
+ // src/server/factories.ts
224
224
  /**
225
225
  * Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.
226
226
  *
@@ -250,7 +250,6 @@ var TerminalBridge = class {
250
250
  function createTerminalRoutes(manager, options) {
251
251
  return new TerminalBridge(manager, options).routes();
252
252
  }
253
- //#endregion
254
253
  exports.TERMINAL_KEEPALIVE_MS = TERMINAL_KEEPALIVE_MS;
255
254
  exports.TERMINAL_ROUTES_PATH = TERMINAL_ROUTES_PATH;
256
255
  exports.createTerminalRoutes = createTerminalRoutes;
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["HEADER_TOKEN","serializePending","serializeExpire","defaultTimer","isFiniteNumber","DEFAULT_BODY_LIMIT","HEADER_TOKEN","createStream","collectRequestBody","isInstance","ContentTooLargeError","parseJSON","isRecord","isNonEmptyString","isFormValues"],"sources":["../../../src/server/constants.ts","../../../src/server/terminals/TerminalConnection.ts","../../../src/server/terminals/TerminalBridge.ts","../../../src/server/factories.ts"],"sourcesContent":["// Server-package constants — UPPER_SNAKE, `Object.freeze`d where structural, every member\n// exported.\n\n/**\n * Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}\n * mounts its GET (SSE) and POST (answer) routes under, `/terminals/:name`.\n */\nexport const TERMINAL_ROUTES_PATH = '/terminals/:name'\n\n/**\n * Holds the default SSE keepalive interval {@link import('./factories.js').createTerminalRoutes}\n * arms per open connection, `15_000` ms — a `: ` comment ping a conforming SSE parser ignores,\n * keeping intermediary proxies from timing out an otherwise-idle stream.\n */\nexport const TERMINAL_KEEPALIVE_MS = 15_000\n","import type {\n\tPendingForm,\n\tTerminalManagerInterface,\n\tTimerCancelFunction,\n\tTimerHandler,\n\tWireEvent,\n} from '@orkestrel/terminal'\nimport type { StreamInterface } from '@orkestrel/server'\nimport { HEADER_TOKEN, serializeExpire, serializePending } from '@orkestrel/terminal'\n\n/**\n * Owns one terminal SSE connection's replay, subscriptions, keepalive, and teardown.\n *\n * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalConnection {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #name: string\n\treadonly #request: Request\n\treadonly #stream: StreamInterface\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #timer: TimerHandler\n\treadonly #keepalive: number\n\treadonly #presented: string | undefined\n\t#cancel: TimerCancelFunction | undefined\n\treadonly #destroyHandler: () => void\n\treadonly #pendingHandler: (form: PendingForm) => void\n\treadonly #expireHandler: (to: string, id: string) => void\n\treadonly #tickHandler: () => void\n\n\t/**\n\t * Creates a terminal stream connection.\n\t *\n\t * @param manager - Terminal manager supplying pending forms and lifecycle events\n\t * @param name - Terminal endpoint streamed by this connection\n\t * @param request - Request whose abort signal owns the connection lifetime\n\t * @param stream - Open SSE stream\n\t * @param accepts - Presented-token validator\n\t * @param timer - Keepalive timer implementation\n\t * @param keepalive - Keepalive interval in milliseconds\n\t */\n\tconstructor(\n\t\tmanager: TerminalManagerInterface,\n\t\tname: string,\n\t\trequest: Request,\n\t\tstream: StreamInterface,\n\t\taccepts: (presented: string | undefined) => boolean,\n\t\ttimer: TimerHandler,\n\t\tkeepalive: number,\n\t) {\n\t\tthis.#manager = manager\n\t\tthis.#name = name\n\t\tthis.#request = request\n\t\tthis.#stream = stream\n\t\tthis.#accepts = accepts\n\t\tthis.#timer = timer\n\t\tthis.#keepalive = keepalive\n\t\tthis.#presented = request.headers.get(HEADER_TOKEN) ?? undefined\n\t\tthis.#destroyHandler = this.#destroy.bind(this)\n\t\tthis.#pendingHandler = this.#pending.bind(this)\n\t\tthis.#expireHandler = this.#expire.bind(this)\n\t\tthis.#tickHandler = this.#tick.bind(this)\n\t}\n\n\t/**\n\t * Opens the connection by replaying pending forms, subscribing, and arming keepalive handling.\n\t *\n\t * @returns The SSE response\n\t */\n\topen(): Response {\n\t\tif (this.#stream.closed || this.#request.signal.aborted) {\n\t\t\tthis.#destroy()\n\t\t\treturn this.#stream.response\n\t\t}\n\t\tif (this.#cancel !== undefined) return this.#stream.response\n\t\tfor (const form of this.#manager.pending(this.#name)) {\n\t\t\tthis.#write(serializePending(form))\n\t\t}\n\t\tthis.#manager.emitter.on('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.on('expire', this.#expireHandler)\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t\tthis.#request.signal.addEventListener('abort', this.#destroyHandler)\n\t\treturn this.#stream.response\n\t}\n\n\t#write(wire: WireEvent): void {\n\t\tthis.#stream.write({\n\t\t\tevent: wire.event,\n\t\t\tdata: wire.data,\n\t\t\t...(wire.id === undefined ? {} : { id: wire.id }),\n\t\t})\n\t}\n\n\t#pending(form: PendingForm): void {\n\t\tif (form.to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializePending(form))\n\t}\n\n\t#expire(to: string, id: string): void {\n\t\tif (to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializeExpire(id))\n\t}\n\n\t#tick(): void {\n\t\tif (this.#stream.closed || !this.#accepted()) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#stream.comment('')\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t}\n\n\t#accepted(): boolean {\n\t\ttry {\n\t\t\treturn this.#accepts(this.#presented)\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#destroy(): void {\n\t\tconst cancel = this.#cancel\n\t\tthis.#cancel = undefined\n\t\tcancel?.()\n\t\tthis.#manager.emitter.off('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.off('expire', this.#expireHandler)\n\t\tthis.#request.signal.removeEventListener('abort', this.#destroyHandler)\n\t\tthis.#stream.end()\n\t}\n}\n","import type { TerminalManagerInterface, TimerHandler } from '@orkestrel/terminal'\nimport type {\n\tTerminalRoute,\n\tTerminalRouteContext,\n\tTerminalRoutesOptions,\n\tTerminalToken,\n} from '../types.js'\nimport { defaultTimer, HEADER_TOKEN } from '@orkestrel/terminal'\nimport { isFormValues } from '@orkestrel/form'\nimport {\n\tisFiniteNumber,\n\tisInstance,\n\tisNonEmptyString,\n\tisRecord,\n\tparseJSON,\n} from '@orkestrel/contract'\nimport {\n\tcollectRequestBody,\n\tContentTooLargeError,\n\tcreateStream,\n\tDEFAULT_BODY_LIMIT,\n} from '@orkestrel/server'\nimport { TERMINAL_KEEPALIVE_MS, TERMINAL_ROUTES_PATH } from '../constants.js'\nimport { TerminalConnection } from './TerminalConnection.js'\n\n/**\n * Bridges a terminal manager onto the wire — owns the shared route options, the token gate, and\n * the bound GET stream and POST answer handlers.\n *\n * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalBridge {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #path: string\n\treadonly #token: TerminalToken | undefined\n\treadonly #keepalive: number\n\treadonly #timer: TimerHandler\n\treadonly #limit: number\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #get: TerminalRoute['handler']\n\treadonly #post: TerminalRoute['handler']\n\n\t/**\n\t * Creates a terminal bridge.\n\t *\n\t * @param manager - Terminal manager bridged onto HTTP\n\t * @param options - Shared route, authorization, keepalive, timer, and body-limit options\n\t */\n\tconstructor(manager: TerminalManagerInterface, options?: TerminalRoutesOptions) {\n\t\tthis.#manager = manager\n\t\tthis.#path = options?.path ?? TERMINAL_ROUTES_PATH\n\t\tthis.#token = options?.token\n\t\tthis.#keepalive = options?.keepalive ?? TERMINAL_KEEPALIVE_MS\n\t\tthis.#timer = options?.timer ?? defaultTimer\n\t\tconst limit = options?.limit\n\t\tthis.#limit =\n\t\t\tlimit === undefined || !isFiniteNumber(limit)\n\t\t\t\t? DEFAULT_BODY_LIMIT\n\t\t\t\t: Math.max(0, Math.floor(limit))\n\t\tthis.#accepts = this.#valid.bind(this)\n\t\tthis.#get = this.#handleGet.bind(this)\n\t\tthis.#post = this.#handlePost.bind(this)\n\t}\n\n\t/**\n\t * Projects the bound GET and POST route records.\n\t *\n\t * @returns The GET stream route followed by the POST answer route\n\t */\n\troutes(): readonly TerminalRoute[] {\n\t\treturn [\n\t\t\t{ method: 'GET', path: this.#path, handler: this.#get },\n\t\t\t{ method: 'POST', path: this.#path, handler: this.#post },\n\t\t]\n\t}\n\n\t#valid(presented: string | undefined): boolean {\n\t\tif (this.#token === undefined) return true\n\t\ttry {\n\t\t\treturn typeof this.#token === 'function' ? this.#token(presented) : presented === this.#token\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#authorized(request: Request): boolean {\n\t\treturn this.#valid(request.headers.get(HEADER_TOKEN) ?? undefined)\n\t}\n\n\t#handleGet(request: Request, context: TerminalRouteContext): Response {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tconst connection = new TerminalConnection(\n\t\t\tthis.#manager,\n\t\t\tname,\n\t\t\trequest,\n\t\t\tcreateStream(),\n\t\t\tthis.#accepts,\n\t\t\tthis.#timer,\n\t\t\tthis.#keepalive,\n\t\t)\n\t\treturn connection.open()\n\t}\n\n\tasync #handlePost(request: Request, context: TerminalRouteContext): Promise<Response> {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tlet bytes: Uint8Array\n\t\ttry {\n\t\t\tbytes = await collectRequestBody(request, Math.max(1, this.#limit))\n\t\t} catch (error) {\n\t\t\tif (isInstance(error, ContentTooLargeError)) return new Response(null, { status: 413 })\n\t\t\tthrow error\n\t\t}\n\t\tif (bytes.byteLength > 0 && bytes.byteLength > this.#limit) {\n\t\t\treturn new Response(null, { status: 413 })\n\t\t}\n\n\t\tconst body = parseJSON(new TextDecoder().decode(bytes))\n\t\tif (body === undefined) {\n\t\t\treturn new Response(null, { status: 400 })\n\t\t}\n\t\tif (!isRecord(body) || !isNonEmptyString(body.id) || !isFormValues(body.values)) {\n\t\t\treturn new Response(null, { status: 422 })\n\t\t}\n\n\t\tconst result = this.#manager.answer(name, body.id, body.values)\n\t\tif (result.success) return Response.json(result)\n\t\tif (result.error.reason === 'target') return Response.json(result, { status: 404 })\n\t\treturn Response.json(result, { status: 422 })\n\t}\n}\n","import type { TerminalManagerInterface } from '@orkestrel/terminal'\nimport type { TerminalRoute, TerminalRoutesOptions } from './types.js'\nimport { TerminalBridge } from './terminals/TerminalBridge.js'\n\n/**\n * Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.\n *\n * @remarks\n * The pair stays byte-compatible with `@orkestrel/terminal`'s own `PromptClient`: the same GET url\n * streams, the same POST url answers, the same `{ id, values }` request body, the same JSON answer\n * `Result` response body, and the same `x-orkestrel-token` header.\n *\n * Both routes share the configured `:name` path and optional token gate. The GET route replays\n * pending prompts, forwards live pending/expire events, and owns abort/keepalive teardown. The\n * POST route bounds the request body before parsing and maps answer outcomes to HTTP statuses.\n *\n * @param manager - The terminal manager whose endpoints are bridged\n * @param options - Route path, token, keepalive, timer, and body-limit options\n * @returns The GET route followed by the POST route\n *\n * @example\n * ```ts\n * import { createTerminalRoutes } from '@src/server'\n * import { createTerminalManager } from '@orkestrel/terminal'\n *\n * const manager = createTerminalManager()\n * manager.add('assistant')\n * const routes = createTerminalRoutes(manager, { token: 'secret' })\n * ```\n */\nexport function createTerminalRoutes(\n\tmanager: TerminalManagerInterface,\n\toptions?: TerminalRoutesOptions,\n): readonly TerminalRoute[] {\n\treturn new TerminalBridge(manager, options).routes()\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["HEADER_TOKEN","serializePending","serializeExpire","defaultTimer","isFiniteNumber","DEFAULT_BODY_LIMIT","HEADER_TOKEN","createStream","parseJSON","isRecord","isNonEmptyString","isFormValues","collectRequestBody","isInstance","ContentTooLargeError"],"sources":["../../../src/server/constants.ts","../../../src/server/terminals/TerminalConnection.ts","../../../src/server/terminals/TerminalBridge.ts","../../../src/server/factories.ts"],"sourcesContent":["// Server-package constants — UPPER_SNAKE, `Object.freeze`d where structural, every member\n// exported.\n\n/**\n * Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}\n * mounts its GET (SSE) and POST (answer) routes under, `/terminals/:name`.\n */\nexport const TERMINAL_ROUTES_PATH = '/terminals/:name'\n\n/**\n * Holds the default SSE keepalive interval {@link import('./factories.js').createTerminalRoutes}\n * arms per open connection, `15_000` ms — a `: ` comment ping a conforming SSE parser ignores,\n * keeping intermediary proxies from timing out an otherwise-idle stream.\n */\nexport const TERMINAL_KEEPALIVE_MS = 15_000\n","import type {\n\tPendingForm,\n\tTerminalManagerInterface,\n\tTimerCancelFunction,\n\tTimerHandler,\n\tWireEvent,\n} from '@orkestrel/terminal'\nimport type { StreamInterface } from '@orkestrel/server'\nimport { HEADER_TOKEN, serializeExpire, serializePending } from '@orkestrel/terminal'\n\n/**\n * Owns one terminal SSE connection's replay, subscriptions, keepalive, and teardown.\n *\n * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalConnection {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #name: string\n\treadonly #request: Request\n\treadonly #stream: StreamInterface\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #timer: TimerHandler\n\treadonly #keepalive: number\n\treadonly #presented: string | undefined\n\t#cancel: TimerCancelFunction | undefined\n\treadonly #destroyHandler: () => void\n\treadonly #pendingHandler: (form: PendingForm) => void\n\treadonly #expireHandler: (to: string, id: string) => void\n\treadonly #tickHandler: () => void\n\n\t/**\n\t * Creates a terminal stream connection.\n\t *\n\t * @param manager - Terminal manager supplying pending forms and lifecycle events\n\t * @param name - Terminal endpoint streamed by this connection\n\t * @param request - Request whose abort signal owns the connection lifetime\n\t * @param stream - Open SSE stream\n\t * @param accepts - Presented-token validator\n\t * @param timer - Keepalive timer implementation\n\t * @param keepalive - Keepalive interval in milliseconds\n\t */\n\tconstructor(\n\t\tmanager: TerminalManagerInterface,\n\t\tname: string,\n\t\trequest: Request,\n\t\tstream: StreamInterface,\n\t\taccepts: (presented: string | undefined) => boolean,\n\t\ttimer: TimerHandler,\n\t\tkeepalive: number,\n\t) {\n\t\tthis.#manager = manager\n\t\tthis.#name = name\n\t\tthis.#request = request\n\t\tthis.#stream = stream\n\t\tthis.#accepts = accepts\n\t\tthis.#timer = timer\n\t\tthis.#keepalive = keepalive\n\t\tthis.#presented = request.headers.get(HEADER_TOKEN) ?? undefined\n\t\tthis.#destroyHandler = this.#destroy.bind(this)\n\t\tthis.#pendingHandler = this.#pending.bind(this)\n\t\tthis.#expireHandler = this.#expire.bind(this)\n\t\tthis.#tickHandler = this.#tick.bind(this)\n\t}\n\n\t/**\n\t * Opens the connection by replaying pending forms, subscribing, and arming keepalive handling.\n\t *\n\t * @returns The SSE response\n\t */\n\topen(): Response {\n\t\tif (this.#stream.closed || this.#request.signal.aborted) {\n\t\t\tthis.#destroy()\n\t\t\treturn this.#stream.response\n\t\t}\n\t\tif (this.#cancel !== undefined) return this.#stream.response\n\t\tfor (const form of this.#manager.pending(this.#name)) {\n\t\t\tthis.#write(serializePending(form))\n\t\t}\n\t\tthis.#manager.emitter.on('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.on('expire', this.#expireHandler)\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t\tthis.#request.signal.addEventListener('abort', this.#destroyHandler)\n\t\treturn this.#stream.response\n\t}\n\n\t#write(wire: WireEvent): void {\n\t\tthis.#stream.write({\n\t\t\tevent: wire.event,\n\t\t\tdata: wire.data,\n\t\t\t...(wire.id === undefined ? {} : { id: wire.id }),\n\t\t})\n\t}\n\n\t#pending(form: PendingForm): void {\n\t\tif (form.to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializePending(form))\n\t}\n\n\t#expire(to: string, id: string): void {\n\t\tif (to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializeExpire(id))\n\t}\n\n\t#tick(): void {\n\t\tif (this.#stream.closed || !this.#accepted()) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#stream.comment('')\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t}\n\n\t#accepted(): boolean {\n\t\ttry {\n\t\t\treturn this.#accepts(this.#presented)\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#destroy(): void {\n\t\tconst cancel = this.#cancel\n\t\tthis.#cancel = undefined\n\t\tcancel?.()\n\t\tthis.#manager.emitter.off('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.off('expire', this.#expireHandler)\n\t\tthis.#request.signal.removeEventListener('abort', this.#destroyHandler)\n\t\tthis.#stream.end()\n\t}\n}\n","import type { TerminalManagerInterface, TimerHandler } from '@orkestrel/terminal'\nimport type {\n\tTerminalRoute,\n\tTerminalRouteContext,\n\tTerminalRoutesOptions,\n\tTerminalToken,\n} from '../types.js'\nimport { defaultTimer, HEADER_TOKEN } from '@orkestrel/terminal'\nimport { isFormValues } from '@orkestrel/form'\nimport {\n\tisFiniteNumber,\n\tisInstance,\n\tisNonEmptyString,\n\tisRecord,\n\tparseJSON,\n} from '@orkestrel/contract'\nimport {\n\tcollectRequestBody,\n\tContentTooLargeError,\n\tcreateStream,\n\tDEFAULT_BODY_LIMIT,\n} from '@orkestrel/server'\nimport { TERMINAL_KEEPALIVE_MS, TERMINAL_ROUTES_PATH } from '../constants.js'\nimport { TerminalConnection } from './TerminalConnection.js'\n\n/**\n * Bridges a terminal manager onto the wire — owns the shared route options, the token gate, and\n * the bound GET stream and POST answer handlers.\n *\n * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalBridge {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #path: string\n\treadonly #token: TerminalToken | undefined\n\treadonly #keepalive: number\n\treadonly #timer: TimerHandler\n\treadonly #limit: number\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #get: TerminalRoute['handler']\n\treadonly #post: TerminalRoute['handler']\n\n\t/**\n\t * Creates a terminal bridge.\n\t *\n\t * @param manager - Terminal manager bridged onto HTTP\n\t * @param options - Shared route, authorization, keepalive, timer, and body-limit options\n\t */\n\tconstructor(manager: TerminalManagerInterface, options?: TerminalRoutesOptions) {\n\t\tthis.#manager = manager\n\t\tthis.#path = options?.path ?? TERMINAL_ROUTES_PATH\n\t\tthis.#token = options?.token\n\t\tthis.#keepalive = options?.keepalive ?? TERMINAL_KEEPALIVE_MS\n\t\tthis.#timer = options?.timer ?? defaultTimer\n\t\tconst limit = options?.limit\n\t\tthis.#limit =\n\t\t\tlimit === undefined || !isFiniteNumber(limit)\n\t\t\t\t? DEFAULT_BODY_LIMIT\n\t\t\t\t: Math.max(0, Math.floor(limit))\n\t\tthis.#accepts = this.#valid.bind(this)\n\t\tthis.#get = this.#handleGet.bind(this)\n\t\tthis.#post = this.#handlePost.bind(this)\n\t}\n\n\t/**\n\t * Projects the bound GET and POST route records.\n\t *\n\t * @returns The GET stream route followed by the POST answer route\n\t */\n\troutes(): readonly TerminalRoute[] {\n\t\treturn [\n\t\t\t{ method: 'GET', path: this.#path, handler: this.#get },\n\t\t\t{ method: 'POST', path: this.#path, handler: this.#post },\n\t\t]\n\t}\n\n\t#valid(presented: string | undefined): boolean {\n\t\tif (this.#token === undefined) return true\n\t\ttry {\n\t\t\treturn typeof this.#token === 'function' ? this.#token(presented) : presented === this.#token\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#authorized(request: Request): boolean {\n\t\treturn this.#valid(request.headers.get(HEADER_TOKEN) ?? undefined)\n\t}\n\n\t#handleGet(request: Request, context: TerminalRouteContext): Response {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tconst connection = new TerminalConnection(\n\t\t\tthis.#manager,\n\t\t\tname,\n\t\t\trequest,\n\t\t\tcreateStream(),\n\t\t\tthis.#accepts,\n\t\t\tthis.#timer,\n\t\t\tthis.#keepalive,\n\t\t)\n\t\treturn connection.open()\n\t}\n\n\tasync #handlePost(request: Request, context: TerminalRouteContext): Promise<Response> {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tconst bytes = await this.#readBody(request)\n\t\tif (bytes instanceof Response) return bytes\n\t\tif (bytes.byteLength > 0 && bytes.byteLength > this.#limit) {\n\t\t\treturn new Response(null, { status: 413 })\n\t\t}\n\n\t\tconst body = parseJSON(new TextDecoder().decode(bytes))\n\t\tif (body === undefined) {\n\t\t\treturn new Response(null, { status: 400 })\n\t\t}\n\t\tif (!isRecord(body) || !isNonEmptyString(body.id) || !isFormValues(body.values)) {\n\t\t\treturn new Response(null, { status: 422 })\n\t\t}\n\n\t\tconst result = this.#manager.answer(name, body.id, body.values)\n\t\tif (result.success) return Response.json(result)\n\t\tif (result.error.reason === 'target') return Response.json(result, { status: 404 })\n\t\treturn Response.json(result, { status: 422 })\n\t}\n\n\tasync #readBody(request: Request): Promise<Uint8Array | Response> {\n\t\ttry {\n\t\t\treturn await collectRequestBody(request, Math.max(1, this.#limit))\n\t\t} catch (error) {\n\t\t\tif (isInstance(error, ContentTooLargeError)) return new Response(null, { status: 413 })\n\t\t\tthrow error\n\t\t}\n\t}\n}\n","import type { TerminalManagerInterface } from '@orkestrel/terminal'\nimport type { TerminalRoute, TerminalRoutesOptions } from './types.js'\nimport { TerminalBridge } from './terminals/TerminalBridge.js'\n\n/**\n * Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.\n *\n * @remarks\n * The pair stays byte-compatible with `@orkestrel/terminal`'s own `PromptClient`: the same GET url\n * streams, the same POST url answers, the same `{ id, values }` request body, the same JSON answer\n * `Result` response body, and the same `x-orkestrel-token` header.\n *\n * Both routes share the configured `:name` path and optional token gate. The GET route replays\n * pending prompts, forwards live pending/expire events, and owns abort/keepalive teardown. The\n * POST route bounds the request body before parsing and maps answer outcomes to HTTP statuses.\n *\n * @param manager - The terminal manager whose endpoints are bridged\n * @param options - Route path, token, keepalive, timer, and body-limit options\n * @returns The GET route followed by the POST route\n *\n * @example\n * ```ts\n * import { createTerminalRoutes } from '@src/server'\n * import { createTerminalManager } from '@orkestrel/terminal'\n *\n * const manager = createTerminalManager()\n * manager.add('assistant')\n * const routes = createTerminalRoutes(manager, { token: 'secret' })\n * ```\n */\nexport function createTerminalRoutes(\n\tmanager: TerminalManagerInterface,\n\toptions?: TerminalRoutesOptions,\n): readonly TerminalRoute[] {\n\treturn new TerminalBridge(manager, options).routes()\n}\n"],"mappings":"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"}
@@ -2,7 +2,7 @@ import { HEADER_TOKEN, defaultTimer, serializeExpire, serializePending } from "@
2
2
  import { isFormValues } from "@orkestrel/form";
3
3
  import { isFiniteNumber, isInstance, isNonEmptyString, isRecord, parseJSON } from "@orkestrel/contract";
4
4
  import { ContentTooLargeError, DEFAULT_BODY_LIMIT, collectRequestBody, createStream } from "@orkestrel/server";
5
- //#region src/server/constants.ts
5
+ // src/server/constants.ts
6
6
  /**
7
7
  * Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}
8
8
  * mounts its GET (SSE) and POST (answer) routes under, `/terminals/:name`.
@@ -14,8 +14,7 @@ var TERMINAL_ROUTES_PATH = "/terminals/:name";
14
14
  * keeping intermediary proxies from timing out an otherwise-idle stream.
15
15
  */
16
16
  var TERMINAL_KEEPALIVE_MS = 15e3;
17
- //#endregion
18
- //#region src/server/terminals/TerminalConnection.ts
17
+ // src/server/terminals/TerminalConnection.ts
19
18
  /**
20
19
  * Owns one terminal SSE connection's replay, subscriptions, keepalive, and teardown.
21
20
  *
@@ -127,8 +126,7 @@ var TerminalConnection = class {
127
126
  this.#stream.end();
128
127
  }
129
128
  };
130
- //#endregion
131
- //#region src/server/terminals/TerminalBridge.ts
129
+ // src/server/terminals/TerminalBridge.ts
132
130
  /**
133
131
  * Bridges a terminal manager onto the wire — owns the shared route options, the token gate, and
134
132
  * the bound GET stream and POST answer handlers.
@@ -201,13 +199,8 @@ var TerminalBridge = class {
201
199
  if (!this.#authorized(request)) return new Response(null, { status: 401 });
202
200
  const name = context.params.name;
203
201
  if (name === void 0 || this.#manager.terminal(name) === void 0) return new Response(null, { status: 404 });
204
- let bytes;
205
- try {
206
- bytes = await collectRequestBody(request, Math.max(1, this.#limit));
207
- } catch (error) {
208
- if (isInstance(error, ContentTooLargeError)) return new Response(null, { status: 413 });
209
- throw error;
210
- }
202
+ const bytes = await this.#readBody(request);
203
+ if (bytes instanceof Response) return bytes;
211
204
  if (bytes.byteLength > 0 && bytes.byteLength > this.#limit) return new Response(null, { status: 413 });
212
205
  const body = parseJSON(new TextDecoder().decode(bytes));
213
206
  if (body === void 0) return new Response(null, { status: 400 });
@@ -217,9 +210,16 @@ var TerminalBridge = class {
217
210
  if (result.error.reason === "target") return Response.json(result, { status: 404 });
218
211
  return Response.json(result, { status: 422 });
219
212
  }
213
+ async #readBody(request) {
214
+ try {
215
+ return await collectRequestBody(request, Math.max(1, this.#limit));
216
+ } catch (error) {
217
+ if (isInstance(error, ContentTooLargeError)) return new Response(null, { status: 413 });
218
+ throw error;
219
+ }
220
+ }
220
221
  };
221
- //#endregion
222
- //#region src/server/factories.ts
222
+ // src/server/factories.ts
223
223
  /**
224
224
  * Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.
225
225
  *
@@ -249,7 +249,6 @@ var TerminalBridge = class {
249
249
  function createTerminalRoutes(manager, options) {
250
250
  return new TerminalBridge(manager, options).routes();
251
251
  }
252
- //#endregion
253
252
  export { TERMINAL_KEEPALIVE_MS, TERMINAL_ROUTES_PATH, createTerminalRoutes };
254
253
 
255
254
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../../src/server/constants.ts","../../../src/server/terminals/TerminalConnection.ts","../../../src/server/terminals/TerminalBridge.ts","../../../src/server/factories.ts"],"sourcesContent":["// Server-package constants — UPPER_SNAKE, `Object.freeze`d where structural, every member\n// exported.\n\n/**\n * Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}\n * mounts its GET (SSE) and POST (answer) routes under, `/terminals/:name`.\n */\nexport const TERMINAL_ROUTES_PATH = '/terminals/:name'\n\n/**\n * Holds the default SSE keepalive interval {@link import('./factories.js').createTerminalRoutes}\n * arms per open connection, `15_000` ms — a `: ` comment ping a conforming SSE parser ignores,\n * keeping intermediary proxies from timing out an otherwise-idle stream.\n */\nexport const TERMINAL_KEEPALIVE_MS = 15_000\n","import type {\n\tPendingForm,\n\tTerminalManagerInterface,\n\tTimerCancelFunction,\n\tTimerHandler,\n\tWireEvent,\n} from '@orkestrel/terminal'\nimport type { StreamInterface } from '@orkestrel/server'\nimport { HEADER_TOKEN, serializeExpire, serializePending } from '@orkestrel/terminal'\n\n/**\n * Owns one terminal SSE connection's replay, subscriptions, keepalive, and teardown.\n *\n * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalConnection {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #name: string\n\treadonly #request: Request\n\treadonly #stream: StreamInterface\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #timer: TimerHandler\n\treadonly #keepalive: number\n\treadonly #presented: string | undefined\n\t#cancel: TimerCancelFunction | undefined\n\treadonly #destroyHandler: () => void\n\treadonly #pendingHandler: (form: PendingForm) => void\n\treadonly #expireHandler: (to: string, id: string) => void\n\treadonly #tickHandler: () => void\n\n\t/**\n\t * Creates a terminal stream connection.\n\t *\n\t * @param manager - Terminal manager supplying pending forms and lifecycle events\n\t * @param name - Terminal endpoint streamed by this connection\n\t * @param request - Request whose abort signal owns the connection lifetime\n\t * @param stream - Open SSE stream\n\t * @param accepts - Presented-token validator\n\t * @param timer - Keepalive timer implementation\n\t * @param keepalive - Keepalive interval in milliseconds\n\t */\n\tconstructor(\n\t\tmanager: TerminalManagerInterface,\n\t\tname: string,\n\t\trequest: Request,\n\t\tstream: StreamInterface,\n\t\taccepts: (presented: string | undefined) => boolean,\n\t\ttimer: TimerHandler,\n\t\tkeepalive: number,\n\t) {\n\t\tthis.#manager = manager\n\t\tthis.#name = name\n\t\tthis.#request = request\n\t\tthis.#stream = stream\n\t\tthis.#accepts = accepts\n\t\tthis.#timer = timer\n\t\tthis.#keepalive = keepalive\n\t\tthis.#presented = request.headers.get(HEADER_TOKEN) ?? undefined\n\t\tthis.#destroyHandler = this.#destroy.bind(this)\n\t\tthis.#pendingHandler = this.#pending.bind(this)\n\t\tthis.#expireHandler = this.#expire.bind(this)\n\t\tthis.#tickHandler = this.#tick.bind(this)\n\t}\n\n\t/**\n\t * Opens the connection by replaying pending forms, subscribing, and arming keepalive handling.\n\t *\n\t * @returns The SSE response\n\t */\n\topen(): Response {\n\t\tif (this.#stream.closed || this.#request.signal.aborted) {\n\t\t\tthis.#destroy()\n\t\t\treturn this.#stream.response\n\t\t}\n\t\tif (this.#cancel !== undefined) return this.#stream.response\n\t\tfor (const form of this.#manager.pending(this.#name)) {\n\t\t\tthis.#write(serializePending(form))\n\t\t}\n\t\tthis.#manager.emitter.on('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.on('expire', this.#expireHandler)\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t\tthis.#request.signal.addEventListener('abort', this.#destroyHandler)\n\t\treturn this.#stream.response\n\t}\n\n\t#write(wire: WireEvent): void {\n\t\tthis.#stream.write({\n\t\t\tevent: wire.event,\n\t\t\tdata: wire.data,\n\t\t\t...(wire.id === undefined ? {} : { id: wire.id }),\n\t\t})\n\t}\n\n\t#pending(form: PendingForm): void {\n\t\tif (form.to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializePending(form))\n\t}\n\n\t#expire(to: string, id: string): void {\n\t\tif (to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializeExpire(id))\n\t}\n\n\t#tick(): void {\n\t\tif (this.#stream.closed || !this.#accepted()) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#stream.comment('')\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t}\n\n\t#accepted(): boolean {\n\t\ttry {\n\t\t\treturn this.#accepts(this.#presented)\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#destroy(): void {\n\t\tconst cancel = this.#cancel\n\t\tthis.#cancel = undefined\n\t\tcancel?.()\n\t\tthis.#manager.emitter.off('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.off('expire', this.#expireHandler)\n\t\tthis.#request.signal.removeEventListener('abort', this.#destroyHandler)\n\t\tthis.#stream.end()\n\t}\n}\n","import type { TerminalManagerInterface, TimerHandler } from '@orkestrel/terminal'\nimport type {\n\tTerminalRoute,\n\tTerminalRouteContext,\n\tTerminalRoutesOptions,\n\tTerminalToken,\n} from '../types.js'\nimport { defaultTimer, HEADER_TOKEN } from '@orkestrel/terminal'\nimport { isFormValues } from '@orkestrel/form'\nimport {\n\tisFiniteNumber,\n\tisInstance,\n\tisNonEmptyString,\n\tisRecord,\n\tparseJSON,\n} from '@orkestrel/contract'\nimport {\n\tcollectRequestBody,\n\tContentTooLargeError,\n\tcreateStream,\n\tDEFAULT_BODY_LIMIT,\n} from '@orkestrel/server'\nimport { TERMINAL_KEEPALIVE_MS, TERMINAL_ROUTES_PATH } from '../constants.js'\nimport { TerminalConnection } from './TerminalConnection.js'\n\n/**\n * Bridges a terminal manager onto the wire — owns the shared route options, the token gate, and\n * the bound GET stream and POST answer handlers.\n *\n * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalBridge {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #path: string\n\treadonly #token: TerminalToken | undefined\n\treadonly #keepalive: number\n\treadonly #timer: TimerHandler\n\treadonly #limit: number\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #get: TerminalRoute['handler']\n\treadonly #post: TerminalRoute['handler']\n\n\t/**\n\t * Creates a terminal bridge.\n\t *\n\t * @param manager - Terminal manager bridged onto HTTP\n\t * @param options - Shared route, authorization, keepalive, timer, and body-limit options\n\t */\n\tconstructor(manager: TerminalManagerInterface, options?: TerminalRoutesOptions) {\n\t\tthis.#manager = manager\n\t\tthis.#path = options?.path ?? TERMINAL_ROUTES_PATH\n\t\tthis.#token = options?.token\n\t\tthis.#keepalive = options?.keepalive ?? TERMINAL_KEEPALIVE_MS\n\t\tthis.#timer = options?.timer ?? defaultTimer\n\t\tconst limit = options?.limit\n\t\tthis.#limit =\n\t\t\tlimit === undefined || !isFiniteNumber(limit)\n\t\t\t\t? DEFAULT_BODY_LIMIT\n\t\t\t\t: Math.max(0, Math.floor(limit))\n\t\tthis.#accepts = this.#valid.bind(this)\n\t\tthis.#get = this.#handleGet.bind(this)\n\t\tthis.#post = this.#handlePost.bind(this)\n\t}\n\n\t/**\n\t * Projects the bound GET and POST route records.\n\t *\n\t * @returns The GET stream route followed by the POST answer route\n\t */\n\troutes(): readonly TerminalRoute[] {\n\t\treturn [\n\t\t\t{ method: 'GET', path: this.#path, handler: this.#get },\n\t\t\t{ method: 'POST', path: this.#path, handler: this.#post },\n\t\t]\n\t}\n\n\t#valid(presented: string | undefined): boolean {\n\t\tif (this.#token === undefined) return true\n\t\ttry {\n\t\t\treturn typeof this.#token === 'function' ? this.#token(presented) : presented === this.#token\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#authorized(request: Request): boolean {\n\t\treturn this.#valid(request.headers.get(HEADER_TOKEN) ?? undefined)\n\t}\n\n\t#handleGet(request: Request, context: TerminalRouteContext): Response {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tconst connection = new TerminalConnection(\n\t\t\tthis.#manager,\n\t\t\tname,\n\t\t\trequest,\n\t\t\tcreateStream(),\n\t\t\tthis.#accepts,\n\t\t\tthis.#timer,\n\t\t\tthis.#keepalive,\n\t\t)\n\t\treturn connection.open()\n\t}\n\n\tasync #handlePost(request: Request, context: TerminalRouteContext): Promise<Response> {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tlet bytes: Uint8Array\n\t\ttry {\n\t\t\tbytes = await collectRequestBody(request, Math.max(1, this.#limit))\n\t\t} catch (error) {\n\t\t\tif (isInstance(error, ContentTooLargeError)) return new Response(null, { status: 413 })\n\t\t\tthrow error\n\t\t}\n\t\tif (bytes.byteLength > 0 && bytes.byteLength > this.#limit) {\n\t\t\treturn new Response(null, { status: 413 })\n\t\t}\n\n\t\tconst body = parseJSON(new TextDecoder().decode(bytes))\n\t\tif (body === undefined) {\n\t\t\treturn new Response(null, { status: 400 })\n\t\t}\n\t\tif (!isRecord(body) || !isNonEmptyString(body.id) || !isFormValues(body.values)) {\n\t\t\treturn new Response(null, { status: 422 })\n\t\t}\n\n\t\tconst result = this.#manager.answer(name, body.id, body.values)\n\t\tif (result.success) return Response.json(result)\n\t\tif (result.error.reason === 'target') return Response.json(result, { status: 404 })\n\t\treturn Response.json(result, { status: 422 })\n\t}\n}\n","import type { TerminalManagerInterface } from '@orkestrel/terminal'\nimport type { TerminalRoute, TerminalRoutesOptions } from './types.js'\nimport { TerminalBridge } from './terminals/TerminalBridge.js'\n\n/**\n * Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.\n *\n * @remarks\n * The pair stays byte-compatible with `@orkestrel/terminal`'s own `PromptClient`: the same GET url\n * streams, the same POST url answers, the same `{ id, values }` request body, the same JSON answer\n * `Result` response body, and the same `x-orkestrel-token` header.\n *\n * Both routes share the configured `:name` path and optional token gate. The GET route replays\n * pending prompts, forwards live pending/expire events, and owns abort/keepalive teardown. The\n * POST route bounds the request body before parsing and maps answer outcomes to HTTP statuses.\n *\n * @param manager - The terminal manager whose endpoints are bridged\n * @param options - Route path, token, keepalive, timer, and body-limit options\n * @returns The GET route followed by the POST route\n *\n * @example\n * ```ts\n * import { createTerminalRoutes } from '@src/server'\n * import { createTerminalManager } from '@orkestrel/terminal'\n *\n * const manager = createTerminalManager()\n * manager.add('assistant')\n * const routes = createTerminalRoutes(manager, { token: 'secret' })\n * ```\n */\nexport function createTerminalRoutes(\n\tmanager: TerminalManagerInterface,\n\toptions?: TerminalRoutesOptions,\n): readonly TerminalRoute[] {\n\treturn new TerminalBridge(manager, options).routes()\n}\n"],"mappings":";;;;;;;;;AAOA,IAAa,uBAAuB;;;;;;AAOpC,IAAa,wBAAwB;;;;;;;;;ACErC,IAAa,qBAAb,MAAgC;CAC/B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;;;;;;;;;;;CAaA,YACC,SACA,MACA,SACA,QACA,SACA,OACA,WACC;EACD,KAAK,WAAW;EAChB,KAAK,QAAQ;EACb,KAAK,WAAW;EAChB,KAAK,UAAU;EACf,KAAK,WAAW;EAChB,KAAK,SAAS;EACd,KAAK,aAAa;EAClB,KAAK,aAAa,QAAQ,QAAQ,IAAI,YAAY,KAAK,KAAA;EACvD,KAAK,kBAAkB,KAAK,SAAS,KAAK,IAAI;EAC9C,KAAK,kBAAkB,KAAK,SAAS,KAAK,IAAI;EAC9C,KAAK,iBAAiB,KAAK,QAAQ,KAAK,IAAI;EAC5C,KAAK,eAAe,KAAK,MAAM,KAAK,IAAI;CACzC;;;;;;CAOA,OAAiB;EAChB,IAAI,KAAK,QAAQ,UAAU,KAAK,SAAS,OAAO,SAAS;GACxD,KAAK,SAAS;GACd,OAAO,KAAK,QAAQ;EACrB;EACA,IAAI,KAAK,YAAY,KAAA,GAAW,OAAO,KAAK,QAAQ;EACpD,KAAK,MAAM,QAAQ,KAAK,SAAS,QAAQ,KAAK,KAAK,GAClD,KAAK,OAAO,iBAAiB,IAAI,CAAC;EAEnC,KAAK,SAAS,QAAQ,GAAG,WAAW,KAAK,eAAe;EACxD,KAAK,SAAS,QAAQ,GAAG,UAAU,KAAK,cAAc;EACtD,KAAK,UAAU,KAAK,OAAO,KAAK,cAAc,KAAK,UAAU;EAC7D,KAAK,SAAS,OAAO,iBAAiB,SAAS,KAAK,eAAe;EACnE,OAAO,KAAK,QAAQ;CACrB;CAEA,OAAO,MAAuB;EAC7B,KAAK,QAAQ,MAAM;GAClB,OAAO,KAAK;GACZ,MAAM,KAAK;GACX,GAAI,KAAK,OAAO,KAAA,IAAY,CAAC,IAAI,EAAE,IAAI,KAAK,GAAG;EAChD,CAAC;CACF;CAEA,SAAS,MAAyB;EACjC,IAAI,KAAK,OAAO,KAAK,OAAO;EAC5B,IAAI,KAAK,QAAQ,QAAQ;GACxB,KAAK,SAAS;GACd;EACD;EACA,KAAK,OAAO,iBAAiB,IAAI,CAAC;CACnC;CAEA,QAAQ,IAAY,IAAkB;EACrC,IAAI,OAAO,KAAK,OAAO;EACvB,IAAI,KAAK,QAAQ,QAAQ;GACxB,KAAK,SAAS;GACd;EACD;EACA,KAAK,OAAO,gBAAgB,EAAE,CAAC;CAChC;CAEA,QAAc;EACb,IAAI,KAAK,QAAQ,UAAU,CAAC,KAAK,UAAU,GAAG;GAC7C,KAAK,SAAS;GACd;EACD;EACA,KAAK,QAAQ,QAAQ,EAAE;EACvB,KAAK,UAAU,KAAK,OAAO,KAAK,cAAc,KAAK,UAAU;CAC9D;CAEA,YAAqB;EACpB,IAAI;GACH,OAAO,KAAK,SAAS,KAAK,UAAU;EACrC,QAAQ;GACP,OAAO;EACR;CACD;CAEA,WAAiB;EAChB,MAAM,SAAS,KAAK;EACpB,KAAK,UAAU,KAAA;EACf,SAAS;EACT,KAAK,SAAS,QAAQ,IAAI,WAAW,KAAK,eAAe;EACzD,KAAK,SAAS,QAAQ,IAAI,UAAU,KAAK,cAAc;EACvD,KAAK,SAAS,OAAO,oBAAoB,SAAS,KAAK,eAAe;EACtE,KAAK,QAAQ,IAAI;CAClB;AACD;;;;;;;;;;AC1GA,IAAa,iBAAb,MAA4B;CAC3B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;;;;;;;CAQA,YAAY,SAAmC,SAAiC;EAC/E,KAAK,WAAW;EAChB,KAAK,QAAQ,SAAS,QAAA;EACtB,KAAK,SAAS,SAAS;EACvB,KAAK,aAAa,SAAS,aAAA;EAC3B,KAAK,SAAS,SAAS,SAAS;EAChC,MAAM,QAAQ,SAAS;EACvB,KAAK,SACJ,UAAU,KAAA,KAAa,CAAC,eAAe,KAAK,IACzC,qBACA,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC;EACjC,KAAK,WAAW,KAAK,OAAO,KAAK,IAAI;EACrC,KAAK,OAAO,KAAK,WAAW,KAAK,IAAI;EACrC,KAAK,QAAQ,KAAK,YAAY,KAAK,IAAI;CACxC;;;;;;CAOA,SAAmC;EAClC,OAAO,CACN;GAAE,QAAQ;GAAO,MAAM,KAAK;GAAO,SAAS,KAAK;EAAK,GACtD;GAAE,QAAQ;GAAQ,MAAM,KAAK;GAAO,SAAS,KAAK;EAAM,CACzD;CACD;CAEA,OAAO,WAAwC;EAC9C,IAAI,KAAK,WAAW,KAAA,GAAW,OAAO;EACtC,IAAI;GACH,OAAO,OAAO,KAAK,WAAW,aAAa,KAAK,OAAO,SAAS,IAAI,cAAc,KAAK;EACxF,QAAQ;GACP,OAAO;EACR;CACD;CAEA,YAAY,SAA2B;EACtC,OAAO,KAAK,OAAO,QAAQ,QAAQ,IAAI,YAAY,KAAK,KAAA,CAAS;CAClE;CAEA,WAAW,SAAkB,SAAyC;EACrE,IAAI,CAAC,KAAK,YAAY,OAAO,GAAG,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;EACzE,MAAM,OAAO,QAAQ,OAAO;EAC5B,IAAI,SAAS,KAAA,KAAa,KAAK,SAAS,SAAS,IAAI,MAAM,KAAA,GAC1D,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;EAW1C,OAAO,IATgB,mBACtB,KAAK,UACL,MACA,SACA,aAAa,GACb,KAAK,UACL,KAAK,QACL,KAAK,UAEC,CAAA,CAAW,KAAK;CACxB;CAEA,MAAM,YAAY,SAAkB,SAAkD;EACrF,IAAI,CAAC,KAAK,YAAY,OAAO,GAAG,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;EACzE,MAAM,OAAO,QAAQ,OAAO;EAC5B,IAAI,SAAS,KAAA,KAAa,KAAK,SAAS,SAAS,IAAI,MAAM,KAAA,GAC1D,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;EAE1C,IAAI;EACJ,IAAI;GACH,QAAQ,MAAM,mBAAmB,SAAS,KAAK,IAAI,GAAG,KAAK,MAAM,CAAC;EACnE,SAAS,OAAO;GACf,IAAI,WAAW,OAAO,oBAAoB,GAAG,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;GACtF,MAAM;EACP;EACA,IAAI,MAAM,aAAa,KAAK,MAAM,aAAa,KAAK,QACnD,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;EAG1C,MAAM,OAAO,UAAU,IAAI,YAAY,CAAC,CAAC,OAAO,KAAK,CAAC;EACtD,IAAI,SAAS,KAAA,GACZ,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;EAE1C,IAAI,CAAC,SAAS,IAAI,KAAK,CAAC,iBAAiB,KAAK,EAAE,KAAK,CAAC,aAAa,KAAK,MAAM,GAC7E,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;EAG1C,MAAM,SAAS,KAAK,SAAS,OAAO,MAAM,KAAK,IAAI,KAAK,MAAM;EAC9D,IAAI,OAAO,SAAS,OAAO,SAAS,KAAK,MAAM;EAC/C,IAAI,OAAO,MAAM,WAAW,UAAU,OAAO,SAAS,KAAK,QAAQ,EAAE,QAAQ,IAAI,CAAC;EAClF,OAAO,SAAS,KAAK,QAAQ,EAAE,QAAQ,IAAI,CAAC;CAC7C;AACD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AC5GA,SAAgB,qBACf,SACA,SAC2B;CAC3B,OAAO,IAAI,eAAe,SAAS,OAAO,CAAC,CAAC,OAAO;AACpD"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../src/server/constants.ts","../../../src/server/terminals/TerminalConnection.ts","../../../src/server/terminals/TerminalBridge.ts","../../../src/server/factories.ts"],"sourcesContent":["// Server-package constants — UPPER_SNAKE, `Object.freeze`d where structural, every member\n// exported.\n\n/**\n * Holds the default `:name`-templated path {@link import('./factories.js').createTerminalRoutes}\n * mounts its GET (SSE) and POST (answer) routes under, `/terminals/:name`.\n */\nexport const TERMINAL_ROUTES_PATH = '/terminals/:name'\n\n/**\n * Holds the default SSE keepalive interval {@link import('./factories.js').createTerminalRoutes}\n * arms per open connection, `15_000` ms — a `: ` comment ping a conforming SSE parser ignores,\n * keeping intermediary proxies from timing out an otherwise-idle stream.\n */\nexport const TERMINAL_KEEPALIVE_MS = 15_000\n","import type {\n\tPendingForm,\n\tTerminalManagerInterface,\n\tTimerCancelFunction,\n\tTimerHandler,\n\tWireEvent,\n} from '@orkestrel/terminal'\nimport type { StreamInterface } from '@orkestrel/server'\nimport { HEADER_TOKEN, serializeExpire, serializePending } from '@orkestrel/terminal'\n\n/**\n * Owns one terminal SSE connection's replay, subscriptions, keepalive, and teardown.\n *\n * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalConnection {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #name: string\n\treadonly #request: Request\n\treadonly #stream: StreamInterface\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #timer: TimerHandler\n\treadonly #keepalive: number\n\treadonly #presented: string | undefined\n\t#cancel: TimerCancelFunction | undefined\n\treadonly #destroyHandler: () => void\n\treadonly #pendingHandler: (form: PendingForm) => void\n\treadonly #expireHandler: (to: string, id: string) => void\n\treadonly #tickHandler: () => void\n\n\t/**\n\t * Creates a terminal stream connection.\n\t *\n\t * @param manager - Terminal manager supplying pending forms and lifecycle events\n\t * @param name - Terminal endpoint streamed by this connection\n\t * @param request - Request whose abort signal owns the connection lifetime\n\t * @param stream - Open SSE stream\n\t * @param accepts - Presented-token validator\n\t * @param timer - Keepalive timer implementation\n\t * @param keepalive - Keepalive interval in milliseconds\n\t */\n\tconstructor(\n\t\tmanager: TerminalManagerInterface,\n\t\tname: string,\n\t\trequest: Request,\n\t\tstream: StreamInterface,\n\t\taccepts: (presented: string | undefined) => boolean,\n\t\ttimer: TimerHandler,\n\t\tkeepalive: number,\n\t) {\n\t\tthis.#manager = manager\n\t\tthis.#name = name\n\t\tthis.#request = request\n\t\tthis.#stream = stream\n\t\tthis.#accepts = accepts\n\t\tthis.#timer = timer\n\t\tthis.#keepalive = keepalive\n\t\tthis.#presented = request.headers.get(HEADER_TOKEN) ?? undefined\n\t\tthis.#destroyHandler = this.#destroy.bind(this)\n\t\tthis.#pendingHandler = this.#pending.bind(this)\n\t\tthis.#expireHandler = this.#expire.bind(this)\n\t\tthis.#tickHandler = this.#tick.bind(this)\n\t}\n\n\t/**\n\t * Opens the connection by replaying pending forms, subscribing, and arming keepalive handling.\n\t *\n\t * @returns The SSE response\n\t */\n\topen(): Response {\n\t\tif (this.#stream.closed || this.#request.signal.aborted) {\n\t\t\tthis.#destroy()\n\t\t\treturn this.#stream.response\n\t\t}\n\t\tif (this.#cancel !== undefined) return this.#stream.response\n\t\tfor (const form of this.#manager.pending(this.#name)) {\n\t\t\tthis.#write(serializePending(form))\n\t\t}\n\t\tthis.#manager.emitter.on('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.on('expire', this.#expireHandler)\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t\tthis.#request.signal.addEventListener('abort', this.#destroyHandler)\n\t\treturn this.#stream.response\n\t}\n\n\t#write(wire: WireEvent): void {\n\t\tthis.#stream.write({\n\t\t\tevent: wire.event,\n\t\t\tdata: wire.data,\n\t\t\t...(wire.id === undefined ? {} : { id: wire.id }),\n\t\t})\n\t}\n\n\t#pending(form: PendingForm): void {\n\t\tif (form.to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializePending(form))\n\t}\n\n\t#expire(to: string, id: string): void {\n\t\tif (to !== this.#name) return\n\t\tif (this.#stream.closed) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#write(serializeExpire(id))\n\t}\n\n\t#tick(): void {\n\t\tif (this.#stream.closed || !this.#accepted()) {\n\t\t\tthis.#destroy()\n\t\t\treturn\n\t\t}\n\t\tthis.#stream.comment('')\n\t\tthis.#cancel = this.#timer(this.#tickHandler, this.#keepalive)\n\t}\n\n\t#accepted(): boolean {\n\t\ttry {\n\t\t\treturn this.#accepts(this.#presented)\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#destroy(): void {\n\t\tconst cancel = this.#cancel\n\t\tthis.#cancel = undefined\n\t\tcancel?.()\n\t\tthis.#manager.emitter.off('pending', this.#pendingHandler)\n\t\tthis.#manager.emitter.off('expire', this.#expireHandler)\n\t\tthis.#request.signal.removeEventListener('abort', this.#destroyHandler)\n\t\tthis.#stream.end()\n\t}\n}\n","import type { TerminalManagerInterface, TimerHandler } from '@orkestrel/terminal'\nimport type {\n\tTerminalRoute,\n\tTerminalRouteContext,\n\tTerminalRoutesOptions,\n\tTerminalToken,\n} from '../types.js'\nimport { defaultTimer, HEADER_TOKEN } from '@orkestrel/terminal'\nimport { isFormValues } from '@orkestrel/form'\nimport {\n\tisFiniteNumber,\n\tisInstance,\n\tisNonEmptyString,\n\tisRecord,\n\tparseJSON,\n} from '@orkestrel/contract'\nimport {\n\tcollectRequestBody,\n\tContentTooLargeError,\n\tcreateStream,\n\tDEFAULT_BODY_LIMIT,\n} from '@orkestrel/server'\nimport { TERMINAL_KEEPALIVE_MS, TERMINAL_ROUTES_PATH } from '../constants.js'\nimport { TerminalConnection } from './TerminalConnection.js'\n\n/**\n * Bridges a terminal manager onto the wire — owns the shared route options, the token gate, and\n * the bound GET stream and POST answer handlers.\n *\n * Not exported from the `@orkestrel/toolbox/server` barrel — reach this behaviour through\n * `createTerminalRoutes`.\n */\nexport class TerminalBridge {\n\treadonly #manager: TerminalManagerInterface\n\treadonly #path: string\n\treadonly #token: TerminalToken | undefined\n\treadonly #keepalive: number\n\treadonly #timer: TimerHandler\n\treadonly #limit: number\n\treadonly #accepts: (presented: string | undefined) => boolean\n\treadonly #get: TerminalRoute['handler']\n\treadonly #post: TerminalRoute['handler']\n\n\t/**\n\t * Creates a terminal bridge.\n\t *\n\t * @param manager - Terminal manager bridged onto HTTP\n\t * @param options - Shared route, authorization, keepalive, timer, and body-limit options\n\t */\n\tconstructor(manager: TerminalManagerInterface, options?: TerminalRoutesOptions) {\n\t\tthis.#manager = manager\n\t\tthis.#path = options?.path ?? TERMINAL_ROUTES_PATH\n\t\tthis.#token = options?.token\n\t\tthis.#keepalive = options?.keepalive ?? TERMINAL_KEEPALIVE_MS\n\t\tthis.#timer = options?.timer ?? defaultTimer\n\t\tconst limit = options?.limit\n\t\tthis.#limit =\n\t\t\tlimit === undefined || !isFiniteNumber(limit)\n\t\t\t\t? DEFAULT_BODY_LIMIT\n\t\t\t\t: Math.max(0, Math.floor(limit))\n\t\tthis.#accepts = this.#valid.bind(this)\n\t\tthis.#get = this.#handleGet.bind(this)\n\t\tthis.#post = this.#handlePost.bind(this)\n\t}\n\n\t/**\n\t * Projects the bound GET and POST route records.\n\t *\n\t * @returns The GET stream route followed by the POST answer route\n\t */\n\troutes(): readonly TerminalRoute[] {\n\t\treturn [\n\t\t\t{ method: 'GET', path: this.#path, handler: this.#get },\n\t\t\t{ method: 'POST', path: this.#path, handler: this.#post },\n\t\t]\n\t}\n\n\t#valid(presented: string | undefined): boolean {\n\t\tif (this.#token === undefined) return true\n\t\ttry {\n\t\t\treturn typeof this.#token === 'function' ? this.#token(presented) : presented === this.#token\n\t\t} catch {\n\t\t\treturn false\n\t\t}\n\t}\n\n\t#authorized(request: Request): boolean {\n\t\treturn this.#valid(request.headers.get(HEADER_TOKEN) ?? undefined)\n\t}\n\n\t#handleGet(request: Request, context: TerminalRouteContext): Response {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tconst connection = new TerminalConnection(\n\t\t\tthis.#manager,\n\t\t\tname,\n\t\t\trequest,\n\t\t\tcreateStream(),\n\t\t\tthis.#accepts,\n\t\t\tthis.#timer,\n\t\t\tthis.#keepalive,\n\t\t)\n\t\treturn connection.open()\n\t}\n\n\tasync #handlePost(request: Request, context: TerminalRouteContext): Promise<Response> {\n\t\tif (!this.#authorized(request)) return new Response(null, { status: 401 })\n\t\tconst name = context.params.name\n\t\tif (name === undefined || this.#manager.terminal(name) === undefined) {\n\t\t\treturn new Response(null, { status: 404 })\n\t\t}\n\t\tconst bytes = await this.#readBody(request)\n\t\tif (bytes instanceof Response) return bytes\n\t\tif (bytes.byteLength > 0 && bytes.byteLength > this.#limit) {\n\t\t\treturn new Response(null, { status: 413 })\n\t\t}\n\n\t\tconst body = parseJSON(new TextDecoder().decode(bytes))\n\t\tif (body === undefined) {\n\t\t\treturn new Response(null, { status: 400 })\n\t\t}\n\t\tif (!isRecord(body) || !isNonEmptyString(body.id) || !isFormValues(body.values)) {\n\t\t\treturn new Response(null, { status: 422 })\n\t\t}\n\n\t\tconst result = this.#manager.answer(name, body.id, body.values)\n\t\tif (result.success) return Response.json(result)\n\t\tif (result.error.reason === 'target') return Response.json(result, { status: 404 })\n\t\treturn Response.json(result, { status: 422 })\n\t}\n\n\tasync #readBody(request: Request): Promise<Uint8Array | Response> {\n\t\ttry {\n\t\t\treturn await collectRequestBody(request, Math.max(1, this.#limit))\n\t\t} catch (error) {\n\t\t\tif (isInstance(error, ContentTooLargeError)) return new Response(null, { status: 413 })\n\t\t\tthrow error\n\t\t}\n\t}\n}\n","import type { TerminalManagerInterface } from '@orkestrel/terminal'\nimport type { TerminalRoute, TerminalRoutesOptions } from './types.js'\nimport { TerminalBridge } from './terminals/TerminalBridge.js'\n\n/**\n * Builds the GET SSE stream and POST answer routes that bridge a terminal manager onto the wire.\n *\n * @remarks\n * The pair stays byte-compatible with `@orkestrel/terminal`'s own `PromptClient`: the same GET url\n * streams, the same POST url answers, the same `{ id, values }` request body, the same JSON answer\n * `Result` response body, and the same `x-orkestrel-token` header.\n *\n * Both routes share the configured `:name` path and optional token gate. The GET route replays\n * pending prompts, forwards live pending/expire events, and owns abort/keepalive teardown. The\n * POST route bounds the request body before parsing and maps answer outcomes to HTTP statuses.\n *\n * @param manager - The terminal manager whose endpoints are bridged\n * @param options - Route path, token, keepalive, timer, and body-limit options\n * @returns The GET route followed by the POST route\n *\n * @example\n * ```ts\n * import { createTerminalRoutes } from '@src/server'\n * import { createTerminalManager } from '@orkestrel/terminal'\n *\n * const manager = createTerminalManager()\n * manager.add('assistant')\n * const routes = createTerminalRoutes(manager, { token: 'secret' })\n * ```\n */\nexport function createTerminalRoutes(\n\tmanager: TerminalManagerInterface,\n\toptions?: TerminalRoutesOptions,\n): readonly TerminalRoute[] {\n\treturn new TerminalBridge(manager, options).routes()\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orkestrel/toolbox",
3
- "version": "0.0.21",
3
+ "version": "0.0.22",
4
4
  "description": "Concrete LLM-callable tools for the @orkestrel line — workflow authoring, workspace editing, sub-agent delegation, and more, over the @orkestrel/tool runtime, with pluggable stores. Part of the @orkestrel line.",
5
5
  "keywords": [
6
6
  "agent",
@@ -79,33 +79,33 @@
79
79
  "prepublishOnly": "npm run format:check && npm run lint:check && npm run check && npm run build && npm test && npm run test:distribution -- --mode release",
80
80
  "test:distribution": "vitest run --config vite.config.ts --no-cache --reporter=dot --project distribution",
81
81
  "test:probe": "vitest run --config vite.config.ts --no-cache --reporter=verbose --project probe",
82
- "test:bench": "vitest bench --config vite.config.ts --no-cache --project probe",
82
+ "test:bench": "vitest bench --config vite.config.ts --no-cache --project \"probe (bench)\" --mode benchmark",
83
83
  "test:setup": "vitest run --config vite.config.ts --no-cache --reporter=dot --project setup"
84
84
  },
85
85
  "dependencies": {
86
- "@orkestrel/agent": "^0.0.32",
87
- "@orkestrel/contract": "^0.0.19",
88
- "@orkestrel/database": "^0.0.18",
89
- "@orkestrel/form": "^0.0.13",
90
- "@orkestrel/relation": "^0.0.16",
91
- "@orkestrel/server": "^0.0.23",
92
- "@orkestrel/terminal": "^0.0.20",
93
- "@orkestrel/tool": "^0.0.19",
94
- "@orkestrel/workflow": "^0.0.23",
95
- "@orkestrel/workspace": "^0.0.12"
86
+ "@orkestrel/agent": "^0.0.33",
87
+ "@orkestrel/contract": "^0.0.20",
88
+ "@orkestrel/database": "^0.0.19",
89
+ "@orkestrel/form": "^0.0.14",
90
+ "@orkestrel/relation": "^0.0.17",
91
+ "@orkestrel/server": "^0.0.24",
92
+ "@orkestrel/terminal": "^0.0.21",
93
+ "@orkestrel/tool": "^0.0.20",
94
+ "@orkestrel/workflow": "^0.0.24",
95
+ "@orkestrel/workspace": "^0.0.13"
96
96
  },
97
97
  "devDependencies": {
98
98
  "@microsoft/api-extractor": "^7.60.0",
99
- "@orkestrel/guide": "^0.0.25",
100
- "@orkestrel/probe": "^0.0.24",
101
- "@orkestrel/scaffold": "^0.0.107",
102
- "@orkestrel/test": "^0.0.29",
99
+ "@orkestrel/guide": "^0.0.26",
100
+ "@orkestrel/probe": "^0.0.26",
101
+ "@orkestrel/scaffold": "^0.0.113",
102
+ "@orkestrel/test": "^0.0.31",
103
103
  "@types/node": "^24.19.2",
104
104
  "oxfmt": "^0.75.0",
105
105
  "oxlint": "^1.90.0",
106
- "typescript": "^6.0.3",
106
+ "typescript": "^7.0.2",
107
107
  "vite": "^8.3.5",
108
- "vitest": "^4.1.12"
108
+ "vitest": "^5.0.5"
109
109
  },
110
110
  "engines": {
111
111
  "node": ">=22.18.0"