@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/runner/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// package.json
|
|
4
4
|
var package_default = {
|
|
5
5
|
name: "@hasna/loops",
|
|
6
|
-
version: "0.4.
|
|
6
|
+
version: "0.4.9",
|
|
7
7
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
8
8
|
type: "module",
|
|
9
9
|
main: "dist/index.js",
|
|
@@ -365,7 +365,7 @@ function scrubSecretsDeep(value) {
|
|
|
365
365
|
}
|
|
366
366
|
|
|
367
367
|
// src/lib/agent-adapter.ts
|
|
368
|
-
var
|
|
368
|
+
var UNSAFE_CODEWITH_EXEC_EXTRA_ARGS = new Set([
|
|
369
369
|
"e",
|
|
370
370
|
"exec",
|
|
371
371
|
"agent",
|
|
@@ -414,12 +414,12 @@ function validateAgentOptions(target, label, capabilities) {
|
|
|
414
414
|
throw new Error(`${label}.agent is not supported for provider codex`);
|
|
415
415
|
}
|
|
416
416
|
if (provider === "codewith" && target.agent !== undefined) {
|
|
417
|
-
throw new Error(`${label}.agent is not supported for provider codewith
|
|
417
|
+
throw new Error(`${label}.agent is not supported for provider codewith`);
|
|
418
418
|
}
|
|
419
419
|
if (provider === "codewith") {
|
|
420
|
-
const unsafe = target.extraArgs?.find((arg) =>
|
|
420
|
+
const unsafe = target.extraArgs?.find((arg) => UNSAFE_CODEWITH_EXEC_EXTRA_ARGS.has(arg));
|
|
421
421
|
if (unsafe) {
|
|
422
|
-
throw new Error(`${label}.extraArgs cannot include ${unsafe}; codewith
|
|
422
|
+
throw new Error(`${label}.extraArgs cannot include ${unsafe}; codewith exec launch flags are managed by the adapter`);
|
|
423
423
|
}
|
|
424
424
|
}
|
|
425
425
|
if (target.addDirs?.length && !["codewith", "codex"].includes(provider)) {
|
|
@@ -502,7 +502,10 @@ function buildAgentInvocation(target) {
|
|
|
502
502
|
args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : []);
|
|
503
503
|
if (target.variant)
|
|
504
504
|
args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
|
|
505
|
-
|
|
505
|
+
const sandbox = codewithLikeSandbox(target);
|
|
506
|
+
if (sandbox === "workspace-write")
|
|
507
|
+
args.push("-c", "sandbox_workspace_write.network_access=true");
|
|
508
|
+
args.push("--ask-for-approval", "never", "exec", "--json", "--ephemeral", "--sandbox", sandbox, "--skip-git-repo-check");
|
|
506
509
|
if (target.cwd)
|
|
507
510
|
args.push("--cd", target.cwd);
|
|
508
511
|
for (const dir of target.addDirs ?? [])
|
|
@@ -510,11 +513,7 @@ function buildAgentInvocation(target) {
|
|
|
510
513
|
if (target.model)
|
|
511
514
|
args.push("--model", target.model);
|
|
512
515
|
args.push(...target.extraArgs ?? []);
|
|
513
|
-
|
|
514
|
-
if (target.cwd)
|
|
515
|
-
args.push("--cwd", target.cwd);
|
|
516
|
-
args.push(target.prompt);
|
|
517
|
-
return { command: "codewith", args };
|
|
516
|
+
return { command: "codewith", args, stdin: target.prompt };
|
|
518
517
|
}
|
|
519
518
|
case "codex": {
|
|
520
519
|
if (target.variant)
|
|
@@ -567,7 +566,7 @@ function adapterFor(provider, capabilities) {
|
|
|
567
566
|
var PROVIDER_ADAPTERS = {
|
|
568
567
|
claude: adapterFor("claude", { sandbox: [], durable: false, remote: true, promptChannel: "stdin" }),
|
|
569
568
|
cursor: adapterFor("cursor", { sandbox: CURSOR_SANDBOXES, durable: false, remote: true, promptChannel: "stdin" }),
|
|
570
|
-
codewith: adapterFor("codewith", { sandbox: CODEX_LIKE_SANDBOXES, durable:
|
|
569
|
+
codewith: adapterFor("codewith", { sandbox: CODEX_LIKE_SANDBOXES, durable: false, remote: true, promptChannel: "stdin" }),
|
|
571
570
|
codex: adapterFor("codex", { sandbox: CODEX_LIKE_SANDBOXES, durable: false, remote: true, promptChannel: "stdin" }),
|
|
572
571
|
aicopilot: adapterFor("aicopilot", { sandbox: [], durable: false, remote: true, promptChannel: "stdin" }),
|
|
573
572
|
opencode: adapterFor("opencode", { sandbox: [], durable: false, remote: true, promptChannel: "stdin" })
|
|
@@ -1041,11 +1040,6 @@ var TRANSPORT_ENV_KEYS = new Set([
|
|
|
1041
1040
|
"USER",
|
|
1042
1041
|
"XDG_RUNTIME_DIR"
|
|
1043
1042
|
]);
|
|
1044
|
-
function boundedText(text, maxBytes) {
|
|
1045
|
-
const buffer = new BoundedOutputBuffer(maxBytes);
|
|
1046
|
-
buffer.append(text);
|
|
1047
|
-
return buffer.value();
|
|
1048
|
-
}
|
|
1049
1043
|
function buildResult(status, startedAt, fields = {}) {
|
|
1050
1044
|
const finishedAt = fields.finishedAt ?? nowIso();
|
|
1051
1045
|
return {
|
|
@@ -1109,21 +1103,6 @@ function metadataEnv(metadata) {
|
|
|
1109
1103
|
env.LOOPS_GOAL_NODE_KEY = metadata.goalNodeKey;
|
|
1110
1104
|
return env;
|
|
1111
1105
|
}
|
|
1112
|
-
function codewithAgentIdempotencyKey(metadata) {
|
|
1113
|
-
const parts = [
|
|
1114
|
-
"openloops",
|
|
1115
|
-
metadata.workflowRunId ? `workflow-run:${metadata.workflowRunId}` : undefined,
|
|
1116
|
-
metadata.workflowStepId ? `step:${metadata.workflowStepId}` : undefined,
|
|
1117
|
-
metadata.runId ? `loop-run:${metadata.runId}` : undefined,
|
|
1118
|
-
metadata.loopId ? `loop:${metadata.loopId}` : undefined,
|
|
1119
|
-
metadata.scheduledFor ? `scheduled:${metadata.scheduledFor}` : undefined,
|
|
1120
|
-
metadata.goalId ? `goal:${metadata.goalId}` : undefined,
|
|
1121
|
-
metadata.goalNodeKey ? `node:${metadata.goalNodeKey}` : undefined
|
|
1122
|
-
].filter(Boolean);
|
|
1123
|
-
if (parts.length > 1)
|
|
1124
|
-
return parts.join(":");
|
|
1125
|
-
return `openloops:adhoc:${randomBytes2(16).toString("hex")}`;
|
|
1126
|
-
}
|
|
1127
1106
|
function allowlistEnv(allowlist) {
|
|
1128
1107
|
const env = {};
|
|
1129
1108
|
if (allowlist?.tools?.length)
|
|
@@ -1178,8 +1157,7 @@ function commandSpec(target, opts) {
|
|
|
1178
1157
|
preflightAnyOf: invocation.preflightAnyOf,
|
|
1179
1158
|
stdin: invocation.stdin,
|
|
1180
1159
|
allowlist: agentTarget.allowlist,
|
|
1181
|
-
worktree: agentTarget.worktree
|
|
1182
|
-
codewithDurableAgent: adapter.capabilities.durable ? { target: agentTarget } : undefined
|
|
1160
|
+
worktree: agentTarget.worktree
|
|
1183
1161
|
};
|
|
1184
1162
|
}
|
|
1185
1163
|
function composeExecutionEnv(spec, metadata, opts, accountEnv) {
|
|
@@ -1389,266 +1367,6 @@ async function preflightNativeAuthProfile(spec, env) {
|
|
|
1389
1367
|
const result = await spawnCapture(spec.command, ["profile", "list"], { env, timeoutMs: 15000 });
|
|
1390
1368
|
assertCodewithProfileListed(spec.nativeAuthProfile.profile, result);
|
|
1391
1369
|
}
|
|
1392
|
-
function codewithAgentStartArgs(target, idempotencyKey) {
|
|
1393
|
-
const args = providerAdapter(target.provider).buildInvocation(target).args;
|
|
1394
|
-
const startIndex = args.findIndex((arg, index) => arg === "start" && args[index - 1] === "agent");
|
|
1395
|
-
if (startIndex === -1)
|
|
1396
|
-
throw new Error("internal error: codewith durable agent args missing agent start");
|
|
1397
|
-
args.splice(startIndex + 1, 0, "--idempotency-key", idempotencyKey);
|
|
1398
|
-
return args;
|
|
1399
|
-
}
|
|
1400
|
-
function codewithAgentControlArgs(target, command, agentId) {
|
|
1401
|
-
return [
|
|
1402
|
-
...target.authProfile ? ["--auth-profile", target.authProfile] : [],
|
|
1403
|
-
"agent",
|
|
1404
|
-
command,
|
|
1405
|
-
...command === "logs" ? ["--limit", "20"] : [],
|
|
1406
|
-
"--json",
|
|
1407
|
-
agentId
|
|
1408
|
-
];
|
|
1409
|
-
}
|
|
1410
|
-
function extractFirstJsonObject(text) {
|
|
1411
|
-
let depth = 0;
|
|
1412
|
-
let start = -1;
|
|
1413
|
-
let inString = false;
|
|
1414
|
-
let escaped = false;
|
|
1415
|
-
for (let i = 0;i < text.length; i += 1) {
|
|
1416
|
-
const ch = text[i];
|
|
1417
|
-
if (inString) {
|
|
1418
|
-
if (escaped)
|
|
1419
|
-
escaped = false;
|
|
1420
|
-
else if (ch === "\\")
|
|
1421
|
-
escaped = true;
|
|
1422
|
-
else if (ch === '"')
|
|
1423
|
-
inString = false;
|
|
1424
|
-
continue;
|
|
1425
|
-
}
|
|
1426
|
-
if (ch === '"') {
|
|
1427
|
-
inString = true;
|
|
1428
|
-
} else if (ch === "{") {
|
|
1429
|
-
if (depth === 0)
|
|
1430
|
-
start = i;
|
|
1431
|
-
depth += 1;
|
|
1432
|
-
} else if (ch === "}") {
|
|
1433
|
-
if (depth > 0) {
|
|
1434
|
-
depth -= 1;
|
|
1435
|
-
if (depth === 0 && start !== -1)
|
|
1436
|
-
return text.slice(start, i + 1);
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
}
|
|
1440
|
-
return;
|
|
1441
|
-
}
|
|
1442
|
-
function parseJsonOutput(stdout, label) {
|
|
1443
|
-
const raw = stdout || "{}";
|
|
1444
|
-
const candidates = [raw.trim()];
|
|
1445
|
-
const scanned = extractFirstJsonObject(raw);
|
|
1446
|
-
if (scanned && scanned !== raw.trim())
|
|
1447
|
-
candidates.push(scanned);
|
|
1448
|
-
let lastError;
|
|
1449
|
-
for (const candidate of candidates) {
|
|
1450
|
-
try {
|
|
1451
|
-
const value = JSON.parse(candidate);
|
|
1452
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1453
|
-
throw new Error("not an object");
|
|
1454
|
-
return value;
|
|
1455
|
-
} catch (error) {
|
|
1456
|
-
lastError = error;
|
|
1457
|
-
}
|
|
1458
|
-
}
|
|
1459
|
-
throw new Error(`${label} did not return JSON${lastError instanceof Error ? `: ${lastError.message}` : ""}`);
|
|
1460
|
-
}
|
|
1461
|
-
function recordField(value, key) {
|
|
1462
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
1463
|
-
return;
|
|
1464
|
-
const field = value[key];
|
|
1465
|
-
return field && typeof field === "object" && !Array.isArray(field) ? field : undefined;
|
|
1466
|
-
}
|
|
1467
|
-
function stringField(value, key) {
|
|
1468
|
-
const field = value?.[key];
|
|
1469
|
-
return typeof field === "string" ? field : undefined;
|
|
1470
|
-
}
|
|
1471
|
-
function numberField(value, key) {
|
|
1472
|
-
const field = value?.[key];
|
|
1473
|
-
return typeof field === "number" && Number.isFinite(field) ? field : undefined;
|
|
1474
|
-
}
|
|
1475
|
-
function codewithAgentStatus(readJson) {
|
|
1476
|
-
return stringField(recordField(readJson, "agent"), "status") ?? stringField(recordField(readJson, "statusSnapshot"), "status");
|
|
1477
|
-
}
|
|
1478
|
-
function codewithAgentLastEventSeq(readJson) {
|
|
1479
|
-
const agentSeq = numberField(recordField(readJson, "agent"), "lastEventSeq");
|
|
1480
|
-
const snapshotSeq = numberField(recordField(readJson, "statusSnapshot"), "lastEventSeq");
|
|
1481
|
-
if (agentSeq !== undefined && snapshotSeq !== undefined)
|
|
1482
|
-
return Math.max(agentSeq, snapshotSeq);
|
|
1483
|
-
return agentSeq ?? snapshotSeq;
|
|
1484
|
-
}
|
|
1485
|
-
function codewithAgentEvidence(startJson, readJson, logsJson) {
|
|
1486
|
-
const agent = recordField(readJson, "agent") ?? recordField(startJson, "agent");
|
|
1487
|
-
const statusSnapshot = recordField(readJson, "statusSnapshot");
|
|
1488
|
-
const events = Array.isArray(logsJson?.data) ? logsJson.data.filter((event) => Boolean(event) && typeof event === "object" && !Array.isArray(event)).map((event) => ({
|
|
1489
|
-
seq: numberField(event, "seq"),
|
|
1490
|
-
eventType: stringField(event, "eventType"),
|
|
1491
|
-
createdAt: numberField(event, "createdAt")
|
|
1492
|
-
})) : undefined;
|
|
1493
|
-
return JSON.stringify({
|
|
1494
|
-
codewithAgent: {
|
|
1495
|
-
agentId: stringField(agent, "agentId"),
|
|
1496
|
-
status: stringField(agent, "status"),
|
|
1497
|
-
desiredState: stringField(agent, "desiredState"),
|
|
1498
|
-
statusReason: stringField(agent, "statusReason"),
|
|
1499
|
-
threadId: stringField(agent, "threadId"),
|
|
1500
|
-
rolloutPath: stringField(agent, "rolloutPath"),
|
|
1501
|
-
pid: numberField(agent, "pid"),
|
|
1502
|
-
exitCode: numberField(agent, "exitCode"),
|
|
1503
|
-
created: typeof startJson.created === "boolean" ? startJson.created : undefined
|
|
1504
|
-
},
|
|
1505
|
-
statusSnapshot: statusSnapshot ? {
|
|
1506
|
-
seq: numberField(statusSnapshot, "seq"),
|
|
1507
|
-
status: stringField(statusSnapshot, "status"),
|
|
1508
|
-
summary: stringField(statusSnapshot, "summary"),
|
|
1509
|
-
pendingInteractionCount: numberField(statusSnapshot, "pendingInteractionCount"),
|
|
1510
|
-
lastEventSeq: numberField(statusSnapshot, "lastEventSeq")
|
|
1511
|
-
} : undefined,
|
|
1512
|
-
events
|
|
1513
|
-
}, null, 2);
|
|
1514
|
-
}
|
|
1515
|
-
function codewithAgentProgress(readJson) {
|
|
1516
|
-
const agent = recordField(readJson, "agent");
|
|
1517
|
-
const statusSnapshot = recordField(readJson, "statusSnapshot");
|
|
1518
|
-
return {
|
|
1519
|
-
provider: "codewith",
|
|
1520
|
-
agentId: stringField(agent, "agentId"),
|
|
1521
|
-
status: stringField(agent, "status") ?? stringField(statusSnapshot, "status"),
|
|
1522
|
-
summary: stringField(statusSnapshot, "summary"),
|
|
1523
|
-
statusReason: stringField(agent, "statusReason"),
|
|
1524
|
-
threadId: stringField(agent, "threadId"),
|
|
1525
|
-
rolloutPath: stringField(agent, "rolloutPath"),
|
|
1526
|
-
pid: numberField(agent, "pid"),
|
|
1527
|
-
lastEventSeq: codewithAgentLastEventSeq(readJson)
|
|
1528
|
-
};
|
|
1529
|
-
}
|
|
1530
|
-
function sleep(ms) {
|
|
1531
|
-
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
1532
|
-
}
|
|
1533
|
-
async function executeCodewithDurableAgent(spec, metadata, opts, env, startedAt) {
|
|
1534
|
-
const target = spec.codewithDurableAgent?.target;
|
|
1535
|
-
if (!target)
|
|
1536
|
-
throw new Error("internal error: missing codewith durable target");
|
|
1537
|
-
const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
1538
|
-
const idempotencyKey = codewithAgentIdempotencyKey(metadata);
|
|
1539
|
-
const start = await spawnCapture(spec.command, codewithAgentStartArgs(target, idempotencyKey), {
|
|
1540
|
-
cwd: spec.cwd,
|
|
1541
|
-
env,
|
|
1542
|
-
timeoutMs: 30000,
|
|
1543
|
-
maxOutputBytes
|
|
1544
|
-
});
|
|
1545
|
-
const stderr = new BoundedOutputBuffer(maxOutputBytes);
|
|
1546
|
-
stderr.append(start.stderr);
|
|
1547
|
-
if (start.error || (start.status ?? 1) !== 0) {
|
|
1548
|
-
return failureResult(startedAt, start.error ?? `codewith agent start exited with code ${start.status ?? "unknown"}`, {
|
|
1549
|
-
exitCode: start.status ?? undefined,
|
|
1550
|
-
stdout: start.stdout,
|
|
1551
|
-
stderr: stderr.value()
|
|
1552
|
-
});
|
|
1553
|
-
}
|
|
1554
|
-
let startJson;
|
|
1555
|
-
try {
|
|
1556
|
-
startJson = parseJsonOutput(start.stdout || "{}", "codewith agent start");
|
|
1557
|
-
} catch (error) {
|
|
1558
|
-
return failureResult(startedAt, error instanceof Error ? error.message : String(error), { stdout: start.stdout, stderr: stderr.value() });
|
|
1559
|
-
}
|
|
1560
|
-
const agentId = stringField(recordField(startJson, "agent"), "agentId");
|
|
1561
|
-
if (!agentId) {
|
|
1562
|
-
return failureResult(startedAt, "codewith agent start did not return agent.agentId", { stdout: start.stdout, stderr: stderr.value() });
|
|
1563
|
-
}
|
|
1564
|
-
opts.onAgentProgress?.(codewithAgentProgress(startJson));
|
|
1565
|
-
const stopAgent = async () => {
|
|
1566
|
-
await spawnCapture(spec.command, codewithAgentControlArgs(target, "stop", agentId), {
|
|
1567
|
-
cwd: spec.cwd,
|
|
1568
|
-
env,
|
|
1569
|
-
timeoutMs: 15000,
|
|
1570
|
-
maxOutputBytes
|
|
1571
|
-
});
|
|
1572
|
-
};
|
|
1573
|
-
const pollMs = Math.max(100, Number(opts.env?.LOOPS_CODEWITH_AGENT_POLL_MS ?? process.env.LOOPS_CODEWITH_AGENT_POLL_MS ?? 2000) || 2000);
|
|
1574
|
-
let lastReadJson = startJson;
|
|
1575
|
-
let lastLogsJson;
|
|
1576
|
-
let lastFingerprint;
|
|
1577
|
-
let lastProgressAt = Date.now();
|
|
1578
|
-
const evidence = () => boundedText(codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes);
|
|
1579
|
-
while (true) {
|
|
1580
|
-
if (opts.signal?.aborted) {
|
|
1581
|
-
await stopAgent();
|
|
1582
|
-
return failureResult(startedAt, "cancelled", { stdout: evidence(), stderr: stderr.value() });
|
|
1583
|
-
}
|
|
1584
|
-
const read = await spawnCapture(spec.command, codewithAgentControlArgs(target, "read", agentId), {
|
|
1585
|
-
cwd: spec.cwd,
|
|
1586
|
-
env,
|
|
1587
|
-
timeoutMs: 30000,
|
|
1588
|
-
maxOutputBytes
|
|
1589
|
-
});
|
|
1590
|
-
stderr.append(read.stderr);
|
|
1591
|
-
if (read.error || (read.status ?? 1) !== 0) {
|
|
1592
|
-
return failureResult(startedAt, read.error ?? `codewith agent read exited with code ${read.status ?? "unknown"}`, {
|
|
1593
|
-
exitCode: read.status ?? undefined,
|
|
1594
|
-
stdout: evidence(),
|
|
1595
|
-
stderr: stderr.value()
|
|
1596
|
-
});
|
|
1597
|
-
}
|
|
1598
|
-
try {
|
|
1599
|
-
lastReadJson = parseJsonOutput(read.stdout || "{}", "codewith agent read");
|
|
1600
|
-
} catch (error) {
|
|
1601
|
-
return failureResult(startedAt, error instanceof Error ? error.message : String(error), {
|
|
1602
|
-
stdout: boundedText(read.stdout, maxOutputBytes),
|
|
1603
|
-
stderr: stderr.value()
|
|
1604
|
-
});
|
|
1605
|
-
}
|
|
1606
|
-
const status = codewithAgentStatus(lastReadJson);
|
|
1607
|
-
const fingerprint = JSON.stringify({
|
|
1608
|
-
status,
|
|
1609
|
-
agentLastEventSeq: numberField(recordField(lastReadJson, "agent"), "lastEventSeq"),
|
|
1610
|
-
snapshot: recordField(lastReadJson, "statusSnapshot") ?? null
|
|
1611
|
-
});
|
|
1612
|
-
if (fingerprint !== lastFingerprint) {
|
|
1613
|
-
lastFingerprint = fingerprint;
|
|
1614
|
-
lastProgressAt = Date.now();
|
|
1615
|
-
opts.onAgentProgress?.(codewithAgentProgress(lastReadJson));
|
|
1616
|
-
}
|
|
1617
|
-
if (status === "completed" || status === "failed" || status === "cancelled") {
|
|
1618
|
-
const logs = await spawnCapture(spec.command, codewithAgentControlArgs(target, "logs", agentId), {
|
|
1619
|
-
cwd: spec.cwd,
|
|
1620
|
-
env,
|
|
1621
|
-
timeoutMs: 30000,
|
|
1622
|
-
maxOutputBytes
|
|
1623
|
-
});
|
|
1624
|
-
if (!logs.error && (logs.status ?? 1) === 0) {
|
|
1625
|
-
try {
|
|
1626
|
-
lastLogsJson = parseJsonOutput(logs.stdout || "{}", "codewith agent logs");
|
|
1627
|
-
} catch {
|
|
1628
|
-
lastLogsJson = undefined;
|
|
1629
|
-
}
|
|
1630
|
-
} else {
|
|
1631
|
-
stderr.append(logs.stderr || logs.error || "");
|
|
1632
|
-
}
|
|
1633
|
-
if (status === "completed") {
|
|
1634
|
-
return successResult(startedAt, { exitCode: 0, stdout: evidence(), stderr: stderr.value() });
|
|
1635
|
-
}
|
|
1636
|
-
return failureResult(startedAt, stringField(recordField(lastReadJson, "agent"), "statusReason") ?? `codewith agent ${status}`, { exitCode: 1, stdout: evidence(), stderr: stderr.value() });
|
|
1637
|
-
}
|
|
1638
|
-
if (typeof spec.timeoutMs === "number" && new Date(nowIso()).getTime() - new Date(startedAt).getTime() >= spec.timeoutMs) {
|
|
1639
|
-
await stopAgent();
|
|
1640
|
-
return timeoutResult(startedAt, `timed out after ${spec.timeoutMs}ms`, { stdout: evidence(), stderr: stderr.value() });
|
|
1641
|
-
}
|
|
1642
|
-
if (typeof spec.idleTimeoutMs === "number" && Date.now() - lastProgressAt >= spec.idleTimeoutMs) {
|
|
1643
|
-
await stopAgent();
|
|
1644
|
-
return timeoutResult(startedAt, `idle timed out after ${spec.idleTimeoutMs}ms without agent progress`, {
|
|
1645
|
-
stdout: evidence(),
|
|
1646
|
-
stderr: stderr.value()
|
|
1647
|
-
});
|
|
1648
|
-
}
|
|
1649
|
-
await sleep(pollMs);
|
|
1650
|
-
}
|
|
1651
|
-
}
|
|
1652
1370
|
function resolvedDirEquals(left, right) {
|
|
1653
1371
|
try {
|
|
1654
1372
|
return realpathSync(left) === realpathSync(right);
|
|
@@ -1882,9 +1600,6 @@ function preflightTarget(target, metadata = {}, opts = {}) {
|
|
|
1882
1600
|
async function executeTarget(target, metadata = {}, opts = {}) {
|
|
1883
1601
|
let spec = commandSpec(target, opts);
|
|
1884
1602
|
const machine = resolvedMachine(opts);
|
|
1885
|
-
if (machine && !machine.local && spec.codewithDurableAgent) {
|
|
1886
|
-
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");
|
|
1887
|
-
}
|
|
1888
1603
|
if (machine && !machine.local) {
|
|
1889
1604
|
const remoteFallbackSpec = spec.worktree?.enabled && spec.worktree.mode === "auto" ? worktreeFallbackSpec(target, opts, spec.worktree.originalCwd) : undefined;
|
|
1890
1605
|
return executeRemoteSpec(spec, machine, metadata, opts, remoteFallbackSpec);
|
|
@@ -1915,9 +1630,6 @@ async function executeTarget(target, metadata = {}, opts = {}) {
|
|
|
1915
1630
|
if (worktreeEntry?.fallbackCwd) {
|
|
1916
1631
|
spec = worktreeFallbackSpec(target, opts, worktreeEntry.fallbackCwd) ?? spec;
|
|
1917
1632
|
}
|
|
1918
|
-
if (spec.codewithDurableAgent) {
|
|
1919
|
-
return executeCodewithDurableAgent(spec, metadata, opts, env, startedAt);
|
|
1920
|
-
}
|
|
1921
1633
|
const child = spawn2(spec.command, spec.args, {
|
|
1922
1634
|
cwd: spec.cwd,
|
|
1923
1635
|
env,
|