@agimon-ai/doompi-loop 0.0.1-alpha.23 → 0.0.1-alpha.24

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 (34) hide show
  1. package/README.md +4 -3
  2. package/dist/adapters/pi/defaultLoopLauncher.cjs +1 -1
  3. package/dist/adapters/pi/defaultLoopLauncher.cjs.map +1 -1
  4. package/dist/adapters/pi/defaultLoopLauncher.mjs +1 -1
  5. package/dist/adapters/pi/defaultLoopLauncher.mjs.map +1 -1
  6. package/dist/adapters/pi/extension.cjs +1 -1
  7. package/dist/adapters/pi/extension.cjs.map +1 -1
  8. package/dist/adapters/pi/extension.d.cts +6 -2
  9. package/dist/adapters/pi/extension.d.cts.map +1 -1
  10. package/dist/adapters/pi/extension.d.mts +6 -2
  11. package/dist/adapters/pi/extension.d.mts.map +1 -1
  12. package/dist/adapters/pi/extension.mjs +1 -1
  13. package/dist/adapters/pi/extension.mjs.map +1 -1
  14. package/dist/extensions/pi.cjs +1 -1
  15. package/dist/extensions/pi.d.cts +2 -2
  16. package/dist/extensions/pi.d.mts +2 -2
  17. package/dist/extensions/pi.mjs +1 -1
  18. package/dist/index.cjs +1 -1
  19. package/dist/index.d.cts +3 -3
  20. package/dist/index.d.mts +3 -3
  21. package/dist/index.mjs +1 -1
  22. package/dist/services/launcher.cjs.map +1 -0
  23. package/dist/services/{loop/launcher.d.cts → launcher.d.cts} +2 -2
  24. package/dist/services/launcher.d.cts.map +1 -0
  25. package/dist/services/{loop/launcher.d.mts → launcher.d.mts} +2 -2
  26. package/dist/services/launcher.d.mts.map +1 -0
  27. package/dist/services/launcher.mjs.map +1 -0
  28. package/package.json +7 -3
  29. package/dist/services/loop/launcher.cjs.map +0 -1
  30. package/dist/services/loop/launcher.d.cts.map +0 -1
  31. package/dist/services/loop/launcher.d.mts.map +0 -1
  32. package/dist/services/loop/launcher.mjs.map +0 -1
  33. /package/dist/services/{loop/launcher.cjs → launcher.cjs} +0 -0
  34. /package/dist/services/{loop/launcher.mjs → launcher.mjs} +0 -0
package/README.md CHANGED
@@ -15,7 +15,8 @@ Several loops can coexist. Loop is a minor mode and remains active while at leas
15
15
 
16
16
  ## Install
17
17
 
18
- DoomPi loads Loop as core. For standalone Pi:
18
+ DoomPi includes Loop in every composition. Pi discovers its sole extension entry through
19
+ `package.json.pi.extensions`. For standalone Pi:
19
20
 
20
21
  ```bash
21
22
  pi install npm:@agimon-ai/doompi-loop
@@ -41,12 +42,12 @@ Every pass can start another model turn and repeat tool or external side effects
41
42
  import {
42
43
  createDefaultLoopLauncher,
43
44
  createLoopLauncherClient,
44
- registerLoopExtension,
45
+ installLoopRuntime,
45
46
  registerLoopLauncher,
46
47
  } from '@agimon-ai/doompi-loop';
47
48
  ```
48
49
 
49
- Other extensions can register a launcher alongside the default prompt launcher without forking the scheduler.
50
+ `installLoopRuntime` installs package resources into a supplied package-local Cordis root. Normal Pi activation should use the discovered `extensions/pi` entry. Other extensions can register a launcher alongside the default prompt launcher without forking the scheduler.
50
51
 
51
52
  ## Development
52
53
 
@@ -1,2 +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;
1
+ const e=require("../../services/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
2
  //# sourceMappingURL=defaultLoopLauncher.cjs.map
@@ -1 +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"}
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/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":"gFAQM,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"}
@@ -1,2 +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};
1
+ import{registerLoopLauncher as e}from"../../services/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
2
  //# sourceMappingURL=defaultLoopLauncher.mjs.map
@@ -1 +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"}
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/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":"kJAKA,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"}
@@ -1,2 +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;
1
+ const e=require("../../services/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"),c=require("@deepseek-ai/cordis");const 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(c,m){let h=!0,g=0,_,v,y,b,x,S,C,w=Promise.resolve(),T=new Set,E=e=>(T.add(e),e.then(()=>T.delete(e),()=>T.delete(e)),e);c.effect(()=>async()=>{h=!1,g+=1;let e=v,n=[];if(y?.(),y=void 0,await Promise.allSettled([w,...T]),x)try{await x.stopAll(f)}catch(e){n.push(e)}try{await b?.dispose(f)}catch(e){n.push(e)}b=void 0,e?.ui?.setStatus(t.STATUS_KEY,void 0);try{S?.publish(p([],[])),S?.dispose()}catch(e){n.push(e)}try{C?.()}catch(e){n.push(e)}if(C=void 0,S=void 0,x=void 0,v=void 0,_=void 0,n.length>0)throw AggregateError(n,`Loop runtime cleanup failed.`)},`${t.PACKAGE_SOURCE}/runtime`);let D=e.createLoopLauncherClient(()=>_);x=D;let O=n.createDefaultLoopLauncher(m);S=(0,s.registerMinorModeOwner)(m,{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(!h)throw Error(`Loop runtime is disposed.`);let n=g;if(e===l){let e=await E(D.launch(t.launcherId,t.instanceId?{instanceId:t.instanceId}:{}));if(!h||n!==g)throw Error(`Loop action became stale.`);return{message:e?`Loop '${e.instanceId}' started.`:`Loop launch was cancelled.`}}if(e===u){let e=await E(D.stop(t.instanceId,t.reason??`Stopped through minor_mode.`));if(!h||n!==g)throw Error(`Loop action became stale.`);return{message:e?`Loop stopped.`:`Loop instance was not active.`}}throw Error(`Unknown loop mode action: ${e}`)}}),C=o.registerLeaderContribution(m);let k=()=>{if(!h)return;let e=D.listLaunchers(),n=D.listInstances(),r=n.length;v?.ui?.setStatus(t.STATUS_KEY,r?`loops: ${r}`:void 0),S?.publish(p(e,n))};r.registerCommands(m,{async start(e){if(!h)return;let n=g,r=D.listLaunchers();if(!r.length){e.ui.notify(`No loop launchers are registered for this session.`,t.NOTIFY_INFO_LEVEL);return}let i=await a.openStartLoopOverlay(e,r);if(!(!h||n!==g||!i))try{let r=await E(D.launch(i));if(!h||n!==g)return;r&&e.ui.notify(`${r.label??r.launcherLabel} started.`,t.NOTIFY_INFO_LEVEL)}catch(t){if(!h||n!==g)return;e.ui.notify(`Loop could not start: ${t instanceof Error?t.message:String(t)}`,`error`)}},async list(e){h&&await i.openLoopListOverlay(e,D)}}),m.on(`session_start`,(e,n)=>{if(!h)return Promise.resolve();let r=++g,i=async()=>{!h||r!==g||(y?.(),y=void 0,await D.stopAll(d),!(!h||r!==g)&&(await b?.dispose(d),!(!h||r!==g)&&(b=void 0,v?.ui?.setStatus(t.STATUS_KEY,void 0),v=n,_=n.sessionManager.getSessionId(),b=O.register(n),y=D.subscribe(k),k())))},a=w.then(i,i);return w=a.then(()=>void 0,()=>void 0),a})}async function h(e){let t=new c.Context,n,r=()=>(n??=t.fiber.dispose(),n);try{m(t,e)}catch(e){throw await r(),e}e.on(`session_shutdown`,r)}exports.default=h,exports.loopExtension=h,exports.installLoopRuntime=m;
2
2
  //# sourceMappingURL=extension.cjs.map
@@ -1 +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
+ {"version":3,"file":"extension.cjs","names":["ACTIVE_MODE_COLOR","STATUS_KEY","PACKAGE_SOURCE","createLoopLauncherClient","createDefaultLoopLauncher","registerMinorModeOwner","MODE_STATUS_ID","registerLeaderContribution","NOTIFY_INFO_LEVEL","openStartLoopOverlay","openLoopListOverlay","Context"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import {\n type MinorModeOwnerHandle,\n type MinorModeState,\n registerMinorModeOwner,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommands } from '../../commands/loopCommand.ts';\nimport { createLoopLauncherClient } from '../../services/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 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\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\n/** Install Loop state and external resources into one package-local Cordis root. */\nexport function installLoopRuntime(cordis: Context, pi: ExtensionAPI): void {\n let active = true;\n let sessionGeneration = 0;\n let sessionId: string | undefined;\n let context: ExtensionContext | undefined;\n let unsubscribe: (() => void) | undefined;\n let defaultRegistration: LoopLauncherRegistration | undefined;\n let client: ReturnType<typeof createLoopLauncherClient> | undefined;\n let mode: MinorModeOwnerHandle | undefined;\n let disposeLeader: (() => void) | undefined;\n let lifecycleQueue: Promise<void> = Promise.resolve();\n const pendingOperations = new Set<Promise<unknown>>();\n\n const trackOperation = <T>(operation: Promise<T>): Promise<T> => {\n pendingOperations.add(operation);\n void operation.then(\n () => pendingOperations.delete(operation),\n () => pendingOperations.delete(operation),\n );\n return operation;\n };\n\n cordis.effect(\n () => async () => {\n active = false;\n sessionGeneration += 1;\n const cleanupContext = context;\n const cleanupErrors: unknown[] = [];\n unsubscribe?.();\n unsubscribe = undefined;\n await Promise.allSettled([lifecycleQueue, ...pendingOperations]);\n if (client) {\n try {\n await client.stopAll(SESSION_SHUTDOWN_REASON);\n } catch (error) {\n cleanupErrors.push(error);\n }\n }\n try {\n await defaultRegistration?.dispose(SESSION_SHUTDOWN_REASON);\n } catch (error) {\n cleanupErrors.push(error);\n }\n defaultRegistration = undefined;\n cleanupContext?.ui?.setStatus(STATUS_KEY, undefined);\n try {\n mode?.publish(loopModeState([], []));\n mode?.dispose();\n } catch (error) {\n cleanupErrors.push(error);\n }\n try {\n disposeLeader?.();\n } catch (error) {\n cleanupErrors.push(error);\n }\n disposeLeader = undefined;\n mode = undefined;\n client = undefined;\n context = undefined;\n sessionId = undefined;\n if (cleanupErrors.length > 0) throw new AggregateError(cleanupErrors, 'Loop runtime cleanup failed.');\n },\n `${PACKAGE_SOURCE}/runtime`,\n );\n\n const launcherClient = createLoopLauncherClient(() => sessionId);\n client = launcherClient;\n const defaultLauncher = createDefaultLoopLauncher(pi);\n 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 (!active) throw new Error('Loop runtime is disposed.');\n const generation = sessionGeneration;\n if (actionId === MODE_ACTION_START) {\n const instance = await trackOperation(\n launcherClient.launch(\n argumentsValue.launcherId as string,\n argumentsValue.instanceId ? { instanceId: argumentsValue.instanceId as string } : {},\n ),\n );\n if (!active || generation !== sessionGeneration) throw new Error('Loop action became stale.');\n return { message: instance ? `Loop '${instance.instanceId}' started.` : 'Loop launch was cancelled.' };\n }\n if (actionId === MODE_ACTION_STOP) {\n const stopped = await trackOperation(\n launcherClient.stop(\n argumentsValue.instanceId as string,\n (argumentsValue.reason as string | undefined) ?? 'Stopped through minor_mode.',\n ),\n );\n if (!active || generation !== sessionGeneration) throw new Error('Loop action became stale.');\n return { message: stopped ? 'Loop stopped.' : 'Loop instance was not active.' };\n }\n throw new Error(`Unknown loop mode action: ${actionId}`);\n },\n });\n disposeLeader = registerLeaderContribution(pi);\n\n const updateStatus = (): void => {\n if (!active) return;\n const launchers = launcherClient.listLaunchers();\n const instances = launcherClient.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 if (!active) return;\n const generation = sessionGeneration;\n const launchers = launcherClient.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 (!active || generation !== sessionGeneration || !launcherId) return;\n try {\n const instance = await trackOperation(launcherClient.launch(launcherId));\n if (!active || generation !== sessionGeneration) return;\n if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, NOTIFY_INFO_LEVEL);\n } catch (error) {\n if (!active || generation !== sessionGeneration) return;\n ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, 'error');\n }\n },\n async list(ctx) {\n if (!active) return;\n await openLoopListOverlay(ctx, launcherClient);\n },\n });\n\n pi.on('session_start', (_event, ctx) => {\n if (!active) return Promise.resolve();\n const generation = ++sessionGeneration;\n const startSession = async (): Promise<void> => {\n if (!active || generation !== sessionGeneration) return;\n unsubscribe?.();\n unsubscribe = undefined;\n await launcherClient.stopAll(SESSION_REPLACED_REASON);\n if (!active || generation !== sessionGeneration) return;\n await defaultRegistration?.dispose(SESSION_REPLACED_REASON);\n if (!active || generation !== sessionGeneration) return;\n defaultRegistration = undefined;\n context?.ui?.setStatus(STATUS_KEY, undefined);\n context = ctx;\n sessionId = ctx.sessionManager.getSessionId();\n defaultRegistration = defaultLauncher.register(ctx);\n unsubscribe = launcherClient.subscribe(updateStatus);\n updateStatus();\n };\n const queued = lifecycleQueue.then(startSession, startSession);\n lifecycleQueue = queued.then(\n () => undefined,\n () => undefined,\n );\n return queued;\n });\n}\n\n/** The package's single standard Pi factory. */\nexport async function loopExtension(pi: ExtensionAPI): Promise<void> {\n const cordis = new Context();\n let disposal: Promise<void> | undefined;\n const dispose = (): Promise<void> => {\n disposal ??= cordis.fiber.dispose();\n return disposal;\n };\n\n try {\n installLoopRuntime(cordis, pi);\n } catch (error) {\n await dispose();\n throw error;\n }\n\n pi.on('session_shutdown', dispose);\n}\n\nexport default loopExtension;\n"],"mappings":"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"}
@@ -1,6 +1,10 @@
1
+ import { Context } from "@deepseek-ai/cordis";
1
2
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
3
  //#region src/adapters/pi/extension.d.ts
3
- declare function registerLoopExtension(pi: ExtensionAPI): void;
4
+ /** Install Loop state and external resources into one package-local Cordis root. */
5
+ declare function installLoopRuntime(cordis: Context, pi: ExtensionAPI): void;
6
+ /** The package's single standard Pi factory. */
7
+ declare function loopExtension(pi: ExtensionAPI): Promise<void>;
4
8
  //#endregion
5
- export { registerLoopExtension };
9
+ export { loopExtension as default, loopExtension, installLoopRuntime };
6
10
  //# sourceMappingURL=extension.d.cts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;iBAyDgB,sBAAsB,IAAI"}
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBAiDgB,mBAAmB,QAAQ,SAAS,IAAI;;iBAgMlC,cAAc,IAAI,eAAe"}
@@ -1,6 +1,10 @@
1
+ import { Context } from "@deepseek-ai/cordis";
1
2
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
3
  //#region src/adapters/pi/extension.d.ts
3
- declare function registerLoopExtension(pi: ExtensionAPI): void;
4
+ /** Install Loop state and external resources into one package-local Cordis root. */
5
+ declare function installLoopRuntime(cordis: Context, pi: ExtensionAPI): void;
6
+ /** The package's single standard Pi factory. */
7
+ declare function loopExtension(pi: ExtensionAPI): Promise<void>;
4
8
  //#endregion
5
- export { registerLoopExtension };
9
+ export { loopExtension as default, loopExtension, installLoopRuntime };
6
10
  //# sourceMappingURL=extension.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;iBAyDgB,sBAAsB,IAAI"}
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBAiDgB,mBAAmB,QAAQ,SAAS,IAAI;;iBAgMlC,cAAc,IAAI,eAAe"}
@@ -1,2 +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};
1
+ import{createLoopLauncherClient as e}from"../../services/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";import{Context as f}from"@deepseek-ai/cordis";const 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(t,f){let v=!0,y=0,b,x,S,C,w,T,E,D=Promise.resolve(),O=new Set,k=e=>(O.add(e),e.then(()=>O.delete(e),()=>O.delete(e)),e);t.effect(()=>async()=>{v=!1,y+=1;let e=x,t=[];if(S?.(),S=void 0,await Promise.allSettled([D,...O]),w)try{await w.stopAll(g)}catch(e){t.push(e)}try{await C?.dispose(g)}catch(e){t.push(e)}C=void 0,e?.ui?.setStatus(a,void 0);try{T?.publish(_([],[])),T?.dispose()}catch(e){t.push(e)}try{E?.()}catch(e){t.push(e)}if(E=void 0,T=void 0,w=void 0,x=void 0,b=void 0,t.length>0)throw AggregateError(t,`Loop runtime cleanup failed.`)},`${i}/runtime`);let A=e(()=>b);w=A;let j=o(f);T=d(f,{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(!v)throw Error(`Loop runtime is disposed.`);let n=y;if(e===p){let e=await k(A.launch(t.launcherId,t.instanceId?{instanceId:t.instanceId}:{}));if(!v||n!==y)throw Error(`Loop action became stale.`);return{message:e?`Loop '${e.instanceId}' started.`:`Loop launch was cancelled.`}}if(e===m){let e=await k(A.stop(t.instanceId,t.reason??`Stopped through minor_mode.`));if(!v||n!==y)throw Error(`Loop action became stale.`);return{message:e?`Loop stopped.`:`Loop instance was not active.`}}throw Error(`Unknown loop mode action: ${e}`)}}),E=u(f);let M=()=>{if(!v)return;let e=A.listLaunchers(),t=A.listInstances(),n=t.length;x?.ui?.setStatus(a,n?`loops: ${n}`:void 0),T?.publish(_(e,t))};s(f,{async start(e){if(!v)return;let t=y,n=A.listLaunchers();if(!n.length){e.ui.notify(`No loop launchers are registered for this session.`,r);return}let i=await l(e,n);if(!(!v||t!==y||!i))try{let n=await k(A.launch(i));if(!v||t!==y)return;n&&e.ui.notify(`${n.label??n.launcherLabel} started.`,r)}catch(n){if(!v||t!==y)return;e.ui.notify(`Loop could not start: ${n instanceof Error?n.message:String(n)}`,`error`)}},async list(e){v&&await c(e,A)}}),f.on(`session_start`,(e,t)=>{if(!v)return Promise.resolve();let n=++y,r=async()=>{!v||n!==y||(S?.(),S=void 0,await A.stopAll(h),!(!v||n!==y)&&(await C?.dispose(h),!(!v||n!==y)&&(C=void 0,x?.ui?.setStatus(a,void 0),x=t,b=t.sessionManager.getSessionId(),C=j.register(t),S=A.subscribe(M),M())))},i=D.then(r,r);return D=i.then(()=>void 0,()=>void 0),i})}async function y(e){let t=new f,n,r=()=>(n??=t.fiber.dispose(),n);try{v(t,e)}catch(e){throw await r(),e}e.on(`session_shutdown`,r)}export{y as default,y as loopExtension,v as installLoopRuntime};
2
2
  //# sourceMappingURL=extension.mjs.map
@@ -1 +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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"}
1
+ {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import {\n type MinorModeOwnerHandle,\n type MinorModeState,\n registerMinorModeOwner,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommands } from '../../commands/loopCommand.ts';\nimport { createLoopLauncherClient } from '../../services/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 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\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\n/** Install Loop state and external resources into one package-local Cordis root. */\nexport function installLoopRuntime(cordis: Context, pi: ExtensionAPI): void {\n let active = true;\n let sessionGeneration = 0;\n let sessionId: string | undefined;\n let context: ExtensionContext | undefined;\n let unsubscribe: (() => void) | undefined;\n let defaultRegistration: LoopLauncherRegistration | undefined;\n let client: ReturnType<typeof createLoopLauncherClient> | undefined;\n let mode: MinorModeOwnerHandle | undefined;\n let disposeLeader: (() => void) | undefined;\n let lifecycleQueue: Promise<void> = Promise.resolve();\n const pendingOperations = new Set<Promise<unknown>>();\n\n const trackOperation = <T>(operation: Promise<T>): Promise<T> => {\n pendingOperations.add(operation);\n void operation.then(\n () => pendingOperations.delete(operation),\n () => pendingOperations.delete(operation),\n );\n return operation;\n };\n\n cordis.effect(\n () => async () => {\n active = false;\n sessionGeneration += 1;\n const cleanupContext = context;\n const cleanupErrors: unknown[] = [];\n unsubscribe?.();\n unsubscribe = undefined;\n await Promise.allSettled([lifecycleQueue, ...pendingOperations]);\n if (client) {\n try {\n await client.stopAll(SESSION_SHUTDOWN_REASON);\n } catch (error) {\n cleanupErrors.push(error);\n }\n }\n try {\n await defaultRegistration?.dispose(SESSION_SHUTDOWN_REASON);\n } catch (error) {\n cleanupErrors.push(error);\n }\n defaultRegistration = undefined;\n cleanupContext?.ui?.setStatus(STATUS_KEY, undefined);\n try {\n mode?.publish(loopModeState([], []));\n mode?.dispose();\n } catch (error) {\n cleanupErrors.push(error);\n }\n try {\n disposeLeader?.();\n } catch (error) {\n cleanupErrors.push(error);\n }\n disposeLeader = undefined;\n mode = undefined;\n client = undefined;\n context = undefined;\n sessionId = undefined;\n if (cleanupErrors.length > 0) throw new AggregateError(cleanupErrors, 'Loop runtime cleanup failed.');\n },\n `${PACKAGE_SOURCE}/runtime`,\n );\n\n const launcherClient = createLoopLauncherClient(() => sessionId);\n client = launcherClient;\n const defaultLauncher = createDefaultLoopLauncher(pi);\n 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 (!active) throw new Error('Loop runtime is disposed.');\n const generation = sessionGeneration;\n if (actionId === MODE_ACTION_START) {\n const instance = await trackOperation(\n launcherClient.launch(\n argumentsValue.launcherId as string,\n argumentsValue.instanceId ? { instanceId: argumentsValue.instanceId as string } : {},\n ),\n );\n if (!active || generation !== sessionGeneration) throw new Error('Loop action became stale.');\n return { message: instance ? `Loop '${instance.instanceId}' started.` : 'Loop launch was cancelled.' };\n }\n if (actionId === MODE_ACTION_STOP) {\n const stopped = await trackOperation(\n launcherClient.stop(\n argumentsValue.instanceId as string,\n (argumentsValue.reason as string | undefined) ?? 'Stopped through minor_mode.',\n ),\n );\n if (!active || generation !== sessionGeneration) throw new Error('Loop action became stale.');\n return { message: stopped ? 'Loop stopped.' : 'Loop instance was not active.' };\n }\n throw new Error(`Unknown loop mode action: ${actionId}`);\n },\n });\n disposeLeader = registerLeaderContribution(pi);\n\n const updateStatus = (): void => {\n if (!active) return;\n const launchers = launcherClient.listLaunchers();\n const instances = launcherClient.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 if (!active) return;\n const generation = sessionGeneration;\n const launchers = launcherClient.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 (!active || generation !== sessionGeneration || !launcherId) return;\n try {\n const instance = await trackOperation(launcherClient.launch(launcherId));\n if (!active || generation !== sessionGeneration) return;\n if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, NOTIFY_INFO_LEVEL);\n } catch (error) {\n if (!active || generation !== sessionGeneration) return;\n ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, 'error');\n }\n },\n async list(ctx) {\n if (!active) return;\n await openLoopListOverlay(ctx, launcherClient);\n },\n });\n\n pi.on('session_start', (_event, ctx) => {\n if (!active) return Promise.resolve();\n const generation = ++sessionGeneration;\n const startSession = async (): Promise<void> => {\n if (!active || generation !== sessionGeneration) return;\n unsubscribe?.();\n unsubscribe = undefined;\n await launcherClient.stopAll(SESSION_REPLACED_REASON);\n if (!active || generation !== sessionGeneration) return;\n await defaultRegistration?.dispose(SESSION_REPLACED_REASON);\n if (!active || generation !== sessionGeneration) return;\n defaultRegistration = undefined;\n context?.ui?.setStatus(STATUS_KEY, undefined);\n context = ctx;\n sessionId = ctx.sessionManager.getSessionId();\n defaultRegistration = defaultLauncher.register(ctx);\n unsubscribe = launcherClient.subscribe(updateStatus);\n updateStatus();\n };\n const queued = lifecycleQueue.then(startSession, startSession);\n lifecycleQueue = queued.then(\n () => undefined,\n () => undefined,\n );\n return queued;\n });\n}\n\n/** The package's single standard Pi factory. */\nexport async function loopExtension(pi: ExtensionAPI): Promise<void> {\n const cordis = new Context();\n let disposal: Promise<void> | undefined;\n const dispose = (): Promise<void> => {\n disposal ??= cordis.fiber.dispose();\n return disposal;\n };\n\n try {\n installLoopRuntime(cordis, pi);\n } catch (error) {\n await dispose();\n throw error;\n }\n\n pi.on('session_shutdown', dispose);\n}\n\nexport default loopExtension;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- const e=require("../adapters/pi/extension.cjs");module.exports=e.registerLoopExtension;
1
+ const e=require("../adapters/pi/extension.cjs");module.exports=e.default;
@@ -1,2 +1,2 @@
1
- import { registerLoopExtension } from "../adapters/pi/extension.cjs";
2
- export = registerLoopExtension;
1
+ import loopExtension from "../adapters/pi/extension.cjs";
2
+ export = loopExtension;
@@ -1,2 +1,2 @@
1
- import { registerLoopExtension } from "../adapters/pi/extension.mjs";
2
- export { registerLoopExtension as default };
1
+ import loopExtension from "../adapters/pi/extension.mjs";
2
+ export { loopExtension as default };
@@ -1 +1 @@
1
- import{registerLoopExtension as e}from"../adapters/pi/extension.mjs";export{e as default};
1
+ import 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("./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;
1
+ Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});const e=require("./services/launcher.cjs"),t=require("./adapters/pi/defaultLoopLauncher.cjs"),n=require("./adapters/pi/extension.cjs");exports.createDefaultLoopLauncher=t.createDefaultLoopLauncher,exports.createLoopLauncherClient=e.createLoopLauncherClient,exports.installLoopRuntime=n.installLoopRuntime,exports.registerLoopLauncher=e.registerLoopLauncher;
package/dist/index.d.cts CHANGED
@@ -1,5 +1,5 @@
1
- import { registerLoopExtension } from "./adapters/pi/extension.cjs";
1
+ import { installLoopRuntime } from "./adapters/pi/extension.cjs";
2
2
  import { LoopInstanceSnapshot, LoopLaunchRequest, LoopLauncherClient, LoopLauncherRegistration, LoopLauncherSummary, RegisterLoopLauncherOptions, StoppableLoop } from "./types/loop.cjs";
3
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 };
4
+ import { createLoopLauncherClient, registerLoopLauncher } from "./services/launcher.cjs";
5
+ export { type DefaultLoopLauncher, type LoopInstanceSnapshot, type LoopLaunchRequest, type LoopLauncherClient, type LoopLauncherRegistration, type LoopLauncherSummary, type RegisterLoopLauncherOptions, type StoppableLoop, createDefaultLoopLauncher, createLoopLauncherClient, installLoopRuntime, registerLoopLauncher };
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { registerLoopExtension } from "./adapters/pi/extension.mjs";
1
+ import { installLoopRuntime } from "./adapters/pi/extension.mjs";
2
2
  import { LoopInstanceSnapshot, LoopLaunchRequest, LoopLauncherClient, LoopLauncherRegistration, LoopLauncherSummary, RegisterLoopLauncherOptions, StoppableLoop } from "./types/loop.mjs";
3
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 };
4
+ import { createLoopLauncherClient, registerLoopLauncher } from "./services/launcher.mjs";
5
+ export { type DefaultLoopLauncher, type LoopInstanceSnapshot, type LoopLaunchRequest, type LoopLauncherClient, type LoopLauncherRegistration, type LoopLauncherSummary, type RegisterLoopLauncherOptions, type StoppableLoop, createDefaultLoopLauncher, createLoopLauncherClient, installLoopRuntime, registerLoopLauncher };
package/dist/index.mjs CHANGED
@@ -1 +1 @@
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};
1
+ import{createLoopLauncherClient as e,registerLoopLauncher as t}from"./services/launcher.mjs";import{createDefaultLoopLauncher as n}from"./adapters/pi/defaultLoopLauncher.mjs";import{installLoopRuntime as r}from"./adapters/pi/extension.mjs";export{n as createDefaultLoopLauncher,e as createLoopLauncherClient,r as installLoopRuntime,t as registerLoopLauncher};
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.cjs","names":[],"sources":["../../src/services/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,5 +1,5 @@
1
- import { LoopLauncherClient, LoopLauncherRegistration, RegisterLoopLauncherOptions } from "../../types/loop.cjs";
2
- //#region src/services/loop/launcher.d.ts
1
+ import { LoopLauncherClient, LoopLauncherRegistration, RegisterLoopLauncherOptions } from "../types/loop.cjs";
2
+ //#region src/services/launcher.d.ts
3
3
  declare function registerLoopLauncher(sessionId: string, options: RegisterLoopLauncherOptions): LoopLauncherRegistration;
4
4
  declare function createLoopLauncherClient(sessionId: () => string | undefined): LoopLauncherClient;
5
5
  //#endregion
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.d.cts","names":[],"sources":["../../src/services/launcher.ts"],"mappings":";;iBAuGgB,qBACd,mBACA,SAAS,8BACR;iBA0Ba,yBAAyB,sCAAsC"}
@@ -1,5 +1,5 @@
1
- import { LoopLauncherClient, LoopLauncherRegistration, RegisterLoopLauncherOptions } from "../../types/loop.mjs";
2
- //#region src/services/loop/launcher.d.ts
1
+ import { LoopLauncherClient, LoopLauncherRegistration, RegisterLoopLauncherOptions } from "../types/loop.mjs";
2
+ //#region src/services/launcher.d.ts
3
3
  declare function registerLoopLauncher(sessionId: string, options: RegisterLoopLauncherOptions): LoopLauncherRegistration;
4
4
  declare function createLoopLauncherClient(sessionId: () => string | undefined): LoopLauncherClient;
5
5
  //#endregion
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.d.mts","names":[],"sources":["../../src/services/launcher.ts"],"mappings":";;iBAuGgB,qBACd,mBACA,SAAS,8BACR;iBA0Ba,yBAAyB,sCAAsC"}
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.mjs","names":[],"sources":["../../src/services/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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"}
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-loop",
3
- "version": "0.0.1-alpha.23",
3
+ "version": "0.0.1-alpha.24",
4
4
  "description": "Session-scoped recurring prompt scheduler and loop controls for Pi coding agents.",
5
5
  "keywords": [
6
+ "ai",
6
7
  "automation",
7
8
  "coding-agent",
9
+ "developer-tools",
8
10
  "doompi",
9
11
  "pi-coding-agent",
10
12
  "pi-extension",
13
+ "pi-package",
11
14
  "prompt-scheduler",
12
15
  "recurring-tasks"
13
16
  ],
@@ -39,8 +42,9 @@
39
42
  "access": "public"
40
43
  },
41
44
  "dependencies": {
42
- "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.23",
43
- "@agimon-ai/doompi-ui": "0.0.1-alpha.23"
45
+ "@deepseek-ai/cordis": "4.0.1",
46
+ "@agimon-ai/doompi-ui": "0.0.1-alpha.24",
47
+ "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.24"
44
48
  },
45
49
  "devDependencies": {
46
50
  "@earendil-works/pi-coding-agent": "0.84.2",
@@ -1 +0,0 @@
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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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"launcher.d.cts","names":[],"sources":["../../../src/services/loop/launcher.ts"],"mappings":";;iBAuGgB,qBACd,mBACA,SAAS,8BACR;iBA0Ba,yBAAyB,sCAAsC"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"launcher.d.mts","names":[],"sources":["../../../src/services/loop/launcher.ts"],"mappings":";;iBAuGgB,qBACd,mBACA,SAAS,8BACR;iBA0Ba,yBAAyB,sCAAsC"}
@@ -1 +0,0 @@
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"}