@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,541 @@
|
|
|
1
|
+
const PLAY_QUEUE_LOOKUP_TIMEOUT_MS = (() => {
|
|
2
|
+
const raw = process.env.ORGX_PLAY_QUEUE_LOOKUP_TIMEOUT_MS;
|
|
3
|
+
const parsed = Number(raw);
|
|
4
|
+
if (!Number.isFinite(parsed))
|
|
5
|
+
return 350;
|
|
6
|
+
return Math.max(200, Math.floor(parsed));
|
|
7
|
+
})();
|
|
8
|
+
async function withSoftTimeout(work, timeoutMs) {
|
|
9
|
+
let timer = null;
|
|
10
|
+
try {
|
|
11
|
+
return await Promise.race([
|
|
12
|
+
work,
|
|
13
|
+
new Promise((_, reject) => {
|
|
14
|
+
timer = setTimeout(() => {
|
|
15
|
+
reject(new Error(`timed out after ${timeoutMs}ms`));
|
|
16
|
+
}, timeoutMs);
|
|
17
|
+
}),
|
|
18
|
+
]);
|
|
19
|
+
}
|
|
20
|
+
finally {
|
|
21
|
+
if (timer)
|
|
22
|
+
clearTimeout(timer);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function registerMissionControlActionsRoutes(router, deps) {
|
|
26
|
+
router.add("POST", "mission-control/next-up/play", async ({ req, query, res }) => {
|
|
27
|
+
try {
|
|
28
|
+
const payload = await deps.parseJsonRequest(req);
|
|
29
|
+
const initiativeId = (deps.pickString(payload, ["initiativeId", "initiative_id"]) ??
|
|
30
|
+
query.get("initiativeId") ??
|
|
31
|
+
query.get("initiative_id") ??
|
|
32
|
+
"")
|
|
33
|
+
.trim();
|
|
34
|
+
const workstreamId = (deps.pickString(payload, ["workstreamId", "workstream_id"]) ??
|
|
35
|
+
query.get("workstreamId") ??
|
|
36
|
+
query.get("workstream_id") ??
|
|
37
|
+
"")
|
|
38
|
+
.trim();
|
|
39
|
+
if (!initiativeId || !workstreamId) {
|
|
40
|
+
deps.sendJson(res, 400, {
|
|
41
|
+
ok: false,
|
|
42
|
+
error: "initiativeId and workstreamId are required",
|
|
43
|
+
});
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
let agentIdRaw = (deps.pickString(payload, ["agentId", "agent_id"]) ??
|
|
47
|
+
query.get("agentId") ??
|
|
48
|
+
query.get("agent_id") ??
|
|
49
|
+
"")
|
|
50
|
+
.trim();
|
|
51
|
+
const fastAckRaw = payload.fastAck ??
|
|
52
|
+
payload.fast_ack ??
|
|
53
|
+
query.get("fastAck") ??
|
|
54
|
+
query.get("fast_ack") ??
|
|
55
|
+
null;
|
|
56
|
+
const fastAck = typeof fastAckRaw === "boolean"
|
|
57
|
+
? fastAckRaw
|
|
58
|
+
: deps.parseBooleanQuery(typeof fastAckRaw === "string" ? fastAckRaw : null);
|
|
59
|
+
let matchedQueueItem = null;
|
|
60
|
+
const shouldLookupQueue = !fastAck || !agentIdRaw;
|
|
61
|
+
if (shouldLookupQueue) {
|
|
62
|
+
try {
|
|
63
|
+
const queue = fastAck
|
|
64
|
+
? await withSoftTimeout(deps.buildNextUpQueue({ initiativeId }), PLAY_QUEUE_LOOKUP_TIMEOUT_MS)
|
|
65
|
+
: await deps.buildNextUpQueue({ initiativeId });
|
|
66
|
+
matchedQueueItem =
|
|
67
|
+
queue.items.find((item) => item.workstreamId === workstreamId) ?? null;
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// Best effort: Play/Autopilot dispatch should still proceed even if queue refresh is slow.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (!agentIdRaw && matchedQueueItem?.runnerAgentId) {
|
|
74
|
+
agentIdRaw = matchedQueueItem.runnerAgentId;
|
|
75
|
+
}
|
|
76
|
+
const agentId = agentIdRaw || "main";
|
|
77
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(agentId)) {
|
|
78
|
+
deps.sendJson(res, 400, {
|
|
79
|
+
ok: false,
|
|
80
|
+
error: "agentId must be a simple identifier (letters, numbers, _ or -).",
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const requestedAgentName = await deps.resolveAgentDisplayName(agentId, matchedQueueItem?.runnerAgentId === agentId
|
|
85
|
+
? matchedQueueItem.runnerAgentName ?? null
|
|
86
|
+
: null);
|
|
87
|
+
const tokenBudget = deps.pickNumber(payload, [
|
|
88
|
+
"tokenBudget",
|
|
89
|
+
"token_budget",
|
|
90
|
+
"tokenBudgetTokens",
|
|
91
|
+
"token_budget_tokens",
|
|
92
|
+
"maxTokens",
|
|
93
|
+
"max_tokens",
|
|
94
|
+
]) ??
|
|
95
|
+
query.get("tokenBudget") ??
|
|
96
|
+
query.get("token_budget") ??
|
|
97
|
+
query.get("tokenBudgetTokens") ??
|
|
98
|
+
query.get("token_budget_tokens") ??
|
|
99
|
+
query.get("maxTokens") ??
|
|
100
|
+
query.get("max_tokens") ??
|
|
101
|
+
null;
|
|
102
|
+
const includeVerificationRaw = payload.includeVerification ??
|
|
103
|
+
payload.include_verification ??
|
|
104
|
+
query.get("includeVerification") ??
|
|
105
|
+
query.get("include_verification") ??
|
|
106
|
+
null;
|
|
107
|
+
const includeVerification = typeof includeVerificationRaw === "boolean"
|
|
108
|
+
? includeVerificationRaw
|
|
109
|
+
: deps.parseBooleanQuery(typeof includeVerificationRaw === "string"
|
|
110
|
+
? includeVerificationRaw
|
|
111
|
+
: null);
|
|
112
|
+
const existingRun = deps.autoContinueRuns.get(initiativeId) ?? null;
|
|
113
|
+
if (existingRun &&
|
|
114
|
+
(existingRun.status === "running" || existingRun.status === "stopping") &&
|
|
115
|
+
existingRun.activeRunId) {
|
|
116
|
+
const activeSlice = deps.autoContinueSliceRuns.get(existingRun.activeRunId) ?? null;
|
|
117
|
+
const activeWorkstreamId = activeSlice?.workstreamId ?? null;
|
|
118
|
+
const activeWorkstreamTitle = activeSlice?.workstreamTitle ?? null;
|
|
119
|
+
deps.sendJson(res, 409, {
|
|
120
|
+
ok: false,
|
|
121
|
+
code: "auto_continue_already_running",
|
|
122
|
+
error: activeWorkstreamId || activeWorkstreamTitle
|
|
123
|
+
? `Auto-continue is already running for ${activeWorkstreamTitle ?? activeWorkstreamId}. Stop it before launching another Play run.`
|
|
124
|
+
: "Auto-continue is already running for this initiative. Stop it before launching another Play run.",
|
|
125
|
+
run: existingRun,
|
|
126
|
+
activeWorkstreamId,
|
|
127
|
+
activeWorkstreamTitle,
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const run = await deps.startAutoContinueRun({
|
|
132
|
+
initiativeId,
|
|
133
|
+
agentId,
|
|
134
|
+
agentName: requestedAgentName,
|
|
135
|
+
tokenBudget,
|
|
136
|
+
includeVerification,
|
|
137
|
+
allowedWorkstreamIds: [workstreamId],
|
|
138
|
+
stopAfterSlice: true,
|
|
139
|
+
});
|
|
140
|
+
let fallbackDispatch = null;
|
|
141
|
+
const maybeDispatchFallback = async () => {
|
|
142
|
+
if (!run.activeRunId &&
|
|
143
|
+
matchedQueueItem &&
|
|
144
|
+
matchedQueueItem.runnerSource === "fallback") {
|
|
145
|
+
return await deps.dispatchFallbackWorkstreamTurn({
|
|
146
|
+
initiativeId,
|
|
147
|
+
initiativeTitle: matchedQueueItem.initiativeTitle,
|
|
148
|
+
workstreamId,
|
|
149
|
+
workstreamTitle: matchedQueueItem.workstreamTitle,
|
|
150
|
+
agentId,
|
|
151
|
+
agentName: requestedAgentName,
|
|
152
|
+
taskId: matchedQueueItem.nextTaskId ?? null,
|
|
153
|
+
taskTitle: matchedQueueItem.nextTaskTitle ?? null,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
};
|
|
158
|
+
if (!fastAck) {
|
|
159
|
+
await deps.tickAutoContinueRun(run);
|
|
160
|
+
// Give short-lived workers a brief window to flush output so Play can resolve
|
|
161
|
+
// in one request/response cycle without requiring extra manual ticks.
|
|
162
|
+
if (run.activeRunId) {
|
|
163
|
+
await new Promise((resolve) => setTimeout(resolve, 140));
|
|
164
|
+
await deps.tickAutoContinueRun(run);
|
|
165
|
+
}
|
|
166
|
+
fallbackDispatch = await maybeDispatchFallback();
|
|
167
|
+
}
|
|
168
|
+
else {
|
|
169
|
+
const tickPromise = deps.tickAutoContinueRun(run);
|
|
170
|
+
const tickCompleted = await Promise.race([
|
|
171
|
+
tickPromise.then(() => true),
|
|
172
|
+
new Promise((resolve) => setTimeout(() => resolve(false), 1100)),
|
|
173
|
+
]);
|
|
174
|
+
if (!tickCompleted) {
|
|
175
|
+
void tickPromise
|
|
176
|
+
.then(async () => {
|
|
177
|
+
await maybeDispatchFallback().catch(() => null);
|
|
178
|
+
})
|
|
179
|
+
.catch(() => {
|
|
180
|
+
// best effort
|
|
181
|
+
});
|
|
182
|
+
deps.sendJson(res, 202, {
|
|
183
|
+
ok: true,
|
|
184
|
+
run,
|
|
185
|
+
initiativeId,
|
|
186
|
+
workstreamId,
|
|
187
|
+
agentId,
|
|
188
|
+
dispatchMode: "pending",
|
|
189
|
+
sessionId: null,
|
|
190
|
+
});
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
await tickPromise;
|
|
194
|
+
fallbackDispatch = await maybeDispatchFallback();
|
|
195
|
+
}
|
|
196
|
+
const fallbackStarted = Boolean(fallbackDispatch?.sessionId);
|
|
197
|
+
const dispatchMode = run.activeRunId
|
|
198
|
+
? "slice"
|
|
199
|
+
: fallbackStarted
|
|
200
|
+
? "fallback"
|
|
201
|
+
: "none";
|
|
202
|
+
if (dispatchMode === "none" &&
|
|
203
|
+
run.lastRunId &&
|
|
204
|
+
(run.stopReason === "completed" ||
|
|
205
|
+
run.stopReason === "blocked" ||
|
|
206
|
+
run.stopReason === "error")) {
|
|
207
|
+
const finalizedDispatchMode = run.stopReason === "completed"
|
|
208
|
+
? "slice_completed"
|
|
209
|
+
: run.stopReason === "blocked"
|
|
210
|
+
? "slice_blocked"
|
|
211
|
+
: "slice_error";
|
|
212
|
+
deps.sendJson(res, 200, {
|
|
213
|
+
ok: true,
|
|
214
|
+
run,
|
|
215
|
+
initiativeId,
|
|
216
|
+
workstreamId,
|
|
217
|
+
agentId,
|
|
218
|
+
dispatchMode: finalizedDispatchMode,
|
|
219
|
+
sessionId: run.lastRunId,
|
|
220
|
+
});
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (dispatchMode === "none") {
|
|
224
|
+
const fallbackBlockedReason = fallbackDispatch?.blockedReason ?? null;
|
|
225
|
+
const reason = fallbackBlockedReason ??
|
|
226
|
+
(run.stopReason === "blocked"
|
|
227
|
+
? "No dispatchable task is ready for this workstream yet."
|
|
228
|
+
: run.stopReason === "completed"
|
|
229
|
+
? "No queued task is available for this workstream."
|
|
230
|
+
: "Unable to dispatch this workstream right now.");
|
|
231
|
+
deps.sendJson(res, fallbackDispatch?.retryable ? 429 : 409, {
|
|
232
|
+
ok: false,
|
|
233
|
+
code: fallbackBlockedReason
|
|
234
|
+
? fallbackDispatch?.retryable
|
|
235
|
+
? "spawn_guard_rate_limited"
|
|
236
|
+
: "spawn_guard_blocked"
|
|
237
|
+
: undefined,
|
|
238
|
+
error: reason,
|
|
239
|
+
run,
|
|
240
|
+
initiativeId,
|
|
241
|
+
workstreamId,
|
|
242
|
+
agentId,
|
|
243
|
+
fallbackDispatch,
|
|
244
|
+
});
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
deps.sendJson(res, 200, {
|
|
248
|
+
ok: true,
|
|
249
|
+
run,
|
|
250
|
+
initiativeId,
|
|
251
|
+
workstreamId,
|
|
252
|
+
agentId,
|
|
253
|
+
dispatchMode,
|
|
254
|
+
sessionId: run.activeRunId ?? fallbackDispatch?.sessionId ?? null,
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
|
|
259
|
+
}
|
|
260
|
+
}, "Mission-control next-up play");
|
|
261
|
+
router.add("POST", "mission-control/next-up/pin", async ({ req, query, res }) => {
|
|
262
|
+
try {
|
|
263
|
+
const payload = await deps.parseJsonRequest(req);
|
|
264
|
+
const initiativeId = (deps.pickString(payload, ["initiativeId", "initiative_id"]) ??
|
|
265
|
+
query.get("initiativeId") ??
|
|
266
|
+
query.get("initiative_id") ??
|
|
267
|
+
"")
|
|
268
|
+
.trim();
|
|
269
|
+
const workstreamId = (deps.pickString(payload, ["workstreamId", "workstream_id"]) ??
|
|
270
|
+
query.get("workstreamId") ??
|
|
271
|
+
query.get("workstream_id") ??
|
|
272
|
+
"")
|
|
273
|
+
.trim();
|
|
274
|
+
const preferredTaskId = (deps.pickString(payload, [
|
|
275
|
+
"taskId",
|
|
276
|
+
"task_id",
|
|
277
|
+
"preferredTaskId",
|
|
278
|
+
"preferred_task_id",
|
|
279
|
+
]) ?? "")
|
|
280
|
+
.trim() || null;
|
|
281
|
+
const preferredMilestoneId = (deps.pickString(payload, [
|
|
282
|
+
"milestoneId",
|
|
283
|
+
"milestone_id",
|
|
284
|
+
"preferredMilestoneId",
|
|
285
|
+
"preferred_milestone_id",
|
|
286
|
+
]) ?? "")
|
|
287
|
+
.trim() || null;
|
|
288
|
+
if (!initiativeId || !workstreamId) {
|
|
289
|
+
deps.sendJson(res, 400, {
|
|
290
|
+
ok: false,
|
|
291
|
+
error: "initiativeId and workstreamId are required",
|
|
292
|
+
});
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const next = deps.upsertNextUpQueuePin({
|
|
296
|
+
initiativeId,
|
|
297
|
+
workstreamId,
|
|
298
|
+
preferredTaskId,
|
|
299
|
+
preferredMilestoneId,
|
|
300
|
+
});
|
|
301
|
+
deps.sendJson(res, 200, { ok: true, pins: next.pins, updatedAt: next.updatedAt });
|
|
302
|
+
}
|
|
303
|
+
catch (err) {
|
|
304
|
+
deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
|
|
305
|
+
}
|
|
306
|
+
}, "Mission-control next-up pin");
|
|
307
|
+
router.add("POST", "mission-control/next-up/unpin", async ({ req, query, res }) => {
|
|
308
|
+
try {
|
|
309
|
+
const payload = await deps.parseJsonRequest(req);
|
|
310
|
+
const initiativeId = (deps.pickString(payload, ["initiativeId", "initiative_id"]) ??
|
|
311
|
+
query.get("initiativeId") ??
|
|
312
|
+
query.get("initiative_id") ??
|
|
313
|
+
"")
|
|
314
|
+
.trim();
|
|
315
|
+
const workstreamId = (deps.pickString(payload, ["workstreamId", "workstream_id"]) ??
|
|
316
|
+
query.get("workstreamId") ??
|
|
317
|
+
query.get("workstream_id") ??
|
|
318
|
+
"")
|
|
319
|
+
.trim();
|
|
320
|
+
if (!initiativeId || !workstreamId) {
|
|
321
|
+
deps.sendJson(res, 400, {
|
|
322
|
+
ok: false,
|
|
323
|
+
error: "initiativeId and workstreamId are required",
|
|
324
|
+
});
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
const next = deps.removeNextUpQueuePin({ initiativeId, workstreamId });
|
|
328
|
+
deps.sendJson(res, 200, { ok: true, pins: next.pins, updatedAt: next.updatedAt });
|
|
329
|
+
}
|
|
330
|
+
catch (err) {
|
|
331
|
+
deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
|
|
332
|
+
}
|
|
333
|
+
}, "Mission-control next-up unpin");
|
|
334
|
+
router.add("POST", "mission-control/next-up/reorder", async ({ req, res }) => {
|
|
335
|
+
try {
|
|
336
|
+
const payload = await deps.parseJsonRequest(req);
|
|
337
|
+
const rawOrder = Array.isArray(payload?.order)
|
|
338
|
+
? payload.order
|
|
339
|
+
: [];
|
|
340
|
+
const order = [];
|
|
341
|
+
for (const entry of rawOrder) {
|
|
342
|
+
if (!entry)
|
|
343
|
+
continue;
|
|
344
|
+
if (typeof entry === "string") {
|
|
345
|
+
const [initiativeId, workstreamId] = entry.split(":", 2).map((s) => s.trim());
|
|
346
|
+
if (initiativeId && workstreamId)
|
|
347
|
+
order.push({ initiativeId, workstreamId });
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (typeof entry === "object") {
|
|
351
|
+
const record = entry;
|
|
352
|
+
const initiativeId = (deps.pickString(record, ["initiativeId", "initiative_id"]) ?? "").trim();
|
|
353
|
+
const workstreamId = (deps.pickString(record, ["workstreamId", "workstream_id"]) ?? "").trim();
|
|
354
|
+
if (initiativeId && workstreamId)
|
|
355
|
+
order.push({ initiativeId, workstreamId });
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
const next = deps.setNextUpQueuePinOrder({ order });
|
|
359
|
+
deps.sendJson(res, 200, { ok: true, pins: next.pins, updatedAt: next.updatedAt });
|
|
360
|
+
}
|
|
361
|
+
catch (err) {
|
|
362
|
+
deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
|
|
363
|
+
}
|
|
364
|
+
}, "Mission-control next-up reorder");
|
|
365
|
+
router.add("POST", "mission-control/auto-continue/start", async ({ req, query, res }) => {
|
|
366
|
+
try {
|
|
367
|
+
const payload = await deps.parseJsonRequest(req);
|
|
368
|
+
const initiativeId = (deps.pickString(payload, ["initiativeId", "initiative_id"]) ??
|
|
369
|
+
query.get("initiativeId") ??
|
|
370
|
+
query.get("initiative_id") ??
|
|
371
|
+
"")
|
|
372
|
+
.trim();
|
|
373
|
+
if (!initiativeId) {
|
|
374
|
+
deps.sendJson(res, 400, { ok: false, error: "initiativeId is required" });
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
const agentIdRaw = (deps.pickString(payload, ["agentId", "agent_id"]) ??
|
|
378
|
+
query.get("agentId") ??
|
|
379
|
+
query.get("agent_id") ??
|
|
380
|
+
"main")
|
|
381
|
+
.trim();
|
|
382
|
+
const agentId = agentIdRaw || "main";
|
|
383
|
+
if (!/^[a-zA-Z0-9_-]+$/.test(agentId)) {
|
|
384
|
+
deps.sendJson(res, 400, {
|
|
385
|
+
ok: false,
|
|
386
|
+
error: "agentId must be a simple identifier (letters, numbers, _ or -).",
|
|
387
|
+
});
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
const tokenBudget = deps.pickNumber(payload, [
|
|
391
|
+
"tokenBudget",
|
|
392
|
+
"token_budget",
|
|
393
|
+
"tokenBudgetTokens",
|
|
394
|
+
"token_budget_tokens",
|
|
395
|
+
"maxTokens",
|
|
396
|
+
"max_tokens",
|
|
397
|
+
]) ??
|
|
398
|
+
query.get("tokenBudget") ??
|
|
399
|
+
query.get("token_budget") ??
|
|
400
|
+
query.get("tokenBudgetTokens") ??
|
|
401
|
+
query.get("token_budget_tokens") ??
|
|
402
|
+
query.get("maxTokens") ??
|
|
403
|
+
query.get("max_tokens") ??
|
|
404
|
+
null;
|
|
405
|
+
const includeVerificationRaw = payload.includeVerification ??
|
|
406
|
+
payload.include_verification ??
|
|
407
|
+
query.get("includeVerification") ??
|
|
408
|
+
query.get("include_verification") ??
|
|
409
|
+
null;
|
|
410
|
+
const includeVerification = typeof includeVerificationRaw === "boolean"
|
|
411
|
+
? includeVerificationRaw
|
|
412
|
+
: deps.parseBooleanQuery(typeof includeVerificationRaw === "string"
|
|
413
|
+
? includeVerificationRaw
|
|
414
|
+
: null);
|
|
415
|
+
const workstreamFilter = deps.dedupeStrings([
|
|
416
|
+
...deps.pickStringArray(payload, [
|
|
417
|
+
"workstreamIds",
|
|
418
|
+
"workstream_ids",
|
|
419
|
+
"workstreamId",
|
|
420
|
+
"workstream_id",
|
|
421
|
+
]),
|
|
422
|
+
...(query.get("workstreamIds") ??
|
|
423
|
+
query.get("workstream_ids") ??
|
|
424
|
+
query.get("workstreamId") ??
|
|
425
|
+
query.get("workstream_id") ??
|
|
426
|
+
"")
|
|
427
|
+
.split(",")
|
|
428
|
+
.map((entry) => entry.trim())
|
|
429
|
+
.filter(Boolean),
|
|
430
|
+
]);
|
|
431
|
+
const allowedWorkstreamIds = workstreamFilter.length > 0 ? workstreamFilter : null;
|
|
432
|
+
const run = await deps.startAutoContinueRun({
|
|
433
|
+
initiativeId,
|
|
434
|
+
agentId,
|
|
435
|
+
agentName: await deps.resolveAgentDisplayName(agentId, null),
|
|
436
|
+
tokenBudget,
|
|
437
|
+
includeVerification,
|
|
438
|
+
allowedWorkstreamIds,
|
|
439
|
+
});
|
|
440
|
+
deps.sendJson(res, 200, { ok: true, run });
|
|
441
|
+
}
|
|
442
|
+
catch (err) {
|
|
443
|
+
deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
|
|
444
|
+
}
|
|
445
|
+
}, "Mission-control auto-continue start");
|
|
446
|
+
router.add("POST", "mission-control/auto-continue/stop", async ({ req, query, res }) => {
|
|
447
|
+
try {
|
|
448
|
+
const payload = await deps.parseJsonRequest(req);
|
|
449
|
+
const initiativeId = (deps.pickString(payload, ["initiativeId", "initiative_id"]) ??
|
|
450
|
+
query.get("initiativeId") ??
|
|
451
|
+
query.get("initiative_id") ??
|
|
452
|
+
"")
|
|
453
|
+
.trim();
|
|
454
|
+
if (!initiativeId) {
|
|
455
|
+
deps.sendJson(res, 400, { ok: false, error: "initiativeId is required" });
|
|
456
|
+
return;
|
|
457
|
+
}
|
|
458
|
+
const run = deps.autoContinueRuns.get(initiativeId) ?? null;
|
|
459
|
+
if (!run) {
|
|
460
|
+
deps.sendJson(res, 404, { ok: false, error: "No auto-continue run found" });
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
const now = new Date().toISOString();
|
|
464
|
+
run.stopRequested = true;
|
|
465
|
+
run.status = run.activeRunId ? "stopping" : "stopped";
|
|
466
|
+
run.updatedAt = now;
|
|
467
|
+
if (!run.activeRunId) {
|
|
468
|
+
await deps.stopAutoContinueRun({ run, reason: "stopped" });
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
try {
|
|
472
|
+
await deps.updateInitiativeAutoContinueState({ initiativeId, run });
|
|
473
|
+
}
|
|
474
|
+
catch {
|
|
475
|
+
// best effort
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
deps.sendJson(res, 200, { ok: true, run });
|
|
479
|
+
}
|
|
480
|
+
catch (err) {
|
|
481
|
+
deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
|
|
482
|
+
}
|
|
483
|
+
}, "Mission-control auto-continue stop");
|
|
484
|
+
router.add("POST", "mission-control/auto-continue/tick", async ({ req, query, res }) => {
|
|
485
|
+
try {
|
|
486
|
+
const payload = await deps.parseJsonRequest(req);
|
|
487
|
+
const initiativeId = (deps.pickString(payload, ["initiativeId", "initiative_id"]) ??
|
|
488
|
+
query.get("initiativeId") ??
|
|
489
|
+
query.get("initiative_id") ??
|
|
490
|
+
"")
|
|
491
|
+
.trim();
|
|
492
|
+
if (initiativeId) {
|
|
493
|
+
const run = deps.autoContinueRuns.get(initiativeId) ?? null;
|
|
494
|
+
if (!run) {
|
|
495
|
+
deps.sendJson(res, 404, { ok: false, error: "No auto-continue run found" });
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
await deps.tickAutoContinueRun(run);
|
|
499
|
+
deps.sendJson(res, 200, { ok: true, initiativeId, run });
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
await deps.tickAllAutoContinue();
|
|
503
|
+
deps.sendJson(res, 200, { ok: true });
|
|
504
|
+
}
|
|
505
|
+
catch (err) {
|
|
506
|
+
deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
|
|
507
|
+
}
|
|
508
|
+
}, "Mission-control auto-continue tick");
|
|
509
|
+
router.add("POST", "mission-control/assignments/auto", async ({ req, res }) => {
|
|
510
|
+
try {
|
|
511
|
+
const payload = await deps.parseJsonRequest(req);
|
|
512
|
+
const entityId = deps.pickString(payload, ["entity_id", "entityId"]);
|
|
513
|
+
const entityType = deps.pickString(payload, ["entity_type", "entityType"]);
|
|
514
|
+
const initiativeId = deps.pickString(payload, ["initiative_id", "initiativeId"]) ?? null;
|
|
515
|
+
const title = deps.pickString(payload, ["title", "name"]) ?? "Untitled";
|
|
516
|
+
const summary = deps.pickString(payload, ["summary", "description", "context"]) ?? null;
|
|
517
|
+
if (!entityId || !entityType) {
|
|
518
|
+
deps.sendJson(res, 400, {
|
|
519
|
+
ok: false,
|
|
520
|
+
error: "entity_id and entity_type are required.",
|
|
521
|
+
});
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
const assignment = await deps.resolveAutoAssignments({
|
|
525
|
+
client: deps.client,
|
|
526
|
+
entityId,
|
|
527
|
+
entityType,
|
|
528
|
+
initiativeId,
|
|
529
|
+
title,
|
|
530
|
+
summary,
|
|
531
|
+
});
|
|
532
|
+
deps.sendJson(res, 200, assignment);
|
|
533
|
+
}
|
|
534
|
+
catch (err) {
|
|
535
|
+
deps.sendJson(res, 500, {
|
|
536
|
+
ok: false,
|
|
537
|
+
error: deps.safeErrorMessage(err),
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}, "Mission-control auto assignment");
|
|
541
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { Router } from "../router.js";
|
|
2
|
+
type AutoContinueRunRecord = {
|
|
3
|
+
id?: string;
|
|
4
|
+
initiativeId?: string;
|
|
5
|
+
status?: string;
|
|
6
|
+
startedAt?: string;
|
|
7
|
+
stoppedAt?: string | null;
|
|
8
|
+
tokenBudget?: number;
|
|
9
|
+
tickMs?: number;
|
|
10
|
+
};
|
|
11
|
+
type NextUpQueue = {
|
|
12
|
+
items: unknown[];
|
|
13
|
+
degraded: string[];
|
|
14
|
+
};
|
|
15
|
+
type RegisterMissionControlReadRoutesDeps<TRes> = {
|
|
16
|
+
autoContinueRuns: Map<string, AutoContinueRunRecord>;
|
|
17
|
+
defaultAutoContinueTokenBudget: () => number;
|
|
18
|
+
autoContinueTickMs: number;
|
|
19
|
+
buildMissionControlGraph: (initiativeId: string) => Promise<unknown>;
|
|
20
|
+
applyLocalInitiativeOverrideToGraph: (graph: unknown) => unknown;
|
|
21
|
+
buildNextUpQueue: (input: {
|
|
22
|
+
initiativeId: string | null;
|
|
23
|
+
}) => Promise<NextUpQueue>;
|
|
24
|
+
sendJson: (res: TRes, status: number, payload: unknown) => void;
|
|
25
|
+
safeErrorMessage: (err: unknown) => string;
|
|
26
|
+
};
|
|
27
|
+
export declare function registerMissionControlReadRoutes<TReq, TRes>(router: Router<Record<string, never>, TReq, TRes>, deps: RegisterMissionControlReadRoutesDeps<TRes>): void;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
export function registerMissionControlReadRoutes(router, deps) {
|
|
2
|
+
async function renderAutoContinueStatus(query, res) {
|
|
3
|
+
const initiativeId = query.get("initiative_id") ?? query.get("initiativeId") ?? "";
|
|
4
|
+
const id = initiativeId.trim();
|
|
5
|
+
if (!id) {
|
|
6
|
+
deps.sendJson(res, 400, {
|
|
7
|
+
ok: false,
|
|
8
|
+
error: "Query parameter 'initiative_id' is required.",
|
|
9
|
+
});
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
const run = deps.autoContinueRuns.get(id) ?? null;
|
|
13
|
+
deps.sendJson(res, 200, {
|
|
14
|
+
ok: true,
|
|
15
|
+
initiativeId: id,
|
|
16
|
+
run,
|
|
17
|
+
defaults: {
|
|
18
|
+
tokenBudget: deps.defaultAutoContinueTokenBudget(),
|
|
19
|
+
tickMs: deps.autoContinueTickMs,
|
|
20
|
+
},
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
async function renderMissionControlGraph(query, res) {
|
|
24
|
+
const initiativeId = query.get("initiative_id") ?? query.get("initiativeId");
|
|
25
|
+
if (!initiativeId || initiativeId.trim().length === 0) {
|
|
26
|
+
deps.sendJson(res, 400, {
|
|
27
|
+
error: "Query parameter 'initiative_id' is required.",
|
|
28
|
+
});
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const graph = deps.applyLocalInitiativeOverrideToGraph(await deps.buildMissionControlGraph(initiativeId.trim()));
|
|
33
|
+
deps.sendJson(res, 200, graph);
|
|
34
|
+
}
|
|
35
|
+
catch (err) {
|
|
36
|
+
deps.sendJson(res, 500, {
|
|
37
|
+
error: deps.safeErrorMessage(err),
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async function renderNextUpQueue(query, res) {
|
|
42
|
+
const initiativeIdRaw = query.get("initiative_id") ?? query.get("initiativeId") ?? "";
|
|
43
|
+
const initiativeId = initiativeIdRaw.trim() || null;
|
|
44
|
+
try {
|
|
45
|
+
const queue = await deps.buildNextUpQueue({ initiativeId });
|
|
46
|
+
deps.sendJson(res, 200, {
|
|
47
|
+
ok: true,
|
|
48
|
+
generatedAt: new Date().toISOString(),
|
|
49
|
+
total: queue.items.length,
|
|
50
|
+
items: queue.items,
|
|
51
|
+
degraded: queue.degraded,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
deps.sendJson(res, 500, {
|
|
56
|
+
ok: false,
|
|
57
|
+
error: deps.safeErrorMessage(err),
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
router.add("GET", "mission-control/auto-continue/status", async ({ query, res }) => renderAutoContinueStatus(query, res), "Get auto-continue status for an initiative");
|
|
62
|
+
router.add("HEAD", "mission-control/auto-continue/status", async ({ query, res }) => renderAutoContinueStatus(query, res), "Get auto-continue status for an initiative (HEAD)");
|
|
63
|
+
router.add("GET", "mission-control/graph", async ({ query, res }) => renderMissionControlGraph(query, res), "Get mission-control dependency graph");
|
|
64
|
+
router.add("HEAD", "mission-control/graph", async ({ query, res }) => renderMissionControlGraph(query, res), "Get mission-control dependency graph (HEAD)");
|
|
65
|
+
router.add("GET", "mission-control/next-up", async ({ query, res }) => renderNextUpQueue(query, res), "Get next-up queue");
|
|
66
|
+
router.add("HEAD", "mission-control/next-up", async ({ query, res }) => renderNextUpQueue(query, res), "Get next-up queue (HEAD)");
|
|
67
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import type { OnboardingState } from "../../types.js";
|
|
2
|
+
import type { Router } from "../router.js";
|
|
3
|
+
type JsonRecord = Record<string, unknown>;
|
|
4
|
+
type OnboardingControllerLike = {
|
|
5
|
+
startPairing: (input: {
|
|
6
|
+
openclawVersion?: string;
|
|
7
|
+
platform?: string;
|
|
8
|
+
deviceName?: string;
|
|
9
|
+
}) => Promise<{
|
|
10
|
+
pairingId: string;
|
|
11
|
+
connectUrl: string;
|
|
12
|
+
expiresAt: string | null;
|
|
13
|
+
pollIntervalMs: number | null;
|
|
14
|
+
state: OnboardingState;
|
|
15
|
+
}>;
|
|
16
|
+
getStatus: () => Promise<OnboardingState>;
|
|
17
|
+
submitManualKey: (input: {
|
|
18
|
+
apiKey: string;
|
|
19
|
+
userId?: string;
|
|
20
|
+
}) => Promise<OnboardingState>;
|
|
21
|
+
disconnect: () => Promise<OnboardingState>;
|
|
22
|
+
};
|
|
23
|
+
type RegisterOnboardingRoutesDeps<TReq, TRes> = {
|
|
24
|
+
onboarding: OnboardingControllerLike;
|
|
25
|
+
parseJsonRequest: (req: TReq) => Promise<JsonRecord>;
|
|
26
|
+
pickString: (input: unknown, keys: string[]) => string | null;
|
|
27
|
+
pickHeaderString: (headers: unknown, names: string[]) => string | null;
|
|
28
|
+
isUserScopedApiKey: (apiKey: string) => boolean;
|
|
29
|
+
sendJson: (res: TRes, status: number, payload: unknown) => void;
|
|
30
|
+
safeErrorMessage: (err: unknown) => string;
|
|
31
|
+
getOnboardingState: (state: OnboardingState) => OnboardingState;
|
|
32
|
+
};
|
|
33
|
+
export declare function registerOnboardingRoutes<TReq, TRes>(router: Router<Record<string, never>, TReq, TRes>, deps: RegisterOnboardingRoutesDeps<TReq, TRes>): void;
|
|
34
|
+
export {};
|