@workerdeck/server 3.3.0 → 3.5.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/build/index.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import { createHash, randomBytes, randomUUID } from "node:crypto";
2
2
  import { createServer } from "node:http";
3
3
  import { WebSocketServer } from "ws";
4
- import { BrowserBridgeExecutor, PEER_MESSAGE_MAX_CHARS, PEER_RECENT_DEFAULT, PEER_RECENT_MAX, SHELL_REFUSAL, SHELL_REFUSAL as SHELL_REFUSAL$1, SessionRunner, TTY_REDRAW_CARRY, agentMayWrite, attachmentKind, checkClaudeAuth, clampShellTail, createEngineSession, encodeShellKeys, getEngineAdapter, installPeerDirectory, installShellDirectory, isSlashCommand, normalizeMediaType, peerDirectoryHandle, peerSummary, recentLines, shellDirectoryHandle, shellOwnershipRefusal, shellSummary, shellTail, shellToolNeedsCard, ttyRedraws, ttyText, withoutGatewaySecrets } from "@workerdeck/core";
4
+ import { BrowserBridgeExecutor, PEER_MESSAGE_MAX_CHARS, PEER_RECENT_DEFAULT, PEER_RECENT_MAX, SHELL_REFUSAL, SHELL_REFUSAL as SHELL_REFUSAL$1, SessionRunner, TTY_REDRAW_CARRY, agentMayWrite, attachmentKind, checkClaudeAuth, clampShellTail, composeInstructions, contextResetDirectoryHandle, createEngineSession, encodeShellKeys, getEngineAdapter, installContextResetDirectory, installPeerDirectory, installShellDirectory, isSlashCommand, normalizeMediaType, peerDirectoryHandle, peerSummary, recentLines, shellDirectoryHandle, shellOwnershipRefusal, shellSummary, shellTail, shellToolNeedsCard, ttyRedraws, ttyText, withoutGatewaySecrets } from "@workerdeck/core";
5
5
  import { JobQueue } from "@workerdeck/queue";
6
- import { ENGINE_CAPABILITIES, PEER_MENTION_MAX, PROTOCOL_VERSION, SHELL_ARTIFACT_MAX_BYTES, SHELL_ARTIFACT_TTL_MS, SHELL_ATTACH_REPLAY_BYTES, SHELL_COLS, SHELL_COMMAND_MAX, SHELL_INPUT_MAX, SHELL_LABEL_MAX, SHELL_LINGER_MS, SHELL_MAX_COLS, SHELL_MAX_ROWS, SHELL_MAX_RUNNING_PER_SESSION, SHELL_MAX_RUNNING_TOTAL, SHELL_MIN_COLS, SHELL_MIN_ROWS, SHELL_READ_MAX_LINES, SHELL_ROWS, SHELL_SPILL_BYTES, SHELL_TAIL_FLUSH_MS, SHELL_TAIL_RING_BYTES, SHELL_WAIT_DEFAULT_MS, SHELL_WAIT_MAX_MS, costOfByModel, imagePartRef, isJobRun, mergeByModel, mergePricing, peerMentionKey, peerMentionSlug, pickCreateSessionRequest, scanPeerMentions, sessionState, subscriptionComparison, supportsPermissionMode } from "@workerdeck/protocol";
6
+ import { AGENT_SLEEP_AFTER_MS_DEFAULT, ENGINE_CAPABILITIES, PEER_MENTION_MAX, PROTOCOL_VERSION, SHELL_ARTIFACT_MAX_BYTES, SHELL_ARTIFACT_TTL_MS, SHELL_ATTACH_REPLAY_BYTES, SHELL_COLS, SHELL_COMMAND_MAX, SHELL_INPUT_MAX, SHELL_LABEL_MAX, SHELL_LINGER_MS, SHELL_MAX_COLS, SHELL_MAX_ROWS, SHELL_MAX_RUNNING_PER_SESSION, SHELL_MAX_RUNNING_TOTAL, SHELL_MIN_COLS, SHELL_MIN_ROWS, SHELL_READ_MAX_LINES, SHELL_ROWS, SHELL_SPILL_BYTES, SHELL_TAIL_FLUSH_MS, SHELL_TAIL_RING_BYTES, SHELL_WAIT_DEFAULT_MS, SHELL_WAIT_MAX_MS, agentRef, costOfByModel, imagePartRef, isJobRun, mergeByModel, mergePricing, peerMentionKey, peerMentionSlug, pickCreateSessionRequest, scanPeerMentions, sessionState, subscriptionComparison, supportsPermissionMode, teamReaches } from "@workerdeck/protocol";
7
7
  import { chmodSync, closeSync, constants, createReadStream, existsSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
8
8
  import { arch, homedir, hostname, platform, tmpdir } from "node:os";
9
9
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
@@ -79,6 +79,7 @@ function closeServer(deps) {
79
79
  shells?.killAll("server_stopped");
80
80
  const flushed = shells?.flush().catch((error) => deps.diagnose(error, "shell-flush")) ?? Promise.resolve();
81
81
  parking.close();
82
+ deps.engineSleep.close();
82
83
  registry.closeAll();
83
84
  for (const ws of wss.clients) ws.close(1001, "server shutting down");
84
85
  deps.closeQueueSockets();
@@ -222,6 +223,23 @@ function cwdAllowed(cwd, roots) {
222
223
  return resolved === r || resolved.startsWith(r + sep);
223
224
  });
224
225
  }
226
+ function listDirs(path) {
227
+ try {
228
+ return readdirSync(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
229
+ } catch {
230
+ return [];
231
+ }
232
+ }
233
+ function listMd(path) {
234
+ try {
235
+ return readdirSync(path).filter((file) => file.endsWith(".md")).map((file) => file.slice(0, -3)).sort();
236
+ } catch {
237
+ return [];
238
+ }
239
+ }
240
+ function count(rules) {
241
+ return Array.isArray(rules) ? rules.length : 0;
242
+ }
225
243
  function readProfileConfig(profile) {
226
244
  const dir = profile.configDir;
227
245
  if (!dir) return {
@@ -230,22 +248,8 @@ function readProfileConfig(profile) {
230
248
  agents: [],
231
249
  commands: []
232
250
  };
233
- const listDirs = (path) => {
234
- try {
235
- return readdirSync(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
236
- } catch {
237
- return [];
238
- }
239
- };
240
- const listMd = (path) => {
241
- try {
242
- return readdirSync(path).filter((file) => file.endsWith(".md")).map((file) => file.slice(0, -3)).sort();
243
- } catch {
244
- return [];
245
- }
246
- };
247
251
  const snapshot = {
248
- hasUserMemory: existsSync(join(dir, "CLAUDE.md")),
252
+ hasUserMemory: ["CLAUDE.md", "AGENTS.md"].some((name) => existsSync(join(dir, name))),
249
253
  skills: listDirs(join(dir, "skills")),
250
254
  agents: listMd(join(dir, "agents")),
251
255
  commands: listMd(join(dir, "commands"))
@@ -253,7 +257,6 @@ function readProfileConfig(profile) {
253
257
  try {
254
258
  const raw = JSON.parse(readFileSync(join(dir, "settings.json"), "utf8"));
255
259
  const permissions = raw.permissions ?? {};
256
- const count = (rules) => Array.isArray(rules) ? rules.length : 0;
257
260
  snapshot.settings = {
258
261
  model: typeof raw.model === "string" ? raw.model : void 0,
259
262
  defaultPermissionMode: typeof permissions.defaultMode === "string" ? permissions.defaultMode : void 0,
@@ -388,6 +391,15 @@ function itemRoute(rawId, parts) {
388
391
  id: decode(rawId),
389
392
  stopTaskId: decode(third)
390
393
  };
394
+ if (length === 3 && section === "tasks" && third === "background") return {
395
+ kind: "background-task",
396
+ id: decode(rawId)
397
+ };
398
+ if (length === 4 && section === "tasks" && fourth === "background") return {
399
+ kind: "background-task",
400
+ id: decode(rawId),
401
+ backgroundTaskId: decode(third)
402
+ };
391
403
  if (length === 3 && section === "project" && third === "icon") return {
392
404
  kind: "project-icon",
393
405
  id: decode(rawId)
@@ -400,6 +412,14 @@ function itemRoute(rawId, parts) {
400
412
  resultSeq: seq
401
413
  } : null;
402
414
  }
415
+ if (length === 2 && section === "sleep") return {
416
+ kind: "sleep",
417
+ id: decode(rawId)
418
+ };
419
+ if (length === 2 && section === "peers") return {
420
+ kind: "peers",
421
+ id: decode(rawId)
422
+ };
403
423
  if (length <= 3 && section === "mcp") return {
404
424
  kind: "mcp",
405
425
  id: decode(rawId),
@@ -464,6 +484,307 @@ function permissionDecision(input) {
464
484
  };
465
485
  }
466
486
  //#endregion
487
+ //#region src/lib/scope.ts
488
+ const MAX_SCOPE_KEYS = 16;
489
+ const MAX_SCOPE_LEN = 200;
490
+ function readScope(value) {
491
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return;
492
+ const entries = Object.entries(value);
493
+ if (entries.some(([, v]) => typeof v !== "string")) return;
494
+ return Object.fromEntries(entries);
495
+ }
496
+ function checkScope(value) {
497
+ if (value === void 0) return null;
498
+ const scope = readScope(value);
499
+ if (!scope) return "scope must be an object of string values";
500
+ const entries = Object.entries(scope);
501
+ if (entries.length > MAX_SCOPE_KEYS) return `scope may carry at most ${MAX_SCOPE_KEYS} keys`;
502
+ for (const [key, val] of entries) {
503
+ if (key.length === 0) return "scope keys must not be empty";
504
+ if (key.length > MAX_SCOPE_LEN || val.length > MAX_SCOPE_LEN) return `scope keys and values must be at most ${MAX_SCOPE_LEN} characters`;
505
+ }
506
+ return null;
507
+ }
508
+ function sameScope(a, b) {
509
+ const left = Object.entries(a ?? {}).sort(([x], [y]) => x < y ? -1 : 1);
510
+ const right = Object.entries(b ?? {}).sort(([x], [y]) => x < y ? -1 : 1);
511
+ return left.length === right.length && left.every(([key, value], i) => right[i][0] === key && right[i][1] === value);
512
+ }
513
+ function scopeMatches(principal, session) {
514
+ if (!principal) return true;
515
+ return Object.entries(principal).every(([key, value]) => session?.[key] === value);
516
+ }
517
+ //#endregion
518
+ //#region src/services/peers.ts
519
+ const DEFAULT_PER_MINUTE = 10;
520
+ const DEFAULT_MAX_HOPS = 12;
521
+ const WINDOW_MS = 6e4;
522
+ function visible(from, to) {
523
+ return to.id !== from.id && scopeMatches(from.scope, to.scope) && teamReaches(from.agent, to.agent);
524
+ }
525
+ function relayable(info, exposed) {
526
+ return info.status !== "closed" && info.agent?.lead === void 0 && scopeMatches(exposed, info.scope);
527
+ }
528
+ function relayEntry(info, live) {
529
+ const items = info.checklist;
530
+ return {
531
+ id: info.id,
532
+ engine: info.engine,
533
+ status: info.status,
534
+ title: info.title,
535
+ project: info.project ? {
536
+ name: info.project.name,
537
+ root: info.project.root
538
+ } : void 0,
539
+ cwd: info.cwd,
540
+ profile: info.profile,
541
+ model: info.model,
542
+ permissionMode: info.permissionMode,
543
+ contextUsage: info.contextUsage,
544
+ checklist: items?.length ? {
545
+ done: items.filter((item) => item.status === "completed").length,
546
+ total: items.length
547
+ } : void 0,
548
+ numTurns: info.numTurns,
549
+ createdAt: info.createdAt,
550
+ lastActivityAt: info.lastActivityAt,
551
+ pendingPermissionCount: info.pendingPermissionCount,
552
+ live
553
+ };
554
+ }
555
+ function createPeerService(deps) {
556
+ const perMinute = deps.options?.perMinute ?? DEFAULT_PER_MINUTE;
557
+ const maxChars = deps.options?.maxMessageChars ?? PEER_MESSAGE_MAX_CHARS;
558
+ const maxHops = deps.options?.maxHops ?? DEFAULT_MAX_HOPS;
559
+ const sent = /* @__PURE__ */ new Map();
560
+ const inbound = /* @__PURE__ */ new Map();
561
+ const registry = () => {
562
+ const found = deps.refs.registry;
563
+ if (!found) throw new Error("peer messaging is not ready");
564
+ return found;
565
+ };
566
+ const allSessions = async () => {
567
+ const live = registry().list();
568
+ const dormant = await deps.refs.parking?.listInfo() ?? [];
569
+ return [...live, ...dormant].map((info) => deps.projects.withProject(info));
570
+ };
571
+ const infoOf = async (id) => {
572
+ const live = registry().get(id)?.info();
573
+ if (live) return deps.projects.withProject(live);
574
+ const record = await deps.refs.parking?.get(id);
575
+ return record ? deps.projects.withProject(record.info) : void 0;
576
+ };
577
+ const sender = async (from) => {
578
+ const info = await infoOf(from);
579
+ if (!info) throw new Error(`unknown sender session: ${from}`);
580
+ return info;
581
+ };
582
+ const recentOf = (sessionId, recent) => {
583
+ const limit = Math.min(Math.max(recent ?? PEER_RECENT_DEFAULT, 0), PEER_RECENT_MAX);
584
+ const runner = registry().get(sessionId);
585
+ const events = [];
586
+ if (runner && limit > 0) runner.subscribe((event) => events.push(event), 0, {
587
+ truncateResults: true,
588
+ imageRefs: true
589
+ })();
590
+ return {
591
+ runner,
592
+ lines: recentLines(events, limit)
593
+ };
594
+ };
595
+ const underRateLimit = (from, to) => {
596
+ const key = `${from}->${to}`;
597
+ const now = Date.now();
598
+ const recent = (sent.get(key) ?? []).filter((at) => now - at < WINDOW_MS);
599
+ if (recent.length >= perMinute) {
600
+ sent.set(key, recent);
601
+ return false;
602
+ }
603
+ recent.push(now);
604
+ sent.set(key, recent);
605
+ return true;
606
+ };
607
+ const list = async (from) => {
608
+ const me = await sender(from);
609
+ return (await allSessions()).filter((info) => visible(me, info)).sort((a, b) => (b.lastActivityAt ?? b.createdAt) - (a.lastActivityAt ?? a.createdAt)).map(peerSummary);
610
+ };
611
+ const peek = async (from, sessionId, options) => {
612
+ const me = await sender(from);
613
+ const info = await infoOf(sessionId);
614
+ if (!info || !visible(me, info)) return;
615
+ const { runner, lines } = recentOf(sessionId, options?.recent);
616
+ return {
617
+ ...peerSummary(info),
618
+ live: runner !== void 0,
619
+ checklist: info.checklist,
620
+ pendingApprovals: runner ? runner.pendingApprovals.map((request) => request.title ?? request.toolName) : [],
621
+ recent: lines
622
+ };
623
+ };
624
+ const send = async (from, sessionId, text, options) => {
625
+ const me = await sender(from);
626
+ if (sessionId === from) return {
627
+ delivered: false,
628
+ reason: "that is this session"
629
+ };
630
+ if (text.length > maxChars) return {
631
+ delivered: false,
632
+ reason: `message is ${text.length} characters; the limit is ${maxChars}. Write it to a file and send the path.`
633
+ };
634
+ const target = await infoOf(sessionId);
635
+ if (!target || !visible(me, target)) return {
636
+ delivered: false,
637
+ reason: `no such session: ${sessionId}`
638
+ };
639
+ const hops = [...options?.hops ?? inbound.get(from) ?? [], from];
640
+ return deliver(target, text, {
641
+ kind: "peer",
642
+ sessionId: from,
643
+ name: me.title,
644
+ engine: me.engine,
645
+ hops
646
+ }, from);
647
+ };
648
+ const deliver = async (target, text, origin, rateKey) => {
649
+ const sessionId = target.id;
650
+ if (target.status === "closed" || target.status === "failed") return {
651
+ delivered: false,
652
+ reason: `session ${sessionId} is ${target.status}`
653
+ };
654
+ const hops = origin.hops ?? [];
655
+ if (hops.length > maxHops) return {
656
+ delivered: false,
657
+ reason: `${hops.length} messages have passed between sessions without a person speaking; stop and ask your user before continuing.`
658
+ };
659
+ if (!underRateLimit(rateKey, sessionId)) return {
660
+ delivered: false,
661
+ reason: `rate limit: at most ${perMinute} messages a minute to one session. Batch what you have to say.`
662
+ };
663
+ const runner = await deps.refs.parking?.ensureLive(sessionId) ?? registry().get(sessionId);
664
+ if (!runner) return {
665
+ delivered: false,
666
+ reason: `session ${sessionId} could not be woken`
667
+ };
668
+ const before = runner.info().status;
669
+ try {
670
+ runner.sendMessage(text, void 0, { origin });
671
+ } catch (error) {
672
+ return {
673
+ delivered: false,
674
+ reason: error instanceof Error ? error.message : String(error)
675
+ };
676
+ }
677
+ inbound.set(sessionId, hops);
678
+ return {
679
+ delivered: true,
680
+ sessionId,
681
+ name: target.title,
682
+ queued: before === "running" || before === "awaiting_approval"
683
+ };
684
+ };
685
+ const exposedInfo = async (sessionId, exposed) => {
686
+ const info = await infoOf(sessionId);
687
+ return info && relayable(info, exposed) ? info : void 0;
688
+ };
689
+ const relayEntries = async (exposed) => {
690
+ const live = new Set(registry().list().map((info) => info.id));
691
+ return (await allSessions()).filter((info) => relayable(info, exposed)).map((info) => relayEntry(info, live.has(info.id)));
692
+ };
693
+ const relayPeek = async (sessionId, recent, exposed) => {
694
+ const info = await exposedInfo(sessionId, exposed);
695
+ if (!info) return;
696
+ const { runner, lines } = recentOf(sessionId, recent);
697
+ return {
698
+ ...relayEntry(info, runner !== void 0),
699
+ checklistItems: info.checklist,
700
+ pendingApprovals: runner ? runner.pendingApprovals.map((request) => request.title ?? request.toolName) : [],
701
+ recent: lines
702
+ };
703
+ };
704
+ const relaySend = async (origin, sessionId, text, exposed) => {
705
+ if (text.length === 0 || text.length > maxChars) return {
706
+ delivered: false,
707
+ reason: `message is ${text.length} characters; the limit is ${maxChars}. Write it to a file and send the path.`
708
+ };
709
+ const target = await exposedInfo(sessionId, exposed);
710
+ if (!target) return {
711
+ delivered: false,
712
+ reason: `no such session: ${sessionId}`
713
+ };
714
+ const from = `${origin.gateway}:${origin.sessionId}`;
715
+ return deliver(target, text, {
716
+ kind: "peer",
717
+ sessionId: from,
718
+ hostId: origin.gateway,
719
+ name: origin.name,
720
+ engine: origin.engine,
721
+ hops: origin.hops
722
+ }, from);
723
+ };
724
+ const watch = (runner) => runner.subscribe((event) => {
725
+ if (event.type === "user_message" && !event.origin && !event.synthetic && event.parentToolUseId == null && !event.replay) inbound.delete(runner.id);
726
+ }, runner.info().lastSeq);
727
+ return {
728
+ list,
729
+ peek,
730
+ send,
731
+ watch,
732
+ relaySender: sender,
733
+ relayChain: (from) => [...inbound.get(from) ?? [], from],
734
+ relayEntries,
735
+ relayPeek,
736
+ relaySend
737
+ };
738
+ }
739
+ function resolvePeerMentions(rows, text) {
740
+ const tokens = scanPeerMentions(text);
741
+ if (tokens.length === 0) return [];
742
+ const byKey = /* @__PURE__ */ new Map();
743
+ const add = (key, row) => {
744
+ const held = byKey.get(key);
745
+ if (held) held.push(row);
746
+ else byKey.set(key, [row]);
747
+ };
748
+ for (const row of rows) {
749
+ const keys = /* @__PURE__ */ new Set([peerMentionKey(peerMentionSlug(row.title, row.id)), peerMentionKey(row.id)]);
750
+ if (row.agent) keys.add(peerMentionKey(peerMentionSlug(row.agent, row.id)));
751
+ for (const key of keys) add(key, row);
752
+ }
753
+ const resolved = [];
754
+ const seen = /* @__PURE__ */ new Set();
755
+ for (const token of tokens) {
756
+ const key = peerMentionKey(token.body);
757
+ const exact = byKey.get(key);
758
+ const matches = exact ?? (key.length >= 4 ? rows.filter((row) => row.id.startsWith(key)) : []);
759
+ const target = matches[0];
760
+ if (!target || matches.length > 1 && !exact) continue;
761
+ if (seen.has(target.id)) continue;
762
+ seen.add(target.id);
763
+ const others = matches.slice(1, 4).map((row) => row.id);
764
+ resolved.push({
765
+ typed: token.body,
766
+ id: target.id,
767
+ name: target.agent ?? target.title,
768
+ engine: target.engine,
769
+ status: target.status,
770
+ cwd: target.cwd,
771
+ ...others.length ? { ambiguousWith: others } : {}
772
+ });
773
+ if (resolved.length >= PEER_MENTION_MAX) break;
774
+ }
775
+ return resolved;
776
+ }
777
+ async function mentionsFor(directory, from, text) {
778
+ return scanPeerMentions(text).length === 0 ? [] : resolvePeerMentions(await directory.list(from), text);
779
+ }
780
+ //#endregion
781
+ //#region src/routes/sleep.ts
782
+ async function sleepRunner(runner) {
783
+ if (!runner.sleep) throw new HttpError(501, `the ${engineOf(runner.info())} engine has no process to put to sleep`);
784
+ const result = await runner.sleep();
785
+ if (!result.ok) throw new HttpError(409, `cannot sleep: ${result.reason}`);
786
+ }
787
+ //#endregion
467
788
  //#region src/services/shell-directory.ts
468
789
  function createShellDirectory(registry, deps = {}) {
469
790
  const take = async (from, shellId, options) => {
@@ -1853,13 +2174,14 @@ function attachClient(ctx, ws, runner, req, access) {
1853
2174
  unsubscribe();
1854
2175
  detachBridge();
1855
2176
  parking.onDetach(runner.id);
2177
+ ctx.engineSleep.onDetach(runner.id);
1856
2178
  });
1857
2179
  }
1858
2180
  async function handleCommand(ctx, frame, runner, access, client) {
1859
2181
  const { attachmentStore, bridge } = ctx;
1860
2182
  switch (frame.type) {
1861
2183
  case "user_message": {
1862
- const mentions = ctx.peers && !isSlashCommand(frame.text) ? await ctx.peers.mentions(runner.id, frame.text).catch(() => []) : [];
2184
+ const mentions = ctx.peers && !isSlashCommand(frame.text) ? await mentionsFor(ctx.peers, runner.id, frame.text).catch(() => []) : [];
1863
2185
  const options = mentions.length > 0 ? { mentions } : void 0;
1864
2186
  if (!frame.attachmentIds?.length) {
1865
2187
  runner.sendMessage(frame.text, void 0, options);
@@ -1876,6 +2198,13 @@ async function handleCommand(ctx, frame, runner, access, client) {
1876
2198
  case "interrupt":
1877
2199
  await runner.interrupt();
1878
2200
  return;
2201
+ case "sleep":
2202
+ await sleepRunner(runner);
2203
+ return;
2204
+ case "background_task":
2205
+ if (!runner.backgroundTask) throw new Error(`the ${engineOf(runner.info())} engine cannot move a task to the background`);
2206
+ await runner.backgroundTask(frame.toolUseId);
2207
+ return;
1879
2208
  case "clear_context":
1880
2209
  if (!runner.clearContext) throw new Error(`the ${engineOf(runner.info())} engine cannot clear a conversation`);
1881
2210
  await runner.clearContext();
@@ -1892,6 +2221,11 @@ async function handleCommand(ctx, frame, runner, access, client) {
1892
2221
  case "set_model":
1893
2222
  await runner.setModel(frame.model);
1894
2223
  return;
2224
+ case "set_effort":
2225
+ if (!runner.setEffort) throw new Error(`the ${engineOf(runner.info())} engine cannot change its reasoning effort`);
2226
+ if (frame.effort !== void 0 && typeof frame.effort !== "string") throw new Error("effort must be a string");
2227
+ await runner.setEffort(frame.effort);
2228
+ return;
1895
2229
  case "tool_call_result":
1896
2230
  bridge.resolve(runner.id, frame.executionId, {
1897
2231
  output: frame.output,
@@ -1993,53 +2327,623 @@ async function attachShell(shells, client, sessionId, shellId, size) {
1993
2327
  scrollback
1994
2328
  });
1995
2329
  }
1996
- function detachShell(client, shellId) {
1997
- client.shells.get(shellId)?.();
1998
- client.shells.delete(shellId);
1999
- }
2000
- function detachShells(client) {
2001
- for (const detach of client.shells.values()) detach();
2002
- client.shells.clear();
2003
- }
2004
- //#endregion
2005
- //#region src/routes/session-upgrade.ts
2006
- async function upgradeSession(ctx, wss, req, socket, head) {
2007
- const { auth, parking } = ctx;
2008
- const route = parseSessionRoute(ctx.basePath, req.url ?? "/");
2009
- if (route?.kind !== "ws") {
2010
- socket.destroy();
2330
+ function detachShell(client, shellId) {
2331
+ client.shells.get(shellId)?.();
2332
+ client.shells.delete(shellId);
2333
+ }
2334
+ function detachShells(client) {
2335
+ for (const detach of client.shells.values()) detach();
2336
+ client.shells.clear();
2337
+ }
2338
+ //#endregion
2339
+ //#region src/routes/session-upgrade.ts
2340
+ async function upgradeSession(ctx, wss, req, socket, head) {
2341
+ const { auth, parking } = ctx;
2342
+ const route = parseSessionRoute(ctx.basePath, req.url ?? "/");
2343
+ if (route?.kind !== "ws") {
2344
+ socket.destroy();
2345
+ return;
2346
+ }
2347
+ const authCtx = await auth.authenticate(req);
2348
+ if (!authCtx.ok) {
2349
+ refuseUpgrade(socket, "401 Unauthorized");
2350
+ return;
2351
+ }
2352
+ const known = await sessionInfoOf(ctx, route.id);
2353
+ if (known && !auth.canSee(authCtx, known)) {
2354
+ refuseUpgrade(socket, "404 Not Found");
2355
+ return;
2356
+ }
2357
+ const runner = await parking.ensureLive(route.id).catch(() => void 0);
2358
+ if (!runner || !auth.canSee(authCtx, runner.info())) {
2359
+ refuseUpgrade(socket, "404 Not Found");
2360
+ return;
2361
+ }
2362
+ wss.handleUpgrade(req, socket, head, (ws) => {
2363
+ attachClient(ctx, ws, runner, req, { operator: auth.isOperator(authCtx) });
2364
+ });
2365
+ }
2366
+ //#endregion
2367
+ //#region src/lib/machine-id.ts
2368
+ let cached;
2369
+ function machineId() {
2370
+ cached ??= createHash("sha256").update([
2371
+ hostname(),
2372
+ platform(),
2373
+ arch(),
2374
+ homedir()
2375
+ ].join("\0")).digest("hex").slice(0, 32);
2376
+ return cached;
2377
+ }
2378
+ //#endregion
2379
+ //#region src/services/agents.ts
2380
+ const NAMES = [
2381
+ "Atlas",
2382
+ "Juno",
2383
+ "Pip",
2384
+ "Rook",
2385
+ "Marlow",
2386
+ "Orbit",
2387
+ "Fern",
2388
+ "Quill",
2389
+ "Sable",
2390
+ "Wren",
2391
+ "Ember",
2392
+ "Moss",
2393
+ "Nova",
2394
+ "Otto",
2395
+ "Pike",
2396
+ "Rune",
2397
+ "Sage",
2398
+ "Tansy",
2399
+ "Vale",
2400
+ "Yarrow",
2401
+ "Basil",
2402
+ "Cedar",
2403
+ "Dune",
2404
+ "Flint",
2405
+ "Hazel",
2406
+ "Iris",
2407
+ "Kestrel",
2408
+ "Lark",
2409
+ "Maple",
2410
+ "Nettle",
2411
+ "Onyx",
2412
+ "Piper",
2413
+ "Reed",
2414
+ "Sorrel",
2415
+ "Thistle",
2416
+ "Umber",
2417
+ "Willow",
2418
+ "Zephyr"
2419
+ ];
2420
+ const MAX_NAME = 64;
2421
+ const MAX_BRIEF = 16e3;
2422
+ const CONFIG_KEYS = [
2423
+ "cwd",
2424
+ "profile",
2425
+ "model",
2426
+ "reasoningEffort",
2427
+ "permissionMode",
2428
+ "brief",
2429
+ "agentContextReset",
2430
+ "sleepAfterMs"
2431
+ ];
2432
+ var AgentService = class {
2433
+ #store;
2434
+ #basePath;
2435
+ #avatars;
2436
+ #sleepAfterMs;
2437
+ #now;
2438
+ #agents = /* @__PURE__ */ new Map();
2439
+ constructor(options) {
2440
+ this.#store = options.store;
2441
+ this.#basePath = options.basePath;
2442
+ this.#avatars = options.avatars ?? false;
2443
+ this.#sleepAfterMs = options.sleepAfterMs ?? AGENT_SLEEP_AFTER_MS_DEFAULT;
2444
+ this.#now = options.now ?? Date.now;
2445
+ }
2446
+ async hydrate() {
2447
+ this.#agents = new Map((await this.#store.list()).map((agent) => [agent.id, agent]));
2448
+ }
2449
+ list() {
2450
+ return [...this.#agents.values()].map((agent) => this.public(agent));
2451
+ }
2452
+ get(id) {
2453
+ return this.#agents.get(id);
2454
+ }
2455
+ bySession(sessionId) {
2456
+ for (const agent of this.#agents.values()) if (agent.sessionId === sessionId) return agent;
2457
+ }
2458
+ decorate(info) {
2459
+ const agent = this.bySession(info.id);
2460
+ if (!agent) return info;
2461
+ const leadName = agent.lead === void 0 ? void 0 : this.#agents.get(agent.lead)?.name;
2462
+ return {
2463
+ ...info,
2464
+ agent: agentRef(this.public(agent), {
2465
+ leadName,
2466
+ leads: this.hasMembers(agent.id)
2467
+ })
2468
+ };
2469
+ }
2470
+ briefFor(sessionId) {
2471
+ return (sessionId === void 0 ? void 0 : this.bySession(sessionId)?.config.brief?.trim()) || void 0;
2472
+ }
2473
+ sleepAfterFor(sessionId) {
2474
+ const agent = this.bySession(sessionId);
2475
+ return agent ? agent.config.sleepAfterMs ?? this.#sleepAfterMs : void 0;
2476
+ }
2477
+ public(agent) {
2478
+ const { avatarSeed: _seed, avatarRecipe: _recipe, avatar: _avatar, ...info } = agent;
2479
+ return this.#avatars ? {
2480
+ ...info,
2481
+ avatar: `${this.#basePath}/agents/${agent.id}/avatar.png`
2482
+ } : info;
2483
+ }
2484
+ draft(input) {
2485
+ const config = readConfig(input.config);
2486
+ if ("error" in config) return config;
2487
+ const name = input.name === void 0 ? this.#suggestName() : readName(input.name);
2488
+ if (typeof name !== "string") return name;
2489
+ const id = randomUUID();
2490
+ const at = this.#now();
2491
+ const agent = {
2492
+ id,
2493
+ name,
2494
+ createdAt: at,
2495
+ updatedAt: at,
2496
+ avatarSeed: id,
2497
+ pastSessions: [],
2498
+ config
2499
+ };
2500
+ if (input.lead !== void 0 && input.lead !== null) {
2501
+ const refused = typeof input.lead === "string" ? this.leadRefusal(agent, input.lead) : {
2502
+ status: 400,
2503
+ error: "lead must be an agent id"
2504
+ };
2505
+ if (refused) return refused;
2506
+ agent.lead = input.lead;
2507
+ }
2508
+ return agent;
2509
+ }
2510
+ async save(agent) {
2511
+ const stored = {
2512
+ ...agent,
2513
+ updatedAt: this.#now()
2514
+ };
2515
+ await this.#store.save(stored);
2516
+ this.#agents.set(stored.id, stored);
2517
+ return stored;
2518
+ }
2519
+ async bind(agent, sessionId) {
2520
+ const past = agent.sessionId !== void 0 && agent.sessionId !== sessionId ? [...agent.pastSessions, agent.sessionId] : agent.pastSessions;
2521
+ return this.save({
2522
+ ...agent,
2523
+ sessionId,
2524
+ pastSessions: past
2525
+ });
2526
+ }
2527
+ hasMembers(id) {
2528
+ for (const agent of this.#agents.values()) if (agent.lead === id) return true;
2529
+ return false;
2530
+ }
2531
+ members(id) {
2532
+ return [...this.#agents.values()].filter((agent) => agent.lead === id).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
2533
+ }
2534
+ leadRefusal(mover, leadId) {
2535
+ if (leadId === mover.id) return {
2536
+ status: 409,
2537
+ error: "an agent cannot lead its own team"
2538
+ };
2539
+ const lead = this.#agents.get(leadId);
2540
+ if (!lead) return {
2541
+ status: 404,
2542
+ error: `no such agent: ${leadId}`
2543
+ };
2544
+ if (lead.lead !== void 0) return {
2545
+ status: 409,
2546
+ error: `${lead.name} is a member of a team; teams are one level deep`
2547
+ };
2548
+ if (this.hasMembers(mover.id)) return {
2549
+ status: 409,
2550
+ error: `${mover.name} leads a team; teams are one level deep`
2551
+ };
2552
+ return null;
2553
+ }
2554
+ async update(id, patch) {
2555
+ const agent = this.#agents.get(id);
2556
+ if (!agent) return {
2557
+ status: 404,
2558
+ error: `no such agent: ${id}`
2559
+ };
2560
+ const next = { ...agent };
2561
+ if (patch.name !== void 0) {
2562
+ const name = readName(patch.name);
2563
+ if (typeof name !== "string") return name;
2564
+ next.name = name;
2565
+ }
2566
+ if (patch.config !== void 0) {
2567
+ if (!isRecord$1(patch.config)) return {
2568
+ status: 400,
2569
+ error: "config must be an object"
2570
+ };
2571
+ const config = readConfig({
2572
+ ...agent.config,
2573
+ ...patch.config
2574
+ });
2575
+ if ("error" in config) return config;
2576
+ next.config = config;
2577
+ }
2578
+ if (patch.lead === null) delete next.lead;
2579
+ else if (patch.lead !== void 0) {
2580
+ if (typeof patch.lead !== "string") return {
2581
+ status: 400,
2582
+ error: "lead must be an agent id or null"
2583
+ };
2584
+ const refused = this.leadRefusal(agent, patch.lead);
2585
+ if (refused) return refused;
2586
+ next.lead = patch.lead;
2587
+ }
2588
+ if (patch.order !== void 0) {
2589
+ if (typeof patch.order !== "number" || !Number.isFinite(patch.order)) return {
2590
+ status: 400,
2591
+ error: "order must be a number"
2592
+ };
2593
+ next.order = patch.order;
2594
+ }
2595
+ return this.save(next);
2596
+ }
2597
+ async retire(id, members) {
2598
+ const agent = this.#agents.get(id);
2599
+ if (!agent) return {
2600
+ status: 404,
2601
+ error: `no such agent: ${id}`
2602
+ };
2603
+ const crew = this.members(id);
2604
+ const retired = [agent, ...members === "retire" ? crew : []];
2605
+ const released = [];
2606
+ if (members === "release") for (const member of crew) {
2607
+ const { lead: _lead, ...rest } = member;
2608
+ released.push(await this.save(rest));
2609
+ }
2610
+ for (const gone of retired) {
2611
+ await this.#store.delete(gone.id);
2612
+ this.#agents.delete(gone.id);
2613
+ }
2614
+ return {
2615
+ retired,
2616
+ released
2617
+ };
2618
+ }
2619
+ #suggestName() {
2620
+ const taken = new Set([...this.#agents.values()].map((agent) => agent.name.toLowerCase()));
2621
+ const free = NAMES.find((name) => !taken.has(name.toLowerCase()));
2622
+ if (free) return free;
2623
+ for (let n = 2;; n++) {
2624
+ const name = `${NAMES[n % NAMES.length]} ${n}`;
2625
+ if (!taken.has(name.toLowerCase())) return name;
2626
+ }
2627
+ }
2628
+ };
2629
+ function isAgentRefusal(value) {
2630
+ return isRecord$1(value) && typeof value.status === "number" && typeof value.error === "string";
2631
+ }
2632
+ function readName(value) {
2633
+ if (typeof value !== "string" || value.trim() === "") return {
2634
+ status: 400,
2635
+ error: "name must be a non-empty string"
2636
+ };
2637
+ const name = value.trim();
2638
+ return name.length > MAX_NAME ? {
2639
+ status: 400,
2640
+ error: `name is longer than ${MAX_NAME} characters`
2641
+ } : name;
2642
+ }
2643
+ function readConfig(value) {
2644
+ if (value === void 0) return {};
2645
+ if (!isRecord$1(value)) return {
2646
+ status: 400,
2647
+ error: "config must be an object"
2648
+ };
2649
+ const config = {};
2650
+ for (const key of CONFIG_KEYS) if (value[key] !== void 0) config[key] = value[key];
2651
+ for (const key of [
2652
+ "cwd",
2653
+ "profile",
2654
+ "model",
2655
+ "reasoningEffort",
2656
+ "permissionMode",
2657
+ "brief"
2658
+ ]) if (config[key] !== void 0 && typeof config[key] !== "string") return {
2659
+ status: 400,
2660
+ error: `config.${key} must be a string`
2661
+ };
2662
+ if (config.agentContextReset !== void 0 && typeof config.agentContextReset !== "boolean") return {
2663
+ status: 400,
2664
+ error: "config.agentContextReset must be a boolean"
2665
+ };
2666
+ const sleep = config.sleepAfterMs;
2667
+ if (sleep !== void 0 && (typeof sleep !== "number" || !Number.isFinite(sleep) || sleep < 0)) return {
2668
+ status: 400,
2669
+ error: "config.sleepAfterMs must be a non-negative number"
2670
+ };
2671
+ if (typeof config.brief === "string" && config.brief.length > MAX_BRIEF) return {
2672
+ status: 400,
2673
+ error: `config.brief is longer than ${MAX_BRIEF} characters`
2674
+ };
2675
+ return config;
2676
+ }
2677
+ function isRecord$1(value) {
2678
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2679
+ }
2680
+ //#endregion
2681
+ //#region src/lib/host-only-keys.ts
2682
+ const HOST_ONLY_KEY_TABLE = {
2683
+ epoch: "durable",
2684
+ pricing: "transient",
2685
+ queryFn: "transient",
2686
+ env: "transient",
2687
+ pathToClaudeCodeExecutable: "durable",
2688
+ extraOptions: "transient",
2689
+ instructions: "transient",
2690
+ defaultApprovalTimeoutMs: "durable",
2691
+ backfillHistory: "durable",
2692
+ historyFn: "transient",
2693
+ sessionInfoFn: "transient",
2694
+ peers: "transient",
2695
+ contextReset: "transient",
2696
+ shells: "transient",
2697
+ shellAgentWrite: "transient",
2698
+ createdByOperator: "durable",
2699
+ fallbackTitle: "transient",
2700
+ startAsleep: "transient",
2701
+ effortDefaults: "transient",
2702
+ connectFn: "transient",
2703
+ codexHome: "durable",
2704
+ codexPathOverride: "durable",
2705
+ languageModel: "transient",
2706
+ tools: "transient",
2707
+ maxSteps: "durable",
2708
+ executor: "transient",
2709
+ executableTools: "transient",
2710
+ vfs: "transient",
2711
+ executionLimits: "durable",
2712
+ executionBackend: "transient",
2713
+ toolTitles: "transient",
2714
+ shouldApprove: "transient",
2715
+ resolveModel: "transient",
2716
+ contextWindow: "transient",
2717
+ reportMcpServers: "transient",
2718
+ onClose: "transient",
2719
+ restore: "transient"
2720
+ };
2721
+ const HOST_ONLY_KEYS = new Set(Object.keys(HOST_ONLY_KEY_TABLE));
2722
+ const DURABLE_HOST_KEYS = Object.entries(HOST_ONLY_KEY_TABLE).filter(([, lifetime]) => lifetime === "durable").map(([key]) => key);
2723
+ //#endregion
2724
+ //#region src/routes/create-vet.ts
2725
+ function vetCreateRequest(ctx, body, auth) {
2726
+ if (typeof body !== "object" || body === null || Array.isArray(body)) return {
2727
+ ok: false,
2728
+ status: 400,
2729
+ error: "request body must be a JSON object"
2730
+ };
2731
+ const hostOnly = Object.keys(body).filter((key) => HOST_ONLY_KEYS.has(key));
2732
+ if (hostOnly.length > 0) return {
2733
+ ok: false,
2734
+ status: 400,
2735
+ error: `host-only runner config is not accepted on a session request: ${hostOnly.join(", ")} (set it from the gateway's buildRunnerConfig hook)`
2736
+ };
2737
+ const req = pickCreateSessionRequest(body);
2738
+ const { availability, factory } = ctx;
2739
+ const early = factory.applyScope(req, auth) ?? refusal(403, factory.applyBypassPolicy(req));
2740
+ if (early) return {
2741
+ ok: false,
2742
+ ...early
2743
+ };
2744
+ const resolved = factory.resolveProfile(req.profile, auth.allowedProfiles);
2745
+ if (!resolved.ok) return resolved;
2746
+ const refused = refusal(403, refuseHostAuthority(ctx, req, resolved.profile, auth)) ?? availability.checkAvailable(resolved.profile) ?? factory.checkCwd(req, resolved.profile) ?? refusal(400, factory.checkPermissionMode(req.permissionMode, resolved.profile) ?? factory.checkEngineGrants(req, resolved.profile));
2747
+ if (refused) return {
2748
+ ok: false,
2749
+ ...refused
2750
+ };
2751
+ factory.stripInertFields(req, resolved.profile);
2752
+ req.profile = resolved.profile?.name;
2753
+ return {
2754
+ ok: true,
2755
+ request: req
2756
+ };
2757
+ }
2758
+ function refusal(status, error) {
2759
+ return error === null ? null : {
2760
+ status,
2761
+ error
2762
+ };
2763
+ }
2764
+ function refuseHostAuthority(ctx, req, profile, auth) {
2765
+ if (ctx.auth.isOperator(auth)) return null;
2766
+ const engine = engineOf(profile);
2767
+ const name = profile?.name ?? "default";
2768
+ if ((engine === "claude" || engine === "codex") && !auth.allowedProfiles?.includes(name)) return `profile '${name}' runs the ${engine} engine on the host; a non-operator caller needs it named in its allowedProfiles`;
2769
+ const mode = refusePermissionMode(req.permissionMode, { operator: false });
2770
+ if (mode) return mode;
2771
+ if (req.allowDangerouslySkipPermissions) return "allowDangerouslySkipPermissions is reserved to operators";
2772
+ if (req.settingSources !== void 0 && req.settingSources.length > 0) return "settingSources is reserved to operators: it loads hooks and settings from the host";
2773
+ if (!(typeof req.mcpServers === "object" && req.mcpServers !== null ? Object.values(req.mcpServers) : []).every(isRemoteMcpServer)) return "a stdio MCP server is reserved to operators: it runs a command on the host";
2774
+ if (req.resume !== void 0 && !ownsSdkSession(ctx, auth, req.resume)) return "resume is limited to a live session this caller can see";
2775
+ return null;
2776
+ }
2777
+ function ownsSdkSession(ctx, auth, sdkSessionId) {
2778
+ return ctx.registry.list().some((info) => info.sdkSessionId === sdkSessionId && ctx.auth.canSee(auth, info));
2779
+ }
2780
+ function isRemoteMcpServer(server) {
2781
+ const type = server?.type;
2782
+ return type === "http" || type === "sse";
2783
+ }
2784
+ //#endregion
2785
+ //#region src/routes/agents.ts
2786
+ async function handleAgents(ctx, req, res, pathname, auth) {
2787
+ const agents = ctx.agents;
2788
+ const rest = pathname.slice((ctx.basePath + "/agents").length).replace(/^\//, "");
2789
+ if (rest === "") {
2790
+ requireMethod(req, "GET", "POST");
2791
+ if (req.method === "GET") {
2792
+ json(res, 200, { agents: agents.list() });
2793
+ return;
2794
+ }
2795
+ await createAgent(ctx, agents, req, res, auth);
2796
+ return;
2797
+ }
2798
+ const [id, action, ...extra] = rest.split("/").map(decodeURIComponent);
2799
+ const agent = id === void 0 ? void 0 : agents.get(id);
2800
+ if (!agent || extra.length > 0) fail(404, "agent not found");
2801
+ if (action === "restart") {
2802
+ requireMethod(req, "POST");
2803
+ const body = await readJsonBody(req, ctx.maxBodyBytes) ?? {};
2804
+ const previous = agent.sessionId === void 0 ? void 0 : ctx.registry.get(agent.sessionId);
2805
+ const created = await startSession(ctx, agent, typeof body.prompt === "string" ? body.prompt : void 0, auth);
2806
+ const status = previous?.info().status;
2807
+ if (previous && status !== "closed" && status !== "failed") previous.close("server");
2808
+ await respond(ctx, res, 200, await bindWithAvatar(ctx, agent, created), created);
2809
+ return;
2810
+ }
2811
+ if (action === "avatar.png" || action === "avatar-busy.png") {
2812
+ requireMethod(req, "GET");
2813
+ await sendAvatar(ctx, req, res, agent, action === "avatar-busy.png");
2814
+ return;
2815
+ }
2816
+ if (action !== void 0) fail(404, "agent not found");
2817
+ requireMethod(req, "GET", "PATCH", "DELETE");
2818
+ if (req.method === "GET") {
2819
+ await respond(ctx, res, 200, agent);
2820
+ return;
2821
+ }
2822
+ if (req.method === "PATCH") {
2823
+ const patch = await readJsonBody(req, ctx.maxBodyBytes);
2824
+ const updated = await agents.update(agent.id, isRecord(patch) ? patch : {});
2825
+ if (isAgentRefusal(updated)) fail(updated.status, updated.error);
2826
+ await respond(ctx, res, 200, updated);
2827
+ return;
2828
+ }
2829
+ const members = (await readJsonBody(req, ctx.maxBodyBytes) ?? {}).members === "retire" ? "retire" : "release";
2830
+ const outcome = await agents.retire(agent.id, members);
2831
+ if (isAgentRefusal(outcome)) fail(outcome.status, outcome.error);
2832
+ for (const gone of outcome.retired) {
2833
+ const runner = gone.sessionId === void 0 ? void 0 : ctx.registry.get(gone.sessionId);
2834
+ const status = runner?.info().status;
2835
+ if (runner && status !== "closed" && status !== "failed") runner.close("client");
2836
+ }
2837
+ json(res, 200, {
2838
+ retired: outcome.retired.map((a) => a.id),
2839
+ released: outcome.released.map((a) => a.id)
2840
+ });
2841
+ }
2842
+ async function createAgent(ctx, agents, req, res, auth) {
2843
+ const body = await readJsonBody(req, ctx.maxBodyBytes);
2844
+ if (!isRecord(body)) fail(400, "request body must be a JSON object");
2845
+ if (body.adopt !== void 0) {
2846
+ await adoptSession(ctx, agents, res, body);
2011
2847
  return;
2012
2848
  }
2013
- const authCtx = await auth.authenticate(req);
2014
- if (!authCtx.ok) {
2015
- refuseUpgrade(socket, "401 Unauthorized");
2849
+ const draft = agents.draft(body);
2850
+ if (isAgentRefusal(draft)) fail(draft.status, draft.error);
2851
+ const created = await startSession(ctx, draft, typeof body.prompt === "string" ? body.prompt : void 0, auth);
2852
+ await respond(ctx, res, 201, await bindWithAvatar(ctx, draft, created), created);
2853
+ }
2854
+ async function adoptSession(ctx, agents, res, body) {
2855
+ if (typeof body.adopt !== "string") fail(400, "adopt must be a session id");
2856
+ const info = await sessionInfoOf(ctx, body.adopt);
2857
+ if (!info) fail(404, "session not found");
2858
+ if (agents.bySession(info.id)) fail(409, "that session already belongs to an agent");
2859
+ const draft = agents.draft({
2860
+ name: body.name ?? info.title,
2861
+ config: {
2862
+ ...configOf(info),
2863
+ ...body.config
2864
+ },
2865
+ lead: body.lead
2866
+ });
2867
+ if (isAgentRefusal(draft)) fail(draft.status, draft.error);
2868
+ await respond(ctx, res, 201, await bindWithAvatar(ctx, draft, info));
2869
+ }
2870
+ async function bindWithAvatar(ctx, agent, session) {
2871
+ const recipe = agent.avatarRecipe ?? await rollAvatar(ctx, agent, session);
2872
+ return ctx.agents.bind(recipe === void 0 ? agent : {
2873
+ ...agent,
2874
+ avatarRecipe: recipe
2875
+ }, session.id);
2876
+ }
2877
+ async function rollAvatar(ctx, agent, session) {
2878
+ if (!ctx.avatars) return;
2879
+ const project = session ? ctx.projects.withProject(session).project?.root ?? session.cwd : void 0;
2880
+ try {
2881
+ return await ctx.avatars.roll(agent.avatarSeed, session?.engine, project || void 0);
2882
+ } catch {
2016
2883
  return;
2017
2884
  }
2018
- const known = await sessionInfoOf(ctx, route.id);
2019
- if (known && !auth.canSee(authCtx, known)) {
2020
- refuseUpgrade(socket, "404 Not Found");
2021
- return;
2885
+ }
2886
+ async function sendAvatar(ctx, req, res, agent, busy) {
2887
+ const avatars = ctx.avatars;
2888
+ if (!avatars) fail(404, "this gateway draws no avatars");
2889
+ let recipe = agent.avatarRecipe;
2890
+ if (recipe === void 0) {
2891
+ recipe = await rollAvatar(ctx, agent, agent.sessionId === void 0 ? void 0 : await sessionInfoOf(ctx, agent.sessionId));
2892
+ if (recipe === void 0) fail(503, "the avatar pack could not be loaded");
2893
+ await ctx.agents.save({
2894
+ ...agent,
2895
+ avatarRecipe: recipe
2896
+ });
2022
2897
  }
2023
- const runner = await parking.ensureLive(route.id).catch(() => void 0);
2024
- if (!runner || !auth.canSee(authCtx, runner.info())) {
2025
- refuseUpgrade(socket, "404 Not Found");
2898
+ const image = busy ? await avatars.busy(recipe) : await avatars.still(recipe);
2899
+ if (!image) fail(404, "this avatar pack has no busy animation");
2900
+ const headers = {
2901
+ "content-type": "image/png",
2902
+ etag: image.etag,
2903
+ "cache-control": "private, max-age=86400"
2904
+ };
2905
+ if (image.durations) headers["x-frame-durations"] = image.durations.join(",");
2906
+ if (req.headers["if-none-match"] === image.etag) {
2907
+ res.writeHead(304, headers).end();
2026
2908
  return;
2027
2909
  }
2028
- wss.handleUpgrade(req, socket, head, (ws) => {
2029
- attachClient(ctx, ws, runner, req, { operator: auth.isOperator(authCtx) });
2910
+ res.writeHead(200, headers).end(Buffer.from(image.png));
2911
+ }
2912
+ async function startSession(ctx, agent, prompt, auth) {
2913
+ const { config } = agent;
2914
+ const vetted = vetCreateRequest(ctx, stripUndefined({
2915
+ cwd: config.cwd,
2916
+ profile: config.profile,
2917
+ model: config.model,
2918
+ reasoningEffort: config.reasoningEffort,
2919
+ permissionMode: config.permissionMode,
2920
+ allowDangerouslySkipPermissions: config.permissionMode === "bypassPermissions" ? true : void 0,
2921
+ agentContextReset: config.agentContextReset ?? true,
2922
+ prompt
2923
+ }), auth);
2924
+ if (!vetted.ok) fail(vetted.status, vetted.error);
2925
+ return (await ctx.factory.createRunner(ctx.factory.buildRunnerConfig(vetted.request, { operator: ctx.auth.isOperator(auth) }), { brief: config.brief?.trim() || void 0 })).info();
2926
+ }
2927
+ async function respond(ctx, res, status, agent, session) {
2928
+ const info = session ?? (agent.sessionId === void 0 ? void 0 : await sessionInfoOf(ctx, agent.sessionId));
2929
+ const body = { agent: ctx.agents.public(agent) };
2930
+ if (info) body.session = ctx.projects.withProject(info);
2931
+ json(res, status, body);
2932
+ }
2933
+ function configOf(info) {
2934
+ return stripUndefined({
2935
+ cwd: info.cwd || void 0,
2936
+ profile: info.profile,
2937
+ model: info.model,
2938
+ reasoningEffort: info.effort ?? void 0,
2939
+ permissionMode: info.permissionMode
2030
2940
  });
2031
2941
  }
2032
- //#endregion
2033
- //#region src/lib/machine-id.ts
2034
- let cached;
2035
- function machineId() {
2036
- cached ??= createHash("sha256").update([
2037
- hostname(),
2038
- platform(),
2039
- arch(),
2040
- homedir()
2041
- ].join("\0")).digest("hex").slice(0, 32);
2042
- return cached;
2942
+ function stripUndefined(value) {
2943
+ return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
2944
+ }
2945
+ function isRecord(value) {
2946
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2043
2947
  }
2044
2948
  //#endregion
2045
2949
  //#region src/routes/executions.ts
@@ -2489,106 +3393,6 @@ async function handleWrite({ ctx, hostFiles, req, res }) {
2489
3393
  });
2490
3394
  }
2491
3395
  //#endregion
2492
- //#region src/lib/host-only-keys.ts
2493
- const HOST_ONLY_KEY_TABLE = {
2494
- epoch: "durable",
2495
- pricing: "transient",
2496
- queryFn: "transient",
2497
- env: "transient",
2498
- pathToClaudeCodeExecutable: "durable",
2499
- extraOptions: "transient",
2500
- instructions: "transient",
2501
- defaultApprovalTimeoutMs: "durable",
2502
- backfillHistory: "durable",
2503
- historyFn: "transient",
2504
- sessionInfoFn: "transient",
2505
- peers: "transient",
2506
- shells: "transient",
2507
- shellAgentWrite: "transient",
2508
- createdByOperator: "durable",
2509
- fallbackTitle: "transient",
2510
- connectFn: "transient",
2511
- codexHome: "durable",
2512
- codexPathOverride: "durable",
2513
- languageModel: "transient",
2514
- tools: "transient",
2515
- maxSteps: "durable",
2516
- executor: "transient",
2517
- executableTools: "transient",
2518
- vfs: "transient",
2519
- executionLimits: "durable",
2520
- executionBackend: "transient",
2521
- toolTitles: "transient",
2522
- shouldApprove: "transient",
2523
- resolveModel: "transient",
2524
- reportMcpServers: "transient",
2525
- onClose: "transient",
2526
- restore: "transient"
2527
- };
2528
- const HOST_ONLY_KEYS = new Set(Object.keys(HOST_ONLY_KEY_TABLE));
2529
- const DURABLE_HOST_KEYS = Object.entries(HOST_ONLY_KEY_TABLE).filter(([, lifetime]) => lifetime === "durable").map(([key]) => key);
2530
- //#endregion
2531
- //#region src/routes/create-vet.ts
2532
- function vetCreateRequest(ctx, body, auth) {
2533
- if (typeof body !== "object" || body === null || Array.isArray(body)) return {
2534
- ok: false,
2535
- status: 400,
2536
- error: "request body must be a JSON object"
2537
- };
2538
- const hostOnly = Object.keys(body).filter((key) => HOST_ONLY_KEYS.has(key));
2539
- if (hostOnly.length > 0) return {
2540
- ok: false,
2541
- status: 400,
2542
- error: `host-only runner config is not accepted on a session request: ${hostOnly.join(", ")} (set it from the gateway's buildRunnerConfig hook)`
2543
- };
2544
- const req = pickCreateSessionRequest(body);
2545
- const { availability, factory } = ctx;
2546
- const early = factory.applyScope(req, auth) ?? refusal(403, factory.applyBypassPolicy(req));
2547
- if (early) return {
2548
- ok: false,
2549
- ...early
2550
- };
2551
- const resolved = factory.resolveProfile(req.profile, auth.allowedProfiles);
2552
- if (!resolved.ok) return resolved;
2553
- const refused = refusal(403, refuseHostAuthority(ctx, req, resolved.profile, auth)) ?? availability.checkAvailable(resolved.profile) ?? factory.checkCwd(req, resolved.profile) ?? refusal(400, factory.checkPermissionMode(req.permissionMode, resolved.profile) ?? factory.checkEngineGrants(req, resolved.profile));
2554
- if (refused) return {
2555
- ok: false,
2556
- ...refused
2557
- };
2558
- factory.stripInertFields(req, resolved.profile);
2559
- req.profile = resolved.profile?.name;
2560
- return {
2561
- ok: true,
2562
- request: req
2563
- };
2564
- }
2565
- function refusal(status, error) {
2566
- return error === null ? null : {
2567
- status,
2568
- error
2569
- };
2570
- }
2571
- function refuseHostAuthority(ctx, req, profile, auth) {
2572
- if (ctx.auth.isOperator(auth)) return null;
2573
- const engine = engineOf(profile);
2574
- const name = profile?.name ?? "default";
2575
- if ((engine === "claude" || engine === "codex") && !auth.allowedProfiles?.includes(name)) return `profile '${name}' runs the ${engine} engine on the host; a non-operator caller needs it named in its allowedProfiles`;
2576
- const mode = refusePermissionMode(req.permissionMode, { operator: false });
2577
- if (mode) return mode;
2578
- if (req.allowDangerouslySkipPermissions) return "allowDangerouslySkipPermissions is reserved to operators";
2579
- if (req.settingSources !== void 0 && req.settingSources.length > 0) return "settingSources is reserved to operators: it loads hooks and settings from the host";
2580
- if (!(typeof req.mcpServers === "object" && req.mcpServers !== null ? Object.values(req.mcpServers) : []).every(isRemoteMcpServer)) return "a stdio MCP server is reserved to operators: it runs a command on the host";
2581
- if (req.resume !== void 0 && !ownsSdkSession(ctx, auth, req.resume)) return "resume is limited to a live session this caller can see";
2582
- return null;
2583
- }
2584
- function ownsSdkSession(ctx, auth, sdkSessionId) {
2585
- return ctx.registry.list().some((info) => info.sdkSessionId === sdkSessionId && ctx.auth.canSee(auth, info));
2586
- }
2587
- function isRemoteMcpServer(server) {
2588
- const type = server?.type;
2589
- return type === "http" || type === "sse";
2590
- }
2591
- //#endregion
2592
3396
  //#region src/routes/jobs.ts
2593
3397
  async function handleJobs(ctx, req, res, pathname, auth) {
2594
3398
  const { auth: authSvc, basePath, queue } = ctx;
@@ -3260,12 +4064,15 @@ const ITEM_HANDLERS = {
3260
4064
  produced: ({ ctx, req, res, route }) => handleProducedFiles(ctx, req, res, route.id, route.producedFileId),
3261
4065
  shells: ({ ctx, req, res, route, runner, auth }) => handleShells(ctx, req, res, route, runner ?? null, ctx.auth.isOperator(auth)),
3262
4066
  "stop-task": handleStopTask,
4067
+ "background-task": handleBackgroundTask,
3263
4068
  "project-icon": ({ ctx, req, res, info }) => handleProjectIcon(ctx.projects, req, res, info.cwd),
3264
4069
  "tool-result": ({ req, res, route, runner, parked }) => {
3265
4070
  const snapshot = parked && !isDormant(parked) ? parked.snapshot.events : void 0;
3266
4071
  handleToolResult(req, res, runner?.eventAt?.bind(runner) ?? (snapshot && ((seq) => snapshot.find((event) => event.seq === seq))), route.resultSeq);
3267
4072
  },
3268
- permission: handlePermission
4073
+ permission: handlePermission,
4074
+ peers: handlePeers,
4075
+ sleep: handleSleep
3269
4076
  };
3270
4077
  async function handleSessions(ctx, req, res, route, auth) {
3271
4078
  if (route.kind === "collection") {
@@ -3329,6 +4136,11 @@ async function handleSession({ ctx, req, res, route, runner, parked, info }) {
3329
4136
  status: "closed"
3330
4137
  }) });
3331
4138
  }
4139
+ async function handlePeers({ ctx, req, res, route }) {
4140
+ requireMethod(req, "GET");
4141
+ if (!ctx.peers) fail(404, "peer messaging is off on this gateway");
4142
+ json(res, 200, { peers: await ctx.peers.list(route.id) });
4143
+ }
3332
4144
  function handleFiles({ req, res, route, runner, parked }) {
3333
4145
  requireMethod(req, "GET");
3334
4146
  const snapshotFiles = parked && !isDormant(parked) ? parked.snapshot.vfs : void 0;
@@ -3356,6 +4168,18 @@ async function handleStopTask({ req, res, route, runner }) {
3356
4168
  if (!await live.stopTask(route.stopTaskId)) fail(404, "no running task to stop");
3357
4169
  json(res, 200, { ok: true });
3358
4170
  }
4171
+ async function handleBackgroundTask({ req, res, route, runner }) {
4172
+ requireMethod(req, "POST");
4173
+ const live = requireLive(runner, "it has no running tasks");
4174
+ if (!live.backgroundTask) fail(501, `the ${engineOf(live.info())} engine cannot move a task to the background`);
4175
+ if (!await live.backgroundTask(route.backgroundTaskId)) fail(404, "no foreground task to move to the background");
4176
+ json(res, 200, { ok: true });
4177
+ }
4178
+ async function handleSleep({ ctx, req, res, runner, info }) {
4179
+ requireMethod(req, "POST");
4180
+ if (runner) await sleepRunner(runner);
4181
+ json(res, 200, { session: ctx.projects.withProject(runner?.info() ?? info) });
4182
+ }
3359
4183
  async function handlePermission({ ctx, req, res, route, runner }) {
3360
4184
  requireMethod(req, "POST");
3361
4185
  const body = await readJsonBody(req, ctx.maxBodyBytes);
@@ -3403,6 +4227,11 @@ function httpRoutes(ctx) {
3403
4227
  auth: "any",
3404
4228
  handler: (req, res, pathname, auth) => handleProfiles(ctx, req, res, pathname, auth)
3405
4229
  }),
4230
+ route({
4231
+ match: pathWhere(under("/agents")),
4232
+ auth: "operator",
4233
+ handler: (req, res, pathname, auth) => handleAgents(ctx, req, res, pathname, auth)
4234
+ }),
3406
4235
  route({
3407
4236
  match: pathWhere((pathname) => pathname.startsWith(base + "/executions/")),
3408
4237
  auth: "any",
@@ -3470,6 +4299,36 @@ function pathWhere(test) {
3470
4299
  return (pathname) => test(pathname) ? pathname : void 0;
3471
4300
  }
3472
4301
  //#endregion
4302
+ //#region src/services/agent-store.ts
4303
+ function createMemoryAgentStore(seed = []) {
4304
+ const agents = new Map(seed.map((a) => [a.id, a]));
4305
+ return {
4306
+ list: () => [...agents.values()],
4307
+ save: (agent) => void agents.set(agent.id, agent),
4308
+ delete: (id) => void agents.delete(id)
4309
+ };
4310
+ }
4311
+ function createFileAgentStore(path = join(process.cwd(), ".workerdeck", "agents.json")) {
4312
+ const read = () => {
4313
+ const parsed = readJsonOrSync(path, []);
4314
+ if (!Array.isArray(parsed)) return /* @__PURE__ */ new Map();
4315
+ return new Map(parsed.filter((a) => a && typeof a.id === "string" && typeof a.name === "string").map((a) => [a.id, a]));
4316
+ };
4317
+ const write = (agents) => writeJsonAtomicSync(path, [...agents.values()], { indent: 2 });
4318
+ return {
4319
+ list: () => [...read().values()],
4320
+ save: (agent) => {
4321
+ const agents = read();
4322
+ agents.set(agent.id, agent);
4323
+ write(agents);
4324
+ },
4325
+ delete: (id) => {
4326
+ const agents = read();
4327
+ if (agents.delete(id)) write(agents);
4328
+ }
4329
+ };
4330
+ }
4331
+ //#endregion
3473
4332
  //#region src/services/attachments.ts
3474
4333
  const DEFAULT_MAX_FILE_BYTES = 10485760;
3475
4334
  const DEFAULT_MAX_SESSION_BYTES = 67108864;
@@ -3568,37 +4427,6 @@ function safeName(name) {
3568
4427
  return cleaned.length > 120 ? cleaned.slice(0, 120) : cleaned;
3569
4428
  }
3570
4429
  //#endregion
3571
- //#region src/lib/scope.ts
3572
- const MAX_SCOPE_KEYS = 16;
3573
- const MAX_SCOPE_LEN = 200;
3574
- function readScope(value) {
3575
- if (typeof value !== "object" || value === null || Array.isArray(value)) return;
3576
- const entries = Object.entries(value);
3577
- if (entries.some(([, v]) => typeof v !== "string")) return;
3578
- return Object.fromEntries(entries);
3579
- }
3580
- function checkScope(value) {
3581
- if (value === void 0) return null;
3582
- const scope = readScope(value);
3583
- if (!scope) return "scope must be an object of string values";
3584
- const entries = Object.entries(scope);
3585
- if (entries.length > MAX_SCOPE_KEYS) return `scope may carry at most ${MAX_SCOPE_KEYS} keys`;
3586
- for (const [key, val] of entries) {
3587
- if (key.length === 0) return "scope keys must not be empty";
3588
- if (key.length > MAX_SCOPE_LEN || val.length > MAX_SCOPE_LEN) return `scope keys and values must be at most ${MAX_SCOPE_LEN} characters`;
3589
- }
3590
- return null;
3591
- }
3592
- function sameScope(a, b) {
3593
- const left = Object.entries(a ?? {}).sort(([x], [y]) => x < y ? -1 : 1);
3594
- const right = Object.entries(b ?? {}).sort(([x], [y]) => x < y ? -1 : 1);
3595
- return left.length === right.length && left.every(([key, value], i) => right[i][0] === key && right[i][1] === value);
3596
- }
3597
- function scopeMatches(principal, session) {
3598
- if (!principal) return true;
3599
- return Object.entries(principal).every(([key, value]) => session?.[key] === value);
3600
- }
3601
- //#endregion
3602
4430
  //#region src/services/auth.ts
3603
4431
  function createAuthService(deps) {
3604
4432
  const { options, registry } = deps;
@@ -3708,6 +4536,192 @@ var AvailabilityTracker = class {
3708
4536
  }
3709
4537
  };
3710
4538
  //#endregion
4539
+ //#region src/services/engine-sleep.ts
4540
+ var EngineSleepTimers = class {
4541
+ #options;
4542
+ #timers = /* @__PURE__ */ new Map();
4543
+ #runners = /* @__PURE__ */ new Map();
4544
+ #closed = false;
4545
+ constructor(options) {
4546
+ this.#options = options;
4547
+ }
4548
+ get enabled() {
4549
+ return this.#options.afterMs > 0 && !this.#closed;
4550
+ }
4551
+ watch(runner) {
4552
+ if (this.#closed || !runner.sleep || !this.enabled && !this.#options.afterMsFor) return;
4553
+ this.#runners.set(runner.id, runner);
4554
+ const unsubscribe = runner.subscribe((event) => {
4555
+ if (event.type === "status_changed") this.#rearm(runner);
4556
+ }, runner.info().lastSeq);
4557
+ this.#rearm(runner);
4558
+ return () => {
4559
+ unsubscribe();
4560
+ this.#disarm(runner.id);
4561
+ if (this.#runners.get(runner.id) === runner) this.#runners.delete(runner.id);
4562
+ };
4563
+ }
4564
+ onDetach(sessionId) {
4565
+ const runner = this.#runners.get(sessionId);
4566
+ if (runner) this.#rearm(runner);
4567
+ }
4568
+ close() {
4569
+ this.#closed = true;
4570
+ for (const timer of this.#timers.values()) clearTimeout(timer);
4571
+ this.#timers.clear();
4572
+ this.#runners.clear();
4573
+ }
4574
+ #afterMs(sessionId) {
4575
+ return this.#closed ? 0 : this.#options.afterMsFor?.(sessionId) ?? this.#options.afterMs;
4576
+ }
4577
+ #rearm(runner) {
4578
+ this.#disarm(runner.id);
4579
+ const afterMs = this.#afterMs(runner.id);
4580
+ if (afterMs <= 0 || !this.#sleepable(runner)) return;
4581
+ const timer = setTimeout(() => {
4582
+ this.#timers.delete(runner.id);
4583
+ if (this.#afterMs(runner.id) <= 0 || !this.#sleepable(runner)) return;
4584
+ runner.sleep?.().catch((error) => this.#options.onError?.(error, runner.id));
4585
+ }, afterMs);
4586
+ timer.unref?.();
4587
+ this.#timers.set(runner.id, timer);
4588
+ }
4589
+ #sleepable(runner) {
4590
+ const info = runner.info();
4591
+ return info.status === "idle" && info.engineAsleep !== true && this.#options.attachedCount(runner.id) === 0;
4592
+ }
4593
+ #disarm(sessionId) {
4594
+ clearTimeout(this.#timers.get(sessionId));
4595
+ this.#timers.delete(sessionId);
4596
+ }
4597
+ };
4598
+ const HOUR_MS = 36e5;
4599
+ const RESET_TIMEOUT_MS = 6e4;
4600
+ const CONTEXT_RESET_SCHEDULED = "Reset scheduled. It runs when this turn ends: your conversation is cleared and your prompt becomes the first message of a fresh one. End your turn now and start no new work. If the turn is interrupted or ends in an error, the reset is cancelled.";
4601
+ var ContextResetService = class {
4602
+ #options;
4603
+ #runners = /* @__PURE__ */ new Map();
4604
+ #pending = /* @__PURE__ */ new Map();
4605
+ #running = /* @__PURE__ */ new Set();
4606
+ #history = /* @__PURE__ */ new Map();
4607
+ #turnFailed = /* @__PURE__ */ new Set();
4608
+ #closed = false;
4609
+ constructor(options = {}) {
4610
+ this.#options = options;
4611
+ }
4612
+ get defaultEnabled() {
4613
+ return this.#options.default === true;
4614
+ }
4615
+ watch(runner) {
4616
+ if (this.#closed) return;
4617
+ this.#runners.set(runner.id, runner);
4618
+ const unsubscribe = runner.subscribe((event) => this.#observe(runner, event), runner.info().lastSeq);
4619
+ return () => {
4620
+ unsubscribe();
4621
+ if (this.#runners.get(runner.id) === runner) {
4622
+ this.#runners.delete(runner.id);
4623
+ this.#pending.delete(runner.id);
4624
+ }
4625
+ };
4626
+ }
4627
+ async request(sessionId, request) {
4628
+ const runner = this.#runners.get(sessionId);
4629
+ if (this.#closed || !runner || runner.info().agentContextReset !== true) throw new Error("context reset is not enabled for this session");
4630
+ if (!runner.clearContext) throw new Error("this engine cannot clear its conversation");
4631
+ if (this.#pending.has(sessionId) || this.#running.has(sessionId)) throw new Error("a reset is already scheduled for this session");
4632
+ const refused = this.#rateRefusal(sessionId);
4633
+ if (refused) throw new Error(refused);
4634
+ this.#pending.set(sessionId, {
4635
+ prompt: request.prompt,
4636
+ reason: request.reason
4637
+ });
4638
+ this.#turnFailed.delete(sessionId);
4639
+ return CONTEXT_RESET_SCHEDULED;
4640
+ }
4641
+ close() {
4642
+ this.#closed = true;
4643
+ this.#pending.clear();
4644
+ this.#runners.clear();
4645
+ }
4646
+ #rateRefusal(sessionId) {
4647
+ const now = this.#now();
4648
+ const minInterval = this.#options.minIntervalMs ?? 6e5;
4649
+ const maxPerHour = this.#options.maxPerHour ?? 3;
4650
+ const recent = (this.#history.get(sessionId) ?? []).filter((at) => now - at < HOUR_MS);
4651
+ const last = recent.at(-1);
4652
+ if (last !== void 0 && now - last < minInterval) return `refused: the last reset was ${minutes(now - last)} ago and resets need ${minutes(minInterval)} between them; keep working`;
4653
+ if (maxPerHour <= 0 || recent.length >= maxPerHour) return `refused: this session already reset ${recent.length} time(s) in the last hour (limit ${maxPerHour}); keep working`;
4654
+ return null;
4655
+ }
4656
+ #observe(runner, event) {
4657
+ if (!this.#pending.has(runner.id)) return;
4658
+ if (event.type === "turn_result" && event.isError) {
4659
+ this.#turnFailed.add(runner.id);
4660
+ return;
4661
+ }
4662
+ if (event.type === "session_closed") {
4663
+ this.#pending.delete(runner.id);
4664
+ return;
4665
+ }
4666
+ if (event.type !== "status_changed" || event.status !== "idle") return;
4667
+ const request = this.#pending.get(runner.id);
4668
+ this.#pending.delete(runner.id);
4669
+ if (this.#turnFailed.delete(runner.id)) return;
4670
+ this.#run(runner, request);
4671
+ }
4672
+ async #run(runner, request) {
4673
+ this.#running.add(runner.id);
4674
+ try {
4675
+ const reset = nextReset(runner, this.#options.resetTimeoutMs ?? RESET_TIMEOUT_MS);
4676
+ try {
4677
+ await runner.clearContext({ agentReason: request.reason });
4678
+ await reset.done;
4679
+ } finally {
4680
+ reset.cancel();
4681
+ }
4682
+ const history = (this.#history.get(runner.id) ?? []).filter((at) => this.#now() - at < HOUR_MS);
4683
+ history.push(this.#now());
4684
+ this.#history.set(runner.id, history);
4685
+ runner.sendMessage(request.prompt);
4686
+ } catch (error) {
4687
+ this.#options.onError?.(error, runner.id);
4688
+ } finally {
4689
+ this.#running.delete(runner.id);
4690
+ }
4691
+ }
4692
+ #now() {
4693
+ return this.#options.now?.() ?? Date.now();
4694
+ }
4695
+ };
4696
+ function nextReset(runner, timeoutMs) {
4697
+ let timer;
4698
+ let unsubscribe;
4699
+ const cancel = () => {
4700
+ clearTimeout(timer);
4701
+ unsubscribe?.();
4702
+ };
4703
+ return {
4704
+ done: new Promise((resolve, reject) => {
4705
+ timer = setTimeout(() => {
4706
+ cancel();
4707
+ reject(/* @__PURE__ */ new Error("the engine did not confirm the reset"));
4708
+ }, timeoutMs);
4709
+ timer.unref?.();
4710
+ unsubscribe = runner.subscribe((event) => {
4711
+ if (event.type === "conversation_reset") {
4712
+ cancel();
4713
+ resolve();
4714
+ }
4715
+ }, runner.info().lastSeq);
4716
+ }),
4717
+ cancel
4718
+ };
4719
+ }
4720
+ function minutes(ms) {
4721
+ const value = Math.max(1, Math.round(ms / 6e4));
4722
+ return `${value} minute${value === 1 ? "" : "s"}`;
4723
+ }
4724
+ //#endregion
3711
4725
  //#region src/services/bridge.ts
3712
4726
  var BridgeHub = class {
3713
4727
  #sessions = /* @__PURE__ */ new Map();
@@ -4023,7 +5037,10 @@ var SessionParkManager = class {
4023
5037
  }
4024
5038
  async listInfo() {
4025
5039
  await Promise.all(this.#storeOps.values());
4026
- return (await this.#options.store.list()).filter((record) => this.#options.registry.get(record.id) === void 0).map((record) => record.info);
5040
+ return (await this.#options.store.list()).filter((record) => this.#options.registry.get(record.id) === void 0).map((record) => isDormant(record) ? {
5041
+ ...record.info,
5042
+ engineAsleep: true
5043
+ } : record.info);
4027
5044
  }
4028
5045
  async ensureLive(id) {
4029
5046
  const live = this.#options.registry.get(id);
@@ -4273,7 +5290,7 @@ var SessionParkManager = class {
4273
5290
  }
4274
5291
  };
4275
5292
  function idleRecordBase(runner, config) {
4276
- const info = runner.info();
5293
+ const { engineAsleep: _engineAsleep, ...info } = runner.info();
4277
5294
  return {
4278
5295
  id: runner.id,
4279
5296
  info: {
@@ -4338,6 +5355,8 @@ var ProfileService = class {
4338
5355
  if (p.codexHome && !existsSync(p.codexHome)) return `profile '${p.name}' codexHome does not exist: ${p.codexHome}`;
4339
5356
  } else if (!p.configDir || !existsSync(p.configDir)) return `profile '${p.name}' configDir does not exist: ${p.configDir}`;
4340
5357
  if (disableBypassPermissions && p.defaults?.permissionMode === "bypassPermissions") return `profile '${p.name}' defaults to bypassPermissions but disableBypassPermissions is set`;
5358
+ const efforts = p.defaults?.efforts;
5359
+ if (efforts !== void 0 && !isEffortMap(efforts)) return `profile '${p.name}' defaults.efforts must map model names to effort levels`;
4341
5360
  const fallbackMode = p.defaults?.permissionMode;
4342
5361
  if (fallbackMode && !supportsPermissionMode(p.engine, fallbackMode)) return `profile '${p.name}' defaults to permission mode '${fallbackMode}', which engine '${engineOf(p)}' does not support (supported: ${adapterFor(engineOf(p)).capabilities.permissionModes.join(", ")})`;
4343
5362
  return null;
@@ -4439,6 +5458,9 @@ var ProfileService = class {
4439
5458
  };
4440
5459
  }
4441
5460
  };
5461
+ function isEffortMap(value) {
5462
+ return typeof value === "object" && value !== null && !Array.isArray(value) && Object.entries(value).every(([model, effort]) => model !== "" && typeof effort === "string" && effort !== "");
5463
+ }
4442
5464
  //#endregion
4443
5465
  //#region src/services/profile-usage.ts
4444
5466
  var ProfileUsageTracker = class {
@@ -4716,7 +5738,7 @@ function createRelayLink(options, peers, log) {
4716
5738
  const remoteTarget = parseRelayPeerId;
4717
5739
  const reachesRemote = async (from) => {
4718
5740
  const me = await peers.relaySender(from);
4719
- return me.scope === void 0 || Object.keys(me.scope).length === 0;
5741
+ return (me.scope === void 0 || Object.keys(me.scope).length === 0) && me.agent?.lead === void 0;
4720
5742
  };
4721
5743
  return {
4722
5744
  directory: {
@@ -4783,264 +5805,6 @@ function createRelayLink(options, peers, log) {
4783
5805
  };
4784
5806
  }
4785
5807
  //#endregion
4786
- //#region src/services/peers.ts
4787
- const DEFAULT_PER_MINUTE = 10;
4788
- const DEFAULT_MAX_HOPS = 12;
4789
- const WINDOW_MS = 6e4;
4790
- function visible(from, to) {
4791
- return to.id !== from.id && scopeMatches(from.scope, to.scope);
4792
- }
4793
- function relayEntry(info, live) {
4794
- const items = info.checklist;
4795
- return {
4796
- id: info.id,
4797
- engine: info.engine,
4798
- status: info.status,
4799
- title: info.title,
4800
- project: info.project ? {
4801
- name: info.project.name,
4802
- root: info.project.root
4803
- } : void 0,
4804
- cwd: info.cwd,
4805
- profile: info.profile,
4806
- model: info.model,
4807
- permissionMode: info.permissionMode,
4808
- contextUsage: info.contextUsage,
4809
- checklist: items?.length ? {
4810
- done: items.filter((item) => item.status === "completed").length,
4811
- total: items.length
4812
- } : void 0,
4813
- numTurns: info.numTurns,
4814
- createdAt: info.createdAt,
4815
- lastActivityAt: info.lastActivityAt,
4816
- pendingPermissionCount: info.pendingPermissionCount,
4817
- live
4818
- };
4819
- }
4820
- function createPeerService(deps) {
4821
- const perMinute = deps.options?.perMinute ?? DEFAULT_PER_MINUTE;
4822
- const maxChars = deps.options?.maxMessageChars ?? PEER_MESSAGE_MAX_CHARS;
4823
- const maxHops = deps.options?.maxHops ?? DEFAULT_MAX_HOPS;
4824
- const sent = /* @__PURE__ */ new Map();
4825
- const inbound = /* @__PURE__ */ new Map();
4826
- const registry = () => {
4827
- const found = deps.refs.registry;
4828
- if (!found) throw new Error("peer messaging is not ready");
4829
- return found;
4830
- };
4831
- const allSessions = async () => {
4832
- const live = registry().list();
4833
- const dormant = await deps.refs.parking?.listInfo() ?? [];
4834
- return [...live, ...dormant].map((info) => deps.projects.withProject(info));
4835
- };
4836
- const infoOf = async (id) => {
4837
- const live = registry().get(id)?.info();
4838
- if (live) return deps.projects.withProject(live);
4839
- const record = await deps.refs.parking?.get(id);
4840
- return record ? deps.projects.withProject(record.info) : void 0;
4841
- };
4842
- const sender = async (from) => {
4843
- const info = await infoOf(from);
4844
- if (!info) throw new Error(`unknown sender session: ${from}`);
4845
- return info;
4846
- };
4847
- const recentOf = (sessionId, recent) => {
4848
- const limit = Math.min(Math.max(recent ?? PEER_RECENT_DEFAULT, 0), PEER_RECENT_MAX);
4849
- const runner = registry().get(sessionId);
4850
- const events = [];
4851
- if (runner && limit > 0) runner.subscribe((event) => events.push(event), 0, {
4852
- truncateResults: true,
4853
- imageRefs: true
4854
- })();
4855
- return {
4856
- runner,
4857
- lines: recentLines(events, limit)
4858
- };
4859
- };
4860
- const underRateLimit = (from, to) => {
4861
- const key = `${from}->${to}`;
4862
- const now = Date.now();
4863
- const recent = (sent.get(key) ?? []).filter((at) => now - at < WINDOW_MS);
4864
- if (recent.length >= perMinute) {
4865
- sent.set(key, recent);
4866
- return false;
4867
- }
4868
- recent.push(now);
4869
- sent.set(key, recent);
4870
- return true;
4871
- };
4872
- const list = async (from) => {
4873
- const me = await sender(from);
4874
- return (await allSessions()).filter((info) => visible(me, info)).sort((a, b) => (b.lastActivityAt ?? b.createdAt) - (a.lastActivityAt ?? a.createdAt)).map(peerSummary);
4875
- };
4876
- const peek = async (from, sessionId, options) => {
4877
- const me = await sender(from);
4878
- const info = await infoOf(sessionId);
4879
- if (!info || !visible(me, info)) return;
4880
- const { runner, lines } = recentOf(sessionId, options?.recent);
4881
- return {
4882
- ...peerSummary(info),
4883
- live: runner !== void 0,
4884
- checklist: info.checklist,
4885
- pendingApprovals: runner ? runner.pendingApprovals.map((request) => request.title ?? request.toolName) : [],
4886
- recent: lines
4887
- };
4888
- };
4889
- const send = async (from, sessionId, text, options) => {
4890
- const me = await sender(from);
4891
- if (sessionId === from) return {
4892
- delivered: false,
4893
- reason: "that is this session"
4894
- };
4895
- if (text.length > maxChars) return {
4896
- delivered: false,
4897
- reason: `message is ${text.length} characters; the limit is ${maxChars}. Write it to a file and send the path.`
4898
- };
4899
- const target = await infoOf(sessionId);
4900
- if (!target || !visible(me, target)) return {
4901
- delivered: false,
4902
- reason: `no such session: ${sessionId}`
4903
- };
4904
- const hops = [...options?.hops ?? inbound.get(from) ?? [], from];
4905
- return deliver(target, text, {
4906
- kind: "peer",
4907
- sessionId: from,
4908
- name: me.title,
4909
- engine: me.engine,
4910
- hops
4911
- }, from);
4912
- };
4913
- const deliver = async (target, text, origin, rateKey) => {
4914
- const sessionId = target.id;
4915
- if (target.status === "closed" || target.status === "failed") return {
4916
- delivered: false,
4917
- reason: `session ${sessionId} is ${target.status}`
4918
- };
4919
- const hops = origin.hops ?? [];
4920
- if (hops.length > maxHops) return {
4921
- delivered: false,
4922
- reason: `${hops.length} messages have passed between sessions without a person speaking; stop and ask your user before continuing.`
4923
- };
4924
- if (!underRateLimit(rateKey, sessionId)) return {
4925
- delivered: false,
4926
- reason: `rate limit: at most ${perMinute} messages a minute to one session. Batch what you have to say.`
4927
- };
4928
- const runner = await deps.refs.parking?.ensureLive(sessionId) ?? registry().get(sessionId);
4929
- if (!runner) return {
4930
- delivered: false,
4931
- reason: `session ${sessionId} could not be woken`
4932
- };
4933
- const before = runner.info().status;
4934
- try {
4935
- runner.sendMessage(text, void 0, { origin });
4936
- } catch (error) {
4937
- return {
4938
- delivered: false,
4939
- reason: error instanceof Error ? error.message : String(error)
4940
- };
4941
- }
4942
- inbound.set(sessionId, hops);
4943
- return {
4944
- delivered: true,
4945
- sessionId,
4946
- name: target.title,
4947
- queued: before === "running" || before === "awaiting_approval"
4948
- };
4949
- };
4950
- const exposedInfo = async (sessionId, exposed) => {
4951
- const info = await infoOf(sessionId);
4952
- return info && info.status !== "closed" && scopeMatches(exposed, info.scope) ? info : void 0;
4953
- };
4954
- const relayEntries = async (exposed) => {
4955
- const live = new Set(registry().list().map((info) => info.id));
4956
- return (await allSessions()).filter((info) => info.status !== "closed" && scopeMatches(exposed, info.scope)).map((info) => relayEntry(info, live.has(info.id)));
4957
- };
4958
- const relayPeek = async (sessionId, recent, exposed) => {
4959
- const info = await exposedInfo(sessionId, exposed);
4960
- if (!info) return;
4961
- const { runner, lines } = recentOf(sessionId, recent);
4962
- return {
4963
- ...relayEntry(info, runner !== void 0),
4964
- checklistItems: info.checklist,
4965
- pendingApprovals: runner ? runner.pendingApprovals.map((request) => request.title ?? request.toolName) : [],
4966
- recent: lines
4967
- };
4968
- };
4969
- const relaySend = async (origin, sessionId, text, exposed) => {
4970
- if (text.length === 0 || text.length > maxChars) return {
4971
- delivered: false,
4972
- reason: `message is ${text.length} characters; the limit is ${maxChars}. Write it to a file and send the path.`
4973
- };
4974
- const target = await exposedInfo(sessionId, exposed);
4975
- if (!target) return {
4976
- delivered: false,
4977
- reason: `no such session: ${sessionId}`
4978
- };
4979
- const from = `${origin.gateway}:${origin.sessionId}`;
4980
- return deliver(target, text, {
4981
- kind: "peer",
4982
- sessionId: from,
4983
- hostId: origin.gateway,
4984
- name: origin.name,
4985
- engine: origin.engine,
4986
- hops: origin.hops
4987
- }, from);
4988
- };
4989
- const mentions = async (from, text) => {
4990
- const tokens = scanPeerMentions(text);
4991
- if (tokens.length === 0) return [];
4992
- const rows = await list(from);
4993
- const byKey = /* @__PURE__ */ new Map();
4994
- const add = (key, row) => {
4995
- const held = byKey.get(key);
4996
- if (held) held.push(row);
4997
- else byKey.set(key, [row]);
4998
- };
4999
- for (const row of rows) {
5000
- add(peerMentionKey(peerMentionSlug(row.title, row.id)), row);
5001
- add(peerMentionKey(row.id), row);
5002
- }
5003
- const resolved = [];
5004
- const seen = /* @__PURE__ */ new Set();
5005
- for (const token of tokens) {
5006
- const key = peerMentionKey(token.body);
5007
- const exact = byKey.get(key);
5008
- const matches = exact ?? (key.length >= 4 ? rows.filter((row) => row.id.startsWith(key)) : []);
5009
- const target = matches[0];
5010
- if (!target || matches.length > 1 && !exact) continue;
5011
- if (seen.has(target.id)) continue;
5012
- seen.add(target.id);
5013
- const others = matches.slice(1, 4).map((row) => row.id);
5014
- resolved.push({
5015
- typed: token.body,
5016
- id: target.id,
5017
- name: target.title,
5018
- engine: target.engine,
5019
- status: target.status,
5020
- cwd: target.cwd,
5021
- ...others.length ? { ambiguousWith: others } : {}
5022
- });
5023
- if (resolved.length >= PEER_MENTION_MAX) break;
5024
- }
5025
- return resolved;
5026
- };
5027
- const watch = (runner) => runner.subscribe((event) => {
5028
- if (event.type === "user_message" && !event.origin && !event.synthetic && event.parentToolUseId == null && !event.replay) inbound.delete(runner.id);
5029
- }, runner.info().lastSeq);
5030
- return {
5031
- list,
5032
- peek,
5033
- send,
5034
- mentions,
5035
- watch,
5036
- relaySender: sender,
5037
- relayChain: (from) => [...inbound.get(from) ?? [], from],
5038
- relayEntries,
5039
- relayPeek,
5040
- relaySend
5041
- };
5042
- }
5043
- //#endregion
5044
5808
  //#region src/services/registry.ts
5045
5809
  var SessionRegistry = class {
5046
5810
  #sessions = /* @__PURE__ */ new Map();
@@ -5252,7 +6016,7 @@ function createSessionFactory(deps) {
5252
6016
  return adapterFor(profile.engine).sessionEnv?.(profile, process.env) ?? process.env;
5253
6017
  }
5254
6018
  };
5255
- const buildRunner = async (built, restore, id) => {
6019
+ const buildRunner = async (built, restore, id, options) => {
5256
6020
  const name = built.profile;
5257
6021
  const profile = name !== void 0 ? profiles.get(name) : void 0;
5258
6022
  if (name !== void 0 && !profile) throw new Error(`unknown profile: ${name}`);
@@ -5262,13 +6026,23 @@ function createSessionFactory(deps) {
5262
6026
  ...deps.peers ? { peers: deps.peers } : {},
5263
6027
  ...deps.pricing ? { pricing: deps.pricing } : {}
5264
6028
  };
6029
+ delete config.contextReset;
5265
6030
  delete config.shells;
5266
6031
  delete config.shellAgentWrite;
6032
+ delete config.effortDefaults;
6033
+ const efforts = {
6034
+ ...deps.effortDefaults,
6035
+ ...profile?.defaults?.efforts
6036
+ };
6037
+ if (Object.keys(efforts).length > 0) config.effortDefaults = efforts;
6038
+ if (deps.contextReset && (built.agentContextReset ?? profile?.defaults?.agentContextReset ?? deps.contextReset.defaultEnabled)) config.contextReset = deps.contextReset.directory;
5267
6039
  if (deps.shells && capabilities.hostCwd === true) {
5268
6040
  config.shells = deps.shells;
5269
6041
  const write = deps.shellAgentWrite ?? "read-only";
5270
6042
  if (write !== "read-only" && built.createdByOperator === true) config.shellAgentWrite = write;
5271
6043
  }
6044
+ const brief = options?.brief ?? deps.agentBrief?.(id ?? restore?.id);
6045
+ if (brief) config.instructions = composeInstructions(config.instructions, brief);
5272
6046
  if (config.instructions !== void 0 && capabilities.systemInstructions === false) throw new Error(`the ${engineOf(profile)} engine cannot deliver system instructions`);
5273
6047
  const runner = profile && isProviderProfile(profile) ? await deps.createEngineRunner({
5274
6048
  config,
@@ -5286,8 +6060,8 @@ function createSessionFactory(deps) {
5286
6060
  if (!sameScope(reported, config.scope)) throw new Error(`runner for session ${runner.id} reports scope ${JSON.stringify(reported)}, expected ${JSON.stringify(config.scope)} - echo config.scope from info()`);
5287
6061
  return runner;
5288
6062
  };
5289
- const createRunner = async (config) => {
5290
- const runner = registry.register(await buildRunner(config));
6063
+ const createRunner = async (config, options) => {
6064
+ const runner = registry.register(await buildRunner(config, void 0, void 0, options));
5291
6065
  parking.remember(runner.id, config);
5292
6066
  parking.watch(runner);
5293
6067
  runner.start();
@@ -5406,13 +6180,31 @@ function createWorkerServer(options = {}) {
5406
6180
  }
5407
6181
  const generation = randomUUID();
5408
6182
  const shells = options.shell?.enabled === true ? shellRegistryFor(options.shell, generation) : null;
5409
- const projects = new ProjectInfoService({ decorate: (info) => shells ? shells.decorate(info) : info });
6183
+ const agents = new AgentService({
6184
+ store: options.agentStore ?? createMemoryAgentStore(),
6185
+ basePath,
6186
+ avatars: options.avatars !== void 0,
6187
+ sleepAfterMs: options.agentSleepAfterMs
6188
+ });
6189
+ const projects = new ProjectInfoService({ decorate: (info) => agents.decorate(shells ? shells.decorate(info) : info) });
5410
6190
  const notifier = new SessionNotifier({
5411
6191
  ...options.notifications,
5412
6192
  decorateInfo: (info) => projects.withProject(info),
5413
6193
  onError: options.notifications?.onError ?? ((error, context) => diagnose(error, `notification-${context.op}`))
5414
6194
  });
5415
6195
  const producedFiles = new ProducedFileStore();
6196
+ const engineSleep = new EngineSleepTimers({
6197
+ afterMs: options.engineSleepAfterMs ?? 0,
6198
+ afterMsFor: (sessionId) => agents.sleepAfterFor(sessionId),
6199
+ attachedCount: (sessionId) => bridge.attachedCount(sessionId),
6200
+ onError: (error) => diagnose(error, "engine-sleep")
6201
+ });
6202
+ const contextResets = options.agentContextReset === false ? void 0 : new ContextResetService({
6203
+ ...options.agentContextReset,
6204
+ onError: (error) => diagnose(error, "context-reset")
6205
+ });
6206
+ let ownContextResets = contextResets;
6207
+ installContextResetDirectory(contextResets);
5416
6208
  const registry = new SessionRegistry({ onRegister: (runner) => {
5417
6209
  const detachers = [
5418
6210
  notifier.watch(runner),
@@ -5421,7 +6213,9 @@ function createWorkerServer(options = {}) {
5421
6213
  spendLedger.watch(runner),
5422
6214
  shells?.watch(runner),
5423
6215
  peers?.watch(runner),
5424
- factory.watchAuthSource(runner)
6216
+ factory.watchAuthSource(runner),
6217
+ engineSleep.watch(runner),
6218
+ contextResets?.watch(runner)
5425
6219
  ];
5426
6220
  const profile = runner.info().profile;
5427
6221
  if (profile) detachers.push(runner.subscribe((event) => {
@@ -5467,6 +6261,7 @@ function createWorkerServer(options = {}) {
5467
6261
  let ownShells = shellDirectory;
5468
6262
  if (ownPeers) installPeerDirectory(ownPeers);
5469
6263
  installShellDirectory(shellDirectory);
6264
+ if (options.effortDefaults !== void 0 && !isEffortMap(options.effortDefaults)) throw new Error("createWorkerServer: `effortDefaults` must map model names to effort levels");
5470
6265
  const factory = createSessionFactory({
5471
6266
  adapterFor,
5472
6267
  profiles,
@@ -5475,14 +6270,20 @@ function createWorkerServer(options = {}) {
5475
6270
  allowedCwdRoots: options.allowedCwdRoots,
5476
6271
  disableBypassPermissions: options.disableBypassPermissions,
5477
6272
  approvalTimeoutMs: options.approvalTimeoutMs,
6273
+ effortDefaults: options.effortDefaults,
5478
6274
  requireApiKey: options.requireApiKey,
5479
6275
  peers: peers ? peerDirectoryHandle(() => ownPeers) : void 0,
6276
+ contextReset: contextResets ? {
6277
+ directory: contextResetDirectoryHandle(() => ownContextResets),
6278
+ defaultEnabled: contextResets.defaultEnabled
6279
+ } : void 0,
5480
6280
  shells: shellDirectory ? shellDirectoryHandle(() => ownShells) : void 0,
5481
6281
  shellAgentWrite: options.shell?.agentWrite,
5482
6282
  pricing: pricing.pricing,
5483
6283
  registry,
5484
6284
  parking,
5485
- bridge
6285
+ bridge,
6286
+ agentBrief: (sessionId) => agents.briefFor(sessionId)
5486
6287
  });
5487
6288
  const availability = new AvailabilityTracker({
5488
6289
  checkCredentials: options.checkCredentials,
@@ -5530,9 +6331,12 @@ function createWorkerServer(options = {}) {
5530
6331
  availability,
5531
6332
  auth,
5532
6333
  factory,
6334
+ agents,
6335
+ avatars: options.avatars,
5533
6336
  registry,
5534
6337
  parking,
5535
- peers,
6338
+ engineSleep,
6339
+ peers: peers ? peerDirectoryHandle(() => ownPeers) : void 0,
5536
6340
  bridge,
5537
6341
  projects,
5538
6342
  queue,
@@ -5579,12 +6383,15 @@ function createWorkerServer(options = {}) {
5579
6383
  parking,
5580
6384
  shells,
5581
6385
  queue,
6386
+ engineSleep,
5582
6387
  closeQueueSockets: queueSockets.clear,
5583
6388
  diagnose,
5584
6389
  releaseDirectories: () => {
5585
6390
  relay?.close();
5586
6391
  ownPeers = void 0;
5587
6392
  ownShells = void 0;
6393
+ contextResets?.close();
6394
+ ownContextResets = void 0;
5588
6395
  }
5589
6396
  });
5590
6397
  return {
@@ -5597,6 +6404,7 @@ function createWorkerServer(options = {}) {
5597
6404
  listen: async (port, host) => {
5598
6405
  await profiles.refreshStored();
5599
6406
  await profiles.seedStore();
6407
+ await agents.hydrate();
5600
6408
  await parking.hydrate();
5601
6409
  await shells?.hydrate();
5602
6410
  return new Promise((resolve, reject) => {
@@ -5684,7 +6492,8 @@ function rebuildRunner(factory, record) {
5684
6492
  resume: record.sdkSessionId
5685
6493
  }),
5686
6494
  epoch: (record.info.epoch ?? 0) + 1,
5687
- fallbackTitle: record.info.title
6495
+ fallbackTitle: record.info.title,
6496
+ startAsleep: true
5688
6497
  }, void 0, record.id);
5689
6498
  }
5690
6499
  //#endregion
@@ -5791,6 +6600,6 @@ function createFileProfileStore(path = join(process.cwd(), ".workerdeck", "profi
5791
6600
  };
5792
6601
  }
5793
6602
  //#endregion
5794
- export { AttachmentStore, BridgeHub, MemorySessionStore, ProducedFileStore, ProfileUsageTracker, SHELL_REFUSAL, SessionNotifier, SessionParkManager, SessionRegistry, SpendLedger, createFileProfileStore, createFileSessionStore, createFileSpendStore, createMemoryProfileStore, createProviderRunner, createShellDirectory, createShellRegistry, createWorkerServer, dayKey, isDormant, isMissing, readJsonOr, reloadPlan, sandboxedProviderProfile, shellPermitted, toDurableRecord, writeFileAtomic, writeJsonAtomic };
6603
+ export { AttachmentStore, BridgeHub, MemorySessionStore, ProducedFileStore, ProfileUsageTracker, SHELL_REFUSAL, SessionNotifier, SessionParkManager, SessionRegistry, SpendLedger, createFileAgentStore, createFileProfileStore, createFileSessionStore, createFileSpendStore, createMemoryAgentStore, createMemoryProfileStore, createProviderRunner, createShellDirectory, createShellRegistry, createWorkerServer, dayKey, isDormant, isMissing, readJsonOr, reloadPlan, sandboxedProviderProfile, shellPermitted, toDurableRecord, writeFileAtomic, writeJsonAtomic };
5795
6604
 
5796
6605
  //# sourceMappingURL=index.mjs.map