@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.
- package/README.md +1 -11
- package/dist/a2a/caller-auth.d.ts +1 -0
- package/dist/a2a/caller-auth.d.ts.map +1 -1
- package/dist/a2a/caller-auth.js +1 -1
- package/dist/a2a/caller-auth.js.map +1 -1
- package/dist/a2a/client.d.ts +7 -0
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +3 -0
- package/dist/a2a/client.js.map +1 -1
- package/dist/agent/production-agent.d.ts +1 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +34 -2
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/code-agent-executor.d.ts.map +1 -1
- package/dist/cli/code-agent-executor.js +47 -256
- package/dist/cli/code-agent-executor.js.map +1 -1
- package/dist/cli/connect.d.ts +94 -0
- package/dist/cli/connect.d.ts.map +1 -0
- package/dist/cli/connect.js +443 -0
- package/dist/cli/connect.js.map +1 -0
- package/dist/cli/index.js +16 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/mcp-config-writers.d.ts +71 -0
- package/dist/cli/mcp-config-writers.d.ts.map +1 -0
- package/dist/cli/mcp-config-writers.js +210 -0
- package/dist/cli/mcp-config-writers.js.map +1 -0
- package/dist/client/AgentPanel.d.ts +3 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +4 -4
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +3 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +22 -66
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +4 -1
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/composer/PromptComposer.d.ts +6 -1
- package/dist/client/composer/PromptComposer.d.ts.map +1 -1
- package/dist/client/composer/PromptComposer.js +5 -4
- package/dist/client/composer/PromptComposer.js.map +1 -1
- package/dist/client/composer/TiptapComposer.d.ts +6 -1
- package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
- package/dist/client/composer/TiptapComposer.js +20 -10
- package/dist/client/composer/TiptapComposer.js.map +1 -1
- package/dist/client/conversation/AgentConversation.d.ts +18 -0
- package/dist/client/conversation/AgentConversation.d.ts.map +1 -0
- package/dist/client/conversation/AgentConversation.js +94 -0
- package/dist/client/conversation/AgentConversation.js.map +1 -0
- package/dist/client/conversation/AgentConversation.spec.d.ts +2 -0
- package/dist/client/conversation/AgentConversation.spec.d.ts.map +1 -0
- package/dist/client/conversation/AgentConversation.spec.js +69 -0
- package/dist/client/conversation/AgentConversation.spec.js.map +1 -0
- package/dist/client/conversation/index.d.ts +4 -0
- package/dist/client/conversation/index.d.ts.map +1 -0
- package/dist/client/conversation/index.js +3 -0
- package/dist/client/conversation/index.js.map +1 -0
- package/dist/client/conversation/types.d.ts +54 -0
- package/dist/client/conversation/types.d.ts.map +1 -0
- package/dist/client/conversation/types.js +2 -0
- package/dist/client/conversation/types.js.map +1 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts +15 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.d.ts.map +1 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.js +66 -0
- package/dist/client/conversation/use-near-bottom-autoscroll.js.map +1 -0
- package/dist/client/dynamic-suggestions.d.ts +43 -0
- package/dist/client/dynamic-suggestions.d.ts.map +1 -0
- package/dist/client/dynamic-suggestions.js +344 -0
- package/dist/client/dynamic-suggestions.js.map +1 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -1
- package/dist/client/resources/ResourceTree.d.ts.map +1 -1
- package/dist/client/resources/ResourceTree.js +2 -2
- package/dist/client/resources/ResourceTree.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +4 -28
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/settings/SettingsPanel.js +2 -2
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/code-agents/index.d.ts +1 -0
- package/dist/code-agents/index.d.ts.map +1 -1
- package/dist/code-agents/index.js +1 -0
- package/dist/code-agents/index.js.map +1 -1
- package/dist/code-agents/transcript-normalizer.d.ts +50 -0
- package/dist/code-agents/transcript-normalizer.d.ts.map +1 -0
- package/dist/code-agents/transcript-normalizer.js +356 -0
- package/dist/code-agents/transcript-normalizer.js.map +1 -0
- package/dist/coding-tools/index.d.ts +31 -0
- package/dist/coding-tools/index.d.ts.map +1 -0
- package/dist/coding-tools/index.js +411 -0
- package/dist/coding-tools/index.js.map +1 -0
- package/dist/extensions/schema.d.ts +1 -1
- package/dist/mcp/build-server.d.ts.map +1 -1
- package/dist/mcp/build-server.js +30 -0
- package/dist/mcp/build-server.js.map +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +85 -26
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/mcp/connect-route.d.ts +43 -0
- package/dist/mcp/connect-route.d.ts.map +1 -0
- package/dist/mcp/connect-route.js +744 -0
- package/dist/mcp/connect-route.js.map +1 -0
- package/dist/mcp/connect-store.d.ts +132 -0
- package/dist/mcp/connect-store.d.ts.map +1 -0
- package/dist/mcp/connect-store.js +434 -0
- package/dist/mcp/connect-store.js.map +1 -0
- package/dist/mcp/org-directory.d.ts +83 -0
- package/dist/mcp/org-directory.d.ts.map +1 -0
- package/dist/mcp/org-directory.js +201 -0
- package/dist/mcp/org-directory.js.map +1 -0
- package/dist/mcp/server.d.ts +38 -1
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +208 -77
- package/dist/mcp/server.js.map +1 -1
- package/dist/scripts/dev/index.d.ts +6 -4
- package/dist/scripts/dev/index.d.ts.map +1 -1
- package/dist/scripts/dev/index.js +28 -13
- package/dist/scripts/dev/index.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +6 -6
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +32 -32
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-teams.js +2 -2
- package/dist/server/agent-teams.js.map +1 -1
- package/dist/server/agents-bundle.d.ts +3 -3
- package/dist/server/agents-bundle.js +5 -5
- package/dist/server/agents-bundle.js.map +1 -1
- package/dist/server/auth.d.ts +17 -0
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +149 -33
- package/dist/server/auth.js.map +1 -1
- package/dist/server/better-auth-instance.d.ts +43 -0
- package/dist/server/better-auth-instance.d.ts.map +1 -1
- package/dist/server/better-auth-instance.js +25 -0
- package/dist/server/better-auth-instance.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts +12 -0
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +42 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/identity-sso-store.d.ts +86 -0
- package/dist/server/identity-sso-store.d.ts.map +1 -0
- package/dist/server/identity-sso-store.js +243 -0
- package/dist/server/identity-sso-store.js.map +1 -0
- package/dist/server/identity-sso.d.ts +78 -0
- package/dist/server/identity-sso.d.ts.map +1 -0
- package/dist/server/identity-sso.js +425 -0
- package/dist/server/identity-sso.js.map +1 -0
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +1 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +2 -1
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/server/sentry.d.ts.map +1 -1
- package/dist/server/sentry.js +17 -2
- package/dist/server/sentry.js.map +1 -1
- package/dist/sharing/schema.d.ts +1 -1
- package/docs/content/client.md +15 -0
- package/docs/content/code-agents-ui.md +25 -4
- package/docs/content/cross-app-sso.md +118 -0
- package/docs/content/drop-in-agent.md +3 -1
- package/docs/content/external-agents.md +130 -51
- package/docs/content/frames.md +1 -1
- package/docs/content/migration-workbench.md +6 -1
- 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: "
|
|
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;
|
|
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"}
|
package/dist/mcp/build-server.js
CHANGED
|
@@ -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: {
|