@useorgx/openclaw-plugin 0.4.9 → 0.7.2

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 (224) hide show
  1. package/README.md +77 -11
  2. package/dashboard/dist/assets/6mILZQ2a.js +1 -0
  3. package/dashboard/dist/assets/6mILZQ2a.js.br +0 -0
  4. package/dashboard/dist/assets/6mILZQ2a.js.gz +0 -0
  5. package/dashboard/dist/assets/8dksYiq4.js +2 -0
  6. package/dashboard/dist/assets/8dksYiq4.js.br +0 -0
  7. package/dashboard/dist/assets/8dksYiq4.js.gz +0 -0
  8. package/dashboard/dist/assets/B5zYRHc3.js +1 -0
  9. package/dashboard/dist/assets/B5zYRHc3.js.br +0 -0
  10. package/dashboard/dist/assets/B5zYRHc3.js.gz +0 -0
  11. package/dashboard/dist/assets/B6wPWJ35.js +1 -0
  12. package/dashboard/dist/assets/B6wPWJ35.js.br +0 -0
  13. package/dashboard/dist/assets/B6wPWJ35.js.gz +0 -0
  14. package/dashboard/dist/assets/BJgZIVUQ.js +53 -0
  15. package/dashboard/dist/assets/BJgZIVUQ.js.br +0 -0
  16. package/dashboard/dist/assets/BJgZIVUQ.js.gz +0 -0
  17. package/dashboard/dist/assets/BWEwjt1W.js +1 -0
  18. package/dashboard/dist/assets/BWEwjt1W.js.br +0 -0
  19. package/dashboard/dist/assets/BWEwjt1W.js.gz +0 -0
  20. package/dashboard/dist/assets/BgOYB78t.js +4 -0
  21. package/dashboard/dist/assets/BgOYB78t.js.br +0 -0
  22. package/dashboard/dist/assets/BgOYB78t.js.gz +0 -0
  23. package/dashboard/dist/assets/BzRbDCAD.css +1 -0
  24. package/dashboard/dist/assets/BzRbDCAD.css.br +0 -0
  25. package/dashboard/dist/assets/BzRbDCAD.css.gz +0 -0
  26. package/dashboard/dist/assets/C-KIc3Wc.js.br +0 -0
  27. package/dashboard/dist/assets/C-KIc3Wc.js.gz +0 -0
  28. package/dashboard/dist/assets/C8uM3AX8.js +1 -0
  29. package/dashboard/dist/assets/C8uM3AX8.js.br +0 -0
  30. package/dashboard/dist/assets/C8uM3AX8.js.gz +0 -0
  31. package/dashboard/dist/assets/C9jy61eu.js +212 -0
  32. package/dashboard/dist/assets/C9jy61eu.js.br +0 -0
  33. package/dashboard/dist/assets/C9jy61eu.js.gz +0 -0
  34. package/dashboard/dist/assets/CC63EwFD.js +1 -0
  35. package/dashboard/dist/assets/CC63EwFD.js.br +0 -0
  36. package/dashboard/dist/assets/CC63EwFD.js.gz +0 -0
  37. package/dashboard/dist/assets/CL_wXqR7.js +1 -0
  38. package/dashboard/dist/assets/CL_wXqR7.js.br +0 -0
  39. package/dashboard/dist/assets/CL_wXqR7.js.gz +0 -0
  40. package/dashboard/dist/assets/CZaT3ob_.js +1 -0
  41. package/dashboard/dist/assets/CZaT3ob_.js.br +0 -0
  42. package/dashboard/dist/assets/CZaT3ob_.js.gz +0 -0
  43. package/dashboard/dist/assets/CgaottFX.js +1 -0
  44. package/dashboard/dist/assets/CgaottFX.js.br +0 -0
  45. package/dashboard/dist/assets/CgaottFX.js.gz +0 -0
  46. package/dashboard/dist/assets/{CpJsfbXo.js → CxQ08qFN.js} +2 -2
  47. package/dashboard/dist/assets/CxQ08qFN.js.br +0 -0
  48. package/dashboard/dist/assets/CxQ08qFN.js.gz +0 -0
  49. package/dashboard/dist/assets/CzCxAZlW.js +1 -0
  50. package/dashboard/dist/assets/CzCxAZlW.js.br +0 -0
  51. package/dashboard/dist/assets/CzCxAZlW.js.gz +0 -0
  52. package/dashboard/dist/assets/D3iMTYEj.js +1 -0
  53. package/dashboard/dist/assets/D3iMTYEj.js.br +0 -0
  54. package/dashboard/dist/assets/D3iMTYEj.js.gz +0 -0
  55. package/dashboard/dist/assets/D8JNX8kq.js +2 -0
  56. package/dashboard/dist/assets/D8JNX8kq.js.br +0 -0
  57. package/dashboard/dist/assets/D8JNX8kq.js.gz +0 -0
  58. package/dashboard/dist/assets/DnA8dpj6.js +1 -0
  59. package/dashboard/dist/assets/DnA8dpj6.js.br +0 -0
  60. package/dashboard/dist/assets/DnA8dpj6.js.gz +0 -0
  61. package/dashboard/dist/assets/IUexzymk.js +1 -0
  62. package/dashboard/dist/assets/IUexzymk.js.br +0 -0
  63. package/dashboard/dist/assets/IUexzymk.js.gz +0 -0
  64. package/dashboard/dist/assets/cNrhgGc1.js +8 -0
  65. package/dashboard/dist/assets/cNrhgGc1.js.br +0 -0
  66. package/dashboard/dist/assets/cNrhgGc1.js.gz +0 -0
  67. package/dashboard/dist/assets/ic2FaMnh.js +1 -0
  68. package/dashboard/dist/assets/ic2FaMnh.js.br +0 -0
  69. package/dashboard/dist/assets/ic2FaMnh.js.gz +0 -0
  70. package/dashboard/dist/assets/qm8xLgv-.css +1 -0
  71. package/dashboard/dist/assets/qm8xLgv-.css.br +0 -0
  72. package/dashboard/dist/assets/qm8xLgv-.css.gz +0 -0
  73. package/dashboard/dist/assets/rttbDbEx.js +1 -0
  74. package/dashboard/dist/assets/rttbDbEx.js.br +0 -0
  75. package/dashboard/dist/assets/rttbDbEx.js.gz +0 -0
  76. package/dashboard/dist/brand/anthropic-mark.svg.br +0 -0
  77. package/dashboard/dist/brand/anthropic-mark.svg.gz +0 -0
  78. package/dashboard/dist/brand/openai-mark.svg.br +0 -0
  79. package/dashboard/dist/brand/openai-mark.svg.gz +0 -0
  80. package/dashboard/dist/brand/openclaw-mark.svg.br +0 -0
  81. package/dashboard/dist/brand/openclaw-mark.svg.gz +0 -0
  82. package/dashboard/dist/brand/xandy-orchestrator.png +0 -0
  83. package/dashboard/dist/index.html +7 -5
  84. package/dashboard/dist/index.html.br +0 -0
  85. package/dashboard/dist/index.html.gz +0 -0
  86. package/dist/activity-actor-fields.js +26 -4
  87. package/dist/activity-store.js +34 -8
  88. package/dist/agent-context-store.js +79 -17
  89. package/dist/agent-run-store.js +44 -3
  90. package/dist/agent-suite.d.ts +9 -0
  91. package/dist/agent-suite.js +149 -9
  92. package/dist/artifacts/artifact-domain-schemas.d.ts +66 -0
  93. package/dist/artifacts/artifact-domain-schemas.js +357 -0
  94. package/dist/artifacts/register-artifact.d.ts +4 -3
  95. package/dist/artifacts/register-artifact.js +170 -57
  96. package/dist/chat-store.d.ts +157 -0
  97. package/dist/chat-store.js +586 -0
  98. package/dist/cli/orgx.js +11 -0
  99. package/dist/contracts/client.d.ts +43 -3
  100. package/dist/contracts/client.js +159 -30
  101. package/dist/contracts/practice-exercise-schema.d.ts +216 -0
  102. package/dist/contracts/practice-exercise-schema.js +314 -0
  103. package/dist/contracts/retro-schema.d.ts +81 -0
  104. package/dist/contracts/retro-schema.js +80 -0
  105. package/dist/contracts/shared-types.d.ts +159 -0
  106. package/dist/contracts/shared-types.js +199 -1
  107. package/dist/contracts/skill-pack-schema.d.ts +192 -0
  108. package/dist/contracts/skill-pack-schema.js +180 -0
  109. package/dist/contracts/types.d.ts +247 -2
  110. package/dist/entities/auto-assignment.js +43 -17
  111. package/dist/event-sanitization.d.ts +11 -0
  112. package/dist/event-sanitization.js +113 -0
  113. package/dist/gateway-watchdog.d.ts +5 -0
  114. package/dist/gateway-watchdog.js +50 -0
  115. package/dist/hooks/post-reporting-event.mjs +1 -5
  116. package/dist/http/helpers/activity-headline.js +13 -132
  117. package/dist/http/helpers/auto-continue-engine.d.ts +198 -10
  118. package/dist/http/helpers/auto-continue-engine.js +3145 -186
  119. package/dist/http/helpers/autopilot-operations.d.ts +19 -0
  120. package/dist/http/helpers/autopilot-operations.js +182 -31
  121. package/dist/http/helpers/autopilot-runtime.d.ts +1 -0
  122. package/dist/http/helpers/autopilot-runtime.js +328 -25
  123. package/dist/http/helpers/autopilot-slice-utils.d.ts +18 -0
  124. package/dist/http/helpers/autopilot-slice-utils.js +514 -93
  125. package/dist/http/helpers/decision-mapper.d.ts +40 -0
  126. package/dist/http/helpers/decision-mapper.js +223 -7
  127. package/dist/http/helpers/dispatch-lifecycle.d.ts +19 -2
  128. package/dist/http/helpers/dispatch-lifecycle.js +242 -37
  129. package/dist/http/helpers/kickoff-context.js +104 -0
  130. package/dist/http/helpers/llm-client.d.ts +47 -0
  131. package/dist/http/helpers/llm-client.js +256 -0
  132. package/dist/http/helpers/mission-control.d.ts +102 -3
  133. package/dist/http/helpers/mission-control.js +498 -9
  134. package/dist/http/helpers/sentinel-catalog.d.ts +23 -0
  135. package/dist/http/helpers/sentinel-catalog.js +193 -0
  136. package/dist/http/helpers/session-classification.d.ts +9 -0
  137. package/dist/http/helpers/session-classification.js +564 -0
  138. package/dist/http/helpers/slice-experience-v2.d.ts +137 -0
  139. package/dist/http/helpers/slice-experience-v2.js +677 -0
  140. package/dist/http/helpers/slice-run-projections.d.ts +72 -0
  141. package/dist/http/helpers/slice-run-projections.js +877 -0
  142. package/dist/http/helpers/triage-mapper.d.ts +43 -0
  143. package/dist/http/helpers/triage-mapper.js +549 -0
  144. package/dist/http/helpers/value-utils.js +7 -2
  145. package/dist/http/helpers/workspace-scope.d.ts +15 -0
  146. package/dist/http/helpers/workspace-scope.js +170 -0
  147. package/dist/http/index.js +1420 -105
  148. package/dist/http/routes/agent-suite.d.ts +9 -0
  149. package/dist/http/routes/agent-suite.js +294 -8
  150. package/dist/http/routes/agents-catalog.js +64 -19
  151. package/dist/http/routes/chat.d.ts +19 -0
  152. package/dist/http/routes/chat.js +522 -0
  153. package/dist/http/routes/decision-actions.d.ts +8 -1
  154. package/dist/http/routes/decision-actions.js +42 -5
  155. package/dist/http/routes/dispatch-gateway-envelope.d.ts +25 -0
  156. package/dist/http/routes/dispatch-gateway-envelope.js +26 -0
  157. package/dist/http/routes/entities.d.ts +16 -0
  158. package/dist/http/routes/entities.js +232 -6
  159. package/dist/http/routes/live-legacy.d.ts +5 -0
  160. package/dist/http/routes/live-legacy.js +23 -509
  161. package/dist/http/routes/live-misc.d.ts +12 -0
  162. package/dist/http/routes/live-misc.js +251 -31
  163. package/dist/http/routes/live-snapshot.d.ts +49 -2
  164. package/dist/http/routes/live-snapshot.js +653 -23
  165. package/dist/http/routes/live-terminal.d.ts +11 -0
  166. package/dist/http/routes/live-terminal.js +154 -0
  167. package/dist/http/routes/live-triage.d.ts +61 -0
  168. package/dist/http/routes/live-triage.js +192 -0
  169. package/dist/http/routes/mission-control-actions.d.ts +49 -1
  170. package/dist/http/routes/mission-control-actions.js +1246 -84
  171. package/dist/http/routes/mission-control-read.d.ts +48 -3
  172. package/dist/http/routes/mission-control-read.js +1658 -20
  173. package/dist/http/routes/realtime-orchestrator.d.ts +10 -0
  174. package/dist/http/routes/realtime-orchestrator.js +74 -0
  175. package/dist/http/routes/run-control.d.ts +5 -2
  176. package/dist/http/routes/run-control.js +10 -0
  177. package/dist/http/routes/sentinels-catalog.d.ts +7 -0
  178. package/dist/http/routes/sentinels-catalog.js +24 -0
  179. package/dist/http/routes/summary.js +10 -3
  180. package/dist/http/routes/usage.d.ts +24 -0
  181. package/dist/http/routes/usage.js +362 -0
  182. package/dist/http/routes/work-artifacts.js +28 -9
  183. package/dist/index.js +165 -27
  184. package/dist/local-openclaw.js +29 -6
  185. package/dist/mcp-client-setup.js +3 -3
  186. package/dist/mcp-http-handler.d.ts +3 -0
  187. package/dist/mcp-http-handler.js +34 -60
  188. package/dist/next-up-queue-store.d.ts +16 -1
  189. package/dist/next-up-queue-store.js +89 -7
  190. package/dist/outbox.d.ts +5 -0
  191. package/dist/outbox.js +113 -9
  192. package/dist/paths.js +36 -5
  193. package/dist/reporting/rollups.d.ts +41 -0
  194. package/dist/reporting/rollups.js +113 -0
  195. package/dist/retro/domain-templates.d.ts +45 -0
  196. package/dist/retro/domain-templates.js +297 -0
  197. package/dist/retro/quality-rubric.d.ts +33 -0
  198. package/dist/retro/quality-rubric.js +213 -0
  199. package/dist/runtime-cleanup.d.ts +18 -0
  200. package/dist/runtime-cleanup.js +87 -0
  201. package/dist/services/background.d.ts +11 -0
  202. package/dist/services/background.js +22 -0
  203. package/dist/services/experiment-randomization.d.ts +21 -0
  204. package/dist/services/experiment-randomization.js +63 -0
  205. package/dist/skill-pack-state.d.ts +36 -5
  206. package/dist/skill-pack-state.js +273 -29
  207. package/dist/sync/local-agent-telemetry.d.ts +13 -0
  208. package/dist/sync/local-agent-telemetry.js +128 -0
  209. package/dist/sync/outbox-replay.js +131 -24
  210. package/dist/team-context-store.d.ts +23 -0
  211. package/dist/team-context-store.js +116 -0
  212. package/dist/telemetry/posthog.js +4 -2
  213. package/dist/tools/core-tools.d.ts +10 -14
  214. package/dist/tools/core-tools.js +1289 -24
  215. package/dist/types.d.ts +2 -0
  216. package/dist/types.js +2 -0
  217. package/dist/worker-supervisor.js +23 -0
  218. package/package.json +20 -6
  219. package/dashboard/dist/assets/B3ziCA02.js +0 -8
  220. package/dashboard/dist/assets/B5NEElEI.css +0 -1
  221. package/dashboard/dist/assets/BhapSNAs.js +0 -215
  222. package/dashboard/dist/assets/iFdvE7lx.js +0 -1
  223. package/dashboard/dist/assets/jRJsmpYM.js +0 -1
  224. package/dashboard/dist/assets/sAhvFnpk.js +0 -4
@@ -0,0 +1,522 @@
1
+ import { getChatThread, linkChatThreadScope, listChatThreads, recordChatLaunch, sendChatMessage, updateChatLaunchStatus, } from "../../chat-store.js";
2
+ function asRecord(value) {
3
+ if (!value || typeof value !== "object" || Array.isArray(value))
4
+ return null;
5
+ return value;
6
+ }
7
+ function asStringArray(value) {
8
+ if (!Array.isArray(value))
9
+ return [];
10
+ const out = [];
11
+ const seen = new Set();
12
+ for (const entry of value) {
13
+ if (typeof entry !== "string")
14
+ continue;
15
+ const trimmed = entry.trim();
16
+ if (!trimmed || seen.has(trimmed))
17
+ continue;
18
+ seen.add(trimmed);
19
+ out.push(trimmed);
20
+ }
21
+ return out;
22
+ }
23
+ function normalizeStatus(value) {
24
+ const normalized = (value ?? "").trim().toLowerCase();
25
+ if (normalized === "requested" ||
26
+ normalized === "queued" ||
27
+ normalized === "running" ||
28
+ normalized === "blocked" ||
29
+ normalized === "completed" ||
30
+ normalized === "failed") {
31
+ return normalized;
32
+ }
33
+ return "requested";
34
+ }
35
+ function normalizeAttachments(input) {
36
+ if (!Array.isArray(input))
37
+ return [];
38
+ return input
39
+ .map((entry) => asRecord(entry))
40
+ .filter((entry) => Boolean(entry))
41
+ .map((entry) => ({
42
+ id: typeof entry.id === "string" ? entry.id : undefined,
43
+ name: typeof entry.name === "string" ? entry.name : undefined,
44
+ mimeType: typeof entry.mimeType === "string" ? entry.mimeType : null,
45
+ sizeBytes: typeof entry.sizeBytes === "number" && Number.isFinite(entry.sizeBytes)
46
+ ? Math.max(0, Math.floor(entry.sizeBytes))
47
+ : null,
48
+ status: entry.status === "preparing" ||
49
+ entry.status === "indexing" ||
50
+ entry.status === "ready" ||
51
+ entry.status === "failed"
52
+ ? entry.status
53
+ : "ready",
54
+ error: typeof entry.error === "string" ? entry.error : null,
55
+ masked: entry.masked === true,
56
+ }));
57
+ }
58
+ function extractUiMessageText(value) {
59
+ const row = asRecord(value);
60
+ if (!row)
61
+ return null;
62
+ const parts = Array.isArray(row.parts) ? row.parts : [];
63
+ const text = parts
64
+ .map((part) => asRecord(part))
65
+ .filter((part) => Boolean(part))
66
+ .filter((part) => part.type === "text" && typeof part.text === "string")
67
+ .map((part) => String(part.text).trim())
68
+ .filter(Boolean)
69
+ .join(" ")
70
+ .trim();
71
+ if (text.length > 0)
72
+ return text;
73
+ if (typeof row.content === "string" && row.content.trim().length > 0) {
74
+ return row.content.trim();
75
+ }
76
+ return null;
77
+ }
78
+ function extractLastUserPrompt(messages) {
79
+ if (!Array.isArray(messages))
80
+ return null;
81
+ for (let index = messages.length - 1; index >= 0; index -= 1) {
82
+ const message = asRecord(messages[index]);
83
+ if (!message)
84
+ continue;
85
+ if (message.role !== "user")
86
+ continue;
87
+ const text = extractUiMessageText(message);
88
+ if (text)
89
+ return text;
90
+ }
91
+ return null;
92
+ }
93
+ function chunkText(value, chunkSize = 36) {
94
+ const compact = value.replace(/\s+/g, " ").trim();
95
+ if (!compact)
96
+ return [];
97
+ const chunks = [];
98
+ for (let i = 0; i < compact.length; i += chunkSize) {
99
+ chunks.push(compact.slice(i, i + chunkSize));
100
+ }
101
+ return chunks;
102
+ }
103
+ function buildAssistantReply(input) {
104
+ const hasAgent = Boolean(input.assigneeName);
105
+ const hasScope = Boolean(input.initiativeTitle);
106
+ if (hasAgent && hasScope) {
107
+ return `Got it \u2014 ${input.assigneeName} is on it. Scoped to ${input.initiativeTitle}.`;
108
+ }
109
+ if (hasAgent && !hasScope) {
110
+ return `Got it \u2014 ${input.assigneeName} is assigned. Scope to an initiative when ready to launch.`;
111
+ }
112
+ if (!hasAgent && hasScope) {
113
+ return `Saved to ${input.initiativeTitle}. Pick an agent with @ to get started.`;
114
+ }
115
+ return "Message saved. Pick an agent with @ and scope with # to get started.";
116
+ }
117
+ function sendThreadList(deps, res, input) {
118
+ const listed = listChatThreads({
119
+ commandCenterId: input.commandCenterId,
120
+ initiativeId: input.initiativeId,
121
+ query: input.query,
122
+ status: input.status,
123
+ limit: input.limit,
124
+ offset: input.offset,
125
+ });
126
+ deps.sendJson(res, 200, {
127
+ ok: true,
128
+ threads: listed.threads,
129
+ total: listed.total,
130
+ limit: input.limit,
131
+ offset: input.offset,
132
+ updatedAt: listed.updatedAt,
133
+ });
134
+ }
135
+ async function emitThreadEvent(deps, input) {
136
+ if (typeof deps.emitActivitySafe !== "function")
137
+ return;
138
+ const normalizedStatus = (input.status ?? "").trim().toLowerCase();
139
+ const phase = normalizedStatus === "running"
140
+ ? "execution"
141
+ : normalizedStatus === "blocked" || normalizedStatus === "failed"
142
+ ? "blocked"
143
+ : normalizedStatus === "completed"
144
+ ? "completed"
145
+ : "intent";
146
+ try {
147
+ await deps.emitActivitySafe({
148
+ initiativeId: input.thread.initiativeId ?? null,
149
+ sourceClient: "openclaw",
150
+ message: input.message,
151
+ phase,
152
+ metadata: {
153
+ event: input.event,
154
+ activity_bucket: "message",
155
+ thread_id: input.thread.id,
156
+ message_id: input.messageId ?? null,
157
+ launch_id: input.launchId ?? null,
158
+ run_id: input.runId ?? null,
159
+ status: input.status ?? null,
160
+ initiative_id: input.thread.initiativeId ?? null,
161
+ workstream_id: input.thread.workstreamId ?? null,
162
+ task_id: input.thread.taskId ?? null,
163
+ assignee_id: input.assigneeId ?? null,
164
+ watcher_count: input.watcherCount ?? null,
165
+ provider: input.provider ?? null,
166
+ },
167
+ });
168
+ }
169
+ catch {
170
+ // best effort
171
+ }
172
+ }
173
+ export function registerChatRoutes(router, deps) {
174
+ router.add("GET", "chat/threads", async ({ query, res }) => {
175
+ try {
176
+ const commandCenterId = query.get("project_id") ??
177
+ query.get("workspace_id") ??
178
+ query.get("workspaceId") ??
179
+ query.get("command_center_id") ??
180
+ query.get("commandCenterId");
181
+ const initiativeId = query.get("initiative_id") ?? query.get("initiative");
182
+ const searchQuery = query.get("query") ?? query.get("q");
183
+ const status = query.get("status");
184
+ const limit = deps.parsePositiveInt(query.get("limit"), 60);
185
+ const offset = deps.parsePositiveInt(query.get("offset"), 0);
186
+ sendThreadList(deps, res, {
187
+ commandCenterId: commandCenterId?.trim() ?? null,
188
+ initiativeId: initiativeId?.trim() ?? null,
189
+ query: searchQuery?.trim() ?? null,
190
+ status: status?.trim() ?? null,
191
+ limit,
192
+ offset,
193
+ });
194
+ }
195
+ catch (err) {
196
+ deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
197
+ }
198
+ }, "List chat threads");
199
+ router.add("GET", "chat/threads/*", async ({ path, res }) => {
200
+ try {
201
+ const match = path.match(/^chat\/threads\/([^/]+)$/);
202
+ if (!match) {
203
+ deps.sendJson(res, 404, { ok: false, error: "Unknown API endpoint" });
204
+ return;
205
+ }
206
+ const threadId = decodeURIComponent(match[1]).trim();
207
+ if (!threadId) {
208
+ deps.sendJson(res, 400, { ok: false, error: "threadId is required" });
209
+ return;
210
+ }
211
+ const thread = getChatThread(threadId);
212
+ if (!thread) {
213
+ deps.sendJson(res, 404, { ok: false, error: "thread not found" });
214
+ return;
215
+ }
216
+ deps.sendJson(res, 200, { ok: true, thread });
217
+ }
218
+ catch (err) {
219
+ deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
220
+ }
221
+ }, "Get chat thread detail");
222
+ router.add("POST", "chat/messages", async ({ req, res }) => {
223
+ try {
224
+ const payload = await deps.parseJsonRequest(req);
225
+ const threadId = deps.pickString(payload, ["threadId", "thread_id"]);
226
+ const body = deps.pickString(payload, ["body", "message", "text"]);
227
+ if (!body) {
228
+ deps.sendJson(res, 400, { ok: false, error: "message body is required" });
229
+ return;
230
+ }
231
+ const result = sendChatMessage({
232
+ threadId,
233
+ commandCenterId: deps.pickString(payload, [
234
+ "projectId",
235
+ "project_id",
236
+ "workspaceId",
237
+ "workspace_id",
238
+ "commandCenterId",
239
+ "command_center_id",
240
+ ]) ??
241
+ null,
242
+ initiativeId: deps.pickString(payload, ["initiativeId", "initiative_id"]) ?? null,
243
+ initiativeTitle: deps.pickString(payload, [
244
+ "initiativeTitle",
245
+ "initiative_title",
246
+ "initiativeName",
247
+ "initiative_name",
248
+ ]) ?? null,
249
+ workstreamId: deps.pickString(payload, ["workstreamId", "workstream_id"]) ?? null,
250
+ taskId: deps.pickString(payload, ["taskId", "task_id"]) ?? null,
251
+ title: deps.pickString(payload, ["title"]),
252
+ body,
253
+ senderId: deps.pickString(payload, ["senderId", "sender_id", "authorId", "author_id"]),
254
+ senderName: deps.pickString(payload, ["senderName", "sender_name", "authorName", "author_name"]),
255
+ assigneeId: deps.pickString(payload, ["assigneeId", "assignee_id"]),
256
+ assigneeName: deps.pickString(payload, ["assigneeName", "assignee_name"]),
257
+ watcherIds: asStringArray(payload.watcherIds),
258
+ watcherNames: asStringArray(payload.watcherNames),
259
+ attachments: normalizeAttachments(payload.attachments),
260
+ metadata: asRecord(payload.metadata) ?? {},
261
+ });
262
+ await emitThreadEvent(deps, {
263
+ event: result.createdThread ? "chat_thread_created" : "chat_message_sent",
264
+ message: result.createdThread
265
+ ? `Thread created: ${result.thread.title}`
266
+ : `Message sent: ${result.thread.title}`,
267
+ thread: result.thread,
268
+ messageId: result.message.id,
269
+ assigneeId: result.thread.assigneeId,
270
+ watcherCount: result.thread.watcherIds.length,
271
+ status: result.thread.status,
272
+ });
273
+ deps.sendJson(res, 201, {
274
+ ok: true,
275
+ createdThread: result.createdThread,
276
+ thread: result.thread,
277
+ message: result.message,
278
+ updatedAt: result.updatedAt,
279
+ });
280
+ }
281
+ catch (err) {
282
+ deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
283
+ }
284
+ }, "Create thread message");
285
+ router.add("POST", "chat/usechat", async ({ req, res }) => {
286
+ try {
287
+ const payload = await deps.parseJsonRequest(req);
288
+ const threadId = deps.pickString(payload, ["threadId", "thread_id"]);
289
+ const prompt = deps.pickString(payload, ["prompt", "text", "message"]) ??
290
+ extractLastUserPrompt(payload.messages);
291
+ if (!prompt) {
292
+ deps.sendJson(res, 400, { ok: false, error: "A user prompt is required." });
293
+ return;
294
+ }
295
+ const assigneeId = deps.pickString(payload, ["assigneeId", "assignee_id"]);
296
+ const assigneeName = deps.pickString(payload, ["assigneeName", "assignee_name"]);
297
+ const initiativeId = deps.pickString(payload, ["initiativeId", "initiative_id"]);
298
+ const initiativeTitle = deps.pickString(payload, [
299
+ "initiativeTitle",
300
+ "initiative_title",
301
+ "initiativeName",
302
+ "initiative_name",
303
+ ]);
304
+ const watcherIds = asStringArray(payload.watcherIds);
305
+ const watcherNames = asStringArray(payload.watcherNames);
306
+ const commandCenterId = deps.pickString(payload, [
307
+ "projectId",
308
+ "project_id",
309
+ "workspaceId",
310
+ "workspace_id",
311
+ "commandCenterId",
312
+ "command_center_id",
313
+ ]) ??
314
+ null;
315
+ const reply = buildAssistantReply({
316
+ prompt,
317
+ assigneeName,
318
+ initiativeTitle,
319
+ watcherCount: watcherIds.length > 0 ? watcherIds.length : watcherNames.length,
320
+ });
321
+ // Persist the assistant guidance onto the active thread when possible, without
322
+ // altering the user-message-first thread summary semantics.
323
+ if (threadId && getChatThread(threadId)) {
324
+ const result = sendChatMessage({
325
+ threadId,
326
+ commandCenterId,
327
+ initiativeId,
328
+ initiativeTitle,
329
+ role: "agent",
330
+ body: reply,
331
+ senderId: assigneeId ?? "orgx-assistant",
332
+ senderName: assigneeName ?? "OrgX Assistant",
333
+ watcherIds,
334
+ watcherNames,
335
+ metadata: {
336
+ source: "usechat",
337
+ mode: "guidance",
338
+ },
339
+ });
340
+ await emitThreadEvent(deps, {
341
+ event: "chat_assistant_guidance_sent",
342
+ message: `Assistant guidance updated: ${result.thread.title}`,
343
+ thread: result.thread,
344
+ messageId: result.message.id,
345
+ assigneeId: result.thread.assigneeId,
346
+ watcherCount: result.thread.watcherIds.length,
347
+ status: result.thread.status,
348
+ });
349
+ }
350
+ const writable = res;
351
+ const write = writable.write?.bind(writable);
352
+ if (!writable.writeHead || !write || !writable.end) {
353
+ deps.sendJson(res, 501, { ok: false, error: "Streaming not supported" });
354
+ return;
355
+ }
356
+ writable.writeHead(200, {
357
+ "Content-Type": "text/plain; charset=utf-8",
358
+ "Cache-Control": "no-cache, no-transform",
359
+ Connection: "keep-alive",
360
+ });
361
+ const chunks = chunkText(reply, 28);
362
+ for (let index = 0; index < chunks.length; index += 1) {
363
+ const chunk = index === chunks.length - 1 ? chunks[index] : `${chunks[index]} `;
364
+ const writeOk = write(chunk);
365
+ if (!writeOk && typeof writable.once === "function") {
366
+ await new Promise((resolve) => writable.once?.("drain", resolve));
367
+ }
368
+ await new Promise((resolve) => setTimeout(resolve, 12));
369
+ }
370
+ writable.end();
371
+ }
372
+ catch (err) {
373
+ deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
374
+ }
375
+ }, "Stream assistant guidance for Vercel useChat");
376
+ router.add("POST", "chat/launch", async ({ req, res }) => {
377
+ try {
378
+ const payload = await deps.parseJsonRequest(req);
379
+ const threadId = deps.pickString(payload, ["threadId", "thread_id"]);
380
+ const messageId = deps.pickString(payload, ["messageId", "message_id"]);
381
+ if (!threadId || !messageId) {
382
+ deps.sendJson(res, 400, {
383
+ ok: false,
384
+ error: "threadId and messageId are required",
385
+ });
386
+ return;
387
+ }
388
+ const status = normalizeStatus(deps.pickString(payload, ["status"]));
389
+ const result = recordChatLaunch({
390
+ threadId,
391
+ messageId,
392
+ assigneeId: deps.pickString(payload, ["assigneeId", "assignee_id"]),
393
+ assigneeName: deps.pickString(payload, ["assigneeName", "assignee_name"]),
394
+ watcherIds: asStringArray(payload.watcherIds),
395
+ watcherNames: asStringArray(payload.watcherNames),
396
+ executionMode: deps.pickString(payload, ["executionMode", "execution_mode"]) === "cloud"
397
+ ? "cloud"
398
+ : deps.pickString(payload, ["executionMode", "execution_mode"]) === "hybrid"
399
+ ? "hybrid"
400
+ : "local_queue",
401
+ provider: deps.pickString(payload, ["provider", "runtimeProvider", "runtime_provider"]),
402
+ runId: deps.pickString(payload, ["runId", "run_id", "sessionId", "session_id"]),
403
+ status,
404
+ blockedReason: deps.pickString(payload, ["blockedReason", "blocked_reason", "error", "reason"]),
405
+ warnings: asStringArray(payload.warnings),
406
+ metadata: asRecord(payload.metadata) ?? {},
407
+ });
408
+ await emitThreadEvent(deps, {
409
+ event: status === "queued"
410
+ ? "chat_launch_queued"
411
+ : status === "running"
412
+ ? "chat_launch_started"
413
+ : status === "blocked"
414
+ ? "chat_launch_blocked"
415
+ : status === "completed"
416
+ ? "chat_launch_completed"
417
+ : status === "failed"
418
+ ? "chat_launch_failed"
419
+ : "chat_launch_requested",
420
+ message: status === "blocked"
421
+ ? `Launch blocked: ${result.thread.title}`
422
+ : `Launch ${status}: ${result.thread.title}`,
423
+ thread: result.thread,
424
+ messageId,
425
+ launchId: result.launch.id,
426
+ runId: result.launch.runId,
427
+ status,
428
+ assigneeId: result.launch.assigneeId,
429
+ watcherCount: result.launch.watcherIds.length,
430
+ provider: result.launch.provider,
431
+ });
432
+ deps.sendJson(res, 201, {
433
+ ok: true,
434
+ thread: result.thread,
435
+ launch: result.launch,
436
+ updatedAt: result.updatedAt,
437
+ });
438
+ }
439
+ catch (err) {
440
+ deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
441
+ }
442
+ }, "Record chat launch attempt");
443
+ router.add("PATCH", "chat/launch", async ({ req, res }) => {
444
+ try {
445
+ const payload = await deps.parseJsonRequest(req);
446
+ const threadId = deps.pickString(payload, ["threadId", "thread_id"]);
447
+ const launchId = deps.pickString(payload, ["launchId", "launch_id"]);
448
+ if (!threadId || !launchId) {
449
+ deps.sendJson(res, 400, { ok: false, error: "threadId and launchId are required" });
450
+ return;
451
+ }
452
+ const result = updateChatLaunchStatus({
453
+ threadId,
454
+ launchId,
455
+ status: normalizeStatus(deps.pickString(payload, ["status"])),
456
+ runId: deps.pickString(payload, ["runId", "run_id", "sessionId", "session_id"]),
457
+ blockedReason: deps.pickString(payload, ["blockedReason", "blocked_reason", "error"]),
458
+ warnings: asStringArray(payload.warnings),
459
+ });
460
+ deps.sendJson(res, 200, {
461
+ ok: true,
462
+ thread: result.thread,
463
+ launch: result.launch,
464
+ updatedAt: result.updatedAt,
465
+ });
466
+ }
467
+ catch (err) {
468
+ deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
469
+ }
470
+ }, "Update chat launch status");
471
+ router.add("POST", "chat/link", async ({ req, res }) => {
472
+ try {
473
+ const payload = await deps.parseJsonRequest(req);
474
+ const threadId = deps.pickString(payload, ["threadId", "thread_id"]);
475
+ if (!threadId) {
476
+ deps.sendJson(res, 400, { ok: false, error: "threadId is required" });
477
+ return;
478
+ }
479
+ const result = linkChatThreadScope({
480
+ threadId,
481
+ commandCenterId: deps.pickString(payload, [
482
+ "projectId",
483
+ "project_id",
484
+ "workspaceId",
485
+ "workspace_id",
486
+ "commandCenterId",
487
+ "command_center_id",
488
+ ]) ??
489
+ null,
490
+ initiativeId: deps.pickString(payload, ["initiativeId", "initiative_id"]),
491
+ initiativeTitle: deps.pickString(payload, [
492
+ "initiativeTitle",
493
+ "initiative_title",
494
+ "initiativeName",
495
+ "initiative_name",
496
+ ]) ?? null,
497
+ workstreamId: deps.pickString(payload, ["workstreamId", "workstream_id"]),
498
+ taskId: deps.pickString(payload, ["taskId", "task_id"]),
499
+ });
500
+ const metadata = asRecord(payload);
501
+ const relinked = Boolean(metadata?.relinked === true);
502
+ await emitThreadEvent(deps, {
503
+ event: relinked ? "chat_thread_relinked_initiative" : "chat_thread_linked_initiative",
504
+ message: result.eventMessage.body,
505
+ thread: result.thread,
506
+ messageId: result.eventMessage.id,
507
+ status: result.thread.status,
508
+ assigneeId: result.thread.assigneeId,
509
+ watcherCount: result.thread.watcherIds.length,
510
+ });
511
+ deps.sendJson(res, 200, {
512
+ ok: true,
513
+ thread: result.thread,
514
+ eventMessage: result.eventMessage,
515
+ updatedAt: result.updatedAt,
516
+ });
517
+ }
518
+ catch (err) {
519
+ deps.sendJson(res, 500, { ok: false, error: deps.safeErrorMessage(err) });
520
+ }
521
+ }, "Link/relink chat thread scope");
522
+ }
@@ -3,9 +3,16 @@ type JsonRecord = Record<string, unknown>;
3
3
  type DecisionAction = "approve" | "reject";
4
4
  type RegisterDecisionActionsRoutesDeps<TReq, TRes> = {
5
5
  parseJsonRequest: (req: TReq) => Promise<JsonRecord>;
6
- bulkDecideDecisions: (ids: string[], action: DecisionAction, note?: string) => Promise<Array<{
6
+ bulkDecideDecisions: (ids: string[], action: DecisionAction, input?: {
7
+ note?: string;
8
+ optionId?: string;
9
+ }) => Promise<Array<{
7
10
  ok?: boolean;
8
11
  }>>;
12
+ emitDecisionResolvedActivity?: (ids: string[], action: DecisionAction, input?: {
13
+ note?: string;
14
+ optionId?: string;
15
+ }) => Promise<void>;
9
16
  sendJson: (res: TRes, status: number, payload: unknown) => void;
10
17
  safeErrorMessage: (err: unknown) => string;
11
18
  };
@@ -13,20 +13,45 @@ async function handleApproveRequest(req, res, deps, routeIds) {
13
13
  const note = typeof payload.note === "string" && payload.note.trim().length > 0
14
14
  ? payload.note.trim()
15
15
  : undefined;
16
+ const optionIdRaw = typeof payload.option_id === "string"
17
+ ? payload.option_id
18
+ : typeof payload.optionId === "string"
19
+ ? payload.optionId
20
+ : null;
21
+ const optionId = typeof optionIdRaw === "string" && optionIdRaw.trim().length > 0
22
+ ? optionIdRaw.trim()
23
+ : undefined;
16
24
  const ids = routeIds ?? extractIdsFromPayload(payload);
17
25
  if (ids.length === 0) {
18
26
  deps.sendJson(res, 400, {
19
27
  error: "Decision IDs are required.",
20
28
  expected: {
21
29
  route: "/orgx/api/live/decisions/approve",
22
- body: { ids: ["decision-id"], action: "approve|reject" },
30
+ body: { ids: ["decision-id"], action: "approve|reject", option_id: "optional-option-id" },
23
31
  },
24
32
  });
25
33
  return;
26
34
  }
27
- const results = await deps.bulkDecideDecisions(ids, action, note);
35
+ const results = await deps.bulkDecideDecisions(ids, action, {
36
+ note,
37
+ optionId,
38
+ });
39
+ const resolvedIds = results
40
+ .map((result, index) => (result.ok === true ? ids[index] ?? null : null))
41
+ .filter((id) => typeof id === "string" && id.length > 0);
28
42
  const updated = results.filter((result) => result.ok === true).length;
29
43
  const failed = results.length - updated;
44
+ if (resolvedIds.length > 0 && typeof deps.emitDecisionResolvedActivity === "function") {
45
+ try {
46
+ await deps.emitDecisionResolvedActivity(resolvedIds, action, {
47
+ note,
48
+ optionId,
49
+ });
50
+ }
51
+ catch {
52
+ // best effort; decision mutation succeeded so do not fail the response
53
+ }
54
+ }
30
55
  deps.sendJson(res, failed > 0 ? 207 : 200, {
31
56
  action,
32
57
  requested: ids.length,
@@ -35,6 +60,15 @@ async function handleApproveRequest(req, res, deps, routeIds) {
35
60
  results,
36
61
  });
37
62
  }
63
+ function decodeDecisionId(encodedDecisionId) {
64
+ try {
65
+ const decoded = decodeURIComponent(encodedDecisionId).trim();
66
+ return decoded.length > 0 ? decoded : null;
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
38
72
  export function registerDecisionActionsRoutes(router, deps) {
39
73
  router.add("POST", "live/decisions/approve", async ({ req, res }) => {
40
74
  try {
@@ -52,10 +86,13 @@ export function registerDecisionActionsRoutes(router, deps) {
52
86
  deps.sendJson(res, 404, { error: "Unknown API endpoint" });
53
87
  return;
54
88
  }
89
+ const decisionId = decodeDecisionId(decisionApproveMatch[1]);
90
+ if (!decisionId) {
91
+ deps.sendJson(res, 400, { error: "Invalid decision ID." });
92
+ return;
93
+ }
55
94
  try {
56
- await handleApproveRequest(req, res, deps, [
57
- decodeURIComponent(decisionApproveMatch[1]),
58
- ]);
95
+ await handleApproveRequest(req, res, deps, [decisionId]);
59
96
  }
60
97
  catch (err) {
61
98
  deps.sendJson(res, 500, {
@@ -0,0 +1,25 @@
1
+ export type DispatchGatewayEnvelopeInput = {
2
+ dispatchId?: string;
3
+ dispatchMode: string;
4
+ route: "mission-control.next-up.play" | "mission-control.auto-continue.start";
5
+ source: "manual_play" | "auto_continue_start";
6
+ initiativeId: string;
7
+ workstreamId?: string | null;
8
+ workstreamIds?: string[] | null;
9
+ taskIds?: string[] | null;
10
+ };
11
+ export declare function buildDispatchGatewayEnvelope(input: DispatchGatewayEnvelopeInput): {
12
+ readonly dispatchId: string;
13
+ readonly dispatchMode: string;
14
+ readonly executionPath: "orgx_orchestrator_gateway";
15
+ readonly dispatchGateway: "orchestrator-agent";
16
+ readonly dispatchLineage: {
17
+ readonly route: "mission-control.next-up.play" | "mission-control.auto-continue.start";
18
+ readonly source: "manual_play" | "auto_continue_start";
19
+ readonly dispatchedAt: string;
20
+ readonly initiativeId: string;
21
+ readonly workstreamId: string | null;
22
+ readonly workstreamIds: string[];
23
+ readonly taskIds: string[];
24
+ };
25
+ };
@@ -0,0 +1,26 @@
1
+ import { randomUUID } from "node:crypto";
2
+ function uniqueStringIds(values) {
3
+ return Array.from(new Set(values
4
+ .map((value) => (typeof value === "string" ? value.trim() : ""))
5
+ .filter((value) => value.length > 0)));
6
+ }
7
+ export function buildDispatchGatewayEnvelope(input) {
8
+ return {
9
+ dispatchId: input.dispatchId ?? randomUUID(),
10
+ dispatchMode: input.dispatchMode,
11
+ executionPath: "orgx_orchestrator_gateway",
12
+ dispatchGateway: "orchestrator-agent",
13
+ dispatchLineage: {
14
+ route: input.route,
15
+ source: input.source,
16
+ dispatchedAt: new Date().toISOString(),
17
+ initiativeId: input.initiativeId,
18
+ workstreamId: input.workstreamId ?? null,
19
+ workstreamIds: uniqueStringIds([
20
+ input.workstreamId,
21
+ ...(input.workstreamIds ?? []),
22
+ ]),
23
+ taskIds: uniqueStringIds(input.taskIds ?? []),
24
+ },
25
+ };
26
+ }