@agimon-ai/doompi-loop 0.0.1-alpha.7 → 0.0.1-alpha.70
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 +84 -21
- package/dist/adapters/pi/defaultLoopLauncher.cjs +2 -0
- package/dist/adapters/pi/defaultLoopLauncher.cjs.map +1 -0
- package/dist/adapters/pi/defaultLoopLauncher.mjs +2 -0
- package/dist/adapters/pi/defaultLoopLauncher.mjs.map +1 -0
- package/dist/adapters/pi/extension.cjs +2 -0
- package/dist/adapters/pi/extension.cjs.map +1 -0
- package/dist/adapters/pi/extension.d.cts +10 -0
- package/dist/adapters/pi/extension.d.cts.map +1 -0
- package/dist/adapters/pi/extension.d.mts +10 -0
- package/dist/adapters/pi/extension.d.mts.map +1 -0
- package/dist/adapters/pi/extension.mjs +2 -0
- package/dist/adapters/pi/extension.mjs.map +1 -0
- package/dist/adapters/pi/leader.cjs +2 -0
- package/dist/adapters/pi/leader.cjs.map +1 -0
- package/dist/adapters/pi/leader.mjs +2 -0
- package/dist/adapters/pi/leader.mjs.map +1 -0
- package/dist/adapters/pi/loopConstants.cjs +2 -0
- package/dist/adapters/pi/loopConstants.cjs.map +1 -0
- package/dist/adapters/pi/loopConstants.mjs +2 -0
- package/dist/adapters/pi/loopConstants.mjs.map +1 -0
- package/dist/commands/loopCommand.cjs +1 -1
- package/dist/commands/loopCommand.cjs.map +1 -1
- package/dist/commands/loopCommand.mjs +1 -1
- package/dist/commands/loopCommand.mjs.map +1 -1
- package/dist/extensions/pi.cjs +1 -2
- package/dist/extensions/pi.d.cts +2 -2
- package/dist/extensions/pi.d.mts +2 -2
- package/dist/extensions/pi.mjs +1 -2
- package/dist/index.cjs +1 -1
- package/dist/index.d.cts +3 -4
- package/dist/index.d.mts +3 -4
- package/dist/index.mjs +1 -1
- package/dist/schemas/loopCommands.cjs +2 -0
- package/dist/schemas/loopCommands.cjs.map +1 -0
- package/dist/schemas/loopCommands.mjs +2 -0
- package/dist/schemas/loopCommands.mjs.map +1 -0
- package/dist/services/loopLaunchers.cjs +2 -0
- package/dist/services/loopLaunchers.cjs.map +1 -0
- package/dist/services/loopLaunchers.mjs +2 -0
- package/dist/services/loopLaunchers.mjs.map +1 -0
- package/dist/tui/loopListOverlay.cjs +1 -1
- package/dist/tui/loopListOverlay.cjs.map +1 -1
- package/dist/tui/loopListOverlay.mjs +1 -1
- package/dist/tui/loopListOverlay.mjs.map +1 -1
- package/dist/tui/startLoopOverlay.cjs +1 -1
- package/dist/tui/startLoopOverlay.cjs.map +1 -1
- package/dist/tui/startLoopOverlay.mjs +1 -1
- package/dist/tui/startLoopOverlay.mjs.map +1 -1
- package/dist/types/loopView.cjs +2 -0
- package/dist/types/loopView.cjs.map +1 -0
- package/dist/types/loopView.mjs +2 -0
- package/dist/types/loopView.mjs.map +1 -0
- package/llms.txt +9 -0
- package/package.json +55 -15
- package/src/exports/webClient.ts +1 -0
- package/src/prompts/doompi-use-loop/SKILL.md +24 -0
- package/src/types/loopView.ts +73 -0
- package/src/web/components/LoopsActivitySection.tsx +93 -0
- package/src/web/index.ts +38 -0
- package/src/web/tsconfig.json +23 -0
- package/dist/constants.cjs +0 -2
- package/dist/constants.cjs.map +0 -1
- package/dist/constants.mjs +0 -2
- package/dist/constants.mjs.map +0 -1
- package/dist/defaultLauncher.cjs +0 -2
- package/dist/defaultLauncher.cjs.map +0 -1
- package/dist/defaultLauncher.d.cts +0 -10
- package/dist/defaultLauncher.d.cts.map +0 -1
- package/dist/defaultLauncher.d.mts +0 -10
- package/dist/defaultLauncher.d.mts.map +0 -1
- package/dist/defaultLauncher.mjs +0 -2
- package/dist/defaultLauncher.mjs.map +0 -1
- package/dist/extension.cjs +0 -2
- package/dist/extension.cjs.map +0 -1
- package/dist/extension.d.cts +0 -6
- package/dist/extension.d.cts.map +0 -1
- package/dist/extension.d.mts +0 -6
- package/dist/extension.d.mts.map +0 -1
- package/dist/extension.mjs +0 -2
- package/dist/extension.mjs.map +0 -1
- package/dist/extensions/pi.cjs.map +0 -1
- package/dist/extensions/pi.mjs.map +0 -1
- package/dist/launcher.cjs +0 -2
- package/dist/launcher.cjs.map +0 -1
- package/dist/launcher.d.cts +0 -47
- package/dist/launcher.d.cts.map +0 -1
- package/dist/launcher.d.mts +0 -47
- package/dist/launcher.d.mts.map +0 -1
- package/dist/launcher.mjs +0 -2
- package/dist/launcher.mjs.map +0 -1
- package/dist/leader.cjs +0 -2
- package/dist/leader.cjs.map +0 -1
- package/dist/leader.mjs +0 -2
- package/dist/leader.mjs.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loopListOverlay.cjs","names":["DoomOverlay","matchesKey","DOOM_OVERLAY_ACCENT","truncateToWidth","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/tui/loopListOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\
|
|
1
|
+
{"version":3,"file":"loopListOverlay.cjs","names":["DoomOverlay","matchesKey","DOOM_NAVIGATION_KEYS","DOOM_OVERLAY_ACCENT","truncateToWidth","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/tui/loopListOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type {\n DoomLoopLaunchersService,\n LoopInstanceSnapshot,\n} from '@agimon-ai/doompi-extension-contracts/loop-launchers';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\n\nconst EMPTY_MESSAGE = 'No loop instances are active in this session.';\nconst STOP_SCOPE_MESSAGE = 'Stopping a loop does not cancel detached work already launched.';\nconst STOP_REASON = 'Stopped from the loop list.';\n\nexport class LoopListOverlay extends DoomOverlay {\n private readonly unsubscribe: () => void;\n private instances: readonly LoopInstanceSnapshot[];\n private selected = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly client: DoomLoopLaunchersService,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n this.instances = client.listInstances();\n this.unsubscribe = client.subscribe(() => this.refresh());\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.move(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.move(1);\n return;\n }\n if (data === 'x') {\n const instance = this.instances[this.selected];\n if (instance) void this.client.stop(instance.instanceId, STOP_REASON);\n }\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: 'LOOPS',\n breadcrumb: 'SPC › l / loops › l / list',\n headerRight: `${this.instances.length} active`,\n footer: `${DOOM_NAVIGATION_KEYS.list} move · x stop · esc close · launched detached work continues`,\n footerHints: [\n [DOOM_NAVIGATION_KEYS.list, 'move'],\n ['x', 'stop scheduling'],\n ['esc', 'close'],\n ],\n accent: DOOM_OVERLAY_ACCENT,\n };\n }\n\n protected renderBody(width: number): string[] {\n const stopScope = this.theme.fg('dim', truncateToWidth(STOP_SCOPE_MESSAGE, width));\n if (!this.instances.length) return [this.theme.fg('dim', EMPTY_MESSAGE), '', stopScope];\n return [\n stopScope,\n '',\n ...this.instances.flatMap((instance, index) => {\n const marker = index === this.selected ? this.theme.fg('accent', '▸') : ' ';\n const name = instance.label ?? instance.launcherLabel;\n const state = this.theme.fg(instance.state === 'running' ? 'success' : 'warning', instance.state);\n const line = truncateToWidth(`${marker} ${name} · ${state}`, width);\n const detail = instance.detail ?? instance.instanceId;\n return [line, ` ${this.theme.fg('dim', truncateToWidth(detail, Math.max(0, width - 4)))}`];\n }),\n ];\n }\n\n private refresh(): void {\n this.instances = this.client.listInstances();\n this.selected = Math.min(this.selected, Math.max(0, this.instances.length - 1));\n this.invalidate();\n }\n\n private move(delta: number): void {\n if (!this.instances.length) return;\n this.selected = Math.max(0, Math.min(this.selected + delta, this.instances.length - 1));\n this.invalidate();\n }\n}\n\nexport async function openLoopListOverlay(ctx: ExtensionContext, client: DoomLoopLaunchersService): Promise<void> {\n if (ctx.mode !== 'tui') {\n const instances = client.listInstances();\n const labels = instances.map((instance) => `${instance.label ?? instance.launcherLabel} (${instance.instanceId})`);\n const selected = await ctx.ui.select('Stop loop scheduling', labels);\n const instance = instances[labels.indexOf(selected ?? '')];\n if (instance) await client.stop(instance.instanceId, STOP_REASON);\n return;\n }\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new LoopListOverlay(tui, theme, client, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{DOOM_FULLSCREEN_UI_OPTIONS as e,
|
|
1
|
+
import{DOOM_FULLSCREEN_UI_OPTIONS as e,DOOM_NAVIGATION_KEYS as t,DOOM_OVERLAY_ACCENT as n,DoomOverlay as r}from"@agimon-ai/doompi-ui/components/doomOverlay";import{matchesKey as i,truncateToWidth as a}from"@earendil-works/pi-tui";const o=`Stopped from the loop list.`;var s=class extends r{client;done;unsubscribe;instances;selected=0;constructor(e,t,n,r){super(e,t),this.client=n,this.done=r,this.instances=n.listInstances(),this.unsubscribe=n.subscribe(()=>this.refresh())}dispose(){this.unsubscribe()}handleInput(e){if(i(e,`escape`)||i(e,`ctrl+c`)||e===`q`){this.done(void 0);return}if(i(e,`up`)||e===`k`){this.move(-1);return}if(i(e,`down`)||e===`j`){this.move(1);return}if(e===`x`){let e=this.instances[this.selected];e&&this.client.stop(e.instanceId,o)}}getChrome(){return{title:`LOOPS`,breadcrumb:`SPC › l / loops › l / list`,headerRight:`${this.instances.length} active`,footer:`${t.list} move · x stop · esc close · launched detached work continues`,footerHints:[[t.list,`move`],[`x`,`stop scheduling`],[`esc`,`close`]],accent:n}}renderBody(e){let t=this.theme.fg(`dim`,a(`Stopping a loop does not cancel detached work already launched.`,e));return this.instances.length?[t,``,...this.instances.flatMap((t,n)=>{let r=n===this.selected?this.theme.fg(`accent`,`▸`):` `,i=t.label??t.launcherLabel,o=this.theme.fg(t.state===`running`?`success`:`warning`,t.state),s=a(`${r} ${i} · ${o}`,e),c=t.detail??t.instanceId;return[s,` ${this.theme.fg(`dim`,a(c,Math.max(0,e-4)))}`]})]:[this.theme.fg(`dim`,`No loop instances are active in this session.`),``,t]}refresh(){this.instances=this.client.listInstances(),this.selected=Math.min(this.selected,Math.max(0,this.instances.length-1)),this.invalidate()}move(e){this.instances.length&&(this.selected=Math.max(0,Math.min(this.selected+e,this.instances.length-1)),this.invalidate())}};async function c(t,n){if(t.mode!==`tui`){let e=n.listInstances(),r=e.map(e=>`${e.label??e.launcherLabel} (${e.instanceId})`),i=await t.ui.select(`Stop loop scheduling`,r),a=e[r.indexOf(i??``)];a&&await n.stop(a.instanceId,o);return}await t.ui.custom((e,t,r,i)=>new s(e,t,n,i),e)}export{s as LoopListOverlay,c as openLoopListOverlay};
|
|
2
2
|
//# sourceMappingURL=loopListOverlay.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"loopListOverlay.mjs","names":[],"sources":["../../src/tui/loopListOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\
|
|
1
|
+
{"version":3,"file":"loopListOverlay.mjs","names":[],"sources":["../../src/tui/loopListOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type {\n DoomLoopLaunchersService,\n LoopInstanceSnapshot,\n} from '@agimon-ai/doompi-extension-contracts/loop-launchers';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\n\nconst EMPTY_MESSAGE = 'No loop instances are active in this session.';\nconst STOP_SCOPE_MESSAGE = 'Stopping a loop does not cancel detached work already launched.';\nconst STOP_REASON = 'Stopped from the loop list.';\n\nexport class LoopListOverlay extends DoomOverlay {\n private readonly unsubscribe: () => void;\n private instances: readonly LoopInstanceSnapshot[];\n private selected = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly client: DoomLoopLaunchersService,\n private readonly done: (result: undefined) => void,\n ) {\n super(tui, theme);\n this.instances = client.listInstances();\n this.unsubscribe = client.subscribe(() => this.refresh());\n }\n\n dispose(): void {\n this.unsubscribe();\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.move(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.move(1);\n return;\n }\n if (data === 'x') {\n const instance = this.instances[this.selected];\n if (instance) void this.client.stop(instance.instanceId, STOP_REASON);\n }\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: 'LOOPS',\n breadcrumb: 'SPC › l / loops › l / list',\n headerRight: `${this.instances.length} active`,\n footer: `${DOOM_NAVIGATION_KEYS.list} move · x stop · esc close · launched detached work continues`,\n footerHints: [\n [DOOM_NAVIGATION_KEYS.list, 'move'],\n ['x', 'stop scheduling'],\n ['esc', 'close'],\n ],\n accent: DOOM_OVERLAY_ACCENT,\n };\n }\n\n protected renderBody(width: number): string[] {\n const stopScope = this.theme.fg('dim', truncateToWidth(STOP_SCOPE_MESSAGE, width));\n if (!this.instances.length) return [this.theme.fg('dim', EMPTY_MESSAGE), '', stopScope];\n return [\n stopScope,\n '',\n ...this.instances.flatMap((instance, index) => {\n const marker = index === this.selected ? this.theme.fg('accent', '▸') : ' ';\n const name = instance.label ?? instance.launcherLabel;\n const state = this.theme.fg(instance.state === 'running' ? 'success' : 'warning', instance.state);\n const line = truncateToWidth(`${marker} ${name} · ${state}`, width);\n const detail = instance.detail ?? instance.instanceId;\n return [line, ` ${this.theme.fg('dim', truncateToWidth(detail, Math.max(0, width - 4)))}`];\n }),\n ];\n }\n\n private refresh(): void {\n this.instances = this.client.listInstances();\n this.selected = Math.min(this.selected, Math.max(0, this.instances.length - 1));\n this.invalidate();\n }\n\n private move(delta: number): void {\n if (!this.instances.length) return;\n this.selected = Math.max(0, Math.min(this.selected + delta, this.instances.length - 1));\n this.invalidate();\n }\n}\n\nexport async function openLoopListOverlay(ctx: ExtensionContext, client: DoomLoopLaunchersService): Promise<void> {\n if (ctx.mode !== 'tui') {\n const instances = client.listInstances();\n const labels = instances.map((instance) => `${instance.label ?? instance.launcherLabel} (${instance.instanceId})`);\n const selected = await ctx.ui.select('Stop loop scheduling', labels);\n const instance = instances[labels.indexOf(selected ?? '')];\n if (instance) await client.stop(instance.instanceId, STOP_REASON);\n return;\n }\n await ctx.ui.custom<undefined>(\n (tui, theme, _keybindings, done) => new LoopListOverlay(tui, theme, client, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
let e=require("@agimon-ai/doompi-ui/components/doomOverlay"),t=require("@earendil-works/pi-tui");var n=class extends e.DoomOverlay{launchers;done;selected=0;constructor(e,t,n,r){super(e,t),this.launchers=n,this.done=r}handleInput(e){if((0,t.matchesKey)(e,`escape`)||(0,t.matchesKey)(e,`ctrl+c`)||e===`q`)
|
|
1
|
+
let e=require("@agimon-ai/doompi-ui/components/doomOverlay"),t=require("@earendil-works/pi-tui");var n=class extends e.DoomOverlay{launchers;done;selected=0;constructor(e,t,n,r){super(e,t),this.launchers=n,this.done=r}handleInput(e){if((0,t.matchesKey)(e,`escape`)||(0,t.matchesKey)(e,`ctrl+c`)||e===`q`){this.done(void 0);return}if((0,t.matchesKey)(e,`up`)||e===`k`){this.move(-1);return}if((0,t.matchesKey)(e,`down`)||e===`j`){this.move(1);return}(0,t.matchesKey)(e,`enter`)&&this.done(this.launchers[this.selected]?.id)}getChrome(){return{title:`START LOOP`,breadcrumb:`SPC › l / loops › s / start`,footer:`${e.DOOM_NAVIGATION_KEYS.list} move · enter launch · esc close`,footerHints:[[e.DOOM_NAVIGATION_KEYS.list,`move`],[`enter`,`launch`],[`esc`,`close`]],accent:e.DOOM_OVERLAY_ACCENT}}renderBody(e){return this.launchers.length?this.launchers.flatMap((n,r)=>{let i=r===this.selected,a=i?this.theme.fg(`accent`,`▸`):` `,o=i?this.theme.bold(n.label):n.label,s=[`${a} ${(0,t.truncateToWidth)(o,Math.max(0,e-2))}`];return n.description&&s.push(` ${this.theme.fg(`dim`,(0,t.truncateToWidth)(n.description,Math.max(0,e-4)))}`),s}):[this.theme.fg(`dim`,`No loop launchers are registered for this session.`)]}move(e){this.launchers.length&&(this.selected=Math.max(0,Math.min(this.selected+e,this.launchers.length-1)),this.invalidate())}};async function r(t,r){if(t.mode!==`tui`){let e=r.map(e=>e.label),n=await t.ui.select(`Start loop`,e);return r[e.indexOf(n??``)]?.id}return t.ui.custom((e,t,i,a)=>new n(e,t,r,a),e.DOOM_FULLSCREEN_UI_OPTIONS)}exports.StartLoopOverlay=n,exports.openStartLoopOverlay=r;
|
|
2
2
|
//# sourceMappingURL=startLoopOverlay.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startLoopOverlay.cjs","names":["DoomOverlay","matchesKey","DOOM_OVERLAY_ACCENT","truncateToWidth","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/tui/startLoopOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\
|
|
1
|
+
{"version":3,"file":"startLoopOverlay.cjs","names":["DoomOverlay","matchesKey","DOOM_NAVIGATION_KEYS","DOOM_OVERLAY_ACCENT","truncateToWidth","DOOM_FULLSCREEN_UI_OPTIONS"],"sources":["../../src/tui/startLoopOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { LoopLauncherSummary } from '@agimon-ai/doompi-extension-contracts/loop-launchers';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\n\nconst EMPTY_MESSAGE = 'No loop launchers are registered for this session.';\n\nexport class StartLoopOverlay extends DoomOverlay {\n private selected = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly launchers: readonly LoopLauncherSummary[],\n private readonly done: (result: string | undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.move(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.move(1);\n return;\n }\n if (matchesKey(data, 'enter')) this.done(this.launchers[this.selected]?.id);\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: 'START LOOP',\n breadcrumb: 'SPC › l / loops › s / start',\n footer: `${DOOM_NAVIGATION_KEYS.list} move · enter launch · esc close`,\n footerHints: [\n [DOOM_NAVIGATION_KEYS.list, 'move'],\n ['enter', 'launch'],\n ['esc', 'close'],\n ],\n accent: DOOM_OVERLAY_ACCENT,\n };\n }\n\n protected renderBody(width: number): string[] {\n if (!this.launchers.length) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n return this.launchers.flatMap((launcher, index) => {\n const selected = index === this.selected;\n const marker = selected ? this.theme.fg('accent', '▸') : ' ';\n const label = selected ? this.theme.bold(launcher.label) : launcher.label;\n const lines = [`${marker} ${truncateToWidth(label, Math.max(0, width - 2))}`];\n if (launcher.description)\n lines.push(` ${this.theme.fg('dim', truncateToWidth(launcher.description, Math.max(0, width - 4)))}`);\n return lines;\n });\n }\n\n private move(delta: number): void {\n if (!this.launchers.length) return;\n this.selected = Math.max(0, Math.min(this.selected + delta, this.launchers.length - 1));\n this.invalidate();\n }\n}\n\nexport async function openStartLoopOverlay(\n ctx: ExtensionContext,\n launchers: readonly LoopLauncherSummary[],\n): Promise<string | undefined> {\n if (ctx.mode !== 'tui') {\n const labels = launchers.map((launcher) => launcher.label);\n const selected = await ctx.ui.select('Start loop', labels);\n return launchers[labels.indexOf(selected ?? '')]?.id;\n }\n return ctx.ui.custom<string | undefined>(\n (tui, theme, _keybindings, done) => new StartLoopOverlay(tui, theme, launchers, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"iGAcA,IAAa,EAAb,cAAsCA,EAAAA,WAAY,CAM7B,UACA,KANnB,SAAmB,EAEnB,YACE,EACA,EACA,EACA,EACA,CACA,MAAM,EAAK,CAAK,EAHC,KAAA,UAAA,EACA,KAAA,KAAA,CAGnB,CAEA,YAAY,EAAoB,CAC9B,IAAA,EAAIC,EAAAA,WAAAA,CAAW,EAAM,QAAQ,IAAA,EAAKA,EAAAA,WAAAA,CAAW,EAAM,QAAQ,GAAK,IAAS,IAAK,CAC5E,KAAK,KAAK,IAAA,EAAS,EACnB,MACF,CACA,IAAA,EAAIA,EAAAA,WAAAA,CAAW,EAAM,IAAI,GAAK,IAAS,IAAK,CAC1C,KAAK,KAAK,EAAE,EACZ,MACF,CACA,IAAA,EAAIA,EAAAA,WAAAA,CAAW,EAAM,MAAM,GAAK,IAAS,IAAK,CAC5C,KAAK,KAAK,CAAC,EACX,MACF,EACA,EAAIA,EAAAA,WAAAA,CAAW,EAAM,OAAO,GAAG,KAAK,KAAK,KAAK,UAAU,KAAK,SAAS,EAAE,EAAE,CAC5E,CAEA,WAAyC,CACvC,MAAO,CACL,MAAO,aACP,WAAY,8BACZ,OAAQ,GAAGC,EAAAA,qBAAqB,KAAK,kCACrC,YAAa,CACX,CAACA,EAAAA,qBAAqB,KAAM,MAAM,EAClC,CAAC,QAAS,QAAQ,EAClB,CAAC,MAAO,OAAO,CACjB,EACA,OAAQC,EAAAA,mBACV,CACF,CAEA,WAAqB,EAAyB,CAE5C,OADK,KAAK,UAAU,OACb,KAAK,UAAU,SAAS,EAAU,IAAU,CACjD,IAAM,EAAW,IAAU,KAAK,SAC1B,EAAS,EAAW,KAAK,MAAM,GAAG,SAAU,GAAG,EAAI,IACnD,EAAQ,EAAW,KAAK,MAAM,KAAK,EAAS,KAAK,EAAI,EAAS,MAC9D,EAAQ,CAAC,GAAG,EAAO,IAAA,EAAGC,EAAAA,gBAAAA,CAAgB,EAAO,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,GAAG,EAG5E,OAFI,EAAS,aACX,EAAM,KAAK,OAAO,KAAK,MAAM,GAAG,OAAA,EAAOA,EAAAA,gBAAAA,CAAgB,EAAS,YAAa,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,CAAC,GAAG,EAClG,CACT,CAAC,EATkC,CAAC,KAAK,MAAM,GAAG,MAAO,oDAAa,CAAC,CAUzE,CAEA,KAAa,EAAqB,CAC3B,KAAK,UAAU,SACpB,KAAK,SAAW,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,SAAW,EAAO,KAAK,UAAU,OAAS,CAAC,CAAC,EACtF,KAAK,WAAW,EAClB,CACF,EAEA,eAAsB,EACpB,EACA,EAC6B,CAC7B,GAAI,EAAI,OAAS,MAAO,CACtB,IAAM,EAAS,EAAU,IAAK,GAAa,EAAS,KAAK,EACnD,EAAW,MAAM,EAAI,GAAG,OAAO,aAAc,CAAM,EACzD,OAAO,EAAU,EAAO,QAAQ,GAAY,EAAE,EAAE,EAAE,EACpD,CACA,OAAO,EAAI,GAAG,QACX,EAAK,EAAO,EAAc,IAAS,IAAI,EAAiB,EAAK,EAAO,EAAW,CAAI,EACpFC,EAAAA,0BACF,CACF"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{DOOM_FULLSCREEN_UI_OPTIONS as e,
|
|
1
|
+
import{DOOM_FULLSCREEN_UI_OPTIONS as e,DOOM_NAVIGATION_KEYS as t,DOOM_OVERLAY_ACCENT as n,DoomOverlay as r}from"@agimon-ai/doompi-ui/components/doomOverlay";import{matchesKey as i,truncateToWidth as a}from"@earendil-works/pi-tui";var o=class extends r{launchers;done;selected=0;constructor(e,t,n,r){super(e,t),this.launchers=n,this.done=r}handleInput(e){if(i(e,`escape`)||i(e,`ctrl+c`)||e===`q`){this.done(void 0);return}if(i(e,`up`)||e===`k`){this.move(-1);return}if(i(e,`down`)||e===`j`){this.move(1);return}i(e,`enter`)&&this.done(this.launchers[this.selected]?.id)}getChrome(){return{title:`START LOOP`,breadcrumb:`SPC › l / loops › s / start`,footer:`${t.list} move · enter launch · esc close`,footerHints:[[t.list,`move`],[`enter`,`launch`],[`esc`,`close`]],accent:n}}renderBody(e){return this.launchers.length?this.launchers.flatMap((t,n)=>{let r=n===this.selected,i=r?this.theme.fg(`accent`,`▸`):` `,o=r?this.theme.bold(t.label):t.label,s=[`${i} ${a(o,Math.max(0,e-2))}`];return t.description&&s.push(` ${this.theme.fg(`dim`,a(t.description,Math.max(0,e-4)))}`),s}):[this.theme.fg(`dim`,`No loop launchers are registered for this session.`)]}move(e){this.launchers.length&&(this.selected=Math.max(0,Math.min(this.selected+e,this.launchers.length-1)),this.invalidate())}};async function s(t,n){if(t.mode!==`tui`){let e=n.map(e=>e.label),r=await t.ui.select(`Start loop`,e);return n[e.indexOf(r??``)]?.id}return t.ui.custom((e,t,r,i)=>new o(e,t,n,i),e)}export{o as StartLoopOverlay,s as openStartLoopOverlay};
|
|
2
2
|
//# sourceMappingURL=startLoopOverlay.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"startLoopOverlay.mjs","names":[],"sources":["../../src/tui/startLoopOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\
|
|
1
|
+
{"version":3,"file":"startLoopOverlay.mjs","names":[],"sources":["../../src/tui/startLoopOverlay.ts"],"sourcesContent":["import {\n DOOM_FULLSCREEN_UI_OPTIONS,\n DOOM_NAVIGATION_KEYS,\n DOOM_OVERLAY_ACCENT,\n DoomOverlay,\n type DoomOverlayChrome,\n type DoomOverlayTui,\n} from '@agimon-ai/doompi-ui/components/doomOverlay';\nimport type { LoopLauncherSummary } from '@agimon-ai/doompi-extension-contracts/loop-launchers';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\n\nconst EMPTY_MESSAGE = 'No loop launchers are registered for this session.';\n\nexport class StartLoopOverlay extends DoomOverlay {\n private selected = 0;\n\n constructor(\n tui: DoomOverlayTui,\n theme: Theme,\n private readonly launchers: readonly LoopLauncherSummary[],\n private readonly done: (result: string | undefined) => void,\n ) {\n super(tui, theme);\n }\n\n handleInput(data: string): void {\n if (matchesKey(data, 'escape') || matchesKey(data, 'ctrl+c') || data === 'q') {\n this.done(undefined);\n return;\n }\n if (matchesKey(data, 'up') || data === 'k') {\n this.move(-1);\n return;\n }\n if (matchesKey(data, 'down') || data === 'j') {\n this.move(1);\n return;\n }\n if (matchesKey(data, 'enter')) this.done(this.launchers[this.selected]?.id);\n }\n\n protected getChrome(): DoomOverlayChrome {\n return {\n title: 'START LOOP',\n breadcrumb: 'SPC › l / loops › s / start',\n footer: `${DOOM_NAVIGATION_KEYS.list} move · enter launch · esc close`,\n footerHints: [\n [DOOM_NAVIGATION_KEYS.list, 'move'],\n ['enter', 'launch'],\n ['esc', 'close'],\n ],\n accent: DOOM_OVERLAY_ACCENT,\n };\n }\n\n protected renderBody(width: number): string[] {\n if (!this.launchers.length) return [this.theme.fg('dim', EMPTY_MESSAGE)];\n return this.launchers.flatMap((launcher, index) => {\n const selected = index === this.selected;\n const marker = selected ? this.theme.fg('accent', '▸') : ' ';\n const label = selected ? this.theme.bold(launcher.label) : launcher.label;\n const lines = [`${marker} ${truncateToWidth(label, Math.max(0, width - 2))}`];\n if (launcher.description)\n lines.push(` ${this.theme.fg('dim', truncateToWidth(launcher.description, Math.max(0, width - 4)))}`);\n return lines;\n });\n }\n\n private move(delta: number): void {\n if (!this.launchers.length) return;\n this.selected = Math.max(0, Math.min(this.selected + delta, this.launchers.length - 1));\n this.invalidate();\n }\n}\n\nexport async function openStartLoopOverlay(\n ctx: ExtensionContext,\n launchers: readonly LoopLauncherSummary[],\n): Promise<string | undefined> {\n if (ctx.mode !== 'tui') {\n const labels = launchers.map((launcher) => launcher.label);\n const selected = await ctx.ui.select('Start loop', labels);\n return launchers[labels.indexOf(selected ?? '')]?.id;\n }\n return ctx.ui.custom<string | undefined>(\n (tui, theme, _keybindings, done) => new StartLoopOverlay(tui, theme, launchers, done),\n DOOM_FULLSCREEN_UI_OPTIONS,\n );\n}\n"],"mappings":"sOAcA,IAAa,EAAb,cAAsC,CAAY,CAM7B,UACA,KANnB,SAAmB,EAEnB,YACE,EACA,EACA,EACA,EACA,CACA,MAAM,EAAK,CAAK,EAHC,KAAA,UAAA,EACA,KAAA,KAAA,CAGnB,CAEA,YAAY,EAAoB,CAC9B,GAAI,EAAW,EAAM,QAAQ,GAAK,EAAW,EAAM,QAAQ,GAAK,IAAS,IAAK,CAC5E,KAAK,KAAK,IAAA,EAAS,EACnB,MACF,CACA,GAAI,EAAW,EAAM,IAAI,GAAK,IAAS,IAAK,CAC1C,KAAK,KAAK,EAAE,EACZ,MACF,CACA,GAAI,EAAW,EAAM,MAAM,GAAK,IAAS,IAAK,CAC5C,KAAK,KAAK,CAAC,EACX,MACF,CACI,EAAW,EAAM,OAAO,GAAG,KAAK,KAAK,KAAK,UAAU,KAAK,SAAS,EAAE,EAAE,CAC5E,CAEA,WAAyC,CACvC,MAAO,CACL,MAAO,aACP,WAAY,8BACZ,OAAQ,GAAG,EAAqB,KAAK,kCACrC,YAAa,CACX,CAAC,EAAqB,KAAM,MAAM,EAClC,CAAC,QAAS,QAAQ,EAClB,CAAC,MAAO,OAAO,CACjB,EACA,OAAQ,CACV,CACF,CAEA,WAAqB,EAAyB,CAE5C,OADK,KAAK,UAAU,OACb,KAAK,UAAU,SAAS,EAAU,IAAU,CACjD,IAAM,EAAW,IAAU,KAAK,SAC1B,EAAS,EAAW,KAAK,MAAM,GAAG,SAAU,GAAG,EAAI,IACnD,EAAQ,EAAW,KAAK,MAAM,KAAK,EAAS,KAAK,EAAI,EAAS,MAC9D,EAAQ,CAAC,GAAG,EAAO,GAAG,EAAgB,EAAO,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,GAAG,EAG5E,OAFI,EAAS,aACX,EAAM,KAAK,OAAO,KAAK,MAAM,GAAG,MAAO,EAAgB,EAAS,YAAa,KAAK,IAAI,EAAG,EAAQ,CAAC,CAAC,CAAC,GAAG,EAClG,CACT,CAAC,EATkC,CAAC,KAAK,MAAM,GAAG,MAAO,oDAAa,CAAC,CAUzE,CAEA,KAAa,EAAqB,CAC3B,KAAK,UAAU,SACpB,KAAK,SAAW,KAAK,IAAI,EAAG,KAAK,IAAI,KAAK,SAAW,EAAO,KAAK,UAAU,OAAS,CAAC,CAAC,EACtF,KAAK,WAAW,EAClB,CACF,EAEA,eAAsB,EACpB,EACA,EAC6B,CAC7B,GAAI,EAAI,OAAS,MAAO,CACtB,IAAM,EAAS,EAAU,IAAK,GAAa,EAAS,KAAK,EACnD,EAAW,MAAM,EAAI,GAAG,OAAO,aAAc,CAAM,EACzD,OAAO,EAAU,EAAO,QAAQ,GAAY,EAAE,EAAE,EAAE,EACpD,CACA,OAAO,EAAI,GAAG,QACX,EAAK,EAAO,EAAc,IAAS,IAAI,EAAiB,EAAK,EAAO,EAAW,CAAI,EACpF,CACF,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
new RegExp(String.raw`\u001b\[[0-9;]*m`,`gu`);function e(e){if(e.length!==0)return JSON.stringify(e.map(e=>({instanceId:e.instanceId,label:e.label??e.launcherLabel,detail:e.detail??e.instanceId,state:e.state})))}exports.LOOP_VIEW_STATUS_KEY=`doom-loop-instances`,exports.formatLoopStatusView=e;
|
|
2
|
+
//# sourceMappingURL=loopView.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopView.cjs","names":[],"sources":["../../src/types/loopView.ts"],"sourcesContent":["/** The browser-only status that carries active Loop instances into the activity dock. */\nexport const LOOP_VIEW_STATUS_KEY = 'doom-loop-instances';\n\nexport type LoopStatusState = 'starting' | 'running' | 'stopping';\n\n/** The small, serializable Loop view the cockpit needs for one activity row. */\nexport interface LoopStatusItem {\n readonly instanceId: string;\n readonly label: string;\n readonly detail: string;\n readonly state: LoopStatusState;\n}\n\ninterface LoopStatusSource {\n readonly instanceId: string;\n readonly launcherLabel: string;\n readonly label?: string;\n readonly detail?: string;\n readonly state: LoopStatusState;\n}\n\nconst ANSI = new RegExp(String.raw`\\u001b\\[[0-9;]*m`, 'gu');\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isState(value: unknown): value is LoopStatusState {\n return value === 'starting' || value === 'running' || value === 'stopping';\n}\n\n/** Projects runtime snapshots into the JSON status consumed by the web plugin. */\nexport function formatLoopStatusView(instances: readonly LoopStatusSource[]): string | undefined {\n if (instances.length === 0) return undefined;\n return JSON.stringify(\n instances.map((instance) => ({\n instanceId: instance.instanceId,\n label: instance.label ?? instance.launcherLabel,\n detail: instance.detail ?? instance.instanceId,\n state: instance.state,\n })),\n );\n}\n\n/** Reads a Loop status without letting malformed session data crash the activity dock. */\nexport function parseLoopStatusView(raw: string | undefined): LoopStatusItem[] | undefined {\n if (raw === undefined) return undefined;\n const text = raw.replace(ANSI, '').trim();\n if (text === '') return undefined;\n\n try {\n const value: unknown = JSON.parse(text);\n if (!Array.isArray(value) || value.length === 0) return undefined;\n const items: LoopStatusItem[] = [];\n for (const item of value) {\n if (\n !isRecord(item) ||\n typeof item.instanceId !== 'string' ||\n item.instanceId.trim() === '' ||\n typeof item.label !== 'string' ||\n item.label.trim() === '' ||\n typeof item.detail !== 'string' ||\n !isState(item.state)\n ) {\n return undefined;\n }\n items.push({ instanceId: item.instanceId, label: item.label, detail: item.detail, state: item.state });\n }\n return items;\n } catch {\n return undefined;\n }\n}\n"],"mappings":"AAqBa,IAAI,OAAO,OAAO,GAAG,mBAAoB,IAAI,EAW1D,SAAgB,EAAqB,EAA4D,CAC3F,KAAU,SAAW,EACzB,OAAO,KAAK,UACV,EAAU,IAAK,IAAc,CAC3B,WAAY,EAAS,WACrB,MAAO,EAAS,OAAS,EAAS,cAClC,OAAQ,EAAS,QAAU,EAAS,WACpC,MAAO,EAAS,KAClB,EAAE,CACJ,CACF"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
const e=`doom-loop-instances`;new RegExp(String.raw`\u001b\[[0-9;]*m`,`gu`);function t(e){if(e.length!==0)return JSON.stringify(e.map(e=>({instanceId:e.instanceId,label:e.label??e.launcherLabel,detail:e.detail??e.instanceId,state:e.state})))}export{e as LOOP_VIEW_STATUS_KEY,t as formatLoopStatusView};
|
|
2
|
+
//# sourceMappingURL=loopView.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loopView.mjs","names":[],"sources":["../../src/types/loopView.ts"],"sourcesContent":["/** The browser-only status that carries active Loop instances into the activity dock. */\nexport const LOOP_VIEW_STATUS_KEY = 'doom-loop-instances';\n\nexport type LoopStatusState = 'starting' | 'running' | 'stopping';\n\n/** The small, serializable Loop view the cockpit needs for one activity row. */\nexport interface LoopStatusItem {\n readonly instanceId: string;\n readonly label: string;\n readonly detail: string;\n readonly state: LoopStatusState;\n}\n\ninterface LoopStatusSource {\n readonly instanceId: string;\n readonly launcherLabel: string;\n readonly label?: string;\n readonly detail?: string;\n readonly state: LoopStatusState;\n}\n\nconst ANSI = new RegExp(String.raw`\\u001b\\[[0-9;]*m`, 'gu');\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value);\n}\n\nfunction isState(value: unknown): value is LoopStatusState {\n return value === 'starting' || value === 'running' || value === 'stopping';\n}\n\n/** Projects runtime snapshots into the JSON status consumed by the web plugin. */\nexport function formatLoopStatusView(instances: readonly LoopStatusSource[]): string | undefined {\n if (instances.length === 0) return undefined;\n return JSON.stringify(\n instances.map((instance) => ({\n instanceId: instance.instanceId,\n label: instance.label ?? instance.launcherLabel,\n detail: instance.detail ?? instance.instanceId,\n state: instance.state,\n })),\n );\n}\n\n/** Reads a Loop status without letting malformed session data crash the activity dock. */\nexport function parseLoopStatusView(raw: string | undefined): LoopStatusItem[] | undefined {\n if (raw === undefined) return undefined;\n const text = raw.replace(ANSI, '').trim();\n if (text === '') return undefined;\n\n try {\n const value: unknown = JSON.parse(text);\n if (!Array.isArray(value) || value.length === 0) return undefined;\n const items: LoopStatusItem[] = [];\n for (const item of value) {\n if (\n !isRecord(item) ||\n typeof item.instanceId !== 'string' ||\n item.instanceId.trim() === '' ||\n typeof item.label !== 'string' ||\n item.label.trim() === '' ||\n typeof item.detail !== 'string' ||\n !isState(item.state)\n ) {\n return undefined;\n }\n items.push({ instanceId: item.instanceId, label: item.label, detail: item.detail, state: item.state });\n }\n return items;\n } catch {\n return undefined;\n }\n}\n"],"mappings":"AACA,MAAa,EAAuB,sBAoBvB,IAAI,OAAO,OAAO,GAAG,mBAAoB,IAAI,EAW1D,SAAgB,EAAqB,EAA4D,CAC3F,KAAU,SAAW,EACzB,OAAO,KAAK,UACV,EAAU,IAAK,IAAc,CAC3B,WAAY,EAAS,WACrB,MAAO,EAAS,OAAS,EAAS,cAClC,OAAQ,EAAS,QAAU,EAAS,WACpC,MAAO,EAAS,KAClB,EAAE,CACJ,CACF"}
|
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,13 +1,41 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@agimon-ai/doompi-loop",
|
|
3
|
-
"version": "0.0.1-alpha.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.1-alpha.70",
|
|
4
|
+
"description": "Session-scoped recurring prompt scheduler and loop controls for Pi coding agents.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"ai",
|
|
7
|
+
"automation",
|
|
8
|
+
"coding-agent",
|
|
9
|
+
"developer-tools",
|
|
10
|
+
"doompi",
|
|
11
|
+
"pi-coding-agent",
|
|
12
|
+
"pi-extension",
|
|
13
|
+
"pi-package",
|
|
14
|
+
"prompt-scheduler",
|
|
15
|
+
"recurring-tasks"
|
|
16
|
+
],
|
|
5
17
|
"homepage": "https://agimon.ai",
|
|
18
|
+
"bugs": {
|
|
19
|
+
"url": "https://github.com/AgiFlow/doompi/issues"
|
|
20
|
+
},
|
|
6
21
|
"license": "MIT",
|
|
7
22
|
"author": "Vuong Ngo",
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/AgiFlow/doompi.git",
|
|
26
|
+
"directory": "packages/minor/doompi-loop"
|
|
27
|
+
},
|
|
8
28
|
"files": [
|
|
9
29
|
"dist",
|
|
10
|
-
"
|
|
30
|
+
"src/web",
|
|
31
|
+
"src/exports/webClient.ts",
|
|
32
|
+
"llms.txt",
|
|
33
|
+
"src/prompts",
|
|
34
|
+
"src/types/loopView.ts",
|
|
35
|
+
"README.md",
|
|
36
|
+
"LICENSE",
|
|
37
|
+
"package.json",
|
|
38
|
+
"!src/web/**/*.stories.tsx"
|
|
11
39
|
],
|
|
12
40
|
"type": "module",
|
|
13
41
|
"main": "./dist/index.cjs",
|
|
@@ -30,21 +58,28 @@
|
|
|
30
58
|
"access": "public"
|
|
31
59
|
},
|
|
32
60
|
"dependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"@agimon-ai/doompi-
|
|
61
|
+
"@deepseek-ai/cordis": "4.0.2",
|
|
62
|
+
"@agimon-ai/doompi-extension-contracts": "0.0.1-alpha.68",
|
|
63
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.69",
|
|
64
|
+
"@agimon-ai/doompi-web-components": "0.0.1-alpha.28",
|
|
65
|
+
"@agimon-ai/doompi-web-contracts": "0.0.1-alpha.31"
|
|
35
66
|
},
|
|
36
67
|
"devDependencies": {
|
|
37
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
38
|
-
"@earendil-works/pi-tui": "0.
|
|
39
|
-
"@types/node": "26.
|
|
40
|
-
"@
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
68
|
+
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
69
|
+
"@earendil-works/pi-tui": "0.85.1",
|
|
70
|
+
"@types/node": "26.4.1",
|
|
71
|
+
"@types/react": "19.2.18",
|
|
72
|
+
"@types/react-dom": "19.2.7",
|
|
73
|
+
"@vitest/coverage-v8": "5.0.0",
|
|
74
|
+
"react": "19.2.8",
|
|
75
|
+
"react-dom": "19.2.8",
|
|
76
|
+
"tsdown": "0.23.0",
|
|
77
|
+
"typescript": "7.0.2",
|
|
78
|
+
"vitest": "5.0.0"
|
|
44
79
|
},
|
|
45
80
|
"peerDependencies": {
|
|
46
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
47
|
-
"@earendil-works/pi-tui": "0.
|
|
81
|
+
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
82
|
+
"@earendil-works/pi-tui": "0.85.1"
|
|
48
83
|
},
|
|
49
84
|
"peerDependenciesMeta": {
|
|
50
85
|
"@earendil-works/pi-coding-agent": {
|
|
@@ -57,6 +92,11 @@
|
|
|
57
92
|
"engines": {
|
|
58
93
|
"node": ">=22.19.0"
|
|
59
94
|
},
|
|
95
|
+
"doompiWeb": {
|
|
96
|
+
"pluginId": "loop",
|
|
97
|
+
"channels": [],
|
|
98
|
+
"client": "./src/exports/webClient.ts"
|
|
99
|
+
},
|
|
60
100
|
"pi": {
|
|
61
101
|
"extensions": [
|
|
62
102
|
"./dist/extensions/pi.mjs"
|
|
@@ -65,7 +105,7 @@
|
|
|
65
105
|
"scripts": {
|
|
66
106
|
"build": "tsdown",
|
|
67
107
|
"test": "vitest --run",
|
|
68
|
-
"typecheck": "tsc --noEmit",
|
|
108
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p src/web/tsconfig.json",
|
|
69
109
|
"lint": "oxlint . && oxfmt . --check",
|
|
70
110
|
"fixcode": "oxlint . --fix && oxfmt ."
|
|
71
111
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { webPlugin } from '../web/index.ts';
|
|
@@ -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.
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** The browser-only status that carries active Loop instances into the activity dock. */
|
|
2
|
+
export const LOOP_VIEW_STATUS_KEY = 'doom-loop-instances';
|
|
3
|
+
|
|
4
|
+
export type LoopStatusState = 'starting' | 'running' | 'stopping';
|
|
5
|
+
|
|
6
|
+
/** The small, serializable Loop view the cockpit needs for one activity row. */
|
|
7
|
+
export interface LoopStatusItem {
|
|
8
|
+
readonly instanceId: string;
|
|
9
|
+
readonly label: string;
|
|
10
|
+
readonly detail: string;
|
|
11
|
+
readonly state: LoopStatusState;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface LoopStatusSource {
|
|
15
|
+
readonly instanceId: string;
|
|
16
|
+
readonly launcherLabel: string;
|
|
17
|
+
readonly label?: string;
|
|
18
|
+
readonly detail?: string;
|
|
19
|
+
readonly state: LoopStatusState;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const ANSI = new RegExp(String.raw`\u001b\[[0-9;]*m`, 'gu');
|
|
23
|
+
|
|
24
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
25
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function isState(value: unknown): value is LoopStatusState {
|
|
29
|
+
return value === 'starting' || value === 'running' || value === 'stopping';
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** Projects runtime snapshots into the JSON status consumed by the web plugin. */
|
|
33
|
+
export function formatLoopStatusView(instances: readonly LoopStatusSource[]): string | undefined {
|
|
34
|
+
if (instances.length === 0) return undefined;
|
|
35
|
+
return JSON.stringify(
|
|
36
|
+
instances.map((instance) => ({
|
|
37
|
+
instanceId: instance.instanceId,
|
|
38
|
+
label: instance.label ?? instance.launcherLabel,
|
|
39
|
+
detail: instance.detail ?? instance.instanceId,
|
|
40
|
+
state: instance.state,
|
|
41
|
+
})),
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Reads a Loop status without letting malformed session data crash the activity dock. */
|
|
46
|
+
export function parseLoopStatusView(raw: string | undefined): LoopStatusItem[] | undefined {
|
|
47
|
+
if (raw === undefined) return undefined;
|
|
48
|
+
const text = raw.replace(ANSI, '').trim();
|
|
49
|
+
if (text === '') return undefined;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
const value: unknown = JSON.parse(text);
|
|
53
|
+
if (!Array.isArray(value) || value.length === 0) return undefined;
|
|
54
|
+
const items: LoopStatusItem[] = [];
|
|
55
|
+
for (const item of value) {
|
|
56
|
+
if (
|
|
57
|
+
!isRecord(item) ||
|
|
58
|
+
typeof item.instanceId !== 'string' ||
|
|
59
|
+
item.instanceId.trim() === '' ||
|
|
60
|
+
typeof item.label !== 'string' ||
|
|
61
|
+
item.label.trim() === '' ||
|
|
62
|
+
typeof item.detail !== 'string' ||
|
|
63
|
+
!isState(item.state)
|
|
64
|
+
) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
items.push({ instanceId: item.instanceId, label: item.label, detail: item.detail, state: item.state });
|
|
68
|
+
}
|
|
69
|
+
return items;
|
|
70
|
+
} catch {
|
|
71
|
+
return undefined;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { Button, Dot, type DotTone } from '@agimon-ai/doompi-web-components';
|
|
2
|
+
import type { WebPluginSlotProps } from '@agimon-ai/doompi-web-contracts';
|
|
3
|
+
import { LOOP_VIEW_STATUS_KEY, parseLoopStatusView, type LoopStatusState } from '../../types/loopView.ts';
|
|
4
|
+
|
|
5
|
+
const MANAGE_COMMAND = '/loops';
|
|
6
|
+
const DEFAULT_LOOP_COMMAND = '/loop doompi.default';
|
|
7
|
+
|
|
8
|
+
/** Starts the built-in prompt loop without opening a generic launcher chooser. */
|
|
9
|
+
export function DefaultLoopLauncher({
|
|
10
|
+
sessionId,
|
|
11
|
+
sendSessionFrame,
|
|
12
|
+
}: Pick<WebPluginSlotProps, 'sessionId' | 'sendSessionFrame'>) {
|
|
13
|
+
return (
|
|
14
|
+
<Button
|
|
15
|
+
variant="subtle"
|
|
16
|
+
size="xs"
|
|
17
|
+
data-testid="activity-loop-default-launch"
|
|
18
|
+
aria-label="launch default loop"
|
|
19
|
+
disabled={sessionId === null}
|
|
20
|
+
onClick={() => {
|
|
21
|
+
if (sessionId === null) return;
|
|
22
|
+
sendSessionFrame(sessionId, { type: 'prompt', message: DEFAULT_LOOP_COMMAND });
|
|
23
|
+
}}
|
|
24
|
+
className="text-2xs font-bold"
|
|
25
|
+
>
|
|
26
|
+
default loop
|
|
27
|
+
</Button>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function toneOf(state: LoopStatusState): DotTone {
|
|
32
|
+
if (state === 'running') return 'green';
|
|
33
|
+
if (state === 'stopping') return 'orange';
|
|
34
|
+
return 'yellow';
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Renders active recurring prompts contributed to the Loop activity section. */
|
|
38
|
+
export function LoopActivityItems({ statuses }: Pick<WebPluginSlotProps, 'statuses'>) {
|
|
39
|
+
const raw = statuses[LOOP_VIEW_STATUS_KEY];
|
|
40
|
+
const loops = parseLoopStatusView(raw);
|
|
41
|
+
const unavailable = raw !== undefined && raw.trim() !== '' && loops === undefined;
|
|
42
|
+
if (!loops && !unavailable) return null;
|
|
43
|
+
|
|
44
|
+
return loops ? (
|
|
45
|
+
<ul aria-label="active loops" className="flex flex-col gap-1">
|
|
46
|
+
{loops.map((loop) => (
|
|
47
|
+
<li
|
|
48
|
+
key={loop.instanceId}
|
|
49
|
+
data-testid={`activity-loop-${loop.instanceId}`}
|
|
50
|
+
data-loop-state={loop.state}
|
|
51
|
+
title={`${loop.label}: ${loop.detail}`}
|
|
52
|
+
className="flex min-w-0 flex-col gap-0.5 px-1 py-0.5"
|
|
53
|
+
>
|
|
54
|
+
<span className="flex min-w-0 items-center gap-1.5">
|
|
55
|
+
<Dot tone={toneOf(loop.state)} pulse={loop.state !== 'running'} />
|
|
56
|
+
<span className="min-w-0 flex-1 truncate text-xs font-bold text-doom-hi">{loop.label}</span>
|
|
57
|
+
<span className="shrink-0 text-2xs text-doom-faint">{loop.state}</span>
|
|
58
|
+
</span>
|
|
59
|
+
<span className="truncate pl-3 text-2xs text-doom-faint">{loop.detail}</span>
|
|
60
|
+
</li>
|
|
61
|
+
))}
|
|
62
|
+
</ul>
|
|
63
|
+
) : (
|
|
64
|
+
<p className="px-1 text-xs text-doom-faint">loop status unavailable</p>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Idle-safe Loop activity shell with extension registration and instance slots. */
|
|
69
|
+
export function LoopsActivitySection({ sessionId, renderSlot, sendSessionFrame }: WebPluginSlotProps) {
|
|
70
|
+
return (
|
|
71
|
+
<div data-testid="activity-loop-instances" className="flex flex-col gap-2">
|
|
72
|
+
<div className="flex flex-wrap items-center gap-1">
|
|
73
|
+
<DefaultLoopLauncher sessionId={sessionId} sendSessionFrame={sendSessionFrame} />
|
|
74
|
+
{renderSlot('loop.registration')}
|
|
75
|
+
</div>
|
|
76
|
+
{renderSlot('loop.items')}
|
|
77
|
+
<Button
|
|
78
|
+
variant="subtle"
|
|
79
|
+
size="xs"
|
|
80
|
+
data-testid="activity-loops-manage"
|
|
81
|
+
aria-label="manage loops"
|
|
82
|
+
disabled={sessionId === null}
|
|
83
|
+
onClick={() => {
|
|
84
|
+
if (sessionId === null) return;
|
|
85
|
+
sendSessionFrame(sessionId, { type: 'prompt', message: MANAGE_COMMAND });
|
|
86
|
+
}}
|
|
87
|
+
className="self-end text-2xs font-bold"
|
|
88
|
+
>
|
|
89
|
+
manage
|
|
90
|
+
</Button>
|
|
91
|
+
</div>
|
|
92
|
+
);
|
|
93
|
+
}
|
package/src/web/index.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { defineSlot, defineWebPlugin } from '@agimon-ai/doompi-web-contracts';
|
|
2
|
+
import { LOOP_VIEW_STATUS_KEY } from '../types/loopView.ts';
|
|
3
|
+
import { LoopActivityItems, LoopsActivitySection } from './components/LoopsActivitySection.tsx';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* This package's cockpit presence. The selection bar carries the minor mode,
|
|
7
|
+
* while the activity dock shows each loop the active session is scheduling.
|
|
8
|
+
*/
|
|
9
|
+
const LOOPS_GROUP = { key: 'l', label: 'loops', detail: 'recurring prompt loops' };
|
|
10
|
+
const LOOPS_ACTIVITY_SOURCE = {
|
|
11
|
+
subscribe: () => () => undefined,
|
|
12
|
+
// Keeping the scheduler's launcher visible does not mean a result is pending.
|
|
13
|
+
isActive: (_sessionId: string | null) => false,
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export const webPlugin = defineWebPlugin({
|
|
17
|
+
id: 'loop',
|
|
18
|
+
minorModes: [{ name: 'loop', keys: 'l l', statusKey: 'doom-loop', activityGroup: 'loops', order: 30 }],
|
|
19
|
+
activityGroups: [
|
|
20
|
+
{ name: 'loops', keys: 'l l', statusKey: LOOP_VIEW_STATUS_KEY, activeSource: LOOPS_ACTIVITY_SOURCE, order: 40 },
|
|
21
|
+
],
|
|
22
|
+
activitySections: [{ id: 'loops', component: LoopsActivitySection }],
|
|
23
|
+
slots: [defineSlot({ slot: 'loop.registration' }), defineSlot({ slot: 'loop.items' })],
|
|
24
|
+
fills: [{ slot: 'loop.items', id: 'instances', component: LoopActivityItems }],
|
|
25
|
+
// The TUI's SPC l s and SPC l l: both are slash commands, so both carry over.
|
|
26
|
+
leaderBindings: [
|
|
27
|
+
{
|
|
28
|
+
id: 'loop.start',
|
|
29
|
+
path: [LOOPS_GROUP, { key: 's', label: 'start', detail: 'begin a recurring loop' }],
|
|
30
|
+
command: 'loop',
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
id: 'loop.list',
|
|
34
|
+
path: [LOOPS_GROUP, { key: 'l', label: 'list', detail: 'loops in this session' }],
|
|
35
|
+
command: 'loops',
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2023", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "Bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"noImplicitAny": true,
|
|
10
|
+
"noUnusedLocals": true,
|
|
11
|
+
"noUnusedParameters": true,
|
|
12
|
+
"noImplicitReturns": true,
|
|
13
|
+
"noFallthroughCasesInSwitch": true,
|
|
14
|
+
"noEmit": true,
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"esModuleInterop": true,
|
|
17
|
+
"skipLibCheck": true,
|
|
18
|
+
"allowImportingTsExtensions": true,
|
|
19
|
+
"forceConsistentCasingInFileNames": true,
|
|
20
|
+
"types": ["node"]
|
|
21
|
+
},
|
|
22
|
+
"include": [".", "../types", "../exports/webClient.ts", "../../tests/web"]
|
|
23
|
+
}
|
package/dist/constants.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
exports.ACTIVE_MODE_COLOR=`warning`,exports.LIST_COMMAND_DESCRIPTION=`List and stop active loops`,exports.LIST_COMMAND_NAME=`loops`,exports.MODE_STATUS_ID=`loop.active`,exports.NOTIFY_INFO_LEVEL=`info`,exports.PACKAGE_SOURCE=`@agimon-ai/doompi-loop`,exports.START_COMMAND_DESCRIPTION=`Start a registered loop`,exports.START_COMMAND_NAME=`loop`,exports.STATUS_KEY=`doom-loop`;
|
|
2
|
-
//# sourceMappingURL=constants.cjs.map
|
package/dist/constants.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants.cjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["export const PACKAGE_SOURCE = '@agimon-ai/doompi-loop';\nexport const START_COMMAND_NAME = 'loop';\nexport const LIST_COMMAND_NAME = 'loops';\nexport const START_COMMAND_DESCRIPTION = 'Start a registered loop';\nexport const LIST_COMMAND_DESCRIPTION = 'List and stop active loops';\nexport const LOOPS_GROUP_ORDER = 60;\nexport const STATUS_KEY = 'doom-loop';\nexport const MODE_STATUS_ID = 'loop.active';\nexport const ACTIVE_MODE_COLOR = 'warning';\nexport const NOTIFY_INFO_LEVEL = 'info';\n"],"mappings":""}
|
package/dist/constants.mjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=`@agimon-ai/doompi-loop`,t=`loop`,n=`loops`,r=`Start a registered loop`,i=`List and stop active loops`,a=`doom-loop`,o=`loop.active`,s=`warning`,c=`info`;export{s as ACTIVE_MODE_COLOR,i as LIST_COMMAND_DESCRIPTION,n as LIST_COMMAND_NAME,o as MODE_STATUS_ID,c as NOTIFY_INFO_LEVEL,e as PACKAGE_SOURCE,r as START_COMMAND_DESCRIPTION,t as START_COMMAND_NAME,a as STATUS_KEY};
|
|
2
|
-
//# sourceMappingURL=constants.mjs.map
|
package/dist/constants.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"constants.mjs","names":[],"sources":["../src/constants.ts"],"sourcesContent":["export const PACKAGE_SOURCE = '@agimon-ai/doompi-loop';\nexport const START_COMMAND_NAME = 'loop';\nexport const LIST_COMMAND_NAME = 'loops';\nexport const START_COMMAND_DESCRIPTION = 'Start a registered loop';\nexport const LIST_COMMAND_DESCRIPTION = 'List and stop active loops';\nexport const LOOPS_GROUP_ORDER = 60;\nexport const STATUS_KEY = 'doom-loop';\nexport const MODE_STATUS_ID = 'loop.active';\nexport const ACTIVE_MODE_COLOR = 'warning';\nexport const NOTIFY_INFO_LEVEL = 'info';\n"],"mappings":"AAAA,MAAa,EAAiB,yBACjB,EAAqB,OACrB,EAAoB,QACpB,EAA4B,0BAC5B,EAA2B,6BAE3B,EAAa,YACb,EAAiB,cACjB,EAAoB,UACpB,EAAoB"}
|
package/dist/defaultLauncher.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./launcher.cjs"),t=3600;function n(e){let n=Number(e);if(!Number.isInteger(n))return`Interval must be a whole number of seconds, got ${e}.`;if(n<30||n>t)return`Interval must be between 30 and ${t} seconds.`}async function r(e,t){let r=(await e.editor(`Loop prompt`,``))?.trim();if(!r||t.aborted)return;let i=await e.input(`Loop interval in seconds`,`Default: 300s`);if(i===void 0||t.aborted)return;let a=i.trim()||`300`,o=n(a);if(o){e.notify(o,`warning`);return}return{prompt:r,intervalSeconds:Number(a)}}function i(e){let t=e.replaceAll(/\s+/gu,` `).trim();return t.length>80?`${t.slice(0,79)}…`:t}function a(t){let n=new Set,a=e=>{if(!e.stopped){if(!e.context.isIdle()){n.add(e);return}n.delete(e);try{t.sendUserMessage(e.prompt)}catch(t){e.context.ui.notify(`Default loop pass could not start: ${t instanceof Error?t.message:String(t)}`,`warning`)}}};return t.on(`agent_settled`,()=>{let e=n.values().next().value;e&&a(e)}),{register(t){return e.registerLoopLauncher(t.sessionManager.getSessionId(),{id:`doompi.default`,source:`@agimon-ai/doompi-loop`,label:`Default loop`,description:`Run your own prompt in this session on an interval`,async launch({instanceId:e,signal:o}){let s=await r(t.ui,o);if(!s||o.aborted)return;let c={...s,context:t,stopped:!1};return c.timer=setInterval(()=>a(c),c.intervalSeconds*1e3),c.timer.unref?.(),a(c),{instanceId:e,label:`Default loop`,detail:`every ${c.intervalSeconds}s · ${i(c.prompt)}`,stop(){c.stopped||(c.stopped=!0,c.timer&&clearInterval(c.timer),c.timer=void 0,n.delete(c))}}}})}}}exports.createDefaultLoopLauncher=a;
|
|
2
|
-
//# sourceMappingURL=defaultLauncher.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"defaultLauncher.cjs","names":["registerLoopLauncher"],"sources":["../src/defaultLauncher.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext, ExtensionUIContext } from '@earendil-works/pi-coding-agent';\nimport { registerLoopLauncher, type LoopLauncherRegistration, type StoppableLoop } from './launcher.ts';\n\nconst DEFAULT_LAUNCHER_ID = 'doompi.default';\nconst DEFAULT_INTERVAL_SECONDS = 300;\nconst MIN_INTERVAL_SECONDS = 30;\nconst MAX_INTERVAL_SECONDS = 3600;\nconst MILLISECONDS_PER_SECOND = 1000;\nconst PROMPT_PREVIEW_LENGTH = 80;\n\ninterface PreparedDefaultLoop {\n readonly prompt: string;\n readonly intervalSeconds: number;\n}\n\ninterface ActiveDefaultLoop extends PreparedDefaultLoop {\n readonly context: ExtensionContext;\n timer?: ReturnType<typeof setInterval>;\n stopped: boolean;\n}\n\nexport interface DefaultLoopLauncher {\n register(ctx: ExtensionContext): LoopLauncherRegistration;\n}\n\nfunction intervalError(raw: string): string | undefined {\n const seconds = Number(raw);\n if (!Number.isInteger(seconds)) return `Interval must be a whole number of seconds, got ${raw}.`;\n if (seconds < MIN_INTERVAL_SECONDS || seconds > MAX_INTERVAL_SECONDS) {\n return `Interval must be between ${MIN_INTERVAL_SECONDS} and ${MAX_INTERVAL_SECONDS} seconds.`;\n }\n return undefined;\n}\n\nasync function prepareDefaultLoop(\n ui: ExtensionUIContext,\n signal: AbortSignal,\n): Promise<PreparedDefaultLoop | undefined> {\n const prompt = (await ui.editor('Loop prompt', ''))?.trim();\n if (!prompt || signal.aborted) return undefined;\n\n const rawInterval = await ui.input('Loop interval in seconds', `Default: ${DEFAULT_INTERVAL_SECONDS}s`);\n if (rawInterval === undefined || signal.aborted) return undefined;\n const normalizedInterval = rawInterval.trim() || String(DEFAULT_INTERVAL_SECONDS);\n const error = intervalError(normalizedInterval);\n if (error) {\n ui.notify(error, 'warning');\n return undefined;\n }\n\n return { prompt, intervalSeconds: Number(normalizedInterval) };\n}\n\nfunction promptPreview(prompt: string): string {\n const oneLine = prompt.replaceAll(/\\s+/gu, ' ').trim();\n return oneLine.length > PROMPT_PREVIEW_LENGTH ? `${oneLine.slice(0, PROMPT_PREVIEW_LENGTH - 1)}…` : oneLine;\n}\n\nexport function createDefaultLoopLauncher(pi: ExtensionAPI): DefaultLoopLauncher {\n const pending = new Set<ActiveDefaultLoop>();\n\n const requestPass = (loop: ActiveDefaultLoop): void => {\n if (loop.stopped) return;\n if (!loop.context.isIdle()) {\n pending.add(loop);\n return;\n }\n\n pending.delete(loop);\n try {\n pi.sendUserMessage(loop.prompt);\n } catch (error) {\n loop.context.ui.notify(\n `Default loop pass could not start: ${error instanceof Error ? error.message : String(error)}`,\n 'warning',\n );\n }\n };\n\n pi.on('agent_settled', () => {\n const next = pending.values().next().value;\n if (next) requestPass(next);\n });\n\n return {\n register(ctx) {\n return registerLoopLauncher(ctx.sessionManager.getSessionId(), {\n id: DEFAULT_LAUNCHER_ID,\n source: '@agimon-ai/doompi-loop',\n label: 'Default loop',\n description: 'Run your own prompt in this session on an interval',\n async launch({ instanceId, signal }) {\n const prepared = await prepareDefaultLoop(ctx.ui, signal);\n if (!prepared || signal.aborted) return undefined;\n\n const loop: ActiveDefaultLoop = { ...prepared, context: ctx, stopped: false };\n loop.timer = setInterval(() => requestPass(loop), loop.intervalSeconds * MILLISECONDS_PER_SECOND);\n loop.timer.unref?.();\n requestPass(loop);\n\n const handle: StoppableLoop = {\n instanceId,\n label: 'Default loop',\n detail: `every ${loop.intervalSeconds}s · ${promptPreview(loop.prompt)}`,\n stop() {\n if (loop.stopped) return;\n loop.stopped = true;\n if (loop.timer) clearInterval(loop.timer);\n loop.timer = undefined;\n pending.delete(loop);\n },\n };\n return handle;\n },\n });\n },\n };\n}\n"],"mappings":"kCAMM,EAAuB,KAmB7B,SAAS,EAAc,EAAiC,CACtD,IAAM,EAAU,OAAO,CAAG,EAC1B,GAAI,CAAC,OAAO,UAAU,CAAO,EAAG,MAAO,mDAAmD,EAAI,GAC9F,GAAI,EAAU,IAAwB,EAAU,EAC9C,MAAO,mCAAwD,EAAqB,UAGxF,CAEA,eAAe,EACb,EACA,EAC0C,CAC1C,IAAM,GAAU,MAAM,EAAG,OAAO,cAAe,EAAE,EAAA,EAAI,KAAK,EAC1D,GAAI,CAAC,GAAU,EAAO,QAAS,OAE/B,IAAM,EAAc,MAAM,EAAG,MAAM,2BAA4B,eAAuC,EACtG,GAAI,IAAgB,IAAA,IAAa,EAAO,QAAS,OACjD,IAAM,EAAqB,EAAY,KAAK,GAAK,MAC3C,EAAQ,EAAc,CAAkB,EAC9C,GAAI,EAAO,CACT,EAAG,OAAO,EAAO,SAAS,EAC1B,MACF,CAEA,MAAO,CAAE,SAAQ,gBAAiB,OAAO,CAAkB,CAAE,CAC/D,CAEA,SAAS,EAAc,EAAwB,CAC7C,IAAM,EAAU,EAAO,WAAW,QAAS,GAAG,CAAC,CAAC,KAAK,EACrD,OAAO,EAAQ,OAAS,GAAwB,GAAG,EAAQ,MAAM,EAAG,EAAyB,EAAE,GAAK,CACtG,CAEA,SAAgB,EAA0B,EAAuC,CAC/E,IAAM,EAAU,IAAI,IAEd,EAAe,GAAkC,CACjD,MAAK,QACT,IAAI,CAAC,EAAK,QAAQ,OAAO,EAAG,CAC1B,EAAQ,IAAI,CAAI,EAChB,MACF,CAEA,EAAQ,OAAO,CAAI,EACnB,GAAI,CACF,EAAG,gBAAgB,EAAK,MAAM,CAChC,OAAS,EAAO,CACd,EAAK,QAAQ,GAAG,OACd,sCAAsC,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,IAC3F,SACF,CACF,CAVA,CAWF,EAOA,OALA,EAAG,GAAG,oBAAuB,CAC3B,IAAM,EAAO,EAAQ,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,MACjC,GAAM,EAAY,CAAI,CAC5B,CAAC,EAEM,CACL,SAAS,EAAK,CACZ,OAAOA,EAAAA,qBAAqB,EAAI,eAAe,aAAa,EAAG,CAC7D,GAAI,iBACJ,OAAQ,yBACR,MAAO,eACP,YAAa,qDACb,MAAM,OAAO,CAAE,aAAY,UAAU,CACnC,IAAM,EAAW,MAAM,EAAmB,EAAI,GAAI,CAAM,EACxD,GAAI,CAAC,GAAY,EAAO,QAAS,OAEjC,IAAM,EAA0B,CAAE,GAAG,EAAU,QAAS,EAAK,QAAS,EAAM,EAiB5E,MAhBA,GAAK,MAAQ,gBAAkB,EAAY,CAAI,EAAG,EAAK,gBAAkB,GAAuB,EAChG,EAAK,MAAM,QAAQ,EACnB,EAAY,CAAI,EAcT,CAXL,aACA,MAAO,eACP,OAAQ,SAAS,EAAK,gBAAgB,MAAM,EAAc,EAAK,MAAM,IACrE,MAAO,CACD,EAAK,UACT,EAAK,QAAU,GACX,EAAK,OAAO,cAAc,EAAK,KAAK,EACxC,EAAK,MAAQ,IAAA,GACb,EAAQ,OAAO,CAAI,EACrB,CAEU,CACd,CACF,CAAC,CACH,CACF,CACF"}
|