@hasna/loops 0.3.12 → 0.3.14
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/README.md +64 -2
- package/dist/cli/index.js +712 -15
- package/dist/daemon/index.js +154 -7
- package/dist/index.d.ts +1 -0
- package/dist/index.js +414 -8
- package/dist/lib/store.js +29 -0
- package/dist/lib/templates.d.ts +41 -0
- package/dist/sdk/index.js +152 -6
- package/dist/types.d.ts +19 -0
- package/docs/USAGE.md +64 -2
- package/package.json +2 -1
package/dist/daemon/index.js
CHANGED
|
@@ -370,6 +370,35 @@ function validateTarget(value, label) {
|
|
|
370
370
|
if (value.provider !== "codewith")
|
|
371
371
|
throw new Error(`${label}.authProfile is currently supported only for provider codewith`);
|
|
372
372
|
}
|
|
373
|
+
if (value.variant !== undefined)
|
|
374
|
+
assertString(value.variant, `${label}.variant`);
|
|
375
|
+
if (value.permissionMode !== undefined) {
|
|
376
|
+
assertString(value.permissionMode, `${label}.permissionMode`);
|
|
377
|
+
const permissionModes = ["default", "plan", "auto", "bypass"];
|
|
378
|
+
if (!permissionModes.includes(value.permissionMode)) {
|
|
379
|
+
throw new Error(`${label}.permissionMode must be one of ${permissionModes.join(", ")}`);
|
|
380
|
+
}
|
|
381
|
+
if (value.permissionMode === "plan" && !["claude", "cursor"].includes(value.provider)) {
|
|
382
|
+
throw new Error(`${label}.permissionMode plan is currently supported only for provider claude or cursor`);
|
|
383
|
+
}
|
|
384
|
+
if (value.permissionMode === "auto" && value.provider !== "claude") {
|
|
385
|
+
throw new Error(`${label}.permissionMode auto is currently supported only for provider claude`);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
if (value.sandbox !== undefined) {
|
|
389
|
+
assertString(value.sandbox, `${label}.sandbox`);
|
|
390
|
+
const codexLike = ["read-only", "workspace-write", "danger-full-access"];
|
|
391
|
+
const cursorLike = ["enabled", "disabled"];
|
|
392
|
+
if (["codewith", "codex"].includes(value.provider)) {
|
|
393
|
+
if (!codexLike.includes(value.sandbox))
|
|
394
|
+
throw new Error(`${label}.sandbox must be one of ${codexLike.join(", ")}`);
|
|
395
|
+
} else if (value.provider === "cursor") {
|
|
396
|
+
if (!cursorLike.includes(value.sandbox))
|
|
397
|
+
throw new Error(`${label}.sandbox must be one of ${cursorLike.join(", ")}`);
|
|
398
|
+
} else {
|
|
399
|
+
throw new Error(`${label}.sandbox is currently supported only for provider codewith, codex, or cursor`);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
373
402
|
return value;
|
|
374
403
|
}
|
|
375
404
|
throw new Error(`${label}.type must be command or agent`);
|
|
@@ -2363,7 +2392,7 @@ function providerCommand(provider) {
|
|
|
2363
2392
|
case "claude":
|
|
2364
2393
|
return "claude";
|
|
2365
2394
|
case "cursor":
|
|
2366
|
-
return "
|
|
2395
|
+
return "sh";
|
|
2367
2396
|
case "codewith":
|
|
2368
2397
|
return "codewith";
|
|
2369
2398
|
case "aicopilot":
|
|
@@ -2374,22 +2403,107 @@ function providerCommand(provider) {
|
|
|
2374
2403
|
return "codex";
|
|
2375
2404
|
}
|
|
2376
2405
|
}
|
|
2406
|
+
function codewithLikeSandbox(target) {
|
|
2407
|
+
const sandbox = target.sandbox ?? (target.permissionMode === "bypass" ? "danger-full-access" : "workspace-write");
|
|
2408
|
+
if (sandbox !== "read-only" && sandbox !== "workspace-write" && sandbox !== "danger-full-access") {
|
|
2409
|
+
throw new Error(`${target.provider} sandbox must be read-only, workspace-write, or danger-full-access`);
|
|
2410
|
+
}
|
|
2411
|
+
return sandbox;
|
|
2412
|
+
}
|
|
2413
|
+
function configStringValue(value) {
|
|
2414
|
+
return JSON.stringify(value);
|
|
2415
|
+
}
|
|
2416
|
+
function assertStringOption(value, label) {
|
|
2417
|
+
if (value !== undefined && typeof value !== "string")
|
|
2418
|
+
throw new Error(`${label} must be a string`);
|
|
2419
|
+
}
|
|
2420
|
+
function assertSupportedAgentOptions(target) {
|
|
2421
|
+
assertStringOption(target.variant, `${target.provider}.variant`);
|
|
2422
|
+
assertStringOption(target.model, `${target.provider}.model`);
|
|
2423
|
+
assertStringOption(target.agent, `${target.provider}.agent`);
|
|
2424
|
+
assertStringOption(target.authProfile, `${target.provider}.authProfile`);
|
|
2425
|
+
if (target.authProfile !== undefined && target.provider !== "codewith") {
|
|
2426
|
+
throw new Error(`${target.provider}.authProfile is supported only for codewith`);
|
|
2427
|
+
}
|
|
2428
|
+
if (target.permissionMode && !["default", "plan", "auto", "bypass"].includes(target.permissionMode)) {
|
|
2429
|
+
throw new Error(`${target.provider}.permissionMode must be default, plan, auto, or bypass`);
|
|
2430
|
+
}
|
|
2431
|
+
if (target.sandbox && !["read-only", "workspace-write", "danger-full-access", "enabled", "disabled"].includes(target.sandbox)) {
|
|
2432
|
+
throw new Error(`${target.provider}.sandbox is not supported: ${target.sandbox}`);
|
|
2433
|
+
}
|
|
2434
|
+
if (["codewith", "codex"].includes(target.provider)) {
|
|
2435
|
+
if (target.permissionMode && !["default", "bypass"].includes(target.permissionMode)) {
|
|
2436
|
+
throw new Error(`${target.provider}.permissionMode supports only default or bypass`);
|
|
2437
|
+
}
|
|
2438
|
+
if (target.sandbox)
|
|
2439
|
+
codewithLikeSandbox(target);
|
|
2440
|
+
return;
|
|
2441
|
+
}
|
|
2442
|
+
if (target.provider === "claude") {
|
|
2443
|
+
if (target.sandbox !== undefined)
|
|
2444
|
+
throw new Error("claude.sandbox is not supported");
|
|
2445
|
+
return;
|
|
2446
|
+
}
|
|
2447
|
+
if (target.provider === "cursor") {
|
|
2448
|
+
if (target.permissionMode === "auto")
|
|
2449
|
+
throw new Error("cursor.permissionMode auto is not supported; use provider-specific extraArgs for Cursor auto-review");
|
|
2450
|
+
if (target.sandbox !== undefined && target.sandbox !== "enabled" && target.sandbox !== "disabled") {
|
|
2451
|
+
throw new Error("cursor.sandbox must be enabled or disabled");
|
|
2452
|
+
}
|
|
2453
|
+
return;
|
|
2454
|
+
}
|
|
2455
|
+
if (target.permissionMode && !["default", "bypass"].includes(target.permissionMode)) {
|
|
2456
|
+
throw new Error(`${target.provider}.permissionMode supports only default or bypass`);
|
|
2457
|
+
}
|
|
2458
|
+
if (target.sandbox !== undefined)
|
|
2459
|
+
throw new Error(`${target.provider}.sandbox is not supported`);
|
|
2460
|
+
}
|
|
2377
2461
|
function agentArgs(target) {
|
|
2462
|
+
assertSupportedAgentOptions(target);
|
|
2378
2463
|
const isolation = target.configIsolation ?? "safe";
|
|
2464
|
+
const permissionMode = target.permissionMode ?? "default";
|
|
2379
2465
|
const args = [];
|
|
2380
2466
|
switch (target.provider) {
|
|
2381
2467
|
case "claude":
|
|
2382
2468
|
if (isolation === "safe")
|
|
2383
2469
|
args.push("--safe-mode", "--setting-sources", "local", "--no-session-persistence");
|
|
2470
|
+
if (permissionMode !== "default") {
|
|
2471
|
+
const mode = permissionMode === "bypass" ? "bypassPermissions" : permissionMode === "plan" || permissionMode === "auto" ? permissionMode : undefined;
|
|
2472
|
+
if (mode)
|
|
2473
|
+
args.push("--permission-mode", mode);
|
|
2474
|
+
}
|
|
2384
2475
|
args.push("-p", "--output-format", "json");
|
|
2385
2476
|
if (target.model)
|
|
2386
2477
|
args.push("--model", target.model);
|
|
2478
|
+
if (target.variant)
|
|
2479
|
+
args.push("--effort", target.variant);
|
|
2387
2480
|
if (target.agent)
|
|
2388
2481
|
args.push("--agent", target.agent);
|
|
2389
2482
|
args.push(...target.extraArgs ?? []);
|
|
2390
2483
|
return args;
|
|
2391
2484
|
case "cursor":
|
|
2392
|
-
args.push("
|
|
2485
|
+
args.push("-c", [
|
|
2486
|
+
"set -eu",
|
|
2487
|
+
"if command -v cursor >/dev/null 2>&1; then",
|
|
2488
|
+
' exec cursor agent "$@"',
|
|
2489
|
+
"elif command -v agent >/dev/null 2>&1; then",
|
|
2490
|
+
' exec agent "$@"',
|
|
2491
|
+
"else",
|
|
2492
|
+
" echo 'Executable not found in PATH: cursor agent or agent' >&2",
|
|
2493
|
+
" exit 127",
|
|
2494
|
+
"fi"
|
|
2495
|
+
].join(`
|
|
2496
|
+
`), "openloops-cursor", "-p");
|
|
2497
|
+
if (permissionMode === "plan")
|
|
2498
|
+
args.push("--mode", "plan");
|
|
2499
|
+
if (permissionMode === "bypass")
|
|
2500
|
+
args.push("--force");
|
|
2501
|
+
const cursorSandbox = target.sandbox ?? (isolation === "safe" ? "enabled" : undefined);
|
|
2502
|
+
if (cursorSandbox) {
|
|
2503
|
+
if (cursorSandbox !== "enabled" && cursorSandbox !== "disabled")
|
|
2504
|
+
throw new Error("cursor sandbox must be enabled or disabled");
|
|
2505
|
+
args.push("--sandbox", cursorSandbox);
|
|
2506
|
+
}
|
|
2393
2507
|
if (target.model)
|
|
2394
2508
|
args.push("--model", target.model);
|
|
2395
2509
|
if (target.agent)
|
|
@@ -2397,7 +2511,10 @@ function agentArgs(target) {
|
|
|
2397
2511
|
args.push(...target.extraArgs ?? []);
|
|
2398
2512
|
return args;
|
|
2399
2513
|
case "codewith":
|
|
2400
|
-
args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : []
|
|
2514
|
+
args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : []);
|
|
2515
|
+
if (target.variant)
|
|
2516
|
+
args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
|
|
2517
|
+
args.push("--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", codewithLikeSandbox(target), "--skip-git-repo-check");
|
|
2401
2518
|
if (isolation === "safe")
|
|
2402
2519
|
args.push("--ignore-rules");
|
|
2403
2520
|
if (target.cwd)
|
|
@@ -2409,7 +2526,9 @@ function agentArgs(target) {
|
|
|
2409
2526
|
args.push(...target.extraArgs ?? []);
|
|
2410
2527
|
return args;
|
|
2411
2528
|
case "codex":
|
|
2412
|
-
|
|
2529
|
+
if (target.variant)
|
|
2530
|
+
args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
|
|
2531
|
+
args.push("exec", "--json", "--ephemeral", "--ask-for-approval", "never", "--sandbox", codewithLikeSandbox(target));
|
|
2413
2532
|
if (isolation === "safe")
|
|
2414
2533
|
args.push("--ignore-rules");
|
|
2415
2534
|
if (target.cwd)
|
|
@@ -2422,10 +2541,14 @@ function agentArgs(target) {
|
|
|
2422
2541
|
args.push("run", "--format", "json");
|
|
2423
2542
|
if (isolation === "safe")
|
|
2424
2543
|
args.push("--pure");
|
|
2544
|
+
if (permissionMode === "bypass")
|
|
2545
|
+
args.push("--dangerously-skip-permissions");
|
|
2425
2546
|
if (target.cwd)
|
|
2426
2547
|
args.push("--dir", target.cwd);
|
|
2427
2548
|
if (target.model)
|
|
2428
2549
|
args.push("--model", target.model);
|
|
2550
|
+
if (target.variant)
|
|
2551
|
+
args.push("--variant", target.variant);
|
|
2429
2552
|
if (target.agent)
|
|
2430
2553
|
args.push("--agent", target.agent);
|
|
2431
2554
|
args.push(...target.extraArgs ?? []);
|
|
@@ -2434,10 +2557,14 @@ function agentArgs(target) {
|
|
|
2434
2557
|
args.push("run", "--format", "json");
|
|
2435
2558
|
if (isolation === "safe")
|
|
2436
2559
|
args.push("--pure");
|
|
2560
|
+
if (permissionMode === "bypass")
|
|
2561
|
+
args.push("--dangerously-skip-permissions");
|
|
2437
2562
|
if (target.cwd)
|
|
2438
2563
|
args.push("--dir", target.cwd);
|
|
2439
2564
|
if (target.model)
|
|
2440
2565
|
args.push("--model", target.model);
|
|
2566
|
+
if (target.variant)
|
|
2567
|
+
args.push("--variant", target.variant);
|
|
2441
2568
|
if (target.agent)
|
|
2442
2569
|
args.push("--agent", target.agent);
|
|
2443
2570
|
args.push(...target.extraArgs ?? []);
|
|
@@ -2466,6 +2593,7 @@ function commandSpec(target) {
|
|
|
2466
2593
|
timeoutMs: agentTarget.timeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
2467
2594
|
account: agentTarget.account,
|
|
2468
2595
|
accountTool: agentTarget.account?.tool ?? accountToolForProvider(agentTarget.provider),
|
|
2596
|
+
preflightAnyOf: agentTarget.provider === "cursor" ? ["cursor", "agent"] : undefined,
|
|
2469
2597
|
stdin: agentTarget.prompt
|
|
2470
2598
|
};
|
|
2471
2599
|
}
|
|
@@ -2533,11 +2661,15 @@ function remoteScript(spec, metadata) {
|
|
|
2533
2661
|
`;
|
|
2534
2662
|
}
|
|
2535
2663
|
function remotePreflightScript(spec, metadata) {
|
|
2536
|
-
|
|
2664
|
+
const lines = [
|
|
2537
2665
|
...remoteBootstrapLines(spec, metadata),
|
|
2538
2666
|
"command -v bash >/dev/null 2>&1",
|
|
2539
2667
|
`command -v ${shellQuote(spec.shell ? "sh" : spec.command)} >/dev/null 2>&1`
|
|
2540
|
-
]
|
|
2668
|
+
];
|
|
2669
|
+
if (spec.preflightAnyOf?.length) {
|
|
2670
|
+
lines.push(`if ! ${spec.preflightAnyOf.map((command) => `command -v ${shellQuote(command)} >/dev/null 2>&1`).join(" && ! ")}; then`, ` echo 'none of required executables found: ${spec.preflightAnyOf.join(", ")}' >&2`, " exit 127", "fi");
|
|
2671
|
+
}
|
|
2672
|
+
return lines.join(`
|
|
2541
2673
|
`);
|
|
2542
2674
|
}
|
|
2543
2675
|
function transportEnv(opts) {
|
|
@@ -2690,6 +2822,9 @@ function preflightTarget(target, metadata = {}, opts = {}) {
|
|
|
2690
2822
|
if (!spec.shell && !executableExists(spec.command, env)) {
|
|
2691
2823
|
throw new Error(commandNotFoundMessage(spec.command, env));
|
|
2692
2824
|
}
|
|
2825
|
+
if (spec.preflightAnyOf?.length && !spec.preflightAnyOf.some((command) => executableExists(command, env))) {
|
|
2826
|
+
throw new Error(`none of required executables found: ${spec.preflightAnyOf.join(", ")}`);
|
|
2827
|
+
}
|
|
2693
2828
|
return {
|
|
2694
2829
|
command: spec.command,
|
|
2695
2830
|
accountProfile: spec.account?.profile,
|
|
@@ -2720,6 +2855,17 @@ async function executeTarget(target, metadata = {}, opts = {}) {
|
|
|
2720
2855
|
durationMs: 0
|
|
2721
2856
|
};
|
|
2722
2857
|
}
|
|
2858
|
+
if (spec.preflightAnyOf?.length && !spec.preflightAnyOf.some((command) => executableExists(command, env))) {
|
|
2859
|
+
return {
|
|
2860
|
+
status: "failed",
|
|
2861
|
+
stdout,
|
|
2862
|
+
stderr,
|
|
2863
|
+
error: `none of required executables found: ${spec.preflightAnyOf.join(", ")}`,
|
|
2864
|
+
startedAt,
|
|
2865
|
+
finishedAt: nowIso(),
|
|
2866
|
+
durationMs: 0
|
|
2867
|
+
};
|
|
2868
|
+
}
|
|
2723
2869
|
const child = spawn(spec.command, spec.args, {
|
|
2724
2870
|
cwd: spec.cwd,
|
|
2725
2871
|
env,
|
|
@@ -4130,7 +4276,7 @@ function enableStartup(result) {
|
|
|
4130
4276
|
// package.json
|
|
4131
4277
|
var package_default = {
|
|
4132
4278
|
name: "@hasna/loops",
|
|
4133
|
-
version: "0.3.
|
|
4279
|
+
version: "0.3.14",
|
|
4134
4280
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
4135
4281
|
type: "module",
|
|
4136
4282
|
main: "dist/index.js",
|
|
@@ -4194,6 +4340,7 @@ var package_default = {
|
|
|
4194
4340
|
bun: ">=1.0.0"
|
|
4195
4341
|
},
|
|
4196
4342
|
dependencies: {
|
|
4343
|
+
"@hasna/events": "^0.1.9",
|
|
4197
4344
|
"@hasna/machines": "0.0.49",
|
|
4198
4345
|
"@openrouter/ai-sdk-provider": "2.9.1",
|
|
4199
4346
|
ai: "6.0.204",
|
package/dist/index.d.ts
CHANGED
|
@@ -8,6 +8,7 @@ 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
12
|
export { runDoctor } from "./lib/doctor.js";
|
|
12
13
|
export { runGoal } from "./lib/goal/runner.js";
|
|
13
14
|
export { resolveGoalModel } from "./lib/goal/model-factory.js";
|