@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 +69 -34
- package/dist/daemon/index.js +26 -10
- package/dist/index.js +26 -10
- package/dist/lib/store.js +18 -2
- package/dist/sdk/index.js +21 -5
- package/package.json +1 -1
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
|
-
|
|
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
|
|
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 (!
|
|
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", "--
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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.
|
|
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
|
|
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 (!
|
|
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 (!
|
|
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
|
|
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
|
|
7595
|
-
return { kind: "deduped", existingItem, existingLoop, existingWorkflow
|
|
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
|
|
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
|
|
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
|
|
7807
|
-
return { kind: "deduped", existingItem, existingLoop, existingWorkflow
|
|
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
|
|
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
|
|
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
|
|
8622
|
-
return { kind: "deduped", existingItem, existingLoop, existingWorkflow
|
|
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
|
|
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 (!
|
|
9557
|
+
if (!existsSync5(path)) {
|
|
9523
9558
|
console.log("");
|
|
9524
9559
|
return;
|
|
9525
9560
|
}
|
package/dist/daemon/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
|
-
|
|
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 (!
|
|
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", "--
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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 (!
|
|
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", "--
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
-
|
|
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
|
-
|
|
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 (!
|
|
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", "--
|
|
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