@yaklabs-ai/plugin-sdk 0.12.5 → 0.13.1

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/dist/jev.d.ts CHANGED
@@ -138,7 +138,7 @@ export declare interface JevScoreQuestion {
138
138
  * Those are host-side (`@kay/agent-runtime`'s `kay/jev/`).
139
139
  * ---
140
140
  *
141
- * The vocabulary is Kay's own (SDK 0.12.0). Before it, `use` received TypeSafe's `TypeSafeClient`
141
+ * The vocabulary is Bonsai's own (SDK 0.12.0). Before it, `use` received TypeSafe's `TypeSafeClient`
142
142
  * and plugins coded against a vendor's types. It follows pi's classifier contract, which the host
143
143
  * runs Jev through: a yes/no question is `bool` and answers a `probability`, every instruction and
144
144
  * criterion is text, and the state is a JSON object.
@@ -20,7 +20,7 @@ declare interface AccountIdentity {
20
20
  readonly displayName?: string;
21
21
  /** Absolute URL of the account's avatar. */
22
22
  readonly avatarUrl?: string;
23
- /** Issuer-asserted beta-tester flag; only Kay's product-account service sets it. */
23
+ /** Issuer-asserted beta-tester flag; only Bonsai's product-account service sets it. */
24
24
  readonly betaTester?: boolean;
25
25
  readonly activeOrganizationId?: string;
26
26
  readonly organizationSelectionState?: "selected" | "unresolved";
@@ -28,7 +28,7 @@ declare interface AccountIdentity {
28
28
  /**
29
29
  * Provider-native session id claim (WorkOS's `sid`), needed to call the provider's own
30
30
  * session-revocation endpoint at sign-out (`POST /user_management/sessions/revoke`, not yet
31
- * wired — see `docs/kay-account-auth.md`'s "Sign-out guarantee" section). Only Kay's WorkOS
31
+ * wired — see `docs/kay-account-auth.md`'s "Sign-out guarantee" section). Only Bonsai's WorkOS
32
32
  * product-account exchange/refresh sets it; every other provider leaves it absent.
33
33
  */
34
34
  readonly providerSessionId?: string;
@@ -164,7 +164,7 @@ declare interface ApiKeyAuthSchemeField {
164
164
  readonly label: string;
165
165
  }
166
166
 
167
- /** Provider-owned instructions for creating a credential outside Kay. */
167
+ /** Provider-owned instructions for creating a credential outside Bonsai. */
168
168
  declare interface ApiKeyAuthSchemeSetup {
169
169
  readonly instructions: string;
170
170
  /** Absolute HTTPS page where the user can create or manage the credential. */
@@ -531,7 +531,7 @@ declare interface ExtensionPaths {
531
531
  export declare function fauxAuthContext(overrides?: Partial<ExtensionAuthContext>): ExtensionAuthContext;
532
532
 
533
533
  /** The capability handles a `KayAgentPluginApi` always carries — authority is decided per call. */
534
- export declare type FauxKayAgentCapabilities = Pick<KayAgentPluginApi, "filesystem" | "network" | "webSession" | "process" | "whiteboard" | "interaction" | "interceptToolCall" | "withJev" | "events" | "time" | "userFiles">;
534
+ export declare type FauxKayAgentCapabilities = Pick<KayAgentPluginApi, "network" | "webSession" | "process" | "whiteboard" | "interaction" | "interceptToolCall" | "withJev" | "events" | "time" | "userFiles" | "familyFolder">;
535
535
 
536
536
  export declare function fauxKayAgentCapabilities(): FauxKayAgentCapabilities;
537
537
 
@@ -641,7 +641,7 @@ declare interface InteractionCandidate {
641
641
  declare type InteractionCandidateKind = "click" | "fill" | "select" | "scroll" | "wait" | "activate" | "press";
642
642
 
643
643
  declare interface InteractionDriver {
644
- /** Unique in the session. `builtin:` is reserved for Kay's own drivers. */
644
+ /** Unique in the session. `builtin:` is reserved for Bonsai's own drivers. */
645
645
  readonly id: string;
646
646
  /** Human-readable name the agent sees beside the driver's targets. */
647
647
  readonly label: string;
@@ -664,7 +664,7 @@ declare interface InteractionDriver {
664
664
  }
665
665
 
666
666
  /**
667
- * The Interaction driver contract: what a plugin implements to let Kay's agent drive something
667
+ * The Interaction driver contract: what a plugin implements to let Bonsai's agent drive something
668
668
  * on the user's machine (a browser, an app, a remote screen) through the harness-owned
669
669
  * Interaction engine.
670
670
  *
@@ -677,7 +677,7 @@ declare interface InteractionDriver {
677
677
  * every tool work on every driver.
678
678
  *
679
679
  * Shape mirrors `SearchProvider` behind `web_search`: one tool set owned by the harness, many
680
- * backends registered per session, the `builtin:` id namespace reserved for Kay's own.
680
+ * backends registered per session, the `builtin:` id namespace reserved for Bonsai's own.
681
681
  */
682
682
  /** "Browser Interaction" drives web pages; "Computer Use Interaction" drives native apps. */
683
683
  declare type InteractionDriverKind = "browser" | "computer-use";
@@ -736,7 +736,7 @@ declare interface InteractionRegion {
736
736
  }
737
737
 
738
738
  /**
739
- * One surface Kay ships, in the driver contract's own shape, so a plugin's driver is the surface
739
+ * One surface Bonsai ships, in the driver contract's own shape, so a plugin's driver is the surface
740
740
  * plus an identity: `{ id, label, kind, ...api.interaction.desktop }`. Each call is checked
741
741
  * against the plugin's `interaction.surface {surface}` grant, and then against the session's
742
742
  * permissions exactly as the agent's `interaction_*` tools are: an act is judged as the plugin
@@ -876,7 +876,7 @@ declare interface JevScoreQuestion {
876
876
  * Those are host-side (`@kay/agent-runtime`'s `kay/jev/`).
877
877
  * ---
878
878
  *
879
- * The vocabulary is Kay's own (SDK 0.12.0). Before it, `use` received TypeSafe's `TypeSafeClient`
879
+ * The vocabulary is Bonsai's own (SDK 0.12.0). Before it, `use` received TypeSafe's `TypeSafeClient`
880
880
  * and plugins coded against a vendor's types. It follows pi's classifier contract, which the host
881
881
  * runs Jev through: a yes/no question is `bool` and answers a `probability`, every instruction and
882
882
  * criterion is text, and the state is a JSON object.
@@ -925,52 +925,14 @@ declare interface KayAgentEvents {
925
925
  publish(localId: string, payload: unknown): void;
926
926
  }
927
927
 
928
- /**
929
- * Claim-checked filesystem operations. Paths are root-relative; canonical host paths never cross
930
- * the seam. Writes are atomic: readers see the old file or the new one, never a partial write. A
931
- * replace keeps the file's mode but makes a new file, so owner and extended attributes are not
932
- * carried over. On a volume without hard links an exclusive create can briefly show an empty file.
933
- */
934
- declare interface KayAgentFilesystem {
935
- readText(location: KayAgentFilesystemLocation): Promise<string>;
936
- writeText(location: KayAgentFilesystemLocation, content: string, options?: KayAgentFilesystemWriteOptions): Promise<void>;
937
- /** The whole file, or the requested window of it. */
938
- readBytes(location: KayAgentFilesystemLocation, range?: KayAgentFilesystemByteRange): Promise<Uint8Array>;
939
- writeBytes(location: KayAgentFilesystemLocation, bytes: Uint8Array, options?: KayAgentFilesystemWriteOptions): Promise<void>;
940
- mkdir(location: KayAgentFilesystemLocation): Promise<void>;
941
- readDir(location: KayAgentFilesystemLocation): Promise<readonly KayAgentFilesystemEntry[]>;
942
- stat(location: KayAgentFilesystemLocation): Promise<KayAgentFilesystemStat>;
943
- rename(from: KayAgentFilesystemLocation, to: KayAgentFilesystemLocation): Promise<void>;
944
- /** Returns the canonical path relative to the selected grant root. */
945
- realpath(location: KayAgentFilesystemLocation): Promise<string>;
946
- }
947
-
948
- /** A byte window of a file; `maxBytes` is capped by the host. */
949
- declare interface KayAgentFilesystemByteRange {
950
- readonly offset: number;
951
- readonly maxBytes: number;
952
- }
953
-
954
- declare interface KayAgentFilesystemEntry {
955
- readonly name: string;
956
- readonly kind: "file" | "directory" | "symlink" | "other";
957
- }
958
-
959
- declare interface KayAgentFilesystemLocation {
960
- /** Exact `root` selector the host bound for this target, plus a path relative to it. */
961
- readonly root: string;
962
- readonly path: string;
963
- }
964
-
965
- declare interface KayAgentFilesystemStat {
966
- readonly kind: "file" | "directory" | "other";
967
- readonly size: number;
968
- readonly modifiedAtMs: number;
969
- }
970
-
971
- declare interface KayAgentFilesystemWriteOptions {
972
- /** Fail with `already_exists` instead of replacing a file already at the location. */
973
- readonly exclusive?: boolean;
928
+ /** This plugin's folder in the session family's shared Whiteboard. */
929
+ declare interface KayAgentFamilyFolder {
930
+ /**
931
+ * The folder's absolute path, created on first use. The plugin that owns the Whiteboard gets
932
+ * the whole family folder; every other plugin gets its own `plugins/<plugin-id>/` inside it.
933
+ * Undefined outside a session.
934
+ */
935
+ path(): Promise<string | undefined>;
974
936
  }
975
937
 
976
938
  declare interface KayAgentInteractionSurfaces {
@@ -982,7 +944,7 @@ declare interface KayAgentInteractionSurfaces {
982
944
  readonly browserPane: InteractionSurface;
983
945
  /**
984
946
  * The user's desktop through the platform's accessibility APIs: native apps as targets
985
- * (`app://<bundle id>`), their windows' controls as elements. On macOS Kay drives it with its
947
+ * (`app://<bundle id>`), their windows' controls as elements. On macOS Bonsai drives it with its
986
948
  * bundled accessibility worker under the app's Accessibility permission; on a platform with no
987
949
  * backend it reports itself unavailable.
988
950
  */
@@ -1112,11 +1074,8 @@ declare interface KayAgentPluginApi {
1112
1074
  /** Register a whole URI module (a scheme handler that owns its address space, e.g. `drive://`). */
1113
1075
  registerModule(module: UriExtensionModule): void;
1114
1076
  };
1115
- /**
1116
- * Always present; each call is checked against `filesystem.read {root}` / `filesystem.write
1117
- * {root}` for the root it touches, and against the host's binding for that root.
1118
- */
1119
- readonly filesystem: KayAgentFilesystem;
1077
+ /** This plugin's folder in the session family's shared Whiteboard, created on first use. Undefined outside a session. */
1078
+ readonly familyFolder: KayAgentFamilyFolder;
1120
1079
  /** Absent unless the `knowledgeSearch` grant is requested and effectively granted. */
1121
1080
  readonly knowledgeSearch?: KayAgentKnowledgeSearch;
1122
1081
  /** Absent unless the `knownResources` grant is requested and effectively granted. */
@@ -1142,7 +1101,7 @@ declare interface KayAgentPluginApi {
1142
1101
  */
1143
1102
  readonly process: KayAgentProcess;
1144
1103
  /**
1145
- * Always present; the interaction surfaces Kay ships (its browser pane, and the desktop through
1104
+ * Always present; the interaction surfaces Bonsai ships (its browser pane, and the desktop through
1146
1105
  * the platform accessibility APIs), each call checked against `interaction.surface {surface}`
1147
1106
  * and the session's permissions (see {@link InteractionSurface}).
1148
1107
  * Wrap one in an {@link InteractionDriver} and register it with
@@ -1169,9 +1128,8 @@ declare interface KayAgentPluginApi {
1169
1128
  readonly time: KayAppTime;
1170
1129
  /**
1171
1130
  * File I/O in user folders, run by the host on its own worker threads under a deadline and
1172
- * answered as a typed result. Always present and ungated: unlike {@link filesystem} it takes
1173
- * absolute paths and checks no grant, because it adds no authority a plugin's `node:fs` does not
1174
- * already have. What it adds is that a slow or unresponsive folder never blocks the daemon. See
1131
+ * answered as a typed result. Always present and ungated: it takes absolute paths and checks no
1132
+ * grant, because it adds no authority a plugin's `node:fs` does not already have. What it adds is that a slow or unresponsive folder never blocks the daemon. See
1175
1133
  * {@link KayAppUserFiles}.
1176
1134
  */
1177
1135
  readonly userFiles: KayAppUserFiles;
@@ -1742,7 +1700,7 @@ declare type ModelPromptCache = Partial<Record<Exclude<CacheRetention, "none">,
1742
1700
  declare type ModelThinkingLevel = "off" | ThinkingLevel;
1743
1701
 
1744
1702
  /**
1745
- * Who may supply the OAuth client for a scheme: Kay's embedded client, or one the user pastes in
1703
+ * Who may supply the OAuth client for a scheme: Bonsai's embedded client, or one the user pastes in
1746
1704
  * (BYOOC). An UNORDERED SET — list order carries no meaning (nothing reads it as a preference).
1747
1705
  */
1748
1706
  declare type OAuthClientSupplier = "kay" | "user";
@@ -1755,7 +1713,7 @@ declare interface OAuthRedirectResult {
1755
1713
 
1756
1714
  /**
1757
1715
  * The two client-supply axes, structured so the illegal cell is unrepresentable: a `confidential`
1758
- * exchange's secret may only ever be Kay's (held by the host's broker, never pasted by a
1716
+ * exchange's secret may only ever be Bonsai's (held by the host's broker, never pasted by a
1759
1717
  * customer), so `confidential` types `clientSuppliers` as `readonly ["kay"]` — `confidential ×
1760
1718
  * user` is a compile-time impossibility in v1, not a runtime refusal. `pkce`'s `clientSuppliers`
1761
1719
  * is a non-empty set; {@link createOAuthPkceAuthScheme} dedupes it and refuses empty.
@@ -1994,7 +1952,7 @@ declare interface PluginDelegatedScheme {
1994
1952
  readonly connect: (ctx: PluginDelegatedConnectContext) => Promise<PluginDelegatedConnectResult>;
1995
1953
  /**
1996
1954
  * Mint fresh material from the current (expiring) material, including the `data` the last
1997
- * connect or refresh returned. Kay calls this only inside its credential authority, which holds
1955
+ * connect or refresh returned. Bonsai calls this only inside its credential authority, which holds
1998
1956
  * the refresh lease and saves the result; this hook performs the exchange and nothing else.
1999
1957
  * Returned `data` replaces the stored `data`; omitted `data` keeps it. Returned `expiresAt`
2000
1958
  * replaces the stored expiry; omitted `expiresAt` means the new material does not expire.
@@ -2068,7 +2026,7 @@ declare type PluginOAuthPkceScheme = PluginOAuthSchemeClientAxes & {
2068
2026
  readonly summary?: string;
2069
2027
  readonly authorizationUrl: string;
2070
2028
  readonly tokenUrl: string;
2071
- /** The Kay-supplied embedded client id; omit for a `confidential` scheme or a `pkce` scheme
2029
+ /** The Bonsai-supplied embedded client id; omit for a `confidential` scheme or a `pkce` scheme
2072
2030
  * the host ships no client for. */
2073
2031
  readonly clientId?: string;
2074
2032
  /**
@@ -2731,6 +2689,14 @@ declare interface ToolDefinition<TParams extends ToolParametersWire = ToolParame
2731
2689
  * explicit Stop.
2732
2690
  */
2733
2691
  durationClass?: "fast" | "long";
2692
+ /**
2693
+ * Set only for a tool whose rerun with the same arguments is harmless, such
2694
+ * as one keyed on its own call: after a crash cuts a call off, a pi-durable
2695
+ * session may rerun it with its recorded arguments (without `before_tool`)
2696
+ * instead of ending it as an interrupted error. Absent: never rerun. Ignored
2697
+ * on pi 0.99.1.
2698
+ */
2699
+ replaySafe?: true;
2734
2700
  /**
2735
2701
  * Optional capability⇄scheme binding. When present this tool is auth-gated: discoverable when
2736
2702
  * unauthed, callable once the binding is satisfied. The SDK's {@link withResolvedAuth}
@@ -2814,9 +2780,9 @@ declare type ToolParametersWire = unknown;
2814
2780
  *
2815
2781
  * The host is trusted to supply honest values; a declared `kind` of `read` moves a call below
2816
2782
  * the protected-path hard-deny (which only fires for non-read kinds), so a tool that could
2817
- * touch protected config must NOT declare `read`. Kay's embedded-experience tools never touch
2783
+ * touch protected config must NOT declare `read`. Bonsai's embedded-experience tools never touch
2818
2784
  * `configDir`, so this is safe for them. `read` is also what every capability surface reports as
2819
- * "read-only" (Kay's Permissions page groups and defaults on it), so it is a claim about what the
2785
+ * "read-only" (Bonsai's Permissions page groups and defaults on it), so it is a claim about what the
2820
2786
  * tool DOES — never a way to buy silence. A tool that should raise no ask on its own account
2821
2787
  * declares {@link ToolPermissionHint.quiet} and keeps its honest `kind`.
2822
2788
  */
@@ -2857,7 +2823,7 @@ declare interface ToolPermissionHint {
2857
2823
  * because the same tool can read on one input and execute on another, which a static `kind`
2858
2824
  * cannot express.
2859
2825
  *
2860
- * Exemplar — Kay's `mcp` proxy tool, one tool spanning every server: `search`/`describe` derive
2826
+ * Exemplar — Bonsai's `mcp` proxy tool, one tool spanning every server: `search`/`describe` derive
2861
2827
  * `{ kind: "read" }` (metadata lookups), while `call` derives
2862
2828
  * `{ kind: "execute", ruleKey: "mcp:<server>::<tool>", scheme: "<server>" }`, so a
2863
2829
  * `{ scheme: "<server>" }` rule gates a whole server and a `{ ruleKey: "mcp:<server>::<tool>" }`
@@ -2898,7 +2864,7 @@ declare interface ToolPermissionHint {
2898
2864
  * really had — not that the result is always stricter than the target's. How much gate is left
2899
2865
  * on such a call is exactly how much the CARRIER carries: an `execute` carrier is stricter than
2900
2866
  * a `read` target and equal to a `mutate`/`destructive` one outside allow mode, while a
2901
- * {@link quiet} carrier (Kay's own `call_tool`) contributes no mode default at all, so the
2867
+ * {@link quiet} carrier (Bonsai's own `call_tool`) contributes no mode default at all, so the
2902
2868
  * fallback leaves nothing but the carrier's rules and the protected-path deny — and the carrier
2903
2869
  * names no path, so that deny has nothing to fire on. This is why a carrier must refuse a target
2904
2870
  * that is itself a carrier: no chain long or circular enough to reach the fallback can be built,
@@ -2922,7 +2888,7 @@ declare interface ToolPermissionHint {
2922
2888
  * whose only effect is invoking the target the gate is already classifying beside it: the
2923
2889
  * target's kind, rules, resources and the protected-path deny all still decide, so a proxied
2924
2890
  * call is gated exactly like the direct one — no more, and no ask the direct call would never
2925
- * have raised. Kay's `call_tool` declares `{ kind: "execute", quiet: true }` (owner ruling,
2891
+ * have raised. Bonsai's `call_tool` declares `{ kind: "execute", quiet: true }` (owner ruling,
2926
2892
  * 2026-08-18): honest about everything it can reach, silent on its own account.
2927
2893
  *
2928
2894
  * Quiet is not ungated, but it IS nearly all the gate a lone carrier has. On a call that
@@ -3112,13 +3078,19 @@ declare interface UriExtensionModule extends Omit<UriModule, "verbs"> {
3112
3078
  readonly permissionDefaults?: UriPermissionDefaults;
3113
3079
  /** Requires a resolved URI connection before any verb handler runs. */
3114
3080
  readonly requiresConnection?: boolean;
3115
- /** Pure route-aware permission facts; may only strengthen the verb default. */
3081
+ /** Pure route-aware permission facts; see {@link UriExtensionOperationPermissionPolicy}. */
3116
3082
  readonly permission?: UriExtensionOperationPermissionPolicy;
3117
3083
  /** False rejects generic write-tool `source` copies before source fetch. */
3118
3084
  readonly acceptsSourceCopy?: boolean;
3119
3085
  }
3120
3086
 
3121
3087
  declare interface UriExtensionOperationPermissionPolicy {
3088
+ /**
3089
+ * Classify one canonical route, purely. `write`/`edit`/`delete` may only be made stricter
3090
+ * than the verb default; `help`/`read`/`list`/`search` must stay `"read"` (a `ruleKey` is
3091
+ * allowed), because the kernel reruns a crash-interrupted read without asking again. Any
3092
+ * other answer, or a throw, fails the call closed.
3093
+ */
3122
3094
  classifyOperation(request: {
3123
3095
  readonly operation: UriOperation;
3124
3096
  readonly uri: string;
@@ -3346,7 +3318,15 @@ declare interface UriModule {
3346
3318
  normalize?(input: string, ctx: UriResolveContext): UriNormalizeResult | undefined;
3347
3319
  describe?(input: string, ctx: UriDescribeContext): UriResourceDescription | undefined;
3348
3320
  locate?(input: string, ctx: UriLocateContext): Promise<UriLocateResult>;
3321
+ /**
3322
+ * `read`, `help`, `list` and `search` observe; they never change the resource or anything
3323
+ * outside the host. Repeating one with the same input must be harmless: the kernel reruns a
3324
+ * call of these verbs that a crash cut off, with the same input, without asking the user
3325
+ * again. Idempotent bookkeeping (a cache fill, an index of what was read, a usage count) is
3326
+ * fine; a send, a mark-as-read or a create is not, and belongs on a mutating verb.
3327
+ */
3349
3328
  read(input: string, ctx: UriResolveContext): Promise<RawResource>;
3329
+ /** Observes only; see {@link read}. */
3350
3330
  help?(input: string, ctx: UriResolveContext): Promise<UriHelp>;
3351
3331
  write?(input: string, content: UriPayload, ctx: UriResolveContext): Promise<UriMutationResult | void>;
3352
3332
  /** Opt-in structured write with one or more host-resolved resources delivered beside the content. */
@@ -3369,7 +3349,9 @@ declare interface UriModule {
3369
3349
  */
3370
3350
  writeBytes?(input: string, bytes: Uint8Array, source: UriSourceDescriptor, ctx: UriResolveContext): Promise<UriMutationResult | void>;
3371
3351
  edit?(input: string, patch: UriPayload, ctx: UriResolveContext): Promise<UriMutationResult>;
3352
+ /** Observes only; see {@link read}. */
3372
3353
  list?(input: string, params: UriListParams, ctx: UriResolveContext): Promise<UriListResult>;
3354
+ /** Observes only; see {@link read}. */
3373
3355
  search?(input: string, params: UriSearchParams, ctx: UriResolveContext): Promise<UriSearchResult>;
3374
3356
  delete?(input: string, ctx: UriResolveContext): Promise<UriMutationResult>;
3375
3357
  toMarkdown?(input: UriMarkdownRenderInput, ctx: UriResolveContext): string | undefined;
@@ -3587,7 +3569,7 @@ declare interface UriResolveContext {
3587
3569
  * {@link UriResolveContext.sessionId} and just as un-influenceable by the caller — so a module
3588
3570
  * writing to a store it owns can record the actor without the agent being able to choose one.
3589
3571
  *
3590
- * Free text minted by the host's own runtime (Kay's form is `user`, `subagent`,
3572
+ * Free text minted by the host's own runtime (Bonsai's form is `user`, `subagent`,
3591
3573
  * `system/<surface>`, `followup/<surface>`) rather than a closed union: the surface half is the
3592
3574
  * host's vocabulary, and a second enumeration of it here would drift from the first.
3593
3575
  *
@@ -147,17 +147,6 @@ var USER_FILE_METHODS = [
147
147
  ];
148
148
  function fauxKayAgentCapabilities() {
149
149
  return {
150
- filesystem: {
151
- readText: unwired("filesystem.readText"),
152
- writeText: unwired("filesystem.writeText"),
153
- readBytes: unwired("filesystem.readBytes"),
154
- writeBytes: unwired("filesystem.writeBytes"),
155
- mkdir: unwired("filesystem.mkdir"),
156
- readDir: unwired("filesystem.readDir"),
157
- stat: unwired("filesystem.stat"),
158
- rename: unwired("filesystem.rename"),
159
- realpath: unwired("filesystem.realpath")
160
- },
161
150
  network: { fetch: unwired("network.fetch") },
162
151
  webSession: {
163
152
  request: unwired("webSession.request"),
@@ -210,7 +199,10 @@ function fauxKayAgentCapabilities() {
210
199
  kind: "error",
211
200
  detail: `userFiles.${name} is not wired in this test double`
212
201
  })
213
- ]))
202
+ ])),
203
+ // A test double runs outside any session, so it has no family folder — the same answer the
204
+ // real capability gives. A test that exercises one overrides this handle after the spread.
205
+ familyFolder: { path: async () => void 0 }
214
206
  };
215
207
  }
216
208
  function defaultTestToolContext() {