chainlesschain 0.45.64 → 0.45.66
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/package.json +1 -1
- package/src/gateways/ws/session-protocol.js +162 -105
- package/src/gateways/ws/worktree-protocol.js +134 -141
- package/src/lib/agent-core.js +173 -13
- package/src/lib/interaction-adapter.js +45 -0
- package/src/lib/plan-mode.js +3 -1
- package/src/lib/session-manager.js +45 -8
- package/src/lib/web-ui-envelope.js +94 -0
- package/src/lib/web-ui-server.js +13 -1
- package/src/lib/ws-agent-handler.js +8 -1
- package/src/lib/ws-session-manager.js +388 -20
- package/src/runtime/agent-runtime.js +140 -37
- package/src/runtime/coding-agent-contract.js +294 -0
- package/src/runtime/coding-agent-events.cjs +372 -0
- package/src/runtime/coding-agent-managed-tool-policy.cjs +294 -0
- package/src/runtime/coding-agent-policy.cjs +354 -0
- package/src/runtime/coding-agent-shell-policy.cjs +233 -0
- package/src/runtime/contracts/session-record.js +13 -0
- package/src/runtime/index.js +14 -0
- package/src/runtime/runtime-events.js +27 -0
- package/src/tools/index.js +12 -0
- package/src/tools/legacy-agent-tools.js +12 -157
|
@@ -15,12 +15,28 @@ import { startChatRepl } from "../gateways/repl/chat-repl.js";
|
|
|
15
15
|
import { ChainlessChainWSServer } from "../gateways/ws/ws-server.js";
|
|
16
16
|
import { WSSessionManager } from "../gateways/ws/ws-session-gateway.js";
|
|
17
17
|
import { createWebUIServer } from "../gateways/ui/web-ui-server.js";
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
} from "../lib/project-detector.js";
|
|
18
|
+
import { MCPClient, MCPServerConfig } from "../lib/mcp-client.js";
|
|
19
|
+
import sharedManagedToolPolicy from "./coding-agent-managed-tool-policy.cjs";
|
|
20
|
+
import { findProjectRoot, loadProjectConfig } from "../lib/project-detector.js";
|
|
22
21
|
import { loadConfig } from "../lib/config-manager.js";
|
|
23
22
|
|
|
23
|
+
const {
|
|
24
|
+
DEFAULT_ALLOWED_MCP_SERVER_NAMES,
|
|
25
|
+
createTrustedMcpServerMap,
|
|
26
|
+
resolveMcpServerPolicy,
|
|
27
|
+
} = sharedManagedToolPolicy;
|
|
28
|
+
|
|
29
|
+
const BUILTIN_CODING_AGENT_MCP_REGISTRY = Object.freeze({
|
|
30
|
+
trustedServers: [
|
|
31
|
+
{
|
|
32
|
+
id: "weather",
|
|
33
|
+
securityLevel: "low",
|
|
34
|
+
requiredPermissions: ["network:http"],
|
|
35
|
+
capabilities: ["tools", "resources"],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
|
|
24
40
|
function openBrowser(url) {
|
|
25
41
|
try {
|
|
26
42
|
const platform = process.platform;
|
|
@@ -48,11 +64,15 @@ export class AgentRuntime {
|
|
|
48
64
|
startChatRepl: deps.startChatRepl || startChatRepl,
|
|
49
65
|
bootstrap: deps.bootstrap || bootstrap,
|
|
50
66
|
createServer:
|
|
51
|
-
deps.createServer ||
|
|
52
|
-
((options) => new ChainlessChainWSServer(options)),
|
|
67
|
+
deps.createServer || ((options) => new ChainlessChainWSServer(options)),
|
|
53
68
|
createSessionManager:
|
|
54
69
|
deps.createSessionManager ||
|
|
55
70
|
((options) => new WSSessionManager(options)),
|
|
71
|
+
createMcpClient: deps.createMcpClient || (() => new MCPClient()),
|
|
72
|
+
createMcpServerConfig:
|
|
73
|
+
deps.createMcpServerConfig || ((db) => new MCPServerConfig(db)),
|
|
74
|
+
mcpServerRegistry:
|
|
75
|
+
deps.mcpServerRegistry || BUILTIN_CODING_AGENT_MCP_REGISTRY,
|
|
56
76
|
createWebServer:
|
|
57
77
|
deps.createWebServer || ((options) => createWebUIServer(options)),
|
|
58
78
|
findProjectRoot: deps.findProjectRoot || findProjectRoot,
|
|
@@ -105,22 +125,29 @@ export class AgentRuntime {
|
|
|
105
125
|
return this.startAgentSession();
|
|
106
126
|
}
|
|
107
127
|
|
|
108
|
-
throw new Error(
|
|
128
|
+
throw new Error(
|
|
129
|
+
`resumeSession is not supported for runtime kind "${this.kind}".`,
|
|
130
|
+
);
|
|
109
131
|
}
|
|
110
132
|
|
|
111
133
|
async runTurn(input, meta = {}) {
|
|
112
134
|
if (typeof this.deps.runTurn !== "function") {
|
|
113
|
-
throw new Error(
|
|
135
|
+
throw new Error(
|
|
136
|
+
`runTurn is not configured for runtime kind "${this.kind}".`,
|
|
137
|
+
);
|
|
114
138
|
}
|
|
115
139
|
|
|
116
140
|
const startedAt = Date.now();
|
|
117
|
-
this.emit(
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
141
|
+
this.emit(
|
|
142
|
+
RUNTIME_EVENTS.TURN_START,
|
|
143
|
+
createAgentTurnRecord({
|
|
144
|
+
kind: this.kind,
|
|
145
|
+
input,
|
|
146
|
+
meta,
|
|
147
|
+
sessionId: this.policy.sessionId || null,
|
|
148
|
+
startedAt,
|
|
149
|
+
}),
|
|
150
|
+
);
|
|
124
151
|
|
|
125
152
|
const result = await this.deps.runTurn({
|
|
126
153
|
input,
|
|
@@ -130,15 +157,18 @@ export class AgentRuntime {
|
|
|
130
157
|
context: this.context,
|
|
131
158
|
});
|
|
132
159
|
|
|
133
|
-
this.emit(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
160
|
+
this.emit(
|
|
161
|
+
RUNTIME_EVENTS.TURN_END,
|
|
162
|
+
createAgentTurnRecord({
|
|
163
|
+
kind: this.kind,
|
|
164
|
+
input,
|
|
165
|
+
meta,
|
|
166
|
+
result,
|
|
167
|
+
sessionId: this.policy.sessionId || null,
|
|
168
|
+
startedAt,
|
|
169
|
+
endedAt: Date.now(),
|
|
170
|
+
}),
|
|
171
|
+
);
|
|
142
172
|
|
|
143
173
|
return result;
|
|
144
174
|
}
|
|
@@ -169,14 +199,8 @@ export class AgentRuntime {
|
|
|
169
199
|
|
|
170
200
|
async startServer() {
|
|
171
201
|
const { logger: runtimeLogger } = this.deps;
|
|
172
|
-
const {
|
|
173
|
-
|
|
174
|
-
maxConnections,
|
|
175
|
-
timeout,
|
|
176
|
-
token,
|
|
177
|
-
allowRemote,
|
|
178
|
-
project,
|
|
179
|
-
} = this.policy;
|
|
202
|
+
const { port, maxConnections, timeout, token, allowRemote, project } =
|
|
203
|
+
this.policy;
|
|
180
204
|
let { host } = this.policy;
|
|
181
205
|
|
|
182
206
|
if (Number.isNaN(port) || port < 1 || port > 65535) {
|
|
@@ -191,9 +215,11 @@ export class AgentRuntime {
|
|
|
191
215
|
}
|
|
192
216
|
|
|
193
217
|
let db = null;
|
|
218
|
+
let rawDb = null;
|
|
194
219
|
try {
|
|
195
220
|
const ctx = await this.deps.bootstrap({ skipDb: false });
|
|
196
|
-
|
|
221
|
+
rawDb = ctx.db?.getDatabase?.() || ctx.db?.getDb?.() || null;
|
|
222
|
+
db = rawDb;
|
|
197
223
|
} catch (_err) {
|
|
198
224
|
runtimeLogger.log(
|
|
199
225
|
chalk.yellow(
|
|
@@ -202,9 +228,16 @@ export class AgentRuntime {
|
|
|
202
228
|
);
|
|
203
229
|
}
|
|
204
230
|
|
|
231
|
+
const mcpClient = await this._initializeCodingAgentMcpClient(rawDb, {
|
|
232
|
+
logger: runtimeLogger,
|
|
233
|
+
});
|
|
234
|
+
|
|
205
235
|
const sessionManager = this.deps.createSessionManager({
|
|
206
236
|
db,
|
|
207
237
|
defaultProjectRoot: project,
|
|
238
|
+
mcpClient,
|
|
239
|
+
allowedMcpServerNames: DEFAULT_ALLOWED_MCP_SERVER_NAMES,
|
|
240
|
+
mcpServerRegistry: this.deps.mcpServerRegistry,
|
|
208
241
|
});
|
|
209
242
|
|
|
210
243
|
const server = this.deps.createServer({
|
|
@@ -217,7 +250,9 @@ export class AgentRuntime {
|
|
|
217
250
|
});
|
|
218
251
|
|
|
219
252
|
server.on("connection", ({ clientId, ip }) => {
|
|
220
|
-
runtimeLogger.log(
|
|
253
|
+
runtimeLogger.log(
|
|
254
|
+
chalk.green(` + Client connected: ${clientId} (${ip})`),
|
|
255
|
+
);
|
|
221
256
|
});
|
|
222
257
|
|
|
223
258
|
server.on("disconnection", ({ clientId, reason }) => {
|
|
@@ -250,6 +285,9 @@ export class AgentRuntime {
|
|
|
250
285
|
runtimeLogger.log(
|
|
251
286
|
"\n" + chalk.yellow("Shutting down WebSocket server..."),
|
|
252
287
|
);
|
|
288
|
+
if (mcpClient && typeof mcpClient.disconnectAll === "function") {
|
|
289
|
+
await mcpClient.disconnectAll().catch(() => undefined);
|
|
290
|
+
}
|
|
253
291
|
await server.stop();
|
|
254
292
|
process.exit(0);
|
|
255
293
|
};
|
|
@@ -305,14 +343,15 @@ export class AgentRuntime {
|
|
|
305
343
|
? this.deps.loadProjectConfig(projectRoot)
|
|
306
344
|
: null;
|
|
307
345
|
const projectName =
|
|
308
|
-
projectConfig?.name ||
|
|
309
|
-
(projectRoot ? path.basename(projectRoot) : null);
|
|
346
|
+
projectConfig?.name || (projectRoot ? path.basename(projectRoot) : null);
|
|
310
347
|
const mode = projectRoot ? "project" : "global";
|
|
311
348
|
|
|
312
349
|
let db = null;
|
|
350
|
+
let rawDb = null;
|
|
313
351
|
try {
|
|
314
352
|
const ctx = await this.deps.bootstrap({ skipDb: false });
|
|
315
|
-
|
|
353
|
+
rawDb = ctx.db?.getDatabase?.() || ctx.db?.getDb?.() || null;
|
|
354
|
+
db = rawDb;
|
|
316
355
|
} catch (_err) {
|
|
317
356
|
runtimeLogger.log(
|
|
318
357
|
chalk.yellow(
|
|
@@ -322,10 +361,16 @@ export class AgentRuntime {
|
|
|
322
361
|
}
|
|
323
362
|
|
|
324
363
|
const appConfig = this.deps.loadConfig();
|
|
364
|
+
const mcpClient = await this._initializeCodingAgentMcpClient(rawDb, {
|
|
365
|
+
logger: runtimeLogger,
|
|
366
|
+
});
|
|
325
367
|
const sessionManager = this.deps.createSessionManager({
|
|
326
368
|
db,
|
|
327
369
|
defaultProjectRoot: projectRoot || process.cwd(),
|
|
328
370
|
config: appConfig,
|
|
371
|
+
mcpClient,
|
|
372
|
+
allowedMcpServerNames: DEFAULT_ALLOWED_MCP_SERVER_NAMES,
|
|
373
|
+
mcpServerRegistry: this.deps.mcpServerRegistry,
|
|
329
374
|
});
|
|
330
375
|
|
|
331
376
|
const wsServer = this.deps.createServer({
|
|
@@ -395,6 +440,9 @@ export class AgentRuntime {
|
|
|
395
440
|
|
|
396
441
|
const shutdown = async () => {
|
|
397
442
|
runtimeLogger.log("\n" + chalk.yellow("Shutting down UI server..."));
|
|
443
|
+
if (mcpClient && typeof mcpClient.disconnectAll === "function") {
|
|
444
|
+
await mcpClient.disconnectAll().catch(() => undefined);
|
|
445
|
+
}
|
|
398
446
|
await Promise.all([
|
|
399
447
|
new Promise((resolve) => httpServer.close(resolve)),
|
|
400
448
|
wsServer.stop(),
|
|
@@ -414,4 +462,59 @@ export class AgentRuntime {
|
|
|
414
462
|
projectName,
|
|
415
463
|
};
|
|
416
464
|
}
|
|
465
|
+
|
|
466
|
+
async _initializeCodingAgentMcpClient(db, options = {}) {
|
|
467
|
+
if (!db) {
|
|
468
|
+
return null;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
const trustedMcpServers = createTrustedMcpServerMap(
|
|
472
|
+
this.deps.mcpServerRegistry,
|
|
473
|
+
);
|
|
474
|
+
const configStore = this.deps.createMcpServerConfig(db);
|
|
475
|
+
const autoConnectServers =
|
|
476
|
+
typeof configStore?.getAutoConnect === "function"
|
|
477
|
+
? configStore.getAutoConnect()
|
|
478
|
+
: [];
|
|
479
|
+
const eligibleServers = autoConnectServers.filter(
|
|
480
|
+
(server) =>
|
|
481
|
+
resolveMcpServerPolicy(
|
|
482
|
+
server?.name,
|
|
483
|
+
{ state: "connected" },
|
|
484
|
+
{
|
|
485
|
+
allowedMcpServerNames: DEFAULT_ALLOWED_MCP_SERVER_NAMES,
|
|
486
|
+
trustedMcpServers,
|
|
487
|
+
},
|
|
488
|
+
).allowed,
|
|
489
|
+
);
|
|
490
|
+
|
|
491
|
+
if (eligibleServers.length === 0) {
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
const mcpClient = this.deps.createMcpClient();
|
|
496
|
+
let connectedCount = 0;
|
|
497
|
+
|
|
498
|
+
for (const server of eligibleServers) {
|
|
499
|
+
try {
|
|
500
|
+
await mcpClient.connect(server.name, server);
|
|
501
|
+
connectedCount += 1;
|
|
502
|
+
} catch (err) {
|
|
503
|
+
options.logger?.log?.(
|
|
504
|
+
chalk.yellow(
|
|
505
|
+
` Warning: MCP server "${server.name}" auto-connect failed: ${err.message}`,
|
|
506
|
+
),
|
|
507
|
+
);
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (connectedCount === 0) {
|
|
512
|
+
if (typeof mcpClient.disconnectAll === "function") {
|
|
513
|
+
await mcpClient.disconnectAll().catch(() => undefined);
|
|
514
|
+
}
|
|
515
|
+
return null;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
return mcpClient;
|
|
519
|
+
}
|
|
417
520
|
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
import { ToolRegistry, createDefaultToolRegistry } from "../tools/registry.js";
|
|
2
|
+
import sharedCodingAgentPolicy from "./coding-agent-policy.cjs";
|
|
3
|
+
|
|
4
|
+
const { TOOL_POLICY_METADATA } = sharedCodingAgentPolicy;
|
|
5
|
+
|
|
6
|
+
const CODING_AGENT_TOOL_CONTRACTS = Object.freeze([
|
|
7
|
+
{
|
|
8
|
+
name: "read_file",
|
|
9
|
+
title: "Read File",
|
|
10
|
+
kind: "filesystem",
|
|
11
|
+
tier: "mvp",
|
|
12
|
+
...TOOL_POLICY_METADATA.read_file,
|
|
13
|
+
permissions: {
|
|
14
|
+
level: "readonly",
|
|
15
|
+
scopes: ["filesystem:read"],
|
|
16
|
+
},
|
|
17
|
+
telemetry: {
|
|
18
|
+
category: "filesystem",
|
|
19
|
+
tags: ["tool:read_file", "contract:coding-agent", "tier:mvp"],
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: "write_file",
|
|
24
|
+
title: "Write File",
|
|
25
|
+
kind: "filesystem",
|
|
26
|
+
tier: "mvp",
|
|
27
|
+
...TOOL_POLICY_METADATA.write_file,
|
|
28
|
+
permissions: {
|
|
29
|
+
level: "elevated",
|
|
30
|
+
scopes: ["filesystem:write"],
|
|
31
|
+
},
|
|
32
|
+
telemetry: {
|
|
33
|
+
category: "filesystem",
|
|
34
|
+
tags: ["tool:write_file", "contract:coding-agent", "tier:mvp"],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "edit_file",
|
|
39
|
+
title: "Edit File",
|
|
40
|
+
kind: "filesystem",
|
|
41
|
+
tier: "mvp",
|
|
42
|
+
...TOOL_POLICY_METADATA.edit_file,
|
|
43
|
+
permissions: {
|
|
44
|
+
level: "elevated",
|
|
45
|
+
scopes: ["filesystem:write"],
|
|
46
|
+
},
|
|
47
|
+
telemetry: {
|
|
48
|
+
category: "filesystem",
|
|
49
|
+
tags: ["tool:edit_file", "contract:coding-agent", "tier:mvp"],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: "run_shell",
|
|
54
|
+
title: "Run Shell",
|
|
55
|
+
kind: "shell",
|
|
56
|
+
tier: "mvp",
|
|
57
|
+
...TOOL_POLICY_METADATA.run_shell,
|
|
58
|
+
runtimeDescriptor: "shell",
|
|
59
|
+
permissions: {
|
|
60
|
+
level: "elevated",
|
|
61
|
+
scopes: ["process:spawn", "filesystem:workspace"],
|
|
62
|
+
},
|
|
63
|
+
telemetry: {
|
|
64
|
+
category: "shell",
|
|
65
|
+
tags: ["tool:run_shell", "contract:coding-agent", "tier:mvp"],
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
name: "git",
|
|
70
|
+
title: "Git",
|
|
71
|
+
kind: "git",
|
|
72
|
+
tier: "mvp",
|
|
73
|
+
...TOOL_POLICY_METADATA.git,
|
|
74
|
+
runtimeDescriptor: "git",
|
|
75
|
+
permissions: {
|
|
76
|
+
level: "elevated",
|
|
77
|
+
scopes: ["process:spawn", "filesystem:workspace", "vcs:git"],
|
|
78
|
+
},
|
|
79
|
+
telemetry: {
|
|
80
|
+
category: "git",
|
|
81
|
+
tags: ["tool:git", "contract:coding-agent", "tier:mvp"],
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
name: "search_files",
|
|
86
|
+
title: "Search Files",
|
|
87
|
+
kind: "filesystem",
|
|
88
|
+
tier: "mvp",
|
|
89
|
+
...TOOL_POLICY_METADATA.search_files,
|
|
90
|
+
permissions: {
|
|
91
|
+
level: "readonly",
|
|
92
|
+
scopes: ["filesystem:read", "search:content"],
|
|
93
|
+
},
|
|
94
|
+
telemetry: {
|
|
95
|
+
category: "filesystem",
|
|
96
|
+
tags: ["tool:search_files", "contract:coding-agent", "tier:mvp"],
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
name: "list_dir",
|
|
101
|
+
title: "List Directory",
|
|
102
|
+
kind: "filesystem",
|
|
103
|
+
tier: "mvp",
|
|
104
|
+
...TOOL_POLICY_METADATA.list_dir,
|
|
105
|
+
permissions: {
|
|
106
|
+
level: "readonly",
|
|
107
|
+
scopes: ["filesystem:read"],
|
|
108
|
+
},
|
|
109
|
+
telemetry: {
|
|
110
|
+
category: "filesystem",
|
|
111
|
+
tags: ["tool:list_dir", "contract:coding-agent", "tier:mvp"],
|
|
112
|
+
},
|
|
113
|
+
},
|
|
114
|
+
{
|
|
115
|
+
name: "run_skill",
|
|
116
|
+
title: "Run Skill",
|
|
117
|
+
kind: "skill",
|
|
118
|
+
tier: "extension",
|
|
119
|
+
...TOOL_POLICY_METADATA.run_skill,
|
|
120
|
+
permissions: {
|
|
121
|
+
level: "standard",
|
|
122
|
+
scopes: ["skill:invoke"],
|
|
123
|
+
},
|
|
124
|
+
telemetry: {
|
|
125
|
+
category: "skill",
|
|
126
|
+
tags: ["tool:run_skill", "contract:coding-agent", "tier:extension"],
|
|
127
|
+
},
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
name: "list_skills",
|
|
131
|
+
title: "List Skills",
|
|
132
|
+
kind: "skill",
|
|
133
|
+
tier: "extension",
|
|
134
|
+
...TOOL_POLICY_METADATA.list_skills,
|
|
135
|
+
permissions: {
|
|
136
|
+
level: "readonly",
|
|
137
|
+
scopes: ["skill:read"],
|
|
138
|
+
},
|
|
139
|
+
telemetry: {
|
|
140
|
+
category: "skill",
|
|
141
|
+
tags: ["tool:list_skills", "contract:coding-agent", "tier:extension"],
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
name: "run_code",
|
|
146
|
+
title: "Run Code",
|
|
147
|
+
kind: "code",
|
|
148
|
+
tier: "extension",
|
|
149
|
+
...TOOL_POLICY_METADATA.run_code,
|
|
150
|
+
permissions: {
|
|
151
|
+
level: "elevated",
|
|
152
|
+
scopes: ["process:spawn", "filesystem:workspace", "runtime:script"],
|
|
153
|
+
},
|
|
154
|
+
telemetry: {
|
|
155
|
+
category: "code",
|
|
156
|
+
tags: ["tool:run_code", "contract:coding-agent", "tier:extension"],
|
|
157
|
+
},
|
|
158
|
+
},
|
|
159
|
+
{
|
|
160
|
+
name: "spawn_sub_agent",
|
|
161
|
+
title: "Spawn Sub Agent",
|
|
162
|
+
kind: "agent",
|
|
163
|
+
tier: "extension",
|
|
164
|
+
...TOOL_POLICY_METADATA.spawn_sub_agent,
|
|
165
|
+
permissions: {
|
|
166
|
+
level: "elevated",
|
|
167
|
+
scopes: ["agent:spawn"],
|
|
168
|
+
},
|
|
169
|
+
telemetry: {
|
|
170
|
+
category: "agent",
|
|
171
|
+
tags: ["tool:spawn_sub_agent", "contract:coding-agent", "tier:extension"],
|
|
172
|
+
},
|
|
173
|
+
},
|
|
174
|
+
]);
|
|
175
|
+
|
|
176
|
+
export const CODING_AGENT_MVP_TOOL_NAMES = Object.freeze(
|
|
177
|
+
CODING_AGENT_TOOL_CONTRACTS.filter((tool) => tool.tier === "mvp").map(
|
|
178
|
+
(tool) => tool.name,
|
|
179
|
+
),
|
|
180
|
+
);
|
|
181
|
+
|
|
182
|
+
export const CODING_AGENT_EXTENSION_TOOL_NAMES = Object.freeze(
|
|
183
|
+
CODING_AGENT_TOOL_CONTRACTS.filter((tool) => tool.tier === "extension").map(
|
|
184
|
+
(tool) => tool.name,
|
|
185
|
+
),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
const TOOL_CONTRACT_MAP = new Map(
|
|
189
|
+
CODING_AGENT_TOOL_CONTRACTS.map((tool) => [tool.name, tool]),
|
|
190
|
+
);
|
|
191
|
+
const runtimeRegistry = createDefaultToolRegistry();
|
|
192
|
+
|
|
193
|
+
function cloneValue(value) {
|
|
194
|
+
return JSON.parse(JSON.stringify(value));
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function getFallbackToolContract(name) {
|
|
198
|
+
return {
|
|
199
|
+
name,
|
|
200
|
+
title: name,
|
|
201
|
+
kind: "legacy",
|
|
202
|
+
tier: "legacy",
|
|
203
|
+
riskLevel: "medium",
|
|
204
|
+
availableInPlanMode: false,
|
|
205
|
+
planModeBehavior: "blocked",
|
|
206
|
+
requiresPlanApproval: false,
|
|
207
|
+
approvalFlow: "policy",
|
|
208
|
+
permissions: {
|
|
209
|
+
level: "standard",
|
|
210
|
+
scopes: [],
|
|
211
|
+
},
|
|
212
|
+
telemetry: {
|
|
213
|
+
category: "legacy",
|
|
214
|
+
tags: [`tool:${name}`, "contract:coding-agent", "tier:legacy"],
|
|
215
|
+
},
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function getCodingAgentToolContract(name) {
|
|
220
|
+
const tool = TOOL_CONTRACT_MAP.get(name);
|
|
221
|
+
return tool ? cloneValue(tool) : null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export function getCodingAgentToolContracts({ tier = null } = {}) {
|
|
225
|
+
return CODING_AGENT_TOOL_CONTRACTS.filter((tool) => {
|
|
226
|
+
if (tier && tool.tier !== tier) {
|
|
227
|
+
return false;
|
|
228
|
+
}
|
|
229
|
+
return true;
|
|
230
|
+
}).map(cloneValue);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
export function listCodingAgentToolNames({ tier = null } = {}) {
|
|
234
|
+
return getCodingAgentToolContracts({ tier }).map((tool) => tool.name);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
export function isCodingAgentMvpTool(name) {
|
|
238
|
+
return CODING_AGENT_MVP_TOOL_NAMES.includes(name);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
export function getCodingAgentToolPolicy(name) {
|
|
242
|
+
const tool = TOOL_CONTRACT_MAP.get(name);
|
|
243
|
+
if (!tool) {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
return {
|
|
248
|
+
tier: tool.tier,
|
|
249
|
+
riskLevel: tool.riskLevel,
|
|
250
|
+
availableInPlanMode: tool.availableInPlanMode,
|
|
251
|
+
planModeBehavior: tool.planModeBehavior || "standard",
|
|
252
|
+
requiresPlanApproval: tool.requiresPlanApproval,
|
|
253
|
+
approvalFlow: tool.approvalFlow,
|
|
254
|
+
permissions: cloneValue(tool.permissions),
|
|
255
|
+
};
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export function mapCodingAgentToolDefinition(definition = {}, options = {}) {
|
|
259
|
+
const fn = definition.function || {};
|
|
260
|
+
const name = fn.name;
|
|
261
|
+
if (!name) {
|
|
262
|
+
throw new Error("Coding agent tool definition requires function.name.");
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const contract =
|
|
266
|
+
getCodingAgentToolContract(name) || getFallbackToolContract(name);
|
|
267
|
+
|
|
268
|
+
return {
|
|
269
|
+
name,
|
|
270
|
+
title: contract.title || name,
|
|
271
|
+
kind: contract.kind,
|
|
272
|
+
description: fn.description || "",
|
|
273
|
+
schema: fn.parameters || { type: "object", properties: {} },
|
|
274
|
+
permissions: cloneValue(contract.permissions),
|
|
275
|
+
telemetry: cloneValue(contract.telemetry),
|
|
276
|
+
source: options.source || "agent-core",
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export function createCodingAgentToolRegistry(definitions = [], options = {}) {
|
|
281
|
+
const registry = new ToolRegistry();
|
|
282
|
+
definitions.forEach((definition) => {
|
|
283
|
+
registry.register(mapCodingAgentToolDefinition(definition, options));
|
|
284
|
+
});
|
|
285
|
+
return registry;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
export function getCodingAgentRuntimeDescriptor(toolName) {
|
|
289
|
+
const descriptorName = TOOL_CONTRACT_MAP.get(toolName)?.runtimeDescriptor;
|
|
290
|
+
if (!descriptorName) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
return runtimeRegistry.get(descriptorName) || null;
|
|
294
|
+
}
|