@llblab/pi-actors 0.35.0 → 0.37.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/AGENTS.md +3 -1
- package/BACKLOG.md +4 -72
- package/CHANGELOG.md +17 -3
- package/README.md +2 -1
- package/dist/index.js +0 -1
- package/dist/lib/async-runs.js +4 -3
- package/dist/lib/command-templates.d.ts +2 -0
- package/dist/lib/command-templates.js +95 -5
- package/dist/lib/recipes-discovery.d.ts +2 -0
- package/dist/lib/recipes-discovery.js +52 -5
- package/dist/lib/recipes-references.d.ts +1 -0
- package/dist/lib/recipes-references.js +122 -35
- package/dist/lib/registry.d.ts +1 -1
- package/dist/lib/registry.js +6 -6
- package/dist/lib/runtime-notifier.js +8 -3
- package/dist/lib/runtime.d.ts +2 -6
- package/dist/lib/runtime.js +10 -11
- package/dist/lib/tools-inspect.js +172 -4
- package/dist/lib/tools-response.js +13 -2
- package/dist/lib/tools.d.ts +1 -1
- package/dist/lib/tools.js +1 -1
- package/dist/scripts/validate-recipe.mjs +164 -7
- package/dist/skills/actors/SKILL.md +45 -13
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/recipe-library.md +2 -0
- package/docs/template-recipes.md +20 -1
- package/docs/tool-registry.md +15 -10
- package/index.ts +0 -1
- package/lib/async-runs.ts +4 -7
- package/lib/command-templates.ts +146 -7
- package/lib/recipes-discovery.ts +69 -6
- package/lib/recipes-references.ts +201 -34
- package/lib/registry.ts +5 -5
- package/lib/runtime-notifier.ts +9 -3
- package/lib/runtime.ts +10 -16
- package/lib/tools-inspect.ts +186 -4
- package/lib/tools-response.ts +16 -2
- package/lib/tools.ts +2 -2
- package/package.json +3 -2
- package/scripts/validate-recipe.mjs +164 -7
- package/skills/actors/SKILL.md +45 -13
- package/skills/swarm/SKILL.md +1 -1
|
@@ -40,19 +40,43 @@ function recipeNameFiles(value) {
|
|
|
40
40
|
return [trimmed];
|
|
41
41
|
return [`${trimmed}.json`, `${trimmed}.md`];
|
|
42
42
|
}
|
|
43
|
-
function
|
|
43
|
+
function recipeCandidatePaths(value, currentRecipeRoot) {
|
|
44
44
|
if (!isBareRecipeName(value))
|
|
45
|
-
return resolveRecipePath(value, currentRecipeRoot);
|
|
45
|
+
return [resolveRecipePath(value, currentRecipeRoot)];
|
|
46
46
|
const roots = [
|
|
47
47
|
Paths.getRecipeRoot(),
|
|
48
48
|
currentRecipeRoot,
|
|
49
49
|
Paths.getPackagedRecipeRoot(),
|
|
50
50
|
];
|
|
51
|
-
|
|
51
|
+
return [
|
|
52
52
|
...new Set(roots.flatMap((root) => recipeNameFiles(value).map((file) => resolve(root, file)))),
|
|
53
53
|
];
|
|
54
|
+
}
|
|
55
|
+
function resolveRecipeImportPath(value, currentRecipeRoot) {
|
|
56
|
+
const candidates = recipeCandidatePaths(value, currentRecipeRoot);
|
|
54
57
|
return candidates.find((candidate) => existsSync(candidate)) ?? candidates[0];
|
|
55
58
|
}
|
|
59
|
+
export function resolveRecipeReferencePath(value, currentRecipeRoot = Paths.getRecipeRoot()) {
|
|
60
|
+
if (typeof value !== "string")
|
|
61
|
+
return undefined;
|
|
62
|
+
const trimmed = value.trim();
|
|
63
|
+
if (!trimmed || hasWhitespace(trimmed))
|
|
64
|
+
return undefined;
|
|
65
|
+
for (const path of recipeCandidatePaths(trimmed, currentRecipeRoot)) {
|
|
66
|
+
if (!existsSync(path))
|
|
67
|
+
continue;
|
|
68
|
+
try {
|
|
69
|
+
const raw = readRawRecipeConfig(path);
|
|
70
|
+
if (raw && typeof raw === "object" && Object.hasOwn(raw, "template"))
|
|
71
|
+
return path;
|
|
72
|
+
return path;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return path;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return undefined;
|
|
79
|
+
}
|
|
56
80
|
export function getRecipePath(value, recipeRoot = Paths.getRecipeRoot()) {
|
|
57
81
|
if (typeof value !== "string")
|
|
58
82
|
return undefined;
|
|
@@ -61,20 +85,7 @@ export function getRecipePath(value, recipeRoot = Paths.getRecipeRoot()) {
|
|
|
61
85
|
return undefined;
|
|
62
86
|
if (trimmed.endsWith(".json") || trimmed.endsWith(".md"))
|
|
63
87
|
return resolveRecipePath(trimmed, recipeRoot);
|
|
64
|
-
|
|
65
|
-
const mdPath = resolveRecipePath(`${trimmed}.md`, recipeRoot);
|
|
66
|
-
const path = existsSync(jsonPath) ? jsonPath : mdPath;
|
|
67
|
-
if (!existsSync(path))
|
|
68
|
-
return undefined;
|
|
69
|
-
try {
|
|
70
|
-
const raw = readRawRecipeConfig(path);
|
|
71
|
-
return raw && typeof raw === "object" && Object.hasOwn(raw, "template")
|
|
72
|
-
? path
|
|
73
|
-
: undefined;
|
|
74
|
-
}
|
|
75
|
-
catch {
|
|
76
|
-
return undefined;
|
|
77
|
-
}
|
|
88
|
+
return resolveRecipeReferencePath(trimmed, recipeRoot);
|
|
78
89
|
}
|
|
79
90
|
function isImportNode(value) {
|
|
80
91
|
if (!isRecord(value) || Object.hasOwn(value, "template"))
|
|
@@ -344,7 +355,7 @@ export function getRecipeIdFromPath(file) {
|
|
|
344
355
|
return basename(file, extname(file));
|
|
345
356
|
}
|
|
346
357
|
function readRecipeConfig(value) {
|
|
347
|
-
const path =
|
|
358
|
+
const path = resolveRecipeReferencePath(value);
|
|
348
359
|
return path ? readResolvedRecipeConfig(path) : undefined;
|
|
349
360
|
}
|
|
350
361
|
function isRecord(value) {
|
|
@@ -483,6 +494,7 @@ function mergeDefaults(...items) {
|
|
|
483
494
|
function applyDefaultsToTemplate(template, defaults, overrides) {
|
|
484
495
|
const cleanOverrides = { ...overrides };
|
|
485
496
|
delete cleanOverrides.name;
|
|
497
|
+
delete cleanOverrides.template;
|
|
486
498
|
delete cleanOverrides.values;
|
|
487
499
|
if (typeof template === "object" && !Array.isArray(template)) {
|
|
488
500
|
return {
|
|
@@ -509,6 +521,56 @@ function withActorRecipeContext(value, context) {
|
|
|
509
521
|
}
|
|
510
522
|
return { actorRecipeContext: context, template: value };
|
|
511
523
|
}
|
|
524
|
+
function loadDelegatedRecipe(value, currentRecipeFile, stack, options) {
|
|
525
|
+
const path = resolveRecipeReferencePath(value, dirname(currentRecipeFile));
|
|
526
|
+
if (!path)
|
|
527
|
+
return undefined;
|
|
528
|
+
const config = readResolvedRecipeConfig(path, [...stack, currentRecipeFile], options);
|
|
529
|
+
if (!config)
|
|
530
|
+
throw new Error(`Template recipe must define template: ${path}`);
|
|
531
|
+
if (config.disabled === true)
|
|
532
|
+
throw new Error(`Template recipe is disabled: ${path}`);
|
|
533
|
+
return config;
|
|
534
|
+
}
|
|
535
|
+
function applyDelegatedRecipeToNode(delegated, overrides = {}) {
|
|
536
|
+
return applyDefaultsToTemplate(delegated.template, delegated.values, overrides);
|
|
537
|
+
}
|
|
538
|
+
function expandRecipeDelegations(value, currentRecipeFile, stack, options = {}) {
|
|
539
|
+
if (typeof value === "string") {
|
|
540
|
+
const delegated = loadDelegatedRecipe(value, currentRecipeFile, stack, options);
|
|
541
|
+
return delegated ? applyDelegatedRecipeToNode(delegated) : value;
|
|
542
|
+
}
|
|
543
|
+
if (Array.isArray(value)) {
|
|
544
|
+
return value.map((item) => expandRecipeDelegations(item, currentRecipeFile, stack, options));
|
|
545
|
+
}
|
|
546
|
+
const record = value;
|
|
547
|
+
if (typeof record.template === "string") {
|
|
548
|
+
const delegated = loadDelegatedRecipe(record.template, currentRecipeFile, stack, options);
|
|
549
|
+
if (delegated)
|
|
550
|
+
return applyDelegatedRecipeToNode(delegated, record);
|
|
551
|
+
}
|
|
552
|
+
if (Array.isArray(record.template)) {
|
|
553
|
+
return {
|
|
554
|
+
...record,
|
|
555
|
+
template: record.template.map((item) => expandRecipeDelegations(item, currentRecipeFile, stack, options)),
|
|
556
|
+
};
|
|
557
|
+
}
|
|
558
|
+
if (record.template && typeof record.template === "object") {
|
|
559
|
+
return {
|
|
560
|
+
...record,
|
|
561
|
+
template: expandRecipeDelegations(record.template, currentRecipeFile, stack, options),
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
return value;
|
|
565
|
+
}
|
|
566
|
+
function getDirectDelegatedRecipe(value, currentRecipeFile, stack, options = {}) {
|
|
567
|
+
if (typeof value === "string")
|
|
568
|
+
return loadDelegatedRecipe(value, currentRecipeFile, stack, options);
|
|
569
|
+
if (!Array.isArray(value) && typeof value.template === "string") {
|
|
570
|
+
return loadDelegatedRecipe(value.template, currentRecipeFile, stack, options);
|
|
571
|
+
}
|
|
572
|
+
return undefined;
|
|
573
|
+
}
|
|
512
574
|
function expandImportNodes(value, imports, options = {}) {
|
|
513
575
|
if (typeof value === "string")
|
|
514
576
|
return value;
|
|
@@ -585,7 +647,14 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
585
647
|
const template = getRecipeCommandTemplate(substituted);
|
|
586
648
|
if (!template)
|
|
587
649
|
return undefined;
|
|
588
|
-
const
|
|
650
|
+
const expandedImportsTemplate = expandImportNodes(template, imports, options);
|
|
651
|
+
const delegated = getDirectDelegatedRecipe(expandedImportsTemplate, path, stack, options);
|
|
652
|
+
const expandedTemplate = delegated
|
|
653
|
+
? applyDelegatedRecipeToNode(delegated, typeof expandedImportsTemplate === "object" &&
|
|
654
|
+
!Array.isArray(expandedImportsTemplate)
|
|
655
|
+
? expandedImportsTemplate
|
|
656
|
+
: {})
|
|
657
|
+
: expandRecipeDelegations(expandedImportsTemplate, path, stack, options);
|
|
589
658
|
const recipeName = getRecipeIdFromPath(path);
|
|
590
659
|
const templateWithContext = options.includeActorRecipeContext
|
|
591
660
|
? withActorRecipeContext(expandedTemplate, {
|
|
@@ -595,33 +664,50 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
595
664
|
role: stack.length > 0 ? "import" : "entry",
|
|
596
665
|
})
|
|
597
666
|
: expandedTemplate;
|
|
667
|
+
const mergedDefaults = mergeDefaults(delegated?.defaults, isRecord(substituted.defaults) ? substituted.defaults : undefined);
|
|
668
|
+
const artifactSource = isRecord(substituted.artifacts)
|
|
669
|
+
? substituted.artifacts
|
|
670
|
+
: delegated?.artifacts;
|
|
671
|
+
const mailboxSource = isRecord(substituted.mailbox)
|
|
672
|
+
? substituted.mailbox
|
|
673
|
+
: delegated?.mailbox;
|
|
598
674
|
return {
|
|
599
675
|
name: recipeName,
|
|
600
676
|
...(typeof substituted.description === "string" &&
|
|
601
677
|
substituted.description.trim()
|
|
602
678
|
? { description: substituted.description.trim() }
|
|
603
|
-
:
|
|
679
|
+
: typeof delegated?.description === "string"
|
|
680
|
+
? { description: delegated.description }
|
|
681
|
+
: {}),
|
|
604
682
|
...(typeof substituted.disabled === "boolean"
|
|
605
683
|
? { disabled: substituted.disabled }
|
|
606
|
-
:
|
|
684
|
+
: typeof delegated?.disabled === "boolean"
|
|
685
|
+
? { disabled: delegated.disabled }
|
|
686
|
+
: {}),
|
|
607
687
|
...(substituted.async === true
|
|
608
688
|
? { async: true }
|
|
609
689
|
: substituted.async === false
|
|
610
690
|
? { async: false }
|
|
611
|
-
:
|
|
691
|
+
: delegated?.async === true
|
|
692
|
+
? { async: true }
|
|
693
|
+
: delegated?.async === false
|
|
694
|
+
? { async: false }
|
|
695
|
+
: {}),
|
|
612
696
|
...(typeof substituted.state_dir === "string"
|
|
613
697
|
? { state_dir: substituted.state_dir }
|
|
614
|
-
:
|
|
698
|
+
: typeof delegated?.state_dir === "string"
|
|
699
|
+
? { state_dir: delegated.state_dir }
|
|
700
|
+
: {}),
|
|
615
701
|
...(Object.keys(imports).length > 0
|
|
616
702
|
? { imports: getRecipeImports(raw) }
|
|
617
703
|
: {}),
|
|
618
704
|
template: templateWithContext,
|
|
619
705
|
...(Array.isArray(substituted.args)
|
|
620
706
|
? { args: substituted.args }
|
|
621
|
-
:
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
707
|
+
: Array.isArray(delegated?.args)
|
|
708
|
+
? { args: delegated.args }
|
|
709
|
+
: {}),
|
|
710
|
+
...(mergedDefaults ? { defaults: mergedDefaults } : {}),
|
|
625
711
|
...(typeof substituted.parallel === "boolean"
|
|
626
712
|
? { parallel: substituted.parallel }
|
|
627
713
|
: {}),
|
|
@@ -643,30 +729,31 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
643
729
|
...(typeof substituted.output === "string"
|
|
644
730
|
? { output: substituted.output }
|
|
645
731
|
: {}),
|
|
646
|
-
...(isRecord(
|
|
732
|
+
...(isRecord(artifactSource)
|
|
647
733
|
? {
|
|
648
|
-
artifacts: Object.fromEntries(Object.entries(
|
|
734
|
+
artifacts: Object.fromEntries(Object.entries(artifactSource).filter((entry) => typeof entry[1] === "string")),
|
|
649
735
|
}
|
|
650
736
|
: {}),
|
|
651
|
-
...(isRecord(
|
|
737
|
+
...(isRecord(mailboxSource)
|
|
652
738
|
? {
|
|
653
739
|
mailbox: {
|
|
654
|
-
...(Array.isArray(
|
|
740
|
+
...(Array.isArray(mailboxSource.accepts)
|
|
655
741
|
? {
|
|
656
|
-
accepts:
|
|
742
|
+
accepts: mailboxSource.accepts.filter((value) => typeof value === "string" ||
|
|
657
743
|
(isRecord(value) && typeof value.type === "string")),
|
|
658
744
|
}
|
|
659
745
|
: {}),
|
|
660
|
-
...(Array.isArray(
|
|
746
|
+
...(Array.isArray(mailboxSource.emits)
|
|
661
747
|
? {
|
|
662
|
-
emits:
|
|
748
|
+
emits: mailboxSource.emits.filter((value) => typeof value === "string" ||
|
|
663
749
|
(isRecord(value) && typeof value.type === "string")),
|
|
664
750
|
}
|
|
665
751
|
: {}),
|
|
666
752
|
},
|
|
667
753
|
}
|
|
668
754
|
: {}),
|
|
669
|
-
...(substituted.retire_when === "children_terminal"
|
|
755
|
+
...(substituted.retire_when === "children_terminal" ||
|
|
756
|
+
delegated?.retire_when === "children_terminal"
|
|
670
757
|
? { retire_when: "children_terminal" }
|
|
671
758
|
: {}),
|
|
672
759
|
...(typeof substituted.retry === "number" ||
|
package/dist/lib/registry.d.ts
CHANGED
|
@@ -39,7 +39,7 @@ export interface RegisterToolResult {
|
|
|
39
39
|
export interface RegisterToolRuntimeDeps<TContext> {
|
|
40
40
|
configPath: string;
|
|
41
41
|
recipeRoot?: string;
|
|
42
|
-
|
|
42
|
+
getToolNameBlocker: (name: string) => string | undefined;
|
|
43
43
|
getTools: () => Map<string, Config.RegisteredTool>;
|
|
44
44
|
getActiveTools: () => string[];
|
|
45
45
|
notify: (ctx: TContext, message: string, type: "info" | "warning" | "error") => void;
|
package/dist/lib/registry.js
CHANGED
|
@@ -51,9 +51,9 @@ function promoteDraftRecipe(name, input, ctx, deps) {
|
|
|
51
51
|
const targetPath = getToolRecipePath(deps, name);
|
|
52
52
|
const tools = deps.getTools();
|
|
53
53
|
const existing = tools.get(name);
|
|
54
|
-
const
|
|
55
|
-
if (
|
|
56
|
-
throw new Error(ExecutionOutput.formatToolText(
|
|
54
|
+
const blocker = deps.getToolNameBlocker(name);
|
|
55
|
+
if (blocker)
|
|
56
|
+
throw new Error(ExecutionOutput.formatToolText(blocker));
|
|
57
57
|
if ((existing || existsSync(targetPath)) && !input.update) {
|
|
58
58
|
throw new Error(ExecutionOutput.formatToolText(`Tool "${name}" already registered. Use update=true to overwrite.`));
|
|
59
59
|
}
|
|
@@ -238,9 +238,9 @@ export async function executeRegisterTool(params, ctx, deps) {
|
|
|
238
238
|
return deleteTool(name, ctx, deps);
|
|
239
239
|
const tools = deps.getTools();
|
|
240
240
|
const existing = tools.get(name);
|
|
241
|
-
const
|
|
242
|
-
if (
|
|
243
|
-
throw new Error(ExecutionOutput.formatToolText(
|
|
241
|
+
const blocker = deps.getToolNameBlocker(name);
|
|
242
|
+
if (blocker)
|
|
243
|
+
throw new Error(ExecutionOutput.formatToolText(blocker));
|
|
244
244
|
if (existing && !input.update) {
|
|
245
245
|
throw new Error(ExecutionOutput.formatToolText(`Tool "${name}" already registered. Use update=true to overwrite.`));
|
|
246
246
|
}
|
|
@@ -74,6 +74,7 @@ export function createFileRuntimeNotifier(stateDir, options = {}) {
|
|
|
74
74
|
subscribe: (actor, onWake, subscribeOptions = {}) => {
|
|
75
75
|
mkdirSync(dirname(file), { recursive: true });
|
|
76
76
|
let position = options.replay || !existsSync(file) ? 0 : statSync(file).size;
|
|
77
|
+
let pending = "";
|
|
77
78
|
let closed = false;
|
|
78
79
|
const reconcile = (reason) => {
|
|
79
80
|
if (closed)
|
|
@@ -89,11 +90,15 @@ export function createFileRuntimeNotifier(stateDir, options = {}) {
|
|
|
89
90
|
if (closed || !existsSync(file))
|
|
90
91
|
return;
|
|
91
92
|
const buffer = readFileSync(file);
|
|
92
|
-
if (position > buffer.length)
|
|
93
|
+
if (position > buffer.length) {
|
|
93
94
|
position = 0;
|
|
94
|
-
|
|
95
|
+
pending = "";
|
|
96
|
+
}
|
|
97
|
+
const chunk = pending + buffer.subarray(position).toString("utf8");
|
|
95
98
|
position = buffer.length;
|
|
96
|
-
|
|
99
|
+
const lines = chunk.split("\n");
|
|
100
|
+
pending = chunk.endsWith("\n") ? "" : (lines.pop() ?? "");
|
|
101
|
+
for (const line of lines) {
|
|
97
102
|
if (!line.trim())
|
|
98
103
|
continue;
|
|
99
104
|
const event = parseRuntimeWakeEventLine(line);
|
package/dist/lib/runtime.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tool registry runtime coordinator
|
|
3
3
|
* Zones: runtime coordination, registry loading, pi tools
|
|
4
|
-
* Owns persisted tool loading,
|
|
4
|
+
* Owns persisted tool loading, reserved-name guards, runtime registration, and warning notification
|
|
5
5
|
*/
|
|
6
6
|
import * as Config from "./config.ts";
|
|
7
7
|
import type { RegisteredToolExec } from "./execution.ts";
|
|
@@ -12,22 +12,18 @@ export interface RuntimeContext {
|
|
|
12
12
|
notify(message: string, type?: "info" | "warning" | "error"): void;
|
|
13
13
|
};
|
|
14
14
|
}
|
|
15
|
-
export interface ToolInfoLike {
|
|
16
|
-
name: string;
|
|
17
|
-
}
|
|
18
15
|
export interface ToolRegistryRuntimeDeps {
|
|
19
16
|
configPath: string;
|
|
20
17
|
exec: RegisteredToolExec;
|
|
21
18
|
packagedRecipeRoot?: string;
|
|
22
19
|
recipeRoot?: string;
|
|
23
20
|
getActiveTools?: () => string[];
|
|
24
|
-
getAllTools: () => ToolInfoLike[];
|
|
25
21
|
registerTool: (definition: ReturnType<typeof ToolsLocal.createRuntimeToolDefinition>) => void;
|
|
26
22
|
reservedToolNames: Set<string>;
|
|
27
23
|
setActiveTools?: (toolNames: string[]) => void;
|
|
28
24
|
}
|
|
29
25
|
export interface ToolRegistryRuntime {
|
|
30
|
-
|
|
26
|
+
getToolNameBlocker(name: string): string | undefined;
|
|
31
27
|
getTools(): Map<string, Config.RegisteredTool>;
|
|
32
28
|
loadTools(ctx: RuntimeContext): void;
|
|
33
29
|
notify(ctx: RuntimeContext, message: string, type: "info" | "warning" | "error"): void;
|
package/dist/lib/runtime.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Tool registry runtime coordinator
|
|
3
3
|
* Zones: runtime coordination, registry loading, pi tools
|
|
4
|
-
* Owns persisted tool loading,
|
|
4
|
+
* Owns persisted tool loading, reserved-name guards, runtime registration, and warning notification
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, watch } from "node:fs";
|
|
7
7
|
import * as Paths from "./paths.js";
|
|
@@ -15,12 +15,9 @@ export function createAutoToolsRuntime(deps) {
|
|
|
15
15
|
if (ctx.hasUI)
|
|
16
16
|
ctx.ui.notify(message, type);
|
|
17
17
|
}
|
|
18
|
-
function
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const existing = deps.getAllTools().find((tool) => tool.name === name);
|
|
22
|
-
return existing
|
|
23
|
-
? `Tool "${name}" is already registered outside pi-actors.`
|
|
18
|
+
function getToolNameBlocker(name) {
|
|
19
|
+
return deps.reservedToolNames.has(name)
|
|
20
|
+
? `Reserved tool name: ${name}`
|
|
24
21
|
: undefined;
|
|
25
22
|
}
|
|
26
23
|
function getToolFingerprint(cfg) {
|
|
@@ -55,6 +52,8 @@ export function createAutoToolsRuntime(deps) {
|
|
|
55
52
|
runtimeToolFingerprints.set(cfg.name, fingerprint);
|
|
56
53
|
}
|
|
57
54
|
function isStartupActionableRegistryWarning(warning) {
|
|
55
|
+
if (warning.includes(" shadows "))
|
|
56
|
+
return false;
|
|
58
57
|
if (warning.includes("invokes bash;") &&
|
|
59
58
|
warning.includes("trusted executable content"))
|
|
60
59
|
return false;
|
|
@@ -104,9 +103,9 @@ export function createAutoToolsRuntime(deps) {
|
|
|
104
103
|
}
|
|
105
104
|
deactivateMissingRuntimeTools(new Set(tools.keys()));
|
|
106
105
|
for (const cfg of tools.values()) {
|
|
107
|
-
const
|
|
108
|
-
if (
|
|
109
|
-
warnings.push(
|
|
106
|
+
const blocker = getToolNameBlocker(cfg.name);
|
|
107
|
+
if (blocker) {
|
|
108
|
+
warnings.push(blocker);
|
|
110
109
|
continue;
|
|
111
110
|
}
|
|
112
111
|
registerRuntimeTool(cfg);
|
|
@@ -117,7 +116,7 @@ export function createAutoToolsRuntime(deps) {
|
|
|
117
116
|
}
|
|
118
117
|
}
|
|
119
118
|
return {
|
|
120
|
-
|
|
119
|
+
getToolNameBlocker,
|
|
121
120
|
getTools: () => tools,
|
|
122
121
|
loadTools,
|
|
123
122
|
notify,
|
|
@@ -212,6 +212,170 @@ function getPiActorsRuntimeStatus() {
|
|
|
212
212
|
function compactPiActorsRuntimeStatus(status) {
|
|
213
213
|
return `\npi-actors version=${String(status.version)} mode=${String(status.mode)} path=${String(status.package_root)} entrypoint=${String(status.entrypoint)}${status.git_commit ? ` git=${String(status.git_commit)}` : ""}`;
|
|
214
214
|
}
|
|
215
|
+
function isStaleClaim(message, now) {
|
|
216
|
+
if (message.status !== "claimed")
|
|
217
|
+
return false;
|
|
218
|
+
const claimedAt = Date.parse(String(message.claimed_at ?? ""));
|
|
219
|
+
return Number.isFinite(claimedAt) && now - claimedAt > 5 * 60 * 1000;
|
|
220
|
+
}
|
|
221
|
+
function getRunTriageSignals(runs) {
|
|
222
|
+
const now = Date.now();
|
|
223
|
+
const staleClaims = [];
|
|
224
|
+
const attentionMessages = [];
|
|
225
|
+
for (const run of runs) {
|
|
226
|
+
const stateDir = String(run.state_dir ?? "");
|
|
227
|
+
const runId = String(run.run ?? "");
|
|
228
|
+
if (!stateDir)
|
|
229
|
+
continue;
|
|
230
|
+
try {
|
|
231
|
+
for (const message of AsyncRuns.readRunInboxMessages(stateDir, 200)) {
|
|
232
|
+
if (!isStaleClaim(message, now))
|
|
233
|
+
continue;
|
|
234
|
+
staleClaims.push({
|
|
235
|
+
run: runId,
|
|
236
|
+
id: message.id,
|
|
237
|
+
claimed_at: message.claimed_at,
|
|
238
|
+
claimed_by: message.claimed_by,
|
|
239
|
+
type: message.type,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
catch { }
|
|
244
|
+
try {
|
|
245
|
+
for (const event of AsyncRuns.readRunEvents(stateDir, 80)) {
|
|
246
|
+
if (event.metadata?.requires_response !== true)
|
|
247
|
+
continue;
|
|
248
|
+
attentionMessages.push({
|
|
249
|
+
run: runId,
|
|
250
|
+
id: event.id,
|
|
251
|
+
summary: event.summary,
|
|
252
|
+
type: event.type ?? event.event,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
catch { }
|
|
257
|
+
}
|
|
258
|
+
return { attention_messages: attentionMessages, stale_claims: staleClaims };
|
|
259
|
+
}
|
|
260
|
+
function isTriageHighRiskRecipe(recipe) {
|
|
261
|
+
if (recipe.tool !== true)
|
|
262
|
+
return false;
|
|
263
|
+
const labels = Array.isArray(recipe.risk_labels)
|
|
264
|
+
? recipe.risk_labels.map((label) => String(label))
|
|
265
|
+
: [];
|
|
266
|
+
return labels.some((label) => label !== "risk.long_running");
|
|
267
|
+
}
|
|
268
|
+
function getPiActorsTriage(ctx, deps) {
|
|
269
|
+
const runtime = getPiActorsRuntimeStatus();
|
|
270
|
+
const currentSession = ToolsAccess.getContextSessionId(ctx);
|
|
271
|
+
const allRuns = AsyncRuns.listRuns().map((run) => AsyncRuns.getRunStatus(String(run.state_dir)));
|
|
272
|
+
const visibleRuns = currentSession
|
|
273
|
+
? allRuns.filter((run) => !run.ownerId || run.ownerId === currentSession)
|
|
274
|
+
: allRuns;
|
|
275
|
+
const activeRuns = visibleRuns.filter((run) => run.status === "running");
|
|
276
|
+
const failedRuns = visibleRuns.filter((run) => run.status === "failed");
|
|
277
|
+
const otherRuns = currentSession
|
|
278
|
+
? allRuns.filter((run) => run.ownerId && run.ownerId !== currentSession)
|
|
279
|
+
: [];
|
|
280
|
+
const recipeRoot = deps.recipeRoot ?? Paths.getRecipeRoot();
|
|
281
|
+
const discovered = RecipesDiscovery.discoverRecipeSources([
|
|
282
|
+
{ root: recipeRoot, defaultTool: true, mutableUsage: true },
|
|
283
|
+
{ root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
|
|
284
|
+
]);
|
|
285
|
+
const recipeSummary = {
|
|
286
|
+
...RecipesDiscovery.summarizeDiscovery(discovered),
|
|
287
|
+
drafts: RecipesDiscovery.listDraftRecipes(join(recipeRoot, "drafts")),
|
|
288
|
+
};
|
|
289
|
+
const activeRecipes = Array.isArray(recipeSummary.active)
|
|
290
|
+
? recipeSummary.active
|
|
291
|
+
: [];
|
|
292
|
+
const highRiskRecipes = activeRecipes.filter(isTriageHighRiskRecipe);
|
|
293
|
+
const signals = getRunTriageSignals(visibleRuns);
|
|
294
|
+
const attentionMessages = signals.attention_messages;
|
|
295
|
+
const staleClaims = signals.stale_claims;
|
|
296
|
+
const invalidRecipes = Array.isArray(recipeSummary.invalid)
|
|
297
|
+
? recipeSummary.invalid
|
|
298
|
+
: [];
|
|
299
|
+
const remediations = Array.isArray(recipeSummary.remediations)
|
|
300
|
+
? recipeSummary.remediations
|
|
301
|
+
: [];
|
|
302
|
+
const drafts = Array.isArray(recipeSummary.drafts)
|
|
303
|
+
? recipeSummary.drafts
|
|
304
|
+
: [];
|
|
305
|
+
const nextActions = [
|
|
306
|
+
invalidRecipes.length || remediations.length
|
|
307
|
+
? "inspect target=recipes view=doctor"
|
|
308
|
+
: "",
|
|
309
|
+
drafts.length ? "inspect target=recipes view=summary verbose=true" : "",
|
|
310
|
+
failedRuns[0]?.run
|
|
311
|
+
? `inspect target=run:${String(failedRuns[0].run)} view=tail lines=80`
|
|
312
|
+
: "",
|
|
313
|
+
attentionMessages[0]?.run
|
|
314
|
+
? `inspect target=run:${String(attentionMessages[0].run)} view=messages`
|
|
315
|
+
: "",
|
|
316
|
+
activeRuns.length
|
|
317
|
+
? "inspect target=session:all view=runs status=active"
|
|
318
|
+
: "",
|
|
319
|
+
].filter(Boolean);
|
|
320
|
+
return {
|
|
321
|
+
runtime,
|
|
322
|
+
current_session: currentSession ?? null,
|
|
323
|
+
active_runs: activeRuns.map((run) => ({
|
|
324
|
+
run: run.run,
|
|
325
|
+
ownerId: run.ownerId,
|
|
326
|
+
recipe: run.recipe,
|
|
327
|
+
status: run.status,
|
|
328
|
+
})),
|
|
329
|
+
other_session_runs: otherRuns.length,
|
|
330
|
+
invalid_recipes: invalidRecipes,
|
|
331
|
+
blocking_recipes: remediations.filter((item) => String(item.kind ?? "").startsWith("blocking_")),
|
|
332
|
+
high_risk_recipes: highRiskRecipes.map((recipe) => ({
|
|
333
|
+
id: recipe.id,
|
|
334
|
+
path: recipe.path,
|
|
335
|
+
risk_labels: recipe.risk_labels,
|
|
336
|
+
})),
|
|
337
|
+
draft_recipes: drafts,
|
|
338
|
+
stale_claims: staleClaims,
|
|
339
|
+
recent_failed_runs: failedRuns.slice(0, 5).map((run) => ({
|
|
340
|
+
run: run.run,
|
|
341
|
+
recipe: run.recipe,
|
|
342
|
+
status: run.status,
|
|
343
|
+
})),
|
|
344
|
+
attention_messages: attentionMessages.slice(-10),
|
|
345
|
+
next_actions: [...new Set(nextActions)].slice(0, 5),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function compactPiActorsTriage(summary) {
|
|
349
|
+
const runtime = asRecord(summary.runtime);
|
|
350
|
+
const activeRuns = Array.isArray(summary.active_runs)
|
|
351
|
+
? summary.active_runs.length
|
|
352
|
+
: 0;
|
|
353
|
+
const invalidRecipes = Array.isArray(summary.invalid_recipes)
|
|
354
|
+
? summary.invalid_recipes.length
|
|
355
|
+
: 0;
|
|
356
|
+
const blockingRecipes = Array.isArray(summary.blocking_recipes)
|
|
357
|
+
? summary.blocking_recipes.length
|
|
358
|
+
: 0;
|
|
359
|
+
const highRiskRecipes = Array.isArray(summary.high_risk_recipes)
|
|
360
|
+
? summary.high_risk_recipes.length
|
|
361
|
+
: 0;
|
|
362
|
+
const drafts = Array.isArray(summary.draft_recipes)
|
|
363
|
+
? summary.draft_recipes.length
|
|
364
|
+
: 0;
|
|
365
|
+
const staleClaims = Array.isArray(summary.stale_claims)
|
|
366
|
+
? summary.stale_claims.length
|
|
367
|
+
: 0;
|
|
368
|
+
const failedRuns = Array.isArray(summary.recent_failed_runs)
|
|
369
|
+
? summary.recent_failed_runs.length
|
|
370
|
+
: 0;
|
|
371
|
+
const attention = Array.isArray(summary.attention_messages)
|
|
372
|
+
? summary.attention_messages.length
|
|
373
|
+
: 0;
|
|
374
|
+
const nextActions = Array.isArray(summary.next_actions)
|
|
375
|
+
? summary.next_actions
|
|
376
|
+
: [];
|
|
377
|
+
return `\ntriage version=${String(runtime.version ?? "unknown")} mode=${String(runtime.mode ?? "unknown")} active_runs=${activeRuns} other_runs=${String(summary.other_session_runs ?? 0)} invalid_recipes=${invalidRecipes} blocking_recipes=${blockingRecipes} high_risk_recipes=${highRiskRecipes} drafts=${drafts} stale_claims=${staleClaims} failed_runs=${failedRuns} attention=${attention}${ToolsResponse.compactNextActions(nextActions)}`;
|
|
378
|
+
}
|
|
215
379
|
function compactToolActor(name, tool) {
|
|
216
380
|
const parameters = asRecord(tool.parameters);
|
|
217
381
|
const required = Array.isArray(parameters.required)
|
|
@@ -314,15 +478,19 @@ export function createInspectToolDefinition(deps = {}) {
|
|
|
314
478
|
}
|
|
315
479
|
if (address.kind === "tool" && address.value) {
|
|
316
480
|
if (address.value === "pi-actors") {
|
|
317
|
-
if (view !== "status") {
|
|
318
|
-
throw new Error("inspect tool:pi-actors supports view=status.");
|
|
481
|
+
if (view !== "status" && view !== "triage") {
|
|
482
|
+
throw new Error("inspect tool:pi-actors supports view=status or view=triage.");
|
|
319
483
|
}
|
|
320
|
-
const details =
|
|
484
|
+
const details = view === "triage"
|
|
485
|
+
? getPiActorsTriage(ctx, deps)
|
|
486
|
+
: getPiActorsRuntimeStatus();
|
|
321
487
|
return {
|
|
322
488
|
content: [
|
|
323
489
|
{
|
|
324
490
|
type: "text",
|
|
325
|
-
text: maybeJsonText(details, input.verbose === true,
|
|
491
|
+
text: maybeJsonText(details, input.verbose === true, view === "triage"
|
|
492
|
+
? compactPiActorsTriage(details)
|
|
493
|
+
: compactPiActorsRuntimeStatus(details)),
|
|
326
494
|
},
|
|
327
495
|
],
|
|
328
496
|
details,
|
|
@@ -143,15 +143,23 @@ export function compactRecipeDoctor(summary) {
|
|
|
143
143
|
const recommendations = Array.isArray(summary.recommendations)
|
|
144
144
|
? summary.recommendations
|
|
145
145
|
: [];
|
|
146
|
+
const riskSummary = Array.isArray(summary.risk_summary)
|
|
147
|
+
? summary.risk_summary
|
|
148
|
+
: [];
|
|
146
149
|
const counts = { error: 0, info: 0, warning: 0 };
|
|
147
150
|
for (const detail of details) {
|
|
148
151
|
const severity = String(detail.severity ?? "info");
|
|
149
152
|
if (severity === "error" || severity === "warning" || severity === "info")
|
|
150
153
|
counts[severity] += 1;
|
|
151
154
|
}
|
|
155
|
+
const riskCount = riskSummary.reduce((total, item) => total + Number(item.count ?? 0), 0);
|
|
156
|
+
const topRisks = riskSummary
|
|
157
|
+
.slice(0, 4)
|
|
158
|
+
.map((item) => `${String(item.label)}:${String(item.count ?? 0)}`)
|
|
159
|
+
.join(",");
|
|
152
160
|
const topAction = asRecord(summary.top_action);
|
|
153
161
|
const lines = [
|
|
154
|
-
`recipes doctor errors=${counts.error} warnings=${counts.warning} info=${counts.info} actions=${remediations.length} recommendations=${recommendations.length}`,
|
|
162
|
+
`recipes doctor errors=${counts.error} warnings=${counts.warning} info=${counts.info} actions=${remediations.length} recommendations=${recommendations.length} risks=${riskCount}${topRisks ? ` top_risks=${topRisks}` : ""}`,
|
|
155
163
|
];
|
|
156
164
|
if (Object.keys(topAction).length > 0) {
|
|
157
165
|
const action = compactPreview(topAction.action, Limits.DOCTOR_ACTION_PREVIEW_CHARS);
|
|
@@ -162,7 +170,10 @@ export function compactRecipeDoctor(summary) {
|
|
|
162
170
|
const blocked = item.blocked_fallback
|
|
163
171
|
? ` blocked=${compactPreview(item.blocked_fallback, Limits.DOCTOR_ACTION_PREVIEW_CHARS)}`
|
|
164
172
|
: "";
|
|
165
|
-
|
|
173
|
+
const labels = Array.isArray(item.risk_labels)
|
|
174
|
+
? ` labels=${item.risk_labels.map(String).slice(0, 4).join(",")}`
|
|
175
|
+
: "";
|
|
176
|
+
lines.push(`${String(item.severity ?? "info")} kind=${String(item.kind ?? "inspect")} id=${String(item.id ?? "root")}${blocked}${labels} action=${action ?? "inspect"}`);
|
|
166
177
|
}
|
|
167
178
|
const nextActions = Array.isArray(summary.next_actions)
|
|
168
179
|
? summary.next_actions
|
package/dist/lib/tools.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export interface CoreActorToolDefinitionDeps<TContext extends RuntimeToolContext
|
|
|
15
15
|
configPath: string;
|
|
16
16
|
getActiveTools: () => string[];
|
|
17
17
|
getRuntimeTool: (name: string) => unknown;
|
|
18
|
-
registryRuntime: Pick<RegisterToolRuntimeDeps<TContext>, "
|
|
18
|
+
registryRuntime: Pick<RegisterToolRuntimeDeps<TContext>, "getToolNameBlocker" | "getTools" | "notify" | "registerRuntimeTool">;
|
|
19
19
|
setActiveTools: (toolNames: string[]) => void;
|
|
20
20
|
}
|
|
21
21
|
export declare const RESERVED_TOOL_NAMES: Set<string>;
|
package/dist/lib/tools.js
CHANGED
|
@@ -25,7 +25,7 @@ export function createCoreActorToolDefinitions(deps) {
|
|
|
25
25
|
ToolsRegister.createRegisterToolDefinition({
|
|
26
26
|
configPath: deps.configPath,
|
|
27
27
|
getActiveTools: deps.getActiveTools,
|
|
28
|
-
|
|
28
|
+
getToolNameBlocker: deps.registryRuntime.getToolNameBlocker,
|
|
29
29
|
getTools: deps.registryRuntime.getTools,
|
|
30
30
|
notify: deps.registryRuntime.notify,
|
|
31
31
|
registerRuntimeTool: deps.registryRuntime.registerRuntimeTool,
|