@dougbots/avenor-pi 0.10.4 → 0.12.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/dist/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { InspectResult, StatusResult, Supervisor, answerPermissionTool, brokerReceiveTool, brokerReplyTool, dial, eventsTool, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, spawnTool, statusTool, workflowCompleteTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowStatusTool, workflowWaitTool } from "@dougbots/avenor-core";
1
+ import { InspectResult, StatusResult, Supervisor, answerPermissionTool, brokerReceiveTool, brokerReplyTool, dial, eventsTool, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, spawnTool, statusTool, workflowCompleteTool, workflowControllerStatusTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowReadyTool, workflowStatusTool, workflowWaitTool } from "@dougbots/avenor-core";
2
2
  import { EventBus, ExtensionAPI } from "@earendil-works/pi-coding-agent";
3
3
 
4
4
  //#region src/subagent-broker.d.ts
@@ -134,6 +134,8 @@ interface ExtensionDeps {
134
134
  workflowEventsTool: typeof workflowEventsTool;
135
135
  workflowCompleteTool: typeof workflowCompleteTool;
136
136
  workflowGateTool: typeof workflowGateTool;
137
+ workflowControllerStatusTool: typeof workflowControllerStatusTool;
138
+ workflowReadyTool: typeof workflowReadyTool;
137
139
  observeRun: typeof observeRun;
138
140
  dial: typeof dial;
139
141
  Supervisor: typeof Supervisor;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { Input, Key, Text, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
2
2
  import { Type } from "typebox";
3
3
  import { createHash, randomUUID } from "node:crypto";
4
- import { Supervisor, THINKING_LEVELS, answerPermissionTool, askTool, brokerReceiveTool, brokerReplyTool, cancelTool, dial, eventsTool, extractEventText, followUpTool, inspectTool, observeRun, peersTool, resultTool, shutdownTool, socketsRoot, spawnTool, statusTool, validateSpawnSelection, workflowCompleteTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowStatusTool, workflowWaitTool } from "@dougbots/avenor-core";
4
+ import { Supervisor, THINKING_LEVELS, answerPermissionTool, askTool, brokerReceiveTool, brokerReplyTool, cancelTool, dial, eventsTool, extractEventText, followUpTool, inspectTool, observeRun, peersTool, resultTool, shutdownTool, socketsRoot, spawnTool, statusTool, validateSpawnSelection, workflowCompleteTool, workflowControllerStatusTool, workflowEventsTool, workflowGateTool, workflowInspectTool, workflowReadyTool, workflowStatusTool, workflowWaitTool } from "@dougbots/avenor-core";
5
5
  import * as fs from "node:fs";
6
6
  import * as path from "node:path";
7
7
  //#region src/subagent-broker.ts
@@ -1230,6 +1230,66 @@ function shutdownLines(details, args, expanded) {
1230
1230
  ...paths.length > CLEANUP_ITEMS ? [itemMarker(paths.length - CLEANUP_ITEMS)] : []
1231
1231
  ];
1232
1232
  }
1233
+ function controllerLeaderLine(leader) {
1234
+ if (!leader) return "Leader: none";
1235
+ const owner = leader.is_this_process === true ? "this process" : `other owner (${scalar(leader.owner_id)})`;
1236
+ const expires = optionalScalar(leader.expires_at);
1237
+ return `Leader: ${owner}${expires ? ` — lease expires ${expires}` : ""}`;
1238
+ }
1239
+ function controllerStatusLines(details, args, expanded) {
1240
+ const status = asRecord(details);
1241
+ if (!status || !stringValue(status.controller_id) || !stringValue(status.desired_state)) return void 0;
1242
+ const controllerId = selected(status, "controller_id", args.controller_id);
1243
+ const desiredState = selected(status, "desired_state");
1244
+ const inflight = numberValue(status.inflight) ?? 0;
1245
+ const maxInflight = numberValue(status.max_inflight);
1246
+ const blocked = asRecord(status.capacity_blocked);
1247
+ const blockedDetail = blocked ? optionalScalar(blocked.detail) : void 0;
1248
+ const lines = [`Controller: ${controllerId} — ${desiredState}`, controllerLeaderLine(asRecord(status.leader))];
1249
+ const candidates = numberValue(status.candidate_count);
1250
+ if (candidates !== void 0) lines.push(`Candidates: ${candidates} (advisory)`);
1251
+ lines.push(`In-flight: ${inflight}${maxInflight !== void 0 ? ` of ${maxInflight}` : ""}`);
1252
+ const lastReconcile = optionalScalar(status.last_reconcile);
1253
+ if (lastReconcile) lines.push(`Last reconcile: ${lastReconcile}`);
1254
+ if (blocked) lines.push(`Capacity blocked: ${selected(blocked, "source")}${blockedDetail ? ` — ${blockedDetail}` : ""}`);
1255
+ const nextPoll = optionalScalar(status.next_poll_at);
1256
+ if (nextPoll) lines.push(`Next external poll: ${nextPoll}`);
1257
+ lines.push(desiredState === "disabled" ? `Guidance: Run \`avenor workflow controller enable ${controllerId}\` to start dispatch and polling.` : `Guidance: Call avenor_workflow_controller_status without controller_id to review all controllers.`);
1258
+ if (!expanded) return lines;
1259
+ const supervisor = stringValue(args.supervisor_id) ? scalar(args.supervisor_id) : "singleton";
1260
+ return [
1261
+ ...lines,
1262
+ `Supervisor: ${supervisor}`,
1263
+ ...maxInflight !== void 0 ? [`Max in-flight: ${maxInflight}`] : [],
1264
+ ...optionalNumber(status.revision) !== void 0 ? [`Revision: ${optionalNumber(status.revision)}`] : [],
1265
+ ...optionalNumber(status.owner_epoch) !== void 0 ? [`Owner epoch: ${optionalNumber(status.owner_epoch)}`] : []
1266
+ ];
1267
+ }
1268
+ function controllerListLines(details, args, expanded) {
1269
+ const result = asRecord(details);
1270
+ if (!result || !Array.isArray(result.controllers) || result.controllers.some((controller) => !asRecord(controller))) return void 0;
1271
+ const controllers = result.controllers.map((controller) => controller);
1272
+ stringValue(args.supervisor_id) && scalar(args.supervisor_id);
1273
+ if (controllers.length === 0) return ["No workflow controllers configured."];
1274
+ const displayed = controllers.slice(0, STATUS_ROWS);
1275
+ const lines = [
1276
+ `Workflow controllers — ${controllers.length}`,
1277
+ ...displayed.map(controllerListRow),
1278
+ ...controllers.length > displayed.length ? [itemMarker(controllers.length - displayed.length)] : []
1279
+ ];
1280
+ if (!expanded) return lines;
1281
+ for (const controller of displayed) {
1282
+ const leader = asRecord(controller.leader);
1283
+ const ownerEpoch = leader ? numberValue(leader.owner_epoch) : void 0;
1284
+ lines.push(`${selected(controller, "controller_id")}:`, controllerLeaderLine(leader), ...ownerEpoch !== void 0 ? [`Owner epoch: ${ownerEpoch}`] : []);
1285
+ }
1286
+ return lines;
1287
+ }
1288
+ function controllerListRow(controller) {
1289
+ const leader = asRecord(controller.leader);
1290
+ const owner = leader ? leader.is_this_process === true ? "this process" : `other owner (${scalar(leader.owner_id)})` : "none";
1291
+ return `${selected(controller, "controller_id")} — ${selected(controller, "desired_state")} — leader: ${owner}`;
1292
+ }
1233
1293
  function render(tool, result, options, theme, build) {
1234
1294
  try {
1235
1295
  return linesText(options.isPartial ? partial(result) : build() ?? fallback(tool), theme);
@@ -1322,6 +1382,15 @@ function renderEventsResult(result, options, theme, args) {
1322
1382
  function renderShutdownResult(result, options, theme, args) {
1323
1383
  return render("shutdown", result, options, theme, () => shutdownLines(result.details, args, options.expanded));
1324
1384
  }
1385
+ function renderWorkflowControllerStatusCall(args, theme) {
1386
+ const pieces = stringValue(args.controller_id) ? [`avenor_workflow_controller_status controller_id ${quoted(args.controller_id)}`] : ["avenor_workflow_controller_status (all controllers)"];
1387
+ const supervisor = optionalQuoted(args.supervisor_id);
1388
+ if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
1389
+ return linesText([pieces.join(" ")], theme);
1390
+ }
1391
+ function renderWorkflowControllerStatusResult(result, options, theme, args) {
1392
+ return render("workflow controller status", result, options, theme, () => controllerStatusLines(result.details, args, options.expanded) ?? controllerListLines(result.details, args, options.expanded));
1393
+ }
1325
1394
  //#endregion
1326
1395
  //#region src/index.ts
1327
1396
  const POLL_INTERVAL_MS = 3e3;
@@ -1349,6 +1418,8 @@ const defaultDeps = {
1349
1418
  workflowEventsTool,
1350
1419
  workflowCompleteTool,
1351
1420
  workflowGateTool,
1421
+ workflowControllerStatusTool,
1422
+ workflowReadyTool,
1352
1423
  observeRun,
1353
1424
  dial,
1354
1425
  Supervisor
@@ -2077,12 +2148,14 @@ function createExtension(deps = defaultDeps, options = {}) {
2077
2148
  promptSnippet: "Dispatch sub-agent runs via avenor (spawn, status, result, inspect, events, follow-up, shutdown)",
2078
2149
  promptGuidelines: [
2079
2150
  "Use avenor_spawn to delegate well-defined tasks to sub-agents.",
2080
- "Use avenor_status with view \"lifecycle\" only to check progress or pending permissions.",
2081
- "Use avenor_result to wait for and retrieve a sub-agent final result.",
2151
+ "Use avenor_status (view \"lifecycle\") only to check progress or pending permissions.",
2152
+ "Use avenor_result to retrieve the final result of a sub-agent; it waits by default, but set wait=false for runs that already completed.",
2082
2153
  "Use avenor_inspect to review a bounded transcript and tool activity from a run.",
2083
- "Use avenor_events only when raw event payloads are specifically needed.",
2154
+ "Use avenor_events only when you specifically need raw event payloads.",
2084
2155
  "Use avenor_follow_up to iterate on a completed run.",
2085
- "Use avenor_shutdown when done delegating work."
2156
+ "Use avenor_shutdown when done delegating work.",
2157
+ "When roster_entry is set, the roster selects agent, model, and backend; do not pass those fields alongside it.",
2158
+ "If a roster spawn fails validation or resolution, do not retry the roster: respawn without roster_file/roster_entry. Direct selectors (agent, model, backend) are optional; omission uses runtime defaults."
2086
2159
  ],
2087
2160
  parameters: Type.Object({
2088
2161
  agent: Type.Optional(Type.String({ description: "Optional agent name; omission uses the supplied model or runtime defaults" })),
@@ -2103,7 +2176,7 @@ function createExtension(deps = defaultDeps, options = {}) {
2103
2176
  ], { description: "Thinking level; omission uses the backend default" })),
2104
2177
  backend: Type.Optional(Type.String({ description: "Backend override (defaults to pi for direct mode)" })),
2105
2178
  roster_file: Type.Optional(Type.String({ description: "Path to the roster map" })),
2106
- roster_entry: Type.Optional(Type.String({ description: "Roster entry to select" })),
2179
+ roster_entry: Type.Optional(Type.String({ description: "Roster entry to select. Requires roster_file unless a roster context is configured; disables agent, model, and backend" })),
2107
2180
  server_url: Type.Optional(Type.String({ description: "Backend server URL" })),
2108
2181
  supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" })),
2109
2182
  wait: Type.Optional(Type.Boolean({ description: "Block until complete (default true)" }))
@@ -2331,10 +2404,10 @@ function createExtension(deps = defaultDeps, options = {}) {
2331
2404
  pi.registerTool({
2332
2405
  name: "avenor_result",
2333
2406
  label: "Avenor Result",
2334
- description: "Wait for a run to finish and return its complete final output without transcript or event details.",
2407
+ description: "Retrieve the complete final output of a run without transcript or event details. Waits by default; set wait=false to retrieve an already-completed run without blocking.",
2335
2408
  parameters: Type.Object({
2336
- run_id: Type.String({ description: "Run ID to await" }),
2337
- wait: Type.Optional(Type.Boolean({ description: "Wait for a terminal result (default true)" })),
2409
+ run_id: Type.String({ description: "Run ID or label" }),
2410
+ wait: Type.Optional(Type.Boolean({ description: "Wait for a terminal result (default true); false retrieves the current state without blocking" })),
2338
2411
  timeout: Type.Optional(Type.String({ description: "Maximum time to wait (e.g. 30s, 5m)" })),
2339
2412
  supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
2340
2413
  }),
@@ -2883,6 +2956,45 @@ function createExtension(deps = defaultDeps, options = {}) {
2883
2956
  };
2884
2957
  }
2885
2958
  });
2959
+ pi.registerTool({
2960
+ name: "avenor_workflow_controller_status",
2961
+ label: "Avenor Workflow Controller Status",
2962
+ description: "Show workflow controller status. With controller_id, returns that controller's full status; without it, lists all controllers with summary state. Create, enable, and disable are CLI-only (avenor workflow controller ...).",
2963
+ parameters: Type.Object({
2964
+ controller_id: Type.Optional(Type.String({ description: "Controller ID; omit to list all controllers" })),
2965
+ supervisor_id: Type.Optional(Type.String({ description: "Supervisor ID for multi-supervisor mode" }))
2966
+ }),
2967
+ async execute(_toolCallId, params) {
2968
+ const result = await deps.workflowControllerStatusTool({
2969
+ controllerId: params.controller_id,
2970
+ supervisorId: params.supervisor_id
2971
+ });
2972
+ const details = { ...result };
2973
+ if (params.controller_id) try {
2974
+ const ready = await deps.workflowReadyTool({
2975
+ controllerId: params.controller_id,
2976
+ supervisorId: params.supervisor_id
2977
+ });
2978
+ if (Array.isArray(ready.candidates)) details.candidate_count = ready.candidates.length;
2979
+ } catch {}
2980
+ return {
2981
+ content: [{
2982
+ type: "text",
2983
+ text: JSON.stringify(result, null, 2)
2984
+ }],
2985
+ details
2986
+ };
2987
+ },
2988
+ renderCall(args, theme, context) {
2989
+ return renderWorkflowControllerStatusCall(context.args, theme);
2990
+ },
2991
+ renderResult(result, { expanded, isPartial }, theme, context) {
2992
+ return renderWorkflowControllerStatusResult(result, {
2993
+ expanded,
2994
+ isPartial
2995
+ }, theme, context.args);
2996
+ }
2997
+ });
2886
2998
  pi.registerCommand("avenor-status", {
2887
2999
  description: "Show status of all avenor runs",
2888
3000
  handler: async (_args, ctx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dougbots/avenor-pi",
3
- "version": "0.10.4",
3
+ "version": "0.12.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.10.4"
38
+ "@dougbots/avenor-core": "^0.12.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@earendil-works/pi-coding-agent": ">=0.74.0",