@useorgx/openclaw-plugin 0.4.0 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/README.md +4 -0
  2. package/dashboard/dist/assets/BqukHQH-.js +8 -0
  3. package/dashboard/dist/assets/{motion-x9c01cgK.js → CE5pVdev.js} +1 -1
  4. package/dashboard/dist/assets/Cpr7n8fE.js +1 -0
  5. package/dashboard/dist/assets/Nip3CrNC.js +1 -0
  6. package/dashboard/dist/assets/TN5wE36J.js +1 -0
  7. package/dashboard/dist/assets/X6IcjS74.js +212 -0
  8. package/dashboard/dist/assets/jyFhCND-.css +1 -0
  9. package/dashboard/dist/index.html +9 -6
  10. package/dist/adapters/outbox.d.ts +0 -1
  11. package/dist/adapters/outbox.js +0 -1
  12. package/dist/agent-context-store.d.ts +0 -1
  13. package/dist/agent-context-store.js +0 -1
  14. package/dist/agent-run-store.d.ts +0 -1
  15. package/dist/agent-run-store.js +0 -1
  16. package/dist/api.d.ts +0 -1
  17. package/dist/api.js +0 -1
  18. package/dist/auth-store.d.ts +0 -1
  19. package/dist/auth-store.js +0 -1
  20. package/dist/byok-store.d.ts +0 -1
  21. package/dist/byok-store.js +181 -55
  22. package/dist/contracts/client.d.ts +0 -1
  23. package/dist/contracts/client.js +0 -1
  24. package/dist/contracts/types.d.ts +0 -1
  25. package/dist/contracts/types.js +0 -1
  26. package/dist/dashboard-api.d.ts +0 -1
  27. package/dist/dashboard-api.js +0 -1
  28. package/dist/fs-utils.d.ts +0 -1
  29. package/dist/fs-utils.js +0 -1
  30. package/dist/gateway-watchdog-runner.d.ts +1 -0
  31. package/dist/gateway-watchdog-runner.js +6 -0
  32. package/dist/gateway-watchdog.d.ts +11 -0
  33. package/dist/gateway-watchdog.js +221 -0
  34. package/dist/http-handler.d.ts +0 -1
  35. package/dist/http-handler.js +729 -79
  36. package/dist/index.d.ts +0 -1
  37. package/dist/index.js +7 -1
  38. package/dist/local-openclaw.d.ts +0 -1
  39. package/dist/local-openclaw.js +0 -1
  40. package/dist/openclaw-settings.d.ts +17 -0
  41. package/dist/openclaw-settings.js +118 -0
  42. package/dist/outbox.d.ts +0 -1
  43. package/dist/outbox.js +0 -1
  44. package/dist/paths.d.ts +0 -1
  45. package/dist/paths.js +0 -1
  46. package/dist/reporting/outbox-replay.d.ts +0 -1
  47. package/dist/reporting/outbox-replay.js +0 -1
  48. package/dist/reporting/rollups.d.ts +0 -1
  49. package/dist/reporting/rollups.js +0 -1
  50. package/dist/runtime-instance-store.d.ts +0 -1
  51. package/dist/runtime-instance-store.js +0 -1
  52. package/dist/snapshot-store.d.ts +0 -1
  53. package/dist/snapshot-store.js +0 -1
  54. package/dist/types.d.ts +0 -1
  55. package/dist/types.js +0 -1
  56. package/package.json +2 -2
  57. package/dashboard/dist/assets/MissionControlView-DVNfDWKZ.js +0 -1
  58. package/dashboard/dist/assets/SessionInspector-BaqnAys4.js +0 -1
  59. package/dashboard/dist/assets/index-B4Yix84X.js +0 -212
  60. package/dashboard/dist/assets/index-BWSvw1HR.css +0 -1
  61. package/dashboard/dist/assets/react-vendor-C2t2w4r2.js +0 -32
  62. package/dashboard/dist/assets/vendor-C-AHK0Ly.js +0 -9
  63. package/dist/adapters/outbox.d.ts.map +0 -1
  64. package/dist/adapters/outbox.js.map +0 -1
  65. package/dist/agent-context-store.d.ts.map +0 -1
  66. package/dist/agent-context-store.js.map +0 -1
  67. package/dist/agent-run-store.d.ts.map +0 -1
  68. package/dist/agent-run-store.js.map +0 -1
  69. package/dist/api.d.ts.map +0 -1
  70. package/dist/api.js.map +0 -1
  71. package/dist/auth-store.d.ts.map +0 -1
  72. package/dist/auth-store.js.map +0 -1
  73. package/dist/byok-store.d.ts.map +0 -1
  74. package/dist/byok-store.js.map +0 -1
  75. package/dist/contracts/client.d.ts.map +0 -1
  76. package/dist/contracts/client.js.map +0 -1
  77. package/dist/contracts/types.d.ts.map +0 -1
  78. package/dist/contracts/types.js.map +0 -1
  79. package/dist/dashboard-api.d.ts.map +0 -1
  80. package/dist/dashboard-api.js.map +0 -1
  81. package/dist/fs-utils.d.ts.map +0 -1
  82. package/dist/fs-utils.js.map +0 -1
  83. package/dist/http-handler.d.ts.map +0 -1
  84. package/dist/http-handler.js.map +0 -1
  85. package/dist/index.d.ts.map +0 -1
  86. package/dist/index.js.map +0 -1
  87. package/dist/local-openclaw.d.ts.map +0 -1
  88. package/dist/local-openclaw.js.map +0 -1
  89. package/dist/mcp-apps/orgx-live.html +0 -690
  90. package/dist/outbox.d.ts.map +0 -1
  91. package/dist/outbox.js.map +0 -1
  92. package/dist/paths.d.ts.map +0 -1
  93. package/dist/paths.js.map +0 -1
  94. package/dist/reporting/outbox-replay.d.ts.map +0 -1
  95. package/dist/reporting/outbox-replay.js.map +0 -1
  96. package/dist/reporting/rollups.d.ts.map +0 -1
  97. package/dist/reporting/rollups.js.map +0 -1
  98. package/dist/runtime-instance-store.d.ts.map +0 -1
  99. package/dist/runtime-instance-store.js.map +0 -1
  100. package/dist/snapshot-store.d.ts.map +0 -1
  101. package/dist/snapshot-store.js.map +0 -1
  102. package/dist/types.d.ts.map +0 -1
  103. package/dist/types.js.map +0 -1
  104. /package/dashboard/dist/assets/{tanstack-C-KIc3Wc.js → C-KIc3Wc.js} +0 -0
  105. /package/dashboard/dist/assets/{orgx-logo-Fm0FhtnV.png → Fm0FhtnV.png} +0 -0
@@ -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 result = await runCommandCollect({
207
- command: "openclaw",
208
- args: [
209
- "models",
210
- "--agent",
211
- input.agentId,
212
- "list",
213
- "--provider",
214
- input.provider,
215
- "--json",
216
- ],
217
- timeoutMs: 10_000,
218
- env: resolveByokEnvOverrides(),
219
- });
220
- if (result.exitCode !== 0) {
221
- throw new Error(result.stderr.trim() || "openclaw models list failed");
222
- }
223
- const parsed = parseJsonSafe(result.stdout);
224
- if (!parsed || typeof parsed !== "object") {
225
- const trimmed = result.stdout.trim();
226
- if (!trimmed || /no models found/i.test(trimmed)) {
227
- return [];
228
- }
229
- throw new Error("openclaw models list returned invalid JSON");
230
- }
231
- const modelsRaw = "models" in parsed && Array.isArray(parsed.models)
232
- ? parsed.models
233
- : [];
234
- return modelsRaw
235
- .map((entry) => {
236
- if (!entry || typeof entry !== "object")
237
- return null;
238
- const row = entry;
239
- const key = typeof row.key === "string" ? row.key.trim() : "";
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
- if (!key)
244
- return null;
245
- return { key, tags };
246
- })
247
- .filter((entry) => Boolean(entry));
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: "opus",
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;
@@ -808,11 +833,30 @@ const CORS_HEADERS = {
808
833
  "Access-Control-Allow-Headers": "Content-Type, Authorization, X-OrgX-Api-Key, X-API-Key, X-OrgX-User-Id, X-OrgX-Hook-Token, X-Hook-Token",
809
834
  Vary: "Origin",
810
835
  };
836
+ const CONTENT_SECURITY_POLICY = [
837
+ "default-src 'self'",
838
+ "base-uri 'self'",
839
+ "frame-ancestors 'none'",
840
+ "form-action 'self'",
841
+ "object-src 'none'",
842
+ "script-src 'self'",
843
+ "style-src 'self' 'unsafe-inline'",
844
+ "img-src 'self' data: blob:",
845
+ "font-src 'self' data:",
846
+ "media-src 'self'",
847
+ "connect-src 'self' https://*.useorgx.com https://*.openclaw.ai http://127.0.0.1:* http://localhost:*",
848
+ ].join("; ");
811
849
  const SECURITY_HEADERS = {
812
850
  "X-Content-Type-Options": "nosniff",
813
851
  "X-Frame-Options": "DENY",
814
852
  "Referrer-Policy": "same-origin",
853
+ "X-Robots-Tag": "noindex, nofollow, noarchive, nosnippet, noimageindex",
854
+ "Permissions-Policy": "camera=(), microphone=(), geolocation=(), payment=(), usb=(), midi=(), magnetometer=(), gyroscope=()",
855
+ "Cross-Origin-Opener-Policy": "same-origin",
815
856
  "Cross-Origin-Resource-Policy": "same-origin",
857
+ "Origin-Agent-Cluster": "?1",
858
+ "X-Permitted-Cross-Domain-Policies": "none",
859
+ "Content-Security-Policy": CONTENT_SECURITY_POLICY,
816
860
  };
817
861
  function normalizeHost(value) {
818
862
  return value.trim().toLowerCase().replace(/^\[|\]$/g, "");
@@ -1215,6 +1259,86 @@ function idempotencyKey(parts) {
1215
1259
  const suffix = stableHash(raw).slice(0, 20);
1216
1260
  return `${cleaned}:${suffix}`.slice(0, 120);
1217
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
+ }
1218
1342
  const DEFAULT_DURATION_HOURS = {
1219
1343
  initiative: 40,
1220
1344
  workstream: 16,
@@ -2106,6 +2230,263 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
2106
2230
  }
2107
2231
  }
2108
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
+ }
2109
2490
  async function syncParentRollupsForTask(input) {
2110
2491
  const initiativeId = input.initiativeId?.trim() ?? "";
2111
2492
  const taskId = input.taskId?.trim() ?? "";
@@ -2445,25 +2826,61 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
2445
2826
  async function dispatchFallbackWorkstreamTurn(input) {
2446
2827
  const now = new Date().toISOString();
2447
2828
  const sessionId = randomUUID();
2448
- const message = [
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 = [
2449
2858
  `Initiative: ${input.initiativeTitle}`,
2450
- `Workstream: ${input.workstreamTitle}`,
2859
+ `Workstream: ${resolvedWorkstreamTitle}`,
2451
2860
  "",
2452
2861
  "Continue this workstream from the latest context.",
2453
2862
  "Identify and execute the next concrete task, then provide a concise progress summary.",
2454
2863
  ].join("\n");
2864
+ const message = buildPolicyEnforcedMessage({
2865
+ baseMessage,
2866
+ executionPolicy,
2867
+ spawnGuardResult: guard.spawnGuardResult,
2868
+ });
2455
2869
  await emitActivitySafe({
2456
2870
  initiativeId: input.initiativeId,
2457
2871
  correlationId: sessionId,
2458
2872
  phase: "execution",
2459
2873
  level: "info",
2460
- message: `Next Up dispatched ${input.workstreamTitle}.`,
2874
+ message: `Next Up dispatched ${resolvedWorkstreamTitle}.`,
2461
2875
  metadata: {
2462
2876
  event: "next_up_manual_dispatch_started",
2463
2877
  agent_id: input.agentId,
2464
2878
  session_id: sessionId,
2465
2879
  workstream_id: input.workstreamId,
2466
- workstream_title: input.workstreamTitle,
2880
+ workstream_title: resolvedWorkstreamTitle,
2881
+ domain: executionPolicy.domain,
2882
+ required_skills: executionPolicy.requiredSkills,
2883
+ spawn_guard_model_tier: extractSpawnGuardModelTier(guard.spawnGuardResult),
2467
2884
  fallback: true,
2468
2885
  },
2469
2886
  });
@@ -2493,7 +2910,14 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
2493
2910
  startedAt: now,
2494
2911
  status: "running",
2495
2912
  });
2496
- return { sessionId, pid: spawned.pid };
2913
+ return {
2914
+ sessionId,
2915
+ pid: spawned.pid,
2916
+ blockedReason: null,
2917
+ retryable: false,
2918
+ executionPolicy,
2919
+ spawnGuardResult: guard.spawnGuardResult,
2920
+ };
2497
2921
  }
2498
2922
  async function tickAutoContinueRun(run) {
2499
2923
  if (run.status !== "running" && run.status !== "stopping")
@@ -2560,6 +2984,27 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
2560
2984
  error_message: summary.errorMessage,
2561
2985
  },
2562
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
+ }
2563
3008
  run.lastRunId = record.runId;
2564
3009
  run.lastTaskId = record.taskId ?? run.lastTaskId;
2565
3010
  run.activeRunId = null;
@@ -2682,30 +3127,120 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
2682
3127
  const milestoneTitle = nextTaskNode.milestoneId
2683
3128
  ? nodeById.get(nextTaskNode.milestoneId)?.title ?? null
2684
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
+ }
2685
3221
  const message = [
2686
3222
  initiativeNode ? `Initiative: ${initiativeNode.title}` : null,
2687
3223
  workstreamTitle ? `Workstream: ${workstreamTitle}` : null,
2688
3224
  milestoneTitle ? `Milestone: ${milestoneTitle}` : null,
2689
3225
  "",
2690
3226
  `Task: ${nextTaskNode.title}`,
3227
+ `Execution policy: ${executionPolicy.domain}`,
3228
+ `Required skills: ${executionPolicy.requiredSkills.map((skill) => `$${skill}`).join(", ")}`,
2691
3229
  "",
2692
3230
  "Execute this task. When finished, provide a concise completion summary and any relevant commands/notes.",
2693
3231
  ]
2694
3232
  .filter((line) => typeof line === "string")
2695
3233
  .join("\n");
2696
- if (nextTaskNode.workstreamId) {
2697
- const workstreamNode = nodeById.get(nextTaskNode.workstreamId);
2698
- if (workstreamNode &&
2699
- !isInProgressStatus(workstreamNode.status) &&
2700
- isDispatchableWorkstreamStatus(workstreamNode.status)) {
2701
- try {
2702
- await client.updateEntity("workstream", workstreamNode.id, {
2703
- status: "active",
2704
- });
2705
- }
2706
- catch {
2707
- // best effort
2708
- }
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
2709
3244
  }
2710
3245
  }
2711
3246
  try {
@@ -2744,6 +3279,11 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
2744
3279
  workstream_title: workstreamTitle,
2745
3280
  milestone_id: nextTaskNode.milestoneId,
2746
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,
2747
3287
  },
2748
3288
  });
2749
3289
  upsertAgentContext({
@@ -3561,6 +4101,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3561
4101
  searchParams.get("model_id") ??
3562
4102
  "")
3563
4103
  .trim() || null;
4104
+ const routingProvider = provider ?? (!provider && !requestedModel ? resolveAutoOpenClawProvider() : null);
3564
4105
  const dryRunRaw = payload.dryRun ??
3565
4106
  payload.dry_run ??
3566
4107
  searchParams.get("dryRun") ??
@@ -3569,7 +4110,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3569
4110
  const dryRun = typeof dryRunRaw === "boolean"
3570
4111
  ? dryRunRaw
3571
4112
  : parseBooleanQuery(typeof dryRunRaw === "string" ? dryRunRaw : null);
3572
- let requiresPremiumLaunch = Boolean(provider) || modelImpliesByok(requestedModel);
4113
+ let requiresPremiumLaunch = Boolean(routingProvider) || modelImpliesByok(requestedModel);
3573
4114
  if (!requiresPremiumLaunch) {
3574
4115
  try {
3575
4116
  const agents = await listAgents();
@@ -3609,12 +4150,23 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3609
4150
  searchParams.get("text") ??
3610
4151
  "")
3611
4152
  .trim();
3612
- const message = messageInput ||
4153
+ const baseMessage = messageInput ||
3613
4154
  (initiativeTitle
3614
4155
  ? `Kick off: ${initiativeTitle}`
3615
4156
  : initiativeId
3616
4157
  ? `Kick off initiative ${initiativeId}`
3617
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);
3618
4170
  if (dryRun) {
3619
4171
  sendJson(res, 200, {
3620
4172
  ok: true,
@@ -3624,11 +4176,48 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3624
4176
  workstreamId,
3625
4177
  taskId,
3626
4178
  requiresPremiumLaunch,
4179
+ provider: routingProvider,
4180
+ model: requestedModel,
3627
4181
  startedAt: new Date().toISOString(),
3628
- message,
4182
+ message: baseMessage,
4183
+ domain: executionPolicy.domain,
4184
+ requiredSkills: executionPolicy.requiredSkills,
4185
+ });
4186
+ return true;
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,
3629
4213
  });
3630
4214
  return true;
3631
4215
  }
4216
+ const message = buildPolicyEnforcedMessage({
4217
+ baseMessage,
4218
+ executionPolicy,
4219
+ spawnGuardResult: guard.spawnGuardResult,
4220
+ });
3632
4221
  if (initiativeId) {
3633
4222
  try {
3634
4223
  await client.updateEntity("initiative", initiativeId, { status: "active" });
@@ -3665,16 +4254,19 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3665
4254
  session_id: sessionId,
3666
4255
  workstream_id: workstreamId,
3667
4256
  task_id: taskId,
3668
- provider,
4257
+ provider: routingProvider,
3669
4258
  model: requestedModel,
4259
+ domain: executionPolicy.domain,
4260
+ required_skills: executionPolicy.requiredSkills,
4261
+ spawn_guard_model_tier: extractSpawnGuardModelTier(guard.spawnGuardResult),
3670
4262
  },
3671
4263
  });
3672
4264
  let routedProvider = null;
3673
4265
  let routedModel = null;
3674
- if (provider) {
4266
+ if (routingProvider) {
3675
4267
  const routed = await configureOpenClawProviderRouting({
3676
4268
  agentId,
3677
- provider,
4269
+ provider: routingProvider,
3678
4270
  requestedModel,
3679
4271
  });
3680
4272
  routedProvider = routed.provider;
@@ -3718,6 +4310,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3718
4310
  workstreamId,
3719
4311
  taskId,
3720
4312
  startedAt: new Date().toISOString(),
4313
+ domain: executionPolicy.domain,
4314
+ requiredSkills: executionPolicy.requiredSkills,
3721
4315
  });
3722
4316
  }
3723
4317
  catch (err) {
@@ -3806,7 +4400,9 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3806
4400
  record.model ??
3807
4401
  "")
3808
4402
  .trim() || null;
3809
- let requiresPremiumRestart = Boolean(providerOverride) ||
4403
+ const routingProvider = providerOverride ??
4404
+ (!providerOverride && !requestedModel ? resolveAutoOpenClawProvider() : null);
4405
+ let requiresPremiumRestart = Boolean(routingProvider) ||
3810
4406
  modelImpliesByok(requestedModel) ||
3811
4407
  modelImpliesByok(record.model ?? null);
3812
4408
  if (!requiresPremiumRestart) {
@@ -3842,13 +4438,52 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3842
4438
  }
3843
4439
  }
3844
4440
  const sessionId = randomUUID();
3845
- const message = messageOverride ?? record.message ?? `Restart agent ${record.agentId}`;
3846
- let routedProvider = providerOverride ?? null;
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;
3847
4482
  let routedModel = requestedModel ?? null;
3848
- if (providerOverride) {
4483
+ if (routingProvider) {
3849
4484
  const routed = await configureOpenClawProviderRouting({
3850
4485
  agentId: record.agentId,
3851
- provider: providerOverride,
4486
+ provider: routingProvider,
3852
4487
  requestedModel,
3853
4488
  });
3854
4489
  routedProvider = routed.provider;
@@ -3888,6 +4523,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3888
4523
  pid: spawned.pid,
3889
4524
  provider: routedProvider,
3890
4525
  model: routedModel,
4526
+ domain: executionPolicy.domain,
4527
+ requiredSkills: executionPolicy.requiredSkills,
3891
4528
  });
3892
4529
  }
3893
4530
  catch (err) {
@@ -3988,24 +4625,33 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
3988
4625
  agentId,
3989
4626
  });
3990
4627
  }
4628
+ const fallbackStarted = Boolean(fallbackDispatch?.sessionId);
3991
4629
  const dispatchMode = run.activeRunId
3992
4630
  ? "task"
3993
- : fallbackDispatch
4631
+ : fallbackStarted
3994
4632
  ? "fallback"
3995
4633
  : "none";
3996
4634
  if (dispatchMode === "none") {
3997
- const reason = run.stopReason === "blocked"
3998
- ? "No dispatchable task is ready for this workstream yet."
3999
- : run.stopReason === "completed"
4000
- ? "No queued task is available for this workstream."
4001
- : "Unable to dispatch this workstream right now.";
4002
- sendJson(res, 409, {
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, {
4003
4643
  ok: false,
4644
+ code: fallbackBlockedReason
4645
+ ? fallbackDispatch?.retryable
4646
+ ? "spawn_guard_rate_limited"
4647
+ : "spawn_guard_blocked"
4648
+ : undefined,
4004
4649
  error: reason,
4005
4650
  run,
4006
4651
  initiativeId,
4007
4652
  workstreamId,
4008
4653
  agentId,
4654
+ fallbackDispatch,
4009
4655
  });
4010
4656
  return true;
4011
4657
  }
@@ -5932,6 +6578,11 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
5932
6578
  // Requests under /orgx/live
5933
6579
  if (url === "/orgx/live" || url.startsWith("/orgx/live/")) {
5934
6580
  const subPath = url.replace(/^\/orgx\/live\/?/, "");
6581
+ // Never expose source maps in shipped plugin dashboards.
6582
+ if (/\.map$/i.test(subPath)) {
6583
+ send404(res);
6584
+ return true;
6585
+ }
5935
6586
  // Static assets: /orgx/live/assets/* → dashboard/dist/assets/*
5936
6587
  // Hashed filenames get long-lived cache
5937
6588
  if (subPath.startsWith("assets/")) {
@@ -5977,4 +6628,3 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
5977
6628
  return true;
5978
6629
  };
5979
6630
  }
5980
- //# sourceMappingURL=http-handler.js.map