@agimon-ai/doompi-loop 0.0.1-alpha.7 → 0.0.1-alpha.71
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 +83 -21
- package/dist/_virtual/_rolldown/runtime.cjs +1 -0
- package/dist/api-contracts.cjs +1 -0
- package/dist/api-contracts.d.cts +2 -0
- package/dist/api-contracts.d.mts +2 -0
- package/dist/api-contracts.mjs +1 -0
- package/dist/extensions/pi.cjs +1 -1
- package/dist/extensions/pi.cjs.map +1 -1
- package/dist/extensions/pi.d.cts +5 -2
- package/dist/extensions/pi.d.cts.map +1 -0
- package/dist/extensions/pi.d.mts +5 -2
- package/dist/extensions/pi.d.mts.map +1 -0
- package/dist/extensions/pi.mjs +1 -1
- package/dist/extensions/pi.mjs.map +1 -1
- package/dist/extensions/server.cjs +2 -0
- package/dist/extensions/server.cjs.map +1 -0
- package/dist/extensions/server.d.cts +5 -0
- package/dist/extensions/server.d.cts.map +1 -0
- package/dist/extensions/server.d.mts +5 -0
- package/dist/extensions/server.d.mts.map +1 -0
- package/dist/extensions/server.mjs +2 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/index.cjs +0 -1
- package/dist/index.d.cts +2 -4
- package/dist/index.d.mts +2 -4
- package/dist/index.mjs +1 -1
- package/dist/loopLaunchers.cjs +1 -0
- package/dist/loopLaunchers.d.cts +2 -0
- package/dist/loopLaunchers.d.mts +2 -0
- package/dist/loopLaunchers.mjs +1 -0
- package/dist/src/constants/loop.cjs +2 -0
- package/dist/src/constants/loop.cjs.map +1 -0
- package/dist/src/constants/loop.mjs +2 -0
- package/dist/src/constants/loop.mjs.map +1 -0
- package/dist/src/constants/piLoop.cjs +2 -0
- package/dist/src/constants/piLoop.cjs.map +1 -0
- package/dist/src/constants/piLoop.mjs +2 -0
- package/dist/src/constants/piLoop.mjs.map +1 -0
- package/dist/src/controllers/loopCommand.cjs +2 -0
- package/dist/src/controllers/loopCommand.cjs.map +1 -0
- package/dist/src/controllers/loopCommand.mjs +2 -0
- package/dist/src/controllers/loopCommand.mjs.map +1 -0
- package/dist/src/controllers/serverRuntime.cjs +2 -0
- package/dist/src/controllers/serverRuntime.cjs.map +1 -0
- package/dist/src/controllers/serverRuntime.mjs +2 -0
- package/dist/src/controllers/serverRuntime.mjs.map +1 -0
- package/dist/src/schemas/apiContracts.cjs +2 -0
- package/dist/src/schemas/apiContracts.cjs.map +1 -0
- package/dist/src/schemas/apiContracts.d.cts +53 -0
- package/dist/src/schemas/apiContracts.d.cts.map +1 -0
- package/dist/src/schemas/apiContracts.d.mts +53 -0
- package/dist/src/schemas/apiContracts.d.mts.map +1 -0
- package/dist/src/schemas/apiContracts.mjs +2 -0
- package/dist/src/schemas/apiContracts.mjs.map +1 -0
- package/dist/src/schemas/loopLaunchers.cjs +2 -0
- package/dist/src/schemas/loopLaunchers.cjs.map +1 -0
- package/dist/src/schemas/loopLaunchers.d.cts +60 -0
- package/dist/src/schemas/loopLaunchers.d.cts.map +1 -0
- package/dist/src/schemas/loopLaunchers.d.mts +60 -0
- package/dist/src/schemas/loopLaunchers.d.mts.map +1 -0
- package/dist/src/schemas/loopLaunchers.mjs +2 -0
- package/dist/src/schemas/loopLaunchers.mjs.map +1 -0
- package/dist/src/services/defaultLoopLauncher/index.cjs +2 -0
- package/dist/src/services/defaultLoopLauncher/index.cjs.map +1 -0
- package/dist/src/services/defaultLoopLauncher/index.mjs +2 -0
- package/dist/src/services/defaultLoopLauncher/index.mjs.map +1 -0
- package/dist/src/services/loopLaunchers/index.cjs +2 -0
- package/dist/src/services/loopLaunchers/index.cjs.map +1 -0
- package/dist/src/services/loopLaunchers/index.mjs +2 -0
- package/dist/src/services/loopLaunchers/index.mjs.map +1 -0
- package/dist/src/services/loopResource/index.cjs +2 -0
- package/dist/src/services/loopResource/index.cjs.map +1 -0
- package/dist/src/services/loopResource/index.mjs +2 -0
- package/dist/src/services/loopResource/index.mjs.map +1 -0
- package/dist/src/tui/leader.cjs +2 -0
- package/dist/src/tui/leader.cjs.map +1 -0
- package/dist/src/tui/leader.mjs +2 -0
- package/dist/src/tui/leader.mjs.map +1 -0
- package/dist/src/tui/loopListOverlay.cjs +2 -0
- package/dist/src/tui/loopListOverlay.cjs.map +1 -0
- package/dist/src/tui/loopListOverlay.mjs +2 -0
- package/dist/src/tui/loopListOverlay.mjs.map +1 -0
- package/dist/src/tui/loopRuntime.cjs +2 -0
- package/dist/src/tui/loopRuntime.cjs.map +1 -0
- package/dist/src/tui/loopRuntime.mjs +2 -0
- package/dist/src/tui/loopRuntime.mjs.map +1 -0
- package/dist/src/tui/startLoopOverlay.cjs +2 -0
- package/dist/src/tui/startLoopOverlay.cjs.map +1 -0
- package/dist/src/tui/startLoopOverlay.mjs +2 -0
- package/dist/src/tui/startLoopOverlay.mjs.map +1 -0
- package/dist/src/types/loopView.cjs +2 -0
- package/dist/src/types/loopView.cjs.map +1 -0
- package/dist/src/types/loopView.mjs +2 -0
- package/dist/src/types/loopView.mjs.map +1 -0
- package/llms.txt +9 -0
- package/package.json +80 -16
- package/src/extensions/web.ts +41 -0
- package/src/prompts/doompi-use-loop/SKILL.md +24 -0
- package/src/types/loopView.ts +73 -0
- package/src/web/components/LoopsActivitySection.tsx +94 -0
- package/src/web/tsconfig.json +23 -0
- package/dist/commands/loopCommand.cjs +0 -2
- package/dist/commands/loopCommand.cjs.map +0 -1
- package/dist/commands/loopCommand.mjs +0 -2
- package/dist/commands/loopCommand.mjs.map +0 -1
- 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/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
- package/dist/tui/loopListOverlay.cjs +0 -2
- package/dist/tui/loopListOverlay.cjs.map +0 -1
- package/dist/tui/loopListOverlay.mjs +0 -2
- package/dist/tui/loopListOverlay.mjs.map +0 -1
- package/dist/tui/startLoopOverlay.cjs +0 -2
- package/dist/tui/startLoopOverlay.cjs.map +0 -1
- package/dist/tui/startLoopOverlay.mjs +0 -2
- package/dist/tui/startLoopOverlay.mjs.map +0 -1
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.71",
|
|
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/extensions/web.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",
|
|
@@ -24,27 +52,44 @@
|
|
|
24
52
|
"import": "./dist/extensions/pi.mjs",
|
|
25
53
|
"require": "./dist/extensions/pi.cjs"
|
|
26
54
|
},
|
|
27
|
-
"./
|
|
55
|
+
"./extensions/server": {
|
|
56
|
+
"types": "./dist/extensions/server.d.mts",
|
|
57
|
+
"import": "./dist/extensions/server.mjs",
|
|
58
|
+
"require": "./dist/extensions/server.cjs"
|
|
59
|
+
},
|
|
60
|
+
"./package.json": "./package.json",
|
|
61
|
+
"./loop-launchers": {
|
|
62
|
+
"types": "./dist/loopLaunchers.d.mts",
|
|
63
|
+
"import": "./dist/loopLaunchers.mjs",
|
|
64
|
+
"require": "./dist/loopLaunchers.cjs"
|
|
65
|
+
}
|
|
28
66
|
},
|
|
29
67
|
"publishConfig": {
|
|
30
68
|
"access": "public"
|
|
31
69
|
},
|
|
32
70
|
"dependencies": {
|
|
33
|
-
"@
|
|
34
|
-
"@agimon-ai/doompi-
|
|
71
|
+
"@deepseek-ai/cordis": "4.0.2",
|
|
72
|
+
"@agimon-ai/doompi-core": "0.0.1-alpha.70",
|
|
73
|
+
"@agimon-ai/doompi-ui": "0.0.1-alpha.70",
|
|
74
|
+
"@agimon-ai/doompi-minor-mode": "0.0.1-alpha.70",
|
|
75
|
+
"@agimon-ai/doompi-web-components": "0.0.1-alpha.29"
|
|
35
76
|
},
|
|
36
77
|
"devDependencies": {
|
|
37
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
38
|
-
"@earendil-works/pi-tui": "0.
|
|
39
|
-
"@types/node": "26.
|
|
40
|
-
"@
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"
|
|
78
|
+
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
79
|
+
"@earendil-works/pi-tui": "0.85.1",
|
|
80
|
+
"@types/node": "26.4.1",
|
|
81
|
+
"@types/react": "19.2.18",
|
|
82
|
+
"@types/react-dom": "19.2.7",
|
|
83
|
+
"@vitest/coverage-v8": "5.0.0",
|
|
84
|
+
"react": "19.2.8",
|
|
85
|
+
"react-dom": "19.2.8",
|
|
86
|
+
"tsdown": "0.23.0",
|
|
87
|
+
"typescript": "7.0.2",
|
|
88
|
+
"vitest": "5.0.0"
|
|
44
89
|
},
|
|
45
90
|
"peerDependencies": {
|
|
46
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
47
|
-
"@earendil-works/pi-tui": "0.
|
|
91
|
+
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
92
|
+
"@earendil-works/pi-tui": "0.85.1"
|
|
48
93
|
},
|
|
49
94
|
"peerDependenciesMeta": {
|
|
50
95
|
"@earendil-works/pi-coding-agent": {
|
|
@@ -57,6 +102,25 @@
|
|
|
57
102
|
"engines": {
|
|
58
103
|
"node": ">=22.19.0"
|
|
59
104
|
},
|
|
105
|
+
"doompiServer": {
|
|
106
|
+
"entry": "./src/extensions/server.ts",
|
|
107
|
+
"dist": "./dist/extensions/server.mjs",
|
|
108
|
+
"scopes": [
|
|
109
|
+
"session"
|
|
110
|
+
],
|
|
111
|
+
"contracts": {
|
|
112
|
+
"entry": "./src/exports/apiContracts.ts",
|
|
113
|
+
"dist": "./dist/api-contracts.mjs"
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"doompiWeb": {
|
|
117
|
+
"pluginId": "loop",
|
|
118
|
+
"channels": [],
|
|
119
|
+
"client": "./src/extensions/web.ts",
|
|
120
|
+
"scopes": [
|
|
121
|
+
"session"
|
|
122
|
+
]
|
|
123
|
+
},
|
|
60
124
|
"pi": {
|
|
61
125
|
"extensions": [
|
|
62
126
|
"./dist/extensions/pi.mjs"
|
|
@@ -65,7 +129,7 @@
|
|
|
65
129
|
"scripts": {
|
|
66
130
|
"build": "tsdown",
|
|
67
131
|
"test": "vitest --run",
|
|
68
|
-
"typecheck": "tsc --noEmit",
|
|
132
|
+
"typecheck": "tsc --noEmit && tsc --noEmit -p src/web/tsconfig.json",
|
|
69
133
|
"lint": "oxlint . && oxfmt . --check",
|
|
70
134
|
"fixcode": "oxlint . --fix && oxfmt ."
|
|
71
135
|
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { defineSlot, defineWebPlugin } from '@agimon-ai/doompi-core/web';
|
|
2
|
+
|
|
3
|
+
import { LOOP_VIEW_STATUS_KEY } from '../types/loopView';
|
|
4
|
+
import { LoopActivityItems, LoopsActivitySection } from '../web/components/LoopsActivitySection';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* This package's cockpit presence. The selection bar carries the minor mode,
|
|
8
|
+
* while the activity dock shows each loop the active session is scheduling.
|
|
9
|
+
*/
|
|
10
|
+
const LOOPS_GROUP = { key: 'l', label: 'loops', detail: 'recurring prompt loops' };
|
|
11
|
+
const LOOPS_ACTIVITY_SOURCE = {
|
|
12
|
+
subscribe: () => () => undefined,
|
|
13
|
+
// Keeping the scheduler's launcher visible does not mean a result is pending.
|
|
14
|
+
isActive: (_sessionId: string | null) => false,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const webPlugin = defineWebPlugin({
|
|
18
|
+
id: 'loop',
|
|
19
|
+
session: {
|
|
20
|
+
minorModes: [{ name: 'loop', keys: 'l l', statusKey: 'doom-loop', activityGroup: 'loops', order: 30 }],
|
|
21
|
+
activityGroups: [
|
|
22
|
+
{ name: 'loops', keys: 'l l', statusKey: LOOP_VIEW_STATUS_KEY, activeSource: LOOPS_ACTIVITY_SOURCE, order: 40 },
|
|
23
|
+
],
|
|
24
|
+
activitySections: [{ id: 'loops', component: LoopsActivitySection }],
|
|
25
|
+
slots: [defineSlot({ slot: 'loop.registration' }), defineSlot({ slot: 'loop.items' })],
|
|
26
|
+
fills: [{ slot: 'loop.items', id: 'instances', component: LoopActivityItems }],
|
|
27
|
+
// The TUI's SPC l s and SPC l l: both are slash commands, so both carry over.
|
|
28
|
+
leaderBindings: [
|
|
29
|
+
{
|
|
30
|
+
id: 'loop.start',
|
|
31
|
+
path: [LOOPS_GROUP, { key: 's', label: 'start', detail: 'begin a recurring loop' }],
|
|
32
|
+
command: 'loop',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
id: 'loop.list',
|
|
36
|
+
path: [LOOPS_GROUP, { key: 'l', label: 'list', detail: 'loops in this session' }],
|
|
37
|
+
command: 'loops',
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -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,94 @@
|
|
|
1
|
+
import type { WebPluginSlotProps } from '@agimon-ai/doompi-core/web';
|
|
2
|
+
import { Button, Dot, type DotTone } from '@agimon-ai/doompi-web-components';
|
|
3
|
+
|
|
4
|
+
import { LOOP_VIEW_STATUS_KEY, parseLoopStatusView, type LoopStatusState } from '../../types/loopView';
|
|
5
|
+
|
|
6
|
+
const MANAGE_COMMAND = '/loops';
|
|
7
|
+
const DEFAULT_LOOP_COMMAND = '/loop doompi.default';
|
|
8
|
+
|
|
9
|
+
/** Starts the built-in prompt loop without opening a generic launcher chooser. */
|
|
10
|
+
export function DefaultLoopLauncher({
|
|
11
|
+
sessionId,
|
|
12
|
+
sendSessionFrame,
|
|
13
|
+
}: Pick<WebPluginSlotProps, 'sessionId' | 'sendSessionFrame'>) {
|
|
14
|
+
return (
|
|
15
|
+
<Button
|
|
16
|
+
variant="subtle"
|
|
17
|
+
size="xs"
|
|
18
|
+
data-testid="activity-loop-default-launch"
|
|
19
|
+
aria-label="launch default loop"
|
|
20
|
+
disabled={sessionId === null}
|
|
21
|
+
onClick={() => {
|
|
22
|
+
if (sessionId === null) return;
|
|
23
|
+
sendSessionFrame(sessionId, { type: 'prompt', message: DEFAULT_LOOP_COMMAND });
|
|
24
|
+
}}
|
|
25
|
+
className="text-2xs font-bold"
|
|
26
|
+
>
|
|
27
|
+
default loop
|
|
28
|
+
</Button>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function toneOf(state: LoopStatusState): DotTone {
|
|
33
|
+
if (state === 'running') return 'green';
|
|
34
|
+
if (state === 'stopping') return 'orange';
|
|
35
|
+
return 'yellow';
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** Renders active recurring prompts contributed to the Loop activity section. */
|
|
39
|
+
export function LoopActivityItems({ statuses }: Pick<WebPluginSlotProps, 'statuses'>) {
|
|
40
|
+
const raw = statuses[LOOP_VIEW_STATUS_KEY];
|
|
41
|
+
const loops = parseLoopStatusView(raw);
|
|
42
|
+
const unavailable = raw !== undefined && raw.trim() !== '' && loops === undefined;
|
|
43
|
+
if (!loops && !unavailable) return null;
|
|
44
|
+
|
|
45
|
+
return loops ? (
|
|
46
|
+
<ul aria-label="active loops" className="flex flex-col gap-1">
|
|
47
|
+
{loops.map((loop) => (
|
|
48
|
+
<li
|
|
49
|
+
key={loop.instanceId}
|
|
50
|
+
data-testid={`activity-loop-${loop.instanceId}`}
|
|
51
|
+
data-loop-state={loop.state}
|
|
52
|
+
title={`${loop.label}: ${loop.detail}`}
|
|
53
|
+
className="flex min-w-0 flex-col gap-0.5 px-1 py-0.5"
|
|
54
|
+
>
|
|
55
|
+
<span className="flex min-w-0 items-center gap-1.5">
|
|
56
|
+
<Dot tone={toneOf(loop.state)} pulse={loop.state !== 'running'} />
|
|
57
|
+
<span className="min-w-0 flex-1 truncate text-xs font-bold text-doom-hi">{loop.label}</span>
|
|
58
|
+
<span className="shrink-0 text-2xs text-doom-faint">{loop.state}</span>
|
|
59
|
+
</span>
|
|
60
|
+
<span className="truncate pl-3 text-2xs text-doom-faint">{loop.detail}</span>
|
|
61
|
+
</li>
|
|
62
|
+
))}
|
|
63
|
+
</ul>
|
|
64
|
+
) : (
|
|
65
|
+
<p className="px-1 text-xs text-doom-faint">loop status unavailable</p>
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Idle-safe Loop activity shell with extension registration and instance slots. */
|
|
70
|
+
export function LoopsActivitySection({ sessionId, renderSlot, sendSessionFrame }: WebPluginSlotProps) {
|
|
71
|
+
return (
|
|
72
|
+
<div data-testid="activity-loop-instances" className="flex flex-col gap-2">
|
|
73
|
+
<div className="flex flex-wrap items-center gap-1">
|
|
74
|
+
<DefaultLoopLauncher sessionId={sessionId} sendSessionFrame={sendSessionFrame} />
|
|
75
|
+
{renderSlot('loop.registration')}
|
|
76
|
+
</div>
|
|
77
|
+
{renderSlot('loop.items')}
|
|
78
|
+
<Button
|
|
79
|
+
variant="subtle"
|
|
80
|
+
size="xs"
|
|
81
|
+
data-testid="activity-loops-manage"
|
|
82
|
+
aria-label="manage loops"
|
|
83
|
+
disabled={sessionId === null}
|
|
84
|
+
onClick={() => {
|
|
85
|
+
if (sessionId === null) return;
|
|
86
|
+
sendSessionFrame(sessionId, { type: 'prompt', message: MANAGE_COMMAND });
|
|
87
|
+
}}
|
|
88
|
+
className="self-end text-2xs font-bold"
|
|
89
|
+
>
|
|
90
|
+
manage
|
|
91
|
+
</Button>
|
|
92
|
+
</div>
|
|
93
|
+
);
|
|
94
|
+
}
|
|
@@ -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", "../extensions/web.ts", "../../tests/web"]
|
|
23
|
+
}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("../constants.cjs");function t(t,n){t.registerCommand(e.START_COMMAND_NAME,{description:e.START_COMMAND_DESCRIPTION,handler:async(e,t)=>n.start(t)}),t.registerCommand(e.LIST_COMMAND_NAME,{description:e.LIST_COMMAND_DESCRIPTION,handler:async(e,t)=>n.list(t)})}exports.registerCommands=t;
|
|
2
|
-
//# sourceMappingURL=loopCommand.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loopCommand.cjs","names":["START_COMMAND_NAME","START_COMMAND_DESCRIPTION","LIST_COMMAND_NAME","LIST_COMMAND_DESCRIPTION"],"sources":["../../src/commands/loopCommand.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n LIST_COMMAND_DESCRIPTION,\n LIST_COMMAND_NAME,\n START_COMMAND_DESCRIPTION,\n START_COMMAND_NAME,\n} from '../constants.ts';\n\nexport interface LoopCommandHandlers {\n start(ctx: ExtensionContext): Promise<void>;\n list(ctx: ExtensionContext): Promise<void>;\n}\n\nexport function registerCommands(pi: ExtensionAPI, handlers: LoopCommandHandlers): void {\n pi.registerCommand(START_COMMAND_NAME, {\n description: START_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.start(ctx),\n });\n pi.registerCommand(LIST_COMMAND_NAME, {\n description: LIST_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.list(ctx),\n });\n}\n"],"mappings":"oCAaA,SAAgB,EAAiB,EAAkB,EAAqC,CACtF,EAAG,gBAAgBA,EAAAA,mBAAoB,CACrC,YAAaC,EAAAA,0BACb,QAAS,MAAO,EAAO,IAAQ,EAAS,MAAM,CAAG,CACnD,CAAC,EACD,EAAG,gBAAgBC,EAAAA,kBAAmB,CACpC,YAAaC,EAAAA,yBACb,QAAS,MAAO,EAAO,IAAQ,EAAS,KAAK,CAAG,CAClD,CAAC,CACH"}
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{LIST_COMMAND_DESCRIPTION as e,LIST_COMMAND_NAME as t,START_COMMAND_DESCRIPTION as n,START_COMMAND_NAME as r}from"../constants.mjs";function i(i,a){i.registerCommand(r,{description:n,handler:async(e,t)=>a.start(t)}),i.registerCommand(t,{description:e,handler:async(e,t)=>a.list(t)})}export{i as registerCommands};
|
|
2
|
-
//# sourceMappingURL=loopCommand.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"loopCommand.mjs","names":[],"sources":["../../src/commands/loopCommand.ts"],"sourcesContent":["import type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport {\n LIST_COMMAND_DESCRIPTION,\n LIST_COMMAND_NAME,\n START_COMMAND_DESCRIPTION,\n START_COMMAND_NAME,\n} from '../constants.ts';\n\nexport interface LoopCommandHandlers {\n start(ctx: ExtensionContext): Promise<void>;\n list(ctx: ExtensionContext): Promise<void>;\n}\n\nexport function registerCommands(pi: ExtensionAPI, handlers: LoopCommandHandlers): void {\n pi.registerCommand(START_COMMAND_NAME, {\n description: START_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.start(ctx),\n });\n pi.registerCommand(LIST_COMMAND_NAME, {\n description: LIST_COMMAND_DESCRIPTION,\n handler: async (_args, ctx) => handlers.list(ctx),\n });\n}\n"],"mappings":"0IAaA,SAAgB,EAAiB,EAAkB,EAAqC,CACtF,EAAG,gBAAgB,EAAoB,CACrC,YAAa,EACb,QAAS,MAAO,EAAO,IAAQ,EAAS,MAAM,CAAG,CACnD,CAAC,EACD,EAAG,gBAAgB,EAAmB,CACpC,YAAa,EACb,QAAS,MAAO,EAAO,IAAQ,EAAS,KAAK,CAAG,CAClD,CAAC,CACH"}
|
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"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { LoopLauncherRegistration } from "./launcher.cjs";
|
|
2
|
-
import { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
//#region src/defaultLauncher.d.ts
|
|
4
|
-
interface DefaultLoopLauncher {
|
|
5
|
-
register(ctx: ExtensionContext): LoopLauncherRegistration;
|
|
6
|
-
}
|
|
7
|
-
declare function createDefaultLoopLauncher(pi: ExtensionAPI): DefaultLoopLauncher;
|
|
8
|
-
//#endregion
|
|
9
|
-
export { DefaultLoopLauncher, createDefaultLoopLauncher };
|
|
10
|
-
//# sourceMappingURL=defaultLauncher.d.cts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"defaultLauncher.d.cts","names":[],"sources":["../src/defaultLauncher.ts"],"mappings":";;;UAqBiB;EACf,SAAS,KAAK,mBAAmB;;iBAoCnB,0BAA0B,IAAI,eAAe"}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import { LoopLauncherRegistration } from "./launcher.mjs";
|
|
2
|
-
import { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
3
|
-
//#region src/defaultLauncher.d.ts
|
|
4
|
-
interface DefaultLoopLauncher {
|
|
5
|
-
register(ctx: ExtensionContext): LoopLauncherRegistration;
|
|
6
|
-
}
|
|
7
|
-
declare function createDefaultLoopLauncher(pi: ExtensionAPI): DefaultLoopLauncher;
|
|
8
|
-
//#endregion
|
|
9
|
-
export { DefaultLoopLauncher, createDefaultLoopLauncher };
|
|
10
|
-
//# sourceMappingURL=defaultLauncher.d.mts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"defaultLauncher.d.mts","names":[],"sources":["../src/defaultLauncher.ts"],"mappings":";;;UAqBiB;EACf,SAAS,KAAK,mBAAmB;;iBAoCnB,0BAA0B,IAAI,eAAe"}
|
package/dist/defaultLauncher.mjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{registerLoopLauncher as e}from"./launcher.mjs";const 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(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))}}}})}}}export{a as createDefaultLoopLauncher};
|
|
2
|
-
//# sourceMappingURL=defaultLauncher.mjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"defaultLauncher.mjs","names":[],"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":"sDAGA,MAGM,EAAuB,KAmB7B,SAAS,EAAc,EAAiC,CACtD,IAAM,EAAU,OAAO,CAAG,EAC1B,GAAI,CAAC,OAAO,UAAU,CAAO,EAAG,MAAO,mDAAmD,EAAI,GAC9F,GAAI,EAAU,IAAwB,EAAU,EAC9C,MAAO,mCAAwD,EAAqB,UAGxF,CAEA,eAAe,EACb,EACA,EAC0C,CAC1C,IAAM,GAAU,MAAM,EAAG,OAAO,cAAe,EAAE,EAAA,EAAI,KAAK,EAC1D,GAAI,CAAC,GAAU,EAAO,QAAS,OAE/B,IAAM,EAAc,MAAM,EAAG,MAAM,2BAA4B,eAAuC,EACtG,GAAI,IAAgB,IAAA,IAAa,EAAO,QAAS,OACjD,IAAM,EAAqB,EAAY,KAAK,GAAK,MAC3C,EAAQ,EAAc,CAAkB,EAC9C,GAAI,EAAO,CACT,EAAG,OAAO,EAAO,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,OAAO,EAAqB,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"}
|
package/dist/extension.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
const e=require("./launcher.cjs"),t=require("./defaultLauncher.cjs"),n=require("./constants.cjs"),r=require("./commands/loopCommand.cjs"),i=require("./leader.cjs"),a=require("./tui/loopListOverlay.cjs"),o=require("./tui/startLoopOverlay.cjs");let s=require("@agimon-ai/doompi-ui/mode");const c=Symbol.for(`@agimon-ai/doompi-loop/installed-hosts`);function l(e){let t=globalThis,n=t[c]??new WeakSet;return t[c]=n,!n.has(e)&&(n.add(e),!0)}function u(c){if(!l(c))return;let u,d,f,p,m=e.createLoopLauncherClient(()=>u),h=t.createDefaultLoopLauncher(c),g=(0,s.registerDoomModeContribution)(c,{source:n.PACKAGE_SOURCE,id:n.MODE_STATUS_ID,order:60});i.registerLeaderContribution(c);let _=()=>{let e=m.listInstances().length;d?.ui?.setStatus(n.STATUS_KEY,e?`loops: ${e}`:void 0),g.update(e?{label:`LOOP`,detail:`${e} active`,color:n.ACTIVE_MODE_COLOR}:void 0)};r.registerCommands(c,{async start(e){let t=m.listLaunchers();if(!t.length){e.ui.notify(`No loop launchers are registered for this session.`,n.NOTIFY_INFO_LEVEL);return}let r=await o.openStartLoopOverlay(e,t);if(r)try{let t=await m.launch(r);t&&e.ui.notify(`${t.label??t.launcherLabel} started.`,n.NOTIFY_INFO_LEVEL)}catch(t){e.ui.notify(`Loop could not start: ${t instanceof Error?t.message:String(t)}`,`error`)}},async list(e){await a.openLoopListOverlay(e,m)}}),c.on(`session_start`,async(e,t)=>{f?.(),f=void 0,await m.stopAll(`Pi session replaced.`),await p?.dispose(`Pi session replaced.`),p=void 0,d?.ui?.setStatus(n.STATUS_KEY,void 0),d=t,u=t.sessionManager.getSessionId(),p=h.register(t),f=m.subscribe(_),_()}),c.on(`session_shutdown`,async()=>{await m.stopAll(`Pi session shut down.`),await p?.dispose(`Pi session shut down.`),p=void 0,f?.(),f=void 0,d?.ui?.setStatus(n.STATUS_KEY,void 0),d=void 0,u=void 0,g.update(void 0)})}exports.registerLoopExtension=u;
|
|
2
|
-
//# sourceMappingURL=extension.cjs.map
|
package/dist/extension.cjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extension.cjs","names":["createLoopLauncherClient","createDefaultLoopLauncher","registerDoomModeContribution","PACKAGE_SOURCE","MODE_STATUS_ID","STATUS_KEY","ACTIVE_MODE_COLOR","NOTIFY_INFO_LEVEL","openStartLoopOverlay","openLoopListOverlay"],"sources":["../src/extension.ts"],"sourcesContent":["import { registerDoomModeContribution } from '@agimon-ai/doompi-ui/mode';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommands } from './commands/loopCommand.ts';\nimport {\n ACTIVE_MODE_COLOR,\n LOOPS_GROUP_ORDER,\n MODE_STATUS_ID,\n NOTIFY_INFO_LEVEL,\n PACKAGE_SOURCE,\n STATUS_KEY,\n} from './constants.ts';\nimport { createDefaultLoopLauncher } from './defaultLauncher.ts';\nimport { createLoopLauncherClient, type LoopLauncherRegistration } from './launcher.ts';\nimport { registerLeaderContribution } from './leader.ts';\nimport { openLoopListOverlay } from './tui/loopListOverlay.ts';\nimport { openStartLoopOverlay } from './tui/startLoopOverlay.ts';\n\nconst INSTALLATIONS_KEY = Symbol.for('@agimon-ai/doompi-loop/installed-hosts');\n\ntype InstallationRegistry = typeof globalThis & {\n [INSTALLATIONS_KEY]?: WeakSet<object>;\n};\n\nfunction claimHostInstallation(pi: ExtensionAPI): boolean {\n const registry = globalThis as InstallationRegistry;\n const installedHosts = registry[INSTALLATIONS_KEY] ?? new WeakSet<object>();\n registry[INSTALLATIONS_KEY] = installedHosts;\n if (installedHosts.has(pi)) return false;\n installedHosts.add(pi);\n return true;\n}\n\nexport function registerLoopExtension(pi: ExtensionAPI): void {\n if (!claimHostInstallation(pi)) return;\n let sessionId: string | undefined;\n let context: ExtensionContext | undefined;\n let unsubscribe: (() => void) | undefined;\n let defaultRegistration: LoopLauncherRegistration | undefined;\n const client = createLoopLauncherClient(() => sessionId);\n const defaultLauncher = createDefaultLoopLauncher(pi);\n const mode = registerDoomModeContribution(pi, {\n source: PACKAGE_SOURCE,\n id: MODE_STATUS_ID,\n order: LOOPS_GROUP_ORDER,\n });\n registerLeaderContribution(pi);\n\n const updateStatus = (): void => {\n const count = client.listInstances().length;\n context?.ui?.setStatus(STATUS_KEY, count ? `loops: ${count}` : undefined);\n mode.update(count ? { label: 'LOOP', detail: `${count} active`, color: ACTIVE_MODE_COLOR } : undefined);\n };\n\n registerCommands(pi, {\n async start(ctx) {\n const launchers = client.listLaunchers();\n if (!launchers.length) {\n ctx.ui.notify('No loop launchers are registered for this session.', NOTIFY_INFO_LEVEL);\n return;\n }\n const launcherId = await openStartLoopOverlay(ctx, launchers);\n if (!launcherId) return;\n try {\n const instance = await client.launch(launcherId);\n if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, NOTIFY_INFO_LEVEL);\n } catch (error) {\n ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, 'error');\n }\n },\n async list(ctx) {\n await openLoopListOverlay(ctx, client);\n },\n });\n\n pi.on('session_start', async (_event, ctx) => {\n unsubscribe?.();\n unsubscribe = undefined;\n await client.stopAll('Pi session replaced.');\n await defaultRegistration?.dispose('Pi session replaced.');\n defaultRegistration = undefined;\n context?.ui?.setStatus(STATUS_KEY, undefined);\n\n context = ctx;\n sessionId = ctx.sessionManager.getSessionId();\n defaultRegistration = defaultLauncher.register(ctx);\n unsubscribe = client.subscribe(updateStatus);\n updateStatus();\n });\n\n pi.on('session_shutdown', async () => {\n await client.stopAll('Pi session shut down.');\n await defaultRegistration?.dispose('Pi session shut down.');\n defaultRegistration = undefined;\n unsubscribe?.();\n unsubscribe = undefined;\n context?.ui?.setStatus(STATUS_KEY, undefined);\n context = undefined;\n sessionId = undefined;\n mode.update(undefined);\n });\n}\n"],"mappings":"8RAiBA,MAAM,EAAoB,OAAO,IAAI,wCAAwC,EAM7E,SAAS,EAAsB,EAA2B,CACxD,IAAM,EAAW,WACX,EAAiB,EAAS,IAAsB,IAAI,QAI1D,MAHA,GAAS,GAAqB,EAC9B,CAAI,EAAe,IAAI,CAAE,IACzB,EAAe,IAAI,CAAE,EACd,GACT,CAEA,SAAgB,EAAsB,EAAwB,CAC5D,GAAI,CAAC,EAAsB,CAAE,EAAG,OAChC,IAAI,EACA,EACA,EACA,EACE,EAASA,EAAAA,6BAA+B,CAAS,EACjD,EAAkBC,EAAAA,0BAA0B,CAAE,EAC9C,GAAA,EAAOC,EAAAA,6BAAAA,CAA6B,EAAI,CAC5C,OAAQC,EAAAA,eACR,GAAIC,EAAAA,eACJ,MAAA,EACF,CAAC,EACD,EAAA,2BAA2B,CAAE,EAE7B,IAAM,MAA2B,CAC/B,IAAM,EAAQ,EAAO,cAAc,CAAC,CAAC,OACrC,GAAS,IAAI,UAAUC,EAAAA,WAAY,EAAQ,UAAU,IAAU,IAAA,EAAS,EACxE,EAAK,OAAO,EAAQ,CAAE,MAAO,OAAQ,OAAQ,GAAG,EAAM,SAAU,MAAOC,EAAAA,iBAAkB,EAAI,IAAA,EAAS,CACxG,EAEA,EAAA,iBAAiB,EAAI,CACnB,MAAM,MAAM,EAAK,CACf,IAAM,EAAY,EAAO,cAAc,EACvC,GAAI,CAAC,EAAU,OAAQ,CACrB,EAAI,GAAG,OAAO,qDAAsDC,EAAAA,iBAAiB,EACrF,MACF,CACA,IAAM,EAAa,MAAMC,EAAAA,qBAAqB,EAAK,CAAS,EACvD,KACL,GAAI,CACF,IAAM,EAAW,MAAM,EAAO,OAAO,CAAU,EAC3C,GAAU,EAAI,GAAG,OAAO,GAAG,EAAS,OAAS,EAAS,cAAc,WAAYD,EAAAA,iBAAiB,CACvG,OAAS,EAAO,CACd,EAAI,GAAG,OAAO,yBAAyB,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,IAAK,OAAO,CAC1G,CACF,EACA,MAAM,KAAK,EAAK,CACd,MAAME,EAAAA,oBAAoB,EAAK,CAAM,CACvC,CACF,CAAC,EAED,EAAG,GAAG,gBAAiB,MAAO,EAAQ,IAAQ,CAC5C,IAAc,EACd,EAAc,IAAA,GACd,MAAM,EAAO,QAAQ,sBAAsB,EAC3C,MAAM,GAAqB,QAAQ,sBAAsB,EACzD,EAAsB,IAAA,GACtB,GAAS,IAAI,UAAUJ,EAAAA,WAAY,IAAA,EAAS,EAE5C,EAAU,EACV,EAAY,EAAI,eAAe,aAAa,EAC5C,EAAsB,EAAgB,SAAS,CAAG,EAClD,EAAc,EAAO,UAAU,CAAY,EAC3C,EAAa,CACf,CAAC,EAED,EAAG,GAAG,mBAAoB,SAAY,CACpC,MAAM,EAAO,QAAQ,uBAAuB,EAC5C,MAAM,GAAqB,QAAQ,uBAAuB,EAC1D,EAAsB,IAAA,GACtB,IAAc,EACd,EAAc,IAAA,GACd,GAAS,IAAI,UAAUA,EAAAA,WAAY,IAAA,EAAS,EAC5C,EAAU,IAAA,GACV,EAAY,IAAA,GACZ,EAAK,OAAO,IAAA,EAAS,CACvB,CAAC,CACH"}
|
package/dist/extension.d.cts
DELETED
package/dist/extension.d.cts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.cts","names":[],"sources":["../src/extension.ts"],"mappings":";;iBAgCgB,sBAAsB,IAAI"}
|
package/dist/extension.d.mts
DELETED
package/dist/extension.d.mts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extension.d.mts","names":[],"sources":["../src/extension.ts"],"mappings":";;iBAgCgB,sBAAsB,IAAI"}
|
package/dist/extension.mjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{createLoopLauncherClient as e}from"./launcher.mjs";import{createDefaultLoopLauncher as t}from"./defaultLauncher.mjs";import{ACTIVE_MODE_COLOR as n,MODE_STATUS_ID as r,NOTIFY_INFO_LEVEL as i,PACKAGE_SOURCE as a,STATUS_KEY as o}from"./constants.mjs";import{registerCommands as s}from"./commands/loopCommand.mjs";import{registerLeaderContribution as c}from"./leader.mjs";import{openLoopListOverlay as l}from"./tui/loopListOverlay.mjs";import{openStartLoopOverlay as u}from"./tui/startLoopOverlay.mjs";import{registerDoomModeContribution as d}from"@agimon-ai/doompi-ui/mode";const f=Symbol.for(`@agimon-ai/doompi-loop/installed-hosts`);function p(e){let t=globalThis,n=t[f]??new WeakSet;return t[f]=n,!n.has(e)&&(n.add(e),!0)}function m(f){if(!p(f))return;let m,h,g,_,v=e(()=>m),y=t(f),b=d(f,{source:a,id:r,order:60});c(f);let x=()=>{let e=v.listInstances().length;h?.ui?.setStatus(o,e?`loops: ${e}`:void 0),b.update(e?{label:`LOOP`,detail:`${e} active`,color:n}:void 0)};s(f,{async start(e){let t=v.listLaunchers();if(!t.length){e.ui.notify(`No loop launchers are registered for this session.`,i);return}let n=await u(e,t);if(n)try{let t=await v.launch(n);t&&e.ui.notify(`${t.label??t.launcherLabel} started.`,i)}catch(t){e.ui.notify(`Loop could not start: ${t instanceof Error?t.message:String(t)}`,`error`)}},async list(e){await l(e,v)}}),f.on(`session_start`,async(e,t)=>{g?.(),g=void 0,await v.stopAll(`Pi session replaced.`),await _?.dispose(`Pi session replaced.`),_=void 0,h?.ui?.setStatus(o,void 0),h=t,m=t.sessionManager.getSessionId(),_=y.register(t),g=v.subscribe(x),x()}),f.on(`session_shutdown`,async()=>{await v.stopAll(`Pi session shut down.`),await _?.dispose(`Pi session shut down.`),_=void 0,g?.(),g=void 0,h?.ui?.setStatus(o,void 0),h=void 0,m=void 0,b.update(void 0)})}export{m as registerLoopExtension};
|
|
2
|
-
//# sourceMappingURL=extension.mjs.map
|
package/dist/extension.mjs.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"extension.mjs","names":[],"sources":["../src/extension.ts"],"sourcesContent":["import { registerDoomModeContribution } from '@agimon-ai/doompi-ui/mode';\nimport type { ExtensionAPI, ExtensionContext } from '@earendil-works/pi-coding-agent';\nimport { registerCommands } from './commands/loopCommand.ts';\nimport {\n ACTIVE_MODE_COLOR,\n LOOPS_GROUP_ORDER,\n MODE_STATUS_ID,\n NOTIFY_INFO_LEVEL,\n PACKAGE_SOURCE,\n STATUS_KEY,\n} from './constants.ts';\nimport { createDefaultLoopLauncher } from './defaultLauncher.ts';\nimport { createLoopLauncherClient, type LoopLauncherRegistration } from './launcher.ts';\nimport { registerLeaderContribution } from './leader.ts';\nimport { openLoopListOverlay } from './tui/loopListOverlay.ts';\nimport { openStartLoopOverlay } from './tui/startLoopOverlay.ts';\n\nconst INSTALLATIONS_KEY = Symbol.for('@agimon-ai/doompi-loop/installed-hosts');\n\ntype InstallationRegistry = typeof globalThis & {\n [INSTALLATIONS_KEY]?: WeakSet<object>;\n};\n\nfunction claimHostInstallation(pi: ExtensionAPI): boolean {\n const registry = globalThis as InstallationRegistry;\n const installedHosts = registry[INSTALLATIONS_KEY] ?? new WeakSet<object>();\n registry[INSTALLATIONS_KEY] = installedHosts;\n if (installedHosts.has(pi)) return false;\n installedHosts.add(pi);\n return true;\n}\n\nexport function registerLoopExtension(pi: ExtensionAPI): void {\n if (!claimHostInstallation(pi)) return;\n let sessionId: string | undefined;\n let context: ExtensionContext | undefined;\n let unsubscribe: (() => void) | undefined;\n let defaultRegistration: LoopLauncherRegistration | undefined;\n const client = createLoopLauncherClient(() => sessionId);\n const defaultLauncher = createDefaultLoopLauncher(pi);\n const mode = registerDoomModeContribution(pi, {\n source: PACKAGE_SOURCE,\n id: MODE_STATUS_ID,\n order: LOOPS_GROUP_ORDER,\n });\n registerLeaderContribution(pi);\n\n const updateStatus = (): void => {\n const count = client.listInstances().length;\n context?.ui?.setStatus(STATUS_KEY, count ? `loops: ${count}` : undefined);\n mode.update(count ? { label: 'LOOP', detail: `${count} active`, color: ACTIVE_MODE_COLOR } : undefined);\n };\n\n registerCommands(pi, {\n async start(ctx) {\n const launchers = client.listLaunchers();\n if (!launchers.length) {\n ctx.ui.notify('No loop launchers are registered for this session.', NOTIFY_INFO_LEVEL);\n return;\n }\n const launcherId = await openStartLoopOverlay(ctx, launchers);\n if (!launcherId) return;\n try {\n const instance = await client.launch(launcherId);\n if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, NOTIFY_INFO_LEVEL);\n } catch (error) {\n ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, 'error');\n }\n },\n async list(ctx) {\n await openLoopListOverlay(ctx, client);\n },\n });\n\n pi.on('session_start', async (_event, ctx) => {\n unsubscribe?.();\n unsubscribe = undefined;\n await client.stopAll('Pi session replaced.');\n await defaultRegistration?.dispose('Pi session replaced.');\n defaultRegistration = undefined;\n context?.ui?.setStatus(STATUS_KEY, undefined);\n\n context = ctx;\n sessionId = ctx.sessionManager.getSessionId();\n defaultRegistration = defaultLauncher.register(ctx);\n unsubscribe = client.subscribe(updateStatus);\n updateStatus();\n });\n\n pi.on('session_shutdown', async () => {\n await client.stopAll('Pi session shut down.');\n await defaultRegistration?.dispose('Pi session shut down.');\n defaultRegistration = undefined;\n unsubscribe?.();\n unsubscribe = undefined;\n context?.ui?.setStatus(STATUS_KEY, undefined);\n context = undefined;\n sessionId = undefined;\n mode.update(undefined);\n });\n}\n"],"mappings":"kkBAiBA,MAAM,EAAoB,OAAO,IAAI,wCAAwC,EAM7E,SAAS,EAAsB,EAA2B,CACxD,IAAM,EAAW,WACX,EAAiB,EAAS,IAAsB,IAAI,QAI1D,MAHA,GAAS,GAAqB,EAC9B,CAAI,EAAe,IAAI,CAAE,IACzB,EAAe,IAAI,CAAE,EACd,GACT,CAEA,SAAgB,EAAsB,EAAwB,CAC5D,GAAI,CAAC,EAAsB,CAAE,EAAG,OAChC,IAAI,EACA,EACA,EACA,EACE,EAAS,MAA+B,CAAS,EACjD,EAAkB,EAA0B,CAAE,EAC9C,EAAO,EAA6B,EAAI,CAC5C,OAAQ,EACR,GAAI,EACJ,MAAA,EACF,CAAC,EACD,EAA2B,CAAE,EAE7B,IAAM,MAA2B,CAC/B,IAAM,EAAQ,EAAO,cAAc,CAAC,CAAC,OACrC,GAAS,IAAI,UAAU,EAAY,EAAQ,UAAU,IAAU,IAAA,EAAS,EACxE,EAAK,OAAO,EAAQ,CAAE,MAAO,OAAQ,OAAQ,GAAG,EAAM,SAAU,MAAO,CAAkB,EAAI,IAAA,EAAS,CACxG,EAEA,EAAiB,EAAI,CACnB,MAAM,MAAM,EAAK,CACf,IAAM,EAAY,EAAO,cAAc,EACvC,GAAI,CAAC,EAAU,OAAQ,CACrB,EAAI,GAAG,OAAO,qDAAsD,CAAiB,EACrF,MACF,CACA,IAAM,EAAa,MAAM,EAAqB,EAAK,CAAS,EACvD,KACL,GAAI,CACF,IAAM,EAAW,MAAM,EAAO,OAAO,CAAU,EAC3C,GAAU,EAAI,GAAG,OAAO,GAAG,EAAS,OAAS,EAAS,cAAc,WAAY,CAAiB,CACvG,OAAS,EAAO,CACd,EAAI,GAAG,OAAO,yBAAyB,aAAiB,MAAQ,EAAM,QAAU,OAAO,CAAK,IAAK,OAAO,CAC1G,CACF,EACA,MAAM,KAAK,EAAK,CACd,MAAM,EAAoB,EAAK,CAAM,CACvC,CACF,CAAC,EAED,EAAG,GAAG,gBAAiB,MAAO,EAAQ,IAAQ,CAC5C,IAAc,EACd,EAAc,IAAA,GACd,MAAM,EAAO,QAAQ,sBAAsB,EAC3C,MAAM,GAAqB,QAAQ,sBAAsB,EACzD,EAAsB,IAAA,GACtB,GAAS,IAAI,UAAU,EAAY,IAAA,EAAS,EAE5C,EAAU,EACV,EAAY,EAAI,eAAe,aAAa,EAC5C,EAAsB,EAAgB,SAAS,CAAG,EAClD,EAAc,EAAO,UAAU,CAAY,EAC3C,EAAa,CACf,CAAC,EAED,EAAG,GAAG,mBAAoB,SAAY,CACpC,MAAM,EAAO,QAAQ,uBAAuB,EAC5C,MAAM,GAAqB,QAAQ,uBAAuB,EAC1D,EAAsB,IAAA,GACtB,IAAc,EACd,EAAc,IAAA,GACd,GAAS,IAAI,UAAU,EAAY,IAAA,EAAS,EAC5C,EAAU,IAAA,GACV,EAAY,IAAA,GACZ,EAAK,OAAO,IAAA,EAAS,CACvB,CAAC,CACH"}
|
package/dist/launcher.cjs
DELETED
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
let e=require("node:crypto");const t=Symbol.for(`@agimon-ai/doompi-loop.launchers.v1`),n=`loop session id`,r=`loop launcher id`,i=`loop instance id`;function a(){let e=globalThis;return e[t]??=new Map}function o(e,t){let n=e.trim(),r=!1;for(let e of n){let t=e.codePointAt(0);if(t!==void 0&&(t<=31||t===127)){r=!0;break}}if(!n||n.length>256||r)throw Error(`Invalid ${t}.`);return n}function s(e,t){let r=o(e,n),i=a().get(r);if(i||!t)return i;let s={launchers:new Map,instances:new Map,listeners:new Set};return a().set(r,s),s}function c(e){for(let t of e.listeners)t()}function l(e){return{instanceId:e.instanceId,launcherId:e.launcher.options.id,launcherLabel:e.launcher.options.label,...e.handle?.label?{label:e.handle.label}:{},...e.handle?.detail?{detail:e.handle.detail}:{},state:e.state,...e.startedAt?{startedAt:e.startedAt}:{}}}async function u(e,t,n){if(e.instances.get(t.instanceId)===t){t.state=`stopping`,t.abortController.abort(n),c(e);try{await t.handle?.stop(n)}finally{e.instances.get(t.instanceId)===t&&e.instances.delete(t.instanceId),c(e)}}}function d(e,t){t.launchers.size||t.instances.size||t.listeners.size||a().get(e)===t&&a().delete(e)}function f(e,t){let i=o(e,n),a=o(t.id,r),l=s(i,!0);if(l.launchers.has(a))throw Error(`Loop launcher '${a}' is already registered for session '${i}'.`);let f={token:Symbol(a),options:{...t,id:a}};l.launchers.set(a,f),c(l);let p=!1;return{async dispose(e=`Loop launcher disposed.`){if(p)return;p=!0;let t=s(i,!1);if(!t||t.launchers.get(a)?.token!==f.token)return;t.launchers.delete(a);let n=[...t.instances.values()].filter(e=>e.launcher.token===f.token);await Promise.allSettled(n.map(n=>u(t,n,e))),c(t),d(i,t)}}}function p(t){let a=e=>{let r=t();if(!r)return;let i=o(r,n),a=s(i,e);return a?{key:i,session:a}:void 0};return{listLaunchers(){let e=a(!1)?.session;return e?[...e.launchers.values()].map(({options:e})=>({id:e.id,source:e.source,label:e.label,...e.description?{description:e.description}:{}})).sort((e,t)=>e.label.localeCompare(t.label)||e.id.localeCompare(t.id)):[]},listInstances(){let e=a(!1)?.session;return e?[...e.instances.values()].map(l).sort((e,t)=>e.instanceId.localeCompare(t.instanceId)):[]},subscribe(e){let t=a(!0);if(!t)return()=>void 0;t.session.listeners.add(e);let n=!1;return()=>{n||(n=!0,t.session.listeners.delete(e),d(t.key,t.session))}},async launch(t,n){let s=a(!0);if(!s)throw Error(`The session-bound loop launcher registry is unavailable.`);let u=o(t,r),d=s.session.launchers.get(u);if(!d)throw Error(`Loop launcher '${u}' is unavailable.`);let f=o(n?.instanceId??(0,e.randomUUID)(),i);if(s.session.instances.has(f))throw Error(`Loop instance '${f}' already exists.`);let p={instanceId:f,launcher:d,abortController:new AbortController,state:`starting`};s.session.instances.set(f,p),c(s.session);try{let e=await d.options.launch({instanceId:f,signal:p.abortController.signal}),t=s.session.instances.get(f)===p&&!p.abortController.signal.aborted;if(!e){s.session.instances.get(f)===p&&s.session.instances.delete(f),c(s.session);return}if(!t||e.instanceId!==f){if(await e.stop(t?`Loop launcher returned a mismatched instance id.`:`Loop launch was cancelled.`),s.session.instances.get(f)===p&&s.session.instances.delete(f),c(s.session),t)throw Error(`Loop launcher '${u}' returned a mismatched instance id.`);return}return p.handle=e,p.state=`running`,p.startedAt=new Date().toISOString(),c(s.session),l(p)}catch(e){let t=p.abortController.signal.aborted;if(s.session.instances.get(f)===p&&s.session.instances.delete(f),c(s.session),t)return;throw e}},async stop(e,t){let n=a(!1)?.session;if(!n)return!1;let r=n.instances.get(o(e,i));return r?(await u(n,r,t),!0):!1},async stopAll(e){let t=a(!1)?.session;t&&await Promise.allSettled([...t.instances.values()].map(n=>u(t,n,e)))}}}exports.createLoopLauncherClient=p,exports.registerLoopLauncher=f;
|
|
2
|
-
//# sourceMappingURL=launcher.cjs.map
|