@agimon-ai/doompi-loop 0.0.1-alpha.83 → 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.
@@ -3,22 +3,15 @@ Object.defineProperties(exports, {
3
3
  [Symbol.toStringTag]: { value: "Module" }
4
4
  });
5
5
  const require_loopLaunchers = require("../loopLaunchers-BtcLH_xN.cjs");
6
- const require_loopView = require("../loopView-CLIBhqYC.cjs");
6
+ const require_loopView = require("../loopView-C_OPuy8L.cjs");
7
7
  let _agimon_ai_doompi_core_piExtension = require("@agimon-ai/doompi-core/piExtension");
8
8
  let _agimon_ai_doompi_core_extensionFile = require("@agimon-ai/doompi-core/extensionFile");
9
9
  let _agimon_ai_doompi_minor_mode = require("@agimon-ai/doompi-minor-mode");
10
10
  let _agimon_ai_doompi_core_cordisHost = require("@agimon-ai/doompi-core/cordisHost");
11
11
  let _agimon_ai_doompi_core_uiHub = require("@agimon-ai/doompi-core/uiHub");
12
+ let croner = require("croner");
12
13
  let _agimon_ai_doompi_ui_doomOverlay = require("@agimon-ai/doompi-ui/doomOverlay");
13
14
  let _earendil_works_pi_tui = require("@earendil-works/pi-tui");
14
- //#region src/constants/piLoop.ts
15
- const PACKAGE_SOURCE = "@agimon-ai/doompi-loop";
16
- const STATUS_KEY = "doom-loop";
17
- const MODE_STATUS_ID = "loop.active";
18
- const ACTIVE_MODE_COLOR = "warning";
19
- const NOTIFY_INFO_LEVEL = "info";
20
- const NOTIFY_WARNING_LEVEL = "warning";
21
- //#endregion
22
15
  //#region src/services/loopCommand/index.ts
23
16
  function createLoopCommands(handlers) {
24
17
  return [[require_loopView.START_COMMAND_NAME, {
@@ -26,37 +19,43 @@ function createLoopCommands(handlers) {
26
19
  handler: async (args, ctx) => handlers.start(ctx, args)
27
20
  }], [require_loopView.LIST_COMMAND_NAME, {
28
21
  description: require_loopView.LIST_COMMAND_DESCRIPTION,
29
- handler: async (_args, ctx) => handlers.list(ctx)
22
+ handler: async (args, ctx) => handlers.list(ctx, args)
30
23
  }]];
31
24
  }
32
25
  //#endregion
33
26
  //#region src/services/defaultLoopLauncher/index.ts
34
- const DEFAULT_LAUNCHER_ID = "doompi.default";
35
27
  const DEFAULT_INTERVAL_SECONDS = 300;
36
28
  const MIN_INTERVAL_SECONDS = 30;
37
29
  const MAX_INTERVAL_SECONDS = 3600;
38
30
  const MILLISECONDS_PER_SECOND = 1e3;
39
31
  const PROMPT_PREVIEW_LENGTH = 80;
40
- function intervalError(raw) {
41
- const seconds = Number(raw);
42
- if (!Number.isInteger(seconds)) return `Interval must be a whole number of seconds, got ${raw}.`;
43
- if (seconds < MIN_INTERVAL_SECONDS || seconds > MAX_INTERVAL_SECONDS) return `Interval must be between ${MIN_INTERVAL_SECONDS} and ${MAX_INTERVAL_SECONDS} seconds.`;
44
- }
45
- async function prepareDefaultLoop(ui, signal) {
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.");
46
35
  const prompt = (await ui.editor("Loop prompt", ""))?.trim();
47
- if (!prompt || signal.aborted) return void 0;
48
- const rawInterval = await ui.input("Loop interval in seconds", `Default: ${DEFAULT_INTERVAL_SECONDS}s`);
49
- if (rawInterval === void 0 || signal.aborted) return void 0;
50
- const normalizedInterval = rawInterval.trim() || String(DEFAULT_INTERVAL_SECONDS);
51
- const error = intervalError(normalizedInterval);
52
- if (error) {
53
- ui.notify(error, NOTIFY_WARNING_LEVEL);
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);
54
53
  return;
55
54
  }
56
- return {
55
+ return require_loopView.parseIntervalLoop({
57
56
  prompt,
58
- intervalSeconds: Number(normalizedInterval)
59
- };
57
+ intervalSeconds
58
+ });
60
59
  }
61
60
  function promptPreview(prompt) {
62
61
  const oneLine = prompt.replaceAll(/\s+/gu, " ").trim();
@@ -74,47 +73,65 @@ function createDefaultLoopLauncher(pi) {
74
73
  try {
75
74
  pi.sendUserMessage(loop.prompt);
76
75
  } catch (error) {
77
- loop.context.ui.notify(`Default loop pass could not start: ${error instanceof Error ? error.message : String(error)}`, NOTIFY_WARNING_LEVEL);
76
+ loop.context.ui.notify(`Default loop pass could not start: ${error instanceof Error ? error.message : String(error)}`, require_loopView.NOTIFY_WARNING_LEVEL);
78
77
  }
79
78
  };
80
- const onAgentSettled = () => {
81
- const next = pending.values().next().value;
82
- if (next) requestPass(next);
83
- };
84
- return {
85
- onAgentSettled,
86
- register(ctx, launchers) {
87
- return launchers.register({
88
- id: DEFAULT_LAUNCHER_ID,
89
- source: PACKAGE_SOURCE,
90
- label: "Default loop",
91
- description: "Run your own prompt in this session on an interval",
92
- async launch({ instanceId, signal }) {
93
- const prepared = await prepareDefaultLoop(ctx.ui, signal);
94
- if (!prepared || signal.aborted) return void 0;
95
- const loop = {
96
- ...prepared,
97
- context: ctx,
98
- stopped: false
99
- };
100
- loop.timer = setInterval(() => requestPass(loop), loop.intervalSeconds * MILLISECONDS_PER_SECOND);
101
- loop.timer.unref?.();
102
- requestPass(loop);
103
- return {
104
- instanceId,
105
- label: "Default loop",
106
- detail: `every ${loop.intervalSeconds}s · ${promptPreview(loop.prompt)}`,
107
- stop() {
108
- if (loop.stopped) return;
109
- loop.stopped = true;
110
- if (loop.timer) clearInterval(loop.timer);
111
- loop.timer = void 0;
112
- pending.delete(loop);
113
- }
114
- };
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.");
115
103
  }
116
- });
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
+ };
117
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)
118
135
  };
119
136
  }
120
137
  //#endregion
@@ -127,7 +144,7 @@ const LOOPS_GROUP_SEGMENT = {
127
144
  };
128
145
  function registerLeaderContribution(hub) {
129
146
  const contribution = hub.registerLeader({
130
- source: PACKAGE_SOURCE,
147
+ source: require_loopView.PACKAGE_SOURCE,
131
148
  bindings: [{
132
149
  id: "loop.start",
133
150
  path: [LOOPS_GROUP_SEGMENT, {
@@ -310,34 +327,27 @@ async function openStartLoopOverlay(ctx, launchers) {
310
327
  }
311
328
  //#endregion
312
329
  //#region src/tui/loopRuntime.ts
313
- const MODE_ACTION_START = "start";
314
- const MODE_ACTION_STOP = "stop";
330
+ const MODE_ACTION_ACTIVATE = "activate";
331
+ const MODE_ACTION_DEACTIVATE = "deactivate";
315
332
  const SESSION_REPLACED_REASON = "Pi session replaced.";
316
333
  const SESSION_SHUTDOWN_REASON = "Pi session shut down.";
317
- function loopModeState(launchers, instances) {
318
- const active = instances.length > 0;
334
+ function loopModeState(enabled, instances) {
319
335
  return {
320
- activation: active ? "active" : "inactive",
321
- condition: instances.some(({ state }) => state === "starting" || state === "stopping") ? "queued" : "ready",
322
- ...active ? {
323
- detail: `${instances.length} active`,
324
- color: ACTIVE_MODE_COLOR
336
+ activation: enabled ? "active" : "inactive",
337
+ condition: "ready",
338
+ ...enabled ? {
339
+ detail: `${instances.length} loops`,
340
+ color: require_loopView.ACTIVE_MODE_COLOR
325
341
  } : {},
326
- actions: [...launchers.length > 0 ? [{
327
- id: MODE_ACTION_START,
328
- enabled: true
329
- }] : [{
330
- id: MODE_ACTION_START,
331
- enabled: false,
332
- disabledReason: "No loop launchers are registered."
333
- }], ...active ? [{
334
- id: MODE_ACTION_STOP,
335
- enabled: true
336
- }] : [{
337
- id: MODE_ACTION_STOP,
338
- enabled: false,
339
- disabledReason: "No loops are active."
340
- }]]
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
+ }]
341
351
  };
342
352
  }
343
353
  /** Install Loop state and external resources into its host-owned Cordis plugin fiber. */
@@ -347,88 +357,78 @@ function createLoopPiRuntime(pi) {
347
357
  let mode;
348
358
  let disposeLeader;
349
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));
350
369
  const currentSession = (context) => {
351
370
  const binding = activeSession;
352
371
  if (!active || !binding || binding.context.sessionManager !== context.sessionManager || binding.hostSession.sessionId !== context.sessionManager.getSessionId()) return;
353
372
  return binding;
354
373
  };
355
374
  const clearStatuses = (binding) => {
356
- binding.context.ui?.setStatus(STATUS_KEY, void 0);
375
+ binding.context.ui?.setStatus(require_loopView.STATUS_KEY, void 0);
357
376
  if (binding.context.mode !== "tui") binding.context.ui?.setStatus(require_loopView.LOOP_VIEW_STATUS_KEY, void 0);
358
377
  };
359
378
  const publishStatus = (binding) => {
360
379
  if (!active || activeSession !== binding) return;
361
380
  const instances = binding.launchers.listInstances();
362
- binding.context.ui?.setStatus(STATUS_KEY, instances.length ? `loops: ${instances.length}` : void 0);
381
+ binding.context.ui?.setStatus(require_loopView.STATUS_KEY, instances.length ? `loops: ${instances.length}` : void 0);
363
382
  if (binding.context.mode !== "tui") binding.context.ui?.setStatus(require_loopView.LOOP_VIEW_STATUS_KEY, require_loopView.formatLoopStatusView(instances));
364
383
  mode?.publish();
365
384
  };
366
385
  const defaultLauncher = createDefaultLoopLauncher(pi);
367
386
  mode = (0, _agimon_ai_doompi_minor_mode.defineMinorMode)({
368
387
  descriptor: {
369
- source: PACKAGE_SOURCE,
370
- id: MODE_STATUS_ID,
388
+ source: require_loopView.PACKAGE_SOURCE,
389
+ id: require_loopView.MODE_STATUS_ID,
371
390
  label: "Loop",
372
- description: "Session-scoped recurring prompt loops.",
391
+ description: "Give the agent tools to create, inspect, and stop session loops.",
373
392
  order: 60,
374
393
  actions: [{
375
- id: MODE_ACTION_START,
376
- label: "Start",
377
- description: "Start a loop with a registered launcher.",
394
+ id: MODE_ACTION_ACTIVATE,
395
+ label: "Activate",
396
+ description: "Expose loop tools to the agent.",
378
397
  contexts: ["tui", "headless"],
379
- parameters: [{
380
- name: "launcherId",
381
- label: "Launcher",
382
- kind: "string",
383
- required: true,
384
- minLength: 1
385
- }, {
386
- name: "instanceId",
387
- label: "Instance ID",
388
- kind: "string",
389
- required: false,
390
- minLength: 1
391
- }]
398
+ parameters: []
392
399
  }, {
393
- id: MODE_ACTION_STOP,
394
- label: "Stop",
395
- description: "Stop one active loop instance.",
400
+ id: MODE_ACTION_DEACTIVATE,
401
+ label: "Deactivate",
402
+ description: "Hide agent loop tools. Existing loops and manual controls remain available.",
396
403
  contexts: ["tui", "headless"],
397
- parameters: [{
398
- name: "instanceId",
399
- label: "Instance ID",
400
- kind: "string",
401
- required: true,
402
- minLength: 1
403
- }, {
404
- name: "reason",
405
- label: "Reason",
406
- kind: "string",
407
- required: false,
408
- minLength: 1
409
- }]
404
+ parameters: []
410
405
  }]
411
406
  },
412
- state: () => activeSession ? loopModeState(activeSession.launchers.listLaunchers(), activeSession.launchers.listInstances()) : loopModeState([], []),
413
- async handleAction(_runtime, actionId, argumentsValue, { signal }) {
407
+ state: () => loopModeState(modeEnabled, activeSession?.launchers.listInstances() ?? []),
408
+ async handleAction(_runtime, actionId, _arguments, { signal }) {
414
409
  signal.throwIfAborted();
415
410
  const binding = activeSession ? currentSession(activeSession.context) : void 0;
416
411
  if (!binding) throw new Error("Loop launchers are unavailable for the active session.");
417
- if (actionId === MODE_ACTION_START) {
418
- const instance = await binding.launchers.launch(argumentsValue.launcherId, argumentsValue.instanceId ? { instanceId: argumentsValue.instanceId } : {});
419
- if (activeSession !== binding) throw new Error("Loop action became stale.");
420
- return { message: instance ? `Loop '${instance.instanceId}' started.` : "Loop launch was cancelled." };
421
- }
422
- if (actionId === MODE_ACTION_STOP) {
423
- const stopped = await binding.launchers.stop(argumentsValue.instanceId, argumentsValue.reason ?? "Stopped through minor_mode.");
424
- if (activeSession !== binding) throw new Error("Loop action became stale.");
425
- return { message: stopped ? "Loop stopped." : "Loop instance was not active." };
426
- }
427
- throw new Error(`Unknown loop mode action: ${actionId}`);
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." };
428
417
  }
429
418
  }).createOwner(void 0);
430
419
  return {
431
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
432
  services: [(cordis) => {
433
433
  cordis.inject([_agimon_ai_doompi_core_uiHub.DOOM_UI_HUB_SERVICE], (uiContext) => {
434
434
  const dispose = registerLeaderContribution((0, _agimon_ai_doompi_core_uiHub.requireDoomUiHub)(uiContext));
@@ -451,13 +451,17 @@ function createLoopPiRuntime(pi) {
451
451
  launchers
452
452
  };
453
453
  activeSession = binding;
454
+ modeEnabled = false;
455
+ for (const listener of toolListeners) listener();
454
456
  const provider = cordis.plugin((providerContext) => {
455
457
  providerContext.provide(require_loopLaunchers.DOOM_LOOP_LAUNCHERS_SERVICE, launchers);
456
458
  });
457
459
  let defaultRegistration;
460
+ let cronRegistration;
458
461
  let unsubscribe;
459
462
  try {
460
463
  defaultRegistration = defaultLauncher.register(hostSession.context, launchers);
464
+ cronRegistration = defaultLauncher.registerCron(hostSession.context, launchers);
461
465
  unsubscribe = launchers.subscribe(() => publishStatus(binding));
462
466
  publishStatus(binding);
463
467
  } catch (error) {
@@ -468,12 +472,14 @@ function createLoopPiRuntime(pi) {
468
472
  }
469
473
  return async () => {
470
474
  if (activeSession === binding) activeSession = void 0;
475
+ modeEnabled = false;
476
+ for (const listener of toolListeners) listener();
471
477
  unsubscribe();
472
478
  clearStatuses(binding);
473
479
  mode?.publish();
474
480
  const cleanupErrors = [];
475
481
  try {
476
- await defaultRegistration.dispose(SESSION_REPLACED_REASON);
482
+ await Promise.all([defaultRegistration.dispose(SESSION_REPLACED_REASON), cronRegistration.dispose(SESSION_REPLACED_REASON)]);
477
483
  } catch (error) {
478
484
  cleanupErrors.push(error);
479
485
  }
@@ -496,14 +502,14 @@ function createLoopPiRuntime(pi) {
496
502
  const binding = currentSession(ctx);
497
503
  if (!binding) return;
498
504
  if (launchesInProgress.has(binding)) {
499
- ctx.ui.notify("A loop setup is already in progress.", NOTIFY_INFO_LEVEL);
505
+ ctx.ui.notify("A loop setup is already in progress.", require_loopView.NOTIFY_INFO_LEVEL);
500
506
  return;
501
507
  }
502
508
  launchesInProgress.add(binding);
503
509
  try {
504
510
  const launchers = binding.launchers.listLaunchers();
505
511
  if (!launchers.length) {
506
- ctx.ui.notify("No loop launchers are registered for this session.", NOTIFY_INFO_LEVEL);
512
+ ctx.ui.notify("No loop launchers are registered for this session.", require_loopView.NOTIFY_INFO_LEVEL);
507
513
  return;
508
514
  }
509
515
  const argument = args.trim();
@@ -524,7 +530,7 @@ function createLoopPiRuntime(pi) {
524
530
  }
525
531
  const instance = await binding.launchers.launch(launcherId);
526
532
  if (activeSession !== binding) return;
527
- if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, NOTIFY_INFO_LEVEL);
533
+ if (instance) ctx.ui.notify(`${instance.label ?? instance.launcherLabel} started.`, require_loopView.NOTIFY_INFO_LEVEL);
528
534
  } catch (error) {
529
535
  if (activeSession !== binding) return;
530
536
  ctx.ui.notify(`Loop could not start: ${error instanceof Error ? error.message : String(error)}`, "error");
@@ -532,9 +538,15 @@ function createLoopPiRuntime(pi) {
532
538
  launchesInProgress.delete(binding);
533
539
  }
534
540
  },
535
- async list(ctx) {
541
+ async list(ctx, args = "") {
536
542
  const binding = currentSession(ctx);
537
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]");
538
550
  await openLoopListOverlay(ctx, binding.launchers);
539
551
  }
540
552
  },
@@ -543,6 +555,9 @@ function createLoopPiRuntime(pi) {
543
555
  active = false;
544
556
  const binding = activeSession;
545
557
  activeSession = void 0;
558
+ modeEnabled = false;
559
+ for (const listener of toolListeners) listener();
560
+ toolListeners.clear();
546
561
  if (binding) clearStatuses(binding);
547
562
  mode?.publish();
548
563
  disposeLeader?.();
@@ -556,11 +571,13 @@ var runtime_cli_default = (({ pi }) => {
556
571
  const runtime = createLoopPiRuntime(pi);
557
572
  return {
558
573
  services: [...runtime.services ?? [], (0, _agimon_ai_doompi_minor_mode.piMinorModes)(runtime.minorModes)],
574
+ loopTools: runtime.loopTools,
575
+ toolRestrictions: runtime.toolRestrictions,
559
576
  events: { agent_settled: runtime.onAgentSettled },
560
577
  commands: createLoopCommands(runtime.handlers),
561
578
  onDispose: () => runtime.onDispose(),
562
579
  resources: [{
563
- source: PACKAGE_SOURCE,
580
+ source: require_loopView.PACKAGE_SOURCE,
564
581
  moduleUrl: require("url").pathToFileURL(__filename).href,
565
582
  skills: [{
566
583
  name: "doompi-use-loop",
@@ -581,7 +598,7 @@ var root_cli_default = (0, _agimon_ai_doompi_core_extensionFile.defineRoot)((con
581
598
  });
582
599
  //#endregion
583
600
  //#region src/extensions/workspaces/sessions/(backend)/command/loop.cli.ts
584
- var loop_cli_default = (0, _agimon_ai_doompi_core_extensionFile.defineCliCommand)((context) => context.root.commands[0]);
601
+ var loop_cli_default$1 = (0, _agimon_ai_doompi_core_extensionFile.defineCliCommand)((context) => context.root.commands[0]);
585
602
  //#endregion
586
603
  //#region src/extensions/workspaces/sessions/(backend)/command/loops.cli.ts
587
604
  var loops_cli_default = (0, _agimon_ai_doompi_core_extensionFile.defineCliCommand)((context) => context.root.commands[1]);
@@ -592,8 +609,24 @@ var agent_settled_cli_default = (0, _agimon_ai_doompi_core_extensionFile.defineC
592
609
  //#region src/extensions/workspaces/sessions/(backend)/resource/doompi-use-loop.cli.ts
593
610
  var doompi_use_loop_cli_default = (0, _agimon_ai_doompi_core_extensionFile.defineResource)((context) => context.root.resources[0]);
594
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
595
624
  //#region generated/pi.ts
596
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
+ };
597
630
  const extension = (0, _agimon_ai_doompi_core_piExtension.definePiExtension)("@agimon-ai/doompi-loop", async (context) => {
598
631
  const scopeSession = await root_cli_default(context);
599
632
  const contextSession = {
@@ -603,12 +636,20 @@ const extension = (0, _agimon_ai_doompi_core_piExtension.definePiExtension)("@ag
603
636
  return {
604
637
  ...(0, _agimon_ai_doompi_core_extensionFile.composeRootHooks)(scopeSession),
605
638
  get commands() {
606
- return [at(loop_cli_default, contextSession), at(loops_cli_default, contextSession)];
639
+ return [at(loop_cli_default$1, contextSession), at(loops_cli_default, contextSession)];
607
640
  },
608
641
  events: { agent_settled: at(agent_settled_cli_default, contextSession) },
609
642
  get resources() {
610
643
  return [at(doompi_use_loop_cli_default, contextSession)];
611
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
+ ]),
612
653
  services: [...scopeSession.services ?? []]
613
654
  };
614
655
  });