@agimon-ai/doompi-ui 0.0.1-alpha.0 → 0.0.1-alpha.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./leaderRegistry.cjs"),n=require("./components/doomEditor.cjs"),r=require("./components/doomFooter.cjs"),i=require("./components/doomHeader.cjs"),a=require("./components/leaderHints.cjs"),o=require("./config.cjs"),s=require("./footer.cjs"),c=require("./logSinkTelemetry.cjs");require("./theme.cjs");const l=require("./uiState.cjs"),u=require("./components/configOverlay.cjs"),d=require("./components/toolsOverlay.cjs"),f=require("./mode.cjs"),p=require("./toolInventory.cjs");let m=require("node:path");m=e.__toESM(m,1);let h=require("@agimon-ai/doompi-extension-contracts/leader"),g=require("@agimon-ai/doompi-extension-contracts/mcp-status"),_=require("@agimon-ai/doompi-extension-contracts/protocol");const v=`doom-pi-leader`,y=`belowEditor`,b=`warning`,x=`doom-pi`,S=new Set([`hotkeys`]);function C(e,t){let n=e.trim().replace(/\s+/g,` `);if(!n)return;let r=n.slice(0,64).trimEnd();return`${x} · ${r} · ${m.default.basename(t)||t}`}function w(e,w=c.createUiTelemetry()){let T=new l.DoomUiState,E=new s.footer_exports.DoomFooterStatusRegistry,D=new f.mode_exports.DoomModeStatusRegistry,O=new o.config_exports.DoomConfigRegistry,k=new t.DoomLeaderRegistry,A=new Set,j=new Map,M,N,P,F,I,L,R,z,B=new Map,V=0,H=0,U=!1,W,G=async()=>{if(W)try{return(await W.request(g.McpStatusQueryProtocol,{},{timeoutMs:250})).servers}catch{return}};e.registerCommand(`tools`,{description:`Browse the tools available in this session`,handler:async(t,n)=>{let r=await G(),i=p.buildToolSources({tools:e.getAllTools(),activeTools:e.getActiveTools(),...r?{mcpServers:r}:{}});await d.openToolsOverlay(n,i)}}),e.registerCommand(`config`,{description:`Browse and change the settings extensions contribute`,handler:async(e,t)=>{await u.openConfigOverlay(t,O)}});let K=e=>{for(let t of e){let e=t.bindingId?` (${t.bindingId})`:``,n=`Leader contribution from ${t.source}${e}: ${t.message}`;A.has(n)||(A.add(n),w.recordWarning(c.UI_EVENT.leaderContributionRejected,t.message,{"leader.source":t.source,...t.bindingId?{"leader.binding.id":t.bindingId}:{}}),N?.mode===`tui`&&N.ui.notify(n,b))}},q=Object.freeze({register(e){let t=Symbol(e.source),n=k.register(e);if(K(n.diagnostics),!n.accepted)return()=>void 0;j.set(e.source,t);let r=!1;return()=>{r||(r=!0,j.get(e.source)===t&&(j.delete(e.source),k.register({source:e.source,bindings:[]})))}}});e.on(`session_start`,(t,o)=>{N=o;let s=++H,l=(0,_.createProtocolRuntime)({bus:e.events,source:`@agimon-ai/doompi-ui`,sessionId:o.sessionManager.getSessionId()});if(W=l,P?.(),E.reset(),P=E.bind(l),F?.(),D.reset(),F=D.bind(l),L?.(),O.reset(),L=O.bind(l),R?.(),z?.(),R=l.provide(h.LeaderRegistrationProtocol,({contribution:e,generation:t},n)=>{if(n.source!==e.source)throw Error(`Leader contribution source does not match the registration envelope source.`);let r=`${n.source}:${t}:${++V}`,i={source:e.source,bindings:e.bindings.map(e=>{let[t,...n]=e.path;if(!t)throw Error(`Leader binding '${e.id}' has no path.`);return{...e,path:[t,...n]}})};return B.set(r,q.register(i)),{handleId:r}}),z=l.provide(h.LeaderUnregistrationProtocol,({handleId:e})=>{let t=B.get(e);return t?(B.delete(e),t(),{removed:!0}):{removed:!1}}),U=o.sessionManager.getEntries().some(e=>e.type===`message`),o.mode!==`tui`)return;for(let e of A)o.ui.notify(e,b);let u=process.env.DOOMPI_THEME||`doom-pi-dark`,d=o.ui.setTheme(u);d.success||(o.ui.notify(`Could not apply Doom Pi theme: ${d.error}`,b),w.recordWarning(c.UI_EVENT.themeApplyFailed,d.error??`Unknown theme error`,{"ui.theme":u})),I?.(),I=D.subscribe(()=>T.setModes(D.getStatuses())),T.setModes(D.getStatuses()),o.ui.setTitle(`${x} · ${m.default.basename(o.cwd)||o.cwd} · pi/coding`),o.ui.setHeader((e,t)=>new i.DoomHeader(t,o.cwd,void 0,o,()=>e.terminal.rows)),o.ui.setFooter((e,t,n)=>(M=n,new r.DoomFooter(e,t,o,n,E,T))),o.ui.setEditorComponent((t,r,i)=>new n.DoomEditor(t,r,i,T,e=>{if(!e.active){o.ui.setWidget(v,void 0,{placement:y});return}o.ui.setWidget(v,(t,n)=>new a.LeaderHints(n,e,o,M,T.getModes()),{placement:y})},void 0,o.ui.theme,{registry:k,isCommandAvailable:t=>S.has(t)||e.getCommands().some(e=>e.name===t),onLeaderAction:(e,t)=>{if(N!==o||H!==s){o.ui.notify(`Leader action ${t} is unavailable.`,b);return}l.notify(h.LeaderActionInvoked,{source:e,action:t})},onUnavailableCommand:e=>{w.recordWarning(c.UI_EVENT.leaderCommandUnavailable,`Leader command /${e} is unavailable.`,{"ui.kind":`command`}),o.ui.notify(`Leader command /${e} is unavailable.`,b)},onUnavailableAction:e=>{w.recordWarning(c.UI_EVENT.leaderActionUnavailable,`Leader action ${e} is unavailable.`,{"ui.kind":`action`}),o.ui.notify(`Leader action ${e} is unavailable.`,b)}})),w.recordEvent(c.UI_EVENT.shellInstalled,{"ui.theme":u,"ui.theme.applied":d.success,"ui.leader.group.count":k.getGroup([])?.options.length??0})}),e.on(`input`,(e,t)=>{if(t.mode!==`tui`||e.source!==`interactive`||U)return;U=!0;let n=C(e.text,t.cwd);n&&t.ui.setTitle(n)}),e.on(`session_shutdown`,async(e,t)=>{++H;for(let e of B.values())e();B.clear(),N=void 0,M=void 0,P?.(),P=void 0,E.reset(),F?.(),F=void 0,I?.(),I=void 0,D.reset(),L?.(),L=void 0,R?.(),R=void 0,z?.(),z=void 0,O.reset(),T.reset(),W=void 0,t.mode===`tui`&&t.ui.setWidget(v,void 0,{placement:y}),await w.shutdown()})}exports.doomPiUiExtension=w;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./_virtual/_rolldown/runtime.cjs"),t=require("./leaderRegistry.cjs"),n=require("./components/doomEditor.cjs"),r=require("./components/doomFooter.cjs"),i=require("./components/doomHeader.cjs"),a=require("./components/leaderHints.cjs"),o=require("./config.cjs"),s=require("./footer.cjs"),c=require("./logSinkTelemetry.cjs");require("./theme.cjs");const l=require("./uiState.cjs"),u=require("./components/configOverlay.cjs"),d=require("./components/toolsOverlay.cjs"),f=require("./mode.cjs"),p=require("./toolInventory.cjs");let m=require("node:path");m=e.__toESM(m,1);let h=require("@agimon-ai/doompi-extension-contracts/leader"),g=require("@agimon-ai/doompi-extension-contracts/mcp-status"),_=require("@agimon-ai/doompi-extension-contracts/protocol");const v=`doom-pi-leader`,y=`belowEditor`,b=`warning`,x=`doom-pi`,S=new Set([`hotkeys`]);function C(e,t){let n=e.trim().replace(/\s+/g,` `);if(!n)return;let r=n.slice(0,64).trimEnd();return`${x} · ${r} · ${m.default.basename(t)||t}`}function w(e,w=c.createUiTelemetry()){let T=new l.DoomUiState,E=new s.footer_exports.DoomFooterStatusRegistry,D=new f.mode_exports.DoomModeStatusRegistry,O=new o.config_exports.DoomConfigRegistry,k=new t.DoomLeaderRegistry,A=new Set,j=new Map,M,N,P,F,I,L,R,z,B=new Map,V=0,H=0,U=!1,W,G,K=async()=>{if(W)try{return(await W.request(g.McpStatusQueryProtocol,{},{timeoutMs:250})).servers}catch{return}};e.registerCommand(`tools`,{description:`Browse the tools available in this session`,handler:async(t,n)=>{let r=await K(),i=p.buildToolSources({tools:e.getAllTools(),activeTools:e.getActiveTools(),...r?{mcpServers:r}:{}});await d.openToolsOverlay(n,i)}}),e.registerCommand(`config`,{description:`Browse and change the settings extensions contribute`,handler:async(e,t)=>{await u.openConfigOverlay(t,O)}});let q=e=>{for(let t of e){let e=t.bindingId?` (${t.bindingId})`:``,n=`Leader contribution from ${t.source}${e}: ${t.message}`;A.has(n)||(A.add(n),w.recordWarning(c.UI_EVENT.leaderContributionRejected,t.message,{"leader.source":t.source,...t.bindingId?{"leader.binding.id":t.bindingId}:{}}),N?.mode===`tui`&&N.ui.notify(n,b))}},J=(e,t)=>{let n=Symbol(e.source),r=t?e=>k.registerDeferred(e):e=>k.register(e),i=r(e);if(q(i.diagnostics),!i.accepted)return{update:()=>void 0,dispose:()=>void 0};j.set(e.source,n);let a=!1;return{update(t){a||j.get(e.source)!==n||q(r({source:e.source,bindings:t}).diagnostics)},dispose(){a||(a=!0,j.get(e.source)===n&&(j.delete(e.source),r({source:e.source,bindings:[]})))}}},Y=Object.freeze({register(e){let t=J(e,!1);return()=>t.dispose()}});G=(0,h.installDoomLeaderBootstrapHost)({register:e=>J(e,!0)}),e.on(`session_start`,(t,o)=>{N=o;let s=++H,l=(0,_.createProtocolRuntime)({bus:e.events,source:`@agimon-ai/doompi-ui`,sessionId:o.sessionManager.getSessionId()});if(W=l,q(k.flush()),P?.(),E.reset(),P=E.bind(l),F?.(),D.reset(),F=D.bind(l),L?.(),O.reset(),L=O.bind(l),R?.(),z?.(),R=l.provide(h.LeaderRegistrationProtocol,({contribution:e,generation:t},n)=>{if(n.source!==e.source)throw Error(`Leader contribution source does not match the registration envelope source.`);let r=`${n.source}:${t}:${++V}`,i={source:e.source,bindings:e.bindings.map(e=>{let[t,...n]=e.path;if(!t)throw Error(`Leader binding '${e.id}' has no path.`);return{...e,path:[t,...n]}})};return B.set(r,Y.register(i)),{handleId:r}}),z=l.provide(h.LeaderUnregistrationProtocol,({handleId:e})=>{let t=B.get(e);return t?(B.delete(e),t(),{removed:!0}):{removed:!1}}),U=o.sessionManager.getEntries().some(e=>e.type===`message`),o.mode!==`tui`)return;for(let e of A)o.ui.notify(e,b);let u=process.env.DOOMPI_THEME||`doom-pi-dark`,d=o.ui.setTheme(u);d.success||(o.ui.notify(`Could not apply Doom Pi theme: ${d.error}`,b),w.recordWarning(c.UI_EVENT.themeApplyFailed,d.error??`Unknown theme error`,{"ui.theme":u})),I?.(),I=D.subscribe(()=>T.setModes(D.getStatuses())),T.setModes(D.getStatuses()),o.ui.setTitle(`${x} · ${m.default.basename(o.cwd)||o.cwd} · pi/coding`),o.ui.setHeader((e,t)=>new i.DoomHeader(t,o.cwd,void 0,o,()=>e.terminal.rows)),o.ui.setFooter((e,t,n)=>(M=n,new r.DoomFooter(e,t,o,n,E,T))),o.ui.setEditorComponent((t,r,i)=>new n.DoomEditor(t,r,i,T,e=>{if(!e.active){o.ui.setWidget(v,void 0,{placement:y});return}o.ui.setWidget(v,(t,n)=>new a.LeaderHints(n,e,o,M,T.getModes()),{placement:y})},void 0,o.ui.theme,{registry:k,isCommandAvailable:t=>S.has(t)||e.getCommands().some(e=>e.name===t),onLeaderAction:(e,t)=>{if(N!==o||H!==s){o.ui.notify(`Leader action ${t} is unavailable.`,b);return}l.notify(h.LeaderActionInvoked,{source:e,action:t})},onUnavailableCommand:e=>{w.recordWarning(c.UI_EVENT.leaderCommandUnavailable,`Leader command /${e} is unavailable.`,{"ui.kind":`command`}),o.ui.notify(`Leader command /${e} is unavailable.`,b)},onUnavailableAction:e=>{w.recordWarning(c.UI_EVENT.leaderActionUnavailable,`Leader action ${e} is unavailable.`,{"ui.kind":`action`}),o.ui.notify(`Leader action ${e} is unavailable.`,b)}})),w.recordEvent(c.UI_EVENT.shellInstalled,{"ui.theme":u,"ui.theme.applied":d.success,"ui.leader.group.count":k.getGroup([])?.options.length??0})}),e.on(`input`,(e,t)=>{if(t.mode!==`tui`||e.source!==`interactive`||U)return;U=!0;let n=C(e.text,t.cwd);n&&t.ui.setTitle(n)}),e.on(`session_shutdown`,async(e,t)=>{++H,G?.(),G=void 0;for(let e of B.values())e();B.clear(),N=void 0,M=void 0,P?.(),P=void 0,E.reset(),F?.(),F=void 0,I?.(),I=void 0,D.reset(),L?.(),L=void 0,R?.(),R=void 0,z?.(),z=void 0,O.reset(),T.reset(),W=void 0,t.mode===`tui`&&t.ui.setWidget(v,void 0,{placement:y}),await w.shutdown()})}exports.doomPiUiExtension=w;
2
2
  //# sourceMappingURL=extension.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.cjs","names":["path","createUiTelemetry","DoomUiState","DoomLeaderRegistry","McpStatusQueryProtocol","buildToolSources","openToolsOverlay","openConfigOverlay","UI_EVENT","createProtocolRuntime","LeaderRegistrationProtocol","LeaderUnregistrationProtocol","DoomHeader","DoomFooter","DoomEditor","LeaderHints","LeaderActionInvoked"],"sources":["../src/extension.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ExtensionAPI, ExtensionContext, ReadonlyFooterDataProvider } from '@earendil-works/pi-coding-agent';\nimport {\n LeaderActionInvoked,\n type LeaderContribution,\n LeaderRegistrationProtocol,\n LeaderUnregistrationProtocol,\n} from '@agimon-ai/doompi-extension-contracts/leader';\nimport { McpStatusQueryProtocol } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport { createProtocolRuntime, type ProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport { DoomEditor } from './components/doomEditor.ts';\nimport { DoomFooter } from './components/doomFooter.ts';\nimport { DoomHeader } from './components/doomHeader.ts';\nimport { LeaderHints } from './components/leaderHints.ts';\nimport { DoomConfigRegistry } from './config.ts';\nimport { DoomFooterStatusRegistry } from './footer.ts';\nimport { DoomModeStatusRegistry } from './mode.ts';\nimport { DoomLeaderRegistry, type DoomLeaderDiagnostic } from './leaderRegistry.ts';\nimport { openConfigOverlay } from './components/configOverlay.ts';\nimport { openToolsOverlay } from './components/toolsOverlay.ts';\nimport { buildToolSources, type McpServerStatus } from './toolInventory.ts';\nimport { createUiTelemetry, UI_EVENT, type UiTelemetry } from './logSinkTelemetry.ts';\nimport { DEFAULT_THEME_NAME } from './theme.ts';\nimport { DoomUiState, type LeaderSnapshot } from './uiState.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';\n/** Short: `/tools` must open promptly whether or not an MCP extension answers. */\nconst MCP_STATUS_TIMEOUT_MS = 250;\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]);\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\nexport function doomPiUiExtension(pi: ExtensionAPI, telemetry: UiTelemetry = createUiTelemetry()): void {\n const uiState = new DoomUiState();\n const footerStatusRegistry = new DoomFooterStatusRegistry();\n const modeStatusRegistry = new DoomModeStatusRegistry();\n const configRegistry = new DoomConfigRegistry();\n const leaderRegistry = new DoomLeaderRegistry();\n const pendingDiagnostics = new Set<string>();\n const contributionGenerations = new Map<string, symbol>();\n let footerData: ReadonlyFooterDataProvider | undefined;\n let activeContext: ExtensionContext | undefined;\n let disposeFooterStatuses: (() => void) | undefined;\n let disposeModeStatuses: (() => void) | undefined;\n let disposeModeSubscription: (() => void) | undefined;\n let disposeConfigContributions: (() => void) | undefined;\n let disposeLeaderRegistration: (() => void) | undefined;\n let disposeLeaderUnregistration: (() => void) | undefined;\n const protocolContributions = new Map<string, () => void>();\n let nextProtocolHandle = 0;\n let sessionGeneration = 0;\n let hasSessionMessage = false;\n let sessionProtocol: ProtocolRuntime | undefined;\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 = async (): Promise<readonly McpServerStatus[] | undefined> => {\n if (!sessionProtocol) return undefined;\n try {\n const snapshot = await sessionProtocol.request(McpStatusQueryProtocol, {}, { timeoutMs: MCP_STATUS_TIMEOUT_MS });\n return snapshot.servers;\n } catch {\n return undefined;\n }\n };\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 = await readMcpServers();\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\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, configRegistry);\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) activeContext.ui.notify(message, WARNING_STYLE);\n }\n };\n\n const leaderContext = Object.freeze({\n register(contribution: LeaderContribution) {\n const generation = Symbol(contribution.source);\n const result = leaderRegistry.register(contribution);\n reportDiagnostics(result.diagnostics);\n if (!result.accepted) return () => undefined;\n contributionGenerations.set(contribution.source, generation);\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n if (contributionGenerations.get(contribution.source) !== generation) return;\n contributionGenerations.delete(contribution.source);\n leaderRegistry.register({ source: contribution.source, bindings: [] });\n };\n },\n });\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n activeContext = ctx;\n const activeGeneration = ++sessionGeneration;\n const protocol = createProtocolRuntime({\n bus: pi.events,\n source: '@agimon-ai/doompi-ui',\n sessionId: ctx.sessionManager.getSessionId(),\n });\n sessionProtocol = protocol;\n disposeFooterStatuses?.();\n footerStatusRegistry.reset();\n disposeFooterStatuses = footerStatusRegistry.bind(protocol);\n disposeModeStatuses?.();\n modeStatusRegistry.reset();\n disposeModeStatuses = modeStatusRegistry.bind(protocol);\n // Bound above the TUI-mode guard below, like footer and mode: a headless run\n // still accepts contributions, it just never draws them.\n disposeConfigContributions?.();\n configRegistry.reset();\n disposeConfigContributions = configRegistry.bind(protocol);\n disposeLeaderRegistration?.();\n disposeLeaderUnregistration?.();\n disposeLeaderRegistration = protocol.provide(\n LeaderRegistrationProtocol,\n ({ contribution, generation }, identity) => {\n if (identity.source !== contribution.source) {\n throw new Error('Leader contribution source does not match the registration envelope source.');\n }\n const handleId = `${identity.source}:${generation}:${++nextProtocolHandle}`;\n const typedContribution: LeaderContribution = {\n source: contribution.source,\n bindings: contribution.bindings.map((binding) => {\n const [first, ...rest] = binding.path;\n if (!first) throw new Error(`Leader binding '${binding.id}' has no path.`);\n return { ...binding, path: [first, ...rest] };\n }),\n };\n protocolContributions.set(handleId, leaderContext.register(typedContribution));\n return { handleId };\n },\n );\n disposeLeaderUnregistration = protocol.provide(LeaderUnregistrationProtocol, ({ handleId }) => {\n const dispose = protocolContributions.get(handleId);\n if (!dispose) return { removed: false };\n protocolContributions.delete(handleId);\n dispose();\n return { removed: true };\n });\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) ctx.ui.notify(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 ctx.ui.notify(`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 // The modes go into shared state rather than a widget of their own: the\n // editor prints their names beside its badge and the leader panel prints\n // them with their detail, so there is no third surface to paint.\n disposeModeSubscription?.();\n disposeModeSubscription = modeStatusRegistry.subscribe(() => uiState.setModes(modeStatusRegistry.getStatuses()));\n uiState.setModes(modeStatusRegistry.getStatuses());\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, footerStatusRegistry, 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 ctx.ui.notify(`Leader action ${action} is unavailable.`, WARNING_STYLE);\n return;\n }\n protocol.notify(LeaderActionInvoked, { 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 ctx.ui.notify(`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 ctx.ui.notify(`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 pi.on(SESSION_SHUTDOWN_EVENT, async (_event, ctx) => {\n ++sessionGeneration;\n for (const dispose of protocolContributions.values()) dispose();\n protocolContributions.clear();\n activeContext = undefined;\n footerData = undefined;\n disposeFooterStatuses?.();\n disposeFooterStatuses = undefined;\n footerStatusRegistry.reset();\n disposeModeStatuses?.();\n disposeModeStatuses = undefined;\n disposeModeSubscription?.();\n disposeModeSubscription = undefined;\n modeStatusRegistry.reset();\n disposeConfigContributions?.();\n disposeConfigContributions = undefined;\n disposeLeaderRegistration?.();\n disposeLeaderRegistration = undefined;\n disposeLeaderUnregistration?.();\n disposeLeaderUnregistration = undefined;\n configRegistry.reset();\n uiState.reset();\n sessionProtocol = undefined;\n if (ctx.mode === TUI_MODE) ctx.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n await telemetry.shutdown();\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.cjs","names":["path","createUiTelemetry","DoomUiState","DoomLeaderRegistry","McpStatusQueryProtocol","buildToolSources","openToolsOverlay","openConfigOverlay","UI_EVENT","installDoomLeaderBootstrapHost","createProtocolRuntime","LeaderRegistrationProtocol","LeaderUnregistrationProtocol","DoomHeader","DoomFooter","DoomEditor","LeaderHints","LeaderActionInvoked"],"sources":["../src/extension.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ExtensionAPI, ExtensionContext, ReadonlyFooterDataProvider } from '@earendil-works/pi-coding-agent';\nimport {\n LeaderActionInvoked,\n installDoomLeaderBootstrapHost,\n type LeaderContribution,\n LeaderRegistrationProtocol,\n LeaderUnregistrationProtocol,\n} from '@agimon-ai/doompi-extension-contracts/leader';\nimport { McpStatusQueryProtocol } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport { createProtocolRuntime, type ProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport { DoomEditor } from './components/doomEditor.ts';\nimport { DoomFooter } from './components/doomFooter.ts';\nimport { DoomHeader } from './components/doomHeader.ts';\nimport { LeaderHints } from './components/leaderHints.ts';\nimport { DoomConfigRegistry } from './config.ts';\nimport { DoomFooterStatusRegistry } from './footer.ts';\nimport { DoomModeStatusRegistry } from './mode.ts';\nimport { DoomLeaderRegistry, type DoomLeaderDiagnostic } from './leaderRegistry.ts';\nimport { openConfigOverlay } from './components/configOverlay.ts';\nimport { openToolsOverlay } from './components/toolsOverlay.ts';\nimport { buildToolSources, type McpServerStatus } from './toolInventory.ts';\nimport { createUiTelemetry, UI_EVENT, type UiTelemetry } from './logSinkTelemetry.ts';\nimport { DEFAULT_THEME_NAME } from './theme.ts';\nimport { DoomUiState, type LeaderSnapshot } from './uiState.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';\n/** Short: `/tools` must open promptly whether or not an MCP extension answers. */\nconst MCP_STATUS_TIMEOUT_MS = 250;\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]);\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\nexport function doomPiUiExtension(pi: ExtensionAPI, telemetry: UiTelemetry = createUiTelemetry()): void {\n const uiState = new DoomUiState();\n const footerStatusRegistry = new DoomFooterStatusRegistry();\n const modeStatusRegistry = new DoomModeStatusRegistry();\n const configRegistry = new DoomConfigRegistry();\n const leaderRegistry = new DoomLeaderRegistry();\n const pendingDiagnostics = new Set<string>();\n const contributionGenerations = new Map<string, symbol>();\n let footerData: ReadonlyFooterDataProvider | undefined;\n let activeContext: ExtensionContext | undefined;\n let disposeFooterStatuses: (() => void) | undefined;\n let disposeModeStatuses: (() => void) | undefined;\n let disposeModeSubscription: (() => void) | undefined;\n let disposeConfigContributions: (() => void) | undefined;\n let disposeLeaderRegistration: (() => void) | undefined;\n let disposeLeaderUnregistration: (() => void) | undefined;\n const protocolContributions = new Map<string, () => void>();\n let nextProtocolHandle = 0;\n let sessionGeneration = 0;\n let hasSessionMessage = false;\n let sessionProtocol: ProtocolRuntime | undefined;\n let disposeLeaderBootstrap: (() => void) | undefined;\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 = async (): Promise<readonly McpServerStatus[] | undefined> => {\n if (!sessionProtocol) return undefined;\n try {\n const snapshot = await sessionProtocol.request(McpStatusQueryProtocol, {}, { timeoutMs: MCP_STATUS_TIMEOUT_MS });\n return snapshot.servers;\n } catch {\n return undefined;\n }\n };\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 = await readMcpServers();\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\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, configRegistry);\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) activeContext.ui.notify(message, WARNING_STYLE);\n }\n };\n\n const registerLeaderContribution = (contribution: LeaderContribution, deferred: boolean) => {\n const generation = Symbol(contribution.source);\n const register = deferred\n ? (value: LeaderContribution) => leaderRegistry.registerDeferred(value)\n : (value: LeaderContribution) => leaderRegistry.register(value);\n const result = register(contribution);\n reportDiagnostics(result.diagnostics);\n if (!result.accepted) return { update: () => undefined, dispose: () => undefined };\n contributionGenerations.set(contribution.source, generation);\n let disposed = false;\n return {\n update(bindings: LeaderContribution['bindings']) {\n if (disposed || contributionGenerations.get(contribution.source) !== generation) return;\n reportDiagnostics(register({ source: contribution.source, bindings }).diagnostics);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n if (contributionGenerations.get(contribution.source) !== generation) return;\n contributionGenerations.delete(contribution.source);\n register({ source: contribution.source, bindings: [] });\n },\n };\n };\n\n const leaderContext = Object.freeze({\n register(contribution: LeaderContribution) {\n const handle = registerLeaderContribution(contribution, false);\n return () => handle.dispose();\n },\n });\n // Installed during the UI factory, before the remaining Doom factories run.\n // Their registrations are validated immediately and rebuilt as one batch at\n // session_start, so the first editor always receives the complete keyspace.\n disposeLeaderBootstrap = installDoomLeaderBootstrapHost({\n register: (contribution) => registerLeaderContribution(contribution, true),\n });\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n activeContext = ctx;\n const activeGeneration = ++sessionGeneration;\n const protocol = createProtocolRuntime({\n bus: pi.events,\n source: '@agimon-ai/doompi-ui',\n sessionId: ctx.sessionManager.getSessionId(),\n });\n sessionProtocol = protocol;\n reportDiagnostics(leaderRegistry.flush());\n disposeFooterStatuses?.();\n footerStatusRegistry.reset();\n disposeFooterStatuses = footerStatusRegistry.bind(protocol);\n disposeModeStatuses?.();\n modeStatusRegistry.reset();\n disposeModeStatuses = modeStatusRegistry.bind(protocol);\n // Bound above the TUI-mode guard below, like footer and mode: a headless run\n // still accepts contributions, it just never draws them.\n disposeConfigContributions?.();\n configRegistry.reset();\n disposeConfigContributions = configRegistry.bind(protocol);\n disposeLeaderRegistration?.();\n disposeLeaderUnregistration?.();\n disposeLeaderRegistration = protocol.provide(\n LeaderRegistrationProtocol,\n ({ contribution, generation }, identity) => {\n if (identity.source !== contribution.source) {\n throw new Error('Leader contribution source does not match the registration envelope source.');\n }\n const handleId = `${identity.source}:${generation}:${++nextProtocolHandle}`;\n const typedContribution: LeaderContribution = {\n source: contribution.source,\n bindings: contribution.bindings.map((binding) => {\n const [first, ...rest] = binding.path;\n if (!first) throw new Error(`Leader binding '${binding.id}' has no path.`);\n return { ...binding, path: [first, ...rest] };\n }),\n };\n protocolContributions.set(handleId, leaderContext.register(typedContribution));\n return { handleId };\n },\n );\n disposeLeaderUnregistration = protocol.provide(LeaderUnregistrationProtocol, ({ handleId }) => {\n const dispose = protocolContributions.get(handleId);\n if (!dispose) return { removed: false };\n protocolContributions.delete(handleId);\n dispose();\n return { removed: true };\n });\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) ctx.ui.notify(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 ctx.ui.notify(`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 // The modes go into shared state rather than a widget of their own: the\n // editor prints their names beside its badge and the leader panel prints\n // them with their detail, so there is no third surface to paint.\n disposeModeSubscription?.();\n disposeModeSubscription = modeStatusRegistry.subscribe(() => uiState.setModes(modeStatusRegistry.getStatuses()));\n uiState.setModes(modeStatusRegistry.getStatuses());\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, footerStatusRegistry, 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 ctx.ui.notify(`Leader action ${action} is unavailable.`, WARNING_STYLE);\n return;\n }\n protocol.notify(LeaderActionInvoked, { 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 ctx.ui.notify(`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 ctx.ui.notify(`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 pi.on(SESSION_SHUTDOWN_EVENT, async (_event, ctx) => {\n ++sessionGeneration;\n disposeLeaderBootstrap?.();\n disposeLeaderBootstrap = undefined;\n for (const dispose of protocolContributions.values()) dispose();\n protocolContributions.clear();\n activeContext = undefined;\n footerData = undefined;\n disposeFooterStatuses?.();\n disposeFooterStatuses = undefined;\n footerStatusRegistry.reset();\n disposeModeStatuses?.();\n disposeModeStatuses = undefined;\n disposeModeSubscription?.();\n disposeModeSubscription = undefined;\n modeStatusRegistry.reset();\n disposeConfigContributions?.();\n disposeConfigContributions = undefined;\n disposeLeaderRegistration?.();\n disposeLeaderRegistration = undefined;\n disposeLeaderUnregistration?.();\n disposeLeaderUnregistration = undefined;\n configRegistry.reset();\n uiState.reset();\n sessionProtocol = undefined;\n if (ctx.mode === TUI_MODE) ctx.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n await telemetry.shutdown();\n });\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../src/extension.ts"],"mappings":";;;iBAmDgB,kBAAkB,IAAI,cAAc,YAAW"}
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../src/extension.ts"],"mappings":";;;iBAoDgB,kBAAkB,IAAI,cAAc,YAAW"}
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../src/extension.ts"],"mappings":";;;iBAmDgB,kBAAkB,IAAI,cAAc,YAAW"}
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../src/extension.ts"],"mappings":";;;iBAoDgB,kBAAkB,IAAI,cAAc,YAAW"}
@@ -1,2 +1,2 @@
1
- import{DoomLeaderRegistry as e}from"./leaderRegistry.mjs";import{DoomEditor as t}from"./components/doomEditor.mjs";import{DoomFooter as n}from"./components/doomFooter.mjs";import{DoomHeader as r}from"./components/doomHeader.mjs";import{LeaderHints as i}from"./components/leaderHints.mjs";import{config_exports as a}from"./config.mjs";import{footer_exports as o}from"./footer.mjs";import{UI_EVENT as s,createUiTelemetry as c}from"./logSinkTelemetry.mjs";import"./theme.mjs";import{DoomUiState as l}from"./uiState.mjs";import{openConfigOverlay as u}from"./components/configOverlay.mjs";import{openToolsOverlay as d}from"./components/toolsOverlay.mjs";import{mode_exports as f}from"./mode.mjs";import{buildToolSources as p}from"./toolInventory.mjs";import m from"node:path";import{LeaderActionInvoked as h,LeaderRegistrationProtocol as g,LeaderUnregistrationProtocol as _}from"@agimon-ai/doompi-extension-contracts/leader";import{McpStatusQueryProtocol as v}from"@agimon-ai/doompi-extension-contracts/mcp-status";import{createProtocolRuntime as y}from"@agimon-ai/doompi-extension-contracts/protocol";const b=`doom-pi-leader`,x=`belowEditor`,S=`warning`,C=`doom-pi`,w=new Set([`hotkeys`]);function T(e,t){let n=e.trim().replace(/\s+/g,` `);if(!n)return;let r=n.slice(0,64).trimEnd();return`${C} · ${r} · ${m.basename(t)||t}`}function E(E,D=c()){let O=new l,k=new o.DoomFooterStatusRegistry,A=new f.DoomModeStatusRegistry,j=new a.DoomConfigRegistry,M=new e,N=new Set,P=new Map,F,I,L,R,z,B,V,H,U=new Map,W=0,G=0,K=!1,q,J=async()=>{if(q)try{return(await q.request(v,{},{timeoutMs:250})).servers}catch{return}};E.registerCommand(`tools`,{description:`Browse the tools available in this session`,handler:async(e,t)=>{let n=await J(),r=p({tools:E.getAllTools(),activeTools:E.getActiveTools(),...n?{mcpServers:n}:{}});await d(t,r)}}),E.registerCommand(`config`,{description:`Browse and change the settings extensions contribute`,handler:async(e,t)=>{await u(t,j)}});let Y=e=>{for(let t of e){let e=t.bindingId?` (${t.bindingId})`:``,n=`Leader contribution from ${t.source}${e}: ${t.message}`;N.has(n)||(N.add(n),D.recordWarning(s.leaderContributionRejected,t.message,{"leader.source":t.source,...t.bindingId?{"leader.binding.id":t.bindingId}:{}}),I?.mode===`tui`&&I.ui.notify(n,S))}},X=Object.freeze({register(e){let t=Symbol(e.source),n=M.register(e);if(Y(n.diagnostics),!n.accepted)return()=>void 0;P.set(e.source,t);let r=!1;return()=>{r||(r=!0,P.get(e.source)===t&&(P.delete(e.source),M.register({source:e.source,bindings:[]})))}}});E.on(`session_start`,(e,a)=>{I=a;let o=++G,c=y({bus:E.events,source:`@agimon-ai/doompi-ui`,sessionId:a.sessionManager.getSessionId()});if(q=c,L?.(),k.reset(),L=k.bind(c),R?.(),A.reset(),R=A.bind(c),B?.(),j.reset(),B=j.bind(c),V?.(),H?.(),V=c.provide(g,({contribution:e,generation:t},n)=>{if(n.source!==e.source)throw Error(`Leader contribution source does not match the registration envelope source.`);let r=`${n.source}:${t}:${++W}`,i={source:e.source,bindings:e.bindings.map(e=>{let[t,...n]=e.path;if(!t)throw Error(`Leader binding '${e.id}' has no path.`);return{...e,path:[t,...n]}})};return U.set(r,X.register(i)),{handleId:r}}),H=c.provide(_,({handleId:e})=>{let t=U.get(e);return t?(U.delete(e),t(),{removed:!0}):{removed:!1}}),K=a.sessionManager.getEntries().some(e=>e.type===`message`),a.mode!==`tui`)return;for(let e of N)a.ui.notify(e,S);let l=process.env.DOOMPI_THEME||`doom-pi-dark`,u=a.ui.setTheme(l);u.success||(a.ui.notify(`Could not apply Doom Pi theme: ${u.error}`,S),D.recordWarning(s.themeApplyFailed,u.error??`Unknown theme error`,{"ui.theme":l})),z?.(),z=A.subscribe(()=>O.setModes(A.getStatuses())),O.setModes(A.getStatuses()),a.ui.setTitle(`${C} · ${m.basename(a.cwd)||a.cwd} · pi/coding`),a.ui.setHeader((e,t)=>new r(t,a.cwd,void 0,a,()=>e.terminal.rows)),a.ui.setFooter((e,t,r)=>(F=r,new n(e,t,a,r,k,O))),a.ui.setEditorComponent((e,n,r)=>new t(e,n,r,O,e=>{if(!e.active){a.ui.setWidget(b,void 0,{placement:x});return}a.ui.setWidget(b,(t,n)=>new i(n,e,a,F,O.getModes()),{placement:x})},void 0,a.ui.theme,{registry:M,isCommandAvailable:e=>w.has(e)||E.getCommands().some(t=>t.name===e),onLeaderAction:(e,t)=>{if(I!==a||G!==o){a.ui.notify(`Leader action ${t} is unavailable.`,S);return}c.notify(h,{source:e,action:t})},onUnavailableCommand:e=>{D.recordWarning(s.leaderCommandUnavailable,`Leader command /${e} is unavailable.`,{"ui.kind":`command`}),a.ui.notify(`Leader command /${e} is unavailable.`,S)},onUnavailableAction:e=>{D.recordWarning(s.leaderActionUnavailable,`Leader action ${e} is unavailable.`,{"ui.kind":`action`}),a.ui.notify(`Leader action ${e} is unavailable.`,S)}})),D.recordEvent(s.shellInstalled,{"ui.theme":l,"ui.theme.applied":u.success,"ui.leader.group.count":M.getGroup([])?.options.length??0})}),E.on(`input`,(e,t)=>{if(t.mode!==`tui`||e.source!==`interactive`||K)return;K=!0;let n=T(e.text,t.cwd);n&&t.ui.setTitle(n)}),E.on(`session_shutdown`,async(e,t)=>{++G;for(let e of U.values())e();U.clear(),I=void 0,F=void 0,L?.(),L=void 0,k.reset(),R?.(),R=void 0,z?.(),z=void 0,A.reset(),B?.(),B=void 0,V?.(),V=void 0,H?.(),H=void 0,j.reset(),O.reset(),q=void 0,t.mode===`tui`&&t.ui.setWidget(b,void 0,{placement:x}),await D.shutdown()})}export{E as doomPiUiExtension};
1
+ import{DoomLeaderRegistry as e}from"./leaderRegistry.mjs";import{DoomEditor as t}from"./components/doomEditor.mjs";import{DoomFooter as n}from"./components/doomFooter.mjs";import{DoomHeader as r}from"./components/doomHeader.mjs";import{LeaderHints as i}from"./components/leaderHints.mjs";import{config_exports as a}from"./config.mjs";import{footer_exports as o}from"./footer.mjs";import{UI_EVENT as s,createUiTelemetry as c}from"./logSinkTelemetry.mjs";import"./theme.mjs";import{DoomUiState as l}from"./uiState.mjs";import{openConfigOverlay as u}from"./components/configOverlay.mjs";import{openToolsOverlay as d}from"./components/toolsOverlay.mjs";import{mode_exports as f}from"./mode.mjs";import{buildToolSources as p}from"./toolInventory.mjs";import m from"node:path";import{LeaderActionInvoked as h,LeaderRegistrationProtocol as g,LeaderUnregistrationProtocol as _,installDoomLeaderBootstrapHost as v}from"@agimon-ai/doompi-extension-contracts/leader";import{McpStatusQueryProtocol as y}from"@agimon-ai/doompi-extension-contracts/mcp-status";import{createProtocolRuntime as b}from"@agimon-ai/doompi-extension-contracts/protocol";const x=`doom-pi-leader`,S=`belowEditor`,C=`warning`,w=`doom-pi`,T=new Set([`hotkeys`]);function E(e,t){let n=e.trim().replace(/\s+/g,` `);if(!n)return;let r=n.slice(0,64).trimEnd();return`${w} · ${r} · ${m.basename(t)||t}`}function D(D,O=c()){let k=new l,A=new o.DoomFooterStatusRegistry,j=new f.DoomModeStatusRegistry,M=new a.DoomConfigRegistry,N=new e,P=new Set,F=new Map,I,L,R,z,B,V,H,U,W=new Map,G=0,K=0,q=!1,J,Y,X=async()=>{if(J)try{return(await J.request(y,{},{timeoutMs:250})).servers}catch{return}};D.registerCommand(`tools`,{description:`Browse the tools available in this session`,handler:async(e,t)=>{let n=await X(),r=p({tools:D.getAllTools(),activeTools:D.getActiveTools(),...n?{mcpServers:n}:{}});await d(t,r)}}),D.registerCommand(`config`,{description:`Browse and change the settings extensions contribute`,handler:async(e,t)=>{await u(t,M)}});let Z=e=>{for(let t of e){let e=t.bindingId?` (${t.bindingId})`:``,n=`Leader contribution from ${t.source}${e}: ${t.message}`;P.has(n)||(P.add(n),O.recordWarning(s.leaderContributionRejected,t.message,{"leader.source":t.source,...t.bindingId?{"leader.binding.id":t.bindingId}:{}}),L?.mode===`tui`&&L.ui.notify(n,C))}},Q=(e,t)=>{let n=Symbol(e.source),r=t?e=>N.registerDeferred(e):e=>N.register(e),i=r(e);if(Z(i.diagnostics),!i.accepted)return{update:()=>void 0,dispose:()=>void 0};F.set(e.source,n);let a=!1;return{update(t){a||F.get(e.source)!==n||Z(r({source:e.source,bindings:t}).diagnostics)},dispose(){a||(a=!0,F.get(e.source)===n&&(F.delete(e.source),r({source:e.source,bindings:[]})))}}},$=Object.freeze({register(e){let t=Q(e,!1);return()=>t.dispose()}});Y=v({register:e=>Q(e,!0)}),D.on(`session_start`,(e,a)=>{L=a;let o=++K,c=b({bus:D.events,source:`@agimon-ai/doompi-ui`,sessionId:a.sessionManager.getSessionId()});if(J=c,Z(N.flush()),R?.(),A.reset(),R=A.bind(c),z?.(),j.reset(),z=j.bind(c),V?.(),M.reset(),V=M.bind(c),H?.(),U?.(),H=c.provide(g,({contribution:e,generation:t},n)=>{if(n.source!==e.source)throw Error(`Leader contribution source does not match the registration envelope source.`);let r=`${n.source}:${t}:${++G}`,i={source:e.source,bindings:e.bindings.map(e=>{let[t,...n]=e.path;if(!t)throw Error(`Leader binding '${e.id}' has no path.`);return{...e,path:[t,...n]}})};return W.set(r,$.register(i)),{handleId:r}}),U=c.provide(_,({handleId:e})=>{let t=W.get(e);return t?(W.delete(e),t(),{removed:!0}):{removed:!1}}),q=a.sessionManager.getEntries().some(e=>e.type===`message`),a.mode!==`tui`)return;for(let e of P)a.ui.notify(e,C);let l=process.env.DOOMPI_THEME||`doom-pi-dark`,u=a.ui.setTheme(l);u.success||(a.ui.notify(`Could not apply Doom Pi theme: ${u.error}`,C),O.recordWarning(s.themeApplyFailed,u.error??`Unknown theme error`,{"ui.theme":l})),B?.(),B=j.subscribe(()=>k.setModes(j.getStatuses())),k.setModes(j.getStatuses()),a.ui.setTitle(`${w} · ${m.basename(a.cwd)||a.cwd} · pi/coding`),a.ui.setHeader((e,t)=>new r(t,a.cwd,void 0,a,()=>e.terminal.rows)),a.ui.setFooter((e,t,r)=>(I=r,new n(e,t,a,r,A,k))),a.ui.setEditorComponent((e,n,r)=>new t(e,n,r,k,e=>{if(!e.active){a.ui.setWidget(x,void 0,{placement:S});return}a.ui.setWidget(x,(t,n)=>new i(n,e,a,I,k.getModes()),{placement:S})},void 0,a.ui.theme,{registry:N,isCommandAvailable:e=>T.has(e)||D.getCommands().some(t=>t.name===e),onLeaderAction:(e,t)=>{if(L!==a||K!==o){a.ui.notify(`Leader action ${t} is unavailable.`,C);return}c.notify(h,{source:e,action:t})},onUnavailableCommand:e=>{O.recordWarning(s.leaderCommandUnavailable,`Leader command /${e} is unavailable.`,{"ui.kind":`command`}),a.ui.notify(`Leader command /${e} is unavailable.`,C)},onUnavailableAction:e=>{O.recordWarning(s.leaderActionUnavailable,`Leader action ${e} is unavailable.`,{"ui.kind":`action`}),a.ui.notify(`Leader action ${e} is unavailable.`,C)}})),O.recordEvent(s.shellInstalled,{"ui.theme":l,"ui.theme.applied":u.success,"ui.leader.group.count":N.getGroup([])?.options.length??0})}),D.on(`input`,(e,t)=>{if(t.mode!==`tui`||e.source!==`interactive`||q)return;q=!0;let n=E(e.text,t.cwd);n&&t.ui.setTitle(n)}),D.on(`session_shutdown`,async(e,t)=>{++K,Y?.(),Y=void 0;for(let e of W.values())e();W.clear(),L=void 0,I=void 0,R?.(),R=void 0,A.reset(),z?.(),z=void 0,B?.(),B=void 0,j.reset(),V?.(),V=void 0,H?.(),H=void 0,U?.(),U=void 0,M.reset(),k.reset(),J=void 0,t.mode===`tui`&&t.ui.setWidget(x,void 0,{placement:S}),await O.shutdown()})}export{D as doomPiUiExtension};
2
2
  //# sourceMappingURL=extension.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":["DoomFooterStatusRegistry","DoomModeStatusRegistry","DoomConfigRegistry"],"sources":["../src/extension.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ExtensionAPI, ExtensionContext, ReadonlyFooterDataProvider } from '@earendil-works/pi-coding-agent';\nimport {\n LeaderActionInvoked,\n type LeaderContribution,\n LeaderRegistrationProtocol,\n LeaderUnregistrationProtocol,\n} from '@agimon-ai/doompi-extension-contracts/leader';\nimport { McpStatusQueryProtocol } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport { createProtocolRuntime, type ProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport { DoomEditor } from './components/doomEditor.ts';\nimport { DoomFooter } from './components/doomFooter.ts';\nimport { DoomHeader } from './components/doomHeader.ts';\nimport { LeaderHints } from './components/leaderHints.ts';\nimport { DoomConfigRegistry } from './config.ts';\nimport { DoomFooterStatusRegistry } from './footer.ts';\nimport { DoomModeStatusRegistry } from './mode.ts';\nimport { DoomLeaderRegistry, type DoomLeaderDiagnostic } from './leaderRegistry.ts';\nimport { openConfigOverlay } from './components/configOverlay.ts';\nimport { openToolsOverlay } from './components/toolsOverlay.ts';\nimport { buildToolSources, type McpServerStatus } from './toolInventory.ts';\nimport { createUiTelemetry, UI_EVENT, type UiTelemetry } from './logSinkTelemetry.ts';\nimport { DEFAULT_THEME_NAME } from './theme.ts';\nimport { DoomUiState, type LeaderSnapshot } from './uiState.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';\n/** Short: `/tools` must open promptly whether or not an MCP extension answers. */\nconst MCP_STATUS_TIMEOUT_MS = 250;\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]);\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\nexport function doomPiUiExtension(pi: ExtensionAPI, telemetry: UiTelemetry = createUiTelemetry()): void {\n const uiState = new DoomUiState();\n const footerStatusRegistry = new DoomFooterStatusRegistry();\n const modeStatusRegistry = new DoomModeStatusRegistry();\n const configRegistry = new DoomConfigRegistry();\n const leaderRegistry = new DoomLeaderRegistry();\n const pendingDiagnostics = new Set<string>();\n const contributionGenerations = new Map<string, symbol>();\n let footerData: ReadonlyFooterDataProvider | undefined;\n let activeContext: ExtensionContext | undefined;\n let disposeFooterStatuses: (() => void) | undefined;\n let disposeModeStatuses: (() => void) | undefined;\n let disposeModeSubscription: (() => void) | undefined;\n let disposeConfigContributions: (() => void) | undefined;\n let disposeLeaderRegistration: (() => void) | undefined;\n let disposeLeaderUnregistration: (() => void) | undefined;\n const protocolContributions = new Map<string, () => void>();\n let nextProtocolHandle = 0;\n let sessionGeneration = 0;\n let hasSessionMessage = false;\n let sessionProtocol: ProtocolRuntime | undefined;\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 = async (): Promise<readonly McpServerStatus[] | undefined> => {\n if (!sessionProtocol) return undefined;\n try {\n const snapshot = await sessionProtocol.request(McpStatusQueryProtocol, {}, { timeoutMs: MCP_STATUS_TIMEOUT_MS });\n return snapshot.servers;\n } catch {\n return undefined;\n }\n };\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 = await readMcpServers();\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\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, configRegistry);\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) activeContext.ui.notify(message, WARNING_STYLE);\n }\n };\n\n const leaderContext = Object.freeze({\n register(contribution: LeaderContribution) {\n const generation = Symbol(contribution.source);\n const result = leaderRegistry.register(contribution);\n reportDiagnostics(result.diagnostics);\n if (!result.accepted) return () => undefined;\n contributionGenerations.set(contribution.source, generation);\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n if (contributionGenerations.get(contribution.source) !== generation) return;\n contributionGenerations.delete(contribution.source);\n leaderRegistry.register({ source: contribution.source, bindings: [] });\n };\n },\n });\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n activeContext = ctx;\n const activeGeneration = ++sessionGeneration;\n const protocol = createProtocolRuntime({\n bus: pi.events,\n source: '@agimon-ai/doompi-ui',\n sessionId: ctx.sessionManager.getSessionId(),\n });\n sessionProtocol = protocol;\n disposeFooterStatuses?.();\n footerStatusRegistry.reset();\n disposeFooterStatuses = footerStatusRegistry.bind(protocol);\n disposeModeStatuses?.();\n modeStatusRegistry.reset();\n disposeModeStatuses = modeStatusRegistry.bind(protocol);\n // Bound above the TUI-mode guard below, like footer and mode: a headless run\n // still accepts contributions, it just never draws them.\n disposeConfigContributions?.();\n configRegistry.reset();\n disposeConfigContributions = configRegistry.bind(protocol);\n disposeLeaderRegistration?.();\n disposeLeaderUnregistration?.();\n disposeLeaderRegistration = protocol.provide(\n LeaderRegistrationProtocol,\n ({ contribution, generation }, identity) => {\n if (identity.source !== contribution.source) {\n throw new Error('Leader contribution source does not match the registration envelope source.');\n }\n const handleId = `${identity.source}:${generation}:${++nextProtocolHandle}`;\n const typedContribution: LeaderContribution = {\n source: contribution.source,\n bindings: contribution.bindings.map((binding) => {\n const [first, ...rest] = binding.path;\n if (!first) throw new Error(`Leader binding '${binding.id}' has no path.`);\n return { ...binding, path: [first, ...rest] };\n }),\n };\n protocolContributions.set(handleId, leaderContext.register(typedContribution));\n return { handleId };\n },\n );\n disposeLeaderUnregistration = protocol.provide(LeaderUnregistrationProtocol, ({ handleId }) => {\n const dispose = protocolContributions.get(handleId);\n if (!dispose) return { removed: false };\n protocolContributions.delete(handleId);\n dispose();\n return { removed: true };\n });\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) ctx.ui.notify(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 ctx.ui.notify(`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 // The modes go into shared state rather than a widget of their own: the\n // editor prints their names beside its badge and the leader panel prints\n // them with their detail, so there is no third surface to paint.\n disposeModeSubscription?.();\n disposeModeSubscription = modeStatusRegistry.subscribe(() => uiState.setModes(modeStatusRegistry.getStatuses()));\n uiState.setModes(modeStatusRegistry.getStatuses());\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, footerStatusRegistry, 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 ctx.ui.notify(`Leader action ${action} is unavailable.`, WARNING_STYLE);\n return;\n }\n protocol.notify(LeaderActionInvoked, { 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 ctx.ui.notify(`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 ctx.ui.notify(`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 pi.on(SESSION_SHUTDOWN_EVENT, async (_event, ctx) => {\n ++sessionGeneration;\n for (const dispose of protocolContributions.values()) dispose();\n protocolContributions.clear();\n activeContext = undefined;\n footerData = undefined;\n disposeFooterStatuses?.();\n disposeFooterStatuses = undefined;\n footerStatusRegistry.reset();\n disposeModeStatuses?.();\n disposeModeStatuses = undefined;\n disposeModeSubscription?.();\n disposeModeSubscription = undefined;\n modeStatusRegistry.reset();\n disposeConfigContributions?.();\n disposeConfigContributions = undefined;\n disposeLeaderRegistration?.();\n disposeLeaderRegistration = undefined;\n disposeLeaderUnregistration?.();\n disposeLeaderUnregistration = undefined;\n configRegistry.reset();\n uiState.reset();\n sessionProtocol = undefined;\n if (ctx.mode === TUI_MODE) ctx.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n await telemetry.shutdown();\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.mjs","names":["DoomFooterStatusRegistry","DoomModeStatusRegistry","DoomConfigRegistry"],"sources":["../src/extension.ts"],"sourcesContent":["import path from 'node:path';\nimport type { ExtensionAPI, ExtensionContext, ReadonlyFooterDataProvider } from '@earendil-works/pi-coding-agent';\nimport {\n LeaderActionInvoked,\n installDoomLeaderBootstrapHost,\n type LeaderContribution,\n LeaderRegistrationProtocol,\n LeaderUnregistrationProtocol,\n} from '@agimon-ai/doompi-extension-contracts/leader';\nimport { McpStatusQueryProtocol } from '@agimon-ai/doompi-extension-contracts/mcp-status';\nimport { createProtocolRuntime, type ProtocolRuntime } from '@agimon-ai/doompi-extension-contracts/protocol';\nimport { DoomEditor } from './components/doomEditor.ts';\nimport { DoomFooter } from './components/doomFooter.ts';\nimport { DoomHeader } from './components/doomHeader.ts';\nimport { LeaderHints } from './components/leaderHints.ts';\nimport { DoomConfigRegistry } from './config.ts';\nimport { DoomFooterStatusRegistry } from './footer.ts';\nimport { DoomModeStatusRegistry } from './mode.ts';\nimport { DoomLeaderRegistry, type DoomLeaderDiagnostic } from './leaderRegistry.ts';\nimport { openConfigOverlay } from './components/configOverlay.ts';\nimport { openToolsOverlay } from './components/toolsOverlay.ts';\nimport { buildToolSources, type McpServerStatus } from './toolInventory.ts';\nimport { createUiTelemetry, UI_EVENT, type UiTelemetry } from './logSinkTelemetry.ts';\nimport { DEFAULT_THEME_NAME } from './theme.ts';\nimport { DoomUiState, type LeaderSnapshot } from './uiState.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';\n/** Short: `/tools` must open promptly whether or not an MCP extension answers. */\nconst MCP_STATUS_TIMEOUT_MS = 250;\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]);\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\nexport function doomPiUiExtension(pi: ExtensionAPI, telemetry: UiTelemetry = createUiTelemetry()): void {\n const uiState = new DoomUiState();\n const footerStatusRegistry = new DoomFooterStatusRegistry();\n const modeStatusRegistry = new DoomModeStatusRegistry();\n const configRegistry = new DoomConfigRegistry();\n const leaderRegistry = new DoomLeaderRegistry();\n const pendingDiagnostics = new Set<string>();\n const contributionGenerations = new Map<string, symbol>();\n let footerData: ReadonlyFooterDataProvider | undefined;\n let activeContext: ExtensionContext | undefined;\n let disposeFooterStatuses: (() => void) | undefined;\n let disposeModeStatuses: (() => void) | undefined;\n let disposeModeSubscription: (() => void) | undefined;\n let disposeConfigContributions: (() => void) | undefined;\n let disposeLeaderRegistration: (() => void) | undefined;\n let disposeLeaderUnregistration: (() => void) | undefined;\n const protocolContributions = new Map<string, () => void>();\n let nextProtocolHandle = 0;\n let sessionGeneration = 0;\n let hasSessionMessage = false;\n let sessionProtocol: ProtocolRuntime | undefined;\n let disposeLeaderBootstrap: (() => void) | undefined;\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 = async (): Promise<readonly McpServerStatus[] | undefined> => {\n if (!sessionProtocol) return undefined;\n try {\n const snapshot = await sessionProtocol.request(McpStatusQueryProtocol, {}, { timeoutMs: MCP_STATUS_TIMEOUT_MS });\n return snapshot.servers;\n } catch {\n return undefined;\n }\n };\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 = await readMcpServers();\n const sources = buildToolSources({\n tools: pi.getAllTools(),\n activeTools: pi.getActiveTools(),\n ...(mcpServers ? { mcpServers } : {}),\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, configRegistry);\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) activeContext.ui.notify(message, WARNING_STYLE);\n }\n };\n\n const registerLeaderContribution = (contribution: LeaderContribution, deferred: boolean) => {\n const generation = Symbol(contribution.source);\n const register = deferred\n ? (value: LeaderContribution) => leaderRegistry.registerDeferred(value)\n : (value: LeaderContribution) => leaderRegistry.register(value);\n const result = register(contribution);\n reportDiagnostics(result.diagnostics);\n if (!result.accepted) return { update: () => undefined, dispose: () => undefined };\n contributionGenerations.set(contribution.source, generation);\n let disposed = false;\n return {\n update(bindings: LeaderContribution['bindings']) {\n if (disposed || contributionGenerations.get(contribution.source) !== generation) return;\n reportDiagnostics(register({ source: contribution.source, bindings }).diagnostics);\n },\n dispose() {\n if (disposed) return;\n disposed = true;\n if (contributionGenerations.get(contribution.source) !== generation) return;\n contributionGenerations.delete(contribution.source);\n register({ source: contribution.source, bindings: [] });\n },\n };\n };\n\n const leaderContext = Object.freeze({\n register(contribution: LeaderContribution) {\n const handle = registerLeaderContribution(contribution, false);\n return () => handle.dispose();\n },\n });\n // Installed during the UI factory, before the remaining Doom factories run.\n // Their registrations are validated immediately and rebuilt as one batch at\n // session_start, so the first editor always receives the complete keyspace.\n disposeLeaderBootstrap = installDoomLeaderBootstrapHost({\n register: (contribution) => registerLeaderContribution(contribution, true),\n });\n pi.on(SESSION_START_EVENT, (_event, ctx) => {\n activeContext = ctx;\n const activeGeneration = ++sessionGeneration;\n const protocol = createProtocolRuntime({\n bus: pi.events,\n source: '@agimon-ai/doompi-ui',\n sessionId: ctx.sessionManager.getSessionId(),\n });\n sessionProtocol = protocol;\n reportDiagnostics(leaderRegistry.flush());\n disposeFooterStatuses?.();\n footerStatusRegistry.reset();\n disposeFooterStatuses = footerStatusRegistry.bind(protocol);\n disposeModeStatuses?.();\n modeStatusRegistry.reset();\n disposeModeStatuses = modeStatusRegistry.bind(protocol);\n // Bound above the TUI-mode guard below, like footer and mode: a headless run\n // still accepts contributions, it just never draws them.\n disposeConfigContributions?.();\n configRegistry.reset();\n disposeConfigContributions = configRegistry.bind(protocol);\n disposeLeaderRegistration?.();\n disposeLeaderUnregistration?.();\n disposeLeaderRegistration = protocol.provide(\n LeaderRegistrationProtocol,\n ({ contribution, generation }, identity) => {\n if (identity.source !== contribution.source) {\n throw new Error('Leader contribution source does not match the registration envelope source.');\n }\n const handleId = `${identity.source}:${generation}:${++nextProtocolHandle}`;\n const typedContribution: LeaderContribution = {\n source: contribution.source,\n bindings: contribution.bindings.map((binding) => {\n const [first, ...rest] = binding.path;\n if (!first) throw new Error(`Leader binding '${binding.id}' has no path.`);\n return { ...binding, path: [first, ...rest] };\n }),\n };\n protocolContributions.set(handleId, leaderContext.register(typedContribution));\n return { handleId };\n },\n );\n disposeLeaderUnregistration = protocol.provide(LeaderUnregistrationProtocol, ({ handleId }) => {\n const dispose = protocolContributions.get(handleId);\n if (!dispose) return { removed: false };\n protocolContributions.delete(handleId);\n dispose();\n return { removed: true };\n });\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) ctx.ui.notify(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 ctx.ui.notify(`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 // The modes go into shared state rather than a widget of their own: the\n // editor prints their names beside its badge and the leader panel prints\n // them with their detail, so there is no third surface to paint.\n disposeModeSubscription?.();\n disposeModeSubscription = modeStatusRegistry.subscribe(() => uiState.setModes(modeStatusRegistry.getStatuses()));\n uiState.setModes(modeStatusRegistry.getStatuses());\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, footerStatusRegistry, 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 ctx.ui.notify(`Leader action ${action} is unavailable.`, WARNING_STYLE);\n return;\n }\n protocol.notify(LeaderActionInvoked, { 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 ctx.ui.notify(`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 ctx.ui.notify(`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 pi.on(SESSION_SHUTDOWN_EVENT, async (_event, ctx) => {\n ++sessionGeneration;\n disposeLeaderBootstrap?.();\n disposeLeaderBootstrap = undefined;\n for (const dispose of protocolContributions.values()) dispose();\n protocolContributions.clear();\n activeContext = undefined;\n footerData = undefined;\n disposeFooterStatuses?.();\n disposeFooterStatuses = undefined;\n footerStatusRegistry.reset();\n disposeModeStatuses?.();\n disposeModeStatuses = undefined;\n disposeModeSubscription?.();\n disposeModeSubscription = undefined;\n modeStatusRegistry.reset();\n disposeConfigContributions?.();\n disposeConfigContributions = undefined;\n disposeLeaderRegistration?.();\n disposeLeaderRegistration = undefined;\n disposeLeaderUnregistration?.();\n disposeLeaderUnregistration = undefined;\n configRegistry.reset();\n uiState.reset();\n sessionProtocol = undefined;\n if (ctx.mode === TUI_MODE) ctx.ui.setWidget(LEADER_WIDGET_KEY, undefined, { placement: WIDGET_PLACEMENT });\n await telemetry.shutdown();\n });\n}\n"],"mappings":"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"}
package/dist/leader.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@agimon-ai/doompi-extension-contracts/leader");Object.defineProperty(exports,"createDoomLeaderContribution",{enumerable:!0,get:function(){return e.createDoomLeaderContribution}}),Object.defineProperty(exports,"registerDoomLeaderActionHandlers",{enumerable:!0,get:function(){return e.registerDoomLeaderActionHandlers}}),Object.defineProperty(exports,"registerDoomLeaderContribution",{enumerable:!0,get:function(){return e.registerDoomLeaderContribution}});
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("@agimon-ai/doompi-extension-contracts/leader");Object.defineProperty(exports,"createDoomLeaderContribution",{enumerable:!0,get:function(){return e.createDoomLeaderContribution}}),Object.defineProperty(exports,"installDoomLeaderBootstrapHost",{enumerable:!0,get:function(){return e.installDoomLeaderBootstrapHost}}),Object.defineProperty(exports,"registerDoomLeaderActionHandlers",{enumerable:!0,get:function(){return e.registerDoomLeaderActionHandlers}}),Object.defineProperty(exports,"registerDoomLeaderContribution",{enumerable:!0,get:function(){return e.registerDoomLeaderContribution}});
package/dist/leader.d.cts CHANGED
@@ -1,2 +1,2 @@
1
- import { DoomLeaderActionHandlerOptions, DoomLeaderContributionHandle, LeaderAction as DoomLeaderExtensionAction, LeaderBinding as DoomLeaderBinding, LeaderCommand as DoomLeaderCommand, LeaderContribution as DoomLeaderContribution, LeaderSegment as DoomLeaderSegment, createDoomLeaderContribution, registerDoomLeaderActionHandlers, registerDoomLeaderContribution } from "@agimon-ai/doompi-extension-contracts/leader";
2
- export { type DoomLeaderActionHandlerOptions, type DoomLeaderBinding, type DoomLeaderCommand, type DoomLeaderContribution, type DoomLeaderContributionHandle, type DoomLeaderExtensionAction, type DoomLeaderSegment, createDoomLeaderContribution, registerDoomLeaderActionHandlers, registerDoomLeaderContribution };
1
+ import { DoomLeaderActionHandlerOptions, DoomLeaderBootstrapHost, DoomLeaderContributionHandle, LeaderAction as DoomLeaderExtensionAction, LeaderBinding as DoomLeaderBinding, LeaderCommand as DoomLeaderCommand, LeaderContribution as DoomLeaderContribution, LeaderSegment as DoomLeaderSegment, createDoomLeaderContribution, installDoomLeaderBootstrapHost, registerDoomLeaderActionHandlers, registerDoomLeaderContribution } from "@agimon-ai/doompi-extension-contracts/leader";
2
+ export { type DoomLeaderActionHandlerOptions, type DoomLeaderBinding, type DoomLeaderBootstrapHost, type DoomLeaderCommand, type DoomLeaderContribution, type DoomLeaderContributionHandle, type DoomLeaderExtensionAction, type DoomLeaderSegment, createDoomLeaderContribution, installDoomLeaderBootstrapHost, registerDoomLeaderActionHandlers, registerDoomLeaderContribution };
package/dist/leader.d.mts CHANGED
@@ -1,2 +1,2 @@
1
- import { DoomLeaderActionHandlerOptions, DoomLeaderContributionHandle, LeaderAction as DoomLeaderExtensionAction, LeaderBinding as DoomLeaderBinding, LeaderCommand as DoomLeaderCommand, LeaderContribution as DoomLeaderContribution, LeaderSegment as DoomLeaderSegment, createDoomLeaderContribution, registerDoomLeaderActionHandlers, registerDoomLeaderContribution } from "@agimon-ai/doompi-extension-contracts/leader";
2
- export { type DoomLeaderActionHandlerOptions, type DoomLeaderBinding, type DoomLeaderCommand, type DoomLeaderContribution, type DoomLeaderContributionHandle, type DoomLeaderExtensionAction, type DoomLeaderSegment, createDoomLeaderContribution, registerDoomLeaderActionHandlers, registerDoomLeaderContribution };
1
+ import { DoomLeaderActionHandlerOptions, DoomLeaderBootstrapHost, DoomLeaderContributionHandle, LeaderAction as DoomLeaderExtensionAction, LeaderBinding as DoomLeaderBinding, LeaderCommand as DoomLeaderCommand, LeaderContribution as DoomLeaderContribution, LeaderSegment as DoomLeaderSegment, createDoomLeaderContribution, installDoomLeaderBootstrapHost, registerDoomLeaderActionHandlers, registerDoomLeaderContribution } from "@agimon-ai/doompi-extension-contracts/leader";
2
+ export { type DoomLeaderActionHandlerOptions, type DoomLeaderBinding, type DoomLeaderBootstrapHost, type DoomLeaderCommand, type DoomLeaderContribution, type DoomLeaderContributionHandle, type DoomLeaderExtensionAction, type DoomLeaderSegment, createDoomLeaderContribution, installDoomLeaderBootstrapHost, registerDoomLeaderActionHandlers, registerDoomLeaderContribution };
package/dist/leader.mjs CHANGED
@@ -1 +1 @@
1
- import{createDoomLeaderContribution as e,registerDoomLeaderActionHandlers as t,registerDoomLeaderContribution as n}from"@agimon-ai/doompi-extension-contracts/leader";export{e as createDoomLeaderContribution,t as registerDoomLeaderActionHandlers,n as registerDoomLeaderContribution};
1
+ import{createDoomLeaderContribution as e,installDoomLeaderBootstrapHost as t,registerDoomLeaderActionHandlers as n,registerDoomLeaderContribution as r}from"@agimon-ai/doompi-extension-contracts/leader";export{e as createDoomLeaderContribution,t as installDoomLeaderBootstrapHost,n as registerDoomLeaderActionHandlers,r as registerDoomLeaderContribution};
@@ -1,2 +1,2 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=`@agimon-ai/doompi-ui`,t=`unknown`,n=1e3,r=/^[a-z0-9]$/,i=/^[a-z0-9][a-z0-9._:-]*$/,a=/^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/,o=/^[a-z0-9][a-z0-9:_-]*$/,s={key:`m`,label:`models`,order:10},c={key:`s`,label:`sessions`,order:30},l={key:`e`,label:`extension`,order:50},u={key:`h`,label:`help`,order:70},d={key:`q`,label:`quit`,order:90};function f(t,n,r){return{id:t,source:e,path:n,action:r}}const p=[f(`model.select`,[s,{key:`m`,label:`select`,detail:`choose model`,order:10}],{type:`app`,action:`app.model.select`}),f(`model.next`,[s,{key:`n`,label:`next`,detail:`cycle model`,order:20}],{type:`app`,action:`app.model.cycleForward`}),f(`thinking.cycle`,[s,{key:`t`,label:`thinking`,detail:`cycle level`,order:30}],{type:`app`,action:`app.thinking.cycle`}),f(`tools.open`,[l,{key:`t`,label:`tools`,detail:`browse tools`,order:20}],{type:`command`,command:{name:`tools`}}),f(`config.open`,[l,{key:`c`,label:`config`,detail:`settings`,order:30}],{type:`command`,command:{name:`config`}}),f(`session.new`,[c,{key:`n`,label:`new`,detail:`fresh session`,order:10}],{type:`app`,action:`app.session.new`}),f(`session.resume`,[c,{key:`r`,label:`resume`,detail:`open history`,order:20}],{type:`app`,action:`app.session.resume`}),f(`session.tree`,[c,{key:`t`,label:`tree`,detail:`branch view`,order:30}],{type:`app`,action:`app.session.tree`}),f(`session.fork`,[c,{key:`f`,label:`fork`,detail:`from selection`,order:40}],{type:`app`,action:`app.session.fork`}),f(`editor.external`,[l,{key:`e`,label:`external`,detail:`open editor`,order:10}],{type:`app`,action:`app.editor.external`}),f(`help.hotkeys`,[u,{key:`h`,label:`hotkeys`,detail:`show bindings`}],{type:`command`,command:{name:`hotkeys`}}),f(`app.exit`,[d,{key:`q`,label:`quit`,detail:`exit Pi`}],{type:`app`,action:`app.exit`})];function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e,t,n){return{source:e,message:t,...n?{bindingId:n}:{}}}function g(e){for(let t=0;t<e.length;t+=1){let n=e.charCodeAt(t);if(n<=31||n===127)return!0}return!1}function _(e,t,n,r,i,a){if(typeof e!=`string`||!e.trim()){i.push(h(r,`${t} must be a non-empty string.`,a));return}let o=e.trim();if(o.length>n||g(o)){i.push(h(r,`${t} must be at most ${n} characters and contain no control characters.`,a));return}return o}function v(e,t,n,r,i,a){if(e!==void 0)return _(e,t,n,r,i,a)}function y(e,t,i,a,o){if(!m(e)){o.push(h(t,`bindings[].path[${a}] must be an object.`,i));return}let s=_(e.key,`bindings[].path[${a}].key`,1,t,o,i),c=_(e.label,`bindings[].path[${a}].label`,32,t,o,i),l=v(e.detail,`bindings[].path[${a}].detail`,80,t,o,i),u;if(e.order!==void 0&&(!Number.isInteger(e.order)||e.order<0||e.order>n?o.push(h(t,`bindings[].path[${a}].order must be an integer from 0 to ${n}.`,i)):u=e.order),!s||!c||!r.test(s)){s&&!r.test(s)&&o.push(h(t,`bindings[].path[${a}].key must match ${r}.`,i));return}return{key:s,label:c,...l?{detail:l}:{},...u===void 0?{}:{order:u}}}function b(e,t,n,r){if(!m(e)){r.push(h(t,`bindings[].command must be an object.`,n));return}let i=_(e.name,`bindings[].command.name`,64,t,r,n),a=v(e.args,`bindings[].command.args`,256,t,r,n);if(!i||!o.test(i)){i&&!o.test(i)&&r.push(h(t,`bindings[].command.name must match ${o}.`,n));return}return{name:i,...a?{args:a}:{}}}function x(e,t,n,r){if(!m(e)){r.push(h(t,`bindings[].action must be an object.`,n));return}let a=_(e.name,`bindings[].action.name`,128,t,r,n);if(!a||!i.test(a)){a&&!i.test(a)&&r.push(h(t,`bindings[].action.name must match ${i}.`,n));return}return{name:a}}function S(e,t,n){if(!m(e)){n.push(h(t,`bindings[] must be an object.`));return}let r=_(e.id,`bindings[].id`,128,t,n);if(!r)return;if(!i.test(r)){n.push(h(t,`bindings[].id must match ${i}.`,r));return}if(!Array.isArray(e.path)||e.path.length===0||e.path.length>4){n.push(h(t,`bindings[].path must contain between 1 and 4 segments.`,r));return}let a=e.path.map((e,i)=>y(e,t,r,i,n)),o=Object.hasOwn(e,`command`);if(o===Object.hasOwn(e,`action`)){n.push(h(t,`bindings[] must contain exactly one of command or action.`,r));return}let s=o?{command:b(e.command,t,r,n)}:{action:x(e.action,t,r,n)};if(!a.some(e=>e===void 0)){if(`command`in s)return s.command?{id:r,path:a,command:s.command}:void 0;if(s.action)return{id:r,path:a,action:s.action}}}function C(e){let n=[];if(!m(e))return{diagnostics:[h(t,`Leader contribution must be an object.`)]};let r=_(e.source,`source`,128,t,n);if(!r)return{diagnostics:n};if(!a.test(r))return n.push(h(r,`source must match ${a}.`)),{diagnostics:n};if(!Array.isArray(e.bindings))return{diagnostics:[h(r,`bindings must be an array.`)]};let i=e.bindings.map(e=>S(e,r,n)),o=i.flatMap(e=>e?[e.id]:[]).filter((e,t,n)=>n.indexOf(e)!==t);for(let e of new Set(o))n.push(h(r,`Binding id '${e}' is duplicated.`,e));return n.length>0||i.some(e=>e===void 0)?{diagnostics:n}:{contribution:{source:r,bindings:i},diagnostics:n}}function w(e){return e.order??500}function T(e){return e.type===`command`?{type:`command`,command:{...e.command}}:e.type===`app`?{type:`app`,action:e.action}:{type:`extension`,source:e.source,action:{...e.action}}}function E(){return{key:``,label:`leader`,order:0,owner:e,children:new Map}}function D(e){return`SPC ${e.map(e=>e.key).join(` `)}`}function O(e,t){return e.label===t.label&&e.detail===t.detail&&e.order===w(t)}function k(e,t){let n=e,r=-1;for(let[e,i]of t.path.entries()){let a=n.children.get(i.key);if(!a){r=e;break}if(!O(a,i))return h(t.source,`${D(t.path.slice(0,e+1))} conflicts with metadata from ${a.owner}.`,t.id);let o=e===t.path.length-1;if(!o&&a.action||o&&(a.action||a.children.size>0))return h(t.source,`${D(t.path)} conflicts with an existing binding from ${a.owner}.`,t.id);n=a}let i=r===-1?t.path.length:r;for(let e=i;e<t.path.length;e+=1){let r=t.path[e],i={key:r.key,label:r.label,...r.detail?{detail:r.detail}:{},order:w(r),owner:t.source,children:new Map};n.children.set(r.key,i),n=i}n.action=T(t.action),n.bindingId=t.id}var A=class{root=E();contributions=new Map;listeners=new Set;diagnostics=[];constructor(){this.rebuild()}register(e){let t=C(e);return t.contribution?(t.contribution.bindings.length===0?this.contributions.delete(t.contribution.source):this.contributions.set(t.contribution.source,t.contribution.bindings),this.rebuild(),{accepted:!0,diagnostics:this.getDiagnostics()}):{accepted:!1,diagnostics:t.diagnostics}}getGroup(e){let t=this.root;for(let n of e){let e=t.children.get(n);if(!e||e.action)return;t=e}let n=[...t.children.values()].sort((e,t)=>e.key.localeCompare(t.key)).map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{},hasChildren:e.children.size>0,...e.action?{action:T(e.action)}:{}}));return{label:t.label,options:n}}getDiagnostics(){return this.diagnostics.map(e=>({...e}))}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}rebuild(){let e=E(),t=[];for(let t of p){let n=k(e,t);if(n)throw Error(`Invalid core leader binding: ${n.message}`)}for(let[n,r]of[...this.contributions.entries()].sort(([e],[t])=>e.localeCompare(t)))for(let i of[...r].sort((e,t)=>e.id.localeCompare(t.id))){let r=k(e,{id:i.id,source:n,path:i.path,action:`command`in i?{type:`command`,command:i.command}:{type:`extension`,source:n,action:i.action}});r&&t.push(r)}this.root=e,this.diagnostics=t;for(let e of this.listeners)e()}};exports.DoomLeaderRegistry=A;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=`@agimon-ai/doompi-ui`,t=`unknown`,n=1e3,r=/^[a-z0-9]$/,i=/^[a-z0-9][a-z0-9._:-]*$/,a=/^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/,o=/^[a-z0-9][a-z0-9:_-]*$/,s={key:`m`,label:`models`,order:10},c={key:`s`,label:`sessions`,order:30},l={key:`e`,label:`extension`,order:50},u={key:`h`,label:`help`,order:70},d={key:`q`,label:`quit`,order:90};function f(t,n,r){return{id:t,source:e,path:n,action:r}}const p=[f(`model.select`,[s,{key:`m`,label:`select`,detail:`choose model`,order:10}],{type:`app`,action:`app.model.select`}),f(`model.next`,[s,{key:`n`,label:`next`,detail:`cycle model`,order:20}],{type:`app`,action:`app.model.cycleForward`}),f(`thinking.cycle`,[s,{key:`t`,label:`thinking`,detail:`cycle level`,order:30}],{type:`app`,action:`app.thinking.cycle`}),f(`tools.open`,[l,{key:`t`,label:`tools`,detail:`browse tools`,order:20}],{type:`command`,command:{name:`tools`}}),f(`config.open`,[l,{key:`c`,label:`config`,detail:`settings`,order:30}],{type:`command`,command:{name:`config`}}),f(`session.new`,[c,{key:`n`,label:`new`,detail:`fresh session`,order:10}],{type:`app`,action:`app.session.new`}),f(`session.resume`,[c,{key:`r`,label:`resume`,detail:`open history`,order:20}],{type:`app`,action:`app.session.resume`}),f(`session.tree`,[c,{key:`t`,label:`tree`,detail:`branch view`,order:30}],{type:`app`,action:`app.session.tree`}),f(`session.fork`,[c,{key:`f`,label:`fork`,detail:`from selection`,order:40}],{type:`app`,action:`app.session.fork`}),f(`editor.external`,[l,{key:`e`,label:`external`,detail:`open editor`,order:10}],{type:`app`,action:`app.editor.external`}),f(`help.hotkeys`,[u,{key:`h`,label:`hotkeys`,detail:`show bindings`}],{type:`command`,command:{name:`hotkeys`}}),f(`app.exit`,[d,{key:`q`,label:`quit`,detail:`exit Pi`}],{type:`app`,action:`app.exit`})];function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e,t,n){return{source:e,message:t,...n?{bindingId:n}:{}}}function g(e){for(let t=0;t<e.length;t+=1){let n=e.charCodeAt(t);if(n<=31||n===127)return!0}return!1}function _(e,t,n,r,i,a){if(typeof e!=`string`||!e.trim()){i.push(h(r,`${t} must be a non-empty string.`,a));return}let o=e.trim();if(o.length>n||g(o)){i.push(h(r,`${t} must be at most ${n} characters and contain no control characters.`,a));return}return o}function v(e,t,n,r,i,a){if(e!==void 0)return _(e,t,n,r,i,a)}function y(e,t,i,a,o){if(!m(e)){o.push(h(t,`bindings[].path[${a}] must be an object.`,i));return}let s=_(e.key,`bindings[].path[${a}].key`,1,t,o,i),c=_(e.label,`bindings[].path[${a}].label`,32,t,o,i),l=v(e.detail,`bindings[].path[${a}].detail`,80,t,o,i),u;if(e.order!==void 0&&(!Number.isInteger(e.order)||e.order<0||e.order>n?o.push(h(t,`bindings[].path[${a}].order must be an integer from 0 to ${n}.`,i)):u=e.order),!s||!c||!r.test(s)){s&&!r.test(s)&&o.push(h(t,`bindings[].path[${a}].key must match ${r}.`,i));return}return{key:s,label:c,...l?{detail:l}:{},...u===void 0?{}:{order:u}}}function b(e,t,n,r){if(!m(e)){r.push(h(t,`bindings[].command must be an object.`,n));return}let i=_(e.name,`bindings[].command.name`,64,t,r,n),a=v(e.args,`bindings[].command.args`,256,t,r,n);if(!i||!o.test(i)){i&&!o.test(i)&&r.push(h(t,`bindings[].command.name must match ${o}.`,n));return}return{name:i,...a?{args:a}:{}}}function x(e,t,n,r){if(!m(e)){r.push(h(t,`bindings[].action must be an object.`,n));return}let a=_(e.name,`bindings[].action.name`,128,t,r,n);if(!a||!i.test(a)){a&&!i.test(a)&&r.push(h(t,`bindings[].action.name must match ${i}.`,n));return}return{name:a}}function S(e,t,n){if(!m(e)){n.push(h(t,`bindings[] must be an object.`));return}let r=_(e.id,`bindings[].id`,128,t,n);if(!r)return;if(!i.test(r)){n.push(h(t,`bindings[].id must match ${i}.`,r));return}if(!Array.isArray(e.path)||e.path.length===0||e.path.length>4){n.push(h(t,`bindings[].path must contain between 1 and 4 segments.`,r));return}let a=e.path.map((e,i)=>y(e,t,r,i,n)),o=Object.hasOwn(e,`command`);if(o===Object.hasOwn(e,`action`)){n.push(h(t,`bindings[] must contain exactly one of command or action.`,r));return}let s=o?{command:b(e.command,t,r,n)}:{action:x(e.action,t,r,n)};if(!a.some(e=>e===void 0)){if(`command`in s)return s.command?{id:r,path:a,command:s.command}:void 0;if(s.action)return{id:r,path:a,action:s.action}}}function C(e){let n=[];if(!m(e))return{diagnostics:[h(t,`Leader contribution must be an object.`)]};let r=_(e.source,`source`,128,t,n);if(!r)return{diagnostics:n};if(!a.test(r))return n.push(h(r,`source must match ${a}.`)),{diagnostics:n};if(!Array.isArray(e.bindings))return{diagnostics:[h(r,`bindings must be an array.`)]};let i=e.bindings.map(e=>S(e,r,n)),o=i.flatMap(e=>e?[e.id]:[]).filter((e,t,n)=>n.indexOf(e)!==t);for(let e of new Set(o))n.push(h(r,`Binding id '${e}' is duplicated.`,e));return n.length>0||i.some(e=>e===void 0)?{diagnostics:n}:{contribution:{source:r,bindings:i},diagnostics:n}}function w(e){return e.order??500}function T(e){return e.type===`command`?{type:`command`,command:{...e.command}}:e.type===`app`?{type:`app`,action:e.action}:{type:`extension`,source:e.source,action:{...e.action}}}function E(){return{key:``,label:`leader`,order:0,owner:e,children:new Map}}function D(e){return`SPC ${e.map(e=>e.key).join(` `)}`}function O(e,t){return e.label===t.label&&e.detail===t.detail&&e.order===w(t)}function k(e,t){let n=e,r=-1;for(let[e,i]of t.path.entries()){let a=n.children.get(i.key);if(!a){r=e;break}if(!O(a,i))return h(t.source,`${D(t.path.slice(0,e+1))} conflicts with metadata from ${a.owner}.`,t.id);let o=e===t.path.length-1;if(!o&&a.action||o&&(a.action||a.children.size>0))return h(t.source,`${D(t.path)} conflicts with an existing binding from ${a.owner}.`,t.id);n=a}let i=r===-1?t.path.length:r;for(let e=i;e<t.path.length;e+=1){let r=t.path[e],i={key:r.key,label:r.label,...r.detail?{detail:r.detail}:{},order:w(r),owner:t.source,children:new Map};n.children.set(r.key,i),n=i}n.action=T(t.action),n.bindingId=t.id}var A=class{root=E();contributions=new Map;listeners=new Set;diagnostics=[];dirty=!1;constructor(){this.rebuild()}register(e){let t=this.apply(e);return t.accepted?(this.rebuild(),{accepted:!0,diagnostics:this.getDiagnostics()}):t}registerDeferred(e){return this.apply(e)}apply(e){let t=C(e);return t.contribution?(t.contribution.bindings.length===0?this.contributions.delete(t.contribution.source):this.contributions.set(t.contribution.source,t.contribution.bindings),this.dirty=!0,{accepted:!0,diagnostics:t.diagnostics}):{accepted:!1,diagnostics:t.diagnostics}}flush(){return this.dirty&&this.rebuild(),this.diagnostics.map(e=>({...e}))}getGroup(e){this.flush();let t=this.root;for(let n of e){let e=t.children.get(n);if(!e||e.action)return;t=e}let n=[...t.children.values()].sort((e,t)=>e.key.localeCompare(t.key)).map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{},hasChildren:e.children.size>0,...e.action?{action:T(e.action)}:{}}));return{label:t.label,options:n}}getDiagnostics(){return this.flush()}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}rebuild(){let e=E(),t=[];for(let t of p){let n=k(e,t);if(n)throw Error(`Invalid core leader binding: ${n.message}`)}for(let[n,r]of[...this.contributions.entries()].sort(([e],[t])=>e.localeCompare(t)))for(let i of[...r].sort((e,t)=>e.id.localeCompare(t.id))){let r=k(e,{id:i.id,source:n,path:i.path,action:`command`in i?{type:`command`,command:i.command}:{type:`extension`,source:n,action:i.action}});r&&t.push(r)}this.root=e,this.diagnostics=t,this.dirty=!1;for(let e of this.listeners)e()}};exports.DoomLeaderRegistry=A;
2
2
  //# sourceMappingURL=leaderRegistry.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"leaderRegistry.cjs","names":[],"sources":["../src/leaderRegistry.ts"],"sourcesContent":["import type { AppKeybinding } from '@earendil-works/pi-coding-agent';\nimport {\n type DoomLeaderBinding,\n type DoomLeaderCommand,\n type DoomLeaderContribution,\n type DoomLeaderExtensionAction,\n type DoomLeaderSegment,\n} from './leader.ts';\n\ninterface AppLeaderAction {\n type: 'app';\n action: AppKeybinding;\n}\n\ninterface CommandLeaderAction {\n type: 'command';\n command: DoomLeaderCommand;\n}\n\ninterface ExtensionLeaderAction {\n type: 'extension';\n source: string;\n action: DoomLeaderExtensionAction;\n}\n\nexport type DoomLeaderAction = AppLeaderAction | CommandLeaderAction | ExtensionLeaderAction;\n\nexport interface DoomLeaderResolvedOption {\n key: string;\n label: string;\n detail?: string;\n hasChildren: boolean;\n action?: DoomLeaderAction;\n}\n\nexport interface DoomLeaderGroup {\n label: string;\n options: readonly DoomLeaderResolvedOption[];\n}\n\nexport interface DoomLeaderDiagnostic {\n source: string;\n bindingId?: string;\n message: string;\n}\n\nexport interface DoomLeaderRegistrationResult {\n accepted: boolean;\n diagnostics: readonly DoomLeaderDiagnostic[];\n}\n\ninterface InternalLeaderBinding {\n id: string;\n source: string;\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]];\n action: DoomLeaderAction;\n}\n\ninterface LeaderNode {\n key: string;\n label: string;\n detail?: string;\n order: number;\n owner: string;\n bindingId?: string;\n children: Map<string, LeaderNode>;\n action?: DoomLeaderAction;\n}\n\ninterface ParsedContribution {\n contribution?: DoomLeaderContribution;\n diagnostics: DoomLeaderDiagnostic[];\n}\n\nconst CORE_SOURCE = '@agimon-ai/doompi-ui';\nconst UNKNOWN_SOURCE = 'unknown';\nconst ROOT_LABEL = 'leader';\nconst DEFAULT_OPTION_ORDER = 500;\nconst MAX_OPTION_ORDER = 1000;\nconst MAX_PATH_LENGTH = 4;\nconst MAX_SOURCE_LENGTH = 128;\nconst MAX_BINDING_ID_LENGTH = 128;\nconst MAX_LABEL_LENGTH = 32;\nconst MAX_DETAIL_LENGTH = 80;\nconst MAX_COMMAND_NAME_LENGTH = 64;\nconst MAX_COMMAND_ARGUMENT_LENGTH = 256;\nconst MAX_ACTION_NAME_LENGTH = 128;\nconst SAFE_KEY = /^[a-z0-9]$/;\nconst SAFE_IDENTIFIER = /^[a-z0-9][a-z0-9._:-]*$/;\nconst SAFE_SOURCE = /^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/;\nconst SAFE_COMMAND_NAME = /^[a-z0-9][a-z0-9:_-]*$/;\nconst C0_CONTROL_MAX = 31;\nconst DELETE_CHARACTER = 127;\nconst MODEL_GROUP = { key: 'm', label: 'models', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', order: 90 } as const;\n\nfunction coreBinding(\n id: string,\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: DoomLeaderAction,\n): InternalLeaderBinding {\n return { id, source: CORE_SOURCE, path, action };\n}\n\nconst CORE_BINDINGS: readonly InternalLeaderBinding[] = [\n coreBinding('model.select', [MODEL_GROUP, { key: 'm', label: 'select', detail: 'choose model', order: 10 }], {\n type: 'app',\n action: 'app.model.select',\n }),\n coreBinding('model.next', [MODEL_GROUP, { key: 'n', label: 'next', detail: 'cycle model', order: 20 }], {\n type: 'app',\n action: 'app.model.cycleForward',\n }),\n coreBinding('thinking.cycle', [MODEL_GROUP, { key: 't', label: 'thinking', detail: 'cycle level', order: 30 }], {\n type: 'app',\n action: 'app.thinking.cycle',\n }),\n coreBinding('tools.open', [EXTENSION_GROUP, { key: 't', label: 'tools', detail: 'browse tools', order: 20 }], {\n type: 'command',\n command: { name: 'tools' },\n }),\n // Core rather than contributed, so no extension owns the key and the panel is\n // reachable even when nothing has contributed a section to it yet.\n coreBinding('config.open', [EXTENSION_GROUP, { key: 'c', label: 'config', detail: 'settings', order: 30 }], {\n type: 'command',\n command: { name: 'config' },\n }),\n coreBinding('session.new', [SESSION_GROUP, { key: 'n', label: 'new', detail: 'fresh session', order: 10 }], {\n type: 'app',\n action: 'app.session.new',\n }),\n coreBinding('session.resume', [SESSION_GROUP, { key: 'r', label: 'resume', detail: 'open history', order: 20 }], {\n type: 'app',\n action: 'app.session.resume',\n }),\n coreBinding('session.tree', [SESSION_GROUP, { key: 't', label: 'tree', detail: 'branch view', order: 30 }], {\n type: 'app',\n action: 'app.session.tree',\n }),\n coreBinding('session.fork', [SESSION_GROUP, { key: 'f', label: 'fork', detail: 'from selection', order: 40 }], {\n type: 'app',\n action: 'app.session.fork',\n }),\n coreBinding('editor.external', [EXTENSION_GROUP, { key: 'e', label: 'external', detail: 'open editor', order: 10 }], {\n type: 'app',\n action: 'app.editor.external',\n }),\n coreBinding('help.hotkeys', [HELP_GROUP, { key: 'h', label: 'hotkeys', detail: 'show bindings' }], {\n type: 'command',\n command: { name: 'hotkeys' },\n }),\n coreBinding('app.exit', [QUIT_GROUP, { key: 'q', label: 'quit', detail: 'exit Pi' }], {\n type: 'app',\n action: 'app.exit',\n }),\n];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction diagnostic(source: string, message: string, bindingId?: string): DoomLeaderDiagnostic {\n return { source, message, ...(bindingId ? { bindingId } : {}) };\n}\n\nfunction containsControlCharacter(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const codeUnit = value.charCodeAt(index);\n if (codeUnit <= C0_CONTROL_MAX || codeUnit === DELETE_CHARACTER) return true;\n }\n return false;\n}\n\nfunction readRequiredText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId?: string,\n): string | undefined {\n if (typeof value !== 'string' || !value.trim()) {\n diagnostics.push(diagnostic(source, `${field} must be a non-empty string.`, bindingId));\n return undefined;\n }\n const text = value.trim();\n if (text.length > maximumLength || containsControlCharacter(text)) {\n diagnostics.push(\n diagnostic(\n source,\n `${field} must be at most ${maximumLength} characters and contain no control characters.`,\n bindingId,\n ),\n );\n return undefined;\n }\n return text;\n}\n\nfunction readOptionalText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId: string,\n): string | undefined {\n if (value === undefined) return undefined;\n return readRequiredText(value, field, maximumLength, source, diagnostics, bindingId);\n}\n\nfunction parseSegment(\n value: unknown,\n source: string,\n bindingId: string,\n index: number,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderSegment | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}] must be an object.`, bindingId));\n return undefined;\n }\n\n const key = readRequiredText(value.key, `bindings[].path[${index}].key`, 1, source, diagnostics, bindingId);\n const label = readRequiredText(\n value.label,\n `bindings[].path[${index}].label`,\n MAX_LABEL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const detail = readOptionalText(\n value.detail,\n `bindings[].path[${index}].detail`,\n MAX_DETAIL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n let order: number | undefined;\n if (value.order !== undefined) {\n if (!Number.isInteger(value.order) || (value.order as number) < 0 || (value.order as number) > MAX_OPTION_ORDER) {\n diagnostics.push(\n diagnostic(\n source,\n `bindings[].path[${index}].order must be an integer from 0 to ${MAX_OPTION_ORDER}.`,\n bindingId,\n ),\n );\n } else {\n order = value.order as number;\n }\n }\n if (!key || !label || !SAFE_KEY.test(key)) {\n if (key && !SAFE_KEY.test(key)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}].key must match ${SAFE_KEY}.`, bindingId));\n }\n return undefined;\n }\n return { key, label, ...(detail ? { detail } : {}), ...(order === undefined ? {} : { order }) };\n}\n\nfunction parseCommand(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderCommand | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].command must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].command.name',\n MAX_COMMAND_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const args = readOptionalText(\n value.args,\n 'bindings[].command.args',\n MAX_COMMAND_ARGUMENT_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_COMMAND_NAME.test(name)) {\n if (name && !SAFE_COMMAND_NAME.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].command.name must match ${SAFE_COMMAND_NAME}.`, bindingId));\n }\n return undefined;\n }\n return { name, ...(args ? { args } : {}) };\n}\n\nfunction parseExtensionAction(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderExtensionAction | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].action must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].action.name',\n MAX_ACTION_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_IDENTIFIER.test(name)) {\n if (name && !SAFE_IDENTIFIER.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].action.name must match ${SAFE_IDENTIFIER}.`, bindingId));\n }\n return undefined;\n }\n return { name };\n}\n\nfunction parseBinding(\n value: unknown,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderBinding | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[] must be an object.'));\n return undefined;\n }\n const bindingId = readRequiredText(value.id, 'bindings[].id', MAX_BINDING_ID_LENGTH, source, diagnostics);\n if (!bindingId) return undefined;\n if (!SAFE_IDENTIFIER.test(bindingId)) {\n diagnostics.push(diagnostic(source, `bindings[].id must match ${SAFE_IDENTIFIER}.`, bindingId));\n return undefined;\n }\n if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH_LENGTH) {\n diagnostics.push(\n diagnostic(source, `bindings[].path must contain between 1 and ${MAX_PATH_LENGTH} segments.`, bindingId),\n );\n return undefined;\n }\n\n const path = value.path.map((segment, index) => parseSegment(segment, source, bindingId, index, diagnostics));\n const hasCommand = Object.hasOwn(value, 'command');\n const hasAction = Object.hasOwn(value, 'action');\n if (hasCommand === hasAction) {\n diagnostics.push(diagnostic(source, 'bindings[] must contain exactly one of command or action.', bindingId));\n return undefined;\n }\n const target = hasCommand\n ? { command: parseCommand(value.command, source, bindingId, diagnostics) }\n : { action: parseExtensionAction(value.action, source, bindingId, diagnostics) };\n if (path.some((segment) => segment === undefined)) return undefined;\n if ('command' in target) {\n if (!target.command) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n command: target.command,\n };\n }\n if (!target.action) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: target.action,\n };\n}\n\nfunction parseContribution(value: unknown): ParsedContribution {\n const diagnostics: DoomLeaderDiagnostic[] = [];\n if (!isRecord(value)) {\n return { diagnostics: [diagnostic(UNKNOWN_SOURCE, 'Leader contribution must be an object.')] };\n }\n const source = readRequiredText(value.source, 'source', MAX_SOURCE_LENGTH, UNKNOWN_SOURCE, diagnostics);\n if (!source) return { diagnostics };\n if (!SAFE_SOURCE.test(source)) {\n diagnostics.push(diagnostic(source, `source must match ${SAFE_SOURCE}.`));\n return { diagnostics };\n }\n if (!Array.isArray(value.bindings)) {\n return { diagnostics: [diagnostic(source, 'bindings must be an array.')] };\n }\n\n const bindings = value.bindings.map((binding) => parseBinding(binding, source, diagnostics));\n const duplicateIds = bindings\n .flatMap((binding) => (binding ? [binding.id] : []))\n .filter((id, index, ids) => ids.indexOf(id) !== index);\n for (const id of new Set(duplicateIds)) {\n diagnostics.push(diagnostic(source, `Binding id '${id}' is duplicated.`, id));\n }\n if (diagnostics.length > 0 || bindings.some((binding) => binding === undefined)) return { diagnostics };\n\n return {\n contribution: {\n source,\n bindings: bindings as DoomLeaderBinding[],\n },\n diagnostics,\n };\n}\n\nfunction optionOrder(segment: DoomLeaderSegment): number {\n return segment.order ?? DEFAULT_OPTION_ORDER;\n}\n\nfunction cloneAction(action: DoomLeaderAction): DoomLeaderAction {\n if (action.type === 'command') return { type: 'command', command: { ...action.command } };\n if (action.type === 'app') return { type: 'app', action: action.action };\n return { type: 'extension', source: action.source, action: { ...action.action } };\n}\n\nfunction createRootNode(): LeaderNode {\n return {\n key: '',\n label: ROOT_LABEL,\n order: 0,\n owner: CORE_SOURCE,\n children: new Map(),\n };\n}\n\nfunction pathLabel(path: readonly DoomLeaderSegment[]): string {\n return `SPC ${path.map((segment) => segment.key).join(' ')}`;\n}\n\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && node.detail === segment.detail && node.order === optionOrder(segment);\n}\n\nfunction insertBinding(root: LeaderNode, binding: InternalLeaderBinding): DoomLeaderDiagnostic | undefined {\n let current = root;\n let firstMissing = -1;\n\n for (const [index, segment] of binding.path.entries()) {\n const existing = current.children.get(segment.key);\n if (!existing) {\n firstMissing = index;\n break;\n }\n if (!metadataMatches(existing, segment)) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} conflicts with metadata from ${existing.owner}.`,\n binding.id,\n );\n }\n const isLeaf = index === binding.path.length - 1;\n if ((!isLeaf && existing.action) || (isLeaf && (existing.action || existing.children.size > 0))) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path)} conflicts with an existing binding from ${existing.owner}.`,\n binding.id,\n );\n }\n current = existing;\n }\n\n const start = firstMissing === -1 ? binding.path.length : firstMissing;\n for (let index = start; index < binding.path.length; index += 1) {\n const segment = binding.path[index] as DoomLeaderSegment;\n const node: LeaderNode = {\n key: segment.key,\n label: segment.label,\n ...(segment.detail ? { detail: segment.detail } : {}),\n order: optionOrder(segment),\n owner: binding.source,\n children: new Map(),\n };\n current.children.set(segment.key, node);\n current = node;\n }\n\n current.action = cloneAction(binding.action);\n current.bindingId = binding.id;\n return undefined;\n}\n\nexport class DoomLeaderRegistry {\n private root = createRootNode();\n private readonly contributions = new Map<string, readonly DoomLeaderBinding[]>();\n private readonly listeners = new Set<() => void>();\n private diagnostics: DoomLeaderDiagnostic[] = [];\n\n constructor() {\n this.rebuild();\n }\n\n register(value: unknown): DoomLeaderRegistrationResult {\n const parsed = parseContribution(value);\n if (!parsed.contribution) return { accepted: false, diagnostics: parsed.diagnostics };\n\n if (parsed.contribution.bindings.length === 0) this.contributions.delete(parsed.contribution.source);\n else this.contributions.set(parsed.contribution.source, parsed.contribution.bindings);\n this.rebuild();\n return { accepted: true, diagnostics: this.getDiagnostics() };\n }\n\n getGroup(path: readonly string[]): DoomLeaderGroup | undefined {\n let current = this.root;\n for (const key of path) {\n const next = current.children.get(key);\n if (!next || next.action) return undefined;\n current = next;\n }\n\n // The key is the sort, at every level. A reader looking for a chord knows the\n // key, not the number some contributor picked for it, and those numbers live\n // across nine packages so no one of them can see the sequence they produce.\n // `order` survives only as group identity: co-contributors to a shared prefix\n // must still agree on it, which is what `metadataMatches` checks.\n const options = [...current.children.values()]\n .sort((left, right) => left.key.localeCompare(right.key))\n .map(\n (node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\n }),\n );\n return { label: current.label, options };\n }\n\n getDiagnostics(): DoomLeaderDiagnostic[] {\n return this.diagnostics.map((item) => ({ ...item }));\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private rebuild(): void {\n const nextRoot = createRootNode();\n const diagnostics: DoomLeaderDiagnostic[] = [];\n for (const binding of CORE_BINDINGS) {\n const issue = insertBinding(nextRoot, binding);\n if (issue) throw new Error(`Invalid core leader binding: ${issue.message}`);\n }\n\n for (const [source, bindings] of [...this.contributions.entries()].sort(([left], [right]) =>\n left.localeCompare(right),\n )) {\n for (const binding of [...bindings].sort((left, right) => left.id.localeCompare(right.id))) {\n const issue = insertBinding(nextRoot, {\n id: binding.id,\n source,\n path: binding.path,\n action:\n 'command' in binding\n ? { type: 'command', command: binding.command }\n : { type: 'extension', source, action: binding.action },\n });\n if (issue) diagnostics.push(issue);\n }\n }\n\n this.root = nextRoot;\n this.diagnostics = diagnostics;\n for (const listener of this.listeners) listener();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"leaderRegistry.cjs","names":[],"sources":["../src/leaderRegistry.ts"],"sourcesContent":["import type { AppKeybinding } from '@earendil-works/pi-coding-agent';\nimport {\n type DoomLeaderBinding,\n type DoomLeaderCommand,\n type DoomLeaderContribution,\n type DoomLeaderExtensionAction,\n type DoomLeaderSegment,\n} from './leader.ts';\n\ninterface AppLeaderAction {\n type: 'app';\n action: AppKeybinding;\n}\n\ninterface CommandLeaderAction {\n type: 'command';\n command: DoomLeaderCommand;\n}\n\ninterface ExtensionLeaderAction {\n type: 'extension';\n source: string;\n action: DoomLeaderExtensionAction;\n}\n\nexport type DoomLeaderAction = AppLeaderAction | CommandLeaderAction | ExtensionLeaderAction;\n\nexport interface DoomLeaderResolvedOption {\n key: string;\n label: string;\n detail?: string;\n hasChildren: boolean;\n action?: DoomLeaderAction;\n}\n\nexport interface DoomLeaderGroup {\n label: string;\n options: readonly DoomLeaderResolvedOption[];\n}\n\nexport interface DoomLeaderDiagnostic {\n source: string;\n bindingId?: string;\n message: string;\n}\n\nexport interface DoomLeaderRegistrationResult {\n accepted: boolean;\n diagnostics: readonly DoomLeaderDiagnostic[];\n}\n\ninterface InternalLeaderBinding {\n id: string;\n source: string;\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]];\n action: DoomLeaderAction;\n}\n\ninterface LeaderNode {\n key: string;\n label: string;\n detail?: string;\n order: number;\n owner: string;\n bindingId?: string;\n children: Map<string, LeaderNode>;\n action?: DoomLeaderAction;\n}\n\ninterface ParsedContribution {\n contribution?: DoomLeaderContribution;\n diagnostics: DoomLeaderDiagnostic[];\n}\n\nconst CORE_SOURCE = '@agimon-ai/doompi-ui';\nconst UNKNOWN_SOURCE = 'unknown';\nconst ROOT_LABEL = 'leader';\nconst DEFAULT_OPTION_ORDER = 500;\nconst MAX_OPTION_ORDER = 1000;\nconst MAX_PATH_LENGTH = 4;\nconst MAX_SOURCE_LENGTH = 128;\nconst MAX_BINDING_ID_LENGTH = 128;\nconst MAX_LABEL_LENGTH = 32;\nconst MAX_DETAIL_LENGTH = 80;\nconst MAX_COMMAND_NAME_LENGTH = 64;\nconst MAX_COMMAND_ARGUMENT_LENGTH = 256;\nconst MAX_ACTION_NAME_LENGTH = 128;\nconst SAFE_KEY = /^[a-z0-9]$/;\nconst SAFE_IDENTIFIER = /^[a-z0-9][a-z0-9._:-]*$/;\nconst SAFE_SOURCE = /^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/;\nconst SAFE_COMMAND_NAME = /^[a-z0-9][a-z0-9:_-]*$/;\nconst C0_CONTROL_MAX = 31;\nconst DELETE_CHARACTER = 127;\nconst MODEL_GROUP = { key: 'm', label: 'models', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', order: 90 } as const;\n\nfunction coreBinding(\n id: string,\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: DoomLeaderAction,\n): InternalLeaderBinding {\n return { id, source: CORE_SOURCE, path, action };\n}\n\nconst CORE_BINDINGS: readonly InternalLeaderBinding[] = [\n coreBinding('model.select', [MODEL_GROUP, { key: 'm', label: 'select', detail: 'choose model', order: 10 }], {\n type: 'app',\n action: 'app.model.select',\n }),\n coreBinding('model.next', [MODEL_GROUP, { key: 'n', label: 'next', detail: 'cycle model', order: 20 }], {\n type: 'app',\n action: 'app.model.cycleForward',\n }),\n coreBinding('thinking.cycle', [MODEL_GROUP, { key: 't', label: 'thinking', detail: 'cycle level', order: 30 }], {\n type: 'app',\n action: 'app.thinking.cycle',\n }),\n coreBinding('tools.open', [EXTENSION_GROUP, { key: 't', label: 'tools', detail: 'browse tools', order: 20 }], {\n type: 'command',\n command: { name: 'tools' },\n }),\n // Core rather than contributed, so no extension owns the key and the panel is\n // reachable even when nothing has contributed a section to it yet.\n coreBinding('config.open', [EXTENSION_GROUP, { key: 'c', label: 'config', detail: 'settings', order: 30 }], {\n type: 'command',\n command: { name: 'config' },\n }),\n coreBinding('session.new', [SESSION_GROUP, { key: 'n', label: 'new', detail: 'fresh session', order: 10 }], {\n type: 'app',\n action: 'app.session.new',\n }),\n coreBinding('session.resume', [SESSION_GROUP, { key: 'r', label: 'resume', detail: 'open history', order: 20 }], {\n type: 'app',\n action: 'app.session.resume',\n }),\n coreBinding('session.tree', [SESSION_GROUP, { key: 't', label: 'tree', detail: 'branch view', order: 30 }], {\n type: 'app',\n action: 'app.session.tree',\n }),\n coreBinding('session.fork', [SESSION_GROUP, { key: 'f', label: 'fork', detail: 'from selection', order: 40 }], {\n type: 'app',\n action: 'app.session.fork',\n }),\n coreBinding('editor.external', [EXTENSION_GROUP, { key: 'e', label: 'external', detail: 'open editor', order: 10 }], {\n type: 'app',\n action: 'app.editor.external',\n }),\n coreBinding('help.hotkeys', [HELP_GROUP, { key: 'h', label: 'hotkeys', detail: 'show bindings' }], {\n type: 'command',\n command: { name: 'hotkeys' },\n }),\n coreBinding('app.exit', [QUIT_GROUP, { key: 'q', label: 'quit', detail: 'exit Pi' }], {\n type: 'app',\n action: 'app.exit',\n }),\n];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction diagnostic(source: string, message: string, bindingId?: string): DoomLeaderDiagnostic {\n return { source, message, ...(bindingId ? { bindingId } : {}) };\n}\n\nfunction containsControlCharacter(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const codeUnit = value.charCodeAt(index);\n if (codeUnit <= C0_CONTROL_MAX || codeUnit === DELETE_CHARACTER) return true;\n }\n return false;\n}\n\nfunction readRequiredText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId?: string,\n): string | undefined {\n if (typeof value !== 'string' || !value.trim()) {\n diagnostics.push(diagnostic(source, `${field} must be a non-empty string.`, bindingId));\n return undefined;\n }\n const text = value.trim();\n if (text.length > maximumLength || containsControlCharacter(text)) {\n diagnostics.push(\n diagnostic(\n source,\n `${field} must be at most ${maximumLength} characters and contain no control characters.`,\n bindingId,\n ),\n );\n return undefined;\n }\n return text;\n}\n\nfunction readOptionalText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId: string,\n): string | undefined {\n if (value === undefined) return undefined;\n return readRequiredText(value, field, maximumLength, source, diagnostics, bindingId);\n}\n\nfunction parseSegment(\n value: unknown,\n source: string,\n bindingId: string,\n index: number,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderSegment | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}] must be an object.`, bindingId));\n return undefined;\n }\n\n const key = readRequiredText(value.key, `bindings[].path[${index}].key`, 1, source, diagnostics, bindingId);\n const label = readRequiredText(\n value.label,\n `bindings[].path[${index}].label`,\n MAX_LABEL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const detail = readOptionalText(\n value.detail,\n `bindings[].path[${index}].detail`,\n MAX_DETAIL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n let order: number | undefined;\n if (value.order !== undefined) {\n if (!Number.isInteger(value.order) || (value.order as number) < 0 || (value.order as number) > MAX_OPTION_ORDER) {\n diagnostics.push(\n diagnostic(\n source,\n `bindings[].path[${index}].order must be an integer from 0 to ${MAX_OPTION_ORDER}.`,\n bindingId,\n ),\n );\n } else {\n order = value.order as number;\n }\n }\n if (!key || !label || !SAFE_KEY.test(key)) {\n if (key && !SAFE_KEY.test(key)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}].key must match ${SAFE_KEY}.`, bindingId));\n }\n return undefined;\n }\n return { key, label, ...(detail ? { detail } : {}), ...(order === undefined ? {} : { order }) };\n}\n\nfunction parseCommand(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderCommand | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].command must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].command.name',\n MAX_COMMAND_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const args = readOptionalText(\n value.args,\n 'bindings[].command.args',\n MAX_COMMAND_ARGUMENT_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_COMMAND_NAME.test(name)) {\n if (name && !SAFE_COMMAND_NAME.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].command.name must match ${SAFE_COMMAND_NAME}.`, bindingId));\n }\n return undefined;\n }\n return { name, ...(args ? { args } : {}) };\n}\n\nfunction parseExtensionAction(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderExtensionAction | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].action must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].action.name',\n MAX_ACTION_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_IDENTIFIER.test(name)) {\n if (name && !SAFE_IDENTIFIER.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].action.name must match ${SAFE_IDENTIFIER}.`, bindingId));\n }\n return undefined;\n }\n return { name };\n}\n\nfunction parseBinding(\n value: unknown,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderBinding | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[] must be an object.'));\n return undefined;\n }\n const bindingId = readRequiredText(value.id, 'bindings[].id', MAX_BINDING_ID_LENGTH, source, diagnostics);\n if (!bindingId) return undefined;\n if (!SAFE_IDENTIFIER.test(bindingId)) {\n diagnostics.push(diagnostic(source, `bindings[].id must match ${SAFE_IDENTIFIER}.`, bindingId));\n return undefined;\n }\n if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH_LENGTH) {\n diagnostics.push(\n diagnostic(source, `bindings[].path must contain between 1 and ${MAX_PATH_LENGTH} segments.`, bindingId),\n );\n return undefined;\n }\n\n const path = value.path.map((segment, index) => parseSegment(segment, source, bindingId, index, diagnostics));\n const hasCommand = Object.hasOwn(value, 'command');\n const hasAction = Object.hasOwn(value, 'action');\n if (hasCommand === hasAction) {\n diagnostics.push(diagnostic(source, 'bindings[] must contain exactly one of command or action.', bindingId));\n return undefined;\n }\n const target = hasCommand\n ? { command: parseCommand(value.command, source, bindingId, diagnostics) }\n : { action: parseExtensionAction(value.action, source, bindingId, diagnostics) };\n if (path.some((segment) => segment === undefined)) return undefined;\n if ('command' in target) {\n if (!target.command) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n command: target.command,\n };\n }\n if (!target.action) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: target.action,\n };\n}\n\nfunction parseContribution(value: unknown): ParsedContribution {\n const diagnostics: DoomLeaderDiagnostic[] = [];\n if (!isRecord(value)) {\n return { diagnostics: [diagnostic(UNKNOWN_SOURCE, 'Leader contribution must be an object.')] };\n }\n const source = readRequiredText(value.source, 'source', MAX_SOURCE_LENGTH, UNKNOWN_SOURCE, diagnostics);\n if (!source) return { diagnostics };\n if (!SAFE_SOURCE.test(source)) {\n diagnostics.push(diagnostic(source, `source must match ${SAFE_SOURCE}.`));\n return { diagnostics };\n }\n if (!Array.isArray(value.bindings)) {\n return { diagnostics: [diagnostic(source, 'bindings must be an array.')] };\n }\n\n const bindings = value.bindings.map((binding) => parseBinding(binding, source, diagnostics));\n const duplicateIds = bindings\n .flatMap((binding) => (binding ? [binding.id] : []))\n .filter((id, index, ids) => ids.indexOf(id) !== index);\n for (const id of new Set(duplicateIds)) {\n diagnostics.push(diagnostic(source, `Binding id '${id}' is duplicated.`, id));\n }\n if (diagnostics.length > 0 || bindings.some((binding) => binding === undefined)) return { diagnostics };\n\n return {\n contribution: {\n source,\n bindings: bindings as DoomLeaderBinding[],\n },\n diagnostics,\n };\n}\n\nfunction optionOrder(segment: DoomLeaderSegment): number {\n return segment.order ?? DEFAULT_OPTION_ORDER;\n}\n\nfunction cloneAction(action: DoomLeaderAction): DoomLeaderAction {\n if (action.type === 'command') return { type: 'command', command: { ...action.command } };\n if (action.type === 'app') return { type: 'app', action: action.action };\n return { type: 'extension', source: action.source, action: { ...action.action } };\n}\n\nfunction createRootNode(): LeaderNode {\n return {\n key: '',\n label: ROOT_LABEL,\n order: 0,\n owner: CORE_SOURCE,\n children: new Map(),\n };\n}\n\nfunction pathLabel(path: readonly DoomLeaderSegment[]): string {\n return `SPC ${path.map((segment) => segment.key).join(' ')}`;\n}\n\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && node.detail === segment.detail && node.order === optionOrder(segment);\n}\n\nfunction insertBinding(root: LeaderNode, binding: InternalLeaderBinding): DoomLeaderDiagnostic | undefined {\n let current = root;\n let firstMissing = -1;\n\n for (const [index, segment] of binding.path.entries()) {\n const existing = current.children.get(segment.key);\n if (!existing) {\n firstMissing = index;\n break;\n }\n if (!metadataMatches(existing, segment)) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} conflicts with metadata from ${existing.owner}.`,\n binding.id,\n );\n }\n const isLeaf = index === binding.path.length - 1;\n if ((!isLeaf && existing.action) || (isLeaf && (existing.action || existing.children.size > 0))) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path)} conflicts with an existing binding from ${existing.owner}.`,\n binding.id,\n );\n }\n current = existing;\n }\n\n const start = firstMissing === -1 ? binding.path.length : firstMissing;\n for (let index = start; index < binding.path.length; index += 1) {\n const segment = binding.path[index] as DoomLeaderSegment;\n const node: LeaderNode = {\n key: segment.key,\n label: segment.label,\n ...(segment.detail ? { detail: segment.detail } : {}),\n order: optionOrder(segment),\n owner: binding.source,\n children: new Map(),\n };\n current.children.set(segment.key, node);\n current = node;\n }\n\n current.action = cloneAction(binding.action);\n current.bindingId = binding.id;\n return undefined;\n}\n\nexport class DoomLeaderRegistry {\n private root = createRootNode();\n private readonly contributions = new Map<string, readonly DoomLeaderBinding[]>();\n private readonly listeners = new Set<() => void>();\n private diagnostics: DoomLeaderDiagnostic[] = [];\n private dirty = false;\n\n constructor() {\n this.rebuild();\n }\n\n register(value: unknown): DoomLeaderRegistrationResult {\n const result = this.apply(value);\n if (!result.accepted) return result;\n this.rebuild();\n return { accepted: true, diagnostics: this.getDiagnostics() };\n }\n\n /** Records a contribution without rebuilding the whole tree immediately. */\n registerDeferred(value: unknown): DoomLeaderRegistrationResult {\n return this.apply(value);\n }\n\n private apply(value: unknown): DoomLeaderRegistrationResult {\n const parsed = parseContribution(value);\n if (!parsed.contribution) return { accepted: false, diagnostics: parsed.diagnostics };\n\n if (parsed.contribution.bindings.length === 0) this.contributions.delete(parsed.contribution.source);\n else this.contributions.set(parsed.contribution.source, parsed.contribution.bindings);\n this.dirty = true;\n return { accepted: true, diagnostics: parsed.diagnostics };\n }\n\n /** Rebuilds one batch and returns its deterministic conflict diagnostics. */\n flush(): DoomLeaderDiagnostic[] {\n if (this.dirty) this.rebuild();\n return this.diagnostics.map((item) => ({ ...item }));\n }\n\n getGroup(path: readonly string[]): DoomLeaderGroup | undefined {\n this.flush();\n let current = this.root;\n for (const key of path) {\n const next = current.children.get(key);\n if (!next || next.action) return undefined;\n current = next;\n }\n\n // The key is the sort, at every level. A reader looking for a chord knows the\n // key, not the number some contributor picked for it, and those numbers live\n // across nine packages so no one of them can see the sequence they produce.\n // `order` survives only as group identity: co-contributors to a shared prefix\n // must still agree on it, which is what `metadataMatches` checks.\n const options = [...current.children.values()]\n .sort((left, right) => left.key.localeCompare(right.key))\n .map(\n (node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\n }),\n );\n return { label: current.label, options };\n }\n\n getDiagnostics(): DoomLeaderDiagnostic[] {\n return this.flush();\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private rebuild(): void {\n const nextRoot = createRootNode();\n const diagnostics: DoomLeaderDiagnostic[] = [];\n for (const binding of CORE_BINDINGS) {\n const issue = insertBinding(nextRoot, binding);\n if (issue) throw new Error(`Invalid core leader binding: ${issue.message}`);\n }\n\n for (const [source, bindings] of [...this.contributions.entries()].sort(([left], [right]) =>\n left.localeCompare(right),\n )) {\n for (const binding of [...bindings].sort((left, right) => left.id.localeCompare(right.id))) {\n const issue = insertBinding(nextRoot, {\n id: binding.id,\n source,\n path: binding.path,\n action:\n 'command' in binding\n ? { type: 'command', command: binding.command }\n : { type: 'extension', source, action: binding.action },\n });\n if (issue) diagnostics.push(issue);\n }\n }\n\n this.root = nextRoot;\n this.diagnostics = diagnostics;\n this.dirty = false;\n for (const listener of this.listeners) listener();\n }\n}\n"],"mappings":"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"}
@@ -40,8 +40,14 @@ declare class DoomLeaderRegistry {
40
40
  private readonly contributions;
41
41
  private readonly listeners;
42
42
  private diagnostics;
43
+ private dirty;
43
44
  constructor();
44
45
  register(value: unknown): DoomLeaderRegistrationResult;
46
+ /** Records a contribution without rebuilding the whole tree immediately. */
47
+ registerDeferred(value: unknown): DoomLeaderRegistrationResult;
48
+ private apply;
49
+ /** Rebuilds one batch and returns its deterministic conflict diagnostics. */
50
+ flush(): DoomLeaderDiagnostic[];
45
51
  getGroup(path: readonly string[]): DoomLeaderGroup | undefined;
46
52
  getDiagnostics(): DoomLeaderDiagnostic[];
47
53
  subscribe(listener: () => void): () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"leaderRegistry.d.cts","names":[],"sources":["../src/leaderRegistry.ts"],"mappings":";;;UASU;EACR;EACA,QAAQ;;UAGA;EACR;EACA,SAAS;;UAGD;EACR;EACA;EACA,QAAQ;;KAGE,mBAAmB,kBAAkB,sBAAsB;UAEtD;EACf;EACA;EACA;EACA;EACA,SAAS;;UAGM;EACf;EACA,kBAAkB;;UAGH;EACf;EACA;EACA;;UAGe;EACf;EACA,sBAAsB;;cAsbX;UACH;mBACS;mBACA;UACT;;EAMR,SAAS,iBAAiB;EAU1B,SAAS,0BAA0B;EA2BnC,kBAAkB;EAIlB,UAAU;UAKF"}
1
+ {"version":3,"file":"leaderRegistry.d.cts","names":[],"sources":["../src/leaderRegistry.ts"],"mappings":";;;UASU;EACR;EACA,QAAQ;;UAGA;EACR;EACA,SAAS;;UAGD;EACR;EACA;EACA,QAAQ;;KAGE,mBAAmB,kBAAkB,sBAAsB;UAEtD;EACf;EACA;EACA;EACA;EACA,SAAS;;UAGM;EACf;EACA,kBAAkB;;UAGH;EACf;EACA;EACA;;UAGe;EACf;EACA,sBAAsB;;cAsbX;UACH;mBACS;mBACA;UACT;UACA;;EAMR,SAAS,iBAAiB;;EAQ1B,iBAAiB,iBAAiB;UAI1B;;EAWR,SAAS;EAKT,SAAS,0BAA0B;EA4BnC,kBAAkB;EAIlB,UAAU;UAKF"}
@@ -40,8 +40,14 @@ declare class DoomLeaderRegistry {
40
40
  private readonly contributions;
41
41
  private readonly listeners;
42
42
  private diagnostics;
43
+ private dirty;
43
44
  constructor();
44
45
  register(value: unknown): DoomLeaderRegistrationResult;
46
+ /** Records a contribution without rebuilding the whole tree immediately. */
47
+ registerDeferred(value: unknown): DoomLeaderRegistrationResult;
48
+ private apply;
49
+ /** Rebuilds one batch and returns its deterministic conflict diagnostics. */
50
+ flush(): DoomLeaderDiagnostic[];
45
51
  getGroup(path: readonly string[]): DoomLeaderGroup | undefined;
46
52
  getDiagnostics(): DoomLeaderDiagnostic[];
47
53
  subscribe(listener: () => void): () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"leaderRegistry.d.mts","names":[],"sources":["../src/leaderRegistry.ts"],"mappings":";;;UASU;EACR;EACA,QAAQ;;UAGA;EACR;EACA,SAAS;;UAGD;EACR;EACA;EACA,QAAQ;;KAGE,mBAAmB,kBAAkB,sBAAsB;UAEtD;EACf;EACA;EACA;EACA;EACA,SAAS;;UAGM;EACf;EACA,kBAAkB;;UAGH;EACf;EACA;EACA;;UAGe;EACf;EACA,sBAAsB;;cAsbX;UACH;mBACS;mBACA;UACT;;EAMR,SAAS,iBAAiB;EAU1B,SAAS,0BAA0B;EA2BnC,kBAAkB;EAIlB,UAAU;UAKF"}
1
+ {"version":3,"file":"leaderRegistry.d.mts","names":[],"sources":["../src/leaderRegistry.ts"],"mappings":";;;UASU;EACR;EACA,QAAQ;;UAGA;EACR;EACA,SAAS;;UAGD;EACR;EACA;EACA,QAAQ;;KAGE,mBAAmB,kBAAkB,sBAAsB;UAEtD;EACf;EACA;EACA;EACA;EACA,SAAS;;UAGM;EACf;EACA,kBAAkB;;UAGH;EACf;EACA;EACA;;UAGe;EACf;EACA,sBAAsB;;cAsbX;UACH;mBACS;mBACA;UACT;UACA;;EAMR,SAAS,iBAAiB;;EAQ1B,iBAAiB,iBAAiB;UAI1B;;EAWR,SAAS;EAKT,SAAS,0BAA0B;EA4BnC,kBAAkB;EAIlB,UAAU;UAKF"}
@@ -1,2 +1,2 @@
1
- const e=`@agimon-ai/doompi-ui`,t=`unknown`,n=1e3,r=/^[a-z0-9]$/,i=/^[a-z0-9][a-z0-9._:-]*$/,a=/^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/,o=/^[a-z0-9][a-z0-9:_-]*$/,s={key:`m`,label:`models`,order:10},c={key:`s`,label:`sessions`,order:30},l={key:`e`,label:`extension`,order:50},u={key:`h`,label:`help`,order:70},d={key:`q`,label:`quit`,order:90};function f(t,n,r){return{id:t,source:e,path:n,action:r}}const p=[f(`model.select`,[s,{key:`m`,label:`select`,detail:`choose model`,order:10}],{type:`app`,action:`app.model.select`}),f(`model.next`,[s,{key:`n`,label:`next`,detail:`cycle model`,order:20}],{type:`app`,action:`app.model.cycleForward`}),f(`thinking.cycle`,[s,{key:`t`,label:`thinking`,detail:`cycle level`,order:30}],{type:`app`,action:`app.thinking.cycle`}),f(`tools.open`,[l,{key:`t`,label:`tools`,detail:`browse tools`,order:20}],{type:`command`,command:{name:`tools`}}),f(`config.open`,[l,{key:`c`,label:`config`,detail:`settings`,order:30}],{type:`command`,command:{name:`config`}}),f(`session.new`,[c,{key:`n`,label:`new`,detail:`fresh session`,order:10}],{type:`app`,action:`app.session.new`}),f(`session.resume`,[c,{key:`r`,label:`resume`,detail:`open history`,order:20}],{type:`app`,action:`app.session.resume`}),f(`session.tree`,[c,{key:`t`,label:`tree`,detail:`branch view`,order:30}],{type:`app`,action:`app.session.tree`}),f(`session.fork`,[c,{key:`f`,label:`fork`,detail:`from selection`,order:40}],{type:`app`,action:`app.session.fork`}),f(`editor.external`,[l,{key:`e`,label:`external`,detail:`open editor`,order:10}],{type:`app`,action:`app.editor.external`}),f(`help.hotkeys`,[u,{key:`h`,label:`hotkeys`,detail:`show bindings`}],{type:`command`,command:{name:`hotkeys`}}),f(`app.exit`,[d,{key:`q`,label:`quit`,detail:`exit Pi`}],{type:`app`,action:`app.exit`})];function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e,t,n){return{source:e,message:t,...n?{bindingId:n}:{}}}function g(e){for(let t=0;t<e.length;t+=1){let n=e.charCodeAt(t);if(n<=31||n===127)return!0}return!1}function _(e,t,n,r,i,a){if(typeof e!=`string`||!e.trim()){i.push(h(r,`${t} must be a non-empty string.`,a));return}let o=e.trim();if(o.length>n||g(o)){i.push(h(r,`${t} must be at most ${n} characters and contain no control characters.`,a));return}return o}function v(e,t,n,r,i,a){if(e!==void 0)return _(e,t,n,r,i,a)}function y(e,t,i,a,o){if(!m(e)){o.push(h(t,`bindings[].path[${a}] must be an object.`,i));return}let s=_(e.key,`bindings[].path[${a}].key`,1,t,o,i),c=_(e.label,`bindings[].path[${a}].label`,32,t,o,i),l=v(e.detail,`bindings[].path[${a}].detail`,80,t,o,i),u;if(e.order!==void 0&&(!Number.isInteger(e.order)||e.order<0||e.order>n?o.push(h(t,`bindings[].path[${a}].order must be an integer from 0 to ${n}.`,i)):u=e.order),!s||!c||!r.test(s)){s&&!r.test(s)&&o.push(h(t,`bindings[].path[${a}].key must match ${r}.`,i));return}return{key:s,label:c,...l?{detail:l}:{},...u===void 0?{}:{order:u}}}function b(e,t,n,r){if(!m(e)){r.push(h(t,`bindings[].command must be an object.`,n));return}let i=_(e.name,`bindings[].command.name`,64,t,r,n),a=v(e.args,`bindings[].command.args`,256,t,r,n);if(!i||!o.test(i)){i&&!o.test(i)&&r.push(h(t,`bindings[].command.name must match ${o}.`,n));return}return{name:i,...a?{args:a}:{}}}function x(e,t,n,r){if(!m(e)){r.push(h(t,`bindings[].action must be an object.`,n));return}let a=_(e.name,`bindings[].action.name`,128,t,r,n);if(!a||!i.test(a)){a&&!i.test(a)&&r.push(h(t,`bindings[].action.name must match ${i}.`,n));return}return{name:a}}function S(e,t,n){if(!m(e)){n.push(h(t,`bindings[] must be an object.`));return}let r=_(e.id,`bindings[].id`,128,t,n);if(!r)return;if(!i.test(r)){n.push(h(t,`bindings[].id must match ${i}.`,r));return}if(!Array.isArray(e.path)||e.path.length===0||e.path.length>4){n.push(h(t,`bindings[].path must contain between 1 and 4 segments.`,r));return}let a=e.path.map((e,i)=>y(e,t,r,i,n)),o=Object.hasOwn(e,`command`);if(o===Object.hasOwn(e,`action`)){n.push(h(t,`bindings[] must contain exactly one of command or action.`,r));return}let s=o?{command:b(e.command,t,r,n)}:{action:x(e.action,t,r,n)};if(!a.some(e=>e===void 0)){if(`command`in s)return s.command?{id:r,path:a,command:s.command}:void 0;if(s.action)return{id:r,path:a,action:s.action}}}function C(e){let n=[];if(!m(e))return{diagnostics:[h(t,`Leader contribution must be an object.`)]};let r=_(e.source,`source`,128,t,n);if(!r)return{diagnostics:n};if(!a.test(r))return n.push(h(r,`source must match ${a}.`)),{diagnostics:n};if(!Array.isArray(e.bindings))return{diagnostics:[h(r,`bindings must be an array.`)]};let i=e.bindings.map(e=>S(e,r,n)),o=i.flatMap(e=>e?[e.id]:[]).filter((e,t,n)=>n.indexOf(e)!==t);for(let e of new Set(o))n.push(h(r,`Binding id '${e}' is duplicated.`,e));return n.length>0||i.some(e=>e===void 0)?{diagnostics:n}:{contribution:{source:r,bindings:i},diagnostics:n}}function w(e){return e.order??500}function T(e){return e.type===`command`?{type:`command`,command:{...e.command}}:e.type===`app`?{type:`app`,action:e.action}:{type:`extension`,source:e.source,action:{...e.action}}}function E(){return{key:``,label:`leader`,order:0,owner:e,children:new Map}}function D(e){return`SPC ${e.map(e=>e.key).join(` `)}`}function O(e,t){return e.label===t.label&&e.detail===t.detail&&e.order===w(t)}function k(e,t){let n=e,r=-1;for(let[e,i]of t.path.entries()){let a=n.children.get(i.key);if(!a){r=e;break}if(!O(a,i))return h(t.source,`${D(t.path.slice(0,e+1))} conflicts with metadata from ${a.owner}.`,t.id);let o=e===t.path.length-1;if(!o&&a.action||o&&(a.action||a.children.size>0))return h(t.source,`${D(t.path)} conflicts with an existing binding from ${a.owner}.`,t.id);n=a}let i=r===-1?t.path.length:r;for(let e=i;e<t.path.length;e+=1){let r=t.path[e],i={key:r.key,label:r.label,...r.detail?{detail:r.detail}:{},order:w(r),owner:t.source,children:new Map};n.children.set(r.key,i),n=i}n.action=T(t.action),n.bindingId=t.id}var A=class{root=E();contributions=new Map;listeners=new Set;diagnostics=[];constructor(){this.rebuild()}register(e){let t=C(e);return t.contribution?(t.contribution.bindings.length===0?this.contributions.delete(t.contribution.source):this.contributions.set(t.contribution.source,t.contribution.bindings),this.rebuild(),{accepted:!0,diagnostics:this.getDiagnostics()}):{accepted:!1,diagnostics:t.diagnostics}}getGroup(e){let t=this.root;for(let n of e){let e=t.children.get(n);if(!e||e.action)return;t=e}let n=[...t.children.values()].sort((e,t)=>e.key.localeCompare(t.key)).map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{},hasChildren:e.children.size>0,...e.action?{action:T(e.action)}:{}}));return{label:t.label,options:n}}getDiagnostics(){return this.diagnostics.map(e=>({...e}))}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}rebuild(){let e=E(),t=[];for(let t of p){let n=k(e,t);if(n)throw Error(`Invalid core leader binding: ${n.message}`)}for(let[n,r]of[...this.contributions.entries()].sort(([e],[t])=>e.localeCompare(t)))for(let i of[...r].sort((e,t)=>e.id.localeCompare(t.id))){let r=k(e,{id:i.id,source:n,path:i.path,action:`command`in i?{type:`command`,command:i.command}:{type:`extension`,source:n,action:i.action}});r&&t.push(r)}this.root=e,this.diagnostics=t;for(let e of this.listeners)e()}};export{A as DoomLeaderRegistry};
1
+ const e=`@agimon-ai/doompi-ui`,t=`unknown`,n=1e3,r=/^[a-z0-9]$/,i=/^[a-z0-9][a-z0-9._:-]*$/,a=/^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/,o=/^[a-z0-9][a-z0-9:_-]*$/,s={key:`m`,label:`models`,order:10},c={key:`s`,label:`sessions`,order:30},l={key:`e`,label:`extension`,order:50},u={key:`h`,label:`help`,order:70},d={key:`q`,label:`quit`,order:90};function f(t,n,r){return{id:t,source:e,path:n,action:r}}const p=[f(`model.select`,[s,{key:`m`,label:`select`,detail:`choose model`,order:10}],{type:`app`,action:`app.model.select`}),f(`model.next`,[s,{key:`n`,label:`next`,detail:`cycle model`,order:20}],{type:`app`,action:`app.model.cycleForward`}),f(`thinking.cycle`,[s,{key:`t`,label:`thinking`,detail:`cycle level`,order:30}],{type:`app`,action:`app.thinking.cycle`}),f(`tools.open`,[l,{key:`t`,label:`tools`,detail:`browse tools`,order:20}],{type:`command`,command:{name:`tools`}}),f(`config.open`,[l,{key:`c`,label:`config`,detail:`settings`,order:30}],{type:`command`,command:{name:`config`}}),f(`session.new`,[c,{key:`n`,label:`new`,detail:`fresh session`,order:10}],{type:`app`,action:`app.session.new`}),f(`session.resume`,[c,{key:`r`,label:`resume`,detail:`open history`,order:20}],{type:`app`,action:`app.session.resume`}),f(`session.tree`,[c,{key:`t`,label:`tree`,detail:`branch view`,order:30}],{type:`app`,action:`app.session.tree`}),f(`session.fork`,[c,{key:`f`,label:`fork`,detail:`from selection`,order:40}],{type:`app`,action:`app.session.fork`}),f(`editor.external`,[l,{key:`e`,label:`external`,detail:`open editor`,order:10}],{type:`app`,action:`app.editor.external`}),f(`help.hotkeys`,[u,{key:`h`,label:`hotkeys`,detail:`show bindings`}],{type:`command`,command:{name:`hotkeys`}}),f(`app.exit`,[d,{key:`q`,label:`quit`,detail:`exit Pi`}],{type:`app`,action:`app.exit`})];function m(e){return typeof e==`object`&&!!e&&!Array.isArray(e)}function h(e,t,n){return{source:e,message:t,...n?{bindingId:n}:{}}}function g(e){for(let t=0;t<e.length;t+=1){let n=e.charCodeAt(t);if(n<=31||n===127)return!0}return!1}function _(e,t,n,r,i,a){if(typeof e!=`string`||!e.trim()){i.push(h(r,`${t} must be a non-empty string.`,a));return}let o=e.trim();if(o.length>n||g(o)){i.push(h(r,`${t} must be at most ${n} characters and contain no control characters.`,a));return}return o}function v(e,t,n,r,i,a){if(e!==void 0)return _(e,t,n,r,i,a)}function y(e,t,i,a,o){if(!m(e)){o.push(h(t,`bindings[].path[${a}] must be an object.`,i));return}let s=_(e.key,`bindings[].path[${a}].key`,1,t,o,i),c=_(e.label,`bindings[].path[${a}].label`,32,t,o,i),l=v(e.detail,`bindings[].path[${a}].detail`,80,t,o,i),u;if(e.order!==void 0&&(!Number.isInteger(e.order)||e.order<0||e.order>n?o.push(h(t,`bindings[].path[${a}].order must be an integer from 0 to ${n}.`,i)):u=e.order),!s||!c||!r.test(s)){s&&!r.test(s)&&o.push(h(t,`bindings[].path[${a}].key must match ${r}.`,i));return}return{key:s,label:c,...l?{detail:l}:{},...u===void 0?{}:{order:u}}}function b(e,t,n,r){if(!m(e)){r.push(h(t,`bindings[].command must be an object.`,n));return}let i=_(e.name,`bindings[].command.name`,64,t,r,n),a=v(e.args,`bindings[].command.args`,256,t,r,n);if(!i||!o.test(i)){i&&!o.test(i)&&r.push(h(t,`bindings[].command.name must match ${o}.`,n));return}return{name:i,...a?{args:a}:{}}}function x(e,t,n,r){if(!m(e)){r.push(h(t,`bindings[].action must be an object.`,n));return}let a=_(e.name,`bindings[].action.name`,128,t,r,n);if(!a||!i.test(a)){a&&!i.test(a)&&r.push(h(t,`bindings[].action.name must match ${i}.`,n));return}return{name:a}}function S(e,t,n){if(!m(e)){n.push(h(t,`bindings[] must be an object.`));return}let r=_(e.id,`bindings[].id`,128,t,n);if(!r)return;if(!i.test(r)){n.push(h(t,`bindings[].id must match ${i}.`,r));return}if(!Array.isArray(e.path)||e.path.length===0||e.path.length>4){n.push(h(t,`bindings[].path must contain between 1 and 4 segments.`,r));return}let a=e.path.map((e,i)=>y(e,t,r,i,n)),o=Object.hasOwn(e,`command`);if(o===Object.hasOwn(e,`action`)){n.push(h(t,`bindings[] must contain exactly one of command or action.`,r));return}let s=o?{command:b(e.command,t,r,n)}:{action:x(e.action,t,r,n)};if(!a.some(e=>e===void 0)){if(`command`in s)return s.command?{id:r,path:a,command:s.command}:void 0;if(s.action)return{id:r,path:a,action:s.action}}}function C(e){let n=[];if(!m(e))return{diagnostics:[h(t,`Leader contribution must be an object.`)]};let r=_(e.source,`source`,128,t,n);if(!r)return{diagnostics:n};if(!a.test(r))return n.push(h(r,`source must match ${a}.`)),{diagnostics:n};if(!Array.isArray(e.bindings))return{diagnostics:[h(r,`bindings must be an array.`)]};let i=e.bindings.map(e=>S(e,r,n)),o=i.flatMap(e=>e?[e.id]:[]).filter((e,t,n)=>n.indexOf(e)!==t);for(let e of new Set(o))n.push(h(r,`Binding id '${e}' is duplicated.`,e));return n.length>0||i.some(e=>e===void 0)?{diagnostics:n}:{contribution:{source:r,bindings:i},diagnostics:n}}function w(e){return e.order??500}function T(e){return e.type===`command`?{type:`command`,command:{...e.command}}:e.type===`app`?{type:`app`,action:e.action}:{type:`extension`,source:e.source,action:{...e.action}}}function E(){return{key:``,label:`leader`,order:0,owner:e,children:new Map}}function D(e){return`SPC ${e.map(e=>e.key).join(` `)}`}function O(e,t){return e.label===t.label&&e.detail===t.detail&&e.order===w(t)}function k(e,t){let n=e,r=-1;for(let[e,i]of t.path.entries()){let a=n.children.get(i.key);if(!a){r=e;break}if(!O(a,i))return h(t.source,`${D(t.path.slice(0,e+1))} conflicts with metadata from ${a.owner}.`,t.id);let o=e===t.path.length-1;if(!o&&a.action||o&&(a.action||a.children.size>0))return h(t.source,`${D(t.path)} conflicts with an existing binding from ${a.owner}.`,t.id);n=a}let i=r===-1?t.path.length:r;for(let e=i;e<t.path.length;e+=1){let r=t.path[e],i={key:r.key,label:r.label,...r.detail?{detail:r.detail}:{},order:w(r),owner:t.source,children:new Map};n.children.set(r.key,i),n=i}n.action=T(t.action),n.bindingId=t.id}var A=class{root=E();contributions=new Map;listeners=new Set;diagnostics=[];dirty=!1;constructor(){this.rebuild()}register(e){let t=this.apply(e);return t.accepted?(this.rebuild(),{accepted:!0,diagnostics:this.getDiagnostics()}):t}registerDeferred(e){return this.apply(e)}apply(e){let t=C(e);return t.contribution?(t.contribution.bindings.length===0?this.contributions.delete(t.contribution.source):this.contributions.set(t.contribution.source,t.contribution.bindings),this.dirty=!0,{accepted:!0,diagnostics:t.diagnostics}):{accepted:!1,diagnostics:t.diagnostics}}flush(){return this.dirty&&this.rebuild(),this.diagnostics.map(e=>({...e}))}getGroup(e){this.flush();let t=this.root;for(let n of e){let e=t.children.get(n);if(!e||e.action)return;t=e}let n=[...t.children.values()].sort((e,t)=>e.key.localeCompare(t.key)).map(e=>({key:e.key,label:e.label,...e.detail?{detail:e.detail}:{},hasChildren:e.children.size>0,...e.action?{action:T(e.action)}:{}}));return{label:t.label,options:n}}getDiagnostics(){return this.flush()}subscribe(e){return this.listeners.add(e),()=>this.listeners.delete(e)}rebuild(){let e=E(),t=[];for(let t of p){let n=k(e,t);if(n)throw Error(`Invalid core leader binding: ${n.message}`)}for(let[n,r]of[...this.contributions.entries()].sort(([e],[t])=>e.localeCompare(t)))for(let i of[...r].sort((e,t)=>e.id.localeCompare(t.id))){let r=k(e,{id:i.id,source:n,path:i.path,action:`command`in i?{type:`command`,command:i.command}:{type:`extension`,source:n,action:i.action}});r&&t.push(r)}this.root=e,this.diagnostics=t,this.dirty=!1;for(let e of this.listeners)e()}};export{A as DoomLeaderRegistry};
2
2
  //# sourceMappingURL=leaderRegistry.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"leaderRegistry.mjs","names":[],"sources":["../src/leaderRegistry.ts"],"sourcesContent":["import type { AppKeybinding } from '@earendil-works/pi-coding-agent';\nimport {\n type DoomLeaderBinding,\n type DoomLeaderCommand,\n type DoomLeaderContribution,\n type DoomLeaderExtensionAction,\n type DoomLeaderSegment,\n} from './leader.ts';\n\ninterface AppLeaderAction {\n type: 'app';\n action: AppKeybinding;\n}\n\ninterface CommandLeaderAction {\n type: 'command';\n command: DoomLeaderCommand;\n}\n\ninterface ExtensionLeaderAction {\n type: 'extension';\n source: string;\n action: DoomLeaderExtensionAction;\n}\n\nexport type DoomLeaderAction = AppLeaderAction | CommandLeaderAction | ExtensionLeaderAction;\n\nexport interface DoomLeaderResolvedOption {\n key: string;\n label: string;\n detail?: string;\n hasChildren: boolean;\n action?: DoomLeaderAction;\n}\n\nexport interface DoomLeaderGroup {\n label: string;\n options: readonly DoomLeaderResolvedOption[];\n}\n\nexport interface DoomLeaderDiagnostic {\n source: string;\n bindingId?: string;\n message: string;\n}\n\nexport interface DoomLeaderRegistrationResult {\n accepted: boolean;\n diagnostics: readonly DoomLeaderDiagnostic[];\n}\n\ninterface InternalLeaderBinding {\n id: string;\n source: string;\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]];\n action: DoomLeaderAction;\n}\n\ninterface LeaderNode {\n key: string;\n label: string;\n detail?: string;\n order: number;\n owner: string;\n bindingId?: string;\n children: Map<string, LeaderNode>;\n action?: DoomLeaderAction;\n}\n\ninterface ParsedContribution {\n contribution?: DoomLeaderContribution;\n diagnostics: DoomLeaderDiagnostic[];\n}\n\nconst CORE_SOURCE = '@agimon-ai/doompi-ui';\nconst UNKNOWN_SOURCE = 'unknown';\nconst ROOT_LABEL = 'leader';\nconst DEFAULT_OPTION_ORDER = 500;\nconst MAX_OPTION_ORDER = 1000;\nconst MAX_PATH_LENGTH = 4;\nconst MAX_SOURCE_LENGTH = 128;\nconst MAX_BINDING_ID_LENGTH = 128;\nconst MAX_LABEL_LENGTH = 32;\nconst MAX_DETAIL_LENGTH = 80;\nconst MAX_COMMAND_NAME_LENGTH = 64;\nconst MAX_COMMAND_ARGUMENT_LENGTH = 256;\nconst MAX_ACTION_NAME_LENGTH = 128;\nconst SAFE_KEY = /^[a-z0-9]$/;\nconst SAFE_IDENTIFIER = /^[a-z0-9][a-z0-9._:-]*$/;\nconst SAFE_SOURCE = /^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/;\nconst SAFE_COMMAND_NAME = /^[a-z0-9][a-z0-9:_-]*$/;\nconst C0_CONTROL_MAX = 31;\nconst DELETE_CHARACTER = 127;\nconst MODEL_GROUP = { key: 'm', label: 'models', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', order: 90 } as const;\n\nfunction coreBinding(\n id: string,\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: DoomLeaderAction,\n): InternalLeaderBinding {\n return { id, source: CORE_SOURCE, path, action };\n}\n\nconst CORE_BINDINGS: readonly InternalLeaderBinding[] = [\n coreBinding('model.select', [MODEL_GROUP, { key: 'm', label: 'select', detail: 'choose model', order: 10 }], {\n type: 'app',\n action: 'app.model.select',\n }),\n coreBinding('model.next', [MODEL_GROUP, { key: 'n', label: 'next', detail: 'cycle model', order: 20 }], {\n type: 'app',\n action: 'app.model.cycleForward',\n }),\n coreBinding('thinking.cycle', [MODEL_GROUP, { key: 't', label: 'thinking', detail: 'cycle level', order: 30 }], {\n type: 'app',\n action: 'app.thinking.cycle',\n }),\n coreBinding('tools.open', [EXTENSION_GROUP, { key: 't', label: 'tools', detail: 'browse tools', order: 20 }], {\n type: 'command',\n command: { name: 'tools' },\n }),\n // Core rather than contributed, so no extension owns the key and the panel is\n // reachable even when nothing has contributed a section to it yet.\n coreBinding('config.open', [EXTENSION_GROUP, { key: 'c', label: 'config', detail: 'settings', order: 30 }], {\n type: 'command',\n command: { name: 'config' },\n }),\n coreBinding('session.new', [SESSION_GROUP, { key: 'n', label: 'new', detail: 'fresh session', order: 10 }], {\n type: 'app',\n action: 'app.session.new',\n }),\n coreBinding('session.resume', [SESSION_GROUP, { key: 'r', label: 'resume', detail: 'open history', order: 20 }], {\n type: 'app',\n action: 'app.session.resume',\n }),\n coreBinding('session.tree', [SESSION_GROUP, { key: 't', label: 'tree', detail: 'branch view', order: 30 }], {\n type: 'app',\n action: 'app.session.tree',\n }),\n coreBinding('session.fork', [SESSION_GROUP, { key: 'f', label: 'fork', detail: 'from selection', order: 40 }], {\n type: 'app',\n action: 'app.session.fork',\n }),\n coreBinding('editor.external', [EXTENSION_GROUP, { key: 'e', label: 'external', detail: 'open editor', order: 10 }], {\n type: 'app',\n action: 'app.editor.external',\n }),\n coreBinding('help.hotkeys', [HELP_GROUP, { key: 'h', label: 'hotkeys', detail: 'show bindings' }], {\n type: 'command',\n command: { name: 'hotkeys' },\n }),\n coreBinding('app.exit', [QUIT_GROUP, { key: 'q', label: 'quit', detail: 'exit Pi' }], {\n type: 'app',\n action: 'app.exit',\n }),\n];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction diagnostic(source: string, message: string, bindingId?: string): DoomLeaderDiagnostic {\n return { source, message, ...(bindingId ? { bindingId } : {}) };\n}\n\nfunction containsControlCharacter(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const codeUnit = value.charCodeAt(index);\n if (codeUnit <= C0_CONTROL_MAX || codeUnit === DELETE_CHARACTER) return true;\n }\n return false;\n}\n\nfunction readRequiredText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId?: string,\n): string | undefined {\n if (typeof value !== 'string' || !value.trim()) {\n diagnostics.push(diagnostic(source, `${field} must be a non-empty string.`, bindingId));\n return undefined;\n }\n const text = value.trim();\n if (text.length > maximumLength || containsControlCharacter(text)) {\n diagnostics.push(\n diagnostic(\n source,\n `${field} must be at most ${maximumLength} characters and contain no control characters.`,\n bindingId,\n ),\n );\n return undefined;\n }\n return text;\n}\n\nfunction readOptionalText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId: string,\n): string | undefined {\n if (value === undefined) return undefined;\n return readRequiredText(value, field, maximumLength, source, diagnostics, bindingId);\n}\n\nfunction parseSegment(\n value: unknown,\n source: string,\n bindingId: string,\n index: number,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderSegment | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}] must be an object.`, bindingId));\n return undefined;\n }\n\n const key = readRequiredText(value.key, `bindings[].path[${index}].key`, 1, source, diagnostics, bindingId);\n const label = readRequiredText(\n value.label,\n `bindings[].path[${index}].label`,\n MAX_LABEL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const detail = readOptionalText(\n value.detail,\n `bindings[].path[${index}].detail`,\n MAX_DETAIL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n let order: number | undefined;\n if (value.order !== undefined) {\n if (!Number.isInteger(value.order) || (value.order as number) < 0 || (value.order as number) > MAX_OPTION_ORDER) {\n diagnostics.push(\n diagnostic(\n source,\n `bindings[].path[${index}].order must be an integer from 0 to ${MAX_OPTION_ORDER}.`,\n bindingId,\n ),\n );\n } else {\n order = value.order as number;\n }\n }\n if (!key || !label || !SAFE_KEY.test(key)) {\n if (key && !SAFE_KEY.test(key)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}].key must match ${SAFE_KEY}.`, bindingId));\n }\n return undefined;\n }\n return { key, label, ...(detail ? { detail } : {}), ...(order === undefined ? {} : { order }) };\n}\n\nfunction parseCommand(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderCommand | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].command must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].command.name',\n MAX_COMMAND_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const args = readOptionalText(\n value.args,\n 'bindings[].command.args',\n MAX_COMMAND_ARGUMENT_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_COMMAND_NAME.test(name)) {\n if (name && !SAFE_COMMAND_NAME.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].command.name must match ${SAFE_COMMAND_NAME}.`, bindingId));\n }\n return undefined;\n }\n return { name, ...(args ? { args } : {}) };\n}\n\nfunction parseExtensionAction(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderExtensionAction | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].action must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].action.name',\n MAX_ACTION_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_IDENTIFIER.test(name)) {\n if (name && !SAFE_IDENTIFIER.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].action.name must match ${SAFE_IDENTIFIER}.`, bindingId));\n }\n return undefined;\n }\n return { name };\n}\n\nfunction parseBinding(\n value: unknown,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderBinding | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[] must be an object.'));\n return undefined;\n }\n const bindingId = readRequiredText(value.id, 'bindings[].id', MAX_BINDING_ID_LENGTH, source, diagnostics);\n if (!bindingId) return undefined;\n if (!SAFE_IDENTIFIER.test(bindingId)) {\n diagnostics.push(diagnostic(source, `bindings[].id must match ${SAFE_IDENTIFIER}.`, bindingId));\n return undefined;\n }\n if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH_LENGTH) {\n diagnostics.push(\n diagnostic(source, `bindings[].path must contain between 1 and ${MAX_PATH_LENGTH} segments.`, bindingId),\n );\n return undefined;\n }\n\n const path = value.path.map((segment, index) => parseSegment(segment, source, bindingId, index, diagnostics));\n const hasCommand = Object.hasOwn(value, 'command');\n const hasAction = Object.hasOwn(value, 'action');\n if (hasCommand === hasAction) {\n diagnostics.push(diagnostic(source, 'bindings[] must contain exactly one of command or action.', bindingId));\n return undefined;\n }\n const target = hasCommand\n ? { command: parseCommand(value.command, source, bindingId, diagnostics) }\n : { action: parseExtensionAction(value.action, source, bindingId, diagnostics) };\n if (path.some((segment) => segment === undefined)) return undefined;\n if ('command' in target) {\n if (!target.command) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n command: target.command,\n };\n }\n if (!target.action) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: target.action,\n };\n}\n\nfunction parseContribution(value: unknown): ParsedContribution {\n const diagnostics: DoomLeaderDiagnostic[] = [];\n if (!isRecord(value)) {\n return { diagnostics: [diagnostic(UNKNOWN_SOURCE, 'Leader contribution must be an object.')] };\n }\n const source = readRequiredText(value.source, 'source', MAX_SOURCE_LENGTH, UNKNOWN_SOURCE, diagnostics);\n if (!source) return { diagnostics };\n if (!SAFE_SOURCE.test(source)) {\n diagnostics.push(diagnostic(source, `source must match ${SAFE_SOURCE}.`));\n return { diagnostics };\n }\n if (!Array.isArray(value.bindings)) {\n return { diagnostics: [diagnostic(source, 'bindings must be an array.')] };\n }\n\n const bindings = value.bindings.map((binding) => parseBinding(binding, source, diagnostics));\n const duplicateIds = bindings\n .flatMap((binding) => (binding ? [binding.id] : []))\n .filter((id, index, ids) => ids.indexOf(id) !== index);\n for (const id of new Set(duplicateIds)) {\n diagnostics.push(diagnostic(source, `Binding id '${id}' is duplicated.`, id));\n }\n if (diagnostics.length > 0 || bindings.some((binding) => binding === undefined)) return { diagnostics };\n\n return {\n contribution: {\n source,\n bindings: bindings as DoomLeaderBinding[],\n },\n diagnostics,\n };\n}\n\nfunction optionOrder(segment: DoomLeaderSegment): number {\n return segment.order ?? DEFAULT_OPTION_ORDER;\n}\n\nfunction cloneAction(action: DoomLeaderAction): DoomLeaderAction {\n if (action.type === 'command') return { type: 'command', command: { ...action.command } };\n if (action.type === 'app') return { type: 'app', action: action.action };\n return { type: 'extension', source: action.source, action: { ...action.action } };\n}\n\nfunction createRootNode(): LeaderNode {\n return {\n key: '',\n label: ROOT_LABEL,\n order: 0,\n owner: CORE_SOURCE,\n children: new Map(),\n };\n}\n\nfunction pathLabel(path: readonly DoomLeaderSegment[]): string {\n return `SPC ${path.map((segment) => segment.key).join(' ')}`;\n}\n\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && node.detail === segment.detail && node.order === optionOrder(segment);\n}\n\nfunction insertBinding(root: LeaderNode, binding: InternalLeaderBinding): DoomLeaderDiagnostic | undefined {\n let current = root;\n let firstMissing = -1;\n\n for (const [index, segment] of binding.path.entries()) {\n const existing = current.children.get(segment.key);\n if (!existing) {\n firstMissing = index;\n break;\n }\n if (!metadataMatches(existing, segment)) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} conflicts with metadata from ${existing.owner}.`,\n binding.id,\n );\n }\n const isLeaf = index === binding.path.length - 1;\n if ((!isLeaf && existing.action) || (isLeaf && (existing.action || existing.children.size > 0))) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path)} conflicts with an existing binding from ${existing.owner}.`,\n binding.id,\n );\n }\n current = existing;\n }\n\n const start = firstMissing === -1 ? binding.path.length : firstMissing;\n for (let index = start; index < binding.path.length; index += 1) {\n const segment = binding.path[index] as DoomLeaderSegment;\n const node: LeaderNode = {\n key: segment.key,\n label: segment.label,\n ...(segment.detail ? { detail: segment.detail } : {}),\n order: optionOrder(segment),\n owner: binding.source,\n children: new Map(),\n };\n current.children.set(segment.key, node);\n current = node;\n }\n\n current.action = cloneAction(binding.action);\n current.bindingId = binding.id;\n return undefined;\n}\n\nexport class DoomLeaderRegistry {\n private root = createRootNode();\n private readonly contributions = new Map<string, readonly DoomLeaderBinding[]>();\n private readonly listeners = new Set<() => void>();\n private diagnostics: DoomLeaderDiagnostic[] = [];\n\n constructor() {\n this.rebuild();\n }\n\n register(value: unknown): DoomLeaderRegistrationResult {\n const parsed = parseContribution(value);\n if (!parsed.contribution) return { accepted: false, diagnostics: parsed.diagnostics };\n\n if (parsed.contribution.bindings.length === 0) this.contributions.delete(parsed.contribution.source);\n else this.contributions.set(parsed.contribution.source, parsed.contribution.bindings);\n this.rebuild();\n return { accepted: true, diagnostics: this.getDiagnostics() };\n }\n\n getGroup(path: readonly string[]): DoomLeaderGroup | undefined {\n let current = this.root;\n for (const key of path) {\n const next = current.children.get(key);\n if (!next || next.action) return undefined;\n current = next;\n }\n\n // The key is the sort, at every level. A reader looking for a chord knows the\n // key, not the number some contributor picked for it, and those numbers live\n // across nine packages so no one of them can see the sequence they produce.\n // `order` survives only as group identity: co-contributors to a shared prefix\n // must still agree on it, which is what `metadataMatches` checks.\n const options = [...current.children.values()]\n .sort((left, right) => left.key.localeCompare(right.key))\n .map(\n (node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\n }),\n );\n return { label: current.label, options };\n }\n\n getDiagnostics(): DoomLeaderDiagnostic[] {\n return this.diagnostics.map((item) => ({ ...item }));\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private rebuild(): void {\n const nextRoot = createRootNode();\n const diagnostics: DoomLeaderDiagnostic[] = [];\n for (const binding of CORE_BINDINGS) {\n const issue = insertBinding(nextRoot, binding);\n if (issue) throw new Error(`Invalid core leader binding: ${issue.message}`);\n }\n\n for (const [source, bindings] of [...this.contributions.entries()].sort(([left], [right]) =>\n left.localeCompare(right),\n )) {\n for (const binding of [...bindings].sort((left, right) => left.id.localeCompare(right.id))) {\n const issue = insertBinding(nextRoot, {\n id: binding.id,\n source,\n path: binding.path,\n action:\n 'command' in binding\n ? { type: 'command', command: binding.command }\n : { type: 'extension', source, action: binding.action },\n });\n if (issue) diagnostics.push(issue);\n }\n }\n\n this.root = nextRoot;\n this.diagnostics = diagnostics;\n for (const listener of this.listeners) listener();\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"leaderRegistry.mjs","names":[],"sources":["../src/leaderRegistry.ts"],"sourcesContent":["import type { AppKeybinding } from '@earendil-works/pi-coding-agent';\nimport {\n type DoomLeaderBinding,\n type DoomLeaderCommand,\n type DoomLeaderContribution,\n type DoomLeaderExtensionAction,\n type DoomLeaderSegment,\n} from './leader.ts';\n\ninterface AppLeaderAction {\n type: 'app';\n action: AppKeybinding;\n}\n\ninterface CommandLeaderAction {\n type: 'command';\n command: DoomLeaderCommand;\n}\n\ninterface ExtensionLeaderAction {\n type: 'extension';\n source: string;\n action: DoomLeaderExtensionAction;\n}\n\nexport type DoomLeaderAction = AppLeaderAction | CommandLeaderAction | ExtensionLeaderAction;\n\nexport interface DoomLeaderResolvedOption {\n key: string;\n label: string;\n detail?: string;\n hasChildren: boolean;\n action?: DoomLeaderAction;\n}\n\nexport interface DoomLeaderGroup {\n label: string;\n options: readonly DoomLeaderResolvedOption[];\n}\n\nexport interface DoomLeaderDiagnostic {\n source: string;\n bindingId?: string;\n message: string;\n}\n\nexport interface DoomLeaderRegistrationResult {\n accepted: boolean;\n diagnostics: readonly DoomLeaderDiagnostic[];\n}\n\ninterface InternalLeaderBinding {\n id: string;\n source: string;\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]];\n action: DoomLeaderAction;\n}\n\ninterface LeaderNode {\n key: string;\n label: string;\n detail?: string;\n order: number;\n owner: string;\n bindingId?: string;\n children: Map<string, LeaderNode>;\n action?: DoomLeaderAction;\n}\n\ninterface ParsedContribution {\n contribution?: DoomLeaderContribution;\n diagnostics: DoomLeaderDiagnostic[];\n}\n\nconst CORE_SOURCE = '@agimon-ai/doompi-ui';\nconst UNKNOWN_SOURCE = 'unknown';\nconst ROOT_LABEL = 'leader';\nconst DEFAULT_OPTION_ORDER = 500;\nconst MAX_OPTION_ORDER = 1000;\nconst MAX_PATH_LENGTH = 4;\nconst MAX_SOURCE_LENGTH = 128;\nconst MAX_BINDING_ID_LENGTH = 128;\nconst MAX_LABEL_LENGTH = 32;\nconst MAX_DETAIL_LENGTH = 80;\nconst MAX_COMMAND_NAME_LENGTH = 64;\nconst MAX_COMMAND_ARGUMENT_LENGTH = 256;\nconst MAX_ACTION_NAME_LENGTH = 128;\nconst SAFE_KEY = /^[a-z0-9]$/;\nconst SAFE_IDENTIFIER = /^[a-z0-9][a-z0-9._:-]*$/;\nconst SAFE_SOURCE = /^[A-Za-z0-9@][A-Za-z0-9@/._:-]*$/;\nconst SAFE_COMMAND_NAME = /^[a-z0-9][a-z0-9:_-]*$/;\nconst C0_CONTROL_MAX = 31;\nconst DELETE_CHARACTER = 127;\nconst MODEL_GROUP = { key: 'm', label: 'models', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', order: 90 } as const;\n\nfunction coreBinding(\n id: string,\n path: readonly [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: DoomLeaderAction,\n): InternalLeaderBinding {\n return { id, source: CORE_SOURCE, path, action };\n}\n\nconst CORE_BINDINGS: readonly InternalLeaderBinding[] = [\n coreBinding('model.select', [MODEL_GROUP, { key: 'm', label: 'select', detail: 'choose model', order: 10 }], {\n type: 'app',\n action: 'app.model.select',\n }),\n coreBinding('model.next', [MODEL_GROUP, { key: 'n', label: 'next', detail: 'cycle model', order: 20 }], {\n type: 'app',\n action: 'app.model.cycleForward',\n }),\n coreBinding('thinking.cycle', [MODEL_GROUP, { key: 't', label: 'thinking', detail: 'cycle level', order: 30 }], {\n type: 'app',\n action: 'app.thinking.cycle',\n }),\n coreBinding('tools.open', [EXTENSION_GROUP, { key: 't', label: 'tools', detail: 'browse tools', order: 20 }], {\n type: 'command',\n command: { name: 'tools' },\n }),\n // Core rather than contributed, so no extension owns the key and the panel is\n // reachable even when nothing has contributed a section to it yet.\n coreBinding('config.open', [EXTENSION_GROUP, { key: 'c', label: 'config', detail: 'settings', order: 30 }], {\n type: 'command',\n command: { name: 'config' },\n }),\n coreBinding('session.new', [SESSION_GROUP, { key: 'n', label: 'new', detail: 'fresh session', order: 10 }], {\n type: 'app',\n action: 'app.session.new',\n }),\n coreBinding('session.resume', [SESSION_GROUP, { key: 'r', label: 'resume', detail: 'open history', order: 20 }], {\n type: 'app',\n action: 'app.session.resume',\n }),\n coreBinding('session.tree', [SESSION_GROUP, { key: 't', label: 'tree', detail: 'branch view', order: 30 }], {\n type: 'app',\n action: 'app.session.tree',\n }),\n coreBinding('session.fork', [SESSION_GROUP, { key: 'f', label: 'fork', detail: 'from selection', order: 40 }], {\n type: 'app',\n action: 'app.session.fork',\n }),\n coreBinding('editor.external', [EXTENSION_GROUP, { key: 'e', label: 'external', detail: 'open editor', order: 10 }], {\n type: 'app',\n action: 'app.editor.external',\n }),\n coreBinding('help.hotkeys', [HELP_GROUP, { key: 'h', label: 'hotkeys', detail: 'show bindings' }], {\n type: 'command',\n command: { name: 'hotkeys' },\n }),\n coreBinding('app.exit', [QUIT_GROUP, { key: 'q', label: 'quit', detail: 'exit Pi' }], {\n type: 'app',\n action: 'app.exit',\n }),\n];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction diagnostic(source: string, message: string, bindingId?: string): DoomLeaderDiagnostic {\n return { source, message, ...(bindingId ? { bindingId } : {}) };\n}\n\nfunction containsControlCharacter(value: string): boolean {\n for (let index = 0; index < value.length; index += 1) {\n const codeUnit = value.charCodeAt(index);\n if (codeUnit <= C0_CONTROL_MAX || codeUnit === DELETE_CHARACTER) return true;\n }\n return false;\n}\n\nfunction readRequiredText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId?: string,\n): string | undefined {\n if (typeof value !== 'string' || !value.trim()) {\n diagnostics.push(diagnostic(source, `${field} must be a non-empty string.`, bindingId));\n return undefined;\n }\n const text = value.trim();\n if (text.length > maximumLength || containsControlCharacter(text)) {\n diagnostics.push(\n diagnostic(\n source,\n `${field} must be at most ${maximumLength} characters and contain no control characters.`,\n bindingId,\n ),\n );\n return undefined;\n }\n return text;\n}\n\nfunction readOptionalText(\n value: unknown,\n field: string,\n maximumLength: number,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n bindingId: string,\n): string | undefined {\n if (value === undefined) return undefined;\n return readRequiredText(value, field, maximumLength, source, diagnostics, bindingId);\n}\n\nfunction parseSegment(\n value: unknown,\n source: string,\n bindingId: string,\n index: number,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderSegment | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}] must be an object.`, bindingId));\n return undefined;\n }\n\n const key = readRequiredText(value.key, `bindings[].path[${index}].key`, 1, source, diagnostics, bindingId);\n const label = readRequiredText(\n value.label,\n `bindings[].path[${index}].label`,\n MAX_LABEL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const detail = readOptionalText(\n value.detail,\n `bindings[].path[${index}].detail`,\n MAX_DETAIL_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n let order: number | undefined;\n if (value.order !== undefined) {\n if (!Number.isInteger(value.order) || (value.order as number) < 0 || (value.order as number) > MAX_OPTION_ORDER) {\n diagnostics.push(\n diagnostic(\n source,\n `bindings[].path[${index}].order must be an integer from 0 to ${MAX_OPTION_ORDER}.`,\n bindingId,\n ),\n );\n } else {\n order = value.order as number;\n }\n }\n if (!key || !label || !SAFE_KEY.test(key)) {\n if (key && !SAFE_KEY.test(key)) {\n diagnostics.push(diagnostic(source, `bindings[].path[${index}].key must match ${SAFE_KEY}.`, bindingId));\n }\n return undefined;\n }\n return { key, label, ...(detail ? { detail } : {}), ...(order === undefined ? {} : { order }) };\n}\n\nfunction parseCommand(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderCommand | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].command must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].command.name',\n MAX_COMMAND_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n const args = readOptionalText(\n value.args,\n 'bindings[].command.args',\n MAX_COMMAND_ARGUMENT_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_COMMAND_NAME.test(name)) {\n if (name && !SAFE_COMMAND_NAME.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].command.name must match ${SAFE_COMMAND_NAME}.`, bindingId));\n }\n return undefined;\n }\n return { name, ...(args ? { args } : {}) };\n}\n\nfunction parseExtensionAction(\n value: unknown,\n source: string,\n bindingId: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderExtensionAction | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[].action must be an object.', bindingId));\n return undefined;\n }\n const name = readRequiredText(\n value.name,\n 'bindings[].action.name',\n MAX_ACTION_NAME_LENGTH,\n source,\n diagnostics,\n bindingId,\n );\n if (!name || !SAFE_IDENTIFIER.test(name)) {\n if (name && !SAFE_IDENTIFIER.test(name)) {\n diagnostics.push(diagnostic(source, `bindings[].action.name must match ${SAFE_IDENTIFIER}.`, bindingId));\n }\n return undefined;\n }\n return { name };\n}\n\nfunction parseBinding(\n value: unknown,\n source: string,\n diagnostics: DoomLeaderDiagnostic[],\n): DoomLeaderBinding | undefined {\n if (!isRecord(value)) {\n diagnostics.push(diagnostic(source, 'bindings[] must be an object.'));\n return undefined;\n }\n const bindingId = readRequiredText(value.id, 'bindings[].id', MAX_BINDING_ID_LENGTH, source, diagnostics);\n if (!bindingId) return undefined;\n if (!SAFE_IDENTIFIER.test(bindingId)) {\n diagnostics.push(diagnostic(source, `bindings[].id must match ${SAFE_IDENTIFIER}.`, bindingId));\n return undefined;\n }\n if (!Array.isArray(value.path) || value.path.length === 0 || value.path.length > MAX_PATH_LENGTH) {\n diagnostics.push(\n diagnostic(source, `bindings[].path must contain between 1 and ${MAX_PATH_LENGTH} segments.`, bindingId),\n );\n return undefined;\n }\n\n const path = value.path.map((segment, index) => parseSegment(segment, source, bindingId, index, diagnostics));\n const hasCommand = Object.hasOwn(value, 'command');\n const hasAction = Object.hasOwn(value, 'action');\n if (hasCommand === hasAction) {\n diagnostics.push(diagnostic(source, 'bindings[] must contain exactly one of command or action.', bindingId));\n return undefined;\n }\n const target = hasCommand\n ? { command: parseCommand(value.command, source, bindingId, diagnostics) }\n : { action: parseExtensionAction(value.action, source, bindingId, diagnostics) };\n if (path.some((segment) => segment === undefined)) return undefined;\n if ('command' in target) {\n if (!target.command) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n command: target.command,\n };\n }\n if (!target.action) return undefined;\n return {\n id: bindingId,\n path: path as [DoomLeaderSegment, ...DoomLeaderSegment[]],\n action: target.action,\n };\n}\n\nfunction parseContribution(value: unknown): ParsedContribution {\n const diagnostics: DoomLeaderDiagnostic[] = [];\n if (!isRecord(value)) {\n return { diagnostics: [diagnostic(UNKNOWN_SOURCE, 'Leader contribution must be an object.')] };\n }\n const source = readRequiredText(value.source, 'source', MAX_SOURCE_LENGTH, UNKNOWN_SOURCE, diagnostics);\n if (!source) return { diagnostics };\n if (!SAFE_SOURCE.test(source)) {\n diagnostics.push(diagnostic(source, `source must match ${SAFE_SOURCE}.`));\n return { diagnostics };\n }\n if (!Array.isArray(value.bindings)) {\n return { diagnostics: [diagnostic(source, 'bindings must be an array.')] };\n }\n\n const bindings = value.bindings.map((binding) => parseBinding(binding, source, diagnostics));\n const duplicateIds = bindings\n .flatMap((binding) => (binding ? [binding.id] : []))\n .filter((id, index, ids) => ids.indexOf(id) !== index);\n for (const id of new Set(duplicateIds)) {\n diagnostics.push(diagnostic(source, `Binding id '${id}' is duplicated.`, id));\n }\n if (diagnostics.length > 0 || bindings.some((binding) => binding === undefined)) return { diagnostics };\n\n return {\n contribution: {\n source,\n bindings: bindings as DoomLeaderBinding[],\n },\n diagnostics,\n };\n}\n\nfunction optionOrder(segment: DoomLeaderSegment): number {\n return segment.order ?? DEFAULT_OPTION_ORDER;\n}\n\nfunction cloneAction(action: DoomLeaderAction): DoomLeaderAction {\n if (action.type === 'command') return { type: 'command', command: { ...action.command } };\n if (action.type === 'app') return { type: 'app', action: action.action };\n return { type: 'extension', source: action.source, action: { ...action.action } };\n}\n\nfunction createRootNode(): LeaderNode {\n return {\n key: '',\n label: ROOT_LABEL,\n order: 0,\n owner: CORE_SOURCE,\n children: new Map(),\n };\n}\n\nfunction pathLabel(path: readonly DoomLeaderSegment[]): string {\n return `SPC ${path.map((segment) => segment.key).join(' ')}`;\n}\n\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && node.detail === segment.detail && node.order === optionOrder(segment);\n}\n\nfunction insertBinding(root: LeaderNode, binding: InternalLeaderBinding): DoomLeaderDiagnostic | undefined {\n let current = root;\n let firstMissing = -1;\n\n for (const [index, segment] of binding.path.entries()) {\n const existing = current.children.get(segment.key);\n if (!existing) {\n firstMissing = index;\n break;\n }\n if (!metadataMatches(existing, segment)) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} conflicts with metadata from ${existing.owner}.`,\n binding.id,\n );\n }\n const isLeaf = index === binding.path.length - 1;\n if ((!isLeaf && existing.action) || (isLeaf && (existing.action || existing.children.size > 0))) {\n return diagnostic(\n binding.source,\n `${pathLabel(binding.path)} conflicts with an existing binding from ${existing.owner}.`,\n binding.id,\n );\n }\n current = existing;\n }\n\n const start = firstMissing === -1 ? binding.path.length : firstMissing;\n for (let index = start; index < binding.path.length; index += 1) {\n const segment = binding.path[index] as DoomLeaderSegment;\n const node: LeaderNode = {\n key: segment.key,\n label: segment.label,\n ...(segment.detail ? { detail: segment.detail } : {}),\n order: optionOrder(segment),\n owner: binding.source,\n children: new Map(),\n };\n current.children.set(segment.key, node);\n current = node;\n }\n\n current.action = cloneAction(binding.action);\n current.bindingId = binding.id;\n return undefined;\n}\n\nexport class DoomLeaderRegistry {\n private root = createRootNode();\n private readonly contributions = new Map<string, readonly DoomLeaderBinding[]>();\n private readonly listeners = new Set<() => void>();\n private diagnostics: DoomLeaderDiagnostic[] = [];\n private dirty = false;\n\n constructor() {\n this.rebuild();\n }\n\n register(value: unknown): DoomLeaderRegistrationResult {\n const result = this.apply(value);\n if (!result.accepted) return result;\n this.rebuild();\n return { accepted: true, diagnostics: this.getDiagnostics() };\n }\n\n /** Records a contribution without rebuilding the whole tree immediately. */\n registerDeferred(value: unknown): DoomLeaderRegistrationResult {\n return this.apply(value);\n }\n\n private apply(value: unknown): DoomLeaderRegistrationResult {\n const parsed = parseContribution(value);\n if (!parsed.contribution) return { accepted: false, diagnostics: parsed.diagnostics };\n\n if (parsed.contribution.bindings.length === 0) this.contributions.delete(parsed.contribution.source);\n else this.contributions.set(parsed.contribution.source, parsed.contribution.bindings);\n this.dirty = true;\n return { accepted: true, diagnostics: parsed.diagnostics };\n }\n\n /** Rebuilds one batch and returns its deterministic conflict diagnostics. */\n flush(): DoomLeaderDiagnostic[] {\n if (this.dirty) this.rebuild();\n return this.diagnostics.map((item) => ({ ...item }));\n }\n\n getGroup(path: readonly string[]): DoomLeaderGroup | undefined {\n this.flush();\n let current = this.root;\n for (const key of path) {\n const next = current.children.get(key);\n if (!next || next.action) return undefined;\n current = next;\n }\n\n // The key is the sort, at every level. A reader looking for a chord knows the\n // key, not the number some contributor picked for it, and those numbers live\n // across nine packages so no one of them can see the sequence they produce.\n // `order` survives only as group identity: co-contributors to a shared prefix\n // must still agree on it, which is what `metadataMatches` checks.\n const options = [...current.children.values()]\n .sort((left, right) => left.key.localeCompare(right.key))\n .map(\n (node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\n }),\n );\n return { label: current.label, options };\n }\n\n getDiagnostics(): DoomLeaderDiagnostic[] {\n return this.flush();\n }\n\n subscribe(listener: () => void): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n private rebuild(): void {\n const nextRoot = createRootNode();\n const diagnostics: DoomLeaderDiagnostic[] = [];\n for (const binding of CORE_BINDINGS) {\n const issue = insertBinding(nextRoot, binding);\n if (issue) throw new Error(`Invalid core leader binding: ${issue.message}`);\n }\n\n for (const [source, bindings] of [...this.contributions.entries()].sort(([left], [right]) =>\n left.localeCompare(right),\n )) {\n for (const binding of [...bindings].sort((left, right) => left.id.localeCompare(right.id))) {\n const issue = insertBinding(nextRoot, {\n id: binding.id,\n source,\n path: binding.path,\n action:\n 'command' in binding\n ? { type: 'command', command: binding.command }\n : { type: 'extension', source, action: binding.action },\n });\n if (issue) diagnostics.push(issue);\n }\n }\n\n this.root = nextRoot;\n this.diagnostics = diagnostics;\n this.dirty = false;\n for (const listener of this.listeners) listener();\n }\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-ui",
3
- "version": "0.0.1-alpha.0",
3
+ "version": "0.0.1-alpha.2",
4
4
  "description": "Doom-inspired interactive UI extension for Pi",
5
5
  "homepage": "https://agimon.ai",
6
6
  "license": "MIT",
@@ -137,8 +137,8 @@
137
137
  },
138
138
  "dependencies": {
139
139
  "yaml": "2.9.0",
140
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.0",
141
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.0"
140
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.2",
141
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.2"
142
142
  },
143
143
  "devDependencies": {
144
144
  "@earendil-works/pi-coding-agent": "0.84.1",