@llblab/pi-actors 0.24.8 → 0.26.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 +5 -9
- package/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/lib/actor-worker.d.ts +3 -0
- package/dist/lib/actor-worker.js +54 -6
- package/dist/lib/recipe-discovery.js +85 -4
- package/dist/lib/tools.js +15 -3
- package/dist/recipes/actor-worker.json +10 -6
- package/dist/recipes/lens-swarm.json +0 -2
- package/dist/recipes/pipeline-architect-coordinator.json +0 -2
- package/dist/recipes/pipeline-artifact-bundle.json +0 -2
- package/dist/recipes/pipeline-artifact-report.json +0 -2
- package/dist/recipes/pipeline-artifact-write.json +0 -2
- package/dist/recipes/pipeline-async-run-ops.json +0 -2
- package/dist/recipes/pipeline-checkpoint-continuation.json +0 -2
- package/dist/recipes/pipeline-development-tasking.json +0 -2
- package/dist/recipes/pipeline-docs-maintenance.json +0 -2
- package/dist/recipes/pipeline-media-library.json +0 -2
- package/dist/recipes/pipeline-quorum-review.json +0 -2
- package/dist/recipes/pipeline-release-readiness.json +0 -2
- package/dist/recipes/pipeline-release-summary.json +0 -2
- package/dist/recipes/pipeline-repo-health.json +0 -2
- package/dist/recipes/pipeline-research-synthesis.json +0 -2
- package/dist/recipes/pipeline-review-readiness.json +0 -2
- package/dist/recipes/pipeline-room-swarm.json +0 -2
- package/dist/recipes/subagent-artifact.json +0 -2
- package/dist/recipes/subagent-checkpoint.json +0 -2
- package/dist/recipes/subagent-conflict-report.json +0 -2
- package/dist/recipes/subagent-contradiction-map.json +0 -2
- package/dist/recipes/subagent-critic.json +0 -2
- package/dist/recipes/subagent-evidence-map.json +0 -2
- package/dist/recipes/subagent-followup.json +0 -2
- package/dist/recipes/subagent-judge.json +0 -2
- package/dist/recipes/subagent-merge.json +0 -2
- package/dist/recipes/subagent-message.json +0 -2
- package/dist/recipes/subagent-normalize.json +0 -2
- package/dist/recipes/subagent-plan.json +0 -2
- package/dist/recipes/subagent-prompt.json +0 -2
- package/dist/recipes/subagent-quorum.json +0 -2
- package/dist/recipes/subagent-review-coordinator.json +0 -2
- package/dist/recipes/subagent-review.json +0 -2
- package/dist/recipes/subagent-task-card.json +0 -2
- package/dist/recipes/subagent-tools.json +0 -2
- package/dist/recipes/subagent-verify.json +0 -2
- package/dist/recipes/subagents-prompts.json +0 -2
- package/dist/skills/actors/SKILL.md +2 -2
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/actor-messages.md +1 -1
- package/docs/template-recipes.md +2 -2
- package/docs/tool-registry.md +2 -1
- package/lib/actor-worker.ts +62 -6
- package/lib/recipe-discovery.ts +94 -4
- package/lib/tools.ts +20 -3
- package/package.json +1 -1
- package/recipes/actor-worker.json +10 -6
- package/recipes/lens-swarm.json +0 -2
- package/recipes/pipeline-architect-coordinator.json +0 -2
- package/recipes/pipeline-artifact-bundle.json +0 -2
- package/recipes/pipeline-artifact-report.json +0 -2
- package/recipes/pipeline-artifact-write.json +0 -2
- package/recipes/pipeline-async-run-ops.json +0 -2
- package/recipes/pipeline-checkpoint-continuation.json +0 -2
- package/recipes/pipeline-development-tasking.json +0 -2
- package/recipes/pipeline-docs-maintenance.json +0 -2
- package/recipes/pipeline-media-library.json +0 -2
- package/recipes/pipeline-quorum-review.json +0 -2
- package/recipes/pipeline-release-readiness.json +0 -2
- package/recipes/pipeline-release-summary.json +0 -2
- package/recipes/pipeline-repo-health.json +0 -2
- package/recipes/pipeline-research-synthesis.json +0 -2
- package/recipes/pipeline-review-readiness.json +0 -2
- package/recipes/pipeline-room-swarm.json +0 -2
- package/recipes/subagent-artifact.json +0 -2
- package/recipes/subagent-checkpoint.json +0 -2
- package/recipes/subagent-conflict-report.json +0 -2
- package/recipes/subagent-contradiction-map.json +0 -2
- package/recipes/subagent-critic.json +0 -2
- package/recipes/subagent-evidence-map.json +0 -2
- package/recipes/subagent-followup.json +0 -2
- package/recipes/subagent-judge.json +0 -2
- package/recipes/subagent-merge.json +0 -2
- package/recipes/subagent-message.json +0 -2
- package/recipes/subagent-normalize.json +0 -2
- package/recipes/subagent-plan.json +0 -2
- package/recipes/subagent-prompt.json +0 -2
- package/recipes/subagent-quorum.json +0 -2
- package/recipes/subagent-review-coordinator.json +0 -2
- package/recipes/subagent-review.json +0 -2
- package/recipes/subagent-task-card.json +0 -2
- package/recipes/subagent-tools.json +0 -2
- package/recipes/subagent-verify.json +0 -2
- package/recipes/subagents-prompts.json +0 -2
- package/skills/actors/SKILL.md +2 -2
- package/skills/swarm/SKILL.md +1 -1
|
@@ -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.
|
|
5
|
+
version: 0.26.0
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Actors (pi-actors)
|
|
@@ -277,7 +277,7 @@ Use packaged recipes by name with `spawn file=<name>` for async actors, or regis
|
|
|
277
277
|
- [`locker`](../../recipes/locker.json): modular queue + acquire/renew/release lease locks + journaled locker messages + platform-adapted control metadata.
|
|
278
278
|
- [`utility-coordinator-lock-snapshot`](../../recipes/utility-coordinator-lock-snapshot.json): one-shot JSON snapshot of a coordinator-locker state directory.
|
|
279
279
|
- [`music-player`](../../recipes/music-player.json): background local/URL/directory/playlist playback actor controlled by messages.
|
|
280
|
-
- [`actor-worker`](../../recipes/actor-worker.json): canonical mailbox-backed branch worker
|
|
280
|
+
- [`actor-worker`](../../recipes/actor-worker.json): canonical mailbox-backed branch worker reference that claims branch inbox work, emits room-visible task lifecycle messages, writes compact `worker-status.json`, optionally writes per-task result artifacts under `worker-artifacts/`, surfaces stale-claim counts when `stale_claim_ms` is set, and terminates on `control.kill`.
|
|
281
281
|
|
|
282
282
|
### Subagent Atoms
|
|
283
283
|
|
package/docs/actor-messages.md
CHANGED
package/docs/template-recipes.md
CHANGED
|
@@ -80,7 +80,7 @@ defaults:
|
|
|
80
80
|
scope: docs
|
|
81
81
|
mailbox:
|
|
82
82
|
accepts:
|
|
83
|
-
- control.
|
|
83
|
+
- control.kill
|
|
84
84
|
---
|
|
85
85
|
|
|
86
86
|
Human notes can explain intent, examples, or review guidance.
|
|
@@ -155,7 +155,7 @@ Use recipe-level `mailbox` to document the semantic messages a recipe actor acce
|
|
|
155
155
|
"control.continue",
|
|
156
156
|
"control.revise",
|
|
157
157
|
"control.approve",
|
|
158
|
-
"control.
|
|
158
|
+
"control.kill"
|
|
159
159
|
],
|
|
160
160
|
"emits": ["checkpoint.needs_scope", "branch.done", "run.done"]
|
|
161
161
|
}
|
package/docs/tool-registry.md
CHANGED
|
@@ -23,10 +23,11 @@ Inspect the discovered registry with:
|
|
|
23
23
|
|
|
24
24
|
```text
|
|
25
25
|
inspect target=recipes view=status
|
|
26
|
+
inspect target=recipes view=doctor
|
|
26
27
|
inspect target=recipes view=summary verbose=true
|
|
27
28
|
```
|
|
28
29
|
|
|
29
|
-
The summary reports active, shadowed, invalid, disabled, and diagnostic entries so operators can answer why a tool is present, hidden, broken, or disabled.
|
|
30
|
+
The summary reports active, shadowed, invalid, disabled, and diagnostic entries so operators can answer why a tool is present, hidden, broken, or disabled. The doctor view keeps the same registry evidence but promotes an advisory action surface: compact output includes the highest-priority `top` remediation plus ordered actions for invalid/blocking, disabled, risky shell-boundary, and shadowed recipes. Verbose inspection keeps the structured `remediations`, `top_action`, diagnostic details, and blocked lower-priority candidate paths when a broken or disabled higher-priority recipe masks a fallback.
|
|
30
31
|
|
|
31
32
|
## Registering Tools
|
|
32
33
|
|
package/lib/actor-worker.ts
CHANGED
|
@@ -3,17 +3,20 @@
|
|
|
3
3
|
* Zones: worker demo, mailbox loop recipe runtime
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { appendFileSync } from "node:fs";
|
|
6
|
+
import { appendFileSync, mkdirSync, writeFileSync } from "node:fs";
|
|
7
7
|
import { join } from "node:path";
|
|
8
8
|
|
|
9
|
-
import { appendRoomMessage, ensureRoomMember } from "./actor-rooms.ts";
|
|
9
|
+
import { appendRoomMessage, ensureRoomMember, readBranchInboxMessages } from "./actor-rooms.ts";
|
|
10
10
|
import { handleMailboxLoopOnce, isMailboxLoopStopMessage } from "./mailbox-loop.ts";
|
|
11
11
|
|
|
12
12
|
interface ActorWorkerArgs {
|
|
13
|
+
artifact_dir?: string;
|
|
13
14
|
branch?: string;
|
|
14
15
|
poll_ms?: string | number;
|
|
15
16
|
run?: string;
|
|
17
|
+
stale_claim_ms?: string | number;
|
|
16
18
|
state_dir?: string;
|
|
19
|
+
write_artifacts?: string | boolean;
|
|
17
20
|
}
|
|
18
21
|
|
|
19
22
|
export function parseActorWorkerArgs(argv: string[]): ActorWorkerArgs {
|
|
@@ -41,12 +44,23 @@ function text(value: unknown): string {
|
|
|
41
44
|
return typeof value === "string" ? value : JSON.stringify(value);
|
|
42
45
|
}
|
|
43
46
|
|
|
47
|
+
function enabled(value: unknown): boolean {
|
|
48
|
+
return value !== false && value !== "false" && value !== "0";
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function safeName(value: unknown): string {
|
|
52
|
+
return String(value ?? "unknown").replaceAll(/[^A-Za-z0-9_.-]+/g, "_");
|
|
53
|
+
}
|
|
54
|
+
|
|
44
55
|
export async function runActorWorker(argv = process.argv.slice(2)): Promise<void> {
|
|
45
56
|
const args = parseActorWorkerArgs(argv);
|
|
46
57
|
const stateDir = String(args.state_dir ?? "");
|
|
47
58
|
const run = String(args.run ?? "");
|
|
48
59
|
const branch = String(args.branch ?? "worker");
|
|
49
60
|
const pollMs = Math.max(50, Number(args.poll_ms ?? 1000));
|
|
61
|
+
const staleClaimMs = Math.max(0, Number(args.stale_claim_ms ?? 0));
|
|
62
|
+
const artifactDir = String(args.artifact_dir ?? join(stateDir, "worker-artifacts"));
|
|
63
|
+
const writeArtifacts = enabled(args.write_artifacts ?? true);
|
|
50
64
|
|
|
51
65
|
if (!stateDir || !run) {
|
|
52
66
|
throw new Error("usage: actor-worker.mjs --state-dir <dir> --run <id> [--branch worker] [--poll-ms 1000]");
|
|
@@ -55,6 +69,8 @@ export async function runActorWorker(argv = process.argv.slice(2)): Promise<void
|
|
|
55
69
|
const branchAddress = `branch:${run}/${branch}`;
|
|
56
70
|
const roomAddress = `room:${run}`;
|
|
57
71
|
const journalPath = join(stateDir, "worker-events.jsonl");
|
|
72
|
+
const statusPath = join(stateDir, "worker-status.json");
|
|
73
|
+
if (writeArtifacts) mkdirSync(artifactDir, { recursive: true });
|
|
58
74
|
|
|
59
75
|
function journal(event: string, data: Record<string, unknown> = {}): void {
|
|
60
76
|
appendFileSync(
|
|
@@ -63,6 +79,30 @@ export async function runActorWorker(argv = process.argv.slice(2)): Promise<void
|
|
|
63
79
|
);
|
|
64
80
|
}
|
|
65
81
|
|
|
82
|
+
function staleClaims(): number {
|
|
83
|
+
if (staleClaimMs <= 0) return 0;
|
|
84
|
+
const cutoff = Date.now() - staleClaimMs;
|
|
85
|
+
return readBranchInboxMessages(stateDir, run, branchAddress).filter((record) => {
|
|
86
|
+
if (record.status !== "claimed" || !record.claimed_at) return false;
|
|
87
|
+
const claimedAt = Date.parse(record.claimed_at);
|
|
88
|
+
return Number.isFinite(claimedAt) && claimedAt < cutoff;
|
|
89
|
+
}).length;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function status(state: string, data: Record<string, unknown> = {}): void {
|
|
93
|
+
writeFileSync(
|
|
94
|
+
statusPath,
|
|
95
|
+
`${JSON.stringify({ artifact_dir: artifactDir, branch, run, stale_claims: staleClaims(), state, ts: new Date().toISOString(), ...data }, null, 2)}\n`,
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function writeResultArtifact(id: unknown, result: string): string | undefined {
|
|
100
|
+
if (!writeArtifacts) return undefined;
|
|
101
|
+
const path = join(artifactDir, `${safeName(id)}.txt`);
|
|
102
|
+
writeFileSync(path, `${result}\n`);
|
|
103
|
+
return path;
|
|
104
|
+
}
|
|
105
|
+
|
|
66
106
|
function room(type: string, summary: string, body: Record<string, unknown> = {}): void {
|
|
67
107
|
appendRoomMessage(stateDir, "main", {
|
|
68
108
|
body,
|
|
@@ -82,7 +122,10 @@ export async function runActorWorker(argv = process.argv.slice(2)): Promise<void
|
|
|
82
122
|
`${branch} joined as mailbox worker`,
|
|
83
123
|
);
|
|
84
124
|
room("awaiting_assignment", `${branch} awaiting assignment`, { branch });
|
|
85
|
-
journal("worker.started", { branch, run });
|
|
125
|
+
journal("worker.started", { artifact_dir: artifactDir, branch, run, stale_claim_ms: staleClaimMs });
|
|
126
|
+
status("awaiting_assignment", { handled: 0, stale_claim_ms: staleClaimMs });
|
|
127
|
+
|
|
128
|
+
let handled = 0;
|
|
86
129
|
|
|
87
130
|
let stopping = false;
|
|
88
131
|
while (!stopping) {
|
|
@@ -93,6 +136,7 @@ export async function runActorWorker(argv = process.argv.slice(2)): Promise<void
|
|
|
93
136
|
stopping = true;
|
|
94
137
|
room("actor.leave", `${branch} stopping`, { branch, reason: message.type });
|
|
95
138
|
journal("worker.stopping", { id: message.id, type: message.type });
|
|
139
|
+
status("stopping", { handled, reason: message.type });
|
|
96
140
|
return;
|
|
97
141
|
}
|
|
98
142
|
room("task.claim", `${branch} claimed ${message.type}`, {
|
|
@@ -100,19 +144,31 @@ export async function runActorWorker(argv = process.argv.slice(2)): Promise<void
|
|
|
100
144
|
id: message.id,
|
|
101
145
|
type: message.type,
|
|
102
146
|
});
|
|
147
|
+
const result = text(message.body);
|
|
148
|
+
const artifact = writeResultArtifact(message.id, result);
|
|
149
|
+
handled += 1;
|
|
103
150
|
room("task.result", `${branch} handled ${message.type}`, {
|
|
151
|
+
artifact,
|
|
104
152
|
branch,
|
|
105
153
|
id: message.id,
|
|
106
|
-
result
|
|
154
|
+
result,
|
|
107
155
|
type: message.type,
|
|
108
156
|
});
|
|
109
157
|
room("awaiting_assignment", `${branch} awaiting assignment`, { branch });
|
|
110
|
-
journal("task.handled", { id: message.id, type: message.type });
|
|
158
|
+
journal("task.handled", { artifact, id: message.id, type: message.type });
|
|
159
|
+
status("awaiting_assignment", {
|
|
160
|
+
handled,
|
|
161
|
+
last_artifact: artifact,
|
|
162
|
+
last_id: message.id,
|
|
163
|
+
last_type: message.type,
|
|
164
|
+
stale_claim_ms: staleClaimMs,
|
|
165
|
+
});
|
|
111
166
|
},
|
|
112
167
|
{ owner: branchAddress },
|
|
113
168
|
);
|
|
114
169
|
if (!result.handled) await sleep(pollMs);
|
|
115
170
|
}
|
|
116
171
|
|
|
117
|
-
journal("worker.done", { branch, run });
|
|
172
|
+
journal("worker.done", { branch, handled, run });
|
|
173
|
+
status("done", { handled });
|
|
118
174
|
}
|
package/lib/recipe-discovery.ts
CHANGED
|
@@ -403,6 +403,10 @@ function diagnosticSeverity(message: string): "info" | "warning" | "error" {
|
|
|
403
403
|
}
|
|
404
404
|
|
|
405
405
|
function diagnosticSuggestedAction(message: string): string {
|
|
406
|
+
if (/world-writable|group-writable/i.test(message))
|
|
407
|
+
return "tighten recipe root permissions";
|
|
408
|
+
if (/invokes bash/i.test(message))
|
|
409
|
+
return "audit the trusted shell boundary and keep only if intentional";
|
|
406
410
|
if (/must define template/i.test(message))
|
|
407
411
|
return "add a template field or remove the recipe";
|
|
408
412
|
if (/JSON|Expected|parse/i.test(message))
|
|
@@ -414,10 +418,6 @@ function diagnosticSuggestedAction(message: string): string {
|
|
|
414
418
|
return "split large prompt or data into separate files";
|
|
415
419
|
if (/repeat must/i.test(message))
|
|
416
420
|
return "use a positive repeat count or an array-typed repeat source";
|
|
417
|
-
if (/world-writable|group-writable/i.test(message))
|
|
418
|
-
return "tighten recipe root permissions";
|
|
419
|
-
if (/invokes bash/i.test(message))
|
|
420
|
-
return "audit the trusted shell boundary or move details to recipe doctor";
|
|
421
421
|
if (/shadows/i.test(message))
|
|
422
422
|
return "confirm the active override or rename one recipe";
|
|
423
423
|
if (/disabled/i.test(message))
|
|
@@ -455,6 +455,93 @@ function diagnosticDetails(
|
|
|
455
455
|
});
|
|
456
456
|
}
|
|
457
457
|
|
|
458
|
+
function remediationForEntry(
|
|
459
|
+
entry: DiscoveredRecipe,
|
|
460
|
+
activePath: string | undefined,
|
|
461
|
+
): Record<string, unknown> | undefined {
|
|
462
|
+
const riskyDiagnostics = entry.diagnostics.filter(
|
|
463
|
+
(message) => diagnosticSeverity(message) === "warning",
|
|
464
|
+
);
|
|
465
|
+
const blockedCandidate = entry.shadows[0];
|
|
466
|
+
if (entry.invalid) {
|
|
467
|
+
return {
|
|
468
|
+
id: entry.id,
|
|
469
|
+
kind: blockedCandidate ? "blocking_invalid" : "invalid",
|
|
470
|
+
severity: "error",
|
|
471
|
+
path: entry.path,
|
|
472
|
+
...(blockedCandidate ? { blocked_candidate: blockedCandidate } : {}),
|
|
473
|
+
reason: blockedCandidate
|
|
474
|
+
? "invalid higher-priority recipe blocks a lower-priority candidate"
|
|
475
|
+
: "recipe is invalid and cannot be exposed as a tool",
|
|
476
|
+
action: "fix recipe syntax/config, or disable/delete/archive it to restore fallback",
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
if (entry.disabled && entry.active) {
|
|
480
|
+
return {
|
|
481
|
+
id: entry.id,
|
|
482
|
+
kind: blockedCandidate ? "blocking_disabled" : "disabled",
|
|
483
|
+
severity: "warning",
|
|
484
|
+
path: entry.path,
|
|
485
|
+
...(blockedCandidate ? { blocked_candidate: blockedCandidate } : {}),
|
|
486
|
+
reason: blockedCandidate
|
|
487
|
+
? "disabled higher-priority recipe intentionally blocks a lower-priority candidate"
|
|
488
|
+
: "recipe is disabled and not exposed as a tool",
|
|
489
|
+
action: "keep disabled intentionally, re-enable, or delete/archive the file",
|
|
490
|
+
};
|
|
491
|
+
}
|
|
492
|
+
if (riskyDiagnostics.length > 0) {
|
|
493
|
+
return {
|
|
494
|
+
id: entry.id,
|
|
495
|
+
kind: "risky_shell_boundary",
|
|
496
|
+
severity: "warning",
|
|
497
|
+
path: entry.path,
|
|
498
|
+
reason: riskyDiagnostics[0],
|
|
499
|
+
action: "audit trusted command boundary; keep only if the recipe is local and intentional",
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
if (entry.shadowed) {
|
|
503
|
+
return {
|
|
504
|
+
id: entry.id,
|
|
505
|
+
kind: "shadowed",
|
|
506
|
+
severity: "info",
|
|
507
|
+
path: entry.path,
|
|
508
|
+
...(activePath ? { active_path: activePath } : {}),
|
|
509
|
+
reason: activePath
|
|
510
|
+
? `shadowed by ${activePath}`
|
|
511
|
+
: "shadowed by a higher-priority recipe",
|
|
512
|
+
action: "keep as fallback/component, merge, rename, delete, or archive",
|
|
513
|
+
};
|
|
514
|
+
}
|
|
515
|
+
return undefined;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
function remediationRank(item: Record<string, unknown>): number {
|
|
519
|
+
const kind = String(item.kind ?? "");
|
|
520
|
+
if (kind === "blocking_invalid") return 0;
|
|
521
|
+
if (kind === "invalid") return 1;
|
|
522
|
+
if (kind === "blocking_disabled") return 2;
|
|
523
|
+
if (kind === "risky_shell_boundary") return 3;
|
|
524
|
+
if (kind === "disabled") return 4;
|
|
525
|
+
if (kind === "shadowed") return 5;
|
|
526
|
+
return 6;
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function discoveryRemediations(
|
|
530
|
+
result: RecipeDiscoveryResult,
|
|
531
|
+
): Array<Record<string, unknown>> {
|
|
532
|
+
return result.entries
|
|
533
|
+
.map((entry) =>
|
|
534
|
+
remediationForEntry(entry, result.active.get(entry.id)?.path),
|
|
535
|
+
)
|
|
536
|
+
.filter((entry): entry is Record<string, unknown> => Boolean(entry))
|
|
537
|
+
.sort(
|
|
538
|
+
(a, b) =>
|
|
539
|
+
remediationRank(a) - remediationRank(b) ||
|
|
540
|
+
String(a.id).localeCompare(String(b.id)) ||
|
|
541
|
+
String(a.path).localeCompare(String(b.path)),
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
|
|
458
545
|
function recommendationForEntry(
|
|
459
546
|
entry: DiscoveredRecipe,
|
|
460
547
|
activePath: string | undefined,
|
|
@@ -483,6 +570,7 @@ export function summarizeDiscovery(
|
|
|
483
570
|
String(a.id).localeCompare(String(b.id)) ||
|
|
484
571
|
String(a.path).localeCompare(String(b.path)),
|
|
485
572
|
);
|
|
573
|
+
const remediations = discoveryRemediations(result);
|
|
486
574
|
return {
|
|
487
575
|
active: [...result.active.values()]
|
|
488
576
|
.map((entry) => ({
|
|
@@ -520,6 +608,8 @@ export function summarizeDiscovery(
|
|
|
520
608
|
.map((entry) => ({ id: entry.id, path: entry.path }))
|
|
521
609
|
.sort((a, b) => a.id.localeCompare(b.id)),
|
|
522
610
|
recommendations,
|
|
611
|
+
remediations,
|
|
612
|
+
top_action: remediations[0],
|
|
523
613
|
diagnostics: result.diagnostics,
|
|
524
614
|
diagnostic_details: diagnosticDetails(result),
|
|
525
615
|
integrity_manifest: createRecipeIntegrityManifest(result),
|
package/lib/tools.ts
CHANGED
|
@@ -470,6 +470,9 @@ function compactRecipeDoctor(summary: Record<string, unknown>): string {
|
|
|
470
470
|
const details = Array.isArray(summary.diagnostic_details)
|
|
471
471
|
? (summary.diagnostic_details as Array<Record<string, unknown>>)
|
|
472
472
|
: [];
|
|
473
|
+
const remediations = Array.isArray(summary.remediations)
|
|
474
|
+
? (summary.remediations as Array<Record<string, unknown>>)
|
|
475
|
+
: [];
|
|
473
476
|
const recommendations = Array.isArray(summary.recommendations)
|
|
474
477
|
? (summary.recommendations as Array<Record<string, unknown>>)
|
|
475
478
|
: [];
|
|
@@ -479,12 +482,26 @@ function compactRecipeDoctor(summary: Record<string, unknown>): string {
|
|
|
479
482
|
if (severity === "error" || severity === "warning" || severity === "info")
|
|
480
483
|
counts[severity] += 1;
|
|
481
484
|
}
|
|
485
|
+
const topAction = asRecord(summary.top_action);
|
|
482
486
|
const lines = [
|
|
483
|
-
`recipes doctor errors=${counts.error} warnings=${counts.warning} info=${counts.info} recommendations=${recommendations.length}`,
|
|
487
|
+
`recipes doctor errors=${counts.error} warnings=${counts.warning} info=${counts.info} actions=${remediations.length} recommendations=${recommendations.length}`,
|
|
484
488
|
];
|
|
485
|
-
|
|
489
|
+
if (Object.keys(topAction).length > 0) {
|
|
490
|
+
const action = compactPreview(
|
|
491
|
+
topAction.action,
|
|
492
|
+
Limits.DOCTOR_ACTION_PREVIEW_CHARS,
|
|
493
|
+
);
|
|
494
|
+
lines.push(
|
|
495
|
+
`top severity=${String(topAction.severity ?? "info")} kind=${String(topAction.kind ?? "inspect")} id=${String(topAction.id ?? "root")} action=${action ?? "inspect"}`,
|
|
496
|
+
);
|
|
497
|
+
}
|
|
498
|
+
for (const item of remediations.slice(0, 8)) {
|
|
499
|
+
const action = compactPreview(item.action, Limits.DOCTOR_ACTION_PREVIEW_CHARS);
|
|
500
|
+
const blocked = item.blocked_candidate
|
|
501
|
+
? ` blocked=${compactPreview(item.blocked_candidate, Limits.DOCTOR_ACTION_PREVIEW_CHARS)}`
|
|
502
|
+
: "";
|
|
486
503
|
lines.push(
|
|
487
|
-
`${String(
|
|
504
|
+
`${String(item.severity ?? "info")} kind=${String(item.kind ?? "inspect")} id=${String(item.id ?? "root")}${blocked} action=${action ?? "inspect"}`,
|
|
488
505
|
);
|
|
489
506
|
}
|
|
490
507
|
return `\n${lines.join("\n")}`;
|
package/package.json
CHANGED
|
@@ -4,18 +4,20 @@
|
|
|
4
4
|
"args": [
|
|
5
5
|
"repo:path",
|
|
6
6
|
"branch:string",
|
|
7
|
-
"poll_ms:int"
|
|
7
|
+
"poll_ms:int",
|
|
8
|
+
"stale_claim_ms:int",
|
|
9
|
+
"write_artifacts:bool"
|
|
8
10
|
],
|
|
9
11
|
"defaults": {
|
|
10
12
|
"repo": "~/.pi/agent/extensions/pi-actors",
|
|
11
13
|
"branch": "worker",
|
|
12
|
-
"poll_ms": "1000"
|
|
14
|
+
"poll_ms": "1000",
|
|
15
|
+
"stale_claim_ms": "0",
|
|
16
|
+
"write_artifacts": "true"
|
|
13
17
|
},
|
|
14
18
|
"mailbox": {
|
|
15
19
|
"accepts": [
|
|
16
20
|
"task.assign",
|
|
17
|
-
"control.stop",
|
|
18
|
-
"control.cancel",
|
|
19
21
|
"control.kill"
|
|
20
22
|
],
|
|
21
23
|
"emits": [
|
|
@@ -29,7 +31,9 @@
|
|
|
29
31
|
]
|
|
30
32
|
},
|
|
31
33
|
"artifacts": {
|
|
32
|
-
"journal": "{state_dir}/worker-events.jsonl"
|
|
34
|
+
"journal": "{state_dir}/worker-events.jsonl",
|
|
35
|
+
"status": "{state_dir}/worker-status.json",
|
|
36
|
+
"results": "{state_dir}/worker-artifacts"
|
|
33
37
|
},
|
|
34
|
-
"template": "{repo}/scripts/actor-worker.mjs --state-dir {state_dir} --run {run_id} --branch {branch} --poll-ms {poll_ms}"
|
|
38
|
+
"template": "{repo}/scripts/actor-worker.mjs --state-dir {state_dir} --run {run_id} --branch {branch} --poll-ms {poll_ms} --stale-claim-ms {stale_claim_ms} --write-artifacts {write_artifacts}"
|
|
35
39
|
}
|
package/recipes/lens-swarm.json
CHANGED