@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/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 };
@@ -3613,6 +3638,9 @@ function appendBounded(current, chunk, maxBytes) {
3613
3638
  return `[truncated ${overflow} bytes]
3614
3639
  ${next.slice(-maxBytes)}`;
3615
3640
  }
3641
+ function appendBoundedText(current, chunk, maxBytes) {
3642
+ return appendBounded(current, Buffer.from(chunk, "utf8"), maxBytes);
3643
+ }
3616
3644
  function killProcessGroup(pid) {
3617
3645
  try {
3618
3646
  process.kill(-pid, "SIGTERM");
@@ -3660,6 +3688,21 @@ function metadataEnv(metadata) {
3660
3688
  env.LOOPS_GOAL_NODE_KEY = metadata.goalNodeKey;
3661
3689
  return env;
3662
3690
  }
3691
+ function codewithAgentIdempotencyKey(metadata) {
3692
+ const parts = [
3693
+ "openloops",
3694
+ metadata.workflowRunId ? `workflow-run:${metadata.workflowRunId}` : undefined,
3695
+ metadata.workflowStepId ? `step:${metadata.workflowStepId}` : undefined,
3696
+ metadata.runId ? `loop-run:${metadata.runId}` : undefined,
3697
+ metadata.loopId ? `loop:${metadata.loopId}` : undefined,
3698
+ metadata.scheduledFor ? `scheduled:${metadata.scheduledFor}` : undefined,
3699
+ metadata.goalId ? `goal:${metadata.goalId}` : undefined,
3700
+ metadata.goalNodeKey ? `node:${metadata.goalNodeKey}` : undefined
3701
+ ].filter(Boolean);
3702
+ if (parts.length > 1)
3703
+ return parts.join(":");
3704
+ return `openloops:adhoc:${randomBytes2(16).toString("hex")}`;
3705
+ }
3663
3706
  function allowlistEnv(allowlist) {
3664
3707
  const env = {};
3665
3708
  if (allowlist?.tools?.length)
@@ -3696,6 +3739,20 @@ function codewithLikeSandbox(target) {
3696
3739
  function configStringValue(value) {
3697
3740
  return JSON.stringify(value);
3698
3741
  }
3742
+ var UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS2 = new Set([
3743
+ "e",
3744
+ "exec",
3745
+ "agent",
3746
+ "start",
3747
+ "--ephemeral",
3748
+ "--ignore-rules",
3749
+ "--skip-git-repo-check",
3750
+ "--json",
3751
+ "--output-last-message",
3752
+ "-o",
3753
+ "--output-schema",
3754
+ "--dangerously-bypass-approvals-and-sandbox"
3755
+ ]);
3699
3756
  function assertStringOption(value, label) {
3700
3757
  if (value !== undefined && typeof value !== "string")
3701
3758
  throw new Error(`${label} must be a string`);
@@ -3726,6 +3783,14 @@ function assertSupportedAgentOptions(target) {
3726
3783
  }
3727
3784
  if (target.provider === "codex" && target.agent !== undefined)
3728
3785
  throw new Error("codex.agent is not supported");
3786
+ if (target.provider === "codewith" && target.agent !== undefined) {
3787
+ throw new Error("codewith.agent is not supported by the durable background-agent adapter");
3788
+ }
3789
+ if (target.provider === "codewith") {
3790
+ const unsafe = target.extraArgs?.find((arg) => UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS2.has(arg));
3791
+ if (unsafe)
3792
+ throw new Error(`codewith.extraArgs cannot include ${unsafe}; durable agent steps use codewith agent start, not exec/ephemeral flags`);
3793
+ }
3729
3794
  if (["codewith", "codex"].includes(target.provider)) {
3730
3795
  if (target.permissionMode && !["default", "bypass"].includes(target.permissionMode)) {
3731
3796
  throw new Error(`${target.provider}.permissionMode supports only default or bypass`);
@@ -3809,18 +3874,18 @@ function agentArgs(target) {
3809
3874
  args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : []);
3810
3875
  if (target.variant)
3811
3876
  args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
3812
- args.push("--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", codewithLikeSandbox(target), "--skip-git-repo-check");
3813
- if (isolation === "safe")
3814
- args.push("--ignore-rules");
3877
+ args.push("--ask-for-approval", "never", "--sandbox", codewithLikeSandbox(target));
3815
3878
  if (target.cwd)
3816
3879
  args.push("--cd", target.cwd);
3817
3880
  for (const dir of target.addDirs ?? [])
3818
3881
  args.push("--add-dir", dir);
3819
3882
  if (target.model)
3820
3883
  args.push("--model", target.model);
3821
- if (target.agent)
3822
- args.push("--agent", target.agent);
3823
3884
  args.push(...target.extraArgs ?? []);
3885
+ args.push("agent", "start");
3886
+ if (target.cwd)
3887
+ args.push("--cwd", target.cwd);
3888
+ args.push(target.prompt);
3824
3889
  return args;
3825
3890
  case "codex":
3826
3891
  if (target.variant)
@@ -3896,8 +3961,9 @@ function commandSpec(target) {
3896
3961
  accountTool: agentTarget.account?.tool ?? accountToolForProvider(agentTarget.provider),
3897
3962
  nativeAuthProfile: agentTarget.authProfile ? { provider: agentTarget.provider, profile: agentTarget.authProfile } : undefined,
3898
3963
  preflightAnyOf: agentTarget.provider === "cursor" ? ["agent"] : undefined,
3899
- stdin: agentTarget.prompt,
3900
- allowlist: agentTarget.allowlist
3964
+ stdin: agentTarget.provider === "codewith" ? undefined : agentTarget.prompt,
3965
+ allowlist: agentTarget.allowlist,
3966
+ codewithDurableAgent: agentTarget.provider === "codewith" ? { target: agentTarget } : undefined
3901
3967
  };
3902
3968
  }
3903
3969
  function executionEnv(spec, metadata, opts) {
@@ -4017,6 +4083,238 @@ function preflightNativeAuthProfile(spec, env) {
4017
4083
  throw new Error(`codewith auth profile not found: ${spec.nativeAuthProfile.profile}`);
4018
4084
  }
4019
4085
  }
4086
+ function codewithAgentStartArgs(target, idempotencyKey) {
4087
+ const args = agentArgs(target);
4088
+ const startIndex = args.findIndex((arg, index) => arg === "start" && args[index - 1] === "agent");
4089
+ if (startIndex === -1)
4090
+ throw new Error("internal error: codewith durable agent args missing agent start");
4091
+ args.splice(startIndex + 1, 0, "--idempotency-key", idempotencyKey);
4092
+ return args;
4093
+ }
4094
+ function codewithAgentControlArgs(target, command, agentId) {
4095
+ return [
4096
+ ...target.authProfile ? ["--auth-profile", target.authProfile] : [],
4097
+ "agent",
4098
+ command,
4099
+ ...command === "logs" ? ["--limit", "20"] : [],
4100
+ agentId
4101
+ ];
4102
+ }
4103
+ function parseJsonOutput(stdout, label) {
4104
+ try {
4105
+ const value = JSON.parse(stdout || "{}");
4106
+ if (!value || typeof value !== "object" || Array.isArray(value))
4107
+ throw new Error("not an object");
4108
+ return value;
4109
+ } catch (error) {
4110
+ throw new Error(`${label} did not return JSON${error instanceof Error ? `: ${error.message}` : ""}`);
4111
+ }
4112
+ }
4113
+ function recordField(value, key) {
4114
+ if (!value || typeof value !== "object" || Array.isArray(value))
4115
+ return;
4116
+ const field = value[key];
4117
+ return field && typeof field === "object" && !Array.isArray(field) ? field : undefined;
4118
+ }
4119
+ function stringField(value, key) {
4120
+ const field = value?.[key];
4121
+ return typeof field === "string" ? field : undefined;
4122
+ }
4123
+ function numberField(value, key) {
4124
+ const field = value?.[key];
4125
+ return typeof field === "number" && Number.isFinite(field) ? field : undefined;
4126
+ }
4127
+ function codewithAgentStatus(readJson) {
4128
+ return stringField(recordField(readJson, "agent"), "status") ?? stringField(recordField(readJson, "statusSnapshot"), "status");
4129
+ }
4130
+ function codewithAgentEvidence(startJson, readJson, logsJson) {
4131
+ const agent = recordField(readJson, "agent") ?? recordField(startJson, "agent");
4132
+ const statusSnapshot = recordField(readJson, "statusSnapshot");
4133
+ const events = Array.isArray(logsJson?.data) ? logsJson.data.filter((event) => Boolean(event) && typeof event === "object" && !Array.isArray(event)).map((event) => ({
4134
+ seq: numberField(event, "seq"),
4135
+ eventType: stringField(event, "eventType"),
4136
+ createdAt: numberField(event, "createdAt")
4137
+ })) : undefined;
4138
+ return JSON.stringify({
4139
+ codewithAgent: {
4140
+ agentId: stringField(agent, "agentId"),
4141
+ status: stringField(agent, "status"),
4142
+ desiredState: stringField(agent, "desiredState"),
4143
+ statusReason: stringField(agent, "statusReason"),
4144
+ threadId: stringField(agent, "threadId"),
4145
+ rolloutPath: stringField(agent, "rolloutPath"),
4146
+ pid: numberField(agent, "pid"),
4147
+ exitCode: numberField(agent, "exitCode"),
4148
+ created: typeof startJson.created === "boolean" ? startJson.created : undefined
4149
+ },
4150
+ statusSnapshot: statusSnapshot ? {
4151
+ seq: numberField(statusSnapshot, "seq"),
4152
+ status: stringField(statusSnapshot, "status"),
4153
+ summary: stringField(statusSnapshot, "summary"),
4154
+ pendingInteractionCount: numberField(statusSnapshot, "pendingInteractionCount"),
4155
+ lastEventSeq: numberField(statusSnapshot, "lastEventSeq")
4156
+ } : undefined,
4157
+ events
4158
+ }, null, 2);
4159
+ }
4160
+ function sleep(ms) {
4161
+ return new Promise((resolve2) => setTimeout(resolve2, ms));
4162
+ }
4163
+ async function executeCodewithDurableAgent(spec, metadata, opts, env, startedAt) {
4164
+ const target = spec.codewithDurableAgent?.target;
4165
+ if (!target)
4166
+ throw new Error("internal error: missing codewith durable target");
4167
+ const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
4168
+ const idempotencyKey = codewithAgentIdempotencyKey(metadata);
4169
+ const start = spawnSync2(spec.command, codewithAgentStartArgs(target, idempotencyKey), {
4170
+ cwd: spec.cwd,
4171
+ env,
4172
+ encoding: "utf8",
4173
+ stdio: ["ignore", "pipe", "pipe"],
4174
+ timeout: 30000
4175
+ });
4176
+ let stderr = start.stderr || "";
4177
+ if (start.error || (start.status ?? 1) !== 0) {
4178
+ const finishedAt = nowIso();
4179
+ return {
4180
+ status: "failed",
4181
+ exitCode: start.status ?? undefined,
4182
+ stdout: start.stdout || "",
4183
+ stderr,
4184
+ error: start.error?.message ?? `codewith agent start exited with code ${start.status ?? "unknown"}`,
4185
+ startedAt,
4186
+ finishedAt,
4187
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4188
+ };
4189
+ }
4190
+ let startJson;
4191
+ try {
4192
+ startJson = parseJsonOutput(start.stdout || "{}", "codewith agent start");
4193
+ } catch (error) {
4194
+ const finishedAt = nowIso();
4195
+ return {
4196
+ status: "failed",
4197
+ stdout: start.stdout || "",
4198
+ stderr,
4199
+ error: error instanceof Error ? error.message : String(error),
4200
+ startedAt,
4201
+ finishedAt,
4202
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4203
+ };
4204
+ }
4205
+ const agentId = stringField(recordField(startJson, "agent"), "agentId");
4206
+ if (!agentId) {
4207
+ const finishedAt = nowIso();
4208
+ return {
4209
+ status: "failed",
4210
+ stdout: start.stdout || "",
4211
+ stderr,
4212
+ error: "codewith agent start did not return agent.agentId",
4213
+ startedAt,
4214
+ finishedAt,
4215
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4216
+ };
4217
+ }
4218
+ const pollMs = Math.max(100, Number(opts.env?.LOOPS_CODEWITH_AGENT_POLL_MS ?? process.env.LOOPS_CODEWITH_AGENT_POLL_MS ?? 2000) || 2000);
4219
+ let lastReadJson = startJson;
4220
+ let lastLogsJson;
4221
+ let stdout = "";
4222
+ while (true) {
4223
+ if (opts.signal?.aborted) {
4224
+ spawnSync2(spec.command, codewithAgentControlArgs(target, "stop", agentId), { cwd: spec.cwd, env, stdio: "ignore", timeout: 15000 });
4225
+ const finishedAt = nowIso();
4226
+ return {
4227
+ status: "failed",
4228
+ stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
4229
+ stderr,
4230
+ error: "cancelled",
4231
+ startedAt,
4232
+ finishedAt,
4233
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4234
+ };
4235
+ }
4236
+ const read = spawnSync2(spec.command, codewithAgentControlArgs(target, "read", agentId), {
4237
+ cwd: spec.cwd,
4238
+ env,
4239
+ encoding: "utf8",
4240
+ stdio: ["ignore", "pipe", "pipe"],
4241
+ timeout: 30000
4242
+ });
4243
+ stderr = appendBoundedText(stderr, read.stderr || "", maxOutputBytes);
4244
+ if (read.error || (read.status ?? 1) !== 0) {
4245
+ const finishedAt = nowIso();
4246
+ return {
4247
+ status: "failed",
4248
+ exitCode: read.status ?? undefined,
4249
+ stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
4250
+ stderr,
4251
+ error: read.error?.message ?? `codewith agent read exited with code ${read.status ?? "unknown"}`,
4252
+ startedAt,
4253
+ finishedAt,
4254
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4255
+ };
4256
+ }
4257
+ try {
4258
+ lastReadJson = parseJsonOutput(read.stdout || "{}", "codewith agent read");
4259
+ } catch (error) {
4260
+ const finishedAt = nowIso();
4261
+ return {
4262
+ status: "failed",
4263
+ stdout: appendBoundedText(stdout, read.stdout || "", maxOutputBytes),
4264
+ stderr,
4265
+ error: error instanceof Error ? error.message : String(error),
4266
+ startedAt,
4267
+ finishedAt,
4268
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4269
+ };
4270
+ }
4271
+ const status = codewithAgentStatus(lastReadJson);
4272
+ if (status === "completed" || status === "failed" || status === "cancelled") {
4273
+ const logs = spawnSync2(spec.command, codewithAgentControlArgs(target, "logs", agentId), {
4274
+ cwd: spec.cwd,
4275
+ env,
4276
+ encoding: "utf8",
4277
+ stdio: ["ignore", "pipe", "pipe"],
4278
+ timeout: 30000
4279
+ });
4280
+ if (!logs.error && (logs.status ?? 1) === 0) {
4281
+ try {
4282
+ lastLogsJson = parseJsonOutput(logs.stdout || "{}", "codewith agent logs");
4283
+ } catch {
4284
+ lastLogsJson = undefined;
4285
+ }
4286
+ } else {
4287
+ stderr = appendBoundedText(stderr, logs.stderr || logs.error?.message || "", maxOutputBytes);
4288
+ }
4289
+ const finishedAt = nowIso();
4290
+ const resultStatus = status === "completed" ? "succeeded" : "failed";
4291
+ return {
4292
+ status: resultStatus,
4293
+ exitCode: resultStatus === "succeeded" ? 0 : 1,
4294
+ stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
4295
+ stderr,
4296
+ error: resultStatus === "succeeded" ? undefined : stringField(recordField(lastReadJson, "agent"), "statusReason") ?? `codewith agent ${status}`,
4297
+ startedAt,
4298
+ finishedAt,
4299
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4300
+ };
4301
+ }
4302
+ if (typeof spec.timeoutMs === "number" && new Date(nowIso()).getTime() - new Date(startedAt).getTime() >= spec.timeoutMs) {
4303
+ spawnSync2(spec.command, codewithAgentControlArgs(target, "stop", agentId), { cwd: spec.cwd, env, stdio: "ignore", timeout: 15000 });
4304
+ const finishedAt = nowIso();
4305
+ return {
4306
+ status: "timed_out",
4307
+ stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
4308
+ stderr,
4309
+ error: `timed out after ${spec.timeoutMs}ms`,
4310
+ startedAt,
4311
+ finishedAt,
4312
+ durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
4313
+ };
4314
+ }
4315
+ await sleep(pollMs);
4316
+ }
4317
+ }
4020
4318
  function preflightRemoteSpec(spec, machine, metadata, opts) {
4021
4319
  const plan = (opts.machineCommandResolver ?? resolveMachineCommand)(machine.id, "bash -s");
4022
4320
  const result = spawnSync2(plan.command, plan.args, {
@@ -4188,6 +4486,19 @@ function preflightTarget(target, metadata = {}, opts = {}) {
4188
4486
  async function executeTarget(target, metadata = {}, opts = {}) {
4189
4487
  const spec = commandSpec(target);
4190
4488
  const machine = resolvedMachine(opts);
4489
+ if (machine && !machine.local && spec.codewithDurableAgent) {
4490
+ const startedAt2 = nowIso();
4491
+ const finishedAt2 = nowIso();
4492
+ return {
4493
+ status: "failed",
4494
+ stdout: "",
4495
+ stderr: "",
4496
+ error: "remote Codewith durable background-agent steps require remote status polling support; run this Codewith step locally or add remote durable readback before dispatch",
4497
+ startedAt: startedAt2,
4498
+ finishedAt: finishedAt2,
4499
+ durationMs: new Date(finishedAt2).getTime() - new Date(startedAt2).getTime()
4500
+ };
4501
+ }
4191
4502
  if (machine && !machine.local)
4192
4503
  return executeRemoteSpec(spec, machine, metadata, opts);
4193
4504
  const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
@@ -4234,6 +4545,9 @@ async function executeTarget(target, metadata = {}, opts = {}) {
4234
4545
  durationMs: 0
4235
4546
  };
4236
4547
  }
4548
+ if (spec.codewithDurableAgent) {
4549
+ return executeCodewithDurableAgent(spec, metadata, opts, env, startedAt);
4550
+ }
4237
4551
  const child = spawn(spec.command, spec.args, {
4238
4552
  cwd: spec.cwd,
4239
4553
  env,
@@ -4585,7 +4899,7 @@ function publicGoalRun(run) {
4585
4899
  // package.json
4586
4900
  var package_default = {
4587
4901
  name: "@hasna/loops",
4588
- version: "0.3.59",
4902
+ version: "0.3.60",
4589
4903
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
4590
4904
  type: "module",
4591
4905
  main: "dist/index.js",
package/dist/lib/store.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 };