u-foo 3.0.18 → 3.0.19

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.
@@ -0,0 +1,115 @@
1
+ "use strict";
2
+
3
+ const {
4
+ IPC_REQUEST_TYPES,
5
+ IPC_RESPONSE_TYPES,
6
+ } = require("../contracts/eventContract");
7
+ const {
8
+ executeProjectRuntimeOperation,
9
+ } = require("./projectRuntimeGateway");
10
+
11
+ function writeResult(socket, payload) {
12
+ if (!socket || socket.destroyed) return;
13
+ socket.write(`${JSON.stringify({
14
+ type: IPC_RESPONSE_TYPES.CONTROL_PLANE_RESULT,
15
+ ...payload,
16
+ })}\n`);
17
+ }
18
+
19
+ function createProjectRuntimeControlPlane(options = {}) {
20
+ const projectRoot = String(options.projectRoot || "").trim();
21
+ const execute = options.execute || executeProjectRuntimeOperation;
22
+ const activeCalls = new Map();
23
+
24
+ async function handleRequest(req, socket) {
25
+ if (!req || typeof req !== "object") return false;
26
+ if (req.type === IPC_REQUEST_TYPES.CONTROL_PLANE_CANCEL) {
27
+ const requestId = String(req.request_id || "");
28
+ const active = activeCalls.get(requestId);
29
+ if (active) active.abortController.abort();
30
+ return true;
31
+ }
32
+ if (req.type !== IPC_REQUEST_TYPES.CONTROL_PLANE_CALL) return false;
33
+
34
+ const requestId = String(req.request_id || "").trim();
35
+ const operation = String(req.operation || "").trim();
36
+ if (!requestId || !operation) {
37
+ writeResult(socket, {
38
+ request_id: requestId,
39
+ ok: false,
40
+ error: {
41
+ code: "invalid_control_plane_call",
42
+ message: "control_plane_call requires request_id and operation",
43
+ },
44
+ });
45
+ return true;
46
+ }
47
+ if (activeCalls.has(requestId)) {
48
+ writeResult(socket, {
49
+ request_id: requestId,
50
+ ok: false,
51
+ error: {
52
+ code: "duplicate_control_plane_call",
53
+ message: `control plane request is already active: ${requestId}`,
54
+ },
55
+ });
56
+ return true;
57
+ }
58
+
59
+ const abortController = new AbortController();
60
+ const cancelOnClose = () => abortController.abort();
61
+ activeCalls.set(requestId, { abortController, socket });
62
+ socket.once("close", cancelOnClose);
63
+ try {
64
+ const result = await execute(
65
+ projectRoot,
66
+ operation,
67
+ req.arguments && typeof req.arguments === "object" ? req.arguments : {},
68
+ {
69
+ requestId,
70
+ toolCallId: req.tool_call_id,
71
+ signal: abortController.signal,
72
+ }
73
+ );
74
+ writeResult(socket, {
75
+ request_id: requestId,
76
+ ok: true,
77
+ result,
78
+ });
79
+ } catch (err) {
80
+ writeResult(socket, {
81
+ request_id: requestId,
82
+ ok: false,
83
+ error: {
84
+ code: err && err.code ? String(err.code) : "control_plane_error",
85
+ message: err && err.message ? err.message : String(err),
86
+ },
87
+ });
88
+ } finally {
89
+ socket.removeListener("close", cancelOnClose);
90
+ activeCalls.delete(requestId);
91
+ }
92
+ return true;
93
+ }
94
+
95
+ function stop() {
96
+ for (const active of activeCalls.values()) {
97
+ active.abortController.abort();
98
+ }
99
+ activeCalls.clear();
100
+ }
101
+
102
+ function activeCount() {
103
+ return activeCalls.size;
104
+ }
105
+
106
+ return {
107
+ handleRequest,
108
+ stop,
109
+ activeCount,
110
+ };
111
+ }
112
+
113
+ module.exports = {
114
+ createProjectRuntimeControlPlane,
115
+ };
@@ -0,0 +1,358 @@
1
+ "use strict";
2
+
3
+ const crypto = require("crypto");
4
+ const net = require("net");
5
+ const { getUfooPaths } = require("../../coordination/state/paths");
6
+ const {
7
+ IPC_REQUEST_TYPES,
8
+ IPC_RESPONSE_TYPES,
9
+ } = require("../contracts/eventContract");
10
+ const {
11
+ assertToolAllowedForCallerTier,
12
+ } = require("../../tools/registry");
13
+ const { CALLER_TIERS } = require("../../tools/types");
14
+
15
+ const MCP_EXPOSED_SHARED_TOOLS = Object.freeze([
16
+ "read_project_registry",
17
+ "read_bus_summary",
18
+ "read_prompt_history",
19
+ "read_open_decisions",
20
+ "list_agents",
21
+ "dispatch_message",
22
+ "ack_bus",
23
+ ]);
24
+
25
+ const CONTROL_PLANE_OPERATIONS = Object.freeze([
26
+ "register_agent",
27
+ "heartbeat_agent",
28
+ "publish_activity_state",
29
+ "update_agent_metadata",
30
+ "poll_inbox",
31
+ "wait_for_message",
32
+ "report_agent_status",
33
+ "unregister_agent",
34
+ ]);
35
+
36
+ function stripRoutingArgs(args = {}) {
37
+ const next = { ...(args || {}) };
38
+ delete next.project_root;
39
+ delete next.projectRoot;
40
+ delete next.subscriber;
41
+ delete next.agent_handle;
42
+ delete next.agentHandle;
43
+ return next;
44
+ }
45
+
46
+ function unsupportedOperationError(operation = "") {
47
+ const err = new Error(`unsupported project runtime operation: ${operation}`);
48
+ err.code = "unsupported_project_runtime_operation";
49
+ return err;
50
+ }
51
+
52
+ function createControlPlaneHandlers(service = null) {
53
+ const resolvedService = service || require("./controlPlaneService");
54
+ return {
55
+ register_agent: (projectRoot, args) => resolvedService.registerAgent(projectRoot, args),
56
+ heartbeat_agent: (projectRoot, args) => resolvedService.heartbeatAgent(projectRoot, args),
57
+ publish_activity_state: (projectRoot, args) => resolvedService.publishActivityState(projectRoot, args),
58
+ update_agent_metadata: (projectRoot, args) => resolvedService.updateAgentMetadata(projectRoot, args),
59
+ poll_inbox: (projectRoot, args) => resolvedService.pollInbox(projectRoot, args),
60
+ wait_for_message: (projectRoot, args, context) => resolvedService.waitForMessage(projectRoot, args, {
61
+ signal: context.signal,
62
+ pollIntervalMs: context.waitPollIntervalMs,
63
+ heartbeatIntervalMs: context.waitHeartbeatIntervalMs,
64
+ now: context.waitNow,
65
+ sleep: context.waitSleep,
66
+ }),
67
+ report_agent_status: (projectRoot, args) => resolvedService.reportAgentStatus(projectRoot, args),
68
+ unregister_agent: (projectRoot, args) => resolvedService.unregisterAgent(projectRoot, args),
69
+ };
70
+ }
71
+
72
+ async function executeProjectRuntimeOperation(
73
+ projectRoot,
74
+ operation,
75
+ args = {},
76
+ context = {},
77
+ options = {}
78
+ ) {
79
+ const name = String(operation || "").trim();
80
+ const handlers = options.controlPlaneHandlers
81
+ || createControlPlaneHandlers(options.controlPlaneService);
82
+ const customHandler = handlers[name];
83
+ if (customHandler) {
84
+ return customHandler(projectRoot, args, context);
85
+ }
86
+
87
+ if (!MCP_EXPOSED_SHARED_TOOLS.includes(name) || name === "read_project_registry") {
88
+ throw unsupportedOperationError(name);
89
+ }
90
+
91
+ if (name === "dispatch_message" || name === "ack_bus") {
92
+ const subscriber = String(args.subscriber || args.source || context.subscriber || "").trim();
93
+ const bus = resolvedBusForAgentHandle(projectRoot, options);
94
+ assertExternalAgentHandle(bus, subscriber, args, options);
95
+ }
96
+
97
+ const tool = assertToolAllowedForCallerTier(name, CALLER_TIERS.WORKER, {
98
+ tool_call_id: context.toolCallId,
99
+ });
100
+ const subscriber = String(args.subscriber || args.source || context.subscriber || "").trim();
101
+ const toolArgs = stripRoutingArgs(args);
102
+ if (name === "dispatch_message" && !toolArgs.source && subscriber) {
103
+ toolArgs.source = subscriber;
104
+ }
105
+ return tool.handler({
106
+ projectRoot,
107
+ subscriber,
108
+ caller_tier: CALLER_TIERS.WORKER,
109
+ tool_call_id: context.toolCallId,
110
+ }, toolArgs);
111
+ }
112
+
113
+ function resolvedBusForAgentHandle(projectRoot, options = {}) {
114
+ const service = options.controlPlaneService || require("./controlPlaneService");
115
+ return service.ensureBusLoaded(projectRoot);
116
+ }
117
+
118
+ function assertExternalAgentHandle(bus, subscriber, args = {}, options = {}) {
119
+ const service = options.controlPlaneService || require("./controlPlaneService");
120
+ return service.assertAgentHandle(bus, subscriber, args);
121
+ }
122
+
123
+ class LocalProjectRuntimeGateway {
124
+ constructor(options = {}) {
125
+ this.options = options;
126
+ }
127
+
128
+ call(projectRoot, operation, args = {}, context = {}) {
129
+ return executeProjectRuntimeOperation(
130
+ projectRoot,
131
+ operation,
132
+ args,
133
+ context,
134
+ this.options
135
+ );
136
+ }
137
+
138
+ cancel() {
139
+ return false;
140
+ }
141
+ }
142
+
143
+ function projectRuntimeError(code, message) {
144
+ const err = new Error(String(message || "project runtime request failed"));
145
+ err.code = String(code || "project_runtime_error");
146
+ return err;
147
+ }
148
+
149
+ function connectProjectRuntimeSocket(sockPath, timeoutMs = 5000) {
150
+ return new Promise((resolve, reject) => {
151
+ let settled = false;
152
+ const socket = net.createConnection(sockPath);
153
+ const timer = setTimeout(() => {
154
+ if (settled) return;
155
+ settled = true;
156
+ socket.destroy();
157
+ reject(projectRuntimeError(
158
+ "project_runtime_connect_timeout",
159
+ `project runtime connect timeout: ${sockPath}`
160
+ ));
161
+ }, timeoutMs);
162
+ if (typeof timer.unref === "function") timer.unref();
163
+
164
+ socket.once("connect", () => {
165
+ if (settled) return;
166
+ settled = true;
167
+ clearTimeout(timer);
168
+ resolve(socket);
169
+ });
170
+ socket.once("error", (err) => {
171
+ if (settled) return;
172
+ settled = true;
173
+ clearTimeout(timer);
174
+ reject(projectRuntimeError(
175
+ err && err.code ? String(err.code) : "project_runtime_unavailable",
176
+ `failed to connect project runtime: ${err && err.message ? err.message : err}`
177
+ ));
178
+ });
179
+ });
180
+ }
181
+
182
+ function resolveCallTimeoutMs(operation, args = {}, fallbackMs = 15000) {
183
+ if (operation !== "wait_for_message") return fallbackMs;
184
+ const timeoutSeconds = Number(args.timeout_seconds ?? args.timeoutSeconds ?? 600);
185
+ const waitMs = Number.isFinite(timeoutSeconds) && timeoutSeconds > 0
186
+ ? timeoutSeconds * 1000
187
+ : 600000;
188
+ return waitMs + 5000;
189
+ }
190
+
191
+ class SocketProjectRuntimeGateway {
192
+ constructor(options = {}) {
193
+ this.connect = options.connect || connectProjectRuntimeSocket;
194
+ this.socketPath = options.socketPath
195
+ || ((projectRoot) => getUfooPaths(projectRoot).ufooSock);
196
+ this.connectTimeoutMs = Number(options.connectTimeoutMs) || 5000;
197
+ this.callTimeoutMs = Number(options.callTimeoutMs) || 15000;
198
+ this.activeCalls = new Map();
199
+ }
200
+
201
+ async call(projectRoot, operation, args = {}, context = {}) {
202
+ if (context.signal && context.signal.aborted) {
203
+ throw projectRuntimeError("request_cancelled", "project runtime request was cancelled");
204
+ }
205
+ const sockPath = this.socketPath(projectRoot);
206
+ const socket = await this.connect(sockPath, this.connectTimeoutMs);
207
+ const requestId = String(
208
+ context.requestId
209
+ || crypto.randomUUID()
210
+ );
211
+ const timeoutMs = resolveCallTimeoutMs(operation, args, this.callTimeoutMs);
212
+
213
+ return new Promise((resolve, reject) => {
214
+ let buffer = "";
215
+ let settled = false;
216
+ let timer = null;
217
+
218
+ const cleanup = () => {
219
+ if (timer) clearTimeout(timer);
220
+ timer = null;
221
+ this.activeCalls.delete(requestId);
222
+ if (context.signal) context.signal.removeEventListener("abort", onAbort);
223
+ socket.removeAllListeners();
224
+ try {
225
+ socket.end();
226
+ } catch {
227
+ // ignore close errors
228
+ }
229
+ };
230
+
231
+ const finishResolve = (value) => {
232
+ if (settled) return;
233
+ settled = true;
234
+ cleanup();
235
+ resolve(value);
236
+ };
237
+
238
+ const finishReject = (err) => {
239
+ if (settled) return;
240
+ settled = true;
241
+ cleanup();
242
+ reject(err);
243
+ };
244
+
245
+ const onAbort = () => {
246
+ if (settled) return;
247
+ try {
248
+ socket.write(`${JSON.stringify({
249
+ type: IPC_REQUEST_TYPES.CONTROL_PLANE_CANCEL,
250
+ request_id: requestId,
251
+ })}\n`);
252
+ } catch {
253
+ // best-effort cancellation
254
+ }
255
+ try {
256
+ socket.destroy();
257
+ } catch {
258
+ // ignore close errors
259
+ }
260
+ finishReject(projectRuntimeError(
261
+ "request_cancelled",
262
+ "project runtime request was cancelled"
263
+ ));
264
+ };
265
+
266
+ this.activeCalls.set(requestId, { cancel: onAbort, socket });
267
+ if (context.signal) {
268
+ context.signal.addEventListener("abort", onAbort, { once: true });
269
+ }
270
+
271
+ socket.on("data", (chunk) => {
272
+ buffer += chunk.toString("utf8");
273
+ const lines = buffer.split(/\r?\n/);
274
+ buffer = lines.pop() || "";
275
+ for (const line of lines) {
276
+ if (!line.trim()) continue;
277
+ let payload;
278
+ try {
279
+ payload = JSON.parse(line);
280
+ } catch {
281
+ continue;
282
+ }
283
+ if (
284
+ payload.type !== IPC_RESPONSE_TYPES.CONTROL_PLANE_RESULT
285
+ || String(payload.request_id || "") !== requestId
286
+ ) {
287
+ continue;
288
+ }
289
+ if (payload.ok === false) {
290
+ const error = payload.error && typeof payload.error === "object"
291
+ ? payload.error
292
+ : {};
293
+ finishReject(projectRuntimeError(
294
+ error.code || "project_runtime_error",
295
+ error.message || "project runtime request failed"
296
+ ));
297
+ return;
298
+ }
299
+ finishResolve(payload.result);
300
+ return;
301
+ }
302
+ });
303
+ socket.once("error", (err) => {
304
+ finishReject(projectRuntimeError(
305
+ err && err.code ? String(err.code) : "project_runtime_connection_error",
306
+ err && err.message ? err.message : String(err)
307
+ ));
308
+ });
309
+ socket.once("close", () => {
310
+ finishReject(projectRuntimeError(
311
+ "project_runtime_connection_closed",
312
+ "project runtime connection closed before a result was returned"
313
+ ));
314
+ });
315
+
316
+ timer = setTimeout(() => {
317
+ onAbort();
318
+ }, timeoutMs);
319
+ if (typeof timer.unref === "function") timer.unref();
320
+
321
+ socket.write(`${JSON.stringify({
322
+ type: IPC_REQUEST_TYPES.CONTROL_PLANE_CALL,
323
+ request_id: requestId,
324
+ operation,
325
+ arguments: args,
326
+ tool_call_id: context.toolCallId,
327
+ })}\n`);
328
+ });
329
+ }
330
+
331
+ cancel(requestId) {
332
+ const active = this.activeCalls.get(String(requestId || ""));
333
+ if (!active) return false;
334
+ active.cancel();
335
+ return true;
336
+ }
337
+ }
338
+
339
+ function createLocalProjectRuntimeGateway(options = {}) {
340
+ return new LocalProjectRuntimeGateway(options);
341
+ }
342
+
343
+ function createSocketProjectRuntimeGateway(options = {}) {
344
+ return new SocketProjectRuntimeGateway(options);
345
+ }
346
+
347
+ module.exports = {
348
+ MCP_EXPOSED_SHARED_TOOLS,
349
+ CONTROL_PLANE_OPERATIONS,
350
+ stripRoutingArgs,
351
+ executeProjectRuntimeOperation,
352
+ LocalProjectRuntimeGateway,
353
+ SocketProjectRuntimeGateway,
354
+ createLocalProjectRuntimeGateway,
355
+ createSocketProjectRuntimeGateway,
356
+ connectProjectRuntimeSocket,
357
+ resolveCallTimeoutMs,
358
+ };