@agimon-ai/doompi-ui 0.0.1-alpha.48 → 0.0.1-alpha.49

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 (40) hide show
  1. package/dist/adapters/pi/extension.cjs +1 -1
  2. package/dist/adapters/pi/extension.cjs.map +1 -1
  3. package/dist/adapters/pi/extension.d.cts.map +1 -1
  4. package/dist/adapters/pi/extension.d.mts.map +1 -1
  5. package/dist/adapters/pi/extension.mjs +1 -1
  6. package/dist/adapters/pi/extension.mjs.map +1 -1
  7. package/dist/adapters/pi/extensionName.cjs +1 -1
  8. package/dist/adapters/pi/extensionName.cjs.map +1 -1
  9. package/dist/adapters/pi/extensionName.d.cts +11 -1
  10. package/dist/adapters/pi/extensionName.d.cts.map +1 -1
  11. package/dist/adapters/pi/extensionName.d.mts +11 -1
  12. package/dist/adapters/pi/extensionName.d.mts.map +1 -1
  13. package/dist/adapters/pi/extensionName.mjs +1 -1
  14. package/dist/adapters/pi/extensionName.mjs.map +1 -1
  15. package/dist/extensionName.cjs +1 -1
  16. package/dist/extensionName.d.cts +2 -2
  17. package/dist/extensionName.d.mts +2 -2
  18. package/dist/extensionName.mjs +1 -1
  19. package/dist/services/tools/toolCost.cjs +3 -0
  20. package/dist/services/tools/toolCost.cjs.map +1 -0
  21. package/dist/services/tools/toolCost.d.cts +39 -0
  22. package/dist/services/tools/toolCost.d.cts.map +1 -0
  23. package/dist/services/tools/toolCost.d.mts +39 -0
  24. package/dist/services/tools/toolCost.d.mts.map +1 -0
  25. package/dist/services/tools/toolCost.mjs +3 -0
  26. package/dist/services/tools/toolCost.mjs.map +1 -0
  27. package/dist/services/tools/toolInventory.cjs +1 -1
  28. package/dist/services/tools/toolInventory.cjs.map +1 -1
  29. package/dist/services/tools/toolInventory.d.cts +13 -0
  30. package/dist/services/tools/toolInventory.d.cts.map +1 -1
  31. package/dist/services/tools/toolInventory.d.mts +13 -0
  32. package/dist/services/tools/toolInventory.d.mts.map +1 -1
  33. package/dist/services/tools/toolInventory.mjs +1 -1
  34. package/dist/services/tools/toolInventory.mjs.map +1 -1
  35. package/dist/toolInventory.cjs +1 -1
  36. package/dist/toolInventory.d.cts +2 -1
  37. package/dist/toolInventory.d.mts +2 -1
  38. package/dist/toolInventory.mjs +1 -1
  39. package/package.json +6 -6
  40. package/src/web/WriteToolMessage.tsx +48 -11
@@ -1,2 +1,2 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../services/leader/leaderRegistry.cjs"),n=require("../../services/hub/uiHub.cjs"),r=require("../../services/state/uiState.cjs"),i=require("../../services/tools/toolInventory.cjs"),a=require("../../tui/configOverlay.cjs"),o=require("../../tui/doomEditor.cjs"),s=require("../../tui/doomFooter.cjs"),c=require("../../tui/doomHeader.cjs"),l=require("../../tui/leaderHints.cjs");require("../../tui/theme.cjs");const u=require("../../tui/toolsOverlay.cjs"),d=require("../telemetry/logSinkTelemetry.cjs"),f=require("./extensionName.cjs"),p=require("./toolOverrides.cjs");let m=require("node:path");m=e.__toESM(m,1);let h=require("@agimon-ai/doompi-extension-contracts/cordis-host"),g=require("@agimon-ai/doompi-extension-contracts/mcp-status"),_=require("@agimon-ai/doompi-extension-contracts/notification"),v=require("@agimon-ai/doompi-extension-contracts/mode"),y=require("@agimon-ai/doompi-extension-contracts/ui-hub"),b=require("@agimon-ai/doompi-extension-contracts/tool-overrides");const x=`doom-pi-leader`,S=`belowEditor`,C=`warning`,w=`doom-pi`,T=new Set([`hotkeys`]),E=`@agimon-ai/doompi-ui`;function D(e,t,n,r){let i=(0,_.readDoomNotificationService)(e);if(i){try{Promise.resolve(i.request({body:n,level:r})).catch(()=>void 0)}catch{}return}t.ui.notify(n,r)}function O(e,t){let n=e.trim().replace(/\s+/g,` `);if(!n)return;let r=n.slice(0,64).trimEnd();return`${w} · ${r} · ${m.default.basename(t)||t}`}function k(e,{pi:h,telemetry:_,hostMode:k}){e.inject([b.DOOM_TOOL_OVERRIDES_SERVICE],e=>{let t=(0,b.requireDoomToolOverrides)(e),n=[],r=e=>{if(k===`composed`)return!0;let r=t.claim({source:E,tools:[e]});return r.granted&&n.push(r),r.granted};try{p.registerBuiltinToolUi(h,process.cwd(),r)}catch(e){for(let e of n)e.dispose();throw e}return()=>{for(let e of n)e.dispose()}});let A=new r.DoomUiState,j=new t.DoomLeaderRegistry,M=new Set,N,P,F=0,I=!1,L=()=>(0,g.readDoomMcpStatus)(e)?.getSnapshot().servers;h.registerCommand(`tools`,{description:`Browse the tools available in this session`,handler:async(e,t)=>{let n=L(),r=i.buildToolSources({tools:h.getAllTools(),activeTools:h.getActiveTools(),...n?{mcpServers:n}:{},resolveExtensionName:f.extensionName,resolveExtensionToolSource:e=>f.extensionToolSource(h,e)});await u.openToolsOverlay(t,r)}}),h.registerCommand(`config`,{description:`Browse and change the settings extensions contribute`,handler:async(e,t)=>{await a.openConfigOverlay(t,z.config)}});let R=t=>{for(let n of t){let t=n.bindingId?` (${n.bindingId})`:``,r=`Leader contribution from ${n.source}${t}: ${n.message}`;M.has(r)||(M.add(r),_.recordWarning(d.UI_EVENT.leaderContributionRejected,n.message,{"leader.source":n.source,...n.bindingId?{"leader.binding.id":n.bindingId}:{}}),P?.mode===`tui`&&D(e,P,r,C))}},z=n.createDoomUiHub({leaderRegistry:j,reportDiagnostics:R});e.provide(y.DOOM_UI_HUB_SERVICE,z),e.inject([v.DOOM_MINOR_MODE_CATALOG_SERVICE],e=>{let t=(0,v.createMinorModeCatalogClient)((0,v.requireMinorModeCatalog)(e)),n=()=>A.setModes(r.projectMinorModeRecords(t.list())),i=t.subscribe(n);return n(),()=>{i(),A.setModes([])}}),h.on(`session_start`,(t,n)=>{P=n,z.setContext(n);let r=++F;if(R(j.flush()),I=n.sessionManager.getEntries().some(e=>e.type===`message`),n.mode!==`tui`)return;for(let t of M)D(e,n,t,C);let i=process.env.DOOMPI_THEME||`doom-pi-dark`,a=n.ui.setTheme(i);a.success||(D(e,n,`Could not apply Doom Pi theme: ${a.error}`,C),_.recordWarning(d.UI_EVENT.themeApplyFailed,a.error??`Unknown theme error`,{"ui.theme":i})),n.ui.setTitle(`${w} · ${m.default.basename(n.cwd)||n.cwd} · pi/coding`),n.ui.setHeader((e,t)=>new c.DoomHeader(t,n.cwd,void 0,n,()=>e.terminal.rows)),n.ui.setFooter((e,t,r)=>(N=r,new s.DoomFooter(e,t,n,r,z.footer,A))),n.ui.setEditorComponent((t,i,a)=>new o.DoomEditor(t,i,a,A,e=>{if(!e.active){n.ui.setWidget(x,void 0,{placement:S});return}n.ui.setWidget(x,(t,r)=>new l.LeaderHints(r,e,n,N,A.getModes()),{placement:S})},void 0,n.ui.theme,{registry:j,isCommandAvailable:e=>T.has(e)||h.getCommands().some(t=>t.name===e),onLeaderAction:(t,i)=>{if(P!==n||F!==r){D(e,n,`Leader action ${i} is unavailable.`,C);return}z.invokeLeaderAction(t,i)},onUnavailableCommand:t=>{_.recordWarning(d.UI_EVENT.leaderCommandUnavailable,`Leader command /${t} is unavailable.`,{"ui.kind":`command`}),D(e,n,`Leader command /${t} is unavailable.`,C)},onUnavailableAction:t=>{_.recordWarning(d.UI_EVENT.leaderActionUnavailable,`Leader action ${t} is unavailable.`,{"ui.kind":`action`}),D(e,n,`Leader action ${t} is unavailable.`,C)}})),_.recordEvent(d.UI_EVENT.shellInstalled,{"ui.theme":i,"ui.theme.applied":a.success,"ui.leader.group.count":j.getGroup([])?.options.length??0})}),h.on(`input`,(e,t)=>{if(t.mode!==`tui`||e.source!==`interactive`||I)return;I=!0;let n=O(e.text,t.cwd);n&&t.ui.setTitle(n)}),e.effect(()=>async()=>{let e=P;++F,P=void 0,z.setContext(void 0),N=void 0,z.dispose(),A.reset(),e?.mode===`tui`&&e.ui.setWidget(x,void 0,{placement:S}),await _.shutdown()},E)}async function A(e,t=d.createUiTelemetry()){let n=await(0,h.connectDoomCordisHost)(e,E),r=n.root.plugin(k,{pi:e,telemetry:t,hostMode:n.runtime.mode});try{await r}catch(e){try{await r.dispose()}finally{await n.dispose()}throw e}let i;e.on(`session_shutdown`,()=>i??=(async()=>{try{await r.dispose()}finally{await n.dispose()}})())}exports.doomPiUiExtension=A;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs"),t=require("../../services/leader/leaderRegistry.cjs"),n=require("../../services/hub/uiHub.cjs"),r=require("../../services/state/uiState.cjs"),i=require("../../services/tools/toolInventory.cjs"),a=require("../../tui/configOverlay.cjs"),o=require("../../tui/doomEditor.cjs"),s=require("../../tui/doomFooter.cjs"),c=require("../../tui/doomHeader.cjs"),l=require("../../tui/leaderHints.cjs");require("../../tui/theme.cjs");const u=require("../../tui/toolsOverlay.cjs"),d=require("../telemetry/logSinkTelemetry.cjs"),f=require("./extensionName.cjs"),p=require("./toolOverrides.cjs");let m=require("node:path");m=e.__toESM(m,1);let h=require("@agimon-ai/doompi-extension-contracts/cordis-host"),g=require("@agimon-ai/doompi-extension-contracts/mcp-status"),_=require("@agimon-ai/doompi-extension-contracts/notification"),v=require("@agimon-ai/doompi-extension-contracts/mode"),y=require("@agimon-ai/doompi-extension-contracts/ui-hub"),b=require("@agimon-ai/doompi-extension-contracts/tool-overrides");const x=`doom-pi-leader`,S=`belowEditor`,C=`warning`,w=`doom-pi`,T=new Set([`hotkeys`]),E=`@agimon-ai/doompi-ui`;function D(e,t,n,r){let i=(0,_.readDoomNotificationService)(e);if(i){try{Promise.resolve(i.request({body:n,level:r})).catch(()=>void 0)}catch{}return}t.ui.notify(n,r)}function O(e,t){let n=e.trim().replace(/\s+/g,` `);if(!n)return;let r=n.slice(0,64).trimEnd();return`${w} · ${r} · ${m.default.basename(t)||t}`}function k(e,{pi:h,telemetry:_,hostMode:k}){e.inject([b.DOOM_TOOL_OVERRIDES_SERVICE],e=>{let t=(0,b.requireDoomToolOverrides)(e),n=[],r=e=>{if(k===`composed`)return!0;let r=t.claim({source:E,tools:[e]});return r.granted&&n.push(r),r.granted};try{p.registerBuiltinToolUi(h,process.cwd(),r)}catch(e){for(let e of n)e.dispose();throw e}return()=>{for(let e of n)e.dispose()}});let A=new r.DoomUiState,j=new t.DoomLeaderRegistry,M=new Set,N,P,F=0,I=!1,L=()=>(0,g.readDoomMcpStatus)(e)?.getSnapshot().servers;h.registerCommand(`tools`,{description:`Browse the tools available in this session`,handler:async(e,t)=>{let n=L(),r=i.buildToolSources({tools:h.getAllTools(),activeTools:h.getActiveTools(),...n?{mcpServers:n}:{},resolveExtensionName:f.extensionName,resolveExtensionPackageName:f.extensionPackageName,resolveExtensionToolSource:e=>f.extensionToolSource(h,e)});await u.openToolsOverlay(t,r)}}),h.registerCommand(`config`,{description:`Browse and change the settings extensions contribute`,handler:async(e,t)=>{await a.openConfigOverlay(t,z.config)}});let R=t=>{for(let n of t){let t=n.bindingId?` (${n.bindingId})`:``,r=`Leader contribution from ${n.source}${t}: ${n.message}`;M.has(r)||(M.add(r),_.recordWarning(d.UI_EVENT.leaderContributionRejected,n.message,{"leader.source":n.source,...n.bindingId?{"leader.binding.id":n.bindingId}:{}}),P?.mode===`tui`&&D(e,P,r,C))}},z=n.createDoomUiHub({leaderRegistry:j,reportDiagnostics:R});e.provide(y.DOOM_UI_HUB_SERVICE,z),e.inject([v.DOOM_MINOR_MODE_CATALOG_SERVICE],e=>{let t=(0,v.createMinorModeCatalogClient)((0,v.requireMinorModeCatalog)(e)),n=()=>A.setModes(r.projectMinorModeRecords(t.list())),i=t.subscribe(n);return n(),()=>{i(),A.setModes([])}}),h.on(`session_start`,(t,n)=>{P=n,z.setContext(n);let r=++F;if(R(j.flush()),I=n.sessionManager.getEntries().some(e=>e.type===`message`),n.mode!==`tui`)return;for(let t of M)D(e,n,t,C);let i=process.env.DOOMPI_THEME||`doom-pi-dark`,a=n.ui.setTheme(i);a.success||(D(e,n,`Could not apply Doom Pi theme: ${a.error}`,C),_.recordWarning(d.UI_EVENT.themeApplyFailed,a.error??`Unknown theme error`,{"ui.theme":i})),n.ui.setTitle(`${w} · ${m.default.basename(n.cwd)||n.cwd} · pi/coding`),n.ui.setHeader((e,t)=>new c.DoomHeader(t,n.cwd,void 0,n,()=>e.terminal.rows)),n.ui.setFooter((e,t,r)=>(N=r,new s.DoomFooter(e,t,n,r,z.footer,A))),n.ui.setEditorComponent((t,i,a)=>new o.DoomEditor(t,i,a,A,e=>{if(!e.active){n.ui.setWidget(x,void 0,{placement:S});return}n.ui.setWidget(x,(t,r)=>new l.LeaderHints(r,e,n,N,A.getModes()),{placement:S})},void 0,n.ui.theme,{registry:j,isCommandAvailable:e=>T.has(e)||h.getCommands().some(t=>t.name===e),onLeaderAction:(t,i)=>{if(P!==n||F!==r){D(e,n,`Leader action ${i} is unavailable.`,C);return}z.invokeLeaderAction(t,i)},onUnavailableCommand:t=>{_.recordWarning(d.UI_EVENT.leaderCommandUnavailable,`Leader command /${t} is unavailable.`,{"ui.kind":`command`}),D(e,n,`Leader command /${t} is unavailable.`,C)},onUnavailableAction:t=>{_.recordWarning(d.UI_EVENT.leaderActionUnavailable,`Leader action ${t} is unavailable.`,{"ui.kind":`action`}),D(e,n,`Leader action ${t} is unavailable.`,C)}})),_.recordEvent(d.UI_EVENT.shellInstalled,{"ui.theme":i,"ui.theme.applied":a.success,"ui.leader.group.count":j.getGroup([])?.options.length??0})}),h.on(`input`,(e,t)=>{if(t.mode!==`tui`||e.source!==`interactive`||I)return;I=!0;let n=O(e.text,t.cwd);n&&t.ui.setTitle(n)}),e.effect(()=>async()=>{let e=P;++F,P=void 0,z.setContext(void 0),N=void 0,z.dispose(),A.reset(),e?.mode===`tui`&&e.ui.setWidget(x,void 0,{placement:S}),await _.shutdown()},E)}async function A(e,t=d.createUiTelemetry()){let n=await(0,h.connectDoomCordisHost)(e,E),r=n.root.plugin(k,{pi:e,telemetry:t,hostMode:n.runtime.mode});try{await r}catch(e){try{await r.dispose()}finally{await n.dispose()}throw e}let i;e.on(`session_shutdown`,()=>i??=(async()=>{try{await r.dispose()}finally{await n.dispose()}})())}exports.doomPiUiExtension=A;
2
2
  //# sourceMappingURL=extension.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.cjs","names":["readDoomNotificationService","path","DOOM_TOOL_OVERRIDES_SERVICE","requireDoomToolOverrides","DoomUiState","DoomLeaderRegistry","readDoomMcpStatus","buildToolSources","extensionName","extensionToolSource","openToolsOverlay","openConfigOverlay","UI_EVENT","createDoomUiHub","DOOM_UI_HUB_SERVICE","DOOM_MINOR_MODE_CATALOG_SERVICE","createMinorModeCatalogClient","requireMinorModeCatalog","projectMinorModeRecords","DoomHeader","DoomFooter","DoomEditor","LeaderHints","createUiTelemetry","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import path from 'node:path';\nimport { connectDoomCordisHost, type DoomCordisHostMode } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { readDoomMcpStatus } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport {\n type DoomNotificationLevel,\n readDoomNotificationService,\n} from '@agimon-ai/doompi-extension-contracts/notification';\nimport {\n DOOM_MINOR_MODE_CATALOG_SERVICE,\n createMinorModeCatalogClient,\n requireMinorModeCatalog,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport { DOOM_UI_HUB_SERVICE } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport {\n DOOM_TOOL_OVERRIDES_SERVICE,\n type DoomToolOverrideRegistration,\n requireDoomToolOverrides,\n} from '@agimon-ai/doompi-extension-contracts/tool-overrides';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext, ReadonlyFooterDataProvider } from '@earendil-works/pi-coding-agent';\nimport { type DoomLeaderDiagnostic, DoomLeaderRegistry } from '../../services/leader/leaderRegistry.ts';\nimport { createDoomUiHub } from '../../services/hub/uiHub.ts';\nimport { DoomUiState, type LeaderSnapshot, projectMinorModeRecords } from '../../services/state/uiState.ts';\nimport { buildToolSources, type McpServerStatus } from '../../services/tools/toolInventory.ts';\nimport { openConfigOverlay } from '../../tui/configOverlay.ts';\nimport { DoomEditor } from '../../tui/doomEditor.ts';\nimport { DoomFooter } from '../../tui/doomFooter.ts';\nimport { DoomHeader } from '../../tui/doomHeader.ts';\nimport { LeaderHints } from '../../tui/leaderHints.ts';\nimport { DEFAULT_THEME_NAME } from '../../tui/theme.ts';\nimport { openToolsOverlay } from '../../tui/toolsOverlay.ts';\nimport { createUiTelemetry, UI_EVENT, type UiTelemetry } from '../telemetry/logSinkTelemetry.ts';\nimport { extensionName, extensionToolSource } from './extensionName.ts';\nimport { registerBuiltinToolUi } from './toolOverrides.ts';\n\nconst LEADER_WIDGET_KEY = 'doom-pi-leader';\nconst TUI_MODE = 'tui';\nconst WIDGET_PLACEMENT = 'belowEditor';\nconst WARNING_STYLE = 'warning';\nconst THEME_ENVIRONMENT_KEY = 'DOOMPI_THEME';\nconst HOTKEYS_COMMAND = 'hotkeys';\nconst TOOLS_COMMAND = 'tools';\nconst CONFIG_COMMAND = 'config';\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_MESSAGE_ENTRY = 'message';\nconst INPUT_EVENT = 'input';\nconst INTERACTIVE_INPUT_SOURCE = 'interactive';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\nconst TITLE_MESSAGE_MAX_LENGTH = 64;\nconst TITLE_PREFIX = 'doom-pi';\nconst BUILTIN_LEADER_COMMANDS = new Set([HOTKEYS_COMMAND]);\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-ui';\n\nfunction notify(cordis: Context, context: ExtensionContext, body: string, level: DoomNotificationLevel): void {\n const service = readDoomNotificationService(cordis);\n if (service) {\n try {\n void Promise.resolve(service.request({ body, level })).catch(() => undefined);\n } catch {\n // Notification delivery is best effort and does not fall back after routing.\n }\n return;\n }\n context.ui.notify(body, level);\n}\n\nfunction createMessageTitle(message: string, cwd: string): string | undefined {\n const normalizedMessage = message.trim().replace(/\\s+/g, ' ');\n if (!normalizedMessage) return undefined;\n const messageTitle = normalizedMessage.slice(0, TITLE_MESSAGE_MAX_LENGTH).trimEnd();\n return `${TITLE_PREFIX} · ${messageTitle} · ${path.basename(cwd) || cwd}`;\n}\n\ninterface UiPluginOptions {\n readonly pi: ExtensionAPI;\n readonly telemetry: UiTelemetry;\n readonly hostMode: DoomCordisHostMode;\n}\n\nfunction doomPiUiPlugin(cordis: Context, { pi, telemetry, hostMode }: UiPluginOptions): void {\n cordis.inject([DOOM_TOOL_OVERRIDES_SERVICE], (overridesContext) => {\n const toolOverrides = requireDoomToolOverrides(overridesContext);\n const registrations: DoomToolOverrideRegistration[] = [];\n const shouldRegister = (tool: string): boolean => {\n if (hostMode === 'composed') return true;\n const registration = toolOverrides.claim({ source: PACKAGE_SOURCE, tools: [tool] });\n if (registration.granted) registrations.push(registration);\n return registration.granted;\n };\n try {\n registerBuiltinToolUi(pi, process.cwd(), shouldRegister);\n } catch (error) {\n for (const registration of registrations) registration.dispose();\n throw error;\n }\n return () => {\n for (const registration of registrations) registration.dispose();\n };\n });\n const uiState = new DoomUiState();\n const leaderRegistry = new DoomLeaderRegistry();\n const pendingDiagnostics = new Set<string>();\n let footerData: ReadonlyFooterDataProvider | undefined;\n let activeContext: ExtensionContext | undefined;\n let sessionGeneration = 0;\n let hasSessionMessage = false;\n\n /**\n * Asks the MCP extension what it is currently connected to.\n *\n * No answer is a normal session: `--no-mcp` leaves nobody providing the query,\n * and the panel simply lists MCP tools under the extension that registered them.\n */\n const readMcpServers = (): readonly McpServerStatus[] | undefined => readDoomMcpStatus(cordis)?.getSnapshot().servers;\n\n pi.registerCommand(TOOLS_COMMAND, {\n description: 'Browse the tools available in this session',\n handler: async (_args, ctx) => {\n // Read at open time so a reconnected server or a plan-mode tool swap shows\n // up without restarting the session.\n const mcpServers = readMcpServers();\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\n resolveExtensionName: extensionName,\n resolveExtensionToolSource: (toolName) => extensionToolSource(pi, toolName),\n });\n await openToolsOverlay(ctx, sources);\n },\n });\n\n pi.registerCommand(CONFIG_COMMAND, {\n description: 'Browse and change the settings extensions contribute',\n handler: async (_args, ctx) => {\n // The registry is passed live rather than snapshotted: a contributor can be\n // fetching or installing something while the panel is open, and that\n // progress is meant to land in it.\n await openConfigOverlay(ctx, hub.config);\n },\n });\n\n const reportDiagnostics = (diagnostics: readonly DoomLeaderDiagnostic[]): void => {\n for (const diagnostic of diagnostics) {\n const binding = diagnostic.bindingId ? ` (${diagnostic.bindingId})` : '';\n const message = `Leader contribution from ${diagnostic.source}${binding}: ${diagnostic.message}`;\n if (pendingDiagnostics.has(message)) continue;\n pendingDiagnostics.add(message);\n // A rejected contribution silently drops another extension's keybindings,\n // and the notice below only reaches a run that has a TUI attached.\n void telemetry.recordWarning(UI_EVENT.leaderContributionRejected, diagnostic.message, {\n 'leader.source': diagnostic.source,\n ...(diagnostic.bindingId ? { 'leader.binding.id': diagnostic.bindingId } : {}),\n });\n if (activeContext?.mode === TUI_MODE) notify(cordis, activeContext, message, WARNING_STYLE);\n }\n };\n\n const hub = createDoomUiHub({ leaderRegistry, reportDiagnostics });\n cordis.provide(DOOM_UI_HUB_SERVICE, hub);\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE], (modeContext) => {\n const catalog = createMinorModeCatalogClient(requireMinorModeCatalog(modeContext));\n const update = (): void => uiState.setModes(projectMinorModeRecords(catalog.list()));\n const dispose = catalog.subscribe(update);\n update();\n return () => {\n dispose();\n uiState.setModes([]);\n };\n });\n\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n activeContext = ctx;\n hub.setContext(ctx);\n const activeGeneration = ++sessionGeneration;\n reportDiagnostics(leaderRegistry.flush());\n hasSessionMessage = ctx.sessionManager.getEntries().some((entry) => entry.type === SESSION_MESSAGE_ENTRY);\n if (ctx.mode !== TUI_MODE) return;\n for (const message of pendingDiagnostics) notify(cordis, ctx, message, WARNING_STYLE);\n\n const themeName = process.env[THEME_ENVIRONMENT_KEY] || DEFAULT_THEME_NAME;\n const themeResult = ctx.ui.setTheme(themeName);\n if (!themeResult.success) {\n notify(cordis, ctx, `Could not apply Doom Pi theme: ${themeResult.error}`, WARNING_STYLE);\n void telemetry.recordWarning(UI_EVENT.themeApplyFailed, themeResult.error ?? 'Unknown theme error', {\n 'ui.theme': themeName,\n });\n }\n\n ctx.ui.setTitle(`${TITLE_PREFIX} · ${path.basename(ctx.cwd) || ctx.cwd} · pi/coding`);\n ctx.ui.setHeader((tui, theme) => new DoomHeader(theme, ctx.cwd, undefined, ctx, () => tui.terminal.rows));\n ctx.ui.setFooter((tui, theme, data) => {\n footerData = data;\n return new DoomFooter(tui, theme, ctx, data, hub.footer, uiState);\n });\n ctx.ui.setEditorComponent(\n (tui, editorTheme, keybindings) =>\n new DoomEditor(\n tui,\n editorTheme,\n keybindings,\n uiState,\n (snapshot: LeaderSnapshot) => {\n if (!snapshot.active) {\n ctx.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n return;\n }\n ctx.ui.setWidget(\n LEADER_WIDGET_KEY,\n (_widgetTui, widgetTheme) => new LeaderHints(widgetTheme, snapshot, ctx, footerData, uiState.getModes()),\n { placement: WIDGET_PLACEMENT },\n );\n },\n undefined,\n ctx.ui.theme,\n {\n registry: leaderRegistry,\n isCommandAvailable: (commandName) =>\n BUILTIN_LEADER_COMMANDS.has(commandName) ||\n pi.getCommands().some((command) => command.name === commandName),\n onLeaderAction: (source, action) => {\n if (activeContext !== ctx || sessionGeneration !== activeGeneration) {\n notify(cordis, ctx, `Leader action ${action} is unavailable.`, WARNING_STYLE);\n return;\n }\n hub.invokeLeaderAction(source, action);\n },\n onUnavailableCommand: (commandName) => {\n void telemetry.recordWarning(\n UI_EVENT.leaderCommandUnavailable,\n `Leader command /${commandName} is unavailable.`,\n { 'ui.kind': 'command' },\n );\n notify(cordis, ctx, `Leader command /${commandName} is unavailable.`, WARNING_STYLE);\n },\n onUnavailableAction: (action) => {\n void telemetry.recordWarning(\n UI_EVENT.leaderActionUnavailable,\n `Leader action ${action} is unavailable.`,\n {\n 'ui.kind': 'action',\n },\n );\n notify(cordis, ctx, `Leader action ${action} is unavailable.`, WARNING_STYLE);\n },\n },\n ),\n );\n\n void telemetry.recordEvent(UI_EVENT.shellInstalled, {\n 'ui.theme': themeName,\n 'ui.theme.applied': themeResult.success,\n 'ui.leader.group.count': leaderRegistry.getGroup([])?.options.length ?? 0,\n });\n });\n\n pi.on(INPUT_EVENT, (event, ctx) => {\n if (ctx.mode !== TUI_MODE || event.source !== INTERACTIVE_INPUT_SOURCE || hasSessionMessage) return;\n hasSessionMessage = true;\n const title = createMessageTitle(event.text, ctx.cwd);\n if (title) ctx.ui.setTitle(title);\n });\n\n cordis.effect(\n () => async () => {\n const context = activeContext;\n ++sessionGeneration;\n activeContext = undefined;\n hub.setContext(undefined);\n footerData = undefined;\n hub.dispose();\n uiState.reset();\n if (context?.mode === TUI_MODE) {\n context.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n }\n await telemetry.shutdown();\n },\n PACKAGE_SOURCE,\n );\n}\n\n/** The package's standard Pi factory, mounted under the session Cordis host. */\nexport async function doomPiUiExtension(pi: ExtensionAPI, telemetry: UiTelemetry = createUiTelemetry()): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(doomPiUiPlugin, { pi, telemetry, hostMode: connection.runtime.mode });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n\n let disposal: Promise<void> | undefined;\n pi.on(\n SESSION_SHUTDOWN_EVENT,\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.cjs","names":["readDoomNotificationService","path","DOOM_TOOL_OVERRIDES_SERVICE","requireDoomToolOverrides","DoomUiState","DoomLeaderRegistry","readDoomMcpStatus","buildToolSources","extensionName","extensionPackageName","extensionToolSource","openToolsOverlay","openConfigOverlay","UI_EVENT","createDoomUiHub","DOOM_UI_HUB_SERVICE","DOOM_MINOR_MODE_CATALOG_SERVICE","createMinorModeCatalogClient","requireMinorModeCatalog","projectMinorModeRecords","DoomHeader","DoomFooter","DoomEditor","LeaderHints","createUiTelemetry","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import path from 'node:path';\nimport { connectDoomCordisHost, type DoomCordisHostMode } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { readDoomMcpStatus } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport {\n type DoomNotificationLevel,\n readDoomNotificationService,\n} from '@agimon-ai/doompi-extension-contracts/notification';\nimport {\n DOOM_MINOR_MODE_CATALOG_SERVICE,\n createMinorModeCatalogClient,\n requireMinorModeCatalog,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport { DOOM_UI_HUB_SERVICE } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport {\n DOOM_TOOL_OVERRIDES_SERVICE,\n type DoomToolOverrideRegistration,\n requireDoomToolOverrides,\n} from '@agimon-ai/doompi-extension-contracts/tool-overrides';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext, ReadonlyFooterDataProvider } from '@earendil-works/pi-coding-agent';\nimport { type DoomLeaderDiagnostic, DoomLeaderRegistry } from '../../services/leader/leaderRegistry.ts';\nimport { createDoomUiHub } from '../../services/hub/uiHub.ts';\nimport { DoomUiState, type LeaderSnapshot, projectMinorModeRecords } from '../../services/state/uiState.ts';\nimport { buildToolSources, type McpServerStatus } from '../../services/tools/toolInventory.ts';\nimport { openConfigOverlay } from '../../tui/configOverlay.ts';\nimport { DoomEditor } from '../../tui/doomEditor.ts';\nimport { DoomFooter } from '../../tui/doomFooter.ts';\nimport { DoomHeader } from '../../tui/doomHeader.ts';\nimport { LeaderHints } from '../../tui/leaderHints.ts';\nimport { DEFAULT_THEME_NAME } from '../../tui/theme.ts';\nimport { openToolsOverlay } from '../../tui/toolsOverlay.ts';\nimport { createUiTelemetry, UI_EVENT, type UiTelemetry } from '../telemetry/logSinkTelemetry.ts';\nimport { extensionName, extensionPackageName, extensionToolSource } from './extensionName.ts';\nimport { registerBuiltinToolUi } from './toolOverrides.ts';\n\nconst LEADER_WIDGET_KEY = 'doom-pi-leader';\nconst TUI_MODE = 'tui';\nconst WIDGET_PLACEMENT = 'belowEditor';\nconst WARNING_STYLE = 'warning';\nconst THEME_ENVIRONMENT_KEY = 'DOOMPI_THEME';\nconst HOTKEYS_COMMAND = 'hotkeys';\nconst TOOLS_COMMAND = 'tools';\nconst CONFIG_COMMAND = 'config';\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_MESSAGE_ENTRY = 'message';\nconst INPUT_EVENT = 'input';\nconst INTERACTIVE_INPUT_SOURCE = 'interactive';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\nconst TITLE_MESSAGE_MAX_LENGTH = 64;\nconst TITLE_PREFIX = 'doom-pi';\nconst BUILTIN_LEADER_COMMANDS = new Set([HOTKEYS_COMMAND]);\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-ui';\n\nfunction notify(cordis: Context, context: ExtensionContext, body: string, level: DoomNotificationLevel): void {\n const service = readDoomNotificationService(cordis);\n if (service) {\n try {\n void Promise.resolve(service.request({ body, level })).catch(() => undefined);\n } catch {\n // Notification delivery is best effort and does not fall back after routing.\n }\n return;\n }\n context.ui.notify(body, level);\n}\n\nfunction createMessageTitle(message: string, cwd: string): string | undefined {\n const normalizedMessage = message.trim().replace(/\\s+/g, ' ');\n if (!normalizedMessage) return undefined;\n const messageTitle = normalizedMessage.slice(0, TITLE_MESSAGE_MAX_LENGTH).trimEnd();\n return `${TITLE_PREFIX} · ${messageTitle} · ${path.basename(cwd) || cwd}`;\n}\n\ninterface UiPluginOptions {\n readonly pi: ExtensionAPI;\n readonly telemetry: UiTelemetry;\n readonly hostMode: DoomCordisHostMode;\n}\n\nfunction doomPiUiPlugin(cordis: Context, { pi, telemetry, hostMode }: UiPluginOptions): void {\n cordis.inject([DOOM_TOOL_OVERRIDES_SERVICE], (overridesContext) => {\n const toolOverrides = requireDoomToolOverrides(overridesContext);\n const registrations: DoomToolOverrideRegistration[] = [];\n const shouldRegister = (tool: string): boolean => {\n if (hostMode === 'composed') return true;\n const registration = toolOverrides.claim({ source: PACKAGE_SOURCE, tools: [tool] });\n if (registration.granted) registrations.push(registration);\n return registration.granted;\n };\n try {\n registerBuiltinToolUi(pi, process.cwd(), shouldRegister);\n } catch (error) {\n for (const registration of registrations) registration.dispose();\n throw error;\n }\n return () => {\n for (const registration of registrations) registration.dispose();\n };\n });\n const uiState = new DoomUiState();\n const leaderRegistry = new DoomLeaderRegistry();\n const pendingDiagnostics = new Set<string>();\n let footerData: ReadonlyFooterDataProvider | undefined;\n let activeContext: ExtensionContext | undefined;\n let sessionGeneration = 0;\n let hasSessionMessage = false;\n\n /**\n * Asks the MCP extension what it is currently connected to.\n *\n * No answer is a normal session: `--no-mcp` leaves nobody providing the query,\n * and the panel simply lists MCP tools under the extension that registered them.\n */\n const readMcpServers = (): readonly McpServerStatus[] | undefined => readDoomMcpStatus(cordis)?.getSnapshot().servers;\n\n pi.registerCommand(TOOLS_COMMAND, {\n description: 'Browse the tools available in this session',\n handler: async (_args, ctx) => {\n // Read at open time so a reconnected server or a plan-mode tool swap shows\n // up without restarting the session.\n const mcpServers = readMcpServers();\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\n resolveExtensionName: extensionName,\n resolveExtensionPackageName: extensionPackageName,\n resolveExtensionToolSource: (toolName) => extensionToolSource(pi, toolName),\n });\n await openToolsOverlay(ctx, sources);\n },\n });\n\n pi.registerCommand(CONFIG_COMMAND, {\n description: 'Browse and change the settings extensions contribute',\n handler: async (_args, ctx) => {\n // The registry is passed live rather than snapshotted: a contributor can be\n // fetching or installing something while the panel is open, and that\n // progress is meant to land in it.\n await openConfigOverlay(ctx, hub.config);\n },\n });\n\n const reportDiagnostics = (diagnostics: readonly DoomLeaderDiagnostic[]): void => {\n for (const diagnostic of diagnostics) {\n const binding = diagnostic.bindingId ? ` (${diagnostic.bindingId})` : '';\n const message = `Leader contribution from ${diagnostic.source}${binding}: ${diagnostic.message}`;\n if (pendingDiagnostics.has(message)) continue;\n pendingDiagnostics.add(message);\n // A rejected contribution silently drops another extension's keybindings,\n // and the notice below only reaches a run that has a TUI attached.\n void telemetry.recordWarning(UI_EVENT.leaderContributionRejected, diagnostic.message, {\n 'leader.source': diagnostic.source,\n ...(diagnostic.bindingId ? { 'leader.binding.id': diagnostic.bindingId } : {}),\n });\n if (activeContext?.mode === TUI_MODE) notify(cordis, activeContext, message, WARNING_STYLE);\n }\n };\n\n const hub = createDoomUiHub({ leaderRegistry, reportDiagnostics });\n cordis.provide(DOOM_UI_HUB_SERVICE, hub);\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE], (modeContext) => {\n const catalog = createMinorModeCatalogClient(requireMinorModeCatalog(modeContext));\n const update = (): void => uiState.setModes(projectMinorModeRecords(catalog.list()));\n const dispose = catalog.subscribe(update);\n update();\n return () => {\n dispose();\n uiState.setModes([]);\n };\n });\n\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n activeContext = ctx;\n hub.setContext(ctx);\n const activeGeneration = ++sessionGeneration;\n reportDiagnostics(leaderRegistry.flush());\n hasSessionMessage = ctx.sessionManager.getEntries().some((entry) => entry.type === SESSION_MESSAGE_ENTRY);\n if (ctx.mode !== TUI_MODE) return;\n for (const message of pendingDiagnostics) notify(cordis, ctx, message, WARNING_STYLE);\n\n const themeName = process.env[THEME_ENVIRONMENT_KEY] || DEFAULT_THEME_NAME;\n const themeResult = ctx.ui.setTheme(themeName);\n if (!themeResult.success) {\n notify(cordis, ctx, `Could not apply Doom Pi theme: ${themeResult.error}`, WARNING_STYLE);\n void telemetry.recordWarning(UI_EVENT.themeApplyFailed, themeResult.error ?? 'Unknown theme error', {\n 'ui.theme': themeName,\n });\n }\n\n ctx.ui.setTitle(`${TITLE_PREFIX} · ${path.basename(ctx.cwd) || ctx.cwd} · pi/coding`);\n ctx.ui.setHeader((tui, theme) => new DoomHeader(theme, ctx.cwd, undefined, ctx, () => tui.terminal.rows));\n ctx.ui.setFooter((tui, theme, data) => {\n footerData = data;\n return new DoomFooter(tui, theme, ctx, data, hub.footer, uiState);\n });\n ctx.ui.setEditorComponent(\n (tui, editorTheme, keybindings) =>\n new DoomEditor(\n tui,\n editorTheme,\n keybindings,\n uiState,\n (snapshot: LeaderSnapshot) => {\n if (!snapshot.active) {\n ctx.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n return;\n }\n ctx.ui.setWidget(\n LEADER_WIDGET_KEY,\n (_widgetTui, widgetTheme) => new LeaderHints(widgetTheme, snapshot, ctx, footerData, uiState.getModes()),\n { placement: WIDGET_PLACEMENT },\n );\n },\n undefined,\n ctx.ui.theme,\n {\n registry: leaderRegistry,\n isCommandAvailable: (commandName) =>\n BUILTIN_LEADER_COMMANDS.has(commandName) ||\n pi.getCommands().some((command) => command.name === commandName),\n onLeaderAction: (source, action) => {\n if (activeContext !== ctx || sessionGeneration !== activeGeneration) {\n notify(cordis, ctx, `Leader action ${action} is unavailable.`, WARNING_STYLE);\n return;\n }\n hub.invokeLeaderAction(source, action);\n },\n onUnavailableCommand: (commandName) => {\n void telemetry.recordWarning(\n UI_EVENT.leaderCommandUnavailable,\n `Leader command /${commandName} is unavailable.`,\n { 'ui.kind': 'command' },\n );\n notify(cordis, ctx, `Leader command /${commandName} is unavailable.`, WARNING_STYLE);\n },\n onUnavailableAction: (action) => {\n void telemetry.recordWarning(\n UI_EVENT.leaderActionUnavailable,\n `Leader action ${action} is unavailable.`,\n {\n 'ui.kind': 'action',\n },\n );\n notify(cordis, ctx, `Leader action ${action} is unavailable.`, WARNING_STYLE);\n },\n },\n ),\n );\n\n void telemetry.recordEvent(UI_EVENT.shellInstalled, {\n 'ui.theme': themeName,\n 'ui.theme.applied': themeResult.success,\n 'ui.leader.group.count': leaderRegistry.getGroup([])?.options.length ?? 0,\n });\n });\n\n pi.on(INPUT_EVENT, (event, ctx) => {\n if (ctx.mode !== TUI_MODE || event.source !== INTERACTIVE_INPUT_SOURCE || hasSessionMessage) return;\n hasSessionMessage = true;\n const title = createMessageTitle(event.text, ctx.cwd);\n if (title) ctx.ui.setTitle(title);\n });\n\n cordis.effect(\n () => async () => {\n const context = activeContext;\n ++sessionGeneration;\n activeContext = undefined;\n hub.setContext(undefined);\n footerData = undefined;\n hub.dispose();\n uiState.reset();\n if (context?.mode === TUI_MODE) {\n context.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n }\n await telemetry.shutdown();\n },\n PACKAGE_SOURCE,\n );\n}\n\n/** The package's standard Pi factory, mounted under the session Cordis host. */\nexport async function doomPiUiExtension(pi: ExtensionAPI, telemetry: UiTelemetry = createUiTelemetry()): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(doomPiUiPlugin, { pi, telemetry, hostMode: connection.runtime.mode });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n\n let disposal: Promise<void> | undefined;\n pi.on(\n SESSION_SHUTDOWN_EVENT,\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBA0RsB,kBAAkB,IAAI,cAAc,YAAW,cAAoC"}
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBA2RsB,kBAAkB,IAAI,cAAc,YAAW,cAAoC"}
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBA0RsB,kBAAkB,IAAI,cAAc,YAAW,cAAoC"}
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBA2RsB,kBAAkB,IAAI,cAAc,YAAW,cAAoC"}
@@ -1,2 +1,2 @@
1
- import{DoomLeaderRegistry as e}from"../../services/leader/leaderRegistry.mjs";import{createDoomUiHub as t}from"../../services/hub/uiHub.mjs";import{DoomUiState as n,projectMinorModeRecords as r}from"../../services/state/uiState.mjs";import{buildToolSources as i}from"../../services/tools/toolInventory.mjs";import{openConfigOverlay as a}from"../../tui/configOverlay.mjs";import{DoomEditor as o}from"../../tui/doomEditor.mjs";import{DoomFooter as s}from"../../tui/doomFooter.mjs";import{DoomHeader as c}from"../../tui/doomHeader.mjs";import{LeaderHints as l}from"../../tui/leaderHints.mjs";import"../../tui/theme.mjs";import{openToolsOverlay as u}from"../../tui/toolsOverlay.mjs";import{UI_EVENT as d,createUiTelemetry as f}from"../telemetry/logSinkTelemetry.mjs";import{extensionName as p,extensionToolSource as m}from"./extensionName.mjs";import{registerBuiltinToolUi as h}from"./toolOverrides.mjs";import g from"node:path";import{connectDoomCordisHost as _}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{readDoomMcpStatus as v}from"@agimon-ai/doompi-extension-contracts/mcp-status";import{readDoomNotificationService as y}from"@agimon-ai/doompi-extension-contracts/notification";import{DOOM_MINOR_MODE_CATALOG_SERVICE as b,createMinorModeCatalogClient as x,requireMinorModeCatalog as S}from"@agimon-ai/doompi-extension-contracts/mode";import{DOOM_UI_HUB_SERVICE as C}from"@agimon-ai/doompi-extension-contracts/ui-hub";import{DOOM_TOOL_OVERRIDES_SERVICE as w,requireDoomToolOverrides as T}from"@agimon-ai/doompi-extension-contracts/tool-overrides";const E=`doom-pi-leader`,D=`belowEditor`,O=`warning`,k=`doom-pi`,A=new Set([`hotkeys`]),j=`@agimon-ai/doompi-ui`;function M(e,t,n,r){let i=y(e);if(i){try{Promise.resolve(i.request({body:n,level:r})).catch(()=>void 0)}catch{}return}t.ui.notify(n,r)}function N(e,t){let n=e.trim().replace(/\s+/g,` `);if(!n)return;let r=n.slice(0,64).trimEnd();return`${k} · ${r} · ${g.basename(t)||t}`}function P(f,{pi:_,telemetry:y,hostMode:P}){f.inject([w],e=>{let t=T(e),n=[],r=e=>{if(P===`composed`)return!0;let r=t.claim({source:j,tools:[e]});return r.granted&&n.push(r),r.granted};try{h(_,process.cwd(),r)}catch(e){for(let e of n)e.dispose();throw e}return()=>{for(let e of n)e.dispose()}});let F=new n,I=new e,L=new Set,R,z,B=0,V=!1,H=()=>v(f)?.getSnapshot().servers;_.registerCommand(`tools`,{description:`Browse the tools available in this session`,handler:async(e,t)=>{let n=H(),r=i({tools:_.getAllTools(),activeTools:_.getActiveTools(),...n?{mcpServers:n}:{},resolveExtensionName:p,resolveExtensionToolSource:e=>m(_,e)});await u(t,r)}}),_.registerCommand(`config`,{description:`Browse and change the settings extensions contribute`,handler:async(e,t)=>{await a(t,W.config)}});let U=e=>{for(let t of e){let e=t.bindingId?` (${t.bindingId})`:``,n=`Leader contribution from ${t.source}${e}: ${t.message}`;L.has(n)||(L.add(n),y.recordWarning(d.leaderContributionRejected,t.message,{"leader.source":t.source,...t.bindingId?{"leader.binding.id":t.bindingId}:{}}),z?.mode===`tui`&&M(f,z,n,O))}},W=t({leaderRegistry:I,reportDiagnostics:U});f.provide(C,W),f.inject([b],e=>{let t=x(S(e)),n=()=>F.setModes(r(t.list())),i=t.subscribe(n);return n(),()=>{i(),F.setModes([])}}),_.on(`session_start`,(e,t)=>{z=t,W.setContext(t);let n=++B;if(U(I.flush()),V=t.sessionManager.getEntries().some(e=>e.type===`message`),t.mode!==`tui`)return;for(let e of L)M(f,t,e,O);let r=process.env.DOOMPI_THEME||`doom-pi-dark`,i=t.ui.setTheme(r);i.success||(M(f,t,`Could not apply Doom Pi theme: ${i.error}`,O),y.recordWarning(d.themeApplyFailed,i.error??`Unknown theme error`,{"ui.theme":r})),t.ui.setTitle(`${k} · ${g.basename(t.cwd)||t.cwd} · pi/coding`),t.ui.setHeader((e,n)=>new c(n,t.cwd,void 0,t,()=>e.terminal.rows)),t.ui.setFooter((e,n,r)=>(R=r,new s(e,n,t,r,W.footer,F))),t.ui.setEditorComponent((e,r,i)=>new o(e,r,i,F,e=>{if(!e.active){t.ui.setWidget(E,void 0,{placement:D});return}t.ui.setWidget(E,(n,r)=>new l(r,e,t,R,F.getModes()),{placement:D})},void 0,t.ui.theme,{registry:I,isCommandAvailable:e=>A.has(e)||_.getCommands().some(t=>t.name===e),onLeaderAction:(e,r)=>{if(z!==t||B!==n){M(f,t,`Leader action ${r} is unavailable.`,O);return}W.invokeLeaderAction(e,r)},onUnavailableCommand:e=>{y.recordWarning(d.leaderCommandUnavailable,`Leader command /${e} is unavailable.`,{"ui.kind":`command`}),M(f,t,`Leader command /${e} is unavailable.`,O)},onUnavailableAction:e=>{y.recordWarning(d.leaderActionUnavailable,`Leader action ${e} is unavailable.`,{"ui.kind":`action`}),M(f,t,`Leader action ${e} is unavailable.`,O)}})),y.recordEvent(d.shellInstalled,{"ui.theme":r,"ui.theme.applied":i.success,"ui.leader.group.count":I.getGroup([])?.options.length??0})}),_.on(`input`,(e,t)=>{if(t.mode!==`tui`||e.source!==`interactive`||V)return;V=!0;let n=N(e.text,t.cwd);n&&t.ui.setTitle(n)}),f.effect(()=>async()=>{let e=z;++B,z=void 0,W.setContext(void 0),R=void 0,W.dispose(),F.reset(),e?.mode===`tui`&&e.ui.setWidget(E,void 0,{placement:D}),await y.shutdown()},j)}async function F(e,t=f()){let n=await _(e,j),r=n.root.plugin(P,{pi:e,telemetry:t,hostMode:n.runtime.mode});try{await r}catch(e){try{await r.dispose()}finally{await n.dispose()}throw e}let i;e.on(`session_shutdown`,()=>i??=(async()=>{try{await r.dispose()}finally{await n.dispose()}})())}export{F as doomPiUiExtension};
1
+ import{DoomLeaderRegistry as e}from"../../services/leader/leaderRegistry.mjs";import{createDoomUiHub as t}from"../../services/hub/uiHub.mjs";import{DoomUiState as n,projectMinorModeRecords as r}from"../../services/state/uiState.mjs";import{buildToolSources as i}from"../../services/tools/toolInventory.mjs";import{openConfigOverlay as a}from"../../tui/configOverlay.mjs";import{DoomEditor as o}from"../../tui/doomEditor.mjs";import{DoomFooter as s}from"../../tui/doomFooter.mjs";import{DoomHeader as c}from"../../tui/doomHeader.mjs";import{LeaderHints as l}from"../../tui/leaderHints.mjs";import"../../tui/theme.mjs";import{openToolsOverlay as u}from"../../tui/toolsOverlay.mjs";import{UI_EVENT as d,createUiTelemetry as f}from"../telemetry/logSinkTelemetry.mjs";import{extensionName as p,extensionPackageName as m,extensionToolSource as h}from"./extensionName.mjs";import{registerBuiltinToolUi as g}from"./toolOverrides.mjs";import _ from"node:path";import{connectDoomCordisHost as v}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{readDoomMcpStatus as y}from"@agimon-ai/doompi-extension-contracts/mcp-status";import{readDoomNotificationService as b}from"@agimon-ai/doompi-extension-contracts/notification";import{DOOM_MINOR_MODE_CATALOG_SERVICE as x,createMinorModeCatalogClient as S,requireMinorModeCatalog as C}from"@agimon-ai/doompi-extension-contracts/mode";import{DOOM_UI_HUB_SERVICE as w}from"@agimon-ai/doompi-extension-contracts/ui-hub";import{DOOM_TOOL_OVERRIDES_SERVICE as T,requireDoomToolOverrides as E}from"@agimon-ai/doompi-extension-contracts/tool-overrides";const D=`doom-pi-leader`,O=`belowEditor`,k=`warning`,A=`doom-pi`,j=new Set([`hotkeys`]),M=`@agimon-ai/doompi-ui`;function N(e,t,n,r){let i=b(e);if(i){try{Promise.resolve(i.request({body:n,level:r})).catch(()=>void 0)}catch{}return}t.ui.notify(n,r)}function P(e,t){let n=e.trim().replace(/\s+/g,` `);if(!n)return;let r=n.slice(0,64).trimEnd();return`${A} · ${r} · ${_.basename(t)||t}`}function F(f,{pi:v,telemetry:b,hostMode:F}){f.inject([T],e=>{let t=E(e),n=[],r=e=>{if(F===`composed`)return!0;let r=t.claim({source:M,tools:[e]});return r.granted&&n.push(r),r.granted};try{g(v,process.cwd(),r)}catch(e){for(let e of n)e.dispose();throw e}return()=>{for(let e of n)e.dispose()}});let I=new n,L=new e,R=new Set,z,B,V=0,H=!1,U=()=>y(f)?.getSnapshot().servers;v.registerCommand(`tools`,{description:`Browse the tools available in this session`,handler:async(e,t)=>{let n=U(),r=i({tools:v.getAllTools(),activeTools:v.getActiveTools(),...n?{mcpServers:n}:{},resolveExtensionName:p,resolveExtensionPackageName:m,resolveExtensionToolSource:e=>h(v,e)});await u(t,r)}}),v.registerCommand(`config`,{description:`Browse and change the settings extensions contribute`,handler:async(e,t)=>{await a(t,G.config)}});let W=e=>{for(let t of e){let e=t.bindingId?` (${t.bindingId})`:``,n=`Leader contribution from ${t.source}${e}: ${t.message}`;R.has(n)||(R.add(n),b.recordWarning(d.leaderContributionRejected,t.message,{"leader.source":t.source,...t.bindingId?{"leader.binding.id":t.bindingId}:{}}),B?.mode===`tui`&&N(f,B,n,k))}},G=t({leaderRegistry:L,reportDiagnostics:W});f.provide(w,G),f.inject([x],e=>{let t=S(C(e)),n=()=>I.setModes(r(t.list())),i=t.subscribe(n);return n(),()=>{i(),I.setModes([])}}),v.on(`session_start`,(e,t)=>{B=t,G.setContext(t);let n=++V;if(W(L.flush()),H=t.sessionManager.getEntries().some(e=>e.type===`message`),t.mode!==`tui`)return;for(let e of R)N(f,t,e,k);let r=process.env.DOOMPI_THEME||`doom-pi-dark`,i=t.ui.setTheme(r);i.success||(N(f,t,`Could not apply Doom Pi theme: ${i.error}`,k),b.recordWarning(d.themeApplyFailed,i.error??`Unknown theme error`,{"ui.theme":r})),t.ui.setTitle(`${A} · ${_.basename(t.cwd)||t.cwd} · pi/coding`),t.ui.setHeader((e,n)=>new c(n,t.cwd,void 0,t,()=>e.terminal.rows)),t.ui.setFooter((e,n,r)=>(z=r,new s(e,n,t,r,G.footer,I))),t.ui.setEditorComponent((e,r,i)=>new o(e,r,i,I,e=>{if(!e.active){t.ui.setWidget(D,void 0,{placement:O});return}t.ui.setWidget(D,(n,r)=>new l(r,e,t,z,I.getModes()),{placement:O})},void 0,t.ui.theme,{registry:L,isCommandAvailable:e=>j.has(e)||v.getCommands().some(t=>t.name===e),onLeaderAction:(e,r)=>{if(B!==t||V!==n){N(f,t,`Leader action ${r} is unavailable.`,k);return}G.invokeLeaderAction(e,r)},onUnavailableCommand:e=>{b.recordWarning(d.leaderCommandUnavailable,`Leader command /${e} is unavailable.`,{"ui.kind":`command`}),N(f,t,`Leader command /${e} is unavailable.`,k)},onUnavailableAction:e=>{b.recordWarning(d.leaderActionUnavailable,`Leader action ${e} is unavailable.`,{"ui.kind":`action`}),N(f,t,`Leader action ${e} is unavailable.`,k)}})),b.recordEvent(d.shellInstalled,{"ui.theme":r,"ui.theme.applied":i.success,"ui.leader.group.count":L.getGroup([])?.options.length??0})}),v.on(`input`,(e,t)=>{if(t.mode!==`tui`||e.source!==`interactive`||H)return;H=!0;let n=P(e.text,t.cwd);n&&t.ui.setTitle(n)}),f.effect(()=>async()=>{let e=B;++V,B=void 0,G.setContext(void 0),z=void 0,G.dispose(),I.reset(),e?.mode===`tui`&&e.ui.setWidget(D,void 0,{placement:O}),await b.shutdown()},M)}async function I(e,t=f()){let n=await v(e,M),r=n.root.plugin(F,{pi:e,telemetry:t,hostMode:n.runtime.mode});try{await r}catch(e){try{await r.dispose()}finally{await n.dispose()}throw e}let i;e.on(`session_shutdown`,()=>i??=(async()=>{try{await r.dispose()}finally{await n.dispose()}})())}export{I as doomPiUiExtension};
2
2
  //# sourceMappingURL=extension.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import path from 'node:path';\nimport { connectDoomCordisHost, type DoomCordisHostMode } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { readDoomMcpStatus } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport {\n type DoomNotificationLevel,\n readDoomNotificationService,\n} from '@agimon-ai/doompi-extension-contracts/notification';\nimport {\n DOOM_MINOR_MODE_CATALOG_SERVICE,\n createMinorModeCatalogClient,\n requireMinorModeCatalog,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport { DOOM_UI_HUB_SERVICE } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport {\n DOOM_TOOL_OVERRIDES_SERVICE,\n type DoomToolOverrideRegistration,\n requireDoomToolOverrides,\n} from '@agimon-ai/doompi-extension-contracts/tool-overrides';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext, ReadonlyFooterDataProvider } from '@earendil-works/pi-coding-agent';\nimport { type DoomLeaderDiagnostic, DoomLeaderRegistry } from '../../services/leader/leaderRegistry.ts';\nimport { createDoomUiHub } from '../../services/hub/uiHub.ts';\nimport { DoomUiState, type LeaderSnapshot, projectMinorModeRecords } from '../../services/state/uiState.ts';\nimport { buildToolSources, type McpServerStatus } from '../../services/tools/toolInventory.ts';\nimport { openConfigOverlay } from '../../tui/configOverlay.ts';\nimport { DoomEditor } from '../../tui/doomEditor.ts';\nimport { DoomFooter } from '../../tui/doomFooter.ts';\nimport { DoomHeader } from '../../tui/doomHeader.ts';\nimport { LeaderHints } from '../../tui/leaderHints.ts';\nimport { DEFAULT_THEME_NAME } from '../../tui/theme.ts';\nimport { openToolsOverlay } from '../../tui/toolsOverlay.ts';\nimport { createUiTelemetry, UI_EVENT, type UiTelemetry } from '../telemetry/logSinkTelemetry.ts';\nimport { extensionName, extensionToolSource } from './extensionName.ts';\nimport { registerBuiltinToolUi } from './toolOverrides.ts';\n\nconst LEADER_WIDGET_KEY = 'doom-pi-leader';\nconst TUI_MODE = 'tui';\nconst WIDGET_PLACEMENT = 'belowEditor';\nconst WARNING_STYLE = 'warning';\nconst THEME_ENVIRONMENT_KEY = 'DOOMPI_THEME';\nconst HOTKEYS_COMMAND = 'hotkeys';\nconst TOOLS_COMMAND = 'tools';\nconst CONFIG_COMMAND = 'config';\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_MESSAGE_ENTRY = 'message';\nconst INPUT_EVENT = 'input';\nconst INTERACTIVE_INPUT_SOURCE = 'interactive';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\nconst TITLE_MESSAGE_MAX_LENGTH = 64;\nconst TITLE_PREFIX = 'doom-pi';\nconst BUILTIN_LEADER_COMMANDS = new Set([HOTKEYS_COMMAND]);\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-ui';\n\nfunction notify(cordis: Context, context: ExtensionContext, body: string, level: DoomNotificationLevel): void {\n const service = readDoomNotificationService(cordis);\n if (service) {\n try {\n void Promise.resolve(service.request({ body, level })).catch(() => undefined);\n } catch {\n // Notification delivery is best effort and does not fall back after routing.\n }\n return;\n }\n context.ui.notify(body, level);\n}\n\nfunction createMessageTitle(message: string, cwd: string): string | undefined {\n const normalizedMessage = message.trim().replace(/\\s+/g, ' ');\n if (!normalizedMessage) return undefined;\n const messageTitle = normalizedMessage.slice(0, TITLE_MESSAGE_MAX_LENGTH).trimEnd();\n return `${TITLE_PREFIX} · ${messageTitle} · ${path.basename(cwd) || cwd}`;\n}\n\ninterface UiPluginOptions {\n readonly pi: ExtensionAPI;\n readonly telemetry: UiTelemetry;\n readonly hostMode: DoomCordisHostMode;\n}\n\nfunction doomPiUiPlugin(cordis: Context, { pi, telemetry, hostMode }: UiPluginOptions): void {\n cordis.inject([DOOM_TOOL_OVERRIDES_SERVICE], (overridesContext) => {\n const toolOverrides = requireDoomToolOverrides(overridesContext);\n const registrations: DoomToolOverrideRegistration[] = [];\n const shouldRegister = (tool: string): boolean => {\n if (hostMode === 'composed') return true;\n const registration = toolOverrides.claim({ source: PACKAGE_SOURCE, tools: [tool] });\n if (registration.granted) registrations.push(registration);\n return registration.granted;\n };\n try {\n registerBuiltinToolUi(pi, process.cwd(), shouldRegister);\n } catch (error) {\n for (const registration of registrations) registration.dispose();\n throw error;\n }\n return () => {\n for (const registration of registrations) registration.dispose();\n };\n });\n const uiState = new DoomUiState();\n const leaderRegistry = new DoomLeaderRegistry();\n const pendingDiagnostics = new Set<string>();\n let footerData: ReadonlyFooterDataProvider | undefined;\n let activeContext: ExtensionContext | undefined;\n let sessionGeneration = 0;\n let hasSessionMessage = false;\n\n /**\n * Asks the MCP extension what it is currently connected to.\n *\n * No answer is a normal session: `--no-mcp` leaves nobody providing the query,\n * and the panel simply lists MCP tools under the extension that registered them.\n */\n const readMcpServers = (): readonly McpServerStatus[] | undefined => readDoomMcpStatus(cordis)?.getSnapshot().servers;\n\n pi.registerCommand(TOOLS_COMMAND, {\n description: 'Browse the tools available in this session',\n handler: async (_args, ctx) => {\n // Read at open time so a reconnected server or a plan-mode tool swap shows\n // up without restarting the session.\n const mcpServers = readMcpServers();\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\n resolveExtensionName: extensionName,\n resolveExtensionToolSource: (toolName) => extensionToolSource(pi, toolName),\n });\n await openToolsOverlay(ctx, sources);\n },\n });\n\n pi.registerCommand(CONFIG_COMMAND, {\n description: 'Browse and change the settings extensions contribute',\n handler: async (_args, ctx) => {\n // The registry is passed live rather than snapshotted: a contributor can be\n // fetching or installing something while the panel is open, and that\n // progress is meant to land in it.\n await openConfigOverlay(ctx, hub.config);\n },\n });\n\n const reportDiagnostics = (diagnostics: readonly DoomLeaderDiagnostic[]): void => {\n for (const diagnostic of diagnostics) {\n const binding = diagnostic.bindingId ? ` (${diagnostic.bindingId})` : '';\n const message = `Leader contribution from ${diagnostic.source}${binding}: ${diagnostic.message}`;\n if (pendingDiagnostics.has(message)) continue;\n pendingDiagnostics.add(message);\n // A rejected contribution silently drops another extension's keybindings,\n // and the notice below only reaches a run that has a TUI attached.\n void telemetry.recordWarning(UI_EVENT.leaderContributionRejected, diagnostic.message, {\n 'leader.source': diagnostic.source,\n ...(diagnostic.bindingId ? { 'leader.binding.id': diagnostic.bindingId } : {}),\n });\n if (activeContext?.mode === TUI_MODE) notify(cordis, activeContext, message, WARNING_STYLE);\n }\n };\n\n const hub = createDoomUiHub({ leaderRegistry, reportDiagnostics });\n cordis.provide(DOOM_UI_HUB_SERVICE, hub);\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE], (modeContext) => {\n const catalog = createMinorModeCatalogClient(requireMinorModeCatalog(modeContext));\n const update = (): void => uiState.setModes(projectMinorModeRecords(catalog.list()));\n const dispose = catalog.subscribe(update);\n update();\n return () => {\n dispose();\n uiState.setModes([]);\n };\n });\n\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n activeContext = ctx;\n hub.setContext(ctx);\n const activeGeneration = ++sessionGeneration;\n reportDiagnostics(leaderRegistry.flush());\n hasSessionMessage = ctx.sessionManager.getEntries().some((entry) => entry.type === SESSION_MESSAGE_ENTRY);\n if (ctx.mode !== TUI_MODE) return;\n for (const message of pendingDiagnostics) notify(cordis, ctx, message, WARNING_STYLE);\n\n const themeName = process.env[THEME_ENVIRONMENT_KEY] || DEFAULT_THEME_NAME;\n const themeResult = ctx.ui.setTheme(themeName);\n if (!themeResult.success) {\n notify(cordis, ctx, `Could not apply Doom Pi theme: ${themeResult.error}`, WARNING_STYLE);\n void telemetry.recordWarning(UI_EVENT.themeApplyFailed, themeResult.error ?? 'Unknown theme error', {\n 'ui.theme': themeName,\n });\n }\n\n ctx.ui.setTitle(`${TITLE_PREFIX} · ${path.basename(ctx.cwd) || ctx.cwd} · pi/coding`);\n ctx.ui.setHeader((tui, theme) => new DoomHeader(theme, ctx.cwd, undefined, ctx, () => tui.terminal.rows));\n ctx.ui.setFooter((tui, theme, data) => {\n footerData = data;\n return new DoomFooter(tui, theme, ctx, data, hub.footer, uiState);\n });\n ctx.ui.setEditorComponent(\n (tui, editorTheme, keybindings) =>\n new DoomEditor(\n tui,\n editorTheme,\n keybindings,\n uiState,\n (snapshot: LeaderSnapshot) => {\n if (!snapshot.active) {\n ctx.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n return;\n }\n ctx.ui.setWidget(\n LEADER_WIDGET_KEY,\n (_widgetTui, widgetTheme) => new LeaderHints(widgetTheme, snapshot, ctx, footerData, uiState.getModes()),\n { placement: WIDGET_PLACEMENT },\n );\n },\n undefined,\n ctx.ui.theme,\n {\n registry: leaderRegistry,\n isCommandAvailable: (commandName) =>\n BUILTIN_LEADER_COMMANDS.has(commandName) ||\n pi.getCommands().some((command) => command.name === commandName),\n onLeaderAction: (source, action) => {\n if (activeContext !== ctx || sessionGeneration !== activeGeneration) {\n notify(cordis, ctx, `Leader action ${action} is unavailable.`, WARNING_STYLE);\n return;\n }\n hub.invokeLeaderAction(source, action);\n },\n onUnavailableCommand: (commandName) => {\n void telemetry.recordWarning(\n UI_EVENT.leaderCommandUnavailable,\n `Leader command /${commandName} is unavailable.`,\n { 'ui.kind': 'command' },\n );\n notify(cordis, ctx, `Leader command /${commandName} is unavailable.`, WARNING_STYLE);\n },\n onUnavailableAction: (action) => {\n void telemetry.recordWarning(\n UI_EVENT.leaderActionUnavailable,\n `Leader action ${action} is unavailable.`,\n {\n 'ui.kind': 'action',\n },\n );\n notify(cordis, ctx, `Leader action ${action} is unavailable.`, WARNING_STYLE);\n },\n },\n ),\n );\n\n void telemetry.recordEvent(UI_EVENT.shellInstalled, {\n 'ui.theme': themeName,\n 'ui.theme.applied': themeResult.success,\n 'ui.leader.group.count': leaderRegistry.getGroup([])?.options.length ?? 0,\n });\n });\n\n pi.on(INPUT_EVENT, (event, ctx) => {\n if (ctx.mode !== TUI_MODE || event.source !== INTERACTIVE_INPUT_SOURCE || hasSessionMessage) return;\n hasSessionMessage = true;\n const title = createMessageTitle(event.text, ctx.cwd);\n if (title) ctx.ui.setTitle(title);\n });\n\n cordis.effect(\n () => async () => {\n const context = activeContext;\n ++sessionGeneration;\n activeContext = undefined;\n hub.setContext(undefined);\n footerData = undefined;\n hub.dispose();\n uiState.reset();\n if (context?.mode === TUI_MODE) {\n context.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n }\n await telemetry.shutdown();\n },\n PACKAGE_SOURCE,\n );\n}\n\n/** The package's standard Pi factory, mounted under the session Cordis host. */\nexport async function doomPiUiExtension(pi: ExtensionAPI, telemetry: UiTelemetry = createUiTelemetry()): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(doomPiUiPlugin, { pi, telemetry, hostMode: connection.runtime.mode });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n\n let disposal: Promise<void> | undefined;\n pi.on(\n SESSION_SHUTDOWN_EVENT,\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import path from 'node:path';\nimport { connectDoomCordisHost, type DoomCordisHostMode } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { readDoomMcpStatus } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport {\n type DoomNotificationLevel,\n readDoomNotificationService,\n} from '@agimon-ai/doompi-extension-contracts/notification';\nimport {\n DOOM_MINOR_MODE_CATALOG_SERVICE,\n createMinorModeCatalogClient,\n requireMinorModeCatalog,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport { DOOM_UI_HUB_SERVICE } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport {\n DOOM_TOOL_OVERRIDES_SERVICE,\n type DoomToolOverrideRegistration,\n requireDoomToolOverrides,\n} from '@agimon-ai/doompi-extension-contracts/tool-overrides';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext, ReadonlyFooterDataProvider } from '@earendil-works/pi-coding-agent';\nimport { type DoomLeaderDiagnostic, DoomLeaderRegistry } from '../../services/leader/leaderRegistry.ts';\nimport { createDoomUiHub } from '../../services/hub/uiHub.ts';\nimport { DoomUiState, type LeaderSnapshot, projectMinorModeRecords } from '../../services/state/uiState.ts';\nimport { buildToolSources, type McpServerStatus } from '../../services/tools/toolInventory.ts';\nimport { openConfigOverlay } from '../../tui/configOverlay.ts';\nimport { DoomEditor } from '../../tui/doomEditor.ts';\nimport { DoomFooter } from '../../tui/doomFooter.ts';\nimport { DoomHeader } from '../../tui/doomHeader.ts';\nimport { LeaderHints } from '../../tui/leaderHints.ts';\nimport { DEFAULT_THEME_NAME } from '../../tui/theme.ts';\nimport { openToolsOverlay } from '../../tui/toolsOverlay.ts';\nimport { createUiTelemetry, UI_EVENT, type UiTelemetry } from '../telemetry/logSinkTelemetry.ts';\nimport { extensionName, extensionPackageName, extensionToolSource } from './extensionName.ts';\nimport { registerBuiltinToolUi } from './toolOverrides.ts';\n\nconst LEADER_WIDGET_KEY = 'doom-pi-leader';\nconst TUI_MODE = 'tui';\nconst WIDGET_PLACEMENT = 'belowEditor';\nconst WARNING_STYLE = 'warning';\nconst THEME_ENVIRONMENT_KEY = 'DOOMPI_THEME';\nconst HOTKEYS_COMMAND = 'hotkeys';\nconst TOOLS_COMMAND = 'tools';\nconst CONFIG_COMMAND = 'config';\nconst SESSION_START_EVENT = 'session_start';\nconst SESSION_MESSAGE_ENTRY = 'message';\nconst INPUT_EVENT = 'input';\nconst INTERACTIVE_INPUT_SOURCE = 'interactive';\nconst SESSION_SHUTDOWN_EVENT = 'session_shutdown';\nconst TITLE_MESSAGE_MAX_LENGTH = 64;\nconst TITLE_PREFIX = 'doom-pi';\nconst BUILTIN_LEADER_COMMANDS = new Set([HOTKEYS_COMMAND]);\nconst PACKAGE_SOURCE = '@agimon-ai/doompi-ui';\n\nfunction notify(cordis: Context, context: ExtensionContext, body: string, level: DoomNotificationLevel): void {\n const service = readDoomNotificationService(cordis);\n if (service) {\n try {\n void Promise.resolve(service.request({ body, level })).catch(() => undefined);\n } catch {\n // Notification delivery is best effort and does not fall back after routing.\n }\n return;\n }\n context.ui.notify(body, level);\n}\n\nfunction createMessageTitle(message: string, cwd: string): string | undefined {\n const normalizedMessage = message.trim().replace(/\\s+/g, ' ');\n if (!normalizedMessage) return undefined;\n const messageTitle = normalizedMessage.slice(0, TITLE_MESSAGE_MAX_LENGTH).trimEnd();\n return `${TITLE_PREFIX} · ${messageTitle} · ${path.basename(cwd) || cwd}`;\n}\n\ninterface UiPluginOptions {\n readonly pi: ExtensionAPI;\n readonly telemetry: UiTelemetry;\n readonly hostMode: DoomCordisHostMode;\n}\n\nfunction doomPiUiPlugin(cordis: Context, { pi, telemetry, hostMode }: UiPluginOptions): void {\n cordis.inject([DOOM_TOOL_OVERRIDES_SERVICE], (overridesContext) => {\n const toolOverrides = requireDoomToolOverrides(overridesContext);\n const registrations: DoomToolOverrideRegistration[] = [];\n const shouldRegister = (tool: string): boolean => {\n if (hostMode === 'composed') return true;\n const registration = toolOverrides.claim({ source: PACKAGE_SOURCE, tools: [tool] });\n if (registration.granted) registrations.push(registration);\n return registration.granted;\n };\n try {\n registerBuiltinToolUi(pi, process.cwd(), shouldRegister);\n } catch (error) {\n for (const registration of registrations) registration.dispose();\n throw error;\n }\n return () => {\n for (const registration of registrations) registration.dispose();\n };\n });\n const uiState = new DoomUiState();\n const leaderRegistry = new DoomLeaderRegistry();\n const pendingDiagnostics = new Set<string>();\n let footerData: ReadonlyFooterDataProvider | undefined;\n let activeContext: ExtensionContext | undefined;\n let sessionGeneration = 0;\n let hasSessionMessage = false;\n\n /**\n * Asks the MCP extension what it is currently connected to.\n *\n * No answer is a normal session: `--no-mcp` leaves nobody providing the query,\n * and the panel simply lists MCP tools under the extension that registered them.\n */\n const readMcpServers = (): readonly McpServerStatus[] | undefined => readDoomMcpStatus(cordis)?.getSnapshot().servers;\n\n pi.registerCommand(TOOLS_COMMAND, {\n description: 'Browse the tools available in this session',\n handler: async (_args, ctx) => {\n // Read at open time so a reconnected server or a plan-mode tool swap shows\n // up without restarting the session.\n const mcpServers = readMcpServers();\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\n resolveExtensionName: extensionName,\n resolveExtensionPackageName: extensionPackageName,\n resolveExtensionToolSource: (toolName) => extensionToolSource(pi, toolName),\n });\n await openToolsOverlay(ctx, sources);\n },\n });\n\n pi.registerCommand(CONFIG_COMMAND, {\n description: 'Browse and change the settings extensions contribute',\n handler: async (_args, ctx) => {\n // The registry is passed live rather than snapshotted: a contributor can be\n // fetching or installing something while the panel is open, and that\n // progress is meant to land in it.\n await openConfigOverlay(ctx, hub.config);\n },\n });\n\n const reportDiagnostics = (diagnostics: readonly DoomLeaderDiagnostic[]): void => {\n for (const diagnostic of diagnostics) {\n const binding = diagnostic.bindingId ? ` (${diagnostic.bindingId})` : '';\n const message = `Leader contribution from ${diagnostic.source}${binding}: ${diagnostic.message}`;\n if (pendingDiagnostics.has(message)) continue;\n pendingDiagnostics.add(message);\n // A rejected contribution silently drops another extension's keybindings,\n // and the notice below only reaches a run that has a TUI attached.\n void telemetry.recordWarning(UI_EVENT.leaderContributionRejected, diagnostic.message, {\n 'leader.source': diagnostic.source,\n ...(diagnostic.bindingId ? { 'leader.binding.id': diagnostic.bindingId } : {}),\n });\n if (activeContext?.mode === TUI_MODE) notify(cordis, activeContext, message, WARNING_STYLE);\n }\n };\n\n const hub = createDoomUiHub({ leaderRegistry, reportDiagnostics });\n cordis.provide(DOOM_UI_HUB_SERVICE, hub);\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE], (modeContext) => {\n const catalog = createMinorModeCatalogClient(requireMinorModeCatalog(modeContext));\n const update = (): void => uiState.setModes(projectMinorModeRecords(catalog.list()));\n const dispose = catalog.subscribe(update);\n update();\n return () => {\n dispose();\n uiState.setModes([]);\n };\n });\n\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n activeContext = ctx;\n hub.setContext(ctx);\n const activeGeneration = ++sessionGeneration;\n reportDiagnostics(leaderRegistry.flush());\n hasSessionMessage = ctx.sessionManager.getEntries().some((entry) => entry.type === SESSION_MESSAGE_ENTRY);\n if (ctx.mode !== TUI_MODE) return;\n for (const message of pendingDiagnostics) notify(cordis, ctx, message, WARNING_STYLE);\n\n const themeName = process.env[THEME_ENVIRONMENT_KEY] || DEFAULT_THEME_NAME;\n const themeResult = ctx.ui.setTheme(themeName);\n if (!themeResult.success) {\n notify(cordis, ctx, `Could not apply Doom Pi theme: ${themeResult.error}`, WARNING_STYLE);\n void telemetry.recordWarning(UI_EVENT.themeApplyFailed, themeResult.error ?? 'Unknown theme error', {\n 'ui.theme': themeName,\n });\n }\n\n ctx.ui.setTitle(`${TITLE_PREFIX} · ${path.basename(ctx.cwd) || ctx.cwd} · pi/coding`);\n ctx.ui.setHeader((tui, theme) => new DoomHeader(theme, ctx.cwd, undefined, ctx, () => tui.terminal.rows));\n ctx.ui.setFooter((tui, theme, data) => {\n footerData = data;\n return new DoomFooter(tui, theme, ctx, data, hub.footer, uiState);\n });\n ctx.ui.setEditorComponent(\n (tui, editorTheme, keybindings) =>\n new DoomEditor(\n tui,\n editorTheme,\n keybindings,\n uiState,\n (snapshot: LeaderSnapshot) => {\n if (!snapshot.active) {\n ctx.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n return;\n }\n ctx.ui.setWidget(\n LEADER_WIDGET_KEY,\n (_widgetTui, widgetTheme) => new LeaderHints(widgetTheme, snapshot, ctx, footerData, uiState.getModes()),\n { placement: WIDGET_PLACEMENT },\n );\n },\n undefined,\n ctx.ui.theme,\n {\n registry: leaderRegistry,\n isCommandAvailable: (commandName) =>\n BUILTIN_LEADER_COMMANDS.has(commandName) ||\n pi.getCommands().some((command) => command.name === commandName),\n onLeaderAction: (source, action) => {\n if (activeContext !== ctx || sessionGeneration !== activeGeneration) {\n notify(cordis, ctx, `Leader action ${action} is unavailable.`, WARNING_STYLE);\n return;\n }\n hub.invokeLeaderAction(source, action);\n },\n onUnavailableCommand: (commandName) => {\n void telemetry.recordWarning(\n UI_EVENT.leaderCommandUnavailable,\n `Leader command /${commandName} is unavailable.`,\n { 'ui.kind': 'command' },\n );\n notify(cordis, ctx, `Leader command /${commandName} is unavailable.`, WARNING_STYLE);\n },\n onUnavailableAction: (action) => {\n void telemetry.recordWarning(\n UI_EVENT.leaderActionUnavailable,\n `Leader action ${action} is unavailable.`,\n {\n 'ui.kind': 'action',\n },\n );\n notify(cordis, ctx, `Leader action ${action} is unavailable.`, WARNING_STYLE);\n },\n },\n ),\n );\n\n void telemetry.recordEvent(UI_EVENT.shellInstalled, {\n 'ui.theme': themeName,\n 'ui.theme.applied': themeResult.success,\n 'ui.leader.group.count': leaderRegistry.getGroup([])?.options.length ?? 0,\n });\n });\n\n pi.on(INPUT_EVENT, (event, ctx) => {\n if (ctx.mode !== TUI_MODE || event.source !== INTERACTIVE_INPUT_SOURCE || hasSessionMessage) return;\n hasSessionMessage = true;\n const title = createMessageTitle(event.text, ctx.cwd);\n if (title) ctx.ui.setTitle(title);\n });\n\n cordis.effect(\n () => async () => {\n const context = activeContext;\n ++sessionGeneration;\n activeContext = undefined;\n hub.setContext(undefined);\n footerData = undefined;\n hub.dispose();\n uiState.reset();\n if (context?.mode === TUI_MODE) {\n context.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n }\n await telemetry.shutdown();\n },\n PACKAGE_SOURCE,\n );\n}\n\n/** The package's standard Pi factory, mounted under the session Cordis host. */\nexport async function doomPiUiExtension(pi: ExtensionAPI, telemetry: UiTelemetry = createUiTelemetry()): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(doomPiUiPlugin, { pi, telemetry, hostMode: connection.runtime.mode });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n\n let disposal: Promise<void> | undefined;\n pi.on(\n SESSION_SHUTDOWN_EVENT,\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("node:fs");n=e.__toESM(n,1);const r=new Map,i=`__doompiExtensionToolSources__`;function a(e){let t=e,n=t[i];if(n)return n;let r=new Map;return Object.defineProperty(t,i,{configurable:!1,enumerable:!1,value:r,writable:!1}),r}function o(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function s(e,t){return new Proxy(e,{get(e,n){return n===`registerTool`?n=>{e.registerTool(n),a(e).set(n.name,Object.freeze({entryPath:t}))}:o(e,n)}})}function c(e,t){return a(e).get(t)?.entryPath}function l(e){let r=e;for(;;){try{let e=JSON.parse(n.default.readFileSync(t.default.join(r,`package.json`),`utf8`));if(typeof e.name==`string`&&e.name.length>0)return e.name}catch{}let e=t.default.dirname(r);if(e===r)return;r=e}}function u(e){let n=r.get(e);if(n!==void 0)return n;let i=l(t.default.dirname(e)),a=i?i.split(`/`).pop()??i:t.default.basename(e,t.default.extname(e));return r.set(e,a),a}exports.extensionName=u,exports.extensionToolSource=c,exports.withExtensionSource=s;
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:path");t=e.__toESM(t,1);let n=require("node:fs");n=e.__toESM(n,1);const r=new Map,i=new Map,a=`__doompiExtensionToolSources__`;function o(e){let t=e,n=t[a];if(n)return n;let r=new Map;return Object.defineProperty(t,a,{configurable:!1,enumerable:!1,value:r,writable:!1}),r}function s(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function c(e,t){return new Proxy(e,{get(e,n){return n===`registerTool`?n=>{e.registerTool(n),o(e).set(n.name,Object.freeze({entryPath:t}))}:s(e,n)}})}function l(e,t){return o(e).get(t)?.entryPath}function u(e){let r=e;for(;;){try{let e=JSON.parse(n.default.readFileSync(t.default.join(r,`package.json`),`utf8`));if(typeof e.name==`string`&&e.name.length>0)return e.name}catch{}let e=t.default.dirname(r);if(e===r)return;r=e}}function d(e){let n=r.get(e);if(n!==void 0)return n;let i=u(t.default.dirname(e)),a=i?i.split(`/`).pop()??i:t.default.basename(e,t.default.extname(e));return r.set(e,a),a}function f(e){let n=i.get(e);if(n!==void 0)return n.name;let r=u(t.default.dirname(e));return i.set(e,{name:r}),r}exports.extensionName=d,exports.extensionPackageName=f,exports.extensionToolSource=l,exports.withExtensionSource=c;
2
2
  //# sourceMappingURL=extensionName.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extensionName.cjs","names":["fs","path"],"sources":["../../../src/adapters/pi/extensionName.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\n\n/**\n * Display name for an extension that Pi could not attribute to a package.\n *\n * Pi stamps every `--extension <path>` argument with the literal source `cli`,\n * and doom-pi launches all of its extensions that way, so the only surviving\n * identity is the entry path. The nearest `package.json` above it names the\n * package that registered the tools.\n */\n\n/** Results are stable for the life of a session, and every source hits the same few dirs. */\nconst cache = new Map<string, string>();\nconst TOOL_SOURCE_REGISTRY_KEY = '__doompiExtensionToolSources__' as const;\n\ntype RegisteredTool = Parameters<ExtensionAPI['registerTool']>[0];\ninterface ToolSourceMetadata {\n readonly entryPath: string;\n}\ntype RegistryHost = ExtensionAPI & { [TOOL_SOURCE_REGISTRY_KEY]?: Map<string, ToolSourceMetadata> };\n\n/** Session-local provenance attached to Pi's API rather than process-global state. */\nfunction toolSourceRegistry(pi: ExtensionAPI): Map<string, ToolSourceMetadata> {\n const host = pi as RegistryHost;\n const existing = host[TOOL_SOURCE_REGISTRY_KEY];\n if (existing) return existing;\n const registry = new Map<string, ToolSourceMetadata>();\n Object.defineProperty(host, TOOL_SOURCE_REGISTRY_KEY, {\n configurable: false,\n enumerable: false,\n value: registry,\n writable: false,\n });\n return registry;\n}\n\nfunction boundValue(target: object, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\n/**\n * Gives one composed extension a Pi API that remembers which tools it registers.\n *\n * Pi attributes every factory inside a compiled Doom bundle to the outer bundle.\n * The compiler still knows each original entry path, so it calls factories through\n * this boundary and preserves that identity before the information is lost.\n */\nexport function withExtensionSource(pi: ExtensionAPI, entryPath: string): ExtensionAPI {\n return new Proxy(pi, {\n get(target, property) {\n if (property !== 'registerTool') return boundValue(target, property);\n return (tool: RegisteredTool): void => {\n target.registerTool(tool);\n toolSourceRegistry(target).set(tool.name, Object.freeze({ entryPath }));\n };\n },\n });\n}\n\n/** Original extension entry that registered a tool inside a composed bundle. */\nexport function extensionToolSource(pi: ExtensionAPI, toolName: string): string | undefined {\n return toolSourceRegistry(pi).get(toolName)?.entryPath;\n}\n\nfunction packageName(directory: string): string | undefined {\n let current = directory;\n for (;;) {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(current, 'package.json'), 'utf8')) as { name?: unknown };\n if (typeof manifest.name === 'string' && manifest.name.length > 0) return manifest.name;\n } catch {\n // No manifest here, or an unreadable one; keep walking up.\n }\n const parent = path.dirname(current);\n if (parent === current) return undefined;\n current = parent;\n }\n}\n\nexport function extensionName(entryPath: string): string {\n const cached = cache.get(entryPath);\n if (cached !== undefined) return cached;\n const name = packageName(path.dirname(entryPath));\n // `@agimon-ai/doompi-task` reads as `doom-task`; the scope is noise in a tree.\n const resolved = name ? (name.split('/').pop() ?? name) : path.basename(entryPath, path.extname(entryPath));\n cache.set(entryPath, resolved);\n return resolved;\n}\n"],"mappings":"8IAcA,MAAM,EAAQ,IAAI,IACZ,EAA2B,iCASjC,SAAS,EAAmB,EAAmD,CAC7E,IAAM,EAAO,EACP,EAAW,EAAK,GACtB,GAAI,EAAU,OAAO,EACrB,IAAM,EAAW,IAAI,IAOrB,OANA,OAAO,eAAe,EAAM,EAA0B,CACpD,aAAc,GACd,WAAY,GACZ,MAAO,EACP,SAAU,EACZ,CAAC,EACM,CACT,CAEA,SAAS,EAAW,EAAgB,EAAgC,CAClE,IAAM,EAAQ,QAAQ,IAAI,EAAQ,EAAU,CAAM,EAClD,OAAO,OAAO,GAAU,WAAa,EAAM,KAAK,CAAM,EAAI,CAC5D,CASA,SAAgB,EAAoB,EAAkB,EAAiC,CACrF,OAAO,IAAI,MAAM,EAAI,CACnB,IAAI,EAAQ,EAAU,CAEpB,OADI,IAAa,eACT,GAA+B,CACrC,EAAO,aAAa,CAAI,EACxB,EAAmB,CAAM,CAAC,CAAC,IAAI,EAAK,KAAM,OAAO,OAAO,CAAE,WAAU,CAAC,CAAC,CACxE,EAJwC,EAAW,EAAQ,CAAQ,CAKrE,CACF,CAAC,CACH,CAGA,SAAgB,EAAoB,EAAkB,EAAsC,CAC1F,OAAO,EAAmB,CAAE,CAAC,CAAC,IAAI,CAAQ,CAAC,EAAE,SAC/C,CAEA,SAAS,EAAY,EAAuC,CAC1D,IAAI,EAAU,EACd,OAAS,CACP,GAAI,CACF,IAAM,EAAW,KAAK,MAAMA,EAAAA,QAAG,aAAaC,EAAAA,QAAK,KAAK,EAAS,cAAc,EAAG,MAAM,CAAC,EACvF,GAAI,OAAO,EAAS,MAAS,UAAY,EAAS,KAAK,OAAS,EAAG,OAAO,EAAS,IACrF,MAAQ,CAER,CACA,IAAM,EAASA,EAAAA,QAAK,QAAQ,CAAO,EACnC,GAAI,IAAW,EAAS,OACxB,EAAU,CACZ,CACF,CAEA,SAAgB,EAAc,EAA2B,CACvD,IAAM,EAAS,EAAM,IAAI,CAAS,EAClC,GAAI,IAAW,IAAA,GAAW,OAAO,EACjC,IAAM,EAAO,EAAYA,EAAAA,QAAK,QAAQ,CAAS,CAAC,EAE1C,EAAW,EAAQ,EAAK,MAAM,GAAG,CAAC,CAAC,IAAI,GAAK,EAAQA,EAAAA,QAAK,SAAS,EAAWA,EAAAA,QAAK,QAAQ,CAAS,CAAC,EAE1G,OADA,EAAM,IAAI,EAAW,CAAQ,EACtB,CACT"}
1
+ {"version":3,"file":"extensionName.cjs","names":["fs","path"],"sources":["../../../src/adapters/pi/extensionName.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\n\n/**\n * Display name for an extension that Pi could not attribute to a package.\n *\n * Pi stamps every `--extension <path>` argument with the literal source `cli`,\n * and doom-pi launches all of its extensions that way, so the only surviving\n * identity is the entry path. The nearest `package.json` above it names the\n * package that registered the tools.\n */\n\n/** Results are stable for the life of a session, and every source hits the same few dirs. */\nconst cache = new Map<string, string>();\n// Wrapped, because \"no package above this path\" is a real answer worth caching\n// and a bare undefined cannot be told apart from a miss.\nconst packageCache = new Map<string, { readonly name: string | undefined }>();\nconst TOOL_SOURCE_REGISTRY_KEY = '__doompiExtensionToolSources__' as const;\n\ntype RegisteredTool = Parameters<ExtensionAPI['registerTool']>[0];\ninterface ToolSourceMetadata {\n readonly entryPath: string;\n}\ntype RegistryHost = ExtensionAPI & { [TOOL_SOURCE_REGISTRY_KEY]?: Map<string, ToolSourceMetadata> };\n\n/** Session-local provenance attached to Pi's API rather than process-global state. */\nfunction toolSourceRegistry(pi: ExtensionAPI): Map<string, ToolSourceMetadata> {\n const host = pi as RegistryHost;\n const existing = host[TOOL_SOURCE_REGISTRY_KEY];\n if (existing) return existing;\n const registry = new Map<string, ToolSourceMetadata>();\n Object.defineProperty(host, TOOL_SOURCE_REGISTRY_KEY, {\n configurable: false,\n enumerable: false,\n value: registry,\n writable: false,\n });\n return registry;\n}\n\nfunction boundValue(target: object, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\n/**\n * Gives one composed extension a Pi API that remembers which tools it registers.\n *\n * Pi attributes every factory inside a compiled Doom bundle to the outer bundle.\n * The compiler still knows each original entry path, so it calls factories through\n * this boundary and preserves that identity before the information is lost.\n */\nexport function withExtensionSource(pi: ExtensionAPI, entryPath: string): ExtensionAPI {\n return new Proxy(pi, {\n get(target, property) {\n if (property !== 'registerTool') return boundValue(target, property);\n return (tool: RegisteredTool): void => {\n target.registerTool(tool);\n toolSourceRegistry(target).set(tool.name, Object.freeze({ entryPath }));\n };\n },\n });\n}\n\n/** Original extension entry that registered a tool inside a composed bundle. */\nexport function extensionToolSource(pi: ExtensionAPI, toolName: string): string | undefined {\n return toolSourceRegistry(pi).get(toolName)?.entryPath;\n}\n\nfunction packageName(directory: string): string | undefined {\n let current = directory;\n for (;;) {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(current, 'package.json'), 'utf8')) as { name?: unknown };\n if (typeof manifest.name === 'string' && manifest.name.length > 0) return manifest.name;\n } catch {\n // No manifest here, or an unreadable one; keep walking up.\n }\n const parent = path.dirname(current);\n if (parent === current) return undefined;\n current = parent;\n }\n}\n\nexport function extensionName(entryPath: string): string {\n const cached = cache.get(entryPath);\n if (cached !== undefined) return cached;\n const name = packageName(path.dirname(entryPath));\n // `@agimon-ai/doompi-task` reads as `doom-task`; the scope is noise in a tree.\n const resolved = name ? (name.split('/').pop() ?? name) : path.basename(entryPath, path.extname(entryPath));\n cache.set(entryPath, resolved);\n return resolved;\n}\n\n/**\n * The package name exactly as a manifest spells it, scope and all.\n *\n * `extensionName` drops the scope because a tree reads better without it, but\n * that makes it useless as a key: `.doom/modes.yaml` names packages as\n * `@agimon-ai/doompi-team`, so joining a registered tool back to the layer that\n * admitted it needs the unshortened name. Attribution and display are different\n * jobs, so they get different functions rather than one lossy one.\n */\nexport function extensionPackageName(entryPath: string): string | undefined {\n const cached = packageCache.get(entryPath);\n if (cached !== undefined) return cached.name;\n const name = packageName(path.dirname(entryPath));\n packageCache.set(entryPath, { name });\n return name;\n}\n"],"mappings":"8IAcA,MAAM,EAAQ,IAAI,IAGZ,EAAe,IAAI,IACnB,EAA2B,iCASjC,SAAS,EAAmB,EAAmD,CAC7E,IAAM,EAAO,EACP,EAAW,EAAK,GACtB,GAAI,EAAU,OAAO,EACrB,IAAM,EAAW,IAAI,IAOrB,OANA,OAAO,eAAe,EAAM,EAA0B,CACpD,aAAc,GACd,WAAY,GACZ,MAAO,EACP,SAAU,EACZ,CAAC,EACM,CACT,CAEA,SAAS,EAAW,EAAgB,EAAgC,CAClE,IAAM,EAAQ,QAAQ,IAAI,EAAQ,EAAU,CAAM,EAClD,OAAO,OAAO,GAAU,WAAa,EAAM,KAAK,CAAM,EAAI,CAC5D,CASA,SAAgB,EAAoB,EAAkB,EAAiC,CACrF,OAAO,IAAI,MAAM,EAAI,CACnB,IAAI,EAAQ,EAAU,CAEpB,OADI,IAAa,eACT,GAA+B,CACrC,EAAO,aAAa,CAAI,EACxB,EAAmB,CAAM,CAAC,CAAC,IAAI,EAAK,KAAM,OAAO,OAAO,CAAE,WAAU,CAAC,CAAC,CACxE,EAJwC,EAAW,EAAQ,CAAQ,CAKrE,CACF,CAAC,CACH,CAGA,SAAgB,EAAoB,EAAkB,EAAsC,CAC1F,OAAO,EAAmB,CAAE,CAAC,CAAC,IAAI,CAAQ,CAAC,EAAE,SAC/C,CAEA,SAAS,EAAY,EAAuC,CAC1D,IAAI,EAAU,EACd,OAAS,CACP,GAAI,CACF,IAAM,EAAW,KAAK,MAAMA,EAAAA,QAAG,aAAaC,EAAAA,QAAK,KAAK,EAAS,cAAc,EAAG,MAAM,CAAC,EACvF,GAAI,OAAO,EAAS,MAAS,UAAY,EAAS,KAAK,OAAS,EAAG,OAAO,EAAS,IACrF,MAAQ,CAER,CACA,IAAM,EAASA,EAAAA,QAAK,QAAQ,CAAO,EACnC,GAAI,IAAW,EAAS,OACxB,EAAU,CACZ,CACF,CAEA,SAAgB,EAAc,EAA2B,CACvD,IAAM,EAAS,EAAM,IAAI,CAAS,EAClC,GAAI,IAAW,IAAA,GAAW,OAAO,EACjC,IAAM,EAAO,EAAYA,EAAAA,QAAK,QAAQ,CAAS,CAAC,EAE1C,EAAW,EAAQ,EAAK,MAAM,GAAG,CAAC,CAAC,IAAI,GAAK,EAAQA,EAAAA,QAAK,SAAS,EAAWA,EAAAA,QAAK,QAAQ,CAAS,CAAC,EAE1G,OADA,EAAM,IAAI,EAAW,CAAQ,EACtB,CACT,CAWA,SAAgB,EAAqB,EAAuC,CAC1E,IAAM,EAAS,EAAa,IAAI,CAAS,EACzC,GAAI,IAAW,IAAA,GAAW,OAAO,EAAO,KACxC,IAAM,EAAO,EAAYA,EAAAA,QAAK,QAAQ,CAAS,CAAC,EAEhD,OADA,EAAa,IAAI,EAAW,CAAE,MAAK,CAAC,EAC7B,CACT"}
@@ -11,6 +11,16 @@ declare function withExtensionSource(pi: ExtensionAPI, entryPath: string): Exten
11
11
  /** Original extension entry that registered a tool inside a composed bundle. */
12
12
  declare function extensionToolSource(pi: ExtensionAPI, toolName: string): string | undefined;
13
13
  declare function extensionName(entryPath: string): string;
14
+ /**
15
+ * The package name exactly as a manifest spells it, scope and all.
16
+ *
17
+ * `extensionName` drops the scope because a tree reads better without it, but
18
+ * that makes it useless as a key: `.doom/modes.yaml` names packages as
19
+ * `@agimon-ai/doompi-team`, so joining a registered tool back to the layer that
20
+ * admitted it needs the unshortened name. Attribution and display are different
21
+ * jobs, so they get different functions rather than one lossy one.
22
+ */
23
+ declare function extensionPackageName(entryPath: string): string | undefined;
14
24
  //#endregion
15
- export { extensionName, extensionToolSource, withExtensionSource };
25
+ export { extensionName, extensionPackageName, extensionToolSource, withExtensionSource };
16
26
  //# sourceMappingURL=extensionName.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extensionName.d.cts","names":[],"sources":["../../../src/adapters/pi/extensionName.ts"],"mappings":";;;;;;;;;iBAkDgB,oBAAoB,IAAI,cAAc,oBAAoB;;iBAa1D,oBAAoB,IAAI,cAAc;iBAmBtC,cAAc"}
1
+ {"version":3,"file":"extensionName.d.cts","names":[],"sources":["../../../src/adapters/pi/extensionName.ts"],"mappings":";;;;;;;;;iBAqDgB,oBAAoB,IAAI,cAAc,oBAAoB;;iBAa1D,oBAAoB,IAAI,cAAc;iBAmBtC,cAAc;;;;;;;;;;iBAmBd,qBAAqB"}
@@ -11,6 +11,16 @@ declare function withExtensionSource(pi: ExtensionAPI, entryPath: string): Exten
11
11
  /** Original extension entry that registered a tool inside a composed bundle. */
12
12
  declare function extensionToolSource(pi: ExtensionAPI, toolName: string): string | undefined;
13
13
  declare function extensionName(entryPath: string): string;
14
+ /**
15
+ * The package name exactly as a manifest spells it, scope and all.
16
+ *
17
+ * `extensionName` drops the scope because a tree reads better without it, but
18
+ * that makes it useless as a key: `.doom/modes.yaml` names packages as
19
+ * `@agimon-ai/doompi-team`, so joining a registered tool back to the layer that
20
+ * admitted it needs the unshortened name. Attribution and display are different
21
+ * jobs, so they get different functions rather than one lossy one.
22
+ */
23
+ declare function extensionPackageName(entryPath: string): string | undefined;
14
24
  //#endregion
15
- export { extensionName, extensionToolSource, withExtensionSource };
25
+ export { extensionName, extensionPackageName, extensionToolSource, withExtensionSource };
16
26
  //# sourceMappingURL=extensionName.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extensionName.d.mts","names":[],"sources":["../../../src/adapters/pi/extensionName.ts"],"mappings":";;;;;;;;;iBAkDgB,oBAAoB,IAAI,cAAc,oBAAoB;;iBAa1D,oBAAoB,IAAI,cAAc;iBAmBtC,cAAc"}
1
+ {"version":3,"file":"extensionName.d.mts","names":[],"sources":["../../../src/adapters/pi/extensionName.ts"],"mappings":";;;;;;;;;iBAqDgB,oBAAoB,IAAI,cAAc,oBAAoB;;iBAa1D,oBAAoB,IAAI,cAAc;iBAmBtC,cAAc;;;;;;;;;;iBAmBd,qBAAqB"}
@@ -1,2 +1,2 @@
1
- import e from"node:path";import t from"node:fs";const n=new Map,r=`__doompiExtensionToolSources__`;function i(e){let t=e,n=t[r];if(n)return n;let i=new Map;return Object.defineProperty(t,r,{configurable:!1,enumerable:!1,value:i,writable:!1}),i}function a(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function o(e,t){return new Proxy(e,{get(e,n){return n===`registerTool`?n=>{e.registerTool(n),i(e).set(n.name,Object.freeze({entryPath:t}))}:a(e,n)}})}function s(e,t){return i(e).get(t)?.entryPath}function c(n){let r=n;for(;;){try{let n=JSON.parse(t.readFileSync(e.join(r,`package.json`),`utf8`));if(typeof n.name==`string`&&n.name.length>0)return n.name}catch{}let n=e.dirname(r);if(n===r)return;r=n}}function l(t){let r=n.get(t);if(r!==void 0)return r;let i=c(e.dirname(t)),a=i?i.split(`/`).pop()??i:e.basename(t,e.extname(t));return n.set(t,a),a}export{l as extensionName,s as extensionToolSource,o as withExtensionSource};
1
+ import e from"node:path";import t from"node:fs";const n=new Map,r=new Map,i=`__doompiExtensionToolSources__`;function a(e){let t=e,n=t[i];if(n)return n;let r=new Map;return Object.defineProperty(t,i,{configurable:!1,enumerable:!1,value:r,writable:!1}),r}function o(e,t){let n=Reflect.get(e,t,e);return typeof n==`function`?n.bind(e):n}function s(e,t){return new Proxy(e,{get(e,n){return n===`registerTool`?n=>{e.registerTool(n),a(e).set(n.name,Object.freeze({entryPath:t}))}:o(e,n)}})}function c(e,t){return a(e).get(t)?.entryPath}function l(n){let r=n;for(;;){try{let n=JSON.parse(t.readFileSync(e.join(r,`package.json`),`utf8`));if(typeof n.name==`string`&&n.name.length>0)return n.name}catch{}let n=e.dirname(r);if(n===r)return;r=n}}function u(t){let r=n.get(t);if(r!==void 0)return r;let i=l(e.dirname(t)),a=i?i.split(`/`).pop()??i:e.basename(t,e.extname(t));return n.set(t,a),a}function d(t){let n=r.get(t);if(n!==void 0)return n.name;let i=l(e.dirname(t));return r.set(t,{name:i}),i}export{u as extensionName,d as extensionPackageName,c as extensionToolSource,s as withExtensionSource};
2
2
  //# sourceMappingURL=extensionName.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extensionName.mjs","names":[],"sources":["../../../src/adapters/pi/extensionName.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\n\n/**\n * Display name for an extension that Pi could not attribute to a package.\n *\n * Pi stamps every `--extension <path>` argument with the literal source `cli`,\n * and doom-pi launches all of its extensions that way, so the only surviving\n * identity is the entry path. The nearest `package.json` above it names the\n * package that registered the tools.\n */\n\n/** Results are stable for the life of a session, and every source hits the same few dirs. */\nconst cache = new Map<string, string>();\nconst TOOL_SOURCE_REGISTRY_KEY = '__doompiExtensionToolSources__' as const;\n\ntype RegisteredTool = Parameters<ExtensionAPI['registerTool']>[0];\ninterface ToolSourceMetadata {\n readonly entryPath: string;\n}\ntype RegistryHost = ExtensionAPI & { [TOOL_SOURCE_REGISTRY_KEY]?: Map<string, ToolSourceMetadata> };\n\n/** Session-local provenance attached to Pi's API rather than process-global state. */\nfunction toolSourceRegistry(pi: ExtensionAPI): Map<string, ToolSourceMetadata> {\n const host = pi as RegistryHost;\n const existing = host[TOOL_SOURCE_REGISTRY_KEY];\n if (existing) return existing;\n const registry = new Map<string, ToolSourceMetadata>();\n Object.defineProperty(host, TOOL_SOURCE_REGISTRY_KEY, {\n configurable: false,\n enumerable: false,\n value: registry,\n writable: false,\n });\n return registry;\n}\n\nfunction boundValue(target: object, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\n/**\n * Gives one composed extension a Pi API that remembers which tools it registers.\n *\n * Pi attributes every factory inside a compiled Doom bundle to the outer bundle.\n * The compiler still knows each original entry path, so it calls factories through\n * this boundary and preserves that identity before the information is lost.\n */\nexport function withExtensionSource(pi: ExtensionAPI, entryPath: string): ExtensionAPI {\n return new Proxy(pi, {\n get(target, property) {\n if (property !== 'registerTool') return boundValue(target, property);\n return (tool: RegisteredTool): void => {\n target.registerTool(tool);\n toolSourceRegistry(target).set(tool.name, Object.freeze({ entryPath }));\n };\n },\n });\n}\n\n/** Original extension entry that registered a tool inside a composed bundle. */\nexport function extensionToolSource(pi: ExtensionAPI, toolName: string): string | undefined {\n return toolSourceRegistry(pi).get(toolName)?.entryPath;\n}\n\nfunction packageName(directory: string): string | undefined {\n let current = directory;\n for (;;) {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(current, 'package.json'), 'utf8')) as { name?: unknown };\n if (typeof manifest.name === 'string' && manifest.name.length > 0) return manifest.name;\n } catch {\n // No manifest here, or an unreadable one; keep walking up.\n }\n const parent = path.dirname(current);\n if (parent === current) return undefined;\n current = parent;\n }\n}\n\nexport function extensionName(entryPath: string): string {\n const cached = cache.get(entryPath);\n if (cached !== undefined) return cached;\n const name = packageName(path.dirname(entryPath));\n // `@agimon-ai/doompi-task` reads as `doom-task`; the scope is noise in a tree.\n const resolved = name ? (name.split('/').pop() ?? name) : path.basename(entryPath, path.extname(entryPath));\n cache.set(entryPath, resolved);\n return resolved;\n}\n"],"mappings":"gDAcA,MAAM,EAAQ,IAAI,IACZ,EAA2B,iCASjC,SAAS,EAAmB,EAAmD,CAC7E,IAAM,EAAO,EACP,EAAW,EAAK,GACtB,GAAI,EAAU,OAAO,EACrB,IAAM,EAAW,IAAI,IAOrB,OANA,OAAO,eAAe,EAAM,EAA0B,CACpD,aAAc,GACd,WAAY,GACZ,MAAO,EACP,SAAU,EACZ,CAAC,EACM,CACT,CAEA,SAAS,EAAW,EAAgB,EAAgC,CAClE,IAAM,EAAQ,QAAQ,IAAI,EAAQ,EAAU,CAAM,EAClD,OAAO,OAAO,GAAU,WAAa,EAAM,KAAK,CAAM,EAAI,CAC5D,CASA,SAAgB,EAAoB,EAAkB,EAAiC,CACrF,OAAO,IAAI,MAAM,EAAI,CACnB,IAAI,EAAQ,EAAU,CAEpB,OADI,IAAa,eACT,GAA+B,CACrC,EAAO,aAAa,CAAI,EACxB,EAAmB,CAAM,CAAC,CAAC,IAAI,EAAK,KAAM,OAAO,OAAO,CAAE,WAAU,CAAC,CAAC,CACxE,EAJwC,EAAW,EAAQ,CAAQ,CAKrE,CACF,CAAC,CACH,CAGA,SAAgB,EAAoB,EAAkB,EAAsC,CAC1F,OAAO,EAAmB,CAAE,CAAC,CAAC,IAAI,CAAQ,CAAC,EAAE,SAC/C,CAEA,SAAS,EAAY,EAAuC,CAC1D,IAAI,EAAU,EACd,OAAS,CACP,GAAI,CACF,IAAM,EAAW,KAAK,MAAM,EAAG,aAAa,EAAK,KAAK,EAAS,cAAc,EAAG,MAAM,CAAC,EACvF,GAAI,OAAO,EAAS,MAAS,UAAY,EAAS,KAAK,OAAS,EAAG,OAAO,EAAS,IACrF,MAAQ,CAER,CACA,IAAM,EAAS,EAAK,QAAQ,CAAO,EACnC,GAAI,IAAW,EAAS,OACxB,EAAU,CACZ,CACF,CAEA,SAAgB,EAAc,EAA2B,CACvD,IAAM,EAAS,EAAM,IAAI,CAAS,EAClC,GAAI,IAAW,IAAA,GAAW,OAAO,EACjC,IAAM,EAAO,EAAY,EAAK,QAAQ,CAAS,CAAC,EAE1C,EAAW,EAAQ,EAAK,MAAM,GAAG,CAAC,CAAC,IAAI,GAAK,EAAQ,EAAK,SAAS,EAAW,EAAK,QAAQ,CAAS,CAAC,EAE1G,OADA,EAAM,IAAI,EAAW,CAAQ,EACtB,CACT"}
1
+ {"version":3,"file":"extensionName.mjs","names":[],"sources":["../../../src/adapters/pi/extensionName.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\n\n/**\n * Display name for an extension that Pi could not attribute to a package.\n *\n * Pi stamps every `--extension <path>` argument with the literal source `cli`,\n * and doom-pi launches all of its extensions that way, so the only surviving\n * identity is the entry path. The nearest `package.json` above it names the\n * package that registered the tools.\n */\n\n/** Results are stable for the life of a session, and every source hits the same few dirs. */\nconst cache = new Map<string, string>();\n// Wrapped, because \"no package above this path\" is a real answer worth caching\n// and a bare undefined cannot be told apart from a miss.\nconst packageCache = new Map<string, { readonly name: string | undefined }>();\nconst TOOL_SOURCE_REGISTRY_KEY = '__doompiExtensionToolSources__' as const;\n\ntype RegisteredTool = Parameters<ExtensionAPI['registerTool']>[0];\ninterface ToolSourceMetadata {\n readonly entryPath: string;\n}\ntype RegistryHost = ExtensionAPI & { [TOOL_SOURCE_REGISTRY_KEY]?: Map<string, ToolSourceMetadata> };\n\n/** Session-local provenance attached to Pi's API rather than process-global state. */\nfunction toolSourceRegistry(pi: ExtensionAPI): Map<string, ToolSourceMetadata> {\n const host = pi as RegistryHost;\n const existing = host[TOOL_SOURCE_REGISTRY_KEY];\n if (existing) return existing;\n const registry = new Map<string, ToolSourceMetadata>();\n Object.defineProperty(host, TOOL_SOURCE_REGISTRY_KEY, {\n configurable: false,\n enumerable: false,\n value: registry,\n writable: false,\n });\n return registry;\n}\n\nfunction boundValue(target: object, property: PropertyKey): unknown {\n const value = Reflect.get(target, property, target);\n return typeof value === 'function' ? value.bind(target) : value;\n}\n\n/**\n * Gives one composed extension a Pi API that remembers which tools it registers.\n *\n * Pi attributes every factory inside a compiled Doom bundle to the outer bundle.\n * The compiler still knows each original entry path, so it calls factories through\n * this boundary and preserves that identity before the information is lost.\n */\nexport function withExtensionSource(pi: ExtensionAPI, entryPath: string): ExtensionAPI {\n return new Proxy(pi, {\n get(target, property) {\n if (property !== 'registerTool') return boundValue(target, property);\n return (tool: RegisteredTool): void => {\n target.registerTool(tool);\n toolSourceRegistry(target).set(tool.name, Object.freeze({ entryPath }));\n };\n },\n });\n}\n\n/** Original extension entry that registered a tool inside a composed bundle. */\nexport function extensionToolSource(pi: ExtensionAPI, toolName: string): string | undefined {\n return toolSourceRegistry(pi).get(toolName)?.entryPath;\n}\n\nfunction packageName(directory: string): string | undefined {\n let current = directory;\n for (;;) {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(current, 'package.json'), 'utf8')) as { name?: unknown };\n if (typeof manifest.name === 'string' && manifest.name.length > 0) return manifest.name;\n } catch {\n // No manifest here, or an unreadable one; keep walking up.\n }\n const parent = path.dirname(current);\n if (parent === current) return undefined;\n current = parent;\n }\n}\n\nexport function extensionName(entryPath: string): string {\n const cached = cache.get(entryPath);\n if (cached !== undefined) return cached;\n const name = packageName(path.dirname(entryPath));\n // `@agimon-ai/doompi-task` reads as `doom-task`; the scope is noise in a tree.\n const resolved = name ? (name.split('/').pop() ?? name) : path.basename(entryPath, path.extname(entryPath));\n cache.set(entryPath, resolved);\n return resolved;\n}\n\n/**\n * The package name exactly as a manifest spells it, scope and all.\n *\n * `extensionName` drops the scope because a tree reads better without it, but\n * that makes it useless as a key: `.doom/modes.yaml` names packages as\n * `@agimon-ai/doompi-team`, so joining a registered tool back to the layer that\n * admitted it needs the unshortened name. Attribution and display are different\n * jobs, so they get different functions rather than one lossy one.\n */\nexport function extensionPackageName(entryPath: string): string | undefined {\n const cached = packageCache.get(entryPath);\n if (cached !== undefined) return cached.name;\n const name = packageName(path.dirname(entryPath));\n packageCache.set(entryPath, { name });\n return name;\n}\n"],"mappings":"gDAcA,MAAM,EAAQ,IAAI,IAGZ,EAAe,IAAI,IACnB,EAA2B,iCASjC,SAAS,EAAmB,EAAmD,CAC7E,IAAM,EAAO,EACP,EAAW,EAAK,GACtB,GAAI,EAAU,OAAO,EACrB,IAAM,EAAW,IAAI,IAOrB,OANA,OAAO,eAAe,EAAM,EAA0B,CACpD,aAAc,GACd,WAAY,GACZ,MAAO,EACP,SAAU,EACZ,CAAC,EACM,CACT,CAEA,SAAS,EAAW,EAAgB,EAAgC,CAClE,IAAM,EAAQ,QAAQ,IAAI,EAAQ,EAAU,CAAM,EAClD,OAAO,OAAO,GAAU,WAAa,EAAM,KAAK,CAAM,EAAI,CAC5D,CASA,SAAgB,EAAoB,EAAkB,EAAiC,CACrF,OAAO,IAAI,MAAM,EAAI,CACnB,IAAI,EAAQ,EAAU,CAEpB,OADI,IAAa,eACT,GAA+B,CACrC,EAAO,aAAa,CAAI,EACxB,EAAmB,CAAM,CAAC,CAAC,IAAI,EAAK,KAAM,OAAO,OAAO,CAAE,WAAU,CAAC,CAAC,CACxE,EAJwC,EAAW,EAAQ,CAAQ,CAKrE,CACF,CAAC,CACH,CAGA,SAAgB,EAAoB,EAAkB,EAAsC,CAC1F,OAAO,EAAmB,CAAE,CAAC,CAAC,IAAI,CAAQ,CAAC,EAAE,SAC/C,CAEA,SAAS,EAAY,EAAuC,CAC1D,IAAI,EAAU,EACd,OAAS,CACP,GAAI,CACF,IAAM,EAAW,KAAK,MAAM,EAAG,aAAa,EAAK,KAAK,EAAS,cAAc,EAAG,MAAM,CAAC,EACvF,GAAI,OAAO,EAAS,MAAS,UAAY,EAAS,KAAK,OAAS,EAAG,OAAO,EAAS,IACrF,MAAQ,CAER,CACA,IAAM,EAAS,EAAK,QAAQ,CAAO,EACnC,GAAI,IAAW,EAAS,OACxB,EAAU,CACZ,CACF,CAEA,SAAgB,EAAc,EAA2B,CACvD,IAAM,EAAS,EAAM,IAAI,CAAS,EAClC,GAAI,IAAW,IAAA,GAAW,OAAO,EACjC,IAAM,EAAO,EAAY,EAAK,QAAQ,CAAS,CAAC,EAE1C,EAAW,EAAQ,EAAK,MAAM,GAAG,CAAC,CAAC,IAAI,GAAK,EAAQ,EAAK,SAAS,EAAW,EAAK,QAAQ,CAAS,CAAC,EAE1G,OADA,EAAM,IAAI,EAAW,CAAQ,EACtB,CACT,CAWA,SAAgB,EAAqB,EAAuC,CAC1E,IAAM,EAAS,EAAa,IAAI,CAAS,EACzC,GAAI,IAAW,IAAA,GAAW,OAAO,EAAO,KACxC,IAAM,EAAO,EAAY,EAAK,QAAQ,CAAS,CAAC,EAEhD,OADA,EAAa,IAAI,EAAW,CAAE,MAAK,CAAC,EAC7B,CACT"}
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters/pi/extensionName.cjs");exports.extensionName=e.extensionName,exports.extensionToolSource=e.extensionToolSource,exports.withExtensionSource=e.withExtensionSource;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./adapters/pi/extensionName.cjs");exports.extensionName=e.extensionName,exports.extensionPackageName=e.extensionPackageName,exports.extensionToolSource=e.extensionToolSource,exports.withExtensionSource=e.withExtensionSource;
@@ -1,2 +1,2 @@
1
- import { extensionName, extensionToolSource, withExtensionSource } from "./adapters/pi/extensionName.cjs";
2
- export { extensionName, extensionToolSource, withExtensionSource };
1
+ import { extensionName, extensionPackageName, extensionToolSource, withExtensionSource } from "./adapters/pi/extensionName.cjs";
2
+ export { extensionName, extensionPackageName, extensionToolSource, withExtensionSource };
@@ -1,2 +1,2 @@
1
- import { extensionName, extensionToolSource, withExtensionSource } from "./adapters/pi/extensionName.mjs";
2
- export { extensionName, extensionToolSource, withExtensionSource };
1
+ import { extensionName, extensionPackageName, extensionToolSource, withExtensionSource } from "./adapters/pi/extensionName.mjs";
2
+ export { extensionName, extensionPackageName, extensionToolSource, withExtensionSource };
@@ -1 +1 @@
1
- import{extensionName as e,extensionToolSource as t,withExtensionSource as n}from"./adapters/pi/extensionName.mjs";export{e as extensionName,t as extensionToolSource,n as withExtensionSource};
1
+ import{extensionName as e,extensionPackageName as t,extensionToolSource as n,withExtensionSource as r}from"./adapters/pi/extensionName.mjs";export{e as extensionName,t as extensionPackageName,n as extensionToolSource,r as withExtensionSource};
@@ -0,0 +1,3 @@
1
+ function e(e){return JSON.stringify({name:e.name,...e.description===void 0?{}:{description:e.description},...e.parameters===void 0?{}:{parameters:e.parameters}})}function t(e){return[e.promptSnippet??``,...e.promptGuidelines??[]].filter(Boolean).join(`
2
+ `)}function n(n,r){let i=r(e(n)),a=t(n),o=a.length===0?0:r(a);return{schemaTokens:i,promptTokens:o,totalTokens:i+o}}function r(e,t){let r=new Map,i=0;for(let a of e.tools){let e=n(a,t);r.set(a.name,e),i+=e.totalTokens}return{tools:r,totalTokens:i}}exports.tokensForSource=r,exports.tokensForTool=n;
3
+ //# sourceMappingURL=toolCost.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolCost.cjs","names":[],"sources":["../../../src/services/tools/toolCost.ts"],"sourcesContent":["import type { ToolEntry, ToolSource } from './toolInventory.ts';\n\n/**\n * What one tool costs before anything is asked of it.\n *\n * A tool is sent to the model twice over. Its schema travels in the tool list,\n * and its prose travels in the system prompt: Pi's `BuildSystemPromptOptions`\n * takes `toolSnippets` keyed by tool name and `promptGuidelines` appended to the\n * default guidelines, separately from the tools themselves. Pricing only the\n * schema would undercount every tool that carries either, so the two are counted\n * apart and reported together.\n *\n * The tokenizer is injected rather than imported, matching how MCP schema\n * pricing already takes a `countTokens`. This module stays free of any opinion\n * about which vocabulary the caller's model actually uses.\n */\nexport interface ToolCost {\n /** The tool-list payload: name, description, and parameter schema. */\n schemaTokens: number;\n /** The system-prompt payload: snippet and guideline bullets. */\n promptTokens: number;\n totalTokens: number;\n}\n\nexport type CountTokens = (text: string) => number;\n\n/** Mirrors the wire shape MCP advertises, so both estimates stay comparable. */\nfunction schemaPayload(entry: ToolEntry): string {\n return JSON.stringify({\n name: entry.name,\n ...(entry.description === undefined ? {} : { description: entry.description }),\n ...(entry.parameters === undefined ? {} : { parameters: entry.parameters }),\n });\n}\n\nfunction promptPayload(entry: ToolEntry): string {\n return [entry.promptSnippet ?? '', ...(entry.promptGuidelines ?? [])].filter(Boolean).join('\\n');\n}\n\nexport function tokensForTool(entry: ToolEntry, countTokens: CountTokens): ToolCost {\n const schemaTokens = countTokens(schemaPayload(entry));\n const prompt = promptPayload(entry);\n const promptTokens = prompt.length === 0 ? 0 : countTokens(prompt);\n return { schemaTokens, promptTokens, totalTokens: schemaTokens + promptTokens };\n}\n\n/**\n * Every tool in a source, and what the source costs in total.\n *\n * An inactive tool is still priced: it is in the composition, and the point of\n * the figure is to show what the composition costs. Whether the model may call\n * it is a separate question from whether it is being paid for.\n */\nexport function tokensForSource(\n source: ToolSource,\n countTokens: CountTokens,\n): { tools: ReadonlyMap<string, ToolCost>; totalTokens: number } {\n const tools = new Map<string, ToolCost>();\n let totalTokens = 0;\n for (const entry of source.tools) {\n const cost = tokensForTool(entry, countTokens);\n tools.set(entry.name, cost);\n totalTokens += cost.totalTokens;\n }\n return { tools, totalTokens };\n}\n"],"mappings":"AA2BA,SAAS,EAAc,EAA0B,CAC/C,OAAO,KAAK,UAAU,CACpB,KAAM,EAAM,KACZ,GAAI,EAAM,cAAgB,IAAA,GAAY,CAAC,EAAI,CAAE,YAAa,EAAM,WAAY,EAC5E,GAAI,EAAM,aAAe,IAAA,GAAY,CAAC,EAAI,CAAE,WAAY,EAAM,UAAW,CAC3E,CAAC,CACH,CAEA,SAAS,EAAc,EAA0B,CAC/C,MAAO,CAAC,EAAM,eAAiB,GAAI,GAAI,EAAM,kBAAoB,CAAC,CAAE,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK;CAAI,CACjG,CAEA,SAAgB,EAAc,EAAkB,EAAoC,CAClF,IAAM,EAAe,EAAY,EAAc,CAAK,CAAC,EAC/C,EAAS,EAAc,CAAK,EAC5B,EAAe,EAAO,SAAW,EAAI,EAAI,EAAY,CAAM,EACjE,MAAO,CAAE,eAAc,eAAc,YAAa,EAAe,CAAa,CAChF,CASA,SAAgB,EACd,EACA,EAC+D,CAC/D,IAAM,EAAQ,IAAI,IACd,EAAc,EAClB,IAAK,IAAM,KAAS,EAAO,MAAO,CAChC,IAAM,EAAO,EAAc,EAAO,CAAW,EAC7C,EAAM,IAAI,EAAM,KAAM,CAAI,EAC1B,GAAe,EAAK,WACtB,CACA,MAAO,CAAE,QAAO,aAAY,CAC9B"}
@@ -0,0 +1,39 @@
1
+ import { ToolEntry, ToolSource } from "./toolInventory.cjs";
2
+ //#region src/services/tools/toolCost.d.ts
3
+ /**
4
+ * What one tool costs before anything is asked of it.
5
+ *
6
+ * A tool is sent to the model twice over. Its schema travels in the tool list,
7
+ * and its prose travels in the system prompt: Pi's `BuildSystemPromptOptions`
8
+ * takes `toolSnippets` keyed by tool name and `promptGuidelines` appended to the
9
+ * default guidelines, separately from the tools themselves. Pricing only the
10
+ * schema would undercount every tool that carries either, so the two are counted
11
+ * apart and reported together.
12
+ *
13
+ * The tokenizer is injected rather than imported, matching how MCP schema
14
+ * pricing already takes a `countTokens`. This module stays free of any opinion
15
+ * about which vocabulary the caller's model actually uses.
16
+ */
17
+ interface ToolCost {
18
+ /** The tool-list payload: name, description, and parameter schema. */
19
+ schemaTokens: number;
20
+ /** The system-prompt payload: snippet and guideline bullets. */
21
+ promptTokens: number;
22
+ totalTokens: number;
23
+ }
24
+ type CountTokens = (text: string) => number;
25
+ declare function tokensForTool(entry: ToolEntry, countTokens: CountTokens): ToolCost;
26
+ /**
27
+ * Every tool in a source, and what the source costs in total.
28
+ *
29
+ * An inactive tool is still priced: it is in the composition, and the point of
30
+ * the figure is to show what the composition costs. Whether the model may call
31
+ * it is a separate question from whether it is being paid for.
32
+ */
33
+ declare function tokensForSource(source: ToolSource, countTokens: CountTokens): {
34
+ tools: ReadonlyMap<string, ToolCost>;
35
+ totalTokens: number;
36
+ };
37
+ //#endregion
38
+ export { CountTokens, ToolCost, tokensForSource, tokensForTool };
39
+ //# sourceMappingURL=toolCost.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolCost.d.cts","names":[],"sources":["../../../src/services/tools/toolCost.ts"],"mappings":";;;;;;;;;;;;;;;;UAgBiB;;EAEf;;EAEA;EACA;;KAGU,eAAe;iBAeX,cAAc,OAAO,WAAW,aAAa,cAAc;;;;;;;;iBAc3D,gBACd,QAAQ,YACR,aAAa;EACV,OAAO,oBAAoB;EAAW"}
@@ -0,0 +1,39 @@
1
+ import { ToolEntry, ToolSource } from "./toolInventory.mjs";
2
+ //#region src/services/tools/toolCost.d.ts
3
+ /**
4
+ * What one tool costs before anything is asked of it.
5
+ *
6
+ * A tool is sent to the model twice over. Its schema travels in the tool list,
7
+ * and its prose travels in the system prompt: Pi's `BuildSystemPromptOptions`
8
+ * takes `toolSnippets` keyed by tool name and `promptGuidelines` appended to the
9
+ * default guidelines, separately from the tools themselves. Pricing only the
10
+ * schema would undercount every tool that carries either, so the two are counted
11
+ * apart and reported together.
12
+ *
13
+ * The tokenizer is injected rather than imported, matching how MCP schema
14
+ * pricing already takes a `countTokens`. This module stays free of any opinion
15
+ * about which vocabulary the caller's model actually uses.
16
+ */
17
+ interface ToolCost {
18
+ /** The tool-list payload: name, description, and parameter schema. */
19
+ schemaTokens: number;
20
+ /** The system-prompt payload: snippet and guideline bullets. */
21
+ promptTokens: number;
22
+ totalTokens: number;
23
+ }
24
+ type CountTokens = (text: string) => number;
25
+ declare function tokensForTool(entry: ToolEntry, countTokens: CountTokens): ToolCost;
26
+ /**
27
+ * Every tool in a source, and what the source costs in total.
28
+ *
29
+ * An inactive tool is still priced: it is in the composition, and the point of
30
+ * the figure is to show what the composition costs. Whether the model may call
31
+ * it is a separate question from whether it is being paid for.
32
+ */
33
+ declare function tokensForSource(source: ToolSource, countTokens: CountTokens): {
34
+ tools: ReadonlyMap<string, ToolCost>;
35
+ totalTokens: number;
36
+ };
37
+ //#endregion
38
+ export { CountTokens, ToolCost, tokensForSource, tokensForTool };
39
+ //# sourceMappingURL=toolCost.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolCost.d.mts","names":[],"sources":["../../../src/services/tools/toolCost.ts"],"mappings":";;;;;;;;;;;;;;;;UAgBiB;;EAEf;;EAEA;EACA;;KAGU,eAAe;iBAeX,cAAc,OAAO,WAAW,aAAa,cAAc;;;;;;;;iBAc3D,gBACd,QAAQ,YACR,aAAa;EACV,OAAO,oBAAoB;EAAW"}
@@ -0,0 +1,3 @@
1
+ function e(e){return JSON.stringify({name:e.name,...e.description===void 0?{}:{description:e.description},...e.parameters===void 0?{}:{parameters:e.parameters}})}function t(e){return[e.promptSnippet??``,...e.promptGuidelines??[]].filter(Boolean).join(`
2
+ `)}function n(n,r){let i=r(e(n)),a=t(n),o=a.length===0?0:r(a);return{schemaTokens:i,promptTokens:o,totalTokens:i+o}}function r(e,t){let r=new Map,i=0;for(let a of e.tools){let e=n(a,t);r.set(a.name,e),i+=e.totalTokens}return{tools:r,totalTokens:i}}export{r as tokensForSource,n as tokensForTool};
3
+ //# sourceMappingURL=toolCost.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolCost.mjs","names":[],"sources":["../../../src/services/tools/toolCost.ts"],"sourcesContent":["import type { ToolEntry, ToolSource } from './toolInventory.ts';\n\n/**\n * What one tool costs before anything is asked of it.\n *\n * A tool is sent to the model twice over. Its schema travels in the tool list,\n * and its prose travels in the system prompt: Pi's `BuildSystemPromptOptions`\n * takes `toolSnippets` keyed by tool name and `promptGuidelines` appended to the\n * default guidelines, separately from the tools themselves. Pricing only the\n * schema would undercount every tool that carries either, so the two are counted\n * apart and reported together.\n *\n * The tokenizer is injected rather than imported, matching how MCP schema\n * pricing already takes a `countTokens`. This module stays free of any opinion\n * about which vocabulary the caller's model actually uses.\n */\nexport interface ToolCost {\n /** The tool-list payload: name, description, and parameter schema. */\n schemaTokens: number;\n /** The system-prompt payload: snippet and guideline bullets. */\n promptTokens: number;\n totalTokens: number;\n}\n\nexport type CountTokens = (text: string) => number;\n\n/** Mirrors the wire shape MCP advertises, so both estimates stay comparable. */\nfunction schemaPayload(entry: ToolEntry): string {\n return JSON.stringify({\n name: entry.name,\n ...(entry.description === undefined ? {} : { description: entry.description }),\n ...(entry.parameters === undefined ? {} : { parameters: entry.parameters }),\n });\n}\n\nfunction promptPayload(entry: ToolEntry): string {\n return [entry.promptSnippet ?? '', ...(entry.promptGuidelines ?? [])].filter(Boolean).join('\\n');\n}\n\nexport function tokensForTool(entry: ToolEntry, countTokens: CountTokens): ToolCost {\n const schemaTokens = countTokens(schemaPayload(entry));\n const prompt = promptPayload(entry);\n const promptTokens = prompt.length === 0 ? 0 : countTokens(prompt);\n return { schemaTokens, promptTokens, totalTokens: schemaTokens + promptTokens };\n}\n\n/**\n * Every tool in a source, and what the source costs in total.\n *\n * An inactive tool is still priced: it is in the composition, and the point of\n * the figure is to show what the composition costs. Whether the model may call\n * it is a separate question from whether it is being paid for.\n */\nexport function tokensForSource(\n source: ToolSource,\n countTokens: CountTokens,\n): { tools: ReadonlyMap<string, ToolCost>; totalTokens: number } {\n const tools = new Map<string, ToolCost>();\n let totalTokens = 0;\n for (const entry of source.tools) {\n const cost = tokensForTool(entry, countTokens);\n tools.set(entry.name, cost);\n totalTokens += cost.totalTokens;\n }\n return { tools, totalTokens };\n}\n"],"mappings":"AA2BA,SAAS,EAAc,EAA0B,CAC/C,OAAO,KAAK,UAAU,CACpB,KAAM,EAAM,KACZ,GAAI,EAAM,cAAgB,IAAA,GAAY,CAAC,EAAI,CAAE,YAAa,EAAM,WAAY,EAC5E,GAAI,EAAM,aAAe,IAAA,GAAY,CAAC,EAAI,CAAE,WAAY,EAAM,UAAW,CAC3E,CAAC,CACH,CAEA,SAAS,EAAc,EAA0B,CAC/C,MAAO,CAAC,EAAM,eAAiB,GAAI,GAAI,EAAM,kBAAoB,CAAC,CAAE,CAAC,CAAC,OAAO,OAAO,CAAC,CAAC,KAAK;CAAI,CACjG,CAEA,SAAgB,EAAc,EAAkB,EAAoC,CAClF,IAAM,EAAe,EAAY,EAAc,CAAK,CAAC,EAC/C,EAAS,EAAc,CAAK,EAC5B,EAAe,EAAO,SAAW,EAAI,EAAI,EAAY,CAAM,EACjE,MAAO,CAAE,eAAc,eAAc,YAAa,EAAe,CAAa,CAChF,CASA,SAAgB,EACd,EACA,EAC+D,CAC/D,IAAM,EAAQ,IAAI,IACd,EAAc,EAClB,IAAK,IAAM,KAAS,EAAO,MAAO,CAChC,IAAM,EAAO,EAAc,EAAO,CAAW,EAC7C,EAAM,IAAI,EAAM,KAAM,CAAI,EAC1B,GAAe,EAAK,WACtB,CACA,MAAO,CAAE,QAAO,aAAY,CAC9B"}
@@ -1,2 +1,2 @@
1
- const e=`core`,t={core:`core`,mcp:`mcp`,extension:`extension`},n={core:0,mcp:1,extension:2};function r(e){return e.state===`connected`?void 0:e.state.replace(/-/g,` `)}function i(e,t){return{name:e.name,...e.description?{description:e.description}:{},...e.parameters===void 0?{}:{parameters:e.parameters},...e.promptGuidelines?.length?{promptGuidelines:e.promptGuidelines}:{},active:t.has(e.name)}}function a(e,t){return e.name.localeCompare(t.name)}function o(e,n){return`${e} · ${t[n]}`}function s(e){let t=new Map;for(let n of e)for(let e of n.tools)t.set(e,n);return t}function c(t){let c=new Set(t.activeTools),l=t.mcpServers??[],u=s(l),d=[],f=new Map,p=new Map;for(let e of t.tools){let n=i(e,c),r=u.get(e.name);if(r){let e=f.get(r.name);e?e.push(n):f.set(r.name,[n]);continue}if(e.sourceInfo.source===`builtin`){d.push(n);continue}let a=t.resolveExtensionToolSource?.(e.name),s=a??e.sourceInfo.path,l=p.get(s);if(l)l.tools.push(n);else{let r=a!==void 0||e.sourceInfo.source===`cli`||e.sourceInfo.source===`local`||!e.sourceInfo.source?t.resolveExtensionName?.(s)??e.sourceInfo.source??s:e.sourceInfo.source??s;p.set(s,{label:o(r,`extension`),detail:s,tools:[n]})}}let m=[];d.length>0&&m.push({key:e,label:o(`pi`,e),kind:e,tools:d.sort(a)});for(let e of l){let t=f.get(e.name)??[],n=r(e);m.push({key:`mcp:${e.name}`,label:o(e.name,`mcp`),kind:`mcp`,...n?{status:n}:{},tools:t.sort(a)})}for(let[e,t]of p)m.push({key:e,label:t.label,kind:`extension`,detail:t.detail,tools:t.tools.sort(a)});return m.sort((e,t)=>n[e.kind]-n[t.kind]||e.label.localeCompare(t.label))}exports.buildToolSources=c;
1
+ const e=`core`,t={core:`core`,mcp:`mcp`,extension:`extension`},n={core:0,mcp:1,extension:2};function r(e){return e.state===`connected`?void 0:e.state.replace(/-/g,` `)}function i(e,t){return{name:e.name,...e.description?{description:e.description}:{},...e.parameters===void 0?{}:{parameters:e.parameters},...e.promptSnippet?{promptSnippet:e.promptSnippet}:{},...e.promptGuidelines?.length?{promptGuidelines:e.promptGuidelines}:{},active:t.has(e.name)}}function a(e,t){return e.name.localeCompare(t.name)}function o(e,n){return`${e} · ${t[n]}`}function s(e){let t=new Map;for(let n of e)for(let e of n.tools)t.set(e,n);return t}function c(t){let c=new Set(t.activeTools),l=t.mcpServers??[],u=s(l),d=[],f=new Map,p=new Map;for(let e of t.tools){let n=i(e,c),r=u.get(e.name);if(r){let e=f.get(r.name);e?e.push(n):f.set(r.name,[n]);continue}if(e.sourceInfo.source===`builtin`){d.push(n);continue}let a=t.resolveExtensionToolSource?.(e.name),s=a??e.sourceInfo.path,l=p.get(s);if(l)l.tools.push(n);else{let r=a!==void 0||e.sourceInfo.source===`cli`||e.sourceInfo.source===`local`||!e.sourceInfo.source?t.resolveExtensionName?.(s)??e.sourceInfo.source??s:e.sourceInfo.source??s,i=t.resolveExtensionPackageName?.(s);p.set(s,{label:o(r,`extension`),detail:s,...i?{packageName:i}:{},tools:[n]})}}let m=[];d.length>0&&m.push({key:e,label:o(`pi`,e),kind:e,tools:d.sort(a)});for(let e of l){let t=f.get(e.name)??[],n=r(e);m.push({key:`mcp:${e.name}`,label:o(e.name,`mcp`),kind:`mcp`,...n?{status:n}:{},tools:t.sort(a)})}for(let[e,t]of p)m.push({key:e,label:t.label,kind:`extension`,detail:t.detail,...t.packageName?{packageName:t.packageName}:{},tools:t.tools.sort(a)});return m.sort((e,t)=>n[e.kind]-n[t.kind]||e.label.localeCompare(t.label))}exports.buildToolSources=c;
2
2
  //# sourceMappingURL=toolInventory.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"toolInventory.cjs","names":[],"sources":["../../../src/services/tools/toolInventory.ts"],"sourcesContent":["import type { McpServerSnapshot } from '@agimon-ai/doompi-extension-contracts/mcp-status';\n\nexport type ToolSourceKind = 'core' | 'mcp' | 'extension';\n\nexport interface ToolEntry {\n name: string;\n description?: string;\n parameters?: unknown;\n promptGuidelines?: readonly string[];\n active: boolean;\n}\n\nexport interface ToolSource {\n key: string;\n label: string;\n kind: ToolSourceKind;\n /** Server state for MCP sources, e.g. `disabled`; absent for core and extensions. */\n status?: string;\n /** The registering file for extension sources. */\n detail?: string;\n tools: readonly ToolEntry[];\n}\n\n/** One server row of the MCP status snapshot. */\nexport type McpServerStatus = McpServerSnapshot;\n\nexport interface ToolInfo {\n name: string;\n description?: string;\n parameters?: unknown;\n promptGuidelines?: readonly string[];\n sourceInfo: { path: string; source?: string };\n}\n\nexport interface ToolInventoryInput {\n tools: readonly ToolInfo[];\n activeTools: readonly string[];\n /** Absent when the MCP adapter is not loaded or has not published yet. */\n mcpServers?: readonly McpServerStatus[];\n resolveExtensionName?: (entryPath: string) => string;\n resolveExtensionToolSource?: (toolName: string) => string | undefined;\n}\n\n/** Pi tags its own tools with this source; everything else came from an extension. */\nconst BUILTIN_SOURCE = 'builtin';\n/** Pi's structural tags for an extension path, neither of which is a package name. */\nconst CLI_SOURCE = 'cli';\nconst LOCAL_SOURCE = 'local';\nconst CORE_KEY = 'core';\nconst CORE_NAME = 'pi';\nconst MCP_KEY_PREFIX = 'mcp:';\n/** Labels read name first, then kind, because the name is what the eye scans for. */\nconst KIND_SUFFIX: Record<ToolSourceKind, string> = { core: 'core', mcp: 'mcp', extension: 'extension' };\nconst CONNECTED_STATUS = 'connected';\nconst KIND_ORDER: Record<ToolSourceKind, number> = { core: 0, mcp: 1, extension: 2 };\n\n/**\n * Server state worth reporting, which takes the place of the tool count.\n *\n * A connected server is the unremarkable case, so it keeps its count rather\n * than spending the column on a word that says nothing went wrong.\n */\nfunction displayStatus(server: McpServerStatus): string | undefined {\n return server.state === CONNECTED_STATUS ? undefined : server.state.replace(/-/g, ' ');\n}\n\nfunction toEntry(tool: ToolInfo, activeTools: ReadonlySet<string>): ToolEntry {\n return {\n name: tool.name,\n ...(tool.description ? { description: tool.description } : {}),\n ...(tool.parameters === undefined ? {} : { parameters: tool.parameters }),\n ...(tool.promptGuidelines?.length ? { promptGuidelines: tool.promptGuidelines } : {}),\n active: activeTools.has(tool.name),\n };\n}\n\nfunction byName(left: ToolEntry, right: ToolEntry): number {\n return left.name.localeCompare(right.name);\n}\n\nfunction sourceLabel(name: string, kind: ToolSourceKind): string {\n return `${name} · ${KIND_SUFFIX[kind]}`;\n}\n\n/**\n * Indexes each server's declared tool names.\n *\n * The snapshot names its tools outright, so attribution is a lookup rather than a\n * guess at the naming scheme. A tool the snapshot does not claim falls through to\n * the extension that registered it.\n */\nfunction serverByTool(servers: readonly McpServerStatus[]): Map<string, McpServerStatus> {\n const index = new Map<string, McpServerStatus>();\n for (const server of servers) {\n for (const toolName of server.tools) index.set(toolName, server);\n }\n return index;\n}\n\n/**\n * Groups the session's tools by where they came from: Pi itself, one group per\n * MCP server, then one per extension that registered a tool.\n *\n * Every MCP tool is registered by one extension, so `sourceInfo` cannot separate\n * servers; the status snapshot supplies the tool-to-server mapping instead.\n */\nexport function buildToolSources(input: ToolInventoryInput): readonly ToolSource[] {\n const activeTools = new Set(input.activeTools);\n const servers = input.mcpServers ?? [];\n const owningServer = serverByTool(servers);\n const core: ToolEntry[] = [];\n const mcpTools = new Map<string, ToolEntry[]>();\n const extensions = new Map<string, { label: string; detail: string; tools: ToolEntry[] }>();\n\n for (const tool of input.tools) {\n const entry = toEntry(tool, activeTools);\n const server = owningServer.get(tool.name);\n if (server) {\n const bucket = mcpTools.get(server.name);\n if (bucket) bucket.push(entry);\n else mcpTools.set(server.name, [entry]);\n continue;\n }\n if (tool.sourceInfo.source === BUILTIN_SOURCE) {\n core.push(entry);\n continue;\n }\n // A composed bundle has one Pi source path for every factory inside it. Doom's\n // compiler records the original entry at registration time so those extensions\n // remain separate here even though Pi can only see the outer bundle.\n const recordedPath = input.resolveExtensionToolSource?.(tool.name);\n const key = recordedPath ?? tool.sourceInfo.path;\n const group = extensions.get(key);\n if (group) group.tools.push(entry);\n else {\n const structuralSource =\n recordedPath !== undefined ||\n tool.sourceInfo.source === CLI_SOURCE ||\n tool.sourceInfo.source === LOCAL_SOURCE ||\n !tool.sourceInfo.source;\n const name = structuralSource\n ? (input.resolveExtensionName?.(key) ?? tool.sourceInfo.source ?? key)\n : (tool.sourceInfo.source ?? key);\n extensions.set(key, { label: sourceLabel(name, 'extension'), detail: key, tools: [entry] });\n }\n }\n\n const sources: ToolSource[] = [];\n if (core.length > 0) {\n sources.push({ key: CORE_KEY, label: sourceLabel(CORE_NAME, CORE_KEY), kind: CORE_KEY, tools: core.sort(byName) });\n }\n for (const server of servers) {\n const tools = mcpTools.get(server.name) ?? [];\n const status = displayStatus(server);\n // Every configured server is listed, including one with no tools: attribution\n // is exact now, so an empty list is a fact about the server rather than a\n // failure to recognise its tool names.\n sources.push({\n key: `${MCP_KEY_PREFIX}${server.name}`,\n label: sourceLabel(server.name, 'mcp'),\n kind: 'mcp',\n ...(status ? { status } : {}),\n tools: tools.sort(byName),\n });\n }\n for (const [key, group] of extensions) {\n sources.push({\n key,\n label: group.label,\n kind: 'extension',\n detail: group.detail,\n tools: group.tools.sort(byName),\n });\n }\n\n return sources.sort(\n (left, right) => KIND_ORDER[left.kind] - KIND_ORDER[right.kind] || left.label.localeCompare(right.label),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"toolInventory.cjs","names":[],"sources":["../../../src/services/tools/toolInventory.ts"],"sourcesContent":["import type { McpServerSnapshot } from '@agimon-ai/doompi-extension-contracts/mcp-status';\n\nexport type ToolSourceKind = 'core' | 'mcp' | 'extension';\n\nexport interface ToolEntry {\n name: string;\n description?: string;\n parameters?: unknown;\n /** One-line snippet Pi keys by tool name in the system prompt. */\n promptSnippet?: string;\n promptGuidelines?: readonly string[];\n active: boolean;\n}\n\nexport interface ToolSource {\n key: string;\n label: string;\n kind: ToolSourceKind;\n /** Server state for MCP sources, e.g. `disabled`; absent for core and extensions. */\n status?: string;\n /** The registering file for extension sources. */\n detail?: string;\n /**\n * Package name as its manifest spells it, scope included.\n *\n * `label` is shortened for reading. Attribution joins against `.doom`\n * configuration, which names packages in full, so the unshortened name is\n * carried separately rather than recovered from the label.\n */\n packageName?: string;\n tools: readonly ToolEntry[];\n}\n\n/** One server row of the MCP status snapshot. */\nexport type McpServerStatus = McpServerSnapshot;\n\nexport interface ToolInfo {\n name: string;\n description?: string;\n parameters?: unknown;\n promptSnippet?: string;\n promptGuidelines?: readonly string[];\n sourceInfo: { path: string; source?: string };\n}\n\nexport interface ToolInventoryInput {\n tools: readonly ToolInfo[];\n activeTools: readonly string[];\n /** Absent when the MCP adapter is not loaded or has not published yet. */\n mcpServers?: readonly McpServerStatus[];\n resolveExtensionName?: (entryPath: string) => string;\n /** Unshortened package name for the same entry path, for attribution. */\n resolveExtensionPackageName?: (entryPath: string) => string | undefined;\n resolveExtensionToolSource?: (toolName: string) => string | undefined;\n}\n\n/** Pi tags its own tools with this source; everything else came from an extension. */\nconst BUILTIN_SOURCE = 'builtin';\n/** Pi's structural tags for an extension path, neither of which is a package name. */\nconst CLI_SOURCE = 'cli';\nconst LOCAL_SOURCE = 'local';\nconst CORE_KEY = 'core';\nconst CORE_NAME = 'pi';\nconst MCP_KEY_PREFIX = 'mcp:';\n/** Labels read name first, then kind, because the name is what the eye scans for. */\nconst KIND_SUFFIX: Record<ToolSourceKind, string> = { core: 'core', mcp: 'mcp', extension: 'extension' };\nconst CONNECTED_STATUS = 'connected';\nconst KIND_ORDER: Record<ToolSourceKind, number> = { core: 0, mcp: 1, extension: 2 };\n\n/**\n * Server state worth reporting, which takes the place of the tool count.\n *\n * A connected server is the unremarkable case, so it keeps its count rather\n * than spending the column on a word that says nothing went wrong.\n */\nfunction displayStatus(server: McpServerStatus): string | undefined {\n return server.state === CONNECTED_STATUS ? undefined : server.state.replace(/-/g, ' ');\n}\n\nfunction toEntry(tool: ToolInfo, activeTools: ReadonlySet<string>): ToolEntry {\n return {\n name: tool.name,\n ...(tool.description ? { description: tool.description } : {}),\n ...(tool.parameters === undefined ? {} : { parameters: tool.parameters }),\n ...(tool.promptSnippet ? { promptSnippet: tool.promptSnippet } : {}),\n ...(tool.promptGuidelines?.length ? { promptGuidelines: tool.promptGuidelines } : {}),\n active: activeTools.has(tool.name),\n };\n}\n\nfunction byName(left: ToolEntry, right: ToolEntry): number {\n return left.name.localeCompare(right.name);\n}\n\nfunction sourceLabel(name: string, kind: ToolSourceKind): string {\n return `${name} · ${KIND_SUFFIX[kind]}`;\n}\n\n/**\n * Indexes each server's declared tool names.\n *\n * The snapshot names its tools outright, so attribution is a lookup rather than a\n * guess at the naming scheme. A tool the snapshot does not claim falls through to\n * the extension that registered it.\n */\nfunction serverByTool(servers: readonly McpServerStatus[]): Map<string, McpServerStatus> {\n const index = new Map<string, McpServerStatus>();\n for (const server of servers) {\n for (const toolName of server.tools) index.set(toolName, server);\n }\n return index;\n}\n\n/**\n * Groups the session's tools by where they came from: Pi itself, one group per\n * MCP server, then one per extension that registered a tool.\n *\n * Every MCP tool is registered by one extension, so `sourceInfo` cannot separate\n * servers; the status snapshot supplies the tool-to-server mapping instead.\n */\nexport function buildToolSources(input: ToolInventoryInput): readonly ToolSource[] {\n const activeTools = new Set(input.activeTools);\n const servers = input.mcpServers ?? [];\n const owningServer = serverByTool(servers);\n const core: ToolEntry[] = [];\n const mcpTools = new Map<string, ToolEntry[]>();\n const extensions = new Map<string, { label: string; detail: string; packageName?: string; tools: ToolEntry[] }>();\n\n for (const tool of input.tools) {\n const entry = toEntry(tool, activeTools);\n const server = owningServer.get(tool.name);\n if (server) {\n const bucket = mcpTools.get(server.name);\n if (bucket) bucket.push(entry);\n else mcpTools.set(server.name, [entry]);\n continue;\n }\n if (tool.sourceInfo.source === BUILTIN_SOURCE) {\n core.push(entry);\n continue;\n }\n // A composed bundle has one Pi source path for every factory inside it. Doom's\n // compiler records the original entry at registration time so those extensions\n // remain separate here even though Pi can only see the outer bundle.\n const recordedPath = input.resolveExtensionToolSource?.(tool.name);\n const key = recordedPath ?? tool.sourceInfo.path;\n const group = extensions.get(key);\n if (group) group.tools.push(entry);\n else {\n const structuralSource =\n recordedPath !== undefined ||\n tool.sourceInfo.source === CLI_SOURCE ||\n tool.sourceInfo.source === LOCAL_SOURCE ||\n !tool.sourceInfo.source;\n const name = structuralSource\n ? (input.resolveExtensionName?.(key) ?? tool.sourceInfo.source ?? key)\n : (tool.sourceInfo.source ?? key);\n const scoped = input.resolveExtensionPackageName?.(key);\n extensions.set(key, {\n label: sourceLabel(name, 'extension'),\n detail: key,\n ...(scoped ? { packageName: scoped } : {}),\n tools: [entry],\n });\n }\n }\n\n const sources: ToolSource[] = [];\n if (core.length > 0) {\n sources.push({ key: CORE_KEY, label: sourceLabel(CORE_NAME, CORE_KEY), kind: CORE_KEY, tools: core.sort(byName) });\n }\n for (const server of servers) {\n const tools = mcpTools.get(server.name) ?? [];\n const status = displayStatus(server);\n // Every configured server is listed, including one with no tools: attribution\n // is exact now, so an empty list is a fact about the server rather than a\n // failure to recognise its tool names.\n sources.push({\n key: `${MCP_KEY_PREFIX}${server.name}`,\n label: sourceLabel(server.name, 'mcp'),\n kind: 'mcp',\n ...(status ? { status } : {}),\n tools: tools.sort(byName),\n });\n }\n for (const [key, group] of extensions) {\n sources.push({\n key,\n label: group.label,\n kind: 'extension',\n detail: group.detail,\n ...(group.packageName ? { packageName: group.packageName } : {}),\n tools: group.tools.sort(byName),\n });\n }\n\n return sources.sort(\n (left, right) => KIND_ORDER[left.kind] - KIND_ORDER[right.kind] || left.label.localeCompare(right.label),\n );\n}\n"],"mappings":"AAyDA,MAIM,EAAW,OAIX,EAA8C,CAAE,KAAM,OAAQ,IAAK,MAAO,UAAW,WAAY,EAEjG,EAA6C,CAAE,KAAM,EAAG,IAAK,EAAG,UAAW,CAAE,EAQnF,SAAS,EAAc,EAA6C,CAClE,OAAO,EAAO,QAAU,YAAmB,IAAA,GAAY,EAAO,MAAM,QAAQ,KAAM,GAAG,CACvF,CAEA,SAAS,EAAQ,EAAgB,EAA6C,CAC5E,MAAO,CACL,KAAM,EAAK,KACX,GAAI,EAAK,YAAc,CAAE,YAAa,EAAK,WAAY,EAAI,CAAC,EAC5D,GAAI,EAAK,aAAe,IAAA,GAAY,CAAC,EAAI,CAAE,WAAY,EAAK,UAAW,EACvE,GAAI,EAAK,cAAgB,CAAE,cAAe,EAAK,aAAc,EAAI,CAAC,EAClE,GAAI,EAAK,kBAAkB,OAAS,CAAE,iBAAkB,EAAK,gBAAiB,EAAI,CAAC,EACnF,OAAQ,EAAY,IAAI,EAAK,IAAI,CACnC,CACF,CAEA,SAAS,EAAO,EAAiB,EAA0B,CACzD,OAAO,EAAK,KAAK,cAAc,EAAM,IAAI,CAC3C,CAEA,SAAS,EAAY,EAAc,EAA8B,CAC/D,MAAO,GAAG,EAAK,KAAK,EAAY,IAClC,CASA,SAAS,EAAa,EAAmE,CACvF,IAAM,EAAQ,IAAI,IAClB,IAAK,IAAM,KAAU,EACnB,IAAK,IAAM,KAAY,EAAO,MAAO,EAAM,IAAI,EAAU,CAAM,EAEjE,OAAO,CACT,CASA,SAAgB,EAAiB,EAAkD,CACjF,IAAM,EAAc,IAAI,IAAI,EAAM,WAAW,EACvC,EAAU,EAAM,YAAc,CAAC,EAC/B,EAAe,EAAa,CAAO,EACnC,EAAoB,CAAC,EACrB,EAAW,IAAI,IACf,EAAa,IAAI,IAEvB,IAAK,IAAM,KAAQ,EAAM,MAAO,CAC9B,IAAM,EAAQ,EAAQ,EAAM,CAAW,EACjC,EAAS,EAAa,IAAI,EAAK,IAAI,EACzC,GAAI,EAAQ,CACV,IAAM,EAAS,EAAS,IAAI,EAAO,IAAI,EACnC,EAAQ,EAAO,KAAK,CAAK,EACxB,EAAS,IAAI,EAAO,KAAM,CAAC,CAAK,CAAC,EACtC,QACF,CACA,GAAI,EAAK,WAAW,SAAW,UAAgB,CAC7C,EAAK,KAAK,CAAK,EACf,QACF,CAIA,IAAM,EAAe,EAAM,6BAA6B,EAAK,IAAI,EAC3D,EAAM,GAAgB,EAAK,WAAW,KACtC,EAAQ,EAAW,IAAI,CAAG,EAChC,GAAI,EAAO,EAAM,MAAM,KAAK,CAAK,MAC5B,CAMH,IAAM,EAJJ,IAAiB,IAAA,IACjB,EAAK,WAAW,SAAW,OAC3B,EAAK,WAAW,SAAW,SAC3B,CAAC,EAAK,WAAW,OAEd,EAAM,uBAAuB,CAAG,GAAK,EAAK,WAAW,QAAU,EAC/D,EAAK,WAAW,QAAU,EACzB,EAAS,EAAM,8BAA8B,CAAG,EACtD,EAAW,IAAI,EAAK,CAClB,MAAO,EAAY,EAAM,WAAW,EACpC,OAAQ,EACR,GAAI,EAAS,CAAE,YAAa,CAAO,EAAI,CAAC,EACxC,MAAO,CAAC,CAAK,CACf,CAAC,CACH,CACF,CAEA,IAAM,EAAwB,CAAC,EAC3B,EAAK,OAAS,GAChB,EAAQ,KAAK,CAAE,IAAK,EAAU,MAAO,EAAY,KAAW,CAAQ,EAAG,KAAM,EAAU,MAAO,EAAK,KAAK,CAAM,CAAE,CAAC,EAEnH,IAAK,IAAM,KAAU,EAAS,CAC5B,IAAM,EAAQ,EAAS,IAAI,EAAO,IAAI,GAAK,CAAC,EACtC,EAAS,EAAc,CAAM,EAInC,EAAQ,KAAK,CACX,IAAK,OAAoB,EAAO,OAChC,MAAO,EAAY,EAAO,KAAM,KAAK,EACrC,KAAM,MACN,GAAI,EAAS,CAAE,QAAO,EAAI,CAAC,EAC3B,MAAO,EAAM,KAAK,CAAM,CAC1B,CAAC,CACH,CACA,IAAK,GAAM,CAAC,EAAK,KAAU,EACzB,EAAQ,KAAK,CACX,MACA,MAAO,EAAM,MACb,KAAM,YACN,OAAQ,EAAM,OACd,GAAI,EAAM,YAAc,CAAE,YAAa,EAAM,WAAY,EAAI,CAAC,EAC9D,MAAO,EAAM,MAAM,KAAK,CAAM,CAChC,CAAC,EAGH,OAAO,EAAQ,MACZ,EAAM,IAAU,EAAW,EAAK,MAAQ,EAAW,EAAM,OAAS,EAAK,MAAM,cAAc,EAAM,KAAK,CACzG,CACF"}
@@ -5,6 +5,8 @@ interface ToolEntry {
5
5
  name: string;
6
6
  description?: string;
7
7
  parameters?: unknown;
8
+ /** One-line snippet Pi keys by tool name in the system prompt. */
9
+ promptSnippet?: string;
8
10
  promptGuidelines?: readonly string[];
9
11
  active: boolean;
10
12
  }
@@ -16,6 +18,14 @@ interface ToolSource {
16
18
  status?: string;
17
19
  /** The registering file for extension sources. */
18
20
  detail?: string;
21
+ /**
22
+ * Package name as its manifest spells it, scope included.
23
+ *
24
+ * `label` is shortened for reading. Attribution joins against `.doom`
25
+ * configuration, which names packages in full, so the unshortened name is
26
+ * carried separately rather than recovered from the label.
27
+ */
28
+ packageName?: string;
19
29
  tools: readonly ToolEntry[];
20
30
  }
21
31
  /** One server row of the MCP status snapshot. */
@@ -24,6 +34,7 @@ interface ToolInfo {
24
34
  name: string;
25
35
  description?: string;
26
36
  parameters?: unknown;
37
+ promptSnippet?: string;
27
38
  promptGuidelines?: readonly string[];
28
39
  sourceInfo: {
29
40
  path: string;
@@ -36,6 +47,8 @@ interface ToolInventoryInput {
36
47
  /** Absent when the MCP adapter is not loaded or has not published yet. */
37
48
  mcpServers?: readonly McpServerStatus[];
38
49
  resolveExtensionName?: (entryPath: string) => string;
50
+ /** Unshortened package name for the same entry path, for attribution. */
51
+ resolveExtensionPackageName?: (entryPath: string) => string | undefined;
39
52
  resolveExtensionToolSource?: (toolName: string) => string | undefined;
40
53
  }
41
54
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"toolInventory.d.cts","names":[],"sources":["../../../src/services/tools/toolInventory.ts"],"mappings":";;KAEY;UAEK;EACf;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA,MAAM;;EAEN;;EAEA;EACA,gBAAgB;;;KAIN,kBAAkB;UAEb;EACf;EACA;EACA;EACA;EACA;IAAc;IAAc;;;UAGb;EACf,gBAAgB;EAChB;;EAEA,sBAAsB;EACtB,wBAAwB;EACxB,8BAA8B;;;;;;;;;iBAkEhB,iBAAiB,OAAO,8BAA8B"}
1
+ {"version":3,"file":"toolInventory.d.cts","names":[],"sources":["../../../src/services/tools/toolInventory.ts"],"mappings":";;KAEY;UAEK;EACf;EACA;EACA;;EAEA;EACA;EACA;;UAGe;EACf;EACA;EACA,MAAM;;EAEN;;EAEA;;;;;;;;EAQA;EACA,gBAAgB;;;KAIN,kBAAkB;UAEb;EACf;EACA;EACA;EACA;EACA;EACA;IAAc;IAAc;;;UAGb;EACf,gBAAgB;EAChB;;EAEA,sBAAsB;EACtB,wBAAwB;;EAExB,+BAA+B;EAC/B,8BAA8B;;;;;;;;;iBAmEhB,iBAAiB,OAAO,8BAA8B"}
@@ -5,6 +5,8 @@ interface ToolEntry {
5
5
  name: string;
6
6
  description?: string;
7
7
  parameters?: unknown;
8
+ /** One-line snippet Pi keys by tool name in the system prompt. */
9
+ promptSnippet?: string;
8
10
  promptGuidelines?: readonly string[];
9
11
  active: boolean;
10
12
  }
@@ -16,6 +18,14 @@ interface ToolSource {
16
18
  status?: string;
17
19
  /** The registering file for extension sources. */
18
20
  detail?: string;
21
+ /**
22
+ * Package name as its manifest spells it, scope included.
23
+ *
24
+ * `label` is shortened for reading. Attribution joins against `.doom`
25
+ * configuration, which names packages in full, so the unshortened name is
26
+ * carried separately rather than recovered from the label.
27
+ */
28
+ packageName?: string;
19
29
  tools: readonly ToolEntry[];
20
30
  }
21
31
  /** One server row of the MCP status snapshot. */
@@ -24,6 +34,7 @@ interface ToolInfo {
24
34
  name: string;
25
35
  description?: string;
26
36
  parameters?: unknown;
37
+ promptSnippet?: string;
27
38
  promptGuidelines?: readonly string[];
28
39
  sourceInfo: {
29
40
  path: string;
@@ -36,6 +47,8 @@ interface ToolInventoryInput {
36
47
  /** Absent when the MCP adapter is not loaded or has not published yet. */
37
48
  mcpServers?: readonly McpServerStatus[];
38
49
  resolveExtensionName?: (entryPath: string) => string;
50
+ /** Unshortened package name for the same entry path, for attribution. */
51
+ resolveExtensionPackageName?: (entryPath: string) => string | undefined;
39
52
  resolveExtensionToolSource?: (toolName: string) => string | undefined;
40
53
  }
41
54
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"toolInventory.d.mts","names":[],"sources":["../../../src/services/tools/toolInventory.ts"],"mappings":";;KAEY;UAEK;EACf;EACA;EACA;EACA;EACA;;UAGe;EACf;EACA;EACA,MAAM;;EAEN;;EAEA;EACA,gBAAgB;;;KAIN,kBAAkB;UAEb;EACf;EACA;EACA;EACA;EACA;IAAc;IAAc;;;UAGb;EACf,gBAAgB;EAChB;;EAEA,sBAAsB;EACtB,wBAAwB;EACxB,8BAA8B;;;;;;;;;iBAkEhB,iBAAiB,OAAO,8BAA8B"}
1
+ {"version":3,"file":"toolInventory.d.mts","names":[],"sources":["../../../src/services/tools/toolInventory.ts"],"mappings":";;KAEY;UAEK;EACf;EACA;EACA;;EAEA;EACA;EACA;;UAGe;EACf;EACA;EACA,MAAM;;EAEN;;EAEA;;;;;;;;EAQA;EACA,gBAAgB;;;KAIN,kBAAkB;UAEb;EACf;EACA;EACA;EACA;EACA;EACA;IAAc;IAAc;;;UAGb;EACf,gBAAgB;EAChB;;EAEA,sBAAsB;EACtB,wBAAwB;;EAExB,+BAA+B;EAC/B,8BAA8B;;;;;;;;;iBAmEhB,iBAAiB,OAAO,8BAA8B"}
@@ -1,2 +1,2 @@
1
- const e=`core`,t={core:`core`,mcp:`mcp`,extension:`extension`},n={core:0,mcp:1,extension:2};function r(e){return e.state===`connected`?void 0:e.state.replace(/-/g,` `)}function i(e,t){return{name:e.name,...e.description?{description:e.description}:{},...e.parameters===void 0?{}:{parameters:e.parameters},...e.promptGuidelines?.length?{promptGuidelines:e.promptGuidelines}:{},active:t.has(e.name)}}function a(e,t){return e.name.localeCompare(t.name)}function o(e,n){return`${e} · ${t[n]}`}function s(e){let t=new Map;for(let n of e)for(let e of n.tools)t.set(e,n);return t}function c(t){let c=new Set(t.activeTools),l=t.mcpServers??[],u=s(l),d=[],f=new Map,p=new Map;for(let e of t.tools){let n=i(e,c),r=u.get(e.name);if(r){let e=f.get(r.name);e?e.push(n):f.set(r.name,[n]);continue}if(e.sourceInfo.source===`builtin`){d.push(n);continue}let a=t.resolveExtensionToolSource?.(e.name),s=a??e.sourceInfo.path,l=p.get(s);if(l)l.tools.push(n);else{let r=a!==void 0||e.sourceInfo.source===`cli`||e.sourceInfo.source===`local`||!e.sourceInfo.source?t.resolveExtensionName?.(s)??e.sourceInfo.source??s:e.sourceInfo.source??s;p.set(s,{label:o(r,`extension`),detail:s,tools:[n]})}}let m=[];d.length>0&&m.push({key:e,label:o(`pi`,e),kind:e,tools:d.sort(a)});for(let e of l){let t=f.get(e.name)??[],n=r(e);m.push({key:`mcp:${e.name}`,label:o(e.name,`mcp`),kind:`mcp`,...n?{status:n}:{},tools:t.sort(a)})}for(let[e,t]of p)m.push({key:e,label:t.label,kind:`extension`,detail:t.detail,tools:t.tools.sort(a)});return m.sort((e,t)=>n[e.kind]-n[t.kind]||e.label.localeCompare(t.label))}export{c as buildToolSources};
1
+ const e=`core`,t={core:`core`,mcp:`mcp`,extension:`extension`},n={core:0,mcp:1,extension:2};function r(e){return e.state===`connected`?void 0:e.state.replace(/-/g,` `)}function i(e,t){return{name:e.name,...e.description?{description:e.description}:{},...e.parameters===void 0?{}:{parameters:e.parameters},...e.promptSnippet?{promptSnippet:e.promptSnippet}:{},...e.promptGuidelines?.length?{promptGuidelines:e.promptGuidelines}:{},active:t.has(e.name)}}function a(e,t){return e.name.localeCompare(t.name)}function o(e,n){return`${e} · ${t[n]}`}function s(e){let t=new Map;for(let n of e)for(let e of n.tools)t.set(e,n);return t}function c(t){let c=new Set(t.activeTools),l=t.mcpServers??[],u=s(l),d=[],f=new Map,p=new Map;for(let e of t.tools){let n=i(e,c),r=u.get(e.name);if(r){let e=f.get(r.name);e?e.push(n):f.set(r.name,[n]);continue}if(e.sourceInfo.source===`builtin`){d.push(n);continue}let a=t.resolveExtensionToolSource?.(e.name),s=a??e.sourceInfo.path,l=p.get(s);if(l)l.tools.push(n);else{let r=a!==void 0||e.sourceInfo.source===`cli`||e.sourceInfo.source===`local`||!e.sourceInfo.source?t.resolveExtensionName?.(s)??e.sourceInfo.source??s:e.sourceInfo.source??s,i=t.resolveExtensionPackageName?.(s);p.set(s,{label:o(r,`extension`),detail:s,...i?{packageName:i}:{},tools:[n]})}}let m=[];d.length>0&&m.push({key:e,label:o(`pi`,e),kind:e,tools:d.sort(a)});for(let e of l){let t=f.get(e.name)??[],n=r(e);m.push({key:`mcp:${e.name}`,label:o(e.name,`mcp`),kind:`mcp`,...n?{status:n}:{},tools:t.sort(a)})}for(let[e,t]of p)m.push({key:e,label:t.label,kind:`extension`,detail:t.detail,...t.packageName?{packageName:t.packageName}:{},tools:t.tools.sort(a)});return m.sort((e,t)=>n[e.kind]-n[t.kind]||e.label.localeCompare(t.label))}export{c as buildToolSources};
2
2
  //# sourceMappingURL=toolInventory.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"toolInventory.mjs","names":[],"sources":["../../../src/services/tools/toolInventory.ts"],"sourcesContent":["import type { McpServerSnapshot } from '@agimon-ai/doompi-extension-contracts/mcp-status';\n\nexport type ToolSourceKind = 'core' | 'mcp' | 'extension';\n\nexport interface ToolEntry {\n name: string;\n description?: string;\n parameters?: unknown;\n promptGuidelines?: readonly string[];\n active: boolean;\n}\n\nexport interface ToolSource {\n key: string;\n label: string;\n kind: ToolSourceKind;\n /** Server state for MCP sources, e.g. `disabled`; absent for core and extensions. */\n status?: string;\n /** The registering file for extension sources. */\n detail?: string;\n tools: readonly ToolEntry[];\n}\n\n/** One server row of the MCP status snapshot. */\nexport type McpServerStatus = McpServerSnapshot;\n\nexport interface ToolInfo {\n name: string;\n description?: string;\n parameters?: unknown;\n promptGuidelines?: readonly string[];\n sourceInfo: { path: string; source?: string };\n}\n\nexport interface ToolInventoryInput {\n tools: readonly ToolInfo[];\n activeTools: readonly string[];\n /** Absent when the MCP adapter is not loaded or has not published yet. */\n mcpServers?: readonly McpServerStatus[];\n resolveExtensionName?: (entryPath: string) => string;\n resolveExtensionToolSource?: (toolName: string) => string | undefined;\n}\n\n/** Pi tags its own tools with this source; everything else came from an extension. */\nconst BUILTIN_SOURCE = 'builtin';\n/** Pi's structural tags for an extension path, neither of which is a package name. */\nconst CLI_SOURCE = 'cli';\nconst LOCAL_SOURCE = 'local';\nconst CORE_KEY = 'core';\nconst CORE_NAME = 'pi';\nconst MCP_KEY_PREFIX = 'mcp:';\n/** Labels read name first, then kind, because the name is what the eye scans for. */\nconst KIND_SUFFIX: Record<ToolSourceKind, string> = { core: 'core', mcp: 'mcp', extension: 'extension' };\nconst CONNECTED_STATUS = 'connected';\nconst KIND_ORDER: Record<ToolSourceKind, number> = { core: 0, mcp: 1, extension: 2 };\n\n/**\n * Server state worth reporting, which takes the place of the tool count.\n *\n * A connected server is the unremarkable case, so it keeps its count rather\n * than spending the column on a word that says nothing went wrong.\n */\nfunction displayStatus(server: McpServerStatus): string | undefined {\n return server.state === CONNECTED_STATUS ? undefined : server.state.replace(/-/g, ' ');\n}\n\nfunction toEntry(tool: ToolInfo, activeTools: ReadonlySet<string>): ToolEntry {\n return {\n name: tool.name,\n ...(tool.description ? { description: tool.description } : {}),\n ...(tool.parameters === undefined ? {} : { parameters: tool.parameters }),\n ...(tool.promptGuidelines?.length ? { promptGuidelines: tool.promptGuidelines } : {}),\n active: activeTools.has(tool.name),\n };\n}\n\nfunction byName(left: ToolEntry, right: ToolEntry): number {\n return left.name.localeCompare(right.name);\n}\n\nfunction sourceLabel(name: string, kind: ToolSourceKind): string {\n return `${name} · ${KIND_SUFFIX[kind]}`;\n}\n\n/**\n * Indexes each server's declared tool names.\n *\n * The snapshot names its tools outright, so attribution is a lookup rather than a\n * guess at the naming scheme. A tool the snapshot does not claim falls through to\n * the extension that registered it.\n */\nfunction serverByTool(servers: readonly McpServerStatus[]): Map<string, McpServerStatus> {\n const index = new Map<string, McpServerStatus>();\n for (const server of servers) {\n for (const toolName of server.tools) index.set(toolName, server);\n }\n return index;\n}\n\n/**\n * Groups the session's tools by where they came from: Pi itself, one group per\n * MCP server, then one per extension that registered a tool.\n *\n * Every MCP tool is registered by one extension, so `sourceInfo` cannot separate\n * servers; the status snapshot supplies the tool-to-server mapping instead.\n */\nexport function buildToolSources(input: ToolInventoryInput): readonly ToolSource[] {\n const activeTools = new Set(input.activeTools);\n const servers = input.mcpServers ?? [];\n const owningServer = serverByTool(servers);\n const core: ToolEntry[] = [];\n const mcpTools = new Map<string, ToolEntry[]>();\n const extensions = new Map<string, { label: string; detail: string; tools: ToolEntry[] }>();\n\n for (const tool of input.tools) {\n const entry = toEntry(tool, activeTools);\n const server = owningServer.get(tool.name);\n if (server) {\n const bucket = mcpTools.get(server.name);\n if (bucket) bucket.push(entry);\n else mcpTools.set(server.name, [entry]);\n continue;\n }\n if (tool.sourceInfo.source === BUILTIN_SOURCE) {\n core.push(entry);\n continue;\n }\n // A composed bundle has one Pi source path for every factory inside it. Doom's\n // compiler records the original entry at registration time so those extensions\n // remain separate here even though Pi can only see the outer bundle.\n const recordedPath = input.resolveExtensionToolSource?.(tool.name);\n const key = recordedPath ?? tool.sourceInfo.path;\n const group = extensions.get(key);\n if (group) group.tools.push(entry);\n else {\n const structuralSource =\n recordedPath !== undefined ||\n tool.sourceInfo.source === CLI_SOURCE ||\n tool.sourceInfo.source === LOCAL_SOURCE ||\n !tool.sourceInfo.source;\n const name = structuralSource\n ? (input.resolveExtensionName?.(key) ?? tool.sourceInfo.source ?? key)\n : (tool.sourceInfo.source ?? key);\n extensions.set(key, { label: sourceLabel(name, 'extension'), detail: key, tools: [entry] });\n }\n }\n\n const sources: ToolSource[] = [];\n if (core.length > 0) {\n sources.push({ key: CORE_KEY, label: sourceLabel(CORE_NAME, CORE_KEY), kind: CORE_KEY, tools: core.sort(byName) });\n }\n for (const server of servers) {\n const tools = mcpTools.get(server.name) ?? [];\n const status = displayStatus(server);\n // Every configured server is listed, including one with no tools: attribution\n // is exact now, so an empty list is a fact about the server rather than a\n // failure to recognise its tool names.\n sources.push({\n key: `${MCP_KEY_PREFIX}${server.name}`,\n label: sourceLabel(server.name, 'mcp'),\n kind: 'mcp',\n ...(status ? { status } : {}),\n tools: tools.sort(byName),\n });\n }\n for (const [key, group] of extensions) {\n sources.push({\n key,\n label: group.label,\n kind: 'extension',\n detail: group.detail,\n tools: group.tools.sort(byName),\n });\n }\n\n return sources.sort(\n (left, right) => KIND_ORDER[left.kind] - KIND_ORDER[right.kind] || left.label.localeCompare(right.label),\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"toolInventory.mjs","names":[],"sources":["../../../src/services/tools/toolInventory.ts"],"sourcesContent":["import type { McpServerSnapshot } from '@agimon-ai/doompi-extension-contracts/mcp-status';\n\nexport type ToolSourceKind = 'core' | 'mcp' | 'extension';\n\nexport interface ToolEntry {\n name: string;\n description?: string;\n parameters?: unknown;\n /** One-line snippet Pi keys by tool name in the system prompt. */\n promptSnippet?: string;\n promptGuidelines?: readonly string[];\n active: boolean;\n}\n\nexport interface ToolSource {\n key: string;\n label: string;\n kind: ToolSourceKind;\n /** Server state for MCP sources, e.g. `disabled`; absent for core and extensions. */\n status?: string;\n /** The registering file for extension sources. */\n detail?: string;\n /**\n * Package name as its manifest spells it, scope included.\n *\n * `label` is shortened for reading. Attribution joins against `.doom`\n * configuration, which names packages in full, so the unshortened name is\n * carried separately rather than recovered from the label.\n */\n packageName?: string;\n tools: readonly ToolEntry[];\n}\n\n/** One server row of the MCP status snapshot. */\nexport type McpServerStatus = McpServerSnapshot;\n\nexport interface ToolInfo {\n name: string;\n description?: string;\n parameters?: unknown;\n promptSnippet?: string;\n promptGuidelines?: readonly string[];\n sourceInfo: { path: string; source?: string };\n}\n\nexport interface ToolInventoryInput {\n tools: readonly ToolInfo[];\n activeTools: readonly string[];\n /** Absent when the MCP adapter is not loaded or has not published yet. */\n mcpServers?: readonly McpServerStatus[];\n resolveExtensionName?: (entryPath: string) => string;\n /** Unshortened package name for the same entry path, for attribution. */\n resolveExtensionPackageName?: (entryPath: string) => string | undefined;\n resolveExtensionToolSource?: (toolName: string) => string | undefined;\n}\n\n/** Pi tags its own tools with this source; everything else came from an extension. */\nconst BUILTIN_SOURCE = 'builtin';\n/** Pi's structural tags for an extension path, neither of which is a package name. */\nconst CLI_SOURCE = 'cli';\nconst LOCAL_SOURCE = 'local';\nconst CORE_KEY = 'core';\nconst CORE_NAME = 'pi';\nconst MCP_KEY_PREFIX = 'mcp:';\n/** Labels read name first, then kind, because the name is what the eye scans for. */\nconst KIND_SUFFIX: Record<ToolSourceKind, string> = { core: 'core', mcp: 'mcp', extension: 'extension' };\nconst CONNECTED_STATUS = 'connected';\nconst KIND_ORDER: Record<ToolSourceKind, number> = { core: 0, mcp: 1, extension: 2 };\n\n/**\n * Server state worth reporting, which takes the place of the tool count.\n *\n * A connected server is the unremarkable case, so it keeps its count rather\n * than spending the column on a word that says nothing went wrong.\n */\nfunction displayStatus(server: McpServerStatus): string | undefined {\n return server.state === CONNECTED_STATUS ? undefined : server.state.replace(/-/g, ' ');\n}\n\nfunction toEntry(tool: ToolInfo, activeTools: ReadonlySet<string>): ToolEntry {\n return {\n name: tool.name,\n ...(tool.description ? { description: tool.description } : {}),\n ...(tool.parameters === undefined ? {} : { parameters: tool.parameters }),\n ...(tool.promptSnippet ? { promptSnippet: tool.promptSnippet } : {}),\n ...(tool.promptGuidelines?.length ? { promptGuidelines: tool.promptGuidelines } : {}),\n active: activeTools.has(tool.name),\n };\n}\n\nfunction byName(left: ToolEntry, right: ToolEntry): number {\n return left.name.localeCompare(right.name);\n}\n\nfunction sourceLabel(name: string, kind: ToolSourceKind): string {\n return `${name} · ${KIND_SUFFIX[kind]}`;\n}\n\n/**\n * Indexes each server's declared tool names.\n *\n * The snapshot names its tools outright, so attribution is a lookup rather than a\n * guess at the naming scheme. A tool the snapshot does not claim falls through to\n * the extension that registered it.\n */\nfunction serverByTool(servers: readonly McpServerStatus[]): Map<string, McpServerStatus> {\n const index = new Map<string, McpServerStatus>();\n for (const server of servers) {\n for (const toolName of server.tools) index.set(toolName, server);\n }\n return index;\n}\n\n/**\n * Groups the session's tools by where they came from: Pi itself, one group per\n * MCP server, then one per extension that registered a tool.\n *\n * Every MCP tool is registered by one extension, so `sourceInfo` cannot separate\n * servers; the status snapshot supplies the tool-to-server mapping instead.\n */\nexport function buildToolSources(input: ToolInventoryInput): readonly ToolSource[] {\n const activeTools = new Set(input.activeTools);\n const servers = input.mcpServers ?? [];\n const owningServer = serverByTool(servers);\n const core: ToolEntry[] = [];\n const mcpTools = new Map<string, ToolEntry[]>();\n const extensions = new Map<string, { label: string; detail: string; packageName?: string; tools: ToolEntry[] }>();\n\n for (const tool of input.tools) {\n const entry = toEntry(tool, activeTools);\n const server = owningServer.get(tool.name);\n if (server) {\n const bucket = mcpTools.get(server.name);\n if (bucket) bucket.push(entry);\n else mcpTools.set(server.name, [entry]);\n continue;\n }\n if (tool.sourceInfo.source === BUILTIN_SOURCE) {\n core.push(entry);\n continue;\n }\n // A composed bundle has one Pi source path for every factory inside it. Doom's\n // compiler records the original entry at registration time so those extensions\n // remain separate here even though Pi can only see the outer bundle.\n const recordedPath = input.resolveExtensionToolSource?.(tool.name);\n const key = recordedPath ?? tool.sourceInfo.path;\n const group = extensions.get(key);\n if (group) group.tools.push(entry);\n else {\n const structuralSource =\n recordedPath !== undefined ||\n tool.sourceInfo.source === CLI_SOURCE ||\n tool.sourceInfo.source === LOCAL_SOURCE ||\n !tool.sourceInfo.source;\n const name = structuralSource\n ? (input.resolveExtensionName?.(key) ?? tool.sourceInfo.source ?? key)\n : (tool.sourceInfo.source ?? key);\n const scoped = input.resolveExtensionPackageName?.(key);\n extensions.set(key, {\n label: sourceLabel(name, 'extension'),\n detail: key,\n ...(scoped ? { packageName: scoped } : {}),\n tools: [entry],\n });\n }\n }\n\n const sources: ToolSource[] = [];\n if (core.length > 0) {\n sources.push({ key: CORE_KEY, label: sourceLabel(CORE_NAME, CORE_KEY), kind: CORE_KEY, tools: core.sort(byName) });\n }\n for (const server of servers) {\n const tools = mcpTools.get(server.name) ?? [];\n const status = displayStatus(server);\n // Every configured server is listed, including one with no tools: attribution\n // is exact now, so an empty list is a fact about the server rather than a\n // failure to recognise its tool names.\n sources.push({\n key: `${MCP_KEY_PREFIX}${server.name}`,\n label: sourceLabel(server.name, 'mcp'),\n kind: 'mcp',\n ...(status ? { status } : {}),\n tools: tools.sort(byName),\n });\n }\n for (const [key, group] of extensions) {\n sources.push({\n key,\n label: group.label,\n kind: 'extension',\n detail: group.detail,\n ...(group.packageName ? { packageName: group.packageName } : {}),\n tools: group.tools.sort(byName),\n });\n }\n\n return sources.sort(\n (left, right) => KIND_ORDER[left.kind] - KIND_ORDER[right.kind] || left.label.localeCompare(right.label),\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/tools/toolInventory.cjs");exports.buildToolSources=e.buildToolSources;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/tools/toolInventory.cjs"),t=require("./services/tools/toolCost.cjs");exports.buildToolSources=e.buildToolSources,exports.tokensForSource=t.tokensForSource,exports.tokensForTool=t.tokensForTool;
@@ -1,2 +1,3 @@
1
1
  import { McpServerStatus, ToolEntry, ToolInventoryInput, ToolSource, ToolSourceKind, buildToolSources } from "./services/tools/toolInventory.cjs";
2
- export { type McpServerStatus, type ToolEntry, type ToolInventoryInput, type ToolSource, type ToolSourceKind, buildToolSources };
2
+ import { CountTokens, ToolCost, tokensForSource, tokensForTool } from "./services/tools/toolCost.cjs";
3
+ export { type CountTokens, type McpServerStatus, type ToolCost, type ToolEntry, type ToolInventoryInput, type ToolSource, type ToolSourceKind, buildToolSources, tokensForSource, tokensForTool };
@@ -1,2 +1,3 @@
1
1
  import { McpServerStatus, ToolEntry, ToolInventoryInput, ToolSource, ToolSourceKind, buildToolSources } from "./services/tools/toolInventory.mjs";
2
- export { type McpServerStatus, type ToolEntry, type ToolInventoryInput, type ToolSource, type ToolSourceKind, buildToolSources };
2
+ import { CountTokens, ToolCost, tokensForSource, tokensForTool } from "./services/tools/toolCost.mjs";
3
+ export { type CountTokens, type McpServerStatus, type ToolCost, type ToolEntry, type ToolInventoryInput, type ToolSource, type ToolSourceKind, buildToolSources, tokensForSource, tokensForTool };
@@ -1 +1 @@
1
- import{buildToolSources as e}from"./services/tools/toolInventory.mjs";export{e as buildToolSources};
1
+ import{buildToolSources as e}from"./services/tools/toolInventory.mjs";import{tokensForSource as t,tokensForTool as n}from"./services/tools/toolCost.mjs";export{e as buildToolSources,t as tokensForSource,n as tokensForTool};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-ui",
3
- "version": "0.0.1-alpha.48",
3
+ "version": "0.0.1-alpha.49",
4
4
  "description": "Leader-key menus, themes, and TUI components for DoomPi and Pi extensions.",
5
5
  "keywords": [
6
6
  "ai",
@@ -170,11 +170,11 @@
170
170
  "dependencies": {
171
171
  "@deepseek-ai/cordis": "4.0.1",
172
172
  "yaml": "2.9.0",
173
- "@agimon-ai/doompi-hashline": "0.0.1-alpha.19",
174
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.47",
175
- "@agimon-ai/doompi-web-components": "0.0.1-alpha.8",
176
- "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.11",
177
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.47"
173
+ "@agimon-ai/doompi-hashline": "0.0.1-alpha.20",
174
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.48",
175
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.9",
176
+ "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.12",
177
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.48"
178
178
  },
179
179
  "devDependencies": {
180
180
  "@earendil-works/pi-coding-agent": "0.84.4",
@@ -3,10 +3,40 @@ import {
3
3
  MessageItemBody,
4
4
  MessageItemHeader,
5
5
  MessageItemStatus,
6
+ SyntaxLine,
7
+ ToolPathLink,
6
8
  toolTone,
9
+ useSyntaxLines,
7
10
  } from '@agimon-ai/doompi-web-components';
8
11
  import type { ToolMessageRenderProps } from '@agimon-ai/doompi-web-contracts';
9
- import { resultText, writeCallView } from './builtinToolView.ts';
12
+ import { resultText, type WriteCallView, writeCallView } from './builtinToolView.ts';
13
+
14
+ /**
15
+ * The numbered preview, coloured as the file it is about to become.
16
+ *
17
+ * Its own component so the highlighting hook has somewhere to live: the body
18
+ * around it is picked inside a render prop. The preview is a prefix of the
19
+ * content, so it parses as the file it came from up to wherever it was cut.
20
+ */
21
+ function WritePreview({ view, path }: { view: WriteCallView; path: string }) {
22
+ const spans = useSyntaxLines(view.preview.map((line) => line.text).join('\n'), { path });
23
+ return (
24
+ <>
25
+ {view.preview.map((line, index) => (
26
+ <span key={line.number} className="flex gap-2">
27
+ <span className="shrink-0 text-right text-doom-faint" style={{ width: `${String(view.gutter)}ch` }}>
28
+ {line.number}
29
+ </span>
30
+ <SyntaxLine
31
+ spans={spans?.[index]}
32
+ text={line.text}
33
+ className="min-w-0 whitespace-pre-wrap break-words text-doom-text"
34
+ />
35
+ </span>
36
+ ))}
37
+ </>
38
+ );
39
+ }
10
40
 
11
41
  /**
12
42
  * The write tool's timeline item: `path · N chars` in the header. Pi's write
@@ -14,8 +44,19 @@ import { resultText, writeCallView } from './builtinToolView.ts';
14
44
  * TUI folds into its call: ten lines, or all of them once the item expands.
15
45
  * A failure shows the tool's message in red instead.
16
46
  */
17
- export function WriteToolMessage({ args, result, output, running, isError }: ToolMessageRenderProps) {
47
+ export function WriteToolMessage({
48
+ args,
49
+ result,
50
+ output,
51
+ running,
52
+ isError,
53
+ fileTabFor,
54
+ openTransientTab,
55
+ }: ToolMessageRenderProps) {
18
56
  const collapsed = writeCallView(args, false);
57
+ // The header names a file the tool is writing, so the reader can open it
58
+ // rather than hunt for it; nothing installed to show files leaves it as text.
59
+ const tab = fileTabFor(collapsed.path);
19
60
  return (
20
61
  <MessageItem tone={toolTone({ running, isError })} expandable={collapsed.hidden > 0}>
21
62
  {({ expanded }) => {
@@ -24,7 +65,10 @@ export function WriteToolMessage({ args, result, output, running, isError }: Too
24
65
  <>
25
66
  <MessageItemHeader title="write">
26
67
  <span data-testid="tool-call-write" className="flex min-w-0 flex-1 items-center gap-2">
27
- <span className="truncate text-doom-text">{view.path}</span>
68
+ <ToolPathLink
69
+ path={view.path}
70
+ {...(tab === undefined ? {} : { onOpen: () => openTransientTab(tab) })}
71
+ />
28
72
  <span className="shrink-0 text-doom-faint">· {view.size}</span>
29
73
  </span>
30
74
  </MessageItemHeader>
@@ -41,14 +85,7 @@ export function WriteToolMessage({ args, result, output, running, isError }: Too
41
85
  ) : null
42
86
  ) : (
43
87
  <MessageItemBody data-testid="tool-result-write" className="flex flex-col font-mono">
44
- {view.preview.map((line) => (
45
- <span key={line.number} className="flex gap-2">
46
- <span className="shrink-0 text-right text-doom-faint" style={{ width: `${String(view.gutter)}ch` }}>
47
- {line.number}
48
- </span>
49
- <span className="min-w-0 whitespace-pre-wrap break-words text-doom-text">{line.text}</span>
50
- </span>
51
- ))}
88
+ <WritePreview view={view} path={view.path} />
52
89
  {view.hidden > 0 ? <MessageItemStatus expands>{view.hidden} more lines</MessageItemStatus> : null}
53
90
  {running ? <MessageItemStatus tone="running">running</MessageItemStatus> : null}
54
91
  </MessageItemBody>