lua-cli 3.33.0 → 3.35.0
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/dist/api-exports.d.ts +29 -4
- package/dist/api-exports.js +331 -16
- package/dist/api-exports.js.map +1 -1
- package/dist/index.js +360 -27
- package/dist/index.js.map +1 -1
- package/dist/workflow-builder.js +324 -12
- package/dist/workflow-builder.js.map +1 -1
- package/docs/README.md +2 -2
- package/docs/api/LuaWorkflow.md +1 -1
- package/docs/workflows/migrating-runs.md +1 -1
- package/docs/workflows/recovery.md +4 -4
- package/docs/workflows/testing-offline.md +1 -1
- package/package.json +3 -3
- package/template/package.json +1 -1
package/dist/workflow-builder.js
CHANGED
|
@@ -100,6 +100,14 @@ function isAllowedReviewableExecuteTool(tool) {
|
|
|
100
100
|
}
|
|
101
101
|
__name(isAllowedReviewableExecuteTool, "isAllowedReviewableExecuteTool");
|
|
102
102
|
__name2(isAllowedReviewableExecuteTool, "isAllowedReviewableExecuteTool");
|
|
103
|
+
var REVIEWABLE_STANDING_START_TOOLS = [
|
|
104
|
+
"scheduleWorkflow"
|
|
105
|
+
];
|
|
106
|
+
function isReviewableStandingStartTool(tool) {
|
|
107
|
+
return REVIEWABLE_STANDING_START_TOOLS.includes(tool);
|
|
108
|
+
}
|
|
109
|
+
__name(isReviewableStandingStartTool, "isReviewableStandingStartTool");
|
|
110
|
+
__name2(isReviewableStandingStartTool, "isReviewableStandingStartTool");
|
|
103
111
|
var REVIEWABLE_MCP_SEND_TOOL_SUFFIX = "_create_messaging_message";
|
|
104
112
|
function isReviewableMcpSendTool(tool) {
|
|
105
113
|
return tool.length > REVIEWABLE_MCP_SEND_TOOL_SUFFIX.length && tool.endsWith(REVIEWABLE_MCP_SEND_TOOL_SUFFIX);
|
|
@@ -152,7 +160,7 @@ function mcpSendSiblingForDraftTool(tool, availableToolIds) {
|
|
|
152
160
|
__name(mcpSendSiblingForDraftTool, "mcpSendSiblingForDraftTool");
|
|
153
161
|
__name2(mcpSendSiblingForDraftTool, "mcpSendSiblingForDraftTool");
|
|
154
162
|
function isReviewableExecuteTool(tool) {
|
|
155
|
-
return isAllowedReviewableExecuteTool(tool) || isReviewableMcpSendTool(tool);
|
|
163
|
+
return isAllowedReviewableExecuteTool(tool) || isReviewableMcpSendTool(tool) || isReviewableStandingStartTool(tool);
|
|
156
164
|
}
|
|
157
165
|
__name(isReviewableExecuteTool, "isReviewableExecuteTool");
|
|
158
166
|
__name2(isReviewableExecuteTool, "isReviewableExecuteTool");
|
|
@@ -312,7 +320,10 @@ function transformChatHistoryContentParts(parts) {
|
|
|
312
320
|
content.push({
|
|
313
321
|
type: "file",
|
|
314
322
|
data: part.data,
|
|
315
|
-
mediaType
|
|
323
|
+
mediaType,
|
|
324
|
+
...part.filename && {
|
|
325
|
+
filename: part.filename
|
|
326
|
+
}
|
|
316
327
|
});
|
|
317
328
|
}
|
|
318
329
|
}
|
|
@@ -1206,6 +1217,70 @@ function normalizeLuaJobExecutionTimeoutSeconds(timeout) {
|
|
|
1206
1217
|
}
|
|
1207
1218
|
__name(normalizeLuaJobExecutionTimeoutSeconds, "normalizeLuaJobExecutionTimeoutSeconds");
|
|
1208
1219
|
__name2(normalizeLuaJobExecutionTimeoutSeconds, "normalizeLuaJobExecutionTimeoutSeconds");
|
|
1220
|
+
var KEYS = /* @__PURE__ */ new Set([
|
|
1221
|
+
"v",
|
|
1222
|
+
"kind",
|
|
1223
|
+
"agentId",
|
|
1224
|
+
"jobId",
|
|
1225
|
+
"scheduledTime",
|
|
1226
|
+
"triggerId"
|
|
1227
|
+
]);
|
|
1228
|
+
var MAX_BODY_BYTES = 4096;
|
|
1229
|
+
var MAX_ID_LENGTH = 512;
|
|
1230
|
+
var UTC_ISO = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
|
|
1231
|
+
function isId(value3) {
|
|
1232
|
+
return typeof value3 === "string" && value3.length > 0 && value3.length <= MAX_ID_LENGTH;
|
|
1233
|
+
}
|
|
1234
|
+
__name(isId, "isId");
|
|
1235
|
+
__name2(isId, "isId");
|
|
1236
|
+
function parseScheduledJobFire(raw) {
|
|
1237
|
+
if (new TextEncoder().encode(raw).byteLength > MAX_BODY_BYTES) {
|
|
1238
|
+
throw new Error("Invalid scheduled job fire: body is too large");
|
|
1239
|
+
}
|
|
1240
|
+
let value3;
|
|
1241
|
+
try {
|
|
1242
|
+
value3 = JSON.parse(raw);
|
|
1243
|
+
} catch {
|
|
1244
|
+
throw new Error("Invalid scheduled job fire: body is not JSON");
|
|
1245
|
+
}
|
|
1246
|
+
if (!value3 || typeof value3 !== "object" || Array.isArray(value3)) {
|
|
1247
|
+
throw new Error("Invalid scheduled job fire: body must be an object");
|
|
1248
|
+
}
|
|
1249
|
+
const record = value3;
|
|
1250
|
+
if (Object.keys(record).some((key) => !KEYS.has(key))) {
|
|
1251
|
+
throw new Error("Invalid scheduled job fire: unknown field");
|
|
1252
|
+
}
|
|
1253
|
+
if (record.v !== 1 || record.kind !== "scheduled-job-fire") {
|
|
1254
|
+
throw new Error("Invalid scheduled job fire: unsupported contract");
|
|
1255
|
+
}
|
|
1256
|
+
if (!isId(record.agentId)) {
|
|
1257
|
+
throw new Error("Invalid scheduled job fire: agentId is required");
|
|
1258
|
+
}
|
|
1259
|
+
if (!isId(record.jobId)) {
|
|
1260
|
+
throw new Error("Invalid scheduled job fire: jobId is required");
|
|
1261
|
+
}
|
|
1262
|
+
if (typeof record.scheduledTime !== "string" || !UTC_ISO.test(record.scheduledTime) || !Number.isFinite(Date.parse(record.scheduledTime)) || ![
|
|
1263
|
+
new Date(record.scheduledTime).toISOString(),
|
|
1264
|
+
new Date(record.scheduledTime).toISOString().replace(".000Z", "Z")
|
|
1265
|
+
].includes(record.scheduledTime)) {
|
|
1266
|
+
throw new Error("Invalid scheduled job fire: scheduledTime must be ISO 8601");
|
|
1267
|
+
}
|
|
1268
|
+
if (record.triggerId !== void 0 && !isId(record.triggerId)) {
|
|
1269
|
+
throw new Error("Invalid scheduled job fire: triggerId must be a non-empty string");
|
|
1270
|
+
}
|
|
1271
|
+
return {
|
|
1272
|
+
v: 1,
|
|
1273
|
+
kind: "scheduled-job-fire",
|
|
1274
|
+
agentId: record.agentId,
|
|
1275
|
+
jobId: record.jobId,
|
|
1276
|
+
scheduledTime: record.scheduledTime,
|
|
1277
|
+
...typeof record.triggerId === "string" ? {
|
|
1278
|
+
triggerId: record.triggerId
|
|
1279
|
+
} : {}
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
__name(parseScheduledJobFire, "parseScheduledJobFire");
|
|
1283
|
+
__name2(parseScheduledJobFire, "parseScheduledJobFire");
|
|
1209
1284
|
var TEMPLATE_TRIGGER_URL_ENV_PREFIX = "LUA_TRIGGER_URL__";
|
|
1210
1285
|
function triggerUrlEnvKey(triggerKey) {
|
|
1211
1286
|
const upper = triggerKey.trim().replace(/[^A-Za-z0-9]+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
|
|
@@ -1550,7 +1625,20 @@ var ProjectedResourceSchema = z3.object({
|
|
|
1550
1625
|
"platform-allowlist"
|
|
1551
1626
|
]),
|
|
1552
1627
|
/** Product data only. A roster row confers nothing. */
|
|
1553
|
-
rostered: z3.boolean()
|
|
1628
|
+
rostered: z3.boolean(),
|
|
1629
|
+
/**
|
|
1630
|
+
* Ownership, product data only (PRO-1754). `createdBy` is the creating
|
|
1631
|
+
* user's id straight off the sub-agent document; `owner` is that id
|
|
1632
|
+
* resolved to a display identity after the list is decided. Both are
|
|
1633
|
+
* absent when the document records no creator or the lookup fails —
|
|
1634
|
+
* they never influence which resources are listed.
|
|
1635
|
+
*/
|
|
1636
|
+
createdBy: z3.string().min(1).max(256).optional(),
|
|
1637
|
+
owner: z3.object({
|
|
1638
|
+
id: z3.string().min(1).max(256),
|
|
1639
|
+
name: z3.string().optional(),
|
|
1640
|
+
email: z3.string().optional()
|
|
1641
|
+
}).optional()
|
|
1554
1642
|
}).passthrough();
|
|
1555
1643
|
var CapabilityProfilesSchema = z3.record(z3.string().min(1).max(64), z3.array(ProjectedScopeSchema));
|
|
1556
1644
|
var RoleCatalogSchema = z3.record(z3.string().min(1).max(128), z3.object({
|
|
@@ -3078,6 +3166,169 @@ var fromKnowledge = /* @__PURE__ */ __name4((k) => ({
|
|
|
3078
3166
|
}), "fromKnowledge");
|
|
3079
3167
|
var SideEffectsSchema = z4.enum(WORKFLOW_SIDE_EFFECTS);
|
|
3080
3168
|
var JobResourcesSchema = z4.enum(WORKFLOW_JOB_RESOURCES);
|
|
3169
|
+
var WORKFLOW_MODEL_TEMPLATE_ROOT = "initData";
|
|
3170
|
+
var WORKFLOW_MODEL_TEMPLATE_RE = /^\$\{\s*initData\.([A-Za-z0-9_.\-[\]]+)\s*(?:\|([^}]*))?\}$/;
|
|
3171
|
+
var PATH_SEGMENT_RE = /^[A-Za-z0-9_-]+(?:\[(?:0|[1-9]\d*)\])*$/;
|
|
3172
|
+
function looksLikeModelTemplate(model) {
|
|
3173
|
+
return typeof model === "string" && model.includes("${");
|
|
3174
|
+
}
|
|
3175
|
+
__name(looksLikeModelTemplate, "looksLikeModelTemplate");
|
|
3176
|
+
__name4(looksLikeModelTemplate, "looksLikeModelTemplate");
|
|
3177
|
+
function parseModelTemplate(model) {
|
|
3178
|
+
if (!looksLikeModelTemplate(model)) return {
|
|
3179
|
+
kind: "static"
|
|
3180
|
+
};
|
|
3181
|
+
const trimmed = model.trim();
|
|
3182
|
+
const m = WORKFLOW_MODEL_TEMPLATE_RE.exec(trimmed);
|
|
3183
|
+
if (!m) {
|
|
3184
|
+
const inner = /^\$\{([^}]*)\}$/.exec(trimmed)?.[1];
|
|
3185
|
+
const root = inner?.split(/[.|]/, 1)[0];
|
|
3186
|
+
const why = inner !== void 0 && root !== void 0 && root !== WORKFLOW_MODEL_TEMPLATE_ROOT ? `only the \`${WORKFLOW_MODEL_TEMPLATE_ROOT}\` root is allowed (got \`${root || "(empty)"}\`)` : inner !== void 0 && (inner === WORKFLOW_MODEL_TEMPLATE_ROOT || inner.startsWith(`${WORKFLOW_MODEL_TEMPLATE_ROOT}.`)) ? "the path after `initData.` is empty" : "the whole value must be exactly one placeholder \u2014 no prefix, suffix or second placeholder";
|
|
3187
|
+
return {
|
|
3188
|
+
kind: "invalid",
|
|
3189
|
+
message: modelTemplateInvalidMessage(model, why)
|
|
3190
|
+
};
|
|
3191
|
+
}
|
|
3192
|
+
const path = m[1];
|
|
3193
|
+
if (!path.split(".").every((seg) => PATH_SEGMENT_RE.test(seg))) {
|
|
3194
|
+
return {
|
|
3195
|
+
kind: "invalid",
|
|
3196
|
+
message: modelTemplateInvalidMessage(model, `the path \`${path}\` is not a dotted path of names and canonical [n] indexes`)
|
|
3197
|
+
};
|
|
3198
|
+
}
|
|
3199
|
+
if (m[2] !== void 0) {
|
|
3200
|
+
const dflt = m[2].trim();
|
|
3201
|
+
if (!dflt) return {
|
|
3202
|
+
kind: "invalid",
|
|
3203
|
+
message: modelTemplateInvalidMessage(model, "the default after `|` is empty")
|
|
3204
|
+
};
|
|
3205
|
+
if (dflt.includes("|")) {
|
|
3206
|
+
return {
|
|
3207
|
+
kind: "invalid",
|
|
3208
|
+
message: modelTemplateInvalidMessage(model, "a placeholder names ONE default \u2014 a second `|` is not a fallback chain")
|
|
3209
|
+
};
|
|
3210
|
+
}
|
|
3211
|
+
return {
|
|
3212
|
+
kind: "template",
|
|
3213
|
+
template: {
|
|
3214
|
+
placeholder: trimmed,
|
|
3215
|
+
path,
|
|
3216
|
+
default: dflt
|
|
3217
|
+
}
|
|
3218
|
+
};
|
|
3219
|
+
}
|
|
3220
|
+
return {
|
|
3221
|
+
kind: "template",
|
|
3222
|
+
template: {
|
|
3223
|
+
placeholder: trimmed,
|
|
3224
|
+
path
|
|
3225
|
+
}
|
|
3226
|
+
};
|
|
3227
|
+
}
|
|
3228
|
+
__name(parseModelTemplate, "parseModelTemplate");
|
|
3229
|
+
__name4(parseModelTemplate, "parseModelTemplate");
|
|
3230
|
+
function modelTemplateInvalidMessage(model, why) {
|
|
3231
|
+
return `model "${model.trim()}" is not a valid run-input-bound model placeholder \u2014 ${why}; the forms are \${initData.<path>} and \${initData.<path>|<provider/model default>}`;
|
|
3232
|
+
}
|
|
3233
|
+
__name(modelTemplateInvalidMessage, "modelTemplateInvalidMessage");
|
|
3234
|
+
__name4(modelTemplateInvalidMessage, "modelTemplateInvalidMessage");
|
|
3235
|
+
function modelTemplateDefaultRequiredMessage(model) {
|
|
3236
|
+
return `model "${model}" runs on the Job tier, so its placeholder needs a default \u2014 the harness / provider gates (harness:'claude-code' needs an Anthropic model) classify it at push; write ${model.replace(/\}$/, "|<provider/model>}")}`;
|
|
3237
|
+
}
|
|
3238
|
+
__name(modelTemplateDefaultRequiredMessage, "modelTemplateDefaultRequiredMessage");
|
|
3239
|
+
__name4(modelTemplateDefaultRequiredMessage, "modelTemplateDefaultRequiredMessage");
|
|
3240
|
+
function staticModelPin(model) {
|
|
3241
|
+
if (typeof model !== "string") return void 0;
|
|
3242
|
+
const parsed = parseModelTemplate(model);
|
|
3243
|
+
if (parsed.kind === "static") return model;
|
|
3244
|
+
if (parsed.kind === "template") return parsed.template.default;
|
|
3245
|
+
return void 0;
|
|
3246
|
+
}
|
|
3247
|
+
__name(staticModelPin, "staticModelPin");
|
|
3248
|
+
__name4(staticModelPin, "staticModelPin");
|
|
3249
|
+
function readPath(root, path) {
|
|
3250
|
+
let cur = root;
|
|
3251
|
+
for (const seg of path.split(".")) {
|
|
3252
|
+
const name = seg.replace(/\[(?:0|[1-9]\d*)\]/g, "");
|
|
3253
|
+
const indexes = [
|
|
3254
|
+
...seg.matchAll(/\[(0|[1-9]\d*)\]/g)
|
|
3255
|
+
].map((x) => Number(x[1]));
|
|
3256
|
+
if (cur === null || typeof cur !== "object") return void 0;
|
|
3257
|
+
cur = cur[name];
|
|
3258
|
+
for (const i of indexes) {
|
|
3259
|
+
if (!Array.isArray(cur)) return void 0;
|
|
3260
|
+
cur = cur[i];
|
|
3261
|
+
}
|
|
3262
|
+
}
|
|
3263
|
+
return cur;
|
|
3264
|
+
}
|
|
3265
|
+
__name(readPath, "readPath");
|
|
3266
|
+
__name4(readPath, "readPath");
|
|
3267
|
+
function renderModelTemplate(model, initData) {
|
|
3268
|
+
if (model === void 0 || !looksLikeModelTemplate(model)) return {
|
|
3269
|
+
ok: true,
|
|
3270
|
+
model,
|
|
3271
|
+
source: "static"
|
|
3272
|
+
};
|
|
3273
|
+
const parsed = parseModelTemplate(model);
|
|
3274
|
+
if (parsed.kind === "invalid") {
|
|
3275
|
+
return {
|
|
3276
|
+
ok: false,
|
|
3277
|
+
placeholder: model.trim(),
|
|
3278
|
+
path: "",
|
|
3279
|
+
reason: "invalid",
|
|
3280
|
+
message: parsed.message
|
|
3281
|
+
};
|
|
3282
|
+
}
|
|
3283
|
+
if (parsed.kind === "static") return {
|
|
3284
|
+
ok: true,
|
|
3285
|
+
model,
|
|
3286
|
+
source: "static"
|
|
3287
|
+
};
|
|
3288
|
+
const { placeholder, path } = parsed.template;
|
|
3289
|
+
if (initData !== void 0 && initData !== null && (typeof initData !== "object" || Array.isArray(initData))) {
|
|
3290
|
+
return {
|
|
3291
|
+
ok: false,
|
|
3292
|
+
placeholder,
|
|
3293
|
+
path,
|
|
3294
|
+
reason: "input-not-object",
|
|
3295
|
+
message: modelTemplateInputNotObjectMessage(placeholder, initData)
|
|
3296
|
+
};
|
|
3297
|
+
}
|
|
3298
|
+
const value22 = readPath(initData, path);
|
|
3299
|
+
const reason = value22 === void 0 || value22 === null ? "unbound" : typeof value22 !== "string" ? "not-a-string" : value22.trim() ? null : "empty";
|
|
3300
|
+
if (reason === null) return {
|
|
3301
|
+
ok: true,
|
|
3302
|
+
model: value22.trim(),
|
|
3303
|
+
source: "initData"
|
|
3304
|
+
};
|
|
3305
|
+
if (parsed.template.default !== void 0) return {
|
|
3306
|
+
ok: true,
|
|
3307
|
+
model: parsed.template.default,
|
|
3308
|
+
source: "default"
|
|
3309
|
+
};
|
|
3310
|
+
return {
|
|
3311
|
+
ok: false,
|
|
3312
|
+
placeholder,
|
|
3313
|
+
path,
|
|
3314
|
+
reason,
|
|
3315
|
+
message: modelTemplateUnboundMessage(placeholder, path, reason)
|
|
3316
|
+
};
|
|
3317
|
+
}
|
|
3318
|
+
__name(renderModelTemplate, "renderModelTemplate");
|
|
3319
|
+
__name4(renderModelTemplate, "renderModelTemplate");
|
|
3320
|
+
function modelTemplateUnboundMessage(placeholder, path, reason) {
|
|
3321
|
+
const what = reason === "unbound" ? `the run input has no ${WORKFLOW_MODEL_TEMPLATE_ROOT}.${path}` : reason === "empty" ? `${WORKFLOW_MODEL_TEMPLATE_ROOT}.${path} is empty on the run input` : `${WORKFLOW_MODEL_TEMPLATE_ROOT}.${path} on the run input is not a string`;
|
|
3322
|
+
return `model placeholder "${placeholder}" is unbound \u2014 ${what} and the placeholder declares no default; pass one in the run input or write \${${WORKFLOW_MODEL_TEMPLATE_ROOT}.${path}|<provider/model>}`;
|
|
3323
|
+
}
|
|
3324
|
+
__name(modelTemplateUnboundMessage, "modelTemplateUnboundMessage");
|
|
3325
|
+
__name4(modelTemplateUnboundMessage, "modelTemplateUnboundMessage");
|
|
3326
|
+
function modelTemplateInputNotObjectMessage(placeholder, initData) {
|
|
3327
|
+
const type = Array.isArray(initData) ? "an array" : `a ${typeof initData}`;
|
|
3328
|
+
return `model placeholder "${placeholder}" cannot be rendered \u2014 the run input is ${type}, not an object; start the run with an object input (a JSON string must be parsed before it is passed)`;
|
|
3329
|
+
}
|
|
3330
|
+
__name(modelTemplateInputNotObjectMessage, "modelTemplateInputNotObjectMessage");
|
|
3331
|
+
__name4(modelTemplateInputNotObjectMessage, "modelTemplateInputNotObjectMessage");
|
|
3081
3332
|
var APPROVER_SPEC_MAX_USERS = 20;
|
|
3082
3333
|
var ESCALATION_MAX_HOPS = 3;
|
|
3083
3334
|
var TemplateBindingSchema = z22.object({
|
|
@@ -3618,6 +3869,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
|
|
|
3618
3869
|
}
|
|
3619
3870
|
}
|
|
3620
3871
|
const declaredKeys = new Set(opts.connectionKeys ?? []);
|
|
3872
|
+
const ownKeys = /* @__PURE__ */ new Set();
|
|
3621
3873
|
if (g.connections !== void 0 && !Array.isArray(g.connections)) {
|
|
3622
3874
|
err("connection-declaration-invalid", "`connections` must be an array of { key, integrationType }", "connections");
|
|
3623
3875
|
}
|
|
@@ -3629,7 +3881,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
|
|
|
3629
3881
|
err("connection-declaration-invalid", `connections[${i}].key must match ${WORKFLOW_CONNECTION_KEY_RE}`, `${path}.key`);
|
|
3630
3882
|
return;
|
|
3631
3883
|
}
|
|
3632
|
-
if (
|
|
3884
|
+
if (ownKeys.has(key)) {
|
|
3633
3885
|
err("connection-declaration-invalid", `connections[${i}].key "${key}" is declared twice`, `${path}.key`);
|
|
3634
3886
|
return;
|
|
3635
3887
|
}
|
|
@@ -3637,6 +3889,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
|
|
|
3637
3889
|
err("connection-declaration-invalid", `connections[${i}] ("${key}") needs an integrationType (the catalog slug, e.g. 'github')`, `${path}.integrationType`);
|
|
3638
3890
|
return;
|
|
3639
3891
|
}
|
|
3892
|
+
ownKeys.add(key);
|
|
3640
3893
|
declaredKeys.add(key);
|
|
3641
3894
|
});
|
|
3642
3895
|
const undeclaredKey = /* @__PURE__ */ __name4((ref) => typeof ref === "string" && !declaredKeys.has(ref) && isConnectionKeyShaped(ref) && opts.connectionIds?.has(ref) !== true, "undeclaredKey");
|
|
@@ -3763,7 +4016,7 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
|
|
|
3763
4016
|
if (node.harness !== void 0 && node.tier !== "job") {
|
|
3764
4017
|
err("harness-requires-job-tier", "`harness` is only legal on a tier:'job' agent step", `${path}.harness`, id);
|
|
3765
4018
|
}
|
|
3766
|
-
const provider = node.type === "agent" ? classifyModelProvider(node.model) : null;
|
|
4019
|
+
const provider = node.type === "agent" ? classifyModelProvider(staticModelPin(node.model)) : null;
|
|
3767
4020
|
if (node.harness === "claude-code" && provider !== null && provider !== "anthropic") {
|
|
3768
4021
|
err("harness-provider-mismatch", `harness:'claude-code' needs an Anthropic model (model "${node.type === "agent" ? node.model : ""}" is ${provider})`, `${path}.harness`, id);
|
|
3769
4022
|
}
|
|
@@ -3773,18 +4026,32 @@ function validateLuaExtensions(g, caps = WORKFLOW_CAPS_DEFAULT, opts = {
|
|
|
3773
4026
|
}, "checkTier");
|
|
3774
4027
|
const checkModel = /* @__PURE__ */ __name4((node, path) => {
|
|
3775
4028
|
if (node.type !== "agent" || typeof node.model !== "string") return;
|
|
4029
|
+
const id = singleId(node);
|
|
4030
|
+
const parsed = parseModelTemplate(node.model);
|
|
4031
|
+
if (parsed.kind === "invalid") {
|
|
4032
|
+
err("model-template-invalid", parsed.message, `${path}.model`, id);
|
|
4033
|
+
return;
|
|
4034
|
+
}
|
|
4035
|
+
if (parsed.kind === "template" && parsed.template.default === void 0) {
|
|
4036
|
+
if (isJobTier(node)) {
|
|
4037
|
+
err("model-template-default-required", modelTemplateDefaultRequiredMessage(node.model), `${path}.model`, id);
|
|
4038
|
+
}
|
|
4039
|
+
return;
|
|
4040
|
+
}
|
|
4041
|
+
const pin = parsed.kind === "template" ? parsed.template.default : node.model;
|
|
4042
|
+
if (pin === void 0) return;
|
|
4043
|
+
const inContext = /* @__PURE__ */ __name4((message) => parsed.kind === "template" ? `${message} (the default of ${parsed.template.placeholder})` : message, "inContext");
|
|
3776
4044
|
const registry = opts.approvedModels;
|
|
3777
4045
|
if (registry === void 0) return;
|
|
3778
|
-
const id = singleId(node);
|
|
3779
4046
|
if (registry === "unavailable") {
|
|
3780
|
-
const
|
|
3781
|
-
if (
|
|
3782
|
-
warn("model-unresolved", `model "${
|
|
4047
|
+
const trimmed = pin.trim();
|
|
4048
|
+
if (trimmed && !normalizeModelId(trimmed, []).ok) {
|
|
4049
|
+
warn("model-unresolved", inContext(`model "${trimmed}" could not be checked against the approved-model registry (unavailable at push) \u2014 it dispatches only if it resolves there (a provider/model registry code, or a bare id exactly one approved model carries)`), `${path}.model`, id);
|
|
3783
4050
|
}
|
|
3784
4051
|
return;
|
|
3785
4052
|
}
|
|
3786
|
-
const resolved = normalizeModelId(
|
|
3787
|
-
if (!resolved.ok) err("model-unresolved", modelUnresolvedMessage(resolved), `${path}.model`, id);
|
|
4053
|
+
const resolved = normalizeModelId(pin, registry);
|
|
4054
|
+
if (!resolved.ok) err("model-unresolved", inContext(modelUnresolvedMessage(resolved)), `${path}.model`, id);
|
|
3788
4055
|
}, "checkModel");
|
|
3789
4056
|
const checkWorkspace = /* @__PURE__ */ __name4((node, path) => {
|
|
3790
4057
|
const id = singleId(node);
|
|
@@ -5764,7 +6031,16 @@ var STEP_ERROR_DETAIL_KEYS = [
|
|
|
5764
6031
|
"secretName",
|
|
5765
6032
|
"executionId",
|
|
5766
6033
|
"credentialsExecutionId",
|
|
5767
|
-
"expired"
|
|
6034
|
+
"expired",
|
|
6035
|
+
// Run-input-bound model pins (the Principal Engineer stage models): the Job tier's `MODEL_ERROR` refusals name the
|
|
6036
|
+
// pin they judged (`requested`), the declared harness, the provider the classifier read and the precedence leg the
|
|
6037
|
+
// pin came from (`source`: initData / default / node / env / org / stamp), and `provider_unsupported`'s fleet
|
|
6038
|
+
// (`allowed`, the LUA_WF_JOB_PROVIDERS list) — the members the PR body promises.
|
|
6039
|
+
"requested",
|
|
6040
|
+
"harness",
|
|
6041
|
+
"provider",
|
|
6042
|
+
"source",
|
|
6043
|
+
"allowed"
|
|
5768
6044
|
];
|
|
5769
6045
|
var STEP_ERROR_DETAIL_MAX_BYTES = 8 * 1024;
|
|
5770
6046
|
var DETAIL_MAX_DEPTH = 4;
|
|
@@ -6766,6 +7042,42 @@ function needsInheritedWorkspace(graph) {
|
|
|
6766
7042
|
}
|
|
6767
7043
|
__name(needsInheritedWorkspace, "needsInheritedWorkspace");
|
|
6768
7044
|
__name4(needsInheritedWorkspace, "needsInheritedWorkspace");
|
|
7045
|
+
function armEntry2(arm) {
|
|
7046
|
+
return Array.isArray(arm) ? arm[arm.length - 1] : arm;
|
|
7047
|
+
}
|
|
7048
|
+
__name(armEntry2, "armEntry2");
|
|
7049
|
+
__name4(armEntry2, "armEntry");
|
|
7050
|
+
function entryHasJobTier(entry) {
|
|
7051
|
+
const n2 = armEntry2(entry);
|
|
7052
|
+
if (!n2 || typeof n2 !== "object") return false;
|
|
7053
|
+
const node = n2;
|
|
7054
|
+
if (node.tier === "job") return true;
|
|
7055
|
+
const ws = node.workspace;
|
|
7056
|
+
if (ws !== void 0 && ws !== "inherit") return true;
|
|
7057
|
+
switch (node.type) {
|
|
7058
|
+
case "parallel":
|
|
7059
|
+
return Array.isArray(node.steps) && node.steps.some(entryHasJobTier);
|
|
7060
|
+
case "conditional":
|
|
7061
|
+
return Array.isArray(node.steps) && node.steps.some(entryHasJobTier) || node.otherwise !== void 0 && entryHasJobTier(node.otherwise);
|
|
7062
|
+
case "foreach":
|
|
7063
|
+
case "loop":
|
|
7064
|
+
return entryHasJobTier(node.step);
|
|
7065
|
+
default:
|
|
7066
|
+
return false;
|
|
7067
|
+
}
|
|
7068
|
+
}
|
|
7069
|
+
__name(entryHasJobTier, "entryHasJobTier");
|
|
7070
|
+
__name4(entryHasJobTier, "entryHasJobTier");
|
|
7071
|
+
function graphHasJobTierStep(envelopeOrGraph) {
|
|
7072
|
+
if (!envelopeOrGraph || typeof envelopeOrGraph !== "object") return false;
|
|
7073
|
+
const env = envelopeOrGraph;
|
|
7074
|
+
const envWorkspace = env.workspace;
|
|
7075
|
+
if (envWorkspace !== void 0 && envWorkspace !== "inherit") return true;
|
|
7076
|
+
const graph = Array.isArray(envelopeOrGraph) ? envelopeOrGraph : env.definition?.graph ?? [];
|
|
7077
|
+
return Array.isArray(graph) && graph.some(entryHasJobTier);
|
|
7078
|
+
}
|
|
7079
|
+
__name(graphHasJobTierStep, "graphHasJobTierStep");
|
|
7080
|
+
__name4(graphHasJobTierStep, "graphHasJobTierStep");
|
|
6769
7081
|
|
|
6770
7082
|
// src/types/workflow.ts
|
|
6771
7083
|
function createStep(s) {
|