PR #32631 followup: ToolDefinition = z.infer<>, kill double factory#32683
Merged
Conversation
Address all 4 review comments from PR #32631 (post-merge): 1. types.ts — ToolDefinitionSchema now uses z.custom<object>(predicate) for input_schema and z.custom<(input, ctx) => Promise<...>>() for execute. ToolDefinition collapses to a direct z.infer<typeof ToolDefinitionSchema> — single declaration, no Omit overlay, both fields typed correctly. z.custom on execute is a runtime no-op (the wire path drops closures anyway, finalizeTool synthesizes a no-op error closure on arrival). 2. tool-defaults.ts — finalizeTool's defaultName arg is now optional with an empty-string default. The empty-string fallback is fail-loud because registerSkillTools rejects an empty name with a clear error. 3. registries.ts — call site is now a one-liner: tools.map((t) => finalizeTool(t)). The defensive '?? ""' moved inside finalizeTool itself, so it doesn't repeat at every IPC handler. Comment trimmed to match. 4. ask-question-tool.ts — killed the createAskQuestionTool factory and the prompterFactory parameter. askQuestionTool is now a plain literal matching every other tool: '} satisfies ToolDefinition'. Test rewrite uses mock.module() on ../../permissions/question- prompter.js + a dynamic import of the tool, replacing makeToolWithStub's prompter-factory seam. Tests: 55/55 across ask-question, registries, plugin-api-tool- definition, plugin-tool-contribution. Lint clean.
dvargasfuertes
approved these changes
May 30, 2026
| } | ||
| return `${prefix} Skipped`; | ||
| }); | ||
| const prompter = new QuestionPrompter({ broadcastMessage }); |
Contributor
There was a problem hiding this comment.
QuestionPrompter can remove its broadcastMessage constructor args and just import it directly
vellum-apollo-bot Bot
pushed a commit
that referenced
this pull request
May 30, 2026
Workspace tools are user-authored files dropped under
`<workspaceDir>/tools/<name>.{ts,js,json}` that authoritatively replace
same-named core tools at registry-load time. The original core tool is
stashed and restored on workspace-tool unregister, so workspace overrides
are non-destructive to the core baseline.
Highlights:
- New `registerWorkspaceTools` / `unregisterWorkspaceTool` /
`removeCoreToolViaWorkspace` / `restoreStrippedCoreTool` registry
entry points. Ownership is tracked in the existing `ownersByName` map
(`kind: "workspace"`, `id: <workspacePath>`) — no extra fields land
on the `Tool` object, keeping the post-#32631 `Required<ToolDefinition>`
shape pristine.
- Workspace ownership wins over plugin / skill / MCP: registrations of
the same name from those surfaces warn-skip (or throw, for skills) so
the workspace override stays canonical for its lifetime.
- `<name>.removed` sentinel strips a core tool from the live registry
while keeping it in the stash. Deleting the sentinel restores it.
Workspace-owned names can't be sentinel-stripped (the user must
remove the workspace tool file first).
- File watcher (`workspace-tools-watcher.ts`) drives hot reload:
re-imports on change, unregister on delete, re-strip / re-restore on
sentinel toggle. Uses `getToolOwner` to detect workspace ownership
(no `origin` field needed).
- Loader (`workspace-tools/loader.ts`) delegates default-filling to the
shared `finalizeTool` from `tool-defaults.ts`, layering only two
workspace-specific overrides on top: `defaultRiskLevel: "high"` (user
code from disk runs with full privilege) and a workspace-flavored
default `execute` error message.
Followup items from PR #31949 round-1 review (now incorporated):
- Generalized the `workspace-tools.md` doc example to use
`ToolDefinition` instead of `PluginTool` (comment 3295343340).
- Default-risk-level table entry shows `"high"` (comment 3295345328).
Followup from PR #32683 review (also incorporated):
- `QuestionPrompter` dropped its `{ broadcastMessage }` constructor arg
and imports `broadcastMessage` directly from `runtime/assistant-event-hub.js`.
Updated `ask-question-tool.ts` caller + `question-prompter.test.ts`
setup (the test now mocks the module export rather than injecting via
constructor; `mock.module` calls in that file also spread the real
module exports so other tests in the same `bun test` run still see
e.g. `assistantEventHub` and `setNestedValue`).
Tests: 88/88 passing across the 4 critical files (registry.test.ts,
workspace-tool-loader.test.ts, question-prompter.test.ts,
ask-question-tool.test.ts).
vellum-apollo-bot Bot
added a commit
that referenced
this pull request
May 30, 2026
Workspace tools are user-authored files dropped under
`<workspaceDir>/tools/<name>.{ts,js,json}` that authoritatively replace
same-named core tools at registry-load time. The original core tool is
stashed and restored on workspace-tool unregister, so workspace overrides
are non-destructive to the core baseline.
Highlights:
- New `registerWorkspaceTools` / `unregisterWorkspaceTool` /
`removeCoreToolViaWorkspace` / `restoreStrippedCoreTool` registry
entry points. Ownership is tracked in the existing `ownersByName` map
(`kind: "workspace"`, `id: <workspacePath>`) — no extra fields land
on the `Tool` object, keeping the post-#32631 `Required<ToolDefinition>`
shape pristine.
- Workspace ownership wins over plugin / skill / MCP: registrations of
the same name from those surfaces warn-skip (or throw, for skills) so
the workspace override stays canonical for its lifetime.
- `<name>.removed` sentinel strips a core tool from the live registry
while keeping it in the stash. Deleting the sentinel restores it.
Workspace-owned names can't be sentinel-stripped (the user must
remove the workspace tool file first).
- File watcher (`workspace-tools-watcher.ts`) drives hot reload:
re-imports on change, unregister on delete, re-strip / re-restore on
sentinel toggle. Uses `getToolOwner` to detect workspace ownership
(no `origin` field needed).
- Loader (`workspace-tools/loader.ts`) delegates default-filling to the
shared `finalizeTool` from `tool-defaults.ts`, layering only two
workspace-specific overrides on top: `defaultRiskLevel: "high"` (user
code from disk runs with full privilege) and a workspace-flavored
default `execute` error message.
Followup items from PR #31949 round-1 review (now incorporated):
- Generalized the `workspace-tools.md` doc example to use
`ToolDefinition` instead of `PluginTool` (comment 3295343340).
- Default-risk-level table entry shows `"high"` (comment 3295345328).
Followup from PR #32683 review (also incorporated):
- `QuestionPrompter` dropped its `{ broadcastMessage }` constructor arg
and imports `broadcastMessage` directly from `runtime/assistant-event-hub.js`.
Updated `ask-question-tool.ts` caller + `question-prompter.test.ts`
setup (the test now mocks the module export rather than injecting via
constructor; `mock.module` calls in that file also spread the real
module exports so other tests in the same `bun test` run still see
e.g. `assistantEventHub` and `setNestedValue`).
Tests: 88/88 passing across the 4 critical files (registry.test.ts,
workspace-tool-loader.test.ts, question-prompter.test.ts,
ask-question-tool.test.ts).
devin-ai-integration Bot
pushed a commit
that referenced
this pull request
May 31, 2026
Workspace tools are user-authored files dropped under
`<workspaceDir>/tools/<name>.{ts,js,json}` that authoritatively replace
same-named core tools at registry-load time. The original core tool is
stashed and restored on workspace-tool unregister, so workspace overrides
are non-destructive to the core baseline.
Highlights:
- New `registerWorkspaceTools` / `unregisterWorkspaceTool` /
`removeCoreToolViaWorkspace` / `restoreStrippedCoreTool` registry
entry points. Ownership is tracked in the existing `ownersByName` map
(`kind: "workspace"`, `id: <workspacePath>`) — no extra fields land
on the `Tool` object, keeping the post-#32631 `Required<ToolDefinition>`
shape pristine.
- Workspace ownership wins over plugin / skill / MCP: registrations of
the same name from those surfaces warn-skip (or throw, for skills) so
the workspace override stays canonical for its lifetime.
- `<name>.removed` sentinel strips a core tool from the live registry
while keeping it in the stash. Deleting the sentinel restores it.
Workspace-owned names can't be sentinel-stripped (the user must
remove the workspace tool file first).
- File watcher (`workspace-tools-watcher.ts`) drives hot reload:
re-imports on change, unregister on delete, re-strip / re-restore on
sentinel toggle. Uses `getToolOwner` to detect workspace ownership
(no `origin` field needed).
- Loader (`workspace-tools/loader.ts`) delegates default-filling to the
shared `finalizeTool` from `tool-defaults.ts`, layering only two
workspace-specific overrides on top: `defaultRiskLevel: "high"` (user
code from disk runs with full privilege) and a workspace-flavored
default `execute` error message.
Followup items from PR #31949 round-1 review (now incorporated):
- Generalized the `workspace-tools.md` doc example to use
`ToolDefinition` instead of `PluginTool` (comment 3295343340).
- Default-risk-level table entry shows `"high"` (comment 3295345328).
Followup from PR #32683 review (also incorporated):
- `QuestionPrompter` dropped its `{ broadcastMessage }` constructor arg
and imports `broadcastMessage` directly from `runtime/assistant-event-hub.js`.
Updated `ask-question-tool.ts` caller + `question-prompter.test.ts`
setup (the test now mocks the module export rather than injecting via
constructor; `mock.module` calls in that file also spread the real
module exports so other tests in the same `bun test` run still see
e.g. `assistantEventHub` and `setNestedValue`).
Tests: 88/88 passing across the 4 critical files (registry.test.ts,
workspace-tool-loader.test.ts, question-prompter.test.ts,
ask-question-tool.test.ts).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #32631 — addresses all 4 review comments left on the APPROVED review.
Changes
1.
types.ts—ToolDefinition = z.infer<typeof ToolDefinitionSchema>(review thread)ToolDefinitionSchemanow usesz.custom<...>()for bothinput_schemaandexecute:Then:
Single declaration. No
Omit<>overlay. Both fields are typed correctly by the schema itself.z.customonexecuteis a runtime no-op — but it doesn't need a runtime guard because the wire path drops closures (they can't cross IPC) andfinalizeToolsynthesizes a no-op error closure on arrival.2.
tool-defaults.ts—finalizeToolsecond arg optional (review thread)The
""fallback is fail-loud —registerSkillToolsalready rejects empty names with a clear error.3.
registries.ts— one-liner (review thread)Defensive default lives inside
finalizeToolnow, not at every IPC call site.4.
ask-question-tool.ts— kill the double factory (review thread)The
createAskQuestionTool(prompterFactory = ...)factory existed only to inject a stubQuestionPrompterfrom tests. That's two layers of indirection for one mock seam. Replaced with a plain literal matching every other tool:Test rewrite uses
mock.module()+ a dynamicawait import()of the tool, which collapses both layers into one mock seam at the module boundary.Verification
ask-question-tool.test.ts(23),registries.test.ts(16),plugin-api-tool-definition.test.ts(4),plugin-tool-contribution.test.ts(12).Stack
Builds on #32631 (merged). No interaction with any open PRs.