@dougbots/avenor-pi 0.10.2 → 0.10.4
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 +9 -0
- package/dist/index.js +149 -1
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
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";
|
|
2
2
|
import { EventBus, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
|
|
4
|
+
//#region src/subagent-broker.d.ts
|
|
5
|
+
interface SubAgentEnv {
|
|
6
|
+
brokerUrl: string;
|
|
7
|
+
runId: string;
|
|
8
|
+
token: string;
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
4
11
|
//#region src/types.d.ts
|
|
5
12
|
interface RunStatusEntry {
|
|
6
13
|
runId: string;
|
|
@@ -152,6 +159,8 @@ declare function buildInspectPayload(result: InspectResult): Record<string, unkn
|
|
|
152
159
|
interface ExtensionOptions {
|
|
153
160
|
/** Polling cadence; injectable so tests can drive deterministic ticks. */
|
|
154
161
|
pollIntervalMs?: number;
|
|
162
|
+
/** Overrides broker-credential autodetection for sub-agent broker mode (tests). */
|
|
163
|
+
subAgentEnv?: SubAgentEnv | null;
|
|
155
164
|
}
|
|
156
165
|
declare function createExtension(deps?: ExtensionDeps, options?: ExtensionOptions): (pi: ExtensionAPI) => Promise<void>;
|
|
157
166
|
declare const _default: (pi: ExtensionAPI) => Promise<void>;
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,78 @@
|
|
|
1
1
|
import { Input, Key, Text, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
3
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
5
|
import * as fs from "node:fs";
|
|
5
6
|
import * as path from "node:path";
|
|
6
|
-
|
|
7
|
+
//#region src/subagent-broker.ts
|
|
8
|
+
function readSubAgentEnv(env = process.env) {
|
|
9
|
+
const brokerUrl = env.AVENOR_BROKER_URL ?? "";
|
|
10
|
+
const runId = env.AVENOR_RUN_ID ?? "";
|
|
11
|
+
const token = env.AVENOR_BROKER_TOKEN ?? "";
|
|
12
|
+
if (brokerUrl && runId && token) return {
|
|
13
|
+
brokerUrl,
|
|
14
|
+
runId,
|
|
15
|
+
token
|
|
16
|
+
};
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
/** Parse a broker control message that is an agent_message into an InboundAsk. */
|
|
20
|
+
function parseControlMessage(msg) {
|
|
21
|
+
if (msg.type !== "agent_message") return null;
|
|
22
|
+
let payload = msg.payload;
|
|
23
|
+
if (typeof payload === "string") try {
|
|
24
|
+
payload = JSON.parse(payload);
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
const p = payload;
|
|
29
|
+
if (!p || typeof p.message !== "string" || !p.message) return null;
|
|
30
|
+
const messageId = String(p.id ?? "");
|
|
31
|
+
if (!messageId) return null;
|
|
32
|
+
const fromRunId = String(p.from_run_id ?? p.from ?? "");
|
|
33
|
+
if (!fromRunId) return null;
|
|
34
|
+
return {
|
|
35
|
+
from_run_id: fromRunId,
|
|
36
|
+
message_id: messageId,
|
|
37
|
+
message: p.message
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function formatInboundAskBody(ask) {
|
|
41
|
+
return `**📨 Inbound ask**${ask.from_run_id ? ` from \`${ask.from_run_id}\`` : ""}\n\n${ask.message}\n\nTo answer, use avenor_reply({ reply_to_message_id: ${JSON.stringify(ask.message_id)}, message: "..." }).`;
|
|
42
|
+
}
|
|
43
|
+
async function postBroker(env, path, body, fetchImpl = fetch) {
|
|
44
|
+
const res = await fetchImpl(`${env.brokerUrl}${path}`, {
|
|
45
|
+
method: "POST",
|
|
46
|
+
headers: { "Content-Type": "application/json" },
|
|
47
|
+
body: JSON.stringify({
|
|
48
|
+
run_id: env.runId,
|
|
49
|
+
token: env.token,
|
|
50
|
+
...body
|
|
51
|
+
})
|
|
52
|
+
});
|
|
53
|
+
if (!res.ok) throw new Error(`broker ${path}: ${res.status} ${(await res.text()).trim()}`);
|
|
54
|
+
return res.json();
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolve which pending ask to answer. Prefers an explicit message id (and its
|
|
58
|
+
* asker); otherwise the most recent pending ask matching fromRunId.
|
|
59
|
+
*/
|
|
60
|
+
function resolveReplyTarget(pending, fromRunId, replyToMessageId) {
|
|
61
|
+
if (replyToMessageId) {
|
|
62
|
+
const hit = pending.get(replyToMessageId);
|
|
63
|
+
if (!hit) return null;
|
|
64
|
+
const from = fromRunId ?? hit.from_run_id;
|
|
65
|
+
if (!from) return null;
|
|
66
|
+
return {
|
|
67
|
+
from_run_id: from,
|
|
68
|
+
message_id: replyToMessageId
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
let match;
|
|
72
|
+
for (const [, entry] of pending) if (!fromRunId || entry.from_run_id === fromRunId) match = entry;
|
|
73
|
+
return match ?? null;
|
|
74
|
+
}
|
|
75
|
+
//#endregion
|
|
7
76
|
//#region src/watch.ts
|
|
8
77
|
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;
|
|
9
78
|
const TRANSCRIPT_SCROLL_STEP = 4;
|
|
@@ -1457,6 +1526,49 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
1457
1526
|
let pollingErrorCount = 0;
|
|
1458
1527
|
const pollingErrors = [];
|
|
1459
1528
|
let lastStatusEntries = [];
|
|
1529
|
+
const subAgentEnv = options.subAgentEnv !== void 0 ? options.subAgentEnv : readSubAgentEnv();
|
|
1530
|
+
const isSubAgentBrokerMode = subAgentEnv !== null;
|
|
1531
|
+
let subAgentPollTimer = null;
|
|
1532
|
+
let subAgentPollGeneration = 0;
|
|
1533
|
+
const subAgentPendingAsks = /* @__PURE__ */ new Map();
|
|
1534
|
+
function stopSubAgentPoll() {
|
|
1535
|
+
subAgentPollGeneration++;
|
|
1536
|
+
if (subAgentPollTimer) {
|
|
1537
|
+
clearTimeout(subAgentPollTimer);
|
|
1538
|
+
subAgentPollTimer = null;
|
|
1539
|
+
}
|
|
1540
|
+
}
|
|
1541
|
+
async function startSubAgentPoll(ctx) {
|
|
1542
|
+
if (!subAgentEnv) return;
|
|
1543
|
+
stopSubAgentPoll();
|
|
1544
|
+
const generation = subAgentPollGeneration;
|
|
1545
|
+
const intervalMs = options.pollIntervalMs ?? 2e3;
|
|
1546
|
+
let consecutiveFailures = 0;
|
|
1547
|
+
const tick = async () => {
|
|
1548
|
+
try {
|
|
1549
|
+
const msgs = await postBroker(subAgentEnv, "/poll-control", {}) ?? [];
|
|
1550
|
+
if (consecutiveFailures >= 3) console.error("[avenor] sub-agent broker poll recovered");
|
|
1551
|
+
consecutiveFailures = 0;
|
|
1552
|
+
for (const msg of msgs) {
|
|
1553
|
+
const ask = parseControlMessage(msg);
|
|
1554
|
+
if (!ask) continue;
|
|
1555
|
+
subAgentPendingAsks.set(ask.message_id, {
|
|
1556
|
+
from_run_id: ask.from_run_id,
|
|
1557
|
+
message_id: ask.message_id
|
|
1558
|
+
});
|
|
1559
|
+
await ctx.sendUserMessage(formatInboundAskBody(ask), { deliverAs: "steer" });
|
|
1560
|
+
}
|
|
1561
|
+
} catch (error) {
|
|
1562
|
+
consecutiveFailures++;
|
|
1563
|
+
if (consecutiveFailures === 3) console.error("[avenor] sub-agent broker poll failing:", error instanceof Error ? error.message : error);
|
|
1564
|
+
}
|
|
1565
|
+
if (generation !== subAgentPollGeneration) return;
|
|
1566
|
+
subAgentPollTimer = setTimeout(() => void tick(), intervalMs);
|
|
1567
|
+
subAgentPollTimer.unref?.();
|
|
1568
|
+
};
|
|
1569
|
+
subAgentPollTimer = setTimeout(() => void tick(), intervalMs);
|
|
1570
|
+
subAgentPollTimer.unref?.();
|
|
1571
|
+
}
|
|
1460
1572
|
function recordPollingError(source, message, error, runId) {
|
|
1461
1573
|
pollingErrorCount++;
|
|
1462
1574
|
const payload = createPollErrorPayload({
|
|
@@ -1925,11 +2037,14 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
1925
2037
|
pollingErrorCount = 0;
|
|
1926
2038
|
pollingErrors.length = 0;
|
|
1927
2039
|
startHostAskPoll(ctx);
|
|
2040
|
+
startSubAgentPoll(ctx);
|
|
1928
2041
|
});
|
|
1929
2042
|
pi.on("session_shutdown", async () => {
|
|
1930
2043
|
await stopPolling();
|
|
1931
2044
|
stopHostAskPoll();
|
|
2045
|
+
stopSubAgentPoll();
|
|
1932
2046
|
hostPendingAsks.clear();
|
|
2047
|
+
subAgentPendingAsks.clear();
|
|
1933
2048
|
sessionCtx = null;
|
|
1934
2049
|
});
|
|
1935
2050
|
pi.on("before_agent_start", async () => {
|
|
@@ -2426,6 +2541,39 @@ function createExtension(deps = defaultDeps, options = {}) {
|
|
|
2426
2541
|
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" }))
|
|
2427
2542
|
}),
|
|
2428
2543
|
async execute(_toolCallId, params) {
|
|
2544
|
+
if (isSubAgentBrokerMode && subAgentEnv) {
|
|
2545
|
+
const target = resolveReplyTarget(subAgentPendingAsks, params.from_run_id, params.reply_to_message_id);
|
|
2546
|
+
if (!target) return {
|
|
2547
|
+
content: [{
|
|
2548
|
+
type: "text",
|
|
2549
|
+
text: "No pending ask found to reply to. Pass from_run_id and/or reply_to_message_id."
|
|
2550
|
+
}],
|
|
2551
|
+
details: { error: true }
|
|
2552
|
+
};
|
|
2553
|
+
subAgentPendingAsks.delete(target.message_id);
|
|
2554
|
+
try {
|
|
2555
|
+
await postBroker(subAgentEnv, "/send", {
|
|
2556
|
+
from_run_id: subAgentEnv.runId,
|
|
2557
|
+
to_run_id: target.from_run_id,
|
|
2558
|
+
type: "agent_message",
|
|
2559
|
+
payload: {
|
|
2560
|
+
id: randomUUID(),
|
|
2561
|
+
from_run_id: subAgentEnv.runId,
|
|
2562
|
+
to_run_id: target.from_run_id,
|
|
2563
|
+
message: params.message,
|
|
2564
|
+
role: "agent",
|
|
2565
|
+
reply_to: target.message_id
|
|
2566
|
+
}
|
|
2567
|
+
});
|
|
2568
|
+
} catch (error) {
|
|
2569
|
+
subAgentPendingAsks.set(target.message_id, target);
|
|
2570
|
+
throw error;
|
|
2571
|
+
}
|
|
2572
|
+
return { content: [{
|
|
2573
|
+
type: "text",
|
|
2574
|
+
text: "Reply sent"
|
|
2575
|
+
}] };
|
|
2576
|
+
}
|
|
2429
2577
|
let fromRunId = params.from_run_id;
|
|
2430
2578
|
let messageId = params.reply_to_message_id;
|
|
2431
2579
|
if (!messageId) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dougbots/avenor-pi",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.4",
|
|
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.
|
|
38
|
+
"@dougbots/avenor-core": "^0.10.4"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
41
|
"@earendil-works/pi-coding-agent": ">=0.74.0",
|