@useorgx/openclaw-plugin 0.4.6 → 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.
- package/README.md +310 -24
- package/dashboard/dist/assets/B5NEElEI.css +1 -0
- package/dashboard/dist/assets/BhapSNAs.js +215 -0
- package/dashboard/dist/assets/iFdvE7lx.js +1 -0
- package/dashboard/dist/assets/jRJsmpYM.js +1 -0
- package/dashboard/dist/index.html +2 -2
- package/dist/activity-actor-fields.d.ts +3 -0
- package/dist/activity-actor-fields.js +128 -0
- package/dist/activity-store.js +12 -19
- package/dist/agent-context-store.js +5 -25
- package/dist/agent-run-store.js +5 -25
- package/dist/agent-suite.js +1 -8
- package/dist/artifacts/register-artifact.d.ts +47 -0
- package/dist/artifacts/register-artifact.js +271 -0
- package/dist/auth/flows.d.ts +47 -0
- package/dist/auth/flows.js +169 -0
- package/dist/auth-store.js +14 -39
- package/dist/byok-store.js +5 -19
- package/dist/cli/orgx.d.ts +66 -0
- package/dist/cli/orgx.js +91 -0
- package/dist/config/refresh.d.ts +32 -0
- package/dist/config/refresh.js +55 -0
- package/dist/config/resolution.d.ts +37 -0
- package/dist/config/resolution.js +178 -0
- package/dist/contracts/client.d.ts +1 -0
- package/dist/contracts/client.js +7 -5
- package/dist/contracts/shared-types.d.ts +147 -0
- package/dist/contracts/shared-types.js +3 -0
- package/dist/contracts/types.d.ts +1 -130
- package/dist/contracts/types.js +5 -0
- package/dist/entities/auto-assignment.d.ts +36 -0
- package/dist/entities/auto-assignment.js +115 -0
- package/dist/entity-comment-store.js +5 -25
- package/dist/hash-utils.d.ts +2 -0
- package/dist/hash-utils.js +12 -0
- package/dist/http/helpers/activity-headline.d.ts +10 -0
- package/dist/http/helpers/activity-headline.js +192 -0
- package/dist/http/helpers/artifact-fallback.d.ts +13 -0
- package/dist/http/helpers/artifact-fallback.js +148 -0
- package/dist/http/helpers/auto-continue-engine.d.ts +298 -0
- package/dist/http/helpers/auto-continue-engine.js +1218 -0
- package/dist/http/helpers/autopilot-operations.d.ts +157 -0
- package/dist/http/helpers/autopilot-operations.js +403 -0
- package/dist/http/helpers/autopilot-runtime.d.ts +42 -0
- package/dist/http/helpers/autopilot-runtime.js +319 -0
- package/dist/http/helpers/autopilot-slice-utils.d.ts +38 -0
- package/dist/http/helpers/autopilot-slice-utils.js +476 -0
- package/dist/http/helpers/decision-mapper.d.ts +12 -0
- package/dist/http/helpers/decision-mapper.js +44 -0
- package/dist/http/helpers/dispatch-lifecycle.d.ts +102 -0
- package/dist/http/helpers/dispatch-lifecycle.js +604 -0
- package/dist/http/helpers/hash-utils.d.ts +1 -0
- package/dist/http/helpers/hash-utils.js +1 -0
- package/dist/http/helpers/kickoff-context.d.ts +12 -0
- package/dist/http/helpers/kickoff-context.js +154 -0
- package/dist/http/helpers/mission-control.d.ts +94 -0
- package/dist/http/helpers/mission-control.js +894 -0
- package/dist/http/helpers/openclaw-cli.d.ts +37 -0
- package/dist/http/helpers/openclaw-cli.js +283 -0
- package/dist/http/helpers/runtime-sse.d.ts +20 -0
- package/dist/http/helpers/runtime-sse.js +110 -0
- package/dist/http/helpers/value-utils.d.ts +6 -0
- package/dist/http/helpers/value-utils.js +67 -0
- package/dist/http/index.d.ts +88 -0
- package/dist/http/index.js +2353 -0
- package/dist/http/router.d.ts +23 -0
- package/dist/http/router.js +23 -0
- package/dist/http/routes/agent-control.d.ts +79 -0
- package/dist/http/routes/agent-control.js +684 -0
- package/dist/http/routes/agent-suite.d.ts +29 -0
- package/dist/http/routes/agent-suite.js +198 -0
- package/dist/http/routes/agents-catalog.d.ts +40 -0
- package/dist/http/routes/agents-catalog.js +83 -0
- package/dist/http/routes/billing.d.ts +23 -0
- package/dist/http/routes/billing.js +55 -0
- package/dist/http/routes/debug.d.ts +14 -0
- package/dist/http/routes/debug.js +21 -0
- package/dist/http/routes/decision-actions.d.ts +13 -0
- package/dist/http/routes/decision-actions.js +66 -0
- package/dist/http/routes/delegation.d.ts +19 -0
- package/dist/http/routes/delegation.js +32 -0
- package/dist/http/routes/entities.d.ts +47 -0
- package/dist/http/routes/entities.js +152 -0
- package/dist/http/routes/entity-dynamic.d.ts +25 -0
- package/dist/http/routes/entity-dynamic.js +191 -0
- package/dist/http/routes/health.d.ts +22 -0
- package/dist/http/routes/health.js +49 -0
- package/dist/http/routes/live-legacy.d.ts +110 -0
- package/dist/http/routes/live-legacy.js +598 -0
- package/dist/http/routes/live-misc.d.ts +69 -0
- package/dist/http/routes/live-misc.js +206 -0
- package/dist/http/routes/live-snapshot.d.ts +90 -0
- package/dist/http/routes/live-snapshot.js +297 -0
- package/dist/http/routes/mission-control-actions.d.ts +83 -0
- package/dist/http/routes/mission-control-actions.js +541 -0
- package/dist/http/routes/mission-control-read.d.ts +28 -0
- package/dist/http/routes/mission-control-read.js +67 -0
- package/dist/http/routes/onboarding.d.ts +34 -0
- package/dist/http/routes/onboarding.js +101 -0
- package/dist/http/routes/run-control.d.ts +24 -0
- package/dist/http/routes/run-control.js +86 -0
- package/dist/http/routes/runtime-hooks.d.ts +69 -0
- package/dist/http/routes/runtime-hooks.js +437 -0
- package/dist/http/routes/settings-byok.d.ts +23 -0
- package/dist/http/routes/settings-byok.js +163 -0
- package/dist/http/routes/summary.d.ts +18 -0
- package/dist/http/routes/summary.js +42 -0
- package/dist/http/routes/work-artifacts.d.ts +9 -0
- package/dist/http/routes/work-artifacts.js +36 -0
- package/dist/http/shared-state.d.ts +16 -0
- package/dist/http/shared-state.js +1 -0
- package/dist/http-handler.d.ts +1 -88
- package/dist/http-handler.js +1 -9664
- package/dist/index.js +122 -2121
- package/dist/json-utils.d.ts +1 -0
- package/dist/json-utils.js +8 -0
- package/dist/local-openclaw.js +8 -0
- package/dist/mcp-client-setup.js +75 -90
- package/dist/next-up-queue-store.js +4 -18
- package/dist/runtime-instance-store.js +8 -34
- package/dist/services/background.d.ts +23 -0
- package/dist/services/background.js +23 -0
- package/dist/services/instrumentation.d.ts +29 -0
- package/dist/services/instrumentation.js +136 -0
- package/dist/snapshot-store.js +5 -25
- package/dist/stores/json-store.d.ts +11 -0
- package/dist/stores/json-store.js +42 -0
- package/dist/sync/outbox-replay.d.ts +55 -0
- package/dist/sync/outbox-replay.js +514 -0
- package/dist/tools/core-tools.d.ts +76 -0
- package/dist/tools/core-tools.js +1005 -0
- package/dist/worker-supervisor.js +15 -0
- package/package.json +6 -1
- package/dashboard/dist/assets/0tOC3wSN.js +0 -214
- package/dashboard/dist/assets/Bm8QnMJ_.js +0 -1
- package/dashboard/dist/assets/CyxZio4Y.js +0 -1
- package/dashboard/dist/assets/DaAIOik3.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
|
+
};
|