@agimon-ai/doompi-major-mode 0.0.1-alpha.3 → 0.0.1-alpha.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/README.md +36 -14
  2. package/dist/_virtual/_rolldown/runtime.cjs +1 -0
  3. package/dist/adapters/pi/extension.cjs +1 -1
  4. package/dist/adapters/pi/extension.cjs.map +1 -1
  5. package/dist/adapters/pi/extension.d.cts.map +1 -1
  6. package/dist/adapters/pi/extension.d.mts.map +1 -1
  7. package/dist/adapters/pi/extension.mjs +1 -1
  8. package/dist/adapters/pi/extension.mjs.map +1 -1
  9. package/dist/adapters/pi/relaunchRequest.cjs +2 -0
  10. package/dist/adapters/pi/relaunchRequest.cjs.map +1 -0
  11. package/dist/adapters/pi/relaunchRequest.mjs +2 -0
  12. package/dist/adapters/pi/relaunchRequest.mjs.map +1 -0
  13. package/dist/commands/majorModeCommand.cjs +1 -1
  14. package/dist/commands/majorModeCommand.cjs.map +1 -1
  15. package/dist/commands/majorModeCommand.d.cts +4 -0
  16. package/dist/commands/majorModeCommand.d.cts.map +1 -1
  17. package/dist/commands/majorModeCommand.d.mts +4 -0
  18. package/dist/commands/majorModeCommand.d.mts.map +1 -1
  19. package/dist/commands/majorModeCommand.mjs +1 -1
  20. package/dist/commands/majorModeCommand.mjs.map +1 -1
  21. package/dist/services/majorModeText.cjs +4 -4
  22. package/dist/services/majorModeText.cjs.map +1 -1
  23. package/dist/services/majorModeText.d.cts +1 -1
  24. package/dist/services/majorModeText.d.cts.map +1 -1
  25. package/dist/services/majorModeText.d.mts +1 -1
  26. package/dist/services/majorModeText.d.mts.map +1 -1
  27. package/dist/services/majorModeText.mjs +4 -4
  28. package/dist/services/majorModeText.mjs.map +1 -1
  29. package/dist/services/pendingSelection.d.cts.map +1 -1
  30. package/dist/services/pendingSelection.d.mts.map +1 -1
  31. package/dist/types/telemetry.d.cts +2 -2
  32. package/dist/types/telemetry.d.cts.map +1 -1
  33. package/dist/types/telemetry.d.mts +2 -2
  34. package/dist/types/telemetry.d.mts.map +1 -1
  35. package/llms.txt +7 -2
  36. package/package.json +24 -15
  37. package/src/prompts/doompi-author-major-mode/SKILL.md +30 -0
  38. package/src/prompts/doompi-author-major-mode/references/modes-contract.md +152 -0
package/README.md CHANGED
@@ -2,40 +2,62 @@
2
2
 
3
3
  Named major-mode selection for [DoomPi](https://www.npmjs.com/package/@agimon-ai/doompi) sessions.
4
4
 
5
- A **major mode** is one of the four axes of a DoomPi selection, alongside domains, the profile and
6
- the minor modes. It names an ordered list of layers, and each layer names the packages it activates:
5
+ A **major mode** is one of the four axes of a DoomPi selection, alongside domains, the profile,
6
+ and the minor modes. It names an ordered list of layers, and each layer names the packages it
7
+ activates:
7
8
 
8
9
  ```yaml
9
10
  # .doom/modes.yaml
11
+ layers:
12
+ team:
13
+ packages: ['@agimon-ai/doompi-team']
14
+ ask-user:
15
+ packages: ['@agimon-ai/doompi-user-feedback']
16
+ task:
17
+ packages: ['@agimon-ai/doompi-task']
18
+
10
19
  majorMode:
11
20
  minimal:
12
- description: Lean coding mode with the smallest tool surface.
13
- layers: [help, team, goal, voice, file-edit]
21
+ description: Lean mode with Team delegation and persistent tasks.
22
+ layers: [team, task]
14
23
  copilot:
15
- description: Full coding copilot with planning and supervised commands.
16
- layers: [help, team, goal, voice, ask-user, runner, file-edit, plan]
24
+ description: General coding with delegation, persistent tasks, and structured user feedback.
25
+ layers: [team, ask-user, task]
17
26
  ```
18
27
 
19
- The `layers` array is authored activation order, not a set.
28
+ Default packages load before these layers. The `layers` array is authored activation order, not a
29
+ set.
20
30
 
21
31
  ## What it registers
22
32
 
23
- | Surface | Behaviour |
33
+ | Surface | Behavior |
24
34
  | ----------------------- | -------------------------------------------------------------------------- |
25
35
  | `/mode` | picks a major mode, applies it, and reloads |
26
36
  | `major_mode` voice tool | `list` returns every mode with its purpose and layers; `switch` queues one |
27
37
  | status line | `*profile*:[major-mode]:domains`, amber while a switch is pending |
38
+ | Help contribution | exposes focused `modes.yaml` guidance while DoomPi Help is active |
39
+
40
+ ## Help guidance
41
+
42
+ This package registers `doompi-author-major-mode` with the session-local `doom/help` Cordis
43
+ service. Registration is inert until `@agimon-ai/doompi-help` is active. Once active, the Help
44
+ skill routes the AI through this package's validated [`llms.txt`](./llms.txt) index.
45
+
46
+ The package owns both the concise [major-mode authoring prompt](./src/prompts/doompi-author-major-mode/SKILL.md)
47
+ and the detailed [`modes.yaml` contract](./src/prompts/doompi-author-major-mode/references/modes-contract.md),
48
+ so the guidance is versioned with the parser and runtime that consume it.
28
49
 
29
50
  ## Switching, and why it is not always a reload
30
51
 
31
52
  A major-mode change alters which extensions load, and Pi freezes the CLI `--extension` list at
32
53
  construction. So the disposition depends on how the session started:
33
54
 
34
- - **synchronized** (`dpi`) — the session composes its own extension set on every load, so the new
35
- layers arrive with a `pi-reload`. If the target composition has no synchronized bundle yet, the
36
- transition reports `sync-required` and asks for `dpi sync`.
37
- - **launcher** — the extension closure is fixed, so a mode whose layers differ needs a new process.
38
- The switch is journalled as pending and the command prints the relaunch line rather than
55
+ - **synchronized** (`dpi`, or regular `pi` after `doompi sync`): the session composes its own
56
+ extension set on every load, so the new layers arrive with a `pi-reload`. If the target
57
+ composition has no synchronized bundle yet, the transition reports `sync-required` and asks for
58
+ `doompi sync`.
59
+ - **launcher**: the extension closure is fixed, so a mode whose layers differ needs a new process.
60
+ The switch is journaled as pending and the command prints the relaunch line rather than
39
61
  pretending it applied.
40
62
 
41
63
  Either way the change is planned through the session's transition coordinator first, and active
@@ -49,7 +71,7 @@ where it can be the terminal action.
49
71
  ## Installation
50
72
 
51
73
  DoomPi depends on this package and activates it as fixed host core, so a DoomPi install already has
52
- it. It is not selectable from `.doom/modes.yaml` — it is what reads that file.
74
+ it. It is not selectable from `.doom/modes.yaml`. It is what reads that file.
53
75
 
54
76
  ## License
55
77
 
@@ -0,0 +1 @@
1
+ var e=Object.create,t=Object.defineProperty,n=Object.getOwnPropertyDescriptor,r=Object.getOwnPropertyNames,i=Object.getPrototypeOf,a=Object.prototype.hasOwnProperty,o=(e,i,o,s)=>{if(i&&typeof i==`object`||typeof i==`function`)for(var c=r(i),l=0,u=c.length,d;l<u;l++)d=c[l],!a.call(e,d)&&d!==o&&t(e,d,{get:(e=>i[e]).bind(null,d),enumerable:!(s=n(i,d))||s.enumerable});return e},s=(n,r,s)=>(s=n==null?{}:e(i(n)),o(r||!n||!n.__esModule||!a.call(n,`default`)?t(s,`default`,{value:n,enumerable:!0}):s,n));exports.__toESM=s;
@@ -1,2 +1,2 @@
1
- const e=require("../../services/statusLine.cjs"),t=require("../../types/telemetry.cjs"),n=require("../../types/majorMode.cjs"),r=require("../../commands/majorModeCommand.cjs"),i=require("../telemetry/logSinkTelemetry.cjs"),a=require("./voiceTool.cjs");let o=require("@agimon-ai/doompi-config/harnessStore"),s=require("@agimon-ai/doompi-config/piContext"),c=require("@agimon-ai/doompi-extension-contracts/config"),l=require("@agimon-ai/doompi-extension-contracts/cordis-host"),u=require("@agimon-ai/doompi-extension-contracts/transition"),d=require("@agimon-ai/doompi-extension-contracts/voice-tools"),f=require("@agimon-ai/doompi-extension-contracts/voice-reload-handoff");function p(){let e,t,n,r;return{config:()=>e??=import(`@agimon-ai/doompi-config/majorModes`),journal:()=>t??=import(`@agimon-ai/doompi-config/piContext`),picker:()=>n??=import(`@agimon-ai/doompi-ui/components/matrixPicker`),selection:()=>r??=import(`@agimon-ai/doompi-config/selectionSwitch`)}}function m(i,{pi:l,telemetry:m}){let h=p(),g,_=i.inject([c.DOOM_CONFIG_SERVICE,u.DOOM_TRANSITION_SERVICE],e=>(g=e,()=>{g===e&&(g=void 0)})),v=()=>{if(!g)throw Error(`Doom major-mode runtime is waiting for the session config service.`);return g},y=async e=>{let t=(0,s.requireDoomConfigContext)(v()).harness,{loadMajorModesConfig:n}=await h.config();return{config:n((0,o.requireHarnessRoot)(t)),majorMode:t.majorMode,domains:t.domains,profile:t.profile}},b=(0,f.createVoiceReloadHandoffStore)({now:()=>Date.now(),createToken:()=>crypto.randomUUID()});i.inject([c.DOOM_CONFIG_SERVICE,u.DOOM_TRANSITION_SERVICE,d.DOOM_VOICE_TOOLS_SERVICE],e=>a.registerMajorModeVoiceCapability((0,d.requireDoomVoiceToolsService)(e),l,y,b,()=>e)),i.effect(function*(){r.registerMajorModeCommand(l,m,{cordisContext:v,currentView:y,reloadHandoffs:b,loadPicker:h.picker,loadSelectionSwitch:h.selection,loadConfigJournal:h.journal,resolveLayers:(e,t)=>[...e.majorMode[t]?.layers??[]]}),l.on(`session_start`,async(n,r)=>{await _.await();try{let{majorMode:t,domains:n,profile:i}=(0,s.requireDoomConfigContext)(v()).harness;r.ui.setStatus(e.STATUS_KEY,e.colorStatus(r.ui.theme,t,n,i,!1))}catch(n){m.recordError(t.MAJOR_MODE_EVENT.majorModeUnavailable,n),r.ui.setStatus(e.STATUS_KEY,`major mode unavailable (${n instanceof Error?n.message:String(n)})`)}}),yield()=>void 0},n.MAJOR_MODE_SOURCE)}async function h(e,t=i.createMajorModeTelemetry()){let r=await(0,l.connectDoomCordisHost)(e,n.MAJOR_MODE_SOURCE),a=r.root.plugin(m,{pi:e,telemetry:t});try{await a}catch(e){try{await a.dispose()}finally{await r.dispose()}throw e}let o;e.on(`session_shutdown`,()=>o??=(async()=>{try{await a.dispose()}finally{await r.dispose()}})())}exports.default=h,exports.majorModeExtension=h;
1
+ const e=require("../../services/statusLine.cjs"),t=require("../../types/telemetry.cjs"),n=require("../../types/majorMode.cjs"),r=require("../../commands/majorModeCommand.cjs"),i=require("./relaunchRequest.cjs"),a=require("../telemetry/logSinkTelemetry.cjs"),o=require("./voiceTool.cjs");let s=require("@agimon-ai/doompi-config/harnessStore"),c=require("@agimon-ai/doompi-config/piContext"),l=require("@agimon-ai/doompi-extension-contracts/config"),u=require("@agimon-ai/doompi-extension-contracts/cordis-host"),d=require("@agimon-ai/doompi-extension-contracts/help"),f=require("@agimon-ai/doompi-extension-contracts/transition"),p=require("@agimon-ai/doompi-extension-contracts/voice-tools"),m=require("@agimon-ai/doompi-extension-contracts/voice-reload-handoff");function h(){let e,t,n,r;return{config:()=>e??=import(`@agimon-ai/doompi-config/majorModes`),journal:()=>t??=import(`@agimon-ai/doompi-config/piContext`),picker:()=>n??=import(`@agimon-ai/doompi-ui/components/matrixPicker`),selection:()=>r??=import(`@agimon-ai/doompi-config/selectionSwitch`)}}function g(a,{pi:u,telemetry:g}){let _=h();a.inject([d.DOOM_HELP_SERVICE],e=>{let t=(0,d.requireDoomHelpService)(e).register({source:n.MAJOR_MODE_SOURCE,moduleUrl:require("url").pathToFileURL(__filename).href,skills:[{name:`doompi-author-major-mode`,description:`Configure DoomPi default packages, layers, extensions, hook groups, and named major modes. Use when creating or editing ~/.pi/.doom/modes.yaml or a repository's .doom/modes.yaml, choosing a default mode, or diagnosing which behavior a mode activates.`}]});return()=>t.dispose()});let v,y=a.inject([l.DOOM_CONFIG_SERVICE,f.DOOM_TRANSITION_SERVICE],e=>(v=e,()=>{v===e&&(v=void 0)})),b=()=>{if(!v)throw Error(`Doom major-mode runtime is waiting for the session config service.`);return v},x=async e=>{let t=(0,c.requireDoomConfigContext)(b()).harness,{loadMajorModesConfig:n}=await _.config();return{config:n((0,s.requireHarnessRoot)(t)),majorMode:t.majorMode,domains:t.domains,profile:t.profile}},S=(0,m.createVoiceReloadHandoffStore)({now:()=>Date.now(),createToken:()=>crypto.randomUUID()});a.inject([l.DOOM_CONFIG_SERVICE,f.DOOM_TRANSITION_SERVICE,p.DOOM_VOICE_TOOLS_SERVICE],e=>o.registerMajorModeVoiceCapability((0,p.requireDoomVoiceToolsService)(e),u,x,S,()=>e)),a.effect(function*(){r.registerMajorModeCommand(u,g,{cordisContext:b,currentView:x,reloadHandoffs:S,loadPicker:_.picker,loadSelectionSwitch:_.selection,loadConfigJournal:_.journal,resolveLayers:(e,t)=>[...e.majorMode[t]?.layers??[]],supervisedRelaunchAvailable:i.supervisedRelaunchAvailable,requestSupervisedRelaunch:i.requestSupervisedRelaunch}),u.on(`session_start`,async(n,r)=>{await y.await();try{let{majorMode:t,domains:n,profile:i}=(0,c.requireDoomConfigContext)(b()).harness;r.ui.setStatus(e.STATUS_KEY,e.colorStatus(r.ui.theme,t,n,i,!1))}catch(n){g.recordError(t.MAJOR_MODE_EVENT.majorModeUnavailable,n),r.ui.setStatus(e.STATUS_KEY,`major mode unavailable (${n instanceof Error?n.message:String(n)})`)}}),yield()=>void 0},n.MAJOR_MODE_SOURCE)}async function _(e,t=a.createMajorModeTelemetry()){let r=await(0,u.connectDoomCordisHost)(e,n.MAJOR_MODE_SOURCE),i=r.root.plugin(g,{pi:e,telemetry:t});try{await i}catch(e){try{await i.dispose()}finally{await r.dispose()}throw e}let o;e.on(`session_shutdown`,()=>o??=(async()=>{try{await i.dispose()}finally{await r.dispose()}})())}exports.default=_,exports.majorModeExtension=_;
2
2
  //# sourceMappingURL=extension.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.cjs","names":["DOOM_CONFIG_SERVICE","DOOM_TRANSITION_SERVICE","requireDoomConfigContext","requireHarnessRoot","createVoiceReloadHandoffStore","DOOM_VOICE_TOOLS_SERVICE","registerMajorModeVoiceCapability","requireDoomVoiceToolsService","STATUS_KEY","colorStatus","MAJOR_MODE_EVENT","MAJOR_MODE_SOURCE","createMajorModeTelemetry","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { requireHarnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-extension-contracts/config';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_TRANSITION_SERVICE } from '@agimon-ai/doompi-extension-contracts/transition';\nimport {\n DOOM_VOICE_TOOLS_SERVICE,\n requireDoomVoiceToolsService,\n} from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport { createVoiceReloadHandoffStore } from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerMajorModeCommand } from '../../commands/majorModeCommand.ts';\nimport { colorStatus, STATUS_KEY } from '../../services/statusLine.ts';\nimport { MAJOR_MODE_SOURCE, type MajorModeView } from '../../types/majorMode.ts';\nimport { MAJOR_MODE_EVENT, type MajorModeTelemetry } from '../../types/telemetry.ts';\nimport { createMajorModeTelemetry } from '../telemetry/logSinkTelemetry.ts';\nimport { registerMajorModeVoiceCapability } from './voiceTool.ts';\n\n/**\n * Everything the axis needs beyond the session, loaded on first use.\n *\n * Every session registers /mode, but most never run it, so the picker, the\n * switch and the journal stay off the startup path.\n */\nfunction lazyModules() {\n let config: Promise<typeof import('@agimon-ai/doompi-config/majorModes')> | undefined;\n let journal: Promise<typeof import('@agimon-ai/doompi-config/piContext')> | undefined;\n let picker: Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')> | undefined;\n let selection: Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')> | undefined;\n return {\n config: () => (config ??= import('@agimon-ai/doompi-config/majorModes')),\n journal: () => (journal ??= import('@agimon-ai/doompi-config/piContext')),\n picker: () => (picker ??= import('@agimon-ai/doompi-ui/components/matrixPicker')),\n selection: () => (selection ??= import('@agimon-ai/doompi-config/selectionSwitch')),\n };\n}\n\ninterface MajorModePluginConfig {\n readonly pi: ExtensionAPI;\n readonly telemetry: MajorModeTelemetry;\n}\n\nfunction majorModePlugin(cordis: Context, { pi, telemetry }: MajorModePluginConfig): void {\n const load = lazyModules();\n let activeContext: Context | undefined;\n const runtimeInjection = cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE], (context) => {\n activeContext = context;\n return () => {\n if (activeContext === context) activeContext = undefined;\n };\n });\n const requireRuntimeContext = (): Context => {\n if (!activeContext) throw new Error('Doom major-mode runtime is waiting for the session config service.');\n return activeContext;\n };\n const currentView = async (_ctx: ExtensionContext): Promise<MajorModeView> => {\n const state = requireDoomConfigContext(requireRuntimeContext()).harness;\n const { loadMajorModesConfig } = await load.config();\n return {\n config: loadMajorModesConfig(requireHarnessRoot(state)),\n majorMode: state.majorMode,\n domains: state.domains,\n profile: state.profile,\n };\n };\n const reloadHandoffs = createVoiceReloadHandoffStore({\n now: () => Date.now(),\n createToken: () => crypto.randomUUID(),\n });\n cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE, DOOM_VOICE_TOOLS_SERVICE], (voiceContext) =>\n registerMajorModeVoiceCapability(\n requireDoomVoiceToolsService(voiceContext),\n pi,\n currentView,\n reloadHandoffs,\n () => voiceContext,\n ),\n );\n\n cordis.effect(function* () {\n registerMajorModeCommand(pi, telemetry, {\n cordisContext: requireRuntimeContext,\n currentView,\n reloadHandoffs,\n loadPicker: load.picker,\n loadSelectionSwitch: load.selection,\n loadConfigJournal: load.journal,\n resolveLayers: (config, majorMode) => [...(config.majorMode[majorMode]?.layers ?? [])],\n });\n pi.on('session_start', async (_event, ctx) => {\n await runtimeInjection.await();\n try {\n const { majorMode, domains, profile } = requireDoomConfigContext(requireRuntimeContext()).harness;\n ctx.ui.setStatus(STATUS_KEY, colorStatus(ctx.ui.theme, majorMode, domains, profile, false));\n } catch (error) {\n void telemetry.recordError(MAJOR_MODE_EVENT.majorModeUnavailable, error);\n ctx.ui.setStatus(\n STATUS_KEY,\n `major mode unavailable (${error instanceof Error ? error.message : String(error)})`,\n );\n }\n });\n yield () => undefined;\n }, MAJOR_MODE_SOURCE);\n}\n\n/** The package's single standard Pi factory. */\nexport async function majorModeExtension(\n pi: ExtensionAPI,\n telemetry: MajorModeTelemetry = createMajorModeTelemetry(),\n): Promise<void> {\n const connection = await connectDoomCordisHost(pi, MAJOR_MODE_SOURCE);\n const fiber = connection.root.plugin(majorModePlugin, { pi, telemetry });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nexport default majorModeExtension;\n"],"mappings":"iqBAyBA,SAAS,GAAc,CACrB,IAAI,EACA,EACA,EACA,EACJ,MAAO,CACL,WAAe,IAAW,OAAO,uCACjC,YAAgB,IAAY,OAAO,sCACnC,WAAe,IAAW,OAAO,gDACjC,cAAkB,IAAc,OAAO,2CACzC,CACF,CAOA,SAAS,EAAgB,EAAiB,CAAE,KAAI,aAA0C,CACxF,IAAM,EAAO,EAAY,EACrB,EACE,EAAmB,EAAO,OAAO,CAACA,EAAAA,oBAAqBC,EAAAA,uBAAuB,EAAI,IACtF,EAAgB,MACH,CACP,IAAkB,IAAS,EAAgB,IAAA,GACjD,EACD,EACK,MAAuC,CAC3C,GAAI,CAAC,EAAe,MAAU,MAAM,oEAAoE,EACxG,OAAO,CACT,EACM,EAAc,KAAO,IAAmD,CAC5E,IAAM,GAAA,EAAQC,EAAAA,yBAAAA,CAAyB,EAAsB,CAAC,CAAC,CAAC,QAC1D,CAAE,wBAAyB,MAAM,EAAK,OAAO,EACnD,MAAO,CACL,OAAQ,GAAA,EAAqBC,EAAAA,mBAAAA,CAAmB,CAAK,CAAC,EACtD,UAAW,EAAM,UACjB,QAAS,EAAM,QACf,QAAS,EAAM,OACjB,CACF,EACM,GAAA,EAAiBC,EAAAA,8BAAAA,CAA8B,CACnD,QAAW,KAAK,IAAI,EACpB,gBAAmB,OAAO,WAAW,CACvC,CAAC,EACD,EAAO,OAAO,CAACJ,EAAAA,oBAAqBC,EAAAA,wBAAyBI,EAAAA,wBAAwB,EAAI,GACvFC,EAAAA,kCAAAA,EACEC,EAAAA,6BAAAA,CAA6B,CAAY,EACzC,EACA,EACA,MACM,CACR,CACF,EAEA,EAAO,OAAO,WAAa,CACzB,EAAA,yBAAyB,EAAI,EAAW,CACtC,cAAe,EACf,cACA,iBACA,WAAY,EAAK,OACjB,oBAAqB,EAAK,UAC1B,kBAAmB,EAAK,QACxB,eAAgB,EAAQ,IAAc,CAAC,GAAI,EAAO,UAAU,EAAU,EAAE,QAAU,CAAC,CAAE,CACvF,CAAC,EACD,EAAG,GAAG,gBAAiB,MAAO,EAAQ,IAAQ,CAC5C,MAAM,EAAiB,MAAM,EAC7B,GAAI,CACF,GAAM,CAAE,YAAW,UAAS,YAAA,EAAYL,EAAAA,yBAAAA,CAAyB,EAAsB,CAAC,CAAC,CAAC,QAC1F,EAAI,GAAG,UAAUM,EAAAA,WAAYC,EAAAA,YAAY,EAAI,GAAG,MAAO,EAAW,EAAS,EAAS,EAAK,CAAC,CAC5F,OAAS,EAAO,CACd,EAAe,YAAYC,EAAAA,iBAAiB,qBAAsB,CAAK,EACvE,EAAI,GAAG,UACLF,EAAAA,WACA,2BAA2B,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EAAE,EACpF,CACF,CACF,CAAC,EACD,SAAY,IAAA,EACd,EAAGG,EAAAA,iBAAiB,CACtB,CAGA,eAAsB,EACpB,EACA,EAAgCC,EAAAA,yBAAyB,EAC1C,CACf,IAAM,EAAa,MAAA,EAAMC,EAAAA,sBAAAA,CAAsB,EAAIF,EAAAA,iBAAiB,EAC9D,EAAQ,EAAW,KAAK,OAAO,EAAiB,CAAE,KAAI,WAAU,CAAC,EACvE,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CACd,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACA,MAAM,CACR,CACA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF"}
1
+ {"version":3,"file":"extension.cjs","names":["DOOM_HELP_SERVICE","requireDoomHelpService","MAJOR_MODE_SOURCE","DOOM_CONFIG_SERVICE","DOOM_TRANSITION_SERVICE","requireDoomConfigContext","requireHarnessRoot","createVoiceReloadHandoffStore","DOOM_VOICE_TOOLS_SERVICE","registerMajorModeVoiceCapability","requireDoomVoiceToolsService","STATUS_KEY","colorStatus","MAJOR_MODE_EVENT","createMajorModeTelemetry","connectDoomCordisHost"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { requireHarnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-extension-contracts/config';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport { DOOM_TRANSITION_SERVICE } from '@agimon-ai/doompi-extension-contracts/transition';\nimport {\n DOOM_VOICE_TOOLS_SERVICE,\n requireDoomVoiceToolsService,\n} from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport { createVoiceReloadHandoffStore } from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerMajorModeCommand } from '../../commands/majorModeCommand.ts';\nimport { requestSupervisedRelaunch, supervisedRelaunchAvailable } from './relaunchRequest.ts';\nimport { colorStatus, STATUS_KEY } from '../../services/statusLine.ts';\nimport { MAJOR_MODE_SOURCE, type MajorModeView } from '../../types/majorMode.ts';\nimport { MAJOR_MODE_EVENT, type MajorModeTelemetry } from '../../types/telemetry.ts';\nimport { createMajorModeTelemetry } from '../telemetry/logSinkTelemetry.ts';\nimport { registerMajorModeVoiceCapability } from './voiceTool.ts';\n\n/**\n * Everything the axis needs beyond the session, loaded on first use.\n *\n * Every session registers /mode, but most never run it, so the picker, the\n * switch and the journal stay off the startup path.\n */\nfunction lazyModules() {\n let config: Promise<typeof import('@agimon-ai/doompi-config/majorModes')> | undefined;\n let journal: Promise<typeof import('@agimon-ai/doompi-config/piContext')> | undefined;\n let picker: Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')> | undefined;\n let selection: Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')> | undefined;\n return {\n config: () => (config ??= import('@agimon-ai/doompi-config/majorModes')),\n journal: () => (journal ??= import('@agimon-ai/doompi-config/piContext')),\n picker: () => (picker ??= import('@agimon-ai/doompi-ui/components/matrixPicker')),\n selection: () => (selection ??= import('@agimon-ai/doompi-config/selectionSwitch')),\n };\n}\n\ninterface MajorModePluginConfig {\n readonly pi: ExtensionAPI;\n readonly telemetry: MajorModeTelemetry;\n}\n\nfunction majorModePlugin(cordis: Context, { pi, telemetry }: MajorModePluginConfig): void {\n const load = lazyModules();\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: MAJOR_MODE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-author-major-mode',\n description:\n \"Configure DoomPi default packages, layers, extensions, hook groups, and named major modes. Use when creating or editing ~/.pi/.doom/modes.yaml or a repository's .doom/modes.yaml, choosing a default mode, or diagnosing which behavior a mode activates.\",\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n let activeContext: Context | undefined;\n const runtimeInjection = cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE], (context) => {\n activeContext = context;\n return () => {\n if (activeContext === context) activeContext = undefined;\n };\n });\n const requireRuntimeContext = (): Context => {\n if (!activeContext) throw new Error('Doom major-mode runtime is waiting for the session config service.');\n return activeContext;\n };\n const currentView = async (_ctx: ExtensionContext): Promise<MajorModeView> => {\n const state = requireDoomConfigContext(requireRuntimeContext()).harness;\n const { loadMajorModesConfig } = await load.config();\n return {\n config: loadMajorModesConfig(requireHarnessRoot(state)),\n majorMode: state.majorMode,\n domains: state.domains,\n profile: state.profile,\n };\n };\n const reloadHandoffs = createVoiceReloadHandoffStore({\n now: () => Date.now(),\n createToken: () => crypto.randomUUID(),\n });\n cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE, DOOM_VOICE_TOOLS_SERVICE], (voiceContext) =>\n registerMajorModeVoiceCapability(\n requireDoomVoiceToolsService(voiceContext),\n pi,\n currentView,\n reloadHandoffs,\n () => voiceContext,\n ),\n );\n\n cordis.effect(function* () {\n registerMajorModeCommand(pi, telemetry, {\n cordisContext: requireRuntimeContext,\n currentView,\n reloadHandoffs,\n loadPicker: load.picker,\n loadSelectionSwitch: load.selection,\n loadConfigJournal: load.journal,\n resolveLayers: (config, majorMode) => [...(config.majorMode[majorMode]?.layers ?? [])],\n supervisedRelaunchAvailable,\n requestSupervisedRelaunch,\n });\n pi.on('session_start', async (_event, ctx) => {\n await runtimeInjection.await();\n try {\n const { majorMode, domains, profile } = requireDoomConfigContext(requireRuntimeContext()).harness;\n ctx.ui.setStatus(STATUS_KEY, colorStatus(ctx.ui.theme, majorMode, domains, profile, false));\n } catch (error) {\n void telemetry.recordError(MAJOR_MODE_EVENT.majorModeUnavailable, error);\n ctx.ui.setStatus(\n STATUS_KEY,\n `major mode unavailable (${error instanceof Error ? error.message : String(error)})`,\n );\n }\n });\n yield () => undefined;\n }, MAJOR_MODE_SOURCE);\n}\n\n/** The package's single standard Pi factory. */\nexport async function majorModeExtension(\n pi: ExtensionAPI,\n telemetry: MajorModeTelemetry = createMajorModeTelemetry(),\n): Promise<void> {\n const connection = await connectDoomCordisHost(pi, MAJOR_MODE_SOURCE);\n const fiber = connection.root.plugin(majorModePlugin, { pi, telemetry });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nexport default majorModeExtension;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBA4GsB,mBACpB,IAAI,cACJ,YAAW,qBACV"}
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBA+HsB,mBACpB,IAAI,cACJ,YAAW,qBACV"}
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBA4GsB,mBACpB,IAAI,cACJ,YAAW,qBACV"}
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBA+HsB,mBACpB,IAAI,cACJ,YAAW,qBACV"}
@@ -1,2 +1,2 @@
1
- import{STATUS_KEY as e,colorStatus as t}from"../../services/statusLine.mjs";import{MAJOR_MODE_EVENT as n}from"../../types/telemetry.mjs";import{MAJOR_MODE_SOURCE as r}from"../../types/majorMode.mjs";import{registerMajorModeCommand as i}from"../../commands/majorModeCommand.mjs";import{createMajorModeTelemetry as a}from"../telemetry/logSinkTelemetry.mjs";import{registerMajorModeVoiceCapability as o}from"./voiceTool.mjs";import{requireHarnessRoot as s}from"@agimon-ai/doompi-config/harnessStore";import{requireDoomConfigContext as c}from"@agimon-ai/doompi-config/piContext";import{DOOM_CONFIG_SERVICE as l}from"@agimon-ai/doompi-extension-contracts/config";import{connectDoomCordisHost as u}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_TRANSITION_SERVICE as d}from"@agimon-ai/doompi-extension-contracts/transition";import{DOOM_VOICE_TOOLS_SERVICE as f,requireDoomVoiceToolsService as p}from"@agimon-ai/doompi-extension-contracts/voice-tools";import{createVoiceReloadHandoffStore as m}from"@agimon-ai/doompi-extension-contracts/voice-reload-handoff";function h(){let e,t,n,r;return{config:()=>e??=import(`@agimon-ai/doompi-config/majorModes`),journal:()=>t??=import(`@agimon-ai/doompi-config/piContext`),picker:()=>n??=import(`@agimon-ai/doompi-ui/components/matrixPicker`),selection:()=>r??=import(`@agimon-ai/doompi-config/selectionSwitch`)}}function g(a,{pi:u,telemetry:g}){let _=h(),v,y=a.inject([l,d],e=>(v=e,()=>{v===e&&(v=void 0)})),b=()=>{if(!v)throw Error(`Doom major-mode runtime is waiting for the session config service.`);return v},x=async e=>{let t=c(b()).harness,{loadMajorModesConfig:n}=await _.config();return{config:n(s(t)),majorMode:t.majorMode,domains:t.domains,profile:t.profile}},S=m({now:()=>Date.now(),createToken:()=>crypto.randomUUID()});a.inject([l,d,f],e=>o(p(e),u,x,S,()=>e)),a.effect(function*(){i(u,g,{cordisContext:b,currentView:x,reloadHandoffs:S,loadPicker:_.picker,loadSelectionSwitch:_.selection,loadConfigJournal:_.journal,resolveLayers:(e,t)=>[...e.majorMode[t]?.layers??[]]}),u.on(`session_start`,async(r,i)=>{await y.await();try{let{majorMode:n,domains:r,profile:a}=c(b()).harness;i.ui.setStatus(e,t(i.ui.theme,n,r,a,!1))}catch(t){g.recordError(n.majorModeUnavailable,t),i.ui.setStatus(e,`major mode unavailable (${t instanceof Error?t.message:String(t)})`)}}),yield()=>void 0},r)}async function _(e,t=a()){let n=await u(e,r),i=n.root.plugin(g,{pi:e,telemetry:t});try{await i}catch(e){try{await i.dispose()}finally{await n.dispose()}throw e}let o;e.on(`session_shutdown`,()=>o??=(async()=>{try{await i.dispose()}finally{await n.dispose()}})())}export{_ as default,_ as majorModeExtension};
1
+ import{STATUS_KEY as e,colorStatus as t}from"../../services/statusLine.mjs";import{MAJOR_MODE_EVENT as n}from"../../types/telemetry.mjs";import{MAJOR_MODE_SOURCE as r}from"../../types/majorMode.mjs";import{registerMajorModeCommand as i}from"../../commands/majorModeCommand.mjs";import{requestSupervisedRelaunch as a,supervisedRelaunchAvailable as o}from"./relaunchRequest.mjs";import{createMajorModeTelemetry as s}from"../telemetry/logSinkTelemetry.mjs";import{registerMajorModeVoiceCapability as c}from"./voiceTool.mjs";import{requireHarnessRoot as l}from"@agimon-ai/doompi-config/harnessStore";import{requireDoomConfigContext as u}from"@agimon-ai/doompi-config/piContext";import{DOOM_CONFIG_SERVICE as d}from"@agimon-ai/doompi-extension-contracts/config";import{connectDoomCordisHost as f}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_HELP_SERVICE as p,requireDoomHelpService as m}from"@agimon-ai/doompi-extension-contracts/help";import{DOOM_TRANSITION_SERVICE as h}from"@agimon-ai/doompi-extension-contracts/transition";import{DOOM_VOICE_TOOLS_SERVICE as g,requireDoomVoiceToolsService as _}from"@agimon-ai/doompi-extension-contracts/voice-tools";import{createVoiceReloadHandoffStore as v}from"@agimon-ai/doompi-extension-contracts/voice-reload-handoff";function y(){let e,t,n,r;return{config:()=>e??=import(`@agimon-ai/doompi-config/majorModes`),journal:()=>t??=import(`@agimon-ai/doompi-config/piContext`),picker:()=>n??=import(`@agimon-ai/doompi-ui/components/matrixPicker`),selection:()=>r??=import(`@agimon-ai/doompi-config/selectionSwitch`)}}function b(s,{pi:f,telemetry:b}){let x=y();s.inject([p],e=>{let t=m(e).register({source:r,moduleUrl:import.meta.url,skills:[{name:`doompi-author-major-mode`,description:`Configure DoomPi default packages, layers, extensions, hook groups, and named major modes. Use when creating or editing ~/.pi/.doom/modes.yaml or a repository's .doom/modes.yaml, choosing a default mode, or diagnosing which behavior a mode activates.`}]});return()=>t.dispose()});let S,C=s.inject([d,h],e=>(S=e,()=>{S===e&&(S=void 0)})),w=()=>{if(!S)throw Error(`Doom major-mode runtime is waiting for the session config service.`);return S},T=async e=>{let t=u(w()).harness,{loadMajorModesConfig:n}=await x.config();return{config:n(l(t)),majorMode:t.majorMode,domains:t.domains,profile:t.profile}},E=v({now:()=>Date.now(),createToken:()=>crypto.randomUUID()});s.inject([d,h,g],e=>c(_(e),f,T,E,()=>e)),s.effect(function*(){i(f,b,{cordisContext:w,currentView:T,reloadHandoffs:E,loadPicker:x.picker,loadSelectionSwitch:x.selection,loadConfigJournal:x.journal,resolveLayers:(e,t)=>[...e.majorMode[t]?.layers??[]],supervisedRelaunchAvailable:o,requestSupervisedRelaunch:a}),f.on(`session_start`,async(r,i)=>{await C.await();try{let{majorMode:n,domains:r,profile:a}=u(w()).harness;i.ui.setStatus(e,t(i.ui.theme,n,r,a,!1))}catch(t){b.recordError(n.majorModeUnavailable,t),i.ui.setStatus(e,`major mode unavailable (${t instanceof Error?t.message:String(t)})`)}}),yield()=>void 0},r)}async function x(e,t=s()){let n=await f(e,r),i=n.root.plugin(b,{pi:e,telemetry:t});try{await i}catch(e){try{await i.dispose()}finally{await n.dispose()}throw e}let a;e.on(`session_shutdown`,()=>a??=(async()=>{try{await i.dispose()}finally{await n.dispose()}})())}export{x as default,x as majorModeExtension};
2
2
  //# sourceMappingURL=extension.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { requireHarnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-extension-contracts/config';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_TRANSITION_SERVICE } from '@agimon-ai/doompi-extension-contracts/transition';\nimport {\n DOOM_VOICE_TOOLS_SERVICE,\n requireDoomVoiceToolsService,\n} from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport { createVoiceReloadHandoffStore } from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerMajorModeCommand } from '../../commands/majorModeCommand.ts';\nimport { colorStatus, STATUS_KEY } from '../../services/statusLine.ts';\nimport { MAJOR_MODE_SOURCE, type MajorModeView } from '../../types/majorMode.ts';\nimport { MAJOR_MODE_EVENT, type MajorModeTelemetry } from '../../types/telemetry.ts';\nimport { createMajorModeTelemetry } from '../telemetry/logSinkTelemetry.ts';\nimport { registerMajorModeVoiceCapability } from './voiceTool.ts';\n\n/**\n * Everything the axis needs beyond the session, loaded on first use.\n *\n * Every session registers /mode, but most never run it, so the picker, the\n * switch and the journal stay off the startup path.\n */\nfunction lazyModules() {\n let config: Promise<typeof import('@agimon-ai/doompi-config/majorModes')> | undefined;\n let journal: Promise<typeof import('@agimon-ai/doompi-config/piContext')> | undefined;\n let picker: Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')> | undefined;\n let selection: Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')> | undefined;\n return {\n config: () => (config ??= import('@agimon-ai/doompi-config/majorModes')),\n journal: () => (journal ??= import('@agimon-ai/doompi-config/piContext')),\n picker: () => (picker ??= import('@agimon-ai/doompi-ui/components/matrixPicker')),\n selection: () => (selection ??= import('@agimon-ai/doompi-config/selectionSwitch')),\n };\n}\n\ninterface MajorModePluginConfig {\n readonly pi: ExtensionAPI;\n readonly telemetry: MajorModeTelemetry;\n}\n\nfunction majorModePlugin(cordis: Context, { pi, telemetry }: MajorModePluginConfig): void {\n const load = lazyModules();\n let activeContext: Context | undefined;\n const runtimeInjection = cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE], (context) => {\n activeContext = context;\n return () => {\n if (activeContext === context) activeContext = undefined;\n };\n });\n const requireRuntimeContext = (): Context => {\n if (!activeContext) throw new Error('Doom major-mode runtime is waiting for the session config service.');\n return activeContext;\n };\n const currentView = async (_ctx: ExtensionContext): Promise<MajorModeView> => {\n const state = requireDoomConfigContext(requireRuntimeContext()).harness;\n const { loadMajorModesConfig } = await load.config();\n return {\n config: loadMajorModesConfig(requireHarnessRoot(state)),\n majorMode: state.majorMode,\n domains: state.domains,\n profile: state.profile,\n };\n };\n const reloadHandoffs = createVoiceReloadHandoffStore({\n now: () => Date.now(),\n createToken: () => crypto.randomUUID(),\n });\n cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE, DOOM_VOICE_TOOLS_SERVICE], (voiceContext) =>\n registerMajorModeVoiceCapability(\n requireDoomVoiceToolsService(voiceContext),\n pi,\n currentView,\n reloadHandoffs,\n () => voiceContext,\n ),\n );\n\n cordis.effect(function* () {\n registerMajorModeCommand(pi, telemetry, {\n cordisContext: requireRuntimeContext,\n currentView,\n reloadHandoffs,\n loadPicker: load.picker,\n loadSelectionSwitch: load.selection,\n loadConfigJournal: load.journal,\n resolveLayers: (config, majorMode) => [...(config.majorMode[majorMode]?.layers ?? [])],\n });\n pi.on('session_start', async (_event, ctx) => {\n await runtimeInjection.await();\n try {\n const { majorMode, domains, profile } = requireDoomConfigContext(requireRuntimeContext()).harness;\n ctx.ui.setStatus(STATUS_KEY, colorStatus(ctx.ui.theme, majorMode, domains, profile, false));\n } catch (error) {\n void telemetry.recordError(MAJOR_MODE_EVENT.majorModeUnavailable, error);\n ctx.ui.setStatus(\n STATUS_KEY,\n `major mode unavailable (${error instanceof Error ? error.message : String(error)})`,\n );\n }\n });\n yield () => undefined;\n }, MAJOR_MODE_SOURCE);\n}\n\n/** The package's single standard Pi factory. */\nexport async function majorModeExtension(\n pi: ExtensionAPI,\n telemetry: MajorModeTelemetry = createMajorModeTelemetry(),\n): Promise<void> {\n const connection = await connectDoomCordisHost(pi, MAJOR_MODE_SOURCE);\n const fiber = connection.root.plugin(majorModePlugin, { pi, telemetry });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nexport default majorModeExtension;\n"],"mappings":"ijCAyBA,SAAS,GAAc,CACrB,IAAI,EACA,EACA,EACA,EACJ,MAAO,CACL,WAAe,IAAW,OAAO,uCACjC,YAAgB,IAAY,OAAO,sCACnC,WAAe,IAAW,OAAO,gDACjC,cAAkB,IAAc,OAAO,2CACzC,CACF,CAOA,SAAS,EAAgB,EAAiB,CAAE,KAAI,aAA0C,CACxF,IAAM,EAAO,EAAY,EACrB,EACE,EAAmB,EAAO,OAAO,CAAC,EAAqB,CAAuB,EAAI,IACtF,EAAgB,MACH,CACP,IAAkB,IAAS,EAAgB,IAAA,GACjD,EACD,EACK,MAAuC,CAC3C,GAAI,CAAC,EAAe,MAAU,MAAM,oEAAoE,EACxG,OAAO,CACT,EACM,EAAc,KAAO,IAAmD,CAC5E,IAAM,EAAQ,EAAyB,EAAsB,CAAC,CAAC,CAAC,QAC1D,CAAE,wBAAyB,MAAM,EAAK,OAAO,EACnD,MAAO,CACL,OAAQ,EAAqB,EAAmB,CAAK,CAAC,EACtD,UAAW,EAAM,UACjB,QAAS,EAAM,QACf,QAAS,EAAM,OACjB,CACF,EACM,EAAiB,EAA8B,CACnD,QAAW,KAAK,IAAI,EACpB,gBAAmB,OAAO,WAAW,CACvC,CAAC,EACD,EAAO,OAAO,CAAC,EAAqB,EAAyB,CAAwB,EAAI,GACvF,EACE,EAA6B,CAAY,EACzC,EACA,EACA,MACM,CACR,CACF,EAEA,EAAO,OAAO,WAAa,CACzB,EAAyB,EAAI,EAAW,CACtC,cAAe,EACf,cACA,iBACA,WAAY,EAAK,OACjB,oBAAqB,EAAK,UAC1B,kBAAmB,EAAK,QACxB,eAAgB,EAAQ,IAAc,CAAC,GAAI,EAAO,UAAU,EAAU,EAAE,QAAU,CAAC,CAAE,CACvF,CAAC,EACD,EAAG,GAAG,gBAAiB,MAAO,EAAQ,IAAQ,CAC5C,MAAM,EAAiB,MAAM,EAC7B,GAAI,CACF,GAAM,CAAE,YAAW,UAAS,WAAY,EAAyB,EAAsB,CAAC,CAAC,CAAC,QAC1F,EAAI,GAAG,UAAU,EAAY,EAAY,EAAI,GAAG,MAAO,EAAW,EAAS,EAAS,EAAK,CAAC,CAC5F,OAAS,EAAO,CACd,EAAe,YAAY,EAAiB,qBAAsB,CAAK,EACvE,EAAI,GAAG,UACL,EACA,2BAA2B,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EAAE,EACpF,CACF,CACF,CAAC,EACD,SAAY,IAAA,EACd,EAAG,CAAiB,CACtB,CAGA,eAAsB,EACpB,EACA,EAAgC,EAAyB,EAC1C,CACf,IAAM,EAAa,MAAM,EAAsB,EAAI,CAAiB,EAC9D,EAAQ,EAAW,KAAK,OAAO,EAAiB,CAAE,KAAI,WAAU,CAAC,EACvE,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CACd,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACA,MAAM,CACR,CACA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF"}
1
+ {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { requireHarnessRoot } from '@agimon-ai/doompi-config/harnessStore';\nimport { requireDoomConfigContext } from '@agimon-ai/doompi-config/piContext';\nimport { DOOM_CONFIG_SERVICE } from '@agimon-ai/doompi-extension-contracts/config';\nimport { connectDoomCordisHost } from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport { DOOM_TRANSITION_SERVICE } from '@agimon-ai/doompi-extension-contracts/transition';\nimport {\n DOOM_VOICE_TOOLS_SERVICE,\n requireDoomVoiceToolsService,\n} from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport { createVoiceReloadHandoffStore } from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerMajorModeCommand } from '../../commands/majorModeCommand.ts';\nimport { requestSupervisedRelaunch, supervisedRelaunchAvailable } from './relaunchRequest.ts';\nimport { colorStatus, STATUS_KEY } from '../../services/statusLine.ts';\nimport { MAJOR_MODE_SOURCE, type MajorModeView } from '../../types/majorMode.ts';\nimport { MAJOR_MODE_EVENT, type MajorModeTelemetry } from '../../types/telemetry.ts';\nimport { createMajorModeTelemetry } from '../telemetry/logSinkTelemetry.ts';\nimport { registerMajorModeVoiceCapability } from './voiceTool.ts';\n\n/**\n * Everything the axis needs beyond the session, loaded on first use.\n *\n * Every session registers /mode, but most never run it, so the picker, the\n * switch and the journal stay off the startup path.\n */\nfunction lazyModules() {\n let config: Promise<typeof import('@agimon-ai/doompi-config/majorModes')> | undefined;\n let journal: Promise<typeof import('@agimon-ai/doompi-config/piContext')> | undefined;\n let picker: Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')> | undefined;\n let selection: Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')> | undefined;\n return {\n config: () => (config ??= import('@agimon-ai/doompi-config/majorModes')),\n journal: () => (journal ??= import('@agimon-ai/doompi-config/piContext')),\n picker: () => (picker ??= import('@agimon-ai/doompi-ui/components/matrixPicker')),\n selection: () => (selection ??= import('@agimon-ai/doompi-config/selectionSwitch')),\n };\n}\n\ninterface MajorModePluginConfig {\n readonly pi: ExtensionAPI;\n readonly telemetry: MajorModeTelemetry;\n}\n\nfunction majorModePlugin(cordis: Context, { pi, telemetry }: MajorModePluginConfig): void {\n const load = lazyModules();\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: MAJOR_MODE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-author-major-mode',\n description:\n \"Configure DoomPi default packages, layers, extensions, hook groups, and named major modes. Use when creating or editing ~/.pi/.doom/modes.yaml or a repository's .doom/modes.yaml, choosing a default mode, or diagnosing which behavior a mode activates.\",\n },\n ],\n });\n return () => contribution.dispose();\n });\n\n let activeContext: Context | undefined;\n const runtimeInjection = cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE], (context) => {\n activeContext = context;\n return () => {\n if (activeContext === context) activeContext = undefined;\n };\n });\n const requireRuntimeContext = (): Context => {\n if (!activeContext) throw new Error('Doom major-mode runtime is waiting for the session config service.');\n return activeContext;\n };\n const currentView = async (_ctx: ExtensionContext): Promise<MajorModeView> => {\n const state = requireDoomConfigContext(requireRuntimeContext()).harness;\n const { loadMajorModesConfig } = await load.config();\n return {\n config: loadMajorModesConfig(requireHarnessRoot(state)),\n majorMode: state.majorMode,\n domains: state.domains,\n profile: state.profile,\n };\n };\n const reloadHandoffs = createVoiceReloadHandoffStore({\n now: () => Date.now(),\n createToken: () => crypto.randomUUID(),\n });\n cordis.inject([DOOM_CONFIG_SERVICE, DOOM_TRANSITION_SERVICE, DOOM_VOICE_TOOLS_SERVICE], (voiceContext) =>\n registerMajorModeVoiceCapability(\n requireDoomVoiceToolsService(voiceContext),\n pi,\n currentView,\n reloadHandoffs,\n () => voiceContext,\n ),\n );\n\n cordis.effect(function* () {\n registerMajorModeCommand(pi, telemetry, {\n cordisContext: requireRuntimeContext,\n currentView,\n reloadHandoffs,\n loadPicker: load.picker,\n loadSelectionSwitch: load.selection,\n loadConfigJournal: load.journal,\n resolveLayers: (config, majorMode) => [...(config.majorMode[majorMode]?.layers ?? [])],\n supervisedRelaunchAvailable,\n requestSupervisedRelaunch,\n });\n pi.on('session_start', async (_event, ctx) => {\n await runtimeInjection.await();\n try {\n const { majorMode, domains, profile } = requireDoomConfigContext(requireRuntimeContext()).harness;\n ctx.ui.setStatus(STATUS_KEY, colorStatus(ctx.ui.theme, majorMode, domains, profile, false));\n } catch (error) {\n void telemetry.recordError(MAJOR_MODE_EVENT.majorModeUnavailable, error);\n ctx.ui.setStatus(\n STATUS_KEY,\n `major mode unavailable (${error instanceof Error ? error.message : String(error)})`,\n );\n }\n });\n yield () => undefined;\n }, MAJOR_MODE_SOURCE);\n}\n\n/** The package's single standard Pi factory. */\nexport async function majorModeExtension(\n pi: ExtensionAPI,\n telemetry: MajorModeTelemetry = createMajorModeTelemetry(),\n): Promise<void> {\n const connection = await connectDoomCordisHost(pi, MAJOR_MODE_SOURCE);\n const fiber = connection.root.plugin(majorModePlugin, { pi, telemetry });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\nexport default majorModeExtension;\n"],"mappings":"+vCA2BA,SAAS,GAAc,CACrB,IAAI,EACA,EACA,EACA,EACJ,MAAO,CACL,WAAe,IAAW,OAAO,uCACjC,YAAgB,IAAY,OAAO,sCACnC,WAAe,IAAW,OAAO,gDACjC,cAAkB,IAAc,OAAO,2CACzC,CACF,CAOA,SAAS,EAAgB,EAAiB,CAAE,KAAI,aAA0C,CACxF,IAAM,EAAO,EAAY,EACzB,EAAO,OAAO,CAAC,CAAiB,EAAI,GAAgB,CAClD,IAAM,EAAe,EAAuB,CAAW,CAAC,CAAC,SAAS,CAChE,OAAQ,EACR,UAAW,YAAY,IACvB,OAAQ,CACN,CACE,KAAM,2BACN,YACE,4PACJ,CACF,CACF,CAAC,EACD,UAAa,EAAa,QAAQ,CACpC,CAAC,EAED,IAAI,EACE,EAAmB,EAAO,OAAO,CAAC,EAAqB,CAAuB,EAAI,IACtF,EAAgB,MACH,CACP,IAAkB,IAAS,EAAgB,IAAA,GACjD,EACD,EACK,MAAuC,CAC3C,GAAI,CAAC,EAAe,MAAU,MAAM,oEAAoE,EACxG,OAAO,CACT,EACM,EAAc,KAAO,IAAmD,CAC5E,IAAM,EAAQ,EAAyB,EAAsB,CAAC,CAAC,CAAC,QAC1D,CAAE,wBAAyB,MAAM,EAAK,OAAO,EACnD,MAAO,CACL,OAAQ,EAAqB,EAAmB,CAAK,CAAC,EACtD,UAAW,EAAM,UACjB,QAAS,EAAM,QACf,QAAS,EAAM,OACjB,CACF,EACM,EAAiB,EAA8B,CACnD,QAAW,KAAK,IAAI,EACpB,gBAAmB,OAAO,WAAW,CACvC,CAAC,EACD,EAAO,OAAO,CAAC,EAAqB,EAAyB,CAAwB,EAAI,GACvF,EACE,EAA6B,CAAY,EACzC,EACA,EACA,MACM,CACR,CACF,EAEA,EAAO,OAAO,WAAa,CACzB,EAAyB,EAAI,EAAW,CACtC,cAAe,EACf,cACA,iBACA,WAAY,EAAK,OACjB,oBAAqB,EAAK,UAC1B,kBAAmB,EAAK,QACxB,eAAgB,EAAQ,IAAc,CAAC,GAAI,EAAO,UAAU,EAAU,EAAE,QAAU,CAAC,CAAE,EACrF,8BACA,2BACF,CAAC,EACD,EAAG,GAAG,gBAAiB,MAAO,EAAQ,IAAQ,CAC5C,MAAM,EAAiB,MAAM,EAC7B,GAAI,CACF,GAAM,CAAE,YAAW,UAAS,WAAY,EAAyB,EAAsB,CAAC,CAAC,CAAC,QAC1F,EAAI,GAAG,UAAU,EAAY,EAAY,EAAI,GAAG,MAAO,EAAW,EAAS,EAAS,EAAK,CAAC,CAC5F,OAAS,EAAO,CACd,EAAe,YAAY,EAAiB,qBAAsB,CAAK,EACvE,EAAI,GAAG,UACL,EACA,2BAA2B,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,EAAE,EACpF,CACF,CACF,CAAC,EACD,SAAY,IAAA,EACd,EAAG,CAAiB,CACtB,CAGA,eAAsB,EACpB,EACA,EAAgC,EAAyB,EAC1C,CACf,IAAM,EAAa,MAAM,EAAsB,EAAI,CAAiB,EAC9D,EAAQ,EAAW,KAAK,OAAO,EAAiB,CAAE,KAAI,WAAU,CAAC,EACvE,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CACd,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACA,MAAM,CACR,CACA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../_virtual/_rolldown/runtime.cjs");let t=require("node:fs");t=e.__toESM(t,1);let n=require("@agimon-ai/doompi-extension-contracts/relaunch-handoff");function r(e=process.env){return!!e[n.DOOM_RELAUNCH_FILE_ENV]}function i(e,r,i=process.env){let a=i[n.DOOM_RELAUNCH_FILE_ENV];if(!a)return!1;try{return t.default.writeFileSync(a,(0,n.serializeRelaunchHandoff)({version:1,majorMode:e,operationId:r})),!0}catch{return!1}}exports.requestSupervisedRelaunch=i,exports.supervisedRelaunchAvailable=r;
2
+ //# sourceMappingURL=relaunchRequest.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relaunchRequest.cjs","names":["DOOM_RELAUNCH_FILE_ENV","serializeRelaunchHandoff"],"sources":["../../../src/adapters/pi/relaunchRequest.ts"],"sourcesContent":["import fs from 'node:fs';\nimport {\n DOOM_RELAUNCH_FILE_ENV,\n serializeRelaunchHandoff,\n} from '@agimon-ai/doompi-extension-contracts/relaunch-handoff';\n\n/** Whether a process supervisor is listening for relaunch requests. */\nexport function supervisedRelaunchAvailable(env: NodeJS.ProcessEnv = process.env): boolean {\n return Boolean(env[DOOM_RELAUNCH_FILE_ENV]);\n}\n\n/**\n * Hands a relaunch-class major-mode switch to the process supervisor.\n *\n * A supervisor that owns this agent process (doompi-server) points\n * DOOMPI_RELAUNCH_FILE at a path it watches; writing the target selection\n * there asks it to end this agent's input for a graceful exit and respawn it\n * with the new major mode under the same session id. Write only when the\n * session is idle: the supervisor acts on the file as soon as it appears.\n * Returns false when no supervisor is listening or the write fails, in which\n * case the caller keeps the manual-relaunch message.\n */\nexport function requestSupervisedRelaunch(\n majorMode: string,\n operationId: string,\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n const file = env[DOOM_RELAUNCH_FILE_ENV];\n if (!file) return false;\n try {\n fs.writeFileSync(file, serializeRelaunchHandoff({ version: 1, majorMode, operationId }));\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"0KAOA,SAAgB,EAA4B,EAAyB,QAAQ,IAAc,CACzF,MAAO,EAAQ,EAAIA,EAAAA,uBACrB,CAaA,SAAgB,EACd,EACA,EACA,EAAyB,QAAQ,IACxB,CACT,IAAM,EAAO,EAAIA,EAAAA,wBACjB,GAAI,CAAC,EAAM,MAAO,GAClB,GAAI,CAEF,OADA,EAAA,QAAG,cAAc,GAAA,EAAMC,EAAAA,yBAAAA,CAAyB,CAAE,QAAS,EAAG,YAAW,aAAY,CAAC,CAAC,EAChF,EACT,MAAQ,CACN,MAAO,EACT,CACF"}
@@ -0,0 +1,2 @@
1
+ import e from"node:fs";import{DOOM_RELAUNCH_FILE_ENV as t,serializeRelaunchHandoff as n}from"@agimon-ai/doompi-extension-contracts/relaunch-handoff";function r(e=process.env){return!!e[t]}function i(r,i,a=process.env){let o=a[t];if(!o)return!1;try{return e.writeFileSync(o,n({version:1,majorMode:r,operationId:i})),!0}catch{return!1}}export{i as requestSupervisedRelaunch,r as supervisedRelaunchAvailable};
2
+ //# sourceMappingURL=relaunchRequest.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"relaunchRequest.mjs","names":[],"sources":["../../../src/adapters/pi/relaunchRequest.ts"],"sourcesContent":["import fs from 'node:fs';\nimport {\n DOOM_RELAUNCH_FILE_ENV,\n serializeRelaunchHandoff,\n} from '@agimon-ai/doompi-extension-contracts/relaunch-handoff';\n\n/** Whether a process supervisor is listening for relaunch requests. */\nexport function supervisedRelaunchAvailable(env: NodeJS.ProcessEnv = process.env): boolean {\n return Boolean(env[DOOM_RELAUNCH_FILE_ENV]);\n}\n\n/**\n * Hands a relaunch-class major-mode switch to the process supervisor.\n *\n * A supervisor that owns this agent process (doompi-server) points\n * DOOMPI_RELAUNCH_FILE at a path it watches; writing the target selection\n * there asks it to end this agent's input for a graceful exit and respawn it\n * with the new major mode under the same session id. Write only when the\n * session is idle: the supervisor acts on the file as soon as it appears.\n * Returns false when no supervisor is listening or the write fails, in which\n * case the caller keeps the manual-relaunch message.\n */\nexport function requestSupervisedRelaunch(\n majorMode: string,\n operationId: string,\n env: NodeJS.ProcessEnv = process.env,\n): boolean {\n const file = env[DOOM_RELAUNCH_FILE_ENV];\n if (!file) return false;\n try {\n fs.writeFileSync(file, serializeRelaunchHandoff({ version: 1, majorMode, operationId }));\n return true;\n } catch {\n return false;\n }\n}\n"],"mappings":"qJAOA,SAAgB,EAA4B,EAAyB,QAAQ,IAAc,CACzF,MAAO,EAAQ,EAAI,EACrB,CAaA,SAAgB,EACd,EACA,EACA,EAAyB,QAAQ,IACxB,CACT,IAAM,EAAO,EAAI,GACjB,GAAI,CAAC,EAAM,MAAO,GAClB,GAAI,CAEF,OADA,EAAG,cAAc,EAAM,EAAyB,CAAE,QAAS,EAAG,YAAW,aAAY,CAAC,CAAC,EAChF,EACT,MAAQ,CACN,MAAO,EACT,CACF"}
@@ -1,2 +1,2 @@
1
- const e=require("../services/majorModeText.cjs"),t=require("../services/statusLine.cjs"),n=require("../types/telemetry.cjs"),r=require("../services/pendingSelection.cjs");require("../types/majorMode.cjs");let i=require("@agimon-ai/doompi-config/harnessStore"),a=require("@agimon-ai/doompi-config/piContext"),o=require("@agimon-ai/doompi-extension-contracts/transition"),s=require("@agimon-ai/doompi-extension-contracts/voice-tools"),c=require("@agimon-ai/doompi-config/majorModes"),l=require("@agimon-ai/doompi-extension-contracts/child-process");function u(e){return Error(`Major-mode transition was ${e.outcome}: ${e.diagnostics.join(`, `)}`)}function d(d,f,p){d.registerCommand(e.MAJOR_MODE_COMMAND,{description:`Pick a named major mode`,handler:async(m,h)=>{let g;try{g=e.voiceSwitchToken(m)}catch(t){h.ui.notify(e.errorMessage(t),`error`);return}if(m.trim()&&!g){h.ui.notify(`Usage: /${e.MAJOR_MODE_COMMAND}`,`warning`);return}let _=h.sessionManager.getSessionId(),v=p.cordisContext(),y,b,x,S=!1;try{let{config:m,majorMode:C,domains:w,profile:T}=await p.currentView(h),E=Object.keys(m.majorMode),D;if(g){let e=(0,s.readDoomVoiceToolsService)(v)?.readSession(_);if(e&&(y={sessionId:_,hostGeneration:e.hostGeneration}),b=y?p.reloadHandoffs.accept(g,y):void 0,b?.kind!==`major-mode-switch`||!b.majorMode)throw Error(`The voice major-mode switch token is stale or belongs to another session.`);D=b.majorMode}else{let t=`Major mode (current: ${C})`;D=(h.mode===`tui`?await p.loadPicker().then(({MatrixPickerComponent:n})=>h.ui.custom((r,i,a,o)=>new n({title:t,items:e.majorModeItems(m,E),selected:[C],multi:!1},i,a,o))):[e.optionName(await h.ui.select(t,E.map(t=>e.majorModeOptionLabel(t,m.majorMode[t]?.layers??[],C)))??``)])?.[0]}if(!D)return;let O=(0,l.alreadyComposed)();if(D===C){g&&y&&p.reloadHandoffs.discard(g,y),r.clearPendingSelection(d,p.cordisContext()),h.ui.notify(e.majorModeSummary(D,p.resolveLayers(m,D),C,O),`info`);return}let k=(0,o.requireDoomTransitionCoordinator)(v),A=await k.execute({sessionId:h.sessionManager.getSessionId(),hostGeneration:k.hostGeneration,operationId:b?.operationId??crypto.randomUUID(),source:b?`voice`:`command`,target:{axis:`major-mode`,majorMode:D}},async(s,l)=>{let u=[...l.candidate.layers],m=l.externalRelaunchRequired,g=(0,a.requireDoomConfigContext)(v),_=g.harness;if(l.strategy===`process-relaunch`){let i={version:1,operationId:l.operationId,active:r.selectionFromSnapshot(l.previous),target:r.selectionFromSnapshot(l.candidate),strategy:l.strategy,phase:`pending`},o=(0,a.requireDoomConfigContext)(v).pendingSelection;return o&&(0,a.supersedeDoomConfigTransition)(d,o),(0,a.appendDoomConfigTransition)(d,i),(0,a.replaceDoomConfigContext)(v,r.bindPendingSelection((0,a.requireDoomConfigContext)(v),i)),f.recordEvent(n.MAJOR_MODE_EVENT.majorModeSwitched,{"harness.major_mode.previous":_.majorMode,"harness.major_mode":D,"harness.layers":u.join(`,`),"harness.major_mode.stale":!0,"harness.transition.disposition":l.disposition,"harness.transition.operation_id":l.operationId}),h.ui.setStatus(t.STATUS_KEY,t.colorStatus(h.ui.theme,_.majorMode,_.domains,_.profile,!0)),h.ui.notify(e.applySummary(D,u,!0,O,l.strategy),`info`),`queued`}let[{applyMajorMode:y},{persistHarnessSelection:b}]=await Promise.all([p.loadSelectionSwitch(),p.loadConfigJournal()]),E={version:1,operationId:l.operationId,active:r.selectionFromSnapshot(l.previous),target:r.selectionFromSnapshot(l.candidate),strategy:l.strategy??`pi-reload`,phase:`pending`},A=(0,c.loadMajorModesConfig)(_.root??h.cwd),j=(0,i.snapshotHarnessState)(),M=g.pendingSelection;x=(0,o.prepareMinorModeReloadHandoff)(v,h.sessionManager.getSessionId(),k.hostGeneration,l.operationId,l.previous.minorModes);try{M&&(0,a.supersedeDoomConfigTransition)(d,M),(0,a.appendDoomConfigTransition)(d,E),b(d,y(A,D,_,l.candidate.compositionFingerprint,l.candidate.childActivation))}catch(e){x?.discard(),x=void 0;try{(0,i.restoreHarnessStateSnapshot)(j)}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`Transition failed and compensation failed: ${n}`,{cause:e})}throw(0,a.appendDoomConfigTransition)(d,{...E,phase:`aborted`}),M&&(0,a.appendDoomConfigTransition)(d,M),e}return f.recordEvent(n.MAJOR_MODE_EVENT.majorModeSwitched,{"harness.major_mode.previous":C,"harness.major_mode":D,"harness.layers":u.join(`,`),"harness.major_mode.stale":m,"harness.transition.disposition":l.disposition,"harness.transition.operation_id":l.operationId}),h.ui.setStatus(t.STATUS_KEY,t.colorStatus(h.ui.theme,D,w,T,!1)),h.ui.notify(e.applySummary(D,u,!1,O,l.strategy),`info`),S=!0,`applied`});if(A.outcome===`rejected`||A.outcome===`stale`)throw u(A)}catch(t){if(S=!1,x?.discard(),x=void 0,!g)throw t;y&&p.reloadHandoffs.discard(g,y),h.ui.notify(e.errorMessage(t),`error`)}if(S)try{await h.waitForIdle(),await h.reload()}catch(e){throw x?.discard(),e}}})}exports.registerMajorModeCommand=d;
1
+ const e=require("../services/majorModeText.cjs"),t=require("../services/statusLine.cjs"),n=require("../types/telemetry.cjs"),r=require("../services/pendingSelection.cjs");require("../types/majorMode.cjs");let i=require("@agimon-ai/doompi-config/harnessStore"),a=require("@agimon-ai/doompi-config/piContext"),o=require("@agimon-ai/doompi-extension-contracts/transition"),s=require("@agimon-ai/doompi-extension-contracts/voice-tools"),c=require("@agimon-ai/doompi-config/majorModes"),l=require("@agimon-ai/doompi-extension-contracts/child-process");function u(e){return Error(`Major-mode transition was ${e.outcome}: ${e.diagnostics.join(`, `)}`)}function d(d,f,p){d.registerCommand(e.MAJOR_MODE_COMMAND,{description:`Pick a named major mode`,handler:async(m,h)=>{let g;try{g=e.voiceSwitchToken(m)}catch(t){h.ui.notify(e.errorMessage(t),`error`);return}if(m.trim()&&!g){h.ui.notify(`Usage: /${e.MAJOR_MODE_COMMAND}`,`warning`);return}let _=h.sessionManager.getSessionId(),v=p.cordisContext(),y,b,x,S=!1,C;try{let{config:m,majorMode:w,domains:T,profile:E}=await p.currentView(h),D=Object.keys(m.majorMode),O;if(g){let e=(0,s.readDoomVoiceToolsService)(v)?.readSession(_);if(e&&(y={sessionId:_,hostGeneration:e.hostGeneration}),b=y?p.reloadHandoffs.accept(g,y):void 0,b?.kind!==`major-mode-switch`||!b.majorMode)throw Error(`The voice major-mode switch token is stale or belongs to another session.`);O=b.majorMode}else{let t=`Major mode (current: ${w})`;O=(h.mode===`tui`?await p.loadPicker().then(({MatrixPickerComponent:n})=>h.ui.custom((r,i,a,o)=>new n({title:t,items:e.majorModeItems(m,D),selected:[w],multi:!1},i,a,o))):[e.optionName(await h.ui.select(t,D.map(t=>e.majorModeOptionLabel(t,m.majorMode[t]?.layers??[],w)))??``)])?.[0]}if(!O)return;let k=(0,l.alreadyComposed)();if(O===w){g&&y&&p.reloadHandoffs.discard(g,y),r.clearPendingSelection(d,p.cordisContext()),h.ui.notify(e.majorModeSummary(O,p.resolveLayers(m,O),w,k),`info`);return}let A=(0,o.requireDoomTransitionCoordinator)(v),j=await A.execute({sessionId:h.sessionManager.getSessionId(),hostGeneration:A.hostGeneration,operationId:b?.operationId??crypto.randomUUID(),source:b?`voice`:`command`,target:{axis:`major-mode`,majorMode:O}},async(s,l)=>{let u=[...l.candidate.layers],m=l.externalRelaunchRequired,g=(0,a.requireDoomConfigContext)(v),_=g.harness;if(l.strategy===`process-relaunch`){let i={version:1,operationId:l.operationId,active:r.selectionFromSnapshot(l.previous),target:r.selectionFromSnapshot(l.candidate),strategy:l.strategy,phase:`pending`},o=(0,a.requireDoomConfigContext)(v).pendingSelection;if(o&&(0,a.supersedeDoomConfigTransition)(d,o),(0,a.appendDoomConfigTransition)(d,i),(0,a.replaceDoomConfigContext)(v,r.bindPendingSelection((0,a.requireDoomConfigContext)(v),i)),f.recordEvent(n.MAJOR_MODE_EVENT.majorModeSwitched,{"harness.major_mode.previous":_.majorMode,"harness.major_mode":O,"harness.layers":u.join(`,`),"harness.major_mode.stale":!0,"harness.transition.disposition":l.disposition,"harness.transition.operation_id":l.operationId}),h.ui.setStatus(t.STATUS_KEY,t.colorStatus(h.ui.theme,_.majorMode,_.domains,_.profile,!0)),p.supervisedRelaunchAvailable()){let e=O;C=()=>p.requestSupervisedRelaunch(e,l.operationId)}return h.ui.notify(e.applySummary(O,u,!0,k,l.strategy,C!==void 0),`info`),`queued`}let[{applyMajorMode:y},{persistHarnessSelection:b}]=await Promise.all([p.loadSelectionSwitch(),p.loadConfigJournal()]),D={version:1,operationId:l.operationId,active:r.selectionFromSnapshot(l.previous),target:r.selectionFromSnapshot(l.candidate),strategy:l.strategy??`pi-reload`,phase:`pending`},j=(0,c.loadMajorModesConfig)(_.root??h.cwd),M=(0,i.snapshotHarnessState)(),N=g.pendingSelection;x=(0,o.prepareMinorModeReloadHandoff)(v,h.sessionManager.getSessionId(),A.hostGeneration,l.operationId,l.previous.minorModes);try{N&&(0,a.supersedeDoomConfigTransition)(d,N),(0,a.appendDoomConfigTransition)(d,D),b(d,y(j,O,_,l.candidate.compositionFingerprint,l.candidate.childActivation))}catch(e){x?.discard(),x=void 0;try{(0,i.restoreHarnessStateSnapshot)(M)}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`Transition failed and compensation failed: ${n}`,{cause:e})}throw(0,a.appendDoomConfigTransition)(d,{...D,phase:`aborted`}),N&&(0,a.appendDoomConfigTransition)(d,N),e}return f.recordEvent(n.MAJOR_MODE_EVENT.majorModeSwitched,{"harness.major_mode.previous":w,"harness.major_mode":O,"harness.layers":u.join(`,`),"harness.major_mode.stale":m,"harness.transition.disposition":l.disposition,"harness.transition.operation_id":l.operationId}),h.ui.setStatus(t.STATUS_KEY,t.colorStatus(h.ui.theme,O,T,E,!1)),h.ui.notify(e.applySummary(O,u,!1,k,l.strategy),`info`),S=!0,`applied`});if(j.outcome===`rejected`||j.outcome===`stale`)throw u(j)}catch(t){if(S=!1,C=void 0,x?.discard(),x=void 0,!g)throw t;y&&p.reloadHandoffs.discard(g,y),h.ui.notify(e.errorMessage(t),`error`)}if(S)try{await h.waitForIdle(),await h.reload()}catch(e){throw x?.discard(),e}C&&(await h.waitForIdle(),C()&&h.shutdown())}})}exports.registerMajorModeCommand=d;
2
2
  //# sourceMappingURL=majorModeCommand.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"majorModeCommand.cjs","names":["MAJOR_MODE_COMMAND","voiceSwitchToken","errorMessage","readDoomVoiceToolsService","majorModeItems","optionName","majorModeOptionLabel","alreadyComposed","majorModeSummary","requireDoomTransitionCoordinator","requireDoomConfigContext","selectionFromSnapshot","bindPendingSelection","MAJOR_MODE_EVENT","STATUS_KEY","colorStatus","applySummary","loadMajorModesConfig","snapshotHarnessState","prepareMinorModeReloadHandoff"],"sources":["../../src/commands/majorModeCommand.ts"],"sourcesContent":["import { restoreHarnessStateSnapshot, snapshotHarnessState } from '@agimon-ai/doompi-config/harnessStore';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport {\n appendDoomConfigTransition,\n replaceDoomConfigContext,\n requireDoomConfigContext,\n supersedeDoomConfigTransition,\n} from '@agimon-ai/doompi-config/piContext';\nimport type { DoomConfigPendingSelection } from '@agimon-ai/doompi-config/types';\nimport { alreadyComposed } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n type DoomTransitionResult,\n type MinorModeReloadHandoffHandle,\n prepareMinorModeReloadHandoff,\n requireDoomTransitionCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/transition';\nimport { readDoomVoiceToolsService } from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport type {\n VoiceReloadHandoff,\n VoiceReloadHandoffIdentity,\n VoiceReloadHandoffStore,\n} from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport {\n applySummary,\n errorMessage,\n MAJOR_MODE_COMMAND,\n majorModeItems,\n majorModeOptionLabel,\n majorModeSummary,\n optionName,\n voiceSwitchToken,\n} from '../services/majorModeText.ts';\nimport { colorStatus, STATUS_KEY } from '../services/statusLine.ts';\nimport { MAJOR_MODE_EVENT, type MajorModeTelemetry } from '../types/telemetry.ts';\nimport { bindPendingSelection, clearPendingSelection, selectionFromSnapshot } from '../services/pendingSelection.ts';\nimport { MAJOR_MODE_SWITCH_HANDOFF_KIND, type MajorModeView } from '../types/majorMode.ts';\n\nfunction transitionError(result: DoomTransitionResult): Error {\n return new Error(`Major-mode transition was ${result.outcome}: ${result.diagnostics.join(', ')}`);\n}\n\nexport interface MajorModeCommandDependencies {\n readonly cordisContext: () => Context;\n readonly currentView: (ctx: ExtensionContext) => Promise<MajorModeView>;\n readonly reloadHandoffs: VoiceReloadHandoffStore;\n readonly loadPicker: () => Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')>;\n readonly loadSelectionSwitch: () => Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')>;\n readonly loadConfigJournal: () => Promise<typeof import('@agimon-ai/doompi-config/piContext')>;\n readonly resolveLayers: (config: MajorModeView['config'], majorMode: string) => string[];\n}\n\nexport function registerMajorModeCommand(\n pi: ExtensionAPI,\n telemetry: MajorModeTelemetry,\n dependencies: MajorModeCommandDependencies,\n): void {\n pi.registerCommand(MAJOR_MODE_COMMAND, {\n description: 'Pick a named major mode',\n handler: async (args, ctx) => {\n let token: string | undefined;\n try {\n token = voiceSwitchToken(args);\n } catch (error) {\n ctx.ui.notify(errorMessage(error), 'error');\n return;\n }\n if (args.trim() && !token) {\n ctx.ui.notify(`Usage: /${MAJOR_MODE_COMMAND}`, 'warning');\n return;\n }\n\n const sessionId = ctx.sessionManager.getSessionId();\n const cordis = dependencies.cordisContext();\n let sessionIdentity: VoiceReloadHandoffIdentity | undefined;\n let handoff: VoiceReloadHandoff | undefined;\n let minorModeReloadHandoff: MinorModeReloadHandoffHandle | undefined;\n let reloadRequested = false;\n try {\n const { config, majorMode, domains, profile } = await dependencies.currentView(ctx);\n const names = Object.keys(config.majorMode);\n let picked: string | undefined;\n\n if (token) {\n const voiceSession = readDoomVoiceToolsService(cordis)?.readSession(sessionId);\n if (voiceSession) sessionIdentity = { sessionId, hostGeneration: voiceSession.hostGeneration };\n handoff = sessionIdentity ? dependencies.reloadHandoffs.accept(token, sessionIdentity) : undefined;\n if (handoff?.kind !== MAJOR_MODE_SWITCH_HANDOFF_KIND || !handoff.majorMode) {\n throw new Error('The voice major-mode switch token is stale or belongs to another session.');\n }\n picked = handoff.majorMode;\n } else {\n const title = `Major mode (current: ${majorMode})`;\n const chosen =\n ctx.mode === 'tui'\n ? await dependencies\n .loadPicker()\n .then(({ MatrixPickerComponent }) =>\n ctx.ui.custom<string[] | undefined>(\n (_tui, theme, keybindings, done) =>\n new MatrixPickerComponent(\n { title, items: majorModeItems(config, names), selected: [majorMode], multi: false },\n theme,\n keybindings,\n done,\n ),\n ),\n )\n : [\n optionName(\n (await ctx.ui.select(\n title,\n names.map((name) => majorModeOptionLabel(name, config.majorMode[name]?.layers ?? [], majorMode)),\n )) ?? '',\n ),\n ];\n picked = chosen?.[0];\n }\n if (!picked) return;\n\n const composed = alreadyComposed();\n if (picked === majorMode) {\n if (token && sessionIdentity) dependencies.reloadHandoffs.discard(token, sessionIdentity);\n clearPendingSelection(pi, dependencies.cordisContext());\n ctx.ui.notify(\n majorModeSummary(picked, dependencies.resolveLayers(config, picked), majorMode, composed),\n 'info',\n );\n return;\n }\n\n const coordinator = requireDoomTransitionCoordinator(cordis);\n const result = await coordinator.execute(\n {\n sessionId: ctx.sessionManager.getSessionId(),\n hostGeneration: coordinator.hostGeneration,\n operationId: handoff?.operationId ?? crypto.randomUUID(),\n source: handoff ? 'voice' : 'command',\n target: { axis: 'major-mode', majorMode: picked },\n },\n async (_request, plan) => {\n const selected = [...plan.candidate.layers];\n const stale = plan.externalRelaunchRequired;\n const currentContextAtExecution = requireDoomConfigContext(cordis);\n const harness = currentContextAtExecution.harness;\n if (plan.strategy === 'process-relaunch') {\n const pendingSelection: DoomConfigPendingSelection = {\n version: 1,\n operationId: plan.operationId,\n active: selectionFromSnapshot(plan.previous),\n target: selectionFromSnapshot(plan.candidate),\n strategy: plan.strategy,\n phase: 'pending',\n };\n const existingPending = requireDoomConfigContext(cordis).pendingSelection;\n if (existingPending) supersedeDoomConfigTransition(pi, existingPending);\n appendDoomConfigTransition(pi, pendingSelection);\n replaceDoomConfigContext(\n cordis,\n bindPendingSelection(requireDoomConfigContext(cordis), pendingSelection),\n );\n void telemetry.recordEvent(MAJOR_MODE_EVENT.majorModeSwitched, {\n 'harness.major_mode.previous': harness.majorMode,\n 'harness.major_mode': picked,\n 'harness.layers': selected.join(','),\n 'harness.major_mode.stale': true,\n 'harness.transition.disposition': plan.disposition,\n 'harness.transition.operation_id': plan.operationId,\n });\n ctx.ui.setStatus(\n STATUS_KEY,\n colorStatus(ctx.ui.theme, harness.majorMode, harness.domains, harness.profile, true),\n );\n ctx.ui.notify(applySummary(picked, selected, true, composed, plan.strategy), 'info');\n return 'queued';\n }\n const [{ applyMajorMode }, { persistHarnessSelection }] = await Promise.all([\n dependencies.loadSelectionSwitch(),\n dependencies.loadConfigJournal(),\n ]);\n const transition: DoomConfigPendingSelection = {\n version: 1,\n operationId: plan.operationId,\n active: selectionFromSnapshot(plan.previous),\n target: selectionFromSnapshot(plan.candidate),\n strategy: plan.strategy ?? 'pi-reload',\n phase: 'pending',\n };\n const activeConfig = loadMajorModesConfig(harness.root ?? ctx.cwd);\n const snapshot = snapshotHarnessState();\n const existingPending = currentContextAtExecution.pendingSelection;\n minorModeReloadHandoff = prepareMinorModeReloadHandoff(\n cordis,\n ctx.sessionManager.getSessionId(),\n coordinator.hostGeneration,\n plan.operationId,\n plan.previous.minorModes,\n );\n try {\n if (existingPending) supersedeDoomConfigTransition(pi, existingPending);\n appendDoomConfigTransition(pi, transition);\n const updated = applyMajorMode(\n activeConfig,\n picked,\n harness,\n plan.candidate.compositionFingerprint,\n plan.candidate.childActivation,\n );\n persistHarnessSelection(pi, updated);\n } catch (error) {\n minorModeReloadHandoff?.discard();\n minorModeReloadHandoff = undefined;\n try {\n restoreHarnessStateSnapshot(snapshot);\n } catch (compensationError) {\n const detail =\n compensationError instanceof Error ? compensationError.message : String(compensationError);\n throw new Error(`Transition failed and compensation failed: ${detail}`, { cause: error });\n }\n appendDoomConfigTransition(pi, { ...transition, phase: 'aborted' });\n if (existingPending) appendDoomConfigTransition(pi, existingPending);\n throw error;\n }\n\n void telemetry.recordEvent(MAJOR_MODE_EVENT.majorModeSwitched, {\n 'harness.major_mode.previous': majorMode,\n 'harness.major_mode': picked,\n 'harness.layers': selected.join(','),\n 'harness.major_mode.stale': stale,\n 'harness.transition.disposition': plan.disposition,\n 'harness.transition.operation_id': plan.operationId,\n });\n ctx.ui.setStatus(STATUS_KEY, colorStatus(ctx.ui.theme, picked, domains, profile, false));\n ctx.ui.notify(applySummary(picked, selected, false, composed, plan.strategy), 'info');\n // Reload must be the command handler's terminal action. Pi invalidates\n // this command context as soon as the replacement session is active.\n reloadRequested = true;\n return 'applied';\n },\n );\n if (result.outcome === 'rejected' || result.outcome === 'stale') throw transitionError(result);\n } catch (error) {\n reloadRequested = false;\n minorModeReloadHandoff?.discard();\n minorModeReloadHandoff = undefined;\n if (!token) throw error;\n if (sessionIdentity) dependencies.reloadHandoffs.discard(token, sessionIdentity);\n ctx.ui.notify(errorMessage(error), 'error');\n }\n\n if (reloadRequested) {\n try {\n await ctx.waitForIdle();\n // The replacement session acknowledges the pending journal after its\n // bootstrap harness is active. Do not touch ctx after this await.\n await ctx.reload();\n } catch (error) {\n minorModeReloadHandoff?.discard();\n throw error;\n }\n }\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"majorModeCommand.cjs","names":["MAJOR_MODE_COMMAND","voiceSwitchToken","errorMessage","readDoomVoiceToolsService","majorModeItems","optionName","majorModeOptionLabel","alreadyComposed","majorModeSummary","requireDoomTransitionCoordinator","requireDoomConfigContext","selectionFromSnapshot","bindPendingSelection","MAJOR_MODE_EVENT","STATUS_KEY","colorStatus","applySummary","loadMajorModesConfig","snapshotHarnessState","prepareMinorModeReloadHandoff"],"sources":["../../src/commands/majorModeCommand.ts"],"sourcesContent":["import { restoreHarnessStateSnapshot, snapshotHarnessState } from '@agimon-ai/doompi-config/harnessStore';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport {\n appendDoomConfigTransition,\n replaceDoomConfigContext,\n requireDoomConfigContext,\n supersedeDoomConfigTransition,\n} from '@agimon-ai/doompi-config/piContext';\nimport type { DoomConfigPendingSelection } from '@agimon-ai/doompi-config/types';\nimport { alreadyComposed } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n type DoomTransitionResult,\n type MinorModeReloadHandoffHandle,\n prepareMinorModeReloadHandoff,\n requireDoomTransitionCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/transition';\nimport { readDoomVoiceToolsService } from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport type {\n VoiceReloadHandoff,\n VoiceReloadHandoffIdentity,\n VoiceReloadHandoffStore,\n} from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport {\n applySummary,\n errorMessage,\n MAJOR_MODE_COMMAND,\n majorModeItems,\n majorModeOptionLabel,\n majorModeSummary,\n optionName,\n voiceSwitchToken,\n} from '../services/majorModeText.ts';\nimport { colorStatus, STATUS_KEY } from '../services/statusLine.ts';\nimport { MAJOR_MODE_EVENT, type MajorModeTelemetry } from '../types/telemetry.ts';\nimport { bindPendingSelection, clearPendingSelection, selectionFromSnapshot } from '../services/pendingSelection.ts';\nimport { MAJOR_MODE_SWITCH_HANDOFF_KIND, type MajorModeView } from '../types/majorMode.ts';\n\nfunction transitionError(result: DoomTransitionResult): Error {\n return new Error(`Major-mode transition was ${result.outcome}: ${result.diagnostics.join(', ')}`);\n}\n\nexport interface MajorModeCommandDependencies {\n readonly cordisContext: () => Context;\n readonly currentView: (ctx: ExtensionContext) => Promise<MajorModeView>;\n readonly reloadHandoffs: VoiceReloadHandoffStore;\n readonly loadPicker: () => Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')>;\n readonly loadSelectionSwitch: () => Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')>;\n readonly loadConfigJournal: () => Promise<typeof import('@agimon-ai/doompi-config/piContext')>;\n readonly resolveLayers: (config: MajorModeView['config'], majorMode: string) => string[];\n /** Whether an owning process supervisor listens for relaunch requests. */\n readonly supervisedRelaunchAvailable: () => boolean;\n /** Asks the supervisor to relaunch with the picked mode; call only at idle. */\n readonly requestSupervisedRelaunch: (majorMode: string, operationId: string) => boolean;\n}\n\nexport function registerMajorModeCommand(\n pi: ExtensionAPI,\n telemetry: MajorModeTelemetry,\n dependencies: MajorModeCommandDependencies,\n): void {\n pi.registerCommand(MAJOR_MODE_COMMAND, {\n description: 'Pick a named major mode',\n handler: async (args, ctx) => {\n let token: string | undefined;\n try {\n token = voiceSwitchToken(args);\n } catch (error) {\n ctx.ui.notify(errorMessage(error), 'error');\n return;\n }\n if (args.trim() && !token) {\n ctx.ui.notify(`Usage: /${MAJOR_MODE_COMMAND}`, 'warning');\n return;\n }\n\n const sessionId = ctx.sessionManager.getSessionId();\n const cordis = dependencies.cordisContext();\n let sessionIdentity: VoiceReloadHandoffIdentity | undefined;\n let handoff: VoiceReloadHandoff | undefined;\n let minorModeReloadHandoff: MinorModeReloadHandoffHandle | undefined;\n let reloadRequested = false;\n let performRelaunch: (() => boolean) | undefined;\n try {\n const { config, majorMode, domains, profile } = await dependencies.currentView(ctx);\n const names = Object.keys(config.majorMode);\n let picked: string | undefined;\n\n if (token) {\n const voiceSession = readDoomVoiceToolsService(cordis)?.readSession(sessionId);\n if (voiceSession) sessionIdentity = { sessionId, hostGeneration: voiceSession.hostGeneration };\n handoff = sessionIdentity ? dependencies.reloadHandoffs.accept(token, sessionIdentity) : undefined;\n if (handoff?.kind !== MAJOR_MODE_SWITCH_HANDOFF_KIND || !handoff.majorMode) {\n throw new Error('The voice major-mode switch token is stale or belongs to another session.');\n }\n picked = handoff.majorMode;\n } else {\n const title = `Major mode (current: ${majorMode})`;\n const chosen =\n ctx.mode === 'tui'\n ? await dependencies\n .loadPicker()\n .then(({ MatrixPickerComponent }) =>\n ctx.ui.custom<string[] | undefined>(\n (_tui, theme, keybindings, done) =>\n new MatrixPickerComponent(\n { title, items: majorModeItems(config, names), selected: [majorMode], multi: false },\n theme,\n keybindings,\n done,\n ),\n ),\n )\n : [\n optionName(\n (await ctx.ui.select(\n title,\n names.map((name) => majorModeOptionLabel(name, config.majorMode[name]?.layers ?? [], majorMode)),\n )) ?? '',\n ),\n ];\n picked = chosen?.[0];\n }\n if (!picked) return;\n\n const composed = alreadyComposed();\n if (picked === majorMode) {\n if (token && sessionIdentity) dependencies.reloadHandoffs.discard(token, sessionIdentity);\n clearPendingSelection(pi, dependencies.cordisContext());\n ctx.ui.notify(\n majorModeSummary(picked, dependencies.resolveLayers(config, picked), majorMode, composed),\n 'info',\n );\n return;\n }\n\n const coordinator = requireDoomTransitionCoordinator(cordis);\n const result = await coordinator.execute(\n {\n sessionId: ctx.sessionManager.getSessionId(),\n hostGeneration: coordinator.hostGeneration,\n operationId: handoff?.operationId ?? crypto.randomUUID(),\n source: handoff ? 'voice' : 'command',\n target: { axis: 'major-mode', majorMode: picked },\n },\n async (_request, plan) => {\n const selected = [...plan.candidate.layers];\n const stale = plan.externalRelaunchRequired;\n const currentContextAtExecution = requireDoomConfigContext(cordis);\n const harness = currentContextAtExecution.harness;\n if (plan.strategy === 'process-relaunch') {\n const pendingSelection: DoomConfigPendingSelection = {\n version: 1,\n operationId: plan.operationId,\n active: selectionFromSnapshot(plan.previous),\n target: selectionFromSnapshot(plan.candidate),\n strategy: plan.strategy,\n phase: 'pending',\n };\n const existingPending = requireDoomConfigContext(cordis).pendingSelection;\n if (existingPending) supersedeDoomConfigTransition(pi, existingPending);\n appendDoomConfigTransition(pi, pendingSelection);\n replaceDoomConfigContext(\n cordis,\n bindPendingSelection(requireDoomConfigContext(cordis), pendingSelection),\n );\n void telemetry.recordEvent(MAJOR_MODE_EVENT.majorModeSwitched, {\n 'harness.major_mode.previous': harness.majorMode,\n 'harness.major_mode': picked,\n 'harness.layers': selected.join(','),\n 'harness.major_mode.stale': true,\n 'harness.transition.disposition': plan.disposition,\n 'harness.transition.operation_id': plan.operationId,\n });\n ctx.ui.setStatus(\n STATUS_KEY,\n colorStatus(ctx.ui.theme, harness.majorMode, harness.domains, harness.profile, true),\n );\n if (dependencies.supervisedRelaunchAvailable()) {\n const target = picked;\n performRelaunch = () => dependencies.requestSupervisedRelaunch(target, plan.operationId);\n }\n ctx.ui.notify(\n applySummary(picked, selected, true, composed, plan.strategy, performRelaunch !== undefined),\n 'info',\n );\n return 'queued';\n }\n const [{ applyMajorMode }, { persistHarnessSelection }] = await Promise.all([\n dependencies.loadSelectionSwitch(),\n dependencies.loadConfigJournal(),\n ]);\n const transition: DoomConfigPendingSelection = {\n version: 1,\n operationId: plan.operationId,\n active: selectionFromSnapshot(plan.previous),\n target: selectionFromSnapshot(plan.candidate),\n strategy: plan.strategy ?? 'pi-reload',\n phase: 'pending',\n };\n const activeConfig = loadMajorModesConfig(harness.root ?? ctx.cwd);\n const snapshot = snapshotHarnessState();\n const existingPending = currentContextAtExecution.pendingSelection;\n minorModeReloadHandoff = prepareMinorModeReloadHandoff(\n cordis,\n ctx.sessionManager.getSessionId(),\n coordinator.hostGeneration,\n plan.operationId,\n plan.previous.minorModes,\n );\n try {\n if (existingPending) supersedeDoomConfigTransition(pi, existingPending);\n appendDoomConfigTransition(pi, transition);\n const updated = applyMajorMode(\n activeConfig,\n picked,\n harness,\n plan.candidate.compositionFingerprint,\n plan.candidate.childActivation,\n );\n persistHarnessSelection(pi, updated);\n } catch (error) {\n minorModeReloadHandoff?.discard();\n minorModeReloadHandoff = undefined;\n try {\n restoreHarnessStateSnapshot(snapshot);\n } catch (compensationError) {\n const detail =\n compensationError instanceof Error ? compensationError.message : String(compensationError);\n throw new Error(`Transition failed and compensation failed: ${detail}`, { cause: error });\n }\n appendDoomConfigTransition(pi, { ...transition, phase: 'aborted' });\n if (existingPending) appendDoomConfigTransition(pi, existingPending);\n throw error;\n }\n\n void telemetry.recordEvent(MAJOR_MODE_EVENT.majorModeSwitched, {\n 'harness.major_mode.previous': majorMode,\n 'harness.major_mode': picked,\n 'harness.layers': selected.join(','),\n 'harness.major_mode.stale': stale,\n 'harness.transition.disposition': plan.disposition,\n 'harness.transition.operation_id': plan.operationId,\n });\n ctx.ui.setStatus(STATUS_KEY, colorStatus(ctx.ui.theme, picked, domains, profile, false));\n ctx.ui.notify(applySummary(picked, selected, false, composed, plan.strategy), 'info');\n // Reload must be the command handler's terminal action. Pi invalidates\n // this command context as soon as the replacement session is active.\n reloadRequested = true;\n return 'applied';\n },\n );\n if (result.outcome === 'rejected' || result.outcome === 'stale') throw transitionError(result);\n } catch (error) {\n reloadRequested = false;\n performRelaunch = undefined;\n minorModeReloadHandoff?.discard();\n minorModeReloadHandoff = undefined;\n if (!token) throw error;\n if (sessionIdentity) dependencies.reloadHandoffs.discard(token, sessionIdentity);\n ctx.ui.notify(errorMessage(error), 'error');\n }\n\n if (reloadRequested) {\n try {\n await ctx.waitForIdle();\n // The replacement session acknowledges the pending journal after its\n // bootstrap harness is active. Do not touch ctx after this await.\n await ctx.reload();\n } catch (error) {\n minorModeReloadHandoff?.discard();\n throw error;\n }\n }\n if (performRelaunch) {\n // Written only at idle: the supervisor ends this agent's input for a\n // graceful exit the moment the file appears, then respawns it with the\n // pending major mode; the replacement resumes the session and\n // acknowledges the journal.\n await ctx.waitForIdle();\n if (performRelaunch()) ctx.shutdown();\n }\n },\n });\n}\n"],"mappings":"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"}
@@ -12,6 +12,10 @@ interface MajorModeCommandDependencies {
12
12
  readonly loadSelectionSwitch: () => Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')>;
13
13
  readonly loadConfigJournal: () => Promise<typeof import('@agimon-ai/doompi-config/piContext')>;
14
14
  readonly resolveLayers: (config: MajorModeView['config'], majorMode: string) => string[];
15
+ /** Whether an owning process supervisor listens for relaunch requests. */
16
+ readonly supervisedRelaunchAvailable: () => boolean;
17
+ /** Asks the supervisor to relaunch with the picked mode; call only at idle. */
18
+ readonly requestSupervisedRelaunch: (majorMode: string, operationId: string) => boolean;
15
19
  }
16
20
  declare function registerMajorModeCommand(pi: ExtensionAPI, telemetry: MajorModeTelemetry, dependencies: MajorModeCommandDependencies): void;
17
21
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"majorModeCommand.d.cts","names":[],"sources":["../../src/commands/majorModeCommand.ts"],"mappings":";;;;;;UA2CiB;WACN,qBAAqB;WACrB,cAAc,KAAK,qBAAqB,QAAQ;WAChD,gBAAgB;WAChB,kBAAkB;WAClB,2BAA2B;WAC3B,yBAAyB;WACzB,gBAAgB,QAAQ,yBAAyB;;iBAG5C,yBACd,IAAI,cACJ,WAAW,oBACX,cAAc"}
1
+ {"version":3,"file":"majorModeCommand.d.cts","names":[],"sources":["../../src/commands/majorModeCommand.ts"],"mappings":";;;;;;UA2CiB;WACN,qBAAqB;WACrB,cAAc,KAAK,qBAAqB,QAAQ;WAChD,gBAAgB;WAChB,kBAAkB;WAClB,2BAA2B;WAC3B,yBAAyB;WACzB,gBAAgB,QAAQ,yBAAyB;;WAEjD;;WAEA,4BAA4B,mBAAmB;;iBAG1C,yBACd,IAAI,cACJ,WAAW,oBACX,cAAc"}
@@ -12,6 +12,10 @@ interface MajorModeCommandDependencies {
12
12
  readonly loadSelectionSwitch: () => Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')>;
13
13
  readonly loadConfigJournal: () => Promise<typeof import('@agimon-ai/doompi-config/piContext')>;
14
14
  readonly resolveLayers: (config: MajorModeView['config'], majorMode: string) => string[];
15
+ /** Whether an owning process supervisor listens for relaunch requests. */
16
+ readonly supervisedRelaunchAvailable: () => boolean;
17
+ /** Asks the supervisor to relaunch with the picked mode; call only at idle. */
18
+ readonly requestSupervisedRelaunch: (majorMode: string, operationId: string) => boolean;
15
19
  }
16
20
  declare function registerMajorModeCommand(pi: ExtensionAPI, telemetry: MajorModeTelemetry, dependencies: MajorModeCommandDependencies): void;
17
21
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"majorModeCommand.d.mts","names":[],"sources":["../../src/commands/majorModeCommand.ts"],"mappings":";;;;;;UA2CiB;WACN,qBAAqB;WACrB,cAAc,KAAK,qBAAqB,QAAQ;WAChD,gBAAgB;WAChB,kBAAkB;WAClB,2BAA2B;WAC3B,yBAAyB;WACzB,gBAAgB,QAAQ,yBAAyB;;iBAG5C,yBACd,IAAI,cACJ,WAAW,oBACX,cAAc"}
1
+ {"version":3,"file":"majorModeCommand.d.mts","names":[],"sources":["../../src/commands/majorModeCommand.ts"],"mappings":";;;;;;UA2CiB;WACN,qBAAqB;WACrB,cAAc,KAAK,qBAAqB,QAAQ;WAChD,gBAAgB;WAChB,kBAAkB;WAClB,2BAA2B;WAC3B,yBAAyB;WACzB,gBAAgB,QAAQ,yBAAyB;;WAEjD;;WAEA,4BAA4B,mBAAmB;;iBAG1C,yBACd,IAAI,cACJ,WAAW,oBACX,cAAc"}
@@ -1,2 +1,2 @@
1
- import{MAJOR_MODE_COMMAND as e,applySummary as t,errorMessage as n,majorModeItems as r,majorModeOptionLabel as i,majorModeSummary as a,optionName as o,voiceSwitchToken as s}from"../services/majorModeText.mjs";import{STATUS_KEY as c,colorStatus as l}from"../services/statusLine.mjs";import{MAJOR_MODE_EVENT as u}from"../types/telemetry.mjs";import{bindPendingSelection as d,clearPendingSelection as f,selectionFromSnapshot as p}from"../services/pendingSelection.mjs";import"../types/majorMode.mjs";import{restoreHarnessStateSnapshot as m,snapshotHarnessState as h}from"@agimon-ai/doompi-config/harnessStore";import{appendDoomConfigTransition as g,replaceDoomConfigContext as _,requireDoomConfigContext as v,supersedeDoomConfigTransition as y}from"@agimon-ai/doompi-config/piContext";import{prepareMinorModeReloadHandoff as b,requireDoomTransitionCoordinator as x}from"@agimon-ai/doompi-extension-contracts/transition";import{readDoomVoiceToolsService as S}from"@agimon-ai/doompi-extension-contracts/voice-tools";import{loadMajorModesConfig as C}from"@agimon-ai/doompi-config/majorModes";import{alreadyComposed as w}from"@agimon-ai/doompi-extension-contracts/child-process";function T(e){return Error(`Major-mode transition was ${e.outcome}: ${e.diagnostics.join(`, `)}`)}function E(E,D,O){E.registerCommand(e,{description:`Pick a named major mode`,handler:async(k,A)=>{let j;try{j=s(k)}catch(e){A.ui.notify(n(e),`error`);return}if(k.trim()&&!j){A.ui.notify(`Usage: /${e}`,`warning`);return}let M=A.sessionManager.getSessionId(),N=O.cordisContext(),P,F,I,L=!1;try{let{config:e,majorMode:n,domains:s,profile:k}=await O.currentView(A),R=Object.keys(e.majorMode),z;if(j){let e=S(N)?.readSession(M);if(e&&(P={sessionId:M,hostGeneration:e.hostGeneration}),F=P?O.reloadHandoffs.accept(j,P):void 0,F?.kind!==`major-mode-switch`||!F.majorMode)throw Error(`The voice major-mode switch token is stale or belongs to another session.`);z=F.majorMode}else{let t=`Major mode (current: ${n})`;z=(A.mode===`tui`?await O.loadPicker().then(({MatrixPickerComponent:i})=>A.ui.custom((a,o,s,c)=>new i({title:t,items:r(e,R),selected:[n],multi:!1},o,s,c))):[o(await A.ui.select(t,R.map(t=>i(t,e.majorMode[t]?.layers??[],n)))??``)])?.[0]}if(!z)return;let B=w();if(z===n){j&&P&&O.reloadHandoffs.discard(j,P),f(E,O.cordisContext()),A.ui.notify(a(z,O.resolveLayers(e,z),n,B),`info`);return}let V=x(N),H=await V.execute({sessionId:A.sessionManager.getSessionId(),hostGeneration:V.hostGeneration,operationId:F?.operationId??crypto.randomUUID(),source:F?`voice`:`command`,target:{axis:`major-mode`,majorMode:z}},async(e,r)=>{let i=[...r.candidate.layers],a=r.externalRelaunchRequired,o=v(N),f=o.harness;if(r.strategy===`process-relaunch`){let e={version:1,operationId:r.operationId,active:p(r.previous),target:p(r.candidate),strategy:r.strategy,phase:`pending`},n=v(N).pendingSelection;return n&&y(E,n),g(E,e),_(N,d(v(N),e)),D.recordEvent(u.majorModeSwitched,{"harness.major_mode.previous":f.majorMode,"harness.major_mode":z,"harness.layers":i.join(`,`),"harness.major_mode.stale":!0,"harness.transition.disposition":r.disposition,"harness.transition.operation_id":r.operationId}),A.ui.setStatus(c,l(A.ui.theme,f.majorMode,f.domains,f.profile,!0)),A.ui.notify(t(z,i,!0,B,r.strategy),`info`),`queued`}let[{applyMajorMode:x},{persistHarnessSelection:S}]=await Promise.all([O.loadSelectionSwitch(),O.loadConfigJournal()]),w={version:1,operationId:r.operationId,active:p(r.previous),target:p(r.candidate),strategy:r.strategy??`pi-reload`,phase:`pending`},T=C(f.root??A.cwd),j=h(),M=o.pendingSelection;I=b(N,A.sessionManager.getSessionId(),V.hostGeneration,r.operationId,r.previous.minorModes);try{M&&y(E,M),g(E,w),S(E,x(T,z,f,r.candidate.compositionFingerprint,r.candidate.childActivation))}catch(e){I?.discard(),I=void 0;try{m(j)}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`Transition failed and compensation failed: ${n}`,{cause:e})}throw g(E,{...w,phase:`aborted`}),M&&g(E,M),e}return D.recordEvent(u.majorModeSwitched,{"harness.major_mode.previous":n,"harness.major_mode":z,"harness.layers":i.join(`,`),"harness.major_mode.stale":a,"harness.transition.disposition":r.disposition,"harness.transition.operation_id":r.operationId}),A.ui.setStatus(c,l(A.ui.theme,z,s,k,!1)),A.ui.notify(t(z,i,!1,B,r.strategy),`info`),L=!0,`applied`});if(H.outcome===`rejected`||H.outcome===`stale`)throw T(H)}catch(e){if(L=!1,I?.discard(),I=void 0,!j)throw e;P&&O.reloadHandoffs.discard(j,P),A.ui.notify(n(e),`error`)}if(L)try{await A.waitForIdle(),await A.reload()}catch(e){throw I?.discard(),e}}})}export{E as registerMajorModeCommand};
1
+ import{MAJOR_MODE_COMMAND as e,applySummary as t,errorMessage as n,majorModeItems as r,majorModeOptionLabel as i,majorModeSummary as a,optionName as o,voiceSwitchToken as s}from"../services/majorModeText.mjs";import{STATUS_KEY as c,colorStatus as l}from"../services/statusLine.mjs";import{MAJOR_MODE_EVENT as u}from"../types/telemetry.mjs";import{bindPendingSelection as d,clearPendingSelection as f,selectionFromSnapshot as p}from"../services/pendingSelection.mjs";import"../types/majorMode.mjs";import{restoreHarnessStateSnapshot as m,snapshotHarnessState as h}from"@agimon-ai/doompi-config/harnessStore";import{appendDoomConfigTransition as g,replaceDoomConfigContext as _,requireDoomConfigContext as v,supersedeDoomConfigTransition as y}from"@agimon-ai/doompi-config/piContext";import{prepareMinorModeReloadHandoff as b,requireDoomTransitionCoordinator as x}from"@agimon-ai/doompi-extension-contracts/transition";import{readDoomVoiceToolsService as S}from"@agimon-ai/doompi-extension-contracts/voice-tools";import{loadMajorModesConfig as C}from"@agimon-ai/doompi-config/majorModes";import{alreadyComposed as w}from"@agimon-ai/doompi-extension-contracts/child-process";function T(e){return Error(`Major-mode transition was ${e.outcome}: ${e.diagnostics.join(`, `)}`)}function E(E,D,O){E.registerCommand(e,{description:`Pick a named major mode`,handler:async(k,A)=>{let j;try{j=s(k)}catch(e){A.ui.notify(n(e),`error`);return}if(k.trim()&&!j){A.ui.notify(`Usage: /${e}`,`warning`);return}let M=A.sessionManager.getSessionId(),N=O.cordisContext(),P,F,I,L=!1,R;try{let{config:e,majorMode:n,domains:s,profile:k}=await O.currentView(A),z=Object.keys(e.majorMode),B;if(j){let e=S(N)?.readSession(M);if(e&&(P={sessionId:M,hostGeneration:e.hostGeneration}),F=P?O.reloadHandoffs.accept(j,P):void 0,F?.kind!==`major-mode-switch`||!F.majorMode)throw Error(`The voice major-mode switch token is stale or belongs to another session.`);B=F.majorMode}else{let t=`Major mode (current: ${n})`;B=(A.mode===`tui`?await O.loadPicker().then(({MatrixPickerComponent:i})=>A.ui.custom((a,o,s,c)=>new i({title:t,items:r(e,z),selected:[n],multi:!1},o,s,c))):[o(await A.ui.select(t,z.map(t=>i(t,e.majorMode[t]?.layers??[],n)))??``)])?.[0]}if(!B)return;let V=w();if(B===n){j&&P&&O.reloadHandoffs.discard(j,P),f(E,O.cordisContext()),A.ui.notify(a(B,O.resolveLayers(e,B),n,V),`info`);return}let H=x(N),U=await H.execute({sessionId:A.sessionManager.getSessionId(),hostGeneration:H.hostGeneration,operationId:F?.operationId??crypto.randomUUID(),source:F?`voice`:`command`,target:{axis:`major-mode`,majorMode:B}},async(e,r)=>{let i=[...r.candidate.layers],a=r.externalRelaunchRequired,o=v(N),f=o.harness;if(r.strategy===`process-relaunch`){let e={version:1,operationId:r.operationId,active:p(r.previous),target:p(r.candidate),strategy:r.strategy,phase:`pending`},n=v(N).pendingSelection;if(n&&y(E,n),g(E,e),_(N,d(v(N),e)),D.recordEvent(u.majorModeSwitched,{"harness.major_mode.previous":f.majorMode,"harness.major_mode":B,"harness.layers":i.join(`,`),"harness.major_mode.stale":!0,"harness.transition.disposition":r.disposition,"harness.transition.operation_id":r.operationId}),A.ui.setStatus(c,l(A.ui.theme,f.majorMode,f.domains,f.profile,!0)),O.supervisedRelaunchAvailable()){let e=B;R=()=>O.requestSupervisedRelaunch(e,r.operationId)}return A.ui.notify(t(B,i,!0,V,r.strategy,R!==void 0),`info`),`queued`}let[{applyMajorMode:x},{persistHarnessSelection:S}]=await Promise.all([O.loadSelectionSwitch(),O.loadConfigJournal()]),w={version:1,operationId:r.operationId,active:p(r.previous),target:p(r.candidate),strategy:r.strategy??`pi-reload`,phase:`pending`},T=C(f.root??A.cwd),j=h(),M=o.pendingSelection;I=b(N,A.sessionManager.getSessionId(),H.hostGeneration,r.operationId,r.previous.minorModes);try{M&&y(E,M),g(E,w),S(E,x(T,B,f,r.candidate.compositionFingerprint,r.candidate.childActivation))}catch(e){I?.discard(),I=void 0;try{m(j)}catch(t){let n=t instanceof Error?t.message:String(t);throw Error(`Transition failed and compensation failed: ${n}`,{cause:e})}throw g(E,{...w,phase:`aborted`}),M&&g(E,M),e}return D.recordEvent(u.majorModeSwitched,{"harness.major_mode.previous":n,"harness.major_mode":B,"harness.layers":i.join(`,`),"harness.major_mode.stale":a,"harness.transition.disposition":r.disposition,"harness.transition.operation_id":r.operationId}),A.ui.setStatus(c,l(A.ui.theme,B,s,k,!1)),A.ui.notify(t(B,i,!1,V,r.strategy),`info`),L=!0,`applied`});if(U.outcome===`rejected`||U.outcome===`stale`)throw T(U)}catch(e){if(L=!1,R=void 0,I?.discard(),I=void 0,!j)throw e;P&&O.reloadHandoffs.discard(j,P),A.ui.notify(n(e),`error`)}if(L)try{await A.waitForIdle(),await A.reload()}catch(e){throw I?.discard(),e}R&&(await A.waitForIdle(),R()&&A.shutdown())}})}export{E as registerMajorModeCommand};
2
2
  //# sourceMappingURL=majorModeCommand.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"majorModeCommand.mjs","names":[],"sources":["../../src/commands/majorModeCommand.ts"],"sourcesContent":["import { restoreHarnessStateSnapshot, snapshotHarnessState } from '@agimon-ai/doompi-config/harnessStore';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport {\n appendDoomConfigTransition,\n replaceDoomConfigContext,\n requireDoomConfigContext,\n supersedeDoomConfigTransition,\n} from '@agimon-ai/doompi-config/piContext';\nimport type { DoomConfigPendingSelection } from '@agimon-ai/doompi-config/types';\nimport { alreadyComposed } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n type DoomTransitionResult,\n type MinorModeReloadHandoffHandle,\n prepareMinorModeReloadHandoff,\n requireDoomTransitionCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/transition';\nimport { readDoomVoiceToolsService } from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport type {\n VoiceReloadHandoff,\n VoiceReloadHandoffIdentity,\n VoiceReloadHandoffStore,\n} from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport {\n applySummary,\n errorMessage,\n MAJOR_MODE_COMMAND,\n majorModeItems,\n majorModeOptionLabel,\n majorModeSummary,\n optionName,\n voiceSwitchToken,\n} from '../services/majorModeText.ts';\nimport { colorStatus, STATUS_KEY } from '../services/statusLine.ts';\nimport { MAJOR_MODE_EVENT, type MajorModeTelemetry } from '../types/telemetry.ts';\nimport { bindPendingSelection, clearPendingSelection, selectionFromSnapshot } from '../services/pendingSelection.ts';\nimport { MAJOR_MODE_SWITCH_HANDOFF_KIND, type MajorModeView } from '../types/majorMode.ts';\n\nfunction transitionError(result: DoomTransitionResult): Error {\n return new Error(`Major-mode transition was ${result.outcome}: ${result.diagnostics.join(', ')}`);\n}\n\nexport interface MajorModeCommandDependencies {\n readonly cordisContext: () => Context;\n readonly currentView: (ctx: ExtensionContext) => Promise<MajorModeView>;\n readonly reloadHandoffs: VoiceReloadHandoffStore;\n readonly loadPicker: () => Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')>;\n readonly loadSelectionSwitch: () => Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')>;\n readonly loadConfigJournal: () => Promise<typeof import('@agimon-ai/doompi-config/piContext')>;\n readonly resolveLayers: (config: MajorModeView['config'], majorMode: string) => string[];\n}\n\nexport function registerMajorModeCommand(\n pi: ExtensionAPI,\n telemetry: MajorModeTelemetry,\n dependencies: MajorModeCommandDependencies,\n): void {\n pi.registerCommand(MAJOR_MODE_COMMAND, {\n description: 'Pick a named major mode',\n handler: async (args, ctx) => {\n let token: string | undefined;\n try {\n token = voiceSwitchToken(args);\n } catch (error) {\n ctx.ui.notify(errorMessage(error), 'error');\n return;\n }\n if (args.trim() && !token) {\n ctx.ui.notify(`Usage: /${MAJOR_MODE_COMMAND}`, 'warning');\n return;\n }\n\n const sessionId = ctx.sessionManager.getSessionId();\n const cordis = dependencies.cordisContext();\n let sessionIdentity: VoiceReloadHandoffIdentity | undefined;\n let handoff: VoiceReloadHandoff | undefined;\n let minorModeReloadHandoff: MinorModeReloadHandoffHandle | undefined;\n let reloadRequested = false;\n try {\n const { config, majorMode, domains, profile } = await dependencies.currentView(ctx);\n const names = Object.keys(config.majorMode);\n let picked: string | undefined;\n\n if (token) {\n const voiceSession = readDoomVoiceToolsService(cordis)?.readSession(sessionId);\n if (voiceSession) sessionIdentity = { sessionId, hostGeneration: voiceSession.hostGeneration };\n handoff = sessionIdentity ? dependencies.reloadHandoffs.accept(token, sessionIdentity) : undefined;\n if (handoff?.kind !== MAJOR_MODE_SWITCH_HANDOFF_KIND || !handoff.majorMode) {\n throw new Error('The voice major-mode switch token is stale or belongs to another session.');\n }\n picked = handoff.majorMode;\n } else {\n const title = `Major mode (current: ${majorMode})`;\n const chosen =\n ctx.mode === 'tui'\n ? await dependencies\n .loadPicker()\n .then(({ MatrixPickerComponent }) =>\n ctx.ui.custom<string[] | undefined>(\n (_tui, theme, keybindings, done) =>\n new MatrixPickerComponent(\n { title, items: majorModeItems(config, names), selected: [majorMode], multi: false },\n theme,\n keybindings,\n done,\n ),\n ),\n )\n : [\n optionName(\n (await ctx.ui.select(\n title,\n names.map((name) => majorModeOptionLabel(name, config.majorMode[name]?.layers ?? [], majorMode)),\n )) ?? '',\n ),\n ];\n picked = chosen?.[0];\n }\n if (!picked) return;\n\n const composed = alreadyComposed();\n if (picked === majorMode) {\n if (token && sessionIdentity) dependencies.reloadHandoffs.discard(token, sessionIdentity);\n clearPendingSelection(pi, dependencies.cordisContext());\n ctx.ui.notify(\n majorModeSummary(picked, dependencies.resolveLayers(config, picked), majorMode, composed),\n 'info',\n );\n return;\n }\n\n const coordinator = requireDoomTransitionCoordinator(cordis);\n const result = await coordinator.execute(\n {\n sessionId: ctx.sessionManager.getSessionId(),\n hostGeneration: coordinator.hostGeneration,\n operationId: handoff?.operationId ?? crypto.randomUUID(),\n source: handoff ? 'voice' : 'command',\n target: { axis: 'major-mode', majorMode: picked },\n },\n async (_request, plan) => {\n const selected = [...plan.candidate.layers];\n const stale = plan.externalRelaunchRequired;\n const currentContextAtExecution = requireDoomConfigContext(cordis);\n const harness = currentContextAtExecution.harness;\n if (plan.strategy === 'process-relaunch') {\n const pendingSelection: DoomConfigPendingSelection = {\n version: 1,\n operationId: plan.operationId,\n active: selectionFromSnapshot(plan.previous),\n target: selectionFromSnapshot(plan.candidate),\n strategy: plan.strategy,\n phase: 'pending',\n };\n const existingPending = requireDoomConfigContext(cordis).pendingSelection;\n if (existingPending) supersedeDoomConfigTransition(pi, existingPending);\n appendDoomConfigTransition(pi, pendingSelection);\n replaceDoomConfigContext(\n cordis,\n bindPendingSelection(requireDoomConfigContext(cordis), pendingSelection),\n );\n void telemetry.recordEvent(MAJOR_MODE_EVENT.majorModeSwitched, {\n 'harness.major_mode.previous': harness.majorMode,\n 'harness.major_mode': picked,\n 'harness.layers': selected.join(','),\n 'harness.major_mode.stale': true,\n 'harness.transition.disposition': plan.disposition,\n 'harness.transition.operation_id': plan.operationId,\n });\n ctx.ui.setStatus(\n STATUS_KEY,\n colorStatus(ctx.ui.theme, harness.majorMode, harness.domains, harness.profile, true),\n );\n ctx.ui.notify(applySummary(picked, selected, true, composed, plan.strategy), 'info');\n return 'queued';\n }\n const [{ applyMajorMode }, { persistHarnessSelection }] = await Promise.all([\n dependencies.loadSelectionSwitch(),\n dependencies.loadConfigJournal(),\n ]);\n const transition: DoomConfigPendingSelection = {\n version: 1,\n operationId: plan.operationId,\n active: selectionFromSnapshot(plan.previous),\n target: selectionFromSnapshot(plan.candidate),\n strategy: plan.strategy ?? 'pi-reload',\n phase: 'pending',\n };\n const activeConfig = loadMajorModesConfig(harness.root ?? ctx.cwd);\n const snapshot = snapshotHarnessState();\n const existingPending = currentContextAtExecution.pendingSelection;\n minorModeReloadHandoff = prepareMinorModeReloadHandoff(\n cordis,\n ctx.sessionManager.getSessionId(),\n coordinator.hostGeneration,\n plan.operationId,\n plan.previous.minorModes,\n );\n try {\n if (existingPending) supersedeDoomConfigTransition(pi, existingPending);\n appendDoomConfigTransition(pi, transition);\n const updated = applyMajorMode(\n activeConfig,\n picked,\n harness,\n plan.candidate.compositionFingerprint,\n plan.candidate.childActivation,\n );\n persistHarnessSelection(pi, updated);\n } catch (error) {\n minorModeReloadHandoff?.discard();\n minorModeReloadHandoff = undefined;\n try {\n restoreHarnessStateSnapshot(snapshot);\n } catch (compensationError) {\n const detail =\n compensationError instanceof Error ? compensationError.message : String(compensationError);\n throw new Error(`Transition failed and compensation failed: ${detail}`, { cause: error });\n }\n appendDoomConfigTransition(pi, { ...transition, phase: 'aborted' });\n if (existingPending) appendDoomConfigTransition(pi, existingPending);\n throw error;\n }\n\n void telemetry.recordEvent(MAJOR_MODE_EVENT.majorModeSwitched, {\n 'harness.major_mode.previous': majorMode,\n 'harness.major_mode': picked,\n 'harness.layers': selected.join(','),\n 'harness.major_mode.stale': stale,\n 'harness.transition.disposition': plan.disposition,\n 'harness.transition.operation_id': plan.operationId,\n });\n ctx.ui.setStatus(STATUS_KEY, colorStatus(ctx.ui.theme, picked, domains, profile, false));\n ctx.ui.notify(applySummary(picked, selected, false, composed, plan.strategy), 'info');\n // Reload must be the command handler's terminal action. Pi invalidates\n // this command context as soon as the replacement session is active.\n reloadRequested = true;\n return 'applied';\n },\n );\n if (result.outcome === 'rejected' || result.outcome === 'stale') throw transitionError(result);\n } catch (error) {\n reloadRequested = false;\n minorModeReloadHandoff?.discard();\n minorModeReloadHandoff = undefined;\n if (!token) throw error;\n if (sessionIdentity) dependencies.reloadHandoffs.discard(token, sessionIdentity);\n ctx.ui.notify(errorMessage(error), 'error');\n }\n\n if (reloadRequested) {\n try {\n await ctx.waitForIdle();\n // The replacement session acknowledges the pending journal after its\n // bootstrap harness is active. Do not touch ctx after this await.\n await ctx.reload();\n } catch (error) {\n minorModeReloadHandoff?.discard();\n throw error;\n }\n }\n },\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"majorModeCommand.mjs","names":[],"sources":["../../src/commands/majorModeCommand.ts"],"sourcesContent":["import { restoreHarnessStateSnapshot, snapshotHarnessState } from '@agimon-ai/doompi-config/harnessStore';\nimport { loadMajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport {\n appendDoomConfigTransition,\n replaceDoomConfigContext,\n requireDoomConfigContext,\n supersedeDoomConfigTransition,\n} from '@agimon-ai/doompi-config/piContext';\nimport type { DoomConfigPendingSelection } from '@agimon-ai/doompi-config/types';\nimport { alreadyComposed } from '@agimon-ai/doompi-extension-contracts/child-process';\nimport {\n type DoomTransitionResult,\n type MinorModeReloadHandoffHandle,\n prepareMinorModeReloadHandoff,\n requireDoomTransitionCoordinator,\n} from '@agimon-ai/doompi-extension-contracts/transition';\nimport { readDoomVoiceToolsService } from '@agimon-ai/doompi-extension-contracts/voice-tools';\nimport type {\n VoiceReloadHandoff,\n VoiceReloadHandoffIdentity,\n VoiceReloadHandoffStore,\n} from '@agimon-ai/doompi-extension-contracts/voice-reload-handoff';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport type { Context } from '@deepseek-ai/cordis';\nimport {\n applySummary,\n errorMessage,\n MAJOR_MODE_COMMAND,\n majorModeItems,\n majorModeOptionLabel,\n majorModeSummary,\n optionName,\n voiceSwitchToken,\n} from '../services/majorModeText.ts';\nimport { colorStatus, STATUS_KEY } from '../services/statusLine.ts';\nimport { MAJOR_MODE_EVENT, type MajorModeTelemetry } from '../types/telemetry.ts';\nimport { bindPendingSelection, clearPendingSelection, selectionFromSnapshot } from '../services/pendingSelection.ts';\nimport { MAJOR_MODE_SWITCH_HANDOFF_KIND, type MajorModeView } from '../types/majorMode.ts';\n\nfunction transitionError(result: DoomTransitionResult): Error {\n return new Error(`Major-mode transition was ${result.outcome}: ${result.diagnostics.join(', ')}`);\n}\n\nexport interface MajorModeCommandDependencies {\n readonly cordisContext: () => Context;\n readonly currentView: (ctx: ExtensionContext) => Promise<MajorModeView>;\n readonly reloadHandoffs: VoiceReloadHandoffStore;\n readonly loadPicker: () => Promise<typeof import('@agimon-ai/doompi-ui/components/matrixPicker')>;\n readonly loadSelectionSwitch: () => Promise<typeof import('@agimon-ai/doompi-config/selectionSwitch')>;\n readonly loadConfigJournal: () => Promise<typeof import('@agimon-ai/doompi-config/piContext')>;\n readonly resolveLayers: (config: MajorModeView['config'], majorMode: string) => string[];\n /** Whether an owning process supervisor listens for relaunch requests. */\n readonly supervisedRelaunchAvailable: () => boolean;\n /** Asks the supervisor to relaunch with the picked mode; call only at idle. */\n readonly requestSupervisedRelaunch: (majorMode: string, operationId: string) => boolean;\n}\n\nexport function registerMajorModeCommand(\n pi: ExtensionAPI,\n telemetry: MajorModeTelemetry,\n dependencies: MajorModeCommandDependencies,\n): void {\n pi.registerCommand(MAJOR_MODE_COMMAND, {\n description: 'Pick a named major mode',\n handler: async (args, ctx) => {\n let token: string | undefined;\n try {\n token = voiceSwitchToken(args);\n } catch (error) {\n ctx.ui.notify(errorMessage(error), 'error');\n return;\n }\n if (args.trim() && !token) {\n ctx.ui.notify(`Usage: /${MAJOR_MODE_COMMAND}`, 'warning');\n return;\n }\n\n const sessionId = ctx.sessionManager.getSessionId();\n const cordis = dependencies.cordisContext();\n let sessionIdentity: VoiceReloadHandoffIdentity | undefined;\n let handoff: VoiceReloadHandoff | undefined;\n let minorModeReloadHandoff: MinorModeReloadHandoffHandle | undefined;\n let reloadRequested = false;\n let performRelaunch: (() => boolean) | undefined;\n try {\n const { config, majorMode, domains, profile } = await dependencies.currentView(ctx);\n const names = Object.keys(config.majorMode);\n let picked: string | undefined;\n\n if (token) {\n const voiceSession = readDoomVoiceToolsService(cordis)?.readSession(sessionId);\n if (voiceSession) sessionIdentity = { sessionId, hostGeneration: voiceSession.hostGeneration };\n handoff = sessionIdentity ? dependencies.reloadHandoffs.accept(token, sessionIdentity) : undefined;\n if (handoff?.kind !== MAJOR_MODE_SWITCH_HANDOFF_KIND || !handoff.majorMode) {\n throw new Error('The voice major-mode switch token is stale or belongs to another session.');\n }\n picked = handoff.majorMode;\n } else {\n const title = `Major mode (current: ${majorMode})`;\n const chosen =\n ctx.mode === 'tui'\n ? await dependencies\n .loadPicker()\n .then(({ MatrixPickerComponent }) =>\n ctx.ui.custom<string[] | undefined>(\n (_tui, theme, keybindings, done) =>\n new MatrixPickerComponent(\n { title, items: majorModeItems(config, names), selected: [majorMode], multi: false },\n theme,\n keybindings,\n done,\n ),\n ),\n )\n : [\n optionName(\n (await ctx.ui.select(\n title,\n names.map((name) => majorModeOptionLabel(name, config.majorMode[name]?.layers ?? [], majorMode)),\n )) ?? '',\n ),\n ];\n picked = chosen?.[0];\n }\n if (!picked) return;\n\n const composed = alreadyComposed();\n if (picked === majorMode) {\n if (token && sessionIdentity) dependencies.reloadHandoffs.discard(token, sessionIdentity);\n clearPendingSelection(pi, dependencies.cordisContext());\n ctx.ui.notify(\n majorModeSummary(picked, dependencies.resolveLayers(config, picked), majorMode, composed),\n 'info',\n );\n return;\n }\n\n const coordinator = requireDoomTransitionCoordinator(cordis);\n const result = await coordinator.execute(\n {\n sessionId: ctx.sessionManager.getSessionId(),\n hostGeneration: coordinator.hostGeneration,\n operationId: handoff?.operationId ?? crypto.randomUUID(),\n source: handoff ? 'voice' : 'command',\n target: { axis: 'major-mode', majorMode: picked },\n },\n async (_request, plan) => {\n const selected = [...plan.candidate.layers];\n const stale = plan.externalRelaunchRequired;\n const currentContextAtExecution = requireDoomConfigContext(cordis);\n const harness = currentContextAtExecution.harness;\n if (plan.strategy === 'process-relaunch') {\n const pendingSelection: DoomConfigPendingSelection = {\n version: 1,\n operationId: plan.operationId,\n active: selectionFromSnapshot(plan.previous),\n target: selectionFromSnapshot(plan.candidate),\n strategy: plan.strategy,\n phase: 'pending',\n };\n const existingPending = requireDoomConfigContext(cordis).pendingSelection;\n if (existingPending) supersedeDoomConfigTransition(pi, existingPending);\n appendDoomConfigTransition(pi, pendingSelection);\n replaceDoomConfigContext(\n cordis,\n bindPendingSelection(requireDoomConfigContext(cordis), pendingSelection),\n );\n void telemetry.recordEvent(MAJOR_MODE_EVENT.majorModeSwitched, {\n 'harness.major_mode.previous': harness.majorMode,\n 'harness.major_mode': picked,\n 'harness.layers': selected.join(','),\n 'harness.major_mode.stale': true,\n 'harness.transition.disposition': plan.disposition,\n 'harness.transition.operation_id': plan.operationId,\n });\n ctx.ui.setStatus(\n STATUS_KEY,\n colorStatus(ctx.ui.theme, harness.majorMode, harness.domains, harness.profile, true),\n );\n if (dependencies.supervisedRelaunchAvailable()) {\n const target = picked;\n performRelaunch = () => dependencies.requestSupervisedRelaunch(target, plan.operationId);\n }\n ctx.ui.notify(\n applySummary(picked, selected, true, composed, plan.strategy, performRelaunch !== undefined),\n 'info',\n );\n return 'queued';\n }\n const [{ applyMajorMode }, { persistHarnessSelection }] = await Promise.all([\n dependencies.loadSelectionSwitch(),\n dependencies.loadConfigJournal(),\n ]);\n const transition: DoomConfigPendingSelection = {\n version: 1,\n operationId: plan.operationId,\n active: selectionFromSnapshot(plan.previous),\n target: selectionFromSnapshot(plan.candidate),\n strategy: plan.strategy ?? 'pi-reload',\n phase: 'pending',\n };\n const activeConfig = loadMajorModesConfig(harness.root ?? ctx.cwd);\n const snapshot = snapshotHarnessState();\n const existingPending = currentContextAtExecution.pendingSelection;\n minorModeReloadHandoff = prepareMinorModeReloadHandoff(\n cordis,\n ctx.sessionManager.getSessionId(),\n coordinator.hostGeneration,\n plan.operationId,\n plan.previous.minorModes,\n );\n try {\n if (existingPending) supersedeDoomConfigTransition(pi, existingPending);\n appendDoomConfigTransition(pi, transition);\n const updated = applyMajorMode(\n activeConfig,\n picked,\n harness,\n plan.candidate.compositionFingerprint,\n plan.candidate.childActivation,\n );\n persistHarnessSelection(pi, updated);\n } catch (error) {\n minorModeReloadHandoff?.discard();\n minorModeReloadHandoff = undefined;\n try {\n restoreHarnessStateSnapshot(snapshot);\n } catch (compensationError) {\n const detail =\n compensationError instanceof Error ? compensationError.message : String(compensationError);\n throw new Error(`Transition failed and compensation failed: ${detail}`, { cause: error });\n }\n appendDoomConfigTransition(pi, { ...transition, phase: 'aborted' });\n if (existingPending) appendDoomConfigTransition(pi, existingPending);\n throw error;\n }\n\n void telemetry.recordEvent(MAJOR_MODE_EVENT.majorModeSwitched, {\n 'harness.major_mode.previous': majorMode,\n 'harness.major_mode': picked,\n 'harness.layers': selected.join(','),\n 'harness.major_mode.stale': stale,\n 'harness.transition.disposition': plan.disposition,\n 'harness.transition.operation_id': plan.operationId,\n });\n ctx.ui.setStatus(STATUS_KEY, colorStatus(ctx.ui.theme, picked, domains, profile, false));\n ctx.ui.notify(applySummary(picked, selected, false, composed, plan.strategy), 'info');\n // Reload must be the command handler's terminal action. Pi invalidates\n // this command context as soon as the replacement session is active.\n reloadRequested = true;\n return 'applied';\n },\n );\n if (result.outcome === 'rejected' || result.outcome === 'stale') throw transitionError(result);\n } catch (error) {\n reloadRequested = false;\n performRelaunch = undefined;\n minorModeReloadHandoff?.discard();\n minorModeReloadHandoff = undefined;\n if (!token) throw error;\n if (sessionIdentity) dependencies.reloadHandoffs.discard(token, sessionIdentity);\n ctx.ui.notify(errorMessage(error), 'error');\n }\n\n if (reloadRequested) {\n try {\n await ctx.waitForIdle();\n // The replacement session acknowledges the pending journal after its\n // bootstrap harness is active. Do not touch ctx after this await.\n await ctx.reload();\n } catch (error) {\n minorModeReloadHandoff?.discard();\n throw error;\n }\n }\n if (performRelaunch) {\n // Written only at idle: the supervisor ends this agent's input for a\n // graceful exit the moment the file appears, then respawns it with the\n // pending major mode; the replacement resumes the session and\n // acknowledges the journal.\n await ctx.waitForIdle();\n if (performRelaunch()) ctx.shutdown();\n }\n },\n });\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
- const e=/^\[[ x]\] /,t=`core only`,n=`--major-mode`;function r(e,n){return n.map(n=>({value:n,label:n,description:e.majorMode[n]?.description??t}))}function i(e,n,r){return`${e===r?`[x]`:`[ ]`} ${e}: ${n.join(`, `)||t}`}function a(t){return t.replace(e,``).split(`:`)[0]??t}function o(e,r,i,a=!1,o){let s=`Switched to ${e}: ${r.join(`, `)||t}`;return o===`process-relaunch`?[`Major mode ${e} is pending; the current process remains active.`,` ./pi.sh ${n} ${e}`].join(`
2
- `):o===`pi-reload`?[s,`Pi reloaded.`].join(`
3
- `):!i||a?[s,`Hooks reloaded.`].join(`
4
- `):[s,`Hooks reloaded. Extensions are fixed at launch, so for those:`,` ./pi.sh ${n} ${e}`].join(`
1
+ const e=/^\[[ x]\] /,t=`core only`,n=`--major-mode`;function r(e,n){return n.map(n=>({value:n,label:n,description:e.majorMode[n]?.description??t}))}function i(e,n,r){return`${e===r?`[x]`:`[ ]`} ${e}: ${n.join(`, `)||t}`}function a(t){return t.replace(e,``).split(`:`)[0]??t}function o(e,r,i,a=!1,o,s=!1){let c=`Switched to ${e}: ${r.join(`, `)||t}`;return o===`process-relaunch`?s?`Major mode ${e} is pending; the supervisor is restarting the agent with it.`:[`Major mode ${e} is pending; the current process remains active.`,` ./pi.sh ${n} ${e}`].join(`
2
+ `):o===`pi-reload`?[c,`Pi reloaded.`].join(`
3
+ `):!i||a?[c,`Hooks reloaded.`].join(`
4
+ `):[c,`Hooks reloaded. Extensions are fixed at launch, so for those:`,` ./pi.sh ${n} ${e}`].join(`
5
5
  `)}function s(e,r,i,a=!1){let o=`Major mode ${e}: ${r.join(`, `)||t}`;return e!==i&&!a?[o,`Relaunch with:\n ./pi.sh ${n} ${e}`].join(`
6
6
  `):e===i?[o,`Already using this major mode.`].join(`
7
7
  `):o}function c(e){let t=e.trim();if(!t.startsWith(`--voice-switch-token=`))return;let n=t.split(/\s+/u);if(n.length!==1)throw Error(`The voice major-mode switch token must be the only command argument.`);let r=n[0]?.slice(21).trim();if(!r)throw Error(`The voice major-mode switch token is missing.`);return r}function l(e){return e instanceof Error?e.message:String(e)}exports.MAJOR_MODE_COMMAND=`mode`,exports.VOICE_SWITCH_TOKEN_PREFIX=`--voice-switch-token=`,exports.applySummary=o,exports.errorMessage=l,exports.majorModeItems=r,exports.majorModeOptionLabel=i,exports.majorModeSummary=s,exports.optionName=a,exports.voiceSwitchToken=c;
@@ -1 +1 @@
1
- {"version":3,"file":"majorModeText.cjs","names":[],"sources":["../../src/services/majorModeText.ts"],"sourcesContent":["import type { MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport type { TransitionExecutionStrategy } from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { SelectItem } from '@earendil-works/pi-tui';\n\nconst OPTION_PREFIX = /^\\[[ x]\\] /;\nconst CORE_ONLY = 'core only';\nconst MARK_ON = '[x]';\nconst MARK_OFF = '[ ]';\nconst RELAUNCH = 'Relaunch with:';\nconst MAJOR_MODE_OPTION = '--major-mode';\nexport const MAJOR_MODE_COMMAND = 'mode';\nexport const VOICE_SWITCH_TOKEN_PREFIX = '--voice-switch-token=';\n\n/** Picker rows show the resolved layers while retaining bare major mode values. */\nexport function majorModeItems(config: MajorModesConfig, names: string[]): SelectItem[] {\n return names.map((name) => ({\n value: name,\n label: name,\n description: config.majorMode[name]?.description ?? CORE_ONLY,\n }));\n}\n\nexport function majorModeOptionLabel(name: string, layers: string[], current: string): string {\n return `${name === current ? MARK_ON : MARK_OFF} ${name}: ${layers.join(', ') || CORE_ONLY}`;\n}\n\nexport function optionName(option: string): string {\n return option.replace(OPTION_PREFIX, '').split(':')[0] ?? option;\n}\n\n/**\n * What a switch actually applied.\n *\n * A synced session composes its extension set on every load, so a reload picks\n * up the new layers outright. A launcher session cannot: Pi freezes the CLI\n * `--extension` list at construction, so a layer that contributes extensions\n * needs a relaunch there and only the hooks change in place.\n */\nexport function applySummary(\n picked: string,\n layers: string[],\n stale: boolean,\n composed = false,\n strategy?: TransitionExecutionStrategy,\n): string {\n const head = `Switched to ${picked}: ${layers.join(', ') || CORE_ONLY}`;\n if (strategy === 'process-relaunch') {\n return [\n `Major mode ${picked} is pending; the current process remains active.`,\n ` ./pi.sh ${MAJOR_MODE_OPTION} ${picked}`,\n ].join('\\n');\n }\n if (strategy === 'pi-reload') return [head, 'Pi reloaded.'].join('\\n');\n if (!stale || composed) return [head, 'Hooks reloaded.'].join('\\n');\n return [\n head,\n 'Hooks reloaded. Extensions are fixed at launch, so for those:',\n ` ./pi.sh ${MAJOR_MODE_OPTION} ${picked}`,\n ].join('\\n');\n}\n\nexport function majorModeSummary(picked: string, layers: string[], current: string, composed = false): string {\n const contents = `Major mode ${picked}: ${layers.join(', ') || CORE_ONLY}`;\n if (picked !== current && !composed) {\n return [contents, `${RELAUNCH}\\n ./pi.sh ${MAJOR_MODE_OPTION} ${picked}`].join('\\n');\n }\n if (picked !== current) return contents;\n return [contents, 'Already using this major mode.'].join('\\n');\n}\n\n/** The opaque token a voice switch hands to the follow-up command invocation. */\nexport function voiceSwitchToken(args: string): string | undefined {\n const trimmed = args.trim();\n if (!trimmed.startsWith(VOICE_SWITCH_TOKEN_PREFIX)) return undefined;\n const parts = trimmed.split(/\\s+/u);\n if (parts.length !== 1) throw new Error('The voice major-mode switch token must be the only command argument.');\n const token = parts[0]?.slice(VOICE_SWITCH_TOKEN_PREFIX.length).trim();\n if (!token) throw new Error('The voice major-mode switch token is missing.');\n return token;\n}\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"AAIA,MAAM,EAAgB,aAChB,EAAY,YAIZ,EAAoB,eAK1B,SAAgB,EAAe,EAA0B,EAA+B,CACtF,OAAO,EAAM,IAAK,IAAU,CAC1B,MAAO,EACP,MAAO,EACP,YAAa,EAAO,UAAU,EAAK,EAAE,aAAe,CACtD,EAAE,CACJ,CAEA,SAAgB,EAAqB,EAAc,EAAkB,EAAyB,CAC5F,MAAO,GAAG,IAAS,EAAU,MAAU,MAAS,GAAG,EAAK,IAAI,EAAO,KAAK,IAAI,GAAK,GACnF,CAEA,SAAgB,EAAW,EAAwB,CACjD,OAAO,EAAO,QAAQ,EAAe,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAM,CAC5D,CAUA,SAAgB,EACd,EACA,EACA,EACA,EAAW,GACX,EACQ,CACR,IAAM,EAAO,eAAe,EAAO,IAAI,EAAO,KAAK,IAAI,GAAK,IAS5D,OARI,IAAa,mBACR,CACL,cAAc,EAAO,kDACrB,aAAa,EAAkB,GAAG,GACpC,CAAC,CAAC,KAAK;CAAI,EAET,IAAa,YAAoB,CAAC,EAAM,cAAc,CAAC,CAAC,KAAK;CAAI,EACjE,CAAC,GAAS,EAAiB,CAAC,EAAM,iBAAiB,CAAC,CAAC,KAAK;CAAI,EAC3D,CACL,EACA,gEACA,aAAa,EAAkB,GAAG,GACpC,CAAC,CAAC,KAAK;CAAI,CACb,CAEA,SAAgB,EAAiB,EAAgB,EAAkB,EAAiB,EAAW,GAAe,CAC5G,IAAM,EAAW,cAAc,EAAO,IAAI,EAAO,KAAK,IAAI,GAAK,IAK/D,OAJI,IAAW,GAAW,CAAC,EAClB,CAAC,EAAU,6BAA0B,EAAkB,GAAG,GAAQ,CAAC,CAAC,KAAK;CAAI,EAElF,IAAW,EACR,CAAC,EAAU,gCAAgC,CAAC,CAAC,KAAK;CAAI,EAD9B,CAEjC,CAGA,SAAgB,EAAiB,EAAkC,CACjE,IAAM,EAAU,EAAK,KAAK,EAC1B,GAAI,CAAC,EAAQ,WAAA,uBAAoC,EAAG,OACpD,IAAM,EAAQ,EAAQ,MAAM,MAAM,EAClC,GAAI,EAAM,SAAW,EAAG,MAAU,MAAM,sEAAsE,EAC9G,IAAM,EAAQ,EAAM,EAAE,EAAE,MAAM,EAAgC,CAAC,CAAC,KAAK,EACrE,GAAI,CAAC,EAAO,MAAU,MAAM,+CAA+C,EAC3E,OAAO,CACT,CAEA,SAAgB,EAAa,EAAwB,CACnD,OAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC9D"}
1
+ {"version":3,"file":"majorModeText.cjs","names":[],"sources":["../../src/services/majorModeText.ts"],"sourcesContent":["import type { MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport type { TransitionExecutionStrategy } from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { SelectItem } from '@earendil-works/pi-tui';\n\nconst OPTION_PREFIX = /^\\[[ x]\\] /;\nconst CORE_ONLY = 'core only';\nconst MARK_ON = '[x]';\nconst MARK_OFF = '[ ]';\nconst RELAUNCH = 'Relaunch with:';\nconst MAJOR_MODE_OPTION = '--major-mode';\nexport const MAJOR_MODE_COMMAND = 'mode';\nexport const VOICE_SWITCH_TOKEN_PREFIX = '--voice-switch-token=';\n\n/** Picker rows show the resolved layers while retaining bare major mode values. */\nexport function majorModeItems(config: MajorModesConfig, names: string[]): SelectItem[] {\n return names.map((name) => ({\n value: name,\n label: name,\n description: config.majorMode[name]?.description ?? CORE_ONLY,\n }));\n}\n\nexport function majorModeOptionLabel(name: string, layers: string[], current: string): string {\n return `${name === current ? MARK_ON : MARK_OFF} ${name}: ${layers.join(', ') || CORE_ONLY}`;\n}\n\nexport function optionName(option: string): string {\n return option.replace(OPTION_PREFIX, '').split(':')[0] ?? option;\n}\n\n/**\n * What a switch actually applied.\n *\n * A synced session composes its extension set on every load, so a reload picks\n * up the new layers outright. A launcher session cannot: Pi freezes the CLI\n * `--extension` list at construction, so a layer that contributes extensions\n * needs a relaunch there and only the hooks change in place.\n */\nexport function applySummary(\n picked: string,\n layers: string[],\n stale: boolean,\n composed = false,\n strategy?: TransitionExecutionStrategy,\n supervised = false,\n): string {\n const head = `Switched to ${picked}: ${layers.join(', ') || CORE_ONLY}`;\n if (strategy === 'process-relaunch') {\n if (supervised) {\n return `Major mode ${picked} is pending; the supervisor is restarting the agent with it.`;\n }\n return [\n `Major mode ${picked} is pending; the current process remains active.`,\n ` ./pi.sh ${MAJOR_MODE_OPTION} ${picked}`,\n ].join('\\n');\n }\n if (strategy === 'pi-reload') return [head, 'Pi reloaded.'].join('\\n');\n if (!stale || composed) return [head, 'Hooks reloaded.'].join('\\n');\n return [\n head,\n 'Hooks reloaded. Extensions are fixed at launch, so for those:',\n ` ./pi.sh ${MAJOR_MODE_OPTION} ${picked}`,\n ].join('\\n');\n}\n\nexport function majorModeSummary(picked: string, layers: string[], current: string, composed = false): string {\n const contents = `Major mode ${picked}: ${layers.join(', ') || CORE_ONLY}`;\n if (picked !== current && !composed) {\n return [contents, `${RELAUNCH}\\n ./pi.sh ${MAJOR_MODE_OPTION} ${picked}`].join('\\n');\n }\n if (picked !== current) return contents;\n return [contents, 'Already using this major mode.'].join('\\n');\n}\n\n/** The opaque token a voice switch hands to the follow-up command invocation. */\nexport function voiceSwitchToken(args: string): string | undefined {\n const trimmed = args.trim();\n if (!trimmed.startsWith(VOICE_SWITCH_TOKEN_PREFIX)) return undefined;\n const parts = trimmed.split(/\\s+/u);\n if (parts.length !== 1) throw new Error('The voice major-mode switch token must be the only command argument.');\n const token = parts[0]?.slice(VOICE_SWITCH_TOKEN_PREFIX.length).trim();\n if (!token) throw new Error('The voice major-mode switch token is missing.');\n return token;\n}\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"AAIA,MAAM,EAAgB,aAChB,EAAY,YAIZ,EAAoB,eAK1B,SAAgB,EAAe,EAA0B,EAA+B,CACtF,OAAO,EAAM,IAAK,IAAU,CAC1B,MAAO,EACP,MAAO,EACP,YAAa,EAAO,UAAU,EAAK,EAAE,aAAe,CACtD,EAAE,CACJ,CAEA,SAAgB,EAAqB,EAAc,EAAkB,EAAyB,CAC5F,MAAO,GAAG,IAAS,EAAU,MAAU,MAAS,GAAG,EAAK,IAAI,EAAO,KAAK,IAAI,GAAK,GACnF,CAEA,SAAgB,EAAW,EAAwB,CACjD,OAAO,EAAO,QAAQ,EAAe,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAM,CAC5D,CAUA,SAAgB,EACd,EACA,EACA,EACA,EAAW,GACX,EACA,EAAa,GACL,CACR,IAAM,EAAO,eAAe,EAAO,IAAI,EAAO,KAAK,IAAI,GAAK,IAY5D,OAXI,IAAa,mBACX,EACK,cAAc,EAAO,8DAEvB,CACL,cAAc,EAAO,kDACrB,aAAa,EAAkB,GAAG,GACpC,CAAC,CAAC,KAAK;CAAI,EAET,IAAa,YAAoB,CAAC,EAAM,cAAc,CAAC,CAAC,KAAK;CAAI,EACjE,CAAC,GAAS,EAAiB,CAAC,EAAM,iBAAiB,CAAC,CAAC,KAAK;CAAI,EAC3D,CACL,EACA,gEACA,aAAa,EAAkB,GAAG,GACpC,CAAC,CAAC,KAAK;CAAI,CACb,CAEA,SAAgB,EAAiB,EAAgB,EAAkB,EAAiB,EAAW,GAAe,CAC5G,IAAM,EAAW,cAAc,EAAO,IAAI,EAAO,KAAK,IAAI,GAAK,IAK/D,OAJI,IAAW,GAAW,CAAC,EAClB,CAAC,EAAU,6BAA0B,EAAkB,GAAG,GAAQ,CAAC,CAAC,KAAK;CAAI,EAElF,IAAW,EACR,CAAC,EAAU,gCAAgC,CAAC,CAAC,KAAK;CAAI,EAD9B,CAEjC,CAGA,SAAgB,EAAiB,EAAkC,CACjE,IAAM,EAAU,EAAK,KAAK,EAC1B,GAAI,CAAC,EAAQ,WAAA,uBAAoC,EAAG,OACpD,IAAM,EAAQ,EAAQ,MAAM,MAAM,EAClC,GAAI,EAAM,SAAW,EAAG,MAAU,MAAM,sEAAsE,EAC9G,IAAM,EAAQ,EAAM,EAAE,EAAE,MAAM,EAAgC,CAAC,CAAC,KAAK,EACrE,GAAI,CAAC,EAAO,MAAU,MAAM,+CAA+C,EAC3E,OAAO,CACT,CAEA,SAAgB,EAAa,EAAwB,CACnD,OAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC9D"}
@@ -16,7 +16,7 @@ declare function optionName(option: string): string;
16
16
  * `--extension` list at construction, so a layer that contributes extensions
17
17
  * needs a relaunch there and only the hooks change in place.
18
18
  */
19
- declare function applySummary(picked: string, layers: string[], stale: boolean, composed?: boolean, strategy?: TransitionExecutionStrategy): string;
19
+ declare function applySummary(picked: string, layers: string[], stale: boolean, composed?: boolean, strategy?: TransitionExecutionStrategy, supervised?: boolean): string;
20
20
  declare function majorModeSummary(picked: string, layers: string[], current: string, composed?: boolean): string;
21
21
  /** The opaque token a voice switch hands to the follow-up command invocation. */
22
22
  declare function voiceSwitchToken(args: string): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"majorModeText.d.cts","names":[],"sources":["../../src/services/majorModeText.ts"],"mappings":";;;;cAUa;cACA;;iBAGG,eAAe,QAAQ,kBAAkB,kBAAkB;iBAQ3D,qBAAqB,cAAc,kBAAkB;iBAIrD,WAAW;;;;;;;;;iBAYX,aACd,gBACA,kBACA,gBACA,oBACA,WAAW;iBAkBG,iBAAiB,gBAAgB,kBAAkB,iBAAiB;;iBAUpE,iBAAiB"}
1
+ {"version":3,"file":"majorModeText.d.cts","names":[],"sources":["../../src/services/majorModeText.ts"],"mappings":";;;;cAUa;cACA;;iBAGG,eAAe,QAAQ,kBAAkB,kBAAkB;iBAQ3D,qBAAqB,cAAc,kBAAkB;iBAIrD,WAAW;;;;;;;;;iBAYX,aACd,gBACA,kBACA,gBACA,oBACA,WAAW,6BACX;iBAqBc,iBAAiB,gBAAgB,kBAAkB,iBAAiB;;iBAUpE,iBAAiB"}
@@ -16,7 +16,7 @@ declare function optionName(option: string): string;
16
16
  * `--extension` list at construction, so a layer that contributes extensions
17
17
  * needs a relaunch there and only the hooks change in place.
18
18
  */
19
- declare function applySummary(picked: string, layers: string[], stale: boolean, composed?: boolean, strategy?: TransitionExecutionStrategy): string;
19
+ declare function applySummary(picked: string, layers: string[], stale: boolean, composed?: boolean, strategy?: TransitionExecutionStrategy, supervised?: boolean): string;
20
20
  declare function majorModeSummary(picked: string, layers: string[], current: string, composed?: boolean): string;
21
21
  /** The opaque token a voice switch hands to the follow-up command invocation. */
22
22
  declare function voiceSwitchToken(args: string): string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"majorModeText.d.mts","names":[],"sources":["../../src/services/majorModeText.ts"],"mappings":";;;;cAUa;cACA;;iBAGG,eAAe,QAAQ,kBAAkB,kBAAkB;iBAQ3D,qBAAqB,cAAc,kBAAkB;iBAIrD,WAAW;;;;;;;;;iBAYX,aACd,gBACA,kBACA,gBACA,oBACA,WAAW;iBAkBG,iBAAiB,gBAAgB,kBAAkB,iBAAiB;;iBAUpE,iBAAiB"}
1
+ {"version":3,"file":"majorModeText.d.mts","names":[],"sources":["../../src/services/majorModeText.ts"],"mappings":";;;;cAUa;cACA;;iBAGG,eAAe,QAAQ,kBAAkB,kBAAkB;iBAQ3D,qBAAqB,cAAc,kBAAkB;iBAIrD,WAAW;;;;;;;;;iBAYX,aACd,gBACA,kBACA,gBACA,oBACA,WAAW,6BACX;iBAqBc,iBAAiB,gBAAgB,kBAAkB,iBAAiB;;iBAUpE,iBAAiB"}
@@ -1,7 +1,7 @@
1
- const e=/^\[[ x]\] /,t=`core only`,n=`--major-mode`,r=`mode`,i=`--voice-switch-token=`;function a(e,n){return n.map(n=>({value:n,label:n,description:e.majorMode[n]?.description??t}))}function o(e,n,r){return`${e===r?`[x]`:`[ ]`} ${e}: ${n.join(`, `)||t}`}function s(t){return t.replace(e,``).split(`:`)[0]??t}function c(e,r,i,a=!1,o){let s=`Switched to ${e}: ${r.join(`, `)||t}`;return o===`process-relaunch`?[`Major mode ${e} is pending; the current process remains active.`,` ./pi.sh ${n} ${e}`].join(`
2
- `):o===`pi-reload`?[s,`Pi reloaded.`].join(`
3
- `):!i||a?[s,`Hooks reloaded.`].join(`
4
- `):[s,`Hooks reloaded. Extensions are fixed at launch, so for those:`,` ./pi.sh ${n} ${e}`].join(`
1
+ const e=/^\[[ x]\] /,t=`core only`,n=`--major-mode`,r=`mode`,i=`--voice-switch-token=`;function a(e,n){return n.map(n=>({value:n,label:n,description:e.majorMode[n]?.description??t}))}function o(e,n,r){return`${e===r?`[x]`:`[ ]`} ${e}: ${n.join(`, `)||t}`}function s(t){return t.replace(e,``).split(`:`)[0]??t}function c(e,r,i,a=!1,o,s=!1){let c=`Switched to ${e}: ${r.join(`, `)||t}`;return o===`process-relaunch`?s?`Major mode ${e} is pending; the supervisor is restarting the agent with it.`:[`Major mode ${e} is pending; the current process remains active.`,` ./pi.sh ${n} ${e}`].join(`
2
+ `):o===`pi-reload`?[c,`Pi reloaded.`].join(`
3
+ `):!i||a?[c,`Hooks reloaded.`].join(`
4
+ `):[c,`Hooks reloaded. Extensions are fixed at launch, so for those:`,` ./pi.sh ${n} ${e}`].join(`
5
5
  `)}function l(e,r,i,a=!1){let o=`Major mode ${e}: ${r.join(`, `)||t}`;return e!==i&&!a?[o,`Relaunch with:\n ./pi.sh ${n} ${e}`].join(`
6
6
  `):e===i?[o,`Already using this major mode.`].join(`
7
7
  `):o}function u(e){let t=e.trim();if(!t.startsWith(`--voice-switch-token=`))return;let n=t.split(/\s+/u);if(n.length!==1)throw Error(`The voice major-mode switch token must be the only command argument.`);let r=n[0]?.slice(21).trim();if(!r)throw Error(`The voice major-mode switch token is missing.`);return r}function d(e){return e instanceof Error?e.message:String(e)}export{r as MAJOR_MODE_COMMAND,i as VOICE_SWITCH_TOKEN_PREFIX,c as applySummary,d as errorMessage,a as majorModeItems,o as majorModeOptionLabel,l as majorModeSummary,s as optionName,u as voiceSwitchToken};
@@ -1 +1 @@
1
- {"version":3,"file":"majorModeText.mjs","names":[],"sources":["../../src/services/majorModeText.ts"],"sourcesContent":["import type { MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport type { TransitionExecutionStrategy } from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { SelectItem } from '@earendil-works/pi-tui';\n\nconst OPTION_PREFIX = /^\\[[ x]\\] /;\nconst CORE_ONLY = 'core only';\nconst MARK_ON = '[x]';\nconst MARK_OFF = '[ ]';\nconst RELAUNCH = 'Relaunch with:';\nconst MAJOR_MODE_OPTION = '--major-mode';\nexport const MAJOR_MODE_COMMAND = 'mode';\nexport const VOICE_SWITCH_TOKEN_PREFIX = '--voice-switch-token=';\n\n/** Picker rows show the resolved layers while retaining bare major mode values. */\nexport function majorModeItems(config: MajorModesConfig, names: string[]): SelectItem[] {\n return names.map((name) => ({\n value: name,\n label: name,\n description: config.majorMode[name]?.description ?? CORE_ONLY,\n }));\n}\n\nexport function majorModeOptionLabel(name: string, layers: string[], current: string): string {\n return `${name === current ? MARK_ON : MARK_OFF} ${name}: ${layers.join(', ') || CORE_ONLY}`;\n}\n\nexport function optionName(option: string): string {\n return option.replace(OPTION_PREFIX, '').split(':')[0] ?? option;\n}\n\n/**\n * What a switch actually applied.\n *\n * A synced session composes its extension set on every load, so a reload picks\n * up the new layers outright. A launcher session cannot: Pi freezes the CLI\n * `--extension` list at construction, so a layer that contributes extensions\n * needs a relaunch there and only the hooks change in place.\n */\nexport function applySummary(\n picked: string,\n layers: string[],\n stale: boolean,\n composed = false,\n strategy?: TransitionExecutionStrategy,\n): string {\n const head = `Switched to ${picked}: ${layers.join(', ') || CORE_ONLY}`;\n if (strategy === 'process-relaunch') {\n return [\n `Major mode ${picked} is pending; the current process remains active.`,\n ` ./pi.sh ${MAJOR_MODE_OPTION} ${picked}`,\n ].join('\\n');\n }\n if (strategy === 'pi-reload') return [head, 'Pi reloaded.'].join('\\n');\n if (!stale || composed) return [head, 'Hooks reloaded.'].join('\\n');\n return [\n head,\n 'Hooks reloaded. Extensions are fixed at launch, so for those:',\n ` ./pi.sh ${MAJOR_MODE_OPTION} ${picked}`,\n ].join('\\n');\n}\n\nexport function majorModeSummary(picked: string, layers: string[], current: string, composed = false): string {\n const contents = `Major mode ${picked}: ${layers.join(', ') || CORE_ONLY}`;\n if (picked !== current && !composed) {\n return [contents, `${RELAUNCH}\\n ./pi.sh ${MAJOR_MODE_OPTION} ${picked}`].join('\\n');\n }\n if (picked !== current) return contents;\n return [contents, 'Already using this major mode.'].join('\\n');\n}\n\n/** The opaque token a voice switch hands to the follow-up command invocation. */\nexport function voiceSwitchToken(args: string): string | undefined {\n const trimmed = args.trim();\n if (!trimmed.startsWith(VOICE_SWITCH_TOKEN_PREFIX)) return undefined;\n const parts = trimmed.split(/\\s+/u);\n if (parts.length !== 1) throw new Error('The voice major-mode switch token must be the only command argument.');\n const token = parts[0]?.slice(VOICE_SWITCH_TOKEN_PREFIX.length).trim();\n if (!token) throw new Error('The voice major-mode switch token is missing.');\n return token;\n}\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"AAIA,MAAM,EAAgB,aAChB,EAAY,YAIZ,EAAoB,eACb,EAAqB,OACrB,EAA4B,wBAGzC,SAAgB,EAAe,EAA0B,EAA+B,CACtF,OAAO,EAAM,IAAK,IAAU,CAC1B,MAAO,EACP,MAAO,EACP,YAAa,EAAO,UAAU,EAAK,EAAE,aAAe,CACtD,EAAE,CACJ,CAEA,SAAgB,EAAqB,EAAc,EAAkB,EAAyB,CAC5F,MAAO,GAAG,IAAS,EAAU,MAAU,MAAS,GAAG,EAAK,IAAI,EAAO,KAAK,IAAI,GAAK,GACnF,CAEA,SAAgB,EAAW,EAAwB,CACjD,OAAO,EAAO,QAAQ,EAAe,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAM,CAC5D,CAUA,SAAgB,EACd,EACA,EACA,EACA,EAAW,GACX,EACQ,CACR,IAAM,EAAO,eAAe,EAAO,IAAI,EAAO,KAAK,IAAI,GAAK,IAS5D,OARI,IAAa,mBACR,CACL,cAAc,EAAO,kDACrB,aAAa,EAAkB,GAAG,GACpC,CAAC,CAAC,KAAK;CAAI,EAET,IAAa,YAAoB,CAAC,EAAM,cAAc,CAAC,CAAC,KAAK;CAAI,EACjE,CAAC,GAAS,EAAiB,CAAC,EAAM,iBAAiB,CAAC,CAAC,KAAK;CAAI,EAC3D,CACL,EACA,gEACA,aAAa,EAAkB,GAAG,GACpC,CAAC,CAAC,KAAK;CAAI,CACb,CAEA,SAAgB,EAAiB,EAAgB,EAAkB,EAAiB,EAAW,GAAe,CAC5G,IAAM,EAAW,cAAc,EAAO,IAAI,EAAO,KAAK,IAAI,GAAK,IAK/D,OAJI,IAAW,GAAW,CAAC,EAClB,CAAC,EAAU,6BAA0B,EAAkB,GAAG,GAAQ,CAAC,CAAC,KAAK;CAAI,EAElF,IAAW,EACR,CAAC,EAAU,gCAAgC,CAAC,CAAC,KAAK;CAAI,EAD9B,CAEjC,CAGA,SAAgB,EAAiB,EAAkC,CACjE,IAAM,EAAU,EAAK,KAAK,EAC1B,GAAI,CAAC,EAAQ,WAAA,uBAAoC,EAAG,OACpD,IAAM,EAAQ,EAAQ,MAAM,MAAM,EAClC,GAAI,EAAM,SAAW,EAAG,MAAU,MAAM,sEAAsE,EAC9G,IAAM,EAAQ,EAAM,EAAE,EAAE,MAAM,EAAgC,CAAC,CAAC,KAAK,EACrE,GAAI,CAAC,EAAO,MAAU,MAAM,+CAA+C,EAC3E,OAAO,CACT,CAEA,SAAgB,EAAa,EAAwB,CACnD,OAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC9D"}
1
+ {"version":3,"file":"majorModeText.mjs","names":[],"sources":["../../src/services/majorModeText.ts"],"sourcesContent":["import type { MajorModesConfig } from '@agimon-ai/doompi-config/majorModes';\nimport type { TransitionExecutionStrategy } from '@agimon-ai/doompi-extension-contracts/transition';\nimport type { SelectItem } from '@earendil-works/pi-tui';\n\nconst OPTION_PREFIX = /^\\[[ x]\\] /;\nconst CORE_ONLY = 'core only';\nconst MARK_ON = '[x]';\nconst MARK_OFF = '[ ]';\nconst RELAUNCH = 'Relaunch with:';\nconst MAJOR_MODE_OPTION = '--major-mode';\nexport const MAJOR_MODE_COMMAND = 'mode';\nexport const VOICE_SWITCH_TOKEN_PREFIX = '--voice-switch-token=';\n\n/** Picker rows show the resolved layers while retaining bare major mode values. */\nexport function majorModeItems(config: MajorModesConfig, names: string[]): SelectItem[] {\n return names.map((name) => ({\n value: name,\n label: name,\n description: config.majorMode[name]?.description ?? CORE_ONLY,\n }));\n}\n\nexport function majorModeOptionLabel(name: string, layers: string[], current: string): string {\n return `${name === current ? MARK_ON : MARK_OFF} ${name}: ${layers.join(', ') || CORE_ONLY}`;\n}\n\nexport function optionName(option: string): string {\n return option.replace(OPTION_PREFIX, '').split(':')[0] ?? option;\n}\n\n/**\n * What a switch actually applied.\n *\n * A synced session composes its extension set on every load, so a reload picks\n * up the new layers outright. A launcher session cannot: Pi freezes the CLI\n * `--extension` list at construction, so a layer that contributes extensions\n * needs a relaunch there and only the hooks change in place.\n */\nexport function applySummary(\n picked: string,\n layers: string[],\n stale: boolean,\n composed = false,\n strategy?: TransitionExecutionStrategy,\n supervised = false,\n): string {\n const head = `Switched to ${picked}: ${layers.join(', ') || CORE_ONLY}`;\n if (strategy === 'process-relaunch') {\n if (supervised) {\n return `Major mode ${picked} is pending; the supervisor is restarting the agent with it.`;\n }\n return [\n `Major mode ${picked} is pending; the current process remains active.`,\n ` ./pi.sh ${MAJOR_MODE_OPTION} ${picked}`,\n ].join('\\n');\n }\n if (strategy === 'pi-reload') return [head, 'Pi reloaded.'].join('\\n');\n if (!stale || composed) return [head, 'Hooks reloaded.'].join('\\n');\n return [\n head,\n 'Hooks reloaded. Extensions are fixed at launch, so for those:',\n ` ./pi.sh ${MAJOR_MODE_OPTION} ${picked}`,\n ].join('\\n');\n}\n\nexport function majorModeSummary(picked: string, layers: string[], current: string, composed = false): string {\n const contents = `Major mode ${picked}: ${layers.join(', ') || CORE_ONLY}`;\n if (picked !== current && !composed) {\n return [contents, `${RELAUNCH}\\n ./pi.sh ${MAJOR_MODE_OPTION} ${picked}`].join('\\n');\n }\n if (picked !== current) return contents;\n return [contents, 'Already using this major mode.'].join('\\n');\n}\n\n/** The opaque token a voice switch hands to the follow-up command invocation. */\nexport function voiceSwitchToken(args: string): string | undefined {\n const trimmed = args.trim();\n if (!trimmed.startsWith(VOICE_SWITCH_TOKEN_PREFIX)) return undefined;\n const parts = trimmed.split(/\\s+/u);\n if (parts.length !== 1) throw new Error('The voice major-mode switch token must be the only command argument.');\n const token = parts[0]?.slice(VOICE_SWITCH_TOKEN_PREFIX.length).trim();\n if (!token) throw new Error('The voice major-mode switch token is missing.');\n return token;\n}\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n"],"mappings":"AAIA,MAAM,EAAgB,aAChB,EAAY,YAIZ,EAAoB,eACb,EAAqB,OACrB,EAA4B,wBAGzC,SAAgB,EAAe,EAA0B,EAA+B,CACtF,OAAO,EAAM,IAAK,IAAU,CAC1B,MAAO,EACP,MAAO,EACP,YAAa,EAAO,UAAU,EAAK,EAAE,aAAe,CACtD,EAAE,CACJ,CAEA,SAAgB,EAAqB,EAAc,EAAkB,EAAyB,CAC5F,MAAO,GAAG,IAAS,EAAU,MAAU,MAAS,GAAG,EAAK,IAAI,EAAO,KAAK,IAAI,GAAK,GACnF,CAEA,SAAgB,EAAW,EAAwB,CACjD,OAAO,EAAO,QAAQ,EAAe,EAAE,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,IAAM,CAC5D,CAUA,SAAgB,EACd,EACA,EACA,EACA,EAAW,GACX,EACA,EAAa,GACL,CACR,IAAM,EAAO,eAAe,EAAO,IAAI,EAAO,KAAK,IAAI,GAAK,IAY5D,OAXI,IAAa,mBACX,EACK,cAAc,EAAO,8DAEvB,CACL,cAAc,EAAO,kDACrB,aAAa,EAAkB,GAAG,GACpC,CAAC,CAAC,KAAK;CAAI,EAET,IAAa,YAAoB,CAAC,EAAM,cAAc,CAAC,CAAC,KAAK;CAAI,EACjE,CAAC,GAAS,EAAiB,CAAC,EAAM,iBAAiB,CAAC,CAAC,KAAK;CAAI,EAC3D,CACL,EACA,gEACA,aAAa,EAAkB,GAAG,GACpC,CAAC,CAAC,KAAK;CAAI,CACb,CAEA,SAAgB,EAAiB,EAAgB,EAAkB,EAAiB,EAAW,GAAe,CAC5G,IAAM,EAAW,cAAc,EAAO,IAAI,EAAO,KAAK,IAAI,GAAK,IAK/D,OAJI,IAAW,GAAW,CAAC,EAClB,CAAC,EAAU,6BAA0B,EAAkB,GAAG,GAAQ,CAAC,CAAC,KAAK;CAAI,EAElF,IAAW,EACR,CAAC,EAAU,gCAAgC,CAAC,CAAC,KAAK;CAAI,EAD9B,CAEjC,CAGA,SAAgB,EAAiB,EAAkC,CACjE,IAAM,EAAU,EAAK,KAAK,EAC1B,GAAI,CAAC,EAAQ,WAAA,uBAAoC,EAAG,OACpD,IAAM,EAAQ,EAAQ,MAAM,MAAM,EAClC,GAAI,EAAM,SAAW,EAAG,MAAU,MAAM,sEAAsE,EAC9G,IAAM,EAAQ,EAAM,EAAE,EAAE,MAAM,EAAgC,CAAC,CAAC,KAAK,EACrE,GAAI,CAAC,EAAO,MAAU,MAAM,+CAA+C,EAC3E,OAAO,CACT,CAEA,SAAgB,EAAa,EAAwB,CACnD,OAAO,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,CAC9D"}
@@ -1 +1 @@
1
- {"version":3,"file":"pendingSelection.d.cts","names":[],"sources":["../../src/services/pendingSelection.ts"],"mappings":";;;;;;iBAWgB,sBAAsB,UAAU;;;;;;;iBAUhC,sBAAsB,IAAI,cAAc,QAAQ;iBAQhD,qBACd,SAAS,mBACT,kBAAkB,6BACjB"}
1
+ {"version":3,"file":"pendingSelection.d.cts","names":[],"sources":["../../src/services/pendingSelection.ts"],"mappings":";;;;;;iBAWgB,sBAAsB,UAAU;EAE5C;EACA;EACA;EACA;EACA;;iBAIY,sBAAsB,IAAI,cAAc,QAAQ;iBAQhD,qBACd,SAAS,mBACT,kBAAkB,6BACjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"pendingSelection.d.mts","names":[],"sources":["../../src/services/pendingSelection.ts"],"mappings":";;;;;;iBAWgB,sBAAsB,UAAU;;;;;;;iBAUhC,sBAAsB,IAAI,cAAc,QAAQ;iBAQhD,qBACd,SAAS,mBACT,kBAAkB,6BACjB"}
1
+ {"version":3,"file":"pendingSelection.d.mts","names":[],"sources":["../../src/services/pendingSelection.ts"],"mappings":";;;;;;iBAWgB,sBAAsB,UAAU;EAE5C;EACA;EACA;EACA;EACA;;iBAIY,sBAAsB,IAAI,cAAc,QAAQ;iBAQhD,qBACd,SAAS,mBACT,kBAAkB,6BACjB"}
@@ -1,8 +1,8 @@
1
1
  //#region src/types/telemetry.d.ts
2
2
  /** Telemetry events this package reports, and the port that records them. */
3
3
  declare const MAJOR_MODE_EVENT: {
4
- readonly majorModeSwitched: "doom_pi_major_mode.switched";
5
- readonly majorModeUnavailable: "doom_pi_major_mode.unavailable";
4
+ readonly majorModeSwitched: 'doom_pi_major_mode.switched';
5
+ readonly majorModeUnavailable: 'doom_pi_major_mode.unavailable';
6
6
  };
7
7
  type MajorModeEventName = (typeof MAJOR_MODE_EVENT)[keyof typeof MAJOR_MODE_EVENT];
8
8
  type MajorModeEventAttributes = Record<string, string | number | boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.d.cts","names":[],"sources":["../../src/types/telemetry.ts"],"mappings":";;cACa;WAAA;WAAA;;KAKD,6BAA6B,+BAA+B;KAC5D,2BAA2B;UAEtB;EACf,YAAY,OAAO,oBAAoB,gBAAgB,aAAa,2BAA2B;EAC/F,YAAY,OAAO,oBAAoB,aAAa,2BAA2B"}
1
+ {"version":3,"file":"telemetry.d.cts","names":[],"sources":["../../src/types/telemetry.ts"],"mappings":";;cACa;WACX;WACA;;KAGU,6BAA6B,+BAA+B;KAC5D,2BAA2B;UAEtB;EACf,YAAY,OAAO,oBAAoB,gBAAgB,aAAa,2BAA2B;EAC/F,YAAY,OAAO,oBAAoB,aAAa,2BAA2B"}
@@ -1,8 +1,8 @@
1
1
  //#region src/types/telemetry.d.ts
2
2
  /** Telemetry events this package reports, and the port that records them. */
3
3
  declare const MAJOR_MODE_EVENT: {
4
- readonly majorModeSwitched: "doom_pi_major_mode.switched";
5
- readonly majorModeUnavailable: "doom_pi_major_mode.unavailable";
4
+ readonly majorModeSwitched: 'doom_pi_major_mode.switched';
5
+ readonly majorModeUnavailable: 'doom_pi_major_mode.unavailable';
6
6
  };
7
7
  type MajorModeEventName = (typeof MAJOR_MODE_EVENT)[keyof typeof MAJOR_MODE_EVENT];
8
8
  type MajorModeEventAttributes = Record<string, string | number | boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.d.mts","names":[],"sources":["../../src/types/telemetry.ts"],"mappings":";;cACa;WAAA;WAAA;;KAKD,6BAA6B,+BAA+B;KAC5D,2BAA2B;UAEtB;EACf,YAAY,OAAO,oBAAoB,gBAAgB,aAAa,2BAA2B;EAC/F,YAAY,OAAO,oBAAoB,aAAa,2BAA2B"}
1
+ {"version":3,"file":"telemetry.d.mts","names":[],"sources":["../../src/types/telemetry.ts"],"mappings":";;cACa;WACX;WACA;;KAGU,6BAA6B,+BAA+B;KAC5D,2BAA2B;UAEtB;EACf,YAAY,OAAO,oBAAoB,gBAAgB,aAAa,2BAA2B;EAC/F,YAAY,OAAO,oBAAoB,aAAa,2BAA2B"}
package/llms.txt CHANGED
@@ -2,6 +2,11 @@
2
2
 
3
3
  Named major-mode selection for Doom Pi sessions.
4
4
 
5
- ## Resources
5
+ ## How to author
6
6
 
7
- - [Package README](./README.md): the `/mode` command, the `major_mode` voice tool, and how a mode resolves to layers.
7
+ - [Author DoomPi major modes](./src/prompts/doompi-author-major-mode/SKILL.md): Edit `modes.yaml` safely.
8
+ - [`modes.yaml` authoring contract](./src/prompts/doompi-author-major-mode/references/modes-contract.md): Source precedence, schema, resolution, and verification details.
9
+
10
+ ## Package reference
11
+
12
+ - [Package README](./README.md): The `/mode` command, the `major_mode` voice tool, and how a mode resolves to layers.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-major-mode",
3
- "version": "0.0.1-alpha.3",
3
+ "version": "0.0.1-alpha.30",
4
4
  "description": "Named major-mode selection and layer composition switching for DoomPi sessions.",
5
5
  "keywords": [
6
6
  "ai",
@@ -15,11 +15,20 @@
15
15
  "session-reload"
16
16
  ],
17
17
  "homepage": "https://agimon.ai",
18
+ "bugs": {
19
+ "url": "https://github.com/AgiFlow/doompi/issues"
20
+ },
18
21
  "license": "MIT",
19
22
  "author": "Vuong Ngo",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/AgiFlow/doompi.git",
26
+ "directory": "packages/core/doompi-major-mode"
27
+ },
20
28
  "files": [
21
29
  "dist",
22
30
  "llms.txt",
31
+ "src/prompts",
23
32
  "README.md",
24
33
  "LICENSE",
25
34
  "package.json"
@@ -46,26 +55,26 @@
46
55
  },
47
56
  "dependencies": {
48
57
  "@deepseek-ai/cordis": "4.0.1",
49
- "typebox": "1.1.38",
50
- "@agimon-ai/doompi-ui": "0.0.1-alpha.25",
51
- "@agimon-ai/doompi-config": "0.0.1-alpha.25",
52
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.25",
53
- "@agimon-ai/doompi-telemetry": "0.0.1-alpha.25"
58
+ "typebox": "1.3.19",
59
+ "@agimon-ai/doompi-config": "0.0.1-alpha.51",
60
+ "@agimon-ai/doompi-telemetry": "0.0.1-alpha.51",
61
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.51",
62
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.52"
54
63
  },
55
64
  "devDependencies": {
56
65
  "@deepseek-ai/cordis": "4.0.1",
57
- "@earendil-works/pi-coding-agent": "0.84.2",
58
- "@earendil-works/pi-tui": "0.84.2",
59
- "@types/node": "26.1.1",
60
- "@vitest/coverage-v8": "4.1.10",
66
+ "@earendil-works/pi-coding-agent": "0.84.4",
67
+ "@earendil-works/pi-tui": "0.84.4",
68
+ "@types/node": "26.4.0",
69
+ "@vitest/coverage-v8": "4.1.11",
61
70
  "tsdown": "0.22.14",
62
- "typescript": "6.0.3",
63
- "vitest": "4.1.10",
64
- "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.25"
71
+ "typescript": "7.0.2",
72
+ "vitest": "4.1.11",
73
+ "@agimon-ai/vibe-lint-plugin-doom-extension": "0.0.1-alpha.49"
65
74
  },
66
75
  "peerDependencies": {
67
- "@earendil-works/pi-coding-agent": "0.84.2",
68
- "@earendil-works/pi-tui": "0.84.2"
76
+ "@earendil-works/pi-coding-agent": "0.84.4",
77
+ "@earendil-works/pi-tui": "0.84.4"
69
78
  },
70
79
  "peerDependenciesMeta": {
71
80
  "@earendil-works/pi-coding-agent": {
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: doompi-author-major-mode
3
+ description: Configure DoomPi default packages, layers, extensions, hook groups, and named major modes. Use when creating or editing ~/.pi/.doom/modes.yaml or a repository's .doom/modes.yaml, choosing a default mode, or diagnosing which behavior a mode activates.
4
+ ---
5
+
6
+ # Author DoomPi major modes
7
+
8
+ Use `modes.yaml` only for runtime composition. Major modes choose ordered layers, and layers choose
9
+ Pi extensions, extension packages, and hook groups. Domains configure plugin content and MCP access;
10
+ profiles configure personas and environment defaults.
11
+
12
+ ## Workflow
13
+
14
+ 1. Read both `~/.pi/.doom/modes.yaml` and the repository's `.doom/modes.yaml` when they exist.
15
+ 2. Decide whether the change belongs in the personal defaults or only in the current repository.
16
+ 3. Preserve unrelated definitions and authored array order.
17
+ 4. Put unconditional packages in `default.packages`, reusable behavior in `layers`, and selections
18
+ under `majorMode`.
19
+ 5. Write new modes as mappings with a useful `description` and an ordered `layers` array.
20
+ 6. Put package-owned settings under that package's `config` mapping. Do not add layer-level `config`
21
+ or the removed `targets` field.
22
+ 7. Check the result with `doompi --major-mode <name> --explain`. Use `doompi sync --check` when the
23
+ change affects installed packages or the synchronized matrix.
24
+
25
+ Relative local paths resolve from the repository root for repository configuration and from
26
+ `~/.pi/.doom` for personal configuration. A repository definition replaces a personal definition
27
+ of the same name as a whole value, so never assume nested fields merge.
28
+
29
+ Read [references/modes-contract.md](references/modes-contract.md) before editing package entries,
30
+ local extension paths, hook-only layers, inherited definitions, or tombstones.
@@ -0,0 +1,152 @@
1
+ # DoomPi `modes.yaml` authoring contract
2
+
3
+ ## Purpose and sources
4
+
5
+ `modes.yaml` controls the behavior axis of a DoomPi session:
6
+
7
+ - `default.packages` is the unconditional package baseline.
8
+ - `layers` are reusable ordered bundles of extensions, packages, and hook groups.
9
+ - `majorMode` maps a session mode to an ordered list of layer names.
10
+ - `defaultMajorMode` selects the starting mode when no flag or inherited environment value wins.
11
+
12
+ DoomPi reads `~/.pi/.doom/modes.yaml` first and `<repoRoot>/.doom/modes.yaml` second. Either file may
13
+ stand alone. A missing or empty file contributes nothing.
14
+
15
+ Named `layers` and `majorMode` entries merge by name. A repository entry replaces the complete
16
+ personal entry with the same name. Setting an inherited layer or mode to `null` removes it. The
17
+ top-level `default` block is also replaced as a whole when the repository declares it. An omitted
18
+ repository `default` keeps the personal definition. `defaultMajorMode` uses the last declared value.
19
+
20
+ Relative paths retain the source that declared them:
21
+
22
+ - Personal paths resolve from `~/.pi/.doom`.
23
+ - Repository paths resolve from the repository root, not from `<repoRoot>/.doom`.
24
+
25
+ ## Supported shape
26
+
27
+ ```yaml
28
+ # Optional package baseline loaded before every selected named layer.
29
+ default:
30
+ packages:
31
+ - '@agimon-ai/doompi-help'
32
+ - name: '@scope/optional-extension'
33
+ optional: true
34
+ config:
35
+ policy: concise
36
+
37
+ layers:
38
+ local-review:
39
+ extensions:
40
+ - './extensions/review.ts'
41
+ packages:
42
+ - '@scope/review-extension'
43
+ hookGroups: [repository]
44
+ team:
45
+ packages:
46
+ - name: '@agimon-ai/doompi-team'
47
+ config:
48
+ models:
49
+ - model: provider/model-id
50
+ thinking: high
51
+
52
+ defaultMajorMode: copilot
53
+ majorMode:
54
+ minimal:
55
+ description: Small sessions with delegation only.
56
+ layers: [team]
57
+ copilot:
58
+ description: General coding with delegation and local review policy.
59
+ layers: [team, local-review]
60
+ ```
61
+
62
+ Treat the `default` example as a shape, not as the current distribution package list. Preserve the
63
+ baseline emitted by `doompi init` unless the user explicitly wants to replace it. If neither source
64
+ declares `default`, DoomPi adds no fallback feature package set. `default.packages: []` deliberately
65
+ selects an empty baseline. Fixed core packages remain active independently.
66
+
67
+ ### `default`
68
+
69
+ `default` accepts exactly one field, `packages`, and `packages` must be an array. It does not accept
70
+ `extensions` or `hookGroups`. Its packages activate before packages from selected named layers.
71
+
72
+ ### `layers`
73
+
74
+ Each layer is a mapping with these supported fields:
75
+
76
+ - `extensions`: an array of non-empty strings. A bare name selects a DoomPi built-in extension.
77
+ A `./` or `../` path, or an absolute path, selects a Pi-compatible script or extension directory.
78
+ - `packages`: an array of package strings or package mappings. A normal npm package activates its
79
+ ordered `package.json` `pi.extensions` entries. An exported subpath can select one adapter.
80
+ Path-like packages are supported for local unpublished Pi packages.
81
+ - `hookGroups`: an array of hook group IDs contributed by the layer. Duplicate group IDs are
82
+ removed while retaining first-seen order.
83
+
84
+ A package mapping accepts only:
85
+
86
+ - `name`: required non-empty package specifier or local path.
87
+ - `optional`: optional boolean. A missing optional package is skipped and is not installed.
88
+ - `config`: optional mapping owned and interpreted by that package. Its contents stay opaque to the
89
+ major-mode parser.
90
+
91
+ Required npm packages missing from the active baseline or selected layers can be installed during
92
+ launch. `doompi sync` considers required packages from the baseline and every declared layer.
93
+ Optional packages and local paths are not installed automatically.
94
+
95
+ Package configuration belongs inside its package mapping. Layer-level `config` is rejected because
96
+ there is no unambiguous owner. The removed `targets` field is also rejected. Select a layer by naming
97
+ it from the appropriate major modes.
98
+
99
+ When hooks are disabled for a run, any selected layer with one or more `hookGroups` is suppressed as
100
+ a whole. Its extension and package entries are suppressed too. Keep unrelated packages out of a
101
+ hook-only layer if they must remain active when hooks are off.
102
+
103
+ ### `majorMode` and `defaultMajorMode`
104
+
105
+ New major modes use this mapping form:
106
+
107
+ ```yaml
108
+ majorMode:
109
+ review:
110
+ description: Focused review with repository policy and Team delegation.
111
+ layers: [team, local-review]
112
+ ```
113
+
114
+ `description` must be a non-empty string and `layers` must be an array of non-empty layer names.
115
+ Every referenced layer must survive source merging. Layer order and repeated entries are preserved.
116
+ The array-only compatibility form, such as `review: [team, local-review]`, is accepted and receives a
117
+ generated description, but new configuration should use the mapping form.
118
+
119
+ When `defaultMajorMode` is explicitly configured, it must name a surviving mode. If no source
120
+ declares it, DoomPi uses the compatible `copilot` fallback. Declare an explicit default when the
121
+ available modes do not include `copilot`.
122
+
123
+ ## Safe changes
124
+
125
+ - Add a package to an existing layer when it should follow that layer's activation and ownership.
126
+ - Add a new layer when the behavior should be reusable across multiple modes or independently
127
+ ordered.
128
+ - Add a new mode by composing existing layers, then add new layers only for missing behavior.
129
+ - Use a repository replacement when a shared personal definition is unsuitable for one repository.
130
+ - Use `null` only to remove an inherited named layer or named mode. Do not use it as a package entry
131
+ or as a partial-field deletion mechanism.
132
+ - Keep local paths within trusted, reviewable locations and confirm they resolve from the declaring
133
+ source's base directory.
134
+
135
+ ## Verification
136
+
137
+ Run a read-only resolution before launching:
138
+
139
+ ```sh
140
+ doompi --major-mode review --explain
141
+ ```
142
+
143
+ Confirm the reported major mode, ordered layers, hook groups, and resulting resources. If packages
144
+ or synchronized composition changed, check drift and missing packages without updating artifacts:
145
+
146
+ ```sh
147
+ doompi sync --check
148
+ ```
149
+
150
+ Run `doompi sync` only when the user intends to install packages or refresh synchronized artifacts.
151
+ For repository changes to the parser or templates themselves, run the affected Doom Config and
152
+ DoomPi lint, typecheck, build, and test targets.