@vibe-forge/mcp 0.11.0 → 0.11.1

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,23 @@
1
+ import { afterEach, describe, expect, it } from 'vitest'
2
+
3
+ import { getParentSessionId } from '#~/sync.js'
4
+
5
+ describe('mcp sync helpers', () => {
6
+ afterEach(() => {
7
+ delete process.env.__VF_PROJECT_AI_SESSION_ID__
8
+ delete process.env.__VF_PROJECT_AI_CTX_ID__
9
+ })
10
+
11
+ it('prefers the current session id when resolving the parent session id', () => {
12
+ process.env.__VF_PROJECT_AI_SESSION_ID__ = 'session-parent'
13
+ process.env.__VF_PROJECT_AI_CTX_ID__ = 'ctx-parent'
14
+
15
+ expect(getParentSessionId()).toBe('session-parent')
16
+ })
17
+
18
+ it('falls back to ctx id when the session id is missing', () => {
19
+ process.env.__VF_PROJECT_AI_CTX_ID__ = 'ctx-parent'
20
+
21
+ expect(getParentSessionId()).toBe('ctx-parent')
22
+ })
23
+ })
@@ -4,11 +4,16 @@ const mocks = vi.hoisted(() => ({
4
4
  run: vi.fn(),
5
5
  generateAdapterQueryOptions: vi.fn(),
6
6
  callHook: vi.fn(),
7
+ buildConfigJsonVariables: vi.fn(),
8
+ loadConfig: vi.fn(),
9
+ updateConfigFile: vi.fn(),
7
10
  loadInjectDefaultSystemPromptValue: vi.fn(),
8
11
  mergeSystemPrompts: vi.fn(),
9
12
  extractTextFromMessage: vi.fn(),
10
13
  postSessionEvent: vi.fn(),
11
- fetchSessionMessages: vi.fn()
14
+ fetchSessionMessages: vi.fn(),
15
+ mkdir: vi.fn(),
16
+ writeFile: vi.fn()
12
17
  }))
13
18
 
14
19
  vi.mock('@vibe-forge/task', () => ({
@@ -21,6 +26,9 @@ vi.mock('@vibe-forge/hooks', () => ({
21
26
  }))
22
27
 
23
28
  vi.mock('@vibe-forge/config', () => ({
29
+ buildConfigJsonVariables: mocks.buildConfigJsonVariables,
30
+ loadConfig: mocks.loadConfig,
31
+ updateConfigFile: mocks.updateConfigFile,
24
32
  loadInjectDefaultSystemPromptValue: mocks.loadInjectDefaultSystemPromptValue,
25
33
  mergeSystemPrompts: mocks.mergeSystemPrompts
26
34
  }))
@@ -29,6 +37,11 @@ vi.mock('@vibe-forge/utils/chat-message', () => ({
29
37
  extractTextFromMessage: mocks.extractTextFromMessage
30
38
  }))
31
39
 
40
+ vi.mock('node:fs/promises', () => ({
41
+ mkdir: mocks.mkdir,
42
+ writeFile: mocks.writeFile
43
+ }))
44
+
32
45
  vi.mock('#~/sync.js', () => ({
33
46
  postSessionEvent: mocks.postSessionEvent,
34
47
  fetchSessionMessages: mocks.fetchSessionMessages
@@ -46,12 +59,17 @@ describe('taskManager fatal error scenarios', () => {
46
59
  mcpServers: undefined
47
60
  }
48
61
  ])
62
+ mocks.buildConfigJsonVariables.mockReturnValue({})
63
+ mocks.loadConfig.mockResolvedValue([undefined, undefined])
64
+ mocks.updateConfigFile.mockResolvedValue(undefined)
49
65
  mocks.callHook.mockResolvedValue(undefined)
50
66
  mocks.loadInjectDefaultSystemPromptValue.mockResolvedValue(true)
51
67
  mocks.mergeSystemPrompts.mockReturnValue(undefined)
52
68
  mocks.postSessionEvent.mockResolvedValue(undefined)
53
69
  mocks.fetchSessionMessages.mockResolvedValue([])
54
70
  mocks.extractTextFromMessage.mockReturnValue('')
71
+ mocks.mkdir.mockResolvedValue(undefined)
72
+ mocks.writeFile.mockResolvedValue(undefined)
55
73
  })
56
74
 
57
75
  it('keeps the task failed when a fatal error is followed by stop', async () => {
@@ -88,4 +106,304 @@ describe('taskManager fatal error scenarios', () => {
88
106
  expect(task?.status).toBe('failed')
89
107
  expect(task?.logs).toContain('Incomplete response returned')
90
108
  })
109
+
110
+ it('surfaces pending interactions in logs and task state', async () => {
111
+ const { TaskManager } = await import('#~/tools/task/manager.js')
112
+
113
+ mocks.run.mockImplementationOnce(async (_options: unknown, adapterOptions: any) => {
114
+ const session = {
115
+ emit: vi.fn(() => {
116
+ adapterOptions.onEvent({
117
+ type: 'interaction_request',
118
+ data: {
119
+ id: 'interaction-1',
120
+ payload: {
121
+ sessionId: 'task-waiting',
122
+ kind: 'permission',
123
+ question: 'Allow editing files?',
124
+ options: [
125
+ { label: 'Allow once', value: 'allow_once' },
126
+ { label: 'Deny once', value: 'deny_once' }
127
+ ]
128
+ }
129
+ }
130
+ })
131
+ }),
132
+ kill: vi.fn(),
133
+ respondInteraction: vi.fn()
134
+ }
135
+ return { session }
136
+ })
137
+
138
+ const managedTaskManager = new TaskManager()
139
+ const result = await managedTaskManager.startTask({
140
+ taskId: 'task-waiting',
141
+ description: 'trigger',
142
+ background: false
143
+ })
144
+
145
+ const task = managedTaskManager.getTask('task-waiting')
146
+ expect(task?.status).toBe('waiting_input')
147
+ expect(task?.pendingInteraction).toEqual({
148
+ id: 'interaction-1',
149
+ payload: expect.objectContaining({
150
+ question: 'Allow editing files?'
151
+ }),
152
+ source: 'adapter'
153
+ })
154
+ expect(result.logs).toContain(
155
+ 'Waiting for permission input: Allow editing files? Available responses: allow_once, deny_once.'
156
+ )
157
+ })
158
+
159
+ it('responds to pending interactions and syncs the response', async () => {
160
+ const { TaskManager } = await import('#~/tools/task/manager.js')
161
+ const respondInteraction = vi.fn()
162
+
163
+ mocks.run.mockImplementationOnce(async (_options: unknown, adapterOptions: any) => {
164
+ const session = {
165
+ emit: vi.fn(() => {
166
+ adapterOptions.onEvent({
167
+ type: 'interaction_request',
168
+ data: {
169
+ id: 'interaction-2',
170
+ payload: {
171
+ sessionId: 'task-respond',
172
+ kind: 'permission',
173
+ question: 'Allow bash?',
174
+ options: [
175
+ { label: 'Allow once', value: 'allow_once' }
176
+ ]
177
+ }
178
+ }
179
+ })
180
+ }),
181
+ kill: vi.fn(),
182
+ respondInteraction
183
+ }
184
+ return { session }
185
+ })
186
+
187
+ const managedTaskManager = new TaskManager()
188
+ await managedTaskManager.startTask({
189
+ taskId: 'task-respond',
190
+ description: 'trigger',
191
+ enableServerSync: true
192
+ })
193
+
194
+ await managedTaskManager.respondToTaskInteraction({
195
+ taskId: 'task-respond',
196
+ data: 'allow_once'
197
+ })
198
+
199
+ const task = managedTaskManager.getTask('task-respond')
200
+ expect(respondInteraction).toHaveBeenCalledWith('interaction-2', 'allow_once')
201
+ expect(task?.status).toBe('running')
202
+ expect(task?.pendingInteraction).toBeUndefined()
203
+ expect(task?.logs).toContain('Interaction response submitted: allow_once')
204
+ expect(mocks.postSessionEvent).toHaveBeenCalledWith('task-respond', {
205
+ type: 'interaction_response',
206
+ id: 'interaction-2',
207
+ data: 'allow_once'
208
+ })
209
+ })
210
+
211
+ it('builds synthetic permission recovery for claude-code and resumes after SubmitTaskInput', async () => {
212
+ const { TaskManager } = await import('#~/tools/task/manager.js')
213
+ const resumedEmit = vi.fn()
214
+
215
+ mocks.run
216
+ .mockImplementationOnce(async (_options: unknown, adapterOptions: any) => {
217
+ const session = {
218
+ emit: vi.fn(() => {
219
+ adapterOptions.onEvent({
220
+ type: 'message',
221
+ data: {
222
+ id: 'assistant-tool-use',
223
+ role: 'assistant',
224
+ content: [{
225
+ type: 'tool_use',
226
+ id: 'tool-use-1',
227
+ name: 'adapter:claude-code:Write',
228
+ input: {
229
+ file_path: '/tmp/demo.txt',
230
+ content: 'ok'
231
+ }
232
+ }],
233
+ createdAt: Date.now()
234
+ }
235
+ })
236
+ adapterOptions.onEvent({
237
+ type: 'error',
238
+ data: {
239
+ message: 'Permission required to continue',
240
+ code: 'permission_required',
241
+ details: {
242
+ toolUseId: 'tool-use-1',
243
+ permissionDenials: [{
244
+ message: 'Write requires approval',
245
+ deniedTools: []
246
+ }]
247
+ },
248
+ fatal: true
249
+ }
250
+ })
251
+ adapterOptions.onEvent({
252
+ type: 'exit',
253
+ data: {
254
+ exitCode: 1,
255
+ stderr: 'permission blocked'
256
+ }
257
+ })
258
+ }),
259
+ kill: vi.fn()
260
+ }
261
+ return {
262
+ session,
263
+ resolvedAdapter: 'claude-code'
264
+ }
265
+ })
266
+ .mockResolvedValueOnce({
267
+ session: {
268
+ emit: resumedEmit,
269
+ kill: vi.fn()
270
+ },
271
+ resolvedAdapter: 'claude-code'
272
+ })
273
+
274
+ const managedTaskManager = new TaskManager()
275
+ await managedTaskManager.startTask({
276
+ taskId: 'task-claude-recovery',
277
+ description: 'trigger',
278
+ adapter: 'claude-code'
279
+ })
280
+
281
+ const waitingTask = managedTaskManager.getTask('task-claude-recovery')
282
+ expect(waitingTask?.status).toBe('waiting_input')
283
+ expect(waitingTask?.pendingInteraction).toMatchObject({
284
+ source: 'permission_recovery',
285
+ subjectKeys: ['Write'],
286
+ payload: {
287
+ question: '当前任务需要使用 Write 才能继续,请选择处理方式。',
288
+ kind: 'permission',
289
+ permissionContext: expect.objectContaining({
290
+ currentMode: undefined,
291
+ deniedTools: ['Write'],
292
+ subjectKey: 'Write',
293
+ subjectLabel: 'Write',
294
+ projectConfigPath: '.ai.config.json'
295
+ })
296
+ }
297
+ })
298
+
299
+ await managedTaskManager.submitTaskInput({
300
+ taskId: 'task-claude-recovery',
301
+ data: 'allow_session'
302
+ })
303
+
304
+ const resumedTask = managedTaskManager.getTask('task-claude-recovery')
305
+ expect(resumedTask?.status).toBe('running')
306
+ expect(resumedTask?.pendingInteraction).toBeUndefined()
307
+ expect(resumedTask?.permissionState).toEqual(expect.objectContaining({
308
+ allow: ['Write']
309
+ }))
310
+ expect(resumedTask?.logs).toContain('Permission decision applied: allow_session. Restarting task.')
311
+ expect(mocks.run).toHaveBeenCalledTimes(2)
312
+ expect(resumedEmit).toHaveBeenCalledWith({
313
+ type: 'message',
314
+ content: [{
315
+ type: 'text',
316
+ text: '权限规则已更新。请继续刚才被权限拦截的工作,并重试被阻止的操作。'
317
+ }]
318
+ })
319
+ expect(mocks.writeFile).toHaveBeenCalled()
320
+ })
321
+
322
+ it('stops blocked tasks even after the failed session has already exited', async () => {
323
+ const { TaskManager } = await import('#~/tools/task/manager.js')
324
+
325
+ mocks.run.mockImplementationOnce(async (_options: unknown, adapterOptions: any) => {
326
+ const session = {
327
+ emit: vi.fn(() => {
328
+ adapterOptions.onEvent({
329
+ type: 'message',
330
+ data: {
331
+ id: 'assistant-tool-use-stop',
332
+ role: 'assistant',
333
+ content: [{
334
+ type: 'tool_use',
335
+ id: 'tool-use-stop-1',
336
+ name: 'adapter:claude-code:Write',
337
+ input: {
338
+ file_path: '/tmp/demo.txt',
339
+ content: 'blocked'
340
+ }
341
+ }],
342
+ createdAt: Date.now()
343
+ }
344
+ })
345
+ adapterOptions.onEvent({
346
+ type: 'error',
347
+ data: {
348
+ message: 'Permission required to continue',
349
+ code: 'permission_required',
350
+ details: {
351
+ toolUseId: 'tool-use-stop-1',
352
+ permissionDenials: [{
353
+ message: 'Write requires approval',
354
+ deniedTools: []
355
+ }]
356
+ },
357
+ fatal: true
358
+ }
359
+ })
360
+ adapterOptions.onEvent({
361
+ type: 'exit',
362
+ data: {
363
+ exitCode: 1,
364
+ stderr: 'permission blocked'
365
+ }
366
+ })
367
+ }),
368
+ kill: vi.fn()
369
+ }
370
+ return {
371
+ session,
372
+ resolvedAdapter: 'claude-code'
373
+ }
374
+ })
375
+
376
+ const managedTaskManager = new TaskManager()
377
+ await managedTaskManager.startTask({
378
+ taskId: 'task-stop-waiting',
379
+ description: 'trigger',
380
+ adapter: 'claude-code',
381
+ enableServerSync: true
382
+ })
383
+
384
+ const waitingTask = managedTaskManager.getTask('task-stop-waiting')
385
+ expect(waitingTask?.status).toBe('waiting_input')
386
+ expect(waitingTask?.session).toBeUndefined()
387
+ expect(waitingTask?.pendingInteraction).toBeDefined()
388
+
389
+ expect(managedTaskManager.stopTask('task-stop-waiting')).toBe(true)
390
+ await new Promise(resolve => setTimeout(resolve, 0))
391
+
392
+ const stoppedTask = managedTaskManager.getTask('task-stop-waiting')
393
+ expect(stoppedTask?.status).toBe('failed')
394
+ expect(stoppedTask?.pendingInteraction).toBeUndefined()
395
+ expect(stoppedTask?.logs).toContain('Task stopped by user')
396
+ expect(mocks.postSessionEvent).toHaveBeenCalledWith('task-stop-waiting', {
397
+ type: 'interaction_response',
398
+ id: expect.stringContaining('task-recovery:task-stop-waiting:'),
399
+ data: 'cancel'
400
+ })
401
+ expect(mocks.postSessionEvent).toHaveBeenCalledWith('task-stop-waiting', {
402
+ type: 'error',
403
+ data: {
404
+ message: 'Task stopped by user',
405
+ fatal: true
406
+ }
407
+ })
408
+ })
91
409
  })
@@ -8,6 +8,8 @@ const mocks = vi.hoisted(() => {
8
8
  createChildSession: vi.fn(),
9
9
  getParentSessionId: vi.fn(),
10
10
  startTask: vi.fn(),
11
+ submitTaskInput: vi.fn(),
12
+ respondToTaskInteraction: vi.fn(),
11
13
  getTask: vi.fn(),
12
14
  stopTask: vi.fn(),
13
15
  getAllTasks: vi.fn(),
@@ -31,6 +33,8 @@ vi.mock('#~/sync.js', () => ({
31
33
  vi.mock('#~/tools/task/manager.js', () => ({
32
34
  TaskManager: class {
33
35
  startTask = mocks.startTask
36
+ submitTaskInput = mocks.submitTaskInput
37
+ respondToTaskInteraction = mocks.respondToTaskInteraction
34
38
  getTask = mocks.getTask
35
39
  stopTask = mocks.stopTask
36
40
  getAllTasks = mocks.getAllTasks
@@ -41,11 +45,15 @@ describe('task tool integration', () => {
41
45
  beforeEach(() => {
42
46
  vi.clearAllMocks()
43
47
  process.env.__VF_PROJECT_AI_SESSION_ID__ = 'sess-1'
48
+ delete process.env.__VF_PROJECT_AI_PERMISSION_MODE__
44
49
  let nextTaskId = 1
45
50
  mocks.uuid.mockImplementation(() => `task-${nextTaskId++}`)
46
51
  mocks.callHook.mockResolvedValue({ continue: true })
47
52
  mocks.getParentSessionId.mockReturnValue(undefined)
53
+ mocks.createChildSession.mockResolvedValue({})
48
54
  mocks.startTask.mockResolvedValue(undefined)
55
+ mocks.submitTaskInput.mockResolvedValue(undefined)
56
+ mocks.respondToTaskInteraction.mockResolvedValue(undefined)
49
57
  mocks.getTask.mockImplementation((taskId: string) => ({
50
58
  taskId,
51
59
  status: 'completed',
@@ -85,4 +93,126 @@ describe('task tool integration', () => {
85
93
  taskId: 'task-1'
86
94
  }))
87
95
  })
96
+
97
+ it('inherits the parent permission mode when the task does not specify one', async () => {
98
+ process.env.__VF_PROJECT_AI_PERMISSION_MODE__ = 'dontAsk'
99
+ mocks.getParentSessionId.mockReturnValue('parent-session')
100
+
101
+ const { createTaskRegister } = await import('#~/tools/task/index.js')
102
+
103
+ const tester = createToolTester()
104
+ createTaskRegister()(tester.mockRegister)
105
+
106
+ await tester.callTool('StartTasks', {
107
+ tasks: [{
108
+ description: 'inherit permissions',
109
+ type: 'default'
110
+ }]
111
+ })
112
+
113
+ expect(mocks.callHook).toHaveBeenCalledWith(
114
+ 'StartTasks',
115
+ expect.objectContaining({
116
+ tasks: [expect.objectContaining({
117
+ taskId: 'task-1',
118
+ permissionMode: 'dontAsk'
119
+ })]
120
+ })
121
+ )
122
+ expect(mocks.createChildSession).toHaveBeenCalledWith(expect.objectContaining({
123
+ id: 'task-1',
124
+ parentSessionId: 'parent-session',
125
+ permissionMode: 'dontAsk'
126
+ }))
127
+ expect(mocks.startTask).toHaveBeenCalledWith(expect.objectContaining({
128
+ taskId: 'task-1',
129
+ permissionMode: 'dontAsk',
130
+ enableServerSync: true
131
+ }))
132
+ })
133
+
134
+ it('keeps an explicit task permission mode over the inherited parent mode', async () => {
135
+ process.env.__VF_PROJECT_AI_PERMISSION_MODE__ = 'dontAsk'
136
+
137
+ const { createTaskRegister } = await import('#~/tools/task/index.js')
138
+
139
+ const tester = createToolTester()
140
+ createTaskRegister()(tester.mockRegister)
141
+
142
+ await tester.callTool('StartTasks', {
143
+ tasks: [{
144
+ description: 'override permissions',
145
+ type: 'default',
146
+ permissionMode: 'plan'
147
+ }]
148
+ })
149
+
150
+ expect(mocks.startTask).toHaveBeenCalledWith(expect.objectContaining({
151
+ taskId: 'task-1',
152
+ permissionMode: 'plan'
153
+ }))
154
+ })
155
+
156
+ it('registers recovery guidance in task tool descriptions', async () => {
157
+ const { createTaskRegister } = await import('#~/tools/task/index.js')
158
+
159
+ const tester = createToolTester()
160
+ createTaskRegister()(tester.mockRegister)
161
+
162
+ expect(tester.getRegisteredTools()).toContain('SubmitTaskInput')
163
+ expect(tester.getRegisteredTools()).toContain('RespondTaskInteraction')
164
+ expect(tester.getToolInfo('StartTasks')?.description).toContain('GetTaskInfo')
165
+ expect(tester.getToolInfo('GetTaskInfo')?.description).toContain('SubmitTaskInput')
166
+ expect(tester.getToolInfo('ListTasks')?.description).toContain('pendingInput')
167
+ expect(tester.getToolInfo('SubmitTaskInput')?.description).toContain('allow_once')
168
+ expect(tester.getToolInfo('RespondTaskInteraction')?.description).toContain('Deprecated alias')
169
+ })
170
+
171
+ it('forwards SubmitTaskInput to the task manager', async () => {
172
+ mocks.getTask.mockReturnValue({
173
+ taskId: 'task-1',
174
+ status: 'running',
175
+ logs: ['Interaction response submitted: allow_once']
176
+ })
177
+
178
+ const { createTaskRegister } = await import('#~/tools/task/index.js')
179
+
180
+ const tester = createToolTester()
181
+ createTaskRegister()(tester.mockRegister)
182
+
183
+ await tester.callTool('SubmitTaskInput', {
184
+ taskId: 'task-1',
185
+ data: 'allow_once'
186
+ })
187
+
188
+ expect(mocks.submitTaskInput).toHaveBeenCalledWith({
189
+ taskId: 'task-1',
190
+ interactionId: undefined,
191
+ data: 'allow_once'
192
+ })
193
+ })
194
+
195
+ it('keeps RespondTaskInteraction as a deprecated alias', async () => {
196
+ mocks.getTask.mockReturnValue({
197
+ taskId: 'task-1',
198
+ status: 'running',
199
+ logs: ['Interaction response submitted: allow_once']
200
+ })
201
+
202
+ const { createTaskRegister } = await import('#~/tools/task/index.js')
203
+
204
+ const tester = createToolTester()
205
+ createTaskRegister()(tester.mockRegister)
206
+
207
+ await tester.callTool('RespondTaskInteraction', {
208
+ taskId: 'task-1',
209
+ response: 'allow_once'
210
+ })
211
+
212
+ expect(mocks.submitTaskInput).toHaveBeenCalledWith({
213
+ taskId: 'task-1',
214
+ interactionId: undefined,
215
+ data: 'allow_once'
216
+ })
217
+ })
88
218
  })
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vibe-forge/mcp",
3
- "version": "0.11.0",
3
+ "version": "0.11.1",
4
4
  "description": "Vibe Forge MCP server",
5
5
  "imports": {
6
6
  "#~/*.js": {
@@ -33,13 +33,13 @@
33
33
  "@modelcontextprotocol/sdk": "^1.25.3",
34
34
  "commander": "^12.1.0",
35
35
  "zod": "^3.24.1",
36
+ "@vibe-forge/hooks": "^0.11.1",
37
+ "@vibe-forge/config": "^0.11.0",
36
38
  "@vibe-forge/cli-helper": "^0.11.0",
39
+ "@vibe-forge/types": "^0.11.1",
40
+ "@vibe-forge/utils": "^0.11.1",
37
41
  "@vibe-forge/register": "^0.11.0",
38
- "@vibe-forge/config": "^0.11.0",
39
- "@vibe-forge/hooks": "^0.11.0",
40
- "@vibe-forge/task": "^0.11.0",
41
- "@vibe-forge/types": "^0.11.0",
42
- "@vibe-forge/utils": "^0.11.0"
42
+ "@vibe-forge/task": "^0.11.2"
43
43
  },
44
44
  "scripts": {
45
45
  "test": "pnpm -C ../.. exec vitest run --workspace vitest.workspace.ts --project bundler packages/mcp/__tests__"
package/src/sync.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import process from 'node:process'
2
2
 
3
- import type { WSEvent } from '@vibe-forge/types'
3
+ import type { SessionPermissionMode, WSEvent } from '@vibe-forge/types'
4
4
  import { extractTextFromMessage } from '@vibe-forge/utils/chat-message'
5
5
 
6
6
  const getServerBaseUrl = () => {
@@ -10,6 +10,10 @@ const getServerBaseUrl = () => {
10
10
  }
11
11
 
12
12
  export const getParentSessionId = () => {
13
+ const sessionId = process.env.__VF_PROJECT_AI_SESSION_ID__
14
+ if (sessionId != null && sessionId !== '') {
15
+ return sessionId
16
+ }
13
17
  const ctxId = process.env.__VF_PROJECT_AI_CTX_ID__
14
18
  return ctxId ?? undefined
15
19
  }
@@ -18,6 +22,7 @@ export const createChildSession = async (params: {
18
22
  id: string
19
23
  title?: string
20
24
  parentSessionId?: string
25
+ permissionMode?: SessionPermissionMode
21
26
  }) => {
22
27
  const baseUrl = getServerBaseUrl()
23
28
  const response = await fetch(`${baseUrl}/api/sessions`, {
@@ -27,6 +32,7 @@ export const createChildSession = async (params: {
27
32
  id: params.id,
28
33
  title: params.title,
29
34
  parentSessionId: params.parentSessionId,
35
+ permissionMode: params.permissionMode,
30
36
  start: false
31
37
  })
32
38
  })