@tangle-network/agent-core 0.8.1 → 0.9.0

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 (36) hide show
  1. package/README.md +37 -4
  2. package/dist/auth/browser.d.ts +1 -1
  3. package/dist/auth/index.d.ts +1 -1
  4. package/dist/events/index.d.ts +1 -1
  5. package/dist/{index-VcOZI9jN.d.ts → index-B4yy8ghH.d.ts} +1 -1
  6. package/dist/{index-VcOZI9jN.d.ts.map → index-B4yy8ghH.d.ts.map} +1 -1
  7. package/dist/{index-BJ31RMR3.d.ts → index-Bgao-NCe.d.ts} +1 -1
  8. package/dist/{index-BJ31RMR3.d.ts.map → index-Bgao-NCe.d.ts.map} +1 -1
  9. package/dist/{index-C7zlWVGc.d.ts → index-C_1MnL24.d.ts} +1 -1
  10. package/dist/{index-C7zlWVGc.d.ts.map → index-C_1MnL24.d.ts.map} +1 -1
  11. package/dist/{index-BRyMBSkE.d.ts → index-D59fZ7n3.d.ts} +1 -1
  12. package/dist/{index-BRyMBSkE.d.ts.map → index-D59fZ7n3.d.ts.map} +1 -1
  13. package/dist/{index-CJA3rTaa.d.ts → index-G0ZmFWEp.d.ts} +2 -2
  14. package/dist/{index-CJA3rTaa.d.ts.map → index-G0ZmFWEp.d.ts.map} +1 -1
  15. package/dist/{index-BOD4IhTr.d.ts → index-HMlPCgTI.d.ts} +2 -2
  16. package/dist/{index-BOD4IhTr.d.ts.map → index-HMlPCgTI.d.ts.map} +1 -1
  17. package/dist/index.d.ts +7 -7
  18. package/dist/interactions/index.d.ts +34 -0
  19. package/dist/interactions/index.d.ts.map +1 -0
  20. package/dist/interactions/index.js +425 -0
  21. package/dist/interactions/index.js.map +1 -0
  22. package/dist/interactions/mcp-server.d.ts +42 -0
  23. package/dist/interactions/mcp-server.d.ts.map +1 -0
  24. package/dist/interactions/mcp-server.js +368 -0
  25. package/dist/interactions/mcp-server.js.map +1 -0
  26. package/dist/lifecycle-CXKYetbu.js +68 -0
  27. package/dist/lifecycle-CXKYetbu.js.map +1 -0
  28. package/dist/resilience/index.d.ts +1 -1
  29. package/dist/telemetry/index.d.ts +1 -1
  30. package/dist/tools-D-I3A7Us.d.ts +115 -0
  31. package/dist/tools-D-I3A7Us.d.ts.map +1 -0
  32. package/dist/transport/index.d.ts +1 -1
  33. package/dist/types/index.d.ts +1 -1
  34. package/dist/{types-D32txapu.d.ts → types-Cd5jUzcr.d.ts} +2 -2
  35. package/dist/{types-D32txapu.d.ts.map → types-Cd5jUzcr.d.ts.map} +1 -1
  36. package/package.json +29 -1
@@ -0,0 +1,425 @@
1
+ import { n as closeHttpServer, t as SerializedTransportLifecycle } from "../lifecycle-CXKYetbu.js";
2
+ import { z } from "zod";
3
+ import { InteractionAnswerSpecSchema, InteractionKind, InteractionRequestSchema, PERMISSION_GRANT_FIELD, interactionRequestDigest, permissionAnswerSpec, validateInteractionResponse } from "@tangle-network/agent-interface";
4
+ import { randomBytes, randomUUID } from "node:crypto";
5
+ import { createServer } from "node:http";
6
+ //#region src/interactions/broker.ts
7
+ const DEFAULT_INTERACTION_DECISION_TIMEOUT_MS = 3e5;
8
+ const MAX_INTERACTION_DECISION_TIMEOUT_MS = 2147483647;
9
+ const InteractionQuestionSchema = z.object({
10
+ question: z.string().min(1),
11
+ options: z.array(z.object({
12
+ label: z.string().min(1),
13
+ description: z.string().optional()
14
+ })).optional(),
15
+ multiSelect: z.boolean().optional()
16
+ });
17
+ const InteractionQuestionsSchema = z.array(InteractionQuestionSchema).min(1);
18
+ function checkedTimeout(timeoutMs) {
19
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > 2147483647) throw new RangeError(`Interaction timeout must be between 1 and ${MAX_INTERACTION_DECISION_TIMEOUT_MS} milliseconds`);
20
+ return timeoutMs;
21
+ }
22
+ function bindInteractionRequest(binding, request) {
23
+ const material = {
24
+ ...request,
25
+ binding: {
26
+ ...binding,
27
+ interactionId: request.id
28
+ }
29
+ };
30
+ return InteractionRequestSchema.parse({
31
+ ...material,
32
+ requestDigest: interactionRequestDigest(material)
33
+ });
34
+ }
35
+ /** Fail-closed coercion: anything other than a known allow grant denies. */
36
+ function coercePermissionGrant(value) {
37
+ return value === "allow_once" || value === "allow_session" || value === "allow_always" ? value : "deny";
38
+ }
39
+ /** Convert canonical question fields (`q0`, `q1`) to positional answers. */
40
+ function interactionDataToQuestionAnswers(data) {
41
+ const positional = {};
42
+ for (const [key, value] of Object.entries(data)) {
43
+ const index = /^q(\d+)$/.exec(key)?.[1] ?? key;
44
+ positional[index] = Array.isArray(value) ? value.map(String) : [String(value)];
45
+ }
46
+ return Object.entries(positional).sort(([left], [right]) => /^\d+$/.test(left) && /^\d+$/.test(right) ? Number(left) - Number(right) : 0).map(([, value]) => value);
47
+ }
48
+ /** Build one canonical answer field for each question. */
49
+ function interactionAnswerSpecForQuestions(questions) {
50
+ const validatedQuestions = InteractionQuestionsSchema.parse(questions);
51
+ return InteractionAnswerSpecSchema.parse({ fields: validatedQuestions.map((question, index) => {
52
+ const name = `q${index}`;
53
+ if (question.options?.length) return {
54
+ type: "select",
55
+ name,
56
+ label: question.question,
57
+ required: true,
58
+ multi: question.multiSelect === true,
59
+ allowCustom: true,
60
+ options: question.options.map((option) => ({
61
+ value: option.label,
62
+ label: option.label,
63
+ ...option.description === void 0 ? {} : { description: option.description }
64
+ }))
65
+ };
66
+ return {
67
+ type: "text",
68
+ name,
69
+ label: question.question,
70
+ required: true
71
+ };
72
+ }) });
73
+ }
74
+ function responseId(response) {
75
+ if (!response || typeof response !== "object") return void 0;
76
+ const id = Reflect.get(response, "id");
77
+ return typeof id === "string" ? id : void 0;
78
+ }
79
+ function matchesExecution(request, binding) {
80
+ const actual = request.binding;
81
+ return actual.runId === binding.runId && actual.provider === binding.provider && actual.environmentId === binding.environmentId && actual.sessionId === binding.sessionId && actual.executionId === binding.executionId;
82
+ }
83
+ /**
84
+ * One interaction round-trip for every runner adapter.
85
+ *
86
+ * A runner emits a request and waits here. The UI returns a canonical response.
87
+ * The first response, timeout, or session teardown settles the request.
88
+ */
89
+ var InteractionBroker = class {
90
+ pendingPermissions = /* @__PURE__ */ new Map();
91
+ pendingQuestions = /* @__PURE__ */ new Map();
92
+ decisionTimeoutMs;
93
+ constructor(options = {}) {
94
+ this.decisionTimeoutMs = checkedTimeout(options.decisionTimeoutMs ?? 3e5);
95
+ }
96
+ hasPendingInteraction(id) {
97
+ return this.pendingPermissions.has(id) || this.pendingQuestions.has(id);
98
+ }
99
+ /** Emit a permission request and wait for its decision. */
100
+ request(opts) {
101
+ if (opts.binding.sessionId !== opts.sessionId || this.hasPendingInteraction(opts.id)) return Promise.resolve("deny");
102
+ const timeoutMs = checkedTimeout(opts.timeoutMs ?? this.decisionTimeoutMs);
103
+ const responseScopes = opts.allowlistGrant === "allow_always" ? [
104
+ "interaction",
105
+ "session",
106
+ "persistent"
107
+ ] : opts.allowlistGrant === "allow_session" ? ["interaction", "session"] : ["interaction"];
108
+ const request = bindInteractionRequest(opts.binding, {
109
+ id: opts.id,
110
+ kind: InteractionKind.Permission,
111
+ title: `Allow tool "${opts.toolName}"?`,
112
+ subject: {
113
+ type: "tool",
114
+ toolName: opts.toolName,
115
+ ...opts.input === void 0 ? {} : { input: opts.input }
116
+ },
117
+ answerSpec: permissionAnswerSpec({ responseScopes }),
118
+ responseScopes,
119
+ default: opts.allowlistGrant === "deny" ? { outcome: "declined" } : {
120
+ outcome: "accepted",
121
+ data: { [PERMISSION_GRANT_FIELD]: [opts.allowlistGrant] }
122
+ },
123
+ timeoutMs,
124
+ onTimeout: "default"
125
+ });
126
+ return new Promise((resolve) => {
127
+ let timer;
128
+ const abort = () => settle("deny");
129
+ const settle = (grant) => {
130
+ if (!this.pendingPermissions.delete(opts.id)) return;
131
+ if (timer) clearTimeout(timer);
132
+ opts.signal?.removeEventListener("abort", abort);
133
+ resolve(grant);
134
+ };
135
+ this.pendingPermissions.set(opts.id, {
136
+ request,
137
+ settle
138
+ });
139
+ if (!opts.emit) {
140
+ settle(opts.allowlistGrant);
141
+ return;
142
+ }
143
+ timer = setTimeout(() => settle(opts.allowlistGrant), timeoutMs);
144
+ if (opts.signal?.aborted) {
145
+ settle("deny");
146
+ return;
147
+ }
148
+ opts.signal?.addEventListener("abort", abort, { once: true });
149
+ if (opts.signal?.aborted) {
150
+ settle("deny");
151
+ return;
152
+ }
153
+ try {
154
+ Promise.resolve(opts.emit({
155
+ type: "interaction",
156
+ request
157
+ })).catch(() => settle(opts.allowlistGrant));
158
+ } catch {
159
+ settle(opts.allowlistGrant);
160
+ }
161
+ });
162
+ }
163
+ /** Resolve a pending permission. Invalid or excessive grants deny. */
164
+ respond(response) {
165
+ const id = responseId(response);
166
+ if (!id) return false;
167
+ const entry = this.pendingPermissions.get(id);
168
+ if (!entry) return false;
169
+ const validation = validateInteractionResponse(entry.request, response);
170
+ if (!validation.ok || validation.response.outcome !== "accepted") {
171
+ entry.settle("deny");
172
+ return true;
173
+ }
174
+ const raw = validation.response.data?.[PERMISSION_GRANT_FIELD];
175
+ const selected = Array.isArray(raw) && raw.length === 1 ? raw[0] : void 0;
176
+ entry.settle(coercePermissionGrant(selected));
177
+ return true;
178
+ }
179
+ /** Emit a question and wait for positional answers. */
180
+ requestQuestion(opts) {
181
+ if (opts.binding.sessionId !== opts.sessionId || this.hasPendingInteraction(opts.id)) return Promise.resolve(null);
182
+ const timeoutMs = checkedTimeout(opts.timeoutMs ?? this.decisionTimeoutMs);
183
+ const request = bindInteractionRequest(opts.binding, {
184
+ id: opts.id,
185
+ kind: InteractionKind.Question,
186
+ title: opts.questions[0]?.question ?? "Question",
187
+ answerSpec: interactionAnswerSpecForQuestions(opts.questions),
188
+ default: { outcome: "cancelled" },
189
+ timeoutMs,
190
+ onTimeout: "default"
191
+ });
192
+ return new Promise((resolve) => {
193
+ let timer;
194
+ const abort = () => settle(null);
195
+ const settle = (answers) => {
196
+ if (!this.pendingQuestions.delete(opts.id)) return;
197
+ if (timer) clearTimeout(timer);
198
+ opts.signal?.removeEventListener("abort", abort);
199
+ resolve(answers);
200
+ };
201
+ this.pendingQuestions.set(opts.id, {
202
+ request,
203
+ settle
204
+ });
205
+ if (!opts.emit) {
206
+ settle(null);
207
+ return;
208
+ }
209
+ timer = setTimeout(() => settle(null), timeoutMs);
210
+ if (opts.signal?.aborted) {
211
+ settle(null);
212
+ return;
213
+ }
214
+ opts.signal?.addEventListener("abort", abort, { once: true });
215
+ if (opts.signal?.aborted) {
216
+ settle(null);
217
+ return;
218
+ }
219
+ try {
220
+ Promise.resolve(opts.emit({
221
+ type: "interaction",
222
+ request
223
+ })).catch(() => settle(null));
224
+ } catch {
225
+ settle(null);
226
+ }
227
+ });
228
+ }
229
+ /** Resolve a pending question. Invalid, declined, and cancelled replies return null. */
230
+ respondQuestion(response) {
231
+ const id = responseId(response);
232
+ if (!id) return false;
233
+ const entry = this.pendingQuestions.get(id);
234
+ if (!entry) return false;
235
+ const validation = validateInteractionResponse(entry.request, response);
236
+ if (!validation.ok) {
237
+ entry.settle(null);
238
+ return true;
239
+ }
240
+ const validatedResponse = validation.response;
241
+ entry.settle(validatedResponse.outcome === "accepted" ? interactionDataToQuestionAnswers(validatedResponse.data ?? {}) : null);
242
+ return true;
243
+ }
244
+ /** Fail closed every request that belongs to one session. */
245
+ failSession(sessionId) {
246
+ for (const entry of this.pendingPermissions.values()) if (entry.request.binding.sessionId === sessionId) entry.settle("deny");
247
+ for (const entry of this.pendingQuestions.values()) if (entry.request.binding.sessionId === sessionId) entry.settle(null);
248
+ }
249
+ /** Fail closed only the requests from one exact provider execution. */
250
+ failExecution(binding) {
251
+ for (const entry of this.pendingPermissions.values()) if (matchesExecution(entry.request, binding)) entry.settle("deny");
252
+ for (const entry of this.pendingQuestions.values()) if (matchesExecution(entry.request, binding)) entry.settle(null);
253
+ }
254
+ };
255
+ //#endregion
256
+ //#region src/interactions/tools.ts
257
+ /** Bind generic interaction tools to one broker session. */
258
+ function brokerInteractionTools(broker, options) {
259
+ return {
260
+ askUser: (request) => broker.requestQuestion({
261
+ id: request.operationId ?? randomUUID(),
262
+ sessionId: options.sessionId,
263
+ binding: options.binding,
264
+ questions: request.questions,
265
+ timeoutMs: options.timeoutMs,
266
+ signal: request.signal,
267
+ emit: options.emit
268
+ }),
269
+ requestPermission: async (request) => {
270
+ return { allowed: await broker.request({
271
+ id: request.operationId ?? randomUUID(),
272
+ sessionId: options.sessionId,
273
+ binding: options.binding,
274
+ toolName: request.tool_name,
275
+ input: request.input,
276
+ allowlistGrant: "deny",
277
+ timeoutMs: options.timeoutMs,
278
+ signal: request.signal,
279
+ emit: options.emit
280
+ }) !== "deny" };
281
+ },
282
+ onStop: () => broker.failExecution(options.binding)
283
+ };
284
+ }
285
+ //#endregion
286
+ //#region src/interactions/http-bridge.ts
287
+ const MAX_HTTP_BODY_BYTES = 1024 * 1024;
288
+ const permissionBodySchema = z.strictObject({
289
+ tool_name: z.string().min(1),
290
+ input: z.record(z.string(), z.unknown()).optional()
291
+ });
292
+ const questionBodySchema = z.strictObject({ questions: z.array(InteractionQuestionSchema).min(1) });
293
+ /**
294
+ * Loopback JSON bridge for runners with an extension API but no MCP support.
295
+ *
296
+ * Extensions call `/permission` or `/question`. The bridge blocks until the
297
+ * shared broker receives an operator response.
298
+ */
299
+ var InteractionHttpBridge = class {
300
+ token = randomBytes(24).toString("hex");
301
+ requestPermission;
302
+ askUser;
303
+ broker;
304
+ binding;
305
+ lifecycle = new SerializedTransportLifecycle();
306
+ httpServer;
307
+ portValue = 0;
308
+ constructor(broker, options) {
309
+ this.broker = broker;
310
+ this.binding = options.binding;
311
+ const tools = brokerInteractionTools(broker, options);
312
+ this.requestPermission = tools.requestPermission;
313
+ this.askUser = tools.askUser;
314
+ }
315
+ get port() {
316
+ return this.portValue;
317
+ }
318
+ get baseUrl() {
319
+ return `http://127.0.0.1:${this.portValue}`;
320
+ }
321
+ async start() {
322
+ return this.lifecycle.start(async () => {
323
+ const httpServer = createServer((request, response) => {
324
+ const send = (status, body) => {
325
+ if (response.writableEnded) return;
326
+ const text = body === void 0 ? "" : JSON.stringify(body);
327
+ response.writeHead(status, { "content-type": "application/json" }).end(text);
328
+ };
329
+ if (request.headers.authorization !== `Bearer ${this.token}`) {
330
+ send(401);
331
+ return;
332
+ }
333
+ if (request.method !== "POST") {
334
+ send(405);
335
+ return;
336
+ }
337
+ const chunks = [];
338
+ let size = 0;
339
+ request.on("error", () => send(400));
340
+ request.on("data", (chunk) => {
341
+ size += chunk.length;
342
+ if (size > MAX_HTTP_BODY_BYTES) {
343
+ send(413);
344
+ request.destroy();
345
+ return;
346
+ }
347
+ chunks.push(chunk);
348
+ });
349
+ request.on("end", () => {
350
+ if (response.writableEnded) return;
351
+ let body;
352
+ try {
353
+ const raw = Buffer.concat(chunks).toString("utf8");
354
+ body = raw ? JSON.parse(raw) : {};
355
+ } catch {
356
+ send(400, { error: "invalid json" });
357
+ return;
358
+ }
359
+ this.route(request.url ?? "", body).then((result) => send(result.status, result.body)).catch(() => send(500, { error: "interaction bridge failed" }));
360
+ });
361
+ });
362
+ try {
363
+ await new Promise((resolve, reject) => {
364
+ httpServer.once("error", reject);
365
+ httpServer.listen(0, "127.0.0.1", resolve);
366
+ });
367
+ } catch (error) {
368
+ httpServer.closeAllConnections?.();
369
+ throw error;
370
+ }
371
+ const address = httpServer.address();
372
+ this.portValue = address && typeof address === "object" ? address.port : 0;
373
+ this.httpServer = httpServer;
374
+ });
375
+ }
376
+ async stop() {
377
+ return this.lifecycle.stop(async () => {
378
+ this.broker.failExecution(this.binding);
379
+ const httpServer = this.httpServer;
380
+ this.httpServer = void 0;
381
+ this.portValue = 0;
382
+ if (httpServer) await closeHttpServer(httpServer);
383
+ });
384
+ }
385
+ async route(url, body) {
386
+ const path = new URL(url, "http://127.0.0.1").pathname.replace(/\/+$/, "");
387
+ if (path === "/permission") {
388
+ const parsed = permissionBodySchema.safeParse(body);
389
+ if (!parsed.success) return {
390
+ status: 400,
391
+ body: { error: "invalid permission request" }
392
+ };
393
+ const result = await this.requestPermission({
394
+ tool_name: parsed.data.tool_name,
395
+ input: parsed.data.input ?? {}
396
+ });
397
+ return {
398
+ status: 200,
399
+ body: {
400
+ allowed: result.allowed,
401
+ ...result.message ? { reason: result.message } : {}
402
+ }
403
+ };
404
+ }
405
+ if (path === "/question") {
406
+ const parsed = questionBodySchema.safeParse(body);
407
+ if (!parsed.success) return {
408
+ status: 400,
409
+ body: { error: "invalid question request" }
410
+ };
411
+ return {
412
+ status: 200,
413
+ body: { answers: await this.askUser({ questions: parsed.data.questions }) }
414
+ };
415
+ }
416
+ return {
417
+ status: 404,
418
+ body: { error: "unknown route" }
419
+ };
420
+ }
421
+ };
422
+ //#endregion
423
+ export { DEFAULT_INTERACTION_DECISION_TIMEOUT_MS, InteractionBroker, InteractionHttpBridge, InteractionQuestionSchema, MAX_INTERACTION_DECISION_TIMEOUT_MS, brokerInteractionTools, coercePermissionGrant, interactionAnswerSpecForQuestions, interactionDataToQuestionAnswers };
424
+
425
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/interactions/broker.ts","../../src/interactions/tools.ts","../../src/interactions/http-bridge.ts"],"sourcesContent":["import {\n type InteractionAnswerSpec,\n InteractionAnswerSpecSchema,\n type InteractionExecutionBinding,\n InteractionKind,\n type InteractionRequest,\n type InteractionRequestMaterial,\n InteractionRequestSchema,\n type InteractionResponseScope,\n interactionRequestDigest,\n PERMISSION_GRANT_FIELD,\n type PermissionGrant,\n permissionAnswerSpec,\n type StreamEvent,\n validateInteractionResponse,\n} from \"@tangle-network/agent-interface\";\nimport { z } from \"zod\";\n\nexport const DEFAULT_INTERACTION_DECISION_TIMEOUT_MS = 300_000;\nexport const MAX_INTERACTION_DECISION_TIMEOUT_MS = 2_147_483_647;\n\nexport interface InteractionBrokerOptions {\n /** Default wait for an operator response before the declared default applies. */\n decisionTimeoutMs?: number;\n}\n\nexport const InteractionQuestionSchema = z.object({\n question: z.string().min(1),\n options: z\n .array(\n z.object({\n label: z.string().min(1),\n description: z.string().optional(),\n }),\n )\n .optional(),\n multiSelect: z.boolean().optional(),\n});\nexport type InteractionQuestion = z.infer<typeof InteractionQuestionSchema>;\nconst InteractionQuestionsSchema = z.array(InteractionQuestionSchema).min(1);\n\nfunction checkedTimeout(timeoutMs: number): number {\n if (\n !Number.isFinite(timeoutMs) ||\n timeoutMs <= 0 ||\n timeoutMs > MAX_INTERACTION_DECISION_TIMEOUT_MS\n ) {\n throw new RangeError(\n `Interaction timeout must be between 1 and ${MAX_INTERACTION_DECISION_TIMEOUT_MS} milliseconds`,\n );\n }\n return timeoutMs;\n}\n\nfunction bindInteractionRequest(\n binding: InteractionExecutionBinding,\n request: Omit<InteractionRequestMaterial, \"binding\">,\n): InteractionRequest {\n const material: InteractionRequestMaterial = {\n ...request,\n binding: { ...binding, interactionId: request.id },\n };\n return InteractionRequestSchema.parse({\n ...material,\n requestDigest: interactionRequestDigest(material),\n });\n}\n\n/** Fail-closed coercion: anything other than a known allow grant denies. */\nexport function coercePermissionGrant(value: unknown): PermissionGrant {\n return value === \"allow_once\" ||\n value === \"allow_session\" ||\n value === \"allow_always\"\n ? value\n : \"deny\";\n}\n\n/** Convert canonical question fields (`q0`, `q1`) to positional answers. */\nexport function interactionDataToQuestionAnswers(\n data: Record<string, unknown>,\n): string[][] {\n const positional: Record<string, string[]> = {};\n for (const [key, value] of Object.entries(data)) {\n const index = /^q(\\d+)$/.exec(key)?.[1] ?? key;\n positional[index] = Array.isArray(value)\n ? value.map(String)\n : [String(value)];\n }\n return Object.entries(positional)\n .sort(([left], [right]) =>\n /^\\d+$/.test(left) && /^\\d+$/.test(right)\n ? Number(left) - Number(right)\n : 0,\n )\n .map(([, value]) => value);\n}\n\n/** Build one canonical answer field for each question. */\nexport function interactionAnswerSpecForQuestions(\n questions: InteractionQuestion[],\n): InteractionAnswerSpec {\n const validatedQuestions = InteractionQuestionsSchema.parse(questions);\n return InteractionAnswerSpecSchema.parse({\n fields: validatedQuestions.map((question, index) => {\n const name = `q${index}`;\n if (question.options?.length) {\n return {\n type: \"select\",\n name,\n label: question.question,\n required: true,\n multi: question.multiSelect === true,\n allowCustom: true,\n options: question.options.map((option) => ({\n value: option.label,\n label: option.label,\n ...(option.description === undefined\n ? {}\n : { description: option.description }),\n })),\n };\n }\n return {\n type: \"text\",\n name,\n label: question.question,\n required: true,\n };\n }),\n });\n}\n\ntype PendingPermission = {\n request: InteractionRequest;\n settle: (grant: PermissionGrant) => void;\n};\n\ntype PendingQuestion = {\n request: InteractionRequest;\n settle: (answers: string[][] | null) => void;\n};\n\ntype InteractionEventSink = (event: StreamEvent) => unknown;\n\nfunction responseId(response: unknown): string | undefined {\n if (!response || typeof response !== \"object\") return undefined;\n const id = Reflect.get(response, \"id\");\n return typeof id === \"string\" ? id : undefined;\n}\n\nfunction matchesExecution(\n request: InteractionRequest,\n binding: InteractionExecutionBinding,\n): boolean {\n const actual = request.binding;\n return (\n actual.runId === binding.runId &&\n actual.provider === binding.provider &&\n actual.environmentId === binding.environmentId &&\n actual.sessionId === binding.sessionId &&\n actual.executionId === binding.executionId\n );\n}\n\n/**\n * One interaction round-trip for every runner adapter.\n *\n * A runner emits a request and waits here. The UI returns a canonical response.\n * The first response, timeout, or session teardown settles the request.\n */\nexport class InteractionBroker {\n private readonly pendingPermissions = new Map<string, PendingPermission>();\n private readonly pendingQuestions = new Map<string, PendingQuestion>();\n private readonly decisionTimeoutMs: number;\n\n constructor(options: InteractionBrokerOptions = {}) {\n this.decisionTimeoutMs = checkedTimeout(\n options.decisionTimeoutMs ?? DEFAULT_INTERACTION_DECISION_TIMEOUT_MS,\n );\n }\n\n private hasPendingInteraction(id: string): boolean {\n return (\n this.pendingPermissions.has(id) || this.pendingQuestions.has(id)\n );\n }\n\n /** Emit a permission request and wait for its decision. */\n request(opts: {\n id: string;\n sessionId: string;\n binding: InteractionExecutionBinding;\n toolName: string;\n input?: unknown;\n allowlistGrant: PermissionGrant;\n timeoutMs?: number;\n signal?: AbortSignal;\n emit?: InteractionEventSink;\n }): Promise<PermissionGrant> {\n if (\n opts.binding.sessionId !== opts.sessionId ||\n this.hasPendingInteraction(opts.id)\n ) {\n return Promise.resolve(\"deny\");\n }\n\n const timeoutMs = checkedTimeout(\n opts.timeoutMs ?? this.decisionTimeoutMs,\n );\n const responseScopes: InteractionResponseScope[] =\n opts.allowlistGrant === \"allow_always\"\n ? [\"interaction\", \"session\", \"persistent\"]\n : opts.allowlistGrant === \"allow_session\"\n ? [\"interaction\", \"session\"]\n : [\"interaction\"];\n const request = bindInteractionRequest(opts.binding, {\n id: opts.id,\n kind: InteractionKind.Permission,\n title: `Allow tool \"${opts.toolName}\"?`,\n subject: {\n type: \"tool\",\n toolName: opts.toolName,\n ...(opts.input === undefined ? {} : { input: opts.input }),\n },\n answerSpec: permissionAnswerSpec({ responseScopes }),\n responseScopes,\n default:\n opts.allowlistGrant === \"deny\"\n ? { outcome: \"declined\" }\n : {\n outcome: \"accepted\",\n data: { [PERMISSION_GRANT_FIELD]: [opts.allowlistGrant] },\n },\n timeoutMs,\n onTimeout: \"default\",\n });\n\n return new Promise<PermissionGrant>((resolve) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const abort = () => settle(\"deny\");\n const settle = (grant: PermissionGrant) => {\n if (!this.pendingPermissions.delete(opts.id)) return;\n if (timer) clearTimeout(timer);\n opts.signal?.removeEventListener(\"abort\", abort);\n resolve(grant);\n };\n this.pendingPermissions.set(opts.id, {\n request,\n settle,\n });\n if (!opts.emit) {\n settle(opts.allowlistGrant);\n return;\n }\n timer = setTimeout(() => settle(opts.allowlistGrant), timeoutMs);\n if (opts.signal?.aborted) {\n settle(\"deny\");\n return;\n }\n opts.signal?.addEventListener(\"abort\", abort, { once: true });\n if (opts.signal?.aborted) {\n settle(\"deny\");\n return;\n }\n try {\n void Promise.resolve(opts.emit({ type: \"interaction\", request })).catch(\n () => settle(opts.allowlistGrant),\n );\n } catch {\n settle(opts.allowlistGrant);\n }\n });\n }\n\n /** Resolve a pending permission. Invalid or excessive grants deny. */\n respond(response: unknown): boolean {\n const id = responseId(response);\n if (!id) return false;\n const entry = this.pendingPermissions.get(id);\n if (!entry) return false;\n const validation = validateInteractionResponse(entry.request, response);\n if (!validation.ok || validation.response.outcome !== \"accepted\") {\n entry.settle(\"deny\");\n return true;\n }\n const raw = validation.response.data?.[PERMISSION_GRANT_FIELD];\n const selected =\n Array.isArray(raw) && raw.length === 1 ? raw[0] : undefined;\n entry.settle(coercePermissionGrant(selected));\n return true;\n }\n\n /** Emit a question and wait for positional answers. */\n requestQuestion(opts: {\n id: string;\n sessionId: string;\n binding: InteractionExecutionBinding;\n questions: InteractionQuestion[];\n timeoutMs?: number;\n signal?: AbortSignal;\n emit?: InteractionEventSink;\n }): Promise<string[][] | null> {\n if (\n opts.binding.sessionId !== opts.sessionId ||\n this.hasPendingInteraction(opts.id)\n ) {\n return Promise.resolve(null);\n }\n\n const timeoutMs = checkedTimeout(\n opts.timeoutMs ?? this.decisionTimeoutMs,\n );\n const request = bindInteractionRequest(opts.binding, {\n id: opts.id,\n kind: InteractionKind.Question,\n title: opts.questions[0]?.question ?? \"Question\",\n answerSpec: interactionAnswerSpecForQuestions(opts.questions),\n default: { outcome: \"cancelled\" },\n timeoutMs,\n onTimeout: \"default\",\n });\n\n return new Promise<string[][] | null>((resolve) => {\n let timer: ReturnType<typeof setTimeout> | undefined;\n const abort = () => settle(null);\n const settle = (answers: string[][] | null) => {\n if (!this.pendingQuestions.delete(opts.id)) return;\n if (timer) clearTimeout(timer);\n opts.signal?.removeEventListener(\"abort\", abort);\n resolve(answers);\n };\n this.pendingQuestions.set(opts.id, {\n request,\n settle,\n });\n if (!opts.emit) {\n settle(null);\n return;\n }\n timer = setTimeout(() => settle(null), timeoutMs);\n if (opts.signal?.aborted) {\n settle(null);\n return;\n }\n opts.signal?.addEventListener(\"abort\", abort, { once: true });\n if (opts.signal?.aborted) {\n settle(null);\n return;\n }\n try {\n void Promise.resolve(opts.emit({ type: \"interaction\", request })).catch(\n () => settle(null),\n );\n } catch {\n settle(null);\n }\n });\n }\n\n /** Resolve a pending question. Invalid, declined, and cancelled replies return null. */\n respondQuestion(response: unknown): boolean {\n const id = responseId(response);\n if (!id) return false;\n const entry = this.pendingQuestions.get(id);\n if (!entry) return false;\n const validation = validateInteractionResponse(entry.request, response);\n if (!validation.ok) {\n entry.settle(null);\n return true;\n }\n const validatedResponse = validation.response;\n entry.settle(\n validatedResponse.outcome === \"accepted\"\n ? interactionDataToQuestionAnswers(validatedResponse.data ?? {})\n : null,\n );\n return true;\n }\n\n /** Fail closed every request that belongs to one session. */\n failSession(sessionId: string): void {\n for (const entry of this.pendingPermissions.values()) {\n if (entry.request.binding.sessionId === sessionId) entry.settle(\"deny\");\n }\n for (const entry of this.pendingQuestions.values()) {\n if (entry.request.binding.sessionId === sessionId) entry.settle(null);\n }\n }\n\n /** Fail closed only the requests from one exact provider execution. */\n failExecution(binding: InteractionExecutionBinding): void {\n for (const entry of this.pendingPermissions.values()) {\n if (matchesExecution(entry.request, binding)) entry.settle(\"deny\");\n }\n for (const entry of this.pendingQuestions.values()) {\n if (matchesExecution(entry.request, binding)) entry.settle(null);\n }\n }\n}\n","import { randomUUID } from \"node:crypto\";\nimport type {\n DurablePlan,\n InteractionExecutionBinding,\n StreamEvent,\n} from \"@tangle-network/agent-interface\";\nimport type {\n InteractionBroker,\n InteractionQuestion,\n} from \"./broker.js\";\n\n/** Claude Code permission prompt callback. */\nexport type PermissionPromptBridge = (request: {\n tool_name: string;\n input: Record<string, unknown>;\n tool_use_id: string;\n operationId: string;\n signal?: AbortSignal;\n}) => Promise<unknown>;\n\n/** Question callback. Null means that no answer was provided. */\nexport type AskUserBridge = (request: {\n questions: InteractionQuestion[];\n operationId?: string;\n signal?: AbortSignal;\n}) => Promise<string[][] | null>;\n\n/** Generic action approval callback. */\nexport type RequestPermissionBridge = (request: {\n tool_name: string;\n input: Record<string, unknown>;\n operationId?: string;\n signal?: AbortSignal;\n}) => Promise<{ allowed: boolean; message?: string }>;\n\n/** Durable plan submission callback. */\nexport type RequestPlanBridge = (request: {\n plan: string;\n sourceToolCallId: string;\n}) => Promise<DurablePlan>;\n\nexport interface BrokerInteractionTools {\n askUser: AskUserBridge;\n requestPermission: RequestPermissionBridge;\n onStop: () => void;\n}\n\n/** Bind generic interaction tools to one broker session. */\nexport function brokerInteractionTools(\n broker: InteractionBroker,\n options: {\n sessionId: string;\n binding: InteractionExecutionBinding;\n timeoutMs?: number;\n emit?: (event: StreamEvent) => unknown;\n },\n): BrokerInteractionTools {\n return {\n askUser: (request) =>\n broker.requestQuestion({\n id: request.operationId ?? randomUUID(),\n sessionId: options.sessionId,\n binding: options.binding,\n questions: request.questions,\n timeoutMs: options.timeoutMs,\n signal: request.signal,\n emit: options.emit,\n }),\n requestPermission: async (request) => {\n const grant = await broker.request({\n id: request.operationId ?? randomUUID(),\n sessionId: options.sessionId,\n binding: options.binding,\n toolName: request.tool_name,\n input: request.input,\n allowlistGrant: \"deny\",\n timeoutMs: options.timeoutMs,\n signal: request.signal,\n emit: options.emit,\n });\n return { allowed: grant !== \"deny\" };\n },\n onStop: () => broker.failExecution(options.binding),\n };\n}\n","import { randomBytes } from \"node:crypto\";\nimport { createServer, type Server } from \"node:http\";\nimport type {\n InteractionExecutionBinding,\n StreamEvent,\n} from \"@tangle-network/agent-interface\";\nimport { z } from \"zod\";\nimport {\n type InteractionBroker,\n InteractionQuestionSchema,\n} from \"./broker.js\";\nimport {\n closeHttpServer,\n SerializedTransportLifecycle,\n} from \"./lifecycle.js\";\nimport {\n type AskUserBridge,\n brokerInteractionTools,\n type RequestPermissionBridge,\n} from \"./tools.js\";\n\nconst MAX_HTTP_BODY_BYTES = 1024 * 1024;\n\nconst permissionBodySchema = z.strictObject({\n tool_name: z.string().min(1),\n input: z.record(z.string(), z.unknown()).optional(),\n});\n\nconst questionBodySchema = z.strictObject({\n questions: z.array(InteractionQuestionSchema).min(1),\n});\n\n/**\n * Loopback JSON bridge for runners with an extension API but no MCP support.\n *\n * Extensions call `/permission` or `/question`. The bridge blocks until the\n * shared broker receives an operator response.\n */\nexport class InteractionHttpBridge {\n readonly token = randomBytes(24).toString(\"hex\");\n private readonly requestPermission: RequestPermissionBridge;\n private readonly askUser: AskUserBridge;\n private readonly broker: InteractionBroker;\n private readonly binding: InteractionExecutionBinding;\n private readonly lifecycle = new SerializedTransportLifecycle();\n private httpServer?: Server;\n private portValue = 0;\n\n constructor(\n broker: InteractionBroker,\n options: {\n sessionId: string;\n binding: InteractionExecutionBinding;\n timeoutMs?: number;\n emit?: (event: StreamEvent) => unknown;\n },\n ) {\n this.broker = broker;\n this.binding = options.binding;\n const tools = brokerInteractionTools(broker, options);\n this.requestPermission = tools.requestPermission;\n this.askUser = tools.askUser;\n }\n\n get port(): number {\n return this.portValue;\n }\n\n get baseUrl(): string {\n return `http://127.0.0.1:${this.portValue}`;\n }\n\n async start(): Promise<void> {\n return this.lifecycle.start(async () => {\n const httpServer = createServer((request, response) => {\n const send = (status: number, body?: unknown) => {\n if (response.writableEnded) return;\n const text = body === undefined ? \"\" : JSON.stringify(body);\n response\n .writeHead(status, { \"content-type\": \"application/json\" })\n .end(text);\n };\n\n if (request.headers.authorization !== `Bearer ${this.token}`) {\n send(401);\n return;\n }\n if (request.method !== \"POST\") {\n send(405);\n return;\n }\n\n const chunks: Buffer[] = [];\n let size = 0;\n request.on(\"error\", () => send(400));\n request.on(\"data\", (chunk: Buffer) => {\n size += chunk.length;\n if (size > MAX_HTTP_BODY_BYTES) {\n send(413);\n request.destroy();\n return;\n }\n chunks.push(chunk);\n });\n request.on(\"end\", () => {\n if (response.writableEnded) return;\n let body: unknown;\n try {\n const raw = Buffer.concat(chunks).toString(\"utf8\");\n body = raw ? JSON.parse(raw) : {};\n } catch {\n send(400, { error: \"invalid json\" });\n return;\n }\n this.route(request.url ?? \"\", body)\n .then((result) => send(result.status, result.body))\n .catch(() => send(500, { error: \"interaction bridge failed\" }));\n });\n });\n\n try {\n await new Promise<void>((resolve, reject) => {\n httpServer.once(\"error\", reject);\n httpServer.listen(0, \"127.0.0.1\", resolve);\n });\n } catch (error) {\n httpServer.closeAllConnections?.();\n throw error;\n }\n const address = httpServer.address();\n this.portValue =\n address && typeof address === \"object\" ? address.port : 0;\n this.httpServer = httpServer;\n });\n }\n\n async stop(): Promise<void> {\n return this.lifecycle.stop(async () => {\n this.broker.failExecution(this.binding);\n const httpServer = this.httpServer;\n this.httpServer = undefined;\n this.portValue = 0;\n if (httpServer) await closeHttpServer(httpServer);\n });\n }\n\n private async route(\n url: string,\n body: unknown,\n ): Promise<{ status: number; body: unknown }> {\n const path = new URL(url, \"http://127.0.0.1\").pathname.replace(/\\/+$/, \"\");\n if (path === \"/permission\") {\n const parsed = permissionBodySchema.safeParse(body);\n if (!parsed.success) {\n return { status: 400, body: { error: \"invalid permission request\" } };\n }\n const result = await this.requestPermission({\n tool_name: parsed.data.tool_name,\n input: parsed.data.input ?? {},\n });\n return {\n status: 200,\n body: {\n allowed: result.allowed,\n ...(result.message ? { reason: result.message } : {}),\n },\n };\n }\n if (path === \"/question\") {\n const parsed = questionBodySchema.safeParse(body);\n if (!parsed.success) {\n return { status: 400, body: { error: \"invalid question request\" } };\n }\n const answers = await this.askUser({ questions: parsed.data.questions });\n return { status: 200, body: { answers } };\n }\n return { status: 404, body: { error: \"unknown route\" } };\n }\n}\n"],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ import { a as RequestPlanBridge, i as RequestPermissionBridge, r as PermissionPromptBridge, t as AskUserBridge } from "../tools-D-I3A7Us.js";
2
+
3
+ //#region src/interactions/mcp-server.d.ts
4
+ declare const DEFAULT_INTERACTION_MCP_SERVER_NAME = "tangle-interaction";
5
+ /** Build the full tool name that a runner uses for an MCP tool. */
6
+ declare function interactionMcpToolName(serverName: string, tool: string): string;
7
+ interface InteractionMcpServerConfig {
8
+ serverName?: string;
9
+ onStop: () => void | Promise<void>;
10
+ permission?: PermissionPromptBridge;
11
+ askUser?: AskUserBridge;
12
+ requestPermission?: RequestPermissionBridge;
13
+ requestPlan?: RequestPlanBridge;
14
+ }
15
+ /**
16
+ * Per-execution loopback MCP server for questions, permissions, and plans.
17
+ *
18
+ * The server listens only on 127.0.0.1 and requires an ephemeral bearer token.
19
+ */
20
+ declare class InteractionMcpServer {
21
+ private readonly server;
22
+ private readonly transport;
23
+ private readonly onStop;
24
+ private readonly lifecycle;
25
+ private readonly requestSignal;
26
+ private readonly activePostRequests;
27
+ private httpServer?;
28
+ private portValue;
29
+ readonly serverName: string;
30
+ readonly token: string;
31
+ constructor(config: InteractionMcpServerConfig);
32
+ get port(): number;
33
+ get url(): string;
34
+ start(): Promise<void>;
35
+ stop(): Promise<void>;
36
+ private operationSignal;
37
+ private operationId;
38
+ private requestGenerationId;
39
+ }
40
+ //#endregion
41
+ export { DEFAULT_INTERACTION_MCP_SERVER_NAME, InteractionMcpServer, InteractionMcpServerConfig, interactionMcpToolName };
42
+ //# sourceMappingURL=mcp-server.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mcp-server.d.ts","names":[],"sources":["../../src/interactions/mcp-server.ts"],"mappings":";;;cA6Ba,mCAAA;;iBAqMG,sBAAA,CACd,UAAA,UACA,IAAA;AAAA,UAKe,0BAAA;EACf,UAAA;EACA,MAAA,eAAqB,OAAA;EACrB,UAAA,GAAa,sBAAA;EACb,OAAA,GAAU,aAAA;EACV,iBAAA,GAAoB,uBAAA;EACpB,WAAA,GAAc,iBAAA;AAAA;;AANhB;;;;cAqCa,oBAAA;EAAA,iBACM,MAAA;EAAA,iBACA,SAAA;EAAA,iBACA,MAAA;EAAA,iBACA,SAAA;EAAA,iBACA,aAAA;EAAA,iBACA,kBAAA;EAAA,QACT,UAAA;EAAA,QACA,SAAA;EAAA,SACC,UAAA;EAAA,SACA,KAAA;cAEG,MAAA,EAAQ,0BAAA;EAAA,IAiJhB,IAAA,CAAA;EAAA,IAIA,GAAA,CAAA;EAIE,KAAA,CAAA,GAAS,OAAA;EAiGT,IAAA,CAAA,GAAQ,OAAA;EAAA,QAYN,eAAA;EAAA,QAOA,WAAA;EAAA,QAcA,mBAAA;AAAA"}