@llblab/pi-actors 0.41.1 → 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.
Files changed (147) hide show
  1. package/AGENTS.md +11 -6
  2. package/BACKLOG.md +2 -62
  3. package/CHANGELOG.md +15 -0
  4. package/README.md +22 -4
  5. package/dist/index.js +25 -121
  6. package/dist/lib/async-runs.d.ts +25 -5
  7. package/dist/lib/async-runs.js +136 -47
  8. package/dist/lib/automatic-review-runtime.d.ts +18 -0
  9. package/dist/lib/automatic-review-runtime.js +96 -0
  10. package/dist/lib/draft-consolidation-transaction.d.ts +65 -0
  11. package/dist/lib/draft-consolidation-transaction.js +610 -0
  12. package/dist/lib/draft-consolidation.d.ts +35 -0
  13. package/dist/lib/draft-consolidation.js +126 -0
  14. package/dist/lib/draft-review.d.ts +56 -0
  15. package/dist/lib/draft-review.js +254 -0
  16. package/dist/lib/draft-sleep.d.ts +65 -0
  17. package/dist/lib/draft-sleep.js +468 -0
  18. package/dist/lib/file-state.d.ts +8 -1
  19. package/dist/lib/file-state.js +115 -18
  20. package/dist/lib/inspector-actions.d.ts +16 -0
  21. package/dist/lib/inspector-actions.js +57 -0
  22. package/dist/lib/inspector-command.d.ts +7 -0
  23. package/dist/lib/inspector-command.js +37 -0
  24. package/dist/lib/inspector-overlay.d.ts +20 -3
  25. package/dist/lib/inspector-overlay.js +275 -74
  26. package/dist/lib/inspector.d.ts +9 -0
  27. package/dist/lib/inspector.js +48 -0
  28. package/dist/lib/observability.d.ts +1 -0
  29. package/dist/lib/observability.js +11 -1
  30. package/dist/lib/paths.d.ts +7 -0
  31. package/dist/lib/paths.js +28 -0
  32. package/dist/lib/recipes-discovery.js +32 -24
  33. package/dist/lib/recipes-usage.d.ts +18 -6
  34. package/dist/lib/recipes-usage.js +445 -34
  35. package/dist/lib/review-control.d.ts +14 -0
  36. package/dist/lib/review-control.js +111 -0
  37. package/dist/lib/review-diagnostics.d.ts +11 -0
  38. package/dist/lib/review-diagnostics.js +148 -0
  39. package/dist/lib/review-projection.d.ts +14 -0
  40. package/dist/lib/review-projection.js +170 -0
  41. package/dist/lib/run-ui-runtime.d.ts +18 -0
  42. package/dist/lib/run-ui-runtime.js +123 -0
  43. package/dist/lib/runs-artifacts.d.ts +1 -1
  44. package/dist/lib/runs-artifacts.js +1 -1
  45. package/dist/lib/runs-control.d.ts +8 -2
  46. package/dist/lib/runs-control.js +23 -6
  47. package/dist/lib/runs-identity.d.ts +1 -1
  48. package/dist/lib/runs-identity.js +1 -1
  49. package/dist/lib/runs-index.d.ts +11 -2
  50. package/dist/lib/runs-index.js +46 -23
  51. package/dist/lib/runs-mailbox.d.ts +1 -1
  52. package/dist/lib/runs-mailbox.js +1 -1
  53. package/dist/lib/runs-messages.d.ts +1 -1
  54. package/dist/lib/runs-messages.js +1 -1
  55. package/dist/lib/runs-outbox.d.ts +1 -1
  56. package/dist/lib/runs-outbox.js +1 -1
  57. package/dist/lib/runs-ownership.d.ts +1 -1
  58. package/dist/lib/runs-ownership.js +1 -1
  59. package/dist/lib/runs-parent-teardown.d.ts +51 -0
  60. package/dist/lib/runs-parent-teardown.js +172 -0
  61. package/dist/lib/runs-process.d.ts +1 -1
  62. package/dist/lib/runs-process.js +1 -1
  63. package/dist/lib/runs-retention.d.ts +1 -1
  64. package/dist/lib/runs-retention.js +1 -1
  65. package/dist/lib/runs-start.d.ts +5 -3
  66. package/dist/lib/runs-start.js +6 -48
  67. package/dist/lib/runs-status.d.ts +5 -3
  68. package/dist/lib/runs-status.js +4 -6
  69. package/dist/lib/runtime.d.ts +7 -1
  70. package/dist/lib/runtime.js +32 -18
  71. package/dist/lib/tool-review-lineage-transaction.d.ts +27 -0
  72. package/dist/lib/tool-review-lineage-transaction.js +597 -0
  73. package/dist/lib/tool-review-lineage.d.ts +24 -0
  74. package/dist/lib/tool-review-lineage.js +98 -0
  75. package/dist/lib/tool-review-scheduler.d.ts +80 -0
  76. package/dist/lib/tool-review-scheduler.js +494 -0
  77. package/dist/lib/tool-review-transaction.d.ts +50 -0
  78. package/dist/lib/tool-review-transaction.js +362 -0
  79. package/dist/lib/tool-review.d.ts +56 -0
  80. package/dist/lib/tool-review.js +197 -0
  81. package/dist/lib/tools-inspect.js +26 -3
  82. package/dist/lib/tools-local.js +4 -2
  83. package/dist/lib/tools-message.d.ts +1 -0
  84. package/dist/lib/tools-message.js +29 -17
  85. package/dist/lib/tools-spawn.js +4 -1
  86. package/dist/lib/tools.d.ts +1 -0
  87. package/dist/lib/tools.js +1 -0
  88. package/dist/recipes/draft-review.json +24 -0
  89. package/dist/recipes/tool-review.json +24 -0
  90. package/dist/scripts/release-gates.mjs +165 -0
  91. package/dist/skills/actors/SKILL.md +9 -8
  92. package/dist/skills/swarm/SKILL.md +1 -1
  93. package/docs/actor-inspector.md +20 -11
  94. package/docs/async-runs.md +9 -1
  95. package/docs/recipe-library.md +7 -3
  96. package/docs/template-recipes.md +4 -11
  97. package/docs/tool-registry.md +11 -4
  98. package/index.ts +27 -142
  99. package/lib/async-runs.ts +218 -62
  100. package/lib/automatic-review-runtime.ts +135 -0
  101. package/lib/draft-consolidation-transaction.ts +821 -0
  102. package/lib/draft-consolidation.ts +181 -0
  103. package/lib/draft-review.ts +325 -0
  104. package/lib/draft-sleep.ts +576 -0
  105. package/lib/file-state.ts +143 -19
  106. package/lib/inspector-actions.ts +79 -0
  107. package/lib/inspector-command.ts +54 -0
  108. package/lib/inspector-overlay.ts +325 -91
  109. package/lib/inspector.ts +72 -0
  110. package/lib/observability.ts +14 -1
  111. package/lib/paths.ts +43 -0
  112. package/lib/recipes-discovery.ts +34 -26
  113. package/lib/recipes-usage.ts +569 -40
  114. package/lib/review-control.ts +137 -0
  115. package/lib/review-diagnostics.ts +164 -0
  116. package/lib/review-projection.ts +200 -0
  117. package/lib/run-ui-runtime.ts +153 -0
  118. package/lib/runs-artifacts.ts +1 -1
  119. package/lib/runs-control.ts +49 -5
  120. package/lib/runs-identity.ts +1 -1
  121. package/lib/runs-index.ts +57 -21
  122. package/lib/runs-mailbox.ts +1 -1
  123. package/lib/runs-messages.ts +1 -1
  124. package/lib/runs-outbox.ts +1 -1
  125. package/lib/runs-ownership.ts +1 -1
  126. package/lib/runs-parent-teardown.ts +257 -0
  127. package/lib/runs-process.ts +1 -1
  128. package/lib/runs-retention.ts +1 -1
  129. package/lib/runs-start.ts +12 -68
  130. package/lib/runs-status.ts +12 -8
  131. package/lib/runtime.ts +34 -17
  132. package/lib/tool-review-lineage-transaction.ts +881 -0
  133. package/lib/tool-review-lineage.ts +145 -0
  134. package/lib/tool-review-scheduler.ts +635 -0
  135. package/lib/tool-review-transaction.ts +563 -0
  136. package/lib/tool-review.ts +270 -0
  137. package/lib/tools-inspect.ts +33 -3
  138. package/lib/tools-local.ts +8 -2
  139. package/lib/tools-message.ts +45 -30
  140. package/lib/tools-spawn.ts +8 -1
  141. package/lib/tools.ts +5 -0
  142. package/package.json +3 -2
  143. package/recipes/draft-review.json +24 -0
  144. package/recipes/tool-review.json +24 -0
  145. package/scripts/release-gates.mjs +165 -0
  146. package/skills/actors/SKILL.md +9 -8
  147. 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[];
@@ -43,6 +43,7 @@ export interface RunObservation {
43
43
  artifacts?: Record<string, string>;
44
44
  launchSource?: AsyncRuns.AsyncRunLaunchSource;
45
45
  modelPolicy?: Record<string, unknown>;
46
+ notificationPolicy?: "normal" | "silent";
46
47
  recipeFile?: string;
47
48
  terminalHandled?: boolean;
48
49
  retireWhen?: string;
@@ -531,6 +532,9 @@ function observeRun(stateDir: string): RunObservation | undefined {
531
532
  !Array.isArray(status.model_policy)
532
533
  ? { modelPolicy: status.model_policy as Record<string, unknown> }
533
534
  : {}),
535
+ ...(status.notification_policy === "silent"
536
+ ? { notificationPolicy: "silent" as const }
537
+ : {}),
534
538
  ...(typeof status.recipe_file === "string"
535
539
  ? { recipeFile: status.recipe_file }
536
540
  : {}),
@@ -919,7 +923,11 @@ export function detectRunTransitions(
919
923
  for (const run of summary.runs) {
920
924
  const key = runObservationKey(run);
921
925
  const old = previous.get(key);
922
- if (!run.terminalHandled && TERMINAL.has(run.status)) {
926
+ if (
927
+ run.notificationPolicy !== "silent" &&
928
+ !run.terminalHandled &&
929
+ TERMINAL.has(run.status)
930
+ ) {
923
931
  transitions.push({
924
932
  from: old ?? "running",
925
933
  run: run.run,
@@ -1037,6 +1045,11 @@ export function detectRunOutboxEvents(
1037
1045
  const key = run.stateDir ?? run.run;
1038
1046
  const records = readOutboxRecords(run);
1039
1047
  const previousCount = previousLineCounts.get(key) ?? 0;
1048
+ if (run.notificationPolicy === "silent") {
1049
+ previousLineCounts.set(key, records.length);
1050
+ seenEventIds.set(key, seenEventIds.get(key) ?? new Set<string>());
1051
+ continue;
1052
+ }
1040
1053
  const start = Math.min(previousCount, records.length);
1041
1054
  const seen = seenEventIds.get(key) ?? new Set<string>();
1042
1055
  for (let index = start; index < records.length; index += 1) {
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 {
@@ -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
- const id = RecipesReferences.getRecipeIdFromPath(path);
707
- const bytes = readFileSync(path);
708
- const stat = statSync(path);
709
- const config = RecipesReferences.readRawRecipeConfig(path);
710
- const resolved = RecipesReferences.readResolvedRecipeConfig(path);
711
- const diagnostics = getRecipeConfigDiagnostics(path, resolved);
712
- const sourceRun = String(config?.description ?? "").match(
713
- /spawn run ([^\s]+)/,
714
- )?.[1];
715
- const preview = templatePreview(config?.template);
716
- const riskLabels = getRecipeRiskLabels(resolved);
717
- return {
718
- id,
719
- path,
720
- sha256: createHash("sha256").update(bytes).digest("hex"),
721
- size: bytes.byteLength,
722
- created_at: stat.birthtime.toISOString(),
723
- modified_at: stat.mtime.toISOString(),
724
- valid: Boolean(resolved),
725
- diagnostics,
726
- ...(riskLabels.length ? { risk_labels: riskLabels } : {}),
727
- ...(config?.description ? { description: config.description } : {}),
728
- ...(sourceRun ? { source_run: sourceRun } : {}),
729
- ...(config?.async !== undefined ? { async: config.async } : {}),
730
- ...(preview ? { template_preview: preview } : {}),
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