@dougbots/avenor-pi 0.4.0 → 0.5.0

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
@@ -112,7 +112,9 @@ Available tools for LLM sub-agent management:
112
112
  | Tool | Description |
113
113
  |---|---|
114
114
  | `avenor_spawn` | Dispatch an agent run (blocking or fire-and-forget). Accepts `agent` (optional, maps to a named profile in `agents.json`), `model`, `prompt`, and `dir`. |
115
- | `avenor_status` | Get status of a run or all runs |
115
+ | `avenor_status` | Get status of a run or all runs; use `view="lifecycle"` for compact progress and permission checks |
116
+ | `avenor_result` | Wait for a run and return its bounded final output without transcript details |
117
+ | `avenor_inspect` | Review a bounded transcript, tool activity, permissions, and final output |
116
118
  | `avenor_answer_permission` | Answer a pending permission request |
117
119
  | `avenor_follow_up` | Resume a completed run with a follow-up message |
118
120
  | `avenor_events` | Read events from a run |
@@ -154,18 +156,20 @@ If you don't need a named profile, `model` alone is sufficient — no agent conf
154
156
  **Blocking (default):**
155
157
  ```
156
158
  1. avenor_spawn → tool call shows live progress, blocks until done
157
- 2. tool call returns → structured result with status + session_id
158
- 3. avenor_events → inspect detailed output
159
- 4. avenor_follow_up → optionally iterate
160
- 5. avenor_shutdown → clean up when finished
159
+ 2. tool call returns → completion preview with status + session_id
160
+ 3. avenor_result → retrieve the bounded final output when needed
161
+ 4. avenor_inspect → inspect transcript and tool details when needed
162
+ 5. avenor_follow_up → optionally iterate
163
+ 6. avenor_shutdown → clean up when finished
161
164
  ```
162
165
 
163
166
  **Parallel / fire-and-forget (`wait=false`):**
164
167
  ```
165
168
  1. avenor_spawn × N → each returns run_id immediately
166
169
  2. (status widget updates) → persistent widget shows all active runs
167
- 3. /avenor-watch <id> → open live event feed for a specific run
168
- 4. avenor_events → inspect output when done
170
+ 3. avenor_status → optional compact lifecycle/permission check
171
+ 4. avenor_result → wait for and retrieve each final output
172
+ 5. /avenor-watch <id> → open live diagnostics for a specific run
169
173
  ```
170
174
 
171
175
  ## Dependencies
package/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { InspectResult, StatusResult, Supervisor, answerPermissionTool, dial, eventsTool, followUpTool, inspectTool, observeRun, shutdownTool, spawnTool, statusTool } from "@dougbots/avenor-core";
1
+ import { InspectResult, StatusResult, Supervisor, answerPermissionTool, dial, eventsTool, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, spawnTool, statusTool } from "@dougbots/avenor-core";
2
2
  import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
 
4
4
  //#region src/types.d.ts
@@ -21,6 +21,7 @@ interface ExtensionDeps {
21
21
  answerPermissionTool: typeof answerPermissionTool;
22
22
  followUpTool: typeof followUpTool;
23
23
  inspectTool: typeof inspectTool;
24
+ resultTool: typeof resultTool;
24
25
  shutdownTool: typeof shutdownTool;
25
26
  observeRun: typeof observeRun;
26
27
  dial: typeof dial;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Input, Key, Text, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
2
2
  import { Type } from "typebox";
3
- import { Supervisor, answerPermissionTool, dial, eventsTool, followUpTool, inspectTool, observeRun, shutdownTool, spawnTool, statusTool } from "@dougbots/avenor-core";
3
+ import { Supervisor, answerPermissionTool, dial, eventsTool, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, spawnTool, statusTool } from "@dougbots/avenor-core";
4
4
  //#region src/watch.ts
5
5
  const ANSI_PATTERN = /[\u001B\u009B][[\]()#;?]*(?:(?:(?:[a-zA-Z\d]*(?:;[a-zA-Z\d]*)*)?\u0007)|(?:(?:\d{1,4}(?:;\d{0,4})*)?[\dA-PR-TZcf-nq-uy=><~]))/g;
6
6
  const TRANSCRIPT_SCROLL_STEP = 4;
@@ -469,22 +469,44 @@ var RunInspectorOverlay = class {
469
469
  return lines.map((line) => truncateToWidth(line, innerWidth));
470
470
  }
471
471
  };
472
+ function matchingWatchRuns(runs, selector) {
473
+ const query = selector.trim().toLowerCase();
474
+ if (!query) return [];
475
+ const byId = runs.find((run) => run.runId.toLowerCase() === query);
476
+ if (byId) return [byId];
477
+ return runs.filter((run) => run.label?.toLowerCase() === query || run.agent?.toLowerCase() === query);
478
+ }
479
+ function watchRunChoice(run) {
480
+ return `${sanitizeText(run.label ?? run.runId)}${run.agent ? ` · ${sanitizeText(run.agent)}` : ""} · ${sanitizeText(run.runId).slice(0, 8)}`;
481
+ }
482
+ async function selectWatchRun(ctx, runs) {
483
+ const choices = runs.map(watchRunChoice);
484
+ const selected = await ctx.ui.select("Watch run:", choices);
485
+ if (!selected) return void 0;
486
+ return runs.find((_run, index) => choices[index] === selected);
487
+ }
472
488
  async function openRunInspector(ctx, options) {
473
489
  if (!ctx.hasUI) {
474
490
  ctx.ui.notify("avenor-watch requires TUI mode", "warning");
475
491
  return;
476
492
  }
477
- let runId = options.initialRunId?.trim();
478
- if (!runId) {
493
+ const selector = options.initialRunId?.trim();
494
+ let tracked;
495
+ let runId;
496
+ if (selector) {
497
+ const matches = matchingWatchRuns(options.trackedRuns, selector);
498
+ tracked = matches.length === 1 ? matches[0] : matches.length > 1 ? await selectWatchRun(ctx, matches) : void 0;
499
+ if (matches.length > 1 && !tracked) return;
500
+ runId = tracked?.runId ?? selector;
501
+ } else {
479
502
  if (options.trackedRuns.length === 0) {
480
503
  ctx.ui.notify("No tracked runs", "warning");
481
504
  return;
482
505
  }
483
- const selected = await ctx.ui.select("Watch run:", options.trackedRuns.map((run) => run.runId));
484
- if (!selected) return;
485
- runId = selected;
506
+ tracked = await selectWatchRun(ctx, options.trackedRuns);
507
+ if (!tracked) return;
508
+ runId = tracked.runId;
486
509
  }
487
- const tracked = options.trackedRuns.find((run) => run.runId === runId);
488
510
  await ctx.ui.custom((tui, theme, keybindings, done) => new RunInspectorOverlay(tui, theme, keybindings, options.deps, tracked ?? { runId }, () => done(), options.onTrackRun), {
489
511
  overlay: true,
490
512
  overlayOptions: {
@@ -497,6 +519,15 @@ async function openRunInspector(ctx, options) {
497
519
  }
498
520
  //#endregion
499
521
  //#region src/types.ts
522
+ function findLiveStatusForTrackedRun(run, liveStatuses) {
523
+ const knownRunIds = new Set([run.runId, run.lastStatus?.run_id].filter((id) => !!id));
524
+ let runtimeMatch;
525
+ for (const status of liveStatuses) {
526
+ if (knownRunIds.has(status.run_id)) return status;
527
+ if (!run.supervisorId && run.runtimeId === status.runtime_id) runtimeMatch = status;
528
+ }
529
+ return runtimeMatch;
530
+ }
500
531
  const TERMINAL_STATUSES = new Set([
501
532
  "done",
502
533
  "failed",
@@ -537,6 +568,7 @@ const defaultDeps = {
537
568
  answerPermissionTool,
538
569
  followUpTool,
539
570
  inspectTool,
571
+ resultTool,
540
572
  shutdownTool,
541
573
  observeRun,
542
574
  dial,
@@ -699,7 +731,7 @@ function createExtension(deps = defaultDeps) {
699
731
  }
700
732
  const entries = [];
701
733
  for (const run of trackedRuns.values()) {
702
- const live = liveMap.get(run.runId);
734
+ const live = findLiveStatusForTrackedRun(run, liveMap.values());
703
735
  if (live) {
704
736
  liveMap.delete(live.run_id);
705
737
  run.lastStatus = live;
@@ -1070,11 +1102,12 @@ function createExtension(deps = defaultDeps) {
1070
1102
  name: "avenor_spawn",
1071
1103
  label: "Avenor Spawn",
1072
1104
  description: "Dispatch an agent run via avenor. Blocks by default, showing live progress. Set wait=false for fire-and-forget.",
1073
- promptSnippet: "Dispatch sub-agent runs via avenor (spawn, status, inspect, events, follow-up, shutdown)",
1105
+ promptSnippet: "Dispatch sub-agent runs via avenor (spawn, status, result, inspect, events, follow-up, shutdown)",
1074
1106
  promptGuidelines: [
1075
1107
  "Use avenor_spawn to delegate well-defined tasks to sub-agents.",
1076
- "Use avenor_status to check progress of fire-and-forget runs.",
1077
- "Use avenor_inspect to review a bounded transcript, tools, and final output from a run.",
1108
+ "Use avenor_status with view \"lifecycle\" only to check progress or pending permissions.",
1109
+ "Use avenor_result to wait for and retrieve a sub-agent final result.",
1110
+ "Use avenor_inspect to review a bounded transcript and tool activity from a run.",
1078
1111
  "Use avenor_events only when raw event payloads are specifically needed.",
1079
1112
  "Use avenor_follow_up to iterate on a completed run.",
1080
1113
  "Use avenor_shutdown when done delegating work."
@@ -1125,7 +1158,7 @@ function createExtension(deps = defaultDeps) {
1125
1158
  if (!wait) return {
1126
1159
  content: [{
1127
1160
  type: "text",
1128
- text: `Dispatched "${label}" (run_id: ${result.run_id}). Call avenor_status to check progress or avenor_inspect for a bounded transcript snapshot.`
1161
+ text: `Dispatched "${label}" (run_id: ${result.run_id}). Completion will be delivered automatically; use avenor_status with view "lifecycle" for progress or avenor_result to wait for the final result.`
1129
1162
  }],
1130
1163
  details: {
1131
1164
  run_id: result.run_id,
@@ -1242,15 +1275,21 @@ function createExtension(deps = defaultDeps) {
1242
1275
  pi.registerTool({
1243
1276
  name: "avenor_status",
1244
1277
  label: "Avenor Status",
1245
- description: "Get status of a run or all runs. Surfaces pending permission requests.",
1278
+ description: "Get lifecycle status of a run or all runs. Use view=\"lifecycle\" for compact polling; full is the compatibility default.",
1246
1279
  parameters: Type.Object({
1247
1280
  run_id: Type.Optional(Type.String({ description: "Specific run ID to query" })),
1281
+ view: Type.Optional(Type.Unsafe({
1282
+ type: "string",
1283
+ enum: ["lifecycle", "full"],
1284
+ description: "Response detail (default full for compatibility)"
1285
+ })),
1248
1286
  supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
1249
1287
  }),
1250
1288
  async execute(_toolCallId, params) {
1251
1289
  const result = await deps.statusTool({
1252
1290
  runId: params.run_id,
1253
- supervisorId: params.supervisor_id
1291
+ supervisorId: params.supervisor_id,
1292
+ view: params.view
1254
1293
  });
1255
1294
  return {
1256
1295
  content: [{
@@ -1261,6 +1300,44 @@ function createExtension(deps = defaultDeps) {
1261
1300
  };
1262
1301
  }
1263
1302
  });
1303
+ pi.registerTool({
1304
+ name: "avenor_result",
1305
+ label: "Avenor Result",
1306
+ description: "Wait for a run to finish and return its bounded final output without transcript or event details.",
1307
+ parameters: Type.Object({
1308
+ run_id: Type.String({ description: "Run ID to await" }),
1309
+ wait: Type.Optional(Type.Boolean({ description: "Wait for a terminal result (default true)" })),
1310
+ timeout: Type.Optional(Type.String({ description: "Maximum time to wait (e.g. 30s, 5m)" })),
1311
+ supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
1312
+ }),
1313
+ async execute(_toolCallId, params, signal) {
1314
+ const tracked = trackedRuns.get(params.run_id);
1315
+ const previousBlocking = tracked?.blocking;
1316
+ if (tracked) tracked.blocking = true;
1317
+ let completed = false;
1318
+ try {
1319
+ const result = await deps.resultTool({
1320
+ runId: params.run_id,
1321
+ supervisorId: params.supervisor_id,
1322
+ wait: params.wait,
1323
+ timeout: params.timeout,
1324
+ signal
1325
+ });
1326
+ completed = result.ready;
1327
+ if (completed) trackedRuns.delete(params.run_id);
1328
+ else if (tracked && result.status === "waiting") tracked.permissionNotified = true;
1329
+ return {
1330
+ content: [{
1331
+ type: "text",
1332
+ text: JSON.stringify(result, null, 2)
1333
+ }],
1334
+ details: result
1335
+ };
1336
+ } finally {
1337
+ if (!completed && tracked && trackedRuns.get(params.run_id) === tracked) tracked.blocking = previousBlocking ?? false;
1338
+ }
1339
+ }
1340
+ });
1264
1341
  pi.registerTool({
1265
1342
  name: "avenor_inspect",
1266
1343
  label: "Avenor Inspect",
@@ -1422,11 +1499,11 @@ function createExtension(deps = defaultDeps) {
1422
1499
  }
1423
1500
  });
1424
1501
  pi.registerCommand("avenor-watch", {
1425
- description: "Open a live run inspector for an avenor run",
1502
+ description: "Open a live run inspector by run ID, label, or agent name",
1426
1503
  getArgumentCompletions: (prefix) => {
1427
- const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => ({
1504
+ const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.agent.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => ({
1428
1505
  value: run.runId,
1429
- label: `${run.label} (${run.runId.slice(0, 8)})`
1506
+ label: `${sanitizeText(run.label)} (${sanitizeText(run.agent)}, ${sanitizeText(run.runId).slice(0, 8)})`
1430
1507
  }));
1431
1508
  return items.length > 0 ? items : null;
1432
1509
  },
@@ -1448,9 +1525,9 @@ function createExtension(deps = defaultDeps) {
1448
1525
  pi.registerCommand("avenor-cancel", {
1449
1526
  description: "Cancel a running avenor sub-agent",
1450
1527
  getArgumentCompletions: (prefix) => {
1451
- const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => ({
1528
+ const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.agent.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => ({
1452
1529
  value: run.runId,
1453
- label: `${run.label} (${run.runId.slice(0, 8)})`
1530
+ label: `${sanitizeText(run.label)} (${sanitizeText(run.agent)}, ${sanitizeText(run.runId).slice(0, 8)})`
1454
1531
  }));
1455
1532
  return items.length > 0 ? items : null;
1456
1533
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dougbots/avenor-pi",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "type": "module",
5
5
  "keywords": [
6
6
  "pi-package"
@@ -35,7 +35,7 @@
35
35
  "dev": "tsdown --watch"
36
36
  },
37
37
  "dependencies": {
38
- "@dougbots/avenor-core": "^0.4.0"
38
+ "@dougbots/avenor-core": "^0.5.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@earendil-works/pi-coding-agent": "*",