fix(host): harden skill.invoke at the enforcement boundary
Review fixes: recheck isUserInvocable on the loaded definition (list and get collect independently, so a provider change between them could swap in a user-disabled body — the skill-tool execute template's second check); thread the carrier signal through the lookup and refuse an abandoned caller's turn as cancelled; fold lookup/loader failures into the structured internal error the list face already uses; refuse cwd-less sessions with the skill.list stance; and reject blank trailing text at the wire schema instead of relying on client trimming.
This commit is contained in:
@@ -27,11 +27,14 @@ export const skillListValueSchema = z.object({
|
||||
skills: z.array(skillEntrySchema),
|
||||
}) satisfies z.ZodType<Wire<ResponseValue<'skill.list'>>>
|
||||
|
||||
/** skill.invoke request payload. */
|
||||
/**
|
||||
* skill.invoke request payload. `text` is the user's trailing message; a
|
||||
* blank one stays off the wire (the boundary, not client courtesy, refuses it).
|
||||
*/
|
||||
export const skillInvokeRequestSchema = z.object({
|
||||
sessionId: sessionIdSchema,
|
||||
name: z.string().min(1),
|
||||
text: z.string().optional(),
|
||||
text: z.string().min(1).optional(),
|
||||
}) satisfies z.ZodType<Wire<RequestPayload<'skill.invoke'>>>
|
||||
|
||||
/** skill.invoke response value. */
|
||||
|
||||
@@ -29,9 +29,13 @@ export interface SkillsApi {
|
||||
* Injects one user-invocable skill into the addressed agent as a user-role
|
||||
* message (the canonical `<skill_content>` rendering, with `text` appended
|
||||
* when present) and starts a turn. The host enforces user-invocation policy
|
||||
* here: a model-only or unknown name is refused regardless of what a client
|
||||
* menu offered. Session-backed subagents reject with `agent-busy`.
|
||||
* here — on the discovery summary and again on the loaded definition, so a
|
||||
* catalog change between the two lookups cannot slip a user-disabled body
|
||||
* through — a model-only or unknown name is refused regardless of what a
|
||||
* client menu offered. The carrier's request signal aborts the skill
|
||||
* lookup and refuses injection once the caller has given up (`cancelled`).
|
||||
* Session-backed subagents reject with `agent-busy`.
|
||||
*/
|
||||
invoke(request: RpcRequest<{ sessionId: SessionId; name: string; text?: string }>):
|
||||
invoke(request: RpcRequest<{ sessionId: SessionId; name: string; text?: string }>, signal: AbortSignal):
|
||||
Promise<RpcResponse<{ accepted: true }>>
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user