@hasna/loops 0.3.22 → 0.3.23

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.
package/dist/cli/index.js CHANGED
@@ -5143,7 +5143,7 @@ function buildScriptInventoryReport(store, opts = {}) {
5143
5143
  // package.json
5144
5144
  var package_default = {
5145
5145
  name: "@hasna/loops",
5146
- version: "0.3.22",
5146
+ version: "0.3.23",
5147
5147
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
5148
5148
  type: "module",
5149
5149
  main: "dist/index.js",
@@ -5662,6 +5662,20 @@ function preflightStoredWorkflow(workflow, context, opts) {
5662
5662
  preflightFailed(error, context);
5663
5663
  }
5664
5664
  }
5665
+ function workflowSpecForPreflight(body, id = "validation") {
5666
+ const now = new Date().toISOString();
5667
+ return {
5668
+ id,
5669
+ name: body.name,
5670
+ description: body.description,
5671
+ version: body.version ?? 1,
5672
+ status: "active",
5673
+ goal: body.goal,
5674
+ steps: body.steps,
5675
+ createdAt: now,
5676
+ updatedAt: now
5677
+ };
5678
+ }
5665
5679
  function printTextOutput(value) {
5666
5680
  for (const line of textOutputBlocks(value, { indent: " " }))
5667
5681
  console.log(line);
@@ -6211,7 +6225,7 @@ templates.command("create-workflow <id>").description("render and store a templa
6211
6225
  }
6212
6226
  });
6213
6227
  var eventsHandle = events.command("handle").description("handle a Hasna event envelope");
6214
- eventsHandle.command("todos-task").description("create a one-shot worker/verifier workflow loop for a todos task event").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--project-path <path>", "fallback project/repo working directory").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:todos-task").option("--dry-run", "print the workflow and loop input without storing anything").action(async (opts) => {
6228
+ eventsHandle.command("todos-task").description("create a one-shot worker/verifier workflow loop for a todos task event").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--project-path <path>", "fallback project/repo working directory").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:todos-task").option("--preflight", "check generated workflow steps before storing the workflow loop").option("--dry-run", "print the workflow and loop input without storing anything").action(async (opts) => {
6215
6229
  const event = await readEventEnvelopeFromStdin();
6216
6230
  const data = eventData(event);
6217
6231
  const metadata = eventMetadata(event);
@@ -6280,7 +6294,12 @@ eventsHandle.command("todos-task").description("create a one-shot worker/verifie
6280
6294
  leaseMs: 90 * 60000
6281
6295
  };
6282
6296
  if (opts.dryRun) {
6283
- print({ deduped: false, idempotencyKey, event, workflow: workflowBody, loop: loopInput }, `dry-run ${loopName}`);
6297
+ const preflight = opts.preflight ? preflightStoredWorkflow(workflowSpecForPreflight(workflowBody, "event-preflight"), {
6298
+ name: workflowBody.name,
6299
+ type: "todos-task-event-workflow",
6300
+ event: event.id
6301
+ }, {}) : undefined;
6302
+ print({ deduped: false, idempotencyKey, event, workflow: workflowBody, loop: loopInput, preflight }, `dry-run ${loopName}`);
6284
6303
  return;
6285
6304
  }
6286
6305
  const store = new Store;
@@ -6299,17 +6318,23 @@ eventsHandle.command("todos-task").description("create a one-shot worker/verifie
6299
6318
  return;
6300
6319
  }
6301
6320
  const existingWorkflow = store.findWorkflowByName(workflowBody.name);
6321
+ const workflowPreflightSpec = existingWorkflow ?? workflowSpecForPreflight(workflowBody, "event-preflight");
6322
+ const preflight = opts.preflight ? preflightStoredWorkflow(workflowPreflightSpec, {
6323
+ name: workflowBody.name,
6324
+ type: "todos-task-event-workflow",
6325
+ event: event.id
6326
+ }, {}) : undefined;
6302
6327
  const workflow = existingWorkflow ?? store.createWorkflow(workflowBody);
6303
6328
  const loop = store.createLoop({
6304
6329
  ...loopInput,
6305
6330
  target: { type: "workflow", workflowId: workflow.id }
6306
6331
  });
6307
- print({ deduped: false, idempotencyKey, event, workflow: publicWorkflow(workflow), loop: publicLoop(loop) }, `created ${loop.id} (${loop.name}) workflow=${workflow.name} event=${event.id} idempotency=${idempotencyKey}`);
6332
+ print({ deduped: false, idempotencyKey, event, workflow: publicWorkflow(workflow), loop: publicLoop(loop), preflight }, `created ${loop.id} (${loop.name}) workflow=${workflow.name} event=${event.id} idempotency=${idempotencyKey}`);
6308
6333
  } finally {
6309
6334
  store.close();
6310
6335
  }
6311
6336
  });
6312
- eventsHandle.command("generic").description("create a one-shot worker/verifier workflow loop for any Hasna event").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--project-path <path>", "fallback project/repo working directory").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:generic").option("--dry-run", "print the workflow and loop input without storing anything").action(async (opts) => {
6337
+ eventsHandle.command("generic").description("create a one-shot worker/verifier workflow loop for any Hasna event").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--project-path <path>", "fallback project/repo working directory").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:generic").option("--preflight", "check generated workflow steps before storing the workflow loop").option("--dry-run", "print the workflow and loop input without storing anything").action(async (opts) => {
6313
6338
  const event = await readEventEnvelopeFromStdin();
6314
6339
  const data = eventData(event);
6315
6340
  const projectPath = opts.projectPath ?? taskEventField(data, ["working_dir", "workingDir", "project_path", "projectPath", "cwd", "repo_path", "repoPath"]) ?? process.cwd();
@@ -6359,7 +6384,12 @@ eventsHandle.command("generic").description("create a one-shot worker/verifier w
6359
6384
  leaseMs: 90 * 60000
6360
6385
  };
6361
6386
  if (opts.dryRun) {
6362
- print({ event, workflow: workflowBody, loop: loopInput }, `dry-run ${loopName}`);
6387
+ const preflight = opts.preflight ? preflightStoredWorkflow(workflowSpecForPreflight(workflowBody, "event-preflight"), {
6388
+ name: workflowBody.name,
6389
+ type: "generic-event-workflow",
6390
+ event: event.id
6391
+ }, {}) : undefined;
6392
+ print({ event, workflow: workflowBody, loop: loopInput, preflight }, `dry-run ${loopName}`);
6363
6393
  return;
6364
6394
  }
6365
6395
  const store = new Store;
@@ -6371,12 +6401,18 @@ eventsHandle.command("generic").description("create a one-shot worker/verifier w
6371
6401
  return;
6372
6402
  }
6373
6403
  const existingWorkflow = store.findWorkflowByName(workflowBody.name);
6404
+ const workflowPreflightSpec = existingWorkflow ?? workflowSpecForPreflight(workflowBody, "event-preflight");
6405
+ const preflight = opts.preflight ? preflightStoredWorkflow(workflowPreflightSpec, {
6406
+ name: workflowBody.name,
6407
+ type: "generic-event-workflow",
6408
+ event: event.id
6409
+ }, {}) : undefined;
6374
6410
  const workflow = existingWorkflow ?? store.createWorkflow(workflowBody);
6375
6411
  const loop = store.createLoop({
6376
6412
  ...loopInput,
6377
6413
  target: { type: "workflow", workflowId: workflow.id }
6378
6414
  });
6379
- print({ deduped: false, event, workflow: publicWorkflow(workflow), loop: publicLoop(loop) }, `created ${loop.id} (${loop.name}) workflow=${workflow.name}`);
6415
+ print({ deduped: false, event, workflow: publicWorkflow(workflow), loop: publicLoop(loop), preflight }, `created ${loop.id} (${loop.name}) workflow=${workflow.name}`);
6380
6416
  } finally {
6381
6417
  store.close();
6382
6418
  }
@@ -6441,27 +6477,20 @@ machines.command("show <id>").description("resolve a machine assignment").action
6441
6477
  });
6442
6478
  workflows.command("validate <file>").description("validate a workflow JSON file without storing or running it").option("--name <name>", "override workflow name from the file").option("--preflight", "also check account env and target executables").action((file, opts) => {
6443
6479
  const body = workflowBodyFromJson(JSON.parse(readFileSync2(file, "utf8")), opts.name);
6444
- const now = new Date().toISOString();
6445
- const workflow = {
6446
- id: "validation",
6447
- name: body.name,
6448
- description: body.description,
6449
- version: body.version ?? 1,
6450
- status: "active",
6451
- goal: body.goal,
6452
- steps: body.steps,
6453
- createdAt: now,
6454
- updatedAt: now
6455
- };
6480
+ const workflow = workflowSpecForPreflight(body);
6456
6481
  const preflight = opts.preflight ? preflightWorkflow(workflow) : undefined;
6457
6482
  print({ valid: true, workflow: publicWorkflow(workflow), preflight }, `valid workflow ${workflow.name} steps=${workflow.steps.length}`);
6458
6483
  });
6459
- workflows.command("create <file>").description("validate and store a workflow JSON file").option("--name <name>", "override workflow name from the file").action((file, opts) => {
6484
+ workflows.command("create <file>").description("validate and store a workflow JSON file").option("--name <name>", "override workflow name from the file").option("--preflight", "also check account env and target executables before storing").action((file, opts) => {
6460
6485
  const store = new Store;
6461
6486
  try {
6462
6487
  const body = workflowBodyFromJson(JSON.parse(readFileSync2(file, "utf8")), opts.name);
6488
+ const preflight = opts.preflight ? preflightStoredWorkflow(workflowSpecForPreflight(body, "creation-preflight"), { name: body.name, type: "workflow" }, {}) : undefined;
6463
6489
  const workflow = store.createWorkflow(body);
6464
- print(publicWorkflow(workflow), `created workflow ${workflow.id} (${workflow.name}) steps=${workflow.steps.length}`);
6490
+ if (preflight !== undefined)
6491
+ print({ workflow: publicWorkflow(workflow), preflight }, `created workflow ${workflow.id} (${workflow.name}) steps=${workflow.steps.length}`);
6492
+ else
6493
+ print(publicWorkflow(workflow), `created workflow ${workflow.id} (${workflow.name}) steps=${workflow.steps.length}`);
6465
6494
  } finally {
6466
6495
  store.close();
6467
6496
  }
@@ -4470,7 +4470,7 @@ function enableStartup(result) {
4470
4470
  // package.json
4471
4471
  var package_default = {
4472
4472
  name: "@hasna/loops",
4473
- version: "0.3.22",
4473
+ version: "0.3.23",
4474
4474
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
4475
4475
  type: "module",
4476
4476
  main: "dist/index.js",
package/docs/USAGE.md CHANGED
@@ -59,10 +59,12 @@ loops create command repo-status \
59
59
  --preflight
60
60
  ```
61
61
 
62
- `--preflight` is available on `loops create command`, `loops create agent`, and
63
- `loops create workflow`. It checks target executables and configured account
64
- profiles before the loop row is stored, so a missing command, provider binary,
65
- OpenAccounts profile, or workflow step dependency fails without creating a
62
+ `--preflight` is available on `loops create command`, `loops create agent`,
63
+ `loops create workflow`, `loops workflows create`, and event-router commands such
64
+ as `loops events handle todos-task` and `loops events handle generic`. It checks
65
+ target executables and configured account profiles before loop or workflow rows
66
+ are stored, so a missing command, provider binary, OpenAccounts profile, native
67
+ Codewith auth profile, or workflow step dependency fails without creating a
66
68
  scheduled loop. Use `--json` with `--preflight` to capture stable machine-readable
67
69
  preflight evidence.
68
70
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/loops",
3
- "version": "0.3.22",
3
+ "version": "0.3.23",
4
4
  "description": "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",