@agimon-ai/doompi-loop 0.0.1-alpha.76 → 0.0.1-alpha.78
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/dist/api-contracts.cjs +7 -0
- package/dist/api-contracts.d.cts +2 -0
- package/dist/api-contracts.d.mts +2 -0
- package/dist/api-contracts.mjs +2 -0
- package/dist/extensions/pi.cjs +36 -0
- package/dist/extensions/pi.cjs.map +1 -0
- package/dist/extensions/pi.d.cts +5 -0
- package/dist/extensions/pi.d.cts.map +1 -0
- package/dist/extensions/pi.d.mts +5 -0
- package/dist/extensions/pi.d.mts.map +1 -0
- package/dist/extensions/pi.mjs +31 -0
- package/dist/extensions/pi.mjs.map +1 -0
- package/dist/extensions/server.cjs +51 -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 +46 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +228 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/index.cjs +0 -0
- package/dist/index.d.cts +2 -0
- package/dist/index.d.mts +2 -0
- package/dist/index.mjs +1 -0
- package/dist/loopLaunchers.cjs +5 -0
- package/dist/loopLaunchers.d.cts +2 -0
- package/dist/loopLaunchers.d.mts +2 -0
- package/dist/loopLaunchers.mjs +2 -0
- package/dist/src/constants/loop.cjs +12 -0
- package/dist/src/constants/loop.cjs.map +1 -0
- package/dist/src/constants/loop.mjs +9 -0
- package/dist/src/constants/loop.mjs.map +1 -0
- package/dist/src/constants/piLoop.cjs +16 -0
- package/dist/src/constants/piLoop.cjs.map +1 -0
- package/dist/src/constants/piLoop.mjs +11 -0
- package/dist/src/constants/piLoop.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/runtime.cli.cjs +26 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/runtime.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/runtime.cli.mjs +26 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/runtime.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/runtime.server.cjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/runtime.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/runtime.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/_lib/runtime.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loop.cli.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loop.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loop.cli.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loop.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loop.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loop.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loop.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loop.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loops.cli.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loops.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loops.cli.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loops.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loops.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loops.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loops.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/command/loops.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/agent-settled.cli.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/agent-settled.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/agent-settled.cli.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/agent-settled.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-shutdown.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-start.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-start.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-start.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/hook/session-start.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-loop.cli.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-loop.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-loop.cli.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-loop.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-loop.server.cjs +6 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-loop.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-loop.server.mjs +7 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/resource/doompi-use-loop.server.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.cjs +14 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.mjs +15 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.cli.mjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs +17 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.cjs.map +1 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs +18 -0
- package/dist/src/extensions/workspaces/sessions/(backend)/root.server.mjs.map +1 -0
- package/dist/src/schemas/apiContracts.cjs +13 -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 +13 -0
- package/dist/src/schemas/apiContracts.mjs.map +1 -0
- package/dist/src/schemas/loopLaunchers.cjs +17 -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 +15 -0
- package/dist/src/schemas/loopLaunchers.mjs.map +1 -0
- package/dist/src/services/defaultLoopLauncher/index.cjs +92 -0
- package/dist/src/services/defaultLoopLauncher/index.cjs.map +1 -0
- package/dist/src/services/defaultLoopLauncher/index.mjs +92 -0
- package/dist/src/services/defaultLoopLauncher/index.mjs.map +1 -0
- package/dist/src/services/loopCommand/index.cjs +15 -0
- package/dist/src/services/loopCommand/index.cjs.map +1 -0
- package/dist/src/services/loopCommand/index.mjs +15 -0
- package/dist/src/services/loopCommand/index.mjs.map +1 -0
- package/dist/src/services/loopLaunchers/index.cjs +180 -0
- package/dist/src/services/loopLaunchers/index.cjs.map +1 -0
- package/dist/src/services/loopLaunchers/index.mjs +180 -0
- package/dist/src/services/loopLaunchers/index.mjs.map +1 -0
- package/dist/src/services/serverRuntime/index.cjs +293 -0
- package/dist/src/services/serverRuntime/index.cjs.map +1 -0
- package/dist/src/services/serverRuntime/index.mjs +293 -0
- package/dist/src/services/serverRuntime/index.mjs.map +1 -0
- package/dist/src/tui/leader.cjs +36 -0
- package/dist/src/tui/leader.cjs.map +1 -0
- package/dist/src/tui/leader.mjs +36 -0
- package/dist/src/tui/leader.mjs.map +1 -0
- package/dist/src/tui/loopListOverlay.cjs +101 -0
- package/dist/src/tui/loopListOverlay.cjs.map +1 -0
- package/dist/src/tui/loopListOverlay.mjs +100 -0
- package/dist/src/tui/loopListOverlay.mjs.map +1 -0
- package/dist/src/tui/loopRuntime.cjs +256 -0
- package/dist/src/tui/loopRuntime.cjs.map +1 -0
- package/dist/src/tui/loopRuntime.mjs +256 -0
- package/dist/src/tui/loopRuntime.mjs.map +1 -0
- package/dist/src/tui/startLoopOverlay.cjs +71 -0
- package/dist/src/tui/startLoopOverlay.cjs.map +1 -0
- package/dist/src/tui/startLoopOverlay.mjs +70 -0
- package/dist/src/tui/startLoopOverlay.mjs.map +1 -0
- package/dist/src/types/loopView.cjs +19 -0
- package/dist/src/types/loopView.cjs.map +1 -0
- package/dist/src/types/loopView.mjs +18 -0
- package/dist/src/types/loopView.mjs.map +1 -0
- package/package.json +6 -6
|
@@ -0,0 +1,293 @@
|
|
|
1
|
+
import { LIST_COMMAND_NAME, START_COMMAND_NAME } from "../../constants/loop.mjs";
|
|
2
|
+
import { createDoomLoopLaunchersService } from "../loopLaunchers/index.mjs";
|
|
3
|
+
import { LOOP_VIEW_STATUS_KEY, formatLoopStatusView } from "../../types/loopView.mjs";
|
|
4
|
+
import { defineMinorMode, serverMinorModes } from "@agimon-ai/doompi-minor-mode";
|
|
5
|
+
import { readPackageResource } from "@agimon-ai/doompi-core/server-facet";
|
|
6
|
+
//#region src/services/serverRuntime/index.ts
|
|
7
|
+
const SOURCE = "@agimon-ai/doompi-loop";
|
|
8
|
+
const MODE_ID = "loop.active";
|
|
9
|
+
const DEFAULT_LAUNCHER_ID = "doompi.default";
|
|
10
|
+
const DEFAULT_INTERVAL_SECONDS = 300;
|
|
11
|
+
const MIN_INTERVAL_SECONDS = 30;
|
|
12
|
+
const MAX_INTERVAL_SECONDS = 3600;
|
|
13
|
+
function notify(context, body, level = "info") {
|
|
14
|
+
return Promise.resolve(context.client.notify({
|
|
15
|
+
body,
|
|
16
|
+
level
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
function numberInput(value, fallback) {
|
|
20
|
+
if (typeof value !== "string" || value.trim() === "") return fallback;
|
|
21
|
+
const parsed = Number(value);
|
|
22
|
+
if (!Number.isInteger(parsed) || parsed < MIN_INTERVAL_SECONDS || parsed > MAX_INTERVAL_SECONDS) throw new Error(`Interval must be between ${MIN_INTERVAL_SECONDS} and ${MAX_INTERVAL_SECONDS} seconds.`);
|
|
23
|
+
return parsed;
|
|
24
|
+
}
|
|
25
|
+
function createSessionState(host) {
|
|
26
|
+
let launchers;
|
|
27
|
+
let defaultRegistration;
|
|
28
|
+
let unsubscribe;
|
|
29
|
+
let modeOwner;
|
|
30
|
+
const modeSelected = () => (host.context.selection.state?.["minor-mode"] ?? []).includes(MODE_ID);
|
|
31
|
+
const modeState = () => {
|
|
32
|
+
const launcherCount = launchers?.listLaunchers().length ?? 0;
|
|
33
|
+
const active = (launchers?.listInstances().length ?? 0) > 0;
|
|
34
|
+
const canStart = launcherCount > 0 || !modeSelected();
|
|
35
|
+
return {
|
|
36
|
+
activation: active ? "active" : "inactive",
|
|
37
|
+
condition: launchers?.listInstances().some(({ state }) => state !== "running") ? "queued" : "ready",
|
|
38
|
+
...active ? {
|
|
39
|
+
detail: `${launchers?.listInstances().length} active`,
|
|
40
|
+
color: "warning"
|
|
41
|
+
} : {},
|
|
42
|
+
actions: [{
|
|
43
|
+
id: "start",
|
|
44
|
+
enabled: canStart,
|
|
45
|
+
...canStart ? {} : { disabledReason: "No loop launchers are registered." }
|
|
46
|
+
}, {
|
|
47
|
+
id: "stop",
|
|
48
|
+
enabled: active,
|
|
49
|
+
...active ? {} : { disabledReason: "No loops are active." }
|
|
50
|
+
}]
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* The instances the activity dock lists.
|
|
55
|
+
*
|
|
56
|
+
* An empty list publishes an empty value rather than nothing, because the key
|
|
57
|
+
* being present is what keeps the group on screen as a launcher. The group
|
|
58
|
+
* declares no `hideWhenEmpty`, so an empty value is a header with no rows.
|
|
59
|
+
*/
|
|
60
|
+
const publishView = () => host.context.client.setStatus(LOOP_VIEW_STATUS_KEY, formatLoopStatusView(launchers?.listInstances() ?? []));
|
|
61
|
+
/** Both surfaces a loop shows up on: the minor-mode catalog and the activity dock. */
|
|
62
|
+
const publishMode = () => {
|
|
63
|
+
modeOwner?.publish();
|
|
64
|
+
publishView();
|
|
65
|
+
};
|
|
66
|
+
const startActivity = async (execution) => {
|
|
67
|
+
if (launchers !== void 0) return async () => void 0;
|
|
68
|
+
const dependencies = {
|
|
69
|
+
generation: `${execution.sessionId}:loop-launchers`,
|
|
70
|
+
createInstanceId: () => crypto.randomUUID(),
|
|
71
|
+
timestamp: () => (/* @__PURE__ */ new Date()).toISOString()
|
|
72
|
+
};
|
|
73
|
+
const service = createDoomLoopLaunchersService(dependencies);
|
|
74
|
+
const registration = service.register({
|
|
75
|
+
id: DEFAULT_LAUNCHER_ID,
|
|
76
|
+
source: SOURCE,
|
|
77
|
+
label: "Default loop",
|
|
78
|
+
description: "Run a prompt in this session on an interval.",
|
|
79
|
+
async launch({ instanceId, signal }) {
|
|
80
|
+
const promptValue = await execution.client.request({
|
|
81
|
+
kind: "input",
|
|
82
|
+
title: "Loop prompt",
|
|
83
|
+
multiline: true
|
|
84
|
+
}, signal);
|
|
85
|
+
const prompt = typeof promptValue === "string" ? promptValue.trim() : "";
|
|
86
|
+
if (!prompt || signal.aborted) return void 0;
|
|
87
|
+
const interval = numberInput(await execution.client.request({
|
|
88
|
+
kind: "input",
|
|
89
|
+
title: "Loop interval in seconds",
|
|
90
|
+
initialValue: String(DEFAULT_INTERVAL_SECONDS)
|
|
91
|
+
}, signal), DEFAULT_INTERVAL_SECONDS);
|
|
92
|
+
let stopped = false;
|
|
93
|
+
const tick = () => {
|
|
94
|
+
if (stopped || signal.aborted) return;
|
|
95
|
+
execution.session.prompt(prompt).catch((error) => {
|
|
96
|
+
notify(execution, `Loop pass failed: ${error instanceof Error ? error.message : String(error)}`, "warning");
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
const timer = setInterval(tick, interval * 1e3);
|
|
100
|
+
timer.unref?.();
|
|
101
|
+
tick();
|
|
102
|
+
return {
|
|
103
|
+
instanceId,
|
|
104
|
+
label: "Default loop",
|
|
105
|
+
detail: `every ${interval}s`,
|
|
106
|
+
stop() {
|
|
107
|
+
stopped = true;
|
|
108
|
+
clearInterval(timer);
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
launchers = service;
|
|
114
|
+
defaultRegistration = registration;
|
|
115
|
+
unsubscribe = service.subscribe(publishMode);
|
|
116
|
+
publishMode();
|
|
117
|
+
return async () => {
|
|
118
|
+
unsubscribe?.();
|
|
119
|
+
unsubscribe = void 0;
|
|
120
|
+
await registration.dispose("Headless loop activity stopped.");
|
|
121
|
+
await service.dispose("Headless loop activity stopped.");
|
|
122
|
+
if (launchers === service) launchers = void 0;
|
|
123
|
+
if (defaultRegistration === registration) defaultRegistration = void 0;
|
|
124
|
+
publishMode();
|
|
125
|
+
};
|
|
126
|
+
};
|
|
127
|
+
modeOwner = defineMinorMode({
|
|
128
|
+
descriptor: {
|
|
129
|
+
source: SOURCE,
|
|
130
|
+
id: MODE_ID,
|
|
131
|
+
label: "Loop",
|
|
132
|
+
description: "Session-scoped recurring prompt loops.",
|
|
133
|
+
order: 60,
|
|
134
|
+
actions: [{
|
|
135
|
+
id: "start",
|
|
136
|
+
label: "Start",
|
|
137
|
+
description: "Start a loop with a registered launcher.",
|
|
138
|
+
contexts: ["headless"],
|
|
139
|
+
parameters: [{
|
|
140
|
+
name: "launcherId",
|
|
141
|
+
label: "Launcher",
|
|
142
|
+
kind: "string",
|
|
143
|
+
required: true,
|
|
144
|
+
minLength: 1
|
|
145
|
+
}, {
|
|
146
|
+
name: "instanceId",
|
|
147
|
+
label: "Instance ID",
|
|
148
|
+
kind: "string",
|
|
149
|
+
required: false,
|
|
150
|
+
minLength: 1
|
|
151
|
+
}]
|
|
152
|
+
}, {
|
|
153
|
+
id: "stop",
|
|
154
|
+
label: "Stop",
|
|
155
|
+
description: "Stop one active loop instance.",
|
|
156
|
+
contexts: ["headless"],
|
|
157
|
+
parameters: [{
|
|
158
|
+
name: "instanceId",
|
|
159
|
+
label: "Instance ID",
|
|
160
|
+
kind: "string",
|
|
161
|
+
required: true,
|
|
162
|
+
minLength: 1
|
|
163
|
+
}, {
|
|
164
|
+
name: "reason",
|
|
165
|
+
label: "Reason",
|
|
166
|
+
kind: "string",
|
|
167
|
+
required: false,
|
|
168
|
+
minLength: 1
|
|
169
|
+
}]
|
|
170
|
+
}]
|
|
171
|
+
},
|
|
172
|
+
state: modeState,
|
|
173
|
+
async handleAction(_runtime, actionId, argumentsValue, { signal }) {
|
|
174
|
+
signal.throwIfAborted();
|
|
175
|
+
if (actionId === "start" && !launchers) {
|
|
176
|
+
const modes = (host.context.selection.state?.["minor-mode"] ?? []).filter((mode) => mode !== MODE_ID);
|
|
177
|
+
await host.changeSelection({
|
|
178
|
+
axis: "state",
|
|
179
|
+
key: "minor-mode",
|
|
180
|
+
values: [...modes, MODE_ID]
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
if (!launchers) throw new Error("Loop activity is not active.");
|
|
184
|
+
if (actionId === "start") {
|
|
185
|
+
const instance = await launchers.launch(String(argumentsValue.launcherId ?? ""), argumentsValue.instanceId ? { instanceId: String(argumentsValue.instanceId) } : {});
|
|
186
|
+
publishMode();
|
|
187
|
+
return { message: instance ? `Loop '${instance.instanceId}' started.` : "Loop launch was cancelled." };
|
|
188
|
+
}
|
|
189
|
+
if (actionId === "stop") {
|
|
190
|
+
const stopped = await launchers.stop(String(argumentsValue.instanceId ?? ""), String(argumentsValue.reason ?? "Stopped through minor_mode."));
|
|
191
|
+
if (stopped && launchers.listInstances().length === 0) {
|
|
192
|
+
const modes = (host.context.selection.state?.["minor-mode"] ?? []).filter((mode) => mode !== MODE_ID);
|
|
193
|
+
await host.changeSelection({
|
|
194
|
+
axis: "state",
|
|
195
|
+
key: "minor-mode",
|
|
196
|
+
values: modes
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
publishMode();
|
|
200
|
+
return { message: stopped ? "Loop stopped." : "Loop instance was not active." };
|
|
201
|
+
}
|
|
202
|
+
throw new Error(`Unknown loop mode action: ${actionId}`);
|
|
203
|
+
}
|
|
204
|
+
}).createOwner(void 0);
|
|
205
|
+
return {
|
|
206
|
+
services: [serverMinorModes([modeOwner])],
|
|
207
|
+
resources: [{
|
|
208
|
+
when: {
|
|
209
|
+
state: { "minor-mode": MODE_ID },
|
|
210
|
+
attribution: {
|
|
211
|
+
kind: "minor",
|
|
212
|
+
mode: MODE_ID
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
name: "doompi-use-loop",
|
|
216
|
+
kind: "skill",
|
|
217
|
+
read: () => readPackageResource(import.meta.url, "src/prompts/doompi-use-loop/SKILL.md")
|
|
218
|
+
}],
|
|
219
|
+
activities: [{
|
|
220
|
+
when: {
|
|
221
|
+
state: { "minor-mode": MODE_ID },
|
|
222
|
+
attribution: {
|
|
223
|
+
kind: "minor",
|
|
224
|
+
mode: MODE_ID
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
name: "doompi-loop",
|
|
228
|
+
start: startActivity
|
|
229
|
+
}],
|
|
230
|
+
commands: [{
|
|
231
|
+
name: START_COMMAND_NAME,
|
|
232
|
+
description: "Start a registered loop.",
|
|
233
|
+
async execute(args, execution) {
|
|
234
|
+
if (!launchers) {
|
|
235
|
+
const modes = host.context.selection.state?.["minor-mode"] ?? [];
|
|
236
|
+
await host.changeSelection({
|
|
237
|
+
axis: "state",
|
|
238
|
+
key: "minor-mode",
|
|
239
|
+
values: [...modes, MODE_ID]
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
if (!launchers) throw new Error("Loop activity is not active.");
|
|
243
|
+
const available = launchers.listLaunchers();
|
|
244
|
+
const selected = args.trim() || await execution.client.request({
|
|
245
|
+
kind: "select",
|
|
246
|
+
title: "Choose a loop launcher",
|
|
247
|
+
options: available.map((entry) => ({
|
|
248
|
+
label: entry.label,
|
|
249
|
+
value: entry.id
|
|
250
|
+
}))
|
|
251
|
+
});
|
|
252
|
+
const launcherId = args.trim() || (typeof selected === "string" ? selected : "");
|
|
253
|
+
if (!launcherId) return;
|
|
254
|
+
const instance = await launchers.launch(launcherId);
|
|
255
|
+
publishMode();
|
|
256
|
+
await notify(execution, instance ? `Loop '${instance.instanceId}' started.` : "Loop launch was cancelled.");
|
|
257
|
+
}
|
|
258
|
+
}, {
|
|
259
|
+
name: LIST_COMMAND_NAME,
|
|
260
|
+
description: "List and stop active loops.",
|
|
261
|
+
async execute(_args, execution) {
|
|
262
|
+
const instances = launchers?.listInstances() ?? [];
|
|
263
|
+
await notify(execution, instances.length === 0 ? "No loops are active." : JSON.stringify(instances, null, 2));
|
|
264
|
+
}
|
|
265
|
+
}],
|
|
266
|
+
hooks: [{
|
|
267
|
+
event: "session_start",
|
|
268
|
+
async handle() {
|
|
269
|
+
publishView();
|
|
270
|
+
}
|
|
271
|
+
}, {
|
|
272
|
+
when: {
|
|
273
|
+
state: { "minor-mode": MODE_ID },
|
|
274
|
+
attribution: {
|
|
275
|
+
kind: "minor",
|
|
276
|
+
mode: MODE_ID
|
|
277
|
+
}
|
|
278
|
+
},
|
|
279
|
+
event: "session_shutdown",
|
|
280
|
+
async handle() {
|
|
281
|
+
await launchers?.stopAll("Headless session shutdown.");
|
|
282
|
+
}
|
|
283
|
+
}],
|
|
284
|
+
async onDispose() {
|
|
285
|
+
await defaultRegistration?.dispose("Headless loop facet disposed.");
|
|
286
|
+
await launchers?.dispose("Headless loop facet disposed.");
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
//#endregion
|
|
291
|
+
export { createSessionState };
|
|
292
|
+
|
|
293
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../../../src/services/serverRuntime/index.ts"],"sourcesContent":["import { serverMinorModes } from '@agimon-ai/doompi-minor-mode';\nimport { defineMinorMode, type MinorModeOwner, type MinorModeState } from '@agimon-ai/doompi-minor-mode';\n\nimport { LIST_COMMAND_NAME, START_COMMAND_NAME } from '../../constants/loop';\nimport type { DoomLoopLaunchersService, LoopLauncherRegistration, StoppableLoop } from '../../schemas/loopLaunchers';\nimport { createDoomLoopLaunchersService, type LoopLaunchersDependencies } from '../../services/loopLaunchers';\nimport { formatLoopStatusView, LOOP_VIEW_STATUS_KEY } from '../../types/loopView';\n\nconst SOURCE = '@agimon-ai/doompi-loop';\nconst MODE_ID = 'loop.active';\nconst DEFAULT_LAUNCHER_ID = 'doompi.default';\nconst DEFAULT_INTERVAL_SECONDS = 300;\nconst MIN_INTERVAL_SECONDS = 30;\nconst MAX_INTERVAL_SECONDS = 3600;\n\nfunction notify(\n context: {\n client: { notify(request: { body: string; level?: 'info' | 'warning' | 'error' }): void | Promise<void> };\n },\n body: string,\n level: 'info' | 'warning' | 'error' = 'info',\n): Promise<void> {\n return Promise.resolve(context.client.notify({ body, level }));\n}\n\nfunction numberInput(value: unknown, fallback: number): number {\n if (typeof value !== 'string' || value.trim() === '') return fallback;\n const parsed = Number(value);\n if (!Number.isInteger(parsed) || parsed < MIN_INTERVAL_SECONDS || parsed > MAX_INTERVAL_SECONDS) {\n throw new Error(`Interval must be between ${MIN_INTERVAL_SECONDS} and ${MAX_INTERVAL_SECONDS} seconds.`);\n }\n return parsed;\n}\n\nimport { type DoomHeadlessExecutionContext, type DoomHeadlessHostService } from '@agimon-ai/doompi-core/headless';\nimport { readPackageResource, type DoomServerSessionPlugin } from '@agimon-ai/doompi-core/server-facet';\nexport function createSessionState(host: DoomHeadlessHostService): DoomServerSessionPlugin {\n let launchers: DoomLoopLaunchersService | undefined;\n let defaultRegistration: LoopLauncherRegistration | undefined;\n let unsubscribe: (() => void) | undefined;\n let modeOwner: MinorModeOwner | undefined;\n const modeSelected = (): boolean => (host.context.selection.state?.['minor-mode'] ?? []).includes(MODE_ID);\n const modeState = (): MinorModeState => {\n const launcherCount = launchers?.listLaunchers().length ?? 0;\n const active = (launchers?.listInstances().length ?? 0) > 0;\n const canStart = launcherCount > 0 || !modeSelected();\n return {\n activation: active ? 'active' : 'inactive',\n condition: launchers?.listInstances().some(({ state }) => state !== 'running') ? 'queued' : 'ready',\n ...(active ? { detail: `${launchers?.listInstances().length} active`, color: 'warning' } : {}),\n actions: [\n {\n id: 'start',\n enabled: canStart,\n ...(canStart ? {} : { disabledReason: 'No loop launchers are registered.' }),\n },\n {\n id: 'stop',\n enabled: active,\n ...(active ? {} : { disabledReason: 'No loops are active.' }),\n },\n ],\n };\n };\n /**\n * The instances the activity dock lists.\n *\n * An empty list publishes an empty value rather than nothing, because the key\n * being present is what keeps the group on screen as a launcher. The group\n * declares no `hideWhenEmpty`, so an empty value is a header with no rows.\n */\n const publishView = (): void =>\n host.context.client.setStatus(LOOP_VIEW_STATUS_KEY, formatLoopStatusView(launchers?.listInstances() ?? []));\n /** Both surfaces a loop shows up on: the minor-mode catalog and the activity dock. */\n const publishMode = (): void => {\n modeOwner?.publish();\n publishView();\n };\n const startActivity = async (execution: typeof host.context): Promise<() => Promise<void>> => {\n if (launchers !== undefined) return async () => undefined;\n const dependencies: LoopLaunchersDependencies = {\n generation: `${execution.sessionId}:loop-launchers`,\n createInstanceId: () => crypto.randomUUID(),\n timestamp: () => new Date().toISOString(),\n };\n const service = createDoomLoopLaunchersService(dependencies);\n const registration = service.register({\n id: DEFAULT_LAUNCHER_ID,\n source: SOURCE,\n label: 'Default loop',\n description: 'Run a prompt in this session on an interval.',\n async launch({ instanceId, signal }): Promise<StoppableLoop | undefined> {\n const promptValue = await execution.client.request(\n { kind: 'input', title: 'Loop prompt', multiline: true },\n signal,\n );\n const prompt = typeof promptValue === 'string' ? promptValue.trim() : '';\n if (!prompt || signal.aborted) return undefined;\n const interval = numberInput(\n await execution.client.request(\n { kind: 'input', title: 'Loop interval in seconds', initialValue: String(DEFAULT_INTERVAL_SECONDS) },\n signal,\n ),\n DEFAULT_INTERVAL_SECONDS,\n );\n let stopped = false;\n const tick = (): void => {\n if (stopped || signal.aborted) return;\n void execution.session.prompt(prompt).catch((error) => {\n void notify(\n execution,\n `Loop pass failed: ${error instanceof Error ? error.message : String(error)}`,\n 'warning',\n );\n });\n };\n const timer = setInterval(tick, interval * 1000);\n timer.unref?.();\n tick();\n return {\n instanceId,\n label: 'Default loop',\n detail: `every ${interval}s`,\n stop() {\n stopped = true;\n clearInterval(timer);\n },\n };\n },\n });\n launchers = service;\n defaultRegistration = registration;\n unsubscribe = service.subscribe(publishMode);\n publishMode();\n return async () => {\n unsubscribe?.();\n unsubscribe = undefined;\n await registration.dispose('Headless loop activity stopped.');\n await service.dispose('Headless loop activity stopped.');\n if (launchers === service) launchers = undefined;\n if (defaultRegistration === registration) defaultRegistration = undefined;\n publishMode();\n };\n };\n modeOwner = defineMinorMode({\n descriptor: {\n source: SOURCE,\n id: MODE_ID,\n label: 'Loop',\n description: 'Session-scoped recurring prompt loops.',\n order: 60,\n actions: [\n {\n id: 'start',\n label: 'Start',\n description: 'Start a loop with a registered launcher.',\n contexts: ['headless'],\n parameters: [\n { name: 'launcherId', label: 'Launcher', kind: 'string', required: true, minLength: 1 },\n { name: 'instanceId', label: 'Instance ID', kind: 'string', required: false, minLength: 1 },\n ],\n },\n {\n id: 'stop',\n label: 'Stop',\n description: 'Stop one active loop instance.',\n contexts: ['headless'],\n parameters: [\n { name: 'instanceId', label: 'Instance ID', kind: 'string', required: true, minLength: 1 },\n { name: 'reason', label: 'Reason', kind: 'string', required: false, minLength: 1 },\n ],\n },\n ],\n },\n state: modeState,\n async handleAction(_runtime, actionId, argumentsValue, { signal }) {\n signal.throwIfAborted();\n if (actionId === 'start' && !launchers) {\n const modes = (host.context.selection.state?.['minor-mode'] ?? []).filter((mode) => mode !== MODE_ID);\n await host.changeSelection({ axis: 'state', key: 'minor-mode', values: [...modes, MODE_ID] });\n }\n if (!launchers) throw new Error('Loop activity is not active.');\n if (actionId === 'start') {\n const instance = await launchers.launch(\n String(argumentsValue.launcherId ?? ''),\n argumentsValue.instanceId ? { instanceId: String(argumentsValue.instanceId) } : {},\n );\n publishMode();\n return { message: instance ? `Loop '${instance.instanceId}' started.` : 'Loop launch was cancelled.' };\n }\n if (actionId === 'stop') {\n const stopped = await launchers.stop(\n String(argumentsValue.instanceId ?? ''),\n String(argumentsValue.reason ?? 'Stopped through minor_mode.'),\n );\n if (stopped && launchers.listInstances().length === 0) {\n const modes = (host.context.selection.state?.['minor-mode'] ?? []).filter((mode) => mode !== MODE_ID);\n await host.changeSelection({ axis: 'state', key: 'minor-mode', values: modes });\n }\n publishMode();\n return { message: stopped ? 'Loop stopped.' : 'Loop instance was not active.' };\n }\n throw new Error(`Unknown loop mode action: ${actionId}`);\n },\n }).createOwner(undefined);\n return {\n services: [serverMinorModes([modeOwner])],\n resources: [\n {\n when: { state: { 'minor-mode': MODE_ID }, attribution: { kind: 'minor', mode: MODE_ID } },\n name: 'doompi-use-loop',\n kind: 'skill',\n read: () => readPackageResource(import.meta.url, 'src/prompts/doompi-use-loop/SKILL.md'),\n },\n ],\n activities: [\n {\n when: { state: { 'minor-mode': MODE_ID }, attribution: { kind: 'minor', mode: MODE_ID } },\n name: 'doompi-loop',\n start: startActivity,\n },\n ],\n commands: [\n {\n name: START_COMMAND_NAME,\n description: 'Start a registered loop.',\n async execute(args: string, execution: DoomHeadlessExecutionContext) {\n if (!launchers) {\n const modes = host.context.selection.state?.['minor-mode'] ?? [];\n await host.changeSelection({ axis: 'state', key: 'minor-mode', values: [...modes, MODE_ID] });\n }\n if (!launchers) throw new Error('Loop activity is not active.');\n const available = launchers.listLaunchers();\n const selected =\n args.trim() ||\n (await execution.client.request({\n kind: 'select',\n title: 'Choose a loop launcher',\n options: available.map((entry) => ({ label: entry.label, value: entry.id })),\n }));\n const launcherId = args.trim() || (typeof selected === 'string' ? selected : '');\n if (!launcherId) return;\n const instance = await launchers.launch(launcherId);\n publishMode();\n await notify(execution, instance ? `Loop '${instance.instanceId}' started.` : 'Loop launch was cancelled.');\n },\n },\n {\n name: LIST_COMMAND_NAME,\n description: 'List and stop active loops.',\n async execute(_args: string, execution: DoomHeadlessExecutionContext) {\n const instances = launchers?.listInstances() ?? [];\n await notify(execution, instances.length === 0 ? 'No loops are active.' : JSON.stringify(instances, null, 2));\n },\n },\n ],\n hooks: [\n {\n // Ungated on purpose: the dock's loops group is a way in, so it has to\n // exist before anyone selects loop mode. The activity below is gated on\n // that selection and cannot do this job.\n event: 'session_start',\n async handle() {\n publishView();\n },\n },\n {\n when: { state: { 'minor-mode': MODE_ID }, attribution: { kind: 'minor', mode: MODE_ID } },\n event: 'session_shutdown',\n async handle() {\n await launchers?.stopAll('Headless session shutdown.');\n },\n },\n ],\n async onDispose() {\n await defaultRegistration?.dispose('Headless loop facet disposed.');\n await launchers?.dispose('Headless loop facet disposed.');\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
const require_piLoop = require("../constants/piLoop.cjs");
|
|
2
|
+
const require_loop = require("../constants/loop.cjs");
|
|
3
|
+
//#region src/tui/leader.ts
|
|
4
|
+
const LOOPS_GROUP_SEGMENT = {
|
|
5
|
+
key: "l",
|
|
6
|
+
label: "loops",
|
|
7
|
+
detail: "recurring prompt loops",
|
|
8
|
+
order: 60
|
|
9
|
+
};
|
|
10
|
+
function registerLeaderContribution(hub) {
|
|
11
|
+
const contribution = hub.registerLeader({
|
|
12
|
+
source: require_piLoop.PACKAGE_SOURCE,
|
|
13
|
+
bindings: [{
|
|
14
|
+
id: "loop.start",
|
|
15
|
+
path: [LOOPS_GROUP_SEGMENT, {
|
|
16
|
+
key: "s",
|
|
17
|
+
label: "start",
|
|
18
|
+
detail: "begin a recurring loop"
|
|
19
|
+
}],
|
|
20
|
+
command: { name: require_loop.START_COMMAND_NAME }
|
|
21
|
+
}, {
|
|
22
|
+
id: "loop.list",
|
|
23
|
+
path: [LOOPS_GROUP_SEGMENT, {
|
|
24
|
+
key: "l",
|
|
25
|
+
label: "list",
|
|
26
|
+
detail: "loops in this session"
|
|
27
|
+
}],
|
|
28
|
+
command: { name: require_loop.LIST_COMMAND_NAME }
|
|
29
|
+
}]
|
|
30
|
+
});
|
|
31
|
+
return () => contribution.dispose();
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
exports.registerLeaderContribution = registerLeaderContribution;
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=leader.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leader.cjs","names":["PACKAGE_SOURCE","START_COMMAND_NAME","LIST_COMMAND_NAME"],"sources":["../../../src/tui/leader.ts"],"sourcesContent":["import type { DoomUiHubService } from '@agimon-ai/doompi-core/ui-hub';\n\nimport { LIST_COMMAND_NAME, START_COMMAND_NAME } from '../constants/loop';\nimport { LOOPS_GROUP_ORDER, PACKAGE_SOURCE } from '../constants/piLoop';\n\nconst LOOPS_GROUP_SEGMENT = {\n key: 'l',\n label: 'loops',\n detail: 'recurring prompt loops',\n order: LOOPS_GROUP_ORDER,\n} as const;\n\nexport function registerLeaderContribution(hub: DoomUiHubService): () => void {\n const contribution = hub.registerLeader({\n source: PACKAGE_SOURCE,\n bindings: [\n {\n id: 'loop.start',\n path: [LOOPS_GROUP_SEGMENT, { key: 's', label: 'start', detail: 'begin a recurring loop' }],\n command: { name: START_COMMAND_NAME },\n },\n {\n id: 'loop.list',\n path: [LOOPS_GROUP_SEGMENT, { key: 'l', label: 'list', detail: 'loops in this session' }],\n command: { name: LIST_COMMAND_NAME },\n },\n ],\n });\n return () => contribution.dispose();\n}\n"],"mappings":";;;AAKA,MAAM,sBAAsB;CAC1B,KAAK;CACL,OAAO;CACP,QAAQ;CACR,OAAA;AACF;AAEA,SAAgB,2BAA2B,KAAmC;CAC5E,MAAM,eAAe,IAAI,eAAe;EACtC,QAAQA,eAAAA;EACR,UAAU,CACR;GACE,IAAI;GACJ,MAAM,CAAC,qBAAqB;IAAE,KAAK;IAAK,OAAO;IAAS,QAAQ;GAAyB,CAAC;GAC1F,SAAS,EAAE,MAAMC,aAAAA,mBAAmB;EACtC,GACA;GACE,IAAI;GACJ,MAAM,CAAC,qBAAqB;IAAE,KAAK;IAAK,OAAO;IAAQ,QAAQ;GAAwB,CAAC;GACxF,SAAS,EAAE,MAAMC,aAAAA,kBAAkB;EACrC,CACF;CACF,CAAC;CACD,aAAa,aAAa,QAAQ;AACpC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { PACKAGE_SOURCE } from "../constants/piLoop.mjs";
|
|
2
|
+
import { LIST_COMMAND_NAME, START_COMMAND_NAME } from "../constants/loop.mjs";
|
|
3
|
+
//#region src/tui/leader.ts
|
|
4
|
+
const LOOPS_GROUP_SEGMENT = {
|
|
5
|
+
key: "l",
|
|
6
|
+
label: "loops",
|
|
7
|
+
detail: "recurring prompt loops",
|
|
8
|
+
order: 60
|
|
9
|
+
};
|
|
10
|
+
function registerLeaderContribution(hub) {
|
|
11
|
+
const contribution = hub.registerLeader({
|
|
12
|
+
source: PACKAGE_SOURCE,
|
|
13
|
+
bindings: [{
|
|
14
|
+
id: "loop.start",
|
|
15
|
+
path: [LOOPS_GROUP_SEGMENT, {
|
|
16
|
+
key: "s",
|
|
17
|
+
label: "start",
|
|
18
|
+
detail: "begin a recurring loop"
|
|
19
|
+
}],
|
|
20
|
+
command: { name: START_COMMAND_NAME }
|
|
21
|
+
}, {
|
|
22
|
+
id: "loop.list",
|
|
23
|
+
path: [LOOPS_GROUP_SEGMENT, {
|
|
24
|
+
key: "l",
|
|
25
|
+
label: "list",
|
|
26
|
+
detail: "loops in this session"
|
|
27
|
+
}],
|
|
28
|
+
command: { name: LIST_COMMAND_NAME }
|
|
29
|
+
}]
|
|
30
|
+
});
|
|
31
|
+
return () => contribution.dispose();
|
|
32
|
+
}
|
|
33
|
+
//#endregion
|
|
34
|
+
export { registerLeaderContribution };
|
|
35
|
+
|
|
36
|
+
//# sourceMappingURL=leader.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"leader.mjs","names":[],"sources":["../../../src/tui/leader.ts"],"sourcesContent":["import type { DoomUiHubService } from '@agimon-ai/doompi-core/ui-hub';\n\nimport { LIST_COMMAND_NAME, START_COMMAND_NAME } from '../constants/loop';\nimport { LOOPS_GROUP_ORDER, PACKAGE_SOURCE } from '../constants/piLoop';\n\nconst LOOPS_GROUP_SEGMENT = {\n key: 'l',\n label: 'loops',\n detail: 'recurring prompt loops',\n order: LOOPS_GROUP_ORDER,\n} as const;\n\nexport function registerLeaderContribution(hub: DoomUiHubService): () => void {\n const contribution = hub.registerLeader({\n source: PACKAGE_SOURCE,\n bindings: [\n {\n id: 'loop.start',\n path: [LOOPS_GROUP_SEGMENT, { key: 's', label: 'start', detail: 'begin a recurring loop' }],\n command: { name: START_COMMAND_NAME },\n },\n {\n id: 'loop.list',\n path: [LOOPS_GROUP_SEGMENT, { key: 'l', label: 'list', detail: 'loops in this session' }],\n command: { name: LIST_COMMAND_NAME },\n },\n ],\n });\n return () => contribution.dispose();\n}\n"],"mappings":";;;AAKA,MAAM,sBAAsB;CAC1B,KAAK;CACL,OAAO;CACP,QAAQ;CACR,OAAA;AACF;AAEA,SAAgB,2BAA2B,KAAmC;CAC5E,MAAM,eAAe,IAAI,eAAe;EACtC,QAAQ;EACR,UAAU,CACR;GACE,IAAI;GACJ,MAAM,CAAC,qBAAqB;IAAE,KAAK;IAAK,OAAO;IAAS,QAAQ;GAAyB,CAAC;GAC1F,SAAS,EAAE,MAAM,mBAAmB;EACtC,GACA;GACE,IAAI;GACJ,MAAM,CAAC,qBAAqB;IAAE,KAAK;IAAK,OAAO;IAAQ,QAAQ;GAAwB,CAAC;GACxF,SAAS,EAAE,MAAM,kBAAkB;EACrC,CACF;CACF,CAAC;CACD,aAAa,aAAa,QAAQ;AACpC"}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
let _agimon_ai_doompi_ui_doom_overlay = require("@agimon-ai/doompi-ui/doom-overlay");
|
|
2
|
+
let _earendil_works_pi_tui = require("@earendil-works/pi-tui");
|
|
3
|
+
//#region src/tui/loopListOverlay.ts
|
|
4
|
+
const EMPTY_MESSAGE = "No loop instances are active in this session.";
|
|
5
|
+
const STOP_SCOPE_MESSAGE = "Stopping a loop does not cancel detached work already launched.";
|
|
6
|
+
const STOP_REASON = "Stopped from the loop list.";
|
|
7
|
+
var LoopListOverlay = class extends _agimon_ai_doompi_ui_doom_overlay.DoomOverlay {
|
|
8
|
+
client;
|
|
9
|
+
done;
|
|
10
|
+
unsubscribe;
|
|
11
|
+
instances;
|
|
12
|
+
selected = 0;
|
|
13
|
+
constructor(tui, theme, client, done) {
|
|
14
|
+
super(tui, theme);
|
|
15
|
+
this.client = client;
|
|
16
|
+
this.done = done;
|
|
17
|
+
this.instances = client.listInstances();
|
|
18
|
+
this.unsubscribe = client.subscribe(() => this.refresh());
|
|
19
|
+
}
|
|
20
|
+
dispose() {
|
|
21
|
+
this.unsubscribe();
|
|
22
|
+
}
|
|
23
|
+
handleInput(data) {
|
|
24
|
+
if ((0, _earendil_works_pi_tui.matchesKey)(data, "escape") || (0, _earendil_works_pi_tui.matchesKey)(data, "ctrl+c") || data === "q") {
|
|
25
|
+
this.done(void 0);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if ((0, _earendil_works_pi_tui.matchesKey)(data, "up") || data === "k") {
|
|
29
|
+
this.move(-1);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if ((0, _earendil_works_pi_tui.matchesKey)(data, "down") || data === "j") {
|
|
33
|
+
this.move(1);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (data === "x") {
|
|
37
|
+
const instance = this.instances[this.selected];
|
|
38
|
+
if (instance) this.client.stop(instance.instanceId, STOP_REASON);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
getChrome() {
|
|
42
|
+
return {
|
|
43
|
+
title: "LOOPS",
|
|
44
|
+
breadcrumb: "SPC › l / loops › l / list",
|
|
45
|
+
headerRight: `${this.instances.length} active`,
|
|
46
|
+
footer: `${_agimon_ai_doompi_ui_doom_overlay.DOOM_NAVIGATION_KEYS.list} move · x stop · esc close · launched detached work continues`,
|
|
47
|
+
footerHints: [
|
|
48
|
+
[_agimon_ai_doompi_ui_doom_overlay.DOOM_NAVIGATION_KEYS.list, "move"],
|
|
49
|
+
["x", "stop scheduling"],
|
|
50
|
+
["esc", "close"]
|
|
51
|
+
],
|
|
52
|
+
accent: _agimon_ai_doompi_ui_doom_overlay.DOOM_OVERLAY_ACCENT
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
renderBody(width) {
|
|
56
|
+
const stopScope = this.theme.fg("dim", (0, _earendil_works_pi_tui.truncateToWidth)(STOP_SCOPE_MESSAGE, width));
|
|
57
|
+
if (!this.instances.length) return [
|
|
58
|
+
this.theme.fg("dim", EMPTY_MESSAGE),
|
|
59
|
+
"",
|
|
60
|
+
stopScope
|
|
61
|
+
];
|
|
62
|
+
return [
|
|
63
|
+
stopScope,
|
|
64
|
+
"",
|
|
65
|
+
...this.instances.flatMap((instance, index) => {
|
|
66
|
+
const marker = index === this.selected ? this.theme.fg("accent", "▸") : " ";
|
|
67
|
+
const name = instance.label ?? instance.launcherLabel;
|
|
68
|
+
const state = this.theme.fg(instance.state === "running" ? "success" : "warning", instance.state);
|
|
69
|
+
const line = (0, _earendil_works_pi_tui.truncateToWidth)(`${marker} ${name} · ${state}`, width);
|
|
70
|
+
const detail = instance.detail ?? instance.instanceId;
|
|
71
|
+
return [line, ` ${this.theme.fg("dim", (0, _earendil_works_pi_tui.truncateToWidth)(detail, Math.max(0, width - 4)))}`];
|
|
72
|
+
})
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
refresh() {
|
|
76
|
+
this.instances = this.client.listInstances();
|
|
77
|
+
this.selected = Math.min(this.selected, Math.max(0, this.instances.length - 1));
|
|
78
|
+
this.invalidate();
|
|
79
|
+
}
|
|
80
|
+
move(delta) {
|
|
81
|
+
if (!this.instances.length) return;
|
|
82
|
+
this.selected = Math.max(0, Math.min(this.selected + delta, this.instances.length - 1));
|
|
83
|
+
this.invalidate();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
async function openLoopListOverlay(ctx, client) {
|
|
87
|
+
if (ctx.mode !== "tui") {
|
|
88
|
+
const instances = client.listInstances();
|
|
89
|
+
const labels = instances.map((instance) => `${instance.label ?? instance.launcherLabel} (${instance.instanceId})`);
|
|
90
|
+
const selected = await ctx.ui.select("Stop loop scheduling", labels);
|
|
91
|
+
const instance = instances[labels.indexOf(selected ?? "")];
|
|
92
|
+
if (instance) await client.stop(instance.instanceId, STOP_REASON);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
await ctx.ui.custom((tui, theme, _keybindings, done) => new LoopListOverlay(tui, theme, client, done), _agimon_ai_doompi_ui_doom_overlay.DOOM_FULLSCREEN_UI_OPTIONS);
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
exports.LoopListOverlay = LoopListOverlay;
|
|
99
|
+
exports.openLoopListOverlay = openLoopListOverlay;
|
|
100
|
+
|
|
101
|
+
//# sourceMappingURL=loopListOverlay.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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/doom-overlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\n\nimport type { DoomLoopLaunchersService, LoopInstanceSnapshot } from '../schemas/loopLaunchers';\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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"}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { DOOM_FULLSCREEN_UI_OPTIONS, DOOM_NAVIGATION_KEYS, DOOM_OVERLAY_ACCENT, DoomOverlay } from "@agimon-ai/doompi-ui/doom-overlay";
|
|
2
|
+
import { matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
|
|
3
|
+
//#region src/tui/loopListOverlay.ts
|
|
4
|
+
const EMPTY_MESSAGE = "No loop instances are active in this session.";
|
|
5
|
+
const STOP_SCOPE_MESSAGE = "Stopping a loop does not cancel detached work already launched.";
|
|
6
|
+
const STOP_REASON = "Stopped from the loop list.";
|
|
7
|
+
var LoopListOverlay = class extends DoomOverlay {
|
|
8
|
+
client;
|
|
9
|
+
done;
|
|
10
|
+
unsubscribe;
|
|
11
|
+
instances;
|
|
12
|
+
selected = 0;
|
|
13
|
+
constructor(tui, theme, client, done) {
|
|
14
|
+
super(tui, theme);
|
|
15
|
+
this.client = client;
|
|
16
|
+
this.done = done;
|
|
17
|
+
this.instances = client.listInstances();
|
|
18
|
+
this.unsubscribe = client.subscribe(() => this.refresh());
|
|
19
|
+
}
|
|
20
|
+
dispose() {
|
|
21
|
+
this.unsubscribe();
|
|
22
|
+
}
|
|
23
|
+
handleInput(data) {
|
|
24
|
+
if (matchesKey(data, "escape") || matchesKey(data, "ctrl+c") || data === "q") {
|
|
25
|
+
this.done(void 0);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
if (matchesKey(data, "up") || data === "k") {
|
|
29
|
+
this.move(-1);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (matchesKey(data, "down") || data === "j") {
|
|
33
|
+
this.move(1);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (data === "x") {
|
|
37
|
+
const instance = this.instances[this.selected];
|
|
38
|
+
if (instance) this.client.stop(instance.instanceId, STOP_REASON);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
getChrome() {
|
|
42
|
+
return {
|
|
43
|
+
title: "LOOPS",
|
|
44
|
+
breadcrumb: "SPC › l / loops › l / list",
|
|
45
|
+
headerRight: `${this.instances.length} active`,
|
|
46
|
+
footer: `${DOOM_NAVIGATION_KEYS.list} move · x stop · esc close · launched detached work continues`,
|
|
47
|
+
footerHints: [
|
|
48
|
+
[DOOM_NAVIGATION_KEYS.list, "move"],
|
|
49
|
+
["x", "stop scheduling"],
|
|
50
|
+
["esc", "close"]
|
|
51
|
+
],
|
|
52
|
+
accent: DOOM_OVERLAY_ACCENT
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
renderBody(width) {
|
|
56
|
+
const stopScope = this.theme.fg("dim", truncateToWidth(STOP_SCOPE_MESSAGE, width));
|
|
57
|
+
if (!this.instances.length) return [
|
|
58
|
+
this.theme.fg("dim", EMPTY_MESSAGE),
|
|
59
|
+
"",
|
|
60
|
+
stopScope
|
|
61
|
+
];
|
|
62
|
+
return [
|
|
63
|
+
stopScope,
|
|
64
|
+
"",
|
|
65
|
+
...this.instances.flatMap((instance, index) => {
|
|
66
|
+
const marker = index === this.selected ? this.theme.fg("accent", "▸") : " ";
|
|
67
|
+
const name = instance.label ?? instance.launcherLabel;
|
|
68
|
+
const state = this.theme.fg(instance.state === "running" ? "success" : "warning", instance.state);
|
|
69
|
+
const line = truncateToWidth(`${marker} ${name} · ${state}`, width);
|
|
70
|
+
const detail = instance.detail ?? instance.instanceId;
|
|
71
|
+
return [line, ` ${this.theme.fg("dim", truncateToWidth(detail, Math.max(0, width - 4)))}`];
|
|
72
|
+
})
|
|
73
|
+
];
|
|
74
|
+
}
|
|
75
|
+
refresh() {
|
|
76
|
+
this.instances = this.client.listInstances();
|
|
77
|
+
this.selected = Math.min(this.selected, Math.max(0, this.instances.length - 1));
|
|
78
|
+
this.invalidate();
|
|
79
|
+
}
|
|
80
|
+
move(delta) {
|
|
81
|
+
if (!this.instances.length) return;
|
|
82
|
+
this.selected = Math.max(0, Math.min(this.selected + delta, this.instances.length - 1));
|
|
83
|
+
this.invalidate();
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
async function openLoopListOverlay(ctx, client) {
|
|
87
|
+
if (ctx.mode !== "tui") {
|
|
88
|
+
const instances = client.listInstances();
|
|
89
|
+
const labels = instances.map((instance) => `${instance.label ?? instance.launcherLabel} (${instance.instanceId})`);
|
|
90
|
+
const selected = await ctx.ui.select("Stop loop scheduling", labels);
|
|
91
|
+
const instance = instances[labels.indexOf(selected ?? "")];
|
|
92
|
+
if (instance) await client.stop(instance.instanceId, STOP_REASON);
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
await ctx.ui.custom((tui, theme, _keybindings, done) => new LoopListOverlay(tui, theme, client, done), DOOM_FULLSCREEN_UI_OPTIONS);
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
export { LoopListOverlay, openLoopListOverlay };
|
|
99
|
+
|
|
100
|
+
//# sourceMappingURL=loopListOverlay.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
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/doom-overlay';\nimport type { ExtensionContext, Theme } from '@earendil-works/pi-coding-agent';\nimport { matchesKey, truncateToWidth } from '@earendil-works/pi-tui';\n\nimport type { DoomLoopLaunchersService, LoopInstanceSnapshot } from '../schemas/loopLaunchers';\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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"}
|