@messenger-agent/claude-agent 0.24.0-alpha.2

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.
@@ -0,0 +1,7 @@
1
+ import { type McpSdkServerConfigWithInstance } from "@anthropic-ai/claude-agent-sdk";
2
+ import { type ManagedTaskExecutionContext } from "@messenger-agent/shared/managed-task-tools";
3
+ export declare const MANAGED_TASK_MCP_SERVER_NAME = "managed_tasks";
4
+ export declare function createManagedTaskMcpServer(options: {
5
+ workspaceRoot: string;
6
+ context?: ManagedTaskExecutionContext;
7
+ }): McpSdkServerConfigWithInstance;
@@ -0,0 +1,41 @@
1
+ import { createSdkMcpServer, tool } from "@anthropic-ai/claude-agent-sdk";
2
+ import { executeManagedTaskTool, MANAGED_TASK_TOOL_DEFINITIONS, } from "@messenger-agent/shared/managed-task-tools";
3
+ import { WorkspaceFileError } from "@messenger-agent/shared/workspace-files";
4
+ export const MANAGED_TASK_MCP_SERVER_NAME = "managed_tasks";
5
+ function jsonResult(data) {
6
+ const structuredContent = data && typeof data === "object" && !Array.isArray(data) ? data : undefined;
7
+ return {
8
+ content: [{ type: "text", text: JSON.stringify(data) }],
9
+ ...(structuredContent ? { structuredContent } : null),
10
+ };
11
+ }
12
+ function errorResult(err) {
13
+ const message = err instanceof WorkspaceFileError && err.code === "INVALID_PATH"
14
+ ? err.message
15
+ : err instanceof Error
16
+ ? err.message
17
+ : String(err);
18
+ return {
19
+ content: [{ type: "text", text: message }],
20
+ isError: true,
21
+ };
22
+ }
23
+ export function createManagedTaskMcpServer(options) {
24
+ return createSdkMcpServer({
25
+ name: MANAGED_TASK_MCP_SERVER_NAME,
26
+ version: "0.1.0",
27
+ instructions: "Use these tools for managed task records in the current workspace. Do not read or write task storage files directly.",
28
+ alwaysLoad: true,
29
+ tools: MANAGED_TASK_TOOL_DEFINITIONS.map((definition) => tool(definition.name, definition.description, definition.schema.shape, async (args) => {
30
+ try {
31
+ return jsonResult(await executeManagedTaskTool(options.workspaceRoot, definition.name, args, options.context));
32
+ }
33
+ catch (err) {
34
+ return errorResult(err);
35
+ }
36
+ }, {
37
+ alwaysLoad: true,
38
+ searchHint: definition.name,
39
+ })),
40
+ });
41
+ }
@@ -0,0 +1,85 @@
1
+ /** SDK tool names whose permission request is satisfied by a later client response. */
2
+ export declare const PENDING_ASK_TOOL_NAMES: readonly ["AskUserQuestion"];
3
+ export declare const PENDING_APPROVAL_TOOL_NAMES: readonly ["ExitPlanMode"];
4
+ export type PendingAskToolName = (typeof PENDING_ASK_TOOL_NAMES)[number];
5
+ export type PendingApprovalToolName = (typeof PENDING_APPROVAL_TOOL_NAMES)[number];
6
+ export type PendingToolKind = "ask_user" | "approval";
7
+ export type PendingAskRecord = {
8
+ id: string;
9
+ kind: "ask_user";
10
+ conversationId: string;
11
+ toolCallId: string;
12
+ workdir: string;
13
+ model?: string;
14
+ agentMode?: string;
15
+ toolName: PendingAskToolName;
16
+ input: unknown;
17
+ createdAt: string;
18
+ updatedAt: string;
19
+ };
20
+ export type PendingApprovalRecord = {
21
+ id: string;
22
+ kind: "approval";
23
+ conversationId: string;
24
+ toolCallId: string;
25
+ workdir: string;
26
+ model?: string;
27
+ agentMode?: string;
28
+ toolName: PendingApprovalToolName;
29
+ input: unknown;
30
+ approveId: string;
31
+ createdAt: string;
32
+ updatedAt: string;
33
+ };
34
+ export type PendingToolRecord = PendingAskRecord | PendingApprovalRecord;
35
+ export type PendingAskAnswer = {
36
+ answers: Record<string, string> | string;
37
+ output?: unknown;
38
+ } | {
39
+ interrupted: true;
40
+ };
41
+ export type PendingApprovalAnswer = {
42
+ approved: boolean;
43
+ reason?: string;
44
+ } | {
45
+ interrupted: true;
46
+ };
47
+ export declare function savePendingAsk(params: {
48
+ conversationId: string;
49
+ toolCallId: string;
50
+ workdir: string;
51
+ model?: string;
52
+ agentMode?: string;
53
+ toolName: PendingAskToolName;
54
+ input: unknown;
55
+ }): PendingAskRecord;
56
+ export declare function loadPendingAsk(toolCallId: string): PendingAskRecord | undefined;
57
+ export declare function findPendingAskByConversation(conversationId: string): PendingAskRecord | undefined;
58
+ export declare function savePendingApproval(params: {
59
+ conversationId: string;
60
+ toolCallId: string;
61
+ workdir: string;
62
+ model?: string;
63
+ agentMode?: string;
64
+ toolName: PendingApprovalToolName;
65
+ input: unknown;
66
+ }): PendingApprovalRecord;
67
+ export declare function loadPendingApproval(toolCallId: string): PendingApprovalRecord | undefined;
68
+ export declare function findPendingApprovalByConversation(conversationId: string): PendingApprovalRecord | undefined;
69
+ export declare function loadPendingClientTool(toolCallId: string): PendingToolRecord | undefined;
70
+ export declare function findPendingClientToolByConversation(conversationId: string): PendingToolRecord | undefined;
71
+ export declare function updatePendingAskConversation(previousConversationId: string, conversationId: string): void;
72
+ export declare function updatePendingClientToolConversation(previousConversationId: string, conversationId: string): void;
73
+ export declare function waitPendingAsk(toolCallId: string): Promise<PendingAskAnswer> | undefined;
74
+ export declare function answerPendingAsk(toolCallId: string, answer: PendingAskAnswer): boolean;
75
+ export declare function waitPendingApproval(toolCallId: string): Promise<PendingApprovalAnswer> | undefined;
76
+ export declare function answerPendingApproval(toolCallId: string, answer: PendingApprovalAnswer): boolean;
77
+ export declare function deletePendingAsk(toolCallId: string, options?: {
78
+ rejectWait?: boolean;
79
+ }): void;
80
+ export declare function deletePendingClientTool(toolCallId: string, options?: {
81
+ rejectWait?: boolean;
82
+ }): void;
83
+ export declare function listPendingAsks(): PendingAskRecord[];
84
+ export declare function listPendingClientTools(): PendingToolRecord[];
85
+ export declare function closePendingToolsDatabaseForTest(): void;
@@ -0,0 +1,283 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { mkdirSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { DatabaseSync } from "node:sqlite";
5
+ import { appConfig } from "./config.js";
6
+ /** SDK tool names whose permission request is satisfied by a later client response. */
7
+ export const PENDING_ASK_TOOL_NAMES = ["AskUserQuestion"];
8
+ export const PENDING_APPROVAL_TOOL_NAMES = ["ExitPlanMode"];
9
+ const pendingTools = new Map();
10
+ const DB_PATH = join(appConfig.dataDir, "pending-asks.sqlite");
11
+ mkdirSync(dirname(DB_PATH), { recursive: true });
12
+ const db = new DatabaseSync(DB_PATH);
13
+ db.exec(`
14
+ CREATE TABLE IF NOT EXISTS pending_tools (
15
+ tool_call_id TEXT PRIMARY KEY,
16
+ id TEXT NOT NULL,
17
+ kind TEXT NOT NULL,
18
+ conversation_id TEXT NOT NULL,
19
+ workdir TEXT NOT NULL,
20
+ model TEXT,
21
+ agent_mode TEXT,
22
+ tool_name TEXT NOT NULL,
23
+ input_json TEXT NOT NULL,
24
+ approval_json TEXT,
25
+ created_at TEXT NOT NULL,
26
+ updated_at TEXT NOT NULL
27
+ );
28
+ CREATE INDEX IF NOT EXISTS idx_pending_tools_conversation_updated
29
+ ON pending_tools (conversation_id, updated_at);
30
+ `);
31
+ const stmtUpsert = db.prepare(`
32
+ INSERT INTO pending_tools (
33
+ tool_call_id, id, kind, conversation_id, workdir, model, agent_mode, tool_name, input_json, approval_json, created_at, updated_at
34
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
35
+ ON CONFLICT(tool_call_id) DO UPDATE SET
36
+ id = excluded.id,
37
+ kind = excluded.kind,
38
+ conversation_id = excluded.conversation_id,
39
+ workdir = excluded.workdir,
40
+ model = excluded.model,
41
+ agent_mode = excluded.agent_mode,
42
+ tool_name = excluded.tool_name,
43
+ input_json = excluded.input_json,
44
+ approval_json = excluded.approval_json,
45
+ updated_at = excluded.updated_at
46
+ `);
47
+ const stmtGetByToolCallId = db.prepare(`SELECT * FROM pending_tools WHERE tool_call_id = ?`);
48
+ const stmtFindByConversation = db.prepare(`
49
+ SELECT * FROM pending_tools
50
+ WHERE conversation_id = ?
51
+ ORDER BY updated_at DESC, created_at DESC
52
+ LIMIT 1
53
+ `);
54
+ const stmtUpdateConversation = db.prepare(`
55
+ UPDATE pending_tools
56
+ SET conversation_id = ?, updated_at = ?
57
+ WHERE conversation_id = ?
58
+ `);
59
+ const stmtDelete = db.prepare(`DELETE FROM pending_tools WHERE tool_call_id = ?`);
60
+ const stmtList = db.prepare(`SELECT * FROM pending_tools ORDER BY updated_at DESC, created_at DESC`);
61
+ function createWait(record) {
62
+ let resolve;
63
+ let reject;
64
+ const promise = new Promise((promiseResolve, promiseReject) => {
65
+ resolve = promiseResolve;
66
+ reject = promiseReject;
67
+ });
68
+ return { record, resolve, reject, promise };
69
+ }
70
+ function rowToRecord(row) {
71
+ try {
72
+ const input = JSON.parse(row.input_json);
73
+ const base = {
74
+ id: row.id,
75
+ conversationId: row.conversation_id,
76
+ toolCallId: row.tool_call_id,
77
+ workdir: row.workdir,
78
+ model: row.model ?? undefined,
79
+ agentMode: row.agent_mode ?? undefined,
80
+ input,
81
+ createdAt: row.created_at,
82
+ updatedAt: row.updated_at,
83
+ };
84
+ if (row.kind === "approval") {
85
+ const approval = row.approval_json ? JSON.parse(row.approval_json) : {};
86
+ if (row.tool_name !== "ExitPlanMode" || typeof approval.approveId !== "string")
87
+ return undefined;
88
+ return {
89
+ ...base,
90
+ kind: "approval",
91
+ toolName: "ExitPlanMode",
92
+ approveId: approval.approveId,
93
+ };
94
+ }
95
+ if (row.kind !== "ask_user" || row.tool_name !== "AskUserQuestion")
96
+ return undefined;
97
+ return {
98
+ ...base,
99
+ kind: "ask_user",
100
+ toolName: "AskUserQuestion",
101
+ };
102
+ }
103
+ catch {
104
+ return undefined;
105
+ }
106
+ }
107
+ function persistPendingTool(record) {
108
+ stmtUpsert.run(record.toolCallId, record.id, record.kind, record.conversationId, record.workdir, record.model ?? null, record.agentMode ?? null, record.toolName, JSON.stringify(record.input), record.kind === "approval" ? JSON.stringify({ approveId: record.approveId }) : null, record.createdAt, record.updatedAt);
109
+ }
110
+ function hydrateWait(record) {
111
+ const existing = pendingTools.get(record.toolCallId);
112
+ if (existing)
113
+ return existing;
114
+ const wait = createWait(record);
115
+ pendingTools.set(record.toolCallId, wait);
116
+ return wait;
117
+ }
118
+ function loadPendingTool(toolCallId) {
119
+ const existing = pendingTools.get(toolCallId)?.record;
120
+ if (existing)
121
+ return existing;
122
+ const row = stmtGetByToolCallId.get(toolCallId);
123
+ const record = row ? rowToRecord(row) : undefined;
124
+ if (!record)
125
+ return undefined;
126
+ hydrateWait(record);
127
+ return record;
128
+ }
129
+ function findPendingToolByConversation(conversationId) {
130
+ const existing = Array.from(pendingTools.values(), (wait) => wait.record)
131
+ .filter((record) => record.conversationId === conversationId)
132
+ .sort((a, b) => b.updatedAt.localeCompare(a.updatedAt))[0];
133
+ if (existing)
134
+ return existing;
135
+ const row = stmtFindByConversation.get(conversationId);
136
+ const record = row ? rowToRecord(row) : undefined;
137
+ if (!record)
138
+ return undefined;
139
+ hydrateWait(record);
140
+ return record;
141
+ }
142
+ export function savePendingAsk(params) {
143
+ const existing = pendingTools.get(params.toolCallId);
144
+ if (existing?.record.kind === "ask_user")
145
+ return existing.record;
146
+ const now = new Date().toISOString();
147
+ const record = {
148
+ id: randomUUID(),
149
+ kind: "ask_user",
150
+ conversationId: params.conversationId,
151
+ toolCallId: params.toolCallId,
152
+ workdir: params.workdir,
153
+ model: params.model,
154
+ agentMode: params.agentMode,
155
+ toolName: params.toolName,
156
+ input: params.input,
157
+ createdAt: now,
158
+ updatedAt: now,
159
+ };
160
+ pendingTools.set(params.toolCallId, createWait(record));
161
+ persistPendingTool(record);
162
+ return record;
163
+ }
164
+ export function loadPendingAsk(toolCallId) {
165
+ const record = loadPendingTool(toolCallId);
166
+ return record?.kind === "ask_user" ? record : undefined;
167
+ }
168
+ export function findPendingAskByConversation(conversationId) {
169
+ const record = findPendingToolByConversation(conversationId);
170
+ return record?.kind === "ask_user" ? record : undefined;
171
+ }
172
+ export function savePendingApproval(params) {
173
+ const existing = pendingTools.get(params.toolCallId);
174
+ if (existing?.record.kind === "approval")
175
+ return existing.record;
176
+ const now = new Date().toISOString();
177
+ const record = {
178
+ id: randomUUID(),
179
+ kind: "approval",
180
+ conversationId: params.conversationId,
181
+ toolCallId: params.toolCallId,
182
+ workdir: params.workdir,
183
+ model: params.model,
184
+ agentMode: params.agentMode,
185
+ toolName: params.toolName,
186
+ input: params.input,
187
+ approveId: randomUUID(),
188
+ createdAt: now,
189
+ updatedAt: now,
190
+ };
191
+ pendingTools.set(params.toolCallId, createWait(record));
192
+ persistPendingTool(record);
193
+ return record;
194
+ }
195
+ export function loadPendingApproval(toolCallId) {
196
+ const record = loadPendingTool(toolCallId);
197
+ return record?.kind === "approval" ? record : undefined;
198
+ }
199
+ export function findPendingApprovalByConversation(conversationId) {
200
+ const record = findPendingToolByConversation(conversationId);
201
+ return record?.kind === "approval" ? record : undefined;
202
+ }
203
+ export function loadPendingClientTool(toolCallId) {
204
+ return loadPendingTool(toolCallId);
205
+ }
206
+ export function findPendingClientToolByConversation(conversationId) {
207
+ return findPendingToolByConversation(conversationId);
208
+ }
209
+ export function updatePendingAskConversation(previousConversationId, conversationId) {
210
+ updatePendingClientToolConversation(previousConversationId, conversationId);
211
+ }
212
+ export function updatePendingClientToolConversation(previousConversationId, conversationId) {
213
+ const now = new Date().toISOString();
214
+ let matched = false;
215
+ for (const wait of pendingTools.values()) {
216
+ if (wait.record.conversationId === previousConversationId) {
217
+ wait.record.conversationId = conversationId;
218
+ wait.record.updatedAt = now;
219
+ matched = true;
220
+ }
221
+ }
222
+ if (!matched)
223
+ return;
224
+ stmtUpdateConversation.run(conversationId, now, previousConversationId);
225
+ }
226
+ export function waitPendingAsk(toolCallId) {
227
+ return pendingTools.get(toolCallId)?.promise;
228
+ }
229
+ export function answerPendingAsk(toolCallId, answer) {
230
+ const wait = pendingTools.get(toolCallId) ?? (loadPendingAsk(toolCallId) ? pendingTools.get(toolCallId) : undefined);
231
+ if (!wait)
232
+ return false;
233
+ if (wait.record.kind !== "ask_user")
234
+ return false;
235
+ pendingTools.delete(toolCallId);
236
+ stmtDelete.run(toolCallId);
237
+ wait.resolve(answer);
238
+ return true;
239
+ }
240
+ export function waitPendingApproval(toolCallId) {
241
+ return pendingTools.get(toolCallId)?.promise;
242
+ }
243
+ export function answerPendingApproval(toolCallId, answer) {
244
+ const wait = pendingTools.get(toolCallId) ?? (loadPendingApproval(toolCallId) ? pendingTools.get(toolCallId) : undefined);
245
+ if (!wait)
246
+ return false;
247
+ if (wait.record.kind !== "approval")
248
+ return false;
249
+ pendingTools.delete(toolCallId);
250
+ stmtDelete.run(toolCallId);
251
+ wait.resolve(answer);
252
+ return true;
253
+ }
254
+ export function deletePendingAsk(toolCallId, options = {}) {
255
+ deletePendingClientTool(toolCallId, options);
256
+ }
257
+ export function deletePendingClientTool(toolCallId, options = {}) {
258
+ const wait = pendingTools.get(toolCallId);
259
+ pendingTools.delete(toolCallId);
260
+ stmtDelete.run(toolCallId);
261
+ if (wait && options.rejectWait) {
262
+ wait.reject(new Error(`Pending client tool was cancelled: ${toolCallId}`));
263
+ }
264
+ }
265
+ export function listPendingAsks() {
266
+ return listPendingClientTools().filter((record) => record.kind === "ask_user");
267
+ }
268
+ export function listPendingClientTools() {
269
+ const records = new Map();
270
+ for (const row of stmtList.all()) {
271
+ const record = rowToRecord(row);
272
+ if (record)
273
+ records.set(record.toolCallId, record);
274
+ }
275
+ for (const wait of pendingTools.values()) {
276
+ records.set(wait.record.toolCallId, wait.record);
277
+ }
278
+ return Array.from(records.values()).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
279
+ }
280
+ export function closePendingToolsDatabaseForTest() {
281
+ pendingTools.clear();
282
+ db.close();
283
+ }
@@ -0,0 +1,2 @@
1
+ export declare function loadClaudePlatformInstructions(templatePath?: string): string;
2
+ export declare const claudePlatformInstructions: string;
@@ -0,0 +1,42 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { fileURLToPath } from "node:url";
3
+ import { logger } from "@messenger-agent/shared/logger";
4
+ const DEFAULT_TEMPLATE_PATHS = [
5
+ fileURLToPath(new URL("./assets/codex-home/AGENTS.md", import.meta.url)),
6
+ fileURLToPath(new URL("../../../assets/codex-home/AGENTS.md", import.meta.url)),
7
+ "/app/assets/codex-home/AGENTS.md",
8
+ ];
9
+ const CLAUDE_MANAGED_TASK_TOOLS = [
10
+ "search_managed_tasks",
11
+ "get_managed_task",
12
+ "create_managed_task",
13
+ "update_managed_task",
14
+ "apply_managed_task_patch",
15
+ "delete_managed_task",
16
+ ];
17
+ const CLAUDE_TASK_GUIDANCE = [
18
+ "## Claude Managed Task Tools",
19
+ "",
20
+ "When the user asks to create, update, inspect, continue, summarize, or delete managed tasks, use the managed task MCP tools. The available tool basenames are: " +
21
+ CLAUDE_MANAGED_TASK_TOOLS.map((name) => `\`${name}\``).join(", ") +
22
+ ". Claude may expose them with an MCP namespace such as `mcp__managed_tasks__<tool>`.",
23
+ "",
24
+ ].join("\n");
25
+ const CLAUDE_RESPONSE_STYLE_GUIDANCE = [
26
+ "## Response Style",
27
+ "",
28
+ "Do not use emoji in responses.",
29
+ "Use Markdown headings, bold, and italic formatting sparingly.",
30
+ "Other Markdown formatting, such as lists, inline code, links, and code blocks, may be used as needed.",
31
+ "",
32
+ ].join("\n");
33
+ const CLAUDE_PLATFORM_GUIDANCE = `${CLAUDE_TASK_GUIDANCE}\n${CLAUDE_RESPONSE_STYLE_GUIDANCE}`;
34
+ export function loadClaudePlatformInstructions(templatePath) {
35
+ const path = [templatePath ?? process.env.CLAUDE_MEMORY_TEMPLATE_PATH, ...DEFAULT_TEMPLATE_PATHS].find((candidate) => Boolean(candidate && existsSync(candidate)));
36
+ if (!path) {
37
+ logger.warn("Claude platform instructions template not found");
38
+ return CLAUDE_PLATFORM_GUIDANCE;
39
+ }
40
+ return `${readFileSync(path, "utf-8").trimEnd()}\n\n${CLAUDE_PLATFORM_GUIDANCE}`;
41
+ }
42
+ export const claudePlatformInstructions = loadClaudePlatformInstructions();
@@ -0,0 +1,27 @@
1
+ import { Hono } from "hono";
2
+ import { type ChatBody } from "../schemas.js";
3
+ declare const chat: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
4
+ export declare function claudeActivitySnapshot(): {
5
+ active: number;
6
+ waiting: number;
7
+ };
8
+ export declare class ChatRequestError extends Error {
9
+ readonly status: 400 | 409;
10
+ constructor(status: 400 | 409, message: string);
11
+ }
12
+ export type ChatStreamRequest = {
13
+ body: ChatBody;
14
+ headers?: Headers;
15
+ signal?: AbortSignal;
16
+ };
17
+ export type ChatStreamWriter = {
18
+ writeData(data: unknown): Promise<void>;
19
+ writeDone(): Promise<void>;
20
+ };
21
+ export type PreparedChatStream = {
22
+ run(writer: ChatStreamWriter, signal?: AbortSignal): Promise<void>;
23
+ };
24
+ export declare function cancelClaudeConversation(conversationId: string): boolean;
25
+ export declare function prepareClaudeChatStream({ body, headers }: ChatStreamRequest): Promise<PreparedChatStream>;
26
+ export declare function handleClaudeChatStream(request: ChatStreamRequest, writer: ChatStreamWriter): Promise<void>;
27
+ export default chat;