@workerdeck/server 3.4.0 → 3.6.1
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 +31 -2
- package/build/index.mjs +785 -163
- package/build/index.mjs.map +1 -1
- package/package.json +7 -7
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, contextResetDirectoryHandle, createEngineSession, encodeShellKeys, getEngineAdapter, installContextResetDirectory, installPeerDirectory, installShellDirectory, isSlashCommand, normalizeMediaType, peerDirectoryHandle, peerSummary, recentLines, shellDirectoryHandle, shellOwnershipRefusal, shellSummary, shellTail, shellToolNeedsCard, ttyRedraws, ttyText, withoutGatewaySecrets } from "@workerdeck/core";
|
|
4
|
+
import { AVATAR_SEED_MAX, 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, avatarDirectoryHandle, checkClaudeAuth, clampShellTail, composeInstructions, contextResetDirectoryHandle, createEngineSession, encodeShellKeys, getEngineAdapter, installAvatarDirectory, 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, readStatusLabelInput, 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";
|
|
@@ -281,6 +281,661 @@ function reloadPlan(runner) {
|
|
|
281
281
|
return "carry";
|
|
282
282
|
}
|
|
283
283
|
//#endregion
|
|
284
|
+
//#region src/services/agents.ts
|
|
285
|
+
const NAMES = [
|
|
286
|
+
"Atlas",
|
|
287
|
+
"Juno",
|
|
288
|
+
"Pip",
|
|
289
|
+
"Rook",
|
|
290
|
+
"Marlow",
|
|
291
|
+
"Orbit",
|
|
292
|
+
"Fern",
|
|
293
|
+
"Quill",
|
|
294
|
+
"Sable",
|
|
295
|
+
"Wren",
|
|
296
|
+
"Ember",
|
|
297
|
+
"Moss",
|
|
298
|
+
"Nova",
|
|
299
|
+
"Otto",
|
|
300
|
+
"Pike",
|
|
301
|
+
"Rune",
|
|
302
|
+
"Sage",
|
|
303
|
+
"Tansy",
|
|
304
|
+
"Vale",
|
|
305
|
+
"Yarrow",
|
|
306
|
+
"Basil",
|
|
307
|
+
"Cedar",
|
|
308
|
+
"Dune",
|
|
309
|
+
"Flint",
|
|
310
|
+
"Hazel",
|
|
311
|
+
"Iris",
|
|
312
|
+
"Kestrel",
|
|
313
|
+
"Lark",
|
|
314
|
+
"Maple",
|
|
315
|
+
"Nettle",
|
|
316
|
+
"Onyx",
|
|
317
|
+
"Piper",
|
|
318
|
+
"Reed",
|
|
319
|
+
"Sorrel",
|
|
320
|
+
"Thistle",
|
|
321
|
+
"Umber",
|
|
322
|
+
"Willow",
|
|
323
|
+
"Zephyr"
|
|
324
|
+
];
|
|
325
|
+
const MAX_NAME = 64;
|
|
326
|
+
const MAX_BRIEF = 16e3;
|
|
327
|
+
const CONFIG_KEYS = [
|
|
328
|
+
"cwd",
|
|
329
|
+
"profile",
|
|
330
|
+
"model",
|
|
331
|
+
"reasoningEffort",
|
|
332
|
+
"permissionMode",
|
|
333
|
+
"brief",
|
|
334
|
+
"agentContextReset",
|
|
335
|
+
"sleepAfterMs"
|
|
336
|
+
];
|
|
337
|
+
var AgentService = class {
|
|
338
|
+
#store;
|
|
339
|
+
#basePath;
|
|
340
|
+
#avatars;
|
|
341
|
+
#sleepAfterMs;
|
|
342
|
+
#now;
|
|
343
|
+
#agents = /* @__PURE__ */ new Map();
|
|
344
|
+
constructor(options) {
|
|
345
|
+
this.#store = options.store;
|
|
346
|
+
this.#basePath = options.basePath;
|
|
347
|
+
this.#avatars = options.avatars ?? false;
|
|
348
|
+
this.#sleepAfterMs = options.sleepAfterMs ?? AGENT_SLEEP_AFTER_MS_DEFAULT;
|
|
349
|
+
this.#now = options.now ?? Date.now;
|
|
350
|
+
}
|
|
351
|
+
async hydrate() {
|
|
352
|
+
this.#agents = new Map((await this.#store.list()).map((agent) => [agent.id, agent]));
|
|
353
|
+
}
|
|
354
|
+
list() {
|
|
355
|
+
return [...this.#agents.values()].map((agent) => this.public(agent));
|
|
356
|
+
}
|
|
357
|
+
get(id) {
|
|
358
|
+
return this.#agents.get(id);
|
|
359
|
+
}
|
|
360
|
+
bySession(sessionId) {
|
|
361
|
+
for (const agent of this.#agents.values()) if (agent.sessionId === sessionId) return agent;
|
|
362
|
+
}
|
|
363
|
+
decorate(info) {
|
|
364
|
+
const agent = this.bySession(info.id);
|
|
365
|
+
if (!agent) return info;
|
|
366
|
+
const leadName = agent.lead === void 0 ? void 0 : this.#agents.get(agent.lead)?.name;
|
|
367
|
+
return {
|
|
368
|
+
...info,
|
|
369
|
+
agent: agentRef(this.public(agent), {
|
|
370
|
+
leadName,
|
|
371
|
+
leads: this.hasMembers(agent.id)
|
|
372
|
+
})
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
briefFor(sessionId) {
|
|
376
|
+
return (sessionId === void 0 ? void 0 : this.bySession(sessionId)?.config.brief?.trim()) || void 0;
|
|
377
|
+
}
|
|
378
|
+
sleepAfterFor(sessionId) {
|
|
379
|
+
const agent = this.bySession(sessionId);
|
|
380
|
+
return agent ? agent.config.sleepAfterMs ?? this.#sleepAfterMs : void 0;
|
|
381
|
+
}
|
|
382
|
+
public(agent) {
|
|
383
|
+
const { avatarSeed: _seed, avatarRecipe: _recipe, avatar: _avatar, ...info } = agent;
|
|
384
|
+
if (!this.#avatars) return info;
|
|
385
|
+
const version = agent.avatarSeed === agent.id ? "" : `?v=${createHash("sha1").update(agent.avatarSeed).digest("hex").slice(0, 10)}`;
|
|
386
|
+
return {
|
|
387
|
+
...info,
|
|
388
|
+
avatar: `${this.#basePath}/agents/${agent.id}/avatar.png${version}`
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
draft(input) {
|
|
392
|
+
const config = readConfig(input.config);
|
|
393
|
+
if ("error" in config) return config;
|
|
394
|
+
const name = input.name === void 0 ? this.#suggestName() : readName(input.name);
|
|
395
|
+
if (typeof name !== "string") return name;
|
|
396
|
+
const id = randomUUID();
|
|
397
|
+
const at = this.#now();
|
|
398
|
+
const agent = {
|
|
399
|
+
id,
|
|
400
|
+
name,
|
|
401
|
+
createdAt: at,
|
|
402
|
+
updatedAt: at,
|
|
403
|
+
avatarSeed: id,
|
|
404
|
+
pastSessions: [],
|
|
405
|
+
config
|
|
406
|
+
};
|
|
407
|
+
if (input.lead !== void 0 && input.lead !== null) {
|
|
408
|
+
const refused = typeof input.lead === "string" ? this.leadRefusal(agent, input.lead) : {
|
|
409
|
+
status: 400,
|
|
410
|
+
error: "lead must be an agent id"
|
|
411
|
+
};
|
|
412
|
+
if (refused) return refused;
|
|
413
|
+
agent.lead = input.lead;
|
|
414
|
+
}
|
|
415
|
+
return agent;
|
|
416
|
+
}
|
|
417
|
+
async save(agent) {
|
|
418
|
+
const stored = {
|
|
419
|
+
...agent,
|
|
420
|
+
updatedAt: this.#now()
|
|
421
|
+
};
|
|
422
|
+
await this.#store.save(stored);
|
|
423
|
+
this.#agents.set(stored.id, stored);
|
|
424
|
+
return stored;
|
|
425
|
+
}
|
|
426
|
+
async bind(agent, sessionId) {
|
|
427
|
+
const past = agent.sessionId !== void 0 && agent.sessionId !== sessionId ? [...agent.pastSessions, agent.sessionId] : agent.pastSessions;
|
|
428
|
+
return this.save({
|
|
429
|
+
...agent,
|
|
430
|
+
sessionId,
|
|
431
|
+
pastSessions: past
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
hasMembers(id) {
|
|
435
|
+
for (const agent of this.#agents.values()) if (agent.lead === id) return true;
|
|
436
|
+
return false;
|
|
437
|
+
}
|
|
438
|
+
members(id) {
|
|
439
|
+
return [...this.#agents.values()].filter((agent) => agent.lead === id).sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
|
|
440
|
+
}
|
|
441
|
+
leadRefusal(mover, leadId) {
|
|
442
|
+
if (leadId === mover.id) return {
|
|
443
|
+
status: 409,
|
|
444
|
+
error: "an agent cannot lead its own team"
|
|
445
|
+
};
|
|
446
|
+
const lead = this.#agents.get(leadId);
|
|
447
|
+
if (!lead) return {
|
|
448
|
+
status: 404,
|
|
449
|
+
error: `no such agent: ${leadId}`
|
|
450
|
+
};
|
|
451
|
+
if (lead.lead !== void 0) return {
|
|
452
|
+
status: 409,
|
|
453
|
+
error: `${lead.name} is a member of a team; teams are one level deep`
|
|
454
|
+
};
|
|
455
|
+
if (this.hasMembers(mover.id)) return {
|
|
456
|
+
status: 409,
|
|
457
|
+
error: `${mover.name} leads a team; teams are one level deep`
|
|
458
|
+
};
|
|
459
|
+
return null;
|
|
460
|
+
}
|
|
461
|
+
async update(id, patch) {
|
|
462
|
+
const agent = this.#agents.get(id);
|
|
463
|
+
if (!agent) return {
|
|
464
|
+
status: 404,
|
|
465
|
+
error: `no such agent: ${id}`
|
|
466
|
+
};
|
|
467
|
+
const next = { ...agent };
|
|
468
|
+
if (patch.name !== void 0) {
|
|
469
|
+
const name = readName(patch.name);
|
|
470
|
+
if (typeof name !== "string") return name;
|
|
471
|
+
next.name = name;
|
|
472
|
+
}
|
|
473
|
+
if (patch.config !== void 0) {
|
|
474
|
+
if (!isRecord$1(patch.config)) return {
|
|
475
|
+
status: 400,
|
|
476
|
+
error: "config must be an object"
|
|
477
|
+
};
|
|
478
|
+
const config = readConfig({
|
|
479
|
+
...agent.config,
|
|
480
|
+
...patch.config
|
|
481
|
+
});
|
|
482
|
+
if ("error" in config) return config;
|
|
483
|
+
next.config = config;
|
|
484
|
+
}
|
|
485
|
+
if (patch.lead === null) delete next.lead;
|
|
486
|
+
else if (patch.lead !== void 0) {
|
|
487
|
+
if (typeof patch.lead !== "string") return {
|
|
488
|
+
status: 400,
|
|
489
|
+
error: "lead must be an agent id or null"
|
|
490
|
+
};
|
|
491
|
+
const refused = this.leadRefusal(agent, patch.lead);
|
|
492
|
+
if (refused) return refused;
|
|
493
|
+
next.lead = patch.lead;
|
|
494
|
+
}
|
|
495
|
+
if (patch.order !== void 0) {
|
|
496
|
+
if (typeof patch.order !== "number" || !Number.isFinite(patch.order)) return {
|
|
497
|
+
status: 400,
|
|
498
|
+
error: "order must be a number"
|
|
499
|
+
};
|
|
500
|
+
next.order = patch.order;
|
|
501
|
+
}
|
|
502
|
+
return this.save(next);
|
|
503
|
+
}
|
|
504
|
+
async retire(id, members) {
|
|
505
|
+
const agent = this.#agents.get(id);
|
|
506
|
+
if (!agent) return {
|
|
507
|
+
status: 404,
|
|
508
|
+
error: `no such agent: ${id}`
|
|
509
|
+
};
|
|
510
|
+
const crew = this.members(id);
|
|
511
|
+
const retired = [agent, ...members === "retire" ? crew : []];
|
|
512
|
+
const released = [];
|
|
513
|
+
if (members === "release") for (const member of crew) {
|
|
514
|
+
const { lead: _lead, ...rest } = member;
|
|
515
|
+
released.push(await this.save(rest));
|
|
516
|
+
}
|
|
517
|
+
for (const gone of retired) {
|
|
518
|
+
await this.#store.delete(gone.id);
|
|
519
|
+
this.#agents.delete(gone.id);
|
|
520
|
+
}
|
|
521
|
+
return {
|
|
522
|
+
retired,
|
|
523
|
+
released
|
|
524
|
+
};
|
|
525
|
+
}
|
|
526
|
+
#suggestName() {
|
|
527
|
+
const taken = new Set([...this.#agents.values()].map((agent) => agent.name.toLowerCase()));
|
|
528
|
+
const free = NAMES.find((name) => !taken.has(name.toLowerCase()));
|
|
529
|
+
if (free) return free;
|
|
530
|
+
for (let n = 2;; n++) {
|
|
531
|
+
const name = `${NAMES[n % NAMES.length]} ${n}`;
|
|
532
|
+
if (!taken.has(name.toLowerCase())) return name;
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
};
|
|
536
|
+
function isAgentRefusal(value) {
|
|
537
|
+
return isRecord$1(value) && typeof value.status === "number" && typeof value.error === "string";
|
|
538
|
+
}
|
|
539
|
+
function readName(value) {
|
|
540
|
+
if (typeof value !== "string" || value.trim() === "") return {
|
|
541
|
+
status: 400,
|
|
542
|
+
error: "name must be a non-empty string"
|
|
543
|
+
};
|
|
544
|
+
const name = value.trim();
|
|
545
|
+
return name.length > MAX_NAME ? {
|
|
546
|
+
status: 400,
|
|
547
|
+
error: `name is longer than ${MAX_NAME} characters`
|
|
548
|
+
} : name;
|
|
549
|
+
}
|
|
550
|
+
function readConfig(value) {
|
|
551
|
+
if (value === void 0) return {};
|
|
552
|
+
if (!isRecord$1(value)) return {
|
|
553
|
+
status: 400,
|
|
554
|
+
error: "config must be an object"
|
|
555
|
+
};
|
|
556
|
+
const config = {};
|
|
557
|
+
for (const key of CONFIG_KEYS) if (value[key] !== void 0) config[key] = value[key];
|
|
558
|
+
for (const key of [
|
|
559
|
+
"cwd",
|
|
560
|
+
"profile",
|
|
561
|
+
"model",
|
|
562
|
+
"reasoningEffort",
|
|
563
|
+
"permissionMode",
|
|
564
|
+
"brief"
|
|
565
|
+
]) if (config[key] !== void 0 && typeof config[key] !== "string") return {
|
|
566
|
+
status: 400,
|
|
567
|
+
error: `config.${key} must be a string`
|
|
568
|
+
};
|
|
569
|
+
if (config.agentContextReset !== void 0 && typeof config.agentContextReset !== "boolean") return {
|
|
570
|
+
status: 400,
|
|
571
|
+
error: "config.agentContextReset must be a boolean"
|
|
572
|
+
};
|
|
573
|
+
const sleep = config.sleepAfterMs;
|
|
574
|
+
if (sleep !== void 0 && (typeof sleep !== "number" || !Number.isFinite(sleep) || sleep < 0)) return {
|
|
575
|
+
status: 400,
|
|
576
|
+
error: "config.sleepAfterMs must be a non-negative number"
|
|
577
|
+
};
|
|
578
|
+
if (typeof config.brief === "string" && config.brief.length > MAX_BRIEF) return {
|
|
579
|
+
status: 400,
|
|
580
|
+
error: `config.brief is longer than ${MAX_BRIEF} characters`
|
|
581
|
+
};
|
|
582
|
+
return config;
|
|
583
|
+
}
|
|
584
|
+
function isRecord$1(value) {
|
|
585
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
586
|
+
}
|
|
587
|
+
//#endregion
|
|
588
|
+
//#region src/lib/host-only-keys.ts
|
|
589
|
+
const HOST_ONLY_KEY_TABLE = {
|
|
590
|
+
epoch: "durable",
|
|
591
|
+
pricing: "transient",
|
|
592
|
+
queryFn: "transient",
|
|
593
|
+
env: "transient",
|
|
594
|
+
pathToClaudeCodeExecutable: "durable",
|
|
595
|
+
extraOptions: "transient",
|
|
596
|
+
instructions: "transient",
|
|
597
|
+
defaultApprovalTimeoutMs: "durable",
|
|
598
|
+
backfillHistory: "durable",
|
|
599
|
+
historyFn: "transient",
|
|
600
|
+
sessionInfoFn: "transient",
|
|
601
|
+
peers: "transient",
|
|
602
|
+
contextReset: "transient",
|
|
603
|
+
avatar: "transient",
|
|
604
|
+
shells: "transient",
|
|
605
|
+
shellAgentWrite: "transient",
|
|
606
|
+
createdByOperator: "durable",
|
|
607
|
+
fallbackTitle: "transient",
|
|
608
|
+
startAsleep: "transient",
|
|
609
|
+
effortDefaults: "transient",
|
|
610
|
+
statusLabel: "durable",
|
|
611
|
+
connectFn: "transient",
|
|
612
|
+
codexHome: "durable",
|
|
613
|
+
codexPathOverride: "durable",
|
|
614
|
+
languageModel: "transient",
|
|
615
|
+
tools: "transient",
|
|
616
|
+
maxSteps: "durable",
|
|
617
|
+
executor: "transient",
|
|
618
|
+
executableTools: "transient",
|
|
619
|
+
vfs: "transient",
|
|
620
|
+
executionLimits: "durable",
|
|
621
|
+
executionBackend: "transient",
|
|
622
|
+
toolTitles: "transient",
|
|
623
|
+
shouldApprove: "transient",
|
|
624
|
+
resolveModel: "transient",
|
|
625
|
+
contextWindow: "transient",
|
|
626
|
+
reportMcpServers: "transient",
|
|
627
|
+
onClose: "transient",
|
|
628
|
+
restore: "transient"
|
|
629
|
+
};
|
|
630
|
+
const HOST_ONLY_KEYS = new Set(Object.keys(HOST_ONLY_KEY_TABLE));
|
|
631
|
+
const DURABLE_HOST_KEYS = Object.entries(HOST_ONLY_KEY_TABLE).filter(([, lifetime]) => lifetime === "durable").map(([key]) => key);
|
|
632
|
+
//#endregion
|
|
633
|
+
//#region src/lib/permissions.ts
|
|
634
|
+
const OPERATOR_ONLY_MODES = /* @__PURE__ */ new Set(["bypassPermissions", "dontAsk"]);
|
|
635
|
+
function refusePermissionMode(mode, policy) {
|
|
636
|
+
if (mode === "bypassPermissions" && policy.disableBypass) return "bypassPermissions is disabled on this server (disableBypassPermissions)";
|
|
637
|
+
if (!policy.operator && mode !== void 0 && OPERATOR_ONLY_MODES.has(mode)) return `permission mode '${mode}' is reserved to operators`;
|
|
638
|
+
return null;
|
|
639
|
+
}
|
|
640
|
+
function permissionDecision(input) {
|
|
641
|
+
return input.behavior === "allow" ? {
|
|
642
|
+
behavior: "allow",
|
|
643
|
+
updatedInput: input.updatedInput
|
|
644
|
+
} : {
|
|
645
|
+
behavior: "deny",
|
|
646
|
+
message: input.message,
|
|
647
|
+
interrupt: input.interrupt
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
//#endregion
|
|
651
|
+
//#region src/routes/create-vet.ts
|
|
652
|
+
function vetCreateRequest(ctx, body, auth) {
|
|
653
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) return {
|
|
654
|
+
ok: false,
|
|
655
|
+
status: 400,
|
|
656
|
+
error: "request body must be a JSON object"
|
|
657
|
+
};
|
|
658
|
+
const hostOnly = Object.keys(body).filter((key) => HOST_ONLY_KEYS.has(key));
|
|
659
|
+
if (hostOnly.length > 0) return {
|
|
660
|
+
ok: false,
|
|
661
|
+
status: 400,
|
|
662
|
+
error: `host-only runner config is not accepted on a session request: ${hostOnly.join(", ")} (set it from the gateway's buildRunnerConfig hook)`
|
|
663
|
+
};
|
|
664
|
+
const req = pickCreateSessionRequest(body);
|
|
665
|
+
const { availability, factory } = ctx;
|
|
666
|
+
const early = factory.applyScope(req, auth) ?? refusal(403, factory.applyBypassPolicy(req));
|
|
667
|
+
if (early) return {
|
|
668
|
+
ok: false,
|
|
669
|
+
...early
|
|
670
|
+
};
|
|
671
|
+
const resolved = factory.resolveProfile(req.profile, auth.allowedProfiles);
|
|
672
|
+
if (!resolved.ok) return resolved;
|
|
673
|
+
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));
|
|
674
|
+
if (refused) return {
|
|
675
|
+
ok: false,
|
|
676
|
+
...refused
|
|
677
|
+
};
|
|
678
|
+
factory.stripInertFields(req, resolved.profile);
|
|
679
|
+
req.profile = resolved.profile?.name;
|
|
680
|
+
return {
|
|
681
|
+
ok: true,
|
|
682
|
+
request: req
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
function refusal(status, error) {
|
|
686
|
+
return error === null ? null : {
|
|
687
|
+
status,
|
|
688
|
+
error
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
function refuseHostAuthority(ctx, req, profile, auth) {
|
|
692
|
+
if (ctx.auth.isOperator(auth)) return null;
|
|
693
|
+
const engine = engineOf(profile);
|
|
694
|
+
const name = profile?.name ?? "default";
|
|
695
|
+
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`;
|
|
696
|
+
const mode = refusePermissionMode(req.permissionMode, { operator: false });
|
|
697
|
+
if (mode) return mode;
|
|
698
|
+
if (req.allowDangerouslySkipPermissions) return "allowDangerouslySkipPermissions is reserved to operators";
|
|
699
|
+
if (req.settingSources !== void 0 && req.settingSources.length > 0) return "settingSources is reserved to operators: it loads hooks and settings from the host";
|
|
700
|
+
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";
|
|
701
|
+
if (req.resume !== void 0 && !ownsSdkSession(ctx, auth, req.resume)) return "resume is limited to a live session this caller can see";
|
|
702
|
+
return null;
|
|
703
|
+
}
|
|
704
|
+
function ownsSdkSession(ctx, auth, sdkSessionId) {
|
|
705
|
+
return ctx.registry.list().some((info) => info.sdkSessionId === sdkSessionId && ctx.auth.canSee(auth, info));
|
|
706
|
+
}
|
|
707
|
+
function isRemoteMcpServer(server) {
|
|
708
|
+
const type = server?.type;
|
|
709
|
+
return type === "http" || type === "sse";
|
|
710
|
+
}
|
|
711
|
+
//#endregion
|
|
712
|
+
//#region src/routes/session-lookup.ts
|
|
713
|
+
async function sessionInfoOf(ctx, id) {
|
|
714
|
+
return ctx.registry.get(id)?.info() ?? (await ctx.parking.get(id))?.info;
|
|
715
|
+
}
|
|
716
|
+
async function resolveSession(ctx, id, auth) {
|
|
717
|
+
const runner = ctx.registry.get(id);
|
|
718
|
+
const parked = runner ? null : await ctx.parking.get(id);
|
|
719
|
+
const info = runner?.info() ?? parked?.info;
|
|
720
|
+
if (!info || !ctx.auth.canSee(auth, info)) fail(404, "session not found");
|
|
721
|
+
return {
|
|
722
|
+
runner,
|
|
723
|
+
parked,
|
|
724
|
+
info
|
|
725
|
+
};
|
|
726
|
+
}
|
|
727
|
+
function requireLive(runner, why) {
|
|
728
|
+
if (!runner) fail(409, `session is parked (${why})`);
|
|
729
|
+
return runner;
|
|
730
|
+
}
|
|
731
|
+
//#endregion
|
|
732
|
+
//#region src/routes/agents.ts
|
|
733
|
+
async function handleAgents(ctx, req, res, pathname, auth) {
|
|
734
|
+
const agents = ctx.agents;
|
|
735
|
+
const rest = pathname.slice((ctx.basePath + "/agents").length).replace(/^\//, "");
|
|
736
|
+
if (rest === "") {
|
|
737
|
+
requireMethod(req, "GET", "POST");
|
|
738
|
+
if (req.method === "GET") {
|
|
739
|
+
json(res, 200, { agents: agents.list() });
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
await createAgent(ctx, agents, req, res, auth);
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
const [id, action, ...extra] = rest.split("/").map(decodeURIComponent);
|
|
746
|
+
const agent = id === void 0 ? void 0 : agents.get(id);
|
|
747
|
+
if (!agent || extra.length > 0) fail(404, "agent not found");
|
|
748
|
+
if (action === "restart") {
|
|
749
|
+
requireMethod(req, "POST");
|
|
750
|
+
const body = await readJsonBody(req, ctx.maxBodyBytes) ?? {};
|
|
751
|
+
const previous = agent.sessionId === void 0 ? void 0 : ctx.registry.get(agent.sessionId);
|
|
752
|
+
const created = await startSession(ctx, agent, typeof body.prompt === "string" ? body.prompt : void 0, auth);
|
|
753
|
+
const status = previous?.info().status;
|
|
754
|
+
if (previous && status !== "closed" && status !== "failed") previous.close("server");
|
|
755
|
+
await respond(ctx, res, 200, await bindWithAvatar(ctx, agent, created), created);
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
if (action === "avatar") {
|
|
759
|
+
requireMethod(req, "POST");
|
|
760
|
+
await respond(ctx, res, 200, await rerollAvatar(ctx, agent, readSeed((await readJsonBody(req, ctx.maxBodyBytes) ?? {}).seed)));
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
if (action === "avatar-preview.png") {
|
|
764
|
+
requireMethod(req, "GET");
|
|
765
|
+
const seed = readSeed(new URL(req.url ?? "", "http://x").searchParams.get("seed") ?? void 0);
|
|
766
|
+
if (seed === void 0) fail(400, "seed is required");
|
|
767
|
+
const recipe = await rollFor(ctx, agent, seed);
|
|
768
|
+
await sendImage(req, res, await ctx.avatars.still(recipe));
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (action === "avatar.png" || action === "avatar-busy.png") {
|
|
772
|
+
requireMethod(req, "GET");
|
|
773
|
+
await sendAvatar(ctx, req, res, agent, action === "avatar-busy.png");
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
if (action !== void 0) fail(404, "agent not found");
|
|
777
|
+
requireMethod(req, "GET", "PATCH", "DELETE");
|
|
778
|
+
if (req.method === "GET") {
|
|
779
|
+
await respond(ctx, res, 200, agent);
|
|
780
|
+
return;
|
|
781
|
+
}
|
|
782
|
+
if (req.method === "PATCH") {
|
|
783
|
+
const patch = await readJsonBody(req, ctx.maxBodyBytes);
|
|
784
|
+
const updated = await agents.update(agent.id, isRecord(patch) ? patch : {});
|
|
785
|
+
if (isAgentRefusal(updated)) fail(updated.status, updated.error);
|
|
786
|
+
await respond(ctx, res, 200, updated);
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
const members = (await readJsonBody(req, ctx.maxBodyBytes) ?? {}).members === "retire" ? "retire" : "release";
|
|
790
|
+
const outcome = await agents.retire(agent.id, members);
|
|
791
|
+
if (isAgentRefusal(outcome)) fail(outcome.status, outcome.error);
|
|
792
|
+
for (const gone of outcome.retired) {
|
|
793
|
+
const runner = gone.sessionId === void 0 ? void 0 : ctx.registry.get(gone.sessionId);
|
|
794
|
+
const status = runner?.info().status;
|
|
795
|
+
if (runner && status !== "closed" && status !== "failed") runner.close("client");
|
|
796
|
+
}
|
|
797
|
+
json(res, 200, {
|
|
798
|
+
retired: outcome.retired.map((a) => a.id),
|
|
799
|
+
released: outcome.released.map((a) => a.id)
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
async function createAgent(ctx, agents, req, res, auth) {
|
|
803
|
+
const body = await readJsonBody(req, ctx.maxBodyBytes);
|
|
804
|
+
if (!isRecord(body)) fail(400, "request body must be a JSON object");
|
|
805
|
+
if (body.adopt !== void 0) {
|
|
806
|
+
await adoptSession(ctx, agents, res, body);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
const draft = agents.draft(body);
|
|
810
|
+
if (isAgentRefusal(draft)) fail(draft.status, draft.error);
|
|
811
|
+
const created = await startSession(ctx, draft, typeof body.prompt === "string" ? body.prompt : void 0, auth);
|
|
812
|
+
await respond(ctx, res, 201, await bindWithAvatar(ctx, draft, created), created);
|
|
813
|
+
}
|
|
814
|
+
async function adoptSession(ctx, agents, res, body) {
|
|
815
|
+
if (typeof body.adopt !== "string") fail(400, "adopt must be a session id");
|
|
816
|
+
const info = await sessionInfoOf(ctx, body.adopt);
|
|
817
|
+
if (!info) fail(404, "session not found");
|
|
818
|
+
if (agents.bySession(info.id)) fail(409, "that session already belongs to an agent");
|
|
819
|
+
const draft = agents.draft({
|
|
820
|
+
name: body.name ?? info.title,
|
|
821
|
+
config: {
|
|
822
|
+
...configOf(info),
|
|
823
|
+
...body.config
|
|
824
|
+
},
|
|
825
|
+
lead: body.lead
|
|
826
|
+
});
|
|
827
|
+
if (isAgentRefusal(draft)) fail(draft.status, draft.error);
|
|
828
|
+
await respond(ctx, res, 201, await bindWithAvatar(ctx, draft, info));
|
|
829
|
+
}
|
|
830
|
+
function readSeed(seed) {
|
|
831
|
+
if (seed === void 0 || seed === null || seed === "") return;
|
|
832
|
+
if (typeof seed !== "string" || seed.trim().length === 0 || seed.length > AVATAR_SEED_MAX) fail(400, `seed must be a string of at most ${AVATAR_SEED_MAX} characters`);
|
|
833
|
+
return seed.trim();
|
|
834
|
+
}
|
|
835
|
+
async function rollFor(ctx, agent, seed) {
|
|
836
|
+
if (!ctx.avatars) fail(404, "this gateway draws no avatars");
|
|
837
|
+
const session = agent.sessionId === void 0 ? void 0 : await sessionInfoOf(ctx, agent.sessionId);
|
|
838
|
+
const recipe = await rollAvatar(ctx, {
|
|
839
|
+
...agent,
|
|
840
|
+
avatarSeed: seed
|
|
841
|
+
}, session);
|
|
842
|
+
if (recipe === void 0) fail(503, "the avatar pack could not be loaded");
|
|
843
|
+
return recipe;
|
|
844
|
+
}
|
|
845
|
+
async function rerollAvatar(ctx, agent, seed) {
|
|
846
|
+
const next = seed ?? randomUUID();
|
|
847
|
+
const recipe = await rollFor(ctx, agent, next);
|
|
848
|
+
return ctx.agents.save({
|
|
849
|
+
...agent,
|
|
850
|
+
avatarSeed: next,
|
|
851
|
+
avatarRecipe: recipe
|
|
852
|
+
});
|
|
853
|
+
}
|
|
854
|
+
async function bindWithAvatar(ctx, agent, session) {
|
|
855
|
+
const recipe = agent.avatarRecipe ?? await rollAvatar(ctx, agent, session);
|
|
856
|
+
return ctx.agents.bind(recipe === void 0 ? agent : {
|
|
857
|
+
...agent,
|
|
858
|
+
avatarRecipe: recipe
|
|
859
|
+
}, session.id);
|
|
860
|
+
}
|
|
861
|
+
async function rollAvatar(ctx, agent, session) {
|
|
862
|
+
if (!ctx.avatars) return;
|
|
863
|
+
const project = session ? ctx.projects.withProject(session).project?.root ?? session.cwd : void 0;
|
|
864
|
+
try {
|
|
865
|
+
return await ctx.avatars.roll(agent.avatarSeed, session?.engine, project || void 0);
|
|
866
|
+
} catch {
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
async function sendAvatar(ctx, req, res, agent, busy) {
|
|
871
|
+
const avatars = ctx.avatars;
|
|
872
|
+
if (!avatars) fail(404, "this gateway draws no avatars");
|
|
873
|
+
let recipe = agent.avatarRecipe;
|
|
874
|
+
if (recipe === void 0) {
|
|
875
|
+
recipe = await rollAvatar(ctx, agent, agent.sessionId === void 0 ? void 0 : await sessionInfoOf(ctx, agent.sessionId));
|
|
876
|
+
if (recipe === void 0) fail(503, "the avatar pack could not be loaded");
|
|
877
|
+
await ctx.agents.save({
|
|
878
|
+
...agent,
|
|
879
|
+
avatarRecipe: recipe
|
|
880
|
+
});
|
|
881
|
+
}
|
|
882
|
+
const image = busy ? await avatars.busy(recipe) : await avatars.still(recipe);
|
|
883
|
+
if (!image) fail(404, "this avatar pack has no busy animation");
|
|
884
|
+
await sendImage(req, res, image);
|
|
885
|
+
}
|
|
886
|
+
async function sendImage(req, res, image) {
|
|
887
|
+
const headers = {
|
|
888
|
+
"content-type": "image/png",
|
|
889
|
+
etag: image.etag,
|
|
890
|
+
"cache-control": "private, no-cache"
|
|
891
|
+
};
|
|
892
|
+
if (image.durations) headers["x-frame-durations"] = image.durations.join(",");
|
|
893
|
+
if (req.headers["if-none-match"] === image.etag) {
|
|
894
|
+
res.writeHead(304, headers).end();
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
res.writeHead(200, headers).end(Buffer.from(image.png));
|
|
898
|
+
}
|
|
899
|
+
async function startSession(ctx, agent, prompt, auth) {
|
|
900
|
+
const { config } = agent;
|
|
901
|
+
const vetted = vetCreateRequest(ctx, stripUndefined({
|
|
902
|
+
cwd: config.cwd,
|
|
903
|
+
profile: config.profile,
|
|
904
|
+
model: config.model,
|
|
905
|
+
reasoningEffort: config.reasoningEffort,
|
|
906
|
+
permissionMode: config.permissionMode,
|
|
907
|
+
allowDangerouslySkipPermissions: config.permissionMode === "bypassPermissions" ? true : void 0,
|
|
908
|
+
agentContextReset: config.agentContextReset ?? true,
|
|
909
|
+
prompt
|
|
910
|
+
}), auth);
|
|
911
|
+
if (!vetted.ok) fail(vetted.status, vetted.error);
|
|
912
|
+
return (await ctx.factory.createRunner(ctx.factory.buildRunnerConfig(vetted.request, { operator: ctx.auth.isOperator(auth) }), {
|
|
913
|
+
brief: config.brief?.trim() || void 0,
|
|
914
|
+
agent: true
|
|
915
|
+
})).info();
|
|
916
|
+
}
|
|
917
|
+
async function respond(ctx, res, status, agent, session) {
|
|
918
|
+
const info = session ?? (agent.sessionId === void 0 ? void 0 : await sessionInfoOf(ctx, agent.sessionId));
|
|
919
|
+
const body = { agent: ctx.agents.public(agent) };
|
|
920
|
+
if (info) body.session = ctx.projects.withProject(info);
|
|
921
|
+
json(res, status, body);
|
|
922
|
+
}
|
|
923
|
+
function configOf(info) {
|
|
924
|
+
return stripUndefined({
|
|
925
|
+
cwd: info.cwd || void 0,
|
|
926
|
+
profile: info.profile,
|
|
927
|
+
model: info.model,
|
|
928
|
+
reasoningEffort: info.effort ?? void 0,
|
|
929
|
+
permissionMode: info.permissionMode
|
|
930
|
+
});
|
|
931
|
+
}
|
|
932
|
+
function stripUndefined(value) {
|
|
933
|
+
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
|
|
934
|
+
}
|
|
935
|
+
function isRecord(value) {
|
|
936
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
937
|
+
}
|
|
938
|
+
//#endregion
|
|
284
939
|
//#region src/routes/queue-ws.ts
|
|
285
940
|
function createQueueSocketHub(deps) {
|
|
286
941
|
const sockets = /* @__PURE__ */ new Set();
|
|
@@ -446,44 +1101,6 @@ function decode(segment) {
|
|
|
446
1101
|
}
|
|
447
1102
|
}
|
|
448
1103
|
//#endregion
|
|
449
|
-
//#region src/routes/session-lookup.ts
|
|
450
|
-
async function sessionInfoOf(ctx, id) {
|
|
451
|
-
return ctx.registry.get(id)?.info() ?? (await ctx.parking.get(id))?.info;
|
|
452
|
-
}
|
|
453
|
-
async function resolveSession(ctx, id, auth) {
|
|
454
|
-
const runner = ctx.registry.get(id);
|
|
455
|
-
const parked = runner ? null : await ctx.parking.get(id);
|
|
456
|
-
const info = runner?.info() ?? parked?.info;
|
|
457
|
-
if (!info || !ctx.auth.canSee(auth, info)) fail(404, "session not found");
|
|
458
|
-
return {
|
|
459
|
-
runner,
|
|
460
|
-
parked,
|
|
461
|
-
info
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
function requireLive(runner, why) {
|
|
465
|
-
if (!runner) fail(409, `session is parked (${why})`);
|
|
466
|
-
return runner;
|
|
467
|
-
}
|
|
468
|
-
//#endregion
|
|
469
|
-
//#region src/lib/permissions.ts
|
|
470
|
-
const OPERATOR_ONLY_MODES = /* @__PURE__ */ new Set(["bypassPermissions", "dontAsk"]);
|
|
471
|
-
function refusePermissionMode(mode, policy) {
|
|
472
|
-
if (mode === "bypassPermissions" && policy.disableBypass) return "bypassPermissions is disabled on this server (disableBypassPermissions)";
|
|
473
|
-
if (!policy.operator && mode !== void 0 && OPERATOR_ONLY_MODES.has(mode)) return `permission mode '${mode}' is reserved to operators`;
|
|
474
|
-
return null;
|
|
475
|
-
}
|
|
476
|
-
function permissionDecision(input) {
|
|
477
|
-
return input.behavior === "allow" ? {
|
|
478
|
-
behavior: "allow",
|
|
479
|
-
updatedInput: input.updatedInput
|
|
480
|
-
} : {
|
|
481
|
-
behavior: "deny",
|
|
482
|
-
message: input.message,
|
|
483
|
-
interrupt: input.interrupt
|
|
484
|
-
};
|
|
485
|
-
}
|
|
486
|
-
//#endregion
|
|
487
1104
|
//#region src/lib/scope.ts
|
|
488
1105
|
const MAX_SCOPE_KEYS = 16;
|
|
489
1106
|
const MAX_SCOPE_LEN = 200;
|
|
@@ -520,7 +1137,10 @@ const DEFAULT_PER_MINUTE = 10;
|
|
|
520
1137
|
const DEFAULT_MAX_HOPS = 12;
|
|
521
1138
|
const WINDOW_MS = 6e4;
|
|
522
1139
|
function visible(from, to) {
|
|
523
|
-
return to.id !== from.id && scopeMatches(from.scope, to.scope);
|
|
1140
|
+
return to.id !== from.id && scopeMatches(from.scope, to.scope) && teamReaches(from.agent, to.agent);
|
|
1141
|
+
}
|
|
1142
|
+
function relayable(info, exposed) {
|
|
1143
|
+
return info.status !== "closed" && info.agent?.lead === void 0 && scopeMatches(exposed, info.scope);
|
|
524
1144
|
}
|
|
525
1145
|
function relayEntry(info, live) {
|
|
526
1146
|
const items = info.checklist;
|
|
@@ -681,11 +1301,11 @@ function createPeerService(deps) {
|
|
|
681
1301
|
};
|
|
682
1302
|
const exposedInfo = async (sessionId, exposed) => {
|
|
683
1303
|
const info = await infoOf(sessionId);
|
|
684
|
-
return info && info
|
|
1304
|
+
return info && relayable(info, exposed) ? info : void 0;
|
|
685
1305
|
};
|
|
686
1306
|
const relayEntries = async (exposed) => {
|
|
687
1307
|
const live = new Set(registry().list().map((info) => info.id));
|
|
688
|
-
return (await allSessions()).filter((info) => info
|
|
1308
|
+
return (await allSessions()).filter((info) => relayable(info, exposed)).map((info) => relayEntry(info, live.has(info.id)));
|
|
689
1309
|
};
|
|
690
1310
|
const relayPeek = async (sessionId, recent, exposed) => {
|
|
691
1311
|
const info = await exposedInfo(sessionId, exposed);
|
|
@@ -743,8 +1363,9 @@ function resolvePeerMentions(rows, text) {
|
|
|
743
1363
|
else byKey.set(key, [row]);
|
|
744
1364
|
};
|
|
745
1365
|
for (const row of rows) {
|
|
746
|
-
|
|
747
|
-
add(peerMentionKey(row.
|
|
1366
|
+
const keys = /* @__PURE__ */ new Set([peerMentionKey(peerMentionSlug(row.title, row.id)), peerMentionKey(row.id)]);
|
|
1367
|
+
if (row.agent) keys.add(peerMentionKey(peerMentionSlug(row.agent, row.id)));
|
|
1368
|
+
for (const key of keys) add(key, row);
|
|
748
1369
|
}
|
|
749
1370
|
const resolved = [];
|
|
750
1371
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -760,7 +1381,7 @@ function resolvePeerMentions(rows, text) {
|
|
|
760
1381
|
resolved.push({
|
|
761
1382
|
typed: token.body,
|
|
762
1383
|
id: target.id,
|
|
763
|
-
name: target.title,
|
|
1384
|
+
name: target.agent ?? target.title,
|
|
764
1385
|
engine: target.engine,
|
|
765
1386
|
status: target.status,
|
|
766
1387
|
cwd: target.cwd,
|
|
@@ -2819,110 +3440,6 @@ async function handleWrite({ ctx, hostFiles, req, res }) {
|
|
|
2819
3440
|
});
|
|
2820
3441
|
}
|
|
2821
3442
|
//#endregion
|
|
2822
|
-
//#region src/lib/host-only-keys.ts
|
|
2823
|
-
const HOST_ONLY_KEY_TABLE = {
|
|
2824
|
-
epoch: "durable",
|
|
2825
|
-
pricing: "transient",
|
|
2826
|
-
queryFn: "transient",
|
|
2827
|
-
env: "transient",
|
|
2828
|
-
pathToClaudeCodeExecutable: "durable",
|
|
2829
|
-
extraOptions: "transient",
|
|
2830
|
-
instructions: "transient",
|
|
2831
|
-
defaultApprovalTimeoutMs: "durable",
|
|
2832
|
-
backfillHistory: "durable",
|
|
2833
|
-
historyFn: "transient",
|
|
2834
|
-
sessionInfoFn: "transient",
|
|
2835
|
-
peers: "transient",
|
|
2836
|
-
contextReset: "transient",
|
|
2837
|
-
shells: "transient",
|
|
2838
|
-
shellAgentWrite: "transient",
|
|
2839
|
-
createdByOperator: "durable",
|
|
2840
|
-
fallbackTitle: "transient",
|
|
2841
|
-
startAsleep: "transient",
|
|
2842
|
-
effortDefaults: "transient",
|
|
2843
|
-
connectFn: "transient",
|
|
2844
|
-
codexHome: "durable",
|
|
2845
|
-
codexPathOverride: "durable",
|
|
2846
|
-
languageModel: "transient",
|
|
2847
|
-
tools: "transient",
|
|
2848
|
-
maxSteps: "durable",
|
|
2849
|
-
executor: "transient",
|
|
2850
|
-
executableTools: "transient",
|
|
2851
|
-
vfs: "transient",
|
|
2852
|
-
executionLimits: "durable",
|
|
2853
|
-
executionBackend: "transient",
|
|
2854
|
-
toolTitles: "transient",
|
|
2855
|
-
shouldApprove: "transient",
|
|
2856
|
-
resolveModel: "transient",
|
|
2857
|
-
contextWindow: "transient",
|
|
2858
|
-
reportMcpServers: "transient",
|
|
2859
|
-
onClose: "transient",
|
|
2860
|
-
restore: "transient"
|
|
2861
|
-
};
|
|
2862
|
-
const HOST_ONLY_KEYS = new Set(Object.keys(HOST_ONLY_KEY_TABLE));
|
|
2863
|
-
const DURABLE_HOST_KEYS = Object.entries(HOST_ONLY_KEY_TABLE).filter(([, lifetime]) => lifetime === "durable").map(([key]) => key);
|
|
2864
|
-
//#endregion
|
|
2865
|
-
//#region src/routes/create-vet.ts
|
|
2866
|
-
function vetCreateRequest(ctx, body, auth) {
|
|
2867
|
-
if (typeof body !== "object" || body === null || Array.isArray(body)) return {
|
|
2868
|
-
ok: false,
|
|
2869
|
-
status: 400,
|
|
2870
|
-
error: "request body must be a JSON object"
|
|
2871
|
-
};
|
|
2872
|
-
const hostOnly = Object.keys(body).filter((key) => HOST_ONLY_KEYS.has(key));
|
|
2873
|
-
if (hostOnly.length > 0) return {
|
|
2874
|
-
ok: false,
|
|
2875
|
-
status: 400,
|
|
2876
|
-
error: `host-only runner config is not accepted on a session request: ${hostOnly.join(", ")} (set it from the gateway's buildRunnerConfig hook)`
|
|
2877
|
-
};
|
|
2878
|
-
const req = pickCreateSessionRequest(body);
|
|
2879
|
-
const { availability, factory } = ctx;
|
|
2880
|
-
const early = factory.applyScope(req, auth) ?? refusal(403, factory.applyBypassPolicy(req));
|
|
2881
|
-
if (early) return {
|
|
2882
|
-
ok: false,
|
|
2883
|
-
...early
|
|
2884
|
-
};
|
|
2885
|
-
const resolved = factory.resolveProfile(req.profile, auth.allowedProfiles);
|
|
2886
|
-
if (!resolved.ok) return resolved;
|
|
2887
|
-
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));
|
|
2888
|
-
if (refused) return {
|
|
2889
|
-
ok: false,
|
|
2890
|
-
...refused
|
|
2891
|
-
};
|
|
2892
|
-
factory.stripInertFields(req, resolved.profile);
|
|
2893
|
-
req.profile = resolved.profile?.name;
|
|
2894
|
-
return {
|
|
2895
|
-
ok: true,
|
|
2896
|
-
request: req
|
|
2897
|
-
};
|
|
2898
|
-
}
|
|
2899
|
-
function refusal(status, error) {
|
|
2900
|
-
return error === null ? null : {
|
|
2901
|
-
status,
|
|
2902
|
-
error
|
|
2903
|
-
};
|
|
2904
|
-
}
|
|
2905
|
-
function refuseHostAuthority(ctx, req, profile, auth) {
|
|
2906
|
-
if (ctx.auth.isOperator(auth)) return null;
|
|
2907
|
-
const engine = engineOf(profile);
|
|
2908
|
-
const name = profile?.name ?? "default";
|
|
2909
|
-
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`;
|
|
2910
|
-
const mode = refusePermissionMode(req.permissionMode, { operator: false });
|
|
2911
|
-
if (mode) return mode;
|
|
2912
|
-
if (req.allowDangerouslySkipPermissions) return "allowDangerouslySkipPermissions is reserved to operators";
|
|
2913
|
-
if (req.settingSources !== void 0 && req.settingSources.length > 0) return "settingSources is reserved to operators: it loads hooks and settings from the host";
|
|
2914
|
-
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";
|
|
2915
|
-
if (req.resume !== void 0 && !ownsSdkSession(ctx, auth, req.resume)) return "resume is limited to a live session this caller can see";
|
|
2916
|
-
return null;
|
|
2917
|
-
}
|
|
2918
|
-
function ownsSdkSession(ctx, auth, sdkSessionId) {
|
|
2919
|
-
return ctx.registry.list().some((info) => info.sdkSessionId === sdkSessionId && ctx.auth.canSee(auth, info));
|
|
2920
|
-
}
|
|
2921
|
-
function isRemoteMcpServer(server) {
|
|
2922
|
-
const type = server?.type;
|
|
2923
|
-
return type === "http" || type === "sse";
|
|
2924
|
-
}
|
|
2925
|
-
//#endregion
|
|
2926
3443
|
//#region src/routes/jobs.ts
|
|
2927
3444
|
async function handleJobs(ctx, req, res, pathname, auth) {
|
|
2928
3445
|
const { auth: authSvc, basePath, queue } = ctx;
|
|
@@ -3643,8 +4160,16 @@ async function handleSession({ ctx, req, res, route, runner, parked, info }) {
|
|
|
3643
4160
|
const body = await readJsonBody(req, ctx.maxBodyBytes);
|
|
3644
4161
|
if (body?.title !== void 0 && body.title !== null && typeof body.title !== "string") fail(400, "title must be a string or null");
|
|
3645
4162
|
const title = typeof body?.title === "string" ? body.title.trim() || void 0 : void 0;
|
|
4163
|
+
const label = body?.statusLabel === void 0 ? void 0 : readStatusLabelInput(body.statusLabel);
|
|
4164
|
+
if (typeof label === "string") fail(400, label);
|
|
3646
4165
|
if (!runner && parked) {
|
|
3647
|
-
|
|
4166
|
+
let renamed = parked.info;
|
|
4167
|
+
if (body?.title !== void 0) renamed = await parking.retitle(route.id, title);
|
|
4168
|
+
if (label !== void 0 && renamed) {
|
|
4169
|
+
const relabeled = await parking.relabel(route.id, label);
|
|
4170
|
+
if (relabeled === "parked") fail(409, "session is parked mid-turn; set its status once it wakes");
|
|
4171
|
+
renamed = relabeled;
|
|
4172
|
+
}
|
|
3648
4173
|
json(res, 200, { session: projects.withProject(requireRenamed(renamed)) });
|
|
3649
4174
|
return;
|
|
3650
4175
|
}
|
|
@@ -3653,6 +4178,10 @@ async function handleSession({ ctx, req, res, route, runner, parked, info }) {
|
|
|
3653
4178
|
live.setTitle(title);
|
|
3654
4179
|
parking.touch(live);
|
|
3655
4180
|
}
|
|
4181
|
+
if (label !== void 0) {
|
|
4182
|
+
if (!live.setStatusLabel) fail(501, "this engine does not carry a status label");
|
|
4183
|
+
live.setStatusLabel(label);
|
|
4184
|
+
}
|
|
3656
4185
|
json(res, 200, { session: projects.withProject(live.info()) });
|
|
3657
4186
|
return;
|
|
3658
4187
|
}
|
|
@@ -3757,6 +4286,11 @@ function httpRoutes(ctx) {
|
|
|
3757
4286
|
auth: "any",
|
|
3758
4287
|
handler: (req, res, pathname, auth) => handleProfiles(ctx, req, res, pathname, auth)
|
|
3759
4288
|
}),
|
|
4289
|
+
route({
|
|
4290
|
+
match: pathWhere(under("/agents")),
|
|
4291
|
+
auth: "operator",
|
|
4292
|
+
handler: (req, res, pathname, auth) => handleAgents(ctx, req, res, pathname, auth)
|
|
4293
|
+
}),
|
|
3760
4294
|
route({
|
|
3761
4295
|
match: pathWhere((pathname) => pathname.startsWith(base + "/executions/")),
|
|
3762
4296
|
auth: "any",
|
|
@@ -3824,6 +4358,36 @@ function pathWhere(test) {
|
|
|
3824
4358
|
return (pathname) => test(pathname) ? pathname : void 0;
|
|
3825
4359
|
}
|
|
3826
4360
|
//#endregion
|
|
4361
|
+
//#region src/services/agent-store.ts
|
|
4362
|
+
function createMemoryAgentStore(seed = []) {
|
|
4363
|
+
const agents = new Map(seed.map((a) => [a.id, a]));
|
|
4364
|
+
return {
|
|
4365
|
+
list: () => [...agents.values()],
|
|
4366
|
+
save: (agent) => void agents.set(agent.id, agent),
|
|
4367
|
+
delete: (id) => void agents.delete(id)
|
|
4368
|
+
};
|
|
4369
|
+
}
|
|
4370
|
+
function createFileAgentStore(path = join(process.cwd(), ".workerdeck", "agents.json")) {
|
|
4371
|
+
const read = () => {
|
|
4372
|
+
const parsed = readJsonOrSync(path, []);
|
|
4373
|
+
if (!Array.isArray(parsed)) return /* @__PURE__ */ new Map();
|
|
4374
|
+
return new Map(parsed.filter((a) => a && typeof a.id === "string" && typeof a.name === "string").map((a) => [a.id, a]));
|
|
4375
|
+
};
|
|
4376
|
+
const write = (agents) => writeJsonAtomicSync(path, [...agents.values()], { indent: 2 });
|
|
4377
|
+
return {
|
|
4378
|
+
list: () => [...read().values()],
|
|
4379
|
+
save: (agent) => {
|
|
4380
|
+
const agents = read();
|
|
4381
|
+
agents.set(agent.id, agent);
|
|
4382
|
+
write(agents);
|
|
4383
|
+
},
|
|
4384
|
+
delete: (id) => {
|
|
4385
|
+
const agents = read();
|
|
4386
|
+
if (agents.delete(id)) write(agents);
|
|
4387
|
+
}
|
|
4388
|
+
};
|
|
4389
|
+
}
|
|
4390
|
+
//#endregion
|
|
3827
4391
|
//#region src/services/attachments.ts
|
|
3828
4392
|
const DEFAULT_MAX_FILE_BYTES = 10485760;
|
|
3829
4393
|
const DEFAULT_MAX_SESSION_BYTES = 67108864;
|
|
@@ -4044,7 +4608,7 @@ var EngineSleepTimers = class {
|
|
|
4044
4608
|
return this.#options.afterMs > 0 && !this.#closed;
|
|
4045
4609
|
}
|
|
4046
4610
|
watch(runner) {
|
|
4047
|
-
if (!this.enabled
|
|
4611
|
+
if (this.#closed || !runner.sleep || !this.enabled && !this.#options.afterMsFor) return;
|
|
4048
4612
|
this.#runners.set(runner.id, runner);
|
|
4049
4613
|
const unsubscribe = runner.subscribe((event) => {
|
|
4050
4614
|
if (event.type === "status_changed") this.#rearm(runner);
|
|
@@ -4066,14 +4630,18 @@ var EngineSleepTimers = class {
|
|
|
4066
4630
|
this.#timers.clear();
|
|
4067
4631
|
this.#runners.clear();
|
|
4068
4632
|
}
|
|
4633
|
+
#afterMs(sessionId) {
|
|
4634
|
+
return this.#closed ? 0 : this.#options.afterMsFor?.(sessionId) ?? this.#options.afterMs;
|
|
4635
|
+
}
|
|
4069
4636
|
#rearm(runner) {
|
|
4070
4637
|
this.#disarm(runner.id);
|
|
4071
|
-
|
|
4638
|
+
const afterMs = this.#afterMs(runner.id);
|
|
4639
|
+
if (afterMs <= 0 || !this.#sleepable(runner)) return;
|
|
4072
4640
|
const timer = setTimeout(() => {
|
|
4073
4641
|
this.#timers.delete(runner.id);
|
|
4074
|
-
if (
|
|
4642
|
+
if (this.#afterMs(runner.id) <= 0 || !this.#sleepable(runner)) return;
|
|
4075
4643
|
runner.sleep?.().catch((error) => this.#options.onError?.(error, runner.id));
|
|
4076
|
-
},
|
|
4644
|
+
}, afterMs);
|
|
4077
4645
|
timer.unref?.();
|
|
4078
4646
|
this.#timers.set(runner.id, timer);
|
|
4079
4647
|
}
|
|
@@ -4437,6 +5005,7 @@ var SessionParkManager = class {
|
|
|
4437
5005
|
this.#persistLive(runner);
|
|
4438
5006
|
return;
|
|
4439
5007
|
case "system_init":
|
|
5008
|
+
case "status_label":
|
|
4440
5009
|
this.#rememberDormant(runner);
|
|
4441
5010
|
return;
|
|
4442
5011
|
case "conversation_reset":
|
|
@@ -4526,6 +5095,30 @@ var SessionParkManager = class {
|
|
|
4526
5095
|
return info;
|
|
4527
5096
|
});
|
|
4528
5097
|
}
|
|
5098
|
+
relabel(id, input) {
|
|
5099
|
+
return this.#queue(id, async () => {
|
|
5100
|
+
const record = await this.#options.store.get(id);
|
|
5101
|
+
if (!record || this.#options.registry.get(id)) return;
|
|
5102
|
+
if (!isDormant(record)) return "parked";
|
|
5103
|
+
const statusLabel = input ? {
|
|
5104
|
+
...input,
|
|
5105
|
+
setAt: Date.now()
|
|
5106
|
+
} : void 0;
|
|
5107
|
+
const info = {
|
|
5108
|
+
...record.info,
|
|
5109
|
+
statusLabel
|
|
5110
|
+
};
|
|
5111
|
+
await this.#options.store.save({
|
|
5112
|
+
...record,
|
|
5113
|
+
info,
|
|
5114
|
+
config: {
|
|
5115
|
+
...record.config,
|
|
5116
|
+
statusLabel
|
|
5117
|
+
}
|
|
5118
|
+
});
|
|
5119
|
+
return info;
|
|
5120
|
+
});
|
|
5121
|
+
}
|
|
4529
5122
|
async listInfo() {
|
|
4530
5123
|
await Promise.all(this.#storeOps.values());
|
|
4531
5124
|
return (await this.#options.store.list()).filter((record) => this.#options.registry.get(record.id) === void 0).map((record) => isDormant(record) ? {
|
|
@@ -4791,7 +5384,8 @@ function idleRecordBase(runner, config) {
|
|
|
4791
5384
|
profile: info.profile,
|
|
4792
5385
|
config: {
|
|
4793
5386
|
...config,
|
|
4794
|
-
meta: info.meta
|
|
5387
|
+
meta: info.meta,
|
|
5388
|
+
statusLabel: info.statusLabel
|
|
4795
5389
|
},
|
|
4796
5390
|
cost: runner.costState?.()
|
|
4797
5391
|
};
|
|
@@ -5229,7 +5823,7 @@ function createRelayLink(options, peers, log) {
|
|
|
5229
5823
|
const remoteTarget = parseRelayPeerId;
|
|
5230
5824
|
const reachesRemote = async (from) => {
|
|
5231
5825
|
const me = await peers.relaySender(from);
|
|
5232
|
-
return me.scope === void 0 || Object.keys(me.scope).length === 0;
|
|
5826
|
+
return (me.scope === void 0 || Object.keys(me.scope).length === 0) && me.agent?.lead === void 0;
|
|
5233
5827
|
};
|
|
5234
5828
|
return {
|
|
5235
5829
|
directory: {
|
|
@@ -5507,7 +6101,7 @@ function createSessionFactory(deps) {
|
|
|
5507
6101
|
return adapterFor(profile.engine).sessionEnv?.(profile, process.env) ?? process.env;
|
|
5508
6102
|
}
|
|
5509
6103
|
};
|
|
5510
|
-
const buildRunner = async (built, restore, id) => {
|
|
6104
|
+
const buildRunner = async (built, restore, id, options) => {
|
|
5511
6105
|
const name = built.profile;
|
|
5512
6106
|
const profile = name !== void 0 ? profiles.get(name) : void 0;
|
|
5513
6107
|
if (name !== void 0 && !profile) throw new Error(`unknown profile: ${name}`);
|
|
@@ -5518,6 +6112,7 @@ function createSessionFactory(deps) {
|
|
|
5518
6112
|
...deps.pricing ? { pricing: deps.pricing } : {}
|
|
5519
6113
|
};
|
|
5520
6114
|
delete config.contextReset;
|
|
6115
|
+
delete config.avatar;
|
|
5521
6116
|
delete config.shells;
|
|
5522
6117
|
delete config.shellAgentWrite;
|
|
5523
6118
|
delete config.effortDefaults;
|
|
@@ -5532,6 +6127,9 @@ function createSessionFactory(deps) {
|
|
|
5532
6127
|
const write = deps.shellAgentWrite ?? "read-only";
|
|
5533
6128
|
if (write !== "read-only" && built.createdByOperator === true) config.shellAgentWrite = write;
|
|
5534
6129
|
}
|
|
6130
|
+
if (deps.avatar && (options?.agent === true || deps.avatar.isAgent(id ?? restore?.id))) config.avatar = deps.avatar.directory;
|
|
6131
|
+
const brief = options?.brief ?? deps.agentBrief?.(id ?? restore?.id);
|
|
6132
|
+
if (brief) config.instructions = composeInstructions(config.instructions, brief);
|
|
5535
6133
|
if (config.instructions !== void 0 && capabilities.systemInstructions === false) throw new Error(`the ${engineOf(profile)} engine cannot deliver system instructions`);
|
|
5536
6134
|
const runner = profile && isProviderProfile(profile) ? await deps.createEngineRunner({
|
|
5537
6135
|
config,
|
|
@@ -5549,8 +6147,8 @@ function createSessionFactory(deps) {
|
|
|
5549
6147
|
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()`);
|
|
5550
6148
|
return runner;
|
|
5551
6149
|
};
|
|
5552
|
-
const createRunner = async (config) => {
|
|
5553
|
-
const runner = registry.register(await buildRunner(config));
|
|
6150
|
+
const createRunner = async (config, options) => {
|
|
6151
|
+
const runner = registry.register(await buildRunner(config, void 0, void 0, options));
|
|
5554
6152
|
parking.remember(runner.id, config);
|
|
5555
6153
|
parking.watch(runner);
|
|
5556
6154
|
runner.start();
|
|
@@ -5669,7 +6267,13 @@ function createWorkerServer(options = {}) {
|
|
|
5669
6267
|
}
|
|
5670
6268
|
const generation = randomUUID();
|
|
5671
6269
|
const shells = options.shell?.enabled === true ? shellRegistryFor(options.shell, generation) : null;
|
|
5672
|
-
const
|
|
6270
|
+
const agents = new AgentService({
|
|
6271
|
+
store: options.agentStore ?? createMemoryAgentStore(),
|
|
6272
|
+
basePath,
|
|
6273
|
+
avatars: options.avatars !== void 0,
|
|
6274
|
+
sleepAfterMs: options.agentSleepAfterMs
|
|
6275
|
+
});
|
|
6276
|
+
const projects = new ProjectInfoService({ decorate: (info) => agents.decorate(shells ? shells.decorate(info) : info) });
|
|
5673
6277
|
const notifier = new SessionNotifier({
|
|
5674
6278
|
...options.notifications,
|
|
5675
6279
|
decorateInfo: (info) => projects.withProject(info),
|
|
@@ -5678,6 +6282,7 @@ function createWorkerServer(options = {}) {
|
|
|
5678
6282
|
const producedFiles = new ProducedFileStore();
|
|
5679
6283
|
const engineSleep = new EngineSleepTimers({
|
|
5680
6284
|
afterMs: options.engineSleepAfterMs ?? 0,
|
|
6285
|
+
afterMsFor: (sessionId) => agents.sleepAfterFor(sessionId),
|
|
5681
6286
|
attachedCount: (sessionId) => bridge.attachedCount(sessionId),
|
|
5682
6287
|
onError: (error) => diagnose(error, "engine-sleep")
|
|
5683
6288
|
});
|
|
@@ -5687,6 +6292,14 @@ function createWorkerServer(options = {}) {
|
|
|
5687
6292
|
});
|
|
5688
6293
|
let ownContextResets = contextResets;
|
|
5689
6294
|
installContextResetDirectory(contextResets);
|
|
6295
|
+
const avatarChanges = options.avatars ? { change: async (sessionId, seed) => {
|
|
6296
|
+
const agent = agents.bySession(sessionId);
|
|
6297
|
+
if (!agent) throw new Error("only an agent has an avatar, and this session is not one");
|
|
6298
|
+
await rerollAvatar(ctx, agent, seed);
|
|
6299
|
+
return seed ? `Your avatar is changed (seed "${seed}"); the human sees it beside your name now.` : "Your avatar is changed; the human sees it beside your name now.";
|
|
6300
|
+
} } : void 0;
|
|
6301
|
+
let ownAvatarChanges = avatarChanges;
|
|
6302
|
+
installAvatarDirectory(avatarChanges);
|
|
5690
6303
|
const registry = new SessionRegistry({ onRegister: (runner) => {
|
|
5691
6304
|
const detachers = [
|
|
5692
6305
|
notifier.watch(runner),
|
|
@@ -5764,7 +6377,12 @@ function createWorkerServer(options = {}) {
|
|
|
5764
6377
|
pricing: pricing.pricing,
|
|
5765
6378
|
registry,
|
|
5766
6379
|
parking,
|
|
5767
|
-
bridge
|
|
6380
|
+
bridge,
|
|
6381
|
+
agentBrief: (sessionId) => agents.briefFor(sessionId),
|
|
6382
|
+
avatar: avatarChanges ? {
|
|
6383
|
+
directory: avatarDirectoryHandle(() => ownAvatarChanges),
|
|
6384
|
+
isAgent: (sessionId) => sessionId !== void 0 && agents.bySession(sessionId) !== void 0
|
|
6385
|
+
} : void 0
|
|
5768
6386
|
});
|
|
5769
6387
|
const availability = new AvailabilityTracker({
|
|
5770
6388
|
checkCredentials: options.checkCredentials,
|
|
@@ -5812,6 +6430,8 @@ function createWorkerServer(options = {}) {
|
|
|
5812
6430
|
availability,
|
|
5813
6431
|
auth,
|
|
5814
6432
|
factory,
|
|
6433
|
+
agents,
|
|
6434
|
+
avatars: options.avatars,
|
|
5815
6435
|
registry,
|
|
5816
6436
|
parking,
|
|
5817
6437
|
engineSleep,
|
|
@@ -5871,6 +6491,7 @@ function createWorkerServer(options = {}) {
|
|
|
5871
6491
|
ownShells = void 0;
|
|
5872
6492
|
contextResets?.close();
|
|
5873
6493
|
ownContextResets = void 0;
|
|
6494
|
+
ownAvatarChanges = void 0;
|
|
5874
6495
|
}
|
|
5875
6496
|
});
|
|
5876
6497
|
return {
|
|
@@ -5883,6 +6504,7 @@ function createWorkerServer(options = {}) {
|
|
|
5883
6504
|
listen: async (port, host) => {
|
|
5884
6505
|
await profiles.refreshStored();
|
|
5885
6506
|
await profiles.seedStore();
|
|
6507
|
+
await agents.hydrate();
|
|
5886
6508
|
await parking.hydrate();
|
|
5887
6509
|
await shells?.hydrate();
|
|
5888
6510
|
return new Promise((resolve, reject) => {
|
|
@@ -6078,6 +6700,6 @@ function createFileProfileStore(path = join(process.cwd(), ".workerdeck", "profi
|
|
|
6078
6700
|
};
|
|
6079
6701
|
}
|
|
6080
6702
|
//#endregion
|
|
6081
|
-
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 };
|
|
6703
|
+
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 };
|
|
6082
6704
|
|
|
6083
6705
|
//# sourceMappingURL=index.mjs.map
|