@orkestrel/toolbox 0.0.24 → 0.0.25

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.
@@ -5,8 +5,8 @@ let _orkestrel_terminal = require("@orkestrel/terminal");
5
5
  let _orkestrel_database = require("@orkestrel/database");
6
6
  let _orkestrel_relation = require("@orkestrel/relation");
7
7
  let _orkestrel_agent = require("@orkestrel/agent");
8
- let _orkestrel_workspace = require("@orkestrel/workspace");
9
8
  let _orkestrel_tool = require("@orkestrel/tool");
9
+ let _orkestrel_workspace = require("@orkestrel/workspace");
10
10
  let _orkestrel_form = require("@orkestrel/form");
11
11
  // src/core/constants.ts
12
12
  /**
@@ -191,7 +191,7 @@ var WORKSPACE_TOOL_DESCRIPTION = [
191
191
  "All file operations act on the ACTIVE workspace; use \"workspaces\" then \"switch\" to move between workspaces.",
192
192
  "",
193
193
  "Operations (each takes the fields listed):",
194
- "- read { \"operation\": \"read\", \"path\": \"<file>\" } — return the file's text.",
194
+ "- read { \"operation\": \"read\", \"path\": \"<file>\" } — return the file's text, or null when the path holds no text.",
195
195
  "- list { \"operation\": \"list\" } — list every file in the active workspace (path, state, size, lines, kind).",
196
196
  "- has { \"operation\": \"has\", \"path\": \"<file>\" } — whether the file exists.",
197
197
  "- search { \"operation\": \"search\", \"query\": \"<text>\", \"regex\"?: bool, \"sensitive\"?: bool, \"limit\"?: int } — find lines matching the query across all files.",
@@ -205,7 +205,7 @@ var WORKSPACE_TOOL_DESCRIPTION = [
205
205
  "- workspaces { \"operation\": \"workspaces\" } — list the workspaces you can switch between (each id, file count, active).",
206
206
  "- switch { \"operation\": \"switch\", \"id\": \"<id>\" } — make the workspace with that id active (ids come from \"workspaces\").",
207
207
  "",
208
- "Notes: lines and columns are 1-based (column 1 is the first character). \"regex\" defaults to false (a literal substring), \"sensitive\" defaults to true (case-sensitive). \"search\"/\"replace\"/\"splice\" act only on text files. Editing with no active workspace auto-creates one.",
208
+ "Notes: lines and columns are 1-based (column 1 is the first character). \"regex\" defaults to false (a literal substring), \"sensitive\" defaults to true (case-sensitive). \"search\"/\"replace\"/\"splice\" act only on text files. With no active workspace, \"write\", \"prepend\", and \"append\" create one, and \"replace\", \"splice\", \"move\", and \"remove\" refuse.",
209
209
  "",
210
210
  "Example — write a file:",
211
211
  JSON.stringify(WORKSPACE_TOOL_EXAMPLE)
@@ -681,59 +681,77 @@ function isAgentFunction(value) {
681
681
  return inspected.success && inspected.value;
682
682
  }
683
683
  /**
684
- * Narrows an unknown value to a {@link ColumnSpec}.
684
+ * Narrows an unknown value to a {@link ColumnPrimitive}.
685
+ *
686
+ * @param value - The value to inspect
687
+ * @returns True if `value` is `'string'`, `'integer'`, `'number'`, or `'boolean'`; false otherwise
688
+ */
689
+ var isColumnPrimitive = (0, _orkestrel_contract.literalOf)("string", "integer", "number", "boolean");
690
+ /**
691
+ * Narrows an unknown value to a {@link ColumnSpec} — a bare {@link ColumnPrimitive}, or exactly
692
+ * `{ primitive, optional? }`.
693
+ *
694
+ * @param value - The value to inspect
695
+ * @returns True if `value` is a valid shorthand or an exact record with a valid `primitive`; false otherwise
696
+ */
697
+ var isColumnSpec = (0, _orkestrel_contract.unionOf)(isColumnPrimitive, (0, _orkestrel_contract.recordOf)({
698
+ primitive: isColumnPrimitive,
699
+ optional: _orkestrel_contract.isBoolean
700
+ }, ["optional"]));
701
+ /**
702
+ * Narrows an unknown value to a {@link TableSpec} — table name to exactly `{ columns }`, each column
703
+ * a valid {@link ColumnSpec}.
685
704
  *
686
705
  * @param value - The value to inspect
687
- * @returns True if `value` is a valid {@link ColumnPrimitive} shorthand or a `{ primitive, optional }` record with a valid `primitive`; false otherwise
706
+ * @returns True if `value` is a table layout; false otherwise
688
707
  */
689
- function isColumnSpec(value) {
690
- if (isColumnPrimitive(value)) return true;
691
- if (!(0, _orkestrel_contract.isRecord)(value)) return false;
692
- return isColumnPrimitive(value.primitive) && (value.optional === void 0 || (0, _orkestrel_contract.isBoolean)(value.optional));
708
+ function isTableSpec(value) {
709
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && Object.values(value).every((table) => (0, _orkestrel_contract.isRecord)(table) && Object.keys(table).length === 1 && (0, _orkestrel_contract.isRecord)(table.columns) && Object.values(table.columns).every((column) => isColumnSpec(column))));
693
710
  }
694
711
  /**
695
- * Narrows an unknown value to a {@link ColumnPrimitive}.
712
+ * Narrows an unknown value to a `PrimaryMap` (`@orkestrel/database`) — table name to a nonempty
713
+ * primary-key column name.
696
714
  *
697
715
  * @param value - The value to inspect
698
- * @returns True if `value` is `'string'`, `'integer'`, `'number'`, or `'boolean'`; false otherwise
716
+ * @returns True if every value is a nonempty string; false otherwise
699
717
  */
700
- var isColumnPrimitive = (0, _orkestrel_contract.literalOf)("string", "integer", "number", "boolean");
718
+ function isPrimaryMap(value) {
719
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && Object.values(value).every((key) => (0, _orkestrel_contract.isNonEmptyString)(key)));
720
+ }
701
721
  /**
702
- * Narrows an unknown value to a {@link DatabaseDefinition} — a non-empty `id` and `driver`, a
703
- * `tables` record whose every value is `{ columns: record of valid ColumnSpec }`, plus optional
704
- * `primary`, `indexes`, and finite `version` schema configuration. The boundary guard a
705
- * {@link import('./types.js').DefinitionStoreInterface} applies to an untrusted persisted blob
706
- * before trusting it as a definition (never an `as`).
722
+ * Narrows an unknown value to an `IndexMap` (`@orkestrel/database`) — table name to its index
723
+ * groups, each group a nonempty list of nonempty column names.
707
724
  *
708
725
  * @param value - The value to inspect
709
- * @returns True if `value` is a complete {@link DatabaseDefinition}; false otherwise
726
+ * @returns True if every value is a list of such groups; false otherwise
710
727
  */
711
- function isDatabaseDefinition(value) {
712
- if (!(0, _orkestrel_contract.isRecord)(value)) return false;
713
- if ("keys" in value) return false;
714
- if (!(0, _orkestrel_contract.isNonEmptyString)(value.id) || !(0, _orkestrel_contract.isNonEmptyString)(value.driver)) return false;
715
- if (!(0, _orkestrel_contract.isRecord)(value.tables)) return false;
716
- for (const table of Object.values(value.tables)) {
717
- if (!(0, _orkestrel_contract.isRecord)(table) || !(0, _orkestrel_contract.isRecord)(table.columns)) return false;
718
- for (const column of Object.values(table.columns)) if (!isColumnSpec(column)) return false;
719
- }
720
- if (value.primary !== void 0) {
721
- if (!(0, _orkestrel_contract.isRecord)(value.primary)) return false;
722
- for (const key of Object.values(value.primary)) if (!(0, _orkestrel_contract.isNonEmptyString)(key)) return false;
723
- }
724
- if (value.indexes !== void 0) {
725
- if (!(0, _orkestrel_contract.isRecord)(value.indexes)) return false;
726
- for (const groups of Object.values(value.indexes)) {
727
- if (!(0, _orkestrel_contract.isArray)(groups)) return false;
728
- for (const group of groups) {
729
- if (!(0, _orkestrel_contract.isArray)(group) || group.length === 0) return false;
730
- for (const column of group) if (!(0, _orkestrel_contract.isNonEmptyString)(column)) return false;
731
- }
732
- }
733
- }
734
- if (value.version !== void 0 && !(0, _orkestrel_contract.isFiniteNumber)(value.version)) return false;
735
- return true;
728
+ function isIndexMap(value) {
729
+ return (0, _orkestrel_contract.holds)(() => (0, _orkestrel_contract.isRecord)(value) && Object.values(value).every((groups) => (0, _orkestrel_contract.isArray)(groups) && groups.every((group) => (0, _orkestrel_contract.isArray)(group) && group.length > 0 && group.every((column) => (0, _orkestrel_contract.isNonEmptyString)(column)))));
736
730
  }
731
+ /**
732
+ * Narrows an unknown value to exactly a {@link DatabaseDefinition} — a non-empty `id` and `driver`,
733
+ * a {@link TableSpec}, plus optional `primary`, `indexes`, and finite `version` schema configuration,
734
+ * refusing every other key. The boundary guard a {@link import('./types.js').DefinitionStoreInterface}
735
+ * applies to an untrusted persisted blob before trusting it as a definition (never an `as`).
736
+ *
737
+ * @remarks
738
+ * A lockstep test holds this guard to {@link import('./shapers.js').definitionShape}.
739
+ *
740
+ * @param value - The value to inspect
741
+ * @returns True if `value` is a complete {@link DatabaseDefinition}; false otherwise
742
+ */
743
+ var isDatabaseDefinition = (0, _orkestrel_contract.recordOf)({
744
+ id: _orkestrel_contract.isNonEmptyString,
745
+ driver: _orkestrel_contract.isNonEmptyString,
746
+ tables: isTableSpec,
747
+ primary: isPrimaryMap,
748
+ indexes: isIndexMap,
749
+ version: _orkestrel_contract.isFiniteNumber
750
+ }, [
751
+ "primary",
752
+ "indexes",
753
+ "version"
754
+ ]);
737
755
  // src/core/shapers.ts
738
756
  /**
739
757
  * Describes the shape of {@link import('./factories.js').createPromptTool}'s call arguments — `to` names the
@@ -821,9 +839,9 @@ var taskDraftShape = (0, _orkestrel_contract.objectShape)({
821
839
  description: "Extra attempts after the first on failure; overrides the phase default. Omitted means none."
822
840
  })),
823
841
  timeout: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
824
- min: 0,
842
+ min: 1,
825
843
  max: _orkestrel_workflow.MAX_TIMER_MS,
826
- description: "Persisted per-attempt deadline in milliseconds (0 disables it); omitted means no deadline."
844
+ description: "Per-attempt deadline in milliseconds. Omit the key for no deadline."
827
845
  }))
828
846
  });
829
847
  /**
@@ -843,6 +861,7 @@ var phaseDraftShape = (0, _orkestrel_contract.objectShape)({
843
861
  tasks: (0, _orkestrel_contract.arrayShape)(taskDraftShape, { description: "The phase tasks; they run CONCURRENTLY." }),
844
862
  concurrency: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
845
863
  min: 1,
864
+ max: Number.MAX_SAFE_INTEGER,
846
865
  description: "Max tasks in flight at once (a resource throttle); omitted means unbounded."
847
866
  })),
848
867
  bail: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.literalShape)([true, false], { description: "Per-phase failure-policy override; omitted inherits the workflow bail." }))
@@ -925,13 +944,22 @@ var workflowStepsShape = (0, _orkestrel_contract.objectShape)({
925
944
  */
926
945
  var workspaceToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contract.objectShape)({
927
946
  operation: (0, _orkestrel_contract.literalShape)(["read"], { description: "Read a whole text file's text by path." }),
928
- path: (0, _orkestrel_contract.stringShape)({ description: "The path of the file to read." })
947
+ path: (0, _orkestrel_contract.stringShape)({
948
+ min: 1,
949
+ description: "The path of the file to read."
950
+ })
929
951
  }), (0, _orkestrel_contract.objectShape)({ operation: (0, _orkestrel_contract.literalShape)(["list"], { description: "List every file in the workspace." }) }), (0, _orkestrel_contract.objectShape)({
930
952
  operation: (0, _orkestrel_contract.literalShape)(["has"], { description: "Check whether a file exists at the path." }),
931
- path: (0, _orkestrel_contract.stringShape)({ description: "The path to check for." })
953
+ path: (0, _orkestrel_contract.stringShape)({
954
+ min: 1,
955
+ description: "The path to check for."
956
+ })
932
957
  }), (0, _orkestrel_contract.objectShape)({
933
958
  operation: (0, _orkestrel_contract.literalShape)(["search"], { description: "Search every text file for a query, returning each hit." }),
934
- query: (0, _orkestrel_contract.stringShape)({ description: "The text (or regular-expression source) to search for." }),
959
+ query: (0, _orkestrel_contract.stringShape)({
960
+ min: 1,
961
+ description: "The text (or regular-expression source) to search for."
962
+ }),
935
963
  regex: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Treat the query as a regular expression. Defaults to false (a literal substring)." })),
936
964
  sensitive: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Match case-sensitively. Defaults to true (set false for case-insensitive)." })),
937
965
  limit: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.integerShape)({
@@ -940,7 +968,10 @@ var workspaceToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_cont
940
968
  }))
941
969
  }), (0, _orkestrel_contract.objectShape)({
942
970
  operation: (0, _orkestrel_contract.literalShape)(["replace"], { description: "Replace a query with a replacement across every text file." }),
943
- query: (0, _orkestrel_contract.stringShape)({ description: "The text (or regular-expression source) to replace." }),
971
+ query: (0, _orkestrel_contract.stringShape)({
972
+ min: 1,
973
+ description: "The text (or regular-expression source) to replace."
974
+ }),
944
975
  replacement: (0, _orkestrel_contract.stringShape)({ description: "The text to substitute for each match." }),
945
976
  regex: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Treat the query as a regular expression. Defaults to false (a literal substring)." })),
946
977
  sensitive: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Match case-sensitively. Defaults to true (set false for case-insensitive)." })),
@@ -950,11 +981,17 @@ var workspaceToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_cont
950
981
  }))
951
982
  }), (0, _orkestrel_contract.objectShape)({
952
983
  operation: (0, _orkestrel_contract.literalShape)(["write"], { description: "Create or overwrite a whole file with content." }),
953
- path: (0, _orkestrel_contract.stringShape)({ description: "The path of the file to write." }),
984
+ path: (0, _orkestrel_contract.stringShape)({
985
+ min: 1,
986
+ description: "The path of the file to write."
987
+ }),
954
988
  content: (0, _orkestrel_contract.stringShape)({ description: "The full new contents of the file." })
955
989
  }), (0, _orkestrel_contract.objectShape)({
956
990
  operation: (0, _orkestrel_contract.literalShape)(["splice"], { description: "Replace a 1-based range of an existing text file (from inclusive, to exclusive) with content." }),
957
- path: (0, _orkestrel_contract.stringShape)({ description: "The path of the text file to edit." }),
991
+ path: (0, _orkestrel_contract.stringShape)({
992
+ min: 1,
993
+ description: "The path of the text file to edit."
994
+ }),
958
995
  content: (0, _orkestrel_contract.stringShape)({ description: "The text to splice in place of the range." }),
959
996
  fromLine: (0, _orkestrel_contract.integerShape)({
960
997
  min: 1,
@@ -974,19 +1011,34 @@ var workspaceToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_cont
974
1011
  })
975
1012
  }), (0, _orkestrel_contract.objectShape)({
976
1013
  operation: (0, _orkestrel_contract.literalShape)(["prepend"], { description: "Add content to the start of a file (creating it when absent)." }),
977
- path: (0, _orkestrel_contract.stringShape)({ description: "The path of the file to prepend to." }),
1014
+ path: (0, _orkestrel_contract.stringShape)({
1015
+ min: 1,
1016
+ description: "The path of the file to prepend to."
1017
+ }),
978
1018
  content: (0, _orkestrel_contract.stringShape)({ description: "The text to add at the start of the file." })
979
1019
  }), (0, _orkestrel_contract.objectShape)({
980
1020
  operation: (0, _orkestrel_contract.literalShape)(["append"], { description: "Add content to the end of a file (creating it when absent)." }),
981
- path: (0, _orkestrel_contract.stringShape)({ description: "The path of the file to append to." }),
1021
+ path: (0, _orkestrel_contract.stringShape)({
1022
+ min: 1,
1023
+ description: "The path of the file to append to."
1024
+ }),
982
1025
  content: (0, _orkestrel_contract.stringShape)({ description: "The text to add at the end of the file." })
983
1026
  }), (0, _orkestrel_contract.objectShape)({
984
1027
  operation: (0, _orkestrel_contract.literalShape)(["move"], { description: "Rename or move a file (overwriting an occupied target)." }),
985
- from: (0, _orkestrel_contract.stringShape)({ description: "The current path of the file." }),
986
- to: (0, _orkestrel_contract.stringShape)({ description: "The new path for the file." })
1028
+ from: (0, _orkestrel_contract.stringShape)({
1029
+ min: 1,
1030
+ description: "The current path of the file."
1031
+ }),
1032
+ to: (0, _orkestrel_contract.stringShape)({
1033
+ min: 1,
1034
+ description: "The new path for the file."
1035
+ })
987
1036
  }), (0, _orkestrel_contract.objectShape)({
988
1037
  operation: (0, _orkestrel_contract.literalShape)(["remove"], { description: "Delete a file from the workspace." }),
989
- path: (0, _orkestrel_contract.stringShape)({ description: "The path of the file to remove." })
1038
+ path: (0, _orkestrel_contract.stringShape)({
1039
+ min: 1,
1040
+ description: "The path of the file to remove."
1041
+ })
990
1042
  }), (0, _orkestrel_contract.objectShape)({ operation: (0, _orkestrel_contract.literalShape)(["workspaces"], { description: "List the workspaces you can move between (each id, file count, and whether it is active), so you can pick an id to switch to." }) }), (0, _orkestrel_contract.objectShape)({
991
1043
  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." }),
992
1044
  id: (0, _orkestrel_contract.stringShape)({ description: "The id of the workspace to make active (from the \"workspaces\" listing)." })
@@ -1005,12 +1057,41 @@ var columnSpecShape = (0, _orkestrel_contract.unionShape)(columnPrimitiveShape,
1005
1057
  }));
1006
1058
  /** Describes a {@link import('./types.js').TableSpec} — table name to `{ columns }`, each column a {@link columnSpecShape}. */
1007
1059
  var tableSpecShape = (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.objectShape)({ columns: (0, _orkestrel_contract.recordShape)(columnSpecShape, { description: "Column name to its primitive or its { primitive, optional } spec." }) }), { description: "Table name to its column layout." });
1060
+ /** Describes a {@link import('./types.js').DatabaseDefinition}'s `primary` map — table name to its primary-key column. */
1061
+ var primaryShape = (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.stringShape)({ min: 1 }), { description: "Table name to its primary-key column." });
1062
+ /** Describes a {@link import('./types.js').DatabaseDefinition}'s `indexes` map — table name to its index column groups. */
1063
+ var indexesShape = (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)({ min: 1 }), {
1064
+ min: 1,
1065
+ description: "One nonempty index group of column names."
1066
+ }), { description: "Zero or more index groups declared for the table." }), { description: "Table name to its index column groups." });
1067
+ /**
1068
+ * Describes a stored {@link import('./types.js').DatabaseDefinition} exactly: the owned record
1069
+ * {@link import('./stores/DatabaseDefinitionStore.js').DatabaseDefinitionStore} reads and writes.
1070
+ *
1071
+ * @remarks
1072
+ * {@link import('./validators.js').isDatabaseDefinition} is the guard the store runs, and a lockstep
1073
+ * test holds it to this shape. The compiled contract fills a refusal's faults.
1074
+ */
1075
+ var definitionShape = (0, _orkestrel_contract.objectShape)({
1076
+ id: (0, _orkestrel_contract.stringShape)({
1077
+ min: 1,
1078
+ description: "The database id."
1079
+ }),
1080
+ driver: (0, _orkestrel_contract.stringShape)({
1081
+ min: 1,
1082
+ description: "The registered driver key."
1083
+ }),
1084
+ tables: tableSpecShape,
1085
+ primary: (0, _orkestrel_contract.optionalShape)(primaryShape),
1086
+ indexes: (0, _orkestrel_contract.optionalShape)(indexesShape),
1087
+ version: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.numberShape)({ description: "The schema version for open-time reconciliation." }))
1088
+ });
1008
1089
  /**
1009
1090
  * Describes one key value for the database tool and the relation tool — a string or number; the
1010
1091
  * array form (multiple keys, positional) resolves first, so an array argument is read as many keys
1011
1092
  * rather than one.
1012
1093
  */
1013
- 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." }));
1094
+ 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 null there." }), (0, _orkestrel_contract.stringShape)({ description: "One row key." }), (0, _orkestrel_contract.numberShape)({ description: "One row key." }));
1014
1095
  /** Describes a loose row — a flat object of column name to JSON value. */
1015
1096
  var rowShape = (0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.jsonShape)(), { description: "A row as a flat object of column name to value." });
1016
1097
  /**
@@ -1087,11 +1168,8 @@ var databaseToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contr
1087
1168
  min: 1,
1088
1169
  description: "The registered driver key. Defaults to \"memory\"."
1089
1170
  })),
1090
- primary: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.stringShape)({ min: 1 }), { description: "Table name to its primary-key column." })),
1091
- indexes: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.recordShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.stringShape)({ min: 1 }), {
1092
- min: 1,
1093
- description: "One nonempty index group of column names."
1094
- }), { description: "Zero or more index groups declared for the table." }), { description: "Table name to its index column groups." })),
1171
+ primary: (0, _orkestrel_contract.optionalShape)(primaryShape),
1172
+ indexes: (0, _orkestrel_contract.optionalShape)(indexesShape),
1095
1173
  version: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.numberShape)({ description: "The schema version for open-time reconciliation." }))
1096
1174
  }), (0, _orkestrel_contract.objectShape)({
1097
1175
  operation: (0, _orkestrel_contract.literalShape)(["tables"], { description: "List a database's table names." }),
@@ -1300,8 +1378,8 @@ var relationToolShape = (0, _orkestrel_contract.unionShape)((0, _orkestrel_contr
1300
1378
  * optional `candidates` array to check against the inferred schema.
1301
1379
  *
1302
1380
  * @remarks
1303
- * `samples` requires at least one element (`min: 1`) — an empty array parses to `undefined`,
1304
- * surfaced by the handler as a typed `TOOL` {@link import('./errors.js').ToolboxError}. When
1381
+ * `samples` requires at least one element (`min: 1`) — the Tool door refuses an empty array with
1382
+ * a `ToolError` carrying code `ARGUMENTS` (`@orkestrel/tool`). When
1305
1383
  * `candidates` is present (any array, including empty), the handler compiles a contract from the
1306
1384
  * freshly inferred schema and checks each candidate against it with a strict guard (`.is`, no
1307
1385
  * coercion) — the opposite of {@link import('./factories.js').createEndpointTool}'s normalizing
@@ -1316,6 +1394,20 @@ var inferToolShape = (0, _orkestrel_contract.objectShape)({
1316
1394
  enum: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.booleanShape)({ description: "Infer enum constraints from repeated literal values. Defaults to false." })),
1317
1395
  candidates: (0, _orkestrel_contract.optionalShape)((0, _orkestrel_contract.arrayShape)((0, _orkestrel_contract.jsonShape)(), { description: "Optional values to check against the freshly inferred schema. When present, the tool returns a per-candidate verdict (strict — no coercion) alongside the inferred parameters." }))
1318
1396
  });
1397
+ // src/core/contracts.ts
1398
+ /**
1399
+ * Holds the compiled {@link import('./shapers.js').definitionShape}, whose `audit` fills the faults a
1400
+ * refused stored definition carries.
1401
+ *
1402
+ * @example
1403
+ * ```ts
1404
+ * import { definitionContract } from '@orkestrel/toolbox'
1405
+ *
1406
+ * definitionContract.audit({ id: 'shop', driver: 'memory', tables: {}, keys: {} })
1407
+ * // [{ reason: 'extra', path: ['keys'] }]
1408
+ * ```
1409
+ */
1410
+ var definitionContract = (0, _orkestrel_contract.createContract)(definitionShape);
1319
1411
  // src/core/helpers.ts
1320
1412
  /**
1321
1413
  * Returns the ancestry identifier of a workflow in a run chain — `workflow:<id>`.
@@ -1564,7 +1656,7 @@ function inferRelationCode(error) {
1564
1656
  function expandInclude(paths, depth) {
1565
1657
  return Iterator.from(paths ?? []).reduce((include, path) => {
1566
1658
  const segments = path.split(".");
1567
- if (segments.length > depth || segments.some((segment) => segment.length === 0)) throw new ToolboxError("TOOL", `malformed include path '${path}'`, {
1659
+ if (segments.length > depth || segments.some((segment) => segment.length === 0)) throw new ToolboxError("TOOL", `malformed include path ${(0, _orkestrel_contract.preview)(path)}; send dot-separated relation names with no empty segment and at most ${String(depth)} segments`, {
1568
1660
  path,
1569
1661
  depth
1570
1662
  });
@@ -1620,7 +1712,7 @@ function expandInclude(paths, depth) {
1620
1712
  function resolveRelationManager(managers, name) {
1621
1713
  if (name !== void 0) {
1622
1714
  const manager = managers[name];
1623
- if (manager === void 0) throw new ToolboxError("TOOL", `unknown relation manager '${name}'`, {
1715
+ if (manager === void 0) throw new ToolboxError("TOOL", `unknown relation manager ${(0, _orkestrel_contract.preview)(name)}; send manager as one of ${renderCandidates(Object.keys(managers))}`, {
1624
1716
  manager: name,
1625
1717
  managers: Object.keys(managers)
1626
1718
  });
@@ -1632,7 +1724,7 @@ function resolveRelationManager(managers, name) {
1632
1724
  const manager = managers[single];
1633
1725
  if (manager !== void 0) return manager;
1634
1726
  }
1635
- throw new ToolboxError("TOOL", "no relation manager resolved for the call", { managers: names });
1727
+ throw new ToolboxError("TOOL", `no relation manager resolved for the call; send manager as one of ${renderCandidates(names)}`, { managers: names });
1636
1728
  }
1637
1729
  /**
1638
1730
  * Resolves a `model` name against a live {@link RelationManagerInterface} — the pure model-lookup
@@ -1644,7 +1736,7 @@ function resolveRelationManager(managers, name) {
1644
1736
  * @returns The model's {@link ModelInterface}
1645
1737
  */
1646
1738
  function resolveRelationModel(manager, name) {
1647
- if (!manager.has(name)) throw new ToolboxError("TOOL", `unknown model '${name}'`, {
1739
+ if (!manager.has(name)) throw new ToolboxError("TOOL", `unknown model ${(0, _orkestrel_contract.preview)(name)}; send model as one of ${renderCandidates(manager.names())}`, {
1648
1740
  model: name,
1649
1741
  models: manager.names()
1650
1742
  });
@@ -1738,7 +1830,21 @@ function clampQuery(query, cap) {
1738
1830
  };
1739
1831
  }
1740
1832
  /**
1741
- * Resolves an owned workflow input through the supplied flat or draft contracts.
1833
+ * Resolves an owned workflow input through the supplied flat or draft contracts, or refuses it with
1834
+ * what to send.
1835
+ *
1836
+ * @remarks
1837
+ * Empty input is the wrapped definition. A `phases` key with no `steps` key takes the draft form,
1838
+ * and everything else takes the advertised flat form, so a blob carrying both keys refuses `phases`
1839
+ * as undeclared rather than ignoring it, and a blob carrying neither is told to send `steps`.
1840
+ *
1841
+ * @param owned - The call's owned JSON arguments
1842
+ * @param definition - The wrapped definition empty input runs
1843
+ * @param steps - The compiled flat form
1844
+ * @param draft - The compiled draft form
1845
+ * @returns The definition the input resolves to
1846
+ * @throws {import('@orkestrel/tool').ToolError} Thrown with code `ARGUMENTS` when the chosen form refuses the input, carrying `context.faults`
1847
+ *
1742
1848
  * @example
1743
1849
  * ```ts
1744
1850
  * import { createContract } from '@orkestrel/contract'
@@ -1751,12 +1857,196 @@ function clampQuery(query, cap) {
1751
1857
  */
1752
1858
  function resolveWorkflowTarget(owned, definition, steps, draft) {
1753
1859
  if (Object.keys(owned).length === 0) return definition;
1754
- if ((0, _orkestrel_contract.isArray)(owned.steps)) {
1755
- const flat = steps.parse(owned);
1756
- return flat === void 0 ? void 0 : expandSteps(flat);
1860
+ return Object.hasOwn(owned, "phases") && !Object.hasOwn(owned, "steps") ? completeDraft(requireToolCall(draft, owned)) : expandSteps(requireToolCall(steps, owned));
1861
+ }
1862
+ /**
1863
+ * Renders the candidates a refusal offers, each encoded and length-capped and at most
1864
+ * `REFUSAL_LIST_LIMIT` of them named, so a long or multiline name cannot fake or balloon the message.
1865
+ *
1866
+ * @param names - The candidates to offer
1867
+ * @returns The encoded candidates joined with `, `, empty when there are none
1868
+ *
1869
+ * @example
1870
+ * ```ts
1871
+ * import { renderCandidates } from '@src/core'
1872
+ *
1873
+ * renderCandidates(['read', 'list']) // '"read", "list"'
1874
+ * ```
1875
+ */
1876
+ function renderCandidates(names) {
1877
+ return (0, _orkestrel_contract.renderList)(names.map((name) => (0, _orkestrel_contract.preview)(name)));
1878
+ }
1879
+ /**
1880
+ * Caps the complete text of a refusal at `FAULT_TEXT_LIMIT` characters, saying where it stopped.
1881
+ *
1882
+ * @remarks
1883
+ * A refusal goes back to a model whose context window can be small. Use this where a message joins
1884
+ * parts this package cannot bound one by one: a head beside a report, or an upstream error's text.
1885
+ *
1886
+ * @param text - The refusal text
1887
+ * @returns The text unchanged when it fits, otherwise cut to the limit and well formed
1888
+ *
1889
+ * @example
1890
+ * ```ts
1891
+ * import { limitRefusal } from '@src/core'
1892
+ *
1893
+ * limitRefusal('send steps') // 'send steps'
1894
+ * limitRefusal('x'.repeat(5000)).length // 2048
1895
+ * ```
1896
+ */
1897
+ function limitRefusal(text) {
1898
+ if (text.length <= _orkestrel_contract.FAULT_TEXT_LIMIT) return text;
1899
+ return `${text.slice(0, _orkestrel_contract.FAULT_TEXT_LIMIT - 62).toWellFormed()}… the message stops here; correct what it names and call again`;
1900
+ }
1901
+ /**
1902
+ * Reads a call's arguments through a compiled contract, or refuses them with what to send.
1903
+ *
1904
+ * @remarks
1905
+ * Uses the contract admission door and translates refusals to tool errors.
1906
+ * Tools with object and union roots enforce `createTool`'s `contract` and use this helper to type
1907
+ * the parsed arguments. The workflow tool uses this door under `strict: false` because it accepts
1908
+ * several authoring forms while advertising the flat form.
1909
+ *
1910
+ * @param contract - The compiled contract of the arguments
1911
+ * @param value - The arguments the call sent
1912
+ * @returns The admitted value, which may retain input identity under a union
1913
+ * @throws {import('@orkestrel/tool').ToolError} Thrown with code `ARGUMENTS`, carrying `context.faults` when the arguments could be read
1914
+ *
1915
+ * @example
1916
+ * ```ts
1917
+ * import { createContract, numberShape, objectShape } from '@orkestrel/contract'
1918
+ * import { requireToolCall } from '@src/core'
1919
+ *
1920
+ * const contract = createContract(objectShape({ amount: numberShape() }))
1921
+ * requireToolCall(contract, { amount: '3' }) // { amount: 3 }
1922
+ * requireToolCall(contract, { amount: 3, extra: 1 }) // throws ToolError ('ARGUMENTS')
1923
+ * ```
1924
+ */
1925
+ function requireToolCall(contract, value) {
1926
+ const read = (0, _orkestrel_contract.admitValue)(contract, value);
1927
+ if (!read.success) {
1928
+ if (read.error.cause === void 0) throw buildArgumentRefusal(contract.schema, value, read.error.faults);
1929
+ throw new _orkestrel_tool.ToolError("ARGUMENTS", "Arguments could not be read; send one JSON object of plain values", void 0, { cause: read.error.cause });
1757
1930
  }
1758
- const parsed = draft.parse(owned);
1759
- return parsed === void 0 ? void 0 : completeDraft(parsed);
1931
+ return read.value.value;
1932
+ }
1933
+ /**
1934
+ * Builds an argument refusal from the supplied faults, auditing the schema when no faults were supplied.
1935
+ *
1936
+ * @remarks
1937
+ * Uses the contract's union reporting: a selected arm names its own faults, and a value selecting
1938
+ * no arm receives a bounded summary of alternatives. Supplied faults remain unchanged.
1939
+ *
1940
+ * @param schema - The compiled schema the arguments are held to
1941
+ * @param value - The arguments the call sent
1942
+ * @param faults - The faults admission reported
1943
+ * @returns The `ToolError` to throw, with code `ARGUMENTS` and the faults in `context.faults`
1944
+ *
1945
+ * @example
1946
+ * ```ts
1947
+ * import { createContract, literalShape, objectShape, stringShape, unionShape } from '@orkestrel/contract'
1948
+ * import { buildArgumentRefusal } from '@src/core'
1949
+ *
1950
+ * const { schema } = createContract(
1951
+ * unionShape(
1952
+ * objectShape({ operation: literalShape(['read']), path: stringShape() }),
1953
+ * objectShape({ operation: literalShape(['list']) }),
1954
+ * ),
1955
+ * )
1956
+ * buildArgumentRefusal(schema, { operation: 'read' }, []).message
1957
+ * // 'path: missing; expected string'
1958
+ * ```
1959
+ */
1960
+ function buildArgumentRefusal(schema, value, faults) {
1961
+ const listed = faults.length === 0 ? (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.schemaToShape)(schema)).audit(value) : faults;
1962
+ return new _orkestrel_tool.ToolError("ARGUMENTS", (0, _orkestrel_contract.describeFaults)(listed, schema), { faults: listed });
1963
+ }
1964
+ /**
1965
+ * Picks the effective value of a positive numeric tool option: the configured value when it is a
1966
+ * positive safe integer, and the fallback when it is omitted.
1967
+ *
1968
+ * @param value - The configured option, or `undefined`
1969
+ * @param fallback - The default an omitted option takes
1970
+ * @param subject - The name the refusal gives the option
1971
+ * @returns The effective value
1972
+ * @throws {ToolboxError} Thrown with code `TOOL` when `value` is present and is anything else, because `NaN` as a bound passes every comparison
1973
+ *
1974
+ * @example
1975
+ * ```ts
1976
+ * import { resolveBound } from '@src/core'
1977
+ *
1978
+ * resolveBound(undefined, 100, 'database limit') // 100
1979
+ * resolveBound(25, 100, 'database limit') // 25
1980
+ * resolveBound(0, 100, 'database limit') // throws ToolboxError ('TOOL')
1981
+ * ```
1982
+ */
1983
+ function resolveBound(value, fallback, subject) {
1984
+ if (value === void 0) return fallback;
1985
+ if ((0, _orkestrel_agent.isPositiveSafeInteger)(value)) return value;
1986
+ throw new ToolboxError("TOOL", `${subject} must be a positive safe integer; received ${(0, _orkestrel_contract.preview)(value)}; omit it for the default ${String(fallback)}`, { received: (0, _orkestrel_contract.preview)(value) });
1987
+ }
1988
+ /**
1989
+ * Reads the active workspace for an operation that edits a file that must already exist, or refuses.
1990
+ *
1991
+ * @param manager - The manager the workspace tool drives
1992
+ * @param operation - The operation that needs an active workspace
1993
+ * @returns The active workspace
1994
+ * @throws {ToolboxError} Thrown with code `TOOL` when no workspace is active, and creates none
1995
+ *
1996
+ * @example
1997
+ * ```ts
1998
+ * import { createWorkspaceManager } from '@orkestrel/workspace'
1999
+ * import { requireActiveWorkspace } from '@src/core'
2000
+ *
2001
+ * const manager = createWorkspaceManager()
2002
+ * const workspace = manager.add()
2003
+ * requireActiveWorkspace(manager, 'remove') === workspace // true
2004
+ * ```
2005
+ */
2006
+ function requireActiveWorkspace(manager, operation) {
2007
+ const active = manager.active;
2008
+ if (active !== void 0) return active;
2009
+ throw new ToolboxError("TOOL", `operation ${(0, _orkestrel_contract.preview)(operation)} needs an active workspace and none is; call "write" to create one, or "workspaces" then "switch"`, { operation });
2010
+ }
2011
+ /**
2012
+ * Describes a stored definition that names another id, naming the id it holds and the id it must hold.
2013
+ *
2014
+ * @param id - The database id the row is stored under
2015
+ * @param held - The id the stored definition names
2016
+ * @returns The refusal message
2017
+ *
2018
+ * @example
2019
+ * ```ts
2020
+ * import { describeDefinitionMismatch } from '@src/core'
2021
+ *
2022
+ * describeDefinitionMismatch('shop', 'other')
2023
+ * // 'database "shop" holds a definition that names id "other"; its id must be "shop"; ...'
2024
+ * ```
2025
+ */
2026
+ function describeDefinitionMismatch(id, held) {
2027
+ const required = (0, _orkestrel_contract.preview)(id);
2028
+ return `database ${required} holds a definition that names id ${(0, _orkestrel_contract.preview)(held)}; its id must be ${required}; read the row through your own table and set its id to ${required}, or delete it with store.delete(${required})`;
2029
+ }
2030
+ /**
2031
+ * Describes a stored definition this package refuses to read, naming its first fault and the remedy.
2032
+ *
2033
+ * @param id - The database id the row is stored under
2034
+ * @param faults - The refused definition's faults
2035
+ * @returns The refusal message
2036
+ *
2037
+ * @example
2038
+ * ```ts
2039
+ * import { describeDefinitionRefusal } from '@src/core'
2040
+ *
2041
+ * describeDefinitionRefusal('shop', [{ reason: 'extra', path: ['keys'] }])
2042
+ * // 'database "shop" holds a definition this version refuses (extra key "keys" at the root; ...'
2043
+ * ```
2044
+ */
2045
+ function describeDefinitionRefusal(id, faults) {
2046
+ const [first] = faults;
2047
+ const path = first === void 0 ? [] : (0, _orkestrel_contract.extractPath)(first);
2048
+ const held = (0, _orkestrel_contract.preview)(id);
2049
+ return `database ${held} holds a definition this version refuses (${first?.reason === "extra" ? `extra key ${(0, _orkestrel_contract.preview)(path.at(-1))} at ${path.length === 1 ? "the root" : (0, _orkestrel_contract.preview)(path.slice(0, -1).join("."))}` : first === void 0 ? "a value its shape cannot name" : `${first.reason} at ${path.length === 0 ? "the root" : (0, _orkestrel_contract.preview)(path.join("."))}`}; a definition written before this change can carry it); read the row through your own table and ${first?.reason === "extra" ? "rewrite it without the key" : "rewrite it to the declared shape"}, or delete it with store.delete(${held})`;
1760
2050
  }
1761
2051
  // src/core/compilers.ts
1762
2052
  /**
@@ -1810,8 +2100,8 @@ function compileColumnPrimitive(primitive) {
1810
2100
  * {@link import('../factories.js').createMemoryDefinitionStore} builds. It implements the same
1811
2101
  * {@link DefinitionStoreInterface} contract as
1812
2102
  * {@link import('./DatabaseDefinitionStore.js').DatabaseDefinitionStore}: this store copies on
1813
- * write and on read; the table-backed store narrows an untrusted stored blob and reports
1814
- * `undefined` for a malformed one.
2103
+ * write and on read; the table-backed store narrows an untrusted stored blob and refuses a malformed
2104
+ * one.
1815
2105
  *
1816
2106
  * @remarks
1817
2107
  * A plain `Map<string, DatabaseDefinition>` (the definition is already pure,
@@ -1895,12 +2185,15 @@ var MemoryDefinitionStore = class {
1895
2185
  * and keeps the row type flat (`definition` reads back as `unknown`).
1896
2186
  *
1897
2187
  * - **`set(definition)` upserts under the definition's own `id`** (no separate id param) — it
1898
- * writes the row `{ id: definition.id, definition }`.
2188
+ * writes the row `{ id: definition.id, definition }`, and first refuses what `get` would refuse,
2189
+ * so no row is written that `get` cannot read.
1899
2190
  * - **`get(id)` resolves the stored definition for an id**, narrowing the opaque JSON column back
1900
2191
  * to a {@link DatabaseDefinition} ({@link import('../validators.js').isDatabaseDefinition} — the
1901
- * boundary narrow for an untrusted storage read), or `undefined` if none is stored
1902
- * or the stored blob is malformed.
1903
- * - **`delete(id)` drops a definition by id**; an absent id is a no-op (no throw).
2192
+ * boundary narrow for an untrusted storage read), or `undefined` if no row is stored. A present
2193
+ * row whose blob is malformed or names another id throws, because reading it as absent would let
2194
+ * a later `set` overwrite it.
2195
+ * - **`delete(id)` drops a definition by id** without reading it, the remedy for a refused row; an
2196
+ * absent id is a no-op (no throw).
1904
2197
  *
1905
2198
  * The public surface is exactly `get` / `set` / `delete` — no extra members (the method
1906
2199
  * bijection with {@link DefinitionStoreInterface}).
@@ -1931,21 +2224,47 @@ var DatabaseDefinitionStore = class {
1931
2224
  * {@link DatabaseDefinition}.
1932
2225
  *
1933
2226
  * @param id - The database id to read
1934
- * @returns The stored {@link DatabaseDefinition}, or `undefined` when none is stored or the
1935
- * stored blob is malformed
2227
+ * @returns The stored {@link DatabaseDefinition}, or `undefined` when no row is stored
2228
+ * @throws {ToolboxError} Thrown with code `DATABASE` when the row's blob is malformed or names another id, with the faults under `context.faults`
1936
2229
  */
1937
2230
  async get(id) {
1938
2231
  const row = await this.#table.get(id);
1939
2232
  if (row === void 0) return void 0;
1940
- return isDatabaseDefinition(row.definition) ? row.definition : void 0;
2233
+ const definition = row.definition;
2234
+ if (!isDatabaseDefinition(definition)) {
2235
+ const faults = definitionContract.audit(definition);
2236
+ throw new ToolboxError("DATABASE", describeDefinitionRefusal(id, faults), {
2237
+ id,
2238
+ faults
2239
+ });
2240
+ }
2241
+ if (definition.id !== id) {
2242
+ const faults = [{
2243
+ reason: "type",
2244
+ path: ["id"],
2245
+ expected: "literal",
2246
+ received: (0, _orkestrel_contract.preview)(definition.id)
2247
+ }];
2248
+ throw new ToolboxError("DATABASE", describeDefinitionMismatch(id, definition.id), {
2249
+ id,
2250
+ faults
2251
+ });
2252
+ }
2253
+ return definition;
1941
2254
  }
1942
2255
  /**
1943
2256
  * Inserts or replaces a definition under its own `id` — the written row is `{ id, definition }`.
1944
2257
  *
1945
2258
  * @param definition - The config to persist; its own `id` is the row key (no separate id param)
1946
2259
  * @returns A promise settling after the row is written
2260
+ * @throws {ToolboxError} Thrown with code `DATABASE` before any write when the definition fails the guard `get` reads, with the faults under `context.faults`
1947
2261
  */
1948
2262
  async set(definition) {
2263
+ if (!isDatabaseDefinition(definition)) {
2264
+ const unchecked = definition;
2265
+ const faults = definitionContract.audit(unchecked);
2266
+ throw new ToolboxError("DATABASE", limitRefusal(`a definition store writes only a complete definition; correct each line and call set again:\n${(0, _orkestrel_contract.describeFaults)(faults, definitionContract.schema)}`), { faults });
2267
+ }
1949
2268
  await this.#table.set({
1950
2269
  id: definition.id,
1951
2270
  definition
@@ -2044,7 +2363,7 @@ var DatabaseResolver = class {
2044
2363
  const definition = await this.#store.get(id);
2045
2364
  if (definition !== void 0) {
2046
2365
  const factory = this.#drivers[definition.driver];
2047
- if (factory === void 0) throw new ToolboxError("TOOL", `unknown driver '${definition.driver}'`, {
2366
+ if (factory === void 0) throw new ToolboxError("TOOL", `database ${(0, _orkestrel_contract.preview)(id)} names unknown driver ${(0, _orkestrel_contract.preview)(definition.driver)}; register it in the tool's drivers, one of ${renderCandidates(Object.keys(this.#drivers))}`, {
2048
2367
  id,
2049
2368
  driver: definition.driver
2050
2369
  });
@@ -2061,7 +2380,7 @@ var DatabaseResolver = class {
2061
2380
  return handle;
2062
2381
  }
2063
2382
  }
2064
- throw new ToolboxError("TOOL", `unknown database '${id}'`, { id });
2383
+ throw new ToolboxError("TOOL", `unknown database ${(0, _orkestrel_contract.preview)(id)}; define it first with { "operation": "create", "id", "tables" }`, { id });
2065
2384
  }
2066
2385
  };
2067
2386
  // src/core/factories.ts
@@ -2118,7 +2437,8 @@ function createToolFunction(tools, name) {
2118
2437
  * targets deeper than {@link import('./constants.js').MAX_WORKFLOW_CHAIN} are rejected before
2119
2438
  * agent or runner activity. When {@link import('./types.js').AgentFunctionOptions.runner} is
2120
2439
  * supplied, the adapter binds a recursion-safe {@link createWorkflowTool} onto the agent's
2121
- * `context.tools`, propagating opaque leaves, raw agents, lineage, and the native store. The
2440
+ * `context.tools`, propagating opaque leaves, raw agents, lineage, and the native store. The agent
2441
+ * needs an `authority` of its own to run that tool, because an agent with none denies every call. The
2122
2442
  * task's cancellation propagates through the Agent's native per-run signal seam:
2123
2443
  * `agent.generate({ signal: controller.signal })` resolves a partial `AgentResult` on a cancel
2124
2444
  * (never rejects), projected onto its exact JSON fields.
@@ -2264,7 +2584,12 @@ function createWorkflowDraftContract() {
2264
2584
  * It is also the propagation carrier
2265
2585
  * {@link createAgentFunction} binds onto a wrapped agent's `context.tools`: because a tool
2266
2586
  * handler receives only the model-supplied `args`, the immutable lineage is closed over at bind
2267
- * time. The authored target is appended and its zero-based depth derived from workflow tags.
2587
+ * time. The authored target is appended and its zero-based depth derived from workflow tags. That
2588
+ * bound tool wraps the containing workflow's id over no phases, and its empty-args call always
2589
+ * refuses as a cycle before the runner, so the wrapper never runs.
2590
+ *
2591
+ * `parameters` advertises the flat form alone, so the tool is built `strict: false` and its handler
2592
+ * runs the door ({@link import('./helpers.js').requireToolCall}) on the form the arguments take.
2268
2593
  *
2269
2594
  * **Widened authoring surface (additive — the canonical contract and runner stay strict and
2270
2595
  * unchanged).** A 2B model reliably calls the tool but cannot reliably emit the full nested
@@ -2279,9 +2604,11 @@ function createWorkflowDraftContract() {
2279
2604
  * super-set.
2280
2605
  *
2281
2606
  * The universal tool-handler contract: returns the plain run summary
2282
- * (`{ status, count, durable?, fault? }`) on success, throws a typed `ToolboxError` on Toolbox
2283
- * boundary failures — malformed authored args (`TOOL`), or an over-deep / cyclic nested run
2284
- * (`DEPTH`) — and preserves genuine runner `WorkflowError`s. The `ToolManagerInterface` isolates every throw into the canonical tool result's
2607
+ * (`{ status, count, durable?, fault? }`) on success. Malformed authored args throw a `ToolError`
2608
+ * with code `ARGUMENTS` (`@orkestrel/tool`) whose text lists what to correct, carrying
2609
+ * `context.faults`; a wrapped definition the strict contract refuses throws `TOOL`, and an over-deep
2610
+ * or cyclic nested run throws `DEPTH`. Genuine runner `WorkflowError`s are preserved, including
2611
+ * `SCHEDULE` for a target with no task. The `ToolManagerInterface` isolates every throw into the canonical tool result's
2285
2612
  * top-level `error`, so nothing escapes the run. `options.lineage` is the propagation carrier
2286
2613
  * across a workflow → agent → workflow chain. Opaque `functions` remain host leaves; raw `agents`
2287
2614
  * are contextually adapted through {@link createWorkflowFunctions}; native checkpoint persistence
@@ -2349,20 +2676,26 @@ function createWorkflowTool(definition, runner, options) {
2349
2676
  description: WORKFLOW_TOOL_DESCRIPTION,
2350
2677
  summary: WORKFLOW_TOOL_SUMMARY,
2351
2678
  ...parameters === void 0 ? {} : { parameters },
2679
+ strict: false,
2352
2680
  async execute(args) {
2353
2681
  const cloned = (0, _orkestrel_contract.attempt)(() => (0, _orkestrel_contract.cloneJSONRecord)(args));
2354
- if (!cloned.success) throw new ToolboxError("TOOL", "malformed workflow definition", { workflow: definition.id });
2355
- const owned = cloned.value;
2356
- const target = resolveWorkflowTarget(owned, definition, steps, draft);
2357
- if (target === void 0 || !strict.is(target)) throw new ToolboxError("TOOL", "malformed workflow definition", { workflow: definition.id });
2682
+ if (!cloned.success) throw new _orkestrel_tool.ToolError("ARGUMENTS", `Arguments could not be read; send one JSON object such as ${JSON.stringify(WORKFLOW_TOOL_FLAT_EXAMPLE)}`, void 0, { cause: cloned.error });
2683
+ const target = resolveWorkflowTarget(cloned.value, definition, steps, draft);
2684
+ if (!strict.is(target)) {
2685
+ const faults = strict.audit(target);
2686
+ throw new ToolboxError("TOOL", limitRefusal(`workflow ${(0, _orkestrel_contract.preview)(definition.id)} cannot run; send steps to author another one, or correct it:\n${(0, _orkestrel_contract.describeFaults)(faults, strict.schema)}`), {
2687
+ workflow: definition.id,
2688
+ faults
2689
+ });
2690
+ }
2358
2691
  const tag = tagWorkflow(target.id);
2359
- if (lineage.includes(tag)) throw new ToolboxError("DEPTH", `workflow '${target.id}' is already an ancestor (cycle)`, {
2692
+ if (lineage.includes(tag)) throw new ToolboxError("DEPTH", `workflow ${(0, _orkestrel_contract.preview)(target.id)} is already an ancestor (cycle); send steps with a new name, or phases with a new id`, {
2360
2693
  workflow: target.id,
2361
2694
  lineage: [...lineage]
2362
2695
  });
2363
2696
  const targetLineage = extendLineage(lineage, tag);
2364
2697
  const depth = deriveWorkflowDepth(targetLineage);
2365
- if (depth > 8) throw new ToolboxError("DEPTH", `nested workflow exceeds max depth 8`, {
2698
+ if (depth > 8) throw new ToolboxError("DEPTH", `nested workflow exceeds max depth 8; finish this task without nesting another workflow`, {
2366
2699
  workflow: target.id,
2367
2700
  depth,
2368
2701
  max: 8
@@ -2395,17 +2728,20 @@ function createWorkflowTool(definition, runner, options) {
2395
2728
  * active workspace by id (lenient — an unknown id is a no-op reporting `switched: false`, never a
2396
2729
  * throw).
2397
2730
  *
2398
- * No-active rule (the ergonomic seam): a writing op (write / splice / prepend / append / move /
2399
- * remove / replace) run when `manager.active` is `undefined` auto-creates and activates a default
2400
- * workspace (`manager.add()`) so the model can start writing; a pure-read op (read / list /
2401
- * has / search) against no active workspace returns the empty result (`undefined` / `[]` /
2402
- * `false`), never creating one and never throwing.
2403
- *
2404
- * The handler conforms to the universal tool-handler contract: it `contract.parse`s
2405
- * the args, throws a `TOOL` `ToolboxError` when no operation arm matched (a malformed / unknown
2406
- * operation), else `switch`es on `op.operation` and returns the plain result — letting a
2407
- * `WorkspaceError` raised by the live workspace (`MISSING` / `MODALITY` / `PATTERN` / `RANGE`)
2408
- * propagate uncaught. The range edit is the flat `'splice'` op: its flat caret integers are
2731
+ * No-active rule (the ergonomic seam): an op that can create the file it names (write / prepend /
2732
+ * append) run when `manager.active` is `undefined` auto-creates and activates a default workspace
2733
+ * (`manager.add()`) so the model can start writing; an op that edits a file that must already exist
2734
+ * (splice / move / remove / replace) refuses with a `TOOL` `ToolboxError` and creates none; a
2735
+ * pure-read op (read / list / has / search) against no active workspace returns the empty result
2736
+ * (`null` / `[]` / `false`), never creating one and never throwing.
2737
+ *
2738
+ * The Tool door enforces the operation union through `contract`, repairing lossless scalar leaves
2739
+ * and refusing malformed args with a `ToolError` carrying code `ARGUMENTS`. A selected operation
2740
+ * reports its own faults; an unknown operation reports a bounded summary of alternatives.
2741
+ * The handler then `switch`es on `op.operation` and returns the plain result
2742
+ * — letting a `WorkspaceError` raised by the live workspace (`MISSING` / `MODALITY` / `PATTERN` /
2743
+ * `RANGE`) propagate uncaught. `read` returns `null` for a path that holds no text, because JSON
2744
+ * tells that from an empty file. The range edit is the flat `'splice'` op: its flat caret integers are
2409
2745
  * reassembled into a `Range` (`@orkestrel/workspace`) by `rangeOf` and fed to the workspace's
2410
2746
  * ranged `write`.
2411
2747
  *
@@ -2426,15 +2762,13 @@ function createWorkflowTool(definition, runner, options) {
2426
2762
  function createWorkspaceTool(options) {
2427
2763
  const manager = options?.manager ?? (0, _orkestrel_workspace.createWorkspaceManager)(options?.store === void 0 ? void 0 : { store: options.store });
2428
2764
  const contract = (0, _orkestrel_contract.createContract)(workspaceToolShape);
2429
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2430
2765
  return (0, _orkestrel_tool.createTool)({
2431
2766
  name: options?.name ?? "workspace",
2432
2767
  description: options?.description ?? WORKSPACE_TOOL_DESCRIPTION,
2433
2768
  summary: WORKSPACE_TOOL_SUMMARY,
2434
- ...parameters === void 0 ? {} : { parameters },
2769
+ contract: workspaceToolShape,
2435
2770
  execute(args) {
2436
- const op = contract.parse(args);
2437
- if (op === void 0) throw new ToolboxError("TOOL", "unknown or malformed operation", { args });
2771
+ const op = requireToolCall(contract, args);
2438
2772
  if (op.operation === "workspaces") {
2439
2773
  const activeId = manager.active?.id;
2440
2774
  return manager.workspaces().map((workspace) => ({
@@ -2456,7 +2790,7 @@ function createWorkspaceTool(options) {
2456
2790
  }
2457
2791
  const active = manager.active;
2458
2792
  switch (op.operation) {
2459
- case "read": return active?.read(op.path);
2793
+ case "read": return active?.read(op.path) ?? null;
2460
2794
  case "list": return (active?.files() ?? []).map((file) => ({
2461
2795
  path: file.path,
2462
2796
  state: file.state,
@@ -2470,7 +2804,7 @@ function createWorkspaceTool(options) {
2470
2804
  ...op.sensitive === void 0 ? {} : { sensitive: op.sensitive },
2471
2805
  ...op.limit === void 0 ? {} : { limit: op.limit }
2472
2806
  }) ?? [];
2473
- case "replace": return (active ?? manager.add()).replace(op.query, op.replacement, {
2807
+ case "replace": return requireActiveWorkspace(manager, op.operation).replace(op.query, op.replacement, {
2474
2808
  ...op.regex === void 0 ? {} : { regex: op.regex },
2475
2809
  ...op.sensitive === void 0 ? {} : { sensitive: op.sensitive },
2476
2810
  ...op.limit === void 0 ? {} : { limit: op.limit }
@@ -2484,7 +2818,7 @@ function createWorkspaceTool(options) {
2484
2818
  };
2485
2819
  }
2486
2820
  case "splice": {
2487
- const workspace = active ?? manager.add();
2821
+ const workspace = requireActiveWorkspace(manager, op.operation);
2488
2822
  workspace.write(op.path, op.content, (0, _orkestrel_workspace.rangeOf)(op.fromLine, op.fromColumn, op.toLine, op.toColumn));
2489
2823
  return {
2490
2824
  path: op.path,
@@ -2508,7 +2842,7 @@ function createWorkspaceTool(options) {
2508
2842
  };
2509
2843
  }
2510
2844
  case "move": {
2511
- const workspace = active ?? manager.add();
2845
+ const workspace = requireActiveWorkspace(manager, op.operation);
2512
2846
  return {
2513
2847
  from: op.from,
2514
2848
  to: op.to,
@@ -2516,7 +2850,7 @@ function createWorkspaceTool(options) {
2516
2850
  };
2517
2851
  }
2518
2852
  case "remove": {
2519
- const workspace = active ?? manager.add();
2853
+ const workspace = requireActiveWorkspace(manager, op.operation);
2520
2854
  return {
2521
2855
  path: op.path,
2522
2856
  removed: workspace.remove(op.path)
@@ -2531,16 +2865,20 @@ function createWorkspaceTool(options) {
2531
2865
  * from `registry` and runs it to completion for one delegated `task`.
2532
2866
  *
2533
2867
  * @remarks
2534
- * The universal tool-handler contract: validates the call args against
2535
- * {@link import('./shapers.js').agentToolShape}, assembles an `AgentJobInput` (`task` seeds the
2868
+ * The universal tool-handler contract: the Tool door holds the call args to
2869
+ * {@link import('./shapers.js').agentToolShape}, refusing a malformed call with a `ToolError`
2870
+ * carrying code `ARGUMENTS`. The handler assembles an `AgentJobInput` (`task` seeds the
2536
2871
  * sub-agent's conversation as a single `user` message; `provider` / `tools` / `system` fall
2537
- * back to the tool's own {@link import('./types.js').AgentToolOptions} defaults), rehydrates the sub-agent through
2872
+ * back to the tool's own {@link import('./types.js').AgentToolOptions} defaults, and `authority` is
2873
+ * the tool's own, never the call's), rehydrates the sub-agent through
2538
2874
  * `registry.build`, runs it with `agent.generate()`, and returns the settled
2539
- * `AgentResult.content` string (the sub-agent's final text). A missing / unresolvable `provider`, or a malformed call, throws a typed `TOOL`
2875
+ * `AgentResult.content` string (the sub-agent's final text). A missing / unresolvable `provider`, or
2876
+ * tools with no configured `authority`, throws a typed `TOOL`
2540
2877
  * {@link import('./errors.js').ToolboxError}; a delegation that would exceed
2541
2878
  * {@link import('./constants.js').AGENT_TOOL_DEPTH}, or re-enter an already-delegated agent (a
2542
2879
  * cycle), throws a typed `DEPTH` {@link import('./errors.js').ToolboxError}, each isolated
2543
- * by the `ToolManagerInterface` into the canonical tool result's top-level `error`.
2880
+ * by the `ToolManagerInterface` into the canonical tool result's top-level `error`. A `depth` that
2881
+ * is not a nonnegative safe integer throws `TOOL` at construction.
2544
2882
  *
2545
2883
  * `AgentInterface` (`@orkestrel/agent`) exposes no teardown method — a bound sub-agent's
2546
2884
  * lifetime is the single `generate()` call this handler awaits; there is nothing to release
@@ -2567,30 +2905,33 @@ function createWorkspaceTool(options) {
2567
2905
  */
2568
2906
  function createAgentTool(registry, options) {
2569
2907
  const contract = (0, _orkestrel_contract.createContract)(agentToolShape);
2570
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2571
2908
  const depth = options?.depth ?? 0;
2909
+ if (!(0, _orkestrel_contract.isNonNegativeInteger)(depth) || !Number.isSafeInteger(depth)) throw new ToolboxError("TOOL", `agent tool depth must be a nonnegative safe integer; received ${(0, _orkestrel_contract.preview)(depth)}; omit it for a direct root`, { received: (0, _orkestrel_contract.preview)(depth) });
2572
2910
  const ancestry = options?.ancestry ?? [];
2573
2911
  return (0, _orkestrel_tool.createTool)({
2574
2912
  name: options?.name ?? "agent",
2575
2913
  description: options?.description ?? AGENT_TOOL_DESCRIPTION,
2576
2914
  summary: AGENT_TOOL_SUMMARY,
2577
- ...parameters === void 0 ? {} : { parameters },
2915
+ contract: agentToolShape,
2578
2916
  async execute(args) {
2579
- const call = contract.parse(args);
2580
- if (call === void 0) throw new ToolboxError("TOOL", "malformed agent-delegation call", { args });
2917
+ const call = requireToolCall(contract, args);
2581
2918
  const provider = call.provider ?? options?.provider;
2582
- if (provider === void 0) throw new ToolboxError("TOOL", "no provider resolved for the delegated agent", { task: call.task });
2583
- if (depth + 1 > 8) throw new ToolboxError("DEPTH", `delegation exceeds max agent depth 8`, {
2919
+ if (provider === void 0) throw new ToolboxError("TOOL", "no provider resolved for the delegated agent; send provider as a registered provider key", { task: call.task });
2920
+ if (depth + 1 > 8) throw new ToolboxError("DEPTH", `delegation exceeds max agent depth 8; carry out this task without delegating`, {
2584
2921
  provider,
2585
2922
  depth,
2586
2923
  max: 8
2587
2924
  });
2588
2925
  const tag = tagAgent(provider);
2589
- if (ancestry.includes(tag)) throw new ToolboxError("DEPTH", `agent '${provider}' is already an ancestor (cycle)`, {
2926
+ if (ancestry.includes(tag)) throw new ToolboxError("DEPTH", `agent ${(0, _orkestrel_contract.preview)(provider)} is already an ancestor (cycle); send another provider, or carry out this task without delegating`, {
2590
2927
  provider,
2591
2928
  ancestry: [...ancestry]
2592
2929
  });
2593
2930
  const tools = call.tools ?? options?.tools;
2931
+ if (tools !== void 0 && tools.length > 0 && options?.authority === void 0) throw new ToolboxError("TOOL", "this agent tool has no authority to decide tool calls, so its sub-agent can load no tools; send tools: [] to delegate with none, or have the host set authority on this tool", {
2932
+ provider,
2933
+ tools: [...tools]
2934
+ });
2594
2935
  const system = call.system ?? options?.system;
2595
2936
  const agent = registry.build({
2596
2937
  provider,
@@ -2599,7 +2940,8 @@ function createAgentTool(registry, options) {
2599
2940
  content: call.task
2600
2941
  }],
2601
2942
  ...system === void 0 ? {} : { system },
2602
- ...tools === void 0 ? {} : { tools }
2943
+ ...tools === void 0 ? {} : { tools },
2944
+ ...options?.authority === void 0 ? {} : { authority: options.authority }
2603
2945
  });
2604
2946
  const result = await agent.generate();
2605
2947
  if (options?.store !== void 0) {
@@ -2620,12 +2962,13 @@ function createAgentTool(registry, options) {
2620
2962
  * description when `summary` is set, keeping the advertised tool list compact for a small model.
2621
2963
  * This tool is the on-demand expansion seam: given a registered tool's `name`, it looks the tool
2622
2964
  * up through `tools.tool(name)` and returns its full `description` (falling back to `summary` when a
2623
- * tool has no `description` of its own, then a placeholder when it has neither).
2965
+ * tool has no `description` of its own, then an explicit `null` when it has neither).
2624
2966
  *
2625
- * The universal tool-handler contract: validates the call args against
2626
- * {@link import('./shapers.js').describeToolShape}, returns the plain description string on
2627
- * success, throws a typed `TOOL` {@link import('./errors.js').ToolboxError} on a malformed call
2628
- * or an unknown tool name.
2967
+ * The universal tool-handler contract: the Tool door holds the call args to
2968
+ * {@link import('./shapers.js').describeToolShape}, refusing a malformed call with a `ToolError`
2969
+ * carrying code `ARGUMENTS`. The handler returns the plain description on success, and throws a typed
2970
+ * `TOOL` {@link import('./errors.js').ToolboxError} that lists the registered names on an unknown
2971
+ * tool name.
2629
2972
  *
2630
2973
  * @param tools - The `ToolManagerInterface` (`@orkestrel/tool`) whose registered tools this
2631
2974
  * tool can describe
@@ -2645,18 +2988,22 @@ function createAgentTool(registry, options) {
2645
2988
  */
2646
2989
  function createDescribeTool(tools) {
2647
2990
  const contract = (0, _orkestrel_contract.createContract)(describeToolShape);
2648
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2649
2991
  return (0, _orkestrel_tool.createTool)({
2650
2992
  name: DESCRIBE_TOOL_NAME,
2651
2993
  description: DESCRIBE_TOOL_DESCRIPTION,
2652
2994
  summary: DESCRIBE_TOOL_SUMMARY,
2653
- ...parameters === void 0 ? {} : { parameters },
2995
+ contract: describeToolShape,
2654
2996
  async execute(args) {
2655
- const call = contract.parse(args);
2656
- if (call === void 0) throw new ToolboxError("TOOL", "malformed describe call", { args });
2997
+ const call = requireToolCall(contract, args);
2657
2998
  const tool = tools.tool(call.name);
2658
- if (tool === void 0) throw new ToolboxError("TOOL", `unknown tool '${call.name}'`, { name: call.name });
2659
- return tool.description ?? tool.summary ?? "<no description>";
2999
+ if (tool === void 0) {
3000
+ const names = tools.tools().map((registered) => registered.name);
3001
+ throw new ToolboxError("TOOL", `unknown tool ${(0, _orkestrel_contract.preview)(call.name)}; send name as one of ${renderCandidates(names)}`, {
3002
+ name: call.name,
3003
+ names
3004
+ });
3005
+ }
3006
+ return tool.description ?? tool.summary ?? null;
2660
3007
  }
2661
3008
  });
2662
3009
  }
@@ -2665,8 +3012,9 @@ function createDescribeTool(tools) {
2665
3012
  * and blocks until the addressed terminal answers, returning the resolved values record.
2666
3013
  *
2667
3014
  * @remarks
2668
- * The universal tool-handler contract: validates the call args against
2669
- * {@link import('./shapers.js').promptToolShape}, parses the call's schema through
3015
+ * The universal tool-handler contract: the Tool door holds the call args to
3016
+ * {@link import('./shapers.js').promptToolShape}, refusing a malformed call with a `ToolError`
3017
+ * carrying code `ARGUMENTS`. The handler parses the call's schema through
2670
3018
  * `@orkestrel/form`, constructs the live form, and passes it to `TerminalManagerInterface.ask`.
2671
3019
  * `from` is fixed at construction
2672
3020
  * ({@link import('./types.js').PromptToolOptions.from}) — never read from the model-supplied
@@ -2674,7 +3022,7 @@ function createDescribeTool(tools) {
2674
3022
  * `TerminalError('DEADLOCK')`, re-surfaced as a typed `DEADLOCK`
2675
3023
  * {@link import('./errors.js').ToolboxError}; an expired form re-surfaces as `EXPIRE`; an
2676
3024
  * unknown `to` (or any other `TerminalError`) re-surfaces as `TOOL`, naming the unknown terminal
2677
- * and how many brokers the manager holds (`manager.count`). A schema `parseForm` refuses — an
3025
+ * and the mounted terminals the manager's refusal lists, encoded and bounded. A schema `parseForm` refuses — an
2678
3026
  * unknown control, a duplicate field name, a `'select'` or `'checkbox'` field without usable
2679
3027
  * `choices` — throws a typed `TOOL` error up front, so a malformed schema never parks as a form
2680
3028
  * nobody can answer.
@@ -2699,37 +3047,40 @@ function createDescribeTool(tools) {
2699
3047
  */
2700
3048
  function createPromptTool(options) {
2701
3049
  const contract = (0, _orkestrel_contract.createContract)(promptToolShape);
2702
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2703
3050
  return (0, _orkestrel_tool.createTool)({
2704
3051
  name: options.name ?? "ask",
2705
3052
  description: options.description ?? PROMPT_TOOL_DESCRIPTION,
2706
3053
  summary: PROMPT_TOOL_SUMMARY,
2707
- ...parameters === void 0 ? {} : { parameters },
3054
+ contract: promptToolShape,
2708
3055
  async execute(args) {
2709
- const call = contract.parse(args);
2710
- if (call === void 0) throw new ToolboxError("TOOL", "malformed ask call", { args });
3056
+ const call = requireToolCall(contract, args);
2711
3057
  const schema = (0, _orkestrel_form.parseForm)(call.schema);
2712
- if (schema === void 0) throw new ToolboxError("TOOL", "malformed form schema", { schema: call.schema });
2713
- for (const field of schema.fields) if ((field.control === "select" || field.control === "checkbox") && !field.choices.some((choice) => choice.disabled !== true)) throw new ToolboxError("TOOL", `${field.control} field '${field.name}' has no enabled choices`, {
3058
+ if (schema === void 0) throw new ToolboxError("TOOL", `malformed form schema; send { "fields": [{ "control", "name" }] } with unique names, where control is one of ${_orkestrel_form.FIELD_CONTROLS.join(", ")}, and select or checkbox carries choices of { "label", "value" }`, { schema: call.schema });
3059
+ for (const field of schema.fields) if ((field.control === "select" || field.control === "checkbox") && !field.choices.some((choice) => choice.disabled !== true)) throw new ToolboxError("TOOL", `${field.control} field ${(0, _orkestrel_contract.preview)(field.name)} has no enabled choices; add a choice that is not disabled`, {
2714
3060
  control: field.control,
2715
3061
  field: field.name
2716
3062
  });
2717
3063
  try {
2718
3064
  return await options.manager.ask(options.from, call.to, (0, _orkestrel_form.createForm)(schema));
2719
3065
  } catch (error) {
2720
- if ((0, _orkestrel_form.isFormError)(error) && error.code === "ABANDONED") throw new ToolboxError("EXPIRE", `form to '${call.to}' expired before it was answered`, { to: call.to });
3066
+ if ((0, _orkestrel_form.isFormError)(error) && error.code === "ABANDONED") throw new ToolboxError("EXPIRE", `form to ${(0, _orkestrel_contract.preview)(call.to)} expired before it was answered; ask again to give it more time`, { to: call.to });
2721
3067
  const code = inferTerminalCode(error);
2722
3068
  if (code === void 0) throw error;
2723
- if (code === "DEADLOCK") throw new ToolboxError("DEADLOCK", `asking '${call.to}' would create a form cycle`, (0, _orkestrel_terminal.isTerminalError)(error) ? error.context : {
3069
+ if (code === "DEADLOCK") throw new ToolboxError("DEADLOCK", `asking ${(0, _orkestrel_contract.preview)(call.to)} would create a form cycle; answer the form addressed to you first`, (0, _orkestrel_terminal.isTerminalError)(error) ? error.context : {
2724
3070
  from: options.from,
2725
3071
  to: call.to
2726
3072
  });
2727
- if (code === "EXPIRE") throw new ToolboxError("EXPIRE", `form to '${call.to}' expired before it was answered`, { to: call.to });
2728
- if ((0, _orkestrel_terminal.isTerminalError)(error) && error.code === "TARGET") throw new ToolboxError("TOOL", `unknown terminal '${call.to}'`, {
2729
- to: call.to,
2730
- count: options.manager.count
2731
- });
2732
- throw new ToolboxError("TOOL", `asking '${call.to}' failed`, { to: call.to });
3073
+ if (code === "EXPIRE") throw new ToolboxError("EXPIRE", `form to ${(0, _orkestrel_contract.preview)(call.to)} expired before it was answered; ask again to give it more time`, { to: call.to });
3074
+ if ((0, _orkestrel_terminal.isTerminalError)(error) && error.code === "TARGET") {
3075
+ const listed = error.context?.known;
3076
+ const known = (0, _orkestrel_contract.arrayOf)(_orkestrel_contract.isString)(listed) ? listed : [];
3077
+ throw new ToolboxError("TOOL", `unknown terminal ${(0, _orkestrel_contract.preview)(call.to)}; ${known.length === 0 ? "no mounted terminal name is available" : `send to as one of ${renderCandidates(known)}`}`, {
3078
+ to: call.to,
3079
+ known,
3080
+ count: options.manager.count
3081
+ });
3082
+ }
3083
+ throw new ToolboxError("TOOL", `asking ${(0, _orkestrel_contract.preview)(call.to)} failed; ask again`, { to: call.to });
2733
3084
  }
2734
3085
  }
2735
3086
  });
@@ -2739,8 +3090,9 @@ function createPromptTool(options) {
2739
3090
  * addressed to {@link import('./types.js').AnswerToolOptions.to}, or answers one of them by id.
2740
3091
  *
2741
3092
  * @remarks
2742
- * The universal tool-handler contract: validates the call args against
2743
- * {@link import('./shapers.js').answerToolShape} (discriminated by `operation`). `'pending'`
3093
+ * The universal tool-handler contract: the Tool door holds the call args to
3094
+ * {@link import('./shapers.js').answerToolShape} (discriminated by `operation`), refusing malformed
3095
+ * args with a `ToolError` carrying code `ARGUMENTS`. `'pending'`
2744
3096
  * returns a compact list (`{ id, from, schema }`) of every form addressed to `to`.
2745
3097
  * `'answer'` looks the form up by `id`, narrows the supplied `values` through `@orkestrel/form`,
2746
3098
  * and applies it through
@@ -2771,31 +3123,32 @@ function createPromptTool(options) {
2771
3123
  */
2772
3124
  function createAnswerTool(options) {
2773
3125
  const contract = (0, _orkestrel_contract.createContract)(answerToolShape);
2774
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2775
3126
  return (0, _orkestrel_tool.createTool)({
2776
3127
  name: options.name ?? "answer",
2777
3128
  description: options.description ?? ANSWER_TOOL_DESCRIPTION,
2778
3129
  summary: ANSWER_TOOL_SUMMARY,
2779
- ...parameters === void 0 ? {} : { parameters },
3130
+ contract: answerToolShape,
2780
3131
  async execute(args) {
2781
- const call = contract.parse(args);
2782
- if (call === void 0) throw new ToolboxError("TOOL", "malformed answer call", { args });
3132
+ const call = requireToolCall(contract, args);
2783
3133
  if (call.operation === "pending") return options.manager.pending(options.to).map((form) => ({
2784
3134
  id: form.id,
2785
3135
  from: form.from,
2786
3136
  schema: form.schema
2787
3137
  }));
2788
- if (options.manager.pending(options.to).find((entry) => entry.id === call.id) === void 0) throw new ToolboxError("ANSWER", `unknown form '${call.id}'`, {
3138
+ if (options.manager.pending(options.to).find((entry) => entry.id === call.id) === void 0) throw new ToolboxError("ANSWER", `unknown form ${(0, _orkestrel_contract.preview)(call.id)}; call { "operation": "pending" } for the ids you can answer`, {
2789
3139
  id: call.id,
2790
3140
  reason: "unknown"
2791
3141
  });
2792
- if (!(0, _orkestrel_form.isFormValues)(call.values)) throw new ToolboxError("TOOL", "malformed form values", { values: call.values });
3142
+ if (!(0, _orkestrel_form.isFormValues)(call.values)) throw new ToolboxError("TOOL", "malformed form values; send values as a record of each field name to a string, a finite number, a boolean, or a list of strings", { values: call.values });
2793
3143
  const result = options.manager.answer(options.to, call.id, call.values);
2794
- if (!result.success) throw new ToolboxError("ANSWER", `failed to answer form '${call.id}': ${result.error.reason}`, {
2795
- id: call.id,
2796
- reason: result.error.reason,
2797
- ...result.error.reason === "rejected" ? { errors: result.error.errors } : {}
2798
- });
3144
+ if (!result.success) {
3145
+ const detail = result.error.reason === "rejected" ? `; correct these fields and answer again: ${(0, _orkestrel_contract.renderList)(result.error.errors.map((fault) => `${(0, _orkestrel_contract.preview)(fault.field)}: ${(0, _orkestrel_contract.preview)(fault.message)}`))}` : "; call { \"operation\": \"pending\" } for the ids you can answer";
3146
+ throw new ToolboxError("ANSWER", `failed to answer form ${(0, _orkestrel_contract.preview)(call.id)}: ${result.error.reason}${detail}`, {
3147
+ id: call.id,
3148
+ reason: result.error.reason,
3149
+ ...result.error.reason === "rejected" ? { errors: result.error.errors } : {}
3150
+ });
3151
+ }
2799
3152
  return { answered: call.id };
2800
3153
  }
2801
3154
  });
@@ -2848,9 +3201,11 @@ function createDatabaseDefinitionStore(driver = (0, _orkestrel_database.createMe
2848
3201
  * {@link createWorkspaceTool}'s single-tool-many-operations shape).
2849
3202
  *
2850
3203
  * @remarks
2851
- * The universal tool-handler contract: validates the call args against
2852
- * {@link import('./shapers.js').databaseToolShape}, dispatches to the matching operation, and
2853
- * returns a plain result on success. A database is resolved lazily and cached for the tool's
3204
+ * The universal tool-handler contract: the Tool door holds the call args to
3205
+ * {@link import('./shapers.js').databaseToolShape}, refusing malformed args with a `ToolError`
3206
+ * carrying code `ARGUMENTS`. The handler dispatches to the matching operation and
3207
+ * returns a plain result on success. A row a `'get'` misses is an explicit `null`. A `limit` that is
3208
+ * not a positive safe integer throws `TOOL` at construction. A database is resolved lazily and cached for the tool's
2854
3209
  * lifetime — `'create'` mints one from `tables` ({@link import('./compilers.js').expandTables}) and
2855
3210
  * a registered `driver` key ({@link import('./types.js').DatabaseToolOptions.drivers}, default
2856
3211
  * `{ memory: () => createMemoryDriver() }`); any other operation addressing an uncached id falls
@@ -2908,30 +3263,28 @@ function createDatabaseDefinitionStore(driver = (0, _orkestrel_database.createMe
2908
3263
  function createDatabaseTool(options = {}) {
2909
3264
  if (options.timeout !== void 0 && (options.timeout < 0 || !(0, _orkestrel_contract.isNumber)(options.timeout) || !Number.isSafeInteger(options.timeout))) throw new ToolboxError("TOOL", "database timeout must be a nonnegative safe integer", { timeout: options.timeout });
2910
3265
  const contract = (0, _orkestrel_contract.createContract)(databaseToolShape);
2911
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
2912
3266
  const handles = new Map(Object.entries(options.databases ?? {}));
2913
3267
  const drivers = options.drivers ?? { memory: _orkestrel_database.createMemoryDriver };
2914
3268
  const generator = options.generator;
2915
- const cap = options.limit ?? 1e3;
3269
+ const cap = resolveBound(options.limit, DATABASE_TOOL_LIMIT, "database limit");
2916
3270
  const store = options.store;
2917
3271
  const resolver = store === void 0 ? new DatabaseResolver(handles, drivers, generator) : new DatabaseResolver(handles, drivers, generator, store);
2918
3272
  return (0, _orkestrel_tool.createTool)({
2919
3273
  name: options.name ?? "database",
2920
3274
  description: options.description ?? DATABASE_TOOL_DESCRIPTION,
2921
3275
  summary: DATABASE_TOOL_SUMMARY,
2922
- ...parameters === void 0 ? {} : { parameters },
3276
+ contract: databaseToolShape,
2923
3277
  async execute(args) {
2924
- const call = contract.parse(args);
2925
- if (call === void 0) throw new ToolboxError("TOOL", "malformed database call", { args });
2926
- if (options.readonly === true && DATABASE_TOOL_MUTATIONS.includes(call.operation)) throw new ToolboxError("TOOL", `operation '${call.operation}' is disabled in readonly mode`, { operation: call.operation });
3278
+ const call = requireToolCall(contract, args);
3279
+ if (options.readonly === true && DATABASE_TOOL_MUTATIONS.includes(call.operation)) throw new ToolboxError("TOOL", `operation '${call.operation}' is disabled in readonly mode; call tables, get, records, count, or aggregate`, { operation: call.operation });
2927
3280
  const read = options.timeout === void 0 ? void 0 : { signal: AbortSignal.timeout(options.timeout) };
2928
3281
  try {
2929
3282
  switch (call.operation) {
2930
3283
  case "create": {
2931
- if (resolver.has(call.id) || store !== void 0 && await store.get(call.id) !== void 0) throw new ToolboxError("TOOL", `database '${call.id}' already exists`, { id: call.id });
3284
+ if (resolver.has(call.id) || store !== void 0 && await store.get(call.id) !== void 0) throw new ToolboxError("TOOL", `database ${(0, _orkestrel_contract.preview)(call.id)} already exists; use it, destroy it first, or send another id`, { id: call.id });
2932
3285
  const name = call.driver ?? "memory";
2933
3286
  const factory = drivers[name];
2934
- if (factory === void 0) throw new ToolboxError("TOOL", `unknown driver '${name}'`, {
3287
+ if (factory === void 0) throw new ToolboxError("TOOL", `unknown driver ${(0, _orkestrel_contract.preview)(name)}; send driver as one of ${renderCandidates(Object.keys(drivers))}`, {
2935
3288
  id: call.id,
2936
3289
  driver: name
2937
3290
  });
@@ -2978,7 +3331,7 @@ function createDatabaseTool(options = {}) {
2978
3331
  const table = (await resolver.resolve(call.id)).table(call.table);
2979
3332
  const many = (0, _orkestrel_contract.isArray)(call.key);
2980
3333
  const keys = (0, _orkestrel_contract.isArray)(call.key) ? call.key : [call.key];
2981
- const rows = await table.get(keys);
3334
+ const rows = (await table.get(keys)).map((row) => row ?? null);
2982
3335
  return many ? { rows } : { row: rows[0] };
2983
3336
  }
2984
3337
  case "records": {
@@ -3043,7 +3396,7 @@ function createDatabaseTool(options = {}) {
3043
3396
  if (isToolboxError(error)) throw error;
3044
3397
  const code = inferDatabaseCode(error);
3045
3398
  if (code === void 0) throw error;
3046
- throw new ToolboxError("DATABASE", (0, _orkestrel_contract.isError)(error) ? error.message : String(error), {
3399
+ throw new ToolboxError("DATABASE", limitRefusal((0, _orkestrel_contract.isError)(error) ? error.message : String(error)), {
3047
3400
  code,
3048
3401
  operation: call.operation,
3049
3402
  id: call.id,
@@ -3059,8 +3412,10 @@ function createDatabaseTool(options = {}) {
3059
3412
  * single-tool-many-operations shape).
3060
3413
  *
3061
3414
  * @remarks
3062
- * The universal tool-handler contract: validates the call args against
3063
- * {@link import('./shapers.js').relationToolShape}, resolves the addressed
3415
+ * The universal tool-handler contract: the Tool door holds the call args to
3416
+ * {@link import('./shapers.js').relationToolShape}, refusing malformed args with a `ToolError`
3417
+ * carrying code `ARGUMENTS`. A `limit` or `depth` that is not a positive safe integer throws `TOOL` at
3418
+ * construction, and a row `'load'` misses is an explicit `null`. The handler resolves the addressed
3064
3419
  * {@link import('@orkestrel/relation').RelationManagerInterface} — an explicit `manager` field
3065
3420
  * must match a key of {@link import('./types.js').RelationToolOptions.managers}, an omitted one
3066
3421
  * resolves to the sole registered manager, either miss throwing a typed `TOOL`
@@ -3106,24 +3461,22 @@ function createDatabaseTool(options = {}) {
3106
3461
  */
3107
3462
  function createRelationTool(options) {
3108
3463
  const contract = (0, _orkestrel_contract.createContract)(relationToolShape);
3109
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
3110
- const depth = options.depth ?? 3;
3111
- const cap = options.limit ?? 1e3;
3464
+ const depth = resolveBound(options.depth, 3, "relation depth");
3465
+ const cap = resolveBound(options.limit, RELATION_TOOL_LIMIT, "relation limit");
3112
3466
  return (0, _orkestrel_tool.createTool)({
3113
3467
  name: options.name ?? "relation",
3114
3468
  description: options.description ?? RELATION_TOOL_DESCRIPTION,
3115
3469
  summary: RELATION_TOOL_SUMMARY,
3116
- ...parameters === void 0 ? {} : { parameters },
3470
+ contract: relationToolShape,
3117
3471
  async execute(args) {
3118
- const call = contract.parse(args);
3119
- if (call === void 0) throw new ToolboxError("TOOL", "malformed relation call", { args });
3472
+ const call = requireToolCall(contract, args);
3120
3473
  try {
3121
3474
  const model = resolveRelationModel(resolveRelationManager(options.managers, call.manager), call.model);
3122
3475
  switch (call.operation) {
3123
3476
  case "load": {
3124
3477
  const include = expandInclude(call.include, depth);
3125
- if ((0, _orkestrel_contract.isString)(call.key) || (0, _orkestrel_contract.isNumber)(call.key)) return { row: await model.load(call.key, include) };
3126
- return { rows: await model.load(call.key, include) };
3478
+ if ((0, _orkestrel_contract.isString)(call.key) || (0, _orkestrel_contract.isNumber)(call.key)) return { row: await model.load(call.key, include) ?? null };
3479
+ return { rows: (await model.load(call.key, include)).map((row) => row ?? null) };
3127
3480
  }
3128
3481
  case "find": {
3129
3482
  const include = expandInclude(call.include, depth);
@@ -3165,7 +3518,7 @@ function createRelationTool(options) {
3165
3518
  } catch (error) {
3166
3519
  if (isToolboxError(error)) throw error;
3167
3520
  const relation = inferRelationCode(error);
3168
- if (relation !== void 0) throw new ToolboxError("RELATION", (0, _orkestrel_contract.isError)(error) ? error.message : String(error), {
3521
+ if (relation !== void 0) throw new ToolboxError("RELATION", limitRefusal((0, _orkestrel_contract.isError)(error) ? error.message : String(error)), {
3169
3522
  code: relation,
3170
3523
  operation: call.operation,
3171
3524
  model: call.model,
@@ -3173,7 +3526,7 @@ function createRelationTool(options) {
3173
3526
  });
3174
3527
  const database = inferDatabaseCode(error);
3175
3528
  if (database === void 0) throw error;
3176
- throw new ToolboxError("DATABASE", (0, _orkestrel_contract.isError)(error) ? error.message : String(error), {
3529
+ throw new ToolboxError("DATABASE", limitRefusal((0, _orkestrel_contract.isError)(error) ? error.message : String(error)), {
3177
3530
  code: database,
3178
3531
  operation: call.operation
3179
3532
  });
@@ -3187,14 +3540,13 @@ function createRelationTool(options) {
3187
3540
  * {@link createEndpointTool}, wraps one concrete endpoint).
3188
3541
  *
3189
3542
  * @remarks
3190
- * The universal tool-handler contract: validates the call args against
3543
+ * The universal tool-handler contract: the Tool door holds the call args to
3191
3544
  * {@link import('./shapers.js').inferToolShape} (`samples` non-empty, `format` / `enum` optional
3192
- * booleans, `candidates` an optional array), infers a schema through `@orkestrel/contract`'s
3545
+ * booleans, `candidates` an optional array), refusing malformed args with a `ToolError` carrying code
3546
+ * `ARGUMENTS`. The handler infers a schema through `@orkestrel/contract`'s
3193
3547
  * `samplesToSchema`, wraps a non-object root as `{ value: <schema> }` through `schemaToObject` (mirrors
3194
3548
  * the tool-parameters convention every other `create*Tool` factory advertises), and returns the
3195
- * resulting parameters record. An empty `samples` array fails `inferToolShape`'s `min: 1` bound —
3196
- * `contract.parse` returns `undefined` and the handler throws a typed `TOOL`
3197
- * {@link import('./errors.js').ToolboxError}.
3549
+ * resulting parameters record.
3198
3550
  *
3199
3551
  * When `candidates` is absent, the return is the bare parameters record — unchanged from before
3200
3552
  * this array existed. When `candidates` is present (any array, including empty), the handler
@@ -3209,18 +3561,14 @@ function createRelationTool(options) {
3209
3561
  * normalizing parse accept this value", that is would {@link createEndpointTool}'s default enforcement
3210
3562
  * admit it (`checker.parse(candidate) !== undefined`) — computed for every candidate regardless of
3211
3563
  * `valid`; by the house parse/guard round-trip, a `valid: true` entry is
3212
- * always also `coercible: true`. `@orkestrel/contract`'s `explain` mirrors the normalizing
3213
- * `parse`'s leniency, not `is`'s strictness — so a strictly-invalid but coercible candidate (`7`
3214
- * against a string slot) yields `{ valid: false, coercible: true, faults: [] }`: empty faults, because
3215
- * the mismatch the normalizing parse would silently fix is not one `explain` reports. `faults`
3216
- * therefore only ever populates for a non-coercible mismatch — a wrong type the parse can't coerce
3217
- * (a boolean in a string slot), a missing required key, or an out-of-enum value — where
3218
- * `coercible: false`. `checker.is` / `.parse` / `.explain` are all total over JSON-safe input — a
3219
- * JSON-safe hostile candidate (a `__proto__`-carrying object, deeply nested data) reaches each of them
3564
+ * always also `coercible: true`. `faults` is `checker.audit(candidate)`, which reports against `is`'s
3565
+ * strictness: a strictly-invalid but coercible candidate (`7` against a string slot) yields
3566
+ * `{ valid: false, coercible: true, faults: [{ reason: 'type', ... }] }`, so every invalid candidate
3567
+ * names why, an undeclared key included. `checker.is` / `.parse` / `.audit` are all total over
3568
+ * JSON-safe input — a JSON-safe hostile candidate (a `__proto__`-carrying object, deeply nested data) reaches each of them
3220
3569
  * and yields a bounded, non-throwing per-candidate verdict; a non-JSON-safe candidate (for example a
3221
- * throwing-getter `Proxy`) never reaches the checker at all — it fails the outer `args` parse
3222
- * against {@link import('./shapers.js').inferToolShape} and rejects the whole call with the same
3223
- * `TOOL` {@link import('./errors.js').ToolboxError} a malformed `samples`/`format`/`enum` throws,
3570
+ * throwing-getter `Proxy`) never reaches the checker at all — the Tool door refuses the whole call
3571
+ * with the same `ToolError` a malformed `samples`/`format`/`enum` throws,
3224
3572
  * with no per-candidate verdict produced.
3225
3573
  *
3226
3574
  * @param options - Advertised `name` / `description` overrides (see
@@ -3260,21 +3608,19 @@ function createRelationTool(options) {
3260
3608
  */
3261
3609
  function createInferTool(options) {
3262
3610
  const contract = (0, _orkestrel_contract.createContract)(inferToolShape);
3263
- const parameters = (0, _orkestrel_contract.schemaToParameters)(contract.schema);
3264
3611
  return (0, _orkestrel_tool.createTool)({
3265
3612
  name: options?.name ?? "infer",
3266
3613
  description: options?.description ?? INFER_TOOL_DESCRIPTION,
3267
3614
  summary: INFER_TOOL_SUMMARY,
3268
- ...parameters === void 0 ? {} : { parameters },
3615
+ contract: inferToolShape,
3269
3616
  async execute(args) {
3270
- const parsed = contract.parse(args);
3271
- if (parsed === void 0) throw new ToolboxError("TOOL", "malformed infer arguments", { args });
3617
+ const parsed = requireToolCall(contract, args);
3272
3618
  const schema = (0, _orkestrel_contract.samplesToSchema)(parsed.samples, {
3273
3619
  format: parsed.format ?? false,
3274
3620
  enum: parsed.enum ?? false
3275
3621
  });
3276
3622
  const result = (0, _orkestrel_contract.schemaToParameters)((0, _orkestrel_contract.schemaToObject)(schema));
3277
- if (result === void 0) throw new ToolboxError("TOOL", "could not infer a schema", { args });
3623
+ if (result === void 0) throw new ToolboxError("TOOL", "could not infer a schema; send samples that are JSON values of one shape", { args });
3278
3624
  if (parsed.candidates === void 0) return result;
3279
3625
  const checker = (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.schemaToShape)(schema));
3280
3626
  return {
@@ -3290,7 +3636,7 @@ function createInferTool(options) {
3290
3636
  index,
3291
3637
  valid,
3292
3638
  coercible,
3293
- faults: checker.explain(candidate)
3639
+ faults: checker.audit(candidate)
3294
3640
  };
3295
3641
  })
3296
3642
  };
@@ -3307,20 +3653,19 @@ function createInferTool(options) {
3307
3653
  * `@orkestrel/contract`'s `samplesToSchema` (tuned by {@link import('./types.js').EndpointToolOptions}'s
3308
3654
  * `format` / `enum`), wrapping a non-object root as `{ value: <schema> }` through `schemaToObject` —
3309
3655
  * the same object-rooted schema is both the advertised `parameters` and, by default
3310
- * ({@link import('./types.js').EndpointToolOptions.validate} `true`), the enforced contract:
3311
- * `@orkestrel/contract`'s `schemaToShape` compiles it once (through `createContract`) into a
3312
- * `ContractInterface` whose `.parse` runs on every call's `args` before `definition.execute` — a
3656
+ * ({@link import('./types.js').EndpointToolOptions.strict} `true`), the enforced contract: the Tool
3657
+ * door converts it through `@orkestrel/contract`'s lenient `schemaToShape` and compiles it on the
3658
+ * first call, then runs on every call's `args` before `definition.execute` — a
3313
3659
  * normalizing parse, not a strict type check: a scalar is coerced to its inferred type where the
3314
3660
  * house parsers coerce (a number to/from a numeric string, a boolean from `'1'`/`'0'`/`'true'`/
3315
3661
  * `'false'`/`1`/`0`), so `definition.execute` receives the coerced value (for example `7` sent for a
3316
- * string slot arrives as `'7'`), not the raw call value. A call whose `args` fails to parse into
3317
- * a record — a required key missing, or a value not coercible to its slot's type — throws a
3318
- * typed `TOOL` {@link import('./errors.js').ToolboxError} carrying the compiled contract's
3319
- * structured `explain` faults, and `definition.execute` is never called. `format` annotations are
3320
- * never asserted, and a key outside the closed inferred schema is silently dropped rather than
3321
- * rejected (see {@link import('./types.js').EndpointToolOptions.validate}). With
3322
- * `validate: false`, the tool's `execute` passes through the model-supplied `args` to
3323
- * `definition.execute` without re-validation — the raw-passthrough opt-out. The execution context
3662
+ * string slot arrives as `'7'`), not the raw call value. A call whose `args` carries a key outside
3663
+ * the closed inferred schema, misses a required key, or holds a value not coercible to its slot's
3664
+ * type throws a `ToolError` with code `ARGUMENTS` (`@orkestrel/tool`) carrying `context.faults`, and
3665
+ * `definition.execute` is never called. `format` annotations are never asserted (see
3666
+ * {@link import('./types.js').EndpointToolOptions.strict}). With
3667
+ * `strict: false`, the tool's `execute` passes through the model-supplied `args` to
3668
+ * `definition.execute` without validation — the raw-passthrough opt-out. The execution context
3324
3669
  * the tool's `execute` receives reaches `definition.execute` unchanged whichever way `args` are
3325
3670
  * handled, so a handler that reads `context.signal` observes the caller's cancellation. Either way,
3326
3671
  * the definition's return flows back as the tool call's plain result; a throw propagates uncaught,
@@ -3329,7 +3674,7 @@ function createInferTool(options) {
3329
3674
  *
3330
3675
  * @param definition - The endpoint's identity, non-empty samples, and local handler (see
3331
3676
  * {@link import('./types.js').EndpointDefinition})
3332
- * @param options - Construction-time inference tuning and the validate opt-out (see
3677
+ * @param options - Construction-time inference tuning and the strict opt-out (see
3333
3678
  * {@link import('./types.js').EndpointToolOptions})
3334
3679
  * @returns A `ToolInterface` named `definition.name`
3335
3680
  *
@@ -3362,7 +3707,7 @@ function createInferTool(options) {
3362
3707
  * name: 'lookupUser',
3363
3708
  * arguments: { id: true, name: 'Ada' },
3364
3709
  * })
3365
- * // rejected.error -> the TOOL ToolboxError message
3710
+ * // rejected.error -> the ARGUMENTS ToolError message, which names `id`
3366
3711
  * ```
3367
3712
  */
3368
3713
  function createEndpointTool(definition, options) {
@@ -3372,26 +3717,13 @@ function createEndpointTool(definition, options) {
3372
3717
  enum: options?.enum ?? false
3373
3718
  }));
3374
3719
  const parameters = (0, _orkestrel_contract.schemaToParameters)(objectSchema);
3375
- if (!(options?.validate ?? true)) return (0, _orkestrel_tool.createTool)({
3376
- name: definition.name,
3377
- description: definition.description,
3378
- ...parameters === void 0 ? {} : { parameters },
3379
- execute(args, context) {
3380
- return definition.execute(args, context);
3381
- }
3382
- });
3383
- const contract = (0, _orkestrel_contract.createContract)((0, _orkestrel_contract.schemaToShape)(objectSchema));
3384
3720
  return (0, _orkestrel_tool.createTool)({
3385
3721
  name: definition.name,
3386
3722
  description: definition.description,
3387
3723
  ...parameters === void 0 ? {} : { parameters },
3724
+ ...options?.strict === void 0 ? {} : { strict: options.strict },
3388
3725
  execute(args, context) {
3389
- const parsed = contract.parse(args);
3390
- if (parsed === void 0 || !(0, _orkestrel_contract.isRecord)(parsed)) throw new ToolboxError("TOOL", "malformed endpoint call arguments", {
3391
- name: definition.name,
3392
- faults: contract.explain(args)
3393
- });
3394
- return definition.execute(parsed, context);
3726
+ return definition.execute(args, context);
3395
3727
  }
3396
3728
  });
3397
3729
  }
@@ -3437,6 +3769,7 @@ exports.WORKSPACE_TOOL_NAME = WORKSPACE_TOOL_NAME;
3437
3769
  exports.WORKSPACE_TOOL_SUMMARY = WORKSPACE_TOOL_SUMMARY;
3438
3770
  exports.agentToolShape = agentToolShape;
3439
3771
  exports.answerToolShape = answerToolShape;
3772
+ exports.buildArgumentRefusal = buildArgumentRefusal;
3440
3773
  exports.clampQuery = clampQuery;
3441
3774
  exports.columnPrimitiveShape = columnPrimitiveShape;
3442
3775
  exports.columnSpecShape = columnSpecShape;
@@ -3463,13 +3796,18 @@ exports.createWorkflowFunctions = createWorkflowFunctions;
3463
3796
  exports.createWorkflowTool = createWorkflowTool;
3464
3797
  exports.createWorkspaceTool = createWorkspaceTool;
3465
3798
  exports.databaseToolShape = databaseToolShape;
3799
+ exports.definitionContract = definitionContract;
3800
+ exports.definitionShape = definitionShape;
3466
3801
  exports.deriveWorkflowDepth = deriveWorkflowDepth;
3802
+ exports.describeDefinitionMismatch = describeDefinitionMismatch;
3803
+ exports.describeDefinitionRefusal = describeDefinitionRefusal;
3467
3804
  exports.describeToolShape = describeToolShape;
3468
3805
  exports.expandInclude = expandInclude;
3469
3806
  exports.expandSteps = expandSteps;
3470
3807
  exports.expandTables = expandTables;
3471
3808
  exports.extendLineage = extendLineage;
3472
3809
  exports.includeShape = includeShape;
3810
+ exports.indexesShape = indexesShape;
3473
3811
  exports.inferDatabaseCode = inferDatabaseCode;
3474
3812
  exports.inferRelationCode = inferRelationCode;
3475
3813
  exports.inferTerminalCode = inferTerminalCode;
@@ -3478,17 +3816,26 @@ exports.isAgentFunction = isAgentFunction;
3478
3816
  exports.isColumnPrimitive = isColumnPrimitive;
3479
3817
  exports.isColumnSpec = isColumnSpec;
3480
3818
  exports.isDatabaseDefinition = isDatabaseDefinition;
3819
+ exports.isIndexMap = isIndexMap;
3820
+ exports.isPrimaryMap = isPrimaryMap;
3821
+ exports.isTableSpec = isTableSpec;
3481
3822
  exports.isToolboxError = isToolboxError;
3482
3823
  exports.isWorkflowLineage = isWorkflowLineage;
3483
3824
  exports.keyShape = keyShape;
3825
+ exports.limitRefusal = limitRefusal;
3484
3826
  exports.managerShape = managerShape;
3485
3827
  exports.normalizeLineage = normalizeLineage;
3486
3828
  exports.normalizeQuery = normalizeQuery;
3487
3829
  exports.orderShape = orderShape;
3488
3830
  exports.phaseDraftShape = phaseDraftShape;
3831
+ exports.primaryShape = primaryShape;
3489
3832
  exports.promptToolShape = promptToolShape;
3490
3833
  exports.queryShape = queryShape;
3491
3834
  exports.relationToolShape = relationToolShape;
3835
+ exports.renderCandidates = renderCandidates;
3836
+ exports.requireActiveWorkspace = requireActiveWorkspace;
3837
+ exports.requireToolCall = requireToolCall;
3838
+ exports.resolveBound = resolveBound;
3492
3839
  exports.resolveLimit = resolveLimit;
3493
3840
  exports.resolveRelationManager = resolveRelationManager;
3494
3841
  exports.resolveRelationModel = resolveRelationModel;