@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
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Async run state index.
3
- * Owns recursive run-state discovery, index rebuild/read, and status filtering.
3
+ * Owns: recursive run-state discovery, index rebuild/read, and status filtering.
4
4
  */
5
5
  import { existsSync, mkdirSync, readdirSync } from "node:fs";
6
6
  import { join, resolve } from "node:path";
@@ -14,28 +14,51 @@ function matchesStatusFilter(status, filter) {
14
14
  return status !== "running";
15
15
  return status === filter;
16
16
  }
17
- export function listRunStateDirs(stateRoot, depth = 0, seen = new Set()) {
18
- if (!existsSync(stateRoot) || seen.has(resolve(stateRoot)))
19
- return [];
20
- seen.add(resolve(stateRoot));
21
- let entries;
22
- try {
23
- entries = readdirSync(stateRoot, { withFileTypes: true });
24
- }
25
- catch {
26
- return [];
27
- }
28
- const result = [];
29
- for (const entry of entries) {
30
- if (!entry.isDirectory())
31
- continue;
32
- const child = join(stateRoot, entry.name);
33
- if (existsSync(join(child, "run.json")))
34
- result.push(child);
35
- if (depth + 1 < 8)
36
- result.push(...listRunStateDirs(child, depth + 1, seen));
37
- }
38
- return result;
17
+ export function discoverRunStateDirs(stateRoot, maxDepth = 8) {
18
+ const issues = [];
19
+ const stateDirs = [];
20
+ const seen = new Set();
21
+ const visit = (directory, depth) => {
22
+ const canonical = resolve(directory);
23
+ if (!existsSync(directory) || seen.has(canonical))
24
+ return;
25
+ seen.add(canonical);
26
+ let entries;
27
+ try {
28
+ entries = readdirSync(directory, { withFileTypes: true });
29
+ }
30
+ catch {
31
+ issues.push({ path: directory, reason: "unreadable" });
32
+ return;
33
+ }
34
+ for (const entry of entries) {
35
+ if (!entry.isDirectory())
36
+ continue;
37
+ const child = join(directory, entry.name);
38
+ if (existsSync(join(child, "run.json")))
39
+ stateDirs.push(child);
40
+ if (depth + 1 < maxDepth)
41
+ visit(child, depth + 1);
42
+ else {
43
+ try {
44
+ if (readdirSync(child, { withFileTypes: true }).some((item) => item.isDirectory())) {
45
+ issues.push({ path: child, reason: "depth_truncated" });
46
+ }
47
+ }
48
+ catch {
49
+ issues.push({ path: child, reason: "unreadable" });
50
+ }
51
+ }
52
+ }
53
+ };
54
+ visit(stateRoot, 0);
55
+ return {
56
+ issues: issues.sort((left, right) => left.path.localeCompare(right.path)),
57
+ stateDirs: [...new Set(stateDirs)].sort(),
58
+ };
59
+ }
60
+ export function listRunStateDirs(stateRoot) {
61
+ return discoverRunStateDirs(stateRoot).stateDirs;
39
62
  }
40
63
  function runIndexPath(stateRoot) {
41
64
  return join(stateRoot, "index.json");
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Async run mailbox state.
3
- * Owns durable run inbox records, claim locks, and handler status transitions.
3
+ * Owns: durable run inbox records, claim locks, and handler status transitions.
4
4
  */
5
5
  export type RunInboxStatus = "queued" | "sent" | "claimed" | "handled" | "failed";
6
6
  export type RunInboxMessage = Record<string, unknown> & {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Async run mailbox state.
3
- * Owns durable run inbox records, claim locks, and handler status transitions.
3
+ * Owns: durable run inbox records, claim locks, and handler status transitions.
4
4
  */
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Async run message delivery.
3
- * Owns run control endpoint resolution, inbox enqueueing, wake notification,
3
+ * Owns: run control endpoint resolution, inbox enqueueing, wake notification,
4
4
  * transport writes, and delivery receipts.
5
5
  */
6
6
  export interface RunControlEndpoint {
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Async run message delivery.
3
- * Owns run control endpoint resolution, inbox enqueueing, wake notification,
3
+ * Owns: run control endpoint resolution, inbox enqueueing, wake notification,
4
4
  * transport writes, and delivery receipts.
5
5
  */
6
6
  import { closeSync, constants, existsSync, openSync, statSync, writeFileSync, writeSync, } from "node:fs";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Async run outbox events.
3
- * Owns outbox event normalization, parsing, and append payload formatting.
3
+ * Owns: outbox event normalization, parsing, and append payload formatting.
4
4
  */
5
5
  export type RunOutboxDelivery = "log" | "notify" | "followup";
6
6
  export type RunOutboxLevel = "info" | "warning" | "error";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Async run outbox events.
3
- * Owns outbox event normalization, parsing, and append payload formatting.
3
+ * Owns: outbox event normalization, parsing, and append payload formatting.
4
4
  */
5
5
  export function normalizeRunOutboxDelivery(value) {
6
6
  return value === "notify" || value === "followup" ? value : "log";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Async run state-directory ownership.
3
- * Owns the marker proof required before launch and destructive retention.
3
+ * Owns: the marker proof required before launch and destructive retention.
4
4
  */
5
5
  export declare const RUN_STATE_OWNERSHIP_FILE = ".pi-actors-run-state.json";
6
6
  export declare function claimRunStateDirectory(stateDir: string, run: string): string;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Async run state-directory ownership.
3
- * Owns the marker proof required before launch and destructive retention.
3
+ * Owns: the marker proof required before launch and destructive retention.
4
4
  */
5
5
  import { existsSync, lstatSync, mkdirSync, readdirSync, readFileSync, realpathSync, } from "node:fs";
6
6
  import { randomUUID } from "node:crypto";
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Parent-session actor teardown.
3
+ * Owns: exact-owner running-run selection, kill revalidation, bounded outcomes, and partial-failure continuation.
4
+ */
5
+ export interface ParentRunTeardownCandidate {
6
+ ownerId: string;
7
+ run: string;
8
+ runInstanceId?: string;
9
+ stateDir: string;
10
+ }
11
+ export type ParentRunTeardownOutcome = "killed" | "skipped" | "failed";
12
+ export interface ParentRunTeardownAttempt extends ParentRunTeardownCandidate {
13
+ outcome: ParentRunTeardownOutcome;
14
+ reason?: string;
15
+ }
16
+ export interface ParentRunTeardownDiscoveryFailure {
17
+ path: string;
18
+ reason: string;
19
+ }
20
+ export interface ParentRunTeardownResult {
21
+ attempted: number;
22
+ discoveryFailed: number;
23
+ discoveryFailures: ParentRunTeardownDiscoveryFailure[];
24
+ failed: number;
25
+ killed: number;
26
+ skipped: number;
27
+ attempts: ParentRunTeardownAttempt[];
28
+ }
29
+ export interface ParentRunTeardownSummary extends ParentRunTeardownResult {
30
+ attemptsOmitted: number;
31
+ discoveryFailuresOmitted: number;
32
+ ownerId: string;
33
+ trigger: string;
34
+ ts: string;
35
+ version: 1;
36
+ }
37
+ export declare function buildBoundedParentTeardownSummary(result: ParentRunTeardownResult, ownerId: string, trigger: string, ts: string): ParentRunTeardownSummary;
38
+ export interface ParentRunTeardownDeps {
39
+ getRunStatus: (stateDir: string) => Record<string, unknown>;
40
+ killRun: (stateDir: string, expected: {
41
+ ownerId: string;
42
+ runInstanceId: string;
43
+ }) => Record<string, unknown>;
44
+ listRunStatuses: () => Array<Record<string, unknown>> | {
45
+ failures: ParentRunTeardownDiscoveryFailure[];
46
+ statuses: Array<Record<string, unknown>>;
47
+ };
48
+ recordAttempt: (attempt: ParentRunTeardownAttempt) => void;
49
+ }
50
+ export declare function selectParentRunTeardownCandidates(ownerId: string | undefined, statuses: Array<Record<string, unknown>>): ParentRunTeardownCandidate[];
51
+ export declare function teardownParentRuns(ownerId: string | undefined, deps: ParentRunTeardownDeps): ParentRunTeardownResult;
@@ -0,0 +1,172 @@
1
+ /**
2
+ * Parent-session actor teardown.
3
+ * Owns: exact-owner running-run selection, kill revalidation, bounded outcomes, and partial-failure continuation.
4
+ */
5
+ const SUMMARY_ENTRY_LIMIT = 200;
6
+ const SUMMARY_TEXT_LIMIT = 500;
7
+ function boundedSummaryText(value) {
8
+ return value.length > SUMMARY_TEXT_LIMIT
9
+ ? `${value.slice(0, SUMMARY_TEXT_LIMIT - 1)}…`
10
+ : value;
11
+ }
12
+ export function buildBoundedParentTeardownSummary(result, ownerId, trigger, ts) {
13
+ const attempts = result.attempts.slice(0, SUMMARY_ENTRY_LIMIT).map((attempt) => ({
14
+ ownerId: boundedSummaryText(attempt.ownerId),
15
+ outcome: attempt.outcome,
16
+ ...(attempt.reason ? { reason: boundedSummaryText(attempt.reason) } : {}),
17
+ run: boundedSummaryText(attempt.run),
18
+ ...(attempt.runInstanceId
19
+ ? { runInstanceId: boundedSummaryText(attempt.runInstanceId) }
20
+ : {}),
21
+ stateDir: boundedSummaryText(attempt.stateDir),
22
+ }));
23
+ const discoveryFailures = result.discoveryFailures
24
+ .slice(0, SUMMARY_ENTRY_LIMIT)
25
+ .map((failure) => ({
26
+ path: boundedSummaryText(failure.path),
27
+ reason: boundedSummaryText(failure.reason),
28
+ }));
29
+ return {
30
+ attempted: result.attempted,
31
+ attempts,
32
+ attemptsOmitted: Math.max(0, result.attempts.length - attempts.length),
33
+ discoveryFailed: result.discoveryFailed,
34
+ discoveryFailures,
35
+ discoveryFailuresOmitted: Math.max(0, result.discoveryFailures.length - discoveryFailures.length),
36
+ failed: result.failed,
37
+ killed: result.killed,
38
+ ownerId: boundedSummaryText(ownerId),
39
+ skipped: result.skipped,
40
+ trigger: boundedSummaryText(trigger),
41
+ ts: boundedSummaryText(ts),
42
+ version: 1,
43
+ };
44
+ }
45
+ function errorMessage(error) {
46
+ return error instanceof Error ? error.message : String(error);
47
+ }
48
+ function candidateFrom(ownerId, status) {
49
+ if (status.ownerId !== ownerId ||
50
+ status.status !== "running" ||
51
+ typeof status.run !== "string" ||
52
+ typeof status.state_dir !== "string") {
53
+ return undefined;
54
+ }
55
+ return {
56
+ ownerId,
57
+ run: status.run,
58
+ ...(typeof status.run_instance_id === "string"
59
+ ? { runInstanceId: status.run_instance_id }
60
+ : {}),
61
+ stateDir: status.state_dir,
62
+ };
63
+ }
64
+ export function selectParentRunTeardownCandidates(ownerId, statuses) {
65
+ if (!ownerId)
66
+ return [];
67
+ const byStateDir = new Map();
68
+ for (const status of statuses) {
69
+ const candidate = candidateFrom(ownerId, status);
70
+ if (candidate && !byStateDir.has(candidate.stateDir)) {
71
+ byStateDir.set(candidate.stateDir, candidate);
72
+ }
73
+ }
74
+ return [...byStateDir.values()].sort((left, right) => left.run.localeCompare(right.run));
75
+ }
76
+ function recordAttempt(deps, attempt) {
77
+ try {
78
+ deps.recordAttempt(attempt);
79
+ return attempt;
80
+ }
81
+ catch (error) {
82
+ return {
83
+ ...attempt,
84
+ outcome: "failed",
85
+ reason: `${attempt.reason ? `${attempt.reason}; ` : ""}evidence: ${errorMessage(error)}`,
86
+ };
87
+ }
88
+ }
89
+ export function teardownParentRuns(ownerId, deps) {
90
+ let discovery;
91
+ try {
92
+ const listed = deps.listRunStatuses();
93
+ discovery = Array.isArray(listed)
94
+ ? { failures: [], statuses: listed }
95
+ : listed;
96
+ }
97
+ catch (error) {
98
+ discovery = {
99
+ failures: [{ path: "<state-root>", reason: errorMessage(error) }],
100
+ statuses: [],
101
+ };
102
+ }
103
+ const candidates = selectParentRunTeardownCandidates(ownerId, discovery.statuses);
104
+ const attempts = [];
105
+ for (const candidate of candidates) {
106
+ let attempt;
107
+ try {
108
+ const current = deps.getRunStatus(candidate.stateDir);
109
+ if (!candidate.runInstanceId) {
110
+ attempt = {
111
+ ...candidate,
112
+ outcome: "failed",
113
+ reason: "run generation unavailable",
114
+ };
115
+ }
116
+ else if (current.ownerId !== candidate.ownerId) {
117
+ attempt = {
118
+ ...candidate,
119
+ outcome: "skipped",
120
+ reason: "ownership changed",
121
+ };
122
+ }
123
+ else if (current.run_instance_id !== candidate.runInstanceId) {
124
+ attempt = {
125
+ ...candidate,
126
+ outcome: "skipped",
127
+ reason: "run generation changed",
128
+ };
129
+ }
130
+ else if (current.status !== "running") {
131
+ attempt = {
132
+ ...candidate,
133
+ outcome: "skipped",
134
+ reason: "already terminal",
135
+ };
136
+ }
137
+ else {
138
+ const killed = deps.killRun(candidate.stateDir, {
139
+ ownerId: candidate.ownerId,
140
+ runInstanceId: candidate.runInstanceId,
141
+ });
142
+ attempt = killed.killed === true
143
+ ? { ...candidate, outcome: "killed" }
144
+ : {
145
+ ...candidate,
146
+ outcome: "failed",
147
+ reason: typeof killed.reason === "string"
148
+ ? killed.reason
149
+ : "kill rejected",
150
+ };
151
+ }
152
+ }
153
+ catch (error) {
154
+ attempt = {
155
+ ...candidate,
156
+ outcome: "failed",
157
+ reason: errorMessage(error),
158
+ };
159
+ }
160
+ attempts.push(recordAttempt(deps, attempt));
161
+ }
162
+ return {
163
+ attempted: candidates.length,
164
+ discoveryFailed: discovery.failures.length,
165
+ discoveryFailures: discovery.failures,
166
+ failed: attempts.filter((attempt) => attempt.outcome === "failed").length +
167
+ discovery.failures.length,
168
+ killed: attempts.filter((attempt) => attempt.outcome === "killed").length,
169
+ skipped: attempts.filter((attempt) => attempt.outcome === "skipped").length,
170
+ attempts,
171
+ };
172
+ }
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Async run process identity helpers.
3
- * Owns cross-platform liveness and stable runner process identity proofs.
3
+ * Owns: cross-platform liveness and stable runner process identity proofs.
4
4
  */
5
5
  export interface RunProcessIdentity {
6
6
  command: string;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Async run process identity helpers.
3
- * Owns cross-platform liveness and stable runner process identity proofs.
3
+ * Owns: cross-platform liveness and stable runner process identity proofs.
4
4
  */
5
5
  import { spawnSync } from "node:child_process";
6
6
  import { existsSync, readFileSync, readlinkSync, realpathSync } from "node:fs";
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Async run retention operations.
3
- * Owns terminal-run archive and prune filesystem behavior.
3
+ * Owns: terminal-run archive and prune filesystem behavior.
4
4
  */
5
5
  import { cpSync } from "node:fs";
6
6
  export declare function archiveTerminalRun(status: Record<string, unknown>): Record<string, unknown>;
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Async run retention operations.
3
- * Owns terminal-run archive and prune filesystem behavior.
3
+ * Owns: terminal-run archive and prune filesystem behavior.
4
4
  */
5
5
  import { createHash } from "node:crypto";
6
6
  import { cpSync, mkdirSync, renameSync, rmSync } from "node:fs";
@@ -1,9 +1,11 @@
1
1
  /**
2
2
  * Async run start guards.
3
- * Owns active-run reuse checks, start lock acquisition, and safe state-dir
3
+ * Owns: active-run reuse checks, start lock acquisition, and safe state-dir
4
4
  * preparation before a new runner process is spawned.
5
5
  */
6
- export type RunJsonReader = (path: string) => Record<string, unknown> | undefined;
6
+ import { type FileMutationLockOptions } from "./file-state.ts";
7
+ type RunJsonReader = (path: string) => Record<string, unknown> | undefined;
7
8
  export declare function assertNoActiveRunState(stateDir: string, readJson: RunJsonReader, _runnerPath: string): void;
8
- export declare function acquireStateStartLock(stateDir: string): () => void;
9
+ export declare function acquireStateStartLock(stateDir: string, options?: FileMutationLockOptions): () => void;
9
10
  export declare function prepareStateDirForStart(stateDir: string, readJson: RunJsonReader, _runnerPath: string): void;
11
+ export {};
@@ -1,12 +1,12 @@
1
1
  /**
2
2
  * Async run start guards.
3
- * Owns active-run reuse checks, start lock acquisition, and safe state-dir
3
+ * Owns: active-run reuse checks, start lock acquisition, and safe state-dir
4
4
  * preparation before a new runner process is spawned.
5
5
  */
6
- import { mkdirSync, readFileSync, rmSync, statSync, writeFileSync, } from "node:fs";
6
+ import { rmSync } from "node:fs";
7
7
  import { join } from "node:path";
8
- import { isAlive, readProcessIdentity, verifyRunProcessIdentity, } from "./runs-process.js";
9
- const START_LOCK_MAX_AGE_MS = 5 * 60 * 1000;
8
+ import { acquireFileMutationLock, } from "./file-state.js";
9
+ import { isAlive, verifyRunProcessIdentity, } from "./runs-process.js";
10
10
  export function assertNoActiveRunState(stateDir, readJson, _runnerPath) {
11
11
  const meta = readJson(join(stateDir, "run.json"));
12
12
  if (!meta)
@@ -26,50 +26,8 @@ export function assertNoActiveRunState(stateDir, readJson, _runnerPath) {
26
26
  throw new Error(`Run state already has an active owned process: ${String(meta.run ?? stateDir)}. Stop it before reusing the same run_id or state_dir.`);
27
27
  }
28
28
  }
29
- function writeStartLockOwner(lockDir, recovered = false) {
30
- const processIdentity = readProcessIdentity(process.pid);
31
- writeFileSync(join(lockDir, "owner.json"), `${JSON.stringify({
32
- pid: process.pid,
33
- createdAt: new Date().toISOString(),
34
- ...(processIdentity ? { process_identity: processIdentity } : {}),
35
- ...(recovered ? { recovered: true } : {}),
36
- })}\n`, "utf8");
37
- }
38
- function isStartLockOwnerProvenDead(lockDir) {
39
- try {
40
- const owner = JSON.parse(readFileSync(join(lockDir, "owner.json"), "utf8"));
41
- const pid = Number(owner.pid || 0);
42
- if (!pid)
43
- return false;
44
- return verifyRunProcessIdentity(pid, owner.process_identity).status === "dead_pid";
45
- }
46
- catch {
47
- return false;
48
- }
49
- }
50
- export function acquireStateStartLock(stateDir) {
51
- const lockDir = join(stateDir, ".start.lock");
52
- try {
53
- mkdirSync(lockDir);
54
- writeStartLockOwner(lockDir);
55
- }
56
- catch (error) {
57
- try {
58
- const stat = statSync(lockDir);
59
- if (Date.now() - stat.mtimeMs > START_LOCK_MAX_AGE_MS &&
60
- isStartLockOwnerProvenDead(lockDir)) {
61
- rmSync(lockDir, { recursive: true, force: true });
62
- mkdirSync(lockDir);
63
- writeStartLockOwner(lockDir, true);
64
- return () => rmSync(lockDir, { recursive: true, force: true });
65
- }
66
- }
67
- catch {
68
- // Keep the original lock acquisition error below.
69
- }
70
- throw new Error(`Run state is already being started: ${stateDir}. Retry after the current start finishes.`, { cause: error });
71
- }
72
- return () => rmSync(lockDir, { recursive: true, force: true });
29
+ export function acquireStateStartLock(stateDir, options = {}) {
30
+ return acquireFileMutationLock(join(stateDir, ".lifecycle"), options);
73
31
  }
74
32
  export function prepareStateDirForStart(stateDir, readJson, _runnerPath) {
75
33
  const existing = readJson(join(stateDir, "run.json"));
@@ -1,8 +1,10 @@
1
1
  /**
2
2
  * Async run status and log readers.
3
- * Owns status derivation from run state files plus bounded log/event tails.
3
+ * Owns: status derivation from run state files plus bounded log/event tails.
4
4
  */
5
- export type RunJsonReader = (path: string) => Record<string, unknown> | undefined;
6
- export declare function buildRunStatus(stateDir: string, runOrDir: string, meta: Record<string, unknown>, readJson: RunJsonReader, _runnerPath: string, runnerIdentityGraceMs: number): Record<string, unknown>;
5
+ export type AsyncRunStatus = "running" | "done" | "failed" | "exited" | "cancelled" | "killed";
6
+ type RunJsonReader = (path: string) => Record<string, unknown> | undefined;
7
+ export declare function buildRunStatus(stateDir: string, runOrDir: string, meta: Record<string, unknown>, readJson: RunJsonReader, _runnerPath: string, _runnerIdentityGraceMs: number): Record<string, unknown>;
7
8
  export declare function tailFile(path: string, lines: number): string;
8
9
  export declare function tailLines(path: string, lines: number): string[];
10
+ export {};
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * Async run status and log readers.
3
- * Owns status derivation from run state files plus bounded log/event tails.
3
+ * Owns: status derivation from run state files plus bounded log/event tails.
4
4
  */
5
5
  import { existsSync, readFileSync } from "node:fs";
6
6
  import { join } from "node:path";
7
- import { isWithinRunnerIdentityGrace, verifyRunProcessIdentity, } from "./runs-process.js";
7
+ import { verifyRunProcessIdentity, } from "./runs-process.js";
8
8
  import { readJsonlFileResilient } from "./state-readers.js";
9
9
  function getInterruptedRunStatus(stateDir) {
10
10
  const events = readJsonlFileResilient(join(stateDir, "events.jsonl")).records.slice(-200);
@@ -16,14 +16,12 @@ function getInterruptedRunStatus(stateDir) {
16
16
  }
17
17
  return undefined;
18
18
  }
19
- export function buildRunStatus(stateDir, runOrDir, meta, readJson, _runnerPath, runnerIdentityGraceMs) {
19
+ export function buildRunStatus(stateDir, runOrDir, meta, readJson, _runnerPath, _runnerIdentityGraceMs) {
20
20
  const result = readJson(join(stateDir, "result.json"));
21
21
  const pid = Number(meta.pid || 0);
22
22
  const processIdentity = verifyRunProcessIdentity(pid, meta.process_identity);
23
23
  const aliveOwnedRunner = Boolean(pid &&
24
- (processIdentity.valid ||
25
- (processIdentity.status === "unsupported_proof" &&
26
- isWithinRunnerIdentityGrace(meta, runnerIdentityGraceMs))));
24
+ (processIdentity.valid || processIdentity.status === "unsupported_proof"));
27
25
  const status = result
28
26
  ? Number(result.code ?? 0) === 0
29
27
  ? "done"
@@ -3,6 +3,7 @@
3
3
  * Zones: runtime coordination, registry loading, pi tools
4
4
  * Owns persisted tool loading, reserved-name guards, runtime registration, and warning notification
5
5
  */
6
+ import { watch } from "node:fs";
6
7
  import * as Config from "./config.ts";
7
8
  import type { RegisteredToolExec } from "./execution.ts";
8
9
  import * as ToolsLocal from "./tools-local.ts";
@@ -34,4 +35,9 @@ export interface RecipeToolReloadWatcher {
34
35
  watch(ctx: RuntimeContext): void;
35
36
  }
36
37
  export declare function createAutoToolsRuntime(deps: ToolRegistryRuntimeDeps): ToolRegistryRuntime;
37
- export declare function createRecipeToolReloadWatcher(runtime: Pick<ToolRegistryRuntime, "loadTools">): RecipeToolReloadWatcher;
38
+ export interface RecipeToolReloadWatcherDeps {
39
+ exists?: (path: string) => boolean;
40
+ recipeRoot?: string;
41
+ watchPath?: typeof watch;
42
+ }
43
+ export declare function createRecipeToolReloadWatcher(runtime: Pick<ToolRegistryRuntime, "loadTools">, deps?: RecipeToolReloadWatcherDeps): RecipeToolReloadWatcher;