@vibe-forge/mcp 3.1.1 → 3.2.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.
@@ -1,172 +0,0 @@
1
- import type {
2
- AdapterErrorData,
3
- AskUserQuestionParams,
4
- PermissionInteractionContext,
5
- PermissionInteractionDecision,
6
- SessionPermissionMode
7
- } from '@vibe-forge/types'
8
- import { normalizePermissionToolName } from '@vibe-forge/utils'
9
- import type { PermissionToolSubject } from '@vibe-forge/utils'
10
-
11
- export { applyTaskPermissionDecision, syncTaskPermissionStateMirror } from './permission-state'
12
-
13
- const PERMISSION_PROJECT_CONFIG_PATH = '.ai.config.json'
14
- export const PERMISSION_DECISION_CANCEL = 'cancel'
15
-
16
- const uniqueStrings = (values: string[]) => [...new Set(values)]
17
-
18
- const normalizeKeys = (values: string[]) =>
19
- uniqueStrings(
20
- values
21
- .map((value) => normalizePermissionToolName(value)?.key ?? value.trim())
22
- .filter((value): value is string => value.trim() !== '')
23
- )
24
-
25
- const buildPermissionOption = (
26
- label: string,
27
- value: PermissionInteractionDecision,
28
- description: string
29
- ) => ({ label, value, description })
30
-
31
- const resolvePermissionToolSubject = (value: string): PermissionToolSubject | undefined => (
32
- normalizePermissionToolName(value)
33
- )
34
-
35
- export interface PermissionErrorContext {
36
- subjectKeys: string[]
37
- deniedTools: string[]
38
- reasons: string[]
39
- }
40
-
41
- export const extractPermissionErrorContext = (error: AdapterErrorData): PermissionErrorContext => {
42
- const details = error.details != null && typeof error.details === 'object'
43
- ? error.details as Record<string, unknown>
44
- : {}
45
- const rawDeniedTools = new Set<string>()
46
- const reasons = new Set<string>()
47
-
48
- const permissionDenials = Array.isArray(details.permissionDenials) ? details.permissionDenials : []
49
- for (const denial of permissionDenials) {
50
- if (denial == null || typeof denial !== 'object') continue
51
- const record = denial as Record<string, unknown>
52
- if (typeof record.message === 'string' && record.message.trim() !== '') {
53
- reasons.add(record.message.trim())
54
- }
55
- if (Array.isArray(record.deniedTools)) {
56
- for (const tool of record.deniedTools) {
57
- if (typeof tool === 'string' && tool.trim() !== '') {
58
- rawDeniedTools.add(tool.trim())
59
- }
60
- }
61
- }
62
- }
63
-
64
- if (Array.isArray(details.deniedTools)) {
65
- for (const tool of details.deniedTools) {
66
- if (typeof tool === 'string' && tool.trim() !== '') {
67
- rawDeniedTools.add(tool.trim())
68
- }
69
- }
70
- }
71
-
72
- if (typeof details.toolName === 'string' && details.toolName.trim() !== '') {
73
- rawDeniedTools.add(details.toolName.trim())
74
- }
75
-
76
- if (typeof error.message === 'string' && error.message.trim() !== '') {
77
- reasons.add(error.message.trim())
78
- }
79
-
80
- const deniedTools = [...rawDeniedTools]
81
- const subjectKeys = uniqueStrings(
82
- deniedTools
83
- .map(tool => resolvePermissionToolSubject(tool)?.key)
84
- .filter((key): key is string => key != null && key.trim() !== '')
85
- )
86
-
87
- return {
88
- subjectKeys,
89
- deniedTools,
90
- reasons: [...reasons]
91
- }
92
- }
93
-
94
- export const resolvePermissionInteractionDecision = (
95
- answer: string | string[]
96
- ): PermissionInteractionDecision | typeof PERMISSION_DECISION_CANCEL | undefined => {
97
- const normalizedAnswer = Array.isArray(answer) ? answer[0] : answer
98
- if (typeof normalizedAnswer !== 'string') return undefined
99
-
100
- const raw = normalizedAnswer.trim()
101
- if (raw === '') return undefined
102
- if (raw === PERMISSION_DECISION_CANCEL) return PERMISSION_DECISION_CANCEL
103
-
104
- if (
105
- raw === 'allow_once' ||
106
- raw === 'allow_session' ||
107
- raw === 'allow_project' ||
108
- raw === 'deny_once' ||
109
- raw === 'deny_session' ||
110
- raw === 'deny_project'
111
- ) {
112
- return raw
113
- }
114
-
115
- return undefined
116
- }
117
-
118
- export const buildPermissionRecoveryPayload = (params: {
119
- sessionId: string
120
- adapter?: string
121
- currentMode?: SessionPermissionMode
122
- context: PermissionErrorContext
123
- }): AskUserQuestionParams | undefined => {
124
- const subjectKeys = normalizeKeys(params.context.subjectKeys)
125
- if (subjectKeys.length === 0) {
126
- return undefined
127
- }
128
-
129
- const primarySubjectKey = subjectKeys[0] ?? 'UnknownTool'
130
- const subjectLabel = subjectKeys.length <= 1
131
- ? primarySubjectKey
132
- : `${subjectKeys[0]} 等 ${subjectKeys.length} 项工具`
133
- const deniedTools = uniqueStrings([
134
- ...params.context.deniedTools,
135
- ...subjectKeys
136
- ])
137
- const permissionContext: PermissionInteractionContext = {
138
- adapter: params.adapter,
139
- currentMode: params.currentMode,
140
- deniedTools,
141
- reasons: uniqueStrings(params.context.reasons),
142
- subjectKey: primarySubjectKey,
143
- subjectLabel,
144
- scope: 'tool',
145
- projectConfigPath: PERMISSION_PROJECT_CONFIG_PATH
146
- }
147
-
148
- return {
149
- sessionId: params.sessionId,
150
- kind: 'permission',
151
- question: subjectKeys.length <= 1
152
- ? `当前任务需要使用 ${subjectLabel} 才能继续,请选择处理方式。`
153
- : `当前任务涉及 ${subjectKeys.join('、')} 等工具,请选择处理方式。`,
154
- options: [
155
- buildPermissionOption('同意本次', 'allow_once', '仅继续这次被拦截的操作。'),
156
- buildPermissionOption('同意并在当前会话忽略类似调用', 'allow_session', '本会话内同类工具不再重复询问。'),
157
- buildPermissionOption(
158
- '同意并在当前项目忽略类似调用',
159
- 'allow_project',
160
- `写入 ${PERMISSION_PROJECT_CONFIG_PATH},后续新会话仍生效。`
161
- ),
162
- buildPermissionOption('拒绝本次', 'deny_once', '拒绝当前这次操作。'),
163
- buildPermissionOption('拒绝并在当前会话阻止类似调用', 'deny_session', '本会话内同类工具直接拒绝。'),
164
- buildPermissionOption(
165
- '拒绝并在当前项目阻止类似调用',
166
- 'deny_project',
167
- `写入 ${PERMISSION_PROJECT_CONFIG_PATH},后续新会话仍生效。`
168
- )
169
- ],
170
- permissionContext
171
- }
172
- }
@@ -1,200 +0,0 @@
1
- import { mkdir, writeFile } from 'node:fs/promises'
2
- import { dirname } from 'node:path'
3
- import process from 'node:process'
4
-
5
- import { buildConfigJsonVariables, loadConfig, updateConfigFile } from '@vibe-forge/config'
6
- import type { Config, PermissionInteractionDecision } from '@vibe-forge/types'
7
- import {
8
- normalizePermissionToolName,
9
- normalizeSessionPermissionState,
10
- resolvePermissionMirrorPath
11
- } from '@vibe-forge/utils'
12
- import type { SessionPermissionState } from '@vibe-forge/utils'
13
-
14
- const PERMISSION_PROJECT_CONFIG_PATH = '.ai.config.json'
15
-
16
- const uniqueStrings = (values: string[]) => [...new Set(values)]
17
-
18
- const normalizeKeys = (values: string[]) =>
19
- uniqueStrings(
20
- values
21
- .map((value) => normalizePermissionToolName(value)?.key ?? value.trim())
22
- .filter((value): value is string => value.trim() !== '')
23
- )
24
-
25
- const removeKeys = (values: string[], keys: Set<string>) => (
26
- values.filter((value) => {
27
- const normalized = normalizePermissionToolName(value)?.key ?? value.trim()
28
- return !keys.has(normalized)
29
- })
30
- )
31
-
32
- const buildGeneralSectionValue = (config: Config | undefined, permissions: Config['permissions']) => ({
33
- baseDir: config?.baseDir,
34
- effort: config?.effort,
35
- defaultAdapter: config?.defaultAdapter,
36
- defaultModelService: config?.defaultModelService,
37
- defaultModel: config?.defaultModel,
38
- recommendedModels: config?.recommendedModels,
39
- interfaceLanguage: config?.interfaceLanguage,
40
- modelLanguage: config?.modelLanguage,
41
- announcements: config?.announcements,
42
- permissions,
43
- env: config?.env,
44
- notifications: config?.notifications,
45
- shortcuts: config?.shortcuts
46
- })
47
-
48
- const mutateSessionPermissionState = (
49
- state: SessionPermissionState,
50
- keys: string[],
51
- action: PermissionInteractionDecision
52
- ): SessionPermissionState => {
53
- const targetKeys = normalizeKeys(keys)
54
- const keySet = new Set(targetKeys)
55
- const next = normalizeSessionPermissionState(state)
56
-
57
- switch (action) {
58
- case 'allow_once':
59
- next.onceAllow = uniqueStrings([...removeKeys(next.onceAllow, keySet), ...targetKeys])
60
- next.onceDeny = removeKeys(next.onceDeny, keySet)
61
- return next
62
- case 'allow_session':
63
- next.allow = uniqueStrings([...removeKeys(next.allow, keySet), ...targetKeys])
64
- next.deny = removeKeys(next.deny, keySet)
65
- next.onceDeny = removeKeys(next.onceDeny, keySet)
66
- next.onceAllow = removeKeys(next.onceAllow, keySet)
67
- return next
68
- case 'allow_project':
69
- next.allow = uniqueStrings([...removeKeys(next.allow, keySet), ...targetKeys])
70
- next.deny = removeKeys(next.deny, keySet)
71
- next.onceDeny = removeKeys(next.onceDeny, keySet)
72
- next.onceAllow = removeKeys(next.onceAllow, keySet)
73
- return next
74
- case 'deny_once':
75
- return next
76
- case 'deny_session':
77
- next.deny = uniqueStrings([...removeKeys(next.deny, keySet), ...targetKeys])
78
- next.allow = removeKeys(next.allow, keySet)
79
- next.onceAllow = removeKeys(next.onceAllow, keySet)
80
- next.onceDeny = removeKeys(next.onceDeny, keySet)
81
- return next
82
- case 'deny_project':
83
- next.deny = uniqueStrings([...removeKeys(next.deny, keySet), ...targetKeys])
84
- next.allow = removeKeys(next.allow, keySet)
85
- next.onceAllow = removeKeys(next.onceAllow, keySet)
86
- next.onceDeny = removeKeys(next.onceDeny, keySet)
87
- return next
88
- }
89
- }
90
-
91
- const loadTaskConfig = async (cwd: string) =>
92
- await loadConfig({
93
- cwd,
94
- jsonVariables: buildConfigJsonVariables(cwd, process.env)
95
- })
96
-
97
- const buildMergedProjectPermissions = async (cwd: string) => {
98
- const [projectConfig, userConfig] = await loadTaskConfig(cwd)
99
- return {
100
- allow: [
101
- ...(projectConfig?.permissions?.allow ?? []),
102
- ...(userConfig?.permissions?.allow ?? [])
103
- ],
104
- deny: [
105
- ...(projectConfig?.permissions?.deny ?? []),
106
- ...(userConfig?.permissions?.deny ?? [])
107
- ],
108
- ask: [
109
- ...(projectConfig?.permissions?.ask ?? []),
110
- ...(userConfig?.permissions?.ask ?? [])
111
- ]
112
- }
113
- }
114
-
115
- const updateProjectPermissionLists = async (
116
- cwd: string,
117
- keys: string[],
118
- target: 'allow' | 'deny'
119
- ) => {
120
- const targetKeys = normalizeKeys(keys)
121
- const keySet = new Set(targetKeys)
122
- const [projectConfig] = await loadTaskConfig(cwd)
123
- const existingPermissions = projectConfig?.permissions ?? {}
124
- const nextPermissions: Config['permissions'] = {
125
- ...existingPermissions,
126
- allow: removeKeys(existingPermissions.allow ?? [], keySet),
127
- deny: removeKeys(existingPermissions.deny ?? [], keySet),
128
- ask: removeKeys(existingPermissions.ask ?? [], keySet)
129
- }
130
- nextPermissions[target] = uniqueStrings([...(nextPermissions[target] ?? []), ...targetKeys])
131
-
132
- await updateConfigFile({
133
- workspaceFolder: cwd,
134
- source: 'project',
135
- section: 'general',
136
- value: buildGeneralSectionValue(projectConfig, nextPermissions)
137
- })
138
- }
139
-
140
- export const syncTaskPermissionStateMirror = async (params: {
141
- cwd: string
142
- adapter?: string
143
- sessionId: string
144
- permissionState: SessionPermissionState
145
- }) => {
146
- if (params.adapter !== 'claude-code' && params.adapter !== 'opencode') {
147
- return
148
- }
149
-
150
- const mirrorPath = resolvePermissionMirrorPath(params.cwd, params.adapter, params.sessionId)
151
- const projectPermissions = await buildMergedProjectPermissions(params.cwd)
152
- await mkdir(dirname(mirrorPath), { recursive: true })
153
- await writeFile(
154
- mirrorPath,
155
- `${
156
- JSON.stringify(
157
- {
158
- sessionId: params.sessionId,
159
- adapter: params.adapter,
160
- permissionState: normalizeSessionPermissionState(params.permissionState),
161
- projectPermissions,
162
- updatedAt: Date.now()
163
- },
164
- null,
165
- 2
166
- )
167
- }\n`,
168
- 'utf8'
169
- )
170
- }
171
-
172
- export const applyTaskPermissionDecision = async (params: {
173
- cwd: string
174
- sessionId: string
175
- adapter?: string
176
- permissionState: SessionPermissionState
177
- subjectKeys: string[]
178
- action: PermissionInteractionDecision
179
- }) => {
180
- const subjectKeys = normalizeKeys(params.subjectKeys)
181
- if (subjectKeys.length === 0) {
182
- return normalizeSessionPermissionState(params.permissionState)
183
- }
184
-
185
- if (params.action === 'allow_project') await updateProjectPermissionLists(params.cwd, subjectKeys, 'allow')
186
- if (params.action === 'deny_project') await updateProjectPermissionLists(params.cwd, subjectKeys, 'deny')
187
-
188
- const nextState = mutateSessionPermissionState(
189
- params.permissionState,
190
- subjectKeys,
191
- params.action
192
- )
193
- await syncTaskPermissionStateMirror({
194
- cwd: params.cwd,
195
- adapter: params.adapter,
196
- sessionId: params.sessionId,
197
- permissionState: nextState
198
- })
199
- return nextState
200
- }
@@ -1,125 +0,0 @@
1
- import process from 'node:process'
2
-
3
- import type { SessionPermissionMode } from '@vibe-forge/types'
4
-
5
- import type { TaskInfo } from './manager'
6
-
7
- export const SESSION_PERMISSION_MODES = ['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions'] as const
8
- export const TASK_LOG_ORDERS = ['asc', 'desc'] as const
9
- export const DEFAULT_TASK_LOG_LIMIT = 10
10
- export type TaskLogsOrder = typeof TASK_LOG_ORDERS[number]
11
-
12
- export const START_TASKS_DESCRIPTION =
13
- 'Start multiple tasks in background or foreground. Use type "workspace" plus name to run in a configured workspace. If a task stalls, fails, or asks for permission/input, call GetTaskInfo. GetTaskInfo returns the 10 most recent logs by default in descending order, so newer log lines appear earlier in the logs array. If you need to add another instruction to a task, use SendTaskMessage: running tasks continue immediately, while completed or failed tasks resume the same conversation instead of forcing a replacement task. If GetTaskInfo returns pendingInput or pendingInteraction, resolve it with SubmitTaskInput. If logs show permission_required, you can answer the recovery prompt with SubmitTaskInput instead of restarting manually.'
14
-
15
- export const GET_TASK_INFO_DESCRIPTION =
16
- 'Get the detailed status, logs, pendingInput, pendingInteraction, lastError, and guidance for a task. By default this returns the 10 most recent logs in descending order, so newer log lines appear earlier in the logs array. Use logLimit to inspect a different number of recent logs, and set logOrder to "asc" when you want the selected log window in oldest-to-newest order. If you need to continue the task, call SendTaskMessage with mode "direct" or "steer": active tasks continue immediately, while completed or failed tasks resume the same conversation. If pendingInput is present, answer it with SubmitTaskInput.'
17
-
18
- export const SEND_TASK_MESSAGE_DESCRIPTION =
19
- 'Send a follow-up user message to a managed task. Use mode "direct" (default) to continue the current running task immediately. Use mode "steer" to queue a follow-up that should run after the current task finishes naturally. If the task already completed or failed and you still want to keep working in that same conversation, SendTaskMessage resumes the same task session instead of starting a replacement task. Do not use this to answer pendingInput or pendingInteraction; use SubmitTaskInput for that.'
20
-
21
- export const SUBMIT_TASK_INPUT_DESCRIPTION =
22
- 'Submit input for a task that is blocked waiting for permission or user input. First call GetTaskInfo or ListTasks, then use taskId plus one of pendingInput.payload.options[].value when available. Do not use this for ordinary follow-up instructions to a running task or queued steer messages; use SendTaskMessage instead. Common permission answers are allow_once, allow_session, allow_project, deny_once, deny_session, or deny_project.'
23
-
24
- export const RESPOND_TASK_INTERACTION_DESCRIPTION =
25
- 'Deprecated alias of SubmitTaskInput. Use SubmitTaskInput for both permission prompts and generic task input.'
26
-
27
- export const STOP_TASK_DESCRIPTION =
28
- 'Stop a running or blocked task. Use this when the task is no longer needed or cannot recover cleanly.'
29
-
30
- export const LIST_TASKS_DESCRIPTION =
31
- 'List all managed tasks with status, logs, pendingInput, pendingInteraction, lastError, and guidance. Each task returns the 10 most recent logs by default in descending order, so newer log lines appear earlier in the logs array. Use logLimit and logOrder to adjust the recent log window for every listed task. If a listed task needs another instruction, call SendTaskMessage with mode "direct" or "steer". If it is waiting for input, call GetTaskInfo for details or SubmitTaskInput to answer it.'
32
-
33
- export const TASK_PERMISSION_MODE_DESCRIPTION =
34
- 'Permission mode for the task. If omitted, inherits the parent session. Raise it only when the task is blocked by permission errors.'
35
-
36
- export const TASK_MODEL_DESCRIPTION =
37
- 'Model override for the task. Uses the same model selector format as a normal session model setting.'
38
-
39
- export const TASK_BACKGROUND_DESCRIPTION =
40
- 'Whether to run in background (default: true). If false, waits until the task completes, fails, or becomes blocked waiting for input, then returns the current logs.'
41
-
42
- export const TASK_LOG_LIMIT_DESCRIPTION =
43
- `How many recent log entries to include. Defaults to ${DEFAULT_TASK_LOG_LIMIT}.`
44
-
45
- export const TASK_LOG_ORDER_DESCRIPTION =
46
- 'Order of the selected log window. Defaults to "desc", which returns newer log lines first. Use "asc" for oldest-to-newest order.'
47
-
48
- export const resolveInheritedPermissionMode = (): SessionPermissionMode | undefined => {
49
- const value = process.env.__VF_PROJECT_AI_PERMISSION_MODE__?.trim()
50
- if (value == null || value === '') return undefined
51
- return (SESSION_PERMISSION_MODES as readonly string[]).includes(value)
52
- ? value as SessionPermissionMode
53
- : undefined
54
- }
55
-
56
- const buildTaskGuidance = (task: {
57
- status?: string
58
- pendingInteraction?: { payload?: { options?: Array<{ label: string; value?: string }> } }
59
- lastError?: { code?: string }
60
- }) => {
61
- const hints: string[] = []
62
-
63
- if (task.pendingInteraction != null) {
64
- const optionValues = task.pendingInteraction.payload?.options
65
- ?.map(option => option.value ?? option.label)
66
- .filter((value): value is string => value.trim() !== '')
67
- hints.push(
68
- optionValues != null && optionValues.length > 0
69
- ? `Task is waiting for input. Call SubmitTaskInput with one of: ${optionValues.join(', ')}.`
70
- : 'Task is waiting for input. Call SubmitTaskInput with the desired answer.'
71
- )
72
- }
73
-
74
- if (task.lastError?.code === 'permission_required') {
75
- hints.push(
76
- 'Task hit a permission error. Retry StartTasks with a more suitable permissionMode, or update project permissions before retrying.'
77
- )
78
- }
79
-
80
- if (task.status === 'failed' && hints.length === 0) {
81
- hints.push(
82
- 'Task failed. Inspect logs and lastError, then use SendTaskMessage to resume it or StartTasks to replace it if needed.'
83
- )
84
- }
85
-
86
- return hints
87
- }
88
-
89
- export const serializeTaskInfo = (params: {
90
- taskId: string
91
- description?: string
92
- status?: TaskInfo['status']
93
- info?: TaskInfo
94
- logLimit?: number
95
- logOrder?: TaskLogsOrder
96
- }) => {
97
- const info = params.info
98
- const safeInfo = (() => {
99
- if (info == null) {
100
- return undefined
101
- }
102
- const { session, onStop, serverSync, createdAt, logs, ...rest } = info
103
- return rest
104
- })()
105
- const selectedLogs = (() => {
106
- const logs = info?.logs ?? []
107
- const limit = params.logLimit
108
- const windowedLogs = limit == null
109
- ? logs
110
- : logs.slice(Math.max(0, logs.length - limit))
111
- return params.logOrder === 'desc'
112
- ? [...windowedLogs].reverse()
113
- : windowedLogs
114
- })()
115
-
116
- return {
117
- taskId: params.taskId,
118
- description: params.description ?? info?.description,
119
- status: info?.status ?? params.status,
120
- logs: selectedLogs,
121
- pendingInput: safeInfo?.pendingInteraction,
122
- ...safeInfo,
123
- guidance: buildTaskGuidance(safeInfo ?? {})
124
- }
125
- }
@@ -1,193 +0,0 @@
1
- import { z } from 'zod'
2
-
3
- import type { Register } from '../types'
4
- import type { TaskManager } from './manager'
5
- import {
6
- DEFAULT_TASK_LOG_LIMIT,
7
- GET_TASK_INFO_DESCRIPTION,
8
- LIST_TASKS_DESCRIPTION,
9
- RESPOND_TASK_INTERACTION_DESCRIPTION,
10
- SEND_TASK_MESSAGE_DESCRIPTION,
11
- STOP_TASK_DESCRIPTION,
12
- SUBMIT_TASK_INPUT_DESCRIPTION,
13
- TASK_LOG_LIMIT_DESCRIPTION,
14
- TASK_LOG_ORDERS,
15
- TASK_LOG_ORDER_DESCRIPTION
16
- } from './presentation'
17
- import {
18
- createSerializedTaskInfoContent,
19
- createSerializedTaskListContent,
20
- createTextContent
21
- } from './task-tool-responses'
22
-
23
- export const registerTaskRuntimeTools = (
24
- server: Parameters<Register>[0],
25
- taskManager: TaskManager
26
- ) => {
27
- server.registerTool(
28
- 'GetTaskInfo',
29
- {
30
- title: 'Get Task Info',
31
- description: GET_TASK_INFO_DESCRIPTION,
32
- inputSchema: z.object({
33
- taskId: z.string().describe('The ID of the task to check'),
34
- logLimit: z
35
- .number()
36
- .int()
37
- .min(1)
38
- .describe(TASK_LOG_LIMIT_DESCRIPTION)
39
- .default(DEFAULT_TASK_LOG_LIMIT),
40
- logOrder: z
41
- .enum(TASK_LOG_ORDERS)
42
- .describe(TASK_LOG_ORDER_DESCRIPTION)
43
- .default('desc')
44
- })
45
- },
46
- async ({ taskId, logLimit, logOrder }) => {
47
- const task = taskManager.getTask(taskId)
48
- if (!task) {
49
- return {
50
- content: createTextContent(`Task ${taskId} not found.`),
51
- isError: true
52
- }
53
- }
54
- return {
55
- content: createSerializedTaskInfoContent({ taskId, info: task, logLimit, logOrder })
56
- }
57
- }
58
- )
59
-
60
- server.registerTool(
61
- 'SendTaskMessage',
62
- {
63
- title: 'Send Task Message',
64
- description: SEND_TASK_MESSAGE_DESCRIPTION,
65
- inputSchema: z.object({
66
- taskId: z.string().describe('The ID of the running task to continue'),
67
- message: z
68
- .string()
69
- .trim()
70
- .min(1)
71
- .describe('The follow-up instruction to send to the task'),
72
- mode: z
73
- .enum(['direct', 'steer'])
74
- .describe('How to deliver the message: direct (default) or steer')
75
- .default('direct')
76
- })
77
- },
78
- async ({ taskId, message, mode }) => {
79
- await taskManager.sendTaskMessage({
80
- taskId,
81
- message,
82
- mode
83
- })
84
- const task = taskManager.getTask(taskId)
85
- return {
86
- content: createSerializedTaskInfoContent({ taskId, info: task })
87
- }
88
- }
89
- )
90
-
91
- server.registerTool(
92
- 'SubmitTaskInput',
93
- {
94
- title: 'Submit Task Input',
95
- description: SUBMIT_TASK_INPUT_DESCRIPTION,
96
- inputSchema: z.object({
97
- taskId: z.string().describe('The ID of the task that is waiting for input'),
98
- interactionId: z
99
- .string()
100
- .describe('Optional interaction ID. Omit it when the task only has one pending input.')
101
- .optional(),
102
- data: z
103
- .union([z.string(), z.array(z.string()).min(1)])
104
- .describe('The input to submit. Prefer pendingInput.payload.options[].value when available.')
105
- })
106
- },
107
- async ({ taskId, interactionId, data }) => {
108
- await taskManager.submitTaskInput({
109
- taskId,
110
- interactionId,
111
- data
112
- })
113
- const task = taskManager.getTask(taskId)
114
- return {
115
- content: createSerializedTaskInfoContent({ taskId, info: task })
116
- }
117
- }
118
- )
119
-
120
- server.registerTool(
121
- 'RespondTaskInteraction',
122
- {
123
- title: 'Respond Task Interaction',
124
- description: RESPOND_TASK_INTERACTION_DESCRIPTION,
125
- inputSchema: z.object({
126
- taskId: z.string().describe('The ID of the task that is waiting for input'),
127
- interactionId: z
128
- .string()
129
- .describe('Optional interaction ID. Omit it when the task only has one pending interaction.')
130
- .optional(),
131
- response: z
132
- .union([z.string(), z.array(z.string()).min(1)])
133
- .describe('The selected answer. Prefer pendingInteraction.options[].value when available.')
134
- })
135
- },
136
- async ({ taskId, interactionId, response }) => {
137
- await taskManager.submitTaskInput({
138
- taskId,
139
- interactionId,
140
- data: response
141
- })
142
- const task = taskManager.getTask(taskId)
143
- return {
144
- content: createSerializedTaskInfoContent({ taskId, info: task })
145
- }
146
- }
147
- )
148
-
149
- server.registerTool(
150
- 'StopTask',
151
- {
152
- title: 'Stop Task',
153
- description: STOP_TASK_DESCRIPTION,
154
- inputSchema: z.object({
155
- taskId: z.string().describe('The ID of the task to stop')
156
- })
157
- },
158
- async ({ taskId }) => {
159
- const success = taskManager.stopTask(taskId)
160
- return {
161
- content: createTextContent(
162
- success ? `Task ${taskId} stopped.` : `Failed to stop task ${taskId} (not found or already stopped).`
163
- )
164
- }
165
- }
166
- )
167
-
168
- server.registerTool(
169
- 'ListTasks',
170
- {
171
- title: 'List Tasks',
172
- description: LIST_TASKS_DESCRIPTION,
173
- inputSchema: z.object({
174
- logLimit: z
175
- .number()
176
- .int()
177
- .min(1)
178
- .describe(TASK_LOG_LIMIT_DESCRIPTION)
179
- .default(DEFAULT_TASK_LOG_LIMIT),
180
- logOrder: z
181
- .enum(TASK_LOG_ORDERS)
182
- .describe(TASK_LOG_ORDER_DESCRIPTION)
183
- .default('desc')
184
- })
185
- },
186
- async ({ logLimit, logOrder }) => {
187
- const tasks = taskManager.getAllTasks()
188
- return {
189
- content: createSerializedTaskListContent(tasks, logLimit, logOrder)
190
- }
191
- }
192
- )
193
- }