happy-mcp-server 1.1.1 → 1.1.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.
- package/dist/server.js +13 -12
- package/dist/tools/answer_question.js +2 -6
- package/dist/tools/approve_permission.js +2 -10
- package/dist/tools/deny_permission.js +2 -7
- package/dist/tools/get_session.js +2 -7
- package/dist/tools/interrupt_session.js +2 -4
- package/dist/tools/list_computers.js +2 -1
- package/dist/tools/list_sessions.js +2 -8
- package/dist/tools/schemas.d.ts +83 -0
- package/dist/tools/schemas.js +81 -0
- package/dist/tools/send_message.js +2 -11
- package/dist/tools/start_session.js +6 -7
- package/dist/tools/watch_session.js +2 -6
- package/package.json +1 -1
package/dist/server.js
CHANGED
|
@@ -9,6 +9,7 @@ import { registerDenyPermission } from './tools/deny_permission.js';
|
|
|
9
9
|
import { registerAnswerQuestion } from './tools/answer_question.js';
|
|
10
10
|
import { registerStartSession } from './tools/start_session.js';
|
|
11
11
|
import { registerInterruptSession } from './tools/interrupt_session.js';
|
|
12
|
+
import { listComputersSchema, listSessionsSchema, getSessionSchema, watchSessionSchema, sendMessageSchema, approvePermissionSchema, denyPermissionSchema, interruptSessionSchema, answerQuestionSchema, startSessionSchema, } from './tools/schemas.js';
|
|
12
13
|
const AUTH_ERROR = {
|
|
13
14
|
isError: true,
|
|
14
15
|
content: [{
|
|
@@ -23,15 +24,15 @@ const AUTH_RETRY = {
|
|
|
23
24
|
}],
|
|
24
25
|
};
|
|
25
26
|
const TOOL_STUBS = [
|
|
26
|
-
['list_computers', 'List available computers filtered by HAPPY_MCP_COMPUTERS.'],
|
|
27
|
-
['list_sessions', 'List all active Happy Coder sessions.'],
|
|
28
|
-
['get_session', 'Get detailed state of a specific session.'],
|
|
29
|
-
['watch_session', 'Watch sessions for real-time updates.'],
|
|
30
|
-
['send_message', 'Send a message to a session.'],
|
|
31
|
-
['approve_permission', 'Approve a pending permission request.'],
|
|
32
|
-
['deny_permission', 'Deny a pending permission request.'],
|
|
33
|
-
['interrupt_session', 'Interrupt a running session to stop its current activity.'],
|
|
34
|
-
['answer_question', 'Answer a question from a session.'],
|
|
27
|
+
['list_computers', 'List available computers filtered by HAPPY_MCP_COMPUTERS.', listComputersSchema],
|
|
28
|
+
['list_sessions', 'List all active Happy Coder sessions.', listSessionsSchema],
|
|
29
|
+
['get_session', 'Get detailed state of a specific session.', getSessionSchema],
|
|
30
|
+
['watch_session', 'Watch sessions for real-time updates.', watchSessionSchema],
|
|
31
|
+
['send_message', 'Send a message to a session.', sendMessageSchema],
|
|
32
|
+
['approve_permission', 'Approve a pending permission request.', approvePermissionSchema],
|
|
33
|
+
['deny_permission', 'Deny a pending permission request.', denyPermissionSchema],
|
|
34
|
+
['interrupt_session', 'Interrupt a running session to stop its current activity.', interruptSessionSchema],
|
|
35
|
+
['answer_question', 'Answer a question from a session.', answerQuestionSchema],
|
|
35
36
|
];
|
|
36
37
|
/**
|
|
37
38
|
* Register all real tool handlers on an McpServer instance.
|
|
@@ -73,11 +74,11 @@ export function createUnauthenticatedServer(config, onToolCallWhileUnauthenticat
|
|
|
73
74
|
};
|
|
74
75
|
function registerStubs() {
|
|
75
76
|
stubRegistrations = [];
|
|
76
|
-
for (const [name, desc] of TOOL_STUBS) {
|
|
77
|
-
stubRegistrations.push(server.tool(name, desc,
|
|
77
|
+
for (const [name, desc, schema] of TOOL_STUBS) {
|
|
78
|
+
stubRegistrations.push(server.tool(name, desc, schema, stubHandler));
|
|
78
79
|
}
|
|
79
80
|
if (config.enableStart) {
|
|
80
|
-
stubRegistrations.push(server.tool('start_session', 'Start a new Happy Coder session.',
|
|
81
|
+
stubRegistrations.push(server.tool('start_session', 'Start a new Happy Coder session.', startSessionSchema, stubHandler));
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
// Start with stubs
|
|
@@ -1,12 +1,8 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
1
|
import { randomUUID } from 'crypto';
|
|
3
2
|
import { encryptToBase64 } from '../auth/crypto.js';
|
|
3
|
+
import { answerQuestionSchema } from './schemas.js';
|
|
4
4
|
export function registerAnswerQuestion(server, api, relay, sessionManager) {
|
|
5
|
-
return server.tool('answer_question', 'Answer a question (AskUserQuestion) from a session. Approves the pending permission and sends a user message with the answer.', {
|
|
6
|
-
sessionId: z.string().describe('The session ID'),
|
|
7
|
-
requestId: z.string().describe('The question request ID from pending permissions'),
|
|
8
|
-
answers: z.record(z.string(), z.string()).describe('Map of question header to selected answer'),
|
|
9
|
-
}, async ({ sessionId, requestId, answers }) => {
|
|
5
|
+
return server.tool('answer_question', 'Answer a question (AskUserQuestion) from a session. Approves the pending permission and sends a user message with the answer.', answerQuestionSchema, async ({ sessionId, requestId, answers }) => {
|
|
10
6
|
try {
|
|
11
7
|
// Check relay is connected (REQUIRED for answer_question)
|
|
12
8
|
if (!relay.connected) {
|
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
1
|
import { logger } from '../logger.js';
|
|
3
|
-
|
|
4
|
-
const DECISIONS = ['approved', 'approved_for_session'];
|
|
2
|
+
import { approvePermissionSchema } from './schemas.js';
|
|
5
3
|
export function registerApprovePermission(server, relay, sessionManager) {
|
|
6
|
-
return server.tool('approve_permission', 'Approve a pending permission request in a session. Permission requests are shown in get_session and watch_session results.', {
|
|
7
|
-
sessionId: z.string().describe('The session ID'),
|
|
8
|
-
requestId: z.string().describe('The permission request ID to approve, or "all_pending" to approve all'),
|
|
9
|
-
mode: z.enum(PERMISSION_MODES).optional().describe('Permission mode to set after approval'),
|
|
10
|
-
allowTools: z.array(z.string()).optional().describe('List of tools to allow'),
|
|
11
|
-
decision: z.enum(DECISIONS).optional().default('approved').describe('Approval decision type'),
|
|
12
|
-
}, async ({ sessionId, requestId, mode, allowTools, decision }) => {
|
|
4
|
+
return server.tool('approve_permission', 'Approve a pending permission request in a session. Permission requests are shown in get_session and watch_session results.', approvePermissionSchema, async ({ sessionId, requestId, mode, allowTools, decision }) => {
|
|
13
5
|
try {
|
|
14
6
|
if (!relay.connected) {
|
|
15
7
|
const msg = relay.state === 'connecting'
|
|
@@ -1,12 +1,7 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
1
|
import { logger } from '../logger.js';
|
|
2
|
+
import { denyPermissionSchema } from './schemas.js';
|
|
3
3
|
export function registerDenyPermission(server, relay, sessionManager) {
|
|
4
|
-
return server.tool('deny_permission', 'Deny a pending permission request in a session. Can also abort the entire session.', {
|
|
5
|
-
sessionId: z.string().describe('The session ID'),
|
|
6
|
-
requestId: z.string().describe('The permission request ID to deny'),
|
|
7
|
-
reason: z.string().optional().describe('Feedback text explaining why the request was denied'),
|
|
8
|
-
decision: z.enum(['denied', 'abort']).optional().default('denied').describe('Denial type: denied (reject this request) or abort (stop the session)'),
|
|
9
|
-
}, async ({ sessionId, requestId, reason, decision }) => {
|
|
4
|
+
return server.tool('deny_permission', 'Deny a pending permission request in a session. Can also abort the entire session.', denyPermissionSchema, async ({ sessionId, requestId, reason, decision }) => {
|
|
10
5
|
try {
|
|
11
6
|
if (!relay.connected) {
|
|
12
7
|
const msg = relay.state === 'connecting'
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
1
|
import { decodeBase64, decrypt } from '../auth/crypto.js';
|
|
2
|
+
import { getSessionSchema } from './schemas.js';
|
|
3
3
|
function renderMessage(msg) {
|
|
4
4
|
const envelope = msg.content;
|
|
5
5
|
if (!envelope)
|
|
@@ -47,12 +47,7 @@ function renderMessage(msg) {
|
|
|
47
47
|
return { id: msg.id, role, content: text, timestamp: new Date(msg.createdAt).toISOString() };
|
|
48
48
|
}
|
|
49
49
|
export function registerGetSession(server, api, sessionManager) {
|
|
50
|
-
return server.tool('get_session', 'Get detailed information about a specific session including recent messages, metadata, and pending permissions.', {
|
|
51
|
-
sessionId: z.string().describe('The session ID to get details for'),
|
|
52
|
-
includeMessages: z.boolean().optional().default(true).describe('Whether to include messages'),
|
|
53
|
-
lastN: z.number().optional().default(20).describe('Number of recent messages to include'),
|
|
54
|
-
after: z.string().optional().describe('ISO 8601 timestamp — only return messages after this time'),
|
|
55
|
-
}, async ({ sessionId, includeMessages, lastN, after }) => {
|
|
50
|
+
return server.tool('get_session', 'Get detailed information about a specific session including recent messages, metadata, and pending permissions.', getSessionSchema, async ({ sessionId, includeMessages, lastN, after }) => {
|
|
56
51
|
try {
|
|
57
52
|
const session = sessionManager.get(sessionId);
|
|
58
53
|
if (!session) {
|
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
1
|
import { logger } from '../logger.js';
|
|
2
|
+
import { interruptSessionSchema } from './schemas.js';
|
|
3
3
|
export function registerInterruptSession(server, relay, sessionManager) {
|
|
4
|
-
return server.tool('interrupt_session', 'Interrupt a running Claude Code session to stop its current activity. This sends an abort signal equivalent to pressing Escape — the session stays alive and can accept new messages afterward. Use this when a session is actively generating output and you need it to stop.', {
|
|
5
|
-
sessionId: z.string().describe('The session ID to interrupt'),
|
|
6
|
-
}, async ({ sessionId }) => {
|
|
4
|
+
return server.tool('interrupt_session', 'Interrupt a running Claude Code session to stop its current activity. This sends an abort signal equivalent to pressing Escape — the session stays alive and can accept new messages afterward. Use this when a session is actively generating output and you need it to stop.', interruptSessionSchema, async ({ sessionId }) => {
|
|
7
5
|
try {
|
|
8
6
|
if (!relay.connected) {
|
|
9
7
|
const msg = relay.state === 'connecting'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { listComputersSchema } from './schemas.js';
|
|
1
2
|
export function registerListComputers(server, sessionManager, config) {
|
|
2
|
-
return server.tool('list_computers', 'List available computers filtered by HAPPY_MCP_COMPUTERS. Shows hostname, online status, and active session count. Use before start_session to find available machines.',
|
|
3
|
+
return server.tool('list_computers', 'List available computers filtered by HAPPY_MCP_COMPUTERS. Shows hostname, online status, and active session count. Use before start_session to find available machines.', listComputersSchema, async () => {
|
|
3
4
|
try {
|
|
4
5
|
const allMachines = sessionManager.getAllMachines();
|
|
5
6
|
const machines = allMachines
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { listSessionsSchema } from './schemas.js';
|
|
2
2
|
export function registerListSessions(server, sessionManager, config) {
|
|
3
|
-
return server.tool('list_sessions', 'List all active Happy Coder sessions. Shows session ID, project path, hostname, status (active/idle/waiting_permission), and pending permissions.', {
|
|
4
|
-
filter: z.object({
|
|
5
|
-
status: z.enum(['active', 'idle', 'waiting_permission']).optional().describe('Filter by session status'),
|
|
6
|
-
computer: z.string().optional().describe('Filter by computer hostname'),
|
|
7
|
-
projectPath: z.string().optional().describe('Filter by project path prefix'),
|
|
8
|
-
}).optional(),
|
|
9
|
-
}, async ({ filter }) => {
|
|
3
|
+
return server.tool('list_sessions', 'List all active Happy Coder sessions. Shows session ID, project path, hostname, status (active/idle/waiting_permission), and pending permissions.', listSessionsSchema, async ({ filter }) => {
|
|
10
4
|
try {
|
|
11
5
|
const computerFilter = filter?.computer ? [filter.computer] : config.computers;
|
|
12
6
|
const pathFilter = filter?.projectPath ? [filter.projectPath] : config.projectPaths;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Centralized Zod schemas for all MCP tools.
|
|
4
|
+
* These schemas are the single source of truth for tool input validation.
|
|
5
|
+
* They are used both for real tool registration and for stub registration
|
|
6
|
+
* to ensure clients always see correct parameter schemas.
|
|
7
|
+
*/
|
|
8
|
+
export declare const PERMISSION_MODES: readonly ["default", "acceptEdits", "bypassPermissions", "plan", "read-only", "safe-yolo", "yolo"];
|
|
9
|
+
export declare const APPROVAL_DECISIONS: readonly ["approved", "approved_for_session"];
|
|
10
|
+
export declare const DENIAL_DECISIONS: readonly ["denied", "abort"];
|
|
11
|
+
export declare const AGENTS: readonly ["claude", "codex", "gemini"];
|
|
12
|
+
export declare const listComputersSchema: {};
|
|
13
|
+
export declare const listSessionsSchema: {
|
|
14
|
+
filter: z.ZodOptional<z.ZodObject<{
|
|
15
|
+
status: z.ZodOptional<z.ZodEnum<["active", "idle", "waiting_permission"]>>;
|
|
16
|
+
computer: z.ZodOptional<z.ZodString>;
|
|
17
|
+
projectPath: z.ZodOptional<z.ZodString>;
|
|
18
|
+
}, "strip", z.ZodTypeAny, {
|
|
19
|
+
status?: "active" | "idle" | "waiting_permission" | undefined;
|
|
20
|
+
computer?: string | undefined;
|
|
21
|
+
projectPath?: string | undefined;
|
|
22
|
+
}, {
|
|
23
|
+
status?: "active" | "idle" | "waiting_permission" | undefined;
|
|
24
|
+
computer?: string | undefined;
|
|
25
|
+
projectPath?: string | undefined;
|
|
26
|
+
}>>;
|
|
27
|
+
};
|
|
28
|
+
export declare const getSessionSchema: {
|
|
29
|
+
sessionId: z.ZodString;
|
|
30
|
+
includeMessages: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
31
|
+
lastN: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
32
|
+
after: z.ZodOptional<z.ZodString>;
|
|
33
|
+
};
|
|
34
|
+
export declare const DEFAULT_TIMEOUT_SECONDS = 300;
|
|
35
|
+
export declare const watchSessionSchema: {
|
|
36
|
+
sessionIds: z.ZodArray<z.ZodString, "many">;
|
|
37
|
+
timeoutSeconds: z.ZodDefault<z.ZodOptional<z.ZodNumber>>;
|
|
38
|
+
};
|
|
39
|
+
export declare const sendMessageSchema: {
|
|
40
|
+
sessionId: z.ZodString;
|
|
41
|
+
message: z.ZodString;
|
|
42
|
+
meta: z.ZodOptional<z.ZodObject<{
|
|
43
|
+
permissionMode: z.ZodOptional<z.ZodEnum<["default", "acceptEdits", "bypassPermissions", "plan", "read-only", "safe-yolo", "yolo"]>>;
|
|
44
|
+
allowedTools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
45
|
+
disallowedTools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
46
|
+
}, "strip", z.ZodTypeAny, {
|
|
47
|
+
permissionMode?: "default" | "acceptEdits" | "bypassPermissions" | "plan" | "read-only" | "safe-yolo" | "yolo" | undefined;
|
|
48
|
+
allowedTools?: string[] | undefined;
|
|
49
|
+
disallowedTools?: string[] | undefined;
|
|
50
|
+
}, {
|
|
51
|
+
permissionMode?: "default" | "acceptEdits" | "bypassPermissions" | "plan" | "read-only" | "safe-yolo" | "yolo" | undefined;
|
|
52
|
+
allowedTools?: string[] | undefined;
|
|
53
|
+
disallowedTools?: string[] | undefined;
|
|
54
|
+
}>>;
|
|
55
|
+
};
|
|
56
|
+
export declare const approvePermissionSchema: {
|
|
57
|
+
sessionId: z.ZodString;
|
|
58
|
+
requestId: z.ZodString;
|
|
59
|
+
mode: z.ZodOptional<z.ZodEnum<["default", "acceptEdits", "bypassPermissions", "plan", "read-only", "safe-yolo", "yolo"]>>;
|
|
60
|
+
allowTools: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
|
|
61
|
+
decision: z.ZodDefault<z.ZodOptional<z.ZodEnum<["approved", "approved_for_session"]>>>;
|
|
62
|
+
};
|
|
63
|
+
export declare const denyPermissionSchema: {
|
|
64
|
+
sessionId: z.ZodString;
|
|
65
|
+
requestId: z.ZodString;
|
|
66
|
+
reason: z.ZodOptional<z.ZodString>;
|
|
67
|
+
decision: z.ZodDefault<z.ZodOptional<z.ZodEnum<["denied", "abort"]>>>;
|
|
68
|
+
};
|
|
69
|
+
export declare const interruptSessionSchema: {
|
|
70
|
+
sessionId: z.ZodString;
|
|
71
|
+
};
|
|
72
|
+
export declare const answerQuestionSchema: {
|
|
73
|
+
sessionId: z.ZodString;
|
|
74
|
+
requestId: z.ZodString;
|
|
75
|
+
answers: z.ZodRecord<z.ZodString, z.ZodString>;
|
|
76
|
+
};
|
|
77
|
+
export declare const startSessionSchema: {
|
|
78
|
+
computer: z.ZodString;
|
|
79
|
+
projectPath: z.ZodString;
|
|
80
|
+
initialMessage: z.ZodOptional<z.ZodString>;
|
|
81
|
+
permissionMode: z.ZodOptional<z.ZodEnum<["default", "acceptEdits", "bypassPermissions", "plan", "read-only", "safe-yolo", "yolo"]>>;
|
|
82
|
+
agent: z.ZodDefault<z.ZodOptional<z.ZodEnum<["claude", "codex", "gemini"]>>>;
|
|
83
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/**
|
|
3
|
+
* Centralized Zod schemas for all MCP tools.
|
|
4
|
+
* These schemas are the single source of truth for tool input validation.
|
|
5
|
+
* They are used both for real tool registration and for stub registration
|
|
6
|
+
* to ensure clients always see correct parameter schemas.
|
|
7
|
+
*/
|
|
8
|
+
// Permission modes (used across multiple tools)
|
|
9
|
+
export const PERMISSION_MODES = ['default', 'acceptEdits', 'bypassPermissions', 'plan', 'read-only', 'safe-yolo', 'yolo'];
|
|
10
|
+
// Decision types for approve_permission
|
|
11
|
+
export const APPROVAL_DECISIONS = ['approved', 'approved_for_session'];
|
|
12
|
+
// Decision types for deny_permission
|
|
13
|
+
export const DENIAL_DECISIONS = ['denied', 'abort'];
|
|
14
|
+
// AI agents for start_session
|
|
15
|
+
export const AGENTS = ['claude', 'codex', 'gemini'];
|
|
16
|
+
// list_computers (no parameters)
|
|
17
|
+
export const listComputersSchema = {};
|
|
18
|
+
// list_sessions
|
|
19
|
+
export const listSessionsSchema = {
|
|
20
|
+
filter: z.object({
|
|
21
|
+
status: z.enum(['active', 'idle', 'waiting_permission']).optional().describe('Filter by session status'),
|
|
22
|
+
computer: z.string().optional().describe('Filter by computer hostname'),
|
|
23
|
+
projectPath: z.string().optional().describe('Filter by project path prefix'),
|
|
24
|
+
}).optional(),
|
|
25
|
+
};
|
|
26
|
+
// get_session
|
|
27
|
+
export const getSessionSchema = {
|
|
28
|
+
sessionId: z.string().describe('The session ID to get details for'),
|
|
29
|
+
includeMessages: z.boolean().optional().default(true).describe('Whether to include messages'),
|
|
30
|
+
lastN: z.number().optional().default(20).describe('Number of recent messages to include'),
|
|
31
|
+
after: z.string().optional().describe('ISO 8601 timestamp — only return messages after this time'),
|
|
32
|
+
};
|
|
33
|
+
// watch_session
|
|
34
|
+
export const DEFAULT_TIMEOUT_SECONDS = 300; // 5 minutes
|
|
35
|
+
export const watchSessionSchema = {
|
|
36
|
+
sessionIds: z.array(z.string()).describe('One or more session IDs to watch'),
|
|
37
|
+
timeoutSeconds: z.number().optional().default(DEFAULT_TIMEOUT_SECONDS).describe('Max wait time in seconds (default 300)'),
|
|
38
|
+
};
|
|
39
|
+
// send_message
|
|
40
|
+
export const sendMessageSchema = {
|
|
41
|
+
sessionId: z.string().describe('The session ID to send the message to'),
|
|
42
|
+
message: z.string().describe('The message text to send'),
|
|
43
|
+
meta: z.object({
|
|
44
|
+
permissionMode: z.enum(PERMISSION_MODES).optional(),
|
|
45
|
+
allowedTools: z.array(z.string()).optional(),
|
|
46
|
+
disallowedTools: z.array(z.string()).optional(),
|
|
47
|
+
}).optional(),
|
|
48
|
+
};
|
|
49
|
+
// approve_permission
|
|
50
|
+
export const approvePermissionSchema = {
|
|
51
|
+
sessionId: z.string().describe('The session ID'),
|
|
52
|
+
requestId: z.string().describe('The permission request ID to approve, or "all_pending" to approve all'),
|
|
53
|
+
mode: z.enum(PERMISSION_MODES).optional().describe('Permission mode to set after approval'),
|
|
54
|
+
allowTools: z.array(z.string()).optional().describe('List of tools to allow'),
|
|
55
|
+
decision: z.enum(APPROVAL_DECISIONS).optional().default('approved').describe('Approval decision type'),
|
|
56
|
+
};
|
|
57
|
+
// deny_permission
|
|
58
|
+
export const denyPermissionSchema = {
|
|
59
|
+
sessionId: z.string().describe('The session ID'),
|
|
60
|
+
requestId: z.string().describe('The permission request ID to deny'),
|
|
61
|
+
reason: z.string().optional().describe('Feedback text explaining why the request was denied'),
|
|
62
|
+
decision: z.enum(DENIAL_DECISIONS).optional().default('denied').describe('Denial type: denied (reject this request) or abort (stop the session)'),
|
|
63
|
+
};
|
|
64
|
+
// interrupt_session
|
|
65
|
+
export const interruptSessionSchema = {
|
|
66
|
+
sessionId: z.string().describe('The session ID to interrupt'),
|
|
67
|
+
};
|
|
68
|
+
// answer_question
|
|
69
|
+
export const answerQuestionSchema = {
|
|
70
|
+
sessionId: z.string().describe('The session ID'),
|
|
71
|
+
requestId: z.string().describe('The question request ID from pending permissions'),
|
|
72
|
+
answers: z.record(z.string(), z.string()).describe('Map of question header to selected answer'),
|
|
73
|
+
};
|
|
74
|
+
// start_session (uses a generic description for projectPath)
|
|
75
|
+
export const startSessionSchema = {
|
|
76
|
+
computer: z.string().describe('The machine ID (from list_computers)'),
|
|
77
|
+
projectPath: z.string().describe('The working directory for the new session'),
|
|
78
|
+
initialMessage: z.string().optional().describe('Initial message to send after session starts'),
|
|
79
|
+
permissionMode: z.enum(PERMISSION_MODES).optional().describe('Permission mode for the session'),
|
|
80
|
+
agent: z.enum(AGENTS).optional().default('claude').describe('The AI agent to use'),
|
|
81
|
+
};
|
|
@@ -1,16 +1,7 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
1
|
import { encryptToBase64 } from '../auth/crypto.js';
|
|
3
|
-
|
|
2
|
+
import { sendMessageSchema } from './schemas.js';
|
|
4
3
|
export function registerSendMessage(server, relay, sessionManager) {
|
|
5
|
-
return server.tool('send_message', 'Send a message to a Happy Coder session. The message will appear as a user message in the session. If the session is actively generating, the message will be queued and processed when the session is ready. Can optionally change the permission mode.', {
|
|
6
|
-
sessionId: z.string().describe('The session ID to send the message to'),
|
|
7
|
-
message: z.string().describe('The message text to send'),
|
|
8
|
-
meta: z.object({
|
|
9
|
-
permissionMode: z.enum(PERMISSION_MODES).optional(),
|
|
10
|
-
allowedTools: z.array(z.string()).optional(),
|
|
11
|
-
disallowedTools: z.array(z.string()).optional(),
|
|
12
|
-
}).optional(),
|
|
13
|
-
}, async ({ sessionId, message, meta }) => {
|
|
4
|
+
return server.tool('send_message', 'Send a message to a Happy Coder session. The message will appear as a user message in the session. If the session is actively generating, the message will be queued and processed when the session is ready. Can optionally change the permission mode.', sendMessageSchema, async ({ sessionId, message, meta }) => {
|
|
14
5
|
try {
|
|
15
6
|
// Check relay connectivity
|
|
16
7
|
if (!relay.connected) {
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
|
|
2
|
+
import { startSessionSchema } from './schemas.js';
|
|
3
3
|
export function registerStartSession(server, _api, relay, sessionManager, config) {
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
// Create a dynamic schema with config-specific projectPath description
|
|
5
|
+
const dynamicSchema = {
|
|
6
|
+
...startSessionSchema,
|
|
6
7
|
projectPath: z.string().describe(config.projectPaths.includes('*')
|
|
7
8
|
? 'The working directory for the new session'
|
|
8
9
|
: `The working directory for the new session. Allowed paths: ${config.projectPaths.join(', ')}`),
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
agent: z.enum(['claude', 'codex', 'gemini']).optional().default('claude').describe('The AI agent to use'),
|
|
12
|
-
}, async ({ computer, projectPath, initialMessage, permissionMode, agent }) => {
|
|
10
|
+
};
|
|
11
|
+
return server.tool('start_session', 'Start a new Happy Coder session on a remote machine. Requires the machine to be online. Use list_computers to find available machines.', dynamicSchema, async ({ computer, projectPath, initialMessage, permissionMode, agent }) => {
|
|
13
12
|
try {
|
|
14
13
|
if (!relay.connected) {
|
|
15
14
|
const msg = relay.state === 'connecting'
|
|
@@ -1,11 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
const DEFAULT_TIMEOUT_SECONDS = 300; // 5 minutes
|
|
1
|
+
import { watchSessionSchema } from './schemas.js';
|
|
3
2
|
const MAX_WAIT_MS = 300_000; // 5 min max wait
|
|
4
3
|
export function registerWatchSession(server, relay, sessionManager) {
|
|
5
|
-
return server.tool('watch_session', 'Watch one or more sessions for state changes. Returns immediately if any session is idle or has pending permissions. Otherwise waits up to 5 minutes for a state change. If sessions are still active, returns current status -- call again to continue watching.', {
|
|
6
|
-
sessionIds: z.array(z.string()).describe('One or more session IDs to watch'),
|
|
7
|
-
timeoutSeconds: z.number().optional().default(DEFAULT_TIMEOUT_SECONDS).describe('Max wait time in seconds (default 300)'),
|
|
8
|
-
}, async ({ sessionIds, timeoutSeconds }, extra) => {
|
|
4
|
+
return server.tool('watch_session', 'Watch one or more sessions for state changes. Returns immediately if any session is idle or has pending permissions. Otherwise waits up to 5 minutes for a state change. If sessions are still active, returns current status -- call again to continue watching.', watchSessionSchema, async ({ sessionIds, timeoutSeconds }, extra) => {
|
|
9
5
|
try {
|
|
10
6
|
// Validate all session IDs exist
|
|
11
7
|
const missing = sessionIds.filter(id => !sessionManager.get(id));
|