@useorgx/openclaw-plugin 0.4.1 → 0.4.4
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 +12 -0
- package/dashboard/dist/assets/{CE5pVdev.js → 4hvaB0UC.js} +1 -1
- package/dashboard/dist/assets/{BqukHQH-.js → B3ziCA02.js} +1 -1
- package/dashboard/dist/assets/{TN5wE36J.js → BgsfM2lz.js} +1 -1
- package/dashboard/dist/assets/DCBlK4MX.js +212 -0
- package/dashboard/dist/assets/{Nip3CrNC.js → DEuY_RBN.js} +1 -1
- package/dashboard/dist/assets/sAhvFnpk.js +4 -0
- package/dashboard/dist/index.html +4 -4
- package/dist/byok-store.js +181 -54
- package/dist/contracts/client.js +22 -2
- package/dist/gateway-watchdog-runner.d.ts +1 -0
- package/dist/gateway-watchdog-runner.js +6 -0
- package/dist/gateway-watchdog.d.ts +11 -0
- package/dist/gateway-watchdog.js +221 -0
- package/dist/http-handler.js +705 -78
- package/dist/index.js +155 -0
- package/dist/openclaw-settings.d.ts +17 -0
- package/dist/openclaw-settings.js +118 -0
- package/dist/telemetry/posthog.d.ts +8 -0
- package/dist/telemetry/posthog.js +81 -0
- package/package.json +1 -1
- package/dashboard/dist/assets/Cpr7n8fE.js +0 -1
- package/dashboard/dist/assets/X6IcjS74.js +0 -212
package/dist/http-handler.js
CHANGED
|
@@ -30,6 +30,7 @@ import { getAgentRun, markAgentRunStopped, readAgentRuns, upsertAgentRun, } from
|
|
|
30
30
|
import { readByokKeys, writeByokKeys } from "./byok-store.js";
|
|
31
31
|
import { computeMilestoneRollup, computeWorkstreamRollup, } from "./reporting/rollups.js";
|
|
32
32
|
import { listRuntimeInstances, resolveRuntimeHookToken, upsertRuntimeInstanceFromHook, } from "./runtime-instance-store.js";
|
|
33
|
+
import { readOpenClawSettingsSnapshot, resolvePreferredOpenClawProvider, } from "./openclaw-settings.js";
|
|
33
34
|
// =============================================================================
|
|
34
35
|
// Helpers
|
|
35
36
|
// =============================================================================
|
|
@@ -203,48 +204,60 @@ async function setOpenClawAgentModel(input) {
|
|
|
203
204
|
}
|
|
204
205
|
}
|
|
205
206
|
async function listOpenClawProviderModels(input) {
|
|
206
|
-
const
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
"
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const
|
|
239
|
-
|
|
240
|
-
const tags = Array.isArray(row.tags)
|
|
241
|
-
? row.tags.filter((t) => typeof t === "string")
|
|
207
|
+
const providerArgs = input.provider === "openai" ? ["openai-codex", "openai"] : [input.provider];
|
|
208
|
+
let lastError = null;
|
|
209
|
+
for (const providerArg of providerArgs) {
|
|
210
|
+
const result = await runCommandCollect({
|
|
211
|
+
command: "openclaw",
|
|
212
|
+
args: [
|
|
213
|
+
"models",
|
|
214
|
+
"--agent",
|
|
215
|
+
input.agentId,
|
|
216
|
+
"list",
|
|
217
|
+
"--provider",
|
|
218
|
+
providerArg,
|
|
219
|
+
"--json",
|
|
220
|
+
],
|
|
221
|
+
timeoutMs: 10_000,
|
|
222
|
+
env: resolveByokEnvOverrides(),
|
|
223
|
+
});
|
|
224
|
+
if (result.exitCode !== 0) {
|
|
225
|
+
lastError = new Error(result.stderr.trim() || "openclaw models list failed");
|
|
226
|
+
continue;
|
|
227
|
+
}
|
|
228
|
+
const parsed = parseJsonSafe(result.stdout);
|
|
229
|
+
if (!parsed || typeof parsed !== "object") {
|
|
230
|
+
const trimmed = result.stdout.trim();
|
|
231
|
+
if (!trimmed || /no models found/i.test(trimmed)) {
|
|
232
|
+
if (providerArg === providerArgs[providerArgs.length - 1])
|
|
233
|
+
return [];
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
lastError = new Error("openclaw models list returned invalid JSON");
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
const modelsRaw = "models" in parsed && Array.isArray(parsed.models)
|
|
240
|
+
? parsed.models
|
|
242
241
|
: [];
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
242
|
+
const models = modelsRaw
|
|
243
|
+
.map((entry) => {
|
|
244
|
+
if (!entry || typeof entry !== "object")
|
|
245
|
+
return null;
|
|
246
|
+
const row = entry;
|
|
247
|
+
const key = typeof row.key === "string" ? row.key.trim() : "";
|
|
248
|
+
const tags = Array.isArray(row.tags)
|
|
249
|
+
? row.tags.filter((t) => typeof t === "string")
|
|
250
|
+
: [];
|
|
251
|
+
if (!key)
|
|
252
|
+
return null;
|
|
253
|
+
return { key, tags };
|
|
254
|
+
})
|
|
255
|
+
.filter((entry) => Boolean(entry));
|
|
256
|
+
if (models.length > 0 || providerArg === providerArgs[providerArgs.length - 1]) {
|
|
257
|
+
return models;
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
throw lastError ?? new Error("openclaw models list failed");
|
|
248
261
|
}
|
|
249
262
|
function pickPreferredModel(models) {
|
|
250
263
|
if (models.length === 0)
|
|
@@ -256,7 +269,7 @@ async function configureOpenClawProviderRouting(input) {
|
|
|
256
269
|
const requestedModel = (input.requestedModel ?? "").trim() || null;
|
|
257
270
|
// Fast path: use known aliases where possible.
|
|
258
271
|
const aliasByProvider = {
|
|
259
|
-
anthropic: "
|
|
272
|
+
anthropic: "sonnet",
|
|
260
273
|
openrouter: "sonnet",
|
|
261
274
|
openai: null,
|
|
262
275
|
};
|
|
@@ -281,6 +294,18 @@ async function configureOpenClawProviderRouting(input) {
|
|
|
281
294
|
await setOpenClawAgentModel({ agentId: input.agentId, model: selected });
|
|
282
295
|
return { provider: input.provider, model: selected };
|
|
283
296
|
}
|
|
297
|
+
function resolveAutoOpenClawProvider() {
|
|
298
|
+
try {
|
|
299
|
+
const settings = readOpenClawSettingsSnapshot();
|
|
300
|
+
const provider = resolvePreferredOpenClawProvider(settings.raw);
|
|
301
|
+
if (!provider)
|
|
302
|
+
return null;
|
|
303
|
+
return provider;
|
|
304
|
+
}
|
|
305
|
+
catch {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
284
309
|
function isPidAlive(pid) {
|
|
285
310
|
if (!Number.isFinite(pid) || pid <= 0)
|
|
286
311
|
return false;
|
|
@@ -1234,6 +1259,86 @@ function idempotencyKey(parts) {
|
|
|
1234
1259
|
const suffix = stableHash(raw).slice(0, 20);
|
|
1235
1260
|
return `${cleaned}:${suffix}`.slice(0, 120);
|
|
1236
1261
|
}
|
|
1262
|
+
const ORGX_SKILL_BY_DOMAIN = {
|
|
1263
|
+
engineering: "orgx-engineering-agent",
|
|
1264
|
+
product: "orgx-product-agent",
|
|
1265
|
+
marketing: "orgx-marketing-agent",
|
|
1266
|
+
sales: "orgx-sales-agent",
|
|
1267
|
+
operations: "orgx-operations-agent",
|
|
1268
|
+
design: "orgx-design-agent",
|
|
1269
|
+
orchestration: "orgx-orchestrator-agent",
|
|
1270
|
+
};
|
|
1271
|
+
function normalizeExecutionDomain(value) {
|
|
1272
|
+
const raw = (value ?? "").trim().toLowerCase();
|
|
1273
|
+
if (!raw)
|
|
1274
|
+
return null;
|
|
1275
|
+
if (raw === "orchestrator")
|
|
1276
|
+
return "orchestration";
|
|
1277
|
+
if (raw === "ops")
|
|
1278
|
+
return "operations";
|
|
1279
|
+
return Object.prototype.hasOwnProperty.call(ORGX_SKILL_BY_DOMAIN, raw)
|
|
1280
|
+
? raw
|
|
1281
|
+
: null;
|
|
1282
|
+
}
|
|
1283
|
+
function inferExecutionDomainFromText(...values) {
|
|
1284
|
+
const text = values
|
|
1285
|
+
.map((value) => (value ?? "").trim().toLowerCase())
|
|
1286
|
+
.filter((value) => value.length > 0)
|
|
1287
|
+
.join(" ");
|
|
1288
|
+
if (!text)
|
|
1289
|
+
return "engineering";
|
|
1290
|
+
if (/\b(marketing|campaign|copy|ad|content)\b/.test(text))
|
|
1291
|
+
return "marketing";
|
|
1292
|
+
if (/\b(sales|meddic|pipeline|deal|outreach)\b/.test(text))
|
|
1293
|
+
return "sales";
|
|
1294
|
+
if (/\b(design|ui|ux|brand|wcag)\b/.test(text))
|
|
1295
|
+
return "design";
|
|
1296
|
+
if (/\b(product|prd|roadmap|prioritization)\b/.test(text))
|
|
1297
|
+
return "product";
|
|
1298
|
+
if (/\b(ops|operations|incident|reliability|oncall|slo)\b/.test(text))
|
|
1299
|
+
return "operations";
|
|
1300
|
+
if (/\b(orchestration|dispatch|handoff)\b/.test(text))
|
|
1301
|
+
return "orchestration";
|
|
1302
|
+
return "engineering";
|
|
1303
|
+
}
|
|
1304
|
+
function deriveExecutionPolicy(taskNode, workstreamNode) {
|
|
1305
|
+
const domainCandidate = taskNode.assignedAgents
|
|
1306
|
+
.map((agent) => normalizeExecutionDomain(agent.domain))
|
|
1307
|
+
.find((domain) => Boolean(domain)) ??
|
|
1308
|
+
(workstreamNode
|
|
1309
|
+
? workstreamNode.assignedAgents
|
|
1310
|
+
.map((agent) => normalizeExecutionDomain(agent.domain))
|
|
1311
|
+
.find((domain) => Boolean(domain))
|
|
1312
|
+
: null) ??
|
|
1313
|
+
inferExecutionDomainFromText(taskNode.title, workstreamNode?.title ?? null);
|
|
1314
|
+
const domain = normalizeExecutionDomain(domainCandidate) ?? "engineering";
|
|
1315
|
+
const requiredSkill = ORGX_SKILL_BY_DOMAIN[domain] ?? ORGX_SKILL_BY_DOMAIN.engineering;
|
|
1316
|
+
return { domain, requiredSkills: [requiredSkill] };
|
|
1317
|
+
}
|
|
1318
|
+
function spawnGuardIsRateLimited(result) {
|
|
1319
|
+
if (!result || typeof result !== "object")
|
|
1320
|
+
return false;
|
|
1321
|
+
const record = result;
|
|
1322
|
+
const checks = record.checks;
|
|
1323
|
+
if (!checks || typeof checks !== "object")
|
|
1324
|
+
return false;
|
|
1325
|
+
const rateLimit = checks.rateLimit;
|
|
1326
|
+
if (!rateLimit || typeof rateLimit !== "object")
|
|
1327
|
+
return false;
|
|
1328
|
+
return rateLimit.passed === false;
|
|
1329
|
+
}
|
|
1330
|
+
function summarizeSpawnGuardBlockReason(result) {
|
|
1331
|
+
if (!result || typeof result !== "object")
|
|
1332
|
+
return "Spawn guard denied dispatch.";
|
|
1333
|
+
const record = result;
|
|
1334
|
+
const blockedReason = pickString(record, ["blockedReason", "blocked_reason"]);
|
|
1335
|
+
if (blockedReason)
|
|
1336
|
+
return blockedReason;
|
|
1337
|
+
if (spawnGuardIsRateLimited(result)) {
|
|
1338
|
+
return "Spawn guard rate limit reached.";
|
|
1339
|
+
}
|
|
1340
|
+
return "Spawn guard denied dispatch.";
|
|
1341
|
+
}
|
|
1237
1342
|
const DEFAULT_DURATION_HOURS = {
|
|
1238
1343
|
initiative: 40,
|
|
1239
1344
|
workstream: 16,
|
|
@@ -2125,6 +2230,263 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2125
2230
|
}
|
|
2126
2231
|
}
|
|
2127
2232
|
}
|
|
2233
|
+
async function requestDecisionSafe(input) {
|
|
2234
|
+
const initiativeId = input.initiativeId?.trim() ?? "";
|
|
2235
|
+
const title = input.title.trim();
|
|
2236
|
+
if (!initiativeId || !title)
|
|
2237
|
+
return;
|
|
2238
|
+
try {
|
|
2239
|
+
await client.applyChangeset({
|
|
2240
|
+
initiative_id: initiativeId,
|
|
2241
|
+
correlation_id: input.correlationId?.trim() || undefined,
|
|
2242
|
+
source_client: "openclaw",
|
|
2243
|
+
idempotency_key: idempotencyKey([
|
|
2244
|
+
"openclaw",
|
|
2245
|
+
"decision",
|
|
2246
|
+
initiativeId,
|
|
2247
|
+
title,
|
|
2248
|
+
input.correlationId ?? null,
|
|
2249
|
+
]),
|
|
2250
|
+
operations: [
|
|
2251
|
+
{
|
|
2252
|
+
op: "decision.create",
|
|
2253
|
+
title,
|
|
2254
|
+
summary: input.summary ?? undefined,
|
|
2255
|
+
urgency: input.urgency ?? "high",
|
|
2256
|
+
options: input.options ?? [],
|
|
2257
|
+
blocking: input.blocking ?? true,
|
|
2258
|
+
},
|
|
2259
|
+
],
|
|
2260
|
+
});
|
|
2261
|
+
}
|
|
2262
|
+
catch {
|
|
2263
|
+
// best effort
|
|
2264
|
+
}
|
|
2265
|
+
}
|
|
2266
|
+
async function checkSpawnGuardSafe(input) {
|
|
2267
|
+
const scopedClient = client;
|
|
2268
|
+
if (typeof scopedClient.checkSpawnGuard !== "function") {
|
|
2269
|
+
return null;
|
|
2270
|
+
}
|
|
2271
|
+
const taskId = input.taskId?.trim() ?? "";
|
|
2272
|
+
const targetLabel = input.targetLabel?.trim() ||
|
|
2273
|
+
(taskId ? `task ${taskId}` : "dispatch target");
|
|
2274
|
+
try {
|
|
2275
|
+
return await scopedClient.checkSpawnGuard(input.domain, taskId || undefined);
|
|
2276
|
+
}
|
|
2277
|
+
catch (err) {
|
|
2278
|
+
await emitActivitySafe({
|
|
2279
|
+
initiativeId: input.initiativeId,
|
|
2280
|
+
correlationId: input.correlationId,
|
|
2281
|
+
phase: "blocked",
|
|
2282
|
+
level: "warn",
|
|
2283
|
+
message: `Spawn guard check degraded for ${targetLabel}; continuing with local policy.`,
|
|
2284
|
+
metadata: {
|
|
2285
|
+
event: "spawn_guard_degraded",
|
|
2286
|
+
task_id: taskId || null,
|
|
2287
|
+
domain: input.domain,
|
|
2288
|
+
error: safeErrorMessage(err),
|
|
2289
|
+
},
|
|
2290
|
+
});
|
|
2291
|
+
return null;
|
|
2292
|
+
}
|
|
2293
|
+
}
|
|
2294
|
+
function extractSpawnGuardModelTier(result) {
|
|
2295
|
+
if (!result || typeof result !== "object")
|
|
2296
|
+
return null;
|
|
2297
|
+
return (pickString(result, ["modelTier", "model_tier"]) ??
|
|
2298
|
+
null);
|
|
2299
|
+
}
|
|
2300
|
+
function formatRequiredSkills(requiredSkills) {
|
|
2301
|
+
const normalized = requiredSkills
|
|
2302
|
+
.map((entry) => entry.trim())
|
|
2303
|
+
.filter((entry) => entry.length > 0)
|
|
2304
|
+
.map((entry) => (entry.startsWith("$") ? entry : `$${entry}`));
|
|
2305
|
+
return normalized.length > 0
|
|
2306
|
+
? normalized.join(", ")
|
|
2307
|
+
: "$orgx-engineering-agent";
|
|
2308
|
+
}
|
|
2309
|
+
function buildPolicyEnforcedMessage(input) {
|
|
2310
|
+
const modelTier = extractSpawnGuardModelTier(input.spawnGuardResult ?? null);
|
|
2311
|
+
return [
|
|
2312
|
+
`Execution policy: ${input.executionPolicy.domain}`,
|
|
2313
|
+
`Required skills: ${formatRequiredSkills(input.executionPolicy.requiredSkills)}`,
|
|
2314
|
+
modelTier ? `Spawn guard model tier: ${modelTier}` : null,
|
|
2315
|
+
"",
|
|
2316
|
+
input.baseMessage,
|
|
2317
|
+
]
|
|
2318
|
+
.filter((entry) => Boolean(entry))
|
|
2319
|
+
.join("\n");
|
|
2320
|
+
}
|
|
2321
|
+
async function resolveDispatchExecutionPolicy(input) {
|
|
2322
|
+
const initiativeId = input.initiativeId?.trim() ?? "";
|
|
2323
|
+
const taskId = input.taskId?.trim() ?? "";
|
|
2324
|
+
const workstreamId = input.workstreamId?.trim() ?? "";
|
|
2325
|
+
let resolvedTaskTitle = input.taskTitle?.trim() || null;
|
|
2326
|
+
let resolvedWorkstreamTitle = input.workstreamTitle?.trim() || null;
|
|
2327
|
+
if (initiativeId && (taskId || workstreamId)) {
|
|
2328
|
+
try {
|
|
2329
|
+
const graph = await buildMissionControlGraph(client, initiativeId);
|
|
2330
|
+
const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
|
|
2331
|
+
const taskNode = taskId ? nodeById.get(taskId) ?? null : null;
|
|
2332
|
+
const workstreamNode = workstreamId ? nodeById.get(workstreamId) ?? null : null;
|
|
2333
|
+
if (taskNode && taskNode.type === "task") {
|
|
2334
|
+
resolvedTaskTitle = resolvedTaskTitle ?? taskNode.title;
|
|
2335
|
+
const relatedWorkstream = (taskNode.workstreamId ? nodeById.get(taskNode.workstreamId) ?? null : null) ??
|
|
2336
|
+
workstreamNode;
|
|
2337
|
+
const normalizedWorkstream = relatedWorkstream && relatedWorkstream.type === "workstream"
|
|
2338
|
+
? relatedWorkstream
|
|
2339
|
+
: null;
|
|
2340
|
+
resolvedWorkstreamTitle =
|
|
2341
|
+
resolvedWorkstreamTitle ?? normalizedWorkstream?.title ?? null;
|
|
2342
|
+
return {
|
|
2343
|
+
executionPolicy: deriveExecutionPolicy(taskNode, normalizedWorkstream),
|
|
2344
|
+
taskTitle: resolvedTaskTitle,
|
|
2345
|
+
workstreamTitle: resolvedWorkstreamTitle,
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2348
|
+
if (workstreamNode && workstreamNode.type === "workstream") {
|
|
2349
|
+
resolvedWorkstreamTitle = resolvedWorkstreamTitle ?? workstreamNode.title;
|
|
2350
|
+
const assignedDomain = workstreamNode.assignedAgents
|
|
2351
|
+
.map((agent) => normalizeExecutionDomain(agent.domain))
|
|
2352
|
+
.find((entry) => Boolean(entry));
|
|
2353
|
+
const domain = assignedDomain ??
|
|
2354
|
+
inferExecutionDomainFromText(workstreamNode.title, input.initiativeTitle, input.message);
|
|
2355
|
+
const normalizedDomain = normalizeExecutionDomain(domain) ?? "engineering";
|
|
2356
|
+
return {
|
|
2357
|
+
executionPolicy: {
|
|
2358
|
+
domain: normalizedDomain,
|
|
2359
|
+
requiredSkills: [
|
|
2360
|
+
ORGX_SKILL_BY_DOMAIN[normalizedDomain] ??
|
|
2361
|
+
ORGX_SKILL_BY_DOMAIN.engineering,
|
|
2362
|
+
],
|
|
2363
|
+
},
|
|
2364
|
+
taskTitle: resolvedTaskTitle,
|
|
2365
|
+
workstreamTitle: resolvedWorkstreamTitle,
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
}
|
|
2369
|
+
catch {
|
|
2370
|
+
// best effort
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
const inferredDomain = normalizeExecutionDomain(inferExecutionDomainFromText(resolvedTaskTitle, resolvedWorkstreamTitle, input.initiativeTitle, input.message)) ?? "engineering";
|
|
2374
|
+
return {
|
|
2375
|
+
executionPolicy: {
|
|
2376
|
+
domain: inferredDomain,
|
|
2377
|
+
requiredSkills: [
|
|
2378
|
+
ORGX_SKILL_BY_DOMAIN[inferredDomain] ?? ORGX_SKILL_BY_DOMAIN.engineering,
|
|
2379
|
+
],
|
|
2380
|
+
},
|
|
2381
|
+
taskTitle: resolvedTaskTitle,
|
|
2382
|
+
workstreamTitle: resolvedWorkstreamTitle,
|
|
2383
|
+
};
|
|
2384
|
+
}
|
|
2385
|
+
async function enforceSpawnGuardForDispatch(input) {
|
|
2386
|
+
const taskId = input.taskId?.trim() ?? "";
|
|
2387
|
+
const workstreamId = input.workstreamId?.trim() ?? "";
|
|
2388
|
+
const taskTitle = input.taskTitle?.trim() || null;
|
|
2389
|
+
const workstreamTitle = input.workstreamTitle?.trim() || null;
|
|
2390
|
+
const targetLabel = taskId
|
|
2391
|
+
? `task ${taskTitle ?? taskId}`
|
|
2392
|
+
: workstreamId
|
|
2393
|
+
? `workstream ${workstreamTitle ?? workstreamId}`
|
|
2394
|
+
: "dispatch target";
|
|
2395
|
+
const spawnGuardResult = await checkSpawnGuardSafe({
|
|
2396
|
+
domain: input.executionPolicy.domain,
|
|
2397
|
+
taskId: taskId || workstreamId || null,
|
|
2398
|
+
initiativeId: input.initiativeId,
|
|
2399
|
+
correlationId: input.correlationId,
|
|
2400
|
+
targetLabel,
|
|
2401
|
+
});
|
|
2402
|
+
if (!spawnGuardResult || typeof spawnGuardResult !== "object") {
|
|
2403
|
+
return {
|
|
2404
|
+
allowed: true,
|
|
2405
|
+
retryable: false,
|
|
2406
|
+
blockedReason: null,
|
|
2407
|
+
spawnGuardResult,
|
|
2408
|
+
};
|
|
2409
|
+
}
|
|
2410
|
+
const allowed = spawnGuardResult.allowed;
|
|
2411
|
+
if (allowed !== false) {
|
|
2412
|
+
return {
|
|
2413
|
+
allowed: true,
|
|
2414
|
+
retryable: false,
|
|
2415
|
+
blockedReason: null,
|
|
2416
|
+
spawnGuardResult,
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
const blockedReason = summarizeSpawnGuardBlockReason(spawnGuardResult);
|
|
2420
|
+
const retryable = spawnGuardIsRateLimited(spawnGuardResult);
|
|
2421
|
+
const blockedEvent = retryable
|
|
2422
|
+
? `${input.sourceEventPrefix}_spawn_guard_rate_limited`
|
|
2423
|
+
: `${input.sourceEventPrefix}_spawn_guard_blocked`;
|
|
2424
|
+
await emitActivitySafe({
|
|
2425
|
+
initiativeId: input.initiativeId,
|
|
2426
|
+
correlationId: input.correlationId,
|
|
2427
|
+
phase: "blocked",
|
|
2428
|
+
level: retryable ? "warn" : "error",
|
|
2429
|
+
message: retryable
|
|
2430
|
+
? `Spawn guard rate-limited ${targetLabel}; deferring launch.`
|
|
2431
|
+
: `Spawn guard blocked ${targetLabel}.`,
|
|
2432
|
+
metadata: {
|
|
2433
|
+
event: blockedEvent,
|
|
2434
|
+
agent_id: input.agentId ?? null,
|
|
2435
|
+
task_id: taskId || null,
|
|
2436
|
+
task_title: taskTitle,
|
|
2437
|
+
workstream_id: workstreamId || null,
|
|
2438
|
+
workstream_title: workstreamTitle,
|
|
2439
|
+
domain: input.executionPolicy.domain,
|
|
2440
|
+
required_skills: input.executionPolicy.requiredSkills,
|
|
2441
|
+
blocked_reason: blockedReason,
|
|
2442
|
+
spawn_guard: spawnGuardResult,
|
|
2443
|
+
},
|
|
2444
|
+
nextStep: retryable
|
|
2445
|
+
? "Retry dispatch when spawn rate limits recover."
|
|
2446
|
+
: "Review decision and unblock guard checks before retry.",
|
|
2447
|
+
});
|
|
2448
|
+
if (!retryable && input.initiativeId && taskId) {
|
|
2449
|
+
try {
|
|
2450
|
+
await client.updateEntity("task", taskId, { status: "blocked" });
|
|
2451
|
+
}
|
|
2452
|
+
catch {
|
|
2453
|
+
// best effort
|
|
2454
|
+
}
|
|
2455
|
+
await syncParentRollupsForTask({
|
|
2456
|
+
initiativeId: input.initiativeId,
|
|
2457
|
+
taskId,
|
|
2458
|
+
workstreamId: workstreamId || null,
|
|
2459
|
+
milestoneId: input.milestoneId ?? null,
|
|
2460
|
+
correlationId: input.correlationId,
|
|
2461
|
+
});
|
|
2462
|
+
}
|
|
2463
|
+
if (!retryable) {
|
|
2464
|
+
await requestDecisionSafe({
|
|
2465
|
+
initiativeId: input.initiativeId,
|
|
2466
|
+
correlationId: input.correlationId,
|
|
2467
|
+
title: `Unblock ${targetLabel}`,
|
|
2468
|
+
summary: [
|
|
2469
|
+
`${targetLabel} failed spawn guard checks.`,
|
|
2470
|
+
`Reason: ${blockedReason}`,
|
|
2471
|
+
`Domain: ${input.executionPolicy.domain}`,
|
|
2472
|
+
`Required skills: ${input.executionPolicy.requiredSkills.join(", ")}`,
|
|
2473
|
+
].join(" "),
|
|
2474
|
+
urgency: "high",
|
|
2475
|
+
options: [
|
|
2476
|
+
"Approve exception and continue",
|
|
2477
|
+
"Reassign task/domain",
|
|
2478
|
+
"Pause and investigate quality gate",
|
|
2479
|
+
],
|
|
2480
|
+
blocking: true,
|
|
2481
|
+
});
|
|
2482
|
+
}
|
|
2483
|
+
return {
|
|
2484
|
+
allowed: false,
|
|
2485
|
+
retryable,
|
|
2486
|
+
blockedReason,
|
|
2487
|
+
spawnGuardResult,
|
|
2488
|
+
};
|
|
2489
|
+
}
|
|
2128
2490
|
async function syncParentRollupsForTask(input) {
|
|
2129
2491
|
const initiativeId = input.initiativeId?.trim() ?? "";
|
|
2130
2492
|
const taskId = input.taskId?.trim() ?? "";
|
|
@@ -2464,25 +2826,61 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2464
2826
|
async function dispatchFallbackWorkstreamTurn(input) {
|
|
2465
2827
|
const now = new Date().toISOString();
|
|
2466
2828
|
const sessionId = randomUUID();
|
|
2467
|
-
const
|
|
2829
|
+
const policyResolution = await resolveDispatchExecutionPolicy({
|
|
2830
|
+
initiativeId: input.initiativeId,
|
|
2831
|
+
initiativeTitle: input.initiativeTitle,
|
|
2832
|
+
workstreamId: input.workstreamId,
|
|
2833
|
+
workstreamTitle: input.workstreamTitle,
|
|
2834
|
+
message: "Continue this workstream from the latest context. Identify and execute the next concrete task.",
|
|
2835
|
+
});
|
|
2836
|
+
const executionPolicy = policyResolution.executionPolicy;
|
|
2837
|
+
const resolvedWorkstreamTitle = policyResolution.workstreamTitle ?? input.workstreamTitle;
|
|
2838
|
+
const guard = await enforceSpawnGuardForDispatch({
|
|
2839
|
+
sourceEventPrefix: "next_up_fallback",
|
|
2840
|
+
initiativeId: input.initiativeId,
|
|
2841
|
+
correlationId: sessionId,
|
|
2842
|
+
executionPolicy,
|
|
2843
|
+
agentId: input.agentId,
|
|
2844
|
+
workstreamId: input.workstreamId,
|
|
2845
|
+
workstreamTitle: resolvedWorkstreamTitle,
|
|
2846
|
+
});
|
|
2847
|
+
if (!guard.allowed) {
|
|
2848
|
+
return {
|
|
2849
|
+
sessionId: null,
|
|
2850
|
+
pid: null,
|
|
2851
|
+
blockedReason: guard.blockedReason,
|
|
2852
|
+
retryable: guard.retryable,
|
|
2853
|
+
executionPolicy,
|
|
2854
|
+
spawnGuardResult: guard.spawnGuardResult,
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
const baseMessage = [
|
|
2468
2858
|
`Initiative: ${input.initiativeTitle}`,
|
|
2469
|
-
`Workstream: ${
|
|
2859
|
+
`Workstream: ${resolvedWorkstreamTitle}`,
|
|
2470
2860
|
"",
|
|
2471
2861
|
"Continue this workstream from the latest context.",
|
|
2472
2862
|
"Identify and execute the next concrete task, then provide a concise progress summary.",
|
|
2473
2863
|
].join("\n");
|
|
2864
|
+
const message = buildPolicyEnforcedMessage({
|
|
2865
|
+
baseMessage,
|
|
2866
|
+
executionPolicy,
|
|
2867
|
+
spawnGuardResult: guard.spawnGuardResult,
|
|
2868
|
+
});
|
|
2474
2869
|
await emitActivitySafe({
|
|
2475
2870
|
initiativeId: input.initiativeId,
|
|
2476
2871
|
correlationId: sessionId,
|
|
2477
2872
|
phase: "execution",
|
|
2478
2873
|
level: "info",
|
|
2479
|
-
message: `Next Up dispatched ${
|
|
2874
|
+
message: `Next Up dispatched ${resolvedWorkstreamTitle}.`,
|
|
2480
2875
|
metadata: {
|
|
2481
2876
|
event: "next_up_manual_dispatch_started",
|
|
2482
2877
|
agent_id: input.agentId,
|
|
2483
2878
|
session_id: sessionId,
|
|
2484
2879
|
workstream_id: input.workstreamId,
|
|
2485
|
-
workstream_title:
|
|
2880
|
+
workstream_title: resolvedWorkstreamTitle,
|
|
2881
|
+
domain: executionPolicy.domain,
|
|
2882
|
+
required_skills: executionPolicy.requiredSkills,
|
|
2883
|
+
spawn_guard_model_tier: extractSpawnGuardModelTier(guard.spawnGuardResult),
|
|
2486
2884
|
fallback: true,
|
|
2487
2885
|
},
|
|
2488
2886
|
});
|
|
@@ -2512,7 +2910,14 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2512
2910
|
startedAt: now,
|
|
2513
2911
|
status: "running",
|
|
2514
2912
|
});
|
|
2515
|
-
return {
|
|
2913
|
+
return {
|
|
2914
|
+
sessionId,
|
|
2915
|
+
pid: spawned.pid,
|
|
2916
|
+
blockedReason: null,
|
|
2917
|
+
retryable: false,
|
|
2918
|
+
executionPolicy,
|
|
2919
|
+
spawnGuardResult: guard.spawnGuardResult,
|
|
2920
|
+
};
|
|
2516
2921
|
}
|
|
2517
2922
|
async function tickAutoContinueRun(run) {
|
|
2518
2923
|
if (run.status !== "running" && run.status !== "stopping")
|
|
@@ -2579,6 +2984,27 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2579
2984
|
error_message: summary.errorMessage,
|
|
2580
2985
|
},
|
|
2581
2986
|
});
|
|
2987
|
+
if (summary.hadError && record.taskId) {
|
|
2988
|
+
await requestDecisionSafe({
|
|
2989
|
+
initiativeId: run.initiativeId,
|
|
2990
|
+
correlationId: record.runId,
|
|
2991
|
+
title: `Unblock auto-continue task ${record.taskId}`,
|
|
2992
|
+
summary: [
|
|
2993
|
+
`Task ${record.taskId} finished with runtime error in session ${record.runId}.`,
|
|
2994
|
+
summary.errorMessage ? `Error: ${summary.errorMessage}` : null,
|
|
2995
|
+
`Workstream: ${record.workstreamId ?? "unknown"}.`,
|
|
2996
|
+
]
|
|
2997
|
+
.filter((line) => Boolean(line))
|
|
2998
|
+
.join(" "),
|
|
2999
|
+
urgency: "high",
|
|
3000
|
+
options: [
|
|
3001
|
+
"Retry task in auto-continue",
|
|
3002
|
+
"Assign manual recovery owner",
|
|
3003
|
+
"Pause initiative until fixed",
|
|
3004
|
+
],
|
|
3005
|
+
blocking: true,
|
|
3006
|
+
});
|
|
3007
|
+
}
|
|
2582
3008
|
run.lastRunId = record.runId;
|
|
2583
3009
|
run.lastTaskId = record.taskId ?? run.lastTaskId;
|
|
2584
3010
|
run.activeRunId = null;
|
|
@@ -2701,30 +3127,120 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2701
3127
|
const milestoneTitle = nextTaskNode.milestoneId
|
|
2702
3128
|
? nodeById.get(nextTaskNode.milestoneId)?.title ?? null
|
|
2703
3129
|
: null;
|
|
3130
|
+
const workstreamNode = nextTaskNode.workstreamId
|
|
3131
|
+
? nodeById.get(nextTaskNode.workstreamId) ?? null
|
|
3132
|
+
: null;
|
|
3133
|
+
const executionPolicy = deriveExecutionPolicy(nextTaskNode, workstreamNode);
|
|
3134
|
+
const spawnGuardResult = await checkSpawnGuardSafe({
|
|
3135
|
+
domain: executionPolicy.domain,
|
|
3136
|
+
taskId: nextTaskNode.id,
|
|
3137
|
+
initiativeId: run.initiativeId,
|
|
3138
|
+
correlationId: sessionId,
|
|
3139
|
+
});
|
|
3140
|
+
if (spawnGuardResult && typeof spawnGuardResult === "object") {
|
|
3141
|
+
const allowed = spawnGuardResult.allowed;
|
|
3142
|
+
if (allowed === false) {
|
|
3143
|
+
const blockedReason = summarizeSpawnGuardBlockReason(spawnGuardResult);
|
|
3144
|
+
if (spawnGuardIsRateLimited(spawnGuardResult)) {
|
|
3145
|
+
run.lastError = blockedReason;
|
|
3146
|
+
run.updatedAt = now;
|
|
3147
|
+
await emitActivitySafe({
|
|
3148
|
+
initiativeId: run.initiativeId,
|
|
3149
|
+
correlationId: sessionId,
|
|
3150
|
+
phase: "blocked",
|
|
3151
|
+
level: "warn",
|
|
3152
|
+
message: `Spawn guard rate-limited task ${nextTaskNode.id}; waiting to retry.`,
|
|
3153
|
+
metadata: {
|
|
3154
|
+
event: "auto_continue_spawn_guard_rate_limited",
|
|
3155
|
+
task_id: nextTaskNode.id,
|
|
3156
|
+
task_title: nextTaskNode.title,
|
|
3157
|
+
domain: executionPolicy.domain,
|
|
3158
|
+
required_skills: executionPolicy.requiredSkills,
|
|
3159
|
+
spawn_guard: spawnGuardResult,
|
|
3160
|
+
},
|
|
3161
|
+
});
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
try {
|
|
3165
|
+
await client.updateEntity("task", nextTaskNode.id, {
|
|
3166
|
+
status: "blocked",
|
|
3167
|
+
});
|
|
3168
|
+
}
|
|
3169
|
+
catch {
|
|
3170
|
+
// best effort
|
|
3171
|
+
}
|
|
3172
|
+
await syncParentRollupsForTask({
|
|
3173
|
+
initiativeId: run.initiativeId,
|
|
3174
|
+
taskId: nextTaskNode.id,
|
|
3175
|
+
workstreamId: nextTaskNode.workstreamId,
|
|
3176
|
+
milestoneId: nextTaskNode.milestoneId,
|
|
3177
|
+
correlationId: sessionId,
|
|
3178
|
+
});
|
|
3179
|
+
await emitActivitySafe({
|
|
3180
|
+
initiativeId: run.initiativeId,
|
|
3181
|
+
correlationId: sessionId,
|
|
3182
|
+
phase: "blocked",
|
|
3183
|
+
level: "error",
|
|
3184
|
+
message: `Auto-continue blocked by spawn guard on task ${nextTaskNode.id}.`,
|
|
3185
|
+
metadata: {
|
|
3186
|
+
event: "auto_continue_spawn_guard_blocked",
|
|
3187
|
+
task_id: nextTaskNode.id,
|
|
3188
|
+
task_title: nextTaskNode.title,
|
|
3189
|
+
domain: executionPolicy.domain,
|
|
3190
|
+
required_skills: executionPolicy.requiredSkills,
|
|
3191
|
+
blocked_reason: blockedReason,
|
|
3192
|
+
spawn_guard: spawnGuardResult,
|
|
3193
|
+
},
|
|
3194
|
+
});
|
|
3195
|
+
await requestDecisionSafe({
|
|
3196
|
+
initiativeId: run.initiativeId,
|
|
3197
|
+
correlationId: sessionId,
|
|
3198
|
+
title: `Unblock auto-continue task ${nextTaskNode.title}`,
|
|
3199
|
+
summary: [
|
|
3200
|
+
`Task ${nextTaskNode.id} failed spawn guard checks.`,
|
|
3201
|
+
`Reason: ${blockedReason}`,
|
|
3202
|
+
`Domain: ${executionPolicy.domain}`,
|
|
3203
|
+
`Required skills: ${executionPolicy.requiredSkills.join(", ")}`,
|
|
3204
|
+
].join(" "),
|
|
3205
|
+
urgency: "high",
|
|
3206
|
+
options: [
|
|
3207
|
+
"Approve exception and continue",
|
|
3208
|
+
"Reassign task/domain",
|
|
3209
|
+
"Pause and investigate quality gate",
|
|
3210
|
+
],
|
|
3211
|
+
blocking: true,
|
|
3212
|
+
});
|
|
3213
|
+
await stopAutoContinueRun({
|
|
3214
|
+
run,
|
|
3215
|
+
reason: "blocked",
|
|
3216
|
+
error: blockedReason,
|
|
3217
|
+
});
|
|
3218
|
+
return;
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
2704
3221
|
const message = [
|
|
2705
3222
|
initiativeNode ? `Initiative: ${initiativeNode.title}` : null,
|
|
2706
3223
|
workstreamTitle ? `Workstream: ${workstreamTitle}` : null,
|
|
2707
3224
|
milestoneTitle ? `Milestone: ${milestoneTitle}` : null,
|
|
2708
3225
|
"",
|
|
2709
3226
|
`Task: ${nextTaskNode.title}`,
|
|
3227
|
+
`Execution policy: ${executionPolicy.domain}`,
|
|
3228
|
+
`Required skills: ${executionPolicy.requiredSkills.map((skill) => `$${skill}`).join(", ")}`,
|
|
2710
3229
|
"",
|
|
2711
3230
|
"Execute this task. When finished, provide a concise completion summary and any relevant commands/notes.",
|
|
2712
3231
|
]
|
|
2713
3232
|
.filter((line) => typeof line === "string")
|
|
2714
3233
|
.join("\n");
|
|
2715
|
-
if (
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
|
|
2723
|
-
|
|
2724
|
-
|
|
2725
|
-
catch {
|
|
2726
|
-
// best effort
|
|
2727
|
-
}
|
|
3234
|
+
if (workstreamNode &&
|
|
3235
|
+
!isInProgressStatus(workstreamNode.status) &&
|
|
3236
|
+
isDispatchableWorkstreamStatus(workstreamNode.status)) {
|
|
3237
|
+
try {
|
|
3238
|
+
await client.updateEntity("workstream", workstreamNode.id, {
|
|
3239
|
+
status: "active",
|
|
3240
|
+
});
|
|
3241
|
+
}
|
|
3242
|
+
catch {
|
|
3243
|
+
// best effort
|
|
2728
3244
|
}
|
|
2729
3245
|
}
|
|
2730
3246
|
try {
|
|
@@ -2763,6 +3279,11 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2763
3279
|
workstream_title: workstreamTitle,
|
|
2764
3280
|
milestone_id: nextTaskNode.milestoneId,
|
|
2765
3281
|
milestone_title: milestoneTitle,
|
|
3282
|
+
domain: executionPolicy.domain,
|
|
3283
|
+
required_skills: executionPolicy.requiredSkills,
|
|
3284
|
+
spawn_guard_model_tier: spawnGuardResult && typeof spawnGuardResult === "object"
|
|
3285
|
+
? pickString(spawnGuardResult, ["modelTier", "model_tier"]) ?? null
|
|
3286
|
+
: null,
|
|
2766
3287
|
},
|
|
2767
3288
|
});
|
|
2768
3289
|
upsertAgentContext({
|
|
@@ -3580,6 +4101,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3580
4101
|
searchParams.get("model_id") ??
|
|
3581
4102
|
"")
|
|
3582
4103
|
.trim() || null;
|
|
4104
|
+
const routingProvider = provider ?? (!provider && !requestedModel ? resolveAutoOpenClawProvider() : null);
|
|
3583
4105
|
const dryRunRaw = payload.dryRun ??
|
|
3584
4106
|
payload.dry_run ??
|
|
3585
4107
|
searchParams.get("dryRun") ??
|
|
@@ -3588,7 +4110,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3588
4110
|
const dryRun = typeof dryRunRaw === "boolean"
|
|
3589
4111
|
? dryRunRaw
|
|
3590
4112
|
: parseBooleanQuery(typeof dryRunRaw === "string" ? dryRunRaw : null);
|
|
3591
|
-
let requiresPremiumLaunch = Boolean(
|
|
4113
|
+
let requiresPremiumLaunch = Boolean(routingProvider) || modelImpliesByok(requestedModel);
|
|
3592
4114
|
if (!requiresPremiumLaunch) {
|
|
3593
4115
|
try {
|
|
3594
4116
|
const agents = await listAgents();
|
|
@@ -3628,12 +4150,23 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3628
4150
|
searchParams.get("text") ??
|
|
3629
4151
|
"")
|
|
3630
4152
|
.trim();
|
|
3631
|
-
const
|
|
4153
|
+
const baseMessage = messageInput ||
|
|
3632
4154
|
(initiativeTitle
|
|
3633
4155
|
? `Kick off: ${initiativeTitle}`
|
|
3634
4156
|
: initiativeId
|
|
3635
4157
|
? `Kick off initiative ${initiativeId}`
|
|
3636
4158
|
: `Kick off agent ${agentId}`);
|
|
4159
|
+
const policyResolution = await resolveDispatchExecutionPolicy({
|
|
4160
|
+
initiativeId,
|
|
4161
|
+
initiativeTitle,
|
|
4162
|
+
workstreamId,
|
|
4163
|
+
taskId,
|
|
4164
|
+
message: baseMessage,
|
|
4165
|
+
});
|
|
4166
|
+
const executionPolicy = policyResolution.executionPolicy;
|
|
4167
|
+
const resolvedTaskTitle = policyResolution.taskTitle;
|
|
4168
|
+
const resolvedWorkstreamTitle = policyResolution.workstreamTitle ??
|
|
4169
|
+
(workstreamId ? `Workstream ${workstreamId}` : null);
|
|
3637
4170
|
if (dryRun) {
|
|
3638
4171
|
sendJson(res, 200, {
|
|
3639
4172
|
ok: true,
|
|
@@ -3643,11 +4176,48 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3643
4176
|
workstreamId,
|
|
3644
4177
|
taskId,
|
|
3645
4178
|
requiresPremiumLaunch,
|
|
4179
|
+
provider: routingProvider,
|
|
4180
|
+
model: requestedModel,
|
|
3646
4181
|
startedAt: new Date().toISOString(),
|
|
3647
|
-
message,
|
|
4182
|
+
message: baseMessage,
|
|
4183
|
+
domain: executionPolicy.domain,
|
|
4184
|
+
requiredSkills: executionPolicy.requiredSkills,
|
|
3648
4185
|
});
|
|
3649
4186
|
return true;
|
|
3650
4187
|
}
|
|
4188
|
+
const guard = await enforceSpawnGuardForDispatch({
|
|
4189
|
+
sourceEventPrefix: "agent_launch",
|
|
4190
|
+
initiativeId,
|
|
4191
|
+
correlationId: sessionId,
|
|
4192
|
+
executionPolicy,
|
|
4193
|
+
agentId,
|
|
4194
|
+
taskId,
|
|
4195
|
+
taskTitle: resolvedTaskTitle,
|
|
4196
|
+
workstreamId,
|
|
4197
|
+
workstreamTitle: resolvedWorkstreamTitle,
|
|
4198
|
+
});
|
|
4199
|
+
if (!guard.allowed) {
|
|
4200
|
+
sendJson(res, guard.retryable ? 429 : 409, {
|
|
4201
|
+
ok: false,
|
|
4202
|
+
code: guard.retryable
|
|
4203
|
+
? "spawn_guard_rate_limited"
|
|
4204
|
+
: "spawn_guard_blocked",
|
|
4205
|
+
error: guard.blockedReason ??
|
|
4206
|
+
"Spawn guard denied this agent launch.",
|
|
4207
|
+
retryable: guard.retryable,
|
|
4208
|
+
initiativeId,
|
|
4209
|
+
workstreamId,
|
|
4210
|
+
taskId,
|
|
4211
|
+
domain: executionPolicy.domain,
|
|
4212
|
+
requiredSkills: executionPolicy.requiredSkills,
|
|
4213
|
+
});
|
|
4214
|
+
return true;
|
|
4215
|
+
}
|
|
4216
|
+
const message = buildPolicyEnforcedMessage({
|
|
4217
|
+
baseMessage,
|
|
4218
|
+
executionPolicy,
|
|
4219
|
+
spawnGuardResult: guard.spawnGuardResult,
|
|
4220
|
+
});
|
|
3651
4221
|
if (initiativeId) {
|
|
3652
4222
|
try {
|
|
3653
4223
|
await client.updateEntity("initiative", initiativeId, { status: "active" });
|
|
@@ -3684,16 +4254,19 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3684
4254
|
session_id: sessionId,
|
|
3685
4255
|
workstream_id: workstreamId,
|
|
3686
4256
|
task_id: taskId,
|
|
3687
|
-
provider,
|
|
4257
|
+
provider: routingProvider,
|
|
3688
4258
|
model: requestedModel,
|
|
4259
|
+
domain: executionPolicy.domain,
|
|
4260
|
+
required_skills: executionPolicy.requiredSkills,
|
|
4261
|
+
spawn_guard_model_tier: extractSpawnGuardModelTier(guard.spawnGuardResult),
|
|
3689
4262
|
},
|
|
3690
4263
|
});
|
|
3691
4264
|
let routedProvider = null;
|
|
3692
4265
|
let routedModel = null;
|
|
3693
|
-
if (
|
|
4266
|
+
if (routingProvider) {
|
|
3694
4267
|
const routed = await configureOpenClawProviderRouting({
|
|
3695
4268
|
agentId,
|
|
3696
|
-
provider,
|
|
4269
|
+
provider: routingProvider,
|
|
3697
4270
|
requestedModel,
|
|
3698
4271
|
});
|
|
3699
4272
|
routedProvider = routed.provider;
|
|
@@ -3737,6 +4310,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3737
4310
|
workstreamId,
|
|
3738
4311
|
taskId,
|
|
3739
4312
|
startedAt: new Date().toISOString(),
|
|
4313
|
+
domain: executionPolicy.domain,
|
|
4314
|
+
requiredSkills: executionPolicy.requiredSkills,
|
|
3740
4315
|
});
|
|
3741
4316
|
}
|
|
3742
4317
|
catch (err) {
|
|
@@ -3825,7 +4400,9 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3825
4400
|
record.model ??
|
|
3826
4401
|
"")
|
|
3827
4402
|
.trim() || null;
|
|
3828
|
-
|
|
4403
|
+
const routingProvider = providerOverride ??
|
|
4404
|
+
(!providerOverride && !requestedModel ? resolveAutoOpenClawProvider() : null);
|
|
4405
|
+
let requiresPremiumRestart = Boolean(routingProvider) ||
|
|
3829
4406
|
modelImpliesByok(requestedModel) ||
|
|
3830
4407
|
modelImpliesByok(record.model ?? null);
|
|
3831
4408
|
if (!requiresPremiumRestart) {
|
|
@@ -3861,13 +4438,52 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3861
4438
|
}
|
|
3862
4439
|
}
|
|
3863
4440
|
const sessionId = randomUUID();
|
|
3864
|
-
const
|
|
3865
|
-
|
|
4441
|
+
const baseMessage = messageOverride ?? record.message ?? `Restart agent ${record.agentId}`;
|
|
4442
|
+
const policyResolution = await resolveDispatchExecutionPolicy({
|
|
4443
|
+
initiativeId: record.initiativeId,
|
|
4444
|
+
initiativeTitle: record.initiativeTitle,
|
|
4445
|
+
workstreamId: record.workstreamId,
|
|
4446
|
+
taskId: record.taskId,
|
|
4447
|
+
message: baseMessage,
|
|
4448
|
+
});
|
|
4449
|
+
const executionPolicy = policyResolution.executionPolicy;
|
|
4450
|
+
const guard = await enforceSpawnGuardForDispatch({
|
|
4451
|
+
sourceEventPrefix: "agent_restart",
|
|
4452
|
+
initiativeId: record.initiativeId,
|
|
4453
|
+
correlationId: sessionId,
|
|
4454
|
+
executionPolicy,
|
|
4455
|
+
agentId: record.agentId,
|
|
4456
|
+
taskId: record.taskId,
|
|
4457
|
+
taskTitle: policyResolution.taskTitle,
|
|
4458
|
+
workstreamId: record.workstreamId,
|
|
4459
|
+
workstreamTitle: policyResolution.workstreamTitle,
|
|
4460
|
+
});
|
|
4461
|
+
if (!guard.allowed) {
|
|
4462
|
+
sendJson(res, guard.retryable ? 429 : 409, {
|
|
4463
|
+
ok: false,
|
|
4464
|
+
code: guard.retryable
|
|
4465
|
+
? "spawn_guard_rate_limited"
|
|
4466
|
+
: "spawn_guard_blocked",
|
|
4467
|
+
error: guard.blockedReason ??
|
|
4468
|
+
"Spawn guard denied this restart.",
|
|
4469
|
+
retryable: guard.retryable,
|
|
4470
|
+
previousRunId,
|
|
4471
|
+
domain: executionPolicy.domain,
|
|
4472
|
+
requiredSkills: executionPolicy.requiredSkills,
|
|
4473
|
+
});
|
|
4474
|
+
return true;
|
|
4475
|
+
}
|
|
4476
|
+
const message = buildPolicyEnforcedMessage({
|
|
4477
|
+
baseMessage,
|
|
4478
|
+
executionPolicy,
|
|
4479
|
+
spawnGuardResult: guard.spawnGuardResult,
|
|
4480
|
+
});
|
|
4481
|
+
let routedProvider = routingProvider ?? null;
|
|
3866
4482
|
let routedModel = requestedModel ?? null;
|
|
3867
|
-
if (
|
|
4483
|
+
if (routingProvider) {
|
|
3868
4484
|
const routed = await configureOpenClawProviderRouting({
|
|
3869
4485
|
agentId: record.agentId,
|
|
3870
|
-
provider:
|
|
4486
|
+
provider: routingProvider,
|
|
3871
4487
|
requestedModel,
|
|
3872
4488
|
});
|
|
3873
4489
|
routedProvider = routed.provider;
|
|
@@ -3907,6 +4523,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3907
4523
|
pid: spawned.pid,
|
|
3908
4524
|
provider: routedProvider,
|
|
3909
4525
|
model: routedModel,
|
|
4526
|
+
domain: executionPolicy.domain,
|
|
4527
|
+
requiredSkills: executionPolicy.requiredSkills,
|
|
3910
4528
|
});
|
|
3911
4529
|
}
|
|
3912
4530
|
catch (err) {
|
|
@@ -4007,24 +4625,33 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4007
4625
|
agentId,
|
|
4008
4626
|
});
|
|
4009
4627
|
}
|
|
4628
|
+
const fallbackStarted = Boolean(fallbackDispatch?.sessionId);
|
|
4010
4629
|
const dispatchMode = run.activeRunId
|
|
4011
4630
|
? "task"
|
|
4012
|
-
:
|
|
4631
|
+
: fallbackStarted
|
|
4013
4632
|
? "fallback"
|
|
4014
4633
|
: "none";
|
|
4015
4634
|
if (dispatchMode === "none") {
|
|
4016
|
-
const
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
? "No
|
|
4020
|
-
:
|
|
4021
|
-
|
|
4635
|
+
const fallbackBlockedReason = fallbackDispatch?.blockedReason ?? null;
|
|
4636
|
+
const reason = fallbackBlockedReason ??
|
|
4637
|
+
(run.stopReason === "blocked"
|
|
4638
|
+
? "No dispatchable task is ready for this workstream yet."
|
|
4639
|
+
: run.stopReason === "completed"
|
|
4640
|
+
? "No queued task is available for this workstream."
|
|
4641
|
+
: "Unable to dispatch this workstream right now.");
|
|
4642
|
+
sendJson(res, fallbackDispatch?.retryable ? 429 : 409, {
|
|
4022
4643
|
ok: false,
|
|
4644
|
+
code: fallbackBlockedReason
|
|
4645
|
+
? fallbackDispatch?.retryable
|
|
4646
|
+
? "spawn_guard_rate_limited"
|
|
4647
|
+
: "spawn_guard_blocked"
|
|
4648
|
+
: undefined,
|
|
4023
4649
|
error: reason,
|
|
4024
4650
|
run,
|
|
4025
4651
|
initiativeId,
|
|
4026
4652
|
workstreamId,
|
|
4027
4653
|
agentId,
|
|
4654
|
+
fallbackDispatch,
|
|
4028
4655
|
});
|
|
4029
4656
|
return true;
|
|
4030
4657
|
}
|