@workerdeck/server 3.2.0 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/index.d.mts +28 -1
- package/build/index.mjs +755 -225
- package/build/index.mjs.map +1 -1
- package/package.json +7 -5
package/build/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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, 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
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";
|
|
7
7
|
import { chmodSync, closeSync, constants, createReadStream, existsSync, fstatSync, ftruncateSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, realpathSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
@@ -10,6 +10,7 @@ import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "nod
|
|
|
10
10
|
import { chmod, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
11
11
|
import { spawnSync } from "node:child_process";
|
|
12
12
|
import xtermHeadless from "@xterm/headless";
|
|
13
|
+
import { connectRelay, parseRelayPeerId } from "@workerdeck/relay-client";
|
|
13
14
|
//#region src/lifecycle.ts
|
|
14
15
|
const SOCKET_CLOSE_GRACE_MS = 250;
|
|
15
16
|
const CLOSE_DEADLINE_MS = 1e3;
|
|
@@ -78,6 +79,7 @@ function closeServer(deps) {
|
|
|
78
79
|
shells?.killAll("server_stopped");
|
|
79
80
|
const flushed = shells?.flush().catch((error) => deps.diagnose(error, "shell-flush")) ?? Promise.resolve();
|
|
80
81
|
parking.close();
|
|
82
|
+
deps.engineSleep.close();
|
|
81
83
|
registry.closeAll();
|
|
82
84
|
for (const ws of wss.clients) ws.close(1001, "server shutting down");
|
|
83
85
|
deps.closeQueueSockets();
|
|
@@ -221,6 +223,23 @@ function cwdAllowed(cwd, roots) {
|
|
|
221
223
|
return resolved === r || resolved.startsWith(r + sep);
|
|
222
224
|
});
|
|
223
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
|
+
}
|
|
224
243
|
function readProfileConfig(profile) {
|
|
225
244
|
const dir = profile.configDir;
|
|
226
245
|
if (!dir) return {
|
|
@@ -229,22 +248,8 @@ function readProfileConfig(profile) {
|
|
|
229
248
|
agents: [],
|
|
230
249
|
commands: []
|
|
231
250
|
};
|
|
232
|
-
const listDirs = (path) => {
|
|
233
|
-
try {
|
|
234
|
-
return readdirSync(path, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
235
|
-
} catch {
|
|
236
|
-
return [];
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
const listMd = (path) => {
|
|
240
|
-
try {
|
|
241
|
-
return readdirSync(path).filter((file) => file.endsWith(".md")).map((file) => file.slice(0, -3)).sort();
|
|
242
|
-
} catch {
|
|
243
|
-
return [];
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
251
|
const snapshot = {
|
|
247
|
-
hasUserMemory: existsSync(join(dir,
|
|
252
|
+
hasUserMemory: ["CLAUDE.md", "AGENTS.md"].some((name) => existsSync(join(dir, name))),
|
|
248
253
|
skills: listDirs(join(dir, "skills")),
|
|
249
254
|
agents: listMd(join(dir, "agents")),
|
|
250
255
|
commands: listMd(join(dir, "commands"))
|
|
@@ -252,7 +257,6 @@ function readProfileConfig(profile) {
|
|
|
252
257
|
try {
|
|
253
258
|
const raw = JSON.parse(readFileSync(join(dir, "settings.json"), "utf8"));
|
|
254
259
|
const permissions = raw.permissions ?? {};
|
|
255
|
-
const count = (rules) => Array.isArray(rules) ? rules.length : 0;
|
|
256
260
|
snapshot.settings = {
|
|
257
261
|
model: typeof raw.model === "string" ? raw.model : void 0,
|
|
258
262
|
defaultPermissionMode: typeof permissions.defaultMode === "string" ? permissions.defaultMode : void 0,
|
|
@@ -387,6 +391,15 @@ function itemRoute(rawId, parts) {
|
|
|
387
391
|
id: decode(rawId),
|
|
388
392
|
stopTaskId: decode(third)
|
|
389
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
|
+
};
|
|
390
403
|
if (length === 3 && section === "project" && third === "icon") return {
|
|
391
404
|
kind: "project-icon",
|
|
392
405
|
id: decode(rawId)
|
|
@@ -399,6 +412,14 @@ function itemRoute(rawId, parts) {
|
|
|
399
412
|
resultSeq: seq
|
|
400
413
|
} : null;
|
|
401
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
|
+
};
|
|
402
423
|
if (length <= 3 && section === "mcp") return {
|
|
403
424
|
kind: "mcp",
|
|
404
425
|
id: decode(rawId),
|
|
@@ -463,6 +484,303 @@ function permissionDecision(input) {
|
|
|
463
484
|
};
|
|
464
485
|
}
|
|
465
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);
|
|
524
|
+
}
|
|
525
|
+
function relayEntry(info, live) {
|
|
526
|
+
const items = info.checklist;
|
|
527
|
+
return {
|
|
528
|
+
id: info.id,
|
|
529
|
+
engine: info.engine,
|
|
530
|
+
status: info.status,
|
|
531
|
+
title: info.title,
|
|
532
|
+
project: info.project ? {
|
|
533
|
+
name: info.project.name,
|
|
534
|
+
root: info.project.root
|
|
535
|
+
} : void 0,
|
|
536
|
+
cwd: info.cwd,
|
|
537
|
+
profile: info.profile,
|
|
538
|
+
model: info.model,
|
|
539
|
+
permissionMode: info.permissionMode,
|
|
540
|
+
contextUsage: info.contextUsage,
|
|
541
|
+
checklist: items?.length ? {
|
|
542
|
+
done: items.filter((item) => item.status === "completed").length,
|
|
543
|
+
total: items.length
|
|
544
|
+
} : void 0,
|
|
545
|
+
numTurns: info.numTurns,
|
|
546
|
+
createdAt: info.createdAt,
|
|
547
|
+
lastActivityAt: info.lastActivityAt,
|
|
548
|
+
pendingPermissionCount: info.pendingPermissionCount,
|
|
549
|
+
live
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
function createPeerService(deps) {
|
|
553
|
+
const perMinute = deps.options?.perMinute ?? DEFAULT_PER_MINUTE;
|
|
554
|
+
const maxChars = deps.options?.maxMessageChars ?? PEER_MESSAGE_MAX_CHARS;
|
|
555
|
+
const maxHops = deps.options?.maxHops ?? DEFAULT_MAX_HOPS;
|
|
556
|
+
const sent = /* @__PURE__ */ new Map();
|
|
557
|
+
const inbound = /* @__PURE__ */ new Map();
|
|
558
|
+
const registry = () => {
|
|
559
|
+
const found = deps.refs.registry;
|
|
560
|
+
if (!found) throw new Error("peer messaging is not ready");
|
|
561
|
+
return found;
|
|
562
|
+
};
|
|
563
|
+
const allSessions = async () => {
|
|
564
|
+
const live = registry().list();
|
|
565
|
+
const dormant = await deps.refs.parking?.listInfo() ?? [];
|
|
566
|
+
return [...live, ...dormant].map((info) => deps.projects.withProject(info));
|
|
567
|
+
};
|
|
568
|
+
const infoOf = async (id) => {
|
|
569
|
+
const live = registry().get(id)?.info();
|
|
570
|
+
if (live) return deps.projects.withProject(live);
|
|
571
|
+
const record = await deps.refs.parking?.get(id);
|
|
572
|
+
return record ? deps.projects.withProject(record.info) : void 0;
|
|
573
|
+
};
|
|
574
|
+
const sender = async (from) => {
|
|
575
|
+
const info = await infoOf(from);
|
|
576
|
+
if (!info) throw new Error(`unknown sender session: ${from}`);
|
|
577
|
+
return info;
|
|
578
|
+
};
|
|
579
|
+
const recentOf = (sessionId, recent) => {
|
|
580
|
+
const limit = Math.min(Math.max(recent ?? PEER_RECENT_DEFAULT, 0), PEER_RECENT_MAX);
|
|
581
|
+
const runner = registry().get(sessionId);
|
|
582
|
+
const events = [];
|
|
583
|
+
if (runner && limit > 0) runner.subscribe((event) => events.push(event), 0, {
|
|
584
|
+
truncateResults: true,
|
|
585
|
+
imageRefs: true
|
|
586
|
+
})();
|
|
587
|
+
return {
|
|
588
|
+
runner,
|
|
589
|
+
lines: recentLines(events, limit)
|
|
590
|
+
};
|
|
591
|
+
};
|
|
592
|
+
const underRateLimit = (from, to) => {
|
|
593
|
+
const key = `${from}->${to}`;
|
|
594
|
+
const now = Date.now();
|
|
595
|
+
const recent = (sent.get(key) ?? []).filter((at) => now - at < WINDOW_MS);
|
|
596
|
+
if (recent.length >= perMinute) {
|
|
597
|
+
sent.set(key, recent);
|
|
598
|
+
return false;
|
|
599
|
+
}
|
|
600
|
+
recent.push(now);
|
|
601
|
+
sent.set(key, recent);
|
|
602
|
+
return true;
|
|
603
|
+
};
|
|
604
|
+
const list = async (from) => {
|
|
605
|
+
const me = await sender(from);
|
|
606
|
+
return (await allSessions()).filter((info) => visible(me, info)).sort((a, b) => (b.lastActivityAt ?? b.createdAt) - (a.lastActivityAt ?? a.createdAt)).map(peerSummary);
|
|
607
|
+
};
|
|
608
|
+
const peek = async (from, sessionId, options) => {
|
|
609
|
+
const me = await sender(from);
|
|
610
|
+
const info = await infoOf(sessionId);
|
|
611
|
+
if (!info || !visible(me, info)) return;
|
|
612
|
+
const { runner, lines } = recentOf(sessionId, options?.recent);
|
|
613
|
+
return {
|
|
614
|
+
...peerSummary(info),
|
|
615
|
+
live: runner !== void 0,
|
|
616
|
+
checklist: info.checklist,
|
|
617
|
+
pendingApprovals: runner ? runner.pendingApprovals.map((request) => request.title ?? request.toolName) : [],
|
|
618
|
+
recent: lines
|
|
619
|
+
};
|
|
620
|
+
};
|
|
621
|
+
const send = async (from, sessionId, text, options) => {
|
|
622
|
+
const me = await sender(from);
|
|
623
|
+
if (sessionId === from) return {
|
|
624
|
+
delivered: false,
|
|
625
|
+
reason: "that is this session"
|
|
626
|
+
};
|
|
627
|
+
if (text.length > maxChars) return {
|
|
628
|
+
delivered: false,
|
|
629
|
+
reason: `message is ${text.length} characters; the limit is ${maxChars}. Write it to a file and send the path.`
|
|
630
|
+
};
|
|
631
|
+
const target = await infoOf(sessionId);
|
|
632
|
+
if (!target || !visible(me, target)) return {
|
|
633
|
+
delivered: false,
|
|
634
|
+
reason: `no such session: ${sessionId}`
|
|
635
|
+
};
|
|
636
|
+
const hops = [...options?.hops ?? inbound.get(from) ?? [], from];
|
|
637
|
+
return deliver(target, text, {
|
|
638
|
+
kind: "peer",
|
|
639
|
+
sessionId: from,
|
|
640
|
+
name: me.title,
|
|
641
|
+
engine: me.engine,
|
|
642
|
+
hops
|
|
643
|
+
}, from);
|
|
644
|
+
};
|
|
645
|
+
const deliver = async (target, text, origin, rateKey) => {
|
|
646
|
+
const sessionId = target.id;
|
|
647
|
+
if (target.status === "closed" || target.status === "failed") return {
|
|
648
|
+
delivered: false,
|
|
649
|
+
reason: `session ${sessionId} is ${target.status}`
|
|
650
|
+
};
|
|
651
|
+
const hops = origin.hops ?? [];
|
|
652
|
+
if (hops.length > maxHops) return {
|
|
653
|
+
delivered: false,
|
|
654
|
+
reason: `${hops.length} messages have passed between sessions without a person speaking; stop and ask your user before continuing.`
|
|
655
|
+
};
|
|
656
|
+
if (!underRateLimit(rateKey, sessionId)) return {
|
|
657
|
+
delivered: false,
|
|
658
|
+
reason: `rate limit: at most ${perMinute} messages a minute to one session. Batch what you have to say.`
|
|
659
|
+
};
|
|
660
|
+
const runner = await deps.refs.parking?.ensureLive(sessionId) ?? registry().get(sessionId);
|
|
661
|
+
if (!runner) return {
|
|
662
|
+
delivered: false,
|
|
663
|
+
reason: `session ${sessionId} could not be woken`
|
|
664
|
+
};
|
|
665
|
+
const before = runner.info().status;
|
|
666
|
+
try {
|
|
667
|
+
runner.sendMessage(text, void 0, { origin });
|
|
668
|
+
} catch (error) {
|
|
669
|
+
return {
|
|
670
|
+
delivered: false,
|
|
671
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
inbound.set(sessionId, hops);
|
|
675
|
+
return {
|
|
676
|
+
delivered: true,
|
|
677
|
+
sessionId,
|
|
678
|
+
name: target.title,
|
|
679
|
+
queued: before === "running" || before === "awaiting_approval"
|
|
680
|
+
};
|
|
681
|
+
};
|
|
682
|
+
const exposedInfo = async (sessionId, exposed) => {
|
|
683
|
+
const info = await infoOf(sessionId);
|
|
684
|
+
return info && info.status !== "closed" && scopeMatches(exposed, info.scope) ? info : void 0;
|
|
685
|
+
};
|
|
686
|
+
const relayEntries = async (exposed) => {
|
|
687
|
+
const live = new Set(registry().list().map((info) => info.id));
|
|
688
|
+
return (await allSessions()).filter((info) => info.status !== "closed" && scopeMatches(exposed, info.scope)).map((info) => relayEntry(info, live.has(info.id)));
|
|
689
|
+
};
|
|
690
|
+
const relayPeek = async (sessionId, recent, exposed) => {
|
|
691
|
+
const info = await exposedInfo(sessionId, exposed);
|
|
692
|
+
if (!info) return;
|
|
693
|
+
const { runner, lines } = recentOf(sessionId, recent);
|
|
694
|
+
return {
|
|
695
|
+
...relayEntry(info, runner !== void 0),
|
|
696
|
+
checklistItems: info.checklist,
|
|
697
|
+
pendingApprovals: runner ? runner.pendingApprovals.map((request) => request.title ?? request.toolName) : [],
|
|
698
|
+
recent: lines
|
|
699
|
+
};
|
|
700
|
+
};
|
|
701
|
+
const relaySend = async (origin, sessionId, text, exposed) => {
|
|
702
|
+
if (text.length === 0 || text.length > maxChars) return {
|
|
703
|
+
delivered: false,
|
|
704
|
+
reason: `message is ${text.length} characters; the limit is ${maxChars}. Write it to a file and send the path.`
|
|
705
|
+
};
|
|
706
|
+
const target = await exposedInfo(sessionId, exposed);
|
|
707
|
+
if (!target) return {
|
|
708
|
+
delivered: false,
|
|
709
|
+
reason: `no such session: ${sessionId}`
|
|
710
|
+
};
|
|
711
|
+
const from = `${origin.gateway}:${origin.sessionId}`;
|
|
712
|
+
return deliver(target, text, {
|
|
713
|
+
kind: "peer",
|
|
714
|
+
sessionId: from,
|
|
715
|
+
hostId: origin.gateway,
|
|
716
|
+
name: origin.name,
|
|
717
|
+
engine: origin.engine,
|
|
718
|
+
hops: origin.hops
|
|
719
|
+
}, from);
|
|
720
|
+
};
|
|
721
|
+
const watch = (runner) => runner.subscribe((event) => {
|
|
722
|
+
if (event.type === "user_message" && !event.origin && !event.synthetic && event.parentToolUseId == null && !event.replay) inbound.delete(runner.id);
|
|
723
|
+
}, runner.info().lastSeq);
|
|
724
|
+
return {
|
|
725
|
+
list,
|
|
726
|
+
peek,
|
|
727
|
+
send,
|
|
728
|
+
watch,
|
|
729
|
+
relaySender: sender,
|
|
730
|
+
relayChain: (from) => [...inbound.get(from) ?? [], from],
|
|
731
|
+
relayEntries,
|
|
732
|
+
relayPeek,
|
|
733
|
+
relaySend
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
function resolvePeerMentions(rows, text) {
|
|
737
|
+
const tokens = scanPeerMentions(text);
|
|
738
|
+
if (tokens.length === 0) return [];
|
|
739
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
740
|
+
const add = (key, row) => {
|
|
741
|
+
const held = byKey.get(key);
|
|
742
|
+
if (held) held.push(row);
|
|
743
|
+
else byKey.set(key, [row]);
|
|
744
|
+
};
|
|
745
|
+
for (const row of rows) {
|
|
746
|
+
add(peerMentionKey(peerMentionSlug(row.title, row.id)), row);
|
|
747
|
+
add(peerMentionKey(row.id), row);
|
|
748
|
+
}
|
|
749
|
+
const resolved = [];
|
|
750
|
+
const seen = /* @__PURE__ */ new Set();
|
|
751
|
+
for (const token of tokens) {
|
|
752
|
+
const key = peerMentionKey(token.body);
|
|
753
|
+
const exact = byKey.get(key);
|
|
754
|
+
const matches = exact ?? (key.length >= 4 ? rows.filter((row) => row.id.startsWith(key)) : []);
|
|
755
|
+
const target = matches[0];
|
|
756
|
+
if (!target || matches.length > 1 && !exact) continue;
|
|
757
|
+
if (seen.has(target.id)) continue;
|
|
758
|
+
seen.add(target.id);
|
|
759
|
+
const others = matches.slice(1, 4).map((row) => row.id);
|
|
760
|
+
resolved.push({
|
|
761
|
+
typed: token.body,
|
|
762
|
+
id: target.id,
|
|
763
|
+
name: target.title,
|
|
764
|
+
engine: target.engine,
|
|
765
|
+
status: target.status,
|
|
766
|
+
cwd: target.cwd,
|
|
767
|
+
...others.length ? { ambiguousWith: others } : {}
|
|
768
|
+
});
|
|
769
|
+
if (resolved.length >= PEER_MENTION_MAX) break;
|
|
770
|
+
}
|
|
771
|
+
return resolved;
|
|
772
|
+
}
|
|
773
|
+
async function mentionsFor(directory, from, text) {
|
|
774
|
+
return scanPeerMentions(text).length === 0 ? [] : resolvePeerMentions(await directory.list(from), text);
|
|
775
|
+
}
|
|
776
|
+
//#endregion
|
|
777
|
+
//#region src/routes/sleep.ts
|
|
778
|
+
async function sleepRunner(runner) {
|
|
779
|
+
if (!runner.sleep) throw new HttpError(501, `the ${engineOf(runner.info())} engine has no process to put to sleep`);
|
|
780
|
+
const result = await runner.sleep();
|
|
781
|
+
if (!result.ok) throw new HttpError(409, `cannot sleep: ${result.reason}`);
|
|
782
|
+
}
|
|
783
|
+
//#endregion
|
|
466
784
|
//#region src/services/shell-directory.ts
|
|
467
785
|
function createShellDirectory(registry, deps = {}) {
|
|
468
786
|
const take = async (from, shellId, options) => {
|
|
@@ -1852,13 +2170,14 @@ function attachClient(ctx, ws, runner, req, access) {
|
|
|
1852
2170
|
unsubscribe();
|
|
1853
2171
|
detachBridge();
|
|
1854
2172
|
parking.onDetach(runner.id);
|
|
2173
|
+
ctx.engineSleep.onDetach(runner.id);
|
|
1855
2174
|
});
|
|
1856
2175
|
}
|
|
1857
2176
|
async function handleCommand(ctx, frame, runner, access, client) {
|
|
1858
2177
|
const { attachmentStore, bridge } = ctx;
|
|
1859
2178
|
switch (frame.type) {
|
|
1860
2179
|
case "user_message": {
|
|
1861
|
-
const mentions = ctx.peers && !isSlashCommand(frame.text) ? await ctx.peers
|
|
2180
|
+
const mentions = ctx.peers && !isSlashCommand(frame.text) ? await mentionsFor(ctx.peers, runner.id, frame.text).catch(() => []) : [];
|
|
1862
2181
|
const options = mentions.length > 0 ? { mentions } : void 0;
|
|
1863
2182
|
if (!frame.attachmentIds?.length) {
|
|
1864
2183
|
runner.sendMessage(frame.text, void 0, options);
|
|
@@ -1875,6 +2194,13 @@ async function handleCommand(ctx, frame, runner, access, client) {
|
|
|
1875
2194
|
case "interrupt":
|
|
1876
2195
|
await runner.interrupt();
|
|
1877
2196
|
return;
|
|
2197
|
+
case "sleep":
|
|
2198
|
+
await sleepRunner(runner);
|
|
2199
|
+
return;
|
|
2200
|
+
case "background_task":
|
|
2201
|
+
if (!runner.backgroundTask) throw new Error(`the ${engineOf(runner.info())} engine cannot move a task to the background`);
|
|
2202
|
+
await runner.backgroundTask(frame.toolUseId);
|
|
2203
|
+
return;
|
|
1878
2204
|
case "clear_context":
|
|
1879
2205
|
if (!runner.clearContext) throw new Error(`the ${engineOf(runner.info())} engine cannot clear a conversation`);
|
|
1880
2206
|
await runner.clearContext();
|
|
@@ -1891,6 +2217,11 @@ async function handleCommand(ctx, frame, runner, access, client) {
|
|
|
1891
2217
|
case "set_model":
|
|
1892
2218
|
await runner.setModel(frame.model);
|
|
1893
2219
|
return;
|
|
2220
|
+
case "set_effort":
|
|
2221
|
+
if (!runner.setEffort) throw new Error(`the ${engineOf(runner.info())} engine cannot change its reasoning effort`);
|
|
2222
|
+
if (frame.effort !== void 0 && typeof frame.effort !== "string") throw new Error("effort must be a string");
|
|
2223
|
+
await runner.setEffort(frame.effort);
|
|
2224
|
+
return;
|
|
1894
2225
|
case "tool_call_result":
|
|
1895
2226
|
bridge.resolve(runner.id, frame.executionId, {
|
|
1896
2227
|
output: frame.output,
|
|
@@ -2502,10 +2833,13 @@ const HOST_ONLY_KEY_TABLE = {
|
|
|
2502
2833
|
historyFn: "transient",
|
|
2503
2834
|
sessionInfoFn: "transient",
|
|
2504
2835
|
peers: "transient",
|
|
2836
|
+
contextReset: "transient",
|
|
2505
2837
|
shells: "transient",
|
|
2506
2838
|
shellAgentWrite: "transient",
|
|
2507
2839
|
createdByOperator: "durable",
|
|
2508
2840
|
fallbackTitle: "transient",
|
|
2841
|
+
startAsleep: "transient",
|
|
2842
|
+
effortDefaults: "transient",
|
|
2509
2843
|
connectFn: "transient",
|
|
2510
2844
|
codexHome: "durable",
|
|
2511
2845
|
codexPathOverride: "durable",
|
|
@@ -2520,6 +2854,7 @@ const HOST_ONLY_KEY_TABLE = {
|
|
|
2520
2854
|
toolTitles: "transient",
|
|
2521
2855
|
shouldApprove: "transient",
|
|
2522
2856
|
resolveModel: "transient",
|
|
2857
|
+
contextWindow: "transient",
|
|
2523
2858
|
reportMcpServers: "transient",
|
|
2524
2859
|
onClose: "transient",
|
|
2525
2860
|
restore: "transient"
|
|
@@ -3259,12 +3594,15 @@ const ITEM_HANDLERS = {
|
|
|
3259
3594
|
produced: ({ ctx, req, res, route }) => handleProducedFiles(ctx, req, res, route.id, route.producedFileId),
|
|
3260
3595
|
shells: ({ ctx, req, res, route, runner, auth }) => handleShells(ctx, req, res, route, runner ?? null, ctx.auth.isOperator(auth)),
|
|
3261
3596
|
"stop-task": handleStopTask,
|
|
3597
|
+
"background-task": handleBackgroundTask,
|
|
3262
3598
|
"project-icon": ({ ctx, req, res, info }) => handleProjectIcon(ctx.projects, req, res, info.cwd),
|
|
3263
3599
|
"tool-result": ({ req, res, route, runner, parked }) => {
|
|
3264
3600
|
const snapshot = parked && !isDormant(parked) ? parked.snapshot.events : void 0;
|
|
3265
3601
|
handleToolResult(req, res, runner?.eventAt?.bind(runner) ?? (snapshot && ((seq) => snapshot.find((event) => event.seq === seq))), route.resultSeq);
|
|
3266
3602
|
},
|
|
3267
|
-
permission: handlePermission
|
|
3603
|
+
permission: handlePermission,
|
|
3604
|
+
peers: handlePeers,
|
|
3605
|
+
sleep: handleSleep
|
|
3268
3606
|
};
|
|
3269
3607
|
async function handleSessions(ctx, req, res, route, auth) {
|
|
3270
3608
|
if (route.kind === "collection") {
|
|
@@ -3328,6 +3666,11 @@ async function handleSession({ ctx, req, res, route, runner, parked, info }) {
|
|
|
3328
3666
|
status: "closed"
|
|
3329
3667
|
}) });
|
|
3330
3668
|
}
|
|
3669
|
+
async function handlePeers({ ctx, req, res, route }) {
|
|
3670
|
+
requireMethod(req, "GET");
|
|
3671
|
+
if (!ctx.peers) fail(404, "peer messaging is off on this gateway");
|
|
3672
|
+
json(res, 200, { peers: await ctx.peers.list(route.id) });
|
|
3673
|
+
}
|
|
3331
3674
|
function handleFiles({ req, res, route, runner, parked }) {
|
|
3332
3675
|
requireMethod(req, "GET");
|
|
3333
3676
|
const snapshotFiles = parked && !isDormant(parked) ? parked.snapshot.vfs : void 0;
|
|
@@ -3355,6 +3698,18 @@ async function handleStopTask({ req, res, route, runner }) {
|
|
|
3355
3698
|
if (!await live.stopTask(route.stopTaskId)) fail(404, "no running task to stop");
|
|
3356
3699
|
json(res, 200, { ok: true });
|
|
3357
3700
|
}
|
|
3701
|
+
async function handleBackgroundTask({ req, res, route, runner }) {
|
|
3702
|
+
requireMethod(req, "POST");
|
|
3703
|
+
const live = requireLive(runner, "it has no running tasks");
|
|
3704
|
+
if (!live.backgroundTask) fail(501, `the ${engineOf(live.info())} engine cannot move a task to the background`);
|
|
3705
|
+
if (!await live.backgroundTask(route.backgroundTaskId)) fail(404, "no foreground task to move to the background");
|
|
3706
|
+
json(res, 200, { ok: true });
|
|
3707
|
+
}
|
|
3708
|
+
async function handleSleep({ ctx, req, res, runner, info }) {
|
|
3709
|
+
requireMethod(req, "POST");
|
|
3710
|
+
if (runner) await sleepRunner(runner);
|
|
3711
|
+
json(res, 200, { session: ctx.projects.withProject(runner?.info() ?? info) });
|
|
3712
|
+
}
|
|
3358
3713
|
async function handlePermission({ ctx, req, res, route, runner }) {
|
|
3359
3714
|
requireMethod(req, "POST");
|
|
3360
3715
|
const body = await readJsonBody(req, ctx.maxBodyBytes);
|
|
@@ -3567,37 +3922,6 @@ function safeName(name) {
|
|
|
3567
3922
|
return cleaned.length > 120 ? cleaned.slice(0, 120) : cleaned;
|
|
3568
3923
|
}
|
|
3569
3924
|
//#endregion
|
|
3570
|
-
//#region src/lib/scope.ts
|
|
3571
|
-
const MAX_SCOPE_KEYS = 16;
|
|
3572
|
-
const MAX_SCOPE_LEN = 200;
|
|
3573
|
-
function readScope(value) {
|
|
3574
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) return;
|
|
3575
|
-
const entries = Object.entries(value);
|
|
3576
|
-
if (entries.some(([, v]) => typeof v !== "string")) return;
|
|
3577
|
-
return Object.fromEntries(entries);
|
|
3578
|
-
}
|
|
3579
|
-
function checkScope(value) {
|
|
3580
|
-
if (value === void 0) return null;
|
|
3581
|
-
const scope = readScope(value);
|
|
3582
|
-
if (!scope) return "scope must be an object of string values";
|
|
3583
|
-
const entries = Object.entries(scope);
|
|
3584
|
-
if (entries.length > MAX_SCOPE_KEYS) return `scope may carry at most ${MAX_SCOPE_KEYS} keys`;
|
|
3585
|
-
for (const [key, val] of entries) {
|
|
3586
|
-
if (key.length === 0) return "scope keys must not be empty";
|
|
3587
|
-
if (key.length > MAX_SCOPE_LEN || val.length > MAX_SCOPE_LEN) return `scope keys and values must be at most ${MAX_SCOPE_LEN} characters`;
|
|
3588
|
-
}
|
|
3589
|
-
return null;
|
|
3590
|
-
}
|
|
3591
|
-
function sameScope(a, b) {
|
|
3592
|
-
const left = Object.entries(a ?? {}).sort(([x], [y]) => x < y ? -1 : 1);
|
|
3593
|
-
const right = Object.entries(b ?? {}).sort(([x], [y]) => x < y ? -1 : 1);
|
|
3594
|
-
return left.length === right.length && left.every(([key, value], i) => right[i][0] === key && right[i][1] === value);
|
|
3595
|
-
}
|
|
3596
|
-
function scopeMatches(principal, session) {
|
|
3597
|
-
if (!principal) return true;
|
|
3598
|
-
return Object.entries(principal).every(([key, value]) => session?.[key] === value);
|
|
3599
|
-
}
|
|
3600
|
-
//#endregion
|
|
3601
3925
|
//#region src/services/auth.ts
|
|
3602
3926
|
function createAuthService(deps) {
|
|
3603
3927
|
const { options, registry } = deps;
|
|
@@ -3707,6 +4031,188 @@ var AvailabilityTracker = class {
|
|
|
3707
4031
|
}
|
|
3708
4032
|
};
|
|
3709
4033
|
//#endregion
|
|
4034
|
+
//#region src/services/engine-sleep.ts
|
|
4035
|
+
var EngineSleepTimers = class {
|
|
4036
|
+
#options;
|
|
4037
|
+
#timers = /* @__PURE__ */ new Map();
|
|
4038
|
+
#runners = /* @__PURE__ */ new Map();
|
|
4039
|
+
#closed = false;
|
|
4040
|
+
constructor(options) {
|
|
4041
|
+
this.#options = options;
|
|
4042
|
+
}
|
|
4043
|
+
get enabled() {
|
|
4044
|
+
return this.#options.afterMs > 0 && !this.#closed;
|
|
4045
|
+
}
|
|
4046
|
+
watch(runner) {
|
|
4047
|
+
if (!this.enabled || !runner.sleep) return;
|
|
4048
|
+
this.#runners.set(runner.id, runner);
|
|
4049
|
+
const unsubscribe = runner.subscribe((event) => {
|
|
4050
|
+
if (event.type === "status_changed") this.#rearm(runner);
|
|
4051
|
+
}, runner.info().lastSeq);
|
|
4052
|
+
this.#rearm(runner);
|
|
4053
|
+
return () => {
|
|
4054
|
+
unsubscribe();
|
|
4055
|
+
this.#disarm(runner.id);
|
|
4056
|
+
if (this.#runners.get(runner.id) === runner) this.#runners.delete(runner.id);
|
|
4057
|
+
};
|
|
4058
|
+
}
|
|
4059
|
+
onDetach(sessionId) {
|
|
4060
|
+
const runner = this.#runners.get(sessionId);
|
|
4061
|
+
if (runner) this.#rearm(runner);
|
|
4062
|
+
}
|
|
4063
|
+
close() {
|
|
4064
|
+
this.#closed = true;
|
|
4065
|
+
for (const timer of this.#timers.values()) clearTimeout(timer);
|
|
4066
|
+
this.#timers.clear();
|
|
4067
|
+
this.#runners.clear();
|
|
4068
|
+
}
|
|
4069
|
+
#rearm(runner) {
|
|
4070
|
+
this.#disarm(runner.id);
|
|
4071
|
+
if (!this.enabled || !this.#sleepable(runner)) return;
|
|
4072
|
+
const timer = setTimeout(() => {
|
|
4073
|
+
this.#timers.delete(runner.id);
|
|
4074
|
+
if (!this.enabled || !this.#sleepable(runner)) return;
|
|
4075
|
+
runner.sleep?.().catch((error) => this.#options.onError?.(error, runner.id));
|
|
4076
|
+
}, this.#options.afterMs);
|
|
4077
|
+
timer.unref?.();
|
|
4078
|
+
this.#timers.set(runner.id, timer);
|
|
4079
|
+
}
|
|
4080
|
+
#sleepable(runner) {
|
|
4081
|
+
const info = runner.info();
|
|
4082
|
+
return info.status === "idle" && info.engineAsleep !== true && this.#options.attachedCount(runner.id) === 0;
|
|
4083
|
+
}
|
|
4084
|
+
#disarm(sessionId) {
|
|
4085
|
+
clearTimeout(this.#timers.get(sessionId));
|
|
4086
|
+
this.#timers.delete(sessionId);
|
|
4087
|
+
}
|
|
4088
|
+
};
|
|
4089
|
+
const HOUR_MS = 36e5;
|
|
4090
|
+
const RESET_TIMEOUT_MS = 6e4;
|
|
4091
|
+
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.";
|
|
4092
|
+
var ContextResetService = class {
|
|
4093
|
+
#options;
|
|
4094
|
+
#runners = /* @__PURE__ */ new Map();
|
|
4095
|
+
#pending = /* @__PURE__ */ new Map();
|
|
4096
|
+
#running = /* @__PURE__ */ new Set();
|
|
4097
|
+
#history = /* @__PURE__ */ new Map();
|
|
4098
|
+
#turnFailed = /* @__PURE__ */ new Set();
|
|
4099
|
+
#closed = false;
|
|
4100
|
+
constructor(options = {}) {
|
|
4101
|
+
this.#options = options;
|
|
4102
|
+
}
|
|
4103
|
+
get defaultEnabled() {
|
|
4104
|
+
return this.#options.default === true;
|
|
4105
|
+
}
|
|
4106
|
+
watch(runner) {
|
|
4107
|
+
if (this.#closed) return;
|
|
4108
|
+
this.#runners.set(runner.id, runner);
|
|
4109
|
+
const unsubscribe = runner.subscribe((event) => this.#observe(runner, event), runner.info().lastSeq);
|
|
4110
|
+
return () => {
|
|
4111
|
+
unsubscribe();
|
|
4112
|
+
if (this.#runners.get(runner.id) === runner) {
|
|
4113
|
+
this.#runners.delete(runner.id);
|
|
4114
|
+
this.#pending.delete(runner.id);
|
|
4115
|
+
}
|
|
4116
|
+
};
|
|
4117
|
+
}
|
|
4118
|
+
async request(sessionId, request) {
|
|
4119
|
+
const runner = this.#runners.get(sessionId);
|
|
4120
|
+
if (this.#closed || !runner || runner.info().agentContextReset !== true) throw new Error("context reset is not enabled for this session");
|
|
4121
|
+
if (!runner.clearContext) throw new Error("this engine cannot clear its conversation");
|
|
4122
|
+
if (this.#pending.has(sessionId) || this.#running.has(sessionId)) throw new Error("a reset is already scheduled for this session");
|
|
4123
|
+
const refused = this.#rateRefusal(sessionId);
|
|
4124
|
+
if (refused) throw new Error(refused);
|
|
4125
|
+
this.#pending.set(sessionId, {
|
|
4126
|
+
prompt: request.prompt,
|
|
4127
|
+
reason: request.reason
|
|
4128
|
+
});
|
|
4129
|
+
this.#turnFailed.delete(sessionId);
|
|
4130
|
+
return CONTEXT_RESET_SCHEDULED;
|
|
4131
|
+
}
|
|
4132
|
+
close() {
|
|
4133
|
+
this.#closed = true;
|
|
4134
|
+
this.#pending.clear();
|
|
4135
|
+
this.#runners.clear();
|
|
4136
|
+
}
|
|
4137
|
+
#rateRefusal(sessionId) {
|
|
4138
|
+
const now = this.#now();
|
|
4139
|
+
const minInterval = this.#options.minIntervalMs ?? 6e5;
|
|
4140
|
+
const maxPerHour = this.#options.maxPerHour ?? 3;
|
|
4141
|
+
const recent = (this.#history.get(sessionId) ?? []).filter((at) => now - at < HOUR_MS);
|
|
4142
|
+
const last = recent.at(-1);
|
|
4143
|
+
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`;
|
|
4144
|
+
if (maxPerHour <= 0 || recent.length >= maxPerHour) return `refused: this session already reset ${recent.length} time(s) in the last hour (limit ${maxPerHour}); keep working`;
|
|
4145
|
+
return null;
|
|
4146
|
+
}
|
|
4147
|
+
#observe(runner, event) {
|
|
4148
|
+
if (!this.#pending.has(runner.id)) return;
|
|
4149
|
+
if (event.type === "turn_result" && event.isError) {
|
|
4150
|
+
this.#turnFailed.add(runner.id);
|
|
4151
|
+
return;
|
|
4152
|
+
}
|
|
4153
|
+
if (event.type === "session_closed") {
|
|
4154
|
+
this.#pending.delete(runner.id);
|
|
4155
|
+
return;
|
|
4156
|
+
}
|
|
4157
|
+
if (event.type !== "status_changed" || event.status !== "idle") return;
|
|
4158
|
+
const request = this.#pending.get(runner.id);
|
|
4159
|
+
this.#pending.delete(runner.id);
|
|
4160
|
+
if (this.#turnFailed.delete(runner.id)) return;
|
|
4161
|
+
this.#run(runner, request);
|
|
4162
|
+
}
|
|
4163
|
+
async #run(runner, request) {
|
|
4164
|
+
this.#running.add(runner.id);
|
|
4165
|
+
try {
|
|
4166
|
+
const reset = nextReset(runner, this.#options.resetTimeoutMs ?? RESET_TIMEOUT_MS);
|
|
4167
|
+
try {
|
|
4168
|
+
await runner.clearContext({ agentReason: request.reason });
|
|
4169
|
+
await reset.done;
|
|
4170
|
+
} finally {
|
|
4171
|
+
reset.cancel();
|
|
4172
|
+
}
|
|
4173
|
+
const history = (this.#history.get(runner.id) ?? []).filter((at) => this.#now() - at < HOUR_MS);
|
|
4174
|
+
history.push(this.#now());
|
|
4175
|
+
this.#history.set(runner.id, history);
|
|
4176
|
+
runner.sendMessage(request.prompt);
|
|
4177
|
+
} catch (error) {
|
|
4178
|
+
this.#options.onError?.(error, runner.id);
|
|
4179
|
+
} finally {
|
|
4180
|
+
this.#running.delete(runner.id);
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
#now() {
|
|
4184
|
+
return this.#options.now?.() ?? Date.now();
|
|
4185
|
+
}
|
|
4186
|
+
};
|
|
4187
|
+
function nextReset(runner, timeoutMs) {
|
|
4188
|
+
let timer;
|
|
4189
|
+
let unsubscribe;
|
|
4190
|
+
const cancel = () => {
|
|
4191
|
+
clearTimeout(timer);
|
|
4192
|
+
unsubscribe?.();
|
|
4193
|
+
};
|
|
4194
|
+
return {
|
|
4195
|
+
done: new Promise((resolve, reject) => {
|
|
4196
|
+
timer = setTimeout(() => {
|
|
4197
|
+
cancel();
|
|
4198
|
+
reject(/* @__PURE__ */ new Error("the engine did not confirm the reset"));
|
|
4199
|
+
}, timeoutMs);
|
|
4200
|
+
timer.unref?.();
|
|
4201
|
+
unsubscribe = runner.subscribe((event) => {
|
|
4202
|
+
if (event.type === "conversation_reset") {
|
|
4203
|
+
cancel();
|
|
4204
|
+
resolve();
|
|
4205
|
+
}
|
|
4206
|
+
}, runner.info().lastSeq);
|
|
4207
|
+
}),
|
|
4208
|
+
cancel
|
|
4209
|
+
};
|
|
4210
|
+
}
|
|
4211
|
+
function minutes(ms) {
|
|
4212
|
+
const value = Math.max(1, Math.round(ms / 6e4));
|
|
4213
|
+
return `${value} minute${value === 1 ? "" : "s"}`;
|
|
4214
|
+
}
|
|
4215
|
+
//#endregion
|
|
3710
4216
|
//#region src/services/bridge.ts
|
|
3711
4217
|
var BridgeHub = class {
|
|
3712
4218
|
#sessions = /* @__PURE__ */ new Map();
|
|
@@ -4022,7 +4528,10 @@ var SessionParkManager = class {
|
|
|
4022
4528
|
}
|
|
4023
4529
|
async listInfo() {
|
|
4024
4530
|
await Promise.all(this.#storeOps.values());
|
|
4025
|
-
return (await this.#options.store.list()).filter((record) => this.#options.registry.get(record.id) === void 0).map((record) => record
|
|
4531
|
+
return (await this.#options.store.list()).filter((record) => this.#options.registry.get(record.id) === void 0).map((record) => isDormant(record) ? {
|
|
4532
|
+
...record.info,
|
|
4533
|
+
engineAsleep: true
|
|
4534
|
+
} : record.info);
|
|
4026
4535
|
}
|
|
4027
4536
|
async ensureLive(id) {
|
|
4028
4537
|
const live = this.#options.registry.get(id);
|
|
@@ -4272,7 +4781,7 @@ var SessionParkManager = class {
|
|
|
4272
4781
|
}
|
|
4273
4782
|
};
|
|
4274
4783
|
function idleRecordBase(runner, config) {
|
|
4275
|
-
const info = runner.info();
|
|
4784
|
+
const { engineAsleep: _engineAsleep, ...info } = runner.info();
|
|
4276
4785
|
return {
|
|
4277
4786
|
id: runner.id,
|
|
4278
4787
|
info: {
|
|
@@ -4337,6 +4846,8 @@ var ProfileService = class {
|
|
|
4337
4846
|
if (p.codexHome && !existsSync(p.codexHome)) return `profile '${p.name}' codexHome does not exist: ${p.codexHome}`;
|
|
4338
4847
|
} else if (!p.configDir || !existsSync(p.configDir)) return `profile '${p.name}' configDir does not exist: ${p.configDir}`;
|
|
4339
4848
|
if (disableBypassPermissions && p.defaults?.permissionMode === "bypassPermissions") return `profile '${p.name}' defaults to bypassPermissions but disableBypassPermissions is set`;
|
|
4849
|
+
const efforts = p.defaults?.efforts;
|
|
4850
|
+
if (efforts !== void 0 && !isEffortMap(efforts)) return `profile '${p.name}' defaults.efforts must map model names to effort levels`;
|
|
4340
4851
|
const fallbackMode = p.defaults?.permissionMode;
|
|
4341
4852
|
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(", ")})`;
|
|
4342
4853
|
return null;
|
|
@@ -4438,6 +4949,9 @@ var ProfileService = class {
|
|
|
4438
4949
|
};
|
|
4439
4950
|
}
|
|
4440
4951
|
};
|
|
4952
|
+
function isEffortMap(value) {
|
|
4953
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) && Object.entries(value).every(([model, effort]) => model !== "" && typeof effort === "string" && effort !== "");
|
|
4954
|
+
}
|
|
4441
4955
|
//#endregion
|
|
4442
4956
|
//#region src/services/profile-usage.ts
|
|
4443
4957
|
var ProfileUsageTracker = class {
|
|
@@ -4625,179 +5139,160 @@ function createFileSpendStore(path, onError) {
|
|
|
4625
5139
|
};
|
|
4626
5140
|
}
|
|
4627
5141
|
//#endregion
|
|
4628
|
-
//#region src/services/
|
|
4629
|
-
const
|
|
4630
|
-
const
|
|
4631
|
-
const
|
|
4632
|
-
function
|
|
4633
|
-
return
|
|
4634
|
-
}
|
|
4635
|
-
function
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
};
|
|
4662
|
-
const underRateLimit = (from, to) => {
|
|
4663
|
-
const key = `${from}->${to}`;
|
|
4664
|
-
const now = Date.now();
|
|
4665
|
-
const recent = (sent.get(key) ?? []).filter((at) => now - at < WINDOW_MS);
|
|
4666
|
-
if (recent.length >= perMinute) {
|
|
4667
|
-
sent.set(key, recent);
|
|
4668
|
-
return false;
|
|
4669
|
-
}
|
|
4670
|
-
recent.push(now);
|
|
4671
|
-
sent.set(key, recent);
|
|
4672
|
-
return true;
|
|
4673
|
-
};
|
|
4674
|
-
const list = async (from) => {
|
|
4675
|
-
const me = await sender(from);
|
|
4676
|
-
return (await allSessions()).filter((info) => visible(me, info)).sort((a, b) => (b.lastActivityAt ?? b.createdAt) - (a.lastActivityAt ?? a.createdAt)).map(peerSummary);
|
|
5142
|
+
//#region src/services/peer-relay.ts
|
|
5143
|
+
const CARRIED = Symbol.for("workerdeck.relay.carried");
|
|
5144
|
+
const slots = globalThis;
|
|
5145
|
+
const UNAVAILABLE = "Remote gateways are unavailable right now; only sessions on this gateway are listed.";
|
|
5146
|
+
function expandHome(path) {
|
|
5147
|
+
return path === "~" || path.startsWith("~/") ? `${homedir()}${path.slice(1)}` : path;
|
|
5148
|
+
}
|
|
5149
|
+
function identityOf(options) {
|
|
5150
|
+
return JSON.stringify([
|
|
5151
|
+
options.url,
|
|
5152
|
+
options.gateway,
|
|
5153
|
+
options.keyFile ?? "",
|
|
5154
|
+
options.key ? "inline" : "",
|
|
5155
|
+
options.caFile ?? "",
|
|
5156
|
+
options.expose?.allow ?? null
|
|
5157
|
+
]);
|
|
5158
|
+
}
|
|
5159
|
+
function remoteSummary(row) {
|
|
5160
|
+
return {
|
|
5161
|
+
id: `${row.gateway}:${row.id}`,
|
|
5162
|
+
gateway: row.gateway,
|
|
5163
|
+
engine: row.engine,
|
|
5164
|
+
status: row.status,
|
|
5165
|
+
title: row.title,
|
|
5166
|
+
project: row.project?.name,
|
|
5167
|
+
projectRoot: row.project?.root,
|
|
5168
|
+
cwd: row.cwd,
|
|
5169
|
+
profile: row.profile,
|
|
5170
|
+
model: row.model,
|
|
5171
|
+
contextUsage: row.contextUsage,
|
|
5172
|
+
lastActivityAt: row.lastActivityAt,
|
|
5173
|
+
pendingPermissionCount: row.pendingPermissionCount,
|
|
5174
|
+
allow: row.allow
|
|
4677
5175
|
};
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
live: runner !== void 0,
|
|
4692
|
-
checklist: info.checklist,
|
|
4693
|
-
pendingApprovals: runner ? runner.pendingApprovals.map((request) => request.title ?? request.toolName) : [],
|
|
4694
|
-
recent: recentLines(events, limit)
|
|
4695
|
-
};
|
|
5176
|
+
}
|
|
5177
|
+
function remotePeek(gateway, peek) {
|
|
5178
|
+
const { allow: _allow, ...summary } = remoteSummary({
|
|
5179
|
+
...peek,
|
|
5180
|
+
gateway,
|
|
5181
|
+
allow: []
|
|
5182
|
+
});
|
|
5183
|
+
return {
|
|
5184
|
+
...summary,
|
|
5185
|
+
live: peek.live,
|
|
5186
|
+
checklist: peek.checklistItems,
|
|
5187
|
+
pendingApprovals: peek.pendingApprovals,
|
|
5188
|
+
recent: peek.recent
|
|
4696
5189
|
};
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
};
|
|
4716
|
-
const hops = [...options?.hops ?? inbound.get(from) ?? [], from];
|
|
4717
|
-
if (hops.length > maxHops) return {
|
|
4718
|
-
delivered: false,
|
|
4719
|
-
reason: `${hops.length} messages have passed between sessions without a person speaking; stop and ask your user before continuing.`
|
|
4720
|
-
};
|
|
4721
|
-
if (!underRateLimit(from, sessionId)) return {
|
|
4722
|
-
delivered: false,
|
|
4723
|
-
reason: `rate limit: at most ${perMinute} messages a minute to one session. Batch what you have to say.`
|
|
4724
|
-
};
|
|
4725
|
-
const runner = await deps.refs.parking?.ensureLive(sessionId) ?? registry().get(sessionId);
|
|
4726
|
-
if (!runner) return {
|
|
4727
|
-
delivered: false,
|
|
4728
|
-
reason: `session ${sessionId} could not be woken`
|
|
4729
|
-
};
|
|
4730
|
-
const before = runner.info().status;
|
|
4731
|
-
const origin = {
|
|
4732
|
-
kind: "peer",
|
|
4733
|
-
sessionId: from,
|
|
4734
|
-
name: me.title,
|
|
4735
|
-
engine: me.engine,
|
|
4736
|
-
hops
|
|
4737
|
-
};
|
|
4738
|
-
try {
|
|
4739
|
-
runner.sendMessage(text, void 0, { origin });
|
|
4740
|
-
} catch (error) {
|
|
4741
|
-
return {
|
|
4742
|
-
delivered: false,
|
|
4743
|
-
reason: error instanceof Error ? error.message : String(error)
|
|
4744
|
-
};
|
|
4745
|
-
}
|
|
4746
|
-
inbound.set(sessionId, hops);
|
|
4747
|
-
return {
|
|
4748
|
-
delivered: true,
|
|
4749
|
-
sessionId,
|
|
4750
|
-
name: target.title,
|
|
4751
|
-
queued: before === "running" || before === "awaiting_approval"
|
|
4752
|
-
};
|
|
5190
|
+
}
|
|
5191
|
+
async function readRelayKey(options) {
|
|
5192
|
+
if (options.key) return options.key;
|
|
5193
|
+
if (!options.keyFile) throw new Error("relay: set relay.keyFile (or WORKERDECK_RELAY_KEY) to the key `workerdeck relay enroll` printed");
|
|
5194
|
+
const key = (await readFile(expandHome(options.keyFile), "utf8")).trim();
|
|
5195
|
+
if (!key) throw new Error(`relay: ${options.keyFile} is empty`);
|
|
5196
|
+
return key;
|
|
5197
|
+
}
|
|
5198
|
+
function createRelayLink(options, peers, log) {
|
|
5199
|
+
const identity = identityOf(options);
|
|
5200
|
+
const exposed = options.expose?.scope;
|
|
5201
|
+
let connection;
|
|
5202
|
+
let closed = false;
|
|
5203
|
+
let released = false;
|
|
5204
|
+
const host = {
|
|
5205
|
+
snapshot: () => peers.relayEntries(exposed),
|
|
5206
|
+
peek: (_origin, sessionId, recent) => peers.relayPeek(sessionId, recent, exposed),
|
|
5207
|
+
send: (origin, sessionId, text) => peers.relaySend(origin, sessionId, text, exposed)
|
|
4753
5208
|
};
|
|
4754
|
-
const
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
const others = matches.slice(1, 4).map((row) => row.id);
|
|
4779
|
-
resolved.push({
|
|
4780
|
-
typed: token.body,
|
|
4781
|
-
id: target.id,
|
|
4782
|
-
name: target.title,
|
|
4783
|
-
engine: target.engine,
|
|
4784
|
-
status: target.status,
|
|
4785
|
-
cwd: target.cwd,
|
|
4786
|
-
...others.length ? { ambiguousWith: others } : {}
|
|
4787
|
-
});
|
|
4788
|
-
if (resolved.length >= PEER_MENTION_MAX) break;
|
|
4789
|
-
}
|
|
4790
|
-
return resolved;
|
|
5209
|
+
const carried = slots[CARRIED];
|
|
5210
|
+
if (carried) {
|
|
5211
|
+
delete slots[CARRIED];
|
|
5212
|
+
if (carried.identity === identity && carried.connection.state() !== "stopped") {
|
|
5213
|
+
carried.connection.setHost(host);
|
|
5214
|
+
connection = carried.connection;
|
|
5215
|
+
} else carried.connection.close();
|
|
5216
|
+
}
|
|
5217
|
+
if (!connection) (async () => {
|
|
5218
|
+
const key = await readRelayKey(options);
|
|
5219
|
+
const ca = options.caFile ? await readFile(expandHome(options.caFile)) : void 0;
|
|
5220
|
+
if (!closed) connection = connectRelay({
|
|
5221
|
+
url: options.url,
|
|
5222
|
+
gateway: options.gateway,
|
|
5223
|
+
key,
|
|
5224
|
+
ca,
|
|
5225
|
+
allow: options.expose?.allow,
|
|
5226
|
+
log
|
|
5227
|
+
}, host);
|
|
5228
|
+
})().catch((error) => log(error instanceof Error ? error.message : String(error)));
|
|
5229
|
+
const remoteTarget = parseRelayPeerId;
|
|
5230
|
+
const reachesRemote = async (from) => {
|
|
5231
|
+
const me = await peers.relaySender(from);
|
|
5232
|
+
return me.scope === void 0 || Object.keys(me.scope).length === 0;
|
|
4791
5233
|
};
|
|
4792
|
-
const watch = (runner) => runner.subscribe((event) => {
|
|
4793
|
-
if (event.type === "user_message" && !event.origin && !event.synthetic && event.parentToolUseId == null && !event.replay) inbound.delete(runner.id);
|
|
4794
|
-
}, runner.info().lastSeq);
|
|
4795
5234
|
return {
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
5235
|
+
directory: {
|
|
5236
|
+
list: async (from) => {
|
|
5237
|
+
const local = await peers.list(from);
|
|
5238
|
+
if (!connection || connection.state() !== "online" || !await reachesRemote(from)) return local;
|
|
5239
|
+
try {
|
|
5240
|
+
return [...local, ...(await connection.list(from)).map(remoteSummary)];
|
|
5241
|
+
} catch {
|
|
5242
|
+
return local;
|
|
5243
|
+
}
|
|
5244
|
+
},
|
|
5245
|
+
notice: async (from) => {
|
|
5246
|
+
if (!await reachesRemote(from)) return;
|
|
5247
|
+
return connection?.state() === "online" ? void 0 : UNAVAILABLE;
|
|
5248
|
+
},
|
|
5249
|
+
peek: async (from, sessionId, peekOptions) => {
|
|
5250
|
+
const target = remoteTarget(sessionId);
|
|
5251
|
+
if (!target) return peers.peek(from, sessionId, peekOptions);
|
|
5252
|
+
if (!connection || !await reachesRemote(from)) return;
|
|
5253
|
+
const peek = await connection.peek(from, target, peekOptions?.recent);
|
|
5254
|
+
return peek ? remotePeek(target.gateway, peek) : void 0;
|
|
5255
|
+
},
|
|
5256
|
+
send: async (from, sessionId, text, sendOptions) => {
|
|
5257
|
+
const target = remoteTarget(sessionId);
|
|
5258
|
+
if (!target) return peers.send(from, sessionId, text, sendOptions);
|
|
5259
|
+
if (!await reachesRemote(from)) return {
|
|
5260
|
+
delivered: false,
|
|
5261
|
+
reason: `no such session: ${sessionId}`
|
|
5262
|
+
};
|
|
5263
|
+
if (!connection || connection.state() !== "online") return {
|
|
5264
|
+
delivered: false,
|
|
5265
|
+
reason: "remote gateways are unavailable right now; try again later"
|
|
5266
|
+
};
|
|
5267
|
+
try {
|
|
5268
|
+
const result = await connection.send(from, target, text, sendOptions?.hops ?? peers.relayChain(from));
|
|
5269
|
+
return result.delivered ? {
|
|
5270
|
+
...result,
|
|
5271
|
+
sessionId
|
|
5272
|
+
} : result;
|
|
5273
|
+
} catch (error) {
|
|
5274
|
+
return {
|
|
5275
|
+
delivered: false,
|
|
5276
|
+
reason: error instanceof Error ? error.message : String(error)
|
|
5277
|
+
};
|
|
5278
|
+
}
|
|
5279
|
+
}
|
|
5280
|
+
},
|
|
5281
|
+
nudge: () => connection?.nudge(),
|
|
5282
|
+
release: () => {
|
|
5283
|
+
if (connection && !closed) {
|
|
5284
|
+
released = true;
|
|
5285
|
+
slots[CARRIED] = {
|
|
5286
|
+
identity,
|
|
5287
|
+
connection
|
|
5288
|
+
};
|
|
5289
|
+
}
|
|
5290
|
+
},
|
|
5291
|
+
close: () => {
|
|
5292
|
+
closed = true;
|
|
5293
|
+
if (!released) connection?.close();
|
|
5294
|
+
connection = void 0;
|
|
5295
|
+
}
|
|
4801
5296
|
};
|
|
4802
5297
|
}
|
|
4803
5298
|
//#endregion
|
|
@@ -5022,8 +5517,16 @@ function createSessionFactory(deps) {
|
|
|
5022
5517
|
...deps.peers ? { peers: deps.peers } : {},
|
|
5023
5518
|
...deps.pricing ? { pricing: deps.pricing } : {}
|
|
5024
5519
|
};
|
|
5520
|
+
delete config.contextReset;
|
|
5025
5521
|
delete config.shells;
|
|
5026
5522
|
delete config.shellAgentWrite;
|
|
5523
|
+
delete config.effortDefaults;
|
|
5524
|
+
const efforts = {
|
|
5525
|
+
...deps.effortDefaults,
|
|
5526
|
+
...profile?.defaults?.efforts
|
|
5527
|
+
};
|
|
5528
|
+
if (Object.keys(efforts).length > 0) config.effortDefaults = efforts;
|
|
5529
|
+
if (deps.contextReset && (built.agentContextReset ?? profile?.defaults?.agentContextReset ?? deps.contextReset.defaultEnabled)) config.contextReset = deps.contextReset.directory;
|
|
5027
5530
|
if (deps.shells && capabilities.hostCwd === true) {
|
|
5028
5531
|
config.shells = deps.shells;
|
|
5029
5532
|
const write = deps.shellAgentWrite ?? "read-only";
|
|
@@ -5173,6 +5676,17 @@ function createWorkerServer(options = {}) {
|
|
|
5173
5676
|
onError: options.notifications?.onError ?? ((error, context) => diagnose(error, `notification-${context.op}`))
|
|
5174
5677
|
});
|
|
5175
5678
|
const producedFiles = new ProducedFileStore();
|
|
5679
|
+
const engineSleep = new EngineSleepTimers({
|
|
5680
|
+
afterMs: options.engineSleepAfterMs ?? 0,
|
|
5681
|
+
attachedCount: (sessionId) => bridge.attachedCount(sessionId),
|
|
5682
|
+
onError: (error) => diagnose(error, "engine-sleep")
|
|
5683
|
+
});
|
|
5684
|
+
const contextResets = options.agentContextReset === false ? void 0 : new ContextResetService({
|
|
5685
|
+
...options.agentContextReset,
|
|
5686
|
+
onError: (error) => diagnose(error, "context-reset")
|
|
5687
|
+
});
|
|
5688
|
+
let ownContextResets = contextResets;
|
|
5689
|
+
installContextResetDirectory(contextResets);
|
|
5176
5690
|
const registry = new SessionRegistry({ onRegister: (runner) => {
|
|
5177
5691
|
const detachers = [
|
|
5178
5692
|
notifier.watch(runner),
|
|
@@ -5181,7 +5695,9 @@ function createWorkerServer(options = {}) {
|
|
|
5181
5695
|
spendLedger.watch(runner),
|
|
5182
5696
|
shells?.watch(runner),
|
|
5183
5697
|
peers?.watch(runner),
|
|
5184
|
-
factory.watchAuthSource(runner)
|
|
5698
|
+
factory.watchAuthSource(runner),
|
|
5699
|
+
engineSleep.watch(runner),
|
|
5700
|
+
contextResets?.watch(runner)
|
|
5185
5701
|
];
|
|
5186
5702
|
const profile = runner.info().profile;
|
|
5187
5703
|
if (profile) detachers.push(runner.subscribe((event) => {
|
|
@@ -5222,10 +5738,12 @@ function createWorkerServer(options = {}) {
|
|
|
5222
5738
|
options: options.peers
|
|
5223
5739
|
});
|
|
5224
5740
|
const shellDirectory = shells ? createShellDirectory(shells, { runnerFor: (id) => registry.get(id) }) : void 0;
|
|
5225
|
-
|
|
5741
|
+
const relay = peers && options.relay ? createRelayLink(options.relay, peers, options.relay.log ?? ((message) => diagnose(new Error(message), "relay"))) : void 0;
|
|
5742
|
+
let ownPeers = relay?.directory ?? peers;
|
|
5226
5743
|
let ownShells = shellDirectory;
|
|
5227
|
-
if (
|
|
5744
|
+
if (ownPeers) installPeerDirectory(ownPeers);
|
|
5228
5745
|
installShellDirectory(shellDirectory);
|
|
5746
|
+
if (options.effortDefaults !== void 0 && !isEffortMap(options.effortDefaults)) throw new Error("createWorkerServer: `effortDefaults` must map model names to effort levels");
|
|
5229
5747
|
const factory = createSessionFactory({
|
|
5230
5748
|
adapterFor,
|
|
5231
5749
|
profiles,
|
|
@@ -5234,8 +5752,13 @@ function createWorkerServer(options = {}) {
|
|
|
5234
5752
|
allowedCwdRoots: options.allowedCwdRoots,
|
|
5235
5753
|
disableBypassPermissions: options.disableBypassPermissions,
|
|
5236
5754
|
approvalTimeoutMs: options.approvalTimeoutMs,
|
|
5755
|
+
effortDefaults: options.effortDefaults,
|
|
5237
5756
|
requireApiKey: options.requireApiKey,
|
|
5238
5757
|
peers: peers ? peerDirectoryHandle(() => ownPeers) : void 0,
|
|
5758
|
+
contextReset: contextResets ? {
|
|
5759
|
+
directory: contextResetDirectoryHandle(() => ownContextResets),
|
|
5760
|
+
defaultEnabled: contextResets.defaultEnabled
|
|
5761
|
+
} : void 0,
|
|
5239
5762
|
shells: shellDirectory ? shellDirectoryHandle(() => ownShells) : void 0,
|
|
5240
5763
|
shellAgentWrite: options.shell?.agentWrite,
|
|
5241
5764
|
pricing: pricing.pricing,
|
|
@@ -5291,7 +5814,8 @@ function createWorkerServer(options = {}) {
|
|
|
5291
5814
|
factory,
|
|
5292
5815
|
registry,
|
|
5293
5816
|
parking,
|
|
5294
|
-
|
|
5817
|
+
engineSleep,
|
|
5818
|
+
peers: peers ? peerDirectoryHandle(() => ownPeers) : void 0,
|
|
5295
5819
|
bridge,
|
|
5296
5820
|
projects,
|
|
5297
5821
|
queue,
|
|
@@ -5338,11 +5862,15 @@ function createWorkerServer(options = {}) {
|
|
|
5338
5862
|
parking,
|
|
5339
5863
|
shells,
|
|
5340
5864
|
queue,
|
|
5865
|
+
engineSleep,
|
|
5341
5866
|
closeQueueSockets: queueSockets.clear,
|
|
5342
5867
|
diagnose,
|
|
5343
5868
|
releaseDirectories: () => {
|
|
5869
|
+
relay?.close();
|
|
5344
5870
|
ownPeers = void 0;
|
|
5345
5871
|
ownShells = void 0;
|
|
5872
|
+
contextResets?.close();
|
|
5873
|
+
ownContextResets = void 0;
|
|
5346
5874
|
}
|
|
5347
5875
|
});
|
|
5348
5876
|
return {
|
|
@@ -5383,6 +5911,7 @@ function createWorkerServer(options = {}) {
|
|
|
5383
5911
|
parking.adopt(runner, config);
|
|
5384
5912
|
return true;
|
|
5385
5913
|
},
|
|
5914
|
+
releaseRelay: () => relay?.release(),
|
|
5386
5915
|
drain: lifecycle.drain,
|
|
5387
5916
|
close: lifecycle.close
|
|
5388
5917
|
};
|
|
@@ -5441,7 +5970,8 @@ function rebuildRunner(factory, record) {
|
|
|
5441
5970
|
resume: record.sdkSessionId
|
|
5442
5971
|
}),
|
|
5443
5972
|
epoch: (record.info.epoch ?? 0) + 1,
|
|
5444
|
-
fallbackTitle: record.info.title
|
|
5973
|
+
fallbackTitle: record.info.title,
|
|
5974
|
+
startAsleep: true
|
|
5445
5975
|
}, void 0, record.id);
|
|
5446
5976
|
}
|
|
5447
5977
|
//#endregion
|