@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 CHANGED
@@ -86,8 +86,9 @@ packages/pi/
86
86
  ├── package.json
87
87
  ├── tsdown.config.ts
88
88
  ├── src/
89
- │ ├── index.ts # main extension (tools, commands, hooks, rendering)
90
- │ ├── types.ts # shared types, status emoji mapping
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 are rendered with status emojis and color-coded output
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
- console.error("avenor pollRuns: failed to list singleton runs", err);
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
- console.error(`avenor pollRuns: statusTool failed for ${run.runId}`, err);
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
- console.error("avenor spawn poll: statusTool failed", err);
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) trackedRuns.delete(params.run_id);
1504
- else if (tracked && result.status === "waiting") tracked.permissionNotified = true;
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.8.3",
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.8.3"
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": {