@useorgx/openclaw-plugin 0.4.6 → 0.4.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/README.md +310 -24
  2. package/dashboard/dist/assets/B5NEElEI.css +1 -0
  3. package/dashboard/dist/assets/BhapSNAs.js +215 -0
  4. package/dashboard/dist/assets/iFdvE7lx.js +1 -0
  5. package/dashboard/dist/assets/jRJsmpYM.js +1 -0
  6. package/dashboard/dist/index.html +2 -2
  7. package/dist/activity-actor-fields.d.ts +3 -0
  8. package/dist/activity-actor-fields.js +128 -0
  9. package/dist/activity-store.js +12 -19
  10. package/dist/agent-context-store.js +5 -25
  11. package/dist/agent-run-store.js +5 -25
  12. package/dist/agent-suite.js +1 -8
  13. package/dist/artifacts/register-artifact.d.ts +47 -0
  14. package/dist/artifacts/register-artifact.js +271 -0
  15. package/dist/auth/flows.d.ts +47 -0
  16. package/dist/auth/flows.js +169 -0
  17. package/dist/auth-store.js +14 -39
  18. package/dist/byok-store.js +5 -19
  19. package/dist/cli/orgx.d.ts +66 -0
  20. package/dist/cli/orgx.js +91 -0
  21. package/dist/config/refresh.d.ts +32 -0
  22. package/dist/config/refresh.js +55 -0
  23. package/dist/config/resolution.d.ts +37 -0
  24. package/dist/config/resolution.js +178 -0
  25. package/dist/contracts/client.d.ts +1 -0
  26. package/dist/contracts/client.js +7 -5
  27. package/dist/contracts/shared-types.d.ts +147 -0
  28. package/dist/contracts/shared-types.js +3 -0
  29. package/dist/contracts/types.d.ts +1 -130
  30. package/dist/contracts/types.js +5 -0
  31. package/dist/entities/auto-assignment.d.ts +36 -0
  32. package/dist/entities/auto-assignment.js +115 -0
  33. package/dist/entity-comment-store.js +5 -25
  34. package/dist/hash-utils.d.ts +2 -0
  35. package/dist/hash-utils.js +12 -0
  36. package/dist/http/helpers/activity-headline.d.ts +10 -0
  37. package/dist/http/helpers/activity-headline.js +192 -0
  38. package/dist/http/helpers/artifact-fallback.d.ts +13 -0
  39. package/dist/http/helpers/artifact-fallback.js +148 -0
  40. package/dist/http/helpers/auto-continue-engine.d.ts +298 -0
  41. package/dist/http/helpers/auto-continue-engine.js +1218 -0
  42. package/dist/http/helpers/autopilot-operations.d.ts +157 -0
  43. package/dist/http/helpers/autopilot-operations.js +403 -0
  44. package/dist/http/helpers/autopilot-runtime.d.ts +42 -0
  45. package/dist/http/helpers/autopilot-runtime.js +319 -0
  46. package/dist/http/helpers/autopilot-slice-utils.d.ts +38 -0
  47. package/dist/http/helpers/autopilot-slice-utils.js +476 -0
  48. package/dist/http/helpers/decision-mapper.d.ts +12 -0
  49. package/dist/http/helpers/decision-mapper.js +44 -0
  50. package/dist/http/helpers/dispatch-lifecycle.d.ts +102 -0
  51. package/dist/http/helpers/dispatch-lifecycle.js +604 -0
  52. package/dist/http/helpers/hash-utils.d.ts +1 -0
  53. package/dist/http/helpers/hash-utils.js +1 -0
  54. package/dist/http/helpers/kickoff-context.d.ts +12 -0
  55. package/dist/http/helpers/kickoff-context.js +154 -0
  56. package/dist/http/helpers/mission-control.d.ts +94 -0
  57. package/dist/http/helpers/mission-control.js +894 -0
  58. package/dist/http/helpers/openclaw-cli.d.ts +37 -0
  59. package/dist/http/helpers/openclaw-cli.js +283 -0
  60. package/dist/http/helpers/runtime-sse.d.ts +20 -0
  61. package/dist/http/helpers/runtime-sse.js +110 -0
  62. package/dist/http/helpers/value-utils.d.ts +6 -0
  63. package/dist/http/helpers/value-utils.js +67 -0
  64. package/dist/http/index.d.ts +88 -0
  65. package/dist/http/index.js +2353 -0
  66. package/dist/http/router.d.ts +23 -0
  67. package/dist/http/router.js +23 -0
  68. package/dist/http/routes/agent-control.d.ts +79 -0
  69. package/dist/http/routes/agent-control.js +684 -0
  70. package/dist/http/routes/agent-suite.d.ts +29 -0
  71. package/dist/http/routes/agent-suite.js +198 -0
  72. package/dist/http/routes/agents-catalog.d.ts +40 -0
  73. package/dist/http/routes/agents-catalog.js +83 -0
  74. package/dist/http/routes/billing.d.ts +23 -0
  75. package/dist/http/routes/billing.js +55 -0
  76. package/dist/http/routes/debug.d.ts +14 -0
  77. package/dist/http/routes/debug.js +21 -0
  78. package/dist/http/routes/decision-actions.d.ts +13 -0
  79. package/dist/http/routes/decision-actions.js +66 -0
  80. package/dist/http/routes/delegation.d.ts +19 -0
  81. package/dist/http/routes/delegation.js +32 -0
  82. package/dist/http/routes/entities.d.ts +47 -0
  83. package/dist/http/routes/entities.js +152 -0
  84. package/dist/http/routes/entity-dynamic.d.ts +25 -0
  85. package/dist/http/routes/entity-dynamic.js +191 -0
  86. package/dist/http/routes/health.d.ts +22 -0
  87. package/dist/http/routes/health.js +49 -0
  88. package/dist/http/routes/live-legacy.d.ts +110 -0
  89. package/dist/http/routes/live-legacy.js +598 -0
  90. package/dist/http/routes/live-misc.d.ts +69 -0
  91. package/dist/http/routes/live-misc.js +206 -0
  92. package/dist/http/routes/live-snapshot.d.ts +90 -0
  93. package/dist/http/routes/live-snapshot.js +297 -0
  94. package/dist/http/routes/mission-control-actions.d.ts +83 -0
  95. package/dist/http/routes/mission-control-actions.js +541 -0
  96. package/dist/http/routes/mission-control-read.d.ts +28 -0
  97. package/dist/http/routes/mission-control-read.js +67 -0
  98. package/dist/http/routes/onboarding.d.ts +34 -0
  99. package/dist/http/routes/onboarding.js +101 -0
  100. package/dist/http/routes/run-control.d.ts +24 -0
  101. package/dist/http/routes/run-control.js +86 -0
  102. package/dist/http/routes/runtime-hooks.d.ts +69 -0
  103. package/dist/http/routes/runtime-hooks.js +437 -0
  104. package/dist/http/routes/settings-byok.d.ts +23 -0
  105. package/dist/http/routes/settings-byok.js +163 -0
  106. package/dist/http/routes/summary.d.ts +18 -0
  107. package/dist/http/routes/summary.js +42 -0
  108. package/dist/http/routes/work-artifacts.d.ts +9 -0
  109. package/dist/http/routes/work-artifacts.js +36 -0
  110. package/dist/http/shared-state.d.ts +16 -0
  111. package/dist/http/shared-state.js +1 -0
  112. package/dist/http-handler.d.ts +1 -88
  113. package/dist/http-handler.js +1 -9664
  114. package/dist/index.js +122 -2121
  115. package/dist/json-utils.d.ts +1 -0
  116. package/dist/json-utils.js +8 -0
  117. package/dist/local-openclaw.js +8 -0
  118. package/dist/mcp-client-setup.js +75 -90
  119. package/dist/next-up-queue-store.js +4 -18
  120. package/dist/runtime-instance-store.js +8 -34
  121. package/dist/services/background.d.ts +23 -0
  122. package/dist/services/background.js +23 -0
  123. package/dist/services/instrumentation.d.ts +29 -0
  124. package/dist/services/instrumentation.js +136 -0
  125. package/dist/snapshot-store.js +5 -25
  126. package/dist/stores/json-store.d.ts +11 -0
  127. package/dist/stores/json-store.js +42 -0
  128. package/dist/sync/outbox-replay.d.ts +55 -0
  129. package/dist/sync/outbox-replay.js +514 -0
  130. package/dist/tools/core-tools.d.ts +76 -0
  131. package/dist/tools/core-tools.js +1005 -0
  132. package/dist/worker-supervisor.js +15 -0
  133. package/package.json +6 -1
  134. package/dashboard/dist/assets/0tOC3wSN.js +0 -214
  135. package/dashboard/dist/assets/Bm8QnMJ_.js +0 -1
  136. package/dashboard/dist/assets/CyxZio4Y.js +0 -1
  137. package/dashboard/dist/assets/DaAIOik3.css +0 -1
@@ -0,0 +1 @@
1
+ export declare function parseJsonSafe<T>(value: string): T | null;
@@ -0,0 +1,8 @@
1
+ export function parseJsonSafe(value) {
2
+ try {
3
+ return JSON.parse(value);
4
+ }
5
+ catch {
6
+ return null;
7
+ }
8
+ }
@@ -580,6 +580,10 @@ function turnToActivity(turn, session, cachedSummary, index) {
580
580
  description: modelAlias,
581
581
  agentId: session.agentId,
582
582
  agentName: session.agentName,
583
+ requesterAgentId: session.agentId ?? null,
584
+ requesterAgentName: session.agentName ?? null,
585
+ executorAgentId: session.agentId ?? null,
586
+ executorAgentName: session.agentName ?? null,
583
587
  runId: session.sessionId ?? session.key,
584
588
  initiativeId: session.agentId ? `agent:${session.agentId}` : null,
585
589
  timestamp: turn.timestamp,
@@ -747,6 +751,10 @@ function makeSessionSummaryItem(session) {
747
751
  description: modelAlias ? `Local session (${modelAlias})` : "Local session",
748
752
  agentId: session.agentId,
749
753
  agentName: session.agentName,
754
+ requesterAgentId: session.agentId ?? null,
755
+ requesterAgentName: session.agentName ?? null,
756
+ executorAgentId: session.agentId ?? null,
757
+ executorAgentName: session.agentName ?? null,
750
758
  runId: session.sessionId ?? session.key,
751
759
  initiativeId: session.agentId ? `agent:${session.agentId}` : null,
752
760
  timestamp: session.updatedAt ?? new Date().toISOString(),
@@ -5,22 +5,6 @@ import { randomUUID } from "node:crypto";
5
5
  import { writeFileAtomicSync, writeJsonFileAtomicSync } from "./fs-utils.js";
6
6
  const ORGX_LOCAL_MCP_KEY = "orgx-openclaw";
7
7
  const ORGX_HOSTED_MCP_URL = "https://mcp.useorgx.com/mcp";
8
- const ORGX_LOCAL_MCP_SCOPES = [
9
- "engineering",
10
- "product",
11
- "design",
12
- "marketing",
13
- "sales",
14
- "operations",
15
- "orchestration",
16
- ];
17
- function scopedMcpServerKey(scope) {
18
- return `${ORGX_LOCAL_MCP_KEY}-${scope}`;
19
- }
20
- function scopedMcpUrl(localMcpUrl, scope) {
21
- const base = localMcpUrl.replace(/\/+$/, "");
22
- return `${base}/${scope}`;
23
- }
24
8
  function escapeRegExp(value) {
25
9
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
26
10
  }
@@ -59,6 +43,20 @@ function backupFileSync(path, mode) {
59
43
  return null;
60
44
  }
61
45
  }
46
+ function removeLegacyScopedMcpServers(servers) {
47
+ const next = { ...servers };
48
+ let updated = false;
49
+ const scopedPrefix = `${ORGX_LOCAL_MCP_KEY}-`;
50
+ for (const key of Object.keys(next)) {
51
+ if (key === ORGX_LOCAL_MCP_KEY)
52
+ continue;
53
+ if (!key.startsWith(scopedPrefix))
54
+ continue;
55
+ delete next[key];
56
+ updated = true;
57
+ }
58
+ return { updated, next };
59
+ }
62
60
  export function patchClaudeMcpConfig(input) {
63
61
  const currentServers = isRecord(input.current.mcpServers) ? input.current.mcpServers : {};
64
62
  const existingOrgx = isRecord(currentServers.orgx) ? currentServers.orgx : {};
@@ -88,41 +86,20 @@ export function patchClaudeMcpConfig(input) {
88
86
  ? existing.description
89
87
  : "OrgX platform via local OpenClaw plugin (no OAuth)",
90
88
  };
91
- const updatedScopes = [];
92
- const scopedEntries = {};
93
- for (const scope of ORGX_LOCAL_MCP_SCOPES) {
94
- const key = scopedMcpServerKey(scope);
95
- const expectedUrl = scopedMcpUrl(input.localMcpUrl, scope);
96
- const existingScoped = isRecord(currentServers[key]) ? currentServers[key] : {};
97
- const priorUrl = typeof existingScoped.url === "string" ? existingScoped.url : "";
98
- const priorType = typeof existingScoped.type === "string" ? existingScoped.type : "";
99
- const nextScoped = {
100
- ...existingScoped,
101
- type: "http",
102
- url: expectedUrl,
103
- description: typeof existingScoped.description === "string" && existingScoped.description.trim().length > 0
104
- ? existingScoped.description
105
- : `OrgX platform via local OpenClaw plugin (${scope} scope)`,
106
- };
107
- scopedEntries[key] = nextScoped;
108
- if (priorUrl !== expectedUrl || priorType !== "http") {
109
- updatedScopes.push(scope);
110
- }
111
- }
112
- const nextServers = {
89
+ const mergedServers = {
113
90
  ...currentServers,
114
91
  ...(shouldSetHostedOrgx ? { orgx: nextOrgxEntry } : {}),
115
92
  [ORGX_LOCAL_MCP_KEY]: nextEntry,
116
- ...scopedEntries,
117
93
  };
94
+ const scopedCleanup = removeLegacyScopedMcpServers(mergedServers);
118
95
  const next = {
119
96
  ...input.current,
120
- mcpServers: nextServers,
97
+ mcpServers: scopedCleanup.next,
121
98
  };
122
99
  const updatedLocal = priorUrl !== input.localMcpUrl || priorType !== "http";
123
100
  const updatedHosted = shouldSetHostedOrgx &&
124
101
  (existingOrgxUrl !== ORGX_HOSTED_MCP_URL || existingOrgxType !== "http");
125
- const updated = updatedLocal || updatedHosted || updatedScopes.length > 0;
102
+ const updated = updatedLocal || updatedHosted || scopedCleanup.updated;
126
103
  return { updated, next };
127
104
  }
128
105
  export function patchCursorMcpConfig(input) {
@@ -133,30 +110,16 @@ export function patchCursorMcpConfig(input) {
133
110
  ...existing,
134
111
  url: input.localMcpUrl,
135
112
  };
136
- const scopedEntries = {};
137
- let updatedScopes = false;
138
- for (const scope of ORGX_LOCAL_MCP_SCOPES) {
139
- const key = scopedMcpServerKey(scope);
140
- const expectedUrl = scopedMcpUrl(input.localMcpUrl, scope);
141
- const existingScoped = isRecord(currentServers[key]) ? currentServers[key] : {};
142
- const priorScopedUrl = typeof existingScoped.url === "string" ? existingScoped.url : "";
143
- scopedEntries[key] = {
144
- ...existingScoped,
145
- url: expectedUrl,
146
- };
147
- if (priorScopedUrl !== expectedUrl)
148
- updatedScopes = true;
149
- }
150
- const nextServers = {
113
+ const mergedServers = {
151
114
  ...currentServers,
152
115
  [ORGX_LOCAL_MCP_KEY]: nextEntry,
153
- ...scopedEntries,
154
116
  };
117
+ const scopedCleanup = removeLegacyScopedMcpServers(mergedServers);
155
118
  const next = {
156
119
  ...input.current,
157
- mcpServers: nextServers,
120
+ mcpServers: scopedCleanup.next,
158
121
  };
159
- const updated = priorUrl !== input.localMcpUrl || updatedScopes;
122
+ const updated = priorUrl !== input.localMcpUrl || scopedCleanup.updated;
160
123
  return { updated, next };
161
124
  }
162
125
  function upsertCodexMcpServerSection(input) {
@@ -173,7 +136,9 @@ function upsertCodexMcpServerSection(input) {
173
136
  }
174
137
  const urlLine = `url = "${input.url}"`;
175
138
  if (headerIndex === -1) {
176
- const suffix = ["", `[mcp_servers.\"${input.key}\"]`, urlLine, ""].join("\n");
139
+ const needsQuote = /[^A-Za-z0-9_]/.test(input.key);
140
+ const keyLiteral = needsQuote ? `"${input.key}"` : input.key;
141
+ const suffix = ["", `[mcp_servers.${keyLiteral}]`, urlLine, ""].join("\n");
177
142
  const normalized = currentText.endsWith("\n") ? currentText : `${currentText}\n`;
178
143
  return { updated: true, next: `${normalized}${suffix}` };
179
144
  }
@@ -221,29 +186,52 @@ function upsertCodexMcpServerSection(input) {
221
186
  }
222
187
  return { updated, next: `${lines.join("\n")}\n` };
223
188
  }
224
- export function patchCodexConfigToml(input) {
225
- let current = input.current;
226
- let updatedHosted = false;
227
- // If the hosted OrgX entry is missing entirely, add a sensible default. This is
228
- // a no-op if the user already has `orgx` pointed at staging or another URL.
229
- const hostedHeaderRegex = /^\[mcp_servers\.(?:"orgx"|orgx)\]\s*$/;
230
- {
231
- const lines = current.split(/\r?\n/);
232
- const hasHosted = lines.some((line) => hostedHeaderRegex.test(line.trim()));
233
- if (!hasHosted) {
234
- const hostedUrlLine = `url = "${ORGX_HOSTED_MCP_URL}"`;
235
- const suffix = [
236
- ...(current.trim().length === 0 ? [] : [""]),
237
- "[mcp_servers.orgx]",
238
- hostedUrlLine,
239
- "",
240
- ].join("\n");
241
- const normalized = current.endsWith("\n") ? current : `${current}\n`;
242
- current = `${normalized}${suffix}`;
243
- updatedHosted = true;
189
+ function removeCodexLegacyScopedMcpSections(input) {
190
+ const lines = input.current.split(/\r?\n/);
191
+ const scopedPrefix = `${input.baseKey}-`;
192
+ let updated = false;
193
+ let index = 0;
194
+ while (index < lines.length) {
195
+ const trimmed = lines[index].trim();
196
+ const headerMatch = trimmed.match(/^\[mcp_servers\.(?:"([^"]+)"|([A-Za-z0-9_]+))\]\s*$/);
197
+ if (!headerMatch) {
198
+ index += 1;
199
+ continue;
200
+ }
201
+ const key = (headerMatch[1] ?? headerMatch[2] ?? "").trim();
202
+ const isLegacyScoped = key !== input.baseKey &&
203
+ key.startsWith(scopedPrefix);
204
+ if (!isLegacyScoped) {
205
+ index += 1;
206
+ continue;
244
207
  }
208
+ let sectionEnd = lines.length;
209
+ for (let i = index + 1; i < lines.length; i += 1) {
210
+ if (lines[i].trim().startsWith("[")) {
211
+ sectionEnd = i;
212
+ break;
213
+ }
214
+ }
215
+ const start = index > 0 && lines[index - 1].trim() === "" ? index - 1 : index;
216
+ lines.splice(start, sectionEnd - start);
217
+ updated = true;
218
+ index = Math.max(0, start);
245
219
  }
220
+ return { updated, next: `${lines.join("\n")}\n` };
221
+ }
222
+ export function patchCodexConfigToml(input) {
223
+ let current = input.current;
246
224
  let updated = false;
225
+ // Ensure the hosted OrgX entry uses a direct `url` (streamable HTTP) so that
226
+ // `codex mcp login orgx` can perform OAuth. Route through upsertCodexMcpServerSection
227
+ // so stale stdio-transport fields (command/args) are stripped automatically.
228
+ const hosted = upsertCodexMcpServerSection({
229
+ current,
230
+ key: "orgx",
231
+ url: ORGX_HOSTED_MCP_URL,
232
+ });
233
+ updated = updated || hosted.updated;
234
+ current = hosted.next;
247
235
  const base = upsertCodexMcpServerSection({
248
236
  current,
249
237
  key: ORGX_LOCAL_MCP_KEY,
@@ -251,16 +239,13 @@ export function patchCodexConfigToml(input) {
251
239
  });
252
240
  updated = updated || base.updated;
253
241
  current = base.next;
254
- for (const scope of ORGX_LOCAL_MCP_SCOPES) {
255
- const next = upsertCodexMcpServerSection({
256
- current,
257
- key: scopedMcpServerKey(scope),
258
- url: scopedMcpUrl(input.localMcpUrl, scope),
259
- });
260
- updated = updated || next.updated;
261
- current = next.next;
262
- }
263
- return { updated: updatedHosted || updated, next: current };
242
+ const removedScoped = removeCodexLegacyScopedMcpSections({
243
+ current,
244
+ baseKey: ORGX_LOCAL_MCP_KEY,
245
+ });
246
+ updated = updated || removedScoped.updated;
247
+ current = removedScoped.next;
248
+ return { updated, next: current };
264
249
  }
265
250
  export async function autoConfigureDetectedMcpClients(input) {
266
251
  const logger = input.logger ?? {};
@@ -1,6 +1,7 @@
1
- import { chmodSync, existsSync, mkdirSync, readFileSync, } from "node:fs";
1
+ import { existsSync, readFileSync, } from "node:fs";
2
2
  import { getOrgxPluginConfigDir, getOrgxPluginConfigPath } from "./paths.js";
3
3
  import { backupCorruptFileSync, writeJsonFileAtomicSync } from "./fs-utils.js";
4
+ import { ensureStoreDirSync, parseJsonSafe, } from "./stores/json-store.js";
4
5
  const MAX_PINS = 240;
5
6
  function storeDir() {
6
7
  return getOrgxPluginConfigDir();
@@ -9,22 +10,7 @@ function storeFile() {
9
10
  return getOrgxPluginConfigPath("next-up-queue.json");
10
11
  }
11
12
  function ensureStoreDir() {
12
- const dir = storeDir();
13
- mkdirSync(dir, { recursive: true, mode: 0o700 });
14
- try {
15
- chmodSync(dir, 0o700);
16
- }
17
- catch {
18
- // best effort
19
- }
20
- }
21
- function parseJson(value) {
22
- try {
23
- return JSON.parse(value);
24
- }
25
- catch {
26
- return null;
27
- }
13
+ ensureStoreDirSync(storeDir());
28
14
  }
29
15
  function normalizeNullableString(value) {
30
16
  if (typeof value !== "string")
@@ -49,7 +35,7 @@ export function readNextUpQueuePins() {
49
35
  return { version: 1, updatedAt: new Date().toISOString(), pins: [] };
50
36
  }
51
37
  const raw = readFileSync(file, "utf8");
52
- const parsed = parseJson(raw);
38
+ const parsed = parseJsonSafe(raw);
53
39
  if (!parsed || typeof parsed !== "object") {
54
40
  backupCorruptFileSync(file);
55
41
  return { version: 1, updatedAt: new Date().toISOString(), pins: [] };
@@ -1,7 +1,8 @@
1
- import { chmodSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from "node:fs";
1
+ import { existsSync, readFileSync, writeFileSync, } from "node:fs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { getOrgxPluginConfigDir, getOrgxPluginConfigPath } from "./paths.js";
4
4
  import { backupCorruptFileSync, writeJsonFileAtomicSync } from "./fs-utils.js";
5
+ import { clearStoreFileSync, ensureStoreDirSync, parseJsonSafe, } from "./stores/json-store.js";
5
6
  const MAX_INSTANCES = 600;
6
7
  export const DEFAULT_RUNTIME_HEARTBEAT_TIMEOUT_MS = 90_000;
7
8
  function runtimeDir() {
@@ -14,33 +15,12 @@ function hookTokenFile() {
14
15
  return getOrgxPluginConfigPath("runtime-hook-token.txt");
15
16
  }
16
17
  function ensureRuntimeDir() {
17
- const dir = runtimeDir();
18
- mkdirSync(dir, { recursive: true, mode: 0o700 });
19
- try {
20
- chmodSync(dir, 0o700);
21
- }
22
- catch {
23
- // best effort
24
- }
18
+ ensureStoreDirSync(runtimeDir());
25
19
  }
26
20
  function writeHookTokenFile(token) {
27
21
  ensureRuntimeDir();
28
22
  const file = hookTokenFile();
29
23
  writeFileSync(file, `${token}\n`, { encoding: "utf8", mode: 0o600 });
30
- try {
31
- chmodSync(file, 0o600);
32
- }
33
- catch {
34
- // best effort
35
- }
36
- }
37
- function parseJson(value) {
38
- try {
39
- return JSON.parse(value);
40
- }
41
- catch {
42
- return null;
43
- }
44
24
  }
45
25
  function normalizeNullableString(value) {
46
26
  if (typeof value !== "string")
@@ -85,7 +65,7 @@ function normalizeHookEvent(value) {
85
65
  }
86
66
  function toProviderLogo(sourceClient) {
87
67
  if (sourceClient === "codex")
88
- return "codex";
68
+ return "openai";
89
69
  if (sourceClient === "claude-code")
90
70
  return "anthropic";
91
71
  if (sourceClient === "openclaw")
@@ -97,9 +77,9 @@ function toProviderLogo(sourceClient) {
97
77
  function normalizeProviderLogo(value, sourceClient) {
98
78
  const normalized = normalizeNullableString(value)?.toLowerCase();
99
79
  if (normalized === "codex")
100
- return "codex";
80
+ return sourceClient === "codex" ? "openai" : "codex";
101
81
  if (normalized === "openai")
102
- return sourceClient === "codex" ? "codex" : "openai";
82
+ return "openai";
103
83
  if (normalized === "anthropic")
104
84
  return "anthropic";
105
85
  if (normalized === "openclaw")
@@ -201,7 +181,7 @@ export function readRuntimeInstances() {
201
181
  return { updatedAt: new Date().toISOString(), instances: {} };
202
182
  }
203
183
  const raw = readFileSync(file, "utf8");
204
- const parsed = parseJson(raw);
184
+ const parsed = parseJsonSafe(raw);
205
185
  if (!parsed || typeof parsed !== "object") {
206
186
  backupCorruptFileSync(file);
207
187
  return { updatedAt: new Date().toISOString(), instances: {} };
@@ -349,13 +329,7 @@ export function listRuntimeInstances(options) {
349
329
  .slice(0, limit);
350
330
  }
351
331
  export function clearRuntimeInstances() {
352
- const file = runtimeFile();
353
- try {
354
- rmSync(file, { force: true });
355
- }
356
- catch {
357
- // best effort
358
- }
332
+ clearStoreFileSync(runtimeFile());
359
333
  }
360
334
  export function resolveRuntimeHookToken() {
361
335
  const envToken = normalizeNullableString(process.env.ORGX_HOOK_TOKEN);
@@ -0,0 +1,23 @@
1
+ export interface RegisterServiceApi {
2
+ registerService: (service: {
3
+ id: string;
4
+ start: () => Promise<void>;
5
+ stop: () => Promise<void>;
6
+ }) => void;
7
+ log?: {
8
+ info?: (msg: string, meta?: Record<string, unknown>) => void;
9
+ };
10
+ }
11
+ export interface RegisterSyncServiceDeps {
12
+ api: RegisterServiceApi;
13
+ syncIntervalMs: number;
14
+ ensureGatewayWatchdog: (logger: Record<string, unknown>) => {
15
+ started: boolean;
16
+ pid?: number | null;
17
+ };
18
+ doSync: () => Promise<void>;
19
+ scheduleNextSync: () => void;
20
+ setSyncServiceRunning: (running: boolean) => void;
21
+ clearSyncTimer: () => void;
22
+ }
23
+ export declare function registerSyncService(deps: RegisterSyncServiceDeps): void;
@@ -0,0 +1,23 @@
1
+ export function registerSyncService(deps) {
2
+ deps.api.registerService({
3
+ id: "orgx-sync",
4
+ start: async () => {
5
+ deps.setSyncServiceRunning(true);
6
+ const watchdog = deps.ensureGatewayWatchdog((deps.api.log ?? {}));
7
+ if (watchdog.started) {
8
+ deps.api.log?.info?.("[orgx] Gateway watchdog started", {
9
+ pid: watchdog.pid,
10
+ });
11
+ }
12
+ deps.api.log?.info?.("[orgx] Starting sync service", {
13
+ interval: deps.syncIntervalMs,
14
+ });
15
+ await deps.doSync();
16
+ deps.scheduleNextSync();
17
+ },
18
+ stop: async () => {
19
+ deps.setSyncServiceRunning(false);
20
+ deps.clearSyncTimer();
21
+ },
22
+ });
23
+ }
@@ -0,0 +1,29 @@
1
+ type ToolLike = {
2
+ name: string;
3
+ description: string;
4
+ parameters: Record<string, unknown>;
5
+ execute: (callId: string, params?: unknown) => Promise<{
6
+ content: Array<{
7
+ type: "text";
8
+ text: string;
9
+ }>;
10
+ }>;
11
+ };
12
+ type ServiceLike = {
13
+ id: string;
14
+ start: () => Promise<void>;
15
+ stop: () => Promise<void>;
16
+ };
17
+ type ApiLike = {
18
+ registerTool: (tool: ToolLike, options?: {
19
+ optional?: boolean;
20
+ }) => void;
21
+ registerService: (service: ServiceLike) => void;
22
+ };
23
+ export declare function instrumentPluginApi(input: {
24
+ api: ApiLike;
25
+ installationId: string;
26
+ pluginVersion: string;
27
+ toErrorMessage: (err: unknown) => string;
28
+ }): void;
29
+ export {};
@@ -0,0 +1,136 @@
1
+ import { posthogCapture } from "../telemetry/posthog.js";
2
+ export function instrumentPluginApi(input) {
3
+ const registerTool = input.api.registerTool.bind(input.api);
4
+ input.api.registerTool = (tool, options) => {
5
+ const toolName = tool.name;
6
+ const optional = Boolean(options?.optional);
7
+ registerTool({
8
+ ...tool,
9
+ execute: async (callId, params) => {
10
+ const startedAt = Date.now();
11
+ void posthogCapture({
12
+ event: "openclaw_tool_called",
13
+ distinctId: input.installationId,
14
+ properties: {
15
+ tool_name: toolName,
16
+ tool_optional: optional,
17
+ call_id: callId,
18
+ plugin_version: input.pluginVersion,
19
+ },
20
+ }).catch(() => {
21
+ // best effort
22
+ });
23
+ try {
24
+ const result = await tool.execute(callId, params);
25
+ const durationMs = Date.now() - startedAt;
26
+ void posthogCapture({
27
+ event: "openclaw_tool_succeeded",
28
+ distinctId: input.installationId,
29
+ properties: {
30
+ tool_name: toolName,
31
+ tool_optional: optional,
32
+ call_id: callId,
33
+ duration_ms: durationMs,
34
+ plugin_version: input.pluginVersion,
35
+ },
36
+ }).catch(() => {
37
+ // best effort
38
+ });
39
+ return result;
40
+ }
41
+ catch (err) {
42
+ const durationMs = Date.now() - startedAt;
43
+ void posthogCapture({
44
+ event: "openclaw_tool_failed",
45
+ distinctId: input.installationId,
46
+ properties: {
47
+ tool_name: toolName,
48
+ tool_optional: optional,
49
+ call_id: callId,
50
+ duration_ms: durationMs,
51
+ plugin_version: input.pluginVersion,
52
+ error: input.toErrorMessage(err),
53
+ },
54
+ }).catch(() => {
55
+ // best effort
56
+ });
57
+ throw err;
58
+ }
59
+ },
60
+ }, options);
61
+ };
62
+ const registerService = input.api.registerService.bind(input.api);
63
+ input.api.registerService = (service) => {
64
+ registerService({
65
+ ...service,
66
+ start: async () => {
67
+ const startedAt = Date.now();
68
+ try {
69
+ await service.start();
70
+ const durationMs = Date.now() - startedAt;
71
+ void posthogCapture({
72
+ event: "openclaw_service_started",
73
+ distinctId: input.installationId,
74
+ properties: {
75
+ service_id: service.id,
76
+ duration_ms: durationMs,
77
+ plugin_version: input.pluginVersion,
78
+ },
79
+ }).catch(() => {
80
+ // best effort
81
+ });
82
+ }
83
+ catch (err) {
84
+ const durationMs = Date.now() - startedAt;
85
+ void posthogCapture({
86
+ event: "openclaw_service_start_failed",
87
+ distinctId: input.installationId,
88
+ properties: {
89
+ service_id: service.id,
90
+ duration_ms: durationMs,
91
+ plugin_version: input.pluginVersion,
92
+ error: input.toErrorMessage(err),
93
+ },
94
+ }).catch(() => {
95
+ // best effort
96
+ });
97
+ throw err;
98
+ }
99
+ },
100
+ stop: async () => {
101
+ const startedAt = Date.now();
102
+ try {
103
+ await service.stop();
104
+ const durationMs = Date.now() - startedAt;
105
+ void posthogCapture({
106
+ event: "openclaw_service_stopped",
107
+ distinctId: input.installationId,
108
+ properties: {
109
+ service_id: service.id,
110
+ duration_ms: durationMs,
111
+ plugin_version: input.pluginVersion,
112
+ },
113
+ }).catch(() => {
114
+ // best effort
115
+ });
116
+ }
117
+ catch (err) {
118
+ const durationMs = Date.now() - startedAt;
119
+ void posthogCapture({
120
+ event: "openclaw_service_stop_failed",
121
+ distinctId: input.installationId,
122
+ properties: {
123
+ service_id: service.id,
124
+ duration_ms: durationMs,
125
+ plugin_version: input.pluginVersion,
126
+ error: input.toErrorMessage(err),
127
+ },
128
+ }).catch(() => {
129
+ // best effort
130
+ });
131
+ throw err;
132
+ }
133
+ },
134
+ });
135
+ };
136
+ }
@@ -1,6 +1,7 @@
1
- import { mkdirSync, readFileSync, chmodSync, existsSync, rmSync } from 'node:fs';
1
+ import { existsSync, readFileSync } from "node:fs";
2
2
  import { getOrgxPluginConfigDir, getOrgxPluginConfigPath } from './paths.js';
3
3
  import { backupCorruptFileSync, writeJsonFileAtomicSync } from './fs-utils.js';
4
+ import { clearStoreFileSync, ensureStoreDirSync, parseJsonSafe, } from "./stores/json-store.js";
4
5
  function snapshotDir() {
5
6
  return getOrgxPluginConfigDir();
6
7
  }
@@ -8,22 +9,7 @@ function snapshotFile() {
8
9
  return getOrgxPluginConfigPath('snapshot.json');
9
10
  }
10
11
  function ensureSnapshotDir() {
11
- const dir = snapshotDir();
12
- mkdirSync(dir, { recursive: true, mode: 0o700 });
13
- try {
14
- chmodSync(dir, 0o700);
15
- }
16
- catch {
17
- // best effort
18
- }
19
- }
20
- function parseJson(value) {
21
- try {
22
- return JSON.parse(value);
23
- }
24
- catch {
25
- return null;
26
- }
12
+ ensureStoreDirSync(snapshotDir());
27
13
  }
28
14
  export function readPersistedSnapshot() {
29
15
  const file = snapshotFile();
@@ -31,7 +17,7 @@ export function readPersistedSnapshot() {
31
17
  if (!existsSync(file))
32
18
  return null;
33
19
  const raw = readFileSync(file, 'utf8');
34
- const parsed = parseJson(raw);
20
+ const parsed = parseJsonSafe(raw);
35
21
  if (!parsed) {
36
22
  backupCorruptFileSync(file);
37
23
  return null;
@@ -57,11 +43,5 @@ export function writePersistedSnapshot(snapshot) {
57
43
  return record;
58
44
  }
59
45
  export function clearPersistedSnapshot() {
60
- const file = snapshotFile();
61
- try {
62
- rmSync(file, { force: true });
63
- }
64
- catch {
65
- // best effort
66
- }
46
+ clearStoreFileSync(snapshotFile());
67
47
  }
@@ -0,0 +1,11 @@
1
+ import { parseJsonSafe } from "../json-utils.js";
2
+ export declare function ensureStoreDirSync(dir: string): void;
3
+ export { parseJsonSafe };
4
+ export declare function readJsonFileOrDefault<T>(input: {
5
+ file: string;
6
+ fallback: () => T;
7
+ isValid?: (value: unknown) => value is T;
8
+ backupOnInvalid?: boolean;
9
+ }): T;
10
+ export declare function writeJsonStoreFileSync(file: string, data: unknown, mode?: number): void;
11
+ export declare function clearStoreFileSync(file: string): void;