@hasna/loops 0.3.10 → 0.3.12

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
@@ -860,31 +860,23 @@ class Store {
860
860
  CREATE INDEX IF NOT EXISTS idx_goal_runs_loop_run ON goal_runs(loop_run_id);
861
861
  CREATE INDEX IF NOT EXISTS idx_goal_runs_workflow_run ON goal_runs(workflow_run_id);
862
862
  `);
863
- try {
864
- this.db.query("ALTER TABLE loops ADD COLUMN machine_json TEXT").run();
865
- } catch {}
866
- try {
867
- this.db.query("ALTER TABLE loops ADD COLUMN goal_json TEXT").run();
868
- } catch {}
869
- try {
870
- this.db.query("ALTER TABLE loop_runs ADD COLUMN goal_run_id TEXT").run();
871
- } catch {}
872
- try {
873
- this.db.query("ALTER TABLE workflow_specs ADD COLUMN goal_json TEXT").run();
874
- } catch {}
875
- try {
876
- this.db.query("ALTER TABLE workflow_runs ADD COLUMN goal_run_id TEXT").run();
877
- } catch {}
878
- try {
879
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN pid INTEGER").run();
880
- } catch {}
881
- try {
882
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN goal_run_id TEXT").run();
883
- } catch {}
863
+ this.addColumnIfMissing("loops", "machine_json", "TEXT");
864
+ this.addColumnIfMissing("loops", "goal_json", "TEXT");
865
+ this.addColumnIfMissing("loop_runs", "goal_run_id", "TEXT");
866
+ this.addColumnIfMissing("workflow_specs", "goal_json", "TEXT");
867
+ this.addColumnIfMissing("workflow_runs", "goal_run_id", "TEXT");
868
+ this.addColumnIfMissing("workflow_step_runs", "pid", "INTEGER");
869
+ this.addColumnIfMissing("workflow_step_runs", "goal_run_id", "TEXT");
884
870
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0001_initial_and_workflows", nowIso());
885
871
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0002_loop_machines", nowIso());
886
872
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0003_goals", nowIso());
887
873
  }
874
+ addColumnIfMissing(table, column, definition) {
875
+ const columns = this.db.query(`PRAGMA table_info(${table})`).all();
876
+ if (columns.some((c) => c.name === column))
877
+ return;
878
+ this.db.query(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).run();
879
+ }
888
880
  assertDaemonLeaseFence(opts = {}, now = nowIso()) {
889
881
  if (!opts.daemonLeaseId)
890
882
  return;
@@ -2502,7 +2494,7 @@ function agentArgs(target) {
2502
2494
  args.push(...target.extraArgs ?? []);
2503
2495
  return args;
2504
2496
  case "cursor":
2505
- args.push("-p");
2497
+ args.push("agent", "-p");
2506
2498
  if (target.model)
2507
2499
  args.push("--model", target.model);
2508
2500
  if (target.agent)
@@ -4338,7 +4330,7 @@ function runDoctor(store) {
4338
4330
  // package.json
4339
4331
  var package_default = {
4340
4332
  name: "@hasna/loops",
4341
- version: "0.3.10",
4333
+ version: "0.3.12",
4342
4334
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
4343
4335
  type: "module",
4344
4336
  main: "dist/index.js",
@@ -860,31 +860,23 @@ class Store {
860
860
  CREATE INDEX IF NOT EXISTS idx_goal_runs_loop_run ON goal_runs(loop_run_id);
861
861
  CREATE INDEX IF NOT EXISTS idx_goal_runs_workflow_run ON goal_runs(workflow_run_id);
862
862
  `);
863
- try {
864
- this.db.query("ALTER TABLE loops ADD COLUMN machine_json TEXT").run();
865
- } catch {}
866
- try {
867
- this.db.query("ALTER TABLE loops ADD COLUMN goal_json TEXT").run();
868
- } catch {}
869
- try {
870
- this.db.query("ALTER TABLE loop_runs ADD COLUMN goal_run_id TEXT").run();
871
- } catch {}
872
- try {
873
- this.db.query("ALTER TABLE workflow_specs ADD COLUMN goal_json TEXT").run();
874
- } catch {}
875
- try {
876
- this.db.query("ALTER TABLE workflow_runs ADD COLUMN goal_run_id TEXT").run();
877
- } catch {}
878
- try {
879
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN pid INTEGER").run();
880
- } catch {}
881
- try {
882
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN goal_run_id TEXT").run();
883
- } catch {}
863
+ this.addColumnIfMissing("loops", "machine_json", "TEXT");
864
+ this.addColumnIfMissing("loops", "goal_json", "TEXT");
865
+ this.addColumnIfMissing("loop_runs", "goal_run_id", "TEXT");
866
+ this.addColumnIfMissing("workflow_specs", "goal_json", "TEXT");
867
+ this.addColumnIfMissing("workflow_runs", "goal_run_id", "TEXT");
868
+ this.addColumnIfMissing("workflow_step_runs", "pid", "INTEGER");
869
+ this.addColumnIfMissing("workflow_step_runs", "goal_run_id", "TEXT");
884
870
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0001_initial_and_workflows", nowIso());
885
871
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0002_loop_machines", nowIso());
886
872
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0003_goals", nowIso());
887
873
  }
874
+ addColumnIfMissing(table, column, definition) {
875
+ const columns = this.db.query(`PRAGMA table_info(${table})`).all();
876
+ if (columns.some((c) => c.name === column))
877
+ return;
878
+ this.db.query(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).run();
879
+ }
888
880
  assertDaemonLeaseFence(opts = {}, now = nowIso()) {
889
881
  if (!opts.daemonLeaseId)
890
882
  return;
@@ -2397,7 +2389,7 @@ function agentArgs(target) {
2397
2389
  args.push(...target.extraArgs ?? []);
2398
2390
  return args;
2399
2391
  case "cursor":
2400
- args.push("-p");
2392
+ args.push("agent", "-p");
2401
2393
  if (target.model)
2402
2394
  args.push("--model", target.model);
2403
2395
  if (target.agent)
@@ -4138,7 +4130,7 @@ function enableStartup(result) {
4138
4130
  // package.json
4139
4131
  var package_default = {
4140
4132
  name: "@hasna/loops",
4141
- version: "0.3.10",
4133
+ version: "0.3.12",
4142
4134
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
4143
4135
  type: "module",
4144
4136
  main: "dist/index.js",
package/dist/index.js CHANGED
@@ -858,31 +858,23 @@ class Store {
858
858
  CREATE INDEX IF NOT EXISTS idx_goal_runs_loop_run ON goal_runs(loop_run_id);
859
859
  CREATE INDEX IF NOT EXISTS idx_goal_runs_workflow_run ON goal_runs(workflow_run_id);
860
860
  `);
861
- try {
862
- this.db.query("ALTER TABLE loops ADD COLUMN machine_json TEXT").run();
863
- } catch {}
864
- try {
865
- this.db.query("ALTER TABLE loops ADD COLUMN goal_json TEXT").run();
866
- } catch {}
867
- try {
868
- this.db.query("ALTER TABLE loop_runs ADD COLUMN goal_run_id TEXT").run();
869
- } catch {}
870
- try {
871
- this.db.query("ALTER TABLE workflow_specs ADD COLUMN goal_json TEXT").run();
872
- } catch {}
873
- try {
874
- this.db.query("ALTER TABLE workflow_runs ADD COLUMN goal_run_id TEXT").run();
875
- } catch {}
876
- try {
877
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN pid INTEGER").run();
878
- } catch {}
879
- try {
880
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN goal_run_id TEXT").run();
881
- } catch {}
861
+ this.addColumnIfMissing("loops", "machine_json", "TEXT");
862
+ this.addColumnIfMissing("loops", "goal_json", "TEXT");
863
+ this.addColumnIfMissing("loop_runs", "goal_run_id", "TEXT");
864
+ this.addColumnIfMissing("workflow_specs", "goal_json", "TEXT");
865
+ this.addColumnIfMissing("workflow_runs", "goal_run_id", "TEXT");
866
+ this.addColumnIfMissing("workflow_step_runs", "pid", "INTEGER");
867
+ this.addColumnIfMissing("workflow_step_runs", "goal_run_id", "TEXT");
882
868
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0001_initial_and_workflows", nowIso());
883
869
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0002_loop_machines", nowIso());
884
870
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0003_goals", nowIso());
885
871
  }
872
+ addColumnIfMissing(table, column, definition) {
873
+ const columns = this.db.query(`PRAGMA table_info(${table})`).all();
874
+ if (columns.some((c) => c.name === column))
875
+ return;
876
+ this.db.query(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).run();
877
+ }
886
878
  assertDaemonLeaseFence(opts = {}, now = nowIso()) {
887
879
  if (!opts.daemonLeaseId)
888
880
  return;
@@ -2387,7 +2379,7 @@ function agentArgs(target) {
2387
2379
  args.push(...target.extraArgs ?? []);
2388
2380
  return args;
2389
2381
  case "cursor":
2390
- args.push("-p");
2382
+ args.push("agent", "-p");
2391
2383
  if (target.model)
2392
2384
  args.push("--model", target.model);
2393
2385
  if (target.agent)
@@ -58,6 +58,14 @@ export declare class Store {
58
58
  private db;
59
59
  constructor(path?: string);
60
60
  private migrate;
61
+ /**
62
+ * Add a column only if it does not already exist. Idempotent — avoids the
63
+ * "duplicate column name" error that SQLite logs (via libsqlite3, before any
64
+ * JS try/catch) when re-running an additive migration on a database that has
65
+ * already been upgraded. Table/column/definition come from hardcoded literals
66
+ * in {@link migrate}, never user input, so interpolation here is safe.
67
+ */
68
+ private addColumnIfMissing;
61
69
  private assertDaemonLeaseFence;
62
70
  createLoop(input: CreateLoopInput, from?: Date): Loop;
63
71
  getLoop(id: string): Loop | undefined;
package/dist/lib/store.js CHANGED
@@ -858,31 +858,23 @@ class Store {
858
858
  CREATE INDEX IF NOT EXISTS idx_goal_runs_loop_run ON goal_runs(loop_run_id);
859
859
  CREATE INDEX IF NOT EXISTS idx_goal_runs_workflow_run ON goal_runs(workflow_run_id);
860
860
  `);
861
- try {
862
- this.db.query("ALTER TABLE loops ADD COLUMN machine_json TEXT").run();
863
- } catch {}
864
- try {
865
- this.db.query("ALTER TABLE loops ADD COLUMN goal_json TEXT").run();
866
- } catch {}
867
- try {
868
- this.db.query("ALTER TABLE loop_runs ADD COLUMN goal_run_id TEXT").run();
869
- } catch {}
870
- try {
871
- this.db.query("ALTER TABLE workflow_specs ADD COLUMN goal_json TEXT").run();
872
- } catch {}
873
- try {
874
- this.db.query("ALTER TABLE workflow_runs ADD COLUMN goal_run_id TEXT").run();
875
- } catch {}
876
- try {
877
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN pid INTEGER").run();
878
- } catch {}
879
- try {
880
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN goal_run_id TEXT").run();
881
- } catch {}
861
+ this.addColumnIfMissing("loops", "machine_json", "TEXT");
862
+ this.addColumnIfMissing("loops", "goal_json", "TEXT");
863
+ this.addColumnIfMissing("loop_runs", "goal_run_id", "TEXT");
864
+ this.addColumnIfMissing("workflow_specs", "goal_json", "TEXT");
865
+ this.addColumnIfMissing("workflow_runs", "goal_run_id", "TEXT");
866
+ this.addColumnIfMissing("workflow_step_runs", "pid", "INTEGER");
867
+ this.addColumnIfMissing("workflow_step_runs", "goal_run_id", "TEXT");
882
868
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0001_initial_and_workflows", nowIso());
883
869
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0002_loop_machines", nowIso());
884
870
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0003_goals", nowIso());
885
871
  }
872
+ addColumnIfMissing(table, column, definition) {
873
+ const columns = this.db.query(`PRAGMA table_info(${table})`).all();
874
+ if (columns.some((c) => c.name === column))
875
+ return;
876
+ this.db.query(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).run();
877
+ }
886
878
  assertDaemonLeaseFence(opts = {}, now = nowIso()) {
887
879
  if (!opts.daemonLeaseId)
888
880
  return;
package/dist/sdk/index.js CHANGED
@@ -858,31 +858,23 @@ class Store {
858
858
  CREATE INDEX IF NOT EXISTS idx_goal_runs_loop_run ON goal_runs(loop_run_id);
859
859
  CREATE INDEX IF NOT EXISTS idx_goal_runs_workflow_run ON goal_runs(workflow_run_id);
860
860
  `);
861
- try {
862
- this.db.query("ALTER TABLE loops ADD COLUMN machine_json TEXT").run();
863
- } catch {}
864
- try {
865
- this.db.query("ALTER TABLE loops ADD COLUMN goal_json TEXT").run();
866
- } catch {}
867
- try {
868
- this.db.query("ALTER TABLE loop_runs ADD COLUMN goal_run_id TEXT").run();
869
- } catch {}
870
- try {
871
- this.db.query("ALTER TABLE workflow_specs ADD COLUMN goal_json TEXT").run();
872
- } catch {}
873
- try {
874
- this.db.query("ALTER TABLE workflow_runs ADD COLUMN goal_run_id TEXT").run();
875
- } catch {}
876
- try {
877
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN pid INTEGER").run();
878
- } catch {}
879
- try {
880
- this.db.query("ALTER TABLE workflow_step_runs ADD COLUMN goal_run_id TEXT").run();
881
- } catch {}
861
+ this.addColumnIfMissing("loops", "machine_json", "TEXT");
862
+ this.addColumnIfMissing("loops", "goal_json", "TEXT");
863
+ this.addColumnIfMissing("loop_runs", "goal_run_id", "TEXT");
864
+ this.addColumnIfMissing("workflow_specs", "goal_json", "TEXT");
865
+ this.addColumnIfMissing("workflow_runs", "goal_run_id", "TEXT");
866
+ this.addColumnIfMissing("workflow_step_runs", "pid", "INTEGER");
867
+ this.addColumnIfMissing("workflow_step_runs", "goal_run_id", "TEXT");
882
868
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0001_initial_and_workflows", nowIso());
883
869
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0002_loop_machines", nowIso());
884
870
  this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run("0003_goals", nowIso());
885
871
  }
872
+ addColumnIfMissing(table, column, definition) {
873
+ const columns = this.db.query(`PRAGMA table_info(${table})`).all();
874
+ if (columns.some((c) => c.name === column))
875
+ return;
876
+ this.db.query(`ALTER TABLE ${table} ADD COLUMN ${column} ${definition}`).run();
877
+ }
886
878
  assertDaemonLeaseFence(opts = {}, now = nowIso()) {
887
879
  if (!opts.daemonLeaseId)
888
880
  return;
@@ -2387,7 +2379,7 @@ function agentArgs(target) {
2387
2379
  args.push(...target.extraArgs ?? []);
2388
2380
  return args;
2389
2381
  case "cursor":
2390
- args.push("-p");
2382
+ args.push("agent", "-p");
2391
2383
  if (target.model)
2392
2384
  args.push("--model", target.model);
2393
2385
  if (target.agent)
package/docs/USAGE.md CHANGED
@@ -240,7 +240,7 @@ The adapters intentionally use provider command surfaces instead of pretending e
240
240
  - Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
241
241
  - Codewith uses `codewith --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`.
242
242
  - AI Copilot and OpenCode use `run --format json --pure`.
243
- - Cursor is CLI-first for now via `cursor-agent -p`; treat output as less stable until a stronger public SDK contract is selected.
243
+ - Cursor is CLI-first for now via `cursor-agent agent -p`; treat output as less stable until a stronger public SDK contract is selected.
244
244
  - Codex uses `codex exec --json --ephemeral --ask-for-approval never`.
245
245
  - Agent prompts are sent through child stdin instead of argv so prompt bodies do not appear in process listings.
246
246
  - When `--account` or a step `account` is set, OpenLoops resolves `accounts env <profile> --tool <tool>` before spawning the target, strips inherited tool home/API-key variables, and applies the selected profile only to that process. Missing account profiles fail before the provider binary receives the prompt.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/loops",
3
- "version": "0.3.10",
3
+ "version": "0.3.12",
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",