@deksden-com/dd-flow-cli 0.9.0-beta.103 → 0.9.0-beta.104

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.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "cli_package": "@deksden-com/dd-flow-cli",
3
- "cli_version": "0.9.0-beta.103",
4
- "cli_commit": "26ffa3e3ac8382c8a83bae2593a91a3e922f10a9",
5
- "built_at": "2026-09-25T12:03:44.786Z",
3
+ "cli_version": "0.9.0-beta.104",
4
+ "cli_commit": "d8b1b96db830c5e56e8409dfc94837fb73638673",
5
+ "built_at": "2026-09-25T13:44:30.094Z",
6
6
  "built_with_canon": {
7
7
  "version": "4.1.1",
8
8
  "commit": "678daa038287c948ada5b2d785a6dcc925c7b891",
@@ -304,10 +304,16 @@ export async function runCli(args, io = defaultIo, env = process.env) {
304
304
  let earlyContext;
305
305
  try {
306
306
  const command = quote(["dd-flow", ...output.args]);
307
- const event = observedLifecycleNativeEvent(createRouterContext(env), command, env.DD_FLOW_DAEMON_ID);
308
- if (event) {
307
+ const router = createRouterContext(env);
308
+ const event = observedLifecycleNativeEvent(router, command, env.DD_FLOW_DAEMON_ID);
309
+ // A correctable prepare rejection precedes the terminal command's
310
+ // single-use claim. Correlate its unique hook by the same lifecycle
311
+ // match key used at dispatch, including compound provider tool shells;
312
+ // otherwise a repaired retry sees two fresh, unclaimed events.
313
+ if (event || (error.details.phase === "prepare" && error.details.effect === "no_effect"
314
+ && fs.existsSync(path.join(router.ddFlowHome, "db.sqlite")))) {
309
315
  earlyContext = createContext({ ...env }, "hook");
310
- observeLifecycleCommand(earlyContext, command, event)({ error });
316
+ observeLifecycleCommand(earlyContext, command, event ?? undefined)({ error });
311
317
  }
312
318
  }
313
319
  catch (persistence) {
@@ -53,15 +53,20 @@ export async function prepareRuntimeOwner(config, stateDir, env = process.env) {
53
53
  }
54
54
 
55
55
  export async function assertDaemonOwnership(stateDir, dispatch = {}, assertCurrent = () => {}) {
56
- for (const lease of leases.values()) {
57
- if (lease.error) throw lease.error;
58
- if (Date.now() >= lease.expiresAt) throw Object.assign(new Error("managed process renewal is overdue"), { code: "process_ownership_unknown" });
59
- }
56
+ for (const lease of leases.values()) if (!lease.stateDir && lease.error) throw lease.error;
60
57
  const state = JSON.parse(await readFile(path.join(stateDir, "daemon.json"), "utf8"));
61
58
  const owner = bindRuntimeOwner({ runtime_owner: state.config?.runtime_owner ?? null }, {});
62
59
  if (!owner) return;
63
60
  const record = state.resource_process;
64
61
  if (owner.state_dir !== stateDir || !record?.id || !record.lease_token) throw Object.assign(new Error("owned daemon has no confirmed resource binding"), { code: "process_ownership_unknown" });
62
+ for (const lease of leases.values()) {
63
+ if (lease.stateDir !== stateDir) continue;
64
+ if (Date.now() >= lease.expiresAt) throw Object.assign(new Error("managed process renewal is overdue"), { code: "process_ownership_unknown", details: { process_id: lease.record.id, cause: lease.error ? errorRecord(lease.error) : null } });
65
+ // A failed renewal is not a lost lease. Reconfirm before productive work;
66
+ // never dispatch on the old lease merely because it has time remaining.
67
+ if (lease.error && !lease.lost) await heartbeatDaemonProcess(lease.config, lease.record);
68
+ if (lease.error) throw lease.error;
69
+ }
65
70
  for (;;) {
66
71
  assertCurrent();
67
72
  const result = await runtimeProcess(state.config, "admission", { id: record.id, "lease-token": record.lease_token, owner: owner.owner_id, "state-dir": stateDir, "daemon-id": state.daemon_id ?? state.config?.daemonId, ...dispatch });
@@ -211,7 +216,7 @@ export async function heartbeatDaemonProcess(config, record) {
211
216
  if (!record?.id || !record.lease_token) return;
212
217
  let lease = leases.get(record.id);
213
218
  if (!lease) {
214
- lease = { pending: null, error: null, timer: null, lost: false, expiresAt: Date.now() + 300_000 };
219
+ lease = { pending: null, error: null, timer: null, lost: false, expiresAt: Date.now() + 300_000, stateDir: config.runtime_owner?.state_dir, config, record };
215
220
  leases.set(record.id, lease);
216
221
  lease.timer = setInterval(() => { void heartbeatDaemonProcess(config, record).catch(() => {}); }, 60_000);
217
222
  lease.timer.unref();
@@ -228,7 +233,7 @@ export async function heartbeatDaemonProcess(config, record) {
228
233
  lease.error = null;
229
234
  lease.expiresAt = Date.now() + 300_000;
230
235
  } catch (cause) {
231
- lease.error = cause.code === "process_lease_lost" ? cause : Object.assign(new Error("cannot confirm managed process ownership"), { code: "process_ownership_unknown", cause, details: { process_id: record.id } });
236
+ lease.error = cause.code === "process_lease_lost" ? cause : Object.assign(new Error("cannot confirm managed process ownership"), { code: "process_ownership_unknown", cause, details: { process_id: record.id, cause: errorRecord(cause) } });
232
237
  throw lease.error;
233
238
  }
234
239
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deksden-com/dd-flow-cli",
3
- "version": "0.9.0-beta.103",
3
+ "version": "0.9.0-beta.104",
4
4
  "description": "Mechanical runtime CLI for dd-flow workflows.",
5
5
  "type": "module",
6
6
  "bin": {