@hasna/loops 0.3.23 → 0.3.25
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/dist/cli/index.js +89 -13
- package/dist/daemon/index.js +56 -1
- package/dist/index.js +187 -6
- package/dist/lib/health.d.ts +1 -1
- package/dist/sdk/index.js +55 -0
- package/dist/types.d.ts +6 -0
- package/docs/USAGE.md +6 -0
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -3243,6 +3243,28 @@ async function executeLoop(loop, run, opts = {}) {
|
|
|
3243
3243
|
if (loop.target.type === "workflow") {
|
|
3244
3244
|
throw new Error("workflow loop targets must be executed with executeLoopTarget");
|
|
3245
3245
|
}
|
|
3246
|
+
if (loop.target.preflight?.beforeRun) {
|
|
3247
|
+
const startedAt = nowIso();
|
|
3248
|
+
try {
|
|
3249
|
+
preflightTarget(loop.target, {
|
|
3250
|
+
loopId: loop.id,
|
|
3251
|
+
loopName: loop.name,
|
|
3252
|
+
runId: run.id,
|
|
3253
|
+
scheduledFor: run.scheduledFor
|
|
3254
|
+
}, { ...opts, machine: opts.machine ?? loop.machine });
|
|
3255
|
+
} catch (error) {
|
|
3256
|
+
const finishedAt = nowIso();
|
|
3257
|
+
return {
|
|
3258
|
+
status: "failed",
|
|
3259
|
+
stdout: "",
|
|
3260
|
+
stderr: "",
|
|
3261
|
+
error: `runtime preflight failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3262
|
+
startedAt,
|
|
3263
|
+
finishedAt,
|
|
3264
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
3265
|
+
};
|
|
3266
|
+
}
|
|
3267
|
+
}
|
|
3246
3268
|
return executeTarget(loop.target, {
|
|
3247
3269
|
loopId: loop.id,
|
|
3248
3270
|
loopName: loop.name,
|
|
@@ -3824,8 +3846,33 @@ function preflightWorkflow(workflow, opts = {}) {
|
|
|
3824
3846
|
}
|
|
3825
3847
|
});
|
|
3826
3848
|
}
|
|
3849
|
+
function preflightFailureResult(error, startedAt = nowIso()) {
|
|
3850
|
+
const finishedAt = nowIso();
|
|
3851
|
+
return {
|
|
3852
|
+
status: "failed",
|
|
3853
|
+
stdout: "",
|
|
3854
|
+
stderr: "",
|
|
3855
|
+
error: `runtime preflight failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3856
|
+
startedAt,
|
|
3857
|
+
finishedAt,
|
|
3858
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
3859
|
+
};
|
|
3860
|
+
}
|
|
3827
3861
|
async function executeLoopTarget(store, loop, run, opts = {}) {
|
|
3828
3862
|
if (loop.target.type !== "workflow") {
|
|
3863
|
+
if (loop.goal && loop.target.preflight?.beforeRun) {
|
|
3864
|
+
const startedAt = nowIso();
|
|
3865
|
+
try {
|
|
3866
|
+
preflightTarget(loop.target, {
|
|
3867
|
+
loopId: loop.id,
|
|
3868
|
+
loopName: loop.name,
|
|
3869
|
+
runId: run.id,
|
|
3870
|
+
scheduledFor: run.scheduledFor
|
|
3871
|
+
}, { ...opts, machine: opts.machine ?? loop.machine });
|
|
3872
|
+
} catch (error) {
|
|
3873
|
+
return preflightFailureResult(error, startedAt);
|
|
3874
|
+
}
|
|
3875
|
+
}
|
|
3829
3876
|
if (loop.goal) {
|
|
3830
3877
|
return runGoal(store, loop.goal, {
|
|
3831
3878
|
...opts,
|
|
@@ -3841,6 +3888,14 @@ async function executeLoopTarget(store, loop, run, opts = {}) {
|
|
|
3841
3888
|
return executeLoop(loop, run, opts);
|
|
3842
3889
|
}
|
|
3843
3890
|
const workflow = store.requireWorkflow(loop.target.workflowId);
|
|
3891
|
+
if (loop.target.preflight?.beforeRun) {
|
|
3892
|
+
const startedAt = nowIso();
|
|
3893
|
+
try {
|
|
3894
|
+
preflightWorkflow(workflow, { ...opts, machine: opts.machine ?? loop.machine });
|
|
3895
|
+
} catch (error) {
|
|
3896
|
+
return preflightFailureResult(error, startedAt);
|
|
3897
|
+
}
|
|
3898
|
+
}
|
|
3844
3899
|
if (loop.goal) {
|
|
3845
3900
|
return runGoal(store, loop.goal, {
|
|
3846
3901
|
...opts,
|
|
@@ -4701,6 +4756,7 @@ var CLASSIFICATIONS = [
|
|
|
4701
4756
|
"context_length",
|
|
4702
4757
|
"schema_response_format",
|
|
4703
4758
|
"node_init",
|
|
4759
|
+
"preflight",
|
|
4704
4760
|
"timeout",
|
|
4705
4761
|
"sigsegv",
|
|
4706
4762
|
"skipped_previous_active",
|
|
@@ -4714,6 +4770,9 @@ function bounded(value, limit = EVIDENCE_CHARS) {
|
|
|
4714
4770
|
return `${value.slice(0, limit)}
|
|
4715
4771
|
[truncated ${value.length - limit} chars]`;
|
|
4716
4772
|
}
|
|
4773
|
+
function redactedEvidence(value) {
|
|
4774
|
+
return redact(bounded(value));
|
|
4775
|
+
}
|
|
4717
4776
|
function searchableText(run) {
|
|
4718
4777
|
return [run.error, run.stderr, run.stdout].filter(Boolean).join(`
|
|
4719
4778
|
`).toLowerCase();
|
|
@@ -4734,9 +4793,9 @@ function stableFailureFingerprint(run, classification) {
|
|
|
4734
4793
|
function healthRun(run) {
|
|
4735
4794
|
return {
|
|
4736
4795
|
...run,
|
|
4737
|
-
error:
|
|
4738
|
-
stdout:
|
|
4739
|
-
stderr:
|
|
4796
|
+
error: redactedEvidence(run.error),
|
|
4797
|
+
stdout: redactedEvidence(run.stdout),
|
|
4798
|
+
stderr: redactedEvidence(run.stderr)
|
|
4740
4799
|
};
|
|
4741
4800
|
}
|
|
4742
4801
|
function classifyRunFailure(run) {
|
|
@@ -4748,6 +4807,8 @@ function classifyRunFailure(run) {
|
|
|
4748
4807
|
classification = "timeout";
|
|
4749
4808
|
else if (run.status === "skipped" && /previous run still active/.test(text))
|
|
4750
4809
|
classification = "skipped_previous_active";
|
|
4810
|
+
else if (/runtime preflight failed|preflight failed|executable not found in path|none of required executables found|auth profile preflight failed|profile not found/.test(text))
|
|
4811
|
+
classification = "preflight";
|
|
4751
4812
|
else if (/rate limit|too many requests|429\b|quota exceeded/.test(text))
|
|
4752
4813
|
classification = "rate_limit";
|
|
4753
4814
|
else if (/unauthorized|authentication|auth\b|api key|invalid token|permission denied|401\b|403\b/.test(text))
|
|
@@ -4766,9 +4827,9 @@ function classifyRunFailure(run) {
|
|
|
4766
4827
|
classification,
|
|
4767
4828
|
fingerprint: stableFailureFingerprint(run, classification),
|
|
4768
4829
|
evidence: {
|
|
4769
|
-
error:
|
|
4770
|
-
stdout:
|
|
4771
|
-
stderr:
|
|
4830
|
+
error: redactedEvidence(run.error),
|
|
4831
|
+
stdout: redactedEvidence(run.stdout),
|
|
4832
|
+
stderr: redactedEvidence(run.stderr),
|
|
4772
4833
|
exitCode: run.exitCode
|
|
4773
4834
|
}
|
|
4774
4835
|
};
|
|
@@ -5143,7 +5204,7 @@ function buildScriptInventoryReport(store, opts = {}) {
|
|
|
5143
5204
|
// package.json
|
|
5144
5205
|
var package_default = {
|
|
5145
5206
|
name: "@hasna/loops",
|
|
5146
|
-
version: "0.3.
|
|
5207
|
+
version: "0.3.25",
|
|
5147
5208
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
5148
5209
|
type: "module",
|
|
5149
5210
|
main: "dist/index.js",
|
|
@@ -5340,8 +5401,17 @@ function accountForRole(input, role, seed) {
|
|
|
5340
5401
|
return input.verifierAccount;
|
|
5341
5402
|
return rolePoolValue(input.accountPool, seed, role) ?? input.account;
|
|
5342
5403
|
}
|
|
5404
|
+
function assertNativeAuthProfileSupport(input, provider) {
|
|
5405
|
+
if (provider === "codewith")
|
|
5406
|
+
return;
|
|
5407
|
+
const hasNativeAuthProfiles = Boolean(input.authProfile || input.authProfilePool?.length || input.workerAuthProfile || input.verifierAuthProfile);
|
|
5408
|
+
if (!hasNativeAuthProfiles)
|
|
5409
|
+
return;
|
|
5410
|
+
throw new Error(`authProfile, authProfilePool, workerAuthProfile, and verifierAuthProfile are supported only for provider codewith; use account/accountPool for ${provider} profile isolation`);
|
|
5411
|
+
}
|
|
5343
5412
|
function agentTarget(input, prompt, role, seed) {
|
|
5344
5413
|
const provider = input.provider ?? "codewith";
|
|
5414
|
+
assertNativeAuthProfileSupport(input, provider);
|
|
5345
5415
|
const sandbox = input.sandbox ?? (provider === "codewith" || provider === "codex" ? "danger-full-access" : provider === "cursor" ? "disabled" : undefined);
|
|
5346
5416
|
return {
|
|
5347
5417
|
type: "agent",
|
|
@@ -5803,6 +5873,9 @@ function accountPoolFromOpts(opts) {
|
|
|
5803
5873
|
function roleAccountFromOpts(opts, profile) {
|
|
5804
5874
|
return profile ? { profile, tool: opts.accountTool } : undefined;
|
|
5805
5875
|
}
|
|
5876
|
+
function runtimePreflightFromOpts(opts) {
|
|
5877
|
+
return opts.preflightEachRun ? { beforeRun: true } : undefined;
|
|
5878
|
+
}
|
|
5806
5879
|
function parseVars(values) {
|
|
5807
5880
|
const vars = {};
|
|
5808
5881
|
for (const value of values ?? []) {
|
|
@@ -6120,7 +6193,7 @@ function permissionModeFromOpts(opts, provider) {
|
|
|
6120
6193
|
return mode;
|
|
6121
6194
|
}
|
|
6122
6195
|
var create = program.command("create").description("create loops");
|
|
6123
|
-
addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.command("command <name>").description("create a deterministic shell command loop").requiredOption("--cmd <command>", "command string to execute").option("--cwd <dir>", "working directory").option("--timeout <duration>", "run timeout").option("--no-shell", "execute without a shell").option("--preflight", "check target executables/accounts before storing the loop"))))).action((name, opts) => {
|
|
6196
|
+
addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.command("command <name>").description("create a deterministic shell command loop").requiredOption("--cmd <command>", "command string to execute").option("--cwd <dir>", "working directory").option("--timeout <duration>", "run timeout").option("--no-shell", "execute without a shell").option("--preflight-each-run", "check target executables/accounts before every scheduled run").option("--preflight", "check target executables/accounts before storing the loop"))))).action((name, opts) => {
|
|
6124
6197
|
const store = new Store;
|
|
6125
6198
|
try {
|
|
6126
6199
|
const target = {
|
|
@@ -6129,7 +6202,8 @@ addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.com
|
|
|
6129
6202
|
cwd: opts.cwd,
|
|
6130
6203
|
shell: opts.shell,
|
|
6131
6204
|
timeoutMs: opts.timeout ? parseDuration(opts.timeout) : undefined,
|
|
6132
|
-
account: accountFromOpts(opts)
|
|
6205
|
+
account: accountFromOpts(opts),
|
|
6206
|
+
preflight: runtimePreflightFromOpts(opts)
|
|
6133
6207
|
};
|
|
6134
6208
|
const input = baseCreateInput(name, opts, target);
|
|
6135
6209
|
const preflight = opts.preflight ? preflightLoopTarget(input.target, { name, type: "command" }, { loopName: name }, { machine: input.machine }) : undefined;
|
|
@@ -6139,7 +6213,7 @@ addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.com
|
|
|
6139
6213
|
store.close();
|
|
6140
6214
|
}
|
|
6141
6215
|
});
|
|
6142
|
-
addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.command("agent <name>").description("create a headless coding-agent loop").requiredOption("--provider <provider>", "claude, cursor, codewith, aicopilot, opencode, or codex").requiredOption("--prompt <prompt>", "agent prompt").option("--cwd <dir>", "working directory").option("--model <model>", "model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--timeout <duration>", "run timeout").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass").option("--sandbox <mode>", "provider sandbox: codewith/codex use read-only/workspace-write/danger-full-access; cursor uses enabled/disabled").option("--allow-tool <name>", "advisory per-session tool allowlist metadata; may be repeated or comma-separated", collectValues, []).option("--allow-command <name>", "advisory per-session command allowlist metadata; may be repeated or comma-separated", collectValues, []).option("--config-isolation <mode>", "safe or none", "safe").option("--preflight", "check target executables/accounts before storing the loop"))))).action((name, opts) => {
|
|
6216
|
+
addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.command("agent <name>").description("create a headless coding-agent loop").requiredOption("--provider <provider>", "claude, cursor, codewith, aicopilot, opencode, or codex").requiredOption("--prompt <prompt>", "agent prompt").option("--cwd <dir>", "working directory").option("--model <model>", "model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--timeout <duration>", "run timeout").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass").option("--sandbox <mode>", "provider sandbox: codewith/codex use read-only/workspace-write/danger-full-access; cursor uses enabled/disabled").option("--allow-tool <name>", "advisory per-session tool allowlist metadata; may be repeated or comma-separated", collectValues, []).option("--allow-command <name>", "advisory per-session command allowlist metadata; may be repeated or comma-separated", collectValues, []).option("--config-isolation <mode>", "safe or none", "safe").option("--preflight-each-run", "check provider/account readiness before every scheduled run").option("--preflight", "check target executables/accounts before storing the loop"))))).action((name, opts) => {
|
|
6143
6217
|
const provider = opts.provider;
|
|
6144
6218
|
if (!["claude", "cursor", "codewith", "aicopilot", "opencode", "codex"].includes(provider)) {
|
|
6145
6219
|
throw new Error("unsupported provider");
|
|
@@ -6163,7 +6237,8 @@ addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.com
|
|
|
6163
6237
|
permissionMode: permissionModeFromOpts(opts, provider),
|
|
6164
6238
|
sandbox: sandboxFromOpts(opts, provider),
|
|
6165
6239
|
allowlist: allowlistFromOpts(opts),
|
|
6166
|
-
account: accountFromOpts(opts)
|
|
6240
|
+
account: accountFromOpts(opts),
|
|
6241
|
+
preflight: runtimePreflightFromOpts(opts)
|
|
6167
6242
|
};
|
|
6168
6243
|
const input = baseCreateInput(name, opts, target);
|
|
6169
6244
|
const preflight = opts.preflight ? preflightLoopTarget(input.target, { name, type: "agent", provider }, { loopName: name }, { machine: input.machine }) : undefined;
|
|
@@ -6173,13 +6248,14 @@ addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.com
|
|
|
6173
6248
|
store.close();
|
|
6174
6249
|
}
|
|
6175
6250
|
});
|
|
6176
|
-
addGoalOptions(addMachineOptions(addScheduleOptions(create.command("workflow <name>").description("schedule a stored workflow").requiredOption("--workflow <idOrName>", "workflow id or name").option("--preflight", "check workflow step executables/accounts before storing the loop")))).action((name, opts) => {
|
|
6251
|
+
addGoalOptions(addMachineOptions(addScheduleOptions(create.command("workflow <name>").description("schedule a stored workflow").requiredOption("--workflow <idOrName>", "workflow id or name").option("--preflight-each-run", "check workflow steps before every scheduled run").option("--preflight", "check workflow step executables/accounts before storing the loop")))).action((name, opts) => {
|
|
6177
6252
|
const store = new Store;
|
|
6178
6253
|
try {
|
|
6179
6254
|
const workflow = store.requireWorkflow(opts.workflow);
|
|
6180
6255
|
const target = {
|
|
6181
6256
|
type: "workflow",
|
|
6182
|
-
workflowId: workflow.id
|
|
6257
|
+
workflowId: workflow.id,
|
|
6258
|
+
preflight: runtimePreflightFromOpts(opts)
|
|
6183
6259
|
};
|
|
6184
6260
|
const input = baseCreateInput(name, opts, target);
|
|
6185
6261
|
const preflight = opts.preflight ? preflightStoredWorkflow(workflow, { name, type: "workflow", workflow: workflow.name }, { machine: input.machine }) : undefined;
|
package/dist/daemon/index.js
CHANGED
|
@@ -3134,6 +3134,28 @@ async function executeLoop(loop, run, opts = {}) {
|
|
|
3134
3134
|
if (loop.target.type === "workflow") {
|
|
3135
3135
|
throw new Error("workflow loop targets must be executed with executeLoopTarget");
|
|
3136
3136
|
}
|
|
3137
|
+
if (loop.target.preflight?.beforeRun) {
|
|
3138
|
+
const startedAt = nowIso();
|
|
3139
|
+
try {
|
|
3140
|
+
preflightTarget(loop.target, {
|
|
3141
|
+
loopId: loop.id,
|
|
3142
|
+
loopName: loop.name,
|
|
3143
|
+
runId: run.id,
|
|
3144
|
+
scheduledFor: run.scheduledFor
|
|
3145
|
+
}, { ...opts, machine: opts.machine ?? loop.machine });
|
|
3146
|
+
} catch (error) {
|
|
3147
|
+
const finishedAt = nowIso();
|
|
3148
|
+
return {
|
|
3149
|
+
status: "failed",
|
|
3150
|
+
stdout: "",
|
|
3151
|
+
stderr: "",
|
|
3152
|
+
error: `runtime preflight failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3153
|
+
startedAt,
|
|
3154
|
+
finishedAt,
|
|
3155
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
3156
|
+
};
|
|
3157
|
+
}
|
|
3158
|
+
}
|
|
3137
3159
|
return executeTarget(loop.target, {
|
|
3138
3160
|
loopId: loop.id,
|
|
3139
3161
|
loopName: loop.name,
|
|
@@ -3715,8 +3737,33 @@ function preflightWorkflow(workflow, opts = {}) {
|
|
|
3715
3737
|
}
|
|
3716
3738
|
});
|
|
3717
3739
|
}
|
|
3740
|
+
function preflightFailureResult(error, startedAt = nowIso()) {
|
|
3741
|
+
const finishedAt = nowIso();
|
|
3742
|
+
return {
|
|
3743
|
+
status: "failed",
|
|
3744
|
+
stdout: "",
|
|
3745
|
+
stderr: "",
|
|
3746
|
+
error: `runtime preflight failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3747
|
+
startedAt,
|
|
3748
|
+
finishedAt,
|
|
3749
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
3750
|
+
};
|
|
3751
|
+
}
|
|
3718
3752
|
async function executeLoopTarget(store, loop, run, opts = {}) {
|
|
3719
3753
|
if (loop.target.type !== "workflow") {
|
|
3754
|
+
if (loop.goal && loop.target.preflight?.beforeRun) {
|
|
3755
|
+
const startedAt = nowIso();
|
|
3756
|
+
try {
|
|
3757
|
+
preflightTarget(loop.target, {
|
|
3758
|
+
loopId: loop.id,
|
|
3759
|
+
loopName: loop.name,
|
|
3760
|
+
runId: run.id,
|
|
3761
|
+
scheduledFor: run.scheduledFor
|
|
3762
|
+
}, { ...opts, machine: opts.machine ?? loop.machine });
|
|
3763
|
+
} catch (error) {
|
|
3764
|
+
return preflightFailureResult(error, startedAt);
|
|
3765
|
+
}
|
|
3766
|
+
}
|
|
3720
3767
|
if (loop.goal) {
|
|
3721
3768
|
return runGoal(store, loop.goal, {
|
|
3722
3769
|
...opts,
|
|
@@ -3732,6 +3779,14 @@ async function executeLoopTarget(store, loop, run, opts = {}) {
|
|
|
3732
3779
|
return executeLoop(loop, run, opts);
|
|
3733
3780
|
}
|
|
3734
3781
|
const workflow = store.requireWorkflow(loop.target.workflowId);
|
|
3782
|
+
if (loop.target.preflight?.beforeRun) {
|
|
3783
|
+
const startedAt = nowIso();
|
|
3784
|
+
try {
|
|
3785
|
+
preflightWorkflow(workflow, { ...opts, machine: opts.machine ?? loop.machine });
|
|
3786
|
+
} catch (error) {
|
|
3787
|
+
return preflightFailureResult(error, startedAt);
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3735
3790
|
if (loop.goal) {
|
|
3736
3791
|
return runGoal(store, loop.goal, {
|
|
3737
3792
|
...opts,
|
|
@@ -4470,7 +4525,7 @@ function enableStartup(result) {
|
|
|
4470
4525
|
// package.json
|
|
4471
4526
|
var package_default = {
|
|
4472
4527
|
name: "@hasna/loops",
|
|
4473
|
-
version: "0.3.
|
|
4528
|
+
version: "0.3.25",
|
|
4474
4529
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
4475
4530
|
type: "module",
|
|
4476
4531
|
main: "dist/index.js",
|
package/dist/index.js
CHANGED
|
@@ -3124,6 +3124,28 @@ async function executeLoop(loop, run, opts = {}) {
|
|
|
3124
3124
|
if (loop.target.type === "workflow") {
|
|
3125
3125
|
throw new Error("workflow loop targets must be executed with executeLoopTarget");
|
|
3126
3126
|
}
|
|
3127
|
+
if (loop.target.preflight?.beforeRun) {
|
|
3128
|
+
const startedAt = nowIso();
|
|
3129
|
+
try {
|
|
3130
|
+
preflightTarget(loop.target, {
|
|
3131
|
+
loopId: loop.id,
|
|
3132
|
+
loopName: loop.name,
|
|
3133
|
+
runId: run.id,
|
|
3134
|
+
scheduledFor: run.scheduledFor
|
|
3135
|
+
}, { ...opts, machine: opts.machine ?? loop.machine });
|
|
3136
|
+
} catch (error) {
|
|
3137
|
+
const finishedAt = nowIso();
|
|
3138
|
+
return {
|
|
3139
|
+
status: "failed",
|
|
3140
|
+
stdout: "",
|
|
3141
|
+
stderr: "",
|
|
3142
|
+
error: `runtime preflight failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3143
|
+
startedAt,
|
|
3144
|
+
finishedAt,
|
|
3145
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3127
3149
|
return executeTarget(loop.target, {
|
|
3128
3150
|
loopId: loop.id,
|
|
3129
3151
|
loopName: loop.name,
|
|
@@ -3705,8 +3727,33 @@ function preflightWorkflow(workflow, opts = {}) {
|
|
|
3705
3727
|
}
|
|
3706
3728
|
});
|
|
3707
3729
|
}
|
|
3730
|
+
function preflightFailureResult(error, startedAt = nowIso()) {
|
|
3731
|
+
const finishedAt = nowIso();
|
|
3732
|
+
return {
|
|
3733
|
+
status: "failed",
|
|
3734
|
+
stdout: "",
|
|
3735
|
+
stderr: "",
|
|
3736
|
+
error: `runtime preflight failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3737
|
+
startedAt,
|
|
3738
|
+
finishedAt,
|
|
3739
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
3740
|
+
};
|
|
3741
|
+
}
|
|
3708
3742
|
async function executeLoopTarget(store, loop, run, opts = {}) {
|
|
3709
3743
|
if (loop.target.type !== "workflow") {
|
|
3744
|
+
if (loop.goal && loop.target.preflight?.beforeRun) {
|
|
3745
|
+
const startedAt = nowIso();
|
|
3746
|
+
try {
|
|
3747
|
+
preflightTarget(loop.target, {
|
|
3748
|
+
loopId: loop.id,
|
|
3749
|
+
loopName: loop.name,
|
|
3750
|
+
runId: run.id,
|
|
3751
|
+
scheduledFor: run.scheduledFor
|
|
3752
|
+
}, { ...opts, machine: opts.machine ?? loop.machine });
|
|
3753
|
+
} catch (error) {
|
|
3754
|
+
return preflightFailureResult(error, startedAt);
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3710
3757
|
if (loop.goal) {
|
|
3711
3758
|
return runGoal(store, loop.goal, {
|
|
3712
3759
|
...opts,
|
|
@@ -3722,6 +3769,14 @@ async function executeLoopTarget(store, loop, run, opts = {}) {
|
|
|
3722
3769
|
return executeLoop(loop, run, opts);
|
|
3723
3770
|
}
|
|
3724
3771
|
const workflow = store.requireWorkflow(loop.target.workflowId);
|
|
3772
|
+
if (loop.target.preflight?.beforeRun) {
|
|
3773
|
+
const startedAt = nowIso();
|
|
3774
|
+
try {
|
|
3775
|
+
preflightWorkflow(workflow, { ...opts, machine: opts.machine ?? loop.machine });
|
|
3776
|
+
} catch (error) {
|
|
3777
|
+
return preflightFailureResult(error, startedAt);
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3725
3780
|
if (loop.goal) {
|
|
3726
3781
|
return runGoal(store, loop.goal, {
|
|
3727
3782
|
...opts,
|
|
@@ -4275,8 +4330,17 @@ function accountForRole(input, role, seed) {
|
|
|
4275
4330
|
return input.verifierAccount;
|
|
4276
4331
|
return rolePoolValue(input.accountPool, seed, role) ?? input.account;
|
|
4277
4332
|
}
|
|
4333
|
+
function assertNativeAuthProfileSupport(input, provider) {
|
|
4334
|
+
if (provider === "codewith")
|
|
4335
|
+
return;
|
|
4336
|
+
const hasNativeAuthProfiles = Boolean(input.authProfile || input.authProfilePool?.length || input.workerAuthProfile || input.verifierAuthProfile);
|
|
4337
|
+
if (!hasNativeAuthProfiles)
|
|
4338
|
+
return;
|
|
4339
|
+
throw new Error(`authProfile, authProfilePool, workerAuthProfile, and verifierAuthProfile are supported only for provider codewith; use account/accountPool for ${provider} profile isolation`);
|
|
4340
|
+
}
|
|
4278
4341
|
function agentTarget(input, prompt, role, seed) {
|
|
4279
4342
|
const provider = input.provider ?? "codewith";
|
|
4343
|
+
assertNativeAuthProfileSupport(input, provider);
|
|
4280
4344
|
const sandbox = input.sandbox ?? (provider === "codewith" || provider === "codex" ? "danger-full-access" : provider === "cursor" ? "disabled" : undefined);
|
|
4281
4345
|
return {
|
|
4282
4346
|
type: "agent",
|
|
@@ -4787,6 +4851,117 @@ function runDoctor(store) {
|
|
|
4787
4851
|
}
|
|
4788
4852
|
// src/lib/health.ts
|
|
4789
4853
|
import { createHash } from "crypto";
|
|
4854
|
+
|
|
4855
|
+
// src/lib/format.ts
|
|
4856
|
+
var TEXT_OUTPUT_LIMIT = 32 * 1024;
|
|
4857
|
+
var SENSITIVE_PAYLOAD_KEYS = new Set(["env", "error", "prompt", "reason", "stderr", "stdout"]);
|
|
4858
|
+
function redact(value, visible = 0) {
|
|
4859
|
+
if (!value)
|
|
4860
|
+
return value;
|
|
4861
|
+
if (value.length <= visible)
|
|
4862
|
+
return value;
|
|
4863
|
+
if (visible <= 0)
|
|
4864
|
+
return `[redacted ${value.length} chars]`;
|
|
4865
|
+
return `${value.slice(0, visible)}... [redacted ${value.length - visible} chars]`;
|
|
4866
|
+
}
|
|
4867
|
+
function truncateTextOutput(value) {
|
|
4868
|
+
if (value.length <= TEXT_OUTPUT_LIMIT)
|
|
4869
|
+
return value;
|
|
4870
|
+
return `${value.slice(0, TEXT_OUTPUT_LIMIT)}
|
|
4871
|
+
[truncated ${value.length - TEXT_OUTPUT_LIMIT} chars]`;
|
|
4872
|
+
}
|
|
4873
|
+
function redactSensitivePayload(value, key) {
|
|
4874
|
+
if (key && SENSITIVE_PAYLOAD_KEYS.has(key)) {
|
|
4875
|
+
if (typeof value === "string")
|
|
4876
|
+
return redact(value);
|
|
4877
|
+
if (value === undefined || value === null)
|
|
4878
|
+
return value;
|
|
4879
|
+
return "[redacted]";
|
|
4880
|
+
}
|
|
4881
|
+
if (Array.isArray(value))
|
|
4882
|
+
return value.map((item) => redactSensitivePayload(item));
|
|
4883
|
+
if (value && typeof value === "object") {
|
|
4884
|
+
return Object.fromEntries(Object.entries(value).map(([entryKey, entryValue]) => [entryKey, redactSensitivePayload(entryValue, entryKey)]));
|
|
4885
|
+
}
|
|
4886
|
+
return value;
|
|
4887
|
+
}
|
|
4888
|
+
function textOutputBlocks(value, opts = {}) {
|
|
4889
|
+
const indent = opts.indent ?? "";
|
|
4890
|
+
const nested = `${indent} `;
|
|
4891
|
+
const blocks = [];
|
|
4892
|
+
for (const [label, output] of [
|
|
4893
|
+
["stdout", value.stdout],
|
|
4894
|
+
["stderr", value.stderr]
|
|
4895
|
+
]) {
|
|
4896
|
+
if (!output)
|
|
4897
|
+
continue;
|
|
4898
|
+
blocks.push(`${indent}${label}:`);
|
|
4899
|
+
for (const line of truncateTextOutput(output).replace(/\s+$/, "").split(/\r?\n/)) {
|
|
4900
|
+
blocks.push(`${nested}${line}`);
|
|
4901
|
+
}
|
|
4902
|
+
}
|
|
4903
|
+
return blocks;
|
|
4904
|
+
}
|
|
4905
|
+
function publicLoop(loop) {
|
|
4906
|
+
const target = loop.target.type === "command" ? { ...loop.target, env: loop.target.env ? "[redacted]" : undefined } : loop.target.type === "agent" ? { ...loop.target, prompt: redact(loop.target.prompt) } : loop.target;
|
|
4907
|
+
return {
|
|
4908
|
+
...loop,
|
|
4909
|
+
target
|
|
4910
|
+
};
|
|
4911
|
+
}
|
|
4912
|
+
function publicRun(run, showOutput = false) {
|
|
4913
|
+
return {
|
|
4914
|
+
...run,
|
|
4915
|
+
stdout: showOutput ? run.stdout : run.stdout ? `[redacted ${run.stdout.length} chars]` : undefined,
|
|
4916
|
+
stderr: showOutput ? run.stderr : run.stderr ? `[redacted ${run.stderr.length} chars]` : undefined
|
|
4917
|
+
};
|
|
4918
|
+
}
|
|
4919
|
+
function publicExecutorResult(result, showOutput = false) {
|
|
4920
|
+
return {
|
|
4921
|
+
...result,
|
|
4922
|
+
stdout: showOutput ? result.stdout : result.stdout ? `[redacted ${result.stdout.length} chars]` : undefined,
|
|
4923
|
+
stderr: showOutput ? result.stderr : result.stderr ? `[redacted ${result.stderr.length} chars]` : undefined,
|
|
4924
|
+
error: redact(result.error)
|
|
4925
|
+
};
|
|
4926
|
+
}
|
|
4927
|
+
function publicWorkflow(workflow) {
|
|
4928
|
+
return {
|
|
4929
|
+
...workflow,
|
|
4930
|
+
steps: workflow.steps.map((step) => ({
|
|
4931
|
+
...step,
|
|
4932
|
+
target: step.target.type === "agent" ? { ...step.target, prompt: redact(step.target.prompt) } : step.target.type === "command" && step.target.env ? { ...step.target, env: "[redacted]" } : step.target
|
|
4933
|
+
}))
|
|
4934
|
+
};
|
|
4935
|
+
}
|
|
4936
|
+
function publicWorkflowRun(run) {
|
|
4937
|
+
return { ...run, error: redact(run.error) };
|
|
4938
|
+
}
|
|
4939
|
+
function publicWorkflowStepRun(run, showOutput = false) {
|
|
4940
|
+
return {
|
|
4941
|
+
...run,
|
|
4942
|
+
stdout: showOutput ? run.stdout : run.stdout ? `[redacted ${run.stdout.length} chars]` : undefined,
|
|
4943
|
+
stderr: showOutput ? run.stderr : run.stderr ? `[redacted ${run.stderr.length} chars]` : undefined,
|
|
4944
|
+
error: redact(run.error)
|
|
4945
|
+
};
|
|
4946
|
+
}
|
|
4947
|
+
function publicWorkflowEvent(event) {
|
|
4948
|
+
return { ...event, payload: redactSensitivePayload(event.payload) };
|
|
4949
|
+
}
|
|
4950
|
+
function publicGoal(goal) {
|
|
4951
|
+
return {
|
|
4952
|
+
...goal,
|
|
4953
|
+
objective: redact(goal.objective, 120)
|
|
4954
|
+
};
|
|
4955
|
+
}
|
|
4956
|
+
function publicGoalRun(run) {
|
|
4957
|
+
return {
|
|
4958
|
+
...run,
|
|
4959
|
+
evidence: redactSensitivePayload(run.evidence),
|
|
4960
|
+
rawResponse: redactSensitivePayload(run.rawResponse)
|
|
4961
|
+
};
|
|
4962
|
+
}
|
|
4963
|
+
|
|
4964
|
+
// src/lib/health.ts
|
|
4790
4965
|
var EVIDENCE_CHARS = 2000;
|
|
4791
4966
|
var FINGERPRINT_EVIDENCE_CHARS = 120;
|
|
4792
4967
|
var CLASSIFICATIONS = [
|
|
@@ -4796,6 +4971,7 @@ var CLASSIFICATIONS = [
|
|
|
4796
4971
|
"context_length",
|
|
4797
4972
|
"schema_response_format",
|
|
4798
4973
|
"node_init",
|
|
4974
|
+
"preflight",
|
|
4799
4975
|
"timeout",
|
|
4800
4976
|
"sigsegv",
|
|
4801
4977
|
"skipped_previous_active",
|
|
@@ -4809,6 +4985,9 @@ function bounded(value, limit = EVIDENCE_CHARS) {
|
|
|
4809
4985
|
return `${value.slice(0, limit)}
|
|
4810
4986
|
[truncated ${value.length - limit} chars]`;
|
|
4811
4987
|
}
|
|
4988
|
+
function redactedEvidence(value) {
|
|
4989
|
+
return redact(bounded(value));
|
|
4990
|
+
}
|
|
4812
4991
|
function searchableText(run) {
|
|
4813
4992
|
return [run.error, run.stderr, run.stdout].filter(Boolean).join(`
|
|
4814
4993
|
`).toLowerCase();
|
|
@@ -4829,9 +5008,9 @@ function stableFailureFingerprint(run, classification) {
|
|
|
4829
5008
|
function healthRun(run) {
|
|
4830
5009
|
return {
|
|
4831
5010
|
...run,
|
|
4832
|
-
error:
|
|
4833
|
-
stdout:
|
|
4834
|
-
stderr:
|
|
5011
|
+
error: redactedEvidence(run.error),
|
|
5012
|
+
stdout: redactedEvidence(run.stdout),
|
|
5013
|
+
stderr: redactedEvidence(run.stderr)
|
|
4835
5014
|
};
|
|
4836
5015
|
}
|
|
4837
5016
|
function classifyRunFailure(run) {
|
|
@@ -4843,6 +5022,8 @@ function classifyRunFailure(run) {
|
|
|
4843
5022
|
classification = "timeout";
|
|
4844
5023
|
else if (run.status === "skipped" && /previous run still active/.test(text))
|
|
4845
5024
|
classification = "skipped_previous_active";
|
|
5025
|
+
else if (/runtime preflight failed|preflight failed|executable not found in path|none of required executables found|auth profile preflight failed|profile not found/.test(text))
|
|
5026
|
+
classification = "preflight";
|
|
4846
5027
|
else if (/rate limit|too many requests|429\b|quota exceeded/.test(text))
|
|
4847
5028
|
classification = "rate_limit";
|
|
4848
5029
|
else if (/unauthorized|authentication|auth\b|api key|invalid token|permission denied|401\b|403\b/.test(text))
|
|
@@ -4861,9 +5042,9 @@ function classifyRunFailure(run) {
|
|
|
4861
5042
|
classification,
|
|
4862
5043
|
fingerprint: stableFailureFingerprint(run, classification),
|
|
4863
5044
|
evidence: {
|
|
4864
|
-
error:
|
|
4865
|
-
stdout:
|
|
4866
|
-
stderr:
|
|
5045
|
+
error: redactedEvidence(run.error),
|
|
5046
|
+
stdout: redactedEvidence(run.stdout),
|
|
5047
|
+
stderr: redactedEvidence(run.stderr),
|
|
4867
5048
|
exitCode: run.exitCode
|
|
4868
5049
|
}
|
|
4869
5050
|
};
|
package/dist/lib/health.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Loop, LoopRun } from "../types.js";
|
|
2
2
|
import type { Store } from "./store.js";
|
|
3
|
-
export type RunFailureClassification = "rate_limit" | "auth" | "model_not_found" | "context_length" | "schema_response_format" | "node_init" | "timeout" | "sigsegv" | "skipped_previous_active" | "unknown";
|
|
3
|
+
export type RunFailureClassification = "rate_limit" | "auth" | "model_not_found" | "context_length" | "schema_response_format" | "node_init" | "preflight" | "timeout" | "sigsegv" | "skipped_previous_active" | "unknown";
|
|
4
4
|
export interface RunFailureSignal {
|
|
5
5
|
classification: RunFailureClassification;
|
|
6
6
|
fingerprint: string;
|
package/dist/sdk/index.js
CHANGED
|
@@ -3124,6 +3124,28 @@ async function executeLoop(loop, run, opts = {}) {
|
|
|
3124
3124
|
if (loop.target.type === "workflow") {
|
|
3125
3125
|
throw new Error("workflow loop targets must be executed with executeLoopTarget");
|
|
3126
3126
|
}
|
|
3127
|
+
if (loop.target.preflight?.beforeRun) {
|
|
3128
|
+
const startedAt = nowIso();
|
|
3129
|
+
try {
|
|
3130
|
+
preflightTarget(loop.target, {
|
|
3131
|
+
loopId: loop.id,
|
|
3132
|
+
loopName: loop.name,
|
|
3133
|
+
runId: run.id,
|
|
3134
|
+
scheduledFor: run.scheduledFor
|
|
3135
|
+
}, { ...opts, machine: opts.machine ?? loop.machine });
|
|
3136
|
+
} catch (error) {
|
|
3137
|
+
const finishedAt = nowIso();
|
|
3138
|
+
return {
|
|
3139
|
+
status: "failed",
|
|
3140
|
+
stdout: "",
|
|
3141
|
+
stderr: "",
|
|
3142
|
+
error: `runtime preflight failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3143
|
+
startedAt,
|
|
3144
|
+
finishedAt,
|
|
3145
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
3146
|
+
};
|
|
3147
|
+
}
|
|
3148
|
+
}
|
|
3127
3149
|
return executeTarget(loop.target, {
|
|
3128
3150
|
loopId: loop.id,
|
|
3129
3151
|
loopName: loop.name,
|
|
@@ -3705,8 +3727,33 @@ function preflightWorkflow(workflow, opts = {}) {
|
|
|
3705
3727
|
}
|
|
3706
3728
|
});
|
|
3707
3729
|
}
|
|
3730
|
+
function preflightFailureResult(error, startedAt = nowIso()) {
|
|
3731
|
+
const finishedAt = nowIso();
|
|
3732
|
+
return {
|
|
3733
|
+
status: "failed",
|
|
3734
|
+
stdout: "",
|
|
3735
|
+
stderr: "",
|
|
3736
|
+
error: `runtime preflight failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
3737
|
+
startedAt,
|
|
3738
|
+
finishedAt,
|
|
3739
|
+
durationMs: new Date(finishedAt).getTime() - new Date(startedAt).getTime()
|
|
3740
|
+
};
|
|
3741
|
+
}
|
|
3708
3742
|
async function executeLoopTarget(store, loop, run, opts = {}) {
|
|
3709
3743
|
if (loop.target.type !== "workflow") {
|
|
3744
|
+
if (loop.goal && loop.target.preflight?.beforeRun) {
|
|
3745
|
+
const startedAt = nowIso();
|
|
3746
|
+
try {
|
|
3747
|
+
preflightTarget(loop.target, {
|
|
3748
|
+
loopId: loop.id,
|
|
3749
|
+
loopName: loop.name,
|
|
3750
|
+
runId: run.id,
|
|
3751
|
+
scheduledFor: run.scheduledFor
|
|
3752
|
+
}, { ...opts, machine: opts.machine ?? loop.machine });
|
|
3753
|
+
} catch (error) {
|
|
3754
|
+
return preflightFailureResult(error, startedAt);
|
|
3755
|
+
}
|
|
3756
|
+
}
|
|
3710
3757
|
if (loop.goal) {
|
|
3711
3758
|
return runGoal(store, loop.goal, {
|
|
3712
3759
|
...opts,
|
|
@@ -3722,6 +3769,14 @@ async function executeLoopTarget(store, loop, run, opts = {}) {
|
|
|
3722
3769
|
return executeLoop(loop, run, opts);
|
|
3723
3770
|
}
|
|
3724
3771
|
const workflow = store.requireWorkflow(loop.target.workflowId);
|
|
3772
|
+
if (loop.target.preflight?.beforeRun) {
|
|
3773
|
+
const startedAt = nowIso();
|
|
3774
|
+
try {
|
|
3775
|
+
preflightWorkflow(workflow, { ...opts, machine: opts.machine ?? loop.machine });
|
|
3776
|
+
} catch (error) {
|
|
3777
|
+
return preflightFailureResult(error, startedAt);
|
|
3778
|
+
}
|
|
3779
|
+
}
|
|
3725
3780
|
if (loop.goal) {
|
|
3726
3781
|
return runGoal(store, loop.goal, {
|
|
3727
3782
|
...opts,
|
package/dist/types.d.ts
CHANGED
|
@@ -40,6 +40,9 @@ export interface DynamicSchedule {
|
|
|
40
40
|
minIntervalMs?: number;
|
|
41
41
|
}
|
|
42
42
|
export type ScheduleSpec = OnceSchedule | IntervalSchedule | CronSchedule | DynamicSchedule;
|
|
43
|
+
export interface RuntimePreflightPolicy {
|
|
44
|
+
beforeRun?: boolean;
|
|
45
|
+
}
|
|
43
46
|
export interface CommandTarget {
|
|
44
47
|
type: "command";
|
|
45
48
|
command: string;
|
|
@@ -49,6 +52,7 @@ export interface CommandTarget {
|
|
|
49
52
|
env?: Record<string, string>;
|
|
50
53
|
timeoutMs?: number;
|
|
51
54
|
account?: AccountRef;
|
|
55
|
+
preflight?: RuntimePreflightPolicy;
|
|
52
56
|
}
|
|
53
57
|
export type AgentProvider = "claude" | "cursor" | "codewith" | "aicopilot" | "opencode" | "codex";
|
|
54
58
|
export type AgentConfigIsolation = "safe" | "none";
|
|
@@ -75,12 +79,14 @@ export interface AgentTarget {
|
|
|
75
79
|
sandbox?: AgentSandbox;
|
|
76
80
|
allowlist?: AgentAllowlistSpec;
|
|
77
81
|
account?: AccountRef;
|
|
82
|
+
preflight?: RuntimePreflightPolicy;
|
|
78
83
|
}
|
|
79
84
|
export interface WorkflowTarget {
|
|
80
85
|
type: "workflow";
|
|
81
86
|
workflowId: string;
|
|
82
87
|
input?: Record<string, string>;
|
|
83
88
|
timeoutMs?: number;
|
|
89
|
+
preflight?: RuntimePreflightPolicy;
|
|
84
90
|
}
|
|
85
91
|
export type ExecutableTarget = CommandTarget | AgentTarget;
|
|
86
92
|
export type LoopTarget = ExecutableTarget | WorkflowTarget;
|
package/docs/USAGE.md
CHANGED
|
@@ -73,6 +73,12 @@ accounts because the command string is interpreted later by the shell. Use
|
|
|
73
73
|
`--no-shell` or workflow command `args` when you need executable-level
|
|
74
74
|
validation before storing the loop.
|
|
75
75
|
|
|
76
|
+
Use `--preflight-each-run` when a loop should repeat the same readiness check at
|
|
77
|
+
run time before launching expensive agent or workflow work. Runtime preflight
|
|
78
|
+
failures are recorded as failed loop runs with a `runtime preflight failed`
|
|
79
|
+
error, so health/routing checks can create follow-up tasks without spawning the
|
|
80
|
+
worker.
|
|
81
|
+
|
|
76
82
|
Run a Claude loop every morning:
|
|
77
83
|
|
|
78
84
|
```bash
|
package/package.json
CHANGED