@orkestrel/tool 0.0.1 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,8 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  let _orkestrel_contract = require("@orkestrel/contract");
3
+ let _orkestrel_terminal = require("@orkestrel/terminal");
4
+ let _orkestrel_database = require("@orkestrel/database");
5
+ let _orkestrel_relation = require("@orkestrel/relation");
3
6
  let _orkestrel_agent = require("@orkestrel/agent");
4
7
  let _orkestrel_workflow = require("@orkestrel/workflow");
5
8
  //#region src/core/constants.ts
@@ -237,13 +240,204 @@ var DESCRIBE_TOOL_SUMMARY = "Return the full description of a named registered t
237
240
  * schema or multi-step protocol to teach.
238
241
  */
239
242
  var DESCRIBE_TOOL_DESCRIPTION = "Return the full description of a registered tool by its name. Required: name - the registered tool name (see another tool listing for available names).";
243
+ /**
244
+ * The name {@link import('./factories.js').createPromptTool} advertises by default — the key a
245
+ * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
246
+ */
247
+ var PROMPT_TOOL_NAME = "ask";
248
+ /**
249
+ * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createPromptTool}
250
+ * advertises in place of {@link PROMPT_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
251
+ * (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
252
+ * for the full teaching description; the full text stays retrievable via
253
+ * {@link import('./factories.js').createDescribeTool}.
254
+ */
255
+ var 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.";
256
+ var PROMPT_TOOL_DESCRIPTION = [
257
+ "Ask another terminal a question and block until it answers. This call does not return until the addressed terminal answers, or the prompt fails.",
258
+ "",
259
+ "Required:",
260
+ " to - the terminal name to ask.",
261
+ " form - the prompt kind: one of \"input\", \"password\", \"confirm\", \"select\", \"checkbox\", \"editor\".",
262
+ " message - the question shown to the answering terminal.",
263
+ "Optional:",
264
+ " options - form-specific options (e.g. choices for \"select\"/\"checkbox\").",
265
+ "A cycle (two terminals asking each other) or an expired prompt fails the call with a typed error.",
266
+ "Example:",
267
+ JSON.stringify({
268
+ to: "reviewer",
269
+ form: "confirm",
270
+ message: "Approve the release?"
271
+ })
272
+ ].join("\n");
273
+ /**
274
+ * The name {@link import('./factories.js').createAnswerTool} advertises by default — the key a
275
+ * model calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
276
+ */
277
+ var ANSWER_TOOL_NAME = "answer";
278
+ /**
279
+ * The lean {@link import('@orkestrel/agent').ToolInterface.summary} {@link import('./factories.js').createAnswerTool}
280
+ * advertises in place of {@link ANSWER_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
281
+ * (`@orkestrel/agent`) advertises `summary ?? description`, so this one-sentence text stands in
282
+ * for the full teaching description; the full text stays retrievable via
283
+ * {@link import('./factories.js').createDescribeTool}.
284
+ */
285
+ var ANSWER_TOOL_SUMMARY = "List prompts addressed to this terminal, or answer one by id. Call describe('answer') for the required fields.";
286
+ var ANSWER_TOOL_DESCRIPTION = [
287
+ "List the prompts currently addressed to this terminal, or answer one of them by id. Every call is ONE operation, chosen by the \"operation\" field.",
288
+ "",
289
+ "Operations:",
290
+ "- pending { \"operation\": \"pending\" } — list every prompt currently addressed to this terminal (id, form, message, options, time).",
291
+ "- answer { \"operation\": \"answer\", \"id\": \"<prompt id>\", \"value\": <answer value> } — answer the prompt with that id; \"value\" must match the prompt's form (a string for \"input\"/\"password\"/\"editor\", a boolean for \"confirm\", a choice for \"select\", an array of choices for \"checkbox\").",
292
+ "Example — list pending prompts:",
293
+ JSON.stringify({ operation: "pending" }),
294
+ "Example — answer one:",
295
+ JSON.stringify({
296
+ operation: "answer",
297
+ id: "abc123",
298
+ value: true
299
+ })
300
+ ].join("\n");
301
+ /**
302
+ * The name the upcoming `createDatabaseTool` factory will advertise by default — the key a model
303
+ * calls and the `ToolManagerInterface` (`@orkestrel/agent`) registers under.
304
+ *
305
+ * @remarks
306
+ * SRC-1 of a 3-unit spine: this unit lands the persistence + schema foundation
307
+ * ({@link import('./types.js').DatabaseDefinition}, {@link import('./types.js').DefinitionStoreInterface},
308
+ * {@link import('./helpers.js').expandTables}); `createDatabaseTool` itself is built in a later unit.
309
+ */
310
+ var DATABASE_TOOL_NAME = "database";
311
+ /**
312
+ * The lean {@link import('@orkestrel/agent').ToolInterface.summary} the upcoming database tool
313
+ * will advertise in place of {@link DATABASE_TOOL_DESCRIPTION}.
314
+ */
315
+ var DATABASE_TOOL_SUMMARY = "Create and query a database — one operation per call (create, tables, get, records, count, aggregate, add, set, update, remove, migrate, destroy), chosen by the 'operation' field. Call describe('database') for the full operation list, the criteria form, and the column DSL.";
316
+ /**
317
+ * The DESCRIPTION the upcoming database tool will advertise — a multi-line guide that teaches a
318
+ * small model the operation list, the SERIALIZED criteria form, and the {@link import('./types.js').TableSpec}
319
+ * column DSL.
320
+ *
321
+ * @remarks
322
+ * The criteria form is deliberately SERIALIZED (never fluent) — every condition is a flat object
323
+ * `{ column, operator, values, connector? }` where `values` is ALWAYS an array, even for a
324
+ * single-value operator (`{ column: 'age', operator: 'from', values: [18] }`), so a small model
325
+ * never has to chain method calls or guess whether a value is scalar or a list.
326
+ */
327
+ var DATABASE_TOOL_DESCRIPTION = [
328
+ "Create and query a database. Every call is ONE operation, chosen by the \"operation\" field.",
329
+ "",
330
+ "Operations (each takes the fields listed):",
331
+ "- create { \"operation\": \"create\", \"id\": \"<database id>\", \"tables\": { \"<table>\": { \"columns\": { \"<column>\": \"string\" | \"integer\" | \"number\" | \"boolean\" | { \"type\": \"string\", \"optional\": true } } } } } — define a new database.",
332
+ "- tables { \"operation\": \"tables\", \"id\": \"<database id>\" } — list a database's table names.",
333
+ "- get { \"operation\": \"get\", \"id\": \"<database id>\", \"table\": \"<table>\", \"key\": \"<row key>\" } — fetch one row by its primary key.",
334
+ "- records { \"operation\": \"records\", \"id\": \"<database id>\", \"table\": \"<table>\", \"criteria\"?: <Criteria> } — list rows matching criteria.",
335
+ "- count { \"operation\": \"count\", \"id\": \"<database id>\", \"table\": \"<table>\", \"criteria\"?: <Criteria> } — count rows matching criteria.",
336
+ "- aggregate { \"operation\": \"aggregate\", \"id\": \"<database id>\", \"table\": \"<table>\", \"column\": \"<column>\", \"function\": \"count\" | \"sum\" | \"average\" | \"minimum\" | \"maximum\", \"criteria\"?: <Criteria> } — compute an aggregate.",
337
+ "- add { \"operation\": \"add\", \"id\": \"<database id>\", \"table\": \"<table>\", \"row\": { ... } } — insert a row (fails on a duplicate key).",
338
+ "- set { \"operation\": \"set\", \"id\": \"<database id>\", \"table\": \"<table>\", \"row\": { ... } } — upsert a row.",
339
+ "- update { \"operation\": \"update\", \"id\": \"<database id>\", \"table\": \"<table>\", \"key\": \"<row key>\", \"row\": { ... } } — patch an existing row.",
340
+ "- remove { \"operation\": \"remove\", \"id\": \"<database id>\", \"table\": \"<table>\", \"key\": \"<row key>\" } — delete a row by key.",
341
+ "- migrate { \"operation\": \"migrate\", \"id\": \"<database id>\", \"tables\": { ... } } — replace the table layout in place.",
342
+ "- destroy { \"operation\": \"destroy\", \"id\": \"<database id>\" } — drop a database entirely.",
343
+ "",
344
+ "Criteria form — SERIALIZED, never fluent. A condition is a flat object; \"values\" is ALWAYS an array, even for one value:",
345
+ " { \"conditions\": [ { \"column\": \"age\", \"operator\": \"from\", \"values\": [18], \"connector\": \"and\" } ], \"order\"?: [...], \"offset\"?: 0, \"limit\"?: 100 }",
346
+ " operators: equals, not, above, below, from, to, between, like, glob, starts, ends, any, none, absent, present.",
347
+ " \"connector\" joins this condition to the next (\"and\" | \"or\"); omit on the last condition.",
348
+ "",
349
+ "Column DSL (used by \"create\"/\"migrate\" \"tables\"): a column is either a bare type string (\"string\" | \"integer\" | \"number\" | \"boolean\"), or { \"type\": \"<type>\", \"optional\": true } when the column may be absent from a row.",
350
+ "Example — create a database:",
351
+ JSON.stringify({
352
+ operation: "create",
353
+ id: "shop",
354
+ tables: { products: { columns: {
355
+ name: "string",
356
+ price: "number",
357
+ notes: {
358
+ type: "string",
359
+ optional: true
360
+ }
361
+ } } }
362
+ }),
363
+ "Example — query with criteria:",
364
+ JSON.stringify({
365
+ operation: "records",
366
+ id: "shop",
367
+ table: "products",
368
+ criteria: { conditions: [{
369
+ column: "price",
370
+ operator: "below",
371
+ values: [50]
372
+ }] }
373
+ })
374
+ ].join("\n");
375
+ /** The default cap on rows a `records` / `remove` call returns (or acts on) when the caller omits `criteria.limit` — the upcoming database tool's default row ceiling. */
376
+ var DATABASE_TOOL_LIMIT = 1e3;
377
+ /** The database tool's mutating operations — disabled by `DatabaseToolOptions.readonly`. */
378
+ var DATABASE_TOOL_MUTATIONS = /* @__PURE__ */ new Set([
379
+ "create",
380
+ "add",
381
+ "set",
382
+ "update",
383
+ "remove",
384
+ "migrate",
385
+ "destroy"
386
+ ]);
387
+ /**
388
+ * The name `createRelationTool` advertises by default — the key a model calls and the
389
+ * `ToolManagerInterface` (`@orkestrel/agent`) registers under.
390
+ */
391
+ var RELATION_TOOL_NAME = "relation";
392
+ /**
393
+ * The lean {@link import('@orkestrel/agent').ToolInterface.summary} the relation tool advertises
394
+ * in place of {@link RELATION_TOOL_DESCRIPTION}.
395
+ */
396
+ var RELATION_TOOL_SUMMARY = "Traverse and edit relationships between database rows — one operation per call (load, find, link, unlink, links), chosen by the 'operation' field. Call describe('relation') for the include-path syntax.";
397
+ /**
398
+ * The DESCRIPTION the relation tool advertises — a multi-line guide that teaches a small model
399
+ * the operation list and the flat dot-path `include` syntax.
400
+ *
401
+ * @remarks
402
+ * An include path is a FLAT dot-separated string (`'contacts.account'`), never a nested object —
403
+ * the same small-model ergonomic lever the other tools in this package use for flat args.
404
+ */
405
+ var RELATION_TOOL_DESCRIPTION = [
406
+ "Traverse and edit relationships between database rows. Every call is ONE operation, chosen by the \"operation\" field. \"manager\" is optional (omit it when only one relation manager is registered).",
407
+ "",
408
+ "Operations (each takes the fields listed):",
409
+ "- load { \"operation\": \"load\", \"model\": \"<model>\", \"key\": \"<row key>\", \"include\"?: [\"<path>\", ...] } — fetch one (or, with an array key, several) row(s) with related rows attached.",
410
+ "- find { \"operation\": \"find\", \"model\": \"<model>\", \"include\"?: [\"<path>\", ...], \"limit\"?: <n>, \"offset\"?: <n>, \"sort\"?: \"<column>\", \"direction\"?: \"ascending\"|\"descending\" } — list rows, each with related rows attached.",
411
+ "- link { \"operation\": \"link\", \"model\": \"<model>\", \"key\": \"<row key>\", \"relation\": \"<relation>\", \"target\": \"<related row key>\" } — connect two rows through a \"through\" relation.",
412
+ "- unlink { \"operation\": \"unlink\", \"model\": \"<model>\", \"key\": \"<row key>\", \"relation\": \"<relation>\", \"target\": \"<related row key>\" } — disconnect two rows.",
413
+ "- links { \"operation\": \"links\", \"model\": \"<model>\", \"key\": \"<row key>\", \"relation\": \"<relation>\" } — list every key linked to a row through a \"through\" relation.",
414
+ "",
415
+ "\"include\" is a FLAT dot-path array (not nested objects) — each string names a chain of relations to attach, up to the configured depth cap. Example: \"contacts.account\" attaches each row's contacts, and each contact's account.",
416
+ "Example — load a row with two levels of relations:",
417
+ JSON.stringify({
418
+ operation: "load",
419
+ model: "orders",
420
+ key: "1",
421
+ include: ["contacts.account"]
422
+ })
423
+ ].join("\n");
424
+ /** The default cap on rows a `find` / `links` call returns when the caller omits `limit` — the relation tool's default row ceiling. */
425
+ var RELATION_TOOL_LIMIT = 1e3;
426
+ /** The default cap on how many `include` path segments deep a `load` / `find` call may traverse — the relation tool's default include-depth ceiling. */
427
+ var RELATION_TOOL_DEPTH = 3;
240
428
  //#endregion
241
429
  //#region src/core/errors.ts
242
430
  /**
243
431
  * Thrown by {@link import('./factories.js').createAgentTool}'s and
244
432
  * {@link import('./factories.js').createDescribeTool}'s handlers on every failure path — a
245
- * malformed / unresolvable call or an unknown tool name (`TOOL`), or a delegation that would
246
- * exceed the configured depth bound or re-enter an ancestor (`DEPTH`).
433
+ * malformed / unresolvable call or an unknown tool name (`TOOL`), a delegation that would
434
+ * exceed the configured depth bound or re-enter an ancestor (`DEPTH`), a prompt cycle
435
+ * (`DEADLOCK`), a prompt that expired before it was answered (`EXPIRE`), or an answer that
436
+ * failed to apply (`ANSWER`) — the last three thrown by
437
+ * {@link import('./factories.js').createPromptTool} / {@link import('./factories.js').createAnswerTool}.
438
+ * The upcoming database / relation tools (SRC-1's later units) will throw it too: a typed
439
+ * `@orkestrel/database` failure re-surfaces as `DATABASE`, a typed `@orkestrel/relation` failure
440
+ * as `RELATION` — each carrying the package's own granular error code in `context`.
247
441
  *
248
442
  * @remarks
249
443
  * Carries a machine-readable `code` (see {@link import('./types.js').AgentToolErrorCode}) and
@@ -295,6 +489,106 @@ function isAgentToolError(value) {
295
489
  //#endregion
296
490
  //#region src/core/shapers.ts
297
491
  /**
492
+ * The shape of {@link import('./factories.js').createPromptTool}'s call arguments — `to` (the
493
+ * terminal identity to address), `form` (which of the six {@link import('@orkestrel/terminal').PromptType}
494
+ * forms to ask), `message`, an optional `timeout` override, and every per-form optional field
495
+ * FLATTENED onto one object (mirrors `workspaceToolShape`'s flat-arm style, but a single shared
496
+ * shape rather than a discriminated union — `form` alone does not vary the REQUIRED fields, only
497
+ * which of the optional ones apply, so a flat shape stays faithful without duplicating `to` /
498
+ * `message` / `timeout` across six near-identical arms).
499
+ *
500
+ * @remarks
501
+ * `choices` backs `'select'` / `'checkbox'`; `default` backs `'input'` / `'confirm'` / `'select'`
502
+ * (a string for the first two forms' text default, `'true'`/`'false'` string for confirm — the
503
+ * contract layer cannot vary a field's type by a sibling field's value, so `default` stays a
504
+ * string and the handler coerces per form); `mask` backs `'password'`; `min` / `max` backs
505
+ * `'checkbox'`; `validate` (declarative only) backs the four text-shaped forms
506
+ * (`'input'` / `'password'` / `'confirm'` / `'editor'`).
507
+ */
508
+ var promptToolShape = (0, _orkestrel_contract.objectShape)({
509
+ to: (0, _orkestrel_contract.stringShape)({
510
+ min: 1,
511
+ description: "The terminal identity to address the prompt to."
512
+ }),
513
+ form: (0, _orkestrel_contract.literalShape)([
514
+ "input",
515
+ "password",
516
+ "confirm",
517
+ "select",
518
+ "checkbox",
519
+ "editor"
520
+ ], { description: "Which prompt form to ask." }),
521
+ message: (0, _orkestrel_contract.stringShape)({
522
+ min: 1,
523
+ description: "The prompt's question."
524
+ }),
525
+ 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." })),
526
+ choices: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.objectShape)({
527
+ name: (0, _orkestrel_contract.stringShape)({
528
+ min: 1,
529
+ description: "The choice label shown to the answering party."
530
+ }),
531
+ value: (0, _orkestrel_contract.stringShape)({
532
+ min: 1,
533
+ description: "The value submitted when this choice is picked."
534
+ }),
535
+ description: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "An optional one-line elaboration." }))
536
+ }), { description: "The selectable choices for 'select' / 'checkbox'." })),
537
+ mask: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
538
+ min: 1,
539
+ description: "The mask character 'password' renders in place of input."
540
+ })),
541
+ min: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
542
+ min: 0,
543
+ description: "The minimum number of 'checkbox' selections required."
544
+ })),
545
+ max: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
546
+ min: 0,
547
+ description: "The maximum number of 'checkbox' selections allowed."
548
+ })),
549
+ validate: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.objectShape)({
550
+ required: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Reject an empty (trimmed) input." })),
551
+ minimum: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
552
+ min: 0,
553
+ description: "Reject an input shorter than this many characters."
554
+ })),
555
+ maximum: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
556
+ min: 0,
557
+ description: "Reject an input longer than this many characters."
558
+ })),
559
+ pattern: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({ description: "Reject an input that fails this regular-expression source." })),
560
+ email: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require a valid email-address shape." })),
561
+ url: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require a valid URL shape." })),
562
+ numeric: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require a numeric value." })),
563
+ integer: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require an integer value." })),
564
+ alphanumeric: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Require letters and digits only." }))
565
+ })),
566
+ timeout: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
567
+ min: 0,
568
+ description: "Milliseconds to wait before the prompt expires."
569
+ }))
570
+ });
571
+ /**
572
+ * The shape of {@link import('./factories.js').createAnswerTool}'s call arguments — discriminated
573
+ * by `operation`: `'pending'` lists the prompts addressed to this tool's terminal, `'answer'`
574
+ * resolves one by `id` with a `value`.
575
+ *
576
+ * @remarks
577
+ * `value`'s type varies by the ORIGINAL prompt's form (`string` for `'input'` / `'password'` /
578
+ * `'select'` / `'editor'`, `boolean` for `'confirm'`, `readonly string[]` for `'checkbox'`) —
579
+ * `unionShape(stringShape(), booleanShape(), arrayShape(stringShape()))` expresses that
580
+ * union directly, so `value` is typed as the full `string | boolean | readonly string[]` union
581
+ * here (no lossy string-only fallback needed).
582
+ */
583
+ 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)({
584
+ operation: (0, _orkestrel_contract.literalShape)(["answer"], { description: "Answer one pending prompt by id." }),
585
+ id: (0, _orkestrel_contract.stringShape)({
586
+ min: 1,
587
+ description: "The id of the pending prompt to answer."
588
+ }),
589
+ value: (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.stringShape)({ description: "A text / select / editor answer." }), (0, _orkestrel_contract.booleanShape)({ description: "A confirm answer." }), (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)(), { description: "A checkbox answer — the checked values." }))
590
+ }));
591
+ /**
298
592
  * The shape of {@link import('./types.js').AgentToolArguments} —
299
593
  * {@link import('./factories.js').createAgentTool}'s advertised `parameters`.
300
594
  *
@@ -514,6 +808,304 @@ var workspaceToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_cont
514
808
  operation: (0, _orkestrel_contract.literalShape)(["switch"], { description: "Switch the active workspace to the one with this id (get ids from the \"workspaces\" operation). Edit and read operations then target it." }),
515
809
  id: (0, _orkestrel_contract.stringShape)({ description: "The id of the workspace to make active (from the \"workspaces\" listing)." })
516
810
  }));
811
+ /** A {@link import('./types.js').ColumnKind} literal — the leaf {@link columnSpecShape} wraps. */
812
+ var columnKindShape = (0, _orkestrel_contract.literalShape)([
813
+ "string",
814
+ "integer",
815
+ "number",
816
+ "boolean"
817
+ ], { description: "A column type: \"string\" | \"integer\" | \"number\" | \"boolean\"." });
818
+ /** A {@link import('./types.js').ColumnSpec} — a bare {@link columnKindShape}, or `{ type, optional }`. */
819
+ var columnSpecShape = (0, _orkestrel_contract.unionShape)(columnKindShape, (0, _orkestrel_contract.objectShape)({
820
+ type: columnKindShape,
821
+ optional: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Whether the column may be absent from a row." }))
822
+ }));
823
+ /** A {@link import('./types.js').TableSpec} — table name to `{ columns }`, each column a {@link columnSpecShape}. */
824
+ var tableSpecShape = (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.objectShape)({ columns: (0, _orkestrel_contract.recordShape)(columnSpecShape, { description: "Column name to its type." }) }), { description: "Table name to its column layout." });
825
+ /** One key value — a string or number; the array form (multiple keys, positional) resolves FIRST per AGENTS §9.2. */
826
+ var keyShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.stringShape)(), (0, _orkestrel_contract.numberShape)()), { description: "Multiple row keys, positional — a miss at an index is undefined there." }), (0, _orkestrel_contract.stringShape)({ description: "One row key." }), (0, _orkestrel_contract.numberShape)({ description: "One row key." }));
827
+ /** A loose row — a flat object of column name to JSON value; the array form (multiple rows) resolves FIRST per AGENTS §9.2. */
828
+ var rowShape = (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.jsonShape)(), { description: "A row as a flat object of column name to value." });
829
+ /** One or many loose rows — the array form resolves FIRST per AGENTS §9.2. */
830
+ var rowsShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.arrayShape)(rowShape, { description: "Multiple rows." }), rowShape);
831
+ /** One SERIALIZED WHERE condition — `values` is ALWAYS an array, even for a single-value operator. */
832
+ var conditionShape = (0, _orkestrel_contract.objectShape)({
833
+ column: (0, _orkestrel_contract.stringShape)({ description: "The column this condition applies to." }),
834
+ operator: (0, _orkestrel_contract.literalShape)([
835
+ "equals",
836
+ "not",
837
+ "above",
838
+ "below",
839
+ "from",
840
+ "to",
841
+ "between",
842
+ "like",
843
+ "glob",
844
+ "starts",
845
+ "ends",
846
+ "any",
847
+ "none",
848
+ "absent",
849
+ "present"
850
+ ], { description: "The comparison operator." }),
851
+ values: (0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.jsonShape)(), { description: "The operand values the operator needs (always an array, even for one value)." }),
852
+ connector: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)(["and", "or"], { description: "Joins this condition to the next; omit on the last condition." }))
853
+ });
854
+ /** One sort term. */
855
+ var orderShape = (0, _orkestrel_contract.objectShape)({
856
+ column: (0, _orkestrel_contract.stringShape)({ description: "The column to sort by." }),
857
+ direction: (0, _orkestrel_contract.literalShape)(["ascending", "descending"], { description: "The sort direction." })
858
+ });
859
+ /** The SERIALIZED criteria form — conditions, order, and pagination. */
860
+ var criteriaShape = (0, _orkestrel_contract.objectShape)({
861
+ conditions: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)(conditionShape, { description: "The WHERE conditions, folded left to right." })),
862
+ order: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)(orderShape, { description: "The sort terms, applied in order." })),
863
+ limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
864
+ min: 0,
865
+ description: "Max rows to return."
866
+ })),
867
+ offset: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
868
+ min: 0,
869
+ description: "Rows to skip before returning."
870
+ }))
871
+ });
872
+ /**
873
+ * The shape of {@link import('./factories.js').createDatabaseTool}'s call arguments —
874
+ * discriminated by `operation` into the 12 database operations (`'create'` / `'tables'` /
875
+ * `'get'` / `'records'` / `'count'` / `'aggregate'` / `'add'` / `'set'` / `'update'` /
876
+ * `'remove'` / `'migrate'` / `'destroy'`).
877
+ *
878
+ * @remarks
879
+ * Every arm carries `id` (the database id). `'create'` / `'migrate'` carry `tables` (the
880
+ * {@link import('./types.js').TableSpec} column DSL, compiled via
881
+ * {@link import('./helpers.js').expandTables}); `'get'` / `'update'` / `'remove'` carry `key`
882
+ * (one key or an array of keys, positional); `'add'` / `'set'` carry `row` (one row or an array of
883
+ * rows); `'update'` also carries `changes` (a loose partial row); `'records'` / `'count'` /
884
+ * `'aggregate'` carry an optional `criteria` (the SERIALIZED form — `values` is ALWAYS an array,
885
+ * even for a single-value operator, so a caller never chains method calls or guesses arity).
886
+ */
887
+ var databaseToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
888
+ operation: (0, _orkestrel_contract.literalShape)(["create"], { description: "Define a new database." }),
889
+ id: (0, _orkestrel_contract.stringShape)({
890
+ min: 1,
891
+ description: "The database id."
892
+ }),
893
+ tables: tableSpecShape,
894
+ driver: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
895
+ min: 1,
896
+ description: "The registered driver key. Defaults to \"memory\"."
897
+ })),
898
+ keys: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.stringShape)(), { description: "Table name to its primary-key column." }))
899
+ }), (0, _orkestrel_contract.objectShape)({
900
+ operation: (0, _orkestrel_contract.literalShape)(["tables"], { description: "List a database's table names." }),
901
+ id: (0, _orkestrel_contract.stringShape)({
902
+ min: 1,
903
+ description: "The database id."
904
+ })
905
+ }), (0, _orkestrel_contract.objectShape)({
906
+ operation: (0, _orkestrel_contract.literalShape)(["get"], { description: "Fetch one or more rows by primary key." }),
907
+ id: (0, _orkestrel_contract.stringShape)({
908
+ min: 1,
909
+ description: "The database id."
910
+ }),
911
+ table: (0, _orkestrel_contract.stringShape)({
912
+ min: 1,
913
+ description: "The table name."
914
+ }),
915
+ key: keyShape
916
+ }), (0, _orkestrel_contract.objectShape)({
917
+ operation: (0, _orkestrel_contract.literalShape)(["records"], { description: "List rows matching criteria." }),
918
+ id: (0, _orkestrel_contract.stringShape)({
919
+ min: 1,
920
+ description: "The database id."
921
+ }),
922
+ table: (0, _orkestrel_contract.stringShape)({
923
+ min: 1,
924
+ description: "The table name."
925
+ }),
926
+ criteria: (0, _orkestrel_contract.optionalShape)(criteriaShape)
927
+ }), (0, _orkestrel_contract.objectShape)({
928
+ operation: (0, _orkestrel_contract.literalShape)(["count"], { description: "Count rows matching criteria." }),
929
+ id: (0, _orkestrel_contract.stringShape)({
930
+ min: 1,
931
+ description: "The database id."
932
+ }),
933
+ table: (0, _orkestrel_contract.stringShape)({
934
+ min: 1,
935
+ description: "The table name."
936
+ }),
937
+ criteria: (0, _orkestrel_contract.optionalShape)(criteriaShape)
938
+ }), (0, _orkestrel_contract.objectShape)({
939
+ operation: (0, _orkestrel_contract.literalShape)(["aggregate"], { description: "Compute an aggregate over a column." }),
940
+ id: (0, _orkestrel_contract.stringShape)({
941
+ min: 1,
942
+ description: "The database id."
943
+ }),
944
+ table: (0, _orkestrel_contract.stringShape)({
945
+ min: 1,
946
+ description: "The table name."
947
+ }),
948
+ function: (0, _orkestrel_contract.literalShape)([
949
+ "count",
950
+ "sum",
951
+ "average",
952
+ "minimum",
953
+ "maximum"
954
+ ], { description: "The aggregate function." }),
955
+ column: (0, _orkestrel_contract.stringShape)({
956
+ min: 1,
957
+ description: "The column to aggregate."
958
+ }),
959
+ criteria: (0, _orkestrel_contract.optionalShape)(criteriaShape)
960
+ }), (0, _orkestrel_contract.objectShape)({
961
+ operation: (0, _orkestrel_contract.literalShape)(["add"], { description: "Insert one or more rows (fails on a duplicate key)." }),
962
+ id: (0, _orkestrel_contract.stringShape)({
963
+ min: 1,
964
+ description: "The database id."
965
+ }),
966
+ table: (0, _orkestrel_contract.stringShape)({
967
+ min: 1,
968
+ description: "The table name."
969
+ }),
970
+ row: rowsShape
971
+ }), (0, _orkestrel_contract.objectShape)({
972
+ operation: (0, _orkestrel_contract.literalShape)(["set"], { description: "Upsert one or more rows." }),
973
+ id: (0, _orkestrel_contract.stringShape)({
974
+ min: 1,
975
+ description: "The database id."
976
+ }),
977
+ table: (0, _orkestrel_contract.stringShape)({
978
+ min: 1,
979
+ description: "The table name."
980
+ }),
981
+ row: rowsShape
982
+ }), (0, _orkestrel_contract.objectShape)({
983
+ operation: (0, _orkestrel_contract.literalShape)(["update"], { description: "Patch one or more existing rows." }),
984
+ id: (0, _orkestrel_contract.stringShape)({
985
+ min: 1,
986
+ description: "The database id."
987
+ }),
988
+ table: (0, _orkestrel_contract.stringShape)({
989
+ min: 1,
990
+ description: "The table name."
991
+ }),
992
+ key: keyShape,
993
+ changes: rowShape
994
+ }), (0, _orkestrel_contract.objectShape)({
995
+ operation: (0, _orkestrel_contract.literalShape)(["remove"], { description: "Delete one or more rows by key." }),
996
+ id: (0, _orkestrel_contract.stringShape)({
997
+ min: 1,
998
+ description: "The database id."
999
+ }),
1000
+ table: (0, _orkestrel_contract.stringShape)({
1001
+ min: 1,
1002
+ description: "The table name."
1003
+ }),
1004
+ key: keyShape
1005
+ }), (0, _orkestrel_contract.objectShape)({
1006
+ operation: (0, _orkestrel_contract.literalShape)(["migrate"], { description: "Replace the table layout in place." }),
1007
+ id: (0, _orkestrel_contract.stringShape)({
1008
+ min: 1,
1009
+ description: "The database id."
1010
+ }),
1011
+ tables: tableSpecShape
1012
+ }), (0, _orkestrel_contract.objectShape)({
1013
+ operation: (0, _orkestrel_contract.literalShape)(["destroy"], { description: "Drop a database entirely." }),
1014
+ id: (0, _orkestrel_contract.stringShape)({
1015
+ min: 1,
1016
+ description: "The database id."
1017
+ })
1018
+ }));
1019
+ /** One key value — a string or number; the array form (multiple keys, positional) resolves FIRST per AGENTS §9.2. */
1020
+ var relationKeyShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.stringShape)(), (0, _orkestrel_contract.numberShape)()), { description: "Multiple row keys, positional — a miss at an index is undefined there." }), (0, _orkestrel_contract.stringShape)({ description: "One row key." }), (0, _orkestrel_contract.numberShape)({ description: "One row key." }));
1021
+ /** A single row key (not an array) — used by `'link'` / `'unlink'` / `'links'`, which address exactly one owning row. */
1022
+ var singleKeyShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.stringShape)({ description: "The owning row key." }), (0, _orkestrel_contract.numberShape)({ description: "The owning row key." }));
1023
+ /** Flat dot-path relation include list, expanded via {@link import('./helpers.js').expandInclude}. */
1024
+ var includeShape = (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)({ description: "A dot-separated chain of relation names, e.g. \"contacts.account\"." }), { description: "Which relations to attach, as flat dot-paths." }));
1025
+ /** Which registered relation manager to address — omitted resolves to the sole registered manager. */
1026
+ var managerShape = (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
1027
+ min: 1,
1028
+ description: "Which registered relation manager to address."
1029
+ }));
1030
+ /**
1031
+ * The shape of {@link import('./factories.js').createRelationTool}'s call arguments —
1032
+ * discriminated by `operation` into the 5 relation operations (`'load'` / `'find'` / `'link'` /
1033
+ * `'unlink'` / `'links'`).
1034
+ *
1035
+ * @remarks
1036
+ * `'load'` fetches one or more rows (positional key/array) with `include` attached. `'find'`
1037
+ * fetches rows (pagination / sort only) with `include` attached. `'link'` / `'unlink'` write /
1038
+ * remove a `through` junction row; `'links'` lists a `through` relation's linked keys.
1039
+ */
1040
+ var relationToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
1041
+ operation: (0, _orkestrel_contract.literalShape)(["load"], { description: "Fetch one or more rows by key, with related rows attached." }),
1042
+ manager: managerShape,
1043
+ model: (0, _orkestrel_contract.stringShape)({
1044
+ min: 1,
1045
+ description: "The model (table) name."
1046
+ }),
1047
+ key: relationKeyShape,
1048
+ include: includeShape
1049
+ }), (0, _orkestrel_contract.objectShape)({
1050
+ operation: (0, _orkestrel_contract.literalShape)(["find"], { description: "List rows, with related rows attached." }),
1051
+ manager: managerShape,
1052
+ model: (0, _orkestrel_contract.stringShape)({
1053
+ min: 1,
1054
+ description: "The model (table) name."
1055
+ }),
1056
+ include: includeShape,
1057
+ limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
1058
+ min: 0,
1059
+ description: "Max rows to return."
1060
+ })),
1061
+ offset: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
1062
+ min: 0,
1063
+ description: "Rows to skip before returning."
1064
+ })),
1065
+ sort: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.stringShape)({
1066
+ min: 1,
1067
+ description: "The column to sort by."
1068
+ })),
1069
+ direction: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)(["ascending", "descending"], { description: "The sort direction." }))
1070
+ }), (0, _orkestrel_contract.objectShape)({
1071
+ operation: (0, _orkestrel_contract.literalShape)(["link"], { description: "Connect two rows through a \"through\" relation." }),
1072
+ manager: managerShape,
1073
+ model: (0, _orkestrel_contract.stringShape)({
1074
+ min: 1,
1075
+ description: "The model (table) name."
1076
+ }),
1077
+ key: singleKeyShape,
1078
+ relation: (0, _orkestrel_contract.stringShape)({
1079
+ min: 1,
1080
+ description: "The \"through\" relation name."
1081
+ }),
1082
+ target: singleKeyShape
1083
+ }), (0, _orkestrel_contract.objectShape)({
1084
+ operation: (0, _orkestrel_contract.literalShape)(["unlink"], { description: "Disconnect two rows previously linked through a \"through\" relation." }),
1085
+ manager: managerShape,
1086
+ model: (0, _orkestrel_contract.stringShape)({
1087
+ min: 1,
1088
+ description: "The model (table) name."
1089
+ }),
1090
+ key: singleKeyShape,
1091
+ relation: (0, _orkestrel_contract.stringShape)({
1092
+ min: 1,
1093
+ description: "The \"through\" relation name."
1094
+ }),
1095
+ target: singleKeyShape
1096
+ }), (0, _orkestrel_contract.objectShape)({
1097
+ operation: (0, _orkestrel_contract.literalShape)(["links"], { description: "List every key linked to a row through a \"through\" relation." }),
1098
+ manager: managerShape,
1099
+ model: (0, _orkestrel_contract.stringShape)({
1100
+ min: 1,
1101
+ description: "The model (table) name."
1102
+ }),
1103
+ key: singleKeyShape,
1104
+ relation: (0, _orkestrel_contract.stringShape)({
1105
+ min: 1,
1106
+ description: "The \"through\" relation name."
1107
+ })
1108
+ }));
517
1109
  //#endregion
518
1110
  //#region src/core/helpers.ts
519
1111
  /**
@@ -656,6 +1248,453 @@ function expandSteps(flat) {
656
1248
  phases: flat.steps.map((step) => ({ tasks: [{ run: step.name }] }))
657
1249
  });
658
1250
  }
1251
+ /**
1252
+ * Normalize an LLM-supplied answer `value` to the type {@link PromptType} `form` expects, so a
1253
+ * caller that only ever emits strings can still answer a typed prompt.
1254
+ *
1255
+ * @remarks
1256
+ * `'confirm'` coerces to a `boolean` — a `boolean` passes through, and the strings `'true'` /
1257
+ * `'false'` (case-insensitively) map to it; any other string is truthy-coerced via
1258
+ * `Boolean(value)`. `'checkbox'` coerces to `readonly string[]` — an array passes through
1259
+ * (stringifying each entry), a comma-separated string splits + trims into one, and any other
1260
+ * single (non-comma) string becomes a one-item array. Every other form (`'input'` / `'password'`
1261
+ * / `'select'` / `'editor'`) coerces to a plain `string` — a string passes through verbatim; a
1262
+ * non-string, non-object scalar (`number` / `boolean`) stringifies via `String(value)`; an
1263
+ * object or array (no lossless string form) falls back to `''` rather than serializing garbage.
1264
+ * Pure and total — never throws.
1265
+ *
1266
+ * @param form - The {@link PromptType} the answer is being coerced FOR
1267
+ * @param value - The raw, LLM-supplied answer value
1268
+ * @returns The coerced answer — `boolean` for `'confirm'`, `readonly string[]` for `'checkbox'`,
1269
+ * `string` otherwise
1270
+ */
1271
+ function coerceAnswer(form, value) {
1272
+ if (form === "confirm") {
1273
+ if (typeof value === "boolean") return value;
1274
+ if (typeof value === "string") {
1275
+ const lower = value.trim().toLowerCase();
1276
+ if (lower === "true") return true;
1277
+ if (lower === "false") return false;
1278
+ }
1279
+ return Boolean(value);
1280
+ }
1281
+ if (form === "checkbox") {
1282
+ if (Array.isArray(value)) return value.map((entry) => String(entry));
1283
+ if (typeof value === "string") {
1284
+ if (value.includes(",")) return value.split(",").map((entry) => entry.trim());
1285
+ return [value];
1286
+ }
1287
+ return [String(value)];
1288
+ }
1289
+ if (typeof value === "string") return value;
1290
+ if (typeof value === "object" && value !== null) return "";
1291
+ return String(value);
1292
+ }
1293
+ /**
1294
+ * Map a caught error to the {@link AgentToolErrorCode} the terminal-tool factory should throw
1295
+ * with — the pure classification step of that factory's error handling.
1296
+ *
1297
+ * @remarks
1298
+ * Narrows `error` with {@link isTerminalError} (`@orkestrel/terminal`) first: a non-`TerminalError`
1299
+ * value returns `undefined`, telling the caller this mapper does not apply (rethrow / handle
1300
+ * otherwise). For a genuine `TerminalError`, `'DEADLOCK'` maps to `'DEADLOCK'`, `'EXPIRE'` maps
1301
+ * to `'EXPIRE'`, and every other {@link import('@orkestrel/terminal').TerminalErrorCode}
1302
+ * (`'TARGET'`, `'CANCEL'`, `'DRIVER'`) maps to the generic `'TOOL'` code. The mapper only
1303
+ * classifies — the factory performs the actual throw.
1304
+ *
1305
+ * @param error - The value caught from a terminal-manager operation (`ask` / `answer` / …)
1306
+ * @returns The mapped {@link AgentToolErrorCode}, or `undefined` if `error` is not a `TerminalError`
1307
+ */
1308
+ function terminalToolCode(error) {
1309
+ if (!(0, _orkestrel_terminal.isTerminalError)(error)) return void 0;
1310
+ if (error.code === "DEADLOCK") return "DEADLOCK";
1311
+ if (error.code === "EXPIRE") return "EXPIRE";
1312
+ return "TOOL";
1313
+ }
1314
+ /** Narrow an unknown value to a {@link ColumnSpec} — a valid {@link import('./types.js').ColumnKind} shorthand, or `{ type, optional }` with a valid `type`. */
1315
+ function isColumnSpec(value) {
1316
+ if (isColumnKind(value)) return true;
1317
+ if (!(0, _orkestrel_contract.isRecord)(value)) return false;
1318
+ return isColumnKind(value.type) && (value.optional === void 0 || typeof value.optional === "boolean");
1319
+ }
1320
+ /** Narrow an unknown value to a {@link import('./types.js').ColumnKind}. */
1321
+ function isColumnKind(value) {
1322
+ return value === "string" || value === "integer" || value === "number" || value === "boolean";
1323
+ }
1324
+ /**
1325
+ * Compile a {@link TableSpec} into the `@orkestrel/database` {@link TablesShape} it configures —
1326
+ * each {@link ColumnSpec} maps to the matching primitive shaper (`'string'` → `stringShape()`,
1327
+ * `'integer'` → `integerShape()`, `'number'` → `numberShape()`, `'boolean'` → `booleanShape()`),
1328
+ * wrapped in `optionalShape` when the column declares `optional: true`. Total, pure.
1329
+ *
1330
+ * @param spec - The small-model-facing table layout
1331
+ * @returns The compiled `TablesShape` a `@orkestrel/database` `createDatabase` call accepts
1332
+ */
1333
+ function expandTables(spec) {
1334
+ const tables = {};
1335
+ for (const [table, definition] of Object.entries(spec)) {
1336
+ const columns = {};
1337
+ for (const [column, kind] of Object.entries(definition.columns)) columns[column] = columnShape(kind);
1338
+ tables[table] = columns;
1339
+ }
1340
+ return tables;
1341
+ }
1342
+ /** Compile one {@link ColumnSpec} into its `@orkestrel/database` column shape — the per-column leaf {@link expandTables} maps over. */
1343
+ function columnShape(spec) {
1344
+ const kind = (0, _orkestrel_contract.isString)(spec) ? spec : spec.type;
1345
+ const optional = !(0, _orkestrel_contract.isString)(spec) && spec.optional === true;
1346
+ const shape = kindShape(kind);
1347
+ return optional ? (0, _orkestrel_contract.optionalShape)(shape) : shape;
1348
+ }
1349
+ /** Map one {@link import('./types.js').ColumnKind} to its primitive `@orkestrel/database` shape — the leaf {@link columnShape} wraps. */
1350
+ function kindShape(kind) {
1351
+ if (kind === "string") return (0, _orkestrel_contract.stringShape)();
1352
+ if (kind === "integer") return (0, _orkestrel_contract.integerShape)();
1353
+ if (kind === "number") return (0, _orkestrel_contract.numberShape)();
1354
+ return (0, _orkestrel_contract.booleanShape)();
1355
+ }
1356
+ /**
1357
+ * Narrow an unknown value to a {@link DatabaseDefinition} — a non-empty `id` + `driver`, a
1358
+ * `tables` record whose every value is `{ columns: record of valid ColumnSpec }`, and an optional
1359
+ * `keys` record of strings. The boundary guard a {@link import('./types.js').DefinitionStoreInterface}
1360
+ * applies to an untrusted persisted blob before trusting it as a definition (never an `as`).
1361
+ */
1362
+ function isDatabaseDefinition(value) {
1363
+ if (!(0, _orkestrel_contract.isRecord)(value)) return false;
1364
+ if (!(0, _orkestrel_contract.isNonEmptyString)(value.id) || !(0, _orkestrel_contract.isNonEmptyString)(value.driver)) return false;
1365
+ if (!(0, _orkestrel_contract.isRecord)(value.tables)) return false;
1366
+ for (const table of Object.values(value.tables)) {
1367
+ if (!(0, _orkestrel_contract.isRecord)(table) || !(0, _orkestrel_contract.isRecord)(table.columns)) return false;
1368
+ for (const column of Object.values(table.columns)) if (!isColumnSpec(column)) return false;
1369
+ }
1370
+ if (value.keys !== void 0) {
1371
+ if (!(0, _orkestrel_contract.isRecord)(value.keys)) return false;
1372
+ for (const key of Object.values(value.keys)) if (!(0, _orkestrel_contract.isString)(key)) return false;
1373
+ }
1374
+ return true;
1375
+ }
1376
+ /**
1377
+ * Map a caught error to the {@link AgentToolErrorCode} the upcoming database tool should throw
1378
+ * with — the pure classification step of that factory's error handling, mirroring
1379
+ * {@link terminalToolCode}'s idiom for `@orkestrel/database`.
1380
+ *
1381
+ * @param error - The value caught from a `@orkestrel/database` table operation
1382
+ * @returns The granular {@link DatabaseErrorCode}, or `undefined` if `error` is not a `DatabaseError`
1383
+ */
1384
+ function databaseToolCode(error) {
1385
+ return (0, _orkestrel_database.isDatabaseError)(error) ? error.code : void 0;
1386
+ }
1387
+ /**
1388
+ * Map a caught error to the {@link AgentToolErrorCode} the upcoming relation tool should throw
1389
+ * with — the pure classification step of that factory's error handling, mirroring
1390
+ * {@link terminalToolCode}'s idiom for `@orkestrel/relation`.
1391
+ *
1392
+ * @param error - The value caught from a `@orkestrel/relation` operation
1393
+ * @returns The granular {@link RelationErrorCode}, or `undefined` if `error` is not a `RelationError`
1394
+ */
1395
+ function relationToolCode(error) {
1396
+ return (0, _orkestrel_relation.isRelationError)(error) ? error.code : void 0;
1397
+ }
1398
+ /**
1399
+ * Expand the relation tool's FLAT dot-path `include` list into a live `@orkestrel/relation`
1400
+ * {@link Include} tree — the pure leaf {@link import('./factories.js').createRelationTool} calls
1401
+ * before a `'load'` / `'find'` call.
1402
+ *
1403
+ * @remarks
1404
+ * Each path splits on `'.'` into a chain of relation names, deep-merged into one nested
1405
+ * `Include` object with a leaf `true`. A longer path SUBSUMES a shorter sibling's bare `true` —
1406
+ * `'contacts'` followed by `'contacts.account'` yields `{ contacts: { account: true } }`, never
1407
+ * overwriting the deeper chain. An EMPTY segment (`''`, from a leading/trailing/doubled `.`) or a
1408
+ * path whose segment count exceeds `depth` throws a typed `TOOL` {@link AgentToolError}.
1409
+ *
1410
+ * @param paths - The flat dot-path `include` list (or `undefined` — yields `{}`)
1411
+ * @param depth - The max segment count a single path may reach
1412
+ * @returns The equivalent nested {@link Include}
1413
+ *
1414
+ * @example
1415
+ * ```ts
1416
+ * import { expandInclude } from '@src/core'
1417
+ *
1418
+ * expandInclude(['contacts', 'contacts.account'], 3)
1419
+ * // { contacts: { account: true } }
1420
+ * ```
1421
+ */
1422
+ function expandInclude(paths, depth) {
1423
+ function merge(base, segments) {
1424
+ const [head, ...rest] = segments;
1425
+ const existing = base[head];
1426
+ if (rest.length === 0) return {
1427
+ ...base,
1428
+ [head]: existing === void 0 ? true : existing
1429
+ };
1430
+ const nextBase = typeof existing === "object" ? existing : {};
1431
+ return {
1432
+ ...base,
1433
+ [head]: merge(nextBase, rest)
1434
+ };
1435
+ }
1436
+ let include = {};
1437
+ for (const path of paths ?? []) {
1438
+ const segments = path.split(".");
1439
+ if (segments.length > depth || segments.some((segment) => segment.length === 0)) throw new AgentToolError("TOOL", `malformed include path '${path}'`, {
1440
+ path,
1441
+ depth
1442
+ });
1443
+ include = merge(include, segments);
1444
+ }
1445
+ return include;
1446
+ }
1447
+ /**
1448
+ * Resolve which registered {@link RelationManagerInterface} a relation-tool call addresses — the
1449
+ * pure manager-resolution leaf {@link import('./factories.js').createRelationTool} calls on
1450
+ * every operation.
1451
+ *
1452
+ * @remarks
1453
+ * An explicit `name` must match a key of `managers` (a miss throws a typed `TOOL`
1454
+ * {@link AgentToolError} naming the registered managers). An OMITTED `name` resolves to the sole
1455
+ * registered manager when exactly one is registered, else throws the same typed error.
1456
+ *
1457
+ * @param managers - The tool's registered `RelationManagerInterface` map
1458
+ * @param name - The call's optional `manager` field
1459
+ * @returns The resolved {@link RelationManagerInterface}
1460
+ */
1461
+ function relationManagerOf(managers, name) {
1462
+ if (name !== void 0) {
1463
+ const manager = managers[name];
1464
+ if (manager === void 0) throw new AgentToolError("TOOL", `unknown relation manager '${name}'`, {
1465
+ manager: name,
1466
+ managers: Object.keys(managers)
1467
+ });
1468
+ return manager;
1469
+ }
1470
+ const names = Object.keys(managers);
1471
+ if (names.length === 1) return managers[names[0]];
1472
+ throw new AgentToolError("TOOL", "no relation manager resolved for the call", { managers: names });
1473
+ }
1474
+ /**
1475
+ * Resolve a `model` name against a live {@link RelationManagerInterface} — the pure model-lookup
1476
+ * leaf {@link import('./factories.js').createRelationTool} calls on every operation, mirroring
1477
+ * {@link relationManagerOf}'s guard shape.
1478
+ *
1479
+ * @param manager - The resolved {@link RelationManagerInterface}
1480
+ * @param name - The call's `model` field
1481
+ * @returns The model's {@link ModelInterface}
1482
+ */
1483
+ function relationModelOf(manager, name) {
1484
+ if (!manager.has(name)) throw new AgentToolError("TOOL", `unknown model '${name}'`, {
1485
+ model: name,
1486
+ models: manager.models()
1487
+ });
1488
+ return manager.model(name);
1489
+ }
1490
+ /**
1491
+ * Normalize the database tool's parsed SERIALIZED criteria into a live `@orkestrel/database`
1492
+ * {@link Criteria} — default each condition's OMITTED `connector` to `'and'`.
1493
+ *
1494
+ * @remarks
1495
+ * The wire form ({@link import('./shapers.js').databaseToolShape}) lets a caller drop `connector`
1496
+ * on the last condition (it has nothing to join FORWARD to); the compiled `Condition` a live
1497
+ * `@orkestrel/database` table call accepts always carries one, so this fills the gap. `order` /
1498
+ * `limit` / `offset` pass through unchanged. Pure and total.
1499
+ *
1500
+ * @param criteria - The parsed criteria (or `undefined`)
1501
+ * @returns The equivalent live `Criteria`, or `undefined` when `criteria` is `undefined`
1502
+ */
1503
+ function criteriaOf(criteria) {
1504
+ if (criteria === void 0) return void 0;
1505
+ const conditions = criteria.conditions?.map((condition) => ({
1506
+ ...condition,
1507
+ connector: condition.connector ?? "and"
1508
+ }));
1509
+ return {
1510
+ ...conditions === void 0 ? {} : { conditions },
1511
+ ...criteria.order === void 0 ? {} : { order: criteria.order },
1512
+ ...criteria.limit === void 0 ? {} : { limit: criteria.limit },
1513
+ ...criteria.offset === void 0 ? {} : { offset: criteria.offset }
1514
+ };
1515
+ }
1516
+ /**
1517
+ * Clamp a `'records'` call's criteria to a row cap, and build the PROBE criteria the caller reads
1518
+ * with — the pure leaf {@link import('./factories.js').createDatabaseTool}'s `'records'` operation
1519
+ * uses to detect truncation without a separate `count` round trip.
1520
+ *
1521
+ * @remarks
1522
+ * The effective limit is `min(criteria?.limit ?? cap, cap)`, floored at `0` (so a caller can never
1523
+ * exceed the configured cap by supplying a larger `criteria.limit`). The returned probe criteria
1524
+ * requests ONE MORE row than the effective limit (`limit: effective + 1`) — if storage returns
1525
+ * that many, the caller knows the true result was truncated (`rows.length > effective`) and slices
1526
+ * back down to `effective` before returning.
1527
+ *
1528
+ * @example
1529
+ * ```ts
1530
+ * import { clampCriteria } from '@src/core'
1531
+ *
1532
+ * const { criteria, limit } = clampCriteria(undefined, 100)
1533
+ * // limit === 100, criteria.limit === 101 — a probe fetching one extra row
1534
+ * const rows = await table.records(criteria)
1535
+ * const truncated = rows.length > limit // true when storage had more than `limit` rows
1536
+ * ```
1537
+ *
1538
+ * @param criteria - The live criteria to clamp (or `undefined`)
1539
+ * @param cap - The row-count ceiling
1540
+ * @returns The PROBE criteria (`limit` bumped by one) and the effective `limit`
1541
+ */
1542
+ function clampCriteria(criteria, cap) {
1543
+ const limit = Math.max(0, Math.min(criteria?.limit ?? cap, cap));
1544
+ return {
1545
+ criteria: {
1546
+ ...criteria,
1547
+ limit: limit + 1
1548
+ },
1549
+ limit
1550
+ };
1551
+ }
1552
+ /** Map a column NAME + its live `@orkestrel/database` `ContractShape` to a {@link ColumnSchema} — the leaf {@link tableSchema} maps over. */
1553
+ function columnSchema(name, shape) {
1554
+ return {
1555
+ name,
1556
+ type: (0, _orkestrel_database.shapeToColumnType)(shape),
1557
+ nullable: shape.type === "optional" || shape.type === "nullable"
1558
+ };
1559
+ }
1560
+ /**
1561
+ * Build one {@link TableSchema} from a table NAME and its `@orkestrel/database` `TableExport` —
1562
+ * the "deployed" schema shape `DatabaseInterface.migrate` diffs against, derived from a LIVE
1563
+ * handle's `export()` rather than a re-declared {@link TableSpec}, so it works for ANY handle
1564
+ * (config-tracked or caller-supplied).
1565
+ *
1566
+ * @param name - The table name
1567
+ * @param table - The table's `TableExport` (`{ key, columns }`, `@orkestrel/database`)
1568
+ * @returns The equivalent {@link TableSchema} (`indexes` empty — this package declares none)
1569
+ */
1570
+ function tableSchema(name, table) {
1571
+ return {
1572
+ name,
1573
+ primary: table.key,
1574
+ columns: Object.entries(table.columns).map(([column, shape]) => columnSchema(column, shape)),
1575
+ indexes: []
1576
+ };
1577
+ }
1578
+ //#endregion
1579
+ //#region src/core/stores/MemoryDefinitionStore.ts
1580
+ /**
1581
+ * The in-memory {@link DefinitionStoreInterface} — a process-lifetime `Map` of
1582
+ * {@link DatabaseDefinition}s keyed by database id, the DEFAULT store
1583
+ * {@link import('../factories.js').createMemoryDefinitionStore} builds. The EXACT twin of
1584
+ * {@link import('./DatabaseDefinitionStore.js').DatabaseDefinitionStore}.
1585
+ *
1586
+ * @remarks
1587
+ * A plain `Map<string, DatabaseDefinition>` (AGENTS §21 — the definition is already pure,
1588
+ * self-contained CONFIG-only JSON, so no encoding is needed for the memory tier). There is NO
1589
+ * idle-TTL and NO eviction: a persisted definition lives until an explicit `delete`. A durable
1590
+ * backend (JSON / SQLite / IndexedDB) swaps in through the SAME interface without touching a
1591
+ * consumer — its driver-pluggable twin is
1592
+ * {@link import('./DatabaseDefinitionStore.js').DatabaseDefinitionStore} (the definition as one
1593
+ * opaque JSON column).
1594
+ *
1595
+ * - **`get` resolves the persisted definition for an id**, or `undefined` if none is stored.
1596
+ * - **`set` inserts / replaces under the definition's OWN `id`** (no separate id param).
1597
+ * - **`delete` drops a definition by id**; an absent id is a no-op (no throw).
1598
+ *
1599
+ * The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method
1600
+ * bijection with {@link DefinitionStoreInterface}).
1601
+ *
1602
+ * @example
1603
+ * ```ts
1604
+ * import { createMemoryDefinitionStore } from '@src/core'
1605
+ *
1606
+ * const store = createMemoryDefinitionStore()
1607
+ * await store.set({ id: 'shop', driver: 'memory', tables: {} })
1608
+ * const definition = await store.get('shop')
1609
+ * await store.delete('shop')
1610
+ * ```
1611
+ */
1612
+ var MemoryDefinitionStore = class {
1613
+ #definitions = /* @__PURE__ */ new Map();
1614
+ get(id) {
1615
+ return Promise.resolve(this.#definitions.get(id));
1616
+ }
1617
+ set(definition) {
1618
+ this.#definitions.set(definition.id, definition);
1619
+ return Promise.resolve();
1620
+ }
1621
+ delete(id) {
1622
+ this.#definitions.delete(id);
1623
+ return Promise.resolve();
1624
+ }
1625
+ };
1626
+ //#endregion
1627
+ //#region src/core/stores/DatabaseDefinitionStore.ts
1628
+ /**
1629
+ * A {@link DefinitionStoreInterface} backed by one table of the `@orkestrel/database` layer — a
1630
+ * database's durable CONFIG state IS a row, so persistence reduces to keyed point-access
1631
+ * (`get` / `set` / `delete`) over a {@link TableInterface}, the driver-pluggable twin of the
1632
+ * plain-`Map` {@link import('./MemoryDefinitionStore.js').MemoryDefinitionStore}.
1633
+ *
1634
+ * @remarks
1635
+ * The store is driver-agnostic: it holds a single {@link TableInterface} whose backend (memory,
1636
+ * JSON, SQLite, IndexedDB) is chosen by whoever builds it (the factories), so a JSON / SQLite /
1637
+ * IndexedDB backend swaps in WITHOUT touching a consumer — the same seam as
1638
+ * {@link import('./MemoryDefinitionStore.js').MemoryDefinitionStore}. The driver defaults to
1639
+ * memory ({@link import('../factories.js').createDatabaseDefinitionStore} passes
1640
+ * `createMemoryDriver()`), so it ALSO works in memory out of the box; you opt into the durable
1641
+ * plumbing by passing a JSON / SQLite / IndexedDB driver.
1642
+ *
1643
+ * The {@link DatabaseDefinition} is stored as ONE OPAQUE JSON COLUMN — the table is a row of
1644
+ * `{ id; definition }` ({@link DatabaseDefinitionRow}). The definition is already a COMPLETE,
1645
+ * self-contained, pure-JSON CONFIG payload (never a live handle), so storing it whole is lossless
1646
+ * AND keeps the row type flat (`definition` reads back as `unknown`).
1647
+ *
1648
+ * - **`set(definition)` upserts under the definition's OWN `id`** (no separate id param) — it
1649
+ * writes the row `{ id: definition.id, definition }`.
1650
+ * - **`get(id)` resolves the stored definition for an id**, narrowing the opaque JSON column back
1651
+ * to a {@link DatabaseDefinition} ({@link import('../helpers.js').isDatabaseDefinition} — the
1652
+ * AGENTS §14 boundary narrow for an untrusted storage read), or `undefined` if none is stored
1653
+ * or the stored blob is malformed.
1654
+ * - **`delete(id)` drops a definition by id**; an absent id is a no-op (no throw).
1655
+ *
1656
+ * The public surface is EXACTLY `get` / `set` / `delete` — no extra members (the §22 method
1657
+ * bijection with {@link DefinitionStoreInterface}).
1658
+ *
1659
+ * @example
1660
+ * ```ts
1661
+ * import { createDatabaseDefinitionStore, createMemoryDriver } from '@src/core'
1662
+ *
1663
+ * const store = createDatabaseDefinitionStore(createMemoryDriver()) // a durable driver swaps in here
1664
+ * await store.set({ id: 'shop', driver: 'memory', tables: {} }) // persist the config (one JSON column)
1665
+ * const definition = await store.get('shop')
1666
+ * await store.delete('shop')
1667
+ * ```
1668
+ */
1669
+ var DatabaseDefinitionStore = class {
1670
+ #table;
1671
+ /**
1672
+ * Wrap a table as a definition store.
1673
+ *
1674
+ * @param table - The {@link TableInterface} holding the definitions — its row is the
1675
+ * {@link DatabaseDefinitionRow} `{ id; definition }` shape (the definition one opaque JSON column)
1676
+ */
1677
+ constructor(table) {
1678
+ this.#table = table;
1679
+ }
1680
+ /** Resolve the persisted definition for `id`, narrowing the opaque JSON column back to a `DatabaseDefinition`. */
1681
+ async get(id) {
1682
+ const row = await this.#table.get(id);
1683
+ if (row === void 0) return void 0;
1684
+ return isDatabaseDefinition(row.definition) ? row.definition : void 0;
1685
+ }
1686
+ /** Insert or replace under the definition's OWN `id` (no separate id param) — the row is `{ id, definition }`. */
1687
+ async set(definition) {
1688
+ await this.#table.set({
1689
+ id: definition.id,
1690
+ definition
1691
+ });
1692
+ }
1693
+ /** Drop a definition by id; an absent id is a no-op (no throw). */
1694
+ async delete(id) {
1695
+ await this.#table.remove(id);
1696
+ }
1697
+ };
659
1698
  //#endregion
660
1699
  //#region src/core/factories.ts
661
1700
  /**
@@ -1194,16 +2233,621 @@ function createDescribeTool(tools) {
1194
2233
  }
1195
2234
  });
1196
2235
  }
2236
+ /**
2237
+ * Build an LLM-callable prompt tool — the ASK side of the terminal seam. Asks
2238
+ * {@link import('./types.js').PromptToolOptions.to} a question and BLOCKS until it answers,
2239
+ * returning the resolved answer value.
2240
+ *
2241
+ * @remarks
2242
+ * The universal tool-handler contract (AGENTS §14): validates the call args against
2243
+ * {@link import('./shapers.js').promptToolShape}, dispatches to the matching
2244
+ * `TerminalManagerInterface.ask` overload (`@orkestrel/terminal`) for the call's `form`, and
2245
+ * RETURNS the resolved answer on success. `from` is FIXED at construction
2246
+ * ({@link import('./types.js').PromptToolOptions.from}) — never read from the model-supplied
2247
+ * args — so a model cannot spoof which terminal is asking. A prompt CYCLE rejects with
2248
+ * `TerminalError('DEADLOCK')`, re-surfaced as a typed `DEADLOCK`
2249
+ * {@link import('./errors.js').AgentToolError}; an expired prompt re-surfaces as `EXPIRE`; an
2250
+ * unknown `to` (or any other `TerminalError`) re-surfaces as `TOOL`, naming the unknown terminal
2251
+ * plus the known ones (`manager.terminals()`).
2252
+ *
2253
+ * @param options - The live manager, the fixed `from` identity, and advertised overrides (see
2254
+ * {@link import('./types.js').PromptToolOptions})
2255
+ * @returns A `ToolInterface` (named {@link import('./constants.js').PROMPT_TOOL_NAME} by default)
2256
+ *
2257
+ * @example
2258
+ * ```ts
2259
+ * import { createPromptTool } from '@src/core'
2260
+ * import { createTerminalManager, createToolManager } from '@orkestrel/terminal'
2261
+ *
2262
+ * const manager = createTerminalManager()
2263
+ * manager.add('agent')
2264
+ * manager.add('reviewer')
2265
+ * const tool = createPromptTool({ manager, from: 'agent' })
2266
+ * const tools = createToolManager()
2267
+ * tools.add(tool) // the agent can now ask 'reviewer' and block for the answer
2268
+ * ```
2269
+ */
2270
+ function createPromptTool(options) {
2271
+ const contract = (0, _orkestrel_contract.createContract)(promptToolShape);
2272
+ const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2273
+ return (0, _orkestrel_agent.createTool)({
2274
+ name: options.name ?? "ask",
2275
+ description: options.description ?? PROMPT_TOOL_DESCRIPTION,
2276
+ summary: PROMPT_TOOL_SUMMARY,
2277
+ parameters,
2278
+ execute: async (args) => {
2279
+ const call = contract.parse(args);
2280
+ if (call === void 0) throw new AgentToolError("TOOL", "malformed ask call", { args });
2281
+ if ((call.form === "select" || call.form === "checkbox") && (call.choices ?? []).length === 0) throw new AgentToolError("TOOL", "select/checkbox requires at least one choice", {
2282
+ to: call.to,
2283
+ form: call.form
2284
+ });
2285
+ try {
2286
+ switch (call.form) {
2287
+ case "input": return await options.manager.ask(options.from, call.to, call.form, {
2288
+ message: call.message,
2289
+ ...call.default === void 0 ? {} : { default: call.default },
2290
+ ...call.validate === void 0 ? {} : { validate: call.validate }
2291
+ });
2292
+ case "editor": return await options.manager.ask(options.from, call.to, call.form, {
2293
+ message: call.message,
2294
+ ...call.default === void 0 ? {} : { default: call.default },
2295
+ ...call.validate === void 0 ? {} : { validate: call.validate }
2296
+ });
2297
+ case "password": return await options.manager.ask(options.from, call.to, call.form, {
2298
+ message: call.message,
2299
+ ...call.mask === void 0 ? {} : { mask: call.mask },
2300
+ ...call.validate === void 0 ? {} : { validate: call.validate }
2301
+ });
2302
+ case "confirm": return await options.manager.ask(options.from, call.to, call.form, {
2303
+ message: call.message,
2304
+ ...call.default === void 0 ? {} : { default: call.default === "true" }
2305
+ });
2306
+ case "select": return await options.manager.ask(options.from, call.to, call.form, {
2307
+ message: call.message,
2308
+ choices: call.choices ?? [],
2309
+ ...call.default === void 0 ? {} : { default: call.default }
2310
+ });
2311
+ case "checkbox": return await options.manager.ask(options.from, call.to, call.form, {
2312
+ message: call.message,
2313
+ choices: call.choices ?? [],
2314
+ ...call.min === void 0 ? {} : { min: call.min },
2315
+ ...call.max === void 0 ? {} : { max: call.max }
2316
+ });
2317
+ }
2318
+ } catch (error) {
2319
+ const code = terminalToolCode(error);
2320
+ if (code === void 0) throw error;
2321
+ if (code === "DEADLOCK") throw new AgentToolError("DEADLOCK", `asking '${call.to}' would form a prompt cycle`, (0, _orkestrel_terminal.isTerminalError)(error) ? error.context : {
2322
+ from: options.from,
2323
+ to: call.to
2324
+ });
2325
+ if (code === "EXPIRE") throw new AgentToolError("EXPIRE", `prompt to '${call.to}' expired before it was answered`, { to: call.to });
2326
+ if ((0, _orkestrel_terminal.isTerminalError)(error) && error.code === "TARGET") throw new AgentToolError("TOOL", `unknown terminal '${call.to}'`, {
2327
+ to: call.to,
2328
+ known: options.manager.terminals()
2329
+ });
2330
+ throw new AgentToolError("TOOL", `asking '${call.to}' failed`, { to: call.to });
2331
+ }
2332
+ }
2333
+ });
2334
+ }
2335
+ /**
2336
+ * Build an LLM-callable answer tool — the ANSWER side of the terminal seam. Lists the prompts
2337
+ * currently addressed to {@link import('./types.js').AnswerToolOptions.to}, or answers one of
2338
+ * them by id.
2339
+ *
2340
+ * @remarks
2341
+ * The universal tool-handler contract (AGENTS §14): validates the call args against
2342
+ * {@link import('./shapers.js').answerToolShape} (discriminated by `operation`). `'pending'`
2343
+ * returns a compact list (`{ id, from, form, message }`) of every prompt currently addressed to
2344
+ * `to` (`TerminalManagerInterface.pending`, `@orkestrel/terminal`). `'answer'` looks the prompt
2345
+ * up by `id` (an unknown id throws a typed `ANSWER` {@link import('./errors.js').AgentToolError}),
2346
+ * normalizes the model-supplied `value` to the prompt's own form
2347
+ * ({@link import('./helpers.js').coerceAnswer}), and applies it via
2348
+ * `TerminalManagerInterface.answer` — a rejected / unknown / unresolvable outcome
2349
+ * (`TerminalAnswerResult.error`) re-surfaces as a typed `ANSWER` `AgentToolError`; success returns
2350
+ * `{ answered: id }`. `to` is FIXED at construction
2351
+ * ({@link import('./types.js').AnswerToolOptions.to}) — never read from the model-supplied args —
2352
+ * so a model cannot spoof which terminal it is answering for. Concurrent answerers racing on one
2353
+ * endpoint are FIRST-WRITE-WINS — a late answer to an already-settled prompt returns a typed
2354
+ * `ANSWER` `AgentToolError` (surfaced as a 422 over HTTP).
2355
+ *
2356
+ * @param options - The live manager, the fixed `to` identity, and advertised overrides (see
2357
+ * {@link import('./types.js').AnswerToolOptions})
2358
+ * @returns A `ToolInterface` (named {@link import('./constants.js').ANSWER_TOOL_NAME} by default)
2359
+ *
2360
+ * @example
2361
+ * ```ts
2362
+ * import { createAnswerTool } from '@src/core'
2363
+ * import { createTerminalManager, createToolManager } from '@orkestrel/terminal'
2364
+ *
2365
+ * const manager = createTerminalManager()
2366
+ * manager.add('reviewer')
2367
+ * const tool = createAnswerTool({ manager, to: 'reviewer' })
2368
+ * const tools = createToolManager()
2369
+ * tools.add(tool) // the reviewer terminal can now list/answer prompts addressed to it
2370
+ * ```
2371
+ */
2372
+ function createAnswerTool(options) {
2373
+ const contract = (0, _orkestrel_contract.createContract)(answerToolShape);
2374
+ const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2375
+ return (0, _orkestrel_agent.createTool)({
2376
+ name: options.name ?? "answer",
2377
+ description: options.description ?? ANSWER_TOOL_DESCRIPTION,
2378
+ summary: ANSWER_TOOL_SUMMARY,
2379
+ parameters,
2380
+ execute: async (args) => {
2381
+ const call = contract.parse(args);
2382
+ if (call === void 0) throw new AgentToolError("TOOL", "malformed answer call", { args });
2383
+ if (call.operation === "pending") return options.manager.pending(options.to).map((prompt) => ({
2384
+ id: prompt.id,
2385
+ from: prompt.from,
2386
+ form: prompt.form,
2387
+ message: prompt.message
2388
+ }));
2389
+ const prompt = options.manager.pending(options.to).find((entry) => entry.id === call.id);
2390
+ if (prompt === void 0) throw new AgentToolError("ANSWER", `unknown prompt '${call.id}'`, {
2391
+ id: call.id,
2392
+ reason: "unknown"
2393
+ });
2394
+ const coerced = coerceAnswer(prompt.form, call.value);
2395
+ const result = options.manager.answer(options.to, call.id, coerced);
2396
+ if (!result.success) throw new AgentToolError("ANSWER", `failed to answer prompt '${call.id}': ${result.error}`, {
2397
+ id: call.id,
2398
+ reason: result.error
2399
+ });
2400
+ return { answered: call.id };
2401
+ }
2402
+ });
2403
+ }
2404
+ /**
2405
+ * Create the in-memory {@link DefinitionStoreInterface} — a process-lifetime `Map` of database
2406
+ * definitions, the DEFAULT store the upcoming database / relation tools will persist their
2407
+ * `DatabaseDefinition` configs through.
2408
+ *
2409
+ * @returns A {@link DefinitionStoreInterface}
2410
+ *
2411
+ * @example
2412
+ * ```ts
2413
+ * import { createMemoryDefinitionStore } from '@src/core'
2414
+ *
2415
+ * const store = createMemoryDefinitionStore()
2416
+ * ```
2417
+ */
2418
+ function createMemoryDefinitionStore() {
2419
+ return new MemoryDefinitionStore();
2420
+ }
2421
+ /**
2422
+ * Create a {@link DefinitionStoreInterface} backed by one table of the `@orkestrel/database`
2423
+ * layer — the driver-pluggable twin of {@link createMemoryDefinitionStore}, storing each
2424
+ * database's definition as one opaque JSON column.
2425
+ *
2426
+ * @param driver - The {@link DriverInterface} backing the table (default an in-memory driver)
2427
+ * @returns A {@link DefinitionStoreInterface}
2428
+ *
2429
+ * @example
2430
+ * ```ts
2431
+ * import { createDatabaseDefinitionStore } from '@src/core'
2432
+ *
2433
+ * const store = createDatabaseDefinitionStore() // in-memory by default
2434
+ * ```
2435
+ */
2436
+ function createDatabaseDefinitionStore(driver = (0, _orkestrel_database.createMemoryDriver)()) {
2437
+ return new DatabaseDefinitionStore((0, _orkestrel_database.createDatabase)({
2438
+ driver,
2439
+ tables: { definitions: {
2440
+ id: (0, _orkestrel_contract.stringShape)(),
2441
+ definition: (0, _orkestrel_contract.rawShape)({})
2442
+ } }
2443
+ }).table("definitions"));
2444
+ }
2445
+ /**
2446
+ * Build an LLM-callable database tool — create, query, and mutate `@orkestrel/database`
2447
+ * databases through one `operation`-discriminated call (AGENTS §14, matching
2448
+ * {@link createWorkspaceTool}'s single-tool-many-operations shape).
2449
+ *
2450
+ * @remarks
2451
+ * The universal tool-handler contract (AGENTS §14): validates the call args against
2452
+ * {@link import('./shapers.js').databaseToolShape}, dispatches to the matching operation, and
2453
+ * RETURNS a plain result on success. A database is resolved lazily and cached for the tool's
2454
+ * lifetime — `'create'` mints one from `tables` ({@link import('./helpers.js').expandTables}) and
2455
+ * a registered `driver` key ({@link import('./types.js').DatabaseToolOptions.drivers}, default
2456
+ * `{ memory: () => createMemoryDriver() }`); any other operation addressing an uncached id falls
2457
+ * back to {@link import('./types.js').DatabaseToolOptions.store} (an unknown id throws a typed
2458
+ * `TOOL` {@link import('./errors.js').AgentToolError}). When a `store` is configured, `'create'`
2459
+ * persists the new {@link import('./types.js').DatabaseDefinition} and `'destroy'` deletes it.
2460
+ *
2461
+ * `'migrate'` re-declares a LIVE handle's tables via `DatabaseInterface.import` (the SAME driver
2462
+ * and storage, a NEW typed view) and calls its `migrate` against the OLD deployed schema —
2463
+ * derived from the handle's OWN `export()` (via {@link import('./helpers.js').tableSchema}), so it
2464
+ * works for any handle, config-tracked or caller-supplied via
2465
+ * {@link import('./types.js').DatabaseToolOptions.databases}. `'records'` clamps its `criteria` to
2466
+ * {@link import('./types.js').DatabaseToolOptions.limit} (default
2467
+ * {@link import('./constants.js').DATABASE_TOOL_LIMIT}) via
2468
+ * {@link import('./helpers.js').clampCriteria}, reporting `truncated` when storage held more rows
2469
+ * than the cap. Every operation's `criteria` is normalized via
2470
+ * {@link import('./helpers.js').criteriaOf} (defaults an omitted condition `connector` to `'and'`).
2471
+ * When {@link import('./types.js').DatabaseToolOptions.readonly} is `true`, every mutating
2472
+ * operation throws a typed `TOOL` `AgentToolError` before doing anything. When
2473
+ * {@link import('./types.js').DatabaseToolOptions.timeout} is set, every `@orkestrel/database` call
2474
+ * this tool makes is given a fresh `AbortSignal.timeout(timeout)`. A typed `@orkestrel/database`
2475
+ * failure (`DatabaseError`) re-surfaces as a typed `DATABASE` `AgentToolError` carrying the
2476
+ * original {@link import('@orkestrel/database').DatabaseErrorCode} in `context.code`
2477
+ * ({@link import('./helpers.js').databaseToolCode}); an `AgentToolError` thrown by this tool's own
2478
+ * guards passes through unwrapped.
2479
+ *
2480
+ * A lazily re-minted database over the DEFAULT in-memory driver yields an EMPTY database — only
2481
+ * the {@link import('./types.js').DatabaseDefinition} schema persists in `store`, never rows;
2482
+ * durable rows need a persistent driver factory registered in
2483
+ * {@link import('./types.js').DatabaseToolOptions.drivers}. `'destroy'` closes whatever handle is
2484
+ * cached for the id, including an embedder-supplied
2485
+ * {@link import('./types.js').DatabaseToolOptions.databases} handle — the embedder relinquishes
2486
+ * that handle's lifecycle to this tool for any id it wires in. This tool assumes the
2487
+ * single-writer, non-reentrant model `@orkestrel/database` itself assumes — concurrent calls
2488
+ * against one id are NOT serialized by this tool. `'get'` is uncapped by
2489
+ * {@link import('./types.js').DatabaseToolOptions.limit} (bounded only by the caller's `key` array
2490
+ * size), unlike `'records'` / `'find'` / `'links'`.
2491
+ *
2492
+ * @param options - The tool's configuration (see {@link import('./types.js').DatabaseToolOptions})
2493
+ * @returns A `ToolInterface` (named {@link import('./constants.js').DATABASE_TOOL_NAME} by default)
2494
+ *
2495
+ * @example
2496
+ * ```ts
2497
+ * import { createDatabaseTool } from '@src/core'
2498
+ *
2499
+ * const tool = createDatabaseTool()
2500
+ * await tool.execute({
2501
+ * operation: 'create',
2502
+ * id: 'shop',
2503
+ * tables: { products: { columns: { name: 'string', price: 'number' } } },
2504
+ * })
2505
+ * ```
2506
+ */
2507
+ function createDatabaseTool(options = {}) {
2508
+ const contract = (0, _orkestrel_contract.createContract)(databaseToolShape);
2509
+ const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2510
+ const handles = new Map(Object.entries(options.databases ?? {}));
2511
+ const definitions = /* @__PURE__ */ new Map();
2512
+ const drivers = options.drivers ?? { memory: () => (0, _orkestrel_database.createMemoryDriver)() };
2513
+ const key = options.key ?? _orkestrel_database.generateUUID;
2514
+ const cap = options.limit ?? 1e3;
2515
+ const store = options.store;
2516
+ async function resolve(id) {
2517
+ const cached = handles.get(id);
2518
+ if (cached !== void 0) return cached;
2519
+ if (store !== void 0) {
2520
+ const definition = await store.get(id);
2521
+ if (definition !== void 0) {
2522
+ const factory = drivers[definition.driver];
2523
+ if (factory === void 0) throw new AgentToolError("TOOL", `unknown driver '${definition.driver}'`, {
2524
+ id,
2525
+ driver: definition.driver
2526
+ });
2527
+ const handle = (0, _orkestrel_database.createDatabase)({
2528
+ driver: factory(),
2529
+ tables: expandTables(definition.tables),
2530
+ ...definition.keys === void 0 ? {} : { keys: definition.keys },
2531
+ key
2532
+ });
2533
+ handles.set(id, handle);
2534
+ definitions.set(id, definition);
2535
+ return handle;
2536
+ }
2537
+ }
2538
+ throw new AgentToolError("TOOL", `unknown database '${id}'`, { id });
2539
+ }
2540
+ return (0, _orkestrel_agent.createTool)({
2541
+ name: options.name ?? "database",
2542
+ description: options.description ?? DATABASE_TOOL_DESCRIPTION,
2543
+ summary: DATABASE_TOOL_SUMMARY,
2544
+ parameters,
2545
+ execute: async (args) => {
2546
+ const call = contract.parse(args);
2547
+ if (call === void 0) throw new AgentToolError("TOOL", "malformed database call", { args });
2548
+ if (options.readonly === true && DATABASE_TOOL_MUTATIONS.has(call.operation)) throw new AgentToolError("TOOL", `operation '${call.operation}' is disabled in readonly mode`, { operation: call.operation });
2549
+ const read = options.timeout === void 0 ? void 0 : { signal: AbortSignal.timeout(options.timeout) };
2550
+ try {
2551
+ switch (call.operation) {
2552
+ case "create": {
2553
+ if (handles.has(call.id) || store !== void 0 && await store.get(call.id) !== void 0) throw new AgentToolError("TOOL", `database '${call.id}' already exists`, { id: call.id });
2554
+ const name = call.driver ?? "memory";
2555
+ const factory = drivers[name];
2556
+ if (factory === void 0) throw new AgentToolError("TOOL", `unknown driver '${name}'`, {
2557
+ id: call.id,
2558
+ driver: name
2559
+ });
2560
+ const tables = call.tables;
2561
+ const keys = call.keys;
2562
+ const handle = (0, _orkestrel_database.createDatabase)({
2563
+ driver: factory(),
2564
+ tables: expandTables(tables),
2565
+ ...keys === void 0 ? {} : { keys },
2566
+ key
2567
+ });
2568
+ handles.set(call.id, handle);
2569
+ const definition = {
2570
+ id: call.id,
2571
+ driver: name,
2572
+ tables,
2573
+ ...keys === void 0 ? {} : { keys }
2574
+ };
2575
+ definitions.set(call.id, definition);
2576
+ if (store !== void 0) await store.set(definition);
2577
+ return {
2578
+ id: call.id,
2579
+ tables: Object.keys(tables)
2580
+ };
2581
+ }
2582
+ case "tables": {
2583
+ const handle = await resolve(call.id);
2584
+ return { tables: Object.keys(handle.export()).map((name) => {
2585
+ const table = handle.table(name);
2586
+ return {
2587
+ name,
2588
+ primary: table.primary,
2589
+ columns: table.contract.schema
2590
+ };
2591
+ }) };
2592
+ }
2593
+ case "get": {
2594
+ const table = (await resolve(call.id)).table(call.table);
2595
+ const many = Array.isArray(call.key);
2596
+ const keys = Array.isArray(call.key) ? call.key : [call.key];
2597
+ const rows = await table.get(keys);
2598
+ return many ? { rows } : { row: rows[0] };
2599
+ }
2600
+ case "records": {
2601
+ const table = (await resolve(call.id)).table(call.table);
2602
+ const { criteria: probe, limit } = clampCriteria(criteriaOf(call.criteria), cap);
2603
+ const rows = await table.records(probe, read);
2604
+ const truncated = rows.length > limit;
2605
+ const sliced = rows.slice(0, limit);
2606
+ return {
2607
+ rows: sliced,
2608
+ count: sliced.length,
2609
+ truncated,
2610
+ limit
2611
+ };
2612
+ }
2613
+ case "count": return { count: await (await resolve(call.id)).table(call.table).count(criteriaOf(call.criteria), read) };
2614
+ case "aggregate": return { value: await (await resolve(call.id)).table(call.table).aggregate(call.function, call.column, criteriaOf(call.criteria), read) };
2615
+ case "add": {
2616
+ const table = (await resolve(call.id)).table(call.table);
2617
+ const many = Array.isArray(call.row);
2618
+ const rows = Array.isArray(call.row) ? call.row : [call.row];
2619
+ const keys = await table.add(rows, read);
2620
+ return many ? { keys } : { key: keys[0] };
2621
+ }
2622
+ case "set": {
2623
+ const table = (await resolve(call.id)).table(call.table);
2624
+ const many = Array.isArray(call.row);
2625
+ const rows = Array.isArray(call.row) ? call.row : [call.row];
2626
+ const keys = await table.set(rows, read);
2627
+ return many ? { keys } : { key: keys[0] };
2628
+ }
2629
+ case "update": {
2630
+ const table = (await resolve(call.id)).table(call.table);
2631
+ const changes = call.changes;
2632
+ const many = Array.isArray(call.key);
2633
+ const keys = Array.isArray(call.key) ? call.key : [call.key];
2634
+ const updated = await table.update(keys, changes, read);
2635
+ return many ? { updated } : { updated: updated[0] };
2636
+ }
2637
+ case "remove": {
2638
+ const table = (await resolve(call.id)).table(call.table);
2639
+ const many = Array.isArray(call.key);
2640
+ const keys = Array.isArray(call.key) ? call.key : [call.key];
2641
+ const removed = await table.remove(keys, read);
2642
+ return many ? { removed } : { removed: removed[0] };
2643
+ }
2644
+ case "migrate": {
2645
+ const handle = await resolve(call.id);
2646
+ const previous = handle.export();
2647
+ const deployed = Object.entries(previous).map(([name, table]) => tableSchema(name, table));
2648
+ const tables = call.tables;
2649
+ const keys = {};
2650
+ for (const name of Object.keys(tables)) {
2651
+ const existing = previous[name];
2652
+ if (existing !== void 0) keys[name] = existing.key;
2653
+ }
2654
+ const declared = expandTables(tables);
2655
+ const migrated = handle.import(declared, Object.keys(keys).length > 0 ? keys : void 0);
2656
+ const migration = await migrated.migrate(deployed, read);
2657
+ handles.set(call.id, migrated);
2658
+ const tracked = definitions.get(call.id);
2659
+ if (tracked !== void 0) {
2660
+ const updated = {
2661
+ id: call.id,
2662
+ driver: tracked.driver,
2663
+ tables,
2664
+ ...Object.keys(keys).length > 0 ? { keys } : {}
2665
+ };
2666
+ definitions.set(call.id, updated);
2667
+ if (store !== void 0) await store.set(updated);
2668
+ }
2669
+ return { migration };
2670
+ }
2671
+ case "destroy": {
2672
+ const cached = handles.get(call.id);
2673
+ const persisted = store !== void 0 && cached === void 0 ? await store.get(call.id) !== void 0 : false;
2674
+ if (cached !== void 0) {
2675
+ await cached.close();
2676
+ handles.delete(call.id);
2677
+ }
2678
+ definitions.delete(call.id);
2679
+ if (store !== void 0) await store.delete(call.id);
2680
+ return {
2681
+ id: call.id,
2682
+ destroyed: cached !== void 0 || persisted
2683
+ };
2684
+ }
2685
+ }
2686
+ } catch (error) {
2687
+ if (isAgentToolError(error)) throw error;
2688
+ const code = databaseToolCode(error);
2689
+ if (code === void 0) throw error;
2690
+ throw new AgentToolError("DATABASE", error instanceof Error ? error.message : String(error), {
2691
+ code,
2692
+ operation: call.operation,
2693
+ id: call.id,
2694
+ ..."table" in call ? { table: call.table } : {}
2695
+ });
2696
+ }
2697
+ }
2698
+ });
2699
+ }
2700
+ /**
2701
+ * Build an LLM-callable relation tool — traverse and edit `@orkestrel/relation` relationships
2702
+ * through one `operation`-discriminated call (AGENTS §14, matching {@link createDatabaseTool}'s
2703
+ * single-tool-many-operations shape).
2704
+ *
2705
+ * @remarks
2706
+ * The universal tool-handler contract (AGENTS §14): validates the call args against
2707
+ * {@link import('./shapers.js').relationToolShape}, resolves the addressed
2708
+ * {@link import('@orkestrel/relation').RelationManagerInterface} — an explicit `manager` field
2709
+ * must match a key of {@link import('./types.js').RelationToolOptions.managers}, an OMITTED one
2710
+ * resolves to the SOLE registered manager, either miss throwing a typed `TOOL`
2711
+ * {@link import('./errors.js').AgentToolError}
2712
+ * ({@link import('./helpers.js').relationManagerOf}) — then resolves `model` against it
2713
+ * ({@link import('./helpers.js').relationModelOf}, same typed-`TOOL`-on-miss shape), and
2714
+ * dispatches to the matched operation, RETURNING a plain result on success.
2715
+ *
2716
+ * `'load'` / `'find'` expand the call's FLAT dot-path `include` list into a live
2717
+ * `@orkestrel/relation` `Include` tree via {@link import('./helpers.js').expandInclude}, capped
2718
+ * at {@link import('./types.js').RelationToolOptions.depth} (default
2719
+ * {@link import('./constants.js').RELATION_TOOL_DEPTH}) — a path exceeding the cap, or carrying an
2720
+ * empty segment, throws a typed `TOOL` error. `'load'` dispatches on whether `key` is an array
2721
+ * (positional many-key form, AGENTS §9.2) or a single key. `'find'` and `'links'` clamp their
2722
+ * result to {@link import('./types.js').RelationToolOptions.limit} (default
2723
+ * {@link import('./constants.js').RELATION_TOOL_LIMIT}) — `'find'` probes one row past the
2724
+ * effective limit (mirroring {@link import('./helpers.js').clampCriteria}'s idiom) to report
2725
+ * `truncated`; `'links'` (which has no upstream pagination) fetches the FULL linked-key list and
2726
+ * slices/truncates it the same way. `'link'` / `'unlink'` write / remove one `through` junction
2727
+ * row.
2728
+ *
2729
+ * A typed `@orkestrel/relation` failure (`RelationError`) re-surfaces as a typed `RELATION`
2730
+ * `AgentToolError` carrying the original {@link import('@orkestrel/relation').RelationErrorCode}
2731
+ * in `context.code`; a typed `@orkestrel/database` failure underneath it (`DatabaseError`)
2732
+ * re-surfaces as a typed `DATABASE` `AgentToolError`, mirroring {@link createDatabaseTool}'s error
2733
+ * mapping; an `AgentToolError` thrown by this tool's own guards (malformed args, an unknown
2734
+ * manager/model) passes through unwrapped.
2735
+ *
2736
+ * @param options - The tool's configuration (see {@link import('./types.js').RelationToolOptions})
2737
+ * @returns A `ToolInterface` (named {@link import('./constants.js').RELATION_TOOL_NAME} by default)
2738
+ *
2739
+ * @example
2740
+ * ```ts
2741
+ * import { createRelationTool } from '@src/core'
2742
+ *
2743
+ * const tool = createRelationTool({ managers: { shop: manager } })
2744
+ * await tool.execute({ operation: 'load', model: 'accounts', key: 'acc1', include: ['contacts'] })
2745
+ * ```
2746
+ */
2747
+ function createRelationTool(options) {
2748
+ const contract = (0, _orkestrel_contract.createContract)(relationToolShape);
2749
+ const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2750
+ const depth = options.depth ?? 3;
2751
+ const cap = options.limit ?? 1e3;
2752
+ return (0, _orkestrel_agent.createTool)({
2753
+ name: options.name ?? "relation",
2754
+ description: options.description ?? RELATION_TOOL_DESCRIPTION,
2755
+ summary: RELATION_TOOL_SUMMARY,
2756
+ parameters,
2757
+ execute: async (args) => {
2758
+ const call = contract.parse(args);
2759
+ if (call === void 0) throw new AgentToolError("TOOL", "malformed relation call", { args });
2760
+ try {
2761
+ const model = relationModelOf(relationManagerOf(options.managers, call.manager), call.model);
2762
+ switch (call.operation) {
2763
+ case "load": {
2764
+ const include = expandInclude(call.include, depth);
2765
+ if (typeof call.key === "string" || typeof call.key === "number") return { row: await model.load(call.key, include) };
2766
+ return { rows: await model.load(call.key, include) };
2767
+ }
2768
+ case "find": {
2769
+ const include = expandInclude(call.include, depth);
2770
+ const effective = Math.min(call.limit ?? cap, cap);
2771
+ const rows = await model.find(include, {
2772
+ limit: effective + 1,
2773
+ ...call.offset === void 0 ? {} : { offset: call.offset },
2774
+ ...call.sort === void 0 ? {} : { sort: call.sort },
2775
+ ...call.direction === void 0 ? {} : { direction: call.direction }
2776
+ });
2777
+ const truncated = rows.length > effective;
2778
+ const sliced = rows.slice(0, effective);
2779
+ return {
2780
+ rows: sliced,
2781
+ count: sliced.length,
2782
+ truncated,
2783
+ limit: effective
2784
+ };
2785
+ }
2786
+ case "link":
2787
+ await model.link(call.key, call.relation, call.target);
2788
+ return { linked: true };
2789
+ case "unlink":
2790
+ await model.unlink(call.key, call.relation, call.target);
2791
+ return { unlinked: true };
2792
+ case "links": {
2793
+ const keys = await model.links(call.key, call.relation);
2794
+ const truncated = keys.length > cap;
2795
+ const sliced = keys.slice(0, cap);
2796
+ return {
2797
+ keys: sliced,
2798
+ count: sliced.length,
2799
+ truncated,
2800
+ limit: cap
2801
+ };
2802
+ }
2803
+ }
2804
+ } catch (error) {
2805
+ if (isAgentToolError(error)) throw error;
2806
+ const relation = relationToolCode(error);
2807
+ if (relation !== void 0) throw new AgentToolError("RELATION", error instanceof Error ? error.message : String(error), {
2808
+ code: relation,
2809
+ operation: call.operation,
2810
+ model: call.model,
2811
+ ..."relation" in call ? { relation: call.relation } : {}
2812
+ });
2813
+ const database = databaseToolCode(error);
2814
+ if (database === void 0) throw error;
2815
+ throw new AgentToolError("DATABASE", error instanceof Error ? error.message : String(error), {
2816
+ code: database,
2817
+ operation: call.operation
2818
+ });
2819
+ }
2820
+ }
2821
+ });
2822
+ }
1197
2823
  //#endregion
1198
2824
  exports.AGENT_TOOL_DEPTH = AGENT_TOOL_DEPTH;
1199
2825
  exports.AGENT_TOOL_DESCRIPTION = AGENT_TOOL_DESCRIPTION;
1200
2826
  exports.AGENT_TOOL_NAME = AGENT_TOOL_NAME;
1201
2827
  exports.AGENT_TOOL_SUMMARY = AGENT_TOOL_SUMMARY;
2828
+ exports.ANSWER_TOOL_DESCRIPTION = ANSWER_TOOL_DESCRIPTION;
2829
+ exports.ANSWER_TOOL_NAME = ANSWER_TOOL_NAME;
2830
+ exports.ANSWER_TOOL_SUMMARY = ANSWER_TOOL_SUMMARY;
1202
2831
  exports.AgentToolError = AgentToolError;
2832
+ exports.DATABASE_TOOL_DESCRIPTION = DATABASE_TOOL_DESCRIPTION;
2833
+ exports.DATABASE_TOOL_LIMIT = DATABASE_TOOL_LIMIT;
2834
+ exports.DATABASE_TOOL_MUTATIONS = DATABASE_TOOL_MUTATIONS;
2835
+ exports.DATABASE_TOOL_NAME = DATABASE_TOOL_NAME;
2836
+ exports.DATABASE_TOOL_SUMMARY = DATABASE_TOOL_SUMMARY;
1203
2837
  exports.DESCRIBE_TOOL_DESCRIPTION = DESCRIBE_TOOL_DESCRIPTION;
1204
2838
  exports.DESCRIBE_TOOL_NAME = DESCRIBE_TOOL_NAME;
1205
2839
  exports.DESCRIBE_TOOL_SUMMARY = DESCRIBE_TOOL_SUMMARY;
2840
+ exports.DatabaseDefinitionStore = DatabaseDefinitionStore;
1206
2841
  exports.MAX_WORKFLOW_DEPTH = MAX_WORKFLOW_DEPTH;
2842
+ exports.MemoryDefinitionStore = MemoryDefinitionStore;
2843
+ exports.PROMPT_TOOL_DESCRIPTION = PROMPT_TOOL_DESCRIPTION;
2844
+ exports.PROMPT_TOOL_NAME = PROMPT_TOOL_NAME;
2845
+ exports.PROMPT_TOOL_SUMMARY = PROMPT_TOOL_SUMMARY;
2846
+ exports.RELATION_TOOL_DEPTH = RELATION_TOOL_DEPTH;
2847
+ exports.RELATION_TOOL_DESCRIPTION = RELATION_TOOL_DESCRIPTION;
2848
+ exports.RELATION_TOOL_LIMIT = RELATION_TOOL_LIMIT;
2849
+ exports.RELATION_TOOL_NAME = RELATION_TOOL_NAME;
2850
+ exports.RELATION_TOOL_SUMMARY = RELATION_TOOL_SUMMARY;
1207
2851
  exports.WORKFLOW_TOOL_DESCRIPTION = WORKFLOW_TOOL_DESCRIPTION;
1208
2852
  exports.WORKFLOW_TOOL_FLAT_EXAMPLE = WORKFLOW_TOOL_FLAT_EXAMPLE;
1209
2853
  exports.WORKFLOW_TOOL_NAME = WORKFLOW_TOOL_NAME;
@@ -1215,22 +2859,62 @@ exports.WORKSPACE_TOOL_NAME = WORKSPACE_TOOL_NAME;
1215
2859
  exports.WORKSPACE_TOOL_SUMMARY = WORKSPACE_TOOL_SUMMARY;
1216
2860
  exports.agentTag = agentTag;
1217
2861
  exports.agentToolShape = agentToolShape;
2862
+ exports.answerToolShape = answerToolShape;
2863
+ exports.clampCriteria = clampCriteria;
2864
+ exports.coerceAnswer = coerceAnswer;
2865
+ exports.columnKindShape = columnKindShape;
2866
+ exports.columnSchema = columnSchema;
2867
+ exports.columnShape = columnShape;
2868
+ exports.columnSpecShape = columnSpecShape;
1218
2869
  exports.completeDraft = completeDraft;
1219
2870
  exports.completePhaseDraft = completePhaseDraft;
1220
2871
  exports.completeTaskDraft = completeTaskDraft;
2872
+ exports.conditionShape = conditionShape;
1221
2873
  exports.createAgentFunction = createAgentFunction;
1222
2874
  exports.createAgentTool = createAgentTool;
2875
+ exports.createAnswerTool = createAnswerTool;
2876
+ exports.createDatabaseDefinitionStore = createDatabaseDefinitionStore;
2877
+ exports.createDatabaseTool = createDatabaseTool;
1223
2878
  exports.createDescribeTool = createDescribeTool;
2879
+ exports.createMemoryDefinitionStore = createMemoryDefinitionStore;
2880
+ exports.createPromptTool = createPromptTool;
2881
+ exports.createRelationTool = createRelationTool;
1224
2882
  exports.createToolFunction = createToolFunction;
1225
2883
  exports.createWorkflowDraftContract = createWorkflowDraftContract;
1226
2884
  exports.createWorkflowTool = createWorkflowTool;
1227
2885
  exports.createWorkspaceTool = createWorkspaceTool;
2886
+ exports.criteriaOf = criteriaOf;
2887
+ exports.criteriaShape = criteriaShape;
2888
+ exports.databaseToolCode = databaseToolCode;
2889
+ exports.databaseToolShape = databaseToolShape;
1228
2890
  exports.describeToolShape = describeToolShape;
2891
+ exports.expandInclude = expandInclude;
1229
2892
  exports.expandSteps = expandSteps;
2893
+ exports.expandTables = expandTables;
2894
+ exports.includeShape = includeShape;
1230
2895
  exports.isAgentToolError = isAgentToolError;
2896
+ exports.isColumnKind = isColumnKind;
2897
+ exports.isColumnSpec = isColumnSpec;
2898
+ exports.isDatabaseDefinition = isDatabaseDefinition;
2899
+ exports.keyShape = keyShape;
2900
+ exports.kindShape = kindShape;
2901
+ exports.managerShape = managerShape;
2902
+ exports.orderShape = orderShape;
1231
2903
  exports.phaseDraftShape = phaseDraftShape;
2904
+ exports.promptToolShape = promptToolShape;
2905
+ exports.relationKeyShape = relationKeyShape;
2906
+ exports.relationManagerOf = relationManagerOf;
2907
+ exports.relationModelOf = relationModelOf;
2908
+ exports.relationToolCode = relationToolCode;
2909
+ exports.relationToolShape = relationToolShape;
2910
+ exports.rowShape = rowShape;
2911
+ exports.rowsShape = rowsShape;
2912
+ exports.singleKeyShape = singleKeyShape;
1232
2913
  exports.stepShape = stepShape;
2914
+ exports.tableSchema = tableSchema;
2915
+ exports.tableSpecShape = tableSpecShape;
1233
2916
  exports.taskDraftShape = taskDraftShape;
2917
+ exports.terminalToolCode = terminalToolCode;
1234
2918
  exports.workflowDraftShape = workflowDraftShape;
1235
2919
  exports.workflowStepsShape = workflowStepsShape;
1236
2920
  exports.workflowTag = workflowTag;