@hasna/loops 0.3.58 → 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 +525 -82
- package/dist/daemon/index.js +394 -16
- package/dist/index.js +387 -9
- package/dist/lib/store.d.ts +10 -0
- package/dist/lib/store.js +90 -1
- package/dist/mcp/index.js +387 -9
- package/dist/sdk/index.js +386 -8
- package/docs/USAGE.md +4 -4
- package/package.json +1 -1
package/dist/cli/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 };
|
|
@@ -1543,6 +1568,70 @@ class Store {
|
|
|
1543
1568
|
throw error;
|
|
1544
1569
|
}
|
|
1545
1570
|
}
|
|
1571
|
+
cloneWorkflowWithoutGoalAndRetargetLoop(idOrName, opts) {
|
|
1572
|
+
const updated = (opts.now ?? new Date).toISOString();
|
|
1573
|
+
this.db.exec("BEGIN IMMEDIATE");
|
|
1574
|
+
try {
|
|
1575
|
+
const current = this.requireUniqueLoop(idOrName);
|
|
1576
|
+
if (current.target.type !== "workflow")
|
|
1577
|
+
throw new Error(`loop is not a workflow loop: ${idOrName}`);
|
|
1578
|
+
if (this.hasRunningRun(current.id))
|
|
1579
|
+
throw new Error(`refusing to retarget running loop: ${current.id}`);
|
|
1580
|
+
if (!current.goal)
|
|
1581
|
+
throw new Error(`workflow loop ${current.name} has no loop-level goal wrapper`);
|
|
1582
|
+
const previousWorkflow = this.requireWorkflow(current.target.workflowId);
|
|
1583
|
+
if (!previousWorkflow.goal)
|
|
1584
|
+
throw new Error(`workflow ${previousWorkflow.name} has no top-level goal wrapper`);
|
|
1585
|
+
const workflow = {
|
|
1586
|
+
id: genId(),
|
|
1587
|
+
name: opts.workflowName,
|
|
1588
|
+
description: previousWorkflow.description,
|
|
1589
|
+
version: previousWorkflow.version,
|
|
1590
|
+
status: "active",
|
|
1591
|
+
steps: previousWorkflow.steps,
|
|
1592
|
+
createdAt: updated,
|
|
1593
|
+
updatedAt: updated
|
|
1594
|
+
};
|
|
1595
|
+
this.db.query(`INSERT INTO workflow_specs (id, name, description, version, status, goal_json, steps_json, created_at, updated_at)
|
|
1596
|
+
VALUES ($id, $name, $description, $version, $status, NULL, $steps, $created, $updated)`).run({
|
|
1597
|
+
$id: workflow.id,
|
|
1598
|
+
$name: workflow.name,
|
|
1599
|
+
$description: workflow.description ?? null,
|
|
1600
|
+
$version: workflow.version,
|
|
1601
|
+
$status: workflow.status,
|
|
1602
|
+
$steps: JSON.stringify(workflow.steps),
|
|
1603
|
+
$created: workflow.createdAt,
|
|
1604
|
+
$updated: workflow.updatedAt
|
|
1605
|
+
});
|
|
1606
|
+
const target = { ...current.target, workflowId: workflow.id };
|
|
1607
|
+
if (opts.workflowTimeoutMs !== undefined)
|
|
1608
|
+
target.timeoutMs = opts.workflowTimeoutMs;
|
|
1609
|
+
const res = this.db.query(`UPDATE loops SET target_json=$target, updated_at=$updated
|
|
1610
|
+
WHERE id=$id
|
|
1611
|
+
AND ($daemonLeaseId IS NULL OR EXISTS (
|
|
1612
|
+
SELECT 1 FROM daemon_lease WHERE id=$daemonLeaseId AND expires_at > $now
|
|
1613
|
+
))`).run({
|
|
1614
|
+
$id: current.id,
|
|
1615
|
+
$target: JSON.stringify(target),
|
|
1616
|
+
$updated: updated,
|
|
1617
|
+
$daemonLeaseId: opts.daemonLeaseId ?? null,
|
|
1618
|
+
$now: updated
|
|
1619
|
+
});
|
|
1620
|
+
if (res.changes !== 1)
|
|
1621
|
+
throw new Error("daemon lease lost");
|
|
1622
|
+
const archivedOld = opts.archiveOld ? this.archiveWorkflowIfUnreferenced(previousWorkflow.id, updated) : undefined;
|
|
1623
|
+
this.db.exec("COMMIT");
|
|
1624
|
+
const loop = this.getLoop(current.id);
|
|
1625
|
+
if (!loop)
|
|
1626
|
+
throw new Error(`loop not found after retarget: ${current.id}`);
|
|
1627
|
+
return { loop, workflow, previousWorkflow, archivedOld };
|
|
1628
|
+
} catch (error) {
|
|
1629
|
+
try {
|
|
1630
|
+
this.db.exec("ROLLBACK");
|
|
1631
|
+
} catch {}
|
|
1632
|
+
throw error;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1546
1635
|
renameLoop(id, name, opts = {}) {
|
|
1547
1636
|
const current = this.getLoop(id);
|
|
1548
1637
|
if (!current)
|
|
@@ -3540,6 +3629,9 @@ function appendBounded(current, chunk, maxBytes) {
|
|
|
3540
3629
|
return `[truncated ${overflow} bytes]
|
|
3541
3630
|
${next.slice(-maxBytes)}`;
|
|
3542
3631
|
}
|
|
3632
|
+
function appendBoundedText(current, chunk, maxBytes) {
|
|
3633
|
+
return appendBounded(current, Buffer.from(chunk, "utf8"), maxBytes);
|
|
3634
|
+
}
|
|
3543
3635
|
function killProcessGroup(pid) {
|
|
3544
3636
|
try {
|
|
3545
3637
|
process.kill(-pid, "SIGTERM");
|
|
@@ -3587,6 +3679,21 @@ function metadataEnv(metadata) {
|
|
|
3587
3679
|
env.LOOPS_GOAL_NODE_KEY = metadata.goalNodeKey;
|
|
3588
3680
|
return env;
|
|
3589
3681
|
}
|
|
3682
|
+
function codewithAgentIdempotencyKey(metadata) {
|
|
3683
|
+
const parts = [
|
|
3684
|
+
"openloops",
|
|
3685
|
+
metadata.workflowRunId ? `workflow-run:${metadata.workflowRunId}` : undefined,
|
|
3686
|
+
metadata.workflowStepId ? `step:${metadata.workflowStepId}` : undefined,
|
|
3687
|
+
metadata.runId ? `loop-run:${metadata.runId}` : undefined,
|
|
3688
|
+
metadata.loopId ? `loop:${metadata.loopId}` : undefined,
|
|
3689
|
+
metadata.scheduledFor ? `scheduled:${metadata.scheduledFor}` : undefined,
|
|
3690
|
+
metadata.goalId ? `goal:${metadata.goalId}` : undefined,
|
|
3691
|
+
metadata.goalNodeKey ? `node:${metadata.goalNodeKey}` : undefined
|
|
3692
|
+
].filter(Boolean);
|
|
3693
|
+
if (parts.length > 1)
|
|
3694
|
+
return parts.join(":");
|
|
3695
|
+
return `openloops:adhoc:${randomBytes2(16).toString("hex")}`;
|
|
3696
|
+
}
|
|
3590
3697
|
function allowlistEnv(allowlist) {
|
|
3591
3698
|
const env = {};
|
|
3592
3699
|
if (allowlist?.tools?.length)
|
|
@@ -3623,6 +3730,20 @@ function codewithLikeSandbox(target) {
|
|
|
3623
3730
|
function configStringValue(value) {
|
|
3624
3731
|
return JSON.stringify(value);
|
|
3625
3732
|
}
|
|
3733
|
+
var UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS2 = new Set([
|
|
3734
|
+
"e",
|
|
3735
|
+
"exec",
|
|
3736
|
+
"agent",
|
|
3737
|
+
"start",
|
|
3738
|
+
"--ephemeral",
|
|
3739
|
+
"--ignore-rules",
|
|
3740
|
+
"--skip-git-repo-check",
|
|
3741
|
+
"--json",
|
|
3742
|
+
"--output-last-message",
|
|
3743
|
+
"-o",
|
|
3744
|
+
"--output-schema",
|
|
3745
|
+
"--dangerously-bypass-approvals-and-sandbox"
|
|
3746
|
+
]);
|
|
3626
3747
|
function assertStringOption(value, label) {
|
|
3627
3748
|
if (value !== undefined && typeof value !== "string")
|
|
3628
3749
|
throw new Error(`${label} must be a string`);
|
|
@@ -3653,6 +3774,14 @@ function assertSupportedAgentOptions(target) {
|
|
|
3653
3774
|
}
|
|
3654
3775
|
if (target.provider === "codex" && target.agent !== undefined)
|
|
3655
3776
|
throw new Error("codex.agent is not supported");
|
|
3777
|
+
if (target.provider === "codewith" && target.agent !== undefined) {
|
|
3778
|
+
throw new Error("codewith.agent is not supported by the durable background-agent adapter");
|
|
3779
|
+
}
|
|
3780
|
+
if (target.provider === "codewith") {
|
|
3781
|
+
const unsafe = target.extraArgs?.find((arg) => UNSAFE_CODEWITH_DURABLE_EXTRA_ARGS2.has(arg));
|
|
3782
|
+
if (unsafe)
|
|
3783
|
+
throw new Error(`codewith.extraArgs cannot include ${unsafe}; durable agent steps use codewith agent start, not exec/ephemeral flags`);
|
|
3784
|
+
}
|
|
3656
3785
|
if (["codewith", "codex"].includes(target.provider)) {
|
|
3657
3786
|
if (target.permissionMode && !["default", "bypass"].includes(target.permissionMode)) {
|
|
3658
3787
|
throw new Error(`${target.provider}.permissionMode supports only default or bypass`);
|
|
@@ -3736,18 +3865,18 @@ function agentArgs(target) {
|
|
|
3736
3865
|
args.push(...target.authProfile ? ["--auth-profile", target.authProfile] : []);
|
|
3737
3866
|
if (target.variant)
|
|
3738
3867
|
args.push("-c", `model_reasoning_effort=${configStringValue(target.variant)}`);
|
|
3739
|
-
args.push("--ask-for-approval", "never", "
|
|
3740
|
-
if (isolation === "safe")
|
|
3741
|
-
args.push("--ignore-rules");
|
|
3868
|
+
args.push("--ask-for-approval", "never", "--sandbox", codewithLikeSandbox(target));
|
|
3742
3869
|
if (target.cwd)
|
|
3743
3870
|
args.push("--cd", target.cwd);
|
|
3744
3871
|
for (const dir of target.addDirs ?? [])
|
|
3745
3872
|
args.push("--add-dir", dir);
|
|
3746
3873
|
if (target.model)
|
|
3747
3874
|
args.push("--model", target.model);
|
|
3748
|
-
if (target.agent)
|
|
3749
|
-
args.push("--agent", target.agent);
|
|
3750
3875
|
args.push(...target.extraArgs ?? []);
|
|
3876
|
+
args.push("agent", "start");
|
|
3877
|
+
if (target.cwd)
|
|
3878
|
+
args.push("--cwd", target.cwd);
|
|
3879
|
+
args.push(target.prompt);
|
|
3751
3880
|
return args;
|
|
3752
3881
|
case "codex":
|
|
3753
3882
|
if (target.variant)
|
|
@@ -3823,8 +3952,9 @@ function commandSpec(target) {
|
|
|
3823
3952
|
accountTool: agentTarget.account?.tool ?? accountToolForProvider(agentTarget.provider),
|
|
3824
3953
|
nativeAuthProfile: agentTarget.authProfile ? { provider: agentTarget.provider, profile: agentTarget.authProfile } : undefined,
|
|
3825
3954
|
preflightAnyOf: agentTarget.provider === "cursor" ? ["agent"] : undefined,
|
|
3826
|
-
stdin: agentTarget.prompt,
|
|
3827
|
-
allowlist: agentTarget.allowlist
|
|
3955
|
+
stdin: agentTarget.provider === "codewith" ? undefined : agentTarget.prompt,
|
|
3956
|
+
allowlist: agentTarget.allowlist,
|
|
3957
|
+
codewithDurableAgent: agentTarget.provider === "codewith" ? { target: agentTarget } : undefined
|
|
3828
3958
|
};
|
|
3829
3959
|
}
|
|
3830
3960
|
function executionEnv(spec, metadata, opts) {
|
|
@@ -3944,6 +4074,238 @@ function preflightNativeAuthProfile(spec, env) {
|
|
|
3944
4074
|
throw new Error(`codewith auth profile not found: ${spec.nativeAuthProfile.profile}`);
|
|
3945
4075
|
}
|
|
3946
4076
|
}
|
|
4077
|
+
function codewithAgentStartArgs(target, idempotencyKey) {
|
|
4078
|
+
const args = agentArgs(target);
|
|
4079
|
+
const startIndex = args.findIndex((arg, index) => arg === "start" && args[index - 1] === "agent");
|
|
4080
|
+
if (startIndex === -1)
|
|
4081
|
+
throw new Error("internal error: codewith durable agent args missing agent start");
|
|
4082
|
+
args.splice(startIndex + 1, 0, "--idempotency-key", idempotencyKey);
|
|
4083
|
+
return args;
|
|
4084
|
+
}
|
|
4085
|
+
function codewithAgentControlArgs(target, command, agentId) {
|
|
4086
|
+
return [
|
|
4087
|
+
...target.authProfile ? ["--auth-profile", target.authProfile] : [],
|
|
4088
|
+
"agent",
|
|
4089
|
+
command,
|
|
4090
|
+
...command === "logs" ? ["--limit", "20"] : [],
|
|
4091
|
+
agentId
|
|
4092
|
+
];
|
|
4093
|
+
}
|
|
4094
|
+
function parseJsonOutput(stdout, label) {
|
|
4095
|
+
try {
|
|
4096
|
+
const value = JSON.parse(stdout || "{}");
|
|
4097
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4098
|
+
throw new Error("not an object");
|
|
4099
|
+
return value;
|
|
4100
|
+
} catch (error) {
|
|
4101
|
+
throw new Error(`${label} did not return JSON${error instanceof Error ? `: ${error.message}` : ""}`);
|
|
4102
|
+
}
|
|
4103
|
+
}
|
|
4104
|
+
function recordField(value, key) {
|
|
4105
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
4106
|
+
return;
|
|
4107
|
+
const field = value[key];
|
|
4108
|
+
return field && typeof field === "object" && !Array.isArray(field) ? field : undefined;
|
|
4109
|
+
}
|
|
4110
|
+
function stringField(value, key) {
|
|
4111
|
+
const field = value?.[key];
|
|
4112
|
+
return typeof field === "string" ? field : undefined;
|
|
4113
|
+
}
|
|
4114
|
+
function numberField(value, key) {
|
|
4115
|
+
const field = value?.[key];
|
|
4116
|
+
return typeof field === "number" && Number.isFinite(field) ? field : undefined;
|
|
4117
|
+
}
|
|
4118
|
+
function codewithAgentStatus(readJson) {
|
|
4119
|
+
return stringField(recordField(readJson, "agent"), "status") ?? stringField(recordField(readJson, "statusSnapshot"), "status");
|
|
4120
|
+
}
|
|
4121
|
+
function codewithAgentEvidence(startJson, readJson, logsJson) {
|
|
4122
|
+
const agent = recordField(readJson, "agent") ?? recordField(startJson, "agent");
|
|
4123
|
+
const statusSnapshot = recordField(readJson, "statusSnapshot");
|
|
4124
|
+
const events = Array.isArray(logsJson?.data) ? logsJson.data.filter((event) => Boolean(event) && typeof event === "object" && !Array.isArray(event)).map((event) => ({
|
|
4125
|
+
seq: numberField(event, "seq"),
|
|
4126
|
+
eventType: stringField(event, "eventType"),
|
|
4127
|
+
createdAt: numberField(event, "createdAt")
|
|
4128
|
+
})) : undefined;
|
|
4129
|
+
return JSON.stringify({
|
|
4130
|
+
codewithAgent: {
|
|
4131
|
+
agentId: stringField(agent, "agentId"),
|
|
4132
|
+
status: stringField(agent, "status"),
|
|
4133
|
+
desiredState: stringField(agent, "desiredState"),
|
|
4134
|
+
statusReason: stringField(agent, "statusReason"),
|
|
4135
|
+
threadId: stringField(agent, "threadId"),
|
|
4136
|
+
rolloutPath: stringField(agent, "rolloutPath"),
|
|
4137
|
+
pid: numberField(agent, "pid"),
|
|
4138
|
+
exitCode: numberField(agent, "exitCode"),
|
|
4139
|
+
created: typeof startJson.created === "boolean" ? startJson.created : undefined
|
|
4140
|
+
},
|
|
4141
|
+
statusSnapshot: statusSnapshot ? {
|
|
4142
|
+
seq: numberField(statusSnapshot, "seq"),
|
|
4143
|
+
status: stringField(statusSnapshot, "status"),
|
|
4144
|
+
summary: stringField(statusSnapshot, "summary"),
|
|
4145
|
+
pendingInteractionCount: numberField(statusSnapshot, "pendingInteractionCount"),
|
|
4146
|
+
lastEventSeq: numberField(statusSnapshot, "lastEventSeq")
|
|
4147
|
+
} : undefined,
|
|
4148
|
+
events
|
|
4149
|
+
}, null, 2);
|
|
4150
|
+
}
|
|
4151
|
+
function sleep(ms) {
|
|
4152
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
4153
|
+
}
|
|
4154
|
+
async function executeCodewithDurableAgent(spec, metadata, opts, env, startedAt) {
|
|
4155
|
+
const target = spec.codewithDurableAgent?.target;
|
|
4156
|
+
if (!target)
|
|
4157
|
+
throw new Error("internal error: missing codewith durable target");
|
|
4158
|
+
const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
4159
|
+
const idempotencyKey = codewithAgentIdempotencyKey(metadata);
|
|
4160
|
+
const start = spawnSync2(spec.command, codewithAgentStartArgs(target, idempotencyKey), {
|
|
4161
|
+
cwd: spec.cwd,
|
|
4162
|
+
env,
|
|
4163
|
+
encoding: "utf8",
|
|
4164
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4165
|
+
timeout: 30000
|
|
4166
|
+
});
|
|
4167
|
+
let stderr = start.stderr || "";
|
|
4168
|
+
if (start.error || (start.status ?? 1) !== 0) {
|
|
4169
|
+
const finishedAt = nowIso();
|
|
4170
|
+
return {
|
|
4171
|
+
status: "failed",
|
|
4172
|
+
exitCode: start.status ?? undefined,
|
|
4173
|
+
stdout: start.stdout || "",
|
|
4174
|
+
stderr,
|
|
4175
|
+
error: start.error?.message ?? `codewith agent start exited with code ${start.status ?? "unknown"}`,
|
|
4176
|
+
startedAt,
|
|
4177
|
+
finishedAt,
|
|
4178
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4179
|
+
};
|
|
4180
|
+
}
|
|
4181
|
+
let startJson;
|
|
4182
|
+
try {
|
|
4183
|
+
startJson = parseJsonOutput(start.stdout || "{}", "codewith agent start");
|
|
4184
|
+
} catch (error) {
|
|
4185
|
+
const finishedAt = nowIso();
|
|
4186
|
+
return {
|
|
4187
|
+
status: "failed",
|
|
4188
|
+
stdout: start.stdout || "",
|
|
4189
|
+
stderr,
|
|
4190
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4191
|
+
startedAt,
|
|
4192
|
+
finishedAt,
|
|
4193
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4194
|
+
};
|
|
4195
|
+
}
|
|
4196
|
+
const agentId = stringField(recordField(startJson, "agent"), "agentId");
|
|
4197
|
+
if (!agentId) {
|
|
4198
|
+
const finishedAt = nowIso();
|
|
4199
|
+
return {
|
|
4200
|
+
status: "failed",
|
|
4201
|
+
stdout: start.stdout || "",
|
|
4202
|
+
stderr,
|
|
4203
|
+
error: "codewith agent start did not return agent.agentId",
|
|
4204
|
+
startedAt,
|
|
4205
|
+
finishedAt,
|
|
4206
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4207
|
+
};
|
|
4208
|
+
}
|
|
4209
|
+
const pollMs = Math.max(100, Number(opts.env?.LOOPS_CODEWITH_AGENT_POLL_MS ?? process.env.LOOPS_CODEWITH_AGENT_POLL_MS ?? 2000) || 2000);
|
|
4210
|
+
let lastReadJson = startJson;
|
|
4211
|
+
let lastLogsJson;
|
|
4212
|
+
let stdout = "";
|
|
4213
|
+
while (true) {
|
|
4214
|
+
if (opts.signal?.aborted) {
|
|
4215
|
+
spawnSync2(spec.command, codewithAgentControlArgs(target, "stop", agentId), { cwd: spec.cwd, env, stdio: "ignore", timeout: 15000 });
|
|
4216
|
+
const finishedAt = nowIso();
|
|
4217
|
+
return {
|
|
4218
|
+
status: "failed",
|
|
4219
|
+
stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
|
|
4220
|
+
stderr,
|
|
4221
|
+
error: "cancelled",
|
|
4222
|
+
startedAt,
|
|
4223
|
+
finishedAt,
|
|
4224
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4225
|
+
};
|
|
4226
|
+
}
|
|
4227
|
+
const read = spawnSync2(spec.command, codewithAgentControlArgs(target, "read", agentId), {
|
|
4228
|
+
cwd: spec.cwd,
|
|
4229
|
+
env,
|
|
4230
|
+
encoding: "utf8",
|
|
4231
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4232
|
+
timeout: 30000
|
|
4233
|
+
});
|
|
4234
|
+
stderr = appendBoundedText(stderr, read.stderr || "", maxOutputBytes);
|
|
4235
|
+
if (read.error || (read.status ?? 1) !== 0) {
|
|
4236
|
+
const finishedAt = nowIso();
|
|
4237
|
+
return {
|
|
4238
|
+
status: "failed",
|
|
4239
|
+
exitCode: read.status ?? undefined,
|
|
4240
|
+
stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
|
|
4241
|
+
stderr,
|
|
4242
|
+
error: read.error?.message ?? `codewith agent read exited with code ${read.status ?? "unknown"}`,
|
|
4243
|
+
startedAt,
|
|
4244
|
+
finishedAt,
|
|
4245
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4246
|
+
};
|
|
4247
|
+
}
|
|
4248
|
+
try {
|
|
4249
|
+
lastReadJson = parseJsonOutput(read.stdout || "{}", "codewith agent read");
|
|
4250
|
+
} catch (error) {
|
|
4251
|
+
const finishedAt = nowIso();
|
|
4252
|
+
return {
|
|
4253
|
+
status: "failed",
|
|
4254
|
+
stdout: appendBoundedText(stdout, read.stdout || "", maxOutputBytes),
|
|
4255
|
+
stderr,
|
|
4256
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4257
|
+
startedAt,
|
|
4258
|
+
finishedAt,
|
|
4259
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4260
|
+
};
|
|
4261
|
+
}
|
|
4262
|
+
const status = codewithAgentStatus(lastReadJson);
|
|
4263
|
+
if (status === "completed" || status === "failed" || status === "cancelled") {
|
|
4264
|
+
const logs = spawnSync2(spec.command, codewithAgentControlArgs(target, "logs", agentId), {
|
|
4265
|
+
cwd: spec.cwd,
|
|
4266
|
+
env,
|
|
4267
|
+
encoding: "utf8",
|
|
4268
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
4269
|
+
timeout: 30000
|
|
4270
|
+
});
|
|
4271
|
+
if (!logs.error && (logs.status ?? 1) === 0) {
|
|
4272
|
+
try {
|
|
4273
|
+
lastLogsJson = parseJsonOutput(logs.stdout || "{}", "codewith agent logs");
|
|
4274
|
+
} catch {
|
|
4275
|
+
lastLogsJson = undefined;
|
|
4276
|
+
}
|
|
4277
|
+
} else {
|
|
4278
|
+
stderr = appendBoundedText(stderr, logs.stderr || logs.error?.message || "", maxOutputBytes);
|
|
4279
|
+
}
|
|
4280
|
+
const finishedAt = nowIso();
|
|
4281
|
+
const resultStatus = status === "completed" ? "succeeded" : "failed";
|
|
4282
|
+
return {
|
|
4283
|
+
status: resultStatus,
|
|
4284
|
+
exitCode: resultStatus === "succeeded" ? 0 : 1,
|
|
4285
|
+
stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
|
|
4286
|
+
stderr,
|
|
4287
|
+
error: resultStatus === "succeeded" ? undefined : stringField(recordField(lastReadJson, "agent"), "statusReason") ?? `codewith agent ${status}`,
|
|
4288
|
+
startedAt,
|
|
4289
|
+
finishedAt,
|
|
4290
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4291
|
+
};
|
|
4292
|
+
}
|
|
4293
|
+
if (typeof spec.timeoutMs === "number" && new Date(nowIso()).getTime() - new Date(startedAt).getTime() >= spec.timeoutMs) {
|
|
4294
|
+
spawnSync2(spec.command, codewithAgentControlArgs(target, "stop", agentId), { cwd: spec.cwd, env, stdio: "ignore", timeout: 15000 });
|
|
4295
|
+
const finishedAt = nowIso();
|
|
4296
|
+
return {
|
|
4297
|
+
status: "timed_out",
|
|
4298
|
+
stdout: appendBoundedText(stdout, codewithAgentEvidence(startJson, lastReadJson, lastLogsJson), maxOutputBytes),
|
|
4299
|
+
stderr,
|
|
4300
|
+
error: `timed out after ${spec.timeoutMs}ms`,
|
|
4301
|
+
startedAt,
|
|
4302
|
+
finishedAt,
|
|
4303
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
4304
|
+
};
|
|
4305
|
+
}
|
|
4306
|
+
await sleep(pollMs);
|
|
4307
|
+
}
|
|
4308
|
+
}
|
|
3947
4309
|
function preflightRemoteSpec(spec, machine, metadata, opts) {
|
|
3948
4310
|
const plan = (opts.machineCommandResolver ?? resolveMachineCommand)(machine.id, "bash -s");
|
|
3949
4311
|
const result = spawnSync2(plan.command, plan.args, {
|
|
@@ -4115,6 +4477,19 @@ function preflightTarget(target, metadata = {}, opts = {}) {
|
|
|
4115
4477
|
async function executeTarget(target, metadata = {}, opts = {}) {
|
|
4116
4478
|
const spec = commandSpec(target);
|
|
4117
4479
|
const machine = resolvedMachine(opts);
|
|
4480
|
+
if (machine && !machine.local && spec.codewithDurableAgent) {
|
|
4481
|
+
const startedAt2 = nowIso();
|
|
4482
|
+
const finishedAt2 = nowIso();
|
|
4483
|
+
return {
|
|
4484
|
+
status: "failed",
|
|
4485
|
+
stdout: "",
|
|
4486
|
+
stderr: "",
|
|
4487
|
+
error: "remote Codewith durable background-agent steps require remote status polling support; run this Codewith step locally or add remote durable readback before dispatch",
|
|
4488
|
+
startedAt: startedAt2,
|
|
4489
|
+
finishedAt: finishedAt2,
|
|
4490
|
+
durationMs: new Date(finishedAt2).getTime() - new Date(startedAt2).getTime()
|
|
4491
|
+
};
|
|
4492
|
+
}
|
|
4118
4493
|
if (machine && !machine.local)
|
|
4119
4494
|
return executeRemoteSpec(spec, machine, metadata, opts);
|
|
4120
4495
|
const maxOutputBytes = opts.maxOutputBytes ?? DEFAULT_MAX_OUTPUT_BYTES;
|
|
@@ -4161,6 +4536,9 @@ async function executeTarget(target, metadata = {}, opts = {}) {
|
|
|
4161
4536
|
durationMs: 0
|
|
4162
4537
|
};
|
|
4163
4538
|
}
|
|
4539
|
+
if (spec.codewithDurableAgent) {
|
|
4540
|
+
return executeCodewithDurableAgent(spec, metadata, opts, env, startedAt);
|
|
4541
|
+
}
|
|
4164
4542
|
const child = spawn(spec.command, spec.args, {
|
|
4165
4543
|
cwd: spec.cwd,
|
|
4166
4544
|
env,
|
|
@@ -5372,7 +5750,7 @@ function realSleep(ms) {
|
|
|
5372
5750
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
5373
5751
|
}
|
|
5374
5752
|
async function runLoop(opts) {
|
|
5375
|
-
const
|
|
5753
|
+
const sleep2 = opts.sleep ?? realSleep;
|
|
5376
5754
|
const sliceMs = opts.sliceMs ?? 200;
|
|
5377
5755
|
while (!opts.shouldStop()) {
|
|
5378
5756
|
try {
|
|
@@ -5383,7 +5761,7 @@ async function runLoop(opts) {
|
|
|
5383
5761
|
let waited = 0;
|
|
5384
5762
|
while (waited < opts.intervalMs && !opts.shouldStop()) {
|
|
5385
5763
|
const chunk = Math.min(sliceMs, opts.intervalMs - waited);
|
|
5386
|
-
await
|
|
5764
|
+
await sleep2(chunk);
|
|
5387
5765
|
waited += chunk;
|
|
5388
5766
|
}
|
|
5389
5767
|
}
|
|
@@ -5465,7 +5843,7 @@ async function stopDaemon(opts = {}) {
|
|
|
5465
5843
|
} finally {
|
|
5466
5844
|
store.close();
|
|
5467
5845
|
}
|
|
5468
|
-
const
|
|
5846
|
+
const sleep2 = opts.sleep ?? realSleep;
|
|
5469
5847
|
try {
|
|
5470
5848
|
process.kill(state.pid, "SIGTERM");
|
|
5471
5849
|
} catch {
|
|
@@ -5474,7 +5852,7 @@ async function stopDaemon(opts = {}) {
|
|
|
5474
5852
|
}
|
|
5475
5853
|
const steps = Math.max(1, Math.ceil((opts.timeoutMs ?? 6000) / 100));
|
|
5476
5854
|
for (let i = 0;i < steps; i++) {
|
|
5477
|
-
await
|
|
5855
|
+
await sleep2(100);
|
|
5478
5856
|
if (!isAlive(state.pid)) {
|
|
5479
5857
|
removePid(path);
|
|
5480
5858
|
return { wasRunning: true, stopped: true, forced: false, pid: state.pid };
|
|
@@ -5483,7 +5861,7 @@ async function stopDaemon(opts = {}) {
|
|
|
5483
5861
|
try {
|
|
5484
5862
|
process.kill(state.pid, "SIGKILL");
|
|
5485
5863
|
} catch {}
|
|
5486
|
-
await
|
|
5864
|
+
await sleep2(150);
|
|
5487
5865
|
removePid(path);
|
|
5488
5866
|
return { wasRunning: true, stopped: !isAlive(state.pid), forced: true, pid: state.pid };
|
|
5489
5867
|
}
|
|
@@ -5652,10 +6030,10 @@ async function startDaemon(opts) {
|
|
|
5652
6030
|
stdio: ["ignore", out, out]
|
|
5653
6031
|
});
|
|
5654
6032
|
child.unref();
|
|
5655
|
-
const
|
|
6033
|
+
const sleep2 = opts.sleep ?? realSleep;
|
|
5656
6034
|
const deadline = Math.max(1, Math.ceil((opts.waitMs ?? 4000) / 100));
|
|
5657
6035
|
for (let i = 0;i < deadline; i++) {
|
|
5658
|
-
await
|
|
6036
|
+
await sleep2(100);
|
|
5659
6037
|
const current = isDaemonRunning();
|
|
5660
6038
|
if (current.running)
|
|
5661
6039
|
return { started: true, alreadyRunning: false, pid: current.pid };
|
|
@@ -6497,7 +6875,7 @@ function buildScriptInventoryReport(store, opts = {}) {
|
|
|
6497
6875
|
// package.json
|
|
6498
6876
|
var package_default = {
|
|
6499
6877
|
name: "@hasna/loops",
|
|
6500
|
-
version: "0.3.
|
|
6878
|
+
version: "0.3.60",
|
|
6501
6879
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
6502
6880
|
type: "module",
|
|
6503
6881
|
main: "dist/index.js",
|
|
@@ -8637,21 +9015,47 @@ function workflowTimeoutMigrationName(workflow, timeoutMs) {
|
|
|
8637
9015
|
const suffix = randomUUID().replace(/-/g, "").slice(0, 8);
|
|
8638
9016
|
return `${workflow.name}-${policy}-${suffix}`;
|
|
8639
9017
|
}
|
|
8640
|
-
function workflowWithoutGoalWrapper(workflow, opts = {}) {
|
|
8641
|
-
return {
|
|
8642
|
-
body: {
|
|
8643
|
-
name: opts.name ?? workflow.name,
|
|
8644
|
-
description: workflow.description,
|
|
8645
|
-
version: workflow.version,
|
|
8646
|
-
steps: workflow.steps
|
|
8647
|
-
},
|
|
8648
|
-
changed: workflow.goal !== undefined
|
|
8649
|
-
};
|
|
8650
|
-
}
|
|
8651
9018
|
function workflowGoalWrapperMigrationName(workflow) {
|
|
8652
9019
|
const suffix = randomUUID().replace(/-/g, "").slice(0, 8);
|
|
8653
9020
|
return `${workflow.name}-no-workflow-goal-${suffix}`;
|
|
8654
9021
|
}
|
|
9022
|
+
function publicMigrationGoalSummary(goal) {
|
|
9023
|
+
if (!goal)
|
|
9024
|
+
return;
|
|
9025
|
+
return {
|
|
9026
|
+
objective: redact(goal.objective),
|
|
9027
|
+
model: goal.model,
|
|
9028
|
+
tokenBudget: goal.tokenBudget,
|
|
9029
|
+
maxTurns: goal.maxTurns
|
|
9030
|
+
};
|
|
9031
|
+
}
|
|
9032
|
+
function publicMigrationWorkflowSummary(workflow) {
|
|
9033
|
+
return {
|
|
9034
|
+
id: workflow.id,
|
|
9035
|
+
name: workflow.name,
|
|
9036
|
+
version: workflow.version,
|
|
9037
|
+
status: workflow.status,
|
|
9038
|
+
stepCount: workflow.steps.length,
|
|
9039
|
+
hasGoal: Boolean(workflow.goal),
|
|
9040
|
+
goal: publicMigrationGoalSummary(workflow.goal)
|
|
9041
|
+
};
|
|
9042
|
+
}
|
|
9043
|
+
function publicMigrationLoopSummary(loop) {
|
|
9044
|
+
return {
|
|
9045
|
+
id: loop.id,
|
|
9046
|
+
name: loop.name,
|
|
9047
|
+
status: loop.status,
|
|
9048
|
+
archivedAt: loop.archivedAt,
|
|
9049
|
+
nextRunAt: loop.nextRunAt,
|
|
9050
|
+
hasGoal: Boolean(loop.goal),
|
|
9051
|
+
goal: publicMigrationGoalSummary(loop.goal),
|
|
9052
|
+
target: loop.target.type === "workflow" ? { type: "workflow", workflowId: loop.target.workflowId, timeoutMs: loop.target.timeoutMs } : { type: loop.target.type }
|
|
9053
|
+
};
|
|
9054
|
+
}
|
|
9055
|
+
function migrationErrorReason(error) {
|
|
9056
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
9057
|
+
return redact(message, 240);
|
|
9058
|
+
}
|
|
8655
9059
|
function printTextOutput(value) {
|
|
8656
9060
|
for (const line of textOutputBlocks(value, { indent: " " }))
|
|
8657
9061
|
console.log(line);
|
|
@@ -9078,7 +9482,7 @@ function eventMetadata(event) {
|
|
|
9078
9482
|
return metadata;
|
|
9079
9483
|
return {};
|
|
9080
9484
|
}
|
|
9081
|
-
function
|
|
9485
|
+
function stringField2(value) {
|
|
9082
9486
|
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
9083
9487
|
}
|
|
9084
9488
|
function slugSegment2(value, fallback = "event") {
|
|
@@ -9089,14 +9493,14 @@ function stableSuffix(value) {
|
|
|
9089
9493
|
}
|
|
9090
9494
|
function taskEventField(data, keys) {
|
|
9091
9495
|
for (const key of keys) {
|
|
9092
|
-
const direct =
|
|
9496
|
+
const direct = stringField2(data[key]);
|
|
9093
9497
|
if (direct)
|
|
9094
9498
|
return direct;
|
|
9095
9499
|
}
|
|
9096
9500
|
const task = data.task;
|
|
9097
9501
|
if (task && typeof task === "object" && !Array.isArray(task)) {
|
|
9098
9502
|
for (const key of keys) {
|
|
9099
|
-
const direct =
|
|
9503
|
+
const direct = stringField2(task[key]);
|
|
9100
9504
|
if (direct)
|
|
9101
9505
|
return direct;
|
|
9102
9506
|
}
|
|
@@ -9104,7 +9508,7 @@ function taskEventField(data, keys) {
|
|
|
9104
9508
|
const payload = data.payload;
|
|
9105
9509
|
if (payload && typeof payload === "object" && !Array.isArray(payload)) {
|
|
9106
9510
|
for (const key of keys) {
|
|
9107
|
-
const direct =
|
|
9511
|
+
const direct = stringField2(payload[key]);
|
|
9108
9512
|
if (direct)
|
|
9109
9513
|
return direct;
|
|
9110
9514
|
}
|
|
@@ -9804,11 +10208,11 @@ async function readEventEnvelopeInput(opts = {}) {
|
|
|
9804
10208
|
const event = JSON.parse(raw);
|
|
9805
10209
|
if (!event || typeof event !== "object" || Array.isArray(event))
|
|
9806
10210
|
throw new Error("event JSON must be an object");
|
|
9807
|
-
if (!
|
|
10211
|
+
if (!stringField2(event.id))
|
|
9808
10212
|
throw new Error("event.id is required");
|
|
9809
|
-
if (!
|
|
10213
|
+
if (!stringField2(event.type))
|
|
9810
10214
|
throw new Error("event.type is required");
|
|
9811
|
-
if (!
|
|
10215
|
+
if (!stringField2(event.source))
|
|
9812
10216
|
throw new Error("event.source is required");
|
|
9813
10217
|
return event;
|
|
9814
10218
|
}
|
|
@@ -10148,8 +10552,8 @@ function routeGenericEvent(event, opts) {
|
|
|
10148
10552
|
eventId: event.id,
|
|
10149
10553
|
eventType: event.type,
|
|
10150
10554
|
eventSource: event.source,
|
|
10151
|
-
eventSubject:
|
|
10152
|
-
eventMessage:
|
|
10555
|
+
eventSubject: stringField2(event.subject),
|
|
10556
|
+
eventMessage: stringField2(event.message),
|
|
10153
10557
|
eventJson: JSON.stringify(event),
|
|
10154
10558
|
projectPath,
|
|
10155
10559
|
routeProjectPath,
|
|
@@ -10195,9 +10599,9 @@ function routeGenericEvent(event, opts) {
|
|
|
10195
10599
|
},
|
|
10196
10600
|
subjectRef: {
|
|
10197
10601
|
kind: "event",
|
|
10198
|
-
id:
|
|
10602
|
+
id: stringField2(event.subject) ?? event.id,
|
|
10199
10603
|
path: routeProjectPath,
|
|
10200
|
-
raw: { message:
|
|
10604
|
+
raw: { message: stringField2(event.message) }
|
|
10201
10605
|
},
|
|
10202
10606
|
intent: "route",
|
|
10203
10607
|
scope: {
|
|
@@ -10221,7 +10625,7 @@ function routeGenericEvent(event, opts) {
|
|
|
10221
10625
|
invocationId: "<created-invocation-id>",
|
|
10222
10626
|
sourceType: event.type,
|
|
10223
10627
|
sourceRef: event.id,
|
|
10224
|
-
subjectRef:
|
|
10628
|
+
subjectRef: stringField2(event.subject) ?? event.id,
|
|
10225
10629
|
projectKey: routeProjectPath,
|
|
10226
10630
|
projectGroup,
|
|
10227
10631
|
priority: 0,
|
|
@@ -10365,14 +10769,14 @@ function routeGenericEvent(event, opts) {
|
|
|
10365
10769
|
}
|
|
10366
10770
|
function taskField(task, keys) {
|
|
10367
10771
|
for (const key of keys) {
|
|
10368
|
-
const value =
|
|
10772
|
+
const value = stringField2(task[key]);
|
|
10369
10773
|
if (value)
|
|
10370
10774
|
return value;
|
|
10371
10775
|
}
|
|
10372
10776
|
return;
|
|
10373
10777
|
}
|
|
10374
10778
|
function taskListId(task) {
|
|
10375
|
-
return taskField(task, ["task_list_id", "taskListId"]) ??
|
|
10779
|
+
return taskField(task, ["task_list_id", "taskListId"]) ?? stringField2(task.task_list?.id);
|
|
10376
10780
|
}
|
|
10377
10781
|
function taskProjectId(task) {
|
|
10378
10782
|
return taskField(task, ["project_id", "projectId"]);
|
|
@@ -10436,12 +10840,12 @@ function compactDrainResult(result) {
|
|
|
10436
10840
|
kind: result.kind,
|
|
10437
10841
|
taskId: event?.subject,
|
|
10438
10842
|
eventId: event?.id,
|
|
10439
|
-
idempotencyKey:
|
|
10440
|
-
reason:
|
|
10441
|
-
loopId:
|
|
10442
|
-
loopName:
|
|
10443
|
-
workflowId:
|
|
10444
|
-
workflowName:
|
|
10843
|
+
idempotencyKey: stringField2(value.idempotencyKey),
|
|
10844
|
+
reason: stringField2(value.reason) ?? throttle?.reason,
|
|
10845
|
+
loopId: stringField2(loop?.id),
|
|
10846
|
+
loopName: stringField2(loop?.name),
|
|
10847
|
+
workflowId: stringField2(workflow?.id),
|
|
10848
|
+
workflowName: stringField2(workflow?.name),
|
|
10445
10849
|
providerRouting,
|
|
10446
10850
|
requeue,
|
|
10447
10851
|
queuedAtSource: value.queuedAtSource
|
|
@@ -10946,7 +11350,7 @@ routes.command("show <id>").description("show one admission work item").action((
|
|
|
10946
11350
|
routes.command("requeue <id>").description("requeue a terminal admission work item for the next task/event delivery").option("--reason <text>", "operator reason recorded on the work item").action((id, opts) => {
|
|
10947
11351
|
const store = new Store;
|
|
10948
11352
|
try {
|
|
10949
|
-
const reason =
|
|
11353
|
+
const reason = stringField2(opts.reason);
|
|
10950
11354
|
if (!reason)
|
|
10951
11355
|
throw new Error("routes requeue requires --reason <text>");
|
|
10952
11356
|
const item = store.requeueWorkflowWorkItem(id, { reason });
|
|
@@ -11275,19 +11679,30 @@ workflows.command("migrate-agent-timeouts").description("append-only migrate act
|
|
|
11275
11679
|
});
|
|
11276
11680
|
continue;
|
|
11277
11681
|
}
|
|
11278
|
-
|
|
11279
|
-
|
|
11280
|
-
|
|
11281
|
-
|
|
11282
|
-
|
|
11283
|
-
|
|
11284
|
-
|
|
11285
|
-
|
|
11286
|
-
|
|
11287
|
-
|
|
11288
|
-
|
|
11289
|
-
|
|
11290
|
-
|
|
11682
|
+
try {
|
|
11683
|
+
const migrated = store.createAndRetargetWorkflowLoop(loop.id, migration.body, {
|
|
11684
|
+
workflowTimeoutMs: timeoutMs,
|
|
11685
|
+
archiveOld: Boolean(opts.archiveOld)
|
|
11686
|
+
});
|
|
11687
|
+
rows.push({
|
|
11688
|
+
loop: publicLoop(migrated.loop),
|
|
11689
|
+
previousWorkflow: publicWorkflow(migrated.previousWorkflow),
|
|
11690
|
+
workflow: publicWorkflow(migrated.workflow),
|
|
11691
|
+
archivedOld: migrated.archivedOld ? publicWorkflow(migrated.archivedOld) : undefined,
|
|
11692
|
+
status: "migrated",
|
|
11693
|
+
agentStepIds: migration.agentStepIds,
|
|
11694
|
+
timeoutMs
|
|
11695
|
+
});
|
|
11696
|
+
} catch (error) {
|
|
11697
|
+
rows.push({
|
|
11698
|
+
loop: publicLoop(loop),
|
|
11699
|
+
workflow: publicWorkflow(workflow),
|
|
11700
|
+
status: "blocked",
|
|
11701
|
+
reason: migrationErrorReason(error),
|
|
11702
|
+
agentStepIds: migration.agentStepIds,
|
|
11703
|
+
timeoutMs
|
|
11704
|
+
});
|
|
11705
|
+
}
|
|
11291
11706
|
}
|
|
11292
11707
|
const summary = {
|
|
11293
11708
|
apply: Boolean(opts.apply),
|
|
@@ -11310,45 +11725,73 @@ workflows.command("migrate-goal-wrappers").description("append-only migrate acti
|
|
|
11310
11725
|
const rows = [];
|
|
11311
11726
|
for (const loop of candidateLoops) {
|
|
11312
11727
|
if (loop.archivedAt) {
|
|
11313
|
-
rows.push({ loop:
|
|
11728
|
+
rows.push({ loop: publicMigrationLoopSummary(loop), status: "skipped", reason: "loop is archived" });
|
|
11314
11729
|
continue;
|
|
11315
11730
|
}
|
|
11316
11731
|
if (loop.target.type !== "workflow") {
|
|
11317
|
-
rows.push({ loop:
|
|
11732
|
+
rows.push({ loop: publicMigrationLoopSummary(loop), status: "skipped", reason: "loop is not a workflow loop" });
|
|
11318
11733
|
continue;
|
|
11319
11734
|
}
|
|
11320
11735
|
const workflow = store.requireWorkflow(loop.target.workflowId);
|
|
11321
|
-
|
|
11322
|
-
|
|
11323
|
-
|
|
11324
|
-
|
|
11736
|
+
if (!loop.goal) {
|
|
11737
|
+
rows.push({
|
|
11738
|
+
loop: publicMigrationLoopSummary(loop),
|
|
11739
|
+
workflow: publicMigrationWorkflowSummary(workflow),
|
|
11740
|
+
status: "skipped",
|
|
11741
|
+
reason: "loop has no loop-level goal wrapper"
|
|
11742
|
+
});
|
|
11325
11743
|
continue;
|
|
11326
11744
|
}
|
|
11745
|
+
if (!workflow.goal) {
|
|
11746
|
+
rows.push({
|
|
11747
|
+
loop: publicMigrationLoopSummary(loop),
|
|
11748
|
+
workflow: publicMigrationWorkflowSummary(workflow),
|
|
11749
|
+
status: "skipped",
|
|
11750
|
+
reason: "workflow has no top-level goal wrapper"
|
|
11751
|
+
});
|
|
11752
|
+
continue;
|
|
11753
|
+
}
|
|
11754
|
+
const nextWorkflowName = workflowGoalWrapperMigrationName(workflow);
|
|
11327
11755
|
if (store.hasRunningRun(loop.id)) {
|
|
11328
|
-
rows.push({
|
|
11756
|
+
rows.push({
|
|
11757
|
+
loop: publicMigrationLoopSummary(loop),
|
|
11758
|
+
workflow: publicMigrationWorkflowSummary(workflow),
|
|
11759
|
+
status: "blocked",
|
|
11760
|
+
reason: "loop has a running run; retry after it finishes"
|
|
11761
|
+
});
|
|
11329
11762
|
continue;
|
|
11330
11763
|
}
|
|
11331
11764
|
if (!opts.apply) {
|
|
11332
11765
|
rows.push({
|
|
11333
|
-
loop:
|
|
11334
|
-
workflow:
|
|
11766
|
+
loop: publicMigrationLoopSummary(loop),
|
|
11767
|
+
workflow: publicMigrationWorkflowSummary(workflow),
|
|
11335
11768
|
status: "would_migrate",
|
|
11336
11769
|
nextWorkflowName,
|
|
11337
|
-
removedGoal: workflow.goal
|
|
11770
|
+
removedGoal: publicMigrationGoalSummary(workflow.goal)
|
|
11338
11771
|
});
|
|
11339
11772
|
continue;
|
|
11340
11773
|
}
|
|
11341
|
-
|
|
11342
|
-
|
|
11343
|
-
|
|
11344
|
-
|
|
11345
|
-
|
|
11346
|
-
|
|
11347
|
-
|
|
11348
|
-
|
|
11349
|
-
|
|
11350
|
-
|
|
11351
|
-
|
|
11774
|
+
try {
|
|
11775
|
+
const migrated = store.cloneWorkflowWithoutGoalAndRetargetLoop(loop.id, {
|
|
11776
|
+
workflowName: nextWorkflowName,
|
|
11777
|
+
workflowTimeoutMs: loop.target.timeoutMs,
|
|
11778
|
+
archiveOld: Boolean(opts.archiveOld)
|
|
11779
|
+
});
|
|
11780
|
+
rows.push({
|
|
11781
|
+
loop: publicMigrationLoopSummary(migrated.loop),
|
|
11782
|
+
previousWorkflow: publicMigrationWorkflowSummary(migrated.previousWorkflow),
|
|
11783
|
+
workflow: publicMigrationWorkflowSummary(migrated.workflow),
|
|
11784
|
+
archivedOld: migrated.archivedOld ? publicMigrationWorkflowSummary(migrated.archivedOld) : undefined,
|
|
11785
|
+
status: "migrated"
|
|
11786
|
+
});
|
|
11787
|
+
} catch (error) {
|
|
11788
|
+
rows.push({
|
|
11789
|
+
loop: publicMigrationLoopSummary(loop),
|
|
11790
|
+
workflow: publicMigrationWorkflowSummary(workflow),
|
|
11791
|
+
status: "blocked",
|
|
11792
|
+
reason: migrationErrorReason(error)
|
|
11793
|
+
});
|
|
11794
|
+
}
|
|
11352
11795
|
}
|
|
11353
11796
|
const summary = {
|
|
11354
11797
|
apply: Boolean(opts.apply),
|