@agimon-ai/doompi-loop 0.0.1-alpha.68 → 0.0.1-alpha.69

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -45,6 +45,12 @@ Every pass can start another model turn and repeat tool or external side effects
45
45
  conservative intervals, make prompts idempotent where possible, and stop loops when the recurring
46
46
  work is complete.
47
47
 
48
+ ## Web extension slots
49
+
50
+ The Loop Activity section declares `loop.registration` for start controls and `loop.items` for
51
+ session-scoped instance rows. Other web plugins may fill either slot; Loop owns the section shell and
52
+ renders all registered fills without requiring a feature-specific dependency.
53
+
48
54
  ## Extension authors: cross-extension launchers
49
55
 
50
56
  Launcher definitions use the shared Cordis service. A consumer registers a launcher and returns its
@@ -1,2 +1,2 @@
1
- const e=require("../../commands/loopCommand.cjs"),t=require("../../services/loopLaunchers.cjs"),n=require("../../tui/loopListOverlay.cjs"),r=require("../../tui/startLoopOverlay.cjs"),i=require("../../types/loopView.cjs"),a=require("./loopConstants.cjs"),o=require("./defaultLoopLauncher.cjs"),s=require("./leader.cjs");let c=require("@agimon-ai/doompi-extension-contracts/mode"),l=require("@agimon-ai/doompi-extension-contracts/cordis-host"),u=require("@agimon-ai/doompi-extension-contracts/help"),d=require("@agimon-ai/doompi-extension-contracts/loop-launchers"),f=require("@agimon-ai/doompi-extension-contracts/ui-hub");const p=`start`,m=`stop`,h=`Pi session replaced.`;function g(e,t){let n=t.length>0;return{activation:n?`active`:`inactive`,condition:t.some(({state:e})=>e===`starting`||e===`stopping`)?`queued`:`ready`,...n?{detail:`${t.length} active`,color:a.ACTIVE_MODE_COLOR}:{},actions:[...e.length>0?[{id:p,enabled:!0}]:[{id:p,enabled:!1,disabledReason:`No loop launchers are registered.`}],...n?[{id:m,enabled:!0}]:[{id:m,enabled:!1,disabledReason:`No loops are active.`}]]}}function _(u,_){let v=!0,y,b,x,S=e=>{let t=y;if(v&&t&&t.context.sessionManager===e.sessionManager&&t.hostSession.sessionId===e.sessionManager.getSessionId())return t},C=e=>{e.context.ui?.setStatus(a.STATUS_KEY,void 0),e.context.mode!==`tui`&&e.context.ui?.setStatus(i.LOOP_VIEW_STATUS_KEY,void 0)},w=e=>{if(!v||y!==e)return;let t=e.launchers.listLaunchers(),n=e.launchers.listInstances();e.context.ui?.setStatus(a.STATUS_KEY,n.length?`loops: ${n.length}`:void 0),e.context.mode!==`tui`&&e.context.ui?.setStatus(i.LOOP_VIEW_STATUS_KEY,i.formatLoopStatusView(n)),b?.publish(g(t,n))};u.effect(()=>()=>{v=!1;let e=y;y=void 0,e&&C(e),b?.publish(g([],[])),b?.dispose(),x?.(),x=void 0,b=void 0},`${a.PACKAGE_SOURCE}/runtime`);let T=o.createDefaultLoopLauncher(_);u.inject([c.DOOM_MINOR_MODE_CATALOG_SERVICE],e=>{let t=(0,c.registerMinorModeOwner)((0,c.requireMinorModeCatalog)(e),{descriptor:{source:a.PACKAGE_SOURCE,id:a.MODE_STATUS_ID,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:g([],[]),async handleAction(e,t,n){let r=S(n.context);if(!r)throw Error(`Loop launchers are unavailable for the active session.`);if(e===p){let e=await r.launchers.launch(t.launcherId,t.instanceId?{instanceId:t.instanceId}:{});if(y!==r)throw Error(`Loop action became stale.`);return{message:e?`Loop '${e.instanceId}' started.`:`Loop launch was cancelled.`}}if(e===m){let e=await r.launchers.stop(t.instanceId,t.reason??`Stopped through minor_mode.`);if(y!==r)throw Error(`Loop action became stale.`);return{message:e?`Loop stopped.`:`Loop instance was not active.`}}throw Error(`Unknown loop mode action: ${e}`)}});b=t;let n=y;return n&&w(n),()=>{t.dispose(),b===t&&(b=void 0)}}),u.inject([f.DOOM_UI_HUB_SERVICE],e=>{let t=s.registerLeaderContribution((0,f.requireDoomUiHub)(e));return x=t,()=>{t(),x===t&&(x=void 0)}}),e.registerCommands(_,{async start(e){let t=S(e);if(!t)return;let n=t.launchers.listLaunchers();if(!n.length){e.ui.notify(`No loop launchers are registered for this session.`,a.NOTIFY_INFO_LEVEL);return}let i=await r.openStartLoopOverlay(e,n);if(y===t&&i)try{let n=await t.launchers.launch(i);if(y!==t)return;n&&e.ui.notify(`${n.label??n.launcherLabel} started.`,a.NOTIFY_INFO_LEVEL)}catch(n){if(y!==t)return;e.ui.notify(`Loop could not start: ${n instanceof Error?n.message:String(n)}`,`error`)}},async list(e){let t=S(e);t&&await n.openLoopListOverlay(e,t.launchers)}}),u.inject([l.DOOM_CORDIS_SESSION_SERVICE],async e=>{let n=e.get(l.DOOM_CORDIS_SESSION_SERVICE),r=t.createDoomLoopLaunchersService({generation:`${n.generation}:loop-launchers`,createInstanceId:()=>crypto.randomUUID(),timestamp:()=>new Date().toISOString()}),i={context:n.context,hostSession:n,launchers:r};y=i,e.provide(d.DOOM_LOOP_LAUNCHERS_SERVICE,r);let a,o;try{a=T.register(n.context,r),o=r.subscribe(()=>w(i)),w(i)}catch(e){throw y===i&&(y=void 0),await r.dispose(h),e}return async()=>{y===i&&(y=void 0),o(),C(i),b?.publish(g([],[]));let e=[];try{await a.dispose(h)}catch(t){e.push(t)}try{await r.dispose(`Pi session shut down.`)}catch(t){e.push(t)}if(e.length>0)throw AggregateError(e,`Loop session cleanup failed.`)}})}async function v(e){let t=await(0,l.connectDoomCordisHost)(e,a.PACKAGE_SOURCE),n=t.root.plugin(y,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function y(e,t){e.inject([u.DOOM_HELP_SERVICE],e=>{let t=(0,u.requireDoomHelpService)(e).register({source:a.PACKAGE_SOURCE,moduleUrl:require("url").pathToFileURL(__filename).href,skills:[{name:`doompi-use-loop`,description:`Use Doom Pi Loop to start, inspect, and stop session-scoped recurring prompts safely.`}]});return()=>t.dispose()}),_(e,t.pi)}exports.default=v,exports.loopExtension=v,exports.installLoopRuntime=_;
1
+ const e=require("../../commands/loopCommand.cjs"),t=require("../../services/loopLaunchers.cjs"),n=require("../../tui/loopListOverlay.cjs"),r=require("../../tui/startLoopOverlay.cjs"),i=require("../../types/loopView.cjs"),a=require("./loopConstants.cjs"),o=require("./defaultLoopLauncher.cjs"),s=require("./leader.cjs");let c=require("@agimon-ai/doompi-extension-contracts/mode"),l=require("@agimon-ai/doompi-extension-contracts/cordis-host"),u=require("@agimon-ai/doompi-extension-contracts/help"),d=require("@agimon-ai/doompi-extension-contracts/loop-launchers"),f=require("@agimon-ai/doompi-extension-contracts/ui-hub");const p=`start`,m=`stop`,h=`Pi session replaced.`;function g(e,t){let n=t.length>0;return{activation:n?`active`:`inactive`,condition:t.some(({state:e})=>e===`starting`||e===`stopping`)?`queued`:`ready`,...n?{detail:`${t.length} active`,color:a.ACTIVE_MODE_COLOR}:{},actions:[...e.length>0?[{id:p,enabled:!0}]:[{id:p,enabled:!1,disabledReason:`No loop launchers are registered.`}],...n?[{id:m,enabled:!0}]:[{id:m,enabled:!1,disabledReason:`No loops are active.`}]]}}function _(u,_){let v=!0,y,b,x,S=new WeakSet,C=e=>{let t=y;if(v&&t&&t.context.sessionManager===e.sessionManager&&t.hostSession.sessionId===e.sessionManager.getSessionId())return t},w=e=>{e.context.ui?.setStatus(a.STATUS_KEY,void 0),e.context.mode!==`tui`&&e.context.ui?.setStatus(i.LOOP_VIEW_STATUS_KEY,void 0)},T=e=>{if(!v||y!==e)return;let t=e.launchers.listLaunchers(),n=e.launchers.listInstances();e.context.ui?.setStatus(a.STATUS_KEY,n.length?`loops: ${n.length}`:void 0),e.context.mode!==`tui`&&e.context.ui?.setStatus(i.LOOP_VIEW_STATUS_KEY,i.formatLoopStatusView(n)),b?.publish(g(t,n))};u.effect(()=>()=>{v=!1;let e=y;y=void 0,e&&w(e),b?.publish(g([],[])),b?.dispose(),x?.(),x=void 0,b=void 0},`${a.PACKAGE_SOURCE}/runtime`);let E=o.createDefaultLoopLauncher(_);u.inject([c.DOOM_MINOR_MODE_CATALOG_SERVICE],e=>{let t=(0,c.registerMinorModeOwner)((0,c.requireMinorModeCatalog)(e),{descriptor:{source:a.PACKAGE_SOURCE,id:a.MODE_STATUS_ID,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:g([],[]),async handleAction(e,t,n){let r=C(n.context);if(!r)throw Error(`Loop launchers are unavailable for the active session.`);if(e===p){let e=await r.launchers.launch(t.launcherId,t.instanceId?{instanceId:t.instanceId}:{});if(y!==r)throw Error(`Loop action became stale.`);return{message:e?`Loop '${e.instanceId}' started.`:`Loop launch was cancelled.`}}if(e===m){let e=await r.launchers.stop(t.instanceId,t.reason??`Stopped through minor_mode.`);if(y!==r)throw Error(`Loop action became stale.`);return{message:e?`Loop stopped.`:`Loop instance was not active.`}}throw Error(`Unknown loop mode action: ${e}`)}});b=t;let n=y;return n&&T(n),()=>{t.dispose(),b===t&&(b=void 0)}}),u.inject([f.DOOM_UI_HUB_SERVICE],e=>{let t=s.registerLeaderContribution((0,f.requireDoomUiHub)(e));return x=t,()=>{t(),x===t&&(x=void 0)}}),e.registerCommands(_,{async start(e,t){let n=C(e);if(n){if(S.has(n)){e.ui.notify(`A loop setup is already in progress.`,a.NOTIFY_INFO_LEVEL);return}S.add(n);try{let i=n.launchers.listLaunchers();if(!i.length){e.ui.notify(`No loop launchers are registered for this session.`,a.NOTIFY_INFO_LEVEL);return}let o=t.trim(),s;if(o){if(o.split(/\s+/u).length!==1){e.ui.notify(`Usage: /loop [launcherId]`,`error`);return}if(!i.some(e=>e.id===o)){e.ui.notify(`Unknown loop launcher: ${o}`,`error`);return}s=o}else if(s=await r.openStartLoopOverlay(e,i),y!==n||!s)return;let c=await n.launchers.launch(s);if(y!==n)return;c&&e.ui.notify(`${c.label??c.launcherLabel} started.`,a.NOTIFY_INFO_LEVEL)}catch(t){if(y!==n)return;e.ui.notify(`Loop could not start: ${t instanceof Error?t.message:String(t)}`,`error`)}finally{S.delete(n)}}},async list(e){let t=C(e);t&&await n.openLoopListOverlay(e,t.launchers)}}),u.inject([l.DOOM_CORDIS_SESSION_SERVICE],async e=>{let n=e.get(l.DOOM_CORDIS_SESSION_SERVICE),r=t.createDoomLoopLaunchersService({generation:`${n.generation}:loop-launchers`,createInstanceId:()=>crypto.randomUUID(),timestamp:()=>new Date().toISOString()}),i={context:n.context,hostSession:n,launchers:r};y=i,e.provide(d.DOOM_LOOP_LAUNCHERS_SERVICE,r);let a,o;try{a=E.register(n.context,r),o=r.subscribe(()=>T(i)),T(i)}catch(e){throw y===i&&(y=void 0),await r.dispose(h),e}return async()=>{y===i&&(y=void 0),o(),w(i),b?.publish(g([],[]));let e=[];try{await a.dispose(h)}catch(t){e.push(t)}try{await r.dispose(`Pi session shut down.`)}catch(t){e.push(t)}if(e.length>0)throw AggregateError(e,`Loop session cleanup failed.`)}})}async function v(e){let t=await(0,l.connectDoomCordisHost)(e,a.PACKAGE_SOURCE),n=t.root.plugin(y,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function y(e,t){e.inject([u.DOOM_HELP_SERVICE],e=>{let t=(0,u.requireDoomHelpService)(e).register({source:a.PACKAGE_SOURCE,moduleUrl:require("url").pathToFileURL(__filename).href,skills:[{name:`doompi-use-loop`,description:`Use Doom Pi Loop to start, inspect, and stop session-scoped recurring prompts safely.`}]});return()=>t.dispose()}),_(e,t.pi)}exports.default=v,exports.loopExtension=v,exports.installLoopRuntime=_;
2
2
  //# sourceMappingURL=extension.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"extension.cjs","names":["ACTIVE_MODE_COLOR","STATUS_KEY","LOOP_VIEW_STATUS_KEY","formatLoopStatusView","PACKAGE_SOURCE","createDefaultLoopLauncher","DOOM_MINOR_MODE_CATALOG_SERVICE","registerMinorModeOwner","requireMinorModeCatalog","MODE_STATUS_ID","DOOM_UI_HUB_SERVICE","registerLeaderContribution","requireDoomUiHub","NOTIFY_INFO_LEVEL","openStartLoopOverlay","openLoopListOverlay","DOOM_CORDIS_SESSION_SERVICE","createDoomLoopLaunchersService","DOOM_LOOP_LAUNCHERS_SERVICE","connectDoomCordisHost","DOOM_HELP_SERVICE","requireDoomHelpService"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import {\n DOOM_MINOR_MODE_CATALOG_SERVICE,\n type MinorModeOwnerHandle,\n type MinorModeState,\n registerMinorModeOwner,\n requireMinorModeCatalog,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n DOOM_LOOP_LAUNCHERS_SERVICE,\n type DoomLoopLaunchersService,\n type LoopInstanceSnapshot,\n type LoopLauncherRegistration,\n type LoopLauncherSummary,\n} from '@agimon-ai/doompi-extension-contracts/loop-launchers';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommands } from '../../commands/loopCommand.ts';\nimport { createDoomLoopLaunchersService } from '../../services/loopLaunchers.ts';\nimport { openLoopListOverlay } from '../../tui/loopListOverlay.ts';\nimport { openStartLoopOverlay } from '../../tui/startLoopOverlay.ts';\nimport { formatLoopStatusView, LOOP_VIEW_STATUS_KEY } from '../../types/loopView.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\ninterface ActiveLoopSession {\n readonly context: ExtensionContext;\n readonly hostSession: DoomCordisSessionService;\n readonly launchers: DoomLoopLaunchersService;\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\n/** Install Loop state and external resources into its host-owned Cordis plugin fiber. */\nexport function installLoopRuntime(cordis: Context, pi: ExtensionAPI): void {\n let active = true;\n let activeSession: ActiveLoopSession | undefined;\n let mode: MinorModeOwnerHandle | undefined;\n let disposeLeader: (() => void) | undefined;\n\n const currentSession = (context: ExtensionContext): ActiveLoopSession | undefined => {\n const binding = activeSession;\n if (\n !active ||\n !binding ||\n binding.context.sessionManager !== context.sessionManager ||\n binding.hostSession.sessionId !== context.sessionManager.getSessionId()\n ) {\n return undefined;\n }\n return binding;\n };\n\n const clearStatuses = (binding: ActiveLoopSession): void => {\n binding.context.ui?.setStatus(STATUS_KEY, undefined);\n if (binding.context.mode !== 'tui') binding.context.ui?.setStatus(LOOP_VIEW_STATUS_KEY, undefined);\n };\n const publishStatus = (binding: ActiveLoopSession): void => {\n if (!active || activeSession !== binding) return;\n const launchers = binding.launchers.listLaunchers();\n const instances = binding.launchers.listInstances();\n binding.context.ui?.setStatus(STATUS_KEY, instances.length ? `loops: ${instances.length}` : undefined);\n if (binding.context.mode !== 'tui') {\n binding.context.ui?.setStatus(LOOP_VIEW_STATUS_KEY, formatLoopStatusView(instances));\n }\n mode?.publish(loopModeState(launchers, instances));\n };\n\n cordis.effect(\n () => () => {\n active = false;\n const binding = activeSession;\n activeSession = undefined;\n if (binding) clearStatuses(binding);\n mode?.publish(loopModeState([], []));\n mode?.dispose();\n disposeLeader?.();\n disposeLeader = undefined;\n mode = undefined;\n },\n `${PACKAGE_SOURCE}/runtime`,\n );\n\n const defaultLauncher = createDefaultLoopLauncher(pi);\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE], (modeContext) => {\n const owner = registerMinorModeOwner<ExtensionContext>(requireMinorModeCatalog(modeContext), {\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, execution) {\n const binding = currentSession(execution.context);\n if (!binding) throw new Error('Loop launchers are unavailable for the active session.');\n if (actionId === MODE_ACTION_START) {\n const instance = await binding.launchers.launch(\n argumentsValue.launcherId as string,\n argumentsValue.instanceId ? { instanceId: argumentsValue.instanceId as string } : {},\n );\n if (activeSession !== binding) 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 binding.launchers.stop(\n argumentsValue.instanceId as string,\n (argumentsValue.reason as string | undefined) ?? 'Stopped through minor_mode.',\n );\n if (activeSession !== binding) 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 mode = owner;\n const binding = activeSession;\n if (binding) publishStatus(binding);\n return () => {\n owner.dispose();\n if (mode === owner) mode = undefined;\n };\n });\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const dispose = registerLeaderContribution(requireDoomUiHub(uiContext));\n disposeLeader = dispose;\n return () => {\n dispose();\n if (disposeLeader === dispose) disposeLeader = undefined;\n };\n });\n\n registerCommands(pi, {\n async start(ctx) {\n const binding = currentSession(ctx);\n if (!binding) return;\n const launchers = binding.launchers.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 (activeSession !== binding || !launcherId) return;\n try {\n const instance = await binding.launchers.launch(launcherId);\n if (activeSession !== binding) return;\n if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, NOTIFY_INFO_LEVEL);\n } catch (error) {\n if (activeSession !== binding) return;\n ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, 'error');\n }\n },\n async list(ctx) {\n const binding = currentSession(ctx);\n if (!binding) return;\n await openLoopListOverlay(ctx, binding.launchers);\n },\n });\n\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE], async (sessionContext) => {\n const hostSession = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const launchers = createDoomLoopLaunchersService({\n generation: `${hostSession.generation}:loop-launchers`,\n createInstanceId: () => crypto.randomUUID(),\n timestamp: () => new Date().toISOString(),\n });\n const binding: ActiveLoopSession = { context: hostSession.context, hostSession, launchers };\n activeSession = binding;\n sessionContext.provide(DOOM_LOOP_LAUNCHERS_SERVICE, launchers);\n\n let defaultRegistration: LoopLauncherRegistration;\n let unsubscribe: () => void;\n try {\n defaultRegistration = defaultLauncher.register(hostSession.context, launchers);\n unsubscribe = launchers.subscribe(() => publishStatus(binding));\n publishStatus(binding);\n } catch (error) {\n if (activeSession === binding) activeSession = undefined;\n await launchers.dispose(SESSION_REPLACED_REASON);\n throw error;\n }\n\n return async () => {\n if (activeSession === binding) activeSession = undefined;\n unsubscribe();\n clearStatuses(binding);\n mode?.publish(loopModeState([], []));\n const cleanupErrors: unknown[] = [];\n try {\n await defaultRegistration.dispose(SESSION_REPLACED_REASON);\n } catch (error) {\n cleanupErrors.push(error);\n }\n try {\n await launchers.dispose(SESSION_SHUTDOWN_REASON);\n } catch (error) {\n cleanupErrors.push(error);\n }\n if (cleanupErrors.length > 0) throw new AggregateError(cleanupErrors, 'Loop session cleanup failed.');\n };\n });\n}\n\n/** The package's single standard Pi factory. */\nexport async function loopExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(loopPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface LoopPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction loopPlugin(cordis: Context, config: LoopPluginConfig): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: PACKAGE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-use-loop',\n description: 'Use Doom Pi Loop to start, inspect, and stop session-scoped recurring prompts safely.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n installLoopRuntime(cordis, config.pi);\n}\n\nexport default loopExtension;\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.cjs","names":["ACTIVE_MODE_COLOR","STATUS_KEY","LOOP_VIEW_STATUS_KEY","formatLoopStatusView","PACKAGE_SOURCE","createDefaultLoopLauncher","DOOM_MINOR_MODE_CATALOG_SERVICE","registerMinorModeOwner","requireMinorModeCatalog","MODE_STATUS_ID","DOOM_UI_HUB_SERVICE","registerLeaderContribution","requireDoomUiHub","NOTIFY_INFO_LEVEL","openStartLoopOverlay","openLoopListOverlay","DOOM_CORDIS_SESSION_SERVICE","createDoomLoopLaunchersService","DOOM_LOOP_LAUNCHERS_SERVICE","connectDoomCordisHost","DOOM_HELP_SERVICE","requireDoomHelpService"],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import {\n DOOM_MINOR_MODE_CATALOG_SERVICE,\n type MinorModeOwnerHandle,\n type MinorModeState,\n registerMinorModeOwner,\n requireMinorModeCatalog,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n DOOM_LOOP_LAUNCHERS_SERVICE,\n type DoomLoopLaunchersService,\n type LoopInstanceSnapshot,\n type LoopLauncherRegistration,\n type LoopLauncherSummary,\n} from '@agimon-ai/doompi-extension-contracts/loop-launchers';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommands } from '../../commands/loopCommand.ts';\nimport { createDoomLoopLaunchersService } from '../../services/loopLaunchers.ts';\nimport { openLoopListOverlay } from '../../tui/loopListOverlay.ts';\nimport { openStartLoopOverlay } from '../../tui/startLoopOverlay.ts';\nimport { formatLoopStatusView, LOOP_VIEW_STATUS_KEY } from '../../types/loopView.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\ninterface ActiveLoopSession {\n readonly context: ExtensionContext;\n readonly hostSession: DoomCordisSessionService;\n readonly launchers: DoomLoopLaunchersService;\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\n/** Install Loop state and external resources into its host-owned Cordis plugin fiber. */\nexport function installLoopRuntime(cordis: Context, pi: ExtensionAPI): void {\n let active = true;\n let activeSession: ActiveLoopSession | undefined;\n let mode: MinorModeOwnerHandle | undefined;\n let disposeLeader: (() => void) | undefined;\n const launchesInProgress = new WeakSet<ActiveLoopSession>();\n\n const currentSession = (context: ExtensionContext): ActiveLoopSession | undefined => {\n const binding = activeSession;\n if (\n !active ||\n !binding ||\n binding.context.sessionManager !== context.sessionManager ||\n binding.hostSession.sessionId !== context.sessionManager.getSessionId()\n ) {\n return undefined;\n }\n return binding;\n };\n\n const clearStatuses = (binding: ActiveLoopSession): void => {\n binding.context.ui?.setStatus(STATUS_KEY, undefined);\n if (binding.context.mode !== 'tui') binding.context.ui?.setStatus(LOOP_VIEW_STATUS_KEY, undefined);\n };\n const publishStatus = (binding: ActiveLoopSession): void => {\n if (!active || activeSession !== binding) return;\n const launchers = binding.launchers.listLaunchers();\n const instances = binding.launchers.listInstances();\n binding.context.ui?.setStatus(STATUS_KEY, instances.length ? `loops: ${instances.length}` : undefined);\n if (binding.context.mode !== 'tui') {\n binding.context.ui?.setStatus(LOOP_VIEW_STATUS_KEY, formatLoopStatusView(instances));\n }\n mode?.publish(loopModeState(launchers, instances));\n };\n\n cordis.effect(\n () => () => {\n active = false;\n const binding = activeSession;\n activeSession = undefined;\n if (binding) clearStatuses(binding);\n mode?.publish(loopModeState([], []));\n mode?.dispose();\n disposeLeader?.();\n disposeLeader = undefined;\n mode = undefined;\n },\n `${PACKAGE_SOURCE}/runtime`,\n );\n\n const defaultLauncher = createDefaultLoopLauncher(pi);\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE], (modeContext) => {\n const owner = registerMinorModeOwner<ExtensionContext>(requireMinorModeCatalog(modeContext), {\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, execution) {\n const binding = currentSession(execution.context);\n if (!binding) throw new Error('Loop launchers are unavailable for the active session.');\n if (actionId === MODE_ACTION_START) {\n const instance = await binding.launchers.launch(\n argumentsValue.launcherId as string,\n argumentsValue.instanceId ? { instanceId: argumentsValue.instanceId as string } : {},\n );\n if (activeSession !== binding) 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 binding.launchers.stop(\n argumentsValue.instanceId as string,\n (argumentsValue.reason as string | undefined) ?? 'Stopped through minor_mode.',\n );\n if (activeSession !== binding) 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 mode = owner;\n const binding = activeSession;\n if (binding) publishStatus(binding);\n return () => {\n owner.dispose();\n if (mode === owner) mode = undefined;\n };\n });\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const dispose = registerLeaderContribution(requireDoomUiHub(uiContext));\n disposeLeader = dispose;\n return () => {\n dispose();\n if (disposeLeader === dispose) disposeLeader = undefined;\n };\n });\n\n registerCommands(pi, {\n async start(ctx, args) {\n const binding = currentSession(ctx);\n if (!binding) return;\n if (launchesInProgress.has(binding)) {\n ctx.ui.notify('A loop setup is already in progress.', NOTIFY_INFO_LEVEL);\n return;\n }\n launchesInProgress.add(binding);\n try {\n const launchers = binding.launchers.listLaunchers();\n if (!launchers.length) {\n ctx.ui.notify('No loop launchers are registered for this session.', NOTIFY_INFO_LEVEL);\n return;\n }\n\n const argument = args.trim();\n let launcherId: string | undefined;\n if (argument) {\n if (argument.split(/\\s+/u).length !== 1) {\n ctx.ui.notify('Usage: /loop [launcherId]', 'error');\n return;\n }\n if (!launchers.some((launcher) => launcher.id === argument)) {\n ctx.ui.notify(`Unknown loop launcher: ${argument}`, 'error');\n return;\n }\n launcherId = argument;\n } else {\n launcherId = await openStartLoopOverlay(ctx, launchers);\n if (activeSession !== binding || !launcherId) return;\n }\n\n const instance = await binding.launchers.launch(launcherId);\n if (activeSession !== binding) return;\n if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, NOTIFY_INFO_LEVEL);\n } catch (error) {\n if (activeSession !== binding) return;\n ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, 'error');\n } finally {\n launchesInProgress.delete(binding);\n }\n },\n async list(ctx) {\n const binding = currentSession(ctx);\n if (!binding) return;\n await openLoopListOverlay(ctx, binding.launchers);\n },\n });\n\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE], async (sessionContext) => {\n const hostSession = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const launchers = createDoomLoopLaunchersService({\n generation: `${hostSession.generation}:loop-launchers`,\n createInstanceId: () => crypto.randomUUID(),\n timestamp: () => new Date().toISOString(),\n });\n const binding: ActiveLoopSession = { context: hostSession.context, hostSession, launchers };\n activeSession = binding;\n sessionContext.provide(DOOM_LOOP_LAUNCHERS_SERVICE, launchers);\n\n let defaultRegistration: LoopLauncherRegistration;\n let unsubscribe: () => void;\n try {\n defaultRegistration = defaultLauncher.register(hostSession.context, launchers);\n unsubscribe = launchers.subscribe(() => publishStatus(binding));\n publishStatus(binding);\n } catch (error) {\n if (activeSession === binding) activeSession = undefined;\n await launchers.dispose(SESSION_REPLACED_REASON);\n throw error;\n }\n\n return async () => {\n if (activeSession === binding) activeSession = undefined;\n unsubscribe();\n clearStatuses(binding);\n mode?.publish(loopModeState([], []));\n const cleanupErrors: unknown[] = [];\n try {\n await defaultRegistration.dispose(SESSION_REPLACED_REASON);\n } catch (error) {\n cleanupErrors.push(error);\n }\n try {\n await launchers.dispose(SESSION_SHUTDOWN_REASON);\n } catch (error) {\n cleanupErrors.push(error);\n }\n if (cleanupErrors.length > 0) throw new AggregateError(cleanupErrors, 'Loop session cleanup failed.');\n };\n });\n}\n\n/** The package's single standard Pi factory. */\nexport async function loopExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(loopPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface LoopPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction loopPlugin(cordis: Context, config: LoopPluginConfig): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: PACKAGE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-use-loop',\n description: 'Use Doom Pi Loop to start, inspect, and stop session-scoped recurring prompts safely.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n installLoopRuntime(cordis, config.pi);\n}\n\nexport default loopExtension;\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;wBAuEgB,mBAAmB,QAAQ,SAAS,IAAI;;wBAiMlC,cAAc,IAAI,eAAe"}
1
+ {"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;wBAuEgB,mBAAmB,QAAQ,SAAS,IAAI;;wBAyNlC,cAAc,IAAI,eAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;wBAuEgB,mBAAmB,QAAQ,SAAS,IAAI;;wBAiMlC,cAAc,IAAI,eAAe"}
1
+ {"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;wBAuEgB,mBAAmB,QAAQ,SAAS,IAAI;;wBAyNlC,cAAc,IAAI,eAAe"}
@@ -1,2 +1,2 @@
1
- import{registerCommands as e}from"../../commands/loopCommand.mjs";import{createDoomLoopLaunchersService as t}from"../../services/loopLaunchers.mjs";import{openLoopListOverlay as n}from"../../tui/loopListOverlay.mjs";import{openStartLoopOverlay as r}from"../../tui/startLoopOverlay.mjs";import{LOOP_VIEW_STATUS_KEY as i,formatLoopStatusView as a}from"../../types/loopView.mjs";import{ACTIVE_MODE_COLOR as o,MODE_STATUS_ID as s,NOTIFY_INFO_LEVEL as c,PACKAGE_SOURCE as l,STATUS_KEY as u}from"./loopConstants.mjs";import{createDefaultLoopLauncher as d}from"./defaultLoopLauncher.mjs";import{registerLeaderContribution as f}from"./leader.mjs";import{DOOM_MINOR_MODE_CATALOG_SERVICE as p,registerMinorModeOwner as m,requireMinorModeCatalog as h}from"@agimon-ai/doompi-extension-contracts/mode";import{DOOM_CORDIS_SESSION_SERVICE as g,connectDoomCordisHost as _}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_HELP_SERVICE as v,requireDoomHelpService as y}from"@agimon-ai/doompi-extension-contracts/help";import{DOOM_LOOP_LAUNCHERS_SERVICE as b}from"@agimon-ai/doompi-extension-contracts/loop-launchers";import{DOOM_UI_HUB_SERVICE as x,requireDoomUiHub as S}from"@agimon-ai/doompi-extension-contracts/ui-hub";const C=`start`,w=`stop`,T=`Pi session replaced.`;function E(e,t){let n=t.length>0;return{activation:n?`active`:`inactive`,condition:t.some(({state:e})=>e===`starting`||e===`stopping`)?`queued`:`ready`,...n?{detail:`${t.length} active`,color:o}:{},actions:[...e.length>0?[{id:C,enabled:!0}]:[{id:C,enabled:!1,disabledReason:`No loop launchers are registered.`}],...n?[{id:w,enabled:!0}]:[{id:w,enabled:!1,disabledReason:`No loops are active.`}]]}}function D(o,_){let v=!0,y,D,O,k=e=>{let t=y;if(v&&t&&t.context.sessionManager===e.sessionManager&&t.hostSession.sessionId===e.sessionManager.getSessionId())return t},A=e=>{e.context.ui?.setStatus(u,void 0),e.context.mode!==`tui`&&e.context.ui?.setStatus(i,void 0)},j=e=>{if(!v||y!==e)return;let t=e.launchers.listLaunchers(),n=e.launchers.listInstances();e.context.ui?.setStatus(u,n.length?`loops: ${n.length}`:void 0),e.context.mode!==`tui`&&e.context.ui?.setStatus(i,a(n)),D?.publish(E(t,n))};o.effect(()=>()=>{v=!1;let e=y;y=void 0,e&&A(e),D?.publish(E([],[])),D?.dispose(),O?.(),O=void 0,D=void 0},`${l}/runtime`);let M=d(_);o.inject([p],e=>{let t=m(h(e),{descriptor:{source:l,id:s,label:`Loop`,description:`Session-scoped recurring prompt loops.`,order:60,actions:[{id:C,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:w,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:E([],[]),async handleAction(e,t,n){let r=k(n.context);if(!r)throw Error(`Loop launchers are unavailable for the active session.`);if(e===C){let e=await r.launchers.launch(t.launcherId,t.instanceId?{instanceId:t.instanceId}:{});if(y!==r)throw Error(`Loop action became stale.`);return{message:e?`Loop '${e.instanceId}' started.`:`Loop launch was cancelled.`}}if(e===w){let e=await r.launchers.stop(t.instanceId,t.reason??`Stopped through minor_mode.`);if(y!==r)throw Error(`Loop action became stale.`);return{message:e?`Loop stopped.`:`Loop instance was not active.`}}throw Error(`Unknown loop mode action: ${e}`)}});D=t;let n=y;return n&&j(n),()=>{t.dispose(),D===t&&(D=void 0)}}),o.inject([x],e=>{let t=f(S(e));return O=t,()=>{t(),O===t&&(O=void 0)}}),e(_,{async start(e){let t=k(e);if(!t)return;let n=t.launchers.listLaunchers();if(!n.length){e.ui.notify(`No loop launchers are registered for this session.`,c);return}let i=await r(e,n);if(y===t&&i)try{let n=await t.launchers.launch(i);if(y!==t)return;n&&e.ui.notify(`${n.label??n.launcherLabel} started.`,c)}catch(n){if(y!==t)return;e.ui.notify(`Loop could not start: ${n instanceof Error?n.message:String(n)}`,`error`)}},async list(e){let t=k(e);t&&await n(e,t.launchers)}}),o.inject([g],async e=>{let n=e.get(g),r=t({generation:`${n.generation}:loop-launchers`,createInstanceId:()=>crypto.randomUUID(),timestamp:()=>new Date().toISOString()}),i={context:n.context,hostSession:n,launchers:r};y=i,e.provide(b,r);let a,o;try{a=M.register(n.context,r),o=r.subscribe(()=>j(i)),j(i)}catch(e){throw y===i&&(y=void 0),await r.dispose(T),e}return async()=>{y===i&&(y=void 0),o(),A(i),D?.publish(E([],[]));let e=[];try{await a.dispose(T)}catch(t){e.push(t)}try{await r.dispose(`Pi session shut down.`)}catch(t){e.push(t)}if(e.length>0)throw AggregateError(e,`Loop session cleanup failed.`)}})}async function O(e){let t=await _(e,l),n=t.root.plugin(k,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function k(e,t){e.inject([v],e=>{let t=y(e).register({source:l,moduleUrl:import.meta.url,skills:[{name:`doompi-use-loop`,description:`Use Doom Pi Loop to start, inspect, and stop session-scoped recurring prompts safely.`}]});return()=>t.dispose()}),D(e,t.pi)}export{O as default,O as loopExtension,D as installLoopRuntime};
1
+ import{registerCommands as e}from"../../commands/loopCommand.mjs";import{createDoomLoopLaunchersService as t}from"../../services/loopLaunchers.mjs";import{openLoopListOverlay as n}from"../../tui/loopListOverlay.mjs";import{openStartLoopOverlay as r}from"../../tui/startLoopOverlay.mjs";import{LOOP_VIEW_STATUS_KEY as i,formatLoopStatusView as a}from"../../types/loopView.mjs";import{ACTIVE_MODE_COLOR as o,MODE_STATUS_ID as s,NOTIFY_INFO_LEVEL as c,PACKAGE_SOURCE as l,STATUS_KEY as u}from"./loopConstants.mjs";import{createDefaultLoopLauncher as d}from"./defaultLoopLauncher.mjs";import{registerLeaderContribution as f}from"./leader.mjs";import{DOOM_MINOR_MODE_CATALOG_SERVICE as p,registerMinorModeOwner as m,requireMinorModeCatalog as h}from"@agimon-ai/doompi-extension-contracts/mode";import{DOOM_CORDIS_SESSION_SERVICE as g,connectDoomCordisHost as _}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_HELP_SERVICE as v,requireDoomHelpService as y}from"@agimon-ai/doompi-extension-contracts/help";import{DOOM_LOOP_LAUNCHERS_SERVICE as b}from"@agimon-ai/doompi-extension-contracts/loop-launchers";import{DOOM_UI_HUB_SERVICE as x,requireDoomUiHub as S}from"@agimon-ai/doompi-extension-contracts/ui-hub";const C=`start`,w=`stop`,T=`Pi session replaced.`;function E(e,t){let n=t.length>0;return{activation:n?`active`:`inactive`,condition:t.some(({state:e})=>e===`starting`||e===`stopping`)?`queued`:`ready`,...n?{detail:`${t.length} active`,color:o}:{},actions:[...e.length>0?[{id:C,enabled:!0}]:[{id:C,enabled:!1,disabledReason:`No loop launchers are registered.`}],...n?[{id:w,enabled:!0}]:[{id:w,enabled:!1,disabledReason:`No loops are active.`}]]}}function D(o,_){let v=!0,y,D,O,k=new WeakSet,A=e=>{let t=y;if(v&&t&&t.context.sessionManager===e.sessionManager&&t.hostSession.sessionId===e.sessionManager.getSessionId())return t},j=e=>{e.context.ui?.setStatus(u,void 0),e.context.mode!==`tui`&&e.context.ui?.setStatus(i,void 0)},M=e=>{if(!v||y!==e)return;let t=e.launchers.listLaunchers(),n=e.launchers.listInstances();e.context.ui?.setStatus(u,n.length?`loops: ${n.length}`:void 0),e.context.mode!==`tui`&&e.context.ui?.setStatus(i,a(n)),D?.publish(E(t,n))};o.effect(()=>()=>{v=!1;let e=y;y=void 0,e&&j(e),D?.publish(E([],[])),D?.dispose(),O?.(),O=void 0,D=void 0},`${l}/runtime`);let N=d(_);o.inject([p],e=>{let t=m(h(e),{descriptor:{source:l,id:s,label:`Loop`,description:`Session-scoped recurring prompt loops.`,order:60,actions:[{id:C,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:w,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:E([],[]),async handleAction(e,t,n){let r=A(n.context);if(!r)throw Error(`Loop launchers are unavailable for the active session.`);if(e===C){let e=await r.launchers.launch(t.launcherId,t.instanceId?{instanceId:t.instanceId}:{});if(y!==r)throw Error(`Loop action became stale.`);return{message:e?`Loop '${e.instanceId}' started.`:`Loop launch was cancelled.`}}if(e===w){let e=await r.launchers.stop(t.instanceId,t.reason??`Stopped through minor_mode.`);if(y!==r)throw Error(`Loop action became stale.`);return{message:e?`Loop stopped.`:`Loop instance was not active.`}}throw Error(`Unknown loop mode action: ${e}`)}});D=t;let n=y;return n&&M(n),()=>{t.dispose(),D===t&&(D=void 0)}}),o.inject([x],e=>{let t=f(S(e));return O=t,()=>{t(),O===t&&(O=void 0)}}),e(_,{async start(e,t){let n=A(e);if(n){if(k.has(n)){e.ui.notify(`A loop setup is already in progress.`,c);return}k.add(n);try{let i=n.launchers.listLaunchers();if(!i.length){e.ui.notify(`No loop launchers are registered for this session.`,c);return}let a=t.trim(),o;if(a){if(a.split(/\s+/u).length!==1){e.ui.notify(`Usage: /loop [launcherId]`,`error`);return}if(!i.some(e=>e.id===a)){e.ui.notify(`Unknown loop launcher: ${a}`,`error`);return}o=a}else if(o=await r(e,i),y!==n||!o)return;let s=await n.launchers.launch(o);if(y!==n)return;s&&e.ui.notify(`${s.label??s.launcherLabel} started.`,c)}catch(t){if(y!==n)return;e.ui.notify(`Loop could not start: ${t instanceof Error?t.message:String(t)}`,`error`)}finally{k.delete(n)}}},async list(e){let t=A(e);t&&await n(e,t.launchers)}}),o.inject([g],async e=>{let n=e.get(g),r=t({generation:`${n.generation}:loop-launchers`,createInstanceId:()=>crypto.randomUUID(),timestamp:()=>new Date().toISOString()}),i={context:n.context,hostSession:n,launchers:r};y=i,e.provide(b,r);let a,o;try{a=N.register(n.context,r),o=r.subscribe(()=>M(i)),M(i)}catch(e){throw y===i&&(y=void 0),await r.dispose(T),e}return async()=>{y===i&&(y=void 0),o(),j(i),D?.publish(E([],[]));let e=[];try{await a.dispose(T)}catch(t){e.push(t)}try{await r.dispose(`Pi session shut down.`)}catch(t){e.push(t)}if(e.length>0)throw AggregateError(e,`Loop session cleanup failed.`)}})}async function O(e){let t=await _(e,l),n=t.root.plugin(k,{pi:e});try{await n}catch(e){try{await n.dispose()}finally{await t.dispose()}throw e}let r;e.on(`session_shutdown`,()=>r??=(async()=>{try{await n.dispose()}finally{await t.dispose()}})())}function k(e,t){e.inject([v],e=>{let t=y(e).register({source:l,moduleUrl:import.meta.url,skills:[{name:`doompi-use-loop`,description:`Use Doom Pi Loop to start, inspect, and stop session-scoped recurring prompts safely.`}]});return()=>t.dispose()}),D(e,t.pi)}export{O as default,O as loopExtension,D 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 {\n DOOM_MINOR_MODE_CATALOG_SERVICE,\n type MinorModeOwnerHandle,\n type MinorModeState,\n registerMinorModeOwner,\n requireMinorModeCatalog,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n DOOM_LOOP_LAUNCHERS_SERVICE,\n type DoomLoopLaunchersService,\n type LoopInstanceSnapshot,\n type LoopLauncherRegistration,\n type LoopLauncherSummary,\n} from '@agimon-ai/doompi-extension-contracts/loop-launchers';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommands } from '../../commands/loopCommand.ts';\nimport { createDoomLoopLaunchersService } from '../../services/loopLaunchers.ts';\nimport { openLoopListOverlay } from '../../tui/loopListOverlay.ts';\nimport { openStartLoopOverlay } from '../../tui/startLoopOverlay.ts';\nimport { formatLoopStatusView, LOOP_VIEW_STATUS_KEY } from '../../types/loopView.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\ninterface ActiveLoopSession {\n readonly context: ExtensionContext;\n readonly hostSession: DoomCordisSessionService;\n readonly launchers: DoomLoopLaunchersService;\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\n/** Install Loop state and external resources into its host-owned Cordis plugin fiber. */\nexport function installLoopRuntime(cordis: Context, pi: ExtensionAPI): void {\n let active = true;\n let activeSession: ActiveLoopSession | undefined;\n let mode: MinorModeOwnerHandle | undefined;\n let disposeLeader: (() => void) | undefined;\n\n const currentSession = (context: ExtensionContext): ActiveLoopSession | undefined => {\n const binding = activeSession;\n if (\n !active ||\n !binding ||\n binding.context.sessionManager !== context.sessionManager ||\n binding.hostSession.sessionId !== context.sessionManager.getSessionId()\n ) {\n return undefined;\n }\n return binding;\n };\n\n const clearStatuses = (binding: ActiveLoopSession): void => {\n binding.context.ui?.setStatus(STATUS_KEY, undefined);\n if (binding.context.mode !== 'tui') binding.context.ui?.setStatus(LOOP_VIEW_STATUS_KEY, undefined);\n };\n const publishStatus = (binding: ActiveLoopSession): void => {\n if (!active || activeSession !== binding) return;\n const launchers = binding.launchers.listLaunchers();\n const instances = binding.launchers.listInstances();\n binding.context.ui?.setStatus(STATUS_KEY, instances.length ? `loops: ${instances.length}` : undefined);\n if (binding.context.mode !== 'tui') {\n binding.context.ui?.setStatus(LOOP_VIEW_STATUS_KEY, formatLoopStatusView(instances));\n }\n mode?.publish(loopModeState(launchers, instances));\n };\n\n cordis.effect(\n () => () => {\n active = false;\n const binding = activeSession;\n activeSession = undefined;\n if (binding) clearStatuses(binding);\n mode?.publish(loopModeState([], []));\n mode?.dispose();\n disposeLeader?.();\n disposeLeader = undefined;\n mode = undefined;\n },\n `${PACKAGE_SOURCE}/runtime`,\n );\n\n const defaultLauncher = createDefaultLoopLauncher(pi);\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE], (modeContext) => {\n const owner = registerMinorModeOwner<ExtensionContext>(requireMinorModeCatalog(modeContext), {\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, execution) {\n const binding = currentSession(execution.context);\n if (!binding) throw new Error('Loop launchers are unavailable for the active session.');\n if (actionId === MODE_ACTION_START) {\n const instance = await binding.launchers.launch(\n argumentsValue.launcherId as string,\n argumentsValue.instanceId ? { instanceId: argumentsValue.instanceId as string } : {},\n );\n if (activeSession !== binding) 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 binding.launchers.stop(\n argumentsValue.instanceId as string,\n (argumentsValue.reason as string | undefined) ?? 'Stopped through minor_mode.',\n );\n if (activeSession !== binding) 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 mode = owner;\n const binding = activeSession;\n if (binding) publishStatus(binding);\n return () => {\n owner.dispose();\n if (mode === owner) mode = undefined;\n };\n });\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const dispose = registerLeaderContribution(requireDoomUiHub(uiContext));\n disposeLeader = dispose;\n return () => {\n dispose();\n if (disposeLeader === dispose) disposeLeader = undefined;\n };\n });\n\n registerCommands(pi, {\n async start(ctx) {\n const binding = currentSession(ctx);\n if (!binding) return;\n const launchers = binding.launchers.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 (activeSession !== binding || !launcherId) return;\n try {\n const instance = await binding.launchers.launch(launcherId);\n if (activeSession !== binding) return;\n if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, NOTIFY_INFO_LEVEL);\n } catch (error) {\n if (activeSession !== binding) return;\n ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, 'error');\n }\n },\n async list(ctx) {\n const binding = currentSession(ctx);\n if (!binding) return;\n await openLoopListOverlay(ctx, binding.launchers);\n },\n });\n\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE], async (sessionContext) => {\n const hostSession = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const launchers = createDoomLoopLaunchersService({\n generation: `${hostSession.generation}:loop-launchers`,\n createInstanceId: () => crypto.randomUUID(),\n timestamp: () => new Date().toISOString(),\n });\n const binding: ActiveLoopSession = { context: hostSession.context, hostSession, launchers };\n activeSession = binding;\n sessionContext.provide(DOOM_LOOP_LAUNCHERS_SERVICE, launchers);\n\n let defaultRegistration: LoopLauncherRegistration;\n let unsubscribe: () => void;\n try {\n defaultRegistration = defaultLauncher.register(hostSession.context, launchers);\n unsubscribe = launchers.subscribe(() => publishStatus(binding));\n publishStatus(binding);\n } catch (error) {\n if (activeSession === binding) activeSession = undefined;\n await launchers.dispose(SESSION_REPLACED_REASON);\n throw error;\n }\n\n return async () => {\n if (activeSession === binding) activeSession = undefined;\n unsubscribe();\n clearStatuses(binding);\n mode?.publish(loopModeState([], []));\n const cleanupErrors: unknown[] = [];\n try {\n await defaultRegistration.dispose(SESSION_REPLACED_REASON);\n } catch (error) {\n cleanupErrors.push(error);\n }\n try {\n await launchers.dispose(SESSION_SHUTDOWN_REASON);\n } catch (error) {\n cleanupErrors.push(error);\n }\n if (cleanupErrors.length > 0) throw new AggregateError(cleanupErrors, 'Loop session cleanup failed.');\n };\n });\n}\n\n/** The package's single standard Pi factory. */\nexport async function loopExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(loopPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface LoopPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction loopPlugin(cordis: Context, config: LoopPluginConfig): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: PACKAGE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-use-loop',\n description: 'Use Doom Pi Loop to start, inspect, and stop session-scoped recurring prompts safely.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n installLoopRuntime(cordis, config.pi);\n}\n\nexport default loopExtension;\n"],"mappings":"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"}
1
+ {"version":3,"file":"extension.mjs","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"sourcesContent":["import {\n DOOM_MINOR_MODE_CATALOG_SERVICE,\n type MinorModeOwnerHandle,\n type MinorModeState,\n registerMinorModeOwner,\n requireMinorModeCatalog,\n} from '@agimon-ai/doompi-extension-contracts/mode';\nimport {\n connectDoomCordisHost,\n DOOM_CORDIS_SESSION_SERVICE,\n type DoomCordisSessionService,\n} from '@agimon-ai/doompi-extension-contracts/cordis-host';\nimport { DOOM_HELP_SERVICE, requireDoomHelpService } from '@agimon-ai/doompi-extension-contracts/help';\nimport {\n DOOM_LOOP_LAUNCHERS_SERVICE,\n type DoomLoopLaunchersService,\n type LoopInstanceSnapshot,\n type LoopLauncherRegistration,\n type LoopLauncherSummary,\n} from '@agimon-ai/doompi-extension-contracts/loop-launchers';\nimport { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from '@agimon-ai/doompi-extension-contracts/ui-hub';\nimport type { Context } from '@deepseek-ai/cordis';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommands } from '../../commands/loopCommand.ts';\nimport { createDoomLoopLaunchersService } from '../../services/loopLaunchers.ts';\nimport { openLoopListOverlay } from '../../tui/loopListOverlay.ts';\nimport { openStartLoopOverlay } from '../../tui/startLoopOverlay.ts';\nimport { formatLoopStatusView, LOOP_VIEW_STATUS_KEY } from '../../types/loopView.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\ninterface ActiveLoopSession {\n readonly context: ExtensionContext;\n readonly hostSession: DoomCordisSessionService;\n readonly launchers: DoomLoopLaunchersService;\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\n/** Install Loop state and external resources into its host-owned Cordis plugin fiber. */\nexport function installLoopRuntime(cordis: Context, pi: ExtensionAPI): void {\n let active = true;\n let activeSession: ActiveLoopSession | undefined;\n let mode: MinorModeOwnerHandle | undefined;\n let disposeLeader: (() => void) | undefined;\n const launchesInProgress = new WeakSet<ActiveLoopSession>();\n\n const currentSession = (context: ExtensionContext): ActiveLoopSession | undefined => {\n const binding = activeSession;\n if (\n !active ||\n !binding ||\n binding.context.sessionManager !== context.sessionManager ||\n binding.hostSession.sessionId !== context.sessionManager.getSessionId()\n ) {\n return undefined;\n }\n return binding;\n };\n\n const clearStatuses = (binding: ActiveLoopSession): void => {\n binding.context.ui?.setStatus(STATUS_KEY, undefined);\n if (binding.context.mode !== 'tui') binding.context.ui?.setStatus(LOOP_VIEW_STATUS_KEY, undefined);\n };\n const publishStatus = (binding: ActiveLoopSession): void => {\n if (!active || activeSession !== binding) return;\n const launchers = binding.launchers.listLaunchers();\n const instances = binding.launchers.listInstances();\n binding.context.ui?.setStatus(STATUS_KEY, instances.length ? `loops: ${instances.length}` : undefined);\n if (binding.context.mode !== 'tui') {\n binding.context.ui?.setStatus(LOOP_VIEW_STATUS_KEY, formatLoopStatusView(instances));\n }\n mode?.publish(loopModeState(launchers, instances));\n };\n\n cordis.effect(\n () => () => {\n active = false;\n const binding = activeSession;\n activeSession = undefined;\n if (binding) clearStatuses(binding);\n mode?.publish(loopModeState([], []));\n mode?.dispose();\n disposeLeader?.();\n disposeLeader = undefined;\n mode = undefined;\n },\n `${PACKAGE_SOURCE}/runtime`,\n );\n\n const defaultLauncher = createDefaultLoopLauncher(pi);\n cordis.inject([DOOM_MINOR_MODE_CATALOG_SERVICE], (modeContext) => {\n const owner = registerMinorModeOwner<ExtensionContext>(requireMinorModeCatalog(modeContext), {\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, execution) {\n const binding = currentSession(execution.context);\n if (!binding) throw new Error('Loop launchers are unavailable for the active session.');\n if (actionId === MODE_ACTION_START) {\n const instance = await binding.launchers.launch(\n argumentsValue.launcherId as string,\n argumentsValue.instanceId ? { instanceId: argumentsValue.instanceId as string } : {},\n );\n if (activeSession !== binding) 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 binding.launchers.stop(\n argumentsValue.instanceId as string,\n (argumentsValue.reason as string | undefined) ?? 'Stopped through minor_mode.',\n );\n if (activeSession !== binding) 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 mode = owner;\n const binding = activeSession;\n if (binding) publishStatus(binding);\n return () => {\n owner.dispose();\n if (mode === owner) mode = undefined;\n };\n });\n cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {\n const dispose = registerLeaderContribution(requireDoomUiHub(uiContext));\n disposeLeader = dispose;\n return () => {\n dispose();\n if (disposeLeader === dispose) disposeLeader = undefined;\n };\n });\n\n registerCommands(pi, {\n async start(ctx, args) {\n const binding = currentSession(ctx);\n if (!binding) return;\n if (launchesInProgress.has(binding)) {\n ctx.ui.notify('A loop setup is already in progress.', NOTIFY_INFO_LEVEL);\n return;\n }\n launchesInProgress.add(binding);\n try {\n const launchers = binding.launchers.listLaunchers();\n if (!launchers.length) {\n ctx.ui.notify('No loop launchers are registered for this session.', NOTIFY_INFO_LEVEL);\n return;\n }\n\n const argument = args.trim();\n let launcherId: string | undefined;\n if (argument) {\n if (argument.split(/\\s+/u).length !== 1) {\n ctx.ui.notify('Usage: /loop [launcherId]', 'error');\n return;\n }\n if (!launchers.some((launcher) => launcher.id === argument)) {\n ctx.ui.notify(`Unknown loop launcher: ${argument}`, 'error');\n return;\n }\n launcherId = argument;\n } else {\n launcherId = await openStartLoopOverlay(ctx, launchers);\n if (activeSession !== binding || !launcherId) return;\n }\n\n const instance = await binding.launchers.launch(launcherId);\n if (activeSession !== binding) return;\n if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, NOTIFY_INFO_LEVEL);\n } catch (error) {\n if (activeSession !== binding) return;\n ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, 'error');\n } finally {\n launchesInProgress.delete(binding);\n }\n },\n async list(ctx) {\n const binding = currentSession(ctx);\n if (!binding) return;\n await openLoopListOverlay(ctx, binding.launchers);\n },\n });\n\n cordis.inject([DOOM_CORDIS_SESSION_SERVICE], async (sessionContext) => {\n const hostSession = sessionContext.get(DOOM_CORDIS_SESSION_SERVICE) as DoomCordisSessionService;\n const launchers = createDoomLoopLaunchersService({\n generation: `${hostSession.generation}:loop-launchers`,\n createInstanceId: () => crypto.randomUUID(),\n timestamp: () => new Date().toISOString(),\n });\n const binding: ActiveLoopSession = { context: hostSession.context, hostSession, launchers };\n activeSession = binding;\n sessionContext.provide(DOOM_LOOP_LAUNCHERS_SERVICE, launchers);\n\n let defaultRegistration: LoopLauncherRegistration;\n let unsubscribe: () => void;\n try {\n defaultRegistration = defaultLauncher.register(hostSession.context, launchers);\n unsubscribe = launchers.subscribe(() => publishStatus(binding));\n publishStatus(binding);\n } catch (error) {\n if (activeSession === binding) activeSession = undefined;\n await launchers.dispose(SESSION_REPLACED_REASON);\n throw error;\n }\n\n return async () => {\n if (activeSession === binding) activeSession = undefined;\n unsubscribe();\n clearStatuses(binding);\n mode?.publish(loopModeState([], []));\n const cleanupErrors: unknown[] = [];\n try {\n await defaultRegistration.dispose(SESSION_REPLACED_REASON);\n } catch (error) {\n cleanupErrors.push(error);\n }\n try {\n await launchers.dispose(SESSION_SHUTDOWN_REASON);\n } catch (error) {\n cleanupErrors.push(error);\n }\n if (cleanupErrors.length > 0) throw new AggregateError(cleanupErrors, 'Loop session cleanup failed.');\n };\n });\n}\n\n/** The package's single standard Pi factory. */\nexport async function loopExtension(pi: ExtensionAPI): Promise<void> {\n const connection = await connectDoomCordisHost(pi, PACKAGE_SOURCE);\n const fiber = connection.root.plugin(loopPlugin, { pi });\n try {\n await fiber;\n } catch (error) {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n throw error;\n }\n let disposal: Promise<void> | undefined;\n pi.on(\n 'session_shutdown',\n () =>\n (disposal ??= (async () => {\n try {\n await fiber.dispose();\n } finally {\n await connection.dispose();\n }\n })()),\n );\n}\n\ninterface LoopPluginConfig {\n readonly pi: ExtensionAPI;\n}\n\nfunction loopPlugin(cordis: Context, config: LoopPluginConfig): void {\n cordis.inject([DOOM_HELP_SERVICE], (helpContext) => {\n const contribution = requireDoomHelpService(helpContext).register({\n source: PACKAGE_SOURCE,\n moduleUrl: import.meta.url,\n skills: [\n {\n name: 'doompi-use-loop',\n description: 'Use Doom Pi Loop to start, inspect, and stop session-scoped recurring prompts safely.',\n },\n ],\n });\n return () => contribution.dispose();\n });\n installLoopRuntime(cordis, config.pi);\n}\n\nexport default loopExtension;\n"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- const e=require("../schemas/loopCommands.cjs");function t(t,n){t.registerCommand(e.START_COMMAND_NAME,{description:`Start a registered loop`,handler:async(e,t)=>n.start(t)}),t.registerCommand(e.LIST_COMMAND_NAME,{description:`List and stop active loops`,handler:async(e,t)=>n.list(t)})}exports.registerCommands=t;
1
+ const e=require("../schemas/loopCommands.cjs");function t(t,n){t.registerCommand(e.START_COMMAND_NAME,{description:`Start a registered loop`,handler:async(e,t)=>n.start(t,e)}),t.registerCommand(e.LIST_COMMAND_NAME,{description:`List and stop active loops`,handler:async(e,t)=>n.list(t)})}exports.registerCommands=t;
2
2
  //# sourceMappingURL=loopCommand.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"loopCommand.cjs","names":["START_COMMAND_NAME","LIST_COMMAND_NAME"],"sources":["../../src/commands/loopCommand.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { LIST_COMMAND_NAME, START_COMMAND_NAME } from '../schemas/loopCommands.ts';\n\nconst START_COMMAND_DESCRIPTION = 'Start a registered loop';\nconst LIST_COMMAND_DESCRIPTION = 'List and stop active loops';\n\nexport interface LoopCommandHandlers {\n start(ctx: ExtensionContext): Promise<void>;\n list(ctx: ExtensionContext): Promise<void>;\n}\n\nexport function registerCommands(pi: ExtensionAPI, handlers: LoopCommandHandlers): void {\n pi.registerCommand(START_COMMAND_NAME, {\n description: START_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.start(ctx),\n });\n pi.registerCommand(LIST_COMMAND_NAME, {\n description: LIST_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.list(ctx),\n });\n}\n"],"mappings":"+CAWA,SAAgB,EAAiB,EAAkB,EAAqC,CACtF,EAAG,gBAAgBA,EAAAA,mBAAoB,CACrC,YAAa,0BACb,QAAS,MAAO,EAAO,IAAQ,EAAS,MAAM,CAAG,CACnD,CAAC,EACD,EAAG,gBAAgBC,EAAAA,kBAAmB,CACpC,YAAa,6BACb,QAAS,MAAO,EAAO,IAAQ,EAAS,KAAK,CAAG,CAClD,CAAC,CACH"}
1
+ {"version":3,"file":"loopCommand.cjs","names":["START_COMMAND_NAME","LIST_COMMAND_NAME"],"sources":["../../src/commands/loopCommand.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { LIST_COMMAND_NAME, START_COMMAND_NAME } from '../schemas/loopCommands.ts';\n\nconst START_COMMAND_DESCRIPTION = 'Start a registered loop';\nconst LIST_COMMAND_DESCRIPTION = 'List and stop active loops';\n\nexport interface LoopCommandHandlers {\n start(ctx: ExtensionContext, args: string): Promise<void>;\n list(ctx: ExtensionContext): Promise<void>;\n}\n\nexport function registerCommands(pi: ExtensionAPI, handlers: LoopCommandHandlers): void {\n pi.registerCommand(START_COMMAND_NAME, {\n description: START_COMMAND_DESCRIPTION,\n handler: async (args, ctx) => handlers.start(ctx, args),\n });\n pi.registerCommand(LIST_COMMAND_NAME, {\n description: LIST_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.list(ctx),\n });\n}\n"],"mappings":"+CAWA,SAAgB,EAAiB,EAAkB,EAAqC,CACtF,EAAG,gBAAgBA,EAAAA,mBAAoB,CACrC,YAAa,0BACb,QAAS,MAAO,EAAM,IAAQ,EAAS,MAAM,EAAK,CAAI,CACxD,CAAC,EACD,EAAG,gBAAgBC,EAAAA,kBAAmB,CACpC,YAAa,6BACb,QAAS,MAAO,EAAO,IAAQ,EAAS,KAAK,CAAG,CAClD,CAAC,CACH"}
@@ -1,2 +1,2 @@
1
- import{LIST_COMMAND_NAME as e,START_COMMAND_NAME as t}from"../schemas/loopCommands.mjs";function n(n,r){n.registerCommand(t,{description:`Start a registered loop`,handler:async(e,t)=>r.start(t)}),n.registerCommand(e,{description:`List and stop active loops`,handler:async(e,t)=>r.list(t)})}export{n as registerCommands};
1
+ import{LIST_COMMAND_NAME as e,START_COMMAND_NAME as t}from"../schemas/loopCommands.mjs";function n(n,r){n.registerCommand(t,{description:`Start a registered loop`,handler:async(e,t)=>r.start(t,e)}),n.registerCommand(e,{description:`List and stop active loops`,handler:async(e,t)=>r.list(t)})}export{n as registerCommands};
2
2
  //# sourceMappingURL=loopCommand.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"loopCommand.mjs","names":[],"sources":["../../src/commands/loopCommand.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { LIST_COMMAND_NAME, START_COMMAND_NAME } from '../schemas/loopCommands.ts';\n\nconst START_COMMAND_DESCRIPTION = 'Start a registered loop';\nconst LIST_COMMAND_DESCRIPTION = 'List and stop active loops';\n\nexport interface LoopCommandHandlers {\n start(ctx: ExtensionContext): Promise<void>;\n list(ctx: ExtensionContext): Promise<void>;\n}\n\nexport function registerCommands(pi: ExtensionAPI, handlers: LoopCommandHandlers): void {\n pi.registerCommand(START_COMMAND_NAME, {\n description: START_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.start(ctx),\n });\n pi.registerCommand(LIST_COMMAND_NAME, {\n description: LIST_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.list(ctx),\n });\n}\n"],"mappings":"wFAWA,SAAgB,EAAiB,EAAkB,EAAqC,CACtF,EAAG,gBAAgB,EAAoB,CACrC,YAAa,0BACb,QAAS,MAAO,EAAO,IAAQ,EAAS,MAAM,CAAG,CACnD,CAAC,EACD,EAAG,gBAAgB,EAAmB,CACpC,YAAa,6BACb,QAAS,MAAO,EAAO,IAAQ,EAAS,KAAK,CAAG,CAClD,CAAC,CACH"}
1
+ {"version":3,"file":"loopCommand.mjs","names":[],"sources":["../../src/commands/loopCommand.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { LIST_COMMAND_NAME, START_COMMAND_NAME } from '../schemas/loopCommands.ts';\n\nconst START_COMMAND_DESCRIPTION = 'Start a registered loop';\nconst LIST_COMMAND_DESCRIPTION = 'List and stop active loops';\n\nexport interface LoopCommandHandlers {\n start(ctx: ExtensionContext, args: string): Promise<void>;\n list(ctx: ExtensionContext): Promise<void>;\n}\n\nexport function registerCommands(pi: ExtensionAPI, handlers: LoopCommandHandlers): void {\n pi.registerCommand(START_COMMAND_NAME, {\n description: START_COMMAND_DESCRIPTION,\n handler: async (args, ctx) => handlers.start(ctx, args),\n });\n pi.registerCommand(LIST_COMMAND_NAME, {\n description: LIST_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.list(ctx),\n });\n}\n"],"mappings":"wFAWA,SAAgB,EAAiB,EAAkB,EAAqC,CACtF,EAAG,gBAAgB,EAAoB,CACrC,YAAa,0BACb,QAAS,MAAO,EAAM,IAAQ,EAAS,MAAM,EAAK,CAAI,CACxD,CAAC,EACD,EAAG,gBAAgB,EAAmB,CACpC,YAAa,6BACb,QAAS,MAAO,EAAO,IAAQ,EAAS,KAAK,CAAG,CAClD,CAAC,CACH"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agimon-ai/doompi-loop",
3
- "version": "0.0.1-alpha.68",
3
+ "version": "0.0.1-alpha.69",
4
4
  "description": "Session-scoped recurring prompt scheduler and loop controls for Pi coding agents.",
5
5
  "keywords": [
6
6
  "ai",
@@ -61,8 +61,8 @@
61
61
  "@deepseek-ai/cordis": "4.0.2",
62
62
  "@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.67",
63
63
  "@agimon-ai/doompi-ui": "0.0.1-alpha.68",
64
- "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.30",
65
- "@agimon-ai/doompi-web-components": "0.0.1-alpha.27"
64
+ "@agimon-ai/doompi-web-components": "0.0.1-alpha.27",
65
+ "@agimon-ai/doompi-web-contracts": "0.0.1-alpha.30"
66
66
  },
67
67
  "devDependencies": {
68
68
  "@earendil-works/pi-coding-agent": "0.85.1",
@@ -10,37 +10,43 @@ function toneOf(state: LoopStatusState): DotTone {
10
10
  return 'yellow';
11
11
  }
12
12
 
13
- /** Active recurring prompts owned by the focused session, plus the existing management flow. */
14
- export function LoopsActivitySection({ sessionId, statuses, sendSessionFrame }: WebPluginSlotProps) {
13
+ /** Renders active recurring prompts contributed to the Loop activity section. */
14
+ export function LoopActivityItems({ statuses }: WebPluginSlotProps) {
15
15
  const raw = statuses[LOOP_VIEW_STATUS_KEY];
16
16
  const loops = parseLoopStatusView(raw);
17
17
  const unavailable = raw !== undefined && raw.trim() !== '' && loops === undefined;
18
18
  if (!loops && !unavailable) return null;
19
19
 
20
+ return loops ? (
21
+ <ul aria-label="active loops" className="flex flex-col gap-1">
22
+ {loops.map((loop) => (
23
+ <li
24
+ key={loop.instanceId}
25
+ data-testid={`activity-loop-${loop.instanceId}`}
26
+ data-loop-state={loop.state}
27
+ title={`${loop.label}: ${loop.detail}`}
28
+ className="flex min-w-0 flex-col gap-0.5 px-1 py-0.5"
29
+ >
30
+ <span className="flex min-w-0 items-center gap-1.5">
31
+ <Dot tone={toneOf(loop.state)} pulse={loop.state !== 'running'} />
32
+ <span className="min-w-0 flex-1 truncate text-xs font-bold text-doom-hi">{loop.label}</span>
33
+ <span className="shrink-0 text-2xs text-doom-faint">{loop.state}</span>
34
+ </span>
35
+ <span className="truncate pl-3 text-2xs text-doom-faint">{loop.detail}</span>
36
+ </li>
37
+ ))}
38
+ </ul>
39
+ ) : (
40
+ <p className="px-1 text-xs text-doom-faint">loop status unavailable</p>
41
+ );
42
+ }
43
+
44
+ /** Idle-safe Loop activity shell with extension registration and instance slots. */
45
+ export function LoopsActivitySection({ sessionId, renderSlot, sendSessionFrame }: WebPluginSlotProps) {
20
46
  return (
21
47
  <div data-testid="activity-loop-instances" className="flex flex-col gap-2">
22
- {loops ? (
23
- <ul aria-label="active loops" className="flex flex-col gap-1">
24
- {loops.map((loop) => (
25
- <li
26
- key={loop.instanceId}
27
- data-testid={`activity-loop-${loop.instanceId}`}
28
- data-loop-state={loop.state}
29
- title={`${loop.label}: ${loop.detail}`}
30
- className="flex min-w-0 flex-col gap-0.5 px-1 py-0.5"
31
- >
32
- <span className="flex min-w-0 items-center gap-1.5">
33
- <Dot tone={toneOf(loop.state)} pulse={loop.state !== 'running'} />
34
- <span className="min-w-0 flex-1 truncate text-xs font-bold text-doom-hi">{loop.label}</span>
35
- <span className="shrink-0 text-2xs text-doom-faint">{loop.state}</span>
36
- </span>
37
- <span className="truncate pl-3 text-2xs text-doom-faint">{loop.detail}</span>
38
- </li>
39
- ))}
40
- </ul>
41
- ) : (
42
- <p className="px-1 text-xs text-doom-faint">loop status unavailable</p>
43
- )}
48
+ {renderSlot('loop.registration')}
49
+ {renderSlot('loop.items')}
44
50
  <Button
45
51
  variant="subtle"
46
52
  size="xs"
package/src/web/index.ts CHANGED
@@ -1,6 +1,6 @@
1
- import { defineWebPlugin } from '@agimon-ai/doompi-web-contracts';
1
+ import { defineSlot, defineWebPlugin } from '@agimon-ai/doompi-web-contracts';
2
2
  import { LOOP_VIEW_STATUS_KEY } from '../types/loopView.ts';
3
- import { LoopsActivitySection } from './components/LoopsActivitySection.tsx';
3
+ import { LoopActivityItems, LoopsActivitySection } from './components/LoopsActivitySection.tsx';
4
4
 
5
5
  /**
6
6
  * This package's cockpit presence. The selection bar carries the minor mode,
@@ -11,8 +11,10 @@ const LOOPS_GROUP = { key: 'l', label: 'loops', detail: 'recurring prompt loops'
11
11
  export const webPlugin = defineWebPlugin({
12
12
  id: 'loop',
13
13
  minorModes: [{ name: 'loop', keys: 'l l', statusKey: 'doom-loop', order: 30 }],
14
- activityGroups: [{ name: 'loops', keys: 'l l', statusKey: LOOP_VIEW_STATUS_KEY, hideWhenEmpty: true, order: 40 }],
14
+ activityGroups: [{ name: 'loops', keys: 'l l', statusKey: LOOP_VIEW_STATUS_KEY, order: 40 }],
15
15
  activitySections: [{ id: 'loops', component: LoopsActivitySection }],
16
+ slots: [defineSlot({ slot: 'loop.registration' }), defineSlot({ slot: 'loop.items' })],
17
+ fills: [{ slot: 'loop.items', id: 'instances', component: LoopActivityItems }],
16
18
  // The TUI's SPC l s and SPC l l: both are slash commands, so both carry over.
17
19
  leaderBindings: [
18
20
  {