@hasna/loops 0.4.8 → 0.4.9
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/CHANGELOG.md +24 -0
- package/README.md +4 -4
- package/dist/api/index.js +1 -1
- package/dist/cli/index.js +83 -345
- package/dist/daemon/index.js +25 -313
- package/dist/index.js +12 -300
- package/dist/lib/agent-adapter.d.ts +1 -1
- package/dist/lib/mode.js +1 -1
- package/dist/lib/route/pr-review.d.ts +11 -1
- package/dist/lib/storage/index.js +10 -11
- package/dist/lib/storage/sqlite.js +10 -11
- package/dist/lib/store.js +10 -11
- package/dist/mcp/index.js +12 -300
- package/dist/runner/index.js +12 -300
- package/dist/sdk/index.js +12 -300
- package/docs/USAGE.md +4 -4
- package/package.json +1 -1
package/dist/sdk/index.js
CHANGED
|
@@ -579,7 +579,7 @@ import { isAbsolute, resolve } from "path";
|
|
|
579
579
|
|
|
580
580
|
// src/lib/agent-adapter.ts
|
|
581
581
|
import { spawn } from "child_process";
|
|
582
|
-
var
|
|
582
|
+
var UNSAFE_CODEWITH_EXEC_EXTRA_ARGS = new Set([
|
|
583
583
|
"e",
|
|
584
584
|
"exec",
|
|
585
585
|
"agent",
|
|
@@ -628,12 +628,12 @@ function validateAgentOptions(target, label, capabilities) {
|
|
|
628
628
|
throw new Error(`${label}.agent is not supported for provider codex`);
|
|
629
629
|
}
|
|
630
630
|
if (provider === "codewith" && target.agent !== undefined) {
|
|
631
|
-
throw new Error(`${label}.agent is not supported for provider codewith
|
|
631
|
+
throw new Error(`${label}.agent is not supported for provider codewith`);
|
|
632
632
|
}
|
|
633
633
|
if (provider === "codewith") {
|
|
634
|
-
const unsafe = target.extraArgs?.find((arg) =>
|
|
634
|
+
const unsafe = target.extraArgs?.find((arg) => UNSAFE_CODEWITH_EXEC_EXTRA_ARGS.has(arg));
|
|
635
635
|
if (unsafe) {
|
|
636
|
-
throw new Error(`${label}.extraArgs cannot include ${unsafe}; codewith
|
|
636
|
+
throw new Error(`${label}.extraArgs cannot include ${unsafe}; codewith exec launch flags are managed by the adapter`);
|
|
637
637
|
}
|
|
638
638
|
}
|
|
639
639
|
if (target.addDirs?.length && !["codewith", "codex"].includes(provider)) {
|
|
@@ -716,7 +716,10 @@ function buildAgentInvocation(target) {
|
|
|
716
716
|
args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : []);
|
|
717
717
|
if (target.variant)
|
|
718
718
|
args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
|
|
719
|
-
|
|
719
|
+
const sandbox = codewithLikeSandbox(target);
|
|
720
|
+
if (sandbox === "workspace-write")
|
|
721
|
+
args.push("-c", "sandbox_workspace_write.network_access=true");
|
|
722
|
+
args.push("--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", sandbox, "--skip-git-repo-check");
|
|
720
723
|
if (target.cwd)
|
|
721
724
|
args.push("--cd", target.cwd);
|
|
722
725
|
for (const dir of target.addDirs ?? [])
|
|
@@ -724,11 +727,7 @@ function buildAgentInvocation(target) {
|
|
|
724
727
|
if (target.model)
|
|
725
728
|
args.push("--model", target.model);
|
|
726
729
|
args.push(...target.extraArgs ?? []);
|
|
727
|
-
|
|
728
|
-
if (target.cwd)
|
|
729
|
-
args.push("--cwd", target.cwd);
|
|
730
|
-
args.push(target.prompt);
|
|
731
|
-
return { command: "codewith", args };
|
|
730
|
+
return { command: "codewith", args, stdin: target.prompt };
|
|
732
731
|
}
|
|
733
732
|
case "codex": {
|
|
734
733
|
if (target.variant)
|
|
@@ -781,7 +780,7 @@ function adapterFor(provider, capabilities) {
|
|
|
781
780
|
var PROVIDER_ADAPTERS = {
|
|
782
781
|
claude: adapterFor("claude", { sandbox: [], durable: false, remote: true, promptChannel: "stdin" }),
|
|
783
782
|
cursor: adapterFor("cursor", { sandbox: CURSOR_SANDBOXES, durable: false, remote: true, promptChannel: "stdin" }),
|
|
784
|
-
codewith: adapterFor("codewith", { sandbox: CODEX_LIKE_SANDBOXES, durable:
|
|
783
|
+
codewith: adapterFor("codewith", { sandbox: CODEX_LIKE_SANDBOXES, durable: false, remote: true, promptChannel: "stdin" }),
|
|
785
784
|
codex: adapterFor("codex", { sandbox: CODEX_LIKE_SANDBOXES, durable: false, remote: true, promptChannel: "stdin" }),
|
|
786
785
|
aicopilot: adapterFor("aicopilot", { sandbox: [], durable: false, remote: true, promptChannel: "stdin" }),
|
|
787
786
|
opencode: adapterFor("opencode", { sandbox: [], durable: false, remote: true, promptChannel: "stdin" })
|
|
@@ -4217,7 +4216,7 @@ class Store {
|
|
|
4217
4216
|
// package.json
|
|
4218
4217
|
var package_default = {
|
|
4219
4218
|
name: "@hasna/loops",
|
|
4220
|
-
version: "0.4.
|
|
4219
|
+
version: "0.4.9",
|
|
4221
4220
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
4222
4221
|
type: "module",
|
|
4223
4222
|
main: "dist/index.js",
|
|
@@ -5004,11 +5003,6 @@ var TRANSPORT_ENV_KEYS = new Set([
|
|
|
5004
5003
|
"USER",
|
|
5005
5004
|
"XDG_RUNTIME_DIR"
|
|
5006
5005
|
]);
|
|
5007
|
-
function boundedText(text, maxBytes) {
|
|
5008
|
-
const buffer = new BoundedOutputBuffer(maxBytes);
|
|
5009
|
-
buffer.append(text);
|
|
5010
|
-
return buffer.value();
|
|
5011
|
-
}
|
|
5012
5006
|
function buildResult(status, startedAt, fields = {}) {
|
|
5013
5007
|
const finishedAt = fields.finishedAt ?? nowIso();
|
|
5014
5008
|
return {
|
|
@@ -5072,21 +5066,6 @@ function metadataEnv(metadata) {
|
|
|
5072
5066
|
env.LOOPS_GOAL_NODE_KEY = metadata.goalNodeKey;
|
|
5073
5067
|
return env;
|
|
5074
5068
|
}
|
|
5075
|
-
function codewithAgentIdempotencyKey(metadata) {
|
|
5076
|
-
const parts = [
|
|
5077
|
-
"openloops",
|
|
5078
|
-
metadata.workflowRunId ? `workflow-run:${metadata.workflowRunId}` : undefined,
|
|
5079
|
-
metadata.workflowStepId ? `step:${metadata.workflowStepId}` : undefined,
|
|
5080
|
-
metadata.runId ? `loop-run:${metadata.runId}` : undefined,
|
|
5081
|
-
metadata.loopId ? `loop:${metadata.loopId}` : undefined,
|
|
5082
|
-
metadata.scheduledFor ? `scheduled:${metadata.scheduledFor}` : undefined,
|
|
5083
|
-
metadata.goalId ? `goal:${metadata.goalId}` : undefined,
|
|
5084
|
-
metadata.goalNodeKey ? `node:${metadata.goalNodeKey}` : undefined
|
|
5085
|
-
].filter(Boolean);
|
|
5086
|
-
if (parts.length > 1)
|
|
5087
|
-
return parts.join(":");
|
|
5088
|
-
return `openloops:adhoc:${randomBytes2(16).toString("hex")}`;
|
|
5089
|
-
}
|
|
5090
5069
|
function allowlistEnv(allowlist) {
|
|
5091
5070
|
const env = {};
|
|
5092
5071
|
if (allowlist?.tools?.length)
|
|
@@ -5141,8 +5120,7 @@ function commandSpec(target, opts) {
|
|
|
5141
5120
|
preflightAnyOf: invocation.preflightAnyOf,
|
|
5142
5121
|
stdin: invocation.stdin,
|
|
5143
5122
|
allowlist: agentTarget.allowlist,
|
|
5144
|
-
worktree: agentTarget.worktree
|
|
5145
|
-
codewithDurableAgent: adapter.capabilities.durable ? { target: agentTarget } : undefined
|
|
5123
|
+
worktree: agentTarget.worktree
|
|
5146
5124
|
};
|
|
5147
5125
|
}
|
|
5148
5126
|
function composeExecutionEnv(spec, metadata, opts, accountEnv) {
|
|
@@ -5352,266 +5330,6 @@ async function preflightNativeAuthProfile(spec, env) {
|
|
|
5352
5330
|
const result = await spawnCapture(spec.command, ["profile", "list"], { env, timeoutMs: 15000 });
|
|
5353
5331
|
assertCodewithProfileListed(spec.nativeAuthProfile.profile, result);
|
|
5354
5332
|
}
|
|
5355
|
-
function codewithAgentStartArgs(target, idempotencyKey) {
|
|
5356
|
-
const args = providerAdapter(target.provider).buildInvocation(target).args;
|
|
5357
|
-
const startIndex = args.findIndex((arg, index) => arg === "start" && args[index - 1] === "agent");
|
|
5358
|
-
if (startIndex === -1)
|
|
5359
|
-
throw new Error("internal error: codewith durable agent args missing agent start");
|
|
5360
|
-
args.splice(startIndex + 1, 0, "--idempotency-key", idempotencyKey);
|
|
5361
|
-
return args;
|
|
5362
|
-
}
|
|
5363
|
-
function codewithAgentControlArgs(target, command, agentId) {
|
|
5364
|
-
return [
|
|
5365
|
-
...target.authProfile ? ["--auth-profile", target.authProfile] : [],
|
|
5366
|
-
"agent",
|
|
5367
|
-
command,
|
|
5368
|
-
...command === "logs" ? ["--limit", "20"] : [],
|
|
5369
|
-
"--json",
|
|
5370
|
-
agentId
|
|
5371
|
-
];
|
|
5372
|
-
}
|
|
5373
|
-
function extractFirstJsonObject(text) {
|
|
5374
|
-
let depth = 0;
|
|
5375
|
-
let start = -1;
|
|
5376
|
-
let inString = false;
|
|
5377
|
-
let escaped = false;
|
|
5378
|
-
for (let i = 0;i < text.length; i += 1) {
|
|
5379
|
-
const ch = text[i];
|
|
5380
|
-
if (inString) {
|
|
5381
|
-
if (escaped)
|
|
5382
|
-
escaped = false;
|
|
5383
|
-
else if (ch === "\\")
|
|
5384
|
-
escaped = true;
|
|
5385
|
-
else if (ch === '"')
|
|
5386
|
-
inString = false;
|
|
5387
|
-
continue;
|
|
5388
|
-
}
|
|
5389
|
-
if (ch === '"') {
|
|
5390
|
-
inString = true;
|
|
5391
|
-
} else if (ch === "{") {
|
|
5392
|
-
if (depth === 0)
|
|
5393
|
-
start = i;
|
|
5394
|
-
depth += 1;
|
|
5395
|
-
} else if (ch === "}") {
|
|
5396
|
-
if (depth > 0) {
|
|
5397
|
-
depth -= 1;
|
|
5398
|
-
if (depth === 0 && start !== -1)
|
|
5399
|
-
return text.slice(start, i + 1);
|
|
5400
|
-
}
|
|
5401
|
-
}
|
|
5402
|
-
}
|
|
5403
|
-
return;
|
|
5404
|
-
}
|
|
5405
|
-
function parseJsonOutput(stdout, label) {
|
|
5406
|
-
const raw = stdout || "{}";
|
|
5407
|
-
const candidates = [raw.trim()];
|
|
5408
|
-
const scanned = extractFirstJsonObject(raw);
|
|
5409
|
-
if (scanned && scanned !== raw.trim())
|
|
5410
|
-
candidates.push(scanned);
|
|
5411
|
-
let lastError;
|
|
5412
|
-
for (const candidate of candidates) {
|
|
5413
|
-
try {
|
|
5414
|
-
const value = JSON.parse(candidate);
|
|
5415
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
5416
|
-
throw new Error("not an object");
|
|
5417
|
-
return value;
|
|
5418
|
-
} catch (error) {
|
|
5419
|
-
lastError = error;
|
|
5420
|
-
}
|
|
5421
|
-
}
|
|
5422
|
-
throw new Error(`${label} did not return JSON${lastError instanceof Error ? `: ${lastError.message}` : ""}`);
|
|
5423
|
-
}
|
|
5424
|
-
function recordField(value, key) {
|
|
5425
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
5426
|
-
return;
|
|
5427
|
-
const field = value[key];
|
|
5428
|
-
return field && typeof field === "object" && !Array.isArray(field) ? field : undefined;
|
|
5429
|
-
}
|
|
5430
|
-
function stringField(value, key) {
|
|
5431
|
-
const field = value?.[key];
|
|
5432
|
-
return typeof field === "string" ? field : undefined;
|
|
5433
|
-
}
|
|
5434
|
-
function numberField(value, key) {
|
|
5435
|
-
const field = value?.[key];
|
|
5436
|
-
return typeof field === "number" && Number.isFinite(field) ? field : undefined;
|
|
5437
|
-
}
|
|
5438
|
-
function codewithAgentStatus(readJson) {
|
|
5439
|
-
return stringField(recordField(readJson, "agent"), "status") ?? stringField(recordField(readJson, "statusSnapshot"), "status");
|
|
5440
|
-
}
|
|
5441
|
-
function codewithAgentLastEventSeq(readJson) {
|
|
5442
|
-
const agentSeq = numberField(recordField(readJson, "agent"), "lastEventSeq");
|
|
5443
|
-
const snapshotSeq = numberField(recordField(readJson, "statusSnapshot"), "lastEventSeq");
|
|
5444
|
-
if (agentSeq !== undefined && snapshotSeq !== undefined)
|
|
5445
|
-
return Math.max(agentSeq, snapshotSeq);
|
|
5446
|
-
return agentSeq ?? snapshotSeq;
|
|
5447
|
-
}
|
|
5448
|
-
function codewithAgentEvidence(startJson, readJson, logsJson) {
|
|
5449
|
-
const agent = recordField(readJson, "agent") ?? recordField(startJson, "agent");
|
|
5450
|
-
const statusSnapshot = recordField(readJson, "statusSnapshot");
|
|
5451
|
-
const events = Array.isArray(logsJson?.data) ? logsJson.data.filter((event) => Boolean(event) && typeof event === "object" && !Array.isArray(event)).map((event) => ({
|
|
5452
|
-
seq: numberField(event, "seq"),
|
|
5453
|
-
eventType: stringField(event, "eventType"),
|
|
5454
|
-
createdAt: numberField(event, "createdAt")
|
|
5455
|
-
})) : undefined;
|
|
5456
|
-
return JSON.stringify({
|
|
5457
|
-
codewithAgent: {
|
|
5458
|
-
agentId: stringField(agent, "agentId"),
|
|
5459
|
-
status: stringField(agent, "status"),
|
|
5460
|
-
desiredState: stringField(agent, "desiredState"),
|
|
5461
|
-
statusReason: stringField(agent, "statusReason"),
|
|
5462
|
-
threadId: stringField(agent, "threadId"),
|
|
5463
|
-
rolloutPath: stringField(agent, "rolloutPath"),
|
|
5464
|
-
pid: numberField(agent, "pid"),
|
|
5465
|
-
exitCode: numberField(agent, "exitCode"),
|
|
5466
|
-
created: typeof startJson.created === "boolean" ? startJson.created : undefined
|
|
5467
|
-
},
|
|
5468
|
-
statusSnapshot: statusSnapshot ? {
|
|
5469
|
-
seq: numberField(statusSnapshot, "seq"),
|
|
5470
|
-
status: stringField(statusSnapshot, "status"),
|
|
5471
|
-
summary: stringField(statusSnapshot, "summary"),
|
|
5472
|
-
pendingInteractionCount: numberField(statusSnapshot, "pendingInteractionCount"),
|
|
5473
|
-
lastEventSeq: numberField(statusSnapshot, "lastEventSeq")
|
|
5474
|
-
} : undefined,
|
|
5475
|
-
events
|
|
5476
|
-
}, null, 2);
|
|
5477
|
-
}
|
|
5478
|
-
function codewithAgentProgress(readJson) {
|
|
5479
|
-
const agent = recordField(readJson, "agent");
|
|
5480
|
-
const statusSnapshot = recordField(readJson, "statusSnapshot");
|
|
5481
|
-
return {
|
|
5482
|
-
provider: "codewith",
|
|
5483
|
-
agentId: stringField(agent, "agentId"),
|
|
5484
|
-
status: stringField(agent, "status") ?? stringField(statusSnapshot, "status"),
|
|
5485
|
-
summary: stringField(statusSnapshot, "summary"),
|
|
5486
|
-
statusReason: stringField(agent, "statusReason"),
|
|
5487
|
-
threadId: stringField(agent, "threadId"),
|
|
5488
|
-
rolloutPath: stringField(agent, "rolloutPath"),
|
|
5489
|
-
pid: numberField(agent, "pid"),
|
|
5490
|
-
lastEventSeq: codewithAgentLastEventSeq(readJson)
|
|
5491
|
-
};
|
|
5492
|
-
}
|
|
5493
|
-
function sleep(ms) {
|
|
5494
|
-
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
5495
|
-
}
|
|
5496
|
-
async function executeCodewithDurableAgent(spec, metadata, opts, env, startedAt) {
|
|
5497
|
-
const target = spec.codewithDurableAgent?.target;
|
|
5498
|
-
if (!target)
|
|
5499
|
-
throw new Error("internal error: missing codewith durable target");
|
|
5500
|
-
const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
5501
|
-
const idempotencyKey = codewithAgentIdempotencyKey(metadata);
|
|
5502
|
-
const start = await spawnCapture(spec.command, codewithAgentStartArgs(target, idempotencyKey), {
|
|
5503
|
-
cwd: spec.cwd,
|
|
5504
|
-
env,
|
|
5505
|
-
timeoutMs: 30000,
|
|
5506
|
-
maxOutputBytes
|
|
5507
|
-
});
|
|
5508
|
-
const stderr = new BoundedOutputBuffer(maxOutputBytes);
|
|
5509
|
-
stderr.append(start.stderr);
|
|
5510
|
-
if (start.error || (start.status ?? 1) !== 0) {
|
|
5511
|
-
return failureResult(startedAt, start.error ?? `codewith agent start exited with code ${start.status ?? "unknown"}`, {
|
|
5512
|
-
exitCode: start.status ?? undefined,
|
|
5513
|
-
stdout: start.stdout,
|
|
5514
|
-
stderr: stderr.value()
|
|
5515
|
-
});
|
|
5516
|
-
}
|
|
5517
|
-
let startJson;
|
|
5518
|
-
try {
|
|
5519
|
-
startJson = parseJsonOutput(start.stdout || "{}", "codewith agent start");
|
|
5520
|
-
} catch (error) {
|
|
5521
|
-
return failureResult(startedAt, error instanceof Error ? error.message : String(error), { stdout: start.stdout, stderr: stderr.value() });
|
|
5522
|
-
}
|
|
5523
|
-
const agentId = stringField(recordField(startJson, "agent"), "agentId");
|
|
5524
|
-
if (!agentId) {
|
|
5525
|
-
return failureResult(startedAt, "codewith agent start did not return agent.agentId", { stdout: start.stdout, stderr: stderr.value() });
|
|
5526
|
-
}
|
|
5527
|
-
opts.onAgentProgress?.(codewithAgentProgress(startJson));
|
|
5528
|
-
const stopAgent = async () => {
|
|
5529
|
-
await spawnCapture(spec.command, codewithAgentControlArgs(target, "stop", agentId), {
|
|
5530
|
-
cwd: spec.cwd,
|
|
5531
|
-
env,
|
|
5532
|
-
timeoutMs: 15000,
|
|
5533
|
-
maxOutputBytes
|
|
5534
|
-
});
|
|
5535
|
-
};
|
|
5536
|
-
const pollMs = Math.max(100, Number(opts.env?.LOOPS_CODEWITH_AGENT_POLL_MS ?? process.env.LOOPS_CODEWITH_AGENT_POLL_MS ?? 2000) || 2000);
|
|
5537
|
-
let lastReadJson = startJson;
|
|
5538
|
-
let lastLogsJson;
|
|
5539
|
-
let lastFingerprint;
|
|
5540
|
-
let lastProgressAt = Date.now();
|
|
5541
|
-
const evidence = () => boundedText(codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes);
|
|
5542
|
-
while (true) {
|
|
5543
|
-
if (opts.signal?.aborted) {
|
|
5544
|
-
await stopAgent();
|
|
5545
|
-
return failureResult(startedAt, "cancelled", { stdout: evidence(), stderr: stderr.value() });
|
|
5546
|
-
}
|
|
5547
|
-
const read = await spawnCapture(spec.command, codewithAgentControlArgs(target, "read", agentId), {
|
|
5548
|
-
cwd: spec.cwd,
|
|
5549
|
-
env,
|
|
5550
|
-
timeoutMs: 30000,
|
|
5551
|
-
maxOutputBytes
|
|
5552
|
-
});
|
|
5553
|
-
stderr.append(read.stderr);
|
|
5554
|
-
if (read.error || (read.status ?? 1) !== 0) {
|
|
5555
|
-
return failureResult(startedAt, read.error ?? `codewith agent read exited with code ${read.status ?? "unknown"}`, {
|
|
5556
|
-
exitCode: read.status ?? undefined,
|
|
5557
|
-
stdout: evidence(),
|
|
5558
|
-
stderr: stderr.value()
|
|
5559
|
-
});
|
|
5560
|
-
}
|
|
5561
|
-
try {
|
|
5562
|
-
lastReadJson = parseJsonOutput(read.stdout || "{}", "codewith agent read");
|
|
5563
|
-
} catch (error) {
|
|
5564
|
-
return failureResult(startedAt, error instanceof Error ? error.message : String(error), {
|
|
5565
|
-
stdout: boundedText(read.stdout, maxOutputBytes),
|
|
5566
|
-
stderr: stderr.value()
|
|
5567
|
-
});
|
|
5568
|
-
}
|
|
5569
|
-
const status = codewithAgentStatus(lastReadJson);
|
|
5570
|
-
const fingerprint = JSON.stringify({
|
|
5571
|
-
status,
|
|
5572
|
-
agentLastEventSeq: numberField(recordField(lastReadJson, "agent"), "lastEventSeq"),
|
|
5573
|
-
snapshot: recordField(lastReadJson, "statusSnapshot") ?? null
|
|
5574
|
-
});
|
|
5575
|
-
if (fingerprint !== lastFingerprint) {
|
|
5576
|
-
lastFingerprint = fingerprint;
|
|
5577
|
-
lastProgressAt = Date.now();
|
|
5578
|
-
opts.onAgentProgress?.(codewithAgentProgress(lastReadJson));
|
|
5579
|
-
}
|
|
5580
|
-
if (status === "completed" || status === "failed" || status === "cancelled") {
|
|
5581
|
-
const logs = await spawnCapture(spec.command, codewithAgentControlArgs(target, "logs", agentId), {
|
|
5582
|
-
cwd: spec.cwd,
|
|
5583
|
-
env,
|
|
5584
|
-
timeoutMs: 30000,
|
|
5585
|
-
maxOutputBytes
|
|
5586
|
-
});
|
|
5587
|
-
if (!logs.error && (logs.status ?? 1) === 0) {
|
|
5588
|
-
try {
|
|
5589
|
-
lastLogsJson = parseJsonOutput(logs.stdout || "{}", "codewith agent logs");
|
|
5590
|
-
} catch {
|
|
5591
|
-
lastLogsJson = undefined;
|
|
5592
|
-
}
|
|
5593
|
-
} else {
|
|
5594
|
-
stderr.append(logs.stderr || logs.error || "");
|
|
5595
|
-
}
|
|
5596
|
-
if (status === "completed") {
|
|
5597
|
-
return successResult(startedAt, { exitCode: 0, stdout: evidence(), stderr: stderr.value() });
|
|
5598
|
-
}
|
|
5599
|
-
return failureResult(startedAt, stringField(recordField(lastReadJson, "agent"), "statusReason") ?? `codewith agent ${status}`, { exitCode: 1, stdout: evidence(), stderr: stderr.value() });
|
|
5600
|
-
}
|
|
5601
|
-
if (typeof spec.timeoutMs === "number" && new Date(nowIso()).getTime() - new Date(startedAt).getTime() >= spec.timeoutMs) {
|
|
5602
|
-
await stopAgent();
|
|
5603
|
-
return timeoutResult(startedAt, `timed out after ${spec.timeoutMs}ms`, { stdout: evidence(), stderr: stderr.value() });
|
|
5604
|
-
}
|
|
5605
|
-
if (typeof spec.idleTimeoutMs === "number" && Date.now() - lastProgressAt >= spec.idleTimeoutMs) {
|
|
5606
|
-
await stopAgent();
|
|
5607
|
-
return timeoutResult(startedAt, `idle timed out after ${spec.idleTimeoutMs}ms without agent progress`, {
|
|
5608
|
-
stdout: evidence(),
|
|
5609
|
-
stderr: stderr.value()
|
|
5610
|
-
});
|
|
5611
|
-
}
|
|
5612
|
-
await sleep(pollMs);
|
|
5613
|
-
}
|
|
5614
|
-
}
|
|
5615
5333
|
function resolvedDirEquals(left, right) {
|
|
5616
5334
|
try {
|
|
5617
5335
|
return realpathSync(left) === realpathSync(right);
|
|
@@ -5845,9 +5563,6 @@ function preflightTarget(target, metadata = {}, opts = {}) {
|
|
|
5845
5563
|
async function executeTarget(target, metadata = {}, opts = {}) {
|
|
5846
5564
|
let spec = commandSpec(target, opts);
|
|
5847
5565
|
const machine = resolvedMachine(opts);
|
|
5848
|
-
if (machine && !machine.local && spec.codewithDurableAgent) {
|
|
5849
|
-
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");
|
|
5850
|
-
}
|
|
5851
5566
|
if (machine && !machine.local) {
|
|
5852
5567
|
const remoteFallbackSpec = spec.worktree?.enabled && spec.worktree.mode === "auto" ? worktreeFallbackSpec(target, opts, spec.worktree.originalCwd) : undefined;
|
|
5853
5568
|
return executeRemoteSpec(spec, machine, metadata, opts, remoteFallbackSpec);
|
|
@@ -5878,9 +5593,6 @@ async function executeTarget(target, metadata = {}, opts = {}) {
|
|
|
5878
5593
|
if (worktreeEntry?.fallbackCwd) {
|
|
5879
5594
|
spec = worktreeFallbackSpec(target, opts, worktreeEntry.fallbackCwd) ?? spec;
|
|
5880
5595
|
}
|
|
5881
|
-
if (spec.codewithDurableAgent) {
|
|
5882
|
-
return executeCodewithDurableAgent(spec, metadata, opts, env, startedAt);
|
|
5883
|
-
}
|
|
5884
5596
|
const child = spawn2(spec.command, spec.args, {
|
|
5885
5597
|
cwd: spec.cwd,
|
|
5886
5598
|
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
|
|
9
|
+
- `codewith exec`
|
|
10
10
|
- `aicopilot run`
|
|
11
11
|
- `opencode run`
|
|
12
12
|
- `codex exec`
|
|
@@ -991,13 +991,13 @@ On Linux this writes a user systemd service. On macOS it writes a LaunchAgent pl
|
|
|
991
991
|
The adapters intentionally use provider command surfaces instead of pretending every agent has one SDK:
|
|
992
992
|
|
|
993
993
|
- Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
|
|
994
|
-
- Codewith
|
|
994
|
+
- Codewith runs non-interactive `codewith --ask-for-approval never exec --json` sessions by default. exec starts a fresh session per invocation, avoiding the multi-megabyte rollout history that `codewith agent start` reloaded every turn (which drove `context_length_exceeded` silent no-ops), and it keeps network egress for gh/git — the `workspace-write` sandbox opts back into `sandbox_workspace_write.network_access`. Codewith exec is remote-capable like codex. OpenLoops rejects Codewith `extraArgs` that try to force `exec`, `--ephemeral`, `--json`, or other exec launch flags that the adapter already manages.
|
|
995
995
|
- AI Copilot and OpenCode use `run --format json --pure`. OpenCode requires an explicit provider/model id because ambient OpenCode config is machine-specific.
|
|
996
996
|
- 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.
|
|
997
997
|
- Codex uses `codex --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
|
|
998
|
-
- Agent prompts are sent through child stdin instead of argv where the provider supports stdin
|
|
998
|
+
- Agent prompts are sent through child stdin instead of argv where the provider supports stdin, including Codewith `exec` (which reads instructions from stdin when no positional prompt is given), so the prompt never lands on argv.
|
|
999
999
|
- 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.
|
|
1000
|
-
- `--auth-profile` and step `authProfile` are provider-native auth selectors. They currently apply to Codewith and are passed to Codewith as `--auth-profile <name>`
|
|
1000
|
+
- `--auth-profile` and step `authProfile` are provider-native auth selectors. They currently apply to Codewith and are passed to Codewith as `--auth-profile <name>` on the `exec` invocation; they do not call OpenAccounts.
|
|
1001
1001
|
- `--sandbox` maps to provider-native sandbox flags. Codewith/Codex accept `read-only`, `workspace-write`, or `danger-full-access`; Cursor accepts `enabled` or `disabled`.
|
|
1002
1002
|
- `--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`.
|
|
1003
1003
|
- `--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`.
|