@dougbots/avenor-pi 0.3.4 → 0.3.6

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.
Files changed (2) hide show
  1. package/dist/index.js +154 -29
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -90,6 +90,14 @@ function buildCompletionText(run, result) {
90
90
  lines.push(`\nCall \`avenor_events\` with \`run_id: "${run.runId}"\` to review the output, or \`avenor_follow_up\` to iterate.`);
91
91
  return lines.join("\n");
92
92
  }
93
+ function buildWaitingText(run, status) {
94
+ const perm = status.pending_permission;
95
+ const lines = [`Sub-agent "${run.label}" (${run.agent}) is waiting for input.`];
96
+ if (perm) lines.push(`Permission request: ${perm.description}`, `To answer: call \`avenor_answer_permission\` with run_id "${run.runId}", option_id "allow_once" | "allow_always" | "deny"`);
97
+ else if (status.phase_label) lines.push(`Question: ${status.phase_label}`);
98
+ if (!perm) lines.push(`Use \`avenor_follow_up\` with run_id "${run.runId}" to respond, or \`avenor_status\` to re-check.`);
99
+ return lines.join("\n");
100
+ }
93
101
  function summarizeEvent(evt) {
94
102
  const type = String(evt.type ?? evt.event ?? "");
95
103
  if (type === "tool.call" || type === "tool.use") return `called ${String(evt.tool ?? evt.name ?? type)}`;
@@ -100,31 +108,79 @@ function summarizeEvent(evt) {
100
108
  async function src_default(pi) {
101
109
  const trackedRuns = /* @__PURE__ */ new Map();
102
110
  let pollingActive = false;
111
+ let pollingGeneration = 0;
112
+ let pollInFlight = null;
103
113
  let sessionCtx = null;
104
114
  async function pollRuns() {
105
- let raw;
115
+ const liveMap = /* @__PURE__ */ new Map();
106
116
  try {
107
- raw = await statusTool({});
117
+ const allLive = await statusTool({});
118
+ const liveList = Array.isArray(allLive) ? allLive : [allLive];
119
+ for (const result of liveList) liveMap.set(result.run_id, result);
108
120
  } catch (err) {
109
- console.error("avenor pollRuns: statusTool failed", err);
110
- return [];
121
+ console.error("avenor pollRuns: failed to list singleton runs", err);
111
122
  }
112
- const results = Array.isArray(raw) ? raw : [raw];
113
123
  const entries = [];
114
- for (const r of results) {
115
- const run = trackedRuns.get(r.run_id);
116
- if (run) run.lastStatus = r;
117
- entries.push({
118
- runId: r.run_id,
119
- label: r.label,
120
- status: r.status,
121
- phase: r.phase,
122
- phaseLabel: r.phase_label,
123
- agent: trackedRuns.get(r.run_id)?.agent ?? "unknown",
124
- pendingPermission: !!r.pending_permission,
125
- permissionDescription: r.pending_permission?.description
126
- });
124
+ for (const run of trackedRuns.values()) {
125
+ const live = liveMap.get(run.runId);
126
+ if (live) {
127
+ liveMap.delete(live.run_id);
128
+ run.lastStatus = live;
129
+ entries.push({
130
+ runId: run.runId,
131
+ label: run.label,
132
+ status: live.status,
133
+ phase: live.phase,
134
+ phaseLabel: live.phase_label,
135
+ agent: run.agent,
136
+ pendingPermission: !!live.pending_permission,
137
+ permissionDescription: live.pending_permission?.description
138
+ });
139
+ continue;
140
+ }
141
+ try {
142
+ const raw = await statusTool({
143
+ runId: run.runId,
144
+ supervisorId: run.supervisorId
145
+ });
146
+ const result = Array.isArray(raw) ? raw[0] : raw;
147
+ if (!result) throw new Error("status response was empty");
148
+ run.lastStatus = result;
149
+ entries.push({
150
+ runId: run.runId,
151
+ label: run.label,
152
+ status: result.status,
153
+ phase: result.phase,
154
+ phaseLabel: result.phase_label,
155
+ agent: run.agent,
156
+ pendingPermission: !!result.pending_permission,
157
+ permissionDescription: result.pending_permission?.description
158
+ });
159
+ } catch (err) {
160
+ console.error(`avenor pollRuns: statusTool failed for ${run.runId}`, err);
161
+ const previous = run.lastStatus;
162
+ entries.push({
163
+ runId: run.runId,
164
+ label: run.label,
165
+ status: previous?.status ?? "unavailable",
166
+ phase: previous?.phase,
167
+ phaseLabel: previous?.phase_label,
168
+ agent: run.agent,
169
+ pendingPermission: !!previous?.pending_permission,
170
+ permissionDescription: previous?.pending_permission?.description
171
+ });
172
+ }
127
173
  }
174
+ for (const live of liveMap.values()) entries.push({
175
+ runId: live.run_id,
176
+ label: live.label,
177
+ status: live.status,
178
+ phase: live.phase,
179
+ phaseLabel: live.phase_label,
180
+ agent: "unknown",
181
+ pendingPermission: !!live.pending_permission,
182
+ permissionDescription: live.pending_permission?.description
183
+ });
128
184
  return entries;
129
185
  }
130
186
  function renderStatusLines(entries) {
@@ -152,32 +208,77 @@ async function src_default(pi) {
152
208
  function startPolling() {
153
209
  if (pollingActive) return;
154
210
  pollingActive = true;
211
+ const generation = ++pollingGeneration;
155
212
  const tick = async () => {
213
+ if (!pollingActive || generation !== pollingGeneration) return;
156
214
  const prevStatuses = /* @__PURE__ */ new Map();
157
215
  for (const [id, run] of trackedRuns) prevStatuses.set(id, run.lastStatus?.status);
158
- const entries = await pollRuns();
216
+ const currentPoll = pollRuns();
217
+ pollInFlight = currentPoll;
218
+ const entries = await currentPoll.finally(() => {
219
+ if (pollInFlight === currentPoll) pollInFlight = null;
220
+ });
221
+ if (!pollingActive || generation !== pollingGeneration) return;
159
222
  updateWidget(entries);
160
- for (const entry of entries) if (TERMINAL_STATUSES.has(entry.status)) {
161
- if (trackedRuns.get(entry.runId)) {
223
+ for (const entry of entries) {
224
+ const run = trackedRuns.get(entry.runId);
225
+ if (!run) continue;
226
+ if (TERMINAL_STATUSES.has(entry.status)) {
162
227
  const prevStatus = prevStatuses.get(entry.runId);
163
- if (prevStatus && !TERMINAL_STATUSES.has(prevStatus)) sessionCtx?.ui.notify(`Sub-agent "${entry.label}" finished: ${entry.status}`, entry.status === "done" ? "info" : "warning");
228
+ if (!prevStatus || !TERMINAL_STATUSES.has(prevStatus)) {
229
+ sessionCtx?.ui.notify(`Sub-agent "${entry.label}" finished: ${entry.status}`, entry.status === "done" ? "info" : "warning");
230
+ if (!run.blocking) try {
231
+ pi.sendUserMessage(buildCompletionText({
232
+ runId: run.runId,
233
+ label: run.label
234
+ }, run.lastStatus), { deliverAs: "followUp" });
235
+ } catch (err) {
236
+ console.error("avenor tick: sendUserMessage failed", err);
237
+ }
238
+ }
164
239
  trackedRuns.delete(entry.runId);
165
- }
240
+ } else if (entry.pendingPermission && !run.blocking && !run.permissionNotified) {
241
+ run.permissionNotified = true;
242
+ try {
243
+ pi.sendUserMessage(buildWaitingText({
244
+ runId: run.runId,
245
+ label: run.label,
246
+ agent: run.agent
247
+ }, run.lastStatus), { deliverAs: "followUp" });
248
+ } catch (err) {
249
+ console.error("avenor tick: sendUserMessage for permission failed", err);
250
+ }
251
+ } else if (!entry.pendingPermission && run.permissionNotified) run.permissionNotified = false;
166
252
  }
167
- if (entries.some((e) => !TERMINAL_STATUSES.has(e.status))) setTimeout(tick, POLL_INTERVAL_MS);
253
+ if (entries.some((e) => !TERMINAL_STATUSES.has(e.status)) && pollingActive && generation === pollingGeneration) setTimeout(() => {
254
+ tick().catch((err) => {
255
+ pollingActive = false;
256
+ console.error("avenor polling loop failed", err);
257
+ });
258
+ }, POLL_INTERVAL_MS);
168
259
  else {
169
260
  pollingActive = false;
170
261
  sessionCtx?.ui.setStatus("avenor", "");
171
262
  sessionCtx?.ui.setWidget("avenor-status", void 0);
172
263
  }
173
264
  };
174
- tick();
265
+ tick().catch((err) => {
266
+ pollingActive = false;
267
+ console.error("avenor polling loop failed", err);
268
+ });
269
+ }
270
+ async function stopPolling() {
271
+ pollingActive = false;
272
+ pollingGeneration++;
273
+ await pollInFlight?.catch(() => {});
274
+ sessionCtx?.ui.setStatus("avenor", "");
275
+ sessionCtx?.ui.setWidget("avenor-status", void 0);
175
276
  }
176
277
  pi.on("session_start", async (_event, ctx) => {
177
278
  sessionCtx = ctx;
178
279
  });
179
280
  pi.on("session_shutdown", async (_event, _ctx) => {
180
- pollingActive = false;
281
+ await stopPolling();
181
282
  sessionCtx = null;
182
283
  });
183
284
  pi.on("before_agent_start", async (event, _ctx) => {
@@ -254,7 +355,9 @@ async function src_default(pi) {
254
355
  agent: params.agent,
255
356
  label,
256
357
  supervisorId: result.supervisor_id,
257
- startTime: Date.now()
358
+ runtimeId: result.runtime_id,
359
+ startTime: Date.now(),
360
+ blocking: wait
258
361
  });
259
362
  startPolling();
260
363
  if (!wait) return {
@@ -305,7 +408,7 @@ async function src_default(pi) {
305
408
  });
306
409
  else {
307
410
  let lastAction = "";
308
- if (!result.supervisor_id) try {
411
+ if (!params.supervisor_id) try {
309
412
  const eventsResult = await eventsTool({
310
413
  runId: result.run_id,
311
414
  limit: 3
@@ -327,6 +430,26 @@ async function src_default(pi) {
327
430
  }
328
431
  });
329
432
  }
433
+ if (status.status === "waiting") {
434
+ const tracked = trackedRuns.get(result.run_id);
435
+ if (tracked) tracked.permissionNotified = true;
436
+ return {
437
+ content: [{
438
+ type: "text",
439
+ text: buildWaitingText({
440
+ runId: result.run_id,
441
+ label,
442
+ agent: params.agent
443
+ }, status)
444
+ }],
445
+ details: {
446
+ status: "waiting",
447
+ run_id: result.run_id,
448
+ session_id: status.session_id,
449
+ ...status.pending_permission && { pending_permission: status.pending_permission }
450
+ }
451
+ };
452
+ }
330
453
  if (TERMINAL_STATUSES.has(status.status)) {
331
454
  trackedRuns.delete(result.run_id);
332
455
  return {
@@ -485,6 +608,8 @@ async function src_default(pi) {
485
608
  force: Type.Optional(Type.Boolean({ description: "Force shutdown instead of graceful" }))
486
609
  }),
487
610
  async execute(_toolCallId, params) {
611
+ await stopPolling();
612
+ trackedRuns.clear();
488
613
  const result = await shutdownTool({
489
614
  supervisorId: params.supervisor_id,
490
615
  force: params.force
@@ -606,7 +731,7 @@ async function src_default(pi) {
606
731
  if (!await ctx.ui.confirm("Cancel run", `Cancel "${label}" (${runId.slice(0, 8)})?`)) return;
607
732
  try {
608
733
  const { Supervisor } = await import("@dougbots/avenor-core");
609
- await (await Supervisor.get()).getClient().cancel(run?.lastStatus?.runtime_id ?? runId);
734
+ await (await Supervisor.get()).getClient().cancel(run?.lastStatus?.runtime_id ?? run?.runtimeId ?? runId);
610
735
  ctx.ui.notify(`Cancelled "${label}"`, "info");
611
736
  } catch (err) {
612
737
  ctx.ui.notify(`Failed to cancel: ${err instanceof Error ? err.message : String(err)}`, "error");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dougbots/avenor-pi",
3
- "version": "0.3.4",
3
+ "version": "0.3.6",
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.3.4"
38
+ "@dougbots/avenor-core": "^0.3.6"
39
39
  },
40
40
  "peerDependencies": {
41
41
  "@earendil-works/pi-coding-agent": "*",