@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.
Files changed (137) hide show
  1. package/README.md +310 -24
  2. package/dashboard/dist/assets/B5NEElEI.css +1 -0
  3. package/dashboard/dist/assets/BhapSNAs.js +215 -0
  4. package/dashboard/dist/assets/iFdvE7lx.js +1 -0
  5. package/dashboard/dist/assets/jRJsmpYM.js +1 -0
  6. package/dashboard/dist/index.html +2 -2
  7. package/dist/activity-actor-fields.d.ts +3 -0
  8. package/dist/activity-actor-fields.js +128 -0
  9. package/dist/activity-store.js +12 -19
  10. package/dist/agent-context-store.js +5 -25
  11. package/dist/agent-run-store.js +5 -25
  12. package/dist/agent-suite.js +1 -8
  13. package/dist/artifacts/register-artifact.d.ts +47 -0
  14. package/dist/artifacts/register-artifact.js +271 -0
  15. package/dist/auth/flows.d.ts +47 -0
  16. package/dist/auth/flows.js +169 -0
  17. package/dist/auth-store.js +14 -39
  18. package/dist/byok-store.js +5 -19
  19. package/dist/cli/orgx.d.ts +66 -0
  20. package/dist/cli/orgx.js +91 -0
  21. package/dist/config/refresh.d.ts +32 -0
  22. package/dist/config/refresh.js +55 -0
  23. package/dist/config/resolution.d.ts +37 -0
  24. package/dist/config/resolution.js +178 -0
  25. package/dist/contracts/client.d.ts +1 -0
  26. package/dist/contracts/client.js +7 -5
  27. package/dist/contracts/shared-types.d.ts +147 -0
  28. package/dist/contracts/shared-types.js +3 -0
  29. package/dist/contracts/types.d.ts +1 -130
  30. package/dist/contracts/types.js +5 -0
  31. package/dist/entities/auto-assignment.d.ts +36 -0
  32. package/dist/entities/auto-assignment.js +115 -0
  33. package/dist/entity-comment-store.js +5 -25
  34. package/dist/hash-utils.d.ts +2 -0
  35. package/dist/hash-utils.js +12 -0
  36. package/dist/http/helpers/activity-headline.d.ts +10 -0
  37. package/dist/http/helpers/activity-headline.js +192 -0
  38. package/dist/http/helpers/artifact-fallback.d.ts +13 -0
  39. package/dist/http/helpers/artifact-fallback.js +148 -0
  40. package/dist/http/helpers/auto-continue-engine.d.ts +298 -0
  41. package/dist/http/helpers/auto-continue-engine.js +1218 -0
  42. package/dist/http/helpers/autopilot-operations.d.ts +157 -0
  43. package/dist/http/helpers/autopilot-operations.js +403 -0
  44. package/dist/http/helpers/autopilot-runtime.d.ts +42 -0
  45. package/dist/http/helpers/autopilot-runtime.js +319 -0
  46. package/dist/http/helpers/autopilot-slice-utils.d.ts +38 -0
  47. package/dist/http/helpers/autopilot-slice-utils.js +476 -0
  48. package/dist/http/helpers/decision-mapper.d.ts +12 -0
  49. package/dist/http/helpers/decision-mapper.js +44 -0
  50. package/dist/http/helpers/dispatch-lifecycle.d.ts +102 -0
  51. package/dist/http/helpers/dispatch-lifecycle.js +604 -0
  52. package/dist/http/helpers/hash-utils.d.ts +1 -0
  53. package/dist/http/helpers/hash-utils.js +1 -0
  54. package/dist/http/helpers/kickoff-context.d.ts +12 -0
  55. package/dist/http/helpers/kickoff-context.js +154 -0
  56. package/dist/http/helpers/mission-control.d.ts +94 -0
  57. package/dist/http/helpers/mission-control.js +894 -0
  58. package/dist/http/helpers/openclaw-cli.d.ts +37 -0
  59. package/dist/http/helpers/openclaw-cli.js +283 -0
  60. package/dist/http/helpers/runtime-sse.d.ts +20 -0
  61. package/dist/http/helpers/runtime-sse.js +110 -0
  62. package/dist/http/helpers/value-utils.d.ts +6 -0
  63. package/dist/http/helpers/value-utils.js +67 -0
  64. package/dist/http/index.d.ts +88 -0
  65. package/dist/http/index.js +2353 -0
  66. package/dist/http/router.d.ts +23 -0
  67. package/dist/http/router.js +23 -0
  68. package/dist/http/routes/agent-control.d.ts +79 -0
  69. package/dist/http/routes/agent-control.js +684 -0
  70. package/dist/http/routes/agent-suite.d.ts +29 -0
  71. package/dist/http/routes/agent-suite.js +198 -0
  72. package/dist/http/routes/agents-catalog.d.ts +40 -0
  73. package/dist/http/routes/agents-catalog.js +83 -0
  74. package/dist/http/routes/billing.d.ts +23 -0
  75. package/dist/http/routes/billing.js +55 -0
  76. package/dist/http/routes/debug.d.ts +14 -0
  77. package/dist/http/routes/debug.js +21 -0
  78. package/dist/http/routes/decision-actions.d.ts +13 -0
  79. package/dist/http/routes/decision-actions.js +66 -0
  80. package/dist/http/routes/delegation.d.ts +19 -0
  81. package/dist/http/routes/delegation.js +32 -0
  82. package/dist/http/routes/entities.d.ts +47 -0
  83. package/dist/http/routes/entities.js +152 -0
  84. package/dist/http/routes/entity-dynamic.d.ts +25 -0
  85. package/dist/http/routes/entity-dynamic.js +191 -0
  86. package/dist/http/routes/health.d.ts +22 -0
  87. package/dist/http/routes/health.js +49 -0
  88. package/dist/http/routes/live-legacy.d.ts +110 -0
  89. package/dist/http/routes/live-legacy.js +598 -0
  90. package/dist/http/routes/live-misc.d.ts +69 -0
  91. package/dist/http/routes/live-misc.js +206 -0
  92. package/dist/http/routes/live-snapshot.d.ts +90 -0
  93. package/dist/http/routes/live-snapshot.js +297 -0
  94. package/dist/http/routes/mission-control-actions.d.ts +83 -0
  95. package/dist/http/routes/mission-control-actions.js +541 -0
  96. package/dist/http/routes/mission-control-read.d.ts +28 -0
  97. package/dist/http/routes/mission-control-read.js +67 -0
  98. package/dist/http/routes/onboarding.d.ts +34 -0
  99. package/dist/http/routes/onboarding.js +101 -0
  100. package/dist/http/routes/run-control.d.ts +24 -0
  101. package/dist/http/routes/run-control.js +86 -0
  102. package/dist/http/routes/runtime-hooks.d.ts +69 -0
  103. package/dist/http/routes/runtime-hooks.js +437 -0
  104. package/dist/http/routes/settings-byok.d.ts +23 -0
  105. package/dist/http/routes/settings-byok.js +163 -0
  106. package/dist/http/routes/summary.d.ts +18 -0
  107. package/dist/http/routes/summary.js +42 -0
  108. package/dist/http/routes/work-artifacts.d.ts +9 -0
  109. package/dist/http/routes/work-artifacts.js +36 -0
  110. package/dist/http/shared-state.d.ts +16 -0
  111. package/dist/http/shared-state.js +1 -0
  112. package/dist/http-handler.d.ts +1 -88
  113. package/dist/http-handler.js +1 -9664
  114. package/dist/index.js +122 -2121
  115. package/dist/json-utils.d.ts +1 -0
  116. package/dist/json-utils.js +8 -0
  117. package/dist/local-openclaw.js +8 -0
  118. package/dist/mcp-client-setup.js +75 -90
  119. package/dist/next-up-queue-store.js +4 -18
  120. package/dist/runtime-instance-store.js +8 -34
  121. package/dist/services/background.d.ts +23 -0
  122. package/dist/services/background.js +23 -0
  123. package/dist/services/instrumentation.d.ts +29 -0
  124. package/dist/services/instrumentation.js +136 -0
  125. package/dist/snapshot-store.js +5 -25
  126. package/dist/stores/json-store.d.ts +11 -0
  127. package/dist/stores/json-store.js +42 -0
  128. package/dist/sync/outbox-replay.d.ts +55 -0
  129. package/dist/sync/outbox-replay.js +514 -0
  130. package/dist/tools/core-tools.d.ts +76 -0
  131. package/dist/tools/core-tools.js +1005 -0
  132. package/dist/worker-supervisor.js +15 -0
  133. package/package.json +6 -1
  134. package/dashboard/dist/assets/0tOC3wSN.js +0 -214
  135. package/dashboard/dist/assets/Bm8QnMJ_.js +0 -1
  136. package/dashboard/dist/assets/CyxZio4Y.js +0 -1
  137. package/dashboard/dist/assets/DaAIOik3.css +0 -1
@@ -0,0 +1,684 @@
1
+ export function registerAgentControlRoutes(router, deps) {
2
+ router.add("POST", "agents/launch", async ({ req, query, res }) => {
3
+ try {
4
+ const payload = await deps.parseJsonRequest(req);
5
+ const agentId = (deps.pickString(payload, ["agentId", "agent_id", "id"]) ??
6
+ query.get("agentId") ??
7
+ query.get("agent_id") ??
8
+ query.get("id") ??
9
+ "")
10
+ .trim();
11
+ if (!agentId) {
12
+ deps.sendJson(res, 400, { ok: false, error: "agentId is required" });
13
+ return;
14
+ }
15
+ if (!/^[a-zA-Z0-9_-]+$/.test(agentId)) {
16
+ deps.sendJson(res, 400, {
17
+ ok: false,
18
+ error: "agentId must be a simple identifier (letters, numbers, _ or -).",
19
+ });
20
+ return;
21
+ }
22
+ const sessionId = (deps.pickString(payload, ["sessionId", "session_id"]) ??
23
+ query.get("sessionId") ??
24
+ query.get("session_id") ??
25
+ "")
26
+ .trim() ||
27
+ deps.randomUUID();
28
+ const initiativeId = deps.pickString(payload, ["initiativeId", "initiative_id"]) ??
29
+ query.get("initiativeId") ??
30
+ query.get("initiative_id") ??
31
+ null;
32
+ const initiativeTitle = deps.pickString(payload, [
33
+ "initiativeTitle",
34
+ "initiative_title",
35
+ "initiativeName",
36
+ "initiative_name",
37
+ ]) ??
38
+ query.get("initiativeTitle") ??
39
+ query.get("initiative_title") ??
40
+ query.get("initiativeName") ??
41
+ query.get("initiative_name") ??
42
+ null;
43
+ const workstreamId = deps.pickString(payload, ["workstreamId", "workstream_id"]) ??
44
+ query.get("workstreamId") ??
45
+ query.get("workstream_id") ??
46
+ null;
47
+ const taskId = deps.pickString(payload, ["taskId", "task_id"]) ??
48
+ query.get("taskId") ??
49
+ query.get("task_id") ??
50
+ null;
51
+ const thinking = (deps.pickString(payload, ["thinking"]) ??
52
+ query.get("thinking") ??
53
+ "")
54
+ .trim() || null;
55
+ const provider = deps.normalizeOpenClawProvider(deps.pickString(payload, ["provider", "modelProvider", "model_provider"]) ??
56
+ query.get("provider") ??
57
+ query.get("modelProvider") ??
58
+ query.get("model_provider") ??
59
+ null);
60
+ const requestedModel = (deps.pickString(payload, ["model", "modelId", "model_id"]) ??
61
+ query.get("model") ??
62
+ query.get("modelId") ??
63
+ query.get("model_id") ??
64
+ "")
65
+ .trim() || null;
66
+ const routingProvider = provider ?? (!provider && !requestedModel ? deps.resolveAutoOpenClawProvider() : null);
67
+ const dryRunRaw = payload.dryRun ??
68
+ payload.dry_run ??
69
+ query.get("dryRun") ??
70
+ query.get("dry_run") ??
71
+ null;
72
+ const dryRun = typeof dryRunRaw === "boolean"
73
+ ? dryRunRaw
74
+ : deps.parseBooleanQuery(typeof dryRunRaw === "string" ? dryRunRaw : null);
75
+ let requiresPremiumLaunch = Boolean(routingProvider) || deps.modelImpliesByok(requestedModel);
76
+ if (!requiresPremiumLaunch) {
77
+ try {
78
+ const agents = await deps.listAgents();
79
+ const agentEntry = agents.find((entry) => String(entry.id ?? "").trim() === agentId) ??
80
+ null;
81
+ const agentModel = agentEntry && typeof agentEntry.model === "string"
82
+ ? agentEntry.model
83
+ : null;
84
+ requiresPremiumLaunch = deps.modelImpliesByok(agentModel);
85
+ }
86
+ catch {
87
+ // ignore
88
+ }
89
+ }
90
+ if (requiresPremiumLaunch) {
91
+ const billingStatus = await deps.fetchBillingStatusSafe(deps.client);
92
+ if (billingStatus && billingStatus.plan === "free") {
93
+ const pricingUrl = `${deps.client.getBaseUrl().replace(/\/+$/, "")}/pricing`;
94
+ deps.sendJson(res, 402, {
95
+ ok: false,
96
+ code: "upgrade_required",
97
+ error: "BYOK agent launch requires a paid OrgX plan. Upgrade, then retry.",
98
+ currentPlan: billingStatus.plan,
99
+ requiredPlan: "starter",
100
+ actions: {
101
+ checkout: "/orgx/api/billing/checkout",
102
+ portal: "/orgx/api/billing/portal",
103
+ pricing: pricingUrl,
104
+ },
105
+ });
106
+ return;
107
+ }
108
+ }
109
+ const messageInput = (deps.pickString(payload, ["message", "prompt", "text"]) ??
110
+ query.get("message") ??
111
+ query.get("prompt") ??
112
+ query.get("text") ??
113
+ "")
114
+ .trim();
115
+ const baseMessage = messageInput ||
116
+ (initiativeTitle
117
+ ? `Kick off: ${initiativeTitle}`
118
+ : initiativeId
119
+ ? `Kick off initiative ${initiativeId}`
120
+ : `Kick off agent ${agentId}`);
121
+ const policyResolution = await deps.resolveDispatchExecutionPolicy({
122
+ initiativeId,
123
+ initiativeTitle,
124
+ workstreamId,
125
+ taskId,
126
+ message: baseMessage,
127
+ });
128
+ const executionPolicy = policyResolution.executionPolicy;
129
+ const resolvedTaskTitle = policyResolution.taskTitle;
130
+ const resolvedWorkstreamTitle = policyResolution.workstreamTitle ??
131
+ (workstreamId ? `Workstream ${workstreamId}` : null);
132
+ const kickoff = await deps.fetchKickoffContextSafe(deps.client, {
133
+ initiative_id: initiativeId,
134
+ workstream_id: workstreamId,
135
+ task_id: taskId,
136
+ agent_id: agentId,
137
+ domain: executionPolicy.domain,
138
+ required_skills: executionPolicy.requiredSkills,
139
+ message: baseMessage,
140
+ });
141
+ const kickoffRendered = deps.renderKickoffMessage({
142
+ baseMessage,
143
+ kickoff,
144
+ domain: executionPolicy.domain,
145
+ requiredSkills: executionPolicy.requiredSkills,
146
+ });
147
+ const kickoffMessage = kickoffRendered.message;
148
+ const kickoffContextHash = kickoffRendered.contextHash;
149
+ void deps
150
+ .posthogCapture({
151
+ event: "openclaw_agent_launch_requested",
152
+ distinctId: deps.telemetryDistinctId,
153
+ properties: {
154
+ plugin_version: deps.pluginVersion,
155
+ agent_id: agentId,
156
+ initiative_present: Boolean(initiativeId),
157
+ workstream_present: Boolean(workstreamId),
158
+ task_present: Boolean(taskId),
159
+ domain: executionPolicy.domain,
160
+ required_skills_count: executionPolicy.requiredSkills.length,
161
+ provider: routingProvider,
162
+ model: requestedModel,
163
+ dry_run: Boolean(dryRun),
164
+ },
165
+ })
166
+ .catch(() => {
167
+ // best effort
168
+ });
169
+ if (dryRun) {
170
+ deps.sendJson(res, 200, {
171
+ ok: true,
172
+ dryRun: true,
173
+ agentId,
174
+ initiativeId,
175
+ workstreamId,
176
+ taskId,
177
+ requiresPremiumLaunch,
178
+ provider: routingProvider,
179
+ model: requestedModel,
180
+ startedAt: new Date().toISOString(),
181
+ message: kickoffMessage,
182
+ kickoffContextHash,
183
+ domain: executionPolicy.domain,
184
+ requiredSkills: executionPolicy.requiredSkills,
185
+ });
186
+ return;
187
+ }
188
+ const guard = await deps.enforceSpawnGuardForDispatch({
189
+ sourceEventPrefix: "agent_launch",
190
+ initiativeId,
191
+ correlationId: sessionId,
192
+ runId: sessionId,
193
+ executionPolicy,
194
+ agentId,
195
+ taskId,
196
+ taskTitle: resolvedTaskTitle,
197
+ workstreamId,
198
+ workstreamTitle: resolvedWorkstreamTitle,
199
+ });
200
+ if (!guard.allowed) {
201
+ void deps
202
+ .posthogCapture({
203
+ event: "openclaw_agent_launch_blocked",
204
+ distinctId: deps.telemetryDistinctId,
205
+ properties: {
206
+ plugin_version: deps.pluginVersion,
207
+ agent_id: agentId,
208
+ initiative_present: Boolean(initiativeId),
209
+ workstream_present: Boolean(workstreamId),
210
+ task_present: Boolean(taskId),
211
+ domain: executionPolicy.domain,
212
+ required_skills_count: executionPolicy.requiredSkills.length,
213
+ retryable: Boolean(guard.retryable),
214
+ blocked_reason: guard.blockedReason ?? null,
215
+ model_tier: deps.extractSpawnGuardModelTier(guard.spawnGuardResult ?? null),
216
+ },
217
+ })
218
+ .catch(() => {
219
+ // best effort
220
+ });
221
+ deps.sendJson(res, guard.retryable ? 429 : 409, {
222
+ ok: false,
223
+ code: guard.retryable
224
+ ? "spawn_guard_rate_limited"
225
+ : "spawn_guard_blocked",
226
+ error: guard.blockedReason ??
227
+ "Spawn guard denied this agent launch.",
228
+ retryable: guard.retryable,
229
+ initiativeId,
230
+ workstreamId,
231
+ taskId,
232
+ domain: executionPolicy.domain,
233
+ requiredSkills: executionPolicy.requiredSkills,
234
+ });
235
+ return;
236
+ }
237
+ const message = deps.buildPolicyEnforcedMessage({
238
+ baseMessage: kickoffMessage,
239
+ executionPolicy,
240
+ spawnGuardResult: guard.spawnGuardResult,
241
+ });
242
+ if (initiativeId) {
243
+ try {
244
+ await deps.client.updateEntity("initiative", initiativeId, { status: "active" });
245
+ }
246
+ catch {
247
+ // best effort
248
+ }
249
+ }
250
+ if (taskId) {
251
+ try {
252
+ await deps.client.updateEntity("task", taskId, { status: "in_progress" });
253
+ }
254
+ catch {
255
+ // best effort
256
+ }
257
+ await deps.syncParentRollupsForTask({
258
+ initiativeId,
259
+ taskId,
260
+ workstreamId,
261
+ correlationId: sessionId,
262
+ });
263
+ }
264
+ await deps.emitActivitySafe({
265
+ initiativeId,
266
+ runId: sessionId,
267
+ correlationId: sessionId,
268
+ phase: "execution",
269
+ message: taskId
270
+ ? `Launched agent ${agentId} for task ${taskId}.`
271
+ : `Launched agent ${agentId}.`,
272
+ level: "info",
273
+ metadata: {
274
+ event: "agent_launch",
275
+ agent_id: agentId,
276
+ session_id: sessionId,
277
+ workstream_id: workstreamId,
278
+ task_id: taskId,
279
+ provider: routingProvider,
280
+ model: requestedModel,
281
+ domain: executionPolicy.domain,
282
+ required_skills: executionPolicy.requiredSkills,
283
+ kickoff_context_hash: kickoffContextHash,
284
+ kickoff_context_source: kickoff ? "orgx" : "fallback",
285
+ orgx_plugin_version: deps.pluginVersion,
286
+ spawn_guard_model_tier: deps.extractSpawnGuardModelTier(guard.spawnGuardResult),
287
+ },
288
+ });
289
+ let routedProvider = null;
290
+ let routedModel = null;
291
+ if (routingProvider) {
292
+ const routed = await deps.configureOpenClawProviderRouting({
293
+ agentId,
294
+ provider: routingProvider,
295
+ requestedModel,
296
+ });
297
+ routedProvider = routed.provider;
298
+ routedModel = routed.model;
299
+ }
300
+ deps.upsertAgentContext({
301
+ agentId,
302
+ initiativeId,
303
+ initiativeTitle,
304
+ workstreamId,
305
+ taskId,
306
+ });
307
+ deps.upsertRunContext({
308
+ runId: sessionId,
309
+ agentId,
310
+ initiativeId,
311
+ initiativeTitle,
312
+ workstreamId,
313
+ taskId,
314
+ });
315
+ const spawned = deps.spawnAgentTurn({
316
+ agentId,
317
+ sessionId,
318
+ message,
319
+ thinking,
320
+ });
321
+ deps.upsertAgentRun({
322
+ runId: sessionId,
323
+ agentId,
324
+ pid: spawned.pid,
325
+ message,
326
+ provider: routedProvider,
327
+ model: routedModel,
328
+ initiativeId,
329
+ initiativeTitle,
330
+ workstreamId,
331
+ taskId,
332
+ startedAt: new Date().toISOString(),
333
+ status: "running",
334
+ });
335
+ void deps
336
+ .posthogCapture({
337
+ event: "openclaw_agent_launch_started",
338
+ distinctId: deps.telemetryDistinctId,
339
+ properties: {
340
+ plugin_version: deps.pluginVersion,
341
+ agent_id: agentId,
342
+ initiative_present: Boolean(initiativeId),
343
+ workstream_present: Boolean(workstreamId),
344
+ task_present: Boolean(taskId),
345
+ domain: executionPolicy.domain,
346
+ required_skills_count: executionPolicy.requiredSkills.length,
347
+ provider: routedProvider,
348
+ model: routedModel,
349
+ model_tier: deps.extractSpawnGuardModelTier(guard.spawnGuardResult ?? null),
350
+ kickoff_context_hash_prefix: typeof kickoffContextHash === "string" && kickoffContextHash.length >= 12
351
+ ? kickoffContextHash.slice(0, 12)
352
+ : kickoffContextHash ?? null,
353
+ },
354
+ })
355
+ .catch(() => {
356
+ // best effort
357
+ });
358
+ deps.sendJson(res, 202, {
359
+ ok: true,
360
+ agentId,
361
+ sessionId,
362
+ pid: spawned.pid,
363
+ provider: routedProvider,
364
+ model: routedModel,
365
+ initiativeId,
366
+ workstreamId,
367
+ taskId,
368
+ startedAt: new Date().toISOString(),
369
+ domain: executionPolicy.domain,
370
+ requiredSkills: executionPolicy.requiredSkills,
371
+ kickoffContextHash,
372
+ });
373
+ }
374
+ catch (err) {
375
+ void deps
376
+ .posthogCapture({
377
+ event: "openclaw_agent_launch_failed",
378
+ distinctId: deps.telemetryDistinctId,
379
+ properties: {
380
+ plugin_version: deps.pluginVersion,
381
+ error: deps.safeErrorMessage(err),
382
+ },
383
+ })
384
+ .catch(() => {
385
+ // best effort
386
+ });
387
+ deps.sendJson(res, 500, {
388
+ ok: false,
389
+ error: deps.safeErrorMessage(err),
390
+ });
391
+ }
392
+ }, "Launch local agent run");
393
+ router.add("POST", "agents/stop", async ({ req, query, res }) => {
394
+ try {
395
+ const payload = await deps.parseJsonRequest(req);
396
+ const runId = (deps.pickString(payload, ["runId", "run_id", "sessionId", "session_id"]) ??
397
+ query.get("runId") ??
398
+ query.get("run_id") ??
399
+ query.get("sessionId") ??
400
+ query.get("session_id") ??
401
+ "")
402
+ .trim();
403
+ if (!runId) {
404
+ deps.sendJson(res, 400, { ok: false, error: "runId is required" });
405
+ return;
406
+ }
407
+ const record = deps.getAgentRun(runId);
408
+ if (!record) {
409
+ deps.sendJson(res, 404, { ok: false, error: "Run not found" });
410
+ return;
411
+ }
412
+ if (!record.pid) {
413
+ deps.sendJson(res, 409, { ok: false, error: "Run has no tracked pid" });
414
+ return;
415
+ }
416
+ const result = await deps.stopProcess(record.pid);
417
+ const updated = deps.markAgentRunStopped(runId);
418
+ try {
419
+ deps.writeRuntimeEvent({
420
+ sourceClient: "codex",
421
+ event: "session_stop",
422
+ runId,
423
+ initiativeId: record.initiativeId ?? "",
424
+ workstreamId: record.workstreamId ?? null,
425
+ taskId: record.taskId ?? null,
426
+ agentId: record.agentId ?? null,
427
+ agentName: null,
428
+ phase: "stopped",
429
+ message: `Agent ${record.agentId ?? "unknown"} stopped.`,
430
+ });
431
+ }
432
+ catch {
433
+ // best effort
434
+ }
435
+ void deps
436
+ .posthogCapture({
437
+ event: "openclaw_agent_stop",
438
+ distinctId: deps.telemetryDistinctId,
439
+ properties: {
440
+ plugin_version: deps.pluginVersion,
441
+ agent_id: record.agentId,
442
+ had_pid: Boolean(record.pid),
443
+ stopped: Boolean(result.stopped),
444
+ was_running: Boolean(result.wasRunning),
445
+ },
446
+ })
447
+ .catch(() => {
448
+ // best effort
449
+ });
450
+ deps.sendJson(res, 200, {
451
+ ok: true,
452
+ runId,
453
+ agentId: record.agentId,
454
+ pid: record.pid,
455
+ stopped: result.stopped,
456
+ wasRunning: result.wasRunning,
457
+ record: updated,
458
+ });
459
+ }
460
+ catch (err) {
461
+ void deps
462
+ .posthogCapture({
463
+ event: "openclaw_agent_stop_failed",
464
+ distinctId: deps.telemetryDistinctId,
465
+ properties: {
466
+ plugin_version: deps.pluginVersion,
467
+ error: deps.safeErrorMessage(err),
468
+ },
469
+ })
470
+ .catch(() => {
471
+ // best effort
472
+ });
473
+ deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
474
+ }
475
+ }, "Stop local agent run");
476
+ router.add("POST", "agents/restart", async ({ req, query, res }) => {
477
+ try {
478
+ const payload = await deps.parseJsonRequest(req);
479
+ const previousRunId = (deps.pickString(payload, ["runId", "run_id", "sessionId", "session_id"]) ??
480
+ query.get("runId") ??
481
+ query.get("run_id") ??
482
+ query.get("sessionId") ??
483
+ query.get("session_id") ??
484
+ "")
485
+ .trim();
486
+ if (!previousRunId) {
487
+ deps.sendJson(res, 400, { ok: false, error: "runId is required" });
488
+ return;
489
+ }
490
+ const record = deps.getAgentRun(previousRunId);
491
+ if (!record) {
492
+ deps.sendJson(res, 404, { ok: false, error: "Run not found" });
493
+ return;
494
+ }
495
+ if (record.pid) {
496
+ try {
497
+ await deps.stopProcess(record.pid);
498
+ }
499
+ catch {
500
+ // best effort
501
+ }
502
+ deps.markAgentRunStopped(previousRunId);
503
+ }
504
+ const messageOverride = (deps.pickString(payload, ["message", "prompt", "text"]) ??
505
+ query.get("message") ??
506
+ query.get("prompt") ??
507
+ query.get("text") ??
508
+ "")
509
+ .trim() || null;
510
+ const providerOverride = deps.normalizeOpenClawProvider(deps.pickString(payload, ["provider", "modelProvider", "model_provider"]) ??
511
+ query.get("provider") ??
512
+ query.get("modelProvider") ??
513
+ query.get("model_provider") ??
514
+ record.provider ??
515
+ null);
516
+ const requestedModel = (deps.pickString(payload, ["model", "modelId", "model_id"]) ??
517
+ query.get("model") ??
518
+ query.get("modelId") ??
519
+ query.get("model_id") ??
520
+ record.model ??
521
+ "")
522
+ .trim() || null;
523
+ const routingProvider = providerOverride ??
524
+ (!providerOverride && !requestedModel ? deps.resolveAutoOpenClawProvider() : null);
525
+ let requiresPremiumRestart = Boolean(routingProvider) ||
526
+ deps.modelImpliesByok(requestedModel) ||
527
+ deps.modelImpliesByok(record.model ?? null);
528
+ if (!requiresPremiumRestart) {
529
+ try {
530
+ const agents = await deps.listAgents();
531
+ const agentEntry = agents.find((entry) => String(entry.id ?? "").trim() === record.agentId) ?? null;
532
+ const agentModel = agentEntry && typeof agentEntry.model === "string"
533
+ ? agentEntry.model
534
+ : null;
535
+ requiresPremiumRestart = deps.modelImpliesByok(agentModel);
536
+ }
537
+ catch {
538
+ // ignore
539
+ }
540
+ }
541
+ if (requiresPremiumRestart) {
542
+ const billingStatus = await deps.fetchBillingStatusSafe(deps.client);
543
+ if (billingStatus && billingStatus.plan === "free") {
544
+ const pricingUrl = `${deps.client.getBaseUrl().replace(/\/+$/, "")}/pricing`;
545
+ deps.sendJson(res, 402, {
546
+ ok: false,
547
+ code: "upgrade_required",
548
+ error: "BYOK agent launch requires a paid OrgX plan. Upgrade, then retry.",
549
+ currentPlan: billingStatus.plan,
550
+ requiredPlan: "starter",
551
+ actions: {
552
+ checkout: "/orgx/api/billing/checkout",
553
+ portal: "/orgx/api/billing/portal",
554
+ pricing: pricingUrl,
555
+ },
556
+ });
557
+ return;
558
+ }
559
+ }
560
+ const sessionId = deps.randomUUID();
561
+ const baseMessage = messageOverride ?? record.message ?? `Restart agent ${record.agentId}`;
562
+ const policyResolution = await deps.resolveDispatchExecutionPolicy({
563
+ initiativeId: record.initiativeId,
564
+ initiativeTitle: record.initiativeTitle,
565
+ workstreamId: record.workstreamId,
566
+ taskId: record.taskId,
567
+ message: baseMessage,
568
+ });
569
+ const executionPolicy = policyResolution.executionPolicy;
570
+ const guard = await deps.enforceSpawnGuardForDispatch({
571
+ sourceEventPrefix: "agent_restart",
572
+ initiativeId: record.initiativeId,
573
+ correlationId: sessionId,
574
+ runId: sessionId,
575
+ executionPolicy,
576
+ agentId: record.agentId,
577
+ taskId: record.taskId,
578
+ taskTitle: policyResolution.taskTitle,
579
+ workstreamId: record.workstreamId,
580
+ workstreamTitle: policyResolution.workstreamTitle,
581
+ });
582
+ if (!guard.allowed) {
583
+ deps.sendJson(res, guard.retryable ? 429 : 409, {
584
+ ok: false,
585
+ code: guard.retryable
586
+ ? "spawn_guard_rate_limited"
587
+ : "spawn_guard_blocked",
588
+ error: guard.blockedReason ??
589
+ "Spawn guard denied this restart.",
590
+ retryable: guard.retryable,
591
+ previousRunId,
592
+ domain: executionPolicy.domain,
593
+ requiredSkills: executionPolicy.requiredSkills,
594
+ });
595
+ return;
596
+ }
597
+ const message = deps.buildPolicyEnforcedMessage({
598
+ baseMessage,
599
+ executionPolicy,
600
+ spawnGuardResult: guard.spawnGuardResult,
601
+ });
602
+ let routedProvider = routingProvider ?? null;
603
+ let routedModel = requestedModel ?? null;
604
+ if (routingProvider) {
605
+ const routed = await deps.configureOpenClawProviderRouting({
606
+ agentId: record.agentId,
607
+ provider: routingProvider,
608
+ requestedModel,
609
+ });
610
+ routedProvider = routed.provider;
611
+ routedModel = routed.model;
612
+ }
613
+ deps.upsertAgentContext({
614
+ agentId: record.agentId,
615
+ initiativeId: record.initiativeId,
616
+ initiativeTitle: record.initiativeTitle,
617
+ workstreamId: record.workstreamId,
618
+ taskId: record.taskId,
619
+ });
620
+ const spawned = deps.spawnAgentTurn({
621
+ agentId: record.agentId,
622
+ sessionId,
623
+ message,
624
+ });
625
+ deps.upsertAgentRun({
626
+ runId: sessionId,
627
+ agentId: record.agentId,
628
+ pid: spawned.pid,
629
+ message,
630
+ provider: routedProvider,
631
+ model: routedModel,
632
+ initiativeId: record.initiativeId,
633
+ initiativeTitle: record.initiativeTitle,
634
+ workstreamId: record.workstreamId,
635
+ taskId: record.taskId,
636
+ startedAt: new Date().toISOString(),
637
+ status: "running",
638
+ });
639
+ void deps
640
+ .posthogCapture({
641
+ event: "openclaw_agent_restart_started",
642
+ distinctId: deps.telemetryDistinctId,
643
+ properties: {
644
+ plugin_version: deps.pluginVersion,
645
+ agent_id: record.agentId,
646
+ provider: routedProvider,
647
+ model: routedModel,
648
+ domain: executionPolicy.domain,
649
+ required_skills_count: executionPolicy.requiredSkills.length,
650
+ model_tier: deps.extractSpawnGuardModelTier(guard.spawnGuardResult ?? null),
651
+ },
652
+ })
653
+ .catch(() => {
654
+ // best effort
655
+ });
656
+ deps.sendJson(res, 202, {
657
+ ok: true,
658
+ previousRunId,
659
+ sessionId,
660
+ agentId: record.agentId,
661
+ pid: spawned.pid,
662
+ provider: routedProvider,
663
+ model: routedModel,
664
+ domain: executionPolicy.domain,
665
+ requiredSkills: executionPolicy.requiredSkills,
666
+ });
667
+ }
668
+ catch (err) {
669
+ void deps
670
+ .posthogCapture({
671
+ event: "openclaw_agent_restart_failed",
672
+ distinctId: deps.telemetryDistinctId,
673
+ properties: {
674
+ plugin_version: deps.pluginVersion,
675
+ error: deps.safeErrorMessage(err),
676
+ },
677
+ })
678
+ .catch(() => {
679
+ // best effort
680
+ });
681
+ deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
682
+ }
683
+ }, "Restart local agent run");
684
+ }