@dougbots/avenor-pi 0.11.0 → 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
@@ -2885,6 +2956,45 @@ function createExtension(deps = defaultDeps, options = {}) {
2885
2956
  };
2886
2957
  }
2887
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
+ });
2888
2998
  pi.registerCommand("avenor-status", {
2889
2999
  description: "Show status of all avenor runs",
2890
3000
  handler: async (_args, ctx) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dougbots/avenor-pi",
3
- "version": "0.11.0",
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.11.0"
38
+ "@dougbots/avenor-core": "^0.12.0"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@earendil-works/pi-coding-agent": ">=0.74.0",