@dougbots/avenor-pi 0.8.3 → 0.9.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/README.md +17 -3
- package/dist/index.d.ts +64 -2
- package/dist/index.js +662 -10
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -86,8 +86,9 @@ packages/pi/
|
|
|
86
86
|
├── package.json
|
|
87
87
|
├── tsdown.config.ts
|
|
88
88
|
├── src/
|
|
89
|
-
│ ├── index.ts # main extension
|
|
90
|
-
│ ├──
|
|
89
|
+
│ ├── index.ts # main extension, tool registration, commands, and hooks
|
|
90
|
+
│ ├── render.ts # host-only tool-call and result presentation
|
|
91
|
+
│ ├── types.ts # shared types and status emoji mapping
|
|
91
92
|
│ └── watch.ts # EventFeedOverlay TUI component
|
|
92
93
|
└── dist/
|
|
93
94
|
└── index.js # built output (loaded by pi)
|
|
@@ -128,12 +129,25 @@ Beyond the tools, the extension integrates with Pi's TUI and event system:
|
|
|
128
129
|
- **Footer status** — active runs shown in the Pi footer bar
|
|
129
130
|
- **Live progress** — blocking `avenor_spawn` calls stream progress updates via `onUpdate`
|
|
130
131
|
- **Context enrichment** — active sub-agents are automatically surfaced in the system prompt via `before_agent_start`
|
|
131
|
-
- **Custom rendering** — tool calls and results
|
|
132
|
+
- **Custom rendering** — The Pi extension renders all Avenor tool calls and results as themed, bounded summaries. Results have collapsed and expanded views.
|
|
132
133
|
- **Commands** — interactive commands for run management:
|
|
133
134
|
- `/avenor-status` — show status of all runs
|
|
134
135
|
- `/avenor-watch <run_id>` — open a live event feed overlay
|
|
135
136
|
- `/avenor-cancel <run_id>` — cancel a running sub-agent
|
|
136
137
|
|
|
138
|
+
### Tool-result channels
|
|
139
|
+
|
|
140
|
+
Pi returns JSON model content in `content[0].text` and structured `details` for
|
|
141
|
+
all tools except `avenor_spawn`. The renderer creates themed, bounded summaries
|
|
142
|
+
for display. It does not modify the underlying model content or the `details`
|
|
143
|
+
object.
|
|
144
|
+
|
|
145
|
+
Pi's rendered summaries sanitize and bound the output, events, and snapshot rows
|
|
146
|
+
they display. Use `avenor_result`, `avenor_inspect`, or `avenor_events` for the
|
|
147
|
+
corresponding data.
|
|
148
|
+
|
|
149
|
+
MCP clients render their own results.
|
|
150
|
+
|
|
137
151
|
### Agent profiles
|
|
138
152
|
|
|
139
153
|
The `avenor_spawn` tool accepts an optional `agent` parameter that maps to a named profile in pi's `agents.json`:
|
package/dist/index.d.ts
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { InspectResult, StatusResult, Supervisor, answerPermissionTool, dial, eventsTool, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, spawnTool, statusTool } from "@dougbots/avenor-core";
|
|
2
|
-
import { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
import { EventBus, ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
3
3
|
|
|
4
4
|
//#region src/types.d.ts
|
|
5
5
|
interface RunStatusEntry {
|
|
6
6
|
runId: string;
|
|
7
|
+
/** Supervisor socket provided at spawn time for namespace scoping. */
|
|
8
|
+
supervisorId?: string;
|
|
9
|
+
/** Runtime ID scoped to the explicit or singleton supervisor. */
|
|
10
|
+
runtimeId?: string;
|
|
7
11
|
label: string;
|
|
8
12
|
status: string;
|
|
9
13
|
phase?: string;
|
|
@@ -19,6 +23,64 @@ interface RunStatusEntry {
|
|
|
19
23
|
nestedCount?: number;
|
|
20
24
|
}
|
|
21
25
|
//#endregion
|
|
26
|
+
//#region src/events.d.ts
|
|
27
|
+
/** Emitted when the official extension completes a polling cycle. */
|
|
28
|
+
declare const CHANNEL_POLL_COMPLETED: "avenor:poll:completed";
|
|
29
|
+
/** Emitted when the extension observes a terminal run result. */
|
|
30
|
+
declare const CHANNEL_RUN_TERMINAL: "avenor:run:terminal";
|
|
31
|
+
type AvenorRunStatus = Readonly<Omit<RunStatusEntry, 'supervisorId' | 'pid' | 'permissionDescription'> & {
|
|
32
|
+
supervisorKey: string;
|
|
33
|
+
}>;
|
|
34
|
+
interface PollCompletedPayload {
|
|
35
|
+
/** Status for each direct run tracked by the official extension. */
|
|
36
|
+
readonly entries: readonly AvenorRunStatus[];
|
|
37
|
+
/** Wall-clock timestamp in epoch milliseconds. */
|
|
38
|
+
readonly timestamp: number;
|
|
39
|
+
/** Monotonic counter for this official-extension instance. */
|
|
40
|
+
readonly generation: number;
|
|
41
|
+
}
|
|
42
|
+
interface RunTerminalPayload {
|
|
43
|
+
readonly runId: string;
|
|
44
|
+
/** Stable opaque scope for the explicit or singleton supervisor. */
|
|
45
|
+
readonly supervisorKey: string;
|
|
46
|
+
readonly runtimeId?: string;
|
|
47
|
+
readonly label: string;
|
|
48
|
+
readonly agent: string;
|
|
49
|
+
readonly status: string;
|
|
50
|
+
/** Number of active descendants when the run was removed, if available. */
|
|
51
|
+
readonly nestedCount?: number;
|
|
52
|
+
readonly backend?: string;
|
|
53
|
+
}
|
|
54
|
+
interface RunTerminalPayloadInput {
|
|
55
|
+
readonly runId: string;
|
|
56
|
+
/** Local supervisor socket, converted to an opaque supervisorKey. */
|
|
57
|
+
readonly supervisorId?: string;
|
|
58
|
+
readonly runtimeId?: string;
|
|
59
|
+
readonly label: string;
|
|
60
|
+
readonly agent: string;
|
|
61
|
+
readonly status: string;
|
|
62
|
+
readonly nestedCount?: number;
|
|
63
|
+
readonly backend?: string;
|
|
64
|
+
}
|
|
65
|
+
interface AvenorEventMap {
|
|
66
|
+
[CHANNEL_POLL_COMPLETED]: PollCompletedPayload;
|
|
67
|
+
[CHANNEL_RUN_TERMINAL]: RunTerminalPayload;
|
|
68
|
+
}
|
|
69
|
+
type AvenorEventChannel = keyof AvenorEventMap;
|
|
70
|
+
type AvenorEventBus = Pick<EventBus, 'emit' | 'on'>;
|
|
71
|
+
/** Emit an Avenor event with a type-checked payload. */
|
|
72
|
+
declare function emitAvenorEvent<K extends AvenorEventChannel>(bus: Pick<EventBus, 'emit'>, event: K, data: AvenorEventMap[K]): void;
|
|
73
|
+
/** Subscribe to an Avenor event and return Pi's unsubscribe function. */
|
|
74
|
+
declare function onAvenorEvent<K extends AvenorEventChannel>(bus: Pick<EventBus, 'on'>, event: K, handler: (data: AvenorEventMap[K]) => void): () => void;
|
|
75
|
+
/**
|
|
76
|
+
* Copy and freeze the selected fields exposed to companion consumers.
|
|
77
|
+
* Keep this allowlist explicit so future private RunStatusEntry fields are not
|
|
78
|
+
* included in the event accidentally.
|
|
79
|
+
*/
|
|
80
|
+
declare function createPollCompletedPayload(entries: readonly RunStatusEntry[], timestamp: number, generation: number): PollCompletedPayload;
|
|
81
|
+
/** Copy and freeze a terminal payload before publishing it. */
|
|
82
|
+
declare function createRunTerminalPayload(payload: RunTerminalPayloadInput): RunTerminalPayload;
|
|
83
|
+
//#endregion
|
|
22
84
|
//#region src/index.d.ts
|
|
23
85
|
interface ExtensionDeps {
|
|
24
86
|
spawnTool: typeof spawnTool;
|
|
@@ -45,4 +107,4 @@ declare function buildInspectPayload(result: InspectResult): Record<string, unkn
|
|
|
45
107
|
declare function createExtension(deps?: ExtensionDeps): (pi: ExtensionAPI) => Promise<void>;
|
|
46
108
|
declare const _default: (pi: ExtensionAPI) => Promise<void>;
|
|
47
109
|
//#endregion
|
|
48
|
-
export { ExtensionDeps, buildCompletionText, buildInspectPayload, compactWhitespace, createExtension, _default as default, isTerminalStatus, renderStatusLines, statusSupervisorId };
|
|
110
|
+
export { type AvenorEventBus, type AvenorEventChannel, type AvenorEventMap, type AvenorRunStatus, CHANNEL_POLL_COMPLETED, CHANNEL_RUN_TERMINAL, ExtensionDeps, type PollCompletedPayload, type RunTerminalPayload, type RunTerminalPayloadInput, buildCompletionText, buildInspectPayload, compactWhitespace, createExtension, createPollCompletedPayload, createRunTerminalPayload, _default as default, emitAvenorEvent, isTerminalStatus, onAvenorEvent, renderStatusLines, statusSupervisorId };
|
package/dist/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { Input, Key, Text, matchesKey, truncateToWidth, visibleWidth, wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
2
2
|
import { Type } from "typebox";
|
|
3
|
-
import { Supervisor, answerPermissionTool, dial, eventsTool, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, socketsRoot, spawnTool, statusTool } from "@dougbots/avenor-core";
|
|
3
|
+
import { Supervisor, answerPermissionTool, dial, eventsTool, extractEventText, followUpTool, inspectTool, observeRun, resultTool, shutdownTool, socketsRoot, spawnTool, statusTool } from "@dougbots/avenor-core";
|
|
4
4
|
import * as fs from "node:fs";
|
|
5
5
|
import * as path from "node:path";
|
|
6
|
+
import { createHash } from "node:crypto";
|
|
6
7
|
//#region src/watch.ts
|
|
7
8
|
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;
|
|
8
9
|
const TRANSCRIPT_SCROLL_STEP = 4;
|
|
9
10
|
const ACTION_STATUS_CHARS = 180;
|
|
10
11
|
const PREVIEW_CHARS = 240;
|
|
11
12
|
const TOOL_PREVIEW_CHARS = 120;
|
|
12
|
-
const FINAL_OUTPUT_CHARS = 600;
|
|
13
|
+
const FINAL_OUTPUT_CHARS$1 = 600;
|
|
13
14
|
const TRANSCRIPT_PADDING = 2;
|
|
14
15
|
const INSPECT_ERROR_PREFIX = "error: ";
|
|
15
16
|
const TERMINAL_STATUSES$1 = new Set([
|
|
@@ -90,7 +91,7 @@ function usageSummary(usage) {
|
|
|
90
91
|
return parts.join(" · ");
|
|
91
92
|
}
|
|
92
93
|
function finalOutputPreview(status, snapshot) {
|
|
93
|
-
return clipText(status?.final_output ?? snapshot?.final_output ?? snapshot?.assistant_text, FINAL_OUTPUT_CHARS);
|
|
94
|
+
return clipText(status?.final_output ?? snapshot?.final_output ?? snapshot?.assistant_text, FINAL_OUTPUT_CHARS$1);
|
|
94
95
|
}
|
|
95
96
|
function currentStatusWord(status, snapshot) {
|
|
96
97
|
const raw = status?.status?.toLowerCase();
|
|
@@ -702,6 +703,521 @@ function resolveAgentProfile(entries, environment = process.env) {
|
|
|
702
703
|
return selected;
|
|
703
704
|
}
|
|
704
705
|
//#endregion
|
|
706
|
+
//#region src/events.ts
|
|
707
|
+
/**
|
|
708
|
+
* Public telemetry types and adapters for companion Pi extensions.
|
|
709
|
+
*
|
|
710
|
+
* The transport is Pi's shared `pi.events` bus. This module owns no event
|
|
711
|
+
* handlers or singleton state, so consumers can use the surface even when
|
|
712
|
+
* package dependencies are resolved from different module instances.
|
|
713
|
+
*
|
|
714
|
+
* Payloads select status fields and omit transcripts, final output, directories,
|
|
715
|
+
* socket paths, raw status objects, and tool arguments.
|
|
716
|
+
*/
|
|
717
|
+
/** Emitted when the official extension completes a polling cycle. */
|
|
718
|
+
const CHANNEL_POLL_COMPLETED = "avenor:poll:completed";
|
|
719
|
+
/** Emitted when the extension observes a terminal run result. */
|
|
720
|
+
const CHANNEL_RUN_TERMINAL = "avenor:run:terminal";
|
|
721
|
+
/** Derive a stable non-connectable scope from a local supervisor socket. */
|
|
722
|
+
function createSupervisorKey(supervisorId) {
|
|
723
|
+
if (!supervisorId) return "singleton";
|
|
724
|
+
return `supervisor:${createHash("sha256").update(supervisorId).digest("hex").slice(0, 16)}`;
|
|
725
|
+
}
|
|
726
|
+
/** Emit an Avenor event with a type-checked payload. */
|
|
727
|
+
function emitAvenorEvent(bus, event, data) {
|
|
728
|
+
bus.emit(event, data);
|
|
729
|
+
}
|
|
730
|
+
/** Subscribe to an Avenor event and return Pi's unsubscribe function. */
|
|
731
|
+
function onAvenorEvent(bus, event, handler) {
|
|
732
|
+
return bus.on(event, handler);
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Copy and freeze the selected fields exposed to companion consumers.
|
|
736
|
+
* Keep this allowlist explicit so future private RunStatusEntry fields are not
|
|
737
|
+
* included in the event accidentally.
|
|
738
|
+
*/
|
|
739
|
+
function createPollCompletedPayload(entries, timestamp, generation) {
|
|
740
|
+
const publicEntries = entries.map((entry) => Object.freeze({
|
|
741
|
+
runId: entry.runId,
|
|
742
|
+
supervisorKey: createSupervisorKey(entry.supervisorId),
|
|
743
|
+
...entry.runtimeId !== void 0 && { runtimeId: entry.runtimeId },
|
|
744
|
+
label: entry.label,
|
|
745
|
+
status: entry.status,
|
|
746
|
+
...entry.phase !== void 0 && { phase: entry.phase },
|
|
747
|
+
...entry.phaseLabel !== void 0 && { phaseLabel: entry.phaseLabel },
|
|
748
|
+
agent: entry.agent,
|
|
749
|
+
...entry.pendingPermission !== void 0 && { pendingPermission: entry.pendingPermission },
|
|
750
|
+
...entry.backend !== void 0 && { backend: entry.backend },
|
|
751
|
+
...entry.nestedCount !== void 0 && { nestedCount: entry.nestedCount }
|
|
752
|
+
}));
|
|
753
|
+
return Object.freeze({
|
|
754
|
+
entries: Object.freeze(publicEntries),
|
|
755
|
+
timestamp,
|
|
756
|
+
generation
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
/** Copy and freeze a terminal payload before publishing it. */
|
|
760
|
+
function createRunTerminalPayload(payload) {
|
|
761
|
+
return Object.freeze({
|
|
762
|
+
runId: payload.runId,
|
|
763
|
+
supervisorKey: createSupervisorKey(payload.supervisorId),
|
|
764
|
+
...payload.runtimeId !== void 0 && { runtimeId: payload.runtimeId },
|
|
765
|
+
label: payload.label,
|
|
766
|
+
agent: payload.agent,
|
|
767
|
+
status: payload.status,
|
|
768
|
+
...payload.nestedCount !== void 0 && { nestedCount: payload.nestedCount },
|
|
769
|
+
...payload.backend !== void 0 && { backend: payload.backend }
|
|
770
|
+
});
|
|
771
|
+
}
|
|
772
|
+
//#endregion
|
|
773
|
+
//#region src/render.ts
|
|
774
|
+
const FINAL_OUTPUT_CHARS = 600;
|
|
775
|
+
const SCALAR_CHARS = 240;
|
|
776
|
+
const EVENT_CHARS = 600;
|
|
777
|
+
const PREVIEW_LINES = 12;
|
|
778
|
+
const STATUS_ROWS = 12;
|
|
779
|
+
const COLLECTION_ITEMS = 8;
|
|
780
|
+
const INSPECT_ITEMS = 5;
|
|
781
|
+
const CLEANUP_ITEMS = 8;
|
|
782
|
+
function asRecord(value) {
|
|
783
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
|
|
784
|
+
}
|
|
785
|
+
function stringValue(value) {
|
|
786
|
+
return typeof value === "string" && value.trim() ? value : void 0;
|
|
787
|
+
}
|
|
788
|
+
function numberValue(value) {
|
|
789
|
+
if (typeof value === "number" && Number.isFinite(value)) return value;
|
|
790
|
+
if (typeof value === "string" && value.trim() && Number.isFinite(Number(value))) return Number(value);
|
|
791
|
+
}
|
|
792
|
+
function itemMarker(count) {
|
|
793
|
+
return `… ${count} items omitted`;
|
|
794
|
+
}
|
|
795
|
+
function lineMarker(count) {
|
|
796
|
+
return `… ${count} lines omitted`;
|
|
797
|
+
}
|
|
798
|
+
function characterMarker(count) {
|
|
799
|
+
return `[preview clipped: ${count} characters omitted]`;
|
|
800
|
+
}
|
|
801
|
+
function cleanMultiline(value) {
|
|
802
|
+
if (typeof value !== "string") return void 0;
|
|
803
|
+
return sanitizeText(value).trim() || void 0;
|
|
804
|
+
}
|
|
805
|
+
function leadingClip(value, limit) {
|
|
806
|
+
if (value.length <= limit) return { text: value };
|
|
807
|
+
let prefixLength = Math.max(0, limit - characterMarker(value.length).length);
|
|
808
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
809
|
+
const omitted = value.length - prefixLength;
|
|
810
|
+
const nextPrefixLength = Math.max(0, limit - characterMarker(omitted).length);
|
|
811
|
+
if (nextPrefixLength === prefixLength) break;
|
|
812
|
+
prefixLength = nextPrefixLength;
|
|
813
|
+
}
|
|
814
|
+
const clipped = clipText(value, Math.max(1, prefixLength + 1)) ?? "";
|
|
815
|
+
const text = clipped.endsWith("…") ? clipped.slice(0, -1) : clipped.slice(0, prefixLength);
|
|
816
|
+
return {
|
|
817
|
+
text,
|
|
818
|
+
omitted: value.length - text.length
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
function scalar(value, fallback = "unknown", limit = SCALAR_CHARS) {
|
|
822
|
+
const clean = cleanMultiline(typeof value === "string" ? value : void 0);
|
|
823
|
+
if (!clean) return fallback;
|
|
824
|
+
const clipped = leadingClip(clean.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim(), limit);
|
|
825
|
+
return clipped.omitted === void 0 ? clipped.text : `${clipped.text}${characterMarker(clipped.omitted)}`;
|
|
826
|
+
}
|
|
827
|
+
function optionalScalar(value, limit = SCALAR_CHARS) {
|
|
828
|
+
const clean = cleanMultiline(typeof value === "string" ? value : void 0);
|
|
829
|
+
return clean ? scalar(clean, "unknown", limit) : void 0;
|
|
830
|
+
}
|
|
831
|
+
function preview(value, limit) {
|
|
832
|
+
const clean = cleanMultiline(value);
|
|
833
|
+
if (!clean) return void 0;
|
|
834
|
+
const clipped = leadingClip(clean, limit);
|
|
835
|
+
const lines = clipped.text.split("\n").slice(0, PREVIEW_LINES);
|
|
836
|
+
const omittedLines = Math.max(0, clean.split("\n").length - lines.length);
|
|
837
|
+
return [
|
|
838
|
+
...lines,
|
|
839
|
+
...clipped.omitted === void 0 ? [] : [characterMarker(clipped.omitted)],
|
|
840
|
+
...omittedLines === 0 ? [] : [lineMarker(omittedLines)]
|
|
841
|
+
];
|
|
842
|
+
}
|
|
843
|
+
function quoted(value) {
|
|
844
|
+
return `"${scalar(value).replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
|
|
845
|
+
}
|
|
846
|
+
function optionalQuoted(value) {
|
|
847
|
+
return stringValue(value) ? quoted(value) : void 0;
|
|
848
|
+
}
|
|
849
|
+
function optionalNumber(value) {
|
|
850
|
+
return numberValue(value) === void 0 ? void 0 : String(numberValue(value));
|
|
851
|
+
}
|
|
852
|
+
function selected(record, key, fallback, defaultValue = "unknown") {
|
|
853
|
+
return scalar(stringValue(record?.[key]) ?? stringValue(fallback), defaultValue);
|
|
854
|
+
}
|
|
855
|
+
function selectedOptional(record, key) {
|
|
856
|
+
return optionalScalar(record?.[key]);
|
|
857
|
+
}
|
|
858
|
+
function hasStatusIdentity(record) {
|
|
859
|
+
return !!record && !!(stringValue(record.run_id) || stringValue(record.label) || stringValue(record.status));
|
|
860
|
+
}
|
|
861
|
+
function linesText(lines, theme) {
|
|
862
|
+
return new Text(lines.map((line, index) => theme.fg(index === 0 ? "toolTitle" : "muted", index === 0 ? theme.bold(line) : line)).join("\n"), 0, 0);
|
|
863
|
+
}
|
|
864
|
+
function fallback(tool) {
|
|
865
|
+
return [`Avenor ${tool} result unavailable.`, "Retry the tool or use avenor_status/avenor_inspect."];
|
|
866
|
+
}
|
|
867
|
+
function partial(result) {
|
|
868
|
+
const details = asRecord(result.details);
|
|
869
|
+
const label = optionalScalar(details?.label);
|
|
870
|
+
const status = optionalScalar(details?.status);
|
|
871
|
+
if (label && status) return [`Working: ${label} — ${status}`];
|
|
872
|
+
if (status) return [`Working: ${status}`];
|
|
873
|
+
if (label) return [`Working: ${label}`];
|
|
874
|
+
const content = result.content?.[0]?.text;
|
|
875
|
+
const firstLine = typeof content === "string" ? content.split(/\r?\n/).find((line) => line.trim()) : void 0;
|
|
876
|
+
if (firstLine) {
|
|
877
|
+
const clean = scalar(firstLine, "", SCALAR_CHARS);
|
|
878
|
+
if (clean && !clean.startsWith("{") && !clean.startsWith("[")) return [`Working: ${clean}`];
|
|
879
|
+
}
|
|
880
|
+
return ["Working…"];
|
|
881
|
+
}
|
|
882
|
+
function isTerminal(status) {
|
|
883
|
+
return TERMINAL_STATUSES.has(status);
|
|
884
|
+
}
|
|
885
|
+
function permission(record) {
|
|
886
|
+
return asRecord(record?.pending_permission);
|
|
887
|
+
}
|
|
888
|
+
function permissionOptions(record) {
|
|
889
|
+
const options = record?.options;
|
|
890
|
+
return Array.isArray(options) ? options.map(asRecord).filter((value) => !!value) : [];
|
|
891
|
+
}
|
|
892
|
+
function optionRows(options) {
|
|
893
|
+
return options.slice(0, COLLECTION_ITEMS).map((option) => `${selected(option, "option_id")} (${selected(option, "label")})`).join(", ");
|
|
894
|
+
}
|
|
895
|
+
function optionLine(prefix, options) {
|
|
896
|
+
const rows = optionRows(options);
|
|
897
|
+
const omitted = options.length - Math.min(options.length, COLLECTION_ITEMS);
|
|
898
|
+
return `${prefix}${rows ? ` ${rows}` : ""}${omitted > 0 ? `${rows ? " " : " "}${itemMarker(omitted)}` : ""}`;
|
|
899
|
+
}
|
|
900
|
+
function guidance(status, pending, runId) {
|
|
901
|
+
if (status === "waiting") {
|
|
902
|
+
const options = permissionOptions(pending);
|
|
903
|
+
if (options.length > 0) return `Guidance: Call avenor_answer_permission with run_id "${runId}" and option_id "${selected(options[0], "option_id")}".`;
|
|
904
|
+
if (pending) return `Guidance: Permission input is required, but no options are available; call avenor_inspect with run_id "${runId}".`;
|
|
905
|
+
return `Guidance: Call avenor_follow_up or avenor_status with run_id "${runId}".`;
|
|
906
|
+
}
|
|
907
|
+
if (isTerminal(status)) return `Guidance: Call avenor_result or avenor_events with run_id "${runId}".`;
|
|
908
|
+
return `Guidance: Call avenor_result with run_id "${runId}".`;
|
|
909
|
+
}
|
|
910
|
+
function statusExpanded(status) {
|
|
911
|
+
return [
|
|
912
|
+
["Phase", "phase"],
|
|
913
|
+
["Phase label", "phase_label"],
|
|
914
|
+
["Session", "session_id"],
|
|
915
|
+
["Backend", "backend"],
|
|
916
|
+
["Agent", "agent"],
|
|
917
|
+
["Model", "model"],
|
|
918
|
+
["Latest sequence", "latest_seq"],
|
|
919
|
+
["Stop reason", "stop_reason"]
|
|
920
|
+
].flatMap(([label, key]) => {
|
|
921
|
+
const value = key === "latest_seq" ? optionalNumber(status[key]) : selectedOptional(status, key);
|
|
922
|
+
return value === void 0 ? [] : [`${label}: ${value}`];
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
function statusLines(details, args, expanded) {
|
|
926
|
+
if (Array.isArray(details)) {
|
|
927
|
+
const statuses = details.map(asRecord);
|
|
928
|
+
if (statuses.some((status) => !hasStatusIdentity(status))) return void 0;
|
|
929
|
+
const displayed = statuses.slice(0, STATUS_ROWS);
|
|
930
|
+
const lines = [
|
|
931
|
+
`Avenor status — ${statuses.length} runs`,
|
|
932
|
+
...displayed.map((status) => {
|
|
933
|
+
const runId = selected(status, "run_id", args.run_id);
|
|
934
|
+
return `${selected(status, "label", void 0, runId)} — ${selected(status, "status")} (run_id: ${runId})`;
|
|
935
|
+
}),
|
|
936
|
+
...statuses.length > displayed.length ? [itemMarker(statuses.length - displayed.length)] : []
|
|
937
|
+
];
|
|
938
|
+
if (!expanded) return lines;
|
|
939
|
+
for (const status of displayed) {
|
|
940
|
+
const runId = selected(status, "run_id", args.run_id);
|
|
941
|
+
lines.push(`Run: ${runId}`, `Label: ${selected(status, "label", void 0, runId)}`, `Status: ${selected(status, "status")}`, ...statusExpanded(status));
|
|
942
|
+
}
|
|
943
|
+
return lines;
|
|
944
|
+
}
|
|
945
|
+
const status = asRecord(details);
|
|
946
|
+
if (!hasStatusIdentity(status)) return void 0;
|
|
947
|
+
const runId = selected(status, "run_id", args.run_id);
|
|
948
|
+
const label = selected(status, "label", void 0, runId);
|
|
949
|
+
const state = selected(status, "status");
|
|
950
|
+
const pending = permission(status);
|
|
951
|
+
const options = permissionOptions(pending);
|
|
952
|
+
const finalPreview = preview(status.final_output, FINAL_OUTPUT_CHARS);
|
|
953
|
+
const lines = [
|
|
954
|
+
`Status: ${label} — ${state}`,
|
|
955
|
+
`Run: ${runId}`,
|
|
956
|
+
...pending ? [`Permission: ${selected(pending, "description")}`, optionLine("Options:", options)] : [],
|
|
957
|
+
...status.final_output_truncated === true ? [`Warning: final output preview is truncated; call avenor_result with run_id "${runId}".`] : [],
|
|
958
|
+
...finalPreview ? ["Final output:", ...finalPreview] : ["Final output: unavailable."],
|
|
959
|
+
guidance(state, pending, runId)
|
|
960
|
+
];
|
|
961
|
+
return expanded ? [...lines, ...statusExpanded(status)] : lines;
|
|
962
|
+
}
|
|
963
|
+
function resultLines(details, args, expanded) {
|
|
964
|
+
const result = asRecord(details);
|
|
965
|
+
if (!hasStatusIdentity(result)) return void 0;
|
|
966
|
+
const runId = selected(result, "run_id", args.run_id);
|
|
967
|
+
const label = selected(result, "label", void 0, runId);
|
|
968
|
+
const state = selected(result, "status");
|
|
969
|
+
const pending = permission(result);
|
|
970
|
+
const finalPreview = preview(result.output, FINAL_OUTPUT_CHARS);
|
|
971
|
+
const lines = [
|
|
972
|
+
`Result: ${label} — ${state}`,
|
|
973
|
+
`Run: ${runId}`,
|
|
974
|
+
...result.timed_out === true ? ["Warning: wait timed out; the run is still active."] : [],
|
|
975
|
+
...pending ? [`Permission: ${selected(pending, "description")}`] : [],
|
|
976
|
+
guidance(state, pending, runId),
|
|
977
|
+
...result.output_truncated === true ? [stringValue(result.output_event_path) ? `Warning: final output may be truncated; retry avenor_result or read the durable event path "${scalar(result.output_event_path)}".` : `Warning: final output may be truncated; call avenor_events with run_id "${runId}".`] : [],
|
|
978
|
+
...finalPreview ? ["Final output:", ...finalPreview] : ["Final output: unavailable."]
|
|
979
|
+
];
|
|
980
|
+
if (!expanded) return lines;
|
|
981
|
+
const session = selectedOptional(result, "session_id");
|
|
982
|
+
const stopReason = selectedOptional(result, "stop_reason");
|
|
983
|
+
return [
|
|
984
|
+
...lines,
|
|
985
|
+
...session ? [`Session: ${session}`] : [],
|
|
986
|
+
...stopReason ? [`Stop reason: ${stopReason}`] : []
|
|
987
|
+
];
|
|
988
|
+
}
|
|
989
|
+
function permissionLines(details, args) {
|
|
990
|
+
const result = asRecord(details);
|
|
991
|
+
if (!result || typeof result.ok !== "boolean") return void 0;
|
|
992
|
+
const runId = scalar(args.run_id);
|
|
993
|
+
return [
|
|
994
|
+
`Permission answer: ${result.ok ? "accepted" : "failed"}.`,
|
|
995
|
+
`Run: ${runId}`,
|
|
996
|
+
`Option: ${scalar(args.option_id)}`,
|
|
997
|
+
...stringValue(args.request_id) ? [`Request: ${scalar(args.request_id)}`] : [],
|
|
998
|
+
`Guidance: Call avenor_status with run_id "${runId}" to verify the permission state.`
|
|
999
|
+
];
|
|
1000
|
+
}
|
|
1001
|
+
function followUpLines(details, args) {
|
|
1002
|
+
const result = asRecord(details);
|
|
1003
|
+
if (!result || !stringValue(result.run_id) || !stringValue(result.label)) return void 0;
|
|
1004
|
+
const runId = selected(result, "run_id", args.run_id);
|
|
1005
|
+
const label = selected(result, "label", args.label, runId);
|
|
1006
|
+
return [
|
|
1007
|
+
`Follow-up: ${label} — dispatched.`,
|
|
1008
|
+
`Source run: ${scalar(args.run_id)}`,
|
|
1009
|
+
`Run: ${runId}`,
|
|
1010
|
+
`Label: ${label}`,
|
|
1011
|
+
`Guidance: Call avenor_status with run_id "${runId}".`
|
|
1012
|
+
];
|
|
1013
|
+
}
|
|
1014
|
+
function eventSequence(value) {
|
|
1015
|
+
if (typeof value === "number" && Number.isFinite(value)) return String(value);
|
|
1016
|
+
return typeof value === "string" && value.trim() && Number.isFinite(Number(value)) ? value : void 0;
|
|
1017
|
+
}
|
|
1018
|
+
function eventRow(value) {
|
|
1019
|
+
const name = stringValue(value.event) ?? stringValue(value.type) ?? "unknown";
|
|
1020
|
+
const sequence = eventSequence(value.seq);
|
|
1021
|
+
const text = extractEventText(value);
|
|
1022
|
+
const previewText = text ? scalar(text, "unknown", EVENT_CHARS) : void 0;
|
|
1023
|
+
return `Event ${sequence ?? "?"}: ${scalar(name)}${previewText ? ` — ${previewText}` : ""}`;
|
|
1024
|
+
}
|
|
1025
|
+
function eventsLines(details, args, expanded) {
|
|
1026
|
+
const result = asRecord(details);
|
|
1027
|
+
if (!result || !Array.isArray(result.events) || result.events.some((event) => !asRecord(event))) return void 0;
|
|
1028
|
+
const events = result.events.map((event) => event);
|
|
1029
|
+
const runId = scalar(args.run_id);
|
|
1030
|
+
const types = Array.isArray(args.types) ? args.types : [];
|
|
1031
|
+
const displayedTypes = types.slice(0, COLLECTION_ITEMS).map((type) => scalar(type)).join(", ");
|
|
1032
|
+
const lines = [`Events: ${runId} — ${events.length} events`, ...types.length > 0 ? [`Filter: ${displayedTypes}${types.length > COLLECTION_ITEMS ? ` ${itemMarker(types.length - COLLECTION_ITEMS)}` : ""}`] : []];
|
|
1033
|
+
if (events.length === 0) return [...lines, "No events."];
|
|
1034
|
+
lines.push(`Preview: ${eventRow(events[0])}`);
|
|
1035
|
+
if (events.length > 1) lines.push(itemMarker(events.length - 1));
|
|
1036
|
+
if (!expanded) return lines;
|
|
1037
|
+
const displayed = events.slice(0, STATUS_ROWS);
|
|
1038
|
+
lines.push(...displayed.map(eventRow));
|
|
1039
|
+
if (events.length > displayed.length) lines.push(itemMarker(events.length - displayed.length));
|
|
1040
|
+
return lines;
|
|
1041
|
+
}
|
|
1042
|
+
function inspectTranscriptRow(value) {
|
|
1043
|
+
const kind = stringValue(value.kind);
|
|
1044
|
+
const text = optionalScalar(value.text);
|
|
1045
|
+
if (!kind) return void 0;
|
|
1046
|
+
if (kind === "tool") {
|
|
1047
|
+
const title = selected(value, "title", stringValue(value.kind) ?? stringValue(value.key));
|
|
1048
|
+
const status = optionalScalar(value.status);
|
|
1049
|
+
return `${title}${status ? ` (${status})` : ""}${text ? `: ${text}` : ""}`;
|
|
1050
|
+
}
|
|
1051
|
+
if (!text) return void 0;
|
|
1052
|
+
if ([
|
|
1053
|
+
"assistant",
|
|
1054
|
+
"thought",
|
|
1055
|
+
"user",
|
|
1056
|
+
"permission",
|
|
1057
|
+
"status",
|
|
1058
|
+
"session"
|
|
1059
|
+
].includes(kind)) return `${kind}: ${text}`;
|
|
1060
|
+
}
|
|
1061
|
+
function inspectToolRow(prefix, value) {
|
|
1062
|
+
const title = selected(value, "title", stringValue(value.kind) ?? stringValue(value.key));
|
|
1063
|
+
const status = optionalScalar(value.status);
|
|
1064
|
+
const text = optionalScalar(value.preview);
|
|
1065
|
+
return `${prefix}: ${title}${status ? ` (${status})` : ""}${text ? `: ${text}` : ""}`;
|
|
1066
|
+
}
|
|
1067
|
+
function inspectPermissionRow(value) {
|
|
1068
|
+
const description = scalar(stringValue(value.description) ?? stringValue(value.question) ?? stringValue(value.tool) ?? stringValue(value.kind));
|
|
1069
|
+
const options = permissionOptions(value);
|
|
1070
|
+
const rows = optionRows(options);
|
|
1071
|
+
return `Permission: ${description} (request_id: ${selected(value, "request_id")}); options:${rows ? ` ${rows}` : ""}${options.length > COLLECTION_ITEMS ? ` ${itemMarker(options.length - COLLECTION_ITEMS)}` : ""}`;
|
|
1072
|
+
}
|
|
1073
|
+
function inspectGroup(title, values, row) {
|
|
1074
|
+
const rows = (Array.isArray(values) ? values.map(asRecord).filter((value) => !!value) : []).map(row).filter((value) => !!value);
|
|
1075
|
+
return [
|
|
1076
|
+
`${title}:`,
|
|
1077
|
+
...rows.slice(0, INSPECT_ITEMS),
|
|
1078
|
+
...rows.length > INSPECT_ITEMS ? [itemMarker(rows.length - INSPECT_ITEMS)] : []
|
|
1079
|
+
];
|
|
1080
|
+
}
|
|
1081
|
+
function inspectLines(details, args, expanded) {
|
|
1082
|
+
const inspect = asRecord(details);
|
|
1083
|
+
const status = asRecord(inspect?.status);
|
|
1084
|
+
if (!inspect || !status || !hasStatusIdentity(inspect) && !hasStatusIdentity(status)) return void 0;
|
|
1085
|
+
const runId = selected(inspect, "run_id", args.run_id);
|
|
1086
|
+
const label = selected(inspect, "label", void 0, runId);
|
|
1087
|
+
const state = selected(status, "status");
|
|
1088
|
+
const finalPreview = preview(inspect.final_output, FINAL_OUTPUT_CHARS);
|
|
1089
|
+
const transcript = Array.isArray(inspect.transcript) ? inspect.transcript : [];
|
|
1090
|
+
const tools = Array.isArray(inspect.tools) ? inspect.tools : [];
|
|
1091
|
+
const liveTools = Array.isArray(inspect.live_tools) ? inspect.live_tools : [];
|
|
1092
|
+
const permissions = Array.isArray(inspect.permissions) ? inspect.permissions : [];
|
|
1093
|
+
const lines = [
|
|
1094
|
+
`Inspect: ${label} — ${state}`,
|
|
1095
|
+
`Run: ${runId}`,
|
|
1096
|
+
`Counts: transcript=${transcript.length}, tools=${tools.length}, live_tools=${liveTools.length}, permissions=${permissions.length}`,
|
|
1097
|
+
...finalPreview ? ["Final output:", ...finalPreview] : ["Final output: unavailable."],
|
|
1098
|
+
`Guidance: Call avenor_events with run_id "${runId}" for raw events.`
|
|
1099
|
+
];
|
|
1100
|
+
if (!expanded) return lines;
|
|
1101
|
+
return [
|
|
1102
|
+
...lines,
|
|
1103
|
+
...inspectGroup("Transcript", transcript, inspectTranscriptRow),
|
|
1104
|
+
...inspectGroup("Tools", tools, (value) => inspectToolRow("Tool", value)),
|
|
1105
|
+
...inspectGroup("Live tools", liveTools, (value) => inspectToolRow("Live tool", value)),
|
|
1106
|
+
...inspectGroup("Permissions", permissions, inspectPermissionRow)
|
|
1107
|
+
];
|
|
1108
|
+
}
|
|
1109
|
+
function shutdownLines(details, args, expanded) {
|
|
1110
|
+
const result = asRecord(details);
|
|
1111
|
+
if (!result || typeof result.ok !== "boolean" || !Array.isArray(result.cleaned_up)) return void 0;
|
|
1112
|
+
const paths = result.cleaned_up.filter((path) => typeof path === "string");
|
|
1113
|
+
const supervisor = stringValue(args.supervisor_id) ? scalar(args.supervisor_id) : "singleton";
|
|
1114
|
+
const lines = [
|
|
1115
|
+
`Avenor shutdown: ${result.ok ? "succeeded" : "failed"}.`,
|
|
1116
|
+
`Supervisor: ${supervisor}`,
|
|
1117
|
+
`Mode: ${args.force === true ? "force" : "graceful"}`,
|
|
1118
|
+
paths.length > 0 ? `Cleanup: ${paths.length} paths` : "Cleanup: none",
|
|
1119
|
+
...!result.ok ? ["Warning: shutdown did not complete.", "Guidance: retry avenor_shutdown."] : []
|
|
1120
|
+
];
|
|
1121
|
+
if (!expanded) return lines;
|
|
1122
|
+
return [
|
|
1123
|
+
...lines,
|
|
1124
|
+
...paths.slice(0, CLEANUP_ITEMS).map((path) => `Cleanup path: ${scalar(path)}`),
|
|
1125
|
+
...paths.length > CLEANUP_ITEMS ? [itemMarker(paths.length - CLEANUP_ITEMS)] : []
|
|
1126
|
+
];
|
|
1127
|
+
}
|
|
1128
|
+
function render(tool, result, options, theme, build) {
|
|
1129
|
+
try {
|
|
1130
|
+
return linesText(options.isPartial ? partial(result) : build() ?? fallback(tool), theme);
|
|
1131
|
+
} catch {
|
|
1132
|
+
return new Text(fallback(tool).join("\n"), 0, 0);
|
|
1133
|
+
}
|
|
1134
|
+
}
|
|
1135
|
+
function renderStatusCall(args, theme) {
|
|
1136
|
+
const pieces = stringValue(args.run_id) ? [`avenor_status run_id ${quoted(args.run_id)}`] : ["avenor_status (all runs)"];
|
|
1137
|
+
const view = optionalQuoted(args.view);
|
|
1138
|
+
const supervisor = optionalQuoted(args.supervisor_id);
|
|
1139
|
+
if (view) pieces.push(`view ${view}`);
|
|
1140
|
+
if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
|
|
1141
|
+
return linesText([pieces.join(" ")], theme);
|
|
1142
|
+
}
|
|
1143
|
+
function renderResultCall(args, theme) {
|
|
1144
|
+
const pieces = [`avenor_result run_id ${quoted(args.run_id)}`];
|
|
1145
|
+
if (args.wait !== void 0) pieces.push(`wait=${args.wait}`);
|
|
1146
|
+
const timeout = optionalQuoted(args.timeout);
|
|
1147
|
+
const supervisor = optionalQuoted(args.supervisor_id);
|
|
1148
|
+
if (timeout) pieces.push(`timeout ${timeout}`);
|
|
1149
|
+
if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
|
|
1150
|
+
return linesText([pieces.join(" ")], theme);
|
|
1151
|
+
}
|
|
1152
|
+
function renderInspectCall(args, theme) {
|
|
1153
|
+
const pieces = [`avenor_inspect run_id ${quoted(args.run_id)}`];
|
|
1154
|
+
const limit = optionalNumber(args.limit);
|
|
1155
|
+
const afterSequence = optionalNumber(args.after_seq);
|
|
1156
|
+
const supervisor = optionalQuoted(args.supervisor_id);
|
|
1157
|
+
if (limit) pieces.push(`limit=${limit}`);
|
|
1158
|
+
if (afterSequence) pieces.push(`after_seq=${afterSequence}`);
|
|
1159
|
+
if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
|
|
1160
|
+
return linesText([pieces.join(" ")], theme);
|
|
1161
|
+
}
|
|
1162
|
+
function renderAnswerPermissionCall(args, theme) {
|
|
1163
|
+
const pieces = [`avenor_answer_permission run_id ${quoted(args.run_id)}`, `option_id ${quoted(args.option_id)}`];
|
|
1164
|
+
const request = optionalQuoted(args.request_id);
|
|
1165
|
+
const message = optionalQuoted(args.message);
|
|
1166
|
+
const supervisor = optionalQuoted(args.supervisor_id);
|
|
1167
|
+
if (request) pieces.push(`request_id ${request}`);
|
|
1168
|
+
if (message) pieces.push(`message ${message}`);
|
|
1169
|
+
if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
|
|
1170
|
+
return linesText([pieces.join(" ")], theme);
|
|
1171
|
+
}
|
|
1172
|
+
function renderFollowUpCall(args, theme) {
|
|
1173
|
+
const pieces = [`avenor_follow_up run_id ${quoted(args.run_id)}`, `message ${quoted(args.message)}`];
|
|
1174
|
+
const label = optionalQuoted(args.label);
|
|
1175
|
+
const supervisor = optionalQuoted(args.supervisor_id);
|
|
1176
|
+
if (label) pieces.push(`label ${label}`);
|
|
1177
|
+
if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
|
|
1178
|
+
return linesText([pieces.join(" ")], theme);
|
|
1179
|
+
}
|
|
1180
|
+
function renderEventsCall(args, theme) {
|
|
1181
|
+
const pieces = [`avenor_events run_id ${quoted(args.run_id)}`];
|
|
1182
|
+
if (Array.isArray(args.types)) {
|
|
1183
|
+
const shown = args.types.slice(0, COLLECTION_ITEMS).map(quoted).join(", ");
|
|
1184
|
+
pieces.push(`types=[${shown}]${args.types.length > COLLECTION_ITEMS ? ` ${itemMarker(args.types.length - COLLECTION_ITEMS)}` : ""}`);
|
|
1185
|
+
}
|
|
1186
|
+
const limit = optionalNumber(args.limit);
|
|
1187
|
+
const supervisor = optionalQuoted(args.supervisor_id);
|
|
1188
|
+
if (limit) pieces.push(`limit=${limit}`);
|
|
1189
|
+
if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
|
|
1190
|
+
return linesText([pieces.join(" ")], theme);
|
|
1191
|
+
}
|
|
1192
|
+
function renderShutdownCall(args, theme) {
|
|
1193
|
+
const pieces = ["avenor_shutdown"];
|
|
1194
|
+
const supervisor = optionalQuoted(args.supervisor_id);
|
|
1195
|
+
if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
|
|
1196
|
+
if (args.force !== void 0) pieces.push(`force=${args.force}`);
|
|
1197
|
+
return linesText([pieces.join(" ")], theme);
|
|
1198
|
+
}
|
|
1199
|
+
function renderStatusResult(result, options, theme, args) {
|
|
1200
|
+
return render("status", result, options, theme, () => statusLines(result.details, args, options.expanded));
|
|
1201
|
+
}
|
|
1202
|
+
function renderResultResult(result, options, theme, args) {
|
|
1203
|
+
return render("result", result, options, theme, () => resultLines(result.details, args, options.expanded));
|
|
1204
|
+
}
|
|
1205
|
+
function renderInspectResult(result, options, theme, args) {
|
|
1206
|
+
return render("inspect", result, options, theme, () => inspectLines(result.details, args, options.expanded));
|
|
1207
|
+
}
|
|
1208
|
+
function renderAnswerPermissionResult(result, options, theme, args) {
|
|
1209
|
+
return render("answer_permission", result, options, theme, () => permissionLines(result.details, args));
|
|
1210
|
+
}
|
|
1211
|
+
function renderFollowUpResult(result, options, theme, args) {
|
|
1212
|
+
return render("follow_up", result, options, theme, () => followUpLines(result.details, args));
|
|
1213
|
+
}
|
|
1214
|
+
function renderEventsResult(result, options, theme, args) {
|
|
1215
|
+
return render("events", result, options, theme, () => eventsLines(result.details, args, options.expanded));
|
|
1216
|
+
}
|
|
1217
|
+
function renderShutdownResult(result, options, theme, args) {
|
|
1218
|
+
return render("shutdown", result, options, theme, () => shutdownLines(result.details, args, options.expanded));
|
|
1219
|
+
}
|
|
1220
|
+
//#endregion
|
|
705
1221
|
//#region src/index.ts
|
|
706
1222
|
const POLL_INTERVAL_MS = 3e3;
|
|
707
1223
|
const INSPECT_LIMIT = 128;
|
|
@@ -837,11 +1353,25 @@ function toWatchRunRef(run) {
|
|
|
837
1353
|
}
|
|
838
1354
|
function createExtension(deps = defaultDeps) {
|
|
839
1355
|
return async function(pi) {
|
|
1356
|
+
let telemetryGeneration = 0;
|
|
840
1357
|
const trackedRuns = /* @__PURE__ */ new Map();
|
|
841
1358
|
let pollingActive = false;
|
|
842
1359
|
let pollingGeneration = 0;
|
|
843
1360
|
let pollInFlight = null;
|
|
844
1361
|
let sessionCtx = null;
|
|
1362
|
+
const reportedPollingFailures = /* @__PURE__ */ new Set();
|
|
1363
|
+
function reportPollingFailure(key, message, error) {
|
|
1364
|
+
if (reportedPollingFailures.has(key)) return;
|
|
1365
|
+
reportedPollingFailures.add(key);
|
|
1366
|
+
console.error(message, error);
|
|
1367
|
+
}
|
|
1368
|
+
function clearPollingFailure(key) {
|
|
1369
|
+
reportedPollingFailures.delete(key);
|
|
1370
|
+
}
|
|
1371
|
+
function clearRunPollingFailures(run) {
|
|
1372
|
+
clearPollingFailure(`run-status:${run.supervisorId ?? "singleton"}:${run.runId}`);
|
|
1373
|
+
clearPollingFailure(`spawn-status:${run.supervisorId ?? "singleton"}:${run.runId}`);
|
|
1374
|
+
}
|
|
845
1375
|
function upsertTrackedRun(run, status, blocking = false) {
|
|
846
1376
|
const current = trackedRuns.get(run.runId);
|
|
847
1377
|
trackedRuns.set(run.runId, {
|
|
@@ -877,17 +1407,21 @@ function createExtension(deps = defaultDeps) {
|
|
|
877
1407
|
const allLive = await deps.statusTool({});
|
|
878
1408
|
const liveList = Array.isArray(allLive) ? allLive : [allLive];
|
|
879
1409
|
for (const result of liveList) liveMap.set(result.run_id, result);
|
|
1410
|
+
clearPollingFailure("singleton-list");
|
|
880
1411
|
} catch (err) {
|
|
881
|
-
|
|
1412
|
+
reportPollingFailure("singleton-list", "avenor pollRuns: failed to list singleton runs", err);
|
|
882
1413
|
}
|
|
883
1414
|
const entries = [];
|
|
884
1415
|
for (const run of trackedRuns.values()) {
|
|
885
1416
|
const live = findLiveStatusForTrackedRun(run, liveMap.values());
|
|
886
1417
|
if (live) {
|
|
887
1418
|
liveMap.delete(live.run_id);
|
|
1419
|
+
clearPollingFailure(`run-status:${run.supervisorId ?? "singleton"}:${run.runId}`);
|
|
888
1420
|
run.lastStatus = live;
|
|
889
1421
|
entries.push({
|
|
890
1422
|
runId: run.runId,
|
|
1423
|
+
supervisorId: run.supervisorId,
|
|
1424
|
+
runtimeId: live.runtime_id ?? run.runtimeId,
|
|
891
1425
|
label: run.label,
|
|
892
1426
|
status: live.status,
|
|
893
1427
|
phase: live.phase,
|
|
@@ -907,9 +1441,12 @@ function createExtension(deps = defaultDeps) {
|
|
|
907
1441
|
});
|
|
908
1442
|
const result = Array.isArray(raw) ? raw[0] : raw;
|
|
909
1443
|
if (!result) throw new Error("status response was empty");
|
|
1444
|
+
clearPollingFailure(`run-status:${run.supervisorId ?? "singleton"}:${run.runId}`);
|
|
910
1445
|
run.lastStatus = result;
|
|
911
1446
|
entries.push({
|
|
912
1447
|
runId: run.runId,
|
|
1448
|
+
supervisorId: run.supervisorId,
|
|
1449
|
+
runtimeId: result.runtime_id ?? run.runtimeId,
|
|
913
1450
|
label: run.label,
|
|
914
1451
|
status: result.status,
|
|
915
1452
|
phase: result.phase,
|
|
@@ -921,10 +1458,12 @@ function createExtension(deps = defaultDeps) {
|
|
|
921
1458
|
backend: result.backend
|
|
922
1459
|
});
|
|
923
1460
|
} catch (err) {
|
|
924
|
-
|
|
1461
|
+
reportPollingFailure(`run-status:${run.supervisorId ?? "singleton"}:${run.runId}`, `avenor pollRuns: statusTool failed for ${run.runId}`, err);
|
|
925
1462
|
const previous = run.lastStatus;
|
|
926
1463
|
entries.push({
|
|
927
1464
|
runId: run.runId,
|
|
1465
|
+
supervisorId: run.supervisorId,
|
|
1466
|
+
runtimeId: previous?.runtime_id ?? run.runtimeId,
|
|
928
1467
|
label: run.label,
|
|
929
1468
|
status: previous?.status ?? "unavailable",
|
|
930
1469
|
phase: previous?.phase,
|
|
@@ -939,6 +1478,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
939
1478
|
}
|
|
940
1479
|
for (const live of liveMap.values()) entries.push({
|
|
941
1480
|
runId: live.run_id,
|
|
1481
|
+
runtimeId: live.runtime_id,
|
|
942
1482
|
label: live.label,
|
|
943
1483
|
status: live.status,
|
|
944
1484
|
phase: live.phase,
|
|
@@ -987,6 +1527,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
987
1527
|
});
|
|
988
1528
|
if (!pollingActive || generation !== pollingGeneration) return;
|
|
989
1529
|
updateWidget(entries);
|
|
1530
|
+
emitAvenorEvent(pi.events, CHANNEL_POLL_COMPLETED, createPollCompletedPayload(entries, Date.now(), ++telemetryGeneration));
|
|
990
1531
|
for (const entry of entries) {
|
|
991
1532
|
const run = trackedRuns.get(entry.runId);
|
|
992
1533
|
if (!run) continue;
|
|
@@ -1009,6 +1550,17 @@ function createExtension(deps = defaultDeps) {
|
|
|
1009
1550
|
} catch (err) {
|
|
1010
1551
|
console.error("avenor tick: sendUserMessage failed", err);
|
|
1011
1552
|
}
|
|
1553
|
+
emitAvenorEvent(pi.events, CHANNEL_RUN_TERMINAL, createRunTerminalPayload({
|
|
1554
|
+
runId: entry.runId,
|
|
1555
|
+
supervisorId: entry.supervisorId,
|
|
1556
|
+
runtimeId: entry.runtimeId,
|
|
1557
|
+
label: entry.label,
|
|
1558
|
+
agent: entry.agent,
|
|
1559
|
+
status: entry.status,
|
|
1560
|
+
nestedCount: entry.nestedCount,
|
|
1561
|
+
backend: entry.backend
|
|
1562
|
+
}));
|
|
1563
|
+
clearRunPollingFailures(run);
|
|
1012
1564
|
trackedRuns.delete(entry.runId);
|
|
1013
1565
|
} else if (entry.pendingPermission && !run.blocking && !run.permissionNotified) {
|
|
1014
1566
|
run.permissionNotified = true;
|
|
@@ -1031,6 +1583,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1031
1583
|
}, POLL_INTERVAL_MS);
|
|
1032
1584
|
else {
|
|
1033
1585
|
pollingActive = false;
|
|
1586
|
+
reportedPollingFailures.clear();
|
|
1034
1587
|
sessionCtx?.ui.setStatus("avenor", "");
|
|
1035
1588
|
sessionCtx?.ui.setWidget("avenor-status", void 0);
|
|
1036
1589
|
}
|
|
@@ -1043,6 +1596,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1043
1596
|
async function stopPolling() {
|
|
1044
1597
|
pollingActive = false;
|
|
1045
1598
|
pollingGeneration++;
|
|
1599
|
+
reportedPollingFailures.clear();
|
|
1046
1600
|
await pollInFlight?.catch(() => {});
|
|
1047
1601
|
sessionCtx?.ui.setStatus("avenor", "");
|
|
1048
1602
|
sessionCtx?.ui.setWidget("avenor-status", void 0);
|
|
@@ -1215,6 +1769,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1215
1769
|
const status = Array.isArray(raw) ? raw[0] : raw;
|
|
1216
1770
|
if (!status) continue;
|
|
1217
1771
|
const text = status.pending_permission ? `[blocked] ${run.label ?? run.runId} — permission: ${status.pending_permission.description}` : `${run.label ?? run.runId}${status.phase_label ? ` (${status.phase_label})` : ""}`;
|
|
1772
|
+
clearPollingFailure(`spawn-status:${run.supervisorId ?? "singleton"}:${run.runId}`);
|
|
1218
1773
|
onUpdate?.({
|
|
1219
1774
|
content: [{
|
|
1220
1775
|
type: "text",
|
|
@@ -1230,7 +1785,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1230
1785
|
aborted: false
|
|
1231
1786
|
};
|
|
1232
1787
|
} catch (err) {
|
|
1233
|
-
|
|
1788
|
+
reportPollingFailure(`spawn-status:${run.supervisorId ?? "singleton"}:${run.runId}`, "avenor spawn poll: statusTool failed", err);
|
|
1234
1789
|
}
|
|
1235
1790
|
}
|
|
1236
1791
|
return { aborted: true };
|
|
@@ -1273,7 +1828,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1273
1828
|
pi.registerTool({
|
|
1274
1829
|
name: "avenor_spawn",
|
|
1275
1830
|
label: "Avenor Spawn",
|
|
1276
|
-
description: "Dispatch an agent run via avenor. Blocks by default, showing live progress. Set wait=false for fire-and-forget.",
|
|
1831
|
+
description: "Dispatch an agent run via avenor. Optional thinking accepts off, minimal, low, medium, high, xhigh, or max; unsupported backends reject explicit values. Blocks by default, showing live progress. Set wait=false for fire-and-forget.",
|
|
1277
1832
|
promptSnippet: "Dispatch sub-agent runs via avenor (spawn, status, result, inspect, events, follow-up, shutdown)",
|
|
1278
1833
|
promptGuidelines: [
|
|
1279
1834
|
"Use avenor_spawn to delegate well-defined tasks to sub-agents.",
|
|
@@ -1292,6 +1847,15 @@ function createExtension(deps = defaultDeps) {
|
|
|
1292
1847
|
label: Type.Optional(Type.String({ description: "Human-readable label for the run" })),
|
|
1293
1848
|
timeout: Type.Optional(Type.String({ description: "Timeout duration (e.g. 3600s)" })),
|
|
1294
1849
|
model: Type.Optional(Type.String({ description: "Model override" })),
|
|
1850
|
+
thinking: Type.Optional(Type.Union([
|
|
1851
|
+
Type.Literal("off"),
|
|
1852
|
+
Type.Literal("minimal"),
|
|
1853
|
+
Type.Literal("low"),
|
|
1854
|
+
Type.Literal("medium"),
|
|
1855
|
+
Type.Literal("high"),
|
|
1856
|
+
Type.Literal("xhigh"),
|
|
1857
|
+
Type.Literal("max")
|
|
1858
|
+
], { description: "Thinking level; omission uses the backend default" })),
|
|
1295
1859
|
backend: Type.Optional(Type.String({ description: "Backend override (defaults to pi)" })),
|
|
1296
1860
|
server_url: Type.Optional(Type.String({ description: "Backend server URL" })),
|
|
1297
1861
|
supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" })),
|
|
@@ -1316,6 +1880,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1316
1880
|
dir,
|
|
1317
1881
|
timeout: params.timeout,
|
|
1318
1882
|
model: params.model,
|
|
1883
|
+
thinking: params.thinking,
|
|
1319
1884
|
backend,
|
|
1320
1885
|
agentProfile,
|
|
1321
1886
|
serverUrl: params.server_url,
|
|
@@ -1353,6 +1918,7 @@ function createExtension(deps = defaultDeps) {
|
|
|
1353
1918
|
});
|
|
1354
1919
|
const waitResult = await waitForRun(runRef, signal, onUpdate);
|
|
1355
1920
|
if (waitResult.aborted) {
|
|
1921
|
+
clearRunPollingFailures(runRef);
|
|
1356
1922
|
trackedRuns.delete(result.run_id);
|
|
1357
1923
|
return {
|
|
1358
1924
|
content: [{
|
|
@@ -1399,6 +1965,16 @@ function createExtension(deps = defaultDeps) {
|
|
|
1399
1965
|
runId: result.run_id,
|
|
1400
1966
|
supervisorId
|
|
1401
1967
|
}, status);
|
|
1968
|
+
emitAvenorEvent(pi.events, CHANNEL_RUN_TERMINAL, createRunTerminalPayload({
|
|
1969
|
+
runId: result.run_id,
|
|
1970
|
+
supervisorId,
|
|
1971
|
+
runtimeId: status.runtime_id ?? runRef.runtimeId,
|
|
1972
|
+
label,
|
|
1973
|
+
agent: params.agent,
|
|
1974
|
+
status: status.status,
|
|
1975
|
+
backend: status.backend
|
|
1976
|
+
}));
|
|
1977
|
+
clearRunPollingFailures(runRef);
|
|
1402
1978
|
trackedRuns.delete(result.run_id);
|
|
1403
1979
|
return {
|
|
1404
1980
|
content: [{
|
|
@@ -1474,6 +2050,15 @@ function createExtension(deps = defaultDeps) {
|
|
|
1474
2050
|
}],
|
|
1475
2051
|
details: result
|
|
1476
2052
|
};
|
|
2053
|
+
},
|
|
2054
|
+
renderCall(args, theme, context) {
|
|
2055
|
+
return renderStatusCall(context.args, theme);
|
|
2056
|
+
},
|
|
2057
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
2058
|
+
return renderStatusResult(result, {
|
|
2059
|
+
expanded,
|
|
2060
|
+
isPartial
|
|
2061
|
+
}, theme, context.args);
|
|
1477
2062
|
}
|
|
1478
2063
|
});
|
|
1479
2064
|
pi.registerTool({
|
|
@@ -1500,8 +2085,21 @@ function createExtension(deps = defaultDeps) {
|
|
|
1500
2085
|
signal
|
|
1501
2086
|
});
|
|
1502
2087
|
completed = result.ready;
|
|
1503
|
-
if (completed)
|
|
1504
|
-
|
|
2088
|
+
if (completed) {
|
|
2089
|
+
emitAvenorEvent(pi.events, CHANNEL_RUN_TERMINAL, createRunTerminalPayload({
|
|
2090
|
+
runId: params.run_id,
|
|
2091
|
+
supervisorId: tracked?.supervisorId ?? params.supervisor_id,
|
|
2092
|
+
runtimeId: tracked?.runtimeId ?? result.runtime_id,
|
|
2093
|
+
label: tracked?.label ?? result.label ?? params.run_id,
|
|
2094
|
+
agent: tracked?.agent ?? "unknown",
|
|
2095
|
+
status: result.status ?? "done"
|
|
2096
|
+
}));
|
|
2097
|
+
clearRunPollingFailures({
|
|
2098
|
+
runId: params.run_id,
|
|
2099
|
+
supervisorId: tracked?.supervisorId ?? params.supervisor_id
|
|
2100
|
+
});
|
|
2101
|
+
trackedRuns.delete(params.run_id);
|
|
2102
|
+
} else if (tracked && result.status === "waiting") tracked.permissionNotified = true;
|
|
1505
2103
|
return {
|
|
1506
2104
|
content: [{
|
|
1507
2105
|
type: "text",
|
|
@@ -1512,6 +2110,15 @@ function createExtension(deps = defaultDeps) {
|
|
|
1512
2110
|
} finally {
|
|
1513
2111
|
if (!completed && tracked && trackedRuns.get(params.run_id) === tracked) tracked.blocking = previousBlocking ?? false;
|
|
1514
2112
|
}
|
|
2113
|
+
},
|
|
2114
|
+
renderCall(args, theme, context) {
|
|
2115
|
+
return renderResultCall(context.args, theme);
|
|
2116
|
+
},
|
|
2117
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
2118
|
+
return renderResultResult(result, {
|
|
2119
|
+
expanded,
|
|
2120
|
+
isPartial
|
|
2121
|
+
}, theme, context.args);
|
|
1515
2122
|
}
|
|
1516
2123
|
});
|
|
1517
2124
|
pi.registerTool({
|
|
@@ -1538,6 +2145,15 @@ function createExtension(deps = defaultDeps) {
|
|
|
1538
2145
|
}],
|
|
1539
2146
|
details: payload
|
|
1540
2147
|
};
|
|
2148
|
+
},
|
|
2149
|
+
renderCall(args, theme, context) {
|
|
2150
|
+
return renderInspectCall(context.args, theme);
|
|
2151
|
+
},
|
|
2152
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
2153
|
+
return renderInspectResult(result, {
|
|
2154
|
+
expanded,
|
|
2155
|
+
isPartial
|
|
2156
|
+
}, theme, context.args);
|
|
1541
2157
|
}
|
|
1542
2158
|
});
|
|
1543
2159
|
pi.registerTool({
|
|
@@ -1566,6 +2182,15 @@ function createExtension(deps = defaultDeps) {
|
|
|
1566
2182
|
}],
|
|
1567
2183
|
details: result
|
|
1568
2184
|
};
|
|
2185
|
+
},
|
|
2186
|
+
renderCall(args, theme, context) {
|
|
2187
|
+
return renderAnswerPermissionCall(context.args, theme);
|
|
2188
|
+
},
|
|
2189
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
2190
|
+
return renderAnswerPermissionResult(result, {
|
|
2191
|
+
expanded,
|
|
2192
|
+
isPartial
|
|
2193
|
+
}, theme, context.args);
|
|
1569
2194
|
}
|
|
1570
2195
|
});
|
|
1571
2196
|
pi.registerTool({
|
|
@@ -1613,6 +2238,15 @@ function createExtension(deps = defaultDeps) {
|
|
|
1613
2238
|
}],
|
|
1614
2239
|
details: result
|
|
1615
2240
|
};
|
|
2241
|
+
},
|
|
2242
|
+
renderCall(args, theme, context) {
|
|
2243
|
+
return renderFollowUpCall(context.args, theme);
|
|
2244
|
+
},
|
|
2245
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
2246
|
+
return renderFollowUpResult(result, {
|
|
2247
|
+
expanded,
|
|
2248
|
+
isPartial
|
|
2249
|
+
}, theme, context.args);
|
|
1616
2250
|
}
|
|
1617
2251
|
});
|
|
1618
2252
|
pi.registerTool({
|
|
@@ -1639,6 +2273,15 @@ function createExtension(deps = defaultDeps) {
|
|
|
1639
2273
|
}],
|
|
1640
2274
|
details: result
|
|
1641
2275
|
};
|
|
2276
|
+
},
|
|
2277
|
+
renderCall(args, theme, context) {
|
|
2278
|
+
return renderEventsCall(context.args, theme);
|
|
2279
|
+
},
|
|
2280
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
2281
|
+
return renderEventsResult(result, {
|
|
2282
|
+
expanded,
|
|
2283
|
+
isPartial
|
|
2284
|
+
}, theme, context.args);
|
|
1642
2285
|
}
|
|
1643
2286
|
});
|
|
1644
2287
|
pi.registerTool({
|
|
@@ -1663,6 +2306,15 @@ function createExtension(deps = defaultDeps) {
|
|
|
1663
2306
|
}],
|
|
1664
2307
|
details: result
|
|
1665
2308
|
};
|
|
2309
|
+
},
|
|
2310
|
+
renderCall(args, theme, context) {
|
|
2311
|
+
return renderShutdownCall(context.args, theme);
|
|
2312
|
+
},
|
|
2313
|
+
renderResult(result, { expanded, isPartial }, theme, context) {
|
|
2314
|
+
return renderShutdownResult(result, {
|
|
2315
|
+
expanded,
|
|
2316
|
+
isPartial
|
|
2317
|
+
}, theme, context.args);
|
|
1666
2318
|
}
|
|
1667
2319
|
});
|
|
1668
2320
|
pi.registerCommand("avenor-status", {
|
|
@@ -1740,4 +2392,4 @@ function createExtension(deps = defaultDeps) {
|
|
|
1740
2392
|
}
|
|
1741
2393
|
var src_default = createExtension();
|
|
1742
2394
|
//#endregion
|
|
1743
|
-
export { buildCompletionText, buildInspectPayload, compactWhitespace, createExtension, src_default as default, isTerminalStatus, renderStatusLines, statusSupervisorId };
|
|
2395
|
+
export { CHANNEL_POLL_COMPLETED, CHANNEL_RUN_TERMINAL, buildCompletionText, buildInspectPayload, compactWhitespace, createExtension, createPollCompletedPayload, createRunTerminalPayload, src_default as default, emitAvenorEvent, isTerminalStatus, onAvenorEvent, renderStatusLines, statusSupervisorId };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dougbots/avenor-pi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package"
|
|
@@ -35,10 +35,10 @@
|
|
|
35
35
|
"dev": "tsdown --watch"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"@dougbots/avenor-core": "^0.
|
|
38
|
+
"@dougbots/avenor-core": "^0.9.0"
|
|
39
39
|
},
|
|
40
40
|
"peerDependencies": {
|
|
41
|
-
"@earendil-works/pi-coding-agent": "
|
|
41
|
+
"@earendil-works/pi-coding-agent": ">=0.74.0",
|
|
42
42
|
"@earendil-works/pi-tui": "*"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|