@hasna/loops 0.3.42 → 0.3.44

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
@@ -3,7 +3,7 @@
3
3
 
4
4
  // src/lib/store.ts
5
5
  import { Database } from "bun:sqlite";
6
- import { mkdirSync as mkdirSync3, mkdtempSync, rmSync } from "fs";
6
+ import { chmodSync, existsSync, mkdirSync as mkdirSync3, mkdtempSync, rmSync } from "fs";
7
7
  import { tmpdir } from "os";
8
8
  import { dirname, join as join3 } from "path";
9
9
 
@@ -824,6 +824,20 @@ function workItemStatusForLoopRun(status, attempt, maxAttempts) {
824
824
  }
825
825
  return;
826
826
  }
827
+ function chmodIfExists(path, mode) {
828
+ try {
829
+ if (existsSync(path))
830
+ chmodSync(path, mode);
831
+ } catch {}
832
+ }
833
+ function ensurePrivateStorePath(file) {
834
+ const dir = dirname(file);
835
+ mkdirSync3(dir, { recursive: true, mode: 448 });
836
+ chmodIfExists(dir, 448);
837
+ chmodIfExists(file, 384);
838
+ chmodIfExists(`${file}-wal`, 384);
839
+ chmodIfExists(`${file}-shm`, 384);
840
+ }
827
841
 
828
842
  class Store {
829
843
  db;
@@ -831,11 +845,13 @@ class Store {
831
845
  constructor(path) {
832
846
  const file = path ?? dbPath();
833
847
  if (file !== ":memory:")
834
- mkdirSync3(dirname(file), { recursive: true, mode: 448 });
848
+ ensurePrivateStorePath(file);
835
849
  this.rootDir = file === ":memory:" ? mkdtempSync(join3(tmpdir(), "open-loops-store-")) : dirname(file);
836
850
  this.db = new Database(file);
837
851
  this.db.exec("PRAGMA busy_timeout = 5000;");
838
852
  this.db.exec("PRAGMA journal_mode = WAL;");
853
+ if (file !== ":memory:")
854
+ ensurePrivateStorePath(file);
839
855
  this.migrate();
840
856
  }
841
857
  migrate() {
@@ -2661,7 +2677,7 @@ class Store {
2661
2677
 
2662
2678
  // src/cli/index.ts
2663
2679
  import { createHash as createHash3, randomUUID } from "crypto";
2664
- import { closeSync, existsSync as existsSync4, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, openSync as openSync2, readFileSync as readFileSync2, realpathSync, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
2680
+ import { closeSync, existsSync as existsSync5, mkdirSync as mkdirSync6, mkdtempSync as mkdtempSync2, openSync as openSync2, readFileSync as readFileSync2, realpathSync, rmSync as rmSync3, writeFileSync as writeFileSync4 } from "fs";
2665
2681
  import { spawnSync as spawnSync5 } from "child_process";
2666
2682
  import { join as join6, resolve as resolve2 } from "path";
2667
2683
  import { tmpdir as tmpdir2 } from "os";
@@ -2791,7 +2807,7 @@ import { resolveMachineCommand } from "@hasna/machines/consumer";
2791
2807
 
2792
2808
  // src/lib/accounts.ts
2793
2809
  import { spawnSync } from "child_process";
2794
- import { existsSync } from "fs";
2810
+ import { existsSync as existsSync2 } from "fs";
2795
2811
  var EXPORT_RE = /^export\s+([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
2796
2812
  function accountToolForProvider(provider) {
2797
2813
  switch (provider) {
@@ -2876,7 +2892,7 @@ function resolveAccountEnv(account, toolHint, env) {
2876
2892
  const profileDir = (accountDirEnvVar(tool) ? accountEnv[accountDirEnvVar(tool)] : undefined) ?? primaryAccountDir(result.stdout);
2877
2893
  if (!profileDir)
2878
2894
  throw new Error(`accounts env returned no profile directory for ${account.profile}/${tool}`);
2879
- if (!existsSync(profileDir))
2895
+ if (!existsSync2(profileDir))
2880
2896
  throw new Error(`account profile directory does not exist for ${account.profile}/${tool}: ${profileDir}`);
2881
2897
  return {
2882
2898
  ...accountEnv,
@@ -3240,7 +3256,7 @@ function agentArgs(target) {
3240
3256
  case "codex":
3241
3257
  if (target.variant)
3242
3258
  args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
3243
- args.push("exec", "--json", "--ephemeral", "--ask-for-approval", "never", "--sandbox", codewithLikeSandbox(target));
3259
+ args.push("exec", "--json", "--ephemeral", "--sandbox", codewithLikeSandbox(target), "--skip-git-repo-check");
3244
3260
  if (isolation === "safe")
3245
3261
  args.push("--ignore-rules");
3246
3262
  if (target.cwd)
@@ -4762,7 +4778,7 @@ async function tick(deps) {
4762
4778
  }
4763
4779
 
4764
4780
  // src/daemon/control.ts
4765
- import { existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
4781
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
4766
4782
  import { hostname } from "os";
4767
4783
  import { dirname as dirname2 } from "path";
4768
4784
 
@@ -4790,7 +4806,7 @@ async function runLoop(opts) {
4790
4806
 
4791
4807
  // src/daemon/control.ts
4792
4808
  function readPid(path = pidFilePath()) {
4793
- if (!existsSync2(path))
4809
+ if (!existsSync3(path))
4794
4810
  return;
4795
4811
  try {
4796
4812
  const pid = Number(readFileSync(path, "utf8").trim());
@@ -5063,7 +5079,7 @@ async function startDaemon(opts) {
5063
5079
  }
5064
5080
 
5065
5081
  // src/daemon/install.ts
5066
- import { chmodSync, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "fs";
5082
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "fs";
5067
5083
  import { spawnSync as spawnSync3 } from "child_process";
5068
5084
  import { dirname as dirname3 } from "path";
5069
5085
  function installStartup(cliEntry, execPath = process.execPath, args = ["daemon", "run"]) {
@@ -5122,7 +5138,7 @@ ${args.map((arg) => ` <string>${arg}</string>`).join(`
5122
5138
  </dict>
5123
5139
  </plist>
5124
5140
  `);
5125
- chmodSync(path, 384);
5141
+ chmodSync2(path, 384);
5126
5142
  return {
5127
5143
  platform: process.platform,
5128
5144
  path,
@@ -5716,7 +5732,7 @@ function buildScriptInventoryReport(store, opts = {}) {
5716
5732
  // package.json
5717
5733
  var package_default = {
5718
5734
  name: "@hasna/loops",
5719
- version: "0.3.42",
5735
+ version: "0.3.44",
5720
5736
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
5721
5737
  type: "module",
5722
5738
  main: "dist/index.js",
@@ -5806,7 +5822,7 @@ function packageVersion() {
5806
5822
 
5807
5823
  // src/lib/templates.ts
5808
5824
  import { execFileSync } from "child_process";
5809
- import { existsSync as existsSync3 } from "fs";
5825
+ import { existsSync as existsSync4 } from "fs";
5810
5826
  import { homedir as homedir3 } from "os";
5811
5827
  import { basename as basename3, isAbsolute, join as join5, relative, resolve } from "path";
5812
5828
  var TODOS_TASK_WORKER_VERIFIER_TEMPLATE_ID = "todos-task-worker-verifier";
@@ -6066,7 +6082,7 @@ function defaultWorktreeRoot(root) {
6066
6082
  return join5(homedir3(), ".hasna", "loops", "worktrees");
6067
6083
  }
6068
6084
  function gitRootFor(path) {
6069
- if (!existsSync3(path))
6085
+ if (!existsSync4(path))
6070
6086
  return;
6071
6087
  try {
6072
6088
  return execFileSync("git", ["-C", path, "rev-parse", "--show-toplevel"], {
@@ -6526,7 +6542,7 @@ function renderLifecycleBoundedTemplate(id, values) {
6526
6542
  variant: values.variant,
6527
6543
  agent: values.agent,
6528
6544
  permissionMode: values.permissionMode,
6529
- sandbox: values.sandbox,
6545
+ sandbox: values.sandbox ?? (id === REPORT_ONLY_TEMPLATE_ID ? "read-only" : undefined),
6530
6546
  manualBreakGlass: booleanVar(values.manualBreakGlass),
6531
6547
  worktreeMode: values.worktreeMode ?? (id === REPORT_ONLY_TEMPLATE_ID ? "main" : "required"),
6532
6548
  worktreeRoot: values.worktreeRoot,
@@ -7026,7 +7042,7 @@ function routeCursorsPath() {
7026
7042
  }
7027
7043
  function readRouteCursors() {
7028
7044
  const path = routeCursorsPath();
7029
- if (!existsSync4(path))
7045
+ if (!existsSync5(path))
7030
7046
  return {};
7031
7047
  try {
7032
7048
  const parsed = JSON.parse(readFileSync2(path, "utf8"));
@@ -7329,6 +7345,31 @@ function generatedRouteSandboxPreflight(workflow) {
7329
7345
  }
7330
7346
  return checks;
7331
7347
  }
7348
+ function generatedRouteWorkflowSignature(workflow) {
7349
+ return JSON.stringify({
7350
+ version: workflow.version ?? 1,
7351
+ goal: workflow.goal ?? null,
7352
+ steps: workflow.steps
7353
+ });
7354
+ }
7355
+ function canReuseGeneratedRouteWorkflow(existing, generated) {
7356
+ if (generatedRouteWorkflowSignature(existing) !== generatedRouteWorkflowSignature(generated))
7357
+ return false;
7358
+ try {
7359
+ generatedRouteSandboxPreflight(existing);
7360
+ return true;
7361
+ } catch {
7362
+ return false;
7363
+ }
7364
+ }
7365
+ function routeWorkflowForStorage(store, workflowBody) {
7366
+ const existingWorkflow = store.findWorkflowByName(workflowBody.name);
7367
+ if (existingWorkflow && canReuseGeneratedRouteWorkflow(existingWorkflow, workflowBody))
7368
+ return existingWorkflow;
7369
+ if (existingWorkflow)
7370
+ store.archiveWorkflow(existingWorkflow.id);
7371
+ return store.createWorkflow(workflowBody);
7372
+ }
7332
7373
  function routeThrottleLimitsFromOpts(opts) {
7333
7374
  return {
7334
7375
  maxActive: positiveInteger(opts.maxActive, "--max-active"),
@@ -7578,8 +7619,7 @@ function routeTodosTaskEvent(event, opts) {
7578
7619
  }
7579
7620
  const store = new Store;
7580
7621
  try {
7581
- const existingWorkflowForPreflight = store.findWorkflowByName(workflowBody.name);
7582
- const workflowPreflightSpec = existingWorkflowForPreflight ?? workflowSpecForPreflight(workflowBody, "event-preflight");
7622
+ const workflowPreflightSpec = workflowSpecForPreflight(workflowBody, "event-preflight");
7583
7623
  generatedRouteSandboxPreflight(workflowPreflightSpec);
7584
7624
  const preflight = opts.preflight ? preflightStoredWorkflow(workflowPreflightSpec, {
7585
7625
  name: workflowBody.name,
@@ -7591,8 +7631,8 @@ function routeTodosTaskEvent(event, opts) {
7591
7631
  const existingItem = store.findWorkflowWorkItem("todos-task", idempotencyKey);
7592
7632
  if (existingItem?.loopId && ["admitted", "running", "succeeded"].includes(existingItem.status)) {
7593
7633
  const existingLoop = store.getLoop(existingItem.loopId);
7594
- const existingWorkflow2 = existingItem.workflowId ? store.getWorkflow(existingItem.workflowId) : undefined;
7595
- return { kind: "deduped", existingItem, existingLoop, existingWorkflow: existingWorkflow2, invocation };
7634
+ const existingWorkflow = existingItem.workflowId ? store.getWorkflow(existingItem.workflowId) : undefined;
7635
+ return { kind: "deduped", existingItem, existingLoop, existingWorkflow, invocation };
7596
7636
  }
7597
7637
  const throttle = hasThrottleLimits(throttleLimits) ? routeThrottleDecision(store, { projectPath: routeProjectPath, projectGroup, limits: throttleLimits }) : undefined;
7598
7638
  const workItem = store.upsertWorkflowWorkItem({
@@ -7603,8 +7643,7 @@ function routeTodosTaskEvent(event, opts) {
7603
7643
  });
7604
7644
  if (throttle && !throttle.allowed)
7605
7645
  return { kind: "throttled", invocation, workItem, throttle };
7606
- const existingWorkflow = store.findWorkflowByName(workflowBody.name);
7607
- const workflow = existingWorkflow ?? store.createWorkflow(workflowBody);
7646
+ const workflow = routeWorkflowForStorage(store, workflowBody);
7608
7647
  const loop = store.createLoop({
7609
7648
  ...loopInput,
7610
7649
  target: {
@@ -7790,8 +7829,7 @@ function routeGenericEvent(event, opts) {
7790
7829
  }
7791
7830
  const store = new Store;
7792
7831
  try {
7793
- const existingWorkflowForPreflight = store.findWorkflowByName(workflowBody.name);
7794
- const workflowPreflightSpec = existingWorkflowForPreflight ?? workflowSpecForPreflight(workflowBody, "event-preflight");
7832
+ const workflowPreflightSpec = workflowSpecForPreflight(workflowBody, "event-preflight");
7795
7833
  generatedRouteSandboxPreflight(workflowPreflightSpec);
7796
7834
  const preflight = opts.preflight ? preflightStoredWorkflow(workflowPreflightSpec, {
7797
7835
  name: workflowBody.name,
@@ -7803,8 +7841,8 @@ function routeGenericEvent(event, opts) {
7803
7841
  const existingItem = store.findWorkflowWorkItem("generic-event", idempotencyKey);
7804
7842
  if (existingItem?.loopId && ["admitted", "running", "succeeded"].includes(existingItem.status)) {
7805
7843
  const existingLoop = store.getLoop(existingItem.loopId);
7806
- const existingWorkflow2 = existingItem.workflowId ? store.getWorkflow(existingItem.workflowId) : undefined;
7807
- return { kind: "deduped", existingItem, existingLoop, existingWorkflow: existingWorkflow2, invocation };
7844
+ const existingWorkflow = existingItem.workflowId ? store.getWorkflow(existingItem.workflowId) : undefined;
7845
+ return { kind: "deduped", existingItem, existingLoop, existingWorkflow, invocation };
7808
7846
  }
7809
7847
  const throttle = hasThrottleLimits(throttleLimits) ? routeThrottleDecision(store, { projectPath: routeProjectPath, projectGroup, limits: throttleLimits }) : undefined;
7810
7848
  const workItem = store.upsertWorkflowWorkItem({
@@ -7815,8 +7853,7 @@ function routeGenericEvent(event, opts) {
7815
7853
  });
7816
7854
  if (throttle && !throttle.allowed)
7817
7855
  return { kind: "throttled", invocation, workItem, throttle };
7818
- const existingWorkflow = store.findWorkflowByName(workflowBody.name);
7819
- const workflow = existingWorkflow ?? store.createWorkflow(workflowBody);
7856
+ const workflow = routeWorkflowForStorage(store, workflowBody);
7820
7857
  const loop = store.createLoop({
7821
7858
  ...loopInput,
7822
7859
  target: {
@@ -8605,8 +8642,7 @@ eventsHandle.command("generic").description("create a one-shot worker/verifier w
8605
8642
  }
8606
8643
  const store = new Store;
8607
8644
  try {
8608
- const existingWorkflowForPreflight = store.findWorkflowByName(workflowBody.name);
8609
- const workflowPreflightSpec = existingWorkflowForPreflight ?? workflowSpecForPreflight(workflowBody, "event-preflight");
8645
+ const workflowPreflightSpec = workflowSpecForPreflight(workflowBody, "event-preflight");
8610
8646
  generatedRouteSandboxPreflight(workflowPreflightSpec);
8611
8647
  const preflight = opts.preflight ? preflightStoredWorkflow(workflowPreflightSpec, {
8612
8648
  name: workflowBody.name,
@@ -8618,8 +8654,8 @@ eventsHandle.command("generic").description("create a one-shot worker/verifier w
8618
8654
  const existingItem = store.findWorkflowWorkItem("generic-event", idempotencyKey);
8619
8655
  if (existingItem?.loopId && ["admitted", "running", "succeeded"].includes(existingItem.status)) {
8620
8656
  const existingLoop = store.getLoop(existingItem.loopId);
8621
- const existingWorkflow2 = existingItem.workflowId ? store.getWorkflow(existingItem.workflowId) : undefined;
8622
- return { kind: "deduped", existingItem, existingLoop, existingWorkflow: existingWorkflow2, invocation };
8657
+ const existingWorkflow = existingItem.workflowId ? store.getWorkflow(existingItem.workflowId) : undefined;
8658
+ return { kind: "deduped", existingItem, existingLoop, existingWorkflow, invocation };
8623
8659
  }
8624
8660
  const throttle = hasThrottleLimits(throttleLimits) ? routeThrottleDecision(store, { projectPath: routeProjectPath, projectGroup, limits: throttleLimits }) : undefined;
8625
8661
  const workItem = store.upsertWorkflowWorkItem({
@@ -8630,8 +8666,7 @@ eventsHandle.command("generic").description("create a one-shot worker/verifier w
8630
8666
  });
8631
8667
  if (throttle && !throttle.allowed)
8632
8668
  return { kind: "throttled", invocation, workItem, throttle };
8633
- const existingWorkflow = store.findWorkflowByName(workflowBody.name);
8634
- const workflow = existingWorkflow ?? store.createWorkflow(workflowBody);
8669
+ const workflow = routeWorkflowForStorage(store, workflowBody);
8635
8670
  const loop = store.createLoop({
8636
8671
  ...loopInput,
8637
8672
  target: {
@@ -9519,7 +9554,7 @@ ${result.instructions.join(`
9519
9554
  });
9520
9555
  daemon.command("logs").option("-n, --lines <n>", "lines", "80").action((opts) => {
9521
9556
  const path = daemonLogPath();
9522
- if (!existsSync4(path)) {
9557
+ if (!existsSync5(path)) {
9523
9558
  console.log("");
9524
9559
  return;
9525
9560
  }
@@ -3,7 +3,7 @@
3
3
 
4
4
  // src/lib/store.ts
5
5
  import { Database } from "bun:sqlite";
6
- import { mkdirSync as mkdirSync3, mkdtempSync, rmSync } from "fs";
6
+ import { chmodSync, existsSync, mkdirSync as mkdirSync3, mkdtempSync, rmSync } from "fs";
7
7
  import { tmpdir } from "os";
8
8
  import { dirname, join as join3 } from "path";
9
9
 
@@ -824,6 +824,20 @@ function workItemStatusForLoopRun(status, attempt, maxAttempts) {
824
824
  }
825
825
  return;
826
826
  }
827
+ function chmodIfExists(path, mode) {
828
+ try {
829
+ if (existsSync(path))
830
+ chmodSync(path, mode);
831
+ } catch {}
832
+ }
833
+ function ensurePrivateStorePath(file) {
834
+ const dir = dirname(file);
835
+ mkdirSync3(dir, { recursive: true, mode: 448 });
836
+ chmodIfExists(dir, 448);
837
+ chmodIfExists(file, 384);
838
+ chmodIfExists(`${file}-wal`, 384);
839
+ chmodIfExists(`${file}-shm`, 384);
840
+ }
827
841
 
828
842
  class Store {
829
843
  db;
@@ -831,11 +845,13 @@ class Store {
831
845
  constructor(path) {
832
846
  const file = path ?? dbPath();
833
847
  if (file !== ":memory:")
834
- mkdirSync3(dirname(file), { recursive: true, mode: 448 });
848
+ ensurePrivateStorePath(file);
835
849
  this.rootDir = file === ":memory:" ? mkdtempSync(join3(tmpdir(), "open-loops-store-")) : dirname(file);
836
850
  this.db = new Database(file);
837
851
  this.db.exec("PRAGMA busy_timeout = 5000;");
838
852
  this.db.exec("PRAGMA journal_mode = WAL;");
853
+ if (file !== ":memory:")
854
+ ensurePrivateStorePath(file);
839
855
  this.migrate();
840
856
  }
841
857
  migrate() {
@@ -2675,7 +2691,7 @@ import { resolveMachineCommand } from "@hasna/machines/consumer";
2675
2691
 
2676
2692
  // src/lib/accounts.ts
2677
2693
  import { spawnSync } from "child_process";
2678
- import { existsSync } from "fs";
2694
+ import { existsSync as existsSync2 } from "fs";
2679
2695
  var EXPORT_RE = /^export\s+([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
2680
2696
  function accountToolForProvider(provider) {
2681
2697
  switch (provider) {
@@ -2760,7 +2776,7 @@ function resolveAccountEnv(account, toolHint, env) {
2760
2776
  const profileDir = (accountDirEnvVar(tool) ? accountEnv[accountDirEnvVar(tool)] : undefined) ?? primaryAccountDir(result.stdout);
2761
2777
  if (!profileDir)
2762
2778
  throw new Error(`accounts env returned no profile directory for ${account.profile}/${tool}`);
2763
- if (!existsSync(profileDir))
2779
+ if (!existsSync2(profileDir))
2764
2780
  throw new Error(`account profile directory does not exist for ${account.profile}/${tool}: ${profileDir}`);
2765
2781
  return {
2766
2782
  ...accountEnv,
@@ -3124,7 +3140,7 @@ function agentArgs(target) {
3124
3140
  case "codex":
3125
3141
  if (target.variant)
3126
3142
  args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
3127
- args.push("exec", "--json", "--ephemeral", "--ask-for-approval", "never", "--sandbox", codewithLikeSandbox(target));
3143
+ args.push("exec", "--json", "--ephemeral", "--sandbox", codewithLikeSandbox(target), "--skip-git-repo-check");
3128
3144
  if (isolation === "safe")
3129
3145
  args.push("--ignore-rules");
3130
3146
  if (target.cwd)
@@ -4646,7 +4662,7 @@ async function tick(deps) {
4646
4662
  }
4647
4663
 
4648
4664
  // src/daemon/control.ts
4649
- import { existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
4665
+ import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
4650
4666
  import { hostname } from "os";
4651
4667
  import { dirname as dirname2 } from "path";
4652
4668
 
@@ -4674,7 +4690,7 @@ async function runLoop(opts) {
4674
4690
 
4675
4691
  // src/daemon/control.ts
4676
4692
  function readPid(path = pidFilePath()) {
4677
- if (!existsSync2(path))
4693
+ if (!existsSync3(path))
4678
4694
  return;
4679
4695
  try {
4680
4696
  const pid = Number(readFileSync(path, "utf8").trim());
@@ -4944,7 +4960,7 @@ async function startDaemon(opts) {
4944
4960
  }
4945
4961
 
4946
4962
  // src/daemon/install.ts
4947
- import { chmodSync, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "fs";
4963
+ import { chmodSync as chmodSync2, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "fs";
4948
4964
  import { spawnSync as spawnSync3 } from "child_process";
4949
4965
  import { dirname as dirname3 } from "path";
4950
4966
  function installStartup(cliEntry, execPath = process.execPath, args = ["daemon", "run"]) {
@@ -5003,7 +5019,7 @@ ${args.map((arg) => ` <string>${arg}</string>`).join(`
5003
5019
  </dict>
5004
5020
  </plist>
5005
5021
  `);
5006
- chmodSync(path, 384);
5022
+ chmodSync2(path, 384);
5007
5023
  return {
5008
5024
  platform: process.platform,
5009
5025
  path,
@@ -5030,7 +5046,7 @@ function enableStartup(result) {
5030
5046
  // package.json
5031
5047
  var package_default = {
5032
5048
  name: "@hasna/loops",
5033
- version: "0.3.42",
5049
+ version: "0.3.44",
5034
5050
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
5035
5051
  type: "module",
5036
5052
  main: "dist/index.js",
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // src/lib/store.ts
3
3
  import { Database } from "bun:sqlite";
4
- import { mkdirSync as mkdirSync3, mkdtempSync, rmSync } from "fs";
4
+ import { chmodSync, existsSync, mkdirSync as mkdirSync3, mkdtempSync, rmSync } from "fs";
5
5
  import { tmpdir } from "os";
6
6
  import { dirname, join as join3 } from "path";
7
7
 
@@ -822,6 +822,20 @@ function workItemStatusForLoopRun(status, attempt, maxAttempts) {
822
822
  }
823
823
  return;
824
824
  }
825
+ function chmodIfExists(path, mode) {
826
+ try {
827
+ if (existsSync(path))
828
+ chmodSync(path, mode);
829
+ } catch {}
830
+ }
831
+ function ensurePrivateStorePath(file) {
832
+ const dir = dirname(file);
833
+ mkdirSync3(dir, { recursive: true, mode: 448 });
834
+ chmodIfExists(dir, 448);
835
+ chmodIfExists(file, 384);
836
+ chmodIfExists(`${file}-wal`, 384);
837
+ chmodIfExists(`${file}-shm`, 384);
838
+ }
825
839
 
826
840
  class Store {
827
841
  db;
@@ -829,11 +843,13 @@ class Store {
829
843
  constructor(path) {
830
844
  const file = path ?? dbPath();
831
845
  if (file !== ":memory:")
832
- mkdirSync3(dirname(file), { recursive: true, mode: 448 });
846
+ ensurePrivateStorePath(file);
833
847
  this.rootDir = file === ":memory:" ? mkdtempSync(join3(tmpdir(), "open-loops-store-")) : dirname(file);
834
848
  this.db = new Database(file);
835
849
  this.db.exec("PRAGMA busy_timeout = 5000;");
836
850
  this.db.exec("PRAGMA journal_mode = WAL;");
851
+ if (file !== ":memory:")
852
+ ensurePrivateStorePath(file);
837
853
  this.migrate();
838
854
  }
839
855
  migrate() {
@@ -2665,7 +2681,7 @@ import { resolveMachineCommand } from "@hasna/machines/consumer";
2665
2681
 
2666
2682
  // src/lib/accounts.ts
2667
2683
  import { spawnSync } from "child_process";
2668
- import { existsSync } from "fs";
2684
+ import { existsSync as existsSync2 } from "fs";
2669
2685
  var EXPORT_RE = /^export\s+([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
2670
2686
  function accountToolForProvider(provider) {
2671
2687
  switch (provider) {
@@ -2750,7 +2766,7 @@ function resolveAccountEnv(account, toolHint, env) {
2750
2766
  const profileDir = (accountDirEnvVar(tool) ? accountEnv[accountDirEnvVar(tool)] : undefined) ?? primaryAccountDir(result.stdout);
2751
2767
  if (!profileDir)
2752
2768
  throw new Error(`accounts env returned no profile directory for ${account.profile}/${tool}`);
2753
- if (!existsSync(profileDir))
2769
+ if (!existsSync2(profileDir))
2754
2770
  throw new Error(`account profile directory does not exist for ${account.profile}/${tool}: ${profileDir}`);
2755
2771
  return {
2756
2772
  ...accountEnv,
@@ -3114,7 +3130,7 @@ function agentArgs(target) {
3114
3130
  case "codex":
3115
3131
  if (target.variant)
3116
3132
  args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
3117
- args.push("exec", "--json", "--ephemeral", "--ask-for-approval", "never", "--sandbox", codewithLikeSandbox(target));
3133
+ args.push("exec", "--json", "--ephemeral", "--sandbox", codewithLikeSandbox(target), "--skip-git-repo-check");
3118
3134
  if (isolation === "safe")
3119
3135
  args.push("--ignore-rules");
3120
3136
  if (target.cwd)
@@ -4767,7 +4783,7 @@ function openAutomationsRuntimeBinding(overrides = {}) {
4767
4783
  }
4768
4784
  // src/lib/templates.ts
4769
4785
  import { execFileSync } from "child_process";
4770
- import { existsSync as existsSync2 } from "fs";
4786
+ import { existsSync as existsSync3 } from "fs";
4771
4787
  import { homedir as homedir3 } from "os";
4772
4788
  import { basename as basename2, isAbsolute, join as join5, relative, resolve } from "path";
4773
4789
  var TODOS_TASK_WORKER_VERIFIER_TEMPLATE_ID = "todos-task-worker-verifier";
@@ -5027,7 +5043,7 @@ function defaultWorktreeRoot(root) {
5027
5043
  return join5(homedir3(), ".hasna", "loops", "worktrees");
5028
5044
  }
5029
5045
  function gitRootFor(path) {
5030
- if (!existsSync2(path))
5046
+ if (!existsSync3(path))
5031
5047
  return;
5032
5048
  try {
5033
5049
  return execFileSync("git", ["-C", path, "rev-parse", "--show-toplevel"], {
@@ -5487,7 +5503,7 @@ function renderLifecycleBoundedTemplate(id, values) {
5487
5503
  variant: values.variant,
5488
5504
  agent: values.agent,
5489
5505
  permissionMode: values.permissionMode,
5490
- sandbox: values.sandbox,
5506
+ sandbox: values.sandbox ?? (id === REPORT_ONLY_TEMPLATE_ID ? "read-only" : undefined),
5491
5507
  manualBreakGlass: booleanVar(values.manualBreakGlass),
5492
5508
  worktreeMode: values.worktreeMode ?? (id === REPORT_ONLY_TEMPLATE_ID ? "main" : "required"),
5493
5509
  worktreeRoot: values.worktreeRoot,
@@ -5704,7 +5720,7 @@ import { spawnSync as spawnSync3 } from "child_process";
5704
5720
  import { accessSync as accessSync2, constants as constants2 } from "fs";
5705
5721
 
5706
5722
  // src/daemon/control.ts
5707
- import { existsSync as existsSync3, mkdirSync as mkdirSync4, readFileSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
5723
+ import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
5708
5724
  import { hostname } from "os";
5709
5725
  import { dirname as dirname2 } from "path";
5710
5726
 
@@ -5732,7 +5748,7 @@ async function runLoop(opts) {
5732
5748
 
5733
5749
  // src/daemon/control.ts
5734
5750
  function readPid(path = pidFilePath()) {
5735
- if (!existsSync3(path))
5751
+ if (!existsSync4(path))
5736
5752
  return;
5737
5753
  try {
5738
5754
  const pid = Number(readFileSync(path, "utf8").trim());
package/dist/lib/store.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // src/lib/store.ts
3
3
  import { Database } from "bun:sqlite";
4
- import { mkdirSync as mkdirSync3, mkdtempSync, rmSync } from "fs";
4
+ import { chmodSync, existsSync, mkdirSync as mkdirSync3, mkdtempSync, rmSync } from "fs";
5
5
  import { tmpdir } from "os";
6
6
  import { dirname, join as join3 } from "path";
7
7
 
@@ -822,6 +822,20 @@ function workItemStatusForLoopRun(status, attempt, maxAttempts) {
822
822
  }
823
823
  return;
824
824
  }
825
+ function chmodIfExists(path, mode) {
826
+ try {
827
+ if (existsSync(path))
828
+ chmodSync(path, mode);
829
+ } catch {}
830
+ }
831
+ function ensurePrivateStorePath(file) {
832
+ const dir = dirname(file);
833
+ mkdirSync3(dir, { recursive: true, mode: 448 });
834
+ chmodIfExists(dir, 448);
835
+ chmodIfExists(file, 384);
836
+ chmodIfExists(`${file}-wal`, 384);
837
+ chmodIfExists(`${file}-shm`, 384);
838
+ }
825
839
 
826
840
  class Store {
827
841
  db;
@@ -829,11 +843,13 @@ class Store {
829
843
  constructor(path) {
830
844
  const file = path ?? dbPath();
831
845
  if (file !== ":memory:")
832
- mkdirSync3(dirname(file), { recursive: true, mode: 448 });
846
+ ensurePrivateStorePath(file);
833
847
  this.rootDir = file === ":memory:" ? mkdtempSync(join3(tmpdir(), "open-loops-store-")) : dirname(file);
834
848
  this.db = new Database(file);
835
849
  this.db.exec("PRAGMA busy_timeout = 5000;");
836
850
  this.db.exec("PRAGMA journal_mode = WAL;");
851
+ if (file !== ":memory:")
852
+ ensurePrivateStorePath(file);
837
853
  this.migrate();
838
854
  }
839
855
  migrate() {
package/dist/sdk/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // @bun
2
2
  // src/lib/store.ts
3
3
  import { Database } from "bun:sqlite";
4
- import { mkdirSync as mkdirSync3, mkdtempSync, rmSync } from "fs";
4
+ import { chmodSync, existsSync, mkdirSync as mkdirSync3, mkdtempSync, rmSync } from "fs";
5
5
  import { tmpdir } from "os";
6
6
  import { dirname, join as join3 } from "path";
7
7
 
@@ -822,6 +822,20 @@ function workItemStatusForLoopRun(status, attempt, maxAttempts) {
822
822
  }
823
823
  return;
824
824
  }
825
+ function chmodIfExists(path, mode) {
826
+ try {
827
+ if (existsSync(path))
828
+ chmodSync(path, mode);
829
+ } catch {}
830
+ }
831
+ function ensurePrivateStorePath(file) {
832
+ const dir = dirname(file);
833
+ mkdirSync3(dir, { recursive: true, mode: 448 });
834
+ chmodIfExists(dir, 448);
835
+ chmodIfExists(file, 384);
836
+ chmodIfExists(`${file}-wal`, 384);
837
+ chmodIfExists(`${file}-shm`, 384);
838
+ }
825
839
 
826
840
  class Store {
827
841
  db;
@@ -829,11 +843,13 @@ class Store {
829
843
  constructor(path) {
830
844
  const file = path ?? dbPath();
831
845
  if (file !== ":memory:")
832
- mkdirSync3(dirname(file), { recursive: true, mode: 448 });
846
+ ensurePrivateStorePath(file);
833
847
  this.rootDir = file === ":memory:" ? mkdtempSync(join3(tmpdir(), "open-loops-store-")) : dirname(file);
834
848
  this.db = new Database(file);
835
849
  this.db.exec("PRAGMA busy_timeout = 5000;");
836
850
  this.db.exec("PRAGMA journal_mode = WAL;");
851
+ if (file !== ":memory:")
852
+ ensurePrivateStorePath(file);
837
853
  this.migrate();
838
854
  }
839
855
  migrate() {
@@ -2665,7 +2681,7 @@ import { resolveMachineCommand } from "@hasna/machines/consumer";
2665
2681
 
2666
2682
  // src/lib/accounts.ts
2667
2683
  import { spawnSync } from "child_process";
2668
- import { existsSync } from "fs";
2684
+ import { existsSync as existsSync2 } from "fs";
2669
2685
  var EXPORT_RE = /^export\s+([A-Za-z_][A-Za-z0-9_]*)=(.*)$/;
2670
2686
  function accountToolForProvider(provider) {
2671
2687
  switch (provider) {
@@ -2750,7 +2766,7 @@ function resolveAccountEnv(account, toolHint, env) {
2750
2766
  const profileDir = (accountDirEnvVar(tool) ? accountEnv[accountDirEnvVar(tool)] : undefined) ?? primaryAccountDir(result.stdout);
2751
2767
  if (!profileDir)
2752
2768
  throw new Error(`accounts env returned no profile directory for ${account.profile}/${tool}`);
2753
- if (!existsSync(profileDir))
2769
+ if (!existsSync2(profileDir))
2754
2770
  throw new Error(`account profile directory does not exist for ${account.profile}/${tool}: ${profileDir}`);
2755
2771
  return {
2756
2772
  ...accountEnv,
@@ -3114,7 +3130,7 @@ function agentArgs(target) {
3114
3130
  case "codex":
3115
3131
  if (target.variant)
3116
3132
  args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
3117
- args.push("exec", "--json", "--ephemeral", "--ask-for-approval", "never", "--sandbox", codewithLikeSandbox(target));
3133
+ args.push("exec", "--json", "--ephemeral", "--sandbox", codewithLikeSandbox(target), "--skip-git-repo-check");
3118
3134
  if (isolation === "safe")
3119
3135
  args.push("--ignore-rules");
3120
3136
  if (target.cwd)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/loops",
3
- "version": "0.3.42",
3
+ "version": "0.3.44",
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",