@llblab/pi-actors 0.43.0 → 0.44.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 +16 -10
- package/CHANGELOG.md +425 -536
- package/README.md +13 -11
- package/dist/index.js +1 -1
- package/dist/lib/async-runs.d.ts +2 -1
- package/dist/lib/async-runs.js +24 -34
- package/dist/lib/automatic-review-runtime.d.ts +1 -1
- package/dist/lib/automatic-review-runtime.js +5 -5
- package/dist/lib/command-templates.d.ts +2 -0
- package/dist/lib/command-templates.js +38 -4
- package/dist/lib/control-projection.d.ts +20 -0
- package/dist/lib/control-projection.js +66 -0
- package/dist/lib/control.d.ts +3 -0
- package/dist/lib/control.js +27 -14
- package/dist/lib/draft-sleep.js +3 -3
- package/dist/lib/file-state.d.ts +4 -1
- package/dist/lib/file-state.js +118 -44
- package/dist/lib/inspector-overlay.d.ts +2 -0
- package/dist/lib/inspector-overlay.js +124 -69
- package/dist/lib/limits.d.ts +15 -3
- package/dist/lib/limits.js +15 -3
- package/dist/lib/observability.d.ts +4 -2
- package/dist/lib/observability.js +43 -36
- package/dist/lib/prompts.d.ts +1 -1
- package/dist/lib/prompts.js +1 -1
- package/dist/lib/recipe-control.js +6 -2
- package/dist/lib/review-control.d.ts +1 -1
- package/dist/lib/review-control.js +4 -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-control-delivery.d.ts +8 -1
- package/dist/lib/runs-control-delivery.js +38 -15
- package/dist/lib/runs-controls.d.ts +8 -4
- package/dist/lib/runs-controls.js +189 -50
- package/dist/lib/runs-retention.js +27 -14
- package/dist/lib/runs-trace.d.ts +26 -2
- package/dist/lib/runs-trace.js +411 -18
- package/dist/lib/runtime-identity.d.ts +7 -0
- package/dist/lib/runtime-identity.js +35 -0
- package/dist/lib/runtime-triage.d.ts +29 -0
- package/dist/lib/runtime-triage.js +60 -0
- package/dist/lib/tool-review-scheduler.js +7 -7
- package/dist/lib/tools-inspect.js +91 -18
- package/dist/lib/tools-message.d.ts +1 -2
- package/dist/lib/tools-message.js +6 -6
- package/dist/lib/tools-response.d.ts +0 -1
- package/dist/lib/tools-response.js +0 -9
- package/dist/lib/tools.d.ts +1 -1
- package/dist/lib/tools.js +1 -1
- package/dist/lib/trace-projection.js +107 -41
- package/dist/scripts/conformance.mjs +5 -0
- package/dist/scripts/locker.mjs +40 -90
- package/dist/scripts/music-player.mjs +48 -142
- package/dist/scripts/release-gates.mjs +56 -3
- package/dist/scripts/validate-recipe.mjs +5 -4
- package/dist/skills/actors/SKILL.md +17 -11
- package/dist/skills/swarm/SKILL.md +2 -4
- package/docs/README.md +1 -4
- package/docs/actor-inspector.md +6 -5
- package/docs/async-runs.md +11 -9
- package/docs/command-templates.md +6 -116
- package/docs/recipe-library.md +4 -6
- package/docs/releasing.md +28 -0
- package/docs/template-recipes.md +1 -1
- package/docs/tool-registry.md +2 -2
- package/index.ts +1 -1
- package/lib/async-runs.ts +26 -50
- package/lib/automatic-review-runtime.ts +7 -7
- package/lib/command-templates.ts +44 -4
- package/lib/control-projection.ts +105 -0
- package/lib/control.ts +33 -18
- package/lib/draft-sleep.ts +3 -3
- package/lib/file-state.ts +91 -63
- package/lib/inspector-overlay.ts +108 -61
- package/lib/limits.ts +15 -3
- package/lib/observability.ts +55 -57
- package/lib/prompts.ts +1 -1
- package/lib/recipe-control.ts +9 -2
- package/lib/review-control.ts +4 -5
- package/lib/run-evidence-policy.ts +242 -0
- package/lib/run-ui-runtime.ts +2 -0
- package/lib/runs-control-delivery.ts +45 -17
- package/lib/runs-controls.ts +180 -102
- package/lib/runs-retention.ts +28 -20
- package/lib/runs-trace.ts +499 -20
- package/lib/runtime-identity.ts +39 -0
- package/lib/runtime-triage.ts +106 -0
- package/lib/tool-review-scheduler.ts +7 -7
- package/lib/tools-inspect.ts +94 -20
- package/lib/tools-message.ts +7 -8
- package/lib/tools-response.ts +0 -12
- package/lib/tools.ts +4 -4
- package/lib/trace-projection.ts +156 -71
- package/package.json +1 -1
- package/scripts/conformance.mjs +5 -0
- package/scripts/locker.mjs +40 -90
- package/scripts/music-player.mjs +48 -142
- package/scripts/release-gates.mjs +56 -3
- package/scripts/validate-recipe.mjs +5 -4
- package/skills/actors/SKILL.md +17 -11
- package/skills/swarm/SKILL.md +2 -4
- package/dist/lib/runtime-notifier.d.ts +0 -48
- package/dist/lib/runtime-notifier.js +0 -138
- package/docs/0.43-baseline.md +0 -44
- package/docs/actors-deep-reference.md +0 -108
- package/docs/component-recipes.md +0 -45
- package/docs/task-first-recipes.md +0 -261
- package/lib/runtime-notifier.ts +0 -211
package/README.md
CHANGED
|
@@ -53,8 +53,10 @@ Send one exact Control:
|
|
|
53
53
|
Run targets accept only actions declared by the captured Recipe. Runtime targets accept only reserved review actions:
|
|
54
54
|
|
|
55
55
|
```text
|
|
56
|
-
message target=runtime action=review.retry
|
|
57
|
-
message target=runtime action=review.
|
|
56
|
+
message target=runtime action=review.retry input={"scope":"draft"}
|
|
57
|
+
message target=runtime action=review.retry input={"scope":"tool"}
|
|
58
|
+
message target=runtime action=review.reset input={"scope":"draft"}
|
|
59
|
+
message target=runtime action=review.reset input={"scope":"tool"}
|
|
58
60
|
```
|
|
59
61
|
|
|
60
62
|
Lifecycle `kill` remains runtime-owned rather than Recipe-declared.
|
|
@@ -67,9 +69,9 @@ Inspect one exact management target:
|
|
|
67
69
|
inspect target=run:test view=recipe
|
|
68
70
|
inspect target=run:test view=trace source=lifecycle lines=40
|
|
69
71
|
inspect target=run:test view=control
|
|
70
|
-
inspect target=runtime
|
|
71
|
-
inspect target=recipes
|
|
72
|
-
inspect target=tool:my_tool
|
|
72
|
+
inspect target=runtime view=status
|
|
73
|
+
inspect target=recipes view=status
|
|
74
|
+
inspect target=tool:my_tool view=status
|
|
73
75
|
```
|
|
74
76
|
|
|
75
77
|
A Run exposes exactly `recipe`, `trace`, and `control` views.
|
|
@@ -119,13 +121,13 @@ String command-template leaves execute directly without shell interpretation. Us
|
|
|
119
121
|
}
|
|
120
122
|
```
|
|
121
123
|
|
|
122
|
-
Trace fields are exact: `id`, `ts`, `kind`, and optional `summary`, `data`, `level`, `attention`. Address, sender, recipient, reply, and routing fields fail validation.
|
|
124
|
+
Trace fields are exact: `id`, `ts`, `kind`, and optional `summary`, `data`, `level`, `attention`. Address, sender, recipient, reply, and routing fields fail validation. Trace is a bounded retained suffix, not an audit archive: the canonical authority appends within 2,048 events and 4 MiB or atomically keeps the newest suffix plus one warning-only `runtime.trace_compacted` marker. That marker means older history was discarded; `result.json`, `execution.json`, terminal state, and declared artifacts retain their own authority. `inspect view=trace` reports whether retained history is complete. Reads are newline-safe and deterministic; first-party scripts never write this file directly.
|
|
123
125
|
|
|
124
|
-
Use `attention: "notify"` for visible status and `attention: "followup"` only when the coordinator needs semantic follow-up context. Store large evidence in artifacts or bounded execution captures.
|
|
126
|
+
Attention is a live wake hint, not a durable queue: persist recovery state or an artifact first. Use `attention: "notify"` for visible status and `attention: "followup"` only when the coordinator needs semantic follow-up context; compaction may discard older hints. Store large evidence in artifacts or bounded execution captures.
|
|
125
127
|
|
|
126
128
|
## Control
|
|
127
129
|
|
|
128
|
-
Controls persist to `controls.jsonl` before transport.
|
|
130
|
+
Controls persist to `controls.jsonl` before transport. One token-owned lock rejects a 65th pending Control or 1 MiB rewrite before admission, fails closed on malformed or stale-generation evidence, and atomically admits one record. Canonical transitions retain a 128-terminal tail, expected-state fencing, and 4 KiB errors. Admitted nonterminal Controls never expire automatically. Every record carries immutable `run_instance_id`. `inspect view=control` reports pending capacity, saturation, stale work, journal bytes, and bounded diagnostics. If a stuck Run is saturated, use runtime-owned `kill`; it bypasses actor-local capacity and creates no synthetic Control.
|
|
129
131
|
|
|
130
132
|
Long-lived services publish `control-endpoint.json` only when ready:
|
|
131
133
|
|
|
@@ -138,7 +140,7 @@ Long-lived services publish `control-endpoint.json` only when ready:
|
|
|
138
140
|
}
|
|
139
141
|
```
|
|
140
142
|
|
|
141
|
-
Supported transports are Unix FIFO and Windows named pipe.
|
|
143
|
+
Supported transports are Unix FIFO and Windows named pipe. Every actor-local Control uses the same portable envelope: action is at most 64 lowercase ASCII characters, serialized JSON input is at most 380 bytes, and the newline-terminated wire record is at most 512 bytes. Put larger data in a declared artifact/path and send only its bounded reference or instruction through Control. Delivery revalidates owner, generation, state, and process identity under the canonical lifecycle lock.
|
|
142
144
|
|
|
143
145
|
## Run State
|
|
144
146
|
|
|
@@ -157,7 +159,7 @@ Core files:
|
|
|
157
159
|
- `execution.json` — command/session provenance and complete-capture references;
|
|
158
160
|
- `result.json`, command logs, progress, and declared artifacts.
|
|
159
161
|
|
|
160
|
-
The runtime preserves owner filtering, process-identity verification, lifecycle locking, shutdown kill, terminal reconciliation, bounded captures, owned Pi sessions, path containment, and redaction.
|
|
162
|
+
The runtime preserves owner filtering, process-identity verification, lifecycle locking, shutdown kill independent of actor-local Control capacity, terminal reconciliation, bounded captures, owned Pi sessions, path containment, and redaction. Trace/Control quotas do not constrain user-declared artifacts, repositories, media sources, complete captures, or actor-owned workload state. Restart clears generation-local Trace, Control, endpoint, execution, and terminal evidence; archive preserves the bounded tree, while prune preserves only requested artifacts.
|
|
161
163
|
|
|
162
164
|
## Actor Inspector
|
|
163
165
|
|
|
@@ -195,7 +197,7 @@ npm run validate
|
|
|
195
197
|
npm run test:preservation
|
|
196
198
|
```
|
|
197
199
|
|
|
198
|
-
See the [documentation index](./docs/README.md), [Run lifecycle](./docs/async-runs.md), [Recipe library](./docs/recipe-library.md)
|
|
200
|
+
See the [documentation index](./docs/README.md), [Run lifecycle](./docs/async-runs.md), and [Recipe library](./docs/recipe-library.md).
|
|
199
201
|
|
|
200
202
|
Project context: [AGENTS.md](./AGENTS.md) · [BACKLOG.md](./BACKLOG.md) · [CHANGELOG.md](./CHANGELOG.md).
|
|
201
203
|
|
package/dist/index.js
CHANGED
|
@@ -106,7 +106,7 @@ export default function toolRegistryExtension(pi) {
|
|
|
106
106
|
configPath: Paths.EXTENSION_RUNTIME_PATHS.configPath,
|
|
107
107
|
getActiveTools: () => pi.getActiveTools(),
|
|
108
108
|
getRuntimeTool: (name) => Tools.resolveActiveRuntimeTool(name, runtime.getTools(), (activeName) => actorToolDefinitions.get(activeName)),
|
|
109
|
-
|
|
109
|
+
handleRuntimeControl: automaticReview.handleControl,
|
|
110
110
|
registryRuntime: runtime,
|
|
111
111
|
setActiveTools: (toolNames) => pi.setActiveTools(toolNames),
|
|
112
112
|
}).map(withCurrentThinkingContext));
|
package/dist/lib/async-runs.d.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { type CurrentPolicyProvenance } from "./model-context.ts";
|
|
|
7
7
|
import * as RecipesReferences from "./recipes-references.ts";
|
|
8
8
|
import { type RunArtifactDeclaration } from "./runs-artifacts.ts";
|
|
9
9
|
import { type RunProcessIdentity } from "./runs-process.ts";
|
|
10
|
+
import * as RuntimeIdentity from "./runtime-identity.ts";
|
|
10
11
|
import * as RunsIndex from "./runs-index.ts";
|
|
11
12
|
import * as RunsParentTeardown from "./runs-parent-teardown.ts";
|
|
12
13
|
import { type DeliverRunControlOptions, type DeliverRunControlRequest } from "./runs-control-delivery.ts";
|
|
@@ -77,7 +78,7 @@ export interface AsyncRunMeta {
|
|
|
77
78
|
run: string;
|
|
78
79
|
run_instance_id: string;
|
|
79
80
|
state_dir: string;
|
|
80
|
-
state_schema:
|
|
81
|
+
state_schema: typeof RuntimeIdentity.RUN_STATE_SCHEMA;
|
|
81
82
|
status: AsyncRunStatus;
|
|
82
83
|
tool?: string;
|
|
83
84
|
template: CommandTemplateValue;
|
package/dist/lib/async-runs.js
CHANGED
|
@@ -18,11 +18,11 @@ import { buildTerminalProgress, markTerminalHandled, signalOwnedRunProcess, } fr
|
|
|
18
18
|
import { claimRunStateDirectory } from "./runs-ownership.js";
|
|
19
19
|
import { appendRunRetentionEvidence, archiveTerminalRun, pruneTerminalRun, } from "./runs-retention.js";
|
|
20
20
|
import { captureRunProcessIdentity, verifyRunProcessIdentity, } from "./runs-process.js";
|
|
21
|
+
import * as RuntimeIdentity from "./runtime-identity.js";
|
|
21
22
|
import * as RunsStart from "./runs-start.js";
|
|
22
23
|
import { appendRunTraceEvent } from "./runs-trace.js";
|
|
23
24
|
import * as RunsIndex from "./runs-index.js";
|
|
24
25
|
import * as RunsParentTeardown from "./runs-parent-teardown.js";
|
|
25
|
-
import { appendRunControlInStateDir, updateRunControlStatusInStateDir, } from "./runs-controls.js";
|
|
26
26
|
import { deliverRunControl, } from "./runs-control-delivery.js";
|
|
27
27
|
import { buildRunStatus, tailFile, } from "./runs-status.js";
|
|
28
28
|
import { readJsonFileResilient } from "./state-readers.js";
|
|
@@ -236,7 +236,21 @@ export function startRun(params, cwd) {
|
|
|
236
236
|
const resolved = resolveRunTemplate(startParams);
|
|
237
237
|
const run = safeRunId(startParams.run_id);
|
|
238
238
|
const stateDir = resolveStateDir(startParams, run);
|
|
239
|
+
const recipeFile = startParams.file
|
|
240
|
+
? resolveRecipeFile(startParams.file)
|
|
241
|
+
: undefined;
|
|
242
|
+
const packagedRecipeRoot = resolve(Paths.getPackagedRecipeRoot());
|
|
243
|
+
const recipeRelation = recipeFile
|
|
244
|
+
? relative(packagedRecipeRoot, recipeFile)
|
|
245
|
+
: undefined;
|
|
246
|
+
const packagedRepo = startParams.defaults?.repo === "~/.pi/agent/extensions/pi-actors" &&
|
|
247
|
+
recipeRelation &&
|
|
248
|
+
!recipeRelation.startsWith("..") &&
|
|
249
|
+
!isAbsolute(recipeRelation)
|
|
250
|
+
? dirname(packagedRecipeRoot)
|
|
251
|
+
: undefined;
|
|
239
252
|
const values = {
|
|
253
|
+
...(packagedRepo ? { repo: packagedRepo } : {}),
|
|
240
254
|
...(startParams.values || {}),
|
|
241
255
|
run_id: run,
|
|
242
256
|
state_dir: stateDir,
|
|
@@ -259,9 +273,6 @@ export function startRun(params, cwd) {
|
|
|
259
273
|
prepareStateDirForStart(stateDir);
|
|
260
274
|
const stdout = join(stateDir, "stdout.log");
|
|
261
275
|
const stderr = join(stateDir, "stderr.log");
|
|
262
|
-
const recipeFile = startParams.file
|
|
263
|
-
? resolveRecipeFile(startParams.file)
|
|
264
|
-
: undefined;
|
|
265
276
|
const recipe = startParams.name || getRunIdFromFile(recipeFile);
|
|
266
277
|
const includeActorRecipeContext = startParams.actor_context !== false &&
|
|
267
278
|
startParams.actor_context !== "off";
|
|
@@ -298,7 +309,7 @@ export function startRun(params, cwd) {
|
|
|
298
309
|
run,
|
|
299
310
|
run_instance_id: randomUUID(),
|
|
300
311
|
state_dir: stateDir,
|
|
301
|
-
state_schema:
|
|
312
|
+
state_schema: RuntimeIdentity.RUN_STATE_SCHEMA,
|
|
302
313
|
status: "running",
|
|
303
314
|
...(startParams.tool ? { tool: startParams.tool } : {}),
|
|
304
315
|
template: resolved.template,
|
|
@@ -564,57 +575,36 @@ function stopRun(runOrDir, signal, event, expected = {}) {
|
|
|
564
575
|
status.run_instance_id !== expected.runInstanceId) {
|
|
565
576
|
return { stopped: false, reason: "run generation changed", status };
|
|
566
577
|
}
|
|
567
|
-
const control = event === "run.kill" && typeof status.run_instance_id === "string"
|
|
568
|
-
? appendRunControlInStateDir(stateDir, {
|
|
569
|
-
action: "kill",
|
|
570
|
-
run_instance_id: status.run_instance_id,
|
|
571
|
-
})
|
|
572
|
-
: undefined;
|
|
573
|
-
if (control) {
|
|
574
|
-
updateRunControlStatusInStateDir(stateDir, control.id, "claimed", {}, ["queued"]);
|
|
575
|
-
}
|
|
576
|
-
const finish = (result) => {
|
|
577
|
-
if (!control)
|
|
578
|
-
return result;
|
|
579
|
-
const handled = result.stopped === true;
|
|
580
|
-
updateRunControlStatusInStateDir(stateDir, control.id, handled ? "handled" : "failed", handled ? {} : { error: String(result.reason ?? "kill rejected") }, ["claimed"]);
|
|
581
|
-
return { ...result, control_id: control.id };
|
|
582
|
-
};
|
|
583
578
|
const pid = Number(status.pid || 0);
|
|
584
579
|
if (status.status !== "running" && status.status !== "exited") {
|
|
585
|
-
return
|
|
580
|
+
return { stopped: false, reason: "not running", status };
|
|
586
581
|
}
|
|
587
582
|
const identity = verifyRunProcessIdentity(pid, status.process_identity);
|
|
588
583
|
if (status.status === "exited") {
|
|
589
584
|
if (identity.status === "owner_mismatch" ||
|
|
590
585
|
identity.status === "unsupported_proof") {
|
|
591
|
-
return
|
|
586
|
+
return {
|
|
592
587
|
stopped: false,
|
|
593
588
|
reason: identity.status.replaceAll("_", " "),
|
|
594
589
|
process_identity_status: identity.status,
|
|
595
590
|
status,
|
|
596
|
-
}
|
|
591
|
+
};
|
|
597
592
|
}
|
|
598
|
-
return
|
|
593
|
+
return { stopped: false, reason: "not running", status };
|
|
599
594
|
}
|
|
600
595
|
if (!identity.valid) {
|
|
601
|
-
return
|
|
596
|
+
return {
|
|
602
597
|
stopped: false,
|
|
603
598
|
reason: identity.status.replaceAll("_", " "),
|
|
604
599
|
process_identity_status: identity.status,
|
|
605
600
|
status,
|
|
606
|
-
}
|
|
601
|
+
};
|
|
607
602
|
}
|
|
608
603
|
let signalResult;
|
|
609
604
|
try {
|
|
610
605
|
signalResult = signalOwnedRunProcess(pid, signal, status.process_identity);
|
|
611
606
|
}
|
|
612
607
|
catch (error) {
|
|
613
|
-
if (control) {
|
|
614
|
-
updateRunControlStatusInStateDir(stateDir, control.id, "failed", {
|
|
615
|
-
error: error instanceof Error ? error.message : String(error),
|
|
616
|
-
});
|
|
617
|
-
}
|
|
618
608
|
throw error;
|
|
619
609
|
}
|
|
620
610
|
appendRunTraceEvent(stateDir, {
|
|
@@ -631,13 +621,13 @@ function stopRun(runOrDir, signal, event, expected = {}) {
|
|
|
631
621
|
finalizeInterruptedExecution(stateDir, "cancelled", signal);
|
|
632
622
|
markTerminalProgress(stateDir, "cancelled");
|
|
633
623
|
}
|
|
634
|
-
return
|
|
624
|
+
return {
|
|
635
625
|
stopped: true,
|
|
636
626
|
pid,
|
|
637
627
|
signal,
|
|
638
628
|
...signalResult,
|
|
639
629
|
state_dir: stateDir,
|
|
640
|
-
}
|
|
630
|
+
};
|
|
641
631
|
}
|
|
642
632
|
finally {
|
|
643
633
|
releaseControlLock();
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import type * as Pi from "./pi.ts";
|
|
7
7
|
export interface AutomaticReviewRuntime {
|
|
8
8
|
close(): void;
|
|
9
|
-
|
|
9
|
+
handleControl(action: string, input: unknown): Record<string, unknown>;
|
|
10
10
|
schedule(): void;
|
|
11
11
|
start(ctx: Pi.ExtensionContext): void;
|
|
12
12
|
}
|
|
@@ -26,14 +26,14 @@ export function createAutomaticReviewRuntime(deps) {
|
|
|
26
26
|
});
|
|
27
27
|
return {
|
|
28
28
|
close,
|
|
29
|
-
|
|
30
|
-
if (
|
|
31
|
-
throw new Error("
|
|
29
|
+
handleControl(action, input) {
|
|
30
|
+
if (action !== "review.retry" && action !== "review.reset") {
|
|
31
|
+
throw new Error("runtime accepts review.retry or review.reset Controls.");
|
|
32
32
|
}
|
|
33
|
-
if (
|
|
33
|
+
if (action === "review.retry" && !Paths.isAutomaticRecipeReviewEnabled()) {
|
|
34
34
|
throw new Error("Automatic recipe review is disabled by PI_ACTORS_AUTOMATIC_REVIEW.");
|
|
35
35
|
}
|
|
36
|
-
return ReviewControl.controlAutomaticReview(
|
|
36
|
+
return ReviewControl.controlAutomaticReview(action, ReviewControl.parseAutomaticReviewScope(input), {
|
|
37
37
|
scheduleDraft: () => draftScheduler?.schedule(),
|
|
38
38
|
scheduleTool: () => toolScheduler?.schedule(),
|
|
39
39
|
});
|
|
@@ -74,6 +74,8 @@ export declare function getCommandTemplateRiskLabels(config: CommandTemplateConf
|
|
|
74
74
|
export declare function getCommandTemplateDefaults(config: CommandTemplateConfig | undefined): Record<string, string>;
|
|
75
75
|
export declare function splitCommandTemplate(input: string): string[];
|
|
76
76
|
export declare function expandCommandTemplateExecutable(command: string, cwd: string): string;
|
|
77
|
+
export type CommandTemplateExecutableResolver = (name: string) => string | undefined;
|
|
78
|
+
export declare function resolveCommandTemplateScriptInvocation(invocation: CommandTemplateInvocation, resolveExecutable?: CommandTemplateExecutableResolver): CommandTemplateInvocation;
|
|
77
79
|
export declare function shouldRunCommandTemplateNode(value: boolean | string | undefined, values: Record<string, unknown>): boolean;
|
|
78
80
|
export declare function substituteCommandTemplateToken(token: string, values: Record<string, unknown>, missingLabel?: string, depth?: number): string;
|
|
79
81
|
export declare function execCommandTemplate(command: string, args: string[], options?: CommandTemplateExecOptions): Promise<CommandTemplateExecResult>;
|
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
* Owns portable command-template parsing, expansion, risk checks, retries, timeouts, and direct execution.
|
|
5
5
|
*/
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
|
-
import { appendFileSync, mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
|
7
|
+
import { accessSync, appendFileSync, constants, mkdirSync, mkdtempSync, statSync, writeFileSync } from "node:fs";
|
|
8
8
|
import { homedir, tmpdir } from "node:os";
|
|
9
|
-
import { isAbsolute, join, resolve as resolvePath } from "node:path";
|
|
9
|
+
import { basename, delimiter, extname, isAbsolute, join, resolve as resolvePath } from "node:path";
|
|
10
10
|
const DEFAULT_COMMAND_CAPTURE_LIMIT_BYTES = 1024 * 1024;
|
|
11
11
|
const COMMAND_TEMPLATE_RISK_LABEL_ORDER = [
|
|
12
12
|
"risk.shell",
|
|
@@ -135,7 +135,7 @@ function getLeafCommandTemplateRiskLabels(config) {
|
|
|
135
135
|
const command = getExecutableName(parts[0]);
|
|
136
136
|
const args = parts.slice(1);
|
|
137
137
|
const labels = new Set();
|
|
138
|
-
if (["bash", "sh", "zsh", "fish"].includes(command)) {
|
|
138
|
+
if (["bash", "sh", "zsh", "fish"].includes(command) || command.endsWith(".sh")) {
|
|
139
139
|
labels.add("risk.shell");
|
|
140
140
|
if (hasAnyFlag(args, ["-c"]))
|
|
141
141
|
labels.add("risk.eval");
|
|
@@ -393,6 +393,40 @@ export function expandCommandTemplateExecutable(command, cwd) {
|
|
|
393
393
|
return resolvePath(cwd, command);
|
|
394
394
|
return command;
|
|
395
395
|
}
|
|
396
|
+
function resolveExecutableOnPath(name) {
|
|
397
|
+
if (name === "node" && /^node(?:\.exe)?$/i.test(basename(process.execPath)))
|
|
398
|
+
return process.execPath;
|
|
399
|
+
const pathValue = Object.entries(process.env)
|
|
400
|
+
.find(([key]) => key.toLowerCase() === "path")?.[1] ?? "";
|
|
401
|
+
const suffixes = process.platform === "win32" ? [".exe", ".com"] : [""];
|
|
402
|
+
for (const entry of pathValue.split(delimiter).filter(Boolean)) {
|
|
403
|
+
const directory = entry.replace(/^"|"$/g, "");
|
|
404
|
+
for (const suffix of suffixes) {
|
|
405
|
+
const candidate = join(directory, `${name}${suffix}`);
|
|
406
|
+
try {
|
|
407
|
+
accessSync(candidate, constants.X_OK);
|
|
408
|
+
if (statSync(candidate).isFile())
|
|
409
|
+
return candidate;
|
|
410
|
+
}
|
|
411
|
+
catch { }
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
return undefined;
|
|
415
|
+
}
|
|
416
|
+
export function resolveCommandTemplateScriptInvocation(invocation, resolveExecutable = resolveExecutableOnPath) {
|
|
417
|
+
const extension = extname(invocation.command).toLowerCase();
|
|
418
|
+
const interpreters = extension === ".js" || extension === ".mjs"
|
|
419
|
+
? [["node"], ["bun"], ["deno", "run"]]
|
|
420
|
+
: extension === ".sh" ? [["bash"]] : [];
|
|
421
|
+
if (interpreters.length === 0)
|
|
422
|
+
return invocation;
|
|
423
|
+
for (const [name, ...prefix] of interpreters) {
|
|
424
|
+
const executable = resolveExecutable(name);
|
|
425
|
+
if (executable)
|
|
426
|
+
return { command: executable, args: [...prefix, invocation.command, ...invocation.args] };
|
|
427
|
+
}
|
|
428
|
+
throw new Error(`No interpreter available for command-template ${extension} script; tried ${interpreters.map(([name]) => name).join(", ")}.`);
|
|
429
|
+
}
|
|
396
430
|
function evaluateCommandTemplateExpression(expression, values) {
|
|
397
431
|
let index = 0;
|
|
398
432
|
const source = expression.replace(/\s+/g, "");
|
|
@@ -766,5 +800,5 @@ export function buildCommandTemplateInvocation(config, values, cwd, options = {}
|
|
|
766
800
|
.slice(1)
|
|
767
801
|
.map((part) => substituteCommandTemplateToken(part, resolvedValues, options.missingLabel))
|
|
768
802
|
.filter((part) => part !== "");
|
|
769
|
-
return { command, args };
|
|
803
|
+
return resolveCommandTemplateScriptInvocation({ command, args });
|
|
770
804
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing Run Control projection.
|
|
3
|
+
* Zones: structured redaction, bounded Control input/error, decision-useful evidence
|
|
4
|
+
* Owns safe Control reads; durable journals and lifecycle transitions remain in runs-controls.
|
|
5
|
+
*/
|
|
6
|
+
import type { RunControlRecord, RunControlStatus } from "./runs-controls.ts";
|
|
7
|
+
export interface ProjectedRunControl {
|
|
8
|
+
action: string;
|
|
9
|
+
claimed_at?: string;
|
|
10
|
+
delivered_at?: string;
|
|
11
|
+
error?: string;
|
|
12
|
+
failed_at?: string;
|
|
13
|
+
handled_at?: string;
|
|
14
|
+
id: string;
|
|
15
|
+
input?: unknown;
|
|
16
|
+
queued_at: string;
|
|
17
|
+
run_instance_id: string;
|
|
18
|
+
status: RunControlStatus;
|
|
19
|
+
}
|
|
20
|
+
export declare function projectRunControl(control: RunControlRecord): ProjectedRunControl;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Model-facing Run Control projection.
|
|
3
|
+
* Zones: structured redaction, bounded Control input/error, decision-useful evidence
|
|
4
|
+
* Owns safe Control reads; durable journals and lifecycle transitions remain in runs-controls.
|
|
5
|
+
*/
|
|
6
|
+
import * as Limits from "./limits.js";
|
|
7
|
+
import * as SessionEvidence from "./session-evidence.js";
|
|
8
|
+
const OPTIONAL_TIMESTAMP_FIELDS = [
|
|
9
|
+
"claimed_at",
|
|
10
|
+
"delivered_at",
|
|
11
|
+
"failed_at",
|
|
12
|
+
"handled_at",
|
|
13
|
+
];
|
|
14
|
+
function boundedRedactedString(value, maxChars) {
|
|
15
|
+
const redacted = SessionEvidence.redactSessionEvidenceValue(value, maxChars);
|
|
16
|
+
return typeof redacted === "string"
|
|
17
|
+
? redacted
|
|
18
|
+
: String(redacted).slice(0, maxChars);
|
|
19
|
+
}
|
|
20
|
+
function boundedRedactedValue(value) {
|
|
21
|
+
const redacted = SessionEvidence.redactSessionEvidenceValue(value, Limits.INSPECTOR_BODY_PREVIEW_CHARS);
|
|
22
|
+
let serialized;
|
|
23
|
+
try {
|
|
24
|
+
serialized = JSON.stringify(redacted);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return "[UNSERIALIZABLE]";
|
|
28
|
+
}
|
|
29
|
+
if (serialized === undefined)
|
|
30
|
+
return "[UNSERIALIZABLE]";
|
|
31
|
+
if (serialized.length <= Limits.INSPECTOR_BODY_PREVIEW_CHARS)
|
|
32
|
+
return redacted;
|
|
33
|
+
let preview = serialized.slice(0, Limits.INSPECTOR_BODY_PREVIEW_CHARS);
|
|
34
|
+
let bounded = { preview: `${preview}…`, truncated: true };
|
|
35
|
+
while (preview &&
|
|
36
|
+
JSON.stringify(bounded).length > Limits.INSPECTOR_BODY_PREVIEW_CHARS) {
|
|
37
|
+
const excess = JSON.stringify(bounded).length - Limits.INSPECTOR_BODY_PREVIEW_CHARS;
|
|
38
|
+
preview = preview.slice(0, Math.max(0, preview.length - Math.max(1, excess)));
|
|
39
|
+
bounded = { preview: `${preview}…`, truncated: true };
|
|
40
|
+
}
|
|
41
|
+
return bounded;
|
|
42
|
+
}
|
|
43
|
+
export function projectRunControl(control) {
|
|
44
|
+
const timestamps = Object.fromEntries(OPTIONAL_TIMESTAMP_FIELDS.flatMap((field) => {
|
|
45
|
+
const value = control[field];
|
|
46
|
+
return typeof value === "string"
|
|
47
|
+
? [[field, boundedRedactedString(value, Limits.COMPACT_PREVIEW_CHARS)]]
|
|
48
|
+
: [];
|
|
49
|
+
}));
|
|
50
|
+
return {
|
|
51
|
+
action: boundedRedactedString(control.action, Limits.COMPACT_PREVIEW_CHARS),
|
|
52
|
+
...timestamps,
|
|
53
|
+
...(typeof control.error === "string"
|
|
54
|
+
? {
|
|
55
|
+
error: boundedRedactedString(control.error, Limits.INSPECTOR_BODY_PREVIEW_CHARS),
|
|
56
|
+
}
|
|
57
|
+
: {}),
|
|
58
|
+
id: boundedRedactedString(control.id, Limits.COMPACT_PREVIEW_CHARS),
|
|
59
|
+
...(control.input !== undefined
|
|
60
|
+
? { input: boundedRedactedValue(control.input) }
|
|
61
|
+
: {}),
|
|
62
|
+
queued_at: boundedRedactedString(control.queued_at, Limits.COMPACT_PREVIEW_CHARS),
|
|
63
|
+
run_instance_id: boundedRedactedString(control.run_instance_id, Limits.COMPACT_PREVIEW_CHARS),
|
|
64
|
+
status: boundedRedactedString(control.status, Limits.COMPACT_PREVIEW_CHARS),
|
|
65
|
+
};
|
|
66
|
+
}
|
package/dist/lib/control.d.ts
CHANGED
|
@@ -9,4 +9,7 @@ export interface ControlRequest {
|
|
|
9
9
|
input?: unknown;
|
|
10
10
|
verbose?: boolean;
|
|
11
11
|
}
|
|
12
|
+
export declare function isControlAction(value: string): boolean;
|
|
13
|
+
export declare function normalizeControlAction(value: unknown): string;
|
|
14
|
+
export declare function normalizeControlInput(input: unknown): unknown;
|
|
12
15
|
export declare function normalizeControlRequest(input: unknown): ControlRequest;
|
package/dist/lib/control.js
CHANGED
|
@@ -17,7 +17,23 @@ const REMOVED_FIELDS = new Set([
|
|
|
17
17
|
"to",
|
|
18
18
|
"type",
|
|
19
19
|
]);
|
|
20
|
-
function
|
|
20
|
+
export function isControlAction(value) {
|
|
21
|
+
return ACTION_PATTERN.test(value);
|
|
22
|
+
}
|
|
23
|
+
export function normalizeControlAction(value) {
|
|
24
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
25
|
+
throw new Error("control.action is required");
|
|
26
|
+
}
|
|
27
|
+
const action = value.trim();
|
|
28
|
+
if (!isControlAction(action)) {
|
|
29
|
+
throw new Error(`invalid control action: ${action}`);
|
|
30
|
+
}
|
|
31
|
+
if (action.length > Limits.CONTROL_ACTION_MAX_LENGTH) {
|
|
32
|
+
throw new Error(`control.action exceeds ${Limits.CONTROL_ACTION_MAX_LENGTH} ASCII characters`);
|
|
33
|
+
}
|
|
34
|
+
return action;
|
|
35
|
+
}
|
|
36
|
+
export function normalizeControlInput(input) {
|
|
21
37
|
let serialized;
|
|
22
38
|
try {
|
|
23
39
|
serialized = JSON.stringify(input);
|
|
@@ -28,7 +44,11 @@ function serializedInputBytes(input) {
|
|
|
28
44
|
if (serialized === undefined) {
|
|
29
45
|
throw new Error("control.input must be JSON-serializable");
|
|
30
46
|
}
|
|
31
|
-
|
|
47
|
+
const bytes = Buffer.byteLength(serialized);
|
|
48
|
+
if (bytes > Limits.CONTROL_INPUT_MAX_BYTES) {
|
|
49
|
+
throw new Error(`control.input exceeds ${Limits.CONTROL_INPUT_MAX_BYTES} serialized bytes`);
|
|
50
|
+
}
|
|
51
|
+
return JSON.parse(serialized);
|
|
32
52
|
}
|
|
33
53
|
function normalizeTarget(value) {
|
|
34
54
|
if (typeof value !== "string" || !value.trim()) {
|
|
@@ -61,24 +81,17 @@ export function normalizeControlRequest(input) {
|
|
|
61
81
|
throw new Error(`unsupported control fields: ${unknown.sort().join(", ")}`);
|
|
62
82
|
}
|
|
63
83
|
const target = normalizeTarget(record.target);
|
|
64
|
-
|
|
65
|
-
throw new Error("control.action is required");
|
|
66
|
-
}
|
|
67
|
-
const action = record.action.trim();
|
|
68
|
-
if (!ACTION_PATTERN.test(action)) {
|
|
69
|
-
throw new Error(`invalid control action: ${action}`);
|
|
70
|
-
}
|
|
84
|
+
const action = normalizeControlAction(record.action);
|
|
71
85
|
if (record.verbose !== undefined && typeof record.verbose !== "boolean") {
|
|
72
86
|
throw new Error("control.verbose must be a boolean");
|
|
73
87
|
}
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
88
|
+
const normalizedInput = record.input === undefined
|
|
89
|
+
? undefined
|
|
90
|
+
: normalizeControlInput(record.input);
|
|
78
91
|
return {
|
|
79
92
|
target,
|
|
80
93
|
action,
|
|
81
|
-
...(
|
|
94
|
+
...(normalizedInput !== undefined ? { input: normalizedInput } : {}),
|
|
82
95
|
...(record.verbose !== undefined ? { verbose: record.verbose } : {}),
|
|
83
96
|
};
|
|
84
97
|
}
|
package/dist/lib/draft-sleep.js
CHANGED
|
@@ -391,7 +391,7 @@ export function createDraftSleepScheduler(deps) {
|
|
|
391
391
|
...existing,
|
|
392
392
|
failedStage: processed.stage ?? "result_processing",
|
|
393
393
|
lastError: processed.error ?? "automatic draft review processing failed",
|
|
394
|
-
nextAction: "message
|
|
394
|
+
nextAction: "message target=runtime action=review.retry input={\"scope\":\"draft\"}",
|
|
395
395
|
phase: "processing_failed",
|
|
396
396
|
processingAttempts: (existing.processingAttempts ?? 0) + 1,
|
|
397
397
|
updatedAt: now().toISOString(),
|
|
@@ -402,7 +402,7 @@ export function createDraftSleepScheduler(deps) {
|
|
|
402
402
|
...existing,
|
|
403
403
|
failedStage: processed.stage ?? "review_result",
|
|
404
404
|
lastError: processed.error ?? "automatic draft reviewer failed",
|
|
405
|
-
nextAction: "message
|
|
405
|
+
nextAction: "message target=runtime action=review.retry input={\"scope\":\"draft\"}",
|
|
406
406
|
phase: "failed",
|
|
407
407
|
runId: undefined,
|
|
408
408
|
updatedAt: now().toISOString(),
|
|
@@ -444,7 +444,7 @@ export function createDraftSleepScheduler(deps) {
|
|
|
444
444
|
attempts: (prior?.batchId === batch.batchId ? prior.attempts : 0) + 1,
|
|
445
445
|
failedStage: "review_launch",
|
|
446
446
|
lastError: error instanceof Error ? error.message : String(error),
|
|
447
|
-
nextAction: "message
|
|
447
|
+
nextAction: "message target=runtime action=review.retry input={\"scope\":\"draft\"}",
|
|
448
448
|
batchId: batch.batchId,
|
|
449
449
|
inputPath: batch.inputPath,
|
|
450
450
|
phase: "failed",
|
package/dist/lib/file-state.d.ts
CHANGED
|
@@ -5,11 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
export declare function mutationLockPath(path: string): string;
|
|
7
7
|
export interface FileMutationLockOptions {
|
|
8
|
+
onBeforeLockPublish?(): void;
|
|
8
9
|
onBeforeReclaimRemove?(): void;
|
|
9
10
|
onContention?(): void;
|
|
10
11
|
onRemovalContention?(): void;
|
|
11
12
|
}
|
|
12
13
|
export declare function acquireFileMutationLock(path: string, options?: FileMutationLockOptions): () => void;
|
|
13
14
|
export declare function withFileMutationLock<T>(path: string, mutate: () => T, options?: FileMutationLockOptions): T;
|
|
14
|
-
export declare function writeTextAtomic(path: string, content: string
|
|
15
|
+
export declare function writeTextAtomic(path: string, content: string, options?: {
|
|
16
|
+
onBeforeReplace?(): void;
|
|
17
|
+
}): void;
|
|
15
18
|
export declare function writeJsonAtomic(path: string, value: unknown): void;
|