@agimon-ai/po-core 0.0.0-alpha.69 → 0.0.0-alpha.70

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.
@@ -1,6 +1,6 @@
1
- import { PO_API_CALLER_DEVICE_ID_HEADER, PO_API_CALLER_FEDERATION_HEADER, PO_API_CALLER_HEADERS, PO_API_CALLER_LOCALITY_HEADER, PO_API_CALLER_STEP_UP_HEADER, PO_API_ROUTE_PREFIX, PO_API_SCOPES, PO_HUB_API_SESSION_QUERY_PARAM, PO_SHARE_FILE_REMOTE_OFF_MESSAGE, PoApi, PoApiCaller, PoApiCallerStepUp, PoApiContext, PoApiHandler, PoApiMount, PoApiPublicRoutes, PoApiScope, PoApiSocket, PoEditPresenceAgent, PoEditPresenceEntry, PoEditPresenceLedger, PoFederationCaller, PoHostExecutionBudget, PoHostExecutionPermit, PoHostMediaArbitration, PoNativeComposition, PoOAuthRedirect, PoPeerAgentRegistry, PoRepositorySyncView, PoRequestReceipt, PoRequestReceiptKey, PoRequestReceiptReservation, PoRequestReceiptReserveResult, PoRequestReceipts, PoSharedFile, PoSocketMount, PoWebComposition, parsePoSocketPath, poApiCallerFrom, poApiMountPath } from "./src/schemas/packageApi.mjs";
1
+ import { PO_API_CALLER_DEVICE_ID_HEADER, PO_API_CALLER_FEDERATION_HEADER, PO_API_CALLER_HEADERS, PO_API_CALLER_LOCALITY_HEADER, PO_API_CALLER_STEP_UP_HEADER, PO_API_ROUTE_PREFIX, PO_API_SCOPES, PO_HUB_API_SESSION_QUERY_PARAM, PO_SHARE_FILE_REMOTE_OFF_MESSAGE, PoApi, PoApiCaller, PoApiCallerStepUp, PoApiContext, PoApiHandler, PoApiMount, PoApiPublicRoutes, PoApiScope, PoApiSocket, PoEditPresenceAgent, PoEditPresenceEntry, PoEditPresenceLedger, PoFederationCaller, PoHostExecutionBudget, PoHostExecutionPermit, PoHostMediaArbitration, PoNativeComposition, PoOAuthRedirect, PoOpenedRegisteredFile, PoPeerAgentRegistry, PoRegisteredFile, PoRepositorySyncView, PoRequestReceipt, PoRequestReceiptKey, PoRequestReceiptReservation, PoRequestReceiptReserveResult, PoRequestReceipts, PoSharedFile, PoSocketMount, PoWebComposition, parsePoSocketPath, poApiCallerFrom, poApiMountPath } from "./src/schemas/packageApi.mjs";
2
2
  import { FederationWorkPaused } from "./src/schemas/federation.mjs";
3
3
  import { createExecutionBudget, createServerExecutionBudget, sharedExecutionBudget } from "./executionBudget.mjs";
4
4
  import { FederationGrantWork, FederationHomeAccess, FederationRefreshPorts, FederationWorkspacePermission, HubFederationBoundaryPorts, HubFederationPlatform } from "./src/services/federationAccess/type.mjs";
5
5
  import { createFederationFeedStore, createFederationGrantStore, createFederationHomeAccess, createFederationReplayStore, createHubFederationBoundary } from "./src/services/federationAccess/index.mjs";
6
- export { type FederationGrantWork, type FederationHomeAccess, type FederationRefreshPorts, type FederationWorkPaused, type FederationWorkspacePermission, type HubFederationBoundaryPorts, type HubFederationPlatform, PO_API_CALLER_DEVICE_ID_HEADER, PO_API_CALLER_FEDERATION_HEADER, PO_API_CALLER_HEADERS, PO_API_CALLER_LOCALITY_HEADER, PO_API_CALLER_STEP_UP_HEADER, PO_API_ROUTE_PREFIX, PO_API_SCOPES, PO_HUB_API_SESSION_QUERY_PARAM, PO_SHARE_FILE_REMOTE_OFF_MESSAGE, type PoApi, type PoApiCaller, type PoApiCallerStepUp, type PoApiContext, type PoApiHandler, type PoApiMount, type PoApiPublicRoutes, type PoApiScope, type PoApiSocket, type PoEditPresenceAgent, type PoEditPresenceEntry, type PoEditPresenceLedger, type PoFederationCaller, type PoHostExecutionBudget, type PoHostExecutionPermit, type PoHostMediaArbitration, type PoNativeComposition, type PoOAuthRedirect, type PoPeerAgentRegistry, type PoRepositorySyncView, type PoRequestReceipt, type PoRequestReceiptKey, type PoRequestReceiptReservation, type PoRequestReceiptReserveResult, type PoRequestReceipts, type PoSharedFile, type PoSocketMount, type PoWebComposition, createExecutionBudget, createFederationFeedStore, createFederationGrantStore, createFederationHomeAccess, createFederationReplayStore, createHubFederationBoundary, createServerExecutionBudget, parsePoSocketPath, poApiCallerFrom, poApiMountPath, sharedExecutionBudget };
6
+ export { type FederationGrantWork, type FederationHomeAccess, type FederationRefreshPorts, type FederationWorkPaused, type FederationWorkspacePermission, type HubFederationBoundaryPorts, type HubFederationPlatform, PO_API_CALLER_DEVICE_ID_HEADER, PO_API_CALLER_FEDERATION_HEADER, PO_API_CALLER_HEADERS, PO_API_CALLER_LOCALITY_HEADER, PO_API_CALLER_STEP_UP_HEADER, PO_API_ROUTE_PREFIX, PO_API_SCOPES, PO_HUB_API_SESSION_QUERY_PARAM, PO_SHARE_FILE_REMOTE_OFF_MESSAGE, type PoApi, type PoApiCaller, type PoApiCallerStepUp, type PoApiContext, type PoApiHandler, type PoApiMount, type PoApiPublicRoutes, type PoApiScope, type PoApiSocket, type PoEditPresenceAgent, type PoEditPresenceEntry, type PoEditPresenceLedger, type PoFederationCaller, type PoHostExecutionBudget, type PoHostExecutionPermit, type PoHostMediaArbitration, type PoNativeComposition, type PoOAuthRedirect, type PoOpenedRegisteredFile, type PoPeerAgentRegistry, type PoRegisteredFile, type PoRepositorySyncView, type PoRequestReceipt, type PoRequestReceiptKey, type PoRequestReceiptReservation, type PoRequestReceiptReserveResult, type PoRequestReceipts, type PoSharedFile, type PoSocketMount, type PoWebComposition, createExecutionBudget, createFederationFeedStore, createFederationGrantStore, createFederationHomeAccess, createFederationReplayStore, createHubFederationBoundary, createServerExecutionBudget, parsePoSocketPath, poApiCallerFrom, poApiMountPath, sharedExecutionBudget };
@@ -141,6 +141,7 @@ async function serveSessionApis(options) {
141
141
  directEvents: options.directEvents,
142
142
  ...options.computerUse === void 0 ? {} : { computerUse: options.computerUse },
143
143
  ...options.shareFile === void 0 ? {} : { shareFile: options.shareFile },
144
+ ...options.shareRegisteredFile === void 0 ? {} : { shareRegisteredFile: options.shareRegisteredFile },
144
145
  ...options.mediaArbitration === void 0 ? {} : { mediaArbitration: options.mediaArbitration },
145
146
  ...options.executionBudget === void 0 ? {} : { executionBudget: options.executionBudget },
146
147
  ...options.editPresence === void 0 ? {} : { editPresence: options.editPresence },
@@ -1 +1 @@
1
- {"version":3,"file":"packageApiServer.cjs","names":["SESSION_FILES_API","readAccessibleFile","prepareSessionEnvironment","createPoServerHost","createFileAccess","PO_SESSION_TEMP_DIR","path","PoServerScope","PO_FILE_ACCESS_SERVICE","PO_API_ROUTE_PREFIX","validatedTraceContext","OperationCancelledError"],"sources":["../src/server/packageApiServer.ts"],"sourcesContent":["import path from 'node:path';\n\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport { SESSION_FILES_API } from '../constants/sessionFiles';\nimport type { PoDirectEventBus, PoHubSessionPromptOptions, PoHubSessionService } from '../exports/hubChannel';\nimport { PO_API_ROUTE_PREFIX, type PoApi, type PoApiContext } from '../exports/packageApi';\nimport type { PoPlatform } from '../exports/platform';\nimport type { PoPluginRegistry } from '../exports/pluginProtocol';\nimport {\n createPoServerHost,\n type CreatePoServerHostOptions,\n type LoadedServerPackage,\n type PoServerPackageFailure,\n type PoServerPlugin,\n PoServerScope,\n} from '../exports/serverPlugin';\nimport {\n createFileAccess,\n PO_FILE_ACCESS_SERVICE,\n type PoFileAccess,\n readAccessibleFile,\n} from '../services/fileAccess';\nimport type { PoTraceContext } from '../services/poTelemetry';\nimport { observe, type ServerTelemetry } from '../services/serverTelemetry';\nimport { PO_SESSION_TEMP_DIR, prepareSessionEnvironment } from '../services/sessionFiles';\nimport { OperationCancelledError } from '../services/telemetry';\nimport { validatedTraceContext } from '../services/traceContext';\n\n/**\n * A body that keeps streaming well after its headers went out is worth its own span. A fast\n * one is not: the request span already carries the duration and outcome, so emitting a second\n * span per call made package API traffic 99.65% of all spans recorded in a two hour sample.\n */\nexport const COMPLETION_SPAN_MIN_DURATION_MS = 1000;\n/** A body this large is worth a span however fast it flushed: big bodies drive heap spikes. */\nexport const COMPLETION_SPAN_MIN_BYTES = 1024 * 1024;\n\n/** A coarse body class, so a size can be attributed without recording a path or file name. */\nexport function contentClass(contentType: string | null): string {\n const type = contentType?.split(';')[0]?.trim().toLowerCase() ?? '';\n if (type === '') return 'none';\n if (type.startsWith('image/')) return 'image';\n if (type.endsWith('json')) return 'json';\n if (type.startsWith('text/')) return 'text';\n return 'binary';\n}\n\nexport interface PackageApiServerOptions {\n sessionId: string;\n workspaceId?: string;\n workspaceRoot?: string;\n sessionContext?: PoApiContext['sessionContext'];\n homeDirectory?: string;\n mountChannel?: CreatePoServerHostOptions['mountChannel'];\n cwd: string;\n /** Admitted environment for APIs serving this session. */\n readonly environment: Readonly<Record<string, string | undefined>>;\n /** Lifecycle-owned direct events shared with the hub. */\n readonly directEvents: PoDirectEventBus;\n readonly computerUse?: PoApiContext['computerUse'];\n /** Bound to this session's id and cwd by the host. */\n readonly shareFile?: PoApiContext['shareFile'];\n readonly mediaArbitration?: PoApiContext['mediaArbitration'];\n readonly executionBudget?: PoApiContext['executionBudget'];\n readonly editPresence?: PoApiContext['editPresence'];\n readonly peerAgents?: PoApiContext['peerAgents'];\n readonly requestApi?: PoApiContext['requestApi'];\n /** Publishes this session's activity line through the hub; bound to this session only. */\n readonly publishActivity?: PoApiContext['publishActivity'];\n internalToken?: string;\n hubToken?: string;\n /** Shared exact-scope dispatch table, so session packages can mount plugin methods. */\n pluginRegistry?: PoPluginRegistry;\n sessionService?: PoHubSessionService;\n apis: readonly PoApi[];\n /** Server packages to install; each registers its own APIs through the host. */\n packages?: readonly (PoServerPlugin | LoadedServerPackage)[];\n /** Publishes the session's own services into its scope root before any package installs. */\n preparePackages?: (root: Context) => void | Promise<void>;\n /** The hub's workspace or global scope; the session's own scope opens below it and closes with it. */\n parentScope?: PoServerScope;\n /** Host capabilities for the session scope root, injected by the composition root. */\n platform: PoPlatform;\n activatePackages?: (installedPackages: readonly string[]) => Promise<void>;\n canDispatch?: () => boolean;\n telemetry?: ServerTelemetry;\n onNotice: (message: string) => void;\n}\n\nexport interface PackageApiServer {\n /** Dispatches an already-authenticated request without crossing a transport boundary. */\n readonly operation?: (basePath: string, method: string, path: string) => string | undefined;\n readonly request: (request: Request) => Promise<Response>;\n /**\n * Brings the session's installed packages up to date: changed and added `packages` install with\n * per-package rollback, and installed ones no longer `declared` are disposed. A declared package\n * whose new build failed to load is absent from `packages` but keeps its running build.\n */\n readonly reconcile?: (\n packages: readonly (PoServerPlugin | LoadedServerPackage)[],\n declared: readonly string[],\n ) => Promise<PackageReconcileResult>;\n close(): Promise<void>;\n}\n\nexport interface PackageReconcileResult {\n readonly installed: readonly string[];\n readonly removed: readonly string[];\n readonly failed: readonly PoServerPackageFailure[];\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction responseWithCompletion(response: Response, complete: (bytes: number) => void): Response {\n if (response.body === null) {\n complete(0);\n return response;\n }\n const reader = response.body.getReader();\n let completed = false;\n let bytes = 0;\n const finish = (): void => {\n if (completed) return;\n completed = true;\n complete(bytes);\n };\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const result = await reader.read();\n if (result.done) {\n finish();\n controller.close();\n } else {\n bytes += result.value.byteLength;\n controller.enqueue(result.value);\n }\n } catch (error) {\n finish();\n controller.error(error);\n }\n },\n async cancel(reason) {\n finish();\n await reader.cancel(reason);\n },\n });\n return new Response(body, response);\n}\n\n/** Reads files under the session's open folders; the hub's session `/file` route forwards here. */\nfunction sessionFilesApi(fileAccess: PoFileAccess, cwd: string): PoApi {\n return {\n basePath: SESSION_FILES_API,\n start: () => ({\n fetch: (request) => {\n if (request.method !== 'GET') return Response.json({ error: 'Method not allowed.' }, { status: 405 });\n return readAccessibleFile({\n roots: fileAccess.roots(),\n cwd,\n path: new URL(request.url).searchParams.get('path'),\n ...(request.headers.get('range') === null ? {} : { range: request.headers.get('range')! }),\n ...(request.headers.get('if-match') === null ? {} : { expectedVersion: request.headers.get('if-match')! }),\n });\n },\n close: () => undefined,\n }),\n };\n}\n/** Installs session packages and dispatches authenticated package API calls in-process. */\nexport async function serveSessionApis(options: PackageApiServerOptions): Promise<PackageApiServer> {\n if (options.environment === undefined)\n throw new Error('Session package API server requires an admitted environment.');\n if (options.directEvents === undefined) throw new Error('Session package API server requires direct events.');\n options = { ...options, environment: await prepareSessionEnvironment(options) };\n const packages = options.packages ?? [];\n // Without a level above it, a session that mounts nothing has nothing to route.\n const routesNothing = (): boolean => !options.preparePackages && options.parentScope === undefined;\n if (options.apis.length === 0 && packages.length === 0 && routesNothing()) {\n return {\n request: async () => Response.json({ error: 'No package APIs are mounted.' }, { status: 404 }),\n close: () => Promise.resolve(),\n };\n }\n\n const owningSessionService = options.sessionService;\n const communication = owningSessionService?.bindCommunication?.(options.sessionId);\n const ownedReadExecutionState = owningSessionService?.readExecutionState?.bind(owningSessionService);\n const ownedPrompt = owningSessionService?.prompt?.bind(owningSessionService);\n const ownedAbort = owningSessionService?.abort?.bind(owningSessionService);\n const ownedSteer = owningSessionService?.steer?.bind(owningSessionService);\n const ownedRelease = owningSessionService?.release?.bind(owningSessionService);\n const exposedSessionService: PoHubSessionService | undefined =\n owningSessionService === undefined\n ? undefined\n : {\n create: (request) => owningSessionService.create(request),\n close: (sessionId) => owningSessionService.close(sessionId),\n isLive: (sessionId) => owningSessionService.isLive(sessionId),\n ...(ownedReadExecutionState === undefined\n ? {}\n : {\n readExecutionState: (sessionId: string) =>\n ownedReadExecutionState(sessionId, { parentSessionId: options.sessionId }),\n }),\n // A session may drive only the children it started, never its parent or a sibling.\n ...(ownedPrompt === undefined\n ? {}\n : {\n prompt: (sessionId: string, text: string, promptOptions?: PoHubSessionPromptOptions) =>\n ownedPrompt(sessionId, text, {\n ...(promptOptions?.signal === undefined ? {} : { signal: promptOptions.signal }),\n parentSessionId: options.sessionId,\n }),\n }),\n ...(ownedAbort === undefined\n ? {}\n : {\n abort: (sessionId: string) => ownedAbort(sessionId, { parentSessionId: options.sessionId }),\n }),\n ...(ownedSteer === undefined\n ? {}\n : {\n steer: (sessionId: string, message: string) =>\n ownedSteer(sessionId, message, { parentSessionId: options.sessionId }),\n }),\n ...(ownedRelease === undefined\n ? {}\n : {\n release: (sessionId, releaseOptions) =>\n ownedRelease(sessionId, {\n ...(releaseOptions?.onlyIfIdle === undefined ? {} : { onlyIfIdle: releaseOptions.onlyIfIdle }),\n parentSessionId: options.sessionId,\n }),\n }),\n ...(owningSessionService.canCommunicate === undefined\n ? {}\n : {\n canCommunicate: (sourceSessionId: string, targetSessionId: string) =>\n owningSessionService.canCommunicate?.(sourceSessionId, targetSessionId) === true,\n }),\n };\n const context: PoApiContext = {\n scope: 'session',\n sessionId: options.sessionId,\n workspaceId: options.workspaceId,\n workspaceRoot: options.workspaceRoot,\n ...(options.sessionContext === undefined ? {} : { sessionContext: options.sessionContext }),\n homeDirectory: options.homeDirectory ?? options.platform.homeDirectory,\n sessionStorage: options.platform.sessions,\n cwd: options.cwd,\n environment: options.environment,\n directEvents: options.directEvents,\n ...(options.computerUse === undefined ? {} : { computerUse: options.computerUse }),\n ...(options.shareFile === undefined ? {} : { shareFile: options.shareFile }),\n ...(options.mediaArbitration === undefined ? {} : { mediaArbitration: options.mediaArbitration }),\n ...(options.executionBudget === undefined ? {} : { executionBudget: options.executionBudget }),\n ...(options.editPresence === undefined ? {} : { editPresence: options.editPresence }),\n ...(options.peerAgents === undefined ? {} : { peerAgents: options.peerAgents }),\n ...(options.requestApi === undefined ? {} : { requestApi: options.requestApi }),\n ...(options.publishActivity === undefined ? {} : { publishActivity: options.publishActivity }),\n ...(options.internalToken === undefined ? {} : { internalToken: options.internalToken }),\n ...(options.hubToken === undefined ? {} : { hubToken: options.hubToken }),\n ...(exposedSessionService === undefined ? {} : { sessionService: exposedSessionService }),\n ...(communication === undefined ? {} : { sessionCommunication: communication }),\n onNotice: options.onNotice,\n };\n const host = createPoServerHost({\n scope: 'session',\n context,\n mountChannel: options.mountChannel,\n ...(options.pluginRegistry === undefined ? {} : { pluginRegistry: options.pluginRegistry }),\n });\n // The session's own folder, its workspace and the temp folders of every session under this Po home;\n // extensions open their own beside these.\n const fileAccess = createFileAccess();\n const tempDirectory = options.environment[PO_SESSION_TEMP_DIR];\n const sessionsTemp =\n tempDirectory !== undefined && path.basename(path.dirname(tempDirectory)) === 'sessions'\n ? path.dirname(tempDirectory)\n : tempDirectory;\n for (const directory of [options.cwd, options.workspaceRoot, sessionsTemp])\n if (directory !== undefined && path.isAbsolute(directory)) fileAccess.register(directory);\n let scope: PoServerScope | undefined;\n const installOptions = (failed?: PoServerPackageFailure[]) => ({\n onNotice: options.onNotice,\n ...(failed === undefined ? {} : { onPackageFailed: (failure: PoServerPackageFailure) => failed.push(failure) }),\n onPackageSettled: (report: { packageName: string; outcome: string; durationMs: number }) => {\n if (!options.telemetry) return;\n observe(\n options.telemetry.recordEvent('po_server.package_install', {\n 'package.name': report.packageName,\n 'package.outcome': report.outcome,\n duration_ms: report.durationMs,\n scope: 'session',\n }),\n options.onNotice,\n );\n },\n });\n try {\n // Registered first, so no package can claim the host's own base path.\n host.registerApi(sessionFilesApi(fileAccess, options.cwd));\n for (const api of options.apis) host.registerApi(api);\n // The session's own level of the server IoC tree; it closes with the hub level it opens below.\n scope = await PoServerScope.open('session', options.parentScope);\n await scope.serve({ host, platform: options.platform });\n await scope.root.plugin((root) => {\n root.provide(PO_FILE_ACCESS_SERVICE, fileAccess);\n });\n await options.preparePackages?.(scope.root);\n const installed = await scope.install(packages, installOptions());\n await options.activatePackages?.(installed);\n } catch (error) {\n await scope?.dispose();\n communication?.close();\n host.dispose();\n throw error;\n }\n if (host.mounted().length === 0 && routesNothing()) {\n await scope.dispose();\n communication?.close();\n host.dispose();\n return {\n request: async () => Response.json({ error: 'No package APIs are mounted.' }, { status: 404 }),\n close: () => Promise.resolve(),\n };\n }\n const sessionScope = scope;\n const reconcile = async (\n next: readonly (PoServerPlugin | LoadedServerPackage)[],\n declared: readonly string[],\n ): Promise<PackageReconcileResult> => {\n const failed: PoServerPackageFailure[] = [];\n const installed = await sessionScope.install(next, installOptions(failed));\n const removed = installed.filter((name) => !declared.includes(name));\n return { installed: await sessionScope.uninstall(removed), removed, failed };\n };\n\n const dispatch = async (request: Request): Promise<Response> => {\n if (options.canDispatch?.() === false)\n return Response.json({ error: 'Session selection is not ready.' }, { status: 503 });\n const url = new URL(request.url);\n if (!url.pathname.startsWith(`${PO_API_ROUTE_PREFIX}/`))\n return Response.json({ error: 'Not found.' }, { status: 404 });\n const rest = url.pathname.slice(PO_API_ROUTE_PREFIX.length + 1);\n const slash = rest.indexOf('/');\n const basePath = slash === -1 ? rest : rest.slice(0, slash);\n // The session's own API, else the nearest level above it that serves one.\n const handler = sessionScope.apiHandler(basePath);\n if (handler === undefined)\n return Response.json({ error: `No API '${basePath}' in this session.` }, { status: 404 });\n url.pathname = slash === -1 ? '/' : rest.slice(slash);\n const startedAt = performance.now();\n const parent = validatedTraceContext(request.headers.get('traceparent'));\n let childContext: PoTraceContext | undefined;\n let response: Response;\n try {\n const invoke = async (context?: PoTraceContext): Promise<Response> => {\n if (options.canDispatch?.() === false || sessionScope.apiHandler(basePath) !== handler) {\n return Response.json({ error: 'Session selection changed before dispatch.' }, { status: 503 });\n }\n childContext = context;\n const incoming = new Request(url, request);\n try {\n return await handler.fetch(incoming);\n } catch (error) {\n if (incoming.signal.aborted && (error === incoming.signal.reason || error instanceof OperationCancelledError))\n return new Response(null, { status: 499 });\n throw error;\n }\n };\n response = options.telemetry\n ? await options.telemetry.runInSpan(\n 'po_server.package_api.request',\n { api: basePath, method: request.method },\n invoke,\n parent,\n )\n : await invoke();\n } catch (error) {\n options.onNotice(`package API '${basePath}' failed on ${url.pathname} (${describeError(error)})`);\n response = Response.json({ error: `The '${basePath}' API failed.` }, { status: 500 });\n }\n return responseWithCompletion(response, (responseBytes) => {\n if (!options.telemetry) return;\n const durationMs = Math.round(performance.now() - startedAt);\n // Keep this span only where it says something the request span cannot: a failing status,\n // a body that took real time to flush after the headers were sent, or a large body.\n if (\n response.status < 400 &&\n durationMs < COMPLETION_SPAN_MIN_DURATION_MS &&\n responseBytes < COMPLETION_SPAN_MIN_BYTES\n )\n return;\n const requestBytes = Number(request.headers.get('content-length'));\n observe(\n options.telemetry.runInSpan(\n 'po_server.package_api.complete',\n {\n api: basePath,\n method: request.method,\n status_code: response.status,\n duration_ms: durationMs,\n response_bytes: responseBytes,\n response_kind: contentClass(response.headers.get('content-type')),\n ...(Number.isFinite(requestBytes) && requestBytes > 0 ? { request_bytes: requestBytes } : {}),\n },\n async () => undefined,\n childContext ?? parent,\n ),\n options.onNotice,\n );\n });\n };\n let closePromise: Promise<void> | undefined;\n\n return {\n operation: (basePath, method, path) => scope?.apiOperation(basePath, method, path),\n request: dispatch,\n reconcile,\n close: () => {\n closePromise ??= (async () => {\n await scope.dispose();\n communication?.close();\n host.dispose();\n })();\n return closePromise;\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"packageApiServer.cjs","names":["SESSION_FILES_API","readAccessibleFile","prepareSessionEnvironment","createPoServerHost","createFileAccess","PO_SESSION_TEMP_DIR","path","PoServerScope","PO_FILE_ACCESS_SERVICE","PO_API_ROUTE_PREFIX","validatedTraceContext","OperationCancelledError"],"sources":["../src/server/packageApiServer.ts"],"sourcesContent":["import path from 'node:path';\n\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport { SESSION_FILES_API } from '../constants/sessionFiles';\nimport type { PoDirectEventBus, PoHubSessionPromptOptions, PoHubSessionService } from '../exports/hubChannel';\nimport { PO_API_ROUTE_PREFIX, type PoApi, type PoApiContext } from '../exports/packageApi';\nimport type { PoPlatform } from '../exports/platform';\nimport type { PoPluginRegistry } from '../exports/pluginProtocol';\nimport {\n createPoServerHost,\n type CreatePoServerHostOptions,\n type LoadedServerPackage,\n type PoServerPackageFailure,\n type PoServerPlugin,\n PoServerScope,\n} from '../exports/serverPlugin';\nimport {\n createFileAccess,\n PO_FILE_ACCESS_SERVICE,\n type PoFileAccess,\n readAccessibleFile,\n} from '../services/fileAccess';\nimport type { PoTraceContext } from '../services/poTelemetry';\nimport { observe, type ServerTelemetry } from '../services/serverTelemetry';\nimport { PO_SESSION_TEMP_DIR, prepareSessionEnvironment } from '../services/sessionFiles';\nimport { OperationCancelledError } from '../services/telemetry';\nimport { validatedTraceContext } from '../services/traceContext';\n\n/**\n * A body that keeps streaming well after its headers went out is worth its own span. A fast\n * one is not: the request span already carries the duration and outcome, so emitting a second\n * span per call made package API traffic 99.65% of all spans recorded in a two hour sample.\n */\nexport const COMPLETION_SPAN_MIN_DURATION_MS = 1000;\n/** A body this large is worth a span however fast it flushed: big bodies drive heap spikes. */\nexport const COMPLETION_SPAN_MIN_BYTES = 1024 * 1024;\n\n/** A coarse body class, so a size can be attributed without recording a path or file name. */\nexport function contentClass(contentType: string | null): string {\n const type = contentType?.split(';')[0]?.trim().toLowerCase() ?? '';\n if (type === '') return 'none';\n if (type.startsWith('image/')) return 'image';\n if (type.endsWith('json')) return 'json';\n if (type.startsWith('text/')) return 'text';\n return 'binary';\n}\n\nexport interface PackageApiServerOptions {\n sessionId: string;\n workspaceId?: string;\n workspaceRoot?: string;\n sessionContext?: PoApiContext['sessionContext'];\n homeDirectory?: string;\n mountChannel?: CreatePoServerHostOptions['mountChannel'];\n cwd: string;\n /** Admitted environment for APIs serving this session. */\n readonly environment: Readonly<Record<string, string | undefined>>;\n /** Lifecycle-owned direct events shared with the hub. */\n readonly directEvents: PoDirectEventBus;\n readonly computerUse?: PoApiContext['computerUse'];\n /** Bound to this session's id and cwd by the host. */\n readonly shareFile?: PoApiContext['shareFile'];\n readonly shareRegisteredFile?: PoApiContext['shareRegisteredFile'];\n readonly mediaArbitration?: PoApiContext['mediaArbitration'];\n readonly executionBudget?: PoApiContext['executionBudget'];\n readonly editPresence?: PoApiContext['editPresence'];\n readonly peerAgents?: PoApiContext['peerAgents'];\n readonly requestApi?: PoApiContext['requestApi'];\n /** Publishes this session's activity line through the hub; bound to this session only. */\n readonly publishActivity?: PoApiContext['publishActivity'];\n internalToken?: string;\n hubToken?: string;\n /** Shared exact-scope dispatch table, so session packages can mount plugin methods. */\n pluginRegistry?: PoPluginRegistry;\n sessionService?: PoHubSessionService;\n apis: readonly PoApi[];\n /** Server packages to install; each registers its own APIs through the host. */\n packages?: readonly (PoServerPlugin | LoadedServerPackage)[];\n /** Publishes the session's own services into its scope root before any package installs. */\n preparePackages?: (root: Context) => void | Promise<void>;\n /** The hub's workspace or global scope; the session's own scope opens below it and closes with it. */\n parentScope?: PoServerScope;\n /** Host capabilities for the session scope root, injected by the composition root. */\n platform: PoPlatform;\n activatePackages?: (installedPackages: readonly string[]) => Promise<void>;\n canDispatch?: () => boolean;\n telemetry?: ServerTelemetry;\n onNotice: (message: string) => void;\n}\n\nexport interface PackageApiServer {\n /** Dispatches an already-authenticated request without crossing a transport boundary. */\n readonly operation?: (basePath: string, method: string, path: string) => string | undefined;\n readonly request: (request: Request) => Promise<Response>;\n /**\n * Brings the session's installed packages up to date: changed and added `packages` install with\n * per-package rollback, and installed ones no longer `declared` are disposed. A declared package\n * whose new build failed to load is absent from `packages` but keeps its running build.\n */\n readonly reconcile?: (\n packages: readonly (PoServerPlugin | LoadedServerPackage)[],\n declared: readonly string[],\n ) => Promise<PackageReconcileResult>;\n close(): Promise<void>;\n}\n\nexport interface PackageReconcileResult {\n readonly installed: readonly string[];\n readonly removed: readonly string[];\n readonly failed: readonly PoServerPackageFailure[];\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction responseWithCompletion(response: Response, complete: (bytes: number) => void): Response {\n if (response.body === null) {\n complete(0);\n return response;\n }\n const reader = response.body.getReader();\n let completed = false;\n let bytes = 0;\n const finish = (): void => {\n if (completed) return;\n completed = true;\n complete(bytes);\n };\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const result = await reader.read();\n if (result.done) {\n finish();\n controller.close();\n } else {\n bytes += result.value.byteLength;\n controller.enqueue(result.value);\n }\n } catch (error) {\n finish();\n controller.error(error);\n }\n },\n async cancel(reason) {\n finish();\n await reader.cancel(reason);\n },\n });\n return new Response(body, response);\n}\n\n/** Reads files under the session's open folders; the hub's session `/file` route forwards here. */\nfunction sessionFilesApi(fileAccess: PoFileAccess, cwd: string): PoApi {\n return {\n basePath: SESSION_FILES_API,\n start: () => ({\n fetch: (request) => {\n if (request.method !== 'GET') return Response.json({ error: 'Method not allowed.' }, { status: 405 });\n return readAccessibleFile({\n roots: fileAccess.roots(),\n cwd,\n path: new URL(request.url).searchParams.get('path'),\n ...(request.headers.get('range') === null ? {} : { range: request.headers.get('range')! }),\n ...(request.headers.get('if-match') === null ? {} : { expectedVersion: request.headers.get('if-match')! }),\n });\n },\n close: () => undefined,\n }),\n };\n}\n/** Installs session packages and dispatches authenticated package API calls in-process. */\nexport async function serveSessionApis(options: PackageApiServerOptions): Promise<PackageApiServer> {\n if (options.environment === undefined)\n throw new Error('Session package API server requires an admitted environment.');\n if (options.directEvents === undefined) throw new Error('Session package API server requires direct events.');\n options = { ...options, environment: await prepareSessionEnvironment(options) };\n const packages = options.packages ?? [];\n // Without a level above it, a session that mounts nothing has nothing to route.\n const routesNothing = (): boolean => !options.preparePackages && options.parentScope === undefined;\n if (options.apis.length === 0 && packages.length === 0 && routesNothing()) {\n return {\n request: async () => Response.json({ error: 'No package APIs are mounted.' }, { status: 404 }),\n close: () => Promise.resolve(),\n };\n }\n\n const owningSessionService = options.sessionService;\n const communication = owningSessionService?.bindCommunication?.(options.sessionId);\n const ownedReadExecutionState = owningSessionService?.readExecutionState?.bind(owningSessionService);\n const ownedPrompt = owningSessionService?.prompt?.bind(owningSessionService);\n const ownedAbort = owningSessionService?.abort?.bind(owningSessionService);\n const ownedSteer = owningSessionService?.steer?.bind(owningSessionService);\n const ownedRelease = owningSessionService?.release?.bind(owningSessionService);\n const exposedSessionService: PoHubSessionService | undefined =\n owningSessionService === undefined\n ? undefined\n : {\n create: (request) => owningSessionService.create(request),\n close: (sessionId) => owningSessionService.close(sessionId),\n isLive: (sessionId) => owningSessionService.isLive(sessionId),\n ...(ownedReadExecutionState === undefined\n ? {}\n : {\n readExecutionState: (sessionId: string) =>\n ownedReadExecutionState(sessionId, { parentSessionId: options.sessionId }),\n }),\n // A session may drive only the children it started, never its parent or a sibling.\n ...(ownedPrompt === undefined\n ? {}\n : {\n prompt: (sessionId: string, text: string, promptOptions?: PoHubSessionPromptOptions) =>\n ownedPrompt(sessionId, text, {\n ...(promptOptions?.signal === undefined ? {} : { signal: promptOptions.signal }),\n parentSessionId: options.sessionId,\n }),\n }),\n ...(ownedAbort === undefined\n ? {}\n : {\n abort: (sessionId: string) => ownedAbort(sessionId, { parentSessionId: options.sessionId }),\n }),\n ...(ownedSteer === undefined\n ? {}\n : {\n steer: (sessionId: string, message: string) =>\n ownedSteer(sessionId, message, { parentSessionId: options.sessionId }),\n }),\n ...(ownedRelease === undefined\n ? {}\n : {\n release: (sessionId, releaseOptions) =>\n ownedRelease(sessionId, {\n ...(releaseOptions?.onlyIfIdle === undefined ? {} : { onlyIfIdle: releaseOptions.onlyIfIdle }),\n parentSessionId: options.sessionId,\n }),\n }),\n ...(owningSessionService.canCommunicate === undefined\n ? {}\n : {\n canCommunicate: (sourceSessionId: string, targetSessionId: string) =>\n owningSessionService.canCommunicate?.(sourceSessionId, targetSessionId) === true,\n }),\n };\n const context: PoApiContext = {\n scope: 'session',\n sessionId: options.sessionId,\n workspaceId: options.workspaceId,\n workspaceRoot: options.workspaceRoot,\n ...(options.sessionContext === undefined ? {} : { sessionContext: options.sessionContext }),\n homeDirectory: options.homeDirectory ?? options.platform.homeDirectory,\n sessionStorage: options.platform.sessions,\n cwd: options.cwd,\n environment: options.environment,\n directEvents: options.directEvents,\n ...(options.computerUse === undefined ? {} : { computerUse: options.computerUse }),\n ...(options.shareFile === undefined ? {} : { shareFile: options.shareFile }),\n ...(options.shareRegisteredFile === undefined ? {} : { shareRegisteredFile: options.shareRegisteredFile }),\n ...(options.mediaArbitration === undefined ? {} : { mediaArbitration: options.mediaArbitration }),\n ...(options.executionBudget === undefined ? {} : { executionBudget: options.executionBudget }),\n ...(options.editPresence === undefined ? {} : { editPresence: options.editPresence }),\n ...(options.peerAgents === undefined ? {} : { peerAgents: options.peerAgents }),\n ...(options.requestApi === undefined ? {} : { requestApi: options.requestApi }),\n ...(options.publishActivity === undefined ? {} : { publishActivity: options.publishActivity }),\n ...(options.internalToken === undefined ? {} : { internalToken: options.internalToken }),\n ...(options.hubToken === undefined ? {} : { hubToken: options.hubToken }),\n ...(exposedSessionService === undefined ? {} : { sessionService: exposedSessionService }),\n ...(communication === undefined ? {} : { sessionCommunication: communication }),\n onNotice: options.onNotice,\n };\n const host = createPoServerHost({\n scope: 'session',\n context,\n mountChannel: options.mountChannel,\n ...(options.pluginRegistry === undefined ? {} : { pluginRegistry: options.pluginRegistry }),\n });\n // The session's own folder, its workspace and the temp folders of every session under this Po home;\n // extensions open their own beside these.\n const fileAccess = createFileAccess();\n const tempDirectory = options.environment[PO_SESSION_TEMP_DIR];\n const sessionsTemp =\n tempDirectory !== undefined && path.basename(path.dirname(tempDirectory)) === 'sessions'\n ? path.dirname(tempDirectory)\n : tempDirectory;\n for (const directory of [options.cwd, options.workspaceRoot, sessionsTemp])\n if (directory !== undefined && path.isAbsolute(directory)) fileAccess.register(directory);\n let scope: PoServerScope | undefined;\n const installOptions = (failed?: PoServerPackageFailure[]) => ({\n onNotice: options.onNotice,\n ...(failed === undefined ? {} : { onPackageFailed: (failure: PoServerPackageFailure) => failed.push(failure) }),\n onPackageSettled: (report: { packageName: string; outcome: string; durationMs: number }) => {\n if (!options.telemetry) return;\n observe(\n options.telemetry.recordEvent('po_server.package_install', {\n 'package.name': report.packageName,\n 'package.outcome': report.outcome,\n duration_ms: report.durationMs,\n scope: 'session',\n }),\n options.onNotice,\n );\n },\n });\n try {\n // Registered first, so no package can claim the host's own base path.\n host.registerApi(sessionFilesApi(fileAccess, options.cwd));\n for (const api of options.apis) host.registerApi(api);\n // The session's own level of the server IoC tree; it closes with the hub level it opens below.\n scope = await PoServerScope.open('session', options.parentScope);\n await scope.serve({ host, platform: options.platform });\n await scope.root.plugin((root) => {\n root.provide(PO_FILE_ACCESS_SERVICE, fileAccess);\n });\n await options.preparePackages?.(scope.root);\n const installed = await scope.install(packages, installOptions());\n await options.activatePackages?.(installed);\n } catch (error) {\n await scope?.dispose();\n communication?.close();\n host.dispose();\n throw error;\n }\n if (host.mounted().length === 0 && routesNothing()) {\n await scope.dispose();\n communication?.close();\n host.dispose();\n return {\n request: async () => Response.json({ error: 'No package APIs are mounted.' }, { status: 404 }),\n close: () => Promise.resolve(),\n };\n }\n const sessionScope = scope;\n const reconcile = async (\n next: readonly (PoServerPlugin | LoadedServerPackage)[],\n declared: readonly string[],\n ): Promise<PackageReconcileResult> => {\n const failed: PoServerPackageFailure[] = [];\n const installed = await sessionScope.install(next, installOptions(failed));\n const removed = installed.filter((name) => !declared.includes(name));\n return { installed: await sessionScope.uninstall(removed), removed, failed };\n };\n\n const dispatch = async (request: Request): Promise<Response> => {\n if (options.canDispatch?.() === false)\n return Response.json({ error: 'Session selection is not ready.' }, { status: 503 });\n const url = new URL(request.url);\n if (!url.pathname.startsWith(`${PO_API_ROUTE_PREFIX}/`))\n return Response.json({ error: 'Not found.' }, { status: 404 });\n const rest = url.pathname.slice(PO_API_ROUTE_PREFIX.length + 1);\n const slash = rest.indexOf('/');\n const basePath = slash === -1 ? rest : rest.slice(0, slash);\n // The session's own API, else the nearest level above it that serves one.\n const handler = sessionScope.apiHandler(basePath);\n if (handler === undefined)\n return Response.json({ error: `No API '${basePath}' in this session.` }, { status: 404 });\n url.pathname = slash === -1 ? '/' : rest.slice(slash);\n const startedAt = performance.now();\n const parent = validatedTraceContext(request.headers.get('traceparent'));\n let childContext: PoTraceContext | undefined;\n let response: Response;\n try {\n const invoke = async (context?: PoTraceContext): Promise<Response> => {\n if (options.canDispatch?.() === false || sessionScope.apiHandler(basePath) !== handler) {\n return Response.json({ error: 'Session selection changed before dispatch.' }, { status: 503 });\n }\n childContext = context;\n const incoming = new Request(url, request);\n try {\n return await handler.fetch(incoming);\n } catch (error) {\n if (incoming.signal.aborted && (error === incoming.signal.reason || error instanceof OperationCancelledError))\n return new Response(null, { status: 499 });\n throw error;\n }\n };\n response = options.telemetry\n ? await options.telemetry.runInSpan(\n 'po_server.package_api.request',\n { api: basePath, method: request.method },\n invoke,\n parent,\n )\n : await invoke();\n } catch (error) {\n options.onNotice(`package API '${basePath}' failed on ${url.pathname} (${describeError(error)})`);\n response = Response.json({ error: `The '${basePath}' API failed.` }, { status: 500 });\n }\n return responseWithCompletion(response, (responseBytes) => {\n if (!options.telemetry) return;\n const durationMs = Math.round(performance.now() - startedAt);\n // Keep this span only where it says something the request span cannot: a failing status,\n // a body that took real time to flush after the headers were sent, or a large body.\n if (\n response.status < 400 &&\n durationMs < COMPLETION_SPAN_MIN_DURATION_MS &&\n responseBytes < COMPLETION_SPAN_MIN_BYTES\n )\n return;\n const requestBytes = Number(request.headers.get('content-length'));\n observe(\n options.telemetry.runInSpan(\n 'po_server.package_api.complete',\n {\n api: basePath,\n method: request.method,\n status_code: response.status,\n duration_ms: durationMs,\n response_bytes: responseBytes,\n response_kind: contentClass(response.headers.get('content-type')),\n ...(Number.isFinite(requestBytes) && requestBytes > 0 ? { request_bytes: requestBytes } : {}),\n },\n async () => undefined,\n childContext ?? parent,\n ),\n options.onNotice,\n );\n });\n };\n let closePromise: Promise<void> | undefined;\n\n return {\n operation: (basePath, method, path) => scope?.apiOperation(basePath, method, path),\n request: dispatch,\n reconcile,\n close: () => {\n closePromise ??= (async () => {\n await scope.dispose();\n communication?.close();\n host.dispose();\n })();\n return closePromise;\n },\n };\n}\n"],"mappings":"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"}
@@ -39,6 +39,7 @@ export interface PackageApiServerOptions {
39
39
  readonly computerUse?: PoApiContext['computerUse'];
40
40
  /** Bound to this session's id and cwd by the host. */
41
41
  readonly shareFile?: PoApiContext['shareFile'];
42
+ readonly shareRegisteredFile?: PoApiContext['shareRegisteredFile'];
42
43
  readonly mediaArbitration?: PoApiContext['mediaArbitration'];
43
44
  readonly executionBudget?: PoApiContext['executionBudget'];
44
45
  readonly editPresence?: PoApiContext['editPresence'];
@@ -1 +1 @@
1
- {"version":3,"file":"packageApiServer.d.mts","names":[],"sources":["../src/server/packageApiServer.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;qBAkCa;;qBAEA;;wBAGG,aAAa;iBASZ;EACf;EACA;EACA;EACA,iBAAiB;EACjB;EACA,eAAe;EACf;;WAES,aAAa,SAAS;;WAEtB,cAAc;WACd,cAAc;;WAEd,YAAY;WACZ,mBAAmB;WACnB,kBAAkB;WAClB,eAAe;WACf,aAAa;WACb,aAAa;;WAEb,kBAAkB;EAC3B;EACA;;EAEA,iBAAiB;EACjB,iBAAiB;EACjB,eAAe;;EAEf,qBAAqB,iBAAiB;;EAEtC,mBAAmB,MAAM,mBAAmB;;EAE5C,cAAc;;EAEd,UAAU;EACV,oBAAoB,yCAAyC;EAC7D;EACA,YAAY;EACZ,WAAW;;iBAGI;;WAEN,aAAa,kBAAkB,gBAAgB;WAC/C,UAAU,SAAS,YAAY,QAAQ;;;;;;WAMvC,aACP,oBAAoB,iBAAiB,wBACrC,gCACG,QAAQ;EACb,SAAS;;iBAGM;WACN;WACA;WACA,iBAAiB;;;wBAgEN,iBAAiB,SAAS,0BAA0B,QAAQ"}
1
+ {"version":3,"file":"packageApiServer.d.mts","names":[],"sources":["../src/server/packageApiServer.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;;qBAkCa;;qBAEA;;wBAGG,aAAa;iBASZ;EACf;EACA;EACA;EACA,iBAAiB;EACjB;EACA,eAAe;EACf;;WAES,aAAa,SAAS;;WAEtB,cAAc;WACd,cAAc;;WAEd,YAAY;WACZ,sBAAsB;WACtB,mBAAmB;WACnB,kBAAkB;WAClB,eAAe;WACf,aAAa;WACb,aAAa;;WAEb,kBAAkB;EAC3B;EACA;;EAEA,iBAAiB;EACjB,iBAAiB;EACjB,eAAe;;EAEf,qBAAqB,iBAAiB;;EAEtC,mBAAmB,MAAM,mBAAmB;;EAE5C,cAAc;;EAEd,UAAU;EACV,oBAAoB,yCAAyC;EAC7D;EACA,YAAY;EACZ,WAAW;;iBAGI;;WAEN,aAAa,kBAAkB,gBAAgB;WAC/C,UAAU,SAAS,YAAY,QAAQ;;;;;;WAMvC,aACP,oBAAoB,iBAAiB,wBACrC,gCACG,QAAQ;EACb,SAAS;;iBAGM;WACN;WACA;WACA,iBAAiB;;;wBAgEN,iBAAiB,SAAS,0BAA0B,QAAQ"}
@@ -138,6 +138,7 @@ async function serveSessionApis(options) {
138
138
  directEvents: options.directEvents,
139
139
  ...options.computerUse === void 0 ? {} : { computerUse: options.computerUse },
140
140
  ...options.shareFile === void 0 ? {} : { shareFile: options.shareFile },
141
+ ...options.shareRegisteredFile === void 0 ? {} : { shareRegisteredFile: options.shareRegisteredFile },
141
142
  ...options.mediaArbitration === void 0 ? {} : { mediaArbitration: options.mediaArbitration },
142
143
  ...options.executionBudget === void 0 ? {} : { executionBudget: options.executionBudget },
143
144
  ...options.editPresence === void 0 ? {} : { editPresence: options.editPresence },
@@ -1 +1 @@
1
- {"version":3,"file":"packageApiServer.mjs","names":[],"sources":["../src/server/packageApiServer.ts"],"sourcesContent":["import path from 'node:path';\n\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport { SESSION_FILES_API } from '../constants/sessionFiles';\nimport type { PoDirectEventBus, PoHubSessionPromptOptions, PoHubSessionService } from '../exports/hubChannel';\nimport { PO_API_ROUTE_PREFIX, type PoApi, type PoApiContext } from '../exports/packageApi';\nimport type { PoPlatform } from '../exports/platform';\nimport type { PoPluginRegistry } from '../exports/pluginProtocol';\nimport {\n createPoServerHost,\n type CreatePoServerHostOptions,\n type LoadedServerPackage,\n type PoServerPackageFailure,\n type PoServerPlugin,\n PoServerScope,\n} from '../exports/serverPlugin';\nimport {\n createFileAccess,\n PO_FILE_ACCESS_SERVICE,\n type PoFileAccess,\n readAccessibleFile,\n} from '../services/fileAccess';\nimport type { PoTraceContext } from '../services/poTelemetry';\nimport { observe, type ServerTelemetry } from '../services/serverTelemetry';\nimport { PO_SESSION_TEMP_DIR, prepareSessionEnvironment } from '../services/sessionFiles';\nimport { OperationCancelledError } from '../services/telemetry';\nimport { validatedTraceContext } from '../services/traceContext';\n\n/**\n * A body that keeps streaming well after its headers went out is worth its own span. A fast\n * one is not: the request span already carries the duration and outcome, so emitting a second\n * span per call made package API traffic 99.65% of all spans recorded in a two hour sample.\n */\nexport const COMPLETION_SPAN_MIN_DURATION_MS = 1000;\n/** A body this large is worth a span however fast it flushed: big bodies drive heap spikes. */\nexport const COMPLETION_SPAN_MIN_BYTES = 1024 * 1024;\n\n/** A coarse body class, so a size can be attributed without recording a path or file name. */\nexport function contentClass(contentType: string | null): string {\n const type = contentType?.split(';')[0]?.trim().toLowerCase() ?? '';\n if (type === '') return 'none';\n if (type.startsWith('image/')) return 'image';\n if (type.endsWith('json')) return 'json';\n if (type.startsWith('text/')) return 'text';\n return 'binary';\n}\n\nexport interface PackageApiServerOptions {\n sessionId: string;\n workspaceId?: string;\n workspaceRoot?: string;\n sessionContext?: PoApiContext['sessionContext'];\n homeDirectory?: string;\n mountChannel?: CreatePoServerHostOptions['mountChannel'];\n cwd: string;\n /** Admitted environment for APIs serving this session. */\n readonly environment: Readonly<Record<string, string | undefined>>;\n /** Lifecycle-owned direct events shared with the hub. */\n readonly directEvents: PoDirectEventBus;\n readonly computerUse?: PoApiContext['computerUse'];\n /** Bound to this session's id and cwd by the host. */\n readonly shareFile?: PoApiContext['shareFile'];\n readonly mediaArbitration?: PoApiContext['mediaArbitration'];\n readonly executionBudget?: PoApiContext['executionBudget'];\n readonly editPresence?: PoApiContext['editPresence'];\n readonly peerAgents?: PoApiContext['peerAgents'];\n readonly requestApi?: PoApiContext['requestApi'];\n /** Publishes this session's activity line through the hub; bound to this session only. */\n readonly publishActivity?: PoApiContext['publishActivity'];\n internalToken?: string;\n hubToken?: string;\n /** Shared exact-scope dispatch table, so session packages can mount plugin methods. */\n pluginRegistry?: PoPluginRegistry;\n sessionService?: PoHubSessionService;\n apis: readonly PoApi[];\n /** Server packages to install; each registers its own APIs through the host. */\n packages?: readonly (PoServerPlugin | LoadedServerPackage)[];\n /** Publishes the session's own services into its scope root before any package installs. */\n preparePackages?: (root: Context) => void | Promise<void>;\n /** The hub's workspace or global scope; the session's own scope opens below it and closes with it. */\n parentScope?: PoServerScope;\n /** Host capabilities for the session scope root, injected by the composition root. */\n platform: PoPlatform;\n activatePackages?: (installedPackages: readonly string[]) => Promise<void>;\n canDispatch?: () => boolean;\n telemetry?: ServerTelemetry;\n onNotice: (message: string) => void;\n}\n\nexport interface PackageApiServer {\n /** Dispatches an already-authenticated request without crossing a transport boundary. */\n readonly operation?: (basePath: string, method: string, path: string) => string | undefined;\n readonly request: (request: Request) => Promise<Response>;\n /**\n * Brings the session's installed packages up to date: changed and added `packages` install with\n * per-package rollback, and installed ones no longer `declared` are disposed. A declared package\n * whose new build failed to load is absent from `packages` but keeps its running build.\n */\n readonly reconcile?: (\n packages: readonly (PoServerPlugin | LoadedServerPackage)[],\n declared: readonly string[],\n ) => Promise<PackageReconcileResult>;\n close(): Promise<void>;\n}\n\nexport interface PackageReconcileResult {\n readonly installed: readonly string[];\n readonly removed: readonly string[];\n readonly failed: readonly PoServerPackageFailure[];\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction responseWithCompletion(response: Response, complete: (bytes: number) => void): Response {\n if (response.body === null) {\n complete(0);\n return response;\n }\n const reader = response.body.getReader();\n let completed = false;\n let bytes = 0;\n const finish = (): void => {\n if (completed) return;\n completed = true;\n complete(bytes);\n };\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const result = await reader.read();\n if (result.done) {\n finish();\n controller.close();\n } else {\n bytes += result.value.byteLength;\n controller.enqueue(result.value);\n }\n } catch (error) {\n finish();\n controller.error(error);\n }\n },\n async cancel(reason) {\n finish();\n await reader.cancel(reason);\n },\n });\n return new Response(body, response);\n}\n\n/** Reads files under the session's open folders; the hub's session `/file` route forwards here. */\nfunction sessionFilesApi(fileAccess: PoFileAccess, cwd: string): PoApi {\n return {\n basePath: SESSION_FILES_API,\n start: () => ({\n fetch: (request) => {\n if (request.method !== 'GET') return Response.json({ error: 'Method not allowed.' }, { status: 405 });\n return readAccessibleFile({\n roots: fileAccess.roots(),\n cwd,\n path: new URL(request.url).searchParams.get('path'),\n ...(request.headers.get('range') === null ? {} : { range: request.headers.get('range')! }),\n ...(request.headers.get('if-match') === null ? {} : { expectedVersion: request.headers.get('if-match')! }),\n });\n },\n close: () => undefined,\n }),\n };\n}\n/** Installs session packages and dispatches authenticated package API calls in-process. */\nexport async function serveSessionApis(options: PackageApiServerOptions): Promise<PackageApiServer> {\n if (options.environment === undefined)\n throw new Error('Session package API server requires an admitted environment.');\n if (options.directEvents === undefined) throw new Error('Session package API server requires direct events.');\n options = { ...options, environment: await prepareSessionEnvironment(options) };\n const packages = options.packages ?? [];\n // Without a level above it, a session that mounts nothing has nothing to route.\n const routesNothing = (): boolean => !options.preparePackages && options.parentScope === undefined;\n if (options.apis.length === 0 && packages.length === 0 && routesNothing()) {\n return {\n request: async () => Response.json({ error: 'No package APIs are mounted.' }, { status: 404 }),\n close: () => Promise.resolve(),\n };\n }\n\n const owningSessionService = options.sessionService;\n const communication = owningSessionService?.bindCommunication?.(options.sessionId);\n const ownedReadExecutionState = owningSessionService?.readExecutionState?.bind(owningSessionService);\n const ownedPrompt = owningSessionService?.prompt?.bind(owningSessionService);\n const ownedAbort = owningSessionService?.abort?.bind(owningSessionService);\n const ownedSteer = owningSessionService?.steer?.bind(owningSessionService);\n const ownedRelease = owningSessionService?.release?.bind(owningSessionService);\n const exposedSessionService: PoHubSessionService | undefined =\n owningSessionService === undefined\n ? undefined\n : {\n create: (request) => owningSessionService.create(request),\n close: (sessionId) => owningSessionService.close(sessionId),\n isLive: (sessionId) => owningSessionService.isLive(sessionId),\n ...(ownedReadExecutionState === undefined\n ? {}\n : {\n readExecutionState: (sessionId: string) =>\n ownedReadExecutionState(sessionId, { parentSessionId: options.sessionId }),\n }),\n // A session may drive only the children it started, never its parent or a sibling.\n ...(ownedPrompt === undefined\n ? {}\n : {\n prompt: (sessionId: string, text: string, promptOptions?: PoHubSessionPromptOptions) =>\n ownedPrompt(sessionId, text, {\n ...(promptOptions?.signal === undefined ? {} : { signal: promptOptions.signal }),\n parentSessionId: options.sessionId,\n }),\n }),\n ...(ownedAbort === undefined\n ? {}\n : {\n abort: (sessionId: string) => ownedAbort(sessionId, { parentSessionId: options.sessionId }),\n }),\n ...(ownedSteer === undefined\n ? {}\n : {\n steer: (sessionId: string, message: string) =>\n ownedSteer(sessionId, message, { parentSessionId: options.sessionId }),\n }),\n ...(ownedRelease === undefined\n ? {}\n : {\n release: (sessionId, releaseOptions) =>\n ownedRelease(sessionId, {\n ...(releaseOptions?.onlyIfIdle === undefined ? {} : { onlyIfIdle: releaseOptions.onlyIfIdle }),\n parentSessionId: options.sessionId,\n }),\n }),\n ...(owningSessionService.canCommunicate === undefined\n ? {}\n : {\n canCommunicate: (sourceSessionId: string, targetSessionId: string) =>\n owningSessionService.canCommunicate?.(sourceSessionId, targetSessionId) === true,\n }),\n };\n const context: PoApiContext = {\n scope: 'session',\n sessionId: options.sessionId,\n workspaceId: options.workspaceId,\n workspaceRoot: options.workspaceRoot,\n ...(options.sessionContext === undefined ? {} : { sessionContext: options.sessionContext }),\n homeDirectory: options.homeDirectory ?? options.platform.homeDirectory,\n sessionStorage: options.platform.sessions,\n cwd: options.cwd,\n environment: options.environment,\n directEvents: options.directEvents,\n ...(options.computerUse === undefined ? {} : { computerUse: options.computerUse }),\n ...(options.shareFile === undefined ? {} : { shareFile: options.shareFile }),\n ...(options.mediaArbitration === undefined ? {} : { mediaArbitration: options.mediaArbitration }),\n ...(options.executionBudget === undefined ? {} : { executionBudget: options.executionBudget }),\n ...(options.editPresence === undefined ? {} : { editPresence: options.editPresence }),\n ...(options.peerAgents === undefined ? {} : { peerAgents: options.peerAgents }),\n ...(options.requestApi === undefined ? {} : { requestApi: options.requestApi }),\n ...(options.publishActivity === undefined ? {} : { publishActivity: options.publishActivity }),\n ...(options.internalToken === undefined ? {} : { internalToken: options.internalToken }),\n ...(options.hubToken === undefined ? {} : { hubToken: options.hubToken }),\n ...(exposedSessionService === undefined ? {} : { sessionService: exposedSessionService }),\n ...(communication === undefined ? {} : { sessionCommunication: communication }),\n onNotice: options.onNotice,\n };\n const host = createPoServerHost({\n scope: 'session',\n context,\n mountChannel: options.mountChannel,\n ...(options.pluginRegistry === undefined ? {} : { pluginRegistry: options.pluginRegistry }),\n });\n // The session's own folder, its workspace and the temp folders of every session under this Po home;\n // extensions open their own beside these.\n const fileAccess = createFileAccess();\n const tempDirectory = options.environment[PO_SESSION_TEMP_DIR];\n const sessionsTemp =\n tempDirectory !== undefined && path.basename(path.dirname(tempDirectory)) === 'sessions'\n ? path.dirname(tempDirectory)\n : tempDirectory;\n for (const directory of [options.cwd, options.workspaceRoot, sessionsTemp])\n if (directory !== undefined && path.isAbsolute(directory)) fileAccess.register(directory);\n let scope: PoServerScope | undefined;\n const installOptions = (failed?: PoServerPackageFailure[]) => ({\n onNotice: options.onNotice,\n ...(failed === undefined ? {} : { onPackageFailed: (failure: PoServerPackageFailure) => failed.push(failure) }),\n onPackageSettled: (report: { packageName: string; outcome: string; durationMs: number }) => {\n if (!options.telemetry) return;\n observe(\n options.telemetry.recordEvent('po_server.package_install', {\n 'package.name': report.packageName,\n 'package.outcome': report.outcome,\n duration_ms: report.durationMs,\n scope: 'session',\n }),\n options.onNotice,\n );\n },\n });\n try {\n // Registered first, so no package can claim the host's own base path.\n host.registerApi(sessionFilesApi(fileAccess, options.cwd));\n for (const api of options.apis) host.registerApi(api);\n // The session's own level of the server IoC tree; it closes with the hub level it opens below.\n scope = await PoServerScope.open('session', options.parentScope);\n await scope.serve({ host, platform: options.platform });\n await scope.root.plugin((root) => {\n root.provide(PO_FILE_ACCESS_SERVICE, fileAccess);\n });\n await options.preparePackages?.(scope.root);\n const installed = await scope.install(packages, installOptions());\n await options.activatePackages?.(installed);\n } catch (error) {\n await scope?.dispose();\n communication?.close();\n host.dispose();\n throw error;\n }\n if (host.mounted().length === 0 && routesNothing()) {\n await scope.dispose();\n communication?.close();\n host.dispose();\n return {\n request: async () => Response.json({ error: 'No package APIs are mounted.' }, { status: 404 }),\n close: () => Promise.resolve(),\n };\n }\n const sessionScope = scope;\n const reconcile = async (\n next: readonly (PoServerPlugin | LoadedServerPackage)[],\n declared: readonly string[],\n ): Promise<PackageReconcileResult> => {\n const failed: PoServerPackageFailure[] = [];\n const installed = await sessionScope.install(next, installOptions(failed));\n const removed = installed.filter((name) => !declared.includes(name));\n return { installed: await sessionScope.uninstall(removed), removed, failed };\n };\n\n const dispatch = async (request: Request): Promise<Response> => {\n if (options.canDispatch?.() === false)\n return Response.json({ error: 'Session selection is not ready.' }, { status: 503 });\n const url = new URL(request.url);\n if (!url.pathname.startsWith(`${PO_API_ROUTE_PREFIX}/`))\n return Response.json({ error: 'Not found.' }, { status: 404 });\n const rest = url.pathname.slice(PO_API_ROUTE_PREFIX.length + 1);\n const slash = rest.indexOf('/');\n const basePath = slash === -1 ? rest : rest.slice(0, slash);\n // The session's own API, else the nearest level above it that serves one.\n const handler = sessionScope.apiHandler(basePath);\n if (handler === undefined)\n return Response.json({ error: `No API '${basePath}' in this session.` }, { status: 404 });\n url.pathname = slash === -1 ? '/' : rest.slice(slash);\n const startedAt = performance.now();\n const parent = validatedTraceContext(request.headers.get('traceparent'));\n let childContext: PoTraceContext | undefined;\n let response: Response;\n try {\n const invoke = async (context?: PoTraceContext): Promise<Response> => {\n if (options.canDispatch?.() === false || sessionScope.apiHandler(basePath) !== handler) {\n return Response.json({ error: 'Session selection changed before dispatch.' }, { status: 503 });\n }\n childContext = context;\n const incoming = new Request(url, request);\n try {\n return await handler.fetch(incoming);\n } catch (error) {\n if (incoming.signal.aborted && (error === incoming.signal.reason || error instanceof OperationCancelledError))\n return new Response(null, { status: 499 });\n throw error;\n }\n };\n response = options.telemetry\n ? await options.telemetry.runInSpan(\n 'po_server.package_api.request',\n { api: basePath, method: request.method },\n invoke,\n parent,\n )\n : await invoke();\n } catch (error) {\n options.onNotice(`package API '${basePath}' failed on ${url.pathname} (${describeError(error)})`);\n response = Response.json({ error: `The '${basePath}' API failed.` }, { status: 500 });\n }\n return responseWithCompletion(response, (responseBytes) => {\n if (!options.telemetry) return;\n const durationMs = Math.round(performance.now() - startedAt);\n // Keep this span only where it says something the request span cannot: a failing status,\n // a body that took real time to flush after the headers were sent, or a large body.\n if (\n response.status < 400 &&\n durationMs < COMPLETION_SPAN_MIN_DURATION_MS &&\n responseBytes < COMPLETION_SPAN_MIN_BYTES\n )\n return;\n const requestBytes = Number(request.headers.get('content-length'));\n observe(\n options.telemetry.runInSpan(\n 'po_server.package_api.complete',\n {\n api: basePath,\n method: request.method,\n status_code: response.status,\n duration_ms: durationMs,\n response_bytes: responseBytes,\n response_kind: contentClass(response.headers.get('content-type')),\n ...(Number.isFinite(requestBytes) && requestBytes > 0 ? { request_bytes: requestBytes } : {}),\n },\n async () => undefined,\n childContext ?? parent,\n ),\n options.onNotice,\n );\n });\n };\n let closePromise: Promise<void> | undefined;\n\n return {\n operation: (basePath, method, path) => scope?.apiOperation(basePath, method, path),\n request: dispatch,\n reconcile,\n close: () => {\n closePromise ??= (async () => {\n await scope.dispose();\n communication?.close();\n host.dispose();\n })();\n return closePromise;\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"packageApiServer.mjs","names":[],"sources":["../src/server/packageApiServer.ts"],"sourcesContent":["import path from 'node:path';\n\nimport type { Context } from '@deepseek-ai/cordis';\n\nimport { SESSION_FILES_API } from '../constants/sessionFiles';\nimport type { PoDirectEventBus, PoHubSessionPromptOptions, PoHubSessionService } from '../exports/hubChannel';\nimport { PO_API_ROUTE_PREFIX, type PoApi, type PoApiContext } from '../exports/packageApi';\nimport type { PoPlatform } from '../exports/platform';\nimport type { PoPluginRegistry } from '../exports/pluginProtocol';\nimport {\n createPoServerHost,\n type CreatePoServerHostOptions,\n type LoadedServerPackage,\n type PoServerPackageFailure,\n type PoServerPlugin,\n PoServerScope,\n} from '../exports/serverPlugin';\nimport {\n createFileAccess,\n PO_FILE_ACCESS_SERVICE,\n type PoFileAccess,\n readAccessibleFile,\n} from '../services/fileAccess';\nimport type { PoTraceContext } from '../services/poTelemetry';\nimport { observe, type ServerTelemetry } from '../services/serverTelemetry';\nimport { PO_SESSION_TEMP_DIR, prepareSessionEnvironment } from '../services/sessionFiles';\nimport { OperationCancelledError } from '../services/telemetry';\nimport { validatedTraceContext } from '../services/traceContext';\n\n/**\n * A body that keeps streaming well after its headers went out is worth its own span. A fast\n * one is not: the request span already carries the duration and outcome, so emitting a second\n * span per call made package API traffic 99.65% of all spans recorded in a two hour sample.\n */\nexport const COMPLETION_SPAN_MIN_DURATION_MS = 1000;\n/** A body this large is worth a span however fast it flushed: big bodies drive heap spikes. */\nexport const COMPLETION_SPAN_MIN_BYTES = 1024 * 1024;\n\n/** A coarse body class, so a size can be attributed without recording a path or file name. */\nexport function contentClass(contentType: string | null): string {\n const type = contentType?.split(';')[0]?.trim().toLowerCase() ?? '';\n if (type === '') return 'none';\n if (type.startsWith('image/')) return 'image';\n if (type.endsWith('json')) return 'json';\n if (type.startsWith('text/')) return 'text';\n return 'binary';\n}\n\nexport interface PackageApiServerOptions {\n sessionId: string;\n workspaceId?: string;\n workspaceRoot?: string;\n sessionContext?: PoApiContext['sessionContext'];\n homeDirectory?: string;\n mountChannel?: CreatePoServerHostOptions['mountChannel'];\n cwd: string;\n /** Admitted environment for APIs serving this session. */\n readonly environment: Readonly<Record<string, string | undefined>>;\n /** Lifecycle-owned direct events shared with the hub. */\n readonly directEvents: PoDirectEventBus;\n readonly computerUse?: PoApiContext['computerUse'];\n /** Bound to this session's id and cwd by the host. */\n readonly shareFile?: PoApiContext['shareFile'];\n readonly shareRegisteredFile?: PoApiContext['shareRegisteredFile'];\n readonly mediaArbitration?: PoApiContext['mediaArbitration'];\n readonly executionBudget?: PoApiContext['executionBudget'];\n readonly editPresence?: PoApiContext['editPresence'];\n readonly peerAgents?: PoApiContext['peerAgents'];\n readonly requestApi?: PoApiContext['requestApi'];\n /** Publishes this session's activity line through the hub; bound to this session only. */\n readonly publishActivity?: PoApiContext['publishActivity'];\n internalToken?: string;\n hubToken?: string;\n /** Shared exact-scope dispatch table, so session packages can mount plugin methods. */\n pluginRegistry?: PoPluginRegistry;\n sessionService?: PoHubSessionService;\n apis: readonly PoApi[];\n /** Server packages to install; each registers its own APIs through the host. */\n packages?: readonly (PoServerPlugin | LoadedServerPackage)[];\n /** Publishes the session's own services into its scope root before any package installs. */\n preparePackages?: (root: Context) => void | Promise<void>;\n /** The hub's workspace or global scope; the session's own scope opens below it and closes with it. */\n parentScope?: PoServerScope;\n /** Host capabilities for the session scope root, injected by the composition root. */\n platform: PoPlatform;\n activatePackages?: (installedPackages: readonly string[]) => Promise<void>;\n canDispatch?: () => boolean;\n telemetry?: ServerTelemetry;\n onNotice: (message: string) => void;\n}\n\nexport interface PackageApiServer {\n /** Dispatches an already-authenticated request without crossing a transport boundary. */\n readonly operation?: (basePath: string, method: string, path: string) => string | undefined;\n readonly request: (request: Request) => Promise<Response>;\n /**\n * Brings the session's installed packages up to date: changed and added `packages` install with\n * per-package rollback, and installed ones no longer `declared` are disposed. A declared package\n * whose new build failed to load is absent from `packages` but keeps its running build.\n */\n readonly reconcile?: (\n packages: readonly (PoServerPlugin | LoadedServerPackage)[],\n declared: readonly string[],\n ) => Promise<PackageReconcileResult>;\n close(): Promise<void>;\n}\n\nexport interface PackageReconcileResult {\n readonly installed: readonly string[];\n readonly removed: readonly string[];\n readonly failed: readonly PoServerPackageFailure[];\n}\n\nfunction describeError(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\nfunction responseWithCompletion(response: Response, complete: (bytes: number) => void): Response {\n if (response.body === null) {\n complete(0);\n return response;\n }\n const reader = response.body.getReader();\n let completed = false;\n let bytes = 0;\n const finish = (): void => {\n if (completed) return;\n completed = true;\n complete(bytes);\n };\n const body = new ReadableStream<Uint8Array>({\n async pull(controller) {\n try {\n const result = await reader.read();\n if (result.done) {\n finish();\n controller.close();\n } else {\n bytes += result.value.byteLength;\n controller.enqueue(result.value);\n }\n } catch (error) {\n finish();\n controller.error(error);\n }\n },\n async cancel(reason) {\n finish();\n await reader.cancel(reason);\n },\n });\n return new Response(body, response);\n}\n\n/** Reads files under the session's open folders; the hub's session `/file` route forwards here. */\nfunction sessionFilesApi(fileAccess: PoFileAccess, cwd: string): PoApi {\n return {\n basePath: SESSION_FILES_API,\n start: () => ({\n fetch: (request) => {\n if (request.method !== 'GET') return Response.json({ error: 'Method not allowed.' }, { status: 405 });\n return readAccessibleFile({\n roots: fileAccess.roots(),\n cwd,\n path: new URL(request.url).searchParams.get('path'),\n ...(request.headers.get('range') === null ? {} : { range: request.headers.get('range')! }),\n ...(request.headers.get('if-match') === null ? {} : { expectedVersion: request.headers.get('if-match')! }),\n });\n },\n close: () => undefined,\n }),\n };\n}\n/** Installs session packages and dispatches authenticated package API calls in-process. */\nexport async function serveSessionApis(options: PackageApiServerOptions): Promise<PackageApiServer> {\n if (options.environment === undefined)\n throw new Error('Session package API server requires an admitted environment.');\n if (options.directEvents === undefined) throw new Error('Session package API server requires direct events.');\n options = { ...options, environment: await prepareSessionEnvironment(options) };\n const packages = options.packages ?? [];\n // Without a level above it, a session that mounts nothing has nothing to route.\n const routesNothing = (): boolean => !options.preparePackages && options.parentScope === undefined;\n if (options.apis.length === 0 && packages.length === 0 && routesNothing()) {\n return {\n request: async () => Response.json({ error: 'No package APIs are mounted.' }, { status: 404 }),\n close: () => Promise.resolve(),\n };\n }\n\n const owningSessionService = options.sessionService;\n const communication = owningSessionService?.bindCommunication?.(options.sessionId);\n const ownedReadExecutionState = owningSessionService?.readExecutionState?.bind(owningSessionService);\n const ownedPrompt = owningSessionService?.prompt?.bind(owningSessionService);\n const ownedAbort = owningSessionService?.abort?.bind(owningSessionService);\n const ownedSteer = owningSessionService?.steer?.bind(owningSessionService);\n const ownedRelease = owningSessionService?.release?.bind(owningSessionService);\n const exposedSessionService: PoHubSessionService | undefined =\n owningSessionService === undefined\n ? undefined\n : {\n create: (request) => owningSessionService.create(request),\n close: (sessionId) => owningSessionService.close(sessionId),\n isLive: (sessionId) => owningSessionService.isLive(sessionId),\n ...(ownedReadExecutionState === undefined\n ? {}\n : {\n readExecutionState: (sessionId: string) =>\n ownedReadExecutionState(sessionId, { parentSessionId: options.sessionId }),\n }),\n // A session may drive only the children it started, never its parent or a sibling.\n ...(ownedPrompt === undefined\n ? {}\n : {\n prompt: (sessionId: string, text: string, promptOptions?: PoHubSessionPromptOptions) =>\n ownedPrompt(sessionId, text, {\n ...(promptOptions?.signal === undefined ? {} : { signal: promptOptions.signal }),\n parentSessionId: options.sessionId,\n }),\n }),\n ...(ownedAbort === undefined\n ? {}\n : {\n abort: (sessionId: string) => ownedAbort(sessionId, { parentSessionId: options.sessionId }),\n }),\n ...(ownedSteer === undefined\n ? {}\n : {\n steer: (sessionId: string, message: string) =>\n ownedSteer(sessionId, message, { parentSessionId: options.sessionId }),\n }),\n ...(ownedRelease === undefined\n ? {}\n : {\n release: (sessionId, releaseOptions) =>\n ownedRelease(sessionId, {\n ...(releaseOptions?.onlyIfIdle === undefined ? {} : { onlyIfIdle: releaseOptions.onlyIfIdle }),\n parentSessionId: options.sessionId,\n }),\n }),\n ...(owningSessionService.canCommunicate === undefined\n ? {}\n : {\n canCommunicate: (sourceSessionId: string, targetSessionId: string) =>\n owningSessionService.canCommunicate?.(sourceSessionId, targetSessionId) === true,\n }),\n };\n const context: PoApiContext = {\n scope: 'session',\n sessionId: options.sessionId,\n workspaceId: options.workspaceId,\n workspaceRoot: options.workspaceRoot,\n ...(options.sessionContext === undefined ? {} : { sessionContext: options.sessionContext }),\n homeDirectory: options.homeDirectory ?? options.platform.homeDirectory,\n sessionStorage: options.platform.sessions,\n cwd: options.cwd,\n environment: options.environment,\n directEvents: options.directEvents,\n ...(options.computerUse === undefined ? {} : { computerUse: options.computerUse }),\n ...(options.shareFile === undefined ? {} : { shareFile: options.shareFile }),\n ...(options.shareRegisteredFile === undefined ? {} : { shareRegisteredFile: options.shareRegisteredFile }),\n ...(options.mediaArbitration === undefined ? {} : { mediaArbitration: options.mediaArbitration }),\n ...(options.executionBudget === undefined ? {} : { executionBudget: options.executionBudget }),\n ...(options.editPresence === undefined ? {} : { editPresence: options.editPresence }),\n ...(options.peerAgents === undefined ? {} : { peerAgents: options.peerAgents }),\n ...(options.requestApi === undefined ? {} : { requestApi: options.requestApi }),\n ...(options.publishActivity === undefined ? {} : { publishActivity: options.publishActivity }),\n ...(options.internalToken === undefined ? {} : { internalToken: options.internalToken }),\n ...(options.hubToken === undefined ? {} : { hubToken: options.hubToken }),\n ...(exposedSessionService === undefined ? {} : { sessionService: exposedSessionService }),\n ...(communication === undefined ? {} : { sessionCommunication: communication }),\n onNotice: options.onNotice,\n };\n const host = createPoServerHost({\n scope: 'session',\n context,\n mountChannel: options.mountChannel,\n ...(options.pluginRegistry === undefined ? {} : { pluginRegistry: options.pluginRegistry }),\n });\n // The session's own folder, its workspace and the temp folders of every session under this Po home;\n // extensions open their own beside these.\n const fileAccess = createFileAccess();\n const tempDirectory = options.environment[PO_SESSION_TEMP_DIR];\n const sessionsTemp =\n tempDirectory !== undefined && path.basename(path.dirname(tempDirectory)) === 'sessions'\n ? path.dirname(tempDirectory)\n : tempDirectory;\n for (const directory of [options.cwd, options.workspaceRoot, sessionsTemp])\n if (directory !== undefined && path.isAbsolute(directory)) fileAccess.register(directory);\n let scope: PoServerScope | undefined;\n const installOptions = (failed?: PoServerPackageFailure[]) => ({\n onNotice: options.onNotice,\n ...(failed === undefined ? {} : { onPackageFailed: (failure: PoServerPackageFailure) => failed.push(failure) }),\n onPackageSettled: (report: { packageName: string; outcome: string; durationMs: number }) => {\n if (!options.telemetry) return;\n observe(\n options.telemetry.recordEvent('po_server.package_install', {\n 'package.name': report.packageName,\n 'package.outcome': report.outcome,\n duration_ms: report.durationMs,\n scope: 'session',\n }),\n options.onNotice,\n );\n },\n });\n try {\n // Registered first, so no package can claim the host's own base path.\n host.registerApi(sessionFilesApi(fileAccess, options.cwd));\n for (const api of options.apis) host.registerApi(api);\n // The session's own level of the server IoC tree; it closes with the hub level it opens below.\n scope = await PoServerScope.open('session', options.parentScope);\n await scope.serve({ host, platform: options.platform });\n await scope.root.plugin((root) => {\n root.provide(PO_FILE_ACCESS_SERVICE, fileAccess);\n });\n await options.preparePackages?.(scope.root);\n const installed = await scope.install(packages, installOptions());\n await options.activatePackages?.(installed);\n } catch (error) {\n await scope?.dispose();\n communication?.close();\n host.dispose();\n throw error;\n }\n if (host.mounted().length === 0 && routesNothing()) {\n await scope.dispose();\n communication?.close();\n host.dispose();\n return {\n request: async () => Response.json({ error: 'No package APIs are mounted.' }, { status: 404 }),\n close: () => Promise.resolve(),\n };\n }\n const sessionScope = scope;\n const reconcile = async (\n next: readonly (PoServerPlugin | LoadedServerPackage)[],\n declared: readonly string[],\n ): Promise<PackageReconcileResult> => {\n const failed: PoServerPackageFailure[] = [];\n const installed = await sessionScope.install(next, installOptions(failed));\n const removed = installed.filter((name) => !declared.includes(name));\n return { installed: await sessionScope.uninstall(removed), removed, failed };\n };\n\n const dispatch = async (request: Request): Promise<Response> => {\n if (options.canDispatch?.() === false)\n return Response.json({ error: 'Session selection is not ready.' }, { status: 503 });\n const url = new URL(request.url);\n if (!url.pathname.startsWith(`${PO_API_ROUTE_PREFIX}/`))\n return Response.json({ error: 'Not found.' }, { status: 404 });\n const rest = url.pathname.slice(PO_API_ROUTE_PREFIX.length + 1);\n const slash = rest.indexOf('/');\n const basePath = slash === -1 ? rest : rest.slice(0, slash);\n // The session's own API, else the nearest level above it that serves one.\n const handler = sessionScope.apiHandler(basePath);\n if (handler === undefined)\n return Response.json({ error: `No API '${basePath}' in this session.` }, { status: 404 });\n url.pathname = slash === -1 ? '/' : rest.slice(slash);\n const startedAt = performance.now();\n const parent = validatedTraceContext(request.headers.get('traceparent'));\n let childContext: PoTraceContext | undefined;\n let response: Response;\n try {\n const invoke = async (context?: PoTraceContext): Promise<Response> => {\n if (options.canDispatch?.() === false || sessionScope.apiHandler(basePath) !== handler) {\n return Response.json({ error: 'Session selection changed before dispatch.' }, { status: 503 });\n }\n childContext = context;\n const incoming = new Request(url, request);\n try {\n return await handler.fetch(incoming);\n } catch (error) {\n if (incoming.signal.aborted && (error === incoming.signal.reason || error instanceof OperationCancelledError))\n return new Response(null, { status: 499 });\n throw error;\n }\n };\n response = options.telemetry\n ? await options.telemetry.runInSpan(\n 'po_server.package_api.request',\n { api: basePath, method: request.method },\n invoke,\n parent,\n )\n : await invoke();\n } catch (error) {\n options.onNotice(`package API '${basePath}' failed on ${url.pathname} (${describeError(error)})`);\n response = Response.json({ error: `The '${basePath}' API failed.` }, { status: 500 });\n }\n return responseWithCompletion(response, (responseBytes) => {\n if (!options.telemetry) return;\n const durationMs = Math.round(performance.now() - startedAt);\n // Keep this span only where it says something the request span cannot: a failing status,\n // a body that took real time to flush after the headers were sent, or a large body.\n if (\n response.status < 400 &&\n durationMs < COMPLETION_SPAN_MIN_DURATION_MS &&\n responseBytes < COMPLETION_SPAN_MIN_BYTES\n )\n return;\n const requestBytes = Number(request.headers.get('content-length'));\n observe(\n options.telemetry.runInSpan(\n 'po_server.package_api.complete',\n {\n api: basePath,\n method: request.method,\n status_code: response.status,\n duration_ms: durationMs,\n response_bytes: responseBytes,\n response_kind: contentClass(response.headers.get('content-type')),\n ...(Number.isFinite(requestBytes) && requestBytes > 0 ? { request_bytes: requestBytes } : {}),\n },\n async () => undefined,\n childContext ?? parent,\n ),\n options.onNotice,\n );\n });\n };\n let closePromise: Promise<void> | undefined;\n\n return {\n operation: (basePath, method, path) => scope?.apiOperation(basePath, method, path),\n request: dispatch,\n reconcile,\n close: () => {\n closePromise ??= (async () => {\n await scope.dispose();\n communication?.close();\n host.dispose();\n })();\n return closePromise;\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAkCA,MAAa,kCAAkC;;AAE/C,MAAa,4BAA4B;;AAGzC,SAAgB,aAAa,aAAoC;CAC/D,MAAM,OAAO,aAAa,MAAM,GAAG,CAAC,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC,YAAY,KAAK;CACjE,IAAI,SAAS,IAAI,OAAO;CACxB,IAAI,KAAK,WAAW,QAAQ,GAAG,OAAO;CACtC,IAAI,KAAK,SAAS,MAAM,GAAG,OAAO;CAClC,IAAI,KAAK,WAAW,OAAO,GAAG,OAAO;CACrC,OAAO;AACT;AAmEA,SAAS,cAAc,OAAwB;CAC7C,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AAC9D;AAEA,SAAS,uBAAuB,UAAoB,UAA6C;CAC/F,IAAI,SAAS,SAAS,MAAM;EAC1B,SAAS,CAAC;EACV,OAAO;CACT;CACA,MAAM,SAAS,SAAS,KAAK,UAAU;CACvC,IAAI,YAAY;CAChB,IAAI,QAAQ;CACZ,MAAM,eAAqB;EACzB,IAAI,WAAW;EACf,YAAY;EACZ,SAAS,KAAK;CAChB;CACA,MAAM,OAAO,IAAI,eAA2B;EAC1C,MAAM,KAAK,YAAY;GACrB,IAAI;IACF,MAAM,SAAS,MAAM,OAAO,KAAK;IACjC,IAAI,OAAO,MAAM;KACf,OAAO;KACP,WAAW,MAAM;IACnB,OAAO;KACL,SAAS,OAAO,MAAM;KACtB,WAAW,QAAQ,OAAO,KAAK;IACjC;GACF,SAAS,OAAO;IACd,OAAO;IACP,WAAW,MAAM,KAAK;GACxB;EACF;EACA,MAAM,OAAO,QAAQ;GACnB,OAAO;GACP,MAAM,OAAO,OAAO,MAAM;EAC5B;CACF,CAAC;CACD,OAAO,IAAI,SAAS,MAAM,QAAQ;AACpC;;AAGA,SAAS,gBAAgB,YAA0B,KAAoB;CACrE,OAAO;EACL,UAAU;EACV,cAAc;GACZ,QAAQ,YAAY;IAClB,IAAI,QAAQ,WAAW,OAAO,OAAO,SAAS,KAAK,EAAE,OAAO,sBAAsB,GAAG,EAAE,QAAQ,IAAI,CAAC;IACpG,OAAO,mBAAmB;KACxB,OAAO,WAAW,MAAM;KACxB;KACA,MAAM,IAAI,IAAI,QAAQ,GAAG,CAAC,CAAC,aAAa,IAAI,MAAM;KAClD,GAAI,QAAQ,QAAQ,IAAI,OAAO,MAAM,OAAO,CAAC,IAAI,EAAE,OAAO,QAAQ,QAAQ,IAAI,OAAO,EAAG;KACxF,GAAI,QAAQ,QAAQ,IAAI,UAAU,MAAM,OAAO,CAAC,IAAI,EAAE,iBAAiB,QAAQ,QAAQ,IAAI,UAAU,EAAG;IAC1G,CAAC;GACH;GACA,aAAa,KAAA;EACf;CACF;AACF;;AAEA,eAAsB,iBAAiB,SAA6D;CAClG,IAAI,QAAQ,gBAAgB,KAAA,GAC1B,MAAM,IAAI,MAAM,8DAA8D;CAChF,IAAI,QAAQ,iBAAiB,KAAA,GAAW,MAAM,IAAI,MAAM,oDAAoD;CAC5G,UAAU;EAAE,GAAG;EAAS,aAAa,MAAM,0BAA0B,OAAO;CAAE;CAC9E,MAAM,WAAW,QAAQ,YAAY,CAAC;CAEtC,MAAM,sBAA+B,CAAC,QAAQ,mBAAmB,QAAQ,gBAAgB,KAAA;CACzF,IAAI,QAAQ,KAAK,WAAW,KAAK,SAAS,WAAW,KAAK,cAAc,GACtE,OAAO;EACL,SAAS,YAAY,SAAS,KAAK,EAAE,OAAO,+BAA+B,GAAG,EAAE,QAAQ,IAAI,CAAC;EAC7F,aAAa,QAAQ,QAAQ;CAC/B;CAGF,MAAM,uBAAuB,QAAQ;CACrC,MAAM,gBAAgB,sBAAsB,oBAAoB,QAAQ,SAAS;CACjF,MAAM,0BAA0B,sBAAsB,oBAAoB,KAAK,oBAAoB;CACnG,MAAM,cAAc,sBAAsB,QAAQ,KAAK,oBAAoB;CAC3E,MAAM,aAAa,sBAAsB,OAAO,KAAK,oBAAoB;CACzE,MAAM,aAAa,sBAAsB,OAAO,KAAK,oBAAoB;CACzE,MAAM,eAAe,sBAAsB,SAAS,KAAK,oBAAoB;CAC7E,MAAM,wBACJ,yBAAyB,KAAA,IACrB,KAAA,IACA;EACE,SAAS,YAAY,qBAAqB,OAAO,OAAO;EACxD,QAAQ,cAAc,qBAAqB,MAAM,SAAS;EAC1D,SAAS,cAAc,qBAAqB,OAAO,SAAS;EAC5D,GAAI,4BAA4B,KAAA,IAC5B,CAAC,IACD,EACE,qBAAqB,cACnB,wBAAwB,WAAW,EAAE,iBAAiB,QAAQ,UAAU,CAAC,EAC7E;EAEJ,GAAI,gBAAgB,KAAA,IAChB,CAAC,IACD,EACE,SAAS,WAAmB,MAAc,kBACxC,YAAY,WAAW,MAAM;GAC3B,GAAI,eAAe,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,QAAQ,cAAc,OAAO;GAC9E,iBAAiB,QAAQ;EAC3B,CAAC,EACL;EACJ,GAAI,eAAe,KAAA,IACf,CAAC,IACD,EACE,QAAQ,cAAsB,WAAW,WAAW,EAAE,iBAAiB,QAAQ,UAAU,CAAC,EAC5F;EACJ,GAAI,eAAe,KAAA,IACf,CAAC,IACD,EACE,QAAQ,WAAmB,YACzB,WAAW,WAAW,SAAS,EAAE,iBAAiB,QAAQ,UAAU,CAAC,EACzE;EACJ,GAAI,iBAAiB,KAAA,IACjB,CAAC,IACD,EACE,UAAU,WAAW,mBACnB,aAAa,WAAW;GACtB,GAAI,gBAAgB,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,eAAe,WAAW;GAC5F,iBAAiB,QAAQ;EAC3B,CAAC,EACL;EACJ,GAAI,qBAAqB,mBAAmB,KAAA,IACxC,CAAC,IACD,EACE,iBAAiB,iBAAyB,oBACxC,qBAAqB,iBAAiB,iBAAiB,eAAe,MAAM,KAChF;CACN;CACN,MAAM,UAAwB;EAC5B,OAAO;EACP,WAAW,QAAQ;EACnB,aAAa,QAAQ;EACrB,eAAe,QAAQ;EACvB,GAAI,QAAQ,mBAAmB,KAAA,IAAY,CAAC,IAAI,EAAE,gBAAgB,QAAQ,eAAe;EACzF,eAAe,QAAQ,iBAAiB,QAAQ,SAAS;EACzD,gBAAgB,QAAQ,SAAS;EACjC,KAAK,QAAQ;EACb,aAAa,QAAQ;EACrB,cAAc,QAAQ;EACtB,GAAI,QAAQ,gBAAgB,KAAA,IAAY,CAAC,IAAI,EAAE,aAAa,QAAQ,YAAY;EAChF,GAAI,QAAQ,cAAc,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW,QAAQ,UAAU;EAC1E,GAAI,QAAQ,wBAAwB,KAAA,IAAY,CAAC,IAAI,EAAE,qBAAqB,QAAQ,oBAAoB;EACxG,GAAI,QAAQ,qBAAqB,KAAA,IAAY,CAAC,IAAI,EAAE,kBAAkB,QAAQ,iBAAiB;EAC/F,GAAI,QAAQ,oBAAoB,KAAA,IAAY,CAAC,IAAI,EAAE,iBAAiB,QAAQ,gBAAgB;EAC5F,GAAI,QAAQ,iBAAiB,KAAA,IAAY,CAAC,IAAI,EAAE,cAAc,QAAQ,aAAa;EACnF,GAAI,QAAQ,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,QAAQ,WAAW;EAC7E,GAAI,QAAQ,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,YAAY,QAAQ,WAAW;EAC7E,GAAI,QAAQ,oBAAoB,KAAA,IAAY,CAAC,IAAI,EAAE,iBAAiB,QAAQ,gBAAgB;EAC5F,GAAI,QAAQ,kBAAkB,KAAA,IAAY,CAAC,IAAI,EAAE,eAAe,QAAQ,cAAc;EACtF,GAAI,QAAQ,aAAa,KAAA,IAAY,CAAC,IAAI,EAAE,UAAU,QAAQ,SAAS;EACvE,GAAI,0BAA0B,KAAA,IAAY,CAAC,IAAI,EAAE,gBAAgB,sBAAsB;EACvF,GAAI,kBAAkB,KAAA,IAAY,CAAC,IAAI,EAAE,sBAAsB,cAAc;EAC7E,UAAU,QAAQ;CACpB;CACA,MAAM,OAAO,mBAAmB;EAC9B,OAAO;EACP;EACA,cAAc,QAAQ;EACtB,GAAI,QAAQ,mBAAmB,KAAA,IAAY,CAAC,IAAI,EAAE,gBAAgB,QAAQ,eAAe;CAC3F,CAAC;CAGD,MAAM,aAAa,iBAAiB;CACpC,MAAM,gBAAgB,QAAQ,YAAY;CAC1C,MAAM,eACJ,kBAAkB,KAAA,KAAa,KAAK,SAAS,KAAK,QAAQ,aAAa,CAAC,MAAM,aAC1E,KAAK,QAAQ,aAAa,IAC1B;CACN,KAAK,MAAM,aAAa;EAAC,QAAQ;EAAK,QAAQ;EAAe;CAAY,GACvE,IAAI,cAAc,KAAA,KAAa,KAAK,WAAW,SAAS,GAAG,WAAW,SAAS,SAAS;CAC1F,IAAI;CACJ,MAAM,kBAAkB,YAAuC;EAC7D,UAAU,QAAQ;EAClB,GAAI,WAAW,KAAA,IAAY,CAAC,IAAI,EAAE,kBAAkB,YAAoC,OAAO,KAAK,OAAO,EAAE;EAC7G,mBAAmB,WAAyE;GAC1F,IAAI,CAAC,QAAQ,WAAW;GACxB,QACE,QAAQ,UAAU,YAAY,6BAA6B;IACzD,gBAAgB,OAAO;IACvB,mBAAmB,OAAO;IAC1B,aAAa,OAAO;IACpB,OAAO;GACT,CAAC,GACD,QAAQ,QACV;EACF;CACF;CACA,IAAI;EAEF,KAAK,YAAY,gBAAgB,YAAY,QAAQ,GAAG,CAAC;EACzD,KAAK,MAAM,OAAO,QAAQ,MAAM,KAAK,YAAY,GAAG;EAEpD,QAAQ,MAAM,cAAc,KAAK,WAAW,QAAQ,WAAW;EAC/D,MAAM,MAAM,MAAM;GAAE;GAAM,UAAU,QAAQ;EAAS,CAAC;EACtD,MAAM,MAAM,KAAK,QAAQ,SAAS;GAChC,KAAK,QAAQ,wBAAwB,UAAU;EACjD,CAAC;EACD,MAAM,QAAQ,kBAAkB,MAAM,IAAI;EAC1C,MAAM,YAAY,MAAM,MAAM,QAAQ,UAAU,eAAe,CAAC;EAChE,MAAM,QAAQ,mBAAmB,SAAS;CAC5C,SAAS,OAAO;EACd,MAAM,OAAO,QAAQ;EACrB,eAAe,MAAM;EACrB,KAAK,QAAQ;EACb,MAAM;CACR;CACA,IAAI,KAAK,QAAQ,CAAC,CAAC,WAAW,KAAK,cAAc,GAAG;EAClD,MAAM,MAAM,QAAQ;EACpB,eAAe,MAAM;EACrB,KAAK,QAAQ;EACb,OAAO;GACL,SAAS,YAAY,SAAS,KAAK,EAAE,OAAO,+BAA+B,GAAG,EAAE,QAAQ,IAAI,CAAC;GAC7F,aAAa,QAAQ,QAAQ;EAC/B;CACF;CACA,MAAM,eAAe;CACrB,MAAM,YAAY,OAChB,MACA,aACoC;EACpC,MAAM,SAAmC,CAAC;EAE1C,MAAM,WAAU,MADQ,aAAa,QAAQ,MAAM,eAAe,MAAM,CAAC,EAAA,CAC/C,QAAQ,SAAS,CAAC,SAAS,SAAS,IAAI,CAAC;EACnE,OAAO;GAAE,WAAW,MAAM,aAAa,UAAU,OAAO;GAAG;GAAS;EAAO;CAC7E;CAEA,MAAM,WAAW,OAAO,YAAwC;EAC9D,IAAI,QAAQ,cAAc,MAAM,OAC9B,OAAO,SAAS,KAAK,EAAE,OAAO,kCAAkC,GAAG,EAAE,QAAQ,IAAI,CAAC;EACpF,MAAM,MAAM,IAAI,IAAI,QAAQ,GAAG;EAC/B,IAAI,CAAC,IAAI,SAAS,WAAW,eAAyB,GACpD,OAAO,SAAS,KAAK,EAAE,OAAO,aAAa,GAAG,EAAE,QAAQ,IAAI,CAAC;EAC/D,MAAM,OAAO,IAAI,SAAS,MAAM,oBAAoB,SAAS,CAAC;EAC9D,MAAM,QAAQ,KAAK,QAAQ,GAAG;EAC9B,MAAM,WAAW,UAAU,KAAK,OAAO,KAAK,MAAM,GAAG,KAAK;EAE1D,MAAM,UAAU,aAAa,WAAW,QAAQ;EAChD,IAAI,YAAY,KAAA,GACd,OAAO,SAAS,KAAK,EAAE,OAAO,WAAW,SAAS,oBAAoB,GAAG,EAAE,QAAQ,IAAI,CAAC;EAC1F,IAAI,WAAW,UAAU,KAAK,MAAM,KAAK,MAAM,KAAK;EACpD,MAAM,YAAY,YAAY,IAAI;EAClC,MAAM,SAAS,sBAAsB,QAAQ,QAAQ,IAAI,aAAa,CAAC;EACvE,IAAI;EACJ,IAAI;EACJ,IAAI;GACF,MAAM,SAAS,OAAO,YAAgD;IACpE,IAAI,QAAQ,cAAc,MAAM,SAAS,aAAa,WAAW,QAAQ,MAAM,SAC7E,OAAO,SAAS,KAAK,EAAE,OAAO,6CAA6C,GAAG,EAAE,QAAQ,IAAI,CAAC;IAE/F,eAAe;IACf,MAAM,WAAW,IAAI,QAAQ,KAAK,OAAO;IACzC,IAAI;KACF,OAAO,MAAM,QAAQ,MAAM,QAAQ;IACrC,SAAS,OAAO;KACd,IAAI,SAAS,OAAO,YAAY,UAAU,SAAS,OAAO,UAAU,iBAAiB,0BACnF,OAAO,IAAI,SAAS,MAAM,EAAE,QAAQ,IAAI,CAAC;KAC3C,MAAM;IACR;GACF;GACA,WAAW,QAAQ,YACf,MAAM,QAAQ,UAAU,UACtB,iCACA;IAAE,KAAK;IAAU,QAAQ,QAAQ;GAAO,GACxC,QACA,MACF,IACA,MAAM,OAAO;EACnB,SAAS,OAAO;GACd,QAAQ,SAAS,gBAAgB,SAAS,cAAc,IAAI,SAAS,IAAI,cAAc,KAAK,EAAE,EAAE;GAChG,WAAW,SAAS,KAAK,EAAE,OAAO,QAAQ,SAAS,eAAe,GAAG,EAAE,QAAQ,IAAI,CAAC;EACtF;EACA,OAAO,uBAAuB,WAAW,kBAAkB;GACzD,IAAI,CAAC,QAAQ,WAAW;GACxB,MAAM,aAAa,KAAK,MAAM,YAAY,IAAI,IAAI,SAAS;GAG3D,IACE,SAAS,SAAS,OAClB,aAAA,OACA,gBAAA,SAEA;GACF,MAAM,eAAe,OAAO,QAAQ,QAAQ,IAAI,gBAAgB,CAAC;GACjE,QACE,QAAQ,UAAU,UAChB,kCACA;IACE,KAAK;IACL,QAAQ,QAAQ;IAChB,aAAa,SAAS;IACtB,aAAa;IACb,gBAAgB;IAChB,eAAe,aAAa,SAAS,QAAQ,IAAI,cAAc,CAAC;IAChE,GAAI,OAAO,SAAS,YAAY,KAAK,eAAe,IAAI,EAAE,eAAe,aAAa,IAAI,CAAC;GAC7F,GACA,YAAY,KAAA,GACZ,gBAAgB,MAClB,GACA,QAAQ,QACV;EACF,CAAC;CACH;CACA,IAAI;CAEJ,OAAO;EACL,YAAY,UAAU,QAAQ,SAAS,OAAO,aAAa,UAAU,QAAQ,IAAI;EACjF,SAAS;EACT;EACA,aAAa;GACX,kBAAkB,YAAY;IAC5B,MAAM,MAAM,QAAQ;IACpB,eAAe,MAAM;IACrB,KAAK,QAAQ;GACf,EAAA,CAAG;GACH,OAAO;EACT;CACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"file":"packageApi.cjs","names":[],"sources":["../../../src/schemas/packageApi.ts"],"sourcesContent":["/**\n * The HTTP surface a package offers its host.\n *\n * A server plugin registers one with its host. The host mounts it under the API\n * route prefix, and the package writes routes relative to that mount.\n *\n * The handler is a plain fetch callback so no host and no package is committed\n * to one HTTP framework; a Hono app satisfies it through `app.fetch`.\n */\n\nimport type {\n PoComputerUseSessionAccess,\n PoDirectEventBus,\n PoHubSessionService,\n PoSessionCommunicationEndpoint,\n PoSessionActivity,\n PoSessionContext,\n} from './hubChannel';\nimport type { PoMcpProjection } from './mcpProjection';\nimport type { PoSessionStorageService } from './platform';\n/** The host-owned sync view a repository-scoped package API may inspect. */\nexport interface PoRepositorySyncView {\n fresh: boolean;\n reasons: string[];\n /** True while a background sync that may prepare what `reasons` names is running. */\n syncing?: boolean;\n mcpProjection?: PoMcpProjection;\n}\n\n/** Where an API runs: inside one session's server, or in the machine-wide hub. */\nexport type PoApiScope = 'global' | 'workspace' | 'session';\n\nexport const PO_API_SCOPES: readonly PoApiScope[] = ['global', 'workspace', 'session'];\n\n/** Identity of an independently mounted plugin host. */\nexport type PoApiMount =\n | { scope: 'global' }\n | { scope: 'workspace'; workspaceId: string }\n | { scope: 'session'; sessionId: string };\n\n/** Immutable browser contribution generation paired with a server mount. */\nexport interface PoWebComposition {\n id: string;\n scope: PoApiScope;\n revision: number;\n manifestUrl: string;\n rawAssetBaseUrl: string;\n verifiedAssetBaseUrl: string;\n /** `[slot, entry]` for each of this mount's plugin copies, the entry an absolute path below the asset prefixes. */\n pluginEntries: [string, string][];\n /** Every signed file this mount's slots can load, absolute below the asset prefixes. */\n assetPaths: string[];\n /** Per-plugin stylesheets of this mount's plugins only, so other mounts' classes never apply here. */\n stylePaths: string[];\n channels: string[];\n /** The slots resolved for this mount; the shared build may hold more. */\n plugins: string[];\n}\n\n/**\n * Immutable native contribution generation paired with a server mount. One signed CommonJS module per\n * plugin, served through the same immutable plugin routes as web compositions.\n */\nexport interface PoNativeComposition {\n id: string;\n scope: PoApiScope;\n revision: number;\n manifestUrl: string;\n rawAssetBaseUrl: string;\n /** `[pluginId, modulePath]` for each plugin of this mount that built; the shell loads each on its own. */\n pluginEntries: [string, string][];\n channels: string[];\n /** The `poNative.pluginId` values resolved for this mount; the shell installs only these. */\n plugins: string[];\n}\n\n/** Canonical public prefix. Scope selection never falls through to a parent. */\nexport function poApiMountPath(mount: PoApiMount & { workspaceId?: string }): string {\n if (mount.scope === 'global') return '/api/plugins';\n if (mount.scope === 'workspace') return `/api/workspaces/${encodeURIComponent(mount.workspaceId)}/plugins`;\n if (!mount.workspaceId) throw new Error('A session API path requires its workspace identity.');\n return `/api/workspaces/${encodeURIComponent(mount.workspaceId)}/sessions/${encodeURIComponent(mount.sessionId)}/plugins`;\n}\n\n/** Scope addressed by a public WebSocket upgrade. */\nexport type PoSocketMount =\n | { scope: 'global' }\n | { scope: 'workspace'; workspaceId: string }\n | { scope: 'session'; workspaceId: string; sessionId: string };\n\n/** Only canonical resource paths may select a protocol listener. */\nexport function parsePoSocketPath(pathname: string): PoSocketMount | undefined {\n if (pathname === '/api/ws') return { scope: 'global' };\n const match = /^\\/api\\/workspaces\\/([^/]+)(?:\\/sessions\\/([^/]+))?\\/ws$/u.exec(pathname);\n if (!match) return undefined;\n try {\n const workspaceId = decodeURIComponent(match[1]);\n return match[2] === undefined\n ? { scope: 'workspace', workspaceId }\n : { scope: 'session', workspaceId, sessionId: decodeURIComponent(match[2]) };\n } catch {\n return undefined;\n }\n}\n\n/** The segment an API is mounted under, below this prefix. */\nexport const PO_API_ROUTE_PREFIX = '/api/plugins';\n/** Selects the hub API bundle that owns the named cockpit session. */\nexport const PO_HUB_API_SESSION_QUERY_PARAM = 'hubSession';\n\n/** Trusted caller headers written only by the cockpit-to-session proxy. */\nexport const PO_API_CALLER_LOCALITY_HEADER = 'x-po-api-caller-locality';\nexport const PO_API_CALLER_DEVICE_ID_HEADER = 'x-po-api-caller-device-id';\nexport const PO_API_CALLER_STEP_UP_HEADER = 'x-po-api-caller-step-up';\n/** Set only after destination verification, never accepted from an untrusted proxy. */\nexport const PO_API_CALLER_FEDERATION_HEADER = 'x-po-api-caller-federation';\nexport type { FederationCaller as PoFederationCaller } from '@agimon-ai/po-web-security/deviceProof';\nexport const PO_API_CALLER_HEADERS = [\n PO_API_CALLER_LOCALITY_HEADER,\n PO_API_CALLER_DEVICE_ID_HEADER,\n PO_API_CALLER_STEP_UP_HEADER,\n PO_API_CALLER_FEDERATION_HEADER,\n] as const;\n\nexport type PoApiCallerStepUp = 'not-required' | 'verified' | 'unavailable';\nexport type PoApiCaller = (\n | { locality: 'local'; stepUp: 'not-required' }\n | { locality: 'remote'; deviceId: string; stepUp: PoApiCallerStepUp }\n) &\n Partial<import('@agimon-ai/po-web-security/deviceProof').FederationCaller>;\n\nfunction federationCallerFrom(\n headers: Headers,\n): import('@agimon-ai/po-web-security/deviceProof').FederationCaller | undefined {\n const raw = headers.get(PO_API_CALLER_FEDERATION_HEADER);\n if (raw === null) return undefined;\n try {\n const value = JSON.parse(raw) as import('@agimon-ai/po-web-security/deviceProof').FederationCaller;\n if (\n typeof value.principal?.issuer !== 'string' ||\n !value.principal.issuer ||\n typeof value.principal.subject !== 'string' ||\n !value.principal.subject ||\n typeof value.org !== 'string' ||\n !value.org ||\n typeof value.device?.keyId !== 'string' ||\n !value.device.keyId ||\n (value.via !== undefined &&\n (typeof value.via.callingHostId !== 'string' ||\n !value.via.callingHostId ||\n typeof value.via.grantId !== 'string' ||\n !value.via.grantId))\n )\n return undefined;\n return {\n principal: { issuer: value.principal.issuer, subject: value.principal.subject },\n org: value.org,\n device: { keyId: value.device.keyId },\n ...(value.via ? { via: { ...value.via } } : {}),\n };\n } catch {\n return undefined;\n }\n}\n\n/** Reads the proxy-authenticated caller identity, rejecting partial or contradictory stamps. */\nexport function poApiCallerFrom(headers: Headers): PoApiCaller | undefined {\n const federation = federationCallerFrom(headers);\n if (headers.has(PO_API_CALLER_FEDERATION_HEADER) && !federation) return undefined;\n const locality = headers.get(PO_API_CALLER_LOCALITY_HEADER);\n const deviceId = headers.get(PO_API_CALLER_DEVICE_ID_HEADER);\n const stepUp = headers.get(PO_API_CALLER_STEP_UP_HEADER);\n if (locality === 'local') {\n return deviceId === null && stepUp === 'not-required' && !federation ? { locality, stepUp } : undefined;\n }\n if (\n locality === 'remote' &&\n deviceId !== null &&\n deviceId !== '' &&\n (stepUp === 'not-required' || stepUp === 'verified' || stepUp === 'unavailable')\n ) {\n return { locality, deviceId, stepUp, ...federation };\n }\n return undefined;\n}\n/**\n * A browser-reachable OAuth redirect the host serves on its own listener.\n *\n * A package brokering third-party OAuth cannot use a loopback redirect when the\n * operator's browser is on another machine: `127.0.0.1` resolves to whichever\n * machine the browser runs on. The host owns a listener that is reachable from\n * wherever the cockpit is being used, so it lends that instead of the package\n * opening a port of its own.\n */\nexport interface PoOAuthRedirect {\n /** Absolute URL to register as the redirect target, already origin-correct. */\n readonly redirectUri: string;\n /**\n * Accept redirects carrying this state. Must complete before the\n * authorization URL is surfaced, or the redirect races its own reservation.\n */\n reserve(state: string, timeoutMs?: number): Promise<void>;\n /** Resolves once the redirect carrying this state arrives. */\n wait(state: string, timeoutMs?: number): Promise<{ code: string; state: string }>;\n /** Drops a reservation whose flow ended without a redirect. */\n cancel(state: string): void;\n}\n\n/** An exact, host-owned receipt address. Provider IDs are not event cursor positions. */\nexport interface PoRequestReceiptKey {\n namespace: string;\n activationId: string;\n requestId: string;\n}\n\nexport interface PoRequestReceipt extends PoRequestReceiptKey {\n fingerprint: string;\n destination: string;\n transactionId: string;\n outcome: 'reserved' | 'admitted' | 'rejected' | 'uncertain';\n}\n\nexport interface PoRequestReceiptReservation extends PoRequestReceiptKey {\n /** Lowercase SHA-256 of the authoritative request text, never the full transcript. */\n fingerprint: string;\n destination: string;\n transactionId: string;\n}\n\nexport type PoRequestReceiptReserveResult =\n | { kind: 'reserved'; token: string }\n | { kind: 'existing'; receipt: PoRequestReceipt }\n | { kind: 'conflict'; receipt: PoRequestReceipt };\n\n/** A global-only receipt capability. A reservation is not an execution receipt. */\nexport interface PoRequestReceipts {\n reserve(request: PoRequestReceiptReservation): Promise<PoRequestReceiptReserveResult>;\n lookup(key: PoRequestReceiptKey): Promise<PoRequestReceipt | undefined>;\n finish(\n request: PoRequestReceiptKey & { token: string; outcome: 'admitted' | 'rejected' | 'uncertain' },\n ): Promise<PoRequestReceipt>;\n}\n\n/** Permit returned for expensive local work. Calling it releases the slot. */\nexport interface PoHostExecutionPermit {\n (): void;\n attachProcess?(pid: number): Promise<void>;\n}\n\n/** Host-wide admission for expensive local work. Release only after the owned process exits. */\nexport interface PoHostExecutionBudget {\n acquire(signal?: AbortSignal): Promise<PoHostExecutionPermit>;\n getSnapshot(): Readonly<{ running: number; queued: number; limit: number }>;\n}\n\n/** Synchronous host-owned exclusion across media APIs mounted at different scopes. */\nexport interface PoHostMediaArbitration {\n register(busy: () => boolean): () => void;\n available(busy: () => boolean): boolean;\n}\n\n/** One host's paired agent registrations, shared across its global and session mounts. */\nexport interface PoPeerAgentRegistry {\n register(sessionId: string, agent: { fetch(request: Request): Promise<Response> }, hubToken: string): () => void;\n get(sessionId: string): { fetch(request: Request): Promise<Response>; hubToken: string } | undefined;\n}\n\n/** One agent that edits files: a hub session or an in-process child run. */\nexport interface PoEditPresenceAgent {\n /** `session:<id>` or `run:<runId>`. Unique across the host. */\n readonly key: string;\n /** Read when a note is rendered, so a name registered after the edit still shows. */\n readonly label: () => string;\n /** Hub session id other agents can reach with intercom. */\n readonly contact: string;\n readonly isLive: () => boolean;\n}\n\nexport interface PoEditPresenceEntry {\n readonly agent: PoEditPresenceAgent;\n /** `@file` tag of the bytes this agent's last successful edit or write left behind. */\n readonly tag: string;\n readonly at: number;\n}\n\n/**\n * Host-wide record of the last edit each live agent made to a file, keyed by realpath then agent key.\n * Owners delete their entries when they dispose, so no callback outlives its session or run.\n */\nexport type PoEditPresenceLedger = Map<string, Map<string, PoEditPresenceEntry>>;\n\n/** Why shareFile rejects while the host has no public tunnel origin. */\nexport const PO_SHARE_FILE_REMOTE_OFF_MESSAGE =\n 'Remote access is off, so Po cannot share files with remote MCP servers. Turn it on and retry.';\n\n/**\n * A file published at a short-lived public tunnel URL: 10 minutes, 3 downloads, and gone\n * when the tunnel revision changes. Call revoke as soon as the consumer is done with it.\n */\nexport interface PoSharedFile {\n /** Bearer link; never log or persist it. */\n url: string;\n fileName: string;\n mimeType: string;\n size: number;\n /** Expiry as epoch milliseconds. */\n expiresAt: number;\n maxDownloads: number;\n /** Idempotent. */\n revoke(): void;\n}\n\n/** What the host tells an API about itself when it starts. */\nexport interface PoApiContext {\n scope: PoApiScope;\n /** Stable, opaque workspace id, shared by every session and member checkout of the workspace. */\n workspaceId?: string;\n /**\n * The admitted workspace root for a workspace mount. For a session mount it is the checkout\n * the session runs in; read sessionContext for both roots. Absent from global mounts.\n */\n workspaceRoot?: string;\n /** The session's baseline context; present on session mounts whose host knows its workspace. */\n sessionContext?: PoSessionContext;\n /** Explicit host home, never selected by a remote request. */\n homeDirectory?: string;\n /** The session this host serves; absent for a hub-scoped API. */\n sessionId?: string;\n /** The session's working directory; absent for a hub-scoped API. */\n cwd?: string;\n /** Session environment admitted by the server, never supplied by remote clients. */\n environment?: Readonly<Record<string, string | undefined>>;\n /** Shared only with child processes in this session, never with remote API clients. */\n internalToken?: string;\n /** Shared only with the cockpit hub for privileged cross-session coordination. */\n hubToken?: string;\n /**\n * Resolves a hub-issued opaque repository id to an admitted canonical root.\n * Hub APIs must never accept a browser-supplied filesystem path instead.\n */\n resolveRepository?(repositoryId: string): string | undefined;\n /** Reads the admitted repository's sync projection without exposing its state path. */\n readRepositorySync?(\n repositoryId: string,\n ): Promise<PoRepositorySyncView | undefined> | PoRepositorySyncView | undefined;\n /** Admitted workspaces available to this mount. */\n repositories?(): readonly { id: string; name: string; path: string; active: boolean }[];\n /** Publish a successful config write to the owning mount's refresh lifecycle. */\n configurationChanged?(): void;\n /**\n * Borrows the hub's OAuth redirect surface. Undefined when the hub cannot\n * currently serve one, in which case the package keeps its own behaviour.\n */\n oauthRedirect?(): PoOAuthRedirect | undefined;\n /** Canonical in-process lifecycle for sessions created by this host. */\n sessionService?: PoHubSessionService;\n /** Trusted in-process storage capability. APIs must retain their mount's session authorization. */\n sessionStorage?: PoSessionStorageService;\n /** Host-bound, authenticated inter-session event endpoint for this session. */\n sessionCommunication?: PoSessionCommunicationEndpoint;\n /** Same-process events shared by session APIs and hub channels. */\n directEvents?: PoDirectEventBus;\n /**\n * Publishes this session's activity line to every page's rail; undefined clears it.\n * Session mounts only. The last publisher wins.\n */\n publishActivity?(activity: PoSessionActivity | undefined): void;\n /** Trusted in-process dispatch to an explicitly selected mounted API. No browser authentication is performed here. */\n requestApi?(mount: PoApiMount, basePath: string, request: Request): Promise<Response>;\n /** Present only on a session mounted by an authenticated Desktop host. */\n computerUse?: PoComputerUseSessionAccess;\n /** Desktop's macOS privacy permissions; present on hub mounts only when Desktop hosts this server. */\n desktopPermissions?: {\n /** A `change` asks macOS for a permission and needs the step-up computer-use activation needs. */\n authorize(headers: Headers, change: boolean): boolean;\n /** The permission states, after asking macOS for `permission` when it is set. */\n request(permission?: string): Promise<unknown>;\n };\n /** Deliver a typed method to the channel serving a session this mount reaches, retaining the caller's connection identity. */\n receiveChannel?(sessionId: string, frameType: string, payload: unknown, connectionId: string): boolean;\n /**\n * Tells connected clients this mount's `name` data changed, so they fetch it again instead of polling.\n * Scoped by the mount: `global:<name>` on the global mount, `workspace:<id>:<name>` on a workspace mount.\n */\n bumpRevision?(name: string): void;\n /** Sets a live session's undecided approval count on its rail card; 0 clears it. Global mount only. */\n setSessionPendingApprovals?(sessionId: string, count: number): void;\n /** Host-owned replay evidence, supplied only on the global mount. Never exposed to browsers or agents. */\n requestReceipts?: PoRequestReceipts;\n /** One host's exclusive media activity, shared across global and session scopes. */\n mediaArbitration?: PoHostMediaArbitration;\n /** One host-wide heavy-job budget, not a separate budget for every session. */\n executionBudget?: PoHostExecutionBudget;\n /** One host-wide edit presence ledger, shared by every session mount. */\n editPresence?: PoEditPresenceLedger;\n /** Paired agents belonging to this host only. */\n peerAgents?: PoPeerAgentRegistry;\n /** Machine-owned Remote Control service, mounted only by Po's own global server plugin. */\n remoteControl?: { fetch(request: Request): Promise<Response> };\n /**\n * Publishes a file from this session's cwd or attachments at a short-lived tunnel URL.\n * Session mounts only; rejects with PO_SHARE_FILE_REMOTE_OFF_MESSAGE while remote access is off.\n * The host asks no consent: the caller must obtain it before passing the URL on.\n */\n shareFile?(path: string, label: string): Promise<PoSharedFile>;\n /**\n * This server's loopback origin, such as `http://127.0.0.1:4242`. Throws until the listener is up,\n * so read it when serving a request, not at start.\n */\n serverOrigin?(): string;\n onNotice(message: string): void;\n}\n\n/** One accepted WebSocket on a public route. Text frames arrive as strings, binary frames as bytes. */\nexport interface PoApiSocket {\n readonly isOpen: boolean;\n send(data: string | Uint8Array): void;\n close(code?: number, reason?: string): void;\n onMessage(listener: (data: string | Uint8Array) => void): void;\n onClose(listener: (code: number, reason: string) => void): void;\n}\n\n/**\n * Routes the host serves without its own token, for a client that holds a package-issued token instead.\n * Global scope only. The host calls `authenticate` before `fetch` or `upgrade` and answers 401 when it\n * returns false, so a handler is never reached unauthenticated. Requests must also come from a loopback\n * peer and Host, carry no Origin or a `chrome-extension://` one, and never arrive through the tunnel.\n */\nexport interface PoApiPublicRoutes {\n /** Path prefixes below the base path, such as `/extension`; each starts with `/`. */\n readonly paths: readonly string[];\n authenticate(request: Request): boolean | Promise<boolean>;\n}\n\n/**\n * One running surface. The host strips the mount prefix before calling, so\n * routes are declared relative ('/runners/:id/log') and a package never\n * repeats where it was mounted.\n */\nexport interface PoApiHandler {\n fetch(request: Request): Response | Promise<Response>;\n /** Accepts a WebSocket upgrade on a public route; the host has already authenticated it. */\n upgrade?(request: Request, socket: PoApiSocket): void;\n close(): void;\n}\n\nexport interface PoApi {\n /** Segment under /api/plugins/; globally unique across loaded packages. */\n basePath: string;\n /** Routes served without the host token; accepted for global-scope packages only. */\n publicRoutes?: PoApiPublicRoutes;\n start(context: PoApiContext): PoApiHandler;\n}\n"],"mappings":";AAgCA,MAAa,gBAAuC;CAAC;CAAU;CAAa;AAAS;;AA6CrF,SAAgB,eAAe,OAAsD;CACnF,IAAI,MAAM,UAAU,UAAU,OAAO;CACrC,IAAI,MAAM,UAAU,aAAa,OAAO,mBAAmB,mBAAmB,MAAM,WAAW,EAAE;CACjG,IAAI,CAAC,MAAM,aAAa,MAAM,IAAI,MAAM,qDAAqD;CAC7F,OAAO,mBAAmB,mBAAmB,MAAM,WAAW,EAAE,YAAY,mBAAmB,MAAM,SAAS,EAAE;AAClH;;AASA,SAAgB,kBAAkB,UAA6C;CAC7E,IAAI,aAAa,WAAW,OAAO,EAAE,OAAO,SAAS;CACrD,MAAM,QAAQ,4DAA4D,KAAK,QAAQ;CACvF,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,IAAI;EACF,MAAM,cAAc,mBAAmB,MAAM,EAAE;EAC/C,OAAO,MAAM,OAAO,KAAA,IAChB;GAAE,OAAO;GAAa;EAAY,IAClC;GAAE,OAAO;GAAW;GAAa,WAAW,mBAAmB,MAAM,EAAE;EAAE;CAC/E,QAAQ;EACN;CACF;AACF;;AAGA,MAAa,sBAAsB;;AAEnC,MAAa,iCAAiC;;AAG9C,MAAa,gCAAgC;AAC7C,MAAa,iCAAiC;AAC9C,MAAa,+BAA+B;;AAE5C,MAAa,kCAAkC;AAE/C,MAAa,wBAAwB;CACnC;CACA;CACA;CACA;AACF;AASA,SAAS,qBACP,SAC+E;CAC/E,MAAM,MAAM,QAAQ,IAAI,+BAA+B;CACvD,IAAI,QAAQ,MAAM,OAAO,KAAA;CACzB,IAAI;EACF,MAAM,QAAQ,KAAK,MAAM,GAAG;EAC5B,IACE,OAAO,MAAM,WAAW,WAAW,YACnC,CAAC,MAAM,UAAU,UACjB,OAAO,MAAM,UAAU,YAAY,YACnC,CAAC,MAAM,UAAU,WACjB,OAAO,MAAM,QAAQ,YACrB,CAAC,MAAM,OACP,OAAO,MAAM,QAAQ,UAAU,YAC/B,CAAC,MAAM,OAAO,SACb,MAAM,QAAQ,KAAA,MACZ,OAAO,MAAM,IAAI,kBAAkB,YAClC,CAAC,MAAM,IAAI,iBACX,OAAO,MAAM,IAAI,YAAY,YAC7B,CAAC,MAAM,IAAI,UAEf,OAAO,KAAA;EACT,OAAO;GACL,WAAW;IAAE,QAAQ,MAAM,UAAU;IAAQ,SAAS,MAAM,UAAU;GAAQ;GAC9E,KAAK,MAAM;GACX,QAAQ,EAAE,OAAO,MAAM,OAAO,MAAM;GACpC,GAAI,MAAM,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,EAAE,IAAI,CAAC;EAC/C;CACF,QAAQ;EACN;CACF;AACF;;AAGA,SAAgB,gBAAgB,SAA2C;CACzE,MAAM,aAAa,qBAAqB,OAAO;CAC/C,IAAI,QAAQ,IAAA,4BAAmC,KAAK,CAAC,YAAY,OAAO,KAAA;CACxE,MAAM,WAAW,QAAQ,IAAI,6BAA6B;CAC1D,MAAM,WAAW,QAAQ,IAAI,8BAA8B;CAC3D,MAAM,SAAS,QAAQ,IAAI,4BAA4B;CACvD,IAAI,aAAa,SACf,OAAO,aAAa,QAAQ,WAAW,kBAAkB,CAAC,aAAa;EAAE;EAAU;CAAO,IAAI,KAAA;CAEhG,IACE,aAAa,YACb,aAAa,QACb,aAAa,OACZ,WAAW,kBAAkB,WAAW,cAAc,WAAW,gBAElE,OAAO;EAAE;EAAU;EAAU;EAAQ,GAAG;CAAW;AAGvD;;AA4GA,MAAa,mCACX"}
1
+ {"version":3,"file":"packageApi.cjs","names":[],"sources":["../../../src/schemas/packageApi.ts"],"sourcesContent":["/**\n * The HTTP surface a package offers its host.\n *\n * A server plugin registers one with its host. The host mounts it under the API\n * route prefix, and the package writes routes relative to that mount.\n *\n * The handler is a plain fetch callback so no host and no package is committed\n * to one HTTP framework; a Hono app satisfies it through `app.fetch`.\n */\n\nimport type {\n PoComputerUseSessionAccess,\n PoDirectEventBus,\n PoHubSessionService,\n PoSessionCommunicationEndpoint,\n PoSessionActivity,\n PoSessionContext,\n} from './hubChannel';\nimport type { PoMcpProjection } from './mcpProjection';\nimport type { PoSessionStorageService } from './platform';\n/** The host-owned sync view a repository-scoped package API may inspect. */\nexport interface PoRepositorySyncView {\n fresh: boolean;\n reasons: string[];\n /** True while a background sync that may prepare what `reasons` names is running. */\n syncing?: boolean;\n mcpProjection?: PoMcpProjection;\n}\n\n/** Where an API runs: inside one session's server, or in the machine-wide hub. */\nexport type PoApiScope = 'global' | 'workspace' | 'session';\n\nexport const PO_API_SCOPES: readonly PoApiScope[] = ['global', 'workspace', 'session'];\n\n/** Identity of an independently mounted plugin host. */\nexport type PoApiMount =\n | { scope: 'global' }\n | { scope: 'workspace'; workspaceId: string }\n | { scope: 'session'; sessionId: string };\n\n/** Immutable browser contribution generation paired with a server mount. */\nexport interface PoWebComposition {\n id: string;\n scope: PoApiScope;\n revision: number;\n manifestUrl: string;\n rawAssetBaseUrl: string;\n verifiedAssetBaseUrl: string;\n /** `[slot, entry]` for each of this mount's plugin copies, the entry an absolute path below the asset prefixes. */\n pluginEntries: [string, string][];\n /** Every signed file this mount's slots can load, absolute below the asset prefixes. */\n assetPaths: string[];\n /** Per-plugin stylesheets of this mount's plugins only, so other mounts' classes never apply here. */\n stylePaths: string[];\n channels: string[];\n /** The slots resolved for this mount; the shared build may hold more. */\n plugins: string[];\n}\n\n/**\n * Immutable native contribution generation paired with a server mount. One signed CommonJS module per\n * plugin, served through the same immutable plugin routes as web compositions.\n */\nexport interface PoNativeComposition {\n id: string;\n scope: PoApiScope;\n revision: number;\n manifestUrl: string;\n rawAssetBaseUrl: string;\n /** `[pluginId, modulePath]` for each plugin of this mount that built; the shell loads each on its own. */\n pluginEntries: [string, string][];\n channels: string[];\n /** The `poNative.pluginId` values resolved for this mount; the shell installs only these. */\n plugins: string[];\n}\n\n/** Canonical public prefix. Scope selection never falls through to a parent. */\nexport function poApiMountPath(mount: PoApiMount & { workspaceId?: string }): string {\n if (mount.scope === 'global') return '/api/plugins';\n if (mount.scope === 'workspace') return `/api/workspaces/${encodeURIComponent(mount.workspaceId)}/plugins`;\n if (!mount.workspaceId) throw new Error('A session API path requires its workspace identity.');\n return `/api/workspaces/${encodeURIComponent(mount.workspaceId)}/sessions/${encodeURIComponent(mount.sessionId)}/plugins`;\n}\n\n/** Scope addressed by a public WebSocket upgrade. */\nexport type PoSocketMount =\n | { scope: 'global' }\n | { scope: 'workspace'; workspaceId: string }\n | { scope: 'session'; workspaceId: string; sessionId: string };\n\n/** Only canonical resource paths may select a protocol listener. */\nexport function parsePoSocketPath(pathname: string): PoSocketMount | undefined {\n if (pathname === '/api/ws') return { scope: 'global' };\n const match = /^\\/api\\/workspaces\\/([^/]+)(?:\\/sessions\\/([^/]+))?\\/ws$/u.exec(pathname);\n if (!match) return undefined;\n try {\n const workspaceId = decodeURIComponent(match[1]);\n return match[2] === undefined\n ? { scope: 'workspace', workspaceId }\n : { scope: 'session', workspaceId, sessionId: decodeURIComponent(match[2]) };\n } catch {\n return undefined;\n }\n}\n\n/** The segment an API is mounted under, below this prefix. */\nexport const PO_API_ROUTE_PREFIX = '/api/plugins';\n/** Selects the hub API bundle that owns the named cockpit session. */\nexport const PO_HUB_API_SESSION_QUERY_PARAM = 'hubSession';\n\n/** Trusted caller headers written only by the cockpit-to-session proxy. */\nexport const PO_API_CALLER_LOCALITY_HEADER = 'x-po-api-caller-locality';\nexport const PO_API_CALLER_DEVICE_ID_HEADER = 'x-po-api-caller-device-id';\nexport const PO_API_CALLER_STEP_UP_HEADER = 'x-po-api-caller-step-up';\n/** Set only after destination verification, never accepted from an untrusted proxy. */\nexport const PO_API_CALLER_FEDERATION_HEADER = 'x-po-api-caller-federation';\nexport type { FederationCaller as PoFederationCaller } from '@agimon-ai/po-web-security/deviceProof';\nexport const PO_API_CALLER_HEADERS = [\n PO_API_CALLER_LOCALITY_HEADER,\n PO_API_CALLER_DEVICE_ID_HEADER,\n PO_API_CALLER_STEP_UP_HEADER,\n PO_API_CALLER_FEDERATION_HEADER,\n] as const;\n\nexport type PoApiCallerStepUp = 'not-required' | 'verified' | 'unavailable';\nexport type PoApiCaller = (\n | { locality: 'local'; stepUp: 'not-required' }\n | { locality: 'remote'; deviceId: string; stepUp: PoApiCallerStepUp }\n) &\n Partial<import('@agimon-ai/po-web-security/deviceProof').FederationCaller>;\n\nfunction federationCallerFrom(\n headers: Headers,\n): import('@agimon-ai/po-web-security/deviceProof').FederationCaller | undefined {\n const raw = headers.get(PO_API_CALLER_FEDERATION_HEADER);\n if (raw === null) return undefined;\n try {\n const value = JSON.parse(raw) as import('@agimon-ai/po-web-security/deviceProof').FederationCaller;\n if (\n typeof value.principal?.issuer !== 'string' ||\n !value.principal.issuer ||\n typeof value.principal.subject !== 'string' ||\n !value.principal.subject ||\n typeof value.org !== 'string' ||\n !value.org ||\n typeof value.device?.keyId !== 'string' ||\n !value.device.keyId ||\n (value.via !== undefined &&\n (typeof value.via.callingHostId !== 'string' ||\n !value.via.callingHostId ||\n typeof value.via.grantId !== 'string' ||\n !value.via.grantId))\n )\n return undefined;\n return {\n principal: { issuer: value.principal.issuer, subject: value.principal.subject },\n org: value.org,\n device: { keyId: value.device.keyId },\n ...(value.via ? { via: { ...value.via } } : {}),\n };\n } catch {\n return undefined;\n }\n}\n\n/** Reads the proxy-authenticated caller identity, rejecting partial or contradictory stamps. */\nexport function poApiCallerFrom(headers: Headers): PoApiCaller | undefined {\n const federation = federationCallerFrom(headers);\n if (headers.has(PO_API_CALLER_FEDERATION_HEADER) && !federation) return undefined;\n const locality = headers.get(PO_API_CALLER_LOCALITY_HEADER);\n const deviceId = headers.get(PO_API_CALLER_DEVICE_ID_HEADER);\n const stepUp = headers.get(PO_API_CALLER_STEP_UP_HEADER);\n if (locality === 'local') {\n return deviceId === null && stepUp === 'not-required' && !federation ? { locality, stepUp } : undefined;\n }\n if (\n locality === 'remote' &&\n deviceId !== null &&\n deviceId !== '' &&\n (stepUp === 'not-required' || stepUp === 'verified' || stepUp === 'unavailable')\n ) {\n return { locality, deviceId, stepUp, ...federation };\n }\n return undefined;\n}\n/**\n * A browser-reachable OAuth redirect the host serves on its own listener.\n *\n * A package brokering third-party OAuth cannot use a loopback redirect when the\n * operator's browser is on another machine: `127.0.0.1` resolves to whichever\n * machine the browser runs on. The host owns a listener that is reachable from\n * wherever the cockpit is being used, so it lends that instead of the package\n * opening a port of its own.\n */\nexport interface PoOAuthRedirect {\n /** Absolute URL to register as the redirect target, already origin-correct. */\n readonly redirectUri: string;\n /**\n * Accept redirects carrying this state. Must complete before the\n * authorization URL is surfaced, or the redirect races its own reservation.\n */\n reserve(state: string, timeoutMs?: number): Promise<void>;\n /** Resolves once the redirect carrying this state arrives. */\n wait(state: string, timeoutMs?: number): Promise<{ code: string; state: string }>;\n /** Drops a reservation whose flow ended without a redirect. */\n cancel(state: string): void;\n}\n\n/** An exact, host-owned receipt address. Provider IDs are not event cursor positions. */\nexport interface PoRequestReceiptKey {\n namespace: string;\n activationId: string;\n requestId: string;\n}\n\nexport interface PoRequestReceipt extends PoRequestReceiptKey {\n fingerprint: string;\n destination: string;\n transactionId: string;\n outcome: 'reserved' | 'admitted' | 'rejected' | 'uncertain';\n}\n\nexport interface PoRequestReceiptReservation extends PoRequestReceiptKey {\n /** Lowercase SHA-256 of the authoritative request text, never the full transcript. */\n fingerprint: string;\n destination: string;\n transactionId: string;\n}\n\nexport type PoRequestReceiptReserveResult =\n | { kind: 'reserved'; token: string }\n | { kind: 'existing'; receipt: PoRequestReceipt }\n | { kind: 'conflict'; receipt: PoRequestReceipt };\n\n/** A global-only receipt capability. A reservation is not an execution receipt. */\nexport interface PoRequestReceipts {\n reserve(request: PoRequestReceiptReservation): Promise<PoRequestReceiptReserveResult>;\n lookup(key: PoRequestReceiptKey): Promise<PoRequestReceipt | undefined>;\n finish(\n request: PoRequestReceiptKey & { token: string; outcome: 'admitted' | 'rejected' | 'uncertain' },\n ): Promise<PoRequestReceipt>;\n}\n\n/** Permit returned for expensive local work. Calling it releases the slot. */\nexport interface PoHostExecutionPermit {\n (): void;\n attachProcess?(pid: number): Promise<void>;\n}\n\n/** Host-wide admission for expensive local work. Release only after the owned process exits. */\nexport interface PoHostExecutionBudget {\n acquire(signal?: AbortSignal): Promise<PoHostExecutionPermit>;\n getSnapshot(): Readonly<{ running: number; queued: number; limit: number }>;\n}\n\n/** Synchronous host-owned exclusion across media APIs mounted at different scopes. */\nexport interface PoHostMediaArbitration {\n register(busy: () => boolean): () => void;\n available(busy: () => boolean): boolean;\n}\n\n/** One host's paired agent registrations, shared across its global and session mounts. */\nexport interface PoPeerAgentRegistry {\n register(sessionId: string, agent: { fetch(request: Request): Promise<Response> }, hubToken: string): () => void;\n get(sessionId: string): { fetch(request: Request): Promise<Response>; hubToken: string } | undefined;\n}\n\n/** One agent that edits files: a hub session or an in-process child run. */\nexport interface PoEditPresenceAgent {\n /** `session:<id>` or `run:<runId>`. Unique across the host. */\n readonly key: string;\n /** Read when a note is rendered, so a name registered after the edit still shows. */\n readonly label: () => string;\n /** Hub session id other agents can reach with intercom. */\n readonly contact: string;\n readonly isLive: () => boolean;\n}\n\nexport interface PoEditPresenceEntry {\n readonly agent: PoEditPresenceAgent;\n /** `@file` tag of the bytes this agent's last successful edit or write left behind. */\n readonly tag: string;\n readonly at: number;\n}\n\n/**\n * Host-wide record of the last edit each live agent made to a file, keyed by realpath then agent key.\n * Owners delete their entries when they dispose, so no callback outlives its session or run.\n */\nexport type PoEditPresenceLedger = Map<string, Map<string, PoEditPresenceEntry>>;\n\n/** Why shareFile rejects while the host has no public tunnel origin. */\nexport const PO_SHARE_FILE_REMOTE_OFF_MESSAGE =\n 'Remote access is off, so Po cannot share files with remote MCP servers. Turn it on and retry.';\n\n/**\n * A file published at a short-lived public tunnel URL: 10 minutes, 3 downloads, and gone\n * when the tunnel revision changes. Call revoke as soon as the consumer is done with it.\n */\nexport interface PoSharedFile {\n /** Bearer link; never log or persist it. */\n url: string;\n fileName: string;\n mimeType: string;\n size: number;\n /** Expiry as epoch milliseconds. */\n expiresAt: number;\n maxDownloads: number;\n /** Idempotent. */\n revoke(): void;\n}\n\n/** Server-only admission for a registered file. Never construct this from browser input.\n * The opener must revalidate ownership, containment, symlinks and registered identity\n * on every call. Core pins identity again and bounds streaming to the admitted size.\n */\nexport interface PoOpenedRegisteredFile {\n identity: string;\n size: number;\n /** Single-use bounded stream. Ending or cancelling closes the opened resource. */\n stream(): ReadableStream<Uint8Array>;\n close(): Promise<void>;\n}\n\nexport interface PoRegisteredFile {\n fileName: string;\n open(): Promise<PoOpenedRegisteredFile>;\n}\n\n/** What the host tells an API about itself when it starts. */\nexport interface PoApiContext {\n scope: PoApiScope;\n /** Stable, opaque workspace id, shared by every session and member checkout of the workspace. */\n workspaceId?: string;\n /**\n * The admitted workspace root for a workspace mount. For a session mount it is the checkout\n * the session runs in; read sessionContext for both roots. Absent from global mounts.\n */\n workspaceRoot?: string;\n /** The session's baseline context; present on session mounts whose host knows its workspace. */\n sessionContext?: PoSessionContext;\n /** Explicit host home, never selected by a remote request. */\n homeDirectory?: string;\n /** The session this host serves; absent for a hub-scoped API. */\n sessionId?: string;\n /** The session's working directory; absent for a hub-scoped API. */\n cwd?: string;\n /** Session environment admitted by the server, never supplied by remote clients. */\n environment?: Readonly<Record<string, string | undefined>>;\n /** Shared only with child processes in this session, never with remote API clients. */\n internalToken?: string;\n /** Shared only with the cockpit hub for privileged cross-session coordination. */\n hubToken?: string;\n /**\n * Resolves a hub-issued opaque repository id to an admitted canonical root.\n * Hub APIs must never accept a browser-supplied filesystem path instead.\n */\n resolveRepository?(repositoryId: string): string | undefined;\n /** Reads the admitted repository's sync projection without exposing its state path. */\n readRepositorySync?(\n repositoryId: string,\n ): Promise<PoRepositorySyncView | undefined> | PoRepositorySyncView | undefined;\n /** Admitted workspaces available to this mount. */\n repositories?(): readonly { id: string; name: string; path: string; active: boolean }[];\n /** Publish a successful config write to the owning mount's refresh lifecycle. */\n configurationChanged?(): void;\n /**\n * Borrows the hub's OAuth redirect surface. Undefined when the hub cannot\n * currently serve one, in which case the package keeps its own behaviour.\n */\n oauthRedirect?(): PoOAuthRedirect | undefined;\n /** Canonical in-process lifecycle for sessions created by this host. */\n sessionService?: PoHubSessionService;\n /** Trusted in-process storage capability. APIs must retain their mount's session authorization. */\n sessionStorage?: PoSessionStorageService;\n /** Host-bound, authenticated inter-session event endpoint for this session. */\n sessionCommunication?: PoSessionCommunicationEndpoint;\n /** Same-process events shared by session APIs and hub channels. */\n directEvents?: PoDirectEventBus;\n /**\n * Publishes this session's activity line to every page's rail; undefined clears it.\n * Session mounts only. The last publisher wins.\n */\n publishActivity?(activity: PoSessionActivity | undefined): void;\n /** Trusted in-process dispatch to an explicitly selected mounted API. No browser authentication is performed here. */\n requestApi?(mount: PoApiMount, basePath: string, request: Request): Promise<Response>;\n /** Present only on a session mounted by an authenticated Desktop host. */\n computerUse?: PoComputerUseSessionAccess;\n /** Desktop's macOS privacy permissions; present on hub mounts only when Desktop hosts this server. */\n desktopPermissions?: {\n /** A `change` asks macOS for a permission and needs the step-up computer-use activation needs. */\n authorize(headers: Headers, change: boolean): boolean;\n /** The permission states, after asking macOS for `permission` when it is set. */\n request(permission?: string): Promise<unknown>;\n };\n /** Deliver a typed method to the channel serving a session this mount reaches, retaining the caller's connection identity. */\n receiveChannel?(sessionId: string, frameType: string, payload: unknown, connectionId: string): boolean;\n /**\n * Tells connected clients this mount's `name` data changed, so they fetch it again instead of polling.\n * Scoped by the mount: `global:<name>` on the global mount, `workspace:<id>:<name>` on a workspace mount.\n */\n bumpRevision?(name: string): void;\n /** Sets a live session's undecided approval count on its rail card; 0 clears it. Global mount only. */\n setSessionPendingApprovals?(sessionId: string, count: number): void;\n /** Host-owned replay evidence, supplied only on the global mount. Never exposed to browsers or agents. */\n requestReceipts?: PoRequestReceipts;\n /** One host's exclusive media activity, shared across global and session scopes. */\n mediaArbitration?: PoHostMediaArbitration;\n /** One host-wide heavy-job budget, not a separate budget for every session. */\n executionBudget?: PoHostExecutionBudget;\n /** One host-wide edit presence ledger, shared by every session mount. */\n editPresence?: PoEditPresenceLedger;\n /** Paired agents belonging to this host only. */\n peerAgents?: PoPeerAgentRegistry;\n /** Machine-owned Remote Control service, mounted only by Po's own global server plugin. */\n remoteControl?: { fetch(request: Request): Promise<Response> };\n /**\n * Publishes a file from this session's cwd or attachments at a short-lived tunnel URL.\n * Session mounts only; rejects with PO_SHARE_FILE_REMOTE_OFF_MESSAGE while remote access is off.\n * The host asks no consent: the caller must obtain it before passing the URL on.\n */\n shareFile?(path: string, label: string): Promise<PoSharedFile>;\n /** Trusted in-process registered-file admission, separate from ordinary capped shareFile. */\n shareRegisteredFile?(file: PoRegisteredFile): Promise<PoSharedFile>;\n /**\n * This server's loopback origin, such as `http://127.0.0.1:4242`. Throws until the listener is up,\n * so read it when serving a request, not at start.\n */\n serverOrigin?(): string;\n onNotice(message: string): void;\n}\n\n/** One accepted WebSocket on a public route. Text frames arrive as strings, binary frames as bytes. */\nexport interface PoApiSocket {\n readonly isOpen: boolean;\n send(data: string | Uint8Array): void;\n close(code?: number, reason?: string): void;\n onMessage(listener: (data: string | Uint8Array) => void): void;\n onClose(listener: (code: number, reason: string) => void): void;\n}\n\n/**\n * Routes the host serves without its own token, for a client that holds a package-issued token instead.\n * Global scope only. The host calls `authenticate` before `fetch` or `upgrade` and answers 401 when it\n * returns false, so a handler is never reached unauthenticated. Requests must also come from a loopback\n * peer and Host, carry no Origin or a `chrome-extension://` one, and never arrive through the tunnel.\n */\nexport interface PoApiPublicRoutes {\n /** Path prefixes below the base path, such as `/extension`; each starts with `/`. */\n readonly paths: readonly string[];\n authenticate(request: Request): boolean | Promise<boolean>;\n}\n\n/**\n * One running surface. The host strips the mount prefix before calling, so\n * routes are declared relative ('/runners/:id/log') and a package never\n * repeats where it was mounted.\n */\nexport interface PoApiHandler {\n fetch(request: Request): Response | Promise<Response>;\n /** Accepts a WebSocket upgrade on a public route; the host has already authenticated it. */\n upgrade?(request: Request, socket: PoApiSocket): void;\n close(): void;\n}\n\nexport interface PoApi {\n /** Segment under /api/plugins/; globally unique across loaded packages. */\n basePath: string;\n /** Routes served without the host token; accepted for global-scope packages only. */\n publicRoutes?: PoApiPublicRoutes;\n start(context: PoApiContext): PoApiHandler;\n}\n"],"mappings":";AAgCA,MAAa,gBAAuC;CAAC;CAAU;CAAa;AAAS;;AA6CrF,SAAgB,eAAe,OAAsD;CACnF,IAAI,MAAM,UAAU,UAAU,OAAO;CACrC,IAAI,MAAM,UAAU,aAAa,OAAO,mBAAmB,mBAAmB,MAAM,WAAW,EAAE;CACjG,IAAI,CAAC,MAAM,aAAa,MAAM,IAAI,MAAM,qDAAqD;CAC7F,OAAO,mBAAmB,mBAAmB,MAAM,WAAW,EAAE,YAAY,mBAAmB,MAAM,SAAS,EAAE;AAClH;;AASA,SAAgB,kBAAkB,UAA6C;CAC7E,IAAI,aAAa,WAAW,OAAO,EAAE,OAAO,SAAS;CACrD,MAAM,QAAQ,4DAA4D,KAAK,QAAQ;CACvF,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,IAAI;EACF,MAAM,cAAc,mBAAmB,MAAM,EAAE;EAC/C,OAAO,MAAM,OAAO,KAAA,IAChB;GAAE,OAAO;GAAa;EAAY,IAClC;GAAE,OAAO;GAAW;GAAa,WAAW,mBAAmB,MAAM,EAAE;EAAE;CAC/E,QAAQ;EACN;CACF;AACF;;AAGA,MAAa,sBAAsB;;AAEnC,MAAa,iCAAiC;;AAG9C,MAAa,gCAAgC;AAC7C,MAAa,iCAAiC;AAC9C,MAAa,+BAA+B;;AAE5C,MAAa,kCAAkC;AAE/C,MAAa,wBAAwB;CACnC;CACA;CACA;CACA;AACF;AASA,SAAS,qBACP,SAC+E;CAC/E,MAAM,MAAM,QAAQ,IAAI,+BAA+B;CACvD,IAAI,QAAQ,MAAM,OAAO,KAAA;CACzB,IAAI;EACF,MAAM,QAAQ,KAAK,MAAM,GAAG;EAC5B,IACE,OAAO,MAAM,WAAW,WAAW,YACnC,CAAC,MAAM,UAAU,UACjB,OAAO,MAAM,UAAU,YAAY,YACnC,CAAC,MAAM,UAAU,WACjB,OAAO,MAAM,QAAQ,YACrB,CAAC,MAAM,OACP,OAAO,MAAM,QAAQ,UAAU,YAC/B,CAAC,MAAM,OAAO,SACb,MAAM,QAAQ,KAAA,MACZ,OAAO,MAAM,IAAI,kBAAkB,YAClC,CAAC,MAAM,IAAI,iBACX,OAAO,MAAM,IAAI,YAAY,YAC7B,CAAC,MAAM,IAAI,UAEf,OAAO,KAAA;EACT,OAAO;GACL,WAAW;IAAE,QAAQ,MAAM,UAAU;IAAQ,SAAS,MAAM,UAAU;GAAQ;GAC9E,KAAK,MAAM;GACX,QAAQ,EAAE,OAAO,MAAM,OAAO,MAAM;GACpC,GAAI,MAAM,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,EAAE,IAAI,CAAC;EAC/C;CACF,QAAQ;EACN;CACF;AACF;;AAGA,SAAgB,gBAAgB,SAA2C;CACzE,MAAM,aAAa,qBAAqB,OAAO;CAC/C,IAAI,QAAQ,IAAA,4BAAmC,KAAK,CAAC,YAAY,OAAO,KAAA;CACxE,MAAM,WAAW,QAAQ,IAAI,6BAA6B;CAC1D,MAAM,WAAW,QAAQ,IAAI,8BAA8B;CAC3D,MAAM,SAAS,QAAQ,IAAI,4BAA4B;CACvD,IAAI,aAAa,SACf,OAAO,aAAa,QAAQ,WAAW,kBAAkB,CAAC,aAAa;EAAE;EAAU;CAAO,IAAI,KAAA;CAEhG,IACE,aAAa,YACb,aAAa,QACb,aAAa,OACZ,WAAW,kBAAkB,WAAW,cAAc,WAAW,gBAElE,OAAO;EAAE;EAAU;EAAU;EAAQ,GAAG;CAAW;AAGvD;;AA4GA,MAAa,mCACX"}
@@ -227,6 +227,21 @@ export interface PoSharedFile {
227
227
  /** Idempotent. */
228
228
  revoke(): void;
229
229
  }
230
+ /** Server-only admission for a registered file. Never construct this from browser input.
231
+ * The opener must revalidate ownership, containment, symlinks and registered identity
232
+ * on every call. Core pins identity again and bounds streaming to the admitted size.
233
+ */
234
+ export interface PoOpenedRegisteredFile {
235
+ identity: string;
236
+ size: number;
237
+ /** Single-use bounded stream. Ending or cancelling closes the opened resource. */
238
+ stream(): ReadableStream<Uint8Array>;
239
+ close(): Promise<void>;
240
+ }
241
+ export interface PoRegisteredFile {
242
+ fileName: string;
243
+ open(): Promise<PoOpenedRegisteredFile>;
244
+ }
230
245
  /** What the host tells an API about itself when it starts. */
231
246
  export interface PoApiContext {
232
247
  scope: PoApiScope;
@@ -325,6 +340,8 @@ export interface PoApiContext {
325
340
  * The host asks no consent: the caller must obtain it before passing the URL on.
326
341
  */
327
342
  shareFile?(path: string, label: string): Promise<PoSharedFile>;
343
+ /** Trusted in-process registered-file admission, separate from ordinary capped shareFile. */
344
+ shareRegisteredFile?(file: PoRegisteredFile): Promise<PoSharedFile>;
328
345
  /**
329
346
  * This server's loopback origin, such as `http://127.0.0.1:4242`. Throws until the listener is up,
330
347
  * so read it when serving a request, not at start.
@@ -1 +1 @@
1
- {"version":3,"file":"packageApi.d.mts","names":[],"sources":["../../../src/schemas/packageApi.ts"],"mappings":";;;;;;iBAqBiB;EACf;EACA;;EAEA;EACA,gBAAgB;;;YAIN;qBAEC,wBAAwB;;YAGzB;EACN;;EACA;EAAoB;;EACpB;EAAkB;;;iBAGP;EACf;EACA,OAAO;EACP;EACA;EACA;EACA;;EAEA;;EAEA;;EAEA;EACA;;EAEA;;;;;;iBAOe;EACf;EACA,OAAO;EACP;EACA;EACA;;EAEA;EACA;;EAEA;;;wBAIc,eAAe,OAAO;EAAe;;;YAQzC;EACN;;EACA;EAAoB;;EACpB;EAAkB;EAAqB;;;wBAG7B,kBAAkB,mBAAmB;;qBAexC;;qBAEA;;qBAGA;qBACA;qBACA;;qBAEA;qBAEA;YAOD;YACA;EACN;EAAmB;;EACnB;EAAoB;EAAkB,QAAQ;KAElD,yDAAyD;;wBAqC3C,gBAAgB,SAAS,UAAU;;;;;;;;;;iBA4BlC;;WAEN;;;;;EAKT,QAAQ,eAAe,qBAAqB;;EAE5C,KAAK,eAAe,qBAAqB;IAAU;IAAc;;;EAEjE,OAAO;;;iBAIQ;EACf;EACA;EACA;;iBAGe,yBAAyB;EACxC;EACA;EACA;EACA;;iBAGe,oCAAoC;;EAEnD;EACA;EACA;;YAGU;EACN;EAAkB;;EAClB;EAAkB,SAAS;;EAC3B;EAAkB,SAAS;;;iBAGhB;EACf,QAAQ,SAAS,8BAA8B,QAAQ;EACvD,OAAO,KAAK,sBAAsB,QAAQ;EAC1C,OACE,SAAS;IAAwB;IAAe;MAC/C,QAAQ;;;iBAII;;EAEf,eAAe,cAAc;;;iBAId;EACf,QAAQ,SAAS,cAAc,QAAQ;EACvC,eAAe;IAAW;IAAiB;IAAgB;;;;iBAI5C;EACf,SAAS;EACT,UAAU;;;iBAIK;EACf,SAAS,mBAAmB;IAAS,MAAM,SAAS,UAAU,QAAQ;KAAa;EACnF,IAAI;IAAsB,MAAM,SAAS,UAAU,QAAQ;IAAW;;;;iBAIvD;;WAEN;;WAEA;;WAEA;WACA;;iBAGM;WACN,OAAO;;WAEP;WACA;;;;;;YAOC,uBAAuB,YAAY,YAAY;;qBAG9C;;;;;iBAOI;;EAEf;EACA;EACA;EACA;;EAEA;EACA;;EAEA;;;iBAIe;EACf,OAAO;;EAEP;;;;;EAKA;;EAEA,iBAAiB;;EAEjB;;EAEA;;EAEA;;EAEA,cAAc,SAAS;;EAEvB;;EAEA;;;;;EAKA,mBAAmB;;EAEnB,oBACE,uBACC,QAAQ,oCAAoC;;EAE/C;IAA4B;IAAY;IAAc;IAAc;;;EAEpE;;;;;EAKA,kBAAkB;;EAElB,iBAAiB;;EAEjB,iBAAiB;;EAEjB,uBAAuB;;EAEvB,eAAe;;;;;EAKf,iBAAiB,UAAU;;EAE3B,YAAY,OAAO,YAAY,kBAAkB,SAAS,UAAU,QAAQ;;EAE5E,cAAc;;EAEd;;IAEE,UAAU,SAAS,SAAS;;IAE5B,QAAQ,sBAAsB;;;EAGhC,gBAAgB,mBAAmB,mBAAmB,kBAAkB;;;;;EAKxE,cAAc;;EAEd,4BAA4B,mBAAmB;;EAE/C,kBAAkB;;EAElB,mBAAmB;;EAEnB,kBAAkB;;EAElB,eAAe;;EAEf,aAAa;;EAEb;IAAkB,MAAM,SAAS,UAAU,QAAQ;;;;;;;EAMnD,WAAW,cAAc,gBAAgB,QAAQ;;;;;EAKjD;EACA,SAAS;;;iBAIM;WACN;EACT,KAAK,eAAe;EACpB,MAAM,eAAe;EACrB,UAAU,WAAW,eAAe;EACpC,QAAQ,WAAW,cAAc;;;;;;;;iBASlB;;WAEN;EACT,aAAa,SAAS,oBAAoB;;;;;;;iBAQ3B;EACf,MAAM,SAAS,UAAU,WAAW,QAAQ;;EAE5C,SAAS,SAAS,SAAS,QAAQ;EACnC;;iBAGe;;EAEf;;EAEA,eAAe;EACf,MAAM,SAAS,eAAe"}
1
+ {"version":3,"file":"packageApi.d.mts","names":[],"sources":["../../../src/schemas/packageApi.ts"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"packageApi.mjs","names":[],"sources":["../../../src/schemas/packageApi.ts"],"sourcesContent":["/**\n * The HTTP surface a package offers its host.\n *\n * A server plugin registers one with its host. The host mounts it under the API\n * route prefix, and the package writes routes relative to that mount.\n *\n * The handler is a plain fetch callback so no host and no package is committed\n * to one HTTP framework; a Hono app satisfies it through `app.fetch`.\n */\n\nimport type {\n PoComputerUseSessionAccess,\n PoDirectEventBus,\n PoHubSessionService,\n PoSessionCommunicationEndpoint,\n PoSessionActivity,\n PoSessionContext,\n} from './hubChannel';\nimport type { PoMcpProjection } from './mcpProjection';\nimport type { PoSessionStorageService } from './platform';\n/** The host-owned sync view a repository-scoped package API may inspect. */\nexport interface PoRepositorySyncView {\n fresh: boolean;\n reasons: string[];\n /** True while a background sync that may prepare what `reasons` names is running. */\n syncing?: boolean;\n mcpProjection?: PoMcpProjection;\n}\n\n/** Where an API runs: inside one session's server, or in the machine-wide hub. */\nexport type PoApiScope = 'global' | 'workspace' | 'session';\n\nexport const PO_API_SCOPES: readonly PoApiScope[] = ['global', 'workspace', 'session'];\n\n/** Identity of an independently mounted plugin host. */\nexport type PoApiMount =\n | { scope: 'global' }\n | { scope: 'workspace'; workspaceId: string }\n | { scope: 'session'; sessionId: string };\n\n/** Immutable browser contribution generation paired with a server mount. */\nexport interface PoWebComposition {\n id: string;\n scope: PoApiScope;\n revision: number;\n manifestUrl: string;\n rawAssetBaseUrl: string;\n verifiedAssetBaseUrl: string;\n /** `[slot, entry]` for each of this mount's plugin copies, the entry an absolute path below the asset prefixes. */\n pluginEntries: [string, string][];\n /** Every signed file this mount's slots can load, absolute below the asset prefixes. */\n assetPaths: string[];\n /** Per-plugin stylesheets of this mount's plugins only, so other mounts' classes never apply here. */\n stylePaths: string[];\n channels: string[];\n /** The slots resolved for this mount; the shared build may hold more. */\n plugins: string[];\n}\n\n/**\n * Immutable native contribution generation paired with a server mount. One signed CommonJS module per\n * plugin, served through the same immutable plugin routes as web compositions.\n */\nexport interface PoNativeComposition {\n id: string;\n scope: PoApiScope;\n revision: number;\n manifestUrl: string;\n rawAssetBaseUrl: string;\n /** `[pluginId, modulePath]` for each plugin of this mount that built; the shell loads each on its own. */\n pluginEntries: [string, string][];\n channels: string[];\n /** The `poNative.pluginId` values resolved for this mount; the shell installs only these. */\n plugins: string[];\n}\n\n/** Canonical public prefix. Scope selection never falls through to a parent. */\nexport function poApiMountPath(mount: PoApiMount & { workspaceId?: string }): string {\n if (mount.scope === 'global') return '/api/plugins';\n if (mount.scope === 'workspace') return `/api/workspaces/${encodeURIComponent(mount.workspaceId)}/plugins`;\n if (!mount.workspaceId) throw new Error('A session API path requires its workspace identity.');\n return `/api/workspaces/${encodeURIComponent(mount.workspaceId)}/sessions/${encodeURIComponent(mount.sessionId)}/plugins`;\n}\n\n/** Scope addressed by a public WebSocket upgrade. */\nexport type PoSocketMount =\n | { scope: 'global' }\n | { scope: 'workspace'; workspaceId: string }\n | { scope: 'session'; workspaceId: string; sessionId: string };\n\n/** Only canonical resource paths may select a protocol listener. */\nexport function parsePoSocketPath(pathname: string): PoSocketMount | undefined {\n if (pathname === '/api/ws') return { scope: 'global' };\n const match = /^\\/api\\/workspaces\\/([^/]+)(?:\\/sessions\\/([^/]+))?\\/ws$/u.exec(pathname);\n if (!match) return undefined;\n try {\n const workspaceId = decodeURIComponent(match[1]);\n return match[2] === undefined\n ? { scope: 'workspace', workspaceId }\n : { scope: 'session', workspaceId, sessionId: decodeURIComponent(match[2]) };\n } catch {\n return undefined;\n }\n}\n\n/** The segment an API is mounted under, below this prefix. */\nexport const PO_API_ROUTE_PREFIX = '/api/plugins';\n/** Selects the hub API bundle that owns the named cockpit session. */\nexport const PO_HUB_API_SESSION_QUERY_PARAM = 'hubSession';\n\n/** Trusted caller headers written only by the cockpit-to-session proxy. */\nexport const PO_API_CALLER_LOCALITY_HEADER = 'x-po-api-caller-locality';\nexport const PO_API_CALLER_DEVICE_ID_HEADER = 'x-po-api-caller-device-id';\nexport const PO_API_CALLER_STEP_UP_HEADER = 'x-po-api-caller-step-up';\n/** Set only after destination verification, never accepted from an untrusted proxy. */\nexport const PO_API_CALLER_FEDERATION_HEADER = 'x-po-api-caller-federation';\nexport type { FederationCaller as PoFederationCaller } from '@agimon-ai/po-web-security/deviceProof';\nexport const PO_API_CALLER_HEADERS = [\n PO_API_CALLER_LOCALITY_HEADER,\n PO_API_CALLER_DEVICE_ID_HEADER,\n PO_API_CALLER_STEP_UP_HEADER,\n PO_API_CALLER_FEDERATION_HEADER,\n] as const;\n\nexport type PoApiCallerStepUp = 'not-required' | 'verified' | 'unavailable';\nexport type PoApiCaller = (\n | { locality: 'local'; stepUp: 'not-required' }\n | { locality: 'remote'; deviceId: string; stepUp: PoApiCallerStepUp }\n) &\n Partial<import('@agimon-ai/po-web-security/deviceProof').FederationCaller>;\n\nfunction federationCallerFrom(\n headers: Headers,\n): import('@agimon-ai/po-web-security/deviceProof').FederationCaller | undefined {\n const raw = headers.get(PO_API_CALLER_FEDERATION_HEADER);\n if (raw === null) return undefined;\n try {\n const value = JSON.parse(raw) as import('@agimon-ai/po-web-security/deviceProof').FederationCaller;\n if (\n typeof value.principal?.issuer !== 'string' ||\n !value.principal.issuer ||\n typeof value.principal.subject !== 'string' ||\n !value.principal.subject ||\n typeof value.org !== 'string' ||\n !value.org ||\n typeof value.device?.keyId !== 'string' ||\n !value.device.keyId ||\n (value.via !== undefined &&\n (typeof value.via.callingHostId !== 'string' ||\n !value.via.callingHostId ||\n typeof value.via.grantId !== 'string' ||\n !value.via.grantId))\n )\n return undefined;\n return {\n principal: { issuer: value.principal.issuer, subject: value.principal.subject },\n org: value.org,\n device: { keyId: value.device.keyId },\n ...(value.via ? { via: { ...value.via } } : {}),\n };\n } catch {\n return undefined;\n }\n}\n\n/** Reads the proxy-authenticated caller identity, rejecting partial or contradictory stamps. */\nexport function poApiCallerFrom(headers: Headers): PoApiCaller | undefined {\n const federation = federationCallerFrom(headers);\n if (headers.has(PO_API_CALLER_FEDERATION_HEADER) && !federation) return undefined;\n const locality = headers.get(PO_API_CALLER_LOCALITY_HEADER);\n const deviceId = headers.get(PO_API_CALLER_DEVICE_ID_HEADER);\n const stepUp = headers.get(PO_API_CALLER_STEP_UP_HEADER);\n if (locality === 'local') {\n return deviceId === null && stepUp === 'not-required' && !federation ? { locality, stepUp } : undefined;\n }\n if (\n locality === 'remote' &&\n deviceId !== null &&\n deviceId !== '' &&\n (stepUp === 'not-required' || stepUp === 'verified' || stepUp === 'unavailable')\n ) {\n return { locality, deviceId, stepUp, ...federation };\n }\n return undefined;\n}\n/**\n * A browser-reachable OAuth redirect the host serves on its own listener.\n *\n * A package brokering third-party OAuth cannot use a loopback redirect when the\n * operator's browser is on another machine: `127.0.0.1` resolves to whichever\n * machine the browser runs on. The host owns a listener that is reachable from\n * wherever the cockpit is being used, so it lends that instead of the package\n * opening a port of its own.\n */\nexport interface PoOAuthRedirect {\n /** Absolute URL to register as the redirect target, already origin-correct. */\n readonly redirectUri: string;\n /**\n * Accept redirects carrying this state. Must complete before the\n * authorization URL is surfaced, or the redirect races its own reservation.\n */\n reserve(state: string, timeoutMs?: number): Promise<void>;\n /** Resolves once the redirect carrying this state arrives. */\n wait(state: string, timeoutMs?: number): Promise<{ code: string; state: string }>;\n /** Drops a reservation whose flow ended without a redirect. */\n cancel(state: string): void;\n}\n\n/** An exact, host-owned receipt address. Provider IDs are not event cursor positions. */\nexport interface PoRequestReceiptKey {\n namespace: string;\n activationId: string;\n requestId: string;\n}\n\nexport interface PoRequestReceipt extends PoRequestReceiptKey {\n fingerprint: string;\n destination: string;\n transactionId: string;\n outcome: 'reserved' | 'admitted' | 'rejected' | 'uncertain';\n}\n\nexport interface PoRequestReceiptReservation extends PoRequestReceiptKey {\n /** Lowercase SHA-256 of the authoritative request text, never the full transcript. */\n fingerprint: string;\n destination: string;\n transactionId: string;\n}\n\nexport type PoRequestReceiptReserveResult =\n | { kind: 'reserved'; token: string }\n | { kind: 'existing'; receipt: PoRequestReceipt }\n | { kind: 'conflict'; receipt: PoRequestReceipt };\n\n/** A global-only receipt capability. A reservation is not an execution receipt. */\nexport interface PoRequestReceipts {\n reserve(request: PoRequestReceiptReservation): Promise<PoRequestReceiptReserveResult>;\n lookup(key: PoRequestReceiptKey): Promise<PoRequestReceipt | undefined>;\n finish(\n request: PoRequestReceiptKey & { token: string; outcome: 'admitted' | 'rejected' | 'uncertain' },\n ): Promise<PoRequestReceipt>;\n}\n\n/** Permit returned for expensive local work. Calling it releases the slot. */\nexport interface PoHostExecutionPermit {\n (): void;\n attachProcess?(pid: number): Promise<void>;\n}\n\n/** Host-wide admission for expensive local work. Release only after the owned process exits. */\nexport interface PoHostExecutionBudget {\n acquire(signal?: AbortSignal): Promise<PoHostExecutionPermit>;\n getSnapshot(): Readonly<{ running: number; queued: number; limit: number }>;\n}\n\n/** Synchronous host-owned exclusion across media APIs mounted at different scopes. */\nexport interface PoHostMediaArbitration {\n register(busy: () => boolean): () => void;\n available(busy: () => boolean): boolean;\n}\n\n/** One host's paired agent registrations, shared across its global and session mounts. */\nexport interface PoPeerAgentRegistry {\n register(sessionId: string, agent: { fetch(request: Request): Promise<Response> }, hubToken: string): () => void;\n get(sessionId: string): { fetch(request: Request): Promise<Response>; hubToken: string } | undefined;\n}\n\n/** One agent that edits files: a hub session or an in-process child run. */\nexport interface PoEditPresenceAgent {\n /** `session:<id>` or `run:<runId>`. Unique across the host. */\n readonly key: string;\n /** Read when a note is rendered, so a name registered after the edit still shows. */\n readonly label: () => string;\n /** Hub session id other agents can reach with intercom. */\n readonly contact: string;\n readonly isLive: () => boolean;\n}\n\nexport interface PoEditPresenceEntry {\n readonly agent: PoEditPresenceAgent;\n /** `@file` tag of the bytes this agent's last successful edit or write left behind. */\n readonly tag: string;\n readonly at: number;\n}\n\n/**\n * Host-wide record of the last edit each live agent made to a file, keyed by realpath then agent key.\n * Owners delete their entries when they dispose, so no callback outlives its session or run.\n */\nexport type PoEditPresenceLedger = Map<string, Map<string, PoEditPresenceEntry>>;\n\n/** Why shareFile rejects while the host has no public tunnel origin. */\nexport const PO_SHARE_FILE_REMOTE_OFF_MESSAGE =\n 'Remote access is off, so Po cannot share files with remote MCP servers. Turn it on and retry.';\n\n/**\n * A file published at a short-lived public tunnel URL: 10 minutes, 3 downloads, and gone\n * when the tunnel revision changes. Call revoke as soon as the consumer is done with it.\n */\nexport interface PoSharedFile {\n /** Bearer link; never log or persist it. */\n url: string;\n fileName: string;\n mimeType: string;\n size: number;\n /** Expiry as epoch milliseconds. */\n expiresAt: number;\n maxDownloads: number;\n /** Idempotent. */\n revoke(): void;\n}\n\n/** What the host tells an API about itself when it starts. */\nexport interface PoApiContext {\n scope: PoApiScope;\n /** Stable, opaque workspace id, shared by every session and member checkout of the workspace. */\n workspaceId?: string;\n /**\n * The admitted workspace root for a workspace mount. For a session mount it is the checkout\n * the session runs in; read sessionContext for both roots. Absent from global mounts.\n */\n workspaceRoot?: string;\n /** The session's baseline context; present on session mounts whose host knows its workspace. */\n sessionContext?: PoSessionContext;\n /** Explicit host home, never selected by a remote request. */\n homeDirectory?: string;\n /** The session this host serves; absent for a hub-scoped API. */\n sessionId?: string;\n /** The session's working directory; absent for a hub-scoped API. */\n cwd?: string;\n /** Session environment admitted by the server, never supplied by remote clients. */\n environment?: Readonly<Record<string, string | undefined>>;\n /** Shared only with child processes in this session, never with remote API clients. */\n internalToken?: string;\n /** Shared only with the cockpit hub for privileged cross-session coordination. */\n hubToken?: string;\n /**\n * Resolves a hub-issued opaque repository id to an admitted canonical root.\n * Hub APIs must never accept a browser-supplied filesystem path instead.\n */\n resolveRepository?(repositoryId: string): string | undefined;\n /** Reads the admitted repository's sync projection without exposing its state path. */\n readRepositorySync?(\n repositoryId: string,\n ): Promise<PoRepositorySyncView | undefined> | PoRepositorySyncView | undefined;\n /** Admitted workspaces available to this mount. */\n repositories?(): readonly { id: string; name: string; path: string; active: boolean }[];\n /** Publish a successful config write to the owning mount's refresh lifecycle. */\n configurationChanged?(): void;\n /**\n * Borrows the hub's OAuth redirect surface. Undefined when the hub cannot\n * currently serve one, in which case the package keeps its own behaviour.\n */\n oauthRedirect?(): PoOAuthRedirect | undefined;\n /** Canonical in-process lifecycle for sessions created by this host. */\n sessionService?: PoHubSessionService;\n /** Trusted in-process storage capability. APIs must retain their mount's session authorization. */\n sessionStorage?: PoSessionStorageService;\n /** Host-bound, authenticated inter-session event endpoint for this session. */\n sessionCommunication?: PoSessionCommunicationEndpoint;\n /** Same-process events shared by session APIs and hub channels. */\n directEvents?: PoDirectEventBus;\n /**\n * Publishes this session's activity line to every page's rail; undefined clears it.\n * Session mounts only. The last publisher wins.\n */\n publishActivity?(activity: PoSessionActivity | undefined): void;\n /** Trusted in-process dispatch to an explicitly selected mounted API. No browser authentication is performed here. */\n requestApi?(mount: PoApiMount, basePath: string, request: Request): Promise<Response>;\n /** Present only on a session mounted by an authenticated Desktop host. */\n computerUse?: PoComputerUseSessionAccess;\n /** Desktop's macOS privacy permissions; present on hub mounts only when Desktop hosts this server. */\n desktopPermissions?: {\n /** A `change` asks macOS for a permission and needs the step-up computer-use activation needs. */\n authorize(headers: Headers, change: boolean): boolean;\n /** The permission states, after asking macOS for `permission` when it is set. */\n request(permission?: string): Promise<unknown>;\n };\n /** Deliver a typed method to the channel serving a session this mount reaches, retaining the caller's connection identity. */\n receiveChannel?(sessionId: string, frameType: string, payload: unknown, connectionId: string): boolean;\n /**\n * Tells connected clients this mount's `name` data changed, so they fetch it again instead of polling.\n * Scoped by the mount: `global:<name>` on the global mount, `workspace:<id>:<name>` on a workspace mount.\n */\n bumpRevision?(name: string): void;\n /** Sets a live session's undecided approval count on its rail card; 0 clears it. Global mount only. */\n setSessionPendingApprovals?(sessionId: string, count: number): void;\n /** Host-owned replay evidence, supplied only on the global mount. Never exposed to browsers or agents. */\n requestReceipts?: PoRequestReceipts;\n /** One host's exclusive media activity, shared across global and session scopes. */\n mediaArbitration?: PoHostMediaArbitration;\n /** One host-wide heavy-job budget, not a separate budget for every session. */\n executionBudget?: PoHostExecutionBudget;\n /** One host-wide edit presence ledger, shared by every session mount. */\n editPresence?: PoEditPresenceLedger;\n /** Paired agents belonging to this host only. */\n peerAgents?: PoPeerAgentRegistry;\n /** Machine-owned Remote Control service, mounted only by Po's own global server plugin. */\n remoteControl?: { fetch(request: Request): Promise<Response> };\n /**\n * Publishes a file from this session's cwd or attachments at a short-lived tunnel URL.\n * Session mounts only; rejects with PO_SHARE_FILE_REMOTE_OFF_MESSAGE while remote access is off.\n * The host asks no consent: the caller must obtain it before passing the URL on.\n */\n shareFile?(path: string, label: string): Promise<PoSharedFile>;\n /**\n * This server's loopback origin, such as `http://127.0.0.1:4242`. Throws until the listener is up,\n * so read it when serving a request, not at start.\n */\n serverOrigin?(): string;\n onNotice(message: string): void;\n}\n\n/** One accepted WebSocket on a public route. Text frames arrive as strings, binary frames as bytes. */\nexport interface PoApiSocket {\n readonly isOpen: boolean;\n send(data: string | Uint8Array): void;\n close(code?: number, reason?: string): void;\n onMessage(listener: (data: string | Uint8Array) => void): void;\n onClose(listener: (code: number, reason: string) => void): void;\n}\n\n/**\n * Routes the host serves without its own token, for a client that holds a package-issued token instead.\n * Global scope only. The host calls `authenticate` before `fetch` or `upgrade` and answers 401 when it\n * returns false, so a handler is never reached unauthenticated. Requests must also come from a loopback\n * peer and Host, carry no Origin or a `chrome-extension://` one, and never arrive through the tunnel.\n */\nexport interface PoApiPublicRoutes {\n /** Path prefixes below the base path, such as `/extension`; each starts with `/`. */\n readonly paths: readonly string[];\n authenticate(request: Request): boolean | Promise<boolean>;\n}\n\n/**\n * One running surface. The host strips the mount prefix before calling, so\n * routes are declared relative ('/runners/:id/log') and a package never\n * repeats where it was mounted.\n */\nexport interface PoApiHandler {\n fetch(request: Request): Response | Promise<Response>;\n /** Accepts a WebSocket upgrade on a public route; the host has already authenticated it. */\n upgrade?(request: Request, socket: PoApiSocket): void;\n close(): void;\n}\n\nexport interface PoApi {\n /** Segment under /api/plugins/; globally unique across loaded packages. */\n basePath: string;\n /** Routes served without the host token; accepted for global-scope packages only. */\n publicRoutes?: PoApiPublicRoutes;\n start(context: PoApiContext): PoApiHandler;\n}\n"],"mappings":";AAgCA,MAAa,gBAAuC;CAAC;CAAU;CAAa;AAAS;;AA6CrF,SAAgB,eAAe,OAAsD;CACnF,IAAI,MAAM,UAAU,UAAU,OAAO;CACrC,IAAI,MAAM,UAAU,aAAa,OAAO,mBAAmB,mBAAmB,MAAM,WAAW,EAAE;CACjG,IAAI,CAAC,MAAM,aAAa,MAAM,IAAI,MAAM,qDAAqD;CAC7F,OAAO,mBAAmB,mBAAmB,MAAM,WAAW,EAAE,YAAY,mBAAmB,MAAM,SAAS,EAAE;AAClH;;AASA,SAAgB,kBAAkB,UAA6C;CAC7E,IAAI,aAAa,WAAW,OAAO,EAAE,OAAO,SAAS;CACrD,MAAM,QAAQ,4DAA4D,KAAK,QAAQ;CACvF,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,IAAI;EACF,MAAM,cAAc,mBAAmB,MAAM,EAAE;EAC/C,OAAO,MAAM,OAAO,KAAA,IAChB;GAAE,OAAO;GAAa;EAAY,IAClC;GAAE,OAAO;GAAW;GAAa,WAAW,mBAAmB,MAAM,EAAE;EAAE;CAC/E,QAAQ;EACN;CACF;AACF;;AAGA,MAAa,sBAAsB;;AAEnC,MAAa,iCAAiC;;AAG9C,MAAa,gCAAgC;AAC7C,MAAa,iCAAiC;AAC9C,MAAa,+BAA+B;;AAE5C,MAAa,kCAAkC;AAE/C,MAAa,wBAAwB;CACnC;CACA;CACA;CACA;AACF;AASA,SAAS,qBACP,SAC+E;CAC/E,MAAM,MAAM,QAAQ,IAAI,+BAA+B;CACvD,IAAI,QAAQ,MAAM,OAAO,KAAA;CACzB,IAAI;EACF,MAAM,QAAQ,KAAK,MAAM,GAAG;EAC5B,IACE,OAAO,MAAM,WAAW,WAAW,YACnC,CAAC,MAAM,UAAU,UACjB,OAAO,MAAM,UAAU,YAAY,YACnC,CAAC,MAAM,UAAU,WACjB,OAAO,MAAM,QAAQ,YACrB,CAAC,MAAM,OACP,OAAO,MAAM,QAAQ,UAAU,YAC/B,CAAC,MAAM,OAAO,SACb,MAAM,QAAQ,KAAA,MACZ,OAAO,MAAM,IAAI,kBAAkB,YAClC,CAAC,MAAM,IAAI,iBACX,OAAO,MAAM,IAAI,YAAY,YAC7B,CAAC,MAAM,IAAI,UAEf,OAAO,KAAA;EACT,OAAO;GACL,WAAW;IAAE,QAAQ,MAAM,UAAU;IAAQ,SAAS,MAAM,UAAU;GAAQ;GAC9E,KAAK,MAAM;GACX,QAAQ,EAAE,OAAO,MAAM,OAAO,MAAM;GACpC,GAAI,MAAM,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,EAAE,IAAI,CAAC;EAC/C;CACF,QAAQ;EACN;CACF;AACF;;AAGA,SAAgB,gBAAgB,SAA2C;CACzE,MAAM,aAAa,qBAAqB,OAAO;CAC/C,IAAI,QAAQ,IAAA,4BAAmC,KAAK,CAAC,YAAY,OAAO,KAAA;CACxE,MAAM,WAAW,QAAQ,IAAI,6BAA6B;CAC1D,MAAM,WAAW,QAAQ,IAAI,8BAA8B;CAC3D,MAAM,SAAS,QAAQ,IAAI,4BAA4B;CACvD,IAAI,aAAa,SACf,OAAO,aAAa,QAAQ,WAAW,kBAAkB,CAAC,aAAa;EAAE;EAAU;CAAO,IAAI,KAAA;CAEhG,IACE,aAAa,YACb,aAAa,QACb,aAAa,OACZ,WAAW,kBAAkB,WAAW,cAAc,WAAW,gBAElE,OAAO;EAAE;EAAU;EAAU;EAAQ,GAAG;CAAW;AAGvD;;AA4GA,MAAa,mCACX"}
1
+ {"version":3,"file":"packageApi.mjs","names":[],"sources":["../../../src/schemas/packageApi.ts"],"sourcesContent":["/**\n * The HTTP surface a package offers its host.\n *\n * A server plugin registers one with its host. The host mounts it under the API\n * route prefix, and the package writes routes relative to that mount.\n *\n * The handler is a plain fetch callback so no host and no package is committed\n * to one HTTP framework; a Hono app satisfies it through `app.fetch`.\n */\n\nimport type {\n PoComputerUseSessionAccess,\n PoDirectEventBus,\n PoHubSessionService,\n PoSessionCommunicationEndpoint,\n PoSessionActivity,\n PoSessionContext,\n} from './hubChannel';\nimport type { PoMcpProjection } from './mcpProjection';\nimport type { PoSessionStorageService } from './platform';\n/** The host-owned sync view a repository-scoped package API may inspect. */\nexport interface PoRepositorySyncView {\n fresh: boolean;\n reasons: string[];\n /** True while a background sync that may prepare what `reasons` names is running. */\n syncing?: boolean;\n mcpProjection?: PoMcpProjection;\n}\n\n/** Where an API runs: inside one session's server, or in the machine-wide hub. */\nexport type PoApiScope = 'global' | 'workspace' | 'session';\n\nexport const PO_API_SCOPES: readonly PoApiScope[] = ['global', 'workspace', 'session'];\n\n/** Identity of an independently mounted plugin host. */\nexport type PoApiMount =\n | { scope: 'global' }\n | { scope: 'workspace'; workspaceId: string }\n | { scope: 'session'; sessionId: string };\n\n/** Immutable browser contribution generation paired with a server mount. */\nexport interface PoWebComposition {\n id: string;\n scope: PoApiScope;\n revision: number;\n manifestUrl: string;\n rawAssetBaseUrl: string;\n verifiedAssetBaseUrl: string;\n /** `[slot, entry]` for each of this mount's plugin copies, the entry an absolute path below the asset prefixes. */\n pluginEntries: [string, string][];\n /** Every signed file this mount's slots can load, absolute below the asset prefixes. */\n assetPaths: string[];\n /** Per-plugin stylesheets of this mount's plugins only, so other mounts' classes never apply here. */\n stylePaths: string[];\n channels: string[];\n /** The slots resolved for this mount; the shared build may hold more. */\n plugins: string[];\n}\n\n/**\n * Immutable native contribution generation paired with a server mount. One signed CommonJS module per\n * plugin, served through the same immutable plugin routes as web compositions.\n */\nexport interface PoNativeComposition {\n id: string;\n scope: PoApiScope;\n revision: number;\n manifestUrl: string;\n rawAssetBaseUrl: string;\n /** `[pluginId, modulePath]` for each plugin of this mount that built; the shell loads each on its own. */\n pluginEntries: [string, string][];\n channels: string[];\n /** The `poNative.pluginId` values resolved for this mount; the shell installs only these. */\n plugins: string[];\n}\n\n/** Canonical public prefix. Scope selection never falls through to a parent. */\nexport function poApiMountPath(mount: PoApiMount & { workspaceId?: string }): string {\n if (mount.scope === 'global') return '/api/plugins';\n if (mount.scope === 'workspace') return `/api/workspaces/${encodeURIComponent(mount.workspaceId)}/plugins`;\n if (!mount.workspaceId) throw new Error('A session API path requires its workspace identity.');\n return `/api/workspaces/${encodeURIComponent(mount.workspaceId)}/sessions/${encodeURIComponent(mount.sessionId)}/plugins`;\n}\n\n/** Scope addressed by a public WebSocket upgrade. */\nexport type PoSocketMount =\n | { scope: 'global' }\n | { scope: 'workspace'; workspaceId: string }\n | { scope: 'session'; workspaceId: string; sessionId: string };\n\n/** Only canonical resource paths may select a protocol listener. */\nexport function parsePoSocketPath(pathname: string): PoSocketMount | undefined {\n if (pathname === '/api/ws') return { scope: 'global' };\n const match = /^\\/api\\/workspaces\\/([^/]+)(?:\\/sessions\\/([^/]+))?\\/ws$/u.exec(pathname);\n if (!match) return undefined;\n try {\n const workspaceId = decodeURIComponent(match[1]);\n return match[2] === undefined\n ? { scope: 'workspace', workspaceId }\n : { scope: 'session', workspaceId, sessionId: decodeURIComponent(match[2]) };\n } catch {\n return undefined;\n }\n}\n\n/** The segment an API is mounted under, below this prefix. */\nexport const PO_API_ROUTE_PREFIX = '/api/plugins';\n/** Selects the hub API bundle that owns the named cockpit session. */\nexport const PO_HUB_API_SESSION_QUERY_PARAM = 'hubSession';\n\n/** Trusted caller headers written only by the cockpit-to-session proxy. */\nexport const PO_API_CALLER_LOCALITY_HEADER = 'x-po-api-caller-locality';\nexport const PO_API_CALLER_DEVICE_ID_HEADER = 'x-po-api-caller-device-id';\nexport const PO_API_CALLER_STEP_UP_HEADER = 'x-po-api-caller-step-up';\n/** Set only after destination verification, never accepted from an untrusted proxy. */\nexport const PO_API_CALLER_FEDERATION_HEADER = 'x-po-api-caller-federation';\nexport type { FederationCaller as PoFederationCaller } from '@agimon-ai/po-web-security/deviceProof';\nexport const PO_API_CALLER_HEADERS = [\n PO_API_CALLER_LOCALITY_HEADER,\n PO_API_CALLER_DEVICE_ID_HEADER,\n PO_API_CALLER_STEP_UP_HEADER,\n PO_API_CALLER_FEDERATION_HEADER,\n] as const;\n\nexport type PoApiCallerStepUp = 'not-required' | 'verified' | 'unavailable';\nexport type PoApiCaller = (\n | { locality: 'local'; stepUp: 'not-required' }\n | { locality: 'remote'; deviceId: string; stepUp: PoApiCallerStepUp }\n) &\n Partial<import('@agimon-ai/po-web-security/deviceProof').FederationCaller>;\n\nfunction federationCallerFrom(\n headers: Headers,\n): import('@agimon-ai/po-web-security/deviceProof').FederationCaller | undefined {\n const raw = headers.get(PO_API_CALLER_FEDERATION_HEADER);\n if (raw === null) return undefined;\n try {\n const value = JSON.parse(raw) as import('@agimon-ai/po-web-security/deviceProof').FederationCaller;\n if (\n typeof value.principal?.issuer !== 'string' ||\n !value.principal.issuer ||\n typeof value.principal.subject !== 'string' ||\n !value.principal.subject ||\n typeof value.org !== 'string' ||\n !value.org ||\n typeof value.device?.keyId !== 'string' ||\n !value.device.keyId ||\n (value.via !== undefined &&\n (typeof value.via.callingHostId !== 'string' ||\n !value.via.callingHostId ||\n typeof value.via.grantId !== 'string' ||\n !value.via.grantId))\n )\n return undefined;\n return {\n principal: { issuer: value.principal.issuer, subject: value.principal.subject },\n org: value.org,\n device: { keyId: value.device.keyId },\n ...(value.via ? { via: { ...value.via } } : {}),\n };\n } catch {\n return undefined;\n }\n}\n\n/** Reads the proxy-authenticated caller identity, rejecting partial or contradictory stamps. */\nexport function poApiCallerFrom(headers: Headers): PoApiCaller | undefined {\n const federation = federationCallerFrom(headers);\n if (headers.has(PO_API_CALLER_FEDERATION_HEADER) && !federation) return undefined;\n const locality = headers.get(PO_API_CALLER_LOCALITY_HEADER);\n const deviceId = headers.get(PO_API_CALLER_DEVICE_ID_HEADER);\n const stepUp = headers.get(PO_API_CALLER_STEP_UP_HEADER);\n if (locality === 'local') {\n return deviceId === null && stepUp === 'not-required' && !federation ? { locality, stepUp } : undefined;\n }\n if (\n locality === 'remote' &&\n deviceId !== null &&\n deviceId !== '' &&\n (stepUp === 'not-required' || stepUp === 'verified' || stepUp === 'unavailable')\n ) {\n return { locality, deviceId, stepUp, ...federation };\n }\n return undefined;\n}\n/**\n * A browser-reachable OAuth redirect the host serves on its own listener.\n *\n * A package brokering third-party OAuth cannot use a loopback redirect when the\n * operator's browser is on another machine: `127.0.0.1` resolves to whichever\n * machine the browser runs on. The host owns a listener that is reachable from\n * wherever the cockpit is being used, so it lends that instead of the package\n * opening a port of its own.\n */\nexport interface PoOAuthRedirect {\n /** Absolute URL to register as the redirect target, already origin-correct. */\n readonly redirectUri: string;\n /**\n * Accept redirects carrying this state. Must complete before the\n * authorization URL is surfaced, or the redirect races its own reservation.\n */\n reserve(state: string, timeoutMs?: number): Promise<void>;\n /** Resolves once the redirect carrying this state arrives. */\n wait(state: string, timeoutMs?: number): Promise<{ code: string; state: string }>;\n /** Drops a reservation whose flow ended without a redirect. */\n cancel(state: string): void;\n}\n\n/** An exact, host-owned receipt address. Provider IDs are not event cursor positions. */\nexport interface PoRequestReceiptKey {\n namespace: string;\n activationId: string;\n requestId: string;\n}\n\nexport interface PoRequestReceipt extends PoRequestReceiptKey {\n fingerprint: string;\n destination: string;\n transactionId: string;\n outcome: 'reserved' | 'admitted' | 'rejected' | 'uncertain';\n}\n\nexport interface PoRequestReceiptReservation extends PoRequestReceiptKey {\n /** Lowercase SHA-256 of the authoritative request text, never the full transcript. */\n fingerprint: string;\n destination: string;\n transactionId: string;\n}\n\nexport type PoRequestReceiptReserveResult =\n | { kind: 'reserved'; token: string }\n | { kind: 'existing'; receipt: PoRequestReceipt }\n | { kind: 'conflict'; receipt: PoRequestReceipt };\n\n/** A global-only receipt capability. A reservation is not an execution receipt. */\nexport interface PoRequestReceipts {\n reserve(request: PoRequestReceiptReservation): Promise<PoRequestReceiptReserveResult>;\n lookup(key: PoRequestReceiptKey): Promise<PoRequestReceipt | undefined>;\n finish(\n request: PoRequestReceiptKey & { token: string; outcome: 'admitted' | 'rejected' | 'uncertain' },\n ): Promise<PoRequestReceipt>;\n}\n\n/** Permit returned for expensive local work. Calling it releases the slot. */\nexport interface PoHostExecutionPermit {\n (): void;\n attachProcess?(pid: number): Promise<void>;\n}\n\n/** Host-wide admission for expensive local work. Release only after the owned process exits. */\nexport interface PoHostExecutionBudget {\n acquire(signal?: AbortSignal): Promise<PoHostExecutionPermit>;\n getSnapshot(): Readonly<{ running: number; queued: number; limit: number }>;\n}\n\n/** Synchronous host-owned exclusion across media APIs mounted at different scopes. */\nexport interface PoHostMediaArbitration {\n register(busy: () => boolean): () => void;\n available(busy: () => boolean): boolean;\n}\n\n/** One host's paired agent registrations, shared across its global and session mounts. */\nexport interface PoPeerAgentRegistry {\n register(sessionId: string, agent: { fetch(request: Request): Promise<Response> }, hubToken: string): () => void;\n get(sessionId: string): { fetch(request: Request): Promise<Response>; hubToken: string } | undefined;\n}\n\n/** One agent that edits files: a hub session or an in-process child run. */\nexport interface PoEditPresenceAgent {\n /** `session:<id>` or `run:<runId>`. Unique across the host. */\n readonly key: string;\n /** Read when a note is rendered, so a name registered after the edit still shows. */\n readonly label: () => string;\n /** Hub session id other agents can reach with intercom. */\n readonly contact: string;\n readonly isLive: () => boolean;\n}\n\nexport interface PoEditPresenceEntry {\n readonly agent: PoEditPresenceAgent;\n /** `@file` tag of the bytes this agent's last successful edit or write left behind. */\n readonly tag: string;\n readonly at: number;\n}\n\n/**\n * Host-wide record of the last edit each live agent made to a file, keyed by realpath then agent key.\n * Owners delete their entries when they dispose, so no callback outlives its session or run.\n */\nexport type PoEditPresenceLedger = Map<string, Map<string, PoEditPresenceEntry>>;\n\n/** Why shareFile rejects while the host has no public tunnel origin. */\nexport const PO_SHARE_FILE_REMOTE_OFF_MESSAGE =\n 'Remote access is off, so Po cannot share files with remote MCP servers. Turn it on and retry.';\n\n/**\n * A file published at a short-lived public tunnel URL: 10 minutes, 3 downloads, and gone\n * when the tunnel revision changes. Call revoke as soon as the consumer is done with it.\n */\nexport interface PoSharedFile {\n /** Bearer link; never log or persist it. */\n url: string;\n fileName: string;\n mimeType: string;\n size: number;\n /** Expiry as epoch milliseconds. */\n expiresAt: number;\n maxDownloads: number;\n /** Idempotent. */\n revoke(): void;\n}\n\n/** Server-only admission for a registered file. Never construct this from browser input.\n * The opener must revalidate ownership, containment, symlinks and registered identity\n * on every call. Core pins identity again and bounds streaming to the admitted size.\n */\nexport interface PoOpenedRegisteredFile {\n identity: string;\n size: number;\n /** Single-use bounded stream. Ending or cancelling closes the opened resource. */\n stream(): ReadableStream<Uint8Array>;\n close(): Promise<void>;\n}\n\nexport interface PoRegisteredFile {\n fileName: string;\n open(): Promise<PoOpenedRegisteredFile>;\n}\n\n/** What the host tells an API about itself when it starts. */\nexport interface PoApiContext {\n scope: PoApiScope;\n /** Stable, opaque workspace id, shared by every session and member checkout of the workspace. */\n workspaceId?: string;\n /**\n * The admitted workspace root for a workspace mount. For a session mount it is the checkout\n * the session runs in; read sessionContext for both roots. Absent from global mounts.\n */\n workspaceRoot?: string;\n /** The session's baseline context; present on session mounts whose host knows its workspace. */\n sessionContext?: PoSessionContext;\n /** Explicit host home, never selected by a remote request. */\n homeDirectory?: string;\n /** The session this host serves; absent for a hub-scoped API. */\n sessionId?: string;\n /** The session's working directory; absent for a hub-scoped API. */\n cwd?: string;\n /** Session environment admitted by the server, never supplied by remote clients. */\n environment?: Readonly<Record<string, string | undefined>>;\n /** Shared only with child processes in this session, never with remote API clients. */\n internalToken?: string;\n /** Shared only with the cockpit hub for privileged cross-session coordination. */\n hubToken?: string;\n /**\n * Resolves a hub-issued opaque repository id to an admitted canonical root.\n * Hub APIs must never accept a browser-supplied filesystem path instead.\n */\n resolveRepository?(repositoryId: string): string | undefined;\n /** Reads the admitted repository's sync projection without exposing its state path. */\n readRepositorySync?(\n repositoryId: string,\n ): Promise<PoRepositorySyncView | undefined> | PoRepositorySyncView | undefined;\n /** Admitted workspaces available to this mount. */\n repositories?(): readonly { id: string; name: string; path: string; active: boolean }[];\n /** Publish a successful config write to the owning mount's refresh lifecycle. */\n configurationChanged?(): void;\n /**\n * Borrows the hub's OAuth redirect surface. Undefined when the hub cannot\n * currently serve one, in which case the package keeps its own behaviour.\n */\n oauthRedirect?(): PoOAuthRedirect | undefined;\n /** Canonical in-process lifecycle for sessions created by this host. */\n sessionService?: PoHubSessionService;\n /** Trusted in-process storage capability. APIs must retain their mount's session authorization. */\n sessionStorage?: PoSessionStorageService;\n /** Host-bound, authenticated inter-session event endpoint for this session. */\n sessionCommunication?: PoSessionCommunicationEndpoint;\n /** Same-process events shared by session APIs and hub channels. */\n directEvents?: PoDirectEventBus;\n /**\n * Publishes this session's activity line to every page's rail; undefined clears it.\n * Session mounts only. The last publisher wins.\n */\n publishActivity?(activity: PoSessionActivity | undefined): void;\n /** Trusted in-process dispatch to an explicitly selected mounted API. No browser authentication is performed here. */\n requestApi?(mount: PoApiMount, basePath: string, request: Request): Promise<Response>;\n /** Present only on a session mounted by an authenticated Desktop host. */\n computerUse?: PoComputerUseSessionAccess;\n /** Desktop's macOS privacy permissions; present on hub mounts only when Desktop hosts this server. */\n desktopPermissions?: {\n /** A `change` asks macOS for a permission and needs the step-up computer-use activation needs. */\n authorize(headers: Headers, change: boolean): boolean;\n /** The permission states, after asking macOS for `permission` when it is set. */\n request(permission?: string): Promise<unknown>;\n };\n /** Deliver a typed method to the channel serving a session this mount reaches, retaining the caller's connection identity. */\n receiveChannel?(sessionId: string, frameType: string, payload: unknown, connectionId: string): boolean;\n /**\n * Tells connected clients this mount's `name` data changed, so they fetch it again instead of polling.\n * Scoped by the mount: `global:<name>` on the global mount, `workspace:<id>:<name>` on a workspace mount.\n */\n bumpRevision?(name: string): void;\n /** Sets a live session's undecided approval count on its rail card; 0 clears it. Global mount only. */\n setSessionPendingApprovals?(sessionId: string, count: number): void;\n /** Host-owned replay evidence, supplied only on the global mount. Never exposed to browsers or agents. */\n requestReceipts?: PoRequestReceipts;\n /** One host's exclusive media activity, shared across global and session scopes. */\n mediaArbitration?: PoHostMediaArbitration;\n /** One host-wide heavy-job budget, not a separate budget for every session. */\n executionBudget?: PoHostExecutionBudget;\n /** One host-wide edit presence ledger, shared by every session mount. */\n editPresence?: PoEditPresenceLedger;\n /** Paired agents belonging to this host only. */\n peerAgents?: PoPeerAgentRegistry;\n /** Machine-owned Remote Control service, mounted only by Po's own global server plugin. */\n remoteControl?: { fetch(request: Request): Promise<Response> };\n /**\n * Publishes a file from this session's cwd or attachments at a short-lived tunnel URL.\n * Session mounts only; rejects with PO_SHARE_FILE_REMOTE_OFF_MESSAGE while remote access is off.\n * The host asks no consent: the caller must obtain it before passing the URL on.\n */\n shareFile?(path: string, label: string): Promise<PoSharedFile>;\n /** Trusted in-process registered-file admission, separate from ordinary capped shareFile. */\n shareRegisteredFile?(file: PoRegisteredFile): Promise<PoSharedFile>;\n /**\n * This server's loopback origin, such as `http://127.0.0.1:4242`. Throws until the listener is up,\n * so read it when serving a request, not at start.\n */\n serverOrigin?(): string;\n onNotice(message: string): void;\n}\n\n/** One accepted WebSocket on a public route. Text frames arrive as strings, binary frames as bytes. */\nexport interface PoApiSocket {\n readonly isOpen: boolean;\n send(data: string | Uint8Array): void;\n close(code?: number, reason?: string): void;\n onMessage(listener: (data: string | Uint8Array) => void): void;\n onClose(listener: (code: number, reason: string) => void): void;\n}\n\n/**\n * Routes the host serves without its own token, for a client that holds a package-issued token instead.\n * Global scope only. The host calls `authenticate` before `fetch` or `upgrade` and answers 401 when it\n * returns false, so a handler is never reached unauthenticated. Requests must also come from a loopback\n * peer and Host, carry no Origin or a `chrome-extension://` one, and never arrive through the tunnel.\n */\nexport interface PoApiPublicRoutes {\n /** Path prefixes below the base path, such as `/extension`; each starts with `/`. */\n readonly paths: readonly string[];\n authenticate(request: Request): boolean | Promise<boolean>;\n}\n\n/**\n * One running surface. The host strips the mount prefix before calling, so\n * routes are declared relative ('/runners/:id/log') and a package never\n * repeats where it was mounted.\n */\nexport interface PoApiHandler {\n fetch(request: Request): Response | Promise<Response>;\n /** Accepts a WebSocket upgrade on a public route; the host has already authenticated it. */\n upgrade?(request: Request, socket: PoApiSocket): void;\n close(): void;\n}\n\nexport interface PoApi {\n /** Segment under /api/plugins/; globally unique across loaded packages. */\n basePath: string;\n /** Routes served without the host token; accepted for global-scope packages only. */\n publicRoutes?: PoApiPublicRoutes;\n start(context: PoApiContext): PoApiHandler;\n}\n"],"mappings":";AAgCA,MAAa,gBAAuC;CAAC;CAAU;CAAa;AAAS;;AA6CrF,SAAgB,eAAe,OAAsD;CACnF,IAAI,MAAM,UAAU,UAAU,OAAO;CACrC,IAAI,MAAM,UAAU,aAAa,OAAO,mBAAmB,mBAAmB,MAAM,WAAW,EAAE;CACjG,IAAI,CAAC,MAAM,aAAa,MAAM,IAAI,MAAM,qDAAqD;CAC7F,OAAO,mBAAmB,mBAAmB,MAAM,WAAW,EAAE,YAAY,mBAAmB,MAAM,SAAS,EAAE;AAClH;;AASA,SAAgB,kBAAkB,UAA6C;CAC7E,IAAI,aAAa,WAAW,OAAO,EAAE,OAAO,SAAS;CACrD,MAAM,QAAQ,4DAA4D,KAAK,QAAQ;CACvF,IAAI,CAAC,OAAO,OAAO,KAAA;CACnB,IAAI;EACF,MAAM,cAAc,mBAAmB,MAAM,EAAE;EAC/C,OAAO,MAAM,OAAO,KAAA,IAChB;GAAE,OAAO;GAAa;EAAY,IAClC;GAAE,OAAO;GAAW;GAAa,WAAW,mBAAmB,MAAM,EAAE;EAAE;CAC/E,QAAQ;EACN;CACF;AACF;;AAGA,MAAa,sBAAsB;;AAEnC,MAAa,iCAAiC;;AAG9C,MAAa,gCAAgC;AAC7C,MAAa,iCAAiC;AAC9C,MAAa,+BAA+B;;AAE5C,MAAa,kCAAkC;AAE/C,MAAa,wBAAwB;CACnC;CACA;CACA;CACA;AACF;AASA,SAAS,qBACP,SAC+E;CAC/E,MAAM,MAAM,QAAQ,IAAI,+BAA+B;CACvD,IAAI,QAAQ,MAAM,OAAO,KAAA;CACzB,IAAI;EACF,MAAM,QAAQ,KAAK,MAAM,GAAG;EAC5B,IACE,OAAO,MAAM,WAAW,WAAW,YACnC,CAAC,MAAM,UAAU,UACjB,OAAO,MAAM,UAAU,YAAY,YACnC,CAAC,MAAM,UAAU,WACjB,OAAO,MAAM,QAAQ,YACrB,CAAC,MAAM,OACP,OAAO,MAAM,QAAQ,UAAU,YAC/B,CAAC,MAAM,OAAO,SACb,MAAM,QAAQ,KAAA,MACZ,OAAO,MAAM,IAAI,kBAAkB,YAClC,CAAC,MAAM,IAAI,iBACX,OAAO,MAAM,IAAI,YAAY,YAC7B,CAAC,MAAM,IAAI,UAEf,OAAO,KAAA;EACT,OAAO;GACL,WAAW;IAAE,QAAQ,MAAM,UAAU;IAAQ,SAAS,MAAM,UAAU;GAAQ;GAC9E,KAAK,MAAM;GACX,QAAQ,EAAE,OAAO,MAAM,OAAO,MAAM;GACpC,GAAI,MAAM,MAAM,EAAE,KAAK,EAAE,GAAG,MAAM,IAAI,EAAE,IAAI,CAAC;EAC/C;CACF,QAAQ;EACN;CACF;AACF;;AAGA,SAAgB,gBAAgB,SAA2C;CACzE,MAAM,aAAa,qBAAqB,OAAO;CAC/C,IAAI,QAAQ,IAAA,4BAAmC,KAAK,CAAC,YAAY,OAAO,KAAA;CACxE,MAAM,WAAW,QAAQ,IAAI,6BAA6B;CAC1D,MAAM,WAAW,QAAQ,IAAI,8BAA8B;CAC3D,MAAM,SAAS,QAAQ,IAAI,4BAA4B;CACvD,IAAI,aAAa,SACf,OAAO,aAAa,QAAQ,WAAW,kBAAkB,CAAC,aAAa;EAAE;EAAU;CAAO,IAAI,KAAA;CAEhG,IACE,aAAa,YACb,aAAa,QACb,aAAa,OACZ,WAAW,kBAAkB,WAAW,cAAc,WAAW,gBAElE,OAAO;EAAE;EAAU;EAAU;EAAQ,GAAG;CAAW;AAGvD;;AA4GA,MAAa,mCACX"}
@@ -81,8 +81,19 @@ async function openPinned(filePath) {
81
81
  const stat = await handle.stat();
82
82
  const named = await node_fs_promises.default.lstat(filePath);
83
83
  if (stat.isFile() && named.dev === stat.dev && named.ino === stat.ino && await node_fs_promises.default.realpath(filePath) === filePath) return {
84
- handle,
85
- stat
84
+ identity: [
85
+ stat.dev,
86
+ stat.ino,
87
+ stat.size,
88
+ stat.mtimeMs,
89
+ stat.ctimeMs
90
+ ].join(":"),
91
+ size: stat.size,
92
+ close: () => handle.close(),
93
+ stream: () => node_stream.Readable.toWeb(handle.createReadStream({
94
+ start: 0,
95
+ end: stat.size - 1
96
+ }))
86
97
  };
87
98
  } catch {}
88
99
  await handle.close();
@@ -92,50 +103,57 @@ function createPublicFileShares(options) {
92
103
  const shares = /* @__PURE__ */ new Map();
93
104
  const authorized = (share) => !share.revoked && Date.now() < share.expiresAt && share.revision === options.publicOriginRevision() && options.publicOrigin() !== void 0;
94
105
  const live = (share) => authorized(share) && share.downloadsLeft > 0;
106
+ const mint = async (request, registered) => {
107
+ const origin = options.publicOrigin();
108
+ const revision = options.publicOriginRevision();
109
+ if (origin === void 0) throw new Error(require_packageApi.PO_SHARE_FILE_REMOTE_OFF_MESSAGE);
110
+ const filePath = registered === void 0 ? await resolveShareable(request, environment) : "";
111
+ const opened = registered === void 0 ? await openPinned(filePath) : await registered.open();
112
+ if (opened === void 0) throw new Error(`"${request.path}" is not a regular file Po can share.`);
113
+ const { size, identity } = opened;
114
+ await opened.close();
115
+ if (registered === void 0 && size > 26214400) throw new Error(`"${request.path}" is larger than the 25 MB file sharing limit.`);
116
+ for (const [key, share] of shares) if (!live(share)) shares.delete(key);
117
+ if ([...shares.values()].filter((share) => share.sessionId === request.sessionId).length >= MAX_LIVE_SHARES_PER_SESSION) throw new Error(`This session already shares ${String(MAX_LIVE_SHARES_PER_SESSION)} files. Retry after a tool call finishes.`);
118
+ const token = (0, node_crypto.randomBytes)(TOKEN_BYTES).toString("base64url");
119
+ const key = tokenDigest(token);
120
+ const fileName = registered?.fileName ?? node_path.default.basename(filePath);
121
+ if (typeof identity !== "string" || !identity || !Number.isSafeInteger(size) || size < 0 || fileName.split("").some((character) => character === "/" || character === "\\" || character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127) || !fileName) throw new Error("Invalid registered file.");
122
+ const share = {
123
+ sessionId: request.sessionId,
124
+ filePath,
125
+ fileName,
126
+ ...registered === void 0 ? {} : { registered },
127
+ identity,
128
+ size,
129
+ revision,
130
+ expiresAt: Date.now() + SHARE_TTL_MS,
131
+ downloadsLeft: SHARE_DOWNLOADS,
132
+ revoked: false
133
+ };
134
+ shares.set(key, share);
135
+ options.onNotice(`file share: published "${fileName}" for ${request.label} on the remote-access tunnel.`);
136
+ return {
137
+ url: `${origin}${PUBLIC_FILE_SHARE_PREFIX}${token}`,
138
+ fileName,
139
+ mimeType: require_sessionFiles.SESSION_FILE_CONTENT_TYPES[node_path.default.extname(fileName).toLowerCase()] ?? "application/octet-stream",
140
+ size,
141
+ expiresAt: share.expiresAt,
142
+ maxDownloads: SHARE_DOWNLOADS,
143
+ revoke: () => {
144
+ share.revoked = true;
145
+ shares.delete(key);
146
+ }
147
+ };
148
+ };
95
149
  return {
96
- async mint(request) {
97
- const origin = options.publicOrigin();
98
- const revision = options.publicOriginRevision();
99
- if (origin === void 0) throw new Error(require_packageApi.PO_SHARE_FILE_REMOTE_OFF_MESSAGE);
100
- const filePath = await resolveShareable(request, environment);
101
- const opened = await openPinned(filePath);
102
- if (opened === void 0) throw new Error(`"${request.path}" is not a regular file Po can share.`);
103
- const { stat } = opened;
104
- await opened.handle.close();
105
- if (stat.size > 26214400) throw new Error(`"${request.path}" is larger than the 25 MB file sharing limit.`);
106
- for (const [key, share] of shares) if (!live(share)) shares.delete(key);
107
- if ([...shares.values()].filter((share) => share.sessionId === request.sessionId).length >= MAX_LIVE_SHARES_PER_SESSION) throw new Error(`This session already shares ${String(MAX_LIVE_SHARES_PER_SESSION)} files. Retry after a tool call finishes.`);
108
- const token = (0, node_crypto.randomBytes)(TOKEN_BYTES).toString("base64url");
109
- const key = tokenDigest(token);
110
- const fileName = node_path.default.basename(filePath);
111
- const share = {
112
- sessionId: request.sessionId,
113
- filePath,
114
- fileName,
115
- dev: stat.dev,
116
- ino: stat.ino,
117
- size: stat.size,
118
- mtimeMs: stat.mtimeMs,
119
- revision,
120
- expiresAt: Date.now() + SHARE_TTL_MS,
121
- downloadsLeft: SHARE_DOWNLOADS,
122
- revoked: false
123
- };
124
- shares.set(key, share);
125
- options.onNotice(`file share: published "${fileName}" for ${request.label} on the remote-access tunnel.`);
126
- return {
127
- url: `${origin}${PUBLIC_FILE_SHARE_PREFIX}${token}`,
128
- fileName,
129
- mimeType: require_sessionFiles.SESSION_FILE_CONTENT_TYPES[node_path.default.extname(fileName).toLowerCase()] ?? "application/octet-stream",
130
- size: stat.size,
131
- expiresAt: share.expiresAt,
132
- maxDownloads: SHARE_DOWNLOADS,
133
- revoke: () => {
134
- share.revoked = true;
135
- shares.delete(key);
136
- }
137
- };
138
- },
150
+ mint: (request) => mint(request),
151
+ mintRegistered: (sessionId, registered) => mint({
152
+ sessionId,
153
+ cwd: "",
154
+ path: "",
155
+ label: "explicit download"
156
+ }, registered),
139
157
  async serve(token, method) {
140
158
  if (method !== "GET" && method !== "HEAD" || !TOKEN_PATTERN.test(token)) return notFound();
141
159
  const key = tokenDigest(token);
@@ -149,15 +167,15 @@ function createPublicFileShares(options) {
149
167
  share.downloadsLeft -= 1;
150
168
  if (share.downloadsLeft === 0) shares.delete(key);
151
169
  }
152
- const opened = await openPinned(share.filePath);
153
- if (opened === void 0 || opened.stat.dev !== share.dev || opened.stat.ino !== share.ino || opened.stat.size !== share.size || opened.stat.mtimeMs !== share.mtimeMs) {
154
- await opened?.handle.close();
170
+ const opened = await (share.registered === void 0 ? openPinned(share.filePath) : share.registered.open()).catch(() => void 0);
171
+ if (opened === void 0 || opened.identity !== share.identity || opened.size !== share.size) {
172
+ await opened?.close();
155
173
  shares.delete(key);
156
174
  options.onNotice(`file share: refused "${share.fileName}" because the file changed after it was shared.`);
157
175
  return notFound();
158
176
  }
159
177
  if (!authorized(share)) {
160
- await opened.handle.close();
178
+ await opened.close();
161
179
  shares.delete(key);
162
180
  return notFound();
163
181
  }
@@ -174,14 +192,15 @@ function createPublicFileShares(options) {
174
192
  "x-robots-tag": "noindex"
175
193
  };
176
194
  if (method === "HEAD" || share.size === 0) {
177
- await opened.handle.close();
195
+ await opened.close();
178
196
  return new Response(method === "HEAD" ? null : /* @__PURE__ */ new Uint8Array(), { headers });
179
197
  }
180
- const body = node_stream.Readable.toWeb(opened.handle.createReadStream({
181
- start: 0,
182
- end: share.size - 1
183
- }));
184
- return new Response(body, { headers });
198
+ try {
199
+ return new Response(opened.stream(), { headers });
200
+ } catch {
201
+ await opened.close();
202
+ return notFound();
203
+ }
185
204
  }
186
205
  };
187
206
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.cjs","names":["fs","createHash","path","sessionAttachmentRoot","PO_SHARE_FILE_REMOTE_OFF_MESSAGE","randomBytes","SESSION_FILE_CONTENT_TYPES","Readable"],"sources":["../../../../src/services/publicFileShares/index.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport type { Stats } from 'node:fs';\nimport fs, { type FileHandle } from 'node:fs/promises';\nimport path from 'node:path';\nimport { Readable } from 'node:stream';\n\nimport {\n MAX_SESSION_FILE_BYTES,\n SESSION_FILE_CONTENT_TYPES,\n SESSION_FILE_DEFAULT_CONTENT_TYPE,\n} from '../../constants/sessionFiles';\nimport { PO_SHARE_FILE_REMOTE_OFF_MESSAGE, type PoSharedFile } from '../../schemas/packageApi';\nimport { sessionAttachmentRoot } from '../sessionAttachments';\n\n/**\n * Short-lived public links to one session file, so a remote MCP server can fetch\n * what the model was asked to hand it.\n *\n * The link is the only credential, so only its hash is kept, and every way a\n * link can fail answers the same 404. A file is pinned by identity when the\n * link is minted and reopened when it is served: a path that now names another\n * file, or reaches it through a new symlink, is refused rather than followed.\n */\n\n/** Public tunnel path prefix; the token is the only segment after it. */\nexport const PUBLIC_FILE_SHARE_PREFIX = '/mcp-file/';\n\nconst TOKEN_BYTES = 32;\nconst TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/u;\nconst SHARE_TTL_MS = 10 * 60 * 1000;\nconst SHARE_DOWNLOADS = 3;\nconst MAX_LIVE_SHARES_PER_SESSION = 16;\nconst OPEN_FLAGS = fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW;\n\nexport interface PublicFileShareRequest {\n sessionId: string;\n cwd: string;\n /** Relative to cwd, or absolute. */\n path: string;\n /** Who the file is for, for the host's notice. */\n label: string;\n}\n\nexport interface PublicFileShares {\n mint(request: PublicFileShareRequest): Promise<PoSharedFile>;\n serve(token: string, method: string): Promise<Response>;\n}\n\nexport interface PublicFileSharesOptions {\n publicOrigin(): string | undefined;\n /** Bumped whenever the tunnel goes away; a link minted under another revision is dead. */\n publicOriginRevision(): number;\n /** Never receives a token or a link. */\n onNotice(message: string): void;\n /** Locates the attachment root; defaults to the process environment, as the upload route does. */\n environment?: Readonly<Record<string, string | undefined>>;\n}\n\ninterface Share {\n sessionId: string;\n filePath: string;\n fileName: string;\n dev: number;\n ino: number;\n size: number;\n mtimeMs: number;\n revision: number;\n expiresAt: number;\n downloadsLeft: number;\n revoked: boolean;\n}\n\nfunction tokenDigest(token: string): string {\n return createHash('sha256').update(token).digest('base64url');\n}\n\nfunction notFound(): Response {\n return Response.json({ error: 'Not found.' }, { status: 404 });\n}\n\n/** RFC 8187 value for `filename*`: encodeURIComponent leaves a few characters the grammar forbids. */\nfunction contentDispositionName(fileName: string): string {\n return encodeURIComponent(fileName).replace(\n /['()*]/gu,\n (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n}\n\n/** True when filePath sits strictly below the real root with no segment that starts with a dot. */\nasync function containedIn(root: string, filePath: string): Promise<boolean> {\n let realRoot: string;\n try {\n realRoot = await fs.realpath(root);\n } catch {\n return false;\n }\n const relative = path.relative(realRoot, filePath);\n return (\n relative !== '' &&\n !path.isAbsolute(relative) &&\n relative.split(path.sep).every((segment) => !segment.startsWith('.'))\n );\n}\n\n/** The real path of a shareable file: inside the session cwd or its attachment root, never through a dot segment. */\nasync function resolveShareable(\n request: PublicFileShareRequest,\n environment: Readonly<Record<string, string | undefined>>,\n): Promise<string> {\n const refused = new Error(`\"${request.path}\" is not a file in this session's working directory or attachments.`);\n if (\n request.path === '' ||\n request.path.includes('\\0') ||\n request.path.split(/[\\\\/]/u).some((segment) => segment === '.' || segment === '..')\n )\n throw refused;\n let filePath: string;\n try {\n filePath = await fs.realpath(path.resolve(request.cwd, request.path));\n } catch {\n throw refused;\n }\n const roots = [request.cwd];\n try {\n roots.push(sessionAttachmentRoot(request.sessionId, environment));\n } catch {\n // An id that cannot hold attachments only has its working directory.\n }\n for (const root of roots) if (await containedIn(root, filePath)) return filePath;\n throw refused;\n}\n\n/**\n * Opens a regular file without following a final symlink, then proves the handle is\n * still the file at that real path: a parent swapped for a symlink changes the real\n * path, and a replaced file changes the identity. Resolves undefined, with nothing\n * left open, when either check fails.\n */\nasync function openPinned(filePath: string): Promise<{ handle: FileHandle; stat: Stats } | undefined> {\n let handle: FileHandle;\n try {\n handle = await fs.open(filePath, OPEN_FLAGS);\n } catch {\n return undefined;\n }\n try {\n const stat = await handle.stat();\n const named = await fs.lstat(filePath);\n if (stat.isFile() && named.dev === stat.dev && named.ino === stat.ino && (await fs.realpath(filePath)) === filePath)\n return { handle, stat };\n } catch {\n // Treated like a mismatch below.\n }\n await handle.close();\n return undefined;\n}\n\nexport function createPublicFileShares(options: PublicFileSharesOptions): PublicFileShares {\n const environment = options.environment ?? process.env;\n const shares = new Map<string, Share>();\n\n const authorized = (share: Share): boolean =>\n !share.revoked &&\n Date.now() < share.expiresAt &&\n share.revision === options.publicOriginRevision() &&\n options.publicOrigin() !== undefined;\n const live = (share: Share): boolean => authorized(share) && share.downloadsLeft > 0;\n\n return {\n async mint(request) {\n const origin = options.publicOrigin();\n const revision = options.publicOriginRevision();\n if (origin === undefined) throw new Error(PO_SHARE_FILE_REMOTE_OFF_MESSAGE);\n const filePath = await resolveShareable(request, environment);\n const opened = await openPinned(filePath);\n if (opened === undefined) throw new Error(`\"${request.path}\" is not a regular file Po can share.`);\n const { stat } = opened;\n await opened.handle.close();\n if (stat.size > MAX_SESSION_FILE_BYTES)\n throw new Error(`\"${request.path}\" is larger than the 25 MB file sharing limit.`);\n for (const [key, share] of shares) if (!live(share)) shares.delete(key);\n if (\n [...shares.values()].filter((share) => share.sessionId === request.sessionId).length >=\n MAX_LIVE_SHARES_PER_SESSION\n )\n throw new Error(\n `This session already shares ${String(MAX_LIVE_SHARES_PER_SESSION)} files. Retry after a tool call finishes.`,\n );\n const token = randomBytes(TOKEN_BYTES).toString('base64url');\n const key = tokenDigest(token);\n const fileName = path.basename(filePath);\n const share: Share = {\n sessionId: request.sessionId,\n filePath,\n fileName,\n dev: stat.dev,\n ino: stat.ino,\n size: stat.size,\n mtimeMs: stat.mtimeMs,\n revision,\n expiresAt: Date.now() + SHARE_TTL_MS,\n downloadsLeft: SHARE_DOWNLOADS,\n revoked: false,\n };\n shares.set(key, share);\n options.onNotice(`file share: published \"${fileName}\" for ${request.label} on the remote-access tunnel.`);\n return {\n url: `${origin}${PUBLIC_FILE_SHARE_PREFIX}${token}`,\n fileName,\n mimeType: SESSION_FILE_CONTENT_TYPES[path.extname(fileName).toLowerCase()] ?? SESSION_FILE_DEFAULT_CONTENT_TYPE,\n size: stat.size,\n expiresAt: share.expiresAt,\n maxDownloads: SHARE_DOWNLOADS,\n revoke: () => {\n share.revoked = true;\n shares.delete(key);\n },\n };\n },\n\n async serve(token, method) {\n if ((method !== 'GET' && method !== 'HEAD') || !TOKEN_PATTERN.test(token)) return notFound();\n const key = tokenDigest(token);\n const share = shares.get(key);\n if (share === undefined) return notFound();\n if (!live(share)) {\n shares.delete(key);\n return notFound();\n }\n // Counted before the first await, so concurrent downloads cannot outrun the limit.\n if (method === 'GET') {\n share.downloadsLeft -= 1;\n if (share.downloadsLeft === 0) shares.delete(key);\n }\n const opened = await openPinned(share.filePath);\n if (\n opened === undefined ||\n opened.stat.dev !== share.dev ||\n opened.stat.ino !== share.ino ||\n opened.stat.size !== share.size ||\n opened.stat.mtimeMs !== share.mtimeMs\n ) {\n await opened?.handle.close();\n shares.delete(key);\n options.onNotice(`file share: refused \"${share.fileName}\" because the file changed after it was shared.`);\n return notFound();\n }\n // Opening a file yields to revocation and tunnel changes. The download already\n // reserved its allowance, so this check must not reject the final permitted GET.\n if (!authorized(share)) {\n await opened.handle.close();\n shares.delete(key);\n return notFound();\n }\n const headers = {\n 'content-type': 'application/octet-stream',\n 'content-length': String(share.size),\n 'content-disposition': `attachment; filename*=UTF-8''${contentDispositionName(share.fileName)}`,\n 'x-content-type-options': 'nosniff',\n 'content-security-policy': \"sandbox; default-src 'none'\",\n 'cross-origin-resource-policy': 'same-origin',\n 'cache-control': 'no-store, private',\n 'referrer-policy': 'no-referrer',\n 'accept-ranges': 'none',\n 'x-robots-tag': 'noindex',\n };\n if (method === 'HEAD' || share.size === 0) {\n await opened.handle.close();\n return new Response(method === 'HEAD' ? null : new Uint8Array(), { headers });\n }\n // The stream closes the handle when it ends, fails, or is cancelled. Bounded to the\n // pinned size, so bytes appended after the identity check are never sent.\n const body = Readable.toWeb(opened.handle.createReadStream({ start: 0, end: share.size - 1 }));\n return new Response(body as ReadableStream<Uint8Array>, { headers });\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.cjs","names":["fs","createHash","path","sessionAttachmentRoot","Readable","PO_SHARE_FILE_REMOTE_OFF_MESSAGE","randomBytes","SESSION_FILE_CONTENT_TYPES"],"sources":["../../../../src/services/publicFileShares/index.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport fs, { type FileHandle } from 'node:fs/promises';\nimport path from 'node:path';\nimport { Readable } from 'node:stream';\n\nimport {\n MAX_SESSION_FILE_BYTES,\n SESSION_FILE_CONTENT_TYPES,\n SESSION_FILE_DEFAULT_CONTENT_TYPE,\n} from '../../constants/sessionFiles';\nimport {\n PO_SHARE_FILE_REMOTE_OFF_MESSAGE,\n type PoOpenedRegisteredFile,\n type PoRegisteredFile,\n type PoSharedFile,\n} from '../../schemas/packageApi';\nimport { sessionAttachmentRoot } from '../sessionAttachments';\n\n/**\n * Short-lived public links to one session file, so a remote MCP server can fetch\n * what the model was asked to hand it.\n *\n * The link is the only credential, so only its hash is kept, and every way a\n * link can fail answers the same 404. A file is pinned by identity when the\n * link is minted and reopened when it is served: a path that now names another\n * file, or reaches it through a new symlink, is refused rather than followed.\n */\n\n/** Public tunnel path prefix; the token is the only segment after it. */\nexport const PUBLIC_FILE_SHARE_PREFIX = '/mcp-file/';\n\nconst TOKEN_BYTES = 32;\nconst TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/u;\nconst SHARE_TTL_MS = 10 * 60 * 1000;\nconst SHARE_DOWNLOADS = 3;\nconst MAX_LIVE_SHARES_PER_SESSION = 16;\nconst OPEN_FLAGS = fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW;\n\nexport interface PublicFileShareRequest {\n sessionId: string;\n cwd: string;\n /** Relative to cwd, or absolute. */\n path: string;\n /** Who the file is for, for the host's notice. */\n label: string;\n}\n\nexport interface PublicFileShares {\n mint(request: PublicFileShareRequest): Promise<PoSharedFile>;\n mintRegistered(sessionId: string, file: PoRegisteredFile): Promise<PoSharedFile>;\n serve(token: string, method: string): Promise<Response>;\n}\n\nexport interface PublicFileSharesOptions {\n publicOrigin(): string | undefined;\n /** Bumped whenever the tunnel goes away; a link minted under another revision is dead. */\n publicOriginRevision(): number;\n /** Never receives a token or a link. */\n onNotice(message: string): void;\n /** Locates the attachment root; defaults to the process environment, as the upload route does. */\n environment?: Readonly<Record<string, string | undefined>>;\n}\n\ninterface Share {\n sessionId: string;\n filePath: string;\n registered?: PoRegisteredFile;\n fileName: string;\n identity: string;\n size: number;\n revision: number;\n expiresAt: number;\n downloadsLeft: number;\n revoked: boolean;\n}\n\nfunction tokenDigest(token: string): string {\n return createHash('sha256').update(token).digest('base64url');\n}\n\nfunction notFound(): Response {\n return Response.json({ error: 'Not found.' }, { status: 404 });\n}\n\n/** RFC 8187 value for `filename*`: encodeURIComponent leaves a few characters the grammar forbids. */\nfunction contentDispositionName(fileName: string): string {\n return encodeURIComponent(fileName).replace(\n /['()*]/gu,\n (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n}\n\n/** True when filePath sits strictly below the real root with no segment that starts with a dot. */\nasync function containedIn(root: string, filePath: string): Promise<boolean> {\n let realRoot: string;\n try {\n realRoot = await fs.realpath(root);\n } catch {\n return false;\n }\n const relative = path.relative(realRoot, filePath);\n return (\n relative !== '' &&\n !path.isAbsolute(relative) &&\n relative.split(path.sep).every((segment) => !segment.startsWith('.'))\n );\n}\n\n/** The real path of a shareable file: inside the session cwd or its attachment root, never through a dot segment. */\nasync function resolveShareable(\n request: PublicFileShareRequest,\n environment: Readonly<Record<string, string | undefined>>,\n): Promise<string> {\n const refused = new Error(`\"${request.path}\" is not a file in this session's working directory or attachments.`);\n if (\n request.path === '' ||\n request.path.includes('\\0') ||\n request.path.split(/[\\\\/]/u).some((segment) => segment === '.' || segment === '..')\n )\n throw refused;\n let filePath: string;\n try {\n filePath = await fs.realpath(path.resolve(request.cwd, request.path));\n } catch {\n throw refused;\n }\n const roots = [request.cwd];\n try {\n roots.push(sessionAttachmentRoot(request.sessionId, environment));\n } catch {\n // An id that cannot hold attachments only has its working directory.\n }\n for (const root of roots) if (await containedIn(root, filePath)) return filePath;\n throw refused;\n}\n\n/**\n * Opens a regular file without following a final symlink, then proves the handle is\n * still the file at that real path: a parent swapped for a symlink changes the real\n * path, and a replaced file changes the identity. Resolves undefined, with nothing\n * left open, when either check fails.\n */\nasync function openPinned(filePath: string): Promise<PoOpenedRegisteredFile | undefined> {\n let handle: FileHandle;\n try {\n handle = await fs.open(filePath, OPEN_FLAGS);\n } catch {\n return undefined;\n }\n try {\n const stat = await handle.stat();\n const named = await fs.lstat(filePath);\n if (stat.isFile() && named.dev === stat.dev && named.ino === stat.ino && (await fs.realpath(filePath)) === filePath)\n return {\n identity: [stat.dev, stat.ino, stat.size, stat.mtimeMs, stat.ctimeMs].join(':'),\n size: stat.size,\n close: () => handle.close(),\n stream: () =>\n Readable.toWeb(handle.createReadStream({ start: 0, end: stat.size - 1 })) as ReadableStream<Uint8Array>,\n };\n } catch {\n // Treated like a mismatch below.\n }\n await handle.close();\n return undefined;\n}\n\nexport function createPublicFileShares(options: PublicFileSharesOptions): PublicFileShares {\n const environment = options.environment ?? process.env;\n const shares = new Map<string, Share>();\n\n const authorized = (share: Share): boolean =>\n !share.revoked &&\n Date.now() < share.expiresAt &&\n share.revision === options.publicOriginRevision() &&\n options.publicOrigin() !== undefined;\n const live = (share: Share): boolean => authorized(share) && share.downloadsLeft > 0;\n\n const mint = async (request: PublicFileShareRequest, registered?: PoRegisteredFile): Promise<PoSharedFile> => {\n const origin = options.publicOrigin();\n const revision = options.publicOriginRevision();\n if (origin === undefined) throw new Error(PO_SHARE_FILE_REMOTE_OFF_MESSAGE);\n const filePath = registered === undefined ? await resolveShareable(request, environment) : '';\n const opened = registered === undefined ? await openPinned(filePath) : await registered.open();\n if (opened === undefined) throw new Error(`\"${request.path}\" is not a regular file Po can share.`);\n const { size, identity } = opened;\n await opened.close();\n if (registered === undefined && size > MAX_SESSION_FILE_BYTES)\n throw new Error(`\"${request.path}\" is larger than the 25 MB file sharing limit.`);\n for (const [key, share] of shares) if (!live(share)) shares.delete(key);\n if (\n [...shares.values()].filter((share) => share.sessionId === request.sessionId).length >=\n MAX_LIVE_SHARES_PER_SESSION\n )\n throw new Error(\n `This session already shares ${String(MAX_LIVE_SHARES_PER_SESSION)} files. Retry after a tool call finishes.`,\n );\n const token = randomBytes(TOKEN_BYTES).toString('base64url');\n const key = tokenDigest(token);\n const fileName = registered?.fileName ?? path.basename(filePath);\n if (\n typeof identity !== 'string' ||\n !identity ||\n !Number.isSafeInteger(size) ||\n size < 0 ||\n fileName\n .split('')\n .some(\n (character) =>\n character === '/' || character === '\\\\' || character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127,\n ) ||\n !fileName\n )\n throw new Error('Invalid registered file.');\n const share: Share = {\n sessionId: request.sessionId,\n filePath,\n fileName,\n ...(registered === undefined ? {} : { registered }),\n identity,\n size,\n revision,\n expiresAt: Date.now() + SHARE_TTL_MS,\n downloadsLeft: SHARE_DOWNLOADS,\n revoked: false,\n };\n shares.set(key, share);\n options.onNotice(`file share: published \"${fileName}\" for ${request.label} on the remote-access tunnel.`);\n return {\n url: `${origin}${PUBLIC_FILE_SHARE_PREFIX}${token}`,\n fileName,\n mimeType: SESSION_FILE_CONTENT_TYPES[path.extname(fileName).toLowerCase()] ?? SESSION_FILE_DEFAULT_CONTENT_TYPE,\n size,\n expiresAt: share.expiresAt,\n maxDownloads: SHARE_DOWNLOADS,\n revoke: () => {\n share.revoked = true;\n shares.delete(key);\n },\n };\n };\n\n return {\n mint: (request) => mint(request),\n mintRegistered: (sessionId, registered) =>\n mint({ sessionId, cwd: '', path: '', label: 'explicit download' }, registered),\n async serve(token, method) {\n if ((method !== 'GET' && method !== 'HEAD') || !TOKEN_PATTERN.test(token)) return notFound();\n const key = tokenDigest(token);\n const share = shares.get(key);\n if (share === undefined) return notFound();\n if (!live(share)) {\n shares.delete(key);\n return notFound();\n }\n // Counted before the first await, so concurrent downloads cannot outrun the limit.\n if (method === 'GET') {\n share.downloadsLeft -= 1;\n if (share.downloadsLeft === 0) shares.delete(key);\n }\n const opened = await (\n share.registered === undefined ? openPinned(share.filePath) : share.registered.open()\n ).catch(() => undefined);\n if (opened === undefined || opened.identity !== share.identity || opened.size !== share.size) {\n await opened?.close();\n shares.delete(key);\n options.onNotice(`file share: refused \"${share.fileName}\" because the file changed after it was shared.`);\n return notFound();\n }\n // Opening a file yields to revocation and tunnel changes. The download already\n // reserved its allowance, so this check must not reject the final permitted GET.\n if (!authorized(share)) {\n await opened.close();\n shares.delete(key);\n return notFound();\n }\n const headers = {\n 'content-type': 'application/octet-stream',\n 'content-length': String(share.size),\n 'content-disposition': `attachment; filename*=UTF-8''${contentDispositionName(share.fileName)}`,\n 'x-content-type-options': 'nosniff',\n 'content-security-policy': \"sandbox; default-src 'none'\",\n 'cross-origin-resource-policy': 'same-origin',\n 'cache-control': 'no-store, private',\n 'referrer-policy': 'no-referrer',\n 'accept-ranges': 'none',\n 'x-robots-tag': 'noindex',\n };\n if (method === 'HEAD' || share.size === 0) {\n await opened.close();\n return new Response(method === 'HEAD' ? null : new Uint8Array(), { headers });\n }\n // The stream closes the handle when it ends, fails, or is cancelled. Bounded to the\n // pinned size, so bytes appended after the identity check are never sent.\n try {\n return new Response(opened.stream(), { headers });\n } catch {\n await opened.close();\n return notFound();\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AA6BA,MAAa,2BAA2B;AAExC,MAAM,cAAc;AACpB,MAAM,gBAAgB;AACtB,MAAM,eAAe;AACrB,MAAM,kBAAkB;AACxB,MAAM,8BAA8B;AACpC,MAAM,aAAaA,iBAAAA,QAAG,UAAU,WAAWA,iBAAAA,QAAG,UAAU;AAwCxD,SAAS,YAAY,OAAuB;CAC1C,QAAA,GAAOC,YAAAA,WAAAA,CAAW,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,WAAW;AAC9D;AAEA,SAAS,WAAqB;CAC5B,OAAO,SAAS,KAAK,EAAE,OAAO,aAAa,GAAG,EAAE,QAAQ,IAAI,CAAC;AAC/D;;AAGA,SAAS,uBAAuB,UAA0B;CACxD,OAAO,mBAAmB,QAAQ,CAAC,CAAC,QAClC,aACC,cAAc,IAAI,UAAU,WAAW,CAAC,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,YAAY,GACtE;AACF;;AAGA,eAAe,YAAY,MAAc,UAAoC;CAC3E,IAAI;CACJ,IAAI;EACF,WAAW,MAAMD,iBAAAA,QAAG,SAAS,IAAI;CACnC,QAAQ;EACN,OAAO;CACT;CACA,MAAM,WAAWE,UAAAA,QAAK,SAAS,UAAU,QAAQ;CACjD,OACE,aAAa,MACb,CAACA,UAAAA,QAAK,WAAW,QAAQ,KACzB,SAAS,MAAMA,UAAAA,QAAK,GAAG,CAAC,CAAC,OAAO,YAAY,CAAC,QAAQ,WAAW,GAAG,CAAC;AAExE;;AAGA,eAAe,iBACb,SACA,aACiB;CACjB,MAAM,0BAAU,IAAI,MAAM,IAAI,QAAQ,KAAK,oEAAoE;CAC/G,IACE,QAAQ,SAAS,MACjB,QAAQ,KAAK,SAAS,IAAI,KAC1B,QAAQ,KAAK,MAAM,QAAQ,CAAC,CAAC,MAAM,YAAY,YAAY,OAAO,YAAY,IAAI,GAElF,MAAM;CACR,IAAI;CACJ,IAAI;EACF,WAAW,MAAMF,iBAAAA,QAAG,SAASE,UAAAA,QAAK,QAAQ,QAAQ,KAAK,QAAQ,IAAI,CAAC;CACtE,QAAQ;EACN,MAAM;CACR;CACA,MAAM,QAAQ,CAAC,QAAQ,GAAG;CAC1B,IAAI;EACF,MAAM,KAAKC,cAAAA,sBAAsB,QAAQ,WAAW,WAAW,CAAC;CAClE,QAAQ,CAER;CACA,KAAK,MAAM,QAAQ,OAAO,IAAI,MAAM,YAAY,MAAM,QAAQ,GAAG,OAAO;CACxE,MAAM;AACR;;;;;;;AAQA,eAAe,WAAW,UAA+D;CACvF,IAAI;CACJ,IAAI;EACF,SAAS,MAAMH,iBAAAA,QAAG,KAAK,UAAU,UAAU;CAC7C,QAAQ;EACN;CACF;CACA,IAAI;EACF,MAAM,OAAO,MAAM,OAAO,KAAK;EAC/B,MAAM,QAAQ,MAAMA,iBAAAA,QAAG,MAAM,QAAQ;EACrC,IAAI,KAAK,OAAO,KAAK,MAAM,QAAQ,KAAK,OAAO,MAAM,QAAQ,KAAK,OAAQ,MAAMA,iBAAAA,QAAG,SAAS,QAAQ,MAAO,UACzG,OAAO;GACL,UAAU;IAAC,KAAK;IAAK,KAAK;IAAK,KAAK;IAAM,KAAK;IAAS,KAAK;GAAO,CAAC,CAAC,KAAK,GAAG;GAC9E,MAAM,KAAK;GACX,aAAa,OAAO,MAAM;GAC1B,cACEI,YAAAA,SAAS,MAAM,OAAO,iBAAiB;IAAE,OAAO;IAAG,KAAK,KAAK,OAAO;GAAE,CAAC,CAAC;EAC5E;CACJ,QAAQ,CAER;CACA,MAAM,OAAO,MAAM;AAErB;AAEA,SAAgB,uBAAuB,SAAoD;CACzF,MAAM,cAAc,QAAQ,eAAe,QAAQ;CACnD,MAAM,yBAAS,IAAI,IAAmB;CAEtC,MAAM,cAAc,UAClB,CAAC,MAAM,WACP,KAAK,IAAI,IAAI,MAAM,aACnB,MAAM,aAAa,QAAQ,qBAAqB,KAChD,QAAQ,aAAa,MAAM,KAAA;CAC7B,MAAM,QAAQ,UAA0B,WAAW,KAAK,KAAK,MAAM,gBAAgB;CAEnF,MAAM,OAAO,OAAO,SAAiC,eAAyD;EAC5G,MAAM,SAAS,QAAQ,aAAa;EACpC,MAAM,WAAW,QAAQ,qBAAqB;EAC9C,IAAI,WAAW,KAAA,GAAW,MAAM,IAAI,MAAMC,mBAAAA,gCAAgC;EAC1E,MAAM,WAAW,eAAe,KAAA,IAAY,MAAM,iBAAiB,SAAS,WAAW,IAAI;EAC3F,MAAM,SAAS,eAAe,KAAA,IAAY,MAAM,WAAW,QAAQ,IAAI,MAAM,WAAW,KAAK;EAC7F,IAAI,WAAW,KAAA,GAAW,MAAM,IAAI,MAAM,IAAI,QAAQ,KAAK,sCAAsC;EACjG,MAAM,EAAE,MAAM,aAAa;EAC3B,MAAM,OAAO,MAAM;EACnB,IAAI,eAAe,KAAA,KAAa,OAAA,UAC9B,MAAM,IAAI,MAAM,IAAI,QAAQ,KAAK,+CAA+C;EAClF,KAAK,MAAM,CAAC,KAAK,UAAU,QAAQ,IAAI,CAAC,KAAK,KAAK,GAAG,OAAO,OAAO,GAAG;EACtE,IACE,CAAC,GAAG,OAAO,OAAO,CAAC,CAAC,CAAC,QAAQ,UAAU,MAAM,cAAc,QAAQ,SAAS,CAAC,CAAC,UAC9E,6BAEA,MAAM,IAAI,MACR,+BAA+B,OAAO,2BAA2B,EAAE,0CACrE;EACF,MAAM,SAAA,GAAQC,YAAAA,YAAAA,CAAY,WAAW,CAAC,CAAC,SAAS,WAAW;EAC3D,MAAM,MAAM,YAAY,KAAK;EAC7B,MAAM,WAAW,YAAY,YAAYJ,UAAAA,QAAK,SAAS,QAAQ;EAC/D,IACE,OAAO,aAAa,YACpB,CAAC,YACD,CAAC,OAAO,cAAc,IAAI,KAC1B,OAAO,KACP,SACG,MAAM,EAAE,CAAC,CACT,MACE,cACC,cAAc,OAAO,cAAc,QAAQ,UAAU,WAAW,CAAC,IAAI,MAAM,UAAU,WAAW,CAAC,MAAM,GAC3G,KACF,CAAC,UAED,MAAM,IAAI,MAAM,0BAA0B;EAC5C,MAAM,QAAe;GACnB,WAAW,QAAQ;GACnB;GACA;GACA,GAAI,eAAe,KAAA,IAAY,CAAC,IAAI,EAAE,WAAW;GACjD;GACA;GACA;GACA,WAAW,KAAK,IAAI,IAAI;GACxB,eAAe;GACf,SAAS;EACX;EACA,OAAO,IAAI,KAAK,KAAK;EACrB,QAAQ,SAAS,0BAA0B,SAAS,QAAQ,QAAQ,MAAM,8BAA8B;EACxG,OAAO;GACL,KAAK,GAAG,SAAS,2BAA2B;GAC5C;GACA,UAAUK,qBAAAA,2BAA2BL,UAAAA,QAAK,QAAQ,QAAQ,CAAC,CAAC,YAAY,MAAA;GACxE;GACA,WAAW,MAAM;GACjB,cAAc;GACd,cAAc;IACZ,MAAM,UAAU;IAChB,OAAO,OAAO,GAAG;GACnB;EACF;CACF;CAEA,OAAO;EACL,OAAO,YAAY,KAAK,OAAO;EAC/B,iBAAiB,WAAW,eAC1B,KAAK;GAAE;GAAW,KAAK;GAAI,MAAM;GAAI,OAAO;EAAoB,GAAG,UAAU;EAC/E,MAAM,MAAM,OAAO,QAAQ;GACzB,IAAK,WAAW,SAAS,WAAW,UAAW,CAAC,cAAc,KAAK,KAAK,GAAG,OAAO,SAAS;GAC3F,MAAM,MAAM,YAAY,KAAK;GAC7B,MAAM,QAAQ,OAAO,IAAI,GAAG;GAC5B,IAAI,UAAU,KAAA,GAAW,OAAO,SAAS;GACzC,IAAI,CAAC,KAAK,KAAK,GAAG;IAChB,OAAO,OAAO,GAAG;IACjB,OAAO,SAAS;GAClB;GAEA,IAAI,WAAW,OAAO;IACpB,MAAM,iBAAiB;IACvB,IAAI,MAAM,kBAAkB,GAAG,OAAO,OAAO,GAAG;GAClD;GACA,MAAM,SAAS,OACb,MAAM,eAAe,KAAA,IAAY,WAAW,MAAM,QAAQ,IAAI,MAAM,WAAW,KAAK,EAAA,CACpF,YAAY,KAAA,CAAS;GACvB,IAAI,WAAW,KAAA,KAAa,OAAO,aAAa,MAAM,YAAY,OAAO,SAAS,MAAM,MAAM;IAC5F,MAAM,QAAQ,MAAM;IACpB,OAAO,OAAO,GAAG;IACjB,QAAQ,SAAS,wBAAwB,MAAM,SAAS,gDAAgD;IACxG,OAAO,SAAS;GAClB;GAGA,IAAI,CAAC,WAAW,KAAK,GAAG;IACtB,MAAM,OAAO,MAAM;IACnB,OAAO,OAAO,GAAG;IACjB,OAAO,SAAS;GAClB;GACA,MAAM,UAAU;IACd,gBAAgB;IAChB,kBAAkB,OAAO,MAAM,IAAI;IACnC,uBAAuB,gCAAgC,uBAAuB,MAAM,QAAQ;IAC5F,0BAA0B;IAC1B,2BAA2B;IAC3B,gCAAgC;IAChC,iBAAiB;IACjB,mBAAmB;IACnB,iBAAiB;IACjB,gBAAgB;GAClB;GACA,IAAI,WAAW,UAAU,MAAM,SAAS,GAAG;IACzC,MAAM,OAAO,MAAM;IACnB,OAAO,IAAI,SAAS,WAAW,SAAS,uBAAO,IAAI,WAAW,GAAG,EAAE,QAAQ,CAAC;GAC9E;GAGA,IAAI;IACF,OAAO,IAAI,SAAS,OAAO,OAAO,GAAG,EAAE,QAAQ,CAAC;GAClD,QAAQ;IACN,MAAM,OAAO,MAAM;IACnB,OAAO,SAAS;GAClB;EACF;CACF;AACF"}
@@ -1,4 +1,4 @@
1
- import { PoSharedFile } from "../../schemas/packageApi.mjs";
1
+ import { PoRegisteredFile, PoSharedFile } from "../../schemas/packageApi.mjs";
2
2
  //#region src/services/publicFileShares/index.d.ts
3
3
  export interface PublicFileShareRequest {
4
4
  sessionId: string;
@@ -10,6 +10,7 @@ export interface PublicFileShareRequest {
10
10
  }
11
11
  export interface PublicFileShares {
12
12
  mint(request: PublicFileShareRequest): Promise<PoSharedFile>;
13
+ mintRegistered(sessionId: string, file: PoRegisteredFile): Promise<PoSharedFile>;
13
14
  serve(token: string, method: string): Promise<Response>;
14
15
  }
15
16
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/publicFileShares/index.ts"],"mappings":";;iBAkCiB;EACf;EACA;;EAEA;;EAEA;;iBAGe;EACf,KAAK,SAAS,yBAAyB,QAAQ;EAC/C,MAAM,eAAe,iBAAiB,QAAQ"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../../../src/services/publicFileShares/index.ts"],"mappings":";;iBAsCiB;EACf;EACA;;EAEA;;EAEA;;iBAGe;EACf,KAAK,SAAS,yBAAyB,QAAQ;EAC/C,eAAe,mBAAmB,MAAM,mBAAmB,QAAQ;EACnE,MAAM,eAAe,iBAAiB,QAAQ"}
@@ -78,8 +78,19 @@ async function openPinned(filePath) {
78
78
  const stat = await handle.stat();
79
79
  const named = await fs.lstat(filePath);
80
80
  if (stat.isFile() && named.dev === stat.dev && named.ino === stat.ino && await fs.realpath(filePath) === filePath) return {
81
- handle,
82
- stat
81
+ identity: [
82
+ stat.dev,
83
+ stat.ino,
84
+ stat.size,
85
+ stat.mtimeMs,
86
+ stat.ctimeMs
87
+ ].join(":"),
88
+ size: stat.size,
89
+ close: () => handle.close(),
90
+ stream: () => Readable.toWeb(handle.createReadStream({
91
+ start: 0,
92
+ end: stat.size - 1
93
+ }))
83
94
  };
84
95
  } catch {}
85
96
  await handle.close();
@@ -89,50 +100,57 @@ function createPublicFileShares(options) {
89
100
  const shares = /* @__PURE__ */ new Map();
90
101
  const authorized = (share) => !share.revoked && Date.now() < share.expiresAt && share.revision === options.publicOriginRevision() && options.publicOrigin() !== void 0;
91
102
  const live = (share) => authorized(share) && share.downloadsLeft > 0;
103
+ const mint = async (request, registered) => {
104
+ const origin = options.publicOrigin();
105
+ const revision = options.publicOriginRevision();
106
+ if (origin === void 0) throw new Error(PO_SHARE_FILE_REMOTE_OFF_MESSAGE);
107
+ const filePath = registered === void 0 ? await resolveShareable(request, environment) : "";
108
+ const opened = registered === void 0 ? await openPinned(filePath) : await registered.open();
109
+ if (opened === void 0) throw new Error(`"${request.path}" is not a regular file Po can share.`);
110
+ const { size, identity } = opened;
111
+ await opened.close();
112
+ if (registered === void 0 && size > 26214400) throw new Error(`"${request.path}" is larger than the 25 MB file sharing limit.`);
113
+ for (const [key, share] of shares) if (!live(share)) shares.delete(key);
114
+ if ([...shares.values()].filter((share) => share.sessionId === request.sessionId).length >= MAX_LIVE_SHARES_PER_SESSION) throw new Error(`This session already shares ${String(MAX_LIVE_SHARES_PER_SESSION)} files. Retry after a tool call finishes.`);
115
+ const token = randomBytes(TOKEN_BYTES).toString("base64url");
116
+ const key = tokenDigest(token);
117
+ const fileName = registered?.fileName ?? path.basename(filePath);
118
+ if (typeof identity !== "string" || !identity || !Number.isSafeInteger(size) || size < 0 || fileName.split("").some((character) => character === "/" || character === "\\" || character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127) || !fileName) throw new Error("Invalid registered file.");
119
+ const share = {
120
+ sessionId: request.sessionId,
121
+ filePath,
122
+ fileName,
123
+ ...registered === void 0 ? {} : { registered },
124
+ identity,
125
+ size,
126
+ revision,
127
+ expiresAt: Date.now() + SHARE_TTL_MS,
128
+ downloadsLeft: SHARE_DOWNLOADS,
129
+ revoked: false
130
+ };
131
+ shares.set(key, share);
132
+ options.onNotice(`file share: published "${fileName}" for ${request.label} on the remote-access tunnel.`);
133
+ return {
134
+ url: `${origin}${PUBLIC_FILE_SHARE_PREFIX}${token}`,
135
+ fileName,
136
+ mimeType: SESSION_FILE_CONTENT_TYPES[path.extname(fileName).toLowerCase()] ?? "application/octet-stream",
137
+ size,
138
+ expiresAt: share.expiresAt,
139
+ maxDownloads: SHARE_DOWNLOADS,
140
+ revoke: () => {
141
+ share.revoked = true;
142
+ shares.delete(key);
143
+ }
144
+ };
145
+ };
92
146
  return {
93
- async mint(request) {
94
- const origin = options.publicOrigin();
95
- const revision = options.publicOriginRevision();
96
- if (origin === void 0) throw new Error(PO_SHARE_FILE_REMOTE_OFF_MESSAGE);
97
- const filePath = await resolveShareable(request, environment);
98
- const opened = await openPinned(filePath);
99
- if (opened === void 0) throw new Error(`"${request.path}" is not a regular file Po can share.`);
100
- const { stat } = opened;
101
- await opened.handle.close();
102
- if (stat.size > 26214400) throw new Error(`"${request.path}" is larger than the 25 MB file sharing limit.`);
103
- for (const [key, share] of shares) if (!live(share)) shares.delete(key);
104
- if ([...shares.values()].filter((share) => share.sessionId === request.sessionId).length >= MAX_LIVE_SHARES_PER_SESSION) throw new Error(`This session already shares ${String(MAX_LIVE_SHARES_PER_SESSION)} files. Retry after a tool call finishes.`);
105
- const token = randomBytes(TOKEN_BYTES).toString("base64url");
106
- const key = tokenDigest(token);
107
- const fileName = path.basename(filePath);
108
- const share = {
109
- sessionId: request.sessionId,
110
- filePath,
111
- fileName,
112
- dev: stat.dev,
113
- ino: stat.ino,
114
- size: stat.size,
115
- mtimeMs: stat.mtimeMs,
116
- revision,
117
- expiresAt: Date.now() + SHARE_TTL_MS,
118
- downloadsLeft: SHARE_DOWNLOADS,
119
- revoked: false
120
- };
121
- shares.set(key, share);
122
- options.onNotice(`file share: published "${fileName}" for ${request.label} on the remote-access tunnel.`);
123
- return {
124
- url: `${origin}${PUBLIC_FILE_SHARE_PREFIX}${token}`,
125
- fileName,
126
- mimeType: SESSION_FILE_CONTENT_TYPES[path.extname(fileName).toLowerCase()] ?? "application/octet-stream",
127
- size: stat.size,
128
- expiresAt: share.expiresAt,
129
- maxDownloads: SHARE_DOWNLOADS,
130
- revoke: () => {
131
- share.revoked = true;
132
- shares.delete(key);
133
- }
134
- };
135
- },
147
+ mint: (request) => mint(request),
148
+ mintRegistered: (sessionId, registered) => mint({
149
+ sessionId,
150
+ cwd: "",
151
+ path: "",
152
+ label: "explicit download"
153
+ }, registered),
136
154
  async serve(token, method) {
137
155
  if (method !== "GET" && method !== "HEAD" || !TOKEN_PATTERN.test(token)) return notFound();
138
156
  const key = tokenDigest(token);
@@ -146,15 +164,15 @@ function createPublicFileShares(options) {
146
164
  share.downloadsLeft -= 1;
147
165
  if (share.downloadsLeft === 0) shares.delete(key);
148
166
  }
149
- const opened = await openPinned(share.filePath);
150
- if (opened === void 0 || opened.stat.dev !== share.dev || opened.stat.ino !== share.ino || opened.stat.size !== share.size || opened.stat.mtimeMs !== share.mtimeMs) {
151
- await opened?.handle.close();
167
+ const opened = await (share.registered === void 0 ? openPinned(share.filePath) : share.registered.open()).catch(() => void 0);
168
+ if (opened === void 0 || opened.identity !== share.identity || opened.size !== share.size) {
169
+ await opened?.close();
152
170
  shares.delete(key);
153
171
  options.onNotice(`file share: refused "${share.fileName}" because the file changed after it was shared.`);
154
172
  return notFound();
155
173
  }
156
174
  if (!authorized(share)) {
157
- await opened.handle.close();
175
+ await opened.close();
158
176
  shares.delete(key);
159
177
  return notFound();
160
178
  }
@@ -171,14 +189,15 @@ function createPublicFileShares(options) {
171
189
  "x-robots-tag": "noindex"
172
190
  };
173
191
  if (method === "HEAD" || share.size === 0) {
174
- await opened.handle.close();
192
+ await opened.close();
175
193
  return new Response(method === "HEAD" ? null : /* @__PURE__ */ new Uint8Array(), { headers });
176
194
  }
177
- const body = Readable.toWeb(opened.handle.createReadStream({
178
- start: 0,
179
- end: share.size - 1
180
- }));
181
- return new Response(body, { headers });
195
+ try {
196
+ return new Response(opened.stream(), { headers });
197
+ } catch {
198
+ await opened.close();
199
+ return notFound();
200
+ }
182
201
  }
183
202
  };
184
203
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/publicFileShares/index.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport type { Stats } from 'node:fs';\nimport fs, { type FileHandle } from 'node:fs/promises';\nimport path from 'node:path';\nimport { Readable } from 'node:stream';\n\nimport {\n MAX_SESSION_FILE_BYTES,\n SESSION_FILE_CONTENT_TYPES,\n SESSION_FILE_DEFAULT_CONTENT_TYPE,\n} from '../../constants/sessionFiles';\nimport { PO_SHARE_FILE_REMOTE_OFF_MESSAGE, type PoSharedFile } from '../../schemas/packageApi';\nimport { sessionAttachmentRoot } from '../sessionAttachments';\n\n/**\n * Short-lived public links to one session file, so a remote MCP server can fetch\n * what the model was asked to hand it.\n *\n * The link is the only credential, so only its hash is kept, and every way a\n * link can fail answers the same 404. A file is pinned by identity when the\n * link is minted and reopened when it is served: a path that now names another\n * file, or reaches it through a new symlink, is refused rather than followed.\n */\n\n/** Public tunnel path prefix; the token is the only segment after it. */\nexport const PUBLIC_FILE_SHARE_PREFIX = '/mcp-file/';\n\nconst TOKEN_BYTES = 32;\nconst TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/u;\nconst SHARE_TTL_MS = 10 * 60 * 1000;\nconst SHARE_DOWNLOADS = 3;\nconst MAX_LIVE_SHARES_PER_SESSION = 16;\nconst OPEN_FLAGS = fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW;\n\nexport interface PublicFileShareRequest {\n sessionId: string;\n cwd: string;\n /** Relative to cwd, or absolute. */\n path: string;\n /** Who the file is for, for the host's notice. */\n label: string;\n}\n\nexport interface PublicFileShares {\n mint(request: PublicFileShareRequest): Promise<PoSharedFile>;\n serve(token: string, method: string): Promise<Response>;\n}\n\nexport interface PublicFileSharesOptions {\n publicOrigin(): string | undefined;\n /** Bumped whenever the tunnel goes away; a link minted under another revision is dead. */\n publicOriginRevision(): number;\n /** Never receives a token or a link. */\n onNotice(message: string): void;\n /** Locates the attachment root; defaults to the process environment, as the upload route does. */\n environment?: Readonly<Record<string, string | undefined>>;\n}\n\ninterface Share {\n sessionId: string;\n filePath: string;\n fileName: string;\n dev: number;\n ino: number;\n size: number;\n mtimeMs: number;\n revision: number;\n expiresAt: number;\n downloadsLeft: number;\n revoked: boolean;\n}\n\nfunction tokenDigest(token: string): string {\n return createHash('sha256').update(token).digest('base64url');\n}\n\nfunction notFound(): Response {\n return Response.json({ error: 'Not found.' }, { status: 404 });\n}\n\n/** RFC 8187 value for `filename*`: encodeURIComponent leaves a few characters the grammar forbids. */\nfunction contentDispositionName(fileName: string): string {\n return encodeURIComponent(fileName).replace(\n /['()*]/gu,\n (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n}\n\n/** True when filePath sits strictly below the real root with no segment that starts with a dot. */\nasync function containedIn(root: string, filePath: string): Promise<boolean> {\n let realRoot: string;\n try {\n realRoot = await fs.realpath(root);\n } catch {\n return false;\n }\n const relative = path.relative(realRoot, filePath);\n return (\n relative !== '' &&\n !path.isAbsolute(relative) &&\n relative.split(path.sep).every((segment) => !segment.startsWith('.'))\n );\n}\n\n/** The real path of a shareable file: inside the session cwd or its attachment root, never through a dot segment. */\nasync function resolveShareable(\n request: PublicFileShareRequest,\n environment: Readonly<Record<string, string | undefined>>,\n): Promise<string> {\n const refused = new Error(`\"${request.path}\" is not a file in this session's working directory or attachments.`);\n if (\n request.path === '' ||\n request.path.includes('\\0') ||\n request.path.split(/[\\\\/]/u).some((segment) => segment === '.' || segment === '..')\n )\n throw refused;\n let filePath: string;\n try {\n filePath = await fs.realpath(path.resolve(request.cwd, request.path));\n } catch {\n throw refused;\n }\n const roots = [request.cwd];\n try {\n roots.push(sessionAttachmentRoot(request.sessionId, environment));\n } catch {\n // An id that cannot hold attachments only has its working directory.\n }\n for (const root of roots) if (await containedIn(root, filePath)) return filePath;\n throw refused;\n}\n\n/**\n * Opens a regular file without following a final symlink, then proves the handle is\n * still the file at that real path: a parent swapped for a symlink changes the real\n * path, and a replaced file changes the identity. Resolves undefined, with nothing\n * left open, when either check fails.\n */\nasync function openPinned(filePath: string): Promise<{ handle: FileHandle; stat: Stats } | undefined> {\n let handle: FileHandle;\n try {\n handle = await fs.open(filePath, OPEN_FLAGS);\n } catch {\n return undefined;\n }\n try {\n const stat = await handle.stat();\n const named = await fs.lstat(filePath);\n if (stat.isFile() && named.dev === stat.dev && named.ino === stat.ino && (await fs.realpath(filePath)) === filePath)\n return { handle, stat };\n } catch {\n // Treated like a mismatch below.\n }\n await handle.close();\n return undefined;\n}\n\nexport function createPublicFileShares(options: PublicFileSharesOptions): PublicFileShares {\n const environment = options.environment ?? process.env;\n const shares = new Map<string, Share>();\n\n const authorized = (share: Share): boolean =>\n !share.revoked &&\n Date.now() < share.expiresAt &&\n share.revision === options.publicOriginRevision() &&\n options.publicOrigin() !== undefined;\n const live = (share: Share): boolean => authorized(share) && share.downloadsLeft > 0;\n\n return {\n async mint(request) {\n const origin = options.publicOrigin();\n const revision = options.publicOriginRevision();\n if (origin === undefined) throw new Error(PO_SHARE_FILE_REMOTE_OFF_MESSAGE);\n const filePath = await resolveShareable(request, environment);\n const opened = await openPinned(filePath);\n if (opened === undefined) throw new Error(`\"${request.path}\" is not a regular file Po can share.`);\n const { stat } = opened;\n await opened.handle.close();\n if (stat.size > MAX_SESSION_FILE_BYTES)\n throw new Error(`\"${request.path}\" is larger than the 25 MB file sharing limit.`);\n for (const [key, share] of shares) if (!live(share)) shares.delete(key);\n if (\n [...shares.values()].filter((share) => share.sessionId === request.sessionId).length >=\n MAX_LIVE_SHARES_PER_SESSION\n )\n throw new Error(\n `This session already shares ${String(MAX_LIVE_SHARES_PER_SESSION)} files. Retry after a tool call finishes.`,\n );\n const token = randomBytes(TOKEN_BYTES).toString('base64url');\n const key = tokenDigest(token);\n const fileName = path.basename(filePath);\n const share: Share = {\n sessionId: request.sessionId,\n filePath,\n fileName,\n dev: stat.dev,\n ino: stat.ino,\n size: stat.size,\n mtimeMs: stat.mtimeMs,\n revision,\n expiresAt: Date.now() + SHARE_TTL_MS,\n downloadsLeft: SHARE_DOWNLOADS,\n revoked: false,\n };\n shares.set(key, share);\n options.onNotice(`file share: published \"${fileName}\" for ${request.label} on the remote-access tunnel.`);\n return {\n url: `${origin}${PUBLIC_FILE_SHARE_PREFIX}${token}`,\n fileName,\n mimeType: SESSION_FILE_CONTENT_TYPES[path.extname(fileName).toLowerCase()] ?? SESSION_FILE_DEFAULT_CONTENT_TYPE,\n size: stat.size,\n expiresAt: share.expiresAt,\n maxDownloads: SHARE_DOWNLOADS,\n revoke: () => {\n share.revoked = true;\n shares.delete(key);\n },\n };\n },\n\n async serve(token, method) {\n if ((method !== 'GET' && method !== 'HEAD') || !TOKEN_PATTERN.test(token)) return notFound();\n const key = tokenDigest(token);\n const share = shares.get(key);\n if (share === undefined) return notFound();\n if (!live(share)) {\n shares.delete(key);\n return notFound();\n }\n // Counted before the first await, so concurrent downloads cannot outrun the limit.\n if (method === 'GET') {\n share.downloadsLeft -= 1;\n if (share.downloadsLeft === 0) shares.delete(key);\n }\n const opened = await openPinned(share.filePath);\n if (\n opened === undefined ||\n opened.stat.dev !== share.dev ||\n opened.stat.ino !== share.ino ||\n opened.stat.size !== share.size ||\n opened.stat.mtimeMs !== share.mtimeMs\n ) {\n await opened?.handle.close();\n shares.delete(key);\n options.onNotice(`file share: refused \"${share.fileName}\" because the file changed after it was shared.`);\n return notFound();\n }\n // Opening a file yields to revocation and tunnel changes. The download already\n // reserved its allowance, so this check must not reject the final permitted GET.\n if (!authorized(share)) {\n await opened.handle.close();\n shares.delete(key);\n return notFound();\n }\n const headers = {\n 'content-type': 'application/octet-stream',\n 'content-length': String(share.size),\n 'content-disposition': `attachment; filename*=UTF-8''${contentDispositionName(share.fileName)}`,\n 'x-content-type-options': 'nosniff',\n 'content-security-policy': \"sandbox; default-src 'none'\",\n 'cross-origin-resource-policy': 'same-origin',\n 'cache-control': 'no-store, private',\n 'referrer-policy': 'no-referrer',\n 'accept-ranges': 'none',\n 'x-robots-tag': 'noindex',\n };\n if (method === 'HEAD' || share.size === 0) {\n await opened.handle.close();\n return new Response(method === 'HEAD' ? null : new Uint8Array(), { headers });\n }\n // The stream closes the handle when it ends, fails, or is cancelled. Bounded to the\n // pinned size, so bytes appended after the identity check are never sent.\n const body = Readable.toWeb(opened.handle.createReadStream({ start: 0, end: share.size - 1 }));\n return new Response(body as ReadableStream<Uint8Array>, { headers });\n },\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/publicFileShares/index.ts"],"sourcesContent":["import { createHash, randomBytes } from 'node:crypto';\nimport fs, { type FileHandle } from 'node:fs/promises';\nimport path from 'node:path';\nimport { Readable } from 'node:stream';\n\nimport {\n MAX_SESSION_FILE_BYTES,\n SESSION_FILE_CONTENT_TYPES,\n SESSION_FILE_DEFAULT_CONTENT_TYPE,\n} from '../../constants/sessionFiles';\nimport {\n PO_SHARE_FILE_REMOTE_OFF_MESSAGE,\n type PoOpenedRegisteredFile,\n type PoRegisteredFile,\n type PoSharedFile,\n} from '../../schemas/packageApi';\nimport { sessionAttachmentRoot } from '../sessionAttachments';\n\n/**\n * Short-lived public links to one session file, so a remote MCP server can fetch\n * what the model was asked to hand it.\n *\n * The link is the only credential, so only its hash is kept, and every way a\n * link can fail answers the same 404. A file is pinned by identity when the\n * link is minted and reopened when it is served: a path that now names another\n * file, or reaches it through a new symlink, is refused rather than followed.\n */\n\n/** Public tunnel path prefix; the token is the only segment after it. */\nexport const PUBLIC_FILE_SHARE_PREFIX = '/mcp-file/';\n\nconst TOKEN_BYTES = 32;\nconst TOKEN_PATTERN = /^[A-Za-z0-9_-]{43}$/u;\nconst SHARE_TTL_MS = 10 * 60 * 1000;\nconst SHARE_DOWNLOADS = 3;\nconst MAX_LIVE_SHARES_PER_SESSION = 16;\nconst OPEN_FLAGS = fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW;\n\nexport interface PublicFileShareRequest {\n sessionId: string;\n cwd: string;\n /** Relative to cwd, or absolute. */\n path: string;\n /** Who the file is for, for the host's notice. */\n label: string;\n}\n\nexport interface PublicFileShares {\n mint(request: PublicFileShareRequest): Promise<PoSharedFile>;\n mintRegistered(sessionId: string, file: PoRegisteredFile): Promise<PoSharedFile>;\n serve(token: string, method: string): Promise<Response>;\n}\n\nexport interface PublicFileSharesOptions {\n publicOrigin(): string | undefined;\n /** Bumped whenever the tunnel goes away; a link minted under another revision is dead. */\n publicOriginRevision(): number;\n /** Never receives a token or a link. */\n onNotice(message: string): void;\n /** Locates the attachment root; defaults to the process environment, as the upload route does. */\n environment?: Readonly<Record<string, string | undefined>>;\n}\n\ninterface Share {\n sessionId: string;\n filePath: string;\n registered?: PoRegisteredFile;\n fileName: string;\n identity: string;\n size: number;\n revision: number;\n expiresAt: number;\n downloadsLeft: number;\n revoked: boolean;\n}\n\nfunction tokenDigest(token: string): string {\n return createHash('sha256').update(token).digest('base64url');\n}\n\nfunction notFound(): Response {\n return Response.json({ error: 'Not found.' }, { status: 404 });\n}\n\n/** RFC 8187 value for `filename*`: encodeURIComponent leaves a few characters the grammar forbids. */\nfunction contentDispositionName(fileName: string): string {\n return encodeURIComponent(fileName).replace(\n /['()*]/gu,\n (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`,\n );\n}\n\n/** True when filePath sits strictly below the real root with no segment that starts with a dot. */\nasync function containedIn(root: string, filePath: string): Promise<boolean> {\n let realRoot: string;\n try {\n realRoot = await fs.realpath(root);\n } catch {\n return false;\n }\n const relative = path.relative(realRoot, filePath);\n return (\n relative !== '' &&\n !path.isAbsolute(relative) &&\n relative.split(path.sep).every((segment) => !segment.startsWith('.'))\n );\n}\n\n/** The real path of a shareable file: inside the session cwd or its attachment root, never through a dot segment. */\nasync function resolveShareable(\n request: PublicFileShareRequest,\n environment: Readonly<Record<string, string | undefined>>,\n): Promise<string> {\n const refused = new Error(`\"${request.path}\" is not a file in this session's working directory or attachments.`);\n if (\n request.path === '' ||\n request.path.includes('\\0') ||\n request.path.split(/[\\\\/]/u).some((segment) => segment === '.' || segment === '..')\n )\n throw refused;\n let filePath: string;\n try {\n filePath = await fs.realpath(path.resolve(request.cwd, request.path));\n } catch {\n throw refused;\n }\n const roots = [request.cwd];\n try {\n roots.push(sessionAttachmentRoot(request.sessionId, environment));\n } catch {\n // An id that cannot hold attachments only has its working directory.\n }\n for (const root of roots) if (await containedIn(root, filePath)) return filePath;\n throw refused;\n}\n\n/**\n * Opens a regular file without following a final symlink, then proves the handle is\n * still the file at that real path: a parent swapped for a symlink changes the real\n * path, and a replaced file changes the identity. Resolves undefined, with nothing\n * left open, when either check fails.\n */\nasync function openPinned(filePath: string): Promise<PoOpenedRegisteredFile | undefined> {\n let handle: FileHandle;\n try {\n handle = await fs.open(filePath, OPEN_FLAGS);\n } catch {\n return undefined;\n }\n try {\n const stat = await handle.stat();\n const named = await fs.lstat(filePath);\n if (stat.isFile() && named.dev === stat.dev && named.ino === stat.ino && (await fs.realpath(filePath)) === filePath)\n return {\n identity: [stat.dev, stat.ino, stat.size, stat.mtimeMs, stat.ctimeMs].join(':'),\n size: stat.size,\n close: () => handle.close(),\n stream: () =>\n Readable.toWeb(handle.createReadStream({ start: 0, end: stat.size - 1 })) as ReadableStream<Uint8Array>,\n };\n } catch {\n // Treated like a mismatch below.\n }\n await handle.close();\n return undefined;\n}\n\nexport function createPublicFileShares(options: PublicFileSharesOptions): PublicFileShares {\n const environment = options.environment ?? process.env;\n const shares = new Map<string, Share>();\n\n const authorized = (share: Share): boolean =>\n !share.revoked &&\n Date.now() < share.expiresAt &&\n share.revision === options.publicOriginRevision() &&\n options.publicOrigin() !== undefined;\n const live = (share: Share): boolean => authorized(share) && share.downloadsLeft > 0;\n\n const mint = async (request: PublicFileShareRequest, registered?: PoRegisteredFile): Promise<PoSharedFile> => {\n const origin = options.publicOrigin();\n const revision = options.publicOriginRevision();\n if (origin === undefined) throw new Error(PO_SHARE_FILE_REMOTE_OFF_MESSAGE);\n const filePath = registered === undefined ? await resolveShareable(request, environment) : '';\n const opened = registered === undefined ? await openPinned(filePath) : await registered.open();\n if (opened === undefined) throw new Error(`\"${request.path}\" is not a regular file Po can share.`);\n const { size, identity } = opened;\n await opened.close();\n if (registered === undefined && size > MAX_SESSION_FILE_BYTES)\n throw new Error(`\"${request.path}\" is larger than the 25 MB file sharing limit.`);\n for (const [key, share] of shares) if (!live(share)) shares.delete(key);\n if (\n [...shares.values()].filter((share) => share.sessionId === request.sessionId).length >=\n MAX_LIVE_SHARES_PER_SESSION\n )\n throw new Error(\n `This session already shares ${String(MAX_LIVE_SHARES_PER_SESSION)} files. Retry after a tool call finishes.`,\n );\n const token = randomBytes(TOKEN_BYTES).toString('base64url');\n const key = tokenDigest(token);\n const fileName = registered?.fileName ?? path.basename(filePath);\n if (\n typeof identity !== 'string' ||\n !identity ||\n !Number.isSafeInteger(size) ||\n size < 0 ||\n fileName\n .split('')\n .some(\n (character) =>\n character === '/' || character === '\\\\' || character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127,\n ) ||\n !fileName\n )\n throw new Error('Invalid registered file.');\n const share: Share = {\n sessionId: request.sessionId,\n filePath,\n fileName,\n ...(registered === undefined ? {} : { registered }),\n identity,\n size,\n revision,\n expiresAt: Date.now() + SHARE_TTL_MS,\n downloadsLeft: SHARE_DOWNLOADS,\n revoked: false,\n };\n shares.set(key, share);\n options.onNotice(`file share: published \"${fileName}\" for ${request.label} on the remote-access tunnel.`);\n return {\n url: `${origin}${PUBLIC_FILE_SHARE_PREFIX}${token}`,\n fileName,\n mimeType: SESSION_FILE_CONTENT_TYPES[path.extname(fileName).toLowerCase()] ?? SESSION_FILE_DEFAULT_CONTENT_TYPE,\n size,\n expiresAt: share.expiresAt,\n maxDownloads: SHARE_DOWNLOADS,\n revoke: () => {\n share.revoked = true;\n shares.delete(key);\n },\n };\n };\n\n return {\n mint: (request) => mint(request),\n mintRegistered: (sessionId, registered) =>\n mint({ sessionId, cwd: '', path: '', label: 'explicit download' }, registered),\n async serve(token, method) {\n if ((method !== 'GET' && method !== 'HEAD') || !TOKEN_PATTERN.test(token)) return notFound();\n const key = tokenDigest(token);\n const share = shares.get(key);\n if (share === undefined) return notFound();\n if (!live(share)) {\n shares.delete(key);\n return notFound();\n }\n // Counted before the first await, so concurrent downloads cannot outrun the limit.\n if (method === 'GET') {\n share.downloadsLeft -= 1;\n if (share.downloadsLeft === 0) shares.delete(key);\n }\n const opened = await (\n share.registered === undefined ? openPinned(share.filePath) : share.registered.open()\n ).catch(() => undefined);\n if (opened === undefined || opened.identity !== share.identity || opened.size !== share.size) {\n await opened?.close();\n shares.delete(key);\n options.onNotice(`file share: refused \"${share.fileName}\" because the file changed after it was shared.`);\n return notFound();\n }\n // Opening a file yields to revocation and tunnel changes. The download already\n // reserved its allowance, so this check must not reject the final permitted GET.\n if (!authorized(share)) {\n await opened.close();\n shares.delete(key);\n return notFound();\n }\n const headers = {\n 'content-type': 'application/octet-stream',\n 'content-length': String(share.size),\n 'content-disposition': `attachment; filename*=UTF-8''${contentDispositionName(share.fileName)}`,\n 'x-content-type-options': 'nosniff',\n 'content-security-policy': \"sandbox; default-src 'none'\",\n 'cross-origin-resource-policy': 'same-origin',\n 'cache-control': 'no-store, private',\n 'referrer-policy': 'no-referrer',\n 'accept-ranges': 'none',\n 'x-robots-tag': 'noindex',\n };\n if (method === 'HEAD' || share.size === 0) {\n await opened.close();\n return new Response(method === 'HEAD' ? null : new Uint8Array(), { headers });\n }\n // The stream closes the handle when it ends, fails, or is cancelled. Bounded to the\n // pinned size, so bytes appended after the identity check are never sent.\n try {\n return new Response(opened.stream(), { headers });\n } catch {\n await opened.close();\n return notFound();\n }\n },\n };\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/po-core",
3
- "version": "0.0.0-alpha.69",
3
+ "version": "0.0.0-alpha.70",
4
4
  "description": "Po runtime systems and reusable core services.",
5
5
  "keywords": [
6
6
  "ai",
@@ -559,8 +559,8 @@
559
559
  "registry": "https://registry.npmjs.org/"
560
560
  },
561
561
  "dependencies": {
562
- "@agimon-ai/po-logsink": "0.0.1-alpha.51",
563
- "@agimon-ai/po-web-security": "0.0.0-alpha.69",
562
+ "@agimon-ai/po-logsink": "0.0.1-alpha.52",
563
+ "@agimon-ai/po-web-security": "0.0.0-alpha.70",
564
564
  "@deepseek-ai/cordis": "4.0.5",
565
565
  "@earendil-works/chord": "1.4.0",
566
566
  "@earendil-works/pi-agent-core": "1.4.0",
@@ -584,7 +584,7 @@
584
584
  "yaml": "2.9.1"
585
585
  },
586
586
  "devDependencies": {
587
- "@agimon-ai/vibe-lint-plugin-po-core": "0.0.0-alpha.69",
587
+ "@agimon-ai/vibe-lint-plugin-po-core": "0.0.0-alpha.70",
588
588
  "@apidevtools/swagger-parser": "^13.1.0",
589
589
  "@asyncapi/parser": "^3.6.4",
590
590
  "@earendil-works/pi-ai": "1.4.0",