@agimon-ai/doompi-loop 0.0.1-alpha.28 → 0.0.1-alpha.29
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 +33 -17
- package/dist/adapters/pi/extension.cjs +1 -1
- package/dist/adapters/pi/extension.cjs.map +1 -1
- package/dist/adapters/pi/extension.d.cts.map +1 -1
- package/dist/adapters/pi/extension.d.mts.map +1 -1
- package/dist/adapters/pi/extension.mjs +1 -1
- package/dist/adapters/pi/extension.mjs.map +1 -1
- package/llms.txt +9 -0
- package/package.json +7 -3
- package/src/prompts/doompi-use-loop/SKILL.md +24 -0
package/README.md
CHANGED
|
@@ -4,7 +4,8 @@ A session-scoped scheduler that sends a prompt immediately and repeats it at a b
|
|
|
4
4
|
|
|
5
5
|
Part of the [DoomPi distribution](https://www.npmjs.com/package/@agimon-ai/doompi).
|
|
6
6
|
|
|
7
|
-
Several loops can coexist. Loop is a minor mode and remains active while at least one scheduled
|
|
7
|
+
Several loops can coexist. Loop is a minor mode and remains active while at least one scheduled
|
|
8
|
+
prompt belongs to the current session.
|
|
8
9
|
|
|
9
10
|
> **Alpha:** launcher and scheduling contracts may change between releases.
|
|
10
11
|
|
|
@@ -15,8 +16,8 @@ Several loops can coexist. Loop is a minor mode and remains active while at leas
|
|
|
15
16
|
|
|
16
17
|
## Install
|
|
17
18
|
|
|
18
|
-
DoomPi includes Loop in every composition. Pi
|
|
19
|
-
|
|
19
|
+
DoomPi includes Loop in every composition. Pi loads it through package extension metadata. For
|
|
20
|
+
standalone Pi:
|
|
20
21
|
|
|
21
22
|
```bash
|
|
22
23
|
pi install npm:@agimon-ai/doompi-loop
|
|
@@ -24,21 +25,31 @@ pi install npm:@agimon-ai/doompi-loop
|
|
|
24
25
|
|
|
25
26
|
## Start and manage loops
|
|
26
27
|
|
|
27
|
-
Use `/loop` to start a loop and `/loops` to inspect or stop active loops. In DoomPi, `SPC l s` opens
|
|
28
|
+
Use `/loop` to start a loop and `/loops` to inspect or stop active loops. In DoomPi, `SPC l s` opens
|
|
29
|
+
the start flow and `SPC l l` opens the list.
|
|
28
30
|
|
|
29
|
-
The default interval is 300 seconds. Accepted intervals range from 30 to 3600 seconds. A loop runs
|
|
31
|
+
The default interval is 300 seconds. Accepted intervals range from 30 to 3600 seconds. A loop runs
|
|
32
|
+
its prompt once immediately, then schedules later passes.
|
|
30
33
|
|
|
31
|
-
If Pi is busy, a due pass waits for the shared agent to become idle. Multiple due signals can
|
|
34
|
+
If Pi is busy, a due pass waits for the shared agent to become idle. Multiple due signals can
|
|
35
|
+
coalesce rather than creating overlapping model turns. Stopping a loop prevents future passes but
|
|
36
|
+
does not rewind side effects from a pass already sent.
|
|
32
37
|
|
|
33
38
|
## Lifecycle and cost
|
|
34
39
|
|
|
35
|
-
Loop state
|
|
40
|
+
Loop state belongs to the session's `doom/loop-launchers` service; it is not a durable queue.
|
|
41
|
+
Replacing or shutting down the session aborts pending launches and stops active timers. Resuming a
|
|
42
|
+
transcript does not restart old loops.
|
|
36
43
|
|
|
37
|
-
Every pass can start another model turn and repeat tool or external side effects. Choose
|
|
44
|
+
Every pass can start another model turn and repeat tool or external side effects. Choose
|
|
45
|
+
conservative intervals, make prompts idempotent where possible, and stop loops when the recurring
|
|
46
|
+
work is complete.
|
|
38
47
|
|
|
39
|
-
##
|
|
48
|
+
## Extension authors: cross-extension launchers
|
|
40
49
|
|
|
41
|
-
Launcher definitions
|
|
50
|
+
Launcher definitions use the shared Cordis service. A consumer registers a launcher and returns its
|
|
51
|
+
cleanup callback. Cordis removes that launcher when either package or the session unloads, then
|
|
52
|
+
registers it again when the provider returns.
|
|
42
53
|
|
|
43
54
|
```ts
|
|
44
55
|
import {
|
|
@@ -54,7 +65,8 @@ export function myLoopPlugin(ctx: Context): void {
|
|
|
54
65
|
source: '@example/doompi-recurring-check',
|
|
55
66
|
label: 'Recurring check',
|
|
56
67
|
async launch({ instanceId, signal }) {
|
|
57
|
-
|
|
68
|
+
signal.throwIfAborted();
|
|
69
|
+
// Start session work. The returned stop callback owns cleanup.
|
|
58
70
|
return { instanceId, stop: () => undefined };
|
|
59
71
|
},
|
|
60
72
|
});
|
|
@@ -63,17 +75,21 @@ export function myLoopPlugin(ctx: Context): void {
|
|
|
63
75
|
}
|
|
64
76
|
```
|
|
65
77
|
|
|
66
|
-
Mount that plugin on the runner's shared Doom Cordis root.
|
|
78
|
+
Mount that plugin on the runner's shared Doom Cordis root. Loop owns the registry and default
|
|
79
|
+
launcher. Consumers depend only on
|
|
80
|
+
`@agimon-ai/doompi-extension-contracts/loop-launchers`; there is no process-global registry or
|
|
81
|
+
session-ID lookup API.
|
|
67
82
|
|
|
68
|
-
The
|
|
83
|
+
The package root exports `installLoopRuntime` for composition tests, plus the service contract
|
|
84
|
+
types. Normal Pi activation uses the discovered `extensions/pi` entry.
|
|
69
85
|
|
|
70
86
|
## Development
|
|
71
87
|
|
|
72
88
|
```bash
|
|
73
|
-
pnpm
|
|
74
|
-
pnpm
|
|
75
|
-
pnpm
|
|
76
|
-
pnpm
|
|
89
|
+
pnpm build
|
|
90
|
+
pnpm typecheck
|
|
91
|
+
pnpm test
|
|
92
|
+
pnpm lint
|
|
77
93
|
```
|
|
78
94
|
|
|
79
95
|
Maintained by [Agimon](https://agimon.ai/about).
|
|
@@ -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("./loopConstants.cjs"),a=require("./defaultLoopLauncher.cjs"),o=require("./leader.cjs");let s=require("@agimon-ai/doompi-extension-contracts/mode"),c=require("@agimon-ai/doompi-extension-contracts/cordis-host"),l=require("@agimon-ai/doompi-extension-contracts/loop-launchers"),
|
|
1
|
+
const e=require("../../commands/loopCommand.cjs"),t=require("../../services/loopLaunchers.cjs"),n=require("../../tui/loopListOverlay.cjs"),r=require("../../tui/startLoopOverlay.cjs"),i=require("./loopConstants.cjs"),a=require("./defaultLoopLauncher.cjs"),o=require("./leader.cjs");let s=require("@agimon-ai/doompi-extension-contracts/mode"),c=require("@agimon-ai/doompi-extension-contracts/cordis-host"),l=require("@agimon-ai/doompi-extension-contracts/help"),u=require("@agimon-ai/doompi-extension-contracts/loop-launchers"),d=require("@agimon-ai/doompi-extension-contracts/ui-hub");const f=`start`,p=`stop`,m=`Pi session replaced.`;function h(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:i.ACTIVE_MODE_COLOR}:{},actions:[...e.length>0?[{id:f,enabled:!0}]:[{id:f,enabled:!1,disabledReason:`No loop launchers are registered.`}],...n?[{id:p,enabled:!0}]:[{id:p,enabled:!1,disabledReason:`No loops are active.`}]]}}function g(l,g){let _=!0,v,y,b,x=e=>{let t=v;if(!(!_||!t||t.context.sessionManager!==e.sessionManager||t.hostSession.sessionId!==e.sessionManager.getSessionId()))return t},S=e=>{if(!_||v!==e)return;let t=e.launchers.listLaunchers(),n=e.launchers.listInstances();e.context.ui?.setStatus(i.STATUS_KEY,n.length?`loops: ${n.length}`:void 0),y?.publish(h(t,n))};l.effect(()=>()=>{_=!1;let e=v;v=void 0,e?.context.ui?.setStatus(i.STATUS_KEY,void 0),y?.publish(h([],[])),y?.dispose(),b?.(),b=void 0,y=void 0},`${i.PACKAGE_SOURCE}/runtime`);let C=a.createDefaultLoopLauncher(g);l.inject([s.DOOM_MINOR_MODE_CATALOG_SERVICE],e=>{let t=(0,s.registerMinorModeOwner)((0,s.requireMinorModeCatalog)(e),{descriptor:{source:i.PACKAGE_SOURCE,id:i.MODE_STATUS_ID,label:`Loop`,description:`Session-scoped recurring prompt loops.`,order:60,actions:[{id:f,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:p,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:h([],[]),async handleAction(e,t,n){let r=x(n.context);if(!r)throw Error(`Loop launchers are unavailable for the active session.`);if(e===f){let e=await r.launchers.launch(t.launcherId,t.instanceId?{instanceId:t.instanceId}:{});if(v!==r)throw Error(`Loop action became stale.`);return{message:e?`Loop '${e.instanceId}' started.`:`Loop launch was cancelled.`}}if(e===p){let e=await r.launchers.stop(t.instanceId,t.reason??`Stopped through minor_mode.`);if(v!==r)throw Error(`Loop action became stale.`);return{message:e?`Loop stopped.`:`Loop instance was not active.`}}throw Error(`Unknown loop mode action: ${e}`)}});y=t;let n=v;return n&&S(n),()=>{t.dispose(),y===t&&(y=void 0)}}),l.inject([d.DOOM_UI_HUB_SERVICE],e=>{let t=o.registerLeaderContribution((0,d.requireDoomUiHub)(e));return b=t,()=>{t(),b===t&&(b=void 0)}}),e.registerCommands(g,{async start(e){let t=x(e);if(!t)return;let n=t.launchers.listLaunchers();if(!n.length){e.ui.notify(`No loop launchers are registered for this session.`,i.NOTIFY_INFO_LEVEL);return}let a=await r.openStartLoopOverlay(e,n);if(!(v!==t||!a))try{let n=await t.launchers.launch(a);if(v!==t)return;n&&e.ui.notify(`${n.label??n.launcherLabel} started.`,i.NOTIFY_INFO_LEVEL)}catch(n){if(v!==t)return;e.ui.notify(`Loop could not start: ${n instanceof Error?n.message:String(n)}`,`error`)}},async list(e){let t=x(e);t&&await n.openLoopListOverlay(e,t.launchers)}}),l.inject([c.DOOM_CORDIS_SESSION_SERVICE],async e=>{let n=e.get(c.DOOM_CORDIS_SESSION_SERVICE),r=t.createDoomLoopLaunchersService({generation:`${n.generation}:loop-launchers`,createInstanceId:()=>crypto.randomUUID(),timestamp:()=>new Date().toISOString()}),a={context:n.context,hostSession:n,launchers:r};v=a,e.provide(u.DOOM_LOOP_LAUNCHERS_SERVICE,r);let o,s;try{o=C.register(n.context,r),s=r.subscribe(()=>S(a)),S(a)}catch(e){throw v===a&&(v=void 0),await r.dispose(m),e}return async()=>{v===a&&(v=void 0),s(),a.context.ui?.setStatus(i.STATUS_KEY,void 0),y?.publish(h([],[]));let e=[];try{await o.dispose(m)}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 _(e){let t=await(0,c.connectDoomCordisHost)(e,i.PACKAGE_SOURCE),n=t.root.plugin(v,{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 v(e,t){e.inject([l.DOOM_HELP_SERVICE],e=>{let t=(0,l.requireDoomHelpService)(e).register({source:i.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()}),g(e,t.pi)}exports.default=_,exports.loopExtension=_,exports.installLoopRuntime=g;
|
|
2
2
|
//# sourceMappingURL=extension.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.cjs","names":["ACTIVE_MODE_COLOR","STATUS_KEY","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"],"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 {\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 { 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 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 mode?.publish(loopModeState(launchers, instances));\n };\n\n cordis.effect(\n () => () => {\n active = false;\n const binding = activeSession;\n activeSession = undefined;\n binding?.context.ui?.setStatus(STATUS_KEY, undefined);\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 binding.context.ui?.setStatus(STATUS_KEY, undefined);\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 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","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 { 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 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 mode?.publish(loopModeState(launchers, instances));\n };\n\n cordis.effect(\n () => () => {\n active = false;\n const binding = activeSession;\n activeSession = undefined;\n binding?.context.ui?.setStatus(STATUS_KEY, undefined);\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 binding.context.ui?.setStatus(STATUS_KEY, undefined);\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":";;;;
|
|
1
|
+
{"version":3,"file":"extension.d.cts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBAsEgB,mBAAmB,QAAQ,SAAS,IAAI;;iBA0LlC,cAAc,IAAI,eAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;
|
|
1
|
+
{"version":3,"file":"extension.d.mts","names":[],"sources":["../../../src/adapters/pi/extension.ts"],"mappings":";;;;iBAsEgB,mBAAmB,QAAQ,SAAS,IAAI;;iBA0LlC,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{ACTIVE_MODE_COLOR as i,MODE_STATUS_ID as a,NOTIFY_INFO_LEVEL as o,PACKAGE_SOURCE as s,STATUS_KEY as c}from"./loopConstants.mjs";import{createDefaultLoopLauncher as l}from"./defaultLoopLauncher.mjs";import{registerLeaderContribution as u}from"./leader.mjs";import{DOOM_MINOR_MODE_CATALOG_SERVICE as d,registerMinorModeOwner as f,requireMinorModeCatalog as p}from"@agimon-ai/doompi-extension-contracts/mode";import{DOOM_CORDIS_SESSION_SERVICE as m,connectDoomCordisHost as h}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{
|
|
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{ACTIVE_MODE_COLOR as i,MODE_STATUS_ID as a,NOTIFY_INFO_LEVEL as o,PACKAGE_SOURCE as s,STATUS_KEY as c}from"./loopConstants.mjs";import{createDefaultLoopLauncher as l}from"./defaultLoopLauncher.mjs";import{registerLeaderContribution as u}from"./leader.mjs";import{DOOM_MINOR_MODE_CATALOG_SERVICE as d,registerMinorModeOwner as f,requireMinorModeCatalog as p}from"@agimon-ai/doompi-extension-contracts/mode";import{DOOM_CORDIS_SESSION_SERVICE as m,connectDoomCordisHost as h}from"@agimon-ai/doompi-extension-contracts/cordis-host";import{DOOM_HELP_SERVICE as g,requireDoomHelpService as _}from"@agimon-ai/doompi-extension-contracts/help";import{DOOM_LOOP_LAUNCHERS_SERVICE as v}from"@agimon-ai/doompi-extension-contracts/loop-launchers";import{DOOM_UI_HUB_SERVICE as y,requireDoomUiHub as b}from"@agimon-ai/doompi-extension-contracts/ui-hub";const x=`start`,S=`stop`,C=`Pi session replaced.`;function w(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:i}:{},actions:[...e.length>0?[{id:x,enabled:!0}]:[{id:x,enabled:!1,disabledReason:`No loop launchers are registered.`}],...n?[{id:S,enabled:!0}]:[{id:S,enabled:!1,disabledReason:`No loops are active.`}]]}}function T(i,h){let g=!0,_,T,E,D=e=>{let t=_;if(!(!g||!t||t.context.sessionManager!==e.sessionManager||t.hostSession.sessionId!==e.sessionManager.getSessionId()))return t},O=e=>{if(!g||_!==e)return;let t=e.launchers.listLaunchers(),n=e.launchers.listInstances();e.context.ui?.setStatus(c,n.length?`loops: ${n.length}`:void 0),T?.publish(w(t,n))};i.effect(()=>()=>{g=!1;let e=_;_=void 0,e?.context.ui?.setStatus(c,void 0),T?.publish(w([],[])),T?.dispose(),E?.(),E=void 0,T=void 0},`${s}/runtime`);let k=l(h);i.inject([d],e=>{let t=f(p(e),{descriptor:{source:s,id:a,label:`Loop`,description:`Session-scoped recurring prompt loops.`,order:60,actions:[{id:x,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:S,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:w([],[]),async handleAction(e,t,n){let r=D(n.context);if(!r)throw Error(`Loop launchers are unavailable for the active session.`);if(e===x){let e=await r.launchers.launch(t.launcherId,t.instanceId?{instanceId:t.instanceId}:{});if(_!==r)throw Error(`Loop action became stale.`);return{message:e?`Loop '${e.instanceId}' started.`:`Loop launch was cancelled.`}}if(e===S){let e=await r.launchers.stop(t.instanceId,t.reason??`Stopped through minor_mode.`);if(_!==r)throw Error(`Loop action became stale.`);return{message:e?`Loop stopped.`:`Loop instance was not active.`}}throw Error(`Unknown loop mode action: ${e}`)}});T=t;let n=_;return n&&O(n),()=>{t.dispose(),T===t&&(T=void 0)}}),i.inject([y],e=>{let t=u(b(e));return E=t,()=>{t(),E===t&&(E=void 0)}}),e(h,{async start(e){let t=D(e);if(!t)return;let n=t.launchers.listLaunchers();if(!n.length){e.ui.notify(`No loop launchers are registered for this session.`,o);return}let i=await r(e,n);if(!(_!==t||!i))try{let n=await t.launchers.launch(i);if(_!==t)return;n&&e.ui.notify(`${n.label??n.launcherLabel} started.`,o)}catch(n){if(_!==t)return;e.ui.notify(`Loop could not start: ${n instanceof Error?n.message:String(n)}`,`error`)}},async list(e){let t=D(e);t&&await n(e,t.launchers)}}),i.inject([m],async e=>{let n=e.get(m),r=t({generation:`${n.generation}:loop-launchers`,createInstanceId:()=>crypto.randomUUID(),timestamp:()=>new Date().toISOString()}),i={context:n.context,hostSession:n,launchers:r};_=i,e.provide(v,r);let a,o;try{a=k.register(n.context,r),o=r.subscribe(()=>O(i)),O(i)}catch(e){throw _===i&&(_=void 0),await r.dispose(C),e}return async()=>{_===i&&(_=void 0),o(),i.context.ui?.setStatus(c,void 0),T?.publish(w([],[]));let e=[];try{await a.dispose(C)}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 E(e){let t=await h(e,s),n=t.root.plugin(D,{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 D(e,t){e.inject([g],e=>{let t=_(e).register({source:s,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()}),T(e,t.pi)}export{E as default,E as loopExtension,T 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 {\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 { 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 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 mode?.publish(loopModeState(launchers, instances));\n };\n\n cordis.effect(\n () => () => {\n active = false;\n const binding = activeSession;\n activeSession = undefined;\n binding?.context.ui?.setStatus(STATUS_KEY, undefined);\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 binding.context.ui?.setStatus(STATUS_KEY, undefined);\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 installLoopRuntime(cordis, config.pi);\n}\n\nexport default loopExtension;\n"],"mappings":"kgCAqCA,MAAM,EAAoB,QACpB,EAAmB,OACnB,EAA0B,uBAShC,SAAS,EACP,EACA,EACgB,CAChB,IAAM,EAAS,EAAU,OAAS,EAClC,MAAO,CACL,WAAY,EAAS,SAAW,WAChC,UAAW,EAAU,MAAM,CAAE,WAAY,IAAU,YAAc,IAAU,UAAU,EAAI,SAAW,QACpG,GAAI,EAAS,CAAE,OAAQ,GAAG,EAAU,OAAO,SAAU,MAAO,CAAkB,EAAI,CAAC,EACnF,QAAS,CACP,GAAI,EAAU,OAAS,EACnB,CAAC,CAAE,GAAI,EAAmB,QAAS,EAAK,CAAU,EAClD,CAAC,CAAE,GAAI,EAAmB,QAAS,GAAO,eAAgB,mCAAoC,CAAU,EAC5G,GAAI,EACA,CAAC,CAAE,GAAI,EAAkB,QAAS,EAAK,CAAU,EACjD,CAAC,CAAE,GAAI,EAAkB,QAAS,GAAO,eAAgB,sBAAuB,CAAU,CAChG,CACF,CACF,CAGA,SAAgB,EAAmB,EAAiB,EAAwB,CAC1E,IAAI,EAAS,GACT,EACA,EACA,EAEE,EAAkB,GAA6D,CACnF,IAAM,EAAU,EAEd,MAAC,GACD,CAAC,GACD,EAAQ,QAAQ,iBAAmB,EAAQ,gBAC3C,EAAQ,YAAY,YAAc,EAAQ,eAAe,aAAa,GAIxE,OAAO,CACT,EAEM,EAAiB,GAAqC,CAC1D,GAAI,CAAC,GAAU,IAAkB,EAAS,OAC1C,IAAM,EAAY,EAAQ,UAAU,cAAc,EAC5C,EAAY,EAAQ,UAAU,cAAc,EAClD,EAAQ,QAAQ,IAAI,UAAU,EAAY,EAAU,OAAS,UAAU,EAAU,SAAW,IAAA,EAAS,EACrG,GAAM,QAAQ,EAAc,EAAW,CAAS,CAAC,CACnD,EAEA,EAAO,eACO,CACV,EAAS,GACT,IAAM,EAAU,EAChB,EAAgB,IAAA,GAChB,GAAS,QAAQ,IAAI,UAAU,EAAY,IAAA,EAAS,EACpD,GAAM,QAAQ,EAAc,CAAC,EAAG,CAAC,CAAC,CAAC,EACnC,GAAM,QAAQ,EACd,IAAgB,EAChB,EAAgB,IAAA,GAChB,EAAO,IAAA,EACT,EACA,GAAG,EAAe,SACpB,EAEA,IAAM,EAAkB,EAA0B,CAAE,EACpD,EAAO,OAAO,CAAC,CAA+B,EAAI,GAAgB,CAChE,IAAM,EAAQ,EAAyC,EAAwB,CAAW,EAAG,CAC3F,WAAY,CACV,OAAQ,EACR,GAAI,EACJ,MAAO,OACP,YAAa,yCACb,MAAA,GACA,QAAS,CACP,CACE,GAAI,EACJ,MAAO,QACP,YAAa,2CACb,SAAU,CAAC,MAAO,UAAU,EAC5B,WAAY,CACV,CAAE,KAAM,aAAc,MAAO,WAAY,KAAM,SAAU,SAAU,GAAM,UAAW,CAAE,EACtF,CAAE,KAAM,aAAc,MAAO,cAAe,KAAM,SAAU,SAAU,GAAO,UAAW,CAAE,CAC5F,CACF,EACA,CACE,GAAI,EACJ,MAAO,OACP,YAAa,iCACb,SAAU,CAAC,MAAO,UAAU,EAC5B,WAAY,CACV,CAAE,KAAM,aAAc,MAAO,cAAe,KAAM,SAAU,SAAU,GAAM,UAAW,CAAE,EACzF,CAAE,KAAM,SAAU,MAAO,SAAU,KAAM,SAAU,SAAU,GAAO,UAAW,CAAE,CACnF,CACF,CACF,CACF,EACA,aAAc,EAAc,CAAC,EAAG,CAAC,CAAC,EAClC,MAAM,aAAa,EAAU,EAAgB,EAAW,CACtD,IAAM,EAAU,EAAe,EAAU,OAAO,EAChD,GAAI,CAAC,EAAS,MAAU,MAAM,wDAAwD,EACtF,GAAI,IAAa,EAAmB,CAClC,IAAM,EAAW,MAAM,EAAQ,UAAU,OACvC,EAAe,WACf,EAAe,WAAa,CAAE,WAAY,EAAe,UAAqB,EAAI,CAAC,CACrF,EACA,GAAI,IAAkB,EAAS,MAAU,MAAM,2BAA2B,EAC1E,MAAO,CAAE,QAAS,EAAW,SAAS,EAAS,WAAW,YAAc,4BAA6B,CACvG,CACA,GAAI,IAAa,EAAkB,CACjC,IAAM,EAAU,MAAM,EAAQ,UAAU,KACtC,EAAe,WACd,EAAe,QAAiC,6BACnD,EACA,GAAI,IAAkB,EAAS,MAAU,MAAM,2BAA2B,EAC1E,MAAO,CAAE,QAAS,EAAU,gBAAkB,+BAAgC,CAChF,CACA,MAAU,MAAM,6BAA6B,GAAU,CACzD,CACF,CAAC,EACD,EAAO,EACP,IAAM,EAAU,EAEhB,OADI,GAAS,EAAc,CAAO,MACrB,CACX,EAAM,QAAQ,EACV,IAAS,IAAO,EAAO,IAAA,GAC7B,CACF,CAAC,EACD,EAAO,OAAO,CAAC,CAAmB,EAAI,GAAc,CAClD,IAAM,EAAU,EAA2B,EAAiB,CAAS,CAAC,EAEtE,MADA,GAAgB,MACH,CACX,EAAQ,EACJ,IAAkB,IAAS,EAAgB,IAAA,GACjD,CACF,CAAC,EAED,EAAiB,EAAI,CACnB,MAAM,MAAM,EAAK,CACf,IAAM,EAAU,EAAe,CAAG,EAClC,GAAI,CAAC,EAAS,OACd,IAAM,EAAY,EAAQ,UAAU,cAAc,EAClD,GAAI,CAAC,EAAU,OAAQ,CACrB,EAAI,GAAG,OAAO,qDAAsD,CAAiB,EACrF,MACF,CACA,IAAM,EAAa,MAAM,EAAqB,EAAK,CAAS,EACxD,SAAkB,GAAW,CAAC,GAClC,GAAI,CACF,IAAM,EAAW,MAAM,EAAQ,UAAU,OAAO,CAAU,EAC1D,GAAI,IAAkB,EAAS,OAC3B,GAAU,EAAI,GAAG,OAAO,GAAG,EAAS,OAAS,EAAS,cAAc,WAAY,CAAiB,CACvG,OAAS,EAAO,CACd,GAAI,IAAkB,EAAS,OAC/B,EAAI,GAAG,OAAO,yBAAyB,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,IAAK,OAAO,CAC1G,CACF,EACA,MAAM,KAAK,EAAK,CACd,IAAM,EAAU,EAAe,CAAG,EAC7B,GACL,MAAM,EAAoB,EAAK,EAAQ,SAAS,CAClD,CACF,CAAC,EAED,EAAO,OAAO,CAAC,CAA2B,EAAG,KAAO,IAAmB,CACrE,IAAM,EAAc,EAAe,IAAI,CAA2B,EAC5D,EAAY,EAA+B,CAC/C,WAAY,GAAG,EAAY,WAAW,iBACtC,qBAAwB,OAAO,WAAW,EAC1C,cAAiB,IAAI,KAAK,CAAA,CAAE,YAAY,CAC1C,CAAC,EACK,EAA6B,CAAE,QAAS,EAAY,QAAS,cAAa,WAAU,EAC1F,EAAgB,EAChB,EAAe,QAAQ,EAA6B,CAAS,EAE7D,IAAI,EACA,EACJ,GAAI,CACF,EAAsB,EAAgB,SAAS,EAAY,QAAS,CAAS,EAC7E,EAAc,EAAU,cAAgB,EAAc,CAAO,CAAC,EAC9D,EAAc,CAAO,CACvB,OAAS,EAAO,CAGd,MAFI,IAAkB,IAAS,EAAgB,IAAA,IAC/C,MAAM,EAAU,QAAQ,CAAuB,EACzC,CACR,CAEA,OAAO,SAAY,CACb,IAAkB,IAAS,EAAgB,IAAA,IAC/C,EAAY,EACZ,EAAQ,QAAQ,IAAI,UAAU,EAAY,IAAA,EAAS,EACnD,GAAM,QAAQ,EAAc,CAAC,EAAG,CAAC,CAAC,CAAC,EACnC,IAAM,EAA2B,CAAC,EAClC,GAAI,CACF,MAAM,EAAoB,QAAQ,CAAuB,CAC3D,OAAS,EAAO,CACd,EAAc,KAAK,CAAK,CAC1B,CACA,GAAI,CACF,MAAM,EAAU,QAAQ,uBAAuB,CACjD,OAAS,EAAO,CACd,EAAc,KAAK,CAAK,CAC1B,CACA,GAAI,EAAc,OAAS,EAAG,MAAU,eAAe,EAAe,8BAA8B,CACtG,CACF,CAAC,CACH,CAGA,eAAsB,EAAc,EAAiC,CACnE,IAAM,EAAa,MAAM,EAAsB,EAAI,CAAc,EAC3D,EAAQ,EAAW,KAAK,OAAO,EAAY,CAAE,IAAG,CAAC,EACvD,GAAI,CACF,MAAM,CACR,OAAS,EAAO,CACd,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACA,MAAM,CACR,CACA,IAAI,EACJ,EAAG,GACD,uBAEG,KAAc,SAAY,CACzB,GAAI,CACF,MAAM,EAAM,QAAQ,CACtB,QAAU,CACR,MAAM,EAAW,QAAQ,CAC3B,CACF,EAAA,CAAG,CACP,CACF,CAMA,SAAS,EAAW,EAAiB,EAAgC,CACnE,EAAmB,EAAQ,EAAO,EAAE,CACtC"}
|
|
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 { 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 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 mode?.publish(loopModeState(launchers, instances));\n };\n\n cordis.effect(\n () => () => {\n active = false;\n const binding = activeSession;\n activeSession = undefined;\n binding?.context.ui?.setStatus(STATUS_KEY, undefined);\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 binding.context.ui?.setStatus(STATUS_KEY, undefined);\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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"}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Doom Pi Loop
|
|
2
|
+
|
|
3
|
+
## Resources
|
|
4
|
+
|
|
5
|
+
- [Package README](./README.md): Commands, interval limits, lifecycle, cost, and cross-extension launchers.
|
|
6
|
+
|
|
7
|
+
## How to use
|
|
8
|
+
|
|
9
|
+
- [doompi-use-loop](./src/prompts/doompi-use-loop/SKILL.md): Start, inspect, and stop session-scoped recurring prompts safely.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-loop",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
3
|
+
"version": "0.0.1-alpha.29",
|
|
4
4
|
"description": "Session-scoped recurring prompt scheduler and loop controls for Pi coding agents.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ai",
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
"author": "Vuong Ngo",
|
|
20
20
|
"files": [
|
|
21
21
|
"dist",
|
|
22
|
+
"llms.txt",
|
|
23
|
+
"src/prompts",
|
|
24
|
+
"README.md",
|
|
25
|
+
"LICENSE",
|
|
22
26
|
"package.json"
|
|
23
27
|
],
|
|
24
28
|
"type": "module",
|
|
@@ -43,8 +47,8 @@
|
|
|
43
47
|
},
|
|
44
48
|
"dependencies": {
|
|
45
49
|
"@deepseek-ai/cordis": "4.0.1",
|
|
46
|
-
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.
|
|
47
|
-
"@agimon-ai/doompi-ui": "0.0.1-alpha.
|
|
50
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.29",
|
|
51
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.29"
|
|
48
52
|
},
|
|
49
53
|
"devDependencies": {
|
|
50
54
|
"@earendil-works/pi-coding-agent": "0.84.2",
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: doompi-use-loop
|
|
3
|
+
description: Use Doom Pi Loop to start, inspect, and stop session-scoped recurring prompts safely.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Use Doom Pi Loop
|
|
7
|
+
|
|
8
|
+
Use Loop when the same prompt should run immediately and then recur while the current Pi session remains active.
|
|
9
|
+
|
|
10
|
+
## Start and control loops
|
|
11
|
+
|
|
12
|
+
1. Use `/loop` or `SPC l s` to start a loop.
|
|
13
|
+
2. Choose a prompt that is safe to repeat and an interval from 30 to 3600 seconds. The default is 300 seconds.
|
|
14
|
+
3. Use `/loops` or `SPC l l` to inspect active loops and stop individual instances.
|
|
15
|
+
4. Stop a loop as soon as its recurring work is complete.
|
|
16
|
+
|
|
17
|
+
Several loops can coexist. When Pi is busy, a due pass waits for the shared agent to become idle, and repeated due signals may coalesce instead of overlapping.
|
|
18
|
+
|
|
19
|
+
## Control cost and side effects
|
|
20
|
+
|
|
21
|
+
- Every pass can start another model turn and repeat tool calls or external side effects.
|
|
22
|
+
- Prefer idempotent checks and conservative intervals.
|
|
23
|
+
- Do not use Loop as a durable job queue. Replacing or shutting down the session stops timers, and resuming a transcript does not restart old loops.
|
|
24
|
+
- Stopping a loop prevents future passes, but it cannot undo effects from a prompt that was already sent.
|