@agimon-ai/doompi-loop 0.0.1-alpha.9 → 0.0.1-alpha.91
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/apiContracts.cjs +17 -0
- package/dist/apiContracts.cjs.map +1 -0
- package/dist/apiContracts.d.mts +53 -0
- package/dist/apiContracts.d.mts.map +1 -0
- package/dist/apiContracts.mjs +13 -0
- package/dist/apiContracts.mjs.map +1 -0
- package/dist/extensions/pi.cjs +659 -1
- package/dist/extensions/pi.cjs.map +1 -1
- package/dist/extensions/pi.d.mts +5 -2
- package/dist/extensions/pi.d.mts.map +1 -0
- package/dist/extensions/pi.mjs +654 -1
- package/dist/extensions/pi.mjs.map +1 -1
- package/dist/extensions/server.cjs +369 -0
- package/dist/extensions/server.cjs.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 +364 -0
- package/dist/extensions/server.mjs.map +1 -0
- package/dist/extensions/web.mjs +331 -0
- package/dist/extensions/web.mjs.map +1 -0
- package/dist/index.cjs +0 -1
- package/dist/index.d.mts +2 -4
- package/dist/index.mjs +1 -1
- package/dist/loopLaunchers-Bciapxt0.d.mts +69 -0
- package/dist/loopLaunchers-Bciapxt0.d.mts.map +1 -0
- package/dist/loopLaunchers-BtcLH_xN.cjs +32 -0
- package/dist/loopLaunchers-BtcLH_xN.cjs.map +1 -0
- package/dist/loopLaunchers-cCniFbVA.mjs +15 -0
- package/dist/loopLaunchers-cCniFbVA.mjs.map +1 -0
- package/dist/loopLaunchers.cjs +5 -0
- package/dist/loopLaunchers.d.mts +2 -0
- package/dist/loopLaunchers.mjs +2 -0
- package/dist/loopView-BK4X_1UI.mjs +349 -0
- package/dist/loopView-BK4X_1UI.mjs.map +1 -0
- package/dist/loopView-C_OPuy8L.cjs +456 -0
- package/dist/loopView-C_OPuy8L.cjs.map +1 -0
- package/llms.txt +9 -0
- package/package.json +82 -14
- package/src/prompts/doompi-use-loop/SKILL.md +44 -0
- package/src/types/loopView.ts +73 -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/extensions/pi.d.cts +0 -2
- package/dist/index.d.cts +0 -4
- 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/dist/extensions/pi.cjs
CHANGED
|
@@ -1,2 +1,660 @@
|
|
|
1
|
-
|
|
1
|
+
Object.defineProperties(exports, {
|
|
2
|
+
__esModule: { value: true },
|
|
3
|
+
[Symbol.toStringTag]: { value: "Module" }
|
|
4
|
+
});
|
|
5
|
+
const require_loopLaunchers = require("../loopLaunchers-BtcLH_xN.cjs");
|
|
6
|
+
const require_loopView = require("../loopView-C_OPuy8L.cjs");
|
|
7
|
+
let _agimon_ai_doompi_core_piExtension = require("@agimon-ai/doompi-core/piExtension");
|
|
8
|
+
let _agimon_ai_doompi_core_extensionFile = require("@agimon-ai/doompi-core/extensionFile");
|
|
9
|
+
let _agimon_ai_doompi_minor_mode = require("@agimon-ai/doompi-minor-mode");
|
|
10
|
+
let _agimon_ai_doompi_core_cordisHost = require("@agimon-ai/doompi-core/cordisHost");
|
|
11
|
+
let _agimon_ai_doompi_core_uiHub = require("@agimon-ai/doompi-core/uiHub");
|
|
12
|
+
let croner = require("croner");
|
|
13
|
+
let _agimon_ai_doompi_ui_doomOverlay = require("@agimon-ai/doompi-ui/doomOverlay");
|
|
14
|
+
let _earendil_works_pi_tui = require("@earendil-works/pi-tui");
|
|
15
|
+
//#region src/services/loopCommand/index.ts
|
|
16
|
+
function createLoopCommands(handlers) {
|
|
17
|
+
return [[require_loopView.START_COMMAND_NAME, {
|
|
18
|
+
description: require_loopView.START_COMMAND_DESCRIPTION,
|
|
19
|
+
handler: async (args, ctx) => handlers.start(ctx, args)
|
|
20
|
+
}], [require_loopView.LIST_COMMAND_NAME, {
|
|
21
|
+
description: require_loopView.LIST_COMMAND_DESCRIPTION,
|
|
22
|
+
handler: async (args, ctx) => handlers.list(ctx, args)
|
|
23
|
+
}]];
|
|
24
|
+
}
|
|
25
|
+
//#endregion
|
|
26
|
+
//#region src/services/defaultLoopLauncher/index.ts
|
|
27
|
+
const DEFAULT_INTERVAL_SECONDS = 300;
|
|
28
|
+
const MIN_INTERVAL_SECONDS = 30;
|
|
29
|
+
const MAX_INTERVAL_SECONDS = 3600;
|
|
30
|
+
const MILLISECONDS_PER_SECOND = 1e3;
|
|
31
|
+
const PROMPT_PREVIEW_LENGTH = 80;
|
|
32
|
+
async function prepareLoop(ui, request, cron) {
|
|
33
|
+
if (request.input !== void 0) return cron ? require_loopView.parseCronLoop(request.input) : require_loopView.parseIntervalLoop(request.input);
|
|
34
|
+
if (request.interactive === false) throw new Error("Loop configuration is required for agent launches.");
|
|
35
|
+
const prompt = (await ui.editor("Loop prompt", ""))?.trim();
|
|
36
|
+
if (!prompt || request.signal.aborted) return void 0;
|
|
37
|
+
if (cron) {
|
|
38
|
+
const expression = await ui.input("Cron expression (five fields)", "0 * * * *");
|
|
39
|
+
if (expression === void 0 || request.signal.aborted) return void 0;
|
|
40
|
+
const timezone = await ui.input("Loop timezone", "UTC");
|
|
41
|
+
if (timezone === void 0 || request.signal.aborted) return void 0;
|
|
42
|
+
return require_loopView.parseCronLoop({
|
|
43
|
+
prompt,
|
|
44
|
+
cron: expression,
|
|
45
|
+
timezone: timezone.trim() || "UTC"
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
const raw = await ui.input("Loop interval in seconds", `Default: ${DEFAULT_INTERVAL_SECONDS}s`);
|
|
49
|
+
if (raw === void 0 || request.signal.aborted) return void 0;
|
|
50
|
+
const intervalSeconds = Number(raw.trim() || DEFAULT_INTERVAL_SECONDS);
|
|
51
|
+
if (!Number.isInteger(intervalSeconds) || intervalSeconds < MIN_INTERVAL_SECONDS || intervalSeconds > MAX_INTERVAL_SECONDS) {
|
|
52
|
+
ui.notify(`Interval must be between ${MIN_INTERVAL_SECONDS} and ${MAX_INTERVAL_SECONDS} seconds.`, require_loopView.NOTIFY_WARNING_LEVEL);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
return require_loopView.parseIntervalLoop({
|
|
56
|
+
prompt,
|
|
57
|
+
intervalSeconds
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
function promptPreview(prompt) {
|
|
61
|
+
const oneLine = prompt.replaceAll(/\s+/gu, " ").trim();
|
|
62
|
+
return oneLine.length > PROMPT_PREVIEW_LENGTH ? `${oneLine.slice(0, 79)}…` : oneLine;
|
|
63
|
+
}
|
|
64
|
+
function createDefaultLoopLauncher(pi) {
|
|
65
|
+
const pending = /* @__PURE__ */ new Set();
|
|
66
|
+
const requestPass = (loop) => {
|
|
67
|
+
if (loop.stopped) return;
|
|
68
|
+
if (!loop.context.isIdle()) {
|
|
69
|
+
pending.add(loop);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
pending.delete(loop);
|
|
73
|
+
try {
|
|
74
|
+
pi.sendUserMessage(loop.prompt);
|
|
75
|
+
} catch (error) {
|
|
76
|
+
loop.context.ui.notify(`Default loop pass could not start: ${error instanceof Error ? error.message : String(error)}`, require_loopView.NOTIFY_WARNING_LEVEL);
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const register = (ctx, launchers, cron) => launchers.register({
|
|
80
|
+
id: cron ? "doompi.cron" : "doompi.default",
|
|
81
|
+
source: require_loopView.PACKAGE_SOURCE,
|
|
82
|
+
label: cron ? "Cron loop" : "Default loop",
|
|
83
|
+
description: cron ? "Run a prompt on a five-field cron schedule." : "Run your own prompt in this session on an interval",
|
|
84
|
+
inputSchema: { ...cron ? require_loopView.CronLoopSchema : require_loopView.IntervalLoopSchema },
|
|
85
|
+
async launch(request) {
|
|
86
|
+
const prepared = await prepareLoop(ctx.ui, request, cron);
|
|
87
|
+
if (!prepared || request.signal.aborted) return void 0;
|
|
88
|
+
const loop = {
|
|
89
|
+
prompt: prepared.prompt,
|
|
90
|
+
context: ctx,
|
|
91
|
+
stopped: false
|
|
92
|
+
};
|
|
93
|
+
let stopTimer;
|
|
94
|
+
let schedule;
|
|
95
|
+
if ("cron" in prepared) {
|
|
96
|
+
const job = new croner.Cron(prepared.cron, {
|
|
97
|
+
timezone: prepared.timezone,
|
|
98
|
+
unref: true
|
|
99
|
+
}, () => requestPass(loop));
|
|
100
|
+
if (job.nextRun() === null) {
|
|
101
|
+
job.stop();
|
|
102
|
+
throw new Error("Cron expression has no future run.");
|
|
103
|
+
}
|
|
104
|
+
stopTimer = () => {
|
|
105
|
+
job.stop();
|
|
106
|
+
};
|
|
107
|
+
schedule = `${prepared.cron} (${prepared.timezone})`;
|
|
108
|
+
} else {
|
|
109
|
+
const timer = setInterval(() => requestPass(loop), prepared.intervalSeconds * MILLISECONDS_PER_SECOND);
|
|
110
|
+
timer.unref?.();
|
|
111
|
+
stopTimer = () => clearInterval(timer);
|
|
112
|
+
schedule = `every ${prepared.intervalSeconds}s`;
|
|
113
|
+
requestPass(loop);
|
|
114
|
+
}
|
|
115
|
+
return {
|
|
116
|
+
instanceId: request.instanceId,
|
|
117
|
+
label: cron ? "Cron loop" : "Default loop",
|
|
118
|
+
detail: `${schedule} · ${promptPreview(loop.prompt)}`,
|
|
119
|
+
stop() {
|
|
120
|
+
if (loop.stopped) return;
|
|
121
|
+
loop.stopped = true;
|
|
122
|
+
stopTimer();
|
|
123
|
+
pending.delete(loop);
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
return {
|
|
129
|
+
onAgentSettled() {
|
|
130
|
+
const next = pending.values().next().value;
|
|
131
|
+
if (next) requestPass(next);
|
|
132
|
+
},
|
|
133
|
+
register: (ctx, launchers) => register(ctx, launchers, false),
|
|
134
|
+
registerCron: (ctx, launchers) => register(ctx, launchers, true)
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/tui/leader.ts
|
|
139
|
+
const LOOPS_GROUP_SEGMENT = {
|
|
140
|
+
key: "l",
|
|
141
|
+
label: "loops",
|
|
142
|
+
detail: "recurring prompt loops",
|
|
143
|
+
order: 60
|
|
144
|
+
};
|
|
145
|
+
function registerLeaderContribution(hub) {
|
|
146
|
+
const contribution = hub.registerLeader({
|
|
147
|
+
source: require_loopView.PACKAGE_SOURCE,
|
|
148
|
+
bindings: [{
|
|
149
|
+
id: "loop.start",
|
|
150
|
+
path: [LOOPS_GROUP_SEGMENT, {
|
|
151
|
+
key: "s",
|
|
152
|
+
label: "start",
|
|
153
|
+
detail: "begin a recurring loop"
|
|
154
|
+
}],
|
|
155
|
+
command: { name: require_loopView.START_COMMAND_NAME }
|
|
156
|
+
}, {
|
|
157
|
+
id: "loop.list",
|
|
158
|
+
path: [LOOPS_GROUP_SEGMENT, {
|
|
159
|
+
key: "l",
|
|
160
|
+
label: "list",
|
|
161
|
+
detail: "loops in this session"
|
|
162
|
+
}],
|
|
163
|
+
command: { name: require_loopView.LIST_COMMAND_NAME }
|
|
164
|
+
}]
|
|
165
|
+
});
|
|
166
|
+
return () => contribution.dispose();
|
|
167
|
+
}
|
|
168
|
+
//#endregion
|
|
169
|
+
//#region src/tui/loopListOverlay.ts
|
|
170
|
+
const EMPTY_MESSAGE$1 = "No loop instances are active in this session.";
|
|
171
|
+
const STOP_SCOPE_MESSAGE = "Stopping a loop does not cancel detached work already launched.";
|
|
172
|
+
const STOP_REASON = "Stopped from the loop list.";
|
|
173
|
+
var LoopListOverlay = class extends _agimon_ai_doompi_ui_doomOverlay.DoomOverlay {
|
|
174
|
+
client;
|
|
175
|
+
done;
|
|
176
|
+
unsubscribe;
|
|
177
|
+
instances;
|
|
178
|
+
selected = 0;
|
|
179
|
+
constructor(tui, theme, client, done) {
|
|
180
|
+
super(tui, theme);
|
|
181
|
+
this.client = client;
|
|
182
|
+
this.done = done;
|
|
183
|
+
this.instances = client.listInstances();
|
|
184
|
+
this.unsubscribe = client.subscribe(() => this.refresh());
|
|
185
|
+
}
|
|
186
|
+
dispose() {
|
|
187
|
+
this.unsubscribe();
|
|
188
|
+
}
|
|
189
|
+
handleInput(data) {
|
|
190
|
+
if ((0, _earendil_works_pi_tui.matchesKey)(data, "escape") || (0, _earendil_works_pi_tui.matchesKey)(data, "ctrl+c") || data === "q") {
|
|
191
|
+
this.done(void 0);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
if ((0, _earendil_works_pi_tui.matchesKey)(data, "up") || data === "k") {
|
|
195
|
+
this.move(-1);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if ((0, _earendil_works_pi_tui.matchesKey)(data, "down") || data === "j") {
|
|
199
|
+
this.move(1);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (data === "x") {
|
|
203
|
+
const instance = this.instances[this.selected];
|
|
204
|
+
if (instance) this.client.stop(instance.instanceId, STOP_REASON);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
getChrome() {
|
|
208
|
+
return {
|
|
209
|
+
title: "LOOPS",
|
|
210
|
+
breadcrumb: "SPC › l / loops › l / list",
|
|
211
|
+
headerRight: `${this.instances.length} active`,
|
|
212
|
+
footer: `${_agimon_ai_doompi_ui_doomOverlay.DOOM_NAVIGATION_KEYS.list} move · x stop · esc close · launched detached work continues`,
|
|
213
|
+
footerHints: [
|
|
214
|
+
[_agimon_ai_doompi_ui_doomOverlay.DOOM_NAVIGATION_KEYS.list, "move"],
|
|
215
|
+
["x", "stop scheduling"],
|
|
216
|
+
["esc", "close"]
|
|
217
|
+
],
|
|
218
|
+
accent: _agimon_ai_doompi_ui_doomOverlay.DOOM_OVERLAY_ACCENT
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
renderBody(width) {
|
|
222
|
+
const stopScope = this.theme.fg("dim", (0, _earendil_works_pi_tui.truncateToWidth)(STOP_SCOPE_MESSAGE, width));
|
|
223
|
+
if (!this.instances.length) return [
|
|
224
|
+
this.theme.fg("dim", EMPTY_MESSAGE$1),
|
|
225
|
+
"",
|
|
226
|
+
stopScope
|
|
227
|
+
];
|
|
228
|
+
return [
|
|
229
|
+
stopScope,
|
|
230
|
+
"",
|
|
231
|
+
...this.instances.flatMap((instance, index) => {
|
|
232
|
+
const marker = index === this.selected ? this.theme.fg("accent", "▸") : " ";
|
|
233
|
+
const name = instance.label ?? instance.launcherLabel;
|
|
234
|
+
const state = this.theme.fg(instance.state === "running" ? "success" : "warning", instance.state);
|
|
235
|
+
const line = (0, _earendil_works_pi_tui.truncateToWidth)(`${marker} ${name} · ${state}`, width);
|
|
236
|
+
const detail = instance.detail ?? instance.instanceId;
|
|
237
|
+
return [line, ` ${this.theme.fg("dim", (0, _earendil_works_pi_tui.truncateToWidth)(detail, Math.max(0, width - 4)))}`];
|
|
238
|
+
})
|
|
239
|
+
];
|
|
240
|
+
}
|
|
241
|
+
refresh() {
|
|
242
|
+
this.instances = this.client.listInstances();
|
|
243
|
+
this.selected = Math.min(this.selected, Math.max(0, this.instances.length - 1));
|
|
244
|
+
this.invalidate();
|
|
245
|
+
}
|
|
246
|
+
move(delta) {
|
|
247
|
+
if (!this.instances.length) return;
|
|
248
|
+
this.selected = Math.max(0, Math.min(this.selected + delta, this.instances.length - 1));
|
|
249
|
+
this.invalidate();
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
async function openLoopListOverlay(ctx, client) {
|
|
253
|
+
if (ctx.mode !== "tui") {
|
|
254
|
+
const instances = client.listInstances();
|
|
255
|
+
const labels = instances.map((instance) => `${instance.label ?? instance.launcherLabel} (${instance.instanceId})`);
|
|
256
|
+
const selected = await ctx.ui.select("Stop loop scheduling", labels);
|
|
257
|
+
const instance = instances[labels.indexOf(selected ?? "")];
|
|
258
|
+
if (instance) await client.stop(instance.instanceId, STOP_REASON);
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
await ctx.ui.custom((tui, theme, _keybindings, done) => new LoopListOverlay(tui, theme, client, done), _agimon_ai_doompi_ui_doomOverlay.DOOM_FULLSCREEN_UI_OPTIONS);
|
|
262
|
+
}
|
|
263
|
+
//#endregion
|
|
264
|
+
//#region src/tui/startLoopOverlay.ts
|
|
265
|
+
const EMPTY_MESSAGE = "No loop launchers are registered for this session.";
|
|
266
|
+
var StartLoopOverlay = class extends _agimon_ai_doompi_ui_doomOverlay.DoomOverlay {
|
|
267
|
+
launchers;
|
|
268
|
+
done;
|
|
269
|
+
selected = 0;
|
|
270
|
+
constructor(tui, theme, launchers, done) {
|
|
271
|
+
super(tui, theme);
|
|
272
|
+
this.launchers = launchers;
|
|
273
|
+
this.done = done;
|
|
274
|
+
}
|
|
275
|
+
handleInput(data) {
|
|
276
|
+
if ((0, _earendil_works_pi_tui.matchesKey)(data, "escape") || (0, _earendil_works_pi_tui.matchesKey)(data, "ctrl+c") || data === "q") {
|
|
277
|
+
this.done(void 0);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if ((0, _earendil_works_pi_tui.matchesKey)(data, "up") || data === "k") {
|
|
281
|
+
this.move(-1);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if ((0, _earendil_works_pi_tui.matchesKey)(data, "down") || data === "j") {
|
|
285
|
+
this.move(1);
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if ((0, _earendil_works_pi_tui.matchesKey)(data, "enter")) this.done(this.launchers[this.selected]?.id);
|
|
289
|
+
}
|
|
290
|
+
getChrome() {
|
|
291
|
+
return {
|
|
292
|
+
title: "START LOOP",
|
|
293
|
+
breadcrumb: "SPC › l / loops › s / start",
|
|
294
|
+
footer: `${_agimon_ai_doompi_ui_doomOverlay.DOOM_NAVIGATION_KEYS.list} move · enter launch · esc close`,
|
|
295
|
+
footerHints: [
|
|
296
|
+
[_agimon_ai_doompi_ui_doomOverlay.DOOM_NAVIGATION_KEYS.list, "move"],
|
|
297
|
+
["enter", "launch"],
|
|
298
|
+
["esc", "close"]
|
|
299
|
+
],
|
|
300
|
+
accent: _agimon_ai_doompi_ui_doomOverlay.DOOM_OVERLAY_ACCENT
|
|
301
|
+
};
|
|
302
|
+
}
|
|
303
|
+
renderBody(width) {
|
|
304
|
+
if (!this.launchers.length) return [this.theme.fg("dim", EMPTY_MESSAGE)];
|
|
305
|
+
return this.launchers.flatMap((launcher, index) => {
|
|
306
|
+
const selected = index === this.selected;
|
|
307
|
+
const marker = selected ? this.theme.fg("accent", "▸") : " ";
|
|
308
|
+
const label = selected ? this.theme.bold(launcher.label) : launcher.label;
|
|
309
|
+
const lines = [`${marker} ${(0, _earendil_works_pi_tui.truncateToWidth)(label, Math.max(0, width - 2))}`];
|
|
310
|
+
if (launcher.description) lines.push(` ${this.theme.fg("dim", (0, _earendil_works_pi_tui.truncateToWidth)(launcher.description, Math.max(0, width - 4)))}`);
|
|
311
|
+
return lines;
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
move(delta) {
|
|
315
|
+
if (!this.launchers.length) return;
|
|
316
|
+
this.selected = Math.max(0, Math.min(this.selected + delta, this.launchers.length - 1));
|
|
317
|
+
this.invalidate();
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
async function openStartLoopOverlay(ctx, launchers) {
|
|
321
|
+
if (ctx.mode !== "tui") {
|
|
322
|
+
const labels = launchers.map((launcher) => launcher.label);
|
|
323
|
+
const selected = await ctx.ui.select("Start loop", labels);
|
|
324
|
+
return launchers[labels.indexOf(selected ?? "")]?.id;
|
|
325
|
+
}
|
|
326
|
+
return ctx.ui.custom((tui, theme, _keybindings, done) => new StartLoopOverlay(tui, theme, launchers, done), _agimon_ai_doompi_ui_doomOverlay.DOOM_FULLSCREEN_UI_OPTIONS);
|
|
327
|
+
}
|
|
328
|
+
//#endregion
|
|
329
|
+
//#region src/tui/loopRuntime.ts
|
|
330
|
+
const MODE_ACTION_ACTIVATE = "activate";
|
|
331
|
+
const MODE_ACTION_DEACTIVATE = "deactivate";
|
|
332
|
+
const SESSION_REPLACED_REASON = "Pi session replaced.";
|
|
333
|
+
const SESSION_SHUTDOWN_REASON = "Pi session shut down.";
|
|
334
|
+
function loopModeState(enabled, instances) {
|
|
335
|
+
return {
|
|
336
|
+
activation: enabled ? "active" : "inactive",
|
|
337
|
+
condition: "ready",
|
|
338
|
+
...enabled ? {
|
|
339
|
+
detail: `${instances.length} loops`,
|
|
340
|
+
color: require_loopView.ACTIVE_MODE_COLOR
|
|
341
|
+
} : {},
|
|
342
|
+
actions: [{
|
|
343
|
+
id: MODE_ACTION_ACTIVATE,
|
|
344
|
+
enabled: !enabled,
|
|
345
|
+
...enabled ? { disabledReason: "Loop mode is already active." } : {}
|
|
346
|
+
}, {
|
|
347
|
+
id: MODE_ACTION_DEACTIVATE,
|
|
348
|
+
enabled,
|
|
349
|
+
...enabled ? {} : { disabledReason: "Loop mode is not active." }
|
|
350
|
+
}]
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
/** Install Loop state and external resources into its host-owned Cordis plugin fiber. */
|
|
354
|
+
function createLoopPiRuntime(pi) {
|
|
355
|
+
let active = true;
|
|
356
|
+
let activeSession;
|
|
357
|
+
let mode;
|
|
358
|
+
let disposeLeader;
|
|
359
|
+
const launchesInProgress = /* @__PURE__ */ new WeakSet();
|
|
360
|
+
let modeEnabled = false;
|
|
361
|
+
const toolListeners = /* @__PURE__ */ new Set();
|
|
362
|
+
const loopTools = require_loopView.createLoopTools(() => {
|
|
363
|
+
if (!activeSession) throw new Error("Loop launchers are unavailable for the active session.");
|
|
364
|
+
return activeSession.launchers;
|
|
365
|
+
}, () => {
|
|
366
|
+
if (!active || !modeEnabled) throw new Error("Enable Loop minor mode before using agent loop tools.");
|
|
367
|
+
});
|
|
368
|
+
const toolNames = new Set(loopTools.map((tool) => tool.name));
|
|
369
|
+
const currentSession = (context) => {
|
|
370
|
+
const binding = activeSession;
|
|
371
|
+
if (!active || !binding || binding.context.sessionManager !== context.sessionManager || binding.hostSession.sessionId !== context.sessionManager.getSessionId()) return;
|
|
372
|
+
return binding;
|
|
373
|
+
};
|
|
374
|
+
const clearStatuses = (binding) => {
|
|
375
|
+
binding.context.ui?.setStatus(require_loopView.STATUS_KEY, void 0);
|
|
376
|
+
if (binding.context.mode !== "tui") binding.context.ui?.setStatus(require_loopView.LOOP_VIEW_STATUS_KEY, void 0);
|
|
377
|
+
};
|
|
378
|
+
const publishStatus = (binding) => {
|
|
379
|
+
if (!active || activeSession !== binding) return;
|
|
380
|
+
const instances = binding.launchers.listInstances();
|
|
381
|
+
binding.context.ui?.setStatus(require_loopView.STATUS_KEY, instances.length ? `loops: ${instances.length}` : void 0);
|
|
382
|
+
if (binding.context.mode !== "tui") binding.context.ui?.setStatus(require_loopView.LOOP_VIEW_STATUS_KEY, require_loopView.formatLoopStatusView(instances));
|
|
383
|
+
mode?.publish();
|
|
384
|
+
};
|
|
385
|
+
const defaultLauncher = createDefaultLoopLauncher(pi);
|
|
386
|
+
mode = (0, _agimon_ai_doompi_minor_mode.defineMinorMode)({
|
|
387
|
+
descriptor: {
|
|
388
|
+
source: require_loopView.PACKAGE_SOURCE,
|
|
389
|
+
id: require_loopView.MODE_STATUS_ID,
|
|
390
|
+
label: "Loop",
|
|
391
|
+
description: "Give the agent tools to create, inspect, and stop session loops.",
|
|
392
|
+
order: 60,
|
|
393
|
+
actions: [{
|
|
394
|
+
id: MODE_ACTION_ACTIVATE,
|
|
395
|
+
label: "Activate",
|
|
396
|
+
description: "Expose loop tools to the agent.",
|
|
397
|
+
contexts: ["tui", "headless"],
|
|
398
|
+
parameters: []
|
|
399
|
+
}, {
|
|
400
|
+
id: MODE_ACTION_DEACTIVATE,
|
|
401
|
+
label: "Deactivate",
|
|
402
|
+
description: "Hide agent loop tools. Existing loops and manual controls remain available.",
|
|
403
|
+
contexts: ["tui", "headless"],
|
|
404
|
+
parameters: []
|
|
405
|
+
}]
|
|
406
|
+
},
|
|
407
|
+
state: () => loopModeState(modeEnabled, activeSession?.launchers.listInstances() ?? []),
|
|
408
|
+
async handleAction(_runtime, actionId, _arguments, { signal }) {
|
|
409
|
+
signal.throwIfAborted();
|
|
410
|
+
const binding = activeSession ? currentSession(activeSession.context) : void 0;
|
|
411
|
+
if (!binding) throw new Error("Loop launchers are unavailable for the active session.");
|
|
412
|
+
if (actionId !== MODE_ACTION_ACTIVATE && actionId !== MODE_ACTION_DEACTIVATE) throw new Error(`Unknown loop mode action: ${actionId}`);
|
|
413
|
+
modeEnabled = actionId === MODE_ACTION_ACTIVATE;
|
|
414
|
+
for (const listener of toolListeners) listener();
|
|
415
|
+
publishStatus(binding);
|
|
416
|
+
return { message: modeEnabled ? "Loop tools activated." : "Loop tools deactivated. Existing loops are unchanged." };
|
|
417
|
+
}
|
|
418
|
+
}).createOwner(void 0);
|
|
419
|
+
return {
|
|
420
|
+
onAgentSettled: defaultLauncher.onAgentSettled,
|
|
421
|
+
loopTools,
|
|
422
|
+
toolRestrictions: [{
|
|
423
|
+
source: require_loopView.PACKAGE_SOURCE,
|
|
424
|
+
restrict: (incoming) => modeEnabled ? incoming : incoming.filter((name) => !toolNames.has(name)),
|
|
425
|
+
subscribe(listener) {
|
|
426
|
+
toolListeners.add(listener);
|
|
427
|
+
return () => {
|
|
428
|
+
toolListeners.delete(listener);
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
}],
|
|
432
|
+
services: [(cordis) => {
|
|
433
|
+
cordis.inject([_agimon_ai_doompi_core_uiHub.DOOM_UI_HUB_SERVICE], (uiContext) => {
|
|
434
|
+
const dispose = registerLeaderContribution((0, _agimon_ai_doompi_core_uiHub.requireDoomUiHub)(uiContext));
|
|
435
|
+
disposeLeader = dispose;
|
|
436
|
+
return () => {
|
|
437
|
+
dispose();
|
|
438
|
+
if (disposeLeader === dispose) disposeLeader = void 0;
|
|
439
|
+
};
|
|
440
|
+
});
|
|
441
|
+
cordis.inject([_agimon_ai_doompi_core_cordisHost.DOOM_CORDIS_SESSION_SERVICE], async (sessionContext) => {
|
|
442
|
+
const hostSession = sessionContext.get(_agimon_ai_doompi_core_cordisHost.DOOM_CORDIS_SESSION_SERVICE);
|
|
443
|
+
const launchers = require_loopView.createDoomLoopLaunchersService({
|
|
444
|
+
generation: `${hostSession.generation}:loop-launchers`,
|
|
445
|
+
createInstanceId: () => crypto.randomUUID(),
|
|
446
|
+
timestamp: () => (/* @__PURE__ */ new Date()).toISOString()
|
|
447
|
+
});
|
|
448
|
+
const binding = {
|
|
449
|
+
context: hostSession.context,
|
|
450
|
+
hostSession,
|
|
451
|
+
launchers
|
|
452
|
+
};
|
|
453
|
+
activeSession = binding;
|
|
454
|
+
modeEnabled = false;
|
|
455
|
+
for (const listener of toolListeners) listener();
|
|
456
|
+
const provider = cordis.plugin((providerContext) => {
|
|
457
|
+
providerContext.provide(require_loopLaunchers.DOOM_LOOP_LAUNCHERS_SERVICE, launchers);
|
|
458
|
+
});
|
|
459
|
+
let defaultRegistration;
|
|
460
|
+
let cronRegistration;
|
|
461
|
+
let unsubscribe;
|
|
462
|
+
try {
|
|
463
|
+
defaultRegistration = defaultLauncher.register(hostSession.context, launchers);
|
|
464
|
+
cronRegistration = defaultLauncher.registerCron(hostSession.context, launchers);
|
|
465
|
+
unsubscribe = launchers.subscribe(() => publishStatus(binding));
|
|
466
|
+
publishStatus(binding);
|
|
467
|
+
} catch (error) {
|
|
468
|
+
if (activeSession === binding) activeSession = void 0;
|
|
469
|
+
await launchers.dispose(SESSION_REPLACED_REASON);
|
|
470
|
+
await provider.dispose();
|
|
471
|
+
throw error;
|
|
472
|
+
}
|
|
473
|
+
return async () => {
|
|
474
|
+
if (activeSession === binding) activeSession = void 0;
|
|
475
|
+
modeEnabled = false;
|
|
476
|
+
for (const listener of toolListeners) listener();
|
|
477
|
+
unsubscribe();
|
|
478
|
+
clearStatuses(binding);
|
|
479
|
+
mode?.publish();
|
|
480
|
+
const cleanupErrors = [];
|
|
481
|
+
try {
|
|
482
|
+
await Promise.all([defaultRegistration.dispose(SESSION_REPLACED_REASON), cronRegistration.dispose(SESSION_REPLACED_REASON)]);
|
|
483
|
+
} catch (error) {
|
|
484
|
+
cleanupErrors.push(error);
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
await launchers.dispose(SESSION_SHUTDOWN_REASON);
|
|
488
|
+
} catch (error) {
|
|
489
|
+
cleanupErrors.push(error);
|
|
490
|
+
}
|
|
491
|
+
try {
|
|
492
|
+
await provider.dispose();
|
|
493
|
+
} catch (error) {
|
|
494
|
+
cleanupErrors.push(error);
|
|
495
|
+
}
|
|
496
|
+
if (cleanupErrors.length > 0) throw new AggregateError(cleanupErrors, "Loop session cleanup failed.");
|
|
497
|
+
};
|
|
498
|
+
});
|
|
499
|
+
}],
|
|
500
|
+
handlers: {
|
|
501
|
+
async start(ctx, args) {
|
|
502
|
+
const binding = currentSession(ctx);
|
|
503
|
+
if (!binding) return;
|
|
504
|
+
if (launchesInProgress.has(binding)) {
|
|
505
|
+
ctx.ui.notify("A loop setup is already in progress.", require_loopView.NOTIFY_INFO_LEVEL);
|
|
506
|
+
return;
|
|
507
|
+
}
|
|
508
|
+
launchesInProgress.add(binding);
|
|
509
|
+
try {
|
|
510
|
+
const launchers = binding.launchers.listLaunchers();
|
|
511
|
+
if (!launchers.length) {
|
|
512
|
+
ctx.ui.notify("No loop launchers are registered for this session.", require_loopView.NOTIFY_INFO_LEVEL);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const argument = args.trim();
|
|
516
|
+
let launcherId;
|
|
517
|
+
if (argument) {
|
|
518
|
+
if (argument.split(/\s+/u).length !== 1) {
|
|
519
|
+
ctx.ui.notify("Usage: /loop [launcherId]", "error");
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (!launchers.some((launcher) => launcher.id === argument)) {
|
|
523
|
+
ctx.ui.notify(`Unknown loop launcher: ${argument}`, "error");
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
launcherId = argument;
|
|
527
|
+
} else {
|
|
528
|
+
launcherId = await openStartLoopOverlay(ctx, launchers);
|
|
529
|
+
if (activeSession !== binding || !launcherId) return;
|
|
530
|
+
}
|
|
531
|
+
const instance = await binding.launchers.launch(launcherId);
|
|
532
|
+
if (activeSession !== binding) return;
|
|
533
|
+
if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, require_loopView.NOTIFY_INFO_LEVEL);
|
|
534
|
+
} catch (error) {
|
|
535
|
+
if (activeSession !== binding) return;
|
|
536
|
+
ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
537
|
+
} finally {
|
|
538
|
+
launchesInProgress.delete(binding);
|
|
539
|
+
}
|
|
540
|
+
},
|
|
541
|
+
async list(ctx, args = "") {
|
|
542
|
+
const binding = currentSession(ctx);
|
|
543
|
+
if (!binding) return;
|
|
544
|
+
if (args.trim().startsWith("stop ")) {
|
|
545
|
+
const stopped = await binding.launchers.stop(args.trim().slice(5).trim(), "Stopped manually.");
|
|
546
|
+
ctx.ui.notify(stopped ? "Loop stopped." : "Loop instance was not active.", require_loopView.NOTIFY_INFO_LEVEL);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
if (args.trim()) throw new Error("Usage: /loops [stop instanceId]");
|
|
550
|
+
await openLoopListOverlay(ctx, binding.launchers);
|
|
551
|
+
}
|
|
552
|
+
},
|
|
553
|
+
minorModes: [mode],
|
|
554
|
+
onDispose() {
|
|
555
|
+
active = false;
|
|
556
|
+
const binding = activeSession;
|
|
557
|
+
activeSession = void 0;
|
|
558
|
+
modeEnabled = false;
|
|
559
|
+
for (const listener of toolListeners) listener();
|
|
560
|
+
toolListeners.clear();
|
|
561
|
+
if (binding) clearStatuses(binding);
|
|
562
|
+
mode?.publish();
|
|
563
|
+
disposeLeader?.();
|
|
564
|
+
disposeLeader = void 0;
|
|
565
|
+
}
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
//#endregion
|
|
569
|
+
//#region src/extensions/workspaces/sessions/(backend)/_lib/runtime.cli.ts
|
|
570
|
+
var runtime_cli_default = (({ pi }) => {
|
|
571
|
+
const runtime = createLoopPiRuntime(pi);
|
|
572
|
+
return {
|
|
573
|
+
services: [...runtime.services ?? [], (0, _agimon_ai_doompi_minor_mode.piMinorModes)(runtime.minorModes)],
|
|
574
|
+
loopTools: runtime.loopTools,
|
|
575
|
+
toolRestrictions: runtime.toolRestrictions,
|
|
576
|
+
events: { agent_settled: runtime.onAgentSettled },
|
|
577
|
+
commands: createLoopCommands(runtime.handlers),
|
|
578
|
+
onDispose: () => runtime.onDispose(),
|
|
579
|
+
resources: [{
|
|
580
|
+
source: require_loopView.PACKAGE_SOURCE,
|
|
581
|
+
moduleUrl: require("url").pathToFileURL(__filename).href,
|
|
582
|
+
skills: [{
|
|
583
|
+
name: "doompi-use-loop",
|
|
584
|
+
description: "Use Doom Pi Loop to start, inspect, and stop session-scoped recurring prompts safely."
|
|
585
|
+
}]
|
|
586
|
+
}]
|
|
587
|
+
};
|
|
588
|
+
});
|
|
589
|
+
//#endregion
|
|
590
|
+
//#region src/extensions/workspaces/sessions/(backend)/root.cli.ts
|
|
591
|
+
var root_cli_default = (0, _agimon_ai_doompi_core_extensionFile.defineRoot)((context) => {
|
|
592
|
+
const value = runtime_cli_default(context);
|
|
593
|
+
return {
|
|
594
|
+
value,
|
|
595
|
+
services: value.services,
|
|
596
|
+
onDispose: value.onDispose
|
|
597
|
+
};
|
|
598
|
+
});
|
|
599
|
+
//#endregion
|
|
600
|
+
//#region src/extensions/workspaces/sessions/(backend)/command/loop.cli.ts
|
|
601
|
+
var loop_cli_default$1 = (0, _agimon_ai_doompi_core_extensionFile.defineCliCommand)((context) => context.root.commands[0]);
|
|
602
|
+
//#endregion
|
|
603
|
+
//#region src/extensions/workspaces/sessions/(backend)/command/loops.cli.ts
|
|
604
|
+
var loops_cli_default = (0, _agimon_ai_doompi_core_extensionFile.defineCliCommand)((context) => context.root.commands[1]);
|
|
605
|
+
//#endregion
|
|
606
|
+
//#region src/extensions/workspaces/sessions/(backend)/hook/agent-settled.cli.ts
|
|
607
|
+
var agent_settled_cli_default = (0, _agimon_ai_doompi_core_extensionFile.defineCliHook)((context) => context.root.events["agent_settled"]);
|
|
608
|
+
//#endregion
|
|
609
|
+
//#region src/extensions/workspaces/sessions/(backend)/resource/doompi-use-loop.cli.ts
|
|
610
|
+
var doompi_use_loop_cli_default = (0, _agimon_ai_doompi_core_extensionFile.defineResource)((context) => context.root.resources[0]);
|
|
611
|
+
//#endregion
|
|
612
|
+
//#region src/extensions/workspaces/sessions/(backend)/tool-restriction/loop.cli.ts
|
|
613
|
+
var loop_cli_default = (0, _agimon_ai_doompi_core_extensionFile.defineToolRestriction)((context) => context.root.toolRestrictions[0]);
|
|
614
|
+
//#endregion
|
|
615
|
+
//#region src/extensions/workspaces/sessions/(backend)/tool/loop-list.cli.ts
|
|
616
|
+
var loop_list_cli_default = (0, _agimon_ai_doompi_core_extensionFile.defineTool)((context) => context.root.loopTools[0]);
|
|
617
|
+
//#endregion
|
|
618
|
+
//#region src/extensions/workspaces/sessions/(backend)/tool/loop-start.cli.ts
|
|
619
|
+
var loop_start_cli_default = (0, _agimon_ai_doompi_core_extensionFile.defineTool)((context) => context.root.loopTools[1]);
|
|
620
|
+
//#endregion
|
|
621
|
+
//#region src/extensions/workspaces/sessions/(backend)/tool/loop-stop.cli.ts
|
|
622
|
+
var loop_stop_cli_default = (0, _agimon_ai_doompi_core_extensionFile.defineTool)((context) => context.root.loopTools[2]);
|
|
623
|
+
//#endregion
|
|
624
|
+
//#region generated/pi.ts
|
|
625
|
+
const at = (value, context) => typeof value === "function" ? value(context) : value;
|
|
626
|
+
const via = (identity, value) => value === void 0 ? void 0 : {
|
|
627
|
+
...identity,
|
|
628
|
+
...value
|
|
629
|
+
};
|
|
630
|
+
const extension = (0, _agimon_ai_doompi_core_piExtension.definePiExtension)("@agimon-ai/doompi-loop", async (context) => {
|
|
631
|
+
const scopeSession = await root_cli_default(context);
|
|
632
|
+
const contextSession = {
|
|
633
|
+
...context,
|
|
634
|
+
root: scopeSession.value
|
|
635
|
+
};
|
|
636
|
+
return {
|
|
637
|
+
...(0, _agimon_ai_doompi_core_extensionFile.composeRootHooks)(scopeSession),
|
|
638
|
+
get commands() {
|
|
639
|
+
return [at(loop_cli_default$1, contextSession), at(loops_cli_default, contextSession)];
|
|
640
|
+
},
|
|
641
|
+
events: { agent_settled: at(agent_settled_cli_default, contextSession) },
|
|
642
|
+
get resources() {
|
|
643
|
+
return [at(doompi_use_loop_cli_default, contextSession)];
|
|
644
|
+
},
|
|
645
|
+
get toolRestrictions() {
|
|
646
|
+
return [at(loop_cli_default, contextSession)];
|
|
647
|
+
},
|
|
648
|
+
...(0, _agimon_ai_doompi_core_piExtension.piToolContributions)("@agimon-ai/doompi-loop", [
|
|
649
|
+
via({ name: "loop_list" }, at(loop_list_cli_default, contextSession)),
|
|
650
|
+
via({ name: "loop_start" }, at(loop_start_cli_default, contextSession)),
|
|
651
|
+
via({ name: "loop_stop" }, at(loop_stop_cli_default, contextSession))
|
|
652
|
+
]),
|
|
653
|
+
services: [...scopeSession.services ?? []]
|
|
654
|
+
};
|
|
655
|
+
});
|
|
656
|
+
//#endregion
|
|
657
|
+
exports.default = extension;
|
|
658
|
+
exports.extension = extension;
|
|
659
|
+
|
|
2
660
|
//# sourceMappingURL=pi.cjs.map
|