@orkestrel/tool 0.0.1 → 0.0.2

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.
@@ -8,7 +8,9 @@ import { LiteralShape } from '@orkestrel/contract';
8
8
  import { NumberShape } from '@orkestrel/contract';
9
9
  import { ObjectShape } from '@orkestrel/contract';
10
10
  import { OptionalShape } from '@orkestrel/contract';
11
+ import { PromptType } from '@orkestrel/terminal';
11
12
  import { StringShape } from '@orkestrel/contract';
13
+ import { TerminalManagerInterface } from '@orkestrel/terminal';
12
14
  import { ToolInterface } from '@orkestrel/agent';
13
15
  import { ToolManagerInterface } from '@orkestrel/agent';
14
16
  import { UnionShape } from '@orkestrel/contract';
@@ -112,8 +114,11 @@ export declare interface AgentToolArguments {
112
114
  /**
113
115
  * Thrown by {@link import('./factories.js').createAgentTool}'s and
114
116
  * {@link import('./factories.js').createDescribeTool}'s handlers on every failure path — a
115
- * malformed / unresolvable call or an unknown tool name (`TOOL`), or a delegation that would
116
- * exceed the configured depth bound or re-enter an ancestor (`DEPTH`).
117
+ * malformed / unresolvable call or an unknown tool name (`TOOL`), a delegation that would
118
+ * exceed the configured depth bound or re-enter an ancestor (`DEPTH`), a prompt cycle
119
+ * (`DEADLOCK`), a prompt that expired before it was answered (`EXPIRE`), or an answer that
120
+ * failed to apply (`ANSWER`) — the last three thrown by
121
+ * {@link import('./factories.js').createPromptTool} / {@link import('./factories.js').createAnswerTool}.
117
122
  *
118
123
  * @remarks
119
124
  * Carries a machine-readable `code` (see {@link import('./types.js').AgentToolErrorCode}) and
@@ -148,8 +153,15 @@ export declare class AgentToolError extends Error {
148
153
  * `TOOL` — malformed / unresolvable call args (a missing `task`, no resolvable `provider`).
149
154
  * `DEPTH` — the delegation would exceed {@link import('./constants.js').AGENT_TOOL_DEPTH}, or
150
155
  * the resolved agent is already an ancestor (a cycle).
156
+ * `DEADLOCK` — an `ask` call ({@link import('./factories.js').createPromptTool}) would form a
157
+ * prompt cycle (`TerminalManagerInterface.ask`, `@orkestrel/terminal`, rejects with its own
158
+ * `TerminalError('DEADLOCK')`, re-surfaced here).
159
+ * `EXPIRE` — the addressed prompt expired before it was answered.
160
+ * `ANSWER` — {@link import('./factories.js').createAnswerTool}'s answer call failed to apply
161
+ * (an unknown prompt id, a rejected value, or the terminal itself unknown —
162
+ * `TerminalAnswerResult.error`, `@orkestrel/terminal`).
151
163
  */
152
- export declare type AgentToolErrorCode = 'TOOL' | 'DEPTH';
164
+ export declare type AgentToolErrorCode = 'TOOL' | 'DEPTH' | 'DEADLOCK' | 'EXPIRE' | 'ANSWER';
153
165
 
154
166
  /**
155
167
  * Options for {@link import('./factories.js').createAgentTool} — the sub-agent delegation
@@ -209,6 +221,86 @@ export declare const agentToolShape: ObjectShape<{
209
221
  system: OptionalShape<StringShape>;
210
222
  }>;
211
223
 
224
+ export declare const ANSWER_TOOL_DESCRIPTION: string;
225
+
226
+ /**
227
+ * The name {@link import('./factories.js').createAnswerTool} advertises by default — the key a
228
+ * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
229
+ */
230
+ export declare const ANSWER_TOOL_NAME = "answer";
231
+
232
+ /**
233
+ * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createAnswerTool}
234
+ * advertises in place of {@link ANSWER_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
235
+ * (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
236
+ * for the full teaching description; the full text stays retrievable via
237
+ * {@link import('./factories.js').createDescribeTool}.
238
+ */
239
+ export declare const ANSWER_TOOL_SUMMARY = "List prompts addressed to this terminal, or answer one by id. Call describe('answer') for the required fields.";
240
+
241
+ /**
242
+ * Options for {@link import('./factories.js').createAnswerTool} — the live
243
+ * {@link TerminalManagerInterface} (`@orkestrel/terminal`) to list / answer prompts through, the
244
+ * terminal name `to`, and the advertised tool overrides.
245
+ *
246
+ * @remarks
247
+ * - `manager` — the terminal manager whose `pending(to)` / `answer(to, id, value)` the tool's
248
+ * handler calls — `pending` lists the prompts currently addressed to `to`, `answer` resolves
249
+ * one by `id`. A failed `answer` (`TerminalAnswerResult.error`) re-surfaces as a typed
250
+ * `ANSWER` {@link import('./errors.js').AgentToolError}.
251
+ * - `to` — the terminal identity this tool lists / answers prompts FOR.
252
+ * - `name` / `description` — advertised tool overrides; default to
253
+ * {@link import('./constants.js').ANSWER_TOOL_NAME} / {@link import('./constants.js').ANSWER_TOOL_DESCRIPTION}.
254
+ */
255
+ export declare interface AnswerToolOptions {
256
+ readonly manager: TerminalManagerInterface;
257
+ readonly to: string;
258
+ readonly name?: string;
259
+ readonly description?: string;
260
+ }
261
+
262
+ /**
263
+ * The shape of {@link import('./factories.js').createAnswerTool}'s call arguments — discriminated
264
+ * by `operation`: `'pending'` lists the prompts addressed to this tool's terminal, `'answer'`
265
+ * resolves one by `id` with a `value`.
266
+ *
267
+ * @remarks
268
+ * `value`'s type varies by the ORIGINAL prompt's form (`string` for `'input'` / `'password'` /
269
+ * `'select'` / `'editor'`, `boolean` for `'confirm'`, `readonly string[]` for `'checkbox'`) —
270
+ * `unionShape(stringShape(), booleanShape(), arrayShape(stringShape()))` expresses that
271
+ * union directly, so `value` is typed as the full `string | boolean | readonly string[]` union
272
+ * here (no lossy string-only fallback needed).
273
+ */
274
+ export declare const answerToolShape: UnionShape<[ ObjectShape<{
275
+ operation: LiteralShape<readonly ["pending"]>;
276
+ }>, ObjectShape<{
277
+ operation: LiteralShape<readonly ["answer"]>;
278
+ id: StringShape;
279
+ value: UnionShape<[ StringShape, BooleanShape, ArrayShape<StringShape>]>;
280
+ }>]>;
281
+
282
+ /**
283
+ * Normalize an LLM-supplied answer `value` to the type {@link PromptType} `form` expects, so a
284
+ * caller that only ever emits strings can still answer a typed prompt.
285
+ *
286
+ * @remarks
287
+ * `'confirm'` coerces to a `boolean` — a `boolean` passes through, and the strings `'true'` /
288
+ * `'false'` (case-insensitively) map to it; any other string is truthy-coerced via
289
+ * `Boolean(value)`. `'checkbox'` coerces to `readonly string[]` — an array passes through
290
+ * (stringifying each entry), a comma-separated string splits + trims into one, and any other
291
+ * single (non-comma) string becomes a one-item array. Every other form (`'input'` / `'password'`
292
+ * / `'select'` / `'editor'`) coerces to a plain `string` — a string passes through verbatim; a
293
+ * non-string, non-object scalar (`number` / `boolean`) stringifies via `String(value)`; an
294
+ * object or array (no lossless string form) falls back to `''` rather than serializing garbage.
295
+ * Pure and total — never throws.
296
+ *
297
+ * @param form - The {@link PromptType} the answer is being coerced FOR
298
+ * @param value - The raw, LLM-supplied answer value
299
+ * @returns The coerced answer — `boolean` for `'confirm'`, `readonly string[]` for `'checkbox'`,
300
+ * `string` otherwise
301
+ */
302
+ export declare function coerceAnswer(form: PromptType, value: unknown): string | boolean | readonly string[];
303
+
212
304
  /**
213
305
  * Complete a {@link WorkflowDraft} into a strict {@link WorkflowDefinition} — synthesize any
214
306
  * MISSING `id` deterministically + positionally, and default any MISSING `name` to its
@@ -334,6 +426,45 @@ export declare function createAgentFunction(agent: AgentInterface, options?: Age
334
426
  */
335
427
  export declare function createAgentTool(registry: AgentRegistryInterface, options?: AgentToolOptions): ToolInterface;
336
428
 
429
+ /**
430
+ * Build an LLM-callable answer tool — the ANSWER side of the terminal seam. Lists the prompts
431
+ * currently addressed to {@link import('./types.js').AnswerToolOptions.to}, or answers one of
432
+ * them by id.
433
+ *
434
+ * @remarks
435
+ * The universal tool-handler contract (AGENTS §14): validates the call args against
436
+ * {@link import('./shapers.js').answerToolShape} (discriminated by `operation`). `'pending'`
437
+ * returns a compact list (`{ id, from, form, message }`) of every prompt currently addressed to
438
+ * `to` (`TerminalManagerInterface.pending`, `@orkestrel/terminal`). `'answer'` looks the prompt
439
+ * up by `id` (an unknown id throws a typed `ANSWER` {@link import('./errors.js').AgentToolError}),
440
+ * normalizes the model-supplied `value` to the prompt's own form
441
+ * ({@link import('./helpers.js').coerceAnswer}), and applies it via
442
+ * `TerminalManagerInterface.answer` — a rejected / unknown / unresolvable outcome
443
+ * (`TerminalAnswerResult.error`) re-surfaces as a typed `ANSWER` `AgentToolError`; success returns
444
+ * `{ answered: id }`. `to` is FIXED at construction
445
+ * ({@link import('./types.js').AnswerToolOptions.to}) — never read from the model-supplied args —
446
+ * so a model cannot spoof which terminal it is answering for. Concurrent answerers racing on one
447
+ * endpoint are FIRST-WRITE-WINS — a late answer to an already-settled prompt returns a typed
448
+ * `ANSWER` `AgentToolError` (surfaced as a 422 over HTTP).
449
+ *
450
+ * @param options - The live manager, the fixed `to` identity, and advertised overrides (see
451
+ * {@link import('./types.js').AnswerToolOptions})
452
+ * @returns A `ToolInterface` (named {@link import('./constants.js').ANSWER_TOOL_NAME} by default)
453
+ *
454
+ * @example
455
+ * ```ts
456
+ * import { createAnswerTool } from '@src/core'
457
+ * import { createTerminalManager, createToolManager } from '@orkestrel/terminal'
458
+ *
459
+ * const manager = createTerminalManager()
460
+ * manager.add('reviewer')
461
+ * const tool = createAnswerTool({ manager, to: 'reviewer' })
462
+ * const tools = createToolManager()
463
+ * tools.add(tool) // the reviewer terminal can now list/answer prompts addressed to it
464
+ * ```
465
+ */
466
+ export declare function createAnswerTool(options: AnswerToolOptions): ToolInterface;
467
+
337
468
  /**
338
469
  * Build an LLM-callable tool that returns the FULL `description` of another registered tool by
339
470
  * name — the counterpart to the lean `summary` the other tools in this package advertise
@@ -370,6 +501,42 @@ export declare function createAgentTool(registry: AgentRegistryInterface, option
370
501
  */
371
502
  export declare function createDescribeTool(tools: ToolManagerInterface): ToolInterface;
372
503
 
504
+ /**
505
+ * Build an LLM-callable prompt tool — the ASK side of the terminal seam. Asks
506
+ * {@link import('./types.js').PromptToolOptions.to} a question and BLOCKS until it answers,
507
+ * returning the resolved answer value.
508
+ *
509
+ * @remarks
510
+ * The universal tool-handler contract (AGENTS §14): validates the call args against
511
+ * {@link import('./shapers.js').promptToolShape}, dispatches to the matching
512
+ * `TerminalManagerInterface.ask` overload (`@orkestrel/terminal`) for the call's `form`, and
513
+ * RETURNS the resolved answer on success. `from` is FIXED at construction
514
+ * ({@link import('./types.js').PromptToolOptions.from}) — never read from the model-supplied
515
+ * args — so a model cannot spoof which terminal is asking. A prompt CYCLE rejects with
516
+ * `TerminalError('DEADLOCK')`, re-surfaced as a typed `DEADLOCK`
517
+ * {@link import('./errors.js').AgentToolError}; an expired prompt re-surfaces as `EXPIRE`; an
518
+ * unknown `to` (or any other `TerminalError`) re-surfaces as `TOOL`, naming the unknown terminal
519
+ * plus the known ones (`manager.terminals()`).
520
+ *
521
+ * @param options - The live manager, the fixed `from` identity, and advertised overrides (see
522
+ * {@link import('./types.js').PromptToolOptions})
523
+ * @returns A `ToolInterface` (named {@link import('./constants.js').PROMPT_TOOL_NAME} by default)
524
+ *
525
+ * @example
526
+ * ```ts
527
+ * import { createPromptTool } from '@src/core'
528
+ * import { createTerminalManager, createToolManager } from '@orkestrel/terminal'
529
+ *
530
+ * const manager = createTerminalManager()
531
+ * manager.add('agent')
532
+ * manager.add('reviewer')
533
+ * const tool = createPromptTool({ manager, from: 'agent' })
534
+ * const tools = createToolManager()
535
+ * tools.add(tool) // the agent can now ask 'reviewer' and block for the answer
536
+ * ```
537
+ */
538
+ export declare function createPromptTool(options: PromptToolOptions): ToolInterface;
539
+
373
540
  /**
374
541
  * Wrap a registered tool as a {@link WorkflowFunction} (`@orkestrel/workflow`) — the OPT-IN
375
542
  * adapter that lets a `function`-form task run a `@orkestrel/agent` tool BY NAME.
@@ -680,6 +847,89 @@ export declare const phaseDraftShape: ObjectShape<{
680
847
  bail: OptionalShape<LiteralShape<readonly [true, false]>>;
681
848
  }>;
682
849
 
850
+ export declare const PROMPT_TOOL_DESCRIPTION: string;
851
+
852
+ /**
853
+ * The name {@link import('./factories.js').createPromptTool} advertises by default — the key a
854
+ * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
855
+ */
856
+ export declare const PROMPT_TOOL_NAME = "ask";
857
+
858
+ /**
859
+ * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createPromptTool}
860
+ * advertises in place of {@link PROMPT_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
861
+ * (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
862
+ * for the full teaching description; the full text stays retrievable via
863
+ * {@link import('./factories.js').createDescribeTool}.
864
+ */
865
+ export declare const PROMPT_TOOL_SUMMARY = "Ask another terminal a question and BLOCK until it answers; the call resolves with the answered value. Call describe('ask') for the required fields.";
866
+
867
+ /**
868
+ * Options for {@link import('./factories.js').createPromptTool} — the live
869
+ * {@link TerminalManagerInterface} (`@orkestrel/terminal`) to `ask` through, the terminal name
870
+ * `from`, and the advertised tool overrides.
871
+ *
872
+ * @remarks
873
+ * - `manager` — the terminal manager whose `ask(from, to, form, options)` the tool's handler
874
+ * calls; BLOCKS the calling agent turn until the addressed terminal answers (or the ask
875
+ * rejects — a cycle throws `TerminalError('DEADLOCK')`, re-surfaced as a typed `DEADLOCK`
876
+ * {@link import('./errors.js').AgentToolError}; an expired prompt re-surfaces as `EXPIRE`).
877
+ * - `from` — the terminal identity this tool asks AS; the model supplies the `to` target and the
878
+ * prompt form per call.
879
+ * - `name` / `description` — advertised tool overrides; default to
880
+ * {@link import('./constants.js').PROMPT_TOOL_NAME} / {@link import('./constants.js').PROMPT_TOOL_DESCRIPTION}.
881
+ */
882
+ export declare interface PromptToolOptions {
883
+ readonly manager: TerminalManagerInterface;
884
+ readonly from: string;
885
+ readonly name?: string;
886
+ readonly description?: string;
887
+ }
888
+
889
+ /**
890
+ * The shape of {@link import('./factories.js').createPromptTool}'s call arguments — `to` (the
891
+ * terminal identity to address), `form` (which of the six {@link import('@orkestrel/terminal').PromptType}
892
+ * forms to ask), `message`, an optional `timeout` override, and every per-form optional field
893
+ * FLATTENED onto one object (mirrors `workspaceToolShape`'s flat-arm style, but a single shared
894
+ * shape rather than a discriminated union — `form` alone does not vary the REQUIRED fields, only
895
+ * which of the optional ones apply, so a flat shape stays faithful without duplicating `to` /
896
+ * `message` / `timeout` across six near-identical arms).
897
+ *
898
+ * @remarks
899
+ * `choices` backs `'select'` / `'checkbox'`; `default` backs `'input'` / `'confirm'` / `'select'`
900
+ * (a string for the first two forms' text default, `'true'`/`'false'` string for confirm — the
901
+ * contract layer cannot vary a field's type by a sibling field's value, so `default` stays a
902
+ * string and the handler coerces per form); `mask` backs `'password'`; `min` / `max` backs
903
+ * `'checkbox'`; `validate` (declarative only) backs the four text-shaped forms
904
+ * (`'input'` / `'password'` / `'confirm'` / `'editor'`).
905
+ */
906
+ export declare const promptToolShape: ObjectShape<{
907
+ to: StringShape;
908
+ form: LiteralShape<readonly ["input", "password", "confirm", "select", "checkbox", "editor"]>;
909
+ message: StringShape;
910
+ default: OptionalShape<StringShape>;
911
+ choices: OptionalShape<ArrayShape<ObjectShape<{
912
+ name: StringShape;
913
+ value: StringShape;
914
+ description: OptionalShape<StringShape>;
915
+ }>>>;
916
+ mask: OptionalShape<StringShape>;
917
+ min: OptionalShape<NumberShape>;
918
+ max: OptionalShape<NumberShape>;
919
+ validate: OptionalShape<ObjectShape<{
920
+ required: OptionalShape<BooleanShape>;
921
+ minimum: OptionalShape<NumberShape>;
922
+ maximum: OptionalShape<NumberShape>;
923
+ pattern: OptionalShape<StringShape>;
924
+ email: OptionalShape<BooleanShape>;
925
+ url: OptionalShape<BooleanShape>;
926
+ numeric: OptionalShape<BooleanShape>;
927
+ integer: OptionalShape<BooleanShape>;
928
+ alphanumeric: OptionalShape<BooleanShape>;
929
+ }>>;
930
+ timeout: OptionalShape<NumberShape>;
931
+ }>;
932
+
683
933
  /**
684
934
  * The shape of ONE flat step — `{ name }` — the building block of {@link workflowStepsShape}.
685
935
  *
@@ -724,6 +974,23 @@ export declare const taskDraftShape: ObjectShape<{
724
974
  timeout: OptionalShape<NumberShape>;
725
975
  }>;
726
976
 
977
+ /**
978
+ * Map a caught error to the {@link AgentToolErrorCode} the terminal-tool factory should throw
979
+ * with — the pure classification step of that factory's error handling.
980
+ *
981
+ * @remarks
982
+ * Narrows `error` with {@link isTerminalError} (`@orkestrel/terminal`) first: a non-`TerminalError`
983
+ * value returns `undefined`, telling the caller this mapper does not apply (rethrow / handle
984
+ * otherwise). For a genuine `TerminalError`, `'DEADLOCK'` maps to `'DEADLOCK'`, `'EXPIRE'` maps
985
+ * to `'EXPIRE'`, and every other {@link import('@orkestrel/terminal').TerminalErrorCode}
986
+ * (`'TARGET'`, `'CANCEL'`, `'DRIVER'`) maps to the generic `'TOOL'` code. The mapper only
987
+ * classifies — the factory performs the actual throw.
988
+ *
989
+ * @param error - The value caught from a terminal-manager operation (`ask` / `answer` / …)
990
+ * @returns The mapped {@link AgentToolErrorCode}, or `undefined` if `error` is not a `TerminalError`
991
+ */
992
+ export declare function terminalToolCode(error: unknown): AgentToolErrorCode | undefined;
993
+
727
994
  export declare const WORKFLOW_TOOL_DESCRIPTION: string;
728
995
 
729
996
  /**
@@ -8,7 +8,9 @@ import { LiteralShape } from '@orkestrel/contract';
8
8
  import { NumberShape } from '@orkestrel/contract';
9
9
  import { ObjectShape } from '@orkestrel/contract';
10
10
  import { OptionalShape } from '@orkestrel/contract';
11
+ import { PromptType } from '@orkestrel/terminal';
11
12
  import { StringShape } from '@orkestrel/contract';
13
+ import { TerminalManagerInterface } from '@orkestrel/terminal';
12
14
  import { ToolInterface } from '@orkestrel/agent';
13
15
  import { ToolManagerInterface } from '@orkestrel/agent';
14
16
  import { UnionShape } from '@orkestrel/contract';
@@ -112,8 +114,11 @@ export declare interface AgentToolArguments {
112
114
  /**
113
115
  * Thrown by {@link import('./factories.js').createAgentTool}'s and
114
116
  * {@link import('./factories.js').createDescribeTool}'s handlers on every failure path — a
115
- * malformed / unresolvable call or an unknown tool name (`TOOL`), or a delegation that would
116
- * exceed the configured depth bound or re-enter an ancestor (`DEPTH`).
117
+ * malformed / unresolvable call or an unknown tool name (`TOOL`), a delegation that would
118
+ * exceed the configured depth bound or re-enter an ancestor (`DEPTH`), a prompt cycle
119
+ * (`DEADLOCK`), a prompt that expired before it was answered (`EXPIRE`), or an answer that
120
+ * failed to apply (`ANSWER`) — the last three thrown by
121
+ * {@link import('./factories.js').createPromptTool} / {@link import('./factories.js').createAnswerTool}.
117
122
  *
118
123
  * @remarks
119
124
  * Carries a machine-readable `code` (see {@link import('./types.js').AgentToolErrorCode}) and
@@ -148,8 +153,15 @@ export declare class AgentToolError extends Error {
148
153
  * `TOOL` — malformed / unresolvable call args (a missing `task`, no resolvable `provider`).
149
154
  * `DEPTH` — the delegation would exceed {@link import('./constants.js').AGENT_TOOL_DEPTH}, or
150
155
  * the resolved agent is already an ancestor (a cycle).
156
+ * `DEADLOCK` — an `ask` call ({@link import('./factories.js').createPromptTool}) would form a
157
+ * prompt cycle (`TerminalManagerInterface.ask`, `@orkestrel/terminal`, rejects with its own
158
+ * `TerminalError('DEADLOCK')`, re-surfaced here).
159
+ * `EXPIRE` — the addressed prompt expired before it was answered.
160
+ * `ANSWER` — {@link import('./factories.js').createAnswerTool}'s answer call failed to apply
161
+ * (an unknown prompt id, a rejected value, or the terminal itself unknown —
162
+ * `TerminalAnswerResult.error`, `@orkestrel/terminal`).
151
163
  */
152
- export declare type AgentToolErrorCode = 'TOOL' | 'DEPTH';
164
+ export declare type AgentToolErrorCode = 'TOOL' | 'DEPTH' | 'DEADLOCK' | 'EXPIRE' | 'ANSWER';
153
165
 
154
166
  /**
155
167
  * Options for {@link import('./factories.js').createAgentTool} — the sub-agent delegation
@@ -209,6 +221,86 @@ export declare const agentToolShape: ObjectShape<{
209
221
  system: OptionalShape<StringShape>;
210
222
  }>;
211
223
 
224
+ export declare const ANSWER_TOOL_DESCRIPTION: string;
225
+
226
+ /**
227
+ * The name {@link import('./factories.js').createAnswerTool} advertises by default — the key a
228
+ * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
229
+ */
230
+ export declare const ANSWER_TOOL_NAME = "answer";
231
+
232
+ /**
233
+ * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createAnswerTool}
234
+ * advertises in place of {@link ANSWER_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
235
+ * (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
236
+ * for the full teaching description; the full text stays retrievable via
237
+ * {@link import('./factories.js').createDescribeTool}.
238
+ */
239
+ export declare const ANSWER_TOOL_SUMMARY = "List prompts addressed to this terminal, or answer one by id. Call describe('answer') for the required fields.";
240
+
241
+ /**
242
+ * Options for {@link import('./factories.js').createAnswerTool} — the live
243
+ * {@link TerminalManagerInterface} (`@orkestrel/terminal`) to list / answer prompts through, the
244
+ * terminal name `to`, and the advertised tool overrides.
245
+ *
246
+ * @remarks
247
+ * - `manager` — the terminal manager whose `pending(to)` / `answer(to, id, value)` the tool's
248
+ * handler calls — `pending` lists the prompts currently addressed to `to`, `answer` resolves
249
+ * one by `id`. A failed `answer` (`TerminalAnswerResult.error`) re-surfaces as a typed
250
+ * `ANSWER` {@link import('./errors.js').AgentToolError}.
251
+ * - `to` — the terminal identity this tool lists / answers prompts FOR.
252
+ * - `name` / `description` — advertised tool overrides; default to
253
+ * {@link import('./constants.js').ANSWER_TOOL_NAME} / {@link import('./constants.js').ANSWER_TOOL_DESCRIPTION}.
254
+ */
255
+ export declare interface AnswerToolOptions {
256
+ readonly manager: TerminalManagerInterface;
257
+ readonly to: string;
258
+ readonly name?: string;
259
+ readonly description?: string;
260
+ }
261
+
262
+ /**
263
+ * The shape of {@link import('./factories.js').createAnswerTool}'s call arguments — discriminated
264
+ * by `operation`: `'pending'` lists the prompts addressed to this tool's terminal, `'answer'`
265
+ * resolves one by `id` with a `value`.
266
+ *
267
+ * @remarks
268
+ * `value`'s type varies by the ORIGINAL prompt's form (`string` for `'input'` / `'password'` /
269
+ * `'select'` / `'editor'`, `boolean` for `'confirm'`, `readonly string[]` for `'checkbox'`) —
270
+ * `unionShape(stringShape(), booleanShape(), arrayShape(stringShape()))` expresses that
271
+ * union directly, so `value` is typed as the full `string | boolean | readonly string[]` union
272
+ * here (no lossy string-only fallback needed).
273
+ */
274
+ export declare const answerToolShape: UnionShape<[ ObjectShape<{
275
+ operation: LiteralShape<readonly ["pending"]>;
276
+ }>, ObjectShape<{
277
+ operation: LiteralShape<readonly ["answer"]>;
278
+ id: StringShape;
279
+ value: UnionShape<[ StringShape, BooleanShape, ArrayShape<StringShape>]>;
280
+ }>]>;
281
+
282
+ /**
283
+ * Normalize an LLM-supplied answer `value` to the type {@link PromptType} `form` expects, so a
284
+ * caller that only ever emits strings can still answer a typed prompt.
285
+ *
286
+ * @remarks
287
+ * `'confirm'` coerces to a `boolean` — a `boolean` passes through, and the strings `'true'` /
288
+ * `'false'` (case-insensitively) map to it; any other string is truthy-coerced via
289
+ * `Boolean(value)`. `'checkbox'` coerces to `readonly string[]` — an array passes through
290
+ * (stringifying each entry), a comma-separated string splits + trims into one, and any other
291
+ * single (non-comma) string becomes a one-item array. Every other form (`'input'` / `'password'`
292
+ * / `'select'` / `'editor'`) coerces to a plain `string` — a string passes through verbatim; a
293
+ * non-string, non-object scalar (`number` / `boolean`) stringifies via `String(value)`; an
294
+ * object or array (no lossless string form) falls back to `''` rather than serializing garbage.
295
+ * Pure and total — never throws.
296
+ *
297
+ * @param form - The {@link PromptType} the answer is being coerced FOR
298
+ * @param value - The raw, LLM-supplied answer value
299
+ * @returns The coerced answer — `boolean` for `'confirm'`, `readonly string[]` for `'checkbox'`,
300
+ * `string` otherwise
301
+ */
302
+ export declare function coerceAnswer(form: PromptType, value: unknown): string | boolean | readonly string[];
303
+
212
304
  /**
213
305
  * Complete a {@link WorkflowDraft} into a strict {@link WorkflowDefinition} — synthesize any
214
306
  * MISSING `id` deterministically + positionally, and default any MISSING `name` to its
@@ -334,6 +426,45 @@ export declare function createAgentFunction(agent: AgentInterface, options?: Age
334
426
  */
335
427
  export declare function createAgentTool(registry: AgentRegistryInterface, options?: AgentToolOptions): ToolInterface;
336
428
 
429
+ /**
430
+ * Build an LLM-callable answer tool — the ANSWER side of the terminal seam. Lists the prompts
431
+ * currently addressed to {@link import('./types.js').AnswerToolOptions.to}, or answers one of
432
+ * them by id.
433
+ *
434
+ * @remarks
435
+ * The universal tool-handler contract (AGENTS §14): validates the call args against
436
+ * {@link import('./shapers.js').answerToolShape} (discriminated by `operation`). `'pending'`
437
+ * returns a compact list (`{ id, from, form, message }`) of every prompt currently addressed to
438
+ * `to` (`TerminalManagerInterface.pending`, `@orkestrel/terminal`). `'answer'` looks the prompt
439
+ * up by `id` (an unknown id throws a typed `ANSWER` {@link import('./errors.js').AgentToolError}),
440
+ * normalizes the model-supplied `value` to the prompt's own form
441
+ * ({@link import('./helpers.js').coerceAnswer}), and applies it via
442
+ * `TerminalManagerInterface.answer` — a rejected / unknown / unresolvable outcome
443
+ * (`TerminalAnswerResult.error`) re-surfaces as a typed `ANSWER` `AgentToolError`; success returns
444
+ * `{ answered: id }`. `to` is FIXED at construction
445
+ * ({@link import('./types.js').AnswerToolOptions.to}) — never read from the model-supplied args —
446
+ * so a model cannot spoof which terminal it is answering for. Concurrent answerers racing on one
447
+ * endpoint are FIRST-WRITE-WINS — a late answer to an already-settled prompt returns a typed
448
+ * `ANSWER` `AgentToolError` (surfaced as a 422 over HTTP).
449
+ *
450
+ * @param options - The live manager, the fixed `to` identity, and advertised overrides (see
451
+ * {@link import('./types.js').AnswerToolOptions})
452
+ * @returns A `ToolInterface` (named {@link import('./constants.js').ANSWER_TOOL_NAME} by default)
453
+ *
454
+ * @example
455
+ * ```ts
456
+ * import { createAnswerTool } from '@src/core'
457
+ * import { createTerminalManager, createToolManager } from '@orkestrel/terminal'
458
+ *
459
+ * const manager = createTerminalManager()
460
+ * manager.add('reviewer')
461
+ * const tool = createAnswerTool({ manager, to: 'reviewer' })
462
+ * const tools = createToolManager()
463
+ * tools.add(tool) // the reviewer terminal can now list/answer prompts addressed to it
464
+ * ```
465
+ */
466
+ export declare function createAnswerTool(options: AnswerToolOptions): ToolInterface;
467
+
337
468
  /**
338
469
  * Build an LLM-callable tool that returns the FULL `description` of another registered tool by
339
470
  * name — the counterpart to the lean `summary` the other tools in this package advertise
@@ -370,6 +501,42 @@ export declare function createAgentTool(registry: AgentRegistryInterface, option
370
501
  */
371
502
  export declare function createDescribeTool(tools: ToolManagerInterface): ToolInterface;
372
503
 
504
+ /**
505
+ * Build an LLM-callable prompt tool — the ASK side of the terminal seam. Asks
506
+ * {@link import('./types.js').PromptToolOptions.to} a question and BLOCKS until it answers,
507
+ * returning the resolved answer value.
508
+ *
509
+ * @remarks
510
+ * The universal tool-handler contract (AGENTS §14): validates the call args against
511
+ * {@link import('./shapers.js').promptToolShape}, dispatches to the matching
512
+ * `TerminalManagerInterface.ask` overload (`@orkestrel/terminal`) for the call's `form`, and
513
+ * RETURNS the resolved answer on success. `from` is FIXED at construction
514
+ * ({@link import('./types.js').PromptToolOptions.from}) — never read from the model-supplied
515
+ * args — so a model cannot spoof which terminal is asking. A prompt CYCLE rejects with
516
+ * `TerminalError('DEADLOCK')`, re-surfaced as a typed `DEADLOCK`
517
+ * {@link import('./errors.js').AgentToolError}; an expired prompt re-surfaces as `EXPIRE`; an
518
+ * unknown `to` (or any other `TerminalError`) re-surfaces as `TOOL`, naming the unknown terminal
519
+ * plus the known ones (`manager.terminals()`).
520
+ *
521
+ * @param options - The live manager, the fixed `from` identity, and advertised overrides (see
522
+ * {@link import('./types.js').PromptToolOptions})
523
+ * @returns A `ToolInterface` (named {@link import('./constants.js').PROMPT_TOOL_NAME} by default)
524
+ *
525
+ * @example
526
+ * ```ts
527
+ * import { createPromptTool } from '@src/core'
528
+ * import { createTerminalManager, createToolManager } from '@orkestrel/terminal'
529
+ *
530
+ * const manager = createTerminalManager()
531
+ * manager.add('agent')
532
+ * manager.add('reviewer')
533
+ * const tool = createPromptTool({ manager, from: 'agent' })
534
+ * const tools = createToolManager()
535
+ * tools.add(tool) // the agent can now ask 'reviewer' and block for the answer
536
+ * ```
537
+ */
538
+ export declare function createPromptTool(options: PromptToolOptions): ToolInterface;
539
+
373
540
  /**
374
541
  * Wrap a registered tool as a {@link WorkflowFunction} (`@orkestrel/workflow`) — the OPT-IN
375
542
  * adapter that lets a `function`-form task run a `@orkestrel/agent` tool BY NAME.
@@ -680,6 +847,89 @@ export declare const phaseDraftShape: ObjectShape<{
680
847
  bail: OptionalShape<LiteralShape<readonly [true, false]>>;
681
848
  }>;
682
849
 
850
+ export declare const PROMPT_TOOL_DESCRIPTION: string;
851
+
852
+ /**
853
+ * The name {@link import('./factories.js').createPromptTool} advertises by default — the key a
854
+ * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
855
+ */
856
+ export declare const PROMPT_TOOL_NAME = "ask";
857
+
858
+ /**
859
+ * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createPromptTool}
860
+ * advertises in place of {@link PROMPT_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
861
+ * (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
862
+ * for the full teaching description; the full text stays retrievable via
863
+ * {@link import('./factories.js').createDescribeTool}.
864
+ */
865
+ export declare const PROMPT_TOOL_SUMMARY = "Ask another terminal a question and BLOCK until it answers; the call resolves with the answered value. Call describe('ask') for the required fields.";
866
+
867
+ /**
868
+ * Options for {@link import('./factories.js').createPromptTool} — the live
869
+ * {@link TerminalManagerInterface} (`@orkestrel/terminal`) to `ask` through, the terminal name
870
+ * `from`, and the advertised tool overrides.
871
+ *
872
+ * @remarks
873
+ * - `manager` — the terminal manager whose `ask(from, to, form, options)` the tool's handler
874
+ * calls; BLOCKS the calling agent turn until the addressed terminal answers (or the ask
875
+ * rejects — a cycle throws `TerminalError('DEADLOCK')`, re-surfaced as a typed `DEADLOCK`
876
+ * {@link import('./errors.js').AgentToolError}; an expired prompt re-surfaces as `EXPIRE`).
877
+ * - `from` — the terminal identity this tool asks AS; the model supplies the `to` target and the
878
+ * prompt form per call.
879
+ * - `name` / `description` — advertised tool overrides; default to
880
+ * {@link import('./constants.js').PROMPT_TOOL_NAME} / {@link import('./constants.js').PROMPT_TOOL_DESCRIPTION}.
881
+ */
882
+ export declare interface PromptToolOptions {
883
+ readonly manager: TerminalManagerInterface;
884
+ readonly from: string;
885
+ readonly name?: string;
886
+ readonly description?: string;
887
+ }
888
+
889
+ /**
890
+ * The shape of {@link import('./factories.js').createPromptTool}'s call arguments — `to` (the
891
+ * terminal identity to address), `form` (which of the six {@link import('@orkestrel/terminal').PromptType}
892
+ * forms to ask), `message`, an optional `timeout` override, and every per-form optional field
893
+ * FLATTENED onto one object (mirrors `workspaceToolShape`'s flat-arm style, but a single shared
894
+ * shape rather than a discriminated union — `form` alone does not vary the REQUIRED fields, only
895
+ * which of the optional ones apply, so a flat shape stays faithful without duplicating `to` /
896
+ * `message` / `timeout` across six near-identical arms).
897
+ *
898
+ * @remarks
899
+ * `choices` backs `'select'` / `'checkbox'`; `default` backs `'input'` / `'confirm'` / `'select'`
900
+ * (a string for the first two forms' text default, `'true'`/`'false'` string for confirm — the
901
+ * contract layer cannot vary a field's type by a sibling field's value, so `default` stays a
902
+ * string and the handler coerces per form); `mask` backs `'password'`; `min` / `max` backs
903
+ * `'checkbox'`; `validate` (declarative only) backs the four text-shaped forms
904
+ * (`'input'` / `'password'` / `'confirm'` / `'editor'`).
905
+ */
906
+ export declare const promptToolShape: ObjectShape<{
907
+ to: StringShape;
908
+ form: LiteralShape<readonly ["input", "password", "confirm", "select", "checkbox", "editor"]>;
909
+ message: StringShape;
910
+ default: OptionalShape<StringShape>;
911
+ choices: OptionalShape<ArrayShape<ObjectShape<{
912
+ name: StringShape;
913
+ value: StringShape;
914
+ description: OptionalShape<StringShape>;
915
+ }>>>;
916
+ mask: OptionalShape<StringShape>;
917
+ min: OptionalShape<NumberShape>;
918
+ max: OptionalShape<NumberShape>;
919
+ validate: OptionalShape<ObjectShape<{
920
+ required: OptionalShape<BooleanShape>;
921
+ minimum: OptionalShape<NumberShape>;
922
+ maximum: OptionalShape<NumberShape>;
923
+ pattern: OptionalShape<StringShape>;
924
+ email: OptionalShape<BooleanShape>;
925
+ url: OptionalShape<BooleanShape>;
926
+ numeric: OptionalShape<BooleanShape>;
927
+ integer: OptionalShape<BooleanShape>;
928
+ alphanumeric: OptionalShape<BooleanShape>;
929
+ }>>;
930
+ timeout: OptionalShape<NumberShape>;
931
+ }>;
932
+
683
933
  /**
684
934
  * The shape of ONE flat step — `{ name }` — the building block of {@link workflowStepsShape}.
685
935
  *
@@ -724,6 +974,23 @@ export declare const taskDraftShape: ObjectShape<{
724
974
  timeout: OptionalShape<NumberShape>;
725
975
  }>;
726
976
 
977
+ /**
978
+ * Map a caught error to the {@link AgentToolErrorCode} the terminal-tool factory should throw
979
+ * with — the pure classification step of that factory's error handling.
980
+ *
981
+ * @remarks
982
+ * Narrows `error` with {@link isTerminalError} (`@orkestrel/terminal`) first: a non-`TerminalError`
983
+ * value returns `undefined`, telling the caller this mapper does not apply (rethrow / handle
984
+ * otherwise). For a genuine `TerminalError`, `'DEADLOCK'` maps to `'DEADLOCK'`, `'EXPIRE'` maps
985
+ * to `'EXPIRE'`, and every other {@link import('@orkestrel/terminal').TerminalErrorCode}
986
+ * (`'TARGET'`, `'CANCEL'`, `'DRIVER'`) maps to the generic `'TOOL'` code. The mapper only
987
+ * classifies — the factory performs the actual throw.
988
+ *
989
+ * @param error - The value caught from a terminal-manager operation (`ask` / `answer` / …)
990
+ * @returns The mapped {@link AgentToolErrorCode}, or `undefined` if `error` is not a `TerminalError`
991
+ */
992
+ export declare function terminalToolCode(error: unknown): AgentToolErrorCode | undefined;
993
+
727
994
  export declare const WORKFLOW_TOOL_DESCRIPTION: string;
728
995
 
729
996
  /**