@agimon-ai/doompi-core 0.0.1-alpha.96 → 0.0.1-alpha.97
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/dist/cordisHost.cjs +1 -0
- package/dist/cordisHost.d.mts +2 -2
- package/dist/cordisHost.mjs +2 -2
- package/dist/headlessHub.cjs +29 -7
- package/dist/headlessHub.cjs.map +1 -1
- package/dist/headlessHub.d.mts +6 -3
- package/dist/headlessHub.d.mts.map +1 -1
- package/dist/headlessHub.mjs +29 -7
- package/dist/headlessHub.mjs.map +1 -1
- package/dist/headlessServer.cjs +6 -4
- package/dist/headlessServer.cjs.map +1 -1
- package/dist/headlessServer.d.mts.map +1 -1
- package/dist/headlessServer.mjs +2 -0
- package/dist/headlessServer.mjs.map +1 -1
- package/dist/headlessSessionHost.d.mts +2 -0
- package/dist/headlessSessionHost.d.mts.map +1 -1
- package/dist/hubChannel.cjs +4 -0
- package/dist/hubChannel.d.mts +2 -2
- package/dist/hubChannel.mjs +2 -1
- package/dist/packageApi.d.mts +2 -2
- package/dist/packageApiServer.cjs +12 -0
- package/dist/packageApiServer.cjs.map +1 -1
- package/dist/packageApiServer.d.mts +1 -0
- package/dist/packageApiServer.d.mts.map +1 -1
- package/dist/packageApiServer.mjs +12 -0
- package/dist/packageApiServer.mjs.map +1 -1
- package/dist/runtimeCordisHost.cjs +2 -0
- package/dist/runtimeCordisHost.cjs.map +1 -1
- package/dist/runtimeCordisHost.d.mts +6 -0
- package/dist/runtimeCordisHost.d.mts.map +1 -1
- package/dist/runtimeCordisHost.mjs +2 -1
- package/dist/runtimeCordisHost.mjs.map +1 -1
- package/dist/runtimeHeadlessSessionManager.d.mts +5 -1
- package/dist/runtimeHeadlessSessionManager.d.mts.map +1 -1
- package/dist/sessionMcp.d.mts +2 -1
- package/dist/src/schemas/hubChannel.cjs +20 -0
- package/dist/src/schemas/hubChannel.cjs.map +1 -0
- package/dist/src/schemas/hubChannel.d.mts +58 -0
- package/dist/src/schemas/hubChannel.d.mts.map +1 -1
- package/dist/src/schemas/hubChannel.mjs +19 -0
- package/dist/src/schemas/hubChannel.mjs.map +1 -0
- package/dist/src/schemas/packageApi.cjs.map +1 -1
- package/dist/src/schemas/packageApi.d.mts +11 -0
- package/dist/src/schemas/packageApi.d.mts.map +1 -1
- package/dist/src/schemas/packageApi.mjs.map +1 -1
- package/dist/src/schemas/sessionMcpActivity.d.mts +45 -0
- package/dist/src/schemas/sessionMcpActivity.d.mts.map +1 -0
- package/dist/src/server/headlessProtocol.cjs +8 -0
- package/dist/src/server/headlessProtocol.cjs.map +1 -1
- package/dist/src/server/headlessProtocol.mjs +8 -0
- package/dist/src/server/headlessProtocol.mjs.map +1 -1
- package/dist/src/server/sessionMcpHandler.cjs +80 -24
- package/dist/src/server/sessionMcpHandler.cjs.map +1 -1
- package/dist/src/server/sessionMcpHandler.d.mts +3 -0
- package/dist/src/server/sessionMcpHandler.d.mts.map +1 -1
- package/dist/src/server/sessionMcpHandler.mjs +80 -24
- package/dist/src/server/sessionMcpHandler.mjs.map +1 -1
- package/dist/src/server/sessionMcpRoutes.cjs +71 -3
- package/dist/src/server/sessionMcpRoutes.cjs.map +1 -1
- package/dist/src/server/sessionMcpRoutes.mjs +71 -3
- package/dist/src/server/sessionMcpRoutes.mjs.map +1 -1
- package/dist/src/services/piExtensionHost/index.cjs +7 -1
- package/dist/src/services/piExtensionHost/index.cjs.map +1 -1
- package/dist/src/services/piExtensionHost/index.mjs +8 -2
- package/dist/src/services/piExtensionHost/index.mjs.map +1 -1
- package/dist/src/services/sessionMcpActivity/index.cjs +115 -0
- package/dist/src/services/sessionMcpActivity/index.cjs.map +1 -0
- package/dist/src/services/sessionMcpActivity/index.mjs +111 -0
- package/dist/src/services/sessionMcpActivity/index.mjs.map +1 -0
- package/dist/src/systems/main/adapters/headlessSessionHost.cjs +5 -0
- package/dist/src/systems/main/adapters/headlessSessionHost.cjs.map +1 -1
- package/dist/src/systems/main/adapters/headlessSessionHost.mjs +5 -0
- package/dist/src/systems/main/adapters/headlessSessionHost.mjs.map +1 -1
- package/dist/src/web/services/testing/slotProps.cjs +1 -0
- package/dist/src/web/services/testing/slotProps.cjs.map +1 -1
- package/dist/src/web/services/testing/slotProps.d.mts +2 -0
- package/dist/src/web/services/testing/slotProps.d.mts.map +1 -1
- package/dist/src/web/services/testing/slotProps.mjs +1 -0
- package/dist/src/web/services/testing/slotProps.mjs.map +1 -1
- package/dist/src/web/types/webPlugin.d.mts +6 -0
- package/dist/src/web/types/webPlugin.d.mts.map +1 -1
- package/package.json +4 -4
package/dist/cordisHost.cjs
CHANGED
|
@@ -3,6 +3,7 @@ const require_runtimeCordisHost = require("./runtimeCordisHost.cjs");
|
|
|
3
3
|
exports.DOOM_CORDIS_HOST_ABI_VERSION = require_runtimeCordisHost.DOOM_CORDIS_HOST_ABI_VERSION;
|
|
4
4
|
exports.DOOM_CORDIS_HOST_QUERY_CHANNEL = require_runtimeCordisHost.DOOM_CORDIS_HOST_QUERY_CHANNEL;
|
|
5
5
|
exports.DOOM_CORDIS_RUNTIME_SERVICE = require_runtimeCordisHost.DOOM_CORDIS_RUNTIME_SERVICE;
|
|
6
|
+
exports.DOOM_CORDIS_SERVER_SERVICES = require_runtimeCordisHost.DOOM_CORDIS_SERVER_SERVICES;
|
|
6
7
|
exports.DOOM_CORDIS_SESSION_SERVICE = require_runtimeCordisHost.DOOM_CORDIS_SESSION_SERVICE;
|
|
7
8
|
exports.connectDoomCordisHost = require_runtimeCordisHost.connectDoomCordisHost;
|
|
8
9
|
exports.finalizeDoomCordisHost = require_runtimeCordisHost.finalizeDoomCordisHost;
|
package/dist/cordisHost.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { DOOM_CORDIS_HOST_ABI_VERSION, DOOM_CORDIS_HOST_QUERY_CHANNEL, DOOM_CORDIS_RUNTIME_SERVICE, DOOM_CORDIS_SESSION_SERVICE, DoomCordisHostConnection, DoomCordisHostController, DoomCordisHostMode, DoomCordisRuntimeService, DoomCordisSessionService, InstallDoomCordisHostOptions, connectDoomCordisHost, finalizeDoomCordisHost, installDoomCordisHost, requireDoomCordisSession } from "./runtimeCordisHost.mjs";
|
|
2
|
-
export { DOOM_CORDIS_HOST_ABI_VERSION, DOOM_CORDIS_HOST_QUERY_CHANNEL, DOOM_CORDIS_RUNTIME_SERVICE, DOOM_CORDIS_SESSION_SERVICE, type DoomCordisHostConnection, type DoomCordisHostController, type DoomCordisHostMode, type DoomCordisRuntimeService, type DoomCordisSessionService, type InstallDoomCordisHostOptions, connectDoomCordisHost, finalizeDoomCordisHost, installDoomCordisHost, requireDoomCordisSession };
|
|
1
|
+
import { DOOM_CORDIS_HOST_ABI_VERSION, DOOM_CORDIS_HOST_QUERY_CHANNEL, DOOM_CORDIS_RUNTIME_SERVICE, DOOM_CORDIS_SERVER_SERVICES, DOOM_CORDIS_SESSION_SERVICE, DoomCordisHostConnection, DoomCordisHostController, DoomCordisHostMode, DoomCordisRuntimeService, DoomCordisServerServices, DoomCordisSessionService, InstallDoomCordisHostOptions, connectDoomCordisHost, finalizeDoomCordisHost, installDoomCordisHost, requireDoomCordisSession } from "./runtimeCordisHost.mjs";
|
|
2
|
+
export { DOOM_CORDIS_HOST_ABI_VERSION, DOOM_CORDIS_HOST_QUERY_CHANNEL, DOOM_CORDIS_RUNTIME_SERVICE, DOOM_CORDIS_SERVER_SERVICES, DOOM_CORDIS_SESSION_SERVICE, type DoomCordisHostConnection, type DoomCordisHostController, type DoomCordisHostMode, type DoomCordisRuntimeService, type DoomCordisServerServices, type DoomCordisSessionService, type InstallDoomCordisHostOptions, connectDoomCordisHost, finalizeDoomCordisHost, installDoomCordisHost, requireDoomCordisSession };
|
package/dist/cordisHost.mjs
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { DOOM_CORDIS_HOST_ABI_VERSION, DOOM_CORDIS_HOST_QUERY_CHANNEL, DOOM_CORDIS_RUNTIME_SERVICE, DOOM_CORDIS_SESSION_SERVICE, connectDoomCordisHost, finalizeDoomCordisHost, installDoomCordisHost, requireDoomCordisSession } from "./runtimeCordisHost.mjs";
|
|
2
|
-
export { DOOM_CORDIS_HOST_ABI_VERSION, DOOM_CORDIS_HOST_QUERY_CHANNEL, DOOM_CORDIS_RUNTIME_SERVICE, DOOM_CORDIS_SESSION_SERVICE, connectDoomCordisHost, finalizeDoomCordisHost, installDoomCordisHost, requireDoomCordisSession };
|
|
1
|
+
import { DOOM_CORDIS_HOST_ABI_VERSION, DOOM_CORDIS_HOST_QUERY_CHANNEL, DOOM_CORDIS_RUNTIME_SERVICE, DOOM_CORDIS_SERVER_SERVICES, DOOM_CORDIS_SESSION_SERVICE, connectDoomCordisHost, finalizeDoomCordisHost, installDoomCordisHost, requireDoomCordisSession } from "./runtimeCordisHost.mjs";
|
|
2
|
+
export { DOOM_CORDIS_HOST_ABI_VERSION, DOOM_CORDIS_HOST_QUERY_CHANNEL, DOOM_CORDIS_RUNTIME_SERVICE, DOOM_CORDIS_SERVER_SERVICES, DOOM_CORDIS_SESSION_SERVICE, connectDoomCordisHost, finalizeDoomCordisHost, installDoomCordisHost, requireDoomCordisSession };
|
package/dist/headlessHub.cjs
CHANGED
|
@@ -415,7 +415,7 @@ function createHeadlessHub(options) {
|
|
|
415
415
|
started.channel.receive(scopeOf(session), payload, { connectionId });
|
|
416
416
|
return true;
|
|
417
417
|
};
|
|
418
|
-
const unregisterSession = (sessionId) => {
|
|
418
|
+
const unregisterSession = (sessionId, options) => {
|
|
419
419
|
const current = sessions.get(sessionId);
|
|
420
420
|
if (current === void 0) return;
|
|
421
421
|
const cleanup = sessionCleanups.get(sessionId);
|
|
@@ -430,10 +430,11 @@ function createHeadlessHub(options) {
|
|
|
430
430
|
directEvents.clearSession?.(sessionId);
|
|
431
431
|
emit({
|
|
432
432
|
kind: "removed",
|
|
433
|
-
sessionId
|
|
433
|
+
sessionId,
|
|
434
|
+
...options?.keepDormant === true ? { dormant: true } : {}
|
|
434
435
|
});
|
|
435
436
|
};
|
|
436
|
-
const startSessionShutdown = (session) => {
|
|
437
|
+
const startSessionShutdown = (session, closeOptions) => {
|
|
437
438
|
const existing = sessionShutdowns.get(session.id);
|
|
438
439
|
if (existing !== void 0) return existing.promise;
|
|
439
440
|
const shutdown = {
|
|
@@ -441,7 +442,7 @@ function createHeadlessHub(options) {
|
|
|
441
442
|
promise: Promise.resolve(),
|
|
442
443
|
pending: true
|
|
443
444
|
};
|
|
444
|
-
const promise = Promise.resolve().then(() => options.manager.closeSession(session.id));
|
|
445
|
+
const promise = Promise.resolve().then(() => closeOptions === void 0 ? options.manager.closeSession(session.id) : options.manager.closeSession(session.id, closeOptions));
|
|
445
446
|
shutdown.promise = promise;
|
|
446
447
|
sessionShutdowns.set(session.id, shutdown);
|
|
447
448
|
promise.then(() => {
|
|
@@ -571,7 +572,7 @@ function createHeadlessHub(options) {
|
|
|
571
572
|
});
|
|
572
573
|
refreshGit();
|
|
573
574
|
};
|
|
574
|
-
const closeSession = async (sessionId) => {
|
|
575
|
+
const closeSession = async (sessionId, closeOptions) => {
|
|
575
576
|
const pending = sessionShutdowns.get(sessionId);
|
|
576
577
|
if (pending !== void 0) {
|
|
577
578
|
await pending.promise;
|
|
@@ -579,8 +580,14 @@ function createHeadlessHub(options) {
|
|
|
579
580
|
}
|
|
580
581
|
const session = sessions.get(sessionId);
|
|
581
582
|
if (session === void 0) return;
|
|
582
|
-
unregisterSession(sessionId);
|
|
583
|
-
await startSessionShutdown(session);
|
|
583
|
+
unregisterSession(sessionId, closeOptions);
|
|
584
|
+
await startSessionShutdown(session, closeOptions);
|
|
585
|
+
};
|
|
586
|
+
const promptTarget = (sessionId, parentSessionId) => {
|
|
587
|
+
const session = closed ? void 0 : sessions.get(sessionId);
|
|
588
|
+
if (session === void 0) throw new Error("The session is not live.");
|
|
589
|
+
if (parentSessionId !== void 0 && session.parentSessionId !== parentSessionId) throw new Error("Only the parent session can drive this session.");
|
|
590
|
+
return session;
|
|
584
591
|
};
|
|
585
592
|
sessionService = {
|
|
586
593
|
create: async (request) => {
|
|
@@ -589,6 +596,21 @@ function createHeadlessHub(options) {
|
|
|
589
596
|
},
|
|
590
597
|
close: closeSession,
|
|
591
598
|
isLive: (sessionId) => !closed && sessions.has(sessionId),
|
|
599
|
+
prompt: async (sessionId, text, promptOptions) => {
|
|
600
|
+
promptOptions?.signal?.throwIfAborted();
|
|
601
|
+
const { settled } = await promptTarget(sessionId, promptOptions?.parentSessionId).host.runtime.submitPrompt(text);
|
|
602
|
+
return { settled };
|
|
603
|
+
},
|
|
604
|
+
abort: async (sessionId, abortOptions) => {
|
|
605
|
+
await promptTarget(sessionId, abortOptions?.parentSessionId).host.runtime.abort();
|
|
606
|
+
},
|
|
607
|
+
steer: async (sessionId, message, steerOptions) => {
|
|
608
|
+
await promptTarget(sessionId, steerOptions?.parentSessionId).host.runtime.steer(message);
|
|
609
|
+
},
|
|
610
|
+
release: async (sessionId, releaseOptions) => {
|
|
611
|
+
promptTarget(sessionId, releaseOptions?.parentSessionId);
|
|
612
|
+
await closeSession(sessionId, { keepDormant: true });
|
|
613
|
+
},
|
|
592
614
|
reservations: options.sessionReservations,
|
|
593
615
|
provisionReservedWorktree: async (request) => {
|
|
594
616
|
const parent = sessions.get(request.parentSessionId);
|
package/dist/headlessHub.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headlessHub.cjs","names":["AGENT_SETTLED_ENTRY_TYPE","BACKGROUND_CONTEXT","createDoomPluginRegistry","readSessionGitStatus","crypto","createDoomServerHost","installServerFacets","PROFILE_ICON_MIME_TYPES"],"sources":["../src/server/headlessHub.ts"],"sourcesContent":["import crypto from 'node:crypto';\n\nimport { BACKGROUND_CONTEXT } from '@earendil-works/pi-agent-core/harness/context';\n\nimport type {\n DoomComputerUseHostBinding,\n DoomDirectEventBus,\n DoomHubChannel,\n DoomHubChannelConnection,\n DoomHubChannelHost,\n DoomHubReservedWorktreeProvisioner,\n DoomHubSessionApiRequest,\n DoomHubSessionCreateRequest,\n DoomHubSessionScope,\n DoomHubSessionService,\n DoomSessionCommunicationEndpoint,\n DoomHubSessionReservations,\n DoomPendingSessionSetup,\n} from '../exports/hubChannel';\nimport type { DoomWebComposition } from '../exports/packageApi';\nimport type { DoomApiContext, DoomApiMount } from '../exports/packageApi';\nimport {\n createDoomPluginRegistry,\n type DoomPluginCaller,\n type DoomPluginDirection,\n type DoomPluginRegistry,\n} from '../exports/pluginProtocol';\nimport {\n createDoomServerHost,\n installServerFacets,\n type DoomServerFacet,\n type DoomServerHost,\n type InstalledServerFacets,\n type LoadedServerFacet,\n} from '../exports/serverFacet';\nimport { PROFILE_ICON_MIME_TYPES } from '../schemas/profileIdentity';\nimport { readSessionGitStatus } from '../services/sessionGitStatus';\nimport type { SessionGitStatus } from '../services/sessionGitStatus/type';\nimport type { HeadlessSessionHost, HeadlessSessionHostOptions } from '../systems/main/types/headlessSessionHost';\nimport type { HeadlessSessionManager } from '../systems/main/types/headlessSessionManager';\nimport { AGENT_SETTLED_ENTRY_TYPE } from './directHarnessRuntime';\n\nconst AUTHORIZATION_HEADER = 'authorization';\nconst CHANNEL_PRIORITY = { global: 0, workspace: 1, session: 2 } as const;\nconst ICON_VERSION_LENGTH = 12;\nconst ICON_DATA_URL = /^data:([a-z/+.-]+);base64,([A-Za-z0-9+/=]+)$/u;\n\n/** The profile a session runs under, as shown on its rail card. The icon itself is served separately. */\nexport interface HeadlessSessionProfile {\n readonly name: string;\n readonly displayName?: string;\n /** Changes whenever the icon does, so a client can cache the avatar by it. */\n readonly iconVersion?: string;\n}\n\n/** A profile's persona presentation: display name and icon as a data: URL. */\nexport interface HeadlessProfileIdentity {\n readonly displayName?: string;\n readonly icon?: string;\n}\n\nexport interface HeadlessHubSession {\n readonly id: string;\n readonly workspaceId?: string;\n webComposition?: DoomWebComposition;\n readonly name: string;\n readonly cwd: string;\n readonly createdAt: string;\n readonly parentSessionId?: string;\n readonly sessionProvenance?: string;\n readonly updatedAt?: string;\n readonly phase?: 'idle' | 'turn' | 'compaction' | 'retry';\n readonly phaseSince?: string;\n readonly pendingMessageCount?: number;\n readonly everPrompted?: boolean;\n readonly awaitingInput?: boolean;\n readonly lastSettledAt?: string;\n /** Branch and dirty flag of the session cwd; absent outside a git work tree. */\n readonly git?: SessionGitStatus;\n /** Selected profile; absent when the session runs without one. */\n readonly profile?: HeadlessSessionProfile;\n readonly pendingSetups?: readonly DoomPendingSessionSetup[];\n /** Environment admitted for this session, when supplied by the host. */\n readonly environment?: Readonly<Record<string, string | undefined>>;\n readonly host: HeadlessSessionHost;\n}\n\nexport interface HeadlessWorkspace {\n readonly id: string;\n readonly root: string;\n readonly name?: string;\n readonly available?: boolean;\n}\n\n/** A workspace to admit: an existing folder, or a name alone for a new default folder. */\nexport interface HeadlessWorkspaceAdmission {\n readonly root?: string;\n readonly name?: string;\n}\n\nexport type HeadlessHubEvent =\n | { kind: 'upsert'; session: HeadlessHubSession }\n | { kind: 'removed'; sessionId: string }\n | { kind: 'workspace_upsert'; workspace: HeadlessWorkspace }\n | { kind: 'workspace_removed'; workspaceId: string }\n | { kind: 'channel'; frameType: string; sessionId: string; payload: unknown; connectionId?: string };\n\nexport interface HeadlessHubOptions {\n manager: HeadlessSessionManager;\n /** Resolves the hub credential, because the server reads its token file after the hub exists. */\n hubToken?: () => string | undefined;\n /** Creates a session through the canonical cockpit lifecycle for hub channels. */\n createSession?: (\n request: DoomHubSessionCreateRequest,\n /**\n * Set when a workspace-mounted package creates a session with no parent. The host must\n * refuse a cwd outside that workspace before admitting anything.\n */\n placement?: { workspaceId: string },\n ) => Promise<DoomHubSessionScope>;\n onNotice?: (message: string) => void;\n sessionReservations?: DoomHubSessionReservations;\n requestSessionApi?: (scope: DoomHubSessionScope, request: DoomHubSessionApiRequest) => Promise<Response>;\n computerUse?: DoomComputerUseHostBinding;\n admitWorkspace?: (request: HeadlessWorkspaceAdmission) => Promise<HeadlessWorkspace>;\n onWorkspaceRemoved?: (workspaceId: string) => void;\n /** Reads a session cwd's git status; defaults to the git CLI. */\n readGitStatus?: (cwd: string) => Promise<SessionGitStatus | undefined>;\n /** Resolves a profile's persona presentation for a workspace root; sessions show no avatar without it. */\n resolveProfileIdentity?: (root: string, profile: string) => HeadlessProfileIdentity | undefined;\n}\n\nexport interface HeadlessHub {\n /** Canonical direct lifecycle shared by hub channels, APIs, and sessions. */\n readonly sessionService: DoomHubSessionService;\n /** Lifecycle-owned event path shared by session facets and hub channels. */\n readonly directEvents: DoomDirectEventBus;\n readonly computerUse?: DoomComputerUseHostBinding;\n snapshot(): readonly HeadlessHubSession[];\n session(sessionId: string): HeadlessHubSession | undefined;\n runtime(sessionId: string): HeadlessSessionHost['runtime'] | undefined;\n /** The session's profile avatar image, when its profile has an icon. */\n sessionAvatar(sessionId: string): { mimeType: string; bytes: Uint8Array } | undefined;\n onEvent(listener: (event: HeadlessHubEvent) => void): () => void;\n register(session: HeadlessHubSession): void;\n create(options: HeadlessSessionHostOptions): Promise<HeadlessHubSession>;\n closeSession(sessionId: string): Promise<void>;\n /** Publishes removal of a persisted session that has no live host. */\n notifySessionRemoved(sessionId: string): void;\n /** Publishes setup-only children through the parent's normal session updates. */\n setPendingSessionSetups?(sessionId: string, pending: readonly DoomPendingSessionSetup[]): void;\n /** Dispatches an authenticated package API request inside the owning process. */\n requestSessionApi(scope: DoomHubSessionScope, request: DoomHubSessionApiRequest): Promise<Response>;\n /** Mounts the selected hub facets once, before the headless server accepts clients. */\n mountFacets(facets: readonly (DoomServerFacet | LoadedServerFacet)[], context?: DoomApiContext): Promise<void>;\n workspaces(): readonly HeadlessWorkspace[];\n registerWorkspace(workspace: HeadlessWorkspace): void;\n admitWorkspace(request: HeadlessWorkspaceAdmission): Promise<HeadlessWorkspace>;\n removeWorkspace(workspaceId: string): Promise<void>;\n requestApi(mount: DoomApiMount, basePath: string, request: Request): Promise<Response>;\n /** The dispatch table hub facets mount into, shared with session hosts this hub serves. */\n readonly pluginRegistry: DoomPluginRegistry;\n invokePlugin(call: unknown, direction: DoomPluginDirection, caller?: DoomPluginCaller): Promise<unknown>;\n registerChannel(channel: DoomHubChannel, mount?: DoomApiMount): () => void;\n channelTypes(): readonly string[];\n channelFrames(sessionId: string): Array<{ type: string; sessionId: string; payload: unknown }>;\n /** Resolves a channel-owned child journal for the browser thread projection. */\n threadJournal(sessionId: string, threadId: string): string | undefined;\n receiveChannel(\n sessionId: string,\n frameType: string,\n payload: unknown,\n connectionId: string,\n desktopAuthorized?: boolean,\n ): void;\n disconnectChannels(connectionId: string): void;\n close(): Promise<void>;\n}\n\ninterface StartedChannel {\n readonly channel: DoomHubChannel;\n readonly source: ReturnType<DoomHubChannel['start']>;\n readonly mount: DoomApiMount;\n}\n\n/**\n * When a reopened session last settled, from the marker its runtime journals at every run end.\n * Without it a session with a whole history would read as fresh until its next turn.\n */\nasync function lastSettledAt(host: HeadlessSessionHost): Promise<string | undefined> {\n try {\n const entry = await host.runtime.lane.findEntry(\n { type: 'custom', customType: AGENT_SETTLED_ENTRY_TYPE, order: 'newestFirst' },\n BACKGROUND_CONTEXT,\n );\n const data = entry?.type === 'custom' ? (entry.data as { timestamp?: unknown } | undefined) : undefined;\n return typeof data?.timestamp === 'number' && Number.isFinite(data.timestamp)\n ? new Date(data.timestamp).toISOString()\n : undefined;\n } catch {\n // A runtime without a readable journal has no history to report; the rail shows it as fresh.\n return undefined;\n }\n}\n\nfunction scopeOf(session: HeadlessHubSession): DoomHubSessionScope {\n return {\n sessionId: session.id,\n ...(session.workspaceId === undefined ? {} : { workspaceId: session.workspaceId }),\n cwd: session.cwd,\n ...(session.environment === undefined ? {} : { environment: session.environment }),\n ...(session.host.sessionContext === undefined ? {} : { sessionContext: session.host.sessionContext }),\n };\n}\n\n/** Owns direct, same-process session and channel state for presentation adapters. */\nexport function createHeadlessHub(options: HeadlessHubOptions): HeadlessHub {\n const sessions = new Map<string, HeadlessHubSession>();\n const pendingSetups = new Map<string, readonly DoomPendingSessionSetup[]>();\n const present = (session: HeadlessHubSession): HeadlessHubSession =>\n pendingSetups.has(session.id) ? { ...session, pendingSetups: pendingSetups.get(session.id) } : session;\n const pluginRegistry = createDoomPluginRegistry();\n const channels = new Map<string, StartedChannel>();\n const listeners = new Set<(event: HeadlessHubEvent) => void>();\n const subscriptions = new Set<() => void>();\n const sessionCleanups = new Map<string, () => void>();\n const presentationCleanups = new Map<string, () => void>();\n /** Profile icons by session, kept out of summaries because each can be hundreds of kilobytes. */\n const avatarIcons = new Map<string, string>();\n const sessionShutdowns = new Map<string, { session: HeadlessHubSession; promise: Promise<void>; pending: boolean }>();\n const directEventListeners = new Map<string, Set<(payload: unknown) => void>>();\n const directEventLatest = new Map<string, unknown>();\n const maxDirectEventLatest = 4096;\n const communicationEndpoints = new Map<\n string,\n {\n listeners: Map<string, Set<(sourceSessionId: string, payload: unknown) => void>>;\n readyListeners: Set<(peerSessionId: string) => void>;\n ready: boolean;\n endpoint: DoomSessionCommunicationEndpoint;\n }\n >();\n let closed = false;\n const mounts = new Map<string, { host: DoomServerHost; installed: InstalledServerFacets }>();\n const workspaces = new Map<string, HeadlessWorkspace>();\n const worktreeProvisioners = new Map<\n string,\n { mount: DoomApiMount; provisioner: DoomHubReservedWorktreeProvisioner }\n >();\n let sessionService: DoomHubSessionService;\n\n const mountKey = (mount: DoomApiMount): string =>\n mount.scope === 'global'\n ? 'global'\n : mount.scope === 'workspace'\n ? `workspace:${mount.workspaceId}`\n : `session:${mount.sessionId}`;\n const belongs = (mount: DoomApiMount, session: HeadlessHubSession): boolean =>\n mount.scope === 'global' ||\n (mount.scope === 'workspace' ? session.workspaceId === mount.workspaceId : session.id === mount.sessionId);\n const canClose = (mount: DoomApiMount, session: HeadlessHubSession): boolean =>\n belongs(mount, session) || (mount.scope === 'session' && session.parentSessionId === mount.sessionId);\n const canCommunicate = (sourceId: string, targetId: string): boolean => {\n if (closed || sourceId === targetId) return false;\n const source = sessions.get(sourceId);\n const target = sessions.get(targetId);\n return (\n source !== undefined &&\n target !== undefined &&\n (source.parentSessionId === target.id || target.parentSessionId === source.id)\n );\n };\n const registerReservedWorktreeProvisioner = (\n mount: DoomApiMount,\n provisioner: DoomHubReservedWorktreeProvisioner,\n ): (() => void) => {\n const key = mountKey(mount);\n if (worktreeProvisioners.has(key)) throw new Error('A worktree provisioner is already registered.');\n worktreeProvisioners.set(key, { mount, provisioner });\n return () => {\n if (worktreeProvisioners.get(key)?.provisioner === provisioner) worktreeProvisioners.delete(key);\n };\n };\n const worktreeProvisionerFor = (parent: HeadlessHubSession): DoomHubReservedWorktreeProvisioner | undefined => {\n let provisioner: DoomHubReservedWorktreeProvisioner | undefined;\n let priority = -1;\n for (const { mount, provisioner: candidate } of worktreeProvisioners.values()) {\n if (!belongs(mount, parent) || CHANNEL_PRIORITY[mount.scope] <= priority) continue;\n provisioner = candidate;\n priority = CHANNEL_PRIORITY[mount.scope];\n }\n return provisioner;\n };\n const selectedChannels = (session: HeadlessHubSession): StartedChannel[] => {\n const selected = new Map<string, StartedChannel>();\n for (const started of channels.values()) {\n if (!belongs(started.mount, session)) continue;\n const previous = selected.get(started.channel.frameType);\n if (!previous || CHANNEL_PRIORITY[started.mount.scope] > CHANNEL_PRIORITY[previous.mount.scope])\n selected.set(started.channel.frameType, started);\n }\n return [...selected.values()];\n };\n const selectedChannel = (session: HeadlessHubSession, frameType: string): StartedChannel | undefined =>\n selectedChannels(session).find((started) => started.channel.frameType === frameType);\n /**\n * One channel serves a session: the narrowest mount that reaches it. A package that\n * registers the same channel globally and per workspace would otherwise run two\n * sources against one session, and only the narrower one can publish or receive.\n *\n * Compared by mount scope rather than identity so a channel being started already\n * knows which sessions it is taking over.\n */\n const serves = (mount: DoomApiMount, frameType: string, session: HeadlessHubSession): boolean => {\n if (!belongs(mount, session)) return false;\n const current = selectedChannel(session, frameType);\n return current === undefined || CHANNEL_PRIORITY[mount.scope] >= CHANNEL_PRIORITY[current.mount.scope];\n };\n\n const directEventKey = (frameType: string, sessionId: string): string => `${frameType}\\0${sessionId}`;\n const directEvents: DoomDirectEventBus = {\n publish(frameType, sessionId, payload) {\n if (closed) return;\n const key = directEventKey(frameType, sessionId);\n directEventLatest.set(key, payload);\n while (directEventLatest.size > maxDirectEventLatest) {\n const oldest = directEventLatest.keys().next().value;\n if (oldest === undefined) break;\n directEventLatest.delete(oldest);\n }\n const listeners = directEventListeners.get(key);\n if (listeners === undefined) return;\n for (const listener of listeners) listener(payload);\n },\n subscribe(frameType, sessionId, listener, options) {\n if (closed) return () => undefined;\n const key = directEventKey(frameType, sessionId);\n const listeners = directEventListeners.get(key) ?? new Set<(payload: unknown) => void>();\n listeners.add(listener);\n directEventListeners.set(key, listeners);\n if (options?.replayLatest && directEventLatest.has(key)) listener(directEventLatest.get(key));\n let released = false;\n return () => {\n if (released) return;\n released = true;\n listeners.delete(listener);\n if (listeners.size === 0) directEventListeners.delete(key);\n };\n },\n clearSession(sessionId) {\n const suffix = `\\0${sessionId}`;\n for (const key of directEventLatest.keys()) if (key.endsWith(suffix)) directEventLatest.delete(key);\n },\n close() {\n directEventListeners.clear();\n directEventLatest.clear();\n },\n };\n\n const announceCommunicationReady = (sessionId: string): void => {\n const current = communicationEndpoints.get(sessionId);\n if (current?.ready !== true) return;\n for (const [peerSessionId, peer] of communicationEndpoints) {\n if (!peer.ready || !canCommunicate(sessionId, peerSessionId)) continue;\n for (const listener of current.readyListeners) listener(peerSessionId);\n for (const listener of peer.readyListeners) listener(sessionId);\n }\n };\n\n const bindCommunication = (sessionId: string): DoomSessionCommunicationEndpoint => {\n const existing = communicationEndpoints.get(sessionId);\n if (existing !== undefined) return existing.endpoint;\n const listeners = new Map<string, Set<(sourceSessionId: string, payload: unknown) => void>>();\n const readyListeners = new Set<(peerSessionId: string) => void>();\n let endpoint!: DoomSessionCommunicationEndpoint;\n const record = {\n listeners,\n readyListeners,\n ready: false,\n get endpoint() {\n return endpoint;\n },\n };\n endpoint = {\n sessionId,\n publish(targetSessionId, type, payload) {\n if (!canCommunicate(sessionId, targetSessionId)) return false;\n const target = communicationEndpoints.get(targetSessionId);\n if (target?.ready !== true) return false;\n for (const listener of target.listeners.get(type) ?? []) listener(sessionId, payload);\n return true;\n },\n subscribe(type, listener) {\n const current = listeners.get(type) ?? new Set<(sourceSessionId: string, payload: unknown) => void>();\n current.add(listener);\n listeners.set(type, current);\n return () => {\n current.delete(listener);\n if (current.size === 0) listeners.delete(type);\n };\n },\n onPeerReady(listener) {\n readyListeners.add(listener);\n if (!record.ready) {\n record.ready = true;\n announceCommunicationReady(sessionId);\n }\n return () => readyListeners.delete(listener);\n },\n close() {\n if (communicationEndpoints.get(sessionId)?.endpoint !== endpoint) return;\n communicationEndpoints.delete(sessionId);\n listeners.clear();\n readyListeners.clear();\n },\n };\n communicationEndpoints.set(sessionId, record);\n return endpoint;\n };\n\n const queuedEvents: HeadlessHubEvent[] = [];\n let publishing = false;\n const emit = (event: HeadlessHubEvent): void => {\n if (closed) return;\n queuedEvents.push(event.kind === 'upsert' ? { ...event, session: present(event.session) } : event);\n if (publishing) return;\n publishing = true;\n try {\n for (let index = 0; index < queuedEvents.length; index += 1)\n for (const listener of listeners) listener(queuedEvents[index]);\n } finally {\n queuedEvents.length = 0;\n publishing = false;\n }\n };\n const sessionScopes = (): readonly DoomHubSessionScope[] => [...sessions.values()].map(scopeOf);\n const publish = (frameType: string, sessionId: string, payload: unknown, connectionId?: string): void => {\n if (!sessions.has(sessionId)) return;\n emit({ kind: 'channel', frameType, sessionId, payload, ...(connectionId === undefined ? {} : { connectionId }) });\n };\n\n /**\n * Hub-owned calls carry the hub credential, so a session API can tell them from a\n * browser call it must not trust. Browser traffic reaches a session API through\n * requestApi instead, and keeps the headers its caller sent.\n */\n const hubAuthenticated = (request: DoomHubSessionApiRequest): DoomHubSessionApiRequest => {\n const token = options.hubToken?.();\n if (token === undefined || token === '') return request;\n const headers = new Headers(request.headers);\n if (headers.has(AUTHORIZATION_HEADER)) return request;\n headers.set(AUTHORIZATION_HEADER, `Bearer ${token}`);\n return { ...request, headers };\n };\n\n const reservationsFor = (mount: DoomApiMount): DoomHubSessionReservations | undefined => {\n const reservations = options.sessionReservations;\n if (!reservations) return undefined;\n const check = (parentId: string): void => {\n const parent = sessions.get(parentId);\n if (!parent || !belongs(mount, parent)) throw new Error('Parent session is outside this mount.');\n };\n return {\n read: (id, parentId) => {\n check(parentId);\n return reservations.read(id, parentId);\n },\n prepare: (id, parentId, cwd) => {\n check(parentId);\n return reservations.prepare(id, parentId, cwd);\n },\n complete: (id, parentId) => {\n check(parentId);\n return reservations.complete(id, parentId);\n },\n };\n };\n const channelHost = (frameType: string, mount: DoomApiMount): DoomHubChannelHost => ({\n sessions: () =>\n sessionScopes().filter((scope) => {\n const session = sessions.get(scope.sessionId);\n return session !== undefined && serves(mount, frameType, session);\n }),\n sessionService: {\n reservations: reservationsFor(mount),\n create: async (request) => {\n // A workspace package may start a top-level session, placed in its own workspace.\n if (mount.scope === 'workspace' && request.parentSessionId === undefined) {\n if (request.reservationId !== undefined) throw new Error('A reserved session requires its owning parent.');\n if (options.createSession === undefined)\n throw new Error('The cockpit session service cannot create sessions in this host.');\n return options.createSession(request, { workspaceId: mount.workspaceId });\n }\n if (mount.scope !== 'global') {\n const parent = request.parentSessionId === undefined ? undefined : sessions.get(request.parentSessionId);\n if (!parent || !belongs(mount, parent)) throw new Error('Parent session is outside this mount.');\n }\n return sessionService.create(request);\n },\n close: async (id) => {\n const session = sessions.get(id);\n if (session === undefined) {\n const shutdown = sessionShutdowns.get(id);\n if (shutdown !== undefined) {\n if (!canClose(mount, shutdown.session)) throw new Error('Session is outside this mount.');\n await shutdown.promise;\n }\n return;\n }\n if (!canClose(mount, session)) throw new Error('Session is outside this mount.');\n await sessionService.close(id);\n },\n isLive: (id) => sessionService.isLive(id),\n registerReservedWorktreeProvisioner: (provisioner) => registerReservedWorktreeProvisioner(mount, provisioner),\n canCommunicate: (sourceId, targetId) => {\n const source = sessions.get(sourceId);\n return (\n source !== undefined && belongs(mount, source) && sessionService.canCommunicate?.(sourceId, targetId) === true\n );\n },\n },\n directEvents: {\n publish: (type, id, payload) => {\n const session = sessions.get(id);\n if (session && serves(mount, frameType, session)) directEvents.publish(type, id, payload);\n },\n subscribe: (type, id, listener, subscriptionOptions) => {\n const session = sessions.get(id);\n return session && serves(mount, frameType, session)\n ? directEvents.subscribe(type, id, listener, subscriptionOptions)\n : () => undefined;\n },\n close: () => undefined,\n },\n publish: (sessionId, payload) => {\n const session = sessions.get(sessionId);\n if (session && serves(mount, frameType, session)) publish(frameType, sessionId, payload);\n },\n publishToConnection: (connectionId, sessionId, payload) => {\n const session = sessions.get(sessionId);\n if (connectionId === '' || !session || !serves(mount, frameType, session)) return false;\n publish(frameType, sessionId, payload, connectionId);\n return true;\n },\n requestSessionApi: async (scope, request) => {\n const session = sessions.get(scope.sessionId);\n if (!session || !belongs(mount, session)) return Response.json({ error: 'Session not found.' }, { status: 404 });\n if (options.requestSessionApi === undefined)\n return Response.json({ error: 'Session API unavailable.' }, { status: 404 });\n return options.requestSessionApi(scope, hubAuthenticated(request));\n },\n ...(options.computerUse === undefined\n ? {}\n : {\n computerUse: {\n get available() {\n return options.computerUse!.available;\n },\n request(scope, request) {\n const session = sessions.get(scope.sessionId);\n if (!session || !belongs(mount, session)) throw new Error('Computer-use session is outside this mount.');\n return options.computerUse!.request(scopeOf(session), request);\n },\n },\n }),\n onNotice: (message) => options.onNotice?.(message),\n });\n\n const startChannel = (channel: DoomHubChannel, mount: DoomApiMount): StartedChannel | undefined => {\n const key = `${mountKey(mount)}:${channel.frameType}`;\n if (channels.has(key)) {\n options.onNotice?.(`hub channel '${channel.frameType}' is already registered`);\n return undefined;\n }\n try {\n const displaced = new Map<string, StartedChannel>();\n for (const session of sessions.values()) {\n if (!belongs(mount, session)) continue;\n const previous = selectedChannel(session, channel.frameType);\n if (previous !== undefined) displaced.set(session.id, previous);\n }\n const source = channel.start(channelHost(channel.frameType, mount));\n const started = { channel, source, mount };\n channels.set(key, started);\n for (const session of sessions.values()) {\n if (selectedChannel(session, channel.frameType) !== started) continue;\n displaced.get(session.id)?.source.sessionRemoved?.(session.id);\n source.sessionAdded?.(scopeOf(session));\n }\n return started;\n } catch (error) {\n options.onNotice?.(`hub channel '${channel.frameType}' failed (${String(error)})`);\n return undefined;\n }\n };\n\n const releaseChannel = (channel: DoomHubChannel, started: StartedChannel): void => {\n const key = `${mountKey(started.mount)}:${channel.frameType}`;\n if (channels.get(key) !== started) return;\n const served = [...sessions.values()].filter((session) => selectedChannel(session, channel.frameType) === started);\n channels.delete(key);\n started.source.close();\n for (const session of served) selectedChannel(session, channel.frameType)?.source.sessionAdded?.(scopeOf(session));\n };\n\n const receiveMountedChannel = (\n mount: DoomApiMount,\n sessionId: string,\n frameType: string,\n payload: unknown,\n connectionId: string,\n ): boolean => {\n if (!connectionId) return false;\n const session = sessions.get(sessionId);\n if (!session || !belongs(mount, session)) return false;\n const started = channels.get(`${mountKey(mount)}:${frameType}`);\n if (!started?.channel.receive) return false;\n started.channel.receive(scopeOf(session), payload, { connectionId });\n return true;\n };\n\n const unregisterSession = (sessionId: string): void => {\n const current = sessions.get(sessionId);\n if (current === undefined) return;\n const cleanup = sessionCleanups.get(sessionId);\n if (cleanup !== undefined) subscriptions.delete(cleanup);\n communicationEndpoints.get(sessionId)?.endpoint.close();\n // Let scoped sources dispatch their final stop while the live-session boundary still admits it.\n for (const { source } of selectedChannels(current)) source.sessionRemoved?.(sessionId);\n sessions.delete(sessionId);\n sessionCleanups.delete(sessionId);\n presentationCleanups.get(sessionId)?.();\n presentationCleanups.delete(sessionId);\n avatarIcons.delete(sessionId);\n directEvents.clearSession?.(sessionId);\n emit({ kind: 'removed', sessionId });\n };\n\n const startSessionShutdown = (session: HeadlessHubSession): Promise<void> => {\n const existing = sessionShutdowns.get(session.id);\n if (existing !== undefined) return existing.promise;\n const shutdown = { session, promise: Promise.resolve(), pending: true };\n const promise = Promise.resolve().then(() => options.manager.closeSession(session.id));\n shutdown.promise = promise;\n sessionShutdowns.set(session.id, shutdown);\n void promise.then(\n () => {\n shutdown.pending = false;\n if (sessionShutdowns.get(session.id) === shutdown) sessionShutdowns.delete(session.id);\n },\n () => {\n shutdown.pending = false;\n },\n );\n return promise;\n };\n\n const register = (session: HeadlessHubSession): void => {\n if (closed) throw new Error('The headless hub is closed.');\n if (sessions.has(session.id)) throw new Error(`Session '${session.id}' is already registered.`);\n if (sessionShutdowns.get(session.id)?.pending) throw new Error(`Session '${session.id}' is still shutting down.`);\n // A failed shutdown stays visible to scoped closers until the id is reused. Reopening\n // the session is that reuse, so it must not be blocked by the stale failure.\n sessionShutdowns.delete(session.id);\n let current: HeadlessHubSession = {\n ...session,\n updatedAt: session.updatedAt ?? session.createdAt,\n phase: session.phase ?? 'idle',\n phaseSince: session.phaseSince ?? session.createdAt,\n pendingMessageCount: session.pendingMessageCount ?? 0,\n everPrompted: session.everPrompted ?? false,\n awaitingInput: session.awaitingInput ?? false,\n };\n sessions.set(session.id, current);\n announceCommunicationReady(session.id);\n // Branch changes happen inside runs, so the status is read at start and after each settle.\n const refreshGit = (): void => {\n void (options.readGitStatus ?? readSessionGitStatus)(session.cwd).then(\n (git) => {\n if (closed || sessions.get(session.id)?.host !== session.host) return;\n if (git?.branch === current.git?.branch && git?.dirty === current.git?.dirty) return;\n const { git: _previous, ...rest } = current;\n current = git === undefined ? rest : { ...rest, git };\n sessions.set(session.id, current);\n emit({ kind: 'upsert', session: current });\n },\n (error: unknown) =>\n options.onNotice?.(\n `session ${session.id} git status failed (${error instanceof Error ? error.message : String(error)})`,\n ),\n );\n };\n const stopPresentation = session.host.onPresentationFrame((frame) => {\n if (sessions.get(session.id)?.host !== session.host) return;\n const type = typeof frame.type === 'string' ? frame.type : '';\n // Streaming updates belong to the session presentation. Replicating the\n // hub's growing event list for every token makes model output quadratic.\n if (\n type !== 'agent_start' &&\n type !== 'agent_settled' &&\n type !== 'message_end' &&\n type !== 'session_info_changed' &&\n type !== 'extension_ui_request' &&\n type !== 'extension_ui_answered'\n )\n return;\n const now = new Date().toISOString();\n const phase = type === 'agent_start' ? 'turn' : type === 'agent_settled' ? 'idle' : current.phase;\n const phaseChanged = phase !== current.phase;\n const method = typeof frame.method === 'string' ? frame.method : '';\n const awaitingInput =\n type === 'extension_ui_request' && ['select', 'confirm', 'input', 'editor'].includes(method)\n ? true\n : type === 'extension_ui_answered' || type === 'agent_settled'\n ? false\n : current.awaitingInput;\n current = {\n ...current,\n name: type === 'session_info_changed' && typeof frame.name === 'string' ? frame.name : current.name,\n updatedAt: now,\n phase,\n phaseSince: phaseChanged ? now : current.phaseSince,\n everPrompted: current.everPrompted || type === 'agent_start',\n awaitingInput,\n ...(type === 'agent_settled' ? { lastSettledAt: now } : {}),\n };\n sessions.set(session.id, current);\n emit({ kind: 'upsert', session: current });\n if (type === 'agent_settled') refreshGit();\n });\n let registered = false;\n const applyProfile = (profileName: string | undefined): void => {\n if (closed || sessions.get(session.id)?.host !== session.host) return;\n let identity: HeadlessProfileIdentity | undefined;\n if (profileName) {\n const root = (session.workspaceId && workspaces.get(session.workspaceId)?.root) || session.cwd;\n try {\n identity = options.resolveProfileIdentity?.(root, profileName);\n } catch (error) {\n options.onNotice?.(\n `session ${session.id} profile \"${profileName}\" could not be resolved (${error instanceof Error ? error.message : String(error)})`,\n );\n }\n }\n const icon = identity?.icon;\n if (icon) avatarIcons.set(session.id, icon);\n else avatarIcons.delete(session.id);\n const profile: HeadlessSessionProfile | undefined = profileName\n ? {\n name: profileName,\n ...(identity?.displayName ? { displayName: identity.displayName } : {}),\n ...(icon\n ? { iconVersion: crypto.createHash('sha256').update(icon).digest('hex').slice(0, ICON_VERSION_LENGTH) }\n : {}),\n }\n : undefined;\n const previous = current.profile;\n if (\n previous?.name === profile?.name &&\n previous?.displayName === profile?.displayName &&\n previous?.iconVersion === profile?.iconVersion\n )\n return;\n const { profile: _previous, ...rest } = current;\n current = profile === undefined ? rest : { ...rest, profile };\n sessions.set(session.id, current);\n if (registered) emit({ kind: 'upsert', session: current });\n };\n const stopSelection = session.host.onSelection?.((selection) => applyProfile(selection.profile));\n presentationCleanups.set(session.id, () => {\n stopPresentation();\n stopSelection?.();\n });\n for (const { source } of selectedChannels(current)) source.sessionAdded?.(scopeOf(session));\n const active = (): boolean => sessions.get(session.id)?.host === session.host && !closed;\n const cleanup = (): void => {\n subscriptions.delete(cleanup);\n sessionCleanups.delete(session.id);\n if (!active()) return;\n void closeSession(session.id).catch((error: unknown) =>\n options.onNotice?.(\n `session ${session.id} cleanup failed (${error instanceof Error ? error.message : String(error)})`,\n ),\n );\n };\n sessionCleanups.set(session.id, cleanup);\n void session.host.runtime.exited.then(cleanup, cleanup);\n subscriptions.add(cleanup);\n registered = true;\n emit({ kind: 'upsert', session: current });\n refreshGit();\n };\n\n const closeSession = async (sessionId: string): Promise<void> => {\n const pending = sessionShutdowns.get(sessionId);\n if (pending !== undefined) {\n await pending.promise;\n return;\n }\n const session = sessions.get(sessionId);\n if (session === undefined) return;\n unregisterSession(sessionId);\n await startSessionShutdown(session);\n };\n\n sessionService = {\n create: async (request) => {\n if (options.createSession === undefined)\n throw new Error('The cockpit session service cannot create sessions in this host.');\n return options.createSession(request);\n },\n close: closeSession,\n isLive: (sessionId) => !closed && sessions.has(sessionId),\n reservations: options.sessionReservations,\n provisionReservedWorktree: async (request) => {\n const parent = sessions.get(request.parentSessionId);\n const provisioner = parent === undefined ? undefined : worktreeProvisionerFor(parent);\n if (provisioner === undefined) throw new Error('Automatic conversation worktree provisioning is unavailable.');\n return provisioner(request);\n },\n registerReservedWorktreeProvisioner: (provisioner) =>\n registerReservedWorktreeProvisioner({ scope: 'global' }, provisioner),\n canCommunicate,\n bindCommunication,\n };\n\n const mountFacets = async (\n facets: readonly (DoomServerFacet | LoadedServerFacet)[],\n suppliedContext?: DoomApiContext,\n ): Promise<void> => {\n if (closed) throw new Error('The headless hub is closed.');\n const context: DoomApiContext = suppliedContext ?? {\n scope: 'global',\n ...(options.hubToken?.() === undefined ? {} : { hubToken: options.hubToken() }),\n sessionService,\n directEvents,\n onNotice: (message) => options.onNotice?.(message),\n };\n if (context.scope === 'session') throw new Error('Session facets belong to their session host.');\n if (context.scope === 'workspace' && (!context.workspaceId || !context.workspaceRoot))\n throw new Error('Workspace mounts require an admitted identity and root.');\n const mount: DoomApiMount =\n context.scope === 'global' ? { scope: 'global' } : { scope: 'workspace', workspaceId: context.workspaceId! };\n const key = mountKey(mount);\n if (mounts.has(key)) throw new Error(`The '${key}' facets are already mounted.`);\n const host = createDoomServerHost({\n scope: context.scope,\n context: {\n ...context,\n sessionService: channelHost('', mount).sessionService,\n directEvents: channelHost('', mount).directEvents,\n receiveChannel: (sessionId, frameType, payload, connectionId) =>\n receiveMountedChannel(mount, sessionId, frameType, payload, connectionId),\n },\n mountChannel: (channel) => {\n const started = startChannel(channel, mount);\n if (started === undefined) return { mounted: false, dispose: () => undefined };\n return { mounted: true, dispose: () => releaseChannel(channel, started) };\n },\n pluginRegistry,\n });\n try {\n const installed = await installServerFacets({ host, facets, onNotice: options.onNotice });\n if (closed) {\n await installed.dispose();\n throw new Error('The headless hub was closed while mounting facets.');\n }\n mounts.set(key, { host, installed });\n if (mount.scope === 'workspace') {\n const workspace = { id: mount.workspaceId, root: context.workspaceRoot!, available: true };\n workspaces.set(mount.workspaceId, workspace);\n emit({ kind: 'workspace_upsert', workspace });\n }\n } catch (error) {\n host.dispose();\n throw error;\n }\n };\n\n return {\n sessionService,\n directEvents,\n ...(options.computerUse === undefined ? {} : { computerUse: options.computerUse }),\n snapshot: () => [...sessions.values()].map(present),\n session: (sessionId) => {\n const session = sessions.get(sessionId);\n return session === undefined ? undefined : present(session);\n },\n setPendingSessionSetups(sessionId, pending) {\n if (JSON.stringify(pendingSetups.get(sessionId) ?? []) === JSON.stringify(pending)) return;\n pendingSetups.set(sessionId, Object.freeze([...pending]));\n const session = sessions.get(sessionId);\n if (session) emit({ kind: 'upsert', session: present(session) });\n },\n runtime: (sessionId) => sessions.get(sessionId)?.host.runtime,\n sessionAvatar(sessionId) {\n const match = ICON_DATA_URL.exec(avatarIcons.get(sessionId) ?? '');\n const mimeType = match?.[1];\n if (match === null || !(PROFILE_ICON_MIME_TYPES as readonly string[]).includes(mimeType ?? '')) return undefined;\n return { mimeType: mimeType!, bytes: Buffer.from(match[2]!, 'base64') };\n },\n onEvent(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n register,\n notifySessionRemoved(sessionId) {\n emit({ kind: 'removed', sessionId });\n },\n mountFacets,\n workspaces: () => [...workspaces.values()],\n registerWorkspace(workspace) {\n workspaces.set(workspace.id, workspace);\n emit({ kind: 'workspace_upsert', workspace });\n },\n admitWorkspace: async (request) => {\n if (!options.admitWorkspace) throw new Error('Workspace admission is unavailable.');\n return options.admitWorkspace(request);\n },\n async removeWorkspace(id) {\n if ([...sessions.values()].some((session) => session.workspaceId === id))\n throw new Error('Workspace still has live sessions.');\n const key = mountKey({ scope: 'workspace', workspaceId: id });\n const mounted = mounts.get(key);\n if (!workspaces.has(id)) return;\n options.onWorkspaceRemoved?.(id);\n workspaces.delete(id);\n emit({ kind: 'workspace_removed', workspaceId: id });\n if (!mounted) return;\n mounts.delete(key);\n try {\n await mounted.installed.dispose();\n } finally {\n mounted.host.dispose();\n }\n },\n async requestApi(mount, basePath, request) {\n if (closed) return Response.json({ error: 'Server is closed.' }, { status: 503 });\n if (mount.scope === 'session') {\n const session = sessions.get(mount.sessionId);\n if (!session || !options.requestSessionApi)\n return Response.json({ error: 'Session not found.' }, { status: 404 });\n const url = new URL(request.url);\n return options.requestSessionApi(scopeOf(session), {\n basePath,\n path: `${url.pathname}${url.search}`,\n method: request.method,\n headers: request.headers,\n signal: request.signal,\n body: request.body === null ? undefined : new Uint8Array(await request.arrayBuffer()),\n });\n }\n const handler = mounts.get(mountKey(mount))?.host.handlerFor(basePath);\n if (!handler)\n return Response.json({ error: `API '${basePath}' is not mounted in ${mountKey(mount)}.` }, { status: 404 });\n return handler.fetch(request);\n },\n pluginRegistry,\n invokePlugin: (call, direction, caller) => pluginRegistry.invoke(call, direction, caller),\n async create(sessionOptions) {\n const id = sessionOptions.sessionId;\n if (closed) throw new Error('The headless hub is closed.');\n // Checked before building the host: once it exists, a refusal has to close it, and a\n // duplicate id would then close the live session that already owns the id.\n if (sessions.has(id)) throw new Error(`Session '${id}' is already registered.`);\n if (sessionShutdowns.get(id)?.pending) throw new Error(`Session '${id}' is still shutting down.`);\n const host = await options.manager.create(sessionOptions);\n const settledAt = await lastSettledAt(host);\n const session: HeadlessHubSession = {\n id: sessionOptions.sessionId,\n workspaceId: sessionOptions.workspaceId,\n webComposition: sessionOptions.webComposition,\n name: sessionOptions.sessionName,\n cwd: sessionOptions.cwd,\n createdAt: new Date().toISOString(),\n ...(sessionOptions.parentSessionId === undefined ? {} : { parentSessionId: sessionOptions.parentSessionId }),\n ...(sessionOptions.sessionProvenance === undefined\n ? {}\n : { sessionProvenance: sessionOptions.sessionProvenance }),\n environment: sessionOptions.environment,\n ...(settledAt === undefined ? {} : { everPrompted: true, lastSettledAt: settledAt }),\n host,\n };\n try {\n register(session);\n } catch (error) {\n // The hub closed, or a racing create took the id, while this host was starting.\n await Promise.resolve(options.manager.closeSession(id)).catch((closeError: unknown) =>\n options.onNotice?.(`Session '${id}' could not be closed after a failed registration: ${String(closeError)}`),\n );\n throw error;\n }\n return session;\n },\n closeSession,\n async requestSessionApi(scope, request) {\n if (closed || !sessions.has(scope.sessionId))\n return Response.json({ error: 'Session not found.' }, { status: 404 });\n if (options.requestSessionApi === undefined)\n return Response.json({ error: 'Session API unavailable.' }, { status: 404 });\n return options.requestSessionApi(scope, request);\n },\n registerChannel(channel, mount = { scope: 'global' }) {\n const started = startChannel(channel, mount);\n if (started === undefined) return () => undefined;\n return () => releaseChannel(channel, started);\n },\n channelTypes: () => [...new Set([...channels.values()].map(({ channel }) => channel.frameType))],\n channelFrames(sessionId) {\n const session = sessions.get(sessionId);\n if (session === undefined) return [];\n const scope = scopeOf(session);\n const frames: Array<{ type: string; sessionId: string; payload: unknown }> = [];\n for (const { channel, source } of selectedChannels(session)) {\n const type = channel.frameType;\n const payload = source.payloadFor(scope);\n if (payload !== undefined) frames.push({ type, sessionId, payload });\n }\n return frames;\n },\n threadJournal(sessionId, threadId) {\n const session = sessions.get(sessionId);\n if (session === undefined) return undefined;\n const scope = scopeOf(session);\n for (const { source } of selectedChannels(session)) {\n const journal = source.threadJournal?.(scope, threadId);\n if (journal !== undefined) return journal;\n }\n return undefined;\n },\n receiveChannel(sessionId, frameType, payload, connectionId, desktopAuthorized = false) {\n if (connectionId === '') return;\n const session = sessions.get(sessionId);\n const started = session && selectedChannels(session).find(({ channel }) => channel.frameType === frameType);\n if (session === undefined || started === undefined) return;\n started.channel.receive?.(scopeOf(session), payload, {\n connectionId,\n ...(desktopAuthorized ? { desktopAuthorized: true } : {}),\n } satisfies DoomHubChannelConnection);\n },\n disconnectChannels(connectionId) {\n if (connectionId === '') return;\n for (const { channel } of channels.values()) channel.disconnected?.({ connectionId });\n },\n async close() {\n if (closed) return;\n const ids = [...sessions.keys()];\n closed = true;\n directEvents.close();\n for (const unsubscribe of subscriptions) unsubscribe();\n subscriptions.clear();\n for (const cleanup of presentationCleanups.values()) cleanup();\n presentationCleanups.clear();\n const failures: unknown[] = [];\n for (const mounted of [...mounts.values()].reverse()) {\n try {\n await mounted.installed.dispose();\n } catch (error) {\n failures.push(error);\n }\n try {\n mounted.host.dispose();\n } catch (error) {\n failures.push(error);\n }\n }\n mounts.clear();\n pluginRegistry.dispose();\n workspaces.clear();\n for (const { source } of channels.values()) source.close();\n channels.clear();\n listeners.clear();\n const shutdowns: Promise<void>[] = [...sessionShutdowns.values()]\n .filter(({ pending }) => pending)\n .map(({ promise }) => promise);\n for (const id of ids) {\n const session = sessions.get(id);\n if (session !== undefined && !sessionShutdowns.has(id)) shutdowns.push(startSessionShutdown(session));\n }\n sessions.clear();\n const outcomes = await Promise.allSettled(shutdowns);\n for (const outcome of outcomes) if (outcome.status === 'rejected') failures.push(outcome.reason);\n if (failures.length) throw new AggregateError(failures, 'Mount shutdown failed');\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"headlessHub.cjs","names":["AGENT_SETTLED_ENTRY_TYPE","BACKGROUND_CONTEXT","createDoomPluginRegistry","readSessionGitStatus","crypto","createDoomServerHost","installServerFacets","PROFILE_ICON_MIME_TYPES"],"sources":["../src/server/headlessHub.ts"],"sourcesContent":["import crypto from 'node:crypto';\n\nimport { BACKGROUND_CONTEXT } from '@earendil-works/pi-agent-core/harness/context';\n\nimport type {\n DoomComputerUseHostBinding,\n DoomDirectEventBus,\n DoomHubChannel,\n DoomHubChannelConnection,\n DoomHubChannelHost,\n DoomHubReservedWorktreeProvisioner,\n DoomHubSessionApiRequest,\n DoomHubSessionCreateRequest,\n DoomHubSessionScope,\n DoomHubSessionService,\n DoomSessionCommunicationEndpoint,\n DoomHubSessionReservations,\n DoomPendingSessionSetup,\n} from '../exports/hubChannel';\nimport type { DoomWebComposition } from '../exports/packageApi';\nimport type { DoomApiContext, DoomApiMount } from '../exports/packageApi';\nimport {\n createDoomPluginRegistry,\n type DoomPluginCaller,\n type DoomPluginDirection,\n type DoomPluginRegistry,\n} from '../exports/pluginProtocol';\nimport {\n createDoomServerHost,\n installServerFacets,\n type DoomServerFacet,\n type DoomServerHost,\n type InstalledServerFacets,\n type LoadedServerFacet,\n} from '../exports/serverFacet';\nimport { PROFILE_ICON_MIME_TYPES } from '../schemas/profileIdentity';\nimport { readSessionGitStatus } from '../services/sessionGitStatus';\nimport type { SessionGitStatus } from '../services/sessionGitStatus/type';\nimport type { HeadlessSessionHost, HeadlessSessionHostOptions } from '../systems/main/types/headlessSessionHost';\nimport type { HeadlessSessionCloseOptions, HeadlessSessionManager } from '../systems/main/types/headlessSessionManager';\nimport { AGENT_SETTLED_ENTRY_TYPE } from './directHarnessRuntime';\n\nconst AUTHORIZATION_HEADER = 'authorization';\nconst CHANNEL_PRIORITY = { global: 0, workspace: 1, session: 2 } as const;\nconst ICON_VERSION_LENGTH = 12;\nconst ICON_DATA_URL = /^data:([a-z/+.-]+);base64,([A-Za-z0-9+/=]+)$/u;\n\n/** The profile a session runs under, as shown on its rail card. The icon itself is served separately. */\nexport interface HeadlessSessionProfile {\n readonly name: string;\n readonly displayName?: string;\n /** Changes whenever the icon does, so a client can cache the avatar by it. */\n readonly iconVersion?: string;\n}\n\n/** A profile's persona presentation: display name and icon as a data: URL. */\nexport interface HeadlessProfileIdentity {\n readonly displayName?: string;\n readonly icon?: string;\n}\n\nexport interface HeadlessHubSession {\n readonly id: string;\n readonly workspaceId?: string;\n webComposition?: DoomWebComposition;\n readonly name: string;\n readonly cwd: string;\n readonly createdAt: string;\n readonly parentSessionId?: string;\n readonly sessionProvenance?: string;\n readonly updatedAt?: string;\n readonly phase?: 'idle' | 'turn' | 'compaction' | 'retry';\n readonly phaseSince?: string;\n readonly pendingMessageCount?: number;\n readonly everPrompted?: boolean;\n readonly awaitingInput?: boolean;\n readonly lastSettledAt?: string;\n /** Branch and dirty flag of the session cwd; absent outside a git work tree. */\n readonly git?: SessionGitStatus;\n /** Selected profile; absent when the session runs without one. */\n readonly profile?: HeadlessSessionProfile;\n readonly pendingSetups?: readonly DoomPendingSessionSetup[];\n /** Environment admitted for this session, when supplied by the host. */\n readonly environment?: Readonly<Record<string, string | undefined>>;\n readonly host: HeadlessSessionHost;\n}\n\nexport interface HeadlessWorkspace {\n readonly id: string;\n readonly root: string;\n readonly name?: string;\n readonly available?: boolean;\n}\n\n/** A workspace to admit: an existing folder, or a name alone for a new default folder. */\nexport interface HeadlessWorkspaceAdmission {\n readonly root?: string;\n readonly name?: string;\n}\n\nexport type HeadlessHubEvent =\n | { kind: 'upsert'; session: HeadlessHubSession }\n /** `dormant`: the session stopped running but keeps its record, so it lists as dormant. */\n | { kind: 'removed'; sessionId: string; dormant?: true }\n | { kind: 'workspace_upsert'; workspace: HeadlessWorkspace }\n | { kind: 'workspace_removed'; workspaceId: string }\n | { kind: 'channel'; frameType: string; sessionId: string; payload: unknown; connectionId?: string };\n\nexport interface HeadlessHubOptions {\n manager: HeadlessSessionManager;\n /** Resolves the hub credential, because the server reads its token file after the hub exists. */\n hubToken?: () => string | undefined;\n /** Creates a session through the canonical cockpit lifecycle for hub channels. */\n createSession?: (\n request: DoomHubSessionCreateRequest,\n /**\n * Set when a workspace-mounted package creates a session with no parent. The host must\n * refuse a cwd outside that workspace before admitting anything.\n */\n placement?: { workspaceId: string },\n ) => Promise<DoomHubSessionScope>;\n onNotice?: (message: string) => void;\n sessionReservations?: DoomHubSessionReservations;\n requestSessionApi?: (scope: DoomHubSessionScope, request: DoomHubSessionApiRequest) => Promise<Response>;\n computerUse?: DoomComputerUseHostBinding;\n admitWorkspace?: (request: HeadlessWorkspaceAdmission) => Promise<HeadlessWorkspace>;\n onWorkspaceRemoved?: (workspaceId: string) => void;\n /** Reads a session cwd's git status; defaults to the git CLI. */\n readGitStatus?: (cwd: string) => Promise<SessionGitStatus | undefined>;\n /** Resolves a profile's persona presentation for a workspace root; sessions show no avatar without it. */\n resolveProfileIdentity?: (root: string, profile: string) => HeadlessProfileIdentity | undefined;\n}\n\nexport interface HeadlessHub {\n /** Canonical direct lifecycle shared by hub channels, APIs, and sessions. */\n readonly sessionService: DoomHubSessionService;\n /** Lifecycle-owned event path shared by session facets and hub channels. */\n readonly directEvents: DoomDirectEventBus;\n readonly computerUse?: DoomComputerUseHostBinding;\n snapshot(): readonly HeadlessHubSession[];\n session(sessionId: string): HeadlessHubSession | undefined;\n runtime(sessionId: string): HeadlessSessionHost['runtime'] | undefined;\n /** The session's profile avatar image, when its profile has an icon. */\n sessionAvatar(sessionId: string): { mimeType: string; bytes: Uint8Array } | undefined;\n onEvent(listener: (event: HeadlessHubEvent) => void): () => void;\n register(session: HeadlessHubSession): void;\n create(options: HeadlessSessionHostOptions): Promise<HeadlessHubSession>;\n closeSession(sessionId: string, options?: HeadlessSessionCloseOptions): Promise<void>;\n /** Publishes removal of a persisted session that has no live host. */\n notifySessionRemoved(sessionId: string): void;\n /** Publishes setup-only children through the parent's normal session updates. */\n setPendingSessionSetups?(sessionId: string, pending: readonly DoomPendingSessionSetup[]): void;\n /** Dispatches an authenticated package API request inside the owning process. */\n requestSessionApi(scope: DoomHubSessionScope, request: DoomHubSessionApiRequest): Promise<Response>;\n /** Mounts the selected hub facets once, before the headless server accepts clients. */\n mountFacets(facets: readonly (DoomServerFacet | LoadedServerFacet)[], context?: DoomApiContext): Promise<void>;\n workspaces(): readonly HeadlessWorkspace[];\n registerWorkspace(workspace: HeadlessWorkspace): void;\n admitWorkspace(request: HeadlessWorkspaceAdmission): Promise<HeadlessWorkspace>;\n removeWorkspace(workspaceId: string): Promise<void>;\n requestApi(mount: DoomApiMount, basePath: string, request: Request): Promise<Response>;\n /** The dispatch table hub facets mount into, shared with session hosts this hub serves. */\n readonly pluginRegistry: DoomPluginRegistry;\n invokePlugin(call: unknown, direction: DoomPluginDirection, caller?: DoomPluginCaller): Promise<unknown>;\n registerChannel(channel: DoomHubChannel, mount?: DoomApiMount): () => void;\n channelTypes(): readonly string[];\n channelFrames(sessionId: string): Array<{ type: string; sessionId: string; payload: unknown }>;\n /** Resolves a channel-owned child journal for the browser thread projection. */\n threadJournal(sessionId: string, threadId: string): string | undefined;\n receiveChannel(\n sessionId: string,\n frameType: string,\n payload: unknown,\n connectionId: string,\n desktopAuthorized?: boolean,\n ): void;\n disconnectChannels(connectionId: string): void;\n close(): Promise<void>;\n}\n\ninterface StartedChannel {\n readonly channel: DoomHubChannel;\n readonly source: ReturnType<DoomHubChannel['start']>;\n readonly mount: DoomApiMount;\n}\n\n/**\n * When a reopened session last settled, from the marker its runtime journals at every run end.\n * Without it a session with a whole history would read as fresh until its next turn.\n */\nasync function lastSettledAt(host: HeadlessSessionHost): Promise<string | undefined> {\n try {\n const entry = await host.runtime.lane.findEntry(\n { type: 'custom', customType: AGENT_SETTLED_ENTRY_TYPE, order: 'newestFirst' },\n BACKGROUND_CONTEXT,\n );\n const data = entry?.type === 'custom' ? (entry.data as { timestamp?: unknown } | undefined) : undefined;\n return typeof data?.timestamp === 'number' && Number.isFinite(data.timestamp)\n ? new Date(data.timestamp).toISOString()\n : undefined;\n } catch {\n // A runtime without a readable journal has no history to report; the rail shows it as fresh.\n return undefined;\n }\n}\n\nfunction scopeOf(session: HeadlessHubSession): DoomHubSessionScope {\n return {\n sessionId: session.id,\n ...(session.workspaceId === undefined ? {} : { workspaceId: session.workspaceId }),\n cwd: session.cwd,\n ...(session.environment === undefined ? {} : { environment: session.environment }),\n ...(session.host.sessionContext === undefined ? {} : { sessionContext: session.host.sessionContext }),\n };\n}\n\n/** Owns direct, same-process session and channel state for presentation adapters. */\nexport function createHeadlessHub(options: HeadlessHubOptions): HeadlessHub {\n const sessions = new Map<string, HeadlessHubSession>();\n const pendingSetups = new Map<string, readonly DoomPendingSessionSetup[]>();\n const present = (session: HeadlessHubSession): HeadlessHubSession =>\n pendingSetups.has(session.id) ? { ...session, pendingSetups: pendingSetups.get(session.id) } : session;\n const pluginRegistry = createDoomPluginRegistry();\n const channels = new Map<string, StartedChannel>();\n const listeners = new Set<(event: HeadlessHubEvent) => void>();\n const subscriptions = new Set<() => void>();\n const sessionCleanups = new Map<string, () => void>();\n const presentationCleanups = new Map<string, () => void>();\n /** Profile icons by session, kept out of summaries because each can be hundreds of kilobytes. */\n const avatarIcons = new Map<string, string>();\n const sessionShutdowns = new Map<string, { session: HeadlessHubSession; promise: Promise<void>; pending: boolean }>();\n const directEventListeners = new Map<string, Set<(payload: unknown) => void>>();\n const directEventLatest = new Map<string, unknown>();\n const maxDirectEventLatest = 4096;\n const communicationEndpoints = new Map<\n string,\n {\n listeners: Map<string, Set<(sourceSessionId: string, payload: unknown) => void>>;\n readyListeners: Set<(peerSessionId: string) => void>;\n ready: boolean;\n endpoint: DoomSessionCommunicationEndpoint;\n }\n >();\n let closed = false;\n const mounts = new Map<string, { host: DoomServerHost; installed: InstalledServerFacets }>();\n const workspaces = new Map<string, HeadlessWorkspace>();\n const worktreeProvisioners = new Map<\n string,\n { mount: DoomApiMount; provisioner: DoomHubReservedWorktreeProvisioner }\n >();\n let sessionService: DoomHubSessionService;\n\n const mountKey = (mount: DoomApiMount): string =>\n mount.scope === 'global'\n ? 'global'\n : mount.scope === 'workspace'\n ? `workspace:${mount.workspaceId}`\n : `session:${mount.sessionId}`;\n const belongs = (mount: DoomApiMount, session: HeadlessHubSession): boolean =>\n mount.scope === 'global' ||\n (mount.scope === 'workspace' ? session.workspaceId === mount.workspaceId : session.id === mount.sessionId);\n const canClose = (mount: DoomApiMount, session: HeadlessHubSession): boolean =>\n belongs(mount, session) || (mount.scope === 'session' && session.parentSessionId === mount.sessionId);\n const canCommunicate = (sourceId: string, targetId: string): boolean => {\n if (closed || sourceId === targetId) return false;\n const source = sessions.get(sourceId);\n const target = sessions.get(targetId);\n return (\n source !== undefined &&\n target !== undefined &&\n (source.parentSessionId === target.id || target.parentSessionId === source.id)\n );\n };\n const registerReservedWorktreeProvisioner = (\n mount: DoomApiMount,\n provisioner: DoomHubReservedWorktreeProvisioner,\n ): (() => void) => {\n const key = mountKey(mount);\n if (worktreeProvisioners.has(key)) throw new Error('A worktree provisioner is already registered.');\n worktreeProvisioners.set(key, { mount, provisioner });\n return () => {\n if (worktreeProvisioners.get(key)?.provisioner === provisioner) worktreeProvisioners.delete(key);\n };\n };\n const worktreeProvisionerFor = (parent: HeadlessHubSession): DoomHubReservedWorktreeProvisioner | undefined => {\n let provisioner: DoomHubReservedWorktreeProvisioner | undefined;\n let priority = -1;\n for (const { mount, provisioner: candidate } of worktreeProvisioners.values()) {\n if (!belongs(mount, parent) || CHANNEL_PRIORITY[mount.scope] <= priority) continue;\n provisioner = candidate;\n priority = CHANNEL_PRIORITY[mount.scope];\n }\n return provisioner;\n };\n const selectedChannels = (session: HeadlessHubSession): StartedChannel[] => {\n const selected = new Map<string, StartedChannel>();\n for (const started of channels.values()) {\n if (!belongs(started.mount, session)) continue;\n const previous = selected.get(started.channel.frameType);\n if (!previous || CHANNEL_PRIORITY[started.mount.scope] > CHANNEL_PRIORITY[previous.mount.scope])\n selected.set(started.channel.frameType, started);\n }\n return [...selected.values()];\n };\n const selectedChannel = (session: HeadlessHubSession, frameType: string): StartedChannel | undefined =>\n selectedChannels(session).find((started) => started.channel.frameType === frameType);\n /**\n * One channel serves a session: the narrowest mount that reaches it. A package that\n * registers the same channel globally and per workspace would otherwise run two\n * sources against one session, and only the narrower one can publish or receive.\n *\n * Compared by mount scope rather than identity so a channel being started already\n * knows which sessions it is taking over.\n */\n const serves = (mount: DoomApiMount, frameType: string, session: HeadlessHubSession): boolean => {\n if (!belongs(mount, session)) return false;\n const current = selectedChannel(session, frameType);\n return current === undefined || CHANNEL_PRIORITY[mount.scope] >= CHANNEL_PRIORITY[current.mount.scope];\n };\n\n const directEventKey = (frameType: string, sessionId: string): string => `${frameType}\\0${sessionId}`;\n const directEvents: DoomDirectEventBus = {\n publish(frameType, sessionId, payload) {\n if (closed) return;\n const key = directEventKey(frameType, sessionId);\n directEventLatest.set(key, payload);\n while (directEventLatest.size > maxDirectEventLatest) {\n const oldest = directEventLatest.keys().next().value;\n if (oldest === undefined) break;\n directEventLatest.delete(oldest);\n }\n const listeners = directEventListeners.get(key);\n if (listeners === undefined) return;\n for (const listener of listeners) listener(payload);\n },\n subscribe(frameType, sessionId, listener, options) {\n if (closed) return () => undefined;\n const key = directEventKey(frameType, sessionId);\n const listeners = directEventListeners.get(key) ?? new Set<(payload: unknown) => void>();\n listeners.add(listener);\n directEventListeners.set(key, listeners);\n if (options?.replayLatest && directEventLatest.has(key)) listener(directEventLatest.get(key));\n let released = false;\n return () => {\n if (released) return;\n released = true;\n listeners.delete(listener);\n if (listeners.size === 0) directEventListeners.delete(key);\n };\n },\n clearSession(sessionId) {\n const suffix = `\\0${sessionId}`;\n for (const key of directEventLatest.keys()) if (key.endsWith(suffix)) directEventLatest.delete(key);\n },\n close() {\n directEventListeners.clear();\n directEventLatest.clear();\n },\n };\n\n const announceCommunicationReady = (sessionId: string): void => {\n const current = communicationEndpoints.get(sessionId);\n if (current?.ready !== true) return;\n for (const [peerSessionId, peer] of communicationEndpoints) {\n if (!peer.ready || !canCommunicate(sessionId, peerSessionId)) continue;\n for (const listener of current.readyListeners) listener(peerSessionId);\n for (const listener of peer.readyListeners) listener(sessionId);\n }\n };\n\n const bindCommunication = (sessionId: string): DoomSessionCommunicationEndpoint => {\n const existing = communicationEndpoints.get(sessionId);\n if (existing !== undefined) return existing.endpoint;\n const listeners = new Map<string, Set<(sourceSessionId: string, payload: unknown) => void>>();\n const readyListeners = new Set<(peerSessionId: string) => void>();\n let endpoint!: DoomSessionCommunicationEndpoint;\n const record = {\n listeners,\n readyListeners,\n ready: false,\n get endpoint() {\n return endpoint;\n },\n };\n endpoint = {\n sessionId,\n publish(targetSessionId, type, payload) {\n if (!canCommunicate(sessionId, targetSessionId)) return false;\n const target = communicationEndpoints.get(targetSessionId);\n if (target?.ready !== true) return false;\n for (const listener of target.listeners.get(type) ?? []) listener(sessionId, payload);\n return true;\n },\n subscribe(type, listener) {\n const current = listeners.get(type) ?? new Set<(sourceSessionId: string, payload: unknown) => void>();\n current.add(listener);\n listeners.set(type, current);\n return () => {\n current.delete(listener);\n if (current.size === 0) listeners.delete(type);\n };\n },\n onPeerReady(listener) {\n readyListeners.add(listener);\n if (!record.ready) {\n record.ready = true;\n announceCommunicationReady(sessionId);\n }\n return () => readyListeners.delete(listener);\n },\n close() {\n if (communicationEndpoints.get(sessionId)?.endpoint !== endpoint) return;\n communicationEndpoints.delete(sessionId);\n listeners.clear();\n readyListeners.clear();\n },\n };\n communicationEndpoints.set(sessionId, record);\n return endpoint;\n };\n\n const queuedEvents: HeadlessHubEvent[] = [];\n let publishing = false;\n const emit = (event: HeadlessHubEvent): void => {\n if (closed) return;\n queuedEvents.push(event.kind === 'upsert' ? { ...event, session: present(event.session) } : event);\n if (publishing) return;\n publishing = true;\n try {\n for (let index = 0; index < queuedEvents.length; index += 1)\n for (const listener of listeners) listener(queuedEvents[index]);\n } finally {\n queuedEvents.length = 0;\n publishing = false;\n }\n };\n const sessionScopes = (): readonly DoomHubSessionScope[] => [...sessions.values()].map(scopeOf);\n const publish = (frameType: string, sessionId: string, payload: unknown, connectionId?: string): void => {\n if (!sessions.has(sessionId)) return;\n emit({ kind: 'channel', frameType, sessionId, payload, ...(connectionId === undefined ? {} : { connectionId }) });\n };\n\n /**\n * Hub-owned calls carry the hub credential, so a session API can tell them from a\n * browser call it must not trust. Browser traffic reaches a session API through\n * requestApi instead, and keeps the headers its caller sent.\n */\n const hubAuthenticated = (request: DoomHubSessionApiRequest): DoomHubSessionApiRequest => {\n const token = options.hubToken?.();\n if (token === undefined || token === '') return request;\n const headers = new Headers(request.headers);\n if (headers.has(AUTHORIZATION_HEADER)) return request;\n headers.set(AUTHORIZATION_HEADER, `Bearer ${token}`);\n return { ...request, headers };\n };\n\n const reservationsFor = (mount: DoomApiMount): DoomHubSessionReservations | undefined => {\n const reservations = options.sessionReservations;\n if (!reservations) return undefined;\n const check = (parentId: string): void => {\n const parent = sessions.get(parentId);\n if (!parent || !belongs(mount, parent)) throw new Error('Parent session is outside this mount.');\n };\n return {\n read: (id, parentId) => {\n check(parentId);\n return reservations.read(id, parentId);\n },\n prepare: (id, parentId, cwd) => {\n check(parentId);\n return reservations.prepare(id, parentId, cwd);\n },\n complete: (id, parentId) => {\n check(parentId);\n return reservations.complete(id, parentId);\n },\n };\n };\n const channelHost = (frameType: string, mount: DoomApiMount): DoomHubChannelHost => ({\n sessions: () =>\n sessionScopes().filter((scope) => {\n const session = sessions.get(scope.sessionId);\n return session !== undefined && serves(mount, frameType, session);\n }),\n sessionService: {\n reservations: reservationsFor(mount),\n create: async (request) => {\n // A workspace package may start a top-level session, placed in its own workspace.\n if (mount.scope === 'workspace' && request.parentSessionId === undefined) {\n if (request.reservationId !== undefined) throw new Error('A reserved session requires its owning parent.');\n if (options.createSession === undefined)\n throw new Error('The cockpit session service cannot create sessions in this host.');\n return options.createSession(request, { workspaceId: mount.workspaceId });\n }\n if (mount.scope !== 'global') {\n const parent = request.parentSessionId === undefined ? undefined : sessions.get(request.parentSessionId);\n if (!parent || !belongs(mount, parent)) throw new Error('Parent session is outside this mount.');\n }\n return sessionService.create(request);\n },\n close: async (id) => {\n const session = sessions.get(id);\n if (session === undefined) {\n const shutdown = sessionShutdowns.get(id);\n if (shutdown !== undefined) {\n if (!canClose(mount, shutdown.session)) throw new Error('Session is outside this mount.');\n await shutdown.promise;\n }\n return;\n }\n if (!canClose(mount, session)) throw new Error('Session is outside this mount.');\n await sessionService.close(id);\n },\n isLive: (id) => sessionService.isLive(id),\n registerReservedWorktreeProvisioner: (provisioner) => registerReservedWorktreeProvisioner(mount, provisioner),\n canCommunicate: (sourceId, targetId) => {\n const source = sessions.get(sourceId);\n return (\n source !== undefined && belongs(mount, source) && sessionService.canCommunicate?.(sourceId, targetId) === true\n );\n },\n },\n directEvents: {\n publish: (type, id, payload) => {\n const session = sessions.get(id);\n if (session && serves(mount, frameType, session)) directEvents.publish(type, id, payload);\n },\n subscribe: (type, id, listener, subscriptionOptions) => {\n const session = sessions.get(id);\n return session && serves(mount, frameType, session)\n ? directEvents.subscribe(type, id, listener, subscriptionOptions)\n : () => undefined;\n },\n close: () => undefined,\n },\n publish: (sessionId, payload) => {\n const session = sessions.get(sessionId);\n if (session && serves(mount, frameType, session)) publish(frameType, sessionId, payload);\n },\n publishToConnection: (connectionId, sessionId, payload) => {\n const session = sessions.get(sessionId);\n if (connectionId === '' || !session || !serves(mount, frameType, session)) return false;\n publish(frameType, sessionId, payload, connectionId);\n return true;\n },\n requestSessionApi: async (scope, request) => {\n const session = sessions.get(scope.sessionId);\n if (!session || !belongs(mount, session)) return Response.json({ error: 'Session not found.' }, { status: 404 });\n if (options.requestSessionApi === undefined)\n return Response.json({ error: 'Session API unavailable.' }, { status: 404 });\n return options.requestSessionApi(scope, hubAuthenticated(request));\n },\n ...(options.computerUse === undefined\n ? {}\n : {\n computerUse: {\n get available() {\n return options.computerUse!.available;\n },\n request(scope, request) {\n const session = sessions.get(scope.sessionId);\n if (!session || !belongs(mount, session)) throw new Error('Computer-use session is outside this mount.');\n return options.computerUse!.request(scopeOf(session), request);\n },\n },\n }),\n onNotice: (message) => options.onNotice?.(message),\n });\n\n const startChannel = (channel: DoomHubChannel, mount: DoomApiMount): StartedChannel | undefined => {\n const key = `${mountKey(mount)}:${channel.frameType}`;\n if (channels.has(key)) {\n options.onNotice?.(`hub channel '${channel.frameType}' is already registered`);\n return undefined;\n }\n try {\n const displaced = new Map<string, StartedChannel>();\n for (const session of sessions.values()) {\n if (!belongs(mount, session)) continue;\n const previous = selectedChannel(session, channel.frameType);\n if (previous !== undefined) displaced.set(session.id, previous);\n }\n const source = channel.start(channelHost(channel.frameType, mount));\n const started = { channel, source, mount };\n channels.set(key, started);\n for (const session of sessions.values()) {\n if (selectedChannel(session, channel.frameType) !== started) continue;\n displaced.get(session.id)?.source.sessionRemoved?.(session.id);\n source.sessionAdded?.(scopeOf(session));\n }\n return started;\n } catch (error) {\n options.onNotice?.(`hub channel '${channel.frameType}' failed (${String(error)})`);\n return undefined;\n }\n };\n\n const releaseChannel = (channel: DoomHubChannel, started: StartedChannel): void => {\n const key = `${mountKey(started.mount)}:${channel.frameType}`;\n if (channels.get(key) !== started) return;\n const served = [...sessions.values()].filter((session) => selectedChannel(session, channel.frameType) === started);\n channels.delete(key);\n started.source.close();\n for (const session of served) selectedChannel(session, channel.frameType)?.source.sessionAdded?.(scopeOf(session));\n };\n\n const receiveMountedChannel = (\n mount: DoomApiMount,\n sessionId: string,\n frameType: string,\n payload: unknown,\n connectionId: string,\n ): boolean => {\n if (!connectionId) return false;\n const session = sessions.get(sessionId);\n if (!session || !belongs(mount, session)) return false;\n const started = channels.get(`${mountKey(mount)}:${frameType}`);\n if (!started?.channel.receive) return false;\n started.channel.receive(scopeOf(session), payload, { connectionId });\n return true;\n };\n\n const unregisterSession = (sessionId: string, options?: HeadlessSessionCloseOptions): void => {\n const current = sessions.get(sessionId);\n if (current === undefined) return;\n const cleanup = sessionCleanups.get(sessionId);\n if (cleanup !== undefined) subscriptions.delete(cleanup);\n communicationEndpoints.get(sessionId)?.endpoint.close();\n // Let scoped sources dispatch their final stop while the live-session boundary still admits it.\n for (const { source } of selectedChannels(current)) source.sessionRemoved?.(sessionId);\n sessions.delete(sessionId);\n sessionCleanups.delete(sessionId);\n presentationCleanups.get(sessionId)?.();\n presentationCleanups.delete(sessionId);\n avatarIcons.delete(sessionId);\n directEvents.clearSession?.(sessionId);\n emit({ kind: 'removed', sessionId, ...(options?.keepDormant === true ? { dormant: true as const } : {}) });\n };\n\n const startSessionShutdown = (\n session: HeadlessHubSession,\n closeOptions?: HeadlessSessionCloseOptions,\n ): Promise<void> => {\n const existing = sessionShutdowns.get(session.id);\n if (existing !== undefined) return existing.promise;\n const shutdown = { session, promise: Promise.resolve(), pending: true };\n const promise = Promise.resolve().then(() =>\n closeOptions === undefined\n ? options.manager.closeSession(session.id)\n : options.manager.closeSession(session.id, closeOptions),\n );\n shutdown.promise = promise;\n sessionShutdowns.set(session.id, shutdown);\n void promise.then(\n () => {\n shutdown.pending = false;\n if (sessionShutdowns.get(session.id) === shutdown) sessionShutdowns.delete(session.id);\n },\n () => {\n shutdown.pending = false;\n },\n );\n return promise;\n };\n\n const register = (session: HeadlessHubSession): void => {\n if (closed) throw new Error('The headless hub is closed.');\n if (sessions.has(session.id)) throw new Error(`Session '${session.id}' is already registered.`);\n if (sessionShutdowns.get(session.id)?.pending) throw new Error(`Session '${session.id}' is still shutting down.`);\n // A failed shutdown stays visible to scoped closers until the id is reused. Reopening\n // the session is that reuse, so it must not be blocked by the stale failure.\n sessionShutdowns.delete(session.id);\n let current: HeadlessHubSession = {\n ...session,\n updatedAt: session.updatedAt ?? session.createdAt,\n phase: session.phase ?? 'idle',\n phaseSince: session.phaseSince ?? session.createdAt,\n pendingMessageCount: session.pendingMessageCount ?? 0,\n everPrompted: session.everPrompted ?? false,\n awaitingInput: session.awaitingInput ?? false,\n };\n sessions.set(session.id, current);\n announceCommunicationReady(session.id);\n // Branch changes happen inside runs, so the status is read at start and after each settle.\n const refreshGit = (): void => {\n void (options.readGitStatus ?? readSessionGitStatus)(session.cwd).then(\n (git) => {\n if (closed || sessions.get(session.id)?.host !== session.host) return;\n if (git?.branch === current.git?.branch && git?.dirty === current.git?.dirty) return;\n const { git: _previous, ...rest } = current;\n current = git === undefined ? rest : { ...rest, git };\n sessions.set(session.id, current);\n emit({ kind: 'upsert', session: current });\n },\n (error: unknown) =>\n options.onNotice?.(\n `session ${session.id} git status failed (${error instanceof Error ? error.message : String(error)})`,\n ),\n );\n };\n const stopPresentation = session.host.onPresentationFrame((frame) => {\n if (sessions.get(session.id)?.host !== session.host) return;\n const type = typeof frame.type === 'string' ? frame.type : '';\n // Streaming updates belong to the session presentation. Replicating the\n // hub's growing event list for every token makes model output quadratic.\n if (\n type !== 'agent_start' &&\n type !== 'agent_settled' &&\n type !== 'message_end' &&\n type !== 'session_info_changed' &&\n type !== 'extension_ui_request' &&\n type !== 'extension_ui_answered'\n )\n return;\n const now = new Date().toISOString();\n const phase = type === 'agent_start' ? 'turn' : type === 'agent_settled' ? 'idle' : current.phase;\n const phaseChanged = phase !== current.phase;\n const method = typeof frame.method === 'string' ? frame.method : '';\n const awaitingInput =\n type === 'extension_ui_request' && ['select', 'confirm', 'input', 'editor'].includes(method)\n ? true\n : type === 'extension_ui_answered' || type === 'agent_settled'\n ? false\n : current.awaitingInput;\n current = {\n ...current,\n name: type === 'session_info_changed' && typeof frame.name === 'string' ? frame.name : current.name,\n updatedAt: now,\n phase,\n phaseSince: phaseChanged ? now : current.phaseSince,\n everPrompted: current.everPrompted || type === 'agent_start',\n awaitingInput,\n ...(type === 'agent_settled' ? { lastSettledAt: now } : {}),\n };\n sessions.set(session.id, current);\n emit({ kind: 'upsert', session: current });\n if (type === 'agent_settled') refreshGit();\n });\n let registered = false;\n const applyProfile = (profileName: string | undefined): void => {\n if (closed || sessions.get(session.id)?.host !== session.host) return;\n let identity: HeadlessProfileIdentity | undefined;\n if (profileName) {\n const root = (session.workspaceId && workspaces.get(session.workspaceId)?.root) || session.cwd;\n try {\n identity = options.resolveProfileIdentity?.(root, profileName);\n } catch (error) {\n options.onNotice?.(\n `session ${session.id} profile \"${profileName}\" could not be resolved (${error instanceof Error ? error.message : String(error)})`,\n );\n }\n }\n const icon = identity?.icon;\n if (icon) avatarIcons.set(session.id, icon);\n else avatarIcons.delete(session.id);\n const profile: HeadlessSessionProfile | undefined = profileName\n ? {\n name: profileName,\n ...(identity?.displayName ? { displayName: identity.displayName } : {}),\n ...(icon\n ? { iconVersion: crypto.createHash('sha256').update(icon).digest('hex').slice(0, ICON_VERSION_LENGTH) }\n : {}),\n }\n : undefined;\n const previous = current.profile;\n if (\n previous?.name === profile?.name &&\n previous?.displayName === profile?.displayName &&\n previous?.iconVersion === profile?.iconVersion\n )\n return;\n const { profile: _previous, ...rest } = current;\n current = profile === undefined ? rest : { ...rest, profile };\n sessions.set(session.id, current);\n if (registered) emit({ kind: 'upsert', session: current });\n };\n const stopSelection = session.host.onSelection?.((selection) => applyProfile(selection.profile));\n presentationCleanups.set(session.id, () => {\n stopPresentation();\n stopSelection?.();\n });\n for (const { source } of selectedChannels(current)) source.sessionAdded?.(scopeOf(session));\n const active = (): boolean => sessions.get(session.id)?.host === session.host && !closed;\n const cleanup = (): void => {\n subscriptions.delete(cleanup);\n sessionCleanups.delete(session.id);\n if (!active()) return;\n void closeSession(session.id).catch((error: unknown) =>\n options.onNotice?.(\n `session ${session.id} cleanup failed (${error instanceof Error ? error.message : String(error)})`,\n ),\n );\n };\n sessionCleanups.set(session.id, cleanup);\n void session.host.runtime.exited.then(cleanup, cleanup);\n subscriptions.add(cleanup);\n registered = true;\n emit({ kind: 'upsert', session: current });\n refreshGit();\n };\n\n const closeSession = async (sessionId: string, closeOptions?: HeadlessSessionCloseOptions): Promise<void> => {\n const pending = sessionShutdowns.get(sessionId);\n if (pending !== undefined) {\n await pending.promise;\n return;\n }\n const session = sessions.get(sessionId);\n if (session === undefined) return;\n unregisterSession(sessionId, closeOptions);\n await startSessionShutdown(session, closeOptions);\n };\n\n const promptTarget = (sessionId: string, parentSessionId: string | undefined): HeadlessHubSession => {\n const session = closed ? undefined : sessions.get(sessionId);\n if (session === undefined) throw new Error('The session is not live.');\n if (parentSessionId !== undefined && session.parentSessionId !== parentSessionId)\n throw new Error('Only the parent session can drive this session.');\n return session;\n };\n\n sessionService = {\n create: async (request) => {\n if (options.createSession === undefined)\n throw new Error('The cockpit session service cannot create sessions in this host.');\n return options.createSession(request);\n },\n close: closeSession,\n isLive: (sessionId) => !closed && sessions.has(sessionId),\n prompt: async (sessionId, text, promptOptions) => {\n promptOptions?.signal?.throwIfAborted();\n const { settled } = await promptTarget(sessionId, promptOptions?.parentSessionId).host.runtime.submitPrompt(text);\n return { settled };\n },\n abort: async (sessionId, abortOptions) => {\n await promptTarget(sessionId, abortOptions?.parentSessionId).host.runtime.abort();\n },\n steer: async (sessionId, message, steerOptions) => {\n await promptTarget(sessionId, steerOptions?.parentSessionId).host.runtime.steer(message);\n },\n release: async (sessionId, releaseOptions) => {\n promptTarget(sessionId, releaseOptions?.parentSessionId);\n await closeSession(sessionId, { keepDormant: true });\n },\n reservations: options.sessionReservations,\n provisionReservedWorktree: async (request) => {\n const parent = sessions.get(request.parentSessionId);\n const provisioner = parent === undefined ? undefined : worktreeProvisionerFor(parent);\n if (provisioner === undefined) throw new Error('Automatic conversation worktree provisioning is unavailable.');\n return provisioner(request);\n },\n registerReservedWorktreeProvisioner: (provisioner) =>\n registerReservedWorktreeProvisioner({ scope: 'global' }, provisioner),\n canCommunicate,\n bindCommunication,\n };\n\n const mountFacets = async (\n facets: readonly (DoomServerFacet | LoadedServerFacet)[],\n suppliedContext?: DoomApiContext,\n ): Promise<void> => {\n if (closed) throw new Error('The headless hub is closed.');\n const context: DoomApiContext = suppliedContext ?? {\n scope: 'global',\n ...(options.hubToken?.() === undefined ? {} : { hubToken: options.hubToken() }),\n sessionService,\n directEvents,\n onNotice: (message) => options.onNotice?.(message),\n };\n if (context.scope === 'session') throw new Error('Session facets belong to their session host.');\n if (context.scope === 'workspace' && (!context.workspaceId || !context.workspaceRoot))\n throw new Error('Workspace mounts require an admitted identity and root.');\n const mount: DoomApiMount =\n context.scope === 'global' ? { scope: 'global' } : { scope: 'workspace', workspaceId: context.workspaceId! };\n const key = mountKey(mount);\n if (mounts.has(key)) throw new Error(`The '${key}' facets are already mounted.`);\n const host = createDoomServerHost({\n scope: context.scope,\n context: {\n ...context,\n sessionService: channelHost('', mount).sessionService,\n directEvents: channelHost('', mount).directEvents,\n receiveChannel: (sessionId, frameType, payload, connectionId) =>\n receiveMountedChannel(mount, sessionId, frameType, payload, connectionId),\n },\n mountChannel: (channel) => {\n const started = startChannel(channel, mount);\n if (started === undefined) return { mounted: false, dispose: () => undefined };\n return { mounted: true, dispose: () => releaseChannel(channel, started) };\n },\n pluginRegistry,\n });\n try {\n const installed = await installServerFacets({ host, facets, onNotice: options.onNotice });\n if (closed) {\n await installed.dispose();\n throw new Error('The headless hub was closed while mounting facets.');\n }\n mounts.set(key, { host, installed });\n if (mount.scope === 'workspace') {\n const workspace = { id: mount.workspaceId, root: context.workspaceRoot!, available: true };\n workspaces.set(mount.workspaceId, workspace);\n emit({ kind: 'workspace_upsert', workspace });\n }\n } catch (error) {\n host.dispose();\n throw error;\n }\n };\n\n return {\n sessionService,\n directEvents,\n ...(options.computerUse === undefined ? {} : { computerUse: options.computerUse }),\n snapshot: () => [...sessions.values()].map(present),\n session: (sessionId) => {\n const session = sessions.get(sessionId);\n return session === undefined ? undefined : present(session);\n },\n setPendingSessionSetups(sessionId, pending) {\n if (JSON.stringify(pendingSetups.get(sessionId) ?? []) === JSON.stringify(pending)) return;\n pendingSetups.set(sessionId, Object.freeze([...pending]));\n const session = sessions.get(sessionId);\n if (session) emit({ kind: 'upsert', session: present(session) });\n },\n runtime: (sessionId) => sessions.get(sessionId)?.host.runtime,\n sessionAvatar(sessionId) {\n const match = ICON_DATA_URL.exec(avatarIcons.get(sessionId) ?? '');\n const mimeType = match?.[1];\n if (match === null || !(PROFILE_ICON_MIME_TYPES as readonly string[]).includes(mimeType ?? '')) return undefined;\n return { mimeType: mimeType!, bytes: Buffer.from(match[2]!, 'base64') };\n },\n onEvent(listener) {\n listeners.add(listener);\n return () => listeners.delete(listener);\n },\n register,\n notifySessionRemoved(sessionId) {\n emit({ kind: 'removed', sessionId });\n },\n mountFacets,\n workspaces: () => [...workspaces.values()],\n registerWorkspace(workspace) {\n workspaces.set(workspace.id, workspace);\n emit({ kind: 'workspace_upsert', workspace });\n },\n admitWorkspace: async (request) => {\n if (!options.admitWorkspace) throw new Error('Workspace admission is unavailable.');\n return options.admitWorkspace(request);\n },\n async removeWorkspace(id) {\n if ([...sessions.values()].some((session) => session.workspaceId === id))\n throw new Error('Workspace still has live sessions.');\n const key = mountKey({ scope: 'workspace', workspaceId: id });\n const mounted = mounts.get(key);\n if (!workspaces.has(id)) return;\n options.onWorkspaceRemoved?.(id);\n workspaces.delete(id);\n emit({ kind: 'workspace_removed', workspaceId: id });\n if (!mounted) return;\n mounts.delete(key);\n try {\n await mounted.installed.dispose();\n } finally {\n mounted.host.dispose();\n }\n },\n async requestApi(mount, basePath, request) {\n if (closed) return Response.json({ error: 'Server is closed.' }, { status: 503 });\n if (mount.scope === 'session') {\n const session = sessions.get(mount.sessionId);\n if (!session || !options.requestSessionApi)\n return Response.json({ error: 'Session not found.' }, { status: 404 });\n const url = new URL(request.url);\n return options.requestSessionApi(scopeOf(session), {\n basePath,\n path: `${url.pathname}${url.search}`,\n method: request.method,\n headers: request.headers,\n signal: request.signal,\n body: request.body === null ? undefined : new Uint8Array(await request.arrayBuffer()),\n });\n }\n const handler = mounts.get(mountKey(mount))?.host.handlerFor(basePath);\n if (!handler)\n return Response.json({ error: `API '${basePath}' is not mounted in ${mountKey(mount)}.` }, { status: 404 });\n return handler.fetch(request);\n },\n pluginRegistry,\n invokePlugin: (call, direction, caller) => pluginRegistry.invoke(call, direction, caller),\n async create(sessionOptions) {\n const id = sessionOptions.sessionId;\n if (closed) throw new Error('The headless hub is closed.');\n // Checked before building the host: once it exists, a refusal has to close it, and a\n // duplicate id would then close the live session that already owns the id.\n if (sessions.has(id)) throw new Error(`Session '${id}' is already registered.`);\n if (sessionShutdowns.get(id)?.pending) throw new Error(`Session '${id}' is still shutting down.`);\n const host = await options.manager.create(sessionOptions);\n const settledAt = await lastSettledAt(host);\n const session: HeadlessHubSession = {\n id: sessionOptions.sessionId,\n workspaceId: sessionOptions.workspaceId,\n webComposition: sessionOptions.webComposition,\n name: sessionOptions.sessionName,\n cwd: sessionOptions.cwd,\n createdAt: new Date().toISOString(),\n ...(sessionOptions.parentSessionId === undefined ? {} : { parentSessionId: sessionOptions.parentSessionId }),\n ...(sessionOptions.sessionProvenance === undefined\n ? {}\n : { sessionProvenance: sessionOptions.sessionProvenance }),\n environment: sessionOptions.environment,\n ...(settledAt === undefined ? {} : { everPrompted: true, lastSettledAt: settledAt }),\n host,\n };\n try {\n register(session);\n } catch (error) {\n // The hub closed, or a racing create took the id, while this host was starting.\n await Promise.resolve(options.manager.closeSession(id)).catch((closeError: unknown) =>\n options.onNotice?.(`Session '${id}' could not be closed after a failed registration: ${String(closeError)}`),\n );\n throw error;\n }\n return session;\n },\n closeSession,\n async requestSessionApi(scope, request) {\n if (closed || !sessions.has(scope.sessionId))\n return Response.json({ error: 'Session not found.' }, { status: 404 });\n if (options.requestSessionApi === undefined)\n return Response.json({ error: 'Session API unavailable.' }, { status: 404 });\n return options.requestSessionApi(scope, request);\n },\n registerChannel(channel, mount = { scope: 'global' }) {\n const started = startChannel(channel, mount);\n if (started === undefined) return () => undefined;\n return () => releaseChannel(channel, started);\n },\n channelTypes: () => [...new Set([...channels.values()].map(({ channel }) => channel.frameType))],\n channelFrames(sessionId) {\n const session = sessions.get(sessionId);\n if (session === undefined) return [];\n const scope = scopeOf(session);\n const frames: Array<{ type: string; sessionId: string; payload: unknown }> = [];\n for (const { channel, source } of selectedChannels(session)) {\n const type = channel.frameType;\n const payload = source.payloadFor(scope);\n if (payload !== undefined) frames.push({ type, sessionId, payload });\n }\n return frames;\n },\n threadJournal(sessionId, threadId) {\n const session = sessions.get(sessionId);\n if (session === undefined) return undefined;\n const scope = scopeOf(session);\n for (const { source } of selectedChannels(session)) {\n const journal = source.threadJournal?.(scope, threadId);\n if (journal !== undefined) return journal;\n }\n return undefined;\n },\n receiveChannel(sessionId, frameType, payload, connectionId, desktopAuthorized = false) {\n if (connectionId === '') return;\n const session = sessions.get(sessionId);\n const started = session && selectedChannels(session).find(({ channel }) => channel.frameType === frameType);\n if (session === undefined || started === undefined) return;\n started.channel.receive?.(scopeOf(session), payload, {\n connectionId,\n ...(desktopAuthorized ? { desktopAuthorized: true } : {}),\n } satisfies DoomHubChannelConnection);\n },\n disconnectChannels(connectionId) {\n if (connectionId === '') return;\n for (const { channel } of channels.values()) channel.disconnected?.({ connectionId });\n },\n async close() {\n if (closed) return;\n const ids = [...sessions.keys()];\n closed = true;\n directEvents.close();\n for (const unsubscribe of subscriptions) unsubscribe();\n subscriptions.clear();\n for (const cleanup of presentationCleanups.values()) cleanup();\n presentationCleanups.clear();\n const failures: unknown[] = [];\n for (const mounted of [...mounts.values()].reverse()) {\n try {\n await mounted.installed.dispose();\n } catch (error) {\n failures.push(error);\n }\n try {\n mounted.host.dispose();\n } catch (error) {\n failures.push(error);\n }\n }\n mounts.clear();\n pluginRegistry.dispose();\n workspaces.clear();\n for (const { source } of channels.values()) source.close();\n channels.clear();\n listeners.clear();\n const shutdowns: Promise<void>[] = [...sessionShutdowns.values()]\n .filter(({ pending }) => pending)\n .map(({ promise }) => promise);\n for (const id of ids) {\n const session = sessions.get(id);\n if (session !== undefined && !sessionShutdowns.has(id)) shutdowns.push(startSessionShutdown(session));\n }\n sessions.clear();\n const outcomes = await Promise.allSettled(shutdowns);\n for (const outcome of outcomes) if (outcome.status === 'rejected') failures.push(outcome.reason);\n if (failures.length) throw new AggregateError(failures, 'Mount shutdown failed');\n },\n };\n}\n"],"mappings":"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"}
|
package/dist/headlessHub.d.mts
CHANGED
|
@@ -6,7 +6,7 @@ import { LoadedServerFacet } from "./src/server/serverFacetLoader.mjs";
|
|
|
6
6
|
import "./serverFacet.mjs";
|
|
7
7
|
import "./packageApi.mjs";
|
|
8
8
|
import { HeadlessSessionHost, HeadlessSessionHostOptions } from "./headlessSessionHost.mjs";
|
|
9
|
-
import { HeadlessSessionManager } from "./runtimeHeadlessSessionManager.mjs";
|
|
9
|
+
import { HeadlessSessionCloseOptions, HeadlessSessionManager } from "./runtimeHeadlessSessionManager.mjs";
|
|
10
10
|
import "./pluginProtocol.mjs";
|
|
11
11
|
import { SessionGitStatus } from "./src/services/sessionGitStatus/type.mjs";
|
|
12
12
|
//#region src/server/headlessHub.d.ts
|
|
@@ -61,9 +61,12 @@ export interface HeadlessWorkspaceAdmission {
|
|
|
61
61
|
export type HeadlessHubEvent = {
|
|
62
62
|
kind: 'upsert';
|
|
63
63
|
session: HeadlessHubSession;
|
|
64
|
-
} |
|
|
64
|
+
} |
|
|
65
|
+
/** `dormant`: the session stopped running but keeps its record, so it lists as dormant. */
|
|
66
|
+
{
|
|
65
67
|
kind: 'removed';
|
|
66
68
|
sessionId: string;
|
|
69
|
+
dormant?: true;
|
|
67
70
|
} | {
|
|
68
71
|
kind: 'workspace_upsert';
|
|
69
72
|
workspace: HeadlessWorkspace;
|
|
@@ -118,7 +121,7 @@ export interface HeadlessHub {
|
|
|
118
121
|
onEvent(listener: (event: HeadlessHubEvent) => void): () => void;
|
|
119
122
|
register(session: HeadlessHubSession): void;
|
|
120
123
|
create(options: HeadlessSessionHostOptions): Promise<HeadlessHubSession>;
|
|
121
|
-
closeSession(sessionId: string): Promise<void>;
|
|
124
|
+
closeSession(sessionId: string, options?: HeadlessSessionCloseOptions): Promise<void>;
|
|
122
125
|
/** Publishes removal of a persisted session that has no live host. */
|
|
123
126
|
notifySessionRemoved(sessionId: string): void;
|
|
124
127
|
/** Publishes setup-only children through the parent's normal session updates. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"headlessHub.d.mts","names":[],"sources":["../src/server/headlessHub.ts"],"mappings":";;;;;;;;;;;;;iBAgDiB;WACN;WACA;;WAEA;;;iBAIM;WACN;WACA;;iBAGM;WACN;WACA;EACT,iBAAiB;WACR;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;WAEA,MAAM;;WAEN,UAAU;WACV,yBAAyB;;WAEzB,cAAc,SAAS;WACvB,MAAM;;iBAGA;WACN;WACA;WACA;WACA;;;iBAIM;WACN;WACA;;YAGC;EACN;EAAgB,SAAS
|
|
1
|
+
{"version":3,"file":"headlessHub.d.mts","names":[],"sources":["../src/server/headlessHub.ts"],"mappings":";;;;;;;;;;;;;iBAgDiB;WACN;WACA;;WAEA;;;iBAIM;WACN;WACA;;iBAGM;WACN;WACA;EACT,iBAAiB;WACR;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;WACA;;WAEA,MAAM;;WAEN,UAAU;WACV,yBAAyB;;WAEzB,cAAc,SAAS;WACvB,MAAM;;iBAGA;WACN;WACA;WACA;WACA;;;iBAIM;WACN;WACA;;YAGC;EACN;EAAgB,SAAS;;;;EAEzB;EAAiB;EAAmB;;EACpC;EAA0B,WAAW;;EACrC;EAA2B;;EAC3B;EAAiB;EAAmB;EAAmB;EAAkB;;iBAE9D;EACf,SAAS;;EAET;;EAEA,iBACE,SAAS,6BAKT;;;;;EAAA;IAAc;QACX,QAAQ;EACb,YAAY;EACZ,sBAAsB;EACtB,qBAAqB,OAAO,qBAAqB,SAAS,6BAA6B,QAAQ;EAC/F,cAAc;EACd,kBAAkB,SAAS,+BAA+B,QAAQ;EAClE,sBAAsB;;EAEtB,iBAAiB,gBAAgB,QAAQ;;EAEzC,0BAA0B,cAAc,oBAAoB;;iBAG7C;;WAEN,gBAAgB;;WAEhB,cAAc;WACd,cAAc;EACvB,qBAAqB;EACrB,QAAQ,oBAAoB;EAC5B,QAAQ,oBAAoB;;EAE5B,cAAc;IAAsB;IAAkB,OAAO;;EAC7D,QAAQ,WAAW,OAAO;EAC1B,SAAS,SAAS;EAClB,OAAO,SAAS,6BAA6B,QAAQ;EACrD,aAAa,mBAAmB,UAAU,8BAA8B;;EAExE,qBAAqB;;EAErB,yBAAyB,mBAAmB,kBAAkB;;EAE9D,kBAAkB,OAAO,qBAAqB,SAAS,2BAA2B,QAAQ;;EAE1F,YAAY,kBAAkB,kBAAkB,sBAAsB,UAAU,iBAAiB;EACjG,uBAAuB;EACvB,kBAAkB,WAAW;EAC7B,eAAe,SAAS,6BAA6B,QAAQ;EAC7D,gBAAgB,sBAAsB;EACtC,WAAW,OAAO,cAAc,kBAAkB,SAAS,UAAU,QAAQ;;WAEpE,gBAAgB;EACzB,aAAa,eAAe,WAAW,qBAAqB,SAAS,mBAAmB;EACxF,gBAAgB,SAAS,gBAAgB,QAAQ;EACjD;EACA,cAAc,oBAAoB;IAAQ;IAAc;IAAmB;;;EAE3E,cAAc,mBAAmB;EACjC,eACE,mBACA,mBACA,kBACA,sBACA;EAEF,mBAAmB;EACnB,SAAS;;;wBAwCK,kBAAkB,SAAS,qBAAqB"}
|
package/dist/headlessHub.mjs
CHANGED
|
@@ -412,7 +412,7 @@ function createHeadlessHub(options) {
|
|
|
412
412
|
started.channel.receive(scopeOf(session), payload, { connectionId });
|
|
413
413
|
return true;
|
|
414
414
|
};
|
|
415
|
-
const unregisterSession = (sessionId) => {
|
|
415
|
+
const unregisterSession = (sessionId, options) => {
|
|
416
416
|
const current = sessions.get(sessionId);
|
|
417
417
|
if (current === void 0) return;
|
|
418
418
|
const cleanup = sessionCleanups.get(sessionId);
|
|
@@ -427,10 +427,11 @@ function createHeadlessHub(options) {
|
|
|
427
427
|
directEvents.clearSession?.(sessionId);
|
|
428
428
|
emit({
|
|
429
429
|
kind: "removed",
|
|
430
|
-
sessionId
|
|
430
|
+
sessionId,
|
|
431
|
+
...options?.keepDormant === true ? { dormant: true } : {}
|
|
431
432
|
});
|
|
432
433
|
};
|
|
433
|
-
const startSessionShutdown = (session) => {
|
|
434
|
+
const startSessionShutdown = (session, closeOptions) => {
|
|
434
435
|
const existing = sessionShutdowns.get(session.id);
|
|
435
436
|
if (existing !== void 0) return existing.promise;
|
|
436
437
|
const shutdown = {
|
|
@@ -438,7 +439,7 @@ function createHeadlessHub(options) {
|
|
|
438
439
|
promise: Promise.resolve(),
|
|
439
440
|
pending: true
|
|
440
441
|
};
|
|
441
|
-
const promise = Promise.resolve().then(() => options.manager.closeSession(session.id));
|
|
442
|
+
const promise = Promise.resolve().then(() => closeOptions === void 0 ? options.manager.closeSession(session.id) : options.manager.closeSession(session.id, closeOptions));
|
|
442
443
|
shutdown.promise = promise;
|
|
443
444
|
sessionShutdowns.set(session.id, shutdown);
|
|
444
445
|
promise.then(() => {
|
|
@@ -568,7 +569,7 @@ function createHeadlessHub(options) {
|
|
|
568
569
|
});
|
|
569
570
|
refreshGit();
|
|
570
571
|
};
|
|
571
|
-
const closeSession = async (sessionId) => {
|
|
572
|
+
const closeSession = async (sessionId, closeOptions) => {
|
|
572
573
|
const pending = sessionShutdowns.get(sessionId);
|
|
573
574
|
if (pending !== void 0) {
|
|
574
575
|
await pending.promise;
|
|
@@ -576,8 +577,14 @@ function createHeadlessHub(options) {
|
|
|
576
577
|
}
|
|
577
578
|
const session = sessions.get(sessionId);
|
|
578
579
|
if (session === void 0) return;
|
|
579
|
-
unregisterSession(sessionId);
|
|
580
|
-
await startSessionShutdown(session);
|
|
580
|
+
unregisterSession(sessionId, closeOptions);
|
|
581
|
+
await startSessionShutdown(session, closeOptions);
|
|
582
|
+
};
|
|
583
|
+
const promptTarget = (sessionId, parentSessionId) => {
|
|
584
|
+
const session = closed ? void 0 : sessions.get(sessionId);
|
|
585
|
+
if (session === void 0) throw new Error("The session is not live.");
|
|
586
|
+
if (parentSessionId !== void 0 && session.parentSessionId !== parentSessionId) throw new Error("Only the parent session can drive this session.");
|
|
587
|
+
return session;
|
|
581
588
|
};
|
|
582
589
|
sessionService = {
|
|
583
590
|
create: async (request) => {
|
|
@@ -586,6 +593,21 @@ function createHeadlessHub(options) {
|
|
|
586
593
|
},
|
|
587
594
|
close: closeSession,
|
|
588
595
|
isLive: (sessionId) => !closed && sessions.has(sessionId),
|
|
596
|
+
prompt: async (sessionId, text, promptOptions) => {
|
|
597
|
+
promptOptions?.signal?.throwIfAborted();
|
|
598
|
+
const { settled } = await promptTarget(sessionId, promptOptions?.parentSessionId).host.runtime.submitPrompt(text);
|
|
599
|
+
return { settled };
|
|
600
|
+
},
|
|
601
|
+
abort: async (sessionId, abortOptions) => {
|
|
602
|
+
await promptTarget(sessionId, abortOptions?.parentSessionId).host.runtime.abort();
|
|
603
|
+
},
|
|
604
|
+
steer: async (sessionId, message, steerOptions) => {
|
|
605
|
+
await promptTarget(sessionId, steerOptions?.parentSessionId).host.runtime.steer(message);
|
|
606
|
+
},
|
|
607
|
+
release: async (sessionId, releaseOptions) => {
|
|
608
|
+
promptTarget(sessionId, releaseOptions?.parentSessionId);
|
|
609
|
+
await closeSession(sessionId, { keepDormant: true });
|
|
610
|
+
},
|
|
589
611
|
reservations: options.sessionReservations,
|
|
590
612
|
provisionReservedWorktree: async (request) => {
|
|
591
613
|
const parent = sessions.get(request.parentSessionId);
|