Skip to content

Commit 5b4dd02

Browse files
committed
fix(mothership): clarify tool permissions and folder context
1 parent 1a6d590 commit 5b4dd02

4 files changed

Lines changed: 67 additions & 30 deletions

File tree

‎apps/docs/openapi-v2-workflows.json‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8069,12 +8069,12 @@
80698069
"usageControl": {
80708070
"type": "string",
80718071
"enum": ["auto", "force", "none"],
8072-
"description": "When the Agent may call the tool: `auto` lets the model decide, `force` requires a call, and `none` disables it. Omitted means `auto`."
8072+
"description": "Selector Permission Mode: `auto` lets the model decide, `force` requires a call, and `none` disables it. Supply this property and omit `usageControlExpression` to select or switch to Selector mode; do not send null or set `canonicalModes` or block-level `advancedMode`. With neither property, the default is `auto`."
80738073
},
80748074
"usageControlExpression": {
80758075
"type": "string",
80768076
"maxLength": 2048,
8077-
"description": "Variable-capable tool mode value used when the matching canonical mode is `advanced`. It must resolve to `auto`, `force`, or `none` at execution time."
8077+
"description": "Variable Permission Mode: a reference such as `<start.toolMode>` or a literal such as `none`, resolving to `auto`, `force`, or `none` at execution time. Supply this property and omit `usageControl` to select or switch to Variable mode; do not send null or set `canonicalModes` or block-level `advancedMode`."
80788078
},
80798079
"params": {
80808080
"type": "object",
@@ -8121,12 +8121,12 @@
81218121
"usageControl": {
81228122
"type": "string",
81238123
"enum": ["auto", "force", "none"],
8124-
"description": "When the Agent may call the tool: `auto` lets the model decide, `force` requires a call, and `none` disables it. Omitted means `auto`."
8124+
"description": "Selector Permission Mode: `auto` lets the model decide, `force` requires a call, and `none` disables it. Supply this property and omit `usageControlExpression` to select or switch to Selector mode; do not send null or set `canonicalModes` or block-level `advancedMode`. With neither property, the default is `auto`."
81258125
},
81268126
"usageControlExpression": {
81278127
"type": "string",
81288128
"maxLength": 2048,
8129-
"description": "Variable-capable tool mode value used when the matching canonical mode is `advanced`. It must resolve to `auto`, `force`, or `none` at execution time."
8129+
"description": "Variable Permission Mode: a reference such as `<start.toolMode>` or a literal such as `none`, resolving to `auto`, `force`, or `none` at execution time. Supply this property and omit `usageControl` to select or switch to Variable mode; do not send null or set `canonicalModes` or block-level `advancedMode`."
81308130
}
81318131
},
81328132
"required": ["type", "customToolId"],
@@ -8194,12 +8194,12 @@
81948194
"usageControl": {
81958195
"type": "string",
81968196
"enum": ["auto", "force", "none"],
8197-
"description": "When the Agent may call the tool: `auto` lets the model decide, `force` requires a call, and `none` disables it. Omitted means `auto`."
8197+
"description": "Selector Permission Mode: `auto` lets the model decide, `force` requires a call, and `none` disables it. Supply this property and omit `usageControlExpression` to select or switch to Selector mode; do not send null or set `canonicalModes` or block-level `advancedMode`. With neither property, the default is `auto`."
81988198
},
81998199
"usageControlExpression": {
82008200
"type": "string",
82018201
"maxLength": 2048,
8202-
"description": "Variable-capable tool mode value used when the matching canonical mode is `advanced`. It must resolve to `auto`, `force`, or `none` at execution time."
8202+
"description": "Variable Permission Mode: a reference such as `<start.toolMode>` or a literal such as `none`, resolving to `auto`, `force`, or `none` at execution time. Supply this property and omit `usageControl` to select or switch to Variable mode; do not send null or set `canonicalModes` or block-level `advancedMode`."
82038203
}
82048204
},
82058205
"required": ["type", "schema", "code"],
@@ -8264,12 +8264,12 @@
82648264
"usageControl": {
82658265
"type": "string",
82668266
"enum": ["auto", "force", "none"],
8267-
"description": "When the Agent may call the tool: `auto` lets the model decide, `force` requires a call, and `none` disables it. Omitted means `auto`."
8267+
"description": "Selector Permission Mode: `auto` lets the model decide, `force` requires a call, and `none` disables it. Supply this property and omit `usageControlExpression` to select or switch to Selector mode; do not send null or set `canonicalModes` or block-level `advancedMode`. With neither property, the default is `auto`."
82688268
},
82698269
"usageControlExpression": {
82708270
"type": "string",
82718271
"maxLength": 2048,
8272-
"description": "Variable-capable tool mode value used when the matching canonical mode is `advanced`. It must resolve to `auto`, `force`, or `none` at execution time."
8272+
"description": "Variable Permission Mode: a reference such as `<start.toolMode>` or a literal such as `none`, resolving to `auto`, `force`, or `none` at execution time. Supply this property and omit `usageControl` to select or switch to Variable mode; do not send null or set `canonicalModes` or block-level `advancedMode`."
82738273
}
82748274
},
82758275
"required": ["type", "params"],
@@ -8377,12 +8377,12 @@
83778377
"usageControl": {
83788378
"type": "string",
83798379
"enum": ["auto", "force", "none"],
8380-
"description": "When the Agent may call the tool: `auto` lets the model decide, `force` requires a call, and `none` disables it. Omitted means `auto`."
8380+
"description": "Selector Permission Mode: `auto` lets the model decide, `force` requires a call, and `none` disables it. Supply this property and omit `usageControlExpression` to select or switch to Selector mode; do not send null or set `canonicalModes` or block-level `advancedMode`. With neither property, the default is `auto`."
83818381
},
83828382
"usageControlExpression": {
83838383
"type": "string",
83848384
"maxLength": 2048,
8385-
"description": "Variable-capable tool mode value used when the matching canonical mode is `advanced`. It must resolve to `auto`, `force`, or `none` at execution time."
8385+
"description": "Variable Permission Mode: a reference such as `<start.toolMode>` or a literal such as `none`, resolving to `auto`, `force`, or `none` at execution time. Supply this property and omit `usageControl` to select or switch to Variable mode; do not send null or set `canonicalModes` or block-level `advancedMode`."
83868386
}
83878387
},
83888388
"required": ["type", "params"],

‎apps/sim/lib/api/contracts/v2/workflows.ts‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3026,14 +3026,14 @@ const WORKFLOW_OPERATION_PARAM_ENVELOPE =
30263026
const v2AgentToolUsageControlSchema = z
30273027
.enum(['auto', 'force', 'none'])
30283028
.describe(
3029-
'When the Agent may call the tool: `auto` lets the model decide, `force` requires a call, and `none` disables it. Omitted means `auto`.'
3029+
'Selector Permission Mode: `auto` lets the model decide, `force` requires a call, and `none` disables it. Supply this property and omit `usageControlExpression` to select or switch to Selector mode; do not send null or set `canonicalModes` or block-level `advancedMode`. With neither property, the default is `auto`.'
30303030
)
30313031

30323032
const v2AgentToolUsageControlExpressionSchema = z
30333033
.string()
30343034
.max(2048, 'Agent tool mode expression must be at most 2048 characters')
30353035
.describe(
3036-
'Variable-capable tool mode value used when the matching canonical mode is `advanced`. It must resolve to `auto`, `force`, or `none` at execution time.'
3036+
'Variable Permission Mode: a reference such as `<start.toolMode>` or a literal such as `none`, resolving to `auto`, `force`, or `none` at execution time. Supply this property and omit `usageControl` to select or switch to Variable mode; do not send null or set `canonicalModes` or block-level `advancedMode`.'
30373037
)
30383038

30393039
const v2AgentToolParamsSchema = z

‎apps/sim/lib/mothership/chat/process-contents.test.ts‎

Lines changed: 49 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -193,14 +193,17 @@ describe('processContextsServer - workflow references', () => {
193193
{ id: 'folder-1', name: 'Leads / new', parentId: 'root' },
194194
],
195195
})
196-
expect(
197-
await resolveActiveResourceContext('folder', 'folder-1', 'workspace-1', 'reader')
198-
).toMatchObject({
199-
type: 'active_resource',
200-
content: JSON.stringify({
201-
resourceType: 'workflow',
202-
folderPath: '/Sales%20team/Leads%20%2F%20new',
203-
}),
196+
const context = await resolveActiveResourceContext(
197+
'folder',
198+
'folder-1',
199+
'workspace-1',
200+
'reader'
201+
)
202+
expect(context?.type).toBe('active_resource')
203+
expect(JSON.parse(context!.content)).toEqual({
204+
resourceType: 'workflow',
205+
folderPath: '/Sales%20team/Leads%20%2F%20new',
206+
discovery: expect.stringContaining('including descendants'),
204207
})
205208
expect(listWorkflowFolders).toHaveBeenCalledWith({
206209
principal: expect.objectContaining({ subjectUserId: 'reader', workspaceId: 'workspace-1' }),
@@ -911,13 +914,16 @@ describe('file folder context', () => {
911914

912915
it('uses the CLI folder path while preserving literal slashes inside folder names', async () => {
913916
resolveFileFolderPath.mockResolvedValueOnce({ path: 'Reports/Client \\/ notes' })
914-
expect(await resolveActiveResourceContext('filefolder', 'folder-1', 'ws-1', 'reader')).toEqual({
917+
const context = await resolveActiveResourceContext('filefolder', 'folder-1', 'ws-1', 'reader')
918+
expect(context).toEqual({
915919
type: 'active_resource',
916920
tag: '@active_resource',
917-
content: JSON.stringify({
918-
resourceType: 'file',
919-
folderPath: '/Reports/Client%20%2F%20notes',
920-
}),
921+
content: expect.any(String),
922+
})
923+
expect(JSON.parse(context!.content)).toEqual({
924+
resourceType: 'file',
925+
folderPath: '/Reports/Client%20%2F%20notes',
926+
discovery: expect.stringContaining('including descendants'),
921927
})
922928
expect(resolveFileFolderPath).toHaveBeenCalledWith({
923929
principal: expect.objectContaining({ subjectUserId: 'reader', workspaceId: 'ws-1' }),
@@ -1549,9 +1555,11 @@ describe('folder and foldered-resource chat pointers', () => {
15491555
'user-1',
15501556
'chat-1'
15511557
)
1552-
const content = JSON.stringify({
1558+
const content = mention.content
1559+
expect(JSON.parse(content)).toMatchObject({
15531560
resourceType: _kind === 'knowledge' ? 'knowledge_base' : _kind,
15541561
folderPath: '/Finance%2FLegal/Q4%20100%25',
1562+
discovery: expect.stringContaining('including descendants'),
15551563
})
15561564
expect(mention).toMatchObject({ type: context.kind, tag: '@Chosen folder', content })
15571565
expect(mention.path).toBeUndefined()
@@ -1586,7 +1594,7 @@ describe('folder and foldered-resource chat pointers', () => {
15861594
'chat-1'
15871595
)
15881596
expect(result.path).toBeUndefined()
1589-
expect(JSON.parse(result.content)).toEqual({
1597+
expect(JSON.parse(result.content)).toMatchObject({
15901598
resourceType: 'file',
15911599
folderPath: '/Finance%2FLegal/Q4%20100%25',
15921600
})
@@ -1606,6 +1614,27 @@ describe('folder and foldered-resource chat pointers', () => {
16061614
})
16071615
})
16081616

1617+
it('keeps literal special characters scoped and explains descendant discovery', async () => {
1618+
listTableFolders.mockResolvedValue({
1619+
folders: [
1620+
{ id: 'selected', name: 'Reports[*]?', parentId: null },
1621+
{ id: 'sibling', name: 'Reports-other', parentId: null },
1622+
],
1623+
})
1624+
const [context] = await processContextsServer(
1625+
[{ kind: 'folder', folderId: 'selected', label: 'Reports' }],
1626+
'user-1',
1627+
'inspect',
1628+
'ws-1'
1629+
)
1630+
const content = JSON.parse(context.content)
1631+
expect(content.folderPath).toBe('/Reports%5B*%5D%3F')
1632+
expect(content.discovery).toContain('literal path, not a glob')
1633+
expect(content.discovery).toContain('follow pagination and descendant folders')
1634+
expect(content.discovery).toContain('An empty folder has no contents')
1635+
expect(context.content).not.toContain('Reports-other')
1636+
})
1637+
16091638
it('shares folder reads within a turn and re-resolves moves on the next turn', async () => {
16101639
listTableFolders.mockResolvedValue({ folders: nestedFolders })
16111640
const contexts: ChatContext[] = [
@@ -1618,7 +1647,10 @@ describe('folder and foldered-resource chat pointers', () => {
16181647
folders: [{ id: 'child', name: 'Moved', parentId: null }],
16191648
})
16201649
const [result] = await processContextsServer([contexts[1]], 'user-1', '', 'ws-1')
1621-
expect(JSON.parse(result.content)).toEqual({ resourceType: 'table', folderPath: '/Moved' })
1650+
expect(JSON.parse(result.content)).toMatchObject({
1651+
resourceType: 'table',
1652+
folderPath: '/Moved',
1653+
})
16221654
expect(listTableFolders).toHaveBeenCalledTimes(2)
16231655
})
16241656

@@ -1631,7 +1663,7 @@ describe('folder and foldered-resource chat pointers', () => {
16311663
'',
16321664
'ws-1'
16331665
)
1634-
expect(JSON.parse(result.content)).toEqual({
1666+
expect(JSON.parse(result.content)).toMatchObject({
16351667
resourceType: 'table',
16361668
folderPath: '/Finance%2FLegal/Q4%20100%25',
16371669
})

‎apps/sim/lib/mothership/chat/process-contents.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -845,7 +845,12 @@ function folderReferenceContent(pointer: string): string {
845845
}
846846
const resourceType = resourceTypes[root]
847847
if (!resourceType) throw new Error('Unsupported folder resource domain')
848-
return JSON.stringify({ resourceType, folderPath: pointer.slice(separator) })
848+
return JSON.stringify({
849+
resourceType,
850+
folderPath: pointer.slice(separator),
851+
discovery:
852+
"This attachment refers to the contents of this exact folder, including descendants. Use the domain's CLI folder/resource listing with this literal path, not a glob; follow pagination and descendant folders, then read relevant resources. An empty folder has no contents; do not substitute a similarly named folder.",
853+
})
849854
}
850855

851856
async function resolveTableResource(

0 commit comments

Comments
 (0)