@useorgx/openclaw-plugin 0.4.4 → 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.
Files changed (51) hide show
  1. package/README.md +85 -2
  2. package/dashboard/dist/assets/0tOC3wSN.js +214 -0
  3. package/dashboard/dist/assets/Bm8QnMJ_.js +1 -0
  4. package/dashboard/dist/assets/CpJsfbXo.js +9 -0
  5. package/dashboard/dist/assets/CyxZio4Y.js +1 -0
  6. package/dashboard/dist/assets/DaAIOik3.css +1 -0
  7. package/dashboard/dist/index.html +3 -3
  8. package/dist/activity-store.d.ts +28 -0
  9. package/dist/activity-store.js +250 -0
  10. package/dist/agent-context-store.d.ts +19 -0
  11. package/dist/agent-context-store.js +60 -3
  12. package/dist/agent-suite.d.ts +83 -0
  13. package/dist/agent-suite.js +615 -0
  14. package/dist/contracts/client.d.ts +22 -1
  15. package/dist/contracts/client.js +120 -3
  16. package/dist/contracts/types.d.ts +190 -1
  17. package/dist/entity-comment-store.d.ts +29 -0
  18. package/dist/entity-comment-store.js +190 -0
  19. package/dist/hooks/post-reporting-event.mjs +326 -0
  20. package/dist/http-handler.d.ts +7 -1
  21. package/dist/http-handler.js +3619 -585
  22. package/dist/index.js +1039 -80
  23. package/dist/mcp-client-setup.d.ts +30 -0
  24. package/dist/mcp-client-setup.js +347 -0
  25. package/dist/mcp-http-handler.d.ts +55 -0
  26. package/dist/mcp-http-handler.js +395 -0
  27. package/dist/next-up-queue-store.d.ts +31 -0
  28. package/dist/next-up-queue-store.js +169 -0
  29. package/dist/openclaw.plugin.json +1 -1
  30. package/dist/outbox.d.ts +1 -1
  31. package/dist/runtime-instance-store.d.ts +1 -1
  32. package/dist/runtime-instance-store.js +20 -3
  33. package/dist/skill-pack-state.d.ts +69 -0
  34. package/dist/skill-pack-state.js +232 -0
  35. package/dist/worker-supervisor.d.ts +25 -0
  36. package/dist/worker-supervisor.js +62 -0
  37. package/openclaw.plugin.json +1 -1
  38. package/package.json +10 -1
  39. package/skills/orgx-design-agent/SKILL.md +38 -0
  40. package/skills/orgx-engineering-agent/SKILL.md +55 -0
  41. package/skills/orgx-marketing-agent/SKILL.md +40 -0
  42. package/skills/orgx-operations-agent/SKILL.md +40 -0
  43. package/skills/orgx-orchestrator-agent/SKILL.md +45 -0
  44. package/skills/orgx-product-agent/SKILL.md +39 -0
  45. package/skills/orgx-sales-agent/SKILL.md +40 -0
  46. package/skills/ship/SKILL.md +63 -0
  47. package/dashboard/dist/assets/4hvaB0UC.js +0 -9
  48. package/dashboard/dist/assets/BgsfM2lz.js +0 -1
  49. package/dashboard/dist/assets/DCBlK4MX.js +0 -212
  50. package/dashboard/dist/assets/DEuY_RBN.js +0 -1
  51. package/dashboard/dist/assets/jyFhCND-.css +0 -1
@@ -0,0 +1,395 @@
1
+ import { randomUUID } from "node:crypto";
2
+ const DEFAULT_PROTOCOL_VERSION = "2024-11-05";
3
+ // Domain-scoped MCP servers are meant to be "default safe". The unscoped
4
+ // `/orgx/mcp` endpoint remains available for power users / debugging.
5
+ //
6
+ // NOTE: This scopes only the tools exposed by this plugin (OrgX reporting + mutation).
7
+ // It cannot restrict OpenClaw-native tools (filesystem, shell, etc).
8
+ const ORGX_MCP_ALLOWED_TOOLS_BY_SCOPE = {
9
+ engineering: [
10
+ "orgx_status",
11
+ "orgx_sync",
12
+ "orgx_emit_activity",
13
+ "orgx_report_progress",
14
+ "orgx_register_artifact",
15
+ "orgx_request_decision",
16
+ "orgx_spawn_check",
17
+ ],
18
+ product: [
19
+ "orgx_status",
20
+ "orgx_sync",
21
+ "orgx_emit_activity",
22
+ "orgx_report_progress",
23
+ "orgx_register_artifact",
24
+ "orgx_request_decision",
25
+ "orgx_spawn_check",
26
+ ],
27
+ design: [
28
+ "orgx_status",
29
+ "orgx_sync",
30
+ "orgx_emit_activity",
31
+ "orgx_report_progress",
32
+ "orgx_register_artifact",
33
+ "orgx_request_decision",
34
+ "orgx_spawn_check",
35
+ ],
36
+ marketing: [
37
+ "orgx_status",
38
+ "orgx_sync",
39
+ "orgx_emit_activity",
40
+ "orgx_report_progress",
41
+ "orgx_register_artifact",
42
+ "orgx_request_decision",
43
+ "orgx_spawn_check",
44
+ ],
45
+ sales: [
46
+ "orgx_status",
47
+ "orgx_sync",
48
+ "orgx_emit_activity",
49
+ "orgx_report_progress",
50
+ "orgx_register_artifact",
51
+ "orgx_request_decision",
52
+ "orgx_spawn_check",
53
+ ],
54
+ operations: [
55
+ "orgx_status",
56
+ "orgx_sync",
57
+ "orgx_emit_activity",
58
+ "orgx_report_progress",
59
+ "orgx_register_artifact",
60
+ "orgx_request_decision",
61
+ "orgx_spawn_check",
62
+ // Operations is allowed to do explicit changesets for remediation/runbooks.
63
+ "orgx_apply_changeset",
64
+ ],
65
+ orchestration: [
66
+ "orgx_status",
67
+ "orgx_sync",
68
+ "orgx_emit_activity",
69
+ "orgx_report_progress",
70
+ "orgx_register_artifact",
71
+ "orgx_request_decision",
72
+ "orgx_spawn_check",
73
+ // Orchestrator is the primary mutation surface by design.
74
+ "orgx_apply_changeset",
75
+ ],
76
+ };
77
+ function isRecord(value) {
78
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
79
+ }
80
+ function sendJson(res, status, payload) {
81
+ res.writeHead(status, {
82
+ "content-type": "application/json; charset=utf-8",
83
+ "cache-control": "no-store",
84
+ });
85
+ res.end(JSON.stringify(payload));
86
+ }
87
+ function sendText(res, status, body) {
88
+ res.writeHead(status, {
89
+ "content-type": "text/plain; charset=utf-8",
90
+ "cache-control": "no-store",
91
+ });
92
+ res.end(body);
93
+ }
94
+ function jsonRpcError(id, code, message) {
95
+ return {
96
+ jsonrpc: "2.0",
97
+ id,
98
+ error: {
99
+ code,
100
+ message,
101
+ },
102
+ };
103
+ }
104
+ function jsonRpcResult(id, result) {
105
+ return {
106
+ jsonrpc: "2.0",
107
+ id,
108
+ result,
109
+ };
110
+ }
111
+ function normalizePath(rawUrl) {
112
+ const [path] = rawUrl.split("?", 2);
113
+ return path || "/";
114
+ }
115
+ function parseScopeKey(url) {
116
+ // Supported paths:
117
+ // - /orgx/mcp (unscoped)
118
+ // - /orgx/mcp/<scope> (domain-scoped)
119
+ if (!url.startsWith("/orgx/mcp/"))
120
+ return null;
121
+ const rest = url.slice("/orgx/mcp/".length);
122
+ const key = rest.split("/", 1)[0]?.trim() ?? "";
123
+ if (!key)
124
+ return null;
125
+ if (!Object.prototype.hasOwnProperty.call(ORGX_MCP_ALLOWED_TOOLS_BY_SCOPE, key))
126
+ return null;
127
+ return key;
128
+ }
129
+ function resolveToolScope(scopeKey) {
130
+ if (!scopeKey)
131
+ return null;
132
+ return {
133
+ key: scopeKey,
134
+ allowedTools: new Set(ORGX_MCP_ALLOWED_TOOLS_BY_SCOPE[scopeKey]),
135
+ };
136
+ }
137
+ async function readRequestBodyBuffer(req) {
138
+ const body = req.body;
139
+ if (typeof body === "string")
140
+ return Buffer.from(body, "utf8");
141
+ if (Buffer.isBuffer(body))
142
+ return body;
143
+ if (body instanceof Uint8Array)
144
+ return Buffer.from(body);
145
+ if (body instanceof ArrayBuffer)
146
+ return Buffer.from(body);
147
+ if (body && typeof body === "object" && !Buffer.isBuffer(body)) {
148
+ try {
149
+ return Buffer.from(JSON.stringify(body), "utf8");
150
+ }
151
+ catch {
152
+ return Buffer.from("", "utf8");
153
+ }
154
+ }
155
+ if (typeof req.on !== "function")
156
+ return Buffer.from("", "utf8");
157
+ return await new Promise((resolve) => {
158
+ const chunks = [];
159
+ const onData = (chunk) => {
160
+ if (typeof chunk === "string") {
161
+ chunks.push(Buffer.from(chunk, "utf8"));
162
+ }
163
+ else if (Buffer.isBuffer(chunk)) {
164
+ chunks.push(chunk);
165
+ }
166
+ else if (chunk instanceof Uint8Array) {
167
+ chunks.push(Buffer.from(chunk));
168
+ }
169
+ };
170
+ const onEnd = () => resolve(Buffer.concat(chunks));
171
+ const onError = () => resolve(Buffer.concat(chunks));
172
+ req.on?.("data", onData);
173
+ req.on?.("end", onEnd);
174
+ req.on?.("error", onError);
175
+ });
176
+ }
177
+ async function parseJsonBody(req) {
178
+ const buffer = await readRequestBodyBuffer(req);
179
+ if (!buffer || buffer.length === 0)
180
+ return null;
181
+ try {
182
+ return JSON.parse(buffer.toString("utf8"));
183
+ }
184
+ catch {
185
+ return null;
186
+ }
187
+ }
188
+ function pickId(value) {
189
+ if (typeof value === "string")
190
+ return value;
191
+ if (typeof value === "number" && Number.isFinite(value))
192
+ return value;
193
+ if (value === null)
194
+ return null;
195
+ return null;
196
+ }
197
+ function normalizeToolArguments(value) {
198
+ if (isRecord(value))
199
+ return value;
200
+ return {};
201
+ }
202
+ function buildToolsList(tools) {
203
+ const entries = Array.from(tools.values())
204
+ .map((tool) => ({
205
+ name: tool.name,
206
+ description: tool.description,
207
+ inputSchema: tool.parameters,
208
+ }))
209
+ .sort((a, b) => String(a.name).localeCompare(String(b.name)));
210
+ return entries;
211
+ }
212
+ async function handleRpcMessage(input) {
213
+ const msg = input.message;
214
+ if (!isRecord(msg)) {
215
+ return jsonRpcError(null, -32600, "Invalid Request");
216
+ }
217
+ const id = pickId(msg.id);
218
+ const method = typeof msg.method === "string" ? msg.method.trim() : "";
219
+ if (!method) {
220
+ return jsonRpcError(id, -32600, "Invalid Request");
221
+ }
222
+ const params = isRecord(msg.params) ? msg.params : {};
223
+ // Notifications do not receive a response.
224
+ if (id === null && method.startsWith("notifications/")) {
225
+ return null;
226
+ }
227
+ if (method === "initialize") {
228
+ const requestedProtocol = typeof params.protocolVersion === "string" ? params.protocolVersion : null;
229
+ const protocolVersion = requestedProtocol?.trim() || DEFAULT_PROTOCOL_VERSION;
230
+ const scopedServerName = input.toolScope ? `${input.serverName}/${input.toolScope.key}` : input.serverName;
231
+ return jsonRpcResult(id, {
232
+ protocolVersion,
233
+ capabilities: {
234
+ tools: {},
235
+ prompts: {},
236
+ },
237
+ serverInfo: {
238
+ name: scopedServerName,
239
+ version: input.serverVersion,
240
+ },
241
+ });
242
+ }
243
+ if (method === "ping") {
244
+ return jsonRpcResult(id, { ok: true });
245
+ }
246
+ if (method === "tools/list") {
247
+ if (input.toolScope) {
248
+ const scopedTools = new Map();
249
+ for (const name of input.toolScope.allowedTools) {
250
+ const tool = input.tools.get(name);
251
+ if (tool)
252
+ scopedTools.set(name, tool);
253
+ }
254
+ return jsonRpcResult(id, { tools: buildToolsList(scopedTools) });
255
+ }
256
+ return jsonRpcResult(id, {
257
+ tools: buildToolsList(input.tools),
258
+ });
259
+ }
260
+ if (method === "tools/call") {
261
+ const toolName = typeof params.name === "string" ? params.name.trim() : "";
262
+ if (!toolName) {
263
+ return jsonRpcError(id, -32602, "Missing tool name");
264
+ }
265
+ if (input.toolScope && !input.toolScope.allowedTools.has(toolName)) {
266
+ return jsonRpcError(id, -32601, `Tool not available in scope '${input.toolScope.key}': ${toolName}`);
267
+ }
268
+ const tool = input.tools.get(toolName) ?? null;
269
+ if (!tool) {
270
+ return jsonRpcError(id, -32601, `Tool not found: ${toolName}`);
271
+ }
272
+ const args = normalizeToolArguments(params.arguments);
273
+ try {
274
+ const callId = `mcp-${id ?? randomUUID()}`;
275
+ const result = await tool.execute(callId, args);
276
+ return jsonRpcResult(id, {
277
+ content: Array.isArray(result?.content) ? result.content : [],
278
+ isError: result?.isError === true,
279
+ });
280
+ }
281
+ catch (err) {
282
+ input.logger.warn?.("[orgx] Local MCP tool call failed", {
283
+ tool: toolName,
284
+ error: err instanceof Error ? err.message : String(err),
285
+ });
286
+ return jsonRpcResult(id, {
287
+ content: [
288
+ {
289
+ type: "text",
290
+ text: `❌ Tool execution failed: ${err instanceof Error ? err.message : String(err)}`,
291
+ },
292
+ ],
293
+ isError: true,
294
+ });
295
+ }
296
+ }
297
+ if (method === "resources/list") {
298
+ return jsonRpcResult(id, { resources: [] });
299
+ }
300
+ if (method === "prompts/list") {
301
+ const prompts = Array.from(input.prompts.values())
302
+ .sort((a, b) => a.name.localeCompare(b.name))
303
+ .map((prompt) => ({
304
+ name: prompt.name,
305
+ description: prompt.description ?? "",
306
+ arguments: Array.isArray(prompt.arguments) ? prompt.arguments : [],
307
+ }));
308
+ return jsonRpcResult(id, { prompts });
309
+ }
310
+ if (method === "prompts/get") {
311
+ const promptName = typeof params.name === "string" ? params.name.trim() : "";
312
+ if (!promptName) {
313
+ return jsonRpcError(id, -32602, "Missing prompt name");
314
+ }
315
+ const prompt = input.prompts.get(promptName) ?? null;
316
+ if (!prompt) {
317
+ return jsonRpcError(id, -32601, `Prompt not found: ${promptName}`);
318
+ }
319
+ return jsonRpcResult(id, {
320
+ description: prompt.description ?? "",
321
+ messages: prompt.messages,
322
+ });
323
+ }
324
+ if (method.startsWith("notifications/")) {
325
+ return null;
326
+ }
327
+ return jsonRpcError(id, -32601, `Method not found: ${method}`);
328
+ }
329
+ export function createMcpHttpHandler(input) {
330
+ const logger = input.logger ?? {};
331
+ const prompts = input.prompts ?? new Map();
332
+ return async function handler(req, res) {
333
+ const method = (req.method ?? "GET").toUpperCase();
334
+ const rawUrl = req.url ?? "/";
335
+ const url = normalizePath(rawUrl);
336
+ if (!(url === "/orgx/mcp" || url.startsWith("/orgx/mcp/"))) {
337
+ return false;
338
+ }
339
+ const scopeKey = parseScopeKey(url);
340
+ const toolScope = resolveToolScope(scopeKey);
341
+ if (url.startsWith("/orgx/mcp/") && !scopeKey) {
342
+ sendText(res, 404, `Unknown OrgX MCP scope. Supported: ${Object.keys(ORGX_MCP_ALLOWED_TOOLS_BY_SCOPE).join(", ")}\n`);
343
+ return true;
344
+ }
345
+ if (method === "OPTIONS") {
346
+ res.writeHead(204, {
347
+ "cache-control": "no-store",
348
+ });
349
+ res.end();
350
+ return true;
351
+ }
352
+ if (method === "GET") {
353
+ const suffix = toolScope ? ` (scope: ${toolScope.key})` : "";
354
+ sendText(res, 200, `OrgX Local MCP bridge is running${suffix}.\n`);
355
+ return true;
356
+ }
357
+ if (method !== "POST") {
358
+ sendJson(res, 405, {
359
+ error: "Use POST /orgx/mcp",
360
+ });
361
+ return true;
362
+ }
363
+ const payload = await parseJsonBody(req);
364
+ if (!payload) {
365
+ sendJson(res, 400, {
366
+ error: "Invalid JSON body",
367
+ });
368
+ return true;
369
+ }
370
+ const messages = Array.isArray(payload) ? payload : [payload];
371
+ const responses = [];
372
+ for (const message of messages) {
373
+ const response = await handleRpcMessage({
374
+ message,
375
+ tools: input.tools,
376
+ prompts,
377
+ logger,
378
+ serverName: input.serverName,
379
+ serverVersion: input.serverVersion,
380
+ toolScope,
381
+ });
382
+ if (response)
383
+ responses.push(response);
384
+ }
385
+ if (responses.length === 0) {
386
+ res.writeHead(204, {
387
+ "cache-control": "no-store",
388
+ });
389
+ res.end();
390
+ return true;
391
+ }
392
+ sendJson(res, 200, Array.isArray(payload) ? responses : responses[0]);
393
+ return true;
394
+ };
395
+ }
@@ -0,0 +1,31 @@
1
+ export type NextUpPinnedEntry = {
2
+ initiativeId: string;
3
+ workstreamId: string;
4
+ preferredTaskId: string | null;
5
+ preferredMilestoneId: string | null;
6
+ createdAt: string;
7
+ updatedAt: string;
8
+ };
9
+ type PersistedNextUpQueue = {
10
+ version: 1;
11
+ updatedAt: string;
12
+ pins: NextUpPinnedEntry[];
13
+ };
14
+ export declare function readNextUpQueuePins(): PersistedNextUpQueue;
15
+ export declare function upsertNextUpQueuePin(input: {
16
+ initiativeId: string;
17
+ workstreamId: string;
18
+ preferredTaskId?: string | null;
19
+ preferredMilestoneId?: string | null;
20
+ }): PersistedNextUpQueue;
21
+ export declare function removeNextUpQueuePin(input: {
22
+ initiativeId: string;
23
+ workstreamId: string;
24
+ }): PersistedNextUpQueue;
25
+ export declare function setNextUpQueuePinOrder(input: {
26
+ order: Array<{
27
+ initiativeId: string;
28
+ workstreamId: string;
29
+ }>;
30
+ }): PersistedNextUpQueue;
31
+ export {};
@@ -0,0 +1,169 @@
1
+ import { chmodSync, existsSync, mkdirSync, readFileSync, } from "node:fs";
2
+ import { getOrgxPluginConfigDir, getOrgxPluginConfigPath } from "./paths.js";
3
+ import { backupCorruptFileSync, writeJsonFileAtomicSync } from "./fs-utils.js";
4
+ const MAX_PINS = 240;
5
+ function storeDir() {
6
+ return getOrgxPluginConfigDir();
7
+ }
8
+ function storeFile() {
9
+ return getOrgxPluginConfigPath("next-up-queue.json");
10
+ }
11
+ 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
+ }
28
+ }
29
+ function normalizeNullableString(value) {
30
+ if (typeof value !== "string")
31
+ return null;
32
+ const trimmed = value.trim();
33
+ return trimmed.length > 0 ? trimmed : null;
34
+ }
35
+ function normalizeEntry(input) {
36
+ return {
37
+ initiativeId: input.initiativeId.trim(),
38
+ workstreamId: input.workstreamId.trim(),
39
+ preferredTaskId: normalizeNullableString(input.preferredTaskId),
40
+ preferredMilestoneId: normalizeNullableString(input.preferredMilestoneId),
41
+ createdAt: input.createdAt,
42
+ updatedAt: input.updatedAt,
43
+ };
44
+ }
45
+ export function readNextUpQueuePins() {
46
+ const file = storeFile();
47
+ try {
48
+ if (!existsSync(file)) {
49
+ return { version: 1, updatedAt: new Date().toISOString(), pins: [] };
50
+ }
51
+ const raw = readFileSync(file, "utf8");
52
+ const parsed = parseJson(raw);
53
+ if (!parsed || typeof parsed !== "object") {
54
+ backupCorruptFileSync(file);
55
+ return { version: 1, updatedAt: new Date().toISOString(), pins: [] };
56
+ }
57
+ const pins = Array.isArray(parsed.pins) ? parsed.pins : [];
58
+ return {
59
+ version: 1,
60
+ updatedAt: typeof parsed.updatedAt === "string"
61
+ ? parsed.updatedAt
62
+ : new Date().toISOString(),
63
+ pins: pins
64
+ .filter((entry) => Boolean(entry && typeof entry === "object"))
65
+ .map((entry) => normalizeEntry(entry)),
66
+ };
67
+ }
68
+ catch {
69
+ return { version: 1, updatedAt: new Date().toISOString(), pins: [] };
70
+ }
71
+ }
72
+ export function upsertNextUpQueuePin(input) {
73
+ const initiativeId = input.initiativeId.trim();
74
+ const workstreamId = input.workstreamId.trim();
75
+ if (!initiativeId || !workstreamId) {
76
+ return readNextUpQueuePins();
77
+ }
78
+ ensureStoreDir();
79
+ const now = new Date().toISOString();
80
+ const next = readNextUpQueuePins();
81
+ const key = `${initiativeId}:${workstreamId}`;
82
+ const existing = next.pins.find((pin) => `${pin.initiativeId}:${pin.workstreamId}` === key);
83
+ const updated = normalizeEntry({
84
+ initiativeId,
85
+ workstreamId,
86
+ preferredTaskId: input.preferredTaskId ?? existing?.preferredTaskId ?? null,
87
+ preferredMilestoneId: input.preferredMilestoneId ?? existing?.preferredMilestoneId ?? null,
88
+ createdAt: existing?.createdAt ?? now,
89
+ updatedAt: now,
90
+ });
91
+ next.pins = [updated, ...next.pins.filter((pin) => `${pin.initiativeId}:${pin.workstreamId}` !== key)].slice(0, MAX_PINS);
92
+ next.updatedAt = now;
93
+ try {
94
+ writeJsonFileAtomicSync(storeFile(), next, 0o600);
95
+ }
96
+ catch {
97
+ // best effort
98
+ }
99
+ return next;
100
+ }
101
+ export function removeNextUpQueuePin(input) {
102
+ const initiativeId = input.initiativeId.trim();
103
+ const workstreamId = input.workstreamId.trim();
104
+ if (!initiativeId || !workstreamId) {
105
+ return readNextUpQueuePins();
106
+ }
107
+ ensureStoreDir();
108
+ const next = readNextUpQueuePins();
109
+ const key = `${initiativeId}:${workstreamId}`;
110
+ const filtered = next.pins.filter((pin) => `${pin.initiativeId}:${pin.workstreamId}` !== key);
111
+ if (filtered.length === next.pins.length)
112
+ return next;
113
+ next.pins = filtered;
114
+ next.updatedAt = new Date().toISOString();
115
+ try {
116
+ writeJsonFileAtomicSync(storeFile(), next, 0o600);
117
+ }
118
+ catch {
119
+ // best effort
120
+ }
121
+ return next;
122
+ }
123
+ export function setNextUpQueuePinOrder(input) {
124
+ ensureStoreDir();
125
+ const next = readNextUpQueuePins();
126
+ const now = new Date().toISOString();
127
+ const byKey = new Map(next.pins.map((pin) => [`${pin.initiativeId}:${pin.workstreamId}`, pin]));
128
+ const ordered = [];
129
+ const seen = new Set();
130
+ for (const entry of input.order) {
131
+ const initiativeId = (entry.initiativeId ?? "").trim();
132
+ const workstreamId = (entry.workstreamId ?? "").trim();
133
+ if (!initiativeId || !workstreamId)
134
+ continue;
135
+ const key = `${initiativeId}:${workstreamId}`;
136
+ if (seen.has(key))
137
+ continue;
138
+ seen.add(key);
139
+ const pin = byKey.get(key);
140
+ if (pin) {
141
+ ordered.push(pin);
142
+ }
143
+ else {
144
+ ordered.push({
145
+ initiativeId,
146
+ workstreamId,
147
+ preferredTaskId: null,
148
+ preferredMilestoneId: null,
149
+ createdAt: now,
150
+ updatedAt: now,
151
+ });
152
+ }
153
+ }
154
+ for (const pin of next.pins) {
155
+ const key = `${pin.initiativeId}:${pin.workstreamId}`;
156
+ if (seen.has(key))
157
+ continue;
158
+ ordered.push(pin);
159
+ }
160
+ next.pins = ordered.slice(0, MAX_PINS);
161
+ next.updatedAt = now;
162
+ try {
163
+ writeJsonFileAtomicSync(storeFile(), next, 0o600);
164
+ }
165
+ catch {
166
+ // best effort
167
+ }
168
+ return next;
169
+ }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "id": "orgx",
3
3
  "name": "OrgX Integration",
4
- "version": "1.0.1",
4
+ "version": "1.0.2",
5
5
  "description": "Connects Clawdbot to OrgX for agent orchestration, quality gates, and model routing",
6
6
  "entry": "./index.js",
7
7
  "author": "OrgX Team",
package/dist/outbox.d.ts CHANGED
@@ -6,7 +6,7 @@
6
6
  import type { LiveActivityItem } from "./types.js";
7
7
  export interface OutboxEvent {
8
8
  id: string;
9
- type: "progress" | "decision" | "artifact" | "changeset";
9
+ type: "progress" | "decision" | "artifact" | "changeset" | "retro" | "outcome";
10
10
  timestamp: string;
11
11
  payload: Record<string, unknown>;
12
12
  /** Converted to a LiveActivityItem for dashboard display. */
@@ -21,7 +21,7 @@ export type RuntimeInstanceRecord = {
21
21
  id: string;
22
22
  sourceClient: RuntimeSourceClient;
23
23
  displayName: string;
24
- providerLogo: "openai" | "anthropic" | "openclaw" | "orgx" | "unknown";
24
+ providerLogo: "codex" | "openai" | "anthropic" | "openclaw" | "orgx" | "unknown";
25
25
  state: RuntimeInstanceState;
26
26
  event: RuntimeHookEvent;
27
27
  runId: string | null;
@@ -85,7 +85,7 @@ function normalizeHookEvent(value) {
85
85
  }
86
86
  function toProviderLogo(sourceClient) {
87
87
  if (sourceClient === "codex")
88
- return "openai";
88
+ return "codex";
89
89
  if (sourceClient === "claude-code")
90
90
  return "anthropic";
91
91
  if (sourceClient === "openclaw")
@@ -94,6 +94,22 @@ function toProviderLogo(sourceClient) {
94
94
  return "orgx";
95
95
  return "unknown";
96
96
  }
97
+ function normalizeProviderLogo(value, sourceClient) {
98
+ const normalized = normalizeNullableString(value)?.toLowerCase();
99
+ if (normalized === "codex")
100
+ return "codex";
101
+ if (normalized === "openai")
102
+ return sourceClient === "codex" ? "codex" : "openai";
103
+ if (normalized === "anthropic")
104
+ return "anthropic";
105
+ if (normalized === "openclaw")
106
+ return "openclaw";
107
+ if (normalized === "orgx")
108
+ return "orgx";
109
+ if (normalized === "unknown")
110
+ return "unknown";
111
+ return toProviderLogo(sourceClient);
112
+ }
97
113
  function toDisplayName(sourceClient) {
98
114
  if (sourceClient === "codex")
99
115
  return "Codex";
@@ -152,11 +168,12 @@ function normalizeProgress(value) {
152
168
  return Math.max(0, Math.min(100, Math.round(value)));
153
169
  }
154
170
  function normalizeRecord(input) {
171
+ const sourceClient = normalizeSourceClient(input.sourceClient);
155
172
  return {
156
173
  id: normalizeNullableString(input.id) ?? input.id,
157
- sourceClient: normalizeSourceClient(input.sourceClient),
174
+ sourceClient,
158
175
  displayName: normalizeNullableString(input.displayName) ?? "Runtime",
159
- providerLogo: input.providerLogo,
176
+ providerLogo: normalizeProviderLogo(input.providerLogo, sourceClient),
160
177
  state: normalizeState(input.state),
161
178
  event: normalizeHookEvent(input.event),
162
179
  runId: normalizeNullableString(input.runId),