@hasna/loops 0.3.44 → 0.3.45
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 +14 -3
- package/dist/cli/index.js +33 -9
- package/dist/daemon/index.js +6 -1
- package/dist/index.js +15 -1
- package/dist/lib/store.js +1 -0
- package/dist/lib/templates.d.ts +4 -0
- package/dist/sdk/index.js +5 -0
- package/dist/types.d.ts +1 -0
- package/docs/USAGE.md +18 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -224,7 +224,9 @@ loops templates render todos-task-worker-verifier \
|
|
|
224
224
|
--var projectPath=/path/to/repo \
|
|
225
225
|
--var provider=codewith \
|
|
226
226
|
--var authProfilePool=account004,account005,account006 \
|
|
227
|
-
--var sandbox=workspace-write
|
|
227
|
+
--var sandbox=workspace-write \
|
|
228
|
+
--var todosProjectPath=$HOME/.hasna/loops \
|
|
229
|
+
--var addDirs=$HOME/.hasna/todos,$HOME/.hasna/loops
|
|
228
230
|
loops templates create-workflow todos-task-worker-verifier \
|
|
229
231
|
--var taskId=<task-id> \
|
|
230
232
|
--var projectPath=/path/to/repo
|
|
@@ -260,6 +262,8 @@ cat task-created-event.json | loops events handle todos-task \
|
|
|
260
262
|
--auth-profile-pool account004,account005,account006 \
|
|
261
263
|
--permission-mode bypass \
|
|
262
264
|
--sandbox workspace-write \
|
|
265
|
+
--todos-project "$HOME/.hasna/loops" \
|
|
266
|
+
--add-dir "$HOME/.hasna/todos,$HOME/.hasna/loops" \
|
|
263
267
|
--worktree-mode required
|
|
264
268
|
```
|
|
265
269
|
|
|
@@ -290,6 +294,13 @@ is requested without `manualBreakGlass=true`. Use `workspace-write` for
|
|
|
290
294
|
unattended task/event routes. Full access is an explicit manual emergency path,
|
|
291
295
|
not a default automation mode.
|
|
292
296
|
|
|
297
|
+
When a sandboxed Codewith/Codex worker must update app stores outside the repo
|
|
298
|
+
worktree, pass those stores explicitly with `--add-dir` or template `addDirs`.
|
|
299
|
+
For task-created routes, pass `--todos-project` so worker/verifier prompts use
|
|
300
|
+
the actual todos storage project while repo routing and worktree isolation still
|
|
301
|
+
use the repository path. This avoids `danger-full-access` for normal todos
|
|
302
|
+
comments, completion state, and loop evidence writes.
|
|
303
|
+
|
|
293
304
|
Inspect route state with:
|
|
294
305
|
|
|
295
306
|
```bash
|
|
@@ -484,10 +495,10 @@ On Linux this writes a user systemd service. On macOS it writes a LaunchAgent pl
|
|
|
484
495
|
The adapters intentionally use provider command surfaces instead of pretending every agent has one SDK:
|
|
485
496
|
|
|
486
497
|
- Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
|
|
487
|
-
- Codewith uses `codewith --ask-for-approval never exec --json --ephemeral --skip-git-repo-check
|
|
498
|
+
- Codewith uses `codewith --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories.
|
|
488
499
|
- AI Copilot and OpenCode use `run --format json --pure`.
|
|
489
500
|
- Cursor is CLI-first for now via `cursor agent -p`, with `agent -p` as the fallback launcher on machines that expose the standalone Cursor Agent binary; treat output as less stable until a stronger public SDK contract is selected.
|
|
490
|
-
- Codex uses `codex exec --json --ephemeral --
|
|
501
|
+
- Codex uses `codex exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
|
|
491
502
|
- Agent prompts are sent through child stdin instead of argv so prompt bodies do not appear in process listings.
|
|
492
503
|
- 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.
|
|
493
504
|
- `--auth-profile` and step `authProfile` are provider-native auth selectors. They currently apply to Codewith and are passed to Codewith as `--auth-profile <name>` before `exec`; they do not call OpenAccounts.
|
package/dist/cli/index.js
CHANGED
|
@@ -388,6 +388,7 @@ function validateTarget(value, label) {
|
|
|
388
388
|
}
|
|
389
389
|
if (value.variant !== undefined)
|
|
390
390
|
assertString(value.variant, `${label}.variant`);
|
|
391
|
+
optionalStringArray(value.addDirs, `${label}.addDirs`);
|
|
391
392
|
if (value.permissionMode !== undefined) {
|
|
392
393
|
assertString(value.permissionMode, `${label}.permissionMode`);
|
|
393
394
|
const permissionModes = ["default", "plan", "auto", "bypass"];
|
|
@@ -3247,6 +3248,8 @@ function agentArgs(target) {
|
|
|
3247
3248
|
args.push("--ignore-rules");
|
|
3248
3249
|
if (target.cwd)
|
|
3249
3250
|
args.push("--cd", target.cwd);
|
|
3251
|
+
for (const dir of target.addDirs ?? [])
|
|
3252
|
+
args.push("--add-dir", dir);
|
|
3250
3253
|
if (target.model)
|
|
3251
3254
|
args.push("--model", target.model);
|
|
3252
3255
|
if (target.agent)
|
|
@@ -3261,6 +3264,8 @@ function agentArgs(target) {
|
|
|
3261
3264
|
args.push("--ignore-rules");
|
|
3262
3265
|
if (target.cwd)
|
|
3263
3266
|
args.push("--cd", target.cwd);
|
|
3267
|
+
for (const dir of target.addDirs ?? [])
|
|
3268
|
+
args.push("--add-dir", dir);
|
|
3264
3269
|
if (target.model)
|
|
3265
3270
|
args.push("--model", target.model);
|
|
3266
3271
|
args.push(...target.extraArgs ?? []);
|
|
@@ -5732,7 +5737,7 @@ function buildScriptInventoryReport(store, opts = {}) {
|
|
|
5732
5737
|
// package.json
|
|
5733
5738
|
var package_default = {
|
|
5734
5739
|
name: "@hasna/loops",
|
|
5735
|
-
version: "0.3.
|
|
5740
|
+
version: "0.3.45",
|
|
5736
5741
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
5737
5742
|
type: "module",
|
|
5738
5743
|
main: "dist/index.js",
|
|
@@ -5845,6 +5850,7 @@ var TEMPLATE_SUMMARIES = [
|
|
|
5845
5850
|
{ name: "taskId", required: true, description: "Todos task id to execute." },
|
|
5846
5851
|
{ name: "taskTitle", description: "Human-readable task title." },
|
|
5847
5852
|
{ name: "projectPath", required: true, description: "Repository or project working directory." },
|
|
5853
|
+
{ name: "todosProjectPath", description: "Todos storage project path used in worker/verifier commands." },
|
|
5848
5854
|
{ name: "routeProjectPath", description: "Canonical project path used for scheduler concurrency limits." },
|
|
5849
5855
|
{ name: "projectGroup", description: "Optional project group used for scheduler concurrency limits." },
|
|
5850
5856
|
{ name: "provider", default: "codewith", description: "Agent provider: codewith, claude, cursor, opencode, aicopilot, or codex." },
|
|
@@ -5855,6 +5861,7 @@ var TEMPLATE_SUMMARIES = [
|
|
|
5855
5861
|
{ name: "accountPool", description: "Comma-separated OpenAccounts profiles; worker/verifier are selected deterministically." },
|
|
5856
5862
|
{ name: "model", description: "Provider model." },
|
|
5857
5863
|
{ name: "variant", description: "Provider reasoning/model effort variant." },
|
|
5864
|
+
{ name: "addDirs", description: "Comma-separated additional writable directories for provider sandboxes." },
|
|
5858
5865
|
{ name: "permissionMode", default: "bypass", description: "Provider permission mode: default, plan, auto, or bypass." },
|
|
5859
5866
|
{ name: "sandbox", default: "workspace-write", description: "Provider sandbox mode." },
|
|
5860
5867
|
{ name: "manualBreakGlass", default: "false", description: "Allow explicit danger-full-access in a generated workflow. Intended for manual emergency use only." },
|
|
@@ -5884,6 +5891,7 @@ var TEMPLATE_SUMMARIES = [
|
|
|
5884
5891
|
{ name: "accountPool", description: "Comma-separated OpenAccounts profiles; worker/verifier are selected deterministically." },
|
|
5885
5892
|
{ name: "model", description: "Provider model." },
|
|
5886
5893
|
{ name: "variant", description: "Provider reasoning/model effort variant." },
|
|
5894
|
+
{ name: "addDirs", description: "Comma-separated additional writable directories for provider sandboxes." },
|
|
5887
5895
|
{ name: "permissionMode", default: "bypass", description: "Provider permission mode: default, plan, auto, or bypass." },
|
|
5888
5896
|
{ name: "sandbox", default: "workspace-write", description: "Provider sandbox mode." },
|
|
5889
5897
|
{ name: "manualBreakGlass", default: "false", description: "Allow explicit danger-full-access in a generated workflow. Intended for manual emergency use only." },
|
|
@@ -6264,6 +6272,7 @@ function agentTarget(input, prompt, role, seed, plan) {
|
|
|
6264
6272
|
model: input.model,
|
|
6265
6273
|
variant: input.variant,
|
|
6266
6274
|
agent: input.agent,
|
|
6275
|
+
addDirs: input.addDirs,
|
|
6267
6276
|
authProfile: provider === "codewith" ? authProfileForRole(input, role, seed) : undefined,
|
|
6268
6277
|
configIsolation: "safe",
|
|
6269
6278
|
permissionMode: input.permissionMode ?? "bypass",
|
|
@@ -6307,7 +6316,7 @@ function renderTodosTaskWorkerVerifierWorkflow(input) {
|
|
|
6307
6316
|
throw new Error("taskId is required");
|
|
6308
6317
|
if (!input.projectPath?.trim())
|
|
6309
6318
|
throw new Error("projectPath is required");
|
|
6310
|
-
const todosProjectPath = input.routeProjectPath ?? input.projectPath;
|
|
6319
|
+
const todosProjectPath = input.todosProjectPath ?? input.routeProjectPath ?? input.projectPath;
|
|
6311
6320
|
const plan = worktreePlan(input, input.taskId);
|
|
6312
6321
|
const taskContext = {
|
|
6313
6322
|
taskId: input.taskId,
|
|
@@ -6541,6 +6550,7 @@ function renderLifecycleBoundedTemplate(id, values) {
|
|
|
6541
6550
|
model: values.model,
|
|
6542
6551
|
variant: values.variant,
|
|
6543
6552
|
agent: values.agent,
|
|
6553
|
+
addDirs: listVar(values.addDirs ?? values.addDir),
|
|
6544
6554
|
permissionMode: values.permissionMode,
|
|
6545
6555
|
sandbox: values.sandbox ?? (id === REPORT_ONLY_TEMPLATE_ID ? "read-only" : undefined),
|
|
6546
6556
|
manualBreakGlass: booleanVar(values.manualBreakGlass),
|
|
@@ -6657,6 +6667,7 @@ function renderLoopTemplate(id, values) {
|
|
|
6657
6667
|
taskTitle: values.taskTitle,
|
|
6658
6668
|
taskDescription: values.taskDescription,
|
|
6659
6669
|
projectPath: values.projectPath ?? values.cwd ?? process.cwd(),
|
|
6670
|
+
todosProjectPath: values.todosProjectPath ?? values.todosProject,
|
|
6660
6671
|
routeProjectPath: values.routeProjectPath,
|
|
6661
6672
|
projectGroup: values.projectGroup,
|
|
6662
6673
|
provider: values.provider,
|
|
@@ -6669,6 +6680,7 @@ function renderLoopTemplate(id, values) {
|
|
|
6669
6680
|
model: values.model,
|
|
6670
6681
|
variant: values.variant,
|
|
6671
6682
|
agent: values.agent,
|
|
6683
|
+
addDirs: listVar(values.addDirs ?? values.addDir),
|
|
6672
6684
|
permissionMode: values.permissionMode,
|
|
6673
6685
|
sandbox: values.sandbox,
|
|
6674
6686
|
manualBreakGlass: booleanVar(values.manualBreakGlass),
|
|
@@ -6700,6 +6712,7 @@ function renderLoopTemplate(id, values) {
|
|
|
6700
6712
|
model: values.model,
|
|
6701
6713
|
variant: values.variant,
|
|
6702
6714
|
agent: values.agent,
|
|
6715
|
+
addDirs: listVar(values.addDirs ?? values.addDir),
|
|
6703
6716
|
permissionMode: values.permissionMode,
|
|
6704
6717
|
sandbox: values.sandbox,
|
|
6705
6718
|
manualBreakGlass: booleanVar(values.manualBreakGlass),
|
|
@@ -6726,6 +6739,7 @@ function renderLoopTemplate(id, values) {
|
|
|
6726
6739
|
model: values.model,
|
|
6727
6740
|
variant: values.variant,
|
|
6728
6741
|
agent: values.agent,
|
|
6742
|
+
addDirs: listVar(values.addDirs ?? values.addDir),
|
|
6729
6743
|
permissionMode: values.permissionMode,
|
|
6730
6744
|
sandbox: values.sandbox,
|
|
6731
6745
|
manualBreakGlass: booleanVar(values.manualBreakGlass),
|
|
@@ -6938,6 +6952,10 @@ function allowlistFromOpts(opts) {
|
|
|
6938
6952
|
enforcement: "metadata_only"
|
|
6939
6953
|
};
|
|
6940
6954
|
}
|
|
6955
|
+
function listFromRepeatedOpts(value) {
|
|
6956
|
+
const values = (value ?? []).flatMap((entry) => splitList(entry) ?? []);
|
|
6957
|
+
return values.length ? values : undefined;
|
|
6958
|
+
}
|
|
6941
6959
|
function accountPoolFromOpts(opts) {
|
|
6942
6960
|
return splitList(opts.accountPool)?.map((profile) => ({ profile, tool: opts.accountTool }));
|
|
6943
6961
|
}
|
|
@@ -7541,6 +7559,7 @@ function routeTodosTaskEvent(event, opts) {
|
|
|
7541
7559
|
model: opts.model,
|
|
7542
7560
|
variant: opts.variant,
|
|
7543
7561
|
agent: opts.agent,
|
|
7562
|
+
addDirs: listFromRepeatedOpts(opts.addDir),
|
|
7544
7563
|
permissionMode,
|
|
7545
7564
|
sandbox,
|
|
7546
7565
|
manualBreakGlass: Boolean(opts.manualBreakGlass),
|
|
@@ -7548,7 +7567,8 @@ function routeTodosTaskEvent(event, opts) {
|
|
|
7548
7567
|
worktreeRoot: opts.worktreeRoot,
|
|
7549
7568
|
worktreeBranchPrefix: opts.worktreeBranchPrefix ?? "openloops",
|
|
7550
7569
|
eventId: event.id,
|
|
7551
|
-
eventType: event.type
|
|
7570
|
+
eventType: event.type,
|
|
7571
|
+
todosProjectPath: opts.todosProject
|
|
7552
7572
|
});
|
|
7553
7573
|
workflowBody.name = workflowName;
|
|
7554
7574
|
workflowBody.description = `Task-triggered worker/verifier workflow for ${taskTitle ?? taskId} from ${event.source}/${event.type}; ` + `idempotency=${idempotencyKey}; event=${event.id}; project=${projectPath}; projectGroup=${projectGroup ?? "-"}`;
|
|
@@ -7753,6 +7773,7 @@ function routeGenericEvent(event, opts) {
|
|
|
7753
7773
|
model: opts.model,
|
|
7754
7774
|
variant: opts.variant,
|
|
7755
7775
|
agent: opts.agent,
|
|
7776
|
+
addDirs: listFromRepeatedOpts(opts.addDir),
|
|
7756
7777
|
permissionMode,
|
|
7757
7778
|
sandbox,
|
|
7758
7779
|
manualBreakGlass: Boolean(opts.manualBreakGlass),
|
|
@@ -8115,7 +8136,7 @@ addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.com
|
|
|
8115
8136
|
store.close();
|
|
8116
8137
|
}
|
|
8117
8138
|
});
|
|
8118
|
-
addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.command("agent <name>").description("create a headless coding-agent loop").requiredOption("--provider <provider>", "claude, cursor, codewith, aicopilot, opencode, or codex").requiredOption("--prompt <prompt>", "agent prompt").option("--cwd <dir>", "working directory").option("--model <model>", "model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--timeout <duration>", "run timeout").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass").option("--sandbox <mode>", "provider sandbox: codewith/codex use read-only/workspace-write/danger-full-access; cursor uses enabled/disabled").option("--allow-tool <name>", "advisory per-session tool allowlist metadata; may be repeated or comma-separated", collectValues, []).option("--allow-command <name>", "advisory per-session command allowlist metadata; may be repeated or comma-separated", collectValues, []).option("--config-isolation <mode>", "safe or none", "safe").option("--preflight-each-run", "check provider/account readiness before every scheduled run").option("--preflight", "check target executables/accounts before storing the loop"))))).action((name, opts) => {
|
|
8139
|
+
addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.command("agent <name>").description("create a headless coding-agent loop").requiredOption("--provider <provider>", "claude, cursor, codewith, aicopilot, opencode, or codex").requiredOption("--prompt <prompt>", "agent prompt").option("--cwd <dir>", "working directory").option("--model <model>", "model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--add-dir <dir>", "additional writable directory for provider sandboxes; may be repeated or comma-separated", collectValues, []).option("--timeout <duration>", "run timeout").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass").option("--sandbox <mode>", "provider sandbox: codewith/codex use read-only/workspace-write/danger-full-access; cursor uses enabled/disabled").option("--allow-tool <name>", "advisory per-session tool allowlist metadata; may be repeated or comma-separated", collectValues, []).option("--allow-command <name>", "advisory per-session command allowlist metadata; may be repeated or comma-separated", collectValues, []).option("--config-isolation <mode>", "safe or none", "safe").option("--preflight-each-run", "check provider/account readiness before every scheduled run").option("--preflight", "check target executables/accounts before storing the loop"))))).action((name, opts) => {
|
|
8119
8140
|
const provider = opts.provider;
|
|
8120
8141
|
if (!["claude", "cursor", "codewith", "aicopilot", "opencode", "codex"].includes(provider)) {
|
|
8121
8142
|
throw new Error("unsupported provider");
|
|
@@ -8134,6 +8155,7 @@ addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.com
|
|
|
8134
8155
|
variant: opts.variant,
|
|
8135
8156
|
agent: opts.agent,
|
|
8136
8157
|
authProfile: providerAuthProfileFromOpts(opts, provider),
|
|
8158
|
+
addDirs: listFromRepeatedOpts(opts.addDir),
|
|
8137
8159
|
timeoutMs: opts.timeout ? parseDuration(opts.timeout) : undefined,
|
|
8138
8160
|
configIsolation: opts.configIsolation,
|
|
8139
8161
|
permissionMode: permissionModeFromOpts(opts, provider),
|
|
@@ -8174,10 +8196,10 @@ var events = program.command("events").description("handle Hasna event envelopes
|
|
|
8174
8196
|
var machines = program.command("machines").description("inspect OpenMachines topology for loop assignment");
|
|
8175
8197
|
var goal = program.command("goal").description("inspect goal runs");
|
|
8176
8198
|
function addRouteEventOptions(command) {
|
|
8177
|
-
return command.option("--event-file <file>", "read event envelope JSON from a file instead of stdin/HASNA_EVENT_JSON").option("--event-json <json>", "read event envelope JSON from this string instead of stdin/HASNA_EVENT_JSON").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--manual-break-glass", "allow danger-full-access in generated worker/verifier workflow metadata; for explicit operator emergency use only").option("--project-path <path>", "fallback project/repo working directory").option("--project-group <name>", "optional project group for concurrency limits").option("--max-active <n>", "skip creating a workflow when this many active routed workflows already exist globally").option("--max-active-per-project <n>", "skip creating a workflow when this many active routed workflows already exist for the project").option("--max-active-per-project-group <n>", "skip creating a workflow when this many active routed workflows already exist for the project group").option("--worktree-mode <mode>", "worktree isolation mode: auto, required, off, or main", "auto").option("--worktree-root <path>", "base directory for OpenLoops-managed git worktrees").option("--worktree-branch-prefix <prefix>", "branch prefix for generated worktrees", "openloops").option("--name-prefix <prefix>", "workflow/loop name prefix").option("--preflight", "check generated workflow steps before storing the workflow loop");
|
|
8199
|
+
return command.option("--event-file <file>", "read event envelope JSON from a file instead of stdin/HASNA_EVENT_JSON").option("--event-json <json>", "read event envelope JSON from this string instead of stdin/HASNA_EVENT_JSON").option("--todos-project <path>", "todos storage project path for generated task commands", defaultLoopsProject()).option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--add-dir <dir>", "additional writable directory for provider sandboxes; may be repeated or comma-separated", collectValues, []).option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--manual-break-glass", "allow danger-full-access in generated worker/verifier workflow metadata; for explicit operator emergency use only").option("--project-path <path>", "fallback project/repo working directory").option("--project-group <name>", "optional project group for concurrency limits").option("--max-active <n>", "skip creating a workflow when this many active routed workflows already exist globally").option("--max-active-per-project <n>", "skip creating a workflow when this many active routed workflows already exist for the project").option("--max-active-per-project-group <n>", "skip creating a workflow when this many active routed workflows already exist for the project group").option("--worktree-mode <mode>", "worktree isolation mode: auto, required, off, or main", "auto").option("--worktree-root <path>", "base directory for OpenLoops-managed git worktrees").option("--worktree-branch-prefix <prefix>", "branch prefix for generated worktrees", "openloops").option("--name-prefix <prefix>", "workflow/loop name prefix").option("--preflight", "check generated workflow steps before storing the workflow loop");
|
|
8178
8200
|
}
|
|
8179
8201
|
function addTodosDrainOptions(command) {
|
|
8180
|
-
return command.option("--todos-project <path>", "todos storage project path", defaultLoopsProject()).option("--todos-project-id <id>", "filter todos ready output to one todos project id").option("--task-list <id-or-slug>", "filter ready tasks to one task-list id, slug, or name").option("--project-path-prefix <path>", "filter ready tasks to a project/repo path prefix").option("--tags <tags>", "require all comma-separated tags before routing").option("--tag <tags>", "alias for --tags").option("--limit <n>", "maximum filtered ready-task candidates to consider", "50").option("--scan-limit <n>", "maximum raw todos ready rows to fetch before filters; defaults to 500 when filters are used").option("--max-dispatch <n>", "maximum new workflow loops to create in this drain run", "1").option("--evidence-dir <path>", "write a JSON drain report to this directory").option("--compact", "print compact JSON to stdout while preserving the full evidence file").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--manual-break-glass", "allow danger-full-access in generated worker/verifier workflow metadata; for explicit operator emergency use only").option("--project-path <path>", "fallback project/repo working directory").option("--project-group <name>", "optional project group for concurrency limits").option("--max-active <n>", "skip creating a workflow when this many active routed workflows already exist globally").option("--max-active-per-project <n>", "skip creating a workflow when this many active routed workflows already exist for the project").option("--max-active-per-project-group <n>", "skip creating a workflow when this many active routed workflows already exist for the project group").option("--worktree-mode <mode>", "worktree isolation mode: auto, required, off, or main", "auto").option("--worktree-root <path>", "base directory for OpenLoops-managed git worktrees").option("--worktree-branch-prefix <prefix>", "branch prefix for generated task worktrees", "openloops").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:todos-task").option("--preflight", "check generated workflow steps before storing workflow loops").option("--dry-run", "preview selected tasks and generated workflow loops without storing anything");
|
|
8202
|
+
return command.option("--todos-project <path>", "todos storage project path", defaultLoopsProject()).option("--todos-project-id <id>", "filter todos ready output to one todos project id").option("--task-list <id-or-slug>", "filter ready tasks to one task-list id, slug, or name").option("--project-path-prefix <path>", "filter ready tasks to a project/repo path prefix").option("--tags <tags>", "require all comma-separated tags before routing").option("--tag <tags>", "alias for --tags").option("--limit <n>", "maximum filtered ready-task candidates to consider", "50").option("--scan-limit <n>", "maximum raw todos ready rows to fetch before filters; defaults to 500 when filters are used").option("--max-dispatch <n>", "maximum new workflow loops to create in this drain run", "1").option("--evidence-dir <path>", "write a JSON drain report to this directory").option("--compact", "print compact JSON to stdout while preserving the full evidence file").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--add-dir <dir>", "additional writable directory for provider sandboxes; may be repeated or comma-separated", collectValues, []).option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--manual-break-glass", "allow danger-full-access in generated worker/verifier workflow metadata; for explicit operator emergency use only").option("--project-path <path>", "fallback project/repo working directory").option("--project-group <name>", "optional project group for concurrency limits").option("--max-active <n>", "skip creating a workflow when this many active routed workflows already exist globally").option("--max-active-per-project <n>", "skip creating a workflow when this many active routed workflows already exist for the project").option("--max-active-per-project-group <n>", "skip creating a workflow when this many active routed workflows already exist for the project group").option("--worktree-mode <mode>", "worktree isolation mode: auto, required, off, or main", "auto").option("--worktree-root <path>", "base directory for OpenLoops-managed git worktrees").option("--worktree-branch-prefix <prefix>", "branch prefix for generated task worktrees", "openloops").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:todos-task").option("--preflight", "check generated workflow steps before storing workflow loops").option("--dry-run", "preview selected tasks and generated workflow loops without storing anything");
|
|
8181
8203
|
}
|
|
8182
8204
|
function routeEventByKind(kind, event, opts) {
|
|
8183
8205
|
if (kind === "todos-task")
|
|
@@ -8219,6 +8241,8 @@ function routeDrainArgs(opts) {
|
|
|
8219
8241
|
add("--model", opts.model);
|
|
8220
8242
|
add("--variant", opts.variant);
|
|
8221
8243
|
add("--agent", opts.agent);
|
|
8244
|
+
for (const dir of listFromRepeatedOpts(opts.addDir) ?? [])
|
|
8245
|
+
add("--add-dir", dir);
|
|
8222
8246
|
add("--permission-mode", opts.permissionMode);
|
|
8223
8247
|
add("--sandbox", opts.sandbox);
|
|
8224
8248
|
addBool("--manual-break-glass", opts.manualBreakGlass);
|
|
@@ -8437,13 +8461,13 @@ addScheduleOptions(addTodosDrainOptions(routes.command("schedule <kind> <name>")
|
|
|
8437
8461
|
}
|
|
8438
8462
|
});
|
|
8439
8463
|
var eventsHandle = events.command("handle").description("handle a Hasna event envelope");
|
|
8440
|
-
eventsHandle.command("todos-task").description("create a one-shot worker/verifier workflow loop for a todos task event").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--manual-break-glass", "allow danger-full-access in generated worker/verifier workflow metadata; for explicit operator emergency use only").option("--project-path <path>", "fallback project/repo working directory").option("--project-group <name>", "optional project group for concurrency limits").option("--max-active <n>", "skip creating a workflow when this many active routed workflows already exist globally").option("--max-active-per-project <n>", "skip creating a workflow when this many active routed workflows already exist for the project").option("--max-active-per-project-group <n>", "skip creating a workflow when this many active routed workflows already exist for the project group").option("--worktree-mode <mode>", "worktree isolation mode: auto, required, off, or main", "auto").option("--worktree-root <path>", "base directory for OpenLoops-managed git worktrees").option("--worktree-branch-prefix <prefix>", "branch prefix for generated task worktrees", "openloops").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:todos-task").option("--preflight", "check generated workflow steps before storing the workflow loop").option("--dry-run", "print the workflow and loop input without storing anything").action(async (opts) => {
|
|
8464
|
+
eventsHandle.command("todos-task").description("create a one-shot worker/verifier workflow loop for a todos task event").option("--todos-project <path>", "todos storage project path for generated task commands", defaultLoopsProject()).option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--add-dir <dir>", "additional writable directory for provider sandboxes; may be repeated or comma-separated", collectValues, []).option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--manual-break-glass", "allow danger-full-access in generated worker/verifier workflow metadata; for explicit operator emergency use only").option("--project-path <path>", "fallback project/repo working directory").option("--project-group <name>", "optional project group for concurrency limits").option("--max-active <n>", "skip creating a workflow when this many active routed workflows already exist globally").option("--max-active-per-project <n>", "skip creating a workflow when this many active routed workflows already exist for the project").option("--max-active-per-project-group <n>", "skip creating a workflow when this many active routed workflows already exist for the project group").option("--worktree-mode <mode>", "worktree isolation mode: auto, required, off, or main", "auto").option("--worktree-root <path>", "base directory for OpenLoops-managed git worktrees").option("--worktree-branch-prefix <prefix>", "branch prefix for generated task worktrees", "openloops").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:todos-task").option("--preflight", "check generated workflow steps before storing the workflow loop").option("--dry-run", "print the workflow and loop input without storing anything").action(async (opts) => {
|
|
8441
8465
|
const event = await readEventEnvelopeFromStdin();
|
|
8442
8466
|
const result = routeTodosTaskEvent(event, opts);
|
|
8443
8467
|
print(result.value, result.human);
|
|
8444
8468
|
});
|
|
8445
8469
|
var eventsDrain = events.command("drain").description("drain durable source queues into bounded OpenLoops workflows");
|
|
8446
|
-
eventsDrain.command("todos-task").description("drain ready todos tasks into bounded worker/verifier workflow loops").option("--todos-project <path>", "todos storage project path", defaultLoopsProject()).option("--todos-project-id <id>", "filter todos ready output to one todos project id").option("--task-list <id-or-slug>", "filter ready tasks to one task-list id, slug, or name").option("--project-path-prefix <path>", "filter ready tasks to a project/repo path prefix").option("--tags <tags>", "require all comma-separated tags before routing").option("--tag <tags>", "alias for --tags").option("--limit <n>", "maximum filtered ready-task candidates to consider", "50").option("--scan-limit <n>", "maximum raw todos ready rows to fetch before filters; defaults to 500 when filters are used").option("--max-dispatch <n>", "maximum new workflow loops to create in this drain run", "1").option("--evidence-dir <path>", "write a JSON drain report to this directory").option("--compact", "print compact JSON to stdout while preserving the full evidence file").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--manual-break-glass", "allow danger-full-access in generated worker/verifier workflow metadata; for explicit operator emergency use only").option("--project-path <path>", "fallback project/repo working directory").option("--project-group <name>", "optional project group for concurrency limits").option("--max-active <n>", "skip creating a workflow when this many active routed workflows already exist globally").option("--max-active-per-project <n>", "skip creating a workflow when this many active routed workflows already exist for the project").option("--max-active-per-project-group <n>", "skip creating a workflow when this many active routed workflows already exist for the project group").option("--worktree-mode <mode>", "worktree isolation mode: auto, required, off, or main", "auto").option("--worktree-root <path>", "base directory for OpenLoops-managed git worktrees").option("--worktree-branch-prefix <prefix>", "branch prefix for generated task worktrees", "openloops").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:todos-task").option("--preflight", "check generated workflow steps before storing workflow loops").option("--dry-run", "preview selected tasks and generated workflow loops without storing anything").action((opts) => {
|
|
8470
|
+
eventsDrain.command("todos-task").description("drain ready todos tasks into bounded worker/verifier workflow loops").option("--todos-project <path>", "todos storage project path", defaultLoopsProject()).option("--todos-project-id <id>", "filter todos ready output to one todos project id").option("--task-list <id-or-slug>", "filter ready tasks to one task-list id, slug, or name").option("--project-path-prefix <path>", "filter ready tasks to a project/repo path prefix").option("--tags <tags>", "require all comma-separated tags before routing").option("--tag <tags>", "alias for --tags").option("--limit <n>", "maximum filtered ready-task candidates to consider", "50").option("--scan-limit <n>", "maximum raw todos ready rows to fetch before filters; defaults to 500 when filters are used").option("--max-dispatch <n>", "maximum new workflow loops to create in this drain run", "1").option("--evidence-dir <path>", "write a JSON drain report to this directory").option("--compact", "print compact JSON to stdout while preserving the full evidence file").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--add-dir <dir>", "additional writable directory for provider sandboxes; may be repeated or comma-separated", collectValues, []).option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--manual-break-glass", "allow danger-full-access in generated worker/verifier workflow metadata; for explicit operator emergency use only").option("--project-path <path>", "fallback project/repo working directory").option("--project-group <name>", "optional project group for concurrency limits").option("--max-active <n>", "skip creating a workflow when this many active routed workflows already exist globally").option("--max-active-per-project <n>", "skip creating a workflow when this many active routed workflows already exist for the project").option("--max-active-per-project-group <n>", "skip creating a workflow when this many active routed workflows already exist for the project group").option("--worktree-mode <mode>", "worktree isolation mode: auto, required, off, or main", "auto").option("--worktree-root <path>", "base directory for OpenLoops-managed git worktrees").option("--worktree-branch-prefix <prefix>", "branch prefix for generated task worktrees", "openloops").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:todos-task").option("--preflight", "check generated workflow steps before storing workflow loops").option("--dry-run", "preview selected tasks and generated workflow loops without storing anything").action((opts) => {
|
|
8447
8471
|
const maxDispatch = positiveInteger(opts.maxDispatch ?? "1", "--max-dispatch") ?? 1;
|
|
8448
8472
|
const todosProject = opts.todosProject ?? defaultLoopsProject();
|
|
8449
8473
|
const requiredTags = splitList(opts.tags ?? opts.tag) ?? [];
|
|
@@ -8527,7 +8551,7 @@ eventsDrain.command("todos-task").description("drain ready todos tasks into boun
|
|
|
8527
8551
|
} : { ...report, evidencePath };
|
|
8528
8552
|
print(output, `drained todos ready queue: considered=${report.considered} created=${report.created} deduped=${report.deduped} throttled=${report.throttled} skipped=${report.skipped}`);
|
|
8529
8553
|
});
|
|
8530
|
-
eventsHandle.command("generic").description("create a one-shot worker/verifier workflow loop for any Hasna event").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--manual-break-glass", "allow danger-full-access in generated worker/verifier workflow metadata; for explicit operator emergency use only").option("--project-path <path>", "fallback project/repo working directory").option("--project-group <name>", "optional project group for concurrency limits").option("--max-active <n>", "skip creating a workflow when this many active routed workflows already exist globally").option("--max-active-per-project <n>", "skip creating a workflow when this many active routed workflows already exist for the project").option("--max-active-per-project-group <n>", "skip creating a workflow when this many active routed workflows already exist for the project group").option("--worktree-mode <mode>", "worktree isolation mode: auto, required, off, or main", "auto").option("--worktree-root <path>", "base directory for OpenLoops-managed git worktrees").option("--worktree-branch-prefix <prefix>", "branch prefix for generated event worktrees", "openloops").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:generic").option("--preflight", "check generated workflow steps before storing the workflow loop").option("--dry-run", "print the workflow and loop input without storing anything").action(async (opts) => {
|
|
8554
|
+
eventsHandle.command("generic").description("create a one-shot worker/verifier workflow loop for any Hasna event").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--add-dir <dir>", "additional writable directory for provider sandboxes; may be repeated or comma-separated", collectValues, []).option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--manual-break-glass", "allow danger-full-access in generated worker/verifier workflow metadata; for explicit operator emergency use only").option("--project-path <path>", "fallback project/repo working directory").option("--project-group <name>", "optional project group for concurrency limits").option("--max-active <n>", "skip creating a workflow when this many active routed workflows already exist globally").option("--max-active-per-project <n>", "skip creating a workflow when this many active routed workflows already exist for the project").option("--max-active-per-project-group <n>", "skip creating a workflow when this many active routed workflows already exist for the project group").option("--worktree-mode <mode>", "worktree isolation mode: auto, required, off, or main", "auto").option("--worktree-root <path>", "base directory for OpenLoops-managed git worktrees").option("--worktree-branch-prefix <prefix>", "branch prefix for generated event worktrees", "openloops").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:generic").option("--preflight", "check generated workflow steps before storing the workflow loop").option("--dry-run", "print the workflow and loop input without storing anything").action(async (opts) => {
|
|
8531
8555
|
const event = await readEventEnvelopeFromStdin();
|
|
8532
8556
|
const data = eventData(event);
|
|
8533
8557
|
const metadata = eventMetadata(event);
|
package/dist/daemon/index.js
CHANGED
|
@@ -388,6 +388,7 @@ function validateTarget(value, label) {
|
|
|
388
388
|
}
|
|
389
389
|
if (value.variant !== undefined)
|
|
390
390
|
assertString(value.variant, `${label}.variant`);
|
|
391
|
+
optionalStringArray(value.addDirs, `${label}.addDirs`);
|
|
391
392
|
if (value.permissionMode !== undefined) {
|
|
392
393
|
assertString(value.permissionMode, `${label}.permissionMode`);
|
|
393
394
|
const permissionModes = ["default", "plan", "auto", "bypass"];
|
|
@@ -3131,6 +3132,8 @@ function agentArgs(target) {
|
|
|
3131
3132
|
args.push("--ignore-rules");
|
|
3132
3133
|
if (target.cwd)
|
|
3133
3134
|
args.push("--cd", target.cwd);
|
|
3135
|
+
for (const dir of target.addDirs ?? [])
|
|
3136
|
+
args.push("--add-dir", dir);
|
|
3134
3137
|
if (target.model)
|
|
3135
3138
|
args.push("--model", target.model);
|
|
3136
3139
|
if (target.agent)
|
|
@@ -3145,6 +3148,8 @@ function agentArgs(target) {
|
|
|
3145
3148
|
args.push("--ignore-rules");
|
|
3146
3149
|
if (target.cwd)
|
|
3147
3150
|
args.push("--cd", target.cwd);
|
|
3151
|
+
for (const dir of target.addDirs ?? [])
|
|
3152
|
+
args.push("--add-dir", dir);
|
|
3148
3153
|
if (target.model)
|
|
3149
3154
|
args.push("--model", target.model);
|
|
3150
3155
|
args.push(...target.extraArgs ?? []);
|
|
@@ -5046,7 +5051,7 @@ function enableStartup(result) {
|
|
|
5046
5051
|
// package.json
|
|
5047
5052
|
var package_default = {
|
|
5048
5053
|
name: "@hasna/loops",
|
|
5049
|
-
version: "0.3.
|
|
5054
|
+
version: "0.3.45",
|
|
5050
5055
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
5051
5056
|
type: "module",
|
|
5052
5057
|
main: "dist/index.js",
|
package/dist/index.js
CHANGED
|
@@ -386,6 +386,7 @@ function validateTarget(value, label) {
|
|
|
386
386
|
}
|
|
387
387
|
if (value.variant !== undefined)
|
|
388
388
|
assertString(value.variant, `${label}.variant`);
|
|
389
|
+
optionalStringArray(value.addDirs, `${label}.addDirs`);
|
|
389
390
|
if (value.permissionMode !== undefined) {
|
|
390
391
|
assertString(value.permissionMode, `${label}.permissionMode`);
|
|
391
392
|
const permissionModes = ["default", "plan", "auto", "bypass"];
|
|
@@ -3121,6 +3122,8 @@ function agentArgs(target) {
|
|
|
3121
3122
|
args.push("--ignore-rules");
|
|
3122
3123
|
if (target.cwd)
|
|
3123
3124
|
args.push("--cd", target.cwd);
|
|
3125
|
+
for (const dir of target.addDirs ?? [])
|
|
3126
|
+
args.push("--add-dir", dir);
|
|
3124
3127
|
if (target.model)
|
|
3125
3128
|
args.push("--model", target.model);
|
|
3126
3129
|
if (target.agent)
|
|
@@ -3135,6 +3138,8 @@ function agentArgs(target) {
|
|
|
3135
3138
|
args.push("--ignore-rules");
|
|
3136
3139
|
if (target.cwd)
|
|
3137
3140
|
args.push("--cd", target.cwd);
|
|
3141
|
+
for (const dir of target.addDirs ?? [])
|
|
3142
|
+
args.push("--add-dir", dir);
|
|
3138
3143
|
if (target.model)
|
|
3139
3144
|
args.push("--model", target.model);
|
|
3140
3145
|
args.push(...target.extraArgs ?? []);
|
|
@@ -4806,6 +4811,7 @@ var TEMPLATE_SUMMARIES = [
|
|
|
4806
4811
|
{ name: "taskId", required: true, description: "Todos task id to execute." },
|
|
4807
4812
|
{ name: "taskTitle", description: "Human-readable task title." },
|
|
4808
4813
|
{ name: "projectPath", required: true, description: "Repository or project working directory." },
|
|
4814
|
+
{ name: "todosProjectPath", description: "Todos storage project path used in worker/verifier commands." },
|
|
4809
4815
|
{ name: "routeProjectPath", description: "Canonical project path used for scheduler concurrency limits." },
|
|
4810
4816
|
{ name: "projectGroup", description: "Optional project group used for scheduler concurrency limits." },
|
|
4811
4817
|
{ name: "provider", default: "codewith", description: "Agent provider: codewith, claude, cursor, opencode, aicopilot, or codex." },
|
|
@@ -4816,6 +4822,7 @@ var TEMPLATE_SUMMARIES = [
|
|
|
4816
4822
|
{ name: "accountPool", description: "Comma-separated OpenAccounts profiles; worker/verifier are selected deterministically." },
|
|
4817
4823
|
{ name: "model", description: "Provider model." },
|
|
4818
4824
|
{ name: "variant", description: "Provider reasoning/model effort variant." },
|
|
4825
|
+
{ name: "addDirs", description: "Comma-separated additional writable directories for provider sandboxes." },
|
|
4819
4826
|
{ name: "permissionMode", default: "bypass", description: "Provider permission mode: default, plan, auto, or bypass." },
|
|
4820
4827
|
{ name: "sandbox", default: "workspace-write", description: "Provider sandbox mode." },
|
|
4821
4828
|
{ name: "manualBreakGlass", default: "false", description: "Allow explicit danger-full-access in a generated workflow. Intended for manual emergency use only." },
|
|
@@ -4845,6 +4852,7 @@ var TEMPLATE_SUMMARIES = [
|
|
|
4845
4852
|
{ name: "accountPool", description: "Comma-separated OpenAccounts profiles; worker/verifier are selected deterministically." },
|
|
4846
4853
|
{ name: "model", description: "Provider model." },
|
|
4847
4854
|
{ name: "variant", description: "Provider reasoning/model effort variant." },
|
|
4855
|
+
{ name: "addDirs", description: "Comma-separated additional writable directories for provider sandboxes." },
|
|
4848
4856
|
{ name: "permissionMode", default: "bypass", description: "Provider permission mode: default, plan, auto, or bypass." },
|
|
4849
4857
|
{ name: "sandbox", default: "workspace-write", description: "Provider sandbox mode." },
|
|
4850
4858
|
{ name: "manualBreakGlass", default: "false", description: "Allow explicit danger-full-access in a generated workflow. Intended for manual emergency use only." },
|
|
@@ -5225,6 +5233,7 @@ function agentTarget(input, prompt, role, seed, plan) {
|
|
|
5225
5233
|
model: input.model,
|
|
5226
5234
|
variant: input.variant,
|
|
5227
5235
|
agent: input.agent,
|
|
5236
|
+
addDirs: input.addDirs,
|
|
5228
5237
|
authProfile: provider === "codewith" ? authProfileForRole(input, role, seed) : undefined,
|
|
5229
5238
|
configIsolation: "safe",
|
|
5230
5239
|
permissionMode: input.permissionMode ?? "bypass",
|
|
@@ -5268,7 +5277,7 @@ function renderTodosTaskWorkerVerifierWorkflow(input) {
|
|
|
5268
5277
|
throw new Error("taskId is required");
|
|
5269
5278
|
if (!input.projectPath?.trim())
|
|
5270
5279
|
throw new Error("projectPath is required");
|
|
5271
|
-
const todosProjectPath = input.routeProjectPath ?? input.projectPath;
|
|
5280
|
+
const todosProjectPath = input.todosProjectPath ?? input.routeProjectPath ?? input.projectPath;
|
|
5272
5281
|
const plan = worktreePlan(input, input.taskId);
|
|
5273
5282
|
const taskContext = {
|
|
5274
5283
|
taskId: input.taskId,
|
|
@@ -5502,6 +5511,7 @@ function renderLifecycleBoundedTemplate(id, values) {
|
|
|
5502
5511
|
model: values.model,
|
|
5503
5512
|
variant: values.variant,
|
|
5504
5513
|
agent: values.agent,
|
|
5514
|
+
addDirs: listVar(values.addDirs ?? values.addDir),
|
|
5505
5515
|
permissionMode: values.permissionMode,
|
|
5506
5516
|
sandbox: values.sandbox ?? (id === REPORT_ONLY_TEMPLATE_ID ? "read-only" : undefined),
|
|
5507
5517
|
manualBreakGlass: booleanVar(values.manualBreakGlass),
|
|
@@ -5618,6 +5628,7 @@ function renderLoopTemplate(id, values) {
|
|
|
5618
5628
|
taskTitle: values.taskTitle,
|
|
5619
5629
|
taskDescription: values.taskDescription,
|
|
5620
5630
|
projectPath: values.projectPath ?? values.cwd ?? process.cwd(),
|
|
5631
|
+
todosProjectPath: values.todosProjectPath ?? values.todosProject,
|
|
5621
5632
|
routeProjectPath: values.routeProjectPath,
|
|
5622
5633
|
projectGroup: values.projectGroup,
|
|
5623
5634
|
provider: values.provider,
|
|
@@ -5630,6 +5641,7 @@ function renderLoopTemplate(id, values) {
|
|
|
5630
5641
|
model: values.model,
|
|
5631
5642
|
variant: values.variant,
|
|
5632
5643
|
agent: values.agent,
|
|
5644
|
+
addDirs: listVar(values.addDirs ?? values.addDir),
|
|
5633
5645
|
permissionMode: values.permissionMode,
|
|
5634
5646
|
sandbox: values.sandbox,
|
|
5635
5647
|
manualBreakGlass: booleanVar(values.manualBreakGlass),
|
|
@@ -5661,6 +5673,7 @@ function renderLoopTemplate(id, values) {
|
|
|
5661
5673
|
model: values.model,
|
|
5662
5674
|
variant: values.variant,
|
|
5663
5675
|
agent: values.agent,
|
|
5676
|
+
addDirs: listVar(values.addDirs ?? values.addDir),
|
|
5664
5677
|
permissionMode: values.permissionMode,
|
|
5665
5678
|
sandbox: values.sandbox,
|
|
5666
5679
|
manualBreakGlass: booleanVar(values.manualBreakGlass),
|
|
@@ -5687,6 +5700,7 @@ function renderLoopTemplate(id, values) {
|
|
|
5687
5700
|
model: values.model,
|
|
5688
5701
|
variant: values.variant,
|
|
5689
5702
|
agent: values.agent,
|
|
5703
|
+
addDirs: listVar(values.addDirs ?? values.addDir),
|
|
5690
5704
|
permissionMode: values.permissionMode,
|
|
5691
5705
|
sandbox: values.sandbox,
|
|
5692
5706
|
manualBreakGlass: booleanVar(values.manualBreakGlass),
|
package/dist/lib/store.js
CHANGED
|
@@ -386,6 +386,7 @@ function validateTarget(value, label) {
|
|
|
386
386
|
}
|
|
387
387
|
if (value.variant !== undefined)
|
|
388
388
|
assertString(value.variant, `${label}.variant`);
|
|
389
|
+
optionalStringArray(value.addDirs, `${label}.addDirs`);
|
|
389
390
|
if (value.permissionMode !== undefined) {
|
|
390
391
|
assertString(value.permissionMode, `${label}.permissionMode`);
|
|
391
392
|
const permissionModes = ["default", "plan", "auto", "bypass"];
|
package/dist/lib/templates.d.ts
CHANGED
|
@@ -14,6 +14,7 @@ export interface TodosTaskWorkflowTemplateInput {
|
|
|
14
14
|
taskTitle?: string;
|
|
15
15
|
taskDescription?: string;
|
|
16
16
|
projectPath: string;
|
|
17
|
+
todosProjectPath?: string;
|
|
17
18
|
routeProjectPath?: string;
|
|
18
19
|
projectGroup?: string;
|
|
19
20
|
provider?: AgentProvider;
|
|
@@ -28,6 +29,7 @@ export interface TodosTaskWorkflowTemplateInput {
|
|
|
28
29
|
model?: string;
|
|
29
30
|
variant?: string;
|
|
30
31
|
agent?: string;
|
|
32
|
+
addDirs?: string[];
|
|
31
33
|
permissionMode?: AgentPermissionMode;
|
|
32
34
|
sandbox?: AgentSandbox;
|
|
33
35
|
manualBreakGlass?: boolean;
|
|
@@ -59,6 +61,7 @@ export interface EventWorkflowTemplateInput {
|
|
|
59
61
|
model?: string;
|
|
60
62
|
variant?: string;
|
|
61
63
|
agent?: string;
|
|
64
|
+
addDirs?: string[];
|
|
62
65
|
permissionMode?: AgentPermissionMode;
|
|
63
66
|
sandbox?: AgentSandbox;
|
|
64
67
|
manualBreakGlass?: boolean;
|
|
@@ -85,6 +88,7 @@ export interface BoundedAgentWorkflowTemplateInput {
|
|
|
85
88
|
model?: string;
|
|
86
89
|
variant?: string;
|
|
87
90
|
agent?: string;
|
|
91
|
+
addDirs?: string[];
|
|
88
92
|
permissionMode?: AgentPermissionMode;
|
|
89
93
|
sandbox?: AgentSandbox;
|
|
90
94
|
manualBreakGlass?: boolean;
|
package/dist/sdk/index.js
CHANGED
|
@@ -386,6 +386,7 @@ function validateTarget(value, label) {
|
|
|
386
386
|
}
|
|
387
387
|
if (value.variant !== undefined)
|
|
388
388
|
assertString(value.variant, `${label}.variant`);
|
|
389
|
+
optionalStringArray(value.addDirs, `${label}.addDirs`);
|
|
389
390
|
if (value.permissionMode !== undefined) {
|
|
390
391
|
assertString(value.permissionMode, `${label}.permissionMode`);
|
|
391
392
|
const permissionModes = ["default", "plan", "auto", "bypass"];
|
|
@@ -3121,6 +3122,8 @@ function agentArgs(target) {
|
|
|
3121
3122
|
args.push("--ignore-rules");
|
|
3122
3123
|
if (target.cwd)
|
|
3123
3124
|
args.push("--cd", target.cwd);
|
|
3125
|
+
for (const dir of target.addDirs ?? [])
|
|
3126
|
+
args.push("--add-dir", dir);
|
|
3124
3127
|
if (target.model)
|
|
3125
3128
|
args.push("--model", target.model);
|
|
3126
3129
|
if (target.agent)
|
|
@@ -3135,6 +3138,8 @@ function agentArgs(target) {
|
|
|
3135
3138
|
args.push("--ignore-rules");
|
|
3136
3139
|
if (target.cwd)
|
|
3137
3140
|
args.push("--cd", target.cwd);
|
|
3141
|
+
for (const dir of target.addDirs ?? [])
|
|
3142
|
+
args.push("--add-dir", dir);
|
|
3138
3143
|
if (target.model)
|
|
3139
3144
|
args.push("--model", target.model);
|
|
3140
3145
|
args.push(...target.extraArgs ?? []);
|
package/dist/types.d.ts
CHANGED
package/docs/USAGE.md
CHANGED
|
@@ -228,7 +228,9 @@ loops templates render todos-task-worker-verifier \
|
|
|
228
228
|
--var projectPath=/path/to/repo \
|
|
229
229
|
--var provider=codewith \
|
|
230
230
|
--var authProfilePool=account004,account005,account006 \
|
|
231
|
-
--var sandbox=workspace-write
|
|
231
|
+
--var sandbox=workspace-write \
|
|
232
|
+
--var todosProjectPath=$HOME/.hasna/loops \
|
|
233
|
+
--var addDirs=$HOME/.hasna/todos,$HOME/.hasna/loops
|
|
232
234
|
loops templates create-workflow todos-task-worker-verifier \
|
|
233
235
|
--var taskId=<task-id> \
|
|
234
236
|
--var projectPath=/path/to/repo
|
|
@@ -286,6 +288,8 @@ cat task-created-event.json | loops events handle todos-task \
|
|
|
286
288
|
--auth-profile-pool account004,account005,account006 \
|
|
287
289
|
--permission-mode bypass \
|
|
288
290
|
--sandbox workspace-write \
|
|
291
|
+
--todos-project "$HOME/.hasna/loops" \
|
|
292
|
+
--add-dir "$HOME/.hasna/todos,$HOME/.hasna/loops" \
|
|
289
293
|
--worktree-mode required
|
|
290
294
|
```
|
|
291
295
|
|
|
@@ -322,6 +326,13 @@ idempotency key before rendering worktree plans or checking route limits. In
|
|
|
322
326
|
dry-run mode, throttle counts are not evaluated because opening the live loop
|
|
323
327
|
store can create or migrate the local database.
|
|
324
328
|
|
|
329
|
+
When a sandboxed Codewith/Codex worker must update app stores outside the repo
|
|
330
|
+
worktree, pass those stores explicitly with `--add-dir` or template `addDirs`.
|
|
331
|
+
For task-created routes, pass `--todos-project` so worker/verifier prompts use
|
|
332
|
+
the actual todos storage project while route concurrency and worktree isolation
|
|
333
|
+
still use the repository path. This avoids `danger-full-access` for normal
|
|
334
|
+
todos comments, completion state, and loop evidence writes.
|
|
335
|
+
|
|
325
336
|
Inspect route state with:
|
|
326
337
|
|
|
327
338
|
```bash
|
|
@@ -351,12 +362,13 @@ locks, or non-pending states stay queued in todos and are not routed:
|
|
|
351
362
|
|
|
352
363
|
```bash
|
|
353
364
|
loops events drain todos-task \
|
|
354
|
-
--todos-project
|
|
365
|
+
--todos-project "$HOME/.hasna/loops" \
|
|
355
366
|
--task-list repoops-pr-queue \
|
|
356
367
|
--tags auto:route \
|
|
357
|
-
--project-path-prefix /
|
|
368
|
+
--project-path-prefix "$HOME/workspace/hasna/opensource" \
|
|
358
369
|
--provider codewith \
|
|
359
370
|
--auth-profile-pool account004,account005,account006 \
|
|
371
|
+
--add-dir "$HOME/.hasna/todos,$HOME/.hasna/loops" \
|
|
360
372
|
--project-group oss \
|
|
361
373
|
--max-dispatch 2 \
|
|
362
374
|
--scan-limit 500 \
|
|
@@ -364,7 +376,7 @@ loops events drain todos-task \
|
|
|
364
376
|
--max-active-per-project-group 4 \
|
|
365
377
|
--max-active 12 \
|
|
366
378
|
--worktree-mode required \
|
|
367
|
-
--evidence-dir
|
|
379
|
+
--evidence-dir "$HOME/.hasna/loops/reports/task-drain"
|
|
368
380
|
```
|
|
369
381
|
|
|
370
382
|
`--max-dispatch` caps new workflow-loop creation per drain run. `--limit` caps
|
|
@@ -562,10 +574,10 @@ On Linux this writes a user systemd service. On macOS it writes a LaunchAgent pl
|
|
|
562
574
|
The adapters intentionally use provider command surfaces instead of pretending every agent has one SDK:
|
|
563
575
|
|
|
564
576
|
- Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
|
|
565
|
-
- Codewith uses `codewith --ask-for-approval never exec --json --ephemeral --skip-git-repo-check
|
|
577
|
+
- Codewith uses `codewith --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories.
|
|
566
578
|
- AI Copilot and OpenCode use `run --format json --pure`.
|
|
567
579
|
- Cursor is CLI-first for now via `cursor agent -p`, with `agent -p` as the fallback launcher on machines that expose the standalone Cursor Agent binary; treat output as less stable until a stronger public SDK contract is selected.
|
|
568
|
-
- Codex uses `codex exec --json --ephemeral --
|
|
580
|
+
- Codex uses `codex exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
|
|
569
581
|
- Agent prompts are sent through child stdin instead of argv so prompt bodies do not appear in process listings.
|
|
570
582
|
- 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.
|
|
571
583
|
- `--auth-profile` and step `authProfile` are provider-native auth selectors. They currently apply to Codewith and are passed to Codewith as `--auth-profile <name>` before `exec`; they do not call OpenAccounts.
|
package/package.json
CHANGED