@hasna/loops 0.3.59 → 0.3.60

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/sdk/index.js CHANGED
@@ -356,6 +356,20 @@ function optionalStringArray(value, label) {
356
356
  }).filter(Boolean);
357
357
  return values.length ? values : undefined;
358
358
  }
359
+ var UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS = new Set([
360
+ "e",
361
+ "exec",
362
+ "agent",
363
+ "start",
364
+ "--ephemeral",
365
+ "--ignore-rules",
366
+ "--skip-git-repo-check",
367
+ "--json",
368
+ "--output-last-message",
369
+ "-o",
370
+ "--output-schema",
371
+ "--dangerously-bypass-approvals-and-sandbox"
372
+ ]);
359
373
  function optionalAccountRef(value, label) {
360
374
  if (value === undefined)
361
375
  return;
@@ -470,6 +484,15 @@ function validateTarget(value, label, opts) {
470
484
  throw new Error(`${label}.variant is not supported for provider cursor`);
471
485
  if (value.provider === "codex" && value.agent !== undefined)
472
486
  throw new Error(`${label}.agent is not supported for provider codex`);
487
+ if (value.provider === "codewith" && value.agent !== undefined) {
488
+ throw new Error(`${label}.agent is not supported for provider codewith durable background-agent execution`);
489
+ }
490
+ const extraArgs = optionalStringArray(value.extraArgs, `${label}.extraArgs`);
491
+ if (value.provider === "codewith") {
492
+ const unsafe = extraArgs?.find((arg) => UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS.has(arg));
493
+ if (unsafe)
494
+ throw new Error(`${label}.extraArgs cannot include ${unsafe}; codewith agent steps use durable agent start, not exec/ephemeral flags`);
495
+ }
473
496
  optionalStringArray(value.addDirs, `${label}.addDirs`);
474
497
  if (Array.isArray(value.addDirs) && value.addDirs.length > 0 && !["codewith", "codex"].includes(value.provider)) {
475
498
  throw new Error(`${label}.addDirs is currently supported only for provider codewith or codex`);
@@ -545,7 +568,9 @@ function validateTarget(value, label, opts) {
545
568
  if (value.routing.eventSource !== undefined)
546
569
  assertString(value.routing.eventSource, `${label}.routing.eventSource`);
547
570
  }
548
- const target = { ...value };
571
+ const target = { ...value, extraArgs };
572
+ if (!extraArgs)
573
+ delete target.extraArgs;
549
574
  delete target.promptFile;
550
575
  delete target.promptSource;
551
576
  return { ...target, ...promptFields };
@@ -3478,6 +3503,9 @@ function appendBounded(current, chunk, maxBytes) {
3478
3503
  return `[truncated ${overflow} bytes]
3479
3504
  ${next.slice(-maxBytes)}`;
3480
3505
  }
3506
+ function appendBoundedText(current, chunk, maxBytes) {
3507
+ return appendBounded(current, Buffer.from(chunk, "utf8"), maxBytes);
3508
+ }
3481
3509
  function killProcessGroup(pid) {
3482
3510
  try {
3483
3511
  process.kill(-pid, "SIGTERM");
@@ -3525,6 +3553,21 @@ function metadataEnv(metadata) {
3525
3553
  env.LOOPS_GOAL_NODE_KEY = metadata.goalNodeKey;
3526
3554
  return env;
3527
3555
  }
3556
+ function codewithAgentIdempotencyKey(metadata) {
3557
+ const parts = [
3558
+ "openloops",
3559
+ metadata.workflowRunId ? `workflow-run:${metadata.workflowRunId}` : undefined,
3560
+ metadata.workflowStepId ? `step:${metadata.workflowStepId}` : undefined,
3561
+ metadata.runId ? `loop-run:${metadata.runId}` : undefined,
3562
+ metadata.loopId ? `loop:${metadata.loopId}` : undefined,
3563
+ metadata.scheduledFor ? `scheduled:${metadata.scheduledFor}` : undefined,
3564
+ metadata.goalId ? `goal:${metadata.goalId}` : undefined,
3565
+ metadata.goalNodeKey ? `node:${metadata.goalNodeKey}` : undefined
3566
+ ].filter(Boolean);
3567
+ if (parts.length > 1)
3568
+ return parts.join(":");
3569
+ return `openloops:adhoc:${randomBytes2(16).toString("hex")}`;
3570
+ }
3528
3571
  function allowlistEnv(allowlist) {
3529
3572
  const env = {};
3530
3573
  if (allowlist?.tools?.length)
@@ -3561,6 +3604,20 @@ function codewithLikeSandbox(target) {
3561
3604
  function configStringValue(value) {
3562
3605
  return JSON.stringify(value);
3563
3606
  }
3607
+ var UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS2 = new Set([
3608
+ "e",
3609
+ "exec",
3610
+ "agent",
3611
+ "start",
3612
+ "--ephemeral",
3613
+ "--ignore-rules",
3614
+ "--skip-git-repo-check",
3615
+ "--json",
3616
+ "--output-last-message",
3617
+ "-o",
3618
+ "--output-schema",
3619
+ "--dangerously-bypass-approvals-and-sandbox"
3620
+ ]);
3564
3621
  function assertStringOption(value, label) {
3565
3622
  if (value !== undefined && typeof value !== "string")
3566
3623
  throw new Error(`${label} must be a string`);
@@ -3591,6 +3648,14 @@ function assertSupportedAgentOptions(target) {
3591
3648
  }
3592
3649
  if (target.provider === "codex" && target.agent !== undefined)
3593
3650
  throw new Error("codex.agent is not supported");
3651
+ if (target.provider === "codewith" && target.agent !== undefined) {
3652
+ throw new Error("codewith.agent is not supported by the durable background-agent adapter");
3653
+ }
3654
+ if (target.provider === "codewith") {
3655
+ const unsafe = target.extraArgs?.find((arg) => UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS2.has(arg));
3656
+ if (unsafe)
3657
+ throw new Error(`codewith.extraArgs cannot include ${unsafe}; durable agent steps use codewith agent start, not exec/ephemeral flags`);
3658
+ }
3594
3659
  if (["codewith", "codex"].includes(target.provider)) {
3595
3660
  if (target.permissionMode && !["default", "bypass"].includes(target.permissionMode)) {
3596
3661
  throw new Error(`${target.provider}.permissionMode supports only default or bypass`);
@@ -3674,18 +3739,18 @@ function agentArgs(target) {
3674
3739
  args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : []);
3675
3740
  if (target.variant)
3676
3741
  args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
3677
- args.push("--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", codewithLikeSandbox(target), "--skip-git-repo-check");
3678
- if (isolation === "safe")
3679
- args.push("--ignore-rules");
3742
+ args.push("--ask-for-approval", "never", "--sandbox", codewithLikeSandbox(target));
3680
3743
  if (target.cwd)
3681
3744
  args.push("--cd", target.cwd);
3682
3745
  for (const dir of target.addDirs ?? [])
3683
3746
  args.push("--add-dir", dir);
3684
3747
  if (target.model)
3685
3748
  args.push("--model", target.model);
3686
- if (target.agent)
3687
- args.push("--agent", target.agent);
3688
3749
  args.push(...target.extraArgs ?? []);
3750
+ args.push("agent", "start");
3751
+ if (target.cwd)
3752
+ args.push("--cwd", target.cwd);
3753
+ args.push(target.prompt);
3689
3754
  return args;
3690
3755
  case "codex":
3691
3756
  if (target.variant)
@@ -3761,8 +3826,9 @@ function commandSpec(target) {
3761
3826
  accountTool: agentTarget.account?.tool ?? accountToolForProvider(agentTarget.provider),
3762
3827
  nativeAuthProfile: agentTarget.authProfile ? { provider: agentTarget.provider, profile: agentTarget.authProfile } : undefined,
3763
3828
  preflightAnyOf: agentTarget.provider === "cursor" ? ["agent"] : undefined,
3764
- stdin: agentTarget.prompt,
3765
- allowlist: agentTarget.allowlist
3829
+ stdin: agentTarget.provider === "codewith" ? undefined : agentTarget.prompt,
3830
+ allowlist: agentTarget.allowlist,
3831
+ codewithDurableAgent: agentTarget.provider === "codewith" ? { target: agentTarget } : undefined
3766
3832
  };
3767
3833
  }
3768
3834
  function executionEnv(spec, metadata, opts) {
@@ -3882,6 +3948,238 @@ function preflightNativeAuthProfile(spec, env) {
3882
3948
  throw new Error(`codewith auth profile not found: ${spec.nativeAuthProfile.profile}`);
3883
3949
  }
3884
3950
  }
3951
+ function codewithAgentStartArgs(target, idempotencyKey) {
3952
+ const args = agentArgs(target);
3953
+ const startIndex = args.findIndex((arg, index) => arg === "start" && args[index - 1] === "agent");
3954
+ if (startIndex === -1)
3955
+ throw new Error("internal error: codewith durable agent args missing agent start");
3956
+ args.splice(startIndex + 1, 0, "--idempotency-key", idempotencyKey);
3957
+ return args;
3958
+ }
3959
+ function codewithAgentControlArgs(target, command, agentId) {
3960
+ return [
3961
+ ...target.authProfile ? ["--auth-profile", target.authProfile] : [],
3962
+ "agent",
3963
+ command,
3964
+ ...command === "logs" ? ["--limit", "20"] : [],
3965
+ agentId
3966
+ ];
3967
+ }
3968
+ function parseJsonOutput(stdout, label) {
3969
+ try {
3970
+ const value = JSON.parse(stdout || "{}");
3971
+ if (!value || typeof value !== "object" || Array.isArray(value))
3972
+ throw new Error("not an object");
3973
+ return value;
3974
+ } catch (error) {
3975
+ throw new Error(`${label} did not return JSON${error instanceof Error ? `: ${error.message}` : ""}`);
3976
+ }
3977
+ }
3978
+ function recordField(value, key) {
3979
+ if (!value || typeof value !== "object" || Array.isArray(value))
3980
+ return;
3981
+ const field = value[key];
3982
+ return field && typeof field === "object" && !Array.isArray(field) ? field : undefined;
3983
+ }
3984
+ function stringField(value, key) {
3985
+ const field = value?.[key];
3986
+ return typeof field === "string" ? field : undefined;
3987
+ }
3988
+ function numberField(value, key) {
3989
+ const field = value?.[key];
3990
+ return typeof field === "number" && Number.isFinite(field) ? field : undefined;
3991
+ }
3992
+ function codewithAgentStatus(readJson) {
3993
+ return stringField(recordField(readJson, "agent"), "status") ?? stringField(recordField(readJson, "statusSnapshot"), "status");
3994
+ }
3995
+ function codewithAgentEvidence(startJson, readJson, logsJson) {
3996
+ const agent = recordField(readJson, "agent") ?? recordField(startJson, "agent");
3997
+ const statusSnapshot = recordField(readJson, "statusSnapshot");
3998
+ const events = Array.isArray(logsJson?.data) ? logsJson.data.filter((event) => Boolean(event) && typeof event === "object" && !Array.isArray(event)).map((event) => ({
3999
+ seq: numberField(event, "seq"),
4000
+ eventType: stringField(event, "eventType"),
4001
+ createdAt: numberField(event, "createdAt")
4002
+ })) : undefined;
4003
+ return JSON.stringify({
4004
+ codewithAgent: {
4005
+ agentId: stringField(agent, "agentId"),
4006
+ status: stringField(agent, "status"),
4007
+ desiredState: stringField(agent, "desiredState"),
4008
+ statusReason: stringField(agent, "statusReason"),
4009
+ threadId: stringField(agent, "threadId"),
4010
+ rolloutPath: stringField(agent, "rolloutPath"),
4011
+ pid: numberField(agent, "pid"),
4012
+ exitCode: numberField(agent, "exitCode"),
4013
+ created: typeof startJson.created === "boolean" ? startJson.created : undefined
4014
+ },
4015
+ statusSnapshot: statusSnapshot ? {
4016
+ seq: numberField(statusSnapshot, "seq"),
4017
+ status: stringField(statusSnapshot, "status"),
4018
+ summary: stringField(statusSnapshot, "summary"),
4019
+ pendingInteractionCount: numberField(statusSnapshot, "pendingInteractionCount"),
4020
+ lastEventSeq: numberField(statusSnapshot, "lastEventSeq")
4021
+ } : undefined,
4022
+ events
4023
+ }, null, 2);
4024
+ }
4025
+ function sleep(ms) {
4026
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
4027
+ }
4028
+ async function executeCodewithDurableAgent(spec, metadata, opts, env, startedAt) {
4029
+ const target = spec.codewithDurableAgent?.target;
4030
+ if (!target)
4031
+ throw new Error("internal error: missing codewith durable target");
4032
+ const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
4033
+ const idempotencyKey = codewithAgentIdempotencyKey(metadata);
4034
+ const start = spawnSync2(spec.command, codewithAgentStartArgs(target, idempotencyKey), {
4035
+ cwd: spec.cwd,
4036
+ env,
4037
+ encoding: "utf8",
4038
+ stdio: ["ignore", "pipe", "pipe"],
4039
+ timeout: 30000
4040
+ });
4041
+ let stderr = start.stderr || "";
4042
+ if (start.error || (start.status ?? 1) !== 0) {
4043
+ const finishedAt = nowIso();
4044
+ return {
4045
+ status: "failed",
4046
+ exitCode: start.status ?? undefined,
4047
+ stdout: start.stdout || "",
4048
+ stderr,
4049
+ error: start.error?.message ?? `codewith agent start exited with code ${start.status ?? "unknown"}`,
4050
+ startedAt,
4051
+ finishedAt,
4052
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4053
+ };
4054
+ }
4055
+ let startJson;
4056
+ try {
4057
+ startJson = parseJsonOutput(start.stdout || "{}", "codewith agent start");
4058
+ } catch (error) {
4059
+ const finishedAt = nowIso();
4060
+ return {
4061
+ status: "failed",
4062
+ stdout: start.stdout || "",
4063
+ stderr,
4064
+ error: error instanceof Error ? error.message : String(error),
4065
+ startedAt,
4066
+ finishedAt,
4067
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4068
+ };
4069
+ }
4070
+ const agentId = stringField(recordField(startJson, "agent"), "agentId");
4071
+ if (!agentId) {
4072
+ const finishedAt = nowIso();
4073
+ return {
4074
+ status: "failed",
4075
+ stdout: start.stdout || "",
4076
+ stderr,
4077
+ error: "codewith agent start did not return agent.agentId",
4078
+ startedAt,
4079
+ finishedAt,
4080
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4081
+ };
4082
+ }
4083
+ const pollMs = Math.max(100, Number(opts.env?.LOOPS_CODEWITH_AGENT_POLL_MS ?? process.env.LOOPS_CODEWITH_AGENT_POLL_MS ?? 2000) || 2000);
4084
+ let lastReadJson = startJson;
4085
+ let lastLogsJson;
4086
+ let stdout = "";
4087
+ while (true) {
4088
+ if (opts.signal?.aborted) {
4089
+ spawnSync2(spec.command, codewithAgentControlArgs(target, "stop", agentId), { cwd: spec.cwd, env, stdio: "ignore", timeout: 15000 });
4090
+ const finishedAt = nowIso();
4091
+ return {
4092
+ status: "failed",
4093
+ stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
4094
+ stderr,
4095
+ error: "cancelled",
4096
+ startedAt,
4097
+ finishedAt,
4098
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4099
+ };
4100
+ }
4101
+ const read = spawnSync2(spec.command, codewithAgentControlArgs(target, "read", agentId), {
4102
+ cwd: spec.cwd,
4103
+ env,
4104
+ encoding: "utf8",
4105
+ stdio: ["ignore", "pipe", "pipe"],
4106
+ timeout: 30000
4107
+ });
4108
+ stderr = appendBoundedText(stderr, read.stderr || "", maxOutputBytes);
4109
+ if (read.error || (read.status ?? 1) !== 0) {
4110
+ const finishedAt = nowIso();
4111
+ return {
4112
+ status: "failed",
4113
+ exitCode: read.status ?? undefined,
4114
+ stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
4115
+ stderr,
4116
+ error: read.error?.message ?? `codewith agent read exited with code ${read.status ?? "unknown"}`,
4117
+ startedAt,
4118
+ finishedAt,
4119
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4120
+ };
4121
+ }
4122
+ try {
4123
+ lastReadJson = parseJsonOutput(read.stdout || "{}", "codewith agent read");
4124
+ } catch (error) {
4125
+ const finishedAt = nowIso();
4126
+ return {
4127
+ status: "failed",
4128
+ stdout: appendBoundedText(stdout, read.stdout || "", maxOutputBytes),
4129
+ stderr,
4130
+ error: error instanceof Error ? error.message : String(error),
4131
+ startedAt,
4132
+ finishedAt,
4133
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4134
+ };
4135
+ }
4136
+ const status = codewithAgentStatus(lastReadJson);
4137
+ if (status === "completed" || status === "failed" || status === "cancelled") {
4138
+ const logs = spawnSync2(spec.command, codewithAgentControlArgs(target, "logs", agentId), {
4139
+ cwd: spec.cwd,
4140
+ env,
4141
+ encoding: "utf8",
4142
+ stdio: ["ignore", "pipe", "pipe"],
4143
+ timeout: 30000
4144
+ });
4145
+ if (!logs.error && (logs.status ?? 1) === 0) {
4146
+ try {
4147
+ lastLogsJson = parseJsonOutput(logs.stdout || "{}", "codewith agent logs");
4148
+ } catch {
4149
+ lastLogsJson = undefined;
4150
+ }
4151
+ } else {
4152
+ stderr = appendBoundedText(stderr, logs.stderr || logs.error?.message || "", maxOutputBytes);
4153
+ }
4154
+ const finishedAt = nowIso();
4155
+ const resultStatus = status === "completed" ? "succeeded" : "failed";
4156
+ return {
4157
+ status: resultStatus,
4158
+ exitCode: resultStatus === "succeeded" ? 0 : 1,
4159
+ stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
4160
+ stderr,
4161
+ error: resultStatus === "succeeded" ? undefined : stringField(recordField(lastReadJson, "agent"), "statusReason") ?? `codewith agent ${status}`,
4162
+ startedAt,
4163
+ finishedAt,
4164
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4165
+ };
4166
+ }
4167
+ if (typeof spec.timeoutMs === "number" && new Date(nowIso()).getTime() - new Date(startedAt).getTime() >= spec.timeoutMs) {
4168
+ spawnSync2(spec.command, codewithAgentControlArgs(target, "stop", agentId), { cwd: spec.cwd, env, stdio: "ignore", timeout: 15000 });
4169
+ const finishedAt = nowIso();
4170
+ return {
4171
+ status: "timed_out",
4172
+ stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
4173
+ stderr,
4174
+ error: `timed out after ${spec.timeoutMs}ms`,
4175
+ startedAt,
4176
+ finishedAt,
4177
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4178
+ };
4179
+ }
4180
+ await sleep(pollMs);
4181
+ }
4182
+ }
3885
4183
  function preflightRemoteSpec(spec, machine, metadata, opts) {
3886
4184
  const plan = (opts.machineCommandResolver ?? resolveMachineCommand)(machine.id, "bash -s");
3887
4185
  const result = spawnSync2(plan.command, plan.args, {
@@ -4053,6 +4351,19 @@ function preflightTarget(target, metadata = {}, opts = {}) {
4053
4351
  async function executeTarget(target, metadata = {}, opts = {}) {
4054
4352
  const spec = commandSpec(target);
4055
4353
  const machine = resolvedMachine(opts);
4354
+ if (machine && !machine.local && spec.codewithDurableAgent) {
4355
+ const startedAt2 = nowIso();
4356
+ const finishedAt2 = nowIso();
4357
+ return {
4358
+ status: "failed",
4359
+ stdout: "",
4360
+ stderr: "",
4361
+ error: "remote Codewith durable background-agent steps require remote status polling support; run this Codewith step locally or add remote durable readback before dispatch",
4362
+ startedAt: startedAt2,
4363
+ finishedAt: finishedAt2,
4364
+ durationMs: new Date(finishedAt2).getTime() - new Date(startedAt2).getTime()
4365
+ };
4366
+ }
4056
4367
  if (machine && !machine.local)
4057
4368
  return executeRemoteSpec(spec, machine, metadata, opts);
4058
4369
  const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
@@ -4099,6 +4410,9 @@ async function executeTarget(target, metadata = {}, opts = {}) {
4099
4410
  durationMs: 0
4100
4411
  };
4101
4412
  }
4413
+ if (spec.codewithDurableAgent) {
4414
+ return executeCodewithDurableAgent(spec, metadata, opts, env, startedAt);
4415
+ }
4102
4416
  const child = spawn(spec.command, spec.args, {
4103
4417
  cwd: spec.cwd,
4104
4418
  env,
package/docs/USAGE.md CHANGED
@@ -6,7 +6,7 @@ It supports deterministic command loops, JSON-defined workflows, and guarded CLI
6
6
 
7
7
  - `claude`
8
8
  - `agent` (Cursor Agent CLI)
9
- - `codewith exec`
9
+ - `codewith agent start`
10
10
  - `aicopilot run`
11
11
  - `opencode run`
12
12
  - `codex exec`
@@ -893,13 +893,13 @@ On Linux this writes a user systemd service. On macOS it writes a LaunchAgent pl
893
893
  The adapters intentionally use provider command surfaces instead of pretending every agent has one SDK:
894
894
 
895
895
  - Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
896
- - Codewith uses `codewith --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories.
896
+ - Codewith uses durable `codewith --ask-for-approval never agent start` background-agent runs by default, then polls `codewith agent read` until the run is terminal and records compact status/event evidence. Remote Codewith agent steps fail closed until remote durable readback is implemented, so workflows do not advance immediately after enqueue. OpenLoops rejects Codewith `extraArgs` that try to force `exec`, `--ephemeral`, or other non-durable exec-only flags for task-lifecycle, planner, worker, verifier, reviewer, release, or other long-running agentic work.
897
897
  - AI Copilot and OpenCode use `run --format json --pure`. OpenCode requires an explicit provider/model id because ambient OpenCode config is machine-specific.
898
898
  - Cursor is CLI-first for now via the standalone `agent -p` binary. OpenLoops no longer falls back to `cursor agent`; install the standalone Cursor Agent CLI so preflight and scheduled runs use the same executable.
899
899
  - Codex uses `codex --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
900
- - Agent prompts are sent through child stdin instead of argv so prompt bodies do not appear in process listings.
900
+ - Agent prompts are sent through child stdin instead of argv where the provider supports stdin. Codewith durable background agents currently accept prompts as native `agent start` arguments, so OpenLoops stores only bounded status/event evidence and omits raw Codewith event payloads from workflow stdout.
901
901
  - 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.
902
- - `--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.
902
+ - `--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 `agent start/read/logs`; they do not call OpenAccounts.
903
903
  - `--sandbox` maps to provider-native sandbox flags. Codewith/Codex accept `read-only`, `workspace-write`, or `danger-full-access`; Cursor accepts `enabled` or `disabled`.
904
904
  - `--permission-mode` maps `plan`, `auto`, and `bypass` where the provider supports it. Claude uses native permission modes, Cursor maps bypass to `--force`, and OpenCode/AICopilot map bypass to `--dangerously-skip-permissions`.
905
905
  - `--variant` is provider-specific reasoning/model effort. Claude maps it to `--effort`, Codewith/Codex map it to `model_reasoning_effort`, and OpenCode/AICopilot pass `--variant`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hasna/loops",
3
- "version": "0.3.59",
3
+ "version": "0.3.60",
4
4
  "description": "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",