@llblab/pi-actors 0.41.1 → 0.42.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 +11 -6
- package/BACKLOG.md +1 -65
- package/CHANGELOG.md +25 -0
- package/README.md +24 -4
- package/dist/index.js +25 -121
- package/dist/lib/async-runs.d.ts +37 -5
- package/dist/lib/async-runs.js +181 -51
- package/dist/lib/automatic-review-runtime.d.ts +18 -0
- package/dist/lib/automatic-review-runtime.js +96 -0
- package/dist/lib/command-templates.js +1 -1
- 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 +23 -3
- package/dist/lib/inspector-overlay.js +327 -70
- package/dist/lib/inspector.d.ts +9 -0
- package/dist/lib/inspector.js +49 -1
- package/dist/lib/observability.d.ts +13 -0
- package/dist/lib/observability.js +160 -6
- 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 +10 -2
- package/dist/lib/runs-control.js +37 -7
- 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 +18 -4
- 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 +5 -4
- 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 +7 -48
- package/dist/lib/runs-status.d.ts +5 -3
- package/dist/lib/runs-status.js +7 -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 +28 -4
- package/dist/lib/tools-local.js +21 -4
- package/dist/lib/tools-message.d.ts +1 -0
- package/dist/lib/tools-message.js +29 -17
- package/dist/lib/tools-spawn.js +14 -2
- 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/async-runner.mjs +9 -9
- package/dist/scripts/build-dist.mjs +6 -1
- package/dist/scripts/conformance.mjs +6 -1
- package/dist/scripts/recipe-utils.mjs +3 -3
- package/dist/scripts/release-gates.mjs +165 -0
- package/dist/skills/actors/SKILL.md +9 -8
- package/dist/skills/swarm/SKILL.md +1 -1
- package/docs/actor-inspector.md +20 -11
- package/docs/async-runs.md +16 -2
- package/docs/recipe-library.md +7 -3
- package/docs/template-recipes.md +4 -11
- package/docs/tool-registry.md +13 -4
- package/index.ts +27 -142
- package/lib/async-runs.ts +283 -67
- package/lib/automatic-review-runtime.ts +135 -0
- package/lib/command-templates.ts +1 -1
- 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 +377 -91
- package/lib/inspector.ts +73 -1
- package/lib/observability.ts +194 -5
- 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 +69 -6
- 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 +23 -4
- package/lib/runs-parent-teardown.ts +257 -0
- package/lib/runs-process.ts +5 -4
- package/lib/runs-retention.ts +1 -1
- package/lib/runs-start.ts +13 -68
- package/lib/runs-status.ts +17 -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 +35 -4
- package/lib/tools-local.ts +29 -4
- package/lib/tools-message.ts +45 -30
- package/lib/tools-spawn.ts +22 -2
- package/lib/tools.ts +5 -0
- package/package.json +5 -3
- package/recipes/draft-review.json +24 -0
- package/recipes/tool-review.json +24 -0
- package/scripts/async-runner.mjs +9 -9
- package/scripts/build-dist.mjs +6 -1
- package/scripts/conformance.mjs +6 -1
- package/scripts/recipe-utils.mjs +3 -3
- package/scripts/release-gates.mjs +165 -0
- package/skills/actors/SKILL.md +9 -8
- package/skills/swarm/SKILL.md +1 -1
package/lib/inspector.ts
CHANGED
|
@@ -74,6 +74,7 @@ export interface ActorInspectorPreviewReadOptions {
|
|
|
74
74
|
|
|
75
75
|
export interface ActorInspectorRunItem {
|
|
76
76
|
run: string;
|
|
77
|
+
runInstanceId?: string;
|
|
77
78
|
status: string;
|
|
78
79
|
updatedAt?: string;
|
|
79
80
|
}
|
|
@@ -89,6 +90,10 @@ export function readActorInspectorRuns(
|
|
|
89
90
|
.flatMap((entry) => {
|
|
90
91
|
const stateDir = path.join(stateRoot, entry.name);
|
|
91
92
|
if (!matchesOwner(stateDir, ownerId)) return [];
|
|
93
|
+
const runMeta = readJsonFileResilient<Record<string, unknown>>(
|
|
94
|
+
path.join(stateDir, "run.json"),
|
|
95
|
+
{},
|
|
96
|
+
).value;
|
|
92
97
|
const progress = readJsonFileResilient<Record<string, unknown>>(
|
|
93
98
|
path.join(stateDir, "progress.json"),
|
|
94
99
|
{},
|
|
@@ -99,6 +104,9 @@ export function readActorInspectorRuns(
|
|
|
99
104
|
).value;
|
|
100
105
|
return [{
|
|
101
106
|
run: entry.name,
|
|
107
|
+
...(typeof runMeta.run_instance_id === "string"
|
|
108
|
+
? { runInstanceId: runMeta.run_instance_id }
|
|
109
|
+
: {}),
|
|
102
110
|
status: String(progress.phase ?? (Object.keys(result).length ? "terminal" : "unknown")),
|
|
103
111
|
...(typeof progress.updatedAt === "string"
|
|
104
112
|
? { updatedAt: progress.updatedAt }
|
|
@@ -119,6 +127,70 @@ export function readActorInspectorRuns(
|
|
|
119
127
|
}
|
|
120
128
|
}
|
|
121
129
|
|
|
130
|
+
export interface ActorInspectorRecipeView {
|
|
131
|
+
composition: Array<Record<string, unknown>>;
|
|
132
|
+
definition?: Record<string, unknown>;
|
|
133
|
+
diagnostics: string[];
|
|
134
|
+
identity: Record<string, unknown>;
|
|
135
|
+
launch: Record<string, unknown>;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function readActorInspectorRecipe(
|
|
139
|
+
stateDir: string,
|
|
140
|
+
): ActorInspectorRecipeView {
|
|
141
|
+
const result = readJsonFileResilient<Record<string, unknown>>(
|
|
142
|
+
path.join(stateDir, "run.json"),
|
|
143
|
+
{},
|
|
144
|
+
);
|
|
145
|
+
const meta = result.value;
|
|
146
|
+
const contexts = Array.isArray(meta.recipe_context_records)
|
|
147
|
+
? meta.recipe_context_records
|
|
148
|
+
.filter((entry): entry is Record<string, unknown> =>
|
|
149
|
+
Boolean(entry && typeof entry === "object" && !Array.isArray(entry)),
|
|
150
|
+
)
|
|
151
|
+
: [];
|
|
152
|
+
const primary = contexts.find((entry) => entry.depth === 0) ?? contexts[0];
|
|
153
|
+
const definition = primary?.recipe && typeof primary.recipe === "object" &&
|
|
154
|
+
!Array.isArray(primary.recipe)
|
|
155
|
+
? primary.recipe as Record<string, unknown>
|
|
156
|
+
: undefined;
|
|
157
|
+
const redacted = (value: unknown): unknown =>
|
|
158
|
+
SessionEvidence.redactSessionEvidenceValue(value);
|
|
159
|
+
const redactedRecord = (value: Record<string, unknown>): Record<string, unknown> =>
|
|
160
|
+
redacted(Object.fromEntries(
|
|
161
|
+
Object.entries(value).filter(([, entry]) => entry !== undefined),
|
|
162
|
+
)) as Record<string, unknown>;
|
|
163
|
+
return {
|
|
164
|
+
composition: contexts.map((entry) => redactedRecord({
|
|
165
|
+
...(typeof entry.alias === "string" ? { alias: entry.alias } : {}),
|
|
166
|
+
depth: entry.depth,
|
|
167
|
+
file: entry.file,
|
|
168
|
+
import_path: entry.import_path,
|
|
169
|
+
name: entry.name,
|
|
170
|
+
})),
|
|
171
|
+
...(definition
|
|
172
|
+
? { definition: redactedRecord(definition) }
|
|
173
|
+
: {}),
|
|
174
|
+
diagnostics: result.diagnostics.map((diagnostic) => diagnostic.message),
|
|
175
|
+
identity: redactedRecord({
|
|
176
|
+
run: meta.run,
|
|
177
|
+
recipe: meta.recipe,
|
|
178
|
+
recipe_file: meta.recipe_file,
|
|
179
|
+
launch_source: meta.launch_source,
|
|
180
|
+
}),
|
|
181
|
+
launch: redactedRecord({
|
|
182
|
+
cwd: meta.cwd,
|
|
183
|
+
template: meta.template,
|
|
184
|
+
values: meta.values,
|
|
185
|
+
model_policy: meta.model_policy,
|
|
186
|
+
mailbox: meta.mailbox,
|
|
187
|
+
artifacts: meta.artifacts,
|
|
188
|
+
notification_policy: meta.notification_policy,
|
|
189
|
+
retire_when: meta.retire_when,
|
|
190
|
+
}),
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
|
|
122
194
|
export interface ActorInspectorTurnItem extends SessionEvidence.SessionEvidenceTurn {
|
|
123
195
|
commandId: string;
|
|
124
196
|
diagnostics: string[];
|
|
@@ -176,7 +248,7 @@ export function readActorInspectorTurns(stateDir: string): ActorInspectorTurnIte
|
|
|
176
248
|
withFileTypes: true,
|
|
177
249
|
})
|
|
178
250
|
.filter((file) => file.isFile() && file.name.endsWith(".jsonl"))
|
|
179
|
-
.map((file) => path.join("sessions", entry.name, file.name))
|
|
251
|
+
.map((file) => path.posix.join("sessions", entry.name, file.name))
|
|
180
252
|
.filter((file) => !recordedFiles.has(file));
|
|
181
253
|
return files.length > 0
|
|
182
254
|
? [{ id: entry.name, session_files: files, stage: "subagent" }]
|
package/lib/observability.ts
CHANGED
|
@@ -5,9 +5,13 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
import {
|
|
8
|
+
closeSync,
|
|
8
9
|
existsSync,
|
|
10
|
+
fstatSync,
|
|
11
|
+
openSync,
|
|
9
12
|
readdirSync,
|
|
10
13
|
readFileSync,
|
|
14
|
+
readSync,
|
|
11
15
|
watch,
|
|
12
16
|
type FSWatcher,
|
|
13
17
|
} from "node:fs";
|
|
@@ -41,12 +45,15 @@ export interface RunObservation {
|
|
|
41
45
|
failures?: number;
|
|
42
46
|
ownerId?: string;
|
|
43
47
|
artifacts?: Record<string, string>;
|
|
48
|
+
launchCorrelation?: Record<string, string>;
|
|
44
49
|
launchSource?: AsyncRuns.AsyncRunLaunchSource;
|
|
45
50
|
modelPolicy?: Record<string, unknown>;
|
|
51
|
+
notificationPolicy?: "normal" | "silent";
|
|
46
52
|
recipeFile?: string;
|
|
47
53
|
terminalHandled?: boolean;
|
|
48
54
|
retireWhen?: string;
|
|
49
55
|
run: string;
|
|
56
|
+
semanticResult?: RunTerminalSemanticResult;
|
|
50
57
|
tool?: string;
|
|
51
58
|
stateDir?: string;
|
|
52
59
|
status: RunObservedStatus;
|
|
@@ -161,6 +168,19 @@ export function deliverRunTransitionNotifications(
|
|
|
161
168
|
transition.to,
|
|
162
169
|
);
|
|
163
170
|
}
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (transition.stateDir) {
|
|
173
|
+
AsyncRuns.recordRunTerminalDeliveryFailure(
|
|
174
|
+
transition.stateDir,
|
|
175
|
+
transition.to,
|
|
176
|
+
error,
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
180
|
+
sink.notify(
|
|
181
|
+
`Actor terminal delivery failed for run:${transition.run}: ${message.replaceAll(/\s+/g, " ").slice(0, 240)}`,
|
|
182
|
+
"error",
|
|
183
|
+
);
|
|
164
184
|
} finally {
|
|
165
185
|
inFlight.delete(key);
|
|
166
186
|
}
|
|
@@ -419,12 +439,23 @@ export interface RunTransition {
|
|
|
419
439
|
run: string;
|
|
420
440
|
stateDir?: string;
|
|
421
441
|
artifacts?: Record<string, string>;
|
|
442
|
+
launchCorrelation?: Record<string, string>;
|
|
422
443
|
launchSource?: AsyncRuns.AsyncRunLaunchSource;
|
|
423
444
|
modelPolicy?: Record<string, unknown>;
|
|
424
445
|
recipeFile?: string;
|
|
425
446
|
terminalHandled?: boolean;
|
|
426
447
|
to: RunObservedStatus;
|
|
427
448
|
tool?: string;
|
|
449
|
+
semanticResult?: RunTerminalSemanticResult;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
export interface RunTerminalSemanticResult {
|
|
453
|
+
body?: string;
|
|
454
|
+
correlationId?: string;
|
|
455
|
+
metadata: Record<string, unknown>;
|
|
456
|
+
summary: string;
|
|
457
|
+
synthesized: boolean;
|
|
458
|
+
type: string;
|
|
428
459
|
}
|
|
429
460
|
|
|
430
461
|
export interface RunOutboxEvent {
|
|
@@ -503,15 +534,145 @@ function scanRunStateDirs(
|
|
|
503
534
|
return result;
|
|
504
535
|
}
|
|
505
536
|
|
|
537
|
+
const TERMINAL_RESULT_BYTES = 8 * 1024;
|
|
538
|
+
const TERMINAL_RESULT_CHARS = 4_000;
|
|
539
|
+
|
|
540
|
+
function readBoundedStart(path: string): string {
|
|
541
|
+
if (!existsSync(path)) return "";
|
|
542
|
+
const fd = openSync(path, "r");
|
|
543
|
+
try {
|
|
544
|
+
const size = Math.min(fstatSync(fd).size, TERMINAL_RESULT_BYTES);
|
|
545
|
+
const buffer = Buffer.alloc(size);
|
|
546
|
+
const bytes = readSync(fd, buffer, 0, size, 0);
|
|
547
|
+
const text = buffer.subarray(0, bytes).toString("utf8").trim();
|
|
548
|
+
return text.length > TERMINAL_RESULT_CHARS
|
|
549
|
+
? `${text.slice(0, TERMINAL_RESULT_CHARS - 1)}…`
|
|
550
|
+
: text;
|
|
551
|
+
} finally {
|
|
552
|
+
closeSync(fd);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function semanticBodyFromStdout(stateDir: string): string | undefined {
|
|
557
|
+
const text = readBoundedStart(join(stateDir, "stdout.log"));
|
|
558
|
+
if (!text) return undefined;
|
|
559
|
+
const withoutMarker = text.replace(/^ACTOR_REVIEW_RESULT\s*(?:\r?\n)?/, "").trim();
|
|
560
|
+
return withoutMarker || undefined;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function terminalSemanticResult(
|
|
564
|
+
status: Record<string, unknown>,
|
|
565
|
+
stateDir: string,
|
|
566
|
+
observedStatus: RunObservedStatus,
|
|
567
|
+
): RunTerminalSemanticResult {
|
|
568
|
+
const correlation = status.launch_correlation &&
|
|
569
|
+
typeof status.launch_correlation === "object" &&
|
|
570
|
+
!Array.isArray(status.launch_correlation)
|
|
571
|
+
? status.launch_correlation as Record<string, unknown>
|
|
572
|
+
: {};
|
|
573
|
+
const correlationId =
|
|
574
|
+
typeof correlation.correlation_id === "string"
|
|
575
|
+
? correlation.correlation_id
|
|
576
|
+
: typeof correlation.tool_call_id === "string"
|
|
577
|
+
? correlation.tool_call_id
|
|
578
|
+
: undefined;
|
|
579
|
+
const mailbox = status.mailbox &&
|
|
580
|
+
typeof status.mailbox === "object" &&
|
|
581
|
+
!Array.isArray(status.mailbox)
|
|
582
|
+
? status.mailbox as Record<string, unknown>
|
|
583
|
+
: {};
|
|
584
|
+
const emits = Array.isArray(mailbox.emits)
|
|
585
|
+
? mailbox.emits.filter((item): item is string => typeof item === "string")
|
|
586
|
+
: [];
|
|
587
|
+
const outbox = readJsonlFileResilient<Record<string, unknown>>(
|
|
588
|
+
join(stateDir, "outbox.jsonl"),
|
|
589
|
+
).records;
|
|
590
|
+
const explicit = outbox.findLast((record) => {
|
|
591
|
+
const type = String(record.type ?? record.event ?? "");
|
|
592
|
+
return emits.includes(type) && !["command.done", "run.done", "run.failed"].includes(type);
|
|
593
|
+
});
|
|
594
|
+
if (explicit) {
|
|
595
|
+
const type = String(explicit.type ?? explicit.event);
|
|
596
|
+
return {
|
|
597
|
+
...(explicit.body === undefined
|
|
598
|
+
? {} : { body: formatSemanticBody(explicit.body) }),
|
|
599
|
+
...(typeof explicit.correlation_id === "string"
|
|
600
|
+
? { correlationId: explicit.correlation_id } : correlationId ? { correlationId } : {}),
|
|
601
|
+
metadata: {
|
|
602
|
+
...(explicit.metadata &&
|
|
603
|
+
typeof explicit.metadata === "object" &&
|
|
604
|
+
!Array.isArray(explicit.metadata)
|
|
605
|
+
? explicit.metadata as Record<string, unknown> : {}),
|
|
606
|
+
...(status.transport_context &&
|
|
607
|
+
typeof status.transport_context === "object" &&
|
|
608
|
+
!Array.isArray(status.transport_context)
|
|
609
|
+
? {
|
|
610
|
+
transport_context: status.transport_context as Record<string, unknown>,
|
|
611
|
+
} : {}),
|
|
612
|
+
run: String(status.run ?? ""),
|
|
613
|
+
status: observedStatus,
|
|
614
|
+
},
|
|
615
|
+
summary: String(explicit.summary ?? type),
|
|
616
|
+
synthesized: false,
|
|
617
|
+
type,
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
const reviewCompleted = observedStatus === "done" && emits.includes("review.completed");
|
|
621
|
+
const result = status.result &&
|
|
622
|
+
typeof status.result === "object" &&
|
|
623
|
+
!Array.isArray(status.result)
|
|
624
|
+
? status.result as Record<string, unknown>
|
|
625
|
+
: {};
|
|
626
|
+
const type = reviewCompleted
|
|
627
|
+
? "review.completed"
|
|
628
|
+
: observedStatus === "done" ? "run.done" : "run.failed";
|
|
629
|
+
const stdoutBody = observedStatus === "done"
|
|
630
|
+
? semanticBodyFromStdout(stateDir) : undefined;
|
|
631
|
+
return {
|
|
632
|
+
...(stdoutBody
|
|
633
|
+
? { body: stdoutBody }
|
|
634
|
+
: typeof result.error === "string" ? { body: result.error } : {}),
|
|
635
|
+
...(correlationId ? { correlationId } : {}),
|
|
636
|
+
metadata: {
|
|
637
|
+
...(status.transport_context &&
|
|
638
|
+
typeof status.transport_context === "object" &&
|
|
639
|
+
!Array.isArray(status.transport_context)
|
|
640
|
+
? {
|
|
641
|
+
transport_context: status.transport_context as Record<string, unknown>,
|
|
642
|
+
} : {}),
|
|
643
|
+
run: String(status.run ?? ""), status: observedStatus,
|
|
644
|
+
},
|
|
645
|
+
summary: reviewCompleted ? "Review completed." :
|
|
646
|
+
observedStatus === "done" ? "Run completed." : `Run ${observedStatus}.`,
|
|
647
|
+
synthesized: true,
|
|
648
|
+
type,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
function formatSemanticBody(body: unknown): string {
|
|
653
|
+
const rendered = typeof body === "string" ? body : JSON.stringify(body);
|
|
654
|
+
const text = typeof rendered === "string" ? rendered : String(body);
|
|
655
|
+
const compact = text.trim();
|
|
656
|
+
return compact.length > TERMINAL_RESULT_CHARS
|
|
657
|
+
? `${compact.slice(0, TERMINAL_RESULT_CHARS - 1)}…`
|
|
658
|
+
: compact;
|
|
659
|
+
}
|
|
660
|
+
|
|
506
661
|
function observeRun(stateDir: string): RunObservation | undefined {
|
|
507
662
|
try {
|
|
508
663
|
const status = AsyncRuns.getRunStatus(stateDir);
|
|
509
664
|
const progress = getProgress(status);
|
|
510
665
|
const run = typeof status.run === "string" ? status.run : undefined;
|
|
511
666
|
if (!run) return undefined;
|
|
667
|
+
const observedStatus = status.status as RunObservedStatus;
|
|
512
668
|
return {
|
|
513
669
|
activeSubagents: toNumber(progress.activeSubagents),
|
|
514
670
|
completed: toNumber(progress.completed),
|
|
671
|
+
...(status.launch_correlation &&
|
|
672
|
+
typeof status.launch_correlation === "object" &&
|
|
673
|
+
!Array.isArray(status.launch_correlation)
|
|
674
|
+
? { launchCorrelation: status.launch_correlation as Record<string, string> }
|
|
675
|
+
: {}),
|
|
515
676
|
failures: Array.isArray(progress.failures)
|
|
516
677
|
? progress.failures.length
|
|
517
678
|
: undefined,
|
|
@@ -531,6 +692,9 @@ function observeRun(stateDir: string): RunObservation | undefined {
|
|
|
531
692
|
!Array.isArray(status.model_policy)
|
|
532
693
|
? { modelPolicy: status.model_policy as Record<string, unknown> }
|
|
533
694
|
: {}),
|
|
695
|
+
...(status.notification_policy === "silent"
|
|
696
|
+
? { notificationPolicy: "silent" as const }
|
|
697
|
+
: {}),
|
|
534
698
|
...(typeof status.recipe_file === "string"
|
|
535
699
|
? { recipeFile: status.recipe_file }
|
|
536
700
|
: {}),
|
|
@@ -538,9 +702,12 @@ function observeRun(stateDir: string): RunObservation | undefined {
|
|
|
538
702
|
...(typeof status.retire_when === "string"
|
|
539
703
|
? { retireWhen: status.retire_when }
|
|
540
704
|
: {}),
|
|
705
|
+
...(TERMINAL.has(observedStatus)
|
|
706
|
+
? { semanticResult: terminalSemanticResult(status, stateDir, observedStatus) }
|
|
707
|
+
: {}),
|
|
541
708
|
run,
|
|
542
709
|
stateDir,
|
|
543
|
-
status:
|
|
710
|
+
status: observedStatus,
|
|
544
711
|
...(typeof status.tool === "string" ? { tool: status.tool } : {}),
|
|
545
712
|
updatedAt: getUpdatedAt(status),
|
|
546
713
|
};
|
|
@@ -919,15 +1086,21 @@ export function detectRunTransitions(
|
|
|
919
1086
|
for (const run of summary.runs) {
|
|
920
1087
|
const key = runObservationKey(run);
|
|
921
1088
|
const old = previous.get(key);
|
|
922
|
-
if (
|
|
1089
|
+
if (
|
|
1090
|
+
run.notificationPolicy !== "silent" &&
|
|
1091
|
+
!run.terminalHandled &&
|
|
1092
|
+
TERMINAL.has(run.status)
|
|
1093
|
+
) {
|
|
923
1094
|
transitions.push({
|
|
924
1095
|
from: old ?? "running",
|
|
925
1096
|
run: run.run,
|
|
926
1097
|
...(run.stateDir ? { stateDir: run.stateDir } : {}),
|
|
927
1098
|
...(run.artifacts ? { artifacts: run.artifacts } : {}),
|
|
1099
|
+
...(run.launchCorrelation ? { launchCorrelation: run.launchCorrelation } : {}),
|
|
928
1100
|
...(run.launchSource ? { launchSource: run.launchSource } : {}),
|
|
929
1101
|
...(run.modelPolicy ? { modelPolicy: run.modelPolicy } : {}),
|
|
930
1102
|
...(run.recipeFile ? { recipeFile: run.recipeFile } : {}),
|
|
1103
|
+
...(run.semanticResult ? { semanticResult: run.semanticResult } : {}),
|
|
931
1104
|
...(run.terminalHandled ? { terminalHandled: true } : {}),
|
|
932
1105
|
to: run.status,
|
|
933
1106
|
...(run.tool ? { tool: run.tool } : {}),
|
|
@@ -1037,6 +1210,11 @@ export function detectRunOutboxEvents(
|
|
|
1037
1210
|
const key = run.stateDir ?? run.run;
|
|
1038
1211
|
const records = readOutboxRecords(run);
|
|
1039
1212
|
const previousCount = previousLineCounts.get(key) ?? 0;
|
|
1213
|
+
if (run.notificationPolicy === "silent") {
|
|
1214
|
+
previousLineCounts.set(key, records.length);
|
|
1215
|
+
seenEventIds.set(key, seenEventIds.get(key) ?? new Set<string>());
|
|
1216
|
+
continue;
|
|
1217
|
+
}
|
|
1040
1218
|
const start = Math.min(previousCount, records.length);
|
|
1041
1219
|
const seen = seenEventIds.get(key) ?? new Set<string>();
|
|
1042
1220
|
for (let index = start; index < records.length; index += 1) {
|
|
@@ -1182,7 +1360,8 @@ function isUserRecipeFile(file: string | undefined): boolean {
|
|
|
1182
1360
|
if (!file) return false;
|
|
1183
1361
|
const recipeRoot = resolve(Paths.getRecipeRoot());
|
|
1184
1362
|
const path = resolve(file);
|
|
1185
|
-
|
|
1363
|
+
const relation = relative(recipeRoot, path);
|
|
1364
|
+
return relation === "" || (!relation.startsWith("..") && !isAbsolute(relation));
|
|
1186
1365
|
}
|
|
1187
1366
|
|
|
1188
1367
|
export function shouldSuggestRecipePersistence(
|
|
@@ -1232,16 +1411,26 @@ function formatTransitionNextActions(transition: RunTransition): string {
|
|
|
1232
1411
|
return `\nNext actions: ${actions.join(" | ")}`;
|
|
1233
1412
|
}
|
|
1234
1413
|
|
|
1414
|
+
function formatTransitionSemanticResult(transition: RunTransition): string {
|
|
1415
|
+
const result = transition.semanticResult;
|
|
1416
|
+
if (!result) return "";
|
|
1417
|
+
const correlation = result.correlationId
|
|
1418
|
+
? `\nCorrelation: ${result.correlationId}` : "";
|
|
1419
|
+
const body = result.body ? `\nResult:\n${result.body}` : "";
|
|
1420
|
+
return `\nSemantic result: ${result.type} — ${result.summary}${correlation}${body}`;
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1235
1423
|
export function formatRunTransitionMessage(transition: RunTransition): string {
|
|
1236
1424
|
const artifacts = formatNamedArtifacts(transition.artifacts);
|
|
1237
1425
|
const runFiles = formatRunFileList(getRunArtifacts(transition));
|
|
1238
1426
|
const persistenceSuggestion = formatRecipePersistenceSuggestion(transition);
|
|
1239
1427
|
const policy = formatTransitionPolicy(transition);
|
|
1240
1428
|
const nextActions = formatTransitionNextActions(transition);
|
|
1429
|
+
const semanticResult = formatTransitionSemanticResult(transition);
|
|
1241
1430
|
if (transition.to === "done")
|
|
1242
|
-
return `Run ${transition.run} completed successfully.${policy}${artifacts}${runFiles}${nextActions}${persistenceSuggestion}`;
|
|
1431
|
+
return `Run ${transition.run} completed successfully.${semanticResult}${policy}${artifacts}${runFiles}${nextActions}${persistenceSuggestion}`;
|
|
1243
1432
|
if (transition.to === "failed")
|
|
1244
|
-
return `Run ${transition.run} failed.${policy}${artifacts}${runFiles}${nextActions}`;
|
|
1433
|
+
return `Run ${transition.run} failed.${semanticResult}${policy}${artifacts}${runFiles}${nextActions}`;
|
|
1245
1434
|
if (transition.to === "cancelled")
|
|
1246
1435
|
return `Run ${transition.run} was cancelled.${policy}${nextActions}`;
|
|
1247
1436
|
if (transition.to === "killed")
|
package/lib/paths.ts
CHANGED
|
@@ -17,6 +17,13 @@ export function getAgentDir(
|
|
|
17
17
|
: join(homedir(), ".pi", "agent");
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
export function isAutomaticRecipeReviewEnabled(
|
|
21
|
+
env: Record<string, string | undefined> = process.env,
|
|
22
|
+
): boolean {
|
|
23
|
+
const value = env.PI_ACTORS_AUTOMATIC_REVIEW?.trim().toLowerCase();
|
|
24
|
+
return value === undefined || !["0", "false", "off"].includes(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
20
27
|
export interface ExtensionRuntimePaths {
|
|
21
28
|
configPath: string;
|
|
22
29
|
runStateRoot: string;
|
|
@@ -38,6 +45,42 @@ export function getRunStateRoot(agentDir = getAgentDir()): string {
|
|
|
38
45
|
return join(getExtensionTmpDir(agentDir), "runs");
|
|
39
46
|
}
|
|
40
47
|
|
|
48
|
+
export function getDraftSleepRoot(agentDir = getAgentDir()): string {
|
|
49
|
+
return join(getExtensionTmpDir(agentDir), "draft-sleep");
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function getDraftSleepStatePath(agentDir = getAgentDir()): string {
|
|
53
|
+
return join(getDraftSleepRoot(agentDir), "state.json");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function getDraftSleepBatchDir(
|
|
57
|
+
batchId: string,
|
|
58
|
+
agentDir = getAgentDir(),
|
|
59
|
+
): string {
|
|
60
|
+
if (!/^[a-f0-9-]{36}$/u.test(batchId)) {
|
|
61
|
+
throw new Error("Invalid draft sleep batch id.");
|
|
62
|
+
}
|
|
63
|
+
return join(getDraftSleepRoot(agentDir), "batches", batchId);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function getToolReviewRoot(agentDir = getAgentDir()): string {
|
|
67
|
+
return join(getExtensionTmpDir(agentDir), "tool-review");
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getToolReviewStatePath(agentDir = getAgentDir()): string {
|
|
71
|
+
return join(getToolReviewRoot(agentDir), "state.json");
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function getToolReviewBatchDir(
|
|
75
|
+
reviewId: string,
|
|
76
|
+
agentDir = getAgentDir(),
|
|
77
|
+
): string {
|
|
78
|
+
if (!/^[a-f0-9-]{36}$/u.test(reviewId)) {
|
|
79
|
+
throw new Error("Invalid tool review id.");
|
|
80
|
+
}
|
|
81
|
+
return join(getToolReviewRoot(agentDir), "batches", reviewId);
|
|
82
|
+
}
|
|
83
|
+
|
|
41
84
|
export function getExtensionRuntimePaths(
|
|
42
85
|
agentDir = getAgentDir(),
|
|
43
86
|
): ExtensionRuntimePaths {
|
package/lib/recipes-discovery.ts
CHANGED
|
@@ -703,32 +703,40 @@ function templatePreview(value: unknown): string | undefined {
|
|
|
703
703
|
|
|
704
704
|
export function listDraftRecipes(root: string): Array<Record<string, unknown>> {
|
|
705
705
|
return listRecipeFiles(root).map((path) => {
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
706
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
707
|
+
const id = RecipesReferences.getRecipeIdFromPath(path);
|
|
708
|
+
const bytes = readFileSync(path);
|
|
709
|
+
const fingerprint = createHash("sha256").update(bytes).digest("hex");
|
|
710
|
+
const stat = statSync(path);
|
|
711
|
+
const config = RecipesReferences.readRawRecipeConfig(path);
|
|
712
|
+
const resolved = RecipesReferences.readResolvedRecipeConfig(path);
|
|
713
|
+
const diagnostics = getRecipeConfigDiagnostics(path, resolved);
|
|
714
|
+
const after = readFileSync(path);
|
|
715
|
+
if (createHash("sha256").update(after).digest("hex") !== fingerprint) {
|
|
716
|
+
continue;
|
|
717
|
+
}
|
|
718
|
+
const sourceRun = String(config?.description ?? "").match(
|
|
719
|
+
/spawn run ([^\s]+)/,
|
|
720
|
+
)?.[1];
|
|
721
|
+
const preview = templatePreview(config?.template);
|
|
722
|
+
const riskLabels = getRecipeRiskLabels(resolved);
|
|
723
|
+
return {
|
|
724
|
+
id,
|
|
725
|
+
path,
|
|
726
|
+
sha256: fingerprint,
|
|
727
|
+
size: bytes.byteLength,
|
|
728
|
+
created_at: stat.birthtime.toISOString(),
|
|
729
|
+
modified_at: stat.mtime.toISOString(),
|
|
730
|
+
valid: Boolean(resolved),
|
|
731
|
+
diagnostics,
|
|
732
|
+
...(riskLabels.length ? { risk_labels: riskLabels } : {}),
|
|
733
|
+
...(config?.description ? { description: config.description } : {}),
|
|
734
|
+
...(sourceRun ? { source_run: sourceRun } : {}),
|
|
735
|
+
...(config?.async !== undefined ? { async: config.async } : {}),
|
|
736
|
+
...(preview ? { template_preview: preview } : {}),
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
throw new Error(`Draft changed repeatedly during inventory: ${path}`);
|
|
732
740
|
});
|
|
733
741
|
}
|
|
734
742
|
|