@hasna/loops 0.4.8 → 0.4.10

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/mcp/index.js CHANGED
@@ -56,8 +56,12 @@ function nowIso() {
56
56
  import { mkdirSync } from "fs";
57
57
  import { homedir } from "os";
58
58
  import { join } from "path";
59
+ function homeDir() {
60
+ const home = process.env.HOME?.trim();
61
+ return home ? home : homedir();
62
+ }
59
63
  function dataDir() {
60
- return process.env.LOOPS_DATA_DIR || join(homedir(), ".hasna", "loops");
64
+ return process.env.LOOPS_DATA_DIR || join(homeDir(), ".hasna", "loops");
61
65
  }
62
66
  function ensureDataDir() {
63
67
  const dir = dataDir();
@@ -74,10 +78,10 @@ function daemonLogPath() {
74
78
  return join(dataDir(), "daemon.log");
75
79
  }
76
80
  function systemdServicePath() {
77
- return join(homedir(), ".config", "systemd", "user", "loops-daemon.service");
81
+ return join(homeDir(), ".config", "systemd", "user", "loops-daemon.service");
78
82
  }
79
83
  function launchdPlistPath() {
80
- return join(homedir(), "Library", "LaunchAgents", "com.hasna.loops.daemon.plist");
84
+ return join(homeDir(), "Library", "LaunchAgents", "com.hasna.loops.daemon.plist");
81
85
  }
82
86
 
83
87
  // src/lib/process-identity.ts
@@ -581,7 +585,7 @@ import { isAbsolute, resolve } from "path";
581
585
 
582
586
  // src/lib/agent-adapter.ts
583
587
  import { spawn } from "child_process";
584
- var UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS = new Set([
588
+ var UNSAFE_CODEWITH_EXEC_EXTRA_ARGS = new Set([
585
589
  "e",
586
590
  "exec",
587
591
  "agent",
@@ -630,12 +634,12 @@ function validateAgentOptions(target, label, capabilities) {
630
634
  throw new Error(`${label}.agent is not supported for provider codex`);
631
635
  }
632
636
  if (provider === "codewith" && target.agent !== undefined) {
633
- throw new Error(`${label}.agent is not supported for provider codewith durable background-agent execution`);
637
+ throw new Error(`${label}.agent is not supported for provider codewith`);
634
638
  }
635
639
  if (provider === "codewith") {
636
- const unsafe = target.extraArgs?.find((arg) => UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS.has(arg));
640
+ const unsafe = target.extraArgs?.find((arg) => UNSAFE_CODEWITH_EXEC_EXTRA_ARGS.has(arg));
637
641
  if (unsafe) {
638
- throw new Error(`${label}.extraArgs cannot include ${unsafe}; codewith agent steps use durable agent start, not exec/ephemeral flags`);
642
+ throw new Error(`${label}.extraArgs cannot include ${unsafe}; codewith exec launch flags are managed by the adapter`);
639
643
  }
640
644
  }
641
645
  if (target.addDirs?.length && !["codewith", "codex"].includes(provider)) {
@@ -718,7 +722,10 @@ function buildAgentInvocation(target) {
718
722
  args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : []);
719
723
  if (target.variant)
720
724
  args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
721
- args.push("--ask-for-approval", "never", "--sandbox", codewithLikeSandbox(target));
725
+ const sandbox = codewithLikeSandbox(target);
726
+ if (sandbox === "workspace-write")
727
+ args.push("-c", "sandbox_workspace_write.network_access=true");
728
+ args.push("--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", sandbox, "--skip-git-repo-check");
722
729
  if (target.cwd)
723
730
  args.push("--cd", target.cwd);
724
731
  for (const dir of target.addDirs ?? [])
@@ -726,11 +733,7 @@ function buildAgentInvocation(target) {
726
733
  if (target.model)
727
734
  args.push("--model", target.model);
728
735
  args.push(...target.extraArgs ?? []);
729
- args.push("agent", "start", "--json");
730
- if (target.cwd)
731
- args.push("--cwd", target.cwd);
732
- args.push(target.prompt);
733
- return { command: "codewith", args };
736
+ return { command: "codewith", args, stdin: target.prompt };
734
737
  }
735
738
  case "codex": {
736
739
  if (target.variant)
@@ -783,7 +786,7 @@ function adapterFor(provider, capabilities) {
783
786
  var PROVIDER_ADAPTERS = {
784
787
  claude: adapterFor("claude", { sandbox: [], durable: false, remote: true, promptChannel: "stdin" }),
785
788
  cursor: adapterFor("cursor", { sandbox: CURSOR_SANDBOXES, durable: false, remote: true, promptChannel: "stdin" }),
786
- codewith: adapterFor("codewith", { sandbox: CODEX_LIKE_SANDBOXES, durable: true, remote: false, promptChannel: "argv" }),
789
+ codewith: adapterFor("codewith", { sandbox: CODEX_LIKE_SANDBOXES, durable: false, remote: true, promptChannel: "stdin" }),
787
790
  codex: adapterFor("codex", { sandbox: CODEX_LIKE_SANDBOXES, durable: false, remote: true, promptChannel: "stdin" }),
788
791
  aicopilot: adapterFor("aicopilot", { sandbox: [], durable: false, remote: true, promptChannel: "stdin" }),
789
792
  opencode: adapterFor("opencode", { sandbox: [], durable: false, remote: true, promptChannel: "stdin" })
@@ -1475,6 +1478,21 @@ function workItemStatusForLoopRun(status, attempt, maxAttempts) {
1475
1478
  function scrubbedOrNull(value) {
1476
1479
  return value == null ? null : scrubSecrets(value);
1477
1480
  }
1481
+ var MAX_PERSISTED_RUN_OUTPUT_CHARS = 64 * 1024;
1482
+ function clampPersistedRunOutput(value) {
1483
+ if (value == null || value.length <= MAX_PERSISTED_RUN_OUTPUT_CHARS)
1484
+ return value;
1485
+ const half = Math.floor(MAX_PERSISTED_RUN_OUTPUT_CHARS / 2);
1486
+ const head = value.slice(0, half);
1487
+ const tail = value.slice(value.length - half);
1488
+ const omitted = value.length - head.length - tail.length;
1489
+ return `${head}
1490
+ \u2026[${omitted} chars truncated by loops run-output retention]\u2026
1491
+ ${tail}`;
1492
+ }
1493
+ function persistedRunOutput(value) {
1494
+ return clampPersistedRunOutput(scrubbedOrNull(value));
1495
+ }
1478
1496
  function chmodIfExists(path, mode) {
1479
1497
  try {
1480
1498
  if (existsSync(path))
@@ -3221,8 +3239,8 @@ class Store {
3221
3239
  ))`).run({
3222
3240
  $workflowRunId: workflowRunId,
3223
3241
  $stepId: stepId,
3224
- $stdout: progress.stdout === undefined ? null : scrubbedOrNull(progress.stdout),
3225
- $stderr: progress.stderr === undefined ? null : scrubbedOrNull(progress.stderr),
3242
+ $stdout: progress.stdout === undefined ? null : persistedRunOutput(progress.stdout),
3243
+ $stderr: progress.stderr === undefined ? null : persistedRunOutput(progress.stderr),
3226
3244
  $updated: now,
3227
3245
  $daemonLeaseId: opts.daemonLeaseId ?? null,
3228
3246
  $now: now
@@ -3283,8 +3301,8 @@ class Store {
3283
3301
  $finished: finishedAt,
3284
3302
  $exitCode: patch.exitCode ?? null,
3285
3303
  $durationMs: patch.durationMs ?? null,
3286
- $stdout: scrubbedOrNull(patch.stdout),
3287
- $stderr: scrubbedOrNull(patch.stderr),
3304
+ $stdout: persistedRunOutput(patch.stdout),
3305
+ $stderr: persistedRunOutput(patch.stderr),
3288
3306
  $error: error ?? null,
3289
3307
  $updated: finishedAt,
3290
3308
  $daemonLeaseId: opts.daemonLeaseId ?? null,
@@ -3662,8 +3680,8 @@ class Store {
3662
3680
  $pid: patch.pid ?? null,
3663
3681
  $exitCode: patch.exitCode ?? null,
3664
3682
  $durationMs: patch.durationMs ?? null,
3665
- $stdout: scrubbedOrNull(patch.stdout),
3666
- $stderr: scrubbedOrNull(patch.stderr),
3683
+ $stdout: persistedRunOutput(patch.stdout),
3684
+ $stderr: persistedRunOutput(patch.stderr),
3667
3685
  $error: error ?? null,
3668
3686
  $updated: finishedAt,
3669
3687
  $claimedBy: opts.claimedBy ?? null,
@@ -4060,8 +4078,8 @@ class Store {
4060
4078
  $processStartedAt: run.processStartedAt ?? null,
4061
4079
  $exitCode: run.exitCode ?? null,
4062
4080
  $durationMs: run.durationMs ?? null,
4063
- $stdout: scrubbedOrNull(run.stdout),
4064
- $stderr: scrubbedOrNull(run.stderr),
4081
+ $stdout: persistedRunOutput(run.stdout),
4082
+ $stderr: persistedRunOutput(run.stderr),
4065
4083
  $error: scrubbedOrNull(run.error),
4066
4084
  $goalRunId: run.goalRunId ?? null,
4067
4085
  $created: run.createdAt,
@@ -4744,11 +4762,6 @@ var TRANSPORT_ENV_KEYS = new Set([
4744
4762
  "USER",
4745
4763
  "XDG_RUNTIME_DIR"
4746
4764
  ]);
4747
- function boundedText(text, maxBytes) {
4748
- const buffer = new BoundedOutputBuffer(maxBytes);
4749
- buffer.append(text);
4750
- return buffer.value();
4751
- }
4752
4765
  function buildResult(status, startedAt, fields = {}) {
4753
4766
  const finishedAt = fields.finishedAt ?? nowIso();
4754
4767
  return {
@@ -4786,6 +4799,20 @@ function notifySpawn(pid, opts) {
4786
4799
  function shellQuote(value) {
4787
4800
  return `'${value.replace(/'/g, `'\\''`)}'`;
4788
4801
  }
4802
+ function codewithProfileCandidateFromLine(line) {
4803
+ const cols = line.trim().split(/\s+/);
4804
+ if (cols[0] === "*")
4805
+ return cols[1];
4806
+ return cols[0];
4807
+ }
4808
+ function codewithProfileForError(profile) {
4809
+ return /[\u0000-\u001F\u007F]/.test(profile) ? JSON.stringify(profile) ?? profile : profile;
4810
+ }
4811
+ function assertCodewithAuthProfileSupported(profile) {
4812
+ if (profile.includes("\x00")) {
4813
+ throw new Error(`codewith auth profile contains unsupported NUL byte: ${codewithProfileForError(profile)}`);
4814
+ }
4815
+ }
4789
4816
  function metadataEnv(metadata) {
4790
4817
  const env = {};
4791
4818
  if (metadata.loopId)
@@ -4812,21 +4839,6 @@ function metadataEnv(metadata) {
4812
4839
  env.LOOPS_GOAL_NODE_KEY = metadata.goalNodeKey;
4813
4840
  return env;
4814
4841
  }
4815
- function codewithAgentIdempotencyKey(metadata) {
4816
- const parts = [
4817
- "openloops",
4818
- metadata.workflowRunId ? `workflow-run:${metadata.workflowRunId}` : undefined,
4819
- metadata.workflowStepId ? `step:${metadata.workflowStepId}` : undefined,
4820
- metadata.runId ? `loop-run:${metadata.runId}` : undefined,
4821
- metadata.loopId ? `loop:${metadata.loopId}` : undefined,
4822
- metadata.scheduledFor ? `scheduled:${metadata.scheduledFor}` : undefined,
4823
- metadata.goalId ? `goal:${metadata.goalId}` : undefined,
4824
- metadata.goalNodeKey ? `node:${metadata.goalNodeKey}` : undefined
4825
- ].filter(Boolean);
4826
- if (parts.length > 1)
4827
- return parts.join(":");
4828
- return `openloops:adhoc:${randomBytes2(16).toString("hex")}`;
4829
- }
4830
4842
  function allowlistEnv(allowlist) {
4831
4843
  const env = {};
4832
4844
  if (allowlist?.tools?.length)
@@ -4867,6 +4879,9 @@ function commandSpec(target, opts) {
4867
4879
  };
4868
4880
  }
4869
4881
  const agentTarget = target;
4882
+ if (agentTarget.provider === "codewith" && agentTarget.authProfile) {
4883
+ assertCodewithAuthProfileSupported(agentTarget.authProfile);
4884
+ }
4870
4885
  const adapter = providerAdapter(agentTarget.provider);
4871
4886
  const invocation = adapter.buildInvocation(agentTarget);
4872
4887
  return {
@@ -4881,8 +4896,7 @@ function commandSpec(target, opts) {
4881
4896
  preflightAnyOf: invocation.preflightAnyOf,
4882
4897
  stdin: invocation.stdin,
4883
4898
  allowlist: agentTarget.allowlist,
4884
- worktree: agentTarget.worktree,
4885
- codewithDurableAgent: adapter.capabilities.durable ? { target: agentTarget } : undefined
4899
+ worktree: agentTarget.worktree
4886
4900
  };
4887
4901
  }
4888
4902
  function composeExecutionEnv(spec, metadata, opts, accountEnv) {
@@ -5040,7 +5054,8 @@ function remotePreflightScript(spec, metadata) {
5040
5054
  lines.push(`if ! ${spec.preflightAnyOf.map((command) => `command -v ${shellQuote(command)} >/dev/null 2>&1`).join(" && ! ")}; then`, ` echo 'none of required executables found: ${spec.preflightAnyOf.join(", ")}' >&2`, " exit 127", "fi");
5041
5055
  }
5042
5056
  if (spec.nativeAuthProfile?.provider === "codewith") {
5043
- lines.push(`__OPENLOOPS_CODEWITH_PROFILES="$(${shellQuote(spec.command)} profile list)" || {`, ` printf '%s\\n' ${shellQuote("codewith auth profile preflight failed")} >&2`, " exit 1", "}", `if ! printf '%s\\n' "$__OPENLOOPS_CODEWITH_PROFILES" | awk 'NR > 1 { print $1 }' | grep -Fx ${shellQuote(spec.nativeAuthProfile.profile)} >/dev/null; then`, ` printf '%s\\n' ${shellQuote(`codewith auth profile not found: ${spec.nativeAuthProfile.profile}`)} >&2`, " exit 1", "fi");
5057
+ const profileForError = codewithProfileForError(spec.nativeAuthProfile.profile);
5058
+ lines.push(`__OPENLOOPS_CODEWITH_PROFILE=${shellQuote(spec.nativeAuthProfile.profile)}`, "export __OPENLOOPS_CODEWITH_PROFILE", `__OPENLOOPS_CODEWITH_PROFILES="$(${shellQuote(spec.command)} profile list)" || {`, ` printf '%s\\n' ${shellQuote("codewith auth profile preflight failed")} >&2`, " exit 1", "}", `if ! printf '%s\\n' "$__OPENLOOPS_CODEWITH_PROFILES" | awk 'NR > 1 { candidate = ($1 == "*" ? $2 : $1); if (candidate == ENVIRON["__OPENLOOPS_CODEWITH_PROFILE"]) found = 1 } END { exit(found ? 0 : 1) }'; then`, ` printf '%s\\n' ${shellQuote(`codewith auth profile not found: ${profileForError}`)} >&2`, " exit 1", "fi");
5044
5059
  }
5045
5060
  return lines.join(`
5046
5061
  `);
@@ -5065,9 +5080,9 @@ function assertCodewithProfileListed(profile, result) {
5065
5080
  const detail = (result.stderr || result.stdout || `exit ${result.status ?? "unknown"}`).trim();
5066
5081
  throw new Error(`codewith auth profile preflight failed${detail ? `: ${detail}` : ""}`);
5067
5082
  }
5068
- const profiles = new Set((result.stdout || "").split(/\r?\n/).slice(1).map((line) => line.trim().split(/\s+/)[0]).filter(Boolean));
5083
+ const profiles = new Set((result.stdout || "").split(/\r?\n/).slice(1).map(codewithProfileCandidateFromLine).filter(Boolean));
5069
5084
  if (!profiles.has(profile)) {
5070
- throw new Error(`codewith auth profile not found: ${profile}`);
5085
+ throw new Error(`codewith auth profile not found: ${codewithProfileForError(profile)}`);
5071
5086
  }
5072
5087
  }
5073
5088
  function preflightNativeAuthProfileSync(spec, env) {
@@ -5092,266 +5107,6 @@ async function preflightNativeAuthProfile(spec, env) {
5092
5107
  const result = await spawnCapture(spec.command, ["profile", "list"], { env, timeoutMs: 15000 });
5093
5108
  assertCodewithProfileListed(spec.nativeAuthProfile.profile, result);
5094
5109
  }
5095
- function codewithAgentStartArgs(target, idempotencyKey) {
5096
- const args = providerAdapter(target.provider).buildInvocation(target).args;
5097
- const startIndex = args.findIndex((arg, index) => arg === "start" && args[index - 1] === "agent");
5098
- if (startIndex === -1)
5099
- throw new Error("internal error: codewith durable agent args missing agent start");
5100
- args.splice(startIndex + 1, 0, "--idempotency-key", idempotencyKey);
5101
- return args;
5102
- }
5103
- function codewithAgentControlArgs(target, command, agentId) {
5104
- return [
5105
- ...target.authProfile ? ["--auth-profile", target.authProfile] : [],
5106
- "agent",
5107
- command,
5108
- ...command === "logs" ? ["--limit", "20"] : [],
5109
- "--json",
5110
- agentId
5111
- ];
5112
- }
5113
- function extractFirstJsonObject(text) {
5114
- let depth = 0;
5115
- let start = -1;
5116
- let inString = false;
5117
- let escaped = false;
5118
- for (let i = 0;i < text.length; i += 1) {
5119
- const ch = text[i];
5120
- if (inString) {
5121
- if (escaped)
5122
- escaped = false;
5123
- else if (ch === "\\")
5124
- escaped = true;
5125
- else if (ch === '"')
5126
- inString = false;
5127
- continue;
5128
- }
5129
- if (ch === '"') {
5130
- inString = true;
5131
- } else if (ch === "{") {
5132
- if (depth === 0)
5133
- start = i;
5134
- depth += 1;
5135
- } else if (ch === "}") {
5136
- if (depth > 0) {
5137
- depth -= 1;
5138
- if (depth === 0 && start !== -1)
5139
- return text.slice(start, i + 1);
5140
- }
5141
- }
5142
- }
5143
- return;
5144
- }
5145
- function parseJsonOutput(stdout, label) {
5146
- const raw = stdout || "{}";
5147
- const candidates = [raw.trim()];
5148
- const scanned = extractFirstJsonObject(raw);
5149
- if (scanned && scanned !== raw.trim())
5150
- candidates.push(scanned);
5151
- let lastError;
5152
- for (const candidate of candidates) {
5153
- try {
5154
- const value = JSON.parse(candidate);
5155
- if (!value || typeof value !== "object" || Array.isArray(value))
5156
- throw new Error("not an object");
5157
- return value;
5158
- } catch (error) {
5159
- lastError = error;
5160
- }
5161
- }
5162
- throw new Error(`${label} did not return JSON${lastError instanceof Error ? `: ${lastError.message}` : ""}`);
5163
- }
5164
- function recordField(value, key) {
5165
- if (!value || typeof value !== "object" || Array.isArray(value))
5166
- return;
5167
- const field = value[key];
5168
- return field && typeof field === "object" && !Array.isArray(field) ? field : undefined;
5169
- }
5170
- function stringField(value, key) {
5171
- const field = value?.[key];
5172
- return typeof field === "string" ? field : undefined;
5173
- }
5174
- function numberField(value, key) {
5175
- const field = value?.[key];
5176
- return typeof field === "number" && Number.isFinite(field) ? field : undefined;
5177
- }
5178
- function codewithAgentStatus(readJson) {
5179
- return stringField(recordField(readJson, "agent"), "status") ?? stringField(recordField(readJson, "statusSnapshot"), "status");
5180
- }
5181
- function codewithAgentLastEventSeq(readJson) {
5182
- const agentSeq = numberField(recordField(readJson, "agent"), "lastEventSeq");
5183
- const snapshotSeq = numberField(recordField(readJson, "statusSnapshot"), "lastEventSeq");
5184
- if (agentSeq !== undefined && snapshotSeq !== undefined)
5185
- return Math.max(agentSeq, snapshotSeq);
5186
- return agentSeq ?? snapshotSeq;
5187
- }
5188
- function codewithAgentEvidence(startJson, readJson, logsJson) {
5189
- const agent = recordField(readJson, "agent") ?? recordField(startJson, "agent");
5190
- const statusSnapshot = recordField(readJson, "statusSnapshot");
5191
- const events = Array.isArray(logsJson?.data) ? logsJson.data.filter((event) => Boolean(event) && typeof event === "object" && !Array.isArray(event)).map((event) => ({
5192
- seq: numberField(event, "seq"),
5193
- eventType: stringField(event, "eventType"),
5194
- createdAt: numberField(event, "createdAt")
5195
- })) : undefined;
5196
- return JSON.stringify({
5197
- codewithAgent: {
5198
- agentId: stringField(agent, "agentId"),
5199
- status: stringField(agent, "status"),
5200
- desiredState: stringField(agent, "desiredState"),
5201
- statusReason: stringField(agent, "statusReason"),
5202
- threadId: stringField(agent, "threadId"),
5203
- rolloutPath: stringField(agent, "rolloutPath"),
5204
- pid: numberField(agent, "pid"),
5205
- exitCode: numberField(agent, "exitCode"),
5206
- created: typeof startJson.created === "boolean" ? startJson.created : undefined
5207
- },
5208
- statusSnapshot: statusSnapshot ? {
5209
- seq: numberField(statusSnapshot, "seq"),
5210
- status: stringField(statusSnapshot, "status"),
5211
- summary: stringField(statusSnapshot, "summary"),
5212
- pendingInteractionCount: numberField(statusSnapshot, "pendingInteractionCount"),
5213
- lastEventSeq: numberField(statusSnapshot, "lastEventSeq")
5214
- } : undefined,
5215
- events
5216
- }, null, 2);
5217
- }
5218
- function codewithAgentProgress(readJson) {
5219
- const agent = recordField(readJson, "agent");
5220
- const statusSnapshot = recordField(readJson, "statusSnapshot");
5221
- return {
5222
- provider: "codewith",
5223
- agentId: stringField(agent, "agentId"),
5224
- status: stringField(agent, "status") ?? stringField(statusSnapshot, "status"),
5225
- summary: stringField(statusSnapshot, "summary"),
5226
- statusReason: stringField(agent, "statusReason"),
5227
- threadId: stringField(agent, "threadId"),
5228
- rolloutPath: stringField(agent, "rolloutPath"),
5229
- pid: numberField(agent, "pid"),
5230
- lastEventSeq: codewithAgentLastEventSeq(readJson)
5231
- };
5232
- }
5233
- function sleep(ms) {
5234
- return new Promise((resolve3) => setTimeout(resolve3, ms));
5235
- }
5236
- async function executeCodewithDurableAgent(spec, metadata, opts, env, startedAt) {
5237
- const target = spec.codewithDurableAgent?.target;
5238
- if (!target)
5239
- throw new Error("internal error: missing codewith durable target");
5240
- const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
5241
- const idempotencyKey = codewithAgentIdempotencyKey(metadata);
5242
- const start = await spawnCapture(spec.command, codewithAgentStartArgs(target, idempotencyKey), {
5243
- cwd: spec.cwd,
5244
- env,
5245
- timeoutMs: 30000,
5246
- maxOutputBytes
5247
- });
5248
- const stderr = new BoundedOutputBuffer(maxOutputBytes);
5249
- stderr.append(start.stderr);
5250
- if (start.error || (start.status ?? 1) !== 0) {
5251
- return failureResult(startedAt, start.error ?? `codewith agent start exited with code ${start.status ?? "unknown"}`, {
5252
- exitCode: start.status ?? undefined,
5253
- stdout: start.stdout,
5254
- stderr: stderr.value()
5255
- });
5256
- }
5257
- let startJson;
5258
- try {
5259
- startJson = parseJsonOutput(start.stdout || "{}", "codewith agent start");
5260
- } catch (error) {
5261
- return failureResult(startedAt, error instanceof Error ? error.message : String(error), { stdout: start.stdout, stderr: stderr.value() });
5262
- }
5263
- const agentId = stringField(recordField(startJson, "agent"), "agentId");
5264
- if (!agentId) {
5265
- return failureResult(startedAt, "codewith agent start did not return agent.agentId", { stdout: start.stdout, stderr: stderr.value() });
5266
- }
5267
- opts.onAgentProgress?.(codewithAgentProgress(startJson));
5268
- const stopAgent = async () => {
5269
- await spawnCapture(spec.command, codewithAgentControlArgs(target, "stop", agentId), {
5270
- cwd: spec.cwd,
5271
- env,
5272
- timeoutMs: 15000,
5273
- maxOutputBytes
5274
- });
5275
- };
5276
- const pollMs = Math.max(100, Number(opts.env?.LOOPS_CODEWITH_AGENT_POLL_MS ?? process.env.LOOPS_CODEWITH_AGENT_POLL_MS ?? 2000) || 2000);
5277
- let lastReadJson = startJson;
5278
- let lastLogsJson;
5279
- let lastFingerprint;
5280
- let lastProgressAt = Date.now();
5281
- const evidence = () => boundedText(codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes);
5282
- while (true) {
5283
- if (opts.signal?.aborted) {
5284
- await stopAgent();
5285
- return failureResult(startedAt, "cancelled", { stdout: evidence(), stderr: stderr.value() });
5286
- }
5287
- const read = await spawnCapture(spec.command, codewithAgentControlArgs(target, "read", agentId), {
5288
- cwd: spec.cwd,
5289
- env,
5290
- timeoutMs: 30000,
5291
- maxOutputBytes
5292
- });
5293
- stderr.append(read.stderr);
5294
- if (read.error || (read.status ?? 1) !== 0) {
5295
- return failureResult(startedAt, read.error ?? `codewith agent read exited with code ${read.status ?? "unknown"}`, {
5296
- exitCode: read.status ?? undefined,
5297
- stdout: evidence(),
5298
- stderr: stderr.value()
5299
- });
5300
- }
5301
- try {
5302
- lastReadJson = parseJsonOutput(read.stdout || "{}", "codewith agent read");
5303
- } catch (error) {
5304
- return failureResult(startedAt, error instanceof Error ? error.message : String(error), {
5305
- stdout: boundedText(read.stdout, maxOutputBytes),
5306
- stderr: stderr.value()
5307
- });
5308
- }
5309
- const status = codewithAgentStatus(lastReadJson);
5310
- const fingerprint = JSON.stringify({
5311
- status,
5312
- agentLastEventSeq: numberField(recordField(lastReadJson, "agent"), "lastEventSeq"),
5313
- snapshot: recordField(lastReadJson, "statusSnapshot") ?? null
5314
- });
5315
- if (fingerprint !== lastFingerprint) {
5316
- lastFingerprint = fingerprint;
5317
- lastProgressAt = Date.now();
5318
- opts.onAgentProgress?.(codewithAgentProgress(lastReadJson));
5319
- }
5320
- if (status === "completed" || status === "failed" || status === "cancelled") {
5321
- const logs = await spawnCapture(spec.command, codewithAgentControlArgs(target, "logs", agentId), {
5322
- cwd: spec.cwd,
5323
- env,
5324
- timeoutMs: 30000,
5325
- maxOutputBytes
5326
- });
5327
- if (!logs.error && (logs.status ?? 1) === 0) {
5328
- try {
5329
- lastLogsJson = parseJsonOutput(logs.stdout || "{}", "codewith agent logs");
5330
- } catch {
5331
- lastLogsJson = undefined;
5332
- }
5333
- } else {
5334
- stderr.append(logs.stderr || logs.error || "");
5335
- }
5336
- if (status === "completed") {
5337
- return successResult(startedAt, { exitCode: 0, stdout: evidence(), stderr: stderr.value() });
5338
- }
5339
- return failureResult(startedAt, stringField(recordField(lastReadJson, "agent"), "statusReason") ?? `codewith agent ${status}`, { exitCode: 1, stdout: evidence(), stderr: stderr.value() });
5340
- }
5341
- if (typeof spec.timeoutMs === "number" && new Date(nowIso()).getTime() - new Date(startedAt).getTime() >= spec.timeoutMs) {
5342
- await stopAgent();
5343
- return timeoutResult(startedAt, `timed out after ${spec.timeoutMs}ms`, { stdout: evidence(), stderr: stderr.value() });
5344
- }
5345
- if (typeof spec.idleTimeoutMs === "number" && Date.now() - lastProgressAt >= spec.idleTimeoutMs) {
5346
- await stopAgent();
5347
- return timeoutResult(startedAt, `idle timed out after ${spec.idleTimeoutMs}ms without agent progress`, {
5348
- stdout: evidence(),
5349
- stderr: stderr.value()
5350
- });
5351
- }
5352
- await sleep(pollMs);
5353
- }
5354
- }
5355
5110
  function resolvedDirEquals(left, right) {
5356
5111
  try {
5357
5112
  return realpathSync(left) === realpathSync(right);
@@ -5585,9 +5340,6 @@ function preflightTarget(target, metadata = {}, opts = {}) {
5585
5340
  async function executeTarget(target, metadata = {}, opts = {}) {
5586
5341
  let spec = commandSpec(target, opts);
5587
5342
  const machine = resolvedMachine(opts);
5588
- if (machine && !machine.local && spec.codewithDurableAgent) {
5589
- return failureResult(nowIso(), "remote Codewith durable background-agent steps require remote status polling support; run this Codewith step locally or add remote durable readback before dispatch");
5590
- }
5591
5343
  if (machine && !machine.local) {
5592
5344
  const remoteFallbackSpec = spec.worktree?.enabled && spec.worktree.mode === "auto" ? worktreeFallbackSpec(target, opts, spec.worktree.originalCwd) : undefined;
5593
5345
  return executeRemoteSpec(spec, machine, metadata, opts, remoteFallbackSpec);
@@ -5618,9 +5370,6 @@ async function executeTarget(target, metadata = {}, opts = {}) {
5618
5370
  if (worktreeEntry?.fallbackCwd) {
5619
5371
  spec = worktreeFallbackSpec(target, opts, worktreeEntry.fallbackCwd) ?? spec;
5620
5372
  }
5621
- if (spec.codewithDurableAgent) {
5622
- return executeCodewithDurableAgent(spec, metadata, opts, env, startedAt);
5623
- }
5624
5373
  const child = spawn2(spec.command, spec.args, {
5625
5374
  cwd: spec.cwd,
5626
5375
  env,
@@ -7719,7 +7468,7 @@ async function tick(deps) {
7719
7468
  // package.json
7720
7469
  var package_default = {
7721
7470
  name: "@hasna/loops",
7722
- version: "0.4.8",
7471
+ version: "0.4.10",
7723
7472
  description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
7724
7473
  type: "module",
7725
7474
  main: "dist/index.js",