@jtsang/pi-extensions 0.1.0 → 0.1.1

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 CHANGED
@@ -18,8 +18,13 @@ keeping the model running between events.
18
18
  - `pi_background_monitor` starts a command in the session working directory.
19
19
  Non-persistent monitors time out after five minutes by default; persistent
20
20
  monitors run until stopped or the session ends.
21
+ - `pi_background_monitor_list` lists every active monitor with its ID, command,
22
+ working directory, elapsed time, and timeout state.
21
23
  - `pi_background_monitor_stop` stops one monitor by ID, or all monitors when no
22
24
  ID is provided.
25
+ - Monitors run concurrently with no extension-level limit; operating-system
26
+ process and resource limits still apply. The footer shows the active count,
27
+ and `/monitors` shows the current details without invoking the model.
23
28
  - Output from stdout and stderr is batched for 200 ms, stripped of terminal
24
29
  control sequences, limited to 16 KB per event, and delivered to the current
25
30
  conversation as untrusted data. An idle agent starts a turn immediately; a
@@ -4,6 +4,7 @@ import { stripVTControlCharacters } from "node:util";
4
4
  import {
5
5
  createLocalBashOperations,
6
6
  formatSize,
7
+ truncateHead,
7
8
  truncateTail,
8
9
  type ExtensionAPI,
9
10
  type ExtensionContext,
@@ -22,6 +23,11 @@ const CONTROL_CHARACTERS = /[\u0000-\u0008\u000b-\u000d\u000e-\u001f\u007f]/g;
22
23
  type RunningMonitor = {
23
24
  id: string;
24
25
  description: string;
26
+ command: string;
27
+ cwd: string;
28
+ persistent: boolean;
29
+ timeoutMs?: number;
30
+ startedAt: number;
25
31
  abort: AbortController;
26
32
  decoder: StringDecoder;
27
33
  partialLine: string;
@@ -55,6 +61,42 @@ export default function monitorExtension(pi: ExtensionAPI) {
55
61
  currentContext.ui.setStatus(STATUS_KEY, count ? `${count} monitor${count === 1 ? "" : "s"}` : undefined);
56
62
  }
57
63
 
64
+ function getMonitorDetails() {
65
+ const now = Date.now();
66
+ return [...monitors.values()].map((monitor) => ({
67
+ id: monitor.id,
68
+ description: monitor.description,
69
+ command: monitor.command,
70
+ cwd: monitor.cwd,
71
+ state: monitor.stopping ? "stopping" : "running",
72
+ persistent: monitor.persistent,
73
+ timeoutMs: monitor.timeoutMs,
74
+ startedAt: monitor.startedAt,
75
+ elapsedMs: now - monitor.startedAt,
76
+ }));
77
+ }
78
+
79
+ function formatMonitorList(): string {
80
+ const active = getMonitorDetails();
81
+ const lines = [`Active monitors: ${active.length} (no extension limit; system resource limits apply)`];
82
+ for (const monitor of active) {
83
+ lines.push(
84
+ `- ${monitor.id} · ${monitor.state} · elapsed=${(monitor.elapsedMs / 1000).toFixed(1)}s · ${monitor.persistent ? "persistent" : `timeout=${monitor.timeoutMs}ms`}`,
85
+ ` description=${JSON.stringify(monitor.description)}`,
86
+ ` cwd=${JSON.stringify(monitor.cwd)}`,
87
+ ` command=${JSON.stringify(monitor.command)}`,
88
+ );
89
+ }
90
+
91
+ const truncated = truncateHead(lines.join("\n"), {
92
+ maxLines: MAX_QUEUED_LINES,
93
+ maxBytes: MAX_EVENT_BYTES - 64,
94
+ });
95
+ return truncated.truncated
96
+ ? `${truncated.content}\n[monitor list truncated to ${formatSize(MAX_EVENT_BYTES)}]`
97
+ : truncated.content;
98
+ }
99
+
58
100
  function discardQueuedOutput(monitor: RunningMonitor): void {
59
101
  if (monitor.flushTimer) clearTimeout(monitor.flushTimer);
60
102
  monitor.flushTimer = undefined;
@@ -194,6 +236,31 @@ export default function monitorExtension(pi: ExtensionAPI) {
194
236
  currentContext = undefined;
195
237
  });
196
238
 
239
+ pi.registerCommand("monitors", {
240
+ description: "Show active background monitors",
241
+ handler: async (_args, ctx) => {
242
+ ctx.ui.notify(formatMonitorList(), "info");
243
+ },
244
+ });
245
+
246
+ pi.registerTool({
247
+ name: "pi_background_monitor_list",
248
+ label: "List Monitors",
249
+ description:
250
+ "List active background monitors with their IDs, commands, working directories, elapsed time, and timeout state. The extension has no fixed concurrency limit; system resources are the limit.",
251
+ promptSnippet: "List active background monitors and their current state",
252
+ promptGuidelines: [
253
+ "Use pi_background_monitor_list to inspect active monitor count, commands, and runtime state.",
254
+ ],
255
+ parameters: Type.Object({}),
256
+ async execute() {
257
+ return {
258
+ content: [{ type: "text", text: formatMonitorList() }],
259
+ details: { monitors: getMonitorDetails(), concurrencyLimit: null },
260
+ };
261
+ },
262
+ });
263
+
197
264
  pi.registerTool({
198
265
  name: "pi_background_monitor",
199
266
  label: "Monitor",
@@ -231,6 +298,11 @@ export default function monitorExtension(pi: ExtensionAPI) {
231
298
  const monitor: RunningMonitor = {
232
299
  id,
233
300
  description: params.description,
301
+ command: params.command,
302
+ cwd: ctx.cwd,
303
+ persistent,
304
+ timeoutMs,
305
+ startedAt: Date.now(),
234
306
  abort,
235
307
  decoder: new StringDecoder("utf8"),
236
308
  partialLine: "",
@@ -286,8 +358,10 @@ export default function monitorExtension(pi: ExtensionAPI) {
286
358
  id,
287
359
  description: params.description,
288
360
  command: params.command,
361
+ cwd: ctx.cwd,
289
362
  persistent,
290
363
  timeoutMs,
364
+ startedAt: monitor.startedAt,
291
365
  },
292
366
  };
293
367
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jtsang/pi-extensions",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "An opinionated collection of extensions for Pi",
5
5
  "type": "module",
6
6
  "license": "MIT",