@hasna/loops 0.3.22 → 0.3.24
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 +120 -27
- package/dist/daemon/index.js +56 -1
- package/dist/index.js +58 -0
- 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 +12 -4
- 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",
|
|
@@ -4748,6 +4804,8 @@ function classifyRunFailure(run) {
|
|
|
4748
4804
|
classification = "timeout";
|
|
4749
4805
|
else if (run.status === "skipped" && /previous run still active/.test(text))
|
|
4750
4806
|
classification = "skipped_previous_active";
|
|
4807
|
+
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))
|
|
4808
|
+
classification = "preflight";
|
|
4751
4809
|
else if (/rate limit|too many requests|429\b|quota exceeded/.test(text))
|
|
4752
4810
|
classification = "rate_limit";
|
|
4753
4811
|
else if (/unauthorized|authentication|auth\b|api key|invalid token|permission denied|401\b|403\b/.test(text))
|
|
@@ -5143,7 +5201,7 @@ function buildScriptInventoryReport(store, opts = {}) {
|
|
|
5143
5201
|
// package.json
|
|
5144
5202
|
var package_default = {
|
|
5145
5203
|
name: "@hasna/loops",
|
|
5146
|
-
version: "0.3.
|
|
5204
|
+
version: "0.3.24",
|
|
5147
5205
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
5148
5206
|
type: "module",
|
|
5149
5207
|
main: "dist/index.js",
|
|
@@ -5662,6 +5720,20 @@ function preflightStoredWorkflow(workflow, context, opts) {
|
|
|
5662
5720
|
preflightFailed(error, context);
|
|
5663
5721
|
}
|
|
5664
5722
|
}
|
|
5723
|
+
function workflowSpecForPreflight(body, id = "validation") {
|
|
5724
|
+
const now = new Date().toISOString();
|
|
5725
|
+
return {
|
|
5726
|
+
id,
|
|
5727
|
+
name: body.name,
|
|
5728
|
+
description: body.description,
|
|
5729
|
+
version: body.version ?? 1,
|
|
5730
|
+
status: "active",
|
|
5731
|
+
goal: body.goal,
|
|
5732
|
+
steps: body.steps,
|
|
5733
|
+
createdAt: now,
|
|
5734
|
+
updatedAt: now
|
|
5735
|
+
};
|
|
5736
|
+
}
|
|
5665
5737
|
function printTextOutput(value) {
|
|
5666
5738
|
for (const line of textOutputBlocks(value, { indent: " " }))
|
|
5667
5739
|
console.log(line);
|
|
@@ -5789,6 +5861,9 @@ function accountPoolFromOpts(opts) {
|
|
|
5789
5861
|
function roleAccountFromOpts(opts, profile) {
|
|
5790
5862
|
return profile ? { profile, tool: opts.accountTool } : undefined;
|
|
5791
5863
|
}
|
|
5864
|
+
function runtimePreflightFromOpts(opts) {
|
|
5865
|
+
return opts.preflightEachRun ? { beforeRun: true } : undefined;
|
|
5866
|
+
}
|
|
5792
5867
|
function parseVars(values) {
|
|
5793
5868
|
const vars = {};
|
|
5794
5869
|
for (const value of values ?? []) {
|
|
@@ -6106,7 +6181,7 @@ function permissionModeFromOpts(opts, provider) {
|
|
|
6106
6181
|
return mode;
|
|
6107
6182
|
}
|
|
6108
6183
|
var create = program.command("create").description("create loops");
|
|
6109
|
-
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) => {
|
|
6184
|
+
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) => {
|
|
6110
6185
|
const store = new Store;
|
|
6111
6186
|
try {
|
|
6112
6187
|
const target = {
|
|
@@ -6115,7 +6190,8 @@ addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.com
|
|
|
6115
6190
|
cwd: opts.cwd,
|
|
6116
6191
|
shell: opts.shell,
|
|
6117
6192
|
timeoutMs: opts.timeout ? parseDuration(opts.timeout) : undefined,
|
|
6118
|
-
account: accountFromOpts(opts)
|
|
6193
|
+
account: accountFromOpts(opts),
|
|
6194
|
+
preflight: runtimePreflightFromOpts(opts)
|
|
6119
6195
|
};
|
|
6120
6196
|
const input = baseCreateInput(name, opts, target);
|
|
6121
6197
|
const preflight = opts.preflight ? preflightLoopTarget(input.target, { name, type: "command" }, { loopName: name }, { machine: input.machine }) : undefined;
|
|
@@ -6125,7 +6201,7 @@ addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.com
|
|
|
6125
6201
|
store.close();
|
|
6126
6202
|
}
|
|
6127
6203
|
});
|
|
6128
|
-
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) => {
|
|
6204
|
+
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) => {
|
|
6129
6205
|
const provider = opts.provider;
|
|
6130
6206
|
if (!["claude", "cursor", "codewith", "aicopilot", "opencode", "codex"].includes(provider)) {
|
|
6131
6207
|
throw new Error("unsupported provider");
|
|
@@ -6149,7 +6225,8 @@ addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.com
|
|
|
6149
6225
|
permissionMode: permissionModeFromOpts(opts, provider),
|
|
6150
6226
|
sandbox: sandboxFromOpts(opts, provider),
|
|
6151
6227
|
allowlist: allowlistFromOpts(opts),
|
|
6152
|
-
account: accountFromOpts(opts)
|
|
6228
|
+
account: accountFromOpts(opts),
|
|
6229
|
+
preflight: runtimePreflightFromOpts(opts)
|
|
6153
6230
|
};
|
|
6154
6231
|
const input = baseCreateInput(name, opts, target);
|
|
6155
6232
|
const preflight = opts.preflight ? preflightLoopTarget(input.target, { name, type: "agent", provider }, { loopName: name }, { machine: input.machine }) : undefined;
|
|
@@ -6159,13 +6236,14 @@ addGoalOptions(addAccountOptions(addMachineOptions(addScheduleOptions(create.com
|
|
|
6159
6236
|
store.close();
|
|
6160
6237
|
}
|
|
6161
6238
|
});
|
|
6162
|
-
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) => {
|
|
6239
|
+
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) => {
|
|
6163
6240
|
const store = new Store;
|
|
6164
6241
|
try {
|
|
6165
6242
|
const workflow = store.requireWorkflow(opts.workflow);
|
|
6166
6243
|
const target = {
|
|
6167
6244
|
type: "workflow",
|
|
6168
|
-
workflowId: workflow.id
|
|
6245
|
+
workflowId: workflow.id,
|
|
6246
|
+
preflight: runtimePreflightFromOpts(opts)
|
|
6169
6247
|
};
|
|
6170
6248
|
const input = baseCreateInput(name, opts, target);
|
|
6171
6249
|
const preflight = opts.preflight ? preflightStoredWorkflow(workflow, { name, type: "workflow", workflow: workflow.name }, { machine: input.machine }) : undefined;
|
|
@@ -6211,7 +6289,7 @@ templates.command("create-workflow <id>").description("render and store a templa
|
|
|
6211
6289
|
}
|
|
6212
6290
|
});
|
|
6213
6291
|
var eventsHandle = events.command("handle").description("handle a Hasna event envelope");
|
|
6214
|
-
eventsHandle.command("todos-task").description("create a one-shot worker/verifier workflow loop for a todos task event").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--project-path <path>", "fallback project/repo working directory").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:todos-task").option("--dry-run", "print the workflow and loop input without storing anything").action(async (opts) => {
|
|
6292
|
+
eventsHandle.command("todos-task").description("create a one-shot worker/verifier workflow loop for a todos task event").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--project-path <path>", "fallback project/repo working directory").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:todos-task").option("--preflight", "check generated workflow steps before storing the workflow loop").option("--dry-run", "print the workflow and loop input without storing anything").action(async (opts) => {
|
|
6215
6293
|
const event = await readEventEnvelopeFromStdin();
|
|
6216
6294
|
const data = eventData(event);
|
|
6217
6295
|
const metadata = eventMetadata(event);
|
|
@@ -6280,7 +6358,12 @@ eventsHandle.command("todos-task").description("create a one-shot worker/verifie
|
|
|
6280
6358
|
leaseMs: 90 * 60000
|
|
6281
6359
|
};
|
|
6282
6360
|
if (opts.dryRun) {
|
|
6283
|
-
|
|
6361
|
+
const preflight = opts.preflight ? preflightStoredWorkflow(workflowSpecForPreflight(workflowBody, "event-preflight"), {
|
|
6362
|
+
name: workflowBody.name,
|
|
6363
|
+
type: "todos-task-event-workflow",
|
|
6364
|
+
event: event.id
|
|
6365
|
+
}, {}) : undefined;
|
|
6366
|
+
print({ deduped: false, idempotencyKey, event, workflow: workflowBody, loop: loopInput, preflight }, `dry-run ${loopName}`);
|
|
6284
6367
|
return;
|
|
6285
6368
|
}
|
|
6286
6369
|
const store = new Store;
|
|
@@ -6299,17 +6382,23 @@ eventsHandle.command("todos-task").description("create a one-shot worker/verifie
|
|
|
6299
6382
|
return;
|
|
6300
6383
|
}
|
|
6301
6384
|
const existingWorkflow = store.findWorkflowByName(workflowBody.name);
|
|
6385
|
+
const workflowPreflightSpec = existingWorkflow ?? workflowSpecForPreflight(workflowBody, "event-preflight");
|
|
6386
|
+
const preflight = opts.preflight ? preflightStoredWorkflow(workflowPreflightSpec, {
|
|
6387
|
+
name: workflowBody.name,
|
|
6388
|
+
type: "todos-task-event-workflow",
|
|
6389
|
+
event: event.id
|
|
6390
|
+
}, {}) : undefined;
|
|
6302
6391
|
const workflow = existingWorkflow ?? store.createWorkflow(workflowBody);
|
|
6303
6392
|
const loop = store.createLoop({
|
|
6304
6393
|
...loopInput,
|
|
6305
6394
|
target: { type: "workflow", workflowId: workflow.id }
|
|
6306
6395
|
});
|
|
6307
|
-
print({ deduped: false, idempotencyKey, event, workflow: publicWorkflow(workflow), loop: publicLoop(loop) }, `created ${loop.id} (${loop.name}) workflow=${workflow.name} event=${event.id} idempotency=${idempotencyKey}`);
|
|
6396
|
+
print({ deduped: false, idempotencyKey, event, workflow: publicWorkflow(workflow), loop: publicLoop(loop), preflight }, `created ${loop.id} (${loop.name}) workflow=${workflow.name} event=${event.id} idempotency=${idempotencyKey}`);
|
|
6308
6397
|
} finally {
|
|
6309
6398
|
store.close();
|
|
6310
6399
|
}
|
|
6311
6400
|
});
|
|
6312
|
-
eventsHandle.command("generic").description("create a one-shot worker/verifier workflow loop for any Hasna event").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--project-path <path>", "fallback project/repo working directory").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:generic").option("--dry-run", "print the workflow and loop input without storing anything").action(async (opts) => {
|
|
6401
|
+
eventsHandle.command("generic").description("create a one-shot worker/verifier workflow loop for any Hasna event").option("--provider <provider>", "agent provider", "codewith").option("--auth-profile <profile>", "provider-native auth profile; currently supported for codewith").option("--auth-profile-pool <profiles>", "comma-separated provider-native auth profile pool").option("--worker-auth-profile <profile>", "provider-native auth profile for worker step").option("--verifier-auth-profile <profile>", "provider-native auth profile for verifier step").option("--account <profile>", "OpenAccounts profile name").option("--account-pool <profiles>", "comma-separated OpenAccounts profile pool").option("--worker-account <profile>", "OpenAccounts profile for worker step").option("--verifier-account <profile>", "OpenAccounts profile for verifier step").option("--account-tool <tool>", "OpenAccounts tool id").option("--model <model>", "provider model").option("--variant <variant>", "provider-specific model variant or reasoning effort").option("--agent <agent>", "provider-specific agent").option("--permission-mode <mode>", "provider permission mode: default, plan, auto, or bypass", "bypass").option("--sandbox <mode>", "provider sandbox").option("--project-path <path>", "fallback project/repo working directory").option("--name-prefix <prefix>", "workflow/loop name prefix", "event:generic").option("--preflight", "check generated workflow steps before storing the workflow loop").option("--dry-run", "print the workflow and loop input without storing anything").action(async (opts) => {
|
|
6313
6402
|
const event = await readEventEnvelopeFromStdin();
|
|
6314
6403
|
const data = eventData(event);
|
|
6315
6404
|
const projectPath = opts.projectPath ?? taskEventField(data, ["working_dir", "workingDir", "project_path", "projectPath", "cwd", "repo_path", "repoPath"]) ?? process.cwd();
|
|
@@ -6359,7 +6448,12 @@ eventsHandle.command("generic").description("create a one-shot worker/verifier w
|
|
|
6359
6448
|
leaseMs: 90 * 60000
|
|
6360
6449
|
};
|
|
6361
6450
|
if (opts.dryRun) {
|
|
6362
|
-
|
|
6451
|
+
const preflight = opts.preflight ? preflightStoredWorkflow(workflowSpecForPreflight(workflowBody, "event-preflight"), {
|
|
6452
|
+
name: workflowBody.name,
|
|
6453
|
+
type: "generic-event-workflow",
|
|
6454
|
+
event: event.id
|
|
6455
|
+
}, {}) : undefined;
|
|
6456
|
+
print({ event, workflow: workflowBody, loop: loopInput, preflight }, `dry-run ${loopName}`);
|
|
6363
6457
|
return;
|
|
6364
6458
|
}
|
|
6365
6459
|
const store = new Store;
|
|
@@ -6371,12 +6465,18 @@ eventsHandle.command("generic").description("create a one-shot worker/verifier w
|
|
|
6371
6465
|
return;
|
|
6372
6466
|
}
|
|
6373
6467
|
const existingWorkflow = store.findWorkflowByName(workflowBody.name);
|
|
6468
|
+
const workflowPreflightSpec = existingWorkflow ?? workflowSpecForPreflight(workflowBody, "event-preflight");
|
|
6469
|
+
const preflight = opts.preflight ? preflightStoredWorkflow(workflowPreflightSpec, {
|
|
6470
|
+
name: workflowBody.name,
|
|
6471
|
+
type: "generic-event-workflow",
|
|
6472
|
+
event: event.id
|
|
6473
|
+
}, {}) : undefined;
|
|
6374
6474
|
const workflow = existingWorkflow ?? store.createWorkflow(workflowBody);
|
|
6375
6475
|
const loop = store.createLoop({
|
|
6376
6476
|
...loopInput,
|
|
6377
6477
|
target: { type: "workflow", workflowId: workflow.id }
|
|
6378
6478
|
});
|
|
6379
|
-
print({ deduped: false, event, workflow: publicWorkflow(workflow), loop: publicLoop(loop) }, `created ${loop.id} (${loop.name}) workflow=${workflow.name}`);
|
|
6479
|
+
print({ deduped: false, event, workflow: publicWorkflow(workflow), loop: publicLoop(loop), preflight }, `created ${loop.id} (${loop.name}) workflow=${workflow.name}`);
|
|
6380
6480
|
} finally {
|
|
6381
6481
|
store.close();
|
|
6382
6482
|
}
|
|
@@ -6441,27 +6541,20 @@ machines.command("show <id>").description("resolve a machine assignment").action
|
|
|
6441
6541
|
});
|
|
6442
6542
|
workflows.command("validate <file>").description("validate a workflow JSON file without storing or running it").option("--name <name>", "override workflow name from the file").option("--preflight", "also check account env and target executables").action((file, opts) => {
|
|
6443
6543
|
const body = workflowBodyFromJson(JSON.parse(readFileSync2(file, "utf8")), opts.name);
|
|
6444
|
-
const
|
|
6445
|
-
const workflow = {
|
|
6446
|
-
id: "validation",
|
|
6447
|
-
name: body.name,
|
|
6448
|
-
description: body.description,
|
|
6449
|
-
version: body.version ?? 1,
|
|
6450
|
-
status: "active",
|
|
6451
|
-
goal: body.goal,
|
|
6452
|
-
steps: body.steps,
|
|
6453
|
-
createdAt: now,
|
|
6454
|
-
updatedAt: now
|
|
6455
|
-
};
|
|
6544
|
+
const workflow = workflowSpecForPreflight(body);
|
|
6456
6545
|
const preflight = opts.preflight ? preflightWorkflow(workflow) : undefined;
|
|
6457
6546
|
print({ valid: true, workflow: publicWorkflow(workflow), preflight }, `valid workflow ${workflow.name} steps=${workflow.steps.length}`);
|
|
6458
6547
|
});
|
|
6459
|
-
workflows.command("create <file>").description("validate and store a workflow JSON file").option("--name <name>", "override workflow name from the file").action((file, opts) => {
|
|
6548
|
+
workflows.command("create <file>").description("validate and store a workflow JSON file").option("--name <name>", "override workflow name from the file").option("--preflight", "also check account env and target executables before storing").action((file, opts) => {
|
|
6460
6549
|
const store = new Store;
|
|
6461
6550
|
try {
|
|
6462
6551
|
const body = workflowBodyFromJson(JSON.parse(readFileSync2(file, "utf8")), opts.name);
|
|
6552
|
+
const preflight = opts.preflight ? preflightStoredWorkflow(workflowSpecForPreflight(body, "creation-preflight"), { name: body.name, type: "workflow" }, {}) : undefined;
|
|
6463
6553
|
const workflow = store.createWorkflow(body);
|
|
6464
|
-
|
|
6554
|
+
if (preflight !== undefined)
|
|
6555
|
+
print({ workflow: publicWorkflow(workflow), preflight }, `created workflow ${workflow.id} (${workflow.name}) steps=${workflow.steps.length}`);
|
|
6556
|
+
else
|
|
6557
|
+
print(publicWorkflow(workflow), `created workflow ${workflow.id} (${workflow.name}) steps=${workflow.steps.length}`);
|
|
6465
6558
|
} finally {
|
|
6466
6559
|
store.close();
|
|
6467
6560
|
}
|
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.24",
|
|
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,
|
|
@@ -4796,6 +4851,7 @@ var CLASSIFICATIONS = [
|
|
|
4796
4851
|
"context_length",
|
|
4797
4852
|
"schema_response_format",
|
|
4798
4853
|
"node_init",
|
|
4854
|
+
"preflight",
|
|
4799
4855
|
"timeout",
|
|
4800
4856
|
"sigsegv",
|
|
4801
4857
|
"skipped_previous_active",
|
|
@@ -4843,6 +4899,8 @@ function classifyRunFailure(run) {
|
|
|
4843
4899
|
classification = "timeout";
|
|
4844
4900
|
else if (run.status === "skipped" && /previous run still active/.test(text))
|
|
4845
4901
|
classification = "skipped_previous_active";
|
|
4902
|
+
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))
|
|
4903
|
+
classification = "preflight";
|
|
4846
4904
|
else if (/rate limit|too many requests|429\b|quota exceeded/.test(text))
|
|
4847
4905
|
classification = "rate_limit";
|
|
4848
4906
|
else if (/unauthorized|authentication|auth\b|api key|invalid token|permission denied|401\b|403\b/.test(text))
|
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
|
@@ -59,10 +59,12 @@ loops create command repo-status \
|
|
|
59
59
|
--preflight
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
-
`--preflight` is available on `loops create command`, `loops create agent`,
|
|
63
|
-
`loops create workflow
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
`--preflight` is available on `loops create command`, `loops create agent`,
|
|
63
|
+
`loops create workflow`, `loops workflows create`, and event-router commands such
|
|
64
|
+
as `loops events handle todos-task` and `loops events handle generic`. It checks
|
|
65
|
+
target executables and configured account profiles before loop or workflow rows
|
|
66
|
+
are stored, so a missing command, provider binary, OpenAccounts profile, native
|
|
67
|
+
Codewith auth profile, or workflow step dependency fails without creating a
|
|
66
68
|
scheduled loop. Use `--json` with `--preflight` to capture stable machine-readable
|
|
67
69
|
preflight evidence.
|
|
68
70
|
|
|
@@ -71,6 +73,12 @@ accounts because the command string is interpreted later by the shell. Use
|
|
|
71
73
|
`--no-shell` or workflow command `args` when you need executable-level
|
|
72
74
|
validation before storing the loop.
|
|
73
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
|
+
|
|
74
82
|
Run a Claude loop every morning:
|
|
75
83
|
|
|
76
84
|
```bash
|
package/package.json
CHANGED