33 *
44 * A `requires_tools` entry is a hard requirement: the tool must mount on
55 * the worker or the dispatch rejects before any session, telemetry, or
6- * worktree exists. Names are canonical engine ids on both sides (aliases
7- * collapse via canonicalToolName), so `shell` and `run_shell` are the
8- * same requirement. A wire name the worker's resolved profile advertises
9- * maps onto the engine ids that profile projects it to, when the director
10- * already allows one of those engines. A name that profile does not
11- * advertise, and that is not a catalog engine, stays unknown. Fail-closed:
12- * unknown names and allowlist/denylist misses reject.
6+ * worktree exists. Engine ids are canonical on both sides. A wire name
7+ * passes only when the worker's resolved profile advertises it, and then
8+ * only as the engine ids that profile projects it onto — a rename is one
9+ * engine, a fold is every covered engine. A wire some other profile
10+ * advertises is unknown here (`shell` is gpt; default advertises `bash`).
11+ * With no resolved profile, hidden aliases still canonicalize and a fold
12+ * target stays unknown. Fail-closed: unknown names and allowlist/denylist
13+ * misses reject.
1314 *
1415 * `stale_snapshot` is never emitted here — it is the mount-time echo in
1516 * run.ts (a stamped tool missing from the live mount). It lives in this
1920import { canonicalToolName } from "../agent/canonical-tool-name.js" ;
2021import {
2122 enginesForAdvertisedWire ,
23+ toolProfiles ,
2224 type ToolProfile ,
2325} from "../agent/tool-aliases.js" ;
2426import {
@@ -57,9 +59,9 @@ export interface PreflightCapabilitiesInput {
5759 /** Worker label for messages (director id or profile id). */
5860 agentLabel : string ;
5961 /**
60- * Tool profile of the model this spawn will run. `apply_patch` is the gpt
61- * wire name for the file-mutation engines, not a catalog id. Omit when no
62- * family is resolved — then apply_patch stays unknown.
62+ * Tool profile of the model this spawn will run. Wire names resolve through
63+ * that profile's projection. Omit when no family is resolved — hidden
64+ * aliases still canonicalize, and a fold target stays unknown.
6365 */
6466 toolProfile ?: ToolProfile | undefined ;
6567}
@@ -248,8 +250,9 @@ function enginesCoveredByDirector(
248250
249251/**
250252 * Profile wire names become the engines that profile advertises. Undefined
251- * means this name is not a wire for the resolved profile — the caller falls
252- * through to catalog engines. No resolved profile does not guess.
253+ * means this name is not a wire for any profile — the caller falls through
254+ * to catalog engines. A wire only another profile advertises is unknown.
255+ * No resolved profile does not guess.
253256 */
254257function expandAdvertisedWire (
255258 trimmed : string ,
@@ -259,7 +262,25 @@ function expandAdvertisedWire(
259262) : string [ ] | { unavailable : CapabilityUnavailable } | undefined {
260263 if ( profile === undefined ) return undefined ;
261264 const mapped = enginesForAdvertisedWire ( trimmed , profile ) ;
262- if ( mapped === undefined ) return undefined ;
265+ if ( mapped === undefined ) {
266+ const foreign = toolProfiles ( ) . some (
267+ ( candidate ) =>
268+ candidate !== profile &&
269+ enginesForAdvertisedWire ( trimmed , candidate ) !== undefined ,
270+ ) ;
271+ if ( ! foreign ) return undefined ;
272+ const suggestion = nearestToolName ( trimmed ) ;
273+ const useful =
274+ suggestion !== undefined &&
275+ suggestion . toLowerCase ( ) !== trimmed . toLowerCase ( ) ;
276+ return {
277+ unavailable : {
278+ code : "unknown_tool" ,
279+ tool : trimmed ,
280+ ...( useful ? { suggestion } : { } ) ,
281+ } ,
282+ } ;
283+ }
263284 const covered = enginesCoveredByDirector ( mapped , filter , postFilter ) ;
264285 if ( covered . length > 0 ) return [ ...covered ] ;
265286 const hint = mapped [ 0 ] ;
0 commit comments