@hasna/loops 0.3.15 → 0.3.17

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.
@@ -328,6 +328,17 @@ function optionalPositiveInteger(value, label) {
328
328
  throw new Error(`${label} must be a positive integer`);
329
329
  return value;
330
330
  }
331
+ function optionalStringArray(value, label) {
332
+ if (value === undefined)
333
+ return;
334
+ if (!Array.isArray(value))
335
+ throw new Error(`${label} must be an array`);
336
+ const values = value.map((entry, index) => {
337
+ assertString(entry, `${label}[${index}]`);
338
+ return entry.trim();
339
+ }).filter(Boolean);
340
+ return values.length ? values : undefined;
341
+ }
331
342
  function normalizeGoalSpec(value, label = "goal") {
332
343
  if (value === undefined)
333
344
  return;
@@ -399,6 +410,14 @@ function validateTarget(value, label) {
399
410
  throw new Error(`${label}.sandbox is currently supported only for provider codewith, codex, or cursor`);
400
411
  }
401
412
  }
413
+ if (value.allowlist !== undefined) {
414
+ assertObject(value.allowlist, `${label}.allowlist`);
415
+ optionalStringArray(value.allowlist.tools, `${label}.allowlist.tools`);
416
+ optionalStringArray(value.allowlist.commands, `${label}.allowlist.commands`);
417
+ if (value.allowlist.enforcement !== undefined && value.allowlist.enforcement !== "metadata_only") {
418
+ throw new Error(`${label}.allowlist.enforcement must be metadata_only`);
419
+ }
420
+ }
402
421
  return value;
403
422
  }
404
423
  throw new Error(`${label}.type must be command or agent`);
@@ -1033,6 +1052,30 @@ class Store {
1033
1052
  throw new Error(`loop not found after update: ${id}`);
1034
1053
  return after;
1035
1054
  }
1055
+ renameLoop(id, name, opts = {}) {
1056
+ const current = this.getLoop(id);
1057
+ if (!current)
1058
+ throw new Error(`loop not found: ${id}`);
1059
+ const trimmed = name.trim();
1060
+ if (!trimmed)
1061
+ throw new Error("loop name must not be empty");
1062
+ const updated = (opts.now ?? new Date).toISOString();
1063
+ this.db.query(`UPDATE loops SET name=$name, updated_at=$updated
1064
+ WHERE id=$id
1065
+ AND ($daemonLeaseId IS NULL OR EXISTS (
1066
+ SELECT 1 FROM daemon_lease WHERE id=$daemonLeaseId AND expires_at > $now
1067
+ ))`).run({
1068
+ $id: id,
1069
+ $name: trimmed,
1070
+ $updated: updated,
1071
+ $daemonLeaseId: opts.daemonLeaseId ?? null,
1072
+ $now: updated
1073
+ });
1074
+ const after = this.getLoop(id);
1075
+ if (!after)
1076
+ throw new Error(`loop not found after rename: ${id}`);
1077
+ return after;
1078
+ }
1036
1079
  archiveLoop(idOrName) {
1037
1080
  const loop = this.requireLoop(idOrName);
1038
1081
  if (loop.archivedAt)
@@ -2465,6 +2508,16 @@ function metadataEnv(metadata) {
2465
2508
  env.LOOPS_GOAL_NODE_KEY = metadata.goalNodeKey;
2466
2509
  return env;
2467
2510
  }
2511
+ function allowlistEnv(allowlist) {
2512
+ const env = {};
2513
+ if (allowlist?.tools?.length)
2514
+ env.LOOPS_AGENT_ALLOWED_TOOLS = allowlist.tools.join(",");
2515
+ if (allowlist?.commands?.length)
2516
+ env.LOOPS_AGENT_ALLOWED_COMMANDS = allowlist.commands.join(",");
2517
+ if (allowlist?.tools?.length || allowlist?.commands?.length)
2518
+ env.LOOPS_AGENT_ALLOWLIST_ENFORCEMENT = "metadata_only";
2519
+ return env;
2520
+ }
2468
2521
  function providerCommand(provider) {
2469
2522
  switch (provider) {
2470
2523
  case "claude":
@@ -2672,7 +2725,8 @@ function commandSpec(target) {
2672
2725
  account: agentTarget.account,
2673
2726
  accountTool: agentTarget.account?.tool ?? accountToolForProvider(agentTarget.provider),
2674
2727
  preflightAnyOf: agentTarget.provider === "cursor" ? ["cursor", "agent"] : undefined,
2675
- stdin: agentTarget.prompt
2728
+ stdin: agentTarget.prompt,
2729
+ allowlist: agentTarget.allowlist
2676
2730
  };
2677
2731
  }
2678
2732
  function executionEnv(spec, metadata, opts) {
@@ -2684,6 +2738,7 @@ function executionEnv(spec, metadata, opts) {
2684
2738
  Object.assign(env, accountEnv);
2685
2739
  }
2686
2740
  Object.assign(env, spec.env ?? {});
2741
+ Object.assign(env, allowlistEnv(spec.allowlist));
2687
2742
  env.PATH = normalizeExecutionPath(env);
2688
2743
  Object.assign(env, metadataEnv(metadata));
2689
2744
  return env;
@@ -2722,6 +2777,9 @@ function remoteBootstrapLines(spec, metadata) {
2722
2777
  continue;
2723
2778
  lines.push(`export ${key}=${shellQuote(value)}`);
2724
2779
  }
2780
+ for (const [key, value] of Object.entries(allowlistEnv(spec.allowlist))) {
2781
+ lines.push(`export ${key}=${shellQuote(value)}`);
2782
+ }
2725
2783
  return lines;
2726
2784
  }
2727
2785
  function remoteScript(spec, metadata) {
@@ -4361,7 +4419,7 @@ function enableStartup(result) {
4361
4419
  // package.json
4362
4420
  var package_default = {
4363
4421
  name: "@hasna/loops",
4364
- version: "0.3.15",
4422
+ version: "0.3.17",
4365
4423
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
4366
4424
  type: "module",
4367
4425
  main: "dist/index.js",
package/dist/index.d.ts CHANGED
@@ -8,8 +8,10 @@ export { listOpenMachines, refreshLoopMachine, resolveLoopMachine } from "./lib/
8
8
  export { tick } from "./lib/scheduler.js";
9
9
  export { executeWorkflow, executeLoopTarget, preflightWorkflow } from "./lib/workflow-runner.js";
10
10
  export { workflowExecutionOrder, workflowBodyFromJson } from "./lib/workflow-spec.js";
11
- export { EVENT_WORKER_VERIFIER_TEMPLATE_ID, TODOS_TASK_WORKER_VERIFIER_TEMPLATE_ID, getLoopTemplate, listLoopTemplates, renderEventWorkerVerifierWorkflow, renderLoopTemplate, renderTodosTaskWorkerVerifierWorkflow, } from "./lib/templates.js";
11
+ export { BOUNDED_AGENT_WORKER_VERIFIER_TEMPLATE_ID, EVENT_WORKER_VERIFIER_TEMPLATE_ID, TODOS_TASK_WORKER_VERIFIER_TEMPLATE_ID, getLoopTemplate, listLoopTemplates, renderBoundedAgentWorkerVerifierWorkflow, renderEventWorkerVerifierWorkflow, renderLoopTemplate, renderTodosTaskWorkerVerifierWorkflow, } from "./lib/templates.js";
12
12
  export { runDoctor } from "./lib/doctor.js";
13
+ export { buildHealthReport, classifyRunFailure, expectationForLoop } from "./lib/health.js";
14
+ export { buildDuplicateOverlapReport, buildNameHygieneReport, buildScriptInventoryReport } from "./lib/hygiene.js";
13
15
  export { runGoal } from "./lib/goal/runner.js";
14
16
  export { resolveGoalModel } from "./lib/goal/model-factory.js";
15
17
  export { isTerminal as isGoalTerminal, readyNodeKeys, rollupSummary } from "./lib/goal/status.js";