@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/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",
|
|
@@ -5009,11 +5008,6 @@ var TRANSPORT_ENV_KEYS = new Set([
|
|
|
5009
5008
|
"USER",
|
|
5010
5009
|
"XDG_RUNTIME_DIR"
|
|
5011
5010
|
]);
|
|
5012
|
-
function boundedText(text, maxBytes) {
|
|
5013
|
-
const buffer = new BoundedOutputBuffer(maxBytes);
|
|
5014
|
-
buffer.append(text);
|
|
5015
|
-
return buffer.value();
|
|
5016
|
-
}
|
|
5017
5011
|
function buildResult(status, startedAt, fields = {}) {
|
|
5018
5012
|
const finishedAt = fields.finishedAt ?? nowIso();
|
|
5019
5013
|
return {
|
|
@@ -5077,21 +5071,6 @@ function metadataEnv(metadata) {
|
|
|
5077
5071
|
env.LOOPS_GOAL_NODE_KEY = metadata.goalNodeKey;
|
|
5078
5072
|
return env;
|
|
5079
5073
|
}
|
|
5080
|
-
function codewithAgentIdempotencyKey(metadata) {
|
|
5081
|
-
const parts = [
|
|
5082
|
-
"openloops",
|
|
5083
|
-
metadata.workflowRunId ? `workflow-run:${metadata.workflowRunId}` : undefined,
|
|
5084
|
-
metadata.workflowStepId ? `step:${metadata.workflowStepId}` : undefined,
|
|
5085
|
-
metadata.runId ? `loop-run:${metadata.runId}` : undefined,
|
|
5086
|
-
metadata.loopId ? `loop:${metadata.loopId}` : undefined,
|
|
5087
|
-
metadata.scheduledFor ? `scheduled:${metadata.scheduledFor}` : undefined,
|
|
5088
|
-
metadata.goalId ? `goal:${metadata.goalId}` : undefined,
|
|
5089
|
-
metadata.goalNodeKey ? `node:${metadata.goalNodeKey}` : undefined
|
|
5090
|
-
].filter(Boolean);
|
|
5091
|
-
if (parts.length > 1)
|
|
5092
|
-
return parts.join(":");
|
|
5093
|
-
return `openloops:adhoc:${randomBytes2(16).toString("hex")}`;
|
|
5094
|
-
}
|
|
5095
5074
|
function allowlistEnv(allowlist) {
|
|
5096
5075
|
const env = {};
|
|
5097
5076
|
if (allowlist?.tools?.length)
|
|
@@ -5146,8 +5125,7 @@ function commandSpec(target, opts) {
|
|
|
5146
5125
|
preflightAnyOf: invocation.preflightAnyOf,
|
|
5147
5126
|
stdin: invocation.stdin,
|
|
5148
5127
|
allowlist: agentTarget.allowlist,
|
|
5149
|
-
worktree: agentTarget.worktree
|
|
5150
|
-
codewithDurableAgent: adapter.capabilities.durable ? { target: agentTarget } : undefined
|
|
5128
|
+
worktree: agentTarget.worktree
|
|
5151
5129
|
};
|
|
5152
5130
|
}
|
|
5153
5131
|
function composeExecutionEnv(spec, metadata, opts, accountEnv) {
|
|
@@ -5357,266 +5335,6 @@ async function preflightNativeAuthProfile(spec, env) {
|
|
|
5357
5335
|
const result = await spawnCapture(spec.command, ["profile", "list"], { env, timeoutMs: 15000 });
|
|
5358
5336
|
assertCodewithProfileListed(spec.nativeAuthProfile.profile, result);
|
|
5359
5337
|
}
|
|
5360
|
-
function codewithAgentStartArgs(target, idempotencyKey) {
|
|
5361
|
-
const args = providerAdapter(target.provider).buildInvocation(target).args;
|
|
5362
|
-
const startIndex = args.findIndex((arg, index) => arg === "start" && args[index - 1] === "agent");
|
|
5363
|
-
if (startIndex === -1)
|
|
5364
|
-
throw new Error("internal error: codewith durable agent args missing agent start");
|
|
5365
|
-
args.splice(startIndex + 1, 0, "--idempotency-key", idempotencyKey);
|
|
5366
|
-
return args;
|
|
5367
|
-
}
|
|
5368
|
-
function codewithAgentControlArgs(target, command, agentId) {
|
|
5369
|
-
return [
|
|
5370
|
-
...target.authProfile ? ["--auth-profile", target.authProfile] : [],
|
|
5371
|
-
"agent",
|
|
5372
|
-
command,
|
|
5373
|
-
...command === "logs" ? ["--limit", "20"] : [],
|
|
5374
|
-
"--json",
|
|
5375
|
-
agentId
|
|
5376
|
-
];
|
|
5377
|
-
}
|
|
5378
|
-
function extractFirstJsonObject(text) {
|
|
5379
|
-
let depth = 0;
|
|
5380
|
-
let start = -1;
|
|
5381
|
-
let inString = false;
|
|
5382
|
-
let escaped = false;
|
|
5383
|
-
for (let i = 0;i < text.length; i += 1) {
|
|
5384
|
-
const ch = text[i];
|
|
5385
|
-
if (inString) {
|
|
5386
|
-
if (escaped)
|
|
5387
|
-
escaped = false;
|
|
5388
|
-
else if (ch === "\\")
|
|
5389
|
-
escaped = true;
|
|
5390
|
-
else if (ch === '"')
|
|
5391
|
-
inString = false;
|
|
5392
|
-
continue;
|
|
5393
|
-
}
|
|
5394
|
-
if (ch === '"') {
|
|
5395
|
-
inString = true;
|
|
5396
|
-
} else if (ch === "{") {
|
|
5397
|
-
if (depth === 0)
|
|
5398
|
-
start = i;
|
|
5399
|
-
depth += 1;
|
|
5400
|
-
} else if (ch === "}") {
|
|
5401
|
-
if (depth > 0) {
|
|
5402
|
-
depth -= 1;
|
|
5403
|
-
if (depth === 0 && start !== -1)
|
|
5404
|
-
return text.slice(start, i + 1);
|
|
5405
|
-
}
|
|
5406
|
-
}
|
|
5407
|
-
}
|
|
5408
|
-
return;
|
|
5409
|
-
}
|
|
5410
|
-
function parseJsonOutput(stdout, label) {
|
|
5411
|
-
const raw = stdout || "{}";
|
|
5412
|
-
const candidates = [raw.trim()];
|
|
5413
|
-
const scanned = extractFirstJsonObject(raw);
|
|
5414
|
-
if (scanned && scanned !== raw.trim())
|
|
5415
|
-
candidates.push(scanned);
|
|
5416
|
-
let lastError;
|
|
5417
|
-
for (const candidate of candidates) {
|
|
5418
|
-
try {
|
|
5419
|
-
const value = JSON.parse(candidate);
|
|
5420
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
5421
|
-
throw new Error("not an object");
|
|
5422
|
-
return value;
|
|
5423
|
-
} catch (error) {
|
|
5424
|
-
lastError = error;
|
|
5425
|
-
}
|
|
5426
|
-
}
|
|
5427
|
-
throw new Error(`${label} did not return JSON${lastError instanceof Error ? `: ${lastError.message}` : ""}`);
|
|
5428
|
-
}
|
|
5429
|
-
function recordField(value, key) {
|
|
5430
|
-
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
5431
|
-
return;
|
|
5432
|
-
const field = value[key];
|
|
5433
|
-
return field && typeof field === "object" && !Array.isArray(field) ? field : undefined;
|
|
5434
|
-
}
|
|
5435
|
-
function stringField(value, key) {
|
|
5436
|
-
const field = value?.[key];
|
|
5437
|
-
return typeof field === "string" ? field : undefined;
|
|
5438
|
-
}
|
|
5439
|
-
function numberField(value, key) {
|
|
5440
|
-
const field = value?.[key];
|
|
5441
|
-
return typeof field === "number" && Number.isFinite(field) ? field : undefined;
|
|
5442
|
-
}
|
|
5443
|
-
function codewithAgentStatus(readJson) {
|
|
5444
|
-
return stringField(recordField(readJson, "agent"), "status") ?? stringField(recordField(readJson, "statusSnapshot"), "status");
|
|
5445
|
-
}
|
|
5446
|
-
function codewithAgentLastEventSeq(readJson) {
|
|
5447
|
-
const agentSeq = numberField(recordField(readJson, "agent"), "lastEventSeq");
|
|
5448
|
-
const snapshotSeq = numberField(recordField(readJson, "statusSnapshot"), "lastEventSeq");
|
|
5449
|
-
if (agentSeq !== undefined && snapshotSeq !== undefined)
|
|
5450
|
-
return Math.max(agentSeq, snapshotSeq);
|
|
5451
|
-
return agentSeq ?? snapshotSeq;
|
|
5452
|
-
}
|
|
5453
|
-
function codewithAgentEvidence(startJson, readJson, logsJson) {
|
|
5454
|
-
const agent = recordField(readJson, "agent") ?? recordField(startJson, "agent");
|
|
5455
|
-
const statusSnapshot = recordField(readJson, "statusSnapshot");
|
|
5456
|
-
const events = Array.isArray(logsJson?.data) ? logsJson.data.filter((event) => Boolean(event) && typeof event === "object" && !Array.isArray(event)).map((event) => ({
|
|
5457
|
-
seq: numberField(event, "seq"),
|
|
5458
|
-
eventType: stringField(event, "eventType"),
|
|
5459
|
-
createdAt: numberField(event, "createdAt")
|
|
5460
|
-
})) : undefined;
|
|
5461
|
-
return JSON.stringify({
|
|
5462
|
-
codewithAgent: {
|
|
5463
|
-
agentId: stringField(agent, "agentId"),
|
|
5464
|
-
status: stringField(agent, "status"),
|
|
5465
|
-
desiredState: stringField(agent, "desiredState"),
|
|
5466
|
-
statusReason: stringField(agent, "statusReason"),
|
|
5467
|
-
threadId: stringField(agent, "threadId"),
|
|
5468
|
-
rolloutPath: stringField(agent, "rolloutPath"),
|
|
5469
|
-
pid: numberField(agent, "pid"),
|
|
5470
|
-
exitCode: numberField(agent, "exitCode"),
|
|
5471
|
-
created: typeof startJson.created === "boolean" ? startJson.created : undefined
|
|
5472
|
-
},
|
|
5473
|
-
statusSnapshot: statusSnapshot ? {
|
|
5474
|
-
seq: numberField(statusSnapshot, "seq"),
|
|
5475
|
-
status: stringField(statusSnapshot, "status"),
|
|
5476
|
-
summary: stringField(statusSnapshot, "summary"),
|
|
5477
|
-
pendingInteractionCount: numberField(statusSnapshot, "pendingInteractionCount"),
|
|
5478
|
-
lastEventSeq: numberField(statusSnapshot, "lastEventSeq")
|
|
5479
|
-
} : undefined,
|
|
5480
|
-
events
|
|
5481
|
-
}, null, 2);
|
|
5482
|
-
}
|
|
5483
|
-
function codewithAgentProgress(readJson) {
|
|
5484
|
-
const agent = recordField(readJson, "agent");
|
|
5485
|
-
const statusSnapshot = recordField(readJson, "statusSnapshot");
|
|
5486
|
-
return {
|
|
5487
|
-
provider: "codewith",
|
|
5488
|
-
agentId: stringField(agent, "agentId"),
|
|
5489
|
-
status: stringField(agent, "status") ?? stringField(statusSnapshot, "status"),
|
|
5490
|
-
summary: stringField(statusSnapshot, "summary"),
|
|
5491
|
-
statusReason: stringField(agent, "statusReason"),
|
|
5492
|
-
threadId: stringField(agent, "threadId"),
|
|
5493
|
-
rolloutPath: stringField(agent, "rolloutPath"),
|
|
5494
|
-
pid: numberField(agent, "pid"),
|
|
5495
|
-
lastEventSeq: codewithAgentLastEventSeq(readJson)
|
|
5496
|
-
};
|
|
5497
|
-
}
|
|
5498
|
-
function sleep(ms) {
|
|
5499
|
-
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
5500
|
-
}
|
|
5501
|
-
async function executeCodewithDurableAgent(spec, metadata, opts, env, startedAt) {
|
|
5502
|
-
const target = spec.codewithDurableAgent?.target;
|
|
5503
|
-
if (!target)
|
|
5504
|
-
throw new Error("internal error: missing codewith durable target");
|
|
5505
|
-
const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
5506
|
-
const idempotencyKey = codewithAgentIdempotencyKey(metadata);
|
|
5507
|
-
const start = await spawnCapture(spec.command, codewithAgentStartArgs(target, idempotencyKey), {
|
|
5508
|
-
cwd: spec.cwd,
|
|
5509
|
-
env,
|
|
5510
|
-
timeoutMs: 30000,
|
|
5511
|
-
maxOutputBytes
|
|
5512
|
-
});
|
|
5513
|
-
const stderr = new BoundedOutputBuffer(maxOutputBytes);
|
|
5514
|
-
stderr.append(start.stderr);
|
|
5515
|
-
if (start.error || (start.status ?? 1) !== 0) {
|
|
5516
|
-
return failureResult(startedAt, start.error ?? `codewith agent start exited with code ${start.status ?? "unknown"}`, {
|
|
5517
|
-
exitCode: start.status ?? undefined,
|
|
5518
|
-
stdout: start.stdout,
|
|
5519
|
-
stderr: stderr.value()
|
|
5520
|
-
});
|
|
5521
|
-
}
|
|
5522
|
-
let startJson;
|
|
5523
|
-
try {
|
|
5524
|
-
startJson = parseJsonOutput(start.stdout || "{}", "codewith agent start");
|
|
5525
|
-
} catch (error) {
|
|
5526
|
-
return failureResult(startedAt, error instanceof Error ? error.message : String(error), { stdout: start.stdout, stderr: stderr.value() });
|
|
5527
|
-
}
|
|
5528
|
-
const agentId = stringField(recordField(startJson, "agent"), "agentId");
|
|
5529
|
-
if (!agentId) {
|
|
5530
|
-
return failureResult(startedAt, "codewith agent start did not return agent.agentId", { stdout: start.stdout, stderr: stderr.value() });
|
|
5531
|
-
}
|
|
5532
|
-
opts.onAgentProgress?.(codewithAgentProgress(startJson));
|
|
5533
|
-
const stopAgent = async () => {
|
|
5534
|
-
await spawnCapture(spec.command, codewithAgentControlArgs(target, "stop", agentId), {
|
|
5535
|
-
cwd: spec.cwd,
|
|
5536
|
-
env,
|
|
5537
|
-
timeoutMs: 15000,
|
|
5538
|
-
maxOutputBytes
|
|
5539
|
-
});
|
|
5540
|
-
};
|
|
5541
|
-
const pollMs = Math.max(100, Number(opts.env?.LOOPS_CODEWITH_AGENT_POLL_MS ?? process.env.LOOPS_CODEWITH_AGENT_POLL_MS ?? 2000) || 2000);
|
|
5542
|
-
let lastReadJson = startJson;
|
|
5543
|
-
let lastLogsJson;
|
|
5544
|
-
let lastFingerprint;
|
|
5545
|
-
let lastProgressAt = Date.now();
|
|
5546
|
-
const evidence = () => boundedText(codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes);
|
|
5547
|
-
while (true) {
|
|
5548
|
-
if (opts.signal?.aborted) {
|
|
5549
|
-
await stopAgent();
|
|
5550
|
-
return failureResult(startedAt, "cancelled", { stdout: evidence(), stderr: stderr.value() });
|
|
5551
|
-
}
|
|
5552
|
-
const read = await spawnCapture(spec.command, codewithAgentControlArgs(target, "read", agentId), {
|
|
5553
|
-
cwd: spec.cwd,
|
|
5554
|
-
env,
|
|
5555
|
-
timeoutMs: 30000,
|
|
5556
|
-
maxOutputBytes
|
|
5557
|
-
});
|
|
5558
|
-
stderr.append(read.stderr);
|
|
5559
|
-
if (read.error || (read.status ?? 1) !== 0) {
|
|
5560
|
-
return failureResult(startedAt, read.error ?? `codewith agent read exited with code ${read.status ?? "unknown"}`, {
|
|
5561
|
-
exitCode: read.status ?? undefined,
|
|
5562
|
-
stdout: evidence(),
|
|
5563
|
-
stderr: stderr.value()
|
|
5564
|
-
});
|
|
5565
|
-
}
|
|
5566
|
-
try {
|
|
5567
|
-
lastReadJson = parseJsonOutput(read.stdout || "{}", "codewith agent read");
|
|
5568
|
-
} catch (error) {
|
|
5569
|
-
return failureResult(startedAt, error instanceof Error ? error.message : String(error), {
|
|
5570
|
-
stdout: boundedText(read.stdout, maxOutputBytes),
|
|
5571
|
-
stderr: stderr.value()
|
|
5572
|
-
});
|
|
5573
|
-
}
|
|
5574
|
-
const status = codewithAgentStatus(lastReadJson);
|
|
5575
|
-
const fingerprint = JSON.stringify({
|
|
5576
|
-
status,
|
|
5577
|
-
agentLastEventSeq: numberField(recordField(lastReadJson, "agent"), "lastEventSeq"),
|
|
5578
|
-
snapshot: recordField(lastReadJson, "statusSnapshot") ?? null
|
|
5579
|
-
});
|
|
5580
|
-
if (fingerprint !== lastFingerprint) {
|
|
5581
|
-
lastFingerprint = fingerprint;
|
|
5582
|
-
lastProgressAt = Date.now();
|
|
5583
|
-
opts.onAgentProgress?.(codewithAgentProgress(lastReadJson));
|
|
5584
|
-
}
|
|
5585
|
-
if (status === "completed" || status === "failed" || status === "cancelled") {
|
|
5586
|
-
const logs = await spawnCapture(spec.command, codewithAgentControlArgs(target, "logs", agentId), {
|
|
5587
|
-
cwd: spec.cwd,
|
|
5588
|
-
env,
|
|
5589
|
-
timeoutMs: 30000,
|
|
5590
|
-
maxOutputBytes
|
|
5591
|
-
});
|
|
5592
|
-
if (!logs.error && (logs.status ?? 1) === 0) {
|
|
5593
|
-
try {
|
|
5594
|
-
lastLogsJson = parseJsonOutput(logs.stdout || "{}", "codewith agent logs");
|
|
5595
|
-
} catch {
|
|
5596
|
-
lastLogsJson = undefined;
|
|
5597
|
-
}
|
|
5598
|
-
} else {
|
|
5599
|
-
stderr.append(logs.stderr || logs.error || "");
|
|
5600
|
-
}
|
|
5601
|
-
if (status === "completed") {
|
|
5602
|
-
return successResult(startedAt, { exitCode: 0, stdout: evidence(), stderr: stderr.value() });
|
|
5603
|
-
}
|
|
5604
|
-
return failureResult(startedAt, stringField(recordField(lastReadJson, "agent"), "statusReason") ?? `codewith agent ${status}`, { exitCode: 1, stdout: evidence(), stderr: stderr.value() });
|
|
5605
|
-
}
|
|
5606
|
-
if (typeof spec.timeoutMs === "number" && new Date(nowIso()).getTime() - new Date(startedAt).getTime() >= spec.timeoutMs) {
|
|
5607
|
-
await stopAgent();
|
|
5608
|
-
return timeoutResult(startedAt, `timed out after ${spec.timeoutMs}ms`, { stdout: evidence(), stderr: stderr.value() });
|
|
5609
|
-
}
|
|
5610
|
-
if (typeof spec.idleTimeoutMs === "number" && Date.now() - lastProgressAt >= spec.idleTimeoutMs) {
|
|
5611
|
-
await stopAgent();
|
|
5612
|
-
return timeoutResult(startedAt, `idle timed out after ${spec.idleTimeoutMs}ms without agent progress`, {
|
|
5613
|
-
stdout: evidence(),
|
|
5614
|
-
stderr: stderr.value()
|
|
5615
|
-
});
|
|
5616
|
-
}
|
|
5617
|
-
await sleep(pollMs);
|
|
5618
|
-
}
|
|
5619
|
-
}
|
|
5620
5338
|
function resolvedDirEquals(left, right) {
|
|
5621
5339
|
try {
|
|
5622
5340
|
return realpathSync(left) === realpathSync(right);
|
|
@@ -5850,9 +5568,6 @@ function preflightTarget(target, metadata = {}, opts = {}) {
|
|
|
5850
5568
|
async function executeTarget(target, metadata = {}, opts = {}) {
|
|
5851
5569
|
let spec = commandSpec(target, opts);
|
|
5852
5570
|
const machine = resolvedMachine(opts);
|
|
5853
|
-
if (machine && !machine.local && spec.codewithDurableAgent) {
|
|
5854
|
-
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");
|
|
5855
|
-
}
|
|
5856
5571
|
if (machine && !machine.local) {
|
|
5857
5572
|
const remoteFallbackSpec = spec.worktree?.enabled && spec.worktree.mode === "auto" ? worktreeFallbackSpec(target, opts, spec.worktree.originalCwd) : undefined;
|
|
5858
5573
|
return executeRemoteSpec(spec, machine, metadata, opts, remoteFallbackSpec);
|
|
@@ -5883,9 +5598,6 @@ async function executeTarget(target, metadata = {}, opts = {}) {
|
|
|
5883
5598
|
if (worktreeEntry?.fallbackCwd) {
|
|
5884
5599
|
spec = worktreeFallbackSpec(target, opts, worktreeEntry.fallbackCwd) ?? spec;
|
|
5885
5600
|
}
|
|
5886
|
-
if (spec.codewithDurableAgent) {
|
|
5887
|
-
return executeCodewithDurableAgent(spec, metadata, opts, env, startedAt);
|
|
5888
|
-
}
|
|
5889
5601
|
const child = spawn2(spec.command, spec.args, {
|
|
5890
5602
|
cwd: spec.cwd,
|
|
5891
5603
|
env,
|
|
@@ -24,7 +24,7 @@ export interface ProviderAdapter {
|
|
|
24
24
|
validate(target: AgentTarget, label?: string): void;
|
|
25
25
|
buildInvocation(target: AgentTarget): AgentInvocation;
|
|
26
26
|
}
|
|
27
|
-
export declare const
|
|
27
|
+
export declare const UNSAFE_CODEWITH_EXEC_EXTRA_ARGS: Set<string>;
|
|
28
28
|
export declare const PROVIDER_ADAPTERS: Record<AgentProvider, ProviderAdapter>;
|
|
29
29
|
export declare const AGENT_PROVIDERS: AgentProvider[];
|
|
30
30
|
export declare function providerAdapter(provider: AgentProvider): ProviderAdapter;
|
package/dist/lib/mode.js
CHANGED
|
@@ -8,4 +8,14 @@ export interface PrReviewRoutingDecision {
|
|
|
8
8
|
selectedReviewer?: string;
|
|
9
9
|
signals: string[];
|
|
10
10
|
}
|
|
11
|
-
|
|
11
|
+
/** A GitHub PR reference resolvable to `owner/repo#number`. */
|
|
12
|
+
export interface PrReference {
|
|
13
|
+
owner: string;
|
|
14
|
+
repo: string;
|
|
15
|
+
number: number;
|
|
16
|
+
}
|
|
17
|
+
/** Resolves a PR author login from a concrete `owner/repo#number` reference. */
|
|
18
|
+
export type PrAuthorResolver = (ref: PrReference) => string | undefined;
|
|
19
|
+
/** Extracts a concrete owner/repo/number PR reference from route evidence text. */
|
|
20
|
+
export declare function prReferenceFrom(text: string): PrReference | undefined;
|
|
21
|
+
export declare function prReviewRoutingDecision(data: Record<string, unknown>, metadata: Record<string, unknown>, opts: TodosTaskRouteOptions, resolveAuthor?: PrAuthorResolver): PrReviewRoutingDecision;
|
|
@@ -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" })
|
|
@@ -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" })
|
package/dist/lib/store.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" })
|