@useorgx/openclaw-plugin 0.4.8 → 0.4.9

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 (125) hide show
  1. package/dashboard/dist/assets/B5NEElEI.css +1 -0
  2. package/dashboard/dist/assets/BhapSNAs.js +215 -0
  3. package/dashboard/dist/assets/{BNeJ0kpF.js → iFdvE7lx.js} +1 -1
  4. package/dashboard/dist/assets/{CUV9IHHi.js → jRJsmpYM.js} +1 -1
  5. package/dashboard/dist/index.html +2 -2
  6. package/dist/activity-store.js +4 -18
  7. package/dist/agent-context-store.js +5 -25
  8. package/dist/agent-run-store.js +5 -25
  9. package/dist/agent-suite.js +1 -8
  10. package/dist/auth/flows.d.ts +47 -0
  11. package/dist/auth/flows.js +169 -0
  12. package/dist/auth-store.js +6 -26
  13. package/dist/byok-store.js +5 -19
  14. package/dist/cli/orgx.d.ts +66 -0
  15. package/dist/cli/orgx.js +91 -0
  16. package/dist/config/refresh.d.ts +32 -0
  17. package/dist/config/refresh.js +55 -0
  18. package/dist/config/resolution.d.ts +37 -0
  19. package/dist/config/resolution.js +178 -0
  20. package/dist/contracts/shared-types.d.ts +147 -0
  21. package/dist/contracts/shared-types.js +3 -0
  22. package/dist/contracts/types.d.ts +1 -134
  23. package/dist/contracts/types.js +5 -0
  24. package/dist/entities/auto-assignment.d.ts +36 -0
  25. package/dist/entities/auto-assignment.js +115 -0
  26. package/dist/entity-comment-store.js +5 -25
  27. package/dist/hash-utils.d.ts +2 -0
  28. package/dist/hash-utils.js +12 -0
  29. package/dist/http/helpers/activity-headline.d.ts +10 -0
  30. package/dist/http/helpers/activity-headline.js +192 -0
  31. package/dist/http/helpers/artifact-fallback.d.ts +13 -0
  32. package/dist/http/helpers/artifact-fallback.js +148 -0
  33. package/dist/http/helpers/auto-continue-engine.d.ts +298 -0
  34. package/dist/http/helpers/auto-continue-engine.js +1218 -0
  35. package/dist/http/helpers/autopilot-operations.d.ts +157 -0
  36. package/dist/http/helpers/autopilot-operations.js +403 -0
  37. package/dist/http/helpers/autopilot-runtime.d.ts +42 -0
  38. package/dist/http/helpers/autopilot-runtime.js +319 -0
  39. package/dist/http/helpers/autopilot-slice-utils.d.ts +38 -0
  40. package/dist/http/helpers/autopilot-slice-utils.js +476 -0
  41. package/dist/http/helpers/decision-mapper.d.ts +12 -0
  42. package/dist/http/helpers/decision-mapper.js +44 -0
  43. package/dist/http/helpers/dispatch-lifecycle.d.ts +102 -0
  44. package/dist/http/helpers/dispatch-lifecycle.js +604 -0
  45. package/dist/http/helpers/hash-utils.d.ts +1 -0
  46. package/dist/http/helpers/hash-utils.js +1 -0
  47. package/dist/http/helpers/kickoff-context.d.ts +12 -0
  48. package/dist/http/helpers/kickoff-context.js +154 -0
  49. package/dist/http/helpers/mission-control.d.ts +94 -0
  50. package/dist/http/helpers/mission-control.js +894 -0
  51. package/dist/http/helpers/openclaw-cli.d.ts +37 -0
  52. package/dist/http/helpers/openclaw-cli.js +283 -0
  53. package/dist/http/helpers/runtime-sse.d.ts +20 -0
  54. package/dist/http/helpers/runtime-sse.js +110 -0
  55. package/dist/http/helpers/value-utils.d.ts +6 -0
  56. package/dist/http/helpers/value-utils.js +67 -0
  57. package/dist/http/index.d.ts +88 -0
  58. package/dist/http/index.js +2353 -0
  59. package/dist/http/router.d.ts +23 -0
  60. package/dist/http/router.js +23 -0
  61. package/dist/http/routes/agent-control.d.ts +79 -0
  62. package/dist/http/routes/agent-control.js +684 -0
  63. package/dist/http/routes/agent-suite.d.ts +29 -0
  64. package/dist/http/routes/agent-suite.js +198 -0
  65. package/dist/http/routes/agents-catalog.d.ts +40 -0
  66. package/dist/http/routes/agents-catalog.js +83 -0
  67. package/dist/http/routes/billing.d.ts +23 -0
  68. package/dist/http/routes/billing.js +55 -0
  69. package/dist/http/routes/debug.d.ts +14 -0
  70. package/dist/http/routes/debug.js +21 -0
  71. package/dist/http/routes/decision-actions.d.ts +13 -0
  72. package/dist/http/routes/decision-actions.js +66 -0
  73. package/dist/http/routes/delegation.d.ts +19 -0
  74. package/dist/http/routes/delegation.js +32 -0
  75. package/dist/http/routes/entities.d.ts +47 -0
  76. package/dist/http/routes/entities.js +152 -0
  77. package/dist/http/routes/entity-dynamic.d.ts +25 -0
  78. package/dist/http/routes/entity-dynamic.js +191 -0
  79. package/dist/http/routes/health.d.ts +22 -0
  80. package/dist/http/routes/health.js +49 -0
  81. package/dist/http/routes/live-legacy.d.ts +110 -0
  82. package/dist/http/routes/live-legacy.js +598 -0
  83. package/dist/http/routes/live-misc.d.ts +69 -0
  84. package/dist/http/routes/live-misc.js +206 -0
  85. package/dist/http/routes/live-snapshot.d.ts +90 -0
  86. package/dist/http/routes/live-snapshot.js +297 -0
  87. package/dist/http/routes/mission-control-actions.d.ts +83 -0
  88. package/dist/http/routes/mission-control-actions.js +541 -0
  89. package/dist/http/routes/mission-control-read.d.ts +28 -0
  90. package/dist/http/routes/mission-control-read.js +67 -0
  91. package/dist/http/routes/onboarding.d.ts +34 -0
  92. package/dist/http/routes/onboarding.js +101 -0
  93. package/dist/http/routes/run-control.d.ts +24 -0
  94. package/dist/http/routes/run-control.js +86 -0
  95. package/dist/http/routes/runtime-hooks.d.ts +69 -0
  96. package/dist/http/routes/runtime-hooks.js +437 -0
  97. package/dist/http/routes/settings-byok.d.ts +23 -0
  98. package/dist/http/routes/settings-byok.js +163 -0
  99. package/dist/http/routes/summary.d.ts +18 -0
  100. package/dist/http/routes/summary.js +42 -0
  101. package/dist/http/routes/work-artifacts.d.ts +9 -0
  102. package/dist/http/routes/work-artifacts.js +36 -0
  103. package/dist/http/shared-state.d.ts +16 -0
  104. package/dist/http/shared-state.js +1 -0
  105. package/dist/http-handler.d.ts +1 -88
  106. package/dist/http-handler.js +1 -10605
  107. package/dist/index.js +108 -2243
  108. package/dist/json-utils.d.ts +1 -0
  109. package/dist/json-utils.js +8 -0
  110. package/dist/next-up-queue-store.js +4 -18
  111. package/dist/runtime-instance-store.js +5 -31
  112. package/dist/services/background.d.ts +23 -0
  113. package/dist/services/background.js +23 -0
  114. package/dist/services/instrumentation.d.ts +29 -0
  115. package/dist/services/instrumentation.js +136 -0
  116. package/dist/snapshot-store.js +5 -25
  117. package/dist/stores/json-store.d.ts +11 -0
  118. package/dist/stores/json-store.js +42 -0
  119. package/dist/sync/outbox-replay.d.ts +55 -0
  120. package/dist/sync/outbox-replay.js +514 -0
  121. package/dist/tools/core-tools.d.ts +76 -0
  122. package/dist/tools/core-tools.js +1005 -0
  123. package/package.json +1 -1
  124. package/dashboard/dist/assets/BzkiMPmM.js +0 -215
  125. package/dashboard/dist/assets/Ie7d9Iq2.css +0 -1
@@ -0,0 +1,604 @@
1
+ import { appendToOutbox } from "../../outbox.js";
2
+ import { computeMilestoneRollup, computeWorkstreamRollup, } from "../../reporting/rollups.js";
3
+ import { readSkillPackState } from "../../skill-pack-state.js";
4
+ import { buildMissionControlGraph, deriveExecutionPolicy, inferExecutionDomainFromText, normalizeExecutionDomain, ORGX_SKILL_BY_DOMAIN, spawnGuardIsRateLimited, summarizeSpawnGuardBlockReason, } from "./mission-control.js";
5
+ export function createDispatchLifecycle(deps) {
6
+ function withProvenanceMetadata(metadata) {
7
+ const input = metadata ?? {};
8
+ const out = { ...input };
9
+ if (out.orgx_plugin_version === undefined) {
10
+ out.orgx_plugin_version = (deps.pluginVersion ?? "").trim() || null;
11
+ }
12
+ try {
13
+ const state = readSkillPackState();
14
+ const overrides = state.overrides;
15
+ if (out.skill_pack_name === undefined) {
16
+ out.skill_pack_name = overrides?.name ?? state.pack?.name ?? null;
17
+ }
18
+ if (out.skill_pack_version === undefined) {
19
+ out.skill_pack_version = overrides?.version ?? state.pack?.version ?? null;
20
+ }
21
+ if (out.skill_pack_checksum === undefined) {
22
+ out.skill_pack_checksum = overrides?.checksum ?? state.pack?.checksum ?? null;
23
+ }
24
+ if (out.skill_pack_source === undefined) {
25
+ out.skill_pack_source = overrides?.source ?? null;
26
+ }
27
+ if (out.skill_pack_etag === undefined) {
28
+ out.skill_pack_etag = state.etag ?? null;
29
+ }
30
+ if (out.skill_pack_policy_frozen === undefined) {
31
+ out.skill_pack_policy_frozen = Boolean(state.policy?.frozen);
32
+ }
33
+ if (out.skill_pack_policy_pinned_checksum === undefined) {
34
+ out.skill_pack_policy_pinned_checksum = state.policy?.pinnedChecksum ?? null;
35
+ }
36
+ }
37
+ catch {
38
+ // best effort
39
+ }
40
+ if (out.orgx_provenance === undefined) {
41
+ out.orgx_provenance = {
42
+ plugin_version: out.orgx_plugin_version ?? null,
43
+ skill_pack: {
44
+ name: out.skill_pack_name ?? null,
45
+ version: out.skill_pack_version ?? null,
46
+ checksum: out.skill_pack_checksum ?? null,
47
+ source: out.skill_pack_source ?? null,
48
+ etag: out.skill_pack_etag ?? null,
49
+ },
50
+ policy: {
51
+ frozen: out.skill_pack_policy_frozen ?? null,
52
+ pinned_checksum: out.skill_pack_policy_pinned_checksum ?? null,
53
+ },
54
+ };
55
+ }
56
+ return Object.keys(out).length > 0 ? out : undefined;
57
+ }
58
+ async function emitActivitySafe(input) {
59
+ const initiativeId = input.initiativeId?.trim() ?? "";
60
+ if (!initiativeId)
61
+ return;
62
+ const message = input.message.trim();
63
+ if (!message)
64
+ return;
65
+ const metadataWithProvenance = withProvenanceMetadata(input.metadata);
66
+ const activityBucket = deps.deriveStructuredActivityBucket({
67
+ phase: input.phase,
68
+ metadata: metadataWithProvenance,
69
+ explicitBucket: input.activityBucket ?? null,
70
+ });
71
+ const enrichedMetadata = {
72
+ ...(metadataWithProvenance ?? {}),
73
+ activity_bucket: activityBucket,
74
+ };
75
+ try {
76
+ await deps.client.emitActivity({
77
+ initiative_id: initiativeId,
78
+ run_id: input.runId ?? undefined,
79
+ correlation_id: input.runId
80
+ ? undefined
81
+ : (input.correlationId?.trim() || `openclaw-${Date.now()}`),
82
+ source_client: "openclaw",
83
+ message,
84
+ phase: input.phase,
85
+ progress_pct: typeof input.progressPct === "number" && Number.isFinite(input.progressPct)
86
+ ? Math.max(0, Math.min(100, Math.round(input.progressPct)))
87
+ : undefined,
88
+ level: input.level,
89
+ next_step: input.nextStep,
90
+ metadata: enrichedMetadata,
91
+ });
92
+ }
93
+ catch (err) {
94
+ const msg = deps.safeErrorMessage(err);
95
+ const runId = input.runId?.trim() || "";
96
+ // If OrgX rejects an unknown run_id, retry by treating the local run_id as a
97
+ // correlation key (non-UUID) so OrgX can create/attach a run deterministically.
98
+ if (runId && /^404\b/.test(msg) && /\brun\b/i.test(msg) && /not found/i.test(msg)) {
99
+ const retryCorrelationId = `openclaw_run_${deps.stableHash(runId).slice(0, 24)}`;
100
+ try {
101
+ await deps.client.emitActivity({
102
+ initiative_id: initiativeId,
103
+ run_id: undefined,
104
+ correlation_id: retryCorrelationId,
105
+ source_client: "openclaw",
106
+ message,
107
+ phase: input.phase,
108
+ progress_pct: typeof input.progressPct === "number" && Number.isFinite(input.progressPct)
109
+ ? Math.max(0, Math.min(100, Math.round(input.progressPct)))
110
+ : undefined,
111
+ level: input.level,
112
+ next_step: input.nextStep,
113
+ metadata: {
114
+ ...(enrichedMetadata ?? {}),
115
+ replay_run_id_as_correlation: true,
116
+ },
117
+ });
118
+ return;
119
+ }
120
+ catch {
121
+ // Fall through to local outbox.
122
+ }
123
+ }
124
+ // Fall back to local outbox so activity is still visible in Mission Control/Activity.
125
+ try {
126
+ const timestamp = new Date().toISOString();
127
+ const runId = input.runId?.trim() ||
128
+ input.correlationId?.trim() ||
129
+ null;
130
+ const activityItem = {
131
+ id: deps.randomUUID(),
132
+ type: input.phase === "completed"
133
+ ? "run_completed"
134
+ : input.phase === "blocked"
135
+ ? "run_failed"
136
+ : "run_started",
137
+ title: message,
138
+ description: input.nextStep ?? null,
139
+ agentId: (typeof metadataWithProvenance?.agent_id === "string"
140
+ ? metadataWithProvenance.agent_id
141
+ : null) ?? null,
142
+ agentName: (typeof metadataWithProvenance?.agent_name === "string"
143
+ ? metadataWithProvenance.agent_name
144
+ : null) ?? null,
145
+ requesterAgentId: null,
146
+ requesterAgentName: null,
147
+ executorAgentId: (typeof metadataWithProvenance?.agent_id === "string"
148
+ ? metadataWithProvenance.agent_id
149
+ : null) ?? null,
150
+ executorAgentName: (typeof metadataWithProvenance?.agent_name === "string"
151
+ ? metadataWithProvenance.agent_name
152
+ : null) ?? null,
153
+ runId,
154
+ initiativeId,
155
+ timestamp,
156
+ phase: input.phase,
157
+ summary: message,
158
+ kind: activityBucket,
159
+ metadata: {
160
+ ...(enrichedMetadata ?? {}),
161
+ source: "openclaw_local_fallback",
162
+ },
163
+ };
164
+ await appendToOutbox(initiativeId, {
165
+ id: deps.randomUUID(),
166
+ type: "progress",
167
+ timestamp,
168
+ payload: {
169
+ initiative_id: initiativeId,
170
+ run_id: input.runId?.trim() || undefined,
171
+ correlation_id: input.runId
172
+ ? undefined
173
+ : (input.correlationId?.trim() || `openclaw-${Date.now()}`),
174
+ source_client: "openclaw",
175
+ message,
176
+ phase: input.phase,
177
+ progress_pct: typeof input.progressPct === "number" && Number.isFinite(input.progressPct)
178
+ ? Math.max(0, Math.min(100, Math.round(input.progressPct)))
179
+ : undefined,
180
+ level: input.level ?? "info",
181
+ next_step: input.nextStep ?? undefined,
182
+ metadata: enrichedMetadata,
183
+ },
184
+ activityItem,
185
+ });
186
+ }
187
+ catch {
188
+ // best effort
189
+ }
190
+ }
191
+ }
192
+ async function requestDecisionSafe(input) {
193
+ const initiativeId = input.initiativeId?.trim() ?? "";
194
+ const title = input.title.trim();
195
+ if (!initiativeId || !title)
196
+ return;
197
+ try {
198
+ await deps.client.applyChangeset({
199
+ initiative_id: initiativeId,
200
+ correlation_id: input.correlationId?.trim() || undefined,
201
+ source_client: "openclaw",
202
+ idempotency_key: deps.idempotencyKey([
203
+ "openclaw",
204
+ "decision",
205
+ initiativeId,
206
+ title,
207
+ input.correlationId ?? null,
208
+ ]),
209
+ operations: [
210
+ {
211
+ op: "decision.create",
212
+ title,
213
+ summary: input.summary ?? undefined,
214
+ urgency: input.urgency ?? "high",
215
+ options: input.options ?? [],
216
+ blocking: input.blocking ?? true,
217
+ },
218
+ ],
219
+ });
220
+ }
221
+ catch (err) {
222
+ const timestamp = new Date().toISOString();
223
+ const correlationId = input.correlationId?.trim() || undefined;
224
+ const operations = [
225
+ {
226
+ op: "decision.create",
227
+ title,
228
+ summary: input.summary ?? undefined,
229
+ urgency: input.urgency ?? "high",
230
+ options: input.options ?? [],
231
+ blocking: input.blocking ?? true,
232
+ },
233
+ ];
234
+ const activityItem = {
235
+ id: deps.randomUUID(),
236
+ type: "decision_requested",
237
+ title,
238
+ description: input.summary ?? null,
239
+ agentId: null,
240
+ agentName: null,
241
+ requesterAgentId: null,
242
+ requesterAgentName: null,
243
+ executorAgentId: null,
244
+ executorAgentName: null,
245
+ runId: correlationId ?? null,
246
+ initiativeId,
247
+ timestamp,
248
+ phase: "review",
249
+ summary: input.summary ?? null,
250
+ metadata: {
251
+ source: "openclaw_local_fallback",
252
+ event: "decision_buffered",
253
+ urgency: input.urgency ?? "high",
254
+ blocking: input.blocking ?? true,
255
+ options: input.options ?? [],
256
+ error: deps.safeErrorMessage(err),
257
+ },
258
+ };
259
+ try {
260
+ await appendToOutbox(initiativeId, {
261
+ id: deps.randomUUID(),
262
+ type: "changeset",
263
+ timestamp,
264
+ payload: {
265
+ initiative_id: initiativeId,
266
+ correlation_id: correlationId,
267
+ source_client: "openclaw",
268
+ idempotency_key: deps.idempotencyKey([
269
+ "openclaw",
270
+ "decision",
271
+ initiativeId,
272
+ title,
273
+ input.correlationId ?? null,
274
+ "outbox",
275
+ ]),
276
+ operations,
277
+ },
278
+ activityItem,
279
+ });
280
+ }
281
+ catch {
282
+ // best effort
283
+ }
284
+ }
285
+ }
286
+ async function checkSpawnGuardSafe(input) {
287
+ const bypassRaw = (process.env.ORGX_SPAWN_GUARD_BYPASS ?? "").trim().toLowerCase();
288
+ const modeRaw = (process.env.ORGX_SPAWN_GUARD_MODE ?? "").trim().toLowerCase();
289
+ const bypass = (bypassRaw && bypassRaw !== "0" && bypassRaw !== "false" && bypassRaw !== "no") ||
290
+ modeRaw === "off" ||
291
+ modeRaw === "bypass";
292
+ if (bypass)
293
+ return null;
294
+ const scopedClient = deps.client;
295
+ if (typeof scopedClient.checkSpawnGuard !== "function") {
296
+ return null;
297
+ }
298
+ const taskId = input.taskId?.trim() ?? "";
299
+ const targetLabel = input.targetLabel?.trim() ||
300
+ (taskId ? `task ${taskId}` : "dispatch target");
301
+ try {
302
+ return await scopedClient.checkSpawnGuard(input.domain, taskId || undefined);
303
+ }
304
+ catch (err) {
305
+ await emitActivitySafe({
306
+ initiativeId: input.initiativeId,
307
+ runId: input.runId ?? null,
308
+ correlationId: input.correlationId,
309
+ phase: "blocked",
310
+ level: "warn",
311
+ message: `Spawn guard check degraded for ${targetLabel}; continuing with local policy.`,
312
+ metadata: {
313
+ event: "spawn_guard_degraded",
314
+ task_id: taskId || null,
315
+ domain: input.domain,
316
+ error: deps.safeErrorMessage(err),
317
+ },
318
+ });
319
+ return null;
320
+ }
321
+ }
322
+ function extractSpawnGuardModelTier(result) {
323
+ if (!result || typeof result !== "object")
324
+ return null;
325
+ return (deps.pickString(result, ["modelTier", "model_tier"]) ??
326
+ null);
327
+ }
328
+ function formatRequiredSkills(requiredSkills) {
329
+ const normalized = requiredSkills
330
+ .map((entry) => entry.trim())
331
+ .filter((entry) => entry.length > 0)
332
+ .map((entry) => (entry.startsWith("$") ? entry : `$${entry}`));
333
+ return normalized.length > 0
334
+ ? normalized.join(", ")
335
+ : "$orgx-engineering-agent";
336
+ }
337
+ function buildPolicyEnforcedMessage(input) {
338
+ const modelTier = extractSpawnGuardModelTier(input.spawnGuardResult ?? null);
339
+ return [
340
+ `Execution policy: ${input.executionPolicy.domain}`,
341
+ `Required skills: ${formatRequiredSkills(input.executionPolicy.requiredSkills)}`,
342
+ modelTier ? `Spawn guard model tier: ${modelTier}` : null,
343
+ "",
344
+ input.baseMessage,
345
+ ]
346
+ .filter((entry) => Boolean(entry))
347
+ .join("\n");
348
+ }
349
+ async function resolveDispatchExecutionPolicy(input) {
350
+ const initiativeId = input.initiativeId?.trim() ?? "";
351
+ const taskId = input.taskId?.trim() ?? "";
352
+ const workstreamId = input.workstreamId?.trim() ?? "";
353
+ let resolvedTaskTitle = input.taskTitle?.trim() || null;
354
+ let resolvedWorkstreamTitle = input.workstreamTitle?.trim() || null;
355
+ if (initiativeId && (taskId || workstreamId)) {
356
+ try {
357
+ const graph = await buildMissionControlGraph(deps.client, initiativeId);
358
+ const nodeById = new Map(graph.nodes.map((node) => [node.id, node]));
359
+ const taskNode = taskId ? nodeById.get(taskId) ?? null : null;
360
+ const workstreamNode = workstreamId ? nodeById.get(workstreamId) ?? null : null;
361
+ if (taskNode && taskNode.type === "task") {
362
+ resolvedTaskTitle = resolvedTaskTitle ?? taskNode.title;
363
+ const relatedWorkstream = (taskNode.workstreamId ? nodeById.get(taskNode.workstreamId) ?? null : null) ??
364
+ workstreamNode;
365
+ const normalizedWorkstream = relatedWorkstream && relatedWorkstream.type === "workstream"
366
+ ? relatedWorkstream
367
+ : null;
368
+ resolvedWorkstreamTitle =
369
+ resolvedWorkstreamTitle ?? normalizedWorkstream?.title ?? null;
370
+ return {
371
+ executionPolicy: deriveExecutionPolicy(taskNode, normalizedWorkstream),
372
+ taskTitle: resolvedTaskTitle,
373
+ workstreamTitle: resolvedWorkstreamTitle,
374
+ };
375
+ }
376
+ if (workstreamNode && workstreamNode.type === "workstream") {
377
+ resolvedWorkstreamTitle = resolvedWorkstreamTitle ?? workstreamNode.title;
378
+ const assignedDomain = workstreamNode.assignedAgents
379
+ .map((agent) => normalizeExecutionDomain(agent.domain))
380
+ .find((entry) => Boolean(entry));
381
+ const domain = assignedDomain ??
382
+ inferExecutionDomainFromText(workstreamNode.title, input.initiativeTitle, input.message);
383
+ const normalizedDomain = normalizeExecutionDomain(domain) ?? "engineering";
384
+ return {
385
+ executionPolicy: {
386
+ domain: normalizedDomain,
387
+ requiredSkills: [
388
+ ORGX_SKILL_BY_DOMAIN[normalizedDomain] ??
389
+ ORGX_SKILL_BY_DOMAIN.engineering,
390
+ ],
391
+ },
392
+ taskTitle: resolvedTaskTitle,
393
+ workstreamTitle: resolvedWorkstreamTitle,
394
+ };
395
+ }
396
+ }
397
+ catch {
398
+ // best effort
399
+ }
400
+ }
401
+ const inferredDomain = normalizeExecutionDomain(inferExecutionDomainFromText(resolvedTaskTitle, resolvedWorkstreamTitle, input.initiativeTitle, input.message)) ?? "engineering";
402
+ return {
403
+ executionPolicy: {
404
+ domain: inferredDomain,
405
+ requiredSkills: [
406
+ ORGX_SKILL_BY_DOMAIN[inferredDomain] ?? ORGX_SKILL_BY_DOMAIN.engineering,
407
+ ],
408
+ },
409
+ taskTitle: resolvedTaskTitle,
410
+ workstreamTitle: resolvedWorkstreamTitle,
411
+ };
412
+ }
413
+ async function syncParentRollupsForTask(input) {
414
+ const initiativeId = input.initiativeId?.trim() ?? "";
415
+ const taskId = input.taskId?.trim() ?? "";
416
+ if (!initiativeId || !taskId)
417
+ return;
418
+ let tasks = [];
419
+ try {
420
+ const response = await deps.client.listEntities("task", {
421
+ initiative_id: initiativeId,
422
+ limit: 4000,
423
+ });
424
+ tasks = Array.isArray(response?.data)
425
+ ? response.data
426
+ : [];
427
+ }
428
+ catch {
429
+ return;
430
+ }
431
+ const task = tasks.find((row) => String(row.id ?? "").trim() === taskId) ?? null;
432
+ const resolvedMilestoneId = (input.milestoneId?.trim() || "") ||
433
+ (task ? deps.pickString(task, ["milestone_id", "milestoneId"]) ?? "" : "");
434
+ const resolvedWorkstreamId = (input.workstreamId?.trim() || "") ||
435
+ (task ? deps.pickString(task, ["workstream_id", "workstreamId"]) ?? "" : "");
436
+ if (resolvedMilestoneId) {
437
+ const milestoneTaskStatuses = tasks
438
+ .filter((row) => deps.pickString(row, ["milestone_id", "milestoneId"]) === resolvedMilestoneId)
439
+ .map((row) => deps.pickString(row, ["status"]) ?? "todo");
440
+ const rollup = computeMilestoneRollup(milestoneTaskStatuses);
441
+ try {
442
+ await deps.client.applyChangeset({
443
+ initiative_id: initiativeId,
444
+ correlation_id: input.correlationId?.trim() || undefined,
445
+ source_client: "openclaw",
446
+ idempotency_key: deps.idempotencyKey([
447
+ "openclaw",
448
+ "rollup",
449
+ "milestone",
450
+ resolvedMilestoneId,
451
+ rollup.status,
452
+ String(rollup.progressPct),
453
+ String(rollup.done),
454
+ String(rollup.total),
455
+ ]),
456
+ operations: [
457
+ {
458
+ op: "milestone.update",
459
+ milestone_id: resolvedMilestoneId,
460
+ status: rollup.status,
461
+ },
462
+ ],
463
+ });
464
+ }
465
+ catch {
466
+ // best effort
467
+ }
468
+ }
469
+ if (resolvedWorkstreamId) {
470
+ const workstreamTaskStatuses = tasks
471
+ .filter((row) => deps.pickString(row, ["workstream_id", "workstreamId"]) === resolvedWorkstreamId)
472
+ .map((row) => deps.pickString(row, ["status"]) ?? "todo");
473
+ const rollup = computeWorkstreamRollup(workstreamTaskStatuses);
474
+ try {
475
+ await deps.client.updateEntity("workstream", resolvedWorkstreamId, {
476
+ status: rollup.status,
477
+ });
478
+ }
479
+ catch {
480
+ // best effort
481
+ }
482
+ }
483
+ }
484
+ async function enforceSpawnGuardForDispatch(input) {
485
+ const taskId = input.taskId?.trim() ?? "";
486
+ const workstreamId = input.workstreamId?.trim() ?? "";
487
+ const taskTitle = input.taskTitle?.trim() || null;
488
+ const workstreamTitle = input.workstreamTitle?.trim() || null;
489
+ const targetLabel = taskId
490
+ ? `task ${taskTitle ?? taskId}`
491
+ : workstreamId
492
+ ? `workstream ${workstreamTitle ?? workstreamId}`
493
+ : "dispatch target";
494
+ const spawnGuardResult = await checkSpawnGuardSafe({
495
+ domain: input.executionPolicy.domain,
496
+ // Spawn guard is task-scoped. Passing a workstream ID causes noisy 404s
497
+ // ("Task ... not found") and makes the UI feel perpetually degraded.
498
+ taskId: taskId || null,
499
+ initiativeId: input.initiativeId,
500
+ correlationId: input.correlationId,
501
+ runId: input.runId ?? null,
502
+ targetLabel,
503
+ });
504
+ if (!spawnGuardResult || typeof spawnGuardResult !== "object") {
505
+ return {
506
+ allowed: true,
507
+ retryable: false,
508
+ blockedReason: null,
509
+ spawnGuardResult,
510
+ };
511
+ }
512
+ const allowed = spawnGuardResult.allowed;
513
+ if (allowed !== false) {
514
+ return {
515
+ allowed: true,
516
+ retryable: false,
517
+ blockedReason: null,
518
+ spawnGuardResult,
519
+ };
520
+ }
521
+ const blockedReason = summarizeSpawnGuardBlockReason(spawnGuardResult);
522
+ const retryable = spawnGuardIsRateLimited(spawnGuardResult);
523
+ const blockedEvent = retryable
524
+ ? `${input.sourceEventPrefix}_spawn_guard_rate_limited`
525
+ : `${input.sourceEventPrefix}_spawn_guard_blocked`;
526
+ await emitActivitySafe({
527
+ initiativeId: input.initiativeId,
528
+ runId: input.runId ?? null,
529
+ correlationId: input.correlationId,
530
+ phase: "blocked",
531
+ level: retryable ? "warn" : "error",
532
+ message: retryable
533
+ ? `Spawn guard rate-limited ${targetLabel}; deferring launch.`
534
+ : `Spawn guard blocked ${targetLabel}.`,
535
+ metadata: {
536
+ event: blockedEvent,
537
+ agent_id: input.agentId ?? null,
538
+ task_id: taskId || null,
539
+ task_title: taskTitle,
540
+ workstream_id: workstreamId || null,
541
+ workstream_title: workstreamTitle,
542
+ domain: input.executionPolicy.domain,
543
+ required_skills: input.executionPolicy.requiredSkills,
544
+ blocked_reason: blockedReason,
545
+ spawn_guard: spawnGuardResult,
546
+ },
547
+ nextStep: retryable
548
+ ? "Retry dispatch when spawn rate limits recover."
549
+ : "Review decision and unblock guard checks before retry.",
550
+ });
551
+ if (!retryable && input.initiativeId && taskId) {
552
+ try {
553
+ await deps.client.updateEntity("task", taskId, { status: "blocked" });
554
+ }
555
+ catch {
556
+ // best effort
557
+ }
558
+ await syncParentRollupsForTask({
559
+ initiativeId: input.initiativeId,
560
+ taskId,
561
+ workstreamId: workstreamId || null,
562
+ milestoneId: input.milestoneId ?? null,
563
+ correlationId: input.correlationId,
564
+ });
565
+ }
566
+ if (!retryable) {
567
+ await requestDecisionSafe({
568
+ initiativeId: input.initiativeId,
569
+ correlationId: input.correlationId,
570
+ title: `Unblock ${targetLabel}`,
571
+ summary: [
572
+ `${targetLabel} failed spawn guard checks.`,
573
+ `Reason: ${blockedReason}`,
574
+ `Domain: ${input.executionPolicy.domain}`,
575
+ `Required skills: ${input.executionPolicy.requiredSkills.join(", ")}`,
576
+ ].join(" "),
577
+ urgency: "high",
578
+ options: [
579
+ "Approve exception and continue",
580
+ "Reassign task/domain",
581
+ "Pause and investigate quality gate",
582
+ ],
583
+ blocking: true,
584
+ });
585
+ }
586
+ return {
587
+ allowed: false,
588
+ retryable,
589
+ blockedReason,
590
+ spawnGuardResult,
591
+ };
592
+ }
593
+ return {
594
+ withProvenanceMetadata,
595
+ emitActivitySafe,
596
+ requestDecisionSafe,
597
+ checkSpawnGuardSafe,
598
+ extractSpawnGuardModelTier,
599
+ buildPolicyEnforcedMessage,
600
+ resolveDispatchExecutionPolicy,
601
+ enforceSpawnGuardForDispatch,
602
+ syncParentRollupsForTask,
603
+ };
604
+ }
@@ -0,0 +1 @@
1
+ export { idempotencyKey, stableHash } from "../../hash-utils.js";
@@ -0,0 +1 @@
1
+ export { idempotencyKey, stableHash } from "../../hash-utils.js";
@@ -0,0 +1,12 @@
1
+ import type { OrgXClient } from "../../api.js";
2
+ import type { KickoffContext, KickoffContextRequest } from "../../types.js";
3
+ export declare function fetchKickoffContextSafe(client: OrgXClient, payload: KickoffContextRequest): Promise<KickoffContext | null>;
4
+ export declare function renderKickoffMessage(input: {
5
+ baseMessage: string;
6
+ kickoff: KickoffContext | null;
7
+ domain: string | null;
8
+ requiredSkills: string[];
9
+ }): {
10
+ message: string;
11
+ contextHash: string | null;
12
+ };