@osolmaz/pi-workflows 0.2.0 → 0.4.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 (152) hide show
  1. package/README.md +72 -5
  2. package/dist/builtins/catalog.d.ts +2 -0
  3. package/dist/builtins/catalog.js +22 -0
  4. package/dist/builtins/catalog.js.map +1 -0
  5. package/dist/builtins/monitor.workflow.d.ts +71 -0
  6. package/dist/builtins/monitor.workflow.js +250 -0
  7. package/dist/builtins/monitor.workflow.js.map +1 -0
  8. package/dist/controllers/conditions.d.ts +6 -0
  9. package/dist/controllers/conditions.js +68 -0
  10. package/dist/controllers/conditions.js.map +1 -0
  11. package/dist/controllers/definition.d.ts +6 -0
  12. package/dist/controllers/definition.js +45 -0
  13. package/dist/controllers/definition.js.map +1 -0
  14. package/dist/controllers/effects.d.ts +14 -0
  15. package/dist/controllers/effects.js +104 -0
  16. package/dist/controllers/effects.js.map +1 -0
  17. package/dist/controllers/errors.d.ts +12 -0
  18. package/dist/controllers/errors.js +25 -0
  19. package/dist/controllers/errors.js.map +1 -0
  20. package/dist/controllers/index.d.ts +13 -0
  21. package/dist/controllers/index.js +13 -0
  22. package/dist/controllers/index.js.map +1 -0
  23. package/dist/controllers/json.d.ts +5 -0
  24. package/dist/controllers/json.js +57 -0
  25. package/dist/controllers/json.js.map +1 -0
  26. package/dist/controllers/loader.d.ts +23 -0
  27. package/dist/controllers/loader.js +74 -0
  28. package/dist/controllers/loader.js.map +1 -0
  29. package/dist/controllers/manager.d.ts +58 -0
  30. package/dist/controllers/manager.js +399 -0
  31. package/dist/controllers/manager.js.map +1 -0
  32. package/dist/controllers/results.d.ts +5 -0
  33. package/dist/controllers/results.js +32 -0
  34. package/dist/controllers/results.js.map +1 -0
  35. package/dist/controllers/sqlite.d.ts +235 -0
  36. package/dist/controllers/sqlite.js +1087 -0
  37. package/dist/controllers/sqlite.js.map +1 -0
  38. package/dist/controllers/store.d.ts +112 -0
  39. package/dist/controllers/store.js +32 -0
  40. package/dist/controllers/store.js.map +1 -0
  41. package/dist/controllers/types.d.ts +159 -0
  42. package/dist/controllers/types.js +2 -0
  43. package/dist/controllers/types.js.map +1 -0
  44. package/dist/controllers/workflow-engine-scheduler.d.ts +25 -0
  45. package/dist/controllers/workflow-engine-scheduler.js +95 -0
  46. package/dist/controllers/workflow-engine-scheduler.js.map +1 -0
  47. package/dist/controllers/workflows.d.ts +27 -0
  48. package/dist/controllers/workflows.js +109 -0
  49. package/dist/controllers/workflows.js.map +1 -0
  50. package/dist/extension/controller-host.d.ts +47 -0
  51. package/dist/extension/controller-host.js +110 -0
  52. package/dist/extension/controller-host.js.map +1 -0
  53. package/dist/extension/executor.d.ts +3 -0
  54. package/dist/extension/executor.js +12 -2
  55. package/dist/extension/executor.js.map +1 -1
  56. package/dist/extension/index.d.ts +7 -0
  57. package/dist/extension/index.js +1118 -106
  58. package/dist/extension/index.js.map +1 -1
  59. package/dist/extension/recorder.d.ts +6 -0
  60. package/dist/extension/recorder.js +20 -8
  61. package/dist/extension/recorder.js.map +1 -1
  62. package/dist/extension/workflow-tool.d.ts +28 -0
  63. package/dist/extension/workflow-tool.js +33 -0
  64. package/dist/extension/workflow-tool.js.map +1 -0
  65. package/dist/host/processes.d.ts +24 -0
  66. package/dist/host/processes.js +114 -0
  67. package/dist/host/processes.js.map +1 -0
  68. package/dist/host/rpc-bridge.d.ts +9 -0
  69. package/dist/host/rpc-bridge.js +39 -0
  70. package/dist/host/rpc-bridge.js.map +1 -0
  71. package/dist/host/rpc-executor.d.ts +38 -0
  72. package/dist/host/rpc-executor.js +254 -0
  73. package/dist/host/rpc-executor.js.map +1 -0
  74. package/dist/host/runner.d.ts +50 -0
  75. package/dist/host/runner.js +379 -0
  76. package/dist/host/runner.js.map +1 -0
  77. package/dist/viewer/cli.d.ts +7 -3
  78. package/dist/viewer/cli.js +150 -19
  79. package/dist/viewer/cli.js.map +1 -1
  80. package/dist/workflows/catalog.d.ts +43 -0
  81. package/dist/workflows/catalog.js +79 -0
  82. package/dist/workflows/catalog.js.map +1 -0
  83. package/dist/workflows/engine.d.ts +37 -2
  84. package/dist/workflows/engine.js +297 -29
  85. package/dist/workflows/engine.js.map +1 -1
  86. package/dist/workflows/errors.d.ts +23 -0
  87. package/dist/workflows/errors.js +38 -0
  88. package/dist/workflows/errors.js.map +1 -1
  89. package/dist/workflows/graph.js +0 -5
  90. package/dist/workflows/graph.js.map +1 -1
  91. package/dist/workflows/index.d.ts +2 -2
  92. package/dist/workflows/index.js +1 -1
  93. package/dist/workflows/index.js.map +1 -1
  94. package/dist/workflows/loader.d.ts +19 -15
  95. package/dist/workflows/loader.js +63 -19
  96. package/dist/workflows/loader.js.map +1 -1
  97. package/dist/workflows/migrate-sources.d.ts +41 -0
  98. package/dist/workflows/migrate-sources.js +129 -0
  99. package/dist/workflows/migrate-sources.js.map +1 -0
  100. package/dist/workflows/schema.js +3 -2
  101. package/dist/workflows/schema.js.map +1 -1
  102. package/dist/workflows/store.d.ts +50 -6
  103. package/dist/workflows/store.js +448 -53
  104. package/dist/workflows/store.js.map +1 -1
  105. package/dist/workflows/types.d.ts +27 -3
  106. package/docs/CONTROLLERS.md +215 -0
  107. package/docs/development.md +14 -9
  108. package/docs/plans/2026-08-04-controller-runtime-plan.md +169 -0
  109. package/docs/plans/2026-08-05-always-on-workflows-plan.md +125 -0
  110. package/docs/plans/2026-08-10-agent-managed-monitor-workflows-plan.md +184 -0
  111. package/docs/plans/2026-08-12-coordinated-workflow-timeouts-plan.md +74 -0
  112. package/docs/plans/2026-08-13-built-in-workflow-catalog-plan.md +97 -0
  113. package/docs/run-bundles.md +85 -28
  114. package/docs/workflows.md +161 -17
  115. package/examples/controllers/pull-request.controller.ts +215 -0
  116. package/package.json +10 -2
  117. package/src/builtins/catalog.ts +22 -0
  118. package/src/builtins/monitor.workflow.ts +302 -0
  119. package/src/controllers/conditions.ts +110 -0
  120. package/src/controllers/definition.ts +65 -0
  121. package/src/controllers/effects.ts +123 -0
  122. package/src/controllers/errors.ts +27 -0
  123. package/src/controllers/index.ts +90 -0
  124. package/src/controllers/json.ts +62 -0
  125. package/src/controllers/loader.ts +104 -0
  126. package/src/controllers/manager.ts +533 -0
  127. package/src/controllers/results.ts +46 -0
  128. package/src/controllers/sqlite.ts +1545 -0
  129. package/src/controllers/store.ts +160 -0
  130. package/src/controllers/types.ts +183 -0
  131. package/src/controllers/workflow-engine-scheduler.ts +148 -0
  132. package/src/controllers/workflows.ts +152 -0
  133. package/src/extension/controller-host.ts +163 -0
  134. package/src/extension/executor.ts +13 -2
  135. package/src/extension/index.ts +1325 -130
  136. package/src/extension/recorder.ts +65 -36
  137. package/src/extension/workflow-tool.ts +59 -0
  138. package/src/host/processes.ts +119 -0
  139. package/src/host/rpc-bridge.ts +44 -0
  140. package/src/host/rpc-executor.ts +299 -0
  141. package/src/host/runner.ts +445 -0
  142. package/src/viewer/cli.ts +167 -21
  143. package/src/workflows/catalog.ts +135 -0
  144. package/src/workflows/engine.ts +404 -41
  145. package/src/workflows/errors.ts +45 -0
  146. package/src/workflows/graph.ts +0 -5
  147. package/src/workflows/index.ts +2 -0
  148. package/src/workflows/loader.ts +76 -22
  149. package/src/workflows/migrate-sources.ts +167 -0
  150. package/src/workflows/schema.ts +3 -2
  151. package/src/workflows/store.ts +557 -48
  152. package/src/workflows/types.ts +22 -3
@@ -0,0 +1,379 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ import { builtinWorkflowCatalog } from "../builtins/catalog.js";
5
+ import { ControllerManager, loadDiscoveredControllers, projectControllerStorePath, SqliteControllerStore, WorkflowEngineScheduler, } from "../controllers/index.js";
6
+ import { WorkflowEngine } from "../workflows/engine.js";
7
+ import { ClaimLostError, errorMessage, isClaimLostError, WorkflowSourceChangedError, } from "../workflows/errors.js";
8
+ import { resolveWorkflowRef, resolveWorkflowSource } from "../workflows/loader.js";
9
+ import { migrateLegacyWorkflowSources } from "../workflows/migrate-sources.js";
10
+ import { WorkflowRunStore, readRunBundle } from "../workflows/store.js";
11
+ import { HostProcessRegistry } from "./processes.js";
12
+ import { RpcStepExecutor } from "./rpc-executor.js";
13
+ const CLAIM_POLL_MS = 2_000;
14
+ const RUN_CLAIM_LEASE_MS = 30_000;
15
+ const RUN_CLAIM_RENEW_MS = 10_000;
16
+ /**
17
+ * The always-on runner: claims parked workflow runs and reconciles durable
18
+ * controllers without a Pi session. Conversation nodes execute in headless
19
+ * `pi --mode rpc` children. Everything the host does is recoverable: claims
20
+ * expire, bundles fence stale writers, and child processes are reaped by
21
+ * the next host.
22
+ */
23
+ export class WorkflowHost {
24
+ options;
25
+ runnerId;
26
+ registry;
27
+ store;
28
+ childRunStore;
29
+ manager = null;
30
+ pollTimer = null;
31
+ activeRuns = new Map();
32
+ migrationBlockedRuns = new Set();
33
+ schedulerExecutors = new Map();
34
+ /** Runs whose resume refused (edited source); skipped until a host restart. */
35
+ skippedRuns = new Set();
36
+ stopping = false;
37
+ stateDir;
38
+ constructor(options) {
39
+ this.options = options;
40
+ this.runnerId = options.runnerId ?? `host-${randomUUID().slice(0, 8)}`;
41
+ this.store = new SqliteControllerStore(options.storeFile ?? projectControllerStorePath(options.cwd));
42
+ this.stateDir = path.dirname(this.store.filePath);
43
+ this.registry = options.registry ?? new HostProcessRegistry(this.stateDir);
44
+ this.childRunStore = new WorkflowRunStore(options.runsDir ?? process.env.PI_WORKFLOWS_RUNS_DIR ?? undefined);
45
+ }
46
+ log(message) {
47
+ this.options.onLog?.(message);
48
+ }
49
+ /** Take the advisory lock, reap orphans, and start claiming. */
50
+ async start() {
51
+ // The lock comes first: a second host must refuse before touching the
52
+ // children registry, or it would kill the live host's child processes
53
+ // as supposed orphans.
54
+ acquireHostLock(this.stateDir, this.runnerId, this.options.cwd);
55
+ const reaped = this.registry.reapOrphans();
56
+ if (reaped.length > 0) {
57
+ this.log(`reaped ${reaped.length} orphaned headless session(s): ${reaped.join(", ")}`);
58
+ }
59
+ const migration = await migrateLegacyWorkflowSources({
60
+ catalog: builtinWorkflowCatalog,
61
+ store: this.childRunStore,
62
+ queue: this.store,
63
+ });
64
+ for (const blocked of migration.blocked) {
65
+ this.migrationBlockedRuns.add(blocked.runId);
66
+ this.log(`run ${blocked.runId} parked: ${blocked.reason}`);
67
+ }
68
+ const definitions = await loadDiscoveredControllers({ cwd: this.options.cwd });
69
+ if (definitions.length > 0) {
70
+ const scheduler = new WorkflowEngineScheduler({
71
+ store: this.childRunStore,
72
+ resolveWorkflow: async (name) => {
73
+ const resolved = await resolveWorkflowRef(name, { cwd: this.options.cwd }, builtinWorkflowCatalog);
74
+ return { workflow: resolved.definition, workflowSource: resolved.source };
75
+ },
76
+ createEngine: () => {
77
+ const executor = new RpcStepExecutor({
78
+ cwd: this.options.cwd,
79
+ registry: this.registry,
80
+ ...(this.options.piArgs !== undefined ? { piArgs: this.options.piArgs } : {}),
81
+ ...(this.options.env !== undefined ? { env: this.options.env } : {}),
82
+ });
83
+ const engine = new WorkflowEngine({ executor, store: this.childRunStore });
84
+ this.schedulerExecutors.set(engine, executor);
85
+ return engine;
86
+ },
87
+ disposeEngine: async (engine) => {
88
+ const executor = this.schedulerExecutors.get(engine);
89
+ if (executor !== undefined) {
90
+ this.schedulerExecutors.delete(engine);
91
+ await executor.close();
92
+ }
93
+ },
94
+ });
95
+ this.manager = new ControllerManager({
96
+ store: this.store,
97
+ controllers: definitions,
98
+ workflowScheduler: scheduler,
99
+ });
100
+ this.manager.start();
101
+ this.log(`controller workers started for ${definitions.length} controller(s)`);
102
+ }
103
+ this.pollTimer = setInterval(() => {
104
+ this.claimOnce();
105
+ }, this.options.claimPollMs ?? CLAIM_POLL_MS);
106
+ this.pollTimer.unref?.();
107
+ this.claimOnce();
108
+ this.log(`host ${this.runnerId} watching ${this.options.cwd}`);
109
+ }
110
+ /** Drain: stop claiming, park in-flight runs, stop controllers, kill children. */
111
+ async stop() {
112
+ if (this.stopping) {
113
+ return;
114
+ }
115
+ this.stopping = true;
116
+ if (this.pollTimer !== null) {
117
+ clearInterval(this.pollTimer);
118
+ this.pollTimer = null;
119
+ }
120
+ const pending = [...this.activeRuns.values()];
121
+ for (const run of this.parkedEngines.splice(0)) {
122
+ run();
123
+ }
124
+ await Promise.allSettled(pending);
125
+ await this.manager?.stop().catch(() => undefined);
126
+ for (const executor of this.schedulerExecutors.values()) {
127
+ await executor.close().catch(() => undefined);
128
+ }
129
+ this.schedulerExecutors.clear();
130
+ this.registry.killAll();
131
+ releaseHostLock(this.stateDir, this.runnerId);
132
+ this.store.close();
133
+ }
134
+ parkedEngines = [];
135
+ claimOnce() {
136
+ if (this.stopping || this.activeRuns.size > 0) {
137
+ return;
138
+ }
139
+ let claimed;
140
+ try {
141
+ claimed = this.store.claimNextWorkflowRun({
142
+ runnerId: this.runnerId,
143
+ claimToken: randomUUID(),
144
+ leaseMs: RUN_CLAIM_LEASE_MS,
145
+ excludeRunIds: [...this.skippedRuns, ...this.migrationBlockedRuns],
146
+ });
147
+ }
148
+ catch (error) {
149
+ // Store contention or corruption must not kill the host's loop.
150
+ this.log(`claim failed, retrying shortly: ${errorMessage(error)}`);
151
+ return;
152
+ }
153
+ if (claimed === undefined) {
154
+ return;
155
+ }
156
+ const task = this.runClaimed(claimed).finally(() => {
157
+ this.activeRuns.delete(claimed.runId);
158
+ });
159
+ this.activeRuns.set(claimed.runId, task);
160
+ }
161
+ async runClaimed(record) {
162
+ const claimToken = record.claimToken;
163
+ const runId = record.runId;
164
+ this.log(`resuming ${record.workflowName} run ${runId}`);
165
+ let workflow;
166
+ let workflowSource;
167
+ try {
168
+ const bundle = await readRunBundle(this.childRunStore.runDirFor(runId));
169
+ if (bundle?.state.workflowSource === undefined) {
170
+ throw new Error(`Workflow run ${runId} has no canonical workflow source`);
171
+ }
172
+ workflow = await resolveWorkflowSource(bundle.state.workflowSource, builtinWorkflowCatalog, runId);
173
+ workflowSource = bundle.state.workflowSource;
174
+ }
175
+ catch (error) {
176
+ if (error instanceof WorkflowSourceChangedError) {
177
+ try {
178
+ this.store.parkWorkflowRun({ runId, claimToken });
179
+ }
180
+ catch {
181
+ // Best-effort.
182
+ }
183
+ this.skippedRuns.add(runId);
184
+ this.recordEvent(runId, record.workflowName, "parked", {
185
+ reason: "workflow source changed",
186
+ });
187
+ this.log(`run ${runId} skipped: workflow source changed; install the matching package revision, then restart the host`);
188
+ return;
189
+ }
190
+ await this.failUnresumable(record, claimToken, errorMessage(error));
191
+ return;
192
+ }
193
+ if (this.stopping) {
194
+ // The drain started during setup: park before anything executes.
195
+ try {
196
+ this.store.parkWorkflowRun({ runId, claimToken });
197
+ }
198
+ catch {
199
+ // Best-effort.
200
+ }
201
+ return;
202
+ }
203
+ const store = this.store;
204
+ const fence = () => {
205
+ if (!store.verifyWorkflowRunClaim({ runId, claimToken })) {
206
+ throw new ClaimLostError(runId);
207
+ }
208
+ };
209
+ const fencedStore = new WorkflowRunStore(this.childRunStore.outputRoot, {
210
+ fenceProvider: () => fence,
211
+ });
212
+ const executor = new RpcStepExecutor({
213
+ cwd: this.options.cwd,
214
+ registry: this.registry,
215
+ ...(this.options.piArgs !== undefined ? { piArgs: this.options.piArgs } : {}),
216
+ ...(this.options.env !== undefined ? { env: this.options.env } : {}),
217
+ });
218
+ const engine = new WorkflowEngine({ executor, store: fencedStore });
219
+ const parkEngine = () => engine.park();
220
+ this.parkedEngines.push(parkEngine);
221
+ const renewTimer = setInterval(() => {
222
+ try {
223
+ if (!store.renewWorkflowRunClaim({ runId, claimToken, leaseMs: RUN_CLAIM_LEASE_MS })) {
224
+ engine.cancel();
225
+ }
226
+ }
227
+ catch {
228
+ // Transient store errors leave fencing to decide ownership.
229
+ }
230
+ }, RUN_CLAIM_RENEW_MS);
231
+ renewTimer.unref?.();
232
+ this.recordEvent(runId, record.workflowName, "resumed", { runnerId: this.runnerId });
233
+ try {
234
+ const result = await engine.resumeRun(workflow, runId, { workflowSource });
235
+ clearInterval(renewTimer);
236
+ if (result.state.status === "running") {
237
+ // Parked again mid-drain: leave it claimable for the next runner.
238
+ this.store.parkWorkflowRun({ runId, claimToken });
239
+ this.recordEvent(runId, record.workflowName, "parked", {});
240
+ this.log(`parked ${record.workflowName} run ${runId}`);
241
+ return;
242
+ }
243
+ this.store.completeWorkflowRun({ runId, claimToken });
244
+ this.recordEvent(runId, record.workflowName, result.state.status, {
245
+ ...(result.state.error !== undefined ? { error: result.state.error } : {}),
246
+ ...(result.state.waitingOn !== undefined ? { waitingOn: result.state.waitingOn } : {}),
247
+ });
248
+ this.log(`${record.workflowName} run ${runId} ${result.state.status}`);
249
+ }
250
+ catch (error) {
251
+ clearInterval(renewTimer);
252
+ if (isClaimLostError(error)) {
253
+ this.log(`run ${runId} continues under another runner`);
254
+ return;
255
+ }
256
+ if (error instanceof WorkflowSourceChangedError) {
257
+ // Edited source is a refusal, not a failure: keep the run claimable
258
+ // for a later fix, and stop spinning on it for this host's lifetime.
259
+ try {
260
+ this.store.parkWorkflowRun({ runId, claimToken });
261
+ }
262
+ catch {
263
+ // Best-effort.
264
+ }
265
+ this.skippedRuns.add(runId);
266
+ this.recordEvent(runId, record.workflowName, "parked", {
267
+ reason: "workflow source changed",
268
+ });
269
+ this.log(`run ${runId} skipped: workflow source changed; revert or force-resume it, then restart the host`);
270
+ return;
271
+ }
272
+ await this.failUnresumable(record, claimToken, errorMessage(error));
273
+ }
274
+ finally {
275
+ const index = this.parkedEngines.indexOf(parkEngine);
276
+ if (index !== -1) {
277
+ this.parkedEngines.splice(index, 1);
278
+ }
279
+ await executor.close().catch(() => undefined);
280
+ }
281
+ }
282
+ async failUnresumable(record, claimToken, message) {
283
+ let actualStatus;
284
+ let heldClaim = false;
285
+ try {
286
+ // The interruption write obeys the same fencing rule as every other
287
+ // bundle write: without a live claim, the current owner decides.
288
+ heldClaim = this.store.verifyWorkflowRunClaim({ runId: record.runId, claimToken });
289
+ if (heldClaim) {
290
+ const bundle = await this.childRunStore.markRunInterrupted(record.runId, message);
291
+ actualStatus = bundle?.state.status;
292
+ }
293
+ }
294
+ catch {
295
+ // The bundle may be unreadable; the queue row still needs closure.
296
+ }
297
+ try {
298
+ this.store.completeWorkflowRun({ runId: record.runId, claimToken });
299
+ }
300
+ catch {
301
+ // Best-effort.
302
+ }
303
+ if (!heldClaim) {
304
+ // Another runner owns the run; its owner reports from here.
305
+ this.log(`run ${record.runId} continues under another runner`);
306
+ return;
307
+ }
308
+ // Report the bundle's real terminal state when the interruption was a
309
+ // no-op (the bundle was already waiting or completed), so the feed
310
+ // stays truthful for sessions syncing from it.
311
+ if (actualStatus !== undefined && actualStatus !== "failed") {
312
+ this.recordEvent(record.runId, record.workflowName, actualStatus, {});
313
+ }
314
+ else {
315
+ this.recordEvent(record.runId, record.workflowName, "failed", { error: message });
316
+ }
317
+ this.log(`run ${record.runId} cannot resume: ${message}`);
318
+ }
319
+ recordEvent(runId, workflowRef, type, payload) {
320
+ try {
321
+ this.store.recordRunEvent({ runId, workflowRef, type, payload, runnerId: this.runnerId });
322
+ }
323
+ catch {
324
+ // The event feed is best-effort.
325
+ }
326
+ }
327
+ }
328
+ function hostLockPath(stateDir) {
329
+ return path.join(stateDir, "host.lock");
330
+ }
331
+ /**
332
+ * The advisory lock guards host-versus-host only: the embedded runner in a
333
+ * Pi session does not take it. A second host refuses to start while the
334
+ * recorded PID is alive.
335
+ */
336
+ function acquireHostLock(stateDir, runnerId, cwd) {
337
+ const lockPath = hostLockPath(stateDir);
338
+ const existing = readLock(lockPath);
339
+ if (existing !== null && existing.runnerId !== runnerId && isAlive(existing.pid)) {
340
+ throw new Error(`Another workflow host (pid ${existing.pid}, ${existing.runnerId}) is already running for ${cwd}`);
341
+ }
342
+ fs.mkdirSync(path.dirname(lockPath), { recursive: true, mode: 0o700 });
343
+ fs.writeFileSync(lockPath, `${JSON.stringify({ pid: process.pid, runnerId, startedAt: new Date().toISOString() })}\n`, { encoding: "utf8", mode: 0o600 });
344
+ }
345
+ function releaseHostLock(stateDir, runnerId) {
346
+ const lockPath = hostLockPath(stateDir);
347
+ const existing = readLock(lockPath);
348
+ if (existing?.runnerId !== runnerId) {
349
+ return;
350
+ }
351
+ try {
352
+ fs.rmSync(lockPath);
353
+ }
354
+ catch {
355
+ // Already gone.
356
+ }
357
+ }
358
+ function readLock(lockPath) {
359
+ try {
360
+ const parsed = JSON.parse(fs.readFileSync(lockPath, "utf8"));
361
+ if (typeof parsed.pid !== "number" || typeof parsed.runnerId !== "string") {
362
+ return null;
363
+ }
364
+ return { pid: parsed.pid, runnerId: parsed.runnerId };
365
+ }
366
+ catch {
367
+ return null;
368
+ }
369
+ }
370
+ function isAlive(pid) {
371
+ try {
372
+ process.kill(pid, 0);
373
+ return true;
374
+ }
375
+ catch {
376
+ return false;
377
+ }
378
+ }
379
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../../src/host/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,EACL,iBAAiB,EACjB,yBAAyB,EACzB,0BAA0B,EAC1B,qBAAqB,EACrB,uBAAuB,GAExB,MAAM,yBAAyB,CAAC;AAEjC,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,EACL,cAAc,EACd,YAAY,EACZ,gBAAgB,EAChB,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EAAE,kBAAkB,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AACnF,OAAO,EAAE,4BAA4B,EAAE,MAAM,iCAAiC,CAAC;AAC/E,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAExE,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAEpD,MAAM,aAAa,GAAG,KAAK,CAAC;AAC5B,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAkBlC;;;;;;GAMG;AACH,MAAM,OAAO,YAAY;IACN,OAAO,CAAsB;IAC7B,QAAQ,CAAS;IACjB,QAAQ,CAAsB;IAC9B,KAAK,CAAwB;IAC7B,aAAa,CAAmB;IACzC,OAAO,GAA6B,IAAI,CAAC;IACzC,SAAS,GAA0C,IAAI,CAAC;IAC/C,UAAU,GAAG,IAAI,GAAG,EAAyB,CAAC;IAC9C,oBAAoB,GAAG,IAAI,GAAG,EAAU,CAAC;IACzC,kBAAkB,GAAG,IAAI,GAAG,EAAmC,CAAC;IACjF,+EAA+E;IAC9D,WAAW,GAAG,IAAI,GAAG,EAAU,CAAC;IACzC,QAAQ,GAAG,KAAK,CAAC;IAER,QAAQ,CAAS;IAElC,YAAY,OAA4B;QACtC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;QACvB,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,QAAQ,UAAU,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC;QACvE,IAAI,CAAC,KAAK,GAAG,IAAI,qBAAqB,CACpC,OAAO,CAAC,SAAS,IAAI,0BAA0B,CAAC,OAAO,CAAC,GAAG,CAAC,CAC7D,CAAC;QACF,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QAClD,IAAI,CAAC,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,IAAI,mBAAmB,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC3E,IAAI,CAAC,aAAa,GAAG,IAAI,gBAAgB,CACvC,OAAO,CAAC,OAAO,IAAI,OAAO,CAAC,GAAG,CAAC,qBAAqB,IAAI,SAAS,CAClE,CAAC;IACJ,CAAC;IAEO,GAAG,CAAC,OAAe;QACzB,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IAED,gEAAgE;IAChE,KAAK,CAAC,KAAK;QACT,sEAAsE;QACtE,sEAAsE;QACtE,uBAAuB;QACvB,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QAChE,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAC;QAC3C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YACtB,IAAI,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,MAAM,kCAAkC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACzF,CAAC;QAED,MAAM,SAAS,GAAG,MAAM,4BAA4B,CAAC;YACnD,OAAO,EAAE,sBAAsB;YAC/B,KAAK,EAAE,IAAI,CAAC,aAAa;YACzB,KAAK,EAAE,IAAI,CAAC,KAAK;SAClB,CAAC,CAAC;QACH,KAAK,MAAM,OAAO,IAAI,SAAS,CAAC,OAAO,EAAE,CAAC;YACxC,IAAI,CAAC,oBAAoB,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC7C,IAAI,CAAC,GAAG,CAAC,OAAO,OAAO,CAAC,KAAK,YAAY,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;QAC7D,CAAC;QAED,MAAM,WAAW,GAAG,MAAM,yBAAyB,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAC/E,IAAI,WAAW,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC3B,MAAM,SAAS,GAAG,IAAI,uBAAuB,CAAC;gBAC5C,KAAK,EAAE,IAAI,CAAC,aAAa;gBACzB,eAAe,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;oBAC9B,MAAM,QAAQ,GAAG,MAAM,kBAAkB,CACvC,IAAI,EACJ,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,EACzB,sBAAsB,CACvB,CAAC;oBACF,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,UAAU,EAAE,cAAc,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;gBAC5E,CAAC;gBACD,YAAY,EAAE,GAAG,EAAE;oBACjB,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC;wBACnC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG;wBACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;wBACvB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBAC7E,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;qBACrE,CAAC,CAAC;oBACH,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,CAAC,aAAa,EAAE,CAAC,CAAC;oBAC3E,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;oBAC9C,OAAO,MAAM,CAAC;gBAChB,CAAC;gBACD,aAAa,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE;oBAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,kBAAkB,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;oBACrD,IAAI,QAAQ,KAAK,SAAS,EAAE,CAAC;wBAC3B,IAAI,CAAC,kBAAkB,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;wBACvC,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC;oBACzB,CAAC;gBACH,CAAC;aACF,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,GAAG,IAAI,iBAAiB,CAAC;gBACnC,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,WAAW,EAAE,WAAW;gBACxB,iBAAiB,EAAE,SAAS;aAC7B,CAAC,CAAC;YACH,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;YACrB,IAAI,CAAC,GAAG,CAAC,kCAAkC,WAAW,CAAC,MAAM,gBAAgB,CAAC,CAAC;QACjF,CAAC;QAED,IAAI,CAAC,SAAS,GAAG,WAAW,CAAC,GAAG,EAAE;YAChC,IAAI,CAAC,SAAS,EAAE,CAAC;QACnB,CAAC,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW,IAAI,aAAa,CAAC,CAAC;QAC9C,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,EAAE,CAAC;QACzB,IAAI,CAAC,SAAS,EAAE,CAAC;QACjB,IAAI,CAAC,GAAG,CAAC,QAAQ,IAAI,CAAC,QAAQ,aAAa,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IACjE,CAAC;IAED,kFAAkF;IAClF,KAAK,CAAC,IAAI;QACR,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,OAAO;QACT,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC;QACrB,IAAI,IAAI,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC;YAC5B,aAAa,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;YAC9B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACxB,CAAC;QACD,MAAM,OAAO,GAAG,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;QAC9C,KAAK,MAAM,GAAG,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;YAC/C,GAAG,EAAE,CAAC;QACR,CAAC;QACD,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;QAClC,MAAM,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAClD,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,kBAAkB,CAAC,MAAM,EAAE,EAAE,CAAC;YACxD,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChD,CAAC;QACD,IAAI,CAAC,kBAAkB,CAAC,KAAK,EAAE,CAAC;QAChC,IAAI,CAAC,QAAQ,CAAC,OAAO,EAAE,CAAC;QACxB,eAAe,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC;QAC9C,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAEgB,aAAa,GAAsB,EAAE,CAAC;IAE/C,SAAS;QACf,IAAI,IAAI,CAAC,QAAQ,IAAI,IAAI,CAAC,UAAU,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YAC9C,OAAO;QACT,CAAC;QACD,IAAI,OAA2C,CAAC;QAChD,IAAI,CAAC;YACH,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,oBAAoB,CAAC;gBACxC,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,UAAU,EAAE,UAAU,EAAE;gBACxB,OAAO,EAAE,kBAAkB;gBAC3B,aAAa,EAAE,CAAC,GAAG,IAAI,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,oBAAoB,CAAC;aACnE,CAAC,CAAC;QACL,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,gEAAgE;YAChE,IAAI,CAAC,GAAG,CAAC,mCAAmC,YAAY,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YACnE,OAAO;QACT,CAAC;QACD,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YAC1B,OAAO;QACT,CAAC;QACD,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;YACjD,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;QACxC,CAAC,CAAC,CAAC;QACH,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,OAAO,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC3C,CAAC;IAEO,KAAK,CAAC,UAAU,CAAC,MAA8B;QACrD,MAAM,UAAU,GAAG,MAAM,CAAC,UAAoB,CAAC;QAC/C,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC;QAC3B,IAAI,CAAC,GAAG,CAAC,YAAY,MAAM,CAAC,YAAY,QAAQ,KAAK,EAAE,CAAC,CAAC;QACzD,IAAI,QAA4B,CAAC;QACjC,IAAI,cAA8D,CAAC;QACnE,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,CAAC,aAAa,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;YACxE,IAAI,MAAM,EAAE,KAAK,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;gBAC/C,MAAM,IAAI,KAAK,CAAC,gBAAgB,KAAK,mCAAmC,CAAC,CAAC;YAC5E,CAAC;YACD,QAAQ,GAAG,MAAM,qBAAqB,CACpC,MAAM,CAAC,KAAK,CAAC,cAAc,EAC3B,sBAAsB,EACtB,KAAK,CACN,CAAC;YACF,cAAc,GAAG,MAAM,CAAC,KAAK,CAAC,cAAc,CAAC;QAC/C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,KAAK,YAAY,0BAA0B,EAAE,CAAC;gBAChD,IAAI,CAAC;oBACH,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;gBACpD,CAAC;gBAAC,MAAM,CAAC;oBACP,eAAe;gBACjB,CAAC;gBACD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBAC5B,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,QAAQ,EAAE;oBACrD,MAAM,EAAE,yBAAyB;iBAClC,CAAC,CAAC;gBACH,IAAI,CAAC,GAAG,CACN,OAAO,KAAK,iGAAiG,CAC9G,CAAC;gBACF,OAAO;YACT,CAAC;YACD,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;YACpE,OAAO;QACT,CAAC;QACD,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;YAClB,iEAAiE;YACjE,IAAI,CAAC;gBACH,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;YACpD,CAAC;YAAC,MAAM,CAAC;gBACP,eAAe;YACjB,CAAC;YACD,OAAO;QACT,CAAC;QAED,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC;QACzB,MAAM,KAAK,GAAG,GAAG,EAAE;YACjB,IAAI,CAAC,KAAK,CAAC,sBAAsB,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,EAAE,CAAC;gBACzD,MAAM,IAAI,cAAc,CAAC,KAAK,CAAC,CAAC;YAClC,CAAC;QACH,CAAC,CAAC;QACF,MAAM,WAAW,GAAG,IAAI,gBAAgB,CAAC,IAAI,CAAC,aAAa,CAAC,UAAU,EAAE;YACtE,aAAa,EAAE,GAAG,EAAE,CAAC,KAAK;SAC3B,CAAC,CAAC;QACH,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC;YACnC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG;YACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YAC7E,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SACrE,CAAC,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,cAAc,CAAC,EAAE,QAAQ,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,CAAC;QACpE,MAAM,UAAU,GAAG,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC;QACvC,IAAI,CAAC,aAAa,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAEpC,MAAM,UAAU,GAAG,WAAW,CAAC,GAAG,EAAE;YAClC,IAAI,CAAC;gBACH,IAAI,CAAC,KAAK,CAAC,qBAAqB,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,EAAE,CAAC;oBACrF,MAAM,CAAC,MAAM,EAAE,CAAC;gBAClB,CAAC;YACH,CAAC;YAAC,MAAM,CAAC;gBACP,4DAA4D;YAC9D,CAAC;QACH,CAAC,EAAE,kBAAkB,CAAC,CAAC;QACvB,UAAU,CAAC,KAAK,EAAE,EAAE,CAAC;QAErB,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,SAAS,EAAE,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QACrF,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,SAAS,CAAC,QAAQ,EAAE,KAAK,EAAE,EAAE,cAAc,EAAE,CAAC,CAAC;YAC3E,aAAa,CAAC,UAAU,CAAC,CAAC;YAC1B,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBACtC,kEAAkE;gBAClE,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;gBAClD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC;gBAC3D,IAAI,CAAC,GAAG,CAAC,UAAU,MAAM,CAAC,YAAY,QAAQ,KAAK,EAAE,CAAC,CAAC;gBACvD,OAAO;YACT,CAAC;YACD,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;YACtD,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE;gBAChE,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;gBAC1E,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;aACvF,CAAC,CAAC;YACH,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,YAAY,QAAQ,KAAK,IAAI,MAAM,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;QACzE,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,aAAa,CAAC,UAAU,CAAC,CAAC;YAC1B,IAAI,gBAAgB,CAAC,KAAK,CAAC,EAAE,CAAC;gBAC5B,IAAI,CAAC,GAAG,CAAC,OAAO,KAAK,iCAAiC,CAAC,CAAC;gBACxD,OAAO;YACT,CAAC;YACD,IAAI,KAAK,YAAY,0BAA0B,EAAE,CAAC;gBAChD,oEAAoE;gBACpE,qEAAqE;gBACrE,IAAI,CAAC;oBACH,IAAI,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;gBACpD,CAAC;gBAAC,MAAM,CAAC;oBACP,eAAe;gBACjB,CAAC;gBACD,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;gBAC5B,IAAI,CAAC,WAAW,CAAC,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,QAAQ,EAAE;oBACrD,MAAM,EAAE,yBAAyB;iBAClC,CAAC,CAAC;gBACH,IAAI,CAAC,GAAG,CACN,OAAO,KAAK,qFAAqF,CAClG,CAAC;gBACF,OAAO;YACT,CAAC;YACD,MAAM,IAAI,CAAC,eAAe,CAAC,MAAM,EAAE,UAAU,EAAE,YAAY,CAAC,KAAK,CAAC,CAAC,CAAC;QACtE,CAAC;gBAAS,CAAC;YACT,MAAM,KAAK,GAAG,IAAI,CAAC,aAAa,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;YACrD,IAAI,KAAK,KAAK,CAAC,CAAC,EAAE,CAAC;gBACjB,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YACtC,CAAC;YACD,MAAM,QAAQ,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAChD,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,eAAe,CAC3B,MAA8B,EAC9B,UAAkB,EAClB,OAAe;QAEf,IAAI,YAAgC,CAAC;QACrC,IAAI,SAAS,GAAG,KAAK,CAAC;QACtB,IAAI,CAAC;YACH,oEAAoE;YACpE,iEAAiE;YACjE,SAAS,GAAG,IAAI,CAAC,KAAK,CAAC,sBAAsB,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;YACnF,IAAI,SAAS,EAAE,CAAC;gBACd,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,aAAa,CAAC,kBAAkB,CAAC,MAAM,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;gBAClF,YAAY,GAAG,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC;YACtC,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,mEAAmE;QACrE,CAAC;QACD,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,mBAAmB,CAAC,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,CAAC,CAAC;QACtE,CAAC;QAAC,MAAM,CAAC;YACP,eAAe;QACjB,CAAC;QACD,IAAI,CAAC,SAAS,EAAE,CAAC;YACf,4DAA4D;YAC5D,IAAI,CAAC,GAAG,CAAC,OAAO,MAAM,CAAC,KAAK,iCAAiC,CAAC,CAAC;YAC/D,OAAO;QACT,CAAC;QACD,sEAAsE;QACtE,mEAAmE;QACnE,+CAA+C;QAC/C,IAAI,YAAY,KAAK,SAAS,IAAI,YAAY,KAAK,QAAQ,EAAE,CAAC;YAC5D,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,YAAY,EAAE,EAAE,CAAC,CAAC;QACxE,CAAC;aAAM,CAAC;YACN,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,YAAY,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QACpF,CAAC;QACD,IAAI,CAAC,GAAG,CAAC,OAAO,MAAM,CAAC,KAAK,mBAAmB,OAAO,EAAE,CAAC,CAAC;IAC5D,CAAC;IAEO,WAAW,CAAC,KAAa,EAAE,WAAmB,EAAE,IAAY,EAAE,OAAmB;QACvF,IAAI,CAAC;YACH,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;QAC5F,CAAC;QAAC,MAAM,CAAC;YACP,iCAAiC;QACnC,CAAC;IACH,CAAC;CACF;AAED,SAAS,YAAY,CAAC,QAAgB;IACpC,OAAO,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC;AAC1C,CAAC;AAED;;;;GAIG;AACH,SAAS,eAAe,CAAC,QAAgB,EAAE,QAAgB,EAAE,GAAW;IACtE,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACpC,IAAI,QAAQ,KAAK,IAAI,IAAI,QAAQ,CAAC,QAAQ,KAAK,QAAQ,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,EAAE,CAAC;QACjF,MAAM,IAAI,KAAK,CACb,8BAA8B,QAAQ,CAAC,GAAG,KAAK,QAAQ,CAAC,QAAQ,4BAA4B,GAAG,EAAE,CAClG,CAAC;IACJ,CAAC;IACD,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACvE,EAAE,CAAC,aAAa,CACd,QAAQ,EACR,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,GAAG,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC,IAAI,EAC1F,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAClC,CAAC;AACJ,CAAC;AAED,SAAS,eAAe,CAAC,QAAgB,EAAE,QAAgB;IACzD,MAAM,QAAQ,GAAG,YAAY,CAAC,QAAQ,CAAC,CAAC;IACxC,MAAM,QAAQ,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACpC,IAAI,QAAQ,EAAE,QAAQ,KAAK,QAAQ,EAAE,CAAC;QACpC,OAAO;IACT,CAAC;IACD,IAAI,CAAC;QACH,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,gBAAgB;IAClB,CAAC;AACH,CAAC;AAED,SAAS,QAAQ,CAAC,QAAgB;IAChC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAG1D,CAAC;QACF,IAAI,OAAO,MAAM,CAAC,GAAG,KAAK,QAAQ,IAAI,OAAO,MAAM,CAAC,QAAQ,KAAK,QAAQ,EAAE,CAAC;YAC1E,OAAO,IAAI,CAAC;QACd,CAAC;QACD,OAAO,EAAE,GAAG,EAAE,MAAM,CAAC,GAAG,EAAE,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,CAAC;IACxD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,SAAS,OAAO,CAAC,GAAW;IAC1B,IAAI,CAAC;QACH,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC;QACrB,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -1,10 +1,14 @@
1
1
  #!/usr/bin/env node
2
- type CliArgs = {
2
+ export type CliArgs = {
3
3
  command: string;
4
- runId?: string | undefined;
4
+ runId?: string;
5
+ controllerName?: string;
6
+ resourceKey?: string;
5
7
  dir: string;
8
+ controllerDir: string;
6
9
  once: boolean;
10
+ project?: string | undefined;
11
+ piArgs?: string[] | undefined;
7
12
  };
8
13
  export declare function parseCliArgs(argv: string[]): CliArgs;
9
14
  export declare function main(argv?: string[]): Promise<number>;
10
- export {};
@@ -1,53 +1,97 @@
1
1
  #!/usr/bin/env node
2
- import { realpathSync } from "node:fs";
2
+ import fs, { realpathSync } from "node:fs";
3
+ import path from "node:path";
3
4
  import { pathToFileURL } from "node:url";
5
+ import { SqliteControllerStore } from "../controllers/sqlite.js";
6
+ import { projectControllerStoreBaseDir } from "../controllers/store.js";
4
7
  import { sanitizeText } from "../render/ansi.js";
5
8
  import { listRunBundles, readRunBundle, workflowRunsBaseDir } from "../workflows/store.js";
6
9
  import { formatDuration, renderRunDetailLines, renderRunListLines, runElapsedMs, statusLabel, } from "./render.js";
7
10
  import { runViewer } from "./tui.js";
8
- const USAGE = `pi-workflows — live terminal viewer for pi workflow runs
11
+ const USAGE = `pi-workflows — workflow runs and controller resources
9
12
 
10
13
  Usage:
11
14
  pi-workflows view [runId] [--dir <runsDir>] [--once]
12
15
  pi-workflows runs [--dir <runsDir>]
16
+ pi-workflows controllers [--controller-dir <dir>]
17
+ pi-workflows controller <controller> <key> [--controller-dir <dir>]
18
+ pi-workflows host [--project <dir>] [-- <extra pi args>]
13
19
 
14
20
  Commands:
15
- view Open the live TUI viewer. With --once, print a snapshot and exit.
16
- runs List recent workflow runs.
21
+ view Open the live workflow TUI. With --once, print a snapshot.
22
+ runs List recent workflow runs.
23
+ controllers List durable controller resources.
24
+ controller Show one resource, its effects, child workflows, and events.
25
+ host Run the always-on workflow host in the foreground.
17
26
 
18
27
  Options:
19
- --dir <runsDir> Runs directory (default: ~/.pi/agent/workflows/runs)
20
- --once Render once to stdout without the interactive TUI
28
+ --dir <runsDir> Runs directory (default: ~/.pi/agent/workflows/runs)
29
+ --controller-dir <dir> Controller directory (default: project-scoped local store)
30
+ --once Render once without the interactive TUI
31
+ --project <dir> Project directory for the host (default: cwd)
21
32
  `;
22
33
  export function parseCliArgs(argv) {
23
34
  const args = [...argv];
24
35
  const command = args[0] && !args[0].startsWith("-") ? args.shift() : "view";
25
36
  let dir = workflowRunsBaseDir();
37
+ let controllerDir = projectControllerStoreBaseDir(process.cwd());
26
38
  let once = false;
27
- let runId;
39
+ const positionals = [];
40
+ let project;
41
+ const piArgs = [];
28
42
  while (args.length > 0) {
29
43
  const arg = args.shift();
30
44
  if (arg === "--dir") {
31
- const value = args.shift();
32
- if (!value) {
33
- throw new Error("--dir requires a path");
34
- }
35
- dir = value;
45
+ dir = requiredValue(args, "--dir");
46
+ }
47
+ else if (arg === "--controller-dir") {
48
+ controllerDir = requiredValue(args, "--controller-dir");
49
+ }
50
+ else if (arg === "--project") {
51
+ project = requiredValue(args, "--project");
36
52
  }
37
53
  else if (arg === "--once") {
38
54
  once = true;
39
55
  }
40
56
  else if (arg === "--help" || arg === "-h") {
41
- return { command: "help", dir, once };
57
+ return { command: "help", dir, controllerDir, once };
42
58
  }
43
- else if (!arg.startsWith("-") && runId === undefined) {
44
- runId = arg;
59
+ else if (arg === "--") {
60
+ piArgs.push(...args.splice(0));
45
61
  }
46
- else {
62
+ else if (arg.startsWith("-")) {
47
63
  throw new Error(`Unknown argument: ${arg}`);
48
64
  }
65
+ else {
66
+ positionals.push(arg);
67
+ }
68
+ }
69
+ if (command === "host") {
70
+ return { command, dir, controllerDir, once, project, piArgs };
71
+ }
72
+ if (command === "controller") {
73
+ if (positionals.length !== 2) {
74
+ throw new Error("controller requires <controller> and <key>");
75
+ }
76
+ return {
77
+ command,
78
+ controllerName: positionals[0],
79
+ resourceKey: positionals[1],
80
+ dir,
81
+ controllerDir,
82
+ once,
83
+ };
49
84
  }
50
- return { command, runId, dir, once };
85
+ if (positionals.length > 1) {
86
+ throw new Error(`Unexpected argument: ${positionals[1]}`);
87
+ }
88
+ return {
89
+ command,
90
+ ...(positionals[0] !== undefined ? { runId: positionals[0] } : {}),
91
+ dir,
92
+ controllerDir,
93
+ once,
94
+ };
51
95
  }
52
96
  async function printRuns(dir) {
53
97
  const bundles = await listRunBundles(dir);
@@ -57,7 +101,6 @@ async function printRuns(dir) {
57
101
  }
58
102
  for (const bundle of bundles) {
59
103
  const state = bundle.state;
60
- // Titles can interpolate untrusted run input; strip control sequences.
61
104
  const title = state.runTitle ? ` — ${sanitizeText(state.runTitle)}` : "";
62
105
  process.stdout.write(`${statusLabel(state.status)} ${sanitizeText(state.workflowName)}${title} ${state.runId} ${formatDuration(runElapsedMs(state))}\n`);
63
106
  }
@@ -79,6 +122,53 @@ async function printOnce(dir, runId) {
79
122
  }
80
123
  process.stdout.write(`${renderRunDetailLines(bundle, size).join("\n")}\n`);
81
124
  }
125
+ function printControllers(controllerDir) {
126
+ const store = openControllerStore(controllerDir);
127
+ if (store === undefined) {
128
+ process.stdout.write(`No controller resources found in ${controllerDir}\n`);
129
+ return;
130
+ }
131
+ try {
132
+ const resources = store.listResources();
133
+ if (resources.length === 0) {
134
+ process.stdout.write(`No controller resources found in ${controllerDir}\n`);
135
+ return;
136
+ }
137
+ for (const resource of resources) {
138
+ const condition = resource.status.conditions.find((item) => item.type === "Ready") ??
139
+ resource.status.conditions[0];
140
+ const conditionText = condition === undefined
141
+ ? "unknown"
142
+ : `${String(condition.status)}:${sanitizeText(condition.reason)}`;
143
+ process.stdout.write(`${sanitizeText(resource.metadata.controller)} ${sanitizeText(resource.metadata.key)} generation=${resource.metadata.generation} ready=${conditionText}\n`);
144
+ }
145
+ }
146
+ finally {
147
+ store.close();
148
+ }
149
+ }
150
+ function printController(controllerDir, controller, key) {
151
+ const store = openControllerStore(controllerDir);
152
+ if (store === undefined) {
153
+ throw new Error(`Controller store not found in ${controllerDir}`);
154
+ }
155
+ try {
156
+ const resource = store.getResource({ controller, key });
157
+ if (resource === undefined) {
158
+ throw new Error(`Controller resource not found: ${controller}/${key}`);
159
+ }
160
+ const value = {
161
+ resource,
162
+ effects: store.listEffects(resource.metadata.uid),
163
+ workflows: store.listWorkflows(resource.metadata.uid),
164
+ events: store.listEvents({ controller, key, limit: 50 }),
165
+ };
166
+ process.stdout.write(`${JSON.stringify(value, null, 2)}\n`);
167
+ }
168
+ finally {
169
+ store.close();
170
+ }
171
+ }
82
172
  export async function main(argv = process.argv.slice(2)) {
83
173
  let args;
84
174
  try {
@@ -97,6 +187,17 @@ export async function main(argv = process.argv.slice(2)) {
97
187
  await printRuns(args.dir);
98
188
  return 0;
99
189
  }
190
+ if (args.command === "controllers") {
191
+ printControllers(args.controllerDir);
192
+ return 0;
193
+ }
194
+ if (args.command === "controller") {
195
+ printController(args.controllerDir, args.controllerName, args.resourceKey);
196
+ return 0;
197
+ }
198
+ if (args.command === "host") {
199
+ return await runHost(args.project ?? process.cwd(), args.piArgs);
200
+ }
100
201
  if (args.command === "view") {
101
202
  if (args.once || !process.stdout.isTTY) {
102
203
  await printOnce(args.dir, args.runId);
@@ -113,8 +214,38 @@ export async function main(argv = process.argv.slice(2)) {
113
214
  return 1;
114
215
  }
115
216
  }
217
+ async function runHost(project, piArgs) {
218
+ const { WorkflowHost } = await import("../host/runner.js");
219
+ const host = new WorkflowHost({
220
+ cwd: project,
221
+ piArgs: piArgs ?? [],
222
+ onLog: (message) => process.stdout.write(`[host] ${message}\n`),
223
+ });
224
+ await host.start();
225
+ await new Promise((resolve) => {
226
+ const shutdown = () => {
227
+ void host.stop().then(() => resolve());
228
+ };
229
+ process.once("SIGTERM", shutdown);
230
+ process.once("SIGINT", shutdown);
231
+ });
232
+ return 0;
233
+ }
234
+ function openControllerStore(controllerDir) {
235
+ const file = path.join(controllerDir, "controller.sqlite");
236
+ if (!fs.existsSync(file)) {
237
+ return undefined;
238
+ }
239
+ return new SqliteControllerStore(file, { readOnly: true });
240
+ }
241
+ function requiredValue(args, option) {
242
+ const value = args.shift();
243
+ if (!value) {
244
+ throw new Error(`${option} requires a path`);
245
+ }
246
+ return value;
247
+ }
116
248
  const entryPath = process.argv[1];
117
- // realpath so invocation through a bin symlink (npm link) still matches.
118
249
  const resolvedEntry = entryPath !== undefined ? realpathSyncSafe(entryPath) : undefined;
119
250
  if (resolvedEntry !== undefined && import.meta.url === pathToFileURL(resolvedEntry).href) {
120
251
  main().then((code) => {