@agimon-ai/doompi-loop 0.0.1-alpha.84 → 0.0.1-alpha.85

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.
@@ -1,20 +1,13 @@
1
1
  import { t as DOOM_LOOP_LAUNCHERS_SERVICE } from "../loopLaunchers-cCniFbVA.mjs";
2
- import { a as LIST_COMMAND_NAME, i as LIST_COMMAND_DESCRIPTION, n as formatLoopStatusView, o as START_COMMAND_DESCRIPTION, r as createDoomLoopLaunchersService, s as START_COMMAND_NAME, t as LOOP_VIEW_STATUS_KEY } from "../loopView-CmuIEbUa.mjs";
3
- import { definePiExtension } from "@agimon-ai/doompi-core/piExtension";
4
- import { composeRootHooks, defineCliCommand, defineCliHook, defineResource, defineRoot } from "@agimon-ai/doompi-core/extensionFile";
2
+ import { _ as PACKAGE_SOURCE, a as CronLoopSchema, c as parseIntervalLoop, d as START_COMMAND_DESCRIPTION, f as START_COMMAND_NAME, g as NOTIFY_WARNING_LEVEL, h as NOTIFY_INFO_LEVEL, i as createDoomLoopLaunchersService, l as LIST_COMMAND_DESCRIPTION, m as MODE_STATUS_ID, n as formatLoopStatusView, o as IntervalLoopSchema, p as ACTIVE_MODE_COLOR, r as createLoopTools, s as parseCronLoop, t as LOOP_VIEW_STATUS_KEY, u as LIST_COMMAND_NAME, v as STATUS_KEY } from "../loopView-BK4X_1UI.mjs";
3
+ import { definePiExtension, piToolContributions } from "@agimon-ai/doompi-core/piExtension";
4
+ import { composeRootHooks, defineCliCommand, defineCliHook, defineResource, defineRoot, defineTool as defineTool$1, defineToolRestriction } from "@agimon-ai/doompi-core/extensionFile";
5
5
  import { defineMinorMode, piMinorModes } from "@agimon-ai/doompi-minor-mode";
6
6
  import { DOOM_CORDIS_SESSION_SERVICE } from "@agimon-ai/doompi-core/cordisHost";
7
7
  import { DOOM_UI_HUB_SERVICE, requireDoomUiHub } from "@agimon-ai/doompi-core/uiHub";
8
+ import { Cron } from "croner";
8
9
  import { DOOM_FULLSCREEN_UI_OPTIONS, DOOM_NAVIGATION_KEYS, DOOM_OVERLAY_ACCENT, DoomOverlay } from "@agimon-ai/doompi-ui/doomOverlay";
9
10
  import { matchesKey, truncateToWidth } from "@earendil-works/pi-tui";
10
- //#region src/constants/piLoop.ts
11
- const PACKAGE_SOURCE = "@agimon-ai/doompi-loop";
12
- const STATUS_KEY = "doom-loop";
13
- const MODE_STATUS_ID = "loop.active";
14
- const ACTIVE_MODE_COLOR = "warning";
15
- const NOTIFY_INFO_LEVEL = "info";
16
- const NOTIFY_WARNING_LEVEL = "warning";
17
- //#endregion
18
11
  //#region src/services/loopCommand/index.ts
19
12
  function createLoopCommands(handlers) {
20
13
  return [[START_COMMAND_NAME, {
@@ -22,37 +15,43 @@ function createLoopCommands(handlers) {
22
15
  handler: async (args, ctx) => handlers.start(ctx, args)
23
16
  }], [LIST_COMMAND_NAME, {
24
17
  description: LIST_COMMAND_DESCRIPTION,
25
- handler: async (_args, ctx) => handlers.list(ctx)
18
+ handler: async (args, ctx) => handlers.list(ctx, args)
26
19
  }]];
27
20
  }
28
21
  //#endregion
29
22
  //#region src/services/defaultLoopLauncher/index.ts
30
- const DEFAULT_LAUNCHER_ID = "doompi.default";
31
23
  const DEFAULT_INTERVAL_SECONDS = 300;
32
24
  const MIN_INTERVAL_SECONDS = 30;
33
25
  const MAX_INTERVAL_SECONDS = 3600;
34
26
  const MILLISECONDS_PER_SECOND = 1e3;
35
27
  const PROMPT_PREVIEW_LENGTH = 80;
36
- function intervalError(raw) {
37
- const seconds = Number(raw);
38
- if (!Number.isInteger(seconds)) return `Interval must be a whole number of seconds, got ${raw}.`;
39
- if (seconds < MIN_INTERVAL_SECONDS || seconds > MAX_INTERVAL_SECONDS) return `Interval must be between ${MIN_INTERVAL_SECONDS} and ${MAX_INTERVAL_SECONDS} seconds.`;
40
- }
41
- async function prepareDefaultLoop(ui, signal) {
28
+ async function prepareLoop(ui, request, cron) {
29
+ if (request.input !== void 0) return cron ? parseCronLoop(request.input) : parseIntervalLoop(request.input);
30
+ if (request.interactive === false) throw new Error("Loop configuration is required for agent launches.");
42
31
  const prompt = (await ui.editor("Loop prompt", ""))?.trim();
43
- if (!prompt || signal.aborted) return void 0;
44
- const rawInterval = await ui.input("Loop interval in seconds", `Default: ${DEFAULT_INTERVAL_SECONDS}s`);
45
- if (rawInterval === void 0 || signal.aborted) return void 0;
46
- const normalizedInterval = rawInterval.trim() || String(DEFAULT_INTERVAL_SECONDS);
47
- const error = intervalError(normalizedInterval);
48
- if (error) {
49
- ui.notify(error, NOTIFY_WARNING_LEVEL);
32
+ if (!prompt || request.signal.aborted) return void 0;
33
+ if (cron) {
34
+ const expression = await ui.input("Cron expression (five fields)", "0 * * * *");
35
+ if (expression === void 0 || request.signal.aborted) return void 0;
36
+ const timezone = await ui.input("Loop timezone", "UTC");
37
+ if (timezone === void 0 || request.signal.aborted) return void 0;
38
+ return parseCronLoop({
39
+ prompt,
40
+ cron: expression,
41
+ timezone: timezone.trim() || "UTC"
42
+ });
43
+ }
44
+ const raw = await ui.input("Loop interval in seconds", `Default: ${DEFAULT_INTERVAL_SECONDS}s`);
45
+ if (raw === void 0 || request.signal.aborted) return void 0;
46
+ const intervalSeconds = Number(raw.trim() || DEFAULT_INTERVAL_SECONDS);
47
+ if (!Number.isInteger(intervalSeconds) || intervalSeconds < MIN_INTERVAL_SECONDS || intervalSeconds > MAX_INTERVAL_SECONDS) {
48
+ ui.notify(`Interval must be between ${MIN_INTERVAL_SECONDS} and ${MAX_INTERVAL_SECONDS} seconds.`, NOTIFY_WARNING_LEVEL);
50
49
  return;
51
50
  }
52
- return {
51
+ return parseIntervalLoop({
53
52
  prompt,
54
- intervalSeconds: Number(normalizedInterval)
55
- };
53
+ intervalSeconds
54
+ });
56
55
  }
57
56
  function promptPreview(prompt) {
58
57
  const oneLine = prompt.replaceAll(/\s+/gu, " ").trim();
@@ -73,44 +72,62 @@ function createDefaultLoopLauncher(pi) {
73
72
  loop.context.ui.notify(`Default loop pass could not start: ${error instanceof Error ? error.message : String(error)}`, NOTIFY_WARNING_LEVEL);
74
73
  }
75
74
  };
76
- const onAgentSettled = () => {
77
- const next = pending.values().next().value;
78
- if (next) requestPass(next);
79
- };
80
- return {
81
- onAgentSettled,
82
- register(ctx, launchers) {
83
- return launchers.register({
84
- id: DEFAULT_LAUNCHER_ID,
85
- source: PACKAGE_SOURCE,
86
- label: "Default loop",
87
- description: "Run your own prompt in this session on an interval",
88
- async launch({ instanceId, signal }) {
89
- const prepared = await prepareDefaultLoop(ctx.ui, signal);
90
- if (!prepared || signal.aborted) return void 0;
91
- const loop = {
92
- ...prepared,
93
- context: ctx,
94
- stopped: false
95
- };
96
- loop.timer = setInterval(() => requestPass(loop), loop.intervalSeconds * MILLISECONDS_PER_SECOND);
97
- loop.timer.unref?.();
98
- requestPass(loop);
99
- return {
100
- instanceId,
101
- label: "Default loop",
102
- detail: `every ${loop.intervalSeconds}s · ${promptPreview(loop.prompt)}`,
103
- stop() {
104
- if (loop.stopped) return;
105
- loop.stopped = true;
106
- if (loop.timer) clearInterval(loop.timer);
107
- loop.timer = void 0;
108
- pending.delete(loop);
109
- }
110
- };
75
+ const register = (ctx, launchers, cron) => launchers.register({
76
+ id: cron ? "doompi.cron" : "doompi.default",
77
+ source: PACKAGE_SOURCE,
78
+ label: cron ? "Cron loop" : "Default loop",
79
+ description: cron ? "Run a prompt on a five-field cron schedule." : "Run your own prompt in this session on an interval",
80
+ inputSchema: { ...cron ? CronLoopSchema : IntervalLoopSchema },
81
+ async launch(request) {
82
+ const prepared = await prepareLoop(ctx.ui, request, cron);
83
+ if (!prepared || request.signal.aborted) return void 0;
84
+ const loop = {
85
+ prompt: prepared.prompt,
86
+ context: ctx,
87
+ stopped: false
88
+ };
89
+ let stopTimer;
90
+ let schedule;
91
+ if ("cron" in prepared) {
92
+ const job = new Cron(prepared.cron, {
93
+ timezone: prepared.timezone,
94
+ unref: true
95
+ }, () => requestPass(loop));
96
+ if (job.nextRun() === null) {
97
+ job.stop();
98
+ throw new Error("Cron expression has no future run.");
111
99
  }
112
- });
100
+ stopTimer = () => {
101
+ job.stop();
102
+ };
103
+ schedule = `${prepared.cron} (${prepared.timezone})`;
104
+ } else {
105
+ const timer = setInterval(() => requestPass(loop), prepared.intervalSeconds * MILLISECONDS_PER_SECOND);
106
+ timer.unref?.();
107
+ stopTimer = () => clearInterval(timer);
108
+ schedule = `every ${prepared.intervalSeconds}s`;
109
+ requestPass(loop);
110
+ }
111
+ return {
112
+ instanceId: request.instanceId,
113
+ label: cron ? "Cron loop" : "Default loop",
114
+ detail: `${schedule} · ${promptPreview(loop.prompt)}`,
115
+ stop() {
116
+ if (loop.stopped) return;
117
+ loop.stopped = true;
118
+ stopTimer();
119
+ pending.delete(loop);
120
+ }
121
+ };
113
122
  }
123
+ });
124
+ return {
125
+ onAgentSettled() {
126
+ const next = pending.values().next().value;
127
+ if (next) requestPass(next);
128
+ },
129
+ register: (ctx, launchers) => register(ctx, launchers, false),
130
+ registerCron: (ctx, launchers) => register(ctx, launchers, true)
114
131
  };
115
132
  }
116
133
  //#endregion
@@ -306,34 +323,27 @@ async function openStartLoopOverlay(ctx, launchers) {
306
323
  }
307
324
  //#endregion
308
325
  //#region src/tui/loopRuntime.ts
309
- const MODE_ACTION_START = "start";
310
- const MODE_ACTION_STOP = "stop";
326
+ const MODE_ACTION_ACTIVATE = "activate";
327
+ const MODE_ACTION_DEACTIVATE = "deactivate";
311
328
  const SESSION_REPLACED_REASON = "Pi session replaced.";
312
329
  const SESSION_SHUTDOWN_REASON = "Pi session shut down.";
313
- function loopModeState(launchers, instances) {
314
- const active = instances.length > 0;
330
+ function loopModeState(enabled, instances) {
315
331
  return {
316
- activation: active ? "active" : "inactive",
317
- condition: instances.some(({ state }) => state === "starting" || state === "stopping") ? "queued" : "ready",
318
- ...active ? {
319
- detail: `${instances.length} active`,
332
+ activation: enabled ? "active" : "inactive",
333
+ condition: "ready",
334
+ ...enabled ? {
335
+ detail: `${instances.length} loops`,
320
336
  color: ACTIVE_MODE_COLOR
321
337
  } : {},
322
- actions: [...launchers.length > 0 ? [{
323
- id: MODE_ACTION_START,
324
- enabled: true
325
- }] : [{
326
- id: MODE_ACTION_START,
327
- enabled: false,
328
- disabledReason: "No loop launchers are registered."
329
- }], ...active ? [{
330
- id: MODE_ACTION_STOP,
331
- enabled: true
332
- }] : [{
333
- id: MODE_ACTION_STOP,
334
- enabled: false,
335
- disabledReason: "No loops are active."
336
- }]]
338
+ actions: [{
339
+ id: MODE_ACTION_ACTIVATE,
340
+ enabled: !enabled,
341
+ ...enabled ? { disabledReason: "Loop mode is already active." } : {}
342
+ }, {
343
+ id: MODE_ACTION_DEACTIVATE,
344
+ enabled,
345
+ ...enabled ? {} : { disabledReason: "Loop mode is not active." }
346
+ }]
337
347
  };
338
348
  }
339
349
  /** Install Loop state and external resources into its host-owned Cordis plugin fiber. */
@@ -343,6 +353,15 @@ function createLoopPiRuntime(pi) {
343
353
  let mode;
344
354
  let disposeLeader;
345
355
  const launchesInProgress = /* @__PURE__ */ new WeakSet();
356
+ let modeEnabled = false;
357
+ const toolListeners = /* @__PURE__ */ new Set();
358
+ const loopTools = createLoopTools(() => {
359
+ if (!activeSession) throw new Error("Loop launchers are unavailable for the active session.");
360
+ return activeSession.launchers;
361
+ }, () => {
362
+ if (!active || !modeEnabled) throw new Error("Enable Loop minor mode before using agent loop tools.");
363
+ });
364
+ const toolNames = new Set(loopTools.map((tool) => tool.name));
346
365
  const currentSession = (context) => {
347
366
  const binding = activeSession;
348
367
  if (!active || !binding || binding.context.sessionManager !== context.sessionManager || binding.hostSession.sessionId !== context.sessionManager.getSessionId()) return;
@@ -365,66 +384,47 @@ function createLoopPiRuntime(pi) {
365
384
  source: PACKAGE_SOURCE,
366
385
  id: MODE_STATUS_ID,
367
386
  label: "Loop",
368
- description: "Session-scoped recurring prompt loops.",
387
+ description: "Give the agent tools to create, inspect, and stop session loops.",
369
388
  order: 60,
370
389
  actions: [{
371
- id: MODE_ACTION_START,
372
- label: "Start",
373
- description: "Start a loop with a registered launcher.",
390
+ id: MODE_ACTION_ACTIVATE,
391
+ label: "Activate",
392
+ description: "Expose loop tools to the agent.",
374
393
  contexts: ["tui", "headless"],
375
- parameters: [{
376
- name: "launcherId",
377
- label: "Launcher",
378
- kind: "string",
379
- required: true,
380
- minLength: 1
381
- }, {
382
- name: "instanceId",
383
- label: "Instance ID",
384
- kind: "string",
385
- required: false,
386
- minLength: 1
387
- }]
394
+ parameters: []
388
395
  }, {
389
- id: MODE_ACTION_STOP,
390
- label: "Stop",
391
- description: "Stop one active loop instance.",
396
+ id: MODE_ACTION_DEACTIVATE,
397
+ label: "Deactivate",
398
+ description: "Hide agent loop tools. Existing loops and manual controls remain available.",
392
399
  contexts: ["tui", "headless"],
393
- parameters: [{
394
- name: "instanceId",
395
- label: "Instance ID",
396
- kind: "string",
397
- required: true,
398
- minLength: 1
399
- }, {
400
- name: "reason",
401
- label: "Reason",
402
- kind: "string",
403
- required: false,
404
- minLength: 1
405
- }]
400
+ parameters: []
406
401
  }]
407
402
  },
408
- state: () => activeSession ? loopModeState(activeSession.launchers.listLaunchers(), activeSession.launchers.listInstances()) : loopModeState([], []),
409
- async handleAction(_runtime, actionId, argumentsValue, { signal }) {
403
+ state: () => loopModeState(modeEnabled, activeSession?.launchers.listInstances() ?? []),
404
+ async handleAction(_runtime, actionId, _arguments, { signal }) {
410
405
  signal.throwIfAborted();
411
406
  const binding = activeSession ? currentSession(activeSession.context) : void 0;
412
407
  if (!binding) throw new Error("Loop launchers are unavailable for the active session.");
413
- if (actionId === MODE_ACTION_START) {
414
- const instance = await binding.launchers.launch(argumentsValue.launcherId, argumentsValue.instanceId ? { instanceId: argumentsValue.instanceId } : {});
415
- if (activeSession !== binding) throw new Error("Loop action became stale.");
416
- return { message: instance ? `Loop '${instance.instanceId}' started.` : "Loop launch was cancelled." };
417
- }
418
- if (actionId === MODE_ACTION_STOP) {
419
- const stopped = await binding.launchers.stop(argumentsValue.instanceId, argumentsValue.reason ?? "Stopped through minor_mode.");
420
- if (activeSession !== binding) throw new Error("Loop action became stale.");
421
- return { message: stopped ? "Loop stopped." : "Loop instance was not active." };
422
- }
423
- throw new Error(`Unknown loop mode action: ${actionId}`);
408
+ if (actionId !== MODE_ACTION_ACTIVATE && actionId !== MODE_ACTION_DEACTIVATE) throw new Error(`Unknown loop mode action: ${actionId}`);
409
+ modeEnabled = actionId === MODE_ACTION_ACTIVATE;
410
+ for (const listener of toolListeners) listener();
411
+ publishStatus(binding);
412
+ return { message: modeEnabled ? "Loop tools activated." : "Loop tools deactivated. Existing loops are unchanged." };
424
413
  }
425
414
  }).createOwner(void 0);
426
415
  return {
427
416
  onAgentSettled: defaultLauncher.onAgentSettled,
417
+ loopTools,
418
+ toolRestrictions: [{
419
+ source: PACKAGE_SOURCE,
420
+ restrict: (incoming) => modeEnabled ? incoming : incoming.filter((name) => !toolNames.has(name)),
421
+ subscribe(listener) {
422
+ toolListeners.add(listener);
423
+ return () => {
424
+ toolListeners.delete(listener);
425
+ };
426
+ }
427
+ }],
428
428
  services: [(cordis) => {
429
429
  cordis.inject([DOOM_UI_HUB_SERVICE], (uiContext) => {
430
430
  const dispose = registerLeaderContribution(requireDoomUiHub(uiContext));
@@ -447,13 +447,17 @@ function createLoopPiRuntime(pi) {
447
447
  launchers
448
448
  };
449
449
  activeSession = binding;
450
+ modeEnabled = false;
451
+ for (const listener of toolListeners) listener();
450
452
  const provider = cordis.plugin((providerContext) => {
451
453
  providerContext.provide(DOOM_LOOP_LAUNCHERS_SERVICE, launchers);
452
454
  });
453
455
  let defaultRegistration;
456
+ let cronRegistration;
454
457
  let unsubscribe;
455
458
  try {
456
459
  defaultRegistration = defaultLauncher.register(hostSession.context, launchers);
460
+ cronRegistration = defaultLauncher.registerCron(hostSession.context, launchers);
457
461
  unsubscribe = launchers.subscribe(() => publishStatus(binding));
458
462
  publishStatus(binding);
459
463
  } catch (error) {
@@ -464,12 +468,14 @@ function createLoopPiRuntime(pi) {
464
468
  }
465
469
  return async () => {
466
470
  if (activeSession === binding) activeSession = void 0;
471
+ modeEnabled = false;
472
+ for (const listener of toolListeners) listener();
467
473
  unsubscribe();
468
474
  clearStatuses(binding);
469
475
  mode?.publish();
470
476
  const cleanupErrors = [];
471
477
  try {
472
- await defaultRegistration.dispose(SESSION_REPLACED_REASON);
478
+ await Promise.all([defaultRegistration.dispose(SESSION_REPLACED_REASON), cronRegistration.dispose(SESSION_REPLACED_REASON)]);
473
479
  } catch (error) {
474
480
  cleanupErrors.push(error);
475
481
  }
@@ -528,9 +534,15 @@ function createLoopPiRuntime(pi) {
528
534
  launchesInProgress.delete(binding);
529
535
  }
530
536
  },
531
- async list(ctx) {
537
+ async list(ctx, args = "") {
532
538
  const binding = currentSession(ctx);
533
539
  if (!binding) return;
540
+ if (args.trim().startsWith("stop ")) {
541
+ const stopped = await binding.launchers.stop(args.trim().slice(5).trim(), "Stopped manually.");
542
+ ctx.ui.notify(stopped ? "Loop stopped." : "Loop instance was not active.", NOTIFY_INFO_LEVEL);
543
+ return;
544
+ }
545
+ if (args.trim()) throw new Error("Usage: /loops [stop instanceId]");
534
546
  await openLoopListOverlay(ctx, binding.launchers);
535
547
  }
536
548
  },
@@ -539,6 +551,9 @@ function createLoopPiRuntime(pi) {
539
551
  active = false;
540
552
  const binding = activeSession;
541
553
  activeSession = void 0;
554
+ modeEnabled = false;
555
+ for (const listener of toolListeners) listener();
556
+ toolListeners.clear();
542
557
  if (binding) clearStatuses(binding);
543
558
  mode?.publish();
544
559
  disposeLeader?.();
@@ -552,6 +567,8 @@ var runtime_cli_default = (({ pi }) => {
552
567
  const runtime = createLoopPiRuntime(pi);
553
568
  return {
554
569
  services: [...runtime.services ?? [], piMinorModes(runtime.minorModes)],
570
+ loopTools: runtime.loopTools,
571
+ toolRestrictions: runtime.toolRestrictions,
555
572
  events: { agent_settled: runtime.onAgentSettled },
556
573
  commands: createLoopCommands(runtime.handlers),
557
574
  onDispose: () => runtime.onDispose(),
@@ -577,7 +594,7 @@ var root_cli_default = defineRoot((context) => {
577
594
  });
578
595
  //#endregion
579
596
  //#region src/extensions/workspaces/sessions/(backend)/command/loop.cli.ts
580
- var loop_cli_default = defineCliCommand((context) => context.root.commands[0]);
597
+ var loop_cli_default$1 = defineCliCommand((context) => context.root.commands[0]);
581
598
  //#endregion
582
599
  //#region src/extensions/workspaces/sessions/(backend)/command/loops.cli.ts
583
600
  var loops_cli_default = defineCliCommand((context) => context.root.commands[1]);
@@ -588,8 +605,24 @@ var agent_settled_cli_default = defineCliHook((context) => context.root.events["
588
605
  //#region src/extensions/workspaces/sessions/(backend)/resource/doompi-use-loop.cli.ts
589
606
  var doompi_use_loop_cli_default = defineResource((context) => context.root.resources[0]);
590
607
  //#endregion
608
+ //#region src/extensions/workspaces/sessions/(backend)/tool-restriction/loop.cli.ts
609
+ var loop_cli_default = defineToolRestriction((context) => context.root.toolRestrictions[0]);
610
+ //#endregion
611
+ //#region src/extensions/workspaces/sessions/(backend)/tool/loop-list.cli.ts
612
+ var loop_list_cli_default = defineTool$1((context) => context.root.loopTools[0]);
613
+ //#endregion
614
+ //#region src/extensions/workspaces/sessions/(backend)/tool/loop-start.cli.ts
615
+ var loop_start_cli_default = defineTool$1((context) => context.root.loopTools[1]);
616
+ //#endregion
617
+ //#region src/extensions/workspaces/sessions/(backend)/tool/loop-stop.cli.ts
618
+ var loop_stop_cli_default = defineTool$1((context) => context.root.loopTools[2]);
619
+ //#endregion
591
620
  //#region generated/pi.ts
592
621
  const at = (value, context) => typeof value === "function" ? value(context) : value;
622
+ const via = (identity, value) => value === void 0 ? void 0 : {
623
+ ...identity,
624
+ ...value
625
+ };
593
626
  const extension = definePiExtension("@agimon-ai/doompi-loop", async (context) => {
594
627
  const scopeSession = await root_cli_default(context);
595
628
  const contextSession = {
@@ -599,12 +632,20 @@ const extension = definePiExtension("@agimon-ai/doompi-loop", async (context) =>
599
632
  return {
600
633
  ...composeRootHooks(scopeSession),
601
634
  get commands() {
602
- return [at(loop_cli_default, contextSession), at(loops_cli_default, contextSession)];
635
+ return [at(loop_cli_default$1, contextSession), at(loops_cli_default, contextSession)];
603
636
  },
604
637
  events: { agent_settled: at(agent_settled_cli_default, contextSession) },
605
638
  get resources() {
606
639
  return [at(doompi_use_loop_cli_default, contextSession)];
607
640
  },
641
+ get toolRestrictions() {
642
+ return [at(loop_cli_default, contextSession)];
643
+ },
644
+ ...piToolContributions("@agimon-ai/doompi-loop", [
645
+ via({ name: "loop_list" }, at(loop_list_cli_default, contextSession)),
646
+ via({ name: "loop_start" }, at(loop_start_cli_default, contextSession)),
647
+ via({ name: "loop_stop" }, at(loop_stop_cli_default, contextSession))
648
+ ]),
608
649
  services: [...scopeSession.services ?? []]
609
650
  };
610
651
  });