@fungi.computer/shiitake-extensions 0.3.0 → 0.4.0

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.
@@ -68,6 +68,26 @@ type HumanResult = Schema.Schema.Type<typeof HumanResult>;
68
68
 
69
69
  Decoded outcome of a human answer or decline.
70
70
 
71
+ ---
72
+
73
+ ### AgentAnswerResult
74
+
75
+ ```ts
76
+ type AgentAnswerResult = Schema.Schema.Type<typeof AgentAnswerResult>;
77
+ ```
78
+
79
+ Decoded result of an authenticated Agent answer.
80
+
81
+ ---
82
+
83
+ ### EscalateResult
84
+
85
+ ```ts
86
+ type EscalateResult = Schema.Schema.Type<typeof EscalateResult>;
87
+ ```
88
+
89
+ Decoded result of passing an Ask on.
90
+
71
91
  ## Variables
72
92
 
73
93
  ### Question
@@ -119,6 +139,13 @@ Bounded question set with distinct question identities.
119
139
  ```ts
120
140
  const OpenAsk: Struct<{
121
141
  askId: String;
142
+ waitingOn: optionalKey<
143
+ Struct<{
144
+ agentId: NonEmptyString;
145
+ sessionId: NonEmptyString;
146
+ name: NonEmptyString;
147
+ }>
148
+ >;
122
149
  questions: $Array<
123
150
  Struct<{
124
151
  id: String;
@@ -149,6 +176,11 @@ The complete question data shared by ordinary Session clients.
149
176
  const AskProjection: Struct<{
150
177
  asks: $Array<Struct<{
151
178
  askId: String;
179
+ waitingOn: optionalKey<Struct<{
180
+ agentId: NonEmptyString;
181
+ sessionId: NonEmptyString;
182
+ name: NonEmptyString;
183
+ }>>;
152
184
  questions: $Array<Struct<{
153
185
  id: String;
154
186
  header: String;
@@ -257,6 +289,7 @@ const HumanResult: Union<
257
289
  Struct<{
258
290
  result: Literal<"accepted">;
259
291
  via: Literals<readonly ["in_turn", "follow_up"]>;
292
+ commandId: optionalKey<String>;
260
293
  }>,
261
294
  Struct<{
262
295
  result: Literal<"closed">;
@@ -282,39 +315,128 @@ Admission, already-closed state, or absence returned to a human reply.
282
315
 
283
316
  ---
284
317
 
285
- ### AnswerFlowInput
318
+ ### SenderRefusal
286
319
 
287
320
  ```ts
288
- const AnswerFlowInput: Union<
321
+ const SenderRefusal: Struct<{
322
+ result: Literal<"refused">;
323
+ reason: Literal<"not_sender">;
324
+ }>;
325
+ ```
326
+
327
+ Only the Agent an Ask waits on may answer or escalate it.
328
+
329
+ ---
330
+
331
+ ### AgentAnswerResult
332
+
333
+ ```ts
334
+ const AgentAnswerResult: Union<
289
335
  readonly [
290
- Struct<{
291
- askId: String;
292
- requestId: String;
293
- index: Number;
294
- answers: $Record<
295
- String,
336
+ Union<
337
+ readonly [
296
338
  Struct<{
297
- selected: $Array<String>;
298
- text: optionalKey<String>;
299
- }>
300
- >;
301
- stage: Literal<"multiple">;
302
- selection: String;
339
+ result: Literal<"accepted">;
340
+ via: Literals<readonly ["in_turn", "follow_up"]>;
341
+ commandId: optionalKey<String>;
342
+ }>,
343
+ Struct<{
344
+ result: Literal<"closed">;
345
+ status: Literals<
346
+ readonly [
347
+ "answered",
348
+ "declined",
349
+ "withdrawn",
350
+ "expired",
351
+ "cancelled",
352
+ "undeliverable",
353
+ ]
354
+ >;
355
+ }>,
356
+ Struct<{
357
+ result: Literal<"not_found">;
358
+ }>,
359
+ ]
360
+ >,
361
+ Struct<{
362
+ result: Literal<"refused">;
363
+ reason: Literal<"not_sender">;
303
364
  }>,
365
+ ]
366
+ >;
367
+ ```
368
+
369
+ Outcome of the Agent an Ask waits on answering it.
370
+
371
+ ---
372
+
373
+ ### AnsweredBy
374
+
375
+ ```ts
376
+ const AnsweredBy: Struct<{
377
+ kind: Literal<"agent">;
378
+ agentId: String;
379
+ }>;
380
+ ```
381
+
382
+ The Agent that answered, as the host authenticated it.
383
+
384
+ ---
385
+
386
+ ### EscalateResult
387
+
388
+ ```ts
389
+ const EscalateResult: Union<
390
+ readonly [
304
391
  Struct<{
305
- askId: String;
306
- requestId: String;
307
- index: Number;
308
- answers: $Record<
309
- String,
310
- Struct<{
311
- selected: $Array<String>;
312
- text: optionalKey<String>;
313
- }>
392
+ result: Literal<"escalated">;
393
+ to: Literal<"person">;
394
+ }>,
395
+ Struct<{
396
+ result: Literal<"closed">;
397
+ status: Literals<
398
+ readonly [
399
+ "answered",
400
+ "declined",
401
+ "withdrawn",
402
+ "expired",
403
+ "cancelled",
404
+ "undeliverable",
405
+ ]
314
406
  >;
315
- stage: Literal<"selection">;
316
- selection: String;
317
407
  }>,
408
+ Struct<{
409
+ result: Literal<"not_found">;
410
+ }>,
411
+ Struct<{
412
+ result: Literal<"refused">;
413
+ reason: Literal<"not_sender">;
414
+ }>,
415
+ ]
416
+ >;
417
+ ```
418
+
419
+ Outcome of the Agent an Ask waits on passing it on.
420
+
421
+ ---
422
+
423
+ ### EscalateInput
424
+
425
+ ```ts
426
+ const EscalateInput: Struct<{
427
+ askId: String;
428
+ }>;
429
+ ```
430
+
431
+ The Agent's request to pass an Ask on; the caller is host context.
432
+
433
+ ---
434
+
435
+ ### AnswerFlowInput
436
+
437
+ ```ts
438
+ const AnswerFlowInput: Union<
439
+ readonly [
318
440
  Struct<{
319
441
  askId: String;
320
442
  requestId: String;
@@ -326,9 +448,11 @@ const AnswerFlowInput: Union<
326
448
  text: optionalKey<String>;
327
449
  }>
328
450
  >;
329
- stage: Literal<"text">;
330
- selected: $Array<String>;
331
- text: String;
451
+ stage: Literal<"decision">;
452
+ answer: Struct<{
453
+ selected: $Array<String>;
454
+ text: optionalKey<String>;
455
+ }>;
332
456
  }>,
333
457
  Struct<{
334
458
  askId: String;
@@ -338,7 +462,7 @@ const AnswerFlowInput: Union<
338
462
  >;
339
463
  ```
340
464
 
341
- The ordinary /answer conversation carries its draft between typed steps.
465
+ The answer conversation carries its draft between decision steps.
342
466
 
343
467
  ---
344
468
 
@@ -353,25 +477,33 @@ host.
353
477
 
354
478
  #### Type Declaration
355
479
 
356
- | Name | Type | Default value | Description |
357
- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------- | ------------------------------------------------------ |
358
- | <a id="property-id"></a> `id` | `string` & `Brand`\<`"ExtensionId"`\> | - | Stable extension identity. |
359
- | <a id="property-commands"></a> `commands` | `object` | - | Human question controls admitted by the Session owner. |
360
- | `commands.answerFlow` | `object` | - | Advance the server-owned answer conversation. |
361
- | `commands.answerFlow.scope` | `"session"` | `"session"` | The Session owns its asks and replies. |
362
- | `commands.answerFlow.input` | `Union`\<readonly \[`Struct`\<\{ `askId`: `String`; `requestId`: `String`; `index`: `Number`; `answers`: `$Record`\<`String`, `Struct`\<\{ `selected`: `$Array`\<...\>; `text`: `optionalKey`\<...\>; \}\>\>; `stage`: `Literal`\<`"multiple"`\>; `selection`: `String`; \}\>, `Struct`\<\{ `askId`: `String`; `requestId`: `String`; `index`: `Number`; `answers`: `$Record`\<`String`, `Struct`\<\{ `selected`: `$Array`\<...\>; `text`: `optionalKey`\<...\>; \}\>\>; `stage`: `Literal`\<`"selection"`\>; `selection`: `String`; \}\>, `Struct`\<\{ `askId`: `String`; `requestId`: `String`; `index`: `Number`; `answers`: `$Record`\<`String`, `Struct`\<\{ `selected`: `$Array`\<...\>; `text`: `optionalKey`\<...\>; \}\>\>; `stage`: `Literal`\<`"text"`\>; `selected`: `$Array`\<`String`\>; `text`: `String`; \}\>, `Struct`\<\{ `askId`: `String`; \}\>, `Struct`\<\{ \}\>\]\> | `AnswerFlowInput` | Decode the current answer-draft step. |
363
- | `commands.answerFlow.output` | `declare`\<`WhistleCatalogStepV1`, `WhistleCatalogStepV1`\> | `CatalogStep` | Return the next human conversation step. |
364
- | `commands.answer` | `object` | - | Submit a person's completed answers. |
365
- | `commands.answer.scope` | `"session"` | `"session"` | The Session owns this ask. |
366
- | `commands.answer.input` | `Struct`\<\{ `askId`: `String`; `requestId`: `String`; `answers`: `$Record`\<`String`, `Struct`\<\{ `selected`: `$Array`\<`String`\>; `text`: `optionalKey`\<`String`\>; \}\>\>; \}\> | `AnswerInput` | Decode the answer submission. |
367
- | `commands.answer.output` | `Union`\<readonly \[`Struct`\<\{ `result`: `Literal`\<`"accepted"`\>; `via`: `Literals`\<readonly \[`"in_turn"`, `"follow_up"`\]\>; \}\>, `Struct`\<\{ `result`: `Literal`\<`"closed"`\>; `status`: `Literals`\<readonly \[`"answered"`, `"declined"`, `"withdrawn"`, `"expired"`, `"cancelled"`, `"undeliverable"`\]\>; \}\>, `Struct`\<\{ `result`: `Literal`\<`"not_found"`\>; \}\>\]\> | `HumanResult` | Return admission or the ask's terminal state. |
368
- | `commands.decline` | `object` | - | Decline an ask with an optional note. |
369
- | `commands.decline.scope` | `"session"` | `"session"` | The Session owns this ask. |
370
- | `commands.decline.input` | `Struct`\<\{ `askId`: `String`; `requestId`: `String`; `note`: `optionalKey`\<`String`\>; \}\> | `DeclineInput` | Decode the decline submission. |
371
- | `commands.decline.output` | `Union`\<readonly \[`Struct`\<\{ `result`: `Literal`\<`"accepted"`\>; `via`: `Literals`\<readonly \[`"in_turn"`, `"follow_up"`\]\>; \}\>, `Struct`\<\{ `result`: `Literal`\<`"closed"`\>; `status`: `Literals`\<readonly \[`"answered"`, `"declined"`, `"withdrawn"`, `"expired"`, `"cancelled"`, `"undeliverable"`\]\>; \}\>, `Struct`\<\{ `result`: `Literal`\<`"not_found"`\>; \}\>\]\> | `HumanResult` | Return admission or the ask's terminal state. |
372
- | `commands.list` | `object` | - | Read the Session's open asks. |
373
- | `commands.list.scope` | `"session"` | `"session"` | The Session owns the ask collection. |
374
- | `commands.list.input` | `Struct`\<\{ \}\> | - | No additional arguments are required. |
375
- | `commands.list.output` | `Struct`\<\{ `asks`: `$Array`\<`Struct`\<\{ `askId`: `String`; `questions`: `$Array`\<`Struct`\<\{ `id`: ...; `header`: ...; `question`: ...; `options`: ...; `multiple`: ...; \}\>\>; `status`: `Literals`\<readonly \[`"live"`, `"parked"`\]\>; `createdAt`: `Number`; `expiresAt`: `NullOr`\<`Number`\>; \}\>\>; \}\> | `AskProjection` | Decode the currently visible asks. |
376
- | <a id="property-projection"></a> `projection` | `object` | - | Open asks visible to Session clients. |
377
- | `projection.schema` | `Struct`\<\{ `asks`: `$Array`\<`Struct`\<\{ `askId`: `String`; `questions`: `$Array`\<`Struct`\<\{ `id`: `String`; `header`: `String`; `question`: `String`; `options`: `$Array`\<...\>; `multiple`: `optionalKey`\<...\>; \}\>\>; `status`: `Literals`\<readonly \[`"live"`, `"parked"`\]\>; `createdAt`: `Number`; `expiresAt`: `NullOr`\<`Number`\>; \}\>\>; \}\> | `AskProjection` | Decoder for projected asks. |
480
+ | Name | Type | Default value | Description |
481
+ | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------- | ----------------------------------------------------------------------- |
482
+ | <a id="property-id"></a> `id` | `string` & `Brand`\<`"ExtensionId"`\> | - | Stable extension identity. |
483
+ | <a id="property-commands"></a> `commands` | `object` | - | Human question controls admitted by the Session owner. |
484
+ | `commands.answerFlow` | `object` | - | Advance the server-owned answer conversation. |
485
+ | `commands.answerFlow.scope` | `"session"` | `"session"` | The Session owns its asks and replies. |
486
+ | `commands.answerFlow.input` | `Union`\<readonly \[`Struct`\<\{ `askId`: `String`; `requestId`: `String`; `index`: `Number`; `answers`: `$Record`\<`String`, `Struct`\<\{ `selected`: `$Array`\<...\>; `text`: `optionalKey`\<...\>; \}\>\>; `stage`: `Literal`\<`"decision"`\>; `answer`: `Struct`\<\{ `selected`: `$Array`\<`String`\>; `text`: `optionalKey`\<`String`\>; \}\>; \}\>, `Struct`\<\{ `askId`: `String`; \}\>, `Struct`\<\{ \}\>\]\> | `AnswerFlowInput` | Decode the current answer-draft step. |
487
+ | `commands.answerFlow.output` | `declare`\<`WhistleCatalogStepV1`, `WhistleCatalogStepV1`\> | `CatalogStep` | Return the next human conversation step. |
488
+ | `commands.answer` | `object` | - | Submit a person's completed answers. |
489
+ | `commands.answer.scope` | `"session"` | `"session"` | The Session owns this ask. |
490
+ | `commands.answer.input` | `Struct`\<\{ `askId`: `String`; `requestId`: `String`; `answers`: `$Record`\<`String`, `Struct`\<\{ `selected`: `$Array`\<`String`\>; `text`: `optionalKey`\<`String`\>; \}\>\>; \}\> | `AnswerInput` | Decode the answer submission. |
491
+ | `commands.answer.output` | `Union`\<readonly \[`Struct`\<\{ `result`: `Literal`\<`"accepted"`\>; `via`: `Literals`\<readonly \[`"in_turn"`, `"follow_up"`\]\>; `commandId`: `optionalKey`\<`String`\>; \}\>, `Struct`\<\{ `result`: `Literal`\<`"closed"`\>; `status`: `Literals`\<readonly \[`"answered"`, `"declined"`, `"withdrawn"`, `"expired"`, `"cancelled"`, `"undeliverable"`\]\>; \}\>, `Struct`\<\{ `result`: `Literal`\<`"not_found"`\>; \}\>\]\> | `HumanResult` | Return admission or the ask's terminal state. |
492
+ | `commands.answerAgent` | `object` | - | Submit the answer of the Agent this Ask waits on; never a human answer. |
493
+ | `commands.answerAgent.scope` | `"session"` | `"session"` | The child Session owns the Ask; caller authority is host context. |
494
+ | `commands.answerAgent.input` | `Struct`\<\{ `askId`: `String`; `requestId`: `String`; `answers`: `$Record`\<`String`, `Struct`\<\{ `selected`: `$Array`\<`String`\>; `text`: `optionalKey`\<`String`\>; \}\>\>; \}\> | `AnswerInput` | Decode the answer submission without any caller-supplied actor. |
495
+ | `commands.answerAgent.output` | `Struct`\<\{ `result`: `Union`\<readonly \[`Union`\<readonly \[`Struct`\<\{ `result`: ...; `via`: ...; `commandId`: ...; \}\>, `Struct`\<\{ `result`: ...; `status`: ...; \}\>, `Struct`\<\{ `result`: ...; \}\>\]\>, `Struct`\<\{ `result`: `Literal`\<`"refused"`\>; `reason`: `Literal`\<`"not_sender"`\>; \}\>\]\>; `answeredBy`: `Struct`\<\{ `kind`: `Literal`\<`"agent"`\>; `agentId`: `String`; \}\>; \}\> | - | Attribute the accepted answer to its authenticated Agent. |
496
+ | `commands.escalateAgent` | `object` | - | Pass the Ask on from the Agent it waits on; never a human action. |
497
+ | `commands.escalateAgent.scope` | `"session"` | `"session"` | The child Session owns the Ask; caller authority is host context. |
498
+ | `commands.escalateAgent.input` | `Struct`\<\{ `askId`: `String`; \}\> | `EscalateInput` | Decode the escalation without any caller-supplied recipient. |
499
+ | `commands.escalateAgent.output` | `Union`\<readonly \[`Struct`\<\{ `result`: `Literal`\<`"escalated"`\>; `to`: `Literal`\<`"person"`\>; \}\>, `Struct`\<\{ `result`: `Literal`\<`"closed"`\>; `status`: `Literals`\<readonly \[`"answered"`, `"declined"`, `"withdrawn"`, `"expired"`, `"cancelled"`, `"undeliverable"`\]\>; \}\>, `Struct`\<\{ `result`: `Literal`\<`"not_found"`\>; \}\>, `Struct`\<\{ `result`: `Literal`\<`"refused"`\>; `reason`: `Literal`\<`"not_sender"`\>; \}\>\]\> | `EscalateResult` | Where the Ask waits now, or why it did not move. |
500
+ | `commands.decline` | `object` | - | Decline an ask with an optional note. |
501
+ | `commands.decline.scope` | `"session"` | `"session"` | The Session owns this ask. |
502
+ | `commands.decline.input` | `Struct`\<\{ `askId`: `String`; `requestId`: `String`; `note`: `optionalKey`\<`String`\>; \}\> | `DeclineInput` | Decode the decline submission. |
503
+ | `commands.decline.output` | `Union`\<readonly \[`Struct`\<\{ `result`: `Literal`\<`"accepted"`\>; `via`: `Literals`\<readonly \[`"in_turn"`, `"follow_up"`\]\>; `commandId`: `optionalKey`\<`String`\>; \}\>, `Struct`\<\{ `result`: `Literal`\<`"closed"`\>; `status`: `Literals`\<readonly \[`"answered"`, `"declined"`, `"withdrawn"`, `"expired"`, `"cancelled"`, `"undeliverable"`\]\>; \}\>, `Struct`\<\{ `result`: `Literal`\<`"not_found"`\>; \}\>\]\> | `HumanResult` | Return admission or the ask's terminal state. |
504
+ | `commands.list` | `object` | - | Read the Session's open asks. |
505
+ | `commands.list.scope` | `"session"` | `"session"` | The Session owns the ask collection. |
506
+ | `commands.list.input` | `Struct`\<\{ \}\> | - | No additional arguments are required. |
507
+ | `commands.list.output` | `Struct`\<\{ `asks`: `$Array`\<`Struct`\<\{ `askId`: `String`; `waitingOn`: `optionalKey`\<`Struct`\<\{ `agentId`: ...; `sessionId`: ...; `name`: ...; \}\>\>; `questions`: `$Array`\<`Struct`\<\{ `id`: ...; `header`: ...; `question`: ...; `options`: ...; `multiple`: ...; \}\>\>; `status`: `Literals`\<readonly \[`"live"`, `"parked"`\]\>; `createdAt`: `Number`; `expiresAt`: `NullOr`\<`Number`\>; \}\>\>; \}\> | `AskProjection` | Decode the currently visible asks. |
508
+ | <a id="property-projection"></a> `projection` | `object` | - | Open asks visible to Session clients. |
509
+ | `projection.schema` | `Struct`\<\{ `asks`: `$Array`\<`Struct`\<\{ `askId`: `String`; `waitingOn`: `optionalKey`\<`Struct`\<\{ `agentId`: `NonEmptyString`; `sessionId`: `NonEmptyString`; `name`: `NonEmptyString`; \}\>\>; `questions`: `$Array`\<`Struct`\<\{ `id`: `String`; `header`: `String`; `question`: `String`; `options`: `$Array`\<...\>; `multiple`: `optionalKey`\<...\>; \}\>\>; `status`: `Literals`\<readonly \[`"live"`, `"parked"`\]\>; `createdAt`: `Number`; `expiresAt`: `NullOr`\<`Number`\>; \}\>\>; \}\> | `AskProjection` | Decoder for projected asks. |
@@ -16,4 +16,34 @@
16
16
  const questionExtension: RosteredExtensionV0;
17
17
  ```
18
18
 
19
+ Hub's shared roster sends every Ask to a person.
20
+
21
+ ---
22
+
23
+ ### localQuestionExtension
24
+
25
+ ```ts
26
+ const localQuestionExtension: RosteredExtensionV0;
27
+ ```
28
+
29
+ Local's roster sends an Ask to the Agent that sent the asking work first.
30
+
31
+ ## Functions
32
+
33
+ ### questionExtensionFor()
34
+
35
+ ```ts
36
+ function questionExtensionFor(routesToSender): RosteredExtensionV0;
37
+ ```
38
+
19
39
  One durable question owner over the ordinary extension doors.
40
+
41
+ #### Parameters
42
+
43
+ | Parameter | Type |
44
+ | ---------------- | --------- |
45
+ | `routesToSender` | `boolean` |
46
+
47
+ #### Returns
48
+
49
+ `RosteredExtensionV0`
@@ -39,8 +39,10 @@ function createConversationAgents(storage): object;
39
39
 
40
40
  This host's own record of the Agents each conversation created or sent accepted
41
41
  work to, written as its Agent-control directory accepts them (Wiki:
42
- agent-runtime-tools-and-clients, read-only sub-agent view). It feeds the footer
43
- only; it owns no relationship and grants nothing.
42
+ agent-runtime-tools-and-clients, read-only sub-agent view). Accepted child
43
+ command receipts are separately reconciled against their owner's settlement
44
+ records for the parent's goal guard. Neither record grants authority over a
45
+ child.
44
46
 
45
47
  #### Parameters
46
48
 
@@ -61,7 +63,14 @@ only; it owns no relationship and grants nothing.
61
63
  ### oysterAgentExtensions()
62
64
 
63
65
  ```ts
64
- function oysterAgentExtensions(source): object;
66
+ function oysterAgentExtensions(source): Readonly<{
67
+ roster: readonly RosteredExtensionV0[];
68
+ grants: readonly Readonly<{
69
+ id: string & Brand<"ExtensionId">;
70
+ capabilities: readonly ExtensionCapabilityV0[];
71
+ grantKey?: string;
72
+ }>[];
73
+ }>;
65
74
  ```
66
75
 
67
76
  Both hosts compose the ordinary roster and its read-only created-Agent footer.
@@ -74,7 +83,6 @@ Both hosts compose the ordinary roster and its read-only created-Agent footer.
74
83
 
75
84
  #### Returns
76
85
 
77
- | Name | Type | Description |
78
- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
79
- | `roster` | `RosteredExtensionV0`[] | Ordinary extensions plus the read-only created-Agent footer. |
80
- | `grants` | `Readonly`\<\{ `id`: `string` & `Brand`\<`"ExtensionId"`\>; `capabilities`: readonly `ExtensionCapabilityV0`[]; `grantKey?`: `string`; \}\>[] | Host grants corresponding to the composed extension roster. |
86
+ `Readonly`\<\{ `roster`: readonly `RosteredExtensionV0`[]; `grants`: readonly
87
+ `Readonly`\<\{ `id`: `string` & `Brand`\<`"ExtensionId"`\>; `capabilities`:
88
+ readonly `ExtensionCapabilityV0`[]; `grantKey?`: `string`; \}\>[]; \}\>
@@ -16,11 +16,33 @@
16
16
  const botanicalAgentExtensions: ExtensionRosterV0;
17
17
  ```
18
18
 
19
- Botanical's product roster with explicit grants. Hub and Local pass this same
20
- value; a stranger host passes its own (or an empty) roster.
19
+ Botanical's shared roster with explicit grants. Local composes its
20
+ sender-routing question extension through `botanicalExtensionsWithSkills`; hosts
21
+ without that Local capability send every Ask to a person.
21
22
 
22
23
  ## Functions
23
24
 
25
+ ### botanicalExtensionsWithSubagents()
26
+
27
+ ```ts
28
+ function botanicalExtensionsWithSubagents(base, source): ExtensionRosterV0;
29
+ ```
30
+
31
+ Compose the goal and footer over one source; an absent goal is a wiring error.
32
+
33
+ #### Parameters
34
+
35
+ | Parameter | Type |
36
+ | --------- | ---------------------------------------------------- |
37
+ | `base` | `ExtensionRosterV0` |
38
+ | `source` | [`SubagentSource`](subagents/host.md#subagentsource) |
39
+
40
+ #### Returns
41
+
42
+ `ExtensionRosterV0`
43
+
44
+ ---
45
+
24
46
  ### botanicalExtensionsWithSkills()
25
47
 
26
48
  ```ts
@@ -38,3 +60,18 @@ A host with a SkillSource grants read-only skill discovery for its own runs.
38
60
  #### Returns
39
61
 
40
62
  `ExtensionRosterV0`
63
+
64
+ ---
65
+
66
+ ### botanicalExtensionsWithSenderRouting()
67
+
68
+ ```ts
69
+ function botanicalExtensionsWithSenderRouting(): ExtensionRosterV0;
70
+ ```
71
+
72
+ Route Asks to the Agent that sent the work, only in a host that owns the Local
73
+ answer door.
74
+
75
+ #### Returns
76
+
77
+ `ExtensionRosterV0`