@pikku/core 0.12.94 → 0.12.96
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/CHANGELOG.md +179 -0
- package/dist/dev/hot-reload.js +24 -4
- package/dist/dev/module-runner.d.ts +20 -3
- package/dist/dev/module-runner.js +17 -4
- package/dist/services/email-template.d.ts +43 -0
- package/dist/services/email-template.js +139 -0
- package/dist/services/http-personas.d.ts +6 -1
- package/dist/services/http-personas.js +4 -1
- package/dist/services/index.d.ts +1 -0
- package/dist/services/index.js +1 -0
- package/dist/wirings/agent/agent-prepare.d.ts +14 -0
- package/dist/wirings/agent/agent-prepare.js +24 -0
- package/dist/wirings/agent/index.d.ts +1 -1
- package/dist/wirings/agent/index.js +1 -1
- package/dist/wirings/scheduler/scheduler-runner.js +0 -1
- package/dist/wirings/virtual-user/index.d.ts +1 -0
- package/dist/wirings/virtual-user/index.js +1 -0
- package/dist/wirings/virtual-user/virtual-user-derive.js +9 -0
- package/dist/wirings/virtual-user/virtual-user-scaffold.d.ts +267 -0
- package/dist/wirings/virtual-user/virtual-user-scaffold.js +400 -0
- package/dist/wirings/workflow/index.d.ts +1 -0
- package/dist/wirings/workflow/index.js +1 -0
- package/dist/wirings/workflow/pikku-workflow-service.js +3 -9
- package/dist/wirings/workflow/scenario-prose.d.ts +23 -1
- package/dist/wirings/workflow/scenario-prose.js +12 -3
- package/dist/wirings/workflow/scenario-run.types.d.ts +7 -0
- package/dist/wirings/workflow/workflow-queue-routing.d.ts +18 -0
- package/dist/wirings/workflow/workflow-queue-routing.js +35 -0
- package/dist/wirings/workflow/workflow-status-stream.d.ts +28 -0
- package/dist/wirings/workflow/workflow-status-stream.js +105 -0
- package/package.json +1 -1
- package/src/dev/hot-reload.test.ts +42 -0
- package/src/dev/hot-reload.ts +30 -4
- package/src/dev/module-runner.test.ts +56 -13
- package/src/dev/module-runner.ts +32 -10
- package/src/public-surface.json +17 -1
- package/src/services/email-template.test.ts +311 -0
- package/src/services/email-template.ts +254 -0
- package/src/services/http-personas.ts +10 -2
- package/src/services/index.ts +8 -0
- package/src/services/persona-sign-in.test.ts +22 -0
- package/src/wirings/agent/agent-helpers.test.ts +63 -0
- package/src/wirings/agent/agent-prepare.ts +25 -0
- package/src/wirings/agent/index.ts +1 -0
- package/src/wirings/scheduler/scheduler-runner.test.ts +178 -0
- package/src/wirings/scheduler/scheduler-runner.ts +0 -1
- package/src/wirings/virtual-user/index.ts +20 -0
- package/src/wirings/virtual-user/virtual-user-derive.test.ts +33 -5
- package/src/wirings/virtual-user/virtual-user-derive.ts +9 -0
- package/src/wirings/virtual-user/virtual-user-scaffold.test.ts +795 -0
- package/src/wirings/virtual-user/virtual-user-scaffold.ts +634 -0
- package/src/wirings/workflow/index.ts +4 -0
- package/src/wirings/workflow/pikku-workflow-service.test.ts +71 -2
- package/src/wirings/workflow/pikku-workflow-service.ts +5 -11
- package/src/wirings/workflow/scenario-prose.test.ts +134 -9
- package/src/wirings/workflow/scenario-prose.ts +37 -2
- package/src/wirings/workflow/scenario-run.types.ts +7 -0
- package/src/wirings/workflow/workflow-child-run-session.test.ts +79 -0
- package/src/wirings/workflow/workflow-queue-routing.ts +44 -0
- package/src/wirings/workflow/workflow-status-stream.test.ts +354 -0
- package/src/wirings/workflow/workflow-status-stream.ts +144 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
agentResume,
|
|
7
7
|
agentApprove,
|
|
8
8
|
} from './agent-helpers.js'
|
|
9
|
+
import { agentCallOptions } from './agent-prepare.js'
|
|
9
10
|
|
|
10
11
|
describe('agent helpers', () => {
|
|
11
12
|
describe('agent', () => {
|
|
@@ -150,3 +151,65 @@ describe('agent helpers', () => {
|
|
|
150
151
|
})
|
|
151
152
|
})
|
|
152
153
|
})
|
|
154
|
+
|
|
155
|
+
describe('agentCallOptions', () => {
|
|
156
|
+
// An explicit `undefined` overrides the agent's own declared default with
|
|
157
|
+
// nothing, so a request that names no model would silently unset the one the
|
|
158
|
+
// agent declares.
|
|
159
|
+
test('a field nobody supplied is left out rather than sent as undefined', () => {
|
|
160
|
+
const options = agentCallOptions({
|
|
161
|
+
message: 'hello',
|
|
162
|
+
threadId: 't1',
|
|
163
|
+
resourceId: 'r1',
|
|
164
|
+
})
|
|
165
|
+
assert.equal('model' in options, false)
|
|
166
|
+
assert.equal('temperature' in options, false)
|
|
167
|
+
assert.equal('context' in options, false)
|
|
168
|
+
assert.equal('attachments' in options, false)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
test('what was supplied is carried through unchanged', () => {
|
|
172
|
+
const options = agentCallOptions({
|
|
173
|
+
message: 'hello',
|
|
174
|
+
threadId: 't1',
|
|
175
|
+
resourceId: 'user-1',
|
|
176
|
+
model: 'a-model',
|
|
177
|
+
temperature: 0.2,
|
|
178
|
+
context: 'some context',
|
|
179
|
+
attachments: [{ type: 'image' as const, url: 'a.png' }],
|
|
180
|
+
})
|
|
181
|
+
assert.deepEqual(options, {
|
|
182
|
+
message: 'hello',
|
|
183
|
+
threadId: 't1',
|
|
184
|
+
resourceId: 'user-1',
|
|
185
|
+
attachments: [{ type: 'image' as const, url: 'a.png' }],
|
|
186
|
+
model: 'a-model',
|
|
187
|
+
temperature: 0.2,
|
|
188
|
+
context: 'some context',
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
|
|
192
|
+
// Zero is a temperature a caller can mean, and the falsy check every other
|
|
193
|
+
// field uses would drop it.
|
|
194
|
+
test('a temperature of zero survives, unlike an empty string', () => {
|
|
195
|
+
assert.equal(
|
|
196
|
+
agentCallOptions({
|
|
197
|
+
message: 'x',
|
|
198
|
+
threadId: 't',
|
|
199
|
+
resourceId: 'r',
|
|
200
|
+
temperature: 0,
|
|
201
|
+
}).temperature,
|
|
202
|
+
0
|
|
203
|
+
)
|
|
204
|
+
assert.equal(
|
|
205
|
+
'context' in
|
|
206
|
+
agentCallOptions({
|
|
207
|
+
message: 'x',
|
|
208
|
+
threadId: 't',
|
|
209
|
+
resourceId: 'r',
|
|
210
|
+
context: '',
|
|
211
|
+
}),
|
|
212
|
+
false
|
|
213
|
+
)
|
|
214
|
+
})
|
|
215
|
+
})
|
|
@@ -149,6 +149,31 @@ export function canAccessThread(
|
|
|
149
149
|
)
|
|
150
150
|
}
|
|
151
151
|
|
|
152
|
+
/**
|
|
153
|
+
* An agent call with the fields nobody supplied left out.
|
|
154
|
+
*
|
|
155
|
+
* Omitted rather than passed as `undefined`, because an explicit `undefined`
|
|
156
|
+
* overrides the agent's own declared default with nothing — a request that
|
|
157
|
+
* names no model would silently unset the one the agent declares.
|
|
158
|
+
*
|
|
159
|
+
* Shared by the scaffolded `run` and `stream` routes, which receive the same
|
|
160
|
+
* input and differ only in what they do with the reply. `agentName` is not part
|
|
161
|
+
* of it: both callers pass that separately, because `rpc.agent.run` and
|
|
162
|
+
* `rpc.agent.stream` take it as their first argument and type the rest
|
|
163
|
+
* against it.
|
|
164
|
+
*/
|
|
165
|
+
export const agentCallOptions = (input: AgentInput): AgentInput => ({
|
|
166
|
+
message: input.message,
|
|
167
|
+
threadId: input.threadId,
|
|
168
|
+
resourceId: input.resourceId,
|
|
169
|
+
...(input.attachments ? { attachments: input.attachments } : {}),
|
|
170
|
+
...(input.model ? { model: input.model } : {}),
|
|
171
|
+
...(input.temperature !== undefined
|
|
172
|
+
? { temperature: input.temperature }
|
|
173
|
+
: {}),
|
|
174
|
+
...(input.context ? { context: input.context } : {}),
|
|
175
|
+
})
|
|
176
|
+
|
|
152
177
|
export type StreamAgentOptions = {
|
|
153
178
|
requiresToolApproval?: 'all' | 'explicit' | false
|
|
154
179
|
onRunCreated?: (runId: string) => void
|
|
@@ -213,6 +213,97 @@ describe('runScheduledTask', () => {
|
|
|
213
213
|
assert.deepEqual(receivedSession, session)
|
|
214
214
|
})
|
|
215
215
|
|
|
216
|
+
test('a task middleware can set the session the task runs as', async () => {
|
|
217
|
+
let frozenSession: CoreUserSession | undefined
|
|
218
|
+
const machineSession: CoreUserSession = {
|
|
219
|
+
userId: 'cron:machine-session-task',
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const mockTask: CoreScheduledTask = {
|
|
223
|
+
name: 'machine-session-task',
|
|
224
|
+
schedule: '0 0 * * *',
|
|
225
|
+
middleware: [
|
|
226
|
+
async (_services: any, wire: any, next: any) => {
|
|
227
|
+
wire.setSession(machineSession)
|
|
228
|
+
return next()
|
|
229
|
+
},
|
|
230
|
+
] as any,
|
|
231
|
+
func: {
|
|
232
|
+
func: async (_services: any, _data: any, wire: any) => {
|
|
233
|
+
frozenSession = wire.session
|
|
234
|
+
},
|
|
235
|
+
auth: false,
|
|
236
|
+
},
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
pikkuState(null, 'scheduler', 'meta')['machine-session-task'] = {
|
|
240
|
+
pikkuFuncId: 'scheduler_machine-session-task',
|
|
241
|
+
name: 'machine-session-task',
|
|
242
|
+
schedule: '0 0 * * *',
|
|
243
|
+
}
|
|
244
|
+
pikkuState(null, 'function', 'meta')['scheduler_machine-session-task'] = {
|
|
245
|
+
pikkuFuncId: 'scheduler_machine-session-task',
|
|
246
|
+
inputSchemaName: null,
|
|
247
|
+
outputSchemaName: null,
|
|
248
|
+
sessionless: true,
|
|
249
|
+
}
|
|
250
|
+
wireScheduler(mockTask)
|
|
251
|
+
|
|
252
|
+
pikkuState(null, 'package', 'singletonServices', {
|
|
253
|
+
logger: createMockLogger(),
|
|
254
|
+
} as any)
|
|
255
|
+
|
|
256
|
+
await runScheduledTask({ name: 'machine-session-task' })
|
|
257
|
+
|
|
258
|
+
assert.deepEqual(frozenSession, machineSession)
|
|
259
|
+
})
|
|
260
|
+
|
|
261
|
+
test('a session-taking task does not warn that auth was disabled', async () => {
|
|
262
|
+
const mockTask: CoreScheduledTask = {
|
|
263
|
+
name: 'identified-task',
|
|
264
|
+
schedule: '0 0 * * *',
|
|
265
|
+
middleware: [
|
|
266
|
+
async (_services: any, wire: any, next: any) => {
|
|
267
|
+
wire.setSession({ userId: 'cron:identified-task' })
|
|
268
|
+
return next()
|
|
269
|
+
},
|
|
270
|
+
] as any,
|
|
271
|
+
func: {
|
|
272
|
+
func: async () => {},
|
|
273
|
+
},
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
pikkuState(null, 'scheduler', 'meta')['identified-task'] = {
|
|
277
|
+
pikkuFuncId: 'scheduler_identified-task',
|
|
278
|
+
name: 'identified-task',
|
|
279
|
+
schedule: '0 0 * * *',
|
|
280
|
+
}
|
|
281
|
+
pikkuState(null, 'function', 'meta')['scheduler_identified-task'] = {
|
|
282
|
+
pikkuFuncId: 'scheduler_identified-task',
|
|
283
|
+
inputSchemaName: null,
|
|
284
|
+
outputSchemaName: null,
|
|
285
|
+
sessionless: false,
|
|
286
|
+
}
|
|
287
|
+
wireScheduler(mockTask)
|
|
288
|
+
|
|
289
|
+
const mockLogger = createMockLogger()
|
|
290
|
+
pikkuState(null, 'package', 'singletonServices', {
|
|
291
|
+
logger: mockLogger,
|
|
292
|
+
} as any)
|
|
293
|
+
|
|
294
|
+
await runScheduledTask({ name: 'identified-task' })
|
|
295
|
+
|
|
296
|
+
assert.deepEqual(
|
|
297
|
+
mockLogger
|
|
298
|
+
.getLogs()
|
|
299
|
+
.filter(
|
|
300
|
+
(log) =>
|
|
301
|
+
log.level === 'warn' && /auth was explicitly disabled/.test(log.message)
|
|
302
|
+
),
|
|
303
|
+
[]
|
|
304
|
+
)
|
|
305
|
+
})
|
|
306
|
+
|
|
216
307
|
test('should throw ScheduledTaskNotFoundError when task not found', async () => {
|
|
217
308
|
const mockLogger = createMockLogger()
|
|
218
309
|
pikkuState(null, 'package', 'singletonServices', {
|
|
@@ -655,3 +746,90 @@ describe('getScheduledTasks', () => {
|
|
|
655
746
|
assert.equal(tasks.size, 0)
|
|
656
747
|
})
|
|
657
748
|
})
|
|
749
|
+
|
|
750
|
+
/**
|
|
751
|
+
* A cron has no caller to authenticate, so the only thing that can give it an
|
|
752
|
+
* identity is its own wiring. These cover the mechanism the virtual-user
|
|
753
|
+
* scaffold's `virtualUserPlatformSession` relies on: middleware on the task
|
|
754
|
+
* sets the session, and the scope gate on the function it drives is enforced
|
|
755
|
+
* against exactly that session — a tick with no identity, or one holding the
|
|
756
|
+
* wrong scope, is refused the same way a person would be.
|
|
757
|
+
*/
|
|
758
|
+
describe('a scheduled task authorizes on the session its own middleware sets', () => {
|
|
759
|
+
const wireGatedTask = (
|
|
760
|
+
name: string,
|
|
761
|
+
middleware?: Array<
|
|
762
|
+
(services: any, wire: any, next: any) => Promise<void> | void
|
|
763
|
+
>
|
|
764
|
+
) => {
|
|
765
|
+
let seen: CoreUserSession | undefined
|
|
766
|
+
const task: CoreScheduledTask = {
|
|
767
|
+
name,
|
|
768
|
+
schedule: '0 * * * *',
|
|
769
|
+
func: {
|
|
770
|
+
func: async (_services: any, _data: any, wire: any) => {
|
|
771
|
+
seen = await wire.getSession()
|
|
772
|
+
},
|
|
773
|
+
} as any,
|
|
774
|
+
middleware,
|
|
775
|
+
}
|
|
776
|
+
pikkuState(null, 'scheduler', 'meta')[name] = {
|
|
777
|
+
pikkuFuncId: `scheduler_${name}`,
|
|
778
|
+
name,
|
|
779
|
+
schedule: '0 * * * *',
|
|
780
|
+
}
|
|
781
|
+
pikkuState(null, 'function', 'meta')[`scheduler_${name}`] = {
|
|
782
|
+
pikkuFuncId: `scheduler_${name}`,
|
|
783
|
+
inputSchemaName: null,
|
|
784
|
+
outputSchemaName: null,
|
|
785
|
+
sessionless: false,
|
|
786
|
+
scopes: ['virtualUser:run'],
|
|
787
|
+
}
|
|
788
|
+
wireScheduler(task)
|
|
789
|
+
pikkuState(null, 'package', 'singletonServices', {
|
|
790
|
+
logger: createMockLogger(),
|
|
791
|
+
} as any)
|
|
792
|
+
return () => seen
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
const settingSession = (session: CoreUserSession) => [
|
|
796
|
+
async (_services: any, wire: any, next: any) => {
|
|
797
|
+
await wire.setSession(session)
|
|
798
|
+
return next()
|
|
799
|
+
},
|
|
800
|
+
]
|
|
801
|
+
|
|
802
|
+
test('is refused outright when nothing gives the tick an identity', async () => {
|
|
803
|
+
wireGatedTask('unidentified-tick')
|
|
804
|
+
await assert.rejects(
|
|
805
|
+
runScheduledTask({ name: 'unidentified-tick' }),
|
|
806
|
+
/Authentication required/
|
|
807
|
+
)
|
|
808
|
+
})
|
|
809
|
+
|
|
810
|
+
test('is refused when the identity it sets holds the wrong scope', async () => {
|
|
811
|
+
wireGatedTask(
|
|
812
|
+
'misscoped-tick',
|
|
813
|
+
settingSession({
|
|
814
|
+
userId: 'pikku-platform',
|
|
815
|
+
scopes: ['admin'],
|
|
816
|
+
} as CoreUserSession)
|
|
817
|
+
)
|
|
818
|
+
await assert.rejects(
|
|
819
|
+
runScheduledTask({ name: 'misscoped-tick' }),
|
|
820
|
+
/virtualUser:run/
|
|
821
|
+
)
|
|
822
|
+
})
|
|
823
|
+
|
|
824
|
+
test('runs as the platform user its middleware set', async () => {
|
|
825
|
+
const seen = wireGatedTask(
|
|
826
|
+
'platform-tick',
|
|
827
|
+
settingSession({
|
|
828
|
+
userId: 'pikku-platform',
|
|
829
|
+
scopes: ['virtualUser:run'],
|
|
830
|
+
} as CoreUserSession)
|
|
831
|
+
)
|
|
832
|
+
await runScheduledTask({ name: 'platform-tick' })
|
|
833
|
+
assert.equal(seen()?.userId, 'pikku-platform')
|
|
834
|
+
})
|
|
835
|
+
})
|
|
@@ -116,7 +116,6 @@ export async function runScheduledTask({
|
|
|
116
116
|
await runPikkuFunc('scheduler', meta.name, meta.pikkuFuncId, {
|
|
117
117
|
singletonServices,
|
|
118
118
|
createWireServices,
|
|
119
|
-
auth: false,
|
|
120
119
|
data: () => undefined,
|
|
121
120
|
inheritedMiddleware: meta.middleware,
|
|
122
121
|
wireMiddleware: task.middleware,
|
|
@@ -85,3 +85,23 @@ export {
|
|
|
85
85
|
personaVirtualUserTarget,
|
|
86
86
|
type PersonaTargetOptions,
|
|
87
87
|
} from './virtual-user-target.js'
|
|
88
|
+
export {
|
|
89
|
+
executeVirtualUserRun,
|
|
90
|
+
logVirtualUserTick,
|
|
91
|
+
requireVirtualUserRunStore,
|
|
92
|
+
requireVirtualUserScheduleStore,
|
|
93
|
+
runnablePersona,
|
|
94
|
+
serializeVirtualUserRun,
|
|
95
|
+
serializeVirtualUserSchedule,
|
|
96
|
+
serializeVirtualUserSteps,
|
|
97
|
+
signInPathFor,
|
|
98
|
+
startVirtualUserRun,
|
|
99
|
+
VIRTUAL_USER_VARIABLES,
|
|
100
|
+
virtualUserScheduleRunInput,
|
|
101
|
+
writeVirtualUserSchedule,
|
|
102
|
+
type ExecuteVirtualUserRunParams,
|
|
103
|
+
type ScaffoldPersonas,
|
|
104
|
+
type StartedVirtualUserRun,
|
|
105
|
+
type StartVirtualUserRunParams,
|
|
106
|
+
type WriteVirtualUserScheduleParams,
|
|
107
|
+
} from './virtual-user-scaffold.js'
|
|
@@ -222,10 +222,10 @@ describe('deriving intents from scenarios', () => {
|
|
|
222
222
|
test('the prose comes through with its placeholders left open', () => {
|
|
223
223
|
const [intent] = deriveIntents(workflows, functions)
|
|
224
224
|
assert.deepEqual(intent!.steps, [
|
|
225
|
-
'Given
|
|
225
|
+
'Given orgAdmin is signed in',
|
|
226
226
|
// The scenario knows which address it invites. The user has to pick one.
|
|
227
|
-
'When
|
|
228
|
-
'Then
|
|
227
|
+
'When orgAdmin invites {email}',
|
|
228
|
+
'Then orgAdmin sees the new member in the list',
|
|
229
229
|
])
|
|
230
230
|
})
|
|
231
231
|
|
|
@@ -298,8 +298,8 @@ describe('deriving intents from scenarios', () => {
|
|
|
298
298
|
)
|
|
299
299
|
|
|
300
300
|
assert.deepEqual(intents[0]!.steps, [
|
|
301
|
-
'Given
|
|
302
|
-
'When
|
|
301
|
+
'Given orgAdmin is signed in',
|
|
302
|
+
'When orgAdmin invites {email}',
|
|
303
303
|
])
|
|
304
304
|
assert.deepEqual(intents[0]!.personas, ['orgAdmin'])
|
|
305
305
|
})
|
|
@@ -396,3 +396,31 @@ describe('driving a virtual user through a signed-in actor', () => {
|
|
|
396
396
|
assert.match(verdict.reasoning, /no assistant called 'concierge'/)
|
|
397
397
|
})
|
|
398
398
|
})
|
|
399
|
+
|
|
400
|
+
// An adversarial run's transcript is working exploits against this same app,
|
|
401
|
+
// and a schedule outlives the run that wrote it. Neither belongs in the hands
|
|
402
|
+
// of the thing being run.
|
|
403
|
+
test('a virtual user is never offered the machinery that runs virtual users', () => {
|
|
404
|
+
const catalogue = deriveCatalogue({
|
|
405
|
+
runVirtualUser: {
|
|
406
|
+
name: 'runVirtualUser',
|
|
407
|
+
expose: true,
|
|
408
|
+
scopes: ['virtualUser:run'],
|
|
409
|
+
},
|
|
410
|
+
getVirtualUserRunSteps: {
|
|
411
|
+
name: 'getVirtualUserRunSteps',
|
|
412
|
+
expose: true,
|
|
413
|
+
scopes: ['virtualUser:read'],
|
|
414
|
+
},
|
|
415
|
+
listBookings: {
|
|
416
|
+
name: 'listBookings',
|
|
417
|
+
expose: true,
|
|
418
|
+
scopes: ['bookings:read'],
|
|
419
|
+
},
|
|
420
|
+
} as any)
|
|
421
|
+
|
|
422
|
+
assert.deepEqual(
|
|
423
|
+
catalogue.map((entry) => entry.name),
|
|
424
|
+
['listBookings']
|
|
425
|
+
)
|
|
426
|
+
})
|
|
@@ -35,6 +35,15 @@ export const deriveCatalogue = (
|
|
|
35
35
|
if (meta.scenario || meta.scenarioStep || meta.scenarioStepKind) continue
|
|
36
36
|
// knowledge: decisions/internals/only-exposed-functions-enter-a-virtual-user-catalogue.md
|
|
37
37
|
if (meta.expose !== true) continue
|
|
38
|
+
// A virtual user is not offered the machinery that runs virtual users. A
|
|
39
|
+
// persona whose role carries `virtualUser:*` would otherwise be able to
|
|
40
|
+
// start further runs, read every run's findings — an adversarial run's
|
|
41
|
+
// transcript is working exploits against this same app — and put a persona
|
|
42
|
+
// on a schedule that outlives it. Same reasoning as the scenario-step rule
|
|
43
|
+
// above: the tool is about the run, not about the product being explored.
|
|
44
|
+
if (meta.scopes?.some((scope) => scope.split(':')[0] === 'virtualUser')) {
|
|
45
|
+
continue
|
|
46
|
+
}
|
|
38
47
|
|
|
39
48
|
const inputSchema = meta.inputSchemaName
|
|
40
49
|
? schemas[meta.inputSchemaName]
|