@hasna/loops 0.3.51 → 0.3.53
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 +15 -0
- package/dist/cli/index.js +374 -27
- package/dist/daemon/index.js +61 -2
- package/dist/index.js +330 -10
- package/dist/lib/store.d.ts +1 -0
- package/dist/lib/store.js +60 -1
- package/dist/lib/templates.d.ts +13 -0
- package/dist/sdk/index.js +60 -1
- package/docs/USAGE.md +17 -0
- package/package.json +1 -1
package/dist/daemon/index.js
CHANGED
|
@@ -623,7 +623,7 @@ function writeWorkflowRunManifest(args) {
|
|
|
623
623
|
var DEFAULT_RECOVERY_BATCH_LIMIT = 100;
|
|
624
624
|
var DEFAULT_RECOVERY_SCAN_MULTIPLIER = 5;
|
|
625
625
|
var LIVE_EXPIRED_RUN_GRACE_MS = 60000;
|
|
626
|
-
var GENERATED_ROUTE_TEMPLATE_IDS = new Set(["todos-task-worker-verifier", "event-worker-verifier"]);
|
|
626
|
+
var GENERATED_ROUTE_TEMPLATE_IDS = new Set(["todos-task-worker-verifier", "task-lifecycle", "event-worker-verifier"]);
|
|
627
627
|
var GENERATED_ROUTE_KEYS = new Set(["todos-task", "generic-event"]);
|
|
628
628
|
function rowToLoop(row) {
|
|
629
629
|
return {
|
|
@@ -1567,6 +1567,65 @@ class Store {
|
|
|
1567
1567
|
throw new Error(`workflow invocation not found after create: ${id}`);
|
|
1568
1568
|
return rowToWorkflowInvocation(row);
|
|
1569
1569
|
}
|
|
1570
|
+
refreshWorkflowInvocationForWorkItem(workItemId, input) {
|
|
1571
|
+
const sourceDedupeKey = input.sourceRef.dedupeKey ?? undefined;
|
|
1572
|
+
if (!sourceDedupeKey)
|
|
1573
|
+
throw new Error("cannot refresh workflow invocation without sourceRef.dedupeKey");
|
|
1574
|
+
const now = nowIso();
|
|
1575
|
+
const claimableStatuses = ["queued", "deferred", "failed", "dead_letter", "cancelled"];
|
|
1576
|
+
const statusBindings = Object.fromEntries(claimableStatuses.map((status, index) => [`$status${index}`, status]));
|
|
1577
|
+
const placeholders = claimableStatuses.map((_, index) => `$status${index}`).join(",");
|
|
1578
|
+
const result = this.db.query(`UPDATE workflow_invocations
|
|
1579
|
+
SET workflow_id=COALESCE($workflowId, workflow_id),
|
|
1580
|
+
template_id=COALESCE($templateId, template_id),
|
|
1581
|
+
source_id=COALESCE($sourceId, source_id),
|
|
1582
|
+
source_json=$sourceJson,
|
|
1583
|
+
subject_kind=$subjectKind,
|
|
1584
|
+
subject_id=COALESCE($subjectId, subject_id),
|
|
1585
|
+
subject_path=COALESCE($subjectPath, subject_path),
|
|
1586
|
+
subject_url=COALESCE($subjectUrl, subject_url),
|
|
1587
|
+
subject_json=$subjectJson,
|
|
1588
|
+
intent=$intent,
|
|
1589
|
+
scope_json=COALESCE($scopeJson, scope_json),
|
|
1590
|
+
output_policy_json=COALESCE($outputPolicyJson, output_policy_json),
|
|
1591
|
+
updated_at=$updated
|
|
1592
|
+
WHERE source_kind=$sourceKind
|
|
1593
|
+
AND source_dedupe_key=$sourceDedupeKey
|
|
1594
|
+
AND EXISTS (
|
|
1595
|
+
SELECT 1
|
|
1596
|
+
FROM workflow_work_items
|
|
1597
|
+
WHERE id=$workItemId
|
|
1598
|
+
AND invocation_id=workflow_invocations.id
|
|
1599
|
+
AND status IN (${placeholders})
|
|
1600
|
+
)`).run({
|
|
1601
|
+
$workItemId: workItemId,
|
|
1602
|
+
$sourceKind: input.sourceRef.kind,
|
|
1603
|
+
$sourceDedupeKey: sourceDedupeKey,
|
|
1604
|
+
$workflowId: input.workflowId ?? null,
|
|
1605
|
+
$templateId: input.templateId ?? null,
|
|
1606
|
+
$sourceId: input.sourceRef.id ?? null,
|
|
1607
|
+
$sourceJson: JSON.stringify(input.sourceRef),
|
|
1608
|
+
$subjectKind: input.subjectRef.kind,
|
|
1609
|
+
$subjectId: input.subjectRef.id ?? null,
|
|
1610
|
+
$subjectPath: input.subjectRef.path ?? null,
|
|
1611
|
+
$subjectUrl: input.subjectRef.url ?? null,
|
|
1612
|
+
$subjectJson: JSON.stringify(input.subjectRef),
|
|
1613
|
+
$intent: input.intent,
|
|
1614
|
+
$scopeJson: input.scope ? JSON.stringify(input.scope) : null,
|
|
1615
|
+
$outputPolicyJson: input.outputPolicy ? JSON.stringify(input.outputPolicy) : null,
|
|
1616
|
+
$updated: now,
|
|
1617
|
+
...statusBindings
|
|
1618
|
+
});
|
|
1619
|
+
if (result.changes !== 1)
|
|
1620
|
+
throw new Error(`workflow work item is not refreshable: ${workItemId}`);
|
|
1621
|
+
const updated = this.db.query(`SELECT workflow_invocations.*
|
|
1622
|
+
FROM workflow_invocations
|
|
1623
|
+
JOIN workflow_work_items ON workflow_work_items.invocation_id = workflow_invocations.id
|
|
1624
|
+
WHERE workflow_work_items.id = ?`).get(workItemId);
|
|
1625
|
+
if (!updated)
|
|
1626
|
+
throw new Error(`workflow invocation not found after refresh for work item: ${workItemId}`);
|
|
1627
|
+
return rowToWorkflowInvocation(updated);
|
|
1628
|
+
}
|
|
1570
1629
|
getWorkflowInvocation(id) {
|
|
1571
1630
|
const row = this.db.query("SELECT * FROM workflow_invocations WHERE id = ?").get(id);
|
|
1572
1631
|
return row ? rowToWorkflowInvocation(row) : undefined;
|
|
@@ -5240,7 +5299,7 @@ function enableStartup(result) {
|
|
|
5240
5299
|
// package.json
|
|
5241
5300
|
var package_default = {
|
|
5242
5301
|
name: "@hasna/loops",
|
|
5243
|
-
version: "0.3.
|
|
5302
|
+
version: "0.3.53",
|
|
5244
5303
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
5245
5304
|
type: "module",
|
|
5246
5305
|
main: "dist/index.js",
|
package/dist/index.js
CHANGED
|
@@ -621,7 +621,7 @@ function writeWorkflowRunManifest(args) {
|
|
|
621
621
|
var DEFAULT_RECOVERY_BATCH_LIMIT = 100;
|
|
622
622
|
var DEFAULT_RECOVERY_SCAN_MULTIPLIER = 5;
|
|
623
623
|
var LIVE_EXPIRED_RUN_GRACE_MS = 60000;
|
|
624
|
-
var GENERATED_ROUTE_TEMPLATE_IDS = new Set(["todos-task-worker-verifier", "event-worker-verifier"]);
|
|
624
|
+
var GENERATED_ROUTE_TEMPLATE_IDS = new Set(["todos-task-worker-verifier", "task-lifecycle", "event-worker-verifier"]);
|
|
625
625
|
var GENERATED_ROUTE_KEYS = new Set(["todos-task", "generic-event"]);
|
|
626
626
|
function rowToLoop(row) {
|
|
627
627
|
return {
|
|
@@ -1565,6 +1565,65 @@ class Store {
|
|
|
1565
1565
|
throw new Error(`workflow invocation not found after create: ${id}`);
|
|
1566
1566
|
return rowToWorkflowInvocation(row);
|
|
1567
1567
|
}
|
|
1568
|
+
refreshWorkflowInvocationForWorkItem(workItemId, input) {
|
|
1569
|
+
const sourceDedupeKey = input.sourceRef.dedupeKey ?? undefined;
|
|
1570
|
+
if (!sourceDedupeKey)
|
|
1571
|
+
throw new Error("cannot refresh workflow invocation without sourceRef.dedupeKey");
|
|
1572
|
+
const now = nowIso();
|
|
1573
|
+
const claimableStatuses = ["queued", "deferred", "failed", "dead_letter", "cancelled"];
|
|
1574
|
+
const statusBindings = Object.fromEntries(claimableStatuses.map((status, index) => [`$status${index}`, status]));
|
|
1575
|
+
const placeholders = claimableStatuses.map((_, index) => `$status${index}`).join(",");
|
|
1576
|
+
const result = this.db.query(`UPDATE workflow_invocations
|
|
1577
|
+
SET workflow_id=COALESCE($workflowId, workflow_id),
|
|
1578
|
+
template_id=COALESCE($templateId, template_id),
|
|
1579
|
+
source_id=COALESCE($sourceId, source_id),
|
|
1580
|
+
source_json=$sourceJson,
|
|
1581
|
+
subject_kind=$subjectKind,
|
|
1582
|
+
subject_id=COALESCE($subjectId, subject_id),
|
|
1583
|
+
subject_path=COALESCE($subjectPath, subject_path),
|
|
1584
|
+
subject_url=COALESCE($subjectUrl, subject_url),
|
|
1585
|
+
subject_json=$subjectJson,
|
|
1586
|
+
intent=$intent,
|
|
1587
|
+
scope_json=COALESCE($scopeJson, scope_json),
|
|
1588
|
+
output_policy_json=COALESCE($outputPolicyJson, output_policy_json),
|
|
1589
|
+
updated_at=$updated
|
|
1590
|
+
WHERE source_kind=$sourceKind
|
|
1591
|
+
AND source_dedupe_key=$sourceDedupeKey
|
|
1592
|
+
AND EXISTS (
|
|
1593
|
+
SELECT 1
|
|
1594
|
+
FROM workflow_work_items
|
|
1595
|
+
WHERE id=$workItemId
|
|
1596
|
+
AND invocation_id=workflow_invocations.id
|
|
1597
|
+
AND status IN (${placeholders})
|
|
1598
|
+
)`).run({
|
|
1599
|
+
$workItemId: workItemId,
|
|
1600
|
+
$sourceKind: input.sourceRef.kind,
|
|
1601
|
+
$sourceDedupeKey: sourceDedupeKey,
|
|
1602
|
+
$workflowId: input.workflowId ?? null,
|
|
1603
|
+
$templateId: input.templateId ?? null,
|
|
1604
|
+
$sourceId: input.sourceRef.id ?? null,
|
|
1605
|
+
$sourceJson: JSON.stringify(input.sourceRef),
|
|
1606
|
+
$subjectKind: input.subjectRef.kind,
|
|
1607
|
+
$subjectId: input.subjectRef.id ?? null,
|
|
1608
|
+
$subjectPath: input.subjectRef.path ?? null,
|
|
1609
|
+
$subjectUrl: input.subjectRef.url ?? null,
|
|
1610
|
+
$subjectJson: JSON.stringify(input.subjectRef),
|
|
1611
|
+
$intent: input.intent,
|
|
1612
|
+
$scopeJson: input.scope ? JSON.stringify(input.scope) : null,
|
|
1613
|
+
$outputPolicyJson: input.outputPolicy ? JSON.stringify(input.outputPolicy) : null,
|
|
1614
|
+
$updated: now,
|
|
1615
|
+
...statusBindings
|
|
1616
|
+
});
|
|
1617
|
+
if (result.changes !== 1)
|
|
1618
|
+
throw new Error(`workflow work item is not refreshable: ${workItemId}`);
|
|
1619
|
+
const updated = this.db.query(`SELECT workflow_invocations.*
|
|
1620
|
+
FROM workflow_invocations
|
|
1621
|
+
JOIN workflow_work_items ON workflow_work_items.invocation_id = workflow_invocations.id
|
|
1622
|
+
WHERE workflow_work_items.id = ?`).get(workItemId);
|
|
1623
|
+
if (!updated)
|
|
1624
|
+
throw new Error(`workflow invocation not found after refresh for work item: ${workItemId}`);
|
|
1625
|
+
return rowToWorkflowInvocation(updated);
|
|
1626
|
+
}
|
|
1568
1627
|
getWorkflowInvocation(id) {
|
|
1569
1628
|
const row = this.db.query("SELECT * FROM workflow_invocations WHERE id = ?").get(id);
|
|
1570
1629
|
return row ? rowToWorkflowInvocation(row) : undefined;
|
|
@@ -5087,6 +5146,10 @@ var TEMPLATE_SUMMARIES = [
|
|
|
5087
5146
|
{ name: "taskId", required: true, description: "Todos task id." },
|
|
5088
5147
|
{ name: "projectPath", required: true, description: "Repository or project working directory." },
|
|
5089
5148
|
{ name: "authProfilePool", description: "Comma-separated Codewith profiles for worker/verifier rotation." },
|
|
5149
|
+
{ name: "triageAuthProfile", description: "Provider-native auth profile for the triage step." },
|
|
5150
|
+
{ name: "plannerAuthProfile", description: "Provider-native auth profile for the planner step." },
|
|
5151
|
+
{ name: "workerAuthProfile", description: "Provider-native auth profile for the worker step." },
|
|
5152
|
+
{ name: "verifierAuthProfile", description: "Provider-native auth profile for the verifier step." },
|
|
5090
5153
|
{ name: "accountPool", description: "Comma-separated OpenAccounts profiles for non-Codewith providers." },
|
|
5091
5154
|
{ name: "provider", default: "codewith", description: "Agent provider." },
|
|
5092
5155
|
{ name: "sandbox", default: "workspace-write", description: "Provider sandbox mode." },
|
|
@@ -5209,9 +5272,17 @@ function rolePoolValue(pool, seed, role) {
|
|
|
5209
5272
|
const workerIndex = stableIndex(seed, pool.length);
|
|
5210
5273
|
if (role === "worker" || pool.length === 1)
|
|
5211
5274
|
return pool[workerIndex];
|
|
5212
|
-
|
|
5275
|
+
if (role === "verifier")
|
|
5276
|
+
return pool[(workerIndex + 1) % pool.length];
|
|
5277
|
+
if (role === "planner")
|
|
5278
|
+
return pool[(workerIndex + 2) % pool.length];
|
|
5279
|
+
return pool[(workerIndex + 3) % pool.length];
|
|
5213
5280
|
}
|
|
5214
5281
|
function authProfileForRole(input, role, seed) {
|
|
5282
|
+
if (role === "triage" && input.triageAuthProfile)
|
|
5283
|
+
return input.triageAuthProfile;
|
|
5284
|
+
if (role === "planner" && input.plannerAuthProfile)
|
|
5285
|
+
return input.plannerAuthProfile;
|
|
5215
5286
|
if (role === "worker" && input.workerAuthProfile)
|
|
5216
5287
|
return input.workerAuthProfile;
|
|
5217
5288
|
if (role === "verifier" && input.verifierAuthProfile)
|
|
@@ -5219,6 +5290,10 @@ function authProfileForRole(input, role, seed) {
|
|
|
5219
5290
|
return rolePoolValue(input.authProfilePool, seed, role) ?? input.authProfile;
|
|
5220
5291
|
}
|
|
5221
5292
|
function accountForRole(input, role, seed) {
|
|
5293
|
+
if (role === "triage" && input.triageAccount)
|
|
5294
|
+
return input.triageAccount;
|
|
5295
|
+
if (role === "planner" && input.plannerAccount)
|
|
5296
|
+
return input.plannerAccount;
|
|
5222
5297
|
if (role === "worker" && input.workerAccount)
|
|
5223
5298
|
return input.workerAccount;
|
|
5224
5299
|
if (role === "verifier" && input.verifierAccount)
|
|
@@ -5405,10 +5480,10 @@ function worktreePrompt(plan) {
|
|
|
5405
5480
|
function assertNativeAuthProfileSupport(input, provider) {
|
|
5406
5481
|
if (provider === "codewith")
|
|
5407
5482
|
return;
|
|
5408
|
-
const hasNativeAuthProfiles = Boolean(input.authProfile || input.authProfilePool?.length || input.workerAuthProfile || input.verifierAuthProfile);
|
|
5483
|
+
const hasNativeAuthProfiles = Boolean(input.authProfile || input.authProfilePool?.length || input.triageAuthProfile || input.plannerAuthProfile || input.workerAuthProfile || input.verifierAuthProfile);
|
|
5409
5484
|
if (!hasNativeAuthProfiles)
|
|
5410
5485
|
return;
|
|
5411
|
-
throw new Error(`authProfile, authProfilePool, workerAuthProfile, and verifierAuthProfile are supported only for provider codewith; use account/accountPool for ${provider} profile isolation`);
|
|
5486
|
+
throw new Error(`authProfile, authProfilePool, triageAuthProfile, plannerAuthProfile, workerAuthProfile, and verifierAuthProfile are supported only for provider codewith; use account/accountPool for ${provider} profile isolation`);
|
|
5412
5487
|
}
|
|
5413
5488
|
function failClosedSandbox(input, provider, sandbox) {
|
|
5414
5489
|
if (!["codewith", "codex"].includes(provider))
|
|
@@ -5460,9 +5535,10 @@ function agentTarget(input, prompt, role, seed, plan) {
|
|
|
5460
5535
|
function workflowStepsWithWorktree(plan, steps) {
|
|
5461
5536
|
if (!plan.prepareStep)
|
|
5462
5537
|
return steps;
|
|
5538
|
+
const firstStepId = steps[0]?.id;
|
|
5463
5539
|
return [
|
|
5464
5540
|
plan.prepareStep,
|
|
5465
|
-
...steps.map((step) => step.id ===
|
|
5541
|
+
...steps.map((step) => step.id === firstStepId ? { ...step, dependsOn: [...new Set([...step.dependsOn ?? [], plan.prepareStep.id])] } : step)
|
|
5466
5542
|
];
|
|
5467
5543
|
}
|
|
5468
5544
|
function assertRecord(value, label) {
|
|
@@ -5897,6 +5973,222 @@ function renderTodosTaskWorkerVerifierWorkflow(input) {
|
|
|
5897
5973
|
])
|
|
5898
5974
|
};
|
|
5899
5975
|
}
|
|
5976
|
+
function renderTaskLifecycleWorkflow(input) {
|
|
5977
|
+
if (!input.taskId?.trim())
|
|
5978
|
+
throw new Error("taskId is required");
|
|
5979
|
+
if (!input.projectPath?.trim())
|
|
5980
|
+
throw new Error("projectPath is required");
|
|
5981
|
+
const todosProjectPath = input.todosProjectPath ?? input.routeProjectPath ?? input.projectPath;
|
|
5982
|
+
const plan = worktreePlan(input, input.taskId);
|
|
5983
|
+
const taskContext = {
|
|
5984
|
+
taskId: input.taskId,
|
|
5985
|
+
taskTitle: input.taskTitle,
|
|
5986
|
+
taskDescription: input.taskDescription,
|
|
5987
|
+
eventId: input.eventId,
|
|
5988
|
+
eventType: input.eventType,
|
|
5989
|
+
projectPath: input.projectPath,
|
|
5990
|
+
routeProjectPath: input.routeProjectPath,
|
|
5991
|
+
projectGroup: input.projectGroup,
|
|
5992
|
+
todosProjectPath,
|
|
5993
|
+
worktree: {
|
|
5994
|
+
mode: plan.mode,
|
|
5995
|
+
enabled: plan.enabled,
|
|
5996
|
+
cwd: plan.cwd,
|
|
5997
|
+
path: plan.path,
|
|
5998
|
+
branch: plan.branch,
|
|
5999
|
+
reason: plan.reason
|
|
6000
|
+
}
|
|
6001
|
+
};
|
|
6002
|
+
const shared = [
|
|
6003
|
+
worktreePrompt(plan),
|
|
6004
|
+
`Todos project path: ${todosProjectPath}`,
|
|
6005
|
+
"Use these exact todos commands so worktree cwd inference cannot attach to the wrong project:",
|
|
6006
|
+
`- Inspect first: todos --project ${todosProjectPath} inspect ${input.taskId}`,
|
|
6007
|
+
`- Record evidence: todos --project ${todosProjectPath} comment ${input.taskId} "<concise evidence, decision, or blocker>"`,
|
|
6008
|
+
"Do not dispatch or paste prompts into tmux panes. If additional work is required, create or update deduped todos tasks so task-created routing can start a fresh headless workflow.",
|
|
6009
|
+
"Preserve unrelated user changes and keep scope tied to the task acceptance criteria.",
|
|
6010
|
+
"",
|
|
6011
|
+
`Task context JSON: ${compactJson(taskContext)}`
|
|
6012
|
+
].join(`
|
|
6013
|
+
`);
|
|
6014
|
+
const gateMarker = (stage, state) => `openloops:${stage}=${state} task=${input.taskId}${input.eventId ? ` event=${input.eventId}` : ""}`;
|
|
6015
|
+
const gateCommand = (stage) => [
|
|
6016
|
+
"set -euo pipefail",
|
|
6017
|
+
`task_json="$(todos --project ${shellQuote2(todosProjectPath)} --json inspect ${shellQuote2(input.taskId)})"`,
|
|
6018
|
+
`TASK_JSON="$task_json" STAGE=${shellQuote2(stage)} bun - <<'BUN'`,
|
|
6019
|
+
"const raw = process.env.TASK_JSON || '{}';",
|
|
6020
|
+
"const payload = JSON.parse(raw);",
|
|
6021
|
+
"const task = payload.task && typeof payload.task === 'object' ? payload.task : payload;",
|
|
6022
|
+
"const stage = process.env.STAGE || 'lifecycle';",
|
|
6023
|
+
`const goMarker = ${JSON.stringify(gateMarker(stage, "go"))};`,
|
|
6024
|
+
`const blockedMarker = ${JSON.stringify(gateMarker(stage, "blocked"))};`,
|
|
6025
|
+
"const status = String(task.status || '').toLowerCase().replace(/_/g, '-');",
|
|
6026
|
+
"const metadata = task.metadata && typeof task.metadata === 'object' ? task.metadata : {};",
|
|
6027
|
+
"const automation = metadata.automation && typeof metadata.automation === 'object' ? metadata.automation : {};",
|
|
6028
|
+
"const comments = Array.isArray(task.comments) ? task.comments : [];",
|
|
6029
|
+
"const blockedStatuses = new Set(['blocked', 'cancelled', 'canceled', 'failed', 'archived', 'deleted', 'done', 'completed']);",
|
|
6030
|
+
"const truthy = (value) => value === true || value === 1 || value === '1' || String(value).toLowerCase() === 'true' || String(value).toLowerCase() === 'yes';",
|
|
6031
|
+
"const falsey = (value) => value === false || value === 0 || value === '0' || String(value).toLowerCase() === 'false' || String(value).toLowerCase() === 'no';",
|
|
6032
|
+
"const commentText = (comment) => String(comment?.content ?? comment?.text ?? comment?.body ?? comment?.comment ?? '');",
|
|
6033
|
+
"const tagsFrom = (value) => Array.isArray(value) ? value.map(String) : typeof value === 'string' ? value.split(',') : [];",
|
|
6034
|
+
"const records = [task, metadata, automation].filter((entry) => entry && typeof entry === 'object');",
|
|
6035
|
+
"const tags = new Set(records.flatMap((entry) => [entry.tags, entry.task_tags, entry.taskTags].flatMap(tagsFrom)).map((tag) => tag.trim().toLowerCase()).filter(Boolean));",
|
|
6036
|
+
"const markerState = (comment) => {",
|
|
6037
|
+
" const line = commentText(comment).trimStart().split(/\\r?\\n/, 1)[0]?.trimEnd() || '';",
|
|
6038
|
+
" if (line === goMarker) return 'go';",
|
|
6039
|
+
" if (line === blockedMarker) return 'blocked';",
|
|
6040
|
+
" if (line.startsWith(`openloops:${stage}=`)) return `invalid marker: ${line}`;",
|
|
6041
|
+
" return undefined;",
|
|
6042
|
+
"};",
|
|
6043
|
+
"const markerTime = (comment, index) => {",
|
|
6044
|
+
" const rawTime = comment?.created_at ?? comment?.createdAt ?? comment?.updated_at ?? comment?.updatedAt;",
|
|
6045
|
+
" const parsed = rawTime ? Date.parse(String(rawTime)) : Number.NaN;",
|
|
6046
|
+
" return Number.isFinite(parsed) ? parsed : index;",
|
|
6047
|
+
"};",
|
|
6048
|
+
"const markers = comments",
|
|
6049
|
+
" .map((comment, index) => ({ state: markerState(comment), order: markerTime(comment, index), index }))",
|
|
6050
|
+
" .filter((entry) => entry.state)",
|
|
6051
|
+
" .sort((a, b) => a.order - b.order || a.index - b.index);",
|
|
6052
|
+
"const latestMarker = markers.at(-1)?.state;",
|
|
6053
|
+
"const blockers = [];",
|
|
6054
|
+
"if (blockedStatuses.has(status)) blockers.push(`task status is ${status}`);",
|
|
6055
|
+
"for (const tag of ['no-auto', 'manual', 'manual-required', 'approval-required']) {",
|
|
6056
|
+
" if (tags.has(tag)) blockers.push(`task has disallowed tag ${tag}`);",
|
|
6057
|
+
"}",
|
|
6058
|
+
"for (const [key, source] of records.entries()) {",
|
|
6059
|
+
" if (truthy(source.no_auto) || truthy(source.noAuto)) blockers.push(`${key}.no_auto is true`);",
|
|
6060
|
+
" if (truthy(source.manual) || truthy(source.manual_required) || truthy(source.manualRequired) || String(source.mode || '').toLowerCase() === 'manual') blockers.push(`${key}.manual/mode requires manual handling`);",
|
|
6061
|
+
" if (truthy(source.requires_approval) || truthy(source.requiresApproval) || truthy(source.approval_required) || truthy(source.approvalRequired)) blockers.push(`${key}.requires_approval is true`);",
|
|
6062
|
+
" if (falsey(source.auto) || falsey(source.enabled) || falsey(source.allowed) || falsey(source.automation_allowed) || falsey(source.automationAllowed) || falsey(source.loop_allowed) || falsey(source.loopAllowed)) blockers.push(`${key} disallows loop automation`);",
|
|
6063
|
+
"}",
|
|
6064
|
+
"if (latestMarker !== 'go') blockers.push(latestMarker ? `latest ${stage} marker is ${latestMarker}` : `missing exact ${goMarker} comment`);",
|
|
6065
|
+
"if (blockers.length) {",
|
|
6066
|
+
" console.error(`task lifecycle ${stage} gate blocked ${task.id || task.taskId || 'task'}: ${blockers.join('; ')}`);",
|
|
6067
|
+
" process.exit(12);",
|
|
6068
|
+
"}",
|
|
6069
|
+
"console.log(`task lifecycle ${stage} gate passed for ${task.id || task.taskId || 'task'} status=${status || 'unknown'}`);",
|
|
6070
|
+
"BUN"
|
|
6071
|
+
].join(`
|
|
6072
|
+
`);
|
|
6073
|
+
const triagePrompt = [
|
|
6074
|
+
`/goal Triage todos task ${input.taskId} for safe automated execution.`,
|
|
6075
|
+
"",
|
|
6076
|
+
"You are the triage step for a full task-triggered OpenLoops lifecycle.",
|
|
6077
|
+
shared,
|
|
6078
|
+
"Decide whether the task is eligible for loop execution. Check status, dependencies, duplicate tasks, no-auto/manual/approval metadata, project path, acceptance criteria, and whether the requested work should be split before implementation.",
|
|
6079
|
+
"Do not implement repo changes in this step.",
|
|
6080
|
+
`If the task is eligible for automated planning, add a task comment whose first line is exactly: ${gateMarker("triage", "go")}`,
|
|
6081
|
+
"Include the triage decision, duplicates/dependencies found, and any follow-up tasks created in that same comment.",
|
|
6082
|
+
`If the task should not proceed automatically, run: todos --project ${todosProjectPath} update ${input.taskId} --status blocked`,
|
|
6083
|
+
`Then add a task comment whose first line is exactly: ${gateMarker("triage", "blocked")}`,
|
|
6084
|
+
"The deterministic triage gate will stop later steps unless the latest triage marker is the exact go marker and the task has no blocked/no-auto/manual/approval-required state."
|
|
6085
|
+
].join(`
|
|
6086
|
+
`);
|
|
6087
|
+
const plannerPrompt = [
|
|
6088
|
+
`/goal Plan todos task ${input.taskId} before implementation.`,
|
|
6089
|
+
"",
|
|
6090
|
+
"You are the planner step for a full task-triggered OpenLoops lifecycle.",
|
|
6091
|
+
shared,
|
|
6092
|
+
"Read the triage comment and current task details.",
|
|
6093
|
+
`If the task is ready for implementation, add a task comment whose first line is exactly: ${gateMarker("planner", "go")}`,
|
|
6094
|
+
"In that same comment, include a concise implementation plan: files/areas to inspect, validation commands, risk checks, expected commit/PR behavior, and any cross-repo tasks that should be created separately.",
|
|
6095
|
+
`Do not implement repo changes in this step. If the task is too broad or unsafe for automation, run: todos --project ${todosProjectPath} update ${input.taskId} --status blocked`,
|
|
6096
|
+
`Then add a task comment whose first line is exactly: ${gateMarker("planner", "blocked")}`,
|
|
6097
|
+
"Create smaller deduped tasks and record evidence. The deterministic planner gate will stop the worker unless the latest planner marker is the exact go marker and the task has no blocked/no-auto/manual/approval-required state."
|
|
6098
|
+
].join(`
|
|
6099
|
+
`);
|
|
6100
|
+
const workerPrompt = [
|
|
6101
|
+
`/goal Complete todos task ${input.taskId} according to the planner evidence.`,
|
|
6102
|
+
"",
|
|
6103
|
+
"You are the worker step for a full task-triggered OpenLoops lifecycle.",
|
|
6104
|
+
shared,
|
|
6105
|
+
`- Claim/start if appropriate: todos --project ${todosProjectPath} start ${input.taskId}`,
|
|
6106
|
+
"Read the triage and planner comments first. Implement only the scoped task, run focused validation, and record changed files, commits, evidence, blockers, and residual risks.",
|
|
6107
|
+
"Do not mark the task complete in the worker step; the verifier step owns completion after independent validation."
|
|
6108
|
+
].join(`
|
|
6109
|
+
`);
|
|
6110
|
+
const verifierPrompt = [
|
|
6111
|
+
`/goal Verify todos task ${input.taskId} after the full lifecycle worker step.`,
|
|
6112
|
+
"",
|
|
6113
|
+
"You are the verifier step for a full task-triggered OpenLoops lifecycle.",
|
|
6114
|
+
shared,
|
|
6115
|
+
`- Record verification: todos --project ${todosProjectPath} comment ${input.taskId} "<verification evidence or blocker>"`,
|
|
6116
|
+
`- If valid and complete: todos --project ${todosProjectPath} done ${input.taskId}`,
|
|
6117
|
+
"Use fresh context. Inspect triage, plan, worker evidence, repo state, commits, tests, and acceptance criteria. Act as an adversarial reviewer focused on correctness, regressions, missing tests, security, and incomplete requirements.",
|
|
6118
|
+
"If the work is valid, record verification evidence in todos and mark/leave the task completed according to the todos CLI. If not valid, add precise follow-up tasks or comments and leave the original task open or blocked with clear evidence.",
|
|
6119
|
+
"Do not make broad unrelated changes. Only apply tiny verification fixes when they are necessary and low risk; otherwise create follow-up tasks."
|
|
6120
|
+
].join(`
|
|
6121
|
+
`);
|
|
6122
|
+
return {
|
|
6123
|
+
name: `task-lifecycle-${input.taskId.slice(0, 8)}-triage-plan-worker-verifier`,
|
|
6124
|
+
description: `Full task lifecycle workflow for ${taskLabel(input)}`,
|
|
6125
|
+
version: 1,
|
|
6126
|
+
steps: workflowStepsWithWorktree(plan, [
|
|
6127
|
+
{
|
|
6128
|
+
id: "triage",
|
|
6129
|
+
name: "Triage",
|
|
6130
|
+
description: "Check task eligibility, duplicates, dependencies, and automation gates.",
|
|
6131
|
+
target: agentTarget(input, triagePrompt, "triage", input.taskId, plan),
|
|
6132
|
+
timeoutMs: 20 * 60000
|
|
6133
|
+
},
|
|
6134
|
+
{
|
|
6135
|
+
id: "triage-gate",
|
|
6136
|
+
name: "Triage Gate",
|
|
6137
|
+
description: "Stop the lifecycle before planning when triage blocked or disallowed automation.",
|
|
6138
|
+
dependsOn: ["triage"],
|
|
6139
|
+
target: {
|
|
6140
|
+
type: "command",
|
|
6141
|
+
command: "bash",
|
|
6142
|
+
args: ["-lc", gateCommand("triage")],
|
|
6143
|
+
cwd: plan.cwd,
|
|
6144
|
+
timeoutMs: 2 * 60000
|
|
6145
|
+
},
|
|
6146
|
+
timeoutMs: 2 * 60000
|
|
6147
|
+
},
|
|
6148
|
+
{
|
|
6149
|
+
id: "planner",
|
|
6150
|
+
name: "Planner",
|
|
6151
|
+
description: "Create a concise implementation plan and split unsafe scope before work starts.",
|
|
6152
|
+
dependsOn: ["triage-gate"],
|
|
6153
|
+
target: agentTarget(input, plannerPrompt, "planner", input.taskId, plan),
|
|
6154
|
+
timeoutMs: 25 * 60000
|
|
6155
|
+
},
|
|
6156
|
+
{
|
|
6157
|
+
id: "planner-gate",
|
|
6158
|
+
name: "Planner Gate",
|
|
6159
|
+
description: "Stop the lifecycle before implementation when planning blocked or disallowed automation.",
|
|
6160
|
+
dependsOn: ["planner"],
|
|
6161
|
+
target: {
|
|
6162
|
+
type: "command",
|
|
6163
|
+
command: "bash",
|
|
6164
|
+
args: ["-lc", gateCommand("planner")],
|
|
6165
|
+
cwd: plan.cwd,
|
|
6166
|
+
timeoutMs: 2 * 60000
|
|
6167
|
+
},
|
|
6168
|
+
timeoutMs: 2 * 60000
|
|
6169
|
+
},
|
|
6170
|
+
{
|
|
6171
|
+
id: "worker",
|
|
6172
|
+
name: "Worker",
|
|
6173
|
+
description: "Implement the todos task according to triage and planner evidence.",
|
|
6174
|
+
dependsOn: ["planner-gate"],
|
|
6175
|
+
target: agentTarget(input, workerPrompt, "worker", input.taskId, plan),
|
|
6176
|
+
timeoutMs: 45 * 60000
|
|
6177
|
+
},
|
|
6178
|
+
{
|
|
6179
|
+
id: "verifier",
|
|
6180
|
+
name: "Verifier",
|
|
6181
|
+
description: "Adversarially verify worker output and update todos.",
|
|
6182
|
+
dependsOn: ["worker"],
|
|
6183
|
+
target: {
|
|
6184
|
+
...agentTarget(input, verifierPrompt, "verifier", input.taskId, plan),
|
|
6185
|
+
idleTimeoutMs: 10 * 60000
|
|
6186
|
+
},
|
|
6187
|
+
timeoutMs: 30 * 60000
|
|
6188
|
+
}
|
|
6189
|
+
])
|
|
6190
|
+
};
|
|
6191
|
+
}
|
|
5900
6192
|
function renderEventWorkerVerifierWorkflow(input) {
|
|
5901
6193
|
if (!input.eventId?.trim())
|
|
5902
6194
|
throw new Error("eventId is required");
|
|
@@ -6062,11 +6354,39 @@ function renderLifecycleBoundedTemplate(id, values) {
|
|
|
6062
6354
|
const taskId = values.taskId ?? "";
|
|
6063
6355
|
if (!taskId.trim())
|
|
6064
6356
|
throw new Error("taskId is required");
|
|
6065
|
-
return
|
|
6066
|
-
|
|
6067
|
-
|
|
6068
|
-
|
|
6069
|
-
|
|
6357
|
+
return renderTaskLifecycleWorkflow({
|
|
6358
|
+
taskId,
|
|
6359
|
+
taskTitle: values.taskTitle,
|
|
6360
|
+
taskDescription: values.taskDescription,
|
|
6361
|
+
projectPath,
|
|
6362
|
+
todosProjectPath: values.todosProjectPath ?? values.todosProject,
|
|
6363
|
+
routeProjectPath: values.routeProjectPath,
|
|
6364
|
+
projectGroup: values.projectGroup,
|
|
6365
|
+
provider: values.provider,
|
|
6366
|
+
authProfile: values.authProfile,
|
|
6367
|
+
authProfilePool: listVar(values.authProfilePool),
|
|
6368
|
+
triageAuthProfile: values.triageAuthProfile,
|
|
6369
|
+
plannerAuthProfile: values.plannerAuthProfile,
|
|
6370
|
+
workerAuthProfile: values.workerAuthProfile,
|
|
6371
|
+
verifierAuthProfile: values.verifierAuthProfile,
|
|
6372
|
+
account: values.account ? { profile: values.account, tool: values.accountTool } : undefined,
|
|
6373
|
+
accountPool: accountPoolVar(values.accountPool, values.accountTool),
|
|
6374
|
+
triageAccount: values.triageAccount ? { profile: values.triageAccount, tool: values.accountTool } : undefined,
|
|
6375
|
+
plannerAccount: values.plannerAccount ? { profile: values.plannerAccount, tool: values.accountTool } : undefined,
|
|
6376
|
+
workerAccount: values.workerAccount ? { profile: values.workerAccount, tool: values.accountTool } : undefined,
|
|
6377
|
+
verifierAccount: values.verifierAccount ? { profile: values.verifierAccount, tool: values.accountTool } : undefined,
|
|
6378
|
+
model: values.model,
|
|
6379
|
+
variant: values.variant,
|
|
6380
|
+
agent: values.agent,
|
|
6381
|
+
addDirs: listVar(values.addDirs ?? values.addDir),
|
|
6382
|
+
permissionMode: values.permissionMode,
|
|
6383
|
+
sandbox: values.sandbox,
|
|
6384
|
+
manualBreakGlass: booleanVar(values.manualBreakGlass),
|
|
6385
|
+
worktreeMode: values.worktreeMode ?? "required",
|
|
6386
|
+
worktreeRoot: values.worktreeRoot,
|
|
6387
|
+
worktreeBranchPrefix: values.worktreeBranchPrefix,
|
|
6388
|
+
eventId: values.eventId,
|
|
6389
|
+
eventType: values.eventType
|
|
6070
6390
|
});
|
|
6071
6391
|
}
|
|
6072
6392
|
if (id === PR_REVIEW_TEMPLATE_ID) {
|
package/dist/lib/store.d.ts
CHANGED
|
@@ -104,6 +104,7 @@ export declare class Store {
|
|
|
104
104
|
private maybeArchiveGeneratedRouteWorkflow;
|
|
105
105
|
private maybeArchiveTerminalGeneratedRouteWorkflow;
|
|
106
106
|
createWorkflowInvocation(input: CreateWorkflowInvocationInput): WorkflowInvocation;
|
|
107
|
+
refreshWorkflowInvocationForWorkItem(workItemId: string, input: CreateWorkflowInvocationInput): WorkflowInvocation;
|
|
107
108
|
getWorkflowInvocation(id: string): WorkflowInvocation | undefined;
|
|
108
109
|
listWorkflowInvocations(opts?: {
|
|
109
110
|
limit?: number;
|
package/dist/lib/store.js
CHANGED
|
@@ -621,7 +621,7 @@ function writeWorkflowRunManifest(args) {
|
|
|
621
621
|
var DEFAULT_RECOVERY_BATCH_LIMIT = 100;
|
|
622
622
|
var DEFAULT_RECOVERY_SCAN_MULTIPLIER = 5;
|
|
623
623
|
var LIVE_EXPIRED_RUN_GRACE_MS = 60000;
|
|
624
|
-
var GENERATED_ROUTE_TEMPLATE_IDS = new Set(["todos-task-worker-verifier", "event-worker-verifier"]);
|
|
624
|
+
var GENERATED_ROUTE_TEMPLATE_IDS = new Set(["todos-task-worker-verifier", "task-lifecycle", "event-worker-verifier"]);
|
|
625
625
|
var GENERATED_ROUTE_KEYS = new Set(["todos-task", "generic-event"]);
|
|
626
626
|
function rowToLoop(row) {
|
|
627
627
|
return {
|
|
@@ -1565,6 +1565,65 @@ class Store {
|
|
|
1565
1565
|
throw new Error(`workflow invocation not found after create: ${id}`);
|
|
1566
1566
|
return rowToWorkflowInvocation(row);
|
|
1567
1567
|
}
|
|
1568
|
+
refreshWorkflowInvocationForWorkItem(workItemId, input) {
|
|
1569
|
+
const sourceDedupeKey = input.sourceRef.dedupeKey ?? undefined;
|
|
1570
|
+
if (!sourceDedupeKey)
|
|
1571
|
+
throw new Error("cannot refresh workflow invocation without sourceRef.dedupeKey");
|
|
1572
|
+
const now = nowIso();
|
|
1573
|
+
const claimableStatuses = ["queued", "deferred", "failed", "dead_letter", "cancelled"];
|
|
1574
|
+
const statusBindings = Object.fromEntries(claimableStatuses.map((status, index) => [`$status${index}`, status]));
|
|
1575
|
+
const placeholders = claimableStatuses.map((_, index) => `$status${index}`).join(",");
|
|
1576
|
+
const result = this.db.query(`UPDATE workflow_invocations
|
|
1577
|
+
SET workflow_id=COALESCE($workflowId, workflow_id),
|
|
1578
|
+
template_id=COALESCE($templateId, template_id),
|
|
1579
|
+
source_id=COALESCE($sourceId, source_id),
|
|
1580
|
+
source_json=$sourceJson,
|
|
1581
|
+
subject_kind=$subjectKind,
|
|
1582
|
+
subject_id=COALESCE($subjectId, subject_id),
|
|
1583
|
+
subject_path=COALESCE($subjectPath, subject_path),
|
|
1584
|
+
subject_url=COALESCE($subjectUrl, subject_url),
|
|
1585
|
+
subject_json=$subjectJson,
|
|
1586
|
+
intent=$intent,
|
|
1587
|
+
scope_json=COALESCE($scopeJson, scope_json),
|
|
1588
|
+
output_policy_json=COALESCE($outputPolicyJson, output_policy_json),
|
|
1589
|
+
updated_at=$updated
|
|
1590
|
+
WHERE source_kind=$sourceKind
|
|
1591
|
+
AND source_dedupe_key=$sourceDedupeKey
|
|
1592
|
+
AND EXISTS (
|
|
1593
|
+
SELECT 1
|
|
1594
|
+
FROM workflow_work_items
|
|
1595
|
+
WHERE id=$workItemId
|
|
1596
|
+
AND invocation_id=workflow_invocations.id
|
|
1597
|
+
AND status IN (${placeholders})
|
|
1598
|
+
)`).run({
|
|
1599
|
+
$workItemId: workItemId,
|
|
1600
|
+
$sourceKind: input.sourceRef.kind,
|
|
1601
|
+
$sourceDedupeKey: sourceDedupeKey,
|
|
1602
|
+
$workflowId: input.workflowId ?? null,
|
|
1603
|
+
$templateId: input.templateId ?? null,
|
|
1604
|
+
$sourceId: input.sourceRef.id ?? null,
|
|
1605
|
+
$sourceJson: JSON.stringify(input.sourceRef),
|
|
1606
|
+
$subjectKind: input.subjectRef.kind,
|
|
1607
|
+
$subjectId: input.subjectRef.id ?? null,
|
|
1608
|
+
$subjectPath: input.subjectRef.path ?? null,
|
|
1609
|
+
$subjectUrl: input.subjectRef.url ?? null,
|
|
1610
|
+
$subjectJson: JSON.stringify(input.subjectRef),
|
|
1611
|
+
$intent: input.intent,
|
|
1612
|
+
$scopeJson: input.scope ? JSON.stringify(input.scope) : null,
|
|
1613
|
+
$outputPolicyJson: input.outputPolicy ? JSON.stringify(input.outputPolicy) : null,
|
|
1614
|
+
$updated: now,
|
|
1615
|
+
...statusBindings
|
|
1616
|
+
});
|
|
1617
|
+
if (result.changes !== 1)
|
|
1618
|
+
throw new Error(`workflow work item is not refreshable: ${workItemId}`);
|
|
1619
|
+
const updated = this.db.query(`SELECT workflow_invocations.*
|
|
1620
|
+
FROM workflow_invocations
|
|
1621
|
+
JOIN workflow_work_items ON workflow_work_items.invocation_id = workflow_invocations.id
|
|
1622
|
+
WHERE workflow_work_items.id = ?`).get(workItemId);
|
|
1623
|
+
if (!updated)
|
|
1624
|
+
throw new Error(`workflow invocation not found after refresh for work item: ${workItemId}`);
|
|
1625
|
+
return rowToWorkflowInvocation(updated);
|
|
1626
|
+
}
|
|
1568
1627
|
getWorkflowInvocation(id) {
|
|
1569
1628
|
const row = this.db.query("SELECT * FROM workflow_invocations WHERE id = ?").get(id);
|
|
1570
1629
|
return row ? rowToWorkflowInvocation(row) : undefined;
|
package/dist/lib/templates.d.ts
CHANGED
|
@@ -20,10 +20,14 @@ export interface TodosTaskWorkflowTemplateInput {
|
|
|
20
20
|
provider?: AgentProvider;
|
|
21
21
|
authProfile?: string;
|
|
22
22
|
authProfilePool?: string[];
|
|
23
|
+
triageAuthProfile?: string;
|
|
24
|
+
plannerAuthProfile?: string;
|
|
23
25
|
workerAuthProfile?: string;
|
|
24
26
|
verifierAuthProfile?: string;
|
|
25
27
|
account?: AccountRef;
|
|
26
28
|
accountPool?: AccountRef[];
|
|
29
|
+
triageAccount?: AccountRef;
|
|
30
|
+
plannerAccount?: AccountRef;
|
|
27
31
|
workerAccount?: AccountRef;
|
|
28
32
|
verifierAccount?: AccountRef;
|
|
29
33
|
model?: string;
|
|
@@ -52,10 +56,14 @@ export interface EventWorkflowTemplateInput {
|
|
|
52
56
|
provider?: AgentProvider;
|
|
53
57
|
authProfile?: string;
|
|
54
58
|
authProfilePool?: string[];
|
|
59
|
+
triageAuthProfile?: string;
|
|
60
|
+
plannerAuthProfile?: string;
|
|
55
61
|
workerAuthProfile?: string;
|
|
56
62
|
verifierAuthProfile?: string;
|
|
57
63
|
account?: AccountRef;
|
|
58
64
|
accountPool?: AccountRef[];
|
|
65
|
+
triageAccount?: AccountRef;
|
|
66
|
+
plannerAccount?: AccountRef;
|
|
59
67
|
workerAccount?: AccountRef;
|
|
60
68
|
verifierAccount?: AccountRef;
|
|
61
69
|
model?: string;
|
|
@@ -79,10 +87,14 @@ export interface BoundedAgentWorkflowTemplateInput {
|
|
|
79
87
|
provider?: AgentProvider;
|
|
80
88
|
authProfile?: string;
|
|
81
89
|
authProfilePool?: string[];
|
|
90
|
+
triageAuthProfile?: string;
|
|
91
|
+
plannerAuthProfile?: string;
|
|
82
92
|
workerAuthProfile?: string;
|
|
83
93
|
verifierAuthProfile?: string;
|
|
84
94
|
account?: AccountRef;
|
|
85
95
|
accountPool?: AccountRef[];
|
|
96
|
+
triageAccount?: AccountRef;
|
|
97
|
+
plannerAccount?: AccountRef;
|
|
86
98
|
workerAccount?: AccountRef;
|
|
87
99
|
verifierAccount?: AccountRef;
|
|
88
100
|
model?: string;
|
|
@@ -120,6 +132,7 @@ export declare function validateLoopTemplateRegistry(opts?: ListLoopTemplatesOpt
|
|
|
120
132
|
export declare function listLoopTemplates(opts?: ListLoopTemplatesOptions): LoopTemplateSummary[];
|
|
121
133
|
export declare function getLoopTemplate(id: string, opts?: ListLoopTemplatesOptions): LoopTemplateSummary | undefined;
|
|
122
134
|
export declare function renderTodosTaskWorkerVerifierWorkflow(input: TodosTaskWorkflowTemplateInput): CreateWorkflowInput;
|
|
135
|
+
export declare function renderTaskLifecycleWorkflow(input: TodosTaskWorkflowTemplateInput): CreateWorkflowInput;
|
|
123
136
|
export declare function renderEventWorkerVerifierWorkflow(input: EventWorkflowTemplateInput): CreateWorkflowInput;
|
|
124
137
|
export declare function renderBoundedAgentWorkerVerifierWorkflow(input: BoundedAgentWorkflowTemplateInput): CreateWorkflowInput;
|
|
125
138
|
export declare function renderLoopTemplate(id: string, values: Record<string, string | undefined>, opts?: ListLoopTemplatesOptions): CreateWorkflowInput;
|