@fungi.computer/shiitake-extensions 0.3.0 → 0.5.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.
Files changed (39) hide show
  1. package/README.md +20 -16
  2. package/dist/goal/contract.d.ts +6 -0
  3. package/dist/goal/contract.js +6 -0
  4. package/dist/goal/host.js +183 -31
  5. package/dist/loop/contract.d.ts +136 -2
  6. package/dist/loop/contract.js +45 -2
  7. package/dist/loop/host.js +23 -0
  8. package/dist/question/answer-flow.d.ts +27 -0
  9. package/dist/question/answer-flow.js +115 -0
  10. package/dist/question/client.d.ts +1 -1
  11. package/dist/question/client.js +22 -3
  12. package/dist/question/contract.d.ts +202 -60
  13. package/dist/question/contract.js +78 -22
  14. package/dist/question/host.d.ts +4 -0
  15. package/dist/question/host.js +348 -339
  16. package/dist/roster/conversation-agents.d.ts +21 -1
  17. package/dist/roster/conversation-agents.js +47 -2
  18. package/dist/roster.d.ts +5 -2
  19. package/dist/roster.js +16 -5
  20. package/dist/schedules.d.ts +178 -0
  21. package/dist/schedules.js +182 -0
  22. package/dist/subagents/client.js +64 -20
  23. package/dist/subagents/contract.d.ts +123 -10
  24. package/dist/subagents/contract.js +38 -5
  25. package/dist/subagents/host.d.ts +2 -4
  26. package/dist/subagents/host.js +152 -22
  27. package/docs/api/README.md +1 -0
  28. package/docs/api/goal/contract.md +1 -1
  29. package/docs/api/goal/host.md +22 -1
  30. package/docs/api/loop/contract.md +43 -31
  31. package/docs/api/question/client.md +2 -1
  32. package/docs/api/question/contract.md +183 -51
  33. package/docs/api/question/host.md +30 -0
  34. package/docs/api/roster/conversation-agents.md +34 -8
  35. package/docs/api/roster.md +39 -2
  36. package/docs/api/schedules.md +481 -0
  37. package/docs/api/subagents/contract.md +101 -28
  38. package/docs/api/subagents/host.md +5 -3
  39. package/package.json +9 -6
package/dist/loop/host.js CHANGED
@@ -281,6 +281,13 @@ function fire(ctx, sessionId, dueAt, now) {
281
281
  delivery: "followUp",
282
282
  },
283
283
  }));
284
+ // The Agent owner will pause this loop after flipping its archive flag.
285
+ // If this timer wins that short interval, keep the loop resumable instead
286
+ // of treating an intentional archive refusal as a permanent failed run.
287
+ if (Result.isFailure(admitted) && admitted.failure.reason === "archived") {
288
+ yield* arm(ctx, { ...loop, lastError: admitted.failure.message }, now + 30_000);
289
+ return yield* ctx.observe(sessionId);
290
+ }
284
291
  if (Result.isFailure(admitted) || admitted.success._tag === "NotReady") {
285
292
  yield* commitLoop(ctx, {
286
293
  ...loop,
@@ -493,6 +500,14 @@ function stopLoop(ctx, sessionId, reason) {
493
500
  return projected(stopped);
494
501
  });
495
502
  }
503
+ /** Match and mutate within the same extension command transaction. */
504
+ function controlIdentifiedLoop(ctx, target, action) {
505
+ return requireLoop(ctx, target.sessionId).pipe(Effect.flatMap((loop) => loop.loopId !== target.loopId
506
+ ? Effect.fail(conflict("Session loop has been replaced"))
507
+ : action === "pause"
508
+ ? pauseLoop(ctx, target.sessionId)
509
+ : stopLoop(ctx, target.sessionId)));
510
+ }
496
511
  function cronForInterval(value, unit) {
497
512
  if (unit === "m" || unit === "min")
498
513
  return value > 59 ? undefined : `0 */${String(value)} * * * *`;
@@ -662,6 +677,14 @@ export const loopExtension = defineExtension({
662
677
  commands: {
663
678
  specs: loopContract.commands,
664
679
  handlers: (ctx) => ({
680
+ pauseLoop: ({ input }) => controlIdentifiedLoop(ctx, input, "pause"),
681
+ stopLoop: ({ input }) => controlIdentifiedLoop(ctx, input, "stop"),
682
+ list: ({ input }) => ctx.state.loops
683
+ .page({ after: input.after, limit: input.limit ?? 64 })
684
+ .pipe(Effect.map((rows) => rows.map((row) => ({
685
+ ...projected(row),
686
+ sessionId: row.sessionId,
687
+ })))),
665
688
  startSelfPaced: ({ sessionId, input }) => start(ctx, sessionId, input, null),
666
689
  startFixedInterval: ({ sessionId, input }) => start(ctx, sessionId, input, {
667
690
  expression: input.expression,
@@ -0,0 +1,27 @@
1
+ import type { WhistleCatalogStepV1 } from "@fungi.computer/whistle/catalog";
2
+ import { ExtensionCommandError } from "@fungi.computer/shiitake/extensions";
3
+ import * as Effect from "effect/Effect";
4
+ import { type Answers, type AnswerFlowInput, type Question } from "./contract.js";
5
+ import type * as Schema from "effect/Schema";
6
+ /** Drafts travel in catalog arguments; only an owner's answer commits them. */
7
+ export type FlowInput = Schema.Schema.Type<typeof AnswerFlowInput>;
8
+ /** What the answer conversation does next for one open Ask. */
9
+ export type AnswerFlowOutcome = Readonly<{
10
+ type: "step";
11
+ step: WhistleCatalogStepV1;
12
+ }> | Readonly<{
13
+ type: "complete";
14
+ askId: string;
15
+ requestId: string;
16
+ answers: Answers;
17
+ }>;
18
+ /**
19
+ * Progress one Ask's answer draft by a single human step. The owner of the
20
+ * Ask commits a completed set; a step only ever returns the next prompt, so
21
+ * the same conversation serves the Ask's own Session and a parent's footer.
22
+ */
23
+ export declare function advanceAnswerFlow(row: Readonly<{
24
+ askId: string;
25
+ questions: readonly Question[];
26
+ closed: boolean;
27
+ }>, input: FlowInput): Effect.Effect<AnswerFlowOutcome, ExtensionCommandError>;
@@ -0,0 +1,115 @@
1
+ import { ExtensionCommandError } from "@fungi.computer/shiitake/extensions";
2
+ import * as Effect from "effect/Effect";
3
+ import { ask, done, freshRequestId } from "../internal/catalog.js";
4
+ import { titleLimit } from "../internal/headline.js";
5
+ import {} from "./contract.js";
6
+ /** Accept only picks the question offered, and a non-empty answer. */
7
+ function decisionAnswer(question, answer) {
8
+ const offered = new Set(question.options.map((option) => option.label));
9
+ const written = answer.text?.trim() ?? "";
10
+ if (answer.selected.some((label) => !offered.has(label)) ||
11
+ new Set(answer.selected).size !== answer.selected.length ||
12
+ (question.multiple !== true && answer.selected.length > 1) ||
13
+ (answer.selected.length === 0 && written === ""))
14
+ return Effect.fail(new ExtensionCommandError({
15
+ reason: "invalid",
16
+ message: "Choose offered options or write an answer",
17
+ }));
18
+ return Effect.succeed(written === ""
19
+ ? { selected: answer.selected }
20
+ : { selected: answer.selected, text: answer.text ?? "" });
21
+ }
22
+ /**
23
+ * The Agent's own words are the title only when they fit one; a longer
24
+ * question gets a plain title and sits in the body, whole in the details.
25
+ */
26
+ function questionHeading(question) {
27
+ const text = question.question.replace(/\s+/gu, " ").trim();
28
+ if (text.length <= titleLimit)
29
+ return { message: text };
30
+ return {
31
+ message: "The Agent has a question",
32
+ body: question.question.slice(0, 4_000),
33
+ bodyLabel: "Question",
34
+ details: [{ label: "Question", value: question.question.slice(0, 4_000) }],
35
+ };
36
+ }
37
+ function promptQuestion(nextQuestion, next, total) {
38
+ const base = {
39
+ type: "decision",
40
+ ...questionHeading(nextQuestion),
41
+ options: nextQuestion.options.map((option) => ({
42
+ value: option.label,
43
+ label: option.label,
44
+ description: option.description,
45
+ })),
46
+ other: {
47
+ label: "Other…",
48
+ placeholder: "Write your own answer",
49
+ maxLength: 4_000,
50
+ },
51
+ };
52
+ // Only present fields travel: the wire is JSON, which has no undefined.
53
+ const numbered = total > 1
54
+ ? {
55
+ ...base,
56
+ summary: `Question ${String(next.index + 1)} of ${String(total)}`,
57
+ }
58
+ : base;
59
+ return ask(nextQuestion.multiple === true ? { ...numbered, multiple: true } : numbered, { stage: "decision", ...next }, "answer");
60
+ }
61
+ /**
62
+ * Progress one Ask's answer draft by a single human step. The owner of the
63
+ * Ask commits a completed set; a step only ever returns the next prompt, so
64
+ * the same conversation serves the Ask's own Session and a parent's footer.
65
+ */
66
+ export function advanceAnswerFlow(row, input) {
67
+ return Effect.gen(function* () {
68
+ const draft = "stage" in input
69
+ ? input
70
+ : {
71
+ stage: "start",
72
+ index: 0,
73
+ requestId: yield* freshRequestId,
74
+ answers: {},
75
+ };
76
+ let { index, answers } = draft;
77
+ const { requestId } = draft;
78
+ const question = row.questions[index];
79
+ if (question === undefined)
80
+ return yield* Effect.fail(new ExtensionCommandError({
81
+ reason: "invalid",
82
+ message: "Unknown question step",
83
+ }));
84
+ const answer = draft.stage === "decision"
85
+ ? yield* decisionAnswer(question, draft.answer)
86
+ : undefined;
87
+ if (answer !== undefined) {
88
+ answers = { ...answers, [question.id]: answer };
89
+ index += 1;
90
+ if (index === row.questions.length)
91
+ return {
92
+ type: "complete",
93
+ askId: row.askId,
94
+ requestId,
95
+ answers,
96
+ };
97
+ }
98
+ if (row.closed)
99
+ return {
100
+ type: "step",
101
+ step: done({
102
+ type: "info",
103
+ message: "That question is already closed.",
104
+ }),
105
+ };
106
+ const next = { askId: row.askId, requestId, index, answers };
107
+ const nextQuestion = row.questions[index];
108
+ if (nextQuestion === undefined)
109
+ throw new Error("Expected the next question");
110
+ return {
111
+ type: "step",
112
+ step: promptQuestion(nextQuestion, next, row.questions.length),
113
+ };
114
+ });
115
+ }
@@ -1,5 +1,5 @@
1
1
  import { type ClientContributionV1 } from "@fungi.computer/shiitake/client-entry";
2
- /** Pending decisions use the existing footer rows and advertised answer flow. */
2
+ /** Pending decisions take over the composer; the footer row is where a postponed one waits. */
3
3
  declare const _default: Readonly<{
4
4
  protocol: "shiitake.client/v1";
5
5
  id: import("@fungi.computer/shiitake/extensions").ExtensionId;
@@ -1,6 +1,6 @@
1
1
  import { defineClientEntry, } from "@fungi.computer/shiitake/client-entry";
2
2
  import { questionContract } from "./contract.js";
3
- /** Pending decisions use the existing footer rows and advertised answer flow. */
3
+ /** Pending decisions take over the composer; the footer row is where a postponed one waits. */
4
4
  export default defineClientEntry(questionContract, {
5
5
  contribute: (view) => ({
6
6
  chips: [],
@@ -8,16 +8,35 @@ export default defineClientEntry(questionContract, {
8
8
  ? []
9
9
  : view.value.asks.map((ask) => {
10
10
  const first = ask.questions[0];
11
+ const title = `${first?.question ?? "Question"}${ask.questions.length > 1 ? ` (+${String(ask.questions.length - 1)} more)` : ""}`;
12
+ const detail = first?.options.map((option) => option.label).join(" · ") ?? "";
13
+ // An ask waiting on an Agent is calm: it never takes over the
14
+ // composer, but a person may still answer it first.
15
+ if (ask.waitingOn !== undefined)
16
+ return {
17
+ id: ask.askId,
18
+ title: `waiting on ${ask.waitingOn.name} · ${title}`,
19
+ detail,
20
+ tone: "muted",
21
+ actions: [
22
+ {
23
+ id: "answerFlow",
24
+ label: "Answer",
25
+ input: { askId: ask.askId },
26
+ },
27
+ ],
28
+ };
11
29
  return {
12
30
  id: ask.askId,
13
- title: `${first?.question ?? "Question"}${ask.questions.length > 1 ? ` (+${String(ask.questions.length - 1)} more)` : ""}`,
14
- detail: first?.options.map((option) => option.label).join(" · ") ?? "",
31
+ title,
32
+ detail,
15
33
  tone: "attention",
16
34
  actions: [
17
35
  {
18
36
  id: "answerFlow",
19
37
  label: "Answer",
20
38
  input: { askId: ask.askId },
39
+ awaiting: "question",
21
40
  },
22
41
  ],
23
42
  };
@@ -41,6 +41,16 @@ export type Question = Schema.Schema.Type<typeof Question>;
41
41
  export declare const OpenAsk: Schema.Struct<{
42
42
  /** Durable identity of the open ask. */
43
43
  readonly askId: Schema.String;
44
+ /**
45
+ * The Agent this ask waits on first, derived by the host from whom the
46
+ * asking run's work came from. Absent means a person. An ask waiting on an
47
+ * Agent is calm: the Agent answers it or escalates it to a person.
48
+ */
49
+ readonly waitingOn: Schema.optionalKey<Schema.Struct<{
50
+ readonly agentId: Schema.NonEmptyString;
51
+ readonly sessionId: Schema.NonEmptyString;
52
+ readonly name: Schema.NonEmptyString;
53
+ }>>;
44
54
  /** Decisions awaiting the person's answers. */
45
55
  readonly questions: Schema.$Array<Schema.Struct<{
46
56
  /** Identity used to associate this question with its answer. */
@@ -74,6 +84,16 @@ export declare const AskProjection: Schema.Struct<{
74
84
  readonly asks: Schema.$Array<Schema.Struct<{
75
85
  /** Durable identity of the open ask. */
76
86
  readonly askId: Schema.String;
87
+ /**
88
+ * The Agent this ask waits on first, derived by the host from whom the
89
+ * asking run's work came from. Absent means a person. An ask waiting on an
90
+ * Agent is calm: the Agent answers it or escalates it to a person.
91
+ */
92
+ readonly waitingOn: Schema.optionalKey<Schema.Struct<{
93
+ readonly agentId: Schema.NonEmptyString;
94
+ readonly sessionId: Schema.NonEmptyString;
95
+ readonly name: Schema.NonEmptyString;
96
+ }>>;
77
97
  /** Decisions awaiting the person's answers. */
78
98
  readonly questions: Schema.$Array<Schema.Struct<{
79
99
  /** Identity used to associate this question with its answer. */
@@ -146,6 +166,8 @@ export declare const HumanResult: Schema.Union<readonly [Schema.Struct<{
146
166
  readonly result: Schema.Literal<"accepted">;
147
167
  /** Whether the reply resumed its turn or became a follow-up. */
148
168
  readonly via: Schema.Literals<readonly ["in_turn", "follow_up"]>;
169
+ /** The follow-up command carrying the reply, when it became one. */
170
+ readonly commandId: Schema.optionalKey<Schema.String>;
149
171
  }>, Schema.Struct<{
150
172
  /** Ask had already reached a terminal state. */
151
173
  readonly result: Schema.Literal<"closed">;
@@ -157,40 +179,80 @@ export declare const HumanResult: Schema.Union<readonly [Schema.Struct<{
157
179
  }>]>;
158
180
  /** Decoded outcome of a human answer or decline. */
159
181
  export type HumanResult = Schema.Schema.Type<typeof HumanResult>;
160
- /** The ordinary /answer conversation carries its draft between typed steps. */
161
- export declare const AnswerFlowInput: Schema.Union<readonly [Schema.Struct<{
162
- /** Encoded selection supplied by the human control. */
163
- readonly selection: Schema.String;
164
- readonly askId: Schema.String;
165
- readonly requestId: Schema.String;
166
- readonly index: Schema.Number;
167
- readonly answers: Schema.$Record<Schema.String, Schema.Struct<{
168
- /** Labels of the offered choices selected by the person. */
169
- readonly selected: Schema.$Array<Schema.String>;
170
- /** Optional free-text answer supplied by the person. */
171
- readonly text: Schema.optionalKey<Schema.String>;
172
- }>>;
173
- /** Multiple-choice selection step. */
174
- readonly stage: Schema.Literal<"multiple">;
182
+ /** Only the Agent an Ask waits on may answer or escalate it. */
183
+ export declare const SenderRefusal: Schema.Struct<{
184
+ /** The caller is not the Agent this Ask waits on. */
185
+ readonly result: Schema.Literal<"refused">;
186
+ /** Explains why the Agent's response cannot be applied. */
187
+ readonly reason: Schema.Literal<"not_sender">;
188
+ }>;
189
+ /** Outcome of the Agent an Ask waits on answering it. */
190
+ export declare const AgentAnswerResult: Schema.Union<readonly [Schema.Union<readonly [Schema.Struct<{
191
+ /** Reply was accepted. */
192
+ readonly result: Schema.Literal<"accepted">;
193
+ /** Whether the reply resumed its turn or became a follow-up. */
194
+ readonly via: Schema.Literals<readonly ["in_turn", "follow_up"]>;
195
+ /** The follow-up command carrying the reply, when it became one. */
196
+ readonly commandId: Schema.optionalKey<Schema.String>;
197
+ }>, Schema.Struct<{
198
+ /** Ask had already reached a terminal state. */
199
+ readonly result: Schema.Literal<"closed">;
200
+ /** Terminal state that prevented another answer. */
201
+ readonly status: Schema.Literals<readonly ["answered", "declined", "withdrawn", "expired", "cancelled", "undeliverable"]>;
175
202
  }>, Schema.Struct<{
176
- /** Encoded selection supplied by the human control. */
177
- readonly selection: Schema.String;
203
+ /** No ask with this identity exists. */
204
+ readonly result: Schema.Literal<"not_found">;
205
+ }>]>, Schema.Struct<{
206
+ /** The caller is not the Agent this Ask waits on. */
207
+ readonly result: Schema.Literal<"refused">;
208
+ /** Explains why the Agent's response cannot be applied. */
209
+ readonly reason: Schema.Literal<"not_sender">;
210
+ }>]>;
211
+ /** Decoded result of an authenticated Agent answer. */
212
+ export type AgentAnswerResult = Schema.Schema.Type<typeof AgentAnswerResult>;
213
+ /** The Agent that answered, as the host authenticated it. */
214
+ export declare const AnsweredBy: Schema.Struct<{
215
+ /** Identifies an Agent, never a person. */
216
+ readonly kind: Schema.Literal<"agent">;
217
+ /** Stable identity of the authenticated Agent. */
218
+ readonly agentId: Schema.String;
219
+ }>;
220
+ /** Outcome of the Agent an Ask waits on passing it on. */
221
+ export declare const EscalateResult: Schema.Union<readonly [Schema.Struct<{
222
+ /** The Ask now waits on someone else. */
223
+ readonly result: Schema.Literal<"escalated">;
224
+ /** Whom it waits on now: a person. */
225
+ readonly to: Schema.Literal<"person">;
226
+ }>, Schema.Struct<{
227
+ /** Ask had already reached a terminal state. */
228
+ readonly result: Schema.Literal<"closed">;
229
+ /** Terminal state that prevented the escalation. */
230
+ readonly status: Schema.Literals<readonly ["answered", "declined", "withdrawn", "expired", "cancelled", "undeliverable"]>;
231
+ }>, Schema.Struct<{
232
+ /** No ask with this identity exists. */
233
+ readonly result: Schema.Literal<"not_found">;
234
+ }>, Schema.Struct<{
235
+ /** The caller is not the Agent this Ask waits on. */
236
+ readonly result: Schema.Literal<"refused">;
237
+ /** Explains why the Agent's response cannot be applied. */
238
+ readonly reason: Schema.Literal<"not_sender">;
239
+ }>]>;
240
+ /** Decoded result of passing an Ask on. */
241
+ export type EscalateResult = Schema.Schema.Type<typeof EscalateResult>;
242
+ /** The Agent's request to pass an Ask on; the caller is host context. */
243
+ export declare const EscalateInput: Schema.Struct<{
244
+ /** Ask being passed on. */
178
245
  readonly askId: Schema.String;
179
- readonly requestId: Schema.String;
180
- readonly index: Schema.Number;
181
- readonly answers: Schema.$Record<Schema.String, Schema.Struct<{
246
+ }>;
247
+ /** The answer conversation carries its draft between decision steps. */
248
+ export declare const AnswerFlowInput: Schema.Union<readonly [Schema.Struct<{
249
+ /** The person's picks and optional written answer for question `index`. */
250
+ readonly answer: Schema.Struct<{
182
251
  /** Labels of the offered choices selected by the person. */
183
252
  readonly selected: Schema.$Array<Schema.String>;
184
253
  /** Optional free-text answer supplied by the person. */
185
254
  readonly text: Schema.optionalKey<Schema.String>;
186
- }>>;
187
- /** Single-choice selection step. */
188
- readonly stage: Schema.Literal<"selection">;
189
- }>, Schema.Struct<{
190
- /** Offered choices retained while entering free text. */
191
- readonly selected: Schema.$Array<Schema.String>;
192
- /** Free-text answer supplied by the person. */
193
- readonly text: Schema.String;
255
+ }>;
194
256
  readonly askId: Schema.String;
195
257
  readonly requestId: Schema.String;
196
258
  readonly index: Schema.Number;
@@ -200,8 +262,8 @@ export declare const AnswerFlowInput: Schema.Union<readonly [Schema.Struct<{
200
262
  /** Optional free-text answer supplied by the person. */
201
263
  readonly text: Schema.optionalKey<Schema.String>;
202
264
  }>>;
203
- /** Free-text answer step. */
204
- readonly stage: Schema.Literal<"text">;
265
+ /** One decision answered in the composer. */
266
+ readonly stage: Schema.Literal<"decision">;
205
267
  }>, Schema.Struct<{
206
268
  /** Ask whose answer conversation should be opened. */
207
269
  readonly askId: Schema.String;
@@ -218,38 +280,13 @@ export declare const questionContract: {
218
280
  readonly scope: "session";
219
281
  /** Decode the current answer-draft step. */
220
282
  readonly input: Schema.Union<readonly [Schema.Struct<{
221
- /** Encoded selection supplied by the human control. */
222
- readonly selection: Schema.String;
223
- readonly askId: Schema.String;
224
- readonly requestId: Schema.String;
225
- readonly index: Schema.Number;
226
- readonly answers: Schema.$Record<Schema.String, Schema.Struct<{
283
+ /** The person's picks and optional written answer for question `index`. */
284
+ readonly answer: Schema.Struct<{
227
285
  /** Labels of the offered choices selected by the person. */
228
286
  readonly selected: Schema.$Array<Schema.String>;
229
287
  /** Optional free-text answer supplied by the person. */
230
288
  readonly text: Schema.optionalKey<Schema.String>;
231
- }>>;
232
- /** Multiple-choice selection step. */
233
- readonly stage: Schema.Literal<"multiple">;
234
- }>, Schema.Struct<{
235
- /** Encoded selection supplied by the human control. */
236
- readonly selection: Schema.String;
237
- readonly askId: Schema.String;
238
- readonly requestId: Schema.String;
239
- readonly index: Schema.Number;
240
- readonly answers: Schema.$Record<Schema.String, Schema.Struct<{
241
- /** Labels of the offered choices selected by the person. */
242
- readonly selected: Schema.$Array<Schema.String>;
243
- /** Optional free-text answer supplied by the person. */
244
- readonly text: Schema.optionalKey<Schema.String>;
245
- }>>;
246
- /** Single-choice selection step. */
247
- readonly stage: Schema.Literal<"selection">;
248
- }>, Schema.Struct<{
249
- /** Offered choices retained while entering free text. */
250
- readonly selected: Schema.$Array<Schema.String>;
251
- /** Free-text answer supplied by the person. */
252
- readonly text: Schema.String;
289
+ }>;
253
290
  readonly askId: Schema.String;
254
291
  readonly requestId: Schema.String;
255
292
  readonly index: Schema.Number;
@@ -259,8 +296,8 @@ export declare const questionContract: {
259
296
  /** Optional free-text answer supplied by the person. */
260
297
  readonly text: Schema.optionalKey<Schema.String>;
261
298
  }>>;
262
- /** Free-text answer step. */
263
- readonly stage: Schema.Literal<"text">;
299
+ /** One decision answered in the composer. */
300
+ readonly stage: Schema.Literal<"decision">;
264
301
  }>, Schema.Struct<{
265
302
  /** Ask whose answer conversation should be opened. */
266
303
  readonly askId: Schema.String;
@@ -292,6 +329,8 @@ export declare const questionContract: {
292
329
  readonly result: Schema.Literal<"accepted">;
293
330
  /** Whether the reply resumed its turn or became a follow-up. */
294
331
  readonly via: Schema.Literals<readonly ["in_turn", "follow_up"]>;
332
+ /** The follow-up command carrying the reply, when it became one. */
333
+ readonly commandId: Schema.optionalKey<Schema.String>;
295
334
  }>, Schema.Struct<{
296
335
  /** Ask had already reached a terminal state. */
297
336
  readonly result: Schema.Literal<"closed">;
@@ -302,6 +341,87 @@ export declare const questionContract: {
302
341
  readonly result: Schema.Literal<"not_found">;
303
342
  }>]>;
304
343
  };
344
+ /** Submit the answer of the Agent this Ask waits on; never a human answer. */
345
+ readonly answerAgent: {
346
+ /** The child Session owns the Ask; caller authority is host context. */
347
+ readonly scope: "session";
348
+ /** Decode the answer submission without any caller-supplied actor. */
349
+ readonly input: Schema.Struct<{
350
+ /** Ask being answered. */
351
+ readonly askId: Schema.String;
352
+ /** Stable identity shared by this command and its retries. */
353
+ readonly requestId: Schema.String;
354
+ /** Answers to the ask's question set. */
355
+ readonly answers: Schema.$Record<Schema.String, Schema.Struct<{
356
+ /** Labels of the offered choices selected by the person. */
357
+ readonly selected: Schema.$Array<Schema.String>;
358
+ /** Optional free-text answer supplied by the person. */
359
+ readonly text: Schema.optionalKey<Schema.String>;
360
+ }>>;
361
+ }>;
362
+ /** Attribute the accepted answer to its authenticated Agent. */
363
+ readonly output: Schema.Struct<{
364
+ /** Whether the answer was accepted, or why not. */
365
+ readonly result: Schema.Union<readonly [Schema.Union<readonly [Schema.Struct<{
366
+ /** Reply was accepted. */
367
+ readonly result: Schema.Literal<"accepted">;
368
+ /** Whether the reply resumed its turn or became a follow-up. */
369
+ readonly via: Schema.Literals<readonly ["in_turn", "follow_up"]>;
370
+ /** The follow-up command carrying the reply, when it became one. */
371
+ readonly commandId: Schema.optionalKey<Schema.String>;
372
+ }>, Schema.Struct<{
373
+ /** Ask had already reached a terminal state. */
374
+ readonly result: Schema.Literal<"closed">;
375
+ /** Terminal state that prevented another answer. */
376
+ readonly status: Schema.Literals<readonly ["answered", "declined", "withdrawn", "expired", "cancelled", "undeliverable"]>;
377
+ }>, Schema.Struct<{
378
+ /** No ask with this identity exists. */
379
+ readonly result: Schema.Literal<"not_found">;
380
+ }>]>, Schema.Struct<{
381
+ /** The caller is not the Agent this Ask waits on. */
382
+ readonly result: Schema.Literal<"refused">;
383
+ /** Explains why the Agent's response cannot be applied. */
384
+ readonly reason: Schema.Literal<"not_sender">;
385
+ }>]>;
386
+ /** The authenticated Agent that answered. */
387
+ readonly answeredBy: Schema.Struct<{
388
+ /** Identifies an Agent, never a person. */
389
+ readonly kind: Schema.Literal<"agent">;
390
+ /** Stable identity of the authenticated Agent. */
391
+ readonly agentId: Schema.String;
392
+ }>;
393
+ }>;
394
+ };
395
+ /** Pass the Ask on from the Agent it waits on; never a human action. */
396
+ readonly escalateAgent: {
397
+ /** The child Session owns the Ask; caller authority is host context. */
398
+ readonly scope: "session";
399
+ /** Decode the escalation without any caller-supplied recipient. */
400
+ readonly input: Schema.Struct<{
401
+ /** Ask being passed on. */
402
+ readonly askId: Schema.String;
403
+ }>;
404
+ /** Where the Ask waits now, or why it did not move. */
405
+ readonly output: Schema.Union<readonly [Schema.Struct<{
406
+ /** The Ask now waits on someone else. */
407
+ readonly result: Schema.Literal<"escalated">;
408
+ /** Whom it waits on now: a person. */
409
+ readonly to: Schema.Literal<"person">;
410
+ }>, Schema.Struct<{
411
+ /** Ask had already reached a terminal state. */
412
+ readonly result: Schema.Literal<"closed">;
413
+ /** Terminal state that prevented the escalation. */
414
+ readonly status: Schema.Literals<readonly ["answered", "declined", "withdrawn", "expired", "cancelled", "undeliverable"]>;
415
+ }>, Schema.Struct<{
416
+ /** No ask with this identity exists. */
417
+ readonly result: Schema.Literal<"not_found">;
418
+ }>, Schema.Struct<{
419
+ /** The caller is not the Agent this Ask waits on. */
420
+ readonly result: Schema.Literal<"refused">;
421
+ /** Explains why the Agent's response cannot be applied. */
422
+ readonly reason: Schema.Literal<"not_sender">;
423
+ }>]>;
424
+ };
305
425
  /** Decline an ask with an optional note. */
306
426
  readonly decline: {
307
427
  /** The Session owns this ask. */
@@ -321,6 +441,8 @@ export declare const questionContract: {
321
441
  readonly result: Schema.Literal<"accepted">;
322
442
  /** Whether the reply resumed its turn or became a follow-up. */
323
443
  readonly via: Schema.Literals<readonly ["in_turn", "follow_up"]>;
444
+ /** The follow-up command carrying the reply, when it became one. */
445
+ readonly commandId: Schema.optionalKey<Schema.String>;
324
446
  }>, Schema.Struct<{
325
447
  /** Ask had already reached a terminal state. */
326
448
  readonly result: Schema.Literal<"closed">;
@@ -343,6 +465,16 @@ export declare const questionContract: {
343
465
  readonly asks: Schema.$Array<Schema.Struct<{
344
466
  /** Durable identity of the open ask. */
345
467
  readonly askId: Schema.String;
468
+ /**
469
+ * The Agent this ask waits on first, derived by the host from whom the
470
+ * asking run's work came from. Absent means a person. An ask waiting on an
471
+ * Agent is calm: the Agent answers it or escalates it to a person.
472
+ */
473
+ readonly waitingOn: Schema.optionalKey<Schema.Struct<{
474
+ readonly agentId: Schema.NonEmptyString;
475
+ readonly sessionId: Schema.NonEmptyString;
476
+ readonly name: Schema.NonEmptyString;
477
+ }>>;
346
478
  /** Decisions awaiting the person's answers. */
347
479
  readonly questions: Schema.$Array<Schema.Struct<{
348
480
  /** Identity used to associate this question with its answer. */
@@ -379,6 +511,16 @@ export declare const questionContract: {
379
511
  readonly asks: Schema.$Array<Schema.Struct<{
380
512
  /** Durable identity of the open ask. */
381
513
  readonly askId: Schema.String;
514
+ /**
515
+ * The Agent this ask waits on first, derived by the host from whom the
516
+ * asking run's work came from. Absent means a person. An ask waiting on an
517
+ * Agent is calm: the Agent answers it or escalates it to a person.
518
+ */
519
+ readonly waitingOn: Schema.optionalKey<Schema.Struct<{
520
+ readonly agentId: Schema.NonEmptyString;
521
+ readonly sessionId: Schema.NonEmptyString;
522
+ readonly name: Schema.NonEmptyString;
523
+ }>>;
382
524
  /** Decisions awaiting the person's answers. */
383
525
  readonly questions: Schema.$Array<Schema.Struct<{
384
526
  /** Identity used to associate this question with its answer. */