@dougbots/avenor-pi 0.7.0 → 0.8.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 +6 -0
- package/dist/index.js +85 -23
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -11,6 +11,12 @@ interface RunStatusEntry {
|
|
|
11
11
|
agent: string;
|
|
12
12
|
pendingPermission?: boolean;
|
|
13
13
|
permissionDescription?: string;
|
|
14
|
+
/** OS PID of the spawned pi process (pi backend only), for discovering child supervisors. */
|
|
15
|
+
pid?: number;
|
|
16
|
+
/** Backend type, used to determine whether to look for child supervisors. */
|
|
17
|
+
backend?: string;
|
|
18
|
+
/** Count of active (non-terminal) runs on this run's child supervisor, if discovered. */
|
|
19
|
+
nestedCount?: number;
|
|
14
20
|
}
|
|
15
21
|
//#endregion
|
|
16
22
|
//#region src/index.d.ts
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Input, Key, Text, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
import { Supervisor, answerPermissionTool, dial, eventsTool, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, socketsRoot, spawnTool, statusTool } from "@dougbots/avenor-core";
|
|
4
5
|
//#region src/watch.ts
|
|
5
6
|
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
7
|
const TRANSCRIPT_SCROLL_STEP = 4;
|
|
@@ -552,6 +553,19 @@ function formatRunLine(entry) {
|
|
|
552
553
|
const phase = entry.phaseLabel ? ` (${entry.phaseLabel})` : "";
|
|
553
554
|
return `${emoji} ${entry.label} (${entry.agent}) — ${entry.status}${phase}${perm}`;
|
|
554
555
|
}
|
|
556
|
+
/**
|
|
557
|
+
* decideCompletion determines whether to skip, defer, or send the completion
|
|
558
|
+
* steering message for a tracked run that has reached terminal status.
|
|
559
|
+
*
|
|
560
|
+
* The deferral prevents duplication when avenor_result consumes the result
|
|
561
|
+
* in the same turn: if the agent calls avenor_result between the defer and
|
|
562
|
+
* the next tick, the run is deleted from tracking and the send never fires.
|
|
563
|
+
*/
|
|
564
|
+
function decideCompletion(run) {
|
|
565
|
+
if (run.blocking) return "skip";
|
|
566
|
+
if (!run.completionPending) return "defer";
|
|
567
|
+
return "send";
|
|
568
|
+
}
|
|
555
569
|
//#endregion
|
|
556
570
|
//#region src/agent-profile.ts
|
|
557
571
|
const PROFILE_ENTRY_TYPE = "pi-agents:profile";
|
|
@@ -640,7 +654,8 @@ function renderStatusLines(entries) {
|
|
|
640
654
|
const emoji = statusEmoji(e.status);
|
|
641
655
|
const perm = e.pendingPermission ? " 🔒" : "";
|
|
642
656
|
const phase = e.phaseLabel ? ` [${e.phaseLabel}]` : "";
|
|
643
|
-
|
|
657
|
+
const nested = e.nestedCount ? ` +${e.nestedCount}` : "";
|
|
658
|
+
return `${emoji} ${e.label} — ${e.status}${phase}${perm}${nested}`;
|
|
644
659
|
});
|
|
645
660
|
}
|
|
646
661
|
function compactWhitespace(text, limit = 100) {
|
|
@@ -725,7 +740,8 @@ function createExtension(deps = defaultDeps) {
|
|
|
725
740
|
startTime: current?.startTime ?? Date.now(),
|
|
726
741
|
lastStatus: status ?? current?.lastStatus,
|
|
727
742
|
blocking: current?.blocking ?? blocking,
|
|
728
|
-
permissionNotified: current?.permissionNotified
|
|
743
|
+
permissionNotified: current?.permissionNotified,
|
|
744
|
+
completionPending: current?.completionPending
|
|
729
745
|
});
|
|
730
746
|
}
|
|
731
747
|
async function resolveFinalOutput(run, status) {
|
|
@@ -742,6 +758,29 @@ function createExtension(deps = defaultDeps) {
|
|
|
742
758
|
return;
|
|
743
759
|
}
|
|
744
760
|
}
|
|
761
|
+
/**
|
|
762
|
+
* Count active (non-terminal) runs on a child supervisor discovered via
|
|
763
|
+
* the spawned pi process's PID. The child pi process creates its own
|
|
764
|
+
* avenor supervisor at `avenor-mcp-<pid>.sock`; we dial it briefly to
|
|
765
|
+
* list runs and count those still active. Returns 0 on any failure
|
|
766
|
+
* (socket not yet ready, supervisor idle-shut-down, etc.).
|
|
767
|
+
*/
|
|
768
|
+
async function countNestedRuns(pid) {
|
|
769
|
+
if (!pid) return 0;
|
|
770
|
+
const socketPath = path.join(socketsRoot(), `avenor-mcp-${pid}.sock`);
|
|
771
|
+
let client;
|
|
772
|
+
try {
|
|
773
|
+
client = await Promise.race([deps.dial(socketPath, { callTimeoutMs: 2e3 }), new Promise((_, reject) => setTimeout(() => reject(/* @__PURE__ */ new Error("dial timeout")), 2e3))]);
|
|
774
|
+
return (await client.list()).filter((r) => {
|
|
775
|
+
const status = String(r.status ?? "running").toLowerCase();
|
|
776
|
+
return status === "running" || status === "idle";
|
|
777
|
+
}).length;
|
|
778
|
+
} catch {
|
|
779
|
+
return 0;
|
|
780
|
+
} finally {
|
|
781
|
+
client?.close();
|
|
782
|
+
}
|
|
783
|
+
}
|
|
745
784
|
async function pollRuns() {
|
|
746
785
|
const liveMap = /* @__PURE__ */ new Map();
|
|
747
786
|
try {
|
|
@@ -765,7 +804,9 @@ function createExtension(deps = defaultDeps) {
|
|
|
765
804
|
phaseLabel: live.phase_label,
|
|
766
805
|
agent: run.agent,
|
|
767
806
|
pendingPermission: !!live.pending_permission,
|
|
768
|
-
permissionDescription: live.pending_permission?.description
|
|
807
|
+
permissionDescription: live.pending_permission?.description,
|
|
808
|
+
pid: live.pid,
|
|
809
|
+
backend: live.backend
|
|
769
810
|
});
|
|
770
811
|
continue;
|
|
771
812
|
}
|
|
@@ -785,7 +826,9 @@ function createExtension(deps = defaultDeps) {
|
|
|
785
826
|
phaseLabel: result.phase_label,
|
|
786
827
|
agent: run.agent,
|
|
787
828
|
pendingPermission: !!result.pending_permission,
|
|
788
|
-
permissionDescription: result.pending_permission?.description
|
|
829
|
+
permissionDescription: result.pending_permission?.description,
|
|
830
|
+
pid: result.pid,
|
|
831
|
+
backend: result.backend
|
|
789
832
|
});
|
|
790
833
|
} catch (err) {
|
|
791
834
|
console.error(`avenor pollRuns: statusTool failed for ${run.runId}`, err);
|
|
@@ -798,7 +841,9 @@ function createExtension(deps = defaultDeps) {
|
|
|
798
841
|
phaseLabel: previous?.phase_label,
|
|
799
842
|
agent: run.agent,
|
|
800
843
|
pendingPermission: !!previous?.pending_permission,
|
|
801
|
-
permissionDescription: previous?.pending_permission?.description
|
|
844
|
+
permissionDescription: previous?.pending_permission?.description,
|
|
845
|
+
pid: previous?.pid,
|
|
846
|
+
backend: previous?.backend
|
|
802
847
|
});
|
|
803
848
|
}
|
|
804
849
|
}
|
|
@@ -810,8 +855,16 @@ function createExtension(deps = defaultDeps) {
|
|
|
810
855
|
phaseLabel: live.phase_label,
|
|
811
856
|
agent: live.agent ?? "unknown",
|
|
812
857
|
pendingPermission: !!live.pending_permission,
|
|
813
|
-
permissionDescription: live.pending_permission?.description
|
|
858
|
+
permissionDescription: live.pending_permission?.description,
|
|
859
|
+
pid: live.pid,
|
|
860
|
+
backend: live.backend
|
|
814
861
|
});
|
|
862
|
+
const nestedPids = entries.filter((e) => e.backend === "pi" && e.pid && !isTerminalStatus(e.status)).map((e) => e.pid);
|
|
863
|
+
if (nestedPids.length > 0) {
|
|
864
|
+
const nestedCounts = await Promise.all(nestedPids.map((pid) => countNestedRuns(pid)));
|
|
865
|
+
let idx = 0;
|
|
866
|
+
for (const entry of entries) if (entry.backend === "pi" && entry.pid && !isTerminalStatus(entry.status)) entry.nestedCount = nestedCounts[idx++] ?? 0;
|
|
867
|
+
}
|
|
815
868
|
return entries;
|
|
816
869
|
}
|
|
817
870
|
function updateWidget(entries) {
|
|
@@ -819,7 +872,11 @@ function createExtension(deps = defaultDeps) {
|
|
|
819
872
|
const lines = renderStatusLines(entries);
|
|
820
873
|
if (lines.length > 0) {
|
|
821
874
|
sessionCtx.ui.setWidget("avenor-status", lines);
|
|
822
|
-
|
|
875
|
+
const statusStr = lines.map((line) => line.slice(0, 60)).join(" ");
|
|
876
|
+
const nestedTotal = entries.filter((e) => !isTerminalStatus(e.status)).reduce((sum, e) => sum + (e.nestedCount ?? 0), 0);
|
|
877
|
+
const total = lines.length + nestedTotal;
|
|
878
|
+
const statusWithTotal = nestedTotal > 0 ? `${statusStr} total:${total}` : statusStr;
|
|
879
|
+
sessionCtx.ui.setStatus("avenor", statusWithTotal);
|
|
823
880
|
} else {
|
|
824
881
|
sessionCtx.ui.setWidget("avenor-status", void 0);
|
|
825
882
|
sessionCtx.ui.setStatus("avenor", "");
|
|
@@ -845,19 +902,22 @@ function createExtension(deps = defaultDeps) {
|
|
|
845
902
|
if (!run) continue;
|
|
846
903
|
if (isTerminalStatus(entry.status)) {
|
|
847
904
|
const prevStatus = prevStatuses.get(entry.runId);
|
|
848
|
-
if (!prevStatus
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
905
|
+
if (!(prevStatus && isTerminalStatus(prevStatus))) sessionCtx?.ui.notify(`Sub-agent "${entry.label}" finished: ${entry.status}`, entry.status === "done" ? "info" : "warning");
|
|
906
|
+
if (run.blocking) continue;
|
|
907
|
+
const action = decideCompletion(run);
|
|
908
|
+
if (action === "defer") {
|
|
909
|
+
run.completionPending = true;
|
|
910
|
+
continue;
|
|
911
|
+
}
|
|
912
|
+
if (action === "skip") continue;
|
|
913
|
+
const finalOutput = await resolveFinalOutput(run, run.lastStatus);
|
|
914
|
+
try {
|
|
915
|
+
pi.sendUserMessage(buildCompletionText({
|
|
916
|
+
runId: run.runId,
|
|
917
|
+
label: run.label
|
|
918
|
+
}, run.lastStatus, finalOutput), { deliverAs: "followUp" });
|
|
919
|
+
} catch (err) {
|
|
920
|
+
console.error("avenor tick: sendUserMessage failed", err);
|
|
861
921
|
}
|
|
862
922
|
trackedRuns.delete(entry.runId);
|
|
863
923
|
} else if (entry.pendingPermission && !run.blocking && !run.permissionNotified) {
|
|
@@ -1398,14 +1458,16 @@ function createExtension(deps = defaultDeps) {
|
|
|
1398
1458
|
run_id: Type.String({ description: "Run ID with the pending permission" }),
|
|
1399
1459
|
option_id: Type.String({ description: "allow_once | allow_always | deny" }),
|
|
1400
1460
|
request_id: Type.Optional(Type.String({ description: "Permission request ID (auto-discovered if omitted)" })),
|
|
1401
|
-
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
|
|
1461
|
+
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" })),
|
|
1462
|
+
message: Type.Optional(Type.String({ description: "Optional write-in response text. Required only when the selected option declares requiresMessage: true." }))
|
|
1402
1463
|
}),
|
|
1403
1464
|
async execute(_toolCallId, params) {
|
|
1404
1465
|
const result = await deps.answerPermissionTool({
|
|
1405
1466
|
runId: params.run_id,
|
|
1406
1467
|
optionId: params.option_id,
|
|
1407
1468
|
requestId: params.request_id,
|
|
1408
|
-
supervisorId: params.supervisor_id
|
|
1469
|
+
supervisorId: params.supervisor_id,
|
|
1470
|
+
message: params.message
|
|
1409
1471
|
});
|
|
1410
1472
|
return {
|
|
1411
1473
|
content: [{
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dougbots/avenor-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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.
|
|
38
|
+
"@dougbots/avenor-core": "^0.8.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@earendil-works/pi-coding-agent": "*",
|