@shipfox/api-workflows 7.1.0 → 9.0.1
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/.turbo/turbo-build.log +4 -4
- package/CHANGELOG.md +80 -0
- package/dist/core/agent-defaults.d.ts +2 -1
- package/dist/core/agent-defaults.d.ts.map +1 -1
- package/dist/core/agent-defaults.js.map +1 -1
- package/dist/core/agent-tools.d.ts +5 -2
- package/dist/core/agent-tools.d.ts.map +1 -1
- package/dist/core/agent-tools.js.map +1 -1
- package/dist/core/checkout.d.ts +2 -2
- package/dist/core/checkout.d.ts.map +1 -1
- package/dist/core/checkout.js.map +1 -1
- package/dist/core/entities/step.d.ts +2 -1
- package/dist/core/entities/step.d.ts.map +1 -1
- package/dist/core/entities/step.js.map +1 -1
- package/dist/core/errors.d.ts +0 -4
- package/dist/core/errors.d.ts.map +1 -1
- package/dist/core/errors.js +0 -6
- package/dist/core/errors.js.map +1 -1
- package/dist/core/job-execution.d.ts +0 -2
- package/dist/core/job-execution.d.ts.map +1 -1
- package/dist/core/job-execution.js +3 -7
- package/dist/core/job-execution.js.map +1 -1
- package/dist/core/run-workflow.d.ts +2 -2
- package/dist/core/run-workflow.d.ts.map +1 -1
- package/dist/core/run-workflow.js.map +1 -1
- package/dist/core/step-config/agent.d.ts +1 -1
- package/dist/core/step-config/agent.d.ts.map +1 -1
- package/dist/core/step-config/agent.js.map +1 -1
- package/dist/db/job-listeners.d.ts +2 -2
- package/dist/db/job-listeners.d.ts.map +1 -1
- package/dist/db/job-listeners.js.map +1 -1
- package/dist/db/workflow-runs/job-executions.d.ts +0 -5
- package/dist/db/workflow-runs/job-executions.d.ts.map +1 -1
- package/dist/db/workflow-runs/job-executions.js +0 -7
- package/dist/db/workflow-runs/job-executions.js.map +1 -1
- package/dist/db/workflow-runs/run-create.d.ts +2 -2
- package/dist/db/workflow-runs/run-create.d.ts.map +1 -1
- package/dist/db/workflow-runs/run-create.js.map +1 -1
- package/dist/db/workflow-runs.d.ts +1 -1
- package/dist/db/workflow-runs.d.ts.map +1 -1
- package/dist/db/workflow-runs.js +1 -1
- package/dist/db/workflow-runs.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/presentation/inter-module.d.ts +2 -2
- package/dist/presentation/inter-module.d.ts.map +1 -1
- package/dist/presentation/inter-module.js +2 -1
- package/dist/presentation/inter-module.js.map +1 -1
- package/dist/presentation/routes/cancel-run.d.ts +1 -1
- package/dist/presentation/routes/cancel-run.d.ts.map +1 -1
- package/dist/presentation/routes/cancel-run.js.map +1 -1
- package/dist/presentation/routes/checkout-token.d.ts +2 -2
- package/dist/presentation/routes/checkout-token.d.ts.map +1 -1
- package/dist/presentation/routes/checkout-token.js +1 -1
- package/dist/presentation/routes/checkout-token.js.map +1 -1
- package/dist/presentation/routes/get-run-aggregates.d.ts +1 -1
- package/dist/presentation/routes/get-run-aggregates.d.ts.map +1 -1
- package/dist/presentation/routes/get-run-aggregates.js.map +1 -1
- package/dist/presentation/routes/get-run.d.ts +1 -1
- package/dist/presentation/routes/get-run.d.ts.map +1 -1
- package/dist/presentation/routes/get-run.js.map +1 -1
- package/dist/presentation/routes/index.d.ts +2 -2
- package/dist/presentation/routes/index.d.ts.map +1 -1
- package/dist/presentation/routes/index.js.map +1 -1
- package/dist/presentation/routes/list-run-attempts.d.ts +1 -1
- package/dist/presentation/routes/list-run-attempts.d.ts.map +1 -1
- package/dist/presentation/routes/list-run-attempts.js.map +1 -1
- package/dist/presentation/routes/list-runs.d.ts +1 -1
- package/dist/presentation/routes/list-runs.d.ts.map +1 -1
- package/dist/presentation/routes/list-runs.js.map +1 -1
- package/dist/presentation/routes/next-step.d.ts.map +1 -1
- package/dist/presentation/routes/next-step.js +11 -8
- package/dist/presentation/routes/next-step.js.map +1 -1
- package/dist/presentation/routes/project-access.d.ts +1 -1
- package/dist/presentation/routes/project-access.d.ts.map +1 -1
- package/dist/presentation/routes/project-access.js.map +1 -1
- package/dist/presentation/routes/require-accessible-run.d.ts +1 -1
- package/dist/presentation/routes/require-accessible-run.d.ts.map +1 -1
- package/dist/presentation/routes/require-accessible-run.js.map +1 -1
- package/dist/presentation/routes/rerun-run.d.ts +1 -1
- package/dist/presentation/routes/rerun-run.d.ts.map +1 -1
- package/dist/presentation/routes/rerun-run.js.map +1 -1
- package/dist/temporal/activities/index.d.ts +2 -2
- package/dist/temporal/activities/index.d.ts.map +1 -1
- package/dist/temporal/activities/index.js.map +1 -1
- package/dist/temporal/activities/orchestration-activities.d.ts +2 -2
- package/dist/temporal/activities/orchestration-activities.d.ts.map +1 -1
- package/dist/temporal/activities/orchestration-activities.js.map +1 -1
- package/dist/tsconfig.test.tsbuildinfo +1 -1
- package/package.json +26 -28
- package/src/core/agent-defaults.ts +2 -1
- package/src/core/agent-tool-capability-warning.test.ts +26 -56
- package/src/core/agent-tools.ts +9 -7
- package/src/core/checkout.test.ts +2 -2
- package/src/core/checkout.ts +2 -2
- package/src/core/entities/step.ts +1 -2
- package/src/core/errors.ts +0 -7
- package/src/core/job-execution.ts +3 -10
- package/src/core/run-workflow.test.ts +22 -55
- package/src/core/run-workflow.ts +2 -2
- package/src/core/step-config/agent.ts +1 -1
- package/src/core/step-config/materialize-job-execution-steps.test.ts +1 -2
- package/src/core/step-config/materialize-workflow-model.test.ts +10 -13
- package/src/db/job-listeners.ts +2 -2
- package/src/db/workflow-runs/job-executions.ts +0 -21
- package/src/db/workflow-runs/run-create.test.ts +30 -23
- package/src/db/workflow-runs/run-create.ts +2 -2
- package/src/db/workflow-runs.ts +0 -1
- package/src/index.ts +3 -3
- package/src/presentation/dto/checkout-token.test.ts +7 -1
- package/src/presentation/inter-module.ts +4 -7
- package/src/presentation/routes/agent-runtime-config.test.ts +40 -132
- package/src/presentation/routes/cancel-run.test.ts +1 -1
- package/src/presentation/routes/cancel-run.ts +1 -1
- package/src/presentation/routes/checkout-token.test.ts +4 -37
- package/src/presentation/routes/checkout-token.ts +2 -2
- package/src/presentation/routes/get-run-aggregates.test.ts +1 -1
- package/src/presentation/routes/get-run-aggregates.ts +1 -1
- package/src/presentation/routes/get-run.test.ts +1 -1
- package/src/presentation/routes/get-run.ts +1 -1
- package/src/presentation/routes/get-step-secrets.test.ts +2 -2
- package/src/presentation/routes/index.test.ts +3 -3
- package/src/presentation/routes/index.ts +2 -2
- package/src/presentation/routes/list-run-attempts.test.ts +1 -1
- package/src/presentation/routes/list-run-attempts.ts +1 -1
- package/src/presentation/routes/list-runs.test.ts +1 -1
- package/src/presentation/routes/list-runs.ts +1 -1
- package/src/presentation/routes/next-step.test.ts +27 -69
- package/src/presentation/routes/next-step.ts +9 -5
- package/src/presentation/routes/project-access.ts +1 -1
- package/src/presentation/routes/report-step.test.ts +2 -3
- package/src/presentation/routes/require-accessible-run.ts +1 -1
- package/src/presentation/routes/rerun-run.test.ts +1 -1
- package/src/presentation/routes/rerun-run.ts +1 -1
- package/src/temporal/activities/index.ts +2 -2
- package/src/temporal/activities/orchestration-activities.ts +2 -2
- package/test/factories/workflow-model.ts +3 -1
- package/test/fixtures/active-lease-token.ts +5 -32
- package/test/fixtures/agent-inter-module.ts +32 -1
- package/test/fixtures/auth-inter-module.ts +2 -13
- package/test/fixtures/lease-token.ts +43 -29
- package/test/fixtures/projects-inter-module.ts +1 -1
- package/test/fixtures/runners-inter-module.ts +40 -48
- package/test/globalSetup.ts +0 -38
- package/test/setup.ts +2 -0
- package/tsconfig.build.tsbuildinfo +1 -1
- package/dist/db/runner-lease-table.d.ts +0 -469
- package/dist/db/runner-lease-table.d.ts.map +0 -1
- package/dist/db/runner-lease-table.js +0 -43
- package/dist/db/runner-lease-table.js.map +0 -1
- package/src/db/runner-lease-table.ts +0 -39
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shipfox/api-workflows",
|
|
3
3
|
"license": "MIT",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "9.0.1",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
7
7
|
"url": "git+https://github.com/ShipfoxHQ/shipfox.git",
|
|
@@ -11,10 +11,6 @@
|
|
|
11
11
|
"type": "module",
|
|
12
12
|
"main": "dist/index.js",
|
|
13
13
|
"types": "dist/index.d.ts",
|
|
14
|
-
"imports": {
|
|
15
|
-
"#test/*": "./test/*",
|
|
16
|
-
"#*": "./dist/*"
|
|
17
|
-
},
|
|
18
14
|
"exports": {
|
|
19
15
|
".": {
|
|
20
16
|
"types": "./dist/index.d.ts",
|
|
@@ -26,29 +22,31 @@
|
|
|
26
22
|
"@temporalio/workflow": "1.18.1",
|
|
27
23
|
"drizzle-orm": "^0.45.2",
|
|
28
24
|
"zod": "^4.4.3",
|
|
29
|
-
"@shipfox/api-auth-
|
|
30
|
-
"@shipfox/api-
|
|
31
|
-
"@shipfox/
|
|
32
|
-
"@shipfox/
|
|
33
|
-
"@shipfox/api-definitions-dto": "
|
|
34
|
-
"@shipfox/api-projects-dto": "
|
|
35
|
-
"@shipfox/api-
|
|
36
|
-
"@shipfox/api-
|
|
37
|
-
"@shipfox/api-
|
|
38
|
-
"@shipfox/
|
|
39
|
-
"@shipfox/api-
|
|
40
|
-
"@shipfox/
|
|
41
|
-
"@shipfox/
|
|
42
|
-
"@shipfox/
|
|
43
|
-
"@shipfox/node-
|
|
44
|
-
"@shipfox/node-
|
|
45
|
-
"@shipfox/node-
|
|
46
|
-
"@shipfox/node-module": "0.
|
|
47
|
-
"@shipfox/node-
|
|
48
|
-
"@shipfox/
|
|
49
|
-
"@shipfox/node-
|
|
50
|
-
"@shipfox/
|
|
51
|
-
|
|
25
|
+
"@shipfox/api-auth-dto": "9.0.1",
|
|
26
|
+
"@shipfox/api-agent-dto": "9.0.1",
|
|
27
|
+
"@shipfox/annotations-dto": "9.0.1",
|
|
28
|
+
"@shipfox/api-auth-context": "9.0.1",
|
|
29
|
+
"@shipfox/api-definitions-dto": "9.0.1",
|
|
30
|
+
"@shipfox/api-projects-dto": "9.0.1",
|
|
31
|
+
"@shipfox/api-secrets-dto": "9.0.1",
|
|
32
|
+
"@shipfox/api-workflows-dto": "9.0.1",
|
|
33
|
+
"@shipfox/api-integration-core-dto": "9.0.1",
|
|
34
|
+
"@shipfox/inter-module": "0.2.1",
|
|
35
|
+
"@shipfox/api-runners-dto": "9.0.1",
|
|
36
|
+
"@shipfox/expression": "1.1.4",
|
|
37
|
+
"@shipfox/node-error-monitoring": "0.2.1",
|
|
38
|
+
"@shipfox/node-fastify": "0.3.1",
|
|
39
|
+
"@shipfox/node-opentelemetry": "0.6.1",
|
|
40
|
+
"@shipfox/node-outbox": "0.2.5",
|
|
41
|
+
"@shipfox/node-postgres": "0.4.3",
|
|
42
|
+
"@shipfox/node-module": "1.0.0",
|
|
43
|
+
"@shipfox/node-temporal": "0.4.1",
|
|
44
|
+
"@shipfox/runner-labels": "0.1.2",
|
|
45
|
+
"@shipfox/node-drizzle": "0.3.3",
|
|
46
|
+
"@shipfox/workflow-document": "2.1.2"
|
|
47
|
+
},
|
|
48
|
+
"imports": {
|
|
49
|
+
"#*": "./dist/*"
|
|
52
50
|
},
|
|
53
51
|
"scripts": {
|
|
54
52
|
"build": "shipfox-swc && shipfox-temporal-bundle dist/temporal/workflows/index.js",
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {ModelProviderRef} from '@shipfox/api-agent-dto';
|
|
2
2
|
import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
|
|
3
|
+
import type {AgentThinking, Harness} from '@shipfox/workflow-document';
|
|
3
4
|
|
|
4
5
|
export interface AgentDefaultsInput {
|
|
5
6
|
readonly harness?: Harness | undefined;
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type {AnnotationsInterModuleClient} from '@shipfox/annotations-dto/inter-module';
|
|
2
|
-
import type {JobLeaseTokenClaims} from '@shipfox/api-auth';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {runnersTestClient} from '#test/fixtures/runners-inter-module.js';
|
|
2
|
+
import type {JobLeaseTokenClaims} from '@shipfox/api-auth-dto';
|
|
3
|
+
import type {RunnersInterModuleClient} from '@shipfox/api-runners-dto/inter-module';
|
|
4
|
+
import {runnersTestClient, setRunnerToolCapabilities} from '#test/fixtures/runners-inter-module.js';
|
|
6
5
|
import {warnAgentToolCapabilityMismatchOnDispatch as warnAgentToolCapabilityMismatchOnDispatchImpl} from './agent-tool-capability-warning.js';
|
|
7
6
|
import type {Step} from './entities/step.js';
|
|
8
7
|
|
|
@@ -86,45 +85,20 @@ function agentStep(params: Partial<Step> = {}): Step {
|
|
|
86
85
|
};
|
|
87
86
|
}
|
|
88
87
|
|
|
89
|
-
|
|
88
|
+
function setRunnerSessionCapabilities(params: {
|
|
90
89
|
runnerSessionId: string;
|
|
91
90
|
workspaceId: string;
|
|
92
|
-
toolCapabilities:
|
|
91
|
+
toolCapabilities:
|
|
92
|
+
| Awaited<
|
|
93
|
+
ReturnType<RunnersInterModuleClient['getEffectiveRunnerToolCapabilities']>
|
|
94
|
+
>['capabilities']
|
|
95
|
+
| null;
|
|
93
96
|
reportedAt?: 'fresh' | 'stale' | 'missing';
|
|
94
|
-
}):
|
|
95
|
-
|
|
96
|
-
params.
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
? sql`now() - interval '1 hour'`
|
|
100
|
-
: sql`now()`;
|
|
101
|
-
const toolCapabilities =
|
|
102
|
-
params.toolCapabilities === null
|
|
103
|
-
? sql`NULL`
|
|
104
|
-
: sql`${JSON.stringify(params.toolCapabilities)}::jsonb`;
|
|
105
|
-
|
|
106
|
-
await db().execute(sql`
|
|
107
|
-
INSERT INTO runners_runner_sessions (
|
|
108
|
-
id,
|
|
109
|
-
workspace_id,
|
|
110
|
-
scope,
|
|
111
|
-
registration_token_id,
|
|
112
|
-
registration_token_kind,
|
|
113
|
-
labels,
|
|
114
|
-
tool_capabilities,
|
|
115
|
-
tool_capabilities_reported_at
|
|
116
|
-
)
|
|
117
|
-
VALUES (
|
|
118
|
-
${params.runnerSessionId},
|
|
119
|
-
${params.workspaceId},
|
|
120
|
-
'workspace',
|
|
121
|
-
${crypto.randomUUID()},
|
|
122
|
-
'manual',
|
|
123
|
-
ARRAY['linux'],
|
|
124
|
-
${toolCapabilities},
|
|
125
|
-
${reportedAt}
|
|
126
|
-
)
|
|
127
|
-
`);
|
|
97
|
+
}): void {
|
|
98
|
+
setRunnerToolCapabilities(params.runnerSessionId, {
|
|
99
|
+
capabilities: params.toolCapabilities === null ? {harnesses: {}} : params.toolCapabilities,
|
|
100
|
+
reportFresh: params.reportedAt !== 'missing' && params.reportedAt !== 'stale',
|
|
101
|
+
});
|
|
128
102
|
}
|
|
129
103
|
|
|
130
104
|
function annotationsFor(jobExecutionId: string): Array<{context: string; body: string}> {
|
|
@@ -138,7 +112,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
|
|
|
138
112
|
it('writes a warning when the matched runner advertised a set without requested tools', async () => {
|
|
139
113
|
const identity = lease();
|
|
140
114
|
const step = agentStep({jobExecutionId: identity.jobExecutionId});
|
|
141
|
-
|
|
115
|
+
setRunnerSessionCapabilities({
|
|
142
116
|
runnerSessionId: identity.runnerSessionId,
|
|
143
117
|
workspaceId: identity.workspaceId,
|
|
144
118
|
toolCapabilities: {harnesses: {pi: {tools: ['read']}}},
|
|
@@ -156,7 +130,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
|
|
|
156
130
|
it('writes unknown wording when capabilities are missing', async () => {
|
|
157
131
|
const identity = lease();
|
|
158
132
|
const step = agentStep({jobExecutionId: identity.jobExecutionId});
|
|
159
|
-
|
|
133
|
+
setRunnerSessionCapabilities({
|
|
160
134
|
runnerSessionId: identity.runnerSessionId,
|
|
161
135
|
workspaceId: identity.workspaceId,
|
|
162
136
|
toolCapabilities: null,
|
|
@@ -173,7 +147,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
|
|
|
173
147
|
it('writes missing-harness wording when fresh capabilities omit the harness', async () => {
|
|
174
148
|
const identity = lease();
|
|
175
149
|
const step = agentStep({jobExecutionId: identity.jobExecutionId});
|
|
176
|
-
|
|
150
|
+
setRunnerSessionCapabilities({
|
|
177
151
|
runnerSessionId: identity.runnerSessionId,
|
|
178
152
|
workspaceId: identity.workspaceId,
|
|
179
153
|
toolCapabilities: {harnesses: {claude: {tools: ['read', 'web_search']}}},
|
|
@@ -199,7 +173,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
|
|
|
199
173
|
prompt: 'Fix it.',
|
|
200
174
|
},
|
|
201
175
|
});
|
|
202
|
-
|
|
176
|
+
setRunnerSessionCapabilities({
|
|
203
177
|
runnerSessionId: identity.runnerSessionId,
|
|
204
178
|
workspaceId: identity.workspaceId,
|
|
205
179
|
toolCapabilities: {harnesses: {pi: {tools: ['read']}}},
|
|
@@ -214,20 +188,16 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
|
|
|
214
188
|
it('removes an existing warning when a later dispatch has all requested tools', async () => {
|
|
215
189
|
const identity = lease();
|
|
216
190
|
const step = agentStep({jobExecutionId: identity.jobExecutionId});
|
|
217
|
-
|
|
191
|
+
setRunnerSessionCapabilities({
|
|
218
192
|
runnerSessionId: identity.runnerSessionId,
|
|
219
193
|
workspaceId: identity.workspaceId,
|
|
220
194
|
toolCapabilities: {harnesses: {pi: {tools: ['read']}}},
|
|
221
195
|
});
|
|
222
196
|
await warnAgentToolCapabilityMismatchOnDispatch({leaseIdentity: identity, step});
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
})}::jsonb,
|
|
228
|
-
tool_capabilities_reported_at = now()
|
|
229
|
-
WHERE id = ${identity.runnerSessionId}
|
|
230
|
-
`);
|
|
197
|
+
setRunnerToolCapabilities(identity.runnerSessionId, {
|
|
198
|
+
capabilities: {harnesses: {pi: {tools: ['read', 'web_search']}}},
|
|
199
|
+
reportFresh: true,
|
|
200
|
+
});
|
|
231
201
|
|
|
232
202
|
await warnAgentToolCapabilityMismatchOnDispatch({leaseIdentity: identity, step});
|
|
233
203
|
|
|
@@ -247,7 +217,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
|
|
|
247
217
|
prompt: 'Fix it.',
|
|
248
218
|
},
|
|
249
219
|
});
|
|
250
|
-
|
|
220
|
+
setRunnerSessionCapabilities({
|
|
251
221
|
runnerSessionId: identity.runnerSessionId,
|
|
252
222
|
workspaceId: identity.workspaceId,
|
|
253
223
|
toolCapabilities: null,
|
|
@@ -265,7 +235,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
|
|
|
265
235
|
type: 'run',
|
|
266
236
|
config: {run: 'echo ok'},
|
|
267
237
|
});
|
|
268
|
-
|
|
238
|
+
setRunnerSessionCapabilities({
|
|
269
239
|
runnerSessionId: identity.runnerSessionId,
|
|
270
240
|
workspaceId: identity.workspaceId,
|
|
271
241
|
toolCapabilities: null,
|
|
@@ -290,7 +260,7 @@ describe('warnAgentToolCapabilityMismatchOnDispatch', () => {
|
|
|
290
260
|
prompt: 'Fix it.',
|
|
291
261
|
},
|
|
292
262
|
});
|
|
293
|
-
|
|
263
|
+
setRunnerSessionCapabilities({
|
|
294
264
|
runnerSessionId: identity.runnerSessionId,
|
|
295
265
|
workspaceId: identity.workspaceId,
|
|
296
266
|
toolCapabilities: {harnesses: {pi: {tools: []}}},
|
package/src/core/agent-tools.ts
CHANGED
|
@@ -3,18 +3,20 @@ import type {
|
|
|
3
3
|
MaterializedAgentIntegrationToolConfigDto,
|
|
4
4
|
} from '@shipfox/api-agent-dto';
|
|
5
5
|
import type {WorkflowModel} from '@shipfox/api-definitions-dto';
|
|
6
|
-
import type {
|
|
7
|
-
|
|
8
|
-
AgentToolCatalogMethod,
|
|
9
|
-
IntegrationProviderKind,
|
|
10
|
-
IntegrationsModuleClient,
|
|
11
|
-
} from '@shipfox/api-integration-core-dto';
|
|
12
|
-
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
|
|
6
|
+
import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';
|
|
7
|
+
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
|
|
13
8
|
import {AgentIntegrationMaterializationError} from './errors.js';
|
|
14
9
|
|
|
15
10
|
type WorkflowModelJob = WorkflowModel['jobs'][number];
|
|
16
11
|
type WorkflowModelAgentStep = Extract<WorkflowModelJob['steps'][number], {kind: 'agent'}>;
|
|
17
12
|
type WorkflowModelStepIntegration = NonNullable<WorkflowModelAgentStep['integrations']>[number];
|
|
13
|
+
type IntegrationsAgentToolsContext = Awaited<
|
|
14
|
+
ReturnType<IntegrationsModuleClient['getAgentToolsContext']>
|
|
15
|
+
>;
|
|
16
|
+
export type AgentToolCatalogEntry =
|
|
17
|
+
IntegrationsAgentToolsContext['catalogs'][number]['tools'][number];
|
|
18
|
+
type AgentToolCatalogMethod = NonNullable<AgentToolCatalogEntry['methods']>[number];
|
|
19
|
+
type IntegrationProviderKind = IntegrationsAgentToolsContext['catalogs'][number]['provider'];
|
|
18
20
|
type AgentToolCatalogs = ReadonlyMap<IntegrationProviderKind, readonly AgentToolCatalogEntry[]>;
|
|
19
21
|
type WorkspaceConnectionSnapshot = ReadonlyMap<
|
|
20
22
|
string,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto';
|
|
2
|
-
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
|
|
1
|
+
import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';
|
|
2
|
+
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
|
|
3
3
|
import * as workflowRuns from '#db/workflow-runs.js';
|
|
4
4
|
import {jobFactory} from '#test/factories/job.js';
|
|
5
5
|
import {projectFactory} from '#test/factories/project.js';
|
package/src/core/checkout.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto';
|
|
2
|
-
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
|
|
1
|
+
import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';
|
|
2
|
+
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
|
|
3
3
|
import {getJobById, getWorkflowRunByAttemptId} from '#db/workflow-runs.js';
|
|
4
4
|
import {
|
|
5
5
|
CheckoutIntentUnresolvedError,
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
AgentIntegrationMcpServerConfigDto,
|
|
3
|
-
AgentThinking,
|
|
4
|
-
Harness,
|
|
5
3
|
MaterializedAgentIntegrationConfigDto,
|
|
6
4
|
} from '@shipfox/api-agent-dto';
|
|
7
5
|
import type {
|
|
@@ -10,6 +8,7 @@ import type {
|
|
|
10
8
|
ResolvedField,
|
|
11
9
|
WorkflowExpression,
|
|
12
10
|
} from '@shipfox/expression';
|
|
11
|
+
import type {AgentThinking, Harness} from '@shipfox/workflow-document';
|
|
13
12
|
import type {InterpolationUnresolvableField} from '../errors.js';
|
|
14
13
|
|
|
15
14
|
export type StepStatus = 'pending' | 'running' | 'succeeded' | 'failed' | 'cancelled' | 'skipped';
|
package/src/core/errors.ts
CHANGED
|
@@ -104,13 +104,6 @@ export class JobNotFoundError extends Error {
|
|
|
104
104
|
}
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
export class JobLeaseNotActiveError extends Error {
|
|
108
|
-
constructor(readonly jobExecutionId: string) {
|
|
109
|
-
super(`Job lease is no longer active: ${jobExecutionId}`);
|
|
110
|
-
this.name = 'JobLeaseNotActiveError';
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
107
|
export class InvalidJobRunnerLabelsError extends Error {
|
|
115
108
|
constructor(readonly labels: readonly string[]) {
|
|
116
109
|
super(`Job runner labels are invalid: ${labels.join(', ')}`);
|
|
@@ -16,7 +16,6 @@ import {
|
|
|
16
16
|
getStepAttemptsByJobExecutionId,
|
|
17
17
|
getStepsByJobExecutionIdForUpdate,
|
|
18
18
|
insertRunningStepAttempt,
|
|
19
|
-
lockActiveJobExecutionLeaseForUpdate,
|
|
20
19
|
markStepRunning,
|
|
21
20
|
markStepSkipped,
|
|
22
21
|
settleJobFailed,
|
|
@@ -33,7 +32,6 @@ import type {PersistedEvaluationTraceEntry, Step, StepStatusReason} from './enti
|
|
|
33
32
|
import {
|
|
34
33
|
AgentConfigUnresolvableError,
|
|
35
34
|
InterpolationUnresolvableError,
|
|
36
|
-
JobLeaseNotActiveError,
|
|
37
35
|
JobNotFoundError,
|
|
38
36
|
StepAttemptAheadError,
|
|
39
37
|
StepNotFoundError,
|
|
@@ -336,21 +334,16 @@ function dispatchConfigError(error: DispatchConfigError): Record<string, unknown
|
|
|
336
334
|
}
|
|
337
335
|
|
|
338
336
|
export interface NextStepForLeasedJobExecutionParams {
|
|
339
|
-
jobId: string;
|
|
340
337
|
jobExecutionId: string;
|
|
341
|
-
runnerSessionId: string;
|
|
342
338
|
agent?: AgentInterModuleClient | undefined;
|
|
343
339
|
}
|
|
344
340
|
|
|
345
341
|
export function nextStepForLeasedJobExecution(
|
|
346
342
|
params: NextStepForLeasedJobExecutionParams,
|
|
347
343
|
): Promise<NextStep> {
|
|
348
|
-
return withTransaction(
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
return nextStepForJobExecutionInTransaction(params.jobExecutionId, tx, params.agent);
|
|
353
|
-
});
|
|
344
|
+
return withTransaction((tx) =>
|
|
345
|
+
nextStepForJobExecutionInTransaction(params.jobExecutionId, tx, params.agent),
|
|
346
|
+
);
|
|
354
347
|
}
|
|
355
348
|
|
|
356
349
|
export function nextStepForJob(
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
|
|
2
|
-
import type
|
|
3
|
-
import {createWorkflowModelSnapshot} from '@shipfox/api-definitions-dto';
|
|
2
|
+
import {createWorkflowModelSnapshot, type WorkflowModel} from '@shipfox/api-definitions-dto';
|
|
4
3
|
import type {DefinitionsInterModuleClient} from '@shipfox/api-definitions-dto/inter-module';
|
|
4
|
+
import {agentValidationCatalog} from '#test/fixtures/agent-inter-module.js';
|
|
5
5
|
import {workflowModel} from '#test/index.js';
|
|
6
6
|
import type {TriggerPayload} from './entities/workflow-run.js';
|
|
7
7
|
import {DefinitionNotFoundError, ProjectMismatchError} from './errors.js';
|
|
@@ -16,61 +16,26 @@ const mockResolveAgentConfig = vi.hoisted(() =>
|
|
|
16
16
|
}),
|
|
17
17
|
);
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
persistCredentials: true,
|
|
23
|
-
},
|
|
24
|
-
getDefinitionById: vi.fn(),
|
|
25
|
-
}));
|
|
19
|
+
type DefinitionForWorkflowRun = NonNullable<
|
|
20
|
+
Awaited<ReturnType<DefinitionsInterModuleClient['getDefinitionForWorkflowRun']>>['definition']
|
|
21
|
+
>;
|
|
26
22
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
const mockGetDefinitionById = vi.mocked(getDefinitionById);
|
|
23
|
+
let definitionResponse: DefinitionForWorkflowRun | null = null;
|
|
30
24
|
const definitions: DefinitionsInterModuleClient = {
|
|
31
|
-
getDefinitionForWorkflowRun: async (
|
|
32
|
-
const definition = await mockGetDefinitionById(definitionId);
|
|
33
|
-
return {
|
|
34
|
-
definition:
|
|
35
|
-
definition === undefined
|
|
36
|
-
? null
|
|
37
|
-
: {
|
|
38
|
-
id: definition.id,
|
|
39
|
-
projectId: definition.projectId,
|
|
40
|
-
name: definition.name,
|
|
41
|
-
model: createWorkflowModelSnapshot(definition.model),
|
|
42
|
-
sourceSnapshot: definition.sourceSnapshot,
|
|
43
|
-
},
|
|
44
|
-
};
|
|
45
|
-
},
|
|
25
|
+
getDefinitionForWorkflowRun: async () => ({definition: definitionResponse}),
|
|
46
26
|
};
|
|
47
27
|
|
|
48
|
-
function buildDefinition(
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
jobs: {
|
|
53
|
-
build: {steps: [{run: 'echo hello'}]},
|
|
54
|
-
},
|
|
55
|
-
};
|
|
28
|
+
function buildDefinition(
|
|
29
|
+
overrides: Partial<Omit<DefinitionForWorkflowRun, 'model'>> & {model?: WorkflowModel} = {},
|
|
30
|
+
): DefinitionForWorkflowRun {
|
|
31
|
+
const {model = workflowModel(), ...definitionOverrides} = overrides;
|
|
56
32
|
return {
|
|
57
33
|
id: crypto.randomUUID(),
|
|
58
34
|
projectId: crypto.randomUUID(),
|
|
59
|
-
configPath: '.shipfox/workflows/test.yml',
|
|
60
|
-
source: 'manual',
|
|
61
|
-
sha: null,
|
|
62
|
-
ref: null,
|
|
63
35
|
name: 'Test Workflow',
|
|
64
|
-
|
|
65
|
-
document,
|
|
66
|
-
model,
|
|
36
|
+
model: createWorkflowModelSnapshot(model),
|
|
67
37
|
sourceSnapshot: null,
|
|
68
|
-
|
|
69
|
-
fetchedAt: new Date(),
|
|
70
|
-
createdAt: new Date(),
|
|
71
|
-
updatedAt: new Date(),
|
|
72
|
-
deletedAt: null,
|
|
73
|
-
...overrides,
|
|
38
|
+
...definitionOverrides,
|
|
74
39
|
};
|
|
75
40
|
}
|
|
76
41
|
|
|
@@ -87,6 +52,7 @@ describe('runWorkflow', () => {
|
|
|
87
52
|
let workspaceId: string;
|
|
88
53
|
let projectId: string;
|
|
89
54
|
const agent: AgentInterModuleClient = {
|
|
55
|
+
getValidationCatalog: vi.fn().mockResolvedValue(agentValidationCatalog),
|
|
90
56
|
resolveAgentConfig: mockResolveAgentConfig,
|
|
91
57
|
resolveRuntimeCredentials: vi.fn(),
|
|
92
58
|
};
|
|
@@ -95,11 +61,12 @@ describe('runWorkflow', () => {
|
|
|
95
61
|
workspaceId = crypto.randomUUID();
|
|
96
62
|
projectId = crypto.randomUUID();
|
|
97
63
|
mockResolveAgentConfig.mockClear();
|
|
64
|
+
definitionResponse = null;
|
|
98
65
|
});
|
|
99
66
|
|
|
100
67
|
test('creates a run from a valid definition with the provided manual trigger payload', async () => {
|
|
101
68
|
const definition = buildDefinition({projectId});
|
|
102
|
-
|
|
69
|
+
definitionResponse = definition;
|
|
103
70
|
const triggerPayload = manualPayload();
|
|
104
71
|
|
|
105
72
|
const run = await runWorkflow(definitions, {
|
|
@@ -129,7 +96,7 @@ describe('runWorkflow', () => {
|
|
|
129
96
|
},
|
|
130
97
|
});
|
|
131
98
|
const definition = buildDefinition({projectId, model});
|
|
132
|
-
|
|
99
|
+
definitionResponse = definition;
|
|
133
100
|
|
|
134
101
|
const run = await runWorkflow(definitions, {
|
|
135
102
|
workspaceId,
|
|
@@ -145,7 +112,7 @@ describe('runWorkflow', () => {
|
|
|
145
112
|
|
|
146
113
|
test('persists an integration trigger payload intact', async () => {
|
|
147
114
|
const definition = buildDefinition({projectId});
|
|
148
|
-
|
|
115
|
+
definitionResponse = definition;
|
|
149
116
|
const triggerPayload: TriggerPayload = {
|
|
150
117
|
provider: 'github',
|
|
151
118
|
source: 'github_acme',
|
|
@@ -171,7 +138,7 @@ describe('runWorkflow', () => {
|
|
|
171
138
|
test('creates the run with the definition source snapshot', async () => {
|
|
172
139
|
const sourceSnapshot = {content: 'name: Test Workflow\njobs: {}\n', format: 'yaml'} as const;
|
|
173
140
|
const definition = buildDefinition({projectId, sourceSnapshot});
|
|
174
|
-
|
|
141
|
+
definitionResponse = definition;
|
|
175
142
|
|
|
176
143
|
const run = await runWorkflow(definitions, {
|
|
177
144
|
workspaceId,
|
|
@@ -185,7 +152,7 @@ describe('runWorkflow', () => {
|
|
|
185
152
|
});
|
|
186
153
|
|
|
187
154
|
test('throws DefinitionNotFoundError for unknown definition', async () => {
|
|
188
|
-
|
|
155
|
+
definitionResponse = null;
|
|
189
156
|
|
|
190
157
|
const unknownId = crypto.randomUUID();
|
|
191
158
|
|
|
@@ -203,7 +170,7 @@ describe('runWorkflow', () => {
|
|
|
203
170
|
test('throws ProjectMismatchError when definition.projectId does not match', async () => {
|
|
204
171
|
const otherProjectId = crypto.randomUUID();
|
|
205
172
|
const definition = buildDefinition({projectId: otherProjectId});
|
|
206
|
-
|
|
173
|
+
definitionResponse = definition;
|
|
207
174
|
|
|
208
175
|
await expect(
|
|
209
176
|
runWorkflow(definitions, {
|
|
@@ -218,7 +185,7 @@ describe('runWorkflow', () => {
|
|
|
218
185
|
|
|
219
186
|
test('passes inputs through to the run', async () => {
|
|
220
187
|
const definition = buildDefinition({projectId});
|
|
221
|
-
|
|
188
|
+
definitionResponse = definition;
|
|
222
189
|
|
|
223
190
|
const run = await runWorkflow(definitions, {
|
|
224
191
|
workspaceId,
|
package/src/core/run-workflow.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
|
|
2
2
|
import {workflowModelFromSnapshot} from '@shipfox/api-definitions-dto';
|
|
3
3
|
import type {DefinitionsInterModuleClient} from '@shipfox/api-definitions-dto/inter-module';
|
|
4
|
-
import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto';
|
|
5
|
-
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
|
|
4
|
+
import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';
|
|
5
|
+
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
|
|
6
6
|
import type {SecretsInterModuleClient} from '@shipfox/api-secrets-dto/inter-module';
|
|
7
7
|
import {createWorkflowRun} from '#db/workflow-runs.js';
|
|
8
8
|
import {createAgentDefaultsResolver} from './agent-defaults.js';
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
AGENT_INTEGRATION_MCP_SERVER_NAME,
|
|
5
5
|
AGENT_INTEGRATION_MCP_TRANSPORT,
|
|
6
6
|
type AgentIntegrationMcpServerConfigDto,
|
|
7
|
-
type AgentThinking,
|
|
8
7
|
type MaterializedAgentIntegrationConfigDto,
|
|
9
8
|
} from '@shipfox/api-agent-dto';
|
|
10
9
|
import {agentInterModuleContract} from '@shipfox/api-agent-dto/inter-module';
|
|
11
10
|
import type {WorkflowModel} from '@shipfox/api-definitions-dto';
|
|
12
11
|
import type {ResolvedField, SiteResolvedField} from '@shipfox/expression';
|
|
13
12
|
import {isInterModuleKnownError} from '@shipfox/inter-module';
|
|
13
|
+
import type {AgentThinking} from '@shipfox/workflow-document';
|
|
14
14
|
import type {AgentDefaultsResolver, ResolvedAgentDefaults} from '#core/agent-defaults.js';
|
|
15
15
|
import {
|
|
16
16
|
type AgentToolMaterializationContext,
|
|
@@ -5,10 +5,9 @@ import {
|
|
|
5
5
|
AGENT_INTEGRATION_MCP_TRANSPORT,
|
|
6
6
|
} from '@shipfox/api-agent-dto';
|
|
7
7
|
import {agentInterModuleContract} from '@shipfox/api-agent-dto/inter-module';
|
|
8
|
-
import type {AgentToolCatalogEntry} from '@shipfox/api-integration-core';
|
|
9
8
|
import {createInterModuleKnownError} from '@shipfox/inter-module';
|
|
10
9
|
import type {AgentDefaultsResolver} from '#core/agent-defaults.js';
|
|
11
|
-
import type {AgentToolMaterializationContext} from '#core/agent-tools.js';
|
|
10
|
+
import type {AgentToolCatalogEntry, AgentToolMaterializationContext} from '#core/agent-tools.js';
|
|
12
11
|
import {AgentConfigUnresolvableError, InterpolationUnresolvableError} from '#core/errors.js';
|
|
13
12
|
import {resolveTestAgentDefaults} from '#test/fixtures/agent-inter-module.js';
|
|
14
13
|
import {workflowModel} from '#test/index.js';
|
|
@@ -1,9 +1,5 @@
|
|
|
1
1
|
import {agentInterModuleContract} from '@shipfox/api-agent-dto/inter-module';
|
|
2
|
-
import {
|
|
3
|
-
DEFAULT_JOB_CHECKOUT,
|
|
4
|
-
normalizeWorkflowDocument,
|
|
5
|
-
type WorkflowModel,
|
|
6
|
-
} from '@shipfox/api-definitions';
|
|
2
|
+
import {DEFAULT_JOB_CHECKOUT, type WorkflowModel} from '@shipfox/api-definitions-dto';
|
|
7
3
|
import {createInterModuleKnownError} from '@shipfox/inter-module';
|
|
8
4
|
import type {AgentDefaultsResolver} from '#core/agent-defaults.js';
|
|
9
5
|
import {AgentConfigUnresolvableError, InterpolationUnresolvableError} from '#core/errors.js';
|
|
@@ -539,7 +535,7 @@ describe('materializeWorkflowModel', () => {
|
|
|
539
535
|
});
|
|
540
536
|
|
|
541
537
|
it('freezes vars from the run-creation context into step config', async () => {
|
|
542
|
-
const model =
|
|
538
|
+
const model = workflowModel({
|
|
543
539
|
name: 'vars workflow',
|
|
544
540
|
runner: 'ubuntu-latest',
|
|
545
541
|
jobs: {
|
|
@@ -999,14 +995,15 @@ describe('materializeWorkflowModel', () => {
|
|
|
999
995
|
|
|
1000
996
|
it('skips listening job steps at workflow-run creation', async () => {
|
|
1001
997
|
const stepNameSource = `Review ${template('execution.index')}`;
|
|
1002
|
-
const model =
|
|
998
|
+
const model = workflowModel({
|
|
1003
999
|
name: 'Listening workflow',
|
|
1004
1000
|
runner: 'ubuntu-latest',
|
|
1005
1001
|
jobs: {
|
|
1006
1002
|
review: {
|
|
1007
1003
|
listening: {
|
|
1008
1004
|
on: [{source: 'github', event: 'pull_request_review'}],
|
|
1009
|
-
|
|
1005
|
+
maxExecutions: 3,
|
|
1006
|
+
onResolve: 'finish',
|
|
1010
1007
|
},
|
|
1011
1008
|
steps: [{name: stepNameSource, prompt: 'Summarize the review.'}],
|
|
1012
1009
|
},
|
|
@@ -1027,14 +1024,15 @@ describe('materializeWorkflowModel', () => {
|
|
|
1027
1024
|
});
|
|
1028
1025
|
|
|
1029
1026
|
it('materializes one-shot siblings while skipping listening steps', async () => {
|
|
1030
|
-
const model =
|
|
1027
|
+
const model = workflowModel({
|
|
1031
1028
|
name: 'Mixed workflow',
|
|
1032
1029
|
runner: 'ubuntu-latest',
|
|
1033
1030
|
jobs: {
|
|
1034
1031
|
review: {
|
|
1035
1032
|
listening: {
|
|
1036
1033
|
on: [{source: 'github', event: 'pull_request_review'}],
|
|
1037
|
-
|
|
1034
|
+
maxExecutions: 3,
|
|
1035
|
+
onResolve: 'finish',
|
|
1038
1036
|
},
|
|
1039
1037
|
steps: [{name: `Review ${template('execution.index')}`, prompt: 'Summarize it.'}],
|
|
1040
1038
|
},
|
|
@@ -1095,12 +1093,12 @@ describe('materializeWorkflowModel', () => {
|
|
|
1095
1093
|
});
|
|
1096
1094
|
|
|
1097
1095
|
it('materializes step if as server-owned condition outside runner config', async () => {
|
|
1098
|
-
const model =
|
|
1096
|
+
const model = workflowModel({
|
|
1099
1097
|
name: 'Conditional workflow',
|
|
1100
1098
|
runner: 'ubuntu-latest',
|
|
1101
1099
|
jobs: {
|
|
1102
1100
|
build: {
|
|
1103
|
-
steps: [{if:
|
|
1101
|
+
steps: [{if: expression('false'), run: 'npm test'}],
|
|
1104
1102
|
},
|
|
1105
1103
|
},
|
|
1106
1104
|
});
|
|
@@ -1112,7 +1110,6 @@ describe('materializeWorkflowModel', () => {
|
|
|
1112
1110
|
language: 'cel',
|
|
1113
1111
|
source: 'false',
|
|
1114
1112
|
check: 'typed',
|
|
1115
|
-
resultType: 'bool',
|
|
1116
1113
|
});
|
|
1117
1114
|
expect(step?.config).toEqual({run: 'npm test'});
|
|
1118
1115
|
});
|
package/src/db/job-listeners.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type {AgentInterModuleClient} from '@shipfox/api-agent-dto/inter-module';
|
|
2
2
|
import {readPersistedWorkflowModel} from '@shipfox/api-definitions-dto';
|
|
3
|
-
import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto';
|
|
4
|
-
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto';
|
|
3
|
+
import type {IntegrationsModuleClient} from '@shipfox/api-integration-core-dto/inter-module';
|
|
4
|
+
import type {ProjectsModuleClient} from '@shipfox/api-projects-dto/inter-module';
|
|
5
5
|
import {
|
|
6
6
|
WORKFLOWS_JOB_ACTIVATED,
|
|
7
7
|
type WorkflowsJobActivatedEventDto,
|