@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.
@@ -1,9 +1,19 @@
1
1
  import { type AgentControlDirectory } from "@fungi.computer/shiitake/agent-control";
2
2
  import type { CapabilitySource } from "@fungi.computer/mycelium";
3
+ import { type ShiitakeSession } from "@fungi.computer/shiitake/client";
3
4
  import type { WatchdogSqliteOwner } from "@fungi.computer/watchdog";
4
5
  import type { SubagentSource } from "../subagents/host.js";
5
6
  /** Host-pack creation admission; attachments/model selection retain their own owners. */
6
7
  export declare function assertOrdinaryAgentCreation(input: Parameters<NonNullable<AgentControlDirectory["create"]>>[0]): void;
8
+ /** The directory a conversation sends through; opening a recipient only needs its Sessions' extension doors. */
9
+ type SendingDirectory = Omit<AgentControlDirectory, "open"> & Readonly<{
10
+ open: (agentId: string, signal: AbortSignal) => Promise<Readonly<{
11
+ agent: Readonly<{
12
+ session: (sessionId: string) => Pick<ShiitakeSession, "extension">;
13
+ }>;
14
+ release: () => Promise<void>;
15
+ }>>;
16
+ }>;
7
17
  /**
8
18
  * This host's own record of the Agents each conversation created or sent
9
19
  * accepted work to, written as its Agent-control directory accepts them
@@ -14,7 +24,7 @@ export declare function assertOrdinaryAgentCreation(input: Parameters<NonNullabl
14
24
  */
15
25
  export declare function createConversationAgents(storage: WatchdogSqliteOwner): {
16
26
  /** Bind acceptance recording to this conversation's existing directory. */
17
- recording: (parentSessionId: string | undefined, directory: AgentControlDirectory) => AgentControlDirectory;
27
+ recording: <Directory extends SendingDirectory>(parentSessionId: string | undefined, directory: Directory) => Directory;
18
28
  /** Agent-control tools bound to the conversation that acquires them. */
19
29
  control: (directoryFor: (parentSessionId: string | undefined) => AgentControlDirectory) => CapabilitySource;
20
30
  /** The footer rows for one conversation; archived and unknown Agents leave it. */
@@ -25,3 +35,4 @@ export declare function oysterAgentExtensions(source: SubagentSource): Readonly<
25
35
  roster: readonly import("@fungi.computer/shiitake").RosteredExtensionV0[];
26
36
  grants: readonly import("@fungi.computer/shiitake").ExtensionGrantV0[];
27
37
  }>;
38
+ export {};
@@ -1,7 +1,9 @@
1
1
  import { AgentCreateRefused, createAgentControlSource, } from "@fungi.computer/shiitake/agent-control";
2
+ import { bindSessionExtension, } from "@fungi.computer/shiitake/client";
2
3
  import * as Effect from "effect/Effect";
3
4
  import * as Schema from "effect/Schema";
4
5
  import { botanicalAgentExtensions, botanicalExtensionsWithSubagents, } from "../roster.js";
6
+ import { goalContract } from "../goal/contract.js";
5
7
  /** Host-pack creation admission; attachments/model selection retain their own owners. */
6
8
  export function assertOrdinaryAgentCreation(input) {
7
9
  if (input.model !== undefined)
@@ -25,6 +27,16 @@ const delegatedCommandRow = Schema.Struct({
25
27
  const maxWatchedCommands = 64;
26
28
  const pollMs = 2_000;
27
29
  const maxWatchMs = 30 * 60_000;
30
+ /** The objective of a delegated lane: the named goal, else the send's first line. */
31
+ function laneObjective(text, goal) {
32
+ const named = goal?.trim();
33
+ if (named !== undefined && named !== "")
34
+ return named;
35
+ return (text
36
+ .split("\n")
37
+ .map((line) => line.trim())
38
+ .find((line) => line !== "") ?? text.trim()).slice(0, 4_000);
39
+ }
28
40
  /**
29
41
  * This host's own record of the Agents each conversation created or sent
30
42
  * accepted work to, written as its Agent-control directory accepts them
@@ -75,8 +87,31 @@ export function createConversationAgents(storage) {
75
87
  ...recorded,
76
88
  send: async (task, sourceSessionId, signal, invocation) => {
77
89
  const accepted = await send(task, sourceSessionId, signal, invocation);
78
- if (accepted.status === "accepted")
79
- sent(sourceSessionId, task.agentId, accepted.sessionId ?? task.sessionId, accepted.commandId);
90
+ if (accepted.status === "accepted") {
91
+ const sessionId = accepted.sessionId ?? task.sessionId;
92
+ sent(sourceSessionId, task.agentId, sessionId, accepted.commandId);
93
+ // A send that opens a new Session puts it under a goal, so the
94
+ // lane keeps working until it reports complete or blocked. A
95
+ // retry replays the same request id, so the goal is set once.
96
+ if (task.delivery === "followUp" &&
97
+ task.sessionId === undefined &&
98
+ task.goal !== false &&
99
+ sessionId !== undefined) {
100
+ const opened = await directory.open(task.agentId, signal);
101
+ try {
102
+ await bindSessionExtension(opened.agent.session(sessionId), goalContract).call("set", {
103
+ objective: laneObjective(task.text, task.goal),
104
+ expectedRevision: null,
105
+ requestId: `lane-goal:${accepted.commandId}`,
106
+ // The send is the lane's first turn; never repeat it.
107
+ adoptCommandId: accepted.commandId,
108
+ });
109
+ }
110
+ finally {
111
+ await opened.release();
112
+ }
113
+ }
114
+ }
80
115
  return accepted;
81
116
  },
82
117
  };
package/dist/roster.d.ts CHANGED
@@ -2,11 +2,14 @@ import type { ExtensionRosterV0 } from "@fungi.computer/shiitake/extensions";
2
2
  import type { SkillSource } from "./skills/source.js";
3
3
  import { type SubagentSource } from "./subagents/host.js";
4
4
  /**
5
- * Botanical's product roster with explicit grants. Hub and Local pass this
6
- * same value; a stranger host passes its own (or an empty) roster.
5
+ * Botanical's shared roster with explicit grants. Local composes its
6
+ * sender-routing question extension through `botanicalExtensionsWithSkills`;
7
+ * hosts without that Local capability send every Ask to a person.
7
8
  */
8
9
  export declare const botanicalAgentExtensions: ExtensionRosterV0;
9
10
  /** Compose the goal and footer over one source; an absent goal is a wiring error. */
10
11
  export declare function botanicalExtensionsWithSubagents(base: ExtensionRosterV0, source: SubagentSource): ExtensionRosterV0;
11
12
  /** A host with a SkillSource grants read-only skill discovery for its own runs. */
12
13
  export declare function botanicalExtensionsWithSkills(source: SkillSource): ExtensionRosterV0;
14
+ /** Route Asks to the Agent that sent the work, only in a host that owns the Local answer door. */
15
+ export declare function botanicalExtensionsWithSenderRouting(): ExtensionRosterV0;
package/dist/roster.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { almanacContract } from "./almanac/contract.js";
2
2
  import { almanacExtension } from "./almanac/host.js";
3
3
  import { questionContract } from "./question/contract.js";
4
- import { questionExtension } from "./question/host.js";
4
+ import { localQuestionExtension, questionExtension } from "./question/host.js";
5
5
  import { goalContract } from "./goal/contract.js";
6
6
  import { goalExtension, goalExtensionFor } from "./goal/host.js";
7
7
  import { loopContract } from "./loop/contract.js";
@@ -11,8 +11,9 @@ import { planExtension } from "./plan/host.js";
11
11
  import { skillsExtension, skillsGrant } from "./skills/host.js";
12
12
  import { subagentsExtension, subagentsGrant, } from "./subagents/host.js";
13
13
  /**
14
- * Botanical's product roster with explicit grants. Hub and Local pass this
15
- * same value; a stranger host passes its own (or an empty) roster.
14
+ * Botanical's shared roster with explicit grants. Local composes its
15
+ * sender-routing question extension through `botanicalExtensionsWithSkills`;
16
+ * hosts without that Local capability send every Ask to a person.
16
17
  */
17
18
  export const botanicalAgentExtensions = Object.freeze({
18
19
  roster: Object.freeze([
@@ -90,8 +91,18 @@ export function botanicalExtensionsWithSubagents(base, source) {
90
91
  }
91
92
  /** A host with a SkillSource grants read-only skill discovery for its own runs. */
92
93
  export function botanicalExtensionsWithSkills(source) {
94
+ const local = botanicalExtensionsWithSenderRouting();
93
95
  return {
94
- roster: [...botanicalAgentExtensions.roster, skillsExtension({ source })],
95
- grants: [...botanicalAgentExtensions.grants, skillsGrant],
96
+ roster: [...local.roster, skillsExtension({ source })],
97
+ grants: [...local.grants, skillsGrant],
98
+ };
99
+ }
100
+ /** Route Asks to the Agent that sent the work, only in a host that owns the Local answer door. */
101
+ export function botanicalExtensionsWithSenderRouting() {
102
+ return {
103
+ ...botanicalAgentExtensions,
104
+ roster: botanicalAgentExtensions.roster.map((extension) => extension.manifest.id === questionContract.id
105
+ ? localQuestionExtension
106
+ : extension),
96
107
  };
97
108
  }
@@ -2,32 +2,64 @@ import { defineClientEntry, } from "@fungi.computer/shiitake/client-entry";
2
2
  import { subagentsContract, } from "./contract.js";
3
3
  const presentation = {
4
4
  working: { label: "working", tone: "running", status: "info" },
5
- waiting: { label: "waiting on you", tone: "attention", status: "warning" },
5
+ waiting_on_agent: { label: "waiting", tone: "muted", status: "neutral" },
6
+ needs_you: { label: "needs you", tone: "attention", status: "warning" },
6
7
  idle: { label: "idle", tone: "settled", status: "success" },
7
8
  failed: { label: "failed", tone: "error", status: "error" },
8
9
  unavailable: { label: "unavailable", tone: "muted", status: "neutral" },
9
10
  };
11
+ /** "waiting on Shiitake": the Agent a calm child waits on, never "you". */
12
+ function label(agent) {
13
+ return agent.state === "waiting_on_agent"
14
+ ? `waiting on ${agent.waitingOn ?? "its parent"}`
15
+ : presentation[agent.state].label;
16
+ }
10
17
  function row(agent) {
11
18
  const shown = presentation[agent.state];
12
19
  const base = {
13
20
  id: agent.agentId,
14
- title: `${agent.name} · ${shown.label}`,
21
+ title: `${agent.name} · ${label(agent)}`,
15
22
  tone: shown.tone,
16
- status: { label: shown.label, tone: shown.status },
23
+ status: { label: label(agent), tone: shown.status },
17
24
  };
18
- const detailed = agent.attention === undefined ? base : { ...base, detail: agent.attention };
25
+ // The question itself, so a person can decide without opening the child.
26
+ const asked = agent.ask?.questions[0]?.question;
27
+ const text = asked ?? agent.attention;
28
+ const detailed = text === undefined ? base : { ...base, detail: text.slice(0, 200) };
19
29
  // An Agent created but never messaged has no conversation to open yet.
20
- return agent.sessionId === null
21
- ? { ...detailed, detail: agent.attention ?? "not messaged yet" }
22
- : {
23
- ...detailed,
24
- open: {
25
- params: [
26
- { key: "agent", value: agent.agentId },
27
- { key: "session", value: agent.sessionId },
28
- ],
29
- },
30
- };
30
+ if (agent.sessionId === null)
31
+ return { ...detailed, detail: agent.attention ?? "not messaged yet" };
32
+ const opened = {
33
+ ...detailed,
34
+ open: {
35
+ params: [
36
+ { key: "agent", value: agent.agentId },
37
+ { key: "session", value: agent.sessionId },
38
+ ],
39
+ },
40
+ };
41
+ if (agent.ask === undefined)
42
+ return opened;
43
+ const answer = {
44
+ id: "answerChild",
45
+ label: "Answer",
46
+ input: {
47
+ agentId: agent.agentId,
48
+ sessionId: agent.sessionId,
49
+ askId: agent.ask.askId,
50
+ },
51
+ };
52
+ // A question offers the one Answer action, calm or not: a person may always
53
+ // answer, and the first answer wins. Only a question that needs the person
54
+ // takes over the composer.
55
+ return {
56
+ ...opened,
57
+ actions: [
58
+ agent.state === "needs_you"
59
+ ? { ...answer, awaiting: "question" }
60
+ : answer,
61
+ ],
62
+ };
31
63
  }
32
64
  /**
33
65
  * One summary row while the footer is collapsed: how
@@ -36,12 +68,24 @@ function row(agent) {
36
68
  */
37
69
  function summary(view) {
38
70
  const count = (state) => view.agents.filter((agent) => agent.state === state).length;
39
- const counts = ["working", "waiting", "failed", "unavailable"]
40
- .map((state) => [state, count(state)])
41
- .filter(([, n]) => n > 0)
42
- .map(([state, n]) => `${String(n)} ${presentation[state].label}`);
71
+ const calm = view.agents.filter((agent) => agent.state === "waiting_on_agent");
72
+ // Calm children name who they wait on; only needs-you children are amber.
73
+ const waiting = [
74
+ ...new Set(calm.map((agent) => agent.waitingOn ?? "its parent")),
75
+ ].map((name) => `${String(calm.filter((agent) => (agent.waitingOn ?? "its parent") === name).length)} waiting on ${name}`);
76
+ const counts = [
77
+ ...["working"]
78
+ .map((state) => [state, count(state)])
79
+ .filter(([, n]) => n > 0)
80
+ .map(([state, n]) => `${String(n)} ${presentation[state].label}`),
81
+ ...waiting,
82
+ ...["needs_you", "failed", "unavailable"]
83
+ .map((state) => [state, count(state)])
84
+ .filter(([, n]) => n > 0)
85
+ .map(([state, n]) => `${String(n)} ${presentation[state].label}`),
86
+ ];
43
87
  const total = view.agents.length + view.more;
44
- const tone = count("waiting") > 0
88
+ const tone = count("needs_you") > 0
45
89
  ? "attention"
46
90
  : count("failed") > 0
47
91
  ? "error"
@@ -2,11 +2,12 @@ import * as Schema from "effect/Schema";
2
2
  /** The most Agents one conversation's footer lists; the rest are counted. */
3
3
  export declare const maximumSubagents = 16;
4
4
  /**
5
- * What the human sees for one ordinary created Agent. `waiting` is any open attention on
6
- * its conversation (a question or a tool approval); `unavailable` means its
7
- * status could not be read, and is never shown as idle.
5
+ * What the human sees for one ordinary created Agent. `waiting_on_agent` is an
6
+ * open question that waits on the Agent that sent the work, calm until it is
7
+ * escalated; `needs_you` is attention that waits on a person; `unavailable`
8
+ * means its status could not be read, and is never shown as idle.
8
9
  */
9
- export declare const SubagentState: Schema.Literals<readonly ["working", "waiting", "idle", "failed", "unavailable"]>;
10
+ export declare const SubagentState: Schema.Literals<readonly ["working", "waiting_on_agent", "needs_you", "idle", "failed", "unavailable"]>;
10
11
  /** Display state of one created Agent's conversation. */
11
12
  export type SubagentState = typeof SubagentState.Type;
12
13
  /** A footer row for an ordinary Agent created by this Agent, never merely sent work. */
@@ -18,9 +19,27 @@ export declare const Subagent: Schema.Struct<{
18
19
  /** Its latest conversation; null when it has none. */
19
20
  readonly sessionId: Schema.NullOr<Schema.String>;
20
21
  /** Observed conversation state, including unreadable status. */
21
- readonly state: Schema.Literals<readonly ["working", "waiting", "idle", "failed", "unavailable"]>;
22
- /** The child's own summary of what waits on the human, when waiting. */
22
+ readonly state: Schema.Literals<readonly ["working", "waiting_on_agent", "needs_you", "idle", "failed", "unavailable"]>;
23
+ /** The child's own summary of what it waits on, when waiting. */
23
24
  readonly attention: Schema.optionalKey<Schema.String>;
25
+ /** Display name of the Agent a `waiting_on_agent` child waits on. */
26
+ readonly waitingOn: Schema.optionalKey<Schema.String>;
27
+ /** The question a waiting child asks, so a person can answer from here. */
28
+ readonly ask: Schema.optionalKey<Schema.Struct<{
29
+ /** Durable identity of the child's open Ask. */
30
+ readonly askId: Schema.String;
31
+ /** The decisions the child asks, so they can be answered from here. */
32
+ readonly questions: Schema.$Array<Schema.Struct<{
33
+ readonly id: Schema.String;
34
+ readonly header: Schema.String;
35
+ readonly question: Schema.String;
36
+ readonly options: Schema.$Array<Schema.Struct<{
37
+ readonly label: Schema.String;
38
+ readonly description: Schema.String;
39
+ }>>;
40
+ readonly multiple: Schema.optionalKey<Schema.Boolean>;
41
+ }>>;
42
+ }>>;
24
43
  }>;
25
44
  /** Decoded footer row for a created Agent. */
26
45
  export type Subagent = typeof Subagent.Type;
@@ -39,9 +58,27 @@ export declare const SubagentsProjection: Schema.Struct<{
39
58
  /** Its latest conversation; null when it has none. */
40
59
  readonly sessionId: Schema.NullOr<Schema.String>;
41
60
  /** Observed conversation state, including unreadable status. */
42
- readonly state: Schema.Literals<readonly ["working", "waiting", "idle", "failed", "unavailable"]>;
43
- /** The child's own summary of what waits on the human, when waiting. */
61
+ readonly state: Schema.Literals<readonly ["working", "waiting_on_agent", "needs_you", "idle", "failed", "unavailable"]>;
62
+ /** The child's own summary of what it waits on, when waiting. */
44
63
  readonly attention: Schema.optionalKey<Schema.String>;
64
+ /** Display name of the Agent a `waiting_on_agent` child waits on. */
65
+ readonly waitingOn: Schema.optionalKey<Schema.String>;
66
+ /** The question a waiting child asks, so a person can answer from here. */
67
+ readonly ask: Schema.optionalKey<Schema.Struct<{
68
+ /** Durable identity of the child's open Ask. */
69
+ readonly askId: Schema.String;
70
+ /** The decisions the child asks, so they can be answered from here. */
71
+ readonly questions: Schema.$Array<Schema.Struct<{
72
+ readonly id: Schema.String;
73
+ readonly header: Schema.String;
74
+ readonly question: Schema.String;
75
+ readonly options: Schema.$Array<Schema.Struct<{
76
+ readonly label: Schema.String;
77
+ readonly description: Schema.String;
78
+ }>>;
79
+ readonly multiple: Schema.optionalKey<Schema.Boolean>;
80
+ }>>;
81
+ }>>;
45
82
  }>>;
46
83
  /** Additional created Agents not included in the visible rows. */
47
84
  readonly more: Schema.Int;
@@ -50,6 +87,33 @@ export declare const SubagentsProjection: Schema.Struct<{
50
87
  }>;
51
88
  /** Decoded list and expansion state projected into the footer. */
52
89
  export type SubagentsProjection = typeof SubagentsProjection.Type;
90
+ /**
91
+ * The question flow's own steps, each naming the child whose Ask they answer.
92
+ * The footer owns the conversation; the child's question owner commits it.
93
+ */
94
+ export declare const AnswerChildInput: Schema.Union<Schema.Struct<{
95
+ readonly agentId: Schema.String;
96
+ readonly sessionId: Schema.String;
97
+ readonly answer: Schema.Struct<{
98
+ readonly selected: Schema.$Array<Schema.String>;
99
+ readonly text: Schema.optionalKey<Schema.String>;
100
+ }>;
101
+ readonly askId: Schema.String;
102
+ readonly requestId: Schema.String;
103
+ readonly index: Schema.Number;
104
+ readonly answers: Schema.$Record<Schema.String, Schema.Struct<{
105
+ readonly selected: Schema.$Array<Schema.String>;
106
+ readonly text: Schema.optionalKey<Schema.String>;
107
+ }>>;
108
+ readonly stage: Schema.Literal<"decision">;
109
+ } | {
110
+ readonly agentId: Schema.String;
111
+ readonly sessionId: Schema.String;
112
+ readonly askId: Schema.String;
113
+ } | {
114
+ readonly agentId: Schema.String;
115
+ readonly sessionId: Schema.String;
116
+ }>[]>;
53
117
  /** Created-Agent footer control and its visible projection. */
54
118
  export declare const subagentsContract: {
55
119
  /** Stable extension identity. */
@@ -65,6 +129,37 @@ export declare const subagentsContract: {
65
129
  /** Return the server-owned human control step. */
66
130
  output: Schema.declare<import("@fungi.computer/whistle/catalog").WhistleCatalogStepV1, import("@fungi.computer/whistle/catalog").WhistleCatalogStepV1>;
67
131
  };
132
+ /** Answer a question an escalated sub-agent asks, from this conversation. */
133
+ answerChild: {
134
+ /** The Session owns its footer. */
135
+ scope: "session";
136
+ /** The next step of the child's answer conversation. */
137
+ input: Schema.Union<Schema.Struct<{
138
+ readonly agentId: Schema.String;
139
+ readonly sessionId: Schema.String;
140
+ readonly answer: Schema.Struct<{
141
+ readonly selected: Schema.$Array<Schema.String>;
142
+ readonly text: Schema.optionalKey<Schema.String>;
143
+ }>;
144
+ readonly askId: Schema.String;
145
+ readonly requestId: Schema.String;
146
+ readonly index: Schema.Number;
147
+ readonly answers: Schema.$Record<Schema.String, Schema.Struct<{
148
+ readonly selected: Schema.$Array<Schema.String>;
149
+ readonly text: Schema.optionalKey<Schema.String>;
150
+ }>>;
151
+ readonly stage: Schema.Literal<"decision">;
152
+ } | {
153
+ readonly agentId: Schema.String;
154
+ readonly sessionId: Schema.String;
155
+ readonly askId: Schema.String;
156
+ } | {
157
+ readonly agentId: Schema.String;
158
+ readonly sessionId: Schema.String;
159
+ }>[]>;
160
+ /** Return the next human conversation step. */
161
+ output: Schema.declare<import("@fungi.computer/whistle/catalog").WhistleCatalogStepV1, import("@fungi.computer/whistle/catalog").WhistleCatalogStepV1>;
162
+ };
68
163
  };
69
164
  /** Footer rows and expansion state. */
70
165
  readonly projection: {
@@ -79,9 +174,27 @@ export declare const subagentsContract: {
79
174
  /** Its latest conversation; null when it has none. */
80
175
  readonly sessionId: Schema.NullOr<Schema.String>;
81
176
  /** Observed conversation state, including unreadable status. */
82
- readonly state: Schema.Literals<readonly ["working", "waiting", "idle", "failed", "unavailable"]>;
83
- /** The child's own summary of what waits on the human, when waiting. */
177
+ readonly state: Schema.Literals<readonly ["working", "waiting_on_agent", "needs_you", "idle", "failed", "unavailable"]>;
178
+ /** The child's own summary of what it waits on, when waiting. */
84
179
  readonly attention: Schema.optionalKey<Schema.String>;
180
+ /** Display name of the Agent a `waiting_on_agent` child waits on. */
181
+ readonly waitingOn: Schema.optionalKey<Schema.String>;
182
+ /** The question a waiting child asks, so a person can answer from here. */
183
+ readonly ask: Schema.optionalKey<Schema.Struct<{
184
+ /** Durable identity of the child's open Ask. */
185
+ readonly askId: Schema.String;
186
+ /** The decisions the child asks, so they can be answered from here. */
187
+ readonly questions: Schema.$Array<Schema.Struct<{
188
+ readonly id: Schema.String;
189
+ readonly header: Schema.String;
190
+ readonly question: Schema.String;
191
+ readonly options: Schema.$Array<Schema.Struct<{
192
+ readonly label: Schema.String;
193
+ readonly description: Schema.String;
194
+ }>>;
195
+ readonly multiple: Schema.optionalKey<Schema.Boolean>;
196
+ }>>;
197
+ }>>;
85
198
  }>>;
86
199
  /** Additional created Agents not included in the visible rows. */
87
200
  readonly more: Schema.Int;
@@ -1,17 +1,20 @@
1
1
  import { ExtensionId, } from "@fungi.computer/shiitake/extensions";
2
2
  import * as Schema from "effect/Schema";
3
3
  import { CatalogStep } from "../internal/catalog.js";
4
+ import { AnswerFlowInput, Questions } from "../question/contract.js";
4
5
  const identity = Schema.String.check(Schema.isMinLength(1), Schema.isMaxLength(256));
5
6
  /** The most Agents one conversation's footer lists; the rest are counted. */
6
7
  export const maximumSubagents = 16;
7
8
  /**
8
- * What the human sees for one ordinary created Agent. `waiting` is any open attention on
9
- * its conversation (a question or a tool approval); `unavailable` means its
10
- * status could not be read, and is never shown as idle.
9
+ * What the human sees for one ordinary created Agent. `waiting_on_agent` is an
10
+ * open question that waits on the Agent that sent the work, calm until it is
11
+ * escalated; `needs_you` is attention that waits on a person; `unavailable`
12
+ * means its status could not be read, and is never shown as idle.
11
13
  */
12
14
  export const SubagentState = Schema.Literals([
13
15
  "working",
14
- "waiting",
16
+ "waiting_on_agent",
17
+ "needs_you",
15
18
  "idle",
16
19
  "failed",
17
20
  "unavailable",
@@ -26,8 +29,17 @@ export const Subagent = Schema.Struct({
26
29
  sessionId: Schema.NullOr(identity),
27
30
  /** Observed conversation state, including unreadable status. */
28
31
  state: SubagentState,
29
- /** The child's own summary of what waits on the human, when waiting. */
32
+ /** The child's own summary of what it waits on, when waiting. */
30
33
  attention: Schema.optionalKey(Schema.String.check(Schema.isMaxLength(200))),
34
+ /** Display name of the Agent a `waiting_on_agent` child waits on. */
35
+ waitingOn: Schema.optionalKey(identity),
36
+ /** The question a waiting child asks, so a person can answer from here. */
37
+ ask: Schema.optionalKey(Schema.Struct({
38
+ /** Durable identity of the child's open Ask. */
39
+ askId: identity,
40
+ /** The decisions the child asks, so they can be answered from here. */
41
+ questions: Questions,
42
+ })),
31
43
  });
32
44
  /**
33
45
  * This Agent's created Agents and how many are not listed.
@@ -42,6 +54,18 @@ export const SubagentsProjection = Schema.Struct({
42
54
  /** Whether individual rows are expanded instead of summarized. */
43
55
  expanded: Schema.Boolean,
44
56
  });
57
+ /** One child, as an answer step names it beside the draft it advances. */
58
+ const child = {
59
+ /** Identity of the child Agent whose Ask is being answered. */
60
+ agentId: identity,
61
+ /** The child's conversation that holds the Ask. */
62
+ sessionId: identity,
63
+ };
64
+ /**
65
+ * The question flow's own steps, each naming the child whose Ask they answer.
66
+ * The footer owns the conversation; the child's question owner commits it.
67
+ */
68
+ export const AnswerChildInput = Schema.Union(AnswerFlowInput.members.map((member) => Schema.Struct({ ...member.fields, ...child })));
45
69
  /** The human `/agents` command: a bare call expands or collapses the footer rows. */
46
70
  const commands = {
47
71
  /** Toggle the created-Agent footer between expanded and summarized views. */
@@ -53,6 +77,15 @@ const commands = {
53
77
  /** Return the server-owned human control step. */
54
78
  output: CatalogStep,
55
79
  },
80
+ /** Answer a question an escalated sub-agent asks, from this conversation. */
81
+ answerChild: {
82
+ /** The Session owns its footer. */
83
+ scope: "session",
84
+ /** The next step of the child's answer conversation. */
85
+ input: AnswerChildInput,
86
+ /** Return the next human conversation step. */
87
+ output: CatalogStep,
88
+ },
56
89
  };
57
90
  /** Created-Agent footer control and its visible projection. */
58
91
  export const subagentsContract = {
@@ -14,7 +14,7 @@ export type SubagentAgent = Readonly<{
14
14
  /** Read the child's owner-ordered conversation summaries. */
15
15
  listSessions: ShiitakeAgent["listSessions"];
16
16
  /** Open the read-only view of a selected child conversation. */
17
- session: (sessionId: string) => Pick<ShiitakeSession, "read" | "readSettlement">;
17
+ session: (sessionId: string) => Pick<ShiitakeSession, "read" | "readSettlement" | "extension">;
18
18
  }>;
19
19
  /**
20
20
  * The host's read-only facts. `read` returns ordinary created Agents,
@@ -44,10 +44,8 @@ export type SubagentSource = Readonly<{
44
44
  export type SubagentTiming = Readonly<{
45
45
  /** A watcher's read refreshes rows at least this old. */
46
46
  refreshMs: number;
47
- /** While a row is working or waiting, rows refresh this often. */
47
+ /** While a live view watches a working or waiting row, refresh this often. */
48
48
  pollMs: number;
49
- /** Polling stops once no watcher has read the Session this long. */
50
- interestMs: number;
51
49
  }>;
52
50
  /** Host admission for the read-only created-Agent footer and human toggle. */
53
51
  export declare const subagentsGrant: ExtensionGrantV0;