@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/daemon/index.js
CHANGED
|
@@ -358,6 +358,20 @@ function optionalStringArray(value, label) {
|
|
|
358
358
|
}).filter(Boolean);
|
|
359
359
|
return values.length ? values : undefined;
|
|
360
360
|
}
|
|
361
|
+
var UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS = new Set([
|
|
362
|
+
"e",
|
|
363
|
+
"exec",
|
|
364
|
+
"agent",
|
|
365
|
+
"start",
|
|
366
|
+
"--ephemeral",
|
|
367
|
+
"--ignore-rules",
|
|
368
|
+
"--skip-git-repo-check",
|
|
369
|
+
"--json",
|
|
370
|
+
"--output-last-message",
|
|
371
|
+
"-o",
|
|
372
|
+
"--output-schema",
|
|
373
|
+
"--dangerously-bypass-approvals-and-sandbox"
|
|
374
|
+
]);
|
|
361
375
|
function optionalAccountRef(value, label) {
|
|
362
376
|
if (value === undefined)
|
|
363
377
|
return;
|
|
@@ -472,6 +486,15 @@ function validateTarget(value, label, opts) {
|
|
|
472
486
|
throw new Error(`${label}.variant is not supported for provider cursor`);
|
|
473
487
|
if (value.provider === "codex" && value.agent !== undefined)
|
|
474
488
|
throw new Error(`${label}.agent is not supported for provider codex`);
|
|
489
|
+
if (value.provider === "codewith" && value.agent !== undefined) {
|
|
490
|
+
throw new Error(`${label}.agent is not supported for provider codewith durable background-agent execution`);
|
|
491
|
+
}
|
|
492
|
+
const extraArgs = optionalStringArray(value.extraArgs, `${label}.extraArgs`);
|
|
493
|
+
if (value.provider === "codewith") {
|
|
494
|
+
const unsafe = extraArgs?.find((arg) => UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS.has(arg));
|
|
495
|
+
if (unsafe)
|
|
496
|
+
throw new Error(`${label}.extraArgs cannot include ${unsafe}; codewith agent steps use durable agent start, not exec/ephemeral flags`);
|
|
497
|
+
}
|
|
475
498
|
optionalStringArray(value.addDirs, `${label}.addDirs`);
|
|
476
499
|
if (Array.isArray(value.addDirs) && value.addDirs.length > 0 && !["codewith", "codex"].includes(value.provider)) {
|
|
477
500
|
throw new Error(`${label}.addDirs is currently supported only for provider codewith or codex`);
|
|
@@ -547,7 +570,9 @@ function validateTarget(value, label, opts) {
|
|
|
547
570
|
if (value.routing.eventSource !== undefined)
|
|
548
571
|
assertString(value.routing.eventSource, `${label}.routing.eventSource`);
|
|
549
572
|
}
|
|
550
|
-
const target = { ...value };
|
|
573
|
+
const target = { ...value, extraArgs };
|
|
574
|
+
if (!extraArgs)
|
|
575
|
+
delete target.extraArgs;
|
|
551
576
|
delete target.promptFile;
|
|
552
577
|
delete target.promptSource;
|
|
553
578
|
return { ...target, ...promptFields };
|
|
@@ -3488,6 +3513,9 @@ function appendBounded(current, chunk, maxBytes) {
|
|
|
3488
3513
|
return `[truncated ${overflow} bytes]
|
|
3489
3514
|
${next.slice(-maxBytes)}`;
|
|
3490
3515
|
}
|
|
3516
|
+
function appendBoundedText(current, chunk, maxBytes) {
|
|
3517
|
+
return appendBounded(current, Buffer.from(chunk, "utf8"), maxBytes);
|
|
3518
|
+
}
|
|
3491
3519
|
function killProcessGroup(pid) {
|
|
3492
3520
|
try {
|
|
3493
3521
|
process.kill(-pid, "SIGTERM");
|
|
@@ -3535,6 +3563,21 @@ function metadataEnv(metadata) {
|
|
|
3535
3563
|
env.LOOPS_GOAL_NODE_KEY = metadata.goalNodeKey;
|
|
3536
3564
|
return env;
|
|
3537
3565
|
}
|
|
3566
|
+
function codewithAgentIdempotencyKey(metadata) {
|
|
3567
|
+
const parts = [
|
|
3568
|
+
"openloops",
|
|
3569
|
+
metadata.workflowRunId ? `workflow-run:${metadata.workflowRunId}` : undefined,
|
|
3570
|
+
metadata.workflowStepId ? `step:${metadata.workflowStepId}` : undefined,
|
|
3571
|
+
metadata.runId ? `loop-run:${metadata.runId}` : undefined,
|
|
3572
|
+
metadata.loopId ? `loop:${metadata.loopId}` : undefined,
|
|
3573
|
+
metadata.scheduledFor ? `scheduled:${metadata.scheduledFor}` : undefined,
|
|
3574
|
+
metadata.goalId ? `goal:${metadata.goalId}` : undefined,
|
|
3575
|
+
metadata.goalNodeKey ? `node:${metadata.goalNodeKey}` : undefined
|
|
3576
|
+
].filter(Boolean);
|
|
3577
|
+
if (parts.length > 1)
|
|
3578
|
+
return parts.join(":");
|
|
3579
|
+
return `openloops:adhoc:${randomBytes2(16).toString("hex")}`;
|
|
3580
|
+
}
|
|
3538
3581
|
function allowlistEnv(allowlist) {
|
|
3539
3582
|
const env = {};
|
|
3540
3583
|
if (allowlist?.tools?.length)
|
|
@@ -3571,6 +3614,20 @@ function codewithLikeSandbox(target) {
|
|
|
3571
3614
|
function configStringValue(value) {
|
|
3572
3615
|
return JSON.stringify(value);
|
|
3573
3616
|
}
|
|
3617
|
+
var UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS2 = new Set([
|
|
3618
|
+
"e",
|
|
3619
|
+
"exec",
|
|
3620
|
+
"agent",
|
|
3621
|
+
"start",
|
|
3622
|
+
"--ephemeral",
|
|
3623
|
+
"--ignore-rules",
|
|
3624
|
+
"--skip-git-repo-check",
|
|
3625
|
+
"--json",
|
|
3626
|
+
"--output-last-message",
|
|
3627
|
+
"-o",
|
|
3628
|
+
"--output-schema",
|
|
3629
|
+
"--dangerously-bypass-approvals-and-sandbox"
|
|
3630
|
+
]);
|
|
3574
3631
|
function assertStringOption(value, label) {
|
|
3575
3632
|
if (value !== undefined && typeof value !== "string")
|
|
3576
3633
|
throw new Error(`${label} must be a string`);
|
|
@@ -3601,6 +3658,14 @@ function assertSupportedAgentOptions(target) {
|
|
|
3601
3658
|
}
|
|
3602
3659
|
if (target.provider === "codex" && target.agent !== undefined)
|
|
3603
3660
|
throw new Error("codex.agent is not supported");
|
|
3661
|
+
if (target.provider === "codewith" && target.agent !== undefined) {
|
|
3662
|
+
throw new Error("codewith.agent is not supported by the durable background-agent adapter");
|
|
3663
|
+
}
|
|
3664
|
+
if (target.provider === "codewith") {
|
|
3665
|
+
const unsafe = target.extraArgs?.find((arg) => UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS2.has(arg));
|
|
3666
|
+
if (unsafe)
|
|
3667
|
+
throw new Error(`codewith.extraArgs cannot include ${unsafe}; durable agent steps use codewith agent start, not exec/ephemeral flags`);
|
|
3668
|
+
}
|
|
3604
3669
|
if (["codewith", "codex"].includes(target.provider)) {
|
|
3605
3670
|
if (target.permissionMode && !["default", "bypass"].includes(target.permissionMode)) {
|
|
3606
3671
|
throw new Error(`${target.provider}.permissionMode supports only default or bypass`);
|
|
@@ -3684,18 +3749,18 @@ function agentArgs(target) {
|
|
|
3684
3749
|
args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : []);
|
|
3685
3750
|
if (target.variant)
|
|
3686
3751
|
args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
|
|
3687
|
-
args.push("--ask-for-approval", "never", "
|
|
3688
|
-
if (isolation === "safe")
|
|
3689
|
-
args.push("--ignore-rules");
|
|
3752
|
+
args.push("--ask-for-approval", "never", "--sandbox", codewithLikeSandbox(target));
|
|
3690
3753
|
if (target.cwd)
|
|
3691
3754
|
args.push("--cd", target.cwd);
|
|
3692
3755
|
for (const dir of target.addDirs ?? [])
|
|
3693
3756
|
args.push("--add-dir", dir);
|
|
3694
3757
|
if (target.model)
|
|
3695
3758
|
args.push("--model", target.model);
|
|
3696
|
-
if (target.agent)
|
|
3697
|
-
args.push("--agent", target.agent);
|
|
3698
3759
|
args.push(...target.extraArgs ?? []);
|
|
3760
|
+
args.push("agent", "start");
|
|
3761
|
+
if (target.cwd)
|
|
3762
|
+
args.push("--cwd", target.cwd);
|
|
3763
|
+
args.push(target.prompt);
|
|
3699
3764
|
return args;
|
|
3700
3765
|
case "codex":
|
|
3701
3766
|
if (target.variant)
|
|
@@ -3771,8 +3836,9 @@ function commandSpec(target) {
|
|
|
3771
3836
|
accountTool: agentTarget.account?.tool ?? accountToolForProvider(agentTarget.provider),
|
|
3772
3837
|
nativeAuthProfile: agentTarget.authProfile ? { provider: agentTarget.provider, profile: agentTarget.authProfile } : undefined,
|
|
3773
3838
|
preflightAnyOf: agentTarget.provider === "cursor" ? ["agent"] : undefined,
|
|
3774
|
-
stdin: agentTarget.prompt,
|
|
3775
|
-
allowlist: agentTarget.allowlist
|
|
3839
|
+
stdin: agentTarget.provider === "codewith" ? undefined : agentTarget.prompt,
|
|
3840
|
+
allowlist: agentTarget.allowlist,
|
|
3841
|
+
codewithDurableAgent: agentTarget.provider === "codewith" ? { target: agentTarget } : undefined
|
|
3776
3842
|
};
|
|
3777
3843
|
}
|
|
3778
3844
|
function executionEnv(spec, metadata, opts) {
|
|
@@ -3892,6 +3958,238 @@ function preflightNativeAuthProfile(spec, env) {
|
|
|
3892
3958
|
throw new Error(`codewith auth profile not found: ${spec.nativeAuthProfile.profile}`);
|
|
3893
3959
|
}
|
|
3894
3960
|
}
|
|
3961
|
+
function codewithAgentStartArgs(target, idempotencyKey) {
|
|
3962
|
+
const args = agentArgs(target);
|
|
3963
|
+
const startIndex = args.findIndex((arg, index) => arg === "start" && args[index - 1] === "agent");
|
|
3964
|
+
if (startIndex === -1)
|
|
3965
|
+
throw new Error("internal error: codewith durable agent args missing agent start");
|
|
3966
|
+
args.splice(startIndex + 1, 0, "--idempotency-key", idempotencyKey);
|
|
3967
|
+
return args;
|
|
3968
|
+
}
|
|
3969
|
+
function codewithAgentControlArgs(target, command, agentId) {
|
|
3970
|
+
return [
|
|
3971
|
+
...target.authProfile ? ["--auth-profile", target.authProfile] : [],
|
|
3972
|
+
"agent",
|
|
3973
|
+
command,
|
|
3974
|
+
...command === "logs" ? ["--limit", "20"] : [],
|
|
3975
|
+
agentId
|
|
3976
|
+
];
|
|
3977
|
+
}
|
|
3978
|
+
function parseJsonOutput(stdout, label) {
|
|
3979
|
+
try {
|
|
3980
|
+
const value = JSON.parse(stdout || "{}");
|
|
3981
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3982
|
+
throw new Error("not an object");
|
|
3983
|
+
return value;
|
|
3984
|
+
} catch (error) {
|
|
3985
|
+
throw new Error(`${label} did not return JSON${error instanceof Error ? `: ${error.message}` : ""}`);
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
function recordField(value, key) {
|
|
3989
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
3990
|
+
return;
|
|
3991
|
+
const field = value[key];
|
|
3992
|
+
return field && typeof field === "object" && !Array.isArray(field) ? field : undefined;
|
|
3993
|
+
}
|
|
3994
|
+
function stringField(value, key) {
|
|
3995
|
+
const field = value?.[key];
|
|
3996
|
+
return typeof field === "string" ? field : undefined;
|
|
3997
|
+
}
|
|
3998
|
+
function numberField(value, key) {
|
|
3999
|
+
const field = value?.[key];
|
|
4000
|
+
return typeof field === "number" && Number.isFinite(field) ? field : undefined;
|
|
4001
|
+
}
|
|
4002
|
+
function codewithAgentStatus(readJson) {
|
|
4003
|
+
return stringField(recordField(readJson, "agent"), "status") ?? stringField(recordField(readJson, "statusSnapshot"), "status");
|
|
4004
|
+
}
|
|
4005
|
+
function codewithAgentEvidence(startJson, readJson, logsJson) {
|
|
4006
|
+
const agent = recordField(readJson, "agent") ?? recordField(startJson, "agent");
|
|
4007
|
+
const statusSnapshot = recordField(readJson, "statusSnapshot");
|
|
4008
|
+
const events = Array.isArray(logsJson?.data) ? logsJson.data.filter((event) => Boolean(event) && typeof event === "object" && !Array.isArray(event)).map((event) => ({
|
|
4009
|
+
seq: numberField(event, "seq"),
|
|
4010
|
+
eventType: stringField(event, "eventType"),
|
|
4011
|
+
createdAt: numberField(event, "createdAt")
|
|
4012
|
+
})) : undefined;
|
|
4013
|
+
return JSON.stringify({
|
|
4014
|
+
codewithAgent: {
|
|
4015
|
+
agentId: stringField(agent, "agentId"),
|
|
4016
|
+
status: stringField(agent, "status"),
|
|
4017
|
+
desiredState: stringField(agent, "desiredState"),
|
|
4018
|
+
statusReason: stringField(agent, "statusReason"),
|
|
4019
|
+
threadId: stringField(agent, "threadId"),
|
|
4020
|
+
rolloutPath: stringField(agent, "rolloutPath"),
|
|
4021
|
+
pid: numberField(agent, "pid"),
|
|
4022
|
+
exitCode: numberField(agent, "exitCode"),
|
|
4023
|
+
created: typeof startJson.created === "boolean" ? startJson.created : undefined
|
|
4024
|
+
},
|
|
4025
|
+
statusSnapshot: statusSnapshot ? {
|
|
4026
|
+
seq: numberField(statusSnapshot, "seq"),
|
|
4027
|
+
status: stringField(statusSnapshot, "status"),
|
|
4028
|
+
summary: stringField(statusSnapshot, "summary"),
|
|
4029
|
+
pendingInteractionCount: numberField(statusSnapshot, "pendingInteractionCount"),
|
|
4030
|
+
lastEventSeq: numberField(statusSnapshot, "lastEventSeq")
|
|
4031
|
+
} : undefined,
|
|
4032
|
+
events
|
|
4033
|
+
}, null, 2);
|
|
4034
|
+
}
|
|
4035
|
+
function sleep(ms) {
|
|
4036
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4037
|
+
}
|
|
4038
|
+
async function executeCodewithDurableAgent(spec, metadata, opts, env, startedAt) {
|
|
4039
|
+
const target = spec.codewithDurableAgent?.target;
|
|
4040
|
+
if (!target)
|
|
4041
|
+
throw new Error("internal error: missing codewith durable target");
|
|
4042
|
+
const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
4043
|
+
const idempotencyKey = codewithAgentIdempotencyKey(metadata);
|
|
4044
|
+
const start = spawnSync2(spec.command, codewithAgentStartArgs(target, idempotencyKey), {
|
|
4045
|
+
cwd: spec.cwd,
|
|
4046
|
+
env,
|
|
4047
|
+
encoding: "utf8",
|
|
4048
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4049
|
+
timeout: 30000
|
|
4050
|
+
});
|
|
4051
|
+
let stderr = start.stderr || "";
|
|
4052
|
+
if (start.error || (start.status ?? 1) !== 0) {
|
|
4053
|
+
const finishedAt = nowIso();
|
|
4054
|
+
return {
|
|
4055
|
+
status: "failed",
|
|
4056
|
+
exitCode: start.status ?? undefined,
|
|
4057
|
+
stdout: start.stdout || "",
|
|
4058
|
+
stderr,
|
|
4059
|
+
error: start.error?.message ?? `codewith agent start exited with code ${start.status ?? "unknown"}`,
|
|
4060
|
+
startedAt,
|
|
4061
|
+
finishedAt,
|
|
4062
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4063
|
+
};
|
|
4064
|
+
}
|
|
4065
|
+
let startJson;
|
|
4066
|
+
try {
|
|
4067
|
+
startJson = parseJsonOutput(start.stdout || "{}", "codewith agent start");
|
|
4068
|
+
} catch (error) {
|
|
4069
|
+
const finishedAt = nowIso();
|
|
4070
|
+
return {
|
|
4071
|
+
status: "failed",
|
|
4072
|
+
stdout: start.stdout || "",
|
|
4073
|
+
stderr,
|
|
4074
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4075
|
+
startedAt,
|
|
4076
|
+
finishedAt,
|
|
4077
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4078
|
+
};
|
|
4079
|
+
}
|
|
4080
|
+
const agentId = stringField(recordField(startJson, "agent"), "agentId");
|
|
4081
|
+
if (!agentId) {
|
|
4082
|
+
const finishedAt = nowIso();
|
|
4083
|
+
return {
|
|
4084
|
+
status: "failed",
|
|
4085
|
+
stdout: start.stdout || "",
|
|
4086
|
+
stderr,
|
|
4087
|
+
error: "codewith agent start did not return agent.agentId",
|
|
4088
|
+
startedAt,
|
|
4089
|
+
finishedAt,
|
|
4090
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4091
|
+
};
|
|
4092
|
+
}
|
|
4093
|
+
const pollMs = Math.max(100, Number(opts.env?.LOOPS_CODEWITH_AGENT_POLL_MS ?? process.env.LOOPS_CODEWITH_AGENT_POLL_MS ?? 2000) || 2000);
|
|
4094
|
+
let lastReadJson = startJson;
|
|
4095
|
+
let lastLogsJson;
|
|
4096
|
+
let stdout = "";
|
|
4097
|
+
while (true) {
|
|
4098
|
+
if (opts.signal?.aborted) {
|
|
4099
|
+
spawnSync2(spec.command, codewithAgentControlArgs(target, "stop", agentId), { cwd: spec.cwd, env, stdio: "ignore", timeout: 15000 });
|
|
4100
|
+
const finishedAt = nowIso();
|
|
4101
|
+
return {
|
|
4102
|
+
status: "failed",
|
|
4103
|
+
stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
|
|
4104
|
+
stderr,
|
|
4105
|
+
error: "cancelled",
|
|
4106
|
+
startedAt,
|
|
4107
|
+
finishedAt,
|
|
4108
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4109
|
+
};
|
|
4110
|
+
}
|
|
4111
|
+
const read = spawnSync2(spec.command, codewithAgentControlArgs(target, "read", agentId), {
|
|
4112
|
+
cwd: spec.cwd,
|
|
4113
|
+
env,
|
|
4114
|
+
encoding: "utf8",
|
|
4115
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4116
|
+
timeout: 30000
|
|
4117
|
+
});
|
|
4118
|
+
stderr = appendBoundedText(stderr, read.stderr || "", maxOutputBytes);
|
|
4119
|
+
if (read.error || (read.status ?? 1) !== 0) {
|
|
4120
|
+
const finishedAt = nowIso();
|
|
4121
|
+
return {
|
|
4122
|
+
status: "failed",
|
|
4123
|
+
exitCode: read.status ?? undefined,
|
|
4124
|
+
stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
|
|
4125
|
+
stderr,
|
|
4126
|
+
error: read.error?.message ?? `codewith agent read exited with code ${read.status ?? "unknown"}`,
|
|
4127
|
+
startedAt,
|
|
4128
|
+
finishedAt,
|
|
4129
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4130
|
+
};
|
|
4131
|
+
}
|
|
4132
|
+
try {
|
|
4133
|
+
lastReadJson = parseJsonOutput(read.stdout || "{}", "codewith agent read");
|
|
4134
|
+
} catch (error) {
|
|
4135
|
+
const finishedAt = nowIso();
|
|
4136
|
+
return {
|
|
4137
|
+
status: "failed",
|
|
4138
|
+
stdout: appendBoundedText(stdout, read.stdout || "", maxOutputBytes),
|
|
4139
|
+
stderr,
|
|
4140
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4141
|
+
startedAt,
|
|
4142
|
+
finishedAt,
|
|
4143
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4144
|
+
};
|
|
4145
|
+
}
|
|
4146
|
+
const status = codewithAgentStatus(lastReadJson);
|
|
4147
|
+
if (status === "completed" || status === "failed" || status === "cancelled") {
|
|
4148
|
+
const logs = spawnSync2(spec.command, codewithAgentControlArgs(target, "logs", agentId), {
|
|
4149
|
+
cwd: spec.cwd,
|
|
4150
|
+
env,
|
|
4151
|
+
encoding: "utf8",
|
|
4152
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4153
|
+
timeout: 30000
|
|
4154
|
+
});
|
|
4155
|
+
if (!logs.error && (logs.status ?? 1) === 0) {
|
|
4156
|
+
try {
|
|
4157
|
+
lastLogsJson = parseJsonOutput(logs.stdout || "{}", "codewith agent logs");
|
|
4158
|
+
} catch {
|
|
4159
|
+
lastLogsJson = undefined;
|
|
4160
|
+
}
|
|
4161
|
+
} else {
|
|
4162
|
+
stderr = appendBoundedText(stderr, logs.stderr || logs.error?.message || "", maxOutputBytes);
|
|
4163
|
+
}
|
|
4164
|
+
const finishedAt = nowIso();
|
|
4165
|
+
const resultStatus = status === "completed" ? "succeeded" : "failed";
|
|
4166
|
+
return {
|
|
4167
|
+
status: resultStatus,
|
|
4168
|
+
exitCode: resultStatus === "succeeded" ? 0 : 1,
|
|
4169
|
+
stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
|
|
4170
|
+
stderr,
|
|
4171
|
+
error: resultStatus === "succeeded" ? undefined : stringField(recordField(lastReadJson, "agent"), "statusReason") ?? `codewith agent ${status}`,
|
|
4172
|
+
startedAt,
|
|
4173
|
+
finishedAt,
|
|
4174
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4175
|
+
};
|
|
4176
|
+
}
|
|
4177
|
+
if (typeof spec.timeoutMs === "number" && new Date(nowIso()).getTime() - new Date(startedAt).getTime() >= spec.timeoutMs) {
|
|
4178
|
+
spawnSync2(spec.command, codewithAgentControlArgs(target, "stop", agentId), { cwd: spec.cwd, env, stdio: "ignore", timeout: 15000 });
|
|
4179
|
+
const finishedAt = nowIso();
|
|
4180
|
+
return {
|
|
4181
|
+
status: "timed_out",
|
|
4182
|
+
stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
|
|
4183
|
+
stderr,
|
|
4184
|
+
error: `timed out after ${spec.timeoutMs}ms`,
|
|
4185
|
+
startedAt,
|
|
4186
|
+
finishedAt,
|
|
4187
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4188
|
+
};
|
|
4189
|
+
}
|
|
4190
|
+
await sleep(pollMs);
|
|
4191
|
+
}
|
|
4192
|
+
}
|
|
3895
4193
|
function preflightRemoteSpec(spec, machine, metadata, opts) {
|
|
3896
4194
|
const plan = (opts.machineCommandResolver ?? resolveMachineCommand)(machine.id, "bash -s");
|
|
3897
4195
|
const result = spawnSync2(plan.command, plan.args, {
|
|
@@ -4063,6 +4361,19 @@ function preflightTarget(target, metadata = {}, opts = {}) {
|
|
|
4063
4361
|
async function executeTarget(target, metadata = {}, opts = {}) {
|
|
4064
4362
|
const spec = commandSpec(target);
|
|
4065
4363
|
const machine = resolvedMachine(opts);
|
|
4364
|
+
if (machine && !machine.local && spec.codewithDurableAgent) {
|
|
4365
|
+
const startedAt2 = nowIso();
|
|
4366
|
+
const finishedAt2 = nowIso();
|
|
4367
|
+
return {
|
|
4368
|
+
status: "failed",
|
|
4369
|
+
stdout: "",
|
|
4370
|
+
stderr: "",
|
|
4371
|
+
error: "remote Codewith durable background-agent steps require remote status polling support; run this Codewith step locally or add remote durable readback before dispatch",
|
|
4372
|
+
startedAt: startedAt2,
|
|
4373
|
+
finishedAt: finishedAt2,
|
|
4374
|
+
durationMs: new Date(finishedAt2).getTime() - new Date(startedAt2).getTime()
|
|
4375
|
+
};
|
|
4376
|
+
}
|
|
4066
4377
|
if (machine && !machine.local)
|
|
4067
4378
|
return executeRemoteSpec(spec, machine, metadata, opts);
|
|
4068
4379
|
const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
@@ -4109,6 +4420,9 @@ async function executeTarget(target, metadata = {}, opts = {}) {
|
|
|
4109
4420
|
durationMs: 0
|
|
4110
4421
|
};
|
|
4111
4422
|
}
|
|
4423
|
+
if (spec.codewithDurableAgent) {
|
|
4424
|
+
return executeCodewithDurableAgent(spec, metadata, opts, env, startedAt);
|
|
4425
|
+
}
|
|
4112
4426
|
const child = spawn(spec.command, spec.args, {
|
|
4113
4427
|
cwd: spec.cwd,
|
|
4114
4428
|
env,
|
|
@@ -5320,7 +5634,7 @@ function realSleep(ms) {
|
|
|
5320
5634
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
5321
5635
|
}
|
|
5322
5636
|
async function runLoop(opts) {
|
|
5323
|
-
const
|
|
5637
|
+
const sleep2 = opts.sleep ?? realSleep;
|
|
5324
5638
|
const sliceMs = opts.sliceMs ?? 200;
|
|
5325
5639
|
while (!opts.shouldStop()) {
|
|
5326
5640
|
try {
|
|
@@ -5331,7 +5645,7 @@ async function runLoop(opts) {
|
|
|
5331
5645
|
let waited = 0;
|
|
5332
5646
|
while (waited < opts.intervalMs && !opts.shouldStop()) {
|
|
5333
5647
|
const chunk = Math.min(sliceMs, opts.intervalMs - waited);
|
|
5334
|
-
await
|
|
5648
|
+
await sleep2(chunk);
|
|
5335
5649
|
waited += chunk;
|
|
5336
5650
|
}
|
|
5337
5651
|
}
|
|
@@ -5413,7 +5727,7 @@ async function stopDaemon(opts = {}) {
|
|
|
5413
5727
|
} finally {
|
|
5414
5728
|
store.close();
|
|
5415
5729
|
}
|
|
5416
|
-
const
|
|
5730
|
+
const sleep2 = opts.sleep ?? realSleep;
|
|
5417
5731
|
try {
|
|
5418
5732
|
process.kill(state.pid, "SIGTERM");
|
|
5419
5733
|
} catch {
|
|
@@ -5422,7 +5736,7 @@ async function stopDaemon(opts = {}) {
|
|
|
5422
5736
|
}
|
|
5423
5737
|
const steps = Math.max(1, Math.ceil((opts.timeoutMs ?? 6000) / 100));
|
|
5424
5738
|
for (let i = 0;i < steps; i++) {
|
|
5425
|
-
await
|
|
5739
|
+
await sleep2(100);
|
|
5426
5740
|
if (!isAlive(state.pid)) {
|
|
5427
5741
|
removePid(path);
|
|
5428
5742
|
return { wasRunning: true, stopped: true, forced: false, pid: state.pid };
|
|
@@ -5431,7 +5745,7 @@ async function stopDaemon(opts = {}) {
|
|
|
5431
5745
|
try {
|
|
5432
5746
|
process.kill(state.pid, "SIGKILL");
|
|
5433
5747
|
} catch {}
|
|
5434
|
-
await
|
|
5748
|
+
await sleep2(150);
|
|
5435
5749
|
removePid(path);
|
|
5436
5750
|
return { wasRunning: true, stopped: !isAlive(state.pid), forced: true, pid: state.pid };
|
|
5437
5751
|
}
|
|
@@ -5597,10 +5911,10 @@ async function startDaemon(opts) {
|
|
|
5597
5911
|
stdio: ["ignore", out, out]
|
|
5598
5912
|
});
|
|
5599
5913
|
child.unref();
|
|
5600
|
-
const
|
|
5914
|
+
const sleep2 = opts.sleep ?? realSleep;
|
|
5601
5915
|
const deadline = Math.max(1, Math.ceil((opts.waitMs ?? 4000) / 100));
|
|
5602
5916
|
for (let i = 0;i < deadline; i++) {
|
|
5603
|
-
await
|
|
5917
|
+
await sleep2(100);
|
|
5604
5918
|
const current = isDaemonRunning();
|
|
5605
5919
|
if (current.running)
|
|
5606
5920
|
return { started: true, alreadyRunning: false, pid: current.pid };
|
|
@@ -5695,7 +6009,7 @@ function enableStartup(result) {
|
|
|
5695
6009
|
// package.json
|
|
5696
6010
|
var package_default = {
|
|
5697
6011
|
name: "@hasna/loops",
|
|
5698
|
-
version: "0.3.
|
|
6012
|
+
version: "0.3.60",
|
|
5699
6013
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
5700
6014
|
type: "module",
|
|
5701
6015
|
main: "dist/index.js",
|