@orkestrel/toolbox 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -2
- package/dist/src/core/index.cjs +424 -365
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +356 -312
- package/dist/src/core/index.d.ts +356 -312
- package/dist/src/core/index.js +416 -361
- package/dist/src/core/index.js.map +1 -1
- package/package.json +8 -7
package/dist/src/core/index.js
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import { arrayShape, booleanShape, createContract, integerShape, isNonEmptyString, isRecord, isString, jsonShape, literalShape, numberShape, objectShape, optionalShape, rawShape, recordShape, samplesToSchema, schemaToObject, schemaToParameters, schemaToShape, stringShape, unionShape } from "@orkestrel/contract";
|
|
1
|
+
import { arrayShape, attempt, booleanShape, cloneJSONRecord, createContract, integerShape, isFiniteNumber, isNonEmptyString, isRecord, isString, jsonShape, literalShape, numberShape, objectShape, optionalShape, parseJSONValue, rawShape, recordShape, samplesToSchema, schemaToObject, schemaToParameters, schemaToShape, stringShape, unionShape } from "@orkestrel/contract";
|
|
2
|
+
import { MAX_TIMER_MS, createWorkflowContract } from "@orkestrel/workflow";
|
|
2
3
|
import { isTerminalError } from "@orkestrel/terminal";
|
|
3
|
-
import { createDatabase, createMemoryDriver,
|
|
4
|
+
import { createDatabase, createMemoryDriver, isDatabaseError } from "@orkestrel/database";
|
|
4
5
|
import { isRelationError } from "@orkestrel/relation";
|
|
5
|
-
import {
|
|
6
|
+
import { agentResultToJSON } from "@orkestrel/agent";
|
|
7
|
+
import { createWorkspaceManager, isText, rangeOf } from "@orkestrel/workspace";
|
|
6
8
|
import { createTool } from "@orkestrel/tool";
|
|
7
|
-
import { WorkflowError, createWorkflowContract } from "@orkestrel/workflow";
|
|
8
9
|
//#region src/core/constants.ts
|
|
9
10
|
/**
|
|
10
11
|
* The name {@link import('./factories.js').createAgentTool} advertises by default — the key a
|
|
@@ -25,15 +26,6 @@ var AGENT_TOOL_NAME = "agent";
|
|
|
25
26
|
*/
|
|
26
27
|
var AGENT_TOOL_DEPTH = 8;
|
|
27
28
|
/**
|
|
28
|
-
* The DESCRIPTION {@link import('./factories.js').createAgentTool} advertises — a short guide
|
|
29
|
-
* that teaches a model how to delegate a task to a sub-agent.
|
|
30
|
-
*
|
|
31
|
-
* @remarks
|
|
32
|
-
* Mirrors {@link WORKFLOW_TOOL_DESCRIPTION} / `WORKSPACE_TOOL_DESCRIPTION`'s teaching style: names
|
|
33
|
-
* the required `task` field, and documents the optional per-call `provider` / `tools` /
|
|
34
|
-
* `system` overrides.
|
|
35
|
-
*/
|
|
36
|
-
/**
|
|
37
29
|
* The lean {@link import('@orkestrel/tool').ToolInterface.summary} {@link import('./factories.js').createAgentTool}
|
|
38
30
|
* advertises in place of {@link AGENT_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
|
|
39
31
|
* (`@orkestrel/tool`) advertises `summary ?? description`, so this one-sentence text stands in
|
|
@@ -41,6 +33,10 @@ var AGENT_TOOL_DEPTH = 8;
|
|
|
41
33
|
* {@link import('./factories.js').createDescribeTool}.
|
|
42
34
|
*/
|
|
43
35
|
var AGENT_TOOL_SUMMARY = "Delegate a task to a sub-agent and return its result; each call runs one sub-agent turn to completion. Call describe('agent') for the optional provider/tools/system overrides.";
|
|
36
|
+
/**
|
|
37
|
+
* The description {@link import('./factories.js').createAgentTool} advertises — a short guide
|
|
38
|
+
* covering the required task and optional provider, tools, and system overrides.
|
|
39
|
+
*/
|
|
44
40
|
var AGENT_TOOL_DESCRIPTION = [
|
|
45
41
|
"Delegate a task to a sub-agent and return its result. Every call runs ONE sub-agent turn to completion.",
|
|
46
42
|
"",
|
|
@@ -59,11 +55,9 @@ var AGENT_TOOL_DESCRIPTION = [
|
|
|
59
55
|
* {@link import('./factories.js').createWorkflowTool}'s depth/cycle guards enforce.
|
|
60
56
|
*
|
|
61
57
|
* @remarks
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
65
|
-
* workflow through its bound workflow tool (that would be depth `MAX_WORKFLOW_DEPTH + 1`), so
|
|
66
|
-
* the over-deep invocation is REJECTED (a typed `DEPTH` `WorkflowError` throw, `@orkestrel/workflow`).
|
|
58
|
+
* Depth is zero-based and counts workflow tags after the root: the root plus eight nested
|
|
59
|
+
* workflows is allowed, while a ninth nested workflow is rejected with this package's typed
|
|
60
|
+
* `DEPTH` `ToolboxError`.
|
|
67
61
|
*/
|
|
68
62
|
var MAX_WORKFLOW_DEPTH = 8;
|
|
69
63
|
/**
|
|
@@ -73,7 +67,7 @@ var MAX_WORKFLOW_DEPTH = 8;
|
|
|
73
67
|
* under onto a wrapped agent's `context.tools`.
|
|
74
68
|
*
|
|
75
69
|
* @remarks
|
|
76
|
-
*
|
|
70
|
+
* The propagation seam's well-known key: when
|
|
77
71
|
* `createAgentFunction`'s `runner` option is supplied, it adds a `createWorkflowTool`-built tool
|
|
78
72
|
* under this name to the agent's `context.tools`, so it can author + run a NESTED workflow
|
|
79
73
|
* (bounded by {@link MAX_WORKFLOW_DEPTH}).
|
|
@@ -84,7 +78,7 @@ var WORKFLOW_TOOL_NAME = "workflow";
|
|
|
84
78
|
* {@link import('./factories.js').createWorkflowTool}: `{ name, steps: [{ name }] }`.
|
|
85
79
|
*
|
|
86
80
|
* @remarks
|
|
87
|
-
*
|
|
81
|
+
* Each step becomes a one-task phase, in
|
|
88
82
|
* order; a step's `name` is a REGISTERED behavior name (not a label) — the registry key its
|
|
89
83
|
* task's `run` resolves against. The tool expands this
|
|
90
84
|
* ({@link import('./helpers.js').expandSteps}) into a valid `WorkflowDefinition`
|
|
@@ -99,7 +93,7 @@ var WORKFLOW_TOOL_FLAT_EXAMPLE = Object.freeze({
|
|
|
99
93
|
* the flat shape: a full `WorkflowDefinition` (`@orkestrel/workflow`).
|
|
100
94
|
*
|
|
101
95
|
* @remarks
|
|
102
|
-
*
|
|
96
|
+
* The full four-level form, documented in
|
|
103
97
|
* {@link WORKFLOW_TOOL_DESCRIPTION} as the advanced alternative. It is embedded VERBATIM.
|
|
104
98
|
*/
|
|
105
99
|
var WORKFLOW_TOOL_NESTED_EXAMPLE = Object.freeze({
|
|
@@ -116,19 +110,6 @@ var WORKFLOW_TOOL_NESTED_EXAMPLE = Object.freeze({
|
|
|
116
110
|
})])
|
|
117
111
|
});
|
|
118
112
|
/**
|
|
119
|
-
* The DESCRIPTION {@link import('./factories.js').createWorkflowTool} advertises — a multi-line
|
|
120
|
-
* guide that teaches a small model how to author a complete workflow tree.
|
|
121
|
-
*
|
|
122
|
-
* @remarks
|
|
123
|
-
* OWNED here now (ported from `@orkestrel/workflow`). Presents the SIMPLE flat shape
|
|
124
|
-
* (`{ name, steps: [{ name }] }`) as the PRIMARY way with one complete worked example
|
|
125
|
-
* ({@link WORKFLOW_TOOL_FLAT_EXAMPLE}), names that a step's `name` is a REGISTERED name (not a
|
|
126
|
-
* human label), and documents the full nested `WorkflowDefinition` as the ADVANCED form with a
|
|
127
|
-
* minimal example ({@link WORKFLOW_TOOL_NESTED_EXAMPLE}). The `parameters` the tool advertises
|
|
128
|
-
* are the FLAT shape's schema; the nested form is the documented escape-hatch (the tool accepts
|
|
129
|
-
* both).
|
|
130
|
-
*/
|
|
131
|
-
/**
|
|
132
113
|
* The lean {@link import('@orkestrel/tool').ToolInterface.summary} {@link import('./factories.js').createWorkflowTool}
|
|
133
114
|
* advertises in place of {@link WORKFLOW_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
|
|
134
115
|
* (`@orkestrel/tool`) advertises `summary ?? description`, so this one-sentence text stands in
|
|
@@ -136,26 +117,28 @@ var WORKFLOW_TOOL_NESTED_EXAMPLE = Object.freeze({
|
|
|
136
117
|
* {@link import('./factories.js').createDescribeTool}.
|
|
137
118
|
*/
|
|
138
119
|
var WORKFLOW_TOOL_SUMMARY = "Author and run a multi-phase workflow in one call — phases run in sequence, tasks within a phase run concurrently. Call describe('workflow') for the full authoring schema and examples.";
|
|
120
|
+
/**
|
|
121
|
+
* The description {@link import('./factories.js').createWorkflowTool} advertises — the flat
|
|
122
|
+
* authoring form, its worked example, and the advanced nested definition form.
|
|
123
|
+
*/
|
|
139
124
|
var WORKFLOW_TOOL_DESCRIPTION = [
|
|
140
125
|
"Author and run a workflow (phases run sequentially, the tasks within a phase run concurrently) in one call.",
|
|
141
126
|
"",
|
|
142
127
|
"SIMPLEST way — a flat list of steps. Each step runs one registered behavior; steps run one after another:",
|
|
143
128
|
" { \"name\": \"<workflow name>\", \"steps\": [ { \"name\": \"<registered name>\" }, ... ] }",
|
|
144
129
|
"- a step's \"name\" is a REGISTERED behavior name (a registry key), NOT a human label.",
|
|
145
|
-
"-
|
|
130
|
+
"- every step name must exist in the host-supplied functions registry; an unknown name is rejected.",
|
|
131
|
+
"- the top-level \"name\" is optional; when present it is also the workflow persistence id. Other ids are filled in for you.",
|
|
146
132
|
"Example:",
|
|
147
133
|
JSON.stringify(WORKFLOW_TOOL_FLAT_EXAMPLE),
|
|
148
134
|
"",
|
|
149
|
-
"ADVANCED — the full nested form, for multi-task phases or explicit ids. A workflow has phases; a phase has tasks; a task
|
|
135
|
+
"ADVANCED — the full nested form, for multi-task phases or explicit ids. A workflow has phases; a phase has tasks; a task may have a \"run\" (a registered behavior name); omitting it creates a JSON-null no-op:",
|
|
150
136
|
JSON.stringify(WORKFLOW_TOOL_NESTED_EXAMPLE),
|
|
151
137
|
"In the nested form you may omit any \"id\"/\"name\" and they are filled in positionally; a provided one is kept."
|
|
152
138
|
].join("\n");
|
|
153
139
|
/**
|
|
154
140
|
* The name {@link import('./factories.js').createWorkspaceTool} advertises by default — the key a
|
|
155
141
|
* model calls and the `ToolManagerInterface` (`@orkestrel/tool`) registers under.
|
|
156
|
-
*
|
|
157
|
-
* @remarks
|
|
158
|
-
* OWNED here now (ported from `@orkestrel/agent`).
|
|
159
142
|
*/
|
|
160
143
|
var WORKSPACE_TOOL_NAME = "workspace";
|
|
161
144
|
/**
|
|
@@ -163,7 +146,7 @@ var WORKSPACE_TOOL_NAME = "workspace";
|
|
|
163
146
|
* VERBATIM in {@link WORKSPACE_TOOL_DESCRIPTION}.
|
|
164
147
|
*
|
|
165
148
|
* @remarks
|
|
166
|
-
*
|
|
149
|
+
* A `'write'` op (the most common authoring
|
|
167
150
|
* action): create or overwrite `notes.txt` with `hello`. Frozen so it cannot be mutated in
|
|
168
151
|
* place.
|
|
169
152
|
*/
|
|
@@ -173,17 +156,6 @@ var WORKSPACE_TOOL_EXAMPLE = Object.freeze({
|
|
|
173
156
|
content: "hello"
|
|
174
157
|
});
|
|
175
158
|
/**
|
|
176
|
-
* The DESCRIPTION {@link import('./factories.js').createWorkspaceTool} advertises — a multi-line
|
|
177
|
-
* guide that teaches a small model how to drive a workspace through the single
|
|
178
|
-
* `operation`-keyed tool.
|
|
179
|
-
*
|
|
180
|
-
* @remarks
|
|
181
|
-
* OWNED here now (ported from `@orkestrel/agent`). Mirrors {@link WORKFLOW_TOOL_DESCRIPTION}'s
|
|
182
|
-
* teaching style: names the `operation` discriminant field, enumerates all 13 operations with
|
|
183
|
-
* their FLAT fields, gives a worked example for the common ones, and embeds
|
|
184
|
-
* {@link WORKSPACE_TOOL_EXAMPLE} verbatim.
|
|
185
|
-
*/
|
|
186
|
-
/**
|
|
187
159
|
* The lean {@link import('@orkestrel/tool').ToolInterface.summary} {@link import('./factories.js').createWorkspaceTool}
|
|
188
160
|
* advertises in place of {@link WORKSPACE_TOOL_DESCRIPTION} — a `ToolManagerInterface.definitions()`
|
|
189
161
|
* (`@orkestrel/tool`) advertises `summary ?? description`, so this one-sentence text stands in
|
|
@@ -191,6 +163,14 @@ var WORKSPACE_TOOL_EXAMPLE = Object.freeze({
|
|
|
191
163
|
* {@link import('./factories.js').createDescribeTool}.
|
|
192
164
|
*/
|
|
193
165
|
var WORKSPACE_TOOL_SUMMARY = "Read and edit files in a workspace — one operation per call (read, write, list, search, replace, splice, move, remove, plus workspace switching), chosen by the 'operation' field. Call describe('workspace') for the full operation list and fields.";
|
|
166
|
+
/**
|
|
167
|
+
* The description {@link import('./factories.js').createWorkspaceTool} advertises — the
|
|
168
|
+
* operation-keyed workspace protocol, all supported operations, and worked examples.
|
|
169
|
+
*
|
|
170
|
+
* @remarks
|
|
171
|
+
* Mirrors {@link WORKFLOW_TOOL_DESCRIPTION}'s teaching style and embeds
|
|
172
|
+
* {@link WORKSPACE_TOOL_EXAMPLE} verbatim.
|
|
173
|
+
*/
|
|
194
174
|
var WORKSPACE_TOOL_DESCRIPTION = [
|
|
195
175
|
"Read and edit files in a workspace. Every call is ONE operation, chosen by the \"operation\" field.",
|
|
196
176
|
"All file operations act on the ACTIVE workspace; use \"workspaces\" then \"switch\" to move between workspaces.",
|
|
@@ -199,8 +179,8 @@ var WORKSPACE_TOOL_DESCRIPTION = [
|
|
|
199
179
|
"- read { \"operation\": \"read\", \"path\": \"<file>\" } — return the file's text.",
|
|
200
180
|
"- list { \"operation\": \"list\" } — list every file in the active workspace (path, state, size, lines, kind).",
|
|
201
181
|
"- has { \"operation\": \"has\", \"path\": \"<file>\" } — whether the file exists.",
|
|
202
|
-
"- search { \"operation\": \"search\", \"query\": \"<text>\", \"regex\"?: bool, \"
|
|
203
|
-
"- replace { \"operation\": \"replace\", \"query\": \"<text>\", \"replacement\": \"<text>\", \"regex\"?: bool, \"
|
|
182
|
+
"- search { \"operation\": \"search\", \"query\": \"<text>\", \"regex\"?: bool, \"sensitive\"?: bool, \"limit\"?: int } — find lines matching the query across all files.",
|
|
183
|
+
"- replace { \"operation\": \"replace\", \"query\": \"<text>\", \"replacement\": \"<text>\", \"regex\"?: bool, \"sensitive\"?: bool, \"limit\"?: int } — replace matches across all files.",
|
|
204
184
|
"- write { \"operation\": \"write\", \"path\": \"<file>\", \"content\": \"<text>\" } — create or overwrite the whole file.",
|
|
205
185
|
"- splice { \"operation\": \"splice\", \"path\": \"<file>\", \"content\": \"<text>\", \"fromLine\": int, \"fromColumn\": int, \"toLine\": int, \"toColumn\": int } — replace a 1-based range (from inclusive, to exclusive) with content.",
|
|
206
186
|
"- prepend { \"operation\": \"prepend\", \"path\": \"<file>\", \"content\": \"<text>\" } — add content to the start of the file.",
|
|
@@ -210,7 +190,7 @@ var WORKSPACE_TOOL_DESCRIPTION = [
|
|
|
210
190
|
"- workspaces { \"operation\": \"workspaces\" } — list the workspaces you can switch between (each id, file count, active).",
|
|
211
191
|
"- switch { \"operation\": \"switch\", \"id\": \"<id>\" } — make the workspace with that id active (ids come from \"workspaces\").",
|
|
212
192
|
"",
|
|
213
|
-
"Notes: lines and columns are 1-based (column 1 is the first character). \"regex\" defaults to false (a literal substring), \"
|
|
193
|
+
"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.",
|
|
214
194
|
"",
|
|
215
195
|
"Example — write a file:",
|
|
216
196
|
JSON.stringify(WORKSPACE_TOOL_EXAMPLE)
|
|
@@ -253,6 +233,7 @@ var PROMPT_TOOL_NAME = "ask";
|
|
|
253
233
|
* {@link import('./factories.js').createDescribeTool}.
|
|
254
234
|
*/
|
|
255
235
|
var PROMPT_TOOL_SUMMARY = "Ask another terminal a question and BLOCK until it answers; the call resolves with the answered value. Call describe('ask') for the required fields.";
|
|
236
|
+
/** The full prompt protocol {@link import('./factories.js').createPromptTool} advertises. */
|
|
256
237
|
var PROMPT_TOOL_DESCRIPTION = [
|
|
257
238
|
"Ask another terminal a question and block until it answers. This call does not return until the addressed terminal answers, or the prompt fails.",
|
|
258
239
|
"",
|
|
@@ -283,6 +264,7 @@ var ANSWER_TOOL_NAME = "answer";
|
|
|
283
264
|
* {@link import('./factories.js').createDescribeTool}.
|
|
284
265
|
*/
|
|
285
266
|
var ANSWER_TOOL_SUMMARY = "List prompts addressed to this terminal, or answer one by id. Call describe('answer') for the required fields.";
|
|
267
|
+
/** The pending/answer protocol {@link import('./factories.js').createAnswerTool} advertises. */
|
|
286
268
|
var ANSWER_TOOL_DESCRIPTION = [
|
|
287
269
|
"List the prompts currently addressed to this terminal, or answer one of them by id. Every call is ONE operation, chosen by the \"operation\" field.",
|
|
288
270
|
"",
|
|
@@ -312,14 +294,14 @@ var DATABASE_TOOL_NAME = "database";
|
|
|
312
294
|
* The lean {@link import('@orkestrel/tool').ToolInterface.summary} the upcoming database tool
|
|
313
295
|
* will advertise in place of {@link DATABASE_TOOL_DESCRIPTION}.
|
|
314
296
|
*/
|
|
315
|
-
var DATABASE_TOOL_SUMMARY = "Create and query a database — one operation per call (create, tables, get, records, count, aggregate, add, set, update, remove,
|
|
297
|
+
var DATABASE_TOOL_SUMMARY = "Create and query a database — one operation per call (create, tables, get, records, count, aggregate, add, set, update, remove, destroy), chosen by the 'operation' field. Call describe('database') for the full operation list, the query form, and the column DSL.";
|
|
316
298
|
/**
|
|
317
299
|
* The DESCRIPTION the upcoming database tool will advertise — a multi-line guide that teaches a
|
|
318
|
-
* small model the operation list, the SERIALIZED
|
|
300
|
+
* small model the operation list, the SERIALIZED query form, and the {@link import('./types.js').TableSpec}
|
|
319
301
|
* column DSL.
|
|
320
302
|
*
|
|
321
303
|
* @remarks
|
|
322
|
-
* The
|
|
304
|
+
* The query form is deliberately SERIALIZED (never fluent) — every condition is a flat object
|
|
323
305
|
* `{ column, operator, values, connector? }` where `values` is ALWAYS an array, even for a
|
|
324
306
|
* single-value operator (`{ column: 'age', operator: 'from', values: [18] }`), so a small model
|
|
325
307
|
* never has to chain method calls or guess whether a value is scalar or a list.
|
|
@@ -328,25 +310,25 @@ var DATABASE_TOOL_DESCRIPTION = [
|
|
|
328
310
|
"Create and query a database. Every call is ONE operation, chosen by the \"operation\" field.",
|
|
329
311
|
"",
|
|
330
312
|
"Operations (each takes the fields listed):",
|
|
331
|
-
"- create { \"operation\": \"create\", \"id\": \"<database id>\", \"tables\": { \"<table>\": { \"columns\": { \"<column>\": \"string\" | \"integer\" | \"number\" | \"boolean\" | { \"type\": \"string\", \"optional\": true } } } } } — define a new database.",
|
|
313
|
+
"- create { \"operation\": \"create\", \"id\": \"<database id>\", \"tables\": { \"<table>\": { \"columns\": { \"<column>\": \"string\" | \"integer\" | \"number\" | \"boolean\" | { \"type\": \"string\", \"optional\": true } } } }, \"primary\"?: { \"<table>\": \"<column>\" }, \"indexes\"?: { \"<table>\": [[\"<column>\"]] }, \"version\"?: <number> } — define a new database.",
|
|
332
314
|
"- tables { \"operation\": \"tables\", \"id\": \"<database id>\" } — list a database's table names.",
|
|
333
315
|
"- get { \"operation\": \"get\", \"id\": \"<database id>\", \"table\": \"<table>\", \"key\": \"<row key>\" } — fetch one row by its primary key.",
|
|
334
|
-
"- records { \"operation\": \"records\", \"id\": \"<database id>\", \"table\": \"<table>\", \"
|
|
335
|
-
"- count { \"operation\": \"count\", \"id\": \"<database id>\", \"table\": \"<table>\", \"
|
|
336
|
-
"- aggregate { \"operation\": \"aggregate\", \"id\": \"<database id>\", \"table\": \"<table>\", \"column\": \"<column>\", \"function\": \"count\" | \"sum\" | \"average\" | \"minimum\" | \"maximum\", \"
|
|
316
|
+
"- records { \"operation\": \"records\", \"id\": \"<database id>\", \"table\": \"<table>\", \"query\"?: <Query> } — list rows matching a query.",
|
|
317
|
+
"- count { \"operation\": \"count\", \"id\": \"<database id>\", \"table\": \"<table>\", \"query\"?: <Query> } — count rows matching a query.",
|
|
318
|
+
"- aggregate { \"operation\": \"aggregate\", \"id\": \"<database id>\", \"table\": \"<table>\", \"column\": \"<column>\", \"function\": \"count\" | \"sum\" | \"average\" | \"minimum\" | \"maximum\", \"query\"?: <Query> } — compute an aggregate.",
|
|
337
319
|
"- add { \"operation\": \"add\", \"id\": \"<database id>\", \"table\": \"<table>\", \"row\": { ... } } — insert a row (fails on a duplicate key).",
|
|
338
320
|
"- set { \"operation\": \"set\", \"id\": \"<database id>\", \"table\": \"<table>\", \"row\": { ... } } — upsert a row.",
|
|
339
|
-
"- update { \"operation\": \"update\", \"id\": \"<database id>\", \"table\": \"<table>\", \"key\": \"<row key>\", \"
|
|
321
|
+
"- update { \"operation\": \"update\", \"id\": \"<database id>\", \"table\": \"<table>\", \"key\": \"<row key>\", \"changes\": { ... } } — patch an existing row.",
|
|
340
322
|
"- remove { \"operation\": \"remove\", \"id\": \"<database id>\", \"table\": \"<table>\", \"key\": \"<row key>\" } — delete a row by key.",
|
|
341
|
-
"- migrate { \"operation\": \"migrate\", \"id\": \"<database id>\", \"tables\": { ... } } — replace the table layout in place.",
|
|
342
323
|
"- destroy { \"operation\": \"destroy\", \"id\": \"<database id>\" } — drop a database entirely.",
|
|
343
324
|
"",
|
|
344
|
-
"
|
|
325
|
+
"Query form — SERIALIZED, never fluent. A condition is a flat object; \"values\" is ALWAYS an array, even for one value:",
|
|
345
326
|
" { \"conditions\": [ { \"column\": \"age\", \"operator\": \"from\", \"values\": [18], \"connector\": \"and\" } ], \"order\"?: [...], \"offset\"?: 0, \"limit\"?: 100 }",
|
|
346
327
|
" operators: equals, not, above, below, from, to, between, like, glob, starts, ends, any, none, absent, present.",
|
|
347
328
|
" \"connector\" joins this condition to the next (\"and\" | \"or\"); omit on the last condition.",
|
|
348
329
|
"",
|
|
349
|
-
"Column DSL (used by \"create\"
|
|
330
|
+
"Column DSL (used by \"create\" \"tables\"): a column is either a bare type string (\"string\" | \"integer\" | \"number\" | \"boolean\"), or { \"type\": \"<type>\", \"optional\": true } when the column may be absent from a row.",
|
|
331
|
+
"Schema configuration is creation-time only. Evolve a schema by opening a newly configured target-version database over a versioned persistent driver; reconciliation requires paired metadata/stamp capabilities.",
|
|
350
332
|
"Example — create a database:",
|
|
351
333
|
JSON.stringify({
|
|
352
334
|
operation: "create",
|
|
@@ -360,28 +342,27 @@ var DATABASE_TOOL_DESCRIPTION = [
|
|
|
360
342
|
}
|
|
361
343
|
} } }
|
|
362
344
|
}),
|
|
363
|
-
"Example — query
|
|
345
|
+
"Example — query records:",
|
|
364
346
|
JSON.stringify({
|
|
365
347
|
operation: "records",
|
|
366
348
|
id: "shop",
|
|
367
349
|
table: "products",
|
|
368
|
-
|
|
350
|
+
query: { conditions: [{
|
|
369
351
|
column: "price",
|
|
370
352
|
operator: "below",
|
|
371
353
|
values: [50]
|
|
372
354
|
}] }
|
|
373
355
|
})
|
|
374
356
|
].join("\n");
|
|
375
|
-
/** The default cap on rows a `records`
|
|
357
|
+
/** The default cap on rows a `records` call returns when the caller omits `query.limit` — the upcoming database tool's default row ceiling. */
|
|
376
358
|
var DATABASE_TOOL_LIMIT = 1e3;
|
|
377
|
-
/** The database
|
|
378
|
-
var DATABASE_TOOL_MUTATIONS =
|
|
359
|
+
/** The runtime-frozen database-tool mutation names disabled by `DatabaseToolOptions.readonly`. */
|
|
360
|
+
var DATABASE_TOOL_MUTATIONS = Object.freeze([
|
|
379
361
|
"create",
|
|
380
362
|
"add",
|
|
381
363
|
"set",
|
|
382
364
|
"update",
|
|
383
365
|
"remove",
|
|
384
|
-
"migrate",
|
|
385
366
|
"destroy"
|
|
386
367
|
]);
|
|
387
368
|
/**
|
|
@@ -438,6 +419,7 @@ var INFER_TOOL_NAME = "infer";
|
|
|
438
419
|
* {@link import('./factories.js').createDescribeTool}.
|
|
439
420
|
*/
|
|
440
421
|
var INFER_TOOL_SUMMARY = "Infer a JSON Schema (as advertised tool parameters) from one or more example values. Call describe('infer') for the required fields.";
|
|
422
|
+
/** The schema-inference protocol {@link import('./factories.js').createInferTool} advertises. */
|
|
441
423
|
var INFER_TOOL_DESCRIPTION = [
|
|
442
424
|
"Infer a JSON Schema from example values, returned in the same shape a tool advertises its parameters.",
|
|
443
425
|
"",
|
|
@@ -530,13 +512,9 @@ var INFER_TOOL_DESCRIPTION = [
|
|
|
530
512
|
//#endregion
|
|
531
513
|
//#region src/core/errors.ts
|
|
532
514
|
/**
|
|
533
|
-
*
|
|
534
|
-
*
|
|
535
|
-
*
|
|
536
|
-
* exceed the configured depth bound or re-enter an ancestor (`DEPTH`), a prompt cycle
|
|
537
|
-
* (`DEADLOCK`), a prompt that expired before it was answered (`EXPIRE`), or an answer that
|
|
538
|
-
* failed to apply (`ANSWER`) — the last three thrown by
|
|
539
|
-
* {@link import('./factories.js').createPromptTool} / {@link import('./factories.js').createAnswerTool}.
|
|
515
|
+
* A package-owned tool-call failure: malformed input or unresolved configuration (`TOOL`),
|
|
516
|
+
* delegation depth/cycle rejection (`DEPTH`), prompt failure (`DEADLOCK` / `EXPIRE` / `ANSWER`),
|
|
517
|
+
* or a translated upstream database/relation failure (`DATABASE` / `RELATION`).
|
|
540
518
|
*
|
|
541
519
|
* @remarks
|
|
542
520
|
* Carries a machine-readable `code` (see {@link import('./types.js').ToolboxErrorCode}) and
|
|
@@ -734,7 +712,7 @@ var taskDraftShape = objectShape({
|
|
|
734
712
|
description: optionalShape(stringShape({ description: "Optional task description." })),
|
|
735
713
|
run: optionalShape(stringShape({
|
|
736
714
|
min: 1,
|
|
737
|
-
description: "The registered behavior name to invoke (a registry key, not a label); omitted
|
|
715
|
+
description: "The registered behavior name to invoke (a registry key, not a label); omitted completes with JSON null."
|
|
738
716
|
})),
|
|
739
717
|
retries: optionalShape(integerShape({
|
|
740
718
|
min: 0,
|
|
@@ -742,7 +720,8 @@ var taskDraftShape = objectShape({
|
|
|
742
720
|
})),
|
|
743
721
|
timeout: optionalShape(integerShape({
|
|
744
722
|
min: 0,
|
|
745
|
-
|
|
723
|
+
max: MAX_TIMER_MS,
|
|
724
|
+
description: "Persisted per-attempt deadline in milliseconds (0 disables it); omitted means no deadline."
|
|
746
725
|
}))
|
|
747
726
|
});
|
|
748
727
|
/**
|
|
@@ -773,7 +752,8 @@ var phaseDraftShape = objectShape({
|
|
|
773
752
|
*
|
|
774
753
|
* @remarks
|
|
775
754
|
* The lenient counterpart {@link import('./factories.js').createWorkflowDraftContract} compiles.
|
|
776
|
-
* `run` stays
|
|
755
|
+
* `run` stays optional like the strict form; omission is the deliberate JSON `null` no-op. A
|
|
756
|
+
* provided `id` / `name` still has `minLength: 1` (so
|
|
777
757
|
* an explicitly-empty `id: ''` is REJECTED, not auto-filled). After
|
|
778
758
|
* {@link import('./helpers.js').completeDraft} fills the missing ids/names, the result is
|
|
779
759
|
* validated against the STRICT `createWorkflowContract` (`@orkestrel/workflow`) gate before
|
|
@@ -834,10 +814,10 @@ var workflowStepsShape = objectShape({
|
|
|
834
814
|
* The union compiles to an `anyOf` JSON Schema + a `unionOf` guard + a first-match parser
|
|
835
815
|
* automatically ({@link import('./factories.js').createWorkspaceTool} types the result to the
|
|
836
816
|
* hand-written {@link import('./types.js').WorkspaceOperation}). `limit` and the four `'splice'`
|
|
837
|
-
* caret components are POSITIVE integers (`integerShape({ min: 1 })`); `regex` / `
|
|
838
|
-
* `optionalShape(booleanShape(...))`. The two REGISTRY arms — `workspaces` (list the
|
|
839
|
-
* the model can move between) and `switch` (re-point the active one by `id`) — let a
|
|
840
|
-
* DISCOVER then CHOOSE which workspace the edit / read arms target.
|
|
817
|
+
* caret components are POSITIVE integers (`integerShape({ min: 1 })`); `regex` / `sensitive`
|
|
818
|
+
* are `optionalShape(booleanShape(...))`. The two REGISTRY arms — `workspaces` (list the
|
|
819
|
+
* workspaces the model can move between) and `switch` (re-point the active one by `id`) — let a
|
|
820
|
+
* model DISCOVER then CHOOSE which workspace the edit / read arms target.
|
|
841
821
|
*/
|
|
842
822
|
var workspaceToolShape = unionShape(objectShape({
|
|
843
823
|
operation: literalShape(["read"], { description: "Read a whole text file's text by path." }),
|
|
@@ -849,7 +829,7 @@ var workspaceToolShape = unionShape(objectShape({
|
|
|
849
829
|
operation: literalShape(["search"], { description: "Search every text file for a query, returning each hit." }),
|
|
850
830
|
query: stringShape({ description: "The text (or regular-expression source) to search for." }),
|
|
851
831
|
regex: optionalShape(booleanShape({ description: "Treat the query as a regular expression. Defaults to false (a literal substring)." })),
|
|
852
|
-
|
|
832
|
+
sensitive: optionalShape(booleanShape({ description: "Match case-sensitively. Defaults to true (set false for case-insensitive)." })),
|
|
853
833
|
limit: optionalShape(integerShape({
|
|
854
834
|
min: 1,
|
|
855
835
|
description: "Stop after this many matches across all files. Omitted means unlimited."
|
|
@@ -859,7 +839,7 @@ var workspaceToolShape = unionShape(objectShape({
|
|
|
859
839
|
query: stringShape({ description: "The text (or regular-expression source) to replace." }),
|
|
860
840
|
replacement: stringShape({ description: "The text to substitute for each match." }),
|
|
861
841
|
regex: optionalShape(booleanShape({ description: "Treat the query as a regular expression. Defaults to false (a literal substring)." })),
|
|
862
|
-
|
|
842
|
+
sensitive: optionalShape(booleanShape({ description: "Match case-sensitively. Defaults to true (set false for case-insensitive)." })),
|
|
863
843
|
limit: optionalShape(integerShape({
|
|
864
844
|
min: 1,
|
|
865
845
|
description: "Stop after this many replacements across all files. Omitted means unlimited."
|
|
@@ -955,8 +935,8 @@ var orderShape = objectShape({
|
|
|
955
935
|
column: stringShape({ description: "The column to sort by." }),
|
|
956
936
|
direction: literalShape(["ascending", "descending"], { description: "The sort direction." })
|
|
957
937
|
});
|
|
958
|
-
/** The SERIALIZED
|
|
959
|
-
var
|
|
938
|
+
/** The SERIALIZED query form — conditions, order, and pagination. */
|
|
939
|
+
var queryShape = objectShape({
|
|
960
940
|
conditions: optionalShape(arrayShape(conditionShape, { description: "The WHERE conditions, folded left to right." })),
|
|
961
941
|
order: optionalShape(arrayShape(orderShape, { description: "The sort terms, applied in order." })),
|
|
962
942
|
limit: optionalShape(integerShape({
|
|
@@ -970,17 +950,17 @@ var criteriaShape = objectShape({
|
|
|
970
950
|
});
|
|
971
951
|
/**
|
|
972
952
|
* The shape of {@link import('./factories.js').createDatabaseTool}'s call arguments —
|
|
973
|
-
* discriminated by `operation` into the
|
|
953
|
+
* discriminated by `operation` into the 11 database operations (`'create'` / `'tables'` /
|
|
974
954
|
* `'get'` / `'records'` / `'count'` / `'aggregate'` / `'add'` / `'set'` / `'update'` /
|
|
975
|
-
* `'remove'` / `'
|
|
955
|
+
* `'remove'` / `'destroy'`).
|
|
976
956
|
*
|
|
977
957
|
* @remarks
|
|
978
|
-
* Every arm carries `id` (the database id). `'create'`
|
|
958
|
+
* Every arm carries `id` (the database id). `'create'` carries `tables` (the
|
|
979
959
|
* {@link import('./types.js').TableSpec} column DSL, compiled via
|
|
980
960
|
* {@link import('./helpers.js').expandTables}); `'get'` / `'update'` / `'remove'` carry `key`
|
|
981
961
|
* (one key or an array of keys, positional); `'add'` / `'set'` carry `row` (one row or an array of
|
|
982
962
|
* rows); `'update'` also carries `changes` (a loose partial row); `'records'` / `'count'` /
|
|
983
|
-
* `'aggregate'` carry an optional `
|
|
963
|
+
* `'aggregate'` carry an optional `query` (the SERIALIZED form — `values` is ALWAYS an array,
|
|
984
964
|
* even for a single-value operator, so a caller never chains method calls or guesses arity).
|
|
985
965
|
*/
|
|
986
966
|
var databaseToolShape = unionShape(objectShape({
|
|
@@ -994,7 +974,12 @@ var databaseToolShape = unionShape(objectShape({
|
|
|
994
974
|
min: 1,
|
|
995
975
|
description: "The registered driver key. Defaults to \"memory\"."
|
|
996
976
|
})),
|
|
997
|
-
|
|
977
|
+
primary: optionalShape(recordShape(stringShape({ min: 1 }), { description: "Table name to its primary-key column." })),
|
|
978
|
+
indexes: optionalShape(recordShape(arrayShape(arrayShape(stringShape({ min: 1 }), {
|
|
979
|
+
min: 1,
|
|
980
|
+
description: "One nonempty index group of column names."
|
|
981
|
+
}), { description: "Zero or more index groups declared for the table." }), { description: "Table name to its index column groups." })),
|
|
982
|
+
version: optionalShape(numberShape({ description: "The schema version for open-time reconciliation." }))
|
|
998
983
|
}), objectShape({
|
|
999
984
|
operation: literalShape(["tables"], { description: "List a database's table names." }),
|
|
1000
985
|
id: stringShape({
|
|
@@ -1013,7 +998,7 @@ var databaseToolShape = unionShape(objectShape({
|
|
|
1013
998
|
}),
|
|
1014
999
|
key: keyShape
|
|
1015
1000
|
}), objectShape({
|
|
1016
|
-
operation: literalShape(["records"], { description: "List rows matching
|
|
1001
|
+
operation: literalShape(["records"], { description: "List rows matching a query." }),
|
|
1017
1002
|
id: stringShape({
|
|
1018
1003
|
min: 1,
|
|
1019
1004
|
description: "The database id."
|
|
@@ -1022,9 +1007,9 @@ var databaseToolShape = unionShape(objectShape({
|
|
|
1022
1007
|
min: 1,
|
|
1023
1008
|
description: "The table name."
|
|
1024
1009
|
}),
|
|
1025
|
-
|
|
1010
|
+
query: optionalShape(queryShape)
|
|
1026
1011
|
}), objectShape({
|
|
1027
|
-
operation: literalShape(["count"], { description: "Count rows matching
|
|
1012
|
+
operation: literalShape(["count"], { description: "Count rows matching a query." }),
|
|
1028
1013
|
id: stringShape({
|
|
1029
1014
|
min: 1,
|
|
1030
1015
|
description: "The database id."
|
|
@@ -1033,7 +1018,7 @@ var databaseToolShape = unionShape(objectShape({
|
|
|
1033
1018
|
min: 1,
|
|
1034
1019
|
description: "The table name."
|
|
1035
1020
|
}),
|
|
1036
|
-
|
|
1021
|
+
query: optionalShape(queryShape)
|
|
1037
1022
|
}), objectShape({
|
|
1038
1023
|
operation: literalShape(["aggregate"], { description: "Compute an aggregate over a column." }),
|
|
1039
1024
|
id: stringShape({
|
|
@@ -1055,7 +1040,7 @@ var databaseToolShape = unionShape(objectShape({
|
|
|
1055
1040
|
min: 1,
|
|
1056
1041
|
description: "The column to aggregate."
|
|
1057
1042
|
}),
|
|
1058
|
-
|
|
1043
|
+
query: optionalShape(queryShape)
|
|
1059
1044
|
}), objectShape({
|
|
1060
1045
|
operation: literalShape(["add"], { description: "Insert one or more rows (fails on a duplicate key)." }),
|
|
1061
1046
|
id: stringShape({
|
|
@@ -1101,13 +1086,6 @@ var databaseToolShape = unionShape(objectShape({
|
|
|
1101
1086
|
description: "The table name."
|
|
1102
1087
|
}),
|
|
1103
1088
|
key: keyShape
|
|
1104
|
-
}), objectShape({
|
|
1105
|
-
operation: literalShape(["migrate"], { description: "Replace the table layout in place." }),
|
|
1106
|
-
id: stringShape({
|
|
1107
|
-
min: 1,
|
|
1108
|
-
description: "The database id."
|
|
1109
|
-
}),
|
|
1110
|
-
tables: tableSpecShape
|
|
1111
1089
|
}), objectShape({
|
|
1112
1090
|
operation: literalShape(["destroy"], { description: "Drop a database entirely." }),
|
|
1113
1091
|
id: stringShape({
|
|
@@ -1249,7 +1227,7 @@ function workflowTag(id) {
|
|
|
1249
1227
|
* @remarks
|
|
1250
1228
|
* The agent counterpart of {@link workflowTag}: {@link import('./factories.js').createAgentFunction}
|
|
1251
1229
|
* / {@link import('./factories.js').createWorkflowTool} guard against re-entering an agent or
|
|
1252
|
-
* workflow already in the chain (
|
|
1230
|
+
* workflow already in the chain (this package's typed `DEPTH` `ToolboxError`). The
|
|
1253
1231
|
* `agent:` namespace keeps it distinct from a same-string workflow id.
|
|
1254
1232
|
*
|
|
1255
1233
|
* @param name - The agent's identifier / registry name
|
|
@@ -1259,23 +1237,89 @@ function agentTag(name) {
|
|
|
1259
1237
|
return `agent:${name}`;
|
|
1260
1238
|
}
|
|
1261
1239
|
/**
|
|
1240
|
+
* Narrow an unknown value to a valid alternating workflow lineage.
|
|
1241
|
+
*
|
|
1242
|
+
* @param value - The value to inspect
|
|
1243
|
+
* @returns Whether `value` is a unique, nonempty-tagged workflow/agent chain
|
|
1244
|
+
*/
|
|
1245
|
+
function isWorkflowLineage(value) {
|
|
1246
|
+
const inspected = attempt(() => {
|
|
1247
|
+
if (!Array.isArray(value)) return false;
|
|
1248
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1249
|
+
for (const [index, tag] of value.entries()) {
|
|
1250
|
+
if (!isString(tag)) return false;
|
|
1251
|
+
const prefix = index % 2 === 0 ? "workflow:" : "agent:";
|
|
1252
|
+
if (!tag.startsWith(prefix) || tag.length === prefix.length || seen.has(tag)) return false;
|
|
1253
|
+
seen.add(tag);
|
|
1254
|
+
}
|
|
1255
|
+
return true;
|
|
1256
|
+
});
|
|
1257
|
+
return inspected.success && inspected.value;
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Build a validated, copied, and frozen workflow lineage value.
|
|
1261
|
+
*
|
|
1262
|
+
* @param lineage - The configured chain; omitted means a direct root
|
|
1263
|
+
* @returns An immutable caller-isolated lineage
|
|
1264
|
+
*/
|
|
1265
|
+
function lineageOf(lineage = []) {
|
|
1266
|
+
if (!isWorkflowLineage(lineage)) throw new ToolboxError("TOOL", "workflow lineage must alternate unique workflow and agent tags");
|
|
1267
|
+
return Object.freeze([...lineage]);
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Append one tag to a workflow lineage and return a frozen copy.
|
|
1271
|
+
*
|
|
1272
|
+
* @param lineage - The valid chain to extend
|
|
1273
|
+
* @param tag - The workflow or agent tag to append
|
|
1274
|
+
* @returns The validated immutable extension
|
|
1275
|
+
*/
|
|
1276
|
+
function extendLineage(lineage, tag) {
|
|
1277
|
+
return lineageOf([...lineage, tag]);
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Derive the zero-based workflow nesting depth from a valid lineage.
|
|
1281
|
+
*
|
|
1282
|
+
* @param lineage - The workflow/agent chain
|
|
1283
|
+
* @returns Zero for an empty/root lineage, then one per nested workflow
|
|
1284
|
+
*/
|
|
1285
|
+
function deriveWorkflowDepth(lineage) {
|
|
1286
|
+
const workflows = lineageOf(lineage).filter((tag) => tag.startsWith("workflow:")).length;
|
|
1287
|
+
return Math.max(0, workflows - 1);
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Narrow an unknown callable to Toolbox's frozen contextual agent adapter metadata.
|
|
1291
|
+
*
|
|
1292
|
+
* @param value - The value to inspect
|
|
1293
|
+
* @returns Whether it is a frozen {@link AgentFunction} with a frozen valid lineage
|
|
1294
|
+
*/
|
|
1295
|
+
function isAgentFunction(value) {
|
|
1296
|
+
const inspected = attempt(() => {
|
|
1297
|
+
if (typeof value !== "function" || !Object.isFrozen(value)) return false;
|
|
1298
|
+
const category = Reflect.getOwnPropertyDescriptor(value, "category");
|
|
1299
|
+
const lineage = Reflect.getOwnPropertyDescriptor(value, "lineage");
|
|
1300
|
+
return category?.value === "agent" && lineage !== void 0 && "value" in lineage && Object.isFrozen(lineage.value) && isWorkflowLineage(lineage.value);
|
|
1301
|
+
});
|
|
1302
|
+
return inspected.success && inspected.value;
|
|
1303
|
+
}
|
|
1304
|
+
/**
|
|
1262
1305
|
* Build the plain success summary {@link import('./factories.js').createWorkflowTool} returns on
|
|
1263
1306
|
* a completed run — the universal tool-handler contract (AGENTS §14): return a plain value on
|
|
1264
1307
|
* success, appearing identically over BOTH the agent loop and MCP.
|
|
1265
1308
|
*
|
|
1266
1309
|
* @remarks
|
|
1267
|
-
* The summary is LEAN: the workflow's terminal `status
|
|
1268
|
-
*
|
|
1269
|
-
*
|
|
1270
|
-
* (`@orkestrel/tool`) supplies the canonical envelope's identity.)
|
|
1310
|
+
* The summary is LEAN: the workflow's terminal `status`, settled-task count, and exact optional
|
|
1311
|
+
* native persistence outcome. It carries no synthetic `id` / `name`: a tool handler has no call
|
|
1312
|
+
* id; the `ToolManagerInterface` (`@orkestrel/tool`) supplies the canonical envelope's identity.
|
|
1271
1313
|
*
|
|
1272
1314
|
* @param result - The terminal `WorkflowResult` (`@orkestrel/workflow`) the run produced
|
|
1273
|
-
* @returns The plain success summary — `{ status, count }`
|
|
1315
|
+
* @returns The plain success summary — `{ status, count, durable?, fault? }`
|
|
1274
1316
|
*/
|
|
1275
1317
|
function workflowToolSummary(result) {
|
|
1276
1318
|
return {
|
|
1277
1319
|
status: result.status,
|
|
1278
|
-
count: result.results.length
|
|
1320
|
+
count: result.results.length,
|
|
1321
|
+
...result.durable === void 0 ? {} : { durable: result.durable },
|
|
1322
|
+
...result.fault === void 0 ? {} : { fault: result.fault }
|
|
1279
1323
|
};
|
|
1280
1324
|
}
|
|
1281
1325
|
/**
|
|
@@ -1356,16 +1400,20 @@ function completeTaskDraft(task, phaseId, index) {
|
|
|
1356
1400
|
* `name` becomes the task's `run` (the behavior-registry key). Ids/names are auto-filled
|
|
1357
1401
|
* positionally — it builds an ids-omitted {@link WorkflowDraft} and delegates to
|
|
1358
1402
|
* {@link completeDraft}, so the two lenient surfaces share ONE synthesis path (step `i` → phase
|
|
1359
|
-
* `phase-<i>`, its task `phase-<i>-task-0`). The optional `name` becomes the workflow's
|
|
1360
|
-
*
|
|
1361
|
-
*
|
|
1403
|
+
* `phase-<i>`, its task `phase-<i>-task-0`). The optional `name` becomes both the workflow's
|
|
1404
|
+
* deterministic id and its name, so named flat workflows retain distinct persistence keys;
|
|
1405
|
+
* omission keeps the shared draft fallback `wf`. The result is a complete definition the caller
|
|
1406
|
+
* validates against the STRICT contract before running.
|
|
1362
1407
|
*
|
|
1363
1408
|
* @param flat - The flat steps blob (`{ name?, steps: [{ name }] }`)
|
|
1364
1409
|
* @returns A complete {@link WorkflowDefinition} (one one-task phase per step)
|
|
1365
1410
|
*/
|
|
1366
1411
|
function expandSteps(flat) {
|
|
1367
1412
|
return completeDraft({
|
|
1368
|
-
...flat.name === void 0 ? {} : {
|
|
1413
|
+
...flat.name === void 0 ? {} : {
|
|
1414
|
+
id: flat.name,
|
|
1415
|
+
name: flat.name
|
|
1416
|
+
},
|
|
1369
1417
|
phases: flat.steps.map((step) => ({ tasks: [{ run: step.name }] }))
|
|
1370
1418
|
});
|
|
1371
1419
|
}
|
|
@@ -1443,13 +1491,13 @@ function isColumnKind(value) {
|
|
|
1443
1491
|
return value === "string" || value === "integer" || value === "number" || value === "boolean";
|
|
1444
1492
|
}
|
|
1445
1493
|
/**
|
|
1446
|
-
* Compile a {@link TableSpec} into the `@orkestrel/database` {@link
|
|
1494
|
+
* Compile a {@link TableSpec} into the `@orkestrel/database` {@link TableMap} it configures —
|
|
1447
1495
|
* each {@link ColumnSpec} maps to the matching primitive shaper (`'string'` → `stringShape()`,
|
|
1448
1496
|
* `'integer'` → `integerShape()`, `'number'` → `numberShape()`, `'boolean'` → `booleanShape()`),
|
|
1449
1497
|
* wrapped in `optionalShape` when the column declares `optional: true`. Total, pure.
|
|
1450
1498
|
*
|
|
1451
1499
|
* @param spec - The small-model-facing table layout
|
|
1452
|
-
* @returns The compiled `
|
|
1500
|
+
* @returns The compiled `TableMap` a `@orkestrel/database` `createDatabase` call accepts
|
|
1453
1501
|
*/
|
|
1454
1502
|
function expandTables(spec) {
|
|
1455
1503
|
const tables = {};
|
|
@@ -1476,22 +1524,35 @@ function kindShape(kind) {
|
|
|
1476
1524
|
}
|
|
1477
1525
|
/**
|
|
1478
1526
|
* Narrow an unknown value to a {@link DatabaseDefinition} — a non-empty `id` + `driver`, a
|
|
1479
|
-
* `tables` record whose every value is `{ columns: record of valid ColumnSpec }`,
|
|
1480
|
-
* `
|
|
1481
|
-
* applies to an untrusted persisted blob
|
|
1527
|
+
* `tables` record whose every value is `{ columns: record of valid ColumnSpec }`, plus optional
|
|
1528
|
+
* `primary`, `indexes`, and finite `version` schema configuration. The boundary guard a
|
|
1529
|
+
* {@link import('./types.js').DefinitionStoreInterface} applies to an untrusted persisted blob
|
|
1530
|
+
* before trusting it as a definition (never an `as`).
|
|
1482
1531
|
*/
|
|
1483
1532
|
function isDatabaseDefinition(value) {
|
|
1484
1533
|
if (!isRecord(value)) return false;
|
|
1534
|
+
if ("keys" in value) return false;
|
|
1485
1535
|
if (!isNonEmptyString(value.id) || !isNonEmptyString(value.driver)) return false;
|
|
1486
1536
|
if (!isRecord(value.tables)) return false;
|
|
1487
1537
|
for (const table of Object.values(value.tables)) {
|
|
1488
1538
|
if (!isRecord(table) || !isRecord(table.columns)) return false;
|
|
1489
1539
|
for (const column of Object.values(table.columns)) if (!isColumnSpec(column)) return false;
|
|
1490
1540
|
}
|
|
1491
|
-
if (value.
|
|
1492
|
-
if (!isRecord(value.
|
|
1493
|
-
for (const key of Object.values(value.
|
|
1541
|
+
if (value.primary !== void 0) {
|
|
1542
|
+
if (!isRecord(value.primary)) return false;
|
|
1543
|
+
for (const key of Object.values(value.primary)) if (!isNonEmptyString(key)) return false;
|
|
1494
1544
|
}
|
|
1545
|
+
if (value.indexes !== void 0) {
|
|
1546
|
+
if (!isRecord(value.indexes)) return false;
|
|
1547
|
+
for (const groups of Object.values(value.indexes)) {
|
|
1548
|
+
if (!Array.isArray(groups)) return false;
|
|
1549
|
+
for (const group of groups) {
|
|
1550
|
+
if (!Array.isArray(group) || group.length === 0) return false;
|
|
1551
|
+
for (const column of group) if (!isNonEmptyString(column)) return false;
|
|
1552
|
+
}
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1555
|
+
if (value.version !== void 0 && !isFiniteNumber(value.version)) return false;
|
|
1495
1556
|
return true;
|
|
1496
1557
|
}
|
|
1497
1558
|
/**
|
|
@@ -1635,8 +1696,8 @@ function relationModelOf(manager, name) {
|
|
|
1635
1696
|
return manager.model(name);
|
|
1636
1697
|
}
|
|
1637
1698
|
/**
|
|
1638
|
-
* Normalize the database tool's parsed SERIALIZED
|
|
1639
|
-
* {@link
|
|
1699
|
+
* Normalize the database tool's parsed SERIALIZED query into a live `@orkestrel/database`
|
|
1700
|
+
* {@link QueryInput} — default each condition's OMITTED `connector` to `'and'`.
|
|
1640
1701
|
*
|
|
1641
1702
|
* @remarks
|
|
1642
1703
|
* The wire form ({@link import('./shapers.js').databaseToolShape}) lets a caller drop `connector`
|
|
@@ -1644,84 +1705,58 @@ function relationModelOf(manager, name) {
|
|
|
1644
1705
|
* `@orkestrel/database` table call accepts always carries one, so this fills the gap. `order` /
|
|
1645
1706
|
* `limit` / `offset` pass through unchanged. Pure and total.
|
|
1646
1707
|
*
|
|
1647
|
-
* @param
|
|
1648
|
-
* @returns The equivalent live `
|
|
1708
|
+
* @param query - The parsed query (or `undefined`)
|
|
1709
|
+
* @returns The equivalent live `QueryInput`, or `undefined` when `query` is `undefined`
|
|
1649
1710
|
*/
|
|
1650
|
-
function
|
|
1651
|
-
if (
|
|
1652
|
-
const conditions =
|
|
1711
|
+
function queryOf(query) {
|
|
1712
|
+
if (query === void 0) return void 0;
|
|
1713
|
+
const conditions = query.conditions?.map((condition) => ({
|
|
1653
1714
|
...condition,
|
|
1654
1715
|
connector: condition.connector ?? "and"
|
|
1655
1716
|
}));
|
|
1656
1717
|
return {
|
|
1657
1718
|
...conditions === void 0 ? {} : { conditions },
|
|
1658
|
-
...
|
|
1659
|
-
...
|
|
1660
|
-
...
|
|
1719
|
+
...query.order === void 0 ? {} : { order: query.order },
|
|
1720
|
+
...query.limit === void 0 ? {} : { limit: query.limit },
|
|
1721
|
+
...query.offset === void 0 ? {} : { offset: query.offset }
|
|
1661
1722
|
};
|
|
1662
1723
|
}
|
|
1663
1724
|
/**
|
|
1664
|
-
* Clamp a `'records'` call's
|
|
1725
|
+
* Clamp a `'records'` call's query to a row cap, and build the PROBE query the caller reads
|
|
1665
1726
|
* with — the pure leaf {@link import('./factories.js').createDatabaseTool}'s `'records'` operation
|
|
1666
1727
|
* uses to detect truncation without a separate `count` round trip.
|
|
1667
1728
|
*
|
|
1668
1729
|
* @remarks
|
|
1669
|
-
* The effective limit is `min(
|
|
1670
|
-
* exceed the configured cap by supplying a larger `
|
|
1730
|
+
* The effective limit is `min(query?.limit ?? cap, cap)`, floored at `0` (so a caller can never
|
|
1731
|
+
* exceed the configured cap by supplying a larger `query.limit`). The returned probe query
|
|
1671
1732
|
* requests ONE MORE row than the effective limit (`limit: effective + 1`) — if storage returns
|
|
1672
1733
|
* that many, the caller knows the true result was truncated (`rows.length > effective`) and slices
|
|
1673
1734
|
* back down to `effective` before returning.
|
|
1674
1735
|
*
|
|
1675
1736
|
* @example
|
|
1676
1737
|
* ```ts
|
|
1677
|
-
* import {
|
|
1738
|
+
* import { clampQuery } from '@src/core'
|
|
1678
1739
|
*
|
|
1679
|
-
* const {
|
|
1680
|
-
* // limit === 100,
|
|
1681
|
-
* const rows = await table.records(
|
|
1740
|
+
* const { query, limit } = clampQuery(undefined, 100)
|
|
1741
|
+
* // limit === 100, query.limit === 101 — a probe fetching one extra row
|
|
1742
|
+
* const rows = await table.records(query)
|
|
1682
1743
|
* const truncated = rows.length > limit // true when storage had more than `limit` rows
|
|
1683
1744
|
* ```
|
|
1684
1745
|
*
|
|
1685
|
-
* @param
|
|
1746
|
+
* @param query - The live query to clamp (or `undefined`)
|
|
1686
1747
|
* @param cap - The row-count ceiling
|
|
1687
|
-
* @returns The PROBE
|
|
1748
|
+
* @returns The PROBE query (`limit` bumped by one) and the effective `limit`
|
|
1688
1749
|
*/
|
|
1689
|
-
function
|
|
1690
|
-
const limit = Math.max(0, Math.min(
|
|
1750
|
+
function clampQuery(query, cap) {
|
|
1751
|
+
const limit = Math.max(0, Math.min(query?.limit ?? cap, cap));
|
|
1691
1752
|
return {
|
|
1692
|
-
|
|
1693
|
-
...
|
|
1753
|
+
query: {
|
|
1754
|
+
...query,
|
|
1694
1755
|
limit: limit + 1
|
|
1695
1756
|
},
|
|
1696
1757
|
limit
|
|
1697
1758
|
};
|
|
1698
1759
|
}
|
|
1699
|
-
/** Map a column NAME + its live `@orkestrel/database` `ContractShape` to a {@link ColumnSchema} — the leaf {@link tableSchema} maps over. */
|
|
1700
|
-
function columnSchema(name, shape) {
|
|
1701
|
-
return {
|
|
1702
|
-
name,
|
|
1703
|
-
type: shapeToColumnType(shape),
|
|
1704
|
-
nullable: shape.type === "optional" || shape.type === "nullable"
|
|
1705
|
-
};
|
|
1706
|
-
}
|
|
1707
|
-
/**
|
|
1708
|
-
* Build one {@link TableSchema} from a table NAME and its `@orkestrel/database` `TableExport` —
|
|
1709
|
-
* the "deployed" schema shape `DatabaseInterface.migrate` diffs against, derived from a LIVE
|
|
1710
|
-
* handle's `export()` rather than a re-declared {@link TableSpec}, so it works for ANY handle
|
|
1711
|
-
* (config-tracked or caller-supplied).
|
|
1712
|
-
*
|
|
1713
|
-
* @param name - The table name
|
|
1714
|
-
* @param table - The table's `TableExport` (`{ key, columns }`, `@orkestrel/database`)
|
|
1715
|
-
* @returns The equivalent {@link TableSchema} (`indexes` empty — this package declares none)
|
|
1716
|
-
*/
|
|
1717
|
-
function tableSchema(name, table) {
|
|
1718
|
-
return {
|
|
1719
|
-
name,
|
|
1720
|
-
primary: table.key,
|
|
1721
|
-
columns: Object.entries(table.columns).map(([column, shape]) => columnSchema(column, shape)),
|
|
1722
|
-
indexes: []
|
|
1723
|
-
};
|
|
1724
|
-
}
|
|
1725
1760
|
//#endregion
|
|
1726
1761
|
//#region src/core/stores/MemoryDefinitionStore.ts
|
|
1727
1762
|
/**
|
|
@@ -1732,7 +1767,8 @@ function tableSchema(name, table) {
|
|
|
1732
1767
|
*
|
|
1733
1768
|
* @remarks
|
|
1734
1769
|
* A plain `Map<string, DatabaseDefinition>` (AGENTS §21 — the definition is already pure,
|
|
1735
|
-
* self-contained CONFIG-only JSON, so no encoding is needed for the memory tier).
|
|
1770
|
+
* self-contained CONFIG-only JSON, so no encoding is needed for the memory tier). Values are
|
|
1771
|
+
* structured-cloned on both write and read, keeping caller mutation outside the store. There is NO
|
|
1736
1772
|
* idle-TTL and NO eviction: a persisted definition lives until an explicit `delete`. A durable
|
|
1737
1773
|
* backend (JSON / SQLite / IndexedDB) swaps in through the SAME interface without touching a
|
|
1738
1774
|
* consumer — its driver-pluggable twin is
|
|
@@ -1759,10 +1795,11 @@ function tableSchema(name, table) {
|
|
|
1759
1795
|
var MemoryDefinitionStore = class {
|
|
1760
1796
|
#definitions = /* @__PURE__ */ new Map();
|
|
1761
1797
|
get(id) {
|
|
1762
|
-
|
|
1798
|
+
const definition = this.#definitions.get(id);
|
|
1799
|
+
return Promise.resolve(definition === void 0 ? void 0 : structuredClone(definition));
|
|
1763
1800
|
}
|
|
1764
1801
|
set(definition) {
|
|
1765
|
-
this.#definitions.set(definition.id, definition);
|
|
1802
|
+
this.#definitions.set(definition.id, structuredClone(definition));
|
|
1766
1803
|
return Promise.resolve();
|
|
1767
1804
|
}
|
|
1768
1805
|
delete(id) {
|
|
@@ -1851,27 +1888,27 @@ var DatabaseDefinitionStore = class {
|
|
|
1851
1888
|
* ```ts
|
|
1852
1889
|
* import { DatabaseResolver } from '@orkestrel/toolbox'
|
|
1853
1890
|
*
|
|
1854
|
-
* const resolver = new DatabaseResolver(handles, drivers,
|
|
1891
|
+
* const resolver = new DatabaseResolver(handles, drivers, generator, store)
|
|
1855
1892
|
* const database = await resolver.resolve('shop')
|
|
1856
1893
|
* ```
|
|
1857
1894
|
*/
|
|
1858
1895
|
var DatabaseResolver = class {
|
|
1859
1896
|
#handles;
|
|
1860
1897
|
#drivers;
|
|
1861
|
-
#
|
|
1898
|
+
#generator;
|
|
1862
1899
|
#store;
|
|
1863
1900
|
/**
|
|
1864
1901
|
* Create a database resolver over the tool's live state and optional definition store.
|
|
1865
1902
|
*
|
|
1866
1903
|
* @param handles - Initial live database handles cached by id
|
|
1867
1904
|
* @param drivers - Driver factories keyed by definition driver name
|
|
1868
|
-
* @param
|
|
1905
|
+
* @param generator - Optional key generator supplied to newly created databases
|
|
1869
1906
|
* @param store - Optional persistent definition store
|
|
1870
1907
|
*/
|
|
1871
|
-
constructor(handles, drivers,
|
|
1908
|
+
constructor(handles, drivers, generator, store) {
|
|
1872
1909
|
this.#handles = new Map(handles);
|
|
1873
1910
|
this.#drivers = drivers;
|
|
1874
|
-
this.#
|
|
1911
|
+
this.#generator = generator;
|
|
1875
1912
|
this.#store = store;
|
|
1876
1913
|
}
|
|
1877
1914
|
/**
|
|
@@ -1931,8 +1968,11 @@ var DatabaseResolver = class {
|
|
|
1931
1968
|
const handle = createDatabase({
|
|
1932
1969
|
driver: factory(),
|
|
1933
1970
|
tables: expandTables(definition.tables),
|
|
1934
|
-
|
|
1935
|
-
|
|
1971
|
+
name: id,
|
|
1972
|
+
...definition.primary === void 0 ? {} : { primary: definition.primary },
|
|
1973
|
+
...definition.indexes === void 0 ? {} : { indexes: definition.indexes },
|
|
1974
|
+
...definition.version === void 0 ? {} : { version: definition.version },
|
|
1975
|
+
...this.#generator === void 0 ? {} : { generator: this.#generator }
|
|
1936
1976
|
});
|
|
1937
1977
|
this.set(id, handle);
|
|
1938
1978
|
return handle;
|
|
@@ -1948,17 +1988,13 @@ var DatabaseResolver = class {
|
|
|
1948
1988
|
* adapter that lets a `function`-form task run a `@orkestrel/tool` tool BY NAME.
|
|
1949
1989
|
*
|
|
1950
1990
|
* @remarks
|
|
1951
|
-
*
|
|
1952
|
-
* `WorkflowOptions.functions` registry like any other behavior
|
|
1991
|
+
* Composes into a caller's `WorkflowOptions.functions` registry like any other behavior
|
|
1953
1992
|
* (`{ publish: createToolFunction(tools, 'publish') }`); the pure workflow runner has no
|
|
1954
1993
|
* knowledge of tools itself. The returned function executes `name` against `tools` with the
|
|
1955
|
-
* task's `controller.input` as the call arguments
|
|
1956
|
-
* `
|
|
1957
|
-
*
|
|
1958
|
-
*
|
|
1959
|
-
* a programmer error (an explicit binding to a name that doesn't exist) — unlike the engine's
|
|
1960
|
-
* own silent auto-complete of an unresolved task handler, this THROWS a typed `TOOL`
|
|
1961
|
-
* `WorkflowError` (`@orkestrel/workflow`).
|
|
1994
|
+
* task's `controller.input` as the call arguments. The adapter resolves the live tool, calls its
|
|
1995
|
+
* `execute` method directly, and deep-gates the returned unknown through `parseJSONValue`.
|
|
1996
|
+
* Genuine execution throws retain identity. An unregistered name or non-JSON return throws this
|
|
1997
|
+
* package's typed `TOOL` `ToolboxError`.
|
|
1962
1998
|
*
|
|
1963
1999
|
* @param tools - The `ToolManagerInterface` (`@orkestrel/tool`) the named tool is registered on
|
|
1964
2000
|
* @param name - The registered tool's name
|
|
@@ -1977,47 +2013,40 @@ var DatabaseResolver = class {
|
|
|
1977
2013
|
* ```
|
|
1978
2014
|
*/
|
|
1979
2015
|
function createToolFunction(tools, name) {
|
|
2016
|
+
if (name === "workflow") throw new ToolboxError("TOOL", `tool '${WORKFLOW_TOOL_NAME}' is reserved for live workflows`, { tool: name });
|
|
1980
2017
|
return async (controller) => {
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
});
|
|
1987
|
-
if (result.error !== void 0) throw new Error(result.error, { cause: result.error });
|
|
1988
|
-
return result.value;
|
|
2018
|
+
const tool = tools.tool(name);
|
|
2019
|
+
if (tool === void 0) throw new ToolboxError("TOOL", `tool '${name}' is not registered`, { tool: name });
|
|
2020
|
+
const result = parseJSONValue(await tool.execute(controller.input));
|
|
2021
|
+
if (result === void 0) throw new ToolboxError("TOOL", `tool '${name}' returned a non-JSON value`, { tool: name });
|
|
2022
|
+
return result;
|
|
1989
2023
|
};
|
|
1990
2024
|
}
|
|
1991
2025
|
/**
|
|
1992
2026
|
* Wrap a live `AgentInterface` (`@orkestrel/agent`) as a {@link WorkflowFunction}
|
|
1993
|
-
* (`@orkestrel/workflow`) — the
|
|
1994
|
-
*
|
|
2027
|
+
* (`@orkestrel/workflow`) — the opt-in adapter that runs the agent to a settled result and carries
|
|
2028
|
+
* immutable lineage metadata for contextual Toolbox composition.
|
|
1995
2029
|
*
|
|
1996
2030
|
* @remarks
|
|
1997
|
-
*
|
|
1998
|
-
*
|
|
1999
|
-
*
|
|
2000
|
-
*
|
|
2001
|
-
*
|
|
2002
|
-
*
|
|
2003
|
-
*
|
|
2004
|
-
*
|
|
2005
|
-
*
|
|
2006
|
-
*
|
|
2007
|
-
*
|
|
2008
|
-
*
|
|
2009
|
-
*
|
|
2010
|
-
*
|
|
2011
|
-
*
|
|
2012
|
-
* A bound agent is effectively SINGLE-RUN: `context.tools.add` binds one `ToolInterface` under
|
|
2013
|
-
* the fixed {@link import('./constants.js').WORKFLOW_TOOL_NAME}, and `agent.generate()` /
|
|
2014
|
-
* `agent.abort()` are per-agent state. Two CONCURRENT tasks sharing the SAME `agent` instance
|
|
2015
|
-
* race on that one tool binding (last-write-wins) and on generate/abort — give each concurrent
|
|
2016
|
-
* task its OWN agent instance.
|
|
2031
|
+
* Composes into a caller's registry like any other behavior; the pure workflow runner has no
|
|
2032
|
+
* knowledge of agents itself. A root adapter derives its workflow lineage from the controller,
|
|
2033
|
+
* while a contextual adapter requires an exact match. Repeated agents, repeated workflows, and
|
|
2034
|
+
* targets deeper than {@link import('./constants.js').MAX_WORKFLOW_DEPTH} are rejected before
|
|
2035
|
+
* agent or runner activity. When {@link import('./types.js').AgentFunctionOptions.runner} is
|
|
2036
|
+
* supplied, the adapter binds a recursion-safe {@link createWorkflowTool} onto the agent's
|
|
2037
|
+
* `context.tools`, propagating opaque leaves, raw agents, lineage, and the native store. The
|
|
2038
|
+
* task's cancellation propagates through the Agent's native per-run signal seam:
|
|
2039
|
+
* `agent.generate({ signal: controller.signal })` resolves a partial `AgentResult` on a cancel
|
|
2040
|
+
* (never rejects), projected onto its exact JSON fields.
|
|
2041
|
+
*
|
|
2042
|
+
* A bound agent is single-run. A concurrent branch sharing the same real agent is rejected before
|
|
2043
|
+
* its fixed {@link import('./constants.js').WORKFLOW_TOOL_NAME} binding can be replaced. Toolbox
|
|
2044
|
+
* cannot serialize opaque external wrappers that hide or bypass the raw agent registry; hosts
|
|
2045
|
+
* must give such concurrent work distinct agents.
|
|
2017
2046
|
*
|
|
2018
2047
|
* @param agent - The live `AgentInterface` to run
|
|
2019
|
-
* @param options - The nested-workflow
|
|
2020
|
-
* @returns A {@link
|
|
2048
|
+
* @param options - The nested-workflow composition and lineage options
|
|
2049
|
+
* @returns A frozen {@link AgentFunction} that runs `agent` to its settled result
|
|
2021
2050
|
*
|
|
2022
2051
|
* @example
|
|
2023
2052
|
* ```ts
|
|
@@ -2030,21 +2059,32 @@ function createToolFunction(tools, name) {
|
|
|
2030
2059
|
* ```
|
|
2031
2060
|
*/
|
|
2032
2061
|
function createAgentFunction(agent, options) {
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2062
|
+
const lineage = lineageOf(options?.lineage);
|
|
2063
|
+
if (lineage.length > 0 && !lineage.at(-1)?.startsWith("workflow:")) throw new ToolboxError("TOOL", "agent function lineage must end with a workflow tag");
|
|
2064
|
+
const functions = options?.functions === void 0 ? void 0 : Object.freeze({ ...options.functions });
|
|
2065
|
+
const agents = options?.agents === void 0 ? void 0 : Object.freeze({ ...options.agents });
|
|
2066
|
+
return Object.freeze(Object.assign(async (controller) => {
|
|
2067
|
+
const workflow = workflowTag(controller.task.phase.workflow.id);
|
|
2068
|
+
const current = lineage.length === 0 ? extendLineage(lineage, workflow) : lineage;
|
|
2069
|
+
if (current.at(-1) !== workflow) throw new ToolboxError("DEPTH", "agent function workflow does not match its lineage", {
|
|
2070
|
+
workflow,
|
|
2071
|
+
lineage: [...current]
|
|
2072
|
+
});
|
|
2073
|
+
const depth = deriveWorkflowDepth(current);
|
|
2074
|
+
if (depth > 8) throw new ToolboxError("DEPTH", `agent '${agent.id}' exceeds max workflow depth`, {
|
|
2037
2075
|
agent: agent.id,
|
|
2038
2076
|
depth,
|
|
2039
2077
|
max: 8
|
|
2040
2078
|
});
|
|
2041
2079
|
const tag = agentTag(agent.id);
|
|
2042
|
-
if (
|
|
2080
|
+
if (current.includes(tag)) throw new ToolboxError("DEPTH", `agent '${agent.id}' is already an ancestor (cycle)`, {
|
|
2043
2081
|
agent: agent.id,
|
|
2044
|
-
|
|
2082
|
+
lineage: [...current]
|
|
2045
2083
|
});
|
|
2084
|
+
const nested = extendLineage(current, tag);
|
|
2046
2085
|
const runner = options?.runner;
|
|
2047
2086
|
if (runner !== void 0) {
|
|
2087
|
+
if (agent.status === "running") throw new ToolboxError("TOOL", `agent '${agent.id}' is already running`, { agent: agent.id });
|
|
2048
2088
|
const workflowId = controller.task.phase.workflow.id;
|
|
2049
2089
|
const wrapped = {
|
|
2050
2090
|
id: workflowId,
|
|
@@ -2052,50 +2092,77 @@ function createAgentFunction(agent, options) {
|
|
|
2052
2092
|
phases: []
|
|
2053
2093
|
};
|
|
2054
2094
|
agent.context.tools.add(createWorkflowTool(wrapped, runner, {
|
|
2055
|
-
|
|
2056
|
-
|
|
2095
|
+
lineage: nested,
|
|
2096
|
+
...functions === void 0 ? {} : { functions },
|
|
2097
|
+
...agents === void 0 ? {} : { agents },
|
|
2098
|
+
...options?.store === void 0 ? {} : { store: options.store }
|
|
2057
2099
|
}));
|
|
2058
2100
|
}
|
|
2059
|
-
const
|
|
2060
|
-
const
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
} finally {
|
|
2068
|
-
signal.removeEventListener("abort", onAbort);
|
|
2069
|
-
}
|
|
2070
|
-
};
|
|
2101
|
+
const generated = agent.generate({ signal: controller.signal });
|
|
2102
|
+
const result = agentResultToJSON(await generated);
|
|
2103
|
+
if (result === void 0) throw new ToolboxError("TOOL", `agent '${agent.id}' returned a non-JSON result`, { agent: agent.id });
|
|
2104
|
+
return result;
|
|
2105
|
+
}, {
|
|
2106
|
+
category: "agent",
|
|
2107
|
+
lineage
|
|
2108
|
+
}));
|
|
2071
2109
|
}
|
|
2072
2110
|
/**
|
|
2073
|
-
*
|
|
2074
|
-
* (`@orkestrel/workflow`) EXCEPT `id` and `name` are OPTIONAL at all three levels (workflow /
|
|
2075
|
-
* phase / task), so a small model can omit the six identity strings.
|
|
2111
|
+
* Compose opaque host functions and raw agents into one immutable workflow registry.
|
|
2076
2112
|
*
|
|
2077
2113
|
* @remarks
|
|
2078
|
-
*
|
|
2079
|
-
*
|
|
2080
|
-
*
|
|
2081
|
-
*
|
|
2082
|
-
*
|
|
2083
|
-
* `minLength: 1`, so an explicitly-empty `id: ''` is REJECTED (parses to `undefined`), never
|
|
2084
|
-
* auto-filled — keeping "garbage" distinct from "omitted". `run` stays optional (a plain name
|
|
2085
|
-
* string).
|
|
2114
|
+
* This is the target-aware composition boundary used by direct runner consumers and
|
|
2115
|
+
* {@link createWorkflowTool}. Opaque leaves are copied unchanged, marked agent adapters are
|
|
2116
|
+
* rejected from that channel, raw agents are rebound to the supplied lineage, collisions fail
|
|
2117
|
+
* closed, and the returned null-prototype record prevents inherited names from becoming phantom
|
|
2118
|
+
* Workflow handlers.
|
|
2086
2119
|
*
|
|
2087
|
-
* @
|
|
2120
|
+
* @param runner - The native runner propagated into contextual agent adapters
|
|
2121
|
+
* @param options - Target lineage, opaque functions, raw agents, and native store
|
|
2122
|
+
* @returns A frozen null-prototype snapshot of the target workflow's callable registry
|
|
2088
2123
|
*
|
|
2089
2124
|
* @example
|
|
2090
2125
|
* ```ts
|
|
2091
|
-
* import {
|
|
2126
|
+
* import { createWorkflowFunctions } from '@src/core'
|
|
2127
|
+
* import { createWorkflowRunner } from '@orkestrel/workflow'
|
|
2092
2128
|
*
|
|
2093
|
-
* const
|
|
2094
|
-
* const
|
|
2095
|
-
*
|
|
2096
|
-
*
|
|
2129
|
+
* const runner = createWorkflowRunner()
|
|
2130
|
+
* const functions = createWorkflowFunctions(runner, {
|
|
2131
|
+
* functions: { publish: async () => 'published' },
|
|
2132
|
+
* agents: { review: reviewAgent },
|
|
2133
|
+
* })
|
|
2134
|
+
* await runner.execute(definition, { functions })
|
|
2097
2135
|
* ```
|
|
2098
2136
|
*/
|
|
2137
|
+
function createWorkflowFunctions(runner, options) {
|
|
2138
|
+
const lineage = lineageOf(options?.lineage);
|
|
2139
|
+
if (lineage.length > 0 && !lineage.at(-1)?.startsWith("workflow:")) throw new ToolboxError("TOOL", "workflow functions lineage must end with a workflow tag");
|
|
2140
|
+
const functions = options?.functions === void 0 ? Object.freeze({}) : Object.freeze({ ...options.functions });
|
|
2141
|
+
const agents = options?.agents === void 0 ? Object.freeze({}) : Object.freeze({ ...options.agents });
|
|
2142
|
+
const composed = {};
|
|
2143
|
+
Object.setPrototypeOf(composed, null);
|
|
2144
|
+
for (const [name, fn] of Object.entries(functions)) {
|
|
2145
|
+
if (typeof fn !== "function") throw new ToolboxError("TOOL", `workflow function '${name}' is not callable`, { function: name });
|
|
2146
|
+
if (isAgentFunction(fn)) throw new ToolboxError("TOOL", `agent function '${name}' must be supplied through agents`, { function: name });
|
|
2147
|
+
composed[name] = fn;
|
|
2148
|
+
}
|
|
2149
|
+
for (const [name, agent] of Object.entries(agents)) {
|
|
2150
|
+
if (Object.hasOwn(composed, name)) throw new ToolboxError("TOOL", `workflow function '${name}' collides with an agent`, { function: name });
|
|
2151
|
+
composed[name] = createAgentFunction(agent, {
|
|
2152
|
+
runner,
|
|
2153
|
+
lineage,
|
|
2154
|
+
functions,
|
|
2155
|
+
agents,
|
|
2156
|
+
...options?.store === void 0 ? {} : { store: options.store }
|
|
2157
|
+
});
|
|
2158
|
+
}
|
|
2159
|
+
return Object.freeze(composed);
|
|
2160
|
+
}
|
|
2161
|
+
/**
|
|
2162
|
+
* Compile the lenient workflow draft contract used by {@link createWorkflowTool}.
|
|
2163
|
+
*
|
|
2164
|
+
* @returns The compiled {@link import('./types.js').WorkflowDraft} contract
|
|
2165
|
+
*/
|
|
2099
2166
|
function createWorkflowDraftContract() {
|
|
2100
2167
|
return createContract(workflowDraftShape);
|
|
2101
2168
|
}
|
|
@@ -2103,19 +2170,16 @@ function createWorkflowDraftContract() {
|
|
|
2103
2170
|
* Wrap a {@link WorkflowDefinition} as an LLM-callable tool — it ADVERTISES the SIMPLE flat
|
|
2104
2171
|
* authoring shape (`{ name?, steps: [{ name }] }`) as its `parameters` so even a small model can
|
|
2105
2172
|
* author a complete tree, and its handler EXPANDS / COMPLETES the authored blob, validates it
|
|
2106
|
-
* against the STRICT contract, runs it through `runner`,
|
|
2107
|
-
*
|
|
2108
|
-
* workflow's final snapshot after the run settles.
|
|
2173
|
+
* against the STRICT contract, and runs it through `runner`, forwarding the caller's optional
|
|
2174
|
+
* named functions and native checkpoint store.
|
|
2109
2175
|
*
|
|
2110
2176
|
* @remarks
|
|
2111
2177
|
* A plain `ToolManagerInterface`-compatible tool (`@orkestrel/tool`), reproducing
|
|
2112
|
-
*
|
|
2113
|
-
*
|
|
2178
|
+
* Toolbox's flat / draft / full authoring contract, strict soundness gate, and lineage guard.
|
|
2179
|
+
* It is ALSO the propagation carrier
|
|
2114
2180
|
* {@link createAgentFunction} binds onto a wrapped agent's `context.tools`: because a tool
|
|
2115
|
-
* handler receives
|
|
2116
|
-
*
|
|
2117
|
-
* and the handler enforces the SAME depth / cycle guard itself before running the nested
|
|
2118
|
-
* workflow at `depth + 1` with the extended ancestry.
|
|
2181
|
+
* handler receives only the model-supplied `args`, the immutable lineage is closed over at bind
|
|
2182
|
+
* time. The authored target is appended and its zero-based depth derived from workflow tags.
|
|
2119
2183
|
*
|
|
2120
2184
|
* **Widened authoring surface (additive — the canonical contract + runner stay STRICT and
|
|
2121
2185
|
* unchanged).** A 2B model reliably CALLS the tool but cannot reliably emit the full four-level
|
|
@@ -2130,17 +2194,17 @@ function createWorkflowDraftContract() {
|
|
|
2130
2194
|
* super-set.
|
|
2131
2195
|
*
|
|
2132
2196
|
* The universal tool-handler contract (AGENTS §14): returns the plain run summary
|
|
2133
|
-
* (`{ status, count }`) on success, THROWS a typed `
|
|
2134
|
-
*
|
|
2135
|
-
* (`DEPTH`). The `ToolManagerInterface` isolates every throw into the canonical tool result's
|
|
2136
|
-
* top-level `error`, so nothing escapes the run. `options.
|
|
2137
|
-
*
|
|
2138
|
-
*
|
|
2139
|
-
*
|
|
2197
|
+
* (`{ status, count, durable?, fault? }`) on success, THROWS a typed `ToolboxError` on Toolbox
|
|
2198
|
+
* boundary failures — malformed authored args (`TOOL`), or an over-deep / cyclic nested run
|
|
2199
|
+
* (`DEPTH`) — and preserves genuine runner `WorkflowError`s. The `ToolManagerInterface` isolates every throw into the canonical tool result's
|
|
2200
|
+
* top-level `error`, so nothing escapes the run. `options.lineage` is the propagation carrier
|
|
2201
|
+
* across a workflow → agent → workflow chain. Opaque `functions` remain host leaves; raw `agents`
|
|
2202
|
+
* are contextually adapted through {@link createWorkflowFunctions}; native checkpoint persistence
|
|
2203
|
+
* makes the final snapshot retrievable without a second Toolbox write.
|
|
2140
2204
|
*
|
|
2141
2205
|
* @param definition - The workflow the tool runs when called with no authored args
|
|
2142
2206
|
* @param runner - The `WorkflowRunnerInterface` (`@orkestrel/workflow`) that executes the (nested) workflow
|
|
2143
|
-
* @param options -
|
|
2207
|
+
* @param options - Lineage-aware functions, agents, and optional native store
|
|
2144
2208
|
* @returns A `ToolInterface` (named {@link import('./constants.js').WORKFLOW_TOOL_NAME}) whose
|
|
2145
2209
|
* `parameters` advertise the flat authoring schema
|
|
2146
2210
|
*
|
|
@@ -2154,15 +2218,17 @@ function createWorkflowDraftContract() {
|
|
|
2154
2218
|
* const store = createMemoryWorkflowStore()
|
|
2155
2219
|
* const tool = createWorkflowTool(definition, runner, { store })
|
|
2156
2220
|
* const tools = createToolManager()
|
|
2157
|
-
* tools.add(tool) // authored runs
|
|
2221
|
+
* tools.add(tool) // authored runs now use native checkpoints through `store`
|
|
2158
2222
|
* ```
|
|
2159
2223
|
*/
|
|
2160
2224
|
function createWorkflowTool(definition, runner, options) {
|
|
2161
2225
|
const strict = createWorkflowContract();
|
|
2162
2226
|
const draft = createWorkflowDraftContract();
|
|
2163
2227
|
const steps = createContract(workflowStepsShape);
|
|
2164
|
-
const
|
|
2165
|
-
|
|
2228
|
+
const lineage = lineageOf(options?.lineage);
|
|
2229
|
+
if (lineage.length > 0 && !lineage.at(-1)?.startsWith("agent:")) throw new ToolboxError("TOOL", "workflow tool lineage must end with an agent tag");
|
|
2230
|
+
const functions = options?.functions === void 0 ? void 0 : Object.freeze({ ...options.functions });
|
|
2231
|
+
const agents = options?.agents === void 0 ? void 0 : Object.freeze({ ...options.agents });
|
|
2166
2232
|
const store = options?.store;
|
|
2167
2233
|
const parameters = schemaToParameters(steps.schema);
|
|
2168
2234
|
return createTool({
|
|
@@ -2171,29 +2237,41 @@ function createWorkflowTool(definition, runner, options) {
|
|
|
2171
2237
|
summary: WORKFLOW_TOOL_SUMMARY,
|
|
2172
2238
|
...parameters === void 0 ? {} : { parameters },
|
|
2173
2239
|
async execute(args) {
|
|
2240
|
+
const cloned = attempt(() => cloneJSONRecord(args));
|
|
2241
|
+
if (!cloned.success) throw new ToolboxError("TOOL", "malformed workflow definition", { workflow: definition.id });
|
|
2242
|
+
const owned = cloned.value;
|
|
2174
2243
|
let target;
|
|
2175
|
-
if (Object.keys(
|
|
2176
|
-
else if (Array.isArray(
|
|
2177
|
-
const flat = steps.parse(
|
|
2244
|
+
if (Object.keys(owned).length === 0) target = definition;
|
|
2245
|
+
else if (Array.isArray(owned.steps)) {
|
|
2246
|
+
const flat = steps.parse(owned);
|
|
2178
2247
|
target = flat === void 0 ? void 0 : expandSteps(flat);
|
|
2179
2248
|
} else {
|
|
2180
|
-
const parsed = draft.parse(
|
|
2249
|
+
const parsed = draft.parse(owned);
|
|
2181
2250
|
target = parsed === void 0 ? void 0 : completeDraft(parsed);
|
|
2182
2251
|
}
|
|
2183
|
-
if (target === void 0 || !strict.is(target)) throw new
|
|
2184
|
-
|
|
2252
|
+
if (target === void 0 || !strict.is(target)) throw new ToolboxError("TOOL", "malformed workflow definition", { workflow: definition.id });
|
|
2253
|
+
const tag = workflowTag(target.id);
|
|
2254
|
+
if (lineage.includes(tag)) throw new ToolboxError("DEPTH", `workflow '${target.id}' is already an ancestor (cycle)`, {
|
|
2255
|
+
workflow: target.id,
|
|
2256
|
+
lineage: [...lineage]
|
|
2257
|
+
});
|
|
2258
|
+
const targetLineage = extendLineage(lineage, tag);
|
|
2259
|
+
const depth = deriveWorkflowDepth(targetLineage);
|
|
2260
|
+
if (depth > 8) throw new ToolboxError("DEPTH", `nested workflow exceeds max depth 8`, {
|
|
2185
2261
|
workflow: target.id,
|
|
2186
2262
|
depth,
|
|
2187
2263
|
max: 8
|
|
2188
2264
|
});
|
|
2189
|
-
const
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2265
|
+
const registry = functions === void 0 && agents === void 0 ? void 0 : createWorkflowFunctions(runner, {
|
|
2266
|
+
lineage: targetLineage,
|
|
2267
|
+
...functions === void 0 ? {} : { functions },
|
|
2268
|
+
...agents === void 0 ? {} : { agents },
|
|
2269
|
+
...store === void 0 ? {} : { store }
|
|
2193
2270
|
});
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2271
|
+
return workflowToolSummary(await runner.execute(target, {
|
|
2272
|
+
...registry === void 0 ? {} : { functions: registry },
|
|
2273
|
+
...store === void 0 ? {} : { store }
|
|
2274
|
+
}));
|
|
2197
2275
|
}
|
|
2198
2276
|
});
|
|
2199
2277
|
}
|
|
@@ -2202,12 +2280,13 @@ function createWorkflowTool(definition, runner, options) {
|
|
|
2202
2280
|
* 13-op union ({@link import('./shapers.js').workspaceToolShape}) as its `parameters`, and its
|
|
2203
2281
|
* handler PARSES the model-supplied args against that contract and DISPATCHES the matched
|
|
2204
2282
|
* operation against the manager's ACTIVE workspace (the registry ops drive the manager itself),
|
|
2205
|
-
* returning the plain result
|
|
2206
|
-
*
|
|
2207
|
-
*
|
|
2208
|
-
*
|
|
2209
|
-
*
|
|
2210
|
-
*
|
|
2283
|
+
* returning the plain result. A malformed operation throws this package's `ToolboxError`; a
|
|
2284
|
+
* genuine workspace-domain failure propagates `@orkestrel/workspace`'s typed `WorkspaceError`.
|
|
2285
|
+
* EITHER drives a caller-supplied {@link WorkspaceToolOptions.manager} directly, OR
|
|
2286
|
+
* constructs a fresh `WorkspaceManagerInterface` (`@orkestrel/workspace`) over
|
|
2287
|
+
* {@link import('./types.js').WorkspaceToolOptions.store} (via `@orkestrel/workspace`'s
|
|
2288
|
+
* `createWorkspaceManager`); neither given constructs a manager backed by
|
|
2289
|
+
* `@orkestrel/workspace`'s in-memory store default.
|
|
2211
2290
|
*
|
|
2212
2291
|
* @remarks
|
|
2213
2292
|
* MANAGER-DRIVEN: every edit / read op (read / list / has / search / replace / write / splice /
|
|
@@ -2225,12 +2304,12 @@ function createWorkflowTool(definition, runner, options) {
|
|
|
2225
2304
|
* `false`), never creating one and never throwing.
|
|
2226
2305
|
*
|
|
2227
2306
|
* The handler conforms to the universal tool-handler contract (AGENTS §14): it `contract.parse`s
|
|
2228
|
-
* the args, THROWS a `TOOL` `
|
|
2307
|
+
* the args, THROWS a `TOOL` `ToolboxError` when no operation arm matched (a malformed / unknown
|
|
2229
2308
|
* operation), else `switch`es on `op.operation` and RETURNS the plain result — letting a
|
|
2230
2309
|
* `WorkspaceError` raised by the live workspace (`MODALITY` / `PATTERN` / `RANGE`) PROPAGATE
|
|
2231
2310
|
* uncaught. The range edit is the FLAT `'splice'` op: its four flat caret integers are
|
|
2232
|
-
* reassembled into a `Range` (`@orkestrel/
|
|
2233
|
-
* `write`.
|
|
2311
|
+
* reassembled into a `Range` (`@orkestrel/workspace`) by `rangeOf` and fed to the workspace's
|
|
2312
|
+
* ranged `write`.
|
|
2234
2313
|
*
|
|
2235
2314
|
* @param options - `manager` (drive directly) OR `store` (build a manager over it); neither ⇒
|
|
2236
2315
|
* an in-memory-backed manager (see {@link import('./types.js').WorkspaceToolOptions})
|
|
@@ -2257,7 +2336,7 @@ function createWorkspaceTool(options) {
|
|
|
2257
2336
|
...parameters === void 0 ? {} : { parameters },
|
|
2258
2337
|
execute(args) {
|
|
2259
2338
|
const op = contract.parse(args);
|
|
2260
|
-
if (op === void 0) throw new
|
|
2339
|
+
if (op === void 0) throw new ToolboxError("TOOL", "unknown or malformed operation", { args });
|
|
2261
2340
|
if (op.operation === "workspaces") {
|
|
2262
2341
|
const activeId = manager.active?.id;
|
|
2263
2342
|
return manager.workspaces().map((workspace) => ({
|
|
@@ -2290,12 +2369,12 @@ function createWorkspaceTool(options) {
|
|
|
2290
2369
|
case "has": return active?.has(op.path) ?? false;
|
|
2291
2370
|
case "search": return active?.search(op.query, {
|
|
2292
2371
|
...op.regex === void 0 ? {} : { regex: op.regex },
|
|
2293
|
-
...op.
|
|
2372
|
+
...op.sensitive === void 0 ? {} : { sensitive: op.sensitive },
|
|
2294
2373
|
...op.limit === void 0 ? {} : { limit: op.limit }
|
|
2295
2374
|
}) ?? [];
|
|
2296
2375
|
case "replace": return (active ?? manager.add()).replace(op.query, op.replacement, {
|
|
2297
2376
|
...op.regex === void 0 ? {} : { regex: op.regex },
|
|
2298
|
-
...op.
|
|
2377
|
+
...op.sensitive === void 0 ? {} : { sensitive: op.sensitive },
|
|
2299
2378
|
...op.limit === void 0 ? {} : { limit: op.limit }
|
|
2300
2379
|
});
|
|
2301
2380
|
case "write": {
|
|
@@ -2712,20 +2791,18 @@ function createDatabaseDefinitionStore(driver = createMemoryDriver()) {
|
|
|
2712
2791
|
* `TOOL` {@link import('./errors.js').ToolboxError}). When a `store` is configured, `'create'`
|
|
2713
2792
|
* persists the new {@link import('./types.js').DatabaseDefinition} and `'destroy'` deletes it.
|
|
2714
2793
|
*
|
|
2715
|
-
* `'
|
|
2716
|
-
* and storage, a NEW typed view) and calls its `migrate` against the OLD deployed schema —
|
|
2717
|
-
* derived from the handle's OWN `export()` (via {@link import('./helpers.js').tableSchema}), so it
|
|
2718
|
-
* works for any handle, config-tracked or caller-supplied via
|
|
2719
|
-
* {@link import('./types.js').DatabaseToolOptions.databases}. `'records'` clamps its `criteria` to
|
|
2794
|
+
* `'records'` clamps its `query` to
|
|
2720
2795
|
* {@link import('./types.js').DatabaseToolOptions.limit} (default
|
|
2721
2796
|
* {@link import('./constants.js').DATABASE_TOOL_LIMIT}) via
|
|
2722
|
-
* {@link import('./helpers.js').
|
|
2723
|
-
* than the cap. Every operation's `
|
|
2724
|
-
* {@link import('./helpers.js').
|
|
2797
|
+
* {@link import('./helpers.js').clampQuery}, reporting `truncated` when storage held more rows
|
|
2798
|
+
* than the cap. Every operation's `query` is normalized via
|
|
2799
|
+
* {@link import('./helpers.js').queryOf} (defaults an omitted condition `connector` to `'and'`).
|
|
2725
2800
|
* When {@link import('./types.js').DatabaseToolOptions.readonly} is `true`, every mutating
|
|
2726
|
-
* operation throws a typed `TOOL` `ToolboxError` before doing anything.
|
|
2727
|
-
* {@link import('./types.js').DatabaseToolOptions.timeout} is
|
|
2728
|
-
*
|
|
2801
|
+
* operation throws a typed `TOOL` `ToolboxError` before doing anything. A configured
|
|
2802
|
+
* {@link import('./types.js').DatabaseToolOptions.timeout} is passed as a fresh
|
|
2803
|
+
* `AbortSignal.timeout(timeout)` only to table operations whose current database API accepts
|
|
2804
|
+
* operation options (`records`, `count`, `aggregate`, `add`, `set`, `update`, and `remove`). It is
|
|
2805
|
+
* not an outer deadline for resolution, construction, schema inspection, `get`, or `close`. A typed `@orkestrel/database`
|
|
2729
2806
|
* failure (`DatabaseError`) re-surfaces as a typed `DATABASE` `ToolboxError` carrying the
|
|
2730
2807
|
* original {@link import('@orkestrel/database').DatabaseErrorCode} in `context.code`
|
|
2731
2808
|
* ({@link import('./helpers.js').databaseToolCode}); an `ToolboxError` thrown by this tool's own
|
|
@@ -2759,15 +2836,15 @@ function createDatabaseDefinitionStore(driver = createMemoryDriver()) {
|
|
|
2759
2836
|
* ```
|
|
2760
2837
|
*/
|
|
2761
2838
|
function createDatabaseTool(options = {}) {
|
|
2839
|
+
if (options.timeout !== void 0 && (options.timeout < 0 || !Number.isSafeInteger(options.timeout))) throw new ToolboxError("TOOL", "database timeout must be a nonnegative safe integer", { timeout: options.timeout });
|
|
2762
2840
|
const contract = createContract(databaseToolShape);
|
|
2763
2841
|
const parameters = schemaToParameters(contract.schema);
|
|
2764
2842
|
const handles = new Map(Object.entries(options.databases ?? {}));
|
|
2765
|
-
const definitions = /* @__PURE__ */ new Map();
|
|
2766
2843
|
const drivers = options.drivers ?? { memory: createMemoryDriver };
|
|
2767
|
-
const
|
|
2844
|
+
const generator = options.generator;
|
|
2768
2845
|
const cap = options.limit ?? 1e3;
|
|
2769
2846
|
const store = options.store;
|
|
2770
|
-
const resolver = store === void 0 ? new DatabaseResolver(handles, drivers,
|
|
2847
|
+
const resolver = store === void 0 ? new DatabaseResolver(handles, drivers, generator) : new DatabaseResolver(handles, drivers, generator, store);
|
|
2771
2848
|
return createTool({
|
|
2772
2849
|
name: options.name ?? "database",
|
|
2773
2850
|
description: options.description ?? DATABASE_TOOL_DESCRIPTION,
|
|
@@ -2776,7 +2853,7 @@ function createDatabaseTool(options = {}) {
|
|
|
2776
2853
|
async execute(args) {
|
|
2777
2854
|
const call = contract.parse(args);
|
|
2778
2855
|
if (call === void 0) throw new ToolboxError("TOOL", "malformed database call", { args });
|
|
2779
|
-
if (options.readonly === true && DATABASE_TOOL_MUTATIONS.
|
|
2856
|
+
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 });
|
|
2780
2857
|
const read = options.timeout === void 0 ? void 0 : { signal: AbortSignal.timeout(options.timeout) };
|
|
2781
2858
|
try {
|
|
2782
2859
|
switch (call.operation) {
|
|
@@ -2789,22 +2866,28 @@ function createDatabaseTool(options = {}) {
|
|
|
2789
2866
|
driver: name
|
|
2790
2867
|
});
|
|
2791
2868
|
const tables = call.tables;
|
|
2792
|
-
const
|
|
2869
|
+
const primary = call.primary;
|
|
2870
|
+
const indexes = call.indexes;
|
|
2871
|
+
const version = call.version;
|
|
2793
2872
|
const handle = createDatabase({
|
|
2794
2873
|
driver: factory(),
|
|
2795
2874
|
tables: expandTables(tables),
|
|
2796
|
-
|
|
2797
|
-
|
|
2875
|
+
name: call.id,
|
|
2876
|
+
...primary === void 0 ? {} : { primary },
|
|
2877
|
+
...indexes === void 0 ? {} : { indexes },
|
|
2878
|
+
...version === void 0 ? {} : { version },
|
|
2879
|
+
...generator === void 0 ? {} : { generator }
|
|
2798
2880
|
});
|
|
2799
|
-
resolver.set(call.id, handle);
|
|
2800
2881
|
const definition = {
|
|
2801
2882
|
id: call.id,
|
|
2802
2883
|
driver: name,
|
|
2803
2884
|
tables,
|
|
2804
|
-
...
|
|
2885
|
+
...primary === void 0 ? {} : { primary },
|
|
2886
|
+
...indexes === void 0 ? {} : { indexes },
|
|
2887
|
+
...version === void 0 ? {} : { version }
|
|
2805
2888
|
};
|
|
2806
|
-
definitions.set(call.id, definition);
|
|
2807
2889
|
if (store !== void 0) await store.set(definition);
|
|
2890
|
+
resolver.set(call.id, handle);
|
|
2808
2891
|
return {
|
|
2809
2892
|
id: call.id,
|
|
2810
2893
|
tables: Object.keys(tables)
|
|
@@ -2830,7 +2913,7 @@ function createDatabaseTool(options = {}) {
|
|
|
2830
2913
|
}
|
|
2831
2914
|
case "records": {
|
|
2832
2915
|
const table = (await resolver.resolve(call.id)).table(call.table);
|
|
2833
|
-
const {
|
|
2916
|
+
const { query: probe, limit } = clampQuery(queryOf(call.query), cap);
|
|
2834
2917
|
const rows = await table.records(probe, read);
|
|
2835
2918
|
const truncated = rows.length > limit;
|
|
2836
2919
|
const sliced = rows.slice(0, limit);
|
|
@@ -2841,8 +2924,8 @@ function createDatabaseTool(options = {}) {
|
|
|
2841
2924
|
limit
|
|
2842
2925
|
};
|
|
2843
2926
|
}
|
|
2844
|
-
case "count": return { count: await (await resolver.resolve(call.id)).table(call.table).count(
|
|
2845
|
-
case "aggregate": return { value: await (await resolver.resolve(call.id)).table(call.table).aggregate(call.function, call.column,
|
|
2927
|
+
case "count": return { count: await (await resolver.resolve(call.id)).table(call.table).count(queryOf(call.query), read) };
|
|
2928
|
+
case "aggregate": return { value: await (await resolver.resolve(call.id)).table(call.table).aggregate(call.function, call.column, queryOf(call.query), read) };
|
|
2846
2929
|
case "add": {
|
|
2847
2930
|
const table = (await resolver.resolve(call.id)).table(call.table);
|
|
2848
2931
|
const many = Array.isArray(call.row);
|
|
@@ -2872,33 +2955,6 @@ function createDatabaseTool(options = {}) {
|
|
|
2872
2955
|
const removed = await table.remove(keys, read);
|
|
2873
2956
|
return many ? { removed } : { removed: removed[0] };
|
|
2874
2957
|
}
|
|
2875
|
-
case "migrate": {
|
|
2876
|
-
const handle = await resolver.resolve(call.id);
|
|
2877
|
-
const previous = handle.export();
|
|
2878
|
-
const deployed = Object.entries(previous).map(([name, table]) => tableSchema(name, table));
|
|
2879
|
-
const tables = call.tables;
|
|
2880
|
-
const keys = {};
|
|
2881
|
-
for (const name of Object.keys(tables)) {
|
|
2882
|
-
const existing = previous[name];
|
|
2883
|
-
if (existing !== void 0) keys[name] = existing.key;
|
|
2884
|
-
}
|
|
2885
|
-
const declared = expandTables(tables);
|
|
2886
|
-
const migrated = handle.import(declared, Object.keys(keys).length > 0 ? keys : void 0);
|
|
2887
|
-
const migration = await migrated.migrate(deployed, read);
|
|
2888
|
-
resolver.set(call.id, migrated);
|
|
2889
|
-
const tracked = definitions.get(call.id) ?? (store === void 0 ? void 0 : await store.get(call.id));
|
|
2890
|
-
if (tracked !== void 0) {
|
|
2891
|
-
const updated = {
|
|
2892
|
-
id: call.id,
|
|
2893
|
-
driver: tracked.driver,
|
|
2894
|
-
tables,
|
|
2895
|
-
...Object.keys(keys).length > 0 ? { keys } : {}
|
|
2896
|
-
};
|
|
2897
|
-
definitions.set(call.id, updated);
|
|
2898
|
-
if (store !== void 0) await store.set(updated);
|
|
2899
|
-
}
|
|
2900
|
-
return { migration };
|
|
2901
|
-
}
|
|
2902
2958
|
case "destroy": {
|
|
2903
2959
|
const cached = resolver.get(call.id);
|
|
2904
2960
|
const persisted = store !== void 0 && cached === void 0 ? await store.get(call.id) !== void 0 : false;
|
|
@@ -2906,7 +2962,6 @@ function createDatabaseTool(options = {}) {
|
|
|
2906
2962
|
await cached.close();
|
|
2907
2963
|
resolver.delete(call.id);
|
|
2908
2964
|
}
|
|
2909
|
-
definitions.delete(call.id);
|
|
2910
2965
|
if (store !== void 0) await store.delete(call.id);
|
|
2911
2966
|
return {
|
|
2912
2967
|
id: call.id,
|
|
@@ -2952,7 +3007,7 @@ function createDatabaseTool(options = {}) {
|
|
|
2952
3007
|
* (positional many-key form, AGENTS §9.2) or a single key. `'find'` and `'links'` clamp their
|
|
2953
3008
|
* result to {@link import('./types.js').RelationToolOptions.limit} (default
|
|
2954
3009
|
* {@link import('./constants.js').RELATION_TOOL_LIMIT}) — `'find'` probes one row past the
|
|
2955
|
-
* effective limit (mirroring {@link import('./helpers.js').
|
|
3010
|
+
* effective limit (mirroring {@link import('./helpers.js').clampQuery}'s idiom) to report
|
|
2956
3011
|
* `truncated`; `'links'` (which has no upstream pagination) fetches the FULL linked-key list and
|
|
2957
3012
|
* slices/truncates it the same way. `'link'` / `'unlink'` write / remove one `through` junction
|
|
2958
3013
|
* row.
|
|
@@ -3264,6 +3319,6 @@ function createEndpointTool(definition, options) {
|
|
|
3264
3319
|
});
|
|
3265
3320
|
}
|
|
3266
3321
|
//#endregion
|
|
3267
|
-
export { AGENT_TOOL_DEPTH, AGENT_TOOL_DESCRIPTION, AGENT_TOOL_NAME, AGENT_TOOL_SUMMARY, ANSWER_TOOL_DESCRIPTION, ANSWER_TOOL_NAME, ANSWER_TOOL_SUMMARY, DATABASE_TOOL_DESCRIPTION, DATABASE_TOOL_LIMIT, DATABASE_TOOL_MUTATIONS, DATABASE_TOOL_NAME, DATABASE_TOOL_SUMMARY, DESCRIBE_TOOL_DESCRIPTION, DESCRIBE_TOOL_NAME, DESCRIBE_TOOL_SUMMARY, DatabaseDefinitionStore, DatabaseResolver, INFER_TOOL_DESCRIPTION, INFER_TOOL_NAME, INFER_TOOL_SUMMARY, MAX_WORKFLOW_DEPTH, MemoryDefinitionStore, PROMPT_TOOL_DESCRIPTION, PROMPT_TOOL_NAME, PROMPT_TOOL_SUMMARY, RELATION_TOOL_DEPTH, RELATION_TOOL_DESCRIPTION, RELATION_TOOL_LIMIT, RELATION_TOOL_NAME, RELATION_TOOL_SUMMARY, ToolboxError, WORKFLOW_TOOL_DESCRIPTION, WORKFLOW_TOOL_FLAT_EXAMPLE, WORKFLOW_TOOL_NAME, WORKFLOW_TOOL_NESTED_EXAMPLE, WORKFLOW_TOOL_SUMMARY, WORKSPACE_TOOL_DESCRIPTION, WORKSPACE_TOOL_EXAMPLE, WORKSPACE_TOOL_NAME, WORKSPACE_TOOL_SUMMARY, agentTag, agentToolShape, answerToolShape,
|
|
3322
|
+
export { AGENT_TOOL_DEPTH, AGENT_TOOL_DESCRIPTION, AGENT_TOOL_NAME, AGENT_TOOL_SUMMARY, ANSWER_TOOL_DESCRIPTION, ANSWER_TOOL_NAME, ANSWER_TOOL_SUMMARY, DATABASE_TOOL_DESCRIPTION, DATABASE_TOOL_LIMIT, DATABASE_TOOL_MUTATIONS, DATABASE_TOOL_NAME, DATABASE_TOOL_SUMMARY, DESCRIBE_TOOL_DESCRIPTION, DESCRIBE_TOOL_NAME, DESCRIBE_TOOL_SUMMARY, DatabaseDefinitionStore, DatabaseResolver, INFER_TOOL_DESCRIPTION, INFER_TOOL_NAME, INFER_TOOL_SUMMARY, MAX_WORKFLOW_DEPTH, MemoryDefinitionStore, PROMPT_TOOL_DESCRIPTION, PROMPT_TOOL_NAME, PROMPT_TOOL_SUMMARY, RELATION_TOOL_DEPTH, RELATION_TOOL_DESCRIPTION, RELATION_TOOL_LIMIT, RELATION_TOOL_NAME, RELATION_TOOL_SUMMARY, ToolboxError, WORKFLOW_TOOL_DESCRIPTION, WORKFLOW_TOOL_FLAT_EXAMPLE, WORKFLOW_TOOL_NAME, WORKFLOW_TOOL_NESTED_EXAMPLE, WORKFLOW_TOOL_SUMMARY, WORKSPACE_TOOL_DESCRIPTION, WORKSPACE_TOOL_EXAMPLE, WORKSPACE_TOOL_NAME, WORKSPACE_TOOL_SUMMARY, agentTag, agentToolShape, answerToolShape, clampQuery, coerceAnswer, columnKindShape, columnShape, columnSpecShape, completeDraft, completePhaseDraft, completeTaskDraft, conditionShape, createAgentFunction, createAgentTool, createAnswerTool, createDatabaseDefinitionStore, createDatabaseTool, createDescribeTool, createEndpointTool, createInferTool, createMemoryDefinitionStore, createPromptTool, createRelationTool, createToolFunction, createWorkflowDraftContract, createWorkflowFunctions, createWorkflowTool, createWorkspaceTool, databaseToolCode, databaseToolShape, deriveWorkflowDepth, describeToolShape, expandInclude, expandSteps, expandTables, extendLineage, includeShape, inferToolShape, isAgentFunction, isColumnKind, isColumnSpec, isDatabaseDefinition, isToolboxError, isWorkflowLineage, keyShape, kindShape, lineageOf, managerShape, orderShape, phaseDraftShape, promptToolShape, queryOf, queryShape, relationKeyShape, relationManagerOf, relationModelOf, relationToolCode, relationToolShape, rowShape, rowsShape, singleKeyShape, stepShape, tableSpecShape, taskDraftShape, terminalToolCode, workflowDraftShape, workflowStepsShape, workflowTag, workflowToolSummary, workspaceToolShape };
|
|
3268
3323
|
|
|
3269
3324
|
//# sourceMappingURL=index.js.map
|