@useorgx/openclaw-plugin 0.4.5 → 0.4.6
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 +24 -3
- package/dashboard/dist/assets/0tOC3wSN.js +214 -0
- package/dashboard/dist/assets/B3ziCA02.js +8 -0
- package/dashboard/dist/assets/Bm8QnMJ_.js +1 -0
- package/dashboard/dist/assets/CpJsfbXo.js +9 -0
- package/dashboard/dist/assets/CyxZio4Y.js +1 -0
- package/dashboard/dist/assets/DaAIOik3.css +1 -0
- package/dashboard/dist/assets/sAhvFnpk.js +4 -0
- package/dashboard/dist/index.html +5 -5
- package/dist/activity-store.d.ts +28 -0
- package/dist/activity-store.js +250 -0
- package/dist/agent-context-store.d.ts +19 -0
- package/dist/agent-context-store.js +60 -3
- package/dist/agent-suite.d.ts +83 -0
- package/dist/agent-suite.js +615 -0
- package/dist/contracts/client.d.ts +22 -1
- package/dist/contracts/client.js +120 -3
- package/dist/contracts/types.d.ts +190 -1
- package/dist/entity-comment-store.d.ts +29 -0
- package/dist/entity-comment-store.js +190 -0
- package/dist/hooks/post-reporting-event.mjs +326 -0
- package/dist/http-handler.d.ts +7 -1
- package/dist/http-handler.js +3603 -578
- package/dist/index.js +936 -62
- package/dist/mcp-client-setup.js +156 -24
- package/dist/mcp-http-handler.d.ts +17 -0
- package/dist/mcp-http-handler.js +144 -3
- package/dist/next-up-queue-store.d.ts +31 -0
- package/dist/next-up-queue-store.js +169 -0
- package/dist/openclaw.plugin.json +1 -1
- package/dist/outbox.d.ts +1 -1
- package/dist/runtime-instance-store.d.ts +1 -1
- package/dist/runtime-instance-store.js +20 -3
- package/dist/skill-pack-state.d.ts +69 -0
- package/dist/skill-pack-state.js +232 -0
- package/dist/worker-supervisor.d.ts +25 -0
- package/dist/worker-supervisor.js +62 -0
- package/openclaw.plugin.json +1 -1
- package/package.json +10 -1
- package/skills/orgx-design-agent/SKILL.md +38 -0
- package/skills/orgx-engineering-agent/SKILL.md +55 -0
- package/skills/orgx-marketing-agent/SKILL.md +40 -0
- package/skills/orgx-operations-agent/SKILL.md +40 -0
- package/skills/orgx-orchestrator-agent/SKILL.md +45 -0
- package/skills/orgx-product-agent/SKILL.md +39 -0
- package/skills/orgx-sales-agent/SKILL.md +40 -0
- package/skills/ship/SKILL.md +63 -0
- package/dashboard/dist/assets/B68j2crt.js +0 -1
- package/dashboard/dist/assets/BZZ-fiJx.js +0 -32
- package/dashboard/dist/assets/BoXlCHKa.js +0 -9
- package/dashboard/dist/assets/Bq9x_Xyh.css +0 -1
- package/dashboard/dist/assets/DBhrRVdp.js +0 -1
- package/dashboard/dist/assets/DD1jv1Hd.js +0 -8
- package/dashboard/dist/assets/DNjbmawF.js +0 -214
package/dist/index.js
CHANGED
|
@@ -12,20 +12,25 @@
|
|
|
12
12
|
*/
|
|
13
13
|
import { OrgXClient } from "./api.js";
|
|
14
14
|
import { createHttpHandler } from "./http-handler.js";
|
|
15
|
-
import {
|
|
15
|
+
import { applyOrgxAgentSuitePlan, computeOrgxAgentSuitePlan } from "./agent-suite.js";
|
|
16
|
+
import { appendActivityItems } from "./activity-store.js";
|
|
17
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
16
18
|
import { join } from "node:path";
|
|
17
19
|
import { homedir } from "node:os";
|
|
18
20
|
import { fileURLToPath } from "node:url";
|
|
19
|
-
import { randomUUID } from "node:crypto";
|
|
21
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
20
22
|
import { clearPersistedApiKey, loadAuthStore, resolveInstallationId, saveAuthStore, } from "./auth-store.js";
|
|
21
23
|
import { clearPersistedSnapshot, readPersistedSnapshot, writePersistedSnapshot, } from "./snapshot-store.js";
|
|
22
24
|
import { appendToOutbox, readOutbox, readOutboxSummary, replaceOutbox, } from "./outbox.js";
|
|
25
|
+
import { getAgentContext, readAgentContexts } from "./agent-context-store.js";
|
|
26
|
+
import { readAgentRuns, markAgentRunStopped } from "./agent-run-store.js";
|
|
23
27
|
import { extractProgressOutboxMessage } from "./reporting/outbox-replay.js";
|
|
24
28
|
import { ensureGatewayWatchdog } from "./gateway-watchdog.js";
|
|
25
|
-
import { createMcpHttpHandler } from "./mcp-http-handler.js";
|
|
29
|
+
import { createMcpHttpHandler, } from "./mcp-http-handler.js";
|
|
26
30
|
import { autoConfigureDetectedMcpClients } from "./mcp-client-setup.js";
|
|
27
31
|
import { readOpenClawGatewayPort, readOpenClawSettingsSnapshot } from "./openclaw-settings.js";
|
|
28
32
|
import { posthogCapture } from "./telemetry/posthog.js";
|
|
33
|
+
import { readSkillPackState, refreshSkillPackState } from "./skill-pack-state.js";
|
|
29
34
|
export { OrgXClient } from "./api.js";
|
|
30
35
|
const DEFAULT_BASE_URL = "https://www.useorgx.com";
|
|
31
36
|
const DEFAULT_DOCS_URL = "https://orgx.mintlify.site/guides/openclaw-plugin-setup";
|
|
@@ -186,6 +191,7 @@ function resolveConfig(api, input) {
|
|
|
186
191
|
baseUrl,
|
|
187
192
|
syncIntervalMs: pluginConf.syncIntervalMs ?? 300_000,
|
|
188
193
|
enabled: pluginConf.enabled ?? true,
|
|
194
|
+
autoInstallAgentSuiteOnConnect: pluginConf.autoInstallAgentSuiteOnConnect ?? true,
|
|
189
195
|
dashboardEnabled: pluginConf.dashboardEnabled ?? true,
|
|
190
196
|
installationId: input.installationId,
|
|
191
197
|
pluginVersion: resolvePluginVersion(),
|
|
@@ -268,6 +274,29 @@ function isUuid(value) {
|
|
|
268
274
|
return false;
|
|
269
275
|
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
270
276
|
}
|
|
277
|
+
function inferReportingInitiativeId(input) {
|
|
278
|
+
const env = pickNonEmptyString(process.env.ORGX_INITIATIVE_ID);
|
|
279
|
+
if (isUuid(env))
|
|
280
|
+
return env;
|
|
281
|
+
const agentId = pickNonEmptyString(input.agent_id, input.agentId);
|
|
282
|
+
if (agentId) {
|
|
283
|
+
const ctx = getAgentContext(agentId);
|
|
284
|
+
const ctxInit = ctx?.initiativeId ?? undefined;
|
|
285
|
+
if (isUuid(ctxInit ?? undefined))
|
|
286
|
+
return ctxInit ?? undefined;
|
|
287
|
+
}
|
|
288
|
+
// Fall back to the most recently updated agent context with a UUID initiative id.
|
|
289
|
+
try {
|
|
290
|
+
const store = readAgentContexts();
|
|
291
|
+
const candidates = Object.values(store.agents ?? {}).filter((ctx) => isUuid(ctx?.initiativeId ?? undefined));
|
|
292
|
+
candidates.sort((a, b) => Date.parse(b.updatedAt) - Date.parse(a.updatedAt));
|
|
293
|
+
const picked = candidates[0]?.initiativeId ?? undefined;
|
|
294
|
+
return isUuid(picked) ? picked : undefined;
|
|
295
|
+
}
|
|
296
|
+
catch {
|
|
297
|
+
return undefined;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
271
300
|
function toReportingPhase(phase, progressPct) {
|
|
272
301
|
if (progressPct === 100)
|
|
273
302
|
return "completed";
|
|
@@ -363,11 +392,66 @@ export default function register(api) {
|
|
|
363
392
|
pollIntervalMs: null,
|
|
364
393
|
};
|
|
365
394
|
let activePairing = null;
|
|
366
|
-
|
|
395
|
+
// NOTE: base URL can be updated at runtime (e.g. user edits OpenClaw config). Keep it mutable.
|
|
396
|
+
let baseApiUrl = config.baseUrl.replace(/\/+$/, "");
|
|
367
397
|
const defaultReportingCorrelationId = pickNonEmptyString(process.env.ORGX_CORRELATION_ID) ??
|
|
368
398
|
`openclaw-${config.installationId}`;
|
|
399
|
+
function refreshConfigFromSources(input) {
|
|
400
|
+
const allowApiKeyChanges = input?.allowApiKeyChanges !== false;
|
|
401
|
+
const previousApiKey = config.apiKey;
|
|
402
|
+
const previousBaseUrl = config.baseUrl;
|
|
403
|
+
const previousUserId = config.userId;
|
|
404
|
+
const previousDocsUrl = config.docsUrl;
|
|
405
|
+
const previousKeySource = config.apiKeySource;
|
|
406
|
+
const latestPersisted = loadAuthStore();
|
|
407
|
+
const next = resolveConfig(api, {
|
|
408
|
+
installationId: config.installationId,
|
|
409
|
+
persistedApiKey: latestPersisted?.apiKey ?? null,
|
|
410
|
+
persistedUserId: latestPersisted?.userId ?? null,
|
|
411
|
+
});
|
|
412
|
+
const nextApiKey = allowApiKeyChanges ? next.apiKey : previousApiKey;
|
|
413
|
+
const nextUserId = allowApiKeyChanges ? next.userId : previousUserId;
|
|
414
|
+
const changed = nextApiKey !== previousApiKey ||
|
|
415
|
+
next.baseUrl !== previousBaseUrl ||
|
|
416
|
+
nextUserId !== previousUserId ||
|
|
417
|
+
next.docsUrl !== previousDocsUrl ||
|
|
418
|
+
next.apiKeySource !== previousKeySource;
|
|
419
|
+
if (!changed) {
|
|
420
|
+
return false;
|
|
421
|
+
}
|
|
422
|
+
if (allowApiKeyChanges) {
|
|
423
|
+
config.apiKey = nextApiKey;
|
|
424
|
+
config.userId = nextUserId;
|
|
425
|
+
config.apiKeySource = next.apiKeySource;
|
|
426
|
+
}
|
|
427
|
+
config.baseUrl = next.baseUrl;
|
|
428
|
+
config.docsUrl = next.docsUrl;
|
|
429
|
+
baseApiUrl = config.baseUrl.replace(/\/+$/, "");
|
|
430
|
+
client.setCredentials({
|
|
431
|
+
apiKey: config.apiKey,
|
|
432
|
+
userId: config.userId,
|
|
433
|
+
baseUrl: config.baseUrl,
|
|
434
|
+
});
|
|
435
|
+
// Keep onboarding state aligned with what's actually configured (without forcing a status transition).
|
|
436
|
+
updateOnboardingState({
|
|
437
|
+
hasApiKey: Boolean(config.apiKey),
|
|
438
|
+
keySource: config.apiKeySource,
|
|
439
|
+
docsUrl: config.docsUrl,
|
|
440
|
+
installationId: config.installationId,
|
|
441
|
+
});
|
|
442
|
+
api.log?.info?.("[orgx] Config refreshed", {
|
|
443
|
+
reason: input?.reason ?? "runtime_refresh",
|
|
444
|
+
baseUrl: config.baseUrl,
|
|
445
|
+
hasApiKey: Boolean(config.apiKey),
|
|
446
|
+
apiKeySource: config.apiKeySource,
|
|
447
|
+
});
|
|
448
|
+
return true;
|
|
449
|
+
}
|
|
369
450
|
function resolveReportingContext(input) {
|
|
370
|
-
|
|
451
|
+
let initiativeId = pickNonEmptyString(input.initiative_id, input.initiativeId, process.env.ORGX_INITIATIVE_ID);
|
|
452
|
+
if (!isUuid(initiativeId)) {
|
|
453
|
+
initiativeId = inferReportingInitiativeId(input);
|
|
454
|
+
}
|
|
371
455
|
if (!initiativeId || !isUuid(initiativeId)) {
|
|
372
456
|
return {
|
|
373
457
|
ok: false,
|
|
@@ -411,6 +495,16 @@ export default function register(api) {
|
|
|
411
495
|
return err.message;
|
|
412
496
|
return typeof err === "string" ? err : "Unexpected error";
|
|
413
497
|
}
|
|
498
|
+
function stableHash(value) {
|
|
499
|
+
return createHash("sha256").update(value).digest("hex");
|
|
500
|
+
}
|
|
501
|
+
function isAuthFailure(err) {
|
|
502
|
+
const message = toErrorMessage(err).toLowerCase();
|
|
503
|
+
return (message.includes("401") ||
|
|
504
|
+
message.includes("unauthorized") ||
|
|
505
|
+
message.includes("invalid_token") ||
|
|
506
|
+
message.includes("invalid api key"));
|
|
507
|
+
}
|
|
414
508
|
const registerTool = api.registerTool.bind(api);
|
|
415
509
|
api.registerTool = (tool, options) => {
|
|
416
510
|
const toolName = tool.name;
|
|
@@ -562,22 +656,47 @@ export default function register(api) {
|
|
|
562
656
|
}
|
|
563
657
|
function buildManualKeyConnectUrl() {
|
|
564
658
|
try {
|
|
565
|
-
|
|
659
|
+
// Deep-link into the Security section where API keys live.
|
|
660
|
+
return new URL("/settings#security", baseApiUrl).toString();
|
|
566
661
|
}
|
|
567
662
|
catch {
|
|
568
|
-
return "https://www.useorgx.com/settings";
|
|
663
|
+
return "https://www.useorgx.com/settings#security";
|
|
569
664
|
}
|
|
570
665
|
}
|
|
571
|
-
async function fetchOrgxJson(method, path, body) {
|
|
666
|
+
async function fetchOrgxJson(method, path, body, options) {
|
|
572
667
|
try {
|
|
573
|
-
const
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
668
|
+
const controller = new AbortController();
|
|
669
|
+
const timeoutMs = typeof options?.timeoutMs === "number" && Number.isFinite(options.timeoutMs)
|
|
670
|
+
? Math.max(1_000, Math.floor(options.timeoutMs))
|
|
671
|
+
: 12_000;
|
|
672
|
+
const timeout = setTimeout(() => controller.abort(), timeoutMs);
|
|
673
|
+
let response;
|
|
674
|
+
let rawText = "";
|
|
675
|
+
try {
|
|
676
|
+
response = await fetch(`${baseApiUrl}${path}`, {
|
|
677
|
+
method,
|
|
678
|
+
signal: controller.signal,
|
|
679
|
+
headers: {
|
|
680
|
+
Accept: "application/json",
|
|
681
|
+
"Content-Type": "application/json",
|
|
682
|
+
},
|
|
683
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
684
|
+
});
|
|
685
|
+
rawText = await response.text().catch(() => "");
|
|
686
|
+
}
|
|
687
|
+
finally {
|
|
688
|
+
clearTimeout(timeout);
|
|
689
|
+
}
|
|
690
|
+
const payload = (() => {
|
|
691
|
+
if (!rawText)
|
|
692
|
+
return null;
|
|
693
|
+
try {
|
|
694
|
+
return JSON.parse(rawText);
|
|
695
|
+
}
|
|
696
|
+
catch {
|
|
697
|
+
return null;
|
|
698
|
+
}
|
|
699
|
+
})();
|
|
581
700
|
if (!response.ok) {
|
|
582
701
|
const rawError = payload?.error ?? payload?.message;
|
|
583
702
|
let errorMessage;
|
|
@@ -590,18 +709,62 @@ export default function register(api) {
|
|
|
590
709
|
typeof rawError.message === "string") {
|
|
591
710
|
errorMessage = rawError.message;
|
|
592
711
|
}
|
|
712
|
+
else if (rawText && rawText.trim().length > 0) {
|
|
713
|
+
// Avoid dumping HTML (Cloudflare / Next.js error pages) into UI; keep it short.
|
|
714
|
+
const sanitized = rawText
|
|
715
|
+
.replace(/\s+/g, " ")
|
|
716
|
+
.replace(/<[^>]+>/g, "")
|
|
717
|
+
.trim();
|
|
718
|
+
errorMessage = sanitized.length > 0 ? sanitized.slice(0, 180) : `OrgX request failed (${response.status})`;
|
|
719
|
+
}
|
|
593
720
|
else {
|
|
594
721
|
errorMessage = `OrgX request failed (${response.status})`;
|
|
595
722
|
}
|
|
596
|
-
|
|
723
|
+
const statusToken = `HTTP ${response.status}`;
|
|
724
|
+
if (response.status &&
|
|
725
|
+
!errorMessage.toLowerCase().includes(statusToken.toLowerCase()) &&
|
|
726
|
+
!errorMessage.includes(`(${response.status})`)) {
|
|
727
|
+
errorMessage = `${errorMessage} (HTTP ${response.status})`;
|
|
728
|
+
}
|
|
729
|
+
const debugParts = [];
|
|
730
|
+
const requestId = response.headers.get("x-request-id");
|
|
731
|
+
const vercelId = response.headers.get("x-vercel-id");
|
|
732
|
+
const cfRay = response.headers.get("cf-ray");
|
|
733
|
+
const clerkStatus = response.headers.get("x-clerk-auth-status");
|
|
734
|
+
const clerkReason = response.headers.get("x-clerk-auth-reason");
|
|
735
|
+
if (requestId)
|
|
736
|
+
debugParts.push(`req=${requestId}`);
|
|
737
|
+
if (vercelId && vercelId !== requestId)
|
|
738
|
+
debugParts.push(`vercel=${vercelId}`);
|
|
739
|
+
if (cfRay)
|
|
740
|
+
debugParts.push(`cf-ray=${cfRay}`);
|
|
741
|
+
if (clerkStatus)
|
|
742
|
+
debugParts.push(`clerk=${clerkStatus}`);
|
|
743
|
+
if (clerkReason)
|
|
744
|
+
debugParts.push(`clerk_reason=${clerkReason}`);
|
|
745
|
+
const debugSuffix = debugParts.length > 0 ? ` (${debugParts.join(", ")})` : "";
|
|
746
|
+
return {
|
|
747
|
+
ok: false,
|
|
748
|
+
status: response.status,
|
|
749
|
+
error: `${errorMessage}${debugSuffix}`,
|
|
750
|
+
};
|
|
597
751
|
}
|
|
598
752
|
if (payload?.data !== undefined) {
|
|
599
753
|
return { ok: true, data: payload.data };
|
|
600
754
|
}
|
|
601
|
-
|
|
755
|
+
if (payload !== null) {
|
|
756
|
+
return { ok: true, data: payload };
|
|
757
|
+
}
|
|
758
|
+
return { ok: true, data: rawText };
|
|
602
759
|
}
|
|
603
760
|
catch (err) {
|
|
604
|
-
|
|
761
|
+
const message = err &&
|
|
762
|
+
typeof err === "object" &&
|
|
763
|
+
"name" in err &&
|
|
764
|
+
err.name === "AbortError"
|
|
765
|
+
? `OrgX request timed out (method=${method}, path=${path})`
|
|
766
|
+
: toErrorMessage(err);
|
|
767
|
+
return { ok: false, status: 0, error: message };
|
|
605
768
|
}
|
|
606
769
|
}
|
|
607
770
|
function setRuntimeApiKey(input) {
|
|
@@ -664,6 +827,7 @@ export default function register(api) {
|
|
|
664
827
|
const probeRemote = input.probeRemote === true;
|
|
665
828
|
const outbox = await readOutboxSummary();
|
|
666
829
|
const checks = [];
|
|
830
|
+
refreshConfigFromSources({ reason: "health_check" });
|
|
667
831
|
const hasApiKey = Boolean(config.apiKey);
|
|
668
832
|
if (hasApiKey) {
|
|
669
833
|
checks.push({
|
|
@@ -721,7 +885,9 @@ export default function register(api) {
|
|
|
721
885
|
else {
|
|
722
886
|
const startedAt = Date.now();
|
|
723
887
|
try {
|
|
724
|
-
|
|
888
|
+
// Avoid probing with /api/client/sync: it's heavier than necessary and can
|
|
889
|
+
// create false negatives during transient server slowness.
|
|
890
|
+
await client.getBillingStatus();
|
|
725
891
|
remoteReachable = true;
|
|
726
892
|
remoteLatencyMs = Date.now() - startedAt;
|
|
727
893
|
checks.push({
|
|
@@ -816,8 +982,305 @@ export default function register(api) {
|
|
|
816
982
|
.filter(Boolean);
|
|
817
983
|
return strings.length > 0 ? strings : undefined;
|
|
818
984
|
}
|
|
985
|
+
function isPidAlive(pid) {
|
|
986
|
+
if (!Number.isFinite(pid) || !pid || pid <= 0)
|
|
987
|
+
return false;
|
|
988
|
+
try {
|
|
989
|
+
process.kill(pid, 0);
|
|
990
|
+
return true;
|
|
991
|
+
}
|
|
992
|
+
catch {
|
|
993
|
+
return false;
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
function toFiniteNumber(value) {
|
|
997
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
998
|
+
return value;
|
|
999
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
1000
|
+
const parsed = Number(value);
|
|
1001
|
+
if (Number.isFinite(parsed))
|
|
1002
|
+
return parsed;
|
|
1003
|
+
}
|
|
1004
|
+
return null;
|
|
1005
|
+
}
|
|
1006
|
+
function isSafePathSegment(value) {
|
|
1007
|
+
const normalized = value.trim();
|
|
1008
|
+
if (!normalized || normalized === "." || normalized === "..")
|
|
1009
|
+
return false;
|
|
1010
|
+
if (normalized.includes("/") || normalized.includes("\\") || normalized.includes("\0")) {
|
|
1011
|
+
return false;
|
|
1012
|
+
}
|
|
1013
|
+
if (normalized.includes(".."))
|
|
1014
|
+
return false;
|
|
1015
|
+
return true;
|
|
1016
|
+
}
|
|
1017
|
+
function parseRetroEntityType(value) {
|
|
1018
|
+
if (!value)
|
|
1019
|
+
return undefined;
|
|
1020
|
+
switch (value) {
|
|
1021
|
+
case "initiative":
|
|
1022
|
+
case "workstream":
|
|
1023
|
+
case "milestone":
|
|
1024
|
+
case "task":
|
|
1025
|
+
return value;
|
|
1026
|
+
default:
|
|
1027
|
+
return undefined;
|
|
1028
|
+
}
|
|
1029
|
+
}
|
|
1030
|
+
function readOpenClawSessionSummary(input) {
|
|
1031
|
+
const agentId = input.agentId.trim();
|
|
1032
|
+
const sessionId = input.sessionId.trim();
|
|
1033
|
+
if (!agentId || !sessionId) {
|
|
1034
|
+
return { tokens: 0, costUsd: 0, hadError: false, errorMessage: null };
|
|
1035
|
+
}
|
|
1036
|
+
if (!isSafePathSegment(agentId) || !isSafePathSegment(sessionId)) {
|
|
1037
|
+
return { tokens: 0, costUsd: 0, hadError: false, errorMessage: null };
|
|
1038
|
+
}
|
|
1039
|
+
const jsonlPath = join(homedir(), ".openclaw", "agents", agentId, "sessions", `${sessionId}.jsonl`);
|
|
1040
|
+
try {
|
|
1041
|
+
if (!existsSync(jsonlPath)) {
|
|
1042
|
+
return { tokens: 0, costUsd: 0, hadError: false, errorMessage: null };
|
|
1043
|
+
}
|
|
1044
|
+
const raw = readFileSync(jsonlPath, "utf8");
|
|
1045
|
+
const lines = raw.split("\n");
|
|
1046
|
+
let tokens = 0;
|
|
1047
|
+
let costUsd = 0;
|
|
1048
|
+
let hadError = false;
|
|
1049
|
+
let errorMessage = null;
|
|
1050
|
+
for (const line of lines) {
|
|
1051
|
+
const trimmed = line.trim();
|
|
1052
|
+
if (!trimmed)
|
|
1053
|
+
continue;
|
|
1054
|
+
try {
|
|
1055
|
+
const evt = JSON.parse(trimmed);
|
|
1056
|
+
if (evt.type !== "message")
|
|
1057
|
+
continue;
|
|
1058
|
+
const msg = evt.message;
|
|
1059
|
+
if (!msg || typeof msg !== "object")
|
|
1060
|
+
continue;
|
|
1061
|
+
const usage = msg.usage;
|
|
1062
|
+
if (usage && typeof usage === "object") {
|
|
1063
|
+
const totalTokens = toFiniteNumber(usage.totalTokens) ??
|
|
1064
|
+
toFiniteNumber(usage.total_tokens) ??
|
|
1065
|
+
null;
|
|
1066
|
+
const inputTokens = toFiniteNumber(usage.input) ?? 0;
|
|
1067
|
+
const outputTokens = toFiniteNumber(usage.output) ?? 0;
|
|
1068
|
+
const cacheReadTokens = toFiniteNumber(usage.cacheRead) ?? 0;
|
|
1069
|
+
const cacheWriteTokens = toFiniteNumber(usage.cacheWrite) ?? 0;
|
|
1070
|
+
tokens += Math.max(0, Math.round(totalTokens ??
|
|
1071
|
+
inputTokens + outputTokens + cacheReadTokens + cacheWriteTokens));
|
|
1072
|
+
const cost = usage.cost;
|
|
1073
|
+
const costTotal = cost ? toFiniteNumber(cost.total) : null;
|
|
1074
|
+
if (costTotal !== null) {
|
|
1075
|
+
costUsd += Math.max(0, costTotal);
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
const stopReason = typeof msg.stopReason === "string" ? msg.stopReason : "";
|
|
1079
|
+
const msgError = typeof msg.errorMessage === "string" && msg.errorMessage.trim().length > 0
|
|
1080
|
+
? msg.errorMessage.trim()
|
|
1081
|
+
: null;
|
|
1082
|
+
if (stopReason === "error" || msgError) {
|
|
1083
|
+
hadError = true;
|
|
1084
|
+
errorMessage = msgError ?? errorMessage;
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
catch {
|
|
1088
|
+
// Ignore malformed lines.
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
return {
|
|
1092
|
+
tokens,
|
|
1093
|
+
costUsd: Math.round(costUsd * 10_000) / 10_000,
|
|
1094
|
+
hadError,
|
|
1095
|
+
errorMessage,
|
|
1096
|
+
};
|
|
1097
|
+
}
|
|
1098
|
+
catch {
|
|
1099
|
+
return { tokens: 0, costUsd: 0, hadError: false, errorMessage: null };
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
async function reconcileStoppedAgentRuns() {
|
|
1103
|
+
try {
|
|
1104
|
+
const store = readAgentRuns();
|
|
1105
|
+
const runs = Object.values(store.runs ?? {});
|
|
1106
|
+
for (const run of runs) {
|
|
1107
|
+
if (!run || typeof run !== "object")
|
|
1108
|
+
continue;
|
|
1109
|
+
if (run.status !== "running")
|
|
1110
|
+
continue;
|
|
1111
|
+
if (!run.pid || isPidAlive(run.pid))
|
|
1112
|
+
continue;
|
|
1113
|
+
const stopped = markAgentRunStopped(run.runId);
|
|
1114
|
+
if (!stopped)
|
|
1115
|
+
continue;
|
|
1116
|
+
const initiativeId = stopped.initiativeId?.trim() ?? "";
|
|
1117
|
+
if (!initiativeId)
|
|
1118
|
+
continue;
|
|
1119
|
+
const summary = readOpenClawSessionSummary({
|
|
1120
|
+
agentId: stopped.agentId,
|
|
1121
|
+
sessionId: stopped.runId,
|
|
1122
|
+
});
|
|
1123
|
+
const completedAt = stopped.stoppedAt ?? new Date().toISOString();
|
|
1124
|
+
const success = !summary.hadError;
|
|
1125
|
+
const correlationId = stopped.runId;
|
|
1126
|
+
const outcomePayload = {
|
|
1127
|
+
initiative_id: initiativeId,
|
|
1128
|
+
correlation_id: correlationId,
|
|
1129
|
+
source_client: "openclaw",
|
|
1130
|
+
execution_id: `openclaw:${stopped.runId}`,
|
|
1131
|
+
execution_type: "openclaw.session",
|
|
1132
|
+
agent_id: stopped.agentId,
|
|
1133
|
+
task_type: stopped.taskId ?? undefined,
|
|
1134
|
+
started_at: stopped.startedAt,
|
|
1135
|
+
completed_at: completedAt,
|
|
1136
|
+
inputs: {
|
|
1137
|
+
message: stopped.message,
|
|
1138
|
+
workstream_id: stopped.workstreamId,
|
|
1139
|
+
task_id: stopped.taskId,
|
|
1140
|
+
},
|
|
1141
|
+
outputs: {
|
|
1142
|
+
had_error: summary.hadError,
|
|
1143
|
+
error_message: summary.errorMessage,
|
|
1144
|
+
},
|
|
1145
|
+
steps: [],
|
|
1146
|
+
success,
|
|
1147
|
+
human_interventions: 0,
|
|
1148
|
+
errors: summary.errorMessage ? [summary.errorMessage] : [],
|
|
1149
|
+
metadata: {
|
|
1150
|
+
provider: stopped.provider,
|
|
1151
|
+
model: stopped.model,
|
|
1152
|
+
tokens: summary.tokens,
|
|
1153
|
+
cost_usd: summary.costUsd,
|
|
1154
|
+
source: "openclaw_agent_run_reconcile",
|
|
1155
|
+
},
|
|
1156
|
+
};
|
|
1157
|
+
const retroEntityType = stopped.taskId
|
|
1158
|
+
? "task"
|
|
1159
|
+
: "initiative";
|
|
1160
|
+
const retroEntityId = stopped.taskId ?? initiativeId;
|
|
1161
|
+
const retroSummary = stopped.taskId
|
|
1162
|
+
? `OpenClaw ${success ? "completed" : "blocked"} task ${stopped.taskId}.`
|
|
1163
|
+
: `OpenClaw run ${success ? "completed" : "blocked"} (session ${stopped.runId}).`;
|
|
1164
|
+
const retroPayload = {
|
|
1165
|
+
initiative_id: initiativeId,
|
|
1166
|
+
correlation_id: correlationId,
|
|
1167
|
+
source_client: "openclaw",
|
|
1168
|
+
entity_type: retroEntityType,
|
|
1169
|
+
entity_id: retroEntityId,
|
|
1170
|
+
title: stopped.taskId ?? stopped.runId,
|
|
1171
|
+
idempotency_key: `retro:${stopped.runId}`,
|
|
1172
|
+
retro: {
|
|
1173
|
+
summary: retroSummary,
|
|
1174
|
+
what_went_well: success ? ["Completed without runtime error."] : [],
|
|
1175
|
+
what_went_wrong: success
|
|
1176
|
+
? []
|
|
1177
|
+
: [summary.errorMessage ?? "Session ended with error."],
|
|
1178
|
+
decisions: [],
|
|
1179
|
+
follow_ups: success
|
|
1180
|
+
? []
|
|
1181
|
+
: [
|
|
1182
|
+
{
|
|
1183
|
+
title: "Investigate OpenClaw session failure and unblock task",
|
|
1184
|
+
priority: "p0",
|
|
1185
|
+
reason: summary.errorMessage ?? "Session ended with error.",
|
|
1186
|
+
},
|
|
1187
|
+
],
|
|
1188
|
+
signals: {
|
|
1189
|
+
tokens: summary.tokens,
|
|
1190
|
+
cost_usd: summary.costUsd,
|
|
1191
|
+
had_error: summary.hadError,
|
|
1192
|
+
error_message: summary.errorMessage,
|
|
1193
|
+
session_id: stopped.runId,
|
|
1194
|
+
task_id: stopped.taskId,
|
|
1195
|
+
workstream_id: stopped.workstreamId,
|
|
1196
|
+
provider: stopped.provider,
|
|
1197
|
+
model: stopped.model,
|
|
1198
|
+
source: "openclaw_agent_run_reconcile",
|
|
1199
|
+
},
|
|
1200
|
+
},
|
|
1201
|
+
};
|
|
1202
|
+
try {
|
|
1203
|
+
await client.recordRunOutcome(outcomePayload);
|
|
1204
|
+
}
|
|
1205
|
+
catch (err) {
|
|
1206
|
+
const timestamp = new Date().toISOString();
|
|
1207
|
+
const activityItem = {
|
|
1208
|
+
id: randomUUID(),
|
|
1209
|
+
type: "run_completed",
|
|
1210
|
+
title: `Buffered outcome for session ${stopped.runId}`,
|
|
1211
|
+
description: null,
|
|
1212
|
+
agentId: stopped.agentId,
|
|
1213
|
+
agentName: null,
|
|
1214
|
+
runId: stopped.runId,
|
|
1215
|
+
initiativeId,
|
|
1216
|
+
timestamp,
|
|
1217
|
+
phase: success ? "completed" : "blocked",
|
|
1218
|
+
summary: retroSummary,
|
|
1219
|
+
metadata: {
|
|
1220
|
+
source: "openclaw_local_fallback",
|
|
1221
|
+
error: toErrorMessage(err),
|
|
1222
|
+
},
|
|
1223
|
+
};
|
|
1224
|
+
await appendToOutbox(initiativeId, {
|
|
1225
|
+
id: randomUUID(),
|
|
1226
|
+
type: "outcome",
|
|
1227
|
+
timestamp,
|
|
1228
|
+
payload: outcomePayload,
|
|
1229
|
+
activityItem,
|
|
1230
|
+
});
|
|
1231
|
+
}
|
|
1232
|
+
try {
|
|
1233
|
+
await client.recordRunRetro(retroPayload);
|
|
1234
|
+
}
|
|
1235
|
+
catch (err) {
|
|
1236
|
+
const timestamp = new Date().toISOString();
|
|
1237
|
+
const activityItem = {
|
|
1238
|
+
id: randomUUID(),
|
|
1239
|
+
type: "artifact_created",
|
|
1240
|
+
title: `Buffered retro for session ${stopped.runId}`,
|
|
1241
|
+
description: null,
|
|
1242
|
+
agentId: stopped.agentId,
|
|
1243
|
+
agentName: null,
|
|
1244
|
+
runId: stopped.runId,
|
|
1245
|
+
initiativeId,
|
|
1246
|
+
timestamp,
|
|
1247
|
+
phase: success ? "completed" : "blocked",
|
|
1248
|
+
summary: retroSummary,
|
|
1249
|
+
metadata: {
|
|
1250
|
+
source: "openclaw_local_fallback",
|
|
1251
|
+
error: toErrorMessage(err),
|
|
1252
|
+
},
|
|
1253
|
+
};
|
|
1254
|
+
await appendToOutbox(initiativeId, {
|
|
1255
|
+
id: randomUUID(),
|
|
1256
|
+
type: "retro",
|
|
1257
|
+
timestamp,
|
|
1258
|
+
payload: retroPayload,
|
|
1259
|
+
activityItem,
|
|
1260
|
+
});
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
}
|
|
1264
|
+
catch {
|
|
1265
|
+
// best effort
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
819
1268
|
async function replayOutboxEvent(event) {
|
|
820
1269
|
const payload = event.payload ?? {};
|
|
1270
|
+
function normalizeRunFields(context) {
|
|
1271
|
+
// We prefer correlation IDs for replay because many local adapters use UUID-like
|
|
1272
|
+
// session IDs that do *not* exist as server-side run IDs.
|
|
1273
|
+
if (context.correlationId) {
|
|
1274
|
+
return { run_id: undefined, correlation_id: context.correlationId };
|
|
1275
|
+
}
|
|
1276
|
+
if (context.runId) {
|
|
1277
|
+
return {
|
|
1278
|
+
run_id: undefined,
|
|
1279
|
+
correlation_id: `openclaw_run_${stableHash(context.runId).slice(0, 24)}`,
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
return { run_id: undefined, correlation_id: undefined };
|
|
1283
|
+
}
|
|
821
1284
|
if (event.type === "progress") {
|
|
822
1285
|
const message = extractProgressOutboxMessage(payload);
|
|
823
1286
|
if (!message) {
|
|
@@ -848,7 +1311,12 @@ export default function register(api) {
|
|
|
848
1311
|
const meta = metaRaw && typeof metaRaw === "object" && !Array.isArray(metaRaw)
|
|
849
1312
|
? metaRaw
|
|
850
1313
|
: {};
|
|
851
|
-
const
|
|
1314
|
+
const baseMetadata = {
|
|
1315
|
+
...meta,
|
|
1316
|
+
source: "orgx_openclaw_outbox_replay",
|
|
1317
|
+
outbox_event_id: event.id,
|
|
1318
|
+
};
|
|
1319
|
+
let emitPayload = {
|
|
852
1320
|
initiative_id: context.value.initiativeId,
|
|
853
1321
|
run_id: context.value.runId,
|
|
854
1322
|
correlation_id: context.value.correlationId,
|
|
@@ -860,12 +1328,22 @@ export default function register(api) {
|
|
|
860
1328
|
next_step: pickStringField(payload, "next_step") ??
|
|
861
1329
|
pickStringField(payload, "nextStep") ??
|
|
862
1330
|
undefined,
|
|
863
|
-
metadata:
|
|
864
|
-
...meta,
|
|
865
|
-
source: "orgx_openclaw_outbox_replay",
|
|
866
|
-
outbox_event_id: event.id,
|
|
867
|
-
},
|
|
1331
|
+
metadata: baseMetadata,
|
|
868
1332
|
};
|
|
1333
|
+
// Locally-buffered progress events often store a local UUID in run_id. OrgX may reject
|
|
1334
|
+
// unknown run IDs on replay; prefer a deterministic non-UUID correlation key instead.
|
|
1335
|
+
if (emitPayload.run_id && !emitPayload.correlation_id) {
|
|
1336
|
+
const replayCorrelationId = `openclaw_run_${stableHash(emitPayload.run_id).slice(0, 24)}`;
|
|
1337
|
+
emitPayload = {
|
|
1338
|
+
...emitPayload,
|
|
1339
|
+
run_id: undefined,
|
|
1340
|
+
correlation_id: replayCorrelationId,
|
|
1341
|
+
metadata: {
|
|
1342
|
+
...(emitPayload.metadata ?? {}),
|
|
1343
|
+
replay_run_id_as_correlation: true,
|
|
1344
|
+
},
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
869
1347
|
try {
|
|
870
1348
|
await client.emitActivity(emitPayload);
|
|
871
1349
|
}
|
|
@@ -879,14 +1357,13 @@ export default function register(api) {
|
|
|
879
1357
|
/^404\\b/.test(msg) &&
|
|
880
1358
|
/\\brun\\b/i.test(msg) &&
|
|
881
1359
|
/not found/i.test(msg)) {
|
|
1360
|
+
const replayCorrelationId = `openclaw_run_${stableHash(emitPayload.run_id).slice(0, 24)}`;
|
|
882
1361
|
await client.emitActivity({
|
|
883
1362
|
...emitPayload,
|
|
884
1363
|
run_id: undefined,
|
|
885
|
-
|
|
886
|
-
// paths may interpret UUIDs as run_id lookups.
|
|
887
|
-
correlation_id: `openclaw:${emitPayload.run_id}`,
|
|
1364
|
+
correlation_id: replayCorrelationId,
|
|
888
1365
|
metadata: {
|
|
889
|
-
...emitPayload.metadata,
|
|
1366
|
+
...(emitPayload.metadata ?? {}),
|
|
890
1367
|
replay_run_id_as_correlation: true,
|
|
891
1368
|
},
|
|
892
1369
|
});
|
|
@@ -909,12 +1386,29 @@ export default function register(api) {
|
|
|
909
1386
|
if (!context.ok) {
|
|
910
1387
|
throw new Error(context.error);
|
|
911
1388
|
}
|
|
1389
|
+
const runFields = normalizeRunFields({
|
|
1390
|
+
runId: context.value.runId,
|
|
1391
|
+
correlationId: context.value.correlationId,
|
|
1392
|
+
});
|
|
1393
|
+
// Payloads should include a stable idempotency_key when enqueued, but older
|
|
1394
|
+
// events may not. Derive a deterministic fallback so outbox replay won't
|
|
1395
|
+
// double-create the same remote decision.
|
|
1396
|
+
const fallbackKey = stableHash(JSON.stringify({
|
|
1397
|
+
t: "decision",
|
|
1398
|
+
initiative_id: context.value.initiativeId,
|
|
1399
|
+
run_id: context.value.runId ?? null,
|
|
1400
|
+
correlation_id: context.value.correlationId ?? null,
|
|
1401
|
+
question,
|
|
1402
|
+
})).slice(0, 24);
|
|
1403
|
+
const resolvedIdempotencyKey = pickStringField(payload, "idempotency_key") ??
|
|
1404
|
+
pickStringField(payload, "idempotencyKey") ??
|
|
1405
|
+
`openclaw:decision:${fallbackKey}`;
|
|
912
1406
|
await client.applyChangeset({
|
|
913
1407
|
initiative_id: context.value.initiativeId,
|
|
914
|
-
run_id:
|
|
915
|
-
correlation_id:
|
|
1408
|
+
run_id: runFields.run_id,
|
|
1409
|
+
correlation_id: runFields.correlation_id,
|
|
916
1410
|
source_client: context.value.sourceClient,
|
|
917
|
-
idempotency_key:
|
|
1411
|
+
idempotency_key: resolvedIdempotencyKey,
|
|
918
1412
|
operations: [
|
|
919
1413
|
{
|
|
920
1414
|
op: "decision.create",
|
|
@@ -933,6 +1427,10 @@ export default function register(api) {
|
|
|
933
1427
|
if (!context.ok) {
|
|
934
1428
|
throw new Error(context.error);
|
|
935
1429
|
}
|
|
1430
|
+
const runFields = normalizeRunFields({
|
|
1431
|
+
runId: context.value.runId,
|
|
1432
|
+
correlationId: context.value.correlationId,
|
|
1433
|
+
});
|
|
936
1434
|
const operations = Array.isArray(payload.operations)
|
|
937
1435
|
? payload.operations
|
|
938
1436
|
: [];
|
|
@@ -942,33 +1440,230 @@ export default function register(api) {
|
|
|
942
1440
|
});
|
|
943
1441
|
return;
|
|
944
1442
|
}
|
|
1443
|
+
// Status updates are the most common offline replay payload, and `updateEntity`
|
|
1444
|
+
// is the most widely supported primitive across OrgX deployments. Prefer it
|
|
1445
|
+
// when the changeset contains only simple status mutations.
|
|
1446
|
+
const statusOps = operations
|
|
1447
|
+
.map((op) => {
|
|
1448
|
+
if (!op || typeof op !== "object")
|
|
1449
|
+
return null;
|
|
1450
|
+
const record = op;
|
|
1451
|
+
const kind = typeof record.op === "string" ? record.op.trim() : "";
|
|
1452
|
+
if (kind === "task.update") {
|
|
1453
|
+
const taskId = typeof record.task_id === "string" ? record.task_id.trim() : "";
|
|
1454
|
+
const statusRaw = typeof record.status === "string" ? record.status.trim() : "";
|
|
1455
|
+
const normalized = statusRaw.toLowerCase().replace(/\s+/g, "_");
|
|
1456
|
+
const status = normalized === "completed" || normalized === "complete" || normalized === "finished"
|
|
1457
|
+
? "done"
|
|
1458
|
+
: normalized === "inprogress"
|
|
1459
|
+
? "in_progress"
|
|
1460
|
+
: normalized;
|
|
1461
|
+
if (!taskId || !status)
|
|
1462
|
+
return null;
|
|
1463
|
+
return { type: "task", id: taskId, status };
|
|
1464
|
+
}
|
|
1465
|
+
if (kind === "milestone.update") {
|
|
1466
|
+
const milestoneId = typeof record.milestone_id === "string" ? record.milestone_id.trim() : "";
|
|
1467
|
+
const statusRaw = typeof record.status === "string" ? record.status.trim() : "";
|
|
1468
|
+
const normalized = statusRaw.toLowerCase().replace(/\s+/g, "_");
|
|
1469
|
+
const status = normalized === "done" || normalized === "complete" || normalized === "finished"
|
|
1470
|
+
? "completed"
|
|
1471
|
+
: normalized === "inprogress"
|
|
1472
|
+
? "in_progress"
|
|
1473
|
+
: normalized === "todo" || normalized === "not_started" || normalized === "pending"
|
|
1474
|
+
? "planned"
|
|
1475
|
+
: normalized === "blocked" || normalized === "stuck"
|
|
1476
|
+
? "at_risk"
|
|
1477
|
+
: normalized;
|
|
1478
|
+
if (!milestoneId || !status)
|
|
1479
|
+
return null;
|
|
1480
|
+
return { type: "milestone", id: milestoneId, status };
|
|
1481
|
+
}
|
|
1482
|
+
return null;
|
|
1483
|
+
})
|
|
1484
|
+
.filter((item) => Boolean(item));
|
|
1485
|
+
if (statusOps.length === operations.length) {
|
|
1486
|
+
for (const op of statusOps) {
|
|
1487
|
+
await client.updateEntity(op.type, op.id, { status: op.status });
|
|
1488
|
+
}
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
// Payloads should include a stable idempotency_key when enqueued, but older
|
|
1492
|
+
// events may not. Derive a deterministic fallback so outbox replay won't
|
|
1493
|
+
// double-create the same remote change.
|
|
1494
|
+
const fallbackKey = stableHash(JSON.stringify({
|
|
1495
|
+
t: "changeset",
|
|
1496
|
+
initiative_id: context.value.initiativeId,
|
|
1497
|
+
run_id: context.value.runId ?? null,
|
|
1498
|
+
correlation_id: context.value.correlationId ?? null,
|
|
1499
|
+
operations,
|
|
1500
|
+
})).slice(0, 24);
|
|
1501
|
+
const resolvedIdempotencyKey = pickStringField(payload, "idempotency_key") ??
|
|
1502
|
+
pickStringField(payload, "idempotencyKey") ??
|
|
1503
|
+
`openclaw:changeset:${fallbackKey}`;
|
|
945
1504
|
await client.applyChangeset({
|
|
946
1505
|
initiative_id: context.value.initiativeId,
|
|
947
|
-
run_id:
|
|
948
|
-
correlation_id:
|
|
1506
|
+
run_id: runFields.run_id,
|
|
1507
|
+
correlation_id: runFields.correlation_id,
|
|
949
1508
|
source_client: context.value.sourceClient,
|
|
950
|
-
idempotency_key:
|
|
1509
|
+
idempotency_key: resolvedIdempotencyKey,
|
|
951
1510
|
operations,
|
|
952
1511
|
});
|
|
953
1512
|
return;
|
|
954
1513
|
}
|
|
955
|
-
if (event.type === "
|
|
956
|
-
const
|
|
957
|
-
if (!
|
|
958
|
-
|
|
1514
|
+
if (event.type === "outcome") {
|
|
1515
|
+
const context = resolveReportingContext(payload);
|
|
1516
|
+
if (!context.ok) {
|
|
1517
|
+
throw new Error(context.error);
|
|
1518
|
+
}
|
|
1519
|
+
const runFields = normalizeRunFields({
|
|
1520
|
+
runId: context.value.runId,
|
|
1521
|
+
correlationId: context.value.correlationId,
|
|
1522
|
+
});
|
|
1523
|
+
const executionId = pickStringField(payload, "execution_id") ??
|
|
1524
|
+
pickStringField(payload, "executionId");
|
|
1525
|
+
const executionType = pickStringField(payload, "execution_type") ??
|
|
1526
|
+
pickStringField(payload, "executionType");
|
|
1527
|
+
const agentId = pickStringField(payload, "agent_id") ??
|
|
1528
|
+
pickStringField(payload, "agentId");
|
|
1529
|
+
const success = typeof payload.success === "boolean"
|
|
1530
|
+
? payload.success
|
|
1531
|
+
: null;
|
|
1532
|
+
if (!executionId || !executionType || !agentId || success === null) {
|
|
1533
|
+
api.log?.warn?.("[orgx] Dropping invalid outcome outbox event", {
|
|
1534
|
+
eventId: event.id,
|
|
1535
|
+
});
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
const metaRaw = payload.metadata;
|
|
1539
|
+
const meta = metaRaw && typeof metaRaw === "object" && !Array.isArray(metaRaw)
|
|
1540
|
+
? metaRaw
|
|
1541
|
+
: {};
|
|
1542
|
+
await client.recordRunOutcome({
|
|
1543
|
+
initiative_id: context.value.initiativeId,
|
|
1544
|
+
run_id: runFields.run_id,
|
|
1545
|
+
correlation_id: runFields.correlation_id,
|
|
1546
|
+
source_client: context.value.sourceClient,
|
|
1547
|
+
execution_id: executionId,
|
|
1548
|
+
execution_type: executionType,
|
|
1549
|
+
agent_id: agentId,
|
|
1550
|
+
task_type: pickStringField(payload, "task_type") ??
|
|
1551
|
+
pickStringField(payload, "taskType") ??
|
|
1552
|
+
undefined,
|
|
1553
|
+
domain: pickStringField(payload, "domain") ?? undefined,
|
|
1554
|
+
started_at: pickStringField(payload, "started_at") ??
|
|
1555
|
+
pickStringField(payload, "startedAt") ??
|
|
1556
|
+
undefined,
|
|
1557
|
+
completed_at: pickStringField(payload, "completed_at") ??
|
|
1558
|
+
pickStringField(payload, "completedAt") ??
|
|
1559
|
+
undefined,
|
|
1560
|
+
inputs: payload.inputs && typeof payload.inputs === "object"
|
|
1561
|
+
? payload.inputs
|
|
1562
|
+
: undefined,
|
|
1563
|
+
outputs: payload.outputs && typeof payload.outputs === "object"
|
|
1564
|
+
? payload.outputs
|
|
1565
|
+
: undefined,
|
|
1566
|
+
steps: Array.isArray(payload.steps)
|
|
1567
|
+
? payload.steps
|
|
1568
|
+
: undefined,
|
|
1569
|
+
success,
|
|
1570
|
+
quality_score: typeof payload.quality_score === "number"
|
|
1571
|
+
? payload.quality_score
|
|
1572
|
+
: typeof payload.qualityScore === "number"
|
|
1573
|
+
? payload.qualityScore
|
|
1574
|
+
: undefined,
|
|
1575
|
+
duration_vs_estimate: typeof payload.duration_vs_estimate === "number"
|
|
1576
|
+
? payload.duration_vs_estimate
|
|
1577
|
+
: typeof payload.durationVsEstimate === "number"
|
|
1578
|
+
? payload.durationVsEstimate
|
|
1579
|
+
: undefined,
|
|
1580
|
+
cost_vs_budget: typeof payload.cost_vs_budget === "number"
|
|
1581
|
+
? payload.cost_vs_budget
|
|
1582
|
+
: typeof payload.costVsBudget === "number"
|
|
1583
|
+
? payload.costVsBudget
|
|
1584
|
+
: undefined,
|
|
1585
|
+
human_interventions: typeof payload.human_interventions === "number"
|
|
1586
|
+
? payload.human_interventions
|
|
1587
|
+
: typeof payload.humanInterventions === "number"
|
|
1588
|
+
? payload.humanInterventions
|
|
1589
|
+
: undefined,
|
|
1590
|
+
user_satisfaction: typeof payload.user_satisfaction === "number"
|
|
1591
|
+
? payload.user_satisfaction
|
|
1592
|
+
: typeof payload.userSatisfaction === "number"
|
|
1593
|
+
? payload.userSatisfaction
|
|
1594
|
+
: undefined,
|
|
1595
|
+
errors: Array.isArray(payload.errors)
|
|
1596
|
+
? payload.errors.filter((e) => typeof e === "string")
|
|
1597
|
+
: undefined,
|
|
1598
|
+
metadata: {
|
|
1599
|
+
...meta,
|
|
1600
|
+
source: "orgx_openclaw_outbox_replay",
|
|
1601
|
+
outbox_event_id: event.id,
|
|
1602
|
+
},
|
|
1603
|
+
});
|
|
1604
|
+
return;
|
|
1605
|
+
}
|
|
1606
|
+
if (event.type === "retro") {
|
|
1607
|
+
const context = resolveReportingContext(payload);
|
|
1608
|
+
if (!context.ok) {
|
|
1609
|
+
throw new Error(context.error);
|
|
1610
|
+
}
|
|
1611
|
+
const runFields = normalizeRunFields({
|
|
1612
|
+
runId: context.value.runId,
|
|
1613
|
+
correlationId: context.value.correlationId,
|
|
1614
|
+
});
|
|
1615
|
+
const retro = payload.retro && typeof payload.retro === "object" && !Array.isArray(payload.retro)
|
|
1616
|
+
? payload.retro
|
|
1617
|
+
: null;
|
|
1618
|
+
const summary = retro && typeof retro.summary === "string" ? retro.summary.trim() : "";
|
|
1619
|
+
if (!retro || !summary) {
|
|
1620
|
+
api.log?.warn?.("[orgx] Dropping invalid retro outbox event", {
|
|
959
1621
|
eventId: event.id,
|
|
960
1622
|
});
|
|
961
1623
|
return;
|
|
962
1624
|
}
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1625
|
+
const entityTypeRaw = pickStringField(payload, "entity_type") ??
|
|
1626
|
+
pickStringField(payload, "entityType");
|
|
1627
|
+
const parsedEntityType = parseRetroEntityType(entityTypeRaw) ?? null;
|
|
1628
|
+
// Server-side enum parity can lag behind local clients. Only attach to the
|
|
1629
|
+
// entity types that are guaranteed to exist today.
|
|
1630
|
+
const entityType = parsedEntityType === "initiative" || parsedEntityType === "task"
|
|
1631
|
+
? parsedEntityType
|
|
1632
|
+
: null;
|
|
1633
|
+
const entityIdRaw = pickStringField(payload, "entity_id") ??
|
|
1634
|
+
pickStringField(payload, "entityId") ??
|
|
1635
|
+
null;
|
|
1636
|
+
const entityId = isUuid(entityIdRaw ?? undefined) ? entityIdRaw : null;
|
|
1637
|
+
await client.recordRunRetro({
|
|
1638
|
+
initiative_id: context.value.initiativeId,
|
|
1639
|
+
run_id: runFields.run_id,
|
|
1640
|
+
correlation_id: runFields.correlation_id,
|
|
1641
|
+
source_client: context.value.sourceClient,
|
|
1642
|
+
entity_type: entityType && entityId ? entityType : undefined,
|
|
1643
|
+
entity_id: entityType && entityId ? entityId : undefined,
|
|
1644
|
+
title: pickStringField(payload, "title") ?? undefined,
|
|
1645
|
+
idempotency_key: pickStringField(payload, "idempotency_key") ??
|
|
1646
|
+
pickStringField(payload, "idempotencyKey") ??
|
|
1647
|
+
undefined,
|
|
1648
|
+
retro: retro,
|
|
1649
|
+
markdown: pickStringField(payload, "markdown") ?? undefined,
|
|
969
1650
|
});
|
|
970
1651
|
return;
|
|
971
1652
|
}
|
|
1653
|
+
if (event.type === "artifact") {
|
|
1654
|
+
// Artifacts are UI-level breadcrumbs and may not be supported by every
|
|
1655
|
+
// OrgX deployment's `/api/entities` schema. Persist locally and drop from
|
|
1656
|
+
// the outbox so progress reporting doesn't wedge on irreplayable items.
|
|
1657
|
+
try {
|
|
1658
|
+
if (event.activityItem) {
|
|
1659
|
+
appendActivityItems([event.activityItem]);
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
catch {
|
|
1663
|
+
// best effort
|
|
1664
|
+
}
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
972
1667
|
}
|
|
973
1668
|
async function flushOutboxQueues() {
|
|
974
1669
|
const attemptAt = new Date().toISOString();
|
|
@@ -1040,6 +1735,9 @@ export default function register(api) {
|
|
|
1040
1735
|
return syncInFlight;
|
|
1041
1736
|
}
|
|
1042
1737
|
syncInFlight = (async () => {
|
|
1738
|
+
if (!config.apiKey) {
|
|
1739
|
+
refreshConfigFromSources({ reason: "sync_no_api_key" });
|
|
1740
|
+
}
|
|
1043
1741
|
if (!config.apiKey) {
|
|
1044
1742
|
updateOnboardingState({
|
|
1045
1743
|
status: "idle",
|
|
@@ -1050,25 +1748,123 @@ export default function register(api) {
|
|
|
1050
1748
|
return;
|
|
1051
1749
|
}
|
|
1052
1750
|
try {
|
|
1053
|
-
|
|
1751
|
+
await reconcileStoppedAgentRuns();
|
|
1752
|
+
let snapshotError = null;
|
|
1753
|
+
try {
|
|
1754
|
+
updateCachedSnapshot(await client.getOrgSnapshot());
|
|
1755
|
+
}
|
|
1756
|
+
catch (err) {
|
|
1757
|
+
if (isAuthFailure(err)) {
|
|
1758
|
+
throw err;
|
|
1759
|
+
}
|
|
1760
|
+
snapshotError = toErrorMessage(err);
|
|
1761
|
+
api.log?.warn?.("[orgx] Snapshot sync failed (continuing)", {
|
|
1762
|
+
error: snapshotError,
|
|
1763
|
+
});
|
|
1764
|
+
}
|
|
1765
|
+
// Best-effort: poll the canonical OrgX SkillPack so the dashboard/install path
|
|
1766
|
+
// can apply it without blocking on an on-demand fetch.
|
|
1767
|
+
try {
|
|
1768
|
+
const refreshed = await refreshSkillPackState({
|
|
1769
|
+
getSkillPack: (args) => client.getSkillPack(args),
|
|
1770
|
+
});
|
|
1771
|
+
if (refreshed.changed) {
|
|
1772
|
+
void posthogCapture({
|
|
1773
|
+
event: "openclaw_skill_pack_updated",
|
|
1774
|
+
distinctId: config.installationId,
|
|
1775
|
+
properties: {
|
|
1776
|
+
plugin_version: config.pluginVersion,
|
|
1777
|
+
skill_pack_name: refreshed.state.pack?.name ?? null,
|
|
1778
|
+
skill_pack_version: refreshed.state.pack?.version ?? null,
|
|
1779
|
+
skill_pack_checksum: refreshed.state.pack?.checksum ?? null,
|
|
1780
|
+
},
|
|
1781
|
+
}).catch(() => {
|
|
1782
|
+
// best effort
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
catch {
|
|
1787
|
+
// best effort
|
|
1788
|
+
}
|
|
1789
|
+
// Best-effort: provision/update the OrgX agent suite after we've verified a working connection.
|
|
1790
|
+
// This makes domain agents available immediately for launches without requiring a manual install.
|
|
1791
|
+
try {
|
|
1792
|
+
if (config.autoInstallAgentSuiteOnConnect !== false) {
|
|
1793
|
+
const state = readSkillPackState();
|
|
1794
|
+
const updateAvailable = Boolean(state.remote?.checksum &&
|
|
1795
|
+
state.pack?.checksum &&
|
|
1796
|
+
state.remote.checksum !== state.pack.checksum);
|
|
1797
|
+
const plan = computeOrgxAgentSuitePlan({
|
|
1798
|
+
packVersion: config.pluginVersion || "0.0.0",
|
|
1799
|
+
skillPack: state.overrides,
|
|
1800
|
+
skillPackRemote: state.remote,
|
|
1801
|
+
skillPackPolicy: state.policy,
|
|
1802
|
+
skillPackUpdateAvailable: updateAvailable,
|
|
1803
|
+
});
|
|
1804
|
+
const hasConflicts = (plan.workspaceFiles ?? []).some((f) => f.action === "conflict");
|
|
1805
|
+
const hasWork = Boolean(plan.openclawConfigWouldUpdate) ||
|
|
1806
|
+
(plan.workspaceFiles ?? []).some((f) => f.action !== "noop");
|
|
1807
|
+
if (hasWork && !hasConflicts) {
|
|
1808
|
+
const applied = applyOrgxAgentSuitePlan({
|
|
1809
|
+
plan,
|
|
1810
|
+
dryRun: false,
|
|
1811
|
+
skillPack: state.overrides,
|
|
1812
|
+
});
|
|
1813
|
+
void applied;
|
|
1814
|
+
void posthogCapture({
|
|
1815
|
+
event: "openclaw_agent_suite_auto_install",
|
|
1816
|
+
distinctId: config.installationId,
|
|
1817
|
+
properties: {
|
|
1818
|
+
plugin_version: (config.pluginVersion ?? "").trim() || null,
|
|
1819
|
+
skill_pack_source: plan.skillPack?.source ?? null,
|
|
1820
|
+
skill_pack_checksum: plan.skillPack?.checksum ?? null,
|
|
1821
|
+
skill_pack_version: plan.skillPack?.version ?? null,
|
|
1822
|
+
openclaw_config_updated: Boolean(plan.openclawConfigWouldUpdate),
|
|
1823
|
+
},
|
|
1824
|
+
}).catch(() => {
|
|
1825
|
+
// best effort
|
|
1826
|
+
});
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
catch (err) {
|
|
1831
|
+
api.log?.debug?.("[orgx] Agent suite auto-provision skipped/failed (best effort)", {
|
|
1832
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1054
1835
|
updateOnboardingState({
|
|
1055
1836
|
status: "connected",
|
|
1056
1837
|
hasApiKey: true,
|
|
1057
|
-
connectionVerified:
|
|
1058
|
-
lastError:
|
|
1838
|
+
connectionVerified: snapshotError === null,
|
|
1839
|
+
lastError: snapshotError,
|
|
1059
1840
|
nextAction: "open_dashboard",
|
|
1060
1841
|
});
|
|
1061
1842
|
await flushOutboxQueues();
|
|
1062
1843
|
api.log?.debug?.("[orgx] Sync OK");
|
|
1063
1844
|
}
|
|
1064
1845
|
catch (err) {
|
|
1846
|
+
const authFailure = isAuthFailure(err);
|
|
1847
|
+
const errorMessage = authFailure
|
|
1848
|
+
? "Unauthorized. Your OrgX key may be revoked or expired. Reconnect in browser or use API key."
|
|
1849
|
+
: toErrorMessage(err);
|
|
1065
1850
|
updateOnboardingState({
|
|
1066
1851
|
status: "error",
|
|
1067
1852
|
hasApiKey: true,
|
|
1068
1853
|
connectionVerified: false,
|
|
1069
|
-
lastError:
|
|
1854
|
+
lastError: errorMessage,
|
|
1070
1855
|
nextAction: "reconnect",
|
|
1071
1856
|
});
|
|
1857
|
+
if (authFailure) {
|
|
1858
|
+
void posthogCapture({
|
|
1859
|
+
event: "openclaw_sync_auth_failed",
|
|
1860
|
+
distinctId: config.installationId,
|
|
1861
|
+
properties: {
|
|
1862
|
+
plugin_version: config.pluginVersion,
|
|
1863
|
+
},
|
|
1864
|
+
}).catch(() => {
|
|
1865
|
+
// best effort
|
|
1866
|
+
});
|
|
1867
|
+
}
|
|
1072
1868
|
api.log?.warn?.(`[orgx] Sync failed: ${err instanceof Error ? err.message : err}`);
|
|
1073
1869
|
}
|
|
1074
1870
|
})();
|
|
@@ -1100,7 +1896,10 @@ export default function register(api) {
|
|
|
1100
1896
|
openclawVersion: input.openclawVersion,
|
|
1101
1897
|
platform: input.platform || process.platform,
|
|
1102
1898
|
deviceName: input.deviceName,
|
|
1103
|
-
}
|
|
1899
|
+
},
|
|
1900
|
+
// Pairing can hit a cold serverless boot + supabase insert + rate-limit checks.
|
|
1901
|
+
// Give it more headroom than typical lightweight API calls.
|
|
1902
|
+
{ timeoutMs: 30_000 });
|
|
1104
1903
|
if (!started.ok) {
|
|
1105
1904
|
if (isAuthRequiredError(started)) {
|
|
1106
1905
|
clearPairingState();
|
|
@@ -1124,7 +1923,8 @@ export default function register(api) {
|
|
|
1124
1923
|
state,
|
|
1125
1924
|
};
|
|
1126
1925
|
}
|
|
1127
|
-
const
|
|
1926
|
+
const statusLabel = started.status ? ` (HTTP ${started.status})` : "";
|
|
1927
|
+
const message = `Pairing start failed${statusLabel}: ${started.error}`;
|
|
1128
1928
|
updateOnboardingState({
|
|
1129
1929
|
status: "error",
|
|
1130
1930
|
hasApiKey: Boolean(config.apiKey),
|
|
@@ -1875,6 +2675,48 @@ export default function register(api) {
|
|
|
1875
2675
|
}
|
|
1876
2676
|
},
|
|
1877
2677
|
}, { optional: true });
|
|
2678
|
+
function withProvenanceMetadata(metadata) {
|
|
2679
|
+
const input = metadata ?? {};
|
|
2680
|
+
const out = { ...input };
|
|
2681
|
+
if (out.orgx_plugin_version === undefined) {
|
|
2682
|
+
out.orgx_plugin_version = (config.pluginVersion ?? "").trim() || null;
|
|
2683
|
+
}
|
|
2684
|
+
try {
|
|
2685
|
+
const state = readSkillPackState();
|
|
2686
|
+
const overrides = state.overrides;
|
|
2687
|
+
if (out.skill_pack_name === undefined) {
|
|
2688
|
+
out.skill_pack_name = overrides?.name ?? state.pack?.name ?? null;
|
|
2689
|
+
}
|
|
2690
|
+
if (out.skill_pack_version === undefined) {
|
|
2691
|
+
out.skill_pack_version = overrides?.version ?? state.pack?.version ?? null;
|
|
2692
|
+
}
|
|
2693
|
+
if (out.skill_pack_checksum === undefined) {
|
|
2694
|
+
out.skill_pack_checksum = overrides?.checksum ?? state.pack?.checksum ?? null;
|
|
2695
|
+
}
|
|
2696
|
+
if (out.skill_pack_source === undefined) {
|
|
2697
|
+
out.skill_pack_source = overrides?.source ?? null;
|
|
2698
|
+
}
|
|
2699
|
+
if (out.skill_pack_etag === undefined) {
|
|
2700
|
+
out.skill_pack_etag = state.etag ?? null;
|
|
2701
|
+
}
|
|
2702
|
+
}
|
|
2703
|
+
catch {
|
|
2704
|
+
// best effort
|
|
2705
|
+
}
|
|
2706
|
+
if (out.orgx_provenance === undefined) {
|
|
2707
|
+
out.orgx_provenance = {
|
|
2708
|
+
plugin_version: out.orgx_plugin_version ?? null,
|
|
2709
|
+
skill_pack: {
|
|
2710
|
+
name: out.skill_pack_name ?? null,
|
|
2711
|
+
version: out.skill_pack_version ?? null,
|
|
2712
|
+
checksum: out.skill_pack_checksum ?? null,
|
|
2713
|
+
source: out.skill_pack_source ?? null,
|
|
2714
|
+
etag: out.skill_pack_etag ?? null,
|
|
2715
|
+
},
|
|
2716
|
+
};
|
|
2717
|
+
}
|
|
2718
|
+
return out;
|
|
2719
|
+
}
|
|
1878
2720
|
async function emitActivityWithFallback(source, payload) {
|
|
1879
2721
|
if (!payload.message || payload.message.trim().length === 0) {
|
|
1880
2722
|
return text("❌ message is required");
|
|
@@ -1895,10 +2737,10 @@ export default function register(api) {
|
|
|
1895
2737
|
progress_pct: payload.progress_pct,
|
|
1896
2738
|
level: payload.level ?? "info",
|
|
1897
2739
|
next_step: payload.next_step,
|
|
1898
|
-
metadata: {
|
|
2740
|
+
metadata: withProvenanceMetadata({
|
|
1899
2741
|
...(payload.metadata ?? {}),
|
|
1900
2742
|
source,
|
|
1901
|
-
},
|
|
2743
|
+
}),
|
|
1902
2744
|
};
|
|
1903
2745
|
const activityItem = {
|
|
1904
2746
|
id,
|
|
@@ -1961,10 +2803,10 @@ export default function register(api) {
|
|
|
1961
2803
|
timestamp: now,
|
|
1962
2804
|
phase: "review",
|
|
1963
2805
|
summary: `${payload.operations.length} operation${payload.operations.length === 1 ? "" : "s"}`,
|
|
1964
|
-
metadata: {
|
|
2806
|
+
metadata: withProvenanceMetadata({
|
|
1965
2807
|
source,
|
|
1966
2808
|
idempotency_key: idempotencyKey,
|
|
1967
|
-
},
|
|
2809
|
+
}),
|
|
1968
2810
|
};
|
|
1969
2811
|
try {
|
|
1970
2812
|
const result = await client.applyChangeset(requestPayload);
|
|
@@ -2239,6 +3081,10 @@ export default function register(api) {
|
|
|
2239
3081
|
parameters: {
|
|
2240
3082
|
type: "object",
|
|
2241
3083
|
properties: {
|
|
3084
|
+
initiative_id: {
|
|
3085
|
+
type: "string",
|
|
3086
|
+
description: "Optional initiative UUID to attach this artifact to",
|
|
3087
|
+
},
|
|
2242
3088
|
name: {
|
|
2243
3089
|
type: "string",
|
|
2244
3090
|
description: "Human-readable artifact name (e.g., 'PR #107: Fix build size')",
|
|
@@ -2263,6 +3109,9 @@ export default function register(api) {
|
|
|
2263
3109
|
async execute(_callId, params = { name: "", artifact_type: "other" }) {
|
|
2264
3110
|
const now = new Date().toISOString();
|
|
2265
3111
|
const id = `artifact:${randomUUID().slice(0, 8)}`;
|
|
3112
|
+
const initiativeId = isUuid(params.initiative_id)
|
|
3113
|
+
? params.initiative_id
|
|
3114
|
+
: inferReportingInitiativeId(params) ?? null;
|
|
2266
3115
|
const activityItem = {
|
|
2267
3116
|
id,
|
|
2268
3117
|
type: "artifact_created",
|
|
@@ -2271,20 +3120,21 @@ export default function register(api) {
|
|
|
2271
3120
|
agentId: null,
|
|
2272
3121
|
agentName: null,
|
|
2273
3122
|
runId: null,
|
|
2274
|
-
initiativeId
|
|
3123
|
+
initiativeId,
|
|
2275
3124
|
timestamp: now,
|
|
2276
3125
|
summary: params.url ?? null,
|
|
2277
|
-
metadata: {
|
|
3126
|
+
metadata: withProvenanceMetadata({
|
|
2278
3127
|
source: "orgx_register_artifact",
|
|
2279
3128
|
artifact_type: params.artifact_type,
|
|
2280
3129
|
url: params.url,
|
|
2281
|
-
},
|
|
3130
|
+
}),
|
|
2282
3131
|
};
|
|
2283
3132
|
try {
|
|
2284
3133
|
const entity = await client.createEntity("artifact", {
|
|
2285
|
-
|
|
3134
|
+
title: params.name,
|
|
2286
3135
|
artifact_type: params.artifact_type,
|
|
2287
|
-
|
|
3136
|
+
summary: params.description,
|
|
3137
|
+
initiative_id: initiativeId ?? undefined,
|
|
2288
3138
|
artifact_url: params.url,
|
|
2289
3139
|
status: "active",
|
|
2290
3140
|
});
|
|
@@ -2295,7 +3145,10 @@ export default function register(api) {
|
|
|
2295
3145
|
id,
|
|
2296
3146
|
type: "artifact",
|
|
2297
3147
|
timestamp: now,
|
|
2298
|
-
payload:
|
|
3148
|
+
payload: {
|
|
3149
|
+
...params,
|
|
3150
|
+
initiative_id: initiativeId,
|
|
3151
|
+
},
|
|
2299
3152
|
activityItem,
|
|
2300
3153
|
});
|
|
2301
3154
|
return text(`Artifact saved locally: ${params.name} [${params.artifact_type}] (will sync when connected)`);
|
|
@@ -2406,8 +3259,29 @@ export default function register(api) {
|
|
|
2406
3259
|
}, {
|
|
2407
3260
|
getHealth: async (input = {}) => buildHealthReport({ probeRemote: input.probeRemote === true }),
|
|
2408
3261
|
});
|
|
3262
|
+
const mcpPromptRegistry = new Map();
|
|
3263
|
+
mcpPromptRegistry.set("ship", {
|
|
3264
|
+
name: "ship",
|
|
3265
|
+
description: "Commit local changes, open a PR, and merge it (GitHub CLI required).",
|
|
3266
|
+
arguments: [],
|
|
3267
|
+
messages: [
|
|
3268
|
+
{
|
|
3269
|
+
role: "user",
|
|
3270
|
+
content: [
|
|
3271
|
+
"Ship the current work:",
|
|
3272
|
+
"- Inspect `git status -sb` and `git diff --stat` and summarize what will be shipped.",
|
|
3273
|
+
"- Run `npm run typecheck`, `npm run test:hooks`, and `npm run build` (fix failures).",
|
|
3274
|
+
"- Create a feature branch if on `main`.",
|
|
3275
|
+
"- Commit with a clear message (do not include secrets).",
|
|
3276
|
+
"- Push branch, open a PR (use `gh pr create`), then merge it (use `gh pr merge --merge --auto`).",
|
|
3277
|
+
"- If `gh` is not authenticated, stop and tell me what to run.",
|
|
3278
|
+
].join("\n"),
|
|
3279
|
+
},
|
|
3280
|
+
],
|
|
3281
|
+
});
|
|
2409
3282
|
const mcpHttpHandler = createMcpHttpHandler({
|
|
2410
3283
|
tools: mcpToolRegistry,
|
|
3284
|
+
prompts: mcpPromptRegistry,
|
|
2411
3285
|
logger: api.log ?? {},
|
|
2412
3286
|
serverName: "@useorgx/openclaw-plugin",
|
|
2413
3287
|
serverVersion: config.pluginVersion,
|