@llblab/pi-actors 0.34.0 → 0.35.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/BACKLOG.md +2 -51
- package/CHANGELOG.md +17 -20
- package/README.md +10 -1
- package/dist/lib/prompts.d.ts +1 -0
- package/dist/lib/prompts.js +1 -0
- package/dist/lib/recipes-discovery.js +25 -0
- package/dist/lib/registry.d.ts +3 -0
- package/dist/lib/registry.js +60 -1
- package/dist/lib/tools-message.js +85 -3
- package/dist/lib/tools-register.js +1 -0
- package/dist/scripts/actor-worker.mjs +5 -1
- package/dist/skills/actors/SKILL.md +1 -1
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/tool-registry.md +2 -2
- package/lib/prompts.ts +2 -0
- package/lib/recipes-discovery.ts +29 -0
- package/lib/registry.ts +94 -1
- package/lib/tools-message.ts +92 -6
- package/lib/tools-register.ts +1 -0
- package/package.json +1 -1
- package/scripts/actor-worker.mjs +5 -1
- package/skills/actors/SKILL.md +1 -1
- package/skills/swarm/SKILL.md +1 -1
package/BACKLOG.md
CHANGED
|
@@ -53,55 +53,6 @@ No open hotfix items.
|
|
|
53
53
|
|
|
54
54
|
The backlog is intentionally pruned to the 20% of work most likely to deliver 80% of value for `pi-actors` as a local actor kernel. Bias toward consolidation, smaller public surface area, and reliability over new feature breadth.
|
|
55
55
|
|
|
56
|
-
### M-15 Worker Stale-Claim Dogfood
|
|
57
|
-
|
|
58
|
-
- Priority: Medium.
|
|
59
|
-
- Status: Planned.
|
|
60
|
-
- Goal: Validate and harden actor-worker v2 stale-claim visibility under intentionally stale claimed branch messages.
|
|
61
|
-
- Why now: M-09 exposed `stale_claims`; real dogfood should verify the operator can diagnose stuck claimed work before adding recovery policy.
|
|
62
|
-
- Direction:
|
|
63
|
-
- Create deterministic stale claimed branch inbox fixtures or smoke tests.
|
|
64
|
-
- Verify `worker-status.json`, room events, and inspect surfaces make stale claims visible.
|
|
65
|
-
- Defer auto-recovery unless workflow evidence proves it is safe.
|
|
66
|
-
- Acceptance:
|
|
67
|
-
- Stale claims are reproducible and visible in worker status.
|
|
68
|
-
- Tests cover stale-claim counting without adding scheduler/broker policy.
|
|
69
|
-
|
|
70
|
-
### M-17 Message Delivery Outcome Contract
|
|
71
|
-
|
|
72
|
-
- Priority: High.
|
|
73
|
-
- Status: Planned.
|
|
74
|
-
- Goal: Normalize `message` results so operators can distinguish delivered, queued, persisted, forwarded, unsupported, and ownership-denied outcomes.
|
|
75
|
-
- Why now: Branch message UX already treats durable branch mailbox persistence as a successful queued outcome when a parent endpoint is unavailable; that local fix should become a consistent message-result membrane.
|
|
76
|
-
- Direction:
|
|
77
|
-
- Define compact delivery fields: `queued`, `delivered`, `persisted`, `forwarded`, `consumer`, `reason`, and `hint`.
|
|
78
|
-
- Apply the shape to `run:<id>`, `branch:<run>/<branch>`, `room:<run>`, `coordinator`, `session:`, and `tool:<name>` where meaningful.
|
|
79
|
-
- Reuse M-14 session mismatch shape for ownership-denied outcomes.
|
|
80
|
-
- Do not claim guaranteed live consumption unless a known consumer exists.
|
|
81
|
-
- Do not add a broker, distributed delivery semantics, or a new public noun.
|
|
82
|
-
- Acceptance:
|
|
83
|
-
- Branch messages clearly report queued/persisted state and known worker-consumer state where available.
|
|
84
|
-
- Room messages distinguish timeline append success from forwarded branch-targeted copies.
|
|
85
|
-
- Tests cover at least run, branch, room, coordinator, and ownership-denied outcomes.
|
|
86
|
-
|
|
87
|
-
### M-18 Draft Recipe Promotion UX
|
|
88
|
-
|
|
89
|
-
- Priority: High.
|
|
90
|
-
- Status: Planned.
|
|
91
|
-
- Goal: Make successful ad hoc actor patterns easy to promote manually from draft memory into active user recipe memory.
|
|
92
|
-
- Why now: Draft recipes under `~/.pi/agent/recipes/drafts` are replayable but intentionally not active tools, and the two-stage memory model needs an explicit operator-gated promotion path.
|
|
93
|
-
- Direction:
|
|
94
|
-
- List draft recipes with source run, timestamp, fingerprint, description/template preview, and validation status.
|
|
95
|
-
- Promote a selected draft to `~/.pi/agent/recipes/<name>.json` only through an explicit action or explicit tool argument.
|
|
96
|
-
- Run recipe validation/doctor before writing and expose collision/shadowing diagnostics.
|
|
97
|
-
- Preserve draft files unless deletion is explicitly requested.
|
|
98
|
-
- Prefer extending existing registry/tool surfaces over adding a new public noun.
|
|
99
|
-
- Acceptance:
|
|
100
|
-
- Draft recipes remain non-tools until promotion.
|
|
101
|
-
- Promotion writes atomically and never auto-promotes.
|
|
102
|
-
- Tests cover valid promotion, invalid draft, name collision, and packaged-recipe shadowing.
|
|
103
|
-
- Docs explain draft memory vs active tool memory in one compact section.
|
|
104
|
-
|
|
105
56
|
### M-19 Recipe Doctor Risk Labels v2
|
|
106
57
|
|
|
107
58
|
- Priority: Medium.
|
|
@@ -185,7 +136,7 @@ These are valid ideas but not current focus. Reintroduce only with concrete evid
|
|
|
185
136
|
## Suggested Milestone Order
|
|
186
137
|
|
|
187
138
|
```text
|
|
188
|
-
Next milestone: M-
|
|
189
|
-
Then: M-
|
|
139
|
+
Next milestone: M-19 Recipe Doctor Risk Labels v2.
|
|
140
|
+
Then: M-20 Runtime Recipe Triage.
|
|
190
141
|
Small cleanup lane: continue opportunistic domain polish only when a real ownership boundary appears.
|
|
191
142
|
```
|
package/CHANGELOG.md
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## Unreleased
|
|
4
|
+
|
|
5
|
+
## 0.35.0: Draft Recipe Promotion UX
|
|
6
|
+
|
|
7
|
+
- `[Recipes]` Completed M-18 Draft Recipe Promotion UX: `inspect target=recipes view=summary verbose=true` now exposes draft timestamps, fingerprints, validation state, source run when known, and template previews, while `register_tool name=<tool> draft=<path>` promotes a validated draft into active recipe memory without deleting the draft and rejects collisions unless `update=true` is explicit.
|
|
8
|
+
|
|
9
|
+
## 0.34.1: Message Delivery Outcome Hotfix
|
|
10
|
+
|
|
11
|
+
- `[Dogfood]` Added a deterministic actor-worker stale-claim smoke covering an intentionally claimed branch inbox record; the worker now reports stale claim counts in both `worker-status.json` and its awaiting-assignment room event without adding auto-recovery or scheduler policy.
|
|
12
|
+
- `[Messages]` Completed the M-17 message delivery outcome contract by normalizing public message results with `delivered`, `persisted`, `queued`, `forwarded`, `consumer`, and `reason` fields across run, branch, room, coordinator, session, and tool destinations; room multicast now also exposes per-recipient branch delivery outcomes.
|
|
13
|
+
- `[Backlog]` Closed M-15 Worker Stale-Claim Dogfood and M-17 Message Delivery Outcome Contract after adding delivery-result coverage for run, branch, room, coordinator, session, tool, and ownership-denied paths.
|
|
14
|
+
|
|
3
15
|
## 0.34.0: Actor Kernel Domain Compression
|
|
4
16
|
|
|
5
|
-
- `[
|
|
6
|
-
- `[Domains]`
|
|
7
|
-
- `[
|
|
8
|
-
- `[
|
|
9
|
-
- `[
|
|
10
|
-
- `[Domains]` Added `tools-response.ts` for compact public tool responses and next-action rendering shared by spawn, inspect, and registered tool paths; moved recipe registry compact summaries and doctor/import next-action rendering into it.
|
|
11
|
-
- `[Domains]` Moved the public `inspect` actor tool behavior into `tools-inspect.ts`, including recipe registry inspection, room/session/tool/run views, and inspect-specific compact observation formatting.
|
|
12
|
-
- `[Domains]` Moved the public `spawn` actor tool behavior into `tools-spawn.ts`, including actor launch, draft recipe capture, shadowed recipe launch diagnostics, and spawn next-action feedback.
|
|
13
|
-
- `[Domains]` Removed the over-thin `tool-context.ts` extraction and kept tool execution context types local to the domains that use them.
|
|
14
|
-
- `[Domains]` Moved saved local capability execution into `tools-local.ts`, including generated schemas, argument usage hints, runtime value normalization, and async recipe launch behavior.
|
|
15
|
-
- `[Domains]` Moved public `register_tool` behavior into `tools-register.ts`, including the register schema and registry mutation bridge for persisted local capabilities.
|
|
16
|
-
- `[Domains]` Kept `tools.ts` as the public tool-family composition owner, moved decomposed behavior into `tools-*` subdomains, removed the redundant `actor-tools.ts` prefix because this package is already actor-scoped, and documented that any `tools-*` helper reused by non-tools domains must drop the prefix in the same slice.
|
|
17
|
-
- `[Domains]` Removed redundant internal `actor-` prefixes from core library domains: `inspector.ts`, `messages.ts`, `recipes-context.ts`, and `rooms.ts`; public recipe/script/docs names that intentionally expose actor terminology stay unchanged.
|
|
18
|
-
- `[Scripts]` Collapsed the one-off `run-executor.ts`, `worker.ts`, `validate-recipe.ts`, `recipe-utils.ts`, `locker.ts`, and `coordinator.ts` domains into their owning scripts; reusable lifecycle, room, mailbox, and recipe-reference primitives stay in `lib/`, while public scripts own their executable control loops directly.
|
|
19
|
-
- `[Domains]` Started decomposing the oversized `async-runs.ts` lifecycle aggregate into `runs-*` subdomains, moving artifact manifest handling to `runs-artifacts.ts`, durable run inbox locking/claim handling to `runs-mailbox.ts`, process-control signal helpers to `runs-control.ts`, outbox event parsing/payload formatting to `runs-outbox.ts`, run-state index discovery/rebuild logic to `runs-index.ts`, id normalization to `runs-identity.ts`, archive/prune retention behavior to `runs-retention.ts`, process identity/liveness checks to `runs-process.ts`, run message delivery to `runs-messages.ts`, status/log derivation to `runs-status.ts`, and start lock/reuse guards to `runs-start.ts` while preserving the `async-runs.ts` facade.
|
|
20
|
-
- `[Context]` Reversed the thin-script default: `scripts/*.mjs` should own script-only executable behavior, and behavior should move into `lib/` only for real non-script reuse or existing reusable domain ownership; packaging/tests/shim neatness alone no longer justify a lib domain.
|
|
21
|
-
- `[Context]` Polished domain ownership headers after the file moves so command templates, actor messages, rooms, recipe context, inspector previews, and mailbox loops describe their actual reasons to change without generic helper wording.
|
|
22
|
-
- `[Domains]` Renamed generic `output.ts` to `execution-output.ts` so registered-tool stdout/stderr truncation and temp artifact formatting are tied to the execution domain instead of a broad output bucket.
|
|
23
|
-
- `[Domains]` Renamed the recipe domain family from singular `recipe-*` to plural `recipes-*` (`recipes-references.ts`, `recipes-discovery.ts`, `recipes-usage.ts`, `recipes-context.ts`) to match the `tools-*` and `runs-*` family convention.
|
|
24
|
-
- `[Tests]` Extended installed-package contract coverage to assert stale renamed lib domains are absent from `dist/lib`, keeping packaged JS output aligned with the current domain names.
|
|
17
|
+
- `[Domains]` Compressed the actor kernel into explicit domain families: `tools.ts` remains the public tool-family owner while `tools-*` owns message, inspect, spawn, register, local execution, response, access, and mailbox-contract behavior; `async-runs.ts` remains the lifecycle facade while `runs-*` owns artifacts, mailbox, process control, delivery, outbox, index, retention, status, start guards, and identity internals.
|
|
18
|
+
- `[Domains]` Removed redundant internal `actor-` prefixes and renamed the recipe family to plural `recipes-*`, leaving public actor-named recipe/script/docs surfaces intact while making core library ownership match the `tools-*` and `runs-*` convention.
|
|
19
|
+
- `[Scripts]` Collapsed script-only runner, worker, validator, recipe-utils, locker, and coordinator library shims back into their owning `scripts/*.mjs` entrypoints; reusable lifecycle, room, mailbox-loop, command-template, and recipe-reference primitives remain in `lib/`.
|
|
20
|
+
- `[Context]` Reversed the thin-script default, polished ownership headers, and kept completed script-autonomy/domain-compression work in the changelog instead of the backlog.
|
|
21
|
+
- `[Tests]` Mirrored renamed domains in test filenames and extended installed-package contract coverage to ensure stale renamed lib domains are absent from `dist/lib` while packaged JS-only script execution still works.
|
|
25
22
|
|
|
26
23
|
## 0.33.0: Signal-First Compatibility Pruning
|
|
27
24
|
|
package/README.md
CHANGED
|
@@ -207,7 +207,8 @@ Rules:
|
|
|
207
207
|
- User recipes override same-name lower-priority recipes;
|
|
208
208
|
- Same-id JSON recipes shadow Markdown recipes in the same priority layer;
|
|
209
209
|
- Packaged recipes are standard-library components, not automatically installed operator policy;
|
|
210
|
-
-
|
|
210
|
+
- Draft recipes in `~/.pi/agent/recipes/drafts/` are replayable memory, not active tools;
|
|
211
|
+
- `register_tool` creates, updates, lists, deletes, or explicitly promotes draft recipe files through the normal agent interface.
|
|
211
212
|
|
|
212
213
|
Example foreground tool:
|
|
213
214
|
|
|
@@ -226,6 +227,14 @@ register_tool name=docs_review \
|
|
|
226
227
|
args="scope:path,model:string"
|
|
227
228
|
```
|
|
228
229
|
|
|
230
|
+
Promote a successful captured draft only after an explicit operator decision:
|
|
231
|
+
|
|
232
|
+
```text
|
|
233
|
+
register_tool name=docs_review draft=~/.pi/agent/recipes/drafts/spawned-run.json
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Promotion validates the draft, writes `~/.pi/agent/recipes/<name>.json`, preserves the draft, and rejects collisions unless `update=true` is supplied.
|
|
237
|
+
|
|
229
238
|
Inspect the discovered registry:
|
|
230
239
|
|
|
231
240
|
```text
|
package/dist/lib/prompts.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export declare const ONBOARDING_SYSTEM_PROMPT = "pi-actors quick model:\n- Local
|
|
|
10
10
|
export declare const REGISTER_TOOL_PARAM_DESCRIPTIONS: {
|
|
11
11
|
readonly name: "Tool name in snake_case (e.g., 'transcribe')";
|
|
12
12
|
readonly description: "Describe what the tool does for the LLM. Required unless deleting; omitted updates keep the old description.";
|
|
13
|
+
readonly draft: "Promote a draft recipe path from ~/.pi/agent/recipes/drafts into an active named recipe under ~/.pi/agent/recipes. Requires name; use update=true to overwrite.";
|
|
13
14
|
readonly async: "Set true for a co-located async template recipe. Omit for ordinary command templates or file-backed recipe references.";
|
|
14
15
|
readonly state_dir: "Optional async run state directory for a co-located template recipe.";
|
|
15
16
|
readonly template: "Command template with {arg} or {arg=default} placeholders, or a template recipe JSON path/name. With async, this is the co-located recipe body. Bare recipe names resolve under ~/.pi/agent/recipes. Omitted updates keep the old template. Empty string deletes the tool.";
|
package/dist/lib/prompts.js
CHANGED
|
@@ -30,6 +30,7 @@ export const ONBOARDING_SYSTEM_PROMPT = `pi-actors quick model:
|
|
|
30
30
|
export const REGISTER_TOOL_PARAM_DESCRIPTIONS = {
|
|
31
31
|
name: "Tool name in snake_case (e.g., 'transcribe')",
|
|
32
32
|
description: "Describe what the tool does for the LLM. Required unless deleting; omitted updates keep the old description.",
|
|
33
|
+
draft: "Promote a draft recipe path from ~/.pi/agent/recipes/drafts into an active named recipe under ~/.pi/agent/recipes. Requires name; use update=true to overwrite.",
|
|
33
34
|
async: "Set true for a co-located async template recipe. Omit for ordinary command templates or file-backed recipe references.",
|
|
34
35
|
state_dir: "Optional async run state directory for a co-located template recipe.",
|
|
35
36
|
template: "Command template with {arg} or {arg=default} placeholders, or a template recipe JSON path/name. With async, this is the co-located recipe body. Bare recipe names resolve under ~/.pi/agent/recipes. Omitted updates keep the old template. Empty string deletes the tool.",
|
|
@@ -425,14 +425,39 @@ export function getShadowedLaunchDiagnostic(result, id) {
|
|
|
425
425
|
reason: active.invalid ? "shadowed_invalid" : "shadowed_disabled",
|
|
426
426
|
};
|
|
427
427
|
}
|
|
428
|
+
function templatePreview(value) {
|
|
429
|
+
const rendered = typeof value === "string"
|
|
430
|
+
? value
|
|
431
|
+
: value === undefined
|
|
432
|
+
? undefined
|
|
433
|
+
: JSON.stringify(value);
|
|
434
|
+
return rendered && rendered.length > 120
|
|
435
|
+
? `${rendered.slice(0, 117)}...`
|
|
436
|
+
: rendered;
|
|
437
|
+
}
|
|
428
438
|
export function listDraftRecipes(root) {
|
|
429
439
|
return listRecipeFiles(root).map((path) => {
|
|
430
440
|
const id = RecipesReferences.getRecipeIdFromPath(path);
|
|
441
|
+
const bytes = readFileSync(path);
|
|
442
|
+
const stat = statSync(path);
|
|
431
443
|
const config = RecipesReferences.readRawRecipeConfig(path);
|
|
444
|
+
const resolved = RecipesReferences.readResolvedRecipeConfig(path);
|
|
445
|
+
const diagnostics = getRecipeConfigDiagnostics(path, resolved);
|
|
446
|
+
const sourceRun = String(config?.description ?? "").match(/spawn run ([^\s]+)/)?.[1];
|
|
447
|
+
const preview = templatePreview(config?.template);
|
|
432
448
|
return {
|
|
433
449
|
id,
|
|
434
450
|
path,
|
|
451
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
452
|
+
size: bytes.byteLength,
|
|
453
|
+
created_at: stat.birthtime.toISOString(),
|
|
454
|
+
modified_at: stat.mtime.toISOString(),
|
|
455
|
+
valid: Boolean(resolved),
|
|
456
|
+
diagnostics,
|
|
435
457
|
...(config?.description ? { description: config.description } : {}),
|
|
458
|
+
...(sourceRun ? { source_run: sourceRun } : {}),
|
|
459
|
+
...(config?.async !== undefined ? { async: config.async } : {}),
|
|
460
|
+
...(preview ? { template_preview: preview } : {}),
|
|
436
461
|
};
|
|
437
462
|
});
|
|
438
463
|
}
|
package/dist/lib/registry.d.ts
CHANGED
|
@@ -11,6 +11,7 @@ export interface RegisterToolInput {
|
|
|
11
11
|
async?: boolean;
|
|
12
12
|
state_dir?: string;
|
|
13
13
|
template?: CommandTemplates.CommandTemplateValue | null;
|
|
14
|
+
draft?: string;
|
|
14
15
|
args?: string;
|
|
15
16
|
update?: boolean;
|
|
16
17
|
values?: Record<string, unknown>;
|
|
@@ -20,6 +21,8 @@ export interface RegisterToolResultDetails {
|
|
|
20
21
|
async?: boolean;
|
|
21
22
|
config?: string;
|
|
22
23
|
defaults?: Record<string, string>;
|
|
24
|
+
draft?: string;
|
|
25
|
+
promoted?: boolean;
|
|
23
26
|
recipeName?: string;
|
|
24
27
|
state_dir?: string;
|
|
25
28
|
template?: CommandTemplates.CommandTemplateValue;
|
package/dist/lib/registry.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Owns register/update/delete validation, persistence, runtime side effects, and result payloads
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, mkdirSync, unlinkSync } from "node:fs";
|
|
7
|
-
import { dirname, join } from "node:path";
|
|
7
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
8
8
|
import * as CommandTemplates from "./command-templates.js";
|
|
9
9
|
import * as ExecutionOutput from "./execution-output.js";
|
|
10
10
|
import { writeJsonAtomic } from "./file-state.js";
|
|
@@ -32,6 +32,62 @@ function getRecipeRoot(deps) {
|
|
|
32
32
|
function getToolRecipePath(deps, name) {
|
|
33
33
|
return join(getRecipeRoot(deps), `${name}.json`);
|
|
34
34
|
}
|
|
35
|
+
function assertDraftPath(deps, draft) {
|
|
36
|
+
const draftRoot = resolve(getRecipeRoot(deps), "drafts");
|
|
37
|
+
const path = resolve(draft);
|
|
38
|
+
const relation = relative(draftRoot, path);
|
|
39
|
+
if (relation === "" ||
|
|
40
|
+
relation.startsWith("..") ||
|
|
41
|
+
resolve(relation) === relation) {
|
|
42
|
+
throw new Error(ExecutionOutput.formatToolText(`Draft must be under ${draftRoot}. Use inspect target=recipes view=summary to list drafts.`));
|
|
43
|
+
}
|
|
44
|
+
if (!existsSync(path)) {
|
|
45
|
+
throw new Error(ExecutionOutput.formatToolText(`Draft not found: ${path}`));
|
|
46
|
+
}
|
|
47
|
+
return path;
|
|
48
|
+
}
|
|
49
|
+
function promoteDraftRecipe(name, input, ctx, deps) {
|
|
50
|
+
const draftPath = assertDraftPath(deps, String(input.draft ?? ""));
|
|
51
|
+
const targetPath = getToolRecipePath(deps, name);
|
|
52
|
+
const tools = deps.getTools();
|
|
53
|
+
const existing = tools.get(name);
|
|
54
|
+
const conflict = deps.getExternalToolConflict(name);
|
|
55
|
+
if (conflict)
|
|
56
|
+
throw new Error(ExecutionOutput.formatToolText(conflict));
|
|
57
|
+
if ((existing || existsSync(targetPath)) && !input.update) {
|
|
58
|
+
throw new Error(ExecutionOutput.formatToolText(`Tool "${name}" already registered. Use update=true to overwrite.`));
|
|
59
|
+
}
|
|
60
|
+
const config = RecipesReferences.readResolvedRecipeConfig(draftPath);
|
|
61
|
+
if (!config) {
|
|
62
|
+
const reason = RecipesReferences.diagnoseRawRecipeConfigFailure(draftPath);
|
|
63
|
+
throw new Error(ExecutionOutput.formatToolText(`Draft recipe is invalid${reason ? `: ${reason}` : "."}`));
|
|
64
|
+
}
|
|
65
|
+
const promoted = buildConfig(name, {
|
|
66
|
+
...input,
|
|
67
|
+
description: input.description ?? config.description,
|
|
68
|
+
template: draftPath,
|
|
69
|
+
}, existing);
|
|
70
|
+
const raw = RecipesReferences.readRawRecipeConfig(draftPath);
|
|
71
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
72
|
+
writeJsonAtomic(targetPath, raw);
|
|
73
|
+
promoted.template = targetPath;
|
|
74
|
+
promoted.sourcePath = targetPath;
|
|
75
|
+
tools.set(name, promoted);
|
|
76
|
+
deps.registerRuntimeTool(promoted);
|
|
77
|
+
deps.notify(ctx, `Promoted draft recipe: ${name}`, "info");
|
|
78
|
+
return {
|
|
79
|
+
content: [
|
|
80
|
+
textContent(ExecutionOutput.formatToolText(`${existing ? "Updated" : "Registered"} tool "${name}" from draft recipe.`)),
|
|
81
|
+
],
|
|
82
|
+
details: {
|
|
83
|
+
args: promoted.args,
|
|
84
|
+
config: targetPath,
|
|
85
|
+
draft: draftPath,
|
|
86
|
+
promoted: true,
|
|
87
|
+
tool: name,
|
|
88
|
+
},
|
|
89
|
+
};
|
|
90
|
+
}
|
|
35
91
|
function persistToolRecipe(deps, cfg) {
|
|
36
92
|
const path = getToolRecipePath(deps, cfg.name);
|
|
37
93
|
mkdirSync(dirname(path), { recursive: true });
|
|
@@ -173,6 +229,9 @@ export async function executeRegisterTool(params, ctx, deps) {
|
|
|
173
229
|
if (deps.reservedToolNames.has(name)) {
|
|
174
230
|
throw new Error(ExecutionOutput.formatToolText(`Reserved tool name: ${name}`));
|
|
175
231
|
}
|
|
232
|
+
if (typeof input.draft === "string" && input.draft.trim()) {
|
|
233
|
+
return promoteDraftRecipe(name, input, ctx, deps);
|
|
234
|
+
}
|
|
176
235
|
const templateProvided = Object.hasOwn(input, "template");
|
|
177
236
|
const template = getInputTemplate(input.template);
|
|
178
237
|
if (templateProvided && (template === null || template === ""))
|
|
@@ -101,6 +101,73 @@ function getRoomMulticastRecipients(message, run) {
|
|
|
101
101
|
return Messages.formatActorAddress(parsed);
|
|
102
102
|
});
|
|
103
103
|
}
|
|
104
|
+
function normalizeDeliveryOutcome(address, result) {
|
|
105
|
+
if (result.reason)
|
|
106
|
+
return result;
|
|
107
|
+
if (address.kind === "run") {
|
|
108
|
+
if (result.stopped === true) {
|
|
109
|
+
return {
|
|
110
|
+
...result,
|
|
111
|
+
consumer: "run-control",
|
|
112
|
+
delivered: true,
|
|
113
|
+
persisted: true,
|
|
114
|
+
reason: "control_applied",
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
return {
|
|
118
|
+
...result,
|
|
119
|
+
consumer: result.control_type ?? result.control ?? "run-control",
|
|
120
|
+
delivered: result.sent === true && result.queued !== true,
|
|
121
|
+
persisted: true,
|
|
122
|
+
reason: result.delivery_error
|
|
123
|
+
? "delivery_failed_persisted"
|
|
124
|
+
: result.queued === true
|
|
125
|
+
? "queued_mailbox"
|
|
126
|
+
: "delivered",
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
if (address.kind === "branch") {
|
|
130
|
+
return {
|
|
131
|
+
...result,
|
|
132
|
+
consumer: "branch-mailbox",
|
|
133
|
+
delivered: result.sent === true,
|
|
134
|
+
persisted: true,
|
|
135
|
+
reason: result.delivery_error
|
|
136
|
+
? "branch_persisted_parent_unavailable"
|
|
137
|
+
: "branch_persisted_forwarded",
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (address.kind === "room") {
|
|
141
|
+
return {
|
|
142
|
+
...result,
|
|
143
|
+
consumer: "room-timeline",
|
|
144
|
+
delivered: true,
|
|
145
|
+
forwarded: Number(result.multicast_count ?? 0) > 0,
|
|
146
|
+
persisted: true,
|
|
147
|
+
reason: "room_persisted",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
if (address.kind === "tool") {
|
|
151
|
+
return {
|
|
152
|
+
...result,
|
|
153
|
+
consumer: "tool",
|
|
154
|
+
delivered: true,
|
|
155
|
+
persisted: false,
|
|
156
|
+
reason: "tool_invoked",
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
if (address.kind === "coordinator" || address.kind === "session") {
|
|
160
|
+
return {
|
|
161
|
+
...result,
|
|
162
|
+
consumer: "run-outbox",
|
|
163
|
+
delivered: false,
|
|
164
|
+
persisted: true,
|
|
165
|
+
queued: true,
|
|
166
|
+
reason: `${address.kind}_outbox_persisted`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
104
171
|
function actorMessageNextActions(message, result) {
|
|
105
172
|
const actions = [];
|
|
106
173
|
const address = Messages.parseActorAddress(message.to);
|
|
@@ -132,8 +199,18 @@ function compactActorMessageResult(message, result) {
|
|
|
132
199
|
];
|
|
133
200
|
if (result.bytes !== undefined)
|
|
134
201
|
tokens.push(`bytes=${String(result.bytes)}`);
|
|
202
|
+
if (result.delivered !== undefined)
|
|
203
|
+
tokens.push(`delivered=${String(result.delivered)}`);
|
|
135
204
|
if (result.queued === true)
|
|
136
205
|
tokens.push("queued=true");
|
|
206
|
+
if (result.persisted !== undefined)
|
|
207
|
+
tokens.push(`persisted=${String(result.persisted)}`);
|
|
208
|
+
if (result.forwarded !== undefined)
|
|
209
|
+
tokens.push(`forwarded=${String(result.forwarded)}`);
|
|
210
|
+
if (result.consumer)
|
|
211
|
+
tokens.push(`consumer=${String(result.consumer)}`);
|
|
212
|
+
if (result.reason)
|
|
213
|
+
tokens.push(`reason=${String(result.reason)}`);
|
|
137
214
|
if (result.control)
|
|
138
215
|
tokens.push(`control=${String(result.control)}`);
|
|
139
216
|
if (result.outbox)
|
|
@@ -252,13 +329,17 @@ export function createActorMessageToolDefinition(deps = {}) {
|
|
|
252
329
|
throw new Error(`${message.to} has no run state directory.`);
|
|
253
330
|
const recipients = getRoomMulticastRecipients(message, runId);
|
|
254
331
|
const roomResult = Rooms.appendRoomMessage(stateDir, address.room, message);
|
|
255
|
-
await Promise.all(recipients.map((recipient) => routeBranchEnvelope(stateDir, runId, recipient, message, {
|
|
332
|
+
const multicastResults = await Promise.all(recipients.map(async (recipient) => normalizeDeliveryOutcome({ kind: "branch", branch: recipient.split("/").at(-1), value: runId }, await routeBranchEnvelope(stateDir, runId, recipient, message, {
|
|
256
333
|
source: "room-multicast",
|
|
257
|
-
})));
|
|
334
|
+
}))));
|
|
258
335
|
result = {
|
|
259
336
|
...roomResult,
|
|
260
337
|
...(recipients.length > 0
|
|
261
|
-
? {
|
|
338
|
+
? {
|
|
339
|
+
multicast: recipients,
|
|
340
|
+
multicast_count: recipients.length,
|
|
341
|
+
multicast_results: multicastResults,
|
|
342
|
+
}
|
|
262
343
|
: {}),
|
|
263
344
|
};
|
|
264
345
|
}
|
|
@@ -328,6 +409,7 @@ export function createActorMessageToolDefinition(deps = {}) {
|
|
|
328
409
|
else {
|
|
329
410
|
throw new Error(`message currently supports run:<id>, branch:<run>/<branch>, room:<run>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`);
|
|
330
411
|
}
|
|
412
|
+
result = normalizeDeliveryOutcome(address, result);
|
|
331
413
|
const nextActions = actorMessageNextActions(message, result);
|
|
332
414
|
const resultWithNext = nextActions.length
|
|
333
415
|
? { ...result, next_actions: nextActions }
|
|
@@ -24,6 +24,7 @@ export function createRegisterToolDefinition(deps) {
|
|
|
24
24
|
args: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.args),
|
|
25
25
|
async: booleanSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.async),
|
|
26
26
|
description: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.description),
|
|
27
|
+
draft: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.draft),
|
|
27
28
|
name: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.name),
|
|
28
29
|
state_dir: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.state_dir),
|
|
29
30
|
template: unionSchema([
|
|
@@ -137,7 +137,11 @@ export async function runActorWorker(argv = process.argv.slice(2)) {
|
|
|
137
137
|
{ display: branch, role: "worker", status: "present" },
|
|
138
138
|
`${branch} joined as mailbox worker`,
|
|
139
139
|
);
|
|
140
|
-
room("awaiting_assignment", `${branch} awaiting assignment`, {
|
|
140
|
+
room("awaiting_assignment", `${branch} awaiting assignment`, {
|
|
141
|
+
branch,
|
|
142
|
+
stale_claim_ms: staleClaimMs,
|
|
143
|
+
stale_claims: staleClaims(),
|
|
144
|
+
});
|
|
141
145
|
journal("worker.started", {
|
|
142
146
|
artifact_dir: artifactDir,
|
|
143
147
|
branch,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: actors
|
|
3
3
|
description: Required practical guide for non-trivial pi-actors use. Read before using or changing spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.35.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Actors (pi-actors)
|
package/docs/tool-registry.md
CHANGED
|
@@ -10,7 +10,7 @@ The registry source is location-discovered recipes, not a live tool-only JSON fi
|
|
|
10
10
|
|
|
11
11
|
- `~/.pi/agent/recipes/*.json` and `*.md` are the highest-priority user recipe root and the operator-managed tool set.
|
|
12
12
|
- Recipes in that root are tools by location.
|
|
13
|
-
- `~/.pi/agent/recipes/drafts/*.json` stores captured inline-spawn draft recipes, not registered tools. Promote one
|
|
13
|
+
- `~/.pi/agent/recipes/drafts/*.json` stores captured inline-spawn draft recipes, not registered tools. Promote one with `register_tool name=<tool_name> draft=<draft_path>` or by manually moving/copying it up one level into `~/.pi/agent/recipes`. `inspect target=recipes view=summary` reports their count, and verbose output lists their paths, timestamps, fingerprints, validation state, source run when known, descriptions, and template previews for explicit replay or promotion.
|
|
14
14
|
- Packaged pi-actors recipes are the lower-priority standard library of declarative actor config components, not automatically registered tools.
|
|
15
15
|
- Ad hoc recipe files outside the user recipe root are components unless explicitly registered/copied into `~/.pi/agent/recipes`.
|
|
16
16
|
- Recipe identity is the filename basename; `~/.pi/agent/recipes/docs_review.json` and `docs_review.md` both have id/tool name `docs_review`.
|
|
@@ -51,7 +51,7 @@ register_tool name=call_subagent \
|
|
|
51
51
|
template="pi -p --model {model} --no-tools {prompt}" args="prompt:string,model:string"
|
|
52
52
|
```
|
|
53
53
|
|
|
54
|
-
Use `update=true` to overwrite an existing tool. Omit `template` and co-located recipe fields during update to keep the previous execution binding.
|
|
54
|
+
Use `update=true` to overwrite an existing tool. Omit `template` and co-located recipe fields during update to keep the previous execution binding. To promote a captured draft, pass `name` plus `draft` with a path under `~/.pi/agent/recipes/drafts`; promotion validates the draft before writing `~/.pi/agent/recipes/<name>.json`, preserves the draft file, rejects name collisions unless `update=true`, and leaves shadowing evidence visible through `inspect target=recipes view=summary` or `view=doctor`.
|
|
55
55
|
|
|
56
56
|
`template` may also be a standard command-template sequence for multi-step tools. Timeout is disabled by default; add explicit positive `timeout` values when individual steps should fail closed:
|
|
57
57
|
|
package/lib/prompts.ts
CHANGED
|
@@ -38,6 +38,8 @@ export const REGISTER_TOOL_PARAM_DESCRIPTIONS = {
|
|
|
38
38
|
name: "Tool name in snake_case (e.g., 'transcribe')",
|
|
39
39
|
description:
|
|
40
40
|
"Describe what the tool does for the LLM. Required unless deleting; omitted updates keep the old description.",
|
|
41
|
+
draft:
|
|
42
|
+
"Promote a draft recipe path from ~/.pi/agent/recipes/drafts into an active named recipe under ~/.pi/agent/recipes. Requires name; use update=true to overwrite.",
|
|
41
43
|
async:
|
|
42
44
|
"Set true for a co-located async template recipe. Omit for ordinary command templates or file-backed recipe references.",
|
|
43
45
|
state_dir:
|
package/lib/recipes-discovery.ts
CHANGED
|
@@ -574,14 +574,43 @@ export function getShadowedLaunchDiagnostic(
|
|
|
574
574
|
};
|
|
575
575
|
}
|
|
576
576
|
|
|
577
|
+
function templatePreview(value: unknown): string | undefined {
|
|
578
|
+
const rendered =
|
|
579
|
+
typeof value === "string"
|
|
580
|
+
? value
|
|
581
|
+
: value === undefined
|
|
582
|
+
? undefined
|
|
583
|
+
: JSON.stringify(value);
|
|
584
|
+
return rendered && rendered.length > 120
|
|
585
|
+
? `${rendered.slice(0, 117)}...`
|
|
586
|
+
: rendered;
|
|
587
|
+
}
|
|
588
|
+
|
|
577
589
|
export function listDraftRecipes(root: string): Array<Record<string, unknown>> {
|
|
578
590
|
return listRecipeFiles(root).map((path) => {
|
|
579
591
|
const id = RecipesReferences.getRecipeIdFromPath(path);
|
|
592
|
+
const bytes = readFileSync(path);
|
|
593
|
+
const stat = statSync(path);
|
|
580
594
|
const config = RecipesReferences.readRawRecipeConfig(path);
|
|
595
|
+
const resolved = RecipesReferences.readResolvedRecipeConfig(path);
|
|
596
|
+
const diagnostics = getRecipeConfigDiagnostics(path, resolved);
|
|
597
|
+
const sourceRun = String(config?.description ?? "").match(
|
|
598
|
+
/spawn run ([^\s]+)/,
|
|
599
|
+
)?.[1];
|
|
600
|
+
const preview = templatePreview(config?.template);
|
|
581
601
|
return {
|
|
582
602
|
id,
|
|
583
603
|
path,
|
|
604
|
+
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
605
|
+
size: bytes.byteLength,
|
|
606
|
+
created_at: stat.birthtime.toISOString(),
|
|
607
|
+
modified_at: stat.mtime.toISOString(),
|
|
608
|
+
valid: Boolean(resolved),
|
|
609
|
+
diagnostics,
|
|
584
610
|
...(config?.description ? { description: config.description } : {}),
|
|
611
|
+
...(sourceRun ? { source_run: sourceRun } : {}),
|
|
612
|
+
...(config?.async !== undefined ? { async: config.async } : {}),
|
|
613
|
+
...(preview ? { template_preview: preview } : {}),
|
|
585
614
|
};
|
|
586
615
|
});
|
|
587
616
|
}
|
package/lib/registry.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import { existsSync, mkdirSync, unlinkSync } from "node:fs";
|
|
8
|
-
import { dirname, join } from "node:path";
|
|
8
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
9
9
|
|
|
10
10
|
import * as CommandTemplates from "./command-templates.ts";
|
|
11
11
|
import * as Config from "./config.ts";
|
|
@@ -22,6 +22,7 @@ export interface RegisterToolInput {
|
|
|
22
22
|
async?: boolean;
|
|
23
23
|
state_dir?: string;
|
|
24
24
|
template?: CommandTemplates.CommandTemplateValue | null;
|
|
25
|
+
draft?: string;
|
|
25
26
|
args?: string;
|
|
26
27
|
update?: boolean;
|
|
27
28
|
values?: Record<string, unknown>;
|
|
@@ -32,6 +33,8 @@ export interface RegisterToolResultDetails {
|
|
|
32
33
|
async?: boolean;
|
|
33
34
|
config?: string;
|
|
34
35
|
defaults?: Record<string, string>;
|
|
36
|
+
draft?: string;
|
|
37
|
+
promoted?: boolean;
|
|
35
38
|
recipeName?: string;
|
|
36
39
|
state_dir?: string;
|
|
37
40
|
template?: CommandTemplates.CommandTemplateValue;
|
|
@@ -95,6 +98,93 @@ function getToolRecipePath<TContext>(
|
|
|
95
98
|
return join(getRecipeRoot(deps), `${name}.json`);
|
|
96
99
|
}
|
|
97
100
|
|
|
101
|
+
function assertDraftPath<TContext>(
|
|
102
|
+
deps: RegisterToolRuntimeDeps<TContext>,
|
|
103
|
+
draft: string,
|
|
104
|
+
): string {
|
|
105
|
+
const draftRoot = resolve(getRecipeRoot(deps), "drafts");
|
|
106
|
+
const path = resolve(draft);
|
|
107
|
+
const relation = relative(draftRoot, path);
|
|
108
|
+
if (
|
|
109
|
+
relation === "" ||
|
|
110
|
+
relation.startsWith("..") ||
|
|
111
|
+
resolve(relation) === relation
|
|
112
|
+
) {
|
|
113
|
+
throw new Error(
|
|
114
|
+
ExecutionOutput.formatToolText(
|
|
115
|
+
`Draft must be under ${draftRoot}. Use inspect target=recipes view=summary to list drafts.`,
|
|
116
|
+
),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
if (!existsSync(path)) {
|
|
120
|
+
throw new Error(ExecutionOutput.formatToolText(`Draft not found: ${path}`));
|
|
121
|
+
}
|
|
122
|
+
return path;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function promoteDraftRecipe<TContext>(
|
|
126
|
+
name: string,
|
|
127
|
+
input: RegisterToolInput,
|
|
128
|
+
ctx: TContext,
|
|
129
|
+
deps: RegisterToolRuntimeDeps<TContext>,
|
|
130
|
+
): RegisterToolResult {
|
|
131
|
+
const draftPath = assertDraftPath(deps, String(input.draft ?? ""));
|
|
132
|
+
const targetPath = getToolRecipePath(deps, name);
|
|
133
|
+
const tools = deps.getTools();
|
|
134
|
+
const existing = tools.get(name);
|
|
135
|
+
const conflict = deps.getExternalToolConflict(name);
|
|
136
|
+
if (conflict) throw new Error(ExecutionOutput.formatToolText(conflict));
|
|
137
|
+
if ((existing || existsSync(targetPath)) && !input.update) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
ExecutionOutput.formatToolText(
|
|
140
|
+
`Tool "${name}" already registered. Use update=true to overwrite.`,
|
|
141
|
+
),
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
const config = RecipesReferences.readResolvedRecipeConfig(draftPath);
|
|
145
|
+
if (!config) {
|
|
146
|
+
const reason = RecipesReferences.diagnoseRawRecipeConfigFailure(draftPath);
|
|
147
|
+
throw new Error(
|
|
148
|
+
ExecutionOutput.formatToolText(
|
|
149
|
+
`Draft recipe is invalid${reason ? `: ${reason}` : "."}`,
|
|
150
|
+
),
|
|
151
|
+
);
|
|
152
|
+
}
|
|
153
|
+
const promoted = buildConfig(
|
|
154
|
+
name,
|
|
155
|
+
{
|
|
156
|
+
...input,
|
|
157
|
+
description: input.description ?? config.description,
|
|
158
|
+
template: draftPath,
|
|
159
|
+
},
|
|
160
|
+
existing,
|
|
161
|
+
);
|
|
162
|
+
const raw = RecipesReferences.readRawRecipeConfig(draftPath)!;
|
|
163
|
+
mkdirSync(dirname(targetPath), { recursive: true });
|
|
164
|
+
writeJsonAtomic(targetPath, raw);
|
|
165
|
+
promoted.template = targetPath;
|
|
166
|
+
promoted.sourcePath = targetPath;
|
|
167
|
+
tools.set(name, promoted);
|
|
168
|
+
deps.registerRuntimeTool(promoted);
|
|
169
|
+
deps.notify(ctx, `Promoted draft recipe: ${name}`, "info");
|
|
170
|
+
return {
|
|
171
|
+
content: [
|
|
172
|
+
textContent(
|
|
173
|
+
ExecutionOutput.formatToolText(
|
|
174
|
+
`${existing ? "Updated" : "Registered"} tool "${name}" from draft recipe.`,
|
|
175
|
+
),
|
|
176
|
+
),
|
|
177
|
+
],
|
|
178
|
+
details: {
|
|
179
|
+
args: promoted.args,
|
|
180
|
+
config: targetPath,
|
|
181
|
+
draft: draftPath,
|
|
182
|
+
promoted: true,
|
|
183
|
+
tool: name,
|
|
184
|
+
} as RegisterToolResultDetails & Record<string, unknown>,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
98
188
|
function persistToolRecipe<TContext>(
|
|
99
189
|
deps: RegisterToolRuntimeDeps<TContext>,
|
|
100
190
|
cfg: Config.RegisteredTool,
|
|
@@ -284,6 +374,9 @@ export async function executeRegisterTool<TContext>(
|
|
|
284
374
|
ExecutionOutput.formatToolText(`Reserved tool name: ${name}`),
|
|
285
375
|
);
|
|
286
376
|
}
|
|
377
|
+
if (typeof input.draft === "string" && input.draft.trim()) {
|
|
378
|
+
return promoteDraftRecipe(name, input, ctx, deps);
|
|
379
|
+
}
|
|
287
380
|
const templateProvided = Object.hasOwn(input, "template");
|
|
288
381
|
const template = getInputTemplate(input.template);
|
|
289
382
|
if (templateProvided && (template === null || template === ""))
|
package/lib/tools-message.ts
CHANGED
|
@@ -144,6 +144,76 @@ function getRoomMulticastRecipients(
|
|
|
144
144
|
});
|
|
145
145
|
}
|
|
146
146
|
|
|
147
|
+
function normalizeDeliveryOutcome(
|
|
148
|
+
address: Messages.ActorAddress,
|
|
149
|
+
result: Record<string, unknown>,
|
|
150
|
+
): Record<string, unknown> {
|
|
151
|
+
if (result.reason) return result;
|
|
152
|
+
if (address.kind === "run") {
|
|
153
|
+
if (result.stopped === true) {
|
|
154
|
+
return {
|
|
155
|
+
...result,
|
|
156
|
+
consumer: "run-control",
|
|
157
|
+
delivered: true,
|
|
158
|
+
persisted: true,
|
|
159
|
+
reason: "control_applied",
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
return {
|
|
163
|
+
...result,
|
|
164
|
+
consumer: result.control_type ?? result.control ?? "run-control",
|
|
165
|
+
delivered: result.sent === true && result.queued !== true,
|
|
166
|
+
persisted: true,
|
|
167
|
+
reason: result.delivery_error
|
|
168
|
+
? "delivery_failed_persisted"
|
|
169
|
+
: result.queued === true
|
|
170
|
+
? "queued_mailbox"
|
|
171
|
+
: "delivered",
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
if (address.kind === "branch") {
|
|
175
|
+
return {
|
|
176
|
+
...result,
|
|
177
|
+
consumer: "branch-mailbox",
|
|
178
|
+
delivered: result.sent === true,
|
|
179
|
+
persisted: true,
|
|
180
|
+
reason: result.delivery_error
|
|
181
|
+
? "branch_persisted_parent_unavailable"
|
|
182
|
+
: "branch_persisted_forwarded",
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
if (address.kind === "room") {
|
|
186
|
+
return {
|
|
187
|
+
...result,
|
|
188
|
+
consumer: "room-timeline",
|
|
189
|
+
delivered: true,
|
|
190
|
+
forwarded: Number(result.multicast_count ?? 0) > 0,
|
|
191
|
+
persisted: true,
|
|
192
|
+
reason: "room_persisted",
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
if (address.kind === "tool") {
|
|
196
|
+
return {
|
|
197
|
+
...result,
|
|
198
|
+
consumer: "tool",
|
|
199
|
+
delivered: true,
|
|
200
|
+
persisted: false,
|
|
201
|
+
reason: "tool_invoked",
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
if (address.kind === "coordinator" || address.kind === "session") {
|
|
205
|
+
return {
|
|
206
|
+
...result,
|
|
207
|
+
consumer: "run-outbox",
|
|
208
|
+
delivered: false,
|
|
209
|
+
persisted: true,
|
|
210
|
+
queued: true,
|
|
211
|
+
reason: `${address.kind}_outbox_persisted`,
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
return result;
|
|
215
|
+
}
|
|
216
|
+
|
|
147
217
|
function actorMessageNextActions(
|
|
148
218
|
message: Messages.ActorMessage,
|
|
149
219
|
result: Record<string, unknown>,
|
|
@@ -183,7 +253,15 @@ function compactActorMessageResult(
|
|
|
183
253
|
`message=${result.sent === true || result.stopped === true ? "sent" : "not_sent"}`,
|
|
184
254
|
];
|
|
185
255
|
if (result.bytes !== undefined) tokens.push(`bytes=${String(result.bytes)}`);
|
|
256
|
+
if (result.delivered !== undefined)
|
|
257
|
+
tokens.push(`delivered=${String(result.delivered)}`);
|
|
186
258
|
if (result.queued === true) tokens.push("queued=true");
|
|
259
|
+
if (result.persisted !== undefined)
|
|
260
|
+
tokens.push(`persisted=${String(result.persisted)}`);
|
|
261
|
+
if (result.forwarded !== undefined)
|
|
262
|
+
tokens.push(`forwarded=${String(result.forwarded)}`);
|
|
263
|
+
if (result.consumer) tokens.push(`consumer=${String(result.consumer)}`);
|
|
264
|
+
if (result.reason) tokens.push(`reason=${String(result.reason)}`);
|
|
187
265
|
if (result.control) tokens.push(`control=${String(result.control)}`);
|
|
188
266
|
if (result.outbox) tokens.push(`messages=${String(result.outbox)}`);
|
|
189
267
|
if (result.message_count !== undefined)
|
|
@@ -364,17 +442,24 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
364
442
|
address.room,
|
|
365
443
|
message,
|
|
366
444
|
);
|
|
367
|
-
await Promise.all(
|
|
368
|
-
recipients.map((recipient) =>
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
445
|
+
const multicastResults = await Promise.all(
|
|
446
|
+
recipients.map(async (recipient) =>
|
|
447
|
+
normalizeDeliveryOutcome(
|
|
448
|
+
{ kind: "branch", branch: recipient.split("/").at(-1), value: runId },
|
|
449
|
+
await routeBranchEnvelope(stateDir, runId, recipient, message, {
|
|
450
|
+
source: "room-multicast",
|
|
451
|
+
}),
|
|
452
|
+
),
|
|
372
453
|
),
|
|
373
454
|
);
|
|
374
455
|
result = {
|
|
375
456
|
...roomResult,
|
|
376
457
|
...(recipients.length > 0
|
|
377
|
-
? {
|
|
458
|
+
? {
|
|
459
|
+
multicast: recipients,
|
|
460
|
+
multicast_count: recipients.length,
|
|
461
|
+
multicast_results: multicastResults,
|
|
462
|
+
}
|
|
378
463
|
: {}),
|
|
379
464
|
};
|
|
380
465
|
} else if (address.kind === "tool" && address.value) {
|
|
@@ -461,6 +546,7 @@ export function createActorMessageToolDefinition<TContext = unknown>(
|
|
|
461
546
|
`message currently supports run:<id>, branch:<run>/<branch>, room:<run>, tool:<name>, coordinator, and session:<id> destinations; unsupported destination: ${message.to}`,
|
|
462
547
|
);
|
|
463
548
|
}
|
|
549
|
+
result = normalizeDeliveryOutcome(address, result);
|
|
464
550
|
const nextActions = actorMessageNextActions(message, result);
|
|
465
551
|
const resultWithNext = nextActions.length
|
|
466
552
|
? { ...result, next_actions: nextActions }
|
package/lib/tools-register.ts
CHANGED
|
@@ -36,6 +36,7 @@ export function createRegisterToolDefinition<TContext>(
|
|
|
36
36
|
description: stringSchema(
|
|
37
37
|
Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.description,
|
|
38
38
|
),
|
|
39
|
+
draft: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.draft),
|
|
39
40
|
name: stringSchema(Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.name),
|
|
40
41
|
state_dir: stringSchema(
|
|
41
42
|
Prompts.REGISTER_TOOL_PARAM_DESCRIPTIONS.state_dir,
|
package/package.json
CHANGED
package/scripts/actor-worker.mjs
CHANGED
|
@@ -137,7 +137,11 @@ export async function runActorWorker(argv = process.argv.slice(2)) {
|
|
|
137
137
|
{ display: branch, role: "worker", status: "present" },
|
|
138
138
|
`${branch} joined as mailbox worker`,
|
|
139
139
|
);
|
|
140
|
-
room("awaiting_assignment", `${branch} awaiting assignment`, {
|
|
140
|
+
room("awaiting_assignment", `${branch} awaiting assignment`, {
|
|
141
|
+
branch,
|
|
142
|
+
stale_claim_ms: staleClaimMs,
|
|
143
|
+
stale_claims: staleClaims(),
|
|
144
|
+
});
|
|
141
145
|
journal("worker.started", {
|
|
142
146
|
artifact_dir: artifactDir,
|
|
143
147
|
branch,
|
package/skills/actors/SKILL.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: actors
|
|
3
3
|
description: Required practical guide for non-trivial pi-actors use. Read before using or changing spawn, message, inspect, actor runs, tools, recipes, command templates, async lifecycle, mailboxes, artifacts, and local orchestration mechanics.
|
|
4
4
|
metadata:
|
|
5
|
-
version: 0.
|
|
5
|
+
version: 0.35.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Actors (pi-actors)
|
package/skills/swarm/SKILL.md
CHANGED