@agimon-ai/doompi-ui 0.0.1-alpha.34 → 0.0.1-alpha.35
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.
- package/dist/services/leader/leaderRegistry.cjs +1 -1
- package/dist/services/leader/leaderRegistry.cjs.map +1 -1
- package/dist/services/leader/leaderRegistry.d.cts.map +1 -1
- package/dist/services/leader/leaderRegistry.d.mts.map +1 -1
- package/dist/services/leader/leaderRegistry.mjs +1 -1
- package/dist/services/leader/leaderRegistry.mjs.map +1 -1
- package/dist/tui/configOverlay.cjs +0 -0
- package/dist/tui/configOverlay.cjs.map +1 -1
- package/dist/tui/configOverlay.d.cts +7 -0
- package/dist/tui/configOverlay.d.cts.map +1 -1
- package/dist/tui/configOverlay.d.mts +7 -0
- package/dist/tui/configOverlay.d.mts.map +1 -1
- package/dist/tui/configOverlay.mjs +0 -0
- package/dist/tui/configOverlay.mjs.map +1 -1
- package/package.json +4 -4
|
@@ -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`,detail:`model and thinking level`,order:10},c={key:`s`,label:`sessions`,detail:`history, forks and branches`,order:30},l={key:`e`,label:`extension`,detail:`tools, skills and config`,order:50},u={key:`h`,label:`help`,detail:`package docs and logs`,order:70},d={key:`q`,label:`quit`,detail:`leave the session`,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(`
|
|
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`,detail:`model and thinking level`,order:10},c={key:`s`,label:`sessions`,detail:`history, forks and branches`,order:30},l={key:`e`,label:`extension`,detail:`tools, skills and config`,order:50},u={key:`h`,label:`help`,detail:`package docs and logs`,order:70},d={key:`q`,label:`quit`,detail:`leave the session`,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(`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=e.tone==="default"||e.tone===`exit`?e.tone:void 0,d;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)):d=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?{tone:u}:{},...d===void 0?{}:{order:d}}}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.order===w(t)}function k(e){if(e.children.size===0)return+!!e.action;let t=0;for(let n of e.children.values())t+=k(n);return t}function A(e,t){let n=[],r=e,i=-1;for(let[e,a]of t.path.entries()){let o=r.children.get(a.key);if(!o){i=e;break}O(o,a)||n.push(h(t.source,`${D(t.path.slice(0,e+1))} keeps the label from ${o.owner}; the binding is registered.`,t.id)),!o.detail&&a.detail&&(o.detail=a.detail);let s=e===t.path.length-1,c=k(o);s&&c>0?(n.push(h(t.source,`${D(t.path)} taken over from ${o.owner}${c>1?`, displacing ${c} bindings`:``}.`,t.id)),o.children.clear(),delete o.action,delete o.bindingId):!s&&o.action&&(n.push(h(t.source,`${D(t.path.slice(0,e+1))} taken over from ${o.owner} to open a group.`,t.id)),delete o.action,delete o.bindingId),(s||o.action===void 0)&&(o.owner=t.source),r=o}let a=i===-1?t.path.length:i;for(let e=a;e<t.path.length;e+=1){let n=t.path[e],i={key:n.key,label:n.label,...n.detail?{detail:n.detail}:{},...n.tone?{tone:n.tone}:{},order:w(n),owner:t.source,children:new Map};r.children.set(n.key,i),r=i}return r.action=T(t.action),r.bindingId=t.id,r.owner=t.source,n}var j=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?(this.contributions.delete(t.contribution.source),t.contribution.bindings.length>0&&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}:{},...e.tone?{tone:e.tone}:{},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]=A(e,t);if(n)throw Error(`Invalid core leader binding: ${n.message}`)}for(let[n,r]of this.contributions.entries())for(let i of[...r].sort((e,t)=>e.id.localeCompare(t.id)))t.push(...A(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}}));this.root=e,this.diagnostics=t,this.dirty=!1;for(let e of this.listeners)e()}};exports.DoomLeaderRegistry=j;
|
|
2
2
|
//# sourceMappingURL=leaderRegistry.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaderRegistry.cjs","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"sourcesContent":["type AppKeybinding =\n | 'app.model.select'\n | 'app.model.cycleForward'\n | 'app.thinking.cycle'\n | 'app.session.new'\n | 'app.session.resume'\n | 'app.session.tree'\n | 'app.session.fork'\n | 'app.editor.external'\n | 'app.exit';\n\nimport type {\n LeaderBinding as DoomLeaderBinding,\n LeaderCommand as DoomLeaderCommand,\n LeaderContribution as DoomLeaderContribution,\n LeaderAction as DoomLeaderExtensionAction,\n LeaderSegment as DoomLeaderSegment,\n LeaderTone as DoomLeaderTone,\n} from '@agimon-ai/doompi-extension-contracts/leader';\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 tone?: DoomLeaderTone;\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 tone?: DoomLeaderTone;\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', detail: 'model and thinking level', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', detail: 'history, forks and branches', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', detail: 'tools, skills and config', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', detail: 'package docs and logs', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', detail: 'leave the session', 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 // An unrecognised tone is ignored, not diagnosed. Every other malformed field\n // here rejects the whole contribution, which is right for a key or a label but\n // not for a badge colour: it would mean a package compiled against a later\n // contract loses its entire menu on an older host over a cosmetic value. The\n // union type already catches a typo at every call site built against this\n // contract, which is the only place a typo can come from.\n const tone: DoomLeaderTone | undefined = value.tone === 'default' || value.tone === 'exit' ? value.tone : undefined;\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 {\n key,\n label,\n ...(detail ? { detail } : {}),\n ...(tone ? { tone } : {}),\n ...(order === undefined ? {} : { order }),\n };\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\n/**\n * Group identity is the key, the label and the sort order. `detail` is left out\n * on purpose: it is a subtitle, and these packages ship on independent npm\n * versions, so requiring every co-contributor to a shared prefix to carry the\n * same wording means one upgraded package silently drops the menus of every\n * package that has not caught up. A subtitle disagreement is not a conflict\n * worth losing `SPC e` over.\n */\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && 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 // First writer wins for the subtitle, but a group created without one still\n // adopts the first that is offered, so the wording does not depend on which\n // package happened to register first.\n if (!existing.detail && segment.detail) existing.detail = segment.detail;\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 ...(segment.tone ? { tone: segment.tone } : {}),\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((node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n ...(node.tone ? { tone: node.tone } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\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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"}
|
|
1
|
+
{"version":3,"file":"leaderRegistry.cjs","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"sourcesContent":["type AppKeybinding =\n | 'app.model.select'\n | 'app.model.cycleForward'\n | 'app.thinking.cycle'\n | 'app.session.new'\n | 'app.session.resume'\n | 'app.session.tree'\n | 'app.session.fork'\n | 'app.editor.external'\n | 'app.exit';\n\nimport type {\n LeaderBinding as DoomLeaderBinding,\n LeaderCommand as DoomLeaderCommand,\n LeaderContribution as DoomLeaderContribution,\n LeaderAction as DoomLeaderExtensionAction,\n LeaderSegment as DoomLeaderSegment,\n LeaderTone as DoomLeaderTone,\n} from '@agimon-ai/doompi-extension-contracts/leader';\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 tone?: DoomLeaderTone;\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 tone?: DoomLeaderTone;\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', detail: 'model and thinking level', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', detail: 'history, forks and branches', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', detail: 'tools, skills and config', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', detail: 'package docs and logs', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', detail: 'leave the session', 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('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 // An unrecognised tone is ignored, not diagnosed. Every other malformed field\n // here rejects the whole contribution, which is right for a key or a label but\n // not for a badge colour: it would mean a package compiled against a later\n // contract loses its entire menu on an older host over a cosmetic value. The\n // union type already catches a typo at every call site built against this\n // contract, which is the only place a typo can come from.\n const tone: DoomLeaderTone | undefined = value.tone === 'default' || value.tone === 'exit' ? value.tone : undefined;\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 {\n key,\n label,\n ...(detail ? { detail } : {}),\n ...(tone ? { tone } : {}),\n ...(order === undefined ? {} : { order }),\n };\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\n/**\n * Whether a shared group prefix is worded the same way by both contributors.\n *\n * `detail` is left out on purpose: it is a subtitle, and these packages ship on\n * independent npm versions, so comparing it means one upgraded package disagrees\n * with every package that has not caught up.\n *\n * A mismatch is REPORTED, never fatal. Losing `SPC e s` because two packages\n * spell the `SPC e` group differently costs the user a key they can no longer\n * press, which is worse than a menu row worded by whoever registered first.\n */\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && node.order === optionOrder(segment);\n}\n\n/** Every binding under a node, for a takeover message that says what it displaced. */\nfunction leafCount(node: LeaderNode): number {\n if (node.children.size === 0) return node.action ? 1 : 0;\n let total = 0;\n for (const child of node.children.values()) total += leafCount(child);\n return total;\n}\n\n/**\n * Inserts one binding, taking a key over from whoever holds it.\n *\n * LAST REGISTRATION WINS, and the diagnostics say who lost what. The rule this\n * replaced rejected the newcomer, which read as \"warning plus nothing happened\":\n * the key stayed bound to the first registrant, or - when the clash was only a\n * group label - to nobody at all. A takeover is recoverable in a way a dropped\n * binding is not: the tree is rebuilt from every stored contribution, so when\n * the winner unregisters, the displaced binding comes back on the next rebuild.\n *\n * Returns diagnostics rather than one issue, because a single insert can both\n * disagree about a group's wording and take a key from someone.\n */\nfunction insertBinding(root: LeaderNode, binding: InternalLeaderBinding): DoomLeaderDiagnostic[] {\n const diagnostics: DoomLeaderDiagnostic[] = [];\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 diagnostics.push(\n diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} keeps the label from ${existing.owner}; the binding is registered.`,\n binding.id,\n ),\n );\n }\n // First writer wins for the subtitle, but a group created without one still\n // adopts the first that is offered, so the wording does not depend on which\n // package happened to register first.\n if (!existing.detail && segment.detail) existing.detail = segment.detail;\n\n const isLeaf = index === binding.path.length - 1;\n const displaced = leafCount(existing);\n if (isLeaf && displaced > 0) {\n diagnostics.push(\n diagnostic(\n binding.source,\n `${pathLabel(binding.path)} taken over from ${existing.owner}` +\n `${displaced > 1 ? `, displacing ${displaced} bindings` : ''}.`,\n binding.id,\n ),\n );\n existing.children.clear();\n delete existing.action;\n delete existing.bindingId;\n } else if (!isLeaf && existing.action) {\n diagnostics.push(\n diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} taken over from ${existing.owner} to open a group.`,\n binding.id,\n ),\n );\n delete existing.action;\n delete existing.bindingId;\n }\n if (isLeaf || existing.action === undefined) existing.owner = binding.source;\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 ...(segment.tone ? { tone: segment.tone } : {}),\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 current.owner = binding.source;\n return diagnostics;\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 // Deleted before it is set again, so re-registering moves a source to the\n // back of the insertion order: registering again IS claiming again.\n this.contributions.delete(parsed.contribution.source);\n if (parsed.contribution.bindings.length > 0) {\n this.contributions.set(parsed.contribution.source, parsed.contribution.bindings);\n }\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((node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n ...(node.tone ? { tone: node.tone } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\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 // Core bindings land in an empty tree, so anything reported here is two\n // core bindings disagreeing with each other: a defect, not a contribution.\n const [issue] = insertBinding(nextRoot, binding);\n if (issue) throw new Error(`Invalid core leader binding: ${issue.message}`);\n }\n\n // Registration order, not alphabetical: the key belongs to whoever claimed\n // it last, and `apply` re-seats a source that registers again so a reload\n // counts as a fresh claim. Bindings inside one contribution stay sorted by\n // id, so a single source's own output is still deterministic.\n for (const [source, bindings] of this.contributions.entries()) {\n for (const binding of [...bindings].sort((left, right) => left.id.localeCompare(right.id))) {\n diagnostics.push(\n ...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 );\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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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaderRegistry.d.cts","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"mappings":";;KAAK;UAoBK;EACR;EACA,QAAQ;;UAGA;EACR;EACA,SAAS;;UAGD;EACR;EACA;EACA,QAAQ;;KAGE,mBAAmB,kBAAkB,sBAAsB;UAEtD;EACf;EACA;EACA;EACA,OAAO;EACP;EACA,SAAS;;UAGM;EACf;EACA,kBAAkB;;UAGH;EACf;EACA;EACA;;UAGe;EACf;EACA,sBAAsB;;
|
|
1
|
+
{"version":3,"file":"leaderRegistry.d.cts","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"mappings":";;KAAK;UAoBK;EACR;EACA,QAAQ;;UAGA;EACR;EACA,SAAS;;UAGD;EACR;EACA;EACA,QAAQ;;KAGE,mBAAmB,kBAAkB,sBAAsB;UAEtD;EACf;EACA;EACA;EACA,OAAO;EACP;EACA,SAAS;;UAGM;EACf;EACA,kBAAkB;;UAGH;EACf;EACA;EACA;;UAGe;EACf;EACA,sBAAsB;;cA4fX;UACH;mBACS;mBACA;UACT;UACA;EAER;EAIA,SAAS,iBAAiB;;EAQ1B,iBAAiB,iBAAiB;UAI1B;;EAeR,SAAS;EAKT,SAAS,0BAA0B;EA2BnC,kBAAkB;EAIlB,UAAU;UAKF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaderRegistry.d.mts","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"mappings":";;KAAK;UAoBK;EACR;EACA,QAAQ;;UAGA;EACR;EACA,SAAS;;UAGD;EACR;EACA;EACA,QAAQ;;KAGE,mBAAmB,kBAAkB,sBAAsB;UAEtD;EACf;EACA;EACA;EACA,OAAO;EACP;EACA,SAAS;;UAGM;EACf;EACA,kBAAkB;;UAGH;EACf;EACA;EACA;;UAGe;EACf;EACA,sBAAsB;;
|
|
1
|
+
{"version":3,"file":"leaderRegistry.d.mts","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"mappings":";;KAAK;UAoBK;EACR;EACA,QAAQ;;UAGA;EACR;EACA,SAAS;;UAGD;EACR;EACA;EACA,QAAQ;;KAGE,mBAAmB,kBAAkB,sBAAsB;UAEtD;EACf;EACA;EACA;EACA,OAAO;EACP;EACA,SAAS;;UAGM;EACf;EACA,kBAAkB;;UAGH;EACf;EACA;EACA;;UAGe;EACf;EACA,sBAAsB;;cA4fX;UACH;mBACS;mBACA;UACT;UACA;EAER;EAIA,SAAS,iBAAiB;;EAQ1B,iBAAiB,iBAAiB;UAI1B;;EAeR,SAAS;EAKT,SAAS,0BAA0B;EA2BnC,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`,detail:`model and thinking level`,order:10},c={key:`s`,label:`sessions`,detail:`history, forks and branches`,order:30},l={key:`e`,label:`extension`,detail:`tools, skills and config`,order:50},u={key:`h`,label:`help`,detail:`package docs and logs`,order:70},d={key:`q`,label:`quit`,detail:`leave the session`,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(`
|
|
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`,detail:`model and thinking level`,order:10},c={key:`s`,label:`sessions`,detail:`history, forks and branches`,order:30},l={key:`e`,label:`extension`,detail:`tools, skills and config`,order:50},u={key:`h`,label:`help`,detail:`package docs and logs`,order:70},d={key:`q`,label:`quit`,detail:`leave the session`,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(`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=e.tone==="default"||e.tone===`exit`?e.tone:void 0,d;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)):d=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?{tone:u}:{},...d===void 0?{}:{order:d}}}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.order===w(t)}function k(e){if(e.children.size===0)return+!!e.action;let t=0;for(let n of e.children.values())t+=k(n);return t}function A(e,t){let n=[],r=e,i=-1;for(let[e,a]of t.path.entries()){let o=r.children.get(a.key);if(!o){i=e;break}O(o,a)||n.push(h(t.source,`${D(t.path.slice(0,e+1))} keeps the label from ${o.owner}; the binding is registered.`,t.id)),!o.detail&&a.detail&&(o.detail=a.detail);let s=e===t.path.length-1,c=k(o);s&&c>0?(n.push(h(t.source,`${D(t.path)} taken over from ${o.owner}${c>1?`, displacing ${c} bindings`:``}.`,t.id)),o.children.clear(),delete o.action,delete o.bindingId):!s&&o.action&&(n.push(h(t.source,`${D(t.path.slice(0,e+1))} taken over from ${o.owner} to open a group.`,t.id)),delete o.action,delete o.bindingId),(s||o.action===void 0)&&(o.owner=t.source),r=o}let a=i===-1?t.path.length:i;for(let e=a;e<t.path.length;e+=1){let n=t.path[e],i={key:n.key,label:n.label,...n.detail?{detail:n.detail}:{},...n.tone?{tone:n.tone}:{},order:w(n),owner:t.source,children:new Map};r.children.set(n.key,i),r=i}return r.action=T(t.action),r.bindingId=t.id,r.owner=t.source,n}var j=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?(this.contributions.delete(t.contribution.source),t.contribution.bindings.length>0&&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}:{},...e.tone?{tone:e.tone}:{},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]=A(e,t);if(n)throw Error(`Invalid core leader binding: ${n.message}`)}for(let[n,r]of this.contributions.entries())for(let i of[...r].sort((e,t)=>e.id.localeCompare(t.id)))t.push(...A(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}}));this.root=e,this.diagnostics=t,this.dirty=!1;for(let e of this.listeners)e()}};export{j as DoomLeaderRegistry};
|
|
2
2
|
//# sourceMappingURL=leaderRegistry.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"leaderRegistry.mjs","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"sourcesContent":["type AppKeybinding =\n | 'app.model.select'\n | 'app.model.cycleForward'\n | 'app.thinking.cycle'\n | 'app.session.new'\n | 'app.session.resume'\n | 'app.session.tree'\n | 'app.session.fork'\n | 'app.editor.external'\n | 'app.exit';\n\nimport type {\n LeaderBinding as DoomLeaderBinding,\n LeaderCommand as DoomLeaderCommand,\n LeaderContribution as DoomLeaderContribution,\n LeaderAction as DoomLeaderExtensionAction,\n LeaderSegment as DoomLeaderSegment,\n LeaderTone as DoomLeaderTone,\n} from '@agimon-ai/doompi-extension-contracts/leader';\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 tone?: DoomLeaderTone;\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 tone?: DoomLeaderTone;\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', detail: 'model and thinking level', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', detail: 'history, forks and branches', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', detail: 'tools, skills and config', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', detail: 'package docs and logs', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', detail: 'leave the session', 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 // An unrecognised tone is ignored, not diagnosed. Every other malformed field\n // here rejects the whole contribution, which is right for a key or a label but\n // not for a badge colour: it would mean a package compiled against a later\n // contract loses its entire menu on an older host over a cosmetic value. The\n // union type already catches a typo at every call site built against this\n // contract, which is the only place a typo can come from.\n const tone: DoomLeaderTone | undefined = value.tone === 'default' || value.tone === 'exit' ? value.tone : undefined;\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 {\n key,\n label,\n ...(detail ? { detail } : {}),\n ...(tone ? { tone } : {}),\n ...(order === undefined ? {} : { order }),\n };\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\n/**\n * Group identity is the key, the label and the sort order. `detail` is left out\n * on purpose: it is a subtitle, and these packages ship on independent npm\n * versions, so requiring every co-contributor to a shared prefix to carry the\n * same wording means one upgraded package silently drops the menus of every\n * package that has not caught up. A subtitle disagreement is not a conflict\n * worth losing `SPC e` over.\n */\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && 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 // First writer wins for the subtitle, but a group created without one still\n // adopts the first that is offered, so the wording does not depend on which\n // package happened to register first.\n if (!existing.detail && segment.detail) existing.detail = segment.detail;\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 ...(segment.tone ? { tone: segment.tone } : {}),\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((node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n ...(node.tone ? { tone: node.tone } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\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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"}
|
|
1
|
+
{"version":3,"file":"leaderRegistry.mjs","names":[],"sources":["../../../src/services/leader/leaderRegistry.ts"],"sourcesContent":["type AppKeybinding =\n | 'app.model.select'\n | 'app.model.cycleForward'\n | 'app.thinking.cycle'\n | 'app.session.new'\n | 'app.session.resume'\n | 'app.session.tree'\n | 'app.session.fork'\n | 'app.editor.external'\n | 'app.exit';\n\nimport type {\n LeaderBinding as DoomLeaderBinding,\n LeaderCommand as DoomLeaderCommand,\n LeaderContribution as DoomLeaderContribution,\n LeaderAction as DoomLeaderExtensionAction,\n LeaderSegment as DoomLeaderSegment,\n LeaderTone as DoomLeaderTone,\n} from '@agimon-ai/doompi-extension-contracts/leader';\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 tone?: DoomLeaderTone;\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 tone?: DoomLeaderTone;\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', detail: 'model and thinking level', order: 10 } as const;\nconst SESSION_GROUP = { key: 's', label: 'sessions', detail: 'history, forks and branches', order: 30 } as const;\nconst EXTENSION_GROUP = { key: 'e', label: 'extension', detail: 'tools, skills and config', order: 50 } as const;\nconst HELP_GROUP = { key: 'h', label: 'help', detail: 'package docs and logs', order: 70 } as const;\nconst QUIT_GROUP = { key: 'q', label: 'quit', detail: 'leave the session', 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('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 // An unrecognised tone is ignored, not diagnosed. Every other malformed field\n // here rejects the whole contribution, which is right for a key or a label but\n // not for a badge colour: it would mean a package compiled against a later\n // contract loses its entire menu on an older host over a cosmetic value. The\n // union type already catches a typo at every call site built against this\n // contract, which is the only place a typo can come from.\n const tone: DoomLeaderTone | undefined = value.tone === 'default' || value.tone === 'exit' ? value.tone : undefined;\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 {\n key,\n label,\n ...(detail ? { detail } : {}),\n ...(tone ? { tone } : {}),\n ...(order === undefined ? {} : { order }),\n };\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\n/**\n * Whether a shared group prefix is worded the same way by both contributors.\n *\n * `detail` is left out on purpose: it is a subtitle, and these packages ship on\n * independent npm versions, so comparing it means one upgraded package disagrees\n * with every package that has not caught up.\n *\n * A mismatch is REPORTED, never fatal. Losing `SPC e s` because two packages\n * spell the `SPC e` group differently costs the user a key they can no longer\n * press, which is worse than a menu row worded by whoever registered first.\n */\nfunction metadataMatches(node: LeaderNode, segment: DoomLeaderSegment): boolean {\n return node.label === segment.label && node.order === optionOrder(segment);\n}\n\n/** Every binding under a node, for a takeover message that says what it displaced. */\nfunction leafCount(node: LeaderNode): number {\n if (node.children.size === 0) return node.action ? 1 : 0;\n let total = 0;\n for (const child of node.children.values()) total += leafCount(child);\n return total;\n}\n\n/**\n * Inserts one binding, taking a key over from whoever holds it.\n *\n * LAST REGISTRATION WINS, and the diagnostics say who lost what. The rule this\n * replaced rejected the newcomer, which read as \"warning plus nothing happened\":\n * the key stayed bound to the first registrant, or - when the clash was only a\n * group label - to nobody at all. A takeover is recoverable in a way a dropped\n * binding is not: the tree is rebuilt from every stored contribution, so when\n * the winner unregisters, the displaced binding comes back on the next rebuild.\n *\n * Returns diagnostics rather than one issue, because a single insert can both\n * disagree about a group's wording and take a key from someone.\n */\nfunction insertBinding(root: LeaderNode, binding: InternalLeaderBinding): DoomLeaderDiagnostic[] {\n const diagnostics: DoomLeaderDiagnostic[] = [];\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 diagnostics.push(\n diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} keeps the label from ${existing.owner}; the binding is registered.`,\n binding.id,\n ),\n );\n }\n // First writer wins for the subtitle, but a group created without one still\n // adopts the first that is offered, so the wording does not depend on which\n // package happened to register first.\n if (!existing.detail && segment.detail) existing.detail = segment.detail;\n\n const isLeaf = index === binding.path.length - 1;\n const displaced = leafCount(existing);\n if (isLeaf && displaced > 0) {\n diagnostics.push(\n diagnostic(\n binding.source,\n `${pathLabel(binding.path)} taken over from ${existing.owner}` +\n `${displaced > 1 ? `, displacing ${displaced} bindings` : ''}.`,\n binding.id,\n ),\n );\n existing.children.clear();\n delete existing.action;\n delete existing.bindingId;\n } else if (!isLeaf && existing.action) {\n diagnostics.push(\n diagnostic(\n binding.source,\n `${pathLabel(binding.path.slice(0, index + 1))} taken over from ${existing.owner} to open a group.`,\n binding.id,\n ),\n );\n delete existing.action;\n delete existing.bindingId;\n }\n if (isLeaf || existing.action === undefined) existing.owner = binding.source;\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 ...(segment.tone ? { tone: segment.tone } : {}),\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 current.owner = binding.source;\n return diagnostics;\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 // Deleted before it is set again, so re-registering moves a source to the\n // back of the insertion order: registering again IS claiming again.\n this.contributions.delete(parsed.contribution.source);\n if (parsed.contribution.bindings.length > 0) {\n this.contributions.set(parsed.contribution.source, parsed.contribution.bindings);\n }\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((node): DoomLeaderResolvedOption => ({\n key: node.key,\n label: node.label,\n ...(node.detail ? { detail: node.detail } : {}),\n ...(node.tone ? { tone: node.tone } : {}),\n hasChildren: node.children.size > 0,\n ...(node.action ? { action: cloneAction(node.action) } : {}),\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 // Core bindings land in an empty tree, so anything reported here is two\n // core bindings disagreeing with each other: a defect, not a contribution.\n const [issue] = insertBinding(nextRoot, binding);\n if (issue) throw new Error(`Invalid core leader binding: ${issue.message}`);\n }\n\n // Registration order, not alphabetical: the key belongs to whoever claimed\n // it last, and `apply` re-seats a source that registers again so a reload\n // counts as a fresh claim. Bindings inside one contribution stay sorted by\n // id, so a single source's own output is still deterministic.\n for (const [source, bindings] of this.contributions.entries()) {\n for (const binding of [...bindings].sort((left, right) => left.id.localeCompare(right.id))) {\n diagnostics.push(\n ...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 );\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":"AAuFA,MAAM,EAAc,uBACd,EAAiB,UAGjB,EAAmB,IASnB,EAAW,aACX,EAAkB,0BAClB,EAAc,mCACd,EAAoB,yBAGpB,EAAc,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,2BAA4B,MAAO,EAAG,EACzF,EAAgB,CAAE,IAAK,IAAK,MAAO,WAAY,OAAQ,8BAA+B,MAAO,EAAG,EAChG,EAAkB,CAAE,IAAK,IAAK,MAAO,YAAa,OAAQ,2BAA4B,MAAO,EAAG,EAChG,EAAa,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,wBAAyB,MAAO,EAAG,EACnF,EAAa,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,oBAAqB,MAAO,EAAG,EAErF,SAAS,EACP,EACA,EACA,EACuB,CACvB,MAAO,CAAE,KAAI,OAAQ,EAAa,OAAM,QAAO,CACjD,CAEA,MAAM,EAAkD,CACtD,EAAY,eAAgB,CAAC,EAAa,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,eAAgB,MAAO,EAAG,CAAC,EAAG,CAC3G,KAAM,MACN,OAAQ,kBACV,CAAC,EACD,EAAY,aAAc,CAAC,EAAa,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,cAAe,MAAO,EAAG,CAAC,EAAG,CACtG,KAAM,MACN,OAAQ,wBACV,CAAC,EACD,EAAY,iBAAkB,CAAC,EAAa,CAAE,IAAK,IAAK,MAAO,WAAY,OAAQ,cAAe,MAAO,EAAG,CAAC,EAAG,CAC9G,KAAM,MACN,OAAQ,oBACV,CAAC,EACD,EAAY,aAAc,CAAC,EAAiB,CAAE,IAAK,IAAK,MAAO,QAAS,OAAQ,eAAgB,MAAO,EAAG,CAAC,EAAG,CAC5G,KAAM,UACN,QAAS,CAAE,KAAM,OAAQ,CAC3B,CAAC,EAGD,EAAY,cAAe,CAAC,EAAiB,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,WAAY,MAAO,EAAG,CAAC,EAAG,CAC1G,KAAM,UACN,QAAS,CAAE,KAAM,QAAS,CAC5B,CAAC,EACD,EAAY,cAAe,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,MAAO,OAAQ,gBAAiB,MAAO,EAAG,CAAC,EAAG,CAC1G,KAAM,MACN,OAAQ,iBACV,CAAC,EACD,EAAY,iBAAkB,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,SAAU,OAAQ,eAAgB,MAAO,EAAG,CAAC,EAAG,CAC/G,KAAM,MACN,OAAQ,oBACV,CAAC,EACD,EAAY,eAAgB,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,cAAe,MAAO,EAAG,CAAC,EAAG,CAC1G,KAAM,MACN,OAAQ,kBACV,CAAC,EACD,EAAY,eAAgB,CAAC,EAAe,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,iBAAkB,MAAO,EAAG,CAAC,EAAG,CAC7G,KAAM,MACN,OAAQ,kBACV,CAAC,EACD,EAAY,eAAgB,CAAC,EAAY,CAAE,IAAK,IAAK,MAAO,UAAW,OAAQ,eAAgB,CAAC,EAAG,CACjG,KAAM,UACN,QAAS,CAAE,KAAM,SAAU,CAC7B,CAAC,EACD,EAAY,WAAY,CAAC,EAAY,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,SAAU,CAAC,EAAG,CACpF,KAAM,MACN,OAAQ,UACV,CAAC,CACH,EAEA,SAAS,EAAS,EAAkD,CAClE,OAAO,OAAO,GAAU,YAAY,GAAkB,CAAC,MAAM,QAAQ,CAAK,CAC5E,CAEA,SAAS,EAAW,EAAgB,EAAiB,EAA0C,CAC7F,MAAO,CAAE,SAAQ,UAAS,GAAI,EAAY,CAAE,WAAU,EAAI,CAAC,CAAG,CAChE,CAEA,SAAS,EAAyB,EAAwB,CACxD,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAM,OAAQ,GAAS,EAAG,CACpD,IAAM,EAAW,EAAM,WAAW,CAAK,EACvC,GAAI,GAAY,IAAkB,IAAa,IAAkB,MAAO,EAC1E,CACA,MAAO,EACT,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACoB,CACpB,GAAI,OAAO,GAAU,UAAY,CAAC,EAAM,KAAK,EAAG,CAC9C,EAAY,KAAK,EAAW,EAAQ,GAAG,EAAM,8BAA+B,CAAS,CAAC,EACtF,MACF,CACA,IAAM,EAAO,EAAM,KAAK,EACxB,GAAI,EAAK,OAAS,GAAiB,EAAyB,CAAI,EAAG,CACjE,EAAY,KACV,EACE,EACA,GAAG,EAAM,mBAAmB,EAAc,gDAC1C,CACF,CACF,EACA,MACF,CACA,OAAO,CACT,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EACA,EACoB,CAChB,OAAU,IAAA,GACd,OAAO,EAAiB,EAAO,EAAO,EAAe,EAAQ,EAAa,CAAS,CACrF,CAEA,SAAS,EACP,EACA,EACA,EACA,EACA,EAC+B,CAC/B,GAAI,CAAC,EAAS,CAAK,EAAG,CACpB,EAAY,KAAK,EAAW,EAAQ,mBAAmB,EAAM,sBAAuB,CAAS,CAAC,EAC9F,MACF,CAEA,IAAM,EAAM,EAAiB,EAAM,IAAK,mBAAmB,EAAM,OAAQ,EAAG,EAAQ,EAAa,CAAS,EACpG,EAAQ,EACZ,EAAM,MACN,mBAAmB,EAAM,SACzB,GACA,EACA,EACA,CACF,EACM,EAAS,EACb,EAAM,OACN,mBAAmB,EAAM,UACzB,GACA,EACA,EACA,CACF,EAOM,EAAmC,EAAM,OAAS,WAAa,EAAM,OAAS,OAAS,EAAM,KAAO,IAAA,GACtG,EAcJ,GAbI,EAAM,QAAU,IAAA,KACd,CAAC,OAAO,UAAU,EAAM,KAAK,GAAM,EAAM,MAAmB,GAAM,EAAM,MAAmB,EAC7F,EAAY,KACV,EACE,EACA,mBAAmB,EAAM,uCAAuC,EAAiB,GACjF,CACF,CACF,EAEA,EAAQ,EAAM,OAGd,CAAC,GAAO,CAAC,GAAS,CAAC,EAAS,KAAK,CAAG,EAAG,CACrC,GAAO,CAAC,EAAS,KAAK,CAAG,GAC3B,EAAY,KAAK,EAAW,EAAQ,mBAAmB,EAAM,mBAAmB,EAAS,GAAI,CAAS,CAAC,EAEzG,MACF,CACA,MAAO,CACL,MACA,QACA,GAAI,EAAS,CAAE,QAAO,EAAI,CAAC,EAC3B,GAAI,EAAO,CAAE,MAAK,EAAI,CAAC,EACvB,GAAI,IAAU,IAAA,GAAY,CAAC,EAAI,CAAE,OAAM,CACzC,CACF,CAEA,SAAS,EACP,EACA,EACA,EACA,EAC+B,CAC/B,GAAI,CAAC,EAAS,CAAK,EAAG,CACpB,EAAY,KAAK,EAAW,EAAQ,wCAAyC,CAAS,CAAC,EACvF,MACF,CACA,IAAM,EAAO,EACX,EAAM,KACN,0BACA,GACA,EACA,EACA,CACF,EACM,EAAO,EACX,EAAM,KACN,0BACA,IACA,EACA,EACA,CACF,EACA,GAAI,CAAC,GAAQ,CAAC,EAAkB,KAAK,CAAI,EAAG,CACtC,GAAQ,CAAC,EAAkB,KAAK,CAAI,GACtC,EAAY,KAAK,EAAW,EAAQ,sCAAsC,EAAkB,GAAI,CAAS,CAAC,EAE5G,MACF,CACA,MAAO,CAAE,OAAM,GAAI,EAAO,CAAE,MAAK,EAAI,CAAC,CAAG,CAC3C,CAEA,SAAS,EACP,EACA,EACA,EACA,EACuC,CACvC,GAAI,CAAC,EAAS,CAAK,EAAG,CACpB,EAAY,KAAK,EAAW,EAAQ,uCAAwC,CAAS,CAAC,EACtF,MACF,CACA,IAAM,EAAO,EACX,EAAM,KACN,yBACA,IACA,EACA,EACA,CACF,EACA,GAAI,CAAC,GAAQ,CAAC,EAAgB,KAAK,CAAI,EAAG,CACpC,GAAQ,CAAC,EAAgB,KAAK,CAAI,GACpC,EAAY,KAAK,EAAW,EAAQ,qCAAqC,EAAgB,GAAI,CAAS,CAAC,EAEzG,MACF,CACA,MAAO,CAAE,MAAK,CAChB,CAEA,SAAS,EACP,EACA,EACA,EAC+B,CAC/B,GAAI,CAAC,EAAS,CAAK,EAAG,CACpB,EAAY,KAAK,EAAW,EAAQ,+BAA+B,CAAC,EACpE,MACF,CACA,IAAM,EAAY,EAAiB,EAAM,GAAI,gBAAiB,IAAuB,EAAQ,CAAW,EACxG,GAAI,CAAC,EAAW,OAChB,GAAI,CAAC,EAAgB,KAAK,CAAS,EAAG,CACpC,EAAY,KAAK,EAAW,EAAQ,4BAA4B,EAAgB,GAAI,CAAS,CAAC,EAC9F,MACF,CACA,GAAI,CAAC,MAAM,QAAQ,EAAM,IAAI,GAAK,EAAM,KAAK,SAAW,GAAK,EAAM,KAAK,OAAS,EAAiB,CAChG,EAAY,KACV,EAAW,EAAQ,yDAA2E,CAAS,CACzG,EACA,MACF,CAEA,IAAM,EAAO,EAAM,KAAK,KAAK,EAAS,IAAU,EAAa,EAAS,EAAQ,EAAW,EAAO,CAAW,CAAC,EACtG,EAAa,OAAO,OAAO,EAAO,SAAS,EAEjD,GAAI,IADc,OAAO,OAAO,EAAO,QACZ,EAAG,CAC5B,EAAY,KAAK,EAAW,EAAQ,4DAA6D,CAAS,CAAC,EAC3G,MACF,CACA,IAAM,EAAS,EACX,CAAE,QAAS,EAAa,EAAM,QAAS,EAAQ,EAAW,CAAW,CAAE,EACvE,CAAE,OAAQ,EAAqB,EAAM,OAAQ,EAAQ,EAAW,CAAW,CAAE,EAC7E,MAAK,KAAM,GAAY,IAAY,IAAA,EAAS,EAChD,IAAI,YAAa,EAEf,OADK,EAAO,QACL,CACL,GAAI,EACE,OACN,QAAS,EAAO,OAClB,EALqB,OAOlB,KAAO,OACZ,MAAO,CACL,GAAI,EACE,OACN,OAAQ,EAAO,MACjB,CANA,CAOF,CAEA,SAAS,EAAkB,EAAoC,CAC7D,IAAM,EAAsC,CAAC,EAC7C,GAAI,CAAC,EAAS,CAAK,EACjB,MAAO,CAAE,YAAa,CAAC,EAAW,EAAgB,wCAAwC,CAAC,CAAE,EAE/F,IAAM,EAAS,EAAiB,EAAM,OAAQ,SAAU,IAAmB,EAAgB,CAAW,EACtG,GAAI,CAAC,EAAQ,MAAO,CAAE,aAAY,EAClC,GAAI,CAAC,EAAY,KAAK,CAAM,EAE1B,OADA,EAAY,KAAK,EAAW,EAAQ,qBAAqB,EAAY,EAAE,CAAC,EACjE,CAAE,aAAY,EAEvB,GAAI,CAAC,MAAM,QAAQ,EAAM,QAAQ,EAC/B,MAAO,CAAE,YAAa,CAAC,EAAW,EAAQ,4BAA4B,CAAC,CAAE,EAG3E,IAAM,EAAW,EAAM,SAAS,IAAK,GAAY,EAAa,EAAS,EAAQ,CAAW,CAAC,EACrF,EAAe,EAClB,QAAS,GAAa,EAAU,CAAC,EAAQ,EAAE,EAAI,CAAC,CAAE,CAAC,CACnD,QAAQ,EAAI,EAAO,IAAQ,EAAI,QAAQ,CAAE,IAAM,CAAK,EACvD,IAAK,IAAM,KAAM,IAAI,IAAI,CAAY,EACnC,EAAY,KAAK,EAAW,EAAQ,eAAe,EAAG,kBAAmB,CAAE,CAAC,EAI9E,OAFI,EAAY,OAAS,GAAK,EAAS,KAAM,GAAY,IAAY,IAAA,EAAS,EAAU,CAAE,aAAY,EAE/F,CACL,aAAc,CACZ,SACU,UACZ,EACA,aACF,CACF,CAEA,SAAS,EAAY,EAAoC,CACvD,OAAO,EAAQ,OAAS,GAC1B,CAEA,SAAS,EAAY,EAA4C,CAG/D,OAFI,EAAO,OAAS,UAAkB,CAAE,KAAM,UAAW,QAAS,CAAE,GAAG,EAAO,OAAQ,CAAE,EACpF,EAAO,OAAS,MAAc,CAAE,KAAM,MAAO,OAAQ,EAAO,MAAO,EAChE,CAAE,KAAM,YAAa,OAAQ,EAAO,OAAQ,OAAQ,CAAE,GAAG,EAAO,MAAO,CAAE,CAClF,CAEA,SAAS,GAA6B,CACpC,MAAO,CACL,IAAK,GACL,MAAO,SACP,MAAO,EACP,MAAO,EACP,SAAU,IAAI,GAChB,CACF,CAEA,SAAS,EAAU,EAA4C,CAC7D,MAAO,OAAO,EAAK,IAAK,GAAY,EAAQ,GAAG,CAAC,CAAC,KAAK,GAAG,GAC3D,CAaA,SAAS,EAAgB,EAAkB,EAAqC,CAC9E,OAAO,EAAK,QAAU,EAAQ,OAAS,EAAK,QAAU,EAAY,CAAO,CAC3E,CAGA,SAAS,EAAU,EAA0B,CAC3C,GAAI,EAAK,SAAS,OAAS,EAAG,MAAO,KAAK,OAC1C,IAAI,EAAQ,EACZ,IAAK,IAAM,KAAS,EAAK,SAAS,OAAO,EAAG,GAAS,EAAU,CAAK,EACpE,OAAO,CACT,CAeA,SAAS,EAAc,EAAkB,EAAwD,CAC/F,IAAM,EAAsC,CAAC,EACzC,EAAU,EACV,EAAe,GAEnB,IAAK,GAAM,CAAC,EAAO,KAAY,EAAQ,KAAK,QAAQ,EAAG,CACrD,IAAM,EAAW,EAAQ,SAAS,IAAI,EAAQ,GAAG,EACjD,GAAI,CAAC,EAAU,CACb,EAAe,EACf,KACF,CACK,EAAgB,EAAU,CAAO,GACpC,EAAY,KACV,EACE,EAAQ,OACR,GAAG,EAAU,EAAQ,KAAK,MAAM,EAAG,EAAQ,CAAC,CAAC,EAAE,wBAAwB,EAAS,MAAM,8BACtF,EAAQ,EACV,CACF,EAKE,CAAC,EAAS,QAAU,EAAQ,SAAQ,EAAS,OAAS,EAAQ,QAElE,IAAM,EAAS,IAAU,EAAQ,KAAK,OAAS,EACzC,EAAY,EAAU,CAAQ,EAChC,GAAU,EAAY,GACxB,EAAY,KACV,EACE,EAAQ,OACR,GAAG,EAAU,EAAQ,IAAI,EAAE,mBAAmB,EAAS,QAClD,EAAY,EAAI,gBAAgB,EAAU,WAAa,GAAG,GAC/D,EAAQ,EACV,CACF,EACA,EAAS,SAAS,MAAM,EACxB,OAAO,EAAS,OAChB,OAAO,EAAS,WACP,CAAC,GAAU,EAAS,SAC7B,EAAY,KACV,EACE,EAAQ,OACR,GAAG,EAAU,EAAQ,KAAK,MAAM,EAAG,EAAQ,CAAC,CAAC,EAAE,mBAAmB,EAAS,MAAM,mBACjF,EAAQ,EACV,CACF,EACA,OAAO,EAAS,OAChB,OAAO,EAAS,YAEd,GAAU,EAAS,SAAW,IAAA,MAAW,EAAS,MAAQ,EAAQ,QACtE,EAAU,CACZ,CAEA,IAAM,EAAQ,IAAiB,GAAK,EAAQ,KAAK,OAAS,EAC1D,IAAK,IAAI,EAAQ,EAAO,EAAQ,EAAQ,KAAK,OAAQ,GAAS,EAAG,CAC/D,IAAM,EAAU,EAAQ,KAAK,GACvB,EAAmB,CACvB,IAAK,EAAQ,IACb,MAAO,EAAQ,MACf,GAAI,EAAQ,OAAS,CAAE,OAAQ,EAAQ,MAAO,EAAI,CAAC,EACnD,GAAI,EAAQ,KAAO,CAAE,KAAM,EAAQ,IAAK,EAAI,CAAC,EAC7C,MAAO,EAAY,CAAO,EAC1B,MAAO,EAAQ,OACf,SAAU,IAAI,GAChB,EACA,EAAQ,SAAS,IAAI,EAAQ,IAAK,CAAI,EACtC,EAAU,CACZ,CAKA,MAHA,GAAQ,OAAS,EAAY,EAAQ,MAAM,EAC3C,EAAQ,UAAY,EAAQ,GAC5B,EAAQ,MAAQ,EAAQ,OACjB,CACT,CAEA,IAAa,EAAb,KAAgC,CAC9B,KAAe,EAAe,EAC9B,cAAiC,IAAI,IACrC,UAA6B,IAAI,IACjC,YAA8C,CAAC,EAC/C,MAAgB,GAEhB,aAAc,CACZ,KAAK,QAAQ,CACf,CAEA,SAAS,EAA8C,CACrD,IAAM,EAAS,KAAK,MAAM,CAAK,EAG/B,OAFK,EAAO,UACZ,KAAK,QAAQ,EACN,CAAE,SAAU,GAAM,YAAa,KAAK,eAAe,CAAE,GAF/B,CAG/B,CAGA,iBAAiB,EAA8C,CAC7D,OAAO,KAAK,MAAM,CAAK,CACzB,CAEA,MAAc,EAA8C,CAC1D,IAAM,EAAS,EAAkB,CAAK,EAUtC,OATK,EAAO,cAIZ,KAAK,cAAc,OAAO,EAAO,aAAa,MAAM,EAChD,EAAO,aAAa,SAAS,OAAS,GACxC,KAAK,cAAc,IAAI,EAAO,aAAa,OAAQ,EAAO,aAAa,QAAQ,EAEjF,KAAK,MAAQ,GACN,CAAE,SAAU,GAAM,YAAa,EAAO,WAAY,GATxB,CAAE,SAAU,GAAO,YAAa,EAAO,WAAY,CAUtF,CAGA,OAAgC,CAE9B,OADI,KAAK,OAAO,KAAK,QAAQ,EACtB,KAAK,YAAY,IAAK,IAAU,CAAE,GAAG,CAAK,EAAE,CACrD,CAEA,SAAS,EAAsD,CAC7D,KAAK,MAAM,EACX,IAAI,EAAU,KAAK,KACnB,IAAK,IAAM,KAAO,EAAM,CACtB,IAAM,EAAO,EAAQ,SAAS,IAAI,CAAG,EACrC,GAAI,CAAC,GAAQ,EAAK,OAAQ,OAC1B,EAAU,CACZ,CAOA,IAAM,EAAU,CAAC,GAAG,EAAQ,SAAS,OAAO,CAAC,CAAC,CAC3C,MAAM,EAAM,IAAU,EAAK,IAAI,cAAc,EAAM,GAAG,CAAC,CAAC,CACxD,IAAK,IAAoC,CACxC,IAAK,EAAK,IACV,MAAO,EAAK,MACZ,GAAI,EAAK,OAAS,CAAE,OAAQ,EAAK,MAAO,EAAI,CAAC,EAC7C,GAAI,EAAK,KAAO,CAAE,KAAM,EAAK,IAAK,EAAI,CAAC,EACvC,YAAa,EAAK,SAAS,KAAO,EAClC,GAAI,EAAK,OAAS,CAAE,OAAQ,EAAY,EAAK,MAAM,CAAE,EAAI,CAAC,CAC5D,EAAE,EACJ,MAAO,CAAE,MAAO,EAAQ,MAAO,SAAQ,CACzC,CAEA,gBAAyC,CACvC,OAAO,KAAK,MAAM,CACpB,CAEA,UAAU,EAAkC,CAE1C,OADA,KAAK,UAAU,IAAI,CAAQ,MACd,KAAK,UAAU,OAAO,CAAQ,CAC7C,CAEA,SAAwB,CACtB,IAAM,EAAW,EAAe,EAC1B,EAAsC,CAAC,EAC7C,IAAK,IAAM,KAAW,EAAe,CAGnC,GAAM,CAAC,GAAS,EAAc,EAAU,CAAO,EAC/C,GAAI,EAAO,MAAU,MAAM,gCAAgC,EAAM,SAAS,CAC5E,CAMA,IAAK,GAAM,CAAC,EAAQ,KAAa,KAAK,cAAc,QAAQ,EAC1D,IAAK,IAAM,IAAW,CAAC,GAAG,CAAQ,CAAC,CAAC,MAAM,EAAM,IAAU,EAAK,GAAG,cAAc,EAAM,EAAE,CAAC,EACvF,EAAY,KACV,GAAG,EAAc,EAAU,CACzB,GAAI,EAAQ,GACZ,SACA,KAAM,EAAQ,KACd,OACE,YAAa,EACT,CAAE,KAAM,UAAW,QAAS,EAAQ,OAAQ,EAC5C,CAAE,KAAM,YAAa,SAAQ,OAAQ,EAAQ,MAAO,CAC5D,CAAC,CACH,EAIJ,KAAK,KAAO,EACZ,KAAK,YAAc,EACnB,KAAK,MAAQ,GACb,IAAK,IAAM,KAAY,KAAK,UAAW,EAAS,CAClD,CACF"}
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configOverlay.cjs","names":["fitStyledLine","visibleWidth","DoomOverlay","matchesKey","DOOM_OVERLAY_ACCENT","truncateToWidth","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/tui/configOverlay.ts"],"sourcesContent":["/**\n * Configuration panel: the `SPC e c` overlay.\n *\n * Sections on the left, one per contributing extension, and that section's\n * fields on the right. The panel owns navigation and text entry; every value it\n * shows was published by an extension, and every change it makes is a request\n * back to whichever extension owns the field. Nothing here knows what a voice\n * model or an editor command is.\n *\n * It subscribes to the registry rather than snapshotting at open time, unlike\n * the tools browser next door, because a contributor can be doing long work\n * (fetching a model, running an installer) and the panel is where that progress\n * is meant to appear.\n *\n * There is no local echo of an edit. A typed value is sent and the field stays\n * pending until its owner republishes, so a rejected value comes back with the\n * reason attached instead of the panel showing a value that was never written.\n */\n\nimport type { ConfigChoice, ConfigField } from '@agimon-ai/doompi-extension-contracts/config';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport type { DoomConfigSectionView } from '@agimon-ai/doompi-extension-contracts/config';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from './doomOverlay.ts';\nimport { fitStyledLine } from './rendering.ts';\n\ntype Pane = 'sections' | 'fields';\ntype Mode = 'browse' | 'edit' | 'choices';\n\nconst TITLE = 'CONFIG';\nconst BREADCRUMB = 'SPC › e / c · config';\nconst ACTION_SET = 'set';\nconst ACTION_CLEAR = 'clear';\nconst ACTION_ABORT = 'abort';\nconst DEFAULT_CHOICE_ACTION = 'select';\n\nconst KEY_BACKSPACE = '\\x7f';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst KEY_TAB = '\\t';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\nconst MIN_WIDTH = 48;\nconst SECTION_PANE_RATIO = 1 / 3;\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst ELLIPSIS = '…';\nconst LABEL_COLUMN = 18;\nconst INDENT = ' ';\nconst EMPTY_MESSAGE = 'No extension has contributed settings to this session.';\nconst NO_FIELDS_MESSAGE = 'This section has no fields.';\nconst UNSET_PLACEHOLDER = 'not set';\nconst SECTION_HEADING = 'SECTIONS';\nconst PROGRESS_WIDTH = 24;\nconst ACTION_INPUT = 'input';\ntype ThemeColor = Parameters<Theme['fg']>[0];\nconst STEP_MARKERS: Readonly<Record<string, readonly [string, ThemeColor]>> = {\n pending: ['·', 'dim'],\n satisfied: ['✓', 'dim'],\n running: ['▸', 'accent'],\n done: ['✓', 'success'],\n failed: ['✗', 'error'],\n};\nconst PROGRESS_FULL = '█';\nconst PROGRESS_EMPTY = '░';\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nfunction isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\nfunction fitRow(text: string, width: number): string {\n return fitStyledLine(text, width, ELLIPSIS);\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fitRow(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fitRow(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\nfunction isSelectable(field: ConfigField): boolean {\n return field.kind !== 'info';\n}\n\nexport class ConfigOverlayComponent extends DoomOverlay {\n private readonly registry: DoomConfigStoreView;\n private readonly done: (result: undefined) => void;\n private readonly unsubscribe: () => void;\n private sections: readonly DoomConfigSectionView[];\n private pane: Pane = 'sections';\n private mode: Mode = 'browse';\n private sectionIndex = 0;\n private fieldIndex = 0;\n private choiceIndex = 0;\n private draft = '';\n /** Fields awaiting a reply, keyed by source, section, and field. */\n private readonly pending = new Set<string>();\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, registry: DoomConfigStoreView, done: (result: undefined) => void) {\n super(tui, theme);\n this.registry = registry;\n this.done = done;\n this.sections = registry.getSections();\n this.fieldIndex = this.firstSelectableField();\n this.unsubscribe = registry.subscribe(() => this.onSectionsChanged());\n }\n\n /** Info fields are labels, so the cursor never starts on one. */\n private firstSelectableField(): number {\n const index = this.currentFields().findIndex(isSelectable);\n return index < 0 ? 0 : index;\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n private onSectionsChanged(): void {\n this.sections = this.registry.getSections();\n // Any republish from an owner is its answer, so nothing stays pending past it.\n this.pending.clear();\n this.sectionIndex = Math.min(this.sectionIndex, Math.max(0, this.sections.length - 1));\n const fields = this.currentFields();\n this.fieldIndex = Math.min(this.fieldIndex, Math.max(0, fields.length - 1));\n if (!isSelectable(fields[this.fieldIndex] ?? { kind: 'info' })) this.fieldIndex = this.firstSelectableField();\n const field = this.currentField();\n // A field that grew an error keeps the draft so the typed value is not lost,\n // and an answer being typed survives the republish each output chunk causes.\n if (this.mode === 'edit' && !field?.statusText && !field?.awaitingInput) this.mode = 'browse';\n this.tui.requestRender();\n }\n\n private currentSection(): DoomConfigSectionView | undefined {\n return this.sections[this.sectionIndex];\n }\n\n private currentFields(): readonly ConfigField[] {\n return this.currentSection()?.fields ?? [];\n }\n\n private currentField(): ConfigField | undefined {\n return this.currentFields()[this.fieldIndex];\n }\n\n private currentChoices(): readonly ConfigChoice[] {\n return this.currentField()?.choices ?? [];\n }\n\n private pendingKey(field: ConfigField): string {\n return `${this.currentSection()?.source ?? ''}\u0000${this.currentSection()?.id ?? ''}\u0000${field.id}`;\n }\n\n private invoke(action: string, value?: string): void {\n const section = this.currentSection();\n const field = this.currentField();\n if (!section || !field) return;\n this.pending.add(this.pendingKey(field));\n this.registry.invoke({\n source: section.source,\n sectionId: section.id,\n fieldId: field.id,\n action,\n ...(value === undefined ? {} : { value }),\n });\n this.tui.requestRender();\n }\n\n private moveSection(delta: number): void {\n if (this.sections.length === 0) return;\n const next = Math.max(0, Math.min(this.sectionIndex + delta, this.sections.length - 1));\n if (next === this.sectionIndex) return;\n this.sectionIndex = next;\n this.fieldIndex = this.firstSelectableField();\n this.tui.requestRender();\n }\n\n /** Info fields are labels, so the cursor steps over them to the next control. */\n private moveField(delta: number): void {\n const fields = this.currentFields();\n const step = delta < 0 ? -1 : 1;\n let next = this.fieldIndex + step;\n while (next >= 0 && next < fields.length && !isSelectable(fields[next] as ConfigField)) next += step;\n if (next < 0 || next >= fields.length) return;\n this.fieldIndex = next;\n this.tui.requestRender();\n }\n\n private moveChoice(delta: number): void {\n const choices = this.currentChoices();\n if (choices.length === 0) return;\n const next = Math.max(0, Math.min(this.choiceIndex + delta, choices.length - 1));\n if (next === this.choiceIndex) return;\n this.choiceIndex = next;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (this.mode === 'edit') {\n this.handleEditInput(data);\n return;\n }\n if (this.mode === 'choices') {\n this.handleChoiceInput(data);\n return;\n }\n this.handleBrowseInput(data);\n }\n\n private handleBrowseInput(data: string): void {\n const field = this.currentField();\n if (matchesKey(data, 'ctrl+c')) {\n if (field?.busy) this.invoke(ACTION_ABORT);\n else this.done(undefined);\n return;\n }\n if (matchesKey(data, 'escape') || data === 'q') {\n // Escape backs out of the fields pane before it closes the panel.\n if (this.pane === 'fields') {\n this.pane = 'sections';\n this.tui.requestRender();\n return;\n }\n this.done(undefined);\n return;\n }\n if (data === KEY_TAB) {\n this.pane = this.pane === 'sections' ? 'fields' : 'sections';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n if (this.pane === 'sections') this.moveSection(-1);\n else this.moveField(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n if (this.pane === 'sections') this.moveSection(1);\n else this.moveField(1);\n return;\n }\n // While a field offers its own keys, those win: a pending confirmation is\n // answered before anything else can be typed.\n const offered = field?.actions?.find((action) => action.key === data);\n if (this.pane === 'fields' && offered) {\n this.invoke(offered.action);\n return;\n }\n // Answering a waiting command beats every other reading of `i`, and it is\n // the only thing that will make progress while one is blocked on a prompt.\n if (this.pane === 'fields' && field?.awaitingInput && data === 'i') {\n this.draft = '';\n this.mode = 'edit';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n this.activate();\n return;\n }\n if (this.pane === 'fields' && data === 'd' && field && isSelectable(field)) this.invoke(ACTION_CLEAR);\n }\n\n private activate(): void {\n if (this.pane === 'sections') {\n this.pane = 'fields';\n this.tui.requestRender();\n return;\n }\n const field = this.currentField();\n if (!field || field.busy || !isSelectable(field)) return;\n // `enum` and `choice` differ only in how many entries they carry, so both\n // open the same list with the cursor on whatever is currently set.\n if (field.kind === 'choice' || field.kind === 'enum') {\n this.choiceIndex = Math.max(\n 0,\n this.currentChoices().findIndex((choice) => choice.id === field.value),\n );\n this.mode = 'choices';\n this.tui.requestRender();\n return;\n }\n this.draft = field.value ?? '';\n this.mode = 'edit';\n this.tui.requestRender();\n }\n\n private handleEditInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.draft = '';\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n const value = this.draft.trim();\n // Answering a prompt is a different write from setting a field, and an\n // empty answer is meaningful there: it accepts the command's default.\n if (this.currentField()?.awaitingInput) {\n this.mode = 'browse';\n this.invoke(ACTION_INPUT, value.length === 0 ? undefined : value);\n this.draft = '';\n return;\n }\n // The parser rejects blank values, so clearing a key means removing it.\n this.invoke(value.length === 0 ? ACTION_CLEAR : ACTION_SET, value.length === 0 ? undefined : value);\n return;\n }\n if (data === KEY_BACKSPACE) this.draft = this.draft.slice(0, -1);\n else if (!isControlInput(data)) this.draft += data;\n this.tui.requestRender();\n }\n\n private handleChoiceInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.moveChoice(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.moveChoice(1);\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n const choice = this.currentChoices()[this.choiceIndex];\n if (!choice) return;\n this.mode = 'browse';\n // The entry names what enter does to it, which is what lets one list hold\n // both an installed thing to select and an absent one to fetch.\n this.invoke(choice.action ?? DEFAULT_CHOICE_ACTION, choice.id);\n }\n }\n\n private sectionLines(width: number): string[] {\n const lines = [this.theme.bold(SECTION_HEADING), ''];\n if (this.sections.length === 0) {\n lines.push(...wrap(EMPTY_MESSAGE, width).map((line) => this.theme.fg('dim', line)));\n return lines;\n }\n for (const [index, section] of this.sections.entries()) {\n const active = index === this.sectionIndex;\n const title = rightAligned(\n active ? this.theme.bold(section.title) : this.theme.fg('text', section.title),\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', section.detail ?? ''),\n width,\n );\n lines.push(active && this.pane === 'sections' ? this.theme.bg('selectedBg', fitRow(title, width)) : title);\n lines.push(this.theme.fg('dim', fitRow(`${INDENT}${section.source}`, width)));\n }\n return lines;\n }\n\n /**\n * The whole plan, not just the running step.\n *\n * Seeing that a package manager already finished and a download is now\n * underway is what makes a long install legible; a lone \"step 3 of 6\" is not.\n */\n private stepLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.steps ?? []).map((step) => {\n const [glyph, color] = STEP_MARKERS[step.state];\n const label = step.state === 'pending' ? this.theme.fg('dim', step.label) : step.label;\n const row = rightAligned(\n `${this.theme.fg(color, glyph)} ${label}`,\n this.theme.fg('dim', step.detail ?? ''),\n inner,\n );\n return `${INDENT}${row}`;\n });\n }\n\n /** The tail of the running command, so a prompt it printed is visible. */\n private outputLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.output ?? []).map((line) => `${INDENT}${this.theme.fg('dim', fitRow(line, inner))}`);\n }\n\n private progressBar(ratio: number | undefined, width: number): string {\n if (ratio === undefined) return '';\n const filled = Math.max(0, Math.min(PROGRESS_WIDTH, Math.round(ratio * PROGRESS_WIDTH)));\n const bar = PROGRESS_FULL.repeat(filled) + PROGRESS_EMPTY.repeat(PROGRESS_WIDTH - filled);\n return fitRow(`${bar} ${Math.round(ratio * 100)}%`, width);\n }\n\n private fieldLines(width: number): string[] {\n const section = this.currentSection();\n if (!section) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n const lines = [this.theme.bold(section.title), ''];\n if (section.notice) {\n lines.push(\n ...wrap(section.notice, width).map((line) =>\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', line),\n ),\n );\n lines.push('');\n }\n if (section.fields.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n\n const valueWidth = Math.max(1, width - LABEL_COLUMN);\n for (const [index, field] of section.fields.entries()) {\n const active = index === this.fieldIndex && this.pane === 'fields';\n const editing = active && this.mode === 'edit';\n const isPending = this.pending.has(this.pendingKey(field));\n const shown = editing ? `${this.draft}█` : (field.value ?? field.placeholder ?? UNSET_PLACEHOLDER);\n const tone = editing || field.value ? 'text' : 'dim';\n const value = this.theme.fg(isPending || field.busy ? 'dim' : tone, shown);\n const status = field.statusText ?? (isPending ? '' : '');\n const row = `${this.theme.fg('dim', fitRow(field.label, LABEL_COLUMN))}${\n status\n ? rightAligned(value, this.theme.fg(field.status === 'blocked' ? 'error' : 'dim', status), valueWidth)\n : fitRow(value, valueWidth)\n }`;\n lines.push(active ? this.theme.bg('selectedBg', fitRow(row, width)) : row);\n lines.push(...this.stepLines(field, width));\n if (field.busy && field.progress) {\n lines.push(this.theme.fg('dim', fitRow(`${INDENT}${field.progress.label}`, width)));\n const bar = this.progressBar(field.progress.ratio, valueWidth);\n if (bar) lines.push(`${INDENT}${this.theme.fg('accent', bar)}`);\n }\n lines.push(...this.outputLines(field, width));\n if (active && field.awaitingInput) {\n // The command is asking something. Answering is the only useful action,\n // so the line is offered without having to enter a mode first.\n const prompt = this.mode === 'edit' ? `${this.draft}█` : 'press i to answer';\n lines.push(\n `${INDENT}${this.theme.fg('warning', fitRow(`waiting for input ${prompt}`, width - INDENT.length))}`,\n );\n }\n if (!active) continue;\n if (field.detail) {\n lines.push(\n ...wrap(field.detail, Math.max(1, width - LABEL_COLUMN)).map(\n (line) => `${' '.repeat(LABEL_COLUMN)}${this.theme.fg('dim', line)}`,\n ),\n );\n }\n if (field.keyPath) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.theme.fg('accent', field.keyPath)}`);\n if (field.actions && field.actions.length > 0) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.hintLine()}`);\n }\n return lines;\n }\n\n private choiceLines(width: number): string[] {\n const field = this.currentField();\n const choices = this.currentChoices();\n const lines = [this.theme.bold(field?.label ?? ''), ''];\n if (choices.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n const budget = Math.max(1, this.bodyHeight - lines.length);\n const selected = Math.max(0, Math.min(this.choiceIndex, choices.length - 1));\n const start = Math.max(0, Math.min(selected - budget + 1, Math.max(0, choices.length - budget)));\n let lastGroup: string | undefined;\n for (const [offset, choice] of choices.slice(start, start + budget).entries()) {\n if (choice.group && choice.group !== lastGroup) {\n lastGroup = choice.group;\n lines.push(this.theme.fg('accent', fitRow(choice.group, width)));\n }\n const status = choice.statusText ?? choice.status ?? '';\n const row = rightAligned(\n `${INDENT}${choice.label}${choice.detail ? this.theme.fg('dim', ` ${choice.detail}`) : ''}`,\n this.theme.fg(choice.status === 'blocked' ? 'error' : 'dim', status),\n width,\n );\n lines.push(start + offset === selected ? this.theme.bg('selectedBg', fitRow(row, width)) : row);\n }\n return lines;\n }\n\n private hints(): readonly (readonly [string, string])[] {\n if (this.mode === 'edit') {\n return [\n ['⌫', 'delete'],\n ['enter', this.currentField()?.awaitingInput ? 'send' : 'save'],\n ['esc', 'cancel'],\n ];\n }\n if (this.mode === 'choices') {\n const choice = this.currentChoices()[this.choiceIndex];\n // The hint names what enter does to the entry under the cursor, because on\n // this list that differs row by row.\n return [\n ['↑↓', 'move'],\n ['enter', choice?.action ?? DEFAULT_CHOICE_ACTION],\n ['esc', 'back'],\n ];\n }\n const field = this.currentField();\n if (field?.awaitingInput) {\n return [\n ['i', 'answer'],\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n if (field?.busy) {\n return [\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n const offered = (field?.actions ?? []).map((action) => [action.key, action.label] as const);\n return [\n ['↑↓', 'move'],\n ['tab', 'pane'],\n ['enter', this.pane === 'sections' ? 'fields' : 'edit'],\n ...offered,\n ...(this.pane === 'fields' && offered.length === 0 ? ([['d', 'clear']] as const) : []),\n ['esc', this.pane === 'fields' ? 'back' : 'close'],\n ];\n }\n\n private hintLine(): string {\n return this.hints()\n .map(\n ([key, label]) =>\n `${this.theme.bg('selectedBg', this.theme.fg('text', ` ${key} `))}${this.theme.fg('dim', ` ${label}`)}`,\n )\n .join(this.theme.fg('dim', ' '));\n }\n\n protected getChrome(): DoomOverlayChrome {\n const section = this.currentSection();\n const field = this.currentField();\n const breadcrumb =\n this.mode === 'choices' && section && field ? `${BREADCRUMB} › ${section.title} / ${field.label}` : BREADCRUMB;\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb,\n headerRight: `${this.sections.length} sections · ${this.sections.length === 0 ? 'none' : (section?.source ?? '')}`,\n footer: this.hintLine(),\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n this.bodyHeight = height;\n const right = this.mode === 'choices' ? this.choiceLines.bind(this) : this.fieldLines.bind(this);\n if (width < MIN_WIDTH) return this.renderStacked(width, height, right);\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * SECTION_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.sectionLines(leftContent);\n const rightLines = right(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fitRow(left[index] ?? '', leftContent)} ${divider} ${fitRow(rightLines[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number, right: (width: number) => string[]): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.sectionLines(width);\n const bottom = right(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n}\n\nexport interface DoomConfigStoreView {\n getSections(): DoomConfigSectionView[];\n invoke(invocation: { source: string; sectionId: string; fieldId: string; action: string; value?: string }): void;\n subscribe(listener: () => void): () => void;\n}\n\nexport async function openConfigOverlay(ctx: ExtensionContext, registry: DoomConfigStoreView): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new ConfigOverlayComponent(tui, theme, registry, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"configOverlay.cjs","names":["fitStyledLine","visibleWidth","DoomOverlay","matchesKey","DOOM_OVERLAY_ACCENT","truncateToWidth","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/tui/configOverlay.ts"],"sourcesContent":["/**\n * Configuration panel: the `SPC e c` overlay.\n *\n * Sections on the left, one per contributing extension, and that section's\n * fields on the right. The panel owns navigation and text entry; every value it\n * shows was published by an extension, and every change it makes is a request\n * back to whichever extension owns the field. Nothing here knows what a voice\n * model or an editor command is.\n *\n * It subscribes to the registry rather than snapshotting at open time, unlike\n * the tools browser next door, because a contributor can be doing long work\n * (fetching a model, running an installer) and the panel is where that progress\n * is meant to appear.\n *\n * There is no local echo of an edit. A typed value is sent and the field stays\n * pending until its owner republishes, so a rejected value comes back with the\n * reason attached instead of the panel showing a value that was never written.\n */\n\nimport type { ConfigChoice, ConfigField } from '@agimon-ai/doompi-extension-contracts/config';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport type { DoomConfigSectionView } from '@agimon-ai/doompi-extension-contracts/config';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from './doomOverlay.ts';\nimport { fitStyledLine } from './rendering.ts';\n\ntype Pane = 'sections' | 'fields';\ntype Mode = 'browse' | 'edit' | 'choices';\n\nconst TITLE = 'CONFIG';\nconst BREADCRUMB = 'SPC › e / c · config';\nconst ACTION_SET = 'set';\nconst ACTION_CLEAR = 'clear';\nconst ACTION_ABORT = 'abort';\nconst DEFAULT_CHOICE_ACTION = 'select';\n\nconst KEY_BACKSPACE = '\\x7f';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst KEY_TAB = '\\t';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\nconst MIN_WIDTH = 48;\nconst SECTION_PANE_RATIO = 1 / 3;\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst ELLIPSIS = '…';\nconst LABEL_COLUMN = 18;\nconst INDENT = ' ';\nconst EMPTY_MESSAGE = 'No extension has contributed settings to this session.';\nconst NO_FIELDS_MESSAGE = 'This section has no fields.';\nconst UNSET_PLACEHOLDER = 'not set';\nconst SECTION_HEADING = 'SECTIONS';\nconst HELP_BADGE = ' HELP ';\n/**\n * Every field here is published by a package, so the panel cannot explain what\n * any one of them means. Help mode can: it loads the owning package's docs into\n * the session, and the agent reads them and answers from there.\n */\nconst HELP_HINT = 'Not sure what a setting does? SPC h e loads package Help, then ask the agent about it.';\n/** Body rows below which the banner is dropped: a field outranks a pointer to the docs. */\nconst HELP_BANNER_MIN_ROWS = 10;\nconst PROGRESS_WIDTH = 24;\nconst ACTION_INPUT = 'input';\ntype ThemeColor = Parameters<Theme['fg']>[0];\nconst STEP_MARKERS: Readonly<Record<string, readonly [string, ThemeColor]>> = {\n pending: ['·', 'dim'],\n satisfied: ['✓', 'dim'],\n running: ['▸', 'accent'],\n done: ['✓', 'success'],\n failed: ['✗', 'error'],\n};\nconst PROGRESS_FULL = '█';\nconst PROGRESS_EMPTY = '░';\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nfunction isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\nfunction fitRow(text: string, width: number): string {\n return fitStyledLine(text, width, ELLIPSIS);\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fitRow(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fitRow(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\nfunction isSelectable(field: ConfigField): boolean {\n return field.kind !== 'info';\n}\n\nexport class ConfigOverlayComponent extends DoomOverlay {\n private readonly registry: DoomConfigStoreView;\n private readonly done: (result: undefined) => void;\n private readonly unsubscribe: () => void;\n private sections: readonly DoomConfigSectionView[];\n private pane: Pane = 'sections';\n private mode: Mode = 'browse';\n private sectionIndex = 0;\n private fieldIndex = 0;\n private choiceIndex = 0;\n private draft = '';\n /** Fields awaiting a reply, keyed by source, section, and field. */\n private readonly pending = new Set<string>();\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, registry: DoomConfigStoreView, done: (result: undefined) => void) {\n super(tui, theme);\n this.registry = registry;\n this.done = done;\n this.sections = registry.getSections();\n this.fieldIndex = this.firstSelectableField();\n this.unsubscribe = registry.subscribe(() => this.onSectionsChanged());\n }\n\n /** Info fields are labels, so the cursor never starts on one. */\n private firstSelectableField(): number {\n const index = this.currentFields().findIndex(isSelectable);\n return index < 0 ? 0 : index;\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n private onSectionsChanged(): void {\n this.sections = this.registry.getSections();\n // Any republish from an owner is its answer, so nothing stays pending past it.\n this.pending.clear();\n this.sectionIndex = Math.min(this.sectionIndex, Math.max(0, this.sections.length - 1));\n const fields = this.currentFields();\n this.fieldIndex = Math.min(this.fieldIndex, Math.max(0, fields.length - 1));\n if (!isSelectable(fields[this.fieldIndex] ?? { kind: 'info' })) this.fieldIndex = this.firstSelectableField();\n const field = this.currentField();\n // A field that grew an error keeps the draft so the typed value is not lost,\n // and an answer being typed survives the republish each output chunk causes.\n if (this.mode === 'edit' && !field?.statusText && !field?.awaitingInput) this.mode = 'browse';\n this.tui.requestRender();\n }\n\n private currentSection(): DoomConfigSectionView | undefined {\n return this.sections[this.sectionIndex];\n }\n\n private currentFields(): readonly ConfigField[] {\n return this.currentSection()?.fields ?? [];\n }\n\n private currentField(): ConfigField | undefined {\n return this.currentFields()[this.fieldIndex];\n }\n\n private currentChoices(): readonly ConfigChoice[] {\n return this.currentField()?.choices ?? [];\n }\n\n private pendingKey(field: ConfigField): string {\n return `${this.currentSection()?.source ?? ''}\u0000${this.currentSection()?.id ?? ''}\u0000${field.id}`;\n }\n\n private invoke(action: string, value?: string): void {\n const section = this.currentSection();\n const field = this.currentField();\n if (!section || !field) return;\n this.pending.add(this.pendingKey(field));\n this.registry.invoke({\n source: section.source,\n sectionId: section.id,\n fieldId: field.id,\n action,\n ...(value === undefined ? {} : { value }),\n });\n this.tui.requestRender();\n }\n\n private moveSection(delta: number): void {\n if (this.sections.length === 0) return;\n const next = Math.max(0, Math.min(this.sectionIndex + delta, this.sections.length - 1));\n if (next === this.sectionIndex) return;\n this.sectionIndex = next;\n this.fieldIndex = this.firstSelectableField();\n this.tui.requestRender();\n }\n\n /** Info fields are labels, so the cursor steps over them to the next control. */\n private moveField(delta: number): void {\n const fields = this.currentFields();\n const step = delta < 0 ? -1 : 1;\n let next = this.fieldIndex + step;\n while (next >= 0 && next < fields.length && !isSelectable(fields[next] as ConfigField)) next += step;\n if (next < 0 || next >= fields.length) return;\n this.fieldIndex = next;\n this.tui.requestRender();\n }\n\n private moveChoice(delta: number): void {\n const choices = this.currentChoices();\n if (choices.length === 0) return;\n const next = Math.max(0, Math.min(this.choiceIndex + delta, choices.length - 1));\n if (next === this.choiceIndex) return;\n this.choiceIndex = next;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (this.mode === 'edit') {\n this.handleEditInput(data);\n return;\n }\n if (this.mode === 'choices') {\n this.handleChoiceInput(data);\n return;\n }\n this.handleBrowseInput(data);\n }\n\n private handleBrowseInput(data: string): void {\n const field = this.currentField();\n if (matchesKey(data, 'ctrl+c')) {\n if (field?.busy) this.invoke(ACTION_ABORT);\n else this.done(undefined);\n return;\n }\n if (matchesKey(data, 'escape') || data === 'q') {\n // Escape backs out of the fields pane before it closes the panel.\n if (this.pane === 'fields') {\n this.pane = 'sections';\n this.tui.requestRender();\n return;\n }\n this.done(undefined);\n return;\n }\n if (data === KEY_TAB) {\n this.pane = this.pane === 'sections' ? 'fields' : 'sections';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n if (this.pane === 'sections') this.moveSection(-1);\n else this.moveField(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n if (this.pane === 'sections') this.moveSection(1);\n else this.moveField(1);\n return;\n }\n // While a field offers its own keys, those win: a pending confirmation is\n // answered before anything else can be typed.\n const offered = field?.actions?.find((action) => action.key === data);\n if (this.pane === 'fields' && offered) {\n this.invoke(offered.action);\n return;\n }\n // Answering a waiting command beats every other reading of `i`, and it is\n // the only thing that will make progress while one is blocked on a prompt.\n if (this.pane === 'fields' && field?.awaitingInput && data === 'i') {\n this.draft = '';\n this.mode = 'edit';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n this.activate();\n return;\n }\n if (this.pane === 'fields' && data === 'd' && field && isSelectable(field)) this.invoke(ACTION_CLEAR);\n }\n\n private activate(): void {\n if (this.pane === 'sections') {\n this.pane = 'fields';\n this.tui.requestRender();\n return;\n }\n const field = this.currentField();\n if (!field || field.busy || !isSelectable(field)) return;\n // `enum` and `choice` differ only in how many entries they carry, so both\n // open the same list with the cursor on whatever is currently set.\n if (field.kind === 'choice' || field.kind === 'enum') {\n this.choiceIndex = Math.max(\n 0,\n this.currentChoices().findIndex((choice) => choice.id === field.value),\n );\n this.mode = 'choices';\n this.tui.requestRender();\n return;\n }\n this.draft = field.value ?? '';\n this.mode = 'edit';\n this.tui.requestRender();\n }\n\n private handleEditInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.draft = '';\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n const value = this.draft.trim();\n // Answering a prompt is a different write from setting a field, and an\n // empty answer is meaningful there: it accepts the command's default.\n if (this.currentField()?.awaitingInput) {\n this.mode = 'browse';\n this.invoke(ACTION_INPUT, value.length === 0 ? undefined : value);\n this.draft = '';\n return;\n }\n // The parser rejects blank values, so clearing a key means removing it.\n this.invoke(value.length === 0 ? ACTION_CLEAR : ACTION_SET, value.length === 0 ? undefined : value);\n return;\n }\n if (data === KEY_BACKSPACE) this.draft = this.draft.slice(0, -1);\n else if (!isControlInput(data)) this.draft += data;\n this.tui.requestRender();\n }\n\n private handleChoiceInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.moveChoice(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.moveChoice(1);\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n const choice = this.currentChoices()[this.choiceIndex];\n if (!choice) return;\n this.mode = 'browse';\n // The entry names what enter does to it, which is what lets one list hold\n // both an installed thing to select and an absent one to fetch.\n this.invoke(choice.action ?? DEFAULT_CHOICE_ACTION, choice.id);\n }\n }\n\n private sectionLines(width: number): string[] {\n const lines = [this.theme.bold(SECTION_HEADING), ''];\n if (this.sections.length === 0) {\n lines.push(...wrap(EMPTY_MESSAGE, width).map((line) => this.theme.fg('dim', line)));\n return lines;\n }\n for (const [index, section] of this.sections.entries()) {\n const active = index === this.sectionIndex;\n const title = rightAligned(\n active ? this.theme.bold(section.title) : this.theme.fg('text', section.title),\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', section.detail ?? ''),\n width,\n );\n lines.push(active && this.pane === 'sections' ? this.theme.bg('selectedBg', fitRow(title, width)) : title);\n lines.push(this.theme.fg('dim', fitRow(`${INDENT}${section.source}`, width)));\n }\n return lines;\n }\n\n /**\n * The whole plan, not just the running step.\n *\n * Seeing that a package manager already finished and a download is now\n * underway is what makes a long install legible; a lone \"step 3 of 6\" is not.\n */\n private stepLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.steps ?? []).map((step) => {\n const [glyph, color] = STEP_MARKERS[step.state];\n const label = step.state === 'pending' ? this.theme.fg('dim', step.label) : step.label;\n const row = rightAligned(\n `${this.theme.fg(color, glyph)} ${label}`,\n this.theme.fg('dim', step.detail ?? ''),\n inner,\n );\n return `${INDENT}${row}`;\n });\n }\n\n /** The tail of the running command, so a prompt it printed is visible. */\n private outputLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.output ?? []).map((line) => `${INDENT}${this.theme.fg('dim', fitRow(line, inner))}`);\n }\n\n private progressBar(ratio: number | undefined, width: number): string {\n if (ratio === undefined) return '';\n const filled = Math.max(0, Math.min(PROGRESS_WIDTH, Math.round(ratio * PROGRESS_WIDTH)));\n const bar = PROGRESS_FULL.repeat(filled) + PROGRESS_EMPTY.repeat(PROGRESS_WIDTH - filled);\n return fitRow(`${bar} ${Math.round(ratio * 100)}%`, width);\n }\n\n private fieldLines(width: number): string[] {\n const section = this.currentSection();\n if (!section) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n const lines = [this.theme.bold(section.title), ''];\n if (section.notice) {\n lines.push(\n ...wrap(section.notice, width).map((line) =>\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', line),\n ),\n );\n lines.push('');\n }\n if (section.fields.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n\n const valueWidth = Math.max(1, width - LABEL_COLUMN);\n for (const [index, field] of section.fields.entries()) {\n const active = index === this.fieldIndex && this.pane === 'fields';\n const editing = active && this.mode === 'edit';\n const isPending = this.pending.has(this.pendingKey(field));\n const shown = editing ? `${this.draft}█` : (field.value ?? field.placeholder ?? UNSET_PLACEHOLDER);\n const tone = editing || field.value ? 'text' : 'dim';\n const value = this.theme.fg(isPending || field.busy ? 'dim' : tone, shown);\n const status = field.statusText ?? (isPending ? '' : '');\n const row = `${this.theme.fg('dim', fitRow(field.label, LABEL_COLUMN))}${\n status\n ? rightAligned(value, this.theme.fg(field.status === 'blocked' ? 'error' : 'dim', status), valueWidth)\n : fitRow(value, valueWidth)\n }`;\n lines.push(active ? this.theme.bg('selectedBg', fitRow(row, width)) : row);\n lines.push(...this.stepLines(field, width));\n if (field.busy && field.progress) {\n lines.push(this.theme.fg('dim', fitRow(`${INDENT}${field.progress.label}`, width)));\n const bar = this.progressBar(field.progress.ratio, valueWidth);\n if (bar) lines.push(`${INDENT}${this.theme.fg('accent', bar)}`);\n }\n lines.push(...this.outputLines(field, width));\n if (active && field.awaitingInput) {\n // The command is asking something. Answering is the only useful action,\n // so the line is offered without having to enter a mode first.\n const prompt = this.mode === 'edit' ? `${this.draft}█` : 'press i to answer';\n lines.push(\n `${INDENT}${this.theme.fg('warning', fitRow(`waiting for input ${prompt}`, width - INDENT.length))}`,\n );\n }\n if (!active) continue;\n if (field.detail) {\n lines.push(\n ...wrap(field.detail, Math.max(1, width - LABEL_COLUMN)).map(\n (line) => `${' '.repeat(LABEL_COLUMN)}${this.theme.fg('dim', line)}`,\n ),\n );\n }\n if (field.keyPath) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.theme.fg('accent', field.keyPath)}`);\n if (field.actions && field.actions.length > 0) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.hintLine()}`);\n }\n return lines;\n }\n\n private choiceLines(width: number): string[] {\n const field = this.currentField();\n const choices = this.currentChoices();\n const lines = [this.theme.bold(field?.label ?? ''), ''];\n if (choices.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n const budget = Math.max(1, this.bodyHeight - lines.length);\n const selected = Math.max(0, Math.min(this.choiceIndex, choices.length - 1));\n const start = Math.max(0, Math.min(selected - budget + 1, Math.max(0, choices.length - budget)));\n let lastGroup: string | undefined;\n for (const [offset, choice] of choices.slice(start, start + budget).entries()) {\n if (choice.group && choice.group !== lastGroup) {\n lastGroup = choice.group;\n lines.push(this.theme.fg('accent', fitRow(choice.group, width)));\n }\n const status = choice.statusText ?? choice.status ?? '';\n const row = rightAligned(\n `${INDENT}${choice.label}${choice.detail ? this.theme.fg('dim', ` ${choice.detail}`) : ''}`,\n this.theme.fg(choice.status === 'blocked' ? 'error' : 'dim', status),\n width,\n );\n lines.push(start + offset === selected ? this.theme.bg('selectedBg', fitRow(row, width)) : row);\n }\n return lines;\n }\n\n private hints(): readonly (readonly [string, string])[] {\n if (this.mode === 'edit') {\n return [\n ['⌫', 'delete'],\n ['enter', this.currentField()?.awaitingInput ? 'send' : 'save'],\n ['esc', 'cancel'],\n ];\n }\n if (this.mode === 'choices') {\n const choice = this.currentChoices()[this.choiceIndex];\n // The hint names what enter does to the entry under the cursor, because on\n // this list that differs row by row.\n return [\n ['↑↓', 'move'],\n ['enter', choice?.action ?? DEFAULT_CHOICE_ACTION],\n ['esc', 'back'],\n ];\n }\n const field = this.currentField();\n if (field?.awaitingInput) {\n return [\n ['i', 'answer'],\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n if (field?.busy) {\n return [\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n const offered = (field?.actions ?? []).map((action) => [action.key, action.label] as const);\n return [\n ['↑↓', 'move'],\n ['tab', 'pane'],\n ['enter', this.pane === 'sections' ? 'fields' : 'edit'],\n ...offered,\n ...(this.pane === 'fields' && offered.length === 0 ? ([['d', 'clear']] as const) : []),\n ['esc', this.pane === 'fields' ? 'back' : 'close'],\n ];\n }\n\n private hintLine(): string {\n return this.hints()\n .map(\n ([key, label]) =>\n `${this.theme.bg('selectedBg', this.theme.fg('text', ` ${key} `))}${this.theme.fg('dim', ` ${label}`)}`,\n )\n .join(this.theme.fg('dim', ' '));\n }\n\n protected getChrome(): DoomOverlayChrome {\n const section = this.currentSection();\n const field = this.currentField();\n const breadcrumb =\n this.mode === 'choices' && section && field ? `${BREADCRUMB} › ${section.title} / ${field.label}` : BREADCRUMB;\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb,\n headerRight: `${this.sections.length} sections · ${this.sections.length === 0 ? 'none' : (section?.source ?? '')}`,\n footer: this.hintLine(),\n };\n }\n\n /**\n * The one line the panel says in its own voice, kept at the bottom where it\n * does not push a section's fields around. Dropped entirely when the body is\n * too short to spare the rows: a field the user came here to change outranks\n * a pointer to the docs.\n */\n private helpBanner(width: number): string[] {\n const badge = this.theme.inverse(this.theme.bold(this.theme.fg('accent', HELP_BADGE)));\n return ['', fitRow(`${badge} ${this.theme.fg('dim', HELP_HINT)}`, width)];\n }\n\n protected renderBody(width: number, height: number): string[] {\n const banner = height >= HELP_BANNER_MIN_ROWS ? this.helpBanner(width) : [];\n const bodyHeight = Math.max(0, height - banner.length);\n this.bodyHeight = bodyHeight;\n const right = this.mode === 'choices' ? this.choiceLines.bind(this) : this.fieldLines.bind(this);\n if (width < MIN_WIDTH) return [...this.renderStacked(width, bodyHeight, right), ...banner];\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * SECTION_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.sectionLines(leftContent);\n const rightLines = right(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n const rows = Array.from({ length: bodyHeight }, (_, index) => {\n const row = `${fitRow(left[index] ?? '', leftContent)} ${divider} ${fitRow(rightLines[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n return [...rows, ...banner];\n }\n\n private renderStacked(width: number, height: number, right: (width: number) => string[]): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.sectionLines(width);\n const bottom = right(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n}\n\nexport interface DoomConfigStoreView {\n getSections(): DoomConfigSectionView[];\n invoke(invocation: { source: string; sectionId: string; fieldId: string; action: string; value?: string }): void;\n subscribe(listener: () => void): () => void;\n}\n\nexport async function openConfigOverlay(ctx: ExtensionContext, registry: DoomConfigStoreView): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new ConfigOverlayComponent(tui, theme, registry, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
@@ -52,6 +52,13 @@ declare class ConfigOverlayComponent extends DoomOverlay {
|
|
|
52
52
|
private hints;
|
|
53
53
|
private hintLine;
|
|
54
54
|
protected getChrome(): DoomOverlayChrome;
|
|
55
|
+
/**
|
|
56
|
+
* The one line the panel says in its own voice, kept at the bottom where it
|
|
57
|
+
* does not push a section's fields around. Dropped entirely when the body is
|
|
58
|
+
* too short to spare the rows: a field the user came here to change outranks
|
|
59
|
+
* a pointer to the docs.
|
|
60
|
+
*/
|
|
61
|
+
private helpBanner;
|
|
55
62
|
protected renderBody(width: number, height: number): string[];
|
|
56
63
|
private renderStacked;
|
|
57
64
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configOverlay.d.cts","names":[],"sources":["../../src/tui/configOverlay.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"configOverlay.d.cts","names":[],"sources":["../../src/tui/configOverlay.ts"],"mappings":";;;;cA6Ha,+BAA+B;mBACzB;mBACA;mBACA;UACT;UACA;UACA;UACA;UACA;UACA;UACA;;mBAES;UACT;EAER,YAAY,KAAK,gBAAgB,OAAO,OAAO,UAAU,qBAAqB,OAAO;;UAU7E;EAKR;UAIQ;UAeA;UAIA;UAIA;UAIA;UAIA;UAIA;UAeA;;UAUA;UAUA;EASR,YAAY;UAYJ;UAsDA;UAwBA;UA0BA;UAwBA;;;;;;;UAyBA;;UAeA;UAKA;UAOA;UA6DA;UA4BA;UA2CA;YASE,aAAa;;;;;;;UAoBf;YAKE,WAAW,eAAe;UAsB5B;;UAeO;EACf,eAAe;EACf,OAAO;IAAc;IAAgB;IAAmB;IAAiB;IAAgB;;EACzF,UAAU;;iBAGU,kBAAkB,KAAK,kBAAkB,UAAU,sBAAsB"}
|
|
@@ -52,6 +52,13 @@ declare class ConfigOverlayComponent extends DoomOverlay {
|
|
|
52
52
|
private hints;
|
|
53
53
|
private hintLine;
|
|
54
54
|
protected getChrome(): DoomOverlayChrome;
|
|
55
|
+
/**
|
|
56
|
+
* The one line the panel says in its own voice, kept at the bottom where it
|
|
57
|
+
* does not push a section's fields around. Dropped entirely when the body is
|
|
58
|
+
* too short to spare the rows: a field the user came here to change outranks
|
|
59
|
+
* a pointer to the docs.
|
|
60
|
+
*/
|
|
61
|
+
private helpBanner;
|
|
55
62
|
protected renderBody(width: number, height: number): string[];
|
|
56
63
|
private renderStacked;
|
|
57
64
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configOverlay.d.mts","names":[],"sources":["../../src/tui/configOverlay.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"configOverlay.d.mts","names":[],"sources":["../../src/tui/configOverlay.ts"],"mappings":";;;;cA6Ha,+BAA+B;mBACzB;mBACA;mBACA;UACT;UACA;UACA;UACA;UACA;UACA;UACA;;mBAES;UACT;EAER,YAAY,KAAK,gBAAgB,OAAO,OAAO,UAAU,qBAAqB,OAAO;;UAU7E;EAKR;UAIQ;UAeA;UAIA;UAIA;UAIA;UAIA;UAIA;UAeA;;UAUA;UAUA;EASR,YAAY;UAYJ;UAsDA;UAwBA;UA0BA;UAwBA;;;;;;;UAyBA;;UAeA;UAKA;UAOA;UA6DA;UA4BA;UA2CA;YASE,aAAa;;;;;;;UAoBf;YAKE,WAAW,eAAe;UAsB5B;;UAeO;EACf,eAAe;EACf,OAAO;IAAc;IAAgB;IAAmB;IAAiB;IAAgB;;EACzF,UAAU;;iBAGU,kBAAkB,KAAK,kBAAkB,UAAU,sBAAsB"}
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"configOverlay.mjs","names":[],"sources":["../../src/tui/configOverlay.ts"],"sourcesContent":["/**\n * Configuration panel: the `SPC e c` overlay.\n *\n * Sections on the left, one per contributing extension, and that section's\n * fields on the right. The panel owns navigation and text entry; every value it\n * shows was published by an extension, and every change it makes is a request\n * back to whichever extension owns the field. Nothing here knows what a voice\n * model or an editor command is.\n *\n * It subscribes to the registry rather than snapshotting at open time, unlike\n * the tools browser next door, because a contributor can be doing long work\n * (fetching a model, running an installer) and the panel is where that progress\n * is meant to appear.\n *\n * There is no local echo of an edit. A typed value is sent and the field stays\n * pending until its owner republishes, so a rejected value comes back with the\n * reason attached instead of the panel showing a value that was never written.\n */\n\nimport type { ConfigChoice, ConfigField } from '@agimon-ai/doompi-extension-contracts/config';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport type { DoomConfigSectionView } from '@agimon-ai/doompi-extension-contracts/config';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from './doomOverlay.ts';\nimport { fitStyledLine } from './rendering.ts';\n\ntype Pane = 'sections' | 'fields';\ntype Mode = 'browse' | 'edit' | 'choices';\n\nconst TITLE = 'CONFIG';\nconst BREADCRUMB = 'SPC › e / c · config';\nconst ACTION_SET = 'set';\nconst ACTION_CLEAR = 'clear';\nconst ACTION_ABORT = 'abort';\nconst DEFAULT_CHOICE_ACTION = 'select';\n\nconst KEY_BACKSPACE = '\\x7f';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst KEY_TAB = '\\t';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\nconst MIN_WIDTH = 48;\nconst SECTION_PANE_RATIO = 1 / 3;\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst ELLIPSIS = '…';\nconst LABEL_COLUMN = 18;\nconst INDENT = ' ';\nconst EMPTY_MESSAGE = 'No extension has contributed settings to this session.';\nconst NO_FIELDS_MESSAGE = 'This section has no fields.';\nconst UNSET_PLACEHOLDER = 'not set';\nconst SECTION_HEADING = 'SECTIONS';\nconst PROGRESS_WIDTH = 24;\nconst ACTION_INPUT = 'input';\ntype ThemeColor = Parameters<Theme['fg']>[0];\nconst STEP_MARKERS: Readonly<Record<string, readonly [string, ThemeColor]>> = {\n pending: ['·', 'dim'],\n satisfied: ['✓', 'dim'],\n running: ['▸', 'accent'],\n done: ['✓', 'success'],\n failed: ['✗', 'error'],\n};\nconst PROGRESS_FULL = '█';\nconst PROGRESS_EMPTY = '░';\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nfunction isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\nfunction fitRow(text: string, width: number): string {\n return fitStyledLine(text, width, ELLIPSIS);\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fitRow(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fitRow(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\nfunction isSelectable(field: ConfigField): boolean {\n return field.kind !== 'info';\n}\n\nexport class ConfigOverlayComponent extends DoomOverlay {\n private readonly registry: DoomConfigStoreView;\n private readonly done: (result: undefined) => void;\n private readonly unsubscribe: () => void;\n private sections: readonly DoomConfigSectionView[];\n private pane: Pane = 'sections';\n private mode: Mode = 'browse';\n private sectionIndex = 0;\n private fieldIndex = 0;\n private choiceIndex = 0;\n private draft = '';\n /** Fields awaiting a reply, keyed by source, section, and field. */\n private readonly pending = new Set<string>();\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, registry: DoomConfigStoreView, done: (result: undefined) => void) {\n super(tui, theme);\n this.registry = registry;\n this.done = done;\n this.sections = registry.getSections();\n this.fieldIndex = this.firstSelectableField();\n this.unsubscribe = registry.subscribe(() => this.onSectionsChanged());\n }\n\n /** Info fields are labels, so the cursor never starts on one. */\n private firstSelectableField(): number {\n const index = this.currentFields().findIndex(isSelectable);\n return index < 0 ? 0 : index;\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n private onSectionsChanged(): void {\n this.sections = this.registry.getSections();\n // Any republish from an owner is its answer, so nothing stays pending past it.\n this.pending.clear();\n this.sectionIndex = Math.min(this.sectionIndex, Math.max(0, this.sections.length - 1));\n const fields = this.currentFields();\n this.fieldIndex = Math.min(this.fieldIndex, Math.max(0, fields.length - 1));\n if (!isSelectable(fields[this.fieldIndex] ?? { kind: 'info' })) this.fieldIndex = this.firstSelectableField();\n const field = this.currentField();\n // A field that grew an error keeps the draft so the typed value is not lost,\n // and an answer being typed survives the republish each output chunk causes.\n if (this.mode === 'edit' && !field?.statusText && !field?.awaitingInput) this.mode = 'browse';\n this.tui.requestRender();\n }\n\n private currentSection(): DoomConfigSectionView | undefined {\n return this.sections[this.sectionIndex];\n }\n\n private currentFields(): readonly ConfigField[] {\n return this.currentSection()?.fields ?? [];\n }\n\n private currentField(): ConfigField | undefined {\n return this.currentFields()[this.fieldIndex];\n }\n\n private currentChoices(): readonly ConfigChoice[] {\n return this.currentField()?.choices ?? [];\n }\n\n private pendingKey(field: ConfigField): string {\n return `${this.currentSection()?.source ?? ''}\u0000${this.currentSection()?.id ?? ''}\u0000${field.id}`;\n }\n\n private invoke(action: string, value?: string): void {\n const section = this.currentSection();\n const field = this.currentField();\n if (!section || !field) return;\n this.pending.add(this.pendingKey(field));\n this.registry.invoke({\n source: section.source,\n sectionId: section.id,\n fieldId: field.id,\n action,\n ...(value === undefined ? {} : { value }),\n });\n this.tui.requestRender();\n }\n\n private moveSection(delta: number): void {\n if (this.sections.length === 0) return;\n const next = Math.max(0, Math.min(this.sectionIndex + delta, this.sections.length - 1));\n if (next === this.sectionIndex) return;\n this.sectionIndex = next;\n this.fieldIndex = this.firstSelectableField();\n this.tui.requestRender();\n }\n\n /** Info fields are labels, so the cursor steps over them to the next control. */\n private moveField(delta: number): void {\n const fields = this.currentFields();\n const step = delta < 0 ? -1 : 1;\n let next = this.fieldIndex + step;\n while (next >= 0 && next < fields.length && !isSelectable(fields[next] as ConfigField)) next += step;\n if (next < 0 || next >= fields.length) return;\n this.fieldIndex = next;\n this.tui.requestRender();\n }\n\n private moveChoice(delta: number): void {\n const choices = this.currentChoices();\n if (choices.length === 0) return;\n const next = Math.max(0, Math.min(this.choiceIndex + delta, choices.length - 1));\n if (next === this.choiceIndex) return;\n this.choiceIndex = next;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (this.mode === 'edit') {\n this.handleEditInput(data);\n return;\n }\n if (this.mode === 'choices') {\n this.handleChoiceInput(data);\n return;\n }\n this.handleBrowseInput(data);\n }\n\n private handleBrowseInput(data: string): void {\n const field = this.currentField();\n if (matchesKey(data, 'ctrl+c')) {\n if (field?.busy) this.invoke(ACTION_ABORT);\n else this.done(undefined);\n return;\n }\n if (matchesKey(data, 'escape') || data === 'q') {\n // Escape backs out of the fields pane before it closes the panel.\n if (this.pane === 'fields') {\n this.pane = 'sections';\n this.tui.requestRender();\n return;\n }\n this.done(undefined);\n return;\n }\n if (data === KEY_TAB) {\n this.pane = this.pane === 'sections' ? 'fields' : 'sections';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n if (this.pane === 'sections') this.moveSection(-1);\n else this.moveField(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n if (this.pane === 'sections') this.moveSection(1);\n else this.moveField(1);\n return;\n }\n // While a field offers its own keys, those win: a pending confirmation is\n // answered before anything else can be typed.\n const offered = field?.actions?.find((action) => action.key === data);\n if (this.pane === 'fields' && offered) {\n this.invoke(offered.action);\n return;\n }\n // Answering a waiting command beats every other reading of `i`, and it is\n // the only thing that will make progress while one is blocked on a prompt.\n if (this.pane === 'fields' && field?.awaitingInput && data === 'i') {\n this.draft = '';\n this.mode = 'edit';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n this.activate();\n return;\n }\n if (this.pane === 'fields' && data === 'd' && field && isSelectable(field)) this.invoke(ACTION_CLEAR);\n }\n\n private activate(): void {\n if (this.pane === 'sections') {\n this.pane = 'fields';\n this.tui.requestRender();\n return;\n }\n const field = this.currentField();\n if (!field || field.busy || !isSelectable(field)) return;\n // `enum` and `choice` differ only in how many entries they carry, so both\n // open the same list with the cursor on whatever is currently set.\n if (field.kind === 'choice' || field.kind === 'enum') {\n this.choiceIndex = Math.max(\n 0,\n this.currentChoices().findIndex((choice) => choice.id === field.value),\n );\n this.mode = 'choices';\n this.tui.requestRender();\n return;\n }\n this.draft = field.value ?? '';\n this.mode = 'edit';\n this.tui.requestRender();\n }\n\n private handleEditInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.draft = '';\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n const value = this.draft.trim();\n // Answering a prompt is a different write from setting a field, and an\n // empty answer is meaningful there: it accepts the command's default.\n if (this.currentField()?.awaitingInput) {\n this.mode = 'browse';\n this.invoke(ACTION_INPUT, value.length === 0 ? undefined : value);\n this.draft = '';\n return;\n }\n // The parser rejects blank values, so clearing a key means removing it.\n this.invoke(value.length === 0 ? ACTION_CLEAR : ACTION_SET, value.length === 0 ? undefined : value);\n return;\n }\n if (data === KEY_BACKSPACE) this.draft = this.draft.slice(0, -1);\n else if (!isControlInput(data)) this.draft += data;\n this.tui.requestRender();\n }\n\n private handleChoiceInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.moveChoice(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.moveChoice(1);\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n const choice = this.currentChoices()[this.choiceIndex];\n if (!choice) return;\n this.mode = 'browse';\n // The entry names what enter does to it, which is what lets one list hold\n // both an installed thing to select and an absent one to fetch.\n this.invoke(choice.action ?? DEFAULT_CHOICE_ACTION, choice.id);\n }\n }\n\n private sectionLines(width: number): string[] {\n const lines = [this.theme.bold(SECTION_HEADING), ''];\n if (this.sections.length === 0) {\n lines.push(...wrap(EMPTY_MESSAGE, width).map((line) => this.theme.fg('dim', line)));\n return lines;\n }\n for (const [index, section] of this.sections.entries()) {\n const active = index === this.sectionIndex;\n const title = rightAligned(\n active ? this.theme.bold(section.title) : this.theme.fg('text', section.title),\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', section.detail ?? ''),\n width,\n );\n lines.push(active && this.pane === 'sections' ? this.theme.bg('selectedBg', fitRow(title, width)) : title);\n lines.push(this.theme.fg('dim', fitRow(`${INDENT}${section.source}`, width)));\n }\n return lines;\n }\n\n /**\n * The whole plan, not just the running step.\n *\n * Seeing that a package manager already finished and a download is now\n * underway is what makes a long install legible; a lone \"step 3 of 6\" is not.\n */\n private stepLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.steps ?? []).map((step) => {\n const [glyph, color] = STEP_MARKERS[step.state];\n const label = step.state === 'pending' ? this.theme.fg('dim', step.label) : step.label;\n const row = rightAligned(\n `${this.theme.fg(color, glyph)} ${label}`,\n this.theme.fg('dim', step.detail ?? ''),\n inner,\n );\n return `${INDENT}${row}`;\n });\n }\n\n /** The tail of the running command, so a prompt it printed is visible. */\n private outputLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.output ?? []).map((line) => `${INDENT}${this.theme.fg('dim', fitRow(line, inner))}`);\n }\n\n private progressBar(ratio: number | undefined, width: number): string {\n if (ratio === undefined) return '';\n const filled = Math.max(0, Math.min(PROGRESS_WIDTH, Math.round(ratio * PROGRESS_WIDTH)));\n const bar = PROGRESS_FULL.repeat(filled) + PROGRESS_EMPTY.repeat(PROGRESS_WIDTH - filled);\n return fitRow(`${bar} ${Math.round(ratio * 100)}%`, width);\n }\n\n private fieldLines(width: number): string[] {\n const section = this.currentSection();\n if (!section) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n const lines = [this.theme.bold(section.title), ''];\n if (section.notice) {\n lines.push(\n ...wrap(section.notice, width).map((line) =>\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', line),\n ),\n );\n lines.push('');\n }\n if (section.fields.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n\n const valueWidth = Math.max(1, width - LABEL_COLUMN);\n for (const [index, field] of section.fields.entries()) {\n const active = index === this.fieldIndex && this.pane === 'fields';\n const editing = active && this.mode === 'edit';\n const isPending = this.pending.has(this.pendingKey(field));\n const shown = editing ? `${this.draft}█` : (field.value ?? field.placeholder ?? UNSET_PLACEHOLDER);\n const tone = editing || field.value ? 'text' : 'dim';\n const value = this.theme.fg(isPending || field.busy ? 'dim' : tone, shown);\n const status = field.statusText ?? (isPending ? '' : '');\n const row = `${this.theme.fg('dim', fitRow(field.label, LABEL_COLUMN))}${\n status\n ? rightAligned(value, this.theme.fg(field.status === 'blocked' ? 'error' : 'dim', status), valueWidth)\n : fitRow(value, valueWidth)\n }`;\n lines.push(active ? this.theme.bg('selectedBg', fitRow(row, width)) : row);\n lines.push(...this.stepLines(field, width));\n if (field.busy && field.progress) {\n lines.push(this.theme.fg('dim', fitRow(`${INDENT}${field.progress.label}`, width)));\n const bar = this.progressBar(field.progress.ratio, valueWidth);\n if (bar) lines.push(`${INDENT}${this.theme.fg('accent', bar)}`);\n }\n lines.push(...this.outputLines(field, width));\n if (active && field.awaitingInput) {\n // The command is asking something. Answering is the only useful action,\n // so the line is offered without having to enter a mode first.\n const prompt = this.mode === 'edit' ? `${this.draft}█` : 'press i to answer';\n lines.push(\n `${INDENT}${this.theme.fg('warning', fitRow(`waiting for input ${prompt}`, width - INDENT.length))}`,\n );\n }\n if (!active) continue;\n if (field.detail) {\n lines.push(\n ...wrap(field.detail, Math.max(1, width - LABEL_COLUMN)).map(\n (line) => `${' '.repeat(LABEL_COLUMN)}${this.theme.fg('dim', line)}`,\n ),\n );\n }\n if (field.keyPath) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.theme.fg('accent', field.keyPath)}`);\n if (field.actions && field.actions.length > 0) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.hintLine()}`);\n }\n return lines;\n }\n\n private choiceLines(width: number): string[] {\n const field = this.currentField();\n const choices = this.currentChoices();\n const lines = [this.theme.bold(field?.label ?? ''), ''];\n if (choices.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n const budget = Math.max(1, this.bodyHeight - lines.length);\n const selected = Math.max(0, Math.min(this.choiceIndex, choices.length - 1));\n const start = Math.max(0, Math.min(selected - budget + 1, Math.max(0, choices.length - budget)));\n let lastGroup: string | undefined;\n for (const [offset, choice] of choices.slice(start, start + budget).entries()) {\n if (choice.group && choice.group !== lastGroup) {\n lastGroup = choice.group;\n lines.push(this.theme.fg('accent', fitRow(choice.group, width)));\n }\n const status = choice.statusText ?? choice.status ?? '';\n const row = rightAligned(\n `${INDENT}${choice.label}${choice.detail ? this.theme.fg('dim', ` ${choice.detail}`) : ''}`,\n this.theme.fg(choice.status === 'blocked' ? 'error' : 'dim', status),\n width,\n );\n lines.push(start + offset === selected ? this.theme.bg('selectedBg', fitRow(row, width)) : row);\n }\n return lines;\n }\n\n private hints(): readonly (readonly [string, string])[] {\n if (this.mode === 'edit') {\n return [\n ['⌫', 'delete'],\n ['enter', this.currentField()?.awaitingInput ? 'send' : 'save'],\n ['esc', 'cancel'],\n ];\n }\n if (this.mode === 'choices') {\n const choice = this.currentChoices()[this.choiceIndex];\n // The hint names what enter does to the entry under the cursor, because on\n // this list that differs row by row.\n return [\n ['↑↓', 'move'],\n ['enter', choice?.action ?? DEFAULT_CHOICE_ACTION],\n ['esc', 'back'],\n ];\n }\n const field = this.currentField();\n if (field?.awaitingInput) {\n return [\n ['i', 'answer'],\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n if (field?.busy) {\n return [\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n const offered = (field?.actions ?? []).map((action) => [action.key, action.label] as const);\n return [\n ['↑↓', 'move'],\n ['tab', 'pane'],\n ['enter', this.pane === 'sections' ? 'fields' : 'edit'],\n ...offered,\n ...(this.pane === 'fields' && offered.length === 0 ? ([['d', 'clear']] as const) : []),\n ['esc', this.pane === 'fields' ? 'back' : 'close'],\n ];\n }\n\n private hintLine(): string {\n return this.hints()\n .map(\n ([key, label]) =>\n `${this.theme.bg('selectedBg', this.theme.fg('text', ` ${key} `))}${this.theme.fg('dim', ` ${label}`)}`,\n )\n .join(this.theme.fg('dim', ' '));\n }\n\n protected getChrome(): DoomOverlayChrome {\n const section = this.currentSection();\n const field = this.currentField();\n const breadcrumb =\n this.mode === 'choices' && section && field ? `${BREADCRUMB} › ${section.title} / ${field.label}` : BREADCRUMB;\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb,\n headerRight: `${this.sections.length} sections · ${this.sections.length === 0 ? 'none' : (section?.source ?? '')}`,\n footer: this.hintLine(),\n };\n }\n\n protected renderBody(width: number, height: number): string[] {\n this.bodyHeight = height;\n const right = this.mode === 'choices' ? this.choiceLines.bind(this) : this.fieldLines.bind(this);\n if (width < MIN_WIDTH) return this.renderStacked(width, height, right);\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * SECTION_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.sectionLines(leftContent);\n const rightLines = right(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n return Array.from({ length: height }, (_, index) => {\n const row = `${fitRow(left[index] ?? '', leftContent)} ${divider} ${fitRow(rightLines[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n }\n\n private renderStacked(width: number, height: number, right: (width: number) => string[]): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.sectionLines(width);\n const bottom = right(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n}\n\nexport interface DoomConfigStoreView {\n getSections(): DoomConfigSectionView[];\n invoke(invocation: { source: string; sectionId: string; fieldId: string; action: string; value?: string }): void;\n subscribe(listener: () => void): () => void;\n}\n\nexport async function openConfigOverlay(ctx: ExtensionContext, registry: DoomConfigStoreView): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new ConfigOverlayComponent(tui, theme, registry, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"oPAmCA,MACM,EAAa,uBAEb,EAAe,QAEf,EAAwB,SAgBxB,EAAgB,yDAChB,EAAoB,8BAMpB,EAAwE,CAC5E,QAAS,CAAC,IAAK,KAAK,EACpB,UAAW,CAAC,IAAK,KAAK,EACtB,QAAS,CAAC,IAAK,QAAQ,EACvB,KAAM,CAAC,IAAK,SAAS,EACrB,OAAQ,CAAC,IAAK,OAAO,CACvB,EASA,SAAS,EAAe,EAAuB,CAC7C,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAK,OAAQ,IAAS,CAChD,IAAM,EAAO,EAAK,WAAW,CAAK,EAClC,GAAI,EAAO,IAA8B,IAAS,IAAmB,MAAO,EAC9E,CACA,MAAO,EACT,CAEA,SAAS,EAAO,EAAc,EAAuB,CACnD,OAAO,EAAc,EAAM,EAAO,GAAQ,CAC5C,CAEA,SAAS,EAAa,EAAc,EAAe,EAAuB,CACxE,IAAM,EAAa,EAAa,CAAK,EAC/B,EAAY,KAAK,IAAI,EAAG,EAAQ,EAAa,CAAC,EACpD,OAAO,EAAO,EAAM,CAAS,EAAI,IAAI,OAAO,KAAK,IAAI,EAAG,EAAQ,EAAY,CAAU,CAAC,EAAI,EAAO,EAAO,CAAU,CACrH,CAEA,SAAS,EAAK,EAAc,EAAyB,CACnD,GAAI,GAAS,EAAG,MAAO,CAAC,EACxB,IAAM,EAAkB,CAAC,EACrB,EAAU,GACd,IAAK,IAAM,KAAQ,EAAK,MAAM,KAAK,CAAC,CAAC,OAAO,OAAO,EAC7C,EAAQ,SAAW,EAAG,EAAU,EAC3B,EAAQ,OAAS,EAAI,EAAK,QAAU,EAAO,GAAW,IAAI,KAEjE,EAAM,KAAK,CAAO,EAClB,EAAU,GAId,OADI,EAAQ,OAAS,GAAG,EAAM,KAAK,CAAO,EACnC,CACT,CAEA,SAAS,EAAa,EAA6B,CACjD,OAAO,EAAM,OAAS,MACxB,CAEA,IAAa,EAAb,cAA4C,CAAY,CACtD,SACA,KACA,YACA,SACA,KAAqB,WACrB,KAAqB,SACrB,aAAuB,EACvB,WAAqB,EACrB,YAAsB,EACtB,MAAgB,GAEhB,QAA2B,IAAI,IAC/B,WAAqB,EAErB,YAAY,EAAqB,EAAc,EAA+B,EAAmC,CAC/G,MAAM,EAAK,CAAK,EAChB,KAAK,SAAW,EAChB,KAAK,KAAO,EACZ,KAAK,SAAW,EAAS,YAAY,EACrC,KAAK,WAAa,KAAK,qBAAqB,EAC5C,KAAK,YAAc,EAAS,cAAgB,KAAK,kBAAkB,CAAC,CACtE,CAGA,sBAAuC,CACrC,IAAM,EAAQ,KAAK,cAAc,CAAC,CAAC,UAAU,CAAY,EACzD,OAAO,EAAQ,EAAI,EAAI,CACzB,CAEA,SAAgB,CACd,KAAK,YAAY,CACnB,CAEA,mBAAkC,CAChC,KAAK,SAAW,KAAK,SAAS,YAAY,EAE1C,KAAK,QAAQ,MAAM,EACnB,KAAK,aAAe,KAAK,IAAI,KAAK,aAAc,KAAK,IAAI,EAAG,KAAK,SAAS,OAAS,CAAC,CAAC,EACrF,IAAM,EAAS,KAAK,cAAc,EAClC,KAAK,WAAa,KAAK,IAAI,KAAK,WAAY,KAAK,IAAI,EAAG,EAAO,OAAS,CAAC,CAAC,EACrE,EAAa,EAAO,KAAK,aAAe,CAAE,KAAM,MAAO,CAAC,IAAG,KAAK,WAAa,KAAK,qBAAqB,GAC5G,IAAM,EAAQ,KAAK,aAAa,EAG5B,KAAK,OAAS,QAAU,CAAC,GAAO,YAAc,CAAC,GAAO,gBAAe,KAAK,KAAO,UACrF,KAAK,IAAI,cAAc,CACzB,CAEA,gBAA4D,CAC1D,OAAO,KAAK,SAAS,KAAK,aAC5B,CAEA,eAAgD,CAC9C,OAAO,KAAK,eAAe,CAAC,EAAE,QAAU,CAAC,CAC3C,CAEA,cAAgD,CAC9C,OAAO,KAAK,cAAc,CAAC,CAAC,KAAK,WACnC,CAEA,gBAAkD,CAChD,OAAO,KAAK,aAAa,CAAC,EAAE,SAAW,CAAC,CAC1C,CAEA,WAAmB,EAA4B,CAC7C,MAAO,GAAG,KAAK,eAAe,CAAC,EAAE,QAAU,GAAG,GAAG,KAAK,eAAe,CAAC,EAAE,IAAM,GAAG,GAAG,EAAM,IAC5F,CAEA,OAAe,EAAgB,EAAsB,CACnD,IAAM,EAAU,KAAK,eAAe,EAC9B,EAAQ,KAAK,aAAa,EAC5B,CAAC,GAAW,CAAC,IACjB,KAAK,QAAQ,IAAI,KAAK,WAAW,CAAK,CAAC,EACvC,KAAK,SAAS,OAAO,CACnB,OAAQ,EAAQ,OAChB,UAAW,EAAQ,GACnB,QAAS,EAAM,GACf,SACA,GAAI,IAAU,IAAA,GAAY,CAAC,EAAI,CAAE,OAAM,CACzC,CAAC,EACD,KAAK,IAAI,cAAc,EACzB,CAEA,YAAoB,EAAqB,CACvC,GAAI,KAAK,SAAS,SAAW,EAAG,OAChC,IAAM,EAAO,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,aAAe,EAAO,KAAK,SAAS,OAAS,CAAC,CAAC,EAClF,IAAS,KAAK,eAClB,KAAK,aAAe,EACpB,KAAK,WAAa,KAAK,qBAAqB,EAC5C,KAAK,IAAI,cAAc,EACzB,CAGA,UAAkB,EAAqB,CACrC,IAAM,EAAS,KAAK,cAAc,EAC5B,EAAO,EAAQ,EAAI,GAAK,EAC1B,EAAO,KAAK,WAAa,EAC7B,KAAO,GAAQ,GAAK,EAAO,EAAO,QAAU,CAAC,EAAa,EAAO,EAAoB,GAAG,GAAQ,EAC5F,EAAO,GAAK,GAAQ,EAAO,SAC/B,KAAK,WAAa,EAClB,KAAK,IAAI,cAAc,EACzB,CAEA,WAAmB,EAAqB,CACtC,IAAM,EAAU,KAAK,eAAe,EACpC,GAAI,EAAQ,SAAW,EAAG,OAC1B,IAAM,EAAO,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,YAAc,EAAO,EAAQ,OAAS,CAAC,CAAC,EAC3E,IAAS,KAAK,cAClB,KAAK,YAAc,EACnB,KAAK,IAAI,cAAc,EACzB,CAEA,YAAY,EAAoB,CAC9B,GAAI,KAAK,OAAS,OAAQ,CACxB,KAAK,gBAAgB,CAAI,EACzB,MACF,CACA,GAAI,KAAK,OAAS,UAAW,CAC3B,KAAK,kBAAkB,CAAI,EAC3B,MACF,CACA,KAAK,kBAAkB,CAAI,CAC7B,CAEA,kBAA0B,EAAoB,CAC5C,IAAM,EAAQ,KAAK,aAAa,EAChC,GAAI,EAAW,EAAM,QAAQ,EAAG,CAC1B,GAAO,KAAM,KAAK,OAAO,OAAY,EACpC,KAAK,KAAK,IAAA,EAAS,EACxB,MACF,CACA,GAAI,EAAW,EAAM,QAAQ,GAAK,IAAS,IAAK,CAE9C,GAAI,KAAK,OAAS,SAAU,CAC1B,KAAK,KAAO,WACZ,KAAK,IAAI,cAAc,EACvB,MACF,CACA,KAAK,KAAK,IAAA,EAAS,EACnB,MACF,CACA,GAAI,IAAS,IAAS,CACpB,KAAK,KAAO,KAAK,OAAS,WAAa,SAAW,WAClD,KAAK,IAAI,cAAc,EACvB,MACF,CACA,GAAI,EAAW,EAAM,IAAI,GAAK,IAAS,IAAK,CACtC,KAAK,OAAS,WAAY,KAAK,YAAY,EAAE,EAC5C,KAAK,UAAU,EAAE,EACtB,MACF,CACA,GAAI,EAAW,EAAM,MAAM,GAAK,IAAS,IAAK,CACxC,KAAK,OAAS,WAAY,KAAK,YAAY,CAAC,EAC3C,KAAK,UAAU,CAAC,EACrB,MACF,CAGA,IAAM,EAAU,GAAO,SAAS,KAAM,GAAW,EAAO,MAAQ,CAAI,EACpE,GAAI,KAAK,OAAS,UAAY,EAAS,CACrC,KAAK,OAAO,EAAQ,MAAM,EAC1B,MACF,CAGA,GAAI,KAAK,OAAS,UAAY,GAAO,eAAiB,IAAS,IAAK,CAClE,KAAK,MAAQ,GACb,KAAK,KAAO,OACZ,KAAK,IAAI,cAAc,EACvB,MACF,CACA,GAAI,EAAW,EAAM,OAAO,GAAK,IAAS,MAAa,IAAS;EAAa,CAC3E,KAAK,SAAS,EACd,MACF,CACI,KAAK,OAAS,UAAY,IAAS,KAAO,GAAS,EAAa,CAAK,GAAG,KAAK,OAAO,CAAY,CACtG,CAEA,UAAyB,CACvB,GAAI,KAAK,OAAS,WAAY,CAC5B,KAAK,KAAO,SACZ,KAAK,IAAI,cAAc,EACvB,MACF,CACA,IAAM,EAAQ,KAAK,aAAa,EAC5B,MAAC,GAAS,EAAM,MAAQ,CAAC,EAAa,CAAK,GAG/C,IAAI,EAAM,OAAS,UAAY,EAAM,OAAS,OAAQ,CACpD,KAAK,YAAc,KAAK,IACtB,EACA,KAAK,eAAe,CAAC,CAAC,UAAW,GAAW,EAAO,KAAO,EAAM,KAAK,CACvE,EACA,KAAK,KAAO,UACZ,KAAK,IAAI,cAAc,EACvB,MACF,CACA,KAAK,MAAQ,EAAM,OAAS,GAC5B,KAAK,KAAO,OACZ,KAAK,IAAI,cAAc,CAHvB,CAIF,CAEA,gBAAwB,EAAoB,CAC1C,GAAI,EAAW,EAAM,QAAQ,EAAG,CAC9B,KAAK,KAAO,SACZ,KAAK,MAAQ,GACb,KAAK,IAAI,cAAc,EACvB,MACF,CACA,GAAI,IAAS,MAAa,IAAS;EAAa,CAC9C,IAAM,EAAQ,KAAK,MAAM,KAAK,EAG9B,GAAI,KAAK,aAAa,CAAC,EAAE,cAAe,CACtC,KAAK,KAAO,SACZ,KAAK,OAAO,QAAc,EAAM,SAAW,EAAI,IAAA,GAAY,CAAK,EAChE,KAAK,MAAQ,GACb,MACF,CAEA,KAAK,OAAO,EAAM,SAAW,EAAI,EAAe,MAAY,EAAM,SAAW,EAAI,IAAA,GAAY,CAAK,EAClG,MACF,CACI,IAAS,IAAe,KAAK,MAAQ,KAAK,MAAM,MAAM,EAAG,EAAE,EACrD,EAAe,CAAI,IAAG,KAAK,OAAS,GAC9C,KAAK,IAAI,cAAc,CACzB,CAEA,kBAA0B,EAAoB,CAC5C,GAAI,EAAW,EAAM,QAAQ,EAAG,CAC9B,KAAK,KAAO,SACZ,KAAK,IAAI,cAAc,EACvB,MACF,CACA,GAAI,EAAW,EAAM,IAAI,GAAK,IAAS,IAAK,CAC1C,KAAK,WAAW,EAAE,EAClB,MACF,CACA,GAAI,EAAW,EAAM,MAAM,GAAK,IAAS,IAAK,CAC5C,KAAK,WAAW,CAAC,EACjB,MACF,CACA,GAAI,EAAW,EAAM,OAAO,GAAK,IAAS,MAAa,IAAS;EAAa,CAC3E,IAAM,EAAS,KAAK,eAAe,CAAC,CAAC,KAAK,aAC1C,GAAI,CAAC,EAAQ,OACb,KAAK,KAAO,SAGZ,KAAK,OAAO,EAAO,QAAU,EAAuB,EAAO,EAAE,CAC/D,CACF,CAEA,aAAqB,EAAyB,CAC5C,IAAM,EAAQ,CAAC,KAAK,MAAM,KAAK,UAAe,EAAG,EAAE,EACnD,GAAI,KAAK,SAAS,SAAW,EAE3B,OADA,EAAM,KAAK,GAAG,EAAK,EAAe,CAAK,CAAC,CAAC,IAAK,GAAS,KAAK,MAAM,GAAG,MAAO,CAAI,CAAC,CAAC,EAC3E,EAET,IAAK,GAAM,CAAC,EAAO,KAAY,KAAK,SAAS,QAAQ,EAAG,CACtD,IAAM,EAAS,IAAU,KAAK,aACxB,EAAQ,EACZ,EAAS,KAAK,MAAM,KAAK,EAAQ,KAAK,EAAI,KAAK,MAAM,GAAG,OAAQ,EAAQ,KAAK,EAC7E,KAAK,MAAM,GAAG,EAAQ,cAAgB,QAAU,QAAU,MAAO,EAAQ,QAAU,EAAE,EACrF,CACF,EACA,EAAM,KAAK,GAAU,KAAK,OAAS,WAAa,KAAK,MAAM,GAAG,aAAc,EAAO,EAAO,CAAK,CAAC,EAAI,CAAK,EACzG,EAAM,KAAK,KAAK,MAAM,GAAG,MAAO,EAAO,KAAY,EAAQ,SAAU,CAAK,CAAC,CAAC,CAC9E,CACA,OAAO,CACT,CAQA,UAAkB,EAAoB,EAAyB,CAC7D,IAAM,EAAQ,KAAK,IAAI,EAAG,EAAQ,CAAa,EAC/C,OAAQ,EAAM,OAAS,CAAC,EAAA,CAAG,IAAK,GAAS,CACvC,GAAM,CAAC,EAAO,GAAS,EAAa,EAAK,OACnC,EAAQ,EAAK,QAAU,UAAY,KAAK,MAAM,GAAG,MAAO,EAAK,KAAK,EAAI,EAAK,MAMjF,MAAO,KALK,EACV,GAAG,KAAK,MAAM,GAAG,EAAO,CAAK,EAAE,GAAG,IAClC,KAAK,MAAM,GAAG,MAAO,EAAK,QAAU,EAAE,EACtC,CAEiB,GACrB,CAAC,CACH,CAGA,YAAoB,EAAoB,EAAyB,CAC/D,IAAM,EAAQ,KAAK,IAAI,EAAG,EAAQ,CAAa,EAC/C,OAAQ,EAAM,QAAU,CAAC,EAAA,CAAG,IAAK,GAAS,KAAY,KAAK,MAAM,GAAG,MAAO,EAAO,EAAM,CAAK,CAAC,GAAG,CACnG,CAEA,YAAoB,EAA2B,EAAuB,CACpE,GAAI,IAAU,IAAA,GAAW,MAAO,GAChC,IAAM,EAAS,KAAK,IAAI,EAAG,KAAK,IAAI,GAAgB,KAAK,MAAM,EAAQ,EAAc,CAAC,CAAC,EAEvF,OAAO,EAAO,GADF,IAAc,OAAO,CAAM,EAAI,IAAe,OAAO,GAAiB,CAAM,EACnE,GAAG,KAAK,MAAM,EAAQ,GAAG,EAAE,GAAI,CAAK,CAC3D,CAEA,WAAmB,EAAyB,CAC1C,IAAM,EAAU,KAAK,eAAe,EACpC,GAAI,CAAC,EAAS,MAAO,CAAC,KAAK,MAAM,GAAG,MAAO,CAAa,CAAC,EACzD,IAAM,EAAQ,CAAC,KAAK,MAAM,KAAK,EAAQ,KAAK,EAAG,EAAE,EASjD,GARI,EAAQ,SACV,EAAM,KACJ,GAAG,EAAK,EAAQ,OAAQ,CAAK,CAAC,CAAC,IAAK,GAClC,KAAK,MAAM,GAAG,EAAQ,cAAgB,QAAU,QAAU,MAAO,CAAI,CACvE,CACF,EACA,EAAM,KAAK,EAAE,GAEX,EAAQ,OAAO,SAAW,EAE5B,OADA,EAAM,KAAK,KAAK,MAAM,GAAG,MAAO,CAAiB,CAAC,EAC3C,EAGT,IAAM,EAAa,KAAK,IAAI,EAAG,EAAQ,EAAY,EACnD,IAAK,GAAM,CAAC,EAAO,KAAU,EAAQ,OAAO,QAAQ,EAAG,CACrD,IAAM,EAAS,IAAU,KAAK,YAAc,KAAK,OAAS,SACpD,EAAU,GAAU,KAAK,OAAS,OAClC,EAAY,KAAK,QAAQ,IAAI,KAAK,WAAW,CAAK,CAAC,EACnD,EAAQ,EAAU,GAAG,KAAK,MAAM,GAAM,EAAM,OAAS,EAAM,aAAe,UAC1E,EAAO,GAAW,EAAM,MAAQ,OAAS,MACzC,EAAQ,KAAK,MAAM,GAAG,GAAa,EAAM,KAAO,MAAQ,EAAM,CAAK,EACnE,EAAS,EAAM,YAA2B,GAC1C,EAAM,GAAG,KAAK,MAAM,GAAG,MAAO,EAAO,EAAM,MAAO,EAAY,CAAC,IACnE,EACI,EAAa,EAAO,KAAK,MAAM,GAAG,EAAM,SAAW,UAAY,QAAU,MAAO,CAAM,EAAG,CAAU,EACnG,EAAO,EAAO,CAAU,IAI9B,GAFA,EAAM,KAAK,EAAS,KAAK,MAAM,GAAG,aAAc,EAAO,EAAK,CAAK,CAAC,EAAI,CAAG,EACzE,EAAM,KAAK,GAAG,KAAK,UAAU,EAAO,CAAK,CAAC,EACtC,EAAM,MAAQ,EAAM,SAAU,CAChC,EAAM,KAAK,KAAK,MAAM,GAAG,MAAO,EAAO,KAAY,EAAM,SAAS,QAAS,CAAK,CAAC,CAAC,EAClF,IAAM,EAAM,KAAK,YAAY,EAAM,SAAS,MAAO,CAAU,EACzD,GAAK,EAAM,KAAK,KAAY,KAAK,MAAM,GAAG,SAAU,CAAG,GAAG,CAChE,CAEA,GADA,EAAM,KAAK,GAAG,KAAK,YAAY,EAAO,CAAK,CAAC,EACxC,GAAU,EAAM,cAAe,CAGjC,IAAM,EAAS,KAAK,OAAS,OAAS,GAAG,KAAK,MAAM,GAAK,oBACzD,EAAM,KACJ,KAAY,KAAK,MAAM,GAAG,UAAW,EAAO,sBAAsB,IAAU,EAAQ,CAAa,CAAC,GACpG,CACF,CACK,IACD,EAAM,QACR,EAAM,KACJ,GAAG,EAAK,EAAM,OAAQ,KAAK,IAAI,EAAG,EAAQ,EAAY,CAAC,CAAC,CAAC,IACtD,GAAS,GAAG,IAAI,OAAO,EAAY,IAAI,KAAK,MAAM,GAAG,MAAO,CAAI,GACnE,CACF,EAEE,EAAM,SAAS,EAAM,KAAK,GAAG,IAAI,OAAO,EAAY,IAAI,KAAK,MAAM,GAAG,SAAU,EAAM,OAAO,GAAG,EAChG,EAAM,SAAW,EAAM,QAAQ,OAAS,GAAG,EAAM,KAAK,GAAG,IAAI,OAAO,EAAY,IAAI,KAAK,SAAS,GAAG,EAC3G,CACA,OAAO,CACT,CAEA,YAAoB,EAAyB,CAC3C,IAAM,EAAQ,KAAK,aAAa,EAC1B,EAAU,KAAK,eAAe,EAC9B,EAAQ,CAAC,KAAK,MAAM,KAAK,GAAO,OAAS,EAAE,EAAG,EAAE,EACtD,GAAI,EAAQ,SAAW,EAErB,OADA,EAAM,KAAK,KAAK,MAAM,GAAG,MAAO,CAAiB,CAAC,EAC3C,EAET,IAAM,EAAS,KAAK,IAAI,EAAG,KAAK,WAAa,EAAM,MAAM,EACnD,EAAW,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,YAAa,EAAQ,OAAS,CAAC,CAAC,EACrE,EAAQ,KAAK,IAAI,EAAG,KAAK,IAAI,EAAW,EAAS,EAAG,KAAK,IAAI,EAAG,EAAQ,OAAS,CAAM,CAAC,CAAC,EAC3F,EACJ,IAAK,GAAM,CAAC,EAAQ,KAAW,EAAQ,MAAM,EAAO,EAAQ,CAAM,CAAC,CAAC,QAAQ,EAAG,CACzE,EAAO,OAAS,EAAO,QAAU,IACnC,EAAY,EAAO,MACnB,EAAM,KAAK,KAAK,MAAM,GAAG,SAAU,EAAO,EAAO,MAAO,CAAK,CAAC,CAAC,GAEjE,IAAM,EAAS,EAAO,YAAc,EAAO,QAAU,GAC/C,EAAM,EACV,KAAY,EAAO,QAAQ,EAAO,OAAS,KAAK,MAAM,GAAG,MAAO,KAAK,EAAO,QAAQ,EAAI,KACxF,KAAK,MAAM,GAAG,EAAO,SAAW,UAAY,QAAU,MAAO,CAAM,EACnE,CACF,EACA,EAAM,KAAK,EAAQ,IAAW,EAAW,KAAK,MAAM,GAAG,aAAc,EAAO,EAAK,CAAK,CAAC,EAAI,CAAG,CAChG,CACA,OAAO,CACT,CAEA,OAAwD,CACtD,GAAI,KAAK,OAAS,OAChB,MAAO,CACL,CAAC,IAAK,QAAQ,EACd,CAAC,QAAS,KAAK,aAAa,CAAC,EAAE,cAAgB,OAAS,MAAM,EAC9D,CAAC,MAAO,QAAQ,CAClB,EAEF,GAAI,KAAK,OAAS,UAIhB,MAAO,CACL,CAAC,KAAM,MAAM,EACb,CAAC,QALY,KAAK,eAAe,CAAC,CAAC,KAAK,YAKxB,EAAE,QAAU,CAAqB,EACjD,CAAC,MAAO,MAAM,CAChB,EAEF,IAAM,EAAQ,KAAK,aAAa,EAChC,GAAI,GAAO,cACT,MAAO,CACL,CAAC,IAAK,QAAQ,EACd,CAAC,SAAU,OAAO,EAClB,CAAC,MAAO,sBAAsB,CAChC,EAEF,GAAI,GAAO,KACT,MAAO,CACL,CAAC,SAAU,OAAO,EAClB,CAAC,MAAO,sBAAsB,CAChC,EAEF,IAAM,GAAW,GAAO,SAAW,CAAC,EAAA,CAAG,IAAK,GAAW,CAAC,EAAO,IAAK,EAAO,KAAK,CAAU,EAC1F,MAAO,CACL,CAAC,KAAM,MAAM,EACb,CAAC,MAAO,MAAM,EACd,CAAC,QAAS,KAAK,OAAS,WAAa,SAAW,MAAM,EACtD,GAAG,EACH,GAAI,KAAK,OAAS,UAAY,EAAQ,SAAW,EAAK,CAAC,CAAC,IAAK,OAAO,CAAC,EAAc,CAAC,EACpF,CAAC,MAAO,KAAK,OAAS,SAAW,OAAS,OAAO,CACnD,CACF,CAEA,UAA2B,CACzB,OAAO,KAAK,MAAM,CAAC,CAChB,KACE,CAAC,EAAK,KACL,GAAG,KAAK,MAAM,GAAG,aAAc,KAAK,MAAM,GAAG,OAAQ,IAAI,EAAI,EAAE,CAAC,IAAI,KAAK,MAAM,GAAG,MAAO,IAAI,GAAO,GACxG,CAAC,CACA,KAAK,KAAK,MAAM,GAAG,MAAO,KAAK,CAAC,CACrC,CAEA,WAAyC,CACvC,IAAM,EAAU,KAAK,eAAe,EAC9B,EAAQ,KAAK,aAAa,EAC1B,EACJ,KAAK,OAAS,WAAa,GAAW,EAAQ,GAAG,EAAW,KAAK,EAAQ,MAAM,KAAK,EAAM,QAAU,EACtG,MAAO,CACL,MAAO,SACP,OAAQ,EACR,aACA,YAAa,GAAG,KAAK,SAAS,OAAO,cAAc,KAAK,SAAS,SAAW,EAAI,OAAU,GAAS,QAAU,KAC7G,OAAQ,KAAK,SAAS,CACxB,CACF,CAEA,WAAqB,EAAe,EAA0B,CAC5D,KAAK,WAAa,EAClB,IAAM,EAAQ,KAAK,OAAS,UAAY,KAAK,YAAY,KAAK,IAAI,EAAI,KAAK,WAAW,KAAK,IAAI,EAC/F,GAAI,EAAQ,GAAW,OAAO,KAAK,cAAc,EAAO,EAAQ,CAAK,EAErE,IAAM,EAAY,KAAK,IAAI,GAAgB,KAAK,OAAO,EAAQ,GAAK,iBAAkB,CAAC,EACjF,EAAa,KAAK,IAAI,EAAG,EAAQ,EAAY,CAAC,EAC9C,EAAc,KAAK,IAAI,EAAG,EAAY,CAAW,EACjD,EAAe,KAAK,IAAI,EAAG,EAAa,CAAW,EACnD,EAAO,KAAK,aAAa,CAAW,EACpC,EAAa,EAAM,CAAY,EAE/B,EAAU,KAAK,MAAM,GAAG,cAAe,GAAG,EAChD,OAAO,MAAM,KAAK,CAAE,OAAQ,CAAO,GAAI,EAAG,IAAU,CAClD,IAAM,EAAM,GAAG,EAAO,EAAK,IAAU,GAAI,CAAW,EAAE,GAAG,EAAQ,GAAG,EAAO,EAAW,IAAU,GAAI,CAAY,IAChH,OAAO,EAAgB,EAAK,EAAO,GAAQ,CAC7C,CAAC,CACH,CAEA,cAAsB,EAAe,EAAgB,EAA8C,CACjG,IAAM,EAAY,KAAK,IAAI,EAAG,KAAK,OAAO,EAAS,GAAK,CAAC,CAAC,EACpD,EAAe,KAAK,IAAI,EAAG,EAAS,EAAY,CAAC,EACvD,KAAK,WAAa,EAClB,IAAM,EAAM,KAAK,aAAa,CAAK,EAC7B,EAAS,EAAM,CAAK,EAC1B,MAAO,CACL,GAAG,EAAI,MAAM,EAAG,CAAS,EACzB,GAAG,MAAM,KAAK,CAAE,OAAQ,KAAK,IAAI,EAAG,EAAY,EAAI,MAAM,CAAE,MAAS,EAAE,EACvE,KAAK,MAAM,GAAG,cAAe,IAAI,OAAO,CAAK,CAAC,EAC9C,GAAG,EAAO,MAAM,EAAG,CAAY,CACjC,CACF,CACF,EAQA,eAAsB,EAAkB,EAAuB,EAA8C,CAC3G,MAAM,EAAI,GAAG,QACV,EAAK,EAAO,EAAc,IAAS,IAAI,EAAuB,EAAK,EAAO,EAAU,CAAI,EACzF,CACF,CACF"}
|
|
1
|
+
{"version":3,"file":"configOverlay.mjs","names":[],"sources":["../../src/tui/configOverlay.ts"],"sourcesContent":["/**\n * Configuration panel: the `SPC e c` overlay.\n *\n * Sections on the left, one per contributing extension, and that section's\n * fields on the right. The panel owns navigation and text entry; every value it\n * shows was published by an extension, and every change it makes is a request\n * back to whichever extension owns the field. Nothing here knows what a voice\n * model or an editor command is.\n *\n * It subscribes to the registry rather than snapshotting at open time, unlike\n * the tools browser next door, because a contributor can be doing long work\n * (fetching a model, running an installer) and the panel is where that progress\n * is meant to appear.\n *\n * There is no local echo of an edit. A typed value is sent and the field stays\n * pending until its owner republishes, so a rejected value comes back with the\n * reason attached instead of the panel showing a value that was never written.\n */\n\nimport type { ConfigChoice, ConfigField } from '@agimon-ai/doompi-extension-contracts/config';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth, visibleWidth } from '@earendil-works/pi-tui';\nimport type { DoomConfigSectionView } from '@agimon-ai/doompi-extension-contracts/config';\nimport {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from './doomOverlay.ts';\nimport { fitStyledLine } from './rendering.ts';\n\ntype Pane = 'sections' | 'fields';\ntype Mode = 'browse' | 'edit' | 'choices';\n\nconst TITLE = 'CONFIG';\nconst BREADCRUMB = 'SPC › e / c · config';\nconst ACTION_SET = 'set';\nconst ACTION_CLEAR = 'clear';\nconst ACTION_ABORT = 'abort';\nconst DEFAULT_CHOICE_ACTION = 'select';\n\nconst KEY_BACKSPACE = '\\x7f';\nconst KEY_ENTER = '\\r';\nconst KEY_NEWLINE = '\\n';\nconst KEY_TAB = '\\t';\nconst FIRST_PRINTABLE_CODE_POINT = 0x20;\nconst DELETE_CODE_POINT = 0x7f;\n\nconst MIN_WIDTH = 48;\nconst SECTION_PANE_RATIO = 1 / 3;\nconst PANE_GUTTER = 1;\nconst MIN_PANE_WIDTH = 18;\nconst ELLIPSIS = '…';\nconst LABEL_COLUMN = 18;\nconst INDENT = ' ';\nconst EMPTY_MESSAGE = 'No extension has contributed settings to this session.';\nconst NO_FIELDS_MESSAGE = 'This section has no fields.';\nconst UNSET_PLACEHOLDER = 'not set';\nconst SECTION_HEADING = 'SECTIONS';\nconst HELP_BADGE = ' HELP ';\n/**\n * Every field here is published by a package, so the panel cannot explain what\n * any one of them means. Help mode can: it loads the owning package's docs into\n * the session, and the agent reads them and answers from there.\n */\nconst HELP_HINT = 'Not sure what a setting does? SPC h e loads package Help, then ask the agent about it.';\n/** Body rows below which the banner is dropped: a field outranks a pointer to the docs. */\nconst HELP_BANNER_MIN_ROWS = 10;\nconst PROGRESS_WIDTH = 24;\nconst ACTION_INPUT = 'input';\ntype ThemeColor = Parameters<Theme['fg']>[0];\nconst STEP_MARKERS: Readonly<Record<string, readonly [string, ThemeColor]>> = {\n pending: ['·', 'dim'],\n satisfied: ['✓', 'dim'],\n running: ['▸', 'accent'],\n done: ['✓', 'success'],\n failed: ['✗', 'error'],\n};\nconst PROGRESS_FULL = '█';\nconst PROGRESS_EMPTY = '░';\n\n/**\n * Keystrokes that carry no text: escape sequences, control keys, delete.\n * Indexed rather than iterated so surrogate pairs stay intact; both halves sit\n * far above the control range, so no emoji is ever mistaken for one.\n */\nfunction isControlInput(data: string): boolean {\n for (let index = 0; index < data.length; index++) {\n const code = data.charCodeAt(index);\n if (code < FIRST_PRINTABLE_CODE_POINT || code === DELETE_CODE_POINT) return true;\n }\n return false;\n}\n\nfunction fitRow(text: string, width: number): string {\n return fitStyledLine(text, width, ELLIPSIS);\n}\n\nfunction rightAligned(left: string, right: string, width: number): string {\n const rightWidth = visibleWidth(right);\n const leftWidth = Math.max(0, width - rightWidth - 1);\n return fitRow(left, leftWidth) + ' '.repeat(Math.max(1, width - leftWidth - rightWidth)) + fitRow(right, rightWidth);\n}\n\nfunction wrap(text: string, width: number): string[] {\n if (width <= 0) return [];\n const lines: string[] = [];\n let current = '';\n for (const word of text.split(/\\s+/).filter(Boolean)) {\n if (current.length === 0) current = word;\n else if (current.length + 1 + word.length <= width) current += ` ${word}`;\n else {\n lines.push(current);\n current = word;\n }\n }\n if (current.length > 0) lines.push(current);\n return lines;\n}\n\nfunction isSelectable(field: ConfigField): boolean {\n return field.kind !== 'info';\n}\n\nexport class ConfigOverlayComponent extends DoomOverlay {\n private readonly registry: DoomConfigStoreView;\n private readonly done: (result: undefined) => void;\n private readonly unsubscribe: () => void;\n private sections: readonly DoomConfigSectionView[];\n private pane: Pane = 'sections';\n private mode: Mode = 'browse';\n private sectionIndex = 0;\n private fieldIndex = 0;\n private choiceIndex = 0;\n private draft = '';\n /** Fields awaiting a reply, keyed by source, section, and field. */\n private readonly pending = new Set<string>();\n private bodyHeight = 1;\n\n constructor(tui: DoomOverlayTui, theme: Theme, registry: DoomConfigStoreView, done: (result: undefined) => void) {\n super(tui, theme);\n this.registry = registry;\n this.done = done;\n this.sections = registry.getSections();\n this.fieldIndex = this.firstSelectableField();\n this.unsubscribe = registry.subscribe(() => this.onSectionsChanged());\n }\n\n /** Info fields are labels, so the cursor never starts on one. */\n private firstSelectableField(): number {\n const index = this.currentFields().findIndex(isSelectable);\n return index < 0 ? 0 : index;\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n private onSectionsChanged(): void {\n this.sections = this.registry.getSections();\n // Any republish from an owner is its answer, so nothing stays pending past it.\n this.pending.clear();\n this.sectionIndex = Math.min(this.sectionIndex, Math.max(0, this.sections.length - 1));\n const fields = this.currentFields();\n this.fieldIndex = Math.min(this.fieldIndex, Math.max(0, fields.length - 1));\n if (!isSelectable(fields[this.fieldIndex] ?? { kind: 'info' })) this.fieldIndex = this.firstSelectableField();\n const field = this.currentField();\n // A field that grew an error keeps the draft so the typed value is not lost,\n // and an answer being typed survives the republish each output chunk causes.\n if (this.mode === 'edit' && !field?.statusText && !field?.awaitingInput) this.mode = 'browse';\n this.tui.requestRender();\n }\n\n private currentSection(): DoomConfigSectionView | undefined {\n return this.sections[this.sectionIndex];\n }\n\n private currentFields(): readonly ConfigField[] {\n return this.currentSection()?.fields ?? [];\n }\n\n private currentField(): ConfigField | undefined {\n return this.currentFields()[this.fieldIndex];\n }\n\n private currentChoices(): readonly ConfigChoice[] {\n return this.currentField()?.choices ?? [];\n }\n\n private pendingKey(field: ConfigField): string {\n return `${this.currentSection()?.source ?? ''}\u0000${this.currentSection()?.id ?? ''}\u0000${field.id}`;\n }\n\n private invoke(action: string, value?: string): void {\n const section = this.currentSection();\n const field = this.currentField();\n if (!section || !field) return;\n this.pending.add(this.pendingKey(field));\n this.registry.invoke({\n source: section.source,\n sectionId: section.id,\n fieldId: field.id,\n action,\n ...(value === undefined ? {} : { value }),\n });\n this.tui.requestRender();\n }\n\n private moveSection(delta: number): void {\n if (this.sections.length === 0) return;\n const next = Math.max(0, Math.min(this.sectionIndex + delta, this.sections.length - 1));\n if (next === this.sectionIndex) return;\n this.sectionIndex = next;\n this.fieldIndex = this.firstSelectableField();\n this.tui.requestRender();\n }\n\n /** Info fields are labels, so the cursor steps over them to the next control. */\n private moveField(delta: number): void {\n const fields = this.currentFields();\n const step = delta < 0 ? -1 : 1;\n let next = this.fieldIndex + step;\n while (next >= 0 && next < fields.length && !isSelectable(fields[next] as ConfigField)) next += step;\n if (next < 0 || next >= fields.length) return;\n this.fieldIndex = next;\n this.tui.requestRender();\n }\n\n private moveChoice(delta: number): void {\n const choices = this.currentChoices();\n if (choices.length === 0) return;\n const next = Math.max(0, Math.min(this.choiceIndex + delta, choices.length - 1));\n if (next === this.choiceIndex) return;\n this.choiceIndex = next;\n this.tui.requestRender();\n }\n\n handleInput(data: string): void {\n if (this.mode === 'edit') {\n this.handleEditInput(data);\n return;\n }\n if (this.mode === 'choices') {\n this.handleChoiceInput(data);\n return;\n }\n this.handleBrowseInput(data);\n }\n\n private handleBrowseInput(data: string): void {\n const field = this.currentField();\n if (matchesKey(data, 'ctrl+c')) {\n if (field?.busy) this.invoke(ACTION_ABORT);\n else this.done(undefined);\n return;\n }\n if (matchesKey(data, 'escape') || data === 'q') {\n // Escape backs out of the fields pane before it closes the panel.\n if (this.pane === 'fields') {\n this.pane = 'sections';\n this.tui.requestRender();\n return;\n }\n this.done(undefined);\n return;\n }\n if (data === KEY_TAB) {\n this.pane = this.pane === 'sections' ? 'fields' : 'sections';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n if (this.pane === 'sections') this.moveSection(-1);\n else this.moveField(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n if (this.pane === 'sections') this.moveSection(1);\n else this.moveField(1);\n return;\n }\n // While a field offers its own keys, those win: a pending confirmation is\n // answered before anything else can be typed.\n const offered = field?.actions?.find((action) => action.key === data);\n if (this.pane === 'fields' && offered) {\n this.invoke(offered.action);\n return;\n }\n // Answering a waiting command beats every other reading of `i`, and it is\n // the only thing that will make progress while one is blocked on a prompt.\n if (this.pane === 'fields' && field?.awaitingInput && data === 'i') {\n this.draft = '';\n this.mode = 'edit';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n this.activate();\n return;\n }\n if (this.pane === 'fields' && data === 'd' && field && isSelectable(field)) this.invoke(ACTION_CLEAR);\n }\n\n private activate(): void {\n if (this.pane === 'sections') {\n this.pane = 'fields';\n this.tui.requestRender();\n return;\n }\n const field = this.currentField();\n if (!field || field.busy || !isSelectable(field)) return;\n // `enum` and `choice` differ only in how many entries they carry, so both\n // open the same list with the cursor on whatever is currently set.\n if (field.kind === 'choice' || field.kind === 'enum') {\n this.choiceIndex = Math.max(\n 0,\n this.currentChoices().findIndex((choice) => choice.id === field.value),\n );\n this.mode = 'choices';\n this.tui.requestRender();\n return;\n }\n this.draft = field.value ?? '';\n this.mode = 'edit';\n this.tui.requestRender();\n }\n\n private handleEditInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.draft = '';\n this.tui.requestRender();\n return;\n }\n if (data === KEY_ENTER || data === KEY_NEWLINE) {\n const value = this.draft.trim();\n // Answering a prompt is a different write from setting a field, and an\n // empty answer is meaningful there: it accepts the command's default.\n if (this.currentField()?.awaitingInput) {\n this.mode = 'browse';\n this.invoke(ACTION_INPUT, value.length === 0 ? undefined : value);\n this.draft = '';\n return;\n }\n // The parser rejects blank values, so clearing a key means removing it.\n this.invoke(value.length === 0 ? ACTION_CLEAR : ACTION_SET, value.length === 0 ? undefined : value);\n return;\n }\n if (data === KEY_BACKSPACE) this.draft = this.draft.slice(0, -1);\n else if (!isControlInput(data)) this.draft += data;\n this.tui.requestRender();\n }\n\n private handleChoiceInput(data: string): void {\n if (matchesKey(data, 'escape')) {\n this.mode = 'browse';\n this.tui.requestRender();\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.moveChoice(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.moveChoice(1);\n return;\n }\n if (matchesKey(data, 'enter') || data === KEY_ENTER || data === KEY_NEWLINE) {\n const choice = this.currentChoices()[this.choiceIndex];\n if (!choice) return;\n this.mode = 'browse';\n // The entry names what enter does to it, which is what lets one list hold\n // both an installed thing to select and an absent one to fetch.\n this.invoke(choice.action ?? DEFAULT_CHOICE_ACTION, choice.id);\n }\n }\n\n private sectionLines(width: number): string[] {\n const lines = [this.theme.bold(SECTION_HEADING), ''];\n if (this.sections.length === 0) {\n lines.push(...wrap(EMPTY_MESSAGE, width).map((line) => this.theme.fg('dim', line)));\n return lines;\n }\n for (const [index, section] of this.sections.entries()) {\n const active = index === this.sectionIndex;\n const title = rightAligned(\n active ? this.theme.bold(section.title) : this.theme.fg('text', section.title),\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', section.detail ?? ''),\n width,\n );\n lines.push(active && this.pane === 'sections' ? this.theme.bg('selectedBg', fitRow(title, width)) : title);\n lines.push(this.theme.fg('dim', fitRow(`${INDENT}${section.source}`, width)));\n }\n return lines;\n }\n\n /**\n * The whole plan, not just the running step.\n *\n * Seeing that a package manager already finished and a download is now\n * underway is what makes a long install legible; a lone \"step 3 of 6\" is not.\n */\n private stepLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.steps ?? []).map((step) => {\n const [glyph, color] = STEP_MARKERS[step.state];\n const label = step.state === 'pending' ? this.theme.fg('dim', step.label) : step.label;\n const row = rightAligned(\n `${this.theme.fg(color, glyph)} ${label}`,\n this.theme.fg('dim', step.detail ?? ''),\n inner,\n );\n return `${INDENT}${row}`;\n });\n }\n\n /** The tail of the running command, so a prompt it printed is visible. */\n private outputLines(field: ConfigField, width: number): string[] {\n const inner = Math.max(1, width - INDENT.length);\n return (field.output ?? []).map((line) => `${INDENT}${this.theme.fg('dim', fitRow(line, inner))}`);\n }\n\n private progressBar(ratio: number | undefined, width: number): string {\n if (ratio === undefined) return '';\n const filled = Math.max(0, Math.min(PROGRESS_WIDTH, Math.round(ratio * PROGRESS_WIDTH)));\n const bar = PROGRESS_FULL.repeat(filled) + PROGRESS_EMPTY.repeat(PROGRESS_WIDTH - filled);\n return fitRow(`${bar} ${Math.round(ratio * 100)}%`, width);\n }\n\n private fieldLines(width: number): string[] {\n const section = this.currentSection();\n if (!section) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n const lines = [this.theme.bold(section.title), ''];\n if (section.notice) {\n lines.push(\n ...wrap(section.notice, width).map((line) =>\n this.theme.fg(section.noticeLevel === 'error' ? 'error' : 'dim', line),\n ),\n );\n lines.push('');\n }\n if (section.fields.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n\n const valueWidth = Math.max(1, width - LABEL_COLUMN);\n for (const [index, field] of section.fields.entries()) {\n const active = index === this.fieldIndex && this.pane === 'fields';\n const editing = active && this.mode === 'edit';\n const isPending = this.pending.has(this.pendingKey(field));\n const shown = editing ? `${this.draft}█` : (field.value ?? field.placeholder ?? UNSET_PLACEHOLDER);\n const tone = editing || field.value ? 'text' : 'dim';\n const value = this.theme.fg(isPending || field.busy ? 'dim' : tone, shown);\n const status = field.statusText ?? (isPending ? '' : '');\n const row = `${this.theme.fg('dim', fitRow(field.label, LABEL_COLUMN))}${\n status\n ? rightAligned(value, this.theme.fg(field.status === 'blocked' ? 'error' : 'dim', status), valueWidth)\n : fitRow(value, valueWidth)\n }`;\n lines.push(active ? this.theme.bg('selectedBg', fitRow(row, width)) : row);\n lines.push(...this.stepLines(field, width));\n if (field.busy && field.progress) {\n lines.push(this.theme.fg('dim', fitRow(`${INDENT}${field.progress.label}`, width)));\n const bar = this.progressBar(field.progress.ratio, valueWidth);\n if (bar) lines.push(`${INDENT}${this.theme.fg('accent', bar)}`);\n }\n lines.push(...this.outputLines(field, width));\n if (active && field.awaitingInput) {\n // The command is asking something. Answering is the only useful action,\n // so the line is offered without having to enter a mode first.\n const prompt = this.mode === 'edit' ? `${this.draft}█` : 'press i to answer';\n lines.push(\n `${INDENT}${this.theme.fg('warning', fitRow(`waiting for input ${prompt}`, width - INDENT.length))}`,\n );\n }\n if (!active) continue;\n if (field.detail) {\n lines.push(\n ...wrap(field.detail, Math.max(1, width - LABEL_COLUMN)).map(\n (line) => `${' '.repeat(LABEL_COLUMN)}${this.theme.fg('dim', line)}`,\n ),\n );\n }\n if (field.keyPath) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.theme.fg('accent', field.keyPath)}`);\n if (field.actions && field.actions.length > 0) lines.push(`${' '.repeat(LABEL_COLUMN)}${this.hintLine()}`);\n }\n return lines;\n }\n\n private choiceLines(width: number): string[] {\n const field = this.currentField();\n const choices = this.currentChoices();\n const lines = [this.theme.bold(field?.label ?? ''), ''];\n if (choices.length === 0) {\n lines.push(this.theme.fg('dim', NO_FIELDS_MESSAGE));\n return lines;\n }\n const budget = Math.max(1, this.bodyHeight - lines.length);\n const selected = Math.max(0, Math.min(this.choiceIndex, choices.length - 1));\n const start = Math.max(0, Math.min(selected - budget + 1, Math.max(0, choices.length - budget)));\n let lastGroup: string | undefined;\n for (const [offset, choice] of choices.slice(start, start + budget).entries()) {\n if (choice.group && choice.group !== lastGroup) {\n lastGroup = choice.group;\n lines.push(this.theme.fg('accent', fitRow(choice.group, width)));\n }\n const status = choice.statusText ?? choice.status ?? '';\n const row = rightAligned(\n `${INDENT}${choice.label}${choice.detail ? this.theme.fg('dim', ` ${choice.detail}`) : ''}`,\n this.theme.fg(choice.status === 'blocked' ? 'error' : 'dim', status),\n width,\n );\n lines.push(start + offset === selected ? this.theme.bg('selectedBg', fitRow(row, width)) : row);\n }\n return lines;\n }\n\n private hints(): readonly (readonly [string, string])[] {\n if (this.mode === 'edit') {\n return [\n ['⌫', 'delete'],\n ['enter', this.currentField()?.awaitingInput ? 'send' : 'save'],\n ['esc', 'cancel'],\n ];\n }\n if (this.mode === 'choices') {\n const choice = this.currentChoices()[this.choiceIndex];\n // The hint names what enter does to the entry under the cursor, because on\n // this list that differs row by row.\n return [\n ['↑↓', 'move'],\n ['enter', choice?.action ?? DEFAULT_CHOICE_ACTION],\n ['esc', 'back'],\n ];\n }\n const field = this.currentField();\n if (field?.awaitingInput) {\n return [\n ['i', 'answer'],\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n if (field?.busy) {\n return [\n ['ctrl+c', 'abort'],\n ['esc', 'close, keeps running'],\n ];\n }\n const offered = (field?.actions ?? []).map((action) => [action.key, action.label] as const);\n return [\n ['↑↓', 'move'],\n ['tab', 'pane'],\n ['enter', this.pane === 'sections' ? 'fields' : 'edit'],\n ...offered,\n ...(this.pane === 'fields' && offered.length === 0 ? ([['d', 'clear']] as const) : []),\n ['esc', this.pane === 'fields' ? 'back' : 'close'],\n ];\n }\n\n private hintLine(): string {\n return this.hints()\n .map(\n ([key, label]) =>\n `${this.theme.bg('selectedBg', this.theme.fg('text', ` ${key} `))}${this.theme.fg('dim', ` ${label}`)}`,\n )\n .join(this.theme.fg('dim', ' '));\n }\n\n protected getChrome(): DoomOverlayChrome {\n const section = this.currentSection();\n const field = this.currentField();\n const breadcrumb =\n this.mode === 'choices' && section && field ? `${BREADCRUMB} › ${section.title} / ${field.label}` : BREADCRUMB;\n return {\n title: TITLE,\n accent: DOOM_OVERLAY_ACCENT,\n breadcrumb,\n headerRight: `${this.sections.length} sections · ${this.sections.length === 0 ? 'none' : (section?.source ?? '')}`,\n footer: this.hintLine(),\n };\n }\n\n /**\n * The one line the panel says in its own voice, kept at the bottom where it\n * does not push a section's fields around. Dropped entirely when the body is\n * too short to spare the rows: a field the user came here to change outranks\n * a pointer to the docs.\n */\n private helpBanner(width: number): string[] {\n const badge = this.theme.inverse(this.theme.bold(this.theme.fg('accent', HELP_BADGE)));\n return ['', fitRow(`${badge} ${this.theme.fg('dim', HELP_HINT)}`, width)];\n }\n\n protected renderBody(width: number, height: number): string[] {\n const banner = height >= HELP_BANNER_MIN_ROWS ? this.helpBanner(width) : [];\n const bodyHeight = Math.max(0, height - banner.length);\n this.bodyHeight = bodyHeight;\n const right = this.mode === 'choices' ? this.choiceLines.bind(this) : this.fieldLines.bind(this);\n if (width < MIN_WIDTH) return [...this.renderStacked(width, bodyHeight, right), ...banner];\n\n const leftWidth = Math.max(MIN_PANE_WIDTH, Math.floor((width - 1) * SECTION_PANE_RATIO));\n const rightWidth = Math.max(1, width - leftWidth - 1);\n const leftContent = Math.max(1, leftWidth - PANE_GUTTER);\n const rightContent = Math.max(1, rightWidth - PANE_GUTTER);\n const left = this.sectionLines(leftContent);\n const rightLines = right(rightContent);\n\n const divider = this.theme.fg('borderMuted', '│');\n const rows = Array.from({ length: bodyHeight }, (_, index) => {\n const row = `${fitRow(left[index] ?? '', leftContent)} ${divider} ${fitRow(rightLines[index] ?? '', rightContent)}`;\n return truncateToWidth(row, width, ELLIPSIS);\n });\n return [...rows, ...banner];\n }\n\n private renderStacked(width: number, height: number, right: (width: number) => string[]): string[] {\n const topHeight = Math.max(1, Math.floor((height - 1) / 2));\n const bottomHeight = Math.max(0, height - topHeight - 1);\n this.bodyHeight = topHeight;\n const top = this.sectionLines(width);\n const bottom = right(width);\n return [\n ...top.slice(0, topHeight),\n ...Array.from({ length: Math.max(0, topHeight - top.length) }, () => ''),\n this.theme.fg('borderMuted', '─'.repeat(width)),\n ...bottom.slice(0, bottomHeight),\n ];\n }\n}\n\nexport interface DoomConfigStoreView {\n getSections(): DoomConfigSectionView[];\n invoke(invocation: { source: string; sectionId: string; fieldId: string; action: string; value?: string }): void;\n subscribe(listener: () => void): () => void;\n}\n\nexport async function openConfigOverlay(ctx: ExtensionContext, registry: DoomConfigStoreView): Promise<void> {\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new ConfigOverlayComponent(tui, theme, registry, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\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.
|
|
3
|
+
"version": "0.0.1-alpha.35",
|
|
4
4
|
"description": "Leader-key menus, themes, and TUI components for DoomPi and Pi extensions.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -160,9 +160,9 @@
|
|
|
160
160
|
"dependencies": {
|
|
161
161
|
"@deepseek-ai/cordis": "4.0.1",
|
|
162
162
|
"yaml": "2.9.0",
|
|
163
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
164
|
-
"@agimon-ai/doompi-
|
|
165
|
-
"@agimon-ai/doompi-
|
|
163
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.35",
|
|
164
|
+
"@agimon-ai/doompi-hashline": "0.0.1-alpha.7",
|
|
165
|
+
"@agimon-ai/doompi-telemetry": "0.0.1-alpha.35"
|
|
166
166
|
},
|
|
167
167
|
"devDependencies": {
|
|
168
168
|
"@earendil-works/pi-coding-agent": "0.84.2",
|