@agimon-ai/doompi-loop 0.0.1-alpha.2 → 0.0.1-alpha.21

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 (84) hide show
  1. package/README.md +22 -15
  2. package/dist/adapters/pi/defaultLoopLauncher.cjs +2 -0
  3. package/dist/adapters/pi/defaultLoopLauncher.cjs.map +1 -0
  4. package/dist/adapters/pi/defaultLoopLauncher.d.cts +10 -0
  5. package/dist/adapters/pi/defaultLoopLauncher.d.cts.map +1 -0
  6. package/dist/adapters/pi/defaultLoopLauncher.d.mts +10 -0
  7. package/dist/adapters/pi/defaultLoopLauncher.d.mts.map +1 -0
  8. package/dist/adapters/pi/defaultLoopLauncher.mjs +2 -0
  9. package/dist/adapters/pi/defaultLoopLauncher.mjs.map +1 -0
  10. package/dist/adapters/pi/extension.cjs +2 -0
  11. package/dist/adapters/pi/extension.cjs.map +1 -0
  12. package/dist/{extension.d.cts → adapters/pi/extension.d.cts} +1 -1
  13. package/dist/adapters/pi/extension.d.cts.map +1 -0
  14. package/dist/{extension.d.mts → adapters/pi/extension.d.mts} +1 -1
  15. package/dist/adapters/pi/extension.d.mts.map +1 -0
  16. package/dist/adapters/pi/extension.mjs +2 -0
  17. package/dist/adapters/pi/extension.mjs.map +1 -0
  18. package/dist/adapters/pi/leader.cjs +2 -0
  19. package/dist/adapters/pi/leader.cjs.map +1 -0
  20. package/dist/adapters/pi/leader.mjs +2 -0
  21. package/dist/adapters/pi/leader.mjs.map +1 -0
  22. package/dist/adapters/pi/loopConstants.cjs +2 -0
  23. package/dist/adapters/pi/loopConstants.cjs.map +1 -0
  24. package/dist/adapters/pi/loopConstants.mjs +2 -0
  25. package/dist/adapters/pi/loopConstants.mjs.map +1 -0
  26. package/dist/commands/loopCommand.cjs +1 -1
  27. package/dist/commands/loopCommand.cjs.map +1 -1
  28. package/dist/commands/loopCommand.mjs +1 -1
  29. package/dist/commands/loopCommand.mjs.map +1 -1
  30. package/dist/extensions/pi.cjs +1 -2
  31. package/dist/extensions/pi.d.cts +1 -1
  32. package/dist/extensions/pi.d.mts +1 -1
  33. package/dist/extensions/pi.mjs +1 -2
  34. package/dist/index.cjs +1 -1
  35. package/dist/index.d.cts +5 -3
  36. package/dist/index.d.mts +5 -3
  37. package/dist/index.mjs +1 -1
  38. package/dist/schemas/loopCommands.cjs +2 -0
  39. package/dist/schemas/loopCommands.cjs.map +1 -0
  40. package/dist/schemas/loopCommands.mjs +2 -0
  41. package/dist/schemas/loopCommands.mjs.map +1 -0
  42. package/dist/services/loop/launcher.cjs +2 -0
  43. package/dist/services/loop/launcher.cjs.map +1 -0
  44. package/dist/services/loop/launcher.d.cts +7 -0
  45. package/dist/services/loop/launcher.d.cts.map +1 -0
  46. package/dist/services/loop/launcher.d.mts +7 -0
  47. package/dist/services/loop/launcher.d.mts.map +1 -0
  48. package/dist/services/loop/launcher.mjs +2 -0
  49. package/dist/services/loop/launcher.mjs.map +1 -0
  50. package/dist/tui/loopListOverlay.cjs +1 -1
  51. package/dist/tui/loopListOverlay.cjs.map +1 -1
  52. package/dist/tui/loopListOverlay.mjs +1 -1
  53. package/dist/tui/loopListOverlay.mjs.map +1 -1
  54. package/dist/tui/startLoopOverlay.cjs +1 -1
  55. package/dist/tui/startLoopOverlay.cjs.map +1 -1
  56. package/dist/tui/startLoopOverlay.mjs +1 -1
  57. package/dist/tui/startLoopOverlay.mjs.map +1 -1
  58. package/dist/{launcher.d.cts → types/loop.d.cts} +3 -5
  59. package/dist/types/loop.d.cts.map +1 -0
  60. package/dist/{launcher.d.mts → types/loop.d.mts} +3 -5
  61. package/dist/types/loop.d.mts.map +1 -0
  62. package/package.json +14 -7
  63. package/dist/constants.cjs +0 -2
  64. package/dist/constants.cjs.map +0 -1
  65. package/dist/constants.mjs +0 -2
  66. package/dist/constants.mjs.map +0 -1
  67. package/dist/extension.cjs +0 -2
  68. package/dist/extension.cjs.map +0 -1
  69. package/dist/extension.d.cts.map +0 -1
  70. package/dist/extension.d.mts.map +0 -1
  71. package/dist/extension.mjs +0 -2
  72. package/dist/extension.mjs.map +0 -1
  73. package/dist/extensions/pi.cjs.map +0 -1
  74. package/dist/extensions/pi.mjs.map +0 -1
  75. package/dist/launcher.cjs +0 -2
  76. package/dist/launcher.cjs.map +0 -1
  77. package/dist/launcher.d.cts.map +0 -1
  78. package/dist/launcher.d.mts.map +0 -1
  79. package/dist/launcher.mjs +0 -2
  80. package/dist/launcher.mjs.map +0 -1
  81. package/dist/leader.cjs +0 -2
  82. package/dist/leader.cjs.map +0 -1
  83. package/dist/leader.mjs +0 -2
  84. package/dist/leader.mjs.map +0 -1
package/README.md CHANGED
@@ -1,13 +1,18 @@
1
1
  # @agimon-ai/doompi-loop
2
2
 
3
- Generic multi-loop controller for Doompi.
3
+ **Run it now. Run it again later.**
4
4
 
5
- Runs a prompt on a schedule or until a condition holds. Loop is a minor mode: it toggles
6
- mid-session, stacks with the others, and reports itself on the shared `MODES` line.
5
+ This package runs a prompt immediately, then repeats it on a schedule. Several loops can
6
+ live in one Pi session, each with its own prompt and interval. Loop is a minor mode: it
7
+ turns on when work is scheduled, stacks with the other modes, and reports itself on the
8
+ shared `MODES` line.
7
9
 
8
- Part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi), a coding agent that loads
9
- only the skills and tools you name. This package is composed by Doompi and is not usually
10
- installed on its own.
10
+ The built-in Default loop asks for a prompt and interval. Other extensions can register
11
+ their own launchers beside it, so a domain can add a recurring job without forking the
12
+ scheduler.
13
+
14
+ This is part of [Doompi](https://www.npmjs.com/package/@agimon-ai/doompi). Most users get
15
+ it with the distribution.
11
16
 
12
17
  ## Install
13
18
 
@@ -17,21 +22,23 @@ npm install @agimon-ai/doompi-loop
17
22
 
18
23
  ## How it loads
19
24
 
20
- Doompi loads this unconditionally. It is core, not a layer, so it does not appear in
21
- `.doom/modes.yaml`: making it optional would only create broken configurations.
22
-
23
- Extension entry: `@agimon-ai/doompi-loop/extensions/pi`
25
+ Doompi loads Loop as core. It is not a major-mode layer and does not appear in
26
+ `.doom/modes.yaml`. The bare package name follows its Pi manifest and loads the Pi adapter.
24
27
 
25
28
  ## Leader
26
29
 
27
- `SPC l s` starts a loop, `SPC l l` lists and stops them, once the extension is loaded.
30
+ - `SPC l s` starts a loop.
31
+ - `SPC l l` lists active loops and lets you stop one.
32
+
33
+ Use Loop for recurring checks or prompts that belong to the current session. Use several
34
+ when the schedules are independent; there is no single-loop lock.
28
35
 
29
36
  ## Entry points
30
37
 
31
- | Import |
32
- | -------------------------------------- |
33
- | `@agimon-ai/doompi-loop` |
34
- | `@agimon-ai/doompi-loop/extensions/pi` |
38
+ | Import | Purpose |
39
+ | -------------------------------------- | ------------------------ |
40
+ | `@agimon-ai/doompi-loop` | Library API |
41
+ | `@agimon-ai/doompi-loop/extensions/pi` | Pi extension entry point |
35
42
 
36
43
  ## License
37
44
 
@@ -0,0 +1,2 @@
1
+ const e=require("../../services/loop/launcher.cjs"),t=require("./loopConstants.cjs"),n=3600;function r(e){let t=Number(e);if(!Number.isInteger(t))return`Interval must be a whole number of seconds, got ${e}.`;if(t<30||t>n)return`Interval must be between 30 and ${n} seconds.`}async function i(e,n){let i=(await e.editor(`Loop prompt`,``))?.trim();if(!i||n.aborted)return;let a=await e.input(`Loop interval in seconds`,`Default: 300s`);if(a===void 0||n.aborted)return;let o=a.trim()||`300`,s=r(o);if(s){e.notify(s,t.NOTIFY_WARNING_LEVEL);return}return{prompt:i,intervalSeconds:Number(o)}}function a(e){let t=e.replaceAll(/\s+/gu,` `).trim();return t.length>80?`${t.slice(0,79)}…`:t}function o(n){let r=new Set,o=e=>{if(!e.stopped){if(!e.context.isIdle()){r.add(e);return}r.delete(e);try{n.sendUserMessage(e.prompt)}catch(n){e.context.ui.notify(`Default loop pass could not start: ${n instanceof Error?n.message:String(n)}`,t.NOTIFY_WARNING_LEVEL)}}};return n.on(`agent_settled`,()=>{let e=r.values().next().value;e&&o(e)}),{register(n){return e.registerLoopLauncher(n.sessionManager.getSessionId(),{id:`doompi.default`,source:t.PACKAGE_SOURCE,label:`Default loop`,description:`Run your own prompt in this session on an interval`,async launch({instanceId:e,signal:t}){let s=await i(n.ui,t);if(!s||t.aborted)return;let c={...s,context:n,stopped:!1};return c.timer=setInterval(()=>o(c),c.intervalSeconds*1e3),c.timer.unref?.(),o(c),{instanceId:e,label:`Default loop`,detail:`every ${c.intervalSeconds}s · ${a(c.prompt)}`,stop(){c.stopped||(c.stopped=!0,c.timer&&clearInterval(c.timer),c.timer=void 0,r.delete(c))}}}})}}}exports.createDefaultLoopLauncher=o;
2
+ //# sourceMappingURL=defaultLoopLauncher.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultLoopLauncher.cjs","names":["NOTIFY_WARNING_LEVEL","registerLoopLauncher","PACKAGE_SOURCE"],"sources":["../../../src/adapters/pi/defaultLoopLauncher.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext, ExtensionUIContext } from '@earendil-works/pi-coding-agent';\nimport { registerLoopLauncher } from '../../services/loop/launcher.ts';\nimport type { LoopLauncherRegistration, StoppableLoop } from '../../types/loop.ts';\nimport { NOTIFY_WARNING_LEVEL, PACKAGE_SOURCE } from './loopConstants.ts';\n\nconst DEFAULT_LAUNCHER_ID = 'doompi.default';\nconst DEFAULT_INTERVAL_SECONDS = 300;\nconst MIN_INTERVAL_SECONDS = 30;\nconst MAX_INTERVAL_SECONDS = 3600;\nconst MILLISECONDS_PER_SECOND = 1000;\nconst PROMPT_PREVIEW_LENGTH = 80;\n\ninterface PreparedDefaultLoop {\n readonly prompt: string;\n readonly intervalSeconds: number;\n}\n\ninterface ActiveDefaultLoop extends PreparedDefaultLoop {\n readonly context: ExtensionContext;\n timer?: ReturnType<typeof setInterval>;\n stopped: boolean;\n}\n\nexport interface DefaultLoopLauncher {\n register(ctx: ExtensionContext): LoopLauncherRegistration;\n}\n\nfunction intervalError(raw: string): string | undefined {\n const seconds = Number(raw);\n if (!Number.isInteger(seconds)) return `Interval must be a whole number of seconds, got ${raw}.`;\n if (seconds < MIN_INTERVAL_SECONDS || seconds > MAX_INTERVAL_SECONDS) {\n return `Interval must be between ${MIN_INTERVAL_SECONDS} and ${MAX_INTERVAL_SECONDS} seconds.`;\n }\n return undefined;\n}\n\nasync function prepareDefaultLoop(\n ui: ExtensionUIContext,\n signal: AbortSignal,\n): Promise<PreparedDefaultLoop | undefined> {\n const prompt = (await ui.editor('Loop prompt', ''))?.trim();\n if (!prompt || signal.aborted) return undefined;\n\n const rawInterval = await ui.input('Loop interval in seconds', `Default: ${DEFAULT_INTERVAL_SECONDS}s`);\n if (rawInterval === undefined || signal.aborted) return undefined;\n const normalizedInterval = rawInterval.trim() || String(DEFAULT_INTERVAL_SECONDS);\n const error = intervalError(normalizedInterval);\n if (error) {\n ui.notify(error, NOTIFY_WARNING_LEVEL);\n return undefined;\n }\n\n return { prompt, intervalSeconds: Number(normalizedInterval) };\n}\n\nfunction promptPreview(prompt: string): string {\n const oneLine = prompt.replaceAll(/\\s+/gu, ' ').trim();\n return oneLine.length > PROMPT_PREVIEW_LENGTH ? `${oneLine.slice(0, PROMPT_PREVIEW_LENGTH - 1)}…` : oneLine;\n}\n\nexport function createDefaultLoopLauncher(pi: ExtensionAPI): DefaultLoopLauncher {\n const pending = new Set<ActiveDefaultLoop>();\n\n const requestPass = (loop: ActiveDefaultLoop): void => {\n if (loop.stopped) return;\n if (!loop.context.isIdle()) {\n pending.add(loop);\n return;\n }\n\n pending.delete(loop);\n try {\n pi.sendUserMessage(loop.prompt);\n } catch (error) {\n loop.context.ui.notify(\n `Default loop pass could not start: ${error instanceof Error ? error.message : String(error)}`,\n NOTIFY_WARNING_LEVEL,\n );\n }\n };\n\n pi.on('agent_settled', () => {\n const next = pending.values().next().value;\n if (next) requestPass(next);\n });\n\n return {\n register(ctx) {\n return registerLoopLauncher(ctx.sessionManager.getSessionId(), {\n id: DEFAULT_LAUNCHER_ID,\n source: PACKAGE_SOURCE,\n label: 'Default loop',\n description: 'Run your own prompt in this session on an interval',\n async launch({ instanceId, signal }) {\n const prepared = await prepareDefaultLoop(ctx.ui, signal);\n if (!prepared || signal.aborted) return undefined;\n\n const loop: ActiveDefaultLoop = { ...prepared, context: ctx, stopped: false };\n loop.timer = setInterval(() => requestPass(loop), loop.intervalSeconds * MILLISECONDS_PER_SECOND);\n loop.timer.unref?.();\n requestPass(loop);\n\n const handle: StoppableLoop = {\n instanceId,\n label: 'Default loop',\n detail: `every ${loop.intervalSeconds}s · ${promptPreview(loop.prompt)}`,\n stop() {\n if (loop.stopped) return;\n loop.stopped = true;\n if (loop.timer) clearInterval(loop.timer);\n loop.timer = undefined;\n pending.delete(loop);\n },\n };\n return handle;\n },\n });\n },\n };\n}\n"],"mappings":"qFAQM,EAAuB,KAmB7B,SAAS,EAAc,EAAiC,CACtD,IAAM,EAAU,OAAO,CAAG,EAC1B,GAAI,CAAC,OAAO,UAAU,CAAO,EAAG,MAAO,mDAAmD,EAAI,GAC9F,GAAI,EAAU,IAAwB,EAAU,EAC9C,MAAO,mCAAwD,EAAqB,UAGxF,CAEA,eAAe,EACb,EACA,EAC0C,CAC1C,IAAM,GAAU,MAAM,EAAG,OAAO,cAAe,EAAE,EAAA,EAAI,KAAK,EAC1D,GAAI,CAAC,GAAU,EAAO,QAAS,OAE/B,IAAM,EAAc,MAAM,EAAG,MAAM,2BAA4B,eAAuC,EACtG,GAAI,IAAgB,IAAA,IAAa,EAAO,QAAS,OACjD,IAAM,EAAqB,EAAY,KAAK,GAAK,MAC3C,EAAQ,EAAc,CAAkB,EAC9C,GAAI,EAAO,CACT,EAAG,OAAO,EAAOA,EAAAA,oBAAoB,EACrC,MACF,CAEA,MAAO,CAAE,SAAQ,gBAAiB,OAAO,CAAkB,CAAE,CAC/D,CAEA,SAAS,EAAc,EAAwB,CAC7C,IAAM,EAAU,EAAO,WAAW,QAAS,GAAG,CAAC,CAAC,KAAK,EACrD,OAAO,EAAQ,OAAS,GAAwB,GAAG,EAAQ,MAAM,EAAG,EAAyB,EAAE,GAAK,CACtG,CAEA,SAAgB,EAA0B,EAAuC,CAC/E,IAAM,EAAU,IAAI,IAEd,EAAe,GAAkC,CACjD,MAAK,QACT,IAAI,CAAC,EAAK,QAAQ,OAAO,EAAG,CAC1B,EAAQ,IAAI,CAAI,EAChB,MACF,CAEA,EAAQ,OAAO,CAAI,EACnB,GAAI,CACF,EAAG,gBAAgB,EAAK,MAAM,CAChC,OAAS,EAAO,CACd,EAAK,QAAQ,GAAG,OACd,sCAAsC,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,IAC3FA,EAAAA,oBACF,CACF,CAVA,CAWF,EAOA,OALA,EAAG,GAAG,oBAAuB,CAC3B,IAAM,EAAO,EAAQ,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,MACjC,GAAM,EAAY,CAAI,CAC5B,CAAC,EAEM,CACL,SAAS,EAAK,CACZ,OAAOC,EAAAA,qBAAqB,EAAI,eAAe,aAAa,EAAG,CAC7D,GAAI,iBACJ,OAAQC,EAAAA,eACR,MAAO,eACP,YAAa,qDACb,MAAM,OAAO,CAAE,aAAY,UAAU,CACnC,IAAM,EAAW,MAAM,EAAmB,EAAI,GAAI,CAAM,EACxD,GAAI,CAAC,GAAY,EAAO,QAAS,OAEjC,IAAM,EAA0B,CAAE,GAAG,EAAU,QAAS,EAAK,QAAS,EAAM,EAiB5E,MAhBA,GAAK,MAAQ,gBAAkB,EAAY,CAAI,EAAG,EAAK,gBAAkB,GAAuB,EAChG,EAAK,MAAM,QAAQ,EACnB,EAAY,CAAI,EAcT,CAXL,aACA,MAAO,eACP,OAAQ,SAAS,EAAK,gBAAgB,MAAM,EAAc,EAAK,MAAM,IACrE,MAAO,CACD,EAAK,UACT,EAAK,QAAU,GACX,EAAK,OAAO,cAAc,EAAK,KAAK,EACxC,EAAK,MAAQ,IAAA,GACb,EAAQ,OAAO,CAAI,EACrB,CAEU,CACd,CACF,CAAC,CACH,CACF,CACF"}
@@ -0,0 +1,10 @@
1
+ import { LoopLauncherRegistration } from "../../types/loop.cjs";
2
+ import { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
3
+ //#region src/adapters/pi/defaultLoopLauncher.d.ts
4
+ interface DefaultLoopLauncher {
5
+ register(ctx: ExtensionContext): LoopLauncherRegistration;
6
+ }
7
+ declare function createDefaultLoopLauncher(pi: ExtensionAPI): DefaultLoopLauncher;
8
+ //#endregion
9
+ export { DefaultLoopLauncher, createDefaultLoopLauncher };
10
+ //# sourceMappingURL=defaultLoopLauncher.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultLoopLauncher.d.cts","names":[],"sources":["../../../src/adapters/pi/defaultLoopLauncher.ts"],"mappings":";;;UAuBiB;EACf,SAAS,KAAK,mBAAmB;;iBAoCnB,0BAA0B,IAAI,eAAe"}
@@ -0,0 +1,10 @@
1
+ import { LoopLauncherRegistration } from "../../types/loop.mjs";
2
+ import { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
3
+ //#region src/adapters/pi/defaultLoopLauncher.d.ts
4
+ interface DefaultLoopLauncher {
5
+ register(ctx: ExtensionContext): LoopLauncherRegistration;
6
+ }
7
+ declare function createDefaultLoopLauncher(pi: ExtensionAPI): DefaultLoopLauncher;
8
+ //#endregion
9
+ export { DefaultLoopLauncher, createDefaultLoopLauncher };
10
+ //# sourceMappingURL=defaultLoopLauncher.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultLoopLauncher.d.mts","names":[],"sources":["../../../src/adapters/pi/defaultLoopLauncher.ts"],"mappings":";;;UAuBiB;EACf,SAAS,KAAK,mBAAmB;;iBAoCnB,0BAA0B,IAAI,eAAe"}
@@ -0,0 +1,2 @@
1
+ import{registerLoopLauncher as e}from"../../services/loop/launcher.mjs";import{NOTIFY_WARNING_LEVEL as t,PACKAGE_SOURCE as n}from"./loopConstants.mjs";const r=3600;function i(e){let t=Number(e);if(!Number.isInteger(t))return`Interval must be a whole number of seconds, got ${e}.`;if(t<30||t>r)return`Interval must be between 30 and ${r} seconds.`}async function a(e,n){let r=(await e.editor(`Loop prompt`,``))?.trim();if(!r||n.aborted)return;let a=await e.input(`Loop interval in seconds`,`Default: 300s`);if(a===void 0||n.aborted)return;let o=a.trim()||`300`,s=i(o);if(s){e.notify(s,t);return}return{prompt:r,intervalSeconds:Number(o)}}function o(e){let t=e.replaceAll(/\s+/gu,` `).trim();return t.length>80?`${t.slice(0,79)}…`:t}function s(r){let i=new Set,s=e=>{if(!e.stopped){if(!e.context.isIdle()){i.add(e);return}i.delete(e);try{r.sendUserMessage(e.prompt)}catch(n){e.context.ui.notify(`Default loop pass could not start: ${n instanceof Error?n.message:String(n)}`,t)}}};return r.on(`agent_settled`,()=>{let e=i.values().next().value;e&&s(e)}),{register(t){return e(t.sessionManager.getSessionId(),{id:`doompi.default`,source:n,label:`Default loop`,description:`Run your own prompt in this session on an interval`,async launch({instanceId:e,signal:n}){let r=await a(t.ui,n);if(!r||n.aborted)return;let c={...r,context:t,stopped:!1};return c.timer=setInterval(()=>s(c),c.intervalSeconds*1e3),c.timer.unref?.(),s(c),{instanceId:e,label:`Default loop`,detail:`every ${c.intervalSeconds}s · ${o(c.prompt)}`,stop(){c.stopped||(c.stopped=!0,c.timer&&clearInterval(c.timer),c.timer=void 0,i.delete(c))}}}})}}}export{s as createDefaultLoopLauncher};
2
+ //# sourceMappingURL=defaultLoopLauncher.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaultLoopLauncher.mjs","names":[],"sources":["../../../src/adapters/pi/defaultLoopLauncher.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext, ExtensionUIContext } from '@earendil-works/pi-coding-agent';\nimport { registerLoopLauncher } from '../../services/loop/launcher.ts';\nimport type { LoopLauncherRegistration, StoppableLoop } from '../../types/loop.ts';\nimport { NOTIFY_WARNING_LEVEL, PACKAGE_SOURCE } from './loopConstants.ts';\n\nconst DEFAULT_LAUNCHER_ID = 'doompi.default';\nconst DEFAULT_INTERVAL_SECONDS = 300;\nconst MIN_INTERVAL_SECONDS = 30;\nconst MAX_INTERVAL_SECONDS = 3600;\nconst MILLISECONDS_PER_SECOND = 1000;\nconst PROMPT_PREVIEW_LENGTH = 80;\n\ninterface PreparedDefaultLoop {\n readonly prompt: string;\n readonly intervalSeconds: number;\n}\n\ninterface ActiveDefaultLoop extends PreparedDefaultLoop {\n readonly context: ExtensionContext;\n timer?: ReturnType<typeof setInterval>;\n stopped: boolean;\n}\n\nexport interface DefaultLoopLauncher {\n register(ctx: ExtensionContext): LoopLauncherRegistration;\n}\n\nfunction intervalError(raw: string): string | undefined {\n const seconds = Number(raw);\n if (!Number.isInteger(seconds)) return `Interval must be a whole number of seconds, got ${raw}.`;\n if (seconds < MIN_INTERVAL_SECONDS || seconds > MAX_INTERVAL_SECONDS) {\n return `Interval must be between ${MIN_INTERVAL_SECONDS} and ${MAX_INTERVAL_SECONDS} seconds.`;\n }\n return undefined;\n}\n\nasync function prepareDefaultLoop(\n ui: ExtensionUIContext,\n signal: AbortSignal,\n): Promise<PreparedDefaultLoop | undefined> {\n const prompt = (await ui.editor('Loop prompt', ''))?.trim();\n if (!prompt || signal.aborted) return undefined;\n\n const rawInterval = await ui.input('Loop interval in seconds', `Default: ${DEFAULT_INTERVAL_SECONDS}s`);\n if (rawInterval === undefined || signal.aborted) return undefined;\n const normalizedInterval = rawInterval.trim() || String(DEFAULT_INTERVAL_SECONDS);\n const error = intervalError(normalizedInterval);\n if (error) {\n ui.notify(error, NOTIFY_WARNING_LEVEL);\n return undefined;\n }\n\n return { prompt, intervalSeconds: Number(normalizedInterval) };\n}\n\nfunction promptPreview(prompt: string): string {\n const oneLine = prompt.replaceAll(/\\s+/gu, ' ').trim();\n return oneLine.length > PROMPT_PREVIEW_LENGTH ? `${oneLine.slice(0, PROMPT_PREVIEW_LENGTH - 1)}…` : oneLine;\n}\n\nexport function createDefaultLoopLauncher(pi: ExtensionAPI): DefaultLoopLauncher {\n const pending = new Set<ActiveDefaultLoop>();\n\n const requestPass = (loop: ActiveDefaultLoop): void => {\n if (loop.stopped) return;\n if (!loop.context.isIdle()) {\n pending.add(loop);\n return;\n }\n\n pending.delete(loop);\n try {\n pi.sendUserMessage(loop.prompt);\n } catch (error) {\n loop.context.ui.notify(\n `Default loop pass could not start: ${error instanceof Error ? error.message : String(error)}`,\n NOTIFY_WARNING_LEVEL,\n );\n }\n };\n\n pi.on('agent_settled', () => {\n const next = pending.values().next().value;\n if (next) requestPass(next);\n });\n\n return {\n register(ctx) {\n return registerLoopLauncher(ctx.sessionManager.getSessionId(), {\n id: DEFAULT_LAUNCHER_ID,\n source: PACKAGE_SOURCE,\n label: 'Default loop',\n description: 'Run your own prompt in this session on an interval',\n async launch({ instanceId, signal }) {\n const prepared = await prepareDefaultLoop(ctx.ui, signal);\n if (!prepared || signal.aborted) return undefined;\n\n const loop: ActiveDefaultLoop = { ...prepared, context: ctx, stopped: false };\n loop.timer = setInterval(() => requestPass(loop), loop.intervalSeconds * MILLISECONDS_PER_SECOND);\n loop.timer.unref?.();\n requestPass(loop);\n\n const handle: StoppableLoop = {\n instanceId,\n label: 'Default loop',\n detail: `every ${loop.intervalSeconds}s · ${promptPreview(loop.prompt)}`,\n stop() {\n if (loop.stopped) return;\n loop.stopped = true;\n if (loop.timer) clearInterval(loop.timer);\n loop.timer = undefined;\n pending.delete(loop);\n },\n };\n return handle;\n },\n });\n },\n };\n}\n"],"mappings":"uJAKA,MAGM,EAAuB,KAmB7B,SAAS,EAAc,EAAiC,CACtD,IAAM,EAAU,OAAO,CAAG,EAC1B,GAAI,CAAC,OAAO,UAAU,CAAO,EAAG,MAAO,mDAAmD,EAAI,GAC9F,GAAI,EAAU,IAAwB,EAAU,EAC9C,MAAO,mCAAwD,EAAqB,UAGxF,CAEA,eAAe,EACb,EACA,EAC0C,CAC1C,IAAM,GAAU,MAAM,EAAG,OAAO,cAAe,EAAE,EAAA,EAAI,KAAK,EAC1D,GAAI,CAAC,GAAU,EAAO,QAAS,OAE/B,IAAM,EAAc,MAAM,EAAG,MAAM,2BAA4B,eAAuC,EACtG,GAAI,IAAgB,IAAA,IAAa,EAAO,QAAS,OACjD,IAAM,EAAqB,EAAY,KAAK,GAAK,MAC3C,EAAQ,EAAc,CAAkB,EAC9C,GAAI,EAAO,CACT,EAAG,OAAO,EAAO,CAAoB,EACrC,MACF,CAEA,MAAO,CAAE,SAAQ,gBAAiB,OAAO,CAAkB,CAAE,CAC/D,CAEA,SAAS,EAAc,EAAwB,CAC7C,IAAM,EAAU,EAAO,WAAW,QAAS,GAAG,CAAC,CAAC,KAAK,EACrD,OAAO,EAAQ,OAAS,GAAwB,GAAG,EAAQ,MAAM,EAAG,EAAyB,EAAE,GAAK,CACtG,CAEA,SAAgB,EAA0B,EAAuC,CAC/E,IAAM,EAAU,IAAI,IAEd,EAAe,GAAkC,CACjD,MAAK,QACT,IAAI,CAAC,EAAK,QAAQ,OAAO,EAAG,CAC1B,EAAQ,IAAI,CAAI,EAChB,MACF,CAEA,EAAQ,OAAO,CAAI,EACnB,GAAI,CACF,EAAG,gBAAgB,EAAK,MAAM,CAChC,OAAS,EAAO,CACd,EAAK,QAAQ,GAAG,OACd,sCAAsC,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,IAC3F,CACF,CACF,CAVA,CAWF,EAOA,OALA,EAAG,GAAG,oBAAuB,CAC3B,IAAM,EAAO,EAAQ,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,MACjC,GAAM,EAAY,CAAI,CAC5B,CAAC,EAEM,CACL,SAAS,EAAK,CACZ,OAAO,EAAqB,EAAI,eAAe,aAAa,EAAG,CAC7D,GAAI,iBACJ,OAAQ,EACR,MAAO,eACP,YAAa,qDACb,MAAM,OAAO,CAAE,aAAY,UAAU,CACnC,IAAM,EAAW,MAAM,EAAmB,EAAI,GAAI,CAAM,EACxD,GAAI,CAAC,GAAY,EAAO,QAAS,OAEjC,IAAM,EAA0B,CAAE,GAAG,EAAU,QAAS,EAAK,QAAS,EAAM,EAiB5E,MAhBA,GAAK,MAAQ,gBAAkB,EAAY,CAAI,EAAG,EAAK,gBAAkB,GAAuB,EAChG,EAAK,MAAM,QAAQ,EACnB,EAAY,CAAI,EAcT,CAXL,aACA,MAAO,eACP,OAAQ,SAAS,EAAK,gBAAgB,MAAM,EAAc,EAAK,MAAM,IACrE,MAAO,CACD,EAAK,UACT,EAAK,QAAU,GACX,EAAK,OAAO,cAAc,EAAK,KAAK,EACxC,EAAK,MAAQ,IAAA,GACb,EAAQ,OAAO,CAAI,EACrB,CAEU,CACd,CACF,CAAC,CACH,CACF,CACF"}
@@ -0,0 +1,2 @@
1
+ const e=require("../../services/loop/launcher.cjs"),t=require("./loopConstants.cjs"),n=require("./defaultLoopLauncher.cjs"),r=require("../../commands/loopCommand.cjs"),i=require("../../tui/loopListOverlay.cjs"),a=require("../../tui/startLoopOverlay.cjs"),o=require("./leader.cjs");let s=require("@agimon-ai/doompi-extension-contracts/mode");const c=Symbol.for(`@agimon-ai/doompi-loop/installed-hosts`),l=`start`,u=`stop`,d=`Pi session replaced.`,f=`Pi session shut down.`;function p(e,n){let r=n.length>0;return{activation:r?`active`:`inactive`,condition:n.some(({state:e})=>e===`starting`||e===`stopping`)?`queued`:`ready`,...r?{detail:`${n.length} active`,color:t.ACTIVE_MODE_COLOR}:{},actions:[...e.length>0?[{id:l,enabled:!0}]:[{id:l,enabled:!1,disabledReason:`No loop launchers are registered.`}],...r?[{id:u,enabled:!0}]:[{id:u,enabled:!1,disabledReason:`No loops are active.`}]]}}function m(e){let t=globalThis,n=t[c]??new WeakSet;return t[c]=n,!n.has(e)&&(n.add(e),!0)}function h(c){if(!m(c))return;let h,g,_,v,y=e.createLoopLauncherClient(()=>h),b=n.createDefaultLoopLauncher(c),x=(0,s.registerMinorModeOwner)(c,{descriptor:{source:t.PACKAGE_SOURCE,id:t.MODE_STATUS_ID,label:`Loop`,description:`Session-scoped recurring prompt loops.`,order:60,actions:[{id:l,label:`Start`,description:`Start a loop with a registered launcher.`,contexts:[`tui`,`headless`],parameters:[{name:`launcherId`,label:`Launcher`,kind:`string`,required:!0,minLength:1},{name:`instanceId`,label:`Instance ID`,kind:`string`,required:!1,minLength:1}]},{id:u,label:`Stop`,description:`Stop one active loop instance.`,contexts:[`tui`,`headless`],parameters:[{name:`instanceId`,label:`Instance ID`,kind:`string`,required:!0,minLength:1},{name:`reason`,label:`Reason`,kind:`string`,required:!1,minLength:1}]}]},initialState:p([],[]),async handleAction(e,t){if(e===l){let e=await y.launch(t.launcherId,t.instanceId?{instanceId:t.instanceId}:{});return{message:e?`Loop '${e.instanceId}' started.`:`Loop launch was cancelled.`}}if(e===u)return{message:await y.stop(t.instanceId,t.reason??`Stopped through minor_mode.`)?`Loop stopped.`:`Loop instance was not active.`};throw Error(`Unknown loop mode action: ${e}`)}});o.registerLeaderContribution(c);let S=()=>{let e=y.listLaunchers(),n=y.listInstances(),r=n.length;g?.ui?.setStatus(t.STATUS_KEY,r?`loops: ${r}`:void 0),x.publish(p(e,n))};r.registerCommands(c,{async start(e){let n=y.listLaunchers();if(!n.length){e.ui.notify(`No loop launchers are registered for this session.`,t.NOTIFY_INFO_LEVEL);return}let r=await a.openStartLoopOverlay(e,n);if(r)try{let n=await y.launch(r);n&&e.ui.notify(`${n.label??n.launcherLabel} started.`,t.NOTIFY_INFO_LEVEL)}catch(t){e.ui.notify(`Loop could not start: ${t instanceof Error?t.message:String(t)}`,`error`)}},async list(e){await i.openLoopListOverlay(e,y)}}),c.on(`session_start`,async(e,n)=>{_?.(),_=void 0,await y.stopAll(d),await v?.dispose(d),v=void 0,g?.ui?.setStatus(t.STATUS_KEY,void 0),g=n,h=n.sessionManager.getSessionId(),v=b.register(n),_=y.subscribe(S),S()}),c.on(`session_shutdown`,async()=>{await y.stopAll(f),await v?.dispose(f),v=void 0,_?.(),_=void 0,g?.ui?.setStatus(t.STATUS_KEY,void 0),g=void 0,h=void 0,x.publish(p([],[]))})}exports.registerLoopExtension=h;
2
+ //# sourceMappingURL=extension.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.cjs","names":["ACTIVE_MODE_COLOR","createLoopLauncherClient","createDefaultLoopLauncher","registerMinorModeOwner","PACKAGE_SOURCE","MODE_STATUS_ID","STATUS_KEY","NOTIFY_INFO_LEVEL","openStartLoopOverlay","openLoopListOverlay"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { type MinorModeState, registerMinorModeOwner } from '@agimon-ai/doompi-extension-contracts/mode';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommands } from '../../commands/loopCommand.ts';\nimport { createLoopLauncherClient } from '../../services/loop/launcher.ts';\nimport { openLoopListOverlay } from '../../tui/loopListOverlay.ts';\nimport { openStartLoopOverlay } from '../../tui/startLoopOverlay.ts';\nimport type { LoopInstanceSnapshot, LoopLauncherRegistration, LoopLauncherSummary } from '../../types/loop.ts';\nimport { createDefaultLoopLauncher } from './defaultLoopLauncher.ts';\nimport { registerLeaderContribution } from './leader.ts';\nimport {\n ACTIVE_MODE_COLOR,\n LOOPS_GROUP_ORDER,\n MODE_STATUS_ID,\n NOTIFY_INFO_LEVEL,\n PACKAGE_SOURCE,\n STATUS_KEY,\n} from './loopConstants.ts';\n\nconst INSTALLATIONS_KEY = Symbol.for('@agimon-ai/doompi-loop/installed-hosts');\nconst MODE_ACTION_START = 'start';\nconst MODE_ACTION_STOP = 'stop';\nconst SESSION_REPLACED_REASON = 'Pi session replaced.';\nconst SESSION_SHUTDOWN_REASON = 'Pi session shut down.';\n\ntype InstallationRegistry = typeof globalThis & {\n [INSTALLATIONS_KEY]?: WeakSet<object>;\n};\n\nfunction loopModeState(\n launchers: readonly LoopLauncherSummary[],\n instances: readonly LoopInstanceSnapshot[],\n): MinorModeState {\n const active = instances.length > 0;\n return {\n activation: active ? 'active' : 'inactive',\n condition: instances.some(({ state }) => state === 'starting' || state === 'stopping') ? 'queued' : 'ready',\n ...(active ? { detail: `${instances.length} active`, color: ACTIVE_MODE_COLOR } : {}),\n actions: [\n ...(launchers.length > 0\n ? [{ id: MODE_ACTION_START, enabled: true } as const]\n : [{ id: MODE_ACTION_START, enabled: false, disabledReason: 'No loop launchers are registered.' } as const]),\n ...(active\n ? [{ id: MODE_ACTION_STOP, enabled: true } as const]\n : [{ id: MODE_ACTION_STOP, enabled: false, disabledReason: 'No loops are active.' } as const]),\n ],\n };\n}\n\nfunction claimHostInstallation(pi: ExtensionAPI): boolean {\n const registry = globalThis as InstallationRegistry;\n const installedHosts = registry[INSTALLATIONS_KEY] ?? new WeakSet<object>();\n registry[INSTALLATIONS_KEY] = installedHosts;\n if (installedHosts.has(pi)) return false;\n installedHosts.add(pi);\n return true;\n}\n\nexport function registerLoopExtension(pi: ExtensionAPI): void {\n if (!claimHostInstallation(pi)) return;\n let sessionId: string | undefined;\n let context: ExtensionContext | undefined;\n let unsubscribe: (() => void) | undefined;\n let defaultRegistration: LoopLauncherRegistration | undefined;\n const client = createLoopLauncherClient(() => sessionId);\n const defaultLauncher = createDefaultLoopLauncher(pi);\n const mode = registerMinorModeOwner(pi, {\n descriptor: {\n source: PACKAGE_SOURCE,\n id: MODE_STATUS_ID,\n label: 'Loop',\n description: 'Session-scoped recurring prompt loops.',\n order: LOOPS_GROUP_ORDER,\n actions: [\n {\n id: MODE_ACTION_START,\n label: 'Start',\n description: 'Start a loop with a registered launcher.',\n contexts: ['tui', 'headless'],\n parameters: [\n { name: 'launcherId', label: 'Launcher', kind: 'string', required: true, minLength: 1 },\n { name: 'instanceId', label: 'Instance ID', kind: 'string', required: false, minLength: 1 },\n ],\n },\n {\n id: MODE_ACTION_STOP,\n label: 'Stop',\n description: 'Stop one active loop instance.',\n contexts: ['tui', 'headless'],\n parameters: [\n { name: 'instanceId', label: 'Instance ID', kind: 'string', required: true, minLength: 1 },\n { name: 'reason', label: 'Reason', kind: 'string', required: false, minLength: 1 },\n ],\n },\n ],\n },\n initialState: loopModeState([], []),\n async handleAction(actionId, argumentsValue) {\n if (actionId === MODE_ACTION_START) {\n const instance = await client.launch(\n argumentsValue.launcherId as string,\n argumentsValue.instanceId ? { instanceId: argumentsValue.instanceId as string } : {},\n );\n return { message: instance ? `Loop '${instance.instanceId}' started.` : 'Loop launch was cancelled.' };\n }\n if (actionId === MODE_ACTION_STOP) {\n const stopped = await client.stop(\n argumentsValue.instanceId as string,\n (argumentsValue.reason as string | undefined) ?? 'Stopped through minor_mode.',\n );\n return { message: stopped ? 'Loop stopped.' : 'Loop instance was not active.' };\n }\n throw new Error(`Unknown loop mode action: ${actionId}`);\n },\n });\n registerLeaderContribution(pi);\n\n const updateStatus = (): void => {\n const launchers = client.listLaunchers();\n const instances = client.listInstances();\n const count = instances.length;\n context?.ui?.setStatus(STATUS_KEY, count ? `loops: ${count}` : undefined);\n mode.publish(loopModeState(launchers, instances));\n };\n\n registerCommands(pi, {\n async start(ctx) {\n const launchers = client.listLaunchers();\n if (!launchers.length) {\n ctx.ui.notify('No loop launchers are registered for this session.', NOTIFY_INFO_LEVEL);\n return;\n }\n const launcherId = await openStartLoopOverlay(ctx, launchers);\n if (!launcherId) return;\n try {\n const instance = await client.launch(launcherId);\n if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, NOTIFY_INFO_LEVEL);\n } catch (error) {\n ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, 'error');\n }\n },\n async list(ctx) {\n await openLoopListOverlay(ctx, client);\n },\n });\n\n pi.on('session_start', async (_event, ctx) => {\n unsubscribe?.();\n unsubscribe = undefined;\n await client.stopAll(SESSION_REPLACED_REASON);\n await defaultRegistration?.dispose(SESSION_REPLACED_REASON);\n defaultRegistration = undefined;\n context?.ui?.setStatus(STATUS_KEY, undefined);\n\n context = ctx;\n sessionId = ctx.sessionManager.getSessionId();\n defaultRegistration = defaultLauncher.register(ctx);\n unsubscribe = client.subscribe(updateStatus);\n updateStatus();\n });\n\n pi.on('session_shutdown', async () => {\n await client.stopAll(SESSION_SHUTDOWN_REASON);\n await defaultRegistration?.dispose(SESSION_SHUTDOWN_REASON);\n defaultRegistration = undefined;\n unsubscribe?.();\n unsubscribe = undefined;\n context?.ui?.setStatus(STATUS_KEY, undefined);\n context = undefined;\n sessionId = undefined;\n mode.publish(loopModeState([], []));\n });\n}\n"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- //#region src/extension.d.ts
2
+ //#region src/adapters/pi/extension.d.ts
3
3
  declare function registerLoopExtension(pi: ExtensionAPI): void;
4
4
  //#endregion
5
5
  export { registerLoopExtension };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;iBAyDgB,sBAAsB,IAAI"}
@@ -1,5 +1,5 @@
1
1
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- //#region src/extension.d.ts
2
+ //#region src/adapters/pi/extension.d.ts
3
3
  declare function registerLoopExtension(pi: ExtensionAPI): void;
4
4
  //#endregion
5
5
  export { registerLoopExtension };
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;iBAyDgB,sBAAsB,IAAI"}
@@ -0,0 +1,2 @@
1
+ import{createLoopLauncherClient as e}from"../../services/loop/launcher.mjs";import{ACTIVE_MODE_COLOR as t,MODE_STATUS_ID as n,NOTIFY_INFO_LEVEL as r,PACKAGE_SOURCE as i,STATUS_KEY as a}from"./loopConstants.mjs";import{createDefaultLoopLauncher as o}from"./defaultLoopLauncher.mjs";import{registerCommands as s}from"../../commands/loopCommand.mjs";import{openLoopListOverlay as c}from"../../tui/loopListOverlay.mjs";import{openStartLoopOverlay as l}from"../../tui/startLoopOverlay.mjs";import{registerLeaderContribution as u}from"./leader.mjs";import{registerMinorModeOwner as d}from"@agimon-ai/doompi-extension-contracts/mode";const f=Symbol.for(`@agimon-ai/doompi-loop/installed-hosts`),p=`start`,m=`stop`,h=`Pi session replaced.`,g=`Pi session shut down.`;function _(e,n){let r=n.length>0;return{activation:r?`active`:`inactive`,condition:n.some(({state:e})=>e===`starting`||e===`stopping`)?`queued`:`ready`,...r?{detail:`${n.length} active`,color:t}:{},actions:[...e.length>0?[{id:p,enabled:!0}]:[{id:p,enabled:!1,disabledReason:`No loop launchers are registered.`}],...r?[{id:m,enabled:!0}]:[{id:m,enabled:!1,disabledReason:`No loops are active.`}]]}}function v(e){let t=globalThis,n=t[f]??new WeakSet;return t[f]=n,!n.has(e)&&(n.add(e),!0)}function y(t){if(!v(t))return;let f,y,b,x,S=e(()=>f),C=o(t),w=d(t,{descriptor:{source:i,id:n,label:`Loop`,description:`Session-scoped recurring prompt loops.`,order:60,actions:[{id:p,label:`Start`,description:`Start a loop with a registered launcher.`,contexts:[`tui`,`headless`],parameters:[{name:`launcherId`,label:`Launcher`,kind:`string`,required:!0,minLength:1},{name:`instanceId`,label:`Instance ID`,kind:`string`,required:!1,minLength:1}]},{id:m,label:`Stop`,description:`Stop one active loop instance.`,contexts:[`tui`,`headless`],parameters:[{name:`instanceId`,label:`Instance ID`,kind:`string`,required:!0,minLength:1},{name:`reason`,label:`Reason`,kind:`string`,required:!1,minLength:1}]}]},initialState:_([],[]),async handleAction(e,t){if(e===p){let e=await S.launch(t.launcherId,t.instanceId?{instanceId:t.instanceId}:{});return{message:e?`Loop '${e.instanceId}' started.`:`Loop launch was cancelled.`}}if(e===m)return{message:await S.stop(t.instanceId,t.reason??`Stopped through minor_mode.`)?`Loop stopped.`:`Loop instance was not active.`};throw Error(`Unknown loop mode action: ${e}`)}});u(t);let T=()=>{let e=S.listLaunchers(),t=S.listInstances(),n=t.length;y?.ui?.setStatus(a,n?`loops: ${n}`:void 0),w.publish(_(e,t))};s(t,{async start(e){let t=S.listLaunchers();if(!t.length){e.ui.notify(`No loop launchers are registered for this session.`,r);return}let n=await l(e,t);if(n)try{let t=await S.launch(n);t&&e.ui.notify(`${t.label??t.launcherLabel} started.`,r)}catch(t){e.ui.notify(`Loop could not start: ${t instanceof Error?t.message:String(t)}`,`error`)}},async list(e){await c(e,S)}}),t.on(`session_start`,async(e,t)=>{b?.(),b=void 0,await S.stopAll(h),await x?.dispose(h),x=void 0,y?.ui?.setStatus(a,void 0),y=t,f=t.sessionManager.getSessionId(),x=C.register(t),b=S.subscribe(T),T()}),t.on(`session_shutdown`,async()=>{await S.stopAll(g),await x?.dispose(g),x=void 0,b?.(),b=void 0,y?.ui?.setStatus(a,void 0),y=void 0,f=void 0,w.publish(_([],[]))})}export{y as registerLoopExtension};
2
+ //# sourceMappingURL=extension.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import { type MinorModeState, registerMinorModeOwner } from '@agimon-ai/doompi-extension-contracts/mode';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommands } from '../../commands/loopCommand.ts';\nimport { createLoopLauncherClient } from '../../services/loop/launcher.ts';\nimport { openLoopListOverlay } from '../../tui/loopListOverlay.ts';\nimport { openStartLoopOverlay } from '../../tui/startLoopOverlay.ts';\nimport type { LoopInstanceSnapshot, LoopLauncherRegistration, LoopLauncherSummary } from '../../types/loop.ts';\nimport { createDefaultLoopLauncher } from './defaultLoopLauncher.ts';\nimport { registerLeaderContribution } from './leader.ts';\nimport {\n ACTIVE_MODE_COLOR,\n LOOPS_GROUP_ORDER,\n MODE_STATUS_ID,\n NOTIFY_INFO_LEVEL,\n PACKAGE_SOURCE,\n STATUS_KEY,\n} from './loopConstants.ts';\n\nconst INSTALLATIONS_KEY = Symbol.for('@agimon-ai/doompi-loop/installed-hosts');\nconst MODE_ACTION_START = 'start';\nconst MODE_ACTION_STOP = 'stop';\nconst SESSION_REPLACED_REASON = 'Pi session replaced.';\nconst SESSION_SHUTDOWN_REASON = 'Pi session shut down.';\n\ntype InstallationRegistry = typeof globalThis & {\n [INSTALLATIONS_KEY]?: WeakSet<object>;\n};\n\nfunction loopModeState(\n launchers: readonly LoopLauncherSummary[],\n instances: readonly LoopInstanceSnapshot[],\n): MinorModeState {\n const active = instances.length > 0;\n return {\n activation: active ? 'active' : 'inactive',\n condition: instances.some(({ state }) => state === 'starting' || state === 'stopping') ? 'queued' : 'ready',\n ...(active ? { detail: `${instances.length} active`, color: ACTIVE_MODE_COLOR } : {}),\n actions: [\n ...(launchers.length > 0\n ? [{ id: MODE_ACTION_START, enabled: true } as const]\n : [{ id: MODE_ACTION_START, enabled: false, disabledReason: 'No loop launchers are registered.' } as const]),\n ...(active\n ? [{ id: MODE_ACTION_STOP, enabled: true } as const]\n : [{ id: MODE_ACTION_STOP, enabled: false, disabledReason: 'No loops are active.' } as const]),\n ],\n };\n}\n\nfunction claimHostInstallation(pi: ExtensionAPI): boolean {\n const registry = globalThis as InstallationRegistry;\n const installedHosts = registry[INSTALLATIONS_KEY] ?? new WeakSet<object>();\n registry[INSTALLATIONS_KEY] = installedHosts;\n if (installedHosts.has(pi)) return false;\n installedHosts.add(pi);\n return true;\n}\n\nexport function registerLoopExtension(pi: ExtensionAPI): void {\n if (!claimHostInstallation(pi)) return;\n let sessionId: string | undefined;\n let context: ExtensionContext | undefined;\n let unsubscribe: (() => void) | undefined;\n let defaultRegistration: LoopLauncherRegistration | undefined;\n const client = createLoopLauncherClient(() => sessionId);\n const defaultLauncher = createDefaultLoopLauncher(pi);\n const mode = registerMinorModeOwner(pi, {\n descriptor: {\n source: PACKAGE_SOURCE,\n id: MODE_STATUS_ID,\n label: 'Loop',\n description: 'Session-scoped recurring prompt loops.',\n order: LOOPS_GROUP_ORDER,\n actions: [\n {\n id: MODE_ACTION_START,\n label: 'Start',\n description: 'Start a loop with a registered launcher.',\n contexts: ['tui', 'headless'],\n parameters: [\n { name: 'launcherId', label: 'Launcher', kind: 'string', required: true, minLength: 1 },\n { name: 'instanceId', label: 'Instance ID', kind: 'string', required: false, minLength: 1 },\n ],\n },\n {\n id: MODE_ACTION_STOP,\n label: 'Stop',\n description: 'Stop one active loop instance.',\n contexts: ['tui', 'headless'],\n parameters: [\n { name: 'instanceId', label: 'Instance ID', kind: 'string', required: true, minLength: 1 },\n { name: 'reason', label: 'Reason', kind: 'string', required: false, minLength: 1 },\n ],\n },\n ],\n },\n initialState: loopModeState([], []),\n async handleAction(actionId, argumentsValue) {\n if (actionId === MODE_ACTION_START) {\n const instance = await client.launch(\n argumentsValue.launcherId as string,\n argumentsValue.instanceId ? { instanceId: argumentsValue.instanceId as string } : {},\n );\n return { message: instance ? `Loop '${instance.instanceId}' started.` : 'Loop launch was cancelled.' };\n }\n if (actionId === MODE_ACTION_STOP) {\n const stopped = await client.stop(\n argumentsValue.instanceId as string,\n (argumentsValue.reason as string | undefined) ?? 'Stopped through minor_mode.',\n );\n return { message: stopped ? 'Loop stopped.' : 'Loop instance was not active.' };\n }\n throw new Error(`Unknown loop mode action: ${actionId}`);\n },\n });\n registerLeaderContribution(pi);\n\n const updateStatus = (): void => {\n const launchers = client.listLaunchers();\n const instances = client.listInstances();\n const count = instances.length;\n context?.ui?.setStatus(STATUS_KEY, count ? `loops: ${count}` : undefined);\n mode.publish(loopModeState(launchers, instances));\n };\n\n registerCommands(pi, {\n async start(ctx) {\n const launchers = client.listLaunchers();\n if (!launchers.length) {\n ctx.ui.notify('No loop launchers are registered for this session.', NOTIFY_INFO_LEVEL);\n return;\n }\n const launcherId = await openStartLoopOverlay(ctx, launchers);\n if (!launcherId) return;\n try {\n const instance = await client.launch(launcherId);\n if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, NOTIFY_INFO_LEVEL);\n } catch (error) {\n ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, 'error');\n }\n },\n async list(ctx) {\n await openLoopListOverlay(ctx, client);\n },\n });\n\n pi.on('session_start', async (_event, ctx) => {\n unsubscribe?.();\n unsubscribe = undefined;\n await client.stopAll(SESSION_REPLACED_REASON);\n await defaultRegistration?.dispose(SESSION_REPLACED_REASON);\n defaultRegistration = undefined;\n context?.ui?.setStatus(STATUS_KEY, undefined);\n\n context = ctx;\n sessionId = ctx.sessionManager.getSessionId();\n defaultRegistration = defaultLauncher.register(ctx);\n unsubscribe = client.subscribe(updateStatus);\n updateStatus();\n });\n\n pi.on('session_shutdown', async () => {\n await client.stopAll(SESSION_SHUTDOWN_REASON);\n await defaultRegistration?.dispose(SESSION_SHUTDOWN_REASON);\n defaultRegistration = undefined;\n unsubscribe?.();\n unsubscribe = undefined;\n context?.ui?.setStatus(STATUS_KEY, undefined);\n context = undefined;\n sessionId = undefined;\n mode.publish(loopModeState([], []));\n });\n}\n"],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ const e=require("./loopConstants.cjs"),t=require("../../schemas/loopCommands.cjs");let n=require("@agimon-ai/doompi-ui/leader");function r(r){return(0,n.registerDoomLeaderContribution)(r,{source:e.PACKAGE_SOURCE,bindings:[{id:`loop.start`,path:[{key:`l`,label:`loops`,order:60},{key:`s`,label:`start`}],command:{name:t.START_COMMAND_NAME}},{id:`loop.list`,path:[{key:`l`,label:`loops`,order:60},{key:`l`,label:`list`,detail:`stop`}],command:{name:t.LIST_COMMAND_NAME}}]})}exports.registerLeaderContribution=r;
2
+ //# sourceMappingURL=leader.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leader.cjs","names":["registerDoomLeaderContribution","PACKAGE_SOURCE","START_COMMAND_NAME","LIST_COMMAND_NAME"],"sources":["../../../src/adapters/pi/leader.ts"],"sourcesContent":["import { registerDoomLeaderContribution } from '@agimon-ai/doompi-ui/leader';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { LIST_COMMAND_NAME, START_COMMAND_NAME } from '../../schemas/loopCommands.ts';\nimport { LOOPS_GROUP_ORDER, PACKAGE_SOURCE } from './loopConstants.ts';\n\nexport function registerLeaderContribution(pi: Pick<ExtensionAPI, 'events' | 'on'>): () => void {\n return registerDoomLeaderContribution(pi, {\n source: PACKAGE_SOURCE,\n bindings: [\n {\n id: 'loop.start',\n path: [\n { key: 'l', label: 'loops', order: LOOPS_GROUP_ORDER },\n { key: 's', label: 'start' },\n ],\n command: { name: START_COMMAND_NAME },\n },\n {\n id: 'loop.list',\n path: [\n { key: 'l', label: 'loops', order: LOOPS_GROUP_ORDER },\n { key: 'l', label: 'list', detail: 'stop' },\n ],\n command: { name: LIST_COMMAND_NAME },\n },\n ],\n });\n}\n"],"mappings":"gIAKA,SAAgB,EAA2B,EAAqD,CAC9F,OAAA,EAAOA,EAAAA,+BAAAA,CAA+B,EAAI,CACxC,OAAQC,EAAAA,eACR,SAAU,CACR,CACE,GAAI,aACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,QAAS,MAAA,EAAyB,EACrD,CAAE,IAAK,IAAK,MAAO,OAAQ,CAC7B,EACA,QAAS,CAAE,KAAMC,EAAAA,kBAAmB,CACtC,EACA,CACE,GAAI,YACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,QAAS,MAAA,EAAyB,EACrD,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,MAAO,CAC5C,EACA,QAAS,CAAE,KAAMC,EAAAA,iBAAkB,CACrC,CACF,CACF,CAAC,CACH"}
@@ -0,0 +1,2 @@
1
+ import{PACKAGE_SOURCE as e}from"./loopConstants.mjs";import{LIST_COMMAND_NAME as t,START_COMMAND_NAME as n}from"../../schemas/loopCommands.mjs";import{registerDoomLeaderContribution as r}from"@agimon-ai/doompi-ui/leader";function i(i){return r(i,{source:e,bindings:[{id:`loop.start`,path:[{key:`l`,label:`loops`,order:60},{key:`s`,label:`start`}],command:{name:n}},{id:`loop.list`,path:[{key:`l`,label:`loops`,order:60},{key:`l`,label:`list`,detail:`stop`}],command:{name:t}}]})}export{i as registerLeaderContribution};
2
+ //# sourceMappingURL=leader.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"leader.mjs","names":[],"sources":["../../../src/adapters/pi/leader.ts"],"sourcesContent":["import { registerDoomLeaderContribution } from '@agimon-ai/doompi-ui/leader';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { LIST_COMMAND_NAME, START_COMMAND_NAME } from '../../schemas/loopCommands.ts';\nimport { LOOPS_GROUP_ORDER, PACKAGE_SOURCE } from './loopConstants.ts';\n\nexport function registerLeaderContribution(pi: Pick<ExtensionAPI, 'events' | 'on'>): () => void {\n return registerDoomLeaderContribution(pi, {\n source: PACKAGE_SOURCE,\n bindings: [\n {\n id: 'loop.start',\n path: [\n { key: 'l', label: 'loops', order: LOOPS_GROUP_ORDER },\n { key: 's', label: 'start' },\n ],\n command: { name: START_COMMAND_NAME },\n },\n {\n id: 'loop.list',\n path: [\n { key: 'l', label: 'loops', order: LOOPS_GROUP_ORDER },\n { key: 'l', label: 'list', detail: 'stop' },\n ],\n command: { name: LIST_COMMAND_NAME },\n },\n ],\n });\n}\n"],"mappings":"6NAKA,SAAgB,EAA2B,EAAqD,CAC9F,OAAO,EAA+B,EAAI,CACxC,OAAQ,EACR,SAAU,CACR,CACE,GAAI,aACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,QAAS,MAAA,EAAyB,EACrD,CAAE,IAAK,IAAK,MAAO,OAAQ,CAC7B,EACA,QAAS,CAAE,KAAM,CAAmB,CACtC,EACA,CACE,GAAI,YACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,QAAS,MAAA,EAAyB,EACrD,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,MAAO,CAC5C,EACA,QAAS,CAAE,KAAM,CAAkB,CACrC,CACF,CACF,CAAC,CACH"}
@@ -0,0 +1,2 @@
1
+ exports.ACTIVE_MODE_COLOR=`warning`,exports.MODE_STATUS_ID=`loop.active`,exports.NOTIFY_INFO_LEVEL=`info`,exports.NOTIFY_WARNING_LEVEL=`warning`,exports.PACKAGE_SOURCE=`@agimon-ai/doompi-loop`,exports.STATUS_KEY=`doom-loop`;
2
+ //# sourceMappingURL=loopConstants.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopConstants.cjs","names":[],"sources":["../../../src/adapters/pi/loopConstants.ts"],"sourcesContent":["export const PACKAGE_SOURCE = '@agimon-ai/doompi-loop';\nexport const LOOPS_GROUP_ORDER = 60;\nexport const STATUS_KEY = 'doom-loop';\nexport const MODE_STATUS_ID = 'loop.active';\nexport const ACTIVE_MODE_COLOR = 'warning';\nexport const NOTIFY_INFO_LEVEL = 'info';\nexport const NOTIFY_WARNING_LEVEL = 'warning';\n"],"mappings":""}
@@ -0,0 +1,2 @@
1
+ const e=`@agimon-ai/doompi-loop`,t=`doom-loop`,n=`loop.active`,r=`warning`,i=`info`,a=`warning`;export{r as ACTIVE_MODE_COLOR,n as MODE_STATUS_ID,i as NOTIFY_INFO_LEVEL,a as NOTIFY_WARNING_LEVEL,e as PACKAGE_SOURCE,t as STATUS_KEY};
2
+ //# sourceMappingURL=loopConstants.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopConstants.mjs","names":[],"sources":["../../../src/adapters/pi/loopConstants.ts"],"sourcesContent":["export const PACKAGE_SOURCE = '@agimon-ai/doompi-loop';\nexport const LOOPS_GROUP_ORDER = 60;\nexport const STATUS_KEY = 'doom-loop';\nexport const MODE_STATUS_ID = 'loop.active';\nexport const ACTIVE_MODE_COLOR = 'warning';\nexport const NOTIFY_INFO_LEVEL = 'info';\nexport const NOTIFY_WARNING_LEVEL = 'warning';\n"],"mappings":"AAAA,MAAa,EAAiB,yBAEjB,EAAa,YACb,EAAiB,cACjB,EAAoB,UACpB,EAAoB,OACpB,EAAuB"}
@@ -1,2 +1,2 @@
1
- const e=require("../constants.cjs");function t(t,n){t.registerCommand(e.START_COMMAND_NAME,{description:e.START_COMMAND_DESCRIPTION,handler:async(e,t)=>n.start(t)}),t.registerCommand(e.LIST_COMMAND_NAME,{description:e.LIST_COMMAND_DESCRIPTION,handler:async(e,t)=>n.list(t)})}exports.registerCommands=t;
1
+ const e=require("../schemas/loopCommands.cjs");function t(t,n){t.registerCommand(e.START_COMMAND_NAME,{description:`Start a registered loop`,handler:async(e,t)=>n.start(t)}),t.registerCommand(e.LIST_COMMAND_NAME,{description:`List and stop active loops`,handler:async(e,t)=>n.list(t)})}exports.registerCommands=t;
2
2
  //# sourceMappingURL=loopCommand.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"loopCommand.cjs","names":["START_COMMAND_NAME","START_COMMAND_DESCRIPTION","LIST_COMMAND_NAME","LIST_COMMAND_DESCRIPTION"],"sources":["../../src/commands/loopCommand.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n LIST_COMMAND_DESCRIPTION,\n LIST_COMMAND_NAME,\n START_COMMAND_DESCRIPTION,\n START_COMMAND_NAME,\n} from '../constants.ts';\n\nexport interface LoopCommandHandlers {\n start(ctx: ExtensionContext): Promise<void>;\n list(ctx: ExtensionContext): Promise<void>;\n}\n\nexport function registerCommands(pi: ExtensionAPI, handlers: LoopCommandHandlers): void {\n pi.registerCommand(START_COMMAND_NAME, {\n description: START_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.start(ctx),\n });\n pi.registerCommand(LIST_COMMAND_NAME, {\n description: LIST_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.list(ctx),\n });\n}\n"],"mappings":"oCAaA,SAAgB,EAAiB,EAAkB,EAAqC,CACtF,EAAG,gBAAgBA,EAAAA,mBAAoB,CACrC,YAAaC,EAAAA,0BACb,QAAS,MAAO,EAAO,IAAQ,EAAS,MAAM,CAAG,CACnD,CAAC,EACD,EAAG,gBAAgBC,EAAAA,kBAAmB,CACpC,YAAaC,EAAAA,yBACb,QAAS,MAAO,EAAO,IAAQ,EAAS,KAAK,CAAG,CAClD,CAAC,CACH"}
1
+ {"version":3,"file":"loopCommand.cjs","names":["START_COMMAND_NAME","LIST_COMMAND_NAME"],"sources":["../../src/commands/loopCommand.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { LIST_COMMAND_NAME, START_COMMAND_NAME } from '../schemas/loopCommands.ts';\n\nconst START_COMMAND_DESCRIPTION = 'Start a registered loop';\nconst LIST_COMMAND_DESCRIPTION = 'List and stop active loops';\n\nexport interface LoopCommandHandlers {\n start(ctx: ExtensionContext): Promise<void>;\n list(ctx: ExtensionContext): Promise<void>;\n}\n\nexport function registerCommands(pi: ExtensionAPI, handlers: LoopCommandHandlers): void {\n pi.registerCommand(START_COMMAND_NAME, {\n description: START_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.start(ctx),\n });\n pi.registerCommand(LIST_COMMAND_NAME, {\n description: LIST_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.list(ctx),\n });\n}\n"],"mappings":"+CAWA,SAAgB,EAAiB,EAAkB,EAAqC,CACtF,EAAG,gBAAgBA,EAAAA,mBAAoB,CACrC,YAAa,0BACb,QAAS,MAAO,EAAO,IAAQ,EAAS,MAAM,CAAG,CACnD,CAAC,EACD,EAAG,gBAAgBC,EAAAA,kBAAmB,CACpC,YAAa,6BACb,QAAS,MAAO,EAAO,IAAQ,EAAS,KAAK,CAAG,CAClD,CAAC,CACH"}
@@ -1,2 +1,2 @@
1
- import{LIST_COMMAND_DESCRIPTION as e,LIST_COMMAND_NAME as t,START_COMMAND_DESCRIPTION as n,START_COMMAND_NAME as r}from"../constants.mjs";function i(i,a){i.registerCommand(r,{description:n,handler:async(e,t)=>a.start(t)}),i.registerCommand(t,{description:e,handler:async(e,t)=>a.list(t)})}export{i as registerCommands};
1
+ import{LIST_COMMAND_NAME as e,START_COMMAND_NAME as t}from"../schemas/loopCommands.mjs";function n(n,r){n.registerCommand(t,{description:`Start a registered loop`,handler:async(e,t)=>r.start(t)}),n.registerCommand(e,{description:`List and stop active loops`,handler:async(e,t)=>r.list(t)})}export{n as registerCommands};
2
2
  //# sourceMappingURL=loopCommand.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"loopCommand.mjs","names":[],"sources":["../../src/commands/loopCommand.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n LIST_COMMAND_DESCRIPTION,\n LIST_COMMAND_NAME,\n START_COMMAND_DESCRIPTION,\n START_COMMAND_NAME,\n} from '../constants.ts';\n\nexport interface LoopCommandHandlers {\n start(ctx: ExtensionContext): Promise<void>;\n list(ctx: ExtensionContext): Promise<void>;\n}\n\nexport function registerCommands(pi: ExtensionAPI, handlers: LoopCommandHandlers): void {\n pi.registerCommand(START_COMMAND_NAME, {\n description: START_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.start(ctx),\n });\n pi.registerCommand(LIST_COMMAND_NAME, {\n description: LIST_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.list(ctx),\n });\n}\n"],"mappings":"0IAaA,SAAgB,EAAiB,EAAkB,EAAqC,CACtF,EAAG,gBAAgB,EAAoB,CACrC,YAAa,EACb,QAAS,MAAO,EAAO,IAAQ,EAAS,MAAM,CAAG,CACnD,CAAC,EACD,EAAG,gBAAgB,EAAmB,CACpC,YAAa,EACb,QAAS,MAAO,EAAO,IAAQ,EAAS,KAAK,CAAG,CAClD,CAAC,CACH"}
1
+ {"version":3,"file":"loopCommand.mjs","names":[],"sources":["../../src/commands/loopCommand.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { LIST_COMMAND_NAME, START_COMMAND_NAME } from '../schemas/loopCommands.ts';\n\nconst START_COMMAND_DESCRIPTION = 'Start a registered loop';\nconst LIST_COMMAND_DESCRIPTION = 'List and stop active loops';\n\nexport interface LoopCommandHandlers {\n start(ctx: ExtensionContext): Promise<void>;\n list(ctx: ExtensionContext): Promise<void>;\n}\n\nexport function registerCommands(pi: ExtensionAPI, handlers: LoopCommandHandlers): void {\n pi.registerCommand(START_COMMAND_NAME, {\n description: START_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.start(ctx),\n });\n pi.registerCommand(LIST_COMMAND_NAME, {\n description: LIST_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.list(ctx),\n });\n}\n"],"mappings":"wFAWA,SAAgB,EAAiB,EAAkB,EAAqC,CACtF,EAAG,gBAAgB,EAAoB,CACrC,YAAa,0BACb,QAAS,MAAO,EAAO,IAAQ,EAAS,MAAM,CAAG,CACnD,CAAC,EACD,EAAG,gBAAgB,EAAmB,CACpC,YAAa,6BACb,QAAS,MAAO,EAAO,IAAQ,EAAS,KAAK,CAAG,CAClD,CAAC,CACH"}
@@ -1,2 +1 @@
1
- var e=require("../extension.cjs").registerLoopExtension;module.exports=e;
2
- //# sourceMappingURL=pi.cjs.map
1
+ const e=require("../adapters/pi/extension.cjs");module.exports=e.registerLoopExtension;
@@ -1,2 +1,2 @@
1
- import { registerLoopExtension } from "../extension.cjs";
1
+ import { registerLoopExtension } from "../adapters/pi/extension.cjs";
2
2
  export = registerLoopExtension;
@@ -1,2 +1,2 @@
1
- import { registerLoopExtension } from "../extension.mjs";
1
+ import { registerLoopExtension } from "../adapters/pi/extension.mjs";
2
2
  export { registerLoopExtension as default };
@@ -1,2 +1 @@
1
- import{registerLoopExtension as e}from"../extension.mjs";var t=e;export{t as default};
2
- //# sourceMappingURL=pi.mjs.map
1
+ import{registerLoopExtension as e}from"../adapters/pi/extension.mjs";export{e as default};
package/dist/index.cjs CHANGED
@@ -1 +1 @@
1
- Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./launcher.cjs"),t=require("./extension.cjs");exports.createLoopLauncherClient=e.createLoopLauncherClient,exports.registerLoopExtension=t.registerLoopExtension,exports.registerLoopLauncher=e.registerLoopLauncher;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/loop/launcher.cjs"),t=require("./adapters/pi/defaultLoopLauncher.cjs"),n=require("./adapters/pi/extension.cjs");exports.createDefaultLoopLauncher=t.createDefaultLoopLauncher,exports.createLoopLauncherClient=e.createLoopLauncherClient,exports.registerLoopExtension=n.registerLoopExtension,exports.registerLoopLauncher=e.registerLoopLauncher;
package/dist/index.d.cts CHANGED
@@ -1,3 +1,5 @@
1
- import { registerLoopExtension } from "./extension.cjs";
2
- import { LoopInstanceSnapshot, LoopLaunchRequest, LoopLauncherClient, LoopLauncherRegistration, LoopLauncherSummary, RegisterLoopLauncherOptions, StoppableLoop, createLoopLauncherClient, registerLoopLauncher } from "./launcher.cjs";
3
- export { type LoopInstanceSnapshot, type LoopLaunchRequest, type LoopLauncherClient, type LoopLauncherRegistration, type LoopLauncherSummary, type RegisterLoopLauncherOptions, type StoppableLoop, createLoopLauncherClient, registerLoopExtension, registerLoopLauncher };
1
+ import { registerLoopExtension } from "./adapters/pi/extension.cjs";
2
+ import { LoopInstanceSnapshot, LoopLaunchRequest, LoopLauncherClient, LoopLauncherRegistration, LoopLauncherSummary, RegisterLoopLauncherOptions, StoppableLoop } from "./types/loop.cjs";
3
+ import { DefaultLoopLauncher, createDefaultLoopLauncher } from "./adapters/pi/defaultLoopLauncher.cjs";
4
+ import { createLoopLauncherClient, registerLoopLauncher } from "./services/loop/launcher.cjs";
5
+ export { type DefaultLoopLauncher, type LoopInstanceSnapshot, type LoopLaunchRequest, type LoopLauncherClient, type LoopLauncherRegistration, type LoopLauncherSummary, type RegisterLoopLauncherOptions, type StoppableLoop, createDefaultLoopLauncher, createLoopLauncherClient, registerLoopExtension, registerLoopLauncher };
package/dist/index.d.mts CHANGED
@@ -1,3 +1,5 @@
1
- import { registerLoopExtension } from "./extension.mjs";
2
- import { LoopInstanceSnapshot, LoopLaunchRequest, LoopLauncherClient, LoopLauncherRegistration, LoopLauncherSummary, RegisterLoopLauncherOptions, StoppableLoop, createLoopLauncherClient, registerLoopLauncher } from "./launcher.mjs";
3
- export { type LoopInstanceSnapshot, type LoopLaunchRequest, type LoopLauncherClient, type LoopLauncherRegistration, type LoopLauncherSummary, type RegisterLoopLauncherOptions, type StoppableLoop, createLoopLauncherClient, registerLoopExtension, registerLoopLauncher };
1
+ import { registerLoopExtension } from "./adapters/pi/extension.mjs";
2
+ import { LoopInstanceSnapshot, LoopLaunchRequest, LoopLauncherClient, LoopLauncherRegistration, LoopLauncherSummary, RegisterLoopLauncherOptions, StoppableLoop } from "./types/loop.mjs";
3
+ import { DefaultLoopLauncher, createDefaultLoopLauncher } from "./adapters/pi/defaultLoopLauncher.mjs";
4
+ import { createLoopLauncherClient, registerLoopLauncher } from "./services/loop/launcher.mjs";
5
+ export { type DefaultLoopLauncher, type LoopInstanceSnapshot, type LoopLaunchRequest, type LoopLauncherClient, type LoopLauncherRegistration, type LoopLauncherSummary, type RegisterLoopLauncherOptions, type StoppableLoop, createDefaultLoopLauncher, createLoopLauncherClient, registerLoopExtension, registerLoopLauncher };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
1
- import{createLoopLauncherClient as e,registerLoopLauncher as t}from"./launcher.mjs";import{registerLoopExtension as n}from"./extension.mjs";export{e as createLoopLauncherClient,n as registerLoopExtension,t as registerLoopLauncher};
1
+ import{createLoopLauncherClient as e,registerLoopLauncher as t}from"./services/loop/launcher.mjs";import{createDefaultLoopLauncher as n}from"./adapters/pi/defaultLoopLauncher.mjs";import{registerLoopExtension as r}from"./adapters/pi/extension.mjs";export{n as createDefaultLoopLauncher,e as createLoopLauncherClient,r as registerLoopExtension,t as registerLoopLauncher};
@@ -0,0 +1,2 @@
1
+ exports.LIST_COMMAND_NAME=`loops`,exports.START_COMMAND_NAME=`loop`;
2
+ //# sourceMappingURL=loopCommands.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopCommands.cjs","names":[],"sources":["../../src/schemas/loopCommands.ts"],"sourcesContent":["export const START_COMMAND_NAME = 'loop';\nexport const LIST_COMMAND_NAME = 'loops';\n"],"mappings":""}
@@ -0,0 +1,2 @@
1
+ const e=`loop`,t=`loops`;export{t as LIST_COMMAND_NAME,e as START_COMMAND_NAME};
2
+ //# sourceMappingURL=loopCommands.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopCommands.mjs","names":[],"sources":["../../src/schemas/loopCommands.ts"],"sourcesContent":["export const START_COMMAND_NAME = 'loop';\nexport const LIST_COMMAND_NAME = 'loops';\n"],"mappings":"AAAA,MAAa,EAAqB,OACrB,EAAoB"}
@@ -0,0 +1,2 @@
1
+ const e=Symbol.for(`@agimon-ai/doompi-loop.launchers.v1`),t=`loop session id`,n=`loop launcher id`,r=`loop instance id`;function i(){let t=globalThis,n=t[e];if(n)return n;let r=new Map;return t[e]=r,r}function a(e,t){let n=e.trim(),r=!1;for(let e of n){let t=e.codePointAt(0);if(t!==void 0&&(t<=31||t===127)){r=!0;break}}if(!n||n.length>256||r)throw Error(`Invalid ${t}.`);return n}function o(e,n){let r=a(e,t),o=i().get(r);if(o||!n)return o;let s={launchers:new Map,instances:new Map,listeners:new Set};return i().set(r,s),s}function s(e){for(let t of e.listeners)t()}function c(e){return{instanceId:e.instanceId,launcherId:e.launcher.options.id,launcherLabel:e.launcher.options.label,...e.handle?.label?{label:e.handle.label}:{},...e.handle?.detail?{detail:e.handle.detail}:{},state:e.state,...e.startedAt?{startedAt:e.startedAt}:{}}}async function l(e,t,n){if(e.instances.get(t.instanceId)===t){t.state=`stopping`,t.abortController.abort(n),s(e);try{await t.handle?.stop(n)}finally{e.instances.get(t.instanceId)===t&&e.instances.delete(t.instanceId),s(e)}}}function u(e,t){t.launchers.size||t.instances.size||t.listeners.size||i().get(e)===t&&i().delete(e)}function d(e,r){let i=a(e,t),c=a(r.id,n),d=o(i,!0);if(d.launchers.has(c))throw Error(`Loop launcher '${c}' is already registered for session '${i}'.`);let f={token:Symbol(c),options:{...r,id:c}};d.launchers.set(c,f),s(d);let p=!1;return{async dispose(e=`Loop launcher disposed.`){if(p)return;p=!0;let t=o(i,!1);if(!t||t.launchers.get(c)?.token!==f.token)return;t.launchers.delete(c);let n=[...t.instances.values()].filter(e=>e.launcher.token===f.token);await Promise.allSettled(n.map(n=>l(t,n,e))),s(t),u(i,t)}}}function f(e){let i=n=>{let r=e();if(!r)return;let i=a(r,t),s=o(i,n);return s?{key:i,session:s}:void 0};return{listLaunchers(){let e=i(!1)?.session;return e?[...e.launchers.values()].map(({options:e})=>({id:e.id,source:e.source,label:e.label,...e.description?{description:e.description}:{}})).sort((e,t)=>e.label.localeCompare(t.label)||e.id.localeCompare(t.id)):[]},listInstances(){let e=i(!1)?.session;return e?[...e.instances.values()].map(c).sort((e,t)=>e.instanceId.localeCompare(t.instanceId)):[]},subscribe(e){let t=i(!0);if(!t)return()=>void 0;t.session.listeners.add(e);let n=!1;return()=>{n||(n=!0,t.session.listeners.delete(e),u(t.key,t.session))}},async launch(e,t){let o=i(!0);if(!o)throw Error(`The session-bound loop launcher registry is unavailable.`);let l=a(e,n),u=o.session.launchers.get(l);if(!u)throw Error(`Loop launcher '${l}' is unavailable.`);let d=a(t?.instanceId??globalThis.crypto.randomUUID(),r);if(o.session.instances.has(d))throw Error(`Loop instance '${d}' already exists.`);let f={instanceId:d,launcher:u,abortController:new AbortController,state:`starting`};o.session.instances.set(d,f),s(o.session);try{let e=await u.options.launch({instanceId:d,signal:f.abortController.signal}),t=o.session.instances.get(d)===f&&!f.abortController.signal.aborted;if(!e){o.session.instances.get(d)===f&&o.session.instances.delete(d),s(o.session);return}if(!t||e.instanceId!==d){if(await e.stop(t?`Loop launcher returned a mismatched instance id.`:`Loop launch was cancelled.`),o.session.instances.get(d)===f&&o.session.instances.delete(d),s(o.session),t)throw Error(`Loop launcher '${l}' returned a mismatched instance id.`);return}return f.handle=e,f.state=`running`,f.startedAt=new Date().toISOString(),s(o.session),c(f)}catch(e){let t=f.abortController.signal.aborted;if(o.session.instances.get(d)===f&&o.session.instances.delete(d),s(o.session),t)return;throw e}},async stop(e,t){let n=i(!1)?.session;if(!n)return!1;let o=n.instances.get(a(e,r));return o?(await l(n,o,t),!0):!1},async stopAll(e){let t=i(!1)?.session;t&&await Promise.allSettled([...t.instances.values()].map(n=>l(t,n,e)))}}}exports.createLoopLauncherClient=f,exports.registerLoopLauncher=d;
2
+ //# sourceMappingURL=launcher.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.cjs","names":[],"sources":["../../../src/services/loop/launcher.ts"],"sourcesContent":["import type {\n LoopInstanceSnapshot,\n LoopLauncherClient,\n LoopLauncherRegistration,\n RegisterLoopLauncherOptions,\n StoppableLoop,\n} from '../../types/loop.ts';\n\nconst REGISTRY_KEY = Symbol.for('@agimon-ai/doompi-loop.launchers.v1');\nconst MAX_ID_LENGTH = 256;\nconst SESSION_ID_LABEL = 'loop session id';\nconst LAUNCHER_ID_LABEL = 'loop launcher id';\nconst INSTANCE_ID_LABEL = 'loop instance id';\n\ninterface LauncherEntry {\n readonly token: symbol;\n readonly options: RegisterLoopLauncherOptions;\n}\n\ninterface InstanceEntry {\n readonly instanceId: string;\n readonly launcher: LauncherEntry;\n readonly abortController: AbortController;\n state: LoopInstanceSnapshot['state'];\n handle?: StoppableLoop;\n startedAt?: string;\n}\n\ninterface SessionRegistry {\n readonly launchers: Map<string, LauncherEntry>;\n readonly instances: Map<string, InstanceEntry>;\n readonly listeners: Set<() => void>;\n}\n\ntype LoopRegistry = Map<string, SessionRegistry>;\n\nfunction registry(): LoopRegistry {\n const root = globalThis as typeof globalThis & { [REGISTRY_KEY]?: LoopRegistry };\n const existing = root[REGISTRY_KEY];\n if (existing) return existing;\n\n const created: LoopRegistry = new Map();\n root[REGISTRY_KEY] = created;\n return created;\n}\n\nfunction validId(value: string, label: string): string {\n const normalized = value.trim();\n let hasControl = false;\n for (const character of normalized) {\n const code = character.codePointAt(0);\n if (code !== undefined && (code <= 0x1f || code === 0x7f)) {\n hasControl = true;\n break;\n }\n }\n if (!normalized || normalized.length > MAX_ID_LENGTH || hasControl) throw new Error(`Invalid ${label}.`);\n return normalized;\n}\n\nfunction getSession(sessionId: string, create: boolean): SessionRegistry | undefined {\n const key = validId(sessionId, SESSION_ID_LABEL);\n const existing = registry().get(key);\n if (existing || !create) return existing;\n const created: SessionRegistry = { launchers: new Map(), instances: new Map(), listeners: new Set() };\n registry().set(key, created);\n return created;\n}\n\nfunction notify(session: SessionRegistry): void {\n for (const listener of session.listeners) listener();\n}\n\nfunction snapshot(entry: InstanceEntry): LoopInstanceSnapshot {\n return {\n instanceId: entry.instanceId,\n launcherId: entry.launcher.options.id,\n launcherLabel: entry.launcher.options.label,\n ...(entry.handle?.label ? { label: entry.handle.label } : {}),\n ...(entry.handle?.detail ? { detail: entry.handle.detail } : {}),\n state: entry.state,\n ...(entry.startedAt ? { startedAt: entry.startedAt } : {}),\n };\n}\n\nasync function stopEntry(session: SessionRegistry, entry: InstanceEntry, reason?: string): Promise<void> {\n if (session.instances.get(entry.instanceId) !== entry) return;\n entry.state = 'stopping';\n entry.abortController.abort(reason);\n notify(session);\n try {\n await entry.handle?.stop(reason);\n } finally {\n if (session.instances.get(entry.instanceId) === entry) session.instances.delete(entry.instanceId);\n notify(session);\n }\n}\n\nfunction cleanSession(sessionId: string, session: SessionRegistry): void {\n if (session.launchers.size || session.instances.size || session.listeners.size) return;\n if (registry().get(sessionId) === session) registry().delete(sessionId);\n}\n\nexport function registerLoopLauncher(\n sessionId: string,\n options: RegisterLoopLauncherOptions,\n): LoopLauncherRegistration {\n const sessionKey = validId(sessionId, SESSION_ID_LABEL);\n const launcherId = validId(options.id, LAUNCHER_ID_LABEL);\n const session = getSession(sessionKey, true) as SessionRegistry;\n if (session.launchers.has(launcherId)) {\n throw new Error(`Loop launcher '${launcherId}' is already registered for session '${sessionKey}'.`);\n }\n const entry: LauncherEntry = { token: Symbol(launcherId), options: { ...options, id: launcherId } };\n session.launchers.set(launcherId, entry);\n notify(session);\n let disposed = false;\n return {\n async dispose(reason = 'Loop launcher disposed.'): Promise<void> {\n if (disposed) return;\n disposed = true;\n const current = getSession(sessionKey, false);\n if (!current || current.launchers.get(launcherId)?.token !== entry.token) return;\n current.launchers.delete(launcherId);\n const owned = [...current.instances.values()].filter((instance) => instance.launcher.token === entry.token);\n await Promise.allSettled(owned.map((instance) => stopEntry(current, instance, reason)));\n notify(current);\n cleanSession(sessionKey, current);\n },\n };\n}\n\nexport function createLoopLauncherClient(sessionId: () => string | undefined): LoopLauncherClient {\n const current = (create: boolean): { key: string; session: SessionRegistry } | undefined => {\n const value = sessionId();\n if (!value) return undefined;\n const key = validId(value, SESSION_ID_LABEL);\n const session = getSession(key, create);\n return session ? { key, session } : undefined;\n };\n return {\n listLaunchers() {\n const session = current(false)?.session;\n if (!session) return [];\n return [...session.launchers.values()]\n .map(({ options }) => ({\n id: options.id,\n source: options.source,\n label: options.label,\n ...(options.description ? { description: options.description } : {}),\n }))\n .sort((left, right) => left.label.localeCompare(right.label) || left.id.localeCompare(right.id));\n },\n listInstances() {\n const session = current(false)?.session;\n return session\n ? [...session.instances.values()]\n .map(snapshot)\n .sort((left, right) => left.instanceId.localeCompare(right.instanceId))\n : [];\n },\n subscribe(listener) {\n const target = current(true);\n if (!target) return () => undefined;\n target.session.listeners.add(listener);\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n target.session.listeners.delete(listener);\n cleanSession(target.key, target.session);\n };\n },\n async launch(launcherId, options) {\n const target = current(true);\n if (!target) throw new Error('The session-bound loop launcher registry is unavailable.');\n const normalizedLauncherId = validId(launcherId, LAUNCHER_ID_LABEL);\n const launcher = target.session.launchers.get(normalizedLauncherId);\n if (!launcher) throw new Error(`Loop launcher '${normalizedLauncherId}' is unavailable.`);\n const instanceId = validId(options?.instanceId ?? globalThis.crypto.randomUUID(), INSTANCE_ID_LABEL);\n if (target.session.instances.has(instanceId)) throw new Error(`Loop instance '${instanceId}' already exists.`);\n const entry: InstanceEntry = { instanceId, launcher, abortController: new AbortController(), state: 'starting' };\n target.session.instances.set(instanceId, entry);\n notify(target.session);\n try {\n const handle = await launcher.options.launch({ instanceId, signal: entry.abortController.signal });\n const active = target.session.instances.get(instanceId) === entry && !entry.abortController.signal.aborted;\n if (!handle) {\n if (target.session.instances.get(instanceId) === entry) target.session.instances.delete(instanceId);\n notify(target.session);\n return undefined;\n }\n if (!active || handle.instanceId !== instanceId) {\n await handle.stop(active ? 'Loop launcher returned a mismatched instance id.' : 'Loop launch was cancelled.');\n if (target.session.instances.get(instanceId) === entry) target.session.instances.delete(instanceId);\n notify(target.session);\n if (active) throw new Error(`Loop launcher '${normalizedLauncherId}' returned a mismatched instance id.`);\n return undefined;\n }\n entry.handle = handle;\n entry.state = 'running';\n entry.startedAt = new Date().toISOString();\n notify(target.session);\n return snapshot(entry);\n } catch (error) {\n const cancelled = entry.abortController.signal.aborted;\n if (target.session.instances.get(instanceId) === entry) target.session.instances.delete(instanceId);\n notify(target.session);\n if (cancelled) return undefined;\n throw error;\n }\n },\n async stop(instanceId, reason) {\n const session = current(false)?.session;\n if (!session) return false;\n const entry = session.instances.get(validId(instanceId, INSTANCE_ID_LABEL));\n if (!entry) return false;\n await stopEntry(session, entry, reason);\n return true;\n },\n async stopAll(reason) {\n const session = current(false)?.session;\n if (!session) return;\n await Promise.allSettled([...session.instances.values()].map((entry) => stopEntry(session, entry, reason)));\n },\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import { LoopLauncherClient, LoopLauncherRegistration, RegisterLoopLauncherOptions } from "../../types/loop.cjs";
2
+ //#region src/services/loop/launcher.d.ts
3
+ declare function registerLoopLauncher(sessionId: string, options: RegisterLoopLauncherOptions): LoopLauncherRegistration;
4
+ declare function createLoopLauncherClient(sessionId: () => string | undefined): LoopLauncherClient;
5
+ //#endregion
6
+ export { createLoopLauncherClient, registerLoopLauncher };
7
+ //# sourceMappingURL=launcher.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.d.cts","names":[],"sources":["../../../src/services/loop/launcher.ts"],"mappings":";;iBAuGgB,qBACd,mBACA,SAAS,8BACR;iBA0Ba,yBAAyB,sCAAsC"}
@@ -0,0 +1,7 @@
1
+ import { LoopLauncherClient, LoopLauncherRegistration, RegisterLoopLauncherOptions } from "../../types/loop.mjs";
2
+ //#region src/services/loop/launcher.d.ts
3
+ declare function registerLoopLauncher(sessionId: string, options: RegisterLoopLauncherOptions): LoopLauncherRegistration;
4
+ declare function createLoopLauncherClient(sessionId: () => string | undefined): LoopLauncherClient;
5
+ //#endregion
6
+ export { createLoopLauncherClient, registerLoopLauncher };
7
+ //# sourceMappingURL=launcher.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.d.mts","names":[],"sources":["../../../src/services/loop/launcher.ts"],"mappings":";;iBAuGgB,qBACd,mBACA,SAAS,8BACR;iBA0Ba,yBAAyB,sCAAsC"}
@@ -0,0 +1,2 @@
1
+ const e=Symbol.for(`@agimon-ai/doompi-loop.launchers.v1`),t=`loop session id`,n=`loop launcher id`,r=`loop instance id`;function i(){let t=globalThis,n=t[e];if(n)return n;let r=new Map;return t[e]=r,r}function a(e,t){let n=e.trim(),r=!1;for(let e of n){let t=e.codePointAt(0);if(t!==void 0&&(t<=31||t===127)){r=!0;break}}if(!n||n.length>256||r)throw Error(`Invalid ${t}.`);return n}function o(e,n){let r=a(e,t),o=i().get(r);if(o||!n)return o;let s={launchers:new Map,instances:new Map,listeners:new Set};return i().set(r,s),s}function s(e){for(let t of e.listeners)t()}function c(e){return{instanceId:e.instanceId,launcherId:e.launcher.options.id,launcherLabel:e.launcher.options.label,...e.handle?.label?{label:e.handle.label}:{},...e.handle?.detail?{detail:e.handle.detail}:{},state:e.state,...e.startedAt?{startedAt:e.startedAt}:{}}}async function l(e,t,n){if(e.instances.get(t.instanceId)===t){t.state=`stopping`,t.abortController.abort(n),s(e);try{await t.handle?.stop(n)}finally{e.instances.get(t.instanceId)===t&&e.instances.delete(t.instanceId),s(e)}}}function u(e,t){t.launchers.size||t.instances.size||t.listeners.size||i().get(e)===t&&i().delete(e)}function d(e,r){let i=a(e,t),c=a(r.id,n),d=o(i,!0);if(d.launchers.has(c))throw Error(`Loop launcher '${c}' is already registered for session '${i}'.`);let f={token:Symbol(c),options:{...r,id:c}};d.launchers.set(c,f),s(d);let p=!1;return{async dispose(e=`Loop launcher disposed.`){if(p)return;p=!0;let t=o(i,!1);if(!t||t.launchers.get(c)?.token!==f.token)return;t.launchers.delete(c);let n=[...t.instances.values()].filter(e=>e.launcher.token===f.token);await Promise.allSettled(n.map(n=>l(t,n,e))),s(t),u(i,t)}}}function f(e){let i=n=>{let r=e();if(!r)return;let i=a(r,t),s=o(i,n);return s?{key:i,session:s}:void 0};return{listLaunchers(){let e=i(!1)?.session;return e?[...e.launchers.values()].map(({options:e})=>({id:e.id,source:e.source,label:e.label,...e.description?{description:e.description}:{}})).sort((e,t)=>e.label.localeCompare(t.label)||e.id.localeCompare(t.id)):[]},listInstances(){let e=i(!1)?.session;return e?[...e.instances.values()].map(c).sort((e,t)=>e.instanceId.localeCompare(t.instanceId)):[]},subscribe(e){let t=i(!0);if(!t)return()=>void 0;t.session.listeners.add(e);let n=!1;return()=>{n||(n=!0,t.session.listeners.delete(e),u(t.key,t.session))}},async launch(e,t){let o=i(!0);if(!o)throw Error(`The session-bound loop launcher registry is unavailable.`);let l=a(e,n),u=o.session.launchers.get(l);if(!u)throw Error(`Loop launcher '${l}' is unavailable.`);let d=a(t?.instanceId??globalThis.crypto.randomUUID(),r);if(o.session.instances.has(d))throw Error(`Loop instance '${d}' already exists.`);let f={instanceId:d,launcher:u,abortController:new AbortController,state:`starting`};o.session.instances.set(d,f),s(o.session);try{let e=await u.options.launch({instanceId:d,signal:f.abortController.signal}),t=o.session.instances.get(d)===f&&!f.abortController.signal.aborted;if(!e){o.session.instances.get(d)===f&&o.session.instances.delete(d),s(o.session);return}if(!t||e.instanceId!==d){if(await e.stop(t?`Loop launcher returned a mismatched instance id.`:`Loop launch was cancelled.`),o.session.instances.get(d)===f&&o.session.instances.delete(d),s(o.session),t)throw Error(`Loop launcher '${l}' returned a mismatched instance id.`);return}return f.handle=e,f.state=`running`,f.startedAt=new Date().toISOString(),s(o.session),c(f)}catch(e){let t=f.abortController.signal.aborted;if(o.session.instances.get(d)===f&&o.session.instances.delete(d),s(o.session),t)return;throw e}},async stop(e,t){let n=i(!1)?.session;if(!n)return!1;let o=n.instances.get(a(e,r));return o?(await l(n,o,t),!0):!1},async stopAll(e){let t=i(!1)?.session;t&&await Promise.allSettled([...t.instances.values()].map(n=>l(t,n,e)))}}}export{f as createLoopLauncherClient,d as registerLoopLauncher};
2
+ //# sourceMappingURL=launcher.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.mjs","names":[],"sources":["../../../src/services/loop/launcher.ts"],"sourcesContent":["import type {\n LoopInstanceSnapshot,\n LoopLauncherClient,\n LoopLauncherRegistration,\n RegisterLoopLauncherOptions,\n StoppableLoop,\n} from '../../types/loop.ts';\n\nconst REGISTRY_KEY = Symbol.for('@agimon-ai/doompi-loop.launchers.v1');\nconst MAX_ID_LENGTH = 256;\nconst SESSION_ID_LABEL = 'loop session id';\nconst LAUNCHER_ID_LABEL = 'loop launcher id';\nconst INSTANCE_ID_LABEL = 'loop instance id';\n\ninterface LauncherEntry {\n readonly token: symbol;\n readonly options: RegisterLoopLauncherOptions;\n}\n\ninterface InstanceEntry {\n readonly instanceId: string;\n readonly launcher: LauncherEntry;\n readonly abortController: AbortController;\n state: LoopInstanceSnapshot['state'];\n handle?: StoppableLoop;\n startedAt?: string;\n}\n\ninterface SessionRegistry {\n readonly launchers: Map<string, LauncherEntry>;\n readonly instances: Map<string, InstanceEntry>;\n readonly listeners: Set<() => void>;\n}\n\ntype LoopRegistry = Map<string, SessionRegistry>;\n\nfunction registry(): LoopRegistry {\n const root = globalThis as typeof globalThis & { [REGISTRY_KEY]?: LoopRegistry };\n const existing = root[REGISTRY_KEY];\n if (existing) return existing;\n\n const created: LoopRegistry = new Map();\n root[REGISTRY_KEY] = created;\n return created;\n}\n\nfunction validId(value: string, label: string): string {\n const normalized = value.trim();\n let hasControl = false;\n for (const character of normalized) {\n const code = character.codePointAt(0);\n if (code !== undefined && (code <= 0x1f || code === 0x7f)) {\n hasControl = true;\n break;\n }\n }\n if (!normalized || normalized.length > MAX_ID_LENGTH || hasControl) throw new Error(`Invalid ${label}.`);\n return normalized;\n}\n\nfunction getSession(sessionId: string, create: boolean): SessionRegistry | undefined {\n const key = validId(sessionId, SESSION_ID_LABEL);\n const existing = registry().get(key);\n if (existing || !create) return existing;\n const created: SessionRegistry = { launchers: new Map(), instances: new Map(), listeners: new Set() };\n registry().set(key, created);\n return created;\n}\n\nfunction notify(session: SessionRegistry): void {\n for (const listener of session.listeners) listener();\n}\n\nfunction snapshot(entry: InstanceEntry): LoopInstanceSnapshot {\n return {\n instanceId: entry.instanceId,\n launcherId: entry.launcher.options.id,\n launcherLabel: entry.launcher.options.label,\n ...(entry.handle?.label ? { label: entry.handle.label } : {}),\n ...(entry.handle?.detail ? { detail: entry.handle.detail } : {}),\n state: entry.state,\n ...(entry.startedAt ? { startedAt: entry.startedAt } : {}),\n };\n}\n\nasync function stopEntry(session: SessionRegistry, entry: InstanceEntry, reason?: string): Promise<void> {\n if (session.instances.get(entry.instanceId) !== entry) return;\n entry.state = 'stopping';\n entry.abortController.abort(reason);\n notify(session);\n try {\n await entry.handle?.stop(reason);\n } finally {\n if (session.instances.get(entry.instanceId) === entry) session.instances.delete(entry.instanceId);\n notify(session);\n }\n}\n\nfunction cleanSession(sessionId: string, session: SessionRegistry): void {\n if (session.launchers.size || session.instances.size || session.listeners.size) return;\n if (registry().get(sessionId) === session) registry().delete(sessionId);\n}\n\nexport function registerLoopLauncher(\n sessionId: string,\n options: RegisterLoopLauncherOptions,\n): LoopLauncherRegistration {\n const sessionKey = validId(sessionId, SESSION_ID_LABEL);\n const launcherId = validId(options.id, LAUNCHER_ID_LABEL);\n const session = getSession(sessionKey, true) as SessionRegistry;\n if (session.launchers.has(launcherId)) {\n throw new Error(`Loop launcher '${launcherId}' is already registered for session '${sessionKey}'.`);\n }\n const entry: LauncherEntry = { token: Symbol(launcherId), options: { ...options, id: launcherId } };\n session.launchers.set(launcherId, entry);\n notify(session);\n let disposed = false;\n return {\n async dispose(reason = 'Loop launcher disposed.'): Promise<void> {\n if (disposed) return;\n disposed = true;\n const current = getSession(sessionKey, false);\n if (!current || current.launchers.get(launcherId)?.token !== entry.token) return;\n current.launchers.delete(launcherId);\n const owned = [...current.instances.values()].filter((instance) => instance.launcher.token === entry.token);\n await Promise.allSettled(owned.map((instance) => stopEntry(current, instance, reason)));\n notify(current);\n cleanSession(sessionKey, current);\n },\n };\n}\n\nexport function createLoopLauncherClient(sessionId: () => string | undefined): LoopLauncherClient {\n const current = (create: boolean): { key: string; session: SessionRegistry } | undefined => {\n const value = sessionId();\n if (!value) return undefined;\n const key = validId(value, SESSION_ID_LABEL);\n const session = getSession(key, create);\n return session ? { key, session } : undefined;\n };\n return {\n listLaunchers() {\n const session = current(false)?.session;\n if (!session) return [];\n return [...session.launchers.values()]\n .map(({ options }) => ({\n id: options.id,\n source: options.source,\n label: options.label,\n ...(options.description ? { description: options.description } : {}),\n }))\n .sort((left, right) => left.label.localeCompare(right.label) || left.id.localeCompare(right.id));\n },\n listInstances() {\n const session = current(false)?.session;\n return session\n ? [...session.instances.values()]\n .map(snapshot)\n .sort((left, right) => left.instanceId.localeCompare(right.instanceId))\n : [];\n },\n subscribe(listener) {\n const target = current(true);\n if (!target) return () => undefined;\n target.session.listeners.add(listener);\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n target.session.listeners.delete(listener);\n cleanSession(target.key, target.session);\n };\n },\n async launch(launcherId, options) {\n const target = current(true);\n if (!target) throw new Error('The session-bound loop launcher registry is unavailable.');\n const normalizedLauncherId = validId(launcherId, LAUNCHER_ID_LABEL);\n const launcher = target.session.launchers.get(normalizedLauncherId);\n if (!launcher) throw new Error(`Loop launcher '${normalizedLauncherId}' is unavailable.`);\n const instanceId = validId(options?.instanceId ?? globalThis.crypto.randomUUID(), INSTANCE_ID_LABEL);\n if (target.session.instances.has(instanceId)) throw new Error(`Loop instance '${instanceId}' already exists.`);\n const entry: InstanceEntry = { instanceId, launcher, abortController: new AbortController(), state: 'starting' };\n target.session.instances.set(instanceId, entry);\n notify(target.session);\n try {\n const handle = await launcher.options.launch({ instanceId, signal: entry.abortController.signal });\n const active = target.session.instances.get(instanceId) === entry && !entry.abortController.signal.aborted;\n if (!handle) {\n if (target.session.instances.get(instanceId) === entry) target.session.instances.delete(instanceId);\n notify(target.session);\n return undefined;\n }\n if (!active || handle.instanceId !== instanceId) {\n await handle.stop(active ? 'Loop launcher returned a mismatched instance id.' : 'Loop launch was cancelled.');\n if (target.session.instances.get(instanceId) === entry) target.session.instances.delete(instanceId);\n notify(target.session);\n if (active) throw new Error(`Loop launcher '${normalizedLauncherId}' returned a mismatched instance id.`);\n return undefined;\n }\n entry.handle = handle;\n entry.state = 'running';\n entry.startedAt = new Date().toISOString();\n notify(target.session);\n return snapshot(entry);\n } catch (error) {\n const cancelled = entry.abortController.signal.aborted;\n if (target.session.instances.get(instanceId) === entry) target.session.instances.delete(instanceId);\n notify(target.session);\n if (cancelled) return undefined;\n throw error;\n }\n },\n async stop(instanceId, reason) {\n const session = current(false)?.session;\n if (!session) return false;\n const entry = session.instances.get(validId(instanceId, INSTANCE_ID_LABEL));\n if (!entry) return false;\n await stopEntry(session, entry, reason);\n return true;\n },\n async stopAll(reason) {\n const session = current(false)?.session;\n if (!session) return;\n await Promise.allSettled([...session.instances.values()].map((entry) => stopEntry(session, entry, reason)));\n },\n };\n}\n"],"mappings":"AAQA,MAAM,EAAe,OAAO,IAAI,qCAAqC,EAE/D,EAAmB,kBACnB,EAAoB,mBACpB,EAAoB,mBAwB1B,SAAS,GAAyB,CAChC,IAAM,EAAO,WACP,EAAW,EAAK,GACtB,GAAI,EAAU,OAAO,EAErB,IAAM,EAAwB,IAAI,IAElC,MADA,GAAK,GAAgB,EACd,CACT,CAEA,SAAS,EAAQ,EAAe,EAAuB,CACrD,IAAM,EAAa,EAAM,KAAK,EAC1B,EAAa,GACjB,IAAK,IAAM,KAAa,EAAY,CAClC,IAAM,EAAO,EAAU,YAAY,CAAC,EACpC,GAAI,IAAS,IAAA,KAAc,GAAQ,IAAQ,IAAS,KAAO,CACzD,EAAa,GACb,KACF,CACF,CACA,GAAI,CAAC,GAAc,EAAW,OAAS,KAAiB,EAAY,MAAU,MAAM,WAAW,EAAM,EAAE,EACvG,OAAO,CACT,CAEA,SAAS,EAAW,EAAmB,EAA8C,CACnF,IAAM,EAAM,EAAQ,EAAW,CAAgB,EACzC,EAAW,EAAS,CAAC,CAAC,IAAI,CAAG,EACnC,GAAI,GAAY,CAAC,EAAQ,OAAO,EAChC,IAAM,EAA2B,CAAE,UAAW,IAAI,IAAO,UAAW,IAAI,IAAO,UAAW,IAAI,GAAM,EAEpG,OADA,EAAS,CAAC,CAAC,IAAI,EAAK,CAAO,EACpB,CACT,CAEA,SAAS,EAAO,EAAgC,CAC9C,IAAK,IAAM,KAAY,EAAQ,UAAW,EAAS,CACrD,CAEA,SAAS,EAAS,EAA4C,CAC5D,MAAO,CACL,WAAY,EAAM,WAClB,WAAY,EAAM,SAAS,QAAQ,GACnC,cAAe,EAAM,SAAS,QAAQ,MACtC,GAAI,EAAM,QAAQ,MAAQ,CAAE,MAAO,EAAM,OAAO,KAAM,EAAI,CAAC,EAC3D,GAAI,EAAM,QAAQ,OAAS,CAAE,OAAQ,EAAM,OAAO,MAAO,EAAI,CAAC,EAC9D,MAAO,EAAM,MACb,GAAI,EAAM,UAAY,CAAE,UAAW,EAAM,SAAU,EAAI,CAAC,CAC1D,CACF,CAEA,eAAe,EAAU,EAA0B,EAAsB,EAAgC,CACnG,KAAQ,UAAU,IAAI,EAAM,UAAU,IAAM,EAGhD,CAFA,EAAM,MAAQ,WACd,EAAM,gBAAgB,MAAM,CAAM,EAClC,EAAO,CAAO,EACd,GAAI,CACF,MAAM,EAAM,QAAQ,KAAK,CAAM,CACjC,QAAU,CACJ,EAAQ,UAAU,IAAI,EAAM,UAAU,IAAM,GAAO,EAAQ,UAAU,OAAO,EAAM,UAAU,EAChG,EAAO,CAAO,CAChB,CANc,CAOhB,CAEA,SAAS,EAAa,EAAmB,EAAgC,CACnE,EAAQ,UAAU,MAAQ,EAAQ,UAAU,MAAQ,EAAQ,UAAU,MACtE,EAAS,CAAC,CAAC,IAAI,CAAS,IAAM,GAAS,EAAS,CAAC,CAAC,OAAO,CAAS,CACxE,CAEA,SAAgB,EACd,EACA,EAC0B,CAC1B,IAAM,EAAa,EAAQ,EAAW,CAAgB,EAChD,EAAa,EAAQ,EAAQ,GAAI,CAAiB,EAClD,EAAU,EAAW,EAAY,EAAI,EAC3C,GAAI,EAAQ,UAAU,IAAI,CAAU,EAClC,MAAU,MAAM,kBAAkB,EAAW,uCAAuC,EAAW,GAAG,EAEpG,IAAM,EAAuB,CAAE,MAAO,OAAO,CAAU,EAAG,QAAS,CAAE,GAAG,EAAS,GAAI,CAAW,CAAE,EAClG,EAAQ,UAAU,IAAI,EAAY,CAAK,EACvC,EAAO,CAAO,EACd,IAAI,EAAW,GACf,MAAO,CACL,MAAM,QAAQ,EAAS,0BAA0C,CAC/D,GAAI,EAAU,OACd,EAAW,GACX,IAAM,EAAU,EAAW,EAAY,EAAK,EAC5C,GAAI,CAAC,GAAW,EAAQ,UAAU,IAAI,CAAU,CAAC,EAAE,QAAU,EAAM,MAAO,OAC1E,EAAQ,UAAU,OAAO,CAAU,EACnC,IAAM,EAAQ,CAAC,GAAG,EAAQ,UAAU,OAAO,CAAC,CAAC,CAAC,OAAQ,GAAa,EAAS,SAAS,QAAU,EAAM,KAAK,EAC1G,MAAM,QAAQ,WAAW,EAAM,IAAK,GAAa,EAAU,EAAS,EAAU,CAAM,CAAC,CAAC,EACtF,EAAO,CAAO,EACd,EAAa,EAAY,CAAO,CAClC,CACF,CACF,CAEA,SAAgB,EAAyB,EAAyD,CAChG,IAAM,EAAW,GAA2E,CAC1F,IAAM,EAAQ,EAAU,EACxB,GAAI,CAAC,EAAO,OACZ,IAAM,EAAM,EAAQ,EAAO,CAAgB,EACrC,EAAU,EAAW,EAAK,CAAM,EACtC,OAAO,EAAU,CAAE,MAAK,SAAQ,EAAI,IAAA,EACtC,EACA,MAAO,CACL,eAAgB,CACd,IAAM,EAAU,EAAQ,EAAK,CAAC,EAAE,QAEhC,OADK,EACE,CAAC,GAAG,EAAQ,UAAU,OAAO,CAAC,CAAC,CACnC,KAAK,CAAE,cAAe,CACrB,GAAI,EAAQ,GACZ,OAAQ,EAAQ,OAChB,MAAO,EAAQ,MACf,GAAI,EAAQ,YAAc,CAAE,YAAa,EAAQ,WAAY,EAAI,CAAC,CACpE,EAAE,CAAC,CACF,MAAM,EAAM,IAAU,EAAK,MAAM,cAAc,EAAM,KAAK,GAAK,EAAK,GAAG,cAAc,EAAM,EAAE,CAAC,EAR5E,CAAC,CASxB,EACA,eAAgB,CACd,IAAM,EAAU,EAAQ,EAAK,CAAC,EAAE,QAChC,OAAO,EACH,CAAC,GAAG,EAAQ,UAAU,OAAO,CAAC,CAAC,CAC5B,IAAI,CAAQ,CAAC,CACb,MAAM,EAAM,IAAU,EAAK,WAAW,cAAc,EAAM,UAAU,CAAC,EACxE,CAAC,CACP,EACA,UAAU,EAAU,CAClB,IAAM,EAAS,EAAQ,EAAI,EAC3B,GAAI,CAAC,EAAQ,UAAa,IAAA,GAC1B,EAAO,QAAQ,UAAU,IAAI,CAAQ,EACrC,IAAI,EAAW,GACf,UAAa,CACP,IACJ,EAAW,GACX,EAAO,QAAQ,UAAU,OAAO,CAAQ,EACxC,EAAa,EAAO,IAAK,EAAO,OAAO,EACzC,CACF,EACA,MAAM,OAAO,EAAY,EAAS,CAChC,IAAM,EAAS,EAAQ,EAAI,EAC3B,GAAI,CAAC,EAAQ,MAAU,MAAM,0DAA0D,EACvF,IAAM,EAAuB,EAAQ,EAAY,CAAiB,EAC5D,EAAW,EAAO,QAAQ,UAAU,IAAI,CAAoB,EAClE,GAAI,CAAC,EAAU,MAAU,MAAM,kBAAkB,EAAqB,kBAAkB,EACxF,IAAM,EAAa,EAAQ,GAAS,YAAc,WAAW,OAAO,WAAW,EAAG,CAAiB,EACnG,GAAI,EAAO,QAAQ,UAAU,IAAI,CAAU,EAAG,MAAU,MAAM,kBAAkB,EAAW,kBAAkB,EAC7G,IAAM,EAAuB,CAAE,aAAY,WAAU,gBAAiB,IAAI,gBAAmB,MAAO,UAAW,EAC/G,EAAO,QAAQ,UAAU,IAAI,EAAY,CAAK,EAC9C,EAAO,EAAO,OAAO,EACrB,GAAI,CACF,IAAM,EAAS,MAAM,EAAS,QAAQ,OAAO,CAAE,aAAY,OAAQ,EAAM,gBAAgB,MAAO,CAAC,EAC3F,EAAS,EAAO,QAAQ,UAAU,IAAI,CAAU,IAAM,GAAS,CAAC,EAAM,gBAAgB,OAAO,QACnG,GAAI,CAAC,EAAQ,CACP,EAAO,QAAQ,UAAU,IAAI,CAAU,IAAM,GAAO,EAAO,QAAQ,UAAU,OAAO,CAAU,EAClG,EAAO,EAAO,OAAO,EACrB,MACF,CACA,GAAI,CAAC,GAAU,EAAO,aAAe,EAAY,CAI/C,GAHA,MAAM,EAAO,KAAK,EAAS,mDAAqD,4BAA4B,EACxG,EAAO,QAAQ,UAAU,IAAI,CAAU,IAAM,GAAO,EAAO,QAAQ,UAAU,OAAO,CAAU,EAClG,EAAO,EAAO,OAAO,EACjB,EAAQ,MAAU,MAAM,kBAAkB,EAAqB,qCAAqC,EACxG,MACF,CAKA,MAJA,GAAM,OAAS,EACf,EAAM,MAAQ,UACd,EAAM,UAAY,IAAI,KAAK,CAAA,CAAE,YAAY,EACzC,EAAO,EAAO,OAAO,EACd,EAAS,CAAK,CACvB,OAAS,EAAO,CACd,IAAM,EAAY,EAAM,gBAAgB,OAAO,QAG/C,GAFI,EAAO,QAAQ,UAAU,IAAI,CAAU,IAAM,GAAO,EAAO,QAAQ,UAAU,OAAO,CAAU,EAClG,EAAO,EAAO,OAAO,EACjB,EAAW,OACf,MAAM,CACR,CACF,EACA,MAAM,KAAK,EAAY,EAAQ,CAC7B,IAAM,EAAU,EAAQ,EAAK,CAAC,EAAE,QAChC,GAAI,CAAC,EAAS,MAAO,GACrB,IAAM,EAAQ,EAAQ,UAAU,IAAI,EAAQ,EAAY,CAAiB,CAAC,EAG1E,OAFK,GACL,MAAM,EAAU,EAAS,EAAO,CAAM,EAC/B,IAFY,EAGrB,EACA,MAAM,QAAQ,EAAQ,CACpB,IAAM,EAAU,EAAQ,EAAK,CAAC,EAAE,QAC3B,GACL,MAAM,QAAQ,WAAW,CAAC,GAAG,EAAQ,UAAU,OAAO,CAAC,CAAC,CAAC,IAAK,GAAU,EAAU,EAAS,EAAO,CAAM,CAAC,CAAC,CAC5G,CACF,CACF"}
@@ -1,2 +1,2 @@
1
- let e=require("@agimon-ai/doompi-ui/components/doomOverlay"),t=require("@earendil-works/pi-tui");const n=`Stopped from the loop list.`;var r=class extends e.DoomOverlay{client;done;unsubscribe;instances;selected=0;constructor(e,t,n,r){super(e,t),this.client=n,this.done=r,this.instances=n.listInstances(),this.unsubscribe=n.subscribe(()=>this.refresh())}dispose(){this.unsubscribe()}handleInput(e){if((0,t.matchesKey)(e,`escape`)||(0,t.matchesKey)(e,`ctrl+c`)||e===`q`)return this.done(void 0);if((0,t.matchesKey)(e,`up`)||e===`k`)return this.move(-1);if((0,t.matchesKey)(e,`down`)||e===`j`)return this.move(1);if(e===`x`){let e=this.instances[this.selected];e&&this.client.stop(e.instanceId,n)}}getChrome(){return{title:`LOOPS`,breadcrumb:`SPC › l / loops › l / list`,headerRight:`${this.instances.length} active`,footer:`x stop · j/k move · esc close · launched detached work continues`,footerHints:[[`x`,`stop scheduling`],[`j/k`,`move`],[`esc`,`close`]],accent:e.DOOM_OVERLAY_ACCENT}}renderBody(e){let n=this.theme.fg(`dim`,(0,t.truncateToWidth)(`Stopping a loop does not cancel detached work already launched.`,e));return this.instances.length?[n,``,...this.instances.flatMap((n,r)=>{let i=r===this.selected?this.theme.fg(`accent`,`▸`):` `,a=n.label??n.launcherLabel,o=this.theme.fg(n.state===`running`?`success`:`warning`,n.state),s=(0,t.truncateToWidth)(`${i} ${a} · ${o}`,e),c=n.detail??n.instanceId;return[s,` ${this.theme.fg(`dim`,(0,t.truncateToWidth)(c,Math.max(0,e-4)))}`]})]:[this.theme.fg(`dim`,`No loop instances are active in this session.`),``,n]}refresh(){this.instances=this.client.listInstances(),this.selected=Math.min(this.selected,Math.max(0,this.instances.length-1)),this.invalidate()}move(e){this.instances.length&&(this.selected=Math.max(0,Math.min(this.selected+e,this.instances.length-1)),this.invalidate())}};async function i(t,i){if(t.mode!==`tui`){let e=i.listInstances(),r=e.map(e=>`${e.label??e.launcherLabel} (${e.instanceId})`),a=await t.ui.select(`Stop loop scheduling`,r),o=e[r.indexOf(a??``)];o&&await i.stop(o.instanceId,n);return}await t.ui.custom((e,t,n,a)=>new r(e,t,i,a),e.DOOM_FULLSCREEN_UI_OPTIONS)}exports.LoopListOverlay=r,exports.openLoopListOverlay=i;
1
+ let e=require("@agimon-ai/doompi-ui/components/doomOverlay"),t=require("@earendil-works/pi-tui");const n=`Stopped from the loop list.`;var r=class extends e.DoomOverlay{client;done;unsubscribe;instances;selected=0;constructor(e,t,n,r){super(e,t),this.client=n,this.done=r,this.instances=n.listInstances(),this.unsubscribe=n.subscribe(()=>this.refresh())}dispose(){this.unsubscribe()}handleInput(e){if((0,t.matchesKey)(e,`escape`)||(0,t.matchesKey)(e,`ctrl+c`)||e===`q`){this.done(void 0);return}if((0,t.matchesKey)(e,`up`)||e===`k`){this.move(-1);return}if((0,t.matchesKey)(e,`down`)||e===`j`){this.move(1);return}if(e===`x`){let e=this.instances[this.selected];e&&this.client.stop(e.instanceId,n)}}getChrome(){return{title:`LOOPS`,breadcrumb:`SPC › l / loops › l / list`,headerRight:`${this.instances.length} active`,footer:`${e.DOOM_NAVIGATION_KEYS.list} move · x stop · esc close · launched detached work continues`,footerHints:[[e.DOOM_NAVIGATION_KEYS.list,`move`],[`x`,`stop scheduling`],[`esc`,`close`]],accent:e.DOOM_OVERLAY_ACCENT}}renderBody(e){let n=this.theme.fg(`dim`,(0,t.truncateToWidth)(`Stopping a loop does not cancel detached work already launched.`,e));return this.instances.length?[n,``,...this.instances.flatMap((n,r)=>{let i=r===this.selected?this.theme.fg(`accent`,`▸`):` `,a=n.label??n.launcherLabel,o=this.theme.fg(n.state===`running`?`success`:`warning`,n.state),s=(0,t.truncateToWidth)(`${i} ${a} · ${o}`,e),c=n.detail??n.instanceId;return[s,` ${this.theme.fg(`dim`,(0,t.truncateToWidth)(c,Math.max(0,e-4)))}`]})]:[this.theme.fg(`dim`,`No loop instances are active in this session.`),``,n]}refresh(){this.instances=this.client.listInstances(),this.selected=Math.min(this.selected,Math.max(0,this.instances.length-1)),this.invalidate()}move(e){this.instances.length&&(this.selected=Math.max(0,Math.min(this.selected+e,this.instances.length-1)),this.invalidate())}};async function i(t,i){if(t.mode!==`tui`){let e=i.listInstances(),r=e.map(e=>`${e.label??e.launcherLabel} (${e.instanceId})`),a=await t.ui.select(`Stop loop scheduling`,r),o=e[r.indexOf(a??``)];o&&await i.stop(o.instanceId,n);return}await t.ui.custom((e,t,n,a)=>new r(e,t,i,a),e.DOOM_FULLSCREEN_UI_OPTIONS)}exports.LoopListOverlay=r,exports.openLoopListOverlay=i;
2
2
  //# sourceMappingURL=loopListOverlay.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"loopListOverlay.cjs","names":["DoomOverlay","matchesKey","DOOM_OVERLAY_ACCENT","truncateToWidth","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/tui/loopListOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { LoopInstanceSnapshot, LoopLauncherClient } from '../launcher.ts';\n\nconst EMPTY_MESSAGE = 'No loop instances are active in this session.';\nconst STOP_SCOPE_MESSAGE = 'Stopping a loop does not cancel detached work already launched.';\nconst STOP_REASON = 'Stopped from the loop list.';\n\nexport class LoopListOverlay extends DoomOverlay {\n private readonly unsubscribe: () => void;\n private instances: readonly LoopInstanceSnapshot[];\n private selected = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly client: LoopLauncherClient,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n this.instances = client.listInstances();\n this.unsubscribe = client.subscribe(() => this.refresh());\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') return this.done(undefined);\n if (matchesKey(data, 'up') || data === 'k') return this.move(-1);\n if (matchesKey(data, 'down') || data === 'j') return this.move(1);\n if (data === 'x') {\n const instance = this.instances[this.selected];\n if (instance) void this.client.stop(instance.instanceId, STOP_REASON);\n }\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: 'LOOPS',\n breadcrumb: 'SPC › l / loops › l / list',\n headerRight: `${this.instances.length} active`,\n footer: 'x stop · j/k move · esc close · launched detached work continues',\n footerHints: [\n ['x', 'stop scheduling'],\n ['j/k', 'move'],\n ['esc', 'close'],\n ],\n accent: DOOM_OVERLAY_ACCENT,\n };\n }\n\n protected renderBody(width: number): string[] {\n const stopScope = this.theme.fg('dim', truncateToWidth(STOP_SCOPE_MESSAGE, width));\n if (!this.instances.length) return [this.theme.fg('dim', EMPTY_MESSAGE), '', stopScope];\n return [\n stopScope,\n '',\n ...this.instances.flatMap((instance, index) => {\n const marker = index === this.selected ? this.theme.fg('accent', '▸') : ' ';\n const name = instance.label ?? instance.launcherLabel;\n const state = this.theme.fg(instance.state === 'running' ? 'success' : 'warning', instance.state);\n const line = truncateToWidth(`${marker} ${name} · ${state}`, width);\n const detail = instance.detail ?? instance.instanceId;\n return [line, ` ${this.theme.fg('dim', truncateToWidth(detail, Math.max(0, width - 4)))}`];\n }),\n ];\n }\n\n private refresh(): void {\n this.instances = this.client.listInstances();\n this.selected = Math.min(this.selected, Math.max(0, this.instances.length - 1));\n this.invalidate();\n }\n\n private move(delta: number): void {\n if (!this.instances.length) return;\n this.selected = Math.max(0, Math.min(this.selected + delta, this.instances.length - 1));\n this.invalidate();\n }\n}\n\nexport async function openLoopListOverlay(ctx: ExtensionContext, client: LoopLauncherClient): Promise<void> {\n if (ctx.mode !== 'tui') {\n const instances = client.listInstances();\n const labels = instances.map((instance) => `${instance.label ?? instance.launcherLabel} (${instance.instanceId})`);\n const selected = await ctx.ui.select('Stop loop scheduling', labels);\n const instance = instances[labels.indexOf(selected ?? '')];\n if (instance) await client.stop(instance.instanceId, STOP_REASON);\n return;\n }\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new LoopListOverlay(tui, theme, client, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"iGAWA,MAEM,EAAc,8BAEpB,IAAa,EAAb,cAAqCA,EAAAA,WAAY,CAQ5B,OACA,KARnB,YACA,UACA,SAAmB,EAEnB,YACE,EACA,EACA,EACA,EACA,CACA,MAAM,EAAK,CAAK,EAHC,KAAA,OAAA,EACA,KAAA,KAAA,EAGjB,KAAK,UAAY,EAAO,cAAc,EACtC,KAAK,YAAc,EAAO,cAAgB,KAAK,QAAQ,CAAC,CAC1D,CAEA,SAAgB,CACd,KAAK,YAAY,CACnB,CAEA,YAAY,EAAoB,CAC9B,IAAA,EAAIC,EAAAA,WAAAA,CAAW,EAAM,QAAQ,IAAA,EAAKA,EAAAA,WAAAA,CAAW,EAAM,QAAQ,GAAK,IAAS,IAAK,OAAO,KAAK,KAAK,IAAA,EAAS,EACxG,IAAA,EAAIA,EAAAA,WAAAA,CAAW,EAAM,IAAI,GAAK,IAAS,IAAK,OAAO,KAAK,KAAK,EAAE,EAC/D,IAAA,EAAIA,EAAAA,WAAAA,CAAW,EAAM,MAAM,GAAK,IAAS,IAAK,OAAO,KAAK,KAAK,CAAC,EAChE,GAAI,IAAS,IAAK,CAChB,IAAM,EAAW,KAAK,UAAU,KAAK,UACjC,GAAU,KAAU,OAAO,KAAK,EAAS,WAAY,CAAW,CACtE,CACF,CAEA,WAAyC,CACvC,MAAO,CACL,MAAO,QACP,WAAY,6BACZ,YAAa,GAAG,KAAK,UAAU,OAAO,SACtC,OAAQ,mEACR,YAAa,CACX,CAAC,IAAK,iBAAiB,EACvB,CAAC,MAAO,MAAM,EACd,CAAC,MAAO,OAAO,CACjB,EACA,OAAQC,EAAAA,mBACV,CACF,CAEA,WAAqB,EAAyB,CAC5C,IAAM,EAAY,KAAK,MAAM,GAAG,OAAA,EAAOC,EAAAA,gBAAAA,CAAgB,kEAAoB,CAAK,CAAC,EAEjF,OADK,KAAK,UAAU,OACb,CACL,EACA,GACA,GAAG,KAAK,UAAU,SAAS,EAAU,IAAU,CAC7C,IAAM,EAAS,IAAU,KAAK,SAAW,KAAK,MAAM,GAAG,SAAU,GAAG,EAAI,IAClE,EAAO,EAAS,OAAS,EAAS,cAClC,EAAQ,KAAK,MAAM,GAAG,EAAS,QAAU,UAAY,UAAY,UAAW,EAAS,KAAK,EAC1F,GAAA,EAAOA,EAAAA,gBAAAA,CAAgB,GAAG,EAAO,GAAG,EAAK,KAAK,IAAS,CAAK,EAC5D,EAAS,EAAS,QAAU,EAAS,WAC3C,MAAO,CAAC,EAAM,OAAO,KAAK,MAAM,GAAG,OAAA,EAAOA,EAAAA,gBAAAA,CAAgB,EAAQ,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,CAAC,GAAG,CAC9F,CAAC,CACH,EAZmC,CAAC,KAAK,MAAM,GAAG,MAAO,+CAAa,EAAG,GAAI,CAAS,CAaxF,CAEA,SAAwB,CACtB,KAAK,UAAY,KAAK,OAAO,cAAc,EAC3C,KAAK,SAAW,KAAK,IAAI,KAAK,SAAU,KAAK,IAAI,EAAG,KAAK,UAAU,OAAS,CAAC,CAAC,EAC9E,KAAK,WAAW,CAClB,CAEA,KAAa,EAAqB,CAC3B,KAAK,UAAU,SACpB,KAAK,SAAW,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,SAAW,EAAO,KAAK,UAAU,OAAS,CAAC,CAAC,EACtF,KAAK,WAAW,EAClB,CACF,EAEA,eAAsB,EAAoB,EAAuB,EAA2C,CAC1G,GAAI,EAAI,OAAS,MAAO,CACtB,IAAM,EAAY,EAAO,cAAc,EACjC,EAAS,EAAU,IAAK,GAAa,GAAG,EAAS,OAAS,EAAS,cAAc,IAAI,EAAS,WAAW,EAAE,EAC3G,EAAW,MAAM,EAAI,GAAG,OAAO,uBAAwB,CAAM,EAC7D,EAAW,EAAU,EAAO,QAAQ,GAAY,EAAE,GACpD,GAAU,MAAM,EAAO,KAAK,EAAS,WAAY,CAAW,EAChE,MACF,CACA,MAAM,EAAI,GAAG,QACV,EAAK,EAAO,EAAc,IAAS,IAAI,EAAgB,EAAK,EAAO,EAAQ,CAAI,EAChFC,EAAAA,0BACF,CACF"}
1
+ {"version":3,"file":"loopListOverlay.cjs","names":["DoomOverlay","matchesKey","DOOM_NAVIGATION_KEYS","DOOM_OVERLAY_ACCENT","truncateToWidth","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/tui/loopListOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { LoopInstanceSnapshot, LoopLauncherClient } from '../types/loop.ts';\n\nconst EMPTY_MESSAGE = 'No loop instances are active in this session.';\nconst STOP_SCOPE_MESSAGE = 'Stopping a loop does not cancel detached work already launched.';\nconst STOP_REASON = 'Stopped from the loop list.';\n\nexport class LoopListOverlay extends DoomOverlay {\n private readonly unsubscribe: () => void;\n private instances: readonly LoopInstanceSnapshot[];\n private selected = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly client: LoopLauncherClient,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n this.instances = client.listInstances();\n this.unsubscribe = client.subscribe(() => this.refresh());\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.move(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.move(1);\n return;\n }\n if (data === 'x') {\n const instance = this.instances[this.selected];\n if (instance) void this.client.stop(instance.instanceId, STOP_REASON);\n }\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: 'LOOPS',\n breadcrumb: 'SPC › l / loops › l / list',\n headerRight: `${this.instances.length} active`,\n footer: `${DOOM_NAVIGATION_KEYS.list} move · x stop · esc close · launched detached work continues`,\n footerHints: [\n [DOOM_NAVIGATION_KEYS.list, 'move'],\n ['x', 'stop scheduling'],\n ['esc', 'close'],\n ],\n accent: DOOM_OVERLAY_ACCENT,\n };\n }\n\n protected renderBody(width: number): string[] {\n const stopScope = this.theme.fg('dim', truncateToWidth(STOP_SCOPE_MESSAGE, width));\n if (!this.instances.length) return [this.theme.fg('dim', EMPTY_MESSAGE), '', stopScope];\n return [\n stopScope,\n '',\n ...this.instances.flatMap((instance, index) => {\n const marker = index === this.selected ? this.theme.fg('accent', '▸') : ' ';\n const name = instance.label ?? instance.launcherLabel;\n const state = this.theme.fg(instance.state === 'running' ? 'success' : 'warning', instance.state);\n const line = truncateToWidth(`${marker} ${name} · ${state}`, width);\n const detail = instance.detail ?? instance.instanceId;\n return [line, ` ${this.theme.fg('dim', truncateToWidth(detail, Math.max(0, width - 4)))}`];\n }),\n ];\n }\n\n private refresh(): void {\n this.instances = this.client.listInstances();\n this.selected = Math.min(this.selected, Math.max(0, this.instances.length - 1));\n this.invalidate();\n }\n\n private move(delta: number): void {\n if (!this.instances.length) return;\n this.selected = Math.max(0, Math.min(this.selected + delta, this.instances.length - 1));\n this.invalidate();\n }\n}\n\nexport async function openLoopListOverlay(ctx: ExtensionContext, client: LoopLauncherClient): Promise<void> {\n if (ctx.mode !== 'tui') {\n const instances = client.listInstances();\n const labels = instances.map((instance) => `${instance.label ?? instance.launcherLabel} (${instance.instanceId})`);\n const selected = await ctx.ui.select('Stop loop scheduling', labels);\n const instance = instances[labels.indexOf(selected ?? '')];\n if (instance) await client.stop(instance.instanceId, STOP_REASON);\n return;\n }\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new LoopListOverlay(tui, theme, client, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{DOOM_FULLSCREEN_UI_OPTIONS as e,DOOM_OVERLAY_ACCENT as t,DoomOverlay as n}from"@agimon-ai/doompi-ui/components/doomOverlay";import{matchesKey as r,truncateToWidth as i}from"@earendil-works/pi-tui";const a=`Stopped from the loop list.`;var o=class extends n{client;done;unsubscribe;instances;selected=0;constructor(e,t,n,r){super(e,t),this.client=n,this.done=r,this.instances=n.listInstances(),this.unsubscribe=n.subscribe(()=>this.refresh())}dispose(){this.unsubscribe()}handleInput(e){if(r(e,`escape`)||r(e,`ctrl+c`)||e===`q`)return this.done(void 0);if(r(e,`up`)||e===`k`)return this.move(-1);if(r(e,`down`)||e===`j`)return this.move(1);if(e===`x`){let e=this.instances[this.selected];e&&this.client.stop(e.instanceId,a)}}getChrome(){return{title:`LOOPS`,breadcrumb:`SPC › l / loops › l / list`,headerRight:`${this.instances.length} active`,footer:`x stop · j/k move · esc close · launched detached work continues`,footerHints:[[`x`,`stop scheduling`],[`j/k`,`move`],[`esc`,`close`]],accent:t}}renderBody(e){let t=this.theme.fg(`dim`,i(`Stopping a loop does not cancel detached work already launched.`,e));return this.instances.length?[t,``,...this.instances.flatMap((t,n)=>{let r=n===this.selected?this.theme.fg(`accent`,`▸`):` `,a=t.label??t.launcherLabel,o=this.theme.fg(t.state===`running`?`success`:`warning`,t.state),s=i(`${r} ${a} · ${o}`,e),c=t.detail??t.instanceId;return[s,` ${this.theme.fg(`dim`,i(c,Math.max(0,e-4)))}`]})]:[this.theme.fg(`dim`,`No loop instances are active in this session.`),``,t]}refresh(){this.instances=this.client.listInstances(),this.selected=Math.min(this.selected,Math.max(0,this.instances.length-1)),this.invalidate()}move(e){this.instances.length&&(this.selected=Math.max(0,Math.min(this.selected+e,this.instances.length-1)),this.invalidate())}};async function s(t,n){if(t.mode!==`tui`){let e=n.listInstances(),r=e.map(e=>`${e.label??e.launcherLabel} (${e.instanceId})`),i=await t.ui.select(`Stop loop scheduling`,r),o=e[r.indexOf(i??``)];o&&await n.stop(o.instanceId,a);return}await t.ui.custom((e,t,r,i)=>new o(e,t,n,i),e)}export{o as LoopListOverlay,s as openLoopListOverlay};
1
+ import{DOOM_FULLSCREEN_UI_OPTIONS as e,DOOM_NAVIGATION_KEYS as t,DOOM_OVERLAY_ACCENT as n,DoomOverlay as r}from"@agimon-ai/doompi-ui/components/doomOverlay";import{matchesKey as i,truncateToWidth as a}from"@earendil-works/pi-tui";const o=`Stopped from the loop list.`;var s=class extends r{client;done;unsubscribe;instances;selected=0;constructor(e,t,n,r){super(e,t),this.client=n,this.done=r,this.instances=n.listInstances(),this.unsubscribe=n.subscribe(()=>this.refresh())}dispose(){this.unsubscribe()}handleInput(e){if(i(e,`escape`)||i(e,`ctrl+c`)||e===`q`){this.done(void 0);return}if(i(e,`up`)||e===`k`){this.move(-1);return}if(i(e,`down`)||e===`j`){this.move(1);return}if(e===`x`){let e=this.instances[this.selected];e&&this.client.stop(e.instanceId,o)}}getChrome(){return{title:`LOOPS`,breadcrumb:`SPC › l / loops › l / list`,headerRight:`${this.instances.length} active`,footer:`${t.list} move · x stop · esc close · launched detached work continues`,footerHints:[[t.list,`move`],[`x`,`stop scheduling`],[`esc`,`close`]],accent:n}}renderBody(e){let t=this.theme.fg(`dim`,a(`Stopping a loop does not cancel detached work already launched.`,e));return this.instances.length?[t,``,...this.instances.flatMap((t,n)=>{let r=n===this.selected?this.theme.fg(`accent`,`▸`):` `,i=t.label??t.launcherLabel,o=this.theme.fg(t.state===`running`?`success`:`warning`,t.state),s=a(`${r} ${i} · ${o}`,e),c=t.detail??t.instanceId;return[s,` ${this.theme.fg(`dim`,a(c,Math.max(0,e-4)))}`]})]:[this.theme.fg(`dim`,`No loop instances are active in this session.`),``,t]}refresh(){this.instances=this.client.listInstances(),this.selected=Math.min(this.selected,Math.max(0,this.instances.length-1)),this.invalidate()}move(e){this.instances.length&&(this.selected=Math.max(0,Math.min(this.selected+e,this.instances.length-1)),this.invalidate())}};async function c(t,n){if(t.mode!==`tui`){let e=n.listInstances(),r=e.map(e=>`${e.label??e.launcherLabel} (${e.instanceId})`),i=await t.ui.select(`Stop loop scheduling`,r),a=e[r.indexOf(i??``)];a&&await n.stop(a.instanceId,o);return}await t.ui.custom((e,t,r,i)=>new s(e,t,n,i),e)}export{s as LoopListOverlay,c as openLoopListOverlay};
2
2
  //# sourceMappingURL=loopListOverlay.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"loopListOverlay.mjs","names":[],"sources":["../../src/tui/loopListOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { LoopInstanceSnapshot, LoopLauncherClient } from '../launcher.ts';\n\nconst EMPTY_MESSAGE = 'No loop instances are active in this session.';\nconst STOP_SCOPE_MESSAGE = 'Stopping a loop does not cancel detached work already launched.';\nconst STOP_REASON = 'Stopped from the loop list.';\n\nexport class LoopListOverlay extends DoomOverlay {\n private readonly unsubscribe: () => void;\n private instances: readonly LoopInstanceSnapshot[];\n private selected = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly client: LoopLauncherClient,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n this.instances = client.listInstances();\n this.unsubscribe = client.subscribe(() => this.refresh());\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') return this.done(undefined);\n if (matchesKey(data, 'up') || data === 'k') return this.move(-1);\n if (matchesKey(data, 'down') || data === 'j') return this.move(1);\n if (data === 'x') {\n const instance = this.instances[this.selected];\n if (instance) void this.client.stop(instance.instanceId, STOP_REASON);\n }\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: 'LOOPS',\n breadcrumb: 'SPC › l / loops › l / list',\n headerRight: `${this.instances.length} active`,\n footer: 'x stop · j/k move · esc close · launched detached work continues',\n footerHints: [\n ['x', 'stop scheduling'],\n ['j/k', 'move'],\n ['esc', 'close'],\n ],\n accent: DOOM_OVERLAY_ACCENT,\n };\n }\n\n protected renderBody(width: number): string[] {\n const stopScope = this.theme.fg('dim', truncateToWidth(STOP_SCOPE_MESSAGE, width));\n if (!this.instances.length) return [this.theme.fg('dim', EMPTY_MESSAGE), '', stopScope];\n return [\n stopScope,\n '',\n ...this.instances.flatMap((instance, index) => {\n const marker = index === this.selected ? this.theme.fg('accent', '▸') : ' ';\n const name = instance.label ?? instance.launcherLabel;\n const state = this.theme.fg(instance.state === 'running' ? 'success' : 'warning', instance.state);\n const line = truncateToWidth(`${marker} ${name} · ${state}`, width);\n const detail = instance.detail ?? instance.instanceId;\n return [line, ` ${this.theme.fg('dim', truncateToWidth(detail, Math.max(0, width - 4)))}`];\n }),\n ];\n }\n\n private refresh(): void {\n this.instances = this.client.listInstances();\n this.selected = Math.min(this.selected, Math.max(0, this.instances.length - 1));\n this.invalidate();\n }\n\n private move(delta: number): void {\n if (!this.instances.length) return;\n this.selected = Math.max(0, Math.min(this.selected + delta, this.instances.length - 1));\n this.invalidate();\n }\n}\n\nexport async function openLoopListOverlay(ctx: ExtensionContext, client: LoopLauncherClient): Promise<void> {\n if (ctx.mode !== 'tui') {\n const instances = client.listInstances();\n const labels = instances.map((instance) => `${instance.label ?? instance.launcherLabel} (${instance.instanceId})`);\n const selected = await ctx.ui.select('Stop loop scheduling', labels);\n const instance = instances[labels.indexOf(selected ?? '')];\n if (instance) await client.stop(instance.instanceId, STOP_REASON);\n return;\n }\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new LoopListOverlay(tui, theme, client, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"4MAWA,MAEM,EAAc,8BAEpB,IAAa,EAAb,cAAqC,CAAY,CAQ5B,OACA,KARnB,YACA,UACA,SAAmB,EAEnB,YACE,EACA,EACA,EACA,EACA,CACA,MAAM,EAAK,CAAK,EAHC,KAAA,OAAA,EACA,KAAA,KAAA,EAGjB,KAAK,UAAY,EAAO,cAAc,EACtC,KAAK,YAAc,EAAO,cAAgB,KAAK,QAAQ,CAAC,CAC1D,CAEA,SAAgB,CACd,KAAK,YAAY,CACnB,CAEA,YAAY,EAAoB,CAC9B,GAAI,EAAW,EAAM,QAAQ,GAAK,EAAW,EAAM,QAAQ,GAAK,IAAS,IAAK,OAAO,KAAK,KAAK,IAAA,EAAS,EACxG,GAAI,EAAW,EAAM,IAAI,GAAK,IAAS,IAAK,OAAO,KAAK,KAAK,EAAE,EAC/D,GAAI,EAAW,EAAM,MAAM,GAAK,IAAS,IAAK,OAAO,KAAK,KAAK,CAAC,EAChE,GAAI,IAAS,IAAK,CAChB,IAAM,EAAW,KAAK,UAAU,KAAK,UACjC,GAAU,KAAU,OAAO,KAAK,EAAS,WAAY,CAAW,CACtE,CACF,CAEA,WAAyC,CACvC,MAAO,CACL,MAAO,QACP,WAAY,6BACZ,YAAa,GAAG,KAAK,UAAU,OAAO,SACtC,OAAQ,mEACR,YAAa,CACX,CAAC,IAAK,iBAAiB,EACvB,CAAC,MAAO,MAAM,EACd,CAAC,MAAO,OAAO,CACjB,EACA,OAAQ,CACV,CACF,CAEA,WAAqB,EAAyB,CAC5C,IAAM,EAAY,KAAK,MAAM,GAAG,MAAO,EAAgB,kEAAoB,CAAK,CAAC,EAEjF,OADK,KAAK,UAAU,OACb,CACL,EACA,GACA,GAAG,KAAK,UAAU,SAAS,EAAU,IAAU,CAC7C,IAAM,EAAS,IAAU,KAAK,SAAW,KAAK,MAAM,GAAG,SAAU,GAAG,EAAI,IAClE,EAAO,EAAS,OAAS,EAAS,cAClC,EAAQ,KAAK,MAAM,GAAG,EAAS,QAAU,UAAY,UAAY,UAAW,EAAS,KAAK,EAC1F,EAAO,EAAgB,GAAG,EAAO,GAAG,EAAK,KAAK,IAAS,CAAK,EAC5D,EAAS,EAAS,QAAU,EAAS,WAC3C,MAAO,CAAC,EAAM,OAAO,KAAK,MAAM,GAAG,MAAO,EAAgB,EAAQ,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,CAAC,GAAG,CAC9F,CAAC,CACH,EAZmC,CAAC,KAAK,MAAM,GAAG,MAAO,+CAAa,EAAG,GAAI,CAAS,CAaxF,CAEA,SAAwB,CACtB,KAAK,UAAY,KAAK,OAAO,cAAc,EAC3C,KAAK,SAAW,KAAK,IAAI,KAAK,SAAU,KAAK,IAAI,EAAG,KAAK,UAAU,OAAS,CAAC,CAAC,EAC9E,KAAK,WAAW,CAClB,CAEA,KAAa,EAAqB,CAC3B,KAAK,UAAU,SACpB,KAAK,SAAW,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,SAAW,EAAO,KAAK,UAAU,OAAS,CAAC,CAAC,EACtF,KAAK,WAAW,EAClB,CACF,EAEA,eAAsB,EAAoB,EAAuB,EAA2C,CAC1G,GAAI,EAAI,OAAS,MAAO,CACtB,IAAM,EAAY,EAAO,cAAc,EACjC,EAAS,EAAU,IAAK,GAAa,GAAG,EAAS,OAAS,EAAS,cAAc,IAAI,EAAS,WAAW,EAAE,EAC3G,EAAW,MAAM,EAAI,GAAG,OAAO,uBAAwB,CAAM,EAC7D,EAAW,EAAU,EAAO,QAAQ,GAAY,EAAE,GACpD,GAAU,MAAM,EAAO,KAAK,EAAS,WAAY,CAAW,EAChE,MACF,CACA,MAAM,EAAI,GAAG,QACV,EAAK,EAAO,EAAc,IAAS,IAAI,EAAgB,EAAK,EAAO,EAAQ,CAAI,EAChF,CACF,CACF"}
1
+ {"version":3,"file":"loopListOverlay.mjs","names":[],"sources":["../../src/tui/loopListOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { LoopInstanceSnapshot, LoopLauncherClient } from '../types/loop.ts';\n\nconst EMPTY_MESSAGE = 'No loop instances are active in this session.';\nconst STOP_SCOPE_MESSAGE = 'Stopping a loop does not cancel detached work already launched.';\nconst STOP_REASON = 'Stopped from the loop list.';\n\nexport class LoopListOverlay extends DoomOverlay {\n private readonly unsubscribe: () => void;\n private instances: readonly LoopInstanceSnapshot[];\n private selected = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly client: LoopLauncherClient,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n this.instances = client.listInstances();\n this.unsubscribe = client.subscribe(() => this.refresh());\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.move(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.move(1);\n return;\n }\n if (data === 'x') {\n const instance = this.instances[this.selected];\n if (instance) void this.client.stop(instance.instanceId, STOP_REASON);\n }\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: 'LOOPS',\n breadcrumb: 'SPC › l / loops › l / list',\n headerRight: `${this.instances.length} active`,\n footer: `${DOOM_NAVIGATION_KEYS.list} move · x stop · esc close · launched detached work continues`,\n footerHints: [\n [DOOM_NAVIGATION_KEYS.list, 'move'],\n ['x', 'stop scheduling'],\n ['esc', 'close'],\n ],\n accent: DOOM_OVERLAY_ACCENT,\n };\n }\n\n protected renderBody(width: number): string[] {\n const stopScope = this.theme.fg('dim', truncateToWidth(STOP_SCOPE_MESSAGE, width));\n if (!this.instances.length) return [this.theme.fg('dim', EMPTY_MESSAGE), '', stopScope];\n return [\n stopScope,\n '',\n ...this.instances.flatMap((instance, index) => {\n const marker = index === this.selected ? this.theme.fg('accent', '▸') : ' ';\n const name = instance.label ?? instance.launcherLabel;\n const state = this.theme.fg(instance.state === 'running' ? 'success' : 'warning', instance.state);\n const line = truncateToWidth(`${marker} ${name} · ${state}`, width);\n const detail = instance.detail ?? instance.instanceId;\n return [line, ` ${this.theme.fg('dim', truncateToWidth(detail, Math.max(0, width - 4)))}`];\n }),\n ];\n }\n\n private refresh(): void {\n this.instances = this.client.listInstances();\n this.selected = Math.min(this.selected, Math.max(0, this.instances.length - 1));\n this.invalidate();\n }\n\n private move(delta: number): void {\n if (!this.instances.length) return;\n this.selected = Math.max(0, Math.min(this.selected + delta, this.instances.length - 1));\n this.invalidate();\n }\n}\n\nexport async function openLoopListOverlay(ctx: ExtensionContext, client: LoopLauncherClient): Promise<void> {\n if (ctx.mode !== 'tui') {\n const instances = client.listInstances();\n const labels = instances.map((instance) => `${instance.label ?? instance.launcherLabel} (${instance.instanceId})`);\n const selected = await ctx.ui.select('Stop loop scheduling', labels);\n const instance = instances[labels.indexOf(selected ?? '')];\n if (instance) await client.stop(instance.instanceId, STOP_REASON);\n return;\n }\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new LoopListOverlay(tui, theme, client, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- let e=require("@agimon-ai/doompi-ui/components/doomOverlay"),t=require("@earendil-works/pi-tui");var n=class extends e.DoomOverlay{launchers;done;selected=0;constructor(e,t,n,r){super(e,t),this.launchers=n,this.done=r}handleInput(e){if((0,t.matchesKey)(e,`escape`)||(0,t.matchesKey)(e,`ctrl+c`)||e===`q`)return this.done(void 0);if((0,t.matchesKey)(e,`up`)||e===`k`)return this.move(-1);if((0,t.matchesKey)(e,`down`)||e===`j`)return this.move(1);(0,t.matchesKey)(e,`enter`)&&this.done(this.launchers[this.selected]?.id)}getChrome(){return{title:`START LOOP`,breadcrumb:`SPC › l / loops › s / start`,footer:`enter launch · j/k move · esc close`,footerHints:[[`enter`,`launch`],[`j/k`,`move`],[`esc`,`close`]],accent:e.DOOM_OVERLAY_ACCENT}}renderBody(e){return this.launchers.length?this.launchers.flatMap((n,r)=>{let i=r===this.selected,a=i?this.theme.fg(`accent`,`▸`):` `,o=i?this.theme.bold(n.label):n.label,s=[`${a} ${(0,t.truncateToWidth)(o,Math.max(0,e-2))}`];return n.description&&s.push(` ${this.theme.fg(`dim`,(0,t.truncateToWidth)(n.description,Math.max(0,e-4)))}`),s}):[this.theme.fg(`dim`,`No loop launchers are registered for this session.`)]}move(e){this.launchers.length&&(this.selected=Math.max(0,Math.min(this.selected+e,this.launchers.length-1)),this.invalidate())}};async function r(t,r){if(t.mode!==`tui`){let e=r.map(e=>e.label),n=await t.ui.select(`Start loop`,e);return r[e.indexOf(n??``)]?.id}return t.ui.custom((e,t,i,a)=>new n(e,t,r,a),e.DOOM_FULLSCREEN_UI_OPTIONS)}exports.StartLoopOverlay=n,exports.openStartLoopOverlay=r;
1
+ let e=require("@agimon-ai/doompi-ui/components/doomOverlay"),t=require("@earendil-works/pi-tui");var n=class extends e.DoomOverlay{launchers;done;selected=0;constructor(e,t,n,r){super(e,t),this.launchers=n,this.done=r}handleInput(e){if((0,t.matchesKey)(e,`escape`)||(0,t.matchesKey)(e,`ctrl+c`)||e===`q`){this.done(void 0);return}if((0,t.matchesKey)(e,`up`)||e===`k`){this.move(-1);return}if((0,t.matchesKey)(e,`down`)||e===`j`){this.move(1);return}(0,t.matchesKey)(e,`enter`)&&this.done(this.launchers[this.selected]?.id)}getChrome(){return{title:`START LOOP`,breadcrumb:`SPC › l / loops › s / start`,footer:`${e.DOOM_NAVIGATION_KEYS.list} move · enter launch · esc close`,footerHints:[[e.DOOM_NAVIGATION_KEYS.list,`move`],[`enter`,`launch`],[`esc`,`close`]],accent:e.DOOM_OVERLAY_ACCENT}}renderBody(e){return this.launchers.length?this.launchers.flatMap((n,r)=>{let i=r===this.selected,a=i?this.theme.fg(`accent`,`▸`):` `,o=i?this.theme.bold(n.label):n.label,s=[`${a} ${(0,t.truncateToWidth)(o,Math.max(0,e-2))}`];return n.description&&s.push(` ${this.theme.fg(`dim`,(0,t.truncateToWidth)(n.description,Math.max(0,e-4)))}`),s}):[this.theme.fg(`dim`,`No loop launchers are registered for this session.`)]}move(e){this.launchers.length&&(this.selected=Math.max(0,Math.min(this.selected+e,this.launchers.length-1)),this.invalidate())}};async function r(t,r){if(t.mode!==`tui`){let e=r.map(e=>e.label),n=await t.ui.select(`Start loop`,e);return r[e.indexOf(n??``)]?.id}return t.ui.custom((e,t,i,a)=>new n(e,t,r,a),e.DOOM_FULLSCREEN_UI_OPTIONS)}exports.StartLoopOverlay=n,exports.openStartLoopOverlay=r;
2
2
  //# sourceMappingURL=startLoopOverlay.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"startLoopOverlay.cjs","names":["DoomOverlay","matchesKey","DOOM_OVERLAY_ACCENT","truncateToWidth","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/tui/startLoopOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { LoopLauncherSummary } from '../launcher.ts';\n\nconst EMPTY_MESSAGE = 'No loop launchers are registered for this session.';\n\nexport class StartLoopOverlay extends DoomOverlay {\n private selected = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly launchers: readonly LoopLauncherSummary[],\n private readonly done: (result: string | undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') return this.done(undefined);\n if (matchesKey(data, 'up') || data === 'k') return this.move(-1);\n if (matchesKey(data, 'down') || data === 'j') return this.move(1);\n if (matchesKey(data, 'enter')) this.done(this.launchers[this.selected]?.id);\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: 'START LOOP',\n breadcrumb: 'SPC › l / loops › s / start',\n footer: 'enter launch · j/k move · esc close',\n footerHints: [\n ['enter', 'launch'],\n ['j/k', 'move'],\n ['esc', 'close'],\n ],\n accent: DOOM_OVERLAY_ACCENT,\n };\n }\n\n protected renderBody(width: number): string[] {\n if (!this.launchers.length) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n return this.launchers.flatMap((launcher, index) => {\n const selected = index === this.selected;\n const marker = selected ? this.theme.fg('accent', '▸') : ' ';\n const label = selected ? this.theme.bold(launcher.label) : launcher.label;\n const lines = [`${marker} ${truncateToWidth(label, Math.max(0, width - 2))}`];\n if (launcher.description)\n lines.push(` ${this.theme.fg('dim', truncateToWidth(launcher.description, Math.max(0, width - 4)))}`);\n return lines;\n });\n }\n\n private move(delta: number): void {\n if (!this.launchers.length) return;\n this.selected = Math.max(0, Math.min(this.selected + delta, this.launchers.length - 1));\n this.invalidate();\n }\n}\n\nexport async function openStartLoopOverlay(\n ctx: ExtensionContext,\n launchers: readonly LoopLauncherSummary[],\n): Promise<string | undefined> {\n if (ctx.mode !== 'tui') {\n const labels = launchers.map((launcher) => launcher.label);\n const selected = await ctx.ui.select('Start loop', labels);\n return launchers[labels.indexOf(selected ?? '')]?.id;\n }\n return ctx.ui.custom<string | undefined>(\n (tui, theme, _keybindings, done) => new StartLoopOverlay(tui, theme, launchers, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"iGAaA,IAAa,EAAb,cAAsCA,EAAAA,WAAY,CAM7B,UACA,KANnB,SAAmB,EAEnB,YACE,EACA,EACA,EACA,EACA,CACA,MAAM,EAAK,CAAK,EAHC,KAAA,UAAA,EACA,KAAA,KAAA,CAGnB,CAEA,YAAY,EAAoB,CAC9B,IAAA,EAAIC,EAAAA,WAAAA,CAAW,EAAM,QAAQ,IAAA,EAAKA,EAAAA,WAAAA,CAAW,EAAM,QAAQ,GAAK,IAAS,IAAK,OAAO,KAAK,KAAK,IAAA,EAAS,EACxG,IAAA,EAAIA,EAAAA,WAAAA,CAAW,EAAM,IAAI,GAAK,IAAS,IAAK,OAAO,KAAK,KAAK,EAAE,EAC/D,IAAA,EAAIA,EAAAA,WAAAA,CAAW,EAAM,MAAM,GAAK,IAAS,IAAK,OAAO,KAAK,KAAK,CAAC,GAChE,EAAIA,EAAAA,WAAAA,CAAW,EAAM,OAAO,GAAG,KAAK,KAAK,KAAK,UAAU,KAAK,SAAS,EAAE,EAAE,CAC5E,CAEA,WAAyC,CACvC,MAAO,CACL,MAAO,aACP,WAAY,8BACZ,OAAQ,sCACR,YAAa,CACX,CAAC,QAAS,QAAQ,EAClB,CAAC,MAAO,MAAM,EACd,CAAC,MAAO,OAAO,CACjB,EACA,OAAQC,EAAAA,mBACV,CACF,CAEA,WAAqB,EAAyB,CAE5C,OADK,KAAK,UAAU,OACb,KAAK,UAAU,SAAS,EAAU,IAAU,CACjD,IAAM,EAAW,IAAU,KAAK,SAC1B,EAAS,EAAW,KAAK,MAAM,GAAG,SAAU,GAAG,EAAI,IACnD,EAAQ,EAAW,KAAK,MAAM,KAAK,EAAS,KAAK,EAAI,EAAS,MAC9D,EAAQ,CAAC,GAAG,EAAO,IAAA,EAAGC,EAAAA,gBAAAA,CAAgB,EAAO,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,GAAG,EAG5E,OAFI,EAAS,aACX,EAAM,KAAK,OAAO,KAAK,MAAM,GAAG,OAAA,EAAOA,EAAAA,gBAAAA,CAAgB,EAAS,YAAa,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,CAAC,GAAG,EAClG,CACT,CAAC,EATkC,CAAC,KAAK,MAAM,GAAG,MAAO,oDAAa,CAAC,CAUzE,CAEA,KAAa,EAAqB,CAC3B,KAAK,UAAU,SACpB,KAAK,SAAW,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,SAAW,EAAO,KAAK,UAAU,OAAS,CAAC,CAAC,EACtF,KAAK,WAAW,EAClB,CACF,EAEA,eAAsB,EACpB,EACA,EAC6B,CAC7B,GAAI,EAAI,OAAS,MAAO,CACtB,IAAM,EAAS,EAAU,IAAK,GAAa,EAAS,KAAK,EACnD,EAAW,MAAM,EAAI,GAAG,OAAO,aAAc,CAAM,EACzD,OAAO,EAAU,EAAO,QAAQ,GAAY,EAAE,EAAE,EAAE,EACpD,CACA,OAAO,EAAI,GAAG,QACX,EAAK,EAAO,EAAc,IAAS,IAAI,EAAiB,EAAK,EAAO,EAAW,CAAI,EACpFC,EAAAA,0BACF,CACF"}
1
+ {"version":3,"file":"startLoopOverlay.cjs","names":["DoomOverlay","matchesKey","DOOM_NAVIGATION_KEYS","DOOM_OVERLAY_ACCENT","truncateToWidth","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/tui/startLoopOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { LoopLauncherSummary } from '../types/loop.ts';\n\nconst EMPTY_MESSAGE = 'No loop launchers are registered for this session.';\n\nexport class StartLoopOverlay extends DoomOverlay {\n private selected = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly launchers: readonly LoopLauncherSummary[],\n private readonly done: (result: string | undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.move(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.move(1);\n return;\n }\n if (matchesKey(data, 'enter')) this.done(this.launchers[this.selected]?.id);\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: 'START LOOP',\n breadcrumb: 'SPC › l / loops › s / start',\n footer: `${DOOM_NAVIGATION_KEYS.list} move · enter launch · esc close`,\n footerHints: [\n [DOOM_NAVIGATION_KEYS.list, 'move'],\n ['enter', 'launch'],\n ['esc', 'close'],\n ],\n accent: DOOM_OVERLAY_ACCENT,\n };\n }\n\n protected renderBody(width: number): string[] {\n if (!this.launchers.length) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n return this.launchers.flatMap((launcher, index) => {\n const selected = index === this.selected;\n const marker = selected ? this.theme.fg('accent', '▸') : ' ';\n const label = selected ? this.theme.bold(launcher.label) : launcher.label;\n const lines = [`${marker} ${truncateToWidth(label, Math.max(0, width - 2))}`];\n if (launcher.description)\n lines.push(` ${this.theme.fg('dim', truncateToWidth(launcher.description, Math.max(0, width - 4)))}`);\n return lines;\n });\n }\n\n private move(delta: number): void {\n if (!this.launchers.length) return;\n this.selected = Math.max(0, Math.min(this.selected + delta, this.launchers.length - 1));\n this.invalidate();\n }\n}\n\nexport async function openStartLoopOverlay(\n ctx: ExtensionContext,\n launchers: readonly LoopLauncherSummary[],\n): Promise<string | undefined> {\n if (ctx.mode !== 'tui') {\n const labels = launchers.map((launcher) => launcher.label);\n const selected = await ctx.ui.select('Start loop', labels);\n return launchers[labels.indexOf(selected ?? '')]?.id;\n }\n return ctx.ui.custom<string | undefined>(\n (tui, theme, _keybindings, done) => new StartLoopOverlay(tui, theme, launchers, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"iGAcA,IAAa,EAAb,cAAsCA,EAAAA,WAAY,CAM7B,UACA,KANnB,SAAmB,EAEnB,YACE,EACA,EACA,EACA,EACA,CACA,MAAM,EAAK,CAAK,EAHC,KAAA,UAAA,EACA,KAAA,KAAA,CAGnB,CAEA,YAAY,EAAoB,CAC9B,IAAA,EAAIC,EAAAA,WAAAA,CAAW,EAAM,QAAQ,IAAA,EAAKA,EAAAA,WAAAA,CAAW,EAAM,QAAQ,GAAK,IAAS,IAAK,CAC5E,KAAK,KAAK,IAAA,EAAS,EACnB,MACF,CACA,IAAA,EAAIA,EAAAA,WAAAA,CAAW,EAAM,IAAI,GAAK,IAAS,IAAK,CAC1C,KAAK,KAAK,EAAE,EACZ,MACF,CACA,IAAA,EAAIA,EAAAA,WAAAA,CAAW,EAAM,MAAM,GAAK,IAAS,IAAK,CAC5C,KAAK,KAAK,CAAC,EACX,MACF,EACA,EAAIA,EAAAA,WAAAA,CAAW,EAAM,OAAO,GAAG,KAAK,KAAK,KAAK,UAAU,KAAK,SAAS,EAAE,EAAE,CAC5E,CAEA,WAAyC,CACvC,MAAO,CACL,MAAO,aACP,WAAY,8BACZ,OAAQ,GAAGC,EAAAA,qBAAqB,KAAK,kCACrC,YAAa,CACX,CAACA,EAAAA,qBAAqB,KAAM,MAAM,EAClC,CAAC,QAAS,QAAQ,EAClB,CAAC,MAAO,OAAO,CACjB,EACA,OAAQC,EAAAA,mBACV,CACF,CAEA,WAAqB,EAAyB,CAE5C,OADK,KAAK,UAAU,OACb,KAAK,UAAU,SAAS,EAAU,IAAU,CACjD,IAAM,EAAW,IAAU,KAAK,SAC1B,EAAS,EAAW,KAAK,MAAM,GAAG,SAAU,GAAG,EAAI,IACnD,EAAQ,EAAW,KAAK,MAAM,KAAK,EAAS,KAAK,EAAI,EAAS,MAC9D,EAAQ,CAAC,GAAG,EAAO,IAAA,EAAGC,EAAAA,gBAAAA,CAAgB,EAAO,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,GAAG,EAG5E,OAFI,EAAS,aACX,EAAM,KAAK,OAAO,KAAK,MAAM,GAAG,OAAA,EAAOA,EAAAA,gBAAAA,CAAgB,EAAS,YAAa,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,CAAC,GAAG,EAClG,CACT,CAAC,EATkC,CAAC,KAAK,MAAM,GAAG,MAAO,oDAAa,CAAC,CAUzE,CAEA,KAAa,EAAqB,CAC3B,KAAK,UAAU,SACpB,KAAK,SAAW,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,SAAW,EAAO,KAAK,UAAU,OAAS,CAAC,CAAC,EACtF,KAAK,WAAW,EAClB,CACF,EAEA,eAAsB,EACpB,EACA,EAC6B,CAC7B,GAAI,EAAI,OAAS,MAAO,CACtB,IAAM,EAAS,EAAU,IAAK,GAAa,EAAS,KAAK,EACnD,EAAW,MAAM,EAAI,GAAG,OAAO,aAAc,CAAM,EACzD,OAAO,EAAU,EAAO,QAAQ,GAAY,EAAE,EAAE,EAAE,EACpD,CACA,OAAO,EAAI,GAAG,QACX,EAAK,EAAO,EAAc,IAAS,IAAI,EAAiB,EAAK,EAAO,EAAW,CAAI,EACpFC,EAAAA,0BACF,CACF"}
@@ -1,2 +1,2 @@
1
- import{DOOM_FULLSCREEN_UI_OPTIONS as e,DOOM_OVERLAY_ACCENT as t,DoomOverlay as n}from"@agimon-ai/doompi-ui/components/doomOverlay";import{matchesKey as r,truncateToWidth as i}from"@earendil-works/pi-tui";var a=class extends n{launchers;done;selected=0;constructor(e,t,n,r){super(e,t),this.launchers=n,this.done=r}handleInput(e){if(r(e,`escape`)||r(e,`ctrl+c`)||e===`q`)return this.done(void 0);if(r(e,`up`)||e===`k`)return this.move(-1);if(r(e,`down`)||e===`j`)return this.move(1);r(e,`enter`)&&this.done(this.launchers[this.selected]?.id)}getChrome(){return{title:`START LOOP`,breadcrumb:`SPC › l / loops › s / start`,footer:`enter launch · j/k move · esc close`,footerHints:[[`enter`,`launch`],[`j/k`,`move`],[`esc`,`close`]],accent:t}}renderBody(e){return this.launchers.length?this.launchers.flatMap((t,n)=>{let r=n===this.selected,a=r?this.theme.fg(`accent`,`▸`):` `,o=r?this.theme.bold(t.label):t.label,s=[`${a} ${i(o,Math.max(0,e-2))}`];return t.description&&s.push(` ${this.theme.fg(`dim`,i(t.description,Math.max(0,e-4)))}`),s}):[this.theme.fg(`dim`,`No loop launchers are registered for this session.`)]}move(e){this.launchers.length&&(this.selected=Math.max(0,Math.min(this.selected+e,this.launchers.length-1)),this.invalidate())}};async function o(t,n){if(t.mode!==`tui`){let e=n.map(e=>e.label),r=await t.ui.select(`Start loop`,e);return n[e.indexOf(r??``)]?.id}return t.ui.custom((e,t,r,i)=>new a(e,t,n,i),e)}export{a as StartLoopOverlay,o as openStartLoopOverlay};
1
+ import{DOOM_FULLSCREEN_UI_OPTIONS as e,DOOM_NAVIGATION_KEYS as t,DOOM_OVERLAY_ACCENT as n,DoomOverlay as r}from"@agimon-ai/doompi-ui/components/doomOverlay";import{matchesKey as i,truncateToWidth as a}from"@earendil-works/pi-tui";var o=class extends r{launchers;done;selected=0;constructor(e,t,n,r){super(e,t),this.launchers=n,this.done=r}handleInput(e){if(i(e,`escape`)||i(e,`ctrl+c`)||e===`q`){this.done(void 0);return}if(i(e,`up`)||e===`k`){this.move(-1);return}if(i(e,`down`)||e===`j`){this.move(1);return}i(e,`enter`)&&this.done(this.launchers[this.selected]?.id)}getChrome(){return{title:`START LOOP`,breadcrumb:`SPC › l / loops › s / start`,footer:`${t.list} move · enter launch · esc close`,footerHints:[[t.list,`move`],[`enter`,`launch`],[`esc`,`close`]],accent:n}}renderBody(e){return this.launchers.length?this.launchers.flatMap((t,n)=>{let r=n===this.selected,i=r?this.theme.fg(`accent`,`▸`):` `,o=r?this.theme.bold(t.label):t.label,s=[`${i} ${a(o,Math.max(0,e-2))}`];return t.description&&s.push(` ${this.theme.fg(`dim`,a(t.description,Math.max(0,e-4)))}`),s}):[this.theme.fg(`dim`,`No loop launchers are registered for this session.`)]}move(e){this.launchers.length&&(this.selected=Math.max(0,Math.min(this.selected+e,this.launchers.length-1)),this.invalidate())}};async function s(t,n){if(t.mode!==`tui`){let e=n.map(e=>e.label),r=await t.ui.select(`Start loop`,e);return n[e.indexOf(r??``)]?.id}return t.ui.custom((e,t,r,i)=>new o(e,t,n,i),e)}export{o as StartLoopOverlay,s as openStartLoopOverlay};
2
2
  //# sourceMappingURL=startLoopOverlay.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"startLoopOverlay.mjs","names":[],"sources":["../../src/tui/startLoopOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { LoopLauncherSummary } from '../launcher.ts';\n\nconst EMPTY_MESSAGE = 'No loop launchers are registered for this session.';\n\nexport class StartLoopOverlay extends DoomOverlay {\n private selected = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly launchers: readonly LoopLauncherSummary[],\n private readonly done: (result: string | undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') return this.done(undefined);\n if (matchesKey(data, 'up') || data === 'k') return this.move(-1);\n if (matchesKey(data, 'down') || data === 'j') return this.move(1);\n if (matchesKey(data, 'enter')) this.done(this.launchers[this.selected]?.id);\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: 'START LOOP',\n breadcrumb: 'SPC › l / loops › s / start',\n footer: 'enter launch · j/k move · esc close',\n footerHints: [\n ['enter', 'launch'],\n ['j/k', 'move'],\n ['esc', 'close'],\n ],\n accent: DOOM_OVERLAY_ACCENT,\n };\n }\n\n protected renderBody(width: number): string[] {\n if (!this.launchers.length) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n return this.launchers.flatMap((launcher, index) => {\n const selected = index === this.selected;\n const marker = selected ? this.theme.fg('accent', '▸') : ' ';\n const label = selected ? this.theme.bold(launcher.label) : launcher.label;\n const lines = [`${marker} ${truncateToWidth(label, Math.max(0, width - 2))}`];\n if (launcher.description)\n lines.push(` ${this.theme.fg('dim', truncateToWidth(launcher.description, Math.max(0, width - 4)))}`);\n return lines;\n });\n }\n\n private move(delta: number): void {\n if (!this.launchers.length) return;\n this.selected = Math.max(0, Math.min(this.selected + delta, this.launchers.length - 1));\n this.invalidate();\n }\n}\n\nexport async function openStartLoopOverlay(\n ctx: ExtensionContext,\n launchers: readonly LoopLauncherSummary[],\n): Promise<string | undefined> {\n if (ctx.mode !== 'tui') {\n const labels = launchers.map((launcher) => launcher.label);\n const selected = await ctx.ui.select('Start loop', labels);\n return launchers[labels.indexOf(selected ?? '')]?.id;\n }\n return ctx.ui.custom<string | undefined>(\n (tui, theme, _keybindings, done) => new StartLoopOverlay(tui, theme, launchers, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"4MAaA,IAAa,EAAb,cAAsC,CAAY,CAM7B,UACA,KANnB,SAAmB,EAEnB,YACE,EACA,EACA,EACA,EACA,CACA,MAAM,EAAK,CAAK,EAHC,KAAA,UAAA,EACA,KAAA,KAAA,CAGnB,CAEA,YAAY,EAAoB,CAC9B,GAAI,EAAW,EAAM,QAAQ,GAAK,EAAW,EAAM,QAAQ,GAAK,IAAS,IAAK,OAAO,KAAK,KAAK,IAAA,EAAS,EACxG,GAAI,EAAW,EAAM,IAAI,GAAK,IAAS,IAAK,OAAO,KAAK,KAAK,EAAE,EAC/D,GAAI,EAAW,EAAM,MAAM,GAAK,IAAS,IAAK,OAAO,KAAK,KAAK,CAAC,EAC5D,EAAW,EAAM,OAAO,GAAG,KAAK,KAAK,KAAK,UAAU,KAAK,SAAS,EAAE,EAAE,CAC5E,CAEA,WAAyC,CACvC,MAAO,CACL,MAAO,aACP,WAAY,8BACZ,OAAQ,sCACR,YAAa,CACX,CAAC,QAAS,QAAQ,EAClB,CAAC,MAAO,MAAM,EACd,CAAC,MAAO,OAAO,CACjB,EACA,OAAQ,CACV,CACF,CAEA,WAAqB,EAAyB,CAE5C,OADK,KAAK,UAAU,OACb,KAAK,UAAU,SAAS,EAAU,IAAU,CACjD,IAAM,EAAW,IAAU,KAAK,SAC1B,EAAS,EAAW,KAAK,MAAM,GAAG,SAAU,GAAG,EAAI,IACnD,EAAQ,EAAW,KAAK,MAAM,KAAK,EAAS,KAAK,EAAI,EAAS,MAC9D,EAAQ,CAAC,GAAG,EAAO,GAAG,EAAgB,EAAO,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,GAAG,EAG5E,OAFI,EAAS,aACX,EAAM,KAAK,OAAO,KAAK,MAAM,GAAG,MAAO,EAAgB,EAAS,YAAa,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,CAAC,GAAG,EAClG,CACT,CAAC,EATkC,CAAC,KAAK,MAAM,GAAG,MAAO,oDAAa,CAAC,CAUzE,CAEA,KAAa,EAAqB,CAC3B,KAAK,UAAU,SACpB,KAAK,SAAW,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,SAAW,EAAO,KAAK,UAAU,OAAS,CAAC,CAAC,EACtF,KAAK,WAAW,EAClB,CACF,EAEA,eAAsB,EACpB,EACA,EAC6B,CAC7B,GAAI,EAAI,OAAS,MAAO,CACtB,IAAM,EAAS,EAAU,IAAK,GAAa,EAAS,KAAK,EACnD,EAAW,MAAM,EAAI,GAAG,OAAO,aAAc,CAAM,EACzD,OAAO,EAAU,EAAO,QAAQ,GAAY,EAAE,EAAE,EAAE,EACpD,CACA,OAAO,EAAI,GAAG,QACX,EAAK,EAAO,EAAc,IAAS,IAAI,EAAiB,EAAK,EAAO,EAAW,CAAI,EACpF,CACF,CACF"}
1
+ {"version":3,"file":"startLoopOverlay.mjs","names":[],"sources":["../../src/tui/startLoopOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\nimport type { LoopLauncherSummary } from '../types/loop.ts';\n\nconst EMPTY_MESSAGE = 'No loop launchers are registered for this session.';\n\nexport class StartLoopOverlay extends DoomOverlay {\n private selected = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly launchers: readonly LoopLauncherSummary[],\n private readonly done: (result: string | undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.move(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.move(1);\n return;\n }\n if (matchesKey(data, 'enter')) this.done(this.launchers[this.selected]?.id);\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: 'START LOOP',\n breadcrumb: 'SPC › l / loops › s / start',\n footer: `${DOOM_NAVIGATION_KEYS.list} move · enter launch · esc close`,\n footerHints: [\n [DOOM_NAVIGATION_KEYS.list, 'move'],\n ['enter', 'launch'],\n ['esc', 'close'],\n ],\n accent: DOOM_OVERLAY_ACCENT,\n };\n }\n\n protected renderBody(width: number): string[] {\n if (!this.launchers.length) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n return this.launchers.flatMap((launcher, index) => {\n const selected = index === this.selected;\n const marker = selected ? this.theme.fg('accent', '▸') : ' ';\n const label = selected ? this.theme.bold(launcher.label) : launcher.label;\n const lines = [`${marker} ${truncateToWidth(label, Math.max(0, width - 2))}`];\n if (launcher.description)\n lines.push(` ${this.theme.fg('dim', truncateToWidth(launcher.description, Math.max(0, width - 4)))}`);\n return lines;\n });\n }\n\n private move(delta: number): void {\n if (!this.launchers.length) return;\n this.selected = Math.max(0, Math.min(this.selected + delta, this.launchers.length - 1));\n this.invalidate();\n }\n}\n\nexport async function openStartLoopOverlay(\n ctx: ExtensionContext,\n launchers: readonly LoopLauncherSummary[],\n): Promise<string | undefined> {\n if (ctx.mode !== 'tui') {\n const labels = launchers.map((launcher) => launcher.label);\n const selected = await ctx.ui.select('Start loop', labels);\n return launchers[labels.indexOf(selected ?? '')]?.id;\n }\n return ctx.ui.custom<string | undefined>(\n (tui, theme, _keybindings, done) => new StartLoopOverlay(tui, theme, launchers, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"sOAcA,IAAa,EAAb,cAAsC,CAAY,CAM7B,UACA,KANnB,SAAmB,EAEnB,YACE,EACA,EACA,EACA,EACA,CACA,MAAM,EAAK,CAAK,EAHC,KAAA,UAAA,EACA,KAAA,KAAA,CAGnB,CAEA,YAAY,EAAoB,CAC9B,GAAI,EAAW,EAAM,QAAQ,GAAK,EAAW,EAAM,QAAQ,GAAK,IAAS,IAAK,CAC5E,KAAK,KAAK,IAAA,EAAS,EACnB,MACF,CACA,GAAI,EAAW,EAAM,IAAI,GAAK,IAAS,IAAK,CAC1C,KAAK,KAAK,EAAE,EACZ,MACF,CACA,GAAI,EAAW,EAAM,MAAM,GAAK,IAAS,IAAK,CAC5C,KAAK,KAAK,CAAC,EACX,MACF,CACI,EAAW,EAAM,OAAO,GAAG,KAAK,KAAK,KAAK,UAAU,KAAK,SAAS,EAAE,EAAE,CAC5E,CAEA,WAAyC,CACvC,MAAO,CACL,MAAO,aACP,WAAY,8BACZ,OAAQ,GAAG,EAAqB,KAAK,kCACrC,YAAa,CACX,CAAC,EAAqB,KAAM,MAAM,EAClC,CAAC,QAAS,QAAQ,EAClB,CAAC,MAAO,OAAO,CACjB,EACA,OAAQ,CACV,CACF,CAEA,WAAqB,EAAyB,CAE5C,OADK,KAAK,UAAU,OACb,KAAK,UAAU,SAAS,EAAU,IAAU,CACjD,IAAM,EAAW,IAAU,KAAK,SAC1B,EAAS,EAAW,KAAK,MAAM,GAAG,SAAU,GAAG,EAAI,IACnD,EAAQ,EAAW,KAAK,MAAM,KAAK,EAAS,KAAK,EAAI,EAAS,MAC9D,EAAQ,CAAC,GAAG,EAAO,GAAG,EAAgB,EAAO,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,GAAG,EAG5E,OAFI,EAAS,aACX,EAAM,KAAK,OAAO,KAAK,MAAM,GAAG,MAAO,EAAgB,EAAS,YAAa,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,CAAC,GAAG,EAClG,CACT,CAAC,EATkC,CAAC,KAAK,MAAM,GAAG,MAAO,oDAAa,CAAC,CAUzE,CAEA,KAAa,EAAqB,CAC3B,KAAK,UAAU,SACpB,KAAK,SAAW,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,SAAW,EAAO,KAAK,UAAU,OAAS,CAAC,CAAC,EACtF,KAAK,WAAW,EAClB,CACF,EAEA,eAAsB,EACpB,EACA,EAC6B,CAC7B,GAAI,EAAI,OAAS,MAAO,CACtB,IAAM,EAAS,EAAU,IAAK,GAAa,EAAS,KAAK,EACnD,EAAW,MAAM,EAAI,GAAG,OAAO,aAAc,CAAM,EACzD,OAAO,EAAU,EAAO,QAAQ,GAAY,EAAE,EAAE,EAAE,EACpD,CACA,OAAO,EAAI,GAAG,QACX,EAAK,EAAO,EAAc,IAAS,IAAI,EAAiB,EAAK,EAAO,EAAW,CAAI,EACpF,CACF,CACF"}
@@ -1,4 +1,4 @@
1
- //#region src/launcher.d.ts
1
+ //#region src/types/loop.d.ts
2
2
  interface LoopLaunchRequest {
3
3
  readonly instanceId: string;
4
4
  readonly signal: AbortSignal;
@@ -40,8 +40,6 @@ interface LoopLauncherClient {
40
40
  interface RegisterLoopLauncherOptions extends LoopLauncherSummary {
41
41
  launch(request: LoopLaunchRequest): Promise<StoppableLoop | undefined>;
42
42
  }
43
- declare function registerLoopLauncher(sessionId: string, options: RegisterLoopLauncherOptions): LoopLauncherRegistration;
44
- declare function createLoopLauncherClient(sessionId: () => string | undefined): LoopLauncherClient;
45
43
  //#endregion
46
- export { LoopInstanceSnapshot, LoopLaunchRequest, LoopLauncherClient, LoopLauncherRegistration, LoopLauncherSummary, RegisterLoopLauncherOptions, StoppableLoop, createLoopLauncherClient, registerLoopLauncher };
47
- //# sourceMappingURL=launcher.d.cts.map
44
+ export { LoopInstanceSnapshot, LoopLaunchRequest, LoopLauncherClient, LoopLauncherRegistration, LoopLauncherSummary, RegisterLoopLauncherOptions, StoppableLoop };
45
+ //# sourceMappingURL=loop.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.d.cts","names":[],"sources":["../../src/types/loop.ts"],"mappings":";UAAiB;WACN;WACA,QAAQ;;UAGF;WACN;WACA;WACA;EACT,KAAK,yBAAyB;;UAGf;WACN;WACA;WACA;WACA;;UAGM;WACN;WACA;WACA;WACA;WACA;WACA;WACA;;UAGM;EACf,QAAQ,kBAAkB;;UAGX;EACf,0BAA0B;EAC1B,0BAA0B;EAC1B,UAAU;EACV,OAAO,oBAAoB;IAAY;MAAwB,QAAQ;EACvE,KAAK,oBAAoB,kBAAkB;EAC3C,QAAQ,kBAAkB;;UAGX,oCAAoC;EACnD,OAAO,SAAS,oBAAoB,QAAQ"}
@@ -1,4 +1,4 @@
1
- //#region src/launcher.d.ts
1
+ //#region src/types/loop.d.ts
2
2
  interface LoopLaunchRequest {
3
3
  readonly instanceId: string;
4
4
  readonly signal: AbortSignal;
@@ -40,8 +40,6 @@ interface LoopLauncherClient {
40
40
  interface RegisterLoopLauncherOptions extends LoopLauncherSummary {
41
41
  launch(request: LoopLaunchRequest): Promise<StoppableLoop | undefined>;
42
42
  }
43
- declare function registerLoopLauncher(sessionId: string, options: RegisterLoopLauncherOptions): LoopLauncherRegistration;
44
- declare function createLoopLauncherClient(sessionId: () => string | undefined): LoopLauncherClient;
45
43
  //#endregion
46
- export { LoopInstanceSnapshot, LoopLaunchRequest, LoopLauncherClient, LoopLauncherRegistration, LoopLauncherSummary, RegisterLoopLauncherOptions, StoppableLoop, createLoopLauncherClient, registerLoopLauncher };
47
- //# sourceMappingURL=launcher.d.mts.map
44
+ export { LoopInstanceSnapshot, LoopLaunchRequest, LoopLauncherClient, LoopLauncherRegistration, LoopLauncherSummary, RegisterLoopLauncherOptions, StoppableLoop };
45
+ //# sourceMappingURL=loop.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loop.d.mts","names":[],"sources":["../../src/types/loop.ts"],"mappings":";UAAiB;WACN;WACA,QAAQ;;UAGF;WACN;WACA;WACA;EACT,KAAK,yBAAyB;;UAGf;WACN;WACA;WACA;WACA;;UAGM;WACN;WACA;WACA;WACA;WACA;WACA;WACA;;UAGM;EACf,QAAQ,kBAAkB;;UAGX;EACf,0BAA0B;EAC1B,0BAA0B;EAC1B,UAAU;EACV,OAAO,oBAAoB;IAAY;MAAwB,QAAQ;EACvE,KAAK,oBAAoB,kBAAkB;EAC3C,QAAQ,kBAAkB;;UAGX,oCAAoC;EACnD,OAAO,SAAS,oBAAoB,QAAQ"}
package/package.json CHANGED
@@ -1,7 +1,14 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-loop",
3
- "version": "0.0.1-alpha.2",
3
+ "version": "0.0.1-alpha.21",
4
4
  "description": "Generic multi-loop controller for Doompi",
5
+ "keywords": [
6
+ "ai",
7
+ "coding-agent",
8
+ "developer-tools",
9
+ "doompi",
10
+ "pi-package"
11
+ ],
5
12
  "homepage": "https://agimon.ai",
6
13
  "license": "MIT",
7
14
  "author": "Vuong Ngo",
@@ -30,12 +37,12 @@
30
37
  "access": "public"
31
38
  },
32
39
  "dependencies": {
33
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.2",
34
- "@agimon-ai/doompi-ui": "0.0.1-alpha.2"
40
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.21",
41
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.21"
35
42
  },
36
43
  "devDependencies": {
37
- "@earendil-works/pi-coding-agent": "0.84.1",
38
- "@earendil-works/pi-tui": "0.84.1",
44
+ "@earendil-works/pi-coding-agent": "0.84.2",
45
+ "@earendil-works/pi-tui": "0.84.2",
39
46
  "@types/node": "26.1.1",
40
47
  "@vitest/coverage-v8": "4.1.10",
41
48
  "tsdown": "0.22.14",
@@ -43,8 +50,8 @@
43
50
  "vitest": "4.1.10"
44
51
  },
45
52
  "peerDependencies": {
46
- "@earendil-works/pi-coding-agent": "0.84.1",
47
- "@earendil-works/pi-tui": "0.84.1"
53
+ "@earendil-works/pi-coding-agent": "0.84.2",
54
+ "@earendil-works/pi-tui": "0.84.2"
48
55
  },
49
56
  "peerDependenciesMeta": {
50
57
  "@earendil-works/pi-coding-agent": {
@@ -1,2 +0,0 @@
1
- exports.ACTIVE_MODE_COLOR=`warning`,exports.LIST_COMMAND_DESCRIPTION=`List and stop active loops`,exports.LIST_COMMAND_NAME=`loops`,exports.MODE_STATUS_ID=`loop.active`,exports.NOTIFY_INFO_LEVEL=`info`,exports.PACKAGE_SOURCE=`@agimon-ai/doompi-loop`,exports.START_COMMAND_DESCRIPTION=`Start a registered loop`,exports.START_COMMAND_NAME=`loop`,exports.STATUS_KEY=`doom-loop`;
2
- //# sourceMappingURL=constants.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants.cjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["export const PACKAGE_SOURCE = '@agimon-ai/doompi-loop';\nexport const START_COMMAND_NAME = 'loop';\nexport const LIST_COMMAND_NAME = 'loops';\nexport const START_COMMAND_DESCRIPTION = 'Start a registered loop';\nexport const LIST_COMMAND_DESCRIPTION = 'List and stop active loops';\nexport const LOOPS_GROUP_ORDER = 60;\nexport const STATUS_KEY = 'doom-loop';\nexport const MODE_STATUS_ID = 'loop.active';\nexport const ACTIVE_MODE_COLOR = 'warning';\nexport const NOTIFY_INFO_LEVEL = 'info';\n"],"mappings":""}
@@ -1,2 +0,0 @@
1
- const e=`@agimon-ai/doompi-loop`,t=`loop`,n=`loops`,r=`Start a registered loop`,i=`List and stop active loops`,a=`doom-loop`,o=`loop.active`,s=`warning`,c=`info`;export{s as ACTIVE_MODE_COLOR,i as LIST_COMMAND_DESCRIPTION,n as LIST_COMMAND_NAME,o as MODE_STATUS_ID,c as NOTIFY_INFO_LEVEL,e as PACKAGE_SOURCE,r as START_COMMAND_DESCRIPTION,t as START_COMMAND_NAME,a as STATUS_KEY};
2
- //# sourceMappingURL=constants.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"constants.mjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["export const PACKAGE_SOURCE = '@agimon-ai/doompi-loop';\nexport const START_COMMAND_NAME = 'loop';\nexport const LIST_COMMAND_NAME = 'loops';\nexport const START_COMMAND_DESCRIPTION = 'Start a registered loop';\nexport const LIST_COMMAND_DESCRIPTION = 'List and stop active loops';\nexport const LOOPS_GROUP_ORDER = 60;\nexport const STATUS_KEY = 'doom-loop';\nexport const MODE_STATUS_ID = 'loop.active';\nexport const ACTIVE_MODE_COLOR = 'warning';\nexport const NOTIFY_INFO_LEVEL = 'info';\n"],"mappings":"AAAA,MAAa,EAAiB,yBACjB,EAAqB,OACrB,EAAoB,QACpB,EAA4B,0BAC5B,EAA2B,6BAE3B,EAAa,YACb,EAAiB,cACjB,EAAoB,UACpB,EAAoB"}
@@ -1,2 +0,0 @@
1
- const e=require("./constants.cjs"),t=require("./commands/loopCommand.cjs"),n=require("./launcher.cjs"),r=require("./leader.cjs"),i=require("./tui/loopListOverlay.cjs"),a=require("./tui/startLoopOverlay.cjs");let o=require("@agimon-ai/doompi-ui/mode");const s=Symbol.for(`@agimon-ai/doompi-loop/installed-hosts`);function c(e){let t=globalThis,n=t[s]??new WeakSet;return t[s]=n,!n.has(e)&&(n.add(e),!0)}function l(s){if(!c(s))return;let l,u,d,f=n.createLoopLauncherClient(()=>l),p=(0,o.registerDoomModeContribution)(s,{source:e.PACKAGE_SOURCE,id:e.MODE_STATUS_ID,order:60});r.registerLeaderContribution(s);let m=()=>{let t=f.listInstances().length;u?.ui?.setStatus(e.STATUS_KEY,t?`loops: ${t}`:void 0),p.update(t?{label:`LOOP`,detail:`${t} active`,color:e.ACTIVE_MODE_COLOR}:void 0)};t.registerCommands(s,{async start(t){let n=f.listLaunchers();if(!n.length){t.ui.notify(`No loop launchers are registered for this session.`,e.NOTIFY_INFO_LEVEL);return}let r=await a.openStartLoopOverlay(t,n);if(r)try{let n=await f.launch(r);n&&t.ui.notify(`${n.label??n.launcherLabel} started.`,e.NOTIFY_INFO_LEVEL)}catch(e){t.ui.notify(`Loop could not start: ${e instanceof Error?e.message:String(e)}`,`error`)}},async list(e){await i.openLoopListOverlay(e,f)}}),s.on(`session_start`,(e,t)=>{d?.(),u=t,l=t.sessionManager.getSessionId(),d=f.subscribe(m),m()}),s.on(`session_shutdown`,async()=>{await f.stopAll(`Pi session shut down.`),d?.(),d=void 0,u?.ui?.setStatus(e.STATUS_KEY,void 0),u=void 0,l=void 0,p.update(void 0)})}exports.registerLoopExtension=l;
2
- //# sourceMappingURL=extension.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.cjs","names":["createLoopLauncherClient","registerDoomModeContribution","PACKAGE_SOURCE","MODE_STATUS_ID","STATUS_KEY","ACTIVE_MODE_COLOR","NOTIFY_INFO_LEVEL","openStartLoopOverlay","openLoopListOverlay"],"sources":["../src/extension.ts"],"sourcesContent":["import { registerDoomModeContribution } from '@agimon-ai/doompi-ui/mode';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommands } from './commands/loopCommand.ts';\nimport {\n ACTIVE_MODE_COLOR,\n LOOPS_GROUP_ORDER,\n MODE_STATUS_ID,\n NOTIFY_INFO_LEVEL,\n PACKAGE_SOURCE,\n STATUS_KEY,\n} from './constants.ts';\nimport { createLoopLauncherClient } from './launcher.ts';\nimport { registerLeaderContribution } from './leader.ts';\nimport { openLoopListOverlay } from './tui/loopListOverlay.ts';\nimport { openStartLoopOverlay } from './tui/startLoopOverlay.ts';\n\nconst INSTALLATIONS_KEY = Symbol.for('@agimon-ai/doompi-loop/installed-hosts');\n\ntype InstallationRegistry = typeof globalThis & {\n [INSTALLATIONS_KEY]?: WeakSet<object>;\n};\n\nfunction claimHostInstallation(pi: ExtensionAPI): boolean {\n const registry = globalThis as InstallationRegistry;\n const installedHosts = registry[INSTALLATIONS_KEY] ?? new WeakSet<object>();\n registry[INSTALLATIONS_KEY] = installedHosts;\n if (installedHosts.has(pi)) return false;\n installedHosts.add(pi);\n return true;\n}\n\nexport function registerLoopExtension(pi: ExtensionAPI): void {\n if (!claimHostInstallation(pi)) return;\n let sessionId: string | undefined;\n let context: ExtensionContext | undefined;\n let unsubscribe: (() => void) | undefined;\n const client = createLoopLauncherClient(() => sessionId);\n const mode = registerDoomModeContribution(pi, {\n source: PACKAGE_SOURCE,\n id: MODE_STATUS_ID,\n order: LOOPS_GROUP_ORDER,\n });\n registerLeaderContribution(pi);\n\n const updateStatus = (): void => {\n const count = client.listInstances().length;\n context?.ui?.setStatus(STATUS_KEY, count ? `loops: ${count}` : undefined);\n mode.update(count ? { label: 'LOOP', detail: `${count} active`, color: ACTIVE_MODE_COLOR } : undefined);\n };\n\n registerCommands(pi, {\n async start(ctx) {\n const launchers = client.listLaunchers();\n if (!launchers.length) {\n ctx.ui.notify('No loop launchers are registered for this session.', NOTIFY_INFO_LEVEL);\n return;\n }\n const launcherId = await openStartLoopOverlay(ctx, launchers);\n if (!launcherId) return;\n try {\n const instance = await client.launch(launcherId);\n if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, NOTIFY_INFO_LEVEL);\n } catch (error) {\n ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, 'error');\n }\n },\n async list(ctx) {\n await openLoopListOverlay(ctx, client);\n },\n });\n\n pi.on('session_start', (_event, ctx) => {\n unsubscribe?.();\n context = ctx;\n sessionId = ctx.sessionManager.getSessionId();\n unsubscribe = client.subscribe(updateStatus);\n updateStatus();\n });\n\n pi.on('session_shutdown', async () => {\n await client.stopAll('Pi session shut down.');\n unsubscribe?.();\n unsubscribe = undefined;\n context?.ui?.setStatus(STATUS_KEY, undefined);\n context = undefined;\n sessionId = undefined;\n mode.update(undefined);\n });\n}\n"],"mappings":"2PAgBA,MAAM,EAAoB,OAAO,IAAI,wCAAwC,EAM7E,SAAS,EAAsB,EAA2B,CACxD,IAAM,EAAW,WACX,EAAiB,EAAS,IAAsB,IAAI,QAI1D,MAHA,GAAS,GAAqB,EAC9B,CAAI,EAAe,IAAI,CAAE,IACzB,EAAe,IAAI,CAAE,EACd,GACT,CAEA,SAAgB,EAAsB,EAAwB,CAC5D,GAAI,CAAC,EAAsB,CAAE,EAAG,OAChC,IAAI,EACA,EACA,EACE,EAASA,EAAAA,6BAA+B,CAAS,EACjD,GAAA,EAAOC,EAAAA,6BAAAA,CAA6B,EAAI,CAC5C,OAAQC,EAAAA,eACR,GAAIC,EAAAA,eACJ,MAAA,EACF,CAAC,EACD,EAAA,2BAA2B,CAAE,EAE7B,IAAM,MAA2B,CAC/B,IAAM,EAAQ,EAAO,cAAc,CAAC,CAAC,OACrC,GAAS,IAAI,UAAUC,EAAAA,WAAY,EAAQ,UAAU,IAAU,IAAA,EAAS,EACxE,EAAK,OAAO,EAAQ,CAAE,MAAO,OAAQ,OAAQ,GAAG,EAAM,SAAU,MAAOC,EAAAA,iBAAkB,EAAI,IAAA,EAAS,CACxG,EAEA,EAAA,iBAAiB,EAAI,CACnB,MAAM,MAAM,EAAK,CACf,IAAM,EAAY,EAAO,cAAc,EACvC,GAAI,CAAC,EAAU,OAAQ,CACrB,EAAI,GAAG,OAAO,qDAAsDC,EAAAA,iBAAiB,EACrF,MACF,CACA,IAAM,EAAa,MAAMC,EAAAA,qBAAqB,EAAK,CAAS,EACvD,KACL,GAAI,CACF,IAAM,EAAW,MAAM,EAAO,OAAO,CAAU,EAC3C,GAAU,EAAI,GAAG,OAAO,GAAG,EAAS,OAAS,EAAS,cAAc,WAAYD,EAAAA,iBAAiB,CACvG,OAAS,EAAO,CACd,EAAI,GAAG,OAAO,yBAAyB,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,IAAK,OAAO,CAC1G,CACF,EACA,MAAM,KAAK,EAAK,CACd,MAAME,EAAAA,oBAAoB,EAAK,CAAM,CACvC,CACF,CAAC,EAED,EAAG,GAAG,iBAAkB,EAAQ,IAAQ,CACtC,IAAc,EACd,EAAU,EACV,EAAY,EAAI,eAAe,aAAa,EAC5C,EAAc,EAAO,UAAU,CAAY,EAC3C,EAAa,CACf,CAAC,EAED,EAAG,GAAG,mBAAoB,SAAY,CACpC,MAAM,EAAO,QAAQ,uBAAuB,EAC5C,IAAc,EACd,EAAc,IAAA,GACd,GAAS,IAAI,UAAUJ,EAAAA,WAAY,IAAA,EAAS,EAC5C,EAAU,IAAA,GACV,EAAY,IAAA,GACZ,EAAK,OAAO,IAAA,EAAS,CACvB,CAAC,CACH"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../src/extension.ts"],"mappings":";;iBA+BgB,sBAAsB,IAAI"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../src/extension.ts"],"mappings":";;iBA+BgB,sBAAsB,IAAI"}
@@ -1,2 +0,0 @@
1
- import{ACTIVE_MODE_COLOR as e,MODE_STATUS_ID as t,NOTIFY_INFO_LEVEL as n,PACKAGE_SOURCE as r,STATUS_KEY as i}from"./constants.mjs";import{registerCommands as a}from"./commands/loopCommand.mjs";import{createLoopLauncherClient as o}from"./launcher.mjs";import{registerLeaderContribution as s}from"./leader.mjs";import{openLoopListOverlay as c}from"./tui/loopListOverlay.mjs";import{openStartLoopOverlay as l}from"./tui/startLoopOverlay.mjs";import{registerDoomModeContribution as u}from"@agimon-ai/doompi-ui/mode";const d=Symbol.for(`@agimon-ai/doompi-loop/installed-hosts`);function f(e){let t=globalThis,n=t[d]??new WeakSet;return t[d]=n,!n.has(e)&&(n.add(e),!0)}function p(d){if(!f(d))return;let p,m,h,g=o(()=>p),_=u(d,{source:r,id:t,order:60});s(d);let v=()=>{let t=g.listInstances().length;m?.ui?.setStatus(i,t?`loops: ${t}`:void 0),_.update(t?{label:`LOOP`,detail:`${t} active`,color:e}:void 0)};a(d,{async start(e){let t=g.listLaunchers();if(!t.length){e.ui.notify(`No loop launchers are registered for this session.`,n);return}let r=await l(e,t);if(r)try{let t=await g.launch(r);t&&e.ui.notify(`${t.label??t.launcherLabel} started.`,n)}catch(t){e.ui.notify(`Loop could not start: ${t instanceof Error?t.message:String(t)}`,`error`)}},async list(e){await c(e,g)}}),d.on(`session_start`,(e,t)=>{h?.(),m=t,p=t.sessionManager.getSessionId(),h=g.subscribe(v),v()}),d.on(`session_shutdown`,async()=>{await g.stopAll(`Pi session shut down.`),h?.(),h=void 0,m?.ui?.setStatus(i,void 0),m=void 0,p=void 0,_.update(void 0)})}export{p as registerLoopExtension};
2
- //# sourceMappingURL=extension.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"extension.mjs","names":[],"sources":["../src/extension.ts"],"sourcesContent":["import { registerDoomModeContribution } from '@agimon-ai/doompi-ui/mode';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommands } from './commands/loopCommand.ts';\nimport {\n ACTIVE_MODE_COLOR,\n LOOPS_GROUP_ORDER,\n MODE_STATUS_ID,\n NOTIFY_INFO_LEVEL,\n PACKAGE_SOURCE,\n STATUS_KEY,\n} from './constants.ts';\nimport { createLoopLauncherClient } from './launcher.ts';\nimport { registerLeaderContribution } from './leader.ts';\nimport { openLoopListOverlay } from './tui/loopListOverlay.ts';\nimport { openStartLoopOverlay } from './tui/startLoopOverlay.ts';\n\nconst INSTALLATIONS_KEY = Symbol.for('@agimon-ai/doompi-loop/installed-hosts');\n\ntype InstallationRegistry = typeof globalThis & {\n [INSTALLATIONS_KEY]?: WeakSet<object>;\n};\n\nfunction claimHostInstallation(pi: ExtensionAPI): boolean {\n const registry = globalThis as InstallationRegistry;\n const installedHosts = registry[INSTALLATIONS_KEY] ?? new WeakSet<object>();\n registry[INSTALLATIONS_KEY] = installedHosts;\n if (installedHosts.has(pi)) return false;\n installedHosts.add(pi);\n return true;\n}\n\nexport function registerLoopExtension(pi: ExtensionAPI): void {\n if (!claimHostInstallation(pi)) return;\n let sessionId: string | undefined;\n let context: ExtensionContext | undefined;\n let unsubscribe: (() => void) | undefined;\n const client = createLoopLauncherClient(() => sessionId);\n const mode = registerDoomModeContribution(pi, {\n source: PACKAGE_SOURCE,\n id: MODE_STATUS_ID,\n order: LOOPS_GROUP_ORDER,\n });\n registerLeaderContribution(pi);\n\n const updateStatus = (): void => {\n const count = client.listInstances().length;\n context?.ui?.setStatus(STATUS_KEY, count ? `loops: ${count}` : undefined);\n mode.update(count ? { label: 'LOOP', detail: `${count} active`, color: ACTIVE_MODE_COLOR } : undefined);\n };\n\n registerCommands(pi, {\n async start(ctx) {\n const launchers = client.listLaunchers();\n if (!launchers.length) {\n ctx.ui.notify('No loop launchers are registered for this session.', NOTIFY_INFO_LEVEL);\n return;\n }\n const launcherId = await openStartLoopOverlay(ctx, launchers);\n if (!launcherId) return;\n try {\n const instance = await client.launch(launcherId);\n if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, NOTIFY_INFO_LEVEL);\n } catch (error) {\n ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, 'error');\n }\n },\n async list(ctx) {\n await openLoopListOverlay(ctx, client);\n },\n });\n\n pi.on('session_start', (_event, ctx) => {\n unsubscribe?.();\n context = ctx;\n sessionId = ctx.sessionManager.getSessionId();\n unsubscribe = client.subscribe(updateStatus);\n updateStatus();\n });\n\n pi.on('session_shutdown', async () => {\n await client.stopAll('Pi session shut down.');\n unsubscribe?.();\n unsubscribe = undefined;\n context?.ui?.setStatus(STATUS_KEY, undefined);\n context = undefined;\n sessionId = undefined;\n mode.update(undefined);\n });\n}\n"],"mappings":"ggBAgBA,MAAM,EAAoB,OAAO,IAAI,wCAAwC,EAM7E,SAAS,EAAsB,EAA2B,CACxD,IAAM,EAAW,WACX,EAAiB,EAAS,IAAsB,IAAI,QAI1D,MAHA,GAAS,GAAqB,EAC9B,CAAI,EAAe,IAAI,CAAE,IACzB,EAAe,IAAI,CAAE,EACd,GACT,CAEA,SAAgB,EAAsB,EAAwB,CAC5D,GAAI,CAAC,EAAsB,CAAE,EAAG,OAChC,IAAI,EACA,EACA,EACE,EAAS,MAA+B,CAAS,EACjD,EAAO,EAA6B,EAAI,CAC5C,OAAQ,EACR,GAAI,EACJ,MAAA,EACF,CAAC,EACD,EAA2B,CAAE,EAE7B,IAAM,MAA2B,CAC/B,IAAM,EAAQ,EAAO,cAAc,CAAC,CAAC,OACrC,GAAS,IAAI,UAAU,EAAY,EAAQ,UAAU,IAAU,IAAA,EAAS,EACxE,EAAK,OAAO,EAAQ,CAAE,MAAO,OAAQ,OAAQ,GAAG,EAAM,SAAU,MAAO,CAAkB,EAAI,IAAA,EAAS,CACxG,EAEA,EAAiB,EAAI,CACnB,MAAM,MAAM,EAAK,CACf,IAAM,EAAY,EAAO,cAAc,EACvC,GAAI,CAAC,EAAU,OAAQ,CACrB,EAAI,GAAG,OAAO,qDAAsD,CAAiB,EACrF,MACF,CACA,IAAM,EAAa,MAAM,EAAqB,EAAK,CAAS,EACvD,KACL,GAAI,CACF,IAAM,EAAW,MAAM,EAAO,OAAO,CAAU,EAC3C,GAAU,EAAI,GAAG,OAAO,GAAG,EAAS,OAAS,EAAS,cAAc,WAAY,CAAiB,CACvG,OAAS,EAAO,CACd,EAAI,GAAG,OAAO,yBAAyB,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,IAAK,OAAO,CAC1G,CACF,EACA,MAAM,KAAK,EAAK,CACd,MAAM,EAAoB,EAAK,CAAM,CACvC,CACF,CAAC,EAED,EAAG,GAAG,iBAAkB,EAAQ,IAAQ,CACtC,IAAc,EACd,EAAU,EACV,EAAY,EAAI,eAAe,aAAa,EAC5C,EAAc,EAAO,UAAU,CAAY,EAC3C,EAAa,CACf,CAAC,EAED,EAAG,GAAG,mBAAoB,SAAY,CACpC,MAAM,EAAO,QAAQ,uBAAuB,EAC5C,IAAc,EACd,EAAc,IAAA,GACd,GAAS,IAAI,UAAU,EAAY,IAAA,EAAS,EAC5C,EAAU,IAAA,GACV,EAAY,IAAA,GACZ,EAAK,OAAO,IAAA,EAAS,CACvB,CAAC,CACH"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"pi.cjs","names":["registerLoopExtension"],"sources":["../../src/extensions/pi.ts"],"sourcesContent":["import { registerLoopExtension } from '../extension.ts';\n\nexport default registerLoopExtension;\n"],"mappings":"AAEA,IAAA,4BAAeA,CAAAA,CAAAA"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"pi.mjs","names":[],"sources":["../../src/extensions/pi.ts"],"sourcesContent":["import { registerLoopExtension } from '../extension.ts';\n\nexport default registerLoopExtension;\n"],"mappings":"yDAEA,IAAA,EAAe"}
package/dist/launcher.cjs DELETED
@@ -1,2 +0,0 @@
1
- let e=require("node:crypto");const t=Symbol.for(`@agimon-ai/doompi-loop.launchers.v1`),n=`loop session id`,r=`loop launcher id`,i=`loop instance id`;function a(){let e=globalThis;return e[t]??=new Map}function o(e,t){let n=e.trim(),r=!1;for(let e of n){let t=e.codePointAt(0);if(t!==void 0&&(t<=31||t===127)){r=!0;break}}if(!n||n.length>256||r)throw Error(`Invalid ${t}.`);return n}function s(e,t){let r=o(e,n),i=a().get(r);if(i||!t)return i;let s={launchers:new Map,instances:new Map,listeners:new Set};return a().set(r,s),s}function c(e){for(let t of e.listeners)t()}function l(e){return{instanceId:e.instanceId,launcherId:e.launcher.options.id,launcherLabel:e.launcher.options.label,...e.handle?.label?{label:e.handle.label}:{},...e.handle?.detail?{detail:e.handle.detail}:{},state:e.state,...e.startedAt?{startedAt:e.startedAt}:{}}}async function u(e,t,n){if(e.instances.get(t.instanceId)===t){t.state=`stopping`,t.abortController.abort(n),c(e);try{await t.handle?.stop(n)}finally{e.instances.get(t.instanceId)===t&&e.instances.delete(t.instanceId),c(e)}}}function d(e,t){t.launchers.size||t.instances.size||t.listeners.size||a().get(e)===t&&a().delete(e)}function f(e,t){let i=o(e,n),a=o(t.id,r),l=s(i,!0);if(l.launchers.has(a))throw Error(`Loop launcher '${a}' is already registered for session '${i}'.`);let f={token:Symbol(a),options:{...t,id:a}};l.launchers.set(a,f),c(l);let p=!1;return{async dispose(e=`Loop launcher disposed.`){if(p)return;p=!0;let t=s(i,!1);if(!t||t.launchers.get(a)?.token!==f.token)return;t.launchers.delete(a);let n=[...t.instances.values()].filter(e=>e.launcher.token===f.token);await Promise.allSettled(n.map(n=>u(t,n,e))),c(t),d(i,t)}}}function p(t){let a=e=>{let r=t();if(!r)return;let i=o(r,n),a=s(i,e);return a?{key:i,session:a}:void 0};return{listLaunchers(){let e=a(!1)?.session;return e?[...e.launchers.values()].map(({options:e})=>({id:e.id,source:e.source,label:e.label,...e.description?{description:e.description}:{}})).sort((e,t)=>e.label.localeCompare(t.label)||e.id.localeCompare(t.id)):[]},listInstances(){let e=a(!1)?.session;return e?[...e.instances.values()].map(l).sort((e,t)=>e.instanceId.localeCompare(t.instanceId)):[]},subscribe(e){let t=a(!0);if(!t)return()=>void 0;t.session.listeners.add(e);let n=!1;return()=>{n||(n=!0,t.session.listeners.delete(e),d(t.key,t.session))}},async launch(t,n){let s=a(!0);if(!s)throw Error(`The session-bound loop launcher registry is unavailable.`);let u=o(t,r),d=s.session.launchers.get(u);if(!d)throw Error(`Loop launcher '${u}' is unavailable.`);let f=o(n?.instanceId??(0,e.randomUUID)(),i);if(s.session.instances.has(f))throw Error(`Loop instance '${f}' already exists.`);let p={instanceId:f,launcher:d,abortController:new AbortController,state:`starting`};s.session.instances.set(f,p),c(s.session);try{let e=await d.options.launch({instanceId:f,signal:p.abortController.signal}),t=s.session.instances.get(f)===p&&!p.abortController.signal.aborted;if(!e){s.session.instances.get(f)===p&&s.session.instances.delete(f),c(s.session);return}if(!t||e.instanceId!==f){if(await e.stop(t?`Loop launcher returned a mismatched instance id.`:`Loop launch was cancelled.`),s.session.instances.get(f)===p&&s.session.instances.delete(f),c(s.session),t)throw Error(`Loop launcher '${u}' returned a mismatched instance id.`);return}return p.handle=e,p.state=`running`,p.startedAt=new Date().toISOString(),c(s.session),l(p)}catch(e){let t=p.abortController.signal.aborted;if(s.session.instances.get(f)===p&&s.session.instances.delete(f),c(s.session),t)return;throw e}},async stop(e,t){let n=a(!1)?.session;if(!n)return!1;let r=n.instances.get(o(e,i));return r?(await u(n,r,t),!0):!1},async stopAll(e){let t=a(!1)?.session;t&&await Promise.allSettled([...t.instances.values()].map(n=>u(t,n,e)))}}}exports.createLoopLauncherClient=p,exports.registerLoopLauncher=f;
2
- //# sourceMappingURL=launcher.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"launcher.cjs","names":["randomUUID"],"sources":["../src/launcher.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nconst REGISTRY_KEY = Symbol.for('@agimon-ai/doompi-loop.launchers.v1');\nconst MAX_ID_LENGTH = 256;\nconst SESSION_ID_LABEL = 'loop session id';\nconst LAUNCHER_ID_LABEL = 'loop launcher id';\nconst INSTANCE_ID_LABEL = 'loop instance id';\n\nexport interface LoopLaunchRequest {\n readonly instanceId: string;\n readonly signal: AbortSignal;\n}\n\nexport interface StoppableLoop {\n readonly instanceId: string;\n readonly label?: string;\n readonly detail?: string;\n stop(reason?: string): void | Promise<void>;\n}\n\nexport interface LoopLauncherSummary {\n readonly id: string;\n readonly source: string;\n readonly label: string;\n readonly description?: string;\n}\n\nexport interface LoopInstanceSnapshot {\n readonly instanceId: string;\n readonly launcherId: string;\n readonly launcherLabel: string;\n readonly label?: string;\n readonly detail?: string;\n readonly state: 'starting' | 'running' | 'stopping';\n readonly startedAt?: string;\n}\n\nexport interface LoopLauncherRegistration {\n dispose(reason?: string): Promise<void>;\n}\n\nexport interface LoopLauncherClient {\n listLaunchers(): readonly LoopLauncherSummary[];\n listInstances(): readonly LoopInstanceSnapshot[];\n subscribe(listener: () => void): () => void;\n launch(launcherId: string, options?: { instanceId?: string }): Promise<LoopInstanceSnapshot | undefined>;\n stop(instanceId: string, reason?: string): Promise<boolean>;\n stopAll(reason?: string): Promise<void>;\n}\n\nexport interface RegisterLoopLauncherOptions extends LoopLauncherSummary {\n launch(request: LoopLaunchRequest): Promise<StoppableLoop | undefined>;\n}\n\ninterface LauncherEntry {\n readonly token: symbol;\n readonly options: RegisterLoopLauncherOptions;\n}\n\ninterface InstanceEntry {\n readonly instanceId: string;\n readonly launcher: LauncherEntry;\n readonly abortController: AbortController;\n state: LoopInstanceSnapshot['state'];\n handle?: StoppableLoop;\n startedAt?: string;\n}\n\ninterface SessionRegistry {\n readonly launchers: Map<string, LauncherEntry>;\n readonly instances: Map<string, InstanceEntry>;\n readonly listeners: Set<() => void>;\n}\n\ntype LoopRegistry = Map<string, SessionRegistry>;\n\nfunction registry(): LoopRegistry {\n const root = globalThis as typeof globalThis & { [REGISTRY_KEY]?: LoopRegistry };\n return (root[REGISTRY_KEY] ??= new Map());\n}\n\nfunction validId(value: string, label: string): string {\n const normalized = value.trim();\n let hasControl = false;\n for (const character of normalized) {\n const code = character.codePointAt(0);\n if (code !== undefined && (code <= 0x1f || code === 0x7f)) {\n hasControl = true;\n break;\n }\n }\n if (!normalized || normalized.length > MAX_ID_LENGTH || hasControl) throw new Error(`Invalid ${label}.`);\n return normalized;\n}\n\nfunction getSession(sessionId: string, create: boolean): SessionRegistry | undefined {\n const key = validId(sessionId, SESSION_ID_LABEL);\n const existing = registry().get(key);\n if (existing || !create) return existing;\n const created: SessionRegistry = { launchers: new Map(), instances: new Map(), listeners: new Set() };\n registry().set(key, created);\n return created;\n}\n\nfunction notify(session: SessionRegistry): void {\n for (const listener of session.listeners) listener();\n}\n\nfunction snapshot(entry: InstanceEntry): LoopInstanceSnapshot {\n return {\n instanceId: entry.instanceId,\n launcherId: entry.launcher.options.id,\n launcherLabel: entry.launcher.options.label,\n ...(entry.handle?.label ? { label: entry.handle.label } : {}),\n ...(entry.handle?.detail ? { detail: entry.handle.detail } : {}),\n state: entry.state,\n ...(entry.startedAt ? { startedAt: entry.startedAt } : {}),\n };\n}\n\nasync function stopEntry(session: SessionRegistry, entry: InstanceEntry, reason?: string): Promise<void> {\n if (session.instances.get(entry.instanceId) !== entry) return;\n entry.state = 'stopping';\n entry.abortController.abort(reason);\n notify(session);\n try {\n await entry.handle?.stop(reason);\n } finally {\n if (session.instances.get(entry.instanceId) === entry) session.instances.delete(entry.instanceId);\n notify(session);\n }\n}\n\nfunction cleanSession(sessionId: string, session: SessionRegistry): void {\n if (session.launchers.size || session.instances.size || session.listeners.size) return;\n if (registry().get(sessionId) === session) registry().delete(sessionId);\n}\n\nexport function registerLoopLauncher(\n sessionId: string,\n options: RegisterLoopLauncherOptions,\n): LoopLauncherRegistration {\n const sessionKey = validId(sessionId, SESSION_ID_LABEL);\n const launcherId = validId(options.id, LAUNCHER_ID_LABEL);\n const session = getSession(sessionKey, true) as SessionRegistry;\n if (session.launchers.has(launcherId)) {\n throw new Error(`Loop launcher '${launcherId}' is already registered for session '${sessionKey}'.`);\n }\n const entry: LauncherEntry = { token: Symbol(launcherId), options: { ...options, id: launcherId } };\n session.launchers.set(launcherId, entry);\n notify(session);\n let disposed = false;\n return {\n async dispose(reason = 'Loop launcher disposed.'): Promise<void> {\n if (disposed) return;\n disposed = true;\n const current = getSession(sessionKey, false);\n if (!current || current.launchers.get(launcherId)?.token !== entry.token) return;\n current.launchers.delete(launcherId);\n const owned = [...current.instances.values()].filter((instance) => instance.launcher.token === entry.token);\n await Promise.allSettled(owned.map((instance) => stopEntry(current, instance, reason)));\n notify(current);\n cleanSession(sessionKey, current);\n },\n };\n}\n\nexport function createLoopLauncherClient(sessionId: () => string | undefined): LoopLauncherClient {\n const current = (create: boolean): { key: string; session: SessionRegistry } | undefined => {\n const value = sessionId();\n if (!value) return undefined;\n const key = validId(value, SESSION_ID_LABEL);\n const session = getSession(key, create);\n return session ? { key, session } : undefined;\n };\n return {\n listLaunchers() {\n const session = current(false)?.session;\n if (!session) return [];\n return [...session.launchers.values()]\n .map(({ options }) => ({\n id: options.id,\n source: options.source,\n label: options.label,\n ...(options.description ? { description: options.description } : {}),\n }))\n .sort((left, right) => left.label.localeCompare(right.label) || left.id.localeCompare(right.id));\n },\n listInstances() {\n const session = current(false)?.session;\n return session\n ? [...session.instances.values()]\n .map(snapshot)\n .sort((left, right) => left.instanceId.localeCompare(right.instanceId))\n : [];\n },\n subscribe(listener) {\n const target = current(true);\n if (!target) return () => undefined;\n target.session.listeners.add(listener);\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n target.session.listeners.delete(listener);\n cleanSession(target.key, target.session);\n };\n },\n async launch(launcherId, options) {\n const target = current(true);\n if (!target) throw new Error('The session-bound loop launcher registry is unavailable.');\n const normalizedLauncherId = validId(launcherId, LAUNCHER_ID_LABEL);\n const launcher = target.session.launchers.get(normalizedLauncherId);\n if (!launcher) throw new Error(`Loop launcher '${normalizedLauncherId}' is unavailable.`);\n const instanceId = validId(options?.instanceId ?? randomUUID(), INSTANCE_ID_LABEL);\n if (target.session.instances.has(instanceId)) throw new Error(`Loop instance '${instanceId}' already exists.`);\n const entry: InstanceEntry = { instanceId, launcher, abortController: new AbortController(), state: 'starting' };\n target.session.instances.set(instanceId, entry);\n notify(target.session);\n try {\n const handle = await launcher.options.launch({ instanceId, signal: entry.abortController.signal });\n const active = target.session.instances.get(instanceId) === entry && !entry.abortController.signal.aborted;\n if (!handle) {\n if (target.session.instances.get(instanceId) === entry) target.session.instances.delete(instanceId);\n notify(target.session);\n return undefined;\n }\n if (!active || handle.instanceId !== instanceId) {\n await handle.stop(active ? 'Loop launcher returned a mismatched instance id.' : 'Loop launch was cancelled.');\n if (target.session.instances.get(instanceId) === entry) target.session.instances.delete(instanceId);\n notify(target.session);\n if (active) throw new Error(`Loop launcher '${normalizedLauncherId}' returned a mismatched instance id.`);\n return undefined;\n }\n entry.handle = handle;\n entry.state = 'running';\n entry.startedAt = new Date().toISOString();\n notify(target.session);\n return snapshot(entry);\n } catch (error) {\n const cancelled = entry.abortController.signal.aborted;\n if (target.session.instances.get(instanceId) === entry) target.session.instances.delete(instanceId);\n notify(target.session);\n if (cancelled) return undefined;\n throw error;\n }\n },\n async stop(instanceId, reason) {\n const session = current(false)?.session;\n if (!session) return false;\n const entry = session.instances.get(validId(instanceId, INSTANCE_ID_LABEL));\n if (!entry) return false;\n await stopEntry(session, entry, reason);\n return true;\n },\n async stopAll(reason) {\n const session = current(false)?.session;\n if (!session) return;\n await Promise.allSettled([...session.instances.values()].map((entry) => stopEntry(session, entry, reason)));\n },\n };\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"launcher.d.cts","names":[],"sources":["../src/launcher.ts"],"mappings":";UAQiB;WACN;WACA,QAAQ;;UAGF;WACN;WACA;WACA;EACT,KAAK,yBAAyB;;UAGf;WACN;WACA;WACA;WACA;;UAGM;WACN;WACA;WACA;WACA;WACA;WACA;WACA;;UAGM;EACf,QAAQ,kBAAkB;;UAGX;EACf,0BAA0B;EAC1B,0BAA0B;EAC1B,UAAU;EACV,OAAO,oBAAoB;IAAY;MAAwB,QAAQ;EACvE,KAAK,oBAAoB,kBAAkB;EAC3C,QAAQ,kBAAkB;;UAGX,oCAAoC;EACnD,OAAO,SAAS,oBAAoB,QAAQ;;iBAuF9B,qBACd,mBACA,SAAS,8BACR;iBA0Ba,yBAAyB,sCAAsC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"launcher.d.mts","names":[],"sources":["../src/launcher.ts"],"mappings":";UAQiB;WACN;WACA,QAAQ;;UAGF;WACN;WACA;WACA;EACT,KAAK,yBAAyB;;UAGf;WACN;WACA;WACA;WACA;;UAGM;WACN;WACA;WACA;WACA;WACA;WACA;WACA;;UAGM;EACf,QAAQ,kBAAkB;;UAGX;EACf,0BAA0B;EAC1B,0BAA0B;EAC1B,UAAU;EACV,OAAO,oBAAoB;IAAY;MAAwB,QAAQ;EACvE,KAAK,oBAAoB,kBAAkB;EAC3C,QAAQ,kBAAkB;;UAGX,oCAAoC;EACnD,OAAO,SAAS,oBAAoB,QAAQ;;iBAuF9B,qBACd,mBACA,SAAS,8BACR;iBA0Ba,yBAAyB,sCAAsC"}
package/dist/launcher.mjs DELETED
@@ -1,2 +0,0 @@
1
- import{randomUUID as e}from"node:crypto";const t=Symbol.for(`@agimon-ai/doompi-loop.launchers.v1`),n=`loop session id`,r=`loop launcher id`,i=`loop instance id`;function a(){let e=globalThis;return e[t]??=new Map}function o(e,t){let n=e.trim(),r=!1;for(let e of n){let t=e.codePointAt(0);if(t!==void 0&&(t<=31||t===127)){r=!0;break}}if(!n||n.length>256||r)throw Error(`Invalid ${t}.`);return n}function s(e,t){let r=o(e,n),i=a().get(r);if(i||!t)return i;let s={launchers:new Map,instances:new Map,listeners:new Set};return a().set(r,s),s}function c(e){for(let t of e.listeners)t()}function l(e){return{instanceId:e.instanceId,launcherId:e.launcher.options.id,launcherLabel:e.launcher.options.label,...e.handle?.label?{label:e.handle.label}:{},...e.handle?.detail?{detail:e.handle.detail}:{},state:e.state,...e.startedAt?{startedAt:e.startedAt}:{}}}async function u(e,t,n){if(e.instances.get(t.instanceId)===t){t.state=`stopping`,t.abortController.abort(n),c(e);try{await t.handle?.stop(n)}finally{e.instances.get(t.instanceId)===t&&e.instances.delete(t.instanceId),c(e)}}}function d(e,t){t.launchers.size||t.instances.size||t.listeners.size||a().get(e)===t&&a().delete(e)}function f(e,t){let i=o(e,n),a=o(t.id,r),l=s(i,!0);if(l.launchers.has(a))throw Error(`Loop launcher '${a}' is already registered for session '${i}'.`);let f={token:Symbol(a),options:{...t,id:a}};l.launchers.set(a,f),c(l);let p=!1;return{async dispose(e=`Loop launcher disposed.`){if(p)return;p=!0;let t=s(i,!1);if(!t||t.launchers.get(a)?.token!==f.token)return;t.launchers.delete(a);let n=[...t.instances.values()].filter(e=>e.launcher.token===f.token);await Promise.allSettled(n.map(n=>u(t,n,e))),c(t),d(i,t)}}}function p(t){let a=e=>{let r=t();if(!r)return;let i=o(r,n),a=s(i,e);return a?{key:i,session:a}:void 0};return{listLaunchers(){let e=a(!1)?.session;return e?[...e.launchers.values()].map(({options:e})=>({id:e.id,source:e.source,label:e.label,...e.description?{description:e.description}:{}})).sort((e,t)=>e.label.localeCompare(t.label)||e.id.localeCompare(t.id)):[]},listInstances(){let e=a(!1)?.session;return e?[...e.instances.values()].map(l).sort((e,t)=>e.instanceId.localeCompare(t.instanceId)):[]},subscribe(e){let t=a(!0);if(!t)return()=>void 0;t.session.listeners.add(e);let n=!1;return()=>{n||(n=!0,t.session.listeners.delete(e),d(t.key,t.session))}},async launch(t,n){let s=a(!0);if(!s)throw Error(`The session-bound loop launcher registry is unavailable.`);let u=o(t,r),d=s.session.launchers.get(u);if(!d)throw Error(`Loop launcher '${u}' is unavailable.`);let f=o(n?.instanceId??e(),i);if(s.session.instances.has(f))throw Error(`Loop instance '${f}' already exists.`);let p={instanceId:f,launcher:d,abortController:new AbortController,state:`starting`};s.session.instances.set(f,p),c(s.session);try{let e=await d.options.launch({instanceId:f,signal:p.abortController.signal}),t=s.session.instances.get(f)===p&&!p.abortController.signal.aborted;if(!e){s.session.instances.get(f)===p&&s.session.instances.delete(f),c(s.session);return}if(!t||e.instanceId!==f){if(await e.stop(t?`Loop launcher returned a mismatched instance id.`:`Loop launch was cancelled.`),s.session.instances.get(f)===p&&s.session.instances.delete(f),c(s.session),t)throw Error(`Loop launcher '${u}' returned a mismatched instance id.`);return}return p.handle=e,p.state=`running`,p.startedAt=new Date().toISOString(),c(s.session),l(p)}catch(e){let t=p.abortController.signal.aborted;if(s.session.instances.get(f)===p&&s.session.instances.delete(f),c(s.session),t)return;throw e}},async stop(e,t){let n=a(!1)?.session;if(!n)return!1;let r=n.instances.get(o(e,i));return r?(await u(n,r,t),!0):!1},async stopAll(e){let t=a(!1)?.session;t&&await Promise.allSettled([...t.instances.values()].map(n=>u(t,n,e)))}}}export{p as createLoopLauncherClient,f as registerLoopLauncher};
2
- //# sourceMappingURL=launcher.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"launcher.mjs","names":[],"sources":["../src/launcher.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto';\n\nconst REGISTRY_KEY = Symbol.for('@agimon-ai/doompi-loop.launchers.v1');\nconst MAX_ID_LENGTH = 256;\nconst SESSION_ID_LABEL = 'loop session id';\nconst LAUNCHER_ID_LABEL = 'loop launcher id';\nconst INSTANCE_ID_LABEL = 'loop instance id';\n\nexport interface LoopLaunchRequest {\n readonly instanceId: string;\n readonly signal: AbortSignal;\n}\n\nexport interface StoppableLoop {\n readonly instanceId: string;\n readonly label?: string;\n readonly detail?: string;\n stop(reason?: string): void | Promise<void>;\n}\n\nexport interface LoopLauncherSummary {\n readonly id: string;\n readonly source: string;\n readonly label: string;\n readonly description?: string;\n}\n\nexport interface LoopInstanceSnapshot {\n readonly instanceId: string;\n readonly launcherId: string;\n readonly launcherLabel: string;\n readonly label?: string;\n readonly detail?: string;\n readonly state: 'starting' | 'running' | 'stopping';\n readonly startedAt?: string;\n}\n\nexport interface LoopLauncherRegistration {\n dispose(reason?: string): Promise<void>;\n}\n\nexport interface LoopLauncherClient {\n listLaunchers(): readonly LoopLauncherSummary[];\n listInstances(): readonly LoopInstanceSnapshot[];\n subscribe(listener: () => void): () => void;\n launch(launcherId: string, options?: { instanceId?: string }): Promise<LoopInstanceSnapshot | undefined>;\n stop(instanceId: string, reason?: string): Promise<boolean>;\n stopAll(reason?: string): Promise<void>;\n}\n\nexport interface RegisterLoopLauncherOptions extends LoopLauncherSummary {\n launch(request: LoopLaunchRequest): Promise<StoppableLoop | undefined>;\n}\n\ninterface LauncherEntry {\n readonly token: symbol;\n readonly options: RegisterLoopLauncherOptions;\n}\n\ninterface InstanceEntry {\n readonly instanceId: string;\n readonly launcher: LauncherEntry;\n readonly abortController: AbortController;\n state: LoopInstanceSnapshot['state'];\n handle?: StoppableLoop;\n startedAt?: string;\n}\n\ninterface SessionRegistry {\n readonly launchers: Map<string, LauncherEntry>;\n readonly instances: Map<string, InstanceEntry>;\n readonly listeners: Set<() => void>;\n}\n\ntype LoopRegistry = Map<string, SessionRegistry>;\n\nfunction registry(): LoopRegistry {\n const root = globalThis as typeof globalThis & { [REGISTRY_KEY]?: LoopRegistry };\n return (root[REGISTRY_KEY] ??= new Map());\n}\n\nfunction validId(value: string, label: string): string {\n const normalized = value.trim();\n let hasControl = false;\n for (const character of normalized) {\n const code = character.codePointAt(0);\n if (code !== undefined && (code <= 0x1f || code === 0x7f)) {\n hasControl = true;\n break;\n }\n }\n if (!normalized || normalized.length > MAX_ID_LENGTH || hasControl) throw new Error(`Invalid ${label}.`);\n return normalized;\n}\n\nfunction getSession(sessionId: string, create: boolean): SessionRegistry | undefined {\n const key = validId(sessionId, SESSION_ID_LABEL);\n const existing = registry().get(key);\n if (existing || !create) return existing;\n const created: SessionRegistry = { launchers: new Map(), instances: new Map(), listeners: new Set() };\n registry().set(key, created);\n return created;\n}\n\nfunction notify(session: SessionRegistry): void {\n for (const listener of session.listeners) listener();\n}\n\nfunction snapshot(entry: InstanceEntry): LoopInstanceSnapshot {\n return {\n instanceId: entry.instanceId,\n launcherId: entry.launcher.options.id,\n launcherLabel: entry.launcher.options.label,\n ...(entry.handle?.label ? { label: entry.handle.label } : {}),\n ...(entry.handle?.detail ? { detail: entry.handle.detail } : {}),\n state: entry.state,\n ...(entry.startedAt ? { startedAt: entry.startedAt } : {}),\n };\n}\n\nasync function stopEntry(session: SessionRegistry, entry: InstanceEntry, reason?: string): Promise<void> {\n if (session.instances.get(entry.instanceId) !== entry) return;\n entry.state = 'stopping';\n entry.abortController.abort(reason);\n notify(session);\n try {\n await entry.handle?.stop(reason);\n } finally {\n if (session.instances.get(entry.instanceId) === entry) session.instances.delete(entry.instanceId);\n notify(session);\n }\n}\n\nfunction cleanSession(sessionId: string, session: SessionRegistry): void {\n if (session.launchers.size || session.instances.size || session.listeners.size) return;\n if (registry().get(sessionId) === session) registry().delete(sessionId);\n}\n\nexport function registerLoopLauncher(\n sessionId: string,\n options: RegisterLoopLauncherOptions,\n): LoopLauncherRegistration {\n const sessionKey = validId(sessionId, SESSION_ID_LABEL);\n const launcherId = validId(options.id, LAUNCHER_ID_LABEL);\n const session = getSession(sessionKey, true) as SessionRegistry;\n if (session.launchers.has(launcherId)) {\n throw new Error(`Loop launcher '${launcherId}' is already registered for session '${sessionKey}'.`);\n }\n const entry: LauncherEntry = { token: Symbol(launcherId), options: { ...options, id: launcherId } };\n session.launchers.set(launcherId, entry);\n notify(session);\n let disposed = false;\n return {\n async dispose(reason = 'Loop launcher disposed.'): Promise<void> {\n if (disposed) return;\n disposed = true;\n const current = getSession(sessionKey, false);\n if (!current || current.launchers.get(launcherId)?.token !== entry.token) return;\n current.launchers.delete(launcherId);\n const owned = [...current.instances.values()].filter((instance) => instance.launcher.token === entry.token);\n await Promise.allSettled(owned.map((instance) => stopEntry(current, instance, reason)));\n notify(current);\n cleanSession(sessionKey, current);\n },\n };\n}\n\nexport function createLoopLauncherClient(sessionId: () => string | undefined): LoopLauncherClient {\n const current = (create: boolean): { key: string; session: SessionRegistry } | undefined => {\n const value = sessionId();\n if (!value) return undefined;\n const key = validId(value, SESSION_ID_LABEL);\n const session = getSession(key, create);\n return session ? { key, session } : undefined;\n };\n return {\n listLaunchers() {\n const session = current(false)?.session;\n if (!session) return [];\n return [...session.launchers.values()]\n .map(({ options }) => ({\n id: options.id,\n source: options.source,\n label: options.label,\n ...(options.description ? { description: options.description } : {}),\n }))\n .sort((left, right) => left.label.localeCompare(right.label) || left.id.localeCompare(right.id));\n },\n listInstances() {\n const session = current(false)?.session;\n return session\n ? [...session.instances.values()]\n .map(snapshot)\n .sort((left, right) => left.instanceId.localeCompare(right.instanceId))\n : [];\n },\n subscribe(listener) {\n const target = current(true);\n if (!target) return () => undefined;\n target.session.listeners.add(listener);\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n target.session.listeners.delete(listener);\n cleanSession(target.key, target.session);\n };\n },\n async launch(launcherId, options) {\n const target = current(true);\n if (!target) throw new Error('The session-bound loop launcher registry is unavailable.');\n const normalizedLauncherId = validId(launcherId, LAUNCHER_ID_LABEL);\n const launcher = target.session.launchers.get(normalizedLauncherId);\n if (!launcher) throw new Error(`Loop launcher '${normalizedLauncherId}' is unavailable.`);\n const instanceId = validId(options?.instanceId ?? randomUUID(), INSTANCE_ID_LABEL);\n if (target.session.instances.has(instanceId)) throw new Error(`Loop instance '${instanceId}' already exists.`);\n const entry: InstanceEntry = { instanceId, launcher, abortController: new AbortController(), state: 'starting' };\n target.session.instances.set(instanceId, entry);\n notify(target.session);\n try {\n const handle = await launcher.options.launch({ instanceId, signal: entry.abortController.signal });\n const active = target.session.instances.get(instanceId) === entry && !entry.abortController.signal.aborted;\n if (!handle) {\n if (target.session.instances.get(instanceId) === entry) target.session.instances.delete(instanceId);\n notify(target.session);\n return undefined;\n }\n if (!active || handle.instanceId !== instanceId) {\n await handle.stop(active ? 'Loop launcher returned a mismatched instance id.' : 'Loop launch was cancelled.');\n if (target.session.instances.get(instanceId) === entry) target.session.instances.delete(instanceId);\n notify(target.session);\n if (active) throw new Error(`Loop launcher '${normalizedLauncherId}' returned a mismatched instance id.`);\n return undefined;\n }\n entry.handle = handle;\n entry.state = 'running';\n entry.startedAt = new Date().toISOString();\n notify(target.session);\n return snapshot(entry);\n } catch (error) {\n const cancelled = entry.abortController.signal.aborted;\n if (target.session.instances.get(instanceId) === entry) target.session.instances.delete(instanceId);\n notify(target.session);\n if (cancelled) return undefined;\n throw error;\n }\n },\n async stop(instanceId, reason) {\n const session = current(false)?.session;\n if (!session) return false;\n const entry = session.instances.get(validId(instanceId, INSTANCE_ID_LABEL));\n if (!entry) return false;\n await stopEntry(session, entry, reason);\n return true;\n },\n async stopAll(reason) {\n const session = current(false)?.session;\n if (!session) return;\n await Promise.allSettled([...session.instances.values()].map((entry) => stopEntry(session, entry, reason)));\n },\n };\n}\n"],"mappings":"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"}
package/dist/leader.cjs DELETED
@@ -1,2 +0,0 @@
1
- const e=require("./constants.cjs");let t=require("@agimon-ai/doompi-ui/leader");function n(n){return(0,t.registerDoomLeaderContribution)(n,{source:e.PACKAGE_SOURCE,bindings:[{id:`loop.start`,path:[{key:`l`,label:`loops`,order:60},{key:`s`,label:`start`}],command:{name:e.START_COMMAND_NAME}},{id:`loop.list`,path:[{key:`l`,label:`loops`,order:60},{key:`l`,label:`list`,detail:`stop`}],command:{name:e.LIST_COMMAND_NAME}}]})}exports.registerLeaderContribution=n;
2
- //# sourceMappingURL=leader.cjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"leader.cjs","names":["registerDoomLeaderContribution","PACKAGE_SOURCE","START_COMMAND_NAME","LIST_COMMAND_NAME"],"sources":["../src/leader.ts"],"sourcesContent":["import { registerDoomLeaderContribution } from '@agimon-ai/doompi-ui/leader';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { LIST_COMMAND_NAME, LOOPS_GROUP_ORDER, PACKAGE_SOURCE, START_COMMAND_NAME } from './constants.ts';\n\nexport function registerLeaderContribution(pi: Pick<ExtensionAPI, 'events' | 'on'>): () => void {\n return registerDoomLeaderContribution(pi, {\n source: PACKAGE_SOURCE,\n bindings: [\n {\n id: 'loop.start',\n path: [\n { key: 'l', label: 'loops', order: LOOPS_GROUP_ORDER },\n { key: 's', label: 'start' },\n ],\n command: { name: START_COMMAND_NAME },\n },\n {\n id: 'loop.list',\n path: [\n { key: 'l', label: 'loops', order: LOOPS_GROUP_ORDER },\n { key: 'l', label: 'list', detail: 'stop' },\n ],\n command: { name: LIST_COMMAND_NAME },\n },\n ],\n });\n}\n"],"mappings":"gFAIA,SAAgB,EAA2B,EAAqD,CAC9F,OAAA,EAAOA,EAAAA,+BAAAA,CAA+B,EAAI,CACxC,OAAQC,EAAAA,eACR,SAAU,CACR,CACE,GAAI,aACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,QAAS,MAAA,EAAyB,EACrD,CAAE,IAAK,IAAK,MAAO,OAAQ,CAC7B,EACA,QAAS,CAAE,KAAMC,EAAAA,kBAAmB,CACtC,EACA,CACE,GAAI,YACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,QAAS,MAAA,EAAyB,EACrD,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,MAAO,CAC5C,EACA,QAAS,CAAE,KAAMC,EAAAA,iBAAkB,CACrC,CACF,CACF,CAAC,CACH"}
package/dist/leader.mjs DELETED
@@ -1,2 +0,0 @@
1
- import{LIST_COMMAND_NAME as e,PACKAGE_SOURCE as t,START_COMMAND_NAME as n}from"./constants.mjs";import{registerDoomLeaderContribution as r}from"@agimon-ai/doompi-ui/leader";function i(i){return r(i,{source:t,bindings:[{id:`loop.start`,path:[{key:`l`,label:`loops`,order:60},{key:`s`,label:`start`}],command:{name:n}},{id:`loop.list`,path:[{key:`l`,label:`loops`,order:60},{key:`l`,label:`list`,detail:`stop`}],command:{name:e}}]})}export{i as registerLeaderContribution};
2
- //# sourceMappingURL=leader.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"leader.mjs","names":[],"sources":["../src/leader.ts"],"sourcesContent":["import { registerDoomLeaderContribution } from '@agimon-ai/doompi-ui/leader';\nimport type { ExtensionAPI } from '@earendil-works/pi-coding-agent';\nimport { LIST_COMMAND_NAME, LOOPS_GROUP_ORDER, PACKAGE_SOURCE, START_COMMAND_NAME } from './constants.ts';\n\nexport function registerLeaderContribution(pi: Pick<ExtensionAPI, 'events' | 'on'>): () => void {\n return registerDoomLeaderContribution(pi, {\n source: PACKAGE_SOURCE,\n bindings: [\n {\n id: 'loop.start',\n path: [\n { key: 'l', label: 'loops', order: LOOPS_GROUP_ORDER },\n { key: 's', label: 'start' },\n ],\n command: { name: START_COMMAND_NAME },\n },\n {\n id: 'loop.list',\n path: [\n { key: 'l', label: 'loops', order: LOOPS_GROUP_ORDER },\n { key: 'l', label: 'list', detail: 'stop' },\n ],\n command: { name: LIST_COMMAND_NAME },\n },\n ],\n });\n}\n"],"mappings":"6KAIA,SAAgB,EAA2B,EAAqD,CAC9F,OAAO,EAA+B,EAAI,CACxC,OAAQ,EACR,SAAU,CACR,CACE,GAAI,aACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,QAAS,MAAA,EAAyB,EACrD,CAAE,IAAK,IAAK,MAAO,OAAQ,CAC7B,EACA,QAAS,CAAE,KAAM,CAAmB,CACtC,EACA,CACE,GAAI,YACJ,KAAM,CACJ,CAAE,IAAK,IAAK,MAAO,QAAS,MAAA,EAAyB,EACrD,CAAE,IAAK,IAAK,MAAO,OAAQ,OAAQ,MAAO,CAC5C,EACA,QAAS,CAAE,KAAM,CAAkB,CACrC,CACF,CACF,CAAC,CACH"}