{"version":3,"file":"code-feature.d.ts","sourceRoot":"","sources":["../../src/bundle/code-feature.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,gBAAgB,EAAkB,MAAM,2BAA2B,CAAC;AAMhG,MAAM,WAAW,iBAAiB;IACjC,EAAE,EAAE,YAAY,CAAC,IAAI,CAAC,CAAC;IACvB,YAAY,EAAE,YAAY,CAAC,cAAc,CAAC,CAAC;CAC3C;AAED,MAAM,WAAW,wBAAwB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,CAAC,GAAG,EAAE,iBAAiB,GAAG,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACpD;AAyBD,sFAAsF;AACtF,wBAAgB,yBAAyB,CACxC,SAAS,EAAE,SAAS,MAAM,EAAE,EAC5B,eAAe,EAAE,SAAS,MAAM,EAAE,EAClC,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,MAAM,EAAE,CAQV;AA0ED;;;;GAIG;AACH,wBAAgB,6BAA6B,CAC5C,OAAO,EAAE,wBAAwB,EACjC,OAAO,GAAE;IAAE,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7B,gBAAgB,CAuClB","sourcesContent":["import type { ExtensionAPI, ExtensionFactory, ToolDefinition } from \"@ch1nyzzz/pi-coding-agent\";\nimport { getEvoPaths } from \"../paths.ts\";\nimport { isEvoFeatureEnabled } from \"./feature-gate.ts\";\n\ntype UntypedHandler = (...args: unknown[]) => unknown;\n\nexport interface EvoCodeFeatureAPI {\n\ton: ExtensionAPI[\"on\"];\n\tregisterTool: ExtensionAPI[\"registerTool\"];\n}\n\nexport interface EvoCodeFeatureDefinition {\n\tid: string;\n\tsetup(api: EvoCodeFeatureAPI): void | Promise<void>;\n}\n\nconst featureToolOwners = new Map<string, Map<string, string>>();\n\nfunction assertFeatureId(featureId: string): void {\n\tif (featureId.length === 0) throw new Error(\"Evo code feature id must not be empty\");\n}\n\nfunction claimFeatureTool(root: string, featureId: string, toolName: string): () => void {\n\tconst owners = featureToolOwners.get(root) ?? new Map<string, string>();\n\tconst existing = owners.get(toolName);\n\tif (existing !== undefined && existing !== featureId) {\n\t\tthrow new Error(`Evo tool ${toolName} is already owned by code feature ${existing}`);\n\t}\n\tif (existing === featureId) return () => {};\n\towners.set(toolName, featureId);\n\tfeatureToolOwners.set(root, owners);\n\treturn () => {\n\t\tconst current = featureToolOwners.get(root);\n\t\tif (current?.get(toolName) !== featureId) return;\n\t\tcurrent.delete(toolName);\n\t\tif (current.size === 0) featureToolOwners.delete(root);\n\t};\n}\n\n/** @internal Used by the pinned policy runtime to hide dormant code-feature tools. */\nexport function filterEvoCodeFeatureTools(\n\ttoolNames: readonly string[],\n\tenabledFeatures: readonly string[],\n\toptions: { root?: string } = {},\n): string[] {\n\tconst owners = featureToolOwners.get(getEvoPaths(options.root).root);\n\tif (!owners) return [...toolNames];\n\tconst enabled = new Set(enabledFeatures);\n\treturn toolNames.filter((toolName) => {\n\t\tconst owner = owners.get(toolName);\n\t\treturn owner === undefined || enabled.has(owner);\n\t});\n}\n\nfunction sessionIdFromContext(value: unknown): string | undefined {\n\tif (typeof value !== \"object\" || value === null || !(\"sessionManager\" in value)) return undefined;\n\tconst sessionManager = value.sessionManager;\n\tif (typeof sessionManager !== \"object\" || sessionManager === null || !(\"getSessionId\" in sessionManager)) {\n\t\treturn undefined;\n\t}\n\tconst getSessionId = sessionManager.getSessionId;\n\tif (typeof getSessionId !== \"function\") return undefined;\n\tconst sessionId = Reflect.apply(getSessionId, sessionManager, []);\n\treturn typeof sessionId === \"string\" ? sessionId : undefined;\n}\n\nfunction guardToolDefinition(\n\ttool: ToolDefinition,\n\tfeatureId: string,\n\troot: string,\n\tisSessionEnabled: (sessionId: string) => boolean,\n): ToolDefinition {\n\tconst prepareArguments = tool.prepareArguments;\n\tconst renderCall = tool.renderCall;\n\tconst renderResult = tool.renderResult;\n\tconst assertEnabledWithoutContext = (): void => {\n\t\tif (!isEvoFeatureEnabled(featureId, { root })) {\n\t\t\tthrow new Error(`Evo code feature is disabled: ${featureId}`);\n\t\t}\n\t};\n\treturn {\n\t\t...tool,\n\t\tprepareArguments:\n\t\t\tprepareArguments === undefined\n\t\t\t\t? undefined\n\t\t\t\t: (args) => {\n\t\t\t\t\t\tassertEnabledWithoutContext();\n\t\t\t\t\t\treturn prepareArguments(args);\n\t\t\t\t\t},\n\t\texecute: async (toolCallId, params, signal, onUpdate, ctx) => {\n\t\t\tif (!isSessionEnabled(ctx.sessionManager.getSessionId())) {\n\t\t\t\tthrow new Error(`Evo code feature is disabled for this session: ${featureId}`);\n\t\t\t}\n\t\t\treturn tool.execute(toolCallId, params, signal, onUpdate, ctx);\n\t\t},\n\t\trenderCall:\n\t\t\trenderCall === undefined\n\t\t\t\t? undefined\n\t\t\t\t: (args, theme, context) => {\n\t\t\t\t\t\tassertEnabledWithoutContext();\n\t\t\t\t\t\treturn renderCall(args, theme, context);\n\t\t\t\t\t},\n\t\trenderResult:\n\t\t\trenderResult === undefined\n\t\t\t\t? undefined\n\t\t\t\t: (result, options, theme, context) => {\n\t\t\t\t\t\tassertEnabledWithoutContext();\n\t\t\t\t\t\treturn renderResult(result, options, theme, context);\n\t\t\t\t\t},\n\t};\n}\n\nfunction createGuardedOn(pi: ExtensionAPI, isSessionEnabled: (sessionId: string) => boolean): ExtensionAPI[\"on\"] {\n\tconst register = (event: string, handler: UntypedHandler): void => {\n\t\tconst guarded = (...args: unknown[]): unknown => {\n\t\t\tconst sessionId = sessionIdFromContext(args[1]);\n\t\t\tif (sessionId === undefined || !isSessionEnabled(sessionId)) {\n\t\t\t\treturn event === \"project_trust\" ? { trusted: \"undecided\" } : undefined;\n\t\t\t}\n\t\t\treturn Reflect.apply(handler, undefined, args);\n\t\t};\n\t\tReflect.apply(pi.on, pi, [event, guarded]);\n\t};\n\treturn register as unknown as ExtensionAPI[\"on\"];\n}\n\n/**\n * Wrap code-owned tools and hooks so only sessions whose pinned bundle enables\n * the feature id can invoke them. Register this after createPolicyRuntimeExtension;\n * createEvoExtension does that ordering automatically.\n */\nexport function createEvoCodeFeatureExtension(\n\tfeature: EvoCodeFeatureDefinition,\n\toptions: { root?: string } = {},\n): ExtensionFactory {\n\tassertFeatureId(feature.id);\n\tconst root = getEvoPaths(options.root).root;\n\treturn async (pi) => {\n\t\tconst enabledSessions = new Set<string>();\n\t\tconst claims: Array<() => void> = [];\n\t\tconst isSessionEnabled = (sessionId: string): boolean => enabledSessions.has(sessionId);\n\n\t\tpi.on(\"session_start\", (_event, ctx) => {\n\t\t\tenabledSessions.clear();\n\t\t\tconst sessionId = ctx.sessionManager.getSessionId();\n\t\t\tif (isEvoFeatureEnabled(feature.id, { root, sessionId })) enabledSessions.add(sessionId);\n\t\t});\n\n\t\tconst api: EvoCodeFeatureAPI = {\n\t\t\ton: createGuardedOn(pi, isSessionEnabled),\n\t\t\tregisterTool: ((tool: ToolDefinition): void => {\n\t\t\t\tconst release = claimFeatureTool(root, feature.id, tool.name);\n\t\t\t\ttry {\n\t\t\t\t\tpi.registerTool(guardToolDefinition(tool, feature.id, root, isSessionEnabled));\n\t\t\t\t\tclaims.push(release);\n\t\t\t\t} catch (error) {\n\t\t\t\t\trelease();\n\t\t\t\t\tthrow error;\n\t\t\t\t}\n\t\t\t}) as ExtensionAPI[\"registerTool\"],\n\t\t};\n\n\t\ttry {\n\t\t\tawait feature.setup(api);\n\t\t} catch (error) {\n\t\t\tfor (const release of claims.reverse()) release();\n\t\t\tthrow error;\n\t\t}\n\n\t\tpi.on(\"session_shutdown\", (_event, ctx) => {\n\t\t\tenabledSessions.delete(ctx.sessionManager.getSessionId());\n\t\t});\n\t};\n}\n"]}