@agimon-ai/doompi-core 0.0.1-alpha.84 → 0.0.1-alpha.86

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.
Files changed (51) hide show
  1. package/dist/childSession.cjs +1 -0
  2. package/dist/childSession.d.mts +2 -2
  3. package/dist/childSession.mjs +2 -2
  4. package/dist/src/schemas/headless.cjs.map +1 -1
  5. package/dist/src/schemas/headless.d.mts +2 -0
  6. package/dist/src/schemas/headless.d.mts.map +1 -1
  7. package/dist/src/schemas/headless.mjs.map +1 -1
  8. package/dist/src/server/sessionMcpHandler.cjs +12 -9
  9. package/dist/src/server/sessionMcpHandler.cjs.map +1 -1
  10. package/dist/src/server/sessionMcpHandler.d.mts +4 -2
  11. package/dist/src/server/sessionMcpHandler.d.mts.map +1 -1
  12. package/dist/src/server/sessionMcpHandler.mjs +12 -9
  13. package/dist/src/server/sessionMcpHandler.mjs.map +1 -1
  14. package/dist/src/server/sessionMcpRoutes.cjs +35 -18
  15. package/dist/src/server/sessionMcpRoutes.cjs.map +1 -1
  16. package/dist/src/server/sessionMcpRoutes.mjs +35 -18
  17. package/dist/src/server/sessionMcpRoutes.mjs.map +1 -1
  18. package/dist/src/services/sessionMcpAuthorization/index.cjs +71 -4
  19. package/dist/src/services/sessionMcpAuthorization/index.cjs.map +1 -1
  20. package/dist/src/services/sessionMcpAuthorization/index.d.mts +4 -2
  21. package/dist/src/services/sessionMcpAuthorization/index.d.mts.map +1 -1
  22. package/dist/src/services/sessionMcpAuthorization/index.mjs +72 -5
  23. package/dist/src/services/sessionMcpAuthorization/index.mjs.map +1 -1
  24. package/dist/src/services/sessionMcpRegistrationStore/index.cjs +1 -1
  25. package/dist/src/services/sessionMcpRegistrationStore/index.cjs.map +1 -1
  26. package/dist/src/services/sessionMcpRegistrationStore/index.mjs +1 -1
  27. package/dist/src/services/sessionMcpRegistrationStore/index.mjs.map +1 -1
  28. package/dist/src/systems/child/adapters/headlessChildSessionService.cjs +26 -6
  29. package/dist/src/systems/child/adapters/headlessChildSessionService.cjs.map +1 -1
  30. package/dist/src/systems/child/adapters/headlessChildSessionService.d.mts +3 -1
  31. package/dist/src/systems/child/adapters/headlessChildSessionService.d.mts.map +1 -1
  32. package/dist/src/systems/child/adapters/headlessChildSessionService.mjs +26 -6
  33. package/dist/src/systems/child/adapters/headlessChildSessionService.mjs.map +1 -1
  34. package/dist/src/systems/child/types/childSession.cjs +3 -0
  35. package/dist/src/systems/child/types/childSession.cjs.map +1 -1
  36. package/dist/src/systems/child/types/childSession.d.mts +9 -2
  37. package/dist/src/systems/child/types/childSession.d.mts.map +1 -1
  38. package/dist/src/systems/child/types/childSession.mjs +3 -1
  39. package/dist/src/systems/child/types/childSession.mjs.map +1 -1
  40. package/dist/src/systems/main/adapters/headlessSessionHost.cjs +7 -2
  41. package/dist/src/systems/main/adapters/headlessSessionHost.cjs.map +1 -1
  42. package/dist/src/systems/main/adapters/headlessSessionHost.d.mts.map +1 -1
  43. package/dist/src/systems/main/adapters/headlessSessionHost.mjs +8 -3
  44. package/dist/src/systems/main/adapters/headlessSessionHost.mjs.map +1 -1
  45. package/dist/terminalPiChildSessionService.cjs +2 -1
  46. package/dist/terminalPiChildSessionService.cjs.map +1 -1
  47. package/dist/terminalPiChildSessionService.d.mts +2 -1
  48. package/dist/terminalPiChildSessionService.d.mts.map +1 -1
  49. package/dist/terminalPiChildSessionService.mjs +2 -1
  50. package/dist/terminalPiChildSessionService.mjs.map +1 -1
  51. package/package.json +1 -1
@@ -1,6 +1,7 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_childSession = require("./src/systems/child/types/childSession.cjs");
3
3
  const require_index = require("./src/systems/child/services/childSession/index.cjs");
4
+ exports.DOOM_CHILD_SESSION_MCP_TOOL_SERVICE = require_childSession.DOOM_CHILD_SESSION_MCP_TOOL_SERVICE;
4
5
  exports.DOOM_CHILD_SESSION_SERVICE = require_childSession.DOOM_CHILD_SESSION_SERVICE;
5
6
  exports.createDoomChildSessionService = require_index.createDoomChildSessionService;
6
7
  exports.readDoomChildSessionService = require_childSession.readDoomChildSessionService;
@@ -1,3 +1,3 @@
1
- import { DOOM_CHILD_SESSION_SERVICE, DoomChildSessionCapabilityCeiling, DoomChildSessionEvent, DoomChildSessionFreshSource, DoomChildSessionHandle, DoomChildSessionIntercom, DoomChildSessionRequest, DoomChildSessionRuntime, DoomChildSessionRuntimeFactory, DoomChildSessionScope, DoomChildSessionService, DoomChildSessionServiceProvider, DoomChildSessionSource, DoomChildSessionState, DoomChildSessionTerminalPiForkSource, DoomChildSessionTool, DoomChildSessionToolResult, DoomChildSessionUsage, DoomChildSessionV4ForkSource, DoomChildSessionV4RestoreSource, readDoomChildSessionService, requireDoomChildSessionService } from "./src/systems/child/types/childSession.mjs";
1
+ import { DOOM_CHILD_SESSION_MCP_TOOL_SERVICE, DOOM_CHILD_SESSION_SERVICE, DoomChildSessionCapabilityCeiling, DoomChildSessionEvent, DoomChildSessionFreshSource, DoomChildSessionHandle, DoomChildSessionIntercom, DoomChildSessionRequest, DoomChildSessionRuntime, DoomChildSessionRuntimeFactory, DoomChildSessionScope, DoomChildSessionService, DoomChildSessionServiceProvider, DoomChildSessionSource, DoomChildSessionState, DoomChildSessionTerminalPiForkSource, DoomChildSessionTool, DoomChildSessionToolResult, DoomChildSessionUsage, DoomChildSessionV4ForkSource, DoomChildSessionV4RestoreSource, readDoomChildSessionService, requireDoomChildSessionService } from "./src/systems/child/types/childSession.mjs";
2
2
  import { DoomChildSessionServiceDependencies, createDoomChildSessionService } from "./src/systems/child/services/childSession/index.mjs";
3
- export { DOOM_CHILD_SESSION_SERVICE, DoomChildSessionCapabilityCeiling, DoomChildSessionEvent, DoomChildSessionFreshSource, DoomChildSessionHandle, DoomChildSessionIntercom, DoomChildSessionRequest, DoomChildSessionRuntime, DoomChildSessionRuntimeFactory, DoomChildSessionScope, DoomChildSessionService, DoomChildSessionServiceDependencies, DoomChildSessionServiceProvider, DoomChildSessionSource, DoomChildSessionState, DoomChildSessionTerminalPiForkSource, DoomChildSessionTool, DoomChildSessionToolResult, DoomChildSessionUsage, DoomChildSessionV4ForkSource, DoomChildSessionV4RestoreSource, createDoomChildSessionService, readDoomChildSessionService, requireDoomChildSessionService };
3
+ export { DOOM_CHILD_SESSION_MCP_TOOL_SERVICE, DOOM_CHILD_SESSION_SERVICE, DoomChildSessionCapabilityCeiling, DoomChildSessionEvent, DoomChildSessionFreshSource, DoomChildSessionHandle, DoomChildSessionIntercom, DoomChildSessionRequest, DoomChildSessionRuntime, DoomChildSessionRuntimeFactory, DoomChildSessionScope, DoomChildSessionService, DoomChildSessionServiceDependencies, DoomChildSessionServiceProvider, DoomChildSessionSource, DoomChildSessionState, DoomChildSessionTerminalPiForkSource, DoomChildSessionTool, DoomChildSessionToolResult, DoomChildSessionUsage, DoomChildSessionV4ForkSource, DoomChildSessionV4RestoreSource, createDoomChildSessionService, readDoomChildSessionService, requireDoomChildSessionService };
@@ -1,3 +1,3 @@
1
- import { DOOM_CHILD_SESSION_SERVICE, readDoomChildSessionService, requireDoomChildSessionService } from "./src/systems/child/types/childSession.mjs";
1
+ import { DOOM_CHILD_SESSION_MCP_TOOL_SERVICE, DOOM_CHILD_SESSION_SERVICE, readDoomChildSessionService, requireDoomChildSessionService } from "./src/systems/child/types/childSession.mjs";
2
2
  import { createDoomChildSessionService } from "./src/systems/child/services/childSession/index.mjs";
3
- export { DOOM_CHILD_SESSION_SERVICE, createDoomChildSessionService, readDoomChildSessionService, requireDoomChildSessionService };
3
+ export { DOOM_CHILD_SESSION_MCP_TOOL_SERVICE, DOOM_CHILD_SESSION_SERVICE, createDoomChildSessionService, readDoomChildSessionService, requireDoomChildSessionService };
@@ -1 +1 @@
1
- {"version":3,"file":"headless.cjs","names":[],"sources":["../../../src/schemas/headless.ts"],"sourcesContent":["import type { Context } from '@deepseek-ai/cordis';\nimport type { ThinkingLevel } from '@earendil-works/pi-agent-core';\nimport type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';\nimport type { Static, TSchema } from 'typebox';\n\nimport type { DoomNotificationRequest } from './notification';\nimport type { DoomServerBundleEntry } from './serverBundle';\n\n/** Session-only contributions. This is not a Pi ExtensionAPI or a TUI adapter. */\nexport const DOOM_HEADLESS_HOST_SERVICE = 'doom/headless-host';\nexport const DOOM_HEADLESS_OWNER = Symbol.for('doom/headless-owner');\n\nexport interface DoomHeadlessSelection {\n readonly majorMode: string;\n readonly activeLayers: readonly string[];\n readonly domains: readonly string[];\n readonly profile?: string;\n /** Feature-owned activation sets, keyed by the contributing capability. */\n readonly state?: Readonly<Record<string, readonly string[]>>;\n}\n\n/** One package-owned selection transition. Server facets change exactly one axis per operation. */\nexport type DoomHeadlessSelectionChange =\n | { readonly axis: 'majorMode'; readonly majorMode: string }\n | { readonly axis: 'domains'; readonly domains: readonly string[] }\n | { readonly axis: 'profile'; readonly profile?: string }\n | { readonly axis: 'state'; readonly key: string; readonly values: readonly string[] };\n\nexport function readDoomHeadlessOwner(context: Context): DoomServerBundleEntry | undefined {\n return (context as Context & { [DOOM_HEADLESS_OWNER]?: DoomServerBundleEntry })[DOOM_HEADLESS_OWNER];\n}\n\nexport interface DoomHeadlessRegistration {\n dispose(): void;\n}\n\nexport type DoomHeadlessContent = { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string };\n\nexport interface DoomHeadlessToolResult {\n content: DoomHeadlessContent[];\n /** Public MCP result data. Internal renderer details are not a remote result contract. */\n structuredContent?: Record<string, unknown>;\n /** Component-only MCP metadata, never inferred from internal renderer details. */\n _meta?: CallToolResult['_meta'];\n details?: unknown;\n isError?: boolean;\n}\n\nexport interface DoomHeadlessClientRequest {\n kind: 'confirm' | 'select' | 'input';\n title: string;\n message?: string;\n options?: readonly { label: string; description?: string; value: string }[];\n initialValue?: string;\n multiline?: boolean;\n}\n\nexport interface DoomHeadlessClient {\n /**\n * Operator-facing surface only: the body is flattened to one line and capped at 4096 characters\n * by schemas/notification.ts:73-83, and the model never sees it. Use session.admitPrompt to\n * reach the model.\n */\n notify(request: DoomNotificationRequest): void | Promise<void>;\n request(request: DoomHeadlessClientRequest, signal?: AbortSignal): Promise<unknown>;\n setStatus(source: string, text: string | undefined): void;\n appendComposerText?(text: string): void;\n}\n\nexport interface DoomHeadlessModelSettings {\n model?: { provider: string; id: string };\n thinkingLevel: ThinkingLevel;\n}\n\nexport interface DoomHeadlessSession {\n readModelSettings?(): Promise<DoomHeadlessModelSettings>;\n setModelSettings?(settings: Partial<DoomHeadlessModelSettings>): Promise<void>;\n /** Query durable history on demand; hosts do not retain the entire transcript. */\n entries(query?: {\n type?: 'custom' | 'message';\n customType?: string;\n limit?: number;\n }): readonly Record<string, unknown>[] | Promise<readonly Record<string, unknown>[]>;\n appendCustomEntry(type: string, data: unknown): Promise<void>;\n /**\n * Delivers and awaits the turn. 'steer' and 'followUp' only enqueue into a running turn; they do\n * NOT wake an idle agent.\n */\n prompt(text: string, delivery?: 'prompt' | 'steer' | 'followUp'): Promise<void>;\n /**\n * Resolves when accepted by the session, before the agent turn settles. Here 'steer' describes\n * how to deliver IF a turn is already running; an idle agent is woken either way, matching Pi's\n * ExtensionAPI.sendMessage({ triggerTurn: true, deliverAs: 'steer' }).\n */\n admitPrompt?(text: string, delivery?: 'prompt' | 'steer' | 'followUp'): Promise<void>;\n abort(): Promise<void>;\n compact(instructions?: string): Promise<void>;\n activity(): Promise<{ hasPendingMessages: boolean; isIdle: boolean }>;\n /** Capture the current persisted branch for an in-process child fork. */\n forkSource?(): Promise<{ kind: 'v4-fork'; sessionFile: string; branch: string; entryId?: string }>;\n}\n\n/** A grant-filtered skill view available only during one remote MCP tool invocation. */\nexport interface DoomMcpSkillAccess {\n list(): Promise<readonly { name: string; description: string }[]>;\n read(name: string): Promise<string>;\n}\n\n/** A stateless auxiliary request. These tools are private to this request, never registered on the agent. */\nexport interface DoomHeadlessToolCompletionRequest {\n systemPrompt: string;\n input: string;\n maxTokens: number;\n cacheRetention?: 'none' | 'short' | 'long';\n signal?: AbortSignal;\n tools: readonly Pick<DoomHeadlessTool, 'name' | 'description' | 'parameters'>[];\n}\n\nexport interface DoomHeadlessToolCompletionResult {\n toolCalls: readonly { id: string; name: string; arguments: unknown }[];\n usage: { totalTokens: number };\n}\n\nexport interface DoomHeadlessExecutionContext {\n readonly cwd: string;\n /** Admitted configuration root, distinct from the tool execution directory. */\n readonly repoRoot: string;\n readonly sessionId: string;\n /** Immutable session configuration. Facets must not read or mutate process.env. */\n readonly environment: Readonly<Record<string, string | undefined>>;\n readonly client: DoomHeadlessClient;\n readonly session: DoomHeadlessSession;\n readonly model?: { provider: string; id: string };\n readonly textCompletion?: {\n available(reference: string): boolean;\n complete(\n reference: string,\n request: {\n systemPrompt: string;\n input: string;\n maxTokens: number;\n cacheRetention?: 'none' | 'short' | 'long';\n signal?: AbortSignal;\n },\n ): Promise<string>;\n };\n readonly toolCompletion?: {\n complete(reference: string, request: DoomHeadlessToolCompletionRequest): Promise<DoomHeadlessToolCompletionResult>;\n };\n readonly selection: DoomHeadlessSelection;\n /** Present only while an authenticated remote MCP tool is running. */\n readonly mcpSkills?: DoomMcpSkillAccess;\n shutdown(): void;\n}\n\n/** Conditions narrow an eligible owner's contributions; they can never restore a disabled owner. */\nexport interface DoomHeadlessCondition {\n state?: Readonly<Record<string, string>>;\n attribution?: { kind: 'minor' | 'domain'; mode: string; label?: string };\n domain?: string;\n}\n\nexport interface DoomHeadlessToolRestriction {\n when?: DoomHeadlessCondition;\n /** Optional capability ceiling. An empty list disables every tool. */\n allowedTools?: readonly string[];\n /**\n * Narrow exclusions without disabling unrelated tools. Exclude a tool your package owns, or a\n * specific foreign tool the mode exists to withdraw. Never enumerate an allowlist here.\n */\n excludedTools?: readonly string[];\n}\n\nexport interface DoomHeadlessTool<TParameters extends TSchema = TSchema> {\n when?: DoomHeadlessCondition;\n name: string;\n label?: string;\n description: string;\n parameters: TParameters;\n annotations?: Tool['annotations'];\n outputSchema?: Tool['outputSchema'];\n /** Remote presentation only. Component tool access must be explicitly opted into. */\n _meta?: Tool['_meta'] & {\n ui?: {\n resourceUri?: string;\n visibility?: ('model' | 'app')[];\n };\n };\n promptSnippet?: string;\n promptGuidelines?: readonly string[];\n executionMode?: 'parallel' | 'serial';\n execute(\n toolCallId: string,\n parameters: Static<TParameters>,\n signal: AbortSignal | undefined,\n onUpdate: ((result: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ): Promise<DoomHeadlessToolResult>;\n}\n\n/**\n * One package contribution to the session's resource surface.\n *\n * `kind` decides what the model is billed for, and the difference is large:\n * - `context` is EAGER. `read()` runs on every prompt build and the text is pasted\n * into the system prompt verbatim. Reserve it for small live session state that\n * exists nowhere on disk. Never use it for a file the package ships: a README\n * registered this way costs its full length on every single turn.\n * - `skill` is LAZY. Only `name`, `description` and `path` reach the prompt; the\n * agent reads the file itself when the description matches the task.\n * - `prompt` is inert until the user invokes it by name.\n *\n * The rule in one line: if it is a file the package ships, it is a `skill`.\n */\nexport interface DoomHeadlessResource {\n when?: DoomHeadlessCondition;\n name: string;\n /** Model-visible selection signal. A `skill` without one tells the agent nothing. */\n description?: string;\n /** Absolute path of the file `read()` returns, so an advertised skill is one the agent can open. */\n path?: string;\n kind: 'prompt' | 'skill' | 'context';\n read(context: DoomHeadlessExecutionContext): string | Promise<string>;\n}\n\nexport interface DoomHeadlessCommand {\n when?: DoomHeadlessCondition;\n name: string;\n description: string;\n execute(args: string, context: DoomHeadlessExecutionContext): void | Promise<void>;\n}\n\nexport type DoomHeadlessEventName =\n | 'session_start'\n | 'session_shutdown'\n | 'session_tree'\n | 'before_agent_start'\n | 'agent_start'\n | 'agent_settled'\n | 'turn_start'\n | 'turn_end'\n | 'context'\n | 'before_provider_request'\n | 'message_start'\n | 'message_update'\n | 'message_end'\n | 'tool_call'\n | 'tool_execution_start'\n | 'tool_execution_update'\n | 'tool_execution_end'\n | 'tool_result'\n | 'model_select'\n | 'session_before_compact'\n | 'session_compact';\n\nexport type DoomHeadlessHookEvent<E extends DoomHeadlessEventName> = E extends 'before_provider_request'\n ? { lane: string; runId: string; model: { provider: string; id: string }; payload: unknown }\n : E extends 'session_before_compact'\n ? { reason: string; preparation: unknown; instructions?: string; customInstructions?: string }\n : E extends 'model_select'\n ? { model: { provider: string; id: string } }\n : Readonly<Record<string, unknown>>;\n\nexport interface DoomHeadlessCompactionResult {\n summary: string;\n tokensBefore: number;\n retainedTail: Record<string, unknown>[];\n usage?: Record<string, unknown>;\n details?: unknown;\n}\n\nexport type DoomHeadlessHookResult<E extends DoomHeadlessEventName> = E extends 'before_provider_request'\n ? { payload: unknown } | void\n : E extends 'session_before_compact'\n ? { cancel?: boolean; decline?: boolean; compaction?: DoomHeadlessCompactionResult } | void\n : E extends 'context'\n ? { messages?: unknown[]; systemPrompt?: string } | void\n : E extends 'before_agent_start'\n ? { systemPrompt?: string } | void\n : E extends 'tool_call'\n ? { args?: Record<string, unknown>; block?: { reason: string; terminate?: boolean } } | void\n : E extends 'tool_result'\n ? {\n content?: DoomHeadlessContent[];\n details?: unknown;\n isError?: boolean;\n usage?: Record<string, unknown>;\n terminate?: boolean;\n } | void\n : unknown;\n\nexport type DoomHeadlessHook<E extends DoomHeadlessEventName = DoomHeadlessEventName> = E extends DoomHeadlessEventName\n ? {\n when?: DoomHeadlessCondition;\n event: E;\n handle(\n event: Readonly<DoomHeadlessHookEvent<E>>,\n context: DoomHeadlessExecutionContext,\n ): DoomHeadlessHookResult<E> | Promise<DoomHeadlessHookResult<E>>;\n }\n : never;\n\n/** A retained facet can register activity without starting disabled watchers or services. */\nexport interface DoomHeadlessActivity {\n when?: DoomHeadlessCondition;\n name: string;\n start(context: DoomHeadlessExecutionContext): (() => void | Promise<void>) | Promise<() => void | Promise<void>>;\n}\n\n/** Read-only metadata for the currently applied session, never executable declarations or resource bodies. */\nexport interface DoomHeadlessCapability {\n readonly source: string;\n readonly name: string;\n readonly kind: 'tool' | 'skill';\n readonly when?: DoomHeadlessCondition;\n readonly active: boolean;\n readonly discoverable: boolean;\n readonly reason?: 'inactive' | 'restricted' | 'shadowed' | 'unavailable' | 'not-discoverable';\n}\n\nexport interface DoomHeadlessCapabilitySnapshot {\n readonly revision: number;\n readonly ready: boolean;\n readonly capabilities: readonly DoomHeadlessCapability[];\n}\n\n/** Registrations inherit package ownership from the caller's Cordis facet. */\nexport interface DoomHeadlessHostService {\n readonly context: DoomHeadlessExecutionContext;\n changeSelection(change: DoomHeadlessSelectionChange): Promise<void>;\n assertActive(source?: string): void;\n subscribeSelection(listener: (selection: DoomHeadlessSelection) => void | Promise<void>): () => void;\n /** Inspect only a coherently applied generation. A transition returns ready=false and no partial inventory. */\n inspectCapabilities(): DoomHeadlessCapabilitySnapshot;\n registerToolRestriction(restriction: DoomHeadlessToolRestriction): DoomHeadlessRegistration;\n registerTool<TParameters extends TSchema>(tool: DoomHeadlessTool<TParameters>): DoomHeadlessRegistration;\n registerResource(resource: DoomHeadlessResource): DoomHeadlessRegistration;\n registerCommand(command: DoomHeadlessCommand): DoomHeadlessRegistration;\n registerHook(hook: DoomHeadlessHook): DoomHeadlessRegistration;\n registerActivity(activity: DoomHeadlessActivity): DoomHeadlessRegistration;\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n 'doom/headless-host': DoomHeadlessHostService;\n }\n}\n\nexport function readDoomHeadlessHost(context: Context): DoomHeadlessHostService | undefined {\n return context.get(DOOM_HEADLESS_HOST_SERVICE) as DoomHeadlessHostService | undefined;\n}\n\nexport function requireDoomHeadlessHost(context: Context): DoomHeadlessHostService {\n const host = readDoomHeadlessHost(context);\n if (!host) throw new Error('This contribution requires the Doom headless session host.');\n return host;\n}\n"],"mappings":";;AASA,MAAa,6BAA6B;AAC1C,MAAa,sBAAsB,OAAO,IAAI,qBAAqB;AAkBnE,SAAgB,sBAAsB,SAAqD;CACzF,OAAQ,QAAwE;AAClF;AA8TA,SAAgB,qBAAqB,SAAuD;CAC1F,OAAO,QAAQ,IAAI,0BAA0B;AAC/C;AAEA,SAAgB,wBAAwB,SAA2C;CACjF,MAAM,OAAO,qBAAqB,OAAO;CACzC,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,4DAA4D;CACvF,OAAO;AACT"}
1
+ {"version":3,"file":"headless.cjs","names":[],"sources":["../../../src/schemas/headless.ts"],"sourcesContent":["import type { Context } from '@deepseek-ai/cordis';\nimport type { ThinkingLevel } from '@earendil-works/pi-agent-core';\nimport type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';\nimport type { Static, TSchema } from 'typebox';\n\nimport type { DoomNotificationRequest } from './notification';\nimport type { DoomServerBundleEntry } from './serverBundle';\n\n/** Session-only contributions. This is not a Pi ExtensionAPI or a TUI adapter. */\nexport const DOOM_HEADLESS_HOST_SERVICE = 'doom/headless-host';\nexport const DOOM_HEADLESS_OWNER = Symbol.for('doom/headless-owner');\n\nexport interface DoomHeadlessSelection {\n readonly majorMode: string;\n readonly activeLayers: readonly string[];\n readonly domains: readonly string[];\n readonly profile?: string;\n /** Feature-owned activation sets, keyed by the contributing capability. */\n readonly state?: Readonly<Record<string, readonly string[]>>;\n}\n\n/** One package-owned selection transition. Server facets change exactly one axis per operation. */\nexport type DoomHeadlessSelectionChange =\n | { readonly axis: 'majorMode'; readonly majorMode: string }\n | { readonly axis: 'domains'; readonly domains: readonly string[] }\n | { readonly axis: 'profile'; readonly profile?: string }\n | { readonly axis: 'state'; readonly key: string; readonly values: readonly string[] };\n\nexport function readDoomHeadlessOwner(context: Context): DoomServerBundleEntry | undefined {\n return (context as Context & { [DOOM_HEADLESS_OWNER]?: DoomServerBundleEntry })[DOOM_HEADLESS_OWNER];\n}\n\nexport interface DoomHeadlessRegistration {\n dispose(): void;\n}\n\nexport type DoomHeadlessContent = { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string };\n\nexport interface DoomHeadlessToolResult {\n content: DoomHeadlessContent[];\n /** Public MCP result data. Internal renderer details are not a remote result contract. */\n structuredContent?: Record<string, unknown>;\n /** Component-only MCP metadata, never inferred from internal renderer details. */\n _meta?: CallToolResult['_meta'];\n details?: unknown;\n isError?: boolean;\n}\n\nexport interface DoomHeadlessClientRequest {\n kind: 'confirm' | 'select' | 'input';\n title: string;\n message?: string;\n options?: readonly { label: string; description?: string; value: string }[];\n initialValue?: string;\n multiline?: boolean;\n}\n\nexport interface DoomHeadlessClient {\n /**\n * Operator-facing surface only: the body is flattened to one line and capped at 4096 characters\n * by schemas/notification.ts:73-83, and the model never sees it. Use session.admitPrompt to\n * reach the model.\n */\n notify(request: DoomNotificationRequest): void | Promise<void>;\n request(request: DoomHeadlessClientRequest, signal?: AbortSignal): Promise<unknown>;\n setStatus(source: string, text: string | undefined): void;\n appendComposerText?(text: string): void;\n}\n\nexport interface DoomHeadlessModelSettings {\n model?: { provider: string; id: string };\n thinkingLevel: ThinkingLevel;\n}\n\nexport interface DoomHeadlessSession {\n readModelSettings?(): Promise<DoomHeadlessModelSettings>;\n setModelSettings?(settings: Partial<DoomHeadlessModelSettings>): Promise<void>;\n /** Query durable history on demand; hosts do not retain the entire transcript. */\n entries(query?: {\n type?: 'custom' | 'message';\n customType?: string;\n limit?: number;\n }): readonly Record<string, unknown>[] | Promise<readonly Record<string, unknown>[]>;\n appendCustomEntry(type: string, data: unknown): Promise<void>;\n /**\n * Delivers and awaits the turn. 'steer' and 'followUp' only enqueue into a running turn; they do\n * NOT wake an idle agent.\n */\n prompt(text: string, delivery?: 'prompt' | 'steer' | 'followUp'): Promise<void>;\n /**\n * Resolves when accepted by the session, before the agent turn settles. Here 'steer' describes\n * how to deliver IF a turn is already running; an idle agent is woken either way, matching Pi's\n * ExtensionAPI.sendMessage({ triggerTurn: true, deliverAs: 'steer' }).\n */\n admitPrompt?(text: string, delivery?: 'prompt' | 'steer' | 'followUp'): Promise<void>;\n abort(): Promise<void>;\n compact(instructions?: string): Promise<void>;\n activity(): Promise<{ hasPendingMessages: boolean; isIdle: boolean }>;\n /** Renames the durable session and publishes the updated title to presentation clients. */\n setName?(name: string): Promise<void>;\n /** Capture the current persisted branch for an in-process child fork. */\n forkSource?(): Promise<{ kind: 'v4-fork'; sessionFile: string; branch: string; entryId?: string }>;\n}\n\n/** A grant-filtered skill view available only during one remote MCP tool invocation. */\nexport interface DoomMcpSkillAccess {\n list(): Promise<readonly { name: string; description: string }[]>;\n read(name: string): Promise<string>;\n}\n\n/** A stateless auxiliary request. These tools are private to this request, never registered on the agent. */\nexport interface DoomHeadlessToolCompletionRequest {\n systemPrompt: string;\n input: string;\n maxTokens: number;\n cacheRetention?: 'none' | 'short' | 'long';\n signal?: AbortSignal;\n tools: readonly Pick<DoomHeadlessTool, 'name' | 'description' | 'parameters'>[];\n}\n\nexport interface DoomHeadlessToolCompletionResult {\n toolCalls: readonly { id: string; name: string; arguments: unknown }[];\n usage: { totalTokens: number };\n}\n\nexport interface DoomHeadlessExecutionContext {\n readonly cwd: string;\n /** Admitted configuration root, distinct from the tool execution directory. */\n readonly repoRoot: string;\n readonly sessionId: string;\n /** Immutable session configuration. Facets must not read or mutate process.env. */\n readonly environment: Readonly<Record<string, string | undefined>>;\n readonly client: DoomHeadlessClient;\n readonly session: DoomHeadlessSession;\n readonly model?: { provider: string; id: string };\n readonly textCompletion?: {\n available(reference: string): boolean;\n complete(\n reference: string,\n request: {\n systemPrompt: string;\n input: string;\n maxTokens: number;\n cacheRetention?: 'none' | 'short' | 'long';\n signal?: AbortSignal;\n },\n ): Promise<string>;\n };\n readonly toolCompletion?: {\n complete(reference: string, request: DoomHeadlessToolCompletionRequest): Promise<DoomHeadlessToolCompletionResult>;\n };\n readonly selection: DoomHeadlessSelection;\n /** Present only while an authenticated remote MCP tool is running. */\n readonly mcpSkills?: DoomMcpSkillAccess;\n shutdown(): void;\n}\n\n/** Conditions narrow an eligible owner's contributions; they can never restore a disabled owner. */\nexport interface DoomHeadlessCondition {\n state?: Readonly<Record<string, string>>;\n attribution?: { kind: 'minor' | 'domain'; mode: string; label?: string };\n domain?: string;\n}\n\nexport interface DoomHeadlessToolRestriction {\n when?: DoomHeadlessCondition;\n /** Optional capability ceiling. An empty list disables every tool. */\n allowedTools?: readonly string[];\n /**\n * Narrow exclusions without disabling unrelated tools. Exclude a tool your package owns, or a\n * specific foreign tool the mode exists to withdraw. Never enumerate an allowlist here.\n */\n excludedTools?: readonly string[];\n}\n\nexport interface DoomHeadlessTool<TParameters extends TSchema = TSchema> {\n when?: DoomHeadlessCondition;\n name: string;\n label?: string;\n description: string;\n parameters: TParameters;\n annotations?: Tool['annotations'];\n outputSchema?: Tool['outputSchema'];\n /** Remote presentation only. Component tool access must be explicitly opted into. */\n _meta?: Tool['_meta'] & {\n ui?: {\n resourceUri?: string;\n visibility?: ('model' | 'app')[];\n };\n };\n promptSnippet?: string;\n promptGuidelines?: readonly string[];\n executionMode?: 'parallel' | 'serial';\n execute(\n toolCallId: string,\n parameters: Static<TParameters>,\n signal: AbortSignal | undefined,\n onUpdate: ((result: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ): Promise<DoomHeadlessToolResult>;\n}\n\n/**\n * One package contribution to the session's resource surface.\n *\n * `kind` decides what the model is billed for, and the difference is large:\n * - `context` is EAGER. `read()` runs on every prompt build and the text is pasted\n * into the system prompt verbatim. Reserve it for small live session state that\n * exists nowhere on disk. Never use it for a file the package ships: a README\n * registered this way costs its full length on every single turn.\n * - `skill` is LAZY. Only `name`, `description` and `path` reach the prompt; the\n * agent reads the file itself when the description matches the task.\n * - `prompt` is inert until the user invokes it by name.\n *\n * The rule in one line: if it is a file the package ships, it is a `skill`.\n */\nexport interface DoomHeadlessResource {\n when?: DoomHeadlessCondition;\n name: string;\n /** Model-visible selection signal. A `skill` without one tells the agent nothing. */\n description?: string;\n /** Absolute path of the file `read()` returns, so an advertised skill is one the agent can open. */\n path?: string;\n kind: 'prompt' | 'skill' | 'context';\n read(context: DoomHeadlessExecutionContext): string | Promise<string>;\n}\n\nexport interface DoomHeadlessCommand {\n when?: DoomHeadlessCondition;\n name: string;\n description: string;\n execute(args: string, context: DoomHeadlessExecutionContext): void | Promise<void>;\n}\n\nexport type DoomHeadlessEventName =\n | 'session_start'\n | 'session_shutdown'\n | 'session_tree'\n | 'before_agent_start'\n | 'agent_start'\n | 'agent_settled'\n | 'turn_start'\n | 'turn_end'\n | 'context'\n | 'before_provider_request'\n | 'message_start'\n | 'message_update'\n | 'message_end'\n | 'tool_call'\n | 'tool_execution_start'\n | 'tool_execution_update'\n | 'tool_execution_end'\n | 'tool_result'\n | 'model_select'\n | 'session_before_compact'\n | 'session_compact';\n\nexport type DoomHeadlessHookEvent<E extends DoomHeadlessEventName> = E extends 'before_provider_request'\n ? { lane: string; runId: string; model: { provider: string; id: string }; payload: unknown }\n : E extends 'session_before_compact'\n ? { reason: string; preparation: unknown; instructions?: string; customInstructions?: string }\n : E extends 'model_select'\n ? { model: { provider: string; id: string } }\n : Readonly<Record<string, unknown>>;\n\nexport interface DoomHeadlessCompactionResult {\n summary: string;\n tokensBefore: number;\n retainedTail: Record<string, unknown>[];\n usage?: Record<string, unknown>;\n details?: unknown;\n}\n\nexport type DoomHeadlessHookResult<E extends DoomHeadlessEventName> = E extends 'before_provider_request'\n ? { payload: unknown } | void\n : E extends 'session_before_compact'\n ? { cancel?: boolean; decline?: boolean; compaction?: DoomHeadlessCompactionResult } | void\n : E extends 'context'\n ? { messages?: unknown[]; systemPrompt?: string } | void\n : E extends 'before_agent_start'\n ? { systemPrompt?: string } | void\n : E extends 'tool_call'\n ? { args?: Record<string, unknown>; block?: { reason: string; terminate?: boolean } } | void\n : E extends 'tool_result'\n ? {\n content?: DoomHeadlessContent[];\n details?: unknown;\n isError?: boolean;\n usage?: Record<string, unknown>;\n terminate?: boolean;\n } | void\n : unknown;\n\nexport type DoomHeadlessHook<E extends DoomHeadlessEventName = DoomHeadlessEventName> = E extends DoomHeadlessEventName\n ? {\n when?: DoomHeadlessCondition;\n event: E;\n handle(\n event: Readonly<DoomHeadlessHookEvent<E>>,\n context: DoomHeadlessExecutionContext,\n ): DoomHeadlessHookResult<E> | Promise<DoomHeadlessHookResult<E>>;\n }\n : never;\n\n/** A retained facet can register activity without starting disabled watchers or services. */\nexport interface DoomHeadlessActivity {\n when?: DoomHeadlessCondition;\n name: string;\n start(context: DoomHeadlessExecutionContext): (() => void | Promise<void>) | Promise<() => void | Promise<void>>;\n}\n\n/** Read-only metadata for the currently applied session, never executable declarations or resource bodies. */\nexport interface DoomHeadlessCapability {\n readonly source: string;\n readonly name: string;\n readonly kind: 'tool' | 'skill';\n readonly when?: DoomHeadlessCondition;\n readonly active: boolean;\n readonly discoverable: boolean;\n readonly reason?: 'inactive' | 'restricted' | 'shadowed' | 'unavailable' | 'not-discoverable';\n}\n\nexport interface DoomHeadlessCapabilitySnapshot {\n readonly revision: number;\n readonly ready: boolean;\n readonly capabilities: readonly DoomHeadlessCapability[];\n}\n\n/** Registrations inherit package ownership from the caller's Cordis facet. */\nexport interface DoomHeadlessHostService {\n readonly context: DoomHeadlessExecutionContext;\n changeSelection(change: DoomHeadlessSelectionChange): Promise<void>;\n assertActive(source?: string): void;\n subscribeSelection(listener: (selection: DoomHeadlessSelection) => void | Promise<void>): () => void;\n /** Inspect only a coherently applied generation. A transition returns ready=false and no partial inventory. */\n inspectCapabilities(): DoomHeadlessCapabilitySnapshot;\n registerToolRestriction(restriction: DoomHeadlessToolRestriction): DoomHeadlessRegistration;\n registerTool<TParameters extends TSchema>(tool: DoomHeadlessTool<TParameters>): DoomHeadlessRegistration;\n registerResource(resource: DoomHeadlessResource): DoomHeadlessRegistration;\n registerCommand(command: DoomHeadlessCommand): DoomHeadlessRegistration;\n registerHook(hook: DoomHeadlessHook): DoomHeadlessRegistration;\n registerActivity(activity: DoomHeadlessActivity): DoomHeadlessRegistration;\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n 'doom/headless-host': DoomHeadlessHostService;\n }\n}\n\nexport function readDoomHeadlessHost(context: Context): DoomHeadlessHostService | undefined {\n return context.get(DOOM_HEADLESS_HOST_SERVICE) as DoomHeadlessHostService | undefined;\n}\n\nexport function requireDoomHeadlessHost(context: Context): DoomHeadlessHostService {\n const host = readDoomHeadlessHost(context);\n if (!host) throw new Error('This contribution requires the Doom headless session host.');\n return host;\n}\n"],"mappings":";;AASA,MAAa,6BAA6B;AAC1C,MAAa,sBAAsB,OAAO,IAAI,qBAAqB;AAkBnE,SAAgB,sBAAsB,SAAqD;CACzF,OAAQ,QAAwE;AAClF;AAgUA,SAAgB,qBAAqB,SAAuD;CAC1F,OAAO,QAAQ,IAAI,0BAA0B;AAC/C;AAEA,SAAgB,wBAAwB,SAA2C;CACjF,MAAM,OAAO,qBAAqB,OAAO;CACzC,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,4DAA4D;CACvF,OAAO;AACT"}
@@ -109,6 +109,8 @@ export interface DoomHeadlessSession {
109
109
  hasPendingMessages: boolean;
110
110
  isIdle: boolean;
111
111
  }>;
112
+ /** Renames the durable session and publishes the updated title to presentation clients. */
113
+ setName?(name: string): Promise<void>;
112
114
  /** Capture the current persisted branch for an in-process child fork. */
113
115
  forkSource?(): Promise<{
114
116
  kind: 'v4-fork';
@@ -1 +1 @@
1
- {"version":3,"file":"headless.d.mts","names":[],"sources":["../../../src/schemas/headless.ts"],"mappings":"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"}
1
+ {"version":3,"file":"headless.d.mts","names":[],"sources":["../../../src/schemas/headless.ts"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"headless.mjs","names":[],"sources":["../../../src/schemas/headless.ts"],"sourcesContent":["import type { Context } from '@deepseek-ai/cordis';\nimport type { ThinkingLevel } from '@earendil-works/pi-agent-core';\nimport type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';\nimport type { Static, TSchema } from 'typebox';\n\nimport type { DoomNotificationRequest } from './notification';\nimport type { DoomServerBundleEntry } from './serverBundle';\n\n/** Session-only contributions. This is not a Pi ExtensionAPI or a TUI adapter. */\nexport const DOOM_HEADLESS_HOST_SERVICE = 'doom/headless-host';\nexport const DOOM_HEADLESS_OWNER = Symbol.for('doom/headless-owner');\n\nexport interface DoomHeadlessSelection {\n readonly majorMode: string;\n readonly activeLayers: readonly string[];\n readonly domains: readonly string[];\n readonly profile?: string;\n /** Feature-owned activation sets, keyed by the contributing capability. */\n readonly state?: Readonly<Record<string, readonly string[]>>;\n}\n\n/** One package-owned selection transition. Server facets change exactly one axis per operation. */\nexport type DoomHeadlessSelectionChange =\n | { readonly axis: 'majorMode'; readonly majorMode: string }\n | { readonly axis: 'domains'; readonly domains: readonly string[] }\n | { readonly axis: 'profile'; readonly profile?: string }\n | { readonly axis: 'state'; readonly key: string; readonly values: readonly string[] };\n\nexport function readDoomHeadlessOwner(context: Context): DoomServerBundleEntry | undefined {\n return (context as Context & { [DOOM_HEADLESS_OWNER]?: DoomServerBundleEntry })[DOOM_HEADLESS_OWNER];\n}\n\nexport interface DoomHeadlessRegistration {\n dispose(): void;\n}\n\nexport type DoomHeadlessContent = { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string };\n\nexport interface DoomHeadlessToolResult {\n content: DoomHeadlessContent[];\n /** Public MCP result data. Internal renderer details are not a remote result contract. */\n structuredContent?: Record<string, unknown>;\n /** Component-only MCP metadata, never inferred from internal renderer details. */\n _meta?: CallToolResult['_meta'];\n details?: unknown;\n isError?: boolean;\n}\n\nexport interface DoomHeadlessClientRequest {\n kind: 'confirm' | 'select' | 'input';\n title: string;\n message?: string;\n options?: readonly { label: string; description?: string; value: string }[];\n initialValue?: string;\n multiline?: boolean;\n}\n\nexport interface DoomHeadlessClient {\n /**\n * Operator-facing surface only: the body is flattened to one line and capped at 4096 characters\n * by schemas/notification.ts:73-83, and the model never sees it. Use session.admitPrompt to\n * reach the model.\n */\n notify(request: DoomNotificationRequest): void | Promise<void>;\n request(request: DoomHeadlessClientRequest, signal?: AbortSignal): Promise<unknown>;\n setStatus(source: string, text: string | undefined): void;\n appendComposerText?(text: string): void;\n}\n\nexport interface DoomHeadlessModelSettings {\n model?: { provider: string; id: string };\n thinkingLevel: ThinkingLevel;\n}\n\nexport interface DoomHeadlessSession {\n readModelSettings?(): Promise<DoomHeadlessModelSettings>;\n setModelSettings?(settings: Partial<DoomHeadlessModelSettings>): Promise<void>;\n /** Query durable history on demand; hosts do not retain the entire transcript. */\n entries(query?: {\n type?: 'custom' | 'message';\n customType?: string;\n limit?: number;\n }): readonly Record<string, unknown>[] | Promise<readonly Record<string, unknown>[]>;\n appendCustomEntry(type: string, data: unknown): Promise<void>;\n /**\n * Delivers and awaits the turn. 'steer' and 'followUp' only enqueue into a running turn; they do\n * NOT wake an idle agent.\n */\n prompt(text: string, delivery?: 'prompt' | 'steer' | 'followUp'): Promise<void>;\n /**\n * Resolves when accepted by the session, before the agent turn settles. Here 'steer' describes\n * how to deliver IF a turn is already running; an idle agent is woken either way, matching Pi's\n * ExtensionAPI.sendMessage({ triggerTurn: true, deliverAs: 'steer' }).\n */\n admitPrompt?(text: string, delivery?: 'prompt' | 'steer' | 'followUp'): Promise<void>;\n abort(): Promise<void>;\n compact(instructions?: string): Promise<void>;\n activity(): Promise<{ hasPendingMessages: boolean; isIdle: boolean }>;\n /** Capture the current persisted branch for an in-process child fork. */\n forkSource?(): Promise<{ kind: 'v4-fork'; sessionFile: string; branch: string; entryId?: string }>;\n}\n\n/** A grant-filtered skill view available only during one remote MCP tool invocation. */\nexport interface DoomMcpSkillAccess {\n list(): Promise<readonly { name: string; description: string }[]>;\n read(name: string): Promise<string>;\n}\n\n/** A stateless auxiliary request. These tools are private to this request, never registered on the agent. */\nexport interface DoomHeadlessToolCompletionRequest {\n systemPrompt: string;\n input: string;\n maxTokens: number;\n cacheRetention?: 'none' | 'short' | 'long';\n signal?: AbortSignal;\n tools: readonly Pick<DoomHeadlessTool, 'name' | 'description' | 'parameters'>[];\n}\n\nexport interface DoomHeadlessToolCompletionResult {\n toolCalls: readonly { id: string; name: string; arguments: unknown }[];\n usage: { totalTokens: number };\n}\n\nexport interface DoomHeadlessExecutionContext {\n readonly cwd: string;\n /** Admitted configuration root, distinct from the tool execution directory. */\n readonly repoRoot: string;\n readonly sessionId: string;\n /** Immutable session configuration. Facets must not read or mutate process.env. */\n readonly environment: Readonly<Record<string, string | undefined>>;\n readonly client: DoomHeadlessClient;\n readonly session: DoomHeadlessSession;\n readonly model?: { provider: string; id: string };\n readonly textCompletion?: {\n available(reference: string): boolean;\n complete(\n reference: string,\n request: {\n systemPrompt: string;\n input: string;\n maxTokens: number;\n cacheRetention?: 'none' | 'short' | 'long';\n signal?: AbortSignal;\n },\n ): Promise<string>;\n };\n readonly toolCompletion?: {\n complete(reference: string, request: DoomHeadlessToolCompletionRequest): Promise<DoomHeadlessToolCompletionResult>;\n };\n readonly selection: DoomHeadlessSelection;\n /** Present only while an authenticated remote MCP tool is running. */\n readonly mcpSkills?: DoomMcpSkillAccess;\n shutdown(): void;\n}\n\n/** Conditions narrow an eligible owner's contributions; they can never restore a disabled owner. */\nexport interface DoomHeadlessCondition {\n state?: Readonly<Record<string, string>>;\n attribution?: { kind: 'minor' | 'domain'; mode: string; label?: string };\n domain?: string;\n}\n\nexport interface DoomHeadlessToolRestriction {\n when?: DoomHeadlessCondition;\n /** Optional capability ceiling. An empty list disables every tool. */\n allowedTools?: readonly string[];\n /**\n * Narrow exclusions without disabling unrelated tools. Exclude a tool your package owns, or a\n * specific foreign tool the mode exists to withdraw. Never enumerate an allowlist here.\n */\n excludedTools?: readonly string[];\n}\n\nexport interface DoomHeadlessTool<TParameters extends TSchema = TSchema> {\n when?: DoomHeadlessCondition;\n name: string;\n label?: string;\n description: string;\n parameters: TParameters;\n annotations?: Tool['annotations'];\n outputSchema?: Tool['outputSchema'];\n /** Remote presentation only. Component tool access must be explicitly opted into. */\n _meta?: Tool['_meta'] & {\n ui?: {\n resourceUri?: string;\n visibility?: ('model' | 'app')[];\n };\n };\n promptSnippet?: string;\n promptGuidelines?: readonly string[];\n executionMode?: 'parallel' | 'serial';\n execute(\n toolCallId: string,\n parameters: Static<TParameters>,\n signal: AbortSignal | undefined,\n onUpdate: ((result: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ): Promise<DoomHeadlessToolResult>;\n}\n\n/**\n * One package contribution to the session's resource surface.\n *\n * `kind` decides what the model is billed for, and the difference is large:\n * - `context` is EAGER. `read()` runs on every prompt build and the text is pasted\n * into the system prompt verbatim. Reserve it for small live session state that\n * exists nowhere on disk. Never use it for a file the package ships: a README\n * registered this way costs its full length on every single turn.\n * - `skill` is LAZY. Only `name`, `description` and `path` reach the prompt; the\n * agent reads the file itself when the description matches the task.\n * - `prompt` is inert until the user invokes it by name.\n *\n * The rule in one line: if it is a file the package ships, it is a `skill`.\n */\nexport interface DoomHeadlessResource {\n when?: DoomHeadlessCondition;\n name: string;\n /** Model-visible selection signal. A `skill` without one tells the agent nothing. */\n description?: string;\n /** Absolute path of the file `read()` returns, so an advertised skill is one the agent can open. */\n path?: string;\n kind: 'prompt' | 'skill' | 'context';\n read(context: DoomHeadlessExecutionContext): string | Promise<string>;\n}\n\nexport interface DoomHeadlessCommand {\n when?: DoomHeadlessCondition;\n name: string;\n description: string;\n execute(args: string, context: DoomHeadlessExecutionContext): void | Promise<void>;\n}\n\nexport type DoomHeadlessEventName =\n | 'session_start'\n | 'session_shutdown'\n | 'session_tree'\n | 'before_agent_start'\n | 'agent_start'\n | 'agent_settled'\n | 'turn_start'\n | 'turn_end'\n | 'context'\n | 'before_provider_request'\n | 'message_start'\n | 'message_update'\n | 'message_end'\n | 'tool_call'\n | 'tool_execution_start'\n | 'tool_execution_update'\n | 'tool_execution_end'\n | 'tool_result'\n | 'model_select'\n | 'session_before_compact'\n | 'session_compact';\n\nexport type DoomHeadlessHookEvent<E extends DoomHeadlessEventName> = E extends 'before_provider_request'\n ? { lane: string; runId: string; model: { provider: string; id: string }; payload: unknown }\n : E extends 'session_before_compact'\n ? { reason: string; preparation: unknown; instructions?: string; customInstructions?: string }\n : E extends 'model_select'\n ? { model: { provider: string; id: string } }\n : Readonly<Record<string, unknown>>;\n\nexport interface DoomHeadlessCompactionResult {\n summary: string;\n tokensBefore: number;\n retainedTail: Record<string, unknown>[];\n usage?: Record<string, unknown>;\n details?: unknown;\n}\n\nexport type DoomHeadlessHookResult<E extends DoomHeadlessEventName> = E extends 'before_provider_request'\n ? { payload: unknown } | void\n : E extends 'session_before_compact'\n ? { cancel?: boolean; decline?: boolean; compaction?: DoomHeadlessCompactionResult } | void\n : E extends 'context'\n ? { messages?: unknown[]; systemPrompt?: string } | void\n : E extends 'before_agent_start'\n ? { systemPrompt?: string } | void\n : E extends 'tool_call'\n ? { args?: Record<string, unknown>; block?: { reason: string; terminate?: boolean } } | void\n : E extends 'tool_result'\n ? {\n content?: DoomHeadlessContent[];\n details?: unknown;\n isError?: boolean;\n usage?: Record<string, unknown>;\n terminate?: boolean;\n } | void\n : unknown;\n\nexport type DoomHeadlessHook<E extends DoomHeadlessEventName = DoomHeadlessEventName> = E extends DoomHeadlessEventName\n ? {\n when?: DoomHeadlessCondition;\n event: E;\n handle(\n event: Readonly<DoomHeadlessHookEvent<E>>,\n context: DoomHeadlessExecutionContext,\n ): DoomHeadlessHookResult<E> | Promise<DoomHeadlessHookResult<E>>;\n }\n : never;\n\n/** A retained facet can register activity without starting disabled watchers or services. */\nexport interface DoomHeadlessActivity {\n when?: DoomHeadlessCondition;\n name: string;\n start(context: DoomHeadlessExecutionContext): (() => void | Promise<void>) | Promise<() => void | Promise<void>>;\n}\n\n/** Read-only metadata for the currently applied session, never executable declarations or resource bodies. */\nexport interface DoomHeadlessCapability {\n readonly source: string;\n readonly name: string;\n readonly kind: 'tool' | 'skill';\n readonly when?: DoomHeadlessCondition;\n readonly active: boolean;\n readonly discoverable: boolean;\n readonly reason?: 'inactive' | 'restricted' | 'shadowed' | 'unavailable' | 'not-discoverable';\n}\n\nexport interface DoomHeadlessCapabilitySnapshot {\n readonly revision: number;\n readonly ready: boolean;\n readonly capabilities: readonly DoomHeadlessCapability[];\n}\n\n/** Registrations inherit package ownership from the caller's Cordis facet. */\nexport interface DoomHeadlessHostService {\n readonly context: DoomHeadlessExecutionContext;\n changeSelection(change: DoomHeadlessSelectionChange): Promise<void>;\n assertActive(source?: string): void;\n subscribeSelection(listener: (selection: DoomHeadlessSelection) => void | Promise<void>): () => void;\n /** Inspect only a coherently applied generation. A transition returns ready=false and no partial inventory. */\n inspectCapabilities(): DoomHeadlessCapabilitySnapshot;\n registerToolRestriction(restriction: DoomHeadlessToolRestriction): DoomHeadlessRegistration;\n registerTool<TParameters extends TSchema>(tool: DoomHeadlessTool<TParameters>): DoomHeadlessRegistration;\n registerResource(resource: DoomHeadlessResource): DoomHeadlessRegistration;\n registerCommand(command: DoomHeadlessCommand): DoomHeadlessRegistration;\n registerHook(hook: DoomHeadlessHook): DoomHeadlessRegistration;\n registerActivity(activity: DoomHeadlessActivity): DoomHeadlessRegistration;\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n 'doom/headless-host': DoomHeadlessHostService;\n }\n}\n\nexport function readDoomHeadlessHost(context: Context): DoomHeadlessHostService | undefined {\n return context.get(DOOM_HEADLESS_HOST_SERVICE) as DoomHeadlessHostService | undefined;\n}\n\nexport function requireDoomHeadlessHost(context: Context): DoomHeadlessHostService {\n const host = readDoomHeadlessHost(context);\n if (!host) throw new Error('This contribution requires the Doom headless session host.');\n return host;\n}\n"],"mappings":";;AASA,MAAa,6BAA6B;AAC1C,MAAa,sBAAsB,OAAO,IAAI,qBAAqB;AAkBnE,SAAgB,sBAAsB,SAAqD;CACzF,OAAQ,QAAwE;AAClF;AA8TA,SAAgB,qBAAqB,SAAuD;CAC1F,OAAO,QAAQ,IAAI,0BAA0B;AAC/C;AAEA,SAAgB,wBAAwB,SAA2C;CACjF,MAAM,OAAO,qBAAqB,OAAO;CACzC,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,4DAA4D;CACvF,OAAO;AACT"}
1
+ {"version":3,"file":"headless.mjs","names":[],"sources":["../../../src/schemas/headless.ts"],"sourcesContent":["import type { Context } from '@deepseek-ai/cordis';\nimport type { ThinkingLevel } from '@earendil-works/pi-agent-core';\nimport type { CallToolResult, Tool } from '@modelcontextprotocol/sdk/types.js';\nimport type { Static, TSchema } from 'typebox';\n\nimport type { DoomNotificationRequest } from './notification';\nimport type { DoomServerBundleEntry } from './serverBundle';\n\n/** Session-only contributions. This is not a Pi ExtensionAPI or a TUI adapter. */\nexport const DOOM_HEADLESS_HOST_SERVICE = 'doom/headless-host';\nexport const DOOM_HEADLESS_OWNER = Symbol.for('doom/headless-owner');\n\nexport interface DoomHeadlessSelection {\n readonly majorMode: string;\n readonly activeLayers: readonly string[];\n readonly domains: readonly string[];\n readonly profile?: string;\n /** Feature-owned activation sets, keyed by the contributing capability. */\n readonly state?: Readonly<Record<string, readonly string[]>>;\n}\n\n/** One package-owned selection transition. Server facets change exactly one axis per operation. */\nexport type DoomHeadlessSelectionChange =\n | { readonly axis: 'majorMode'; readonly majorMode: string }\n | { readonly axis: 'domains'; readonly domains: readonly string[] }\n | { readonly axis: 'profile'; readonly profile?: string }\n | { readonly axis: 'state'; readonly key: string; readonly values: readonly string[] };\n\nexport function readDoomHeadlessOwner(context: Context): DoomServerBundleEntry | undefined {\n return (context as Context & { [DOOM_HEADLESS_OWNER]?: DoomServerBundleEntry })[DOOM_HEADLESS_OWNER];\n}\n\nexport interface DoomHeadlessRegistration {\n dispose(): void;\n}\n\nexport type DoomHeadlessContent = { type: 'text'; text: string } | { type: 'image'; data: string; mimeType: string };\n\nexport interface DoomHeadlessToolResult {\n content: DoomHeadlessContent[];\n /** Public MCP result data. Internal renderer details are not a remote result contract. */\n structuredContent?: Record<string, unknown>;\n /** Component-only MCP metadata, never inferred from internal renderer details. */\n _meta?: CallToolResult['_meta'];\n details?: unknown;\n isError?: boolean;\n}\n\nexport interface DoomHeadlessClientRequest {\n kind: 'confirm' | 'select' | 'input';\n title: string;\n message?: string;\n options?: readonly { label: string; description?: string; value: string }[];\n initialValue?: string;\n multiline?: boolean;\n}\n\nexport interface DoomHeadlessClient {\n /**\n * Operator-facing surface only: the body is flattened to one line and capped at 4096 characters\n * by schemas/notification.ts:73-83, and the model never sees it. Use session.admitPrompt to\n * reach the model.\n */\n notify(request: DoomNotificationRequest): void | Promise<void>;\n request(request: DoomHeadlessClientRequest, signal?: AbortSignal): Promise<unknown>;\n setStatus(source: string, text: string | undefined): void;\n appendComposerText?(text: string): void;\n}\n\nexport interface DoomHeadlessModelSettings {\n model?: { provider: string; id: string };\n thinkingLevel: ThinkingLevel;\n}\n\nexport interface DoomHeadlessSession {\n readModelSettings?(): Promise<DoomHeadlessModelSettings>;\n setModelSettings?(settings: Partial<DoomHeadlessModelSettings>): Promise<void>;\n /** Query durable history on demand; hosts do not retain the entire transcript. */\n entries(query?: {\n type?: 'custom' | 'message';\n customType?: string;\n limit?: number;\n }): readonly Record<string, unknown>[] | Promise<readonly Record<string, unknown>[]>;\n appendCustomEntry(type: string, data: unknown): Promise<void>;\n /**\n * Delivers and awaits the turn. 'steer' and 'followUp' only enqueue into a running turn; they do\n * NOT wake an idle agent.\n */\n prompt(text: string, delivery?: 'prompt' | 'steer' | 'followUp'): Promise<void>;\n /**\n * Resolves when accepted by the session, before the agent turn settles. Here 'steer' describes\n * how to deliver IF a turn is already running; an idle agent is woken either way, matching Pi's\n * ExtensionAPI.sendMessage({ triggerTurn: true, deliverAs: 'steer' }).\n */\n admitPrompt?(text: string, delivery?: 'prompt' | 'steer' | 'followUp'): Promise<void>;\n abort(): Promise<void>;\n compact(instructions?: string): Promise<void>;\n activity(): Promise<{ hasPendingMessages: boolean; isIdle: boolean }>;\n /** Renames the durable session and publishes the updated title to presentation clients. */\n setName?(name: string): Promise<void>;\n /** Capture the current persisted branch for an in-process child fork. */\n forkSource?(): Promise<{ kind: 'v4-fork'; sessionFile: string; branch: string; entryId?: string }>;\n}\n\n/** A grant-filtered skill view available only during one remote MCP tool invocation. */\nexport interface DoomMcpSkillAccess {\n list(): Promise<readonly { name: string; description: string }[]>;\n read(name: string): Promise<string>;\n}\n\n/** A stateless auxiliary request. These tools are private to this request, never registered on the agent. */\nexport interface DoomHeadlessToolCompletionRequest {\n systemPrompt: string;\n input: string;\n maxTokens: number;\n cacheRetention?: 'none' | 'short' | 'long';\n signal?: AbortSignal;\n tools: readonly Pick<DoomHeadlessTool, 'name' | 'description' | 'parameters'>[];\n}\n\nexport interface DoomHeadlessToolCompletionResult {\n toolCalls: readonly { id: string; name: string; arguments: unknown }[];\n usage: { totalTokens: number };\n}\n\nexport interface DoomHeadlessExecutionContext {\n readonly cwd: string;\n /** Admitted configuration root, distinct from the tool execution directory. */\n readonly repoRoot: string;\n readonly sessionId: string;\n /** Immutable session configuration. Facets must not read or mutate process.env. */\n readonly environment: Readonly<Record<string, string | undefined>>;\n readonly client: DoomHeadlessClient;\n readonly session: DoomHeadlessSession;\n readonly model?: { provider: string; id: string };\n readonly textCompletion?: {\n available(reference: string): boolean;\n complete(\n reference: string,\n request: {\n systemPrompt: string;\n input: string;\n maxTokens: number;\n cacheRetention?: 'none' | 'short' | 'long';\n signal?: AbortSignal;\n },\n ): Promise<string>;\n };\n readonly toolCompletion?: {\n complete(reference: string, request: DoomHeadlessToolCompletionRequest): Promise<DoomHeadlessToolCompletionResult>;\n };\n readonly selection: DoomHeadlessSelection;\n /** Present only while an authenticated remote MCP tool is running. */\n readonly mcpSkills?: DoomMcpSkillAccess;\n shutdown(): void;\n}\n\n/** Conditions narrow an eligible owner's contributions; they can never restore a disabled owner. */\nexport interface DoomHeadlessCondition {\n state?: Readonly<Record<string, string>>;\n attribution?: { kind: 'minor' | 'domain'; mode: string; label?: string };\n domain?: string;\n}\n\nexport interface DoomHeadlessToolRestriction {\n when?: DoomHeadlessCondition;\n /** Optional capability ceiling. An empty list disables every tool. */\n allowedTools?: readonly string[];\n /**\n * Narrow exclusions without disabling unrelated tools. Exclude a tool your package owns, or a\n * specific foreign tool the mode exists to withdraw. Never enumerate an allowlist here.\n */\n excludedTools?: readonly string[];\n}\n\nexport interface DoomHeadlessTool<TParameters extends TSchema = TSchema> {\n when?: DoomHeadlessCondition;\n name: string;\n label?: string;\n description: string;\n parameters: TParameters;\n annotations?: Tool['annotations'];\n outputSchema?: Tool['outputSchema'];\n /** Remote presentation only. Component tool access must be explicitly opted into. */\n _meta?: Tool['_meta'] & {\n ui?: {\n resourceUri?: string;\n visibility?: ('model' | 'app')[];\n };\n };\n promptSnippet?: string;\n promptGuidelines?: readonly string[];\n executionMode?: 'parallel' | 'serial';\n execute(\n toolCallId: string,\n parameters: Static<TParameters>,\n signal: AbortSignal | undefined,\n onUpdate: ((result: DoomHeadlessToolResult) => void) | undefined,\n context: DoomHeadlessExecutionContext,\n ): Promise<DoomHeadlessToolResult>;\n}\n\n/**\n * One package contribution to the session's resource surface.\n *\n * `kind` decides what the model is billed for, and the difference is large:\n * - `context` is EAGER. `read()` runs on every prompt build and the text is pasted\n * into the system prompt verbatim. Reserve it for small live session state that\n * exists nowhere on disk. Never use it for a file the package ships: a README\n * registered this way costs its full length on every single turn.\n * - `skill` is LAZY. Only `name`, `description` and `path` reach the prompt; the\n * agent reads the file itself when the description matches the task.\n * - `prompt` is inert until the user invokes it by name.\n *\n * The rule in one line: if it is a file the package ships, it is a `skill`.\n */\nexport interface DoomHeadlessResource {\n when?: DoomHeadlessCondition;\n name: string;\n /** Model-visible selection signal. A `skill` without one tells the agent nothing. */\n description?: string;\n /** Absolute path of the file `read()` returns, so an advertised skill is one the agent can open. */\n path?: string;\n kind: 'prompt' | 'skill' | 'context';\n read(context: DoomHeadlessExecutionContext): string | Promise<string>;\n}\n\nexport interface DoomHeadlessCommand {\n when?: DoomHeadlessCondition;\n name: string;\n description: string;\n execute(args: string, context: DoomHeadlessExecutionContext): void | Promise<void>;\n}\n\nexport type DoomHeadlessEventName =\n | 'session_start'\n | 'session_shutdown'\n | 'session_tree'\n | 'before_agent_start'\n | 'agent_start'\n | 'agent_settled'\n | 'turn_start'\n | 'turn_end'\n | 'context'\n | 'before_provider_request'\n | 'message_start'\n | 'message_update'\n | 'message_end'\n | 'tool_call'\n | 'tool_execution_start'\n | 'tool_execution_update'\n | 'tool_execution_end'\n | 'tool_result'\n | 'model_select'\n | 'session_before_compact'\n | 'session_compact';\n\nexport type DoomHeadlessHookEvent<E extends DoomHeadlessEventName> = E extends 'before_provider_request'\n ? { lane: string; runId: string; model: { provider: string; id: string }; payload: unknown }\n : E extends 'session_before_compact'\n ? { reason: string; preparation: unknown; instructions?: string; customInstructions?: string }\n : E extends 'model_select'\n ? { model: { provider: string; id: string } }\n : Readonly<Record<string, unknown>>;\n\nexport interface DoomHeadlessCompactionResult {\n summary: string;\n tokensBefore: number;\n retainedTail: Record<string, unknown>[];\n usage?: Record<string, unknown>;\n details?: unknown;\n}\n\nexport type DoomHeadlessHookResult<E extends DoomHeadlessEventName> = E extends 'before_provider_request'\n ? { payload: unknown } | void\n : E extends 'session_before_compact'\n ? { cancel?: boolean; decline?: boolean; compaction?: DoomHeadlessCompactionResult } | void\n : E extends 'context'\n ? { messages?: unknown[]; systemPrompt?: string } | void\n : E extends 'before_agent_start'\n ? { systemPrompt?: string } | void\n : E extends 'tool_call'\n ? { args?: Record<string, unknown>; block?: { reason: string; terminate?: boolean } } | void\n : E extends 'tool_result'\n ? {\n content?: DoomHeadlessContent[];\n details?: unknown;\n isError?: boolean;\n usage?: Record<string, unknown>;\n terminate?: boolean;\n } | void\n : unknown;\n\nexport type DoomHeadlessHook<E extends DoomHeadlessEventName = DoomHeadlessEventName> = E extends DoomHeadlessEventName\n ? {\n when?: DoomHeadlessCondition;\n event: E;\n handle(\n event: Readonly<DoomHeadlessHookEvent<E>>,\n context: DoomHeadlessExecutionContext,\n ): DoomHeadlessHookResult<E> | Promise<DoomHeadlessHookResult<E>>;\n }\n : never;\n\n/** A retained facet can register activity without starting disabled watchers or services. */\nexport interface DoomHeadlessActivity {\n when?: DoomHeadlessCondition;\n name: string;\n start(context: DoomHeadlessExecutionContext): (() => void | Promise<void>) | Promise<() => void | Promise<void>>;\n}\n\n/** Read-only metadata for the currently applied session, never executable declarations or resource bodies. */\nexport interface DoomHeadlessCapability {\n readonly source: string;\n readonly name: string;\n readonly kind: 'tool' | 'skill';\n readonly when?: DoomHeadlessCondition;\n readonly active: boolean;\n readonly discoverable: boolean;\n readonly reason?: 'inactive' | 'restricted' | 'shadowed' | 'unavailable' | 'not-discoverable';\n}\n\nexport interface DoomHeadlessCapabilitySnapshot {\n readonly revision: number;\n readonly ready: boolean;\n readonly capabilities: readonly DoomHeadlessCapability[];\n}\n\n/** Registrations inherit package ownership from the caller's Cordis facet. */\nexport interface DoomHeadlessHostService {\n readonly context: DoomHeadlessExecutionContext;\n changeSelection(change: DoomHeadlessSelectionChange): Promise<void>;\n assertActive(source?: string): void;\n subscribeSelection(listener: (selection: DoomHeadlessSelection) => void | Promise<void>): () => void;\n /** Inspect only a coherently applied generation. A transition returns ready=false and no partial inventory. */\n inspectCapabilities(): DoomHeadlessCapabilitySnapshot;\n registerToolRestriction(restriction: DoomHeadlessToolRestriction): DoomHeadlessRegistration;\n registerTool<TParameters extends TSchema>(tool: DoomHeadlessTool<TParameters>): DoomHeadlessRegistration;\n registerResource(resource: DoomHeadlessResource): DoomHeadlessRegistration;\n registerCommand(command: DoomHeadlessCommand): DoomHeadlessRegistration;\n registerHook(hook: DoomHeadlessHook): DoomHeadlessRegistration;\n registerActivity(activity: DoomHeadlessActivity): DoomHeadlessRegistration;\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n 'doom/headless-host': DoomHeadlessHostService;\n }\n}\n\nexport function readDoomHeadlessHost(context: Context): DoomHeadlessHostService | undefined {\n return context.get(DOOM_HEADLESS_HOST_SERVICE) as DoomHeadlessHostService | undefined;\n}\n\nexport function requireDoomHeadlessHost(context: Context): DoomHeadlessHostService {\n const host = readDoomHeadlessHost(context);\n if (!host) throw new Error('This contribution requires the Doom headless session host.');\n return host;\n}\n"],"mappings":";;AASA,MAAa,6BAA6B;AAC1C,MAAa,sBAAsB,OAAO,IAAI,qBAAqB;AAkBnE,SAAgB,sBAAsB,SAAqD;CACzF,OAAQ,QAAwE;AAClF;AAgUA,SAAgB,qBAAqB,SAAuD;CAC1F,OAAO,QAAQ,IAAI,0BAA0B;AAC/C;AAEA,SAAgB,wBAAwB,SAA2C;CACjF,MAAM,OAAO,qBAAqB,OAAO;CACzC,IAAI,CAAC,MAAM,MAAM,IAAI,MAAM,4DAA4D;CACvF,OAAO;AACT"}
@@ -46,11 +46,14 @@ function grantedSurface(grant, surface) {
46
46
  uiResources: (snapshot.uiResources ?? []).filter((resource) => resourceUris.has(resource.uri))
47
47
  };
48
48
  }
49
- /** Creates a Bearer-only, stateless Streamable HTTP MCP request handler for session capabilities. */
49
+ /** Creates a stateless Streamable HTTP MCP request handler for session capabilities. */
50
50
  function createSessionMcpHttpHandler(options) {
51
51
  const audience = new URL(options.audience);
52
52
  if (audience.protocol !== "https:" || audience.username !== "" || audience.password !== "" || audience.search !== "" || audience.hash !== "") throw new Error("Session MCP audience must be an absolute HTTPS URL without credentials, a query, or a fragment.");
53
53
  const exactAudience = audience.href;
54
+ if (options.pathToken !== void 0 && !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/u.test(options.pathToken)) throw new Error("Session MCP path token must be a compact JWT.");
55
+ const exactResource = options.pathToken === void 0 ? exactAudience : `${exactAudience}/${options.pathToken}`;
56
+ const authenticate = options.pathToken === void 0 ? options.authorization.authenticateAccessToken : options.authorization.authenticateUrlToken;
54
57
  const operations = /* @__PURE__ */ new Map();
55
58
  const operationOwner = (grant, requestId) => JSON.stringify([
56
59
  grant.clientId,
@@ -60,16 +63,16 @@ function createSessionMcpHttpHandler(options) {
60
63
  requestId
61
64
  ]);
62
65
  return async (request) => {
63
- if (currentUrl(request) !== exactAudience) return jsonError(404, "MCP resource not found.");
64
- const token = bearerToken(request);
66
+ if (currentUrl(request) !== exactResource) return jsonError(404, "MCP resource not found.");
67
+ const token = options.pathToken ?? bearerToken(request);
65
68
  if (token === void 0) return jsonError(401, "A Bearer access token is required.", true, options.resourceMetadataUrl);
66
- const grant = options.authorization.authenticateAccessToken(token, exactAudience);
67
- if (grant === void 0) return jsonError(401, "The Bearer access token is invalid or expired.", true, options.resourceMetadataUrl);
69
+ const grant = authenticate(token, exactAudience);
70
+ if (grant === void 0) return options.pathToken === void 0 ? jsonError(401, "The Bearer access token is invalid or expired.", true, options.resourceMetadataUrl) : jsonError(401, "The signed MCP URL is invalid or revoked.");
68
71
  const authorizeOperation = async () => {
69
- const activeGrant = options.authorization.authenticateAccessToken(token, exactAudience);
72
+ const activeGrant = authenticate(token, exactAudience);
70
73
  if (activeGrant === void 0 || activeGrant.id !== grant.id) throw new _modelcontextprotocol_sdk_types_js.McpError(_modelcontextprotocol_sdk_types_js.ErrorCode.InvalidRequest, "The session grant is no longer active.");
71
74
  const target = await options.resolveSession(activeGrant.sessionId);
72
- const recheckedGrant = options.authorization.authenticateAccessToken(token, exactAudience);
75
+ const recheckedGrant = authenticate(token, exactAudience);
73
76
  if (recheckedGrant === void 0 || recheckedGrant.id !== activeGrant.id || target === void 0 || target.generation !== recheckedGrant.sessionGeneration) throw new _modelcontextprotocol_sdk_types_js.McpError(_modelcontextprotocol_sdk_types_js.ErrorCode.InvalidRequest, "The session grant is no longer active.");
74
77
  return {
75
78
  grant: recheckedGrant,
@@ -79,7 +82,7 @@ function createSessionMcpHttpHandler(options) {
79
82
  try {
80
83
  await authorizeOperation();
81
84
  } catch {
82
- return jsonError(401, "The session grant is no longer active.", true, options.resourceMetadataUrl);
85
+ return options.pathToken === void 0 ? jsonError(401, "The session grant is no longer active.", true, options.resourceMetadataUrl) : jsonError(401, "The signed MCP URL is invalid or revoked.");
83
86
  }
84
87
  const server = new _modelcontextprotocol_sdk_server_index_js.Server({
85
88
  name: options.serverName ?? "doompi-session",
@@ -278,7 +281,7 @@ function createSessionMcpHttpHandler(options) {
278
281
  clientId: grant.clientId,
279
282
  scopes: grant.scope === "session" ? ["session"] : [...grant.tools.map((name) => `tool:${name}`), ...grant.skills.map((name) => `skill:${name}`)],
280
283
  expiresAt: Math.floor(grant.expiresAt / 1e3),
281
- resource: new URL(exactAudience)
284
+ resource: new URL(exactResource)
282
285
  } });
283
286
  } catch {
284
287
  return jsonError(500, "Internal MCP server error.");
@@ -1 +1 @@
1
- {"version":3,"file":"sessionMcpHandler.cjs","names":["McpError","ErrorCode","Server","CancelledNotificationSchema","sessionMcpConversationDigest","ListToolsRequestSchema","CallToolRequestSchema","randomUUID","SessionMcpConversationError","isDeepStrictEqual","ListResourcesRequestSchema","ReadResourceRequestSchema","WebStandardStreamableHTTPServerTransport"],"sources":["../../../src/server/sessionMcpHandler.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { isDeepStrictEqual } from 'node:util';\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';\nimport {\n CallToolRequestSchema,\n CancelledNotificationSchema,\n ErrorCode,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n McpError,\n ReadResourceRequestSchema,\n type CallToolResult,\n type Tool,\n} from '@modelcontextprotocol/sdk/types.js';\n\nimport type { SessionMcpAccessGrant, SessionMcpAuthorizationService } from '../services/sessionMcpAuthorization';\nimport { sessionMcpConversationDigest, SessionMcpConversationError } from '../services/sessionMcpConversations';\nimport type { SessionToolSurface } from '../types/server/sessionToolSurface';\n\nexport interface SessionMcpTarget {\n readonly generation: number;\n readonly sessionId?: string;\n readonly toolSurface: SessionToolSurface;\n}\n\nexport interface SessionMcpHttpHandlerOptions {\n /** Exact HTTPS resource indicator minted into access grants. */\n readonly audience: string;\n readonly authorization: Pick<SessionMcpAuthorizationService, 'authenticateAccessToken'>;\n readonly onVerified?: (grant: SessionMcpAccessGrant) => void | Promise<void>;\n readonly resourceMetadataUrl?: string;\n readonly resolveSession: (sessionId: string) => SessionMcpTarget | undefined | Promise<SessionMcpTarget | undefined>;\n readonly resolveConversation?: (\n grant: SessionMcpAccessGrant,\n digest: string,\n reserve: boolean,\n signal?: AbortSignal,\n ) => SessionMcpTarget | Promise<SessionMcpTarget>;\n /** Only bounded, non-reversible correlation values are reported here. */\n readonly onNotice?: (message: string) => void;\n readonly serverName?: string;\n readonly serverVersion?: string;\n}\n\nexport type SessionMcpHttpHandler = (request: Request) => Promise<Response>;\n\nfunction jsonError(status: number, message: string, authenticate = false, resourceMetadataUrl?: string): Response {\n return Response.json(\n { jsonrpc: '2.0', error: { code: -32_000, message }, id: null },\n {\n status,\n headers: {\n 'cache-control': 'no-store',\n ...(authenticate\n ? {\n 'www-authenticate':\n resourceMetadataUrl === undefined ? 'Bearer' : `Bearer resource_metadata=\"${resourceMetadataUrl}\"`,\n }\n : {}),\n },\n },\n );\n}\n\nfunction bearerToken(request: Request): string | undefined {\n const authorization = request.headers.get('authorization');\n const match = authorization === null ? undefined : /^Bearer ([A-Za-z0-9_-]+)$/iu.exec(authorization);\n return match?.[1];\n}\n\nfunction currentUrl(request: Request): string | undefined {\n try {\n const url = new URL(request.url);\n return url.search === '' && url.hash === '' ? url.href : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction grantedSurface(grant: SessionMcpAccessGrant, surface: SessionToolSurface) {\n const snapshot = surface.readSurface();\n const toolNames = new Set(grant.tools);\n const skillNames = new Set(grant.skills);\n const tools = grant.scope === 'session' ? snapshot.tools : snapshot.tools.filter((tool) => toolNames.has(tool.name));\n const resourceUris = new Set(tools.map((tool) => tool._meta?.ui?.resourceUri));\n return {\n snapshot,\n tools,\n skills: grant.scope === 'session' ? snapshot.skills : snapshot.skills.filter((skill) => skillNames.has(skill.name)),\n uiResources: (snapshot.uiResources ?? []).filter((resource) => resourceUris.has(resource.uri)),\n };\n}\n\n/** Creates a Bearer-only, stateless Streamable HTTP MCP request handler for session capabilities. */\nexport function createSessionMcpHttpHandler(options: SessionMcpHttpHandlerOptions): SessionMcpHttpHandler {\n const audience = new URL(options.audience);\n if (\n audience.protocol !== 'https:' ||\n audience.username !== '' ||\n audience.password !== '' ||\n audience.search !== '' ||\n audience.hash !== ''\n ) {\n throw new Error('Session MCP audience must be an absolute HTTPS URL without credentials, a query, or a fragment.');\n }\n const exactAudience = audience.href;\n const operations = new Map<string, { controller: AbortController; owner: string; conversation?: string }>();\n const operationOwner = (grant: SessionMcpAccessGrant, requestId: string | number): string =>\n JSON.stringify([grant.clientId, grant.sessionId, grant.sessionGeneration, grant.id, requestId]);\n\n return async (request) => {\n if (currentUrl(request) !== exactAudience) return jsonError(404, 'MCP resource not found.');\n const token = bearerToken(request);\n if (token === undefined)\n return jsonError(401, 'A Bearer access token is required.', true, options.resourceMetadataUrl);\n const grant = options.authorization.authenticateAccessToken(token, exactAudience);\n if (grant === undefined)\n return jsonError(401, 'The Bearer access token is invalid or expired.', true, options.resourceMetadataUrl);\n const authorizeOperation = async (): Promise<{\n grant: SessionMcpAccessGrant;\n target: SessionMcpTarget;\n }> => {\n const activeGrant = options.authorization.authenticateAccessToken(token, exactAudience);\n if (activeGrant === undefined || activeGrant.id !== grant.id) {\n throw new McpError(ErrorCode.InvalidRequest, 'The session grant is no longer active.');\n }\n const target = await options.resolveSession(activeGrant.sessionId);\n const recheckedGrant = options.authorization.authenticateAccessToken(token, exactAudience);\n if (\n recheckedGrant === undefined ||\n recheckedGrant.id !== activeGrant.id ||\n target === undefined ||\n target.generation !== recheckedGrant.sessionGeneration\n ) {\n throw new McpError(ErrorCode.InvalidRequest, 'The session grant is no longer active.');\n }\n return { grant: recheckedGrant, target };\n };\n try {\n await authorizeOperation();\n } catch {\n return jsonError(401, 'The session grant is no longer active.', true, options.resourceMetadataUrl);\n }\n\n const server = new Server(\n { name: options.serverName ?? 'doompi-session', version: options.serverVersion ?? '1.0.0' },\n {\n capabilities: { tools: {}, resources: {} },\n instructions:\n 'Use only this bound DoomPi session. Call load_context before repository work and after selection changes. ' +\n 'Use search_skills and load_skill for relevant guidance. Inspect before editing and follow repository checks. ' +\n 'Do not assume local UI access, downloadable host files, or background completion notifications. ' +\n 'Read saved command logs instead of relaunching work. Saving a plan does not authorize implementation.',\n },\n );\n server.setNotificationHandler(CancelledNotificationSchema, async (notification) => {\n const active = await authorizeOperation();\n if (notification.params.requestId === undefined) return;\n const owner = operationOwner(active.grant, notification.params.requestId);\n const conversation = sessionMcpConversationDigest(notification.params._meta);\n // A notification without a conversation identity cannot safely cancel one\n // of several child runtimes that may reuse its request ID.\n if (conversation === undefined) return;\n const candidates = [...operations.values()].filter(\n (operation) => operation.owner === owner && operation.conversation === conversation,\n );\n if (candidates.length === 1) candidates[0]!.controller.abort();\n });\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n const active = await authorizeOperation();\n await options.onVerified?.(active.grant);\n const { tools } = grantedSurface(active.grant, active.target.toolSurface);\n return {\n tools: tools.map((tool) => ({\n name: tool.name,\n title: tool.label,\n description: tool.description,\n inputSchema: tool.parameters as Tool['inputSchema'],\n ...(tool.annotations === undefined ? {} : { annotations: tool.annotations }),\n ...(tool.outputSchema === undefined ? {} : { outputSchema: tool.outputSchema }),\n _meta: {\n ...tool._meta,\n // Doompi tools can execute code and write files. UI access is opt-in, not the spec's default.\n ui: { ...tool._meta?.ui, visibility: tool._meta?.ui?.visibility ?? ['model'] },\n },\n })),\n };\n });\n server.setRequestHandler(CallToolRequestSchema, async (message, extra): Promise<CallToolResult> => {\n const conversation = sessionMcpConversationDigest((message.params as { _meta?: unknown })._meta);\n const owner = operationOwner(grant, extra.requestId);\n const key = JSON.stringify([owner, conversation]);\n if (operations.has(key))\n throw new McpError(ErrorCode.InvalidRequest, 'A tool call with this request identity is already active.');\n const controller = new AbortController();\n operations.set(key, { controller, owner, conversation });\n const signal = AbortSignal.any([request.signal, extra.signal, controller.signal]);\n const invocationId = randomUUID();\n let skillAccessOpen = true;\n try {\n const parent = await authorizeOperation();\n const advertised = grantedSurface(parent.grant, parent.target.toolSurface).tools.find(\n (tool) => tool.name === message.params.name,\n );\n if (!advertised)\n throw new McpError(ErrorCode.InvalidParams, `Tool '${message.params.name}' is not granted or active.`);\n // A binding must not outlive an unpersisted registration when tools/call precedes tools/list.\n await options.onVerified?.(parent.grant);\n options.onNotice?.(`session MCP invocation id=${invocationId} lifecycle=started`);\n const resolveTarget = async (reserve: boolean) => {\n const active = await authorizeOperation();\n if (!conversation)\n throw new SessionMcpConversationError(\n 'CONVERSATION_ID_REQUIRED',\n 'This connection requires conversation metadata. Use a compatible ChatGPT conversation and retry.',\n );\n if (!options.resolveConversation)\n throw new SessionMcpConversationError(\n 'CONVERSATION_ROUTING_UNAVAILABLE',\n 'Conversation routing is unavailable in this host.',\n );\n signal.throwIfAborted();\n const target = await options.resolveConversation(active.grant, conversation, reserve, signal);\n await authorizeOperation();\n signal.throwIfAborted();\n return { grant: active.grant, target };\n };\n const active = await resolveTarget(true);\n const { snapshot, tools } = grantedSurface(active.grant, active.target.toolSurface);\n const selected = tools.find((tool) => tool.name === message.params.name);\n if (\n !selected ||\n !isDeepStrictEqual(selected.parameters, advertised.parameters) ||\n !isDeepStrictEqual(selected.outputSchema, advertised.outputSchema) ||\n !isDeepStrictEqual(selected.annotations, advertised.annotations) ||\n !isDeepStrictEqual(selected._meta, advertised._meta)\n ) {\n throw new SessionMcpConversationError(\n 'SESSION_TOOL_SURFACE_CHANGED',\n 'The target session does not expose the approved tool contract. Refresh the connection or restore a compatible session selection.',\n );\n }\n const recheck = async () => {\n const current = await resolveTarget(false);\n if (\n current.target.toolSurface !== active.target.toolSurface ||\n current.target.generation !== active.target.generation ||\n current.target.sessionId !== active.target.sessionId\n )\n throw new McpError(ErrorCode.InvalidRequest, 'The session tool surface has changed.');\n const currentAdvertised = grantedSurface(current.grant, parent.target.toolSurface).tools.find(\n (tool) => tool.name === message.params.name,\n );\n if (!currentAdvertised || !isDeepStrictEqual(currentAdvertised, advertised))\n throw new McpError(ErrorCode.InvalidRequest, 'The connection tool surface has changed.');\n return current;\n };\n const authorizedSkills = async () => {\n if (!skillAccessOpen)\n throw new McpError(ErrorCode.InvalidRequest, 'Remote skill access is no longer active.');\n signal.throwIfAborted();\n const current = await recheck();\n signal.throwIfAborted();\n return grantedSurface(current.grant, current.target.toolSurface);\n };\n const mcpSkills = {\n async list() {\n const { skills } = await authorizedSkills();\n return skills.map(({ name, description }) => ({ name, description }));\n },\n async read(name: string) {\n const current = await authorizedSkills();\n const skill = current.skills.find((candidate) => candidate.name === name);\n if (!skill) throw new McpError(ErrorCode.InvalidParams, 'Skill is not granted or active.');\n const text = await active.target.toolSurface.readSkill(current.snapshot.revision, skill.uri);\n signal.throwIfAborted();\n const after = await authorizedSkills();\n if (after.snapshot.revision !== current.snapshot.revision)\n throw new McpError(ErrorCode.InvalidRequest, 'The session skill surface has changed.');\n return text;\n },\n };\n const result = await active.target.toolSurface.invokeTool({\n revision: snapshot.revision,\n name: message.params.name,\n arguments: message.params.arguments ?? {},\n signal,\n mcpSkills,\n authorize: async () => {\n await recheck();\n },\n });\n await recheck();\n options.onNotice?.(`session MCP invocation id=${invocationId} lifecycle=succeeded`);\n return {\n content: result.content,\n ...(result.structuredContent === undefined ? {} : { structuredContent: result.structuredContent }),\n ...(result._meta === undefined ? {} : { _meta: result._meta }),\n isError: result.isError ?? false,\n };\n } catch (error) {\n options.onNotice?.(`session MCP invocation id=${invocationId} lifecycle=failed`);\n if (!(error instanceof SessionMcpConversationError)) throw error;\n return {\n content: [{ type: 'text', text: error.message }],\n isError: true,\n structuredContent: {\n code: error.code,\n message: error.message,\n ...(error.bindingId === undefined ? {} : { bindingId: error.bindingId }),\n },\n };\n } finally {\n skillAccessOpen = false;\n operations.delete(key);\n }\n });\n server.setRequestHandler(ListResourcesRequestSchema, async () => {\n const active = await authorizeOperation();\n const { uiResources } = grantedSurface(active.grant, active.target.toolSurface);\n return {\n resources: [...uiResources],\n };\n });\n server.setRequestHandler(ReadResourceRequestSchema, async (message) => {\n const conversation = sessionMcpConversationDigest((message.params as { _meta?: unknown })._meta);\n const active = await authorizeOperation();\n const { snapshot, skills, uiResources } = grantedSurface(active.grant, active.target.toolSurface);\n const skill = skills.find((candidate) => candidate.uri === message.params.uri);\n if (skill !== undefined) {\n if (!conversation)\n throw new McpError(\n ErrorCode.InvalidParams,\n 'Use load_skill with conversation metadata to read target-session guidance.',\n );\n if (!options.resolveConversation)\n throw new McpError(ErrorCode.InvalidRequest, 'Conversation routing is unavailable in this host.');\n const target = await options.resolveConversation(active.grant, conversation, false, request.signal);\n const text = await target.toolSurface.readSkill(snapshot.revision, skill.uri);\n await authorizeOperation();\n return { contents: [{ uri: skill.uri, mimeType: 'text/markdown', text }] };\n }\n const resource = uiResources.find((candidate) => candidate.uri === message.params.uri);\n if (resource !== undefined && active.target.toolSurface.readUiResource !== undefined) {\n // Templates are immutable and session-independent, so hosts may prefetch them without a conversation ID.\n request.signal.throwIfAborted();\n const text = await active.target.toolSurface.readUiResource(snapshot.revision, resource.uri);\n request.signal.throwIfAborted();\n const current = await authorizeOperation();\n const after = grantedSurface(current.grant, current.target.toolSurface);\n if (\n current.target.toolSurface !== active.target.toolSurface ||\n after.snapshot.revision !== snapshot.revision ||\n !isDeepStrictEqual(\n after.uiResources.find((candidate) => candidate.uri === resource.uri),\n resource,\n )\n )\n throw new McpError(ErrorCode.InvalidRequest, 'The session UI resource surface has changed.');\n return { contents: [{ ...resource, text }] };\n }\n if (message.params.uri.startsWith('ui:'))\n throw new McpError(ErrorCode.InvalidParams, 'UI resource is not granted or active.');\n throw new McpError(\n ErrorCode.InvalidParams,\n 'Use load_skill with conversation metadata to read target-session guidance.',\n );\n });\n\n const transport = new WebStandardStreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: true,\n });\n try {\n await server.connect(transport);\n return await transport.handleRequest(request, {\n authInfo: {\n token,\n clientId: grant.clientId,\n scopes:\n grant.scope === 'session'\n ? ['session']\n : [...grant.tools.map((name) => `tool:${name}`), ...grant.skills.map((name) => `skill:${name}`)],\n expiresAt: Math.floor(grant.expiresAt / 1000),\n resource: new URL(exactAudience),\n },\n });\n } catch {\n return jsonError(500, 'Internal MCP server error.');\n }\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"sessionMcpHandler.cjs","names":["McpError","ErrorCode","Server","CancelledNotificationSchema","sessionMcpConversationDigest","ListToolsRequestSchema","CallToolRequestSchema","randomUUID","SessionMcpConversationError","isDeepStrictEqual","ListResourcesRequestSchema","ReadResourceRequestSchema","WebStandardStreamableHTTPServerTransport"],"sources":["../../../src/server/sessionMcpHandler.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\nimport { isDeepStrictEqual } from 'node:util';\n\nimport { Server } from '@modelcontextprotocol/sdk/server/index.js';\nimport { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js';\nimport {\n CallToolRequestSchema,\n CancelledNotificationSchema,\n ErrorCode,\n ListResourcesRequestSchema,\n ListToolsRequestSchema,\n McpError,\n ReadResourceRequestSchema,\n type CallToolResult,\n type Tool,\n} from '@modelcontextprotocol/sdk/types.js';\n\nimport type { SessionMcpAccessGrant, SessionMcpAuthorizationService } from '../services/sessionMcpAuthorization';\nimport { sessionMcpConversationDigest, SessionMcpConversationError } from '../services/sessionMcpConversations';\nimport type { SessionToolSurface } from '../types/server/sessionToolSurface';\n\nexport interface SessionMcpTarget {\n readonly generation: number;\n readonly sessionId?: string;\n readonly toolSurface: SessionToolSurface;\n}\n\nexport interface SessionMcpHttpHandlerOptions {\n /** Exact HTTPS resource indicator minted into access grants. */\n readonly audience: string;\n readonly authorization: Pick<SessionMcpAuthorizationService, 'authenticateAccessToken' | 'authenticateUrlToken'>;\n readonly onVerified?: (grant: SessionMcpAccessGrant) => void | Promise<void>;\n readonly resourceMetadataUrl?: string;\n /** Signed JWT carried as the final URL path segment instead of an Authorization header. */\n readonly pathToken?: string;\n readonly resolveSession: (sessionId: string) => SessionMcpTarget | undefined | Promise<SessionMcpTarget | undefined>;\n readonly resolveConversation?: (\n grant: SessionMcpAccessGrant,\n digest: string,\n reserve: boolean,\n signal?: AbortSignal,\n ) => SessionMcpTarget | Promise<SessionMcpTarget>;\n /** Only bounded, non-reversible correlation values are reported here. */\n readonly onNotice?: (message: string) => void;\n readonly serverName?: string;\n readonly serverVersion?: string;\n}\n\nexport type SessionMcpHttpHandler = (request: Request) => Promise<Response>;\n\nfunction jsonError(status: number, message: string, authenticate = false, resourceMetadataUrl?: string): Response {\n return Response.json(\n { jsonrpc: '2.0', error: { code: -32_000, message }, id: null },\n {\n status,\n headers: {\n 'cache-control': 'no-store',\n ...(authenticate\n ? {\n 'www-authenticate':\n resourceMetadataUrl === undefined ? 'Bearer' : `Bearer resource_metadata=\"${resourceMetadataUrl}\"`,\n }\n : {}),\n },\n },\n );\n}\n\nfunction bearerToken(request: Request): string | undefined {\n const authorization = request.headers.get('authorization');\n const match = authorization === null ? undefined : /^Bearer ([A-Za-z0-9_-]+)$/iu.exec(authorization);\n return match?.[1];\n}\n\nfunction currentUrl(request: Request): string | undefined {\n try {\n const url = new URL(request.url);\n return url.search === '' && url.hash === '' ? url.href : undefined;\n } catch {\n return undefined;\n }\n}\n\nfunction grantedSurface(grant: SessionMcpAccessGrant, surface: SessionToolSurface) {\n const snapshot = surface.readSurface();\n const toolNames = new Set(grant.tools);\n const skillNames = new Set(grant.skills);\n const tools = grant.scope === 'session' ? snapshot.tools : snapshot.tools.filter((tool) => toolNames.has(tool.name));\n const resourceUris = new Set(tools.map((tool) => tool._meta?.ui?.resourceUri));\n return {\n snapshot,\n tools,\n skills: grant.scope === 'session' ? snapshot.skills : snapshot.skills.filter((skill) => skillNames.has(skill.name)),\n uiResources: (snapshot.uiResources ?? []).filter((resource) => resourceUris.has(resource.uri)),\n };\n}\n\n/** Creates a stateless Streamable HTTP MCP request handler for session capabilities. */\nexport function createSessionMcpHttpHandler(options: SessionMcpHttpHandlerOptions): SessionMcpHttpHandler {\n const audience = new URL(options.audience);\n if (\n audience.protocol !== 'https:' ||\n audience.username !== '' ||\n audience.password !== '' ||\n audience.search !== '' ||\n audience.hash !== ''\n ) {\n throw new Error('Session MCP audience must be an absolute HTTPS URL without credentials, a query, or a fragment.');\n }\n const exactAudience = audience.href;\n if (options.pathToken !== undefined && !/^[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+\\.[A-Za-z0-9_-]+$/u.test(options.pathToken)) {\n throw new Error('Session MCP path token must be a compact JWT.');\n }\n const exactResource = options.pathToken === undefined ? exactAudience : `${exactAudience}/${options.pathToken}`;\n const authenticate =\n options.pathToken === undefined\n ? options.authorization.authenticateAccessToken\n : options.authorization.authenticateUrlToken;\n const operations = new Map<string, { controller: AbortController; owner: string; conversation?: string }>();\n const operationOwner = (grant: SessionMcpAccessGrant, requestId: string | number): string =>\n JSON.stringify([grant.clientId, grant.sessionId, grant.sessionGeneration, grant.id, requestId]);\n\n return async (request) => {\n if (currentUrl(request) !== exactResource) return jsonError(404, 'MCP resource not found.');\n const token = options.pathToken ?? bearerToken(request);\n if (token === undefined)\n return jsonError(401, 'A Bearer access token is required.', true, options.resourceMetadataUrl);\n const grant = authenticate(token, exactAudience);\n if (grant === undefined)\n return options.pathToken === undefined\n ? jsonError(401, 'The Bearer access token is invalid or expired.', true, options.resourceMetadataUrl)\n : jsonError(401, 'The signed MCP URL is invalid or revoked.');\n const authorizeOperation = async (): Promise<{\n grant: SessionMcpAccessGrant;\n target: SessionMcpTarget;\n }> => {\n const activeGrant = authenticate(token, exactAudience);\n if (activeGrant === undefined || activeGrant.id !== grant.id) {\n throw new McpError(ErrorCode.InvalidRequest, 'The session grant is no longer active.');\n }\n const target = await options.resolveSession(activeGrant.sessionId);\n const recheckedGrant = authenticate(token, exactAudience);\n if (\n recheckedGrant === undefined ||\n recheckedGrant.id !== activeGrant.id ||\n target === undefined ||\n target.generation !== recheckedGrant.sessionGeneration\n ) {\n throw new McpError(ErrorCode.InvalidRequest, 'The session grant is no longer active.');\n }\n return { grant: recheckedGrant, target };\n };\n try {\n await authorizeOperation();\n } catch {\n return options.pathToken === undefined\n ? jsonError(401, 'The session grant is no longer active.', true, options.resourceMetadataUrl)\n : jsonError(401, 'The signed MCP URL is invalid or revoked.');\n }\n\n const server = new Server(\n { name: options.serverName ?? 'doompi-session', version: options.serverVersion ?? '1.0.0' },\n {\n capabilities: { tools: {}, resources: {} },\n instructions:\n 'Use only this bound DoomPi session. Call load_context before repository work and after selection changes. ' +\n 'Use search_skills and load_skill for relevant guidance. Inspect before editing and follow repository checks. ' +\n 'Do not assume local UI access, downloadable host files, or background completion notifications. ' +\n 'Read saved command logs instead of relaunching work. Saving a plan does not authorize implementation.',\n },\n );\n server.setNotificationHandler(CancelledNotificationSchema, async (notification) => {\n const active = await authorizeOperation();\n if (notification.params.requestId === undefined) return;\n const owner = operationOwner(active.grant, notification.params.requestId);\n const conversation = sessionMcpConversationDigest(notification.params._meta);\n // A notification without a conversation identity cannot safely cancel one\n // of several child runtimes that may reuse its request ID.\n if (conversation === undefined) return;\n const candidates = [...operations.values()].filter(\n (operation) => operation.owner === owner && operation.conversation === conversation,\n );\n if (candidates.length === 1) candidates[0]!.controller.abort();\n });\n server.setRequestHandler(ListToolsRequestSchema, async () => {\n const active = await authorizeOperation();\n await options.onVerified?.(active.grant);\n const { tools } = grantedSurface(active.grant, active.target.toolSurface);\n return {\n tools: tools.map((tool) => ({\n name: tool.name,\n title: tool.label,\n description: tool.description,\n inputSchema: tool.parameters as Tool['inputSchema'],\n ...(tool.annotations === undefined ? {} : { annotations: tool.annotations }),\n ...(tool.outputSchema === undefined ? {} : { outputSchema: tool.outputSchema }),\n _meta: {\n ...tool._meta,\n // Doompi tools can execute code and write files. UI access is opt-in, not the spec's default.\n ui: { ...tool._meta?.ui, visibility: tool._meta?.ui?.visibility ?? ['model'] },\n },\n })),\n };\n });\n server.setRequestHandler(CallToolRequestSchema, async (message, extra): Promise<CallToolResult> => {\n const conversation = sessionMcpConversationDigest((message.params as { _meta?: unknown })._meta);\n const owner = operationOwner(grant, extra.requestId);\n const key = JSON.stringify([owner, conversation]);\n if (operations.has(key))\n throw new McpError(ErrorCode.InvalidRequest, 'A tool call with this request identity is already active.');\n const controller = new AbortController();\n operations.set(key, { controller, owner, conversation });\n const signal = AbortSignal.any([request.signal, extra.signal, controller.signal]);\n const invocationId = randomUUID();\n let skillAccessOpen = true;\n try {\n const parent = await authorizeOperation();\n const advertised = grantedSurface(parent.grant, parent.target.toolSurface).tools.find(\n (tool) => tool.name === message.params.name,\n );\n if (!advertised)\n throw new McpError(ErrorCode.InvalidParams, `Tool '${message.params.name}' is not granted or active.`);\n // A binding must not outlive an unpersisted registration when tools/call precedes tools/list.\n await options.onVerified?.(parent.grant);\n options.onNotice?.(`session MCP invocation id=${invocationId} lifecycle=started`);\n const resolveTarget = async (reserve: boolean) => {\n const active = await authorizeOperation();\n if (!conversation)\n throw new SessionMcpConversationError(\n 'CONVERSATION_ID_REQUIRED',\n 'This connection requires conversation metadata. Use a compatible ChatGPT conversation and retry.',\n );\n if (!options.resolveConversation)\n throw new SessionMcpConversationError(\n 'CONVERSATION_ROUTING_UNAVAILABLE',\n 'Conversation routing is unavailable in this host.',\n );\n signal.throwIfAborted();\n const target = await options.resolveConversation(active.grant, conversation, reserve, signal);\n await authorizeOperation();\n signal.throwIfAborted();\n return { grant: active.grant, target };\n };\n const active = await resolveTarget(true);\n const { snapshot, tools } = grantedSurface(active.grant, active.target.toolSurface);\n const selected = tools.find((tool) => tool.name === message.params.name);\n if (\n !selected ||\n !isDeepStrictEqual(selected.parameters, advertised.parameters) ||\n !isDeepStrictEqual(selected.outputSchema, advertised.outputSchema) ||\n !isDeepStrictEqual(selected.annotations, advertised.annotations) ||\n !isDeepStrictEqual(selected._meta, advertised._meta)\n ) {\n throw new SessionMcpConversationError(\n 'SESSION_TOOL_SURFACE_CHANGED',\n 'The target session does not expose the approved tool contract. Refresh the connection or restore a compatible session selection.',\n );\n }\n const recheck = async () => {\n const current = await resolveTarget(false);\n if (\n current.target.toolSurface !== active.target.toolSurface ||\n current.target.generation !== active.target.generation ||\n current.target.sessionId !== active.target.sessionId\n )\n throw new McpError(ErrorCode.InvalidRequest, 'The session tool surface has changed.');\n const currentAdvertised = grantedSurface(current.grant, parent.target.toolSurface).tools.find(\n (tool) => tool.name === message.params.name,\n );\n if (!currentAdvertised || !isDeepStrictEqual(currentAdvertised, advertised))\n throw new McpError(ErrorCode.InvalidRequest, 'The connection tool surface has changed.');\n return current;\n };\n const authorizedSkills = async () => {\n if (!skillAccessOpen)\n throw new McpError(ErrorCode.InvalidRequest, 'Remote skill access is no longer active.');\n signal.throwIfAborted();\n const current = await recheck();\n signal.throwIfAborted();\n return grantedSurface(current.grant, current.target.toolSurface);\n };\n const mcpSkills = {\n async list() {\n const { skills } = await authorizedSkills();\n return skills.map(({ name, description }) => ({ name, description }));\n },\n async read(name: string) {\n const current = await authorizedSkills();\n const skill = current.skills.find((candidate) => candidate.name === name);\n if (!skill) throw new McpError(ErrorCode.InvalidParams, 'Skill is not granted or active.');\n const text = await active.target.toolSurface.readSkill(current.snapshot.revision, skill.uri);\n signal.throwIfAborted();\n const after = await authorizedSkills();\n if (after.snapshot.revision !== current.snapshot.revision)\n throw new McpError(ErrorCode.InvalidRequest, 'The session skill surface has changed.');\n return text;\n },\n };\n const result = await active.target.toolSurface.invokeTool({\n revision: snapshot.revision,\n name: message.params.name,\n arguments: message.params.arguments ?? {},\n signal,\n mcpSkills,\n authorize: async () => {\n await recheck();\n },\n });\n await recheck();\n options.onNotice?.(`session MCP invocation id=${invocationId} lifecycle=succeeded`);\n return {\n content: result.content,\n ...(result.structuredContent === undefined ? {} : { structuredContent: result.structuredContent }),\n ...(result._meta === undefined ? {} : { _meta: result._meta }),\n isError: result.isError ?? false,\n };\n } catch (error) {\n options.onNotice?.(`session MCP invocation id=${invocationId} lifecycle=failed`);\n if (!(error instanceof SessionMcpConversationError)) throw error;\n return {\n content: [{ type: 'text', text: error.message }],\n isError: true,\n structuredContent: {\n code: error.code,\n message: error.message,\n ...(error.bindingId === undefined ? {} : { bindingId: error.bindingId }),\n },\n };\n } finally {\n skillAccessOpen = false;\n operations.delete(key);\n }\n });\n server.setRequestHandler(ListResourcesRequestSchema, async () => {\n const active = await authorizeOperation();\n const { uiResources } = grantedSurface(active.grant, active.target.toolSurface);\n return {\n resources: [...uiResources],\n };\n });\n server.setRequestHandler(ReadResourceRequestSchema, async (message) => {\n const conversation = sessionMcpConversationDigest((message.params as { _meta?: unknown })._meta);\n const active = await authorizeOperation();\n const { snapshot, skills, uiResources } = grantedSurface(active.grant, active.target.toolSurface);\n const skill = skills.find((candidate) => candidate.uri === message.params.uri);\n if (skill !== undefined) {\n if (!conversation)\n throw new McpError(\n ErrorCode.InvalidParams,\n 'Use load_skill with conversation metadata to read target-session guidance.',\n );\n if (!options.resolveConversation)\n throw new McpError(ErrorCode.InvalidRequest, 'Conversation routing is unavailable in this host.');\n const target = await options.resolveConversation(active.grant, conversation, false, request.signal);\n const text = await target.toolSurface.readSkill(snapshot.revision, skill.uri);\n await authorizeOperation();\n return { contents: [{ uri: skill.uri, mimeType: 'text/markdown', text }] };\n }\n const resource = uiResources.find((candidate) => candidate.uri === message.params.uri);\n if (resource !== undefined && active.target.toolSurface.readUiResource !== undefined) {\n // Templates are immutable and session-independent, so hosts may prefetch them without a conversation ID.\n request.signal.throwIfAborted();\n const text = await active.target.toolSurface.readUiResource(snapshot.revision, resource.uri);\n request.signal.throwIfAborted();\n const current = await authorizeOperation();\n const after = grantedSurface(current.grant, current.target.toolSurface);\n if (\n current.target.toolSurface !== active.target.toolSurface ||\n after.snapshot.revision !== snapshot.revision ||\n !isDeepStrictEqual(\n after.uiResources.find((candidate) => candidate.uri === resource.uri),\n resource,\n )\n )\n throw new McpError(ErrorCode.InvalidRequest, 'The session UI resource surface has changed.');\n return { contents: [{ ...resource, text }] };\n }\n if (message.params.uri.startsWith('ui:'))\n throw new McpError(ErrorCode.InvalidParams, 'UI resource is not granted or active.');\n throw new McpError(\n ErrorCode.InvalidParams,\n 'Use load_skill with conversation metadata to read target-session guidance.',\n );\n });\n\n const transport = new WebStandardStreamableHTTPServerTransport({\n sessionIdGenerator: undefined,\n enableJsonResponse: true,\n });\n try {\n await server.connect(transport);\n return await transport.handleRequest(request, {\n authInfo: {\n token,\n clientId: grant.clientId,\n scopes:\n grant.scope === 'session'\n ? ['session']\n : [...grant.tools.map((name) => `tool:${name}`), ...grant.skills.map((name) => `skill:${name}`)],\n expiresAt: Math.floor(grant.expiresAt / 1000),\n resource: new URL(exactResource),\n },\n });\n } catch {\n return jsonError(500, 'Internal MCP server error.');\n }\n };\n}\n"],"mappings":"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"}
@@ -9,9 +9,11 @@ export interface SessionMcpTarget {
9
9
  export interface SessionMcpHttpHandlerOptions {
10
10
  /** Exact HTTPS resource indicator minted into access grants. */
11
11
  readonly audience: string;
12
- readonly authorization: Pick<SessionMcpAuthorizationService, 'authenticateAccessToken'>;
12
+ readonly authorization: Pick<SessionMcpAuthorizationService, 'authenticateAccessToken' | 'authenticateUrlToken'>;
13
13
  readonly onVerified?: (grant: SessionMcpAccessGrant) => void | Promise<void>;
14
14
  readonly resourceMetadataUrl?: string;
15
+ /** Signed JWT carried as the final URL path segment instead of an Authorization header. */
16
+ readonly pathToken?: string;
15
17
  readonly resolveSession: (sessionId: string) => SessionMcpTarget | undefined | Promise<SessionMcpTarget | undefined>;
16
18
  readonly resolveConversation?: (grant: SessionMcpAccessGrant, digest: string, reserve: boolean, signal?: AbortSignal) => SessionMcpTarget | Promise<SessionMcpTarget>;
17
19
  /** Only bounded, non-reversible correlation values are reported here. */
@@ -20,7 +22,7 @@ export interface SessionMcpHttpHandlerOptions {
20
22
  readonly serverVersion?: string;
21
23
  }
22
24
  export type SessionMcpHttpHandler = (request: Request) => Promise<Response>;
23
- /** Creates a Bearer-only, stateless Streamable HTTP MCP request handler for session capabilities. */
25
+ /** Creates a stateless Streamable HTTP MCP request handler for session capabilities. */
24
26
  export declare function createSessionMcpHttpHandler(options: SessionMcpHttpHandlerOptions): SessionMcpHttpHandler;
25
27
  //#endregion
26
28
  //# sourceMappingURL=sessionMcpHandler.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"sessionMcpHandler.d.mts","names":[],"sources":["../../../src/server/sessionMcpHandler.ts"],"mappings":";;;iBAqBiB;WACN;WACA;WACA,aAAa;;iBAGP;;WAEN;WACA,eAAe,KAAK;WACpB,cAAc,OAAO,iCAAiC;WACtD;WACA,iBAAiB,sBAAsB,+BAA+B,QAAQ;WAC9E,uBACP,OAAO,uBACP,gBACA,kBACA,SAAS,gBACN,mBAAmB,QAAQ;;WAEvB,YAAY;WACZ;WACA;;YAGC,yBAAyB,SAAS,YAAY,QAAQ;;wBAkDlD,4BAA4B,SAAS,+BAA+B"}
1
+ {"version":3,"file":"sessionMcpHandler.d.mts","names":[],"sources":["../../../src/server/sessionMcpHandler.ts"],"mappings":";;;iBAqBiB;WACN;WACA;WACA,aAAa;;iBAGP;;WAEN;WACA,eAAe,KAAK;WACpB,cAAc,OAAO,iCAAiC;WACtD;;WAEA;WACA,iBAAiB,sBAAsB,+BAA+B,QAAQ;WAC9E,uBACP,OAAO,uBACP,gBACA,kBACA,SAAS,gBACN,mBAAmB,QAAQ;;WAEvB,YAAY;WACZ;WACA;;YAGC,yBAAyB,SAAS,YAAY,QAAQ;;wBAkDlD,4BAA4B,SAAS,+BAA+B"}
@@ -46,11 +46,14 @@ function grantedSurface(grant, surface) {
46
46
  uiResources: (snapshot.uiResources ?? []).filter((resource) => resourceUris.has(resource.uri))
47
47
  };
48
48
  }
49
- /** Creates a Bearer-only, stateless Streamable HTTP MCP request handler for session capabilities. */
49
+ /** Creates a stateless Streamable HTTP MCP request handler for session capabilities. */
50
50
  function createSessionMcpHttpHandler(options) {
51
51
  const audience = new URL(options.audience);
52
52
  if (audience.protocol !== "https:" || audience.username !== "" || audience.password !== "" || audience.search !== "" || audience.hash !== "") throw new Error("Session MCP audience must be an absolute HTTPS URL without credentials, a query, or a fragment.");
53
53
  const exactAudience = audience.href;
54
+ if (options.pathToken !== void 0 && !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/u.test(options.pathToken)) throw new Error("Session MCP path token must be a compact JWT.");
55
+ const exactResource = options.pathToken === void 0 ? exactAudience : `${exactAudience}/${options.pathToken}`;
56
+ const authenticate = options.pathToken === void 0 ? options.authorization.authenticateAccessToken : options.authorization.authenticateUrlToken;
54
57
  const operations = /* @__PURE__ */ new Map();
55
58
  const operationOwner = (grant, requestId) => JSON.stringify([
56
59
  grant.clientId,
@@ -60,16 +63,16 @@ function createSessionMcpHttpHandler(options) {
60
63
  requestId
61
64
  ]);
62
65
  return async (request) => {
63
- if (currentUrl(request) !== exactAudience) return jsonError(404, "MCP resource not found.");
64
- const token = bearerToken(request);
66
+ if (currentUrl(request) !== exactResource) return jsonError(404, "MCP resource not found.");
67
+ const token = options.pathToken ?? bearerToken(request);
65
68
  if (token === void 0) return jsonError(401, "A Bearer access token is required.", true, options.resourceMetadataUrl);
66
- const grant = options.authorization.authenticateAccessToken(token, exactAudience);
67
- if (grant === void 0) return jsonError(401, "The Bearer access token is invalid or expired.", true, options.resourceMetadataUrl);
69
+ const grant = authenticate(token, exactAudience);
70
+ if (grant === void 0) return options.pathToken === void 0 ? jsonError(401, "The Bearer access token is invalid or expired.", true, options.resourceMetadataUrl) : jsonError(401, "The signed MCP URL is invalid or revoked.");
68
71
  const authorizeOperation = async () => {
69
- const activeGrant = options.authorization.authenticateAccessToken(token, exactAudience);
72
+ const activeGrant = authenticate(token, exactAudience);
70
73
  if (activeGrant === void 0 || activeGrant.id !== grant.id) throw new McpError(ErrorCode.InvalidRequest, "The session grant is no longer active.");
71
74
  const target = await options.resolveSession(activeGrant.sessionId);
72
- const recheckedGrant = options.authorization.authenticateAccessToken(token, exactAudience);
75
+ const recheckedGrant = authenticate(token, exactAudience);
73
76
  if (recheckedGrant === void 0 || recheckedGrant.id !== activeGrant.id || target === void 0 || target.generation !== recheckedGrant.sessionGeneration) throw new McpError(ErrorCode.InvalidRequest, "The session grant is no longer active.");
74
77
  return {
75
78
  grant: recheckedGrant,
@@ -79,7 +82,7 @@ function createSessionMcpHttpHandler(options) {
79
82
  try {
80
83
  await authorizeOperation();
81
84
  } catch {
82
- return jsonError(401, "The session grant is no longer active.", true, options.resourceMetadataUrl);
85
+ return options.pathToken === void 0 ? jsonError(401, "The session grant is no longer active.", true, options.resourceMetadataUrl) : jsonError(401, "The signed MCP URL is invalid or revoked.");
83
86
  }
84
87
  const server = new Server({
85
88
  name: options.serverName ?? "doompi-session",
@@ -278,7 +281,7 @@ function createSessionMcpHttpHandler(options) {
278
281
  clientId: grant.clientId,
279
282
  scopes: grant.scope === "session" ? ["session"] : [...grant.tools.map((name) => `tool:${name}`), ...grant.skills.map((name) => `skill:${name}`)],
280
283
  expiresAt: Math.floor(grant.expiresAt / 1e3),
281
- resource: new URL(exactAudience)
284
+ resource: new URL(exactResource)
282
285
  } });
283
286
  } catch {
284
287
  return jsonError(500, "Internal MCP server error.");