@hasna/loops 0.4.27 → 0.4.28
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/CHANGELOG.md +121 -0
- package/README.md +6 -0
- package/dist/api/index.js +689 -20
- package/dist/cli/index.js +509 -85
- package/dist/daemon/index.js +118 -11
- package/dist/index.js +525 -64
- package/dist/lib/executor.d.ts +9 -0
- package/dist/lib/migration.d.ts +66 -0
- package/dist/lib/mode.js +3 -3
- package/dist/lib/route/fields.d.ts +8 -0
- package/dist/lib/storage/index.js +197 -7
- package/dist/lib/storage/postgres-loop-storage.d.ts +2 -2
- package/dist/lib/storage/postgres-schema.js +3 -0
- package/dist/lib/storage/postgres.js +3 -0
- package/dist/lib/storage/sqlite.js +83 -3
- package/dist/lib/store/index.d.ts +3 -0
- package/dist/lib/store.d.ts +77 -0
- package/dist/lib/store.js +89 -4
- package/dist/mcp/index.js +118 -11
- package/dist/runner/index.js +35 -8
- package/dist/sdk/http.d.ts +153 -1
- package/dist/sdk/http.js +78 -1
- package/dist/sdk/index.js +411 -60
- package/dist/serve/index.js +919 -60
- package/dist/types.d.ts +11 -0
- package/docs/DEPLOYMENT_MODES.md +6 -0
- package/docs/USAGE.md +11 -8
- package/package.json +3 -3
package/dist/serve/index.js
CHANGED
|
@@ -1571,6 +1571,7 @@ var DEFAULT_RECOVERY_BATCH_LIMIT = 100;
|
|
|
1571
1571
|
var DEFAULT_RECOVERY_SCAN_MULTIPLIER = 5;
|
|
1572
1572
|
var LIVE_EXPIRED_RUN_GRACE_MS = 60000;
|
|
1573
1573
|
var SCHEMA_USER_VERSION = 8;
|
|
1574
|
+
var BREAKING_SCHEMA_FLOOR = 7;
|
|
1574
1575
|
var TERMINAL_RUN_STATUSES = ["succeeded", "failed", "timed_out", "abandoned", "skipped"];
|
|
1575
1576
|
var PRUNE_BATCH_SIZE = 400;
|
|
1576
1577
|
var GENERATED_ROUTE_TEMPLATE_IDS = new Set(["todos-task-worker-verifier", "task-lifecycle", "event-worker-verifier"]);
|
|
@@ -1713,6 +1714,7 @@ function rowToWorkflowWorkItem(row) {
|
|
|
1713
1714
|
priority: row.priority,
|
|
1714
1715
|
status: row.status,
|
|
1715
1716
|
attempts: row.attempts,
|
|
1717
|
+
gateDeaths: row.gate_deaths ?? 0,
|
|
1716
1718
|
nextAttemptAt: row.next_attempt_at ?? undefined,
|
|
1717
1719
|
leaseExpiresAt: row.lease_expires_at ?? undefined,
|
|
1718
1720
|
workflowId: row.workflow_id ?? undefined,
|
|
@@ -1862,6 +1864,23 @@ function workItemStatusForLoopRun(status, attempt, maxAttempts) {
|
|
|
1862
1864
|
}
|
|
1863
1865
|
return;
|
|
1864
1866
|
}
|
|
1867
|
+
var WORK_ITEM_TEMPFAIL_EXIT_CODE = 75;
|
|
1868
|
+
var GATE_STEP_IDS = new Set(["triage", "planner", "plan"]);
|
|
1869
|
+
var GATE_DEATH_MAX_DURATION_MS = 60000;
|
|
1870
|
+
var GATE_DEATH_CEILING = 20;
|
|
1871
|
+
function classifyNonProductiveStepFailure(steps) {
|
|
1872
|
+
const failing = [...steps].reverse().find((step) => step.status === "failed" || step.status === "timed_out");
|
|
1873
|
+
if (!failing)
|
|
1874
|
+
return;
|
|
1875
|
+
if (failing.exitCode === WORK_ITEM_TEMPFAIL_EXIT_CODE)
|
|
1876
|
+
return "tempfail";
|
|
1877
|
+
if (typeof failing.error === "string" && failing.error.includes("worktree preparation failed"))
|
|
1878
|
+
return "gate-death";
|
|
1879
|
+
const fast = failing.durationMs === undefined || failing.durationMs < GATE_DEATH_MAX_DURATION_MS;
|
|
1880
|
+
if (GATE_STEP_IDS.has(failing.stepId) && fast)
|
|
1881
|
+
return "gate-death";
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1865
1884
|
function scrubbedOrNull(value) {
|
|
1866
1885
|
return value == null ? null : scrubSecrets(value);
|
|
1867
1886
|
}
|
|
@@ -1962,11 +1981,21 @@ class Store {
|
|
|
1962
1981
|
id TEXT PRIMARY KEY,
|
|
1963
1982
|
applied_at TEXT NOT NULL
|
|
1964
1983
|
);
|
|
1984
|
+
CREATE TABLE IF NOT EXISTS schema_compat (
|
|
1985
|
+
id INTEGER PRIMARY KEY CHECK (id = 1),
|
|
1986
|
+
min_compatible_user_version INTEGER NOT NULL
|
|
1987
|
+
);
|
|
1965
1988
|
`);
|
|
1966
1989
|
const versionRow = this.db.query("PRAGMA user_version").get();
|
|
1967
1990
|
const userVersion = versionRow?.user_version ?? 0;
|
|
1968
1991
|
if (userVersion > SCHEMA_USER_VERSION) {
|
|
1969
|
-
|
|
1992
|
+
const floorRow = this.db.query("SELECT min_compatible_user_version FROM schema_compat WHERE id = 1").get();
|
|
1993
|
+
if (!floorRow) {
|
|
1994
|
+
throw new Error(`loops database schema version ${userVersion} is newer than this binary supports (${SCHEMA_USER_VERSION}) and carries no compatibility floor; upgrade open-loops before opening this database`);
|
|
1995
|
+
}
|
|
1996
|
+
if (SCHEMA_USER_VERSION < floorRow.min_compatible_user_version) {
|
|
1997
|
+
throw new Error(`loops database schema version ${userVersion} requires a binary with schema support >= ${floorRow.min_compatible_user_version} (this binary supports ${SCHEMA_USER_VERSION}); upgrade open-loops before opening this database`);
|
|
1998
|
+
}
|
|
1970
1999
|
}
|
|
1971
2000
|
const applied = new Set(this.db.query("SELECT id FROM schema_migrations").all().map((row) => row.id));
|
|
1972
2001
|
for (const migration of this.migrations()) {
|
|
@@ -1977,8 +2006,10 @@ class Store {
|
|
|
1977
2006
|
this.db.query("INSERT OR IGNORE INTO schema_migrations (id, applied_at) VALUES (?, ?)").run(migration.id, nowIso());
|
|
1978
2007
|
}
|
|
1979
2008
|
}
|
|
1980
|
-
if (userVersion
|
|
2009
|
+
if (userVersion < SCHEMA_USER_VERSION)
|
|
1981
2010
|
this.db.exec(`PRAGMA user_version = ${SCHEMA_USER_VERSION}`);
|
|
2011
|
+
this.db.query(`INSERT INTO schema_compat (id, min_compatible_user_version) VALUES (1, ?)
|
|
2012
|
+
ON CONFLICT(id) DO UPDATE SET min_compatible_user_version = MAX(min_compatible_user_version, excluded.min_compatible_user_version)`).run(BREAKING_SCHEMA_FLOOR);
|
|
1982
2013
|
}
|
|
1983
2014
|
migrations() {
|
|
1984
2015
|
return [
|
|
@@ -2045,6 +2076,12 @@ class Store {
|
|
|
2045
2076
|
this.addColumnIfMissing("workflow_work_items", "machine_id", "TEXT");
|
|
2046
2077
|
this.db.exec("CREATE INDEX IF NOT EXISTS idx_workflow_work_items_machine ON workflow_work_items(machine_id, status)");
|
|
2047
2078
|
}
|
|
2079
|
+
},
|
|
2080
|
+
{
|
|
2081
|
+
id: "0011_work_item_gate_deaths",
|
|
2082
|
+
apply: () => {
|
|
2083
|
+
this.addColumnIfMissing("workflow_work_items", "gate_deaths", "INTEGER NOT NULL DEFAULT 0");
|
|
2084
|
+
}
|
|
2048
2085
|
}
|
|
2049
2086
|
];
|
|
2050
2087
|
}
|
|
@@ -3175,7 +3212,7 @@ class Store {
|
|
|
3175
3212
|
}
|
|
3176
3213
|
upsertWorkflowWorkItem(input) {
|
|
3177
3214
|
const now = nowIso();
|
|
3178
|
-
const id = genId();
|
|
3215
|
+
const id = input.id ?? genId();
|
|
3179
3216
|
const status = input.status ?? "queued";
|
|
3180
3217
|
this.db.query(`INSERT INTO workflow_work_items (id, route_key, idempotency_key, invocation_id, source_type, source_ref,
|
|
3181
3218
|
subject_ref, project_key, project_group, machine_id, route_scope, priority, status, attempts, next_attempt_at, lease_expires_at,
|
|
@@ -3303,6 +3340,7 @@ class Store {
|
|
|
3303
3340
|
const placeholders = requeueableStatuses.map(() => "?").join(",");
|
|
3304
3341
|
const res = this.db.query(`UPDATE workflow_work_items
|
|
3305
3342
|
SET status='queued', workflow_id=NULL, loop_id=NULL, workflow_run_id=NULL,
|
|
3343
|
+
${patch.resetAttempts ? "attempts=0, gate_deaths=0," : ""}
|
|
3306
3344
|
next_attempt_at=NULL, lease_expires_at=NULL, last_reason=?, updated_at=?
|
|
3307
3345
|
WHERE id=? AND status IN (${placeholders})`).run(reason, now, id, ...requeueableStatuses);
|
|
3308
3346
|
const item = this.getWorkflowWorkItem(id);
|
|
@@ -3312,6 +3350,46 @@ class Store {
|
|
|
3312
3350
|
throw new Error(`workflow work item was not requeued: ${id} status=${item.status}`);
|
|
3313
3351
|
return item;
|
|
3314
3352
|
}
|
|
3353
|
+
deadLetterWorkflowWorkItem(id, patch = {}) {
|
|
3354
|
+
const now = nowIso();
|
|
3355
|
+
const reason = patch.reason?.trim() || "redispatch cap reached; dead-lettered";
|
|
3356
|
+
this.db.query(`UPDATE workflow_work_items
|
|
3357
|
+
SET status='dead_letter', next_attempt_at=NULL, lease_expires_at=NULL, last_reason=?, updated_at=?
|
|
3358
|
+
WHERE id=? AND status IN ('succeeded','failed','cancelled')`).run(reason, now, id);
|
|
3359
|
+
const item = this.getWorkflowWorkItem(id);
|
|
3360
|
+
if (!item)
|
|
3361
|
+
throw new Error(`workflow work item not found after dead-letter: ${id}`);
|
|
3362
|
+
return item;
|
|
3363
|
+
}
|
|
3364
|
+
demoteNonProductiveWorkItems(workflowRunId, finishedAt) {
|
|
3365
|
+
const kind = classifyNonProductiveStepFailure(this.listWorkflowStepRuns(workflowRunId));
|
|
3366
|
+
if (!kind) {
|
|
3367
|
+
this.db.query("UPDATE workflow_work_items SET gate_deaths=0, updated_at=? WHERE workflow_run_id=? AND status='failed' AND gate_deaths > 0").run(finishedAt, workflowRunId);
|
|
3368
|
+
return;
|
|
3369
|
+
}
|
|
3370
|
+
if (kind === "tempfail") {
|
|
3371
|
+
this.db.query(`UPDATE workflow_work_items
|
|
3372
|
+
SET status='queued', attempts=CASE WHEN attempts > 0 THEN attempts - 1 ELSE 0 END,
|
|
3373
|
+
gate_deaths=0,
|
|
3374
|
+
workflow_id=NULL, loop_id=NULL, workflow_run_id=NULL,
|
|
3375
|
+
next_attempt_at=NULL, lease_expires_at=NULL,
|
|
3376
|
+
last_reason='worker exited 75 (tempfail): requeued for retry; attempt refunded (does not count toward redispatch cap)',
|
|
3377
|
+
updated_at=?
|
|
3378
|
+
WHERE workflow_run_id=? AND status='failed'`).run(finishedAt, workflowRunId);
|
|
3379
|
+
return;
|
|
3380
|
+
}
|
|
3381
|
+
this.db.query(`UPDATE workflow_work_items
|
|
3382
|
+
SET attempts=CASE WHEN attempts > 0 THEN attempts - 1 ELSE 0 END,
|
|
3383
|
+
gate_deaths=gate_deaths + 1,
|
|
3384
|
+
status=CASE WHEN gate_deaths + 1 >= ${GATE_DEATH_CEILING} THEN 'dead_letter' ELSE status END,
|
|
3385
|
+
last_reason=CASE
|
|
3386
|
+
WHEN gate_deaths + 1 >= ${GATE_DEATH_CEILING}
|
|
3387
|
+
THEN 'gate-death ceiling reached (' || (gate_deaths + 1) || '/${GATE_DEATH_CEILING} consecutive runs died at worktree prep / triage / planner without reaching the worker): dead-lettered \u2014 the infrastructure fault needs an operator; ''loops routes requeue'' resets and retries'
|
|
3388
|
+
ELSE 'gate death before real work (worktree prep / triage / planner): attempt refunded (does not count toward redispatch cap); consecutive gate deaths: ' || (gate_deaths + 1) || '/${GATE_DEATH_CEILING}'
|
|
3389
|
+
END,
|
|
3390
|
+
updated_at=?
|
|
3391
|
+
WHERE workflow_run_id=? AND status='failed'`).run(finishedAt, workflowRunId);
|
|
3392
|
+
}
|
|
3315
3393
|
admitWorkflowWorkItem(id, patch) {
|
|
3316
3394
|
const now = nowIso();
|
|
3317
3395
|
const res = this.db.query(`UPDATE workflow_work_items
|
|
@@ -4023,6 +4101,8 @@ class Store {
|
|
|
4023
4101
|
if (changed) {
|
|
4024
4102
|
const itemStatus = status === "succeeded" ? "succeeded" : status === "cancelled" ? "cancelled" : "failed";
|
|
4025
4103
|
this.setWorkflowWorkItemsForWorkflowRun(workflowRunId, itemStatus, error, finishedAt);
|
|
4104
|
+
if (itemStatus === "failed")
|
|
4105
|
+
this.demoteNonProductiveWorkItems(workflowRunId, finishedAt);
|
|
4026
4106
|
this.maybeArchiveTerminalGeneratedRouteWorkflow(workflowRunId, finishedAt);
|
|
4027
4107
|
}
|
|
4028
4108
|
this.db.exec("COMMIT");
|
|
@@ -4972,7 +5052,7 @@ class Store {
|
|
|
4972
5052
|
// package.json
|
|
4973
5053
|
var package_default = {
|
|
4974
5054
|
name: "@hasna/loops",
|
|
4975
|
-
version: "0.4.
|
|
5055
|
+
version: "0.4.28",
|
|
4976
5056
|
description: "Persistent local loop and workflow runner for deterministic commands and headless AI coding agents",
|
|
4977
5057
|
type: "module",
|
|
4978
5058
|
main: "dist/index.js",
|
|
@@ -5082,6 +5162,7 @@ var package_default = {
|
|
|
5082
5162
|
bun: ">=1.0.0"
|
|
5083
5163
|
},
|
|
5084
5164
|
dependencies: {
|
|
5165
|
+
"@hasna/contracts": "^0.5.1",
|
|
5085
5166
|
"@hasna/events": "^0.1.9",
|
|
5086
5167
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
5087
5168
|
"@openrouter/ai-sdk-provider": "2.9.1",
|
|
@@ -5091,8 +5172,7 @@ var package_default = {
|
|
|
5091
5172
|
zod: "4.4.3"
|
|
5092
5173
|
},
|
|
5093
5174
|
optionalDependencies: {
|
|
5094
|
-
"@hasna/machines": "0.0.49"
|
|
5095
|
-
"@hasna/contracts": "^0.4.2"
|
|
5175
|
+
"@hasna/machines": "0.0.49"
|
|
5096
5176
|
},
|
|
5097
5177
|
devDependencies: {
|
|
5098
5178
|
"@types/bun": "latest",
|
|
@@ -5412,6 +5492,14 @@ var loops_default = {
|
|
|
5412
5492
|
type: "integer"
|
|
5413
5493
|
}
|
|
5414
5494
|
},
|
|
5495
|
+
{
|
|
5496
|
+
name: "offset",
|
|
5497
|
+
in: "query",
|
|
5498
|
+
required: false,
|
|
5499
|
+
schema: {
|
|
5500
|
+
type: "integer"
|
|
5501
|
+
}
|
|
5502
|
+
},
|
|
5415
5503
|
{
|
|
5416
5504
|
name: "includeArchived",
|
|
5417
5505
|
in: "query",
|
|
@@ -5687,6 +5775,22 @@ var loops_default = {
|
|
|
5687
5775
|
schema: {
|
|
5688
5776
|
type: "integer"
|
|
5689
5777
|
}
|
|
5778
|
+
},
|
|
5779
|
+
{
|
|
5780
|
+
name: "offset",
|
|
5781
|
+
in: "query",
|
|
5782
|
+
required: false,
|
|
5783
|
+
schema: {
|
|
5784
|
+
type: "integer"
|
|
5785
|
+
}
|
|
5786
|
+
},
|
|
5787
|
+
{
|
|
5788
|
+
name: "showOutput",
|
|
5789
|
+
in: "query",
|
|
5790
|
+
required: false,
|
|
5791
|
+
schema: {
|
|
5792
|
+
type: "boolean"
|
|
5793
|
+
}
|
|
5690
5794
|
}
|
|
5691
5795
|
],
|
|
5692
5796
|
responses: {
|
|
@@ -5743,6 +5847,14 @@ var loops_default = {
|
|
|
5743
5847
|
schema: {
|
|
5744
5848
|
type: "string"
|
|
5745
5849
|
}
|
|
5850
|
+
},
|
|
5851
|
+
{
|
|
5852
|
+
name: "showOutput",
|
|
5853
|
+
in: "query",
|
|
5854
|
+
required: false,
|
|
5855
|
+
schema: {
|
|
5856
|
+
type: "boolean"
|
|
5857
|
+
}
|
|
5746
5858
|
}
|
|
5747
5859
|
],
|
|
5748
5860
|
responses: {
|
|
@@ -5763,7 +5875,7 @@ var loops_default = {
|
|
|
5763
5875
|
post: {
|
|
5764
5876
|
operationId: "importRows",
|
|
5765
5877
|
summary: "Bulk id-preserving import (self-hosted backfill)",
|
|
5766
|
-
description: "Upsert full workflow/loop/run rows by id (idempotent; ON CONFLICT(id) DO UPDATE). Preserves
|
|
5878
|
+
description: "Upsert full workflow/loop/run rows by id (idempotent; ON CONFLICT(id) DO UPDATE). Preserves ids, definitions, and timestamps while applying safe defaults: imported workflows are archived, imported loops are paused, and nextRunAt/retryScheduledFor are cleared by default so backfills never schedule immediately. Set preserveWorkflowActivation=true or preserveLoopScheduling=true only for intentional activation. Applied FK-safe: workflows, then loops, then runs. Volatile running runs are skipped and reported.",
|
|
5767
5879
|
requestBody: {
|
|
5768
5880
|
required: true,
|
|
5769
5881
|
content: {
|
|
@@ -5788,51 +5900,141 @@ var loops_default = {
|
|
|
5788
5900
|
}
|
|
5789
5901
|
}
|
|
5790
5902
|
},
|
|
5791
|
-
"/v1/
|
|
5903
|
+
"/v1/workflows": {
|
|
5792
5904
|
get: {
|
|
5793
|
-
operationId: "
|
|
5794
|
-
summary: "List
|
|
5905
|
+
operationId: "listWorkflows",
|
|
5906
|
+
summary: "List workflow specs",
|
|
5795
5907
|
parameters: [
|
|
5796
5908
|
{
|
|
5797
|
-
name: "
|
|
5909
|
+
name: "status",
|
|
5798
5910
|
in: "query",
|
|
5799
5911
|
required: false,
|
|
5800
5912
|
schema: {
|
|
5801
|
-
type: "string"
|
|
5913
|
+
type: "string",
|
|
5914
|
+
enum: [
|
|
5915
|
+
"active",
|
|
5916
|
+
"archived"
|
|
5917
|
+
]
|
|
5802
5918
|
}
|
|
5803
5919
|
},
|
|
5804
5920
|
{
|
|
5805
|
-
name: "
|
|
5921
|
+
name: "limit",
|
|
5806
5922
|
in: "query",
|
|
5807
5923
|
required: false,
|
|
5808
5924
|
schema: {
|
|
5809
|
-
type: "
|
|
5925
|
+
type: "integer"
|
|
5810
5926
|
}
|
|
5811
5927
|
},
|
|
5812
5928
|
{
|
|
5813
|
-
name: "
|
|
5929
|
+
name: "offset",
|
|
5814
5930
|
in: "query",
|
|
5815
5931
|
required: false,
|
|
5816
5932
|
schema: {
|
|
5817
|
-
type: "
|
|
5933
|
+
type: "integer"
|
|
5818
5934
|
}
|
|
5819
|
-
}
|
|
5935
|
+
}
|
|
5936
|
+
],
|
|
5937
|
+
responses: {
|
|
5938
|
+
"200": {
|
|
5939
|
+
description: "workflow specs",
|
|
5940
|
+
content: {
|
|
5941
|
+
"application/json": {
|
|
5942
|
+
schema: {
|
|
5943
|
+
$ref: "#/components/schemas/WorkflowListResponse"
|
|
5944
|
+
}
|
|
5945
|
+
}
|
|
5946
|
+
}
|
|
5947
|
+
}
|
|
5948
|
+
}
|
|
5949
|
+
},
|
|
5950
|
+
post: {
|
|
5951
|
+
operationId: "createWorkflow",
|
|
5952
|
+
summary: "Create a workflow spec",
|
|
5953
|
+
requestBody: {
|
|
5954
|
+
required: true,
|
|
5955
|
+
content: {
|
|
5956
|
+
"application/json": {
|
|
5957
|
+
schema: {
|
|
5958
|
+
$ref: "#/components/schemas/CreateWorkflowInput"
|
|
5959
|
+
}
|
|
5960
|
+
}
|
|
5961
|
+
}
|
|
5962
|
+
},
|
|
5963
|
+
responses: {
|
|
5964
|
+
"201": {
|
|
5965
|
+
description: "workflow",
|
|
5966
|
+
content: {
|
|
5967
|
+
"application/json": {
|
|
5968
|
+
schema: {
|
|
5969
|
+
$ref: "#/components/schemas/WorkflowResponse"
|
|
5970
|
+
}
|
|
5971
|
+
}
|
|
5972
|
+
}
|
|
5973
|
+
}
|
|
5974
|
+
}
|
|
5975
|
+
}
|
|
5976
|
+
},
|
|
5977
|
+
"/v1/workflows/count": {
|
|
5978
|
+
get: {
|
|
5979
|
+
operationId: "countWorkflows",
|
|
5980
|
+
summary: "Count workflow specs",
|
|
5981
|
+
parameters: [
|
|
5820
5982
|
{
|
|
5821
|
-
name: "
|
|
5983
|
+
name: "status",
|
|
5822
5984
|
in: "query",
|
|
5823
5985
|
required: false,
|
|
5824
5986
|
schema: {
|
|
5825
5987
|
type: "string"
|
|
5826
5988
|
}
|
|
5827
|
-
}
|
|
5989
|
+
}
|
|
5990
|
+
],
|
|
5991
|
+
responses: {
|
|
5992
|
+
"200": {
|
|
5993
|
+
description: "count",
|
|
5994
|
+
content: {
|
|
5995
|
+
"application/json": {
|
|
5996
|
+
schema: {
|
|
5997
|
+
$ref: "#/components/schemas/CountResponse"
|
|
5998
|
+
}
|
|
5999
|
+
}
|
|
6000
|
+
}
|
|
6001
|
+
}
|
|
6002
|
+
}
|
|
6003
|
+
}
|
|
6004
|
+
},
|
|
6005
|
+
"/v1/workflows/{id}": {
|
|
6006
|
+
get: {
|
|
6007
|
+
operationId: "getWorkflow",
|
|
6008
|
+
summary: "Get a workflow spec by id",
|
|
6009
|
+
parameters: [
|
|
5828
6010
|
{
|
|
5829
|
-
name: "
|
|
5830
|
-
in: "
|
|
5831
|
-
required:
|
|
6011
|
+
name: "id",
|
|
6012
|
+
in: "path",
|
|
6013
|
+
required: true,
|
|
5832
6014
|
schema: {
|
|
5833
6015
|
type: "string"
|
|
5834
6016
|
}
|
|
5835
|
-
}
|
|
6017
|
+
}
|
|
6018
|
+
],
|
|
6019
|
+
responses: {
|
|
6020
|
+
"200": {
|
|
6021
|
+
description: "workflow",
|
|
6022
|
+
content: {
|
|
6023
|
+
"application/json": {
|
|
6024
|
+
schema: {
|
|
6025
|
+
$ref: "#/components/schemas/WorkflowResponse"
|
|
6026
|
+
}
|
|
6027
|
+
}
|
|
6028
|
+
}
|
|
6029
|
+
}
|
|
6030
|
+
}
|
|
6031
|
+
}
|
|
6032
|
+
},
|
|
6033
|
+
"/v1/invocations": {
|
|
6034
|
+
get: {
|
|
6035
|
+
operationId: "listWorkflowInvocations",
|
|
6036
|
+
summary: "List workflow route invocations",
|
|
6037
|
+
parameters: [
|
|
5836
6038
|
{
|
|
5837
6039
|
name: "limit",
|
|
5838
6040
|
in: "query",
|
|
@@ -5844,11 +6046,11 @@ var loops_default = {
|
|
|
5844
6046
|
],
|
|
5845
6047
|
responses: {
|
|
5846
6048
|
"200": {
|
|
5847
|
-
description: "
|
|
6049
|
+
description: "workflow invocations",
|
|
5848
6050
|
content: {
|
|
5849
6051
|
"application/json": {
|
|
5850
6052
|
schema: {
|
|
5851
|
-
$ref: "#/components/schemas/
|
|
6053
|
+
$ref: "#/components/schemas/WorkflowInvocationListResponse"
|
|
5852
6054
|
}
|
|
5853
6055
|
}
|
|
5854
6056
|
}
|
|
@@ -5856,25 +6058,25 @@ var loops_default = {
|
|
|
5856
6058
|
}
|
|
5857
6059
|
},
|
|
5858
6060
|
post: {
|
|
5859
|
-
operationId: "
|
|
5860
|
-
summary: "
|
|
6061
|
+
operationId: "createWorkflowInvocation",
|
|
6062
|
+
summary: "Create an id-preserving workflow route invocation",
|
|
5861
6063
|
requestBody: {
|
|
5862
6064
|
required: true,
|
|
5863
6065
|
content: {
|
|
5864
6066
|
"application/json": {
|
|
5865
6067
|
schema: {
|
|
5866
|
-
$ref: "#/components/schemas/
|
|
6068
|
+
$ref: "#/components/schemas/CreateWorkflowInvocationInput"
|
|
5867
6069
|
}
|
|
5868
6070
|
}
|
|
5869
6071
|
}
|
|
5870
6072
|
},
|
|
5871
6073
|
responses: {
|
|
5872
6074
|
"201": {
|
|
5873
|
-
description: "
|
|
6075
|
+
description: "workflow invocation",
|
|
5874
6076
|
content: {
|
|
5875
6077
|
"application/json": {
|
|
5876
6078
|
schema: {
|
|
5877
|
-
$ref: "#/components/schemas/
|
|
6079
|
+
$ref: "#/components/schemas/WorkflowInvocationResponse"
|
|
5878
6080
|
}
|
|
5879
6081
|
}
|
|
5880
6082
|
}
|
|
@@ -5882,51 +6084,215 @@ var loops_default = {
|
|
|
5882
6084
|
}
|
|
5883
6085
|
}
|
|
5884
6086
|
},
|
|
5885
|
-
"/v1/
|
|
6087
|
+
"/v1/work-items": {
|
|
5886
6088
|
get: {
|
|
5887
|
-
operationId: "
|
|
5888
|
-
summary: "
|
|
6089
|
+
operationId: "listWorkflowWorkItems",
|
|
6090
|
+
summary: "List workflow route work items",
|
|
5889
6091
|
parameters: [
|
|
5890
6092
|
{
|
|
5891
|
-
name: "
|
|
5892
|
-
in: "
|
|
5893
|
-
required:
|
|
6093
|
+
name: "status",
|
|
6094
|
+
in: "query",
|
|
6095
|
+
required: false,
|
|
6096
|
+
schema: {
|
|
6097
|
+
type: "string"
|
|
6098
|
+
}
|
|
6099
|
+
},
|
|
6100
|
+
{
|
|
6101
|
+
name: "routeKey",
|
|
6102
|
+
in: "query",
|
|
6103
|
+
required: false,
|
|
5894
6104
|
schema: {
|
|
5895
6105
|
type: "string"
|
|
5896
6106
|
}
|
|
6107
|
+
},
|
|
6108
|
+
{
|
|
6109
|
+
name: "limit",
|
|
6110
|
+
in: "query",
|
|
6111
|
+
required: false,
|
|
6112
|
+
schema: {
|
|
6113
|
+
type: "integer"
|
|
6114
|
+
}
|
|
5897
6115
|
}
|
|
5898
6116
|
],
|
|
5899
6117
|
responses: {
|
|
5900
6118
|
"200": {
|
|
5901
|
-
description: "
|
|
6119
|
+
description: "workflow work items",
|
|
5902
6120
|
content: {
|
|
5903
6121
|
"application/json": {
|
|
5904
6122
|
schema: {
|
|
5905
|
-
$ref: "#/components/schemas/
|
|
6123
|
+
$ref: "#/components/schemas/WorkflowWorkItemListResponse"
|
|
6124
|
+
}
|
|
6125
|
+
}
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
6128
|
+
}
|
|
6129
|
+
},
|
|
6130
|
+
post: {
|
|
6131
|
+
operationId: "upsertWorkflowWorkItem",
|
|
6132
|
+
summary: "Upsert an id-preserving workflow route work item",
|
|
6133
|
+
requestBody: {
|
|
6134
|
+
required: true,
|
|
6135
|
+
content: {
|
|
6136
|
+
"application/json": {
|
|
6137
|
+
schema: {
|
|
6138
|
+
$ref: "#/components/schemas/UpsertWorkflowWorkItemInput"
|
|
6139
|
+
}
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
},
|
|
6143
|
+
responses: {
|
|
6144
|
+
"201": {
|
|
6145
|
+
description: "workflow work item",
|
|
6146
|
+
content: {
|
|
6147
|
+
"application/json": {
|
|
6148
|
+
schema: {
|
|
6149
|
+
$ref: "#/components/schemas/WorkflowWorkItemResponse"
|
|
5906
6150
|
}
|
|
5907
6151
|
}
|
|
5908
6152
|
}
|
|
5909
6153
|
}
|
|
5910
6154
|
}
|
|
5911
6155
|
}
|
|
5912
|
-
}
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
6156
|
+
},
|
|
6157
|
+
"/v1/receipts": {
|
|
6158
|
+
get: {
|
|
6159
|
+
operationId: "listRunReceipts",
|
|
6160
|
+
summary: "List run receipts",
|
|
6161
|
+
parameters: [
|
|
6162
|
+
{
|
|
6163
|
+
name: "loopId",
|
|
6164
|
+
in: "query",
|
|
6165
|
+
required: false,
|
|
6166
|
+
schema: {
|
|
6167
|
+
type: "string"
|
|
6168
|
+
}
|
|
5921
6169
|
},
|
|
5922
|
-
|
|
5923
|
-
|
|
6170
|
+
{
|
|
6171
|
+
name: "repo",
|
|
6172
|
+
in: "query",
|
|
6173
|
+
required: false,
|
|
6174
|
+
schema: {
|
|
6175
|
+
type: "string"
|
|
6176
|
+
}
|
|
5924
6177
|
},
|
|
5925
|
-
|
|
5926
|
-
|
|
6178
|
+
{
|
|
6179
|
+
name: "taskId",
|
|
6180
|
+
in: "query",
|
|
6181
|
+
required: false,
|
|
6182
|
+
schema: {
|
|
6183
|
+
type: "string"
|
|
6184
|
+
}
|
|
5927
6185
|
},
|
|
5928
|
-
|
|
5929
|
-
|
|
6186
|
+
{
|
|
6187
|
+
name: "knowledgeId",
|
|
6188
|
+
in: "query",
|
|
6189
|
+
required: false,
|
|
6190
|
+
schema: {
|
|
6191
|
+
type: "string"
|
|
6192
|
+
}
|
|
6193
|
+
},
|
|
6194
|
+
{
|
|
6195
|
+
name: "status",
|
|
6196
|
+
in: "query",
|
|
6197
|
+
required: false,
|
|
6198
|
+
schema: {
|
|
6199
|
+
type: "string"
|
|
6200
|
+
}
|
|
6201
|
+
},
|
|
6202
|
+
{
|
|
6203
|
+
name: "limit",
|
|
6204
|
+
in: "query",
|
|
6205
|
+
required: false,
|
|
6206
|
+
schema: {
|
|
6207
|
+
type: "integer"
|
|
6208
|
+
}
|
|
6209
|
+
}
|
|
6210
|
+
],
|
|
6211
|
+
responses: {
|
|
6212
|
+
"200": {
|
|
6213
|
+
description: "run receipts",
|
|
6214
|
+
content: {
|
|
6215
|
+
"application/json": {
|
|
6216
|
+
schema: {
|
|
6217
|
+
$ref: "#/components/schemas/RunReceiptListResponse"
|
|
6218
|
+
}
|
|
6219
|
+
}
|
|
6220
|
+
}
|
|
6221
|
+
}
|
|
6222
|
+
}
|
|
6223
|
+
},
|
|
6224
|
+
post: {
|
|
6225
|
+
operationId: "writeRunReceipt",
|
|
6226
|
+
summary: "Write a run receipt",
|
|
6227
|
+
requestBody: {
|
|
6228
|
+
required: true,
|
|
6229
|
+
content: {
|
|
6230
|
+
"application/json": {
|
|
6231
|
+
schema: {
|
|
6232
|
+
$ref: "#/components/schemas/WriteRunReceiptInput"
|
|
6233
|
+
}
|
|
6234
|
+
}
|
|
6235
|
+
}
|
|
6236
|
+
},
|
|
6237
|
+
responses: {
|
|
6238
|
+
"201": {
|
|
6239
|
+
description: "run receipt",
|
|
6240
|
+
content: {
|
|
6241
|
+
"application/json": {
|
|
6242
|
+
schema: {
|
|
6243
|
+
$ref: "#/components/schemas/RunReceiptResponse"
|
|
6244
|
+
}
|
|
6245
|
+
}
|
|
6246
|
+
}
|
|
6247
|
+
}
|
|
6248
|
+
}
|
|
6249
|
+
}
|
|
6250
|
+
},
|
|
6251
|
+
"/v1/receipts/{runId}": {
|
|
6252
|
+
get: {
|
|
6253
|
+
operationId: "getRunReceipt",
|
|
6254
|
+
summary: "Get a run receipt by run id",
|
|
6255
|
+
parameters: [
|
|
6256
|
+
{
|
|
6257
|
+
name: "runId",
|
|
6258
|
+
in: "path",
|
|
6259
|
+
required: true,
|
|
6260
|
+
schema: {
|
|
6261
|
+
type: "string"
|
|
6262
|
+
}
|
|
6263
|
+
}
|
|
6264
|
+
],
|
|
6265
|
+
responses: {
|
|
6266
|
+
"200": {
|
|
6267
|
+
description: "run receipt",
|
|
6268
|
+
content: {
|
|
6269
|
+
"application/json": {
|
|
6270
|
+
schema: {
|
|
6271
|
+
$ref: "#/components/schemas/RunReceiptResponse"
|
|
6272
|
+
}
|
|
6273
|
+
}
|
|
6274
|
+
}
|
|
6275
|
+
}
|
|
6276
|
+
}
|
|
6277
|
+
}
|
|
6278
|
+
}
|
|
6279
|
+
},
|
|
6280
|
+
components: {
|
|
6281
|
+
schemas: {
|
|
6282
|
+
Foundation: {
|
|
6283
|
+
type: "object",
|
|
6284
|
+
properties: {
|
|
6285
|
+
status: {
|
|
6286
|
+
type: "string"
|
|
6287
|
+
},
|
|
6288
|
+
version: {
|
|
6289
|
+
type: "string"
|
|
6290
|
+
},
|
|
6291
|
+
mode: {
|
|
6292
|
+
type: "string"
|
|
6293
|
+
},
|
|
6294
|
+
service: {
|
|
6295
|
+
type: "string"
|
|
5930
6296
|
},
|
|
5931
6297
|
detail: {
|
|
5932
6298
|
type: "string"
|
|
@@ -6390,6 +6756,352 @@ var loops_default = {
|
|
|
6390
6756
|
"receipts"
|
|
6391
6757
|
]
|
|
6392
6758
|
},
|
|
6759
|
+
Workflow: {
|
|
6760
|
+
type: "object",
|
|
6761
|
+
properties: {
|
|
6762
|
+
id: {
|
|
6763
|
+
type: "string"
|
|
6764
|
+
},
|
|
6765
|
+
name: {
|
|
6766
|
+
type: "string"
|
|
6767
|
+
},
|
|
6768
|
+
description: {
|
|
6769
|
+
type: "string",
|
|
6770
|
+
nullable: true
|
|
6771
|
+
},
|
|
6772
|
+
version: {
|
|
6773
|
+
type: "integer"
|
|
6774
|
+
},
|
|
6775
|
+
status: {
|
|
6776
|
+
type: "string"
|
|
6777
|
+
},
|
|
6778
|
+
steps: {
|
|
6779
|
+
type: "array",
|
|
6780
|
+
items: {
|
|
6781
|
+
type: "object",
|
|
6782
|
+
additionalProperties: true
|
|
6783
|
+
}
|
|
6784
|
+
},
|
|
6785
|
+
goal: {
|
|
6786
|
+
type: "object",
|
|
6787
|
+
additionalProperties: true
|
|
6788
|
+
},
|
|
6789
|
+
createdAt: {
|
|
6790
|
+
type: "string"
|
|
6791
|
+
},
|
|
6792
|
+
updatedAt: {
|
|
6793
|
+
type: "string"
|
|
6794
|
+
}
|
|
6795
|
+
},
|
|
6796
|
+
required: [
|
|
6797
|
+
"id",
|
|
6798
|
+
"name",
|
|
6799
|
+
"version",
|
|
6800
|
+
"status",
|
|
6801
|
+
"steps"
|
|
6802
|
+
]
|
|
6803
|
+
},
|
|
6804
|
+
CreateWorkflowInput: {
|
|
6805
|
+
type: "object",
|
|
6806
|
+
properties: {
|
|
6807
|
+
name: {
|
|
6808
|
+
type: "string"
|
|
6809
|
+
},
|
|
6810
|
+
description: {
|
|
6811
|
+
type: "string"
|
|
6812
|
+
},
|
|
6813
|
+
steps: {
|
|
6814
|
+
type: "array",
|
|
6815
|
+
items: {
|
|
6816
|
+
type: "object",
|
|
6817
|
+
additionalProperties: true
|
|
6818
|
+
}
|
|
6819
|
+
},
|
|
6820
|
+
goal: {
|
|
6821
|
+
type: "object",
|
|
6822
|
+
additionalProperties: true
|
|
6823
|
+
}
|
|
6824
|
+
},
|
|
6825
|
+
required: [
|
|
6826
|
+
"name",
|
|
6827
|
+
"steps"
|
|
6828
|
+
]
|
|
6829
|
+
},
|
|
6830
|
+
WorkflowResponse: {
|
|
6831
|
+
type: "object",
|
|
6832
|
+
properties: {
|
|
6833
|
+
ok: {
|
|
6834
|
+
type: "boolean"
|
|
6835
|
+
},
|
|
6836
|
+
workflow: {
|
|
6837
|
+
$ref: "#/components/schemas/Workflow"
|
|
6838
|
+
}
|
|
6839
|
+
},
|
|
6840
|
+
required: [
|
|
6841
|
+
"ok",
|
|
6842
|
+
"workflow"
|
|
6843
|
+
]
|
|
6844
|
+
},
|
|
6845
|
+
WorkflowListResponse: {
|
|
6846
|
+
type: "object",
|
|
6847
|
+
properties: {
|
|
6848
|
+
ok: {
|
|
6849
|
+
type: "boolean"
|
|
6850
|
+
},
|
|
6851
|
+
workflows: {
|
|
6852
|
+
type: "array",
|
|
6853
|
+
items: {
|
|
6854
|
+
$ref: "#/components/schemas/Workflow"
|
|
6855
|
+
}
|
|
6856
|
+
}
|
|
6857
|
+
},
|
|
6858
|
+
required: [
|
|
6859
|
+
"ok",
|
|
6860
|
+
"workflows"
|
|
6861
|
+
]
|
|
6862
|
+
},
|
|
6863
|
+
CreateWorkflowInvocationInput: {
|
|
6864
|
+
type: "object",
|
|
6865
|
+
properties: {
|
|
6866
|
+
id: {
|
|
6867
|
+
type: "string"
|
|
6868
|
+
},
|
|
6869
|
+
workflowId: {
|
|
6870
|
+
type: "string"
|
|
6871
|
+
},
|
|
6872
|
+
templateId: {
|
|
6873
|
+
type: "string"
|
|
6874
|
+
},
|
|
6875
|
+
sourceRef: {
|
|
6876
|
+
type: "object",
|
|
6877
|
+
additionalProperties: true
|
|
6878
|
+
},
|
|
6879
|
+
subjectRef: {
|
|
6880
|
+
type: "object",
|
|
6881
|
+
additionalProperties: true
|
|
6882
|
+
},
|
|
6883
|
+
intent: {
|
|
6884
|
+
type: "string"
|
|
6885
|
+
},
|
|
6886
|
+
scope: {
|
|
6887
|
+
type: "object",
|
|
6888
|
+
additionalProperties: true
|
|
6889
|
+
},
|
|
6890
|
+
outputPolicy: {
|
|
6891
|
+
type: "object",
|
|
6892
|
+
additionalProperties: true
|
|
6893
|
+
}
|
|
6894
|
+
},
|
|
6895
|
+
required: [
|
|
6896
|
+
"sourceRef",
|
|
6897
|
+
"subjectRef",
|
|
6898
|
+
"intent"
|
|
6899
|
+
]
|
|
6900
|
+
},
|
|
6901
|
+
WorkflowInvocation: {
|
|
6902
|
+
allOf: [
|
|
6903
|
+
{
|
|
6904
|
+
$ref: "#/components/schemas/CreateWorkflowInvocationInput"
|
|
6905
|
+
},
|
|
6906
|
+
{
|
|
6907
|
+
type: "object",
|
|
6908
|
+
properties: {
|
|
6909
|
+
id: {
|
|
6910
|
+
type: "string"
|
|
6911
|
+
},
|
|
6912
|
+
createdAt: {
|
|
6913
|
+
type: "string"
|
|
6914
|
+
},
|
|
6915
|
+
updatedAt: {
|
|
6916
|
+
type: "string"
|
|
6917
|
+
}
|
|
6918
|
+
},
|
|
6919
|
+
required: [
|
|
6920
|
+
"id",
|
|
6921
|
+
"createdAt",
|
|
6922
|
+
"updatedAt"
|
|
6923
|
+
]
|
|
6924
|
+
}
|
|
6925
|
+
]
|
|
6926
|
+
},
|
|
6927
|
+
WorkflowInvocationResponse: {
|
|
6928
|
+
type: "object",
|
|
6929
|
+
properties: {
|
|
6930
|
+
ok: {
|
|
6931
|
+
type: "boolean"
|
|
6932
|
+
},
|
|
6933
|
+
invocation: {
|
|
6934
|
+
$ref: "#/components/schemas/WorkflowInvocation"
|
|
6935
|
+
}
|
|
6936
|
+
},
|
|
6937
|
+
required: [
|
|
6938
|
+
"ok",
|
|
6939
|
+
"invocation"
|
|
6940
|
+
]
|
|
6941
|
+
},
|
|
6942
|
+
WorkflowInvocationListResponse: {
|
|
6943
|
+
type: "object",
|
|
6944
|
+
properties: {
|
|
6945
|
+
ok: {
|
|
6946
|
+
type: "boolean"
|
|
6947
|
+
},
|
|
6948
|
+
invocations: {
|
|
6949
|
+
type: "array",
|
|
6950
|
+
items: {
|
|
6951
|
+
$ref: "#/components/schemas/WorkflowInvocation"
|
|
6952
|
+
}
|
|
6953
|
+
}
|
|
6954
|
+
},
|
|
6955
|
+
required: [
|
|
6956
|
+
"ok",
|
|
6957
|
+
"invocations"
|
|
6958
|
+
]
|
|
6959
|
+
},
|
|
6960
|
+
WorkflowWorkItem: {
|
|
6961
|
+
type: "object",
|
|
6962
|
+
additionalProperties: true,
|
|
6963
|
+
properties: {
|
|
6964
|
+
id: {
|
|
6965
|
+
type: "string"
|
|
6966
|
+
},
|
|
6967
|
+
routeKey: {
|
|
6968
|
+
type: "string"
|
|
6969
|
+
},
|
|
6970
|
+
idempotencyKey: {
|
|
6971
|
+
type: "string"
|
|
6972
|
+
},
|
|
6973
|
+
invocationId: {
|
|
6974
|
+
type: "string"
|
|
6975
|
+
},
|
|
6976
|
+
sourceType: {
|
|
6977
|
+
type: "string"
|
|
6978
|
+
},
|
|
6979
|
+
sourceRef: {
|
|
6980
|
+
type: "string"
|
|
6981
|
+
},
|
|
6982
|
+
subjectRef: {
|
|
6983
|
+
type: "string"
|
|
6984
|
+
},
|
|
6985
|
+
status: {
|
|
6986
|
+
type: "string"
|
|
6987
|
+
},
|
|
6988
|
+
priority: {
|
|
6989
|
+
type: "integer"
|
|
6990
|
+
},
|
|
6991
|
+
createdAt: {
|
|
6992
|
+
type: "string"
|
|
6993
|
+
},
|
|
6994
|
+
updatedAt: {
|
|
6995
|
+
type: "string"
|
|
6996
|
+
}
|
|
6997
|
+
},
|
|
6998
|
+
required: [
|
|
6999
|
+
"id",
|
|
7000
|
+
"routeKey",
|
|
7001
|
+
"idempotencyKey",
|
|
7002
|
+
"invocationId",
|
|
7003
|
+
"sourceType",
|
|
7004
|
+
"sourceRef",
|
|
7005
|
+
"subjectRef",
|
|
7006
|
+
"status",
|
|
7007
|
+
"priority"
|
|
7008
|
+
]
|
|
7009
|
+
},
|
|
7010
|
+
UpsertWorkflowWorkItemInput: {
|
|
7011
|
+
type: "object",
|
|
7012
|
+
properties: {
|
|
7013
|
+
id: {
|
|
7014
|
+
type: "string"
|
|
7015
|
+
},
|
|
7016
|
+
routeKey: {
|
|
7017
|
+
type: "string"
|
|
7018
|
+
},
|
|
7019
|
+
idempotencyKey: {
|
|
7020
|
+
type: "string"
|
|
7021
|
+
},
|
|
7022
|
+
invocationId: {
|
|
7023
|
+
type: "string"
|
|
7024
|
+
},
|
|
7025
|
+
sourceType: {
|
|
7026
|
+
type: "string"
|
|
7027
|
+
},
|
|
7028
|
+
sourceRef: {
|
|
7029
|
+
type: "string"
|
|
7030
|
+
},
|
|
7031
|
+
subjectRef: {
|
|
7032
|
+
type: "string"
|
|
7033
|
+
},
|
|
7034
|
+
projectKey: {
|
|
7035
|
+
type: "string"
|
|
7036
|
+
},
|
|
7037
|
+
projectGroup: {
|
|
7038
|
+
type: "string"
|
|
7039
|
+
},
|
|
7040
|
+
machineId: {
|
|
7041
|
+
type: "string"
|
|
7042
|
+
},
|
|
7043
|
+
routeScope: {
|
|
7044
|
+
type: "string"
|
|
7045
|
+
},
|
|
7046
|
+
priority: {
|
|
7047
|
+
type: "integer"
|
|
7048
|
+
},
|
|
7049
|
+
status: {
|
|
7050
|
+
type: "string",
|
|
7051
|
+
enum: [
|
|
7052
|
+
"queued",
|
|
7053
|
+
"deferred"
|
|
7054
|
+
]
|
|
7055
|
+
},
|
|
7056
|
+
nextAttemptAt: {
|
|
7057
|
+
type: "string"
|
|
7058
|
+
},
|
|
7059
|
+
lastReason: {
|
|
7060
|
+
type: "string"
|
|
7061
|
+
}
|
|
7062
|
+
},
|
|
7063
|
+
required: [
|
|
7064
|
+
"routeKey",
|
|
7065
|
+
"idempotencyKey",
|
|
7066
|
+
"invocationId",
|
|
7067
|
+
"sourceType",
|
|
7068
|
+
"sourceRef",
|
|
7069
|
+
"subjectRef"
|
|
7070
|
+
]
|
|
7071
|
+
},
|
|
7072
|
+
WorkflowWorkItemResponse: {
|
|
7073
|
+
type: "object",
|
|
7074
|
+
properties: {
|
|
7075
|
+
ok: {
|
|
7076
|
+
type: "boolean"
|
|
7077
|
+
},
|
|
7078
|
+
workItem: {
|
|
7079
|
+
$ref: "#/components/schemas/WorkflowWorkItem"
|
|
7080
|
+
}
|
|
7081
|
+
},
|
|
7082
|
+
required: [
|
|
7083
|
+
"ok",
|
|
7084
|
+
"workItem"
|
|
7085
|
+
]
|
|
7086
|
+
},
|
|
7087
|
+
WorkflowWorkItemListResponse: {
|
|
7088
|
+
type: "object",
|
|
7089
|
+
properties: {
|
|
7090
|
+
ok: {
|
|
7091
|
+
type: "boolean"
|
|
7092
|
+
},
|
|
7093
|
+
workItems: {
|
|
7094
|
+
type: "array",
|
|
7095
|
+
items: {
|
|
7096
|
+
$ref: "#/components/schemas/WorkflowWorkItem"
|
|
7097
|
+
}
|
|
7098
|
+
}
|
|
7099
|
+
},
|
|
7100
|
+
required: [
|
|
7101
|
+
"ok",
|
|
7102
|
+
"workItems"
|
|
7103
|
+
]
|
|
7104
|
+
},
|
|
6393
7105
|
ImportInput: {
|
|
6394
7106
|
type: "object",
|
|
6395
7107
|
description: "Batches of full rows to upsert by id. Any array may be omitted.",
|
|
@@ -6417,7 +7129,15 @@ var loops_default = {
|
|
|
6417
7129
|
},
|
|
6418
7130
|
replace: {
|
|
6419
7131
|
type: "boolean",
|
|
6420
|
-
description: "Update existing rows whose id matches
|
|
7132
|
+
description: "Update existing rows whose id matches. Default safe imports may still archive/pause same-id workflows/loops when preserve flags are false."
|
|
7133
|
+
},
|
|
7134
|
+
preserveLoopScheduling: {
|
|
7135
|
+
type: "boolean",
|
|
7136
|
+
description: "Default false. When false, imported loops are written paused with nextRunAt and retryScheduledFor cleared so backfills never schedule immediately."
|
|
7137
|
+
},
|
|
7138
|
+
preserveWorkflowActivation: {
|
|
7139
|
+
type: "boolean",
|
|
7140
|
+
description: "Default false. When false, imported workflows are written archived, including same-id active rows, so backfills cannot activate workflow execution unless explicitly requested."
|
|
6421
7141
|
}
|
|
6422
7142
|
}
|
|
6423
7143
|
},
|
|
@@ -6652,6 +7372,21 @@ async function handleV1Request(ctx) {
|
|
|
6652
7372
|
return errorResponse(error);
|
|
6653
7373
|
}
|
|
6654
7374
|
}
|
|
7375
|
+
function safeImportedWorkflow(workflow, opts) {
|
|
7376
|
+
if (opts.preserveWorkflowActivation)
|
|
7377
|
+
return workflow;
|
|
7378
|
+
return { ...workflow, status: "archived" };
|
|
7379
|
+
}
|
|
7380
|
+
function safeImportedLoop(loop, opts) {
|
|
7381
|
+
if (opts.preserveLoopScheduling)
|
|
7382
|
+
return loop;
|
|
7383
|
+
return {
|
|
7384
|
+
...loop,
|
|
7385
|
+
status: "paused",
|
|
7386
|
+
nextRunAt: undefined,
|
|
7387
|
+
retryScheduledFor: undefined
|
|
7388
|
+
};
|
|
7389
|
+
}
|
|
6655
7390
|
async function handleImportRequest(ctx, segments) {
|
|
6656
7391
|
if (segments.length !== 0 || ctx.request.method !== "POST")
|
|
6657
7392
|
return fail("not_found", 404);
|
|
@@ -6661,14 +7396,20 @@ async function handleImportRequest(ctx, segments) {
|
|
|
6661
7396
|
const workflows = Array.isArray(body.workflows) ? body.workflows : [];
|
|
6662
7397
|
const loops = Array.isArray(body.loops) ? body.loops : [];
|
|
6663
7398
|
const runs = Array.isArray(body.runs) ? body.runs : [];
|
|
7399
|
+
const preserveWorkflowActivation = body.preserveWorkflowActivation === true;
|
|
7400
|
+
const preserveLoopScheduling = body.preserveLoopScheduling === true;
|
|
6664
7401
|
const imported = { workflows: 0, loops: 0, runs: 0 };
|
|
6665
7402
|
let skippedRunning = 0;
|
|
6666
7403
|
for (const workflow of workflows) {
|
|
6667
|
-
await storage.upsertMigrationWorkflow(workflow, {
|
|
7404
|
+
await storage.upsertMigrationWorkflow(safeImportedWorkflow(workflow, { preserveWorkflowActivation }), {
|
|
7405
|
+
replace: replace || !preserveWorkflowActivation
|
|
7406
|
+
});
|
|
6668
7407
|
imported.workflows += 1;
|
|
6669
7408
|
}
|
|
6670
7409
|
for (const loop of loops) {
|
|
6671
|
-
await storage.upsertMigrationLoop(loop, {
|
|
7410
|
+
await storage.upsertMigrationLoop(safeImportedLoop(loop, { preserveLoopScheduling }), {
|
|
7411
|
+
replace: replace || !preserveLoopScheduling
|
|
7412
|
+
});
|
|
6672
7413
|
imported.loops += 1;
|
|
6673
7414
|
}
|
|
6674
7415
|
for (const run of runs) {
|
|
@@ -6827,6 +7568,10 @@ async function handleWorkItemsRequest(ctx, segments) {
|
|
|
6827
7568
|
});
|
|
6828
7569
|
return ok({ workItems: items.map(publicWorkflowWorkItem) });
|
|
6829
7570
|
}
|
|
7571
|
+
if (segments.length === 0 && ctx.request.method === "POST") {
|
|
7572
|
+
const body = await readJsonBody(ctx.request, ctx.bodyLimitBytes);
|
|
7573
|
+
return ok({ workItem: publicWorkflowWorkItem(await storage.upsertWorkflowWorkItem(body)) }, { status: 201 });
|
|
7574
|
+
}
|
|
6830
7575
|
const id = segments[0];
|
|
6831
7576
|
if (!id)
|
|
6832
7577
|
return fail("not_found", 404);
|
|
@@ -6844,6 +7589,10 @@ async function handleInvocationsRequest(ctx, segments) {
|
|
|
6844
7589
|
const invocations = await storage.listWorkflowInvocations({ limit: optionalLimit(ctx.url.searchParams.get("limit")) });
|
|
6845
7590
|
return ok({ invocations: invocations.map(publicWorkflowInvocation) });
|
|
6846
7591
|
}
|
|
7592
|
+
if (segments.length === 0 && ctx.request.method === "POST") {
|
|
7593
|
+
const body = await readJsonBody(ctx.request, ctx.bodyLimitBytes);
|
|
7594
|
+
return ok({ invocation: publicWorkflowInvocation(await storage.createWorkflowInvocation(body)) }, { status: 201 });
|
|
7595
|
+
}
|
|
6847
7596
|
const id = segments[0];
|
|
6848
7597
|
if (!id)
|
|
6849
7598
|
return fail("not_found", 404);
|
|
@@ -7680,6 +8429,9 @@ CREATE INDEX IF NOT EXISTS idx_run_receipts_knowledge_ids ON run_receipts USING
|
|
|
7680
8429
|
migration("0006_work_item_machine_id", `
|
|
7681
8430
|
ALTER TABLE workflow_work_items ADD COLUMN IF NOT EXISTS machine_id TEXT;
|
|
7682
8431
|
CREATE INDEX IF NOT EXISTS idx_workflow_work_items_machine ON workflow_work_items(machine_id, status);
|
|
8432
|
+
`),
|
|
8433
|
+
migration("0007_work_item_gate_deaths", `
|
|
8434
|
+
ALTER TABLE workflow_work_items ADD COLUMN IF NOT EXISTS gate_deaths INTEGER NOT NULL DEFAULT 0;
|
|
7683
8435
|
`)
|
|
7684
8436
|
]);
|
|
7685
8437
|
|
|
@@ -8972,11 +9724,118 @@ class PostgresLoopStorage {
|
|
|
8972
9724
|
throw new Error(`workflow not found after archive: ${existing.id}`);
|
|
8973
9725
|
return archived;
|
|
8974
9726
|
}
|
|
8975
|
-
createWorkflowInvocation() {
|
|
8976
|
-
|
|
9727
|
+
async createWorkflowInvocation(...args) {
|
|
9728
|
+
const [input] = args;
|
|
9729
|
+
const now = nowIso();
|
|
9730
|
+
const sourceDedupeKey = input.sourceRef.dedupeKey ?? undefined;
|
|
9731
|
+
if (sourceDedupeKey) {
|
|
9732
|
+
const existing = await this.client.get("SELECT * FROM workflow_invocations WHERE source_kind = $1 AND source_dedupe_key = $2 LIMIT 1", [input.sourceRef.kind, sourceDedupeKey]);
|
|
9733
|
+
if (existing)
|
|
9734
|
+
return rowToWorkflowInvocation(existing);
|
|
9735
|
+
}
|
|
9736
|
+
const id = input.id ?? genId();
|
|
9737
|
+
await this.client.execute(`INSERT INTO workflow_invocations (id, workflow_id, template_id, source_kind, source_id, source_dedupe_key,
|
|
9738
|
+
source_json, subject_kind, subject_id, subject_path, subject_url, subject_json, intent, scope_json,
|
|
9739
|
+
output_policy_json, created_at, updated_at)
|
|
9740
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7::jsonb,$8,$9,$10,$11,$12::jsonb,$13,$14::jsonb,$15::jsonb,$16,$17)`, [
|
|
9741
|
+
id,
|
|
9742
|
+
input.workflowId ?? null,
|
|
9743
|
+
input.templateId ?? null,
|
|
9744
|
+
input.sourceRef.kind,
|
|
9745
|
+
input.sourceRef.id ?? null,
|
|
9746
|
+
sourceDedupeKey ?? null,
|
|
9747
|
+
JSON.stringify(input.sourceRef),
|
|
9748
|
+
input.subjectRef.kind,
|
|
9749
|
+
input.subjectRef.id ?? null,
|
|
9750
|
+
input.subjectRef.path ?? null,
|
|
9751
|
+
input.subjectRef.url ?? null,
|
|
9752
|
+
JSON.stringify(input.subjectRef),
|
|
9753
|
+
input.intent,
|
|
9754
|
+
input.scope ? JSON.stringify(input.scope) : null,
|
|
9755
|
+
input.outputPolicy ? JSON.stringify(input.outputPolicy) : null,
|
|
9756
|
+
now,
|
|
9757
|
+
now
|
|
9758
|
+
]);
|
|
9759
|
+
const row = await this.client.get("SELECT * FROM workflow_invocations WHERE id = $1", [id]);
|
|
9760
|
+
if (!row)
|
|
9761
|
+
throw new Error(`workflow invocation not found after create: ${id}`);
|
|
9762
|
+
return rowToWorkflowInvocation(row);
|
|
8977
9763
|
}
|
|
8978
|
-
upsertWorkflowWorkItem() {
|
|
8979
|
-
|
|
9764
|
+
async upsertWorkflowWorkItem(...args) {
|
|
9765
|
+
const [input] = args;
|
|
9766
|
+
const now = nowIso();
|
|
9767
|
+
const id = input.id ?? genId();
|
|
9768
|
+
const status = input.status ?? "queued";
|
|
9769
|
+
await this.client.execute(`INSERT INTO workflow_work_items (id, route_key, idempotency_key, invocation_id, source_type, source_ref,
|
|
9770
|
+
subject_ref, project_key, project_group, machine_id, route_scope, priority, status, attempts, next_attempt_at,
|
|
9771
|
+
lease_expires_at, workflow_id, loop_id, workflow_run_id, last_reason, created_at, updated_at)
|
|
9772
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,0,$14,NULL,NULL,NULL,NULL,$15,$16,$17)
|
|
9773
|
+
ON CONFLICT(route_key, idempotency_key) DO UPDATE SET
|
|
9774
|
+
invocation_id=excluded.invocation_id,
|
|
9775
|
+
source_type=excluded.source_type,
|
|
9776
|
+
source_ref=excluded.source_ref,
|
|
9777
|
+
subject_ref=excluded.subject_ref,
|
|
9778
|
+
project_key=excluded.project_key,
|
|
9779
|
+
project_group=excluded.project_group,
|
|
9780
|
+
machine_id=CASE
|
|
9781
|
+
WHEN workflow_work_items.status IN ('succeeded', 'admitted', 'running', 'failed', 'dead_letter', 'cancelled') THEN workflow_work_items.machine_id
|
|
9782
|
+
ELSE excluded.machine_id
|
|
9783
|
+
END,
|
|
9784
|
+
route_scope=excluded.route_scope,
|
|
9785
|
+
priority=excluded.priority,
|
|
9786
|
+
status=CASE
|
|
9787
|
+
WHEN workflow_work_items.status IN ('succeeded', 'admitted', 'running', 'failed', 'dead_letter', 'cancelled')
|
|
9788
|
+
THEN workflow_work_items.status
|
|
9789
|
+
ELSE excluded.status
|
|
9790
|
+
END,
|
|
9791
|
+
workflow_id=CASE
|
|
9792
|
+
WHEN workflow_work_items.status IN ('succeeded', 'admitted', 'running', 'failed', 'dead_letter', 'cancelled') THEN workflow_work_items.workflow_id
|
|
9793
|
+
ELSE NULL
|
|
9794
|
+
END,
|
|
9795
|
+
loop_id=CASE
|
|
9796
|
+
WHEN workflow_work_items.status IN ('succeeded', 'admitted', 'running', 'failed', 'dead_letter', 'cancelled') THEN workflow_work_items.loop_id
|
|
9797
|
+
ELSE NULL
|
|
9798
|
+
END,
|
|
9799
|
+
workflow_run_id=CASE
|
|
9800
|
+
WHEN workflow_work_items.status IN ('succeeded', 'admitted', 'running', 'failed', 'dead_letter', 'cancelled') THEN workflow_work_items.workflow_run_id
|
|
9801
|
+
ELSE NULL
|
|
9802
|
+
END,
|
|
9803
|
+
lease_expires_at=CASE
|
|
9804
|
+
WHEN workflow_work_items.status IN ('succeeded', 'admitted', 'running', 'failed', 'dead_letter', 'cancelled') THEN workflow_work_items.lease_expires_at
|
|
9805
|
+
ELSE NULL
|
|
9806
|
+
END,
|
|
9807
|
+
next_attempt_at=excluded.next_attempt_at,
|
|
9808
|
+
last_reason=CASE
|
|
9809
|
+
WHEN workflow_work_items.attempts > 0
|
|
9810
|
+
AND workflow_work_items.status IN ('queued', 'deferred')
|
|
9811
|
+
AND workflow_work_items.last_reason IS NOT NULL
|
|
9812
|
+
AND excluded.last_reason IS NOT NULL
|
|
9813
|
+
THEN workflow_work_items.last_reason || '; ' || excluded.last_reason
|
|
9814
|
+
ELSE COALESCE(excluded.last_reason, workflow_work_items.last_reason)
|
|
9815
|
+
END,
|
|
9816
|
+
updated_at=excluded.updated_at`, [
|
|
9817
|
+
id,
|
|
9818
|
+
input.routeKey,
|
|
9819
|
+
input.idempotencyKey,
|
|
9820
|
+
input.invocationId,
|
|
9821
|
+
input.sourceType,
|
|
9822
|
+
input.sourceRef,
|
|
9823
|
+
input.subjectRef,
|
|
9824
|
+
input.projectKey ?? null,
|
|
9825
|
+
input.projectGroup ?? null,
|
|
9826
|
+
input.machineId ?? null,
|
|
9827
|
+
input.routeScope ?? null,
|
|
9828
|
+
input.priority ?? 0,
|
|
9829
|
+
status,
|
|
9830
|
+
input.nextAttemptAt ?? null,
|
|
9831
|
+
input.lastReason ?? null,
|
|
9832
|
+
now,
|
|
9833
|
+
now
|
|
9834
|
+
]);
|
|
9835
|
+
const row = await this.client.get("SELECT * FROM workflow_work_items WHERE route_key = $1 AND idempotency_key = $2 LIMIT 1", [input.routeKey, input.idempotencyKey]);
|
|
9836
|
+
if (!row)
|
|
9837
|
+
throw new Error(`workflow work item not found after upsert: ${input.routeKey}/${input.idempotencyKey}`);
|
|
9838
|
+
return rowToWorkflowWorkItem(row);
|
|
8980
9839
|
}
|
|
8981
9840
|
admitWorkflowWorkItem() {
|
|
8982
9841
|
throw new NotImplementedError("admitWorkflowWorkItem");
|