@agimon-ai/po-loop 0.0.0-alpha.0

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/package.json ADDED
@@ -0,0 +1,125 @@
1
+ {
2
+ "name": "@agimon-ai/po-loop",
3
+ "version": "0.0.0-alpha.0",
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
+ "pi-coding-agent",
11
+ "pi-extension",
12
+ "pi-package",
13
+ "po",
14
+ "prompt-scheduler",
15
+ "recurring-tasks"
16
+ ],
17
+ "homepage": "https://agimon.ai",
18
+ "bugs": {
19
+ "url": "https://github.com/AgiFlow/po/issues"
20
+ },
21
+ "license": "MIT",
22
+ "author": "Vuong Ngo",
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/AgiFlow/po.git",
26
+ "directory": "packages/minor/po-loop"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "llms.txt",
31
+ "src/prompts",
32
+ "src/types/loopView.ts",
33
+ "README.md",
34
+ "LICENSE",
35
+ "package.json"
36
+ ],
37
+ "type": "module",
38
+ "main": "./dist/index.cjs",
39
+ "module": "./dist/index.mjs",
40
+ "types": "./dist/index.d.mts",
41
+ "jsnext:main": "./dist/index.mjs",
42
+ "exports": {
43
+ ".": {
44
+ "types": "./dist/index.d.mts",
45
+ "import": "./dist/index.mjs",
46
+ "require": "./dist/index.cjs"
47
+ },
48
+ "./extensions/server": {
49
+ "types": "./dist/extensions/server.d.mts",
50
+ "import": "./dist/extensions/server.mjs",
51
+ "require": "./dist/extensions/server.cjs"
52
+ },
53
+ "./extensions/web": {
54
+ "import": "./dist/extensions/web.mjs"
55
+ },
56
+ "./loopLaunchers": {
57
+ "types": "./dist/loopLaunchers.d.mts",
58
+ "import": "./dist/loopLaunchers.mjs",
59
+ "require": "./dist/loopLaunchers.cjs"
60
+ },
61
+ "./package.json": "./package.json"
62
+ },
63
+ "publishConfig": {
64
+ "access": "public"
65
+ },
66
+ "dependencies": {
67
+ "@agimon-ai/po-core": "0.0.0-alpha.0",
68
+ "@agimon-ai/po-minor-mode": "0.0.0-alpha.0",
69
+ "@agimon-ai/po-ui": "0.0.0-alpha.0",
70
+ "@agimon-ai/po-web-components": "0.0.0-alpha.0",
71
+ "@deepseek-ai/cordis": "4.0.4",
72
+ "croner": "10.0.1",
73
+ "typebox": "1.3.34"
74
+ },
75
+ "devDependencies": {
76
+ "@agimon-ai/po-build": "0.0.0-alpha.0",
77
+ "@earendil-works/pi-coding-agent": "1.0.2",
78
+ "@types/node": "26.6.4",
79
+ "@types/react": "19.3.0",
80
+ "@types/react-dom": "19.3.0",
81
+ "@vitest/coverage-v8": "5.0.3",
82
+ "react": "19.3.0",
83
+ "react-dom": "19.3.0",
84
+ "tsdown": "0.23.0",
85
+ "typescript": "7.0.2",
86
+ "vitest": "5.0.3"
87
+ },
88
+ "peerDependencies": {
89
+ "@earendil-works/pi-coding-agent": "1.0.2"
90
+ },
91
+ "peerDependenciesMeta": {
92
+ "@earendil-works/pi-coding-agent": {
93
+ "optional": true
94
+ }
95
+ },
96
+ "engines": {
97
+ "node": ">=24"
98
+ },
99
+ "poServer": {
100
+ "entry": "./generated/server.ts",
101
+ "dist": "./dist/extensions/server.mjs",
102
+ "scopes": [
103
+ "session"
104
+ ],
105
+ "contracts": {
106
+ "entry": "./src/exports/apiContracts.ts",
107
+ "dist": "./dist/apiContracts.mjs"
108
+ }
109
+ },
110
+ "poWeb": {
111
+ "pluginId": "loop",
112
+ "channels": [],
113
+ "client": "./dist/extensions/web.mjs",
114
+ "scopes": [
115
+ "session"
116
+ ]
117
+ },
118
+ "scripts": {
119
+ "build": "tsdown",
120
+ "test": "vitest --run",
121
+ "typecheck": "tsc --noEmit && tsc --noEmit -p tsconfig.web.json",
122
+ "lint": "oxlint . && oxfmt . --check",
123
+ "fixcode": "oxlint . --fix && oxfmt ."
124
+ }
125
+ }
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: po-use-loop
3
+ description: Create, inspect, and stop session-scoped interval, cron, and extension-contributed loops.
4
+ ---
5
+
6
+ # Use Doom Pi Loop
7
+
8
+ Enable the Loop minor mode to expose `loop_list`, `loop_start`, and `loop_stop` to the agent. Enabling the mode does not create a loop. Disabling it hides agent tools but leaves existing loops and manual Activity controls available.
9
+
10
+ ## Agent configuration
11
+
12
+ Call `loop_list` first. It returns the available launcher IDs, their input schemas, and active instance IDs. Providers without an input schema require manual setup and cannot be started by an agent tool.
13
+
14
+ An interval loop runs immediately when the session can accept a prompt, then every 30 to 3600 seconds:
15
+
16
+ ```json
17
+ {
18
+ "launcherId": "po.default",
19
+ "input": { "prompt": "Check the current build status and report changes.", "intervalSeconds": 300 }
20
+ }
21
+ ```
22
+
23
+ A cron loop runs at its next scheduled minute, not immediately. Supply a five-field cron expression and an optional IANA timezone. The default timezone is UTC:
24
+
25
+ ```json
26
+ {
27
+ "launcherId": "po.cron",
28
+ "input": { "prompt": "Summarize the work remaining today.", "cron": "0 9 * * 1-5", "timezone": "Australia/Brisbane" }
29
+ }
30
+ ```
31
+
32
+ Pass either configuration to `loop_start`. Stop one instance with `loop_stop` and its exact `instanceId`. Extension launchers, such as Agiflow project dispatch, use their own schemas from `loop_list`; do not guess their configuration fields.
33
+
34
+ ## Manual controls
35
+
36
+ Activity's Loops group lists this session's loops whenever the Loop package is loaded, even with the minor mode off, and each row has a stop control. Loops are set by asking the agent while the minor mode is active. `/loops` lists instances, and `/loops stop <instanceId>` stops one.
37
+
38
+ ## Cost and lifecycle
39
+
40
+ Several loops can coexist, but built-in loops do not deliberately overlap prompts on the same agent. The CLI coalesces due passes while busy and retries after the agent settles. The server skips busy ticks and checks again at the next scheduled tick.
41
+
42
+ Every pass can start a model turn and repeat tool calls or external side effects. Prefer idempotent checks and conservative schedules. Stop loops when the recurring work is complete.
43
+
44
+ Loops are session-scoped, not a durable job queue. Replacing or shutting down the session stops timers; reopening a transcript does not restart them or replay missed ticks. Stopping prevents future submissions but does not undo or cancel a prompt already submitted.
@@ -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 {
34
+ if (instances.length === 0) return '';
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
+ }