@workerdeck/server 0.6.0 → 0.9.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/README.md +5 -1
- package/build/index.d.mts +137 -5
- package/build/index.mjs +1093 -71
- package/build/index.mjs.map +1 -1
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -39,7 +39,8 @@ import { createWorkerServer } from '@workerdeck/server'
|
|
|
39
39
|
|
|
40
40
|
const worker = createWorkerServer({
|
|
41
41
|
authenticate: async (req) => verifyMyAppToken(req.headers.authorization),
|
|
42
|
-
allowedCwdRoots: ['/srv/checkouts'], //
|
|
42
|
+
allowedCwdRoots: ['/srv/checkouts'], // where sessions may run — and what /fs serves
|
|
43
|
+
hostFiles: { write: true }, // /fs reads follow the roots above; writing opts in
|
|
43
44
|
buildRunnerConfig: (req) => ({ ...req, env: { ...process.env } }),
|
|
44
45
|
requireApiKey: true, // fail closed on subscription credentials
|
|
45
46
|
})
|
|
@@ -57,6 +58,9 @@ Routes (default `basePath: '/v1'`):
|
|
|
57
58
|
| `POST /v1/sessions/:id/permissions/:requestId` | Resolve a pending approval over REST |
|
|
58
59
|
| `GET /v1/sdk-sessions?dir=…` | List the Agent SDK's on-disk sessions to offer resume |
|
|
59
60
|
| `GET /v1/sessions/:id/files`, `…/files/<path>` | List and download a session's scratch-filesystem deliverables |
|
|
61
|
+
| `GET /v1/fs/roots`, `/fs/list?path=`, `/fs/read?path=` | Browse and read the **host's** real tree (the `allowedCwdRoots` trees, unless `hostFiles.roots` narrows them; 404 when neither is set) |
|
|
62
|
+
| `GET /v1/fs/find?path=&q=` | Recursive fuzzy file search under one directory — what backs `@file` completion |
|
|
63
|
+
| `PUT /v1/fs/write` | Save a host file — needs `hostFiles.write`, and always carries the hash it replaces |
|
|
60
64
|
| `POST /v1/executions/:executionId/result` | Deliver a deferred execution's result, waking a parked session |
|
|
61
65
|
| `GET /v1/profiles`, `GET /v1/profiles/:name` | What sessions may run as (+ a view-only config snapshot) |
|
|
62
66
|
| `GET/POST /v1/jobs`, `GET/DELETE /v1/jobs/:id` | Job queue (when `queue` is configured) |
|
package/build/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { IncomingMessage, Server, ServerResponse } from "node:http";
|
|
2
|
-
import { BridgeAnswer, BrowserBridgeExecutor, ClaudeAuthProbe, ParkedExecution, Runner, RunnerSnapshot, SessionRunnerConfig, ToolExecutionResult } from "@workerdeck/core";
|
|
2
|
+
import { AttachmentInput, BridgeAnswer, BrowserBridgeExecutor, ClaudeAuthProbe, EngineAdapter, ParkedExecution, Runner, RunnerSnapshot, SessionRunnerConfig, ToolExecutionResult } from "@workerdeck/core";
|
|
3
3
|
import { JobQueue, QueueAdapter } from "@workerdeck/queue";
|
|
4
|
-
import { CreateSessionRequest, JobEvent, ProfileInfo, SdkSessionSummary, ServerFrame, SessionInfo, SessionNotification, SessionWebhookConfig } from "@workerdeck/protocol";
|
|
4
|
+
import { CreateSessionRequest, JobEvent, MessageAttachment, ProfileEngine, ProfileInfo, SdkSessionSummary, ServerFrame, SessionInfo, SessionNotification, SessionWebhookConfig } from "@workerdeck/protocol";
|
|
5
5
|
|
|
6
6
|
//#region src/registry.d.ts
|
|
7
7
|
type SessionRegistryOptions = {
|
|
@@ -327,6 +327,62 @@ type WorkerServerOptions = {
|
|
|
327
327
|
/** Required unless `allowUnauthenticated: true` — the worker must never be exposed bare. */authenticate?: Authenticator; /** Explicit opt-in to run without auth (local dev only). */
|
|
328
328
|
allowUnauthenticated?: boolean; /** If set, session cwd must resolve inside one of these roots. Strongly recommended. */
|
|
329
329
|
allowedCwdRoots?: string[];
|
|
330
|
+
/**
|
|
331
|
+
* The host filesystem routes (`{basePath}/fs/*`) — browse and read the
|
|
332
|
+
* operator's real project tree, and optionally write to it.
|
|
333
|
+
*
|
|
334
|
+
* **Reading follows {@link allowedCwdRoots} and needs no grant of its own.**
|
|
335
|
+
* A caller holding the auth key can already start a session in any allowed root
|
|
336
|
+
* and have the agent read whatever is in it, so serving those same trees over
|
|
337
|
+
* `/fs` adds no authority — it only removes the absurdity of going through a
|
|
338
|
+
* language model to `cat` a file. Set `roots` here only to *narrow* that (or to
|
|
339
|
+
* expose a tree sessions may not run in).
|
|
340
|
+
*
|
|
341
|
+
* With neither set the routes 404. That is not the same as inheriting
|
|
342
|
+
* `allowedCwdRoots`' permissive "unset means anywhere": no cwd policy means
|
|
343
|
+
* there is nothing to inherit, and "anywhere" is a statement about paths the
|
|
344
|
+
* operator types at a keyboard, not one about what a phone may read.
|
|
345
|
+
*
|
|
346
|
+
* **Writing is a separate opt-in**, because it is the one part that is not
|
|
347
|
+
* already implied. An agent's writes go through the permission flow; a `PUT` to
|
|
348
|
+
* `/fs/write` does not. These routes are operator-privileged by design — the
|
|
349
|
+
* caller is the operator — but that is a reason to make the bypass deliberate,
|
|
350
|
+
* not a reason to skip the switch.
|
|
351
|
+
*
|
|
352
|
+
* Containment is *not* `cwdAllowed`, whichever roots are in play: these routes
|
|
353
|
+
* walk paths the agent may have authored, so a symlink can escape a lexical
|
|
354
|
+
* prefix check. See `host-files.ts` — canonicalize, then re-check.
|
|
355
|
+
*/
|
|
356
|
+
hostFiles?: {
|
|
357
|
+
/** Absolute paths. Unset inherits {@link allowedCwdRoots}; an explicit empty
|
|
358
|
+
* array disables the routes (a policy, not an absence). */
|
|
359
|
+
roots?: string[]; /** Enable `PUT {basePath}/fs/write`. Default false — read-only. */
|
|
360
|
+
write?: boolean;
|
|
361
|
+
/** Refuse reads above this (413) rather than streaming a gigabyte to a phone.
|
|
362
|
+
* Default 1 MiB. Writes are bounded by {@link maxBodyBytes} instead. */
|
|
363
|
+
maxFileBytes?: number;
|
|
364
|
+
/** Cap on entries returned per directory (the response says `truncated`).
|
|
365
|
+
* Default 5000. */
|
|
366
|
+
maxEntries?: number;
|
|
367
|
+
/** Directory names `GET /fs/find` will not descend into. Defaults to
|
|
368
|
+
* `DEFAULT_IGNORED_DIRS` (`.git`, `node_modules`, build output…) — the thing
|
|
369
|
+
* that keeps a per-keystroke search cheap on a real source tree. */
|
|
370
|
+
ignore?: string[];
|
|
371
|
+
};
|
|
372
|
+
/**
|
|
373
|
+
* Message attachments (`{basePath}/sessions/:id/attachments`) — the photos and
|
|
374
|
+
* files a client sends alongside a message. Always on; these knobs only size it.
|
|
375
|
+
*
|
|
376
|
+
* There is no grant to make here the way `hostFiles.write` is one: an upload
|
|
377
|
+
* lands in the session's own in-memory hold and reaches the model as message
|
|
378
|
+
* content, which is exactly what typing does. What it *can* do is cost memory,
|
|
379
|
+
* so both caps default low enough that a phone camera roll cannot fill the
|
|
380
|
+
* gateway.
|
|
381
|
+
*/
|
|
382
|
+
attachments?: {
|
|
383
|
+
/** Largest single upload; over it is a 413. Default 10 MiB. */maxFileBytes?: number; /** Ceiling on what one session holds at once. Default 64 MiB. */
|
|
384
|
+
maxSessionBytes?: number;
|
|
385
|
+
};
|
|
330
386
|
/**
|
|
331
387
|
* Named Claude Code config directories sessions can run under (each becomes the
|
|
332
388
|
* session's CLAUDE_CONFIG_DIR — settings, memory, skills, and the credentials the
|
|
@@ -413,8 +469,11 @@ type WorkerServerOptions = {
|
|
|
413
469
|
probe?: ClaudeAuthProbe;
|
|
414
470
|
timeoutMs?: number;
|
|
415
471
|
};
|
|
416
|
-
/** Injectable lister for GET /sdk-sessions (tests)
|
|
417
|
-
*
|
|
472
|
+
/** Injectable lister for GET /sdk-sessions (tests) — honored for the CLAUDE
|
|
473
|
+
* engine only, like the injectable claude auth probe (it predates the adapter
|
|
474
|
+
* layer). Defaults to the claude adapter's lister (the SDK's on-disk session
|
|
475
|
+
* store); other engines always answer through their adapter's
|
|
476
|
+
* `listSessions`. */
|
|
418
477
|
listSdkSessions?: SdkSessionLister;
|
|
419
478
|
/** Enable the job queue (`/jobs` + `/queue` routes). Jobs run as ordinary registry
|
|
420
479
|
* sessions — attachable over the sessions WS — governed by these limits. */
|
|
@@ -467,6 +526,8 @@ type WorkerServerOptions = {
|
|
|
467
526
|
* Kept as a host hook so the server package neither imports a model SDK nor
|
|
468
527
|
* decides how provider credentials are resolved: the factory reads them from
|
|
469
528
|
* the operator's environment, exactly like the Claude credential chain.
|
|
529
|
+
* `claude` and `codex` profiles never come through here — those engines ship
|
|
530
|
+
* as in-repo adapters (`@workerdeck/core`'s `getEngineAdapter`).
|
|
470
531
|
*
|
|
471
532
|
* May be async: assembly that has to await — a per-session MCP connect, a
|
|
472
533
|
* credential lookup — belongs here, with `AiSdkRunnerConfig.onClose` as the
|
|
@@ -474,6 +535,13 @@ type WorkerServerOptions = {
|
|
|
474
535
|
* the message, a job goes straight to `failed`.
|
|
475
536
|
*/
|
|
476
537
|
createEngineRunner?: (context: EngineRunnerContext) => Runner | Promise<Runner>;
|
|
538
|
+
/**
|
|
539
|
+
* Adapter overrides, keyed by engine — **for tests only** (the server
|
|
540
|
+
* integration suite injects a fake codex engine so `pnpm test` spawns no
|
|
541
|
+
* binary). Not a public extension point: third engines belong in core as
|
|
542
|
+
* adapters, or behind `createEngineRunner` as provider profiles.
|
|
543
|
+
*/
|
|
544
|
+
engines?: Partial<Record<ProfileEngine, EngineAdapter>>;
|
|
477
545
|
};
|
|
478
546
|
type EngineRunnerContext = {
|
|
479
547
|
/** The session config, with profile defaults already applied. */config: SessionRunnerConfig; /** The profile that selected this engine. */
|
|
@@ -526,5 +594,69 @@ type WorkerServer = {
|
|
|
526
594
|
};
|
|
527
595
|
declare function createWorkerServer(options?: WorkerServerOptions): WorkerServer;
|
|
528
596
|
//#endregion
|
|
529
|
-
|
|
597
|
+
//#region src/attachments.d.ts
|
|
598
|
+
type AttachmentStoreOptions = {
|
|
599
|
+
/** Largest single upload. Default 10 MiB. */maxFileBytes?: number; /** Ceiling on everything one session is holding. Default 64 MiB. */
|
|
600
|
+
maxSessionBytes?: number;
|
|
601
|
+
};
|
|
602
|
+
type AttachmentRejection = {
|
|
603
|
+
code: 'too_large';
|
|
604
|
+
message: string;
|
|
605
|
+
} | {
|
|
606
|
+
code: 'session_full';
|
|
607
|
+
message: string;
|
|
608
|
+
} | {
|
|
609
|
+
code: 'unsupported_type';
|
|
610
|
+
message: string;
|
|
611
|
+
} | {
|
|
612
|
+
code: 'empty';
|
|
613
|
+
message: string;
|
|
614
|
+
};
|
|
615
|
+
type PutResult = {
|
|
616
|
+
ok: true;
|
|
617
|
+
attachment: MessageAttachment;
|
|
618
|
+
} | {
|
|
619
|
+
ok: false;
|
|
620
|
+
error: AttachmentRejection;
|
|
621
|
+
};
|
|
622
|
+
/**
|
|
623
|
+
* Per-session hold for files the user attached to a message.
|
|
624
|
+
*
|
|
625
|
+
* In memory, and deliberately so. An attachment is only *needed* for the instant
|
|
626
|
+
* between the upload and the message that names it; everything after that is
|
|
627
|
+
* convenience (a client re-rendering a thumbnail after a reattach). That is the
|
|
628
|
+
* same bargain `GET /sessions/:id/files` makes — the session's lifetime, no
|
|
629
|
+
* durability tier — and it keeps the gateway from accumulating a photo library
|
|
630
|
+
* on disk that nobody asked it to look after.
|
|
631
|
+
*
|
|
632
|
+
* Both caps are enforced here rather than at the route, so a host embedding the
|
|
633
|
+
* server cannot forget one: a single file that is too big is a 413, and so is a
|
|
634
|
+
* session whose total would go over.
|
|
635
|
+
*/
|
|
636
|
+
declare class AttachmentStore {
|
|
637
|
+
#private;
|
|
638
|
+
constructor(options?: AttachmentStoreOptions);
|
|
639
|
+
get maxFileBytes(): number;
|
|
640
|
+
put(sessionId: string, name: string, mediaType: string, body: Buffer): PutResult;
|
|
641
|
+
/** The stored record, bytes included — for the download route and for the send
|
|
642
|
+
* path that turns ids into content blocks. */
|
|
643
|
+
get(sessionId: string, id: string): AttachmentInput | undefined;
|
|
644
|
+
/**
|
|
645
|
+
* Resolve the ids a `user_message` named, in the order given.
|
|
646
|
+
*
|
|
647
|
+
* Missing ids are reported rather than skipped: a message that quietly lost its
|
|
648
|
+
* picture reads as the model ignoring it, which is a far worse failure than a
|
|
649
|
+
* command that errors.
|
|
650
|
+
*/
|
|
651
|
+
resolve(sessionId: string, ids: readonly string[]): {
|
|
652
|
+
ok: true;
|
|
653
|
+
attachments: AttachmentInput[];
|
|
654
|
+
} | {
|
|
655
|
+
ok: false;
|
|
656
|
+
missing: string[];
|
|
657
|
+
};
|
|
658
|
+
drop(sessionId: string): void;
|
|
659
|
+
}
|
|
660
|
+
//#endregion
|
|
661
|
+
export { AttachmentStore, type AttachmentStoreOptions, type Authenticator, BridgeHub, type BridgeHubOptions, type EngineRunnerContext, type FileSessionStoreOptions, MemorySessionStore, type ParkedSessionRecord, type ProfileStore, type QueueServerOptions, type SdkSessionLister, type SessionNotificationOptions, SessionNotifier, SessionParkManager, type SessionParkOptions, SessionRegistry, type SessionRegistryOptions, type SessionStore, type WorkerServer, type WorkerServerOptions, createFileProfileStore, createFileSessionStore, createMemoryProfileStore, createWorkerServer, toDurableRecord };
|
|
530
662
|
//# sourceMappingURL=index.d.mts.map
|