@dougbots/avenor-pi 0.8.3 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.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, validateSpawnSelection } 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,542 @@ 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
+ /** Emitted when a best-effort polling operation fails. */
722
+ const CHANNEL_POLL_ERROR = "avenor:poll:error";
723
+ /** Maximum sanitized error text exposed to companion extensions. */
724
+ const POLL_ERROR_TEXT_CHARS = 600;
725
+ function sanitizeAndBoundText(value) {
726
+ const text = sanitizeText(String(value)).trim();
727
+ if (!text) return "unknown error";
728
+ return text.length <= POLL_ERROR_TEXT_CHARS ? text : `${text.slice(0, POLL_ERROR_TEXT_CHARS - 1)}…`;
729
+ }
730
+ function createSupervisorKey(supervisorId) {
731
+ if (!supervisorId) return "singleton";
732
+ return `supervisor:${createHash("sha256").update(supervisorId).digest("hex").slice(0, 16)}`;
733
+ }
734
+ /** Emit an Avenor event with a type-checked payload. */
735
+ function emitAvenorEvent(bus, event, data) {
736
+ bus.emit(event, data);
737
+ }
738
+ /** Subscribe to an Avenor event and return Pi's unsubscribe function. */
739
+ function onAvenorEvent(bus, event, handler) {
740
+ return bus.on(event, handler);
741
+ }
742
+ /**
743
+ * Copy and freeze the selected fields exposed to companion consumers.
744
+ * Keep this allowlist explicit so future private RunStatusEntry fields are not
745
+ * included in the event accidentally.
746
+ */
747
+ function createPollCompletedPayload(entries, timestamp, generation) {
748
+ const publicEntries = entries.map((entry) => Object.freeze({
749
+ runId: entry.runId,
750
+ supervisorKey: createSupervisorKey(entry.supervisorId),
751
+ ...entry.runtimeId !== void 0 && { runtimeId: entry.runtimeId },
752
+ label: entry.label,
753
+ status: entry.status,
754
+ ...entry.phase !== void 0 && { phase: entry.phase },
755
+ ...entry.phaseLabel !== void 0 && { phaseLabel: entry.phaseLabel },
756
+ agent: entry.agent,
757
+ ...entry.pendingPermission !== void 0 && { pendingPermission: entry.pendingPermission },
758
+ ...entry.backend !== void 0 && { backend: entry.backend },
759
+ ...entry.nestedCount !== void 0 && { nestedCount: entry.nestedCount }
760
+ }));
761
+ return Object.freeze({
762
+ entries: Object.freeze(publicEntries),
763
+ timestamp,
764
+ generation
765
+ });
766
+ }
767
+ /** Copy and freeze a terminal payload before publishing it. */
768
+ function createRunTerminalPayload(payload) {
769
+ return Object.freeze({
770
+ runId: payload.runId,
771
+ supervisorKey: createSupervisorKey(payload.supervisorId),
772
+ ...payload.runtimeId !== void 0 && { runtimeId: payload.runtimeId },
773
+ label: payload.label,
774
+ agent: payload.agent,
775
+ status: payload.status,
776
+ ...payload.nestedCount !== void 0 && { nestedCount: payload.nestedCount },
777
+ ...payload.backend !== void 0 && { backend: payload.backend }
778
+ });
779
+ }
780
+ /** Copy, bound, sanitize, and freeze a polling error before publishing it. */
781
+ function createPollErrorPayload(payload) {
782
+ return Object.freeze({
783
+ source: payload.source,
784
+ ...payload.runId !== void 0 && { runId: payload.runId },
785
+ message: sanitizeAndBoundText(payload.message),
786
+ error: sanitizeAndBoundText(payload.error),
787
+ count: payload.count,
788
+ timestamp: payload.timestamp
789
+ });
790
+ }
791
+ //#endregion
792
+ //#region src/render.ts
793
+ const FINAL_OUTPUT_CHARS = 600;
794
+ const SCALAR_CHARS = 240;
795
+ const EVENT_CHARS = 600;
796
+ const PREVIEW_LINES = 12;
797
+ const STATUS_ROWS = 12;
798
+ const COLLECTION_ITEMS = 8;
799
+ const INSPECT_ITEMS = 5;
800
+ const CLEANUP_ITEMS = 8;
801
+ function asRecord(value) {
802
+ return value && typeof value === "object" && !Array.isArray(value) ? value : void 0;
803
+ }
804
+ function stringValue(value) {
805
+ return typeof value === "string" && value.trim() ? value : void 0;
806
+ }
807
+ function numberValue(value) {
808
+ if (typeof value === "number" && Number.isFinite(value)) return value;
809
+ if (typeof value === "string" && value.trim() && Number.isFinite(Number(value))) return Number(value);
810
+ }
811
+ function itemMarker(count) {
812
+ return `… ${count} items omitted`;
813
+ }
814
+ function lineMarker(count) {
815
+ return `… ${count} lines omitted`;
816
+ }
817
+ function characterMarker(count) {
818
+ return `[preview clipped: ${count} characters omitted]`;
819
+ }
820
+ function cleanMultiline(value) {
821
+ if (typeof value !== "string") return void 0;
822
+ return sanitizeText(value).trim() || void 0;
823
+ }
824
+ function leadingClip(value, limit) {
825
+ if (value.length <= limit) return { text: value };
826
+ let prefixLength = Math.max(0, limit - characterMarker(value.length).length);
827
+ for (let attempt = 0; attempt < 3; attempt++) {
828
+ const omitted = value.length - prefixLength;
829
+ const nextPrefixLength = Math.max(0, limit - characterMarker(omitted).length);
830
+ if (nextPrefixLength === prefixLength) break;
831
+ prefixLength = nextPrefixLength;
832
+ }
833
+ const clipped = clipText(value, Math.max(1, prefixLength + 1)) ?? "";
834
+ const text = clipped.endsWith("…") ? clipped.slice(0, -1) : clipped.slice(0, prefixLength);
835
+ return {
836
+ text,
837
+ omitted: value.length - text.length
838
+ };
839
+ }
840
+ function scalar(value, fallback = "unknown", limit = SCALAR_CHARS) {
841
+ const clean = cleanMultiline(typeof value === "string" ? value : void 0);
842
+ if (!clean) return fallback;
843
+ const clipped = leadingClip(clean.replace(/[\r\n]+/g, " ").replace(/\s+/g, " ").trim(), limit);
844
+ return clipped.omitted === void 0 ? clipped.text : `${clipped.text}${characterMarker(clipped.omitted)}`;
845
+ }
846
+ function optionalScalar(value, limit = SCALAR_CHARS) {
847
+ const clean = cleanMultiline(typeof value === "string" ? value : void 0);
848
+ return clean ? scalar(clean, "unknown", limit) : void 0;
849
+ }
850
+ function preview(value, limit) {
851
+ const clean = cleanMultiline(value);
852
+ if (!clean) return void 0;
853
+ const clipped = leadingClip(clean, limit);
854
+ const lines = clipped.text.split("\n").slice(0, PREVIEW_LINES);
855
+ const omittedLines = Math.max(0, clean.split("\n").length - lines.length);
856
+ return [
857
+ ...lines,
858
+ ...clipped.omitted === void 0 ? [] : [characterMarker(clipped.omitted)],
859
+ ...omittedLines === 0 ? [] : [lineMarker(omittedLines)]
860
+ ];
861
+ }
862
+ function quoted(value) {
863
+ return `"${scalar(value).replaceAll("\\", "\\\\").replaceAll("\"", "\\\"")}"`;
864
+ }
865
+ function optionalQuoted(value) {
866
+ return stringValue(value) ? quoted(value) : void 0;
867
+ }
868
+ function optionalNumber(value) {
869
+ return numberValue(value) === void 0 ? void 0 : String(numberValue(value));
870
+ }
871
+ function selected(record, key, fallback, defaultValue = "unknown") {
872
+ return scalar(stringValue(record?.[key]) ?? stringValue(fallback), defaultValue);
873
+ }
874
+ function selectedOptional(record, key) {
875
+ return optionalScalar(record?.[key]);
876
+ }
877
+ function hasStatusIdentity(record) {
878
+ return !!record && !!(stringValue(record.run_id) || stringValue(record.label) || stringValue(record.status));
879
+ }
880
+ function linesText(lines, theme) {
881
+ return new Text(lines.map((line, index) => theme.fg(index === 0 ? "toolTitle" : "muted", index === 0 ? theme.bold(line) : line)).join("\n"), 0, 0);
882
+ }
883
+ function fallback(tool) {
884
+ return [`Avenor ${tool} result unavailable.`, "Retry the tool or use avenor_status/avenor_inspect."];
885
+ }
886
+ function partial(result) {
887
+ const details = asRecord(result.details);
888
+ const label = optionalScalar(details?.label);
889
+ const status = optionalScalar(details?.status);
890
+ if (label && status) return [`Working: ${label} — ${status}`];
891
+ if (status) return [`Working: ${status}`];
892
+ if (label) return [`Working: ${label}`];
893
+ const content = result.content?.[0]?.text;
894
+ const firstLine = typeof content === "string" ? content.split(/\r?\n/).find((line) => line.trim()) : void 0;
895
+ if (firstLine) {
896
+ const clean = scalar(firstLine, "", SCALAR_CHARS);
897
+ if (clean && !clean.startsWith("{") && !clean.startsWith("[")) return [`Working: ${clean}`];
898
+ }
899
+ return ["Working…"];
900
+ }
901
+ function isTerminal(status) {
902
+ return TERMINAL_STATUSES.has(status);
903
+ }
904
+ function permission(record) {
905
+ return asRecord(record?.pending_permission);
906
+ }
907
+ function permissionOptions(record) {
908
+ const options = record?.options;
909
+ return Array.isArray(options) ? options.map(asRecord).filter((value) => !!value) : [];
910
+ }
911
+ function optionRows(options) {
912
+ return options.slice(0, COLLECTION_ITEMS).map((option) => `${selected(option, "option_id")} (${selected(option, "label")})`).join(", ");
913
+ }
914
+ function optionLine(prefix, options) {
915
+ const rows = optionRows(options);
916
+ const omitted = options.length - Math.min(options.length, COLLECTION_ITEMS);
917
+ return `${prefix}${rows ? ` ${rows}` : ""}${omitted > 0 ? `${rows ? " " : " "}${itemMarker(omitted)}` : ""}`;
918
+ }
919
+ function guidance(status, pending, runId) {
920
+ if (status === "waiting") {
921
+ const options = permissionOptions(pending);
922
+ if (options.length > 0) return `Guidance: Call avenor_answer_permission with run_id "${runId}" and option_id "${selected(options[0], "option_id")}".`;
923
+ if (pending) return `Guidance: Permission input is required, but no options are available; call avenor_inspect with run_id "${runId}".`;
924
+ return `Guidance: Call avenor_follow_up or avenor_status with run_id "${runId}".`;
925
+ }
926
+ if (isTerminal(status)) return `Guidance: Call avenor_result or avenor_events with run_id "${runId}".`;
927
+ return `Guidance: Call avenor_result with run_id "${runId}".`;
928
+ }
929
+ function statusExpanded(status) {
930
+ return [
931
+ ["Phase", "phase"],
932
+ ["Phase label", "phase_label"],
933
+ ["Session", "session_id"],
934
+ ["Backend", "backend"],
935
+ ["Agent", "agent"],
936
+ ["Model", "model"],
937
+ ["Latest sequence", "latest_seq"],
938
+ ["Stop reason", "stop_reason"]
939
+ ].flatMap(([label, key]) => {
940
+ const value = key === "latest_seq" ? optionalNumber(status[key]) : selectedOptional(status, key);
941
+ return value === void 0 ? [] : [`${label}: ${value}`];
942
+ });
943
+ }
944
+ function statusLines(details, args, expanded) {
945
+ if (Array.isArray(details)) {
946
+ const statuses = details.map(asRecord);
947
+ if (statuses.some((status) => !hasStatusIdentity(status))) return void 0;
948
+ const displayed = statuses.slice(0, STATUS_ROWS);
949
+ const lines = [
950
+ `Avenor status — ${statuses.length} runs`,
951
+ ...displayed.map((status) => {
952
+ const runId = selected(status, "run_id", args.run_id);
953
+ return `${selected(status, "label", void 0, runId)} — ${selected(status, "status")} (run_id: ${runId})`;
954
+ }),
955
+ ...statuses.length > displayed.length ? [itemMarker(statuses.length - displayed.length)] : []
956
+ ];
957
+ if (!expanded) return lines;
958
+ for (const status of displayed) {
959
+ const runId = selected(status, "run_id", args.run_id);
960
+ lines.push(`Run: ${runId}`, `Label: ${selected(status, "label", void 0, runId)}`, `Status: ${selected(status, "status")}`, ...statusExpanded(status));
961
+ }
962
+ return lines;
963
+ }
964
+ const status = asRecord(details);
965
+ if (!hasStatusIdentity(status)) return void 0;
966
+ const runId = selected(status, "run_id", args.run_id);
967
+ const label = selected(status, "label", void 0, runId);
968
+ const state = selected(status, "status");
969
+ const pending = permission(status);
970
+ const options = permissionOptions(pending);
971
+ const finalPreview = preview(status.final_output, FINAL_OUTPUT_CHARS);
972
+ const lines = [
973
+ `Status: ${label} — ${state}`,
974
+ `Run: ${runId}`,
975
+ ...pending ? [`Permission: ${selected(pending, "description")}`, optionLine("Options:", options)] : [],
976
+ ...status.final_output_truncated === true ? [`Warning: final output preview is truncated; call avenor_result with run_id "${runId}".`] : [],
977
+ ...finalPreview ? ["Final output:", ...finalPreview] : ["Final output: unavailable."],
978
+ guidance(state, pending, runId)
979
+ ];
980
+ return expanded ? [...lines, ...statusExpanded(status)] : lines;
981
+ }
982
+ function resultLines(details, args, expanded) {
983
+ const result = asRecord(details);
984
+ if (!hasStatusIdentity(result)) return void 0;
985
+ const runId = selected(result, "run_id", args.run_id);
986
+ const label = selected(result, "label", void 0, runId);
987
+ const state = selected(result, "status");
988
+ const pending = permission(result);
989
+ const finalPreview = preview(result.output, FINAL_OUTPUT_CHARS);
990
+ const lines = [
991
+ `Result: ${label} — ${state}`,
992
+ `Run: ${runId}`,
993
+ ...result.timed_out === true ? ["Warning: wait timed out; the run is still active."] : [],
994
+ ...pending ? [`Permission: ${selected(pending, "description")}`] : [],
995
+ guidance(state, pending, runId),
996
+ ...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}".`] : [],
997
+ ...finalPreview ? ["Final output:", ...finalPreview] : ["Final output: unavailable."]
998
+ ];
999
+ if (!expanded) return lines;
1000
+ const session = selectedOptional(result, "session_id");
1001
+ const stopReason = selectedOptional(result, "stop_reason");
1002
+ return [
1003
+ ...lines,
1004
+ ...session ? [`Session: ${session}`] : [],
1005
+ ...stopReason ? [`Stop reason: ${stopReason}`] : []
1006
+ ];
1007
+ }
1008
+ function permissionLines(details, args) {
1009
+ const result = asRecord(details);
1010
+ if (!result || typeof result.ok !== "boolean") return void 0;
1011
+ const runId = scalar(args.run_id);
1012
+ return [
1013
+ `Permission answer: ${result.ok ? "accepted" : "failed"}.`,
1014
+ `Run: ${runId}`,
1015
+ `Option: ${scalar(args.option_id)}`,
1016
+ ...stringValue(args.request_id) ? [`Request: ${scalar(args.request_id)}`] : [],
1017
+ `Guidance: Call avenor_status with run_id "${runId}" to verify the permission state.`
1018
+ ];
1019
+ }
1020
+ function followUpLines(details, args) {
1021
+ const result = asRecord(details);
1022
+ if (!result || !stringValue(result.run_id) || !stringValue(result.label)) return void 0;
1023
+ const runId = selected(result, "run_id", args.run_id);
1024
+ const label = selected(result, "label", args.label, runId);
1025
+ return [
1026
+ `Follow-up: ${label} — dispatched.`,
1027
+ `Source run: ${scalar(args.run_id)}`,
1028
+ `Run: ${runId}`,
1029
+ `Label: ${label}`,
1030
+ `Guidance: Call avenor_status with run_id "${runId}".`
1031
+ ];
1032
+ }
1033
+ function eventSequence(value) {
1034
+ if (typeof value === "number" && Number.isFinite(value)) return String(value);
1035
+ return typeof value === "string" && value.trim() && Number.isFinite(Number(value)) ? value : void 0;
1036
+ }
1037
+ function eventRow(value) {
1038
+ const name = stringValue(value.event) ?? stringValue(value.type) ?? "unknown";
1039
+ const sequence = eventSequence(value.seq);
1040
+ const text = extractEventText(value);
1041
+ const previewText = text ? scalar(text, "unknown", EVENT_CHARS) : void 0;
1042
+ return `Event ${sequence ?? "?"}: ${scalar(name)}${previewText ? ` — ${previewText}` : ""}`;
1043
+ }
1044
+ function eventsLines(details, args, expanded) {
1045
+ const result = asRecord(details);
1046
+ if (!result || !Array.isArray(result.events) || result.events.some((event) => !asRecord(event))) return void 0;
1047
+ const events = result.events.map((event) => event);
1048
+ const runId = scalar(args.run_id);
1049
+ const types = Array.isArray(args.types) ? args.types : [];
1050
+ const displayedTypes = types.slice(0, COLLECTION_ITEMS).map((type) => scalar(type)).join(", ");
1051
+ const lines = [`Events: ${runId} — ${events.length} events`, ...types.length > 0 ? [`Filter: ${displayedTypes}${types.length > COLLECTION_ITEMS ? ` ${itemMarker(types.length - COLLECTION_ITEMS)}` : ""}`] : []];
1052
+ if (events.length === 0) return [...lines, "No events."];
1053
+ if (!expanded) {
1054
+ lines.push(`Preview: ${eventRow(events[0])}`);
1055
+ if (events.length > 1) lines.push(itemMarker(events.length - 1));
1056
+ return lines;
1057
+ }
1058
+ const displayed = events.slice(0, STATUS_ROWS);
1059
+ lines.push(...displayed.map(eventRow));
1060
+ if (events.length > displayed.length) lines.push(itemMarker(events.length - displayed.length));
1061
+ return lines;
1062
+ }
1063
+ function inspectTranscriptRow(value) {
1064
+ const kind = stringValue(value.kind);
1065
+ const text = optionalScalar(value.text);
1066
+ if (!kind) return void 0;
1067
+ if (kind === "tool") {
1068
+ const title = selected(value, "title", stringValue(value.kind) ?? stringValue(value.key));
1069
+ const status = optionalScalar(value.status);
1070
+ return `${title}${status ? ` (${status})` : ""}${text ? `: ${text}` : ""}`;
1071
+ }
1072
+ if (!text) return void 0;
1073
+ if ([
1074
+ "assistant",
1075
+ "thought",
1076
+ "user",
1077
+ "permission",
1078
+ "status",
1079
+ "session"
1080
+ ].includes(kind)) return `${kind}: ${text}`;
1081
+ }
1082
+ function inspectToolRow(prefix, value) {
1083
+ const title = selected(value, "title", stringValue(value.kind) ?? stringValue(value.key));
1084
+ const status = optionalScalar(value.status);
1085
+ const text = optionalScalar(value.preview);
1086
+ return `${prefix}: ${title}${status ? ` (${status})` : ""}${text ? `: ${text}` : ""}`;
1087
+ }
1088
+ function inspectPermissionRow(value) {
1089
+ const description = scalar(stringValue(value.description) ?? stringValue(value.question) ?? stringValue(value.tool) ?? stringValue(value.kind));
1090
+ const options = permissionOptions(value);
1091
+ const rows = optionRows(options);
1092
+ return `Permission: ${description} (request_id: ${selected(value, "request_id")}); options:${rows ? ` ${rows}` : ""}${options.length > COLLECTION_ITEMS ? ` ${itemMarker(options.length - COLLECTION_ITEMS)}` : ""}`;
1093
+ }
1094
+ function inspectGroup(title, values, row) {
1095
+ const rows = (Array.isArray(values) ? values.map(asRecord).filter((value) => !!value) : []).map(row).filter((value) => !!value);
1096
+ return [
1097
+ `${title}:`,
1098
+ ...rows.slice(0, INSPECT_ITEMS),
1099
+ ...rows.length > INSPECT_ITEMS ? [itemMarker(rows.length - INSPECT_ITEMS)] : []
1100
+ ];
1101
+ }
1102
+ function inspectLines(details, args, expanded) {
1103
+ const inspect = asRecord(details);
1104
+ const status = asRecord(inspect?.status);
1105
+ if (!inspect || !status || !hasStatusIdentity(inspect) && !hasStatusIdentity(status)) return void 0;
1106
+ const runId = selected(inspect, "run_id", args.run_id);
1107
+ const label = selected(inspect, "label", void 0, runId);
1108
+ const state = selected(status, "status");
1109
+ const finalPreview = preview(inspect.final_output, FINAL_OUTPUT_CHARS);
1110
+ const transcript = Array.isArray(inspect.transcript) ? inspect.transcript : [];
1111
+ const tools = Array.isArray(inspect.tools) ? inspect.tools : [];
1112
+ const liveTools = Array.isArray(inspect.live_tools) ? inspect.live_tools : [];
1113
+ const permissions = Array.isArray(inspect.permissions) ? inspect.permissions : [];
1114
+ const lines = [
1115
+ `Inspect: ${label} — ${state}`,
1116
+ `Run: ${runId}`,
1117
+ `Counts: transcript=${transcript.length}, tools=${tools.length}, live_tools=${liveTools.length}, permissions=${permissions.length}`,
1118
+ ...finalPreview ? ["Final output:", ...finalPreview] : ["Final output: unavailable."],
1119
+ `Guidance: Call avenor_events with run_id "${runId}" for raw events.`
1120
+ ];
1121
+ if (!expanded) return lines;
1122
+ return [
1123
+ ...lines,
1124
+ ...inspectGroup("Transcript", transcript, inspectTranscriptRow),
1125
+ ...inspectGroup("Tools", tools, (value) => inspectToolRow("Tool", value)),
1126
+ ...inspectGroup("Live tools", liveTools, (value) => inspectToolRow("Live tool", value)),
1127
+ ...inspectGroup("Permissions", permissions, inspectPermissionRow)
1128
+ ];
1129
+ }
1130
+ function shutdownLines(details, args, expanded) {
1131
+ const result = asRecord(details);
1132
+ if (!result || typeof result.ok !== "boolean" || !Array.isArray(result.cleaned_up)) return void 0;
1133
+ const paths = result.cleaned_up.filter((path) => typeof path === "string");
1134
+ const supervisor = stringValue(args.supervisor_id) ? scalar(args.supervisor_id) : "singleton";
1135
+ const lines = [
1136
+ `Avenor shutdown: ${result.ok ? "succeeded" : "failed"}.`,
1137
+ `Supervisor: ${supervisor}`,
1138
+ `Mode: ${args.force === true ? "force" : "graceful"}`,
1139
+ paths.length > 0 ? `Cleanup: ${paths.length} paths` : "Cleanup: none",
1140
+ ...!result.ok ? ["Warning: shutdown did not complete.", "Guidance: retry avenor_shutdown."] : []
1141
+ ];
1142
+ if (!expanded) return lines;
1143
+ return [
1144
+ ...lines,
1145
+ ...paths.slice(0, CLEANUP_ITEMS).map((path) => `Cleanup path: ${scalar(path)}`),
1146
+ ...paths.length > CLEANUP_ITEMS ? [itemMarker(paths.length - CLEANUP_ITEMS)] : []
1147
+ ];
1148
+ }
1149
+ function render(tool, result, options, theme, build) {
1150
+ try {
1151
+ return linesText(options.isPartial ? partial(result) : build() ?? fallback(tool), theme);
1152
+ } catch {
1153
+ return new Text(fallback(tool).join("\n"), 0, 0);
1154
+ }
1155
+ }
1156
+ function renderStatusCall(args, theme) {
1157
+ const pieces = stringValue(args.run_id) ? [`avenor_status run_id ${quoted(args.run_id)}`] : ["avenor_status (all runs)"];
1158
+ const view = optionalQuoted(args.view);
1159
+ const supervisor = optionalQuoted(args.supervisor_id);
1160
+ if (view) pieces.push(`view ${view}`);
1161
+ if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
1162
+ return linesText([pieces.join(" ")], theme);
1163
+ }
1164
+ function renderResultCall(args, theme) {
1165
+ const pieces = [`avenor_result run_id ${quoted(args.run_id)}`];
1166
+ if (args.wait !== void 0) pieces.push(`wait=${args.wait}`);
1167
+ const timeout = optionalQuoted(args.timeout);
1168
+ const supervisor = optionalQuoted(args.supervisor_id);
1169
+ if (timeout) pieces.push(`timeout ${timeout}`);
1170
+ if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
1171
+ return linesText([pieces.join(" ")], theme);
1172
+ }
1173
+ function renderInspectCall(args, theme) {
1174
+ const pieces = [`avenor_inspect run_id ${quoted(args.run_id)}`];
1175
+ const limit = optionalNumber(args.limit);
1176
+ const afterSequence = optionalNumber(args.after_seq);
1177
+ const supervisor = optionalQuoted(args.supervisor_id);
1178
+ if (limit) pieces.push(`limit=${limit}`);
1179
+ if (afterSequence) pieces.push(`after_seq=${afterSequence}`);
1180
+ if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
1181
+ return linesText([pieces.join(" ")], theme);
1182
+ }
1183
+ function renderAnswerPermissionCall(args, theme) {
1184
+ const pieces = [`avenor_answer_permission run_id ${quoted(args.run_id)}`, `option_id ${quoted(args.option_id)}`];
1185
+ const request = optionalQuoted(args.request_id);
1186
+ const message = optionalQuoted(args.message);
1187
+ const supervisor = optionalQuoted(args.supervisor_id);
1188
+ if (request) pieces.push(`request_id ${request}`);
1189
+ if (message) pieces.push(`message ${message}`);
1190
+ if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
1191
+ return linesText([pieces.join(" ")], theme);
1192
+ }
1193
+ function renderFollowUpCall(args, theme) {
1194
+ const pieces = [`avenor_follow_up run_id ${quoted(args.run_id)}`, `message ${quoted(args.message)}`];
1195
+ const label = optionalQuoted(args.label);
1196
+ const supervisor = optionalQuoted(args.supervisor_id);
1197
+ if (label) pieces.push(`label ${label}`);
1198
+ if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
1199
+ return linesText([pieces.join(" ")], theme);
1200
+ }
1201
+ function renderEventsCall(args, theme) {
1202
+ const pieces = [`avenor_events run_id ${quoted(args.run_id)}`];
1203
+ if (Array.isArray(args.types)) {
1204
+ const shown = args.types.slice(0, COLLECTION_ITEMS).map(quoted).join(", ");
1205
+ pieces.push(`types=[${shown}]${args.types.length > COLLECTION_ITEMS ? ` ${itemMarker(args.types.length - COLLECTION_ITEMS)}` : ""}`);
1206
+ }
1207
+ const limit = optionalNumber(args.limit);
1208
+ const supervisor = optionalQuoted(args.supervisor_id);
1209
+ if (limit) pieces.push(`limit=${limit}`);
1210
+ if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
1211
+ return linesText([pieces.join(" ")], theme);
1212
+ }
1213
+ function renderShutdownCall(args, theme) {
1214
+ const pieces = ["avenor_shutdown"];
1215
+ const supervisor = optionalQuoted(args.supervisor_id);
1216
+ if (supervisor) pieces.push(`supervisor_id ${supervisor}`);
1217
+ if (args.force !== void 0) pieces.push(`force=${args.force}`);
1218
+ return linesText([pieces.join(" ")], theme);
1219
+ }
1220
+ function renderStatusResult(result, options, theme, args) {
1221
+ return render("status", result, options, theme, () => statusLines(result.details, args, options.expanded));
1222
+ }
1223
+ function renderResultResult(result, options, theme, args) {
1224
+ return render("result", result, options, theme, () => resultLines(result.details, args, options.expanded));
1225
+ }
1226
+ function renderInspectResult(result, options, theme, args) {
1227
+ return render("inspect", result, options, theme, () => inspectLines(result.details, args, options.expanded));
1228
+ }
1229
+ function renderAnswerPermissionResult(result, options, theme, args) {
1230
+ return render("answer_permission", result, options, theme, () => permissionLines(result.details, args));
1231
+ }
1232
+ function renderFollowUpResult(result, options, theme, args) {
1233
+ return render("follow_up", result, options, theme, () => followUpLines(result.details, args));
1234
+ }
1235
+ function renderEventsResult(result, options, theme, args) {
1236
+ return render("events", result, options, theme, () => eventsLines(result.details, args, options.expanded));
1237
+ }
1238
+ function renderShutdownResult(result, options, theme, args) {
1239
+ return render("shutdown", result, options, theme, () => shutdownLines(result.details, args, options.expanded));
1240
+ }
1241
+ //#endregion
705
1242
  //#region src/index.ts
706
1243
  const POLL_INTERVAL_MS = 3e3;
707
1244
  const INSPECT_LIMIT = 128;
@@ -710,6 +1247,7 @@ const INSPECT_JSON_STRING_CHARS = 600;
710
1247
  const INSPECT_JSON_ARRAY_ITEMS = 24;
711
1248
  const INSPECT_JSON_OBJECT_KEYS = 24;
712
1249
  const INSPECT_JSON_DEPTH = 8;
1250
+ const MAX_POLL_ERRORS = 100;
713
1251
  const defaultDeps = {
714
1252
  spawnTool,
715
1253
  statusTool,
@@ -726,6 +1264,19 @@ const defaultDeps = {
726
1264
  function statusSupervisorId(requestedSupervisorId, spawnedSupervisorId) {
727
1265
  return requestedSupervisorId ? spawnedSupervisorId : void 0;
728
1266
  }
1267
+ /** Project shared spawn identity into Pi tool metadata without resolving rosters here. */
1268
+ function spawnIdentityMetadata(params, status) {
1269
+ const metadata = {};
1270
+ const add = (key, value) => {
1271
+ if (typeof value === "string" && value.length > 0) metadata[key] = value;
1272
+ };
1273
+ add("roster_file", status?.roster_file ?? params.roster_file);
1274
+ add("roster_entry", status?.roster_entry ?? params.roster_entry);
1275
+ add("effective_agent", status?.effective_agent ?? status?.agent ?? params.agent);
1276
+ add("effective_model", status?.effective_model ?? status?.model ?? params.model);
1277
+ add("effective_backend", status?.effective_backend ?? status?.backend ?? params.backend);
1278
+ return metadata;
1279
+ }
729
1280
  function sleep(ms) {
730
1281
  return new Promise((resolve) => setTimeout(resolve, ms));
731
1282
  }
@@ -751,7 +1302,8 @@ function buildCompletionText(run, result, finalOutput) {
751
1302
  }
752
1303
  function buildWaitingText(run, status) {
753
1304
  const perm = status.pending_permission;
754
- const lines = [`Sub-agent "${run.label}" (${run.agent}) is waiting for input.`];
1305
+ const agent = run.agent ?? status.effective_agent ?? status.agent ?? "roster";
1306
+ const lines = [`Sub-agent "${run.label}" (${agent}) is waiting for input.`];
755
1307
  if (perm) lines.push(`Permission request: ${perm.description}`, `To answer: call \`avenor_answer_permission\` with run_id "${run.runId}", option_id "allow_once" | "allow_always" | "deny"`);
756
1308
  else if (status.phase_label) lines.push(`Question: ${status.phase_label}`);
757
1309
  if (!perm) lines.push(`Use \`avenor_follow_up\` with run_id "${run.runId}" to respond, or \`avenor_inspect\` to review context first.`);
@@ -837,11 +1389,34 @@ function toWatchRunRef(run) {
837
1389
  }
838
1390
  function createExtension(deps = defaultDeps) {
839
1391
  return async function(pi) {
1392
+ let telemetryGeneration = 0;
840
1393
  const trackedRuns = /* @__PURE__ */ new Map();
841
1394
  let pollingActive = false;
842
1395
  let pollingGeneration = 0;
843
1396
  let pollInFlight = null;
844
1397
  let sessionCtx = null;
1398
+ let pollingErrorCount = 0;
1399
+ const pollingErrors = [];
1400
+ let lastStatusEntries = [];
1401
+ function recordPollingError(source, message, error, runId) {
1402
+ pollingErrorCount++;
1403
+ const payload = createPollErrorPayload({
1404
+ source,
1405
+ runId,
1406
+ message,
1407
+ error,
1408
+ count: pollingErrorCount,
1409
+ timestamp: Date.now()
1410
+ });
1411
+ pollingErrors.push(payload);
1412
+ if (pollingErrors.length > MAX_POLL_ERRORS) pollingErrors.shift();
1413
+ emitAvenorEvent(pi.events, CHANNEL_POLL_ERROR, payload);
1414
+ }
1415
+ function clearPollingErrors() {
1416
+ pollingErrorCount = 0;
1417
+ pollingErrors.length = 0;
1418
+ updateWidget(lastStatusEntries);
1419
+ }
845
1420
  function upsertTrackedRun(run, status, blocking = false) {
846
1421
  const current = trackedRuns.get(run.runId);
847
1422
  trackedRuns.set(run.runId, {
@@ -872,13 +1447,14 @@ function createExtension(deps = defaultDeps) {
872
1447
  }
873
1448
  }
874
1449
  async function pollRuns() {
1450
+ const errorCountAtPollStart = pollingErrorCount;
875
1451
  const liveMap = /* @__PURE__ */ new Map();
876
1452
  try {
877
1453
  const allLive = await deps.statusTool({});
878
1454
  const liveList = Array.isArray(allLive) ? allLive : [allLive];
879
1455
  for (const result of liveList) liveMap.set(result.run_id, result);
880
1456
  } catch (err) {
881
- console.error("avenor pollRuns: failed to list singleton runs", err);
1457
+ recordPollingError("singleton-list", "failed to list singleton runs", err);
882
1458
  }
883
1459
  const entries = [];
884
1460
  for (const run of trackedRuns.values()) {
@@ -886,13 +1462,16 @@ function createExtension(deps = defaultDeps) {
886
1462
  if (live) {
887
1463
  liveMap.delete(live.run_id);
888
1464
  run.lastStatus = live;
1465
+ run.agent = live.agent ?? run.agent;
889
1466
  entries.push({
890
1467
  runId: run.runId,
1468
+ supervisorId: run.supervisorId,
1469
+ runtimeId: live.runtime_id ?? run.runtimeId,
891
1470
  label: run.label,
892
1471
  status: live.status,
893
1472
  phase: live.phase,
894
1473
  phaseLabel: live.phase_label,
895
- agent: run.agent,
1474
+ agent: run.agent ?? "unknown",
896
1475
  pendingPermission: !!live.pending_permission,
897
1476
  permissionDescription: live.pending_permission?.description,
898
1477
  pid: live.pid,
@@ -908,28 +1487,33 @@ function createExtension(deps = defaultDeps) {
908
1487
  const result = Array.isArray(raw) ? raw[0] : raw;
909
1488
  if (!result) throw new Error("status response was empty");
910
1489
  run.lastStatus = result;
1490
+ run.agent = result.agent ?? run.agent;
911
1491
  entries.push({
912
1492
  runId: run.runId,
1493
+ supervisorId: run.supervisorId,
1494
+ runtimeId: result.runtime_id ?? run.runtimeId,
913
1495
  label: run.label,
914
1496
  status: result.status,
915
1497
  phase: result.phase,
916
1498
  phaseLabel: result.phase_label,
917
- agent: run.agent,
1499
+ agent: run.agent ?? "unknown",
918
1500
  pendingPermission: !!result.pending_permission,
919
1501
  permissionDescription: result.pending_permission?.description,
920
1502
  pid: result.pid,
921
1503
  backend: result.backend
922
1504
  });
923
1505
  } catch (err) {
924
- console.error(`avenor pollRuns: statusTool failed for ${run.runId}`, err);
1506
+ recordPollingError("run-status", "statusTool failed while polling a run", err, run.runId);
925
1507
  const previous = run.lastStatus;
926
1508
  entries.push({
927
1509
  runId: run.runId,
1510
+ supervisorId: run.supervisorId,
1511
+ runtimeId: previous?.runtime_id ?? run.runtimeId,
928
1512
  label: run.label,
929
1513
  status: previous?.status ?? "unavailable",
930
1514
  phase: previous?.phase,
931
1515
  phaseLabel: previous?.phase_label,
932
- agent: run.agent,
1516
+ agent: run.agent ?? previous?.agent ?? "unknown",
933
1517
  pendingPermission: !!previous?.pending_permission,
934
1518
  permissionDescription: previous?.pending_permission?.description,
935
1519
  pid: previous?.pid,
@@ -939,6 +1523,7 @@ function createExtension(deps = defaultDeps) {
939
1523
  }
940
1524
  for (const live of liveMap.values()) entries.push({
941
1525
  runId: live.run_id,
1526
+ runtimeId: live.runtime_id,
942
1527
  label: live.label,
943
1528
  status: live.status,
944
1529
  phase: live.phase,
@@ -955,21 +1540,24 @@ function createExtension(deps = defaultDeps) {
955
1540
  let idx = 0;
956
1541
  for (const entry of entries) if (entry.backend === "pi" && entry.pid && !isTerminalStatus(entry.status)) entry.nestedCount = nestedCounts[idx++] ?? 0;
957
1542
  }
1543
+ if (pollingErrorCount === errorCountAtPollStart) pollingErrorCount = 0;
958
1544
  return entries;
959
1545
  }
960
1546
  function updateWidget(entries) {
1547
+ lastStatusEntries = entries;
961
1548
  if (!sessionCtx) return;
962
1549
  const lines = renderStatusLines(entries);
1550
+ const errorSuffix = pollingErrorCount > 0 ? `errors:${pollingErrorCount}` : "";
963
1551
  if (lines.length > 0) {
964
1552
  sessionCtx.ui.setWidget("avenor-status", lines);
965
1553
  const statusStr = lines.map((line) => line.slice(0, 60)).join(" ");
966
1554
  const nestedTotal = entries.filter((e) => !isTerminalStatus(e.status)).reduce((sum, e) => sum + (e.nestedCount ?? 0), 0);
967
1555
  const total = lines.length + nestedTotal;
968
- const statusWithTotal = nestedTotal > 0 ? `${statusStr} total:${total}` : statusStr;
1556
+ const statusWithTotal = [nestedTotal > 0 ? `${statusStr} total:${total}` : statusStr, errorSuffix].filter(Boolean).join(" ");
969
1557
  sessionCtx.ui.setStatus("avenor", statusWithTotal);
970
1558
  } else {
971
1559
  sessionCtx.ui.setWidget("avenor-status", void 0);
972
- sessionCtx.ui.setStatus("avenor", "");
1560
+ sessionCtx.ui.setStatus("avenor", errorSuffix);
973
1561
  }
974
1562
  }
975
1563
  function startPolling() {
@@ -987,6 +1575,7 @@ function createExtension(deps = defaultDeps) {
987
1575
  });
988
1576
  if (!pollingActive || generation !== pollingGeneration) return;
989
1577
  updateWidget(entries);
1578
+ emitAvenorEvent(pi.events, CHANNEL_POLL_COMPLETED, createPollCompletedPayload(entries, Date.now(), ++telemetryGeneration));
990
1579
  for (const entry of entries) {
991
1580
  const run = trackedRuns.get(entry.runId);
992
1581
  if (!run) continue;
@@ -1009,6 +1598,16 @@ function createExtension(deps = defaultDeps) {
1009
1598
  } catch (err) {
1010
1599
  console.error("avenor tick: sendUserMessage failed", err);
1011
1600
  }
1601
+ emitAvenorEvent(pi.events, CHANNEL_RUN_TERMINAL, createRunTerminalPayload({
1602
+ runId: entry.runId,
1603
+ supervisorId: entry.supervisorId,
1604
+ runtimeId: entry.runtimeId,
1605
+ label: entry.label,
1606
+ agent: entry.agent,
1607
+ status: entry.status,
1608
+ nestedCount: entry.nestedCount,
1609
+ backend: entry.backend
1610
+ }));
1012
1611
  trackedRuns.delete(entry.runId);
1013
1612
  } else if (entry.pendingPermission && !run.blocking && !run.permissionNotified) {
1014
1613
  run.permissionNotified = true;
@@ -1031,8 +1630,7 @@ function createExtension(deps = defaultDeps) {
1031
1630
  }, POLL_INTERVAL_MS);
1032
1631
  else {
1033
1632
  pollingActive = false;
1034
- sessionCtx?.ui.setStatus("avenor", "");
1035
- sessionCtx?.ui.setWidget("avenor-status", void 0);
1633
+ updateWidget([]);
1036
1634
  }
1037
1635
  };
1038
1636
  tick().catch((err) => {
@@ -1044,8 +1642,10 @@ function createExtension(deps = defaultDeps) {
1044
1642
  pollingActive = false;
1045
1643
  pollingGeneration++;
1046
1644
  await pollInFlight?.catch(() => {});
1047
- sessionCtx?.ui.setStatus("avenor", "");
1048
- sessionCtx?.ui.setWidget("avenor-status", void 0);
1645
+ pollingErrorCount = 0;
1646
+ pollingErrors.length = 0;
1647
+ lastStatusEntries = [];
1648
+ updateWidget([]);
1049
1649
  }
1050
1650
  function createWatchDeps() {
1051
1651
  async function resolveSupervisorSocket(run) {
@@ -1230,7 +1830,7 @@ function createExtension(deps = defaultDeps) {
1230
1830
  aborted: false
1231
1831
  };
1232
1832
  } catch (err) {
1233
- console.error("avenor spawn poll: statusTool failed", err);
1833
+ recordPollingError("spawn-status", "statusTool failed while waiting for a run", err, run.runId);
1234
1834
  }
1235
1835
  }
1236
1836
  return { aborted: true };
@@ -1242,6 +1842,9 @@ function createExtension(deps = defaultDeps) {
1242
1842
  }
1243
1843
  pi.on("session_start", async (_event, ctx) => {
1244
1844
  sessionCtx = ctx;
1845
+ lastStatusEntries = [];
1846
+ pollingErrorCount = 0;
1847
+ pollingErrors.length = 0;
1245
1848
  });
1246
1849
  pi.on("session_shutdown", async () => {
1247
1850
  await stopPolling();
@@ -1273,7 +1876,7 @@ function createExtension(deps = defaultDeps) {
1273
1876
  pi.registerTool({
1274
1877
  name: "avenor_spawn",
1275
1878
  label: "Avenor Spawn",
1276
- description: "Dispatch an agent run via avenor. Blocks by default, showing live progress. Set wait=false for fire-and-forget.",
1879
+ 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
1880
  promptSnippet: "Dispatch sub-agent runs via avenor (spawn, status, result, inspect, events, follow-up, shutdown)",
1278
1881
  promptGuidelines: [
1279
1882
  "Use avenor_spawn to delegate well-defined tasks to sub-agents.",
@@ -1285,14 +1888,25 @@ function createExtension(deps = defaultDeps) {
1285
1888
  "Use avenor_shutdown when done delegating work."
1286
1889
  ],
1287
1890
  parameters: Type.Object({
1288
- agent: Type.String({ description: "Agent name (required, no default)" }),
1891
+ agent: Type.Optional(Type.String({ description: "Optional agent name; omission uses the supplied model or runtime defaults" })),
1289
1892
  prompt: Type.Optional(Type.String({ description: "Prompt text" })),
1290
1893
  prompt_file: Type.Optional(Type.String({ description: "Path to prompt file" })),
1291
1894
  dir: Type.Optional(Type.String({ description: "Working directory (defaults to session directory)" })),
1292
1895
  label: Type.Optional(Type.String({ description: "Human-readable label for the run" })),
1293
1896
  timeout: Type.Optional(Type.String({ description: "Timeout duration (e.g. 3600s)" })),
1294
1897
  model: Type.Optional(Type.String({ description: "Model override" })),
1295
- backend: Type.Optional(Type.String({ description: "Backend override (defaults to pi)" })),
1898
+ thinking: Type.Optional(Type.Union([
1899
+ Type.Literal("off"),
1900
+ Type.Literal("minimal"),
1901
+ Type.Literal("low"),
1902
+ Type.Literal("medium"),
1903
+ Type.Literal("high"),
1904
+ Type.Literal("xhigh"),
1905
+ Type.Literal("max")
1906
+ ], { description: "Thinking level; omission uses the backend default" })),
1907
+ backend: Type.Optional(Type.String({ description: "Backend override (defaults to pi for direct mode)" })),
1908
+ roster_file: Type.Optional(Type.String({ description: "Path to the roster map" })),
1909
+ roster_entry: Type.Optional(Type.String({ description: "Roster entry to select" })),
1296
1910
  server_url: Type.Optional(Type.String({ description: "Backend server URL" })),
1297
1911
  supervisor_id: Type.Optional(Type.String({ description: "Reuse an existing supervisor by socket path" })),
1298
1912
  wait: Type.Optional(Type.Boolean({ description: "Block until complete (default true)" }))
@@ -1302,24 +1916,39 @@ function createExtension(deps = defaultDeps) {
1302
1916
  type: "text",
1303
1917
  text: "Cancelled"
1304
1918
  }] };
1919
+ validateSpawnSelection({
1920
+ agent: params.agent,
1921
+ model: params.model,
1922
+ backend: params.backend,
1923
+ roster_file: params.roster_file,
1924
+ roster_entry: params.roster_entry
1925
+ });
1305
1926
  const wait = params.wait ?? true;
1306
1927
  const dir = params.dir ?? ctx.cwd;
1307
- const label = params.label ?? `${params.agent}-${Date.now()}`;
1308
- const backend = params.backend ?? "pi";
1928
+ const label = params.label ?? `${params.agent ?? params.roster_entry ?? params.model ?? "avenor"}-${Date.now()}`;
1929
+ const rosterMode = Boolean(params.roster_entry);
1930
+ const backend = rosterMode ? params.backend : params.backend ?? "pi";
1309
1931
  const entries = ctx.sessionManager?.getEntries?.();
1310
- const agentProfile = backend === "pi" ? resolveAgentProfile(entries) : void 0;
1932
+ const agentProfile = rosterMode || backend === "pi" ? resolveAgentProfile(entries) : void 0;
1933
+ const hostParams = {
1934
+ ...params,
1935
+ backend
1936
+ };
1311
1937
  const result = await deps.spawnTool({
1312
- agent: params.agent,
1313
- prompt: params.prompt,
1314
- promptFile: params.prompt_file,
1938
+ ...params.agent !== void 0 ? { agent: params.agent } : {},
1939
+ ...params.prompt !== void 0 ? { prompt: params.prompt } : {},
1940
+ ...params.prompt_file !== void 0 ? { promptFile: params.prompt_file } : {},
1315
1941
  label,
1316
1942
  dir,
1317
- timeout: params.timeout,
1318
- model: params.model,
1319
- backend,
1320
- agentProfile,
1321
- serverUrl: params.server_url,
1322
- supervisorId: params.supervisor_id
1943
+ ...params.timeout !== void 0 ? { timeout: params.timeout } : {},
1944
+ ...params.model !== void 0 ? { model: params.model } : {},
1945
+ ...params.thinking !== void 0 ? { thinking: params.thinking } : {},
1946
+ ...backend !== void 0 ? { backend } : {},
1947
+ ...params.roster_file !== void 0 ? { rosterFile: params.roster_file } : {},
1948
+ ...params.roster_entry !== void 0 ? { rosterEntry: params.roster_entry } : {},
1949
+ ...agentProfile !== void 0 ? { agentProfile } : {},
1950
+ ...params.server_url !== void 0 ? { serverUrl: params.server_url } : {},
1951
+ ...params.supervisor_id !== void 0 ? { supervisorId: params.supervisor_id } : {}
1323
1952
  });
1324
1953
  const supervisorId = statusSupervisorId(params.supervisor_id, result.supervisor_id);
1325
1954
  const runRef = {
@@ -1338,7 +1967,8 @@ function createExtension(deps = defaultDeps) {
1338
1967
  }],
1339
1968
  details: {
1340
1969
  run_id: result.run_id,
1341
- label
1970
+ label,
1971
+ ...spawnIdentityMetadata(hostParams)
1342
1972
  }
1343
1973
  };
1344
1974
  onUpdate?.({
@@ -1359,7 +1989,10 @@ function createExtension(deps = defaultDeps) {
1359
1989
  type: "text",
1360
1990
  text: `Monitoring of "${label}" (run_id: ${result.run_id}) was interrupted. Use avenor_status or avenor_inspect to check.`
1361
1991
  }],
1362
- details: { run_id: result.run_id }
1992
+ details: {
1993
+ run_id: result.run_id,
1994
+ ...spawnIdentityMetadata(hostParams)
1995
+ }
1363
1996
  };
1364
1997
  }
1365
1998
  const status = waitResult.status;
@@ -1370,7 +2003,8 @@ function createExtension(deps = defaultDeps) {
1370
2003
  }],
1371
2004
  details: {
1372
2005
  status: "running",
1373
- run_id: result.run_id
2006
+ run_id: result.run_id,
2007
+ ...spawnIdentityMetadata(hostParams)
1374
2008
  }
1375
2009
  };
1376
2010
  upsertTrackedRun(runRef, status, wait);
@@ -1390,7 +2024,8 @@ function createExtension(deps = defaultDeps) {
1390
2024
  status: "waiting",
1391
2025
  run_id: result.run_id,
1392
2026
  session_id: status.session_id,
1393
- ...status.pending_permission && { pending_permission: status.pending_permission }
2027
+ ...status.pending_permission && { pending_permission: status.pending_permission },
2028
+ ...spawnIdentityMetadata(hostParams, status)
1394
2029
  }
1395
2030
  };
1396
2031
  }
@@ -1399,6 +2034,15 @@ function createExtension(deps = defaultDeps) {
1399
2034
  runId: result.run_id,
1400
2035
  supervisorId
1401
2036
  }, status);
2037
+ emitAvenorEvent(pi.events, CHANNEL_RUN_TERMINAL, createRunTerminalPayload({
2038
+ runId: result.run_id,
2039
+ supervisorId,
2040
+ runtimeId: status.runtime_id ?? runRef.runtimeId,
2041
+ label,
2042
+ agent: params.agent,
2043
+ status: status.status,
2044
+ backend: status.backend
2045
+ }));
1402
2046
  trackedRuns.delete(result.run_id);
1403
2047
  return {
1404
2048
  content: [{
@@ -1412,7 +2056,8 @@ function createExtension(deps = defaultDeps) {
1412
2056
  status: status.status,
1413
2057
  run_id: result.run_id,
1414
2058
  session_id: status.session_id,
1415
- final_output: finalOutput
2059
+ final_output: finalOutput,
2060
+ ...spawnIdentityMetadata(hostParams, status)
1416
2061
  }
1417
2062
  };
1418
2063
  }
@@ -1423,13 +2068,14 @@ function createExtension(deps = defaultDeps) {
1423
2068
  }],
1424
2069
  details: {
1425
2070
  status: status.status,
1426
- run_id: result.run_id
2071
+ run_id: result.run_id,
2072
+ ...spawnIdentityMetadata(hostParams, status)
1427
2073
  }
1428
2074
  };
1429
2075
  },
1430
2076
  renderCall(args, theme) {
1431
2077
  const agent = theme.fg("toolTitle", theme.bold("avenor_spawn "));
1432
- const agentName = theme.fg("accent", args.agent);
2078
+ const agentName = theme.fg("accent", args.agent ?? args.roster_entry ?? args.model ?? "roster");
1433
2079
  const label = args.label ? theme.fg("dim", ` "${args.label}"`) : "";
1434
2080
  const wait = args.wait === false ? theme.fg("warning", " [async]") : "";
1435
2081
  return new Text(agent + agentName + label + wait, 0, 0);
@@ -1474,6 +2120,15 @@ function createExtension(deps = defaultDeps) {
1474
2120
  }],
1475
2121
  details: result
1476
2122
  };
2123
+ },
2124
+ renderCall(args, theme, context) {
2125
+ return renderStatusCall(context.args, theme);
2126
+ },
2127
+ renderResult(result, { expanded, isPartial }, theme, context) {
2128
+ return renderStatusResult(result, {
2129
+ expanded,
2130
+ isPartial
2131
+ }, theme, context.args);
1477
2132
  }
1478
2133
  });
1479
2134
  pi.registerTool({
@@ -1500,8 +2155,17 @@ function createExtension(deps = defaultDeps) {
1500
2155
  signal
1501
2156
  });
1502
2157
  completed = result.ready;
1503
- if (completed) trackedRuns.delete(params.run_id);
1504
- else if (tracked && result.status === "waiting") tracked.permissionNotified = true;
2158
+ if (completed) {
2159
+ emitAvenorEvent(pi.events, CHANNEL_RUN_TERMINAL, createRunTerminalPayload({
2160
+ runId: params.run_id,
2161
+ supervisorId: tracked?.supervisorId ?? params.supervisor_id,
2162
+ runtimeId: tracked?.runtimeId ?? result.runtime_id,
2163
+ label: tracked?.label ?? result.label ?? params.run_id,
2164
+ agent: tracked?.agent ?? "unknown",
2165
+ status: result.status ?? "done"
2166
+ }));
2167
+ trackedRuns.delete(params.run_id);
2168
+ } else if (tracked && result.status === "waiting") tracked.permissionNotified = true;
1505
2169
  return {
1506
2170
  content: [{
1507
2171
  type: "text",
@@ -1512,6 +2176,15 @@ function createExtension(deps = defaultDeps) {
1512
2176
  } finally {
1513
2177
  if (!completed && tracked && trackedRuns.get(params.run_id) === tracked) tracked.blocking = previousBlocking ?? false;
1514
2178
  }
2179
+ },
2180
+ renderCall(args, theme, context) {
2181
+ return renderResultCall(context.args, theme);
2182
+ },
2183
+ renderResult(result, { expanded, isPartial }, theme, context) {
2184
+ return renderResultResult(result, {
2185
+ expanded,
2186
+ isPartial
2187
+ }, theme, context.args);
1515
2188
  }
1516
2189
  });
1517
2190
  pi.registerTool({
@@ -1538,6 +2211,15 @@ function createExtension(deps = defaultDeps) {
1538
2211
  }],
1539
2212
  details: payload
1540
2213
  };
2214
+ },
2215
+ renderCall(args, theme, context) {
2216
+ return renderInspectCall(context.args, theme);
2217
+ },
2218
+ renderResult(result, { expanded, isPartial }, theme, context) {
2219
+ return renderInspectResult(result, {
2220
+ expanded,
2221
+ isPartial
2222
+ }, theme, context.args);
1541
2223
  }
1542
2224
  });
1543
2225
  pi.registerTool({
@@ -1566,6 +2248,15 @@ function createExtension(deps = defaultDeps) {
1566
2248
  }],
1567
2249
  details: result
1568
2250
  };
2251
+ },
2252
+ renderCall(args, theme, context) {
2253
+ return renderAnswerPermissionCall(context.args, theme);
2254
+ },
2255
+ renderResult(result, { expanded, isPartial }, theme, context) {
2256
+ return renderAnswerPermissionResult(result, {
2257
+ expanded,
2258
+ isPartial
2259
+ }, theme, context.args);
1569
2260
  }
1570
2261
  });
1571
2262
  pi.registerTool({
@@ -1613,6 +2304,15 @@ function createExtension(deps = defaultDeps) {
1613
2304
  }],
1614
2305
  details: result
1615
2306
  };
2307
+ },
2308
+ renderCall(args, theme, context) {
2309
+ return renderFollowUpCall(context.args, theme);
2310
+ },
2311
+ renderResult(result, { expanded, isPartial }, theme, context) {
2312
+ return renderFollowUpResult(result, {
2313
+ expanded,
2314
+ isPartial
2315
+ }, theme, context.args);
1616
2316
  }
1617
2317
  });
1618
2318
  pi.registerTool({
@@ -1639,6 +2339,15 @@ function createExtension(deps = defaultDeps) {
1639
2339
  }],
1640
2340
  details: result
1641
2341
  };
2342
+ },
2343
+ renderCall(args, theme, context) {
2344
+ return renderEventsCall(context.args, theme);
2345
+ },
2346
+ renderResult(result, { expanded, isPartial }, theme, context) {
2347
+ return renderEventsResult(result, {
2348
+ expanded,
2349
+ isPartial
2350
+ }, theme, context.args);
1642
2351
  }
1643
2352
  });
1644
2353
  pi.registerTool({
@@ -1663,12 +2372,22 @@ function createExtension(deps = defaultDeps) {
1663
2372
  }],
1664
2373
  details: result
1665
2374
  };
2375
+ },
2376
+ renderCall(args, theme, context) {
2377
+ return renderShutdownCall(context.args, theme);
2378
+ },
2379
+ renderResult(result, { expanded, isPartial }, theme, context) {
2380
+ return renderShutdownResult(result, {
2381
+ expanded,
2382
+ isPartial
2383
+ }, theme, context.args);
1666
2384
  }
1667
2385
  });
1668
2386
  pi.registerCommand("avenor-status", {
1669
2387
  description: "Show status of all avenor runs",
1670
2388
  handler: async (_args, ctx) => {
1671
2389
  const entries = await pollRuns();
2390
+ updateWidget(entries);
1672
2391
  if (entries.length === 0) {
1673
2392
  ctx.ui.notify("No active avenor runs", "info");
1674
2393
  return;
@@ -1676,12 +2395,28 @@ function createExtension(deps = defaultDeps) {
1676
2395
  ctx.ui.setWidget("avenor-status-detail", entries.map((entry) => formatRunLine(entry)));
1677
2396
  }
1678
2397
  });
2398
+ pi.registerCommand("avenor-errors", {
2399
+ description: "Show and clear recent Avenor polling errors",
2400
+ handler: async (_args, ctx) => {
2401
+ if (pollingErrors.length === 0) {
2402
+ ctx.ui.setWidget("avenor-errors", void 0);
2403
+ ctx.ui.notify("No recorded avenor polling errors", "info");
2404
+ return;
2405
+ }
2406
+ const errorLines = pollingErrors.map((error) => {
2407
+ const run = error.runId ? ` [${error.runId}]` : "";
2408
+ return `${new Date(error.timestamp).toLocaleTimeString()} ${error.message}${run}: ${error.error}`;
2409
+ });
2410
+ ctx.ui.setWidget("avenor-errors", errorLines);
2411
+ clearPollingErrors();
2412
+ }
2413
+ });
1679
2414
  pi.registerCommand("avenor-watch", {
1680
2415
  description: "Open a live run inspector by run ID, label, or agent name",
1681
2416
  getArgumentCompletions: (prefix) => {
1682
- const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.agent.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => ({
2417
+ const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.agent?.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => ({
1683
2418
  value: run.runId,
1684
- label: `${sanitizeText(run.label)} (${sanitizeText(run.agent)}, ${sanitizeText(run.runId).slice(0, 8)})`
2419
+ label: `${sanitizeText(run.label)} (${sanitizeText(run.agent ?? "roster")}, ${sanitizeText(run.runId).slice(0, 8)})`
1685
2420
  }));
1686
2421
  return items.length > 0 ? items : null;
1687
2422
  },
@@ -1703,9 +2438,9 @@ function createExtension(deps = defaultDeps) {
1703
2438
  pi.registerCommand("avenor-cancel", {
1704
2439
  description: "Cancel a running avenor sub-agent",
1705
2440
  getArgumentCompletions: (prefix) => {
1706
- const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.agent.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => ({
2441
+ const items = Array.from(trackedRuns.values()).filter((run) => run.label.startsWith(prefix) || run.agent?.startsWith(prefix) || run.runId.startsWith(prefix)).map((run) => ({
1707
2442
  value: run.runId,
1708
- label: `${sanitizeText(run.label)} (${sanitizeText(run.agent)}, ${sanitizeText(run.runId).slice(0, 8)})`
2443
+ label: `${sanitizeText(run.label)} (${sanitizeText(run.agent ?? "roster")}, ${sanitizeText(run.runId).slice(0, 8)})`
1709
2444
  }));
1710
2445
  return items.length > 0 ? items : null;
1711
2446
  },
@@ -1740,4 +2475,4 @@ function createExtension(deps = defaultDeps) {
1740
2475
  }
1741
2476
  var src_default = createExtension();
1742
2477
  //#endregion
1743
- export { buildCompletionText, buildInspectPayload, compactWhitespace, createExtension, src_default as default, isTerminalStatus, renderStatusLines, statusSupervisorId };
2478
+ export { CHANNEL_POLL_COMPLETED, CHANNEL_POLL_ERROR, CHANNEL_RUN_TERMINAL, buildCompletionText, buildInspectPayload, compactWhitespace, createExtension, createPollCompletedPayload, createPollErrorPayload, createRunTerminalPayload, src_default as default, emitAvenorEvent, isTerminalStatus, onAvenorEvent, renderStatusLines, spawnIdentityMetadata, statusSupervisorId };