@useorgx/openclaw-plugin 0.4.6 → 0.4.8
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/BNeJ0kpF.js +1 -0
- package/dashboard/dist/assets/BzkiMPmM.js +215 -0
- package/dashboard/dist/assets/CUV9IHHi.js +1 -0
- package/dashboard/dist/assets/Ie7d9Iq2.css +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 +8 -1
- package/dist/artifacts/register-artifact.d.ts +47 -0
- package/dist/artifacts/register-artifact.js +271 -0
- package/dist/auth-store.js +8 -13
- package/dist/contracts/client.d.ts +1 -0
- package/dist/contracts/client.js +7 -5
- package/dist/contracts/types.d.ts +4 -0
- package/dist/http-handler.js +1055 -114
- package/dist/index.js +165 -29
- package/dist/local-openclaw.js +8 -0
- package/dist/mcp-client-setup.js +75 -90
- package/dist/runtime-instance-store.js +3 -3
- 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
package/dist/http-handler.js
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
* /orgx/api/runs/:id/checkpoints/:checkpointId/restore → restore checkpoint
|
|
18
18
|
* /orgx/api/runs/:id/actions/:action → run control action
|
|
19
19
|
*/
|
|
20
|
-
import { readFileSync, existsSync, mkdirSync, chmodSync, createWriteStream, readdirSync, statSync, writeFileSync, } from "node:fs";
|
|
20
|
+
import { readFileSync, existsSync, mkdirSync, chmodSync, createWriteStream, readdirSync, statSync, writeFileSync, openSync, readSync, closeSync, } from "node:fs";
|
|
21
21
|
import { homedir } from "node:os";
|
|
22
22
|
import { join, extname, normalize, resolve, relative, sep, dirname } from "node:path";
|
|
23
23
|
import { fileURLToPath } from "node:url";
|
|
@@ -25,6 +25,7 @@ import { spawn, spawnSync } from "node:child_process";
|
|
|
25
25
|
import { createHash, randomUUID } from "node:crypto";
|
|
26
26
|
import { backupCorruptFileSync, writeFileAtomicSync } from "./fs-utils.js";
|
|
27
27
|
import { getOrgxPluginConfigDir } from "./paths.js";
|
|
28
|
+
import { registerArtifact } from "./artifacts/register-artifact.js";
|
|
28
29
|
import { readNextUpQueuePins, removeNextUpQueuePin, setNextUpQueuePinOrder, upsertNextUpQueuePin, } from "./next-up-queue-store.js";
|
|
29
30
|
import { formatStatus, formatAgents, formatActivity, formatInitiatives, getOnboardingState, } from "./dashboard-api.js";
|
|
30
31
|
import { loadLocalOpenClawSnapshot, loadLocalTurnDetail, toLocalLiveActivity, toLocalLiveAgents, toLocalLiveInitiatives, toLocalSessionTree, } from "./local-openclaw.js";
|
|
@@ -35,6 +36,7 @@ import { readAgentContexts, upsertAgentContext, upsertRunContext } from "./agent
|
|
|
35
36
|
import { getAgentRun, markAgentRunStopped, readAgentRuns, upsertAgentRun, } from "./agent-run-store.js";
|
|
36
37
|
import { appendEntityComment, listEntityComments, mergeEntityComments, } from "./entity-comment-store.js";
|
|
37
38
|
import { appendActivityItems, listActivityPage, } from "./activity-store.js";
|
|
39
|
+
import { enrichActivityActorFields } from "./activity-actor-fields.js";
|
|
38
40
|
import { readByokKeys, writeByokKeys } from "./byok-store.js";
|
|
39
41
|
import { applyOrgxAgentSuitePlan, computeOrgxAgentSuitePlan, generateAgentSuiteOperationId, } from "./agent-suite.js";
|
|
40
42
|
import { computeMilestoneRollup, computeWorkstreamRollup, } from "./reporting/rollups.js";
|
|
@@ -53,11 +55,17 @@ async function resolveSkillPackOverrides(input) {
|
|
|
53
55
|
const getSkillPack = input.client.getSkillPack;
|
|
54
56
|
if (typeof getSkillPack !== "function")
|
|
55
57
|
return state.overrides;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
58
|
+
try {
|
|
59
|
+
const refreshed = await refreshSkillPackState({
|
|
60
|
+
getSkillPack: (args) => getSkillPack(args),
|
|
61
|
+
force,
|
|
62
|
+
});
|
|
63
|
+
return refreshed.state.overrides;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
// If refresh fails (network, disk, etc.), fall back to cached overrides.
|
|
67
|
+
return state.overrides;
|
|
68
|
+
}
|
|
61
69
|
}
|
|
62
70
|
function safeErrorMessage(err) {
|
|
63
71
|
if (err instanceof Error)
|
|
@@ -96,6 +104,167 @@ function isUnauthorizedOrgxError(err) {
|
|
|
96
104
|
}
|
|
97
105
|
const ACTIVITY_WARM_THROTTLE_MS = 30_000;
|
|
98
106
|
const activityWarmByKey = new Map();
|
|
107
|
+
const SNAPSHOT_RESPONSE_CACHE_TTL_MS = 1_500;
|
|
108
|
+
const SNAPSHOT_RESPONSE_CACHE_MAX_ENTRIES = 16;
|
|
109
|
+
const SNAPSHOT_ACTIVITY_PERSIST_MIN_INTERVAL_MS = 15_000;
|
|
110
|
+
const SNAPSHOT_ACTIVITY_FINGERPRINT_DEPTH = 8;
|
|
111
|
+
let lastSnapshotActivityPersistAt = 0;
|
|
112
|
+
let lastSnapshotActivityFingerprint = "";
|
|
113
|
+
const snapshotResponseCache = new Map();
|
|
114
|
+
const ACTIVITY_DECISION_EVENT_HINTS = new Set([
|
|
115
|
+
"decision_buffered",
|
|
116
|
+
"auto_continue_spawn_guard_blocked",
|
|
117
|
+
"autopilot_slice_mcp_handshake_failed",
|
|
118
|
+
"autopilot_slice_timeout",
|
|
119
|
+
"autopilot_slice_log_stall",
|
|
120
|
+
]);
|
|
121
|
+
const ACTIVITY_ARTIFACT_EVENT_HINTS = new Set([
|
|
122
|
+
"autopilot_slice_artifact_buffered",
|
|
123
|
+
]);
|
|
124
|
+
function normalizeActivityBucket(value) {
|
|
125
|
+
if (typeof value !== "string")
|
|
126
|
+
return null;
|
|
127
|
+
const normalized = value.trim().toLowerCase();
|
|
128
|
+
if (normalized === "artifact")
|
|
129
|
+
return "artifact";
|
|
130
|
+
if (normalized === "decision")
|
|
131
|
+
return "decision";
|
|
132
|
+
if (normalized === "message")
|
|
133
|
+
return "message";
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
function activityMetadataBoolean(metadata, keys) {
|
|
137
|
+
if (!metadata)
|
|
138
|
+
return null;
|
|
139
|
+
for (const key of keys) {
|
|
140
|
+
const value = metadata[key];
|
|
141
|
+
if (typeof value === "boolean")
|
|
142
|
+
return value;
|
|
143
|
+
if (typeof value === "string") {
|
|
144
|
+
const normalized = value.trim().toLowerCase();
|
|
145
|
+
if (normalized === "true")
|
|
146
|
+
return true;
|
|
147
|
+
if (normalized === "false")
|
|
148
|
+
return false;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
function activityMetadataNumber(metadata, keys) {
|
|
154
|
+
if (!metadata)
|
|
155
|
+
return null;
|
|
156
|
+
for (const key of keys) {
|
|
157
|
+
const value = metadata[key];
|
|
158
|
+
if (typeof value === "number" && Number.isFinite(value)) {
|
|
159
|
+
return Math.max(0, value);
|
|
160
|
+
}
|
|
161
|
+
if (typeof value === "string") {
|
|
162
|
+
const parsed = Number(value);
|
|
163
|
+
if (Number.isFinite(parsed)) {
|
|
164
|
+
return Math.max(0, parsed);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
function activityMetadataEventName(metadata) {
|
|
171
|
+
if (!metadata)
|
|
172
|
+
return null;
|
|
173
|
+
const raw = metadata.event;
|
|
174
|
+
if (typeof raw !== "string")
|
|
175
|
+
return null;
|
|
176
|
+
const normalized = raw.trim().toLowerCase();
|
|
177
|
+
return normalized.length > 0 ? normalized : null;
|
|
178
|
+
}
|
|
179
|
+
function deriveStructuredActivityBucket(input) {
|
|
180
|
+
const metadata = input.metadata;
|
|
181
|
+
const explicit = normalizeActivityBucket(input.explicitBucket) ??
|
|
182
|
+
normalizeActivityBucket(metadata?.activity_bucket) ??
|
|
183
|
+
normalizeActivityBucket(metadata?.activityBucket) ??
|
|
184
|
+
normalizeActivityBucket(metadata?.bucket) ??
|
|
185
|
+
null;
|
|
186
|
+
if (explicit)
|
|
187
|
+
return explicit;
|
|
188
|
+
const event = activityMetadataEventName(metadata);
|
|
189
|
+
const decisionRequired = activityMetadataBoolean(metadata, ["decision_required", "decisionRequired"]) === true;
|
|
190
|
+
const artifacts = activityMetadataNumber(metadata, ["artifacts", "artifact_count", "artifactCount"]) ?? 0;
|
|
191
|
+
const decisions = activityMetadataNumber(metadata, ["decisions", "decision_count", "decisionCount"]) ?? 0;
|
|
192
|
+
const blockingDecisions = activityMetadataNumber(metadata, [
|
|
193
|
+
"blocking_decisions",
|
|
194
|
+
"blockingDecisions",
|
|
195
|
+
"blocking_decision_count",
|
|
196
|
+
"blockingDecisionCount",
|
|
197
|
+
]) ?? 0;
|
|
198
|
+
const nonBlockingDecisions = activityMetadataNumber(metadata, [
|
|
199
|
+
"non_blocking_decisions",
|
|
200
|
+
"nonBlockingDecisions",
|
|
201
|
+
"non_blocking_decision_count",
|
|
202
|
+
"nonBlockingDecisionCount",
|
|
203
|
+
]) ?? 0;
|
|
204
|
+
if (event === "autopilot_slice_result") {
|
|
205
|
+
if (decisionRequired || blockingDecisions > 0)
|
|
206
|
+
return "decision";
|
|
207
|
+
if (artifacts > 0)
|
|
208
|
+
return "artifact";
|
|
209
|
+
if (decisions > 0 || nonBlockingDecisions > 0)
|
|
210
|
+
return "decision";
|
|
211
|
+
return "message";
|
|
212
|
+
}
|
|
213
|
+
if (event && ACTIVITY_ARTIFACT_EVENT_HINTS.has(event))
|
|
214
|
+
return "artifact";
|
|
215
|
+
if (event && ACTIVITY_DECISION_EVENT_HINTS.has(event))
|
|
216
|
+
return "decision";
|
|
217
|
+
const hasArtifactReference = typeof metadata?.artifact_id === "string" ||
|
|
218
|
+
typeof metadata?.artifactId === "string" ||
|
|
219
|
+
typeof metadata?.work_artifact_id === "string";
|
|
220
|
+
if (hasArtifactReference || artifacts > 0)
|
|
221
|
+
return "artifact";
|
|
222
|
+
if (decisionRequired || blockingDecisions > 0 || decisions > 0 || nonBlockingDecisions > 0) {
|
|
223
|
+
return "decision";
|
|
224
|
+
}
|
|
225
|
+
return "message";
|
|
226
|
+
}
|
|
227
|
+
function snapshotActivityFingerprint(items) {
|
|
228
|
+
if (!Array.isArray(items) || items.length === 0)
|
|
229
|
+
return "0";
|
|
230
|
+
const sample = items
|
|
231
|
+
.slice(0, SNAPSHOT_ACTIVITY_FINGERPRINT_DEPTH)
|
|
232
|
+
.map((item) => `${item.id}|${item.timestamp}`)
|
|
233
|
+
.join(";");
|
|
234
|
+
return `${items.length}:${sample}`;
|
|
235
|
+
}
|
|
236
|
+
function readSnapshotResponseCache(key) {
|
|
237
|
+
const entry = snapshotResponseCache.get(key);
|
|
238
|
+
if (!entry)
|
|
239
|
+
return null;
|
|
240
|
+
if (entry.expiresAt <= Date.now()) {
|
|
241
|
+
snapshotResponseCache.delete(key);
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
return entry.payload;
|
|
245
|
+
}
|
|
246
|
+
function writeSnapshotResponseCache(key, payload) {
|
|
247
|
+
const now = Date.now();
|
|
248
|
+
snapshotResponseCache.set(key, {
|
|
249
|
+
expiresAt: now + SNAPSHOT_RESPONSE_CACHE_TTL_MS,
|
|
250
|
+
payload,
|
|
251
|
+
});
|
|
252
|
+
if (snapshotResponseCache.size <= SNAPSHOT_RESPONSE_CACHE_MAX_ENTRIES)
|
|
253
|
+
return;
|
|
254
|
+
for (const [cachedKey, entry] of snapshotResponseCache.entries()) {
|
|
255
|
+
if (entry.expiresAt <= now)
|
|
256
|
+
snapshotResponseCache.delete(cachedKey);
|
|
257
|
+
}
|
|
258
|
+
while (snapshotResponseCache.size > SNAPSHOT_RESPONSE_CACHE_MAX_ENTRIES) {
|
|
259
|
+
const oldestKey = snapshotResponseCache.keys().next().value;
|
|
260
|
+
if (!oldestKey)
|
|
261
|
+
break;
|
|
262
|
+
snapshotResponseCache.delete(oldestKey);
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function clearSnapshotResponseCache() {
|
|
266
|
+
snapshotResponseCache.clear();
|
|
267
|
+
}
|
|
99
268
|
function isUserScopedApiKey(apiKey) {
|
|
100
269
|
return apiKey.trim().toLowerCase().startsWith("oxk_");
|
|
101
270
|
}
|
|
@@ -107,6 +276,150 @@ function parseJsonSafe(value) {
|
|
|
107
276
|
return null;
|
|
108
277
|
}
|
|
109
278
|
}
|
|
279
|
+
function asRecord(value) {
|
|
280
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
281
|
+
return null;
|
|
282
|
+
return value;
|
|
283
|
+
}
|
|
284
|
+
function flattenActivityMetadata(value) {
|
|
285
|
+
const record = asRecord(value);
|
|
286
|
+
if (!record)
|
|
287
|
+
return null;
|
|
288
|
+
const nested = asRecord(record.metadata);
|
|
289
|
+
if (!nested)
|
|
290
|
+
return record;
|
|
291
|
+
return { ...record, ...nested };
|
|
292
|
+
}
|
|
293
|
+
function metadataString(metadata, keys) {
|
|
294
|
+
if (!metadata)
|
|
295
|
+
return null;
|
|
296
|
+
for (const key of keys) {
|
|
297
|
+
const value = metadata[key];
|
|
298
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
299
|
+
return value.trim();
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
function metadataNumber(metadata, keys) {
|
|
305
|
+
if (!metadata)
|
|
306
|
+
return null;
|
|
307
|
+
for (const key of keys) {
|
|
308
|
+
const value = metadata[key];
|
|
309
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
310
|
+
return value;
|
|
311
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
312
|
+
const parsed = Number(value.trim());
|
|
313
|
+
if (Number.isFinite(parsed))
|
|
314
|
+
return parsed;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
function resolveArtifactIdFromActivityItem(item) {
|
|
320
|
+
const metadata = flattenActivityMetadata(item.metadata);
|
|
321
|
+
if (!metadata)
|
|
322
|
+
return null;
|
|
323
|
+
const rawId = metadataString(metadata, ["artifact_id", "artifactId", "work_artifact_id"]);
|
|
324
|
+
return rawId && rawId.length > 0 ? rawId : null;
|
|
325
|
+
}
|
|
326
|
+
function resolveArtifactHref(value) {
|
|
327
|
+
if (!value)
|
|
328
|
+
return null;
|
|
329
|
+
const trimmed = value.trim();
|
|
330
|
+
if (!trimmed)
|
|
331
|
+
return null;
|
|
332
|
+
if (/^https?:\/\//i.test(trimmed))
|
|
333
|
+
return trimmed;
|
|
334
|
+
return `/orgx/api/live/filesystem/open?path=${encodeURIComponent(trimmed)}`;
|
|
335
|
+
}
|
|
336
|
+
function buildLocalArtifactDetailFallback(artifactId, warning) {
|
|
337
|
+
let cursor = null;
|
|
338
|
+
const maxPages = 8;
|
|
339
|
+
for (let pageIndex = 0; pageIndex < maxPages; pageIndex += 1) {
|
|
340
|
+
const page = listActivityPage({ limit: 500, cursor });
|
|
341
|
+
const activities = Array.isArray(page.activities) ? page.activities : [];
|
|
342
|
+
for (const item of activities) {
|
|
343
|
+
const matchId = resolveArtifactIdFromActivityItem(item);
|
|
344
|
+
if (!matchId || matchId !== artifactId)
|
|
345
|
+
continue;
|
|
346
|
+
const metadata = flattenActivityMetadata(item.metadata) ?? {};
|
|
347
|
+
const rawPath = metadataString(metadata, [
|
|
348
|
+
"url",
|
|
349
|
+
"path",
|
|
350
|
+
"file_path",
|
|
351
|
+
"filepath",
|
|
352
|
+
"artifact_path",
|
|
353
|
+
"output_path",
|
|
354
|
+
"external_url",
|
|
355
|
+
"artifact_url",
|
|
356
|
+
]) ?? null;
|
|
357
|
+
const artifactHref = resolveArtifactHref(rawPath);
|
|
358
|
+
const artifactType = metadataString(metadata, ["artifact_type", "artifactType", "type"]) ?? "report";
|
|
359
|
+
const artifactName = metadataString(metadata, ["artifact_name", "artifactName", "name", "title"]) ??
|
|
360
|
+
item.title?.trim() ??
|
|
361
|
+
`Artifact ${artifactId.slice(0, 8)}`;
|
|
362
|
+
const status = metadataString(metadata, ["artifact_status", "status", "state"]) ??
|
|
363
|
+
(typeof metadata.event === "string" && metadata.event.includes("buffered")
|
|
364
|
+
? "buffered"
|
|
365
|
+
: "draft");
|
|
366
|
+
const entityType = metadataString(metadata, ["entity_type", "entityType"]) ??
|
|
367
|
+
(metadataString(metadata, ["initiative_id", "initiativeId"]) ? "initiative" : "task");
|
|
368
|
+
const entityId = metadataString(metadata, [
|
|
369
|
+
"entity_id",
|
|
370
|
+
"entityId",
|
|
371
|
+
"task_id",
|
|
372
|
+
"taskId",
|
|
373
|
+
"workstream_id",
|
|
374
|
+
"workstreamId",
|
|
375
|
+
"initiative_id",
|
|
376
|
+
"initiativeId",
|
|
377
|
+
"run_id",
|
|
378
|
+
"runId",
|
|
379
|
+
]) ?? "local";
|
|
380
|
+
const description = metadataString(metadata, ["description", "summary", "message"]) ??
|
|
381
|
+
item.summary ??
|
|
382
|
+
item.description ??
|
|
383
|
+
null;
|
|
384
|
+
const version = Math.max(1, Math.round(metadataNumber(metadata, ["version", "artifact_version"]) ?? 1));
|
|
385
|
+
const createdAt = item.timestamp ?? new Date().toISOString();
|
|
386
|
+
const updatedAt = item.timestamp ?? createdAt;
|
|
387
|
+
return {
|
|
388
|
+
artifact: {
|
|
389
|
+
id: artifactId,
|
|
390
|
+
name: artifactName,
|
|
391
|
+
description,
|
|
392
|
+
artifact_url: artifactHref ?? "",
|
|
393
|
+
artifact_type: artifactType,
|
|
394
|
+
status,
|
|
395
|
+
version,
|
|
396
|
+
entity_type: entityType,
|
|
397
|
+
entity_id: entityId,
|
|
398
|
+
metadata: {
|
|
399
|
+
...metadata,
|
|
400
|
+
local_fallback: true,
|
|
401
|
+
local_warning: warning,
|
|
402
|
+
local_activity_event_id: item.id,
|
|
403
|
+
local_activity_type: item.type,
|
|
404
|
+
local_activity_timestamp: item.timestamp,
|
|
405
|
+
local_source_path: rawPath,
|
|
406
|
+
},
|
|
407
|
+
created_at: createdAt,
|
|
408
|
+
updated_at: updatedAt,
|
|
409
|
+
catalog: null,
|
|
410
|
+
cached_metadata: null,
|
|
411
|
+
},
|
|
412
|
+
relationships: [],
|
|
413
|
+
localFallback: true,
|
|
414
|
+
warning,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
if (!page.nextCursor)
|
|
418
|
+
break;
|
|
419
|
+
cursor = page.nextCursor;
|
|
420
|
+
}
|
|
421
|
+
return null;
|
|
422
|
+
}
|
|
110
423
|
function maskSecret(value) {
|
|
111
424
|
if (!value)
|
|
112
425
|
return null;
|
|
@@ -756,48 +1069,52 @@ function applyAgentContextsToActivity(input, contexts) {
|
|
|
756
1069
|
if (!Array.isArray(input))
|
|
757
1070
|
return [];
|
|
758
1071
|
return input.map((item) => {
|
|
1072
|
+
let nextItem = item;
|
|
759
1073
|
const existingInitiativeId = (item.initiativeId ?? "").trim();
|
|
760
|
-
if (isUuidLike(existingInitiativeId))
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
}
|
|
1074
|
+
if (!isUuidLike(existingInitiativeId)) {
|
|
1075
|
+
const runCtx = item.runId ? contexts.runs[item.runId] : null;
|
|
1076
|
+
if (runCtx && runCtx.initiativeId && runCtx.initiativeId.trim().length > 0) {
|
|
1077
|
+
const initiativeId = runCtx.initiativeId.trim();
|
|
1078
|
+
const metadata = item.metadata && typeof item.metadata === "object"
|
|
1079
|
+
? { ...item.metadata }
|
|
1080
|
+
: {};
|
|
1081
|
+
metadata.orgx_context = {
|
|
1082
|
+
initiativeId,
|
|
1083
|
+
workstreamId: runCtx.workstreamId ?? null,
|
|
1084
|
+
taskId: runCtx.taskId ?? null,
|
|
1085
|
+
updatedAt: runCtx.updatedAt,
|
|
1086
|
+
};
|
|
1087
|
+
nextItem = {
|
|
1088
|
+
...item,
|
|
1089
|
+
initiativeId,
|
|
1090
|
+
metadata,
|
|
1091
|
+
};
|
|
1092
|
+
}
|
|
1093
|
+
else {
|
|
1094
|
+
const agentId = item.agentId?.trim() ?? "";
|
|
1095
|
+
if (agentId) {
|
|
1096
|
+
const ctx = contexts.agents[agentId];
|
|
1097
|
+
const initiativeId = ctx?.initiativeId?.trim() ?? "";
|
|
1098
|
+
if (initiativeId) {
|
|
1099
|
+
const metadata = item.metadata && typeof item.metadata === "object"
|
|
1100
|
+
? { ...item.metadata }
|
|
1101
|
+
: {};
|
|
1102
|
+
metadata.orgx_context = {
|
|
1103
|
+
initiativeId,
|
|
1104
|
+
workstreamId: ctx.workstreamId ?? null,
|
|
1105
|
+
taskId: ctx.taskId ?? null,
|
|
1106
|
+
updatedAt: ctx.updatedAt,
|
|
1107
|
+
};
|
|
1108
|
+
nextItem = {
|
|
1109
|
+
...item,
|
|
1110
|
+
initiativeId,
|
|
1111
|
+
metadata,
|
|
1112
|
+
};
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
779
1116
|
}
|
|
780
|
-
|
|
781
|
-
if (!agentId)
|
|
782
|
-
return item;
|
|
783
|
-
const ctx = contexts.agents[agentId];
|
|
784
|
-
const initiativeId = ctx?.initiativeId?.trim() ?? "";
|
|
785
|
-
if (!initiativeId)
|
|
786
|
-
return item;
|
|
787
|
-
const metadata = item.metadata && typeof item.metadata === "object"
|
|
788
|
-
? { ...item.metadata }
|
|
789
|
-
: {};
|
|
790
|
-
metadata.orgx_context = {
|
|
791
|
-
initiativeId,
|
|
792
|
-
workstreamId: ctx.workstreamId ?? null,
|
|
793
|
-
taskId: ctx.taskId ?? null,
|
|
794
|
-
updatedAt: ctx.updatedAt,
|
|
795
|
-
};
|
|
796
|
-
return {
|
|
797
|
-
...item,
|
|
798
|
-
initiativeId,
|
|
799
|
-
metadata,
|
|
800
|
-
};
|
|
1117
|
+
return enrichActivityActorFields(nextItem);
|
|
801
1118
|
});
|
|
802
1119
|
}
|
|
803
1120
|
function mergeSessionTrees(base, extra) {
|
|
@@ -849,7 +1166,12 @@ function mergeSessionTrees(base, extra) {
|
|
|
849
1166
|
};
|
|
850
1167
|
}
|
|
851
1168
|
function mergeActivities(base, extra, limit) {
|
|
852
|
-
const merged = [...(base ?? []), ...(extra ?? [])].sort((a, b) =>
|
|
1169
|
+
const merged = [...(base ?? []), ...(extra ?? [])].sort((a, b) => {
|
|
1170
|
+
const timestampDelta = Date.parse(b.timestamp) - Date.parse(a.timestamp);
|
|
1171
|
+
if (timestampDelta !== 0)
|
|
1172
|
+
return timestampDelta;
|
|
1173
|
+
return b.id.localeCompare(a.id);
|
|
1174
|
+
});
|
|
853
1175
|
const deduped = [];
|
|
854
1176
|
const seen = new Set();
|
|
855
1177
|
for (const item of merged) {
|
|
@@ -899,6 +1221,56 @@ function normalizeRuntimeSource(value) {
|
|
|
899
1221
|
return "api";
|
|
900
1222
|
return "unknown";
|
|
901
1223
|
}
|
|
1224
|
+
function runtimeSourceDefaultAgentLabel(sourceClient) {
|
|
1225
|
+
if (sourceClient === "codex")
|
|
1226
|
+
return "Codex";
|
|
1227
|
+
if (sourceClient === "claude-code")
|
|
1228
|
+
return "Claude Code";
|
|
1229
|
+
if (sourceClient === "openclaw")
|
|
1230
|
+
return "OpenClaw";
|
|
1231
|
+
if (sourceClient === "api")
|
|
1232
|
+
return "OrgX API";
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
function runtimeSourceDefaultAgentId(sourceClient) {
|
|
1236
|
+
if (sourceClient === "codex")
|
|
1237
|
+
return "runtime:codex";
|
|
1238
|
+
if (sourceClient === "claude-code")
|
|
1239
|
+
return "runtime:claude-code";
|
|
1240
|
+
if (sourceClient === "openclaw")
|
|
1241
|
+
return "runtime:openclaw";
|
|
1242
|
+
if (sourceClient === "api")
|
|
1243
|
+
return "runtime:api";
|
|
1244
|
+
return null;
|
|
1245
|
+
}
|
|
1246
|
+
function deriveRuntimeFallbackAgent(instance) {
|
|
1247
|
+
const sourceClient = normalizeRuntimeSource(instance.sourceClient);
|
|
1248
|
+
const agentId = (instance.agentId ?? "").trim() || runtimeSourceDefaultAgentId(sourceClient);
|
|
1249
|
+
const agentName = (instance.agentName ?? "").trim() ||
|
|
1250
|
+
(instance.displayName ?? "").trim() ||
|
|
1251
|
+
runtimeSourceDefaultAgentLabel(sourceClient);
|
|
1252
|
+
return {
|
|
1253
|
+
agentId: agentId || null,
|
|
1254
|
+
agentName: agentName || null,
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
function deriveRuntimeSessionStatus(instance) {
|
|
1258
|
+
const state = (instance.state ?? "").trim().toLowerCase();
|
|
1259
|
+
const phase = (instance.phase ?? "").trim().toLowerCase();
|
|
1260
|
+
if (phase === "blocked" || state === "error")
|
|
1261
|
+
return "blocked";
|
|
1262
|
+
if (phase === "completed")
|
|
1263
|
+
return "completed";
|
|
1264
|
+
if (phase === "handoff")
|
|
1265
|
+
return "handoff";
|
|
1266
|
+
if (phase === "review")
|
|
1267
|
+
return "review";
|
|
1268
|
+
if (state === "stopped")
|
|
1269
|
+
return "paused";
|
|
1270
|
+
if (state === "stale")
|
|
1271
|
+
return "queued";
|
|
1272
|
+
return "running";
|
|
1273
|
+
}
|
|
902
1274
|
function runtimeMatchMaps(instances) {
|
|
903
1275
|
const byRunId = new Map();
|
|
904
1276
|
const byAgentInitiative = new Map();
|
|
@@ -931,8 +1303,32 @@ function enrichSessionsWithRuntime(input, instances) {
|
|
|
931
1303
|
const match = byRun ?? byAgent;
|
|
932
1304
|
if (!match)
|
|
933
1305
|
return node;
|
|
1306
|
+
const runtimeStatus = deriveRuntimeSessionStatus(match);
|
|
1307
|
+
const fallbackAgent = deriveRuntimeFallbackAgent(match);
|
|
1308
|
+
const agentId = (node.agentId ?? "").trim() || fallbackAgent.agentId;
|
|
1309
|
+
const agentName = (node.agentName ?? "").trim() || fallbackAgent.agentName;
|
|
1310
|
+
const nodeStatus = (node.status ?? "").trim().toLowerCase();
|
|
1311
|
+
const isLiveLikeNodeStatus = nodeStatus === "running" ||
|
|
1312
|
+
nodeStatus === "active" ||
|
|
1313
|
+
nodeStatus === "in_progress" ||
|
|
1314
|
+
nodeStatus === "working" ||
|
|
1315
|
+
nodeStatus === "planning" ||
|
|
1316
|
+
nodeStatus === "dispatching";
|
|
1317
|
+
const shouldDowngradeStatusFromRuntime = isLiveLikeNodeStatus && (runtimeStatus === "queued" || runtimeStatus === "paused");
|
|
1318
|
+
const blockerReason = (node.blockerReason ?? "").trim() ||
|
|
1319
|
+
(node.status?.toLowerCase() === "blocked" || match.phase?.toLowerCase() === "blocked"
|
|
1320
|
+
? (match.lastMessage ?? "").trim()
|
|
1321
|
+
: "");
|
|
934
1322
|
return {
|
|
935
1323
|
...node,
|
|
1324
|
+
agentId: agentId || null,
|
|
1325
|
+
agentName: agentName || null,
|
|
1326
|
+
status: shouldDowngradeStatusFromRuntime ? runtimeStatus : node.status,
|
|
1327
|
+
state: node.state ?? match.state ?? null,
|
|
1328
|
+
lastEventSummary: shouldDowngradeStatusFromRuntime && runtimeStatus === "queued"
|
|
1329
|
+
? node.lastEventSummary ?? "Recovered stale runtime; awaiting next dispatch."
|
|
1330
|
+
: node.lastEventSummary,
|
|
1331
|
+
blockerReason: blockerReason || node.blockerReason || null,
|
|
936
1332
|
runtimeClient: normalizeRuntimeSource(match.sourceClient),
|
|
937
1333
|
runtimeLabel: match.displayName,
|
|
938
1334
|
runtimeProvider: match.providerLogo,
|
|
@@ -966,13 +1362,15 @@ function injectRuntimeInstancesAsSessions(input, instances) {
|
|
|
966
1362
|
continue;
|
|
967
1363
|
if (existingRunIds.has(runId))
|
|
968
1364
|
continue;
|
|
969
|
-
// Only surface active
|
|
970
|
-
//
|
|
971
|
-
if (instance.state !== "active"
|
|
1365
|
+
// Only surface active runtime instances as synthetic sessions.
|
|
1366
|
+
// Stale instances are reconciled onto existing sessions but shouldn't appear as fresh work.
|
|
1367
|
+
if (instance.state !== "active")
|
|
972
1368
|
continue;
|
|
973
1369
|
const initiativeId = instance.initiativeId?.trim() || null;
|
|
974
1370
|
const workstreamId = instance.workstreamId?.trim() || null;
|
|
975
|
-
const
|
|
1371
|
+
const runtimeClient = normalizeRuntimeSource(instance.sourceClient);
|
|
1372
|
+
const fallbackAgent = deriveRuntimeFallbackAgent(instance);
|
|
1373
|
+
const groupId = initiativeId ?? fallbackAgent.agentId ?? `runtime:${runtimeClient}`;
|
|
976
1374
|
const meta = instance.metadata && typeof instance.metadata === "object"
|
|
977
1375
|
? instance.metadata
|
|
978
1376
|
: {};
|
|
@@ -980,7 +1378,7 @@ function injectRuntimeInstancesAsSessions(input, instances) {
|
|
|
980
1378
|
(workstreamId ? `Workstream ${workstreamId.slice(0, 8)}` : null);
|
|
981
1379
|
const initiativeHint = pickString(meta, ["initiative_title", "initiativeTitle"]) ??
|
|
982
1380
|
(initiativeId ? `Initiative ${initiativeId.slice(0, 8)}` : null);
|
|
983
|
-
const groupLabel = (initiativeHint ?? groupId).trim();
|
|
1381
|
+
const groupLabel = (initiativeHint ?? fallbackAgent.agentName ?? groupId).trim();
|
|
984
1382
|
if (!groupsById.has(groupId)) {
|
|
985
1383
|
const group = { id: groupId, label: groupLabel, status: null };
|
|
986
1384
|
groupsById.set(groupId, group);
|
|
@@ -991,14 +1389,19 @@ function injectRuntimeInstancesAsSessions(input, instances) {
|
|
|
991
1389
|
continue;
|
|
992
1390
|
existingNodeIds.add(nodeId);
|
|
993
1391
|
existingRunIds.add(runId);
|
|
1392
|
+
const status = deriveRuntimeSessionStatus(instance);
|
|
1393
|
+
const blockerReason = status === "blocked" ? (instance.lastMessage ?? null) : null;
|
|
1394
|
+
const blockers = status === "blocked" && typeof blockerReason === "string" && blockerReason.trim().length > 0
|
|
1395
|
+
? [blockerReason.trim()]
|
|
1396
|
+
: [];
|
|
994
1397
|
const node = {
|
|
995
1398
|
id: nodeId,
|
|
996
1399
|
parentId: null,
|
|
997
1400
|
runId,
|
|
998
1401
|
title: titleHint ?? instance.lastMessage ?? `Runtime ${runId.slice(0, 8)}`,
|
|
999
|
-
agentId:
|
|
1000
|
-
agentName:
|
|
1001
|
-
status
|
|
1402
|
+
agentId: fallbackAgent.agentId,
|
|
1403
|
+
agentName: fallbackAgent.agentName,
|
|
1404
|
+
status,
|
|
1002
1405
|
progress: instance.progressPct ?? null,
|
|
1003
1406
|
initiativeId,
|
|
1004
1407
|
workstreamId,
|
|
@@ -1008,10 +1411,11 @@ function injectRuntimeInstancesAsSessions(input, instances) {
|
|
|
1008
1411
|
updatedAt: instance.updatedAt ?? null,
|
|
1009
1412
|
lastEventAt: instance.lastEventAt ?? null,
|
|
1010
1413
|
lastEventSummary: instance.lastMessage ?? null,
|
|
1011
|
-
blockers
|
|
1414
|
+
blockers,
|
|
1415
|
+
blockerReason,
|
|
1012
1416
|
phase: instance.phase ?? null,
|
|
1013
1417
|
state: instance.state ?? null,
|
|
1014
|
-
runtimeClient
|
|
1418
|
+
runtimeClient,
|
|
1015
1419
|
runtimeLabel: instance.displayName,
|
|
1016
1420
|
runtimeProvider: instance.providerLogo,
|
|
1017
1421
|
instanceId: instance.id,
|
|
@@ -1362,6 +1766,8 @@ function resolveSafeDistPath(subPath) {
|
|
|
1362
1766
|
// =============================================================================
|
|
1363
1767
|
const IMMUTABLE_FILE_CACHE = new Map();
|
|
1364
1768
|
const IMMUTABLE_FILE_CACHE_MAX = 128;
|
|
1769
|
+
const FILE_PREVIEW_MAX_BYTES = 1_000_000;
|
|
1770
|
+
const FILE_PREVIEW_MAX_DIR_ENTRIES = 300;
|
|
1365
1771
|
function sendJson(res, status, data) {
|
|
1366
1772
|
const body = JSON.stringify(data);
|
|
1367
1773
|
res.writeHead(status, {
|
|
@@ -1373,6 +1779,70 @@ function sendJson(res, status, data) {
|
|
|
1373
1779
|
});
|
|
1374
1780
|
res.end(body);
|
|
1375
1781
|
}
|
|
1782
|
+
function sendHtml(res, status, html) {
|
|
1783
|
+
res.writeHead(status, {
|
|
1784
|
+
"Content-Type": "text/html; charset=utf-8",
|
|
1785
|
+
"Cache-Control": "no-store",
|
|
1786
|
+
...SECURITY_HEADERS,
|
|
1787
|
+
...CORS_HEADERS,
|
|
1788
|
+
});
|
|
1789
|
+
res.end(html);
|
|
1790
|
+
}
|
|
1791
|
+
function escapeHtml(value) {
|
|
1792
|
+
return value
|
|
1793
|
+
.replaceAll("&", "&")
|
|
1794
|
+
.replaceAll("<", "<")
|
|
1795
|
+
.replaceAll(">", ">")
|
|
1796
|
+
.replaceAll('"', """)
|
|
1797
|
+
.replaceAll("'", "'");
|
|
1798
|
+
}
|
|
1799
|
+
function resolveFilesystemOpenPath(rawPath) {
|
|
1800
|
+
let value = rawPath.trim();
|
|
1801
|
+
if (value.toLowerCase().startsWith("file://")) {
|
|
1802
|
+
value = value.replace(/^file:\/\//i, "");
|
|
1803
|
+
try {
|
|
1804
|
+
value = decodeURIComponent(value);
|
|
1805
|
+
}
|
|
1806
|
+
catch {
|
|
1807
|
+
// best effort
|
|
1808
|
+
}
|
|
1809
|
+
if (process.platform === "win32" && value.startsWith("/")) {
|
|
1810
|
+
value = value.slice(1);
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
if (value.startsWith("~/")) {
|
|
1814
|
+
return resolve(homedir(), value.slice(2));
|
|
1815
|
+
}
|
|
1816
|
+
const looksWindowsAbsolute = /^[A-Za-z]:[\\/]/.test(value);
|
|
1817
|
+
if (value.startsWith("/") || looksWindowsAbsolute) {
|
|
1818
|
+
return resolve(value);
|
|
1819
|
+
}
|
|
1820
|
+
return resolve(process.cwd(), value);
|
|
1821
|
+
}
|
|
1822
|
+
function readFilePreview(pathname, totalBytes) {
|
|
1823
|
+
if (totalBytes <= 0) {
|
|
1824
|
+
return { previewBuffer: Buffer.alloc(0), truncated: false };
|
|
1825
|
+
}
|
|
1826
|
+
const previewBytes = Math.min(totalBytes, FILE_PREVIEW_MAX_BYTES);
|
|
1827
|
+
const previewBuffer = Buffer.alloc(previewBytes);
|
|
1828
|
+
const fd = openSync(pathname, "r");
|
|
1829
|
+
try {
|
|
1830
|
+
const bytesRead = readSync(fd, previewBuffer, 0, previewBytes, 0);
|
|
1831
|
+
if (bytesRead < previewBytes) {
|
|
1832
|
+
return {
|
|
1833
|
+
previewBuffer: previewBuffer.subarray(0, bytesRead),
|
|
1834
|
+
truncated: totalBytes > bytesRead,
|
|
1835
|
+
};
|
|
1836
|
+
}
|
|
1837
|
+
return {
|
|
1838
|
+
previewBuffer,
|
|
1839
|
+
truncated: totalBytes > previewBytes,
|
|
1840
|
+
};
|
|
1841
|
+
}
|
|
1842
|
+
finally {
|
|
1843
|
+
closeSync(fd);
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1376
1846
|
function sendFile(res, filePath, cacheControl) {
|
|
1377
1847
|
try {
|
|
1378
1848
|
const shouldCacheImmutable = cacheControl.includes("immutable");
|
|
@@ -2647,7 +3117,16 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2647
3117
|
const message = input.message.trim();
|
|
2648
3118
|
if (!message)
|
|
2649
3119
|
return;
|
|
2650
|
-
const
|
|
3120
|
+
const metadataWithProvenance = withProvenanceMetadata(input.metadata);
|
|
3121
|
+
const activityBucket = deriveStructuredActivityBucket({
|
|
3122
|
+
phase: input.phase,
|
|
3123
|
+
metadata: metadataWithProvenance,
|
|
3124
|
+
explicitBucket: input.activityBucket ?? null,
|
|
3125
|
+
});
|
|
3126
|
+
const enrichedMetadata = {
|
|
3127
|
+
...(metadataWithProvenance ?? {}),
|
|
3128
|
+
activity_bucket: activityBucket,
|
|
3129
|
+
};
|
|
2651
3130
|
try {
|
|
2652
3131
|
await client.emitActivity({
|
|
2653
3132
|
initiative_id: initiativeId,
|
|
@@ -2712,17 +3191,26 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2712
3191
|
: "run_started",
|
|
2713
3192
|
title: message,
|
|
2714
3193
|
description: input.nextStep ?? null,
|
|
2715
|
-
agentId: (typeof
|
|
2716
|
-
?
|
|
3194
|
+
agentId: (typeof metadataWithProvenance?.agent_id === "string"
|
|
3195
|
+
? metadataWithProvenance.agent_id
|
|
2717
3196
|
: null) ?? null,
|
|
2718
|
-
agentName: (typeof
|
|
2719
|
-
?
|
|
3197
|
+
agentName: (typeof metadataWithProvenance?.agent_name === "string"
|
|
3198
|
+
? metadataWithProvenance.agent_name
|
|
3199
|
+
: null) ?? null,
|
|
3200
|
+
requesterAgentId: null,
|
|
3201
|
+
requesterAgentName: null,
|
|
3202
|
+
executorAgentId: (typeof metadataWithProvenance?.agent_id === "string"
|
|
3203
|
+
? metadataWithProvenance.agent_id
|
|
3204
|
+
: null) ?? null,
|
|
3205
|
+
executorAgentName: (typeof metadataWithProvenance?.agent_name === "string"
|
|
3206
|
+
? metadataWithProvenance.agent_name
|
|
2720
3207
|
: null) ?? null,
|
|
2721
3208
|
runId,
|
|
2722
3209
|
initiativeId,
|
|
2723
3210
|
timestamp,
|
|
2724
3211
|
phase: input.phase,
|
|
2725
3212
|
summary: message,
|
|
3213
|
+
kind: activityBucket,
|
|
2726
3214
|
metadata: {
|
|
2727
3215
|
...(enrichedMetadata ?? {}),
|
|
2728
3216
|
source: "openclaw_local_fallback",
|
|
@@ -2807,6 +3295,10 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
2807
3295
|
description: input.summary ?? null,
|
|
2808
3296
|
agentId: null,
|
|
2809
3297
|
agentName: null,
|
|
3298
|
+
requesterAgentId: null,
|
|
3299
|
+
requesterAgentName: null,
|
|
3300
|
+
executorAgentId: null,
|
|
3301
|
+
executorAgentName: null,
|
|
2810
3302
|
runId: correlationId ?? null,
|
|
2811
3303
|
initiativeId,
|
|
2812
3304
|
timestamp,
|
|
@@ -3157,10 +3649,22 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3157
3649
|
}
|
|
3158
3650
|
const autoContinueRuns = new Map();
|
|
3159
3651
|
const localInitiativeStatusOverrides = new Map();
|
|
3160
|
-
let autoContinueTickInFlight =
|
|
3161
|
-
const AUTO_CONTINUE_TICK_MS = 2_500
|
|
3652
|
+
let autoContinueTickInFlight = null;
|
|
3653
|
+
const AUTO_CONTINUE_TICK_MS = readBudgetEnvNumber("ORGX_AUTO_CONTINUE_TICK_MS", 2_500, {
|
|
3654
|
+
min: 250,
|
|
3655
|
+
max: 60_000,
|
|
3656
|
+
});
|
|
3162
3657
|
const autoContinueSliceRuns = new Map();
|
|
3658
|
+
// Keep child handles alive so stdout/stderr capture remains reliable even when the process is detached.
|
|
3659
|
+
const autoContinueSliceChildren = new Map();
|
|
3163
3660
|
const autoContinueSliceLastHeartbeatMs = new Map();
|
|
3661
|
+
const clearAutoContinueSliceTransientState = (sliceRunId) => {
|
|
3662
|
+
const id = (sliceRunId ?? "").trim();
|
|
3663
|
+
if (!id)
|
|
3664
|
+
return;
|
|
3665
|
+
autoContinueSliceChildren.delete(id);
|
|
3666
|
+
autoContinueSliceLastHeartbeatMs.delete(id);
|
|
3667
|
+
};
|
|
3164
3668
|
const AUTO_CONTINUE_SLICE_MAX_TASKS = 6;
|
|
3165
3669
|
const AUTO_CONTINUE_SLICE_TIMEOUT_MS = readBudgetEnvNumber("ORGX_AUTOPILOT_SLICE_TIMEOUT_MS", 55 * 60_000,
|
|
3166
3670
|
// Keep test runs fast; real-world defaults are still ~1h unless overridden.
|
|
@@ -3287,7 +3791,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3287
3791
|
async function updateInitiativeAutoContinueState(input) {
|
|
3288
3792
|
const now = new Date().toISOString();
|
|
3289
3793
|
const patch = {
|
|
3290
|
-
auto_continue_enabled:
|
|
3794
|
+
auto_continue_enabled: input.run.status === "running" || input.run.status === "stopping",
|
|
3291
3795
|
auto_continue_status: input.run.status,
|
|
3292
3796
|
auto_continue_stop_reason: input.run.stopReason,
|
|
3293
3797
|
auto_continue_started_at: input.run.startedAt,
|
|
@@ -3308,6 +3812,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3308
3812
|
}
|
|
3309
3813
|
async function stopAutoContinueRun(input) {
|
|
3310
3814
|
const now = new Date().toISOString();
|
|
3815
|
+
const activeRunId = input.run.activeRunId;
|
|
3311
3816
|
input.run.status = "stopped";
|
|
3312
3817
|
input.run.stopReason = input.reason;
|
|
3313
3818
|
input.run.stoppedAt = now;
|
|
@@ -3315,16 +3820,21 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3315
3820
|
input.run.stopRequested = false;
|
|
3316
3821
|
input.run.activeRunId = null;
|
|
3317
3822
|
input.run.activeTaskId = null;
|
|
3823
|
+
input.run.activeTaskTokenEstimate = null;
|
|
3318
3824
|
if (input.error)
|
|
3319
3825
|
input.run.lastError = input.error;
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3826
|
+
clearAutoContinueSliceTransientState(activeRunId);
|
|
3827
|
+
// Only pause the initiative on non-terminal stops (error, blocked, user-requested).
|
|
3828
|
+
// Completed / budget-exhausted runs should not override the initiative status.
|
|
3829
|
+
if (input.reason !== "completed" && input.reason !== "budget_exhausted") {
|
|
3830
|
+
try {
|
|
3831
|
+
await client.updateEntity("initiative", input.run.initiativeId, {
|
|
3832
|
+
status: "paused",
|
|
3833
|
+
});
|
|
3834
|
+
}
|
|
3835
|
+
catch {
|
|
3836
|
+
// best effort
|
|
3837
|
+
}
|
|
3328
3838
|
}
|
|
3329
3839
|
try {
|
|
3330
3840
|
await updateInitiativeAutoContinueState({
|
|
@@ -3335,6 +3845,48 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3335
3845
|
catch {
|
|
3336
3846
|
// best effort
|
|
3337
3847
|
}
|
|
3848
|
+
const scopeSuffix = Array.isArray(input.run.allowedWorkstreamIds) && input.run.allowedWorkstreamIds.length === 1
|
|
3849
|
+
? ` (${input.run.allowedWorkstreamIds[0]})`
|
|
3850
|
+
: "";
|
|
3851
|
+
const message = input.reason === "completed"
|
|
3852
|
+
? `Autopilot stopped: current dispatch scope completed${scopeSuffix}.`
|
|
3853
|
+
: input.reason === "budget_exhausted"
|
|
3854
|
+
? `Autopilot stopped: token budget exhausted (${input.run.tokensUsed}/${input.run.tokenBudget}).`
|
|
3855
|
+
: input.reason === "stopped"
|
|
3856
|
+
? `Autopilot stopped by user request${scopeSuffix}.`
|
|
3857
|
+
: input.reason === "blocked"
|
|
3858
|
+
? `Autopilot stopped: blocked pending decision${scopeSuffix}.`
|
|
3859
|
+
: `Autopilot stopped due to error${scopeSuffix}.`;
|
|
3860
|
+
const phase = input.reason === "completed"
|
|
3861
|
+
? "completed"
|
|
3862
|
+
: input.reason === "blocked" || input.reason === "error"
|
|
3863
|
+
? "blocked"
|
|
3864
|
+
: "review";
|
|
3865
|
+
const level = input.reason === "completed"
|
|
3866
|
+
? "info"
|
|
3867
|
+
: input.reason === "budget_exhausted" || input.reason === "stopped"
|
|
3868
|
+
? "warn"
|
|
3869
|
+
: "error";
|
|
3870
|
+
await emitActivitySafe({
|
|
3871
|
+
initiativeId: input.run.initiativeId,
|
|
3872
|
+
runId: activeRunId ?? input.run.lastRunId ?? undefined,
|
|
3873
|
+
correlationId: activeRunId ?? input.run.lastRunId ?? undefined,
|
|
3874
|
+
phase,
|
|
3875
|
+
level,
|
|
3876
|
+
message,
|
|
3877
|
+
metadata: {
|
|
3878
|
+
event: "auto_continue_stopped",
|
|
3879
|
+
stop_reason: input.reason,
|
|
3880
|
+
requested_by_agent_id: input.run.agentId,
|
|
3881
|
+
requested_by_agent_name: input.run.agentName,
|
|
3882
|
+
active_run_id: activeRunId,
|
|
3883
|
+
last_run_id: input.run.lastRunId,
|
|
3884
|
+
token_budget: input.run.tokenBudget,
|
|
3885
|
+
tokens_used: input.run.tokensUsed,
|
|
3886
|
+
allowed_workstream_ids: input.run.allowedWorkstreamIds,
|
|
3887
|
+
last_error: input.run.lastError,
|
|
3888
|
+
},
|
|
3889
|
+
});
|
|
3338
3890
|
}
|
|
3339
3891
|
function ensurePrivateDirForFile(pathname) {
|
|
3340
3892
|
const dir = dirname(pathname);
|
|
@@ -3460,11 +4012,54 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3460
4012
|
const direct = parseJsonSafe(trimmed);
|
|
3461
4013
|
if (direct && typeof direct === "object")
|
|
3462
4014
|
return direct;
|
|
3463
|
-
// Tolerant parse:
|
|
3464
|
-
|
|
3465
|
-
const
|
|
3466
|
-
|
|
3467
|
-
|
|
4015
|
+
// Tolerant parse: extract the last complete top-level JSON object from mixed logs.
|
|
4016
|
+
// (Workers sometimes append multiple JSON payloads, or wrap output with extra text.)
|
|
4017
|
+
const extractLastTopLevelObject = (text) => {
|
|
4018
|
+
let inString = false;
|
|
4019
|
+
let escaped = false;
|
|
4020
|
+
let depth = 0;
|
|
4021
|
+
let start = -1;
|
|
4022
|
+
let lastObject = null;
|
|
4023
|
+
for (let i = 0; i < text.length; i += 1) {
|
|
4024
|
+
const ch = text[i];
|
|
4025
|
+
if (inString) {
|
|
4026
|
+
if (escaped) {
|
|
4027
|
+
escaped = false;
|
|
4028
|
+
continue;
|
|
4029
|
+
}
|
|
4030
|
+
if (ch === "\\") {
|
|
4031
|
+
escaped = true;
|
|
4032
|
+
continue;
|
|
4033
|
+
}
|
|
4034
|
+
if (ch === "\"") {
|
|
4035
|
+
inString = false;
|
|
4036
|
+
}
|
|
4037
|
+
continue;
|
|
4038
|
+
}
|
|
4039
|
+
if (ch === "\"") {
|
|
4040
|
+
inString = true;
|
|
4041
|
+
continue;
|
|
4042
|
+
}
|
|
4043
|
+
if (ch === "{") {
|
|
4044
|
+
if (depth === 0)
|
|
4045
|
+
start = i;
|
|
4046
|
+
depth += 1;
|
|
4047
|
+
continue;
|
|
4048
|
+
}
|
|
4049
|
+
if (ch === "}") {
|
|
4050
|
+
if (depth <= 0)
|
|
4051
|
+
continue;
|
|
4052
|
+
depth -= 1;
|
|
4053
|
+
if (depth === 0 && start >= 0) {
|
|
4054
|
+
lastObject = text.slice(start, i + 1);
|
|
4055
|
+
start = -1;
|
|
4056
|
+
}
|
|
4057
|
+
}
|
|
4058
|
+
}
|
|
4059
|
+
return lastObject;
|
|
4060
|
+
};
|
|
4061
|
+
const candidate = extractLastTopLevelObject(trimmed);
|
|
4062
|
+
if (candidate) {
|
|
3468
4063
|
const parsed = parseJsonSafe(candidate);
|
|
3469
4064
|
if (parsed && typeof parsed === "object")
|
|
3470
4065
|
return parsed;
|
|
@@ -3717,8 +4312,16 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3717
4312
|
...input.env,
|
|
3718
4313
|
},
|
|
3719
4314
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3720
|
-
|
|
4315
|
+
// Keep the mock worker as a normal child so stdout/stderr capture is deterministic.
|
|
4316
|
+
detached: false,
|
|
3721
4317
|
});
|
|
4318
|
+
autoContinueSliceChildren.set(input.runId, child);
|
|
4319
|
+
try {
|
|
4320
|
+
logStream.write(`spawned pid=${String(child.pid ?? "")} stdout=${String(Boolean(child.stdout))} stderr=${String(Boolean(child.stderr))}\n`);
|
|
4321
|
+
}
|
|
4322
|
+
catch {
|
|
4323
|
+
// ignore
|
|
4324
|
+
}
|
|
3722
4325
|
child.stdout?.on("data", (chunk) => {
|
|
3723
4326
|
try {
|
|
3724
4327
|
logStream.write(chunk);
|
|
@@ -3742,6 +4345,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3742
4345
|
}
|
|
3743
4346
|
});
|
|
3744
4347
|
child.on("close", (code, signal) => {
|
|
4348
|
+
autoContinueSliceChildren.delete(input.runId);
|
|
3745
4349
|
const stamp = new Date().toISOString();
|
|
3746
4350
|
try {
|
|
3747
4351
|
logStream.write(`\n==== ${stamp} :: exit code=${String(code)} signal=${String(signal)} ====\n`);
|
|
@@ -3763,6 +4367,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3763
4367
|
}
|
|
3764
4368
|
});
|
|
3765
4369
|
child.on("error", (error) => {
|
|
4370
|
+
autoContinueSliceChildren.delete(input.runId);
|
|
3766
4371
|
const msg = safeErrorMessage(error);
|
|
3767
4372
|
try {
|
|
3768
4373
|
logStream.write(`\nworker error: ${msg}\n`);
|
|
@@ -3783,7 +4388,6 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3783
4388
|
// ignore
|
|
3784
4389
|
}
|
|
3785
4390
|
});
|
|
3786
|
-
child.unref();
|
|
3787
4391
|
return { pid: child.pid ?? null };
|
|
3788
4392
|
}
|
|
3789
4393
|
if (workerKind === "claude-code" || workerKind === "claude_code") {
|
|
@@ -3805,6 +4409,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3805
4409
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3806
4410
|
detached: true,
|
|
3807
4411
|
});
|
|
4412
|
+
autoContinueSliceChildren.set(input.runId, child);
|
|
3808
4413
|
child.stdout?.on("data", (chunk) => {
|
|
3809
4414
|
try {
|
|
3810
4415
|
logStream.write(chunk);
|
|
@@ -3828,6 +4433,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3828
4433
|
}
|
|
3829
4434
|
});
|
|
3830
4435
|
child.on("close", (code, signal) => {
|
|
4436
|
+
autoContinueSliceChildren.delete(input.runId);
|
|
3831
4437
|
const stamp = new Date().toISOString();
|
|
3832
4438
|
try {
|
|
3833
4439
|
logStream.write(`\n==== ${stamp} :: exit code=${String(code)} signal=${String(signal)} ====\n`);
|
|
@@ -3849,6 +4455,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3849
4455
|
}
|
|
3850
4456
|
});
|
|
3851
4457
|
child.on("error", (error) => {
|
|
4458
|
+
autoContinueSliceChildren.delete(input.runId);
|
|
3852
4459
|
const msg = safeErrorMessage(error);
|
|
3853
4460
|
try {
|
|
3854
4461
|
logStream.write(`\nworker error: ${msg}\n`);
|
|
@@ -3908,6 +4515,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3908
4515
|
stdio: ["ignore", "pipe", "pipe"],
|
|
3909
4516
|
detached: true,
|
|
3910
4517
|
});
|
|
4518
|
+
autoContinueSliceChildren.set(input.runId, child);
|
|
3911
4519
|
child.stdout?.on("data", (chunk) => {
|
|
3912
4520
|
try {
|
|
3913
4521
|
logStream.write(chunk);
|
|
@@ -3925,6 +4533,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3925
4533
|
}
|
|
3926
4534
|
});
|
|
3927
4535
|
child.on("close", (code, signal) => {
|
|
4536
|
+
autoContinueSliceChildren.delete(input.runId);
|
|
3928
4537
|
const stamp = new Date().toISOString();
|
|
3929
4538
|
try {
|
|
3930
4539
|
logStream.write(`\n==== ${stamp} :: exit code=${String(code)} signal=${String(signal)} ====\n`);
|
|
@@ -3940,6 +4549,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3940
4549
|
}
|
|
3941
4550
|
});
|
|
3942
4551
|
child.on("error", (error) => {
|
|
4552
|
+
autoContinueSliceChildren.delete(input.runId);
|
|
3943
4553
|
const msg = safeErrorMessage(error);
|
|
3944
4554
|
try {
|
|
3945
4555
|
logStream.write(`\nworker error: ${msg}\n`);
|
|
@@ -3982,6 +4592,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
3982
4592
|
});
|
|
3983
4593
|
// Make runtime updates feel instantaneous (don't wait for the 15s staleness timer).
|
|
3984
4594
|
broadcastRuntimeSse("runtime.updated", instance);
|
|
4595
|
+
clearSnapshotResponseCache();
|
|
3985
4596
|
return instance;
|
|
3986
4597
|
}
|
|
3987
4598
|
function buildWorkstreamSlicePrompt(input) {
|
|
@@ -4026,6 +4637,10 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4026
4637
|
`- Your JSON MUST conform to this schema file: ${input.schemaPath}`,
|
|
4027
4638
|
`- Artifacts must be verifiable: include URLs or local paths, plus verification steps.`,
|
|
4028
4639
|
`- If you need a human decision, include it in decisions_needed.`,
|
|
4640
|
+
`- For every decisions_needed entry, ALWAYS set blocking explicitly (true or false).`,
|
|
4641
|
+
`- Status/decision consistency is strict:`,
|
|
4642
|
+
` - If any decision is blocking=true, status MUST be needs_decision or blocked (never completed).`,
|
|
4643
|
+
` - Only use status=completed when all listed decisions are non-blocking follow-ups.`,
|
|
4029
4644
|
`- If you are confident OrgX statuses should change, include task_updates and/or milestone_updates (with a short reason).`,
|
|
4030
4645
|
` - task_updates.status must be one of: todo, in_progress, done, blocked`,
|
|
4031
4646
|
` - milestone_updates.status must be one of: planned, in_progress, completed, at_risk, cancelled`,
|
|
@@ -4037,6 +4652,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4037
4652
|
if (!name)
|
|
4038
4653
|
return { ok: false, id: null };
|
|
4039
4654
|
const artifactType = (input.artifact.artifact_type ?? "other").trim() || "other";
|
|
4655
|
+
const artifactId = randomUUID();
|
|
4040
4656
|
const verificationSteps = Array.isArray(input.artifact.verification_steps)
|
|
4041
4657
|
? input.artifact.verification_steps
|
|
4042
4658
|
.filter((step) => typeof step === "string")
|
|
@@ -4051,23 +4667,38 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4051
4667
|
descriptionParts.push(`Verification:\n${verificationSteps.map((step) => `- ${step}`).join("\n")}`);
|
|
4052
4668
|
}
|
|
4053
4669
|
const description = descriptionParts.length > 0 ? descriptionParts.join("\n\n") : undefined;
|
|
4670
|
+
const hasUuidAgent = typeof input.agentId === "string" &&
|
|
4671
|
+
/^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(input.agentId);
|
|
4672
|
+
const createdByType = hasUuidAgent ? "agent" : "human";
|
|
4673
|
+
const createdById = hasUuidAgent ? input.agentId : null;
|
|
4054
4674
|
try {
|
|
4055
|
-
const
|
|
4056
|
-
|
|
4057
|
-
|
|
4058
|
-
|
|
4675
|
+
const entityType = input.artifact.milestone_id ? "milestone" : "initiative";
|
|
4676
|
+
const entityId = input.artifact.milestone_id ? input.artifact.milestone_id : input.initiativeId;
|
|
4677
|
+
const result = await registerArtifact(client, client.getBaseUrl(), {
|
|
4678
|
+
artifact_id: artifactId,
|
|
4679
|
+
entity_type: entityType,
|
|
4680
|
+
entity_id: entityId,
|
|
4681
|
+
name,
|
|
4059
4682
|
artifact_type: artifactType,
|
|
4683
|
+
created_by_type: createdByType,
|
|
4684
|
+
created_by_id: createdById,
|
|
4060
4685
|
description,
|
|
4061
|
-
|
|
4062
|
-
|
|
4686
|
+
external_url: input.artifact.url ?? null,
|
|
4687
|
+
preview_markdown: null,
|
|
4688
|
+
status: "draft",
|
|
4063
4689
|
metadata: {
|
|
4064
4690
|
source: "autopilot_slice",
|
|
4691
|
+
artifact_id: artifactId,
|
|
4065
4692
|
run_id: input.runId,
|
|
4693
|
+
initiative_id: input.initiativeId,
|
|
4694
|
+
workstream_id: input.workstreamId,
|
|
4066
4695
|
milestone_id: input.artifact.milestone_id ?? null,
|
|
4067
4696
|
task_ids: input.artifact.task_ids ?? null,
|
|
4068
4697
|
},
|
|
4698
|
+
// Make persistence validation opt-in to avoid adding latency to every slice by default.
|
|
4699
|
+
validate_persistence: process.env.ORGX_VALIDATE_ARTIFACT_PERSISTENCE === "1",
|
|
4069
4700
|
});
|
|
4070
|
-
return { ok:
|
|
4701
|
+
return { ok: result.ok, id: result.artifact_id };
|
|
4071
4702
|
}
|
|
4072
4703
|
catch (err) {
|
|
4073
4704
|
try {
|
|
@@ -4076,10 +4707,13 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4076
4707
|
type: "artifact",
|
|
4077
4708
|
timestamp: now,
|
|
4078
4709
|
payload: {
|
|
4079
|
-
|
|
4080
|
-
|
|
4710
|
+
artifact_id: artifactId,
|
|
4711
|
+
entity_type: input.artifact.milestone_id ? "milestone" : "initiative",
|
|
4712
|
+
entity_id: input.artifact.milestone_id ?? input.initiativeId,
|
|
4081
4713
|
name,
|
|
4082
4714
|
artifact_type: artifactType,
|
|
4715
|
+
created_by_type: createdByType,
|
|
4716
|
+
created_by_id: createdById,
|
|
4083
4717
|
description,
|
|
4084
4718
|
url: input.artifact.url ?? undefined,
|
|
4085
4719
|
run_id: input.runId,
|
|
@@ -4091,6 +4725,10 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4091
4725
|
description: description ?? null,
|
|
4092
4726
|
agentId: input.agentId,
|
|
4093
4727
|
agentName: input.agentName ?? null,
|
|
4728
|
+
requesterAgentId: input.agentId,
|
|
4729
|
+
requesterAgentName: input.agentName ?? null,
|
|
4730
|
+
executorAgentId: input.agentId,
|
|
4731
|
+
executorAgentName: input.agentName ?? null,
|
|
4094
4732
|
runId: input.runId,
|
|
4095
4733
|
initiativeId: input.initiativeId,
|
|
4096
4734
|
timestamp: now,
|
|
@@ -4100,6 +4738,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4100
4738
|
source: "openclaw_local_fallback",
|
|
4101
4739
|
event: "autopilot_slice_artifact_buffered",
|
|
4102
4740
|
artifact_type: artifactType,
|
|
4741
|
+
artifact_id: artifactId,
|
|
4103
4742
|
url: input.artifact.url ?? null,
|
|
4104
4743
|
error: safeErrorMessage(err),
|
|
4105
4744
|
},
|
|
@@ -4228,6 +4867,10 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4228
4867
|
description: null,
|
|
4229
4868
|
agentId: null,
|
|
4230
4869
|
agentName: null,
|
|
4870
|
+
requesterAgentId: null,
|
|
4871
|
+
requesterAgentName: null,
|
|
4872
|
+
executorAgentId: null,
|
|
4873
|
+
executorAgentName: null,
|
|
4231
4874
|
runId: input.runId,
|
|
4232
4875
|
initiativeId: input.initiativeId,
|
|
4233
4876
|
timestamp,
|
|
@@ -4330,6 +4973,9 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4330
4973
|
metadata: {
|
|
4331
4974
|
event: "next_up_manual_dispatch_started",
|
|
4332
4975
|
agent_id: input.agentId,
|
|
4976
|
+
agent_name: input.agentName ?? input.agentId,
|
|
4977
|
+
requested_by_agent_id: input.agentId,
|
|
4978
|
+
requested_by_agent_name: input.agentName ?? input.agentId,
|
|
4333
4979
|
session_id: sessionId,
|
|
4334
4980
|
workstream_id: input.workstreamId,
|
|
4335
4981
|
workstream_title: resolvedWorkstreamTitle,
|
|
@@ -4384,6 +5030,32 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4384
5030
|
spawnGuardResult: guard.spawnGuardResult,
|
|
4385
5031
|
};
|
|
4386
5032
|
}
|
|
5033
|
+
async function resolveAgentDisplayName(agentId, fallbackName) {
|
|
5034
|
+
const normalizedAgentId = agentId.trim();
|
|
5035
|
+
if (!normalizedAgentId)
|
|
5036
|
+
return null;
|
|
5037
|
+
const normalizedFallback = typeof fallbackName === "string" && fallbackName.trim().length > 0
|
|
5038
|
+
? fallbackName.trim()
|
|
5039
|
+
: null;
|
|
5040
|
+
try {
|
|
5041
|
+
const agents = await listAgents();
|
|
5042
|
+
for (const entry of agents) {
|
|
5043
|
+
const record = entry;
|
|
5044
|
+
const candidateId = pickString(record, ["id", "agent_id", "agentId"]);
|
|
5045
|
+
if (!candidateId || candidateId.trim() !== normalizedAgentId)
|
|
5046
|
+
continue;
|
|
5047
|
+
const candidateName = pickString(record, ["name", "agent_name", "agentName"]);
|
|
5048
|
+
if (candidateName && candidateName.trim().length > 0) {
|
|
5049
|
+
return candidateName.trim();
|
|
5050
|
+
}
|
|
5051
|
+
break;
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
catch {
|
|
5055
|
+
// best effort
|
|
5056
|
+
}
|
|
5057
|
+
return normalizedFallback ?? normalizedAgentId;
|
|
5058
|
+
}
|
|
4387
5059
|
async function tickAutoContinueRun(run) {
|
|
4388
5060
|
if (run.status !== "running" && run.status !== "stopping")
|
|
4389
5061
|
return;
|
|
@@ -4408,13 +5080,15 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4408
5080
|
typeof outputParsed.summary === "string");
|
|
4409
5081
|
if (outputComplete) {
|
|
4410
5082
|
// Some platforms can report a just-finished detached process as still "alive" (zombie).
|
|
4411
|
-
// Best-effort stop so we can proceed to parse the output contract below.
|
|
5083
|
+
// Best-effort stop, then clear pid so we can proceed to parse the output contract below.
|
|
4412
5084
|
try {
|
|
4413
5085
|
await stopProcess(pid);
|
|
4414
5086
|
}
|
|
4415
5087
|
catch {
|
|
4416
5088
|
// best effort
|
|
4417
5089
|
}
|
|
5090
|
+
slice.pid = null;
|
|
5091
|
+
autoContinueSliceRuns.set(slice.runId, slice);
|
|
4418
5092
|
}
|
|
4419
5093
|
else {
|
|
4420
5094
|
const lastHeartbeat = autoContinueSliceLastHeartbeatMs.get(slice.runId) ?? 0;
|
|
@@ -4433,6 +5107,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4433
5107
|
message: `Autopilot slice running: ${slice.workstreamTitle ?? slice.workstreamId}`,
|
|
4434
5108
|
metadata: {
|
|
4435
5109
|
event: "autopilot_slice_heartbeat",
|
|
5110
|
+
requested_by_agent_id: run.agentId,
|
|
5111
|
+
requested_by_agent_name: run.agentName,
|
|
4436
5112
|
domain: slice.domain,
|
|
4437
5113
|
required_skills: slice.requiredSkills,
|
|
4438
5114
|
workstream_id: slice.workstreamId,
|
|
@@ -4470,6 +5146,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4470
5146
|
autoContinueSliceRuns.set(slice.runId, slice);
|
|
4471
5147
|
run.lastError = slice.lastError;
|
|
4472
5148
|
run.updatedAt = now;
|
|
5149
|
+
clearAutoContinueSliceTransientState(slice.runId);
|
|
4473
5150
|
await emitActivitySafe({
|
|
4474
5151
|
initiativeId: run.initiativeId,
|
|
4475
5152
|
runId: slice.runId,
|
|
@@ -4479,6 +5156,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4479
5156
|
message: `Autopilot slice MCP failed: ${slice.workstreamTitle ?? slice.workstreamId}.`,
|
|
4480
5157
|
metadata: {
|
|
4481
5158
|
event: "autopilot_slice_mcp_handshake_failed",
|
|
5159
|
+
requested_by_agent_id: run.agentId,
|
|
5160
|
+
requested_by_agent_name: run.agentName,
|
|
4482
5161
|
mcp_server: mcpHandshake.server,
|
|
4483
5162
|
mcp_line: mcpHandshake.line,
|
|
4484
5163
|
workstream_id: slice.workstreamId,
|
|
@@ -4530,6 +5209,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4530
5209
|
autoContinueSliceRuns.set(slice.runId, slice);
|
|
4531
5210
|
run.lastError = slice.lastError;
|
|
4532
5211
|
run.updatedAt = now;
|
|
5212
|
+
clearAutoContinueSliceTransientState(slice.runId);
|
|
4533
5213
|
const event = killDecision.kind === "timeout" ? "autopilot_slice_timeout" : "autopilot_slice_log_stall";
|
|
4534
5214
|
const humanLabel = killDecision.kind === "timeout" ? "timed out" : "stalled";
|
|
4535
5215
|
await emitActivitySafe({
|
|
@@ -4541,6 +5221,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4541
5221
|
message: `Autopilot slice ${humanLabel}: ${slice.workstreamTitle ?? slice.workstreamId}.`,
|
|
4542
5222
|
metadata: {
|
|
4543
5223
|
event,
|
|
5224
|
+
requested_by_agent_id: run.agentId,
|
|
5225
|
+
requested_by_agent_name: run.agentName,
|
|
4544
5226
|
workstream_id: slice.workstreamId,
|
|
4545
5227
|
task_ids: slice.taskIds,
|
|
4546
5228
|
milestone_ids: slice.milestoneIds,
|
|
@@ -4579,17 +5261,28 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4579
5261
|
// best effort
|
|
4580
5262
|
}
|
|
4581
5263
|
}
|
|
4582
|
-
|
|
5264
|
+
if (!outputComplete)
|
|
5265
|
+
return;
|
|
4583
5266
|
}
|
|
4584
5267
|
}
|
|
4585
5268
|
// Slice finished.
|
|
4586
5269
|
const raw = readSliceOutputFile(slice.outputPath);
|
|
4587
5270
|
const parsed = raw ? parseSliceResult(raw) : null;
|
|
4588
5271
|
const parsedStatus = parsed?.status ?? "error";
|
|
5272
|
+
const defaultDecisionBlocking = parsedStatus === "completed" ? false : true;
|
|
5273
|
+
const decisions = Array.isArray(parsed?.decisions_needed)
|
|
5274
|
+
? (parsed?.decisions_needed ?? [])
|
|
5275
|
+
.filter((item) => Boolean(item && typeof item.question === "string" && item.question.trim()))
|
|
5276
|
+
: [];
|
|
5277
|
+
const blockingDecisionCount = decisions.filter((item) => typeof item.blocking === "boolean" ? item.blocking : defaultDecisionBlocking).length;
|
|
5278
|
+
const nonBlockingDecisionCount = Math.max(0, decisions.length - blockingDecisionCount);
|
|
5279
|
+
const effectiveParsedStatus = parsedStatus === "completed" && blockingDecisionCount > 0
|
|
5280
|
+
? "needs_decision"
|
|
5281
|
+
: parsedStatus;
|
|
4589
5282
|
slice.status =
|
|
4590
|
-
|
|
5283
|
+
effectiveParsedStatus === "completed"
|
|
4591
5284
|
? "completed"
|
|
4592
|
-
:
|
|
5285
|
+
: effectiveParsedStatus === "blocked" || effectiveParsedStatus === "needs_decision"
|
|
4593
5286
|
? "blocked"
|
|
4594
5287
|
: "error";
|
|
4595
5288
|
slice.finishedAt = now;
|
|
@@ -4599,14 +5292,11 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4599
5292
|
? slice.lastError ?? "Autopilot slice failed or returned invalid output."
|
|
4600
5293
|
: null;
|
|
4601
5294
|
autoContinueSliceRuns.set(slice.runId, slice);
|
|
5295
|
+
clearAutoContinueSliceTransientState(slice.runId);
|
|
4602
5296
|
// Token accounting: codex CLI doesn't provide tokens here; use the modeled estimate.
|
|
4603
5297
|
const modeledTokens = slice.tokenEstimate ?? run.activeTaskTokenEstimate ?? 0;
|
|
4604
5298
|
run.tokensUsed += Math.max(0, modeledTokens);
|
|
4605
5299
|
run.activeTaskTokenEstimate = null;
|
|
4606
|
-
const decisions = Array.isArray(parsed?.decisions_needed)
|
|
4607
|
-
? (parsed?.decisions_needed ?? [])
|
|
4608
|
-
.filter((item) => Boolean(item && typeof item.question === "string" && item.question.trim()))
|
|
4609
|
-
: [];
|
|
4610
5300
|
const artifacts = Array.isArray(parsed?.artifacts)
|
|
4611
5301
|
? (parsed?.artifacts ?? [])
|
|
4612
5302
|
.filter((item) => Boolean(item && typeof item.name === "string" && item.name.trim()))
|
|
@@ -4627,7 +5317,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4627
5317
|
options: Array.isArray(decision.options)
|
|
4628
5318
|
? decision.options.filter((opt) => typeof opt === "string" && opt.trim())
|
|
4629
5319
|
: [],
|
|
4630
|
-
blocking: typeof decision.blocking === "boolean" ? decision.blocking :
|
|
5320
|
+
blocking: typeof decision.blocking === "boolean" ? decision.blocking : defaultDecisionBlocking,
|
|
4631
5321
|
});
|
|
4632
5322
|
}
|
|
4633
5323
|
for (const artifact of artifacts) {
|
|
@@ -4661,9 +5351,13 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4661
5351
|
message: parsed?.summary ?? slice.lastError ?? "Autopilot slice finished.",
|
|
4662
5352
|
metadata: {
|
|
4663
5353
|
event: "autopilot_slice_finished",
|
|
4664
|
-
|
|
5354
|
+
requested_by_agent_id: run.agentId,
|
|
5355
|
+
requested_by_agent_name: run.agentName,
|
|
5356
|
+
status: effectiveParsedStatus,
|
|
4665
5357
|
artifacts: artifacts.length,
|
|
4666
5358
|
decisions: decisions.length,
|
|
5359
|
+
blocking_decisions: blockingDecisionCount,
|
|
5360
|
+
non_blocking_decisions: nonBlockingDecisionCount,
|
|
4667
5361
|
status_updates: statusUpdateResult.applied,
|
|
4668
5362
|
status_updates_buffered: statusUpdateResult.buffered,
|
|
4669
5363
|
},
|
|
@@ -4679,10 +5373,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4679
5373
|
phase: slice.status === "completed" ? "completed" : "blocked",
|
|
4680
5374
|
level: slice.status === "completed" ? "info" : "warn",
|
|
4681
5375
|
message: slice.status === "completed"
|
|
4682
|
-
? `Autopilot slice completed
|
|
5376
|
+
? `Autopilot slice completed for ${slice.workstreamTitle ?? slice.workstreamId} (${slice.taskIds.length} task${slice.taskIds.length === 1 ? "" : "s"}).`
|
|
4683
5377
|
: `Autopilot slice blocked: ${slice.workstreamTitle ?? slice.workstreamId}.`,
|
|
4684
5378
|
metadata: {
|
|
4685
5379
|
event: "autopilot_slice_result",
|
|
5380
|
+
requested_by_agent_id: run.agentId,
|
|
5381
|
+
requested_by_agent_name: run.agentName,
|
|
4686
5382
|
agent_id: slice.agentId,
|
|
4687
5383
|
agent_name: slice.agentName,
|
|
4688
5384
|
domain: slice.domain,
|
|
@@ -4690,10 +5386,13 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4690
5386
|
workstream_id: slice.workstreamId,
|
|
4691
5387
|
task_ids: slice.taskIds,
|
|
4692
5388
|
milestone_ids: slice.milestoneIds,
|
|
4693
|
-
parsed_status:
|
|
5389
|
+
parsed_status: effectiveParsedStatus,
|
|
4694
5390
|
has_output: Boolean(parsed),
|
|
4695
5391
|
artifacts: artifacts.length,
|
|
4696
5392
|
decisions: decisions.length,
|
|
5393
|
+
blocking_decisions: blockingDecisionCount,
|
|
5394
|
+
non_blocking_decisions: nonBlockingDecisionCount,
|
|
5395
|
+
decision_required: blockingDecisionCount > 0,
|
|
4697
5396
|
status_updates_applied: statusUpdateResult.applied,
|
|
4698
5397
|
status_updates_buffered: statusUpdateResult.buffered,
|
|
4699
5398
|
output_path: slice.outputPath,
|
|
@@ -4724,7 +5423,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4724
5423
|
reason: slice.status === "error" ? "error" : "blocked",
|
|
4725
5424
|
error: parsed?.summary ??
|
|
4726
5425
|
slice.lastError ??
|
|
4727
|
-
`Slice returned status: ${
|
|
5426
|
+
`Slice returned status: ${effectiveParsedStatus}`,
|
|
4728
5427
|
});
|
|
4729
5428
|
return;
|
|
4730
5429
|
}
|
|
@@ -4839,7 +5538,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4839
5538
|
continue;
|
|
4840
5539
|
if (!run.includeVerification &&
|
|
4841
5540
|
typeof node.title === "string" &&
|
|
4842
|
-
/^verification
|
|
5541
|
+
/^verification[ \t]+scenario/i.test(node.title)) {
|
|
4843
5542
|
continue;
|
|
4844
5543
|
}
|
|
4845
5544
|
if (run.allowedWorkstreamIds && node.workstreamId) {
|
|
@@ -4875,7 +5574,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
4875
5574
|
taskIsReady(node) &&
|
|
4876
5575
|
!taskHasBlockedParent(node) &&
|
|
4877
5576
|
(run.includeVerification ||
|
|
4878
|
-
!/^verification
|
|
5577
|
+
!/^verification[ \t]+scenario/i.test(String(node.title ?? "")))))
|
|
4879
5578
|
.slice(0, AUTO_CONTINUE_SLICE_MAX_TASKS);
|
|
4880
5579
|
const primaryTask = sliceTaskNodes[0] ?? null;
|
|
4881
5580
|
if (!primaryTask) {
|
|
@@ -5055,6 +5754,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
5055
5754
|
ORGX_TASK_ID: primaryTask.id,
|
|
5056
5755
|
ORGX_AGENT_ID: sliceAgent.id,
|
|
5057
5756
|
ORGX_AGENT_NAME: sliceAgent.name,
|
|
5757
|
+
ORGX_OUTPUT_PATH: outputPath,
|
|
5058
5758
|
ORGX_RUNTIME_HOOK_URL: runtimeHookUrl ?? undefined,
|
|
5059
5759
|
ORGX_HOOK_TOKEN: runtimeHookToken ?? undefined,
|
|
5060
5760
|
},
|
|
@@ -5097,6 +5797,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
5097
5797
|
message: `Autopilot slice started: ${workstreamTitle ?? selectedWorkstreamId}`,
|
|
5098
5798
|
metadata: {
|
|
5099
5799
|
event: "autopilot_slice_started",
|
|
5800
|
+
requested_by_agent_id: run.agentId,
|
|
5801
|
+
requested_by_agent_name: run.agentName,
|
|
5100
5802
|
domain: executionPolicy.domain,
|
|
5101
5803
|
required_skills: executionPolicy.requiredSkills,
|
|
5102
5804
|
task_ids: slice.taskIds,
|
|
@@ -5120,6 +5822,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
5120
5822
|
message: `Autopilot dispatched slice for ${workstreamTitle ?? selectedWorkstreamId}.`,
|
|
5121
5823
|
metadata: {
|
|
5122
5824
|
event: "autopilot_slice_dispatched",
|
|
5825
|
+
requested_by_agent_id: run.agentId,
|
|
5826
|
+
requested_by_agent_name: run.agentName,
|
|
5123
5827
|
agent_id: slice.agentId,
|
|
5124
5828
|
agent_name: sliceAgent.name,
|
|
5125
5829
|
domain: executionPolicy.domain,
|
|
@@ -5163,10 +5867,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
5163
5867
|
}
|
|
5164
5868
|
}
|
|
5165
5869
|
async function tickAllAutoContinue() {
|
|
5166
|
-
if (autoContinueTickInFlight)
|
|
5870
|
+
if (autoContinueTickInFlight) {
|
|
5871
|
+
// Wait for the in-flight tick to finish instead of silently dropping.
|
|
5872
|
+
await autoContinueTickInFlight.catch(() => { });
|
|
5167
5873
|
return;
|
|
5168
|
-
|
|
5169
|
-
|
|
5874
|
+
}
|
|
5875
|
+
const work = (async () => {
|
|
5170
5876
|
for (const run of autoContinueRuns.values()) {
|
|
5171
5877
|
try {
|
|
5172
5878
|
await tickAutoContinueRun(run);
|
|
@@ -5178,9 +5884,13 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
5178
5884
|
await stopAutoContinueRun({ run, reason: "error", error: run.lastError });
|
|
5179
5885
|
}
|
|
5180
5886
|
}
|
|
5887
|
+
})();
|
|
5888
|
+
autoContinueTickInFlight = work;
|
|
5889
|
+
try {
|
|
5890
|
+
await work;
|
|
5181
5891
|
}
|
|
5182
5892
|
finally {
|
|
5183
|
-
autoContinueTickInFlight =
|
|
5893
|
+
autoContinueTickInFlight = null;
|
|
5184
5894
|
}
|
|
5185
5895
|
}
|
|
5186
5896
|
function isInitiativeActiveStatus(status) {
|
|
@@ -5207,10 +5917,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
5207
5917
|
async function startAutoContinueRun(input) {
|
|
5208
5918
|
const now = new Date().toISOString();
|
|
5209
5919
|
const existing = autoContinueRuns.get(input.initiativeId) ?? null;
|
|
5920
|
+
const existingIsLive = existing?.status === "running" || existing?.status === "stopping";
|
|
5210
5921
|
const run = existing ??
|
|
5211
5922
|
{
|
|
5212
5923
|
initiativeId: input.initiativeId,
|
|
5213
5924
|
agentId: input.agentId,
|
|
5925
|
+
agentName: input.agentName ?? null,
|
|
5214
5926
|
includeVerification: false,
|
|
5215
5927
|
allowedWorkstreamIds: null,
|
|
5216
5928
|
stopAfterSlice: false,
|
|
@@ -5230,6 +5942,10 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
5230
5942
|
activeTaskTokenEstimate: null,
|
|
5231
5943
|
};
|
|
5232
5944
|
run.agentId = input.agentId;
|
|
5945
|
+
run.agentName =
|
|
5946
|
+
typeof input.agentName === "string" && input.agentName.trim().length > 0
|
|
5947
|
+
? input.agentName.trim()
|
|
5948
|
+
: null;
|
|
5233
5949
|
run.includeVerification = input.includeVerification;
|
|
5234
5950
|
run.allowedWorkstreamIds = input.allowedWorkstreamIds;
|
|
5235
5951
|
run.stopAfterSlice = Boolean(input.stopAfterSlice);
|
|
@@ -5237,10 +5953,19 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
5237
5953
|
run.status = "running";
|
|
5238
5954
|
run.stopReason = null;
|
|
5239
5955
|
run.stopRequested = false;
|
|
5240
|
-
run.startedAt = now;
|
|
5241
5956
|
run.stoppedAt = null;
|
|
5242
5957
|
run.updatedAt = now;
|
|
5243
5958
|
run.lastError = null;
|
|
5959
|
+
const forceFreshRun = Boolean(input.stopAfterSlice);
|
|
5960
|
+
if (!existingIsLive || forceFreshRun) {
|
|
5961
|
+
run.tokensUsed = 0;
|
|
5962
|
+
run.startedAt = now;
|
|
5963
|
+
run.lastTaskId = null;
|
|
5964
|
+
run.lastRunId = null;
|
|
5965
|
+
run.activeTaskId = null;
|
|
5966
|
+
run.activeRunId = null;
|
|
5967
|
+
run.activeTaskTokenEstimate = null;
|
|
5968
|
+
}
|
|
5244
5969
|
autoContinueRuns.set(input.initiativeId, run);
|
|
5245
5970
|
try {
|
|
5246
5971
|
await client.updateEntity("initiative", input.initiativeId, { status: "active" });
|
|
@@ -5784,6 +6509,8 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
5784
6509
|
const isEntitiesRoute = route === "entities";
|
|
5785
6510
|
const entityCommentsMatch = route.match(/^entities\/([^/]+)\/([^/]+)\/comments$/);
|
|
5786
6511
|
const entityActionMatch = route.match(/^entities\/([^/]+)\/([^/]+)\/([^/]+)$/);
|
|
6512
|
+
const isArtifactsByEntityRoute = route === "work-artifacts/by-entity";
|
|
6513
|
+
const artifactDetailMatch = route.match(/^artifacts\/([^/]+)$/);
|
|
5787
6514
|
const isOnboardingStartRoute = route === "onboarding/start";
|
|
5788
6515
|
const isOnboardingStatusRoute = route === "onboarding/status";
|
|
5789
6516
|
const isOnboardingManualKeyRoute = route === "onboarding/manual-key";
|
|
@@ -6306,6 +7033,23 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
6306
7033
|
}
|
|
6307
7034
|
const result = await stopProcess(record.pid);
|
|
6308
7035
|
const updated = markAgentRunStopped(runId);
|
|
7036
|
+
try {
|
|
7037
|
+
writeRuntimeEvent({
|
|
7038
|
+
sourceClient: "codex",
|
|
7039
|
+
event: "session_stop",
|
|
7040
|
+
runId,
|
|
7041
|
+
initiativeId: record.initiativeId ?? "",
|
|
7042
|
+
workstreamId: record.workstreamId ?? null,
|
|
7043
|
+
taskId: record.taskId ?? null,
|
|
7044
|
+
agentId: record.agentId ?? null,
|
|
7045
|
+
agentName: null,
|
|
7046
|
+
phase: "stopped",
|
|
7047
|
+
message: `Agent ${record.agentId ?? "unknown"} stopped.`,
|
|
7048
|
+
});
|
|
7049
|
+
}
|
|
7050
|
+
catch {
|
|
7051
|
+
// best effort
|
|
7052
|
+
}
|
|
6309
7053
|
void posthogCapture({
|
|
6310
7054
|
event: "openclaw_agent_stop",
|
|
6311
7055
|
distinctId: telemetryDistinctId,
|
|
@@ -6363,6 +7107,17 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
6363
7107
|
sendJson(res, 404, { ok: false, error: "Run not found" });
|
|
6364
7108
|
return true;
|
|
6365
7109
|
}
|
|
7110
|
+
// Stop the previous process before spawning a replacement to avoid
|
|
7111
|
+
// orphaned workers consuming resources or writing conflicting output.
|
|
7112
|
+
if (record.pid) {
|
|
7113
|
+
try {
|
|
7114
|
+
await stopProcess(record.pid);
|
|
7115
|
+
}
|
|
7116
|
+
catch {
|
|
7117
|
+
// best effort — process may have already exited
|
|
7118
|
+
}
|
|
7119
|
+
markAgentRunStopped(previousRunId);
|
|
7120
|
+
}
|
|
6366
7121
|
const messageOverride = (pickString(payload, ["message", "prompt", "text"]) ??
|
|
6367
7122
|
searchParams.get("message") ??
|
|
6368
7123
|
searchParams.get("prompt") ??
|
|
@@ -6578,6 +7333,9 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
6578
7333
|
});
|
|
6579
7334
|
return true;
|
|
6580
7335
|
}
|
|
7336
|
+
const requestedAgentName = await resolveAgentDisplayName(agentId, matchedQueueItem?.runnerAgentId === agentId
|
|
7337
|
+
? matchedQueueItem.runnerAgentName
|
|
7338
|
+
: null);
|
|
6581
7339
|
// Autopilot v2 runs slices via local codex dispatch, so BYOK plan gating does not apply here.
|
|
6582
7340
|
const tokenBudget = pickNumber(payload, [
|
|
6583
7341
|
"tokenBudget",
|
|
@@ -6612,9 +7370,29 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
6612
7370
|
const fastAck = typeof fastAckRaw === "boolean"
|
|
6613
7371
|
? fastAckRaw
|
|
6614
7372
|
: parseBooleanQuery(typeof fastAckRaw === "string" ? fastAckRaw : null);
|
|
7373
|
+
const existingRun = autoContinueRuns.get(initiativeId) ?? null;
|
|
7374
|
+
if (existingRun &&
|
|
7375
|
+
(existingRun.status === "running" || existingRun.status === "stopping") &&
|
|
7376
|
+
existingRun.activeRunId) {
|
|
7377
|
+
const activeSlice = autoContinueSliceRuns.get(existingRun.activeRunId) ?? null;
|
|
7378
|
+
const activeWorkstreamId = activeSlice?.workstreamId ?? null;
|
|
7379
|
+
const activeWorkstreamTitle = activeSlice?.workstreamTitle ?? null;
|
|
7380
|
+
sendJson(res, 409, {
|
|
7381
|
+
ok: false,
|
|
7382
|
+
code: "auto_continue_already_running",
|
|
7383
|
+
error: activeWorkstreamId || activeWorkstreamTitle
|
|
7384
|
+
? `Auto-continue is already running for ${activeWorkstreamTitle ?? activeWorkstreamId}. Stop it before launching another Play run.`
|
|
7385
|
+
: "Auto-continue is already running for this initiative. Stop it before launching another Play run.",
|
|
7386
|
+
run: existingRun,
|
|
7387
|
+
activeWorkstreamId,
|
|
7388
|
+
activeWorkstreamTitle,
|
|
7389
|
+
});
|
|
7390
|
+
return true;
|
|
7391
|
+
}
|
|
6615
7392
|
const run = await startAutoContinueRun({
|
|
6616
7393
|
initiativeId,
|
|
6617
7394
|
agentId,
|
|
7395
|
+
agentName: requestedAgentName,
|
|
6618
7396
|
tokenBudget,
|
|
6619
7397
|
includeVerification,
|
|
6620
7398
|
allowedWorkstreamIds: [workstreamId],
|
|
@@ -6631,6 +7409,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
6631
7409
|
workstreamId,
|
|
6632
7410
|
workstreamTitle: matchedQueueItem.workstreamTitle,
|
|
6633
7411
|
agentId,
|
|
7412
|
+
agentName: requestedAgentName,
|
|
6634
7413
|
taskId: matchedQueueItem.nextTaskId ?? null,
|
|
6635
7414
|
taskTitle: matchedQueueItem.nextTaskTitle ?? null,
|
|
6636
7415
|
});
|
|
@@ -6880,6 +7659,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
6880
7659
|
const run = await startAutoContinueRun({
|
|
6881
7660
|
initiativeId,
|
|
6882
7661
|
agentId,
|
|
7662
|
+
agentName: await resolveAgentDisplayName(agentId, null),
|
|
6883
7663
|
tokenBudget,
|
|
6884
7664
|
includeVerification,
|
|
6885
7665
|
allowedWorkstreamIds,
|
|
@@ -7120,6 +7900,40 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
7120
7900
|
}
|
|
7121
7901
|
return true;
|
|
7122
7902
|
}
|
|
7903
|
+
// Work artifacts by entity: GET /orgx/api/work-artifacts/by-entity?entity_type=...&entity_id=...&limit=...&status=...
|
|
7904
|
+
if (isArtifactsByEntityRoute && method === "GET") {
|
|
7905
|
+
try {
|
|
7906
|
+
const qs = rawUrl.includes("?") ? rawUrl.split("?")[1] : "";
|
|
7907
|
+
const path = `/api/work-artifacts/by-entity${qs ? `?${qs}` : ""}`;
|
|
7908
|
+
const data = await client.rawRequest("GET", path);
|
|
7909
|
+
sendJson(res, 200, data);
|
|
7910
|
+
}
|
|
7911
|
+
catch (err) {
|
|
7912
|
+
sendJson(res, 502, { error: safeErrorMessage(err) });
|
|
7913
|
+
}
|
|
7914
|
+
return true;
|
|
7915
|
+
}
|
|
7916
|
+
// Artifact detail: GET /orgx/api/artifacts/:artifactId
|
|
7917
|
+
if (artifactDetailMatch && method === "GET") {
|
|
7918
|
+
try {
|
|
7919
|
+
const artifactId = decodeURIComponent(artifactDetailMatch[1]);
|
|
7920
|
+
const path = `/api/artifacts/${encodeURIComponent(artifactId)}`;
|
|
7921
|
+
const data = await client.rawRequest("GET", path);
|
|
7922
|
+
sendJson(res, 200, data);
|
|
7923
|
+
}
|
|
7924
|
+
catch (err) {
|
|
7925
|
+
const warning = safeErrorMessage(err);
|
|
7926
|
+
const artifactId = decodeURIComponent(artifactDetailMatch[1]);
|
|
7927
|
+
const fallback = buildLocalArtifactDetailFallback(artifactId, warning);
|
|
7928
|
+
if (fallback) {
|
|
7929
|
+
sendJson(res, 200, fallback);
|
|
7930
|
+
}
|
|
7931
|
+
else {
|
|
7932
|
+
sendJson(res, 502, { error: warning });
|
|
7933
|
+
}
|
|
7934
|
+
}
|
|
7935
|
+
return true;
|
|
7936
|
+
}
|
|
7123
7937
|
// Entity comments route: GET/POST /orgx/api/entities/{type}/{id}/comments
|
|
7124
7938
|
if (entityCommentsMatch && (method === "GET" || method === "POST")) {
|
|
7125
7939
|
try {
|
|
@@ -7339,7 +8153,15 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
7339
8153
|
switch (route) {
|
|
7340
8154
|
case "agent-suite/status": {
|
|
7341
8155
|
try {
|
|
7342
|
-
|
|
8156
|
+
// Resolve skill pack overrides — tolerate failures so the plan
|
|
8157
|
+
// is still returned even when the remote API is unreachable.
|
|
8158
|
+
let skillPack = null;
|
|
8159
|
+
try {
|
|
8160
|
+
skillPack = await resolveSkillPackOverrides({ client });
|
|
8161
|
+
}
|
|
8162
|
+
catch {
|
|
8163
|
+
// Fall through with null — plan will use builtin defaults.
|
|
8164
|
+
}
|
|
7343
8165
|
const state = readSkillPackState();
|
|
7344
8166
|
const updateAvailable = Boolean(state.remote?.checksum &&
|
|
7345
8167
|
state.pack?.checksum &&
|
|
@@ -8050,6 +8872,7 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
8050
8872
|
};
|
|
8051
8873
|
const instance = upsertRuntimeInstanceFromHook(payload);
|
|
8052
8874
|
broadcastRuntimeSse("runtime.updated", instance);
|
|
8875
|
+
clearSnapshotResponseCache();
|
|
8053
8876
|
const fallbackPhaseByEvent = {
|
|
8054
8877
|
session_start: "intent",
|
|
8055
8878
|
heartbeat: "execution",
|
|
@@ -8547,6 +9370,12 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
8547
9370
|
const decisionStatus = searchParams.get("status") ?? "pending";
|
|
8548
9371
|
const includeIdleRaw = searchParams.get("include_idle");
|
|
8549
9372
|
const includeIdle = includeIdleRaw === null ? undefined : includeIdleRaw !== "false";
|
|
9373
|
+
const snapshotCacheKey = `${route}?${searchParams.toString()}`;
|
|
9374
|
+
const cachedSnapshot = readSnapshotResponseCache(snapshotCacheKey);
|
|
9375
|
+
if (cachedSnapshot) {
|
|
9376
|
+
sendJson(res, 200, cachedSnapshot);
|
|
9377
|
+
return true;
|
|
9378
|
+
}
|
|
8550
9379
|
const degraded = [];
|
|
8551
9380
|
const contextStore = readAgentContexts();
|
|
8552
9381
|
const agentContexts = contextStore.agents;
|
|
@@ -8808,13 +9637,27 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
8808
9637
|
sessions = injectRuntimeInstancesAsSessions(sessions, runtimeInstances);
|
|
8809
9638
|
sessions = enrichSessionsWithRuntime(sessions, runtimeInstances);
|
|
8810
9639
|
activity = enrichActivityWithRuntime(activity, runtimeInstances);
|
|
9640
|
+
activity = applyAgentContextsToActivity(activity, {
|
|
9641
|
+
agents: agentContexts,
|
|
9642
|
+
runs: runContexts,
|
|
9643
|
+
});
|
|
8811
9644
|
try {
|
|
8812
|
-
|
|
9645
|
+
// Avoid reprocessing/storing large activity snapshots when the leading window
|
|
9646
|
+
// is unchanged and we persisted recently.
|
|
9647
|
+
const fingerprint = snapshotActivityFingerprint(activity);
|
|
9648
|
+
const now = Date.now();
|
|
9649
|
+
const shouldPersist = fingerprint !== lastSnapshotActivityFingerprint ||
|
|
9650
|
+
now - lastSnapshotActivityPersistAt >= SNAPSHOT_ACTIVITY_PERSIST_MIN_INTERVAL_MS;
|
|
9651
|
+
if (shouldPersist) {
|
|
9652
|
+
appendActivityItems(activity);
|
|
9653
|
+
lastSnapshotActivityFingerprint = fingerprint;
|
|
9654
|
+
lastSnapshotActivityPersistAt = now;
|
|
9655
|
+
}
|
|
8813
9656
|
}
|
|
8814
9657
|
catch {
|
|
8815
9658
|
// best effort
|
|
8816
9659
|
}
|
|
8817
|
-
|
|
9660
|
+
const payload = {
|
|
8818
9661
|
sessions,
|
|
8819
9662
|
activity,
|
|
8820
9663
|
handoffs,
|
|
@@ -8824,7 +9667,9 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
8824
9667
|
outbox: outboxStatus,
|
|
8825
9668
|
generatedAt: new Date().toISOString(),
|
|
8826
9669
|
degraded: degraded.length > 0 ? degraded : undefined,
|
|
8827
|
-
}
|
|
9670
|
+
};
|
|
9671
|
+
writeSnapshotResponseCache(snapshotCacheKey, payload);
|
|
9672
|
+
sendJson(res, 200, payload);
|
|
8828
9673
|
return true;
|
|
8829
9674
|
}
|
|
8830
9675
|
// Legacy endpoints retained for backwards compatibility.
|
|
@@ -8897,6 +9742,16 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
8897
9742
|
until,
|
|
8898
9743
|
cursor,
|
|
8899
9744
|
});
|
|
9745
|
+
{
|
|
9746
|
+
const ctx = readAgentContexts();
|
|
9747
|
+
page = {
|
|
9748
|
+
...page,
|
|
9749
|
+
activities: applyAgentContextsToActivity(page.activities, {
|
|
9750
|
+
agents: ctx.agents,
|
|
9751
|
+
runs: ctx.runs ?? {},
|
|
9752
|
+
}),
|
|
9753
|
+
};
|
|
9754
|
+
}
|
|
8900
9755
|
// If the local store is empty or we haven't warmed enough history yet, opportunistically
|
|
8901
9756
|
// warm from the remote API (which only supports since+limit) and re-page.
|
|
8902
9757
|
const warmKey = `${run ?? ""}::${since ?? ""}::${until ?? ""}`;
|
|
@@ -8929,6 +9784,16 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
8929
9784
|
until,
|
|
8930
9785
|
cursor,
|
|
8931
9786
|
});
|
|
9787
|
+
{
|
|
9788
|
+
const ctx = readAgentContexts();
|
|
9789
|
+
page = {
|
|
9790
|
+
...page,
|
|
9791
|
+
activities: applyAgentContextsToActivity(page.activities, {
|
|
9792
|
+
agents: ctx.agents,
|
|
9793
|
+
runs: ctx.runs ?? {},
|
|
9794
|
+
}),
|
|
9795
|
+
};
|
|
9796
|
+
}
|
|
8932
9797
|
}
|
|
8933
9798
|
catch {
|
|
8934
9799
|
// best effort
|
|
@@ -9098,6 +9963,75 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
9098
9963
|
}
|
|
9099
9964
|
return true;
|
|
9100
9965
|
}
|
|
9966
|
+
case "live/filesystem/open": {
|
|
9967
|
+
if (method !== "GET") {
|
|
9968
|
+
sendJson(res, 405, { error: "Use GET /orgx/api/live/filesystem/open?path=..." });
|
|
9969
|
+
return true;
|
|
9970
|
+
}
|
|
9971
|
+
const rawPath = searchParams.get("path") ?? "";
|
|
9972
|
+
if (!rawPath.trim()) {
|
|
9973
|
+
sendJson(res, 400, { error: "path is required" });
|
|
9974
|
+
return true;
|
|
9975
|
+
}
|
|
9976
|
+
const pathInput = rawPath.trim();
|
|
9977
|
+
if (/^https?:\/\//i.test(pathInput)) {
|
|
9978
|
+
res.writeHead(302, {
|
|
9979
|
+
Location: pathInput,
|
|
9980
|
+
...SECURITY_HEADERS,
|
|
9981
|
+
...CORS_HEADERS,
|
|
9982
|
+
});
|
|
9983
|
+
res.end();
|
|
9984
|
+
return true;
|
|
9985
|
+
}
|
|
9986
|
+
const resolvedPath = resolveFilesystemOpenPath(pathInput);
|
|
9987
|
+
const escapedInput = escapeHtml(pathInput);
|
|
9988
|
+
const escapedResolved = escapeHtml(resolvedPath);
|
|
9989
|
+
const shellPath = resolvedPath.replaceAll("'", "'\\''");
|
|
9990
|
+
if (!existsSync(resolvedPath)) {
|
|
9991
|
+
sendHtml(res, 404, `<!doctype html><html><head><meta charset="utf-8"/><title>Path Not Found</title><style>body{margin:0;padding:24px;background:#080808;color:#e5e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}pre{background:#0f0f0f;border:1px solid rgba(255,255,255,.08);padding:12px;border-radius:10px;white-space:pre-wrap;word-break:break-word}a{color:#BFFF00}</style></head><body><h1 style="margin:0 0 8px;font-size:18px;">Path not found</h1><p style="margin:0 0 12px;color:#9ca3af;">The evidence path no longer exists.</p><pre>${escapedInput}</pre><p style="margin:12px 0 0;color:#9ca3af;">Resolved as:</p><pre>${escapedResolved}</pre></body></html>`);
|
|
9992
|
+
return true;
|
|
9993
|
+
}
|
|
9994
|
+
try {
|
|
9995
|
+
const stats = statSync(resolvedPath);
|
|
9996
|
+
if (stats.isDirectory()) {
|
|
9997
|
+
const entries = readdirSync(resolvedPath);
|
|
9998
|
+
const visibleEntries = entries.slice(0, FILE_PREVIEW_MAX_DIR_ENTRIES);
|
|
9999
|
+
const items = visibleEntries
|
|
10000
|
+
.map((name) => {
|
|
10001
|
+
const nextPath = resolve(resolvedPath, name);
|
|
10002
|
+
const href = `/orgx/api/live/filesystem/open?path=${encodeURIComponent(nextPath)}`;
|
|
10003
|
+
return `<li style="margin:0 0 6px;"><a href="${href}" target="_blank" rel="noreferrer" style="color:#BFFF00;text-decoration:none;">${escapeHtml(name)}</a></li>`;
|
|
10004
|
+
})
|
|
10005
|
+
.join("");
|
|
10006
|
+
const overflowNote = entries.length > visibleEntries.length
|
|
10007
|
+
? `<p style="margin:12px 0 0;color:#9ca3af;">Showing ${visibleEntries.length} of ${entries.length} entries.</p>`
|
|
10008
|
+
: "";
|
|
10009
|
+
sendHtml(res, 200, `<!doctype html><html><head><meta charset="utf-8"/><title>Directory Preview</title><style>body{margin:0;padding:24px;background:#080808;color:#e5e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}pre,ul{background:#0f0f0f;border:1px solid rgba(255,255,255,.08);padding:12px;border-radius:10px}ul{list-style:none;margin:0;max-height:70vh;overflow:auto}pre{white-space:pre-wrap;word-break:break-word}code{color:#7dd3c0}</style></head><body><h1 style="margin:0 0 8px;font-size:18px;">Directory</h1><p style="margin:0 0 12px;color:#9ca3af;">${escapedResolved}</p><ul>${items || "<li style=\"color:#9ca3af;\">(empty)</li>"}</ul>${overflowNote}<p style="margin:12px 0 0;color:#9ca3af;">Tip: open in terminal with <code>ls -la '${escapeHtml(shellPath)}'</code></p></body></html>`);
|
|
10010
|
+
return true;
|
|
10011
|
+
}
|
|
10012
|
+
if (!stats.isFile()) {
|
|
10013
|
+
sendHtml(res, 200, `<!doctype html><html><head><meta charset="utf-8"/><title>Unsupported Path</title><style>body{margin:0;padding:24px;background:#080808;color:#e5e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}pre{background:#0f0f0f;border:1px solid rgba(255,255,255,.08);padding:12px;border-radius:10px;white-space:pre-wrap;word-break:break-word}</style></head><body><h1 style="margin:0 0 8px;font-size:18px;">Unsupported path type</h1><p style="margin:0 0 12px;color:#9ca3af;">Only files and directories are previewable.</p><pre>${escapedResolved}</pre></body></html>`);
|
|
10014
|
+
return true;
|
|
10015
|
+
}
|
|
10016
|
+
const totalBytes = Number.isFinite(stats.size) ? Math.max(0, stats.size) : 0;
|
|
10017
|
+
const { previewBuffer, truncated } = readFilePreview(resolvedPath, totalBytes);
|
|
10018
|
+
const isBinary = previewBuffer.includes(0);
|
|
10019
|
+
const sizeLabel = `${totalBytes.toLocaleString()} bytes`;
|
|
10020
|
+
if (isBinary) {
|
|
10021
|
+
sendHtml(res, 200, `<!doctype html><html><head><meta charset="utf-8"/><title>Binary File</title><style>body{margin:0;padding:24px;background:#080808;color:#e5e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}pre{background:#0f0f0f;border:1px solid rgba(255,255,255,.08);padding:12px;border-radius:10px;white-space:pre-wrap;word-break:break-word}code{color:#7dd3c0}</style></head><body><h1 style="margin:0 0 8px;font-size:18px;">Binary file</h1><p style="margin:0 0 12px;color:#9ca3af;">Cannot render binary content in browser preview.</p><pre>${escapedResolved}\n${escapeHtml(sizeLabel)}</pre><p style="margin:12px 0 0;color:#9ca3af;">Inspect in terminal with <code>file '${escapeHtml(shellPath)}'</code></p></body></html>`);
|
|
10022
|
+
return true;
|
|
10023
|
+
}
|
|
10024
|
+
const previewText = previewBuffer.toString("utf8");
|
|
10025
|
+
const truncationNote = truncated
|
|
10026
|
+
? `<p style="margin:12px 0 0;color:#9ca3af;">Preview truncated to first ${FILE_PREVIEW_MAX_BYTES.toLocaleString()} bytes.</p>`
|
|
10027
|
+
: "";
|
|
10028
|
+
sendHtml(res, 200, `<!doctype html><html><head><meta charset="utf-8"/><title>File Preview</title><style>body{margin:0;padding:24px;background:#080808;color:#e5e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}pre{background:#0f0f0f;border:1px solid rgba(255,255,255,.08);padding:12px;border-radius:10px;white-space:pre;overflow:auto;max-height:75vh}code{color:#7dd3c0}</style></head><body><h1 style="margin:0 0 8px;font-size:18px;">File preview</h1><p style="margin:0 0 12px;color:#9ca3af;">${escapedResolved}</p><p style="margin:0 0 12px;color:#9ca3af;">${escapeHtml(sizeLabel)}</p><pre>${escapeHtml(previewText)}</pre>${truncationNote}<p style="margin:12px 0 0;color:#9ca3af;">Open in terminal with <code>cat '${escapeHtml(shellPath)}'</code></p></body></html>`);
|
|
10029
|
+
}
|
|
10030
|
+
catch (err) {
|
|
10031
|
+
sendHtml(res, 500, `<!doctype html><html><head><meta charset="utf-8"/><title>Preview Error</title><style>body{margin:0;padding:24px;background:#080808;color:#e5e7eb;font:14px/1.5 ui-sans-serif,system-ui,-apple-system,Segoe UI,sans-serif}pre{background:#0f0f0f;border:1px solid rgba(255,255,255,.08);padding:12px;border-radius:10px;white-space:pre-wrap;word-break:break-word}</style></head><body><h1 style="margin:0 0 8px;font-size:18px;">Unable to preview path</h1><p style="margin:0 0 12px;color:#9ca3af;">${escapeHtml(safeErrorMessage(err))}</p><pre>${escapedResolved}</pre></body></html>`);
|
|
10032
|
+
}
|
|
10033
|
+
return true;
|
|
10034
|
+
}
|
|
9101
10035
|
case "live/activity/headline": {
|
|
9102
10036
|
if (method !== "POST") {
|
|
9103
10037
|
sendJson(res, 405, { error: "Use POST /orgx/api/live/activity/headline" });
|
|
@@ -9628,7 +10562,14 @@ export function createHttpHandler(config, client, getSnapshot, onboarding, diagn
|
|
|
9628
10562
|
assetPath.startsWith(`${RESOLVED_DIST_ASSETS_DIR}${sep}`);
|
|
9629
10563
|
}
|
|
9630
10564
|
if (assetPath && isWithinAssetsDir && existsSync(assetPath)) {
|
|
9631
|
-
|
|
10565
|
+
const assetExt = extname(assetPath).toLowerCase();
|
|
10566
|
+
// JS/CSS chunks can be invalidated by dashboard rebuilds while browsers retain
|
|
10567
|
+
// immutable cached entry chunks in local plugin environments.
|
|
10568
|
+
// Revalidate executable assets to avoid stale chunk graph 404s.
|
|
10569
|
+
const cacheControl = assetExt === ".js" || assetExt === ".css"
|
|
10570
|
+
? "no-cache"
|
|
10571
|
+
: "public, max-age=31536000, immutable";
|
|
10572
|
+
sendFile(res, assetPath, cacheControl);
|
|
9632
10573
|
}
|
|
9633
10574
|
else {
|
|
9634
10575
|
send404(res);
|