@hasna/loops 0.3.59 → 0.3.60
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/cli/index.js +351 -37
- package/dist/daemon/index.js +330 -16
- package/dist/index.js +323 -9
- package/dist/lib/store.js +26 -1
- package/dist/mcp/index.js +323 -9
- package/dist/sdk/index.js +322 -8
- package/docs/USAGE.md +4 -4
- package/package.json +1 -1
package/dist/sdk/index.js
CHANGED
|
@@ -356,6 +356,20 @@ function optionalStringArray(value, label) {
|
|
|
356
356
|
}).filter(Boolean);
|
|
357
357
|
return values.length ? values : undefined;
|
|
358
358
|
}
|
|
359
|
+
var UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS = new Set([
|
|
360
|
+
"e",
|
|
361
|
+
"exec",
|
|
362
|
+
"agent",
|
|
363
|
+
"start",
|
|
364
|
+
"--ephemeral",
|
|
365
|
+
"--ignore-rules",
|
|
366
|
+
"--skip-git-repo-check",
|
|
367
|
+
"--json",
|
|
368
|
+
"--output-last-message",
|
|
369
|
+
"-o",
|
|
370
|
+
"--output-schema",
|
|
371
|
+
"--dangerously-bypass-approvals-and-sandbox"
|
|
372
|
+
]);
|
|
359
373
|
function optionalAccountRef(value, label) {
|
|
360
374
|
if (value === undefined)
|
|
361
375
|
return;
|
|
@@ -470,6 +484,15 @@ function validateTarget(value, label, opts) {
|
|
|
470
484
|
throw new Error(`${label}.variant is not supported for provider cursor`);
|
|
471
485
|
if (value.provider === "codex" && value.agent !== undefined)
|
|
472
486
|
throw new Error(`${label}.agent is not supported for provider codex`);
|
|
487
|
+
if (value.provider === "codewith" && value.agent !== undefined) {
|
|
488
|
+
throw new Error(`${label}.agent is not supported for provider codewith durable background-agent execution`);
|
|
489
|
+
}
|
|
490
|
+
const extraArgs = optionalStringArray(value.extraArgs, `${label}.extraArgs`);
|
|
491
|
+
if (value.provider === "codewith") {
|
|
492
|
+
const unsafe = extraArgs?.find((arg) => UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS.has(arg));
|
|
493
|
+
if (unsafe)
|
|
494
|
+
throw new Error(`${label}.extraArgs cannot include ${unsafe}; codewith agent steps use durable agent start, not exec/ephemeral flags`);
|
|
495
|
+
}
|
|
473
496
|
optionalStringArray(value.addDirs, `${label}.addDirs`);
|
|
474
497
|
if (Array.isArray(value.addDirs) && value.addDirs.length > 0 && !["codewith", "codex"].includes(value.provider)) {
|
|
475
498
|
throw new Error(`${label}.addDirs is currently supported only for provider codewith or codex`);
|
|
@@ -545,7 +568,9 @@ function validateTarget(value, label, opts) {
|
|
|
545
568
|
if (value.routing.eventSource !== undefined)
|
|
546
569
|
assertString(value.routing.eventSource, `${label}.routing.eventSource`);
|
|
547
570
|
}
|
|
548
|
-
const target = { ...value };
|
|
571
|
+
const target = { ...value, extraArgs };
|
|
572
|
+
if (!extraArgs)
|
|
573
|
+
delete target.extraArgs;
|
|
549
574
|
delete target.promptFile;
|
|
550
575
|
delete target.promptSource;
|
|
551
576
|
return { ...target, ...promptFields };
|
|
@@ -3478,6 +3503,9 @@ function appendBounded(current, chunk, maxBytes) {
|
|
|
3478
3503
|
return `[truncated ${overflow} bytes]
|
|
3479
3504
|
${next.slice(-maxBytes)}`;
|
|
3480
3505
|
}
|
|
3506
|
+
function appendBoundedText(current, chunk, maxBytes) {
|
|
3507
|
+
return appendBounded(current, Buffer.from(chunk, "utf8"), maxBytes);
|
|
3508
|
+
}
|
|
3481
3509
|
function killProcessGroup(pid) {
|
|
3482
3510
|
try {
|
|
3483
3511
|
process.kill(-pid, "SIGTERM");
|
|
@@ -3525,6 +3553,21 @@ function metadataEnv(metadata) {
|
|
|
3525
3553
|
env.LOOPS_GOAL_NODE_KEY = metadata.goalNodeKey;
|
|
3526
3554
|
return env;
|
|
3527
3555
|
}
|
|
3556
|
+
function codewithAgentIdempotencyKey(metadata) {
|
|
3557
|
+
const parts = [
|
|
3558
|
+
"openloops",
|
|
3559
|
+
metadata.workflowRunId ? `workflow-run:${metadata.workflowRunId}` : undefined,
|
|
3560
|
+
metadata.workflowStepId ? `step:${metadata.workflowStepId}` : undefined,
|
|
3561
|
+
metadata.runId ? `loop-run:${metadata.runId}` : undefined,
|
|
3562
|
+
metadata.loopId ? `loop:${metadata.loopId}` : undefined,
|
|
3563
|
+
metadata.scheduledFor ? `scheduled:${metadata.scheduledFor}` : undefined,
|
|
3564
|
+
metadata.goalId ? `goal:${metadata.goalId}` : undefined,
|
|
3565
|
+
metadata.goalNodeKey ? `node:${metadata.goalNodeKey}` : undefined
|
|
3566
|
+
].filter(Boolean);
|
|
3567
|
+
if (parts.length > 1)
|
|
3568
|
+
return parts.join(":");
|
|
3569
|
+
return `openloops:adhoc:${randomBytes2(16).toString("hex")}`;
|
|
3570
|
+
}
|
|
3528
3571
|
function allowlistEnv(allowlist) {
|
|
3529
3572
|
const env = {};
|
|
3530
3573
|
if (allowlist?.tools?.length)
|
|
@@ -3561,6 +3604,20 @@ function codewithLikeSandbox(target) {
|
|
|
3561
3604
|
function configStringValue(value) {
|
|
3562
3605
|
return JSON.stringify(value);
|
|
3563
3606
|
}
|
|
3607
|
+
var UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS2 = new Set([
|
|
3608
|
+
"e",
|
|
3609
|
+
"exec",
|
|
3610
|
+
"agent",
|
|
3611
|
+
"start",
|
|
3612
|
+
"--ephemeral",
|
|
3613
|
+
"--ignore-rules",
|
|
3614
|
+
"--skip-git-repo-check",
|
|
3615
|
+
"--json",
|
|
3616
|
+
"--output-last-message",
|
|
3617
|
+
"-o",
|
|
3618
|
+
"--output-schema",
|
|
3619
|
+
"--dangerously-bypass-approvals-and-sandbox"
|
|
3620
|
+
]);
|
|
3564
3621
|
function assertStringOption(value, label) {
|
|
3565
3622
|
if (value !== undefined && typeof value !== "string")
|
|
3566
3623
|
throw new Error(`${label} must be a string`);
|
|
@@ -3591,6 +3648,14 @@ function assertSupportedAgentOptions(target) {
|
|
|
3591
3648
|
}
|
|
3592
3649
|
if (target.provider === "codex" && target.agent !== undefined)
|
|
3593
3650
|
throw new Error("codex.agent is not supported");
|
|
3651
|
+
if (target.provider === "codewith" && target.agent !== undefined) {
|
|
3652
|
+
throw new Error("codewith.agent is not supported by the durable background-agent adapter");
|
|
3653
|
+
}
|
|
3654
|
+
if (target.provider === "codewith") {
|
|
3655
|
+
const unsafe = target.extraArgs?.find((arg) => UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS2.has(arg));
|
|
3656
|
+
if (unsafe)
|
|
3657
|
+
throw new Error(`codewith.extraArgs cannot include ${unsafe}; durable agent steps use codewith agent start, not exec/ephemeral flags`);
|
|
3658
|
+
}
|
|
3594
3659
|
if (["codewith", "codex"].includes(target.provider)) {
|
|
3595
3660
|
if (target.permissionMode && !["default", "bypass"].includes(target.permissionMode)) {
|
|
3596
3661
|
throw new Error(`${target.provider}.permissionMode supports only default or bypass`);
|
|
@@ -3674,18 +3739,18 @@ function agentArgs(target) {
|
|
|
3674
3739
|
args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : []);
|
|
3675
3740
|
if (target.variant)
|
|
3676
3741
|
args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
|
|
3677
|
-
args.push("--ask-for-approval", "never", "
|
|
3678
|
-
if (isolation === "safe")
|
|
3679
|
-
args.push("--ignore-rules");
|
|
3742
|
+
args.push("--ask-for-approval", "never", "--sandbox", codewithLikeSandbox(target));
|
|
3680
3743
|
if (target.cwd)
|
|
3681
3744
|
args.push("--cd", target.cwd);
|
|
3682
3745
|
for (const dir of target.addDirs ?? [])
|
|
3683
3746
|
args.push("--add-dir", dir);
|
|
3684
3747
|
if (target.model)
|
|
3685
3748
|
args.push("--model", target.model);
|
|
3686
|
-
if (target.agent)
|
|
3687
|
-
args.push("--agent", target.agent);
|
|
3688
3749
|
args.push(...target.extraArgs ?? []);
|
|
3750
|
+
args.push("agent", "start");
|
|
3751
|
+
if (target.cwd)
|
|
3752
|
+
args.push("--cwd", target.cwd);
|
|
3753
|
+
args.push(target.prompt);
|
|
3689
3754
|
return args;
|
|
3690
3755
|
case "codex":
|
|
3691
3756
|
if (target.variant)
|
|
@@ -3761,8 +3826,9 @@ function commandSpec(target) {
|
|
|
3761
3826
|
accountTool: agentTarget.account?.tool ?? accountToolForProvider(agentTarget.provider),
|
|
3762
3827
|
nativeAuthProfile: agentTarget.authProfile ? { provider: agentTarget.provider, profile: agentTarget.authProfile } : undefined,
|
|
3763
3828
|
preflightAnyOf: agentTarget.provider === "cursor" ? ["agent"] : undefined,
|
|
3764
|
-
stdin: agentTarget.prompt,
|
|
3765
|
-
allowlist: agentTarget.allowlist
|
|
3829
|
+
stdin: agentTarget.provider === "codewith" ? undefined : agentTarget.prompt,
|
|
3830
|
+
allowlist: agentTarget.allowlist,
|
|
3831
|
+
codewithDurableAgent: agentTarget.provider === "codewith" ? { target: agentTarget } : undefined
|
|
3766
3832
|
};
|
|
3767
3833
|
}
|
|
3768
3834
|
function executionEnv(spec, metadata, opts) {
|
|
@@ -3882,6 +3948,238 @@ function preflightNativeAuthProfile(spec, env) {
|
|
|
3882
3948
|
throw new Error(`codewith auth profile not found: ${spec.nativeAuthProfile.profile}`);
|
|
3883
3949
|
}
|
|
3884
3950
|
}
|
|
3951
|
+
function codewithAgentStartArgs(target, idempotencyKey) {
|
|
3952
|
+
const args = agentArgs(target);
|
|
3953
|
+
const startIndex = args.findIndex((arg, index) => arg === "start" && args[index - 1] === "agent");
|
|
3954
|
+
if (startIndex === -1)
|
|
3955
|
+
throw new Error("internal error: codewith durable agent args missing agent start");
|
|
3956
|
+
args.splice(startIndex + 1, 0, "--idempotency-key", idempotencyKey);
|
|
3957
|
+
return args;
|
|
3958
|
+
}
|
|
3959
|
+
function codewithAgentControlArgs(target, command, agentId) {
|
|
3960
|
+
return [
|
|
3961
|
+
...target.authProfile ? ["--auth-profile", target.authProfile] : [],
|
|
3962
|
+
"agent",
|
|
3963
|
+
command,
|
|
3964
|
+
...command === "logs" ? ["--limit", "20"] : [],
|
|
3965
|
+
agentId
|
|
3966
|
+
];
|
|
3967
|
+
}
|
|
3968
|
+
function parseJsonOutput(stdout, label) {
|
|
3969
|
+
try {
|
|
3970
|
+
const value = JSON.parse(stdout || "{}");
|
|
3971
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3972
|
+
throw new Error("not an object");
|
|
3973
|
+
return value;
|
|
3974
|
+
} catch (error) {
|
|
3975
|
+
throw new Error(`${label} did not return JSON${error instanceof Error ? `: ${error.message}` : ""}`);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
function recordField(value, key) {
|
|
3979
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3980
|
+
return;
|
|
3981
|
+
const field = value[key];
|
|
3982
|
+
return field && typeof field === "object" && !Array.isArray(field) ? field : undefined;
|
|
3983
|
+
}
|
|
3984
|
+
function stringField(value, key) {
|
|
3985
|
+
const field = value?.[key];
|
|
3986
|
+
return typeof field === "string" ? field : undefined;
|
|
3987
|
+
}
|
|
3988
|
+
function numberField(value, key) {
|
|
3989
|
+
const field = value?.[key];
|
|
3990
|
+
return typeof field === "number" && Number.isFinite(field) ? field : undefined;
|
|
3991
|
+
}
|
|
3992
|
+
function codewithAgentStatus(readJson) {
|
|
3993
|
+
return stringField(recordField(readJson, "agent"), "status") ?? stringField(recordField(readJson, "statusSnapshot"), "status");
|
|
3994
|
+
}
|
|
3995
|
+
function codewithAgentEvidence(startJson, readJson, logsJson) {
|
|
3996
|
+
const agent = recordField(readJson, "agent") ?? recordField(startJson, "agent");
|
|
3997
|
+
const statusSnapshot = recordField(readJson, "statusSnapshot");
|
|
3998
|
+
const events = Array.isArray(logsJson?.data) ? logsJson.data.filter((event) => Boolean(event) && typeof event === "object" && !Array.isArray(event)).map((event) => ({
|
|
3999
|
+
seq: numberField(event, "seq"),
|
|
4000
|
+
eventType: stringField(event, "eventType"),
|
|
4001
|
+
createdAt: numberField(event, "createdAt")
|
|
4002
|
+
})) : undefined;
|
|
4003
|
+
return JSON.stringify({
|
|
4004
|
+
codewithAgent: {
|
|
4005
|
+
agentId: stringField(agent, "agentId"),
|
|
4006
|
+
status: stringField(agent, "status"),
|
|
4007
|
+
desiredState: stringField(agent, "desiredState"),
|
|
4008
|
+
statusReason: stringField(agent, "statusReason"),
|
|
4009
|
+
threadId: stringField(agent, "threadId"),
|
|
4010
|
+
rolloutPath: stringField(agent, "rolloutPath"),
|
|
4011
|
+
pid: numberField(agent, "pid"),
|
|
4012
|
+
exitCode: numberField(agent, "exitCode"),
|
|
4013
|
+
created: typeof startJson.created === "boolean" ? startJson.created : undefined
|
|
4014
|
+
},
|
|
4015
|
+
statusSnapshot: statusSnapshot ? {
|
|
4016
|
+
seq: numberField(statusSnapshot, "seq"),
|
|
4017
|
+
status: stringField(statusSnapshot, "status"),
|
|
4018
|
+
summary: stringField(statusSnapshot, "summary"),
|
|
4019
|
+
pendingInteractionCount: numberField(statusSnapshot, "pendingInteractionCount"),
|
|
4020
|
+
lastEventSeq: numberField(statusSnapshot, "lastEventSeq")
|
|
4021
|
+
} : undefined,
|
|
4022
|
+
events
|
|
4023
|
+
}, null, 2);
|
|
4024
|
+
}
|
|
4025
|
+
function sleep(ms) {
|
|
4026
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4027
|
+
}
|
|
4028
|
+
async function executeCodewithDurableAgent(spec, metadata, opts, env, startedAt) {
|
|
4029
|
+
const target = spec.codewithDurableAgent?.target;
|
|
4030
|
+
if (!target)
|
|
4031
|
+
throw new Error("internal error: missing codewith durable target");
|
|
4032
|
+
const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
4033
|
+
const idempotencyKey = codewithAgentIdempotencyKey(metadata);
|
|
4034
|
+
const start = spawnSync2(spec.command, codewithAgentStartArgs(target, idempotencyKey), {
|
|
4035
|
+
cwd: spec.cwd,
|
|
4036
|
+
env,
|
|
4037
|
+
encoding: "utf8",
|
|
4038
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4039
|
+
timeout: 30000
|
|
4040
|
+
});
|
|
4041
|
+
let stderr = start.stderr || "";
|
|
4042
|
+
if (start.error || (start.status ?? 1) !== 0) {
|
|
4043
|
+
const finishedAt = nowIso();
|
|
4044
|
+
return {
|
|
4045
|
+
status: "failed",
|
|
4046
|
+
exitCode: start.status ?? undefined,
|
|
4047
|
+
stdout: start.stdout || "",
|
|
4048
|
+
stderr,
|
|
4049
|
+
error: start.error?.message ?? `codewith agent start exited with code ${start.status ?? "unknown"}`,
|
|
4050
|
+
startedAt,
|
|
4051
|
+
finishedAt,
|
|
4052
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4053
|
+
};
|
|
4054
|
+
}
|
|
4055
|
+
let startJson;
|
|
4056
|
+
try {
|
|
4057
|
+
startJson = parseJsonOutput(start.stdout || "{}", "codewith agent start");
|
|
4058
|
+
} catch (error) {
|
|
4059
|
+
const finishedAt = nowIso();
|
|
4060
|
+
return {
|
|
4061
|
+
status: "failed",
|
|
4062
|
+
stdout: start.stdout || "",
|
|
4063
|
+
stderr,
|
|
4064
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4065
|
+
startedAt,
|
|
4066
|
+
finishedAt,
|
|
4067
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4068
|
+
};
|
|
4069
|
+
}
|
|
4070
|
+
const agentId = stringField(recordField(startJson, "agent"), "agentId");
|
|
4071
|
+
if (!agentId) {
|
|
4072
|
+
const finishedAt = nowIso();
|
|
4073
|
+
return {
|
|
4074
|
+
status: "failed",
|
|
4075
|
+
stdout: start.stdout || "",
|
|
4076
|
+
stderr,
|
|
4077
|
+
error: "codewith agent start did not return agent.agentId",
|
|
4078
|
+
startedAt,
|
|
4079
|
+
finishedAt,
|
|
4080
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4081
|
+
};
|
|
4082
|
+
}
|
|
4083
|
+
const pollMs = Math.max(100, Number(opts.env?.LOOPS_CODEWITH_AGENT_POLL_MS ?? process.env.LOOPS_CODEWITH_AGENT_POLL_MS ?? 2000) || 2000);
|
|
4084
|
+
let lastReadJson = startJson;
|
|
4085
|
+
let lastLogsJson;
|
|
4086
|
+
let stdout = "";
|
|
4087
|
+
while (true) {
|
|
4088
|
+
if (opts.signal?.aborted) {
|
|
4089
|
+
spawnSync2(spec.command, codewithAgentControlArgs(target, "stop", agentId), { cwd: spec.cwd, env, stdio: "ignore", timeout: 15000 });
|
|
4090
|
+
const finishedAt = nowIso();
|
|
4091
|
+
return {
|
|
4092
|
+
status: "failed",
|
|
4093
|
+
stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
|
|
4094
|
+
stderr,
|
|
4095
|
+
error: "cancelled",
|
|
4096
|
+
startedAt,
|
|
4097
|
+
finishedAt,
|
|
4098
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4099
|
+
};
|
|
4100
|
+
}
|
|
4101
|
+
const read = spawnSync2(spec.command, codewithAgentControlArgs(target, "read", agentId), {
|
|
4102
|
+
cwd: spec.cwd,
|
|
4103
|
+
env,
|
|
4104
|
+
encoding: "utf8",
|
|
4105
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4106
|
+
timeout: 30000
|
|
4107
|
+
});
|
|
4108
|
+
stderr = appendBoundedText(stderr, read.stderr || "", maxOutputBytes);
|
|
4109
|
+
if (read.error || (read.status ?? 1) !== 0) {
|
|
4110
|
+
const finishedAt = nowIso();
|
|
4111
|
+
return {
|
|
4112
|
+
status: "failed",
|
|
4113
|
+
exitCode: read.status ?? undefined,
|
|
4114
|
+
stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
|
|
4115
|
+
stderr,
|
|
4116
|
+
error: read.error?.message ?? `codewith agent read exited with code ${read.status ?? "unknown"}`,
|
|
4117
|
+
startedAt,
|
|
4118
|
+
finishedAt,
|
|
4119
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4120
|
+
};
|
|
4121
|
+
}
|
|
4122
|
+
try {
|
|
4123
|
+
lastReadJson = parseJsonOutput(read.stdout || "{}", "codewith agent read");
|
|
4124
|
+
} catch (error) {
|
|
4125
|
+
const finishedAt = nowIso();
|
|
4126
|
+
return {
|
|
4127
|
+
status: "failed",
|
|
4128
|
+
stdout: appendBoundedText(stdout, read.stdout || "", maxOutputBytes),
|
|
4129
|
+
stderr,
|
|
4130
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4131
|
+
startedAt,
|
|
4132
|
+
finishedAt,
|
|
4133
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4134
|
+
};
|
|
4135
|
+
}
|
|
4136
|
+
const status = codewithAgentStatus(lastReadJson);
|
|
4137
|
+
if (status === "completed" || status === "failed" || status === "cancelled") {
|
|
4138
|
+
const logs = spawnSync2(spec.command, codewithAgentControlArgs(target, "logs", agentId), {
|
|
4139
|
+
cwd: spec.cwd,
|
|
4140
|
+
env,
|
|
4141
|
+
encoding: "utf8",
|
|
4142
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4143
|
+
timeout: 30000
|
|
4144
|
+
});
|
|
4145
|
+
if (!logs.error && (logs.status ?? 1) === 0) {
|
|
4146
|
+
try {
|
|
4147
|
+
lastLogsJson = parseJsonOutput(logs.stdout || "{}", "codewith agent logs");
|
|
4148
|
+
} catch {
|
|
4149
|
+
lastLogsJson = undefined;
|
|
4150
|
+
}
|
|
4151
|
+
} else {
|
|
4152
|
+
stderr = appendBoundedText(stderr, logs.stderr || logs.error?.message || "", maxOutputBytes);
|
|
4153
|
+
}
|
|
4154
|
+
const finishedAt = nowIso();
|
|
4155
|
+
const resultStatus = status === "completed" ? "succeeded" : "failed";
|
|
4156
|
+
return {
|
|
4157
|
+
status: resultStatus,
|
|
4158
|
+
exitCode: resultStatus === "succeeded" ? 0 : 1,
|
|
4159
|
+
stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
|
|
4160
|
+
stderr,
|
|
4161
|
+
error: resultStatus === "succeeded" ? undefined : stringField(recordField(lastReadJson, "agent"), "statusReason") ?? `codewith agent ${status}`,
|
|
4162
|
+
startedAt,
|
|
4163
|
+
finishedAt,
|
|
4164
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4165
|
+
};
|
|
4166
|
+
}
|
|
4167
|
+
if (typeof spec.timeoutMs === "number" && new Date(nowIso()).getTime() - new Date(startedAt).getTime() >= spec.timeoutMs) {
|
|
4168
|
+
spawnSync2(spec.command, codewithAgentControlArgs(target, "stop", agentId), { cwd: spec.cwd, env, stdio: "ignore", timeout: 15000 });
|
|
4169
|
+
const finishedAt = nowIso();
|
|
4170
|
+
return {
|
|
4171
|
+
status: "timed_out",
|
|
4172
|
+
stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
|
|
4173
|
+
stderr,
|
|
4174
|
+
error: `timed out after ${spec.timeoutMs}ms`,
|
|
4175
|
+
startedAt,
|
|
4176
|
+
finishedAt,
|
|
4177
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4178
|
+
};
|
|
4179
|
+
}
|
|
4180
|
+
await sleep(pollMs);
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
3885
4183
|
function preflightRemoteSpec(spec, machine, metadata, opts) {
|
|
3886
4184
|
const plan = (opts.machineCommandResolver ?? resolveMachineCommand)(machine.id, "bash -s");
|
|
3887
4185
|
const result = spawnSync2(plan.command, plan.args, {
|
|
@@ -4053,6 +4351,19 @@ function preflightTarget(target, metadata = {}, opts = {}) {
|
|
|
4053
4351
|
async function executeTarget(target, metadata = {}, opts = {}) {
|
|
4054
4352
|
const spec = commandSpec(target);
|
|
4055
4353
|
const machine = resolvedMachine(opts);
|
|
4354
|
+
if (machine && !machine.local && spec.codewithDurableAgent) {
|
|
4355
|
+
const startedAt2 = nowIso();
|
|
4356
|
+
const finishedAt2 = nowIso();
|
|
4357
|
+
return {
|
|
4358
|
+
status: "failed",
|
|
4359
|
+
stdout: "",
|
|
4360
|
+
stderr: "",
|
|
4361
|
+
error: "remote Codewith durable background-agent steps require remote status polling support; run this Codewith step locally or add remote durable readback before dispatch",
|
|
4362
|
+
startedAt: startedAt2,
|
|
4363
|
+
finishedAt: finishedAt2,
|
|
4364
|
+
durationMs: new Date(finishedAt2).getTime() - new Date(startedAt2).getTime()
|
|
4365
|
+
};
|
|
4366
|
+
}
|
|
4056
4367
|
if (machine && !machine.local)
|
|
4057
4368
|
return executeRemoteSpec(spec, machine, metadata, opts);
|
|
4058
4369
|
const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
@@ -4099,6 +4410,9 @@ async function executeTarget(target, metadata = {}, opts = {}) {
|
|
|
4099
4410
|
durationMs: 0
|
|
4100
4411
|
};
|
|
4101
4412
|
}
|
|
4413
|
+
if (spec.codewithDurableAgent) {
|
|
4414
|
+
return executeCodewithDurableAgent(spec, metadata, opts, env, startedAt);
|
|
4415
|
+
}
|
|
4102
4416
|
const child = spawn(spec.command, spec.args, {
|
|
4103
4417
|
cwd: spec.cwd,
|
|
4104
4418
|
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 agent start`
|
|
10
10
|
- `aicopilot run`
|
|
11
11
|
- `opencode run`
|
|
12
12
|
- `codex exec`
|
|
@@ -893,13 +893,13 @@ On Linux this writes a user systemd service. On macOS it writes a LaunchAgent pl
|
|
|
893
893
|
The adapters intentionally use provider command surfaces instead of pretending every agent has one SDK:
|
|
894
894
|
|
|
895
895
|
- Claude uses `claude -p --output-format json` and safe-mode/local setting sources by default.
|
|
896
|
-
- Codewith uses `codewith --ask-for-approval never exec
|
|
896
|
+
- Codewith uses durable `codewith --ask-for-approval never agent start` background-agent runs by default, then polls `codewith agent read` until the run is terminal and records compact status/event evidence. Remote Codewith agent steps fail closed until remote durable readback is implemented, so workflows do not advance immediately after enqueue. OpenLoops rejects Codewith `extraArgs` that try to force `exec`, `--ephemeral`, or other non-durable exec-only flags for task-lifecycle, planner, worker, verifier, reviewer, release, or other long-running agentic work.
|
|
897
897
|
- AI Copilot and OpenCode use `run --format json --pure`. OpenCode requires an explicit provider/model id because ambient OpenCode config is machine-specific.
|
|
898
898
|
- 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.
|
|
899
899
|
- Codex uses `codex --ask-for-approval never exec --json --ephemeral --skip-git-repo-check`, with `--add-dir` for explicit extra writable directories where supported.
|
|
900
|
-
- Agent prompts are sent through child stdin instead of argv so
|
|
900
|
+
- Agent prompts are sent through child stdin instead of argv where the provider supports stdin. Codewith durable background agents currently accept prompts as native `agent start` arguments, so OpenLoops stores only bounded status/event evidence and omits raw Codewith event payloads from workflow stdout.
|
|
901
901
|
- 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.
|
|
902
|
-
- `--auth-profile` and step `authProfile` are provider-native auth selectors. They currently apply to Codewith and are passed to Codewith as `--auth-profile <name>` before `
|
|
902
|
+
- `--auth-profile` and step `authProfile` are provider-native auth selectors. They currently apply to Codewith and are passed to Codewith as `--auth-profile <name>` before `agent start/read/logs`; they do not call OpenAccounts.
|
|
903
903
|
- `--sandbox` maps to provider-native sandbox flags. Codewith/Codex accept `read-only`, `workspace-write`, or `danger-full-access`; Cursor accepts `enabled` or `disabled`.
|
|
904
904
|
- `--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`.
|
|
905
905
|
- `--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`.
|
package/package.json
CHANGED