@llblab/pi-actors 0.43.1 → 0.45.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 +6 -6
- package/BACKLOG.md +1 -189
- package/CHANGELOG.md +419 -402
- package/README.md +8 -6
- package/dist/index.js +7 -3
- package/dist/lib/async-runs.d.ts +2 -0
- package/dist/lib/async-runs.js +41 -34
- package/dist/lib/config.js +1 -0
- package/dist/lib/file-state.d.ts +3 -1
- package/dist/lib/file-state.js +25 -7
- package/dist/lib/inspector-overlay.js +46 -30
- package/dist/lib/inspector.js +8 -2
- package/dist/lib/limits.d.ts +10 -0
- package/dist/lib/limits.js +10 -0
- package/dist/lib/observability.d.ts +4 -2
- package/dist/lib/observability.js +43 -36
- package/dist/lib/recipes-discovery.js +20 -3
- package/dist/lib/recipes-references.d.ts +10 -0
- package/dist/lib/recipes-references.js +168 -5
- package/dist/lib/run-evidence-policy.d.ts +95 -0
- package/dist/lib/run-evidence-policy.js +177 -0
- package/dist/lib/run-ui-runtime.js +2 -0
- package/dist/lib/runs-controls.d.ts +7 -5
- package/dist/lib/runs-controls.js +186 -49
- package/dist/lib/runs-retention.js +27 -14
- package/dist/lib/runs-trace.d.ts +25 -1
- package/dist/lib/runs-trace.js +410 -21
- package/dist/lib/runtime-triage.js +6 -22
- package/dist/lib/schema.d.ts +3 -0
- package/dist/lib/schema.js +34 -1
- package/dist/lib/tools-inspect.js +58 -14
- package/dist/lib/tools-local.js +27 -10
- package/dist/lib/tools-spawn.js +2 -2
- package/dist/lib/trace-projection.js +90 -44
- package/dist/scripts/conformance.mjs +5 -0
- package/dist/scripts/locker.mjs +31 -74
- package/dist/scripts/music-player.mjs +41 -129
- package/dist/scripts/release-gates.mjs +26 -4
- package/dist/skills/actors/SKILL.md +9 -8
- package/dist/skills/swarm/SKILL.md +2 -2
- package/docs/README.md +0 -1
- package/docs/actor-inspector.md +3 -2
- package/docs/async-runs.md +10 -8
- package/docs/command-templates.md +2 -2
- package/docs/recipe-library.md +4 -2
- package/docs/template-recipes.md +10 -4
- package/docs/tool-registry.md +1 -1
- package/index.ts +9 -3
- package/lib/async-runs.ts +51 -54
- package/lib/config.ts +4 -0
- package/lib/file-state.ts +28 -7
- package/lib/inspector-overlay.ts +29 -17
- package/lib/inspector.ts +16 -2
- package/lib/limits.ts +10 -0
- package/lib/observability.ts +55 -57
- package/lib/recipes-discovery.ts +23 -3
- package/lib/recipes-references.ts +227 -7
- package/lib/run-evidence-policy.ts +242 -0
- package/lib/run-ui-runtime.ts +2 -0
- package/lib/runs-controls.ts +177 -108
- package/lib/runs-retention.ts +28 -20
- package/lib/runs-trace.ts +496 -21
- package/lib/runtime-triage.ts +11 -25
- package/lib/schema.ts +53 -1
- package/lib/tools-inspect.ts +56 -15
- package/lib/tools-local.ts +49 -19
- package/lib/tools-spawn.ts +2 -2
- package/lib/trace-projection.ts +137 -75
- package/package.json +1 -1
- package/scripts/conformance.mjs +5 -0
- package/scripts/locker.mjs +31 -74
- package/scripts/music-player.mjs +41 -129
- package/scripts/release-gates.mjs +26 -4
- package/skills/actors/SKILL.md +9 -8
- package/skills/swarm/SKILL.md +2 -2
- package/dist/lib/runtime-notifier.d.ts +0 -48
- package/dist/lib/runtime-notifier.js +0 -138
- package/docs/0.43-baseline.md +0 -39
- package/lib/runtime-notifier.ts +0 -211
|
@@ -7,29 +7,33 @@ import { closeSync, existsSync, fstatSync, openSync, readdirSync, readFileSync,
|
|
|
7
7
|
import { basename, dirname, isAbsolute, join, relative, } from "node:path";
|
|
8
8
|
import * as AsyncRuns from "./async-runs.js";
|
|
9
9
|
import * as Paths from "./paths.js";
|
|
10
|
+
import * as RunsTrace from "./runs-trace.js";
|
|
10
11
|
import { readJsonlFileResilient } from "./state-readers.js";
|
|
11
12
|
export function createRunUiObservationState() {
|
|
12
13
|
return {
|
|
13
14
|
attentionEventIds: new Map(),
|
|
14
|
-
|
|
15
|
+
legacyEventLines: new Map(),
|
|
15
16
|
frame: 0,
|
|
16
17
|
observed: new Map(),
|
|
17
18
|
};
|
|
18
19
|
}
|
|
20
|
+
export function primeRunAttentionState(state, ownerId, stateRoot) {
|
|
21
|
+
detectRunAttentionEvents(state.legacyEventLines, summarizeRuns(stateRoot, ownerId), state.attentionEventIds, true);
|
|
22
|
+
}
|
|
19
23
|
export function readRunUiSnapshot(state, ownerId, options = {}) {
|
|
20
24
|
const summary = summarizeRuns(options.stateRoot, ownerId);
|
|
21
25
|
const status = renderRunStatus(summary, state.frame++);
|
|
22
26
|
return {
|
|
23
27
|
attentionEvents: options.includeAttention === false
|
|
24
28
|
? []
|
|
25
|
-
: detectRunAttentionEvents(state.
|
|
29
|
+
: detectRunAttentionEvents(state.legacyEventLines, summary, state.attentionEventIds),
|
|
26
30
|
status,
|
|
27
31
|
summary,
|
|
28
32
|
transitions: detectRunTransitions(state.observed, summary),
|
|
29
33
|
};
|
|
30
34
|
}
|
|
31
35
|
export function pruneRunUiObservationState(state, snapshot) {
|
|
32
|
-
pruneRunObservationState(state.observed, state.
|
|
36
|
+
pruneRunObservationState(state.observed, state.legacyEventLines, snapshot.summary, snapshot.transitions.map((transition) => transition.stateDir ?? transition.run), state.attentionEventIds);
|
|
33
37
|
}
|
|
34
38
|
export function deliverRunTransitionNotifications(transitions, sink, inFlight = new Set()) {
|
|
35
39
|
for (const transition of transitions) {
|
|
@@ -68,9 +72,11 @@ export function deliverRunTransitionNotifications(transitions, sink, inFlight =
|
|
|
68
72
|
}
|
|
69
73
|
export function reconcileRunTerminalNotifications(input) {
|
|
70
74
|
const snapshot = readRunUiSnapshot(input.state, input.ownerId, {
|
|
71
|
-
includeAttention:
|
|
75
|
+
includeAttention: input.includeAttention,
|
|
72
76
|
stateRoot: input.stateRoot,
|
|
73
77
|
});
|
|
78
|
+
if (input.includeAttention)
|
|
79
|
+
deliverRunAttentionNotifications(snapshot.attentionEvents, input.sink);
|
|
74
80
|
deliverRunTransitionNotifications(snapshot.transitions, input.sink, input.inFlight);
|
|
75
81
|
pruneRunUiObservationState(input.state, snapshot);
|
|
76
82
|
return snapshot;
|
|
@@ -769,12 +775,13 @@ function parseAttentionRecord(raw, run, index) {
|
|
|
769
775
|
}
|
|
770
776
|
function readTraceAttentionRecords(run) {
|
|
771
777
|
if (!run.stateDir)
|
|
772
|
-
return [];
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
+
return { canonical: true, records: [] };
|
|
779
|
+
if (existsSync(join(run.stateDir, "trace.jsonl")))
|
|
780
|
+
return {
|
|
781
|
+
canonical: true,
|
|
782
|
+
records: RunsTrace.readRunTraceJournal(run.stateDir).events.map(({ event }) => event),
|
|
783
|
+
};
|
|
784
|
+
return { canonical: false, records: readJsonlFileResilient(join(run.stateDir, "outbox.jsonl")).records };
|
|
778
785
|
}
|
|
779
786
|
export function pruneRunObservationState(previousStatuses, previousLineCounts, summary, terminalRuns = [], seenEventIds = new Map()) {
|
|
780
787
|
const activeRuns = new Set(summary.runs.map((run) => runObservationKey(run)));
|
|
@@ -785,43 +792,43 @@ export function pruneRunObservationState(previousStatuses, previousLineCounts, s
|
|
|
785
792
|
const activeLineKeys = new Set(summary.runs.map((run) => run.stateDir ?? run.run));
|
|
786
793
|
for (const run of terminalRunSet)
|
|
787
794
|
previousStatuses.delete(run);
|
|
788
|
-
for (const run of previousStatuses.keys())
|
|
795
|
+
for (const run of previousStatuses.keys())
|
|
789
796
|
if (!activeRuns.has(run))
|
|
790
797
|
previousStatuses.delete(run);
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
if (terminalLineKeys.has(key) || !activeLineKeys.has(key)) {
|
|
798
|
+
for (const key of previousLineCounts.keys())
|
|
799
|
+
if (terminalLineKeys.has(key) || !activeLineKeys.has(key))
|
|
794
800
|
previousLineCounts.delete(key);
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
for (const key of seenEventIds.keys()) {
|
|
798
|
-
if (terminalLineKeys.has(key) || !activeLineKeys.has(key)) {
|
|
801
|
+
for (const key of seenEventIds.keys())
|
|
802
|
+
if (terminalLineKeys.has(key) || !activeLineKeys.has(key))
|
|
799
803
|
seenEventIds.delete(key);
|
|
800
|
-
}
|
|
801
|
-
}
|
|
802
804
|
}
|
|
803
|
-
export function detectRunAttentionEvents(
|
|
805
|
+
export function detectRunAttentionEvents(legacyLineCounts, summary, seenEventIds = new Map(), prime = false) {
|
|
804
806
|
const events = [];
|
|
805
807
|
for (const run of summary.runs) {
|
|
806
808
|
const key = run.stateDir ?? run.run;
|
|
807
|
-
const
|
|
808
|
-
const
|
|
809
|
-
if (run.notificationPolicy === "silent") {
|
|
810
|
-
previousLineCounts.set(key, records.length);
|
|
811
|
-
seenEventIds.set(key, seenEventIds.get(key) ?? new Set());
|
|
812
|
-
continue;
|
|
813
|
-
}
|
|
814
|
-
const start = Math.min(previousCount, records.length);
|
|
809
|
+
const read = readTraceAttentionRecords(run);
|
|
810
|
+
const retained = new Set();
|
|
815
811
|
const seen = seenEventIds.get(key) ?? new Set();
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
812
|
+
const start = read.canonical ? 0
|
|
813
|
+
: Math.min(legacyLineCounts.get(key) ?? 0, read.records.length);
|
|
814
|
+
for (const [index, record] of read.records.entries()) {
|
|
815
|
+
const event = parseAttentionRecord(record, run, index);
|
|
816
|
+
if (!event || !shouldNotifyRunAttentionEvent(event) ||
|
|
817
|
+
event.kind === "runtime.trace_compacted")
|
|
819
818
|
continue;
|
|
820
|
-
|
|
821
|
-
|
|
819
|
+
retained.add(event.id);
|
|
820
|
+
if (prime || run.notificationPolicy === "silent")
|
|
821
|
+
seen.add(event.id);
|
|
822
|
+
else if (index >= start && !seen.has(event.id)) {
|
|
823
|
+
events.push(event);
|
|
824
|
+
seen.add(event.id);
|
|
825
|
+
}
|
|
822
826
|
}
|
|
823
|
-
|
|
824
|
-
|
|
827
|
+
seenEventIds.set(key, new Set([...retained].filter((id) => seen.has(id))));
|
|
828
|
+
if (read.canonical)
|
|
829
|
+
legacyLineCounts.delete(key);
|
|
830
|
+
else
|
|
831
|
+
legacyLineCounts.set(key, read.records.length);
|
|
825
832
|
}
|
|
826
833
|
return events;
|
|
827
834
|
}
|
|
@@ -53,17 +53,33 @@ function getRecipeCommandTemplateConfig(config) {
|
|
|
53
53
|
? config.template
|
|
54
54
|
: config);
|
|
55
55
|
}
|
|
56
|
+
function getRecipeDiagnosticTemplateConfig(config) {
|
|
57
|
+
const templateConfig = getRecipeCommandTemplateConfig(config);
|
|
58
|
+
return typeof templateConfig === "object" && !Array.isArray(templateConfig)
|
|
59
|
+
? {
|
|
60
|
+
...templateConfig,
|
|
61
|
+
defaults: {
|
|
62
|
+
...(templateConfig.defaults ?? {}),
|
|
63
|
+
...(config.defaults ?? {}),
|
|
64
|
+
...(config.values ?? {}),
|
|
65
|
+
},
|
|
66
|
+
}
|
|
67
|
+
: {
|
|
68
|
+
defaults: { ...(config.defaults ?? {}), ...(config.values ?? {}) },
|
|
69
|
+
template: templateConfig,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
56
72
|
function getRecipeConfigDiagnostics(file, config) {
|
|
57
73
|
if (!config) {
|
|
58
74
|
const reason = RecipesReferences.diagnoseRawRecipeConfigFailure(file);
|
|
59
75
|
return [`Invalid recipe: ${file}${reason ? `: ${reason}` : ""}`];
|
|
60
76
|
}
|
|
61
|
-
return CommandTemplates.getCommandTemplateWarnings(
|
|
77
|
+
return CommandTemplates.getCommandTemplateWarnings(getRecipeDiagnosticTemplateConfig(config)).map((warning) => `Recipe ${file}: ${warning}`);
|
|
62
78
|
}
|
|
63
79
|
function getRecipeRiskLabels(config) {
|
|
64
80
|
if (!config)
|
|
65
81
|
return [];
|
|
66
|
-
const labels = new Set(CommandTemplates.getCommandTemplateRiskLabels(
|
|
82
|
+
const labels = new Set(CommandTemplates.getCommandTemplateRiskLabels(getRecipeDiagnosticTemplateConfig(config)));
|
|
67
83
|
if (config.async === true)
|
|
68
84
|
labels.add("risk.long_running");
|
|
69
85
|
return [
|
|
@@ -652,8 +668,9 @@ export function toRegisteredTool(entry) {
|
|
|
652
668
|
const parsed = Schema.parseToolArgToken(String(arg));
|
|
653
669
|
return [parsed.arg, parsed.type];
|
|
654
670
|
}));
|
|
655
|
-
assertToolSafeRepeatConfig(argTemplateConfig, explicitArgTypes, cfg.defaults ?? {});
|
|
671
|
+
assertToolSafeRepeatConfig(argTemplateConfig, explicitArgTypes, { ...(cfg.defaults ?? {}), ...(cfg.values ?? {}) });
|
|
656
672
|
const argTypes = Schema.getTemplateArgTypes(argTemplateConfig);
|
|
673
|
+
Schema.assertCompatibleToolArgTypes(explicitArgTypes, argTypes);
|
|
657
674
|
return {
|
|
658
675
|
name: entry.id,
|
|
659
676
|
description,
|
|
@@ -40,6 +40,8 @@ export interface TemplateRecipeDefinition {
|
|
|
40
40
|
}
|
|
41
41
|
export interface TemplateRecipeConfig extends TemplateRecipeDefinition {
|
|
42
42
|
async?: boolean;
|
|
43
|
+
recipe_dir?: string;
|
|
44
|
+
skill_dir?: string;
|
|
43
45
|
}
|
|
44
46
|
export interface TemplateRecipeContextRecord {
|
|
45
47
|
alias?: string;
|
|
@@ -47,12 +49,20 @@ export interface TemplateRecipeContextRecord {
|
|
|
47
49
|
file: string;
|
|
48
50
|
import_path: string[];
|
|
49
51
|
name: string;
|
|
52
|
+
qualified_name?: string;
|
|
50
53
|
recipe: Record<string, unknown>;
|
|
51
54
|
role: "entry" | "import";
|
|
52
55
|
}
|
|
53
56
|
export interface ReadResolvedRecipeConfigOptions {
|
|
54
57
|
includeActorRecipeContext?: boolean;
|
|
55
58
|
}
|
|
59
|
+
export interface ActiveSkillRecipeSource {
|
|
60
|
+
name: string;
|
|
61
|
+
filePath?: string;
|
|
62
|
+
baseDir?: string;
|
|
63
|
+
}
|
|
64
|
+
export declare function setActiveSkillRecipeSources(skills: ActiveSkillRecipeSource[]): void;
|
|
65
|
+
export declare function getActiveSkillRecipeNamespaces(): Record<string, string[]>;
|
|
56
66
|
export declare function resolveRecipePath(value: string, recipeRoot?: string): string;
|
|
57
67
|
export declare function resolveRecipeReferencePath(value: unknown, currentRecipeRoot?: string): string | undefined;
|
|
58
68
|
export declare function getRecipePath(value: unknown, recipeRoot?: string): string | undefined;
|
|
@@ -5,15 +5,106 @@
|
|
|
5
5
|
*/
|
|
6
6
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
7
7
|
import { homedir } from "node:os";
|
|
8
|
-
import { basename, dirname, extname, resolve } from "node:path";
|
|
8
|
+
import { basename, dirname, extname, isAbsolute, relative, resolve } from "node:path";
|
|
9
9
|
import * as CommandTemplates from "./command-templates.js";
|
|
10
10
|
import * as Paths from "./paths.js";
|
|
11
11
|
import * as RecipeControl from "./recipe-control.js";
|
|
12
|
+
import * as Schema from "./schema.js";
|
|
12
13
|
const MAX_RECIPE_FILE_BYTES = 1024 * 1024;
|
|
13
14
|
const MAX_RECIPE_IMPORT_DEPTH = 32;
|
|
15
|
+
const RUNTIME_RECIPE_VALUE_NAMES = new Set(["recipe_dir", "skill_dir"]);
|
|
16
|
+
const activeSkillRecipeRoots = new Map();
|
|
17
|
+
export function setActiveSkillRecipeSources(skills) {
|
|
18
|
+
activeSkillRecipeRoots.clear();
|
|
19
|
+
for (const skill of skills) {
|
|
20
|
+
const name = skill.name.trim();
|
|
21
|
+
const skillDir = skill.baseDir
|
|
22
|
+
? resolve(skill.baseDir)
|
|
23
|
+
: skill.filePath
|
|
24
|
+
? dirname(resolve(skill.filePath))
|
|
25
|
+
: undefined;
|
|
26
|
+
if (!name || !skillDir)
|
|
27
|
+
continue;
|
|
28
|
+
const roots = activeSkillRecipeRoots.get(name) ?? new Set();
|
|
29
|
+
roots.add(resolve(skillDir, "recipes"));
|
|
30
|
+
activeSkillRecipeRoots.set(name, roots);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
export function getActiveSkillRecipeNamespaces() {
|
|
34
|
+
return Object.fromEntries([...activeSkillRecipeRoots.entries()]
|
|
35
|
+
.sort(([left], [right]) => left.localeCompare(right))
|
|
36
|
+
.map(([name, roots]) => [name, [...roots].sort()]));
|
|
37
|
+
}
|
|
38
|
+
function qualifiedRecipeNameForFile(file) {
|
|
39
|
+
const path = resolve(file);
|
|
40
|
+
const stdRelation = relative(Paths.getPackagedRecipeRoot(), path);
|
|
41
|
+
if (stdRelation &&
|
|
42
|
+
!stdRelation.startsWith("..") &&
|
|
43
|
+
!isAbsolute(stdRelation)) {
|
|
44
|
+
return `std:${stdRelation.replace(/\.(?:json|md)$/u, "")}`;
|
|
45
|
+
}
|
|
46
|
+
for (const [skillName, roots] of activeSkillRecipeRoots) {
|
|
47
|
+
if (roots.size !== 1)
|
|
48
|
+
continue;
|
|
49
|
+
const root = [...roots][0];
|
|
50
|
+
const relation = relative(root, path);
|
|
51
|
+
if (relation && !relation.startsWith("..") && !isAbsolute(relation))
|
|
52
|
+
return `skill:${skillName}/${relation.replaceAll("\\", "/").replace(/\.(?:json|md)$/u, "")}`;
|
|
53
|
+
}
|
|
54
|
+
return undefined;
|
|
55
|
+
}
|
|
56
|
+
function qualifiedRecipeCandidates(value) {
|
|
57
|
+
if (value.startsWith("std:")) {
|
|
58
|
+
return recipeNameFiles(value.slice(4)).map((file) => resolve(Paths.getPackagedRecipeRoot(), file));
|
|
59
|
+
}
|
|
60
|
+
if (!value.startsWith("skill:"))
|
|
61
|
+
return undefined;
|
|
62
|
+
const qualified = value.slice(6);
|
|
63
|
+
const separator = qualified.indexOf("/");
|
|
64
|
+
if (separator <= 0 || separator === qualified.length - 1)
|
|
65
|
+
throw new Error(`Invalid Skill Recipe reference: ${value}`);
|
|
66
|
+
const skillName = qualified.slice(0, separator);
|
|
67
|
+
const recipeName = qualified.slice(separator + 1);
|
|
68
|
+
const roots = [...(activeSkillRecipeRoots.get(skillName) ?? [])];
|
|
69
|
+
if (roots.length === 0)
|
|
70
|
+
throw new Error(`Active Skill Recipe namespace not found: ${skillName}`);
|
|
71
|
+
if (roots.length > 1)
|
|
72
|
+
throw new Error(`Ambiguous active Skill Recipe namespace ${skillName}: ${roots.join(", ")}`);
|
|
73
|
+
const root = roots[0];
|
|
74
|
+
return recipeNameFiles(recipeName).map((file) => {
|
|
75
|
+
const candidate = resolve(root, file);
|
|
76
|
+
const relation = relative(root, candidate);
|
|
77
|
+
if (!relation || relation.startsWith("..") || isAbsolute(relation))
|
|
78
|
+
throw new Error(`Skill Recipe reference escapes its recipes root: ${value}`);
|
|
79
|
+
return candidate;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
14
82
|
function hasWhitespace(value) {
|
|
15
83
|
return /\s/.test(value);
|
|
16
84
|
}
|
|
85
|
+
function findOwningSkillDir(recipeFile) {
|
|
86
|
+
let current = dirname(recipeFile);
|
|
87
|
+
while (true) {
|
|
88
|
+
if (existsSync(resolve(current, "SKILL.md")))
|
|
89
|
+
return current;
|
|
90
|
+
const parent = dirname(current);
|
|
91
|
+
if (parent === current)
|
|
92
|
+
return undefined;
|
|
93
|
+
current = parent;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
function assertRuntimeRecipeValuesNotDeclared(raw) {
|
|
97
|
+
const args = Array.isArray(raw.args)
|
|
98
|
+
? Schema.getExplicitToolArgNames(raw.args.map(String))
|
|
99
|
+
: [];
|
|
100
|
+
for (const name of RUNTIME_RECIPE_VALUE_NAMES) {
|
|
101
|
+
if (args.includes(name) ||
|
|
102
|
+
(isRecord(raw.defaults) && Object.hasOwn(raw.defaults, name)) ||
|
|
103
|
+
(isRecord(raw.values) && Object.hasOwn(raw.values, name))) {
|
|
104
|
+
throw new Error(`Recipe runtime value is reserved: ${name}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
17
108
|
export function resolveRecipePath(value, recipeRoot = Paths.getRecipeRoot()) {
|
|
18
109
|
const trimmed = value.trim();
|
|
19
110
|
const repoRoot = resolve(recipeRoot, "..");
|
|
@@ -42,6 +133,9 @@ function recipeNameFiles(value) {
|
|
|
42
133
|
return [`${trimmed}.json`, `${trimmed}.md`];
|
|
43
134
|
}
|
|
44
135
|
function recipeCandidatePaths(value, currentRecipeRoot) {
|
|
136
|
+
const qualified = qualifiedRecipeCandidates(value.trim());
|
|
137
|
+
if (qualified)
|
|
138
|
+
return qualified;
|
|
45
139
|
if (!isBareRecipeName(value))
|
|
46
140
|
return [resolveRecipePath(value, currentRecipeRoot)];
|
|
47
141
|
const roots = [
|
|
@@ -495,11 +589,46 @@ function mergeDefaults(...items) {
|
|
|
495
589
|
const merged = Object.assign({}, ...items.filter(Boolean));
|
|
496
590
|
return Object.keys(merged).length > 0 ? merged : undefined;
|
|
497
591
|
}
|
|
592
|
+
function assertRecipeDefaultsDeclared(args, defaults, label) {
|
|
593
|
+
const spec = Schema.parseToolArgDeclarationList(args ?? []);
|
|
594
|
+
if (spec.error)
|
|
595
|
+
throw new Error(spec.error);
|
|
596
|
+
if (spec.args.length === 0)
|
|
597
|
+
return;
|
|
598
|
+
const declared = new Set(spec.args);
|
|
599
|
+
for (const key of Object.keys(defaults ?? {})) {
|
|
600
|
+
if (!declared.has(key))
|
|
601
|
+
throw new Error(`Unknown ${label} argument: ${key}`);
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
function assertRecipeArgumentContract(config, label = "Recipe default") {
|
|
605
|
+
assertRecipeDefaultsDeclared(config.args, config.defaults, label);
|
|
606
|
+
const spec = Schema.parseToolArgDeclarationList(config.args ?? []);
|
|
607
|
+
if (spec.error)
|
|
608
|
+
throw new Error(spec.error);
|
|
609
|
+
Schema.assertCompatibleToolArgTypes(spec.argTypes, Schema.getTemplateArgTypes({ template: config.template }));
|
|
610
|
+
const staticValues = Object.fromEntries(Object.entries({ ...config.defaults, ...config.values }).filter(([, value]) => typeof value !== "string" || !/\{[^{}]+\}/.test(value)));
|
|
611
|
+
Schema.normalizeRuntimeValues(staticValues, spec.argTypes);
|
|
612
|
+
}
|
|
613
|
+
function resolveRecipeValueLayers(args, ...layers) {
|
|
614
|
+
const spec = Schema.parseToolArgDeclarationList(args ?? []);
|
|
615
|
+
if (spec.error)
|
|
616
|
+
throw new Error(spec.error);
|
|
617
|
+
const merged = mergeDefaults(spec.defaults, ...layers);
|
|
618
|
+
if (!merged)
|
|
619
|
+
return undefined;
|
|
620
|
+
const staticValues = Object.fromEntries(Object.entries(merged).filter(([, value]) => typeof value !== "string" || !/\{[^{}]+\}/.test(value)));
|
|
621
|
+
return {
|
|
622
|
+
...merged,
|
|
623
|
+
...Schema.normalizeRuntimeValues(staticValues, spec.argTypes),
|
|
624
|
+
};
|
|
625
|
+
}
|
|
498
626
|
function applyDefaultsToTemplate(template, defaults, overrides) {
|
|
499
627
|
const cleanOverrides = { ...overrides };
|
|
500
628
|
delete cleanOverrides.name;
|
|
501
629
|
delete cleanOverrides.template;
|
|
502
630
|
delete cleanOverrides.values;
|
|
631
|
+
delete cleanOverrides.defaults;
|
|
503
632
|
if (typeof template === "object" && !Array.isArray(template)) {
|
|
504
633
|
return {
|
|
505
634
|
...template,
|
|
@@ -537,7 +666,15 @@ function loadDelegatedRecipe(value, currentRecipeFile, stack, options) {
|
|
|
537
666
|
return config;
|
|
538
667
|
}
|
|
539
668
|
function applyDelegatedRecipeToNode(delegated, overrides = {}) {
|
|
540
|
-
|
|
669
|
+
const nodeDefaults = isRecord(overrides.defaults)
|
|
670
|
+
? overrides.defaults
|
|
671
|
+
: undefined;
|
|
672
|
+
const nodeValues = isRecord(overrides.values) ? overrides.values : undefined;
|
|
673
|
+
const values = resolveRecipeValueLayers(delegated.args, delegated.defaults, delegated.values, nodeDefaults, nodeValues, {
|
|
674
|
+
...(delegated.recipe_dir ? { recipe_dir: delegated.recipe_dir } : {}),
|
|
675
|
+
...(delegated.skill_dir ? { skill_dir: delegated.skill_dir } : {}),
|
|
676
|
+
});
|
|
677
|
+
return applyDefaultsToTemplate(delegated.template, values, overrides);
|
|
541
678
|
}
|
|
542
679
|
function expandRecipeDelegations(value, currentRecipeFile, stack, options = {}) {
|
|
543
680
|
if (typeof value === "string") {
|
|
@@ -593,7 +730,7 @@ function expandImportNodes(value, imports, options = {}) {
|
|
|
593
730
|
? record.defaults
|
|
594
731
|
: undefined;
|
|
595
732
|
const nodeValues = isRecord(record.values) ? record.values : undefined;
|
|
596
|
-
const defaults =
|
|
733
|
+
const defaults = resolveRecipeValueLayers(imported.config.args, imported.defaults, imported.values, nodeDefaults, nodeValues);
|
|
597
734
|
const expanded = applyDefaultsToTemplate(imported.config.template, defaults, record);
|
|
598
735
|
return options.includeActorRecipeContext
|
|
599
736
|
? withActorRecipeContext(expanded, {
|
|
@@ -631,6 +768,8 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
631
768
|
if (!raw || !Object.hasOwn(raw, "template"))
|
|
632
769
|
return undefined;
|
|
633
770
|
RecipeControl.assertRecipeHasNoMailbox(raw);
|
|
771
|
+
assertRuntimeRecipeValuesNotDeclared(raw);
|
|
772
|
+
assertRecipeArgumentContract(raw);
|
|
634
773
|
const imports = {};
|
|
635
774
|
for (const [alias, binding] of Object.entries(getRecipeImports(raw))) {
|
|
636
775
|
const importPath = resolveRecipeImportPath(getImportFrom(binding), dirname(path));
|
|
@@ -639,13 +778,18 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
639
778
|
throw new Error(`Recipe import not found: ${alias}`);
|
|
640
779
|
const bindingDefaults = typeof binding === "string" ? undefined : binding.defaults;
|
|
641
780
|
const bindingValues = typeof binding === "string" ? undefined : binding.values;
|
|
781
|
+
assertRecipeDefaultsDeclared(config.args, bindingDefaults, `Recipe import ${alias} default`);
|
|
642
782
|
imports[alias] = {
|
|
643
783
|
alias,
|
|
644
784
|
file: importPath,
|
|
645
785
|
name: config.name ?? alias,
|
|
646
786
|
config,
|
|
647
787
|
defaults: { ...(config.defaults ?? {}), ...(bindingDefaults ?? {}) },
|
|
648
|
-
values: {
|
|
788
|
+
values: {
|
|
789
|
+
...(bindingValues ?? {}),
|
|
790
|
+
...(config.recipe_dir ? { recipe_dir: config.recipe_dir } : {}),
|
|
791
|
+
...(config.skill_dir ? { skill_dir: config.skill_dir } : {}),
|
|
792
|
+
},
|
|
649
793
|
};
|
|
650
794
|
}
|
|
651
795
|
const substituted = substituteImportRefs(raw, imports);
|
|
@@ -661,6 +805,17 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
661
805
|
: {})
|
|
662
806
|
: expandRecipeDelegations(expandedImportsTemplate, path, stack, options);
|
|
663
807
|
const recipeName = getRecipeIdFromPath(path);
|
|
808
|
+
const recipeDir = dirname(path);
|
|
809
|
+
const skillDir = findOwningSkillDir(path);
|
|
810
|
+
if (!skillDir &&
|
|
811
|
+
JSON.stringify({
|
|
812
|
+
artifacts: substituted.artifacts,
|
|
813
|
+
defaults: substituted.defaults,
|
|
814
|
+
template: substituted.template,
|
|
815
|
+
values: substituted.values,
|
|
816
|
+
}).includes("{skill_dir}")) {
|
|
817
|
+
throw new Error(`Recipe uses {skill_dir} outside an owning Skill directory: ${path}`);
|
|
818
|
+
}
|
|
664
819
|
const templateWithContext = options.includeActorRecipeContext
|
|
665
820
|
? withActorRecipeContext(expandedTemplate, {
|
|
666
821
|
file: path,
|
|
@@ -678,6 +833,8 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
678
833
|
: delegated?.control);
|
|
679
834
|
return {
|
|
680
835
|
name: recipeName,
|
|
836
|
+
recipe_dir: recipeDir,
|
|
837
|
+
...(skillDir ? { skill_dir: skillDir } : {}),
|
|
681
838
|
...(typeof substituted.description === "string" &&
|
|
682
839
|
substituted.description.trim()
|
|
683
840
|
? { description: substituted.description.trim() }
|
|
@@ -765,7 +922,11 @@ export function readResolvedRecipeConfig(file, stack = [], options = {}) {
|
|
|
765
922
|
...(typeof substituted.repeat === "number"
|
|
766
923
|
? { repeat: substituted.repeat }
|
|
767
924
|
: {}),
|
|
768
|
-
|
|
925
|
+
values: {
|
|
926
|
+
...(isRecord(substituted.values) ? substituted.values : {}),
|
|
927
|
+
recipe_dir: recipeDir,
|
|
928
|
+
...(skillDir ? { skill_dir: skillDir } : {}),
|
|
929
|
+
},
|
|
769
930
|
...(isRecord(substituted.usage) ? { usage: substituted.usage } : {}),
|
|
770
931
|
};
|
|
771
932
|
}
|
|
@@ -780,12 +941,14 @@ function collectRecipeContextRecords(file, stack, importPath, alias) {
|
|
|
780
941
|
const raw = readRawRecipeConfig(path);
|
|
781
942
|
if (!raw || !Object.hasOwn(raw, "template"))
|
|
782
943
|
return [];
|
|
944
|
+
const qualifiedName = qualifiedRecipeNameForFile(path);
|
|
783
945
|
const record = {
|
|
784
946
|
...(alias ? { alias } : {}),
|
|
785
947
|
depth: stack.length,
|
|
786
948
|
file: path,
|
|
787
949
|
import_path: importPath,
|
|
788
950
|
name: getRecipeIdFromPath(path),
|
|
951
|
+
...(qualifiedName ? { qualified_name: qualifiedName } : {}),
|
|
789
952
|
recipe: raw,
|
|
790
953
|
role: stack.length === 0 ? "entry" : "import",
|
|
791
954
|
};
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure bounded Run-evidence policy.
|
|
3
|
+
* Zones: Trace retention, compaction accounting, Control capacity and admission
|
|
4
|
+
* Owns deterministic limits policy; journal I/O, locks, lifecycle authorization, and projection stay in adapters.
|
|
5
|
+
* Trace keeps the newest physical suffix because semantic priority would silently
|
|
6
|
+
* redefine causal history. Admitted Controls never expire: only a real terminal
|
|
7
|
+
* transition or generation replacement proves that durable work is finished.
|
|
8
|
+
*/
|
|
9
|
+
export declare const TRACE_COMPACTION_KIND = "runtime.trace_compacted";
|
|
10
|
+
export declare const TRACE_COMPACTION_VERSION = 1;
|
|
11
|
+
export type TraceJournalUsage = {
|
|
12
|
+
bytes: number;
|
|
13
|
+
events: number;
|
|
14
|
+
};
|
|
15
|
+
export type TraceRetentionCandidate<T> = {
|
|
16
|
+
encodedBytes: number;
|
|
17
|
+
value: T;
|
|
18
|
+
};
|
|
19
|
+
export type TraceRetentionSelection<T> = {
|
|
20
|
+
droppedEvents: number;
|
|
21
|
+
retained: TraceRetentionCandidate<T>[];
|
|
22
|
+
retainedBytes: number;
|
|
23
|
+
retainedEvents: number;
|
|
24
|
+
};
|
|
25
|
+
export interface TraceCompactionData {
|
|
26
|
+
version: typeof TRACE_COMPACTION_VERSION;
|
|
27
|
+
compactions_total: number;
|
|
28
|
+
dropped_valid_events_total: number;
|
|
29
|
+
dropped_malformed_lines_total: number;
|
|
30
|
+
dropped_bytes_total: number;
|
|
31
|
+
dropped_event_count_exact: boolean;
|
|
32
|
+
retained_events: number;
|
|
33
|
+
retained_bytes: number;
|
|
34
|
+
history_complete: false;
|
|
35
|
+
}
|
|
36
|
+
export interface TraceCompactionDelta {
|
|
37
|
+
dropped_valid_events: number;
|
|
38
|
+
dropped_malformed_lines: number;
|
|
39
|
+
dropped_bytes: number;
|
|
40
|
+
dropped_event_count_exact: boolean;
|
|
41
|
+
retained_events: number;
|
|
42
|
+
retained_bytes: number;
|
|
43
|
+
}
|
|
44
|
+
export type RunControlStatus = "queued" | "delivered" | "claimed" | "handled" | "failed";
|
|
45
|
+
export type RunControlCapacity = {
|
|
46
|
+
available: number;
|
|
47
|
+
backpressured: boolean;
|
|
48
|
+
limit: number;
|
|
49
|
+
oldest_pending_at?: string;
|
|
50
|
+
pending: number;
|
|
51
|
+
};
|
|
52
|
+
export type RunControlAdmissionIntegrity = "valid" | "unreadable" | "malformed" | "noncanonical" | "invalid_record" | "invalid_status_timestamp" | "generation_mismatch" | "journal_bytes";
|
|
53
|
+
export interface RunControlAdmissionErrorDetails {
|
|
54
|
+
reason: "control_backpressure" | "control_journal_integrity";
|
|
55
|
+
pending: number;
|
|
56
|
+
limit: number;
|
|
57
|
+
journal_bytes: number;
|
|
58
|
+
journal_limit: number;
|
|
59
|
+
oldest_pending_at?: string;
|
|
60
|
+
run_instance_id: string;
|
|
61
|
+
next_actions: string[];
|
|
62
|
+
integrity_reason?: string;
|
|
63
|
+
}
|
|
64
|
+
export type RunControlAdmissionDecision = {
|
|
65
|
+
admitted: true;
|
|
66
|
+
capacity: RunControlCapacity;
|
|
67
|
+
} | {
|
|
68
|
+
admitted: false;
|
|
69
|
+
capacity: RunControlCapacity;
|
|
70
|
+
error: RunControlAdmissionErrorDetails;
|
|
71
|
+
};
|
|
72
|
+
export type RunControlAdmissionInput = {
|
|
73
|
+
integrity: RunControlAdmissionIntegrity;
|
|
74
|
+
journalBytes: number;
|
|
75
|
+
newRecordBytes: number;
|
|
76
|
+
records: readonly unknown[];
|
|
77
|
+
retainedJournalBytes: number;
|
|
78
|
+
runInstanceId: string;
|
|
79
|
+
capacity?: RunControlCapacity;
|
|
80
|
+
};
|
|
81
|
+
export declare function traceAppendFits(usage: TraceJournalUsage, encodedEventBytes: number): boolean;
|
|
82
|
+
export declare function selectNewestTraceSuffix<T>(candidates: readonly TraceRetentionCandidate<T>[], reserve?: {
|
|
83
|
+
bytes?: number;
|
|
84
|
+
events?: number;
|
|
85
|
+
}): TraceRetentionSelection<T>;
|
|
86
|
+
export declare function accumulateTraceCompactionStatistics(previous: TraceCompactionData | undefined, delta: TraceCompactionDelta): TraceCompactionData;
|
|
87
|
+
export declare function traceCompactionMarkerInput(data: TraceCompactionData): {
|
|
88
|
+
data: TraceCompactionData;
|
|
89
|
+
kind: typeof TRACE_COMPACTION_KIND;
|
|
90
|
+
level: "warning";
|
|
91
|
+
};
|
|
92
|
+
export declare function classifyRunControlRecord(value: unknown): "pending" | "terminal" | undefined;
|
|
93
|
+
export declare function runControlStatusTimestamp(record: Record<string, unknown>, status: RunControlStatus): unknown;
|
|
94
|
+
export declare function computeRunControlCapacity(records: readonly unknown[]): RunControlCapacity;
|
|
95
|
+
export declare function decideRunControlAdmission(input: RunControlAdmissionInput): RunControlAdmissionDecision;
|