@llblab/pi-actors 0.41.0 → 0.42.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 +13 -8
- package/BACKLOG.md +2 -81
- package/CHANGELOG.md +22 -0
- package/README.md +22 -4
- package/dist/index.js +29 -82
- package/dist/lib/async-runs.d.ts +25 -5
- package/dist/lib/async-runs.js +136 -47
- package/dist/lib/automatic-review-runtime.d.ts +18 -0
- package/dist/lib/automatic-review-runtime.js +96 -0
- package/dist/lib/draft-consolidation-transaction.d.ts +65 -0
- package/dist/lib/draft-consolidation-transaction.js +610 -0
- package/dist/lib/draft-consolidation.d.ts +35 -0
- package/dist/lib/draft-consolidation.js +126 -0
- package/dist/lib/draft-review.d.ts +56 -0
- package/dist/lib/draft-review.js +254 -0
- package/dist/lib/draft-sleep.d.ts +65 -0
- package/dist/lib/draft-sleep.js +468 -0
- package/dist/lib/file-state.d.ts +8 -1
- package/dist/lib/file-state.js +115 -18
- package/dist/lib/inspector-actions.d.ts +16 -0
- package/dist/lib/inspector-actions.js +57 -0
- package/dist/lib/inspector-command.d.ts +7 -0
- package/dist/lib/inspector-command.js +37 -0
- package/dist/lib/inspector-overlay.d.ts +29 -1
- package/dist/lib/inspector-overlay.js +537 -90
- package/dist/lib/inspector.d.ts +9 -0
- package/dist/lib/inspector.js +52 -1
- package/dist/lib/observability.d.ts +39 -3
- package/dist/lib/observability.js +162 -34
- package/dist/lib/paths.d.ts +7 -0
- package/dist/lib/paths.js +28 -0
- package/dist/lib/recipes-discovery.js +32 -24
- package/dist/lib/recipes-usage.d.ts +18 -6
- package/dist/lib/recipes-usage.js +445 -34
- package/dist/lib/review-control.d.ts +14 -0
- package/dist/lib/review-control.js +111 -0
- package/dist/lib/review-diagnostics.d.ts +11 -0
- package/dist/lib/review-diagnostics.js +148 -0
- package/dist/lib/review-projection.d.ts +14 -0
- package/dist/lib/review-projection.js +170 -0
- package/dist/lib/run-ui-runtime.d.ts +18 -0
- package/dist/lib/run-ui-runtime.js +123 -0
- package/dist/lib/runs-artifacts.d.ts +1 -1
- package/dist/lib/runs-artifacts.js +1 -1
- package/dist/lib/runs-control.d.ts +8 -2
- package/dist/lib/runs-control.js +23 -6
- package/dist/lib/runs-identity.d.ts +1 -1
- package/dist/lib/runs-identity.js +1 -1
- package/dist/lib/runs-index.d.ts +11 -2
- package/dist/lib/runs-index.js +46 -23
- package/dist/lib/runs-mailbox.d.ts +1 -1
- package/dist/lib/runs-mailbox.js +1 -1
- package/dist/lib/runs-messages.d.ts +1 -1
- package/dist/lib/runs-messages.js +1 -1
- package/dist/lib/runs-outbox.d.ts +1 -1
- package/dist/lib/runs-outbox.js +1 -1
- package/dist/lib/runs-ownership.d.ts +1 -1
- package/dist/lib/runs-ownership.js +1 -1
- package/dist/lib/runs-parent-teardown.d.ts +51 -0
- package/dist/lib/runs-parent-teardown.js +172 -0
- package/dist/lib/runs-process.d.ts +1 -1
- package/dist/lib/runs-process.js +1 -1
- package/dist/lib/runs-retention.d.ts +1 -1
- package/dist/lib/runs-retention.js +1 -1
- package/dist/lib/runs-start.d.ts +5 -3
- package/dist/lib/runs-start.js +6 -48
- package/dist/lib/runs-status.d.ts +5 -3
- package/dist/lib/runs-status.js +4 -6
- package/dist/lib/runtime.d.ts +7 -1
- package/dist/lib/runtime.js +32 -18
- package/dist/lib/tool-review-lineage-transaction.d.ts +27 -0
- package/dist/lib/tool-review-lineage-transaction.js +597 -0
- package/dist/lib/tool-review-lineage.d.ts +24 -0
- package/dist/lib/tool-review-lineage.js +98 -0
- package/dist/lib/tool-review-scheduler.d.ts +80 -0
- package/dist/lib/tool-review-scheduler.js +494 -0
- package/dist/lib/tool-review-transaction.d.ts +50 -0
- package/dist/lib/tool-review-transaction.js +362 -0
- package/dist/lib/tool-review.d.ts +56 -0
- package/dist/lib/tool-review.js +197 -0
- package/dist/lib/tools-inspect.js +26 -3
- package/dist/lib/tools-local.js +4 -2
- package/dist/lib/tools-message.d.ts +1 -0
- package/dist/lib/tools-message.js +29 -17
- package/dist/lib/tools-response.d.ts +1 -1
- package/dist/lib/tools-response.js +5 -7
- package/dist/lib/tools-spawn.js +4 -1
- package/dist/lib/tools.d.ts +1 -0
- package/dist/lib/tools.js +1 -0
- package/dist/recipes/draft-review.json +24 -0
- package/dist/recipes/tool-review.json +24 -0
- package/dist/scripts/release-gates.mjs +165 -0
- package/dist/skills/actors/SKILL.md +11 -14
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/actor-inspector.md +32 -18
- package/docs/async-runs.md +10 -2
- package/docs/recipe-library.md +7 -3
- package/docs/template-recipes.md +4 -11
- package/docs/tool-registry.md +11 -4
- package/index.ts +29 -103
- package/lib/async-runs.ts +218 -62
- package/lib/automatic-review-runtime.ts +135 -0
- package/lib/draft-consolidation-transaction.ts +821 -0
- package/lib/draft-consolidation.ts +181 -0
- package/lib/draft-review.ts +325 -0
- package/lib/draft-sleep.ts +576 -0
- package/lib/file-state.ts +143 -19
- package/lib/inspector-actions.ts +79 -0
- package/lib/inspector-command.ts +54 -0
- package/lib/inspector-overlay.ts +675 -105
- package/lib/inspector.ts +78 -3
- package/lib/observability.ts +219 -40
- package/lib/paths.ts +43 -0
- package/lib/recipes-discovery.ts +34 -26
- package/lib/recipes-usage.ts +569 -40
- package/lib/review-control.ts +137 -0
- package/lib/review-diagnostics.ts +164 -0
- package/lib/review-projection.ts +200 -0
- package/lib/run-ui-runtime.ts +153 -0
- package/lib/runs-artifacts.ts +1 -1
- package/lib/runs-control.ts +49 -5
- package/lib/runs-identity.ts +1 -1
- package/lib/runs-index.ts +57 -21
- package/lib/runs-mailbox.ts +1 -1
- package/lib/runs-messages.ts +1 -1
- package/lib/runs-outbox.ts +1 -1
- package/lib/runs-ownership.ts +1 -1
- package/lib/runs-parent-teardown.ts +257 -0
- package/lib/runs-process.ts +1 -1
- package/lib/runs-retention.ts +1 -1
- package/lib/runs-start.ts +12 -68
- package/lib/runs-status.ts +12 -8
- package/lib/runtime.ts +34 -17
- package/lib/tool-review-lineage-transaction.ts +881 -0
- package/lib/tool-review-lineage.ts +145 -0
- package/lib/tool-review-scheduler.ts +635 -0
- package/lib/tool-review-transaction.ts +563 -0
- package/lib/tool-review.ts +270 -0
- package/lib/tools-inspect.ts +33 -3
- package/lib/tools-local.ts +8 -2
- package/lib/tools-message.ts +45 -30
- package/lib/tools-response.ts +5 -6
- package/lib/tools-spawn.ts +8 -1
- package/lib/tools.ts +5 -0
- package/package.json +3 -2
- package/recipes/draft-review.json +24 -0
- package/recipes/tool-review.json +24 -0
- package/scripts/release-gates.mjs +165 -0
- package/skills/actors/SKILL.md +11 -14
- package/skills/swarm/SKILL.md +1 -1
package/dist/lib/inspector.d.ts
CHANGED
|
@@ -55,10 +55,19 @@ export interface ActorInspectorPreviewReadOptions {
|
|
|
55
55
|
}
|
|
56
56
|
export interface ActorInspectorRunItem {
|
|
57
57
|
run: string;
|
|
58
|
+
runInstanceId?: string;
|
|
58
59
|
status: string;
|
|
59
60
|
updatedAt?: string;
|
|
60
61
|
}
|
|
61
62
|
export declare function readActorInspectorRuns(stateRoot: string, ownerId: string): ActorInspectorRunItem[];
|
|
63
|
+
export interface ActorInspectorRecipeView {
|
|
64
|
+
composition: Array<Record<string, unknown>>;
|
|
65
|
+
definition?: Record<string, unknown>;
|
|
66
|
+
diagnostics: string[];
|
|
67
|
+
identity: Record<string, unknown>;
|
|
68
|
+
launch: Record<string, unknown>;
|
|
69
|
+
}
|
|
70
|
+
export declare function readActorInspectorRecipe(stateDir: string): ActorInspectorRecipeView;
|
|
62
71
|
export interface ActorInspectorTurnItem extends SessionEvidence.SessionEvidenceTurn {
|
|
63
72
|
commandId: string;
|
|
64
73
|
diagnostics: string[];
|
package/dist/lib/inspector.js
CHANGED
|
@@ -19,10 +19,14 @@ export function readActorInspectorRuns(stateRoot, ownerId) {
|
|
|
19
19
|
const stateDir = path.join(stateRoot, entry.name);
|
|
20
20
|
if (!matchesOwner(stateDir, ownerId))
|
|
21
21
|
return [];
|
|
22
|
+
const runMeta = readJsonFileResilient(path.join(stateDir, "run.json"), {}).value;
|
|
22
23
|
const progress = readJsonFileResilient(path.join(stateDir, "progress.json"), {}).value;
|
|
23
24
|
const result = readJsonFileResilient(path.join(stateDir, "result.json"), {}).value;
|
|
24
25
|
return [{
|
|
25
26
|
run: entry.name,
|
|
27
|
+
...(typeof runMeta.run_instance_id === "string"
|
|
28
|
+
? { runInstanceId: runMeta.run_instance_id }
|
|
29
|
+
: {}),
|
|
26
30
|
status: String(progress.phase ?? (Object.keys(result).length ? "terminal" : "unknown")),
|
|
27
31
|
...(typeof progress.updatedAt === "string"
|
|
28
32
|
? { updatedAt: progress.updatedAt }
|
|
@@ -31,7 +35,10 @@ export function readActorInspectorRuns(stateRoot, ownerId) {
|
|
|
31
35
|
: {}),
|
|
32
36
|
}];
|
|
33
37
|
})
|
|
34
|
-
.sort((left, right) =>
|
|
38
|
+
.sort((left, right) => {
|
|
39
|
+
const recency = String(right.updatedAt ?? "").localeCompare(String(left.updatedAt ?? ""));
|
|
40
|
+
return recency || right.run.localeCompare(left.run);
|
|
41
|
+
});
|
|
35
42
|
}
|
|
36
43
|
catch (error) {
|
|
37
44
|
if (error.code === "ENOENT")
|
|
@@ -39,6 +46,50 @@ export function readActorInspectorRuns(stateRoot, ownerId) {
|
|
|
39
46
|
return [];
|
|
40
47
|
}
|
|
41
48
|
}
|
|
49
|
+
export function readActorInspectorRecipe(stateDir) {
|
|
50
|
+
const result = readJsonFileResilient(path.join(stateDir, "run.json"), {});
|
|
51
|
+
const meta = result.value;
|
|
52
|
+
const contexts = Array.isArray(meta.recipe_context_records)
|
|
53
|
+
? meta.recipe_context_records
|
|
54
|
+
.filter((entry) => Boolean(entry && typeof entry === "object" && !Array.isArray(entry)))
|
|
55
|
+
: [];
|
|
56
|
+
const primary = contexts.find((entry) => entry.depth === 0) ?? contexts[0];
|
|
57
|
+
const definition = primary?.recipe && typeof primary.recipe === "object" &&
|
|
58
|
+
!Array.isArray(primary.recipe)
|
|
59
|
+
? primary.recipe
|
|
60
|
+
: undefined;
|
|
61
|
+
const redacted = (value) => SessionEvidence.redactSessionEvidenceValue(value);
|
|
62
|
+
const redactedRecord = (value) => redacted(Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined)));
|
|
63
|
+
return {
|
|
64
|
+
composition: contexts.map((entry) => redactedRecord({
|
|
65
|
+
...(typeof entry.alias === "string" ? { alias: entry.alias } : {}),
|
|
66
|
+
depth: entry.depth,
|
|
67
|
+
file: entry.file,
|
|
68
|
+
import_path: entry.import_path,
|
|
69
|
+
name: entry.name,
|
|
70
|
+
})),
|
|
71
|
+
...(definition
|
|
72
|
+
? { definition: redactedRecord(definition) }
|
|
73
|
+
: {}),
|
|
74
|
+
diagnostics: result.diagnostics.map((diagnostic) => diagnostic.message),
|
|
75
|
+
identity: redactedRecord({
|
|
76
|
+
run: meta.run,
|
|
77
|
+
recipe: meta.recipe,
|
|
78
|
+
recipe_file: meta.recipe_file,
|
|
79
|
+
launch_source: meta.launch_source,
|
|
80
|
+
}),
|
|
81
|
+
launch: redactedRecord({
|
|
82
|
+
cwd: meta.cwd,
|
|
83
|
+
template: meta.template,
|
|
84
|
+
values: meta.values,
|
|
85
|
+
model_policy: meta.model_policy,
|
|
86
|
+
mailbox: meta.mailbox,
|
|
87
|
+
artifacts: meta.artifacts,
|
|
88
|
+
notification_policy: meta.notification_policy,
|
|
89
|
+
retire_when: meta.retire_when,
|
|
90
|
+
}),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
42
93
|
function ownedSessionPath(stateDir, sessionFile) {
|
|
43
94
|
if (path.isAbsolute(sessionFile))
|
|
44
95
|
return undefined;
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Zones: async runtime, ambient UI, diagnostics
|
|
4
4
|
* Owns ambient summaries, terminal events, and run outbox delivery for detached command-template runs
|
|
5
5
|
*/
|
|
6
|
+
import { type FSWatcher } from "node:fs";
|
|
6
7
|
import * as AsyncRuns from "./async-runs.ts";
|
|
7
8
|
export type RunObservedStatus = "running" | "done" | "failed" | "exited" | "cancelled" | "killed";
|
|
8
9
|
export type RunOutboxDelivery = "log" | "notify" | "followup";
|
|
@@ -16,6 +17,7 @@ export interface RunObservation {
|
|
|
16
17
|
artifacts?: Record<string, string>;
|
|
17
18
|
launchSource?: AsyncRuns.AsyncRunLaunchSource;
|
|
18
19
|
modelPolicy?: Record<string, unknown>;
|
|
20
|
+
notificationPolicy?: "normal" | "silent";
|
|
19
21
|
recipeFile?: string;
|
|
20
22
|
terminalHandled?: boolean;
|
|
21
23
|
retireWhen?: string;
|
|
@@ -58,9 +60,19 @@ export interface RunUiNotificationSink {
|
|
|
58
60
|
}): void;
|
|
59
61
|
}
|
|
60
62
|
export declare function createRunUiObservationState(): RunUiObservationState;
|
|
61
|
-
export declare function readRunUiSnapshot(state: RunUiObservationState, ownerId: string
|
|
63
|
+
export declare function readRunUiSnapshot(state: RunUiObservationState, ownerId: string, options?: {
|
|
64
|
+
includeOutbox?: boolean;
|
|
65
|
+
stateRoot?: string;
|
|
66
|
+
}): RunUiSnapshot;
|
|
62
67
|
export declare function pruneRunUiObservationState(state: RunUiObservationState, snapshot: Pick<RunUiSnapshot, "summary" | "transitions">): void;
|
|
63
|
-
export declare function deliverRunTransitionNotifications(transitions: RunTransition[], sink: RunUiNotificationSink): void;
|
|
68
|
+
export declare function deliverRunTransitionNotifications(transitions: RunTransition[], sink: RunUiNotificationSink, inFlight?: Set<string>): void;
|
|
69
|
+
export declare function reconcileRunTerminalNotifications(input: {
|
|
70
|
+
inFlight?: Set<string>;
|
|
71
|
+
ownerId: string;
|
|
72
|
+
sink: RunUiNotificationSink;
|
|
73
|
+
state: RunUiObservationState;
|
|
74
|
+
stateRoot?: string;
|
|
75
|
+
}): RunUiSnapshot;
|
|
64
76
|
export declare function deliverRunOutboxNotifications(events: RunOutboxEvent[], sink: RunUiNotificationSink): void;
|
|
65
77
|
export interface RunRetirementCandidate {
|
|
66
78
|
activeSubagents: number;
|
|
@@ -76,14 +88,38 @@ export interface RunRetirementExecution {
|
|
|
76
88
|
run: string;
|
|
77
89
|
stateDir: string;
|
|
78
90
|
}
|
|
91
|
+
export type RunStateWatcherDiagnosticCode = "attach_failed" | "error" | "removed" | "rearmed";
|
|
92
|
+
export interface RunStateWatcherDiagnostic {
|
|
93
|
+
code: RunStateWatcherDiagnosticCode;
|
|
94
|
+
id: number;
|
|
95
|
+
message: string;
|
|
96
|
+
path: string;
|
|
97
|
+
scope: "root" | "run";
|
|
98
|
+
ts: string;
|
|
99
|
+
}
|
|
79
100
|
export interface RunStateWatcher {
|
|
80
101
|
close(): void;
|
|
102
|
+
getDiagnostics(): RunStateWatcherDiagnostic[];
|
|
81
103
|
refresh(): void;
|
|
82
104
|
}
|
|
83
105
|
export declare function createRunStateWatcher(input: {
|
|
84
|
-
|
|
106
|
+
exists?: (path: string) => boolean;
|
|
107
|
+
listDirectories?: (path: string) => string[];
|
|
85
108
|
onChange: () => void;
|
|
109
|
+
stateRoot?: string;
|
|
110
|
+
watchPath?: (path: string, onChange: () => void) => FSWatcher;
|
|
86
111
|
}): RunStateWatcher;
|
|
112
|
+
export interface RunTerminalReconciliationLoop {
|
|
113
|
+
close(): void;
|
|
114
|
+
reconcileNow(): void;
|
|
115
|
+
start(): void;
|
|
116
|
+
}
|
|
117
|
+
export declare function createRunTerminalReconciliationLoop(input: {
|
|
118
|
+
intervalMs?: number;
|
|
119
|
+
onError?: (error: unknown) => void;
|
|
120
|
+
reconcile: () => void;
|
|
121
|
+
refreshWatcher: () => void;
|
|
122
|
+
}): RunTerminalReconciliationLoop;
|
|
87
123
|
export interface RunRetirementExecutorOptions {
|
|
88
124
|
attempted?: Set<string>;
|
|
89
125
|
cancelRun: (candidate: RunRetirementCandidate) => Record<string, unknown>;
|
|
@@ -16,11 +16,13 @@ export function createRunUiObservationState() {
|
|
|
16
16
|
outboxEventIds: new Map(),
|
|
17
17
|
};
|
|
18
18
|
}
|
|
19
|
-
export function readRunUiSnapshot(state, ownerId) {
|
|
20
|
-
const summary = summarizeRuns(
|
|
19
|
+
export function readRunUiSnapshot(state, ownerId, options = {}) {
|
|
20
|
+
const summary = summarizeRuns(options.stateRoot, ownerId);
|
|
21
21
|
const status = renderRunStatus(summary, state.frame++);
|
|
22
22
|
return {
|
|
23
|
-
outboxEvents:
|
|
23
|
+
outboxEvents: options.includeOutbox === false
|
|
24
|
+
? []
|
|
25
|
+
: detectRunOutboxEvents(state.eventLines, summary, state.outboxEventIds),
|
|
24
26
|
status,
|
|
25
27
|
summary,
|
|
26
28
|
transitions: detectRunTransitions(state.observed, summary),
|
|
@@ -29,25 +31,43 @@ export function readRunUiSnapshot(state, ownerId) {
|
|
|
29
31
|
export function pruneRunUiObservationState(state, snapshot) {
|
|
30
32
|
pruneRunObservationState(state.observed, state.eventLines, snapshot.summary, snapshot.transitions.map((transition) => transition.stateDir ?? transition.run), state.outboxEventIds);
|
|
31
33
|
}
|
|
32
|
-
export function deliverRunTransitionNotifications(transitions, sink) {
|
|
34
|
+
export function deliverRunTransitionNotifications(transitions, sink, inFlight = new Set()) {
|
|
33
35
|
for (const transition of transitions) {
|
|
34
36
|
if (!shouldNotifyRunTransition(transition))
|
|
35
37
|
continue;
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
if (!shouldSendRunTransitionFollowUp(transition))
|
|
38
|
+
const key = transition.stateDir ?? transition.run;
|
|
39
|
+
if (inFlight.has(key))
|
|
39
40
|
continue;
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
41
|
+
inFlight.add(key);
|
|
42
|
+
try {
|
|
43
|
+
const text = formatRunTransitionMessage(transition);
|
|
44
|
+
sink.notify(text, getRunTransitionNotificationType(transition));
|
|
45
|
+
if (!shouldSendRunTransitionFollowUp(transition))
|
|
46
|
+
continue;
|
|
47
|
+
sink.sendFollowUp({
|
|
48
|
+
customType: "pi-actors-run",
|
|
49
|
+
content: text,
|
|
50
|
+
display: true,
|
|
51
|
+
details: transition,
|
|
52
|
+
});
|
|
53
|
+
if (transition.stateDir) {
|
|
54
|
+
AsyncRuns.markRunTerminalNotificationHandled(transition.stateDir, transition.to);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
inFlight.delete(key);
|
|
48
59
|
}
|
|
49
60
|
}
|
|
50
61
|
}
|
|
62
|
+
export function reconcileRunTerminalNotifications(input) {
|
|
63
|
+
const snapshot = readRunUiSnapshot(input.state, input.ownerId, {
|
|
64
|
+
includeOutbox: false,
|
|
65
|
+
stateRoot: input.stateRoot,
|
|
66
|
+
});
|
|
67
|
+
deliverRunTransitionNotifications(snapshot.transitions, input.sink, input.inFlight);
|
|
68
|
+
pruneRunUiObservationState(input.state, snapshot);
|
|
69
|
+
return snapshot;
|
|
70
|
+
}
|
|
51
71
|
export function deliverRunOutboxNotifications(events, sink) {
|
|
52
72
|
for (const event of events) {
|
|
53
73
|
if (!shouldNotifyRunOutboxEvent(event))
|
|
@@ -64,10 +84,58 @@ export function deliverRunOutboxNotifications(events, sink) {
|
|
|
64
84
|
});
|
|
65
85
|
}
|
|
66
86
|
}
|
|
87
|
+
const RUN_WATCHER_DIAGNOSTIC_LIMIT = 32;
|
|
67
88
|
export function createRunStateWatcher(input) {
|
|
68
89
|
const stateRoot = input.stateRoot ?? Paths.getRunStateRoot();
|
|
90
|
+
const pathExists = input.exists ?? existsSync;
|
|
91
|
+
const listDirectories = input.listDirectories ??
|
|
92
|
+
((path) => readdirSync(path, { withFileTypes: true })
|
|
93
|
+
.filter((entry) => entry.isDirectory())
|
|
94
|
+
.map((entry) => join(path, entry.name)));
|
|
95
|
+
const watchPath = input.watchPath ?? ((path, onChange) => watch(path, onChange));
|
|
96
|
+
let diagnosticId = 0;
|
|
97
|
+
let rootDegraded = false;
|
|
69
98
|
let stateRootWatcher;
|
|
99
|
+
const degradedRunDirs = new Set();
|
|
100
|
+
const diagnostics = [];
|
|
101
|
+
const lastDiagnosticSignatures = new Map();
|
|
70
102
|
const runDirWatchers = new Map();
|
|
103
|
+
const record = (code, scope, path, error) => {
|
|
104
|
+
const detail = error instanceof Error ? `: ${error.message}` : "";
|
|
105
|
+
const signature = `${code}${detail}`;
|
|
106
|
+
const signatureKey = `${scope}:${path}`;
|
|
107
|
+
if (lastDiagnosticSignatures.get(signatureKey) === signature)
|
|
108
|
+
return;
|
|
109
|
+
lastDiagnosticSignatures.set(signatureKey, signature);
|
|
110
|
+
const recovery = code === "rearmed"
|
|
111
|
+
? "; terminal watch acceleration restored"
|
|
112
|
+
: "; terminal reconciliation remains active and watcher rearm will retry";
|
|
113
|
+
diagnostics.push({
|
|
114
|
+
code,
|
|
115
|
+
id: ++diagnosticId,
|
|
116
|
+
message: `Run-state ${scope} watcher ${code.replace("_", " ")} for ${path}${detail}${recovery}`,
|
|
117
|
+
path,
|
|
118
|
+
scope,
|
|
119
|
+
ts: new Date().toISOString(),
|
|
120
|
+
});
|
|
121
|
+
if (diagnostics.length > RUN_WATCHER_DIAGNOSTIC_LIMIT)
|
|
122
|
+
diagnostics.shift();
|
|
123
|
+
};
|
|
124
|
+
const removeRunWatcher = (stateDir, watcher, options = {}) => {
|
|
125
|
+
if (runDirWatchers.get(stateDir) !== watcher)
|
|
126
|
+
return;
|
|
127
|
+
watcher.close();
|
|
128
|
+
runDirWatchers.delete(stateDir);
|
|
129
|
+
if (options.degraded === false) {
|
|
130
|
+
degradedRunDirs.delete(stateDir);
|
|
131
|
+
lastDiagnosticSignatures.delete(`run:${stateDir}`);
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
degradedRunDirs.add(stateDir);
|
|
135
|
+
if (options.error)
|
|
136
|
+
record("error", "run", stateDir, options.error);
|
|
137
|
+
record("removed", "run", stateDir);
|
|
138
|
+
};
|
|
71
139
|
const close = () => {
|
|
72
140
|
stateRootWatcher?.close();
|
|
73
141
|
stateRootWatcher = undefined;
|
|
@@ -76,42 +144,92 @@ export function createRunStateWatcher(input) {
|
|
|
76
144
|
runDirWatchers.clear();
|
|
77
145
|
};
|
|
78
146
|
const watchRunDir = (stateDir) => {
|
|
79
|
-
if (runDirWatchers.has(stateDir) || !
|
|
147
|
+
if (runDirWatchers.has(stateDir) || !pathExists(stateDir))
|
|
80
148
|
return;
|
|
81
149
|
try {
|
|
82
|
-
const watcher =
|
|
83
|
-
watcher.on("error", () => {
|
|
84
|
-
watcher.close();
|
|
85
|
-
runDirWatchers.delete(stateDir);
|
|
86
|
-
});
|
|
150
|
+
const watcher = watchPath(stateDir, input.onChange);
|
|
151
|
+
watcher.on("error", (error) => removeRunWatcher(stateDir, watcher, { error }));
|
|
87
152
|
runDirWatchers.set(stateDir, watcher);
|
|
153
|
+
if (degradedRunDirs.delete(stateDir))
|
|
154
|
+
record("rearmed", "run", stateDir);
|
|
88
155
|
}
|
|
89
|
-
catch {
|
|
90
|
-
|
|
156
|
+
catch (error) {
|
|
157
|
+
degradedRunDirs.add(stateDir);
|
|
158
|
+
record("attach_failed", "run", stateDir, error);
|
|
91
159
|
}
|
|
92
160
|
};
|
|
93
161
|
function refresh() {
|
|
94
|
-
if (!
|
|
162
|
+
if (!pathExists(stateRoot))
|
|
95
163
|
return;
|
|
96
164
|
if (!stateRootWatcher) {
|
|
97
165
|
try {
|
|
98
|
-
|
|
99
|
-
stateRootWatcher
|
|
100
|
-
|
|
166
|
+
const watcher = watchPath(stateRoot, input.onChange);
|
|
167
|
+
stateRootWatcher = watcher;
|
|
168
|
+
watcher.on("error", (error) => {
|
|
169
|
+
if (stateRootWatcher !== watcher)
|
|
170
|
+
return;
|
|
171
|
+
watcher.close();
|
|
101
172
|
stateRootWatcher = undefined;
|
|
173
|
+
rootDegraded = true;
|
|
174
|
+
record("error", "root", stateRoot, error);
|
|
175
|
+
record("removed", "root", stateRoot);
|
|
102
176
|
});
|
|
177
|
+
if (rootDegraded) {
|
|
178
|
+
rootDegraded = false;
|
|
179
|
+
record("rearmed", "root", stateRoot);
|
|
180
|
+
}
|
|
103
181
|
}
|
|
104
|
-
catch {
|
|
105
|
-
|
|
182
|
+
catch (error) {
|
|
183
|
+
rootDegraded = true;
|
|
184
|
+
record("attach_failed", "root", stateRoot, error);
|
|
106
185
|
}
|
|
107
186
|
}
|
|
108
|
-
|
|
109
|
-
|
|
187
|
+
let stateDirs;
|
|
188
|
+
try {
|
|
189
|
+
stateDirs = listDirectories(stateRoot);
|
|
190
|
+
}
|
|
191
|
+
catch (error) {
|
|
192
|
+
record("attach_failed", "root", stateRoot, error);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
const present = new Set(stateDirs);
|
|
196
|
+
for (const [stateDir, watcher] of runDirWatchers) {
|
|
197
|
+
if (present.has(stateDir) && pathExists(stateDir))
|
|
110
198
|
continue;
|
|
111
|
-
|
|
199
|
+
removeRunWatcher(stateDir, watcher, { degraded: false });
|
|
112
200
|
}
|
|
201
|
+
for (const stateDir of stateDirs)
|
|
202
|
+
watchRunDir(stateDir);
|
|
113
203
|
}
|
|
114
|
-
return {
|
|
204
|
+
return {
|
|
205
|
+
close,
|
|
206
|
+
getDiagnostics: () => [...diagnostics],
|
|
207
|
+
refresh,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
export function createRunTerminalReconciliationLoop(input) {
|
|
211
|
+
const intervalMs = input.intervalMs ?? 10_000;
|
|
212
|
+
let interval;
|
|
213
|
+
const reconcileNow = () => {
|
|
214
|
+
try {
|
|
215
|
+
input.refreshWatcher();
|
|
216
|
+
input.reconcile();
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
input.onError?.(error);
|
|
220
|
+
}
|
|
221
|
+
};
|
|
222
|
+
const close = () => {
|
|
223
|
+
if (interval)
|
|
224
|
+
clearInterval(interval);
|
|
225
|
+
interval = undefined;
|
|
226
|
+
};
|
|
227
|
+
const start = () => {
|
|
228
|
+
close();
|
|
229
|
+
interval = setInterval(reconcileNow, intervalMs);
|
|
230
|
+
interval.unref?.();
|
|
231
|
+
};
|
|
232
|
+
return { close, reconcileNow, start };
|
|
115
233
|
}
|
|
116
234
|
const TERMINAL = new Set([
|
|
117
235
|
"done",
|
|
@@ -193,6 +311,9 @@ function observeRun(stateDir) {
|
|
|
193
311
|
!Array.isArray(status.model_policy)
|
|
194
312
|
? { modelPolicy: status.model_policy }
|
|
195
313
|
: {}),
|
|
314
|
+
...(status.notification_policy === "silent"
|
|
315
|
+
? { notificationPolicy: "silent" }
|
|
316
|
+
: {}),
|
|
196
317
|
...(typeof status.recipe_file === "string"
|
|
197
318
|
? { recipeFile: status.recipe_file }
|
|
198
319
|
: {}),
|
|
@@ -497,7 +618,9 @@ export function detectRunTransitions(previous, summary) {
|
|
|
497
618
|
for (const run of summary.runs) {
|
|
498
619
|
const key = runObservationKey(run);
|
|
499
620
|
const old = previous.get(key);
|
|
500
|
-
if (
|
|
621
|
+
if (run.notificationPolicy !== "silent" &&
|
|
622
|
+
!run.terminalHandled &&
|
|
623
|
+
TERMINAL.has(run.status)) {
|
|
501
624
|
transitions.push({
|
|
502
625
|
from: old ?? "running",
|
|
503
626
|
run: run.run,
|
|
@@ -589,6 +712,11 @@ export function detectRunOutboxEvents(previousLineCounts, summary, seenEventIds
|
|
|
589
712
|
const key = run.stateDir ?? run.run;
|
|
590
713
|
const records = readOutboxRecords(run);
|
|
591
714
|
const previousCount = previousLineCounts.get(key) ?? 0;
|
|
715
|
+
if (run.notificationPolicy === "silent") {
|
|
716
|
+
previousLineCounts.set(key, records.length);
|
|
717
|
+
seenEventIds.set(key, seenEventIds.get(key) ?? new Set());
|
|
718
|
+
continue;
|
|
719
|
+
}
|
|
592
720
|
const start = Math.min(previousCount, records.length);
|
|
593
721
|
const seen = seenEventIds.get(key) ?? new Set();
|
|
594
722
|
for (let index = start; index < records.length; index += 1) {
|
package/dist/lib/paths.d.ts
CHANGED
|
@@ -4,6 +4,7 @@
|
|
|
4
4
|
* Owns agent directory, tools config, recipe root, and actor run state root resolution
|
|
5
5
|
*/
|
|
6
6
|
export declare function getAgentDir(env?: Record<string, string | undefined>): string;
|
|
7
|
+
export declare function isAutomaticRecipeReviewEnabled(env?: Record<string, string | undefined>): boolean;
|
|
7
8
|
export interface ExtensionRuntimePaths {
|
|
8
9
|
configPath: string;
|
|
9
10
|
runStateRoot: string;
|
|
@@ -12,6 +13,12 @@ export interface ExtensionRuntimePaths {
|
|
|
12
13
|
export declare function getConfigPath(agentDir?: string): string;
|
|
13
14
|
export declare function getExtensionTmpDir(agentDir?: string, extensionName?: string): string;
|
|
14
15
|
export declare function getRunStateRoot(agentDir?: string): string;
|
|
16
|
+
export declare function getDraftSleepRoot(agentDir?: string): string;
|
|
17
|
+
export declare function getDraftSleepStatePath(agentDir?: string): string;
|
|
18
|
+
export declare function getDraftSleepBatchDir(batchId: string, agentDir?: string): string;
|
|
19
|
+
export declare function getToolReviewRoot(agentDir?: string): string;
|
|
20
|
+
export declare function getToolReviewStatePath(agentDir?: string): string;
|
|
21
|
+
export declare function getToolReviewBatchDir(reviewId: string, agentDir?: string): string;
|
|
15
22
|
export declare function getExtensionRuntimePaths(agentDir?: string): ExtensionRuntimePaths;
|
|
16
23
|
export declare const EXTENSION_RUNTIME_PATHS: ExtensionRuntimePaths;
|
|
17
24
|
export declare function getExtensionSkillsDir(extensionUrl: string): string;
|
package/dist/lib/paths.js
CHANGED
|
@@ -12,6 +12,10 @@ export function getAgentDir(env = process.env) {
|
|
|
12
12
|
? resolve(env.PI_CODING_AGENT_DIR)
|
|
13
13
|
: join(homedir(), ".pi", "agent");
|
|
14
14
|
}
|
|
15
|
+
export function isAutomaticRecipeReviewEnabled(env = process.env) {
|
|
16
|
+
const value = env.PI_ACTORS_AUTOMATIC_REVIEW?.trim().toLowerCase();
|
|
17
|
+
return value === undefined || !["0", "false", "off"].includes(value);
|
|
18
|
+
}
|
|
15
19
|
export function getConfigPath(agentDir = getAgentDir()) {
|
|
16
20
|
return join(agentDir, "tool-registry.json");
|
|
17
21
|
}
|
|
@@ -21,6 +25,30 @@ export function getExtensionTmpDir(agentDir = getAgentDir(), extensionName = "pi
|
|
|
21
25
|
export function getRunStateRoot(agentDir = getAgentDir()) {
|
|
22
26
|
return join(getExtensionTmpDir(agentDir), "runs");
|
|
23
27
|
}
|
|
28
|
+
export function getDraftSleepRoot(agentDir = getAgentDir()) {
|
|
29
|
+
return join(getExtensionTmpDir(agentDir), "draft-sleep");
|
|
30
|
+
}
|
|
31
|
+
export function getDraftSleepStatePath(agentDir = getAgentDir()) {
|
|
32
|
+
return join(getDraftSleepRoot(agentDir), "state.json");
|
|
33
|
+
}
|
|
34
|
+
export function getDraftSleepBatchDir(batchId, agentDir = getAgentDir()) {
|
|
35
|
+
if (!/^[a-f0-9-]{36}$/u.test(batchId)) {
|
|
36
|
+
throw new Error("Invalid draft sleep batch id.");
|
|
37
|
+
}
|
|
38
|
+
return join(getDraftSleepRoot(agentDir), "batches", batchId);
|
|
39
|
+
}
|
|
40
|
+
export function getToolReviewRoot(agentDir = getAgentDir()) {
|
|
41
|
+
return join(getExtensionTmpDir(agentDir), "tool-review");
|
|
42
|
+
}
|
|
43
|
+
export function getToolReviewStatePath(agentDir = getAgentDir()) {
|
|
44
|
+
return join(getToolReviewRoot(agentDir), "state.json");
|
|
45
|
+
}
|
|
46
|
+
export function getToolReviewBatchDir(reviewId, agentDir = getAgentDir()) {
|
|
47
|
+
if (!/^[a-f0-9-]{36}$/u.test(reviewId)) {
|
|
48
|
+
throw new Error("Invalid tool review id.");
|
|
49
|
+
}
|
|
50
|
+
return join(getToolReviewRoot(agentDir), "batches", reviewId);
|
|
51
|
+
}
|
|
24
52
|
export function getExtensionRuntimePaths(agentDir = getAgentDir()) {
|
|
25
53
|
return {
|
|
26
54
|
configPath: getConfigPath(agentDir),
|
|
@@ -528,30 +528,38 @@ function templatePreview(value) {
|
|
|
528
528
|
}
|
|
529
529
|
export function listDraftRecipes(root) {
|
|
530
530
|
return listRecipeFiles(root).map((path) => {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
531
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
532
|
+
const id = RecipesReferences.getRecipeIdFromPath(path);
|
|
533
|
+
const bytes = readFileSync(path);
|
|
534
|
+
const fingerprint = createHash("sha256").update(bytes).digest("hex");
|
|
535
|
+
const stat = statSync(path);
|
|
536
|
+
const config = RecipesReferences.readRawRecipeConfig(path);
|
|
537
|
+
const resolved = RecipesReferences.readResolvedRecipeConfig(path);
|
|
538
|
+
const diagnostics = getRecipeConfigDiagnostics(path, resolved);
|
|
539
|
+
const after = readFileSync(path);
|
|
540
|
+
if (createHash("sha256").update(after).digest("hex") !== fingerprint) {
|
|
541
|
+
continue;
|
|
542
|
+
}
|
|
543
|
+
const sourceRun = String(config?.description ?? "").match(/spawn run ([^\s]+)/)?.[1];
|
|
544
|
+
const preview = templatePreview(config?.template);
|
|
545
|
+
const riskLabels = getRecipeRiskLabels(resolved);
|
|
546
|
+
return {
|
|
547
|
+
id,
|
|
548
|
+
path,
|
|
549
|
+
sha256: fingerprint,
|
|
550
|
+
size: bytes.byteLength,
|
|
551
|
+
created_at: stat.birthtime.toISOString(),
|
|
552
|
+
modified_at: stat.mtime.toISOString(),
|
|
553
|
+
valid: Boolean(resolved),
|
|
554
|
+
diagnostics,
|
|
555
|
+
...(riskLabels.length ? { risk_labels: riskLabels } : {}),
|
|
556
|
+
...(config?.description ? { description: config.description } : {}),
|
|
557
|
+
...(sourceRun ? { source_run: sourceRun } : {}),
|
|
558
|
+
...(config?.async !== undefined ? { async: config.async } : {}),
|
|
559
|
+
...(preview ? { template_preview: preview } : {}),
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
throw new Error(`Draft changed repeatedly during inventory: ${path}`);
|
|
555
563
|
});
|
|
556
564
|
}
|
|
557
565
|
export function summarizeDiscovery(result) {
|
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Recipe usage metadata
|
|
3
|
-
* Zones: recipe telemetry,
|
|
4
|
-
* Owns
|
|
2
|
+
* Recipe usage and name-based lineage metadata.
|
|
3
|
+
* Zones: recipe lineage, launch telemetry, revision history, migration
|
|
4
|
+
* Owns priority-compatible recipe names and lifetime/revision counters for user recipes.
|
|
5
5
|
*/
|
|
6
6
|
export type RecipeLaunchKind = "direct" | "spawn" | "tool";
|
|
7
|
-
export declare function
|
|
8
|
-
export declare function
|
|
9
|
-
export declare
|
|
7
|
+
export declare function getRecipeUsageIndexPath(recipeRoot: string): string;
|
|
8
|
+
export declare function getRecipeUsageLedgerPath(lineageName: string, recipeRoot: string): string;
|
|
9
|
+
export declare const RECIPE_REVISION_SNAPSHOT_LIMIT = 32;
|
|
10
|
+
export declare function getRecipeRevisionSnapshotPath(lineageName: string, revision: number, recipeRoot: string): string;
|
|
11
|
+
export declare function readRecipeUsage(path: string, recipeRoot?: string): Record<string, unknown> | undefined;
|
|
12
|
+
export declare function ensureRecipeLineage(path: string, now?: Date, recipeRoot?: string): boolean;
|
|
13
|
+
export declare function isCurrentRecipeRevisionReviewed(path: string, recipeRoot?: string): boolean;
|
|
14
|
+
export interface RecipeLaunchRecordOptions {
|
|
15
|
+
onMutationLockContention?(): void;
|
|
16
|
+
}
|
|
17
|
+
export declare function recordRecipeLaunch(path: string, now?: Date, kind?: RecipeLaunchKind, recipeRoot?: string, options?: RecipeLaunchRecordOptions): boolean;
|
|
18
|
+
export declare function recordRecipeReview(path: string, reviewEpoch: string, now?: Date, recipeRoot?: string): boolean;
|
|
19
|
+
export declare function recordRecipeRollback(path: string, rollbackRevision: number, now?: Date, recipeRoot?: string): boolean;
|
|
20
|
+
export declare function retireRecipeUsage(path: string, recipeRoot?: string): boolean;
|
|
21
|
+
export declare function moveRecipeUsage(fromPath: string, toPath: string, recipeRoot?: string): boolean;
|