@llblab/pi-actors 0.44.0 → 0.45.1
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 +2 -0
- package/CHANGELOG.md +13 -0
- package/README.md +5 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.js +10 -106
- package/dist/lib/async-runs.d.ts +2 -0
- package/dist/lib/async-runs.js +33 -4
- package/dist/lib/config.js +1 -0
- package/dist/lib/extension-runtime.d.ts +21 -0
- package/dist/lib/extension-runtime.js +116 -0
- package/dist/lib/inspector.js +8 -2
- 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/schema.d.ts +3 -0
- package/dist/lib/schema.js +34 -1
- package/dist/lib/tools-inspect.js +10 -0
- package/dist/lib/tools-local.js +27 -10
- package/dist/lib/tools-spawn.js +2 -2
- package/dist/scripts/async-runner.mjs +0 -16
- package/dist/scripts/executable-block-style.d.mts +1 -0
- package/dist/scripts/executable-block-style.mjs +73 -0
- package/dist/scripts/locker.mjs +0 -21
- package/dist/scripts/recipe-utils.mjs +0 -1
- package/dist/scripts/release-gates.mjs +14 -11
- package/dist/scripts/validate-recipe.mjs +0 -1
- package/dist/skills/actors/SKILL.md +2 -2
- package/docs/command-templates.md +2 -2
- package/docs/recipe-library.md +2 -0
- package/docs/template-recipes.md +10 -4
- package/docs/tool-registry.md +1 -1
- package/index.ts +18 -107
- package/lib/async-runs.ts +43 -9
- package/lib/config.ts +4 -0
- package/lib/extension-runtime.ts +139 -0
- package/lib/inspector.ts +16 -2
- package/lib/recipes-discovery.ts +23 -3
- package/lib/recipes-references.ts +227 -7
- package/lib/schema.ts +53 -1
- package/lib/tools-inspect.ts +11 -0
- package/lib/tools-local.ts +49 -19
- package/lib/tools-spawn.ts +2 -2
- package/package.json +2 -2
- package/scripts/async-runner.mjs +0 -16
- package/scripts/executable-block-style.d.mts +1 -0
- package/scripts/executable-block-style.mjs +73 -0
- package/scripts/locker.mjs +0 -21
- package/scripts/recipe-utils.mjs +0 -1
- package/scripts/release-gates.mjs +14 -11
- package/scripts/validate-recipe.mjs +0 -1
- package/skills/actors/SKILL.md +2 -2
package/lib/tools-inspect.ts
CHANGED
|
@@ -14,6 +14,7 @@ import * as Limits from "./limits.ts";
|
|
|
14
14
|
import { computeRunControlCapacity } from "./run-evidence-policy.ts";
|
|
15
15
|
import * as Paths from "./paths.ts";
|
|
16
16
|
import * as RecipesDiscovery from "./recipes-discovery.ts";
|
|
17
|
+
import * as RecipesReferences from "./recipes-references.ts";
|
|
17
18
|
import * as ReviewDiagnostics from "./review-diagnostics.ts";
|
|
18
19
|
import * as RunsControls from "./runs-controls.ts";
|
|
19
20
|
import * as RunControlDelivery from "./runs-control-delivery.ts";
|
|
@@ -214,8 +215,18 @@ function inspectRecipes(
|
|
|
214
215
|
{ root: recipeRoot, defaultTool: true, mutableUsage: true },
|
|
215
216
|
{ root: deps.packagedRecipeRoot ?? Paths.getPackagedRecipeRoot() },
|
|
216
217
|
]);
|
|
218
|
+
const skillRecipeNamespaces =
|
|
219
|
+
RecipesReferences.getActiveSkillRecipeNamespaces();
|
|
217
220
|
const summary = {
|
|
218
221
|
...RecipesDiscovery.summarizeDiscovery(discovered),
|
|
222
|
+
skill_recipe_namespaces: skillRecipeNamespaces,
|
|
223
|
+
skill_recipe_namespace_diagnostics: Object.entries(skillRecipeNamespaces)
|
|
224
|
+
.filter(([, roots]) => roots.length > 1)
|
|
225
|
+
.map(([name, roots]) => ({
|
|
226
|
+
name,
|
|
227
|
+
roots,
|
|
228
|
+
error: `Ambiguous active Skill Recipe namespace ${name}`,
|
|
229
|
+
})),
|
|
219
230
|
drafts: RecipesDiscovery.listDraftRecipes(join(recipeRoot, "drafts")),
|
|
220
231
|
};
|
|
221
232
|
return {
|
package/lib/tools-local.ts
CHANGED
|
@@ -67,11 +67,16 @@ function formatRuntimeToolUsageHint(
|
|
|
67
67
|
includeRunId: boolean,
|
|
68
68
|
): string {
|
|
69
69
|
const optional = cfg.args.filter((arg) => !required.includes(arg));
|
|
70
|
+
const exampleDefaults = {
|
|
71
|
+
...Schema.getExplicitToolArgDefaults(cfg.recipe?.args),
|
|
72
|
+
...cfg.defaults,
|
|
73
|
+
...(cfg.recipe?.values ?? {}),
|
|
74
|
+
};
|
|
70
75
|
const example: Record<string, unknown> = {};
|
|
71
76
|
for (const arg of required)
|
|
72
|
-
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg],
|
|
77
|
+
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], exampleDefaults);
|
|
73
78
|
for (const arg of optional)
|
|
74
|
-
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg],
|
|
79
|
+
example[arg] = sampleValueForArg(arg, cfg.argTypes?.[arg], exampleDefaults);
|
|
75
80
|
if (includeRunId) example.run_id = `${cfg.name}-1`;
|
|
76
81
|
const lines = [
|
|
77
82
|
`Expected call shape for ${cfg.name}:`,
|
|
@@ -85,6 +90,32 @@ function formatRuntimeToolUsageHint(
|
|
|
85
90
|
return lines.join("\n");
|
|
86
91
|
}
|
|
87
92
|
|
|
93
|
+
function resolveRecipeToolValues(
|
|
94
|
+
cfg: RegisteredTool,
|
|
95
|
+
callerValues: Record<string, unknown>,
|
|
96
|
+
ctx: RuntimeToolContext,
|
|
97
|
+
): Record<string, unknown> {
|
|
98
|
+
const recipeRuntimeValues = {
|
|
99
|
+
...(cfg.recipe?.recipe_dir
|
|
100
|
+
? { recipe_dir: cfg.recipe.recipe_dir }
|
|
101
|
+
: {}),
|
|
102
|
+
...(cfg.recipe?.skill_dir ? { skill_dir: cfg.recipe.skill_dir } : {}),
|
|
103
|
+
};
|
|
104
|
+
return Schema.normalizeRuntimeValues(
|
|
105
|
+
ModelContext.withCurrentModelValues(
|
|
106
|
+
{
|
|
107
|
+
...Schema.getExplicitToolArgDefaults(cfg.recipe?.args),
|
|
108
|
+
...cfg.defaults,
|
|
109
|
+
...(cfg.recipe?.values ?? {}),
|
|
110
|
+
...callerValues,
|
|
111
|
+
...recipeRuntimeValues,
|
|
112
|
+
},
|
|
113
|
+
ctx,
|
|
114
|
+
),
|
|
115
|
+
cfg.argTypes,
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
88
119
|
function formatRuntimeToolArgumentError(
|
|
89
120
|
cfg: RegisteredTool,
|
|
90
121
|
error: unknown,
|
|
@@ -128,9 +159,18 @@ export function createRuntimeToolDefinition(
|
|
|
128
159
|
defaults: cfg.defaults,
|
|
129
160
|
template: requiredTemplate,
|
|
130
161
|
};
|
|
162
|
+
const recipeInlineDefaults = Schema.getExplicitToolArgDefaults(
|
|
163
|
+
cfg.recipe?.args,
|
|
164
|
+
);
|
|
131
165
|
const requiredArgs =
|
|
132
166
|
isRecipe && cfg.storedArgs !== undefined
|
|
133
|
-
? new Set(
|
|
167
|
+
? new Set(
|
|
168
|
+
cfg.args.filter(
|
|
169
|
+
(arg) =>
|
|
170
|
+
!Object.hasOwn(cfg.defaults, arg) &&
|
|
171
|
+
!Object.hasOwn(recipeInlineDefaults, arg),
|
|
172
|
+
),
|
|
173
|
+
)
|
|
134
174
|
: RecipesReferences.isRecipeReference(cfg.template) && !recipeTemplate
|
|
135
175
|
? new Set(cfg.args.filter((arg) => !Object.hasOwn(cfg.defaults, arg)))
|
|
136
176
|
: Schema.getRequiredToolArgNames(requiredTemplateConfig);
|
|
@@ -200,13 +240,7 @@ export function createRuntimeToolDefinition(
|
|
|
200
240
|
{ ...(cfg.recipe?.values ?? {}), ...values },
|
|
201
241
|
ctx,
|
|
202
242
|
),
|
|
203
|
-
values:
|
|
204
|
-
ModelContext.withCurrentModelValues(
|
|
205
|
-
{ ...(cfg.recipe?.values ?? {}), ...cfg.defaults, ...values },
|
|
206
|
-
ctx,
|
|
207
|
-
),
|
|
208
|
-
cfg.argTypes,
|
|
209
|
-
),
|
|
243
|
+
values: resolveRecipeToolValues(cfg, values, ctx),
|
|
210
244
|
},
|
|
211
245
|
ctx.cwd,
|
|
212
246
|
);
|
|
@@ -221,17 +255,13 @@ export function createRuntimeToolDefinition(
|
|
|
221
255
|
};
|
|
222
256
|
}
|
|
223
257
|
if (isRecipe && recipeTemplate) {
|
|
224
|
-
const paramsWithDefaults = ModelContext.withCurrentModelValues(
|
|
225
|
-
{
|
|
226
|
-
...(cfg.recipe?.values ?? {}),
|
|
227
|
-
...cfg.defaults,
|
|
228
|
-
...(params as Record<string, unknown>),
|
|
229
|
-
},
|
|
230
|
-
ctx,
|
|
231
|
-
);
|
|
232
258
|
return await Execution.executeRegisteredTool(
|
|
233
259
|
{ ...cfg, template: recipeTemplate },
|
|
234
|
-
|
|
260
|
+
resolveRecipeToolValues(
|
|
261
|
+
cfg,
|
|
262
|
+
params as Record<string, unknown>,
|
|
263
|
+
ctx,
|
|
264
|
+
),
|
|
235
265
|
exec,
|
|
236
266
|
ctx.cwd,
|
|
237
267
|
signal,
|
package/lib/tools-spawn.ts
CHANGED
|
@@ -138,10 +138,10 @@ export function createSpawnToolDefinition<
|
|
|
138
138
|
"Optional Run identity in exact run:<id> form.",
|
|
139
139
|
),
|
|
140
140
|
file: Schema.stringSchema(
|
|
141
|
-
"Optional template
|
|
141
|
+
"Optional template Recipe reference. Supports paths, bare user/adjacent/std names, std:<name>, and skill:<skill>/<recipe>.",
|
|
142
142
|
),
|
|
143
143
|
recipe: Schema.stringSchema(
|
|
144
|
-
"Alias for file;
|
|
144
|
+
"Alias for file; qualified Skill/std or ordinary Recipe reference to spawn.",
|
|
145
145
|
),
|
|
146
146
|
template: Schema.unionSchema([
|
|
147
147
|
Schema.stringSchema("Inline command template string"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@llblab/pi-actors",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.45.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Local Actor Kernel for Pi",
|
|
6
6
|
"keywords": [
|
|
@@ -65,7 +65,7 @@
|
|
|
65
65
|
"sourceSkills": [
|
|
66
66
|
"./skills"
|
|
67
67
|
],
|
|
68
|
-
"image": "https://
|
|
68
|
+
"image": "https://raw.githubusercontent.com/llblab/pi-actors/main/banner.jpg"
|
|
69
69
|
},
|
|
70
70
|
"peerDependencies": {
|
|
71
71
|
"@earendil-works/pi-coding-agent": "*",
|
package/scripts/async-runner.mjs
CHANGED
|
@@ -74,7 +74,6 @@ function summarizeCommandDetail(commandDetail) {
|
|
|
74
74
|
|
|
75
75
|
export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
76
76
|
if (!stateDir) throw new Error("missing state dir");
|
|
77
|
-
|
|
78
77
|
const runPath = join(stateDir, "run.json");
|
|
79
78
|
const progressPath = join(stateDir, "progress.json");
|
|
80
79
|
const resultPath = join(stateDir, "result.json");
|
|
@@ -83,11 +82,9 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
83
82
|
const stderrPath = join(stateDir, "stderr.log");
|
|
84
83
|
const executionPath = join(stateDir, "execution.json");
|
|
85
84
|
const meta = JSON.parse(readFileSync(runPath, "utf8"));
|
|
86
|
-
|
|
87
85
|
function event(kind, data = {}) {
|
|
88
86
|
appendRunTraceEvent(stateDir, { kind, data });
|
|
89
87
|
}
|
|
90
|
-
|
|
91
88
|
function observation(kind, summary, data = {}, delivery = "log", level = "info") {
|
|
92
89
|
appendRunTraceEvent(stateDir, {
|
|
93
90
|
kind,
|
|
@@ -97,7 +94,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
97
94
|
...(level === "error" ? { level: "error" } : {}),
|
|
98
95
|
});
|
|
99
96
|
}
|
|
100
|
-
|
|
101
97
|
function progress(phase, extra = {}) {
|
|
102
98
|
writeJsonAtomic(progressPath, {
|
|
103
99
|
...(meta.model_policy ? { model_policy: meta.model_policy } : {}),
|
|
@@ -106,7 +102,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
106
102
|
...extra,
|
|
107
103
|
});
|
|
108
104
|
}
|
|
109
|
-
|
|
110
105
|
let activeSubagents = 0;
|
|
111
106
|
let completedSubagents = 0;
|
|
112
107
|
let promptCounter = 0;
|
|
@@ -115,7 +110,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
115
110
|
const evidenceRecords = [];
|
|
116
111
|
const stageOccurrences = new Map();
|
|
117
112
|
let reportEvidence;
|
|
118
|
-
|
|
119
113
|
function writeExecutionManifest(status) {
|
|
120
114
|
writeJsonAtomic(executionPath, {
|
|
121
115
|
version: 1,
|
|
@@ -129,7 +123,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
129
123
|
updated_at: new Date().toISOString(),
|
|
130
124
|
});
|
|
131
125
|
}
|
|
132
|
-
|
|
133
126
|
function commandSessionFiles(sessionDir) {
|
|
134
127
|
if (!sessionDir) return [];
|
|
135
128
|
try {
|
|
@@ -141,7 +134,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
141
134
|
return [];
|
|
142
135
|
}
|
|
143
136
|
}
|
|
144
|
-
|
|
145
137
|
function commandEvidenceStartRecord({
|
|
146
138
|
commandDetail,
|
|
147
139
|
commandId,
|
|
@@ -181,7 +173,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
181
173
|
: "not_required",
|
|
182
174
|
};
|
|
183
175
|
}
|
|
184
|
-
|
|
185
176
|
function commandEvidenceRecord({
|
|
186
177
|
captureDir,
|
|
187
178
|
commandDetail,
|
|
@@ -273,7 +264,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
273
264
|
: "rejected",
|
|
274
265
|
};
|
|
275
266
|
}
|
|
276
|
-
|
|
277
267
|
function auditReviewReport(text) {
|
|
278
268
|
const required = evidenceRecords
|
|
279
269
|
.filter((record) =>
|
|
@@ -294,11 +284,9 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
294
284
|
complete_allowed: missing.length === 0,
|
|
295
285
|
};
|
|
296
286
|
}
|
|
297
|
-
|
|
298
287
|
function getCommandDoneDelivery(result) {
|
|
299
288
|
return result.code !== 0 || activeSubagents > 0 ? "followup" : "log";
|
|
300
289
|
}
|
|
301
|
-
|
|
302
290
|
function progressRunning() {
|
|
303
291
|
progress("running", {
|
|
304
292
|
activeSubagents,
|
|
@@ -306,14 +294,12 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
306
294
|
failures: subagentFailures,
|
|
307
295
|
});
|
|
308
296
|
}
|
|
309
|
-
|
|
310
297
|
function promptFilePath() {
|
|
311
298
|
promptCounter += 1;
|
|
312
299
|
const dir = join(stateDir, "prompts");
|
|
313
300
|
mkdirSync(dir, { recursive: true });
|
|
314
301
|
return join(dir, `command-${String(promptCounter).padStart(3, "0")}.md`);
|
|
315
302
|
}
|
|
316
|
-
|
|
317
303
|
function readPromptText(promptFile) {
|
|
318
304
|
if (!promptFile) return undefined;
|
|
319
305
|
try {
|
|
@@ -322,7 +308,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
322
308
|
return undefined;
|
|
323
309
|
}
|
|
324
310
|
}
|
|
325
|
-
|
|
326
311
|
async function observedExec(command, args, options) {
|
|
327
312
|
captureCounter += 1;
|
|
328
313
|
const commandId = `command-${String(captureCounter).padStart(3, "0")}`;
|
|
@@ -485,7 +470,6 @@ export async function runAsyncRunner(stateDir = process.argv[2]) {
|
|
|
485
470
|
progressRunning();
|
|
486
471
|
return result;
|
|
487
472
|
}
|
|
488
|
-
|
|
489
473
|
try {
|
|
490
474
|
event("run.runner.start", { pid: process.pid });
|
|
491
475
|
progressRunning();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export function executableBlockBlankLines(source: string): number[];
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/** Detect blank lines inside executable JavaScript and TypeScript blocks. */
|
|
2
|
+
|
|
3
|
+
export function executableBlockBlankLines(source) {
|
|
4
|
+
const lines = source.split(/\r?\n/u);
|
|
5
|
+
const blanks = [];
|
|
6
|
+
const stack = [];
|
|
7
|
+
let blockComment = false;
|
|
8
|
+
let currentExecutableDepth = 0;
|
|
9
|
+
let previousCode = "";
|
|
10
|
+
let quote;
|
|
11
|
+
for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
|
|
12
|
+
const line = lines[lineIndex];
|
|
13
|
+
if (
|
|
14
|
+
/^\s*$/u.test(line) &&
|
|
15
|
+
currentExecutableDepth > 0 &&
|
|
16
|
+
quote !== "`"
|
|
17
|
+
)
|
|
18
|
+
blanks.push(lineIndex + 1);
|
|
19
|
+
let code = "";
|
|
20
|
+
let regularExpression = false;
|
|
21
|
+
for (let index = 0; index < line.length; index += 1) {
|
|
22
|
+
const char = line[index];
|
|
23
|
+
const next = line[index + 1];
|
|
24
|
+
if (blockComment) {
|
|
25
|
+
if (char === "*" && next === "/") {
|
|
26
|
+
blockComment = false;
|
|
27
|
+
index += 1;
|
|
28
|
+
}
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (quote) {
|
|
32
|
+
if (char === "\\") index += 1;
|
|
33
|
+
else if (char === quote) quote = undefined;
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (regularExpression) {
|
|
37
|
+
if (char === "\\") index += 1;
|
|
38
|
+
else if (char === "/") regularExpression = false;
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (char === "/" && next === "*") {
|
|
42
|
+
blockComment = true;
|
|
43
|
+
index += 1;
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
if (char === "/" && next === "/") break;
|
|
47
|
+
if (
|
|
48
|
+
char === "/" &&
|
|
49
|
+
/^(?:|.*(?:[=(,:;!&|?{}]|\b(?:return|case|throw)))\s*$/u.test(code)
|
|
50
|
+
) {
|
|
51
|
+
regularExpression = true;
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
if (char === '"' || char === "'" || char === "`") {
|
|
55
|
+
quote = char;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (char === "{") {
|
|
59
|
+
const prefix = code.trimEnd() || previousCode;
|
|
60
|
+
const executable =
|
|
61
|
+
/\)\s*$|=>\s*$|\b(?:else|try|finally|do)\s*$/u.test(prefix);
|
|
62
|
+
stack.push(executable);
|
|
63
|
+
if (executable) currentExecutableDepth += 1;
|
|
64
|
+
} else if (char === "}") {
|
|
65
|
+
if (stack.pop()) currentExecutableDepth -= 1;
|
|
66
|
+
}
|
|
67
|
+
code += char;
|
|
68
|
+
}
|
|
69
|
+
if (quote !== "`") quote = undefined;
|
|
70
|
+
if (code.trim()) previousCode = code.trim();
|
|
71
|
+
}
|
|
72
|
+
return blanks;
|
|
73
|
+
}
|
package/scripts/locker.mjs
CHANGED
|
@@ -65,7 +65,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
65
65
|
const controlPath = join(stateDir, "control.fifo");
|
|
66
66
|
let runInstanceId;
|
|
67
67
|
mkdirSync(stateDir, { recursive: true });
|
|
68
|
-
|
|
69
68
|
function readJson(path, fallback) {
|
|
70
69
|
if (!existsSync(path)) return fallback;
|
|
71
70
|
try {
|
|
@@ -74,11 +73,9 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
74
73
|
return fallback;
|
|
75
74
|
}
|
|
76
75
|
}
|
|
77
|
-
|
|
78
76
|
function writeJson(path, value) {
|
|
79
77
|
writeJsonAtomic(path, value);
|
|
80
78
|
}
|
|
81
|
-
|
|
82
79
|
function getControlEndpoint() {
|
|
83
80
|
if (process.platform !== "win32") return { path: controlPath, type: "fifo" };
|
|
84
81
|
const hash = createHash("sha256")
|
|
@@ -87,7 +84,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
87
84
|
.slice(0, 20);
|
|
88
85
|
return { path: `\\\\.\\pipe\\pi-actors-locker-${hash}`, type: "named-pipe" };
|
|
89
86
|
}
|
|
90
|
-
|
|
91
87
|
function writeControlEndpoint(endpoint) {
|
|
92
88
|
if (!runInstanceId) throw new Error("Run generation unavailable for Control endpoint");
|
|
93
89
|
writeJson(join(stateDir, "control-endpoint.json"), {
|
|
@@ -96,7 +92,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
96
92
|
run_instance_id: runInstanceId,
|
|
97
93
|
});
|
|
98
94
|
}
|
|
99
|
-
|
|
100
95
|
function journal(event, data = {}) {
|
|
101
96
|
const release = acquireFileMutationLock(journalPath);
|
|
102
97
|
try {
|
|
@@ -111,11 +106,9 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
111
106
|
writeTextAtomic(journalPath, content);
|
|
112
107
|
} finally { release(); }
|
|
113
108
|
}
|
|
114
|
-
|
|
115
109
|
function encodeJournal(records) {
|
|
116
110
|
return records.length ? `${records.map((record) => JSON.stringify(record)).join("\n")}\n` : "";
|
|
117
111
|
}
|
|
118
|
-
|
|
119
112
|
function emitTrace(kind, summary, data = {}, level = "info") {
|
|
120
113
|
appendRunTraceEvent(stateDir, {
|
|
121
114
|
attention: "followup",
|
|
@@ -125,11 +118,9 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
125
118
|
summary,
|
|
126
119
|
});
|
|
127
120
|
}
|
|
128
|
-
|
|
129
121
|
function now() {
|
|
130
122
|
return Date.now();
|
|
131
123
|
}
|
|
132
|
-
|
|
133
124
|
function cleanExpiredLocks(locks) {
|
|
134
125
|
const current = now();
|
|
135
126
|
const kept = {};
|
|
@@ -139,7 +130,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
139
130
|
}
|
|
140
131
|
return kept;
|
|
141
132
|
}
|
|
142
|
-
|
|
143
133
|
function normalizeControl(line) {
|
|
144
134
|
const trimmed = line.trim();
|
|
145
135
|
if (!trimmed) return undefined;
|
|
@@ -160,8 +150,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
160
150
|
);
|
|
161
151
|
}
|
|
162
152
|
}
|
|
163
|
-
|
|
164
|
-
|
|
165
153
|
function tailJournal(count) {
|
|
166
154
|
if (!existsSync(journalPath)) return [];
|
|
167
155
|
return readFileSync(journalPath, "utf8")
|
|
@@ -177,7 +165,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
177
165
|
}
|
|
178
166
|
});
|
|
179
167
|
}
|
|
180
|
-
|
|
181
168
|
function printSnapshot() {
|
|
182
169
|
const locks = cleanExpiredLocks(readJson(locksPath, {}));
|
|
183
170
|
writeJson(locksPath, locks);
|
|
@@ -195,7 +182,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
195
182
|
),
|
|
196
183
|
);
|
|
197
184
|
}
|
|
198
|
-
|
|
199
185
|
function nextTask(queue, locks) {
|
|
200
186
|
const items = Array.isArray(queue.items) ? queue.items : [];
|
|
201
187
|
const index = items.findIndex((item) => {
|
|
@@ -205,7 +191,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
205
191
|
if (index < 0) return undefined;
|
|
206
192
|
return items.splice(index, 1)[0];
|
|
207
193
|
}
|
|
208
|
-
|
|
209
194
|
function handle(control) {
|
|
210
195
|
const action = control.action;
|
|
211
196
|
const body =
|
|
@@ -333,18 +318,15 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
333
318
|
journal("lock.unknown", { action, input: body });
|
|
334
319
|
emitTrace("lock.unknown", `Unknown Control ${action}`, { action, input: body }, "warning");
|
|
335
320
|
}
|
|
336
|
-
|
|
337
321
|
if (mode === "snapshot") {
|
|
338
322
|
printSnapshot();
|
|
339
323
|
process.exit(0);
|
|
340
324
|
}
|
|
341
|
-
|
|
342
325
|
const startupRun = readJson(join(stateDir, "run.json"), undefined);
|
|
343
326
|
if (!startupRun || typeof startupRun.run_instance_id !== "string") {
|
|
344
327
|
throw new Error("Run generation unavailable for Control service");
|
|
345
328
|
}
|
|
346
329
|
runInstanceId = startupRun.run_instance_id;
|
|
347
|
-
|
|
348
330
|
function handleLine(line) {
|
|
349
331
|
const control = normalizeControl(line);
|
|
350
332
|
if (!control) return false;
|
|
@@ -376,7 +358,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
376
358
|
return false;
|
|
377
359
|
}
|
|
378
360
|
}
|
|
379
|
-
|
|
380
361
|
async function serveFifo(endpoint) {
|
|
381
362
|
if (!existsSync(endpoint.path)) {
|
|
382
363
|
const result = spawnSync("mkfifo", [endpoint.path]);
|
|
@@ -403,7 +384,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
403
384
|
fs.closeSync(fd);
|
|
404
385
|
}
|
|
405
386
|
}
|
|
406
|
-
|
|
407
387
|
async function serveNamedPipe(endpoint) {
|
|
408
388
|
let resolveStopped;
|
|
409
389
|
const stopped = new Promise((resolve) => {
|
|
@@ -428,7 +408,6 @@ async function runLocker(argv = process.argv.slice(2)) {
|
|
|
428
408
|
});
|
|
429
409
|
await stopped;
|
|
430
410
|
}
|
|
431
|
-
|
|
432
411
|
const endpoint = getControlEndpoint();
|
|
433
412
|
writeJson(queuePath, readJson(queuePath, { items: [] }));
|
|
434
413
|
writeJson(locksPath, cleanExpiredLocks(readJson(locksPath, {})));
|
package/scripts/recipe-utils.mjs
CHANGED
|
@@ -7,6 +7,8 @@ import { dirname, extname, join, normalize, relative, resolve } from "node:path"
|
|
|
7
7
|
import { spawnSync } from "node:child_process";
|
|
8
8
|
import { fileURLToPath } from "node:url";
|
|
9
9
|
|
|
10
|
+
import { executableBlockBlankLines } from "./executable-block-style.mjs";
|
|
11
|
+
|
|
10
12
|
const root = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
11
13
|
const temp = mkdtempSync(join(tmpdir(), "pi-actors-release-index-"));
|
|
12
14
|
const indexPath = join(temp, "index");
|
|
@@ -71,7 +73,6 @@ try {
|
|
|
71
73
|
.filter(Boolean);
|
|
72
74
|
const fileSet = new Set(files);
|
|
73
75
|
console.log(`[release] exact temporary index: ${files.length} files`);
|
|
74
|
-
|
|
75
76
|
const secretPatterns = [
|
|
76
77
|
[/-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----[\s\S]{80,}?-----END(?: [A-Z0-9]+)? PRIVATE KEY-----/u, "private-key block"],
|
|
77
78
|
[/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/u, "AWS access key"],
|
|
@@ -92,7 +93,17 @@ try {
|
|
|
92
93
|
}
|
|
93
94
|
}
|
|
94
95
|
console.log("[release] bounded secret and public-path hygiene checked");
|
|
95
|
-
|
|
96
|
+
const executableSources = files.filter((path) =>
|
|
97
|
+
/\.(?:[cm]?js|ts)$/u.test(path) && !path.startsWith("dist/"),
|
|
98
|
+
);
|
|
99
|
+
for (const path of executableSources) {
|
|
100
|
+
const blankLines = executableBlockBlankLines(stagedText(path) ?? "");
|
|
101
|
+
check(
|
|
102
|
+
blankLines.length === 0,
|
|
103
|
+
`blank lines inside executable blocks: ${path}:${blankLines.join(",")}`,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
console.log("[release] executable blocks contain no blank lines");
|
|
96
107
|
const removedPaths = [
|
|
97
108
|
"lib/messages.ts",
|
|
98
109
|
"lib/rooms.ts",
|
|
@@ -113,7 +124,6 @@ try {
|
|
|
113
124
|
"lib/run-inspector-overlay.ts",
|
|
114
125
|
];
|
|
115
126
|
for (const path of removedPaths) check(!fileSet.has(path), `removed surface returned: ${path}`);
|
|
116
|
-
|
|
117
127
|
const currentSurface = /^(?:lib\/|scripts\/|recipes\/|fixtures\/|skills\/|docs\/|README\.md|AGENTS\.md)/u;
|
|
118
128
|
const forbiddenPatterns = [
|
|
119
129
|
[/room:</u, "room address"],
|
|
@@ -144,7 +154,6 @@ try {
|
|
|
144
154
|
}
|
|
145
155
|
}
|
|
146
156
|
console.log("[release] removed-surface and legacy-fallback allowlists checked");
|
|
147
|
-
|
|
148
157
|
const directTraceWrite = /(?:appendFileSync|writeFileSync|writeText(?:Atomic)?)\s*\(\s*[A-Za-z0-9_.]*?(?:trace|event)(?:Path|File)/iu;
|
|
149
158
|
for (const path of files.filter((candidate) =>
|
|
150
159
|
(candidate.startsWith("lib/") && candidate.endsWith(".ts")) ||
|
|
@@ -172,7 +181,6 @@ try {
|
|
|
172
181
|
);
|
|
173
182
|
}
|
|
174
183
|
console.log("[release] canonical Trace and Control writer residue checked");
|
|
175
|
-
|
|
176
184
|
// owner | class | lock | record bound | byte bound | recovery
|
|
177
185
|
const appendOnlyOwners = new Set([
|
|
178
186
|
"lib/runs-trace.ts", // canonical Run evidence | token lock | 2048 | 4 MiB | compact valid suffix
|
|
@@ -189,7 +197,6 @@ try {
|
|
|
189
197
|
check(appendOnlyOwners.has(path), `unregistered shipped append-only writer: ${path}`);
|
|
190
198
|
for (const path of appendOnlyOwners) check(fileSet.has(path), `stale append-only owner inventory: ${path}`);
|
|
191
199
|
console.log(`[release] append-only owner inventory checked: ${appendOnlyOwners.size} source owners`);
|
|
192
|
-
|
|
193
200
|
check(
|
|
194
201
|
(stagedText("scripts/validate-recipe.mjs") ?? "").includes(
|
|
195
202
|
"qaReport.diagnostics.length === 0 && qaReport.warnings.length === 0",
|
|
@@ -197,15 +204,13 @@ try {
|
|
|
197
204
|
"Recipe QA warnings are not release-blocking",
|
|
198
205
|
);
|
|
199
206
|
console.log("[release] zero-warning Recipe QA gate checked");
|
|
200
|
-
|
|
201
|
-
const maximumShippedLines = 29_598;
|
|
207
|
+
const maximumShippedLines = 32_000;
|
|
202
208
|
const shippedPath = /^(?:lib\/|scripts\/|recipes\/|docs\/|skills\/)/u;
|
|
203
209
|
const shippedLines = files
|
|
204
210
|
.filter((path) => shippedPath.test(path))
|
|
205
211
|
.reduce((total, path) => total + (((stagedText(path) ?? "").match(/\n/gu) ?? []).length + 1), 0);
|
|
206
212
|
check(shippedLines <= maximumShippedLines, `shipped lines ${shippedLines} exceed release maximum ${maximumShippedLines}`);
|
|
207
213
|
console.log(`[release] shipped lines ${shippedLines} <= release maximum ${maximumShippedLines}`);
|
|
208
|
-
|
|
209
214
|
const sources = files.filter((path) => path === "index.ts" || (path.startsWith("lib/") && path.endsWith(".ts")));
|
|
210
215
|
const sourceSet = new Set(sources);
|
|
211
216
|
const graph = new Map();
|
|
@@ -240,7 +245,6 @@ try {
|
|
|
240
245
|
}
|
|
241
246
|
for (const path of sources) visit(path);
|
|
242
247
|
console.log(`[release] strict Domain DAG: ${sources.length} sources, acyclic`);
|
|
243
|
-
|
|
244
248
|
for (const path of ["README.md", "AGENTS.md", "BACKLOG.md", "CHANGELOG.md", "docs/README.md"]) {
|
|
245
249
|
check(fileSet.has(path), `missing ABCd root/context file: ${path}`);
|
|
246
250
|
}
|
|
@@ -263,7 +267,6 @@ try {
|
|
|
263
267
|
check(docsIndex.includes(relative("docs", path).replaceAll("\\", "/")), `docs/README.md omits ${path}`);
|
|
264
268
|
}
|
|
265
269
|
console.log("[release] ABCd context roots, routing, and Markdown links checked");
|
|
266
|
-
|
|
267
270
|
if (failures.length > 0) {
|
|
268
271
|
for (const failure of failures) console.error(`[FAIL] ${failure}`);
|
|
269
272
|
process.exitCode = 1;
|
|
@@ -187,7 +187,6 @@ export function validateRecipes(argv) {
|
|
|
187
187
|
if (!targetArg || argv.includes("--help") || argv.includes("-h")) {
|
|
188
188
|
return { help: true, ok: Boolean(targetArg), usage: validateRecipeUsage() };
|
|
189
189
|
}
|
|
190
|
-
|
|
191
190
|
const files = recipeFiles(expandPath(targetArg), all);
|
|
192
191
|
const results = files.map((file) => validateFile(file, qa));
|
|
193
192
|
const failed = results.filter((result) => !result.ok).length;
|
package/skills/actors/SKILL.md
CHANGED
|
@@ -25,11 +25,11 @@ A Run target exposes exactly three inspect views: `recipe`, `trace`, and `contro
|
|
|
25
25
|
|
|
26
26
|
## Recipe
|
|
27
27
|
|
|
28
|
-
A Recipe defines execution. It may declare args,
|
|
28
|
+
A Recipe defines execution. It may declare named typed args, inline fallbacks, configuration `defaults`, composition `values`, imports, artifacts, command-template flags, and `control: ["action"]` when a long-lived service actually consumes actor-local inputs.
|
|
29
29
|
|
|
30
30
|
Do not declare Control for ordinary one-shot work. Runtime lifecycle actions such as `kill` stay runtime-owned and must not appear in Recipe Control declarations. Imported Recipes act as local definitions inside one Run; they do not create nested Runs unless execution explicitly spawns them.
|
|
31
31
|
|
|
32
|
-
Prefer maintained packaged Recipes over ad hoc wrappers. Keep model, thinking, mission, concurrency, quorum, and timeout choices caller-owned unless a Recipe documents a stable policy.
|
|
32
|
+
Prefer maintained packaged Recipes over ad hoc wrappers. Use `std:<recipe>` for exact packaged lookup and `skill:<skill>/<recipe-path>` for a component bundled with a Pi-active Skill. File-backed Recipes own `{recipe_dir}` and Skill Recipes own `{skill_dir}`; callers never pass or override these origins. Skill Recipes are components, not automatic tools. Keep model, thinking, mission, concurrency, quorum, and timeout choices caller-owned unless a Recipe documents a stable policy.
|
|
33
33
|
|
|
34
34
|
## Trace
|
|
35
35
|
|