@llblab/pi-actors 0.20.0 → 0.20.2
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/BACKLOG.md +33 -0
- package/CHANGELOG.md +14 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +374 -0
- package/dist/lib/actor-rooms.js +13 -10
- package/dist/lib/paths.js +6 -1
- package/index.js +19 -0
- package/lib/actor-rooms.ts +12 -8
- package/lib/paths.ts +5 -1
- package/package.json +5 -3
- package/skills/actors/SKILL.md +1 -1
- package/skills/swarm/SKILL.md +1 -1
package/BACKLOG.md
CHANGED
|
@@ -2,6 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
## Open Work
|
|
4
4
|
|
|
5
|
+
### Branch Inbox Retention and Transition Scaling
|
|
6
|
+
|
|
7
|
+
- Priority: Medium.
|
|
8
|
+
- Goal: Keep direct branch message queues reliable for long-lived interactive branch runners without unbounded rewrite amplification.
|
|
9
|
+
- Direction:
|
|
10
|
+
- Evaluate current whole-file branch inbox status rewrites under realistic long-lived direct-message workloads.
|
|
11
|
+
- Consider bounded retention, compaction, or append-only transition logs for `queued` / `claimed` / `handled` / `failed` state changes while preserving stable message IDs and exact-once claim semantics.
|
|
12
|
+
- Keep branch-local inbox append/status mutations lock-guarded and preserve inspector visibility for unread/current-branch filters.
|
|
13
|
+
- Exit:
|
|
14
|
+
- A documented decision or implementation explains how branch inboxes scale for persistent runners and proves existing direct-message semantics remain compatible.
|
|
15
|
+
|
|
16
|
+
### Installed Recipe Trust Boundary Hardening
|
|
17
|
+
|
|
18
|
+
- Priority: Medium.
|
|
19
|
+
- Goal: Keep recipe-library growth local-first without letting operator muscle memory become an accidental sandbox bypass.
|
|
20
|
+
- Direction:
|
|
21
|
+
- Review packaged recipes, examples, and docs for destructive or external side effects and ensure they require explicit paths, typed args, narrow helper scripts, and clear operator gates.
|
|
22
|
+
- Keep warnings framed as diagnostics, not a security boundary.
|
|
23
|
+
- Prefer small audited helper scripts over broad shell templates when recipes touch files, processes, networks, or external services.
|
|
24
|
+
- Exit:
|
|
25
|
+
- A trust-boundary review confirms packaged recipes and docs preserve the current local-first/not-sandbox-first contract, with any needed hardening captured in tests or docs.
|
|
26
|
+
|
|
27
|
+
### Direct Branch Message Consumption Semantics
|
|
28
|
+
|
|
29
|
+
- Priority: Medium.
|
|
30
|
+
- Goal: Make it impossible to misunderstand branch inbox delivery as universal active delivery without a consuming coordinator or runner protocol.
|
|
31
|
+
- Direction:
|
|
32
|
+
- Audit README, actor-message docs, async-run docs, actors skill, and recipe guidance for branch inbox wording.
|
|
33
|
+
- Clarify that direct branch messages are queued and become active work only when the relevant coordinator/runner claims, injects, handles, or fails them.
|
|
34
|
+
- Add or update a bounded smoke scenario that demonstrates queued direct messages, claim/handle transitions, and inspector visibility.
|
|
35
|
+
- Exit:
|
|
36
|
+
- Public docs and tests show both halves of direct branch delivery: durable branch-local queueing and explicit worker consumption semantics.
|
|
37
|
+
|
|
5
38
|
### Actor Rooms, Roster, and Cross-Branch Messaging
|
|
6
39
|
|
|
7
40
|
- Priority: High.
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.20.2: Installed Extension Entrypoint Hotfix
|
|
4
|
+
|
|
5
|
+
- `[Packaging]` Added a JavaScript extension entrypoint wrapper and changed package metadata to load `./index.js`, so npm-installed packages import compiled `dist/index.js` instead of asking Node to strip `index.ts` under `node_modules`. Source checkouts still fall back to `index.ts` before a local build exists.
|
|
6
|
+
- `[Build]` Extended the compiled runtime build to emit `dist/index.js` alongside `dist/lib/*.js`, keeping extension entrypoint imports and script runtime imports on the same installed-package path model.
|
|
7
|
+
- `[Rooms]` Fixed immediate room append results to report the true persisted room message count after long timelines instead of the default 40-message preview length; `appendRoomMessage`, existing-member room joins, and `getRoomStatus()` now share the same line-count helper.
|
|
8
|
+
- `[Tests]` Added installed-package coverage that imports the extension entrypoint from package metadata without TypeScript stripping, plus room-count regression coverage beyond the default preview limit.
|
|
9
|
+
- `[Package]` Bumped package metadata and packaged skill metadata to `0.20.2` for the hotfix release.
|
|
10
|
+
|
|
11
|
+
## 0.20.1: Installed Packaged Recipe Root Hotfix
|
|
12
|
+
|
|
13
|
+
- `[Recipe Imports]` Fixed installed compiled runtime path resolution so bare user recipe imports can fall back to the packaged standard-library `recipes/` directory instead of looking for a non-existent `dist/recipes` directory.
|
|
14
|
+
- `[Tests]` Added installed-package validation coverage for a user recipe that imports a packaged recipe by bare name, preserving the documented priority order for user, adjacent, and packaged recipes.
|
|
15
|
+
- `[Package]` Bumped package metadata and packaged skill metadata to `0.20.1` for the hotfix release.
|
|
16
|
+
|
|
3
17
|
## 0.20.0: Compiled Runtime Entrypoints
|
|
4
18
|
|
|
5
19
|
- `[Packaging]` Added a build step that emits compiled `dist/lib/*.js` and declaration files from the TypeScript runtime modules, with relative `.ts` imports rewritten to `.js` for installed package execution.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-actors — actor runtime and persistent local tool registry for pi.
|
|
3
|
+
* Zones: composition root, pi agent, actor runtime
|
|
4
|
+
*
|
|
5
|
+
* Wraps command templates as callable pi tools, stores durable user tools as recipe files, and exposes actor orchestration across reloads and sessions.
|
|
6
|
+
*/
|
|
7
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
8
|
+
export default function toolRegistryExtension(pi: ExtensionAPI): void;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-actors — actor runtime and persistent local tool registry for pi.
|
|
3
|
+
* Zones: composition root, pi agent, actor runtime
|
|
4
|
+
*
|
|
5
|
+
* Wraps command templates as callable pi tools, stores durable user tools as recipe files, and exposes actor orchestration across reloads and sessions.
|
|
6
|
+
*/
|
|
7
|
+
import { existsSync, readdirSync, watch } from "node:fs";
|
|
8
|
+
import * as ActorInspectorTui from "./lib/actor-inspector-tui.js";
|
|
9
|
+
import * as CommandTemplates from "./lib/command-templates.js";
|
|
10
|
+
import * as Observability from "./lib/observability.js";
|
|
11
|
+
import * as Paths from "./lib/paths.js";
|
|
12
|
+
import * as Prompts from "./lib/prompts.js";
|
|
13
|
+
import * as Runtime from "./lib/runtime.js";
|
|
14
|
+
import * as Temp from "./lib/temp.js";
|
|
15
|
+
import * as Tools from "./lib/tools.js";
|
|
16
|
+
const CONFIG_PATH = Paths.getConfigPath();
|
|
17
|
+
const TEMP_DIR = Paths.getExtensionTmpDir();
|
|
18
|
+
const RUN_STATE_ROOT = Paths.getRunStateRoot();
|
|
19
|
+
const RESERVED_TOOL_NAMES = new Set([
|
|
20
|
+
"read",
|
|
21
|
+
"write",
|
|
22
|
+
"edit",
|
|
23
|
+
"bash",
|
|
24
|
+
"find",
|
|
25
|
+
"grep",
|
|
26
|
+
"ls",
|
|
27
|
+
"register_tool",
|
|
28
|
+
"message",
|
|
29
|
+
"spawn",
|
|
30
|
+
"inspect",
|
|
31
|
+
]);
|
|
32
|
+
export default function toolRegistryExtension(pi) {
|
|
33
|
+
let runsAnimationInterval;
|
|
34
|
+
let runsNotifyTimeout;
|
|
35
|
+
let recipeReloadTimeout;
|
|
36
|
+
let recipeRootWatcher;
|
|
37
|
+
let stateRootWatcher;
|
|
38
|
+
const runDirWatchers = new Map();
|
|
39
|
+
const observedRuns = new Map();
|
|
40
|
+
const observedRunEventLines = new Map();
|
|
41
|
+
let runStatusFrame = 0;
|
|
42
|
+
let communicationWidgetVisible = false;
|
|
43
|
+
let actorInspectorRows = 12;
|
|
44
|
+
let actorInspectorChannels;
|
|
45
|
+
let actorInspectorMention;
|
|
46
|
+
let actorInspectorBranch;
|
|
47
|
+
let actorInspectorUnreadOnly = false;
|
|
48
|
+
let actorInspectorRoomLimitPerRun = 12;
|
|
49
|
+
let selectedInspectorSequence;
|
|
50
|
+
let recipeWatcherFailureNotified = false;
|
|
51
|
+
const getRunOwnerId = (ctx) => ctx.sessionManager.getSessionId();
|
|
52
|
+
const updateRunUi = (ctx, notify = false) => {
|
|
53
|
+
const ownerId = getRunOwnerId(ctx);
|
|
54
|
+
const summary = Observability.summarizeRuns(undefined, ownerId);
|
|
55
|
+
const status = Observability.renderRunStatus(summary, runStatusFrame++);
|
|
56
|
+
ctx.ui.setStatus("zz-pi-actors-runs", status ? ctx.ui.theme.fg("dim", status) : undefined);
|
|
57
|
+
ctx.ui.setWidget("zz-pi-actors-comms", communicationWidgetVisible
|
|
58
|
+
? () => {
|
|
59
|
+
const style = {
|
|
60
|
+
actor: (text) => ctx.ui.theme.fg("accent", text),
|
|
61
|
+
muted: (text) => ctx.ui.theme.fg("dim", text),
|
|
62
|
+
preview: (text) => ctx.ui.theme.fg("text", text),
|
|
63
|
+
stripe: (text) => text,
|
|
64
|
+
stripeAlt: (text) => ctx.ui.theme.bg("customMessageBg", text),
|
|
65
|
+
target: (text) => ctx.ui.theme.fg("success", text),
|
|
66
|
+
type: (text) => ctx.ui.theme.fg("warning", text),
|
|
67
|
+
};
|
|
68
|
+
return {
|
|
69
|
+
invalidate() { },
|
|
70
|
+
render(width) {
|
|
71
|
+
const previews = ActorInspectorTui.readActorInspectorPreviews(RUN_STATE_ROOT, actorInspectorRows, {
|
|
72
|
+
channels: actorInspectorChannels,
|
|
73
|
+
currentRunOnly: true,
|
|
74
|
+
branch: actorInspectorBranch,
|
|
75
|
+
mention: actorInspectorMention,
|
|
76
|
+
ownerId,
|
|
77
|
+
roomLimitPerRun: actorInspectorRoomLimitPerRun,
|
|
78
|
+
unreadOnly: actorInspectorUnreadOnly,
|
|
79
|
+
});
|
|
80
|
+
const rows = (selectedInspectorSequence !== undefined
|
|
81
|
+
? ActorInspectorTui.renderInspectorItemView(previews, width, style, { sequence: selectedInspectorSequence })
|
|
82
|
+
: ActorInspectorTui.renderInspectorWidget(previews, width, style)) ?? [];
|
|
83
|
+
const run = previews[0]?.run;
|
|
84
|
+
const roster = run
|
|
85
|
+
? ActorInspectorTui.renderInspectorRosterPanel(ActorInspectorTui.readActorInspectorRoster(RUN_STATE_ROOT, run), width, style)
|
|
86
|
+
: undefined;
|
|
87
|
+
return roster ? [...roster, ...rows] : rows;
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
: undefined, { placement: "belowEditor" });
|
|
92
|
+
const transitions = Observability.detectRunTransitions(observedRuns, summary);
|
|
93
|
+
const outboxEvents = Observability.detectRunOutboxEvents(observedRunEventLines, summary);
|
|
94
|
+
if (!notify)
|
|
95
|
+
return;
|
|
96
|
+
for (const transition of transitions) {
|
|
97
|
+
if (!Observability.shouldNotifyRunTransition(transition))
|
|
98
|
+
continue;
|
|
99
|
+
const text = Observability.formatRunTransitionMessage(transition);
|
|
100
|
+
const notificationType = Observability.getRunTransitionNotificationType(transition);
|
|
101
|
+
ctx.ui.notify(text, notificationType);
|
|
102
|
+
if (!Observability.shouldSendRunTransitionFollowUp(transition))
|
|
103
|
+
continue;
|
|
104
|
+
pi.sendMessage({
|
|
105
|
+
customType: "pi-actors-run",
|
|
106
|
+
content: text,
|
|
107
|
+
display: true,
|
|
108
|
+
details: transition,
|
|
109
|
+
}, { deliverAs: "followUp", triggerTurn: true });
|
|
110
|
+
}
|
|
111
|
+
Observability.pruneRunObservationState(observedRuns, observedRunEventLines, summary, transitions.map((transition) => transition.run));
|
|
112
|
+
for (const event of outboxEvents) {
|
|
113
|
+
if (!Observability.shouldNotifyRunOutboxEvent(event))
|
|
114
|
+
continue;
|
|
115
|
+
const text = Observability.formatRunOutboxMessage(event);
|
|
116
|
+
const notificationType = Observability.getRunOutboxNotificationType(event);
|
|
117
|
+
ctx.ui.notify(text, notificationType);
|
|
118
|
+
if (!Observability.shouldSendRunOutboxFollowUp(event))
|
|
119
|
+
continue;
|
|
120
|
+
pi.sendMessage({
|
|
121
|
+
customType: "pi-actors-run-message",
|
|
122
|
+
content: text,
|
|
123
|
+
display: true,
|
|
124
|
+
details: event,
|
|
125
|
+
}, { deliverAs: "followUp", triggerTurn: true });
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
const closeRunWatchers = () => {
|
|
129
|
+
stateRootWatcher?.close();
|
|
130
|
+
stateRootWatcher = undefined;
|
|
131
|
+
for (const watcher of runDirWatchers.values())
|
|
132
|
+
watcher.close();
|
|
133
|
+
runDirWatchers.clear();
|
|
134
|
+
if (runsNotifyTimeout)
|
|
135
|
+
clearTimeout(runsNotifyTimeout);
|
|
136
|
+
runsNotifyTimeout = undefined;
|
|
137
|
+
};
|
|
138
|
+
const scheduleRunEventUpdate = (ctx) => {
|
|
139
|
+
if (runsNotifyTimeout)
|
|
140
|
+
clearTimeout(runsNotifyTimeout);
|
|
141
|
+
runsNotifyTimeout = setTimeout(() => {
|
|
142
|
+
refreshRunWatchers(ctx);
|
|
143
|
+
updateRunUi(ctx, true);
|
|
144
|
+
}, 50);
|
|
145
|
+
runsNotifyTimeout.unref?.();
|
|
146
|
+
};
|
|
147
|
+
const watchRunDir = (ctx, stateDir) => {
|
|
148
|
+
if (runDirWatchers.has(stateDir) || !existsSync(stateDir))
|
|
149
|
+
return;
|
|
150
|
+
try {
|
|
151
|
+
const watcher = watch(stateDir, () => scheduleRunEventUpdate(ctx));
|
|
152
|
+
watcher.on("error", () => {
|
|
153
|
+
watcher.close();
|
|
154
|
+
runDirWatchers.delete(stateDir);
|
|
155
|
+
});
|
|
156
|
+
runDirWatchers.set(stateDir, watcher);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
// Watching is best-effort; explicit inspect remains available.
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
function refreshRunWatchers(ctx) {
|
|
163
|
+
if (!existsSync(RUN_STATE_ROOT))
|
|
164
|
+
return;
|
|
165
|
+
if (!stateRootWatcher) {
|
|
166
|
+
try {
|
|
167
|
+
stateRootWatcher = watch(RUN_STATE_ROOT, () => scheduleRunEventUpdate(ctx));
|
|
168
|
+
stateRootWatcher.on("error", () => {
|
|
169
|
+
stateRootWatcher?.close();
|
|
170
|
+
stateRootWatcher = undefined;
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
// Watching is best-effort; explicit inspect remains available.
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (const entry of readdirSync(RUN_STATE_ROOT, { withFileTypes: true })) {
|
|
178
|
+
if (!entry.isDirectory())
|
|
179
|
+
continue;
|
|
180
|
+
watchRunDir(ctx, `${RUN_STATE_ROOT}/${entry.name}`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
const closeRecipeWatcher = () => {
|
|
184
|
+
recipeRootWatcher?.close();
|
|
185
|
+
recipeRootWatcher = undefined;
|
|
186
|
+
if (recipeReloadTimeout)
|
|
187
|
+
clearTimeout(recipeReloadTimeout);
|
|
188
|
+
recipeReloadTimeout = undefined;
|
|
189
|
+
};
|
|
190
|
+
const notifyRecipeWatcherFailure = (ctx) => {
|
|
191
|
+
if (recipeWatcherFailureNotified)
|
|
192
|
+
return;
|
|
193
|
+
recipeWatcherFailureNotified = true;
|
|
194
|
+
ctx.ui.notify("Recipe live reload watcher failed; restart the session or use register_tool again to refresh recipe tools.", "warning");
|
|
195
|
+
};
|
|
196
|
+
const scheduleRecipeReload = (ctx) => {
|
|
197
|
+
recipeWatcherFailureNotified = false;
|
|
198
|
+
if (recipeReloadTimeout)
|
|
199
|
+
clearTimeout(recipeReloadTimeout);
|
|
200
|
+
recipeReloadTimeout = setTimeout(() => {
|
|
201
|
+
runtime.loadTools(ctx);
|
|
202
|
+
ctx.ui.notify("Recipe tools refreshed from ~/.pi/agent/recipes", "info");
|
|
203
|
+
}, 150);
|
|
204
|
+
recipeReloadTimeout.unref?.();
|
|
205
|
+
};
|
|
206
|
+
const watchRecipeRoot = (ctx) => {
|
|
207
|
+
const recipeRoot = Paths.getRecipeRoot();
|
|
208
|
+
if (recipeRootWatcher || !existsSync(recipeRoot))
|
|
209
|
+
return;
|
|
210
|
+
try {
|
|
211
|
+
recipeRootWatcher = watch(recipeRoot, () => scheduleRecipeReload(ctx));
|
|
212
|
+
recipeRootWatcher.on("error", () => {
|
|
213
|
+
recipeRootWatcher?.close();
|
|
214
|
+
recipeRootWatcher = undefined;
|
|
215
|
+
notifyRecipeWatcherFailure(ctx);
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
notifyRecipeWatcherFailure(ctx);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const actorToolDefinitions = new Map();
|
|
223
|
+
const runtime = Runtime.createAutoToolsRuntime({
|
|
224
|
+
configPath: CONFIG_PATH,
|
|
225
|
+
exec: CommandTemplates.execCommandTemplate,
|
|
226
|
+
getActiveTools: () => pi.getActiveTools(),
|
|
227
|
+
getAllTools: () => pi.getAllTools(),
|
|
228
|
+
registerTool: (definition) => {
|
|
229
|
+
actorToolDefinitions.set(definition.name, definition);
|
|
230
|
+
pi.registerTool(definition);
|
|
231
|
+
},
|
|
232
|
+
reservedToolNames: RESERVED_TOOL_NAMES,
|
|
233
|
+
setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
|
|
234
|
+
});
|
|
235
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
236
|
+
await Temp.prepareExtensionTempDir(TEMP_DIR);
|
|
237
|
+
runtime.loadTools(ctx);
|
|
238
|
+
updateRunUi(ctx);
|
|
239
|
+
closeRunWatchers();
|
|
240
|
+
closeRecipeWatcher();
|
|
241
|
+
refreshRunWatchers(ctx);
|
|
242
|
+
watchRecipeRoot(ctx);
|
|
243
|
+
if (runsAnimationInterval)
|
|
244
|
+
clearInterval(runsAnimationInterval);
|
|
245
|
+
runsAnimationInterval = setInterval(() => updateRunUi(ctx, false), 1000);
|
|
246
|
+
runsAnimationInterval.unref?.();
|
|
247
|
+
});
|
|
248
|
+
pi.on("session_shutdown", async () => {
|
|
249
|
+
if (runsAnimationInterval)
|
|
250
|
+
clearInterval(runsAnimationInterval);
|
|
251
|
+
runsAnimationInterval = undefined;
|
|
252
|
+
closeRunWatchers();
|
|
253
|
+
closeRecipeWatcher();
|
|
254
|
+
});
|
|
255
|
+
pi.registerCommand("actors-inspector-toggle", {
|
|
256
|
+
description: "Toggle actor inspector widget; optional row count",
|
|
257
|
+
handler: async (args, ctx) => {
|
|
258
|
+
const raw = Array.isArray(args) ? args[0] : String(args ?? "");
|
|
259
|
+
if (String(raw).trim()) {
|
|
260
|
+
const rows = Number.parseInt(String(raw), 10);
|
|
261
|
+
if (!Number.isFinite(rows) || rows <= 0) {
|
|
262
|
+
ctx.ui.notify("Usage: /actors-inspector-toggle [rows] where rows > 0", "warning");
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
actorInspectorRows = rows;
|
|
266
|
+
actorInspectorRoomLimitPerRun = rows;
|
|
267
|
+
selectedInspectorSequence = undefined;
|
|
268
|
+
communicationWidgetVisible = true;
|
|
269
|
+
updateRunUi(ctx);
|
|
270
|
+
ctx.ui.notify(`Actor inspector rows ${rows}`, "info");
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (selectedInspectorSequence !== undefined) {
|
|
274
|
+
selectedInspectorSequence = undefined;
|
|
275
|
+
communicationWidgetVisible = true;
|
|
276
|
+
updateRunUi(ctx);
|
|
277
|
+
ctx.ui.notify("Actor inspector table", "info");
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (communicationWidgetVisible) {
|
|
281
|
+
communicationWidgetVisible = false;
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
actorInspectorRows = 12;
|
|
285
|
+
actorInspectorRoomLimitPerRun = 12;
|
|
286
|
+
communicationWidgetVisible = true;
|
|
287
|
+
}
|
|
288
|
+
updateRunUi(ctx);
|
|
289
|
+
ctx.ui.notify(`Actor inspector ${communicationWidgetVisible ? "shown" : "hidden"}`, "info");
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
pi.registerCommand("actors-inspector-filter", {
|
|
293
|
+
description: "Filter actor inspector rows: all, room, direct, broadcast, unread, branch <name>, mention <text>",
|
|
294
|
+
handler: async (args, ctx) => {
|
|
295
|
+
const parts = Array.isArray(args)
|
|
296
|
+
? args.map(String)
|
|
297
|
+
: String(args ?? "").split(/\s+/);
|
|
298
|
+
const mode = (parts[0] ?? "").trim().toLowerCase();
|
|
299
|
+
if (!mode || mode === "all" || mode === "clear") {
|
|
300
|
+
actorInspectorChannels = undefined;
|
|
301
|
+
actorInspectorMention = undefined;
|
|
302
|
+
actorInspectorBranch = undefined;
|
|
303
|
+
actorInspectorUnreadOnly = false;
|
|
304
|
+
}
|
|
305
|
+
else if (mode === "room" || mode === "direct" || mode === "broadcast") {
|
|
306
|
+
actorInspectorChannels = [mode];
|
|
307
|
+
actorInspectorMention = undefined;
|
|
308
|
+
}
|
|
309
|
+
else if (mode === "unread") {
|
|
310
|
+
actorInspectorUnreadOnly = true;
|
|
311
|
+
}
|
|
312
|
+
else if (mode === "branch" || mode === "current-branch") {
|
|
313
|
+
const branch = parts.slice(1).join(" ").trim();
|
|
314
|
+
if (!branch) {
|
|
315
|
+
ctx.ui.notify(`Usage: /actors-inspector-filter ${mode} <branch-name>`, "warning");
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
actorInspectorBranch = branch;
|
|
319
|
+
}
|
|
320
|
+
else if (mode === "mention") {
|
|
321
|
+
const mention = parts.slice(1).join(" ").trim();
|
|
322
|
+
if (!mention) {
|
|
323
|
+
ctx.ui.notify("Usage: /actors-inspector-filter mention <text>", "warning");
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
actorInspectorChannels = undefined;
|
|
327
|
+
actorInspectorMention = mention;
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
ctx.ui.notify("Usage: /actors-inspector-filter all|room|direct|broadcast|unread|branch <name>|mention <text>", "warning");
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
selectedInspectorSequence = undefined;
|
|
334
|
+
communicationWidgetVisible = true;
|
|
335
|
+
updateRunUi(ctx);
|
|
336
|
+
ctx.ui.notify(`Actor inspector filter ${mode || "all"}`, "info");
|
|
337
|
+
},
|
|
338
|
+
});
|
|
339
|
+
pi.registerCommand("actors-inspect", {
|
|
340
|
+
description: "Inspect actor message by visible number",
|
|
341
|
+
handler: async (args, ctx) => {
|
|
342
|
+
const raw = Array.isArray(args) ? args[0] : String(args ?? "");
|
|
343
|
+
const sequence = Number.parseInt(String(raw), 10);
|
|
344
|
+
if (!Number.isFinite(sequence) || sequence <= 0) {
|
|
345
|
+
ctx.ui.notify("Usage: /actors-inspect <number>", "warning");
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
selectedInspectorSequence = sequence;
|
|
349
|
+
communicationWidgetVisible = true;
|
|
350
|
+
updateRunUi(ctx);
|
|
351
|
+
ctx.ui.notify(`Actor inspect item ${sequence}`, "info");
|
|
352
|
+
},
|
|
353
|
+
});
|
|
354
|
+
pi.on("before_agent_start", async (event) => ({
|
|
355
|
+
systemPrompt: `${event.systemPrompt}\n\n${Prompts.ONBOARDING_SYSTEM_PROMPT}`,
|
|
356
|
+
}));
|
|
357
|
+
pi.registerTool(Tools.createRegisterToolDefinition({
|
|
358
|
+
configPath: CONFIG_PATH,
|
|
359
|
+
getActiveTools: () => pi.getActiveTools(),
|
|
360
|
+
getExternalToolConflict: runtime.getExternalToolConflict,
|
|
361
|
+
getTools: runtime.getTools,
|
|
362
|
+
notify: runtime.notify,
|
|
363
|
+
registerRuntimeTool: runtime.registerRuntimeTool,
|
|
364
|
+
reservedToolNames: RESERVED_TOOL_NAMES,
|
|
365
|
+
setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
|
|
366
|
+
}));
|
|
367
|
+
pi.registerTool(Tools.createSpawnToolDefinition());
|
|
368
|
+
pi.registerTool(Tools.createActorMessageToolDefinition({
|
|
369
|
+
getTool: (name) => actorToolDefinitions.get(name),
|
|
370
|
+
}));
|
|
371
|
+
pi.registerTool(Tools.createInspectToolDefinition({
|
|
372
|
+
getTool: (name) => actorToolDefinitions.get(name),
|
|
373
|
+
}));
|
|
374
|
+
}
|
package/dist/lib/actor-rooms.js
CHANGED
|
@@ -151,6 +151,16 @@ function readJsonlLineCount(file) {
|
|
|
151
151
|
fs.closeSync(fd);
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
|
+
function readRoomMessageCount(stateDir, room) {
|
|
155
|
+
try {
|
|
156
|
+
return readJsonlLineCount(messagesFile(stateDir, room));
|
|
157
|
+
}
|
|
158
|
+
catch (error) {
|
|
159
|
+
if (error.code === "ENOENT")
|
|
160
|
+
return 0;
|
|
161
|
+
throw error;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
154
164
|
function readJsonlTailLines(file, limit) {
|
|
155
165
|
const lineLimit = Math.max(1, limit);
|
|
156
166
|
const stat = fs.statSync(file);
|
|
@@ -318,7 +328,7 @@ export function appendRoomMessage(stateDir, room, message) {
|
|
|
318
328
|
}
|
|
319
329
|
}
|
|
320
330
|
return {
|
|
321
|
-
message_count:
|
|
331
|
+
message_count: readRoomMessageCount(stateDir, room),
|
|
322
332
|
room,
|
|
323
333
|
roster_count: Object.keys(roster).length,
|
|
324
334
|
sent: true,
|
|
@@ -361,14 +371,7 @@ export function readRoomMessagePreviews(stateDir, room, limit = 40) {
|
|
|
361
371
|
}));
|
|
362
372
|
}
|
|
363
373
|
export function getRoomStatus(stateDir, room) {
|
|
364
|
-
|
|
365
|
-
try {
|
|
366
|
-
messageCount = readJsonlLineCount(messagesFile(stateDir, room));
|
|
367
|
-
}
|
|
368
|
-
catch (error) {
|
|
369
|
-
if (error.code !== "ENOENT")
|
|
370
|
-
throw error;
|
|
371
|
-
}
|
|
374
|
+
const messageCount = readRoomMessageCount(stateDir, room);
|
|
372
375
|
const [last] = readRoomMessages(stateDir, room, 1);
|
|
373
376
|
return {
|
|
374
377
|
...(last
|
|
@@ -388,7 +391,7 @@ export function ensureRoomMember(stateDir, run, room, address, body, summary) {
|
|
|
388
391
|
const roster = readRoomRoster(stateDir, room);
|
|
389
392
|
if (roster[address]) {
|
|
390
393
|
return {
|
|
391
|
-
message_count:
|
|
394
|
+
message_count: readRoomMessageCount(stateDir, room),
|
|
392
395
|
room,
|
|
393
396
|
roster_count: Object.keys(roster).length,
|
|
394
397
|
sent: true,
|
package/dist/lib/paths.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Zones: paths, registry config, temp directory
|
|
4
4
|
* Owns agent directory, tools config, recipe root, and actor run state root resolution
|
|
5
5
|
*/
|
|
6
|
+
import { existsSync } from "node:fs";
|
|
6
7
|
import { homedir } from "node:os";
|
|
7
8
|
import { dirname, join, resolve } from "node:path";
|
|
8
9
|
import { fileURLToPath } from "node:url";
|
|
@@ -24,5 +25,9 @@ export function getRecipeRoot(agentDir = getAgentDir()) {
|
|
|
24
25
|
return join(agentDir, "recipes");
|
|
25
26
|
}
|
|
26
27
|
export function getPackagedRecipeRoot() {
|
|
27
|
-
|
|
28
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
29
|
+
const compiledRoot = resolve(here, "..", "..", "recipes");
|
|
30
|
+
if (existsSync(compiledRoot))
|
|
31
|
+
return compiledRoot;
|
|
32
|
+
return resolve(here, "..", "recipes");
|
|
28
33
|
}
|
package/index.js
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime extension entrypoint wrapper.
|
|
3
|
+
*
|
|
4
|
+
* Installed npm packages load compiled JS from dist so Node does not try to strip
|
|
5
|
+
* TypeScript under node_modules. Source checkouts fall back to index.ts for local
|
|
6
|
+
* development before dist has been built.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { dirname, resolve } from "node:path";
|
|
11
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
12
|
+
|
|
13
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const compiledEntry = resolve(here, "dist", "index.js");
|
|
15
|
+
const sourceEntry = resolve(here, "index.ts");
|
|
16
|
+
const entry = existsSync(compiledEntry) ? compiledEntry : sourceEntry;
|
|
17
|
+
const entryModule = await import(pathToFileURL(entry).href);
|
|
18
|
+
|
|
19
|
+
export default entryModule.default;
|
package/lib/actor-rooms.ts
CHANGED
|
@@ -241,6 +241,15 @@ function readJsonlLineCount(file: string): number {
|
|
|
241
241
|
}
|
|
242
242
|
}
|
|
243
243
|
|
|
244
|
+
function readRoomMessageCount(stateDir: string, room: string): number {
|
|
245
|
+
try {
|
|
246
|
+
return readJsonlLineCount(messagesFile(stateDir, room));
|
|
247
|
+
} catch (error) {
|
|
248
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return 0;
|
|
249
|
+
throw error;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
244
253
|
function readJsonlTailLines(file: string, limit: number): string[] {
|
|
245
254
|
const lineLimit = Math.max(1, limit);
|
|
246
255
|
const stat = fs.statSync(file);
|
|
@@ -446,7 +455,7 @@ export function appendRoomMessage(
|
|
|
446
455
|
}
|
|
447
456
|
}
|
|
448
457
|
return {
|
|
449
|
-
message_count:
|
|
458
|
+
message_count: readRoomMessageCount(stateDir, room),
|
|
450
459
|
room,
|
|
451
460
|
roster_count: Object.keys(roster).length,
|
|
452
461
|
sent: true,
|
|
@@ -496,12 +505,7 @@ export function readRoomMessagePreviews(
|
|
|
496
505
|
}
|
|
497
506
|
|
|
498
507
|
export function getRoomStatus(stateDir: string, room: string): RoomStatus {
|
|
499
|
-
|
|
500
|
-
try {
|
|
501
|
-
messageCount = readJsonlLineCount(messagesFile(stateDir, room));
|
|
502
|
-
} catch (error) {
|
|
503
|
-
if ((error as NodeJS.ErrnoException).code !== "ENOENT") throw error;
|
|
504
|
-
}
|
|
508
|
+
const messageCount = readRoomMessageCount(stateDir, room);
|
|
505
509
|
const [last] = readRoomMessages(stateDir, room, 1);
|
|
506
510
|
return {
|
|
507
511
|
...(last
|
|
@@ -529,7 +533,7 @@ export function ensureRoomMember(
|
|
|
529
533
|
const roster = readRoomRoster(stateDir, room);
|
|
530
534
|
if (roster[address]) {
|
|
531
535
|
return {
|
|
532
|
-
message_count:
|
|
536
|
+
message_count: readRoomMessageCount(stateDir, room),
|
|
533
537
|
room,
|
|
534
538
|
roster_count: Object.keys(roster).length,
|
|
535
539
|
sent: true,
|
package/lib/paths.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Owns agent directory, tools config, recipe root, and actor run state root resolution
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
+
import { existsSync } from "node:fs";
|
|
7
8
|
import { homedir } from "node:os";
|
|
8
9
|
import { dirname, join, resolve } from "node:path";
|
|
9
10
|
import { fileURLToPath } from "node:url";
|
|
@@ -36,5 +37,8 @@ export function getRecipeRoot(agentDir = getAgentDir()): string {
|
|
|
36
37
|
}
|
|
37
38
|
|
|
38
39
|
export function getPackagedRecipeRoot(): string {
|
|
39
|
-
|
|
40
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
41
|
+
const compiledRoot = resolve(here, "..", "..", "recipes");
|
|
42
|
+
if (existsSync(compiledRoot)) return compiledRoot;
|
|
43
|
+
return resolve(here, "..", "recipes");
|
|
40
44
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-actors",
|
|
3
|
-
"version": "0.20.
|
|
3
|
+
"version": "0.20.2",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Local Actor Kernel for Pi",
|
|
6
6
|
"keywords": [
|
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
"prepack": "npm run build"
|
|
34
34
|
},
|
|
35
35
|
"files": [
|
|
36
|
+
"index.js",
|
|
36
37
|
"index.ts",
|
|
37
38
|
"lib",
|
|
38
39
|
"scripts",
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
],
|
|
49
50
|
"pi": {
|
|
50
51
|
"extensions": [
|
|
51
|
-
"./index.
|
|
52
|
+
"./index.js"
|
|
52
53
|
],
|
|
53
54
|
"skills": [
|
|
54
55
|
"./skills/actors/SKILL.md",
|
|
@@ -57,7 +58,8 @@
|
|
|
57
58
|
"image": "https://github.com/llblab/pi-actors/raw/main/banner.jpg"
|
|
58
59
|
},
|
|
59
60
|
"peerDependencies": {
|
|
60
|
-
"@earendil-works/pi-coding-agent": "*"
|
|
61
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
62
|
+
"@earendil-works/pi-tui": "*"
|
|
61
63
|
},
|
|
62
64
|
"devDependencies": {
|
|
63
65
|
"@types/node": "latest",
|
package/skills/actors/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: actors
|
|
3
3
|
description: Highest-density practical guide for pi-actors. Read this skill whenever prompt and tools are not enough for spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.20.
|
|
5
|
+
version: 0.20.2
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Actors (pi-actors)
|
package/skills/swarm/SKILL.md
CHANGED