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