@sixb/pg 0.1.1 → 0.1.3
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 +7 -6
- package/dist/agents/agent-storage.d.ts +7 -4
- package/dist/agents/agent-storage.d.ts.map +1 -1
- package/dist/agents/checkpoints.d.ts +12 -0
- package/dist/agents/checkpoints.d.ts.map +1 -0
- package/dist/agents/index.d.ts +1 -0
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/messages.d.ts +2 -1
- package/dist/agents/messages.d.ts.map +1 -1
- package/dist/agents/rows.d.ts +22 -12
- package/dist/agents/rows.d.ts.map +1 -1
- package/dist/agents/runs.d.ts +3 -2
- package/dist/agents/runs.d.ts.map +1 -1
- package/dist/auth-storage/service-accounts.d.ts.map +1 -1
- package/dist/auth-storage/shared.d.ts +0 -4
- package/dist/auth-storage/shared.d.ts.map +1 -1
- package/dist/connector-connection-storage.d.ts +9 -0
- package/dist/connector-connection-storage.d.ts.map +1 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5010 -1837
- package/dist/index.js.map +31 -26
- package/dist/latest-run-query.d.ts.map +1 -1
- package/dist/migrations.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-session.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-state.d.ts +0 -2
- package/dist/ontology-storage/materialization-state.d.ts.map +1 -1
- package/dist/ontology-storage/materialization-writer.d.ts.map +1 -1
- package/dist/ontology-storage/materializations.d.ts.map +1 -1
- package/dist/ontology-storage/outbox.d.ts.map +1 -1
- package/dist/ontology-storage/shared.d.ts +3 -6
- package/dist/ontology-storage/shared.d.ts.map +1 -1
- package/dist/pg-action-run-storage.d.ts +3 -2
- package/dist/pg-action-run-storage.d.ts.map +1 -1
- package/dist/pg-ai-cost-storage.d.ts +12 -0
- package/dist/pg-ai-cost-storage.d.ts.map +1 -0
- package/dist/pg-ai-usage-storage.d.ts +16 -0
- package/dist/pg-ai-usage-storage.d.ts.map +1 -0
- package/dist/pg-execution-storage.d.ts +14 -0
- package/dist/pg-execution-storage.d.ts.map +1 -0
- package/dist/pg-pipeline-run-storage.d.ts +5 -2
- package/dist/pg-pipeline-run-storage.d.ts.map +1 -1
- package/dist/pg-projection-run-storage.d.ts +5 -2
- package/dist/pg-projection-run-storage.d.ts.map +1 -1
- package/dist/pg-sync-run-storage.d.ts +5 -2
- package/dist/pg-sync-run-storage.d.ts.map +1 -1
- package/dist/pg-webhook-run-storage.d.ts +5 -2
- package/dist/pg-webhook-run-storage.d.ts.map +1 -1
- package/dist/pg-workflow-run-storage.d.ts +5 -3
- package/dist/pg-workflow-run-storage.d.ts.map +1 -1
- package/dist/run-list-query.d.ts +1 -1
- package/dist/run-list-query.d.ts.map +1 -1
- package/dist/testing.d.ts +6 -0
- package/dist/testing.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/agents/agent-storage.ts +9 -5
- package/src/agents/checkpoints.ts +175 -0
- package/src/agents/index.ts +1 -0
- package/src/agents/messages.ts +71 -13
- package/src/agents/rows.ts +57 -43
- package/src/agents/runs.ts +29 -15
- package/src/auth-storage/service-accounts.ts +14 -15
- package/src/auth-storage/shared.ts +0 -8
- package/src/connector-connection-storage.ts +760 -0
- package/src/index.ts +38 -15
- package/src/latest-run-query.ts +7 -1
- package/src/migrations/004-executions.sql +140 -0
- package/src/migrations/005-workflow-executions.sql +10 -0
- package/src/migrations/006-narrow-ontology-source-root-index.sql +4 -0
- package/src/migrations/007-split-overrides.sql +153 -0
- package/src/migrations/008-action-executions.sql +7 -0
- package/src/migrations/009-agent-executions.sql +21 -0
- package/src/migrations/010-ai-usage-accounting-foundation.sql +76 -0
- package/src/migrations/011-sync-failure-record.sql +23 -0
- package/src/migrations/012-pipeline-failure-record.sql +47 -0
- package/src/migrations/013-workflow-failure-record.sql +45 -0
- package/src/migrations/014-agent-failure-record.sql +56 -0
- package/src/migrations/015-projection-failure-record.sql +20 -0
- package/src/migrations/016-webhook-run-failure-record.sql +19 -0
- package/src/migrations/017-action-failure-record.sql +65 -0
- package/src/migrations/018-ontology-outbox-failure-record.sql +29 -0
- package/src/migrations/019-webhook-delivery-failure-record.sql +27 -0
- package/src/migrations/020-drop-run-usage-projections.sql +8 -0
- package/src/migrations/021-sync-pipeline-executions.sql +50 -0
- package/src/migrations/022-projection-executions.sql +115 -0
- package/src/migrations/023-webhook-executions.sql +30 -0
- package/src/migrations/024-ontology-commit-executions.sql +12 -0
- package/src/migrations/025-connector-connections.sql +231 -0
- package/src/migrations/026-ai-cost-accounting.sql +53 -0
- package/src/migrations/027-agent-context-checkpoints.sql +34 -0
- package/src/migrations/028-object-override-edit-times.sql +3 -0
- package/src/migrations.ts +82 -0
- package/src/ontology-storage/materialization-session.ts +25 -28
- package/src/ontology-storage/materialization-state.ts +161 -43
- package/src/ontology-storage/materialization-writer.ts +149 -76
- package/src/ontology-storage/materializations.ts +3 -1
- package/src/ontology-storage/outbox.ts +9 -2
- package/src/ontology-storage/shared.ts +10 -20
- package/src/pg-action-run-storage.ts +43 -67
- package/src/pg-ai-cost-storage.ts +707 -0
- package/src/pg-ai-usage-storage.ts +290 -0
- package/src/pg-execution-storage.ts +147 -0
- package/src/pg-pipeline-run-storage.ts +107 -41
- package/src/pg-projection-run-storage.ts +159 -91
- package/src/pg-sync-run-storage.ts +100 -28
- package/src/pg-webhook-run-storage.ts +89 -57
- package/src/pg-workflow-run-storage.ts +97 -130
- package/src/run-list-query.ts +9 -4
- package/src/testing.ts +18 -0
- package/dist/pg-webhook-delivery-storage.d.ts +0 -17
- package/dist/pg-webhook-delivery-storage.d.ts.map +0 -1
- package/src/pg-webhook-delivery-storage.ts +0 -180
|
@@ -1,10 +1,17 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { JsonValue } from "@sixb/core"
|
|
2
|
+
import { normalizeRequesterGroupIds } from "@sixb/core/internal/auth"
|
|
3
|
+
import { parseSixbFailure, serializeSixbFailure } from "@sixb/core/internal/errors"
|
|
4
|
+
import {
|
|
5
|
+
assertWorkflowAgentNodeRunExecution,
|
|
6
|
+
assertWorkflowRunExecution,
|
|
7
|
+
} from "@sixb/core/internal/workflow-run-storage-provider"
|
|
2
8
|
import type { WorkflowIOSnapshot } from "@sixb/core/internal/workflows"
|
|
3
9
|
import type {
|
|
4
10
|
CancelWorkflowAgentNodeRunInput,
|
|
5
11
|
ConfirmWorkflowAgentNodeRunExecutionOwnershipInput,
|
|
6
12
|
ConfirmWorkflowRunExecutionOwnershipInput,
|
|
7
13
|
CreateWorkflowAgentNodeRunInput,
|
|
14
|
+
ExecutionStorage,
|
|
8
15
|
FinishWorkflowAgentNodeRunInput,
|
|
9
16
|
FinishWorkflowNodeRunInput,
|
|
10
17
|
FinishWorkflowRunInput,
|
|
@@ -32,7 +39,11 @@ import type {
|
|
|
32
39
|
WorkflowRunRecord,
|
|
33
40
|
WorkflowRunStorage,
|
|
34
41
|
} from "@sixb/core/storage"
|
|
35
|
-
import {
|
|
42
|
+
import {
|
|
43
|
+
AGENT_RUN_FAILURE_CODES,
|
|
44
|
+
WORKFLOW_RUN_FAILURE_CODES,
|
|
45
|
+
WorkflowRunError,
|
|
46
|
+
} from "@sixb/core/storage"
|
|
36
47
|
import { queryLatestRunsByOwnerId } from "./latest-run-query"
|
|
37
48
|
import type { SqlParameter } from "./pg-client"
|
|
38
49
|
import { appendRunListFilters, hasEmptyStatuses, queryRunList } from "./run-list-query"
|
|
@@ -43,39 +54,47 @@ export class PgWorkflowRunStorage implements WorkflowRunStorage {
|
|
|
43
54
|
readonly nodes: PgWorkflowNodeRunStorage
|
|
44
55
|
readonly agentNodes: PgWorkflowAgentNodeRunStorage
|
|
45
56
|
|
|
46
|
-
constructor(
|
|
57
|
+
constructor(
|
|
58
|
+
private readonly sql: PgStoreClient,
|
|
59
|
+
private readonly executions: ExecutionStorage
|
|
60
|
+
) {
|
|
47
61
|
this.nodes = new PgWorkflowNodeRunStorage(sql)
|
|
48
|
-
this.agentNodes = new PgWorkflowAgentNodeRunStorage(sql)
|
|
62
|
+
this.agentNodes = new PgWorkflowAgentNodeRunStorage(sql, executions)
|
|
49
63
|
}
|
|
50
64
|
|
|
51
65
|
async queue(input: QueueWorkflowRunInput): Promise<WorkflowRunRecord> {
|
|
52
66
|
const queuedAt = input.queuedAt ?? new Date()
|
|
67
|
+
await assertWorkflowRunExecution({
|
|
68
|
+
executions: this.executions,
|
|
69
|
+
projectId: input.projectId,
|
|
70
|
+
executionId: input.executionId,
|
|
71
|
+
runId: input.id,
|
|
72
|
+
workflowId: input.workflowId,
|
|
73
|
+
})
|
|
53
74
|
|
|
54
75
|
try {
|
|
55
76
|
const [row] = await this.sql<WorkflowRunDatabaseRow[]>`
|
|
56
77
|
INSERT INTO workflow_runs (
|
|
57
78
|
project_id,
|
|
58
79
|
id,
|
|
80
|
+
execution_id,
|
|
59
81
|
workflow_id,
|
|
60
82
|
status,
|
|
61
83
|
input,
|
|
62
84
|
queued_at,
|
|
63
85
|
started_at,
|
|
64
|
-
|
|
65
|
-
requested_by_principal_type,
|
|
66
|
-
requested_by_principal_id,
|
|
86
|
+
requester_group_ids,
|
|
67
87
|
attempt
|
|
68
88
|
) VALUES (
|
|
69
89
|
${input.projectId},
|
|
70
90
|
${input.id},
|
|
91
|
+
${input.executionId},
|
|
71
92
|
${input.workflowId},
|
|
72
93
|
${"queued"},
|
|
73
94
|
${serializeRecord(input.input)}::text::jsonb,
|
|
74
95
|
${queuedAt},
|
|
75
96
|
${queuedAt},
|
|
76
|
-
${
|
|
77
|
-
${input.requestedByPrincipal?.type ?? "system"},
|
|
78
|
-
${input.requestedByPrincipal?.id ?? "system"},
|
|
97
|
+
${JSON.stringify(normalizeRequesterGroupIds(input.requesterGroupIds))}::text::jsonb,
|
|
79
98
|
${0}
|
|
80
99
|
)
|
|
81
100
|
RETURNING *
|
|
@@ -85,7 +104,7 @@ export class PgWorkflowRunStorage implements WorkflowRunStorage {
|
|
|
85
104
|
} catch (error) {
|
|
86
105
|
if (isUniqueViolation(error)) {
|
|
87
106
|
throw new WorkflowRunError(
|
|
88
|
-
`[SixbPg] Workflow run '${input.id}' already
|
|
107
|
+
`[SixbPg] Workflow run '${input.id}' or execution '${input.executionId}' is already linked for project '${input.projectId}'.`
|
|
89
108
|
)
|
|
90
109
|
}
|
|
91
110
|
|
|
@@ -102,83 +121,29 @@ export class PgWorkflowRunStorage implements WorkflowRunStorage {
|
|
|
102
121
|
FOR UPDATE
|
|
103
122
|
`
|
|
104
123
|
|
|
105
|
-
if (existing) {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
`[SixbPg] Workflow run '${input.id}'
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
if (existing.workflow_id !== input.workflowId) {
|
|
113
|
-
throw new WorkflowRunError(
|
|
114
|
-
`[SixbPg] Workflow run '${input.id}' workflow '${input.workflowId}' does not match existing workflow '${existing.workflow_id}'.`
|
|
115
|
-
)
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
const [updated] = await tx<WorkflowRunDatabaseRow[]>`
|
|
119
|
-
UPDATE workflow_runs
|
|
120
|
-
SET
|
|
121
|
-
status = ${"running"},
|
|
122
|
-
input = ${serializeRecord(input.input)}::text::jsonb,
|
|
123
|
-
started_at = ${startedAt},
|
|
124
|
-
finished_at = ${null},
|
|
125
|
-
error = ${null},
|
|
126
|
-
source = COALESCE(
|
|
127
|
-
source,
|
|
128
|
-
${input.source ? JSON.stringify(input.source) : null}::text::jsonb
|
|
129
|
-
),
|
|
130
|
-
attempt = attempt + 1,
|
|
131
|
-
execution_token = ${input.execution?.token ?? null},
|
|
132
|
-
execution_queue_lease_expires_at = ${input.execution?.queueLeaseExpiresAt ?? null}
|
|
133
|
-
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
134
|
-
RETURNING *
|
|
135
|
-
`
|
|
136
|
-
|
|
137
|
-
return rowToWorkflowRunRecord(updated)
|
|
124
|
+
if (!existing || existing.status !== "queued") {
|
|
125
|
+
throw new WorkflowRunError(
|
|
126
|
+
existing
|
|
127
|
+
? `[SixbPg] Workflow run '${input.id}' cannot start from status '${existing.status}'.`
|
|
128
|
+
: `[SixbPg] Workflow run '${input.id}' not found for project '${input.projectId}'.`
|
|
129
|
+
)
|
|
138
130
|
}
|
|
139
131
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
execution_token,
|
|
154
|
-
execution_queue_lease_expires_at
|
|
155
|
-
) VALUES (
|
|
156
|
-
${input.projectId},
|
|
157
|
-
${input.id},
|
|
158
|
-
${input.workflowId},
|
|
159
|
-
${"running"},
|
|
160
|
-
${serializeRecord(input.input)}::text::jsonb,
|
|
161
|
-
${startedAt},
|
|
162
|
-
${input.source ? JSON.stringify(input.source) : null}::text::jsonb,
|
|
163
|
-
${input.requestedByPrincipal?.type ?? "system"},
|
|
164
|
-
${input.requestedByPrincipal?.id ?? "system"},
|
|
165
|
-
${1},
|
|
166
|
-
${input.execution?.token ?? null},
|
|
167
|
-
${input.execution?.queueLeaseExpiresAt ?? null}
|
|
168
|
-
)
|
|
169
|
-
RETURNING *
|
|
170
|
-
`
|
|
171
|
-
|
|
172
|
-
return rowToWorkflowRunRecord(row)
|
|
173
|
-
} catch (error) {
|
|
174
|
-
if (isUniqueViolation(error)) {
|
|
175
|
-
throw new WorkflowRunError(
|
|
176
|
-
`[SixbPg] Workflow run '${input.id}' already exists for project '${input.projectId}'.`
|
|
177
|
-
)
|
|
178
|
-
}
|
|
132
|
+
const [updated] = await tx<WorkflowRunDatabaseRow[]>`
|
|
133
|
+
UPDATE workflow_runs
|
|
134
|
+
SET
|
|
135
|
+
status = ${"running"},
|
|
136
|
+
started_at = ${startedAt},
|
|
137
|
+
finished_at = ${null},
|
|
138
|
+
error = ${null},
|
|
139
|
+
attempt = attempt + 1,
|
|
140
|
+
execution_token = ${input.execution?.token ?? null},
|
|
141
|
+
execution_queue_lease_expires_at = ${input.execution?.queueLeaseExpiresAt ?? null}
|
|
142
|
+
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
143
|
+
RETURNING *
|
|
144
|
+
`
|
|
179
145
|
|
|
180
|
-
|
|
181
|
-
}
|
|
146
|
+
return rowToWorkflowRunRecord(updated)
|
|
182
147
|
})
|
|
183
148
|
}
|
|
184
149
|
|
|
@@ -258,7 +223,7 @@ export class PgWorkflowRunStorage implements WorkflowRunStorage {
|
|
|
258
223
|
status = ${input.status},
|
|
259
224
|
finished_at = ${input.finishedAt ?? new Date()},
|
|
260
225
|
output = ${null},
|
|
261
|
-
error = ${input.error
|
|
226
|
+
error = ${input.error === undefined ? null : serializeSixbFailure(input.error, WORKFLOW_RUN_FAILURE_CODES)}::text::jsonb,
|
|
262
227
|
execution_token = ${null},
|
|
263
228
|
execution_queue_lease_expires_at = ${null}
|
|
264
229
|
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
@@ -416,16 +381,43 @@ export class PgWorkflowRunStorage implements WorkflowRunStorage {
|
|
|
416
381
|
}
|
|
417
382
|
|
|
418
383
|
export class PgWorkflowAgentNodeRunStorage implements WorkflowAgentNodeRunStorage {
|
|
419
|
-
constructor(
|
|
384
|
+
constructor(
|
|
385
|
+
private readonly sql: PgStoreClient,
|
|
386
|
+
private readonly executions: ExecutionStorage
|
|
387
|
+
) {}
|
|
420
388
|
|
|
421
389
|
async create(input: CreateWorkflowAgentNodeRunInput): Promise<WorkflowAgentNodeRunRecord> {
|
|
390
|
+
const [parent] = await this.sql<{ execution_id: string }[]>`
|
|
391
|
+
SELECT workflow_runs.execution_id
|
|
392
|
+
FROM workflow_node_runs
|
|
393
|
+
JOIN workflow_runs
|
|
394
|
+
ON workflow_runs.project_id = workflow_node_runs.project_id
|
|
395
|
+
AND workflow_runs.id = workflow_node_runs.workflow_run_id
|
|
396
|
+
WHERE workflow_node_runs.project_id = ${input.projectId}
|
|
397
|
+
AND workflow_node_runs.id = ${input.nodeRunId}
|
|
398
|
+
AND workflow_node_runs.node_type = ${"agent"}
|
|
399
|
+
`
|
|
400
|
+
if (!parent) {
|
|
401
|
+
throw new WorkflowRunError(
|
|
402
|
+
`[SixbPg] Agent workflow node run '${input.nodeRunId}' was not found.`
|
|
403
|
+
)
|
|
404
|
+
}
|
|
405
|
+
await assertWorkflowAgentNodeRunExecution({
|
|
406
|
+
executions: this.executions,
|
|
407
|
+
projectId: input.projectId,
|
|
408
|
+
executionId: input.executionId,
|
|
409
|
+
nodeRunId: input.nodeRunId,
|
|
410
|
+
agentId: input.agentId,
|
|
411
|
+
workflowExecutionId: parent.execution_id,
|
|
412
|
+
})
|
|
413
|
+
|
|
422
414
|
try {
|
|
423
415
|
const [row] = await this.sql<WorkflowAgentNodeRunDatabaseRow[]>`
|
|
424
416
|
INSERT INTO workflow_agent_node_runs (
|
|
425
|
-
project_id, node_run_id, agent_id, status, prompt, attempt, created_at
|
|
417
|
+
project_id, node_run_id, execution_id, agent_id, status, prompt, attempt, created_at
|
|
426
418
|
) VALUES (
|
|
427
|
-
${input.projectId}, ${input.nodeRunId}, ${input.
|
|
428
|
-
${input.prompt}, ${0}, ${input.createdAt ?? new Date()}
|
|
419
|
+
${input.projectId}, ${input.nodeRunId}, ${input.executionId}, ${input.agentId},
|
|
420
|
+
${"queued"}, ${input.prompt}, ${0}, ${input.createdAt ?? new Date()}
|
|
429
421
|
) RETURNING *
|
|
430
422
|
`
|
|
431
423
|
return rowToWorkflowAgentNodeRunRecord(row)
|
|
@@ -445,8 +437,6 @@ export class PgWorkflowAgentNodeRunStorage implements WorkflowAgentNodeRunStorag
|
|
|
445
437
|
const [updated] = await tx<WorkflowAgentNodeRunDatabaseRow[]>`
|
|
446
438
|
UPDATE workflow_agent_node_runs SET
|
|
447
439
|
status = ${"running"},
|
|
448
|
-
execution_principal_type = ${input.executionPrincipal?.type ?? null},
|
|
449
|
-
execution_principal_id = ${input.executionPrincipal?.id ?? null},
|
|
450
440
|
model_id = ${input.modelId ?? null},
|
|
451
441
|
attempt = ${1},
|
|
452
442
|
execution_token = ${input.execution.token},
|
|
@@ -504,10 +494,9 @@ export class PgWorkflowAgentNodeRunStorage implements WorkflowAgentNodeRunStorag
|
|
|
504
494
|
status = ${input.status},
|
|
505
495
|
model_id = COALESCE(${input.modelId ?? null}, model_id),
|
|
506
496
|
finish_reason = ${input.finishReason ?? null},
|
|
507
|
-
usage = ${input.usage ? JSON.stringify(input.usage) : null}::text::jsonb,
|
|
508
497
|
trace = ${input.trace ? JSON.stringify(input.trace) : null}::text::jsonb,
|
|
509
498
|
diagnostics = ${input.diagnostics ? JSON.stringify(input.diagnostics) : null}::text::jsonb,
|
|
510
|
-
error = ${input.status === "succeeded" ? null : (input.error
|
|
499
|
+
error = ${input.status === "succeeded" || input.error === undefined ? null : serializeSixbFailure(input.error, AGENT_RUN_FAILURE_CODES)}::text::jsonb,
|
|
511
500
|
execution_token = ${null},
|
|
512
501
|
execution_queue_lease_expires_at = ${null},
|
|
513
502
|
completed_at = ${input.completedAt ?? new Date()}
|
|
@@ -538,7 +527,7 @@ export class PgWorkflowAgentNodeRunStorage implements WorkflowAgentNodeRunStorag
|
|
|
538
527
|
const [updated] = await tx<WorkflowAgentNodeRunDatabaseRow[]>`
|
|
539
528
|
UPDATE workflow_agent_node_runs SET
|
|
540
529
|
status = ${"cancelled"},
|
|
541
|
-
error = ${input.error
|
|
530
|
+
error = ${input.error === undefined ? null : serializeSixbFailure(input.error, AGENT_RUN_FAILURE_CODES)}::text::jsonb,
|
|
542
531
|
execution_token = ${null},
|
|
543
532
|
execution_queue_lease_expires_at = ${null},
|
|
544
533
|
completed_at = ${input.completedAt ?? new Date()}
|
|
@@ -698,7 +687,7 @@ export class PgWorkflowNodeRunStorage implements WorkflowNodeRunStorage {
|
|
|
698
687
|
status = ${input.status},
|
|
699
688
|
finished_at = ${input.finishedAt ?? new Date()},
|
|
700
689
|
output = ${null},
|
|
701
|
-
error = ${input.error
|
|
690
|
+
error = ${input.error === undefined ? null : serializeSixbFailure(input.error, WORKFLOW_RUN_FAILURE_CODES)}::text::jsonb
|
|
702
691
|
WHERE project_id = ${input.projectId} AND id = ${input.id}
|
|
703
692
|
RETURNING *
|
|
704
693
|
`
|
|
@@ -824,6 +813,7 @@ function rowToWorkflowRunRecord(row: WorkflowRunDatabaseRow): WorkflowRunRecord
|
|
|
824
813
|
return {
|
|
825
814
|
id: row.id,
|
|
826
815
|
projectId: row.project_id,
|
|
816
|
+
executionId: row.execution_id,
|
|
827
817
|
workflowId: row.workflow_id,
|
|
828
818
|
status: row.status,
|
|
829
819
|
input: parseRecord(row.input),
|
|
@@ -831,12 +821,8 @@ function rowToWorkflowRunRecord(row: WorkflowRunDatabaseRow): WorkflowRunRecord
|
|
|
831
821
|
queuedAt: row.queued_at ? new Date(row.queued_at) : undefined,
|
|
832
822
|
startedAt: new Date(row.started_at),
|
|
833
823
|
finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
|
|
834
|
-
error: row.error
|
|
835
|
-
|
|
836
|
-
requestedByPrincipal: {
|
|
837
|
-
type: row.requested_by_principal_type,
|
|
838
|
-
id: row.requested_by_principal_id,
|
|
839
|
-
},
|
|
824
|
+
error: row.error === null ? undefined : parseSixbFailure(row.error, WORKFLOW_RUN_FAILURE_CODES),
|
|
825
|
+
requesterGroupIds: parseJson(row.requester_group_ids),
|
|
840
826
|
attempt: Number(row.attempt),
|
|
841
827
|
...(row.execution_token && row.execution_queue_lease_expires_at
|
|
842
828
|
? {
|
|
@@ -849,14 +835,6 @@ function rowToWorkflowRunRecord(row: WorkflowRunDatabaseRow): WorkflowRunRecord
|
|
|
849
835
|
}
|
|
850
836
|
}
|
|
851
837
|
|
|
852
|
-
function parseSource(value: WorkflowRunSource | string | null): WorkflowRunSource | undefined {
|
|
853
|
-
if (value === null || value === undefined) {
|
|
854
|
-
return undefined
|
|
855
|
-
}
|
|
856
|
-
|
|
857
|
-
return typeof value === "string" ? (JSON.parse(value) as WorkflowRunSource) : value
|
|
858
|
-
}
|
|
859
|
-
|
|
860
838
|
function rowToWorkflowNodeRunRecord(row: WorkflowNodeRunDatabaseRow): WorkflowNodeRunRecord {
|
|
861
839
|
return {
|
|
862
840
|
id: row.id,
|
|
@@ -872,7 +850,7 @@ function rowToWorkflowNodeRunRecord(row: WorkflowNodeRunDatabaseRow): WorkflowNo
|
|
|
872
850
|
startedAt: new Date(row.started_at),
|
|
873
851
|
finishedAt: row.finished_at ? new Date(row.finished_at) : undefined,
|
|
874
852
|
output: row.output ? parseRecord(row.output) : undefined,
|
|
875
|
-
error: row.error
|
|
853
|
+
error: row.error === null ? undefined : parseSixbFailure(row.error, WORKFLOW_RUN_FAILURE_CODES),
|
|
876
854
|
}
|
|
877
855
|
}
|
|
878
856
|
|
|
@@ -896,6 +874,7 @@ function canFinishWorkflowRun(
|
|
|
896
874
|
interface WorkflowRunDatabaseRow {
|
|
897
875
|
project_id: string
|
|
898
876
|
id: string
|
|
877
|
+
execution_id: string
|
|
899
878
|
workflow_id: string
|
|
900
879
|
status: WorkflowRunRecord["status"]
|
|
901
880
|
input: WorkflowIOSnapshot | string
|
|
@@ -903,10 +882,8 @@ interface WorkflowRunDatabaseRow {
|
|
|
903
882
|
queued_at: Date | string | null
|
|
904
883
|
started_at: Date | string
|
|
905
884
|
finished_at: Date | string | null
|
|
906
|
-
error:
|
|
907
|
-
|
|
908
|
-
requested_by_principal_type: Principal["type"]
|
|
909
|
-
requested_by_principal_id: string
|
|
885
|
+
error: JsonValue | null
|
|
886
|
+
requester_group_ids: string[] | string
|
|
910
887
|
attempt: number | string
|
|
911
888
|
execution_token: string | null
|
|
912
889
|
execution_queue_lease_expires_at: Date | string | null
|
|
@@ -926,23 +903,21 @@ interface WorkflowNodeRunDatabaseRow {
|
|
|
926
903
|
started_at: Date | string
|
|
927
904
|
finished_at: Date | string | null
|
|
928
905
|
output: WorkflowIOSnapshot | string | null
|
|
929
|
-
error:
|
|
906
|
+
error: JsonValue | null
|
|
930
907
|
}
|
|
931
908
|
|
|
932
909
|
interface WorkflowAgentNodeRunDatabaseRow {
|
|
933
910
|
project_id: string
|
|
934
911
|
node_run_id: string
|
|
912
|
+
execution_id: string
|
|
935
913
|
agent_id: string
|
|
936
914
|
status: WorkflowAgentNodeRunRecord["status"]
|
|
937
915
|
prompt: string
|
|
938
|
-
execution_principal_type: "serviceAccount" | null
|
|
939
|
-
execution_principal_id: string | null
|
|
940
916
|
model_id: string | null
|
|
941
917
|
finish_reason: WorkflowAgentNodeRunRecord["finishReason"] | null
|
|
942
|
-
usage: WorkflowAgentNodeRunRecord["usage"] | string | null
|
|
943
918
|
trace: WorkflowAgentNodeRunRecord["trace"] | string | null
|
|
944
919
|
diagnostics: WorkflowAgentNodeRunRecord["diagnostics"] | string | null
|
|
945
|
-
error:
|
|
920
|
+
error: JsonValue | null
|
|
946
921
|
attempt: number | string
|
|
947
922
|
execution_token: string | null
|
|
948
923
|
execution_queue_lease_expires_at: Date | string | null
|
|
@@ -992,23 +967,15 @@ function rowToWorkflowAgentNodeRunRecord(
|
|
|
992
967
|
return {
|
|
993
968
|
projectId: row.project_id,
|
|
994
969
|
nodeRunId: row.node_run_id,
|
|
970
|
+
executionId: row.execution_id,
|
|
995
971
|
agentId: row.agent_id,
|
|
996
972
|
status: row.status,
|
|
997
973
|
prompt: row.prompt,
|
|
998
|
-
...(row.execution_principal_type && row.execution_principal_id
|
|
999
|
-
? {
|
|
1000
|
-
executionPrincipal: {
|
|
1001
|
-
type: row.execution_principal_type,
|
|
1002
|
-
id: row.execution_principal_id,
|
|
1003
|
-
},
|
|
1004
|
-
}
|
|
1005
|
-
: {}),
|
|
1006
974
|
...(row.model_id ? { modelId: row.model_id } : {}),
|
|
1007
975
|
...(row.finish_reason ? { finishReason: row.finish_reason } : {}),
|
|
1008
|
-
...(row.usage ? { usage: parseJson(row.usage) } : {}),
|
|
1009
976
|
...(row.trace ? { trace: parseJson(row.trace) } : {}),
|
|
1010
977
|
...(row.diagnostics ? { diagnostics: parseJson(row.diagnostics) } : {}),
|
|
1011
|
-
...(row.error ? { error: row.error } : {}),
|
|
978
|
+
...(row.error ? { error: parseSixbFailure(row.error, AGENT_RUN_FAILURE_CODES) } : {}),
|
|
1012
979
|
attempt: Number(row.attempt),
|
|
1013
980
|
...(row.execution_token && row.execution_queue_lease_expires_at
|
|
1014
981
|
? {
|
package/src/run-list-query.ts
CHANGED
|
@@ -12,7 +12,10 @@ type PgRunListTable =
|
|
|
12
12
|
|
|
13
13
|
/** Column projection — `sync_runs` needs the derived `checkpoint_present` flag. */
|
|
14
14
|
type PgRunListSelectList = "*" | "*, checkpoint IS NOT NULL AS checkpoint_present"
|
|
15
|
-
type StartedAtExpression =
|
|
15
|
+
type StartedAtExpression =
|
|
16
|
+
| "started_at"
|
|
17
|
+
| "COALESCE(started_at, created_at)"
|
|
18
|
+
| "COALESCE(started_at, queued_at)"
|
|
16
19
|
|
|
17
20
|
export function hasEmptyStatuses(input: { readonly statuses?: readonly unknown[] }): boolean {
|
|
18
21
|
return input.statuses !== undefined && input.statuses.length === 0
|
|
@@ -78,9 +81,11 @@ export async function queryRunList<TRow>(input: {
|
|
|
78
81
|
const orderColumn =
|
|
79
82
|
input.tableName === "agent_runs"
|
|
80
83
|
? "COALESCE(started_at, created_at)"
|
|
81
|
-
: input.tableName === "
|
|
82
|
-
? "
|
|
83
|
-
: "
|
|
84
|
+
: input.tableName === "sync_runs" || input.tableName === "pipeline_runs"
|
|
85
|
+
? "COALESCE(started_at, queued_at)"
|
|
86
|
+
: input.tableName === "workflow_agent_node_runs"
|
|
87
|
+
? "created_at"
|
|
88
|
+
: "started_at"
|
|
84
89
|
const idColumn = input.tableName === "workflow_agent_node_runs" ? "node_run_id" : "id"
|
|
85
90
|
let query = `
|
|
86
91
|
SELECT ${input.selectList ?? "*"} FROM ${input.tableName}
|
package/src/testing.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export interface PostgresStorageTestingAdapter {
|
|
2
|
+
advanceConnectorConnectionTime(durationMs: number): void
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
const adapters = new WeakMap<object, PostgresStorageTestingAdapter>()
|
|
6
|
+
|
|
7
|
+
export function registerPostgresStorageTestingAdapter(
|
|
8
|
+
storage: object,
|
|
9
|
+
advanceConnectorConnectionTime: (durationMs: number) => void
|
|
10
|
+
): void {
|
|
11
|
+
adapters.set(storage, { advanceConnectorConnectionTime })
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function getPostgresStorageTestingAdapter(storage: object): PostgresStorageTestingAdapter {
|
|
15
|
+
const adapter = adapters.get(storage)
|
|
16
|
+
if (!adapter) throw new Error("[SixbPg] Storage testing adapter is unavailable.")
|
|
17
|
+
return adapter
|
|
18
|
+
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { WebhookDeliveryClaimRecord, WebhookDeliveryKey, WebhookDeliveryRecord, WebhookDeliveryStorage } from "@sixb/core/storage";
|
|
2
|
-
import { type PgStoreClient } from "./transactions";
|
|
3
|
-
export declare class PgWebhookDeliveryStorage implements WebhookDeliveryStorage {
|
|
4
|
-
private readonly sql;
|
|
5
|
-
constructor(sql: PgStoreClient);
|
|
6
|
-
claim(input: WebhookDeliveryKey & {
|
|
7
|
-
receivedAt: string;
|
|
8
|
-
}): Promise<WebhookDeliveryClaimRecord>;
|
|
9
|
-
complete(input: WebhookDeliveryKey & {
|
|
10
|
-
completedAt: string;
|
|
11
|
-
}): Promise<WebhookDeliveryRecord>;
|
|
12
|
-
fail(input: WebhookDeliveryKey & {
|
|
13
|
-
failedAt: string;
|
|
14
|
-
error: string;
|
|
15
|
-
}): Promise<WebhookDeliveryRecord>;
|
|
16
|
-
}
|
|
17
|
-
//# sourceMappingURL=pg-webhook-delivery-storage.d.ts.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"pg-webhook-delivery-storage.d.ts","sourceRoot":"","sources":["../src/pg-webhook-delivery-storage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,0BAA0B,EAE1B,kBAAkB,EAClB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,oBAAoB,CAAA;AAC3B,OAAO,EAAE,KAAK,aAAa,EAAoB,MAAM,gBAAgB,CAAA;AAErE,qBAAa,wBAAyB,YAAW,sBAAsB;IACzD,OAAO,CAAC,QAAQ,CAAC,GAAG;gBAAH,GAAG,EAAE,aAAa;IAEzC,KAAK,CACT,KAAK,EAAE,kBAAkB,GAAG;QAAE,UAAU,EAAE,MAAM,CAAA;KAAE,GACjD,OAAO,CAAC,0BAA0B,CAAC;IAyEhC,QAAQ,CACZ,KAAK,EAAE,kBAAkB,GAAG;QAAE,WAAW,EAAE,MAAM,CAAA;KAAE,GAClD,OAAO,CAAC,qBAAqB,CAAC;IAuB3B,IAAI,CACR,KAAK,EAAE,kBAAkB,GAAG;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,GAC9D,OAAO,CAAC,qBAAqB,CAAC;CAqBlC"}
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
WebhookDeliveryClaimRecord,
|
|
3
|
-
WebhookDeliveryClaimResult,
|
|
4
|
-
WebhookDeliveryKey,
|
|
5
|
-
WebhookDeliveryRecord,
|
|
6
|
-
WebhookDeliveryStorage,
|
|
7
|
-
} from "@sixb/core/storage"
|
|
8
|
-
import { type PgStoreClient, runPgTransaction } from "./transactions"
|
|
9
|
-
|
|
10
|
-
export class PgWebhookDeliveryStorage implements WebhookDeliveryStorage {
|
|
11
|
-
constructor(private readonly sql: PgStoreClient) {}
|
|
12
|
-
|
|
13
|
-
async claim(
|
|
14
|
-
input: WebhookDeliveryKey & { receivedAt: string }
|
|
15
|
-
): Promise<WebhookDeliveryClaimRecord> {
|
|
16
|
-
return runPgTransaction(this.sql, async (tx) => {
|
|
17
|
-
// Atomic first claim: only one transaction can insert this scoped delivery key.
|
|
18
|
-
const [inserted] = await tx<WebhookDeliveryRow[]>`
|
|
19
|
-
INSERT INTO webhook_deliveries (
|
|
20
|
-
project_id,
|
|
21
|
-
connector_id,
|
|
22
|
-
webhook_id,
|
|
23
|
-
idempotency_key,
|
|
24
|
-
status,
|
|
25
|
-
received_at
|
|
26
|
-
) VALUES (
|
|
27
|
-
${input.projectId},
|
|
28
|
-
${input.connectorId},
|
|
29
|
-
${input.webhookId},
|
|
30
|
-
${input.idempotencyKey},
|
|
31
|
-
${"in_progress"},
|
|
32
|
-
${input.receivedAt}
|
|
33
|
-
)
|
|
34
|
-
ON CONFLICT (project_id, connector_id, webhook_id, idempotency_key) DO NOTHING
|
|
35
|
-
RETURNING *
|
|
36
|
-
`
|
|
37
|
-
|
|
38
|
-
if (inserted) {
|
|
39
|
-
return toWebhookDeliveryClaimRecord(inserted, "claimed")
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const [existing] = await tx<WebhookDeliveryRow[]>`
|
|
43
|
-
SELECT * FROM webhook_deliveries
|
|
44
|
-
WHERE project_id = ${input.projectId}
|
|
45
|
-
AND connector_id = ${input.connectorId}
|
|
46
|
-
AND webhook_id = ${input.webhookId}
|
|
47
|
-
AND idempotency_key = ${input.idempotencyKey}
|
|
48
|
-
FOR UPDATE
|
|
49
|
-
`
|
|
50
|
-
|
|
51
|
-
if (existing?.status === "completed") {
|
|
52
|
-
return toWebhookDeliveryClaimRecord(existing, "duplicate")
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (existing?.status === "in_progress") {
|
|
56
|
-
return toWebhookDeliveryClaimRecord(existing, "in_progress")
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (existing?.status !== "failed") {
|
|
60
|
-
throw new Error("[SixbPg] Webhook delivery row disappeared during claim.")
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Failed deliveries are intentionally claimable so provider retries can run again.
|
|
64
|
-
const [updated] = await tx<WebhookDeliveryRow[]>`
|
|
65
|
-
UPDATE webhook_deliveries
|
|
66
|
-
SET status = ${"in_progress"},
|
|
67
|
-
received_at = ${input.receivedAt},
|
|
68
|
-
completed_at = ${null},
|
|
69
|
-
failed_at = ${null},
|
|
70
|
-
error = ${null}
|
|
71
|
-
WHERE project_id = ${input.projectId}
|
|
72
|
-
AND connector_id = ${input.connectorId}
|
|
73
|
-
AND webhook_id = ${input.webhookId}
|
|
74
|
-
AND idempotency_key = ${input.idempotencyKey}
|
|
75
|
-
RETURNING *
|
|
76
|
-
`
|
|
77
|
-
|
|
78
|
-
if (!updated) {
|
|
79
|
-
throw new Error(
|
|
80
|
-
`[SixbPg] Missing webhook delivery row after claim for ${serializeKey(input)}.`
|
|
81
|
-
)
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
return toWebhookDeliveryClaimRecord(updated, "claimed")
|
|
85
|
-
})
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
async complete(
|
|
89
|
-
input: WebhookDeliveryKey & { completedAt: string }
|
|
90
|
-
): Promise<WebhookDeliveryRecord> {
|
|
91
|
-
const [updated] = await this.sql<WebhookDeliveryRow[]>`
|
|
92
|
-
UPDATE webhook_deliveries
|
|
93
|
-
SET status = ${"completed"},
|
|
94
|
-
completed_at = ${input.completedAt},
|
|
95
|
-
failed_at = ${null},
|
|
96
|
-
error = ${null}
|
|
97
|
-
WHERE project_id = ${input.projectId}
|
|
98
|
-
AND connector_id = ${input.connectorId}
|
|
99
|
-
AND webhook_id = ${input.webhookId}
|
|
100
|
-
AND idempotency_key = ${input.idempotencyKey}
|
|
101
|
-
RETURNING *
|
|
102
|
-
`
|
|
103
|
-
|
|
104
|
-
if (!updated) {
|
|
105
|
-
throw new Error(
|
|
106
|
-
`[SixbPg] Missing webhook delivery row after complete for ${serializeKey(input)}.`
|
|
107
|
-
)
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
return toWebhookDeliveryRecord(updated)
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
async fail(
|
|
114
|
-
input: WebhookDeliveryKey & { failedAt: string; error: string }
|
|
115
|
-
): Promise<WebhookDeliveryRecord> {
|
|
116
|
-
const [updated] = await this.sql<WebhookDeliveryRow[]>`
|
|
117
|
-
UPDATE webhook_deliveries
|
|
118
|
-
SET status = ${"failed"},
|
|
119
|
-
failed_at = ${input.failedAt},
|
|
120
|
-
error = ${input.error}
|
|
121
|
-
WHERE project_id = ${input.projectId}
|
|
122
|
-
AND connector_id = ${input.connectorId}
|
|
123
|
-
AND webhook_id = ${input.webhookId}
|
|
124
|
-
AND idempotency_key = ${input.idempotencyKey}
|
|
125
|
-
RETURNING *
|
|
126
|
-
`
|
|
127
|
-
|
|
128
|
-
if (!updated) {
|
|
129
|
-
throw new Error(
|
|
130
|
-
`[SixbPg] Missing webhook delivery row after fail for ${serializeKey(input)}.`
|
|
131
|
-
)
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return toWebhookDeliveryRecord(updated)
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function toWebhookDeliveryClaimRecord(
|
|
139
|
-
row: WebhookDeliveryRow,
|
|
140
|
-
claimResult: WebhookDeliveryClaimResult
|
|
141
|
-
): WebhookDeliveryClaimRecord {
|
|
142
|
-
return {
|
|
143
|
-
...toWebhookDeliveryRecord(row),
|
|
144
|
-
claimResult,
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
function toWebhookDeliveryRecord(row: WebhookDeliveryRow): WebhookDeliveryRecord {
|
|
149
|
-
return {
|
|
150
|
-
projectId: row.project_id,
|
|
151
|
-
connectorId: row.connector_id,
|
|
152
|
-
webhookId: row.webhook_id,
|
|
153
|
-
idempotencyKey: row.idempotency_key,
|
|
154
|
-
status: row.status,
|
|
155
|
-
receivedAt: toIsoString(row.received_at),
|
|
156
|
-
completedAt: row.completed_at ? toIsoString(row.completed_at) : undefined,
|
|
157
|
-
failedAt: row.failed_at ? toIsoString(row.failed_at) : undefined,
|
|
158
|
-
error: row.error ?? undefined,
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function toIsoString(value: Date | string): string {
|
|
163
|
-
return value instanceof Date ? value.toISOString() : new Date(value).toISOString()
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function serializeKey(key: WebhookDeliveryKey): string {
|
|
167
|
-
return JSON.stringify([key.projectId, key.connectorId, key.webhookId, key.idempotencyKey])
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
interface WebhookDeliveryRow {
|
|
171
|
-
readonly project_id: string
|
|
172
|
-
readonly connector_id: string
|
|
173
|
-
readonly webhook_id: string
|
|
174
|
-
readonly idempotency_key: string
|
|
175
|
-
readonly status: WebhookDeliveryRecord["status"]
|
|
176
|
-
readonly received_at: Date | string
|
|
177
|
-
readonly completed_at: Date | string | null
|
|
178
|
-
readonly failed_at: Date | string | null
|
|
179
|
-
readonly error: string | null
|
|
180
|
-
}
|