@agent-native/core 0.18.1 → 0.19.1

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 (168) hide show
  1. package/README.md +1 -11
  2. package/dist/a2a/caller-auth.d.ts +1 -0
  3. package/dist/a2a/caller-auth.d.ts.map +1 -1
  4. package/dist/a2a/caller-auth.js +1 -1
  5. package/dist/a2a/caller-auth.js.map +1 -1
  6. package/dist/a2a/client.d.ts +7 -0
  7. package/dist/a2a/client.d.ts.map +1 -1
  8. package/dist/a2a/client.js +3 -0
  9. package/dist/a2a/client.js.map +1 -1
  10. package/dist/agent/production-agent.d.ts +1 -1
  11. package/dist/agent/production-agent.d.ts.map +1 -1
  12. package/dist/agent/production-agent.js +34 -2
  13. package/dist/agent/production-agent.js.map +1 -1
  14. package/dist/cli/code-agent-executor.d.ts.map +1 -1
  15. package/dist/cli/code-agent-executor.js +47 -256
  16. package/dist/cli/code-agent-executor.js.map +1 -1
  17. package/dist/cli/connect.d.ts +94 -0
  18. package/dist/cli/connect.d.ts.map +1 -0
  19. package/dist/cli/connect.js +443 -0
  20. package/dist/cli/connect.js.map +1 -0
  21. package/dist/cli/index.js +16 -0
  22. package/dist/cli/index.js.map +1 -1
  23. package/dist/cli/mcp-config-writers.d.ts +71 -0
  24. package/dist/cli/mcp-config-writers.d.ts.map +1 -0
  25. package/dist/cli/mcp-config-writers.js +210 -0
  26. package/dist/cli/mcp-config-writers.js.map +1 -0
  27. package/dist/client/AgentPanel.d.ts +3 -1
  28. package/dist/client/AgentPanel.d.ts.map +1 -1
  29. package/dist/client/AgentPanel.js +4 -4
  30. package/dist/client/AgentPanel.js.map +1 -1
  31. package/dist/client/AssistantChat.d.ts +3 -0
  32. package/dist/client/AssistantChat.d.ts.map +1 -1
  33. package/dist/client/AssistantChat.js +22 -66
  34. package/dist/client/AssistantChat.js.map +1 -1
  35. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  36. package/dist/client/MultiTabAssistantChat.js +4 -1
  37. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  38. package/dist/client/composer/PromptComposer.d.ts +6 -1
  39. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  40. package/dist/client/composer/PromptComposer.js +5 -4
  41. package/dist/client/composer/PromptComposer.js.map +1 -1
  42. package/dist/client/composer/TiptapComposer.d.ts +6 -1
  43. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  44. package/dist/client/composer/TiptapComposer.js +20 -10
  45. package/dist/client/composer/TiptapComposer.js.map +1 -1
  46. package/dist/client/conversation/AgentConversation.d.ts +18 -0
  47. package/dist/client/conversation/AgentConversation.d.ts.map +1 -0
  48. package/dist/client/conversation/AgentConversation.js +94 -0
  49. package/dist/client/conversation/AgentConversation.js.map +1 -0
  50. package/dist/client/conversation/AgentConversation.spec.d.ts +2 -0
  51. package/dist/client/conversation/AgentConversation.spec.d.ts.map +1 -0
  52. package/dist/client/conversation/AgentConversation.spec.js +69 -0
  53. package/dist/client/conversation/AgentConversation.spec.js.map +1 -0
  54. package/dist/client/conversation/index.d.ts +4 -0
  55. package/dist/client/conversation/index.d.ts.map +1 -0
  56. package/dist/client/conversation/index.js +3 -0
  57. package/dist/client/conversation/index.js.map +1 -0
  58. package/dist/client/conversation/types.d.ts +54 -0
  59. package/dist/client/conversation/types.d.ts.map +1 -0
  60. package/dist/client/conversation/types.js +2 -0
  61. package/dist/client/conversation/types.js.map +1 -0
  62. package/dist/client/conversation/use-near-bottom-autoscroll.d.ts +15 -0
  63. package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -0
  64. package/dist/client/conversation/use-near-bottom-autoscroll.js +66 -0
  65. package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -0
  66. package/dist/client/dynamic-suggestions.d.ts +43 -0
  67. package/dist/client/dynamic-suggestions.d.ts.map +1 -0
  68. package/dist/client/dynamic-suggestions.js +344 -0
  69. package/dist/client/dynamic-suggestions.js.map +1 -0
  70. package/dist/client/index.d.ts +2 -0
  71. package/dist/client/index.d.ts.map +1 -1
  72. package/dist/client/index.js +2 -0
  73. package/dist/client/index.js.map +1 -1
  74. package/dist/client/resources/ResourceTree.d.ts.map +1 -1
  75. package/dist/client/resources/ResourceTree.js +2 -2
  76. package/dist/client/resources/ResourceTree.js.map +1 -1
  77. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  78. package/dist/client/resources/ResourcesPanel.js +4 -28
  79. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  80. package/dist/client/settings/SettingsPanel.js +2 -2
  81. package/dist/client/settings/SettingsPanel.js.map +1 -1
  82. package/dist/code-agents/index.d.ts +1 -0
  83. package/dist/code-agents/index.d.ts.map +1 -1
  84. package/dist/code-agents/index.js +1 -0
  85. package/dist/code-agents/index.js.map +1 -1
  86. package/dist/code-agents/transcript-normalizer.d.ts +50 -0
  87. package/dist/code-agents/transcript-normalizer.d.ts.map +1 -0
  88. package/dist/code-agents/transcript-normalizer.js +356 -0
  89. package/dist/code-agents/transcript-normalizer.js.map +1 -0
  90. package/dist/coding-tools/index.d.ts +31 -0
  91. package/dist/coding-tools/index.d.ts.map +1 -0
  92. package/dist/coding-tools/index.js +411 -0
  93. package/dist/coding-tools/index.js.map +1 -0
  94. package/dist/extensions/schema.d.ts +1 -1
  95. package/dist/mcp/build-server.d.ts.map +1 -1
  96. package/dist/mcp/build-server.js +30 -0
  97. package/dist/mcp/build-server.js.map +1 -1
  98. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  99. package/dist/mcp/builtin-tools.js +85 -26
  100. package/dist/mcp/builtin-tools.js.map +1 -1
  101. package/dist/mcp/connect-route.d.ts +43 -0
  102. package/dist/mcp/connect-route.d.ts.map +1 -0
  103. package/dist/mcp/connect-route.js +744 -0
  104. package/dist/mcp/connect-route.js.map +1 -0
  105. package/dist/mcp/connect-store.d.ts +132 -0
  106. package/dist/mcp/connect-store.d.ts.map +1 -0
  107. package/dist/mcp/connect-store.js +434 -0
  108. package/dist/mcp/connect-store.js.map +1 -0
  109. package/dist/mcp/org-directory.d.ts +83 -0
  110. package/dist/mcp/org-directory.d.ts.map +1 -0
  111. package/dist/mcp/org-directory.js +201 -0
  112. package/dist/mcp/org-directory.js.map +1 -0
  113. package/dist/mcp/server.d.ts +38 -1
  114. package/dist/mcp/server.d.ts.map +1 -1
  115. package/dist/mcp/server.js +208 -77
  116. package/dist/mcp/server.js.map +1 -1
  117. package/dist/scripts/dev/index.d.ts +6 -4
  118. package/dist/scripts/dev/index.d.ts.map +1 -1
  119. package/dist/scripts/dev/index.js +28 -13
  120. package/dist/scripts/dev/index.js.map +1 -1
  121. package/dist/server/agent-chat-plugin.d.ts +6 -6
  122. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  123. package/dist/server/agent-chat-plugin.js +32 -32
  124. package/dist/server/agent-chat-plugin.js.map +1 -1
  125. package/dist/server/agent-teams.js +2 -2
  126. package/dist/server/agent-teams.js.map +1 -1
  127. package/dist/server/agents-bundle.d.ts +3 -3
  128. package/dist/server/agents-bundle.js +5 -5
  129. package/dist/server/agents-bundle.js.map +1 -1
  130. package/dist/server/auth.d.ts +17 -0
  131. package/dist/server/auth.d.ts.map +1 -1
  132. package/dist/server/auth.js +149 -33
  133. package/dist/server/auth.js.map +1 -1
  134. package/dist/server/better-auth-instance.d.ts +43 -0
  135. package/dist/server/better-auth-instance.d.ts.map +1 -1
  136. package/dist/server/better-auth-instance.js +25 -0
  137. package/dist/server/better-auth-instance.js.map +1 -1
  138. package/dist/server/core-routes-plugin.d.ts +12 -0
  139. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  140. package/dist/server/core-routes-plugin.js +42 -0
  141. package/dist/server/core-routes-plugin.js.map +1 -1
  142. package/dist/server/identity-sso-store.d.ts +86 -0
  143. package/dist/server/identity-sso-store.d.ts.map +1 -0
  144. package/dist/server/identity-sso-store.js +243 -0
  145. package/dist/server/identity-sso-store.js.map +1 -0
  146. package/dist/server/identity-sso.d.ts +78 -0
  147. package/dist/server/identity-sso.d.ts.map +1 -0
  148. package/dist/server/identity-sso.js +425 -0
  149. package/dist/server/identity-sso.js.map +1 -0
  150. package/dist/server/index.d.ts +1 -0
  151. package/dist/server/index.d.ts.map +1 -1
  152. package/dist/server/index.js +1 -0
  153. package/dist/server/index.js.map +1 -1
  154. package/dist/server/onboarding-html.d.ts.map +1 -1
  155. package/dist/server/onboarding-html.js +2 -1
  156. package/dist/server/onboarding-html.js.map +1 -1
  157. package/dist/server/sentry.d.ts.map +1 -1
  158. package/dist/server/sentry.js +17 -2
  159. package/dist/server/sentry.js.map +1 -1
  160. package/dist/sharing/schema.d.ts +1 -1
  161. package/docs/content/client.md +15 -0
  162. package/docs/content/code-agents-ui.md +25 -4
  163. package/docs/content/cross-app-sso.md +118 -0
  164. package/docs/content/drop-in-agent.md +3 -1
  165. package/docs/content/external-agents.md +130 -51
  166. package/docs/content/frames.md +1 -1
  167. package/docs/content/migration-workbench.md +6 -1
  168. package/package.json +2 -1
@@ -0,0 +1,411 @@
1
+ import { spawn } from "node:child_process";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
4
+ const DEFAULT_COMMAND_TIMEOUT_MS = 30_000;
5
+ const DEFAULT_MAX_OUTPUT_CHARS = 50_000;
6
+ const DEFAULT_MAX_FILE_READ_CHARS = 120_000;
7
+ const mutationQueues = new Map();
8
+ export function createCodingToolRegistry(options = {}) {
9
+ const cwd = path.resolve(options.cwd ?? process.cwd());
10
+ const restrictToCwd = options.restrictToCwd ?? false;
11
+ const commandTimeoutMs = options.commandTimeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;
12
+ const maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;
13
+ const maxFileReadChars = options.maxFileReadChars ?? DEFAULT_MAX_FILE_READ_CHARS;
14
+ return {
15
+ bash: {
16
+ tool: {
17
+ description: "Run a bash command. Use this for file discovery and search (rg --files, rg, find, ls), tests, builds, package commands, git status/diff, and project CLIs.",
18
+ parameters: {
19
+ type: "object",
20
+ properties: {
21
+ command: {
22
+ type: "string",
23
+ description: "The bash command to run.",
24
+ },
25
+ cwd: {
26
+ type: "string",
27
+ description: "Optional working directory, relative to the workspace unless absolute paths are allowed.",
28
+ },
29
+ timeoutMs: {
30
+ type: "string",
31
+ description: "Optional timeout in milliseconds.",
32
+ },
33
+ stdin: {
34
+ type: "string",
35
+ description: "Optional stdin to pipe into the command.",
36
+ },
37
+ },
38
+ required: ["command"],
39
+ },
40
+ },
41
+ run: async (args) => {
42
+ const command = stringArg(args.command);
43
+ if (!command)
44
+ return "Error: command is required.";
45
+ const commandCwd = resolveCodingPath(cwd, stringArg(args.cwd) || ".", {
46
+ restrictToCwd,
47
+ allowEmpty: true,
48
+ }) ?? "";
49
+ if (!commandCwd) {
50
+ return "Error: cwd must stay inside the workspace.";
51
+ }
52
+ const requestedTimeoutMs = Number(args.timeoutMs);
53
+ const timeoutMs = Number.isFinite(requestedTimeoutMs) && requestedTimeoutMs > 0
54
+ ? Math.min(requestedTimeoutMs, 10 * 60_000)
55
+ : commandTimeoutMs;
56
+ const policyResult = (await options.beforeBash?.({
57
+ command,
58
+ cwd: commandCwd,
59
+ timeoutMs,
60
+ })) ?? null;
61
+ if (policyResult)
62
+ return policyResult;
63
+ const result = await runCodingCommand(command, commandCwd, timeoutMs, {
64
+ stdin: stringArg(args.stdin) || undefined,
65
+ });
66
+ if (options.bashThrowsOnNonZero && result.code !== 0) {
67
+ throw new Error(formatCodingCommandResult(result, maxOutputChars));
68
+ }
69
+ return formatCodingCommandResult(result, maxOutputChars, {
70
+ omitEmptyExitCode: options.bashThrowsOnNonZero && result.code === 0,
71
+ });
72
+ },
73
+ },
74
+ read: {
75
+ readOnly: true,
76
+ tool: {
77
+ description: "Read a UTF-8 text file with line numbers. Use bash for directories, file lists, and search.",
78
+ parameters: {
79
+ type: "object",
80
+ properties: {
81
+ path: {
82
+ type: "string",
83
+ description: "File path to read.",
84
+ },
85
+ offset: {
86
+ type: "string",
87
+ description: "1-based line number to start reading from.",
88
+ },
89
+ limit: {
90
+ type: "string",
91
+ description: "Maximum number of lines to read.",
92
+ },
93
+ },
94
+ required: ["path"],
95
+ },
96
+ },
97
+ run: async (args) => {
98
+ const requestedPath = stringArg(args.path);
99
+ const filePath = resolveCodingPath(cwd, requestedPath, {
100
+ restrictToCwd,
101
+ });
102
+ if (!filePath)
103
+ return "Error: path must stay inside the workspace.";
104
+ if (!fs.existsSync(filePath)) {
105
+ return `Error: file not found: ${requestedPath}`;
106
+ }
107
+ const stat = fs.statSync(filePath);
108
+ if (!stat.isFile()) {
109
+ return `Error: ${requestedPath} is not a file. Use bash for directories and file lists.`;
110
+ }
111
+ const content = fs.readFileSync(filePath, "utf8");
112
+ return truncateCodingOutput(formatFileReadOutput(cwd, filePath, content, args), maxFileReadChars);
113
+ },
114
+ },
115
+ edit: {
116
+ tool: {
117
+ description: "Edit an existing UTF-8 text file by replacing exact text. Prefer this for focused source changes. oldText must match exactly and uniquely unless replaceAll is true. For batch edits, pass edits as a JSON array of {oldText,newText,replaceAll}.",
118
+ parameters: {
119
+ type: "object",
120
+ properties: {
121
+ path: {
122
+ type: "string",
123
+ description: "File path to edit.",
124
+ },
125
+ oldText: {
126
+ type: "string",
127
+ description: "Exact text to replace for a single edit.",
128
+ },
129
+ newText: {
130
+ type: "string",
131
+ description: "Replacement text for a single edit.",
132
+ },
133
+ replaceAll: {
134
+ type: "string",
135
+ description: 'Set to "true" to replace every occurrence.',
136
+ enum: ["true", "false"],
137
+ },
138
+ edits: {
139
+ type: "string",
140
+ description: 'Optional JSON array of edits, e.g. [{"oldText":"foo","newText":"bar"}].',
141
+ },
142
+ },
143
+ required: ["path"],
144
+ },
145
+ },
146
+ run: async (args) => {
147
+ const permissionError = options.canWrite?.("edit") ?? null;
148
+ if (permissionError)
149
+ return permissionError;
150
+ const requestedPath = stringArg(args.path);
151
+ const filePath = resolveCodingPath(cwd, requestedPath, {
152
+ restrictToCwd,
153
+ });
154
+ if (!filePath)
155
+ return "Error: path must stay inside the workspace.";
156
+ const edits = parseEditOperations(args);
157
+ return queueFileMutation(filePath, async () => {
158
+ if (!fs.existsSync(filePath)) {
159
+ throw new Error(`file not found: ${requestedPath}`);
160
+ }
161
+ const stat = fs.statSync(filePath);
162
+ if (!stat.isFile()) {
163
+ throw new Error(`${requestedPath} is not a file`);
164
+ }
165
+ let content = fs.readFileSync(filePath, "utf8");
166
+ let replacements = 0;
167
+ for (const edit of edits) {
168
+ const count = countOccurrences(content, edit.oldText);
169
+ if (count === 0) {
170
+ throw new Error(`oldText was not found in ${requestedPath}: ${previewText(edit.oldText)}`);
171
+ }
172
+ if (!edit.replaceAll && count !== 1) {
173
+ throw new Error(`oldText matched ${count} times in ${requestedPath}; make it unique or set replaceAll=true.`);
174
+ }
175
+ content = edit.replaceAll
176
+ ? content.split(edit.oldText).join(edit.newText)
177
+ : content.replace(edit.oldText, edit.newText);
178
+ replacements += edit.replaceAll ? count : 1;
179
+ }
180
+ fs.writeFileSync(filePath, content, "utf8");
181
+ return `Edited ${path.relative(cwd, filePath) || requestedPath} (${replacements} replacement${replacements === 1 ? "" : "s"}).`;
182
+ });
183
+ },
184
+ },
185
+ write: {
186
+ tool: {
187
+ description: "Create or fully overwrite a UTF-8 text file. Prefer edit for existing files unless a complete rewrite is intended.",
188
+ parameters: {
189
+ type: "object",
190
+ properties: {
191
+ path: {
192
+ type: "string",
193
+ description: "File path to write.",
194
+ },
195
+ content: {
196
+ type: "string",
197
+ description: "Full file content.",
198
+ },
199
+ },
200
+ required: ["path", "content"],
201
+ },
202
+ },
203
+ run: async (args) => {
204
+ const permissionError = options.canWrite?.("write") ?? null;
205
+ if (permissionError)
206
+ return permissionError;
207
+ const requestedPath = stringArg(args.path);
208
+ const filePath = resolveCodingPath(cwd, requestedPath, {
209
+ restrictToCwd,
210
+ });
211
+ if (!filePath)
212
+ return "Error: path must stay inside the workspace.";
213
+ const content = stringArg(args.content);
214
+ return queueFileMutation(filePath, async () => {
215
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
216
+ const existed = fs.existsSync(filePath);
217
+ fs.writeFileSync(filePath, content, "utf8");
218
+ const bytes = Buffer.byteLength(content, "utf8");
219
+ const lines = content.split("\n").length;
220
+ return `${existed ? "Updated" : "Created"} ${path.relative(cwd, filePath) || requestedPath} (${lines} lines, ${bytes} bytes).`;
221
+ });
222
+ },
223
+ },
224
+ };
225
+ }
226
+ export async function runCodingCommand(command, cwd, timeoutMs, options = {}) {
227
+ const child = spawn(command, {
228
+ cwd,
229
+ shell: true,
230
+ stdio: ["pipe", "pipe", "pipe"],
231
+ env: { ...process.env, FORCE_COLOR: "0" },
232
+ });
233
+ let stdout = "";
234
+ let stderr = "";
235
+ let timedOut = false;
236
+ const timer = setTimeout(() => {
237
+ timedOut = true;
238
+ child.kill("SIGTERM");
239
+ }, timeoutMs);
240
+ child.stdout?.on("data", (chunk) => {
241
+ stdout += chunk.toString();
242
+ });
243
+ child.stderr?.on("data", (chunk) => {
244
+ stderr += chunk.toString();
245
+ });
246
+ if (options.stdin)
247
+ child.stdin?.end(options.stdin);
248
+ else
249
+ child.stdin?.end();
250
+ const code = await new Promise((resolve, reject) => {
251
+ child.once("error", reject);
252
+ child.once("exit", resolve);
253
+ });
254
+ clearTimeout(timer);
255
+ return { code, stdout, stderr, timedOut };
256
+ }
257
+ export function formatCodingCommandResult(result, maxChars = DEFAULT_MAX_OUTPUT_CHARS, options = {}) {
258
+ const parts = [
259
+ options.omitEmptyExitCode && result.code === 0
260
+ ? ""
261
+ : `exitCode: ${result.code}`,
262
+ result.timedOut ? "timedOut: true" : "",
263
+ result.stdout ? `stdout:\n${result.stdout}` : "",
264
+ result.stderr ? `stderr:\n${result.stderr}` : "",
265
+ ].filter(Boolean);
266
+ return truncateCodingOutput(parts.join("\n\n") || "(no output)", maxChars);
267
+ }
268
+ export function truncateCodingOutput(value, max) {
269
+ if (value.length <= max)
270
+ return value;
271
+ return `${value.slice(0, max)}\n\n...[truncated ${value.length - max} chars]`;
272
+ }
273
+ export function isReadOnlyShellCommand(command) {
274
+ const normalized = command.trim().toLowerCase();
275
+ if (!normalized)
276
+ return false;
277
+ // Read-only modes get a deliberately tiny shell grammar: one command only,
278
+ // no redirection, pipes, sequencing, backgrounding, or command substitution.
279
+ // Prefix allowlists are not safe until these shell forms are excluded.
280
+ if (/[\n\r;&|<>]/.test(normalized))
281
+ return false;
282
+ if (/\$\(|`|\${|\\\n/.test(command))
283
+ return false;
284
+ // `sed` can WRITE even in `-n` mode via the `w`/`W` commands or `-i`
285
+ // (e.g. `sed -n '1w out.txt' file`), so the `^sed -n` allowlist entry
286
+ // below is not safe on its own. Reject any sed that can write.
287
+ if (/^sed\b/.test(normalized)) {
288
+ if (/(^|\s)-i(\b|=)|--in-place/.test(normalized))
289
+ return false;
290
+ // `w`/`W` used as a sed command: preceded by an address/separator
291
+ // (digit, $, /, }, ;, quote, space) and followed by a filename arg or
292
+ // end. Catches `1w f`, `$w f`, `/re/w f`, `s/x/y/w f`, `2W f`; leaves
293
+ // prints like `/window/p`, `1,5p`, `s/a/b/` untouched.
294
+ if (/[\s'"0-9$}/;](w|W)([\s'"]|$)/.test(normalized))
295
+ return false;
296
+ }
297
+ const allowedPrefixes = [
298
+ /^pwd\b/,
299
+ /^ls\b/,
300
+ /^find\b/,
301
+ /^rg\b/,
302
+ /^grep\b/,
303
+ /^cat\b/,
304
+ /^sed\s+-n\b/,
305
+ /^head\b/,
306
+ /^tail\b/,
307
+ /^wc\b/,
308
+ /^git\s+(status|diff|show|log)\b/,
309
+ /^git\s+branch\s+--show-current\b/,
310
+ ];
311
+ return allowedPrefixes.some((pattern) => pattern.test(normalized));
312
+ }
313
+ function resolveCodingPath(cwd, value, options) {
314
+ if (!value.trim() && !options.allowEmpty)
315
+ return null;
316
+ const target = value.trim() || ".";
317
+ const resolved = path.isAbsolute(target)
318
+ ? path.resolve(target)
319
+ : path.resolve(cwd, target);
320
+ if (!options.restrictToCwd)
321
+ return resolved;
322
+ const relative = path.relative(cwd, resolved);
323
+ if (relative.startsWith("..") || path.isAbsolute(relative))
324
+ return null;
325
+ return resolved;
326
+ }
327
+ function formatFileReadOutput(cwd, filePath, content, args) {
328
+ const lines = content.split("\n");
329
+ const offset = positiveInteger(args.offset, 1);
330
+ const limit = positiveInteger(args.limit, lines.length - offset + 1);
331
+ const selected = lines.slice(offset - 1, offset - 1 + limit);
332
+ const body = selected
333
+ .map((line, index) => `${String(offset + index).padStart(5)} | ${line}`)
334
+ .join("\n");
335
+ return `${path.relative(cwd, filePath) || filePath} (${lines.length} lines)\n${body}`;
336
+ }
337
+ function parseEditOperations(args) {
338
+ const editsJson = stringArg(args.edits);
339
+ if (editsJson.trim()) {
340
+ let parsed;
341
+ try {
342
+ parsed = JSON.parse(editsJson);
343
+ }
344
+ catch (err) {
345
+ throw new Error(`edits must be valid JSON: ${err instanceof Error ? err.message : String(err)}`);
346
+ }
347
+ if (!Array.isArray(parsed) || parsed.length === 0) {
348
+ throw new Error("edits must be a non-empty JSON array.");
349
+ }
350
+ return parsed.map((edit, index) => {
351
+ if (!edit || typeof edit !== "object") {
352
+ throw new Error(`edits[${index}] must be an object.`);
353
+ }
354
+ return normalizeEditOperation(edit, index);
355
+ });
356
+ }
357
+ return [normalizeEditOperation(args, 0)];
358
+ }
359
+ function normalizeEditOperation(edit, index) {
360
+ const oldText = typeof edit.oldText === "string" ? edit.oldText : undefined;
361
+ const newText = typeof edit.newText === "string" ? edit.newText : undefined;
362
+ if (!oldText) {
363
+ throw new Error(index === 0
364
+ ? "oldText is required and cannot be empty."
365
+ : `edits[${index}].oldText is required and cannot be empty.`);
366
+ }
367
+ if (newText === undefined) {
368
+ throw new Error(index === 0
369
+ ? "newText is required."
370
+ : `edits[${index}].newText is required.`);
371
+ }
372
+ return {
373
+ oldText,
374
+ newText,
375
+ replaceAll: stringArg(edit.replaceAll).toLowerCase() === "true",
376
+ };
377
+ }
378
+ function countOccurrences(value, needle) {
379
+ let count = 0;
380
+ let index = 0;
381
+ while (true) {
382
+ index = value.indexOf(needle, index);
383
+ if (index === -1)
384
+ return count;
385
+ count += 1;
386
+ index += needle.length;
387
+ }
388
+ }
389
+ function queueFileMutation(filePath, task) {
390
+ const previous = mutationQueues.get(filePath) ?? Promise.resolve();
391
+ const next = previous.catch(() => undefined).then(task);
392
+ let queued;
393
+ queued = next.finally(() => {
394
+ if (mutationQueues.get(filePath) === queued)
395
+ mutationQueues.delete(filePath);
396
+ });
397
+ mutationQueues.set(filePath, queued);
398
+ return next;
399
+ }
400
+ function previewText(value) {
401
+ const oneLine = value.replace(/\s+/g, " ").trim();
402
+ return oneLine.length > 80 ? `${oneLine.slice(0, 80)}...` : oneLine;
403
+ }
404
+ function stringArg(value) {
405
+ return typeof value === "string" ? value : "";
406
+ }
407
+ function positiveInteger(value, fallback) {
408
+ const parsed = Number(value);
409
+ return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;
410
+ }
411
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/coding-tools/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { spawn } from \"node:child_process\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport type { ActionEntry } from \"../agent/production-agent.js\";\n\nexport interface CodingCommandResult {\n code: number | null;\n stdout: string;\n stderr: string;\n timedOut: boolean;\n}\n\nexport interface CreateCodingToolRegistryOptions {\n cwd?: string;\n restrictToCwd?: boolean;\n commandTimeoutMs?: number;\n maxOutputChars?: number;\n maxFileReadChars?: number;\n bashThrowsOnNonZero?: boolean;\n canWrite?: (toolName: \"edit\" | \"write\") => string | null;\n beforeBash?: (input: {\n command: string;\n cwd: string;\n timeoutMs: number;\n }) => string | null | Promise<string | null>;\n}\n\ninterface EditOperation {\n oldText: string;\n newText: string;\n replaceAll: boolean;\n}\n\nconst DEFAULT_COMMAND_TIMEOUT_MS = 30_000;\nconst DEFAULT_MAX_OUTPUT_CHARS = 50_000;\nconst DEFAULT_MAX_FILE_READ_CHARS = 120_000;\n\nconst mutationQueues = new Map<string, Promise<unknown>>();\n\nexport function createCodingToolRegistry(\n options: CreateCodingToolRegistryOptions = {},\n): Record<\"bash\" | \"read\" | \"edit\" | \"write\", ActionEntry> {\n const cwd = path.resolve(options.cwd ?? process.cwd());\n const restrictToCwd = options.restrictToCwd ?? false;\n const commandTimeoutMs =\n options.commandTimeoutMs ?? DEFAULT_COMMAND_TIMEOUT_MS;\n const maxOutputChars = options.maxOutputChars ?? DEFAULT_MAX_OUTPUT_CHARS;\n const maxFileReadChars =\n options.maxFileReadChars ?? DEFAULT_MAX_FILE_READ_CHARS;\n\n return {\n bash: {\n tool: {\n description:\n \"Run a bash command. Use this for file discovery and search (rg --files, rg, find, ls), tests, builds, package commands, git status/diff, and project CLIs.\",\n parameters: {\n type: \"object\",\n properties: {\n command: {\n type: \"string\",\n description: \"The bash command to run.\",\n },\n cwd: {\n type: \"string\",\n description:\n \"Optional working directory, relative to the workspace unless absolute paths are allowed.\",\n },\n timeoutMs: {\n type: \"string\",\n description: \"Optional timeout in milliseconds.\",\n },\n stdin: {\n type: \"string\",\n description: \"Optional stdin to pipe into the command.\",\n },\n },\n required: [\"command\"],\n },\n },\n run: async (args) => {\n const command = stringArg(args.command);\n if (!command) return \"Error: command is required.\";\n const commandCwd =\n resolveCodingPath(cwd, stringArg(args.cwd) || \".\", {\n restrictToCwd,\n allowEmpty: true,\n }) ?? \"\";\n if (!commandCwd) {\n return \"Error: cwd must stay inside the workspace.\";\n }\n const requestedTimeoutMs = Number(args.timeoutMs);\n const timeoutMs =\n Number.isFinite(requestedTimeoutMs) && requestedTimeoutMs > 0\n ? Math.min(requestedTimeoutMs, 10 * 60_000)\n : commandTimeoutMs;\n\n const policyResult =\n (await options.beforeBash?.({\n command,\n cwd: commandCwd,\n timeoutMs,\n })) ?? null;\n if (policyResult) return policyResult;\n\n const result = await runCodingCommand(command, commandCwd, timeoutMs, {\n stdin: stringArg(args.stdin) || undefined,\n });\n if (options.bashThrowsOnNonZero && result.code !== 0) {\n throw new Error(formatCodingCommandResult(result, maxOutputChars));\n }\n return formatCodingCommandResult(result, maxOutputChars, {\n omitEmptyExitCode: options.bashThrowsOnNonZero && result.code === 0,\n });\n },\n },\n read: {\n readOnly: true,\n tool: {\n description:\n \"Read a UTF-8 text file with line numbers. Use bash for directories, file lists, and search.\",\n parameters: {\n type: \"object\",\n properties: {\n path: {\n type: \"string\",\n description: \"File path to read.\",\n },\n offset: {\n type: \"string\",\n description: \"1-based line number to start reading from.\",\n },\n limit: {\n type: \"string\",\n description: \"Maximum number of lines to read.\",\n },\n },\n required: [\"path\"],\n },\n },\n run: async (args) => {\n const requestedPath = stringArg(args.path);\n const filePath = resolveCodingPath(cwd, requestedPath, {\n restrictToCwd,\n });\n if (!filePath) return \"Error: path must stay inside the workspace.\";\n if (!fs.existsSync(filePath)) {\n return `Error: file not found: ${requestedPath}`;\n }\n const stat = fs.statSync(filePath);\n if (!stat.isFile()) {\n return `Error: ${requestedPath} is not a file. Use bash for directories and file lists.`;\n }\n const content = fs.readFileSync(filePath, \"utf8\");\n return truncateCodingOutput(\n formatFileReadOutput(cwd, filePath, content, args),\n maxFileReadChars,\n );\n },\n },\n edit: {\n tool: {\n description:\n \"Edit an existing UTF-8 text file by replacing exact text. Prefer this for focused source changes. oldText must match exactly and uniquely unless replaceAll is true. For batch edits, pass edits as a JSON array of {oldText,newText,replaceAll}.\",\n parameters: {\n type: \"object\",\n properties: {\n path: {\n type: \"string\",\n description: \"File path to edit.\",\n },\n oldText: {\n type: \"string\",\n description: \"Exact text to replace for a single edit.\",\n },\n newText: {\n type: \"string\",\n description: \"Replacement text for a single edit.\",\n },\n replaceAll: {\n type: \"string\",\n description: 'Set to \"true\" to replace every occurrence.',\n enum: [\"true\", \"false\"],\n },\n edits: {\n type: \"string\",\n description:\n 'Optional JSON array of edits, e.g. [{\"oldText\":\"foo\",\"newText\":\"bar\"}].',\n },\n },\n required: [\"path\"],\n },\n },\n run: async (args) => {\n const permissionError = options.canWrite?.(\"edit\") ?? null;\n if (permissionError) return permissionError;\n\n const requestedPath = stringArg(args.path);\n const filePath = resolveCodingPath(cwd, requestedPath, {\n restrictToCwd,\n });\n if (!filePath) return \"Error: path must stay inside the workspace.\";\n const edits = parseEditOperations(args);\n\n return queueFileMutation(filePath, async () => {\n if (!fs.existsSync(filePath)) {\n throw new Error(`file not found: ${requestedPath}`);\n }\n const stat = fs.statSync(filePath);\n if (!stat.isFile()) {\n throw new Error(`${requestedPath} is not a file`);\n }\n\n let content = fs.readFileSync(filePath, \"utf8\");\n let replacements = 0;\n for (const edit of edits) {\n const count = countOccurrences(content, edit.oldText);\n if (count === 0) {\n throw new Error(\n `oldText was not found in ${requestedPath}: ${previewText(\n edit.oldText,\n )}`,\n );\n }\n if (!edit.replaceAll && count !== 1) {\n throw new Error(\n `oldText matched ${count} times in ${requestedPath}; make it unique or set replaceAll=true.`,\n );\n }\n content = edit.replaceAll\n ? content.split(edit.oldText).join(edit.newText)\n : content.replace(edit.oldText, edit.newText);\n replacements += edit.replaceAll ? count : 1;\n }\n\n fs.writeFileSync(filePath, content, \"utf8\");\n return `Edited ${path.relative(cwd, filePath) || requestedPath} (${replacements} replacement${replacements === 1 ? \"\" : \"s\"}).`;\n });\n },\n },\n write: {\n tool: {\n description:\n \"Create or fully overwrite a UTF-8 text file. Prefer edit for existing files unless a complete rewrite is intended.\",\n parameters: {\n type: \"object\",\n properties: {\n path: {\n type: \"string\",\n description: \"File path to write.\",\n },\n content: {\n type: \"string\",\n description: \"Full file content.\",\n },\n },\n required: [\"path\", \"content\"],\n },\n },\n run: async (args) => {\n const permissionError = options.canWrite?.(\"write\") ?? null;\n if (permissionError) return permissionError;\n\n const requestedPath = stringArg(args.path);\n const filePath = resolveCodingPath(cwd, requestedPath, {\n restrictToCwd,\n });\n if (!filePath) return \"Error: path must stay inside the workspace.\";\n const content = stringArg(args.content);\n\n return queueFileMutation(filePath, async () => {\n fs.mkdirSync(path.dirname(filePath), { recursive: true });\n const existed = fs.existsSync(filePath);\n fs.writeFileSync(filePath, content, \"utf8\");\n const bytes = Buffer.byteLength(content, \"utf8\");\n const lines = content.split(\"\\n\").length;\n return `${existed ? \"Updated\" : \"Created\"} ${path.relative(cwd, filePath) || requestedPath} (${lines} lines, ${bytes} bytes).`;\n });\n },\n },\n };\n}\n\nexport async function runCodingCommand(\n command: string,\n cwd: string,\n timeoutMs: number,\n options: { stdin?: string } = {},\n): Promise<CodingCommandResult> {\n const child = spawn(command, {\n cwd,\n shell: true,\n stdio: [\"pipe\", \"pipe\", \"pipe\"],\n env: { ...process.env, FORCE_COLOR: \"0\" },\n });\n let stdout = \"\";\n let stderr = \"\";\n let timedOut = false;\n const timer = setTimeout(() => {\n timedOut = true;\n child.kill(\"SIGTERM\");\n }, timeoutMs);\n child.stdout?.on(\"data\", (chunk) => {\n stdout += chunk.toString();\n });\n child.stderr?.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n if (options.stdin) child.stdin?.end(options.stdin);\n else child.stdin?.end();\n const code = await new Promise<number | null>((resolve, reject) => {\n child.once(\"error\", reject);\n child.once(\"exit\", resolve);\n });\n clearTimeout(timer);\n return { code, stdout, stderr, timedOut };\n}\n\nexport function formatCodingCommandResult(\n result: CodingCommandResult,\n maxChars = DEFAULT_MAX_OUTPUT_CHARS,\n options: { omitEmptyExitCode?: boolean } = {},\n): string {\n const parts = [\n options.omitEmptyExitCode && result.code === 0\n ? \"\"\n : `exitCode: ${result.code}`,\n result.timedOut ? \"timedOut: true\" : \"\",\n result.stdout ? `stdout:\\n${result.stdout}` : \"\",\n result.stderr ? `stderr:\\n${result.stderr}` : \"\",\n ].filter(Boolean);\n return truncateCodingOutput(parts.join(\"\\n\\n\") || \"(no output)\", maxChars);\n}\n\nexport function truncateCodingOutput(value: string, max: number): string {\n if (value.length <= max) return value;\n return `${value.slice(0, max)}\\n\\n...[truncated ${value.length - max} chars]`;\n}\n\nexport function isReadOnlyShellCommand(command: string): boolean {\n const normalized = command.trim().toLowerCase();\n if (!normalized) return false;\n\n // Read-only modes get a deliberately tiny shell grammar: one command only,\n // no redirection, pipes, sequencing, backgrounding, or command substitution.\n // Prefix allowlists are not safe until these shell forms are excluded.\n if (/[\\n\\r;&|<>]/.test(normalized)) return false;\n if (/\\$\\(|`|\\${|\\\\\\n/.test(command)) return false;\n\n // `sed` can WRITE even in `-n` mode via the `w`/`W` commands or `-i`\n // (e.g. `sed -n '1w out.txt' file`), so the `^sed -n` allowlist entry\n // below is not safe on its own. Reject any sed that can write.\n if (/^sed\\b/.test(normalized)) {\n if (/(^|\\s)-i(\\b|=)|--in-place/.test(normalized)) return false;\n // `w`/`W` used as a sed command: preceded by an address/separator\n // (digit, $, /, }, ;, quote, space) and followed by a filename arg or\n // end. Catches `1w f`, `$w f`, `/re/w f`, `s/x/y/w f`, `2W f`; leaves\n // prints like `/window/p`, `1,5p`, `s/a/b/` untouched.\n if (/[\\s'\"0-9$}/;](w|W)([\\s'\"]|$)/.test(normalized)) return false;\n }\n\n const allowedPrefixes = [\n /^pwd\\b/,\n /^ls\\b/,\n /^find\\b/,\n /^rg\\b/,\n /^grep\\b/,\n /^cat\\b/,\n /^sed\\s+-n\\b/,\n /^head\\b/,\n /^tail\\b/,\n /^wc\\b/,\n /^git\\s+(status|diff|show|log)\\b/,\n /^git\\s+branch\\s+--show-current\\b/,\n ];\n return allowedPrefixes.some((pattern) => pattern.test(normalized));\n}\n\nfunction resolveCodingPath(\n cwd: string,\n value: string,\n options: { restrictToCwd: boolean; allowEmpty?: boolean },\n): string | null {\n if (!value.trim() && !options.allowEmpty) return null;\n const target = value.trim() || \".\";\n const resolved = path.isAbsolute(target)\n ? path.resolve(target)\n : path.resolve(cwd, target);\n if (!options.restrictToCwd) return resolved;\n\n const relative = path.relative(cwd, resolved);\n if (relative.startsWith(\"..\") || path.isAbsolute(relative)) return null;\n return resolved;\n}\n\nfunction formatFileReadOutput(\n cwd: string,\n filePath: string,\n content: string,\n args: Record<string, unknown>,\n): string {\n const lines = content.split(\"\\n\");\n const offset = positiveInteger(args.offset, 1);\n const limit = positiveInteger(args.limit, lines.length - offset + 1);\n const selected = lines.slice(offset - 1, offset - 1 + limit);\n const body = selected\n .map((line, index) => `${String(offset + index).padStart(5)} | ${line}`)\n .join(\"\\n\");\n return `${path.relative(cwd, filePath) || filePath} (${lines.length} lines)\\n${body}`;\n}\n\nfunction parseEditOperations(args: Record<string, unknown>): EditOperation[] {\n const editsJson = stringArg(args.edits);\n if (editsJson.trim()) {\n let parsed: unknown;\n try {\n parsed = JSON.parse(editsJson);\n } catch (err) {\n throw new Error(\n `edits must be valid JSON: ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n if (!Array.isArray(parsed) || parsed.length === 0) {\n throw new Error(\"edits must be a non-empty JSON array.\");\n }\n return parsed.map((edit, index) => {\n if (!edit || typeof edit !== \"object\") {\n throw new Error(`edits[${index}] must be an object.`);\n }\n return normalizeEditOperation(edit as Record<string, unknown>, index);\n });\n }\n\n return [normalizeEditOperation(args, 0)];\n}\n\nfunction normalizeEditOperation(\n edit: Record<string, unknown>,\n index: number,\n): EditOperation {\n const oldText = typeof edit.oldText === \"string\" ? edit.oldText : undefined;\n const newText = typeof edit.newText === \"string\" ? edit.newText : undefined;\n if (!oldText) {\n throw new Error(\n index === 0\n ? \"oldText is required and cannot be empty.\"\n : `edits[${index}].oldText is required and cannot be empty.`,\n );\n }\n if (newText === undefined) {\n throw new Error(\n index === 0\n ? \"newText is required.\"\n : `edits[${index}].newText is required.`,\n );\n }\n return {\n oldText,\n newText,\n replaceAll: stringArg(edit.replaceAll).toLowerCase() === \"true\",\n };\n}\n\nfunction countOccurrences(value: string, needle: string): number {\n let count = 0;\n let index = 0;\n while (true) {\n index = value.indexOf(needle, index);\n if (index === -1) return count;\n count += 1;\n index += needle.length;\n }\n}\n\nfunction queueFileMutation<T>(filePath: string, task: () => Promise<T> | T) {\n const previous = mutationQueues.get(filePath) ?? Promise.resolve();\n const next = previous.catch(() => undefined).then(task);\n let queued: Promise<unknown>;\n queued = next.finally(() => {\n if (mutationQueues.get(filePath) === queued)\n mutationQueues.delete(filePath);\n });\n mutationQueues.set(filePath, queued);\n return next;\n}\n\nfunction previewText(value: string): string {\n const oneLine = value.replace(/\\s+/g, \" \").trim();\n return oneLine.length > 80 ? `${oneLine.slice(0, 80)}...` : oneLine;\n}\n\nfunction stringArg(value: unknown): string {\n return typeof value === \"string\" ? value : \"\";\n}\n\nfunction positiveInteger(value: unknown, fallback: number): number {\n const parsed = Number(value);\n return Number.isFinite(parsed) && parsed > 0 ? Math.floor(parsed) : fallback;\n}\n"]}
@@ -297,7 +297,7 @@ export declare const extensionShares: import("drizzle-orm/sqlite-core").SQLiteTa
297
297
  tableName: string;
298
298
  dataType: "string";
299
299
  columnType: "SQLiteText";
300
- data: "viewer" | "editor" | "admin";
300
+ data: "admin" | "viewer" | "editor";
301
301
  driverParam: string;
302
302
  notNull: true;
303
303
  hasDefault: true;
@@ -1 +1 @@
1
- {"version":3,"file":"build-server.d.ts","sourceRoot":"","sources":["../../src/mcp/build-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAKhE,MAAM,WAAW,SAAS;IACxB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED;;;kEAGkE;AAClE,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;CAC7C;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,MAAM,EAAE,GAAG,EACX,IAAI,EAAE,cAAc,GAAG,SAAS,GAC/B;IACD,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAsBA;AA2BD;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,iBAAiB,GAAG,SAAS,EACvC,WAAW,CAAC,EAAE,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA8J7B;AAOD,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAc1C;AAyCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,GACpC,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAA;CAAE,CAAC,CAiD5D;AAED,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAS7B"}
1
+ {"version":3,"file":"build-server.d.ts","sourceRoot":"","sources":["../../src/mcp/build-server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,8BAA8B,CAAC;AAMhE,MAAM,WAAW,SAAS;IACxB,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb;;;;;;;OAOG;IACH,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sBAAsB;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IACrC,qEAAqE;IACrE,QAAQ,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IAChD;;;;;;OAMG;IACH,oBAAoB,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;;;;GAOG;AACH,MAAM,WAAW,iBAAiB;IAChC,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;IAC9B,SAAS,EAAE,MAAM,GAAG,SAAS,CAAC;CAC/B;AAED;;;kEAGkE;AAClE,MAAM,WAAW,cAAc;IAC7B,+DAA+D;IAC/D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yEAAyE;IACzE,MAAM,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC;CAC7C;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,EACzB,MAAM,EAAE,GAAG,EACX,IAAI,EAAE,cAAc,GAAG,SAAS,GAC/B;IACD,KAAK,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACjC,CAsBA;AA2BD;;;;;;;GAOG;AACH,wBAAsB,yBAAyB,CAC7C,MAAM,EAAE,SAAS,EACjB,QAAQ,EAAE,iBAAiB,GAAG,SAAS,EACvC,WAAW,CAAC,EAAE,cAAc;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;IA8J7B;AAOD,wBAAgB,eAAe,IAAI,MAAM,EAAE,CAc1C;AAyCD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,UAAU,CAC9B,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,gBAAgB,CAAC,EAAE,MAAM,GAAG,SAAS,GACpC,OAAO,CAAC;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,QAAQ,CAAC,EAAE,iBAAiB,CAAA;CAAE,CAAC,CAkF5D;AAED,wBAAsB,sBAAsB,CAC1C,SAAS,EAAE,MAAM,GAAG,SAAS,GAC5B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAS7B"}
@@ -20,6 +20,7 @@
20
20
  import { runWithRequestContext } from "../server/request-context.js";
21
21
  import { toAbsoluteOpenUrl, toDesktopOpenUrl } from "../server/deep-link.js";
22
22
  import { getBuiltinCrossAppTools } from "./builtin-tools.js";
23
+ import { MCP_CONNECT_SCOPE } from "./connect-store.js";
23
24
  /**
24
25
  * Build the deep-link content block + structured `_meta` for a tool result.
25
26
  * Best-effort: any throw / nullish link is swallowed so a bad `link` builder
@@ -309,6 +310,35 @@ export async function verifyAuth(authHeader, ownerEmailHeader) {
309
310
  try {
310
311
  const jose = await import("jose");
311
312
  const { payload } = await jose.jwtVerify(token, new TextEncoder().encode(process.env.A2A_SECRET));
313
+ const tokenScope = typeof payload.scope === "string" ? payload.scope : undefined;
314
+ if (tokenScope && tokenScope !== MCP_CONNECT_SCOPE) {
315
+ return { authed: false };
316
+ }
317
+ // Connect-minted tokens (scope === "mcp-connect") carry a random `jti`
318
+ // and are individually revocable. Only these tokens hit the revoke
319
+ // store — ordinary A2A delegation JWTs skip the DB lookup entirely so
320
+ // the hot path is unchanged. The revoke check FAILS OPEN on any
321
+ // store/DB error: a transient Neon WS drop must never lock every
322
+ // connected agent out. The signature was already cryptographically
323
+ // verified above, so failing open here only widens the explicit-revoke
324
+ // gate, never the trust boundary.
325
+ if (tokenScope === MCP_CONNECT_SCOPE) {
326
+ if (typeof payload.jti !== "string" || !payload.jti) {
327
+ return { authed: false };
328
+ }
329
+ const jti = payload.jti;
330
+ try {
331
+ const { isJtiRevoked, touchTokenUsed } = await import("./connect-store.js");
332
+ if (await isJtiRevoked(jti)) {
333
+ return { authed: false };
334
+ }
335
+ // Best-effort usage telemetry — never blocks / throws.
336
+ void touchTokenUsed(jti);
337
+ }
338
+ catch {
339
+ // Store import / lookup failed — fail open (see comment above).
340
+ }
341
+ }
312
342
  return {
313
343
  authed: true,
314
344
  identity: {