@orkestrel/toolbox 0.0.6 → 0.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/core/index.cjs +77 -209
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +34 -94
- package/dist/src/core/index.d.ts +34 -94
- package/dist/src/core/index.js +78 -209
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +13 -11
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.js +14 -12
- package/dist/src/server/index.js.map +1 -1
- package/package.json +23 -20
package/dist/src/core/index.cjs
CHANGED
|
@@ -7,6 +7,7 @@ let _orkestrel_relation = require("@orkestrel/relation");
|
|
|
7
7
|
let _orkestrel_agent = require("@orkestrel/agent");
|
|
8
8
|
let _orkestrel_workspace = require("@orkestrel/workspace");
|
|
9
9
|
let _orkestrel_tool = require("@orkestrel/tool");
|
|
10
|
+
let _orkestrel_form = require("@orkestrel/form");
|
|
10
11
|
//#region src/core/constants.ts
|
|
11
12
|
/**
|
|
12
13
|
* The name {@link import('./factories.js').createAgentTool} advertises by default — the key a
|
|
@@ -233,23 +234,30 @@ var PROMPT_TOOL_NAME = "ask";
|
|
|
233
234
|
* for the full teaching description; the full text stays retrievable via
|
|
234
235
|
* {@link import('./factories.js').createDescribeTool}.
|
|
235
236
|
*/
|
|
236
|
-
var PROMPT_TOOL_SUMMARY = "Ask another terminal a
|
|
237
|
-
/** The full
|
|
237
|
+
var PROMPT_TOOL_SUMMARY = "Ask another terminal a multi-field form and BLOCK until it answers; the call resolves with the values record. Call describe('ask') for the schema.";
|
|
238
|
+
/** The full form protocol {@link import('./factories.js').createPromptTool} advertises. */
|
|
238
239
|
var PROMPT_TOOL_DESCRIPTION = [
|
|
239
|
-
"Ask another terminal a
|
|
240
|
+
"Ask another terminal a multi-field form and block until it answers. This call does not return until the addressed terminal answers, or the form expires.",
|
|
240
241
|
"",
|
|
241
242
|
"Required:",
|
|
242
|
-
" to
|
|
243
|
-
"
|
|
244
|
-
"
|
|
245
|
-
"Optional:",
|
|
246
|
-
" options - form-specific options (e.g. choices for \"select\"/\"checkbox\").",
|
|
247
|
-
"A cycle (two terminals asking each other) or an expired prompt fails the call with a typed error.",
|
|
243
|
+
" to - the terminal name to ask.",
|
|
244
|
+
" schema - an @orkestrel/form schema: each field declares `control` and `name`, plus optional `label`, `rule`, `default`, and `choices`; `fields` is an ordered array and each `name` must be unique.",
|
|
245
|
+
"The result is one values object keyed by those field names. A cycle or expired form fails with a typed error.",
|
|
248
246
|
"Example:",
|
|
249
247
|
JSON.stringify({
|
|
250
248
|
to: "reviewer",
|
|
251
|
-
|
|
252
|
-
|
|
249
|
+
schema: {
|
|
250
|
+
label: "Release review",
|
|
251
|
+
fields: [{
|
|
252
|
+
control: "confirm",
|
|
253
|
+
name: "approved",
|
|
254
|
+
label: "Approve the release?"
|
|
255
|
+
}, {
|
|
256
|
+
control: "editor",
|
|
257
|
+
name: "notes",
|
|
258
|
+
label: "Review notes"
|
|
259
|
+
}]
|
|
260
|
+
}
|
|
253
261
|
})
|
|
254
262
|
].join("\n");
|
|
255
263
|
/**
|
|
@@ -264,21 +272,24 @@ var ANSWER_TOOL_NAME = "answer";
|
|
|
264
272
|
* for the full teaching description; the full text stays retrievable via
|
|
265
273
|
* {@link import('./factories.js').createDescribeTool}.
|
|
266
274
|
*/
|
|
267
|
-
var ANSWER_TOOL_SUMMARY = "List
|
|
275
|
+
var ANSWER_TOOL_SUMMARY = "List forms addressed to this terminal, or answer one by id with a values record. Call describe('answer') for the required fields.";
|
|
268
276
|
/** The pending/answer protocol {@link import('./factories.js').createAnswerTool} advertises. */
|
|
269
277
|
var ANSWER_TOOL_DESCRIPTION = [
|
|
270
|
-
"List the
|
|
278
|
+
"List the forms currently addressed to this terminal, or answer one of them by id. Every call is ONE operation, chosen by the \"operation\" field.",
|
|
271
279
|
"",
|
|
272
280
|
"Operations:",
|
|
273
|
-
"- pending { \"operation\": \"pending\" } — list every
|
|
274
|
-
"- answer { \"operation\": \"answer\", \"id\": \"<
|
|
275
|
-
"Example — list pending
|
|
281
|
+
"- pending { \"operation\": \"pending\" } — list every form currently addressed to this terminal (id, from, schema).",
|
|
282
|
+
"- answer { \"operation\": \"answer\", \"id\": \"<form id>\", \"values\": { \"<field name>\": <field value> } } — answer every required field with the value its control accepts.",
|
|
283
|
+
"Example — list pending forms:",
|
|
276
284
|
JSON.stringify({ operation: "pending" }),
|
|
277
285
|
"Example — answer one:",
|
|
278
286
|
JSON.stringify({
|
|
279
287
|
operation: "answer",
|
|
280
288
|
id: "abc123",
|
|
281
|
-
|
|
289
|
+
values: {
|
|
290
|
+
approved: true,
|
|
291
|
+
notes: "Ready"
|
|
292
|
+
}
|
|
282
293
|
})
|
|
283
294
|
].join("\n");
|
|
284
295
|
/**
|
|
@@ -567,104 +578,36 @@ function isToolboxError(value) {
|
|
|
567
578
|
//#endregion
|
|
568
579
|
//#region src/core/shapers.ts
|
|
569
580
|
/**
|
|
570
|
-
* The shape of {@link import('./factories.js').createPromptTool}'s call arguments — `to`
|
|
571
|
-
* terminal identity
|
|
572
|
-
* forms to ask), `message`, an optional `timeout` override, and every per-form optional field
|
|
573
|
-
* FLATTENED onto one object (mirrors `workspaceToolShape`'s flat-arm style, but a single shared
|
|
574
|
-
* shape rather than a discriminated union — `form` alone does not vary the REQUIRED fields, only
|
|
575
|
-
* which of the optional ones apply, so a flat shape stays faithful without duplicating `to` /
|
|
576
|
-
* `message` / `timeout` across six near-identical arms).
|
|
581
|
+
* The shape of {@link import('./factories.js').createPromptTool}'s call arguments — `to` names the
|
|
582
|
+
* terminal identity and `schema` carries the complete multi-field form document.
|
|
577
583
|
*
|
|
578
584
|
* @remarks
|
|
579
|
-
*
|
|
580
|
-
*
|
|
581
|
-
* contract layer cannot vary a field's type by a sibling field's value, so `default` stays a
|
|
582
|
-
* string and the handler coerces per form); `mask` backs `'password'`; `min` / `max` backs
|
|
583
|
-
* `'checkbox'`; `validate` (declarative only) backs the four text-shaped forms
|
|
584
|
-
* (`'input'` / `'password'` / `'confirm'` / `'editor'`).
|
|
585
|
+
* The contract first requires exact JSON. The handler then delegates schema parsing and semantic
|
|
586
|
+
* validation to `@orkestrel/form`'s `parseForm`, so Toolbox does not duplicate the form schema.
|
|
585
587
|
*/
|
|
586
588
|
var promptToolShape = (0, _orkestrel_contract.objectShape)({
|
|
587
589
|
to: (0, _orkestrel_contract.stringShape)({
|
|
588
590
|
min: 1,
|
|
589
|
-
description: "The terminal identity to address the
|
|
591
|
+
description: "The terminal identity to address the form to."
|
|
590
592
|
}),
|
|
591
|
-
|
|
592
|
-
"input",
|
|
593
|
-
"password",
|
|
594
|
-
"confirm",
|
|
595
|
-
"select",
|
|
596
|
-
"checkbox",
|
|
597
|
-
"editor"
|
|
598
|
-
], { description: "Which prompt form to ask." }),
|
|
599
|
-
message: (0, _orkestrel_contract.stringShape)({
|
|
600
|
-
min: 1,
|
|
601
|
-
description: "The prompt's question."
|
|
602
|
-
}),
|
|
603
|
-
default: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "The default answer if the responder submits blank — 'input' / 'editor' text, 'confirm' 'true'/'false', or a 'select' choice value." })),
|
|
604
|
-
choices: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.objectShape)({
|
|
605
|
-
name: (0, _orkestrel_contract.stringShape)({
|
|
606
|
-
min: 1,
|
|
607
|
-
description: "The choice label shown to the answering party."
|
|
608
|
-
}),
|
|
609
|
-
value: (0, _orkestrel_contract.stringShape)({
|
|
610
|
-
min: 1,
|
|
611
|
-
description: "The value submitted when this choice is picked."
|
|
612
|
-
}),
|
|
613
|
-
description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "An optional one-line elaboration." }))
|
|
614
|
-
}), { description: "The selectable choices for 'select' / 'checkbox'." })),
|
|
615
|
-
mask: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
|
|
616
|
-
min: 1,
|
|
617
|
-
description: "The mask character 'password' renders in place of input."
|
|
618
|
-
})),
|
|
619
|
-
min: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
620
|
-
min: 0,
|
|
621
|
-
description: "The minimum number of 'checkbox' selections required."
|
|
622
|
-
})),
|
|
623
|
-
max: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
624
|
-
min: 0,
|
|
625
|
-
description: "The maximum number of 'checkbox' selections allowed."
|
|
626
|
-
})),
|
|
627
|
-
validate: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
|
|
628
|
-
required: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Reject an empty (trimmed) input." })),
|
|
629
|
-
minimum: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
630
|
-
min: 0,
|
|
631
|
-
description: "Reject an input shorter than this many characters."
|
|
632
|
-
})),
|
|
633
|
-
maximum: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
634
|
-
min: 0,
|
|
635
|
-
description: "Reject an input longer than this many characters."
|
|
636
|
-
})),
|
|
637
|
-
pattern: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "Reject an input that fails this regular-expression source." })),
|
|
638
|
-
email: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require a valid email-address shape." })),
|
|
639
|
-
url: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require a valid URL shape." })),
|
|
640
|
-
numeric: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require a numeric value." })),
|
|
641
|
-
integer: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require an integer value." })),
|
|
642
|
-
alphanumeric: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require letters and digits only." }))
|
|
643
|
-
})),
|
|
644
|
-
timeout: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
|
|
645
|
-
min: 0,
|
|
646
|
-
description: "Milliseconds to wait before the prompt expires."
|
|
647
|
-
}))
|
|
593
|
+
schema: (0, _orkestrel_contract.jsonShape)({ description: "The @orkestrel/form schema to ask, with form metadata and one or more typed fields." })
|
|
648
594
|
});
|
|
649
595
|
/**
|
|
650
596
|
* The shape of {@link import('./factories.js').createAnswerTool}'s call arguments — discriminated
|
|
651
|
-
* by `operation`: `'pending'` lists the
|
|
652
|
-
* resolves one by `id` with a `
|
|
597
|
+
* by `operation`: `'pending'` lists the forms addressed to this tool's terminal, while `'answer'`
|
|
598
|
+
* resolves one by `id` with a complete `values` record.
|
|
653
599
|
*
|
|
654
600
|
* @remarks
|
|
655
|
-
* `
|
|
656
|
-
*
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
*/
|
|
661
|
-
var answerToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({ operation: (0, _orkestrel_contract.literalShape)(["pending"], { description: "List the prompts currently addressed to this terminal." }) }), (0, _orkestrel_contract.objectShape)({
|
|
662
|
-
operation: (0, _orkestrel_contract.literalShape)(["answer"], { description: "Answer one pending prompt by id." }),
|
|
601
|
+
* `values` is first bounded to exact JSON. The handler then narrows it with the originating Form
|
|
602
|
+
* package's `isFormValues` guard before the terminal manager applies it to the parked live form.
|
|
603
|
+
*/
|
|
604
|
+
var answerToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({ operation: (0, _orkestrel_contract.literalShape)(["pending"], { description: "List the forms currently addressed to this terminal." }) }), (0, _orkestrel_contract.objectShape)({
|
|
605
|
+
operation: (0, _orkestrel_contract.literalShape)(["answer"], { description: "Answer one pending form by id." }),
|
|
663
606
|
id: (0, _orkestrel_contract.stringShape)({
|
|
664
607
|
min: 1,
|
|
665
|
-
description: "The id of the pending
|
|
608
|
+
description: "The id of the pending form to answer."
|
|
666
609
|
}),
|
|
667
|
-
|
|
610
|
+
values: (0, _orkestrel_contract.jsonShape)({ description: "The form answers keyed by field name." })
|
|
668
611
|
}));
|
|
669
612
|
/**
|
|
670
613
|
* The shape of {@link import('./types.js').AgentToolArguments} —
|
|
@@ -1419,48 +1362,6 @@ function expandSteps(flat) {
|
|
|
1419
1362
|
});
|
|
1420
1363
|
}
|
|
1421
1364
|
/**
|
|
1422
|
-
* Normalize an LLM-supplied answer `value` to the type {@link PromptType} `form` expects, so a
|
|
1423
|
-
* caller that only ever emits strings can still answer a typed prompt.
|
|
1424
|
-
*
|
|
1425
|
-
* @remarks
|
|
1426
|
-
* `'confirm'` coerces to a `boolean` — a `boolean` passes through, and the strings `'true'` /
|
|
1427
|
-
* `'false'` (case-insensitively) map to it; any other string is truthy-coerced via
|
|
1428
|
-
* `Boolean(value)`. `'checkbox'` coerces to `readonly string[]` — an array passes through
|
|
1429
|
-
* (stringifying each entry), a comma-separated string splits + trims into one, and any other
|
|
1430
|
-
* single (non-comma) string becomes a one-item array. Every other form (`'input'` / `'password'`
|
|
1431
|
-
* / `'select'` / `'editor'`) coerces to a plain `string` — a string passes through verbatim; a
|
|
1432
|
-
* non-string, non-object scalar (`number` / `boolean`) stringifies via `String(value)`; an
|
|
1433
|
-
* object or array (no lossless string form) falls back to `''` rather than serializing garbage.
|
|
1434
|
-
* Pure and total — never throws.
|
|
1435
|
-
*
|
|
1436
|
-
* @param form - The {@link PromptType} the answer is being coerced FOR
|
|
1437
|
-
* @param value - The raw, LLM-supplied answer value
|
|
1438
|
-
* @returns The coerced answer — `boolean` for `'confirm'`, `readonly string[]` for `'checkbox'`,
|
|
1439
|
-
* `string` otherwise
|
|
1440
|
-
*/
|
|
1441
|
-
function coerceAnswer(form, value) {
|
|
1442
|
-
if (form === "confirm") {
|
|
1443
|
-
if (typeof value === "boolean") return value;
|
|
1444
|
-
if (typeof value === "string") {
|
|
1445
|
-
const lower = value.trim().toLowerCase();
|
|
1446
|
-
if (lower === "true") return true;
|
|
1447
|
-
if (lower === "false") return false;
|
|
1448
|
-
}
|
|
1449
|
-
return Boolean(value);
|
|
1450
|
-
}
|
|
1451
|
-
if (form === "checkbox") {
|
|
1452
|
-
if (Array.isArray(value)) return value.map((entry) => String(entry));
|
|
1453
|
-
if (typeof value === "string") {
|
|
1454
|
-
if (value.includes(",")) return value.split(",").map((entry) => entry.trim());
|
|
1455
|
-
return [value];
|
|
1456
|
-
}
|
|
1457
|
-
return [String(value)];
|
|
1458
|
-
}
|
|
1459
|
-
if (typeof value === "string") return value;
|
|
1460
|
-
if (typeof value === "object" && value !== null) return "";
|
|
1461
|
-
return String(value);
|
|
1462
|
-
}
|
|
1463
|
-
/**
|
|
1464
1365
|
* Map a caught error to the {@link ToolboxErrorCode} the terminal-tool factory should throw
|
|
1465
1366
|
* with — the pure classification step of that factory's error handling.
|
|
1466
1367
|
*
|
|
@@ -1469,8 +1370,8 @@ function coerceAnswer(form, value) {
|
|
|
1469
1370
|
* value returns `undefined`, telling the caller this mapper does not apply (rethrow / handle
|
|
1470
1371
|
* otherwise). For a genuine `TerminalError`, `'DEADLOCK'` maps to `'DEADLOCK'`, `'EXPIRE'` maps
|
|
1471
1372
|
* to `'EXPIRE'`, and every other {@link import('@orkestrel/terminal').TerminalErrorCode}
|
|
1472
|
-
* (`'TARGET'`, `'CANCEL'`, `'DRIVER'`) maps to the generic `'TOOL'`
|
|
1473
|
-
* classifies — the factory performs the actual throw.
|
|
1373
|
+
* (`'TARGET'`, `'LIMIT'`, `'CANCEL'`, `'DRIVER'`, `'DESTROYED'`) maps to the generic `'TOOL'`
|
|
1374
|
+
* code. The mapper only classifies — the factory performs the actual throw.
|
|
1474
1375
|
*
|
|
1475
1376
|
* @param error - The value caught from a terminal-manager operation (`ask` / `answer` / …)
|
|
1476
1377
|
* @returns The mapped {@link ToolboxErrorCode}, or `undefined` if `error` is not a `TerminalError`
|
|
@@ -2565,19 +2466,18 @@ function createDescribeTool(tools) {
|
|
|
2565
2466
|
});
|
|
2566
2467
|
}
|
|
2567
2468
|
/**
|
|
2568
|
-
* Build an LLM-callable
|
|
2569
|
-
*
|
|
2570
|
-
* returning the resolved answer value.
|
|
2469
|
+
* Build an LLM-callable form tool — the ASK side of the terminal seam. Asks
|
|
2470
|
+
* a multi-field form and BLOCKS until it answers, returning the resolved values record.
|
|
2571
2471
|
*
|
|
2572
2472
|
* @remarks
|
|
2573
2473
|
* The universal tool-handler contract (AGENTS §14): validates the call args against
|
|
2574
|
-
* {@link import('./shapers.js').promptToolShape},
|
|
2575
|
-
*
|
|
2576
|
-
*
|
|
2474
|
+
* {@link import('./shapers.js').promptToolShape}, parses the call's schema through
|
|
2475
|
+
* `@orkestrel/form`, constructs the live form, and passes it to `TerminalManagerInterface.ask`.
|
|
2476
|
+
* `from` is FIXED at construction
|
|
2577
2477
|
* ({@link import('./types.js').PromptToolOptions.from}) — never read from the model-supplied
|
|
2578
|
-
* args — so a model cannot spoof which terminal is asking. A
|
|
2478
|
+
* args — so a model cannot spoof which terminal is asking. A form CYCLE rejects with
|
|
2579
2479
|
* `TerminalError('DEADLOCK')`, re-surfaced as a typed `DEADLOCK`
|
|
2580
|
-
* {@link import('./errors.js').ToolboxError}; an expired
|
|
2480
|
+
* {@link import('./errors.js').ToolboxError}; an expired form re-surfaces as `EXPIRE`; an
|
|
2581
2481
|
* unknown `to` (or any other `TerminalError`) re-surfaces as `TOOL`, naming the unknown terminal
|
|
2582
2482
|
* plus the known ones (`manager.terminals()`).
|
|
2583
2483
|
*
|
|
@@ -2610,51 +2510,23 @@ function createPromptTool(options) {
|
|
|
2610
2510
|
async execute(args) {
|
|
2611
2511
|
const call = contract.parse(args);
|
|
2612
2512
|
if (call === void 0) throw new ToolboxError("TOOL", "malformed ask call", { args });
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2513
|
+
const schema = (0, _orkestrel_form.parseForm)(call.schema);
|
|
2514
|
+
if (schema === void 0) throw new ToolboxError("TOOL", "malformed form schema", { schema: call.schema });
|
|
2515
|
+
for (const field of schema.fields) if ((field.control === "select" || field.control === "checkbox") && !field.choices.some((choice) => choice.disabled !== true)) throw new ToolboxError("TOOL", `${field.control} field '${field.name}' has no enabled choices`, {
|
|
2516
|
+
control: field.control,
|
|
2517
|
+
field: field.name
|
|
2616
2518
|
});
|
|
2617
2519
|
try {
|
|
2618
|
-
|
|
2619
|
-
case "input": return await options.manager.ask(options.from, call.to, call.form, {
|
|
2620
|
-
message: call.message,
|
|
2621
|
-
...call.default === void 0 ? {} : { default: call.default },
|
|
2622
|
-
...call.validate === void 0 ? {} : { validate: call.validate }
|
|
2623
|
-
});
|
|
2624
|
-
case "editor": return await options.manager.ask(options.from, call.to, call.form, {
|
|
2625
|
-
message: call.message,
|
|
2626
|
-
...call.default === void 0 ? {} : { default: call.default },
|
|
2627
|
-
...call.validate === void 0 ? {} : { validate: call.validate }
|
|
2628
|
-
});
|
|
2629
|
-
case "password": return await options.manager.ask(options.from, call.to, call.form, {
|
|
2630
|
-
message: call.message,
|
|
2631
|
-
...call.mask === void 0 ? {} : { mask: call.mask },
|
|
2632
|
-
...call.validate === void 0 ? {} : { validate: call.validate }
|
|
2633
|
-
});
|
|
2634
|
-
case "confirm": return await options.manager.ask(options.from, call.to, call.form, {
|
|
2635
|
-
message: call.message,
|
|
2636
|
-
...call.default === void 0 ? {} : { default: call.default === "true" }
|
|
2637
|
-
});
|
|
2638
|
-
case "select": return await options.manager.ask(options.from, call.to, call.form, {
|
|
2639
|
-
message: call.message,
|
|
2640
|
-
choices: call.choices ?? [],
|
|
2641
|
-
...call.default === void 0 ? {} : { default: call.default }
|
|
2642
|
-
});
|
|
2643
|
-
case "checkbox": return await options.manager.ask(options.from, call.to, call.form, {
|
|
2644
|
-
message: call.message,
|
|
2645
|
-
choices: call.choices ?? [],
|
|
2646
|
-
...call.min === void 0 ? {} : { min: call.min },
|
|
2647
|
-
...call.max === void 0 ? {} : { max: call.max }
|
|
2648
|
-
});
|
|
2649
|
-
}
|
|
2520
|
+
return await options.manager.ask(options.from, call.to, (0, _orkestrel_form.createForm)(schema));
|
|
2650
2521
|
} catch (error) {
|
|
2522
|
+
if ((0, _orkestrel_form.isFormError)(error) && error.code === "ABANDONED") throw new ToolboxError("EXPIRE", `form to '${call.to}' expired before it was answered`, { to: call.to });
|
|
2651
2523
|
const code = terminalToolCode(error);
|
|
2652
2524
|
if (code === void 0) throw error;
|
|
2653
|
-
if (code === "DEADLOCK") throw new ToolboxError("DEADLOCK", `asking '${call.to}' would
|
|
2525
|
+
if (code === "DEADLOCK") throw new ToolboxError("DEADLOCK", `asking '${call.to}' would create a form cycle`, (0, _orkestrel_terminal.isTerminalError)(error) ? error.context : {
|
|
2654
2526
|
from: options.from,
|
|
2655
2527
|
to: call.to
|
|
2656
2528
|
});
|
|
2657
|
-
if (code === "EXPIRE") throw new ToolboxError("EXPIRE", `
|
|
2529
|
+
if (code === "EXPIRE") throw new ToolboxError("EXPIRE", `form to '${call.to}' expired before it was answered`, { to: call.to });
|
|
2658
2530
|
if ((0, _orkestrel_terminal.isTerminalError)(error) && error.code === "TARGET") throw new ToolboxError("TOOL", `unknown terminal '${call.to}'`, {
|
|
2659
2531
|
to: call.to,
|
|
2660
2532
|
known: options.manager.terminals()
|
|
@@ -2665,24 +2537,22 @@ function createPromptTool(options) {
|
|
|
2665
2537
|
});
|
|
2666
2538
|
}
|
|
2667
2539
|
/**
|
|
2668
|
-
* Build an LLM-callable answer tool — the ANSWER side of the terminal seam. Lists the
|
|
2540
|
+
* Build an LLM-callable answer tool — the ANSWER side of the terminal seam. Lists the forms
|
|
2669
2541
|
* currently addressed to {@link import('./types.js').AnswerToolOptions.to}, or answers one of
|
|
2670
2542
|
* them by id.
|
|
2671
2543
|
*
|
|
2672
2544
|
* @remarks
|
|
2673
2545
|
* The universal tool-handler contract (AGENTS §14): validates the call args against
|
|
2674
2546
|
* {@link import('./shapers.js').answerToolShape} (discriminated by `operation`). `'pending'`
|
|
2675
|
-
* returns a compact list (`{ id, from,
|
|
2676
|
-
* `
|
|
2677
|
-
*
|
|
2678
|
-
* normalizes the model-supplied `value` to the prompt's own form
|
|
2679
|
-
* ({@link import('./helpers.js').coerceAnswer}), and applies it via
|
|
2547
|
+
* returns a compact list (`{ id, from, schema }`) of every form currently addressed to `to`.
|
|
2548
|
+
* `'answer'` looks the form up by `id`, narrows the supplied `values` through `@orkestrel/form`,
|
|
2549
|
+
* and applies it via
|
|
2680
2550
|
* `TerminalManagerInterface.answer` — a rejected / unknown / unresolvable outcome
|
|
2681
2551
|
* (`TerminalAnswerResult.error`) re-surfaces as a typed `ANSWER` `ToolboxError`; success returns
|
|
2682
2552
|
* `{ answered: id }`. `to` is FIXED at construction
|
|
2683
2553
|
* ({@link import('./types.js').AnswerToolOptions.to}) — never read from the model-supplied args —
|
|
2684
2554
|
* so a model cannot spoof which terminal it is answering for. Concurrent answerers racing on one
|
|
2685
|
-
* endpoint are FIRST-WRITE-WINS — a late answer to an already-settled
|
|
2555
|
+
* endpoint are FIRST-WRITE-WINS — a late answer to an already-settled form returns a typed
|
|
2686
2556
|
* `ANSWER` `ToolboxError` (surfaced as a 422 over HTTP).
|
|
2687
2557
|
*
|
|
2688
2558
|
* @param options - The live manager, the fixed `to` identity, and advertised overrides (see
|
|
@@ -2699,7 +2569,7 @@ function createPromptTool(options) {
|
|
|
2699
2569
|
* manager.add('reviewer')
|
|
2700
2570
|
* const tool = createAnswerTool({ manager, to: 'reviewer' })
|
|
2701
2571
|
* const tools = createToolManager()
|
|
2702
|
-
* tools.add(tool) // the reviewer terminal can now list/answer
|
|
2572
|
+
* tools.add(tool) // the reviewer terminal can now list/answer forms addressed to it
|
|
2703
2573
|
* ```
|
|
2704
2574
|
*/
|
|
2705
2575
|
function createAnswerTool(options) {
|
|
@@ -2713,22 +2583,21 @@ function createAnswerTool(options) {
|
|
|
2713
2583
|
async execute(args) {
|
|
2714
2584
|
const call = contract.parse(args);
|
|
2715
2585
|
if (call === void 0) throw new ToolboxError("TOOL", "malformed answer call", { args });
|
|
2716
|
-
if (call.operation === "pending") return options.manager.pending(options.to).map((
|
|
2717
|
-
id:
|
|
2718
|
-
from:
|
|
2719
|
-
|
|
2720
|
-
message: prompt.message
|
|
2586
|
+
if (call.operation === "pending") return options.manager.pending(options.to).map((form) => ({
|
|
2587
|
+
id: form.id,
|
|
2588
|
+
from: form.from,
|
|
2589
|
+
schema: form.schema
|
|
2721
2590
|
}));
|
|
2722
|
-
|
|
2723
|
-
if (prompt === void 0) throw new ToolboxError("ANSWER", `unknown prompt '${call.id}'`, {
|
|
2591
|
+
if (options.manager.pending(options.to).find((entry) => entry.id === call.id) === void 0) throw new ToolboxError("ANSWER", `unknown form '${call.id}'`, {
|
|
2724
2592
|
id: call.id,
|
|
2725
2593
|
reason: "unknown"
|
|
2726
2594
|
});
|
|
2727
|
-
|
|
2728
|
-
const result = options.manager.answer(options.to, call.id,
|
|
2729
|
-
if (!result.success) throw new ToolboxError("ANSWER", `failed to answer
|
|
2595
|
+
if (!(0, _orkestrel_form.isFormValues)(call.values)) throw new ToolboxError("TOOL", "malformed form values", { values: call.values });
|
|
2596
|
+
const result = options.manager.answer(options.to, call.id, call.values);
|
|
2597
|
+
if (!result.success) throw new ToolboxError("ANSWER", `failed to answer form '${call.id}': ${result.error.reason}`, {
|
|
2730
2598
|
id: call.id,
|
|
2731
|
-
reason: result.error
|
|
2599
|
+
reason: result.error.reason,
|
|
2600
|
+
...result.error.reason === "rejected" ? { errors: result.error.errors } : {}
|
|
2732
2601
|
});
|
|
2733
2602
|
return { answered: call.id };
|
|
2734
2603
|
}
|
|
@@ -3364,7 +3233,6 @@ exports.agentTag = agentTag;
|
|
|
3364
3233
|
exports.agentToolShape = agentToolShape;
|
|
3365
3234
|
exports.answerToolShape = answerToolShape;
|
|
3366
3235
|
exports.clampQuery = clampQuery;
|
|
3367
|
-
exports.coerceAnswer = coerceAnswer;
|
|
3368
3236
|
exports.columnKindShape = columnKindShape;
|
|
3369
3237
|
exports.columnShape = columnShape;
|
|
3370
3238
|
exports.columnSpecShape = columnSpecShape;
|