agent-worker 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +335 -296
- package/dist/backends-D3MAlJBX.mjs +3 -0
- package/dist/backends-DenGdkrj.mjs +735 -0
- package/dist/cli/index.mjs +2087 -834
- package/dist/context-C7nBmU5D.mjs +4 -0
- package/dist/index.d.mts +272 -173
- package/dist/index.mjs +27 -36
- package/dist/mcp-server-DtIApaBD.mjs +549 -0
- package/dist/{skills-CVdxwuvV.mjs → skills-VyC7eQyK.mjs} +263 -136
- package/dist/workflow-CaRCNEh6.mjs +1784 -0
- package/package.json +10 -4
- package/dist/backends-BZ866Ij9.mjs +0 -564
- package/dist/backends-DXpJ7FJI.mjs +0 -3
package/dist/index.mjs
CHANGED
|
@@ -1,67 +1,69 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { a as createSkillsTool, c as
|
|
1
|
+
import { _ as FRONTIER_MODELS, a as createMockBackend, b as createModelAsync, c as CodexBackend, i as MockAIBackend, l as ClaudeCodeBackend, n as createBackend, o as SdkBackend, r as listBackends, s as CursorBackend, t as checkBackends, v as SUPPORTED_PROVIDERS, y as createModel } from "./backends-DenGdkrj.mjs";
|
|
2
|
+
import { a as createSkillsTool, c as createFeedbackTool, i as parseImportSpec, l as AgentSession, n as buildGitUrl, o as SkillsProvider, r as getSpecDisplayName, s as FEEDBACK_PROMPT, t as SkillImporter } from "./skills-VyC7eQyK.mjs";
|
|
3
|
+
import { jsonSchema, tool } from "ai";
|
|
3
4
|
import { createBashTool } from "bash-tool";
|
|
4
5
|
|
|
5
|
-
//#region src/tools/bash.ts
|
|
6
|
+
//#region src/agent/tools/bash.ts
|
|
6
7
|
/**
|
|
7
8
|
* Integration with Vercel's bash-tool for file system operations
|
|
8
9
|
*
|
|
9
10
|
* Provides bash, readFile, writeFile tools for AI agents in a sandboxed environment
|
|
10
11
|
*/
|
|
11
12
|
/**
|
|
12
|
-
* Create bash tools as
|
|
13
|
+
* Create bash tools as AI SDK tool() objects for use with AgentSession
|
|
13
14
|
*
|
|
14
15
|
* @example
|
|
15
16
|
* ```typescript
|
|
16
|
-
* const
|
|
17
|
+
* const { tools } = await createBashTools({
|
|
17
18
|
* files: { 'src/index.ts': 'console.log("hello")' }
|
|
18
19
|
* })
|
|
19
20
|
*
|
|
20
21
|
* const session = new AgentSession({
|
|
21
22
|
* model: 'anthropic/claude-sonnet-4-5',
|
|
22
23
|
* system: 'You are a coding assistant.',
|
|
23
|
-
* tools
|
|
24
|
+
* tools
|
|
24
25
|
* })
|
|
25
26
|
* ```
|
|
26
27
|
*/
|
|
27
28
|
async function createBashTools(options = {}) {
|
|
28
29
|
const { includeReadFile = true, includeWriteFile = true, ...bashOptions } = options;
|
|
29
30
|
const toolkit = await createBashTool(bashOptions);
|
|
30
|
-
const tools =
|
|
31
|
-
tools.
|
|
32
|
-
name: "bash",
|
|
31
|
+
const tools = {};
|
|
32
|
+
tools.bash = tool({
|
|
33
33
|
description: "Execute bash commands in a sandboxed environment. Returns stdout, stderr, and exit code.",
|
|
34
|
-
|
|
34
|
+
inputSchema: jsonSchema({
|
|
35
35
|
type: "object",
|
|
36
36
|
properties: { command: {
|
|
37
37
|
type: "string",
|
|
38
38
|
description: "The bash command to execute"
|
|
39
39
|
} },
|
|
40
40
|
required: ["command"]
|
|
41
|
-
},
|
|
41
|
+
}),
|
|
42
42
|
execute: async (args) => {
|
|
43
|
-
|
|
43
|
+
const bashTool = toolkit.tools.bash;
|
|
44
|
+
if (!bashTool?.execute) throw new Error("Bash tool not available");
|
|
45
|
+
return bashTool.execute(args, {});
|
|
44
46
|
}
|
|
45
47
|
});
|
|
46
|
-
if (includeReadFile) tools.
|
|
47
|
-
name: "readFile",
|
|
48
|
+
if (includeReadFile) tools.readFile = tool({
|
|
48
49
|
description: "Read the contents of a file from the sandbox filesystem.",
|
|
49
|
-
|
|
50
|
+
inputSchema: jsonSchema({
|
|
50
51
|
type: "object",
|
|
51
52
|
properties: { path: {
|
|
52
53
|
type: "string",
|
|
53
54
|
description: "The path to the file to read"
|
|
54
55
|
} },
|
|
55
56
|
required: ["path"]
|
|
56
|
-
},
|
|
57
|
+
}),
|
|
57
58
|
execute: async (args) => {
|
|
58
|
-
|
|
59
|
+
const readFileTool = toolkit.tools.readFile;
|
|
60
|
+
if (!readFileTool?.execute) throw new Error("ReadFile tool not available");
|
|
61
|
+
return readFileTool.execute(args, {});
|
|
59
62
|
}
|
|
60
63
|
});
|
|
61
|
-
if (includeWriteFile) tools.
|
|
62
|
-
name: "writeFile",
|
|
64
|
+
if (includeWriteFile) tools.writeFile = tool({
|
|
63
65
|
description: "Write content to a file in the sandbox filesystem. Creates parent directories if needed.",
|
|
64
|
-
|
|
66
|
+
inputSchema: jsonSchema({
|
|
65
67
|
type: "object",
|
|
66
68
|
properties: {
|
|
67
69
|
path: {
|
|
@@ -74,9 +76,11 @@ async function createBashTools(options = {}) {
|
|
|
74
76
|
}
|
|
75
77
|
},
|
|
76
78
|
required: ["path", "content"]
|
|
77
|
-
},
|
|
79
|
+
}),
|
|
78
80
|
execute: async (args) => {
|
|
79
|
-
|
|
81
|
+
const writeFileTool = toolkit.tools.writeFile;
|
|
82
|
+
if (!writeFileTool?.execute) throw new Error("WriteFile tool not available");
|
|
83
|
+
return writeFileTool.execute(args, {});
|
|
80
84
|
}
|
|
81
85
|
});
|
|
82
86
|
return {
|
|
@@ -86,11 +90,6 @@ async function createBashTools(options = {}) {
|
|
|
86
90
|
}
|
|
87
91
|
/**
|
|
88
92
|
* Quick helper to create bash tools with a directory
|
|
89
|
-
*
|
|
90
|
-
* @example
|
|
91
|
-
* ```typescript
|
|
92
|
-
* const { tools } = await createBashToolsFromDirectory('./src')
|
|
93
|
-
* ```
|
|
94
93
|
*/
|
|
95
94
|
async function createBashToolsFromDirectory(source, options = {}) {
|
|
96
95
|
return createBashTools({
|
|
@@ -100,14 +99,6 @@ async function createBashToolsFromDirectory(source, options = {}) {
|
|
|
100
99
|
}
|
|
101
100
|
/**
|
|
102
101
|
* Quick helper to create bash tools with inline files
|
|
103
|
-
*
|
|
104
|
-
* @example
|
|
105
|
-
* ```typescript
|
|
106
|
-
* const { tools } = await createBashToolsFromFiles({
|
|
107
|
-
* 'index.ts': 'console.log("hello")',
|
|
108
|
-
* 'package.json': '{"name": "test"}'
|
|
109
|
-
* })
|
|
110
|
-
* ```
|
|
111
102
|
*/
|
|
112
103
|
async function createBashToolsFromFiles(files, options = {}) {
|
|
113
104
|
return createBashTools({
|
|
@@ -117,4 +108,4 @@ async function createBashToolsFromFiles(files, options = {}) {
|
|
|
117
108
|
}
|
|
118
109
|
|
|
119
110
|
//#endregion
|
|
120
|
-
export { AgentSession,
|
|
111
|
+
export { AgentSession, ClaudeCodeBackend, CodexBackend, CursorBackend, FEEDBACK_PROMPT, FRONTIER_MODELS, MockAIBackend, SUPPORTED_PROVIDERS, SdkBackend, SkillImporter, SkillsProvider, buildGitUrl, checkBackends, createBackend, createBashTool, createBashTools, createBashToolsFromDirectory, createBashToolsFromFiles, createFeedbackTool, createMockBackend, createModel, createModelAsync, createSkillsTool, getSpecDisplayName, listBackends, parseImportSpec };
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
import { o as MemoryStorage, s as ContextProviderImpl } from "./cli/index.mjs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
|
+
|
|
7
|
+
//#region src/workflow/context/memory-provider.ts
|
|
8
|
+
/**
|
|
9
|
+
* In-memory ContextProvider for testing.
|
|
10
|
+
* All domain logic is inherited from ContextProviderImpl;
|
|
11
|
+
* this class adds test helpers for inspection and cleanup.
|
|
12
|
+
*/
|
|
13
|
+
var MemoryContextProvider = class extends ContextProviderImpl {
|
|
14
|
+
memoryStorage;
|
|
15
|
+
constructor(validAgents) {
|
|
16
|
+
const storage = new MemoryStorage();
|
|
17
|
+
super(storage, validAgents);
|
|
18
|
+
this.memoryStorage = storage;
|
|
19
|
+
}
|
|
20
|
+
/** Get underlying MemoryStorage (for testing) */
|
|
21
|
+
getStorage() {
|
|
22
|
+
return this.memoryStorage;
|
|
23
|
+
}
|
|
24
|
+
/** Get all channel messages (for testing, unfiltered) */
|
|
25
|
+
async getMessages() {
|
|
26
|
+
return this.readChannel();
|
|
27
|
+
}
|
|
28
|
+
/** Clear all data (for testing) */
|
|
29
|
+
clear() {
|
|
30
|
+
this.memoryStorage.clear();
|
|
31
|
+
}
|
|
32
|
+
/** Get all resources (for testing) */
|
|
33
|
+
async getResources() {
|
|
34
|
+
const keys = await this.memoryStorage.list("resources/");
|
|
35
|
+
const map = /* @__PURE__ */ new Map();
|
|
36
|
+
for (const key of keys) {
|
|
37
|
+
const content = await this.memoryStorage.read(`resources/${key}`);
|
|
38
|
+
if (content !== null) {
|
|
39
|
+
const id = key.replace(/\.[^.]+$/, "");
|
|
40
|
+
map.set(id, content);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return map;
|
|
44
|
+
}
|
|
45
|
+
/** Get inbox state for an agent (for testing) */
|
|
46
|
+
async getInboxState(agent) {
|
|
47
|
+
const raw = await this.memoryStorage.read("_state/inbox.json");
|
|
48
|
+
if (!raw) return void 0;
|
|
49
|
+
try {
|
|
50
|
+
return JSON.parse(raw).readCursors?.[agent];
|
|
51
|
+
} catch {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
/** Get all documents (for testing) */
|
|
56
|
+
async getDocuments() {
|
|
57
|
+
const files = await this.memoryStorage.list("documents/");
|
|
58
|
+
const map = /* @__PURE__ */ new Map();
|
|
59
|
+
for (const file of files) {
|
|
60
|
+
const content = await this.memoryStorage.read(`documents/${file}`);
|
|
61
|
+
if (content !== null) map.set(file, content);
|
|
62
|
+
}
|
|
63
|
+
return map;
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
/**
|
|
67
|
+
* Create a memory context provider
|
|
68
|
+
*/
|
|
69
|
+
function createMemoryContextProvider(validAgents) {
|
|
70
|
+
return new MemoryContextProvider(validAgents);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/workflow/context/proposals.ts
|
|
75
|
+
/**
|
|
76
|
+
* Format a proposal for display
|
|
77
|
+
*/
|
|
78
|
+
function formatProposal(proposal) {
|
|
79
|
+
const lines = [];
|
|
80
|
+
lines.push(`📋 **${proposal.title}** (${proposal.id})`);
|
|
81
|
+
lines.push(`Type: ${proposal.type} | Status: ${proposal.status}`);
|
|
82
|
+
if (proposal.description) lines.push(`\n${proposal.description}`);
|
|
83
|
+
lines.push("\nOptions:");
|
|
84
|
+
for (const option of proposal.options) {
|
|
85
|
+
const count = proposal.result?.counts[option.id] || 0;
|
|
86
|
+
const marker = proposal.result?.winner === option.id ? "✓ " : " ";
|
|
87
|
+
lines.push(`${marker}- ${option.label} (${option.id}): ${count} votes`);
|
|
88
|
+
}
|
|
89
|
+
if (proposal.result && Object.keys(proposal.result.votes).length > 0) {
|
|
90
|
+
lines.push("\nVotes:");
|
|
91
|
+
for (const [voter, choice] of Object.entries(proposal.result.votes)) lines.push(` @${voter} → ${choice}`);
|
|
92
|
+
}
|
|
93
|
+
if (proposal.status === "active" && proposal.expiresAt) {
|
|
94
|
+
const remaining = new Date(proposal.expiresAt).getTime() - Date.now();
|
|
95
|
+
const minutes = Math.max(0, Math.floor(remaining / 6e4));
|
|
96
|
+
lines.push(`\nExpires in: ${minutes} minutes`);
|
|
97
|
+
}
|
|
98
|
+
if (proposal.status === "resolved" && proposal.result?.winner) {
|
|
99
|
+
const winningOption = proposal.options.find((o) => o.id === proposal.result?.winner);
|
|
100
|
+
lines.push(`\n🏆 Winner: ${winningOption?.label || proposal.result.winner}`);
|
|
101
|
+
}
|
|
102
|
+
return lines.join("\n");
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Format multiple proposals as a summary
|
|
106
|
+
*/
|
|
107
|
+
function formatProposalList(proposals) {
|
|
108
|
+
if (proposals.length === 0) return "(no proposals)";
|
|
109
|
+
return proposals.map((p) => {
|
|
110
|
+
const votes = Object.keys(p.result?.votes || {}).length;
|
|
111
|
+
return `- ${p.id}: ${p.title} [${p.status}] (${votes} votes)`;
|
|
112
|
+
}).join("\n");
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
//#endregion
|
|
116
|
+
//#region src/workflow/context/mcp-server.ts
|
|
117
|
+
/**
|
|
118
|
+
* Context MCP Server
|
|
119
|
+
* Provides context tools to agents via Model Context Protocol
|
|
120
|
+
*
|
|
121
|
+
* Tool Taxonomy:
|
|
122
|
+
* - Channel: channel_send, channel_read (public append-only log)
|
|
123
|
+
* - Team: team_members, team_doc_*, team_proposal_*, team_vote (shared workspace)
|
|
124
|
+
* - My: my_inbox, my_inbox_ack (personal agent tools)
|
|
125
|
+
* - Resource: resource_create, resource_read (general-purpose reference mechanism)
|
|
126
|
+
* - Feedback: feedback_submit (agent observations about tools/workflows, opt-in)
|
|
127
|
+
*/
|
|
128
|
+
/**
|
|
129
|
+
* Format inbox messages for display
|
|
130
|
+
*/
|
|
131
|
+
function formatInbox(messages) {
|
|
132
|
+
if (messages.length === 0) return JSON.stringify({
|
|
133
|
+
messages: [],
|
|
134
|
+
count: 0
|
|
135
|
+
});
|
|
136
|
+
return JSON.stringify({
|
|
137
|
+
messages: messages.map((m) => ({
|
|
138
|
+
id: m.entry.id,
|
|
139
|
+
from: m.entry.from,
|
|
140
|
+
content: m.entry.content,
|
|
141
|
+
timestamp: m.entry.timestamp,
|
|
142
|
+
priority: m.priority
|
|
143
|
+
})),
|
|
144
|
+
count: messages.length
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Create an MCP server that exposes context tools
|
|
149
|
+
*
|
|
150
|
+
* Tool taxonomy:
|
|
151
|
+
* - Channel: channel_send, channel_read
|
|
152
|
+
* - Team: team_members, team_doc_read, team_doc_write, team_doc_append,
|
|
153
|
+
* team_doc_list, team_doc_create, team_proposal_create, team_vote,
|
|
154
|
+
* team_proposal_status, team_proposal_cancel
|
|
155
|
+
* - My: my_inbox, my_inbox_ack
|
|
156
|
+
* - Resource: resource_create, resource_read
|
|
157
|
+
*/
|
|
158
|
+
function createContextMCPServer(options) {
|
|
159
|
+
const { provider, validAgents, name = "workflow-context", version = "1.0.0", onMention, proposalManager, feedback: feedbackEnabled, debugLog } = options;
|
|
160
|
+
const feedbackEntries = [];
|
|
161
|
+
const logTool = (tool, agent, params) => {
|
|
162
|
+
if (debugLog) debugLog(`[mcp:${agent || "anonymous"}] ${tool}(${Object.entries(params).filter(([_, v]) => v !== void 0).map(([k, v]) => {
|
|
163
|
+
const val = typeof v === "string" && v.length > 50 ? v.slice(0, 50) + "..." : v;
|
|
164
|
+
return `${k}=${JSON.stringify(val)}`;
|
|
165
|
+
}).join(", ")})`);
|
|
166
|
+
};
|
|
167
|
+
const server = new McpServer({
|
|
168
|
+
name,
|
|
169
|
+
version
|
|
170
|
+
});
|
|
171
|
+
const agentConnections = /* @__PURE__ */ new Map();
|
|
172
|
+
const CHANNEL_MSG_LIMIT = 2e3;
|
|
173
|
+
server.tool("channel_send", `Send a message to the shared channel. Use @agent to mention/notify. Use "to" for private DMs. Max ${CHANNEL_MSG_LIMIT} chars — for longer content, use resource_create first then reference the resource ID in your message.`, {
|
|
174
|
+
message: z.string().describe("Message content, can include @mentions like @reviewer or @coder"),
|
|
175
|
+
to: z.string().optional().describe("Send as DM to a specific agent (private, only you and recipient see it)")
|
|
176
|
+
}, async ({ message, to }, extra) => {
|
|
177
|
+
const from = getAgentId(extra) || "anonymous";
|
|
178
|
+
logTool("channel_send", from, {
|
|
179
|
+
message,
|
|
180
|
+
to
|
|
181
|
+
});
|
|
182
|
+
if (message.length > CHANNEL_MSG_LIMIT) return {
|
|
183
|
+
isError: true,
|
|
184
|
+
content: [{
|
|
185
|
+
type: "text",
|
|
186
|
+
text: `Message too long (${message.length} chars, max ${CHANNEL_MSG_LIMIT}). Use resource_create to store the full content, then send a short message referencing the resource ID.`
|
|
187
|
+
}]
|
|
188
|
+
};
|
|
189
|
+
const sendOpts = to ? { to } : void 0;
|
|
190
|
+
const msg = await provider.appendChannel(from, message, sendOpts);
|
|
191
|
+
for (const target of msg.mentions) if (onMention) onMention(from, target, msg);
|
|
192
|
+
if (to && !msg.mentions.includes(to) && onMention) onMention(from, to, msg);
|
|
193
|
+
return { content: [{
|
|
194
|
+
type: "text",
|
|
195
|
+
text: JSON.stringify({
|
|
196
|
+
status: "sent",
|
|
197
|
+
timestamp: msg.timestamp,
|
|
198
|
+
mentions: msg.mentions,
|
|
199
|
+
to: msg.to
|
|
200
|
+
})
|
|
201
|
+
}] };
|
|
202
|
+
});
|
|
203
|
+
server.tool("channel_read", "Read messages from the shared channel. DMs and logs are automatically filtered based on your identity.", {
|
|
204
|
+
since: z.string().optional().describe("Read entries after this timestamp (ISO format)"),
|
|
205
|
+
limit: z.number().optional().describe("Maximum entries to return")
|
|
206
|
+
}, async ({ since, limit }, extra) => {
|
|
207
|
+
const agent = getAgentId(extra);
|
|
208
|
+
logTool("channel_read", agent, {
|
|
209
|
+
since,
|
|
210
|
+
limit
|
|
211
|
+
});
|
|
212
|
+
const entries = await provider.readChannel({
|
|
213
|
+
since,
|
|
214
|
+
limit,
|
|
215
|
+
agent
|
|
216
|
+
});
|
|
217
|
+
return { content: [{
|
|
218
|
+
type: "text",
|
|
219
|
+
text: JSON.stringify(entries)
|
|
220
|
+
}] };
|
|
221
|
+
});
|
|
222
|
+
server.tool("resource_create", "Store large content as a resource. Returns a reference (resource:id) usable in channel messages or documents.", {
|
|
223
|
+
content: z.string().describe("Content to store as resource"),
|
|
224
|
+
type: z.enum([
|
|
225
|
+
"markdown",
|
|
226
|
+
"json",
|
|
227
|
+
"text",
|
|
228
|
+
"diff"
|
|
229
|
+
]).optional().describe("Content type hint (default: text)")
|
|
230
|
+
}, async ({ content, type }, extra) => {
|
|
231
|
+
const createdBy = getAgentId(extra) || "anonymous";
|
|
232
|
+
logTool("resource_create", createdBy, {
|
|
233
|
+
type,
|
|
234
|
+
contentLen: content.length
|
|
235
|
+
});
|
|
236
|
+
const result = await provider.createResource(content, createdBy, type);
|
|
237
|
+
return { content: [{
|
|
238
|
+
type: "text",
|
|
239
|
+
text: JSON.stringify({
|
|
240
|
+
id: result.id,
|
|
241
|
+
ref: result.ref,
|
|
242
|
+
hint: `Use [description](${result.ref}) in messages or documents`
|
|
243
|
+
})
|
|
244
|
+
}] };
|
|
245
|
+
});
|
|
246
|
+
server.tool("resource_read", "Read resource content by ID. Use when you encounter resource:id references.", { id: z.string().describe("Resource ID (e.g., res_abc123)") }, async ({ id }) => {
|
|
247
|
+
const content = await provider.readResource(id);
|
|
248
|
+
if (content === null) return { content: [{
|
|
249
|
+
type: "text",
|
|
250
|
+
text: JSON.stringify({ error: `Resource not found: ${id}` })
|
|
251
|
+
}] };
|
|
252
|
+
return { content: [{
|
|
253
|
+
type: "text",
|
|
254
|
+
text: content
|
|
255
|
+
}] };
|
|
256
|
+
});
|
|
257
|
+
server.tool("my_inbox", "Check your unread inbox messages. Does NOT acknowledge — use my_inbox_ack after processing.", {}, async (_args, extra) => {
|
|
258
|
+
const agent = getAgentId(extra) || "anonymous";
|
|
259
|
+
logTool("my_inbox", agent, {});
|
|
260
|
+
const messages = await provider.getInbox(agent);
|
|
261
|
+
if (debugLog && messages.length > 0) debugLog(`[mcp:${agent}] my_inbox → ${messages.length} unread`);
|
|
262
|
+
return { content: [{
|
|
263
|
+
type: "text",
|
|
264
|
+
text: formatInbox(messages)
|
|
265
|
+
}] };
|
|
266
|
+
});
|
|
267
|
+
server.tool("my_inbox_ack", "Acknowledge inbox messages up to a message ID. Call after processing messages.", { until: z.string().describe("Acknowledge messages up to and including this message ID") }, async ({ until }, extra) => {
|
|
268
|
+
const agent = getAgentId(extra) || "anonymous";
|
|
269
|
+
logTool("my_inbox_ack", agent, { until });
|
|
270
|
+
await provider.ackInbox(agent, until);
|
|
271
|
+
return { content: [{
|
|
272
|
+
type: "text",
|
|
273
|
+
text: JSON.stringify({
|
|
274
|
+
status: "acknowledged",
|
|
275
|
+
until
|
|
276
|
+
})
|
|
277
|
+
}] };
|
|
278
|
+
});
|
|
279
|
+
server.tool("team_members", "List all agents in this workflow. Use to discover who you can @mention.", {}, async (_args, extra) => {
|
|
280
|
+
const currentAgent = getAgentId(extra) || "anonymous";
|
|
281
|
+
const agents = validAgents.map((name) => ({
|
|
282
|
+
name,
|
|
283
|
+
mention: `@${name}`,
|
|
284
|
+
isYou: name === currentAgent
|
|
285
|
+
}));
|
|
286
|
+
return { content: [{
|
|
287
|
+
type: "text",
|
|
288
|
+
text: JSON.stringify({
|
|
289
|
+
agents,
|
|
290
|
+
count: agents.length,
|
|
291
|
+
hint: "Use @agent in channel_send to mention other agents"
|
|
292
|
+
})
|
|
293
|
+
}] };
|
|
294
|
+
});
|
|
295
|
+
server.tool("team_doc_read", "Read a shared team document.", { file: z.string().optional().describe("Document file path (default: notes.md)") }, async ({ file }, extra) => {
|
|
296
|
+
logTool("team_doc_read", getAgentId(extra), { file });
|
|
297
|
+
return { content: [{
|
|
298
|
+
type: "text",
|
|
299
|
+
text: await provider.readDocument(file) || "(empty document)"
|
|
300
|
+
}] };
|
|
301
|
+
});
|
|
302
|
+
server.tool("team_doc_write", "Write/replace a shared team document.", {
|
|
303
|
+
content: z.string().describe("New document content (replaces existing)"),
|
|
304
|
+
file: z.string().optional().describe("Document file path (default: notes.md)")
|
|
305
|
+
}, async ({ content, file }, extra) => {
|
|
306
|
+
logTool("team_doc_write", getAgentId(extra), {
|
|
307
|
+
file,
|
|
308
|
+
contentLen: content.length
|
|
309
|
+
});
|
|
310
|
+
await provider.writeDocument(content, file);
|
|
311
|
+
return { content: [{
|
|
312
|
+
type: "text",
|
|
313
|
+
text: `Document ${file || "notes.md"} written successfully`
|
|
314
|
+
}] };
|
|
315
|
+
});
|
|
316
|
+
server.tool("team_doc_append", "Append content to a shared team document.", {
|
|
317
|
+
content: z.string().describe("Content to append to the document"),
|
|
318
|
+
file: z.string().optional().describe("Document file path (default: notes.md)")
|
|
319
|
+
}, async ({ content, file }, extra) => {
|
|
320
|
+
logTool("team_doc_append", getAgentId(extra), {
|
|
321
|
+
file,
|
|
322
|
+
contentLen: content.length
|
|
323
|
+
});
|
|
324
|
+
await provider.appendDocument(content, file);
|
|
325
|
+
return { content: [{
|
|
326
|
+
type: "text",
|
|
327
|
+
text: `Content appended to ${file || "notes.md"}`
|
|
328
|
+
}] };
|
|
329
|
+
});
|
|
330
|
+
server.tool("team_doc_list", "List all shared team document files.", {}, async () => {
|
|
331
|
+
const files = await provider.listDocuments();
|
|
332
|
+
return { content: [{
|
|
333
|
+
type: "text",
|
|
334
|
+
text: JSON.stringify({
|
|
335
|
+
files,
|
|
336
|
+
count: files.length
|
|
337
|
+
})
|
|
338
|
+
}] };
|
|
339
|
+
});
|
|
340
|
+
server.tool("team_doc_create", "Create a new shared team document file.", {
|
|
341
|
+
file: z.string().describe("Document file path (e.g., \"findings/auth.md\")"),
|
|
342
|
+
content: z.string().describe("Initial document content")
|
|
343
|
+
}, async ({ file, content }) => {
|
|
344
|
+
await provider.createDocument(file, content);
|
|
345
|
+
return { content: [{
|
|
346
|
+
type: "text",
|
|
347
|
+
text: `Document ${file} created successfully`
|
|
348
|
+
}] };
|
|
349
|
+
});
|
|
350
|
+
if (proposalManager) {
|
|
351
|
+
server.tool("team_proposal_create", "Create a new proposal for team voting. Use for decisions, elections, approvals, or assignments.", {
|
|
352
|
+
type: z.enum([
|
|
353
|
+
"election",
|
|
354
|
+
"decision",
|
|
355
|
+
"approval",
|
|
356
|
+
"assignment"
|
|
357
|
+
]).describe("Type of proposal"),
|
|
358
|
+
title: z.string().describe("Brief title for the proposal"),
|
|
359
|
+
description: z.string().optional().describe("Detailed description"),
|
|
360
|
+
options: z.array(z.object({
|
|
361
|
+
id: z.string().describe("Unique option identifier"),
|
|
362
|
+
label: z.string().describe("Display label for the option")
|
|
363
|
+
})).optional().describe("Voting options (required except for approval type)"),
|
|
364
|
+
resolution: z.object({
|
|
365
|
+
type: z.enum([
|
|
366
|
+
"plurality",
|
|
367
|
+
"majority",
|
|
368
|
+
"unanimous"
|
|
369
|
+
]).optional().describe("How to determine winner"),
|
|
370
|
+
quorum: z.number().optional().describe("Minimum votes required"),
|
|
371
|
+
tieBreaker: z.enum([
|
|
372
|
+
"first",
|
|
373
|
+
"random",
|
|
374
|
+
"creator-decides"
|
|
375
|
+
]).optional().describe("How to break ties")
|
|
376
|
+
}).optional().describe("Resolution rules"),
|
|
377
|
+
binding: z.boolean().optional().describe("Whether result is binding (default: true)"),
|
|
378
|
+
timeoutSeconds: z.number().optional().describe("Timeout in seconds (default: 3600)")
|
|
379
|
+
}, async (params, extra) => {
|
|
380
|
+
const createdBy = getAgentId(extra) || "anonymous";
|
|
381
|
+
try {
|
|
382
|
+
const proposal = proposalManager.create({
|
|
383
|
+
type: params.type,
|
|
384
|
+
title: params.title,
|
|
385
|
+
description: params.description,
|
|
386
|
+
options: params.options,
|
|
387
|
+
resolution: params.resolution,
|
|
388
|
+
binding: params.binding,
|
|
389
|
+
timeoutSeconds: params.timeoutSeconds,
|
|
390
|
+
createdBy
|
|
391
|
+
});
|
|
392
|
+
const optionsList = proposal.options.map((o) => `${o.id}: ${o.label}`).join(", ");
|
|
393
|
+
const otherAgents = validAgents.filter((a) => a !== createdBy).map((a) => `@${a}`).join(" ");
|
|
394
|
+
await provider.appendChannel(createdBy, `Created proposal "${proposal.title}" (${proposal.id})\nOptions: ${optionsList}\nUse team_vote tool to cast your vote. ${otherAgents}`);
|
|
395
|
+
return { content: [{
|
|
396
|
+
type: "text",
|
|
397
|
+
text: JSON.stringify({
|
|
398
|
+
status: "created",
|
|
399
|
+
proposal: {
|
|
400
|
+
id: proposal.id,
|
|
401
|
+
title: proposal.title,
|
|
402
|
+
options: proposal.options,
|
|
403
|
+
expiresAt: proposal.expiresAt
|
|
404
|
+
}
|
|
405
|
+
})
|
|
406
|
+
}] };
|
|
407
|
+
} catch (error) {
|
|
408
|
+
return { content: [{
|
|
409
|
+
type: "text",
|
|
410
|
+
text: JSON.stringify({
|
|
411
|
+
status: "error",
|
|
412
|
+
error: error instanceof Error ? error.message : String(error)
|
|
413
|
+
})
|
|
414
|
+
}] };
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
server.tool("team_vote", "Cast your vote on a team proposal.", {
|
|
418
|
+
proposal: z.string().describe("Proposal ID (e.g., prop-1)"),
|
|
419
|
+
choice: z.string().describe("Option ID to vote for"),
|
|
420
|
+
reason: z.string().optional().describe("Optional reason for your vote")
|
|
421
|
+
}, async ({ proposal: proposalId, choice, reason }, extra) => {
|
|
422
|
+
const voter = getAgentId(extra) || "anonymous";
|
|
423
|
+
const result = proposalManager.vote({
|
|
424
|
+
proposalId,
|
|
425
|
+
voter,
|
|
426
|
+
choice,
|
|
427
|
+
reason
|
|
428
|
+
});
|
|
429
|
+
if (!result.success) return { content: [{
|
|
430
|
+
type: "text",
|
|
431
|
+
text: JSON.stringify({
|
|
432
|
+
status: "error",
|
|
433
|
+
error: result.error
|
|
434
|
+
})
|
|
435
|
+
}] };
|
|
436
|
+
const reasonText = reason ? ` (reason: ${reason})` : "";
|
|
437
|
+
await provider.appendChannel(voter, `Voted "${choice}" on ${proposalId}${reasonText}`);
|
|
438
|
+
if (result.resolved && result.proposal) {
|
|
439
|
+
const winnerOption = result.proposal.options.find((o) => o.id === result.proposal.result?.winner);
|
|
440
|
+
const mentions = Object.keys(result.proposal.result?.votes || {}).map((v) => `@${v}`).join(" ");
|
|
441
|
+
await provider.appendChannel("system", `Proposal ${proposalId} resolved! Winner: ${winnerOption?.label || result.proposal.result?.winner || "none"} ${mentions}`);
|
|
442
|
+
}
|
|
443
|
+
return { content: [{
|
|
444
|
+
type: "text",
|
|
445
|
+
text: JSON.stringify({
|
|
446
|
+
status: "voted",
|
|
447
|
+
proposal: proposalId,
|
|
448
|
+
choice,
|
|
449
|
+
resolved: result.resolved,
|
|
450
|
+
winner: result.proposal?.result?.winner
|
|
451
|
+
})
|
|
452
|
+
}] };
|
|
453
|
+
});
|
|
454
|
+
server.tool("team_proposal_status", "Check status of team proposals. Omit proposal ID to see all active proposals.", { proposal: z.string().optional().describe("Proposal ID (omit for all active)") }, async ({ proposal: proposalId }) => {
|
|
455
|
+
if (proposalId) {
|
|
456
|
+
const proposal = proposalManager.get(proposalId);
|
|
457
|
+
if (!proposal) return { content: [{
|
|
458
|
+
type: "text",
|
|
459
|
+
text: JSON.stringify({
|
|
460
|
+
status: "error",
|
|
461
|
+
error: `Proposal not found: ${proposalId}`
|
|
462
|
+
})
|
|
463
|
+
}] };
|
|
464
|
+
return { content: [{
|
|
465
|
+
type: "text",
|
|
466
|
+
text: formatProposal(proposal)
|
|
467
|
+
}] };
|
|
468
|
+
}
|
|
469
|
+
const activeProposals = proposalManager.list("active");
|
|
470
|
+
return { content: [{
|
|
471
|
+
type: "text",
|
|
472
|
+
text: activeProposals.length > 0 ? formatProposalList(activeProposals) : "(no active proposals)"
|
|
473
|
+
}] };
|
|
474
|
+
});
|
|
475
|
+
server.tool("team_proposal_cancel", "Cancel a proposal you created.", { proposal: z.string().describe("Proposal ID to cancel") }, async ({ proposal: proposalId }, extra) => {
|
|
476
|
+
const cancelledBy = getAgentId(extra) || "anonymous";
|
|
477
|
+
const result = proposalManager.cancel(proposalId, cancelledBy);
|
|
478
|
+
if (!result.success) return { content: [{
|
|
479
|
+
type: "text",
|
|
480
|
+
text: JSON.stringify({
|
|
481
|
+
status: "error",
|
|
482
|
+
error: result.error
|
|
483
|
+
})
|
|
484
|
+
}] };
|
|
485
|
+
await provider.appendChannel(cancelledBy, `Cancelled proposal ${proposalId}`);
|
|
486
|
+
return { content: [{
|
|
487
|
+
type: "text",
|
|
488
|
+
text: JSON.stringify({
|
|
489
|
+
status: "cancelled",
|
|
490
|
+
proposal: proposalId
|
|
491
|
+
})
|
|
492
|
+
}] };
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
if (feedbackEnabled) server.tool("feedback_submit", "Report a workflow improvement need. Use when you hit something inconvenient — a missing tool, an awkward step, or a capability you wished you had.", {
|
|
496
|
+
target: z.string().describe("The area this is about — a tool name, a workflow step, or a general area (e.g. file search, code review)."),
|
|
497
|
+
type: z.enum([
|
|
498
|
+
"missing",
|
|
499
|
+
"friction",
|
|
500
|
+
"suggestion"
|
|
501
|
+
]).describe("missing: a tool or capability you needed but didn't have. friction: something that works but is awkward or slow. suggestion: a concrete improvement idea."),
|
|
502
|
+
description: z.string().describe("What you needed or what could be improved. Be specific."),
|
|
503
|
+
context: z.string().optional().describe("Optional: what you were trying to do when you hit this.")
|
|
504
|
+
}, async ({ target, type, description, context: ctx }, extra) => {
|
|
505
|
+
logTool("feedback_submit", getAgentId(extra) || "anonymous", {
|
|
506
|
+
target,
|
|
507
|
+
type
|
|
508
|
+
});
|
|
509
|
+
const entry = {
|
|
510
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
511
|
+
target,
|
|
512
|
+
type,
|
|
513
|
+
description,
|
|
514
|
+
...ctx ? { context: ctx } : {}
|
|
515
|
+
};
|
|
516
|
+
if (feedbackEntries.length >= 50) feedbackEntries.shift();
|
|
517
|
+
feedbackEntries.push(entry);
|
|
518
|
+
return { content: [{
|
|
519
|
+
type: "text",
|
|
520
|
+
text: JSON.stringify({ status: "recorded" })
|
|
521
|
+
}] };
|
|
522
|
+
});
|
|
523
|
+
return {
|
|
524
|
+
server,
|
|
525
|
+
agentConnections,
|
|
526
|
+
validAgents,
|
|
527
|
+
proposalManager,
|
|
528
|
+
getFeedback: () => [...feedbackEntries]
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Extract agent ID from MCP extra context
|
|
533
|
+
* The agent ID is set via X-Agent-Id header or session metadata
|
|
534
|
+
*/
|
|
535
|
+
function getAgentId(extra) {
|
|
536
|
+
if (!extra || typeof extra !== "object") return void 0;
|
|
537
|
+
if ("sessionId" in extra && typeof extra.sessionId === "string") {
|
|
538
|
+
const sid = extra.sessionId;
|
|
539
|
+
const match = sid.match(/^(.+)-[0-9a-f]{8}$/);
|
|
540
|
+
return match ? match[1] : sid;
|
|
541
|
+
}
|
|
542
|
+
if ("meta" in extra && extra.meta && typeof extra.meta === "object") {
|
|
543
|
+
const meta = extra.meta;
|
|
544
|
+
if ("agentId" in meta && typeof meta.agentId === "string") return meta.agentId;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
//#endregion
|
|
549
|
+
export { createMemoryContextProvider as a, MemoryContextProvider as i, formatProposal as n, formatProposalList as r, createContextMCPServer as t };
|