@rulvar/cli 1.83.0 → 1.85.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 (2) hide show
  1. package/dist/index.js +52 -15
  2. package/package.json +9 -9
package/dist/index.js CHANGED
@@ -142,6 +142,26 @@ function createServer(options) {
142
142
  })().catch(() => {
143
143
  pumpFailed = true;
144
144
  });
145
+ /**
146
+ * The terminal release cascade shared by the settle and the
147
+ * rejection paths: durable retention first (deletes the record AND
148
+ * untracks), then memory retention (untracks only), then the
149
+ * settled cap. It runs after the drain so the released buffer is
150
+ * the full one.
151
+ */
152
+ const releaseAfterTerminal = async () => {
153
+ const meta = options.retention === void 0 && options.memoryRetention === void 0 ? void 0 : await metaOf(run.runId);
154
+ if (meta !== void 0 && options.retention?.(meta) === true) {
155
+ await engine.deleteRun(run.runId);
156
+ runs.delete(run.runId);
157
+ return;
158
+ }
159
+ if (meta !== void 0 && options.memoryRetention?.(meta) === true) {
160
+ runs.delete(run.runId);
161
+ return;
162
+ }
163
+ enforceTrackedCap();
164
+ };
145
165
  handle.result.then(async (outcome) => {
146
166
  run.outcome = outcome;
147
167
  if (outcome.status === "suspended") return;
@@ -149,20 +169,29 @@ function createServer(options) {
149
169
  run.done = true;
150
170
  for (const feed of [...run.feeds]) feed(null, pumpFailed ? "event pump failed; the stream may be incomplete, reconnect with Last-Event-ID to replay" : void 0);
151
171
  run.feeds.clear();
152
- (async () => {
153
- const meta = options.retention === void 0 && options.memoryRetention === void 0 ? void 0 : await metaOf(run.runId);
154
- if (meta !== void 0 && options.retention?.(meta) === true) {
155
- await engine.deleteRun(run.runId);
156
- runs.delete(run.runId);
157
- return;
158
- }
159
- if (meta !== void 0 && options.memoryRetention?.(meta) === true) {
160
- runs.delete(run.runId);
161
- return;
162
- }
163
- enforceTrackedCap();
164
- })().catch(() => void 0);
165
- }).catch(() => void 0);
172
+ releaseAfterTerminal().catch(() => void 0);
173
+ }, handleRejection).catch(() => void 0);
174
+ /**
175
+ * The segment rejected: no outcome will ever arrive. The engine
176
+ * closes the segment's surfaces before every rejection (the boot
177
+ * refusal explicitly, the withheld settlement after run:end), so
178
+ * awaiting the pump here delivers whatever tail exists and then
179
+ * terminates. Without this branch the tracked run stayed 'running'
180
+ * for the life of the process, its SSE connections never closed,
181
+ * and neither retention nor the settled cap could ever release it.
182
+ */
183
+ async function handleRejection(thrown) {
184
+ await pump;
185
+ run.done = true;
186
+ run.rejection = thrown instanceof RulvarError ? thrown.toWire() : {
187
+ code: "error",
188
+ message: thrown instanceof Error ? thrown.message : String(thrown),
189
+ retryable: false
190
+ };
191
+ for (const feed of [...run.feeds]) feed(null, `run failed before settling: ${run.rejection.message}`);
192
+ run.feeds.clear();
193
+ await releaseAfterTerminal();
194
+ }
166
195
  }
167
196
  function track(runId, workflowName, args, handle) {
168
197
  const run = {
@@ -228,6 +257,13 @@ function createServer(options) {
228
257
  const run = runs.get(runId);
229
258
  if (run !== void 0) {
230
259
  const outcome = run.outcome;
260
+ if (run.rejection !== void 0) return json(200, {
261
+ runId,
262
+ status: "error",
263
+ workflow: run.workflowName,
264
+ live: true,
265
+ error: run.rejection
266
+ });
231
267
  if (outcome === void 0) return json(200, {
232
268
  runId,
233
269
  status: "running",
@@ -329,6 +365,7 @@ function createServer(options) {
329
365
  controller.enqueue(encoder.encode(sseFrame(run.buffer[i])));
330
366
  }
331
367
  if (run.done) {
368
+ if (run.rejection !== void 0) controller.enqueue(encoder.encode(`: run failed before settling: ${run.rejection.message}\n\n`));
332
369
  controller.close();
333
370
  return;
334
371
  }
@@ -369,7 +406,7 @@ function createServer(options) {
369
406
  const section = run.queue.then(async () => {
370
407
  if (run.done || run.outcome !== void 0 && run.outcome.status !== "suspended") return json(409, { error: {
371
408
  code: "config",
372
- message: `run '${run.runId}' already settled '${run.outcome?.status ?? "unknown"}'`
409
+ message: run.rejection === void 0 ? `run '${run.runId}' already settled '${run.outcome?.status ?? "unknown"}'` : `run '${run.runId}' failed before settling: ${run.rejection.message}`
373
410
  } });
374
411
  const settledSuspended = run.outcome?.status === "suspended";
375
412
  if (settledSuspended) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rulvar/cli",
3
- "version": "1.83.0",
3
+ "version": "1.85.0",
4
4
  "description": "Rulvar shell: run/resume/runs/inspect/plan/kb commands, TUI progress, createServer, createWorker, OTel exporter.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -22,17 +22,17 @@
22
22
  "access": "public"
23
23
  },
24
24
  "dependencies": {
25
- "@rulvar/core": "1.83.0"
25
+ "@rulvar/core": "1.85.0"
26
26
  },
27
27
  "devDependencies": {
28
- "@types/node": "^22.20.0",
29
- "tsdown": "^0.22.3",
28
+ "@types/node": "^22.20.1",
29
+ "tsdown": "^0.22.14",
30
30
  "typescript": "~6.0.3",
31
- "@rulvar/testing": "1.83.0",
32
- "@rulvar/planner": "1.83.0",
33
- "@rulvar/store-sqlite": "1.83.0",
34
- "@rulvar/evals": "1.83.0",
35
- "@rulvar/plan": "1.83.0"
31
+ "@rulvar/store-sqlite": "1.85.0",
32
+ "@rulvar/planner": "1.85.0",
33
+ "@rulvar/plan": "1.85.0",
34
+ "@rulvar/testing": "1.85.0",
35
+ "@rulvar/evals": "1.85.0"
36
36
  },
37
37
  "bin": {
38
38
  "rulvar": "./dist/cli.js"