gencow 0.1.241 → 0.1.242
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/core/index.js +259 -140
- package/core/package.json +1 -1
- package/lib/component-readme.mjs +24 -55
- package/lib/deploy-command.mjs +6 -2
- package/lib/deploy-failure-diagnostic.mjs +86 -2
- package/lib/deployment-operation-poll.mjs +17 -33
- package/lib/init-command.mjs +39 -2
- package/lib/jobs-command.mjs +17 -1
- package/lib/readme-codegen.mjs +13 -20
- package/lib/static-deploy-command.mjs +39 -5
- package/package.json +2 -2
- package/runtime/server.mjs +12106 -9328
- package/runtime/tooling.mjs +259 -142
- package/scripts/bundle-core.mjs +3 -2
- package/templateFeature/agent/agent.ts +1 -1
- package/templateFeature/agent/manifest.json +1 -1
- package/templateFeature/ai/ai-facade.ts +16 -10
- package/templateFeature/ai/ai-provider.ts +16 -3
- package/templateFeature/ai/manifest.json +5 -5
- package/templateFeature/guardrails/guardrails.ts +22 -13
- package/templateFeature/guardrails/manifest.json +1 -1
- package/templateFeature/memory/manifest.json +1 -1
- package/templateFeature/memory/memory.ts +2 -1
- package/templateFeature/rag/manifest.json +1 -1
- package/templateFeature/rag/rag.ts +1 -1
- package/templateFeature/reranker/manifest.json +4 -9
- package/templateFeature/reranker/reranker.ts +3 -6
- package/templateFeature/search/manifest.json +1 -0
- package/templateFeature/search/schema-search.ts +1 -1
- package/templateFeature/search/search-index-contract.ts +20 -0
- package/templateFeature/tools/manifest.json +1 -1
- package/templates/SECURITY.md +18 -19
- package/templates/ai-chat/README.md +18 -28
- package/templates/ai-chat/chat.ts +11 -7
- package/templates/ai-chat/prompt.md +3 -3
- package/templates/fullstack/README.md +8 -33
- package/templates/fullstack/prompt.md +1 -1
package/core/index.js
CHANGED
|
@@ -2925,15 +2925,8 @@ function deriveWorkflowStatus(status, currentStep) {
|
|
|
2925
2925
|
|
|
2926
2926
|
// ../core/src/workflows-api.ts
|
|
2927
2927
|
import { sql as sql4 } from "drizzle-orm";
|
|
2928
|
-
|
|
2929
|
-
|
|
2930
|
-
function rowsFromResult(result) {
|
|
2931
|
-
if (Array.isArray(result)) return result;
|
|
2932
|
-
if (result && typeof result === "object" && Array.isArray(result.rows)) {
|
|
2933
|
-
return result.rows;
|
|
2934
|
-
}
|
|
2935
|
-
return [];
|
|
2936
|
-
}
|
|
2928
|
+
|
|
2929
|
+
// ../core/src/workflow-public-read-model.ts
|
|
2937
2930
|
function parseJsonField(value) {
|
|
2938
2931
|
if (typeof value !== "string") return value;
|
|
2939
2932
|
try {
|
|
@@ -2950,6 +2943,57 @@ function toIsoString(value) {
|
|
|
2950
2943
|
function toOptionalIsoString(value) {
|
|
2951
2944
|
return value ? toIsoString(value) : null;
|
|
2952
2945
|
}
|
|
2946
|
+
function publicV2Status(status) {
|
|
2947
|
+
if (status === "completed" || status === "failed") return status;
|
|
2948
|
+
if (status === "cancelled") return "failed";
|
|
2949
|
+
if (status === "running") return "running";
|
|
2950
|
+
return "pending";
|
|
2951
|
+
}
|
|
2952
|
+
function publicV2DerivedStatus(status) {
|
|
2953
|
+
if (status === "queued" || status === "waiting" || status === "sleeping") return status;
|
|
2954
|
+
return publicV2Status(status);
|
|
2955
|
+
}
|
|
2956
|
+
function mapV2WorkflowSummary(row) {
|
|
2957
|
+
const error = parseJsonField(row.error_json);
|
|
2958
|
+
return {
|
|
2959
|
+
id: row.id,
|
|
2960
|
+
name: row.workflow_name,
|
|
2961
|
+
status: publicV2Status(row.status),
|
|
2962
|
+
derivedStatus: publicV2DerivedStatus(row.status),
|
|
2963
|
+
currentStep: row.current_step_key,
|
|
2964
|
+
error: typeof error === "string" ? error : error && typeof error === "object" && "message" in error ? String(error.message) : null,
|
|
2965
|
+
errorCode: row.status === "cancelled" ? "cancelled" : row.error_code,
|
|
2966
|
+
retryCount: row.retry_count,
|
|
2967
|
+
maxRetries: row.max_retries,
|
|
2968
|
+
maxDurationMs: Number(row.max_active_duration_ms),
|
|
2969
|
+
startedAt: toIsoString(row.created_at),
|
|
2970
|
+
updatedAt: toIsoString(row.updated_at),
|
|
2971
|
+
completedAt: toOptionalIsoString(row.completed_at)
|
|
2972
|
+
};
|
|
2973
|
+
}
|
|
2974
|
+
function mapV2WorkflowStep(row) {
|
|
2975
|
+
const error = parseJsonField(row.error_json);
|
|
2976
|
+
return {
|
|
2977
|
+
name: row.step_key,
|
|
2978
|
+
status: row.status === "running" || row.status === "failed" || row.status === "completed" ? row.status : row.status === "skipped" ? "completed" : "pending",
|
|
2979
|
+
output: parseJsonField(row.output_json),
|
|
2980
|
+
error: typeof error === "string" ? error : error && typeof error === "object" && "message" in error ? String(error.message) : null,
|
|
2981
|
+
startedAt: toOptionalIsoString(row.started_at),
|
|
2982
|
+
updatedAt: toIsoString(row.updated_at),
|
|
2983
|
+
completedAt: toOptionalIsoString(row.completed_at)
|
|
2984
|
+
};
|
|
2985
|
+
}
|
|
2986
|
+
|
|
2987
|
+
// ../core/src/workflows-api.ts
|
|
2988
|
+
var WORKFLOW_STATUSES = /* @__PURE__ */ new Set(["pending", "running", "completed", "failed"]);
|
|
2989
|
+
var WORKFLOW_DERIVED_PENDING_STATUSES = /* @__PURE__ */ new Set(["queued", "waiting", "sleeping"]);
|
|
2990
|
+
function rowsFromResult(result) {
|
|
2991
|
+
if (Array.isArray(result)) return result;
|
|
2992
|
+
if (result && typeof result === "object" && Array.isArray(result.rows)) {
|
|
2993
|
+
return result.rows;
|
|
2994
|
+
}
|
|
2995
|
+
return [];
|
|
2996
|
+
}
|
|
2953
2997
|
function mapWorkflowSummary(row) {
|
|
2954
2998
|
return {
|
|
2955
2999
|
id: row.id,
|
|
@@ -2967,6 +3011,26 @@ function mapWorkflowSummary(row) {
|
|
|
2967
3011
|
completedAt: toOptionalIsoString(row.completed_at)
|
|
2968
3012
|
};
|
|
2969
3013
|
}
|
|
3014
|
+
function mapJoinedWorkflowSummary(row) {
|
|
3015
|
+
if (!row.v2_id) return mapWorkflowSummary(row);
|
|
3016
|
+
return mapV2WorkflowSummary({
|
|
3017
|
+
id: row.v2_id,
|
|
3018
|
+
workflow_name: row.v2_name,
|
|
3019
|
+
status: row.v2_status,
|
|
3020
|
+
args_json: null,
|
|
3021
|
+
result_json: null,
|
|
3022
|
+
error_json: row.v2_error_json,
|
|
3023
|
+
error_code: row.v2_error_code,
|
|
3024
|
+
current_step_key: row.v2_current_step,
|
|
3025
|
+
retry_count: row.v2_retry_count,
|
|
3026
|
+
max_retries: row.v2_max_retries,
|
|
3027
|
+
max_active_duration_ms: row.v2_max_duration_ms,
|
|
3028
|
+
created_at: row.v2_created_at,
|
|
3029
|
+
updated_at: row.v2_updated_at,
|
|
3030
|
+
completed_at: row.v2_completed_at,
|
|
3031
|
+
user_id: null
|
|
3032
|
+
});
|
|
3033
|
+
}
|
|
2970
3034
|
function mapWorkflowStep(row) {
|
|
2971
3035
|
return {
|
|
2972
3036
|
name: row.step_name,
|
|
@@ -2999,13 +3063,6 @@ function normalizeDerivedStatus(status) {
|
|
|
2999
3063
|
}
|
|
3000
3064
|
return normalizeStatus(status);
|
|
3001
3065
|
}
|
|
3002
|
-
function toWorkflowStatusFilter(status) {
|
|
3003
|
-
if (status == null) return void 0;
|
|
3004
|
-
if (WORKFLOW_DERIVED_PENDING_STATUSES.has(status)) {
|
|
3005
|
-
return "pending";
|
|
3006
|
-
}
|
|
3007
|
-
return status;
|
|
3008
|
-
}
|
|
3009
3066
|
async function ensureWorkflowRealtimeToken(db, workflowId, currentToken) {
|
|
3010
3067
|
if (currentToken && currentToken.trim() !== "") return currentToken;
|
|
3011
3068
|
const nextToken = createWorkflowRealtimeToken();
|
|
@@ -3028,7 +3085,8 @@ async function ensureWorkflowRealtimeToken(db, workflowId, currentToken) {
|
|
|
3028
3085
|
return rereadToken && rereadToken.trim() !== "" ? rereadToken : null;
|
|
3029
3086
|
}
|
|
3030
3087
|
async function loadWorkflowSignalTarget(db, workflowId) {
|
|
3031
|
-
|
|
3088
|
+
try {
|
|
3089
|
+
const result = await db.execute(sql4`
|
|
3032
3090
|
SELECT
|
|
3033
3091
|
id,
|
|
3034
3092
|
name,
|
|
@@ -3038,8 +3096,12 @@ async function loadWorkflowSignalTarget(db, workflowId) {
|
|
|
3038
3096
|
FROM _gencow_workflows
|
|
3039
3097
|
WHERE id = ${workflowId}
|
|
3040
3098
|
LIMIT 1
|
|
3041
|
-
|
|
3042
|
-
|
|
3099
|
+
`);
|
|
3100
|
+
return rowsFromResult(result)[0] ?? null;
|
|
3101
|
+
} catch (error) {
|
|
3102
|
+
if (isMissingWorkflowV1Table(error)) return null;
|
|
3103
|
+
throw error;
|
|
3104
|
+
}
|
|
3043
3105
|
}
|
|
3044
3106
|
function isMissingWorkflowV2SignalSchemaError(error) {
|
|
3045
3107
|
const code = error && typeof error === "object" && "code" in error ? String(error.code) : "";
|
|
@@ -3055,39 +3117,55 @@ function isMissingWorkflowV2PayloadSchemaError(error) {
|
|
|
3055
3117
|
const message = error instanceof Error ? error.message : String(error);
|
|
3056
3118
|
return code === "42P01" || causeCode === "42P01" || message.includes('relation "_gencow_workflow_runs_v2" does not exist') || message.includes("relation _gencow_workflow_runs_v2 does not exist") || message.includes('relation "_gencow_workflow_steps_v2" does not exist') || message.includes("relation _gencow_workflow_steps_v2 does not exist");
|
|
3057
3119
|
}
|
|
3058
|
-
|
|
3120
|
+
function isMissingWorkflowV1Table(error) {
|
|
3121
|
+
const cause = error && typeof error === "object" && "cause" in error ? error.cause : null;
|
|
3122
|
+
const message = `${error instanceof Error ? error.message : String(error)} ${cause instanceof Error ? cause.message : String(cause ?? "")}`;
|
|
3123
|
+
return /relation "?_gencow_workflows"? does not exist|relation "?_gencow_workflow_steps"? does not exist|relation "?_gencow_workflow_events"? does not exist/.test(
|
|
3124
|
+
message
|
|
3125
|
+
);
|
|
3126
|
+
}
|
|
3127
|
+
async function loadWorkflowV2Run(db, workflowId) {
|
|
3059
3128
|
try {
|
|
3060
|
-
const
|
|
3061
|
-
SELECT args_json, result_json
|
|
3129
|
+
const result = await db.execute(sql4`
|
|
3130
|
+
SELECT id, workflow_name, status, args_json, result_json, error_json, error_code,
|
|
3131
|
+
current_step_key, retry_count, max_retries, max_active_duration_ms,
|
|
3132
|
+
created_at, updated_at, completed_at, user_id
|
|
3062
3133
|
FROM _gencow_workflow_runs_v2
|
|
3063
3134
|
WHERE id = ${workflowId}
|
|
3064
3135
|
LIMIT 1
|
|
3065
3136
|
`);
|
|
3066
|
-
|
|
3067
|
-
|
|
3137
|
+
return rowsFromResult(result)[0] ?? null;
|
|
3138
|
+
} catch (error) {
|
|
3139
|
+
if (isMissingWorkflowV2PayloadSchemaError(error)) return null;
|
|
3140
|
+
throw error;
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
async function loadWorkflowV2Steps(db, workflowId) {
|
|
3144
|
+
try {
|
|
3068
3145
|
const stepResult = await db.execute(sql4`
|
|
3069
|
-
SELECT step_key, output_json
|
|
3146
|
+
SELECT step_key, status, output_json, error_json, started_at, updated_at, completed_at
|
|
3070
3147
|
FROM _gencow_workflow_steps_v2
|
|
3071
3148
|
WHERE run_id = ${workflowId}
|
|
3149
|
+
ORDER BY step_index ASC, COALESCE(started_at, updated_at) ASC, step_key ASC
|
|
3072
3150
|
`);
|
|
3073
|
-
return
|
|
3074
|
-
run,
|
|
3075
|
-
steps: new Map(
|
|
3076
|
-
rowsFromResult(stepResult).map((step) => [
|
|
3077
|
-
step.step_key,
|
|
3078
|
-
parseJsonField(step.output_json)
|
|
3079
|
-
])
|
|
3080
|
-
)
|
|
3081
|
-
};
|
|
3151
|
+
return rowsFromResult(stepResult);
|
|
3082
3152
|
} catch (error) {
|
|
3083
|
-
if (isMissingWorkflowV2PayloadSchemaError(error)) return
|
|
3153
|
+
if (isMissingWorkflowV2PayloadSchemaError(error)) return [];
|
|
3084
3154
|
throw error;
|
|
3085
3155
|
}
|
|
3086
3156
|
}
|
|
3087
3157
|
async function tryRecordWorkflowV2Signal(options) {
|
|
3088
3158
|
try {
|
|
3089
|
-
await options.db.execute(sql4`
|
|
3090
|
-
WITH
|
|
3159
|
+
const result = await options.db.execute(sql4`
|
|
3160
|
+
WITH eligible_run AS (
|
|
3161
|
+
SELECT id
|
|
3162
|
+
FROM _gencow_workflow_runs_v2
|
|
3163
|
+
WHERE id = ${options.workflowId}
|
|
3164
|
+
AND status IN ('queued', 'running', 'sleeping', 'waiting')
|
|
3165
|
+
AND completed_at IS NULL
|
|
3166
|
+
AND cancel_requested_at IS NULL
|
|
3167
|
+
FOR UPDATE
|
|
3168
|
+
), inserted AS (
|
|
3091
3169
|
INSERT INTO _gencow_workflow_signals_v2 (
|
|
3092
3170
|
id,
|
|
3093
3171
|
run_id,
|
|
@@ -3095,30 +3173,15 @@ async function tryRecordWorkflowV2Signal(options) {
|
|
|
3095
3173
|
payload_json,
|
|
3096
3174
|
idempotency_key
|
|
3097
3175
|
)
|
|
3098
|
-
|
|
3099
|
-
${
|
|
3100
|
-
|
|
3176
|
+
SELECT
|
|
3177
|
+
${options.signalId},
|
|
3178
|
+
eligible_run.id,
|
|
3101
3179
|
${options.event},
|
|
3102
3180
|
${workflowJsonb(options.payload)},
|
|
3103
|
-
${
|
|
3104
|
-
|
|
3181
|
+
${options.signalId}
|
|
3182
|
+
FROM eligible_run
|
|
3105
3183
|
RETURNING id, run_id
|
|
3106
3184
|
),
|
|
3107
|
-
inserted_legacy AS (
|
|
3108
|
-
INSERT INTO _gencow_workflow_events (
|
|
3109
|
-
id,
|
|
3110
|
-
workflow_id,
|
|
3111
|
-
event_name,
|
|
3112
|
-
payload
|
|
3113
|
-
)
|
|
3114
|
-
VALUES (
|
|
3115
|
-
${options.eventId},
|
|
3116
|
-
${options.workflowId},
|
|
3117
|
-
${options.event},
|
|
3118
|
-
NULL
|
|
3119
|
-
)
|
|
3120
|
-
RETURNING id
|
|
3121
|
-
),
|
|
3122
3185
|
queued_run AS (
|
|
3123
3186
|
UPDATE _gencow_workflow_runs_v2 run
|
|
3124
3187
|
SET
|
|
@@ -3160,17 +3223,34 @@ async function tryRecordWorkflowV2Signal(options) {
|
|
|
3160
3223
|
)
|
|
3161
3224
|
SELECT 1
|
|
3162
3225
|
FROM inserted
|
|
3163
|
-
LEFT JOIN inserted_legacy ON TRUE
|
|
3164
3226
|
LEFT JOIN wake_notified ON TRUE
|
|
3165
3227
|
`);
|
|
3166
|
-
return
|
|
3228
|
+
return rowsFromResult(result).length > 0 ? "accepted" : "rejected";
|
|
3167
3229
|
} catch (error) {
|
|
3168
|
-
if (isMissingWorkflowV2SignalSchemaError(error)) return
|
|
3230
|
+
if (isMissingWorkflowV2SignalSchemaError(error)) return "absent";
|
|
3169
3231
|
throw error;
|
|
3170
3232
|
}
|
|
3171
3233
|
}
|
|
3234
|
+
async function mirrorWorkflowV2SignalToV1(options) {
|
|
3235
|
+
try {
|
|
3236
|
+
await options.db.execute(sql4`
|
|
3237
|
+
INSERT INTO _gencow_workflow_events (id, workflow_id, event_name, payload)
|
|
3238
|
+
SELECT ${options.signalId}, workflow.id, ${options.event}, NULL
|
|
3239
|
+
FROM _gencow_workflows workflow
|
|
3240
|
+
WHERE workflow.id = ${options.workflowId}
|
|
3241
|
+
ON CONFLICT (id) DO NOTHING
|
|
3242
|
+
`);
|
|
3243
|
+
} catch (error) {
|
|
3244
|
+
if (!isMissingWorkflowV1Table(error)) {
|
|
3245
|
+
console.warn("Workflow V1 signal mirror failed", { signalId: options.signalId });
|
|
3246
|
+
}
|
|
3247
|
+
}
|
|
3248
|
+
}
|
|
3172
3249
|
async function loadWorkflowSnapshot(db, workflowId, options) {
|
|
3173
|
-
const
|
|
3250
|
+
const v2Run = await loadWorkflowV2Run(db, workflowId);
|
|
3251
|
+
let workflowResult;
|
|
3252
|
+
try {
|
|
3253
|
+
workflowResult = await db.execute(sql4`
|
|
3174
3254
|
SELECT
|
|
3175
3255
|
id,
|
|
3176
3256
|
name,
|
|
@@ -3192,16 +3272,25 @@ async function loadWorkflowSnapshot(db, workflowId, options) {
|
|
|
3192
3272
|
WHERE id = ${workflowId}
|
|
3193
3273
|
LIMIT 1
|
|
3194
3274
|
`);
|
|
3275
|
+
} catch (error) {
|
|
3276
|
+
if (!v2Run || !isMissingWorkflowV1Table(error)) throw error;
|
|
3277
|
+
workflowResult = [];
|
|
3278
|
+
}
|
|
3195
3279
|
const row = rowsFromResult(workflowResult)[0] ?? null;
|
|
3196
|
-
if (!row) return null;
|
|
3280
|
+
if (!v2Run && !row) return null;
|
|
3197
3281
|
const viewerUserId = options?.viewerUserId ?? null;
|
|
3198
|
-
|
|
3282
|
+
const ownerId = v2Run ? v2Run.user_id : row?.user_id;
|
|
3283
|
+
if (options?.requireViewerMatch && ownerId && ownerId !== viewerUserId) {
|
|
3199
3284
|
return null;
|
|
3200
3285
|
}
|
|
3201
|
-
const realtimeToken = await ensureWorkflowRealtimeToken(db, workflowId, row.realtime_token);
|
|
3286
|
+
const realtimeToken = row ? await ensureWorkflowRealtimeToken(db, workflowId, row.realtime_token) : v2Run?.id ?? null;
|
|
3202
3287
|
if (!realtimeToken) return null;
|
|
3203
|
-
const
|
|
3204
|
-
|
|
3288
|
+
const v2Steps = v2Run ? await loadWorkflowV2Steps(db, workflowId) : [];
|
|
3289
|
+
let steps = v2Steps.map(mapV2WorkflowStep);
|
|
3290
|
+
if (steps.length === 0) {
|
|
3291
|
+
let stepsResult;
|
|
3292
|
+
try {
|
|
3293
|
+
stepsResult = await db.execute(sql4`
|
|
3205
3294
|
SELECT
|
|
3206
3295
|
step_name,
|
|
3207
3296
|
status,
|
|
@@ -3213,17 +3302,19 @@ async function loadWorkflowSnapshot(db, workflowId, options) {
|
|
|
3213
3302
|
FROM _gencow_workflow_steps
|
|
3214
3303
|
WHERE workflow_id = ${workflowId}
|
|
3215
3304
|
ORDER BY COALESCE(started_at, updated_at) ASC, step_name ASC
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3305
|
+
`);
|
|
3306
|
+
} catch (error) {
|
|
3307
|
+
if (!v2Run || !isMissingWorkflowV1Table(error)) throw error;
|
|
3308
|
+
stepsResult = [];
|
|
3309
|
+
}
|
|
3310
|
+
steps = rowsFromResult(stepsResult).map(mapWorkflowStep);
|
|
3311
|
+
}
|
|
3221
3312
|
return {
|
|
3222
|
-
...mapWorkflowSummary(row),
|
|
3223
|
-
args:
|
|
3224
|
-
result:
|
|
3313
|
+
...v2Run ? mapV2WorkflowSummary(v2Run) : mapWorkflowSummary(row),
|
|
3314
|
+
args: deserializeWorkflowValue(parseJsonField(v2Run ? v2Run.args_json : row.args)),
|
|
3315
|
+
result: v2Run ? parseJsonField(v2Run.result_json) : deserializeWorkflowValue(parseJsonField(row.result)),
|
|
3225
3316
|
steps,
|
|
3226
|
-
realtimeKey: getWorkflowRealtimeKey(
|
|
3317
|
+
realtimeKey: getWorkflowRealtimeKey(workflowId, realtimeToken)
|
|
3227
3318
|
};
|
|
3228
3319
|
}
|
|
3229
3320
|
function buildWorkflowBuiltinDefinitions() {
|
|
@@ -3251,9 +3342,11 @@ function buildWorkflowBuiltinDefinitions() {
|
|
|
3251
3342
|
if (!normalizedEvent) {
|
|
3252
3343
|
throw new GencowValidationError(`Argument "event": expected a non-empty string`);
|
|
3253
3344
|
}
|
|
3345
|
+
const v2Run = await loadWorkflowV2Run(ctx.unsafeDb, args.id);
|
|
3254
3346
|
const workflow2 = await loadWorkflowSignalTarget(ctx.unsafeDb, args.id);
|
|
3255
3347
|
const viewerUserId = ctx.auth.getUserIdentity()?.id ?? null;
|
|
3256
|
-
|
|
3348
|
+
const ownerId = v2Run ? v2Run.user_id : workflow2?.user_id;
|
|
3349
|
+
if (!v2Run && !workflow2 || ownerId && ownerId !== viewerUserId) {
|
|
3257
3350
|
return {
|
|
3258
3351
|
ok: false,
|
|
3259
3352
|
workflowId: args.id,
|
|
@@ -3261,29 +3354,43 @@ function buildWorkflowBuiltinDefinitions() {
|
|
|
3261
3354
|
scheduledJobId: null
|
|
3262
3355
|
};
|
|
3263
3356
|
}
|
|
3264
|
-
|
|
3357
|
+
const workflowId = v2Run?.id ?? workflow2.id;
|
|
3358
|
+
const workflowName = v2Run?.workflow_name ?? workflow2.name;
|
|
3359
|
+
if (v2Run ? v2Run.status === "completed" || v2Run.status === "failed" || v2Run.status === "cancelled" || v2Run.completed_at !== null : workflow2.status === "completed" || workflow2.status === "failed") {
|
|
3265
3360
|
return {
|
|
3266
3361
|
ok: false,
|
|
3267
|
-
workflowId
|
|
3362
|
+
workflowId,
|
|
3268
3363
|
event: normalizedEvent,
|
|
3269
3364
|
scheduledJobId: null
|
|
3270
3365
|
};
|
|
3271
3366
|
}
|
|
3272
3367
|
const persistedPayload = serializeWorkflowValue(args.payload);
|
|
3273
3368
|
assertWorkflowPayloadWithinLimit({
|
|
3274
|
-
workflowName
|
|
3369
|
+
workflowName,
|
|
3275
3370
|
kind: "signal",
|
|
3276
3371
|
serializedValue: persistedPayload
|
|
3277
3372
|
});
|
|
3278
|
-
const
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3373
|
+
const signalId = crypto.randomUUID();
|
|
3374
|
+
if (v2Run) {
|
|
3375
|
+
const recordedV2 = await tryRecordWorkflowV2Signal({
|
|
3376
|
+
db: ctx.unsafeDb,
|
|
3377
|
+
workflowId,
|
|
3378
|
+
event: normalizedEvent,
|
|
3379
|
+
signalId,
|
|
3380
|
+
payload: persistedPayload
|
|
3381
|
+
});
|
|
3382
|
+
if (recordedV2 !== "accepted") {
|
|
3383
|
+
return { ok: false, workflowId, event: normalizedEvent, scheduledJobId: null };
|
|
3384
|
+
}
|
|
3385
|
+
if (workflow2) {
|
|
3386
|
+
await mirrorWorkflowV2SignalToV1({
|
|
3387
|
+
db: ctx.unsafeDb,
|
|
3388
|
+
workflowId,
|
|
3389
|
+
event: normalizedEvent,
|
|
3390
|
+
signalId
|
|
3391
|
+
});
|
|
3392
|
+
}
|
|
3393
|
+
} else {
|
|
3287
3394
|
await ctx.unsafeDb.execute(sql4`
|
|
3288
3395
|
INSERT INTO _gencow_workflow_events (
|
|
3289
3396
|
id,
|
|
@@ -3292,18 +3399,18 @@ function buildWorkflowBuiltinDefinitions() {
|
|
|
3292
3399
|
payload
|
|
3293
3400
|
)
|
|
3294
3401
|
VALUES (
|
|
3295
|
-
${
|
|
3296
|
-
${
|
|
3402
|
+
${signalId},
|
|
3403
|
+
${workflowId},
|
|
3297
3404
|
${normalizedEvent},
|
|
3298
3405
|
${workflowJsonb(persistedPayload)}
|
|
3299
3406
|
)
|
|
3300
3407
|
`);
|
|
3301
3408
|
}
|
|
3302
3409
|
let scheduledJobId = null;
|
|
3303
|
-
if (workflow2
|
|
3410
|
+
if (workflow2?.status === "pending" && workflow2.current_step?.startsWith("wait:")) {
|
|
3304
3411
|
try {
|
|
3305
|
-
scheduledJobId = ctx.scheduler.runAfter(0, getWorkflowResumeActionName(
|
|
3306
|
-
workflowId
|
|
3412
|
+
scheduledJobId = ctx.scheduler.runAfter(0, getWorkflowResumeActionName(workflowName), {
|
|
3413
|
+
workflowId
|
|
3307
3414
|
});
|
|
3308
3415
|
} catch {
|
|
3309
3416
|
scheduledJobId = null;
|
|
@@ -3311,7 +3418,7 @@ function buildWorkflowBuiltinDefinitions() {
|
|
|
3311
3418
|
}
|
|
3312
3419
|
return {
|
|
3313
3420
|
ok: true,
|
|
3314
|
-
workflowId
|
|
3421
|
+
workflowId,
|
|
3315
3422
|
event: normalizedEvent,
|
|
3316
3423
|
scheduledJobId
|
|
3317
3424
|
};
|
|
@@ -3328,52 +3435,64 @@ function buildWorkflowBuiltinDefinitions() {
|
|
|
3328
3435
|
const userId = ctx.auth.requireAuth().id;
|
|
3329
3436
|
const limit = normalizeListLimit(args.limit);
|
|
3330
3437
|
const requestedStatus = normalizeDerivedStatus(args.status);
|
|
3331
|
-
const status =
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
|
|
3342
|
-
|
|
3343
|
-
|
|
3344
|
-
|
|
3345
|
-
|
|
3346
|
-
|
|
3347
|
-
|
|
3348
|
-
|
|
3349
|
-
|
|
3350
|
-
|
|
3351
|
-
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
|
|
3358
|
-
|
|
3359
|
-
|
|
3360
|
-
|
|
3361
|
-
|
|
3362
|
-
|
|
3363
|
-
|
|
3364
|
-
|
|
3365
|
-
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
|
|
3371
|
-
|
|
3372
|
-
|
|
3373
|
-
|
|
3374
|
-
|
|
3375
|
-
|
|
3376
|
-
|
|
3438
|
+
const v1Derived = sql4`CASE WHEN w.status = 'pending' AND w.current_step LIKE 'sleep#%' THEN 'sleeping'
|
|
3439
|
+
WHEN w.status = 'pending' AND w.current_step LIKE 'wait:%' THEN 'waiting'
|
|
3440
|
+
WHEN w.status = 'pending' THEN 'queued' ELSE w.status END`;
|
|
3441
|
+
const effectiveStatus = sql4`CASE WHEN v.id IS NOT NULL THEN
|
|
3442
|
+
CASE WHEN v.status = 'cancelled' THEN 'failed'
|
|
3443
|
+
WHEN v.status IN ('queued', 'waiting', 'sleeping') THEN 'pending'
|
|
3444
|
+
ELSE v.status END
|
|
3445
|
+
ELSE w.status END`;
|
|
3446
|
+
const effectiveDerived = sql4`CASE WHEN v.id IS NOT NULL THEN
|
|
3447
|
+
CASE WHEN v.status = 'cancelled' THEN 'failed'
|
|
3448
|
+
WHEN v.status IN ('queued', 'waiting', 'sleeping') THEN v.status
|
|
3449
|
+
ELSE v.status END
|
|
3450
|
+
ELSE ${v1Derived} END`;
|
|
3451
|
+
const statusForFilter = requestedStatus === "pending" ? effectiveStatus : effectiveDerived;
|
|
3452
|
+
const v1StatusForFilter = requestedStatus === "pending" ? sql4`w.status` : v1Derived;
|
|
3453
|
+
try {
|
|
3454
|
+
const result = await ctx.unsafeDb.execute(sql4`
|
|
3455
|
+
SELECT w.*, v.id AS v2_id, v.workflow_name AS v2_name,
|
|
3456
|
+
v.status AS v2_status, v.error_json AS v2_error_json,
|
|
3457
|
+
v.error_code AS v2_error_code, v.current_step_key AS v2_current_step,
|
|
3458
|
+
v.retry_count AS v2_retry_count, v.max_retries AS v2_max_retries,
|
|
3459
|
+
v.max_active_duration_ms AS v2_max_duration_ms,
|
|
3460
|
+
v.created_at AS v2_created_at, v.updated_at AS v2_updated_at,
|
|
3461
|
+
v.completed_at AS v2_completed_at
|
|
3462
|
+
FROM _gencow_workflows w
|
|
3463
|
+
FULL OUTER JOIN _gencow_workflow_runs_v2 v ON v.id = w.id
|
|
3464
|
+
WHERE (CASE WHEN v.id IS NOT NULL THEN v.user_id ELSE w.user_id END) = ${userId}
|
|
3465
|
+
AND (${requestedStatus ?? null}::text IS NULL OR ${statusForFilter} = ${requestedStatus ?? null})
|
|
3466
|
+
ORDER BY COALESCE(v.created_at, w.started_at) DESC, COALESCE(v.id, w.id) DESC
|
|
3467
|
+
LIMIT ${limit}
|
|
3468
|
+
`);
|
|
3469
|
+
return rowsFromResult(result).map(mapJoinedWorkflowSummary);
|
|
3470
|
+
} catch (error) {
|
|
3471
|
+
if (isMissingWorkflowV1Table(error)) {
|
|
3472
|
+
const v2StatusForFilter = requestedStatus === "pending" ? sql4`CASE WHEN status IN ('queued', 'waiting', 'sleeping') THEN 'pending'
|
|
3473
|
+
WHEN status = 'cancelled' THEN 'failed' ELSE status END` : sql4`CASE WHEN status = 'cancelled' THEN 'failed' ELSE status END`;
|
|
3474
|
+
const result2 = await ctx.unsafeDb.execute(sql4`
|
|
3475
|
+
SELECT id, workflow_name, status, args_json, result_json, error_json, error_code,
|
|
3476
|
+
current_step_key, retry_count, max_retries, max_active_duration_ms,
|
|
3477
|
+
created_at, updated_at, completed_at, user_id
|
|
3478
|
+
FROM _gencow_workflow_runs_v2
|
|
3479
|
+
WHERE user_id = ${userId}
|
|
3480
|
+
AND (${requestedStatus ?? null}::text IS NULL OR ${v2StatusForFilter} = ${requestedStatus ?? null})
|
|
3481
|
+
ORDER BY created_at DESC, id DESC
|
|
3482
|
+
LIMIT ${limit}
|
|
3483
|
+
`);
|
|
3484
|
+
return rowsFromResult(result2).map(mapV2WorkflowSummary);
|
|
3485
|
+
}
|
|
3486
|
+
if (!isMissingWorkflowV2PayloadSchemaError(error)) throw error;
|
|
3487
|
+
const result = await ctx.unsafeDb.execute(sql4`
|
|
3488
|
+
SELECT w.* FROM _gencow_workflows w
|
|
3489
|
+
WHERE w.user_id = ${userId}
|
|
3490
|
+
AND (${requestedStatus ?? null}::text IS NULL OR ${v1StatusForFilter} = ${requestedStatus ?? null})
|
|
3491
|
+
ORDER BY w.started_at DESC, w.id DESC
|
|
3492
|
+
LIMIT ${limit}
|
|
3493
|
+
`);
|
|
3494
|
+
return rowsFromResult(result).map(mapWorkflowSummary);
|
|
3495
|
+
}
|
|
3377
3496
|
}
|
|
3378
3497
|
};
|
|
3379
3498
|
const definitions = Object.freeze({
|