@llblab/pi-actors 0.40.1 → 0.41.1

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/index.ts CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  import * as AsyncRuns from "./lib/async-runs.ts";
9
9
  import * as CommandTemplates from "./lib/command-templates.ts";
10
- import * as Inspector from "./lib/inspector.ts";
10
+ import * as InspectorOverlay from "./lib/inspector-overlay.ts";
11
11
  import * as Observability from "./lib/observability.ts";
12
12
  import * as Paths from "./lib/paths.ts";
13
13
  import * as Pi from "./lib/pi.ts";
@@ -15,14 +15,16 @@ import * as Prompts from "./lib/prompts.ts";
15
15
  import * as Runtime from "./lib/runtime.ts";
16
16
  import * as Temp from "./lib/temp.ts";
17
17
  import * as Tools from "./lib/tools.ts";
18
+ import * as ToolsResponse from "./lib/tools-response.ts";
18
19
 
19
20
  export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
20
21
  let runsAnimationInterval: NodeJS.Timeout | undefined;
21
22
  let runsNotifyTimeout: NodeJS.Timeout | undefined;
22
23
  let activeRunContext: Pi.ExtensionContext | undefined;
24
+ let lastRunWatcherDiagnosticId = 0;
23
25
  const runUi = Observability.createRunUiObservationState();
24
26
  const retirementAttempts = new Set<string>();
25
- const actorInspector = Inspector.createActorInspectorControllerState();
27
+ const terminalNotificationsInFlight = new Set<string>();
26
28
  const getRunOwnerId = Pi.getSessionId;
27
29
  const retireCandidateRuns = (
28
30
  ctx: Pi.ExtensionContext,
@@ -47,39 +49,13 @@ export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
47
49
  "zz-pi-actors-runs",
48
50
  snapshot.status ? ctx.ui.theme.fg("dim", snapshot.status) : undefined,
49
51
  );
50
- ctx.ui.setWidget(
51
- "zz-pi-actors-comms",
52
- actorInspector.visible
53
- ? () => ({
54
- invalidate() {},
55
- render(width: number) {
56
- return Inspector.renderActorInspectorPanel({
57
- stateRoot: Paths.EXTENSION_RUNTIME_PATHS.runStateRoot,
58
- state: actorInspector,
59
- ownerId,
60
- width,
61
- style: {
62
- actor: (text: string) => ctx.ui.theme.fg("accent", text),
63
- muted: (text: string) => ctx.ui.theme.fg("dim", text),
64
- preview: (text: string) => ctx.ui.theme.fg("text", text),
65
- stripe: (text: string) => text,
66
- stripeAlt: (text: string) =>
67
- ctx.ui.theme.bg("customMessageBg", text),
68
- target: (text: string) => ctx.ui.theme.fg("success", text),
69
- type: (text: string) => ctx.ui.theme.fg("warning", text),
70
- },
71
- });
72
- },
73
- })
74
- : undefined,
75
- { placement: "belowEditor" },
76
- );
77
52
  if (!notify) return;
78
53
  const notificationSink = Pi.createNotificationSink(pi, ctx);
79
54
  retireCandidateRuns(ctx, snapshot.summary);
80
55
  Observability.deliverRunTransitionNotifications(
81
56
  snapshot.transitions,
82
57
  notificationSink,
58
+ terminalNotificationsInFlight,
83
59
  );
84
60
  Observability.pruneRunUiObservationState(runUi, snapshot);
85
61
  if (!terminalOnly) {
@@ -91,22 +67,56 @@ export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
91
67
  };
92
68
  const closeRunWatchers = (): void => {
93
69
  runWatcher.close();
70
+ terminalReconciliation.close();
94
71
  if (runsNotifyTimeout) clearTimeout(runsNotifyTimeout);
95
72
  runsNotifyTimeout = undefined;
96
73
  };
97
- const scheduleRunEventUpdate = (ctx: Pi.ExtensionContext): void => {
74
+ const reportRunWatcherDiagnostics = (ctx: Pi.ExtensionContext): void => {
75
+ for (const diagnostic of runWatcher.getDiagnostics()) {
76
+ if (diagnostic.id <= lastRunWatcherDiagnosticId) continue;
77
+ lastRunWatcherDiagnosticId = diagnostic.id;
78
+ ctx.ui.notify(
79
+ diagnostic.message,
80
+ diagnostic.code === "rearmed" ? "info" : "warning",
81
+ );
82
+ }
83
+ };
84
+ const scheduleRunEventUpdate = (): void => {
98
85
  if (runsNotifyTimeout) clearTimeout(runsNotifyTimeout);
99
86
  runsNotifyTimeout = setTimeout(() => {
87
+ const ctx = activeRunContext;
88
+ if (!ctx) return;
100
89
  runWatcher.refresh();
101
90
  updateRunUi(ctx, true);
91
+ reportRunWatcherDiagnostics(ctx);
102
92
  }, 50);
103
93
  runsNotifyTimeout.unref?.();
104
94
  };
105
95
  const runWatcher = Observability.createRunStateWatcher({
106
96
  stateRoot: Paths.EXTENSION_RUNTIME_PATHS.runStateRoot,
107
- onChange: () =>
108
- activeRunContext && scheduleRunEventUpdate(activeRunContext),
97
+ onChange: scheduleRunEventUpdate,
109
98
  });
99
+ const terminalReconciliation =
100
+ Observability.createRunTerminalReconciliationLoop({
101
+ onError: (error) => {
102
+ const ctx = activeRunContext;
103
+ if (!ctx) return;
104
+ const message = error instanceof Error ? error.message : String(error);
105
+ ctx.ui.notify(`Actor terminal reconciliation failed: ${message}`, "error");
106
+ },
107
+ reconcile: () => {
108
+ const ctx = activeRunContext;
109
+ if (!ctx) return;
110
+ Observability.reconcileRunTerminalNotifications({
111
+ inFlight: terminalNotificationsInFlight,
112
+ ownerId: getRunOwnerId(ctx),
113
+ sink: Pi.createNotificationSink(pi, ctx),
114
+ state: runUi,
115
+ });
116
+ reportRunWatcherDiagnostics(ctx);
117
+ },
118
+ refreshWatcher: () => runWatcher.refresh(),
119
+ });
110
120
  const actorToolDefinitions = new Map<string, Tools.ActorToolDefinition>();
111
121
  const withCurrentThinkingContext = <T extends Tools.ActorToolDefinition>(
112
122
  definition: T,
@@ -115,7 +125,7 @@ export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
115
125
  const execute = definition.execute as (...args: unknown[]) => unknown;
116
126
  return {
117
127
  ...definition,
118
- execute: (...args: unknown[]) => {
128
+ execute: async (...args: unknown[]) => {
119
129
  const nextArgs = [...args];
120
130
  const ctx = nextArgs[4];
121
131
  if (ctx && typeof ctx === "object") {
@@ -124,7 +134,11 @@ export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
124
134
  getThinkingLevel: () => pi.getThinkingLevel(),
125
135
  };
126
136
  }
127
- return execute(...nextArgs);
137
+ try {
138
+ return ToolsResponse.spaceToolResult(await execute(...nextArgs));
139
+ } catch (error) {
140
+ throw ToolsResponse.spaceToolError(error);
141
+ }
128
142
  },
129
143
  } as T;
130
144
  };
@@ -147,16 +161,22 @@ export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
147
161
  return { skillPaths };
148
162
  });
149
163
  pi.on("session_start", async (_event, ctx) => {
164
+ // Clear the pre-overlay widget after hot reloads from older pi-actors builds.
165
+ ctx.ui.setWidget("zz-pi-actors-comms", undefined);
150
166
  activeRunContext = ctx;
167
+ closeRunWatchers();
168
+ recipeReload.close();
151
169
  await Temp.prepareExtensionTempDir(Paths.EXTENSION_RUNTIME_PATHS.tempDir);
170
+ if (activeRunContext !== ctx) return;
152
171
  runtime.loadTools(ctx);
153
172
  updateRunUi(ctx, true, true);
154
- closeRunWatchers();
155
- recipeReload.close();
156
173
  runWatcher.refresh();
174
+ terminalReconciliation.start();
157
175
  recipeReload.watch(ctx);
158
176
  if (runsAnimationInterval) clearInterval(runsAnimationInterval);
159
- runsAnimationInterval = setInterval(() => updateRunUi(ctx, false), 1000);
177
+ runsAnimationInterval = setInterval(() => {
178
+ if (activeRunContext === ctx) updateRunUi(ctx, false);
179
+ }, 1000);
160
180
  runsAnimationInterval.unref?.();
161
181
  });
162
182
  pi.on("session_shutdown", async () => {
@@ -166,40 +186,30 @@ export default function toolRegistryExtension(pi: Pi.ExtensionAPI) {
166
186
  closeRunWatchers();
167
187
  recipeReload.close();
168
188
  });
169
- pi.registerCommand("actors-inspector-toggle", {
170
- description: Inspector.ACTOR_INSPECTOR_COMMAND_DESCRIPTIONS.toggle,
171
- handler: async (args, ctx) => {
172
- const result = Inspector.handleActorInspectorToggle(actorInspector, args);
173
- if (result.update) updateRunUi(ctx);
174
- ctx.ui.notify(result.notify, result.type);
175
- },
176
- });
177
- pi.registerCommand("actors-inspector-filter", {
178
- description: Inspector.ACTOR_INSPECTOR_COMMAND_DESCRIPTIONS.filter,
179
- handler: async (args, ctx) => {
180
- const result = Inspector.handleActorInspectorFilter(actorInspector, args);
181
- if (result.update) updateRunUi(ctx);
182
- ctx.ui.notify(result.notify, result.type);
183
- },
184
- });
185
- pi.registerCommand("actors-inspect", {
186
- description: Inspector.ACTOR_INSPECTOR_COMMAND_DESCRIPTIONS.inspect,
187
- handler: async (args, ctx) => {
188
- const previews = Inspector.readActorInspectorPreviews(
189
- Paths.EXTENSION_RUNTIME_PATHS.runStateRoot,
190
- actorInspector.rows,
191
- Inspector.getActorInspectorPreviewOptions(
192
- actorInspector,
193
- getRunOwnerId(ctx),
194
- ),
195
- );
196
- const result = Inspector.handleActorInspectorInspect(
197
- actorInspector,
198
- args,
199
- previews,
189
+ pi.registerCommand("actors-inspector", {
190
+ description: "Open the keyboard-driven actor inspector overlay",
191
+ handler: async (_args, ctx) => {
192
+ ctx.ui.setWidget("zz-pi-actors-comms", undefined);
193
+ await ctx.ui.custom<void>(
194
+ (tui, theme, _keybindings, done) =>
195
+ new InspectorOverlay.ActorInspectorOverlay({
196
+ done,
197
+ ownerId: getRunOwnerId(ctx),
198
+ stateRoot: Paths.EXTENSION_RUNTIME_PATHS.runStateRoot,
199
+ theme,
200
+ tui,
201
+ }),
202
+ {
203
+ overlay: true,
204
+ overlayOptions: {
205
+ anchor: "center",
206
+ width: "94%",
207
+ minWidth: 72,
208
+ maxHeight: "94%",
209
+ margin: 1,
210
+ },
211
+ },
200
212
  );
201
- if (result.update) updateRunUi(ctx);
202
- ctx.ui.notify(result.notify, result.type);
203
213
  },
204
214
  });
205
215
  pi.on("before_agent_start", async (event) => ({