@orkestrel/agent 0.0.20 → 0.0.21
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 +10 -10
- package/dist/src/core/index.cjs +414 -329
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +907 -724
- package/dist/src/core/index.d.ts +907 -724
- package/dist/src/core/index.js +414 -329
- package/dist/src/core/index.js.map +1 -1
- package/package.json +21 -22
package/dist/src/core/index.cjs
CHANGED
|
@@ -11,37 +11,37 @@ let _orkestrel_workspace = require("@orkestrel/workspace");
|
|
|
11
11
|
let _orkestrel_budget = require("@orkestrel/budget");
|
|
12
12
|
//#region src/core/constants.ts
|
|
13
13
|
/**
|
|
14
|
-
* Caps an {@link AgentInterface} turn's tool iterations by default —
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* `AgentOptions.limit`.
|
|
14
|
+
* Caps an {@link AgentInterface} turn's tool iterations by default — `10` context → provider →
|
|
15
|
+
* tools cycles before the loop stops, so a model that keeps requesting tools can never loop
|
|
16
|
+
* forever. Overridable per agent through `AgentOptions.limit`.
|
|
18
17
|
*/
|
|
19
18
|
var DEFAULT_AGENT_LIMIT = 10;
|
|
20
19
|
/**
|
|
21
20
|
* Names the zone an {@link AuthorityInterface}'s default fallback {@link AuthorityDecision}
|
|
22
|
-
* carries — the classification for a tool call that matched no rule. Paired with
|
|
23
|
-
* default `allowed: true` fallback, an unmatched call is allowed under this zone, so a
|
|
24
|
-
* rules list of denials acts as a denylist; a caller wanting deny-by-default supplies
|
|
25
|
-
*
|
|
21
|
+
* carries — `'default'`, the classification for a tool call that matched no rule. Paired with
|
|
22
|
+
* the default `allowed: true` fallback, an unmatched call is allowed under this zone, so a
|
|
23
|
+
* rules list of denials acts as a denylist; a caller wanting deny-by-default supplies an
|
|
24
|
+
* `allowed: false` `fallback` of their own (see `AuthorityOptions`).
|
|
26
25
|
*/
|
|
27
26
|
var DEFAULT_AUTHORITY_ZONE = "default";
|
|
28
27
|
/**
|
|
29
28
|
* Sets the default number of recent live messages a {@link ConversationInterface}'s `compact()`
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
29
|
+
* retains verbatim — `0`, so a manual `compact()` folds every current live message into one
|
|
30
|
+
* summarized section and keeps no tail. A caller retains a recent tail by passing `keep` (on
|
|
31
|
+
* {@link ConversationOptions}, {@link ConversationManagerOptions}, or per-fold through
|
|
32
|
+
* {@link CompactOptions}), folding only the older `count - keep` messages and leaving the most
|
|
33
|
+
* recent `keep` live for the next turn. Overridable everywhere `keep` is accepted.
|
|
35
34
|
*/
|
|
36
35
|
var DEFAULT_CONVERSATION_KEEP = 0;
|
|
37
36
|
/**
|
|
38
37
|
* Names the framing label a {@link ConversationInterface}'s `view()` prefixes onto each compacted
|
|
39
|
-
* section's summary so a small model reads it as a
|
|
40
|
-
* literal assistant turn to echo or treat as
|
|
38
|
+
* section's summary so a small model reads it as a condensed recap of earlier turns — the lean
|
|
39
|
+
* `'[Summary of earlier messages] '` marker, never a literal assistant turn to echo or treat as
|
|
40
|
+
* the live answer.
|
|
41
41
|
*
|
|
42
42
|
* @remarks
|
|
43
|
-
* Deliberately a
|
|
44
|
-
* bounded `prefix × sections` overhead and
|
|
43
|
+
* Deliberately a fixed, lean handful of tokens (a short bracketed marker) so the framing adds a
|
|
44
|
+
* bounded `prefix × sections` overhead and never an open-ended blow-up — the
|
|
45
45
|
* {@link ConversationInterface} no-bloat test guard pins exactly that. Kept here (beside
|
|
46
46
|
* {@link DEFAULT_CONVERSATION_KEEP}) as the conversation layer's one tunable framing constant, so
|
|
47
47
|
* the wording has a single source of truth as it is optimized against real small-model behavior
|
|
@@ -51,43 +51,46 @@ var DEFAULT_CONVERSATION_KEEP = 0;
|
|
|
51
51
|
var CONVERSATION_RECAP_PREFIX = "[Summary of earlier messages] ";
|
|
52
52
|
/**
|
|
53
53
|
* Names the opening tag a {@link import('./ThinkSplitter.js').ThinkSplitter} recognizes as the start of
|
|
54
|
-
* an in-content reasoning span — the de-facto wire convention thinking models
|
|
55
|
-
* family) emit their chain-of-thought under when a daemon renders it inline
|
|
56
|
-
* separate wire field. Paired with {@link THINK_CLOSE}.
|
|
54
|
+
* an in-content reasoning span — `'<think>'`, the de-facto wire convention thinking models
|
|
55
|
+
* (qwen3, DeepSeek-R1 family) emit their chain-of-thought under when a daemon renders it inline
|
|
56
|
+
* instead of on a separate wire field. Paired with {@link THINK_CLOSE}.
|
|
57
57
|
*/
|
|
58
58
|
var THINK_OPEN = "<think>";
|
|
59
59
|
/**
|
|
60
|
-
* Names the closing tag that ends a {@link THINK_OPEN} reasoning span
|
|
61
|
-
* (the model was cut off mid-reasoning) is treated as thinking to its end
|
|
62
|
-
* {@link import('./types.js').ThinkSplitterInterface.flush} settles it.
|
|
60
|
+
* Names the closing tag that ends a {@link THINK_OPEN} reasoning span — `'</think>'`. A span the
|
|
61
|
+
* stream never closes (the model was cut off mid-reasoning) is treated as thinking to its end,
|
|
62
|
+
* and {@link import('./types.js').ThinkSplitterInterface.flush} settles it.
|
|
63
63
|
*/
|
|
64
64
|
var THINK_CLOSE = "</think>";
|
|
65
65
|
/**
|
|
66
66
|
* Names the section header {@link import('./AgentContext.js').AgentContext}'s `build()` renders the
|
|
67
|
-
*
|
|
68
|
-
* system message
|
|
67
|
+
* active workspace's text files under — `'## Workspace'`, the leading line of the dedicated
|
|
68
|
+
* workspace block in the system message and the carrier-split counterpart to the documents and
|
|
69
|
+
* images section headers.
|
|
69
70
|
*
|
|
70
71
|
* @remarks
|
|
71
|
-
* `build()`
|
|
72
|
+
* `build()` owns the workspace render (a `Workspace` / `WorkspaceManager` stays file-focused — no
|
|
72
73
|
* `open` / `format` getters), so this header lives here as the agents module's one
|
|
73
74
|
* workspace-section framing constant rather than on a manager. Each workspace text file renders
|
|
74
|
-
* beneath it as a fenced `` File: <path>\n```<language>\n<text>\n``` `` block — the
|
|
75
|
+
* beneath it as a fenced `` File: <path>\n```<language>\n<text>\n``` `` block — the same framing
|
|
75
76
|
* the documents section uses — placed immediately after the documents section in the system block.
|
|
76
77
|
*/
|
|
77
78
|
var WORKSPACE_SECTION_HEADER = "## Workspace";
|
|
78
79
|
/**
|
|
79
|
-
* Estimates the per-message role
|
|
80
|
-
* adds on top of a message's content estimate —
|
|
81
|
-
* conversation turn carries (its role tag, delimiters) that
|
|
82
|
-
* content-only heuristic does not otherwise
|
|
80
|
+
* Estimates the per-message role and framing overhead {@link import('./helpers.js').estimateMessages}
|
|
81
|
+
* adds on top of a message's content estimate — `4` tokens for the fixed wire framing every
|
|
82
|
+
* conversation turn carries (its role tag, its delimiters) that
|
|
83
|
+
* {@link import('./helpers.js').estimateTokens}'s content-only heuristic does not otherwise
|
|
84
|
+
* capture.
|
|
83
85
|
*/
|
|
84
86
|
var MESSAGE_TOKEN_OVERHEAD = 4;
|
|
85
87
|
/**
|
|
86
|
-
* Names the coarse, deliberately
|
|
87
|
-
* charges for each attached image
|
|
88
|
+
* Names the coarse, deliberately approximate per-image token cost
|
|
89
|
+
* {@link import('./helpers.js').estimateMessages} charges for each attached image — `512`, because
|
|
90
|
+
* a base64 payload's length is no reliable token proxy.
|
|
88
91
|
*
|
|
89
92
|
* @remarks
|
|
90
|
-
* A base64 image payload's
|
|
93
|
+
* A base64 image payload's length is not a reliable token proxy (a vision model's actual image
|
|
91
94
|
* token cost depends on resolution / tiling, not byte size), so this is a fixed, coarse
|
|
92
95
|
* per-image estimate rather than a derivation from `image.length` — a planning heuristic, not an
|
|
93
96
|
* exact count.
|
|
@@ -97,8 +100,8 @@ var IMAGE_TOKEN_ESTIMATE = 512;
|
|
|
97
100
|
//#region src/core/errors.ts
|
|
98
101
|
/**
|
|
99
102
|
* Reports a provider stream cancelled mid-flight by its bound signal — thrown by a
|
|
100
|
-
* {@link ProviderInterface}'s `stream`, carrying the {@link ProviderResult} assembled
|
|
101
|
-
*
|
|
103
|
+
* {@link ProviderInterface}'s `stream`, carrying the {@link ProviderResult} assembled from
|
|
104
|
+
* whatever streamed before the cancel and the machine-readable `code` `'ABORT'`.
|
|
102
105
|
*
|
|
103
106
|
* @remarks
|
|
104
107
|
* Lets a caller recover the partial content (and any tool calls / usage seen so far)
|
|
@@ -117,7 +120,8 @@ var ProviderAbortError = class extends Error {
|
|
|
117
120
|
}
|
|
118
121
|
};
|
|
119
122
|
/**
|
|
120
|
-
* Narrows an unknown caught value to a {@link ProviderAbortError}
|
|
123
|
+
* Narrows an unknown caught value to a {@link ProviderAbortError} through `instanceof`, so a
|
|
124
|
+
* `catch` can recover its `partial` result.
|
|
121
125
|
*
|
|
122
126
|
* @param value - The value to test (typically a `catch` binding)
|
|
123
127
|
* @returns True if `value` is a {@link ProviderAbortError}; false otherwise
|
|
@@ -137,13 +141,13 @@ function isProviderAbortError(value) {
|
|
|
137
141
|
/**
|
|
138
142
|
* Reports an {@link AgentInterface} run that ended {@link AgentResult.partial} under a
|
|
139
143
|
* `partial` policy of `false` (the default) — thrown by an agent-job handler (a
|
|
140
|
-
* `createAgentQueue` / `createAgentRunner` job), carrying the partial
|
|
141
|
-
*
|
|
144
|
+
* `createAgentQueue` / `createAgentRunner` job), carrying the partial {@link AgentResult} so
|
|
145
|
+
* the failure stays inspectable, and the machine-readable `code` `'PARTIAL'`.
|
|
142
146
|
*
|
|
143
147
|
* @remarks
|
|
144
148
|
* A partial result means the agent was cancelled (an external `signal` abort, a queue /
|
|
145
149
|
* runner abort threaded in, a `timeout` deadline, or an exhausted token `budget`) rather
|
|
146
|
-
* than finishing naturally. For a durable
|
|
150
|
+
* than finishing naturally. For a durable job that is a failure by default: throwing this
|
|
147
151
|
* lets the Queue's retries re-run the job and a Runner's fail-fast abort its siblings.
|
|
148
152
|
* Set `partial: true` (see `AgentQueueOptions` / `AgentRunnerOptions`) to treat a
|
|
149
153
|
* partial as success instead, in which case this is never thrown. Narrow a caught value
|
|
@@ -163,7 +167,8 @@ var AgentJobError = class extends Error {
|
|
|
163
167
|
}
|
|
164
168
|
};
|
|
165
169
|
/**
|
|
166
|
-
* Narrows an unknown caught value to an {@link AgentJobError}
|
|
170
|
+
* Narrows an unknown caught value to an {@link AgentJobError} through `instanceof`, so a
|
|
171
|
+
* `catch` can recover its `partial` result.
|
|
167
172
|
*
|
|
168
173
|
* @param value - The value to test (typically a `catch` binding or a rejected enqueue)
|
|
169
174
|
* @returns True if `value` is an {@link AgentJobError}; false otherwise
|
|
@@ -181,12 +186,13 @@ function isAgentJobError(value) {
|
|
|
181
186
|
return value instanceof AgentJobError;
|
|
182
187
|
}
|
|
183
188
|
/**
|
|
184
|
-
* Reports a conversation with no {@link ConversationSummaryHandler} to fold its messages
|
|
185
|
-
*
|
|
186
|
-
*
|
|
189
|
+
* Reports a conversation with no {@link ConversationSummaryHandler} to fold its messages with,
|
|
190
|
+
* or with a `sections` cap below `1` — thrown by a {@link ConversationInterface}'s `compact()`
|
|
191
|
+
* or its construction, carrying the machine-readable `code`
|
|
192
|
+
* `'SUMMARIZER' | 'SECTIONS'`.
|
|
187
193
|
*
|
|
188
194
|
* @remarks
|
|
189
|
-
* Compaction
|
|
195
|
+
* Compaction requires a summarizer (it digests the folded slice into a section summary and
|
|
190
196
|
* regenerates the rollup); a conversation created without one can still store + `view()` its
|
|
191
197
|
* live tail, but a `compact()` is a programmer error and throws this with `'SUMMARIZER'`.
|
|
192
198
|
* A `sections` cap (on {@link import('./types.js').ConversationOptions} /
|
|
@@ -205,7 +211,8 @@ var ConversationError = class extends Error {
|
|
|
205
211
|
}
|
|
206
212
|
};
|
|
207
213
|
/**
|
|
208
|
-
* Narrows an unknown caught value to a {@link ConversationError}
|
|
214
|
+
* Narrows an unknown caught value to a {@link ConversationError} through `instanceof`, so a
|
|
215
|
+
* `catch` can branch on its `code`.
|
|
209
216
|
*
|
|
210
217
|
* @param value - The value to test (typically a `catch` binding)
|
|
211
218
|
* @returns True if `value` is a {@link ConversationError}; false otherwise
|
|
@@ -223,13 +230,16 @@ function isConversationError(value) {
|
|
|
223
230
|
return value instanceof ConversationError;
|
|
224
231
|
}
|
|
225
232
|
/**
|
|
226
|
-
* Reports a concurrent run that would corrupt
|
|
233
|
+
* Reports a concurrent run that would corrupt shared per-agent accounting, or a rehydration
|
|
227
234
|
* name absent from its registry pool — thrown synchronously by an {@link AgentInterface}'s
|
|
228
|
-
* `stream()` and by
|
|
229
|
-
* `code
|
|
235
|
+
* `stream()` (and so by `generate()`, which calls it) and by an
|
|
236
|
+
* {@link AgentRegistryInterface}'s accessors, carrying the machine-readable `code`
|
|
237
|
+
* `'CONCURRENCY' | 'REGISTRY'`. Synchronous means a fire-and-forget
|
|
238
|
+
* `agent.generate().catch(…)` never catches it: `await` the call inside `try`/`catch`, or wrap
|
|
239
|
+
* the call expression itself.
|
|
230
240
|
*
|
|
231
241
|
* @remarks
|
|
232
|
-
* `'CONCURRENCY'` reports a run already in flight on the same agent,
|
|
242
|
+
* `'CONCURRENCY'` reports a run already in flight on the same agent, plus a construction-level
|
|
233
243
|
* `window` (a shared context budget) or a construction-level `budget` with no per-run override
|
|
234
244
|
* (a shared cost budget) — a second concurrent `stream()` would race its charges against the
|
|
235
245
|
* same shared instance, corrupting the accounting. Use separate agents, or per-run `budget`
|
|
@@ -247,7 +257,8 @@ var AgentError = class extends Error {
|
|
|
247
257
|
}
|
|
248
258
|
};
|
|
249
259
|
/**
|
|
250
|
-
* Narrows an unknown caught value to an {@link AgentError}
|
|
260
|
+
* Narrows an unknown caught value to an {@link AgentError} through `instanceof`, so a `catch`
|
|
261
|
+
* can branch on its `code`.
|
|
251
262
|
*
|
|
252
263
|
* @param value - The value to test (typically a `catch` binding)
|
|
253
264
|
* @returns True if `value` is an {@link AgentError}; false otherwise
|
|
@@ -267,8 +278,11 @@ function isAgentError(value) {
|
|
|
267
278
|
//#endregion
|
|
268
279
|
//#region src/core/helpers.ts
|
|
269
280
|
/**
|
|
270
|
-
* Projects an unknown value onto
|
|
271
|
-
* {@link AgentResult}
|
|
281
|
+
* Projects an unknown value onto a fresh, exact `JSONValue` representation of an
|
|
282
|
+
* {@link AgentResult} — capturing each structural field once through a total boundary,
|
|
283
|
+
* accepting conforming accessors and inherited properties, preserving finite negative and
|
|
284
|
+
* fractional usage counts, dropping extras, and resolving `undefined` for a malformed field, a
|
|
285
|
+
* non-finite usage number, a throwing getter, or a hostile or revoked proxy.
|
|
272
286
|
*
|
|
273
287
|
* @remarks
|
|
274
288
|
* This is a total hostile-boundary projection. Each structural field is captured once
|
|
@@ -322,15 +336,16 @@ function agentResultToJSON(value) {
|
|
|
322
336
|
return captured.success ? (0, _orkestrel_contract.parseJSONValue)(captured.value) : void 0;
|
|
323
337
|
}
|
|
324
338
|
/**
|
|
325
|
-
* Filters a list of items by a {@link import('./types.js').ScopeInterface} allow-list of
|
|
326
|
-
*
|
|
327
|
-
*
|
|
339
|
+
* Filters a list of items by a {@link import('./types.js').ScopeInterface} allow-list of keys —
|
|
340
|
+
* `undefined` passes everything, `[]` passes nothing, and a non-empty list passes the listed keys
|
|
341
|
+
* alone, order preserved. The pure, total set-membership primitive the context's build step and
|
|
342
|
+
* the agent loop's tool-advertise step apply a scope through.
|
|
328
343
|
*
|
|
329
344
|
* @remarks
|
|
330
345
|
* Three-way by the allow-list's shape, so a `Scope` category cleanly expresses "all /
|
|
331
346
|
* none / only these":
|
|
332
|
-
* - `undefined` ⇒
|
|
333
|
-
* - `[]` (empty) ⇒
|
|
347
|
+
* - `undefined` ⇒ no constraint — every item passes (returned unchanged).
|
|
348
|
+
* - `[]` (empty) ⇒ none pass (no key is in an empty set).
|
|
334
349
|
* - a non-empty list ⇒ only items whose `key(item)` is in the list pass.
|
|
335
350
|
*
|
|
336
351
|
* Order-preserving (it filters `items` in place order, never reorders) and total — never
|
|
@@ -357,15 +372,15 @@ function filterAllowList(allow, items, key) {
|
|
|
357
372
|
return items.filter((item) => set.has(key(item)));
|
|
358
373
|
}
|
|
359
374
|
/**
|
|
360
|
-
* Estimates the context-token footprint of a string — the deterministic
|
|
361
|
-
* {@link estimateMessages} sums over a conversation's messages (the default
|
|
362
|
-
* estimator).
|
|
375
|
+
* Estimates the context-token footprint of a string — the deterministic `ceil(length / 4)`
|
|
376
|
+
* character heuristic {@link estimateMessages} sums over a conversation's messages (the default
|
|
377
|
+
* context-budget estimator).
|
|
363
378
|
*
|
|
364
379
|
* @remarks
|
|
365
380
|
* Approximates `ceil(length / 4)` (≈ four characters per token — the rough average for
|
|
366
381
|
* English text), so the same input always yields the same estimate (no model round-trip).
|
|
367
382
|
* Empty text is `0`. This is a planning heuristic for reasoning about how much a turn's
|
|
368
|
-
* messages cost the next request,
|
|
383
|
+
* messages cost the next request, not an exact tokenizer count — it never calls a provider,
|
|
369
384
|
* so the agent layer stays provider-agnostic and synchronous where it can be.
|
|
370
385
|
*
|
|
371
386
|
* @param text - The text to estimate (a section summary, a message's content)
|
|
@@ -382,16 +397,19 @@ function estimateTokens(text) {
|
|
|
382
397
|
return Math.ceil(text.length / 4);
|
|
383
398
|
}
|
|
384
399
|
/**
|
|
385
|
-
* Estimates the context-token footprint of a batch of messages —
|
|
386
|
-
*
|
|
387
|
-
*
|
|
400
|
+
* Estimates the context-token footprint of a batch of messages — each message's content plus
|
|
401
|
+
* {@link import('./constants.js').MESSAGE_TOKEN_OVERHEAD}, a tool-call JSON estimate, and
|
|
402
|
+
* {@link import('./constants.js').IMAGE_TOKEN_ESTIMATE} for each attached image. The default
|
|
403
|
+
* `consumer` estimator for an agent's context budget (the
|
|
404
|
+
* {@link import('./types.js').AgentOptions} `window`), total and never throwing, and a
|
|
405
|
+
* deliberate provider-agnostic approximation rather than an exact tokenizer count.
|
|
388
406
|
*
|
|
389
407
|
* @remarks
|
|
390
408
|
* Sums, per message, {@link estimateTokens} over its `content` (the `ceil(length / 4)` char
|
|
391
|
-
* heuristic)
|
|
392
|
-
* role/framing overhead)
|
|
393
|
-
* `calls`
|
|
394
|
-
* deliberately-approximate per-image cost — a base64 length is
|
|
409
|
+
* heuristic) plus {@link import('./constants.js').MESSAGE_TOKEN_OVERHEAD} (a fixed per-message
|
|
410
|
+
* role/framing overhead) plus, when present, {@link estimateTokens} over its JSON-stringified
|
|
411
|
+
* `calls` plus `images.length * `{@link import('./constants.js').IMAGE_TOKEN_ESTIMATE} (a coarse,
|
|
412
|
+
* deliberately-approximate per-image cost — a base64 length is not a token proxy). Deterministic
|
|
395
413
|
* and provider-free — the same messages always yield the same estimate, with an empty batch `0`.
|
|
396
414
|
* It is the fully-swappable default an agent's auto-compaction context budget charges each
|
|
397
415
|
* turn's new messages through; a caller wanting a sharper count supplies its own `consumer` to
|
|
@@ -423,15 +441,17 @@ function estimateMessages(messages) {
|
|
|
423
441
|
}, 0);
|
|
424
442
|
}
|
|
425
443
|
/**
|
|
426
|
-
* Runs one rehydrated agent and applies the partial-as-configurable-failure policy —
|
|
427
|
-
*
|
|
428
|
-
*
|
|
444
|
+
* Runs one rehydrated agent and applies the partial-as-configurable-failure policy — a partial
|
|
445
|
+
* run throws an {@link import('./errors.js').AgentJobError} unless the `partial` policy allows
|
|
446
|
+
* it, and a natural finish resolves. The shared job-handler step `createAgentQueue` and
|
|
447
|
+
* `createAgentRunner` both settle each job through, so the policy can never diverge between
|
|
448
|
+
* them.
|
|
429
449
|
*
|
|
430
450
|
* @remarks
|
|
431
|
-
* A turn that committed
|
|
432
|
-
*
|
|
433
|
-
* (the Queue's retries + a Runner's fail-fast then engage); the `partial` policy
|
|
434
|
-
* it as success instead. A natural finish
|
|
451
|
+
* A turn that committed partial (a cancel — abort / budget / timeout) is by default a
|
|
452
|
+
* failure, so it throws an {@link import('./errors.js').AgentJobError} carrying the partial
|
|
453
|
+
* (the Queue's retries + a Runner's fail-fast then engage); the `partial` policy resolves
|
|
454
|
+
* it as success instead. A natural finish always resolves with its result.
|
|
435
455
|
*
|
|
436
456
|
* @param agent - The rehydrated {@link AgentInterface} to run to its {@link AgentResult}
|
|
437
457
|
* @param partial - The partial policy. If `true`, a partial result resolves as success; if
|
|
@@ -483,9 +503,9 @@ function handleAgentRunnerJob(registry, partial, controller) {
|
|
|
483
503
|
return settleAgentJob(registry.build(controller.input, controller.signal), partial);
|
|
484
504
|
}
|
|
485
505
|
/**
|
|
486
|
-
* Renders a path-addressed text body as a fenced reference block —
|
|
487
|
-
*
|
|
488
|
-
* active
|
|
506
|
+
* Renders a path-addressed text body as a fenced reference block — a `File: <path>` label line
|
|
507
|
+
* over a language-tagged fence, the framing an
|
|
508
|
+
* {@link import('./AgentContext.js').AgentContext}'s active-workspace text-file render emits.
|
|
489
509
|
*
|
|
490
510
|
* @remarks
|
|
491
511
|
* Produces `` File: <path>\n```<language>\n<content>\n``` `` — the `File:` label line, then a
|
|
@@ -511,7 +531,8 @@ function renderFencedFile(path, language, content) {
|
|
|
511
531
|
return `File: ${path}\n\`\`\`${language}\n${content}\n\`\`\``;
|
|
512
532
|
}
|
|
513
533
|
/**
|
|
514
|
-
* Sanitizes one reported token count into a safe non-negative integer
|
|
534
|
+
* Sanitizes one reported token count into a safe non-negative integer — a non-finite or
|
|
535
|
+
* non-positive value becomes `0`, and a positive fractional value floors down.
|
|
515
536
|
*
|
|
516
537
|
* @param value - The token count to sanitize
|
|
517
538
|
* @returns The floored count, or `0` when the value is non-finite or non-positive
|
|
@@ -597,10 +618,11 @@ function sumUsage(running, next) {
|
|
|
597
618
|
}
|
|
598
619
|
/**
|
|
599
620
|
* Assembles the settled {@link AgentResult} from a run's {@link RunOutcome} — `thinking` and
|
|
600
|
-
* `usage` are carried only when the run surfaced them
|
|
621
|
+
* `usage` are carried only when the run surfaced them, and the loop-internal `exhausted` flag is
|
|
622
|
+
* left out.
|
|
601
623
|
*
|
|
602
624
|
* @remarks
|
|
603
|
-
* Pure and total. An absent optional is
|
|
625
|
+
* Pure and total. An absent optional is omitted rather than stored as `undefined` (the
|
|
604
626
|
* present-when-given convention the message store follows), so a settled result JSON
|
|
605
627
|
* round-trips without an explicit `undefined` field. `exhausted` is loop bookkeeping and does
|
|
606
628
|
* not reach the public result — the `exhaust` event carries it instead.
|
|
@@ -652,10 +674,10 @@ function denyCall(call, reason) {
|
|
|
652
674
|
}
|
|
653
675
|
/**
|
|
654
676
|
* Renders one context section — the resolved `open`, each item's rendering, and the resolved
|
|
655
|
-
* `close` when one exists, blank-line joined.
|
|
677
|
+
* `close` when one exists, blank-line joined; `undefined` when the section has no items.
|
|
656
678
|
*
|
|
657
679
|
* @remarks
|
|
658
|
-
* Pure and total. A section with
|
|
680
|
+
* Pure and total. A section with no items renders nothing (`undefined`), so an empty or fully
|
|
659
681
|
* scoped-out manager stays silent — its `open` / `close` never appear without items. `close`
|
|
660
682
|
* is the only optional slot: an unset one (there is no built-in close) drops the
|
|
661
683
|
* trailing line.
|
|
@@ -681,13 +703,13 @@ function renderSection(open, items, render, close) {
|
|
|
681
703
|
return lines.join("\n\n");
|
|
682
704
|
}
|
|
683
705
|
/**
|
|
684
|
-
* Resolves one section's
|
|
706
|
+
* Resolves one section's open text through the format cascade — manager-options override >
|
|
685
707
|
* provider default > built-in header.
|
|
686
708
|
*
|
|
687
709
|
* @remarks
|
|
688
|
-
* Pure and total. The leading text has
|
|
710
|
+
* Pure and total. The leading text has no per-item level. A manager's `open` already
|
|
689
711
|
* encapsulates `[options-override → built-in]`, so it is reached only when neither the
|
|
690
|
-
* override's `open` nor the provider's `open` applies — and there it
|
|
712
|
+
* override's `open` nor the provider's `open` applies — and there it is the built-in header.
|
|
691
713
|
*
|
|
692
714
|
* @typeParam T - The section item the manager renders
|
|
693
715
|
* @param manager - The section source (its `format` override + its built-in `open`)
|
|
@@ -704,13 +726,13 @@ function resolveOpen(manager, provider) {
|
|
|
704
726
|
return manager.format?.open ?? provider?.open ?? manager.open;
|
|
705
727
|
}
|
|
706
728
|
/**
|
|
707
|
-
* Resolves one section's
|
|
708
|
-
* provider default.
|
|
729
|
+
* Resolves one section's close text through the format cascade — manager-options override >
|
|
730
|
+
* provider default; `undefined` when neither sets one, because there is no built-in close.
|
|
709
731
|
*
|
|
710
732
|
* @remarks
|
|
711
|
-
* Pure and total. There is
|
|
733
|
+
* Pure and total. There is no built-in close, so a section with neither level set returns
|
|
712
734
|
* `undefined` and {@link renderSection} appends no closing line. Paired with
|
|
713
|
-
* {@link resolveOpen}, one level can
|
|
735
|
+
* {@link resolveOpen}, one level can wrap the whole group.
|
|
714
736
|
*
|
|
715
737
|
* @typeParam T - The section item the manager renders
|
|
716
738
|
* @param manager - The section source (its `format` override)
|
|
@@ -727,14 +749,14 @@ function resolveClose(manager, provider) {
|
|
|
727
749
|
return manager.format?.close ?? provider?.close;
|
|
728
750
|
}
|
|
729
751
|
/**
|
|
730
|
-
* Resolves
|
|
731
|
-
*
|
|
752
|
+
* Resolves one item's rendering through the format cascade — item override > manager-options
|
|
753
|
+
* override > provider default > built-in rendering.
|
|
732
754
|
*
|
|
733
755
|
* @remarks
|
|
734
756
|
* Pure and total. The item's own `override` is the most-specific level (a fully-rendered
|
|
735
757
|
* string for that item alone). A manager's `render(item)` already encapsulates
|
|
736
758
|
* `[options-override → built-in]`, so it is reached only when no higher level applies — and
|
|
737
|
-
* there it
|
|
759
|
+
* there it is the built-in rendering.
|
|
738
760
|
*
|
|
739
761
|
* @typeParam T - The section item being rendered (it may carry its own `override`)
|
|
740
762
|
* @param manager - The section source (its `format` override + its built-in `render`)
|
|
@@ -753,11 +775,11 @@ function resolveItem(manager, provider, item) {
|
|
|
753
775
|
return item.override ?? manager.format?.render?.(item) ?? provider?.render?.(item) ?? manager.render(item);
|
|
754
776
|
}
|
|
755
777
|
/**
|
|
756
|
-
* Copies a message with image data merged onto its `images` — the message's own images
|
|
757
|
-
*
|
|
778
|
+
* Copies a message with image data merged onto its `images` — the message's own images first,
|
|
779
|
+
* then the attached data, carrying `calls` only when present and never mutating the original.
|
|
758
780
|
*
|
|
759
781
|
* @remarks
|
|
760
|
-
* Pure and total: the original message is
|
|
782
|
+
* Pure and total: the original message is never mutated. `calls` is carried only when the
|
|
761
783
|
* source message has one (kept omitted otherwise, mirroring the store's present-when-given
|
|
762
784
|
* convention).
|
|
763
785
|
*
|
|
@@ -787,13 +809,14 @@ function attachImages(message, data) {
|
|
|
787
809
|
};
|
|
788
810
|
}
|
|
789
811
|
/**
|
|
790
|
-
* Attaches image data to a conversation's
|
|
791
|
-
*
|
|
812
|
+
* Attaches image data to a conversation's last user message — the turn a vision provider reads
|
|
813
|
+
* images off — as a new array with that one message replaced by its carrying copy, and unchanged
|
|
814
|
+
* when there is no data or no user turn.
|
|
792
815
|
*
|
|
793
816
|
* @remarks
|
|
794
|
-
* Pure and total: the conversation and its messages are
|
|
817
|
+
* Pure and total: the conversation and its messages are never mutated, and the returned array
|
|
795
818
|
* replaces exactly the one target message with the copy {@link attachImages} builds. Empty
|
|
796
|
-
* data returns the conversation unchanged; a conversation with
|
|
819
|
+
* data returns the conversation unchanged; a conversation with no user message returns it
|
|
797
820
|
* unchanged too (there is nowhere to attach, and the images already rode the system block).
|
|
798
821
|
*
|
|
799
822
|
* @param conversation - The messages to attach into (left unchanged)
|
|
@@ -817,11 +840,11 @@ function attachUserImages(conversation, data) {
|
|
|
817
840
|
return conversation.map((message, index) => index === target ? attachImages(message, data) : message);
|
|
818
841
|
}
|
|
819
842
|
/**
|
|
820
|
-
* Collects the `base64` payload of the
|
|
821
|
-
*
|
|
843
|
+
* Collects the `base64` payload of the image files in a workspace file list — the data an agent
|
|
844
|
+
* context attaches to the last user message.
|
|
822
845
|
*
|
|
823
846
|
* @remarks
|
|
824
|
-
* Pure and total. `isBinary`
|
|
847
|
+
* Pure and total. `isBinary` narrows the tagless content to its binary arm (a total guard,
|
|
825
848
|
* never an assertion), then the MIME prefix gates it to an image, so a text file and a non-image
|
|
826
849
|
* binary (a PDF) are both skipped. Order follows the file list.
|
|
827
850
|
*
|
|
@@ -840,12 +863,12 @@ function collectImageData(files) {
|
|
|
840
863
|
return data;
|
|
841
864
|
}
|
|
842
865
|
/**
|
|
843
|
-
* Builds the
|
|
844
|
-
*
|
|
866
|
+
* Builds the raw synthetic summary message for one compacted section — role `'assistant'`, the
|
|
867
|
+
* section's stable `id`, and its `summary` verbatim as content.
|
|
845
868
|
*
|
|
846
869
|
* @remarks
|
|
847
870
|
* Pure and total. This is the unframed form the rollup regeneration digests (a
|
|
848
|
-
* summary-of-summaries over the section summaries); the recap
|
|
871
|
+
* summary-of-summaries over the section summaries); the recap label is a `view()`
|
|
849
872
|
* presentation concern kept out of what the summarizer re-reads — see
|
|
850
873
|
* {@link buildRecapMessage}.
|
|
851
874
|
*
|
|
@@ -866,13 +889,13 @@ function buildSummaryMessage(section) {
|
|
|
866
889
|
};
|
|
867
890
|
}
|
|
868
891
|
/**
|
|
869
|
-
* Builds the
|
|
870
|
-
*
|
|
871
|
-
*
|
|
892
|
+
* Builds the framed recap message for one compacted section — the same role and stable `id` as
|
|
893
|
+
* {@link buildSummaryMessage}, with the content prefixed by {@link
|
|
894
|
+
* import('./constants.js').CONVERSATION_RECAP_PREFIX}.
|
|
872
895
|
*
|
|
873
896
|
* @remarks
|
|
874
|
-
* Pure and total. The prefix is what makes a small model read the message as a
|
|
875
|
-
*
|
|
897
|
+
* Pure and total. The prefix is what makes a small model read the message as a condensed
|
|
898
|
+
* recap of earlier turns rather than a literal assistant turn to echo or answer from. It is a
|
|
876
899
|
* fixed handful of tokens, so a conversation's `view()` stays lean however many sections it
|
|
877
900
|
* carries.
|
|
878
901
|
*
|
|
@@ -893,12 +916,12 @@ function buildRecapMessage(section) {
|
|
|
893
916
|
};
|
|
894
917
|
}
|
|
895
918
|
/**
|
|
896
|
-
* Intersects two scope category lists under the "`undefined` is the universal set" rule —
|
|
897
|
-
* primitive a scope narrows through.
|
|
919
|
+
* Intersects two scope category lists under the "`undefined` is the universal set" rule — a
|
|
920
|
+
* fresh copy that can only tighten, and the primitive a scope narrows through.
|
|
898
921
|
*
|
|
899
922
|
* @remarks
|
|
900
|
-
* Pure and total, and it can only
|
|
901
|
-
* constraint); `undefined` ∩ a list is a
|
|
923
|
+
* Pure and total, and it can only tighten: `undefined` ∩ `undefined` is `undefined` (still no
|
|
924
|
+
* constraint); `undefined` ∩ a list is a copy of that list (the `undefined` side imposes
|
|
902
925
|
* nothing); a list ∩ a list keeps the child keys the parent also allows, so a parent-excluded
|
|
903
926
|
* key can never be re-admitted. Every returned list is a fresh copy, so a later mutation of
|
|
904
927
|
* either input cannot leak into the result.
|
|
@@ -923,36 +946,37 @@ function intersectKeys(parent, child) {
|
|
|
923
946
|
//#endregion
|
|
924
947
|
//#region src/core/conversations/Conversation.ts
|
|
925
948
|
/**
|
|
926
|
-
* Represents a conversation — a live uncompacted tail of messages it
|
|
927
|
-
*
|
|
928
|
-
* `
|
|
929
|
-
* {@link ConversationSummaryHandler} seam
|
|
949
|
+
* Represents a conversation — a live uncompacted tail of messages it owns directly above a flat
|
|
950
|
+
* message store, plus compacted, summarized {@link Section}s, a regenerated rollup `summary`, and
|
|
951
|
+
* a `summarizable` flag, with on-demand `rehydrate` and substring `search`, driven by a
|
|
952
|
+
* provider-agnostic {@link ConversationSummaryHandler} seam so `core` never imports a provider.
|
|
953
|
+
* Observable through its own `emitter`.
|
|
930
954
|
*
|
|
931
955
|
* @remarks
|
|
932
|
-
* - **Live tail + sections.** The conversation
|
|
956
|
+
* - **Live tail + sections.** The conversation owns its live tail directly — `#messages` is an
|
|
933
957
|
* insertion-ordered `Map` of immutable {@link Message}s keyed by their minted id
|
|
934
|
-
* (the
|
|
958
|
+
* (the same store mechanics a flat manager had, folded in: `add` / `message` / `messages` /
|
|
935
959
|
* `remove` / `clear` / `count`), exactly as a `Workspace` owns its files (no separate
|
|
936
960
|
* per-value manager). `#sections` are the compacted history (oldest → newest), each a
|
|
937
|
-
* summarized slice that
|
|
961
|
+
* summarized slice that retains its originals. `#summary` is the rollup (a
|
|
938
962
|
* summary-of-summaries over all sections), regenerated on each compaction (`undefined`
|
|
939
963
|
* until the first).
|
|
940
|
-
* - **`view()`.** Each section folds to
|
|
964
|
+
* - **`view()`.** Each section folds to one synthetic summary message (role `'assistant'` — a
|
|
941
965
|
* prior-context recap — keyed by the section's stable `id`), then the live messages
|
|
942
|
-
* verbatim. The rollup `summary` is
|
|
943
|
-
* carries the per-section summaries, which
|
|
966
|
+
* verbatim. The rollup `summary` is not injected (it is separately pull-able); `view()`
|
|
967
|
+
* carries the per-section summaries, which are the compaction benefit.
|
|
944
968
|
* - **`compact()`.** Folds the oldest `count - keep` live messages into a new section
|
|
945
969
|
* (its `summary` from `#summarize`), removes them from the live tail by id, regenerates the
|
|
946
|
-
* rollup (a
|
|
970
|
+
* rollup (a second `#summarize` over all section summaries), and emits `summary` then
|
|
947
971
|
* `compact`. Returns the section, or `undefined` when nothing folds (`count <= keep`).
|
|
948
|
-
*
|
|
949
|
-
*
|
|
972
|
+
* Throws a {@link ConversationError} when no `#summarize` was supplied. A compaction calls
|
|
973
|
+
* the summarizer for the section digest and again for the rollup.
|
|
950
974
|
* - **`rehydrate(id)` / `search(query)`.** `rehydrate` returns a section's full original
|
|
951
975
|
* messages (`[]` for an unknown id) and emits `rehydrate` — a pure read (the caller decides
|
|
952
976
|
* whether to re-add them; `rehydrate` never reinserts). `search` is a case-insensitive
|
|
953
|
-
* substring scan of `content` across
|
|
977
|
+
* substring scan of `content` across all messages (every section's originals + the live tail).
|
|
954
978
|
* - **Observable.** The owned {@link emitter} ({@link ConversationEventMap}) carries
|
|
955
|
-
* `compact` / `summary` / `rehydrate`, emitted directly, strictly
|
|
979
|
+
* `compact` / `summary` / `rehydrate`, emitted directly, strictly after the state change;
|
|
956
980
|
* the emitter isolates a listener throw and routes it to its `error` handler (the `error`
|
|
957
981
|
* option), so a buggy observer can never corrupt a compaction.
|
|
958
982
|
*
|
|
@@ -1118,26 +1142,27 @@ var Conversation = class {
|
|
|
1118
1142
|
//#endregion
|
|
1119
1143
|
//#region src/core/conversations/ConversationManager.ts
|
|
1120
1144
|
/**
|
|
1121
|
-
* Registers {@link Conversation}s keyed by `id`, in insertion order,
|
|
1122
|
-
*
|
|
1123
|
-
* {@link import('../AgentContext.js').AgentContext} renders
|
|
1124
|
-
*
|
|
1125
|
-
*
|
|
1145
|
+
* Registers {@link Conversation}s keyed by `id`, in insertion order, with an active pointer —
|
|
1146
|
+
* the id-keyed store over the conversation layer, the `active` / `switch` seam the
|
|
1147
|
+
* {@link import('../AgentContext.js').AgentContext} renders, and the durable `open` / `save`
|
|
1148
|
+
* store seam. Event-free (a registry, like
|
|
1149
|
+
* {@link import('@orkestrel/workspace').WorkspaceManager}); the observability lives on each
|
|
1150
|
+
* {@link Conversation}.
|
|
1126
1151
|
*
|
|
1127
1152
|
* @remarks
|
|
1128
1153
|
* - **Registry.** Conversations live in an insertion-ordered `Map` keyed by `id`. `add(input?)`
|
|
1129
1154
|
* mints a {@link Conversation} (its `id` from `input` or `crypto.randomUUID()`), flowing the
|
|
1130
|
-
* manager's default `#summarize` / `#keep` in unless the `input`
|
|
1131
|
-
* it (an already-present `id`
|
|
1155
|
+
* manager's default `#summarize` / `#keep` in unless the `input` overrides them, and stores
|
|
1156
|
+
* it (an already-present `id` overwrites — last write wins). `count` is the map size,
|
|
1132
1157
|
* `conversation(id)` looks one up, `conversations()` lists them in insertion order.
|
|
1133
1158
|
* - **Active pointer.** `active` is the active conversation (the agent's message source the
|
|
1134
|
-
* context renders), `undefined` until the
|
|
1159
|
+
* context renders), `undefined` until the first `add` (which auto-activates it — a registry
|
|
1135
1160
|
* with conversations always has one active). A subsequent `add` leaves `active` unchanged.
|
|
1136
1161
|
* `switch(id)` re-points `active` to the conversation with `id` and returns it; an unknown `id`
|
|
1137
1162
|
* returns `undefined` and leaves `active` unchanged (the lenient lookup style — never throws,
|
|
1138
1163
|
* no new error code).
|
|
1139
|
-
* - **Removal.** `remove` drops one by id, or a batch — `true` only when
|
|
1140
|
-
* removed; removing the
|
|
1164
|
+
* - **Removal.** `remove` drops one by id, or a batch — `true` only when every supplied id was
|
|
1165
|
+
* removed; removing the active conversation sets `active` to `undefined`. `clear` empties the registry
|
|
1141
1166
|
* and sets `active` to `undefined`.
|
|
1142
1167
|
* - **Event-free.** A purely registry store — no Emitter, no events (each conversation owns
|
|
1143
1168
|
* its own observable `emitter`).
|
|
@@ -1237,15 +1262,15 @@ var ConversationManager = class {
|
|
|
1237
1262
|
//#endregion
|
|
1238
1263
|
//#region src/core/instructions/Instruction.ts
|
|
1239
1264
|
/**
|
|
1240
|
-
* Represents an immutable named directive —
|
|
1241
|
-
*
|
|
1242
|
-
*
|
|
1265
|
+
* Represents an immutable named directive — an {@link InstructionInterface} assembled once from
|
|
1266
|
+
* its input (`name` / `content`, an optional `priority` defaulting to `0`), the `id` minted at
|
|
1267
|
+
* construction.
|
|
1243
1268
|
*
|
|
1244
1269
|
* @remarks
|
|
1245
1270
|
* A thin immutable value object (mirroring {@link import('@orkestrel/tool').Tool}): the
|
|
1246
1271
|
* constructor mints a fresh `id` (`crypto.randomUUID()`), copies the input's `name` /
|
|
1247
1272
|
* `content`, resolves `priority` to the input's value or `0`, and carries the input's
|
|
1248
|
-
* per-item `override`
|
|
1273
|
+
* per-item `override` only when supplied (assigned when present, mirroring a
|
|
1249
1274
|
* message's `images` / `calls` present-when-given convention — kept absent otherwise).
|
|
1250
1275
|
* Never mutated after construction. An
|
|
1251
1276
|
* {@link import('./InstructionManager.js').InstructionManager} keys it by `name` and
|
|
@@ -1274,28 +1299,29 @@ var Instruction = class {
|
|
|
1274
1299
|
//#endregion
|
|
1275
1300
|
//#region src/core/instructions/InstructionManager.ts
|
|
1276
1301
|
/**
|
|
1277
|
-
* Registers the immutable {@link Instruction}s a richer context assembles a directives
|
|
1278
|
-
*
|
|
1302
|
+
* Registers the immutable {@link Instruction}s a richer context assembles a directives block
|
|
1303
|
+
* from — keyed by `name` so a re-`add` overwrites, last write wins, and listed by descending
|
|
1304
|
+
* `priority`, carrying the `open` / `render` build contract and an observable `emitter`.
|
|
1279
1305
|
*
|
|
1280
1306
|
* @remarks
|
|
1281
1307
|
* - **Registry.** Instructions live in an insertion-ordered `Map` keyed by `name`;
|
|
1282
|
-
* `add` takes one {@link InstructionInput} or a batch,
|
|
1283
|
-
* `id`, and a re-`add` of the same name
|
|
1284
|
-
* map size, `instruction(name)` looks one up, and `instructions()` lists them
|
|
1308
|
+
* `add` takes one {@link InstructionInput} or a batch, mints each instruction's
|
|
1309
|
+
* `id`, and a re-`add` of the same name overwrites it (last write wins). `count` is the
|
|
1310
|
+
* map size, `instruction(name)` looks one up, and `instructions()` lists them sorted by
|
|
1285
1311
|
* descending `priority` (a stable sort, so equal priorities keep insertion order).
|
|
1286
1312
|
* - **Build contract (with the manager-options override).** `open` is the section
|
|
1287
1313
|
* header a context renders the instructions under; `render(instruction)` renders one
|
|
1288
|
-
* instruction (its `content`). Each
|
|
1314
|
+
* instruction (its `content`). Each encapsulates the cascade's `[options-override →
|
|
1289
1315
|
* built-in]` half: when `InstructionManagerOptions.format` supplies an `open` /
|
|
1290
|
-
* `render`, `open` / `render` return
|
|
1316
|
+
* `render`, `open` / `render` return it, else the built-in — so a richer context
|
|
1291
1317
|
* reads one consistent pair and layers the provider default + per-item override on top
|
|
1292
1318
|
* (see {@link import('../AgentContext.js').AgentContext}). The per-item
|
|
1293
1319
|
* {@link InstructionInput.override} is round-tripped onto the stored instruction.
|
|
1294
|
-
* - **Removal.** `remove` drops one by name, or a batch — `true` only when
|
|
1320
|
+
* - **Removal.** `remove` drops one by name, or a batch — `true` only when every supplied
|
|
1295
1321
|
* name was removed; `clear` empties the registry.
|
|
1296
1322
|
* - **Observable.** The owned {@link emitter} ({@link InstructionManagerEventMap})
|
|
1297
1323
|
* carries `add` (the created instruction) / `remove` (the name) / `clear` for
|
|
1298
|
-
* fire-and-forget observers. Every event is emitted directly, strictly
|
|
1324
|
+
* fire-and-forget observers. Every event is emitted directly, strictly after the map
|
|
1299
1325
|
* mutation completes; the emitter isolates a listener throw and routes it to its `error`
|
|
1300
1326
|
* handler (the `error` option), so a buggy observer can never corrupt a mutation.
|
|
1301
1327
|
*
|
|
@@ -1374,58 +1400,59 @@ var InstructionManager = class {
|
|
|
1374
1400
|
/**
|
|
1375
1401
|
* Assembles a provider request from the richer turn context — the optional system prompt, the
|
|
1376
1402
|
* observable context managers (instructions / workspaces), the
|
|
1377
|
-
* {@link ConversationManagerInterface} message source
|
|
1378
|
-
*
|
|
1379
|
-
* {@link AgentContextInterface.apply}.
|
|
1403
|
+
* {@link ConversationManagerInterface} message source whose active conversation is `messages`, the
|
|
1404
|
+
* {@link ToolManagerInterface} registry, and an active {@link ScopeInterface} changed through
|
|
1405
|
+
* {@link AgentContextInterface.apply}. `build()` folds the scoped managers and the active
|
|
1406
|
+
* workspace into one system block, then the conversation, and never reads `tools`.
|
|
1380
1407
|
*
|
|
1381
1408
|
* @remarks
|
|
1382
1409
|
* - **Composition.** `system` is the optional system prompt; `instructions` / `tools` /
|
|
1383
1410
|
* `workspaces` / `conversations` are the registries passed in `options` (bring your own), or
|
|
1384
|
-
* fresh empty ones when omitted (so `workspaces` is
|
|
1385
|
-
* conversation's live tail (
|
|
1411
|
+
* fresh empty ones when omitted (so `workspaces` is always present); `messages` is the active
|
|
1412
|
+
* conversation's live tail (always defined — see the following item). `scope` is the active filter —
|
|
1386
1413
|
* `undefined` (the default) ⇒ no filtering; change it through `apply(scope)`. The structural
|
|
1387
1414
|
* `workspaces` / `conversations` registries are fixed at construction; switch their active
|
|
1388
1415
|
* members through their own `switch(id)` methods.
|
|
1389
|
-
* - **The message source — the conversation registry's
|
|
1390
|
-
* {@link ConversationManagerInterface}; the context
|
|
1391
|
-
* (at construction it `add`s a default when the manager has none), so the
|
|
1392
|
-
* getter — `this.#conversations.active` — is
|
|
1393
|
-
* conversation
|
|
1416
|
+
* - **The message source — the conversation registry's active conversation.** `conversations` is a
|
|
1417
|
+
* {@link ConversationManagerInterface}; the context ensures it always has an active conversation
|
|
1418
|
+
* (at construction it `add`s a default when the manager has none), so the dynamic `messages`
|
|
1419
|
+
* getter — `this.#conversations.active` — is always defined. `messages` returns the active
|
|
1420
|
+
* conversation itself (it owns the live tail + the message verbs directly, satisfying
|
|
1394
1421
|
* {@link MessageManagerInterface} structurally — the same reference, no duplication), and
|
|
1395
1422
|
* `build()` folds that conversation's `view()` (its per-section summaries + live tail) as the
|
|
1396
|
-
*
|
|
1423
|
+
* authoritative message inclusion — the scope does not filter the conversation (it owns inclusion
|
|
1397
1424
|
* through compaction; scope filters only instructions / tools / workspace files). Because `messages`
|
|
1398
|
-
* is read dynamically, an agent
|
|
1399
|
-
* conversation
|
|
1425
|
+
* is read dynamically, an agent switches the active
|
|
1426
|
+
* conversation between runs (`conversations.switch(id)`) to serve many threads (the real
|
|
1400
1427
|
* multi-conversation pattern); switch between runs, not during a run, and use separate agents
|
|
1401
1428
|
* for concurrent threads.
|
|
1402
1429
|
* - **`build(format?)` — the scoped assembly + the format cascade.** It folds, in order,
|
|
1403
|
-
* the system prompt then the scope-filtered instructions → the
|
|
1430
|
+
* the system prompt then the scope-filtered instructions → the active workspace's text files
|
|
1404
1431
|
* (each as a block: the section's resolved `open` text, each item's resolved rendering, then
|
|
1405
|
-
* any resolved `close` text) into
|
|
1406
|
-
* one part exists), then appends the
|
|
1407
|
-
* message inclusion through compaction — the scope does
|
|
1432
|
+
* any resolved `close` text) into one leading `system` message (prepended only when at least
|
|
1433
|
+
* one part exists), then appends the active conversation's `view()` (the conversation owns
|
|
1434
|
+
* message inclusion through compaction — the scope does not filter the conversation). Each
|
|
1408
1435
|
* `open` / item / `close`
|
|
1409
|
-
* resolves
|
|
1410
|
-
* per-section default) is the
|
|
1436
|
+
* resolves independently, most-specific-first — `build()`'s optional `format` (a provider's
|
|
1437
|
+
* per-section default) is the provider level: `open` = manager-options-override > provider >
|
|
1411
1438
|
* built-in; per item = item-override > manager-options-override > provider > built-in; `close` =
|
|
1412
|
-
* manager-options-override > provider (
|
|
1413
|
-
* {@link AgentContextInterface.build}). Passing
|
|
1439
|
+
* manager-options-override > provider (no built-in ⇒ no closing line when unset) (see
|
|
1440
|
+
* {@link AgentContextInterface.build}). Passing no `format` (and with no overrides / no per-item
|
|
1414
1441
|
* override) reproduces the built-in framing byte-for-byte (each section is its built-in header +
|
|
1415
1442
|
* items, no closing line). The active workspace's scoped-in image files' `base64` payload is attached to
|
|
1416
|
-
* the
|
|
1443
|
+
* the last user message (a vision provider reads images off a user turn); when no user message
|
|
1417
1444
|
* exists the attachment is skipped. Built fresh each call (recomputed, never cached), so it
|
|
1418
1445
|
* always reflects the current managers / messages / scope / active workspace; it never mutates a
|
|
1419
1446
|
* manager or the stored messages.
|
|
1420
|
-
* - **The
|
|
1447
|
+
* - **The active workspace, rendered by carrier — the sole document/image context.**
|
|
1421
1448
|
* `workspaces.active` (when set) has its {@link import('@orkestrel/workspace').FileInterface}s scope-filtered by `scope.files`,
|
|
1422
|
-
* then split:
|
|
1423
|
-
* blocks — placed right after the instructions section), and
|
|
1424
|
-
* to the last user message.
|
|
1425
|
-
* workspace nothing renders for workspaces. `build()`
|
|
1449
|
+
* then split: text files fold into a dedicated `## Workspace` system section (fenced reference
|
|
1450
|
+
* blocks — placed right after the instructions section), and image files' `base64` payload attaches
|
|
1451
|
+
* to the last user message. Active-only — never the other registered workspaces; with no active
|
|
1452
|
+
* workspace nothing renders for workspaces. `build()` owns this render (a `Workspace` /
|
|
1426
1453
|
* `WorkspaceManager` stays file-focused).
|
|
1427
1454
|
* - **Tools are structural, not in the prompt.** The registry is advertised to the provider
|
|
1428
|
-
* through `tools.definitions()` (scope-filtered by the loop),
|
|
1455
|
+
* through `tools.definitions()` (scope-filtered by the loop), never serialized into the
|
|
1429
1456
|
* message array — so `build()`'s output carries no tool content, scoped or not.
|
|
1430
1457
|
* - **Event-free context; observable managers.** The context itself owns no Emitter; the
|
|
1431
1458
|
* context managers each carry their own (the push observation surface).
|
|
@@ -1504,10 +1531,11 @@ var AgentContext = class {
|
|
|
1504
1531
|
//#endregion
|
|
1505
1532
|
//#region src/core/Channel.ts
|
|
1506
1533
|
/**
|
|
1507
|
-
* Buffers chunks in a minimal unbounded async channel — the eager pump
|
|
1508
|
-
*
|
|
1509
|
-
*
|
|
1510
|
-
*
|
|
1534
|
+
* Buffers chunks in a minimal unbounded async channel — the eager pump writes them in (`push`)
|
|
1535
|
+
* and ends it (`close` / `fail`) regardless of consumption, while a consumer reads them back live
|
|
1536
|
+
* through the `drain` async-iterator. Decoupling write from read is what lets a producer make
|
|
1537
|
+
* progress without a consumer pulling, and it is why an agent's `result` settles whether or not
|
|
1538
|
+
* its `events` are drained.
|
|
1511
1539
|
*
|
|
1512
1540
|
* @remarks
|
|
1513
1541
|
* The standard resolver-swap: a waiting `drain` parks on `#wake` (a void resolver);
|
|
@@ -1568,8 +1596,10 @@ var Channel = class {
|
|
|
1568
1596
|
//#region src/core/Agent.ts
|
|
1569
1597
|
/**
|
|
1570
1598
|
* Composes a {@link ProviderInterface}, an {@link AgentContext}, and a
|
|
1571
|
-
* {@link ToolManagerInterface} into a bounded context → provider → tools → repeat
|
|
1572
|
-
*
|
|
1599
|
+
* {@link ToolManagerInterface} into a bounded context → provider → tools → repeat turn, exposed
|
|
1600
|
+
* as both a one-shot `generate` and a live `stream` that share one private run — bounded by the
|
|
1601
|
+
* run `signal`, the `timeout`, and the `budget` folded through `AbortSignal.any`, paced by
|
|
1602
|
+
* `scheduler`, with tool iteration capped at `limit`.
|
|
1573
1603
|
*
|
|
1574
1604
|
* @remarks
|
|
1575
1605
|
* - **One loop, two faces.** A single private async generator (`#run`) drives the
|
|
@@ -1586,15 +1616,15 @@ var Channel = class {
|
|
|
1586
1616
|
* assistant message and stop.
|
|
1587
1617
|
* - **Bounded.** Each run arms one cancel through `createAbort({ signal: AbortSignal.any([
|
|
1588
1618
|
* …]) })` folding the external `signal`, the `timeout` deadline, and the `budget`
|
|
1589
|
-
* signal; `abort()` fires it. Any trip stops the loop and commits a
|
|
1590
|
-
* (the `result` promise
|
|
1619
|
+
* signal; `abort()` fires it. Any trip stops the loop and commits a partial result
|
|
1620
|
+
* (the `result` promise resolves, never rejects, on a cancel) — only a genuine
|
|
1591
1621
|
* provider / tool error rejects.
|
|
1592
1622
|
* - **Paced + capped.** The `scheduler` (when given) `yield`s between turns; tool
|
|
1593
1623
|
* iteration is capped at `limit`.
|
|
1594
|
-
* - **Two observation surfaces.** The
|
|
1595
|
-
* deltas (+ usage/tool chunks); the
|
|
1624
|
+
* - **Two observation surfaces.** The pull {@link AgentChunk} stream carries per-token
|
|
1625
|
+
* deltas (+ usage/tool chunks); the push {@link emitter} ({@link AgentEventMap}) carries
|
|
1596
1626
|
* lifecycle + usage/tool/deny moments for fire-and-forget observers. Every event is
|
|
1597
|
-
* emitted directly,
|
|
1627
|
+
* emitted directly, after the relevant state transition / settle; the emitter isolates a
|
|
1598
1628
|
* listener throw and routes it to its `error` handler (the `error` option), so a buggy
|
|
1599
1629
|
* observer can never escape into / reorder / corrupt the settle-once loop — observation is
|
|
1600
1630
|
* purely a side-channel.
|
|
@@ -1946,9 +1976,10 @@ var Agent = class {
|
|
|
1946
1976
|
//#endregion
|
|
1947
1977
|
//#region src/core/AgentRegistry.ts
|
|
1948
1978
|
/**
|
|
1949
|
-
* Makes a durable, JSON-serializable {@link AgentJobInput} runnable — holds the named
|
|
1950
|
-
*
|
|
1951
|
-
*
|
|
1979
|
+
* Makes a durable, JSON-serializable {@link AgentJobInput} runnable — holds the named pools of
|
|
1980
|
+
* live, non-serializable pieces (providers, tools, authorities, schedulers), throws on a name
|
|
1981
|
+
* absent from its pool, and `build`s a seeded, signal-wired {@link Agent} from a job's names and
|
|
1982
|
+
* data.
|
|
1952
1983
|
*
|
|
1953
1984
|
* @remarks
|
|
1954
1985
|
* - **Why it exists.** An `AgentJobInput` is serializable so it can survive a crash in a
|
|
@@ -1957,9 +1988,9 @@ var Agent = class {
|
|
|
1957
1988
|
* construct it once with the live pools, then a queue / runner handler calls `build`
|
|
1958
1989
|
* on each (possibly restored) job to get a ready agent.
|
|
1959
1990
|
* - **Accessors throw on a miss.** `provider` / `tool` / `authority` / `scheduler` resolve a
|
|
1960
|
-
* name against their pool and
|
|
1991
|
+
* name against their pool and throw an {@link AgentError} carrying `code: 'REGISTRY'` and
|
|
1961
1992
|
* the message `unknown <category>: <name>` when it is absent — an unknown name in a
|
|
1962
|
-
* rehydrated job is a programmer / config error that must fail
|
|
1993
|
+
* rehydrated job is a programmer / config error that must fail loudly at build time, never
|
|
1963
1994
|
* silently resolve to `undefined` and run an agent missing a dependency.
|
|
1964
1995
|
* - **`build` rehydrates.** Resolve the job's `provider`; assemble a fresh
|
|
1965
1996
|
* {@link ToolManager} from the `tools` names; rebuild the token `budget` from its
|
|
@@ -2040,18 +2071,20 @@ var AgentRegistry = class {
|
|
|
2040
2071
|
//#endregion
|
|
2041
2072
|
//#region src/core/Authority.ts
|
|
2042
2073
|
/**
|
|
2043
|
-
* Gates the agent loop's tool calls — the synchronous policy consulted before each call
|
|
2044
|
-
*
|
|
2074
|
+
* Gates the agent loop's tool calls — the synchronous policy consulted before each call runs,
|
|
2075
|
+
* turning one {@link AuthorityContext} into an {@link AuthorityDecision} by walking the ordered
|
|
2076
|
+
* rules first-match-wins and falling back to a configurable default, which allows an unmatched
|
|
2077
|
+
* call unless its `fallback` denies.
|
|
2045
2078
|
*
|
|
2046
2079
|
* @remarks
|
|
2047
2080
|
* - **Ordered, first-match-wins.** `evaluate` walks the configured rules in order and
|
|
2048
|
-
* returns the
|
|
2049
|
-
* `{ zone, allowed: rule.allowed ?? true, reason }` — a matched rule
|
|
2050
|
-
* default and
|
|
2081
|
+
* returns the first whose `match(context)` is true as
|
|
2082
|
+
* `{ zone, allowed: rule.allowed ?? true, reason }` — a matched rule allows by
|
|
2083
|
+
* default and denies only when its `allowed` is explicitly `false`.
|
|
2051
2084
|
* - **Fallback.** When no rule matches, `evaluate` returns the configured `fallback`.
|
|
2052
2085
|
* It defaults to `{ zone: DEFAULT_AUTHORITY_ZONE, allowed: true }` (allow-unmatched),
|
|
2053
|
-
* so a rules list of denials behaves as a
|
|
2054
|
-
* (an
|
|
2086
|
+
* so a rules list of denials behaves as a denylist. To make the gate deny-by-default
|
|
2087
|
+
* (an allowlist — only matched rules that allow get through), pass an `allowed: false`
|
|
2055
2088
|
* `fallback`.
|
|
2056
2089
|
* - **Consulted before each tool call.** The agent loop calls `evaluate({ call })` for
|
|
2057
2090
|
* every {@link import('@orkestrel/tool').ToolCall} the model emits; a denied call is fed
|
|
@@ -2094,14 +2127,15 @@ var Authority = class {
|
|
|
2094
2127
|
//#region src/core/validators.ts
|
|
2095
2128
|
/**
|
|
2096
2129
|
* Checks whether an `unknown` is structurally a {@link Message} record — the per-message step of
|
|
2097
|
-
* the {@link isConversationSnapshot} read-boundary
|
|
2098
|
-
*
|
|
2130
|
+
* the {@link isConversationSnapshot} and {@link isSection} read-boundary narrows, where a present
|
|
2131
|
+
* `calls` must be an array of valid {@link import('@orkestrel/tool').ToolCall}s. Total, never
|
|
2132
|
+
* throwing, and never an assertion; the conversation analogue of
|
|
2099
2133
|
* {@link import('@orkestrel/workspace').isFile}.
|
|
2100
2134
|
*
|
|
2101
2135
|
* @remarks
|
|
2102
|
-
* A total guard (it
|
|
2103
|
-
*
|
|
2104
|
-
* — a `calls` array
|
|
2136
|
+
* A total guard (it never throws — adversarial input returns `false`). It checks the message's
|
|
2137
|
+
* shape: a record with a `string` `id`, a `string` `role`, a `string` `content`, and — when present
|
|
2138
|
+
* — a `calls` array every element of which is a valid
|
|
2105
2139
|
* {@link import('@orkestrel/tool').ToolCall} ({@link isToolCall} — the
|
|
2106
2140
|
* fail-closed deepening: a tampered `calls` element rejects the message, so the snapshot
|
|
2107
2141
|
* reads back as absent rather than replaying a malformed call) and an `images` that is an array
|
|
@@ -2109,7 +2143,7 @@ var Authority = class {
|
|
|
2109
2143
|
* {@link import('./types.js').MessageRole}, so any storage-read role string is accepted
|
|
2110
2144
|
* defensively rather than rejected against the current literal set). Enough to safely impose the
|
|
2111
2145
|
* {@link Message} type at
|
|
2112
|
-
* a storage boundary
|
|
2146
|
+
* a storage boundary without a cast.
|
|
2113
2147
|
*
|
|
2114
2148
|
* @param value - The value to test (one element of a snapshot's `messages` / a section's `messages`)
|
|
2115
2149
|
* @returns True if `value` has the structural shape of a {@link Message}; false otherwise
|
|
@@ -2129,15 +2163,16 @@ function isMessage(value) {
|
|
|
2129
2163
|
return value.images === void 0 || (0, _orkestrel_contract.isArray)(value.images);
|
|
2130
2164
|
}
|
|
2131
2165
|
/**
|
|
2132
|
-
* Checks whether an `unknown` is structurally a {@link Section} record —
|
|
2133
|
-
*
|
|
2134
|
-
* read, never an
|
|
2166
|
+
* Checks whether an `unknown` is structurally a {@link Section} record — a `string` `id` and
|
|
2167
|
+
* `summary` beside a `messages` array of valid {@link Message}s, the per-section step of the
|
|
2168
|
+
* {@link isConversationSnapshot} read-boundary narrow. Total, never throwing, and never an
|
|
2169
|
+
* assertion.
|
|
2135
2170
|
*
|
|
2136
2171
|
* @remarks
|
|
2137
|
-
* A total guard (it
|
|
2138
|
-
*
|
|
2172
|
+
* A total guard (it never throws — adversarial input returns `false`). It checks the section's
|
|
2173
|
+
* shape: a record with a `string` `id`, a `string` `summary`, and a `messages` array every element
|
|
2139
2174
|
* of which is a valid {@link Message} record ({@link isMessage}). Enough to safely impose
|
|
2140
|
-
* the {@link Section} type at a storage boundary
|
|
2175
|
+
* the {@link Section} type at a storage boundary without a cast.
|
|
2141
2176
|
*
|
|
2142
2177
|
* @param value - The value to test (one element of a snapshot's `sections` array)
|
|
2143
2178
|
* @returns True if `value` has the structural shape of a {@link Section}; false otherwise
|
|
@@ -2155,19 +2190,20 @@ function isSection(value) {
|
|
|
2155
2190
|
return (0, _orkestrel_contract.isArray)(value.messages) && value.messages.every(isMessage);
|
|
2156
2191
|
}
|
|
2157
2192
|
/**
|
|
2158
|
-
* Narrows an `unknown` to a {@link ConversationSnapshot} —
|
|
2159
|
-
*
|
|
2193
|
+
* Narrows an `unknown` to a {@link ConversationSnapshot} — a `string` `id`, an optional `string`
|
|
2194
|
+
* `summary`, and valid `sections` and `messages` arrays; the total boundary guard for an
|
|
2195
|
+
* untrusted snapshot read (a storage row a
|
|
2160
2196
|
* {@link import('./conversations/stores/DatabaseConversationStore.js').DatabaseConversationStore}
|
|
2161
|
-
* reads back from its opaque JSON column, a snapshot loaded from disk). The
|
|
2162
|
-
* {@link import('@orkestrel/workspace').isWorkspaceSnapshot}.
|
|
2197
|
+
* reads back from its opaque JSON column, a snapshot loaded from disk), never throwing. The exact
|
|
2198
|
+
* analogue of {@link import('@orkestrel/workspace').isWorkspaceSnapshot}.
|
|
2163
2199
|
*
|
|
2164
2200
|
* @remarks
|
|
2165
|
-
* A total guard (it
|
|
2166
|
-
*
|
|
2167
|
-
* `undefined` until the first compaction), a `sections` array
|
|
2168
|
-
* {@link Section} ({@link isSection}), and a `messages` array
|
|
2201
|
+
* A total guard (it never throws — adversarial input returns `false`). It checks the snapshot's
|
|
2202
|
+
* shape: a `string` `id`, an optional `string` `summary` (present-or-absent — the rollup is
|
|
2203
|
+
* `undefined` until the first compaction), a `sections` array every element of which is a valid
|
|
2204
|
+
* {@link Section} ({@link isSection}), and a `messages` array every element of which is a
|
|
2169
2205
|
* valid {@link Message} ({@link isMessage}) — enough to safely impose the
|
|
2170
|
-
* {@link ConversationSnapshot} type at a storage boundary
|
|
2206
|
+
* {@link ConversationSnapshot} type at a storage boundary without a cast. The structural twin of
|
|
2171
2207
|
* {@link import('@orkestrel/workspace').isWorkspaceSnapshot}. A malformed blob (a non-record, a missing / non-string `id`, a
|
|
2172
2208
|
* non-string `summary` when present, a non-array `sections` / `messages`, or any malformed
|
|
2173
2209
|
* element) resolves `false`, so a
|
|
@@ -2196,37 +2232,39 @@ function isConversationSnapshot(value) {
|
|
|
2196
2232
|
//#region src/core/conversations/stores/DatabaseConversationStore.ts
|
|
2197
2233
|
/**
|
|
2198
2234
|
* Backs a {@link ConversationStoreInterface} with one table of the `databases` layer — a
|
|
2199
|
-
* conversation's durable state
|
|
2200
|
-
*
|
|
2201
|
-
* {@link
|
|
2235
|
+
* conversation's durable state is a row holding the snapshot as one opaque JSON column, narrowed
|
|
2236
|
+
* back on `get` by {@link import('../../validators.js').isConversationSnapshot}, so persistence
|
|
2237
|
+
* reduces to keyed point-access (`get` / `set` / `delete`) over a {@link TableInterface}. The
|
|
2238
|
+
* driver-pluggable twin of the plain-`Map`
|
|
2239
|
+
* {@link import('./MemoryConversationStore.js').MemoryConversationStore}, and the exact twin of
|
|
2202
2240
|
* {@link import('@orkestrel/workspace').DatabaseWorkspaceStore}.
|
|
2203
2241
|
*
|
|
2204
2242
|
* @remarks
|
|
2205
2243
|
* The store is driver-agnostic: it holds a single {@link TableInterface} whose backend (memory,
|
|
2206
2244
|
* JSON, SQLite, IndexedDB) is chosen by whoever builds it (the factories), so a JSON / SQLite /
|
|
2207
|
-
* IndexedDB backend swaps in
|
|
2245
|
+
* IndexedDB backend swaps in without touching the
|
|
2208
2246
|
* {@link import('../ConversationManager.js').ConversationManager} or the
|
|
2209
2247
|
* {@link import('../Conversation.js').Conversation} — the same seam as
|
|
2210
2248
|
* {@link import('@orkestrel/workspace').DatabaseWorkspaceStore}. The
|
|
2211
2249
|
* driver defaults to memory ({@link import('../../factories.js').createDatabaseConversationStore}
|
|
2212
|
-
* passes `createMemoryDriver()`), so it
|
|
2250
|
+
* passes `createMemoryDriver()`), so it also works in memory out of the box; you opt into the
|
|
2213
2251
|
* durable plumbing by passing a JSON / SQLite / IndexedDB driver.
|
|
2214
2252
|
*
|
|
2215
|
-
* The {@link ConversationSnapshot} is stored as
|
|
2253
|
+
* The {@link ConversationSnapshot} is stored as one opaque JSON column — the table is a row of
|
|
2216
2254
|
* `{ id; snapshot }` ({@link ConversationSnapshotRow}), the snapshot the whole JSON blob (a
|
|
2217
2255
|
* `rawShape` column the factory builds) — exactly as `DatabaseWorkspaceStore` stores its snapshot.
|
|
2218
|
-
* The snapshot is already a
|
|
2219
|
-
* lossless
|
|
2256
|
+
* The snapshot is already a complete, self-contained, pure-JSON payload, so storing it whole is
|
|
2257
|
+
* lossless and keeps the row type flat (`snapshot` reads back as `unknown`).
|
|
2220
2258
|
*
|
|
2221
|
-
* - **`set(snapshot)` upserts under the snapshot's
|
|
2259
|
+
* - **`set(snapshot)` upserts under the snapshot's own `id`** (no separate id param) — it writes
|
|
2222
2260
|
* the row `{ id: snapshot.id, snapshot }`.
|
|
2223
2261
|
* - **`get(id)` resolves the stored snapshot for an id**, narrowing the opaque JSON column back to
|
|
2224
2262
|
* a {@link ConversationSnapshot} ({@link import('../../validators.js').isConversationSnapshot} — the
|
|
2225
2263
|
* total guard for an untrusted storage read), or `undefined` if none is stored.
|
|
2226
2264
|
* - **`delete(id)` drops a snapshot by id**; an absent id is a no-op (no throw).
|
|
2227
2265
|
*
|
|
2228
|
-
*
|
|
2229
|
-
* explicit `delete`. The public surface is
|
|
2266
|
+
* Unlike a session store there is no idle-TTL / eviction — a persisted conversation lives until an
|
|
2267
|
+
* explicit `delete`. The public surface is exactly `get` / `set` / `delete` — no extra members (the
|
|
2230
2268
|
* method bijection with {@link ConversationStoreInterface}). Hydration stays a caller concern: a
|
|
2231
2269
|
* {@link import('../ConversationManager.js').ConversationManager} reads a snapshot back and rebuilds
|
|
2232
2270
|
* the live conversation through the `snapshot` option (its `open` / `save`).
|
|
@@ -2261,7 +2299,7 @@ var DatabaseConversationStore = class {
|
|
|
2261
2299
|
if (row === void 0) return void 0;
|
|
2262
2300
|
return isConversationSnapshot(row.snapshot) ? row.snapshot : void 0;
|
|
2263
2301
|
}
|
|
2264
|
-
/** Inserts or replaces under the snapshot's
|
|
2302
|
+
/** Inserts or replaces under the snapshot's own `id` (no separate id param) — the row is `{ id, snapshot }`. */
|
|
2265
2303
|
async set(snapshot) {
|
|
2266
2304
|
await this.#table.set({
|
|
2267
2305
|
id: snapshot.id,
|
|
@@ -2277,26 +2315,27 @@ var DatabaseConversationStore = class {
|
|
|
2277
2315
|
//#region src/core/conversations/stores/MemoryConversationStore.ts
|
|
2278
2316
|
/**
|
|
2279
2317
|
* Implements the {@link ConversationStoreInterface} in memory — a process-lifetime `Map` of
|
|
2280
|
-
* {@link ConversationSnapshot}s keyed by conversation id, the
|
|
2281
|
-
* {@link import('../../factories.js').createMemoryConversationStore} builds
|
|
2318
|
+
* {@link ConversationSnapshot}s keyed by conversation id, the default store
|
|
2319
|
+
* {@link import('../../factories.js').createMemoryConversationStore} builds and the default
|
|
2320
|
+
* backing for `open` / `save`. The exact twin of
|
|
2282
2321
|
* {@link import('@orkestrel/workspace').MemoryWorkspaceStore}.
|
|
2283
2322
|
*
|
|
2284
2323
|
* @remarks
|
|
2285
2324
|
* A plain `Map<string, ConversationSnapshot>` — the snapshot is already pure,
|
|
2286
2325
|
* self-contained JSON, so no encoding is needed for the memory tier. Like the
|
|
2287
2326
|
* {@link import('@orkestrel/workspace').MemoryWorkspaceStore} it twins,
|
|
2288
|
-
* there is
|
|
2289
|
-
* durable backend (JSON / SQLite / IndexedDB) swaps in through the
|
|
2327
|
+
* there is no idle-TTL and no eviction: a persisted conversation lives until an explicit `delete`. A
|
|
2328
|
+
* durable backend (JSON / SQLite / IndexedDB) swaps in through the same interface without touching
|
|
2290
2329
|
* the {@link import('../ConversationManager.js').ConversationManager} or the
|
|
2291
2330
|
* {@link import('../Conversation.js').Conversation} — its driver-pluggable twin is
|
|
2292
2331
|
* {@link import('./DatabaseConversationStore.js').DatabaseConversationStore} (the snapshot as one
|
|
2293
2332
|
* opaque JSON column).
|
|
2294
2333
|
*
|
|
2295
2334
|
* - **`get` resolves the persisted snapshot for an id**, or `undefined` if none is stored.
|
|
2296
|
-
* - **`set` inserts / replaces under the snapshot's
|
|
2335
|
+
* - **`set` inserts / replaces under the snapshot's own `id`** (no separate id param).
|
|
2297
2336
|
* - **`delete` drops a snapshot by id**; an absent id is a no-op (no throw).
|
|
2298
2337
|
*
|
|
2299
|
-
* The public surface is
|
|
2338
|
+
* The public surface is exactly `get` / `set` / `delete` — no extra members (the method
|
|
2300
2339
|
* bijection with {@link ConversationStoreInterface}). Hydration is a caller concern: a
|
|
2301
2340
|
* {@link import('../ConversationManager.js').ConversationManager} reads a snapshot back and rebuilds
|
|
2302
2341
|
* the live conversation through the `snapshot` option (its `open` / `save`).
|
|
@@ -2315,13 +2354,32 @@ var DatabaseConversationStore = class {
|
|
|
2315
2354
|
*/
|
|
2316
2355
|
var MemoryConversationStore = class {
|
|
2317
2356
|
#snapshots = /* @__PURE__ */ new Map();
|
|
2357
|
+
/**
|
|
2358
|
+
* Resolves the persisted snapshot for `id`, or `undefined` if none is stored.
|
|
2359
|
+
*
|
|
2360
|
+
* @param id - The conversation id to resolve (a {@link ConversationSnapshot.id})
|
|
2361
|
+
* @returns The persisted snapshot, or `undefined` if absent
|
|
2362
|
+
*/
|
|
2318
2363
|
get(id) {
|
|
2319
2364
|
return Promise.resolve(this.#snapshots.get(id));
|
|
2320
2365
|
}
|
|
2366
|
+
/**
|
|
2367
|
+
* Inserts or replaces a snapshot under its own `snapshot.id` (no separate id param —
|
|
2368
|
+
* mirroring {@link import('@orkestrel/workspace').WorkspaceStoreInterface}'s `set`).
|
|
2369
|
+
*
|
|
2370
|
+
* @param snapshot - The snapshot to store (keyed by its `id`)
|
|
2371
|
+
* @returns A promise that resolves after the snapshot is stored
|
|
2372
|
+
*/
|
|
2321
2373
|
set(snapshot) {
|
|
2322
2374
|
this.#snapshots.set(snapshot.id, snapshot);
|
|
2323
2375
|
return Promise.resolve();
|
|
2324
2376
|
}
|
|
2377
|
+
/**
|
|
2378
|
+
* Drops a snapshot by id; an absent id is a no-op (no throw).
|
|
2379
|
+
*
|
|
2380
|
+
* @param id - The conversation id to drop
|
|
2381
|
+
* @returns A promise that resolves after the snapshot is dropped
|
|
2382
|
+
*/
|
|
2325
2383
|
delete(id) {
|
|
2326
2384
|
this.#snapshots.delete(id);
|
|
2327
2385
|
return Promise.resolve();
|
|
@@ -2330,23 +2388,25 @@ var MemoryConversationStore = class {
|
|
|
2330
2388
|
//#endregion
|
|
2331
2389
|
//#region src/core/scopes/Scope.ts
|
|
2332
2390
|
/**
|
|
2333
|
-
* Represents a named, immutable filter over a richer context's items — an optional
|
|
2334
|
-
*
|
|
2335
|
-
*
|
|
2391
|
+
* Represents a named, immutable filter over a richer context's items — an optional allow-list per
|
|
2392
|
+
* category (`instructions` / `tools` / `files`), each keyed by that category's identity (an
|
|
2393
|
+
* instruction's `name`, a tool's `name`, a workspace file's `path`) and read as an allow-list:
|
|
2394
|
+
* `undefined` lets everything pass, `[]` lets nothing pass, and a non-empty list passes the listed
|
|
2395
|
+
* keys alone. `narrow` composes a tighter child by set intersection.
|
|
2336
2396
|
*
|
|
2337
2397
|
* @remarks
|
|
2338
|
-
* - **A category list is three-way.** `undefined` ⇒
|
|
2339
|
-
* pass); `[]` ⇒
|
|
2398
|
+
* - **A category list is three-way.** `undefined` ⇒ no constraint on that category (all
|
|
2399
|
+
* pass); `[]` ⇒ none pass; a non-empty list ⇒ only the listed keys pass. The build
|
|
2340
2400
|
* step / loop apply this through `filterAllowList`.
|
|
2341
|
-
* - **Immutable.** The `id` is minted at construction; every supplied list is
|
|
2401
|
+
* - **Immutable.** The `id` is minted at construction; every supplied list is copied in
|
|
2342
2402
|
* (so a later mutation of the caller's array can't leak in), and the lists are
|
|
2343
|
-
* `readonly`. A `Scope` is never mutated after construction — `narrow` returns a
|
|
2403
|
+
* `readonly`. A `Scope` is never mutated after construction — `narrow` returns a new
|
|
2344
2404
|
* one rather than altering this one.
|
|
2345
|
-
* - **`narrow` is set-
|
|
2346
|
-
* per category is the intersection of
|
|
2405
|
+
* - **`narrow` is set-intersection (immutable composition).** A child scope's visible set
|
|
2406
|
+
* per category is the intersection of this scope's list and the config's list — but
|
|
2347
2407
|
* `undefined` means "no constraint", so it acts as the universal set: intersecting
|
|
2348
2408
|
* `undefined` with a list yields the list, and `undefined` with `undefined` stays
|
|
2349
|
-
* `undefined`. Narrowing can only
|
|
2409
|
+
* `undefined`. Narrowing can only tighten, never widen — a key excluded by a parent
|
|
2350
2410
|
* can never be re-admitted by a child.
|
|
2351
2411
|
*
|
|
2352
2412
|
* @example
|
|
@@ -2386,8 +2446,9 @@ var Scope = class Scope {
|
|
|
2386
2446
|
//#endregion
|
|
2387
2447
|
//#region src/core/scopes/ScopeManager.ts
|
|
2388
2448
|
/**
|
|
2389
|
-
* Registers the named filters a richer context reuses — immutable {@link Scope}s keyed by
|
|
2390
|
-
*
|
|
2449
|
+
* Registers the named filters a richer context reuses — immutable {@link Scope}s keyed by their
|
|
2450
|
+
* minted `id`, in insertion order, where `create` always mints and stores rather than overwriting,
|
|
2451
|
+
* and an observable `emitter` reports each change.
|
|
2391
2452
|
*
|
|
2392
2453
|
* @remarks
|
|
2393
2454
|
* - **Registry.** Scopes live in an insertion-ordered `Map` keyed by their minted `id`;
|
|
@@ -2396,11 +2457,11 @@ var Scope = class Scope {
|
|
|
2396
2457
|
* one up, and `scopes()` lists them in insertion order. (Unlike the name-keyed
|
|
2397
2458
|
* instruction registry, a scope's key is its minted `id`, so two scopes may share a
|
|
2398
2459
|
* `name`; `create` therefore always adds — it never overwrites.)
|
|
2399
|
-
* - **Removal.** `remove` drops one by id, or a batch — `true` only when
|
|
2460
|
+
* - **Removal.** `remove` drops one by id, or a batch — `true` only when every supplied id
|
|
2400
2461
|
* was removed; `clear` empties the registry.
|
|
2401
2462
|
* - **Observable.** The owned {@link emitter} ({@link ScopeManagerEventMap}) carries
|
|
2402
2463
|
* `create` (the created scope) / `remove` (the id) / `clear`. Every event is emitted
|
|
2403
|
-
* directly, strictly
|
|
2464
|
+
* directly, strictly after the map mutation completes; the emitter isolates a listener
|
|
2404
2465
|
* throw and routes it to its `error` handler (the `error` option), so a buggy observer can
|
|
2405
2466
|
* never corrupt a mutation.
|
|
2406
2467
|
*
|
|
@@ -2460,24 +2521,25 @@ var ScopeManager = class {
|
|
|
2460
2521
|
//#endregion
|
|
2461
2522
|
//#region src/core/ThinkSplitter.ts
|
|
2462
2523
|
/**
|
|
2463
|
-
* Feeds raw content deltas through a tiny stream-stateful state machine that routes
|
|
2464
|
-
*
|
|
2465
|
-
*
|
|
2466
|
-
* {@link import('./types.js').ProviderResult.thinking}.
|
|
2524
|
+
* Feeds raw content deltas through a tiny stream-stateful state machine that routes everything
|
|
2525
|
+
* inside a `<think>…</think>` span to `thinking` and returns everything outside it as clean
|
|
2526
|
+
* content, so a provider yields the answer alone and surfaces the reasoning as
|
|
2527
|
+
* {@link import('./types.js').ProviderResult.thinking}. A tag split across deltas is held until
|
|
2528
|
+
* disambiguated, `flush()` settles the stream end, and one splitter serves one stream.
|
|
2467
2529
|
*
|
|
2468
2530
|
* @remarks
|
|
2469
2531
|
* - **Cross-chunk tags.** A tag may arrive split across wire deltas (`'<thi'` then
|
|
2470
|
-
* `'nk>'`): any suffix of the pending text that is a strict
|
|
2471
|
-
* scanned for is
|
|
2532
|
+
* `'nk>'`): any suffix of the pending text that is a strict prefix of a tag being
|
|
2533
|
+
* scanned for is held back (neither surfaced nor routed) until the next delta — or
|
|
2472
2534
|
* `flush()` — disambiguates it. A held tag prefix that never completes is real
|
|
2473
2535
|
* content; a held close-tag prefix inside a span is thinking.
|
|
2474
|
-
* - **The
|
|
2475
|
-
*
|
|
2476
|
-
*
|
|
2477
|
-
* close therefore
|
|
2536
|
+
* - **The implicit leading open (the qwen3-template shape).** Some chat templates
|
|
2537
|
+
* pre-seed `<think>` into the prompt scaffold, so the wire stream begins
|
|
2538
|
+
* mid-reasoning and only a bare `</think>` appears. Before any tag event, a bare
|
|
2539
|
+
* close therefore reclassifies everything surfaced so far (plus the pre-close
|
|
2478
2540
|
* pending) as thinking — `content` is corrected retroactively (the already-returned
|
|
2479
2541
|
* prefix cannot be recalled, so `content` is the authoritative accumulation). The
|
|
2480
|
-
* rule is
|
|
2542
|
+
* rule is one-shot: after any tag event a bare `</think>` is plain text.
|
|
2481
2543
|
* - **Multiple spans** accumulate onto `thinking` in stream order. A nested-looking
|
|
2482
2544
|
* `<think>` inside an open span is thinking text (no nesting is tracked — the
|
|
2483
2545
|
* first `</think>` closes the span), matching how the models emit it.
|
|
@@ -2579,16 +2641,16 @@ var ThinkSplitter = class {
|
|
|
2579
2641
|
//#region src/core/factories.ts
|
|
2580
2642
|
/**
|
|
2581
2643
|
* Creates a conversation — a {@link ConversationInterface} grouping messages above a flat
|
|
2582
|
-
* message store it
|
|
2644
|
+
* message store it owns directly, with compaction into summarized sections, a regenerated
|
|
2583
2645
|
* rollup `summary`, on-demand `rehydrate`, and substring `search`, driven by a
|
|
2584
2646
|
* provider-agnostic {@link ConversationSummaryHandler} seam.
|
|
2585
2647
|
*
|
|
2586
2648
|
* @remarks
|
|
2587
2649
|
* Append turns through the conversation's own `add` (the live tail it owns); `view()` is the model input
|
|
2588
2650
|
* (each section as a summary message, then the live tail). `compact()` folds the older live
|
|
2589
|
-
* messages into a summarized {@link Section} and regenerates the rollup — it
|
|
2651
|
+
* messages into a summarized {@link Section} and regenerates the rollup — it requires
|
|
2590
2652
|
* a `summarize` (omitted ⇒ `compact()` throws a `ConversationError`); `keep` retains a recent
|
|
2591
|
-
* tail (default `DEFAULT_CONVERSATION_KEEP` — fold
|
|
2653
|
+
* tail (default `DEFAULT_CONVERSATION_KEEP` — fold all). `rehydrate(id)` / `search(query)` read
|
|
2592
2654
|
* the retained originals. Observable (`emitter` — `compact` / `summary` / `rehydrate`), wired
|
|
2593
2655
|
* through the reserved `on` option; the emitter isolates a listener throw and routes it to
|
|
2594
2656
|
* its `error` handler (the `error` option), so it can never corrupt a compaction.
|
|
@@ -2596,20 +2658,39 @@ var ThinkSplitter = class {
|
|
|
2596
2658
|
* @param options - Optional `id` / `on` hooks + the `summarize` seam + `keep` (see {@link ConversationOptions})
|
|
2597
2659
|
* @returns A working {@link ConversationInterface}
|
|
2598
2660
|
*
|
|
2599
|
-
* @example
|
|
2661
|
+
* @example Conversations & compaction
|
|
2600
2662
|
* ```ts
|
|
2601
|
-
* import type { ProviderInterface } from '@orkestrel/agent'
|
|
2602
2663
|
* import { createConversation } from '@orkestrel/agent'
|
|
2664
|
+
* import type { ProviderInterface } from '@orkestrel/agent'
|
|
2603
2665
|
*
|
|
2604
2666
|
* declare const provider: ProviderInterface // any concrete implementation supplied by the host app
|
|
2667
|
+
* // The summarizer seam — built from the provider by the runtime; core stays provider-agnostic.
|
|
2668
|
+
* // Append the instruction as the FINAL user turn: a chat model emits nothing when the prompt
|
|
2669
|
+
* // ends on an assistant turn, so a leading-system instruction is unreliable.
|
|
2605
2670
|
* const conversation = createConversation({
|
|
2606
|
-
* // Append the instruction as the FINAL user turn — a chat model emits nothing when the
|
|
2607
|
-
* // prompt ends on an assistant turn, so a leading-system instruction is unreliable.
|
|
2608
2671
|
* summarize: async (messages) =>
|
|
2609
|
-
* (
|
|
2672
|
+
* (
|
|
2673
|
+
* await provider.generate(
|
|
2674
|
+
* [
|
|
2675
|
+
* ...messages,
|
|
2676
|
+
* { id: 's', role: 'user', content: 'Summarize the conversation so far concisely.' },
|
|
2677
|
+
* ],
|
|
2678
|
+
* AbortSignal.timeout(30_000),
|
|
2679
|
+
* )
|
|
2680
|
+
* ).content,
|
|
2681
|
+
* keep: 2, // retain the two most recent turns verbatim on each compaction
|
|
2610
2682
|
* })
|
|
2611
|
-
* conversation.add(
|
|
2612
|
-
*
|
|
2683
|
+
* conversation.add([
|
|
2684
|
+
* { role: 'user', content: 'My name is Ada.' },
|
|
2685
|
+
* { role: 'assistant', content: 'Nice to meet you, Ada.' },
|
|
2686
|
+
* { role: 'user', content: 'What did I say my name was?' },
|
|
2687
|
+
* ])
|
|
2688
|
+
*
|
|
2689
|
+
* const section = await conversation.compact() // folds the older turns → a summarized section
|
|
2690
|
+
* conversation.view() // [<section summary message>, ...the retained recent tail] — the model input
|
|
2691
|
+
* conversation.summary // the regenerated rollup (a summary-of-summaries over all sections)
|
|
2692
|
+
* conversation.search('ada') // case-insensitive across sections' originals + the live tail
|
|
2693
|
+
* section && conversation.rehydrate(section.id) // the section's full original messages (a pure read)
|
|
2613
2694
|
* ```
|
|
2614
2695
|
*/
|
|
2615
2696
|
function createConversation(options) {
|
|
@@ -2617,21 +2698,22 @@ function createConversation(options) {
|
|
|
2617
2698
|
}
|
|
2618
2699
|
/**
|
|
2619
2700
|
* Creates a conversation registry — a {@link ConversationManagerInterface} holding
|
|
2620
|
-
* {@link ConversationInterface}s keyed by their `id
|
|
2701
|
+
* {@link ConversationInterface}s keyed by their `id`, in insertion order, with an active pointer:
|
|
2621
2702
|
* the id-keyed store over the conversation layer plus the `active` / `switch` seam the context
|
|
2622
|
-
* renders.
|
|
2703
|
+
* renders. `add` auto-activates the first conversation and flows the registry's default
|
|
2704
|
+
* `summarize` / `keep` into every conversation it creates.
|
|
2623
2705
|
*
|
|
2624
2706
|
* @remarks
|
|
2625
2707
|
* Starts empty; `add(input?)` mints a {@link ConversationInterface} (its `id` from the input
|
|
2626
2708
|
* or a random UUID), flowing the manager's default `summarize` / `keep` in unless the input
|
|
2627
2709
|
* overrides them, and stores it (an already-present `id` overwrites — last write wins) — and
|
|
2628
|
-
*
|
|
2710
|
+
* auto-activates the first one (a registry with conversations always has one `active`); a later
|
|
2629
2711
|
* `add` leaves `active` unchanged. `switch(id)` re-points `active` (an unknown `id` returns
|
|
2630
2712
|
* `undefined`, leaving `active` unchanged — lenient, never throws); `conversation(id)` /
|
|
2631
2713
|
* `conversations()` look up; `remove` (one or a batch) reports `true` only when every supplied id
|
|
2632
|
-
* was removed
|
|
2714
|
+
* was removed and clears `active` if it was a removed one; `clear` empties it and clears `active`.
|
|
2633
2715
|
* Event-free
|
|
2634
|
-
* (each conversation owns its own observable `emitter`). A conversation created with
|
|
2716
|
+
* (each conversation owns its own observable `emitter`). A conversation created with neither a
|
|
2635
2717
|
* manager default nor a per-`add` `summarize` cannot `compact` (it throws a `ConversationError`).
|
|
2636
2718
|
*
|
|
2637
2719
|
* @param options - Optional default `summarize` / `keep` (see {@link ConversationManagerOptions})
|
|
@@ -2652,7 +2734,7 @@ function createConversationManager(options) {
|
|
|
2652
2734
|
/**
|
|
2653
2735
|
* Creates the in-memory conversation store — a {@link ConversationStoreInterface} backed by a
|
|
2654
2736
|
* process-lifetime `Map` of {@link import('./types.js').ConversationSnapshot}s keyed by conversation
|
|
2655
|
-
* id, the
|
|
2737
|
+
* id, the default backing for the durable {@link ConversationManagerInterface.open} /
|
|
2656
2738
|
* {@link ConversationManagerInterface.save} seam. The exact twin of
|
|
2657
2739
|
* {@link import('@orkestrel/workspace').createMemoryWorkspaceStore}.
|
|
2658
2740
|
*
|
|
@@ -2660,10 +2742,10 @@ function createConversationManager(options) {
|
|
|
2660
2742
|
* A plain `Map` (the snapshot is already pure JSON, so no encoding is needed for the memory tier),
|
|
2661
2743
|
* the structural twin of {@link import('@orkestrel/workspace').createMemoryWorkspaceStore}.
|
|
2662
2744
|
* `get` / `set` / `delete` are async (the
|
|
2663
|
-
* same shape a durable backend fits);
|
|
2745
|
+
* same shape a durable backend fits); unlike a session store there is no idle-TTL / eviction — a
|
|
2664
2746
|
* persisted conversation lives until an explicit `delete`. Its driver-pluggable twin is
|
|
2665
2747
|
* {@link createDatabaseConversationStore} (the snapshot as one opaque JSON column over a `databases`
|
|
2666
|
-
* table) — for a
|
|
2748
|
+
* table) — for a durable store pass it a JSON / SQLite / IndexedDB driver, and it swaps in without
|
|
2667
2749
|
* touching the manager or the conversation. Hydration stays a manager concern: read a snapshot back
|
|
2668
2750
|
* and rebuild the live conversation through the `snapshot` option (re-supplying the live
|
|
2669
2751
|
* `summarize` / `keep`).
|
|
@@ -2685,23 +2767,24 @@ function createMemoryConversationStore() {
|
|
|
2685
2767
|
return new MemoryConversationStore();
|
|
2686
2768
|
}
|
|
2687
2769
|
/**
|
|
2688
|
-
* Creates a {@link DatabaseConversationStore} over any {@link DriverInterface}
|
|
2689
|
-
* driver-pluggable backing for the conversation persistence
|
|
2770
|
+
* Creates a {@link DatabaseConversationStore} over any {@link DriverInterface}, defaulting to
|
|
2771
|
+
* `createMemoryDriver()` — the durable, driver-pluggable backing for the conversation persistence
|
|
2772
|
+
* seam, holding each snapshot as one opaque JSON column and standing as the opt-in twin of
|
|
2690
2773
|
* {@link createMemoryConversationStore}. The exact twin of
|
|
2691
2774
|
* {@link import('@orkestrel/workspace').createDatabaseWorkspaceStore}.
|
|
2692
2775
|
*
|
|
2693
2776
|
* @remarks
|
|
2694
2777
|
* Builds a one-table database (`conversations`, keyed by `id`) over the supplied driver, the snapshot
|
|
2695
|
-
* held as
|
|
2778
|
+
* held as one opaque JSON column — the column map is `{ id; snapshot }` where `snapshot` is a
|
|
2696
2779
|
* `rawShape` (a JSON blob), exactly as
|
|
2697
2780
|
* {@link import('@orkestrel/workspace').createDatabaseWorkspaceStore} stores its snapshot. The
|
|
2698
|
-
* snapshot is already a
|
|
2699
|
-
*
|
|
2700
|
-
* {@link import('./validators.js').isConversationSnapshot}). The `driver`
|
|
2701
|
-
* {@link createMemoryDriver}, so the store
|
|
2781
|
+
* snapshot is already a complete, self-contained, pure-JSON payload, so storing it whole is lossless
|
|
2782
|
+
* and keeps the row type flat (the column reads back as `unknown`, narrowed on `get` by
|
|
2783
|
+
* {@link import('./validators.js').isConversationSnapshot}). The `driver` defaults to
|
|
2784
|
+
* {@link createMemoryDriver}, so the store also works in memory out of the box; pass a server
|
|
2702
2785
|
* `createJSONDriver` / `createSQLiteDriver` (or a browser IndexedDB driver) for a persistent one —
|
|
2703
2786
|
* the durability is the driver's job, the store engine is shared. It swaps in behind
|
|
2704
|
-
* {@link ConversationStoreInterface}
|
|
2787
|
+
* {@link ConversationStoreInterface} without touching the manager or the conversation.
|
|
2705
2788
|
*
|
|
2706
2789
|
* @param driver - The storage backend the snapshots persist to (defaults to {@link createMemoryDriver})
|
|
2707
2790
|
* @returns A {@link ConversationStoreInterface} over the driver
|
|
@@ -2756,7 +2839,7 @@ function createInstruction(input) {
|
|
|
2756
2839
|
* immutable instructions keyed by `name`, listed by descending `priority`.
|
|
2757
2840
|
*
|
|
2758
2841
|
* @remarks
|
|
2759
|
-
* Starts empty; `add` (one or a batch)
|
|
2842
|
+
* Starts empty; `add` (one or a batch) mints each `id` and overwrites a same-name
|
|
2760
2843
|
* instruction (last write wins); `instructions()` lists them sorted by descending
|
|
2761
2844
|
* `priority` (stable for ties); `open` / `render` are the build contract a richer
|
|
2762
2845
|
* context renders an instructions block with; `remove` (one or a batch) reports `true` only
|
|
@@ -2787,9 +2870,9 @@ function createInstructionManager(options) {
|
|
|
2787
2870
|
* per-category allow-lists, the `id` minted at construction.
|
|
2788
2871
|
*
|
|
2789
2872
|
* @remarks
|
|
2790
|
-
* Each list is
|
|
2791
|
-
*
|
|
2792
|
-
* tighter child by set-
|
|
2873
|
+
* Each list is three-way: `undefined` ⇒ no constraint on that category (all pass), `[]` ⇒
|
|
2874
|
+
* none pass, a non-empty list ⇒ only the listed keys pass. `narrow(config)` composes a
|
|
2875
|
+
* tighter child by set-intersection (an `undefined` side imposing no constraint). Stored
|
|
2793
2876
|
* immutable — never mutated after creation (`narrow` returns a new scope).
|
|
2794
2877
|
*
|
|
2795
2878
|
* @param input - `name` (required) and the optional `instructions` / `tools` / `files`
|
|
@@ -2835,19 +2918,20 @@ function createScopeManager(options) {
|
|
|
2835
2918
|
return new ScopeManager(options);
|
|
2836
2919
|
}
|
|
2837
2920
|
/**
|
|
2838
|
-
* Creates a richer turn context — an {@link AgentContextInterface} assembling a provider
|
|
2839
|
-
*
|
|
2840
|
-
* the conversation
|
|
2921
|
+
* Creates a richer turn context — an {@link AgentContextInterface} assembling a provider request
|
|
2922
|
+
* from the optional system prompt, the instruction registry, the workspace registry (the only
|
|
2923
|
+
* document channel), the conversation registry that is its `messages` source, the tool registry,
|
|
2924
|
+
* and the active scope, which `build()` folds into the next turn's input.
|
|
2841
2925
|
*
|
|
2842
2926
|
* @remarks
|
|
2843
2927
|
* `system` is the optional system prompt; `tools` / `instructions` / `workspaces` are pre-built
|
|
2844
|
-
* managers to reuse (empty ones are created when omitted, so `context.workspaces` is
|
|
2928
|
+
* managers to reuse (empty ones are created when omitted, so `context.workspaces` is always
|
|
2845
2929
|
* present); `scope` is the initial active filter (`undefined` ⇒ no filtering, changeable afterwards
|
|
2846
2930
|
* through `context.apply(...)`). The `messages` store is always fresh. `build()` folds the scoped
|
|
2847
|
-
* instructions —
|
|
2931
|
+
* instructions — plus the active workspace's scope-filtered text files (fenced) — into one leading
|
|
2848
2932
|
* `system` message and appends the scoped conversation (attaching the active workspace's
|
|
2849
2933
|
* scope-filtered image files' `base64` payload to the last user message), built fresh each call; the active
|
|
2850
|
-
* workspace is the
|
|
2934
|
+
* workspace is the sole document/image context. Tools are advertised structurally (through
|
|
2851
2935
|
* `tools.definitions()`, scope-filtered by the loop), never serialized into the prompt.
|
|
2852
2936
|
*
|
|
2853
2937
|
* @param options - Optional `system` / `tools` / `instructions` / `workspaces` / `scope`
|
|
@@ -2874,10 +2958,10 @@ function createAgentContext(options) {
|
|
|
2874
2958
|
* `generate` and a live `stream`.
|
|
2875
2959
|
*
|
|
2876
2960
|
* @remarks
|
|
2877
|
-
* One private loop drives the turn; `generate`
|
|
2961
|
+
* One private loop drives the turn; `generate` drains the same stream `stream`
|
|
2878
2962
|
* exposes, so they can never diverge. Each turn is bounded by one cancel folded from
|
|
2879
2963
|
* `signal` + `timeout` + `budget` (through `AbortSignal.any`) — any trip (or `abort()`)
|
|
2880
|
-
* commits a
|
|
2964
|
+
* commits a partial result (the stream's `result` resolves on a cancel, rejects only
|
|
2881
2965
|
* on a genuine provider / tool error). The `scheduler` paces between turns; tool
|
|
2882
2966
|
* iteration is capped at `limit` (default `DEFAULT_AGENT_LIMIT`). Tools are advertised
|
|
2883
2967
|
* structurally through `context.tools.definitions()`. Two observation surfaces: the
|
|
@@ -2913,18 +2997,19 @@ function createAgent(provider, options) {
|
|
|
2913
2997
|
return new Agent(provider, options);
|
|
2914
2998
|
}
|
|
2915
2999
|
/**
|
|
2916
|
-
* Creates a stream-stateful `<think>` separator — a {@link ThinkSplitterInterface} that
|
|
2917
|
-
* splits a thinking model's in-content `<think>…</think>` reasoning spans away from
|
|
2918
|
-
*
|
|
2919
|
-
*
|
|
3000
|
+
* Creates a fresh stream-stateful `<think>` separator — a {@link ThinkSplitterInterface} that
|
|
3001
|
+
* splits a thinking model's in-content `<think>…</think>` reasoning spans away from the answer,
|
|
3002
|
+
* delta by delta, so a provider yields clean content alone and surfaces the accumulated
|
|
3003
|
+
* reasoning as {@link import('./types.js').ProviderResult.thinking}. One splitter serves one
|
|
3004
|
+
* stream.
|
|
2920
3005
|
*
|
|
2921
3006
|
* @remarks
|
|
2922
3007
|
* Feed each raw wire delta through `split(delta)` (it returns the clean content to
|
|
2923
3008
|
* surface — possibly `''` mid-think) and settle the stream end with `flush()` (a held
|
|
2924
|
-
* partial open tag that never completed returns as final content; an
|
|
2925
|
-
* span lands on `thinking`). Tags split
|
|
3009
|
+
* partial open tag that never completed returns as final content; an unclosed think
|
|
3010
|
+
* span lands on `thinking`). Tags split across deltas are held back until
|
|
2926
3011
|
* disambiguated, multiple spans accumulate in order, and a nested-looking `<think>`
|
|
2927
|
-
* inside an open span is thinking text. One splitter serves
|
|
3012
|
+
* inside an open span is thinking text. One splitter serves one stream — create
|
|
2928
3013
|
* a fresh one per provider call.
|
|
2929
3014
|
*
|
|
2930
3015
|
* @returns A fresh {@link ThinkSplitterInterface} (state empty, outside any span)
|
|
@@ -2951,7 +3036,7 @@ function createThinkSplitter() {
|
|
|
2951
3036
|
* Write and read are decoupled, so the producer never waits for a consumer: an agent's eager
|
|
2952
3037
|
* pump writes each chunk into one, which is why a run's `result` settles whether or not the
|
|
2953
3038
|
* live events are drained. A value pushed at an already-parked consumer is delivered, buffered
|
|
2954
|
-
* values are yielded before the end is reported, and the
|
|
3039
|
+
* values are yielded before the end is reported, and the first failure wins.
|
|
2955
3040
|
*
|
|
2956
3041
|
* @typeParam T - The value type the channel carries
|
|
2957
3042
|
* @returns A fresh, empty {@link ChannelInterface}
|
|
@@ -2977,11 +3062,11 @@ function createChannel() {
|
|
|
2977
3062
|
* to the configured default when none match.
|
|
2978
3063
|
*
|
|
2979
3064
|
* @remarks
|
|
2980
|
-
* `rules` are evaluated in order — the
|
|
2981
|
-
* rule
|
|
3065
|
+
* `rules` are evaluated in order — the first whose `match` is true decides (a matched
|
|
3066
|
+
* rule allows unless its `allowed` is explicitly `false`). When no rule matches, the
|
|
2982
3067
|
* `fallback` decides; it defaults to `{ zone: DEFAULT_AUTHORITY_ZONE, allowed: true }`
|
|
2983
|
-
* (allow-unmatched — a rules list of denials acts as a
|
|
2984
|
-
* `allowed: false` `fallback` to flip the gate to deny-by-default (an
|
|
3068
|
+
* (allow-unmatched — a rules list of denials acts as a denylist). Pass an
|
|
3069
|
+
* `allowed: false` `fallback` to flip the gate to deny-by-default (an allowlist). Wire
|
|
2985
3070
|
* the result into `createAgent` through `AgentOptions.authority`: a denied call is fed back
|
|
2986
3071
|
* to the model as a denial `ToolResult` (not executed, no budget cost), so the model
|
|
2987
3072
|
* can react. Synchronous — `evaluate` returns the verdict directly.
|
|
@@ -3011,7 +3096,7 @@ function createAuthority(options) {
|
|
|
3011
3096
|
*
|
|
3012
3097
|
* @remarks
|
|
3013
3098
|
* `providers` is required; `tools` / `authorities` / `schedulers` are optional pools.
|
|
3014
|
-
* The accessors (`provider` / `tool` / `authority` / `scheduler`)
|
|
3099
|
+
* The accessors (`provider` / `tool` / `authority` / `scheduler`) throw an
|
|
3015
3100
|
* {@link import('./errors.js').AgentError} carrying `code: 'REGISTRY'` and the message
|
|
3016
3101
|
* `unknown <category>: <name>` on an unregistered name — a misconfigured or crash-restored
|
|
3017
3102
|
* job fails loudly rather than running with a missing dependency. `build(input, signal)`
|
|
@@ -3041,7 +3126,7 @@ function createAgentRegistry(options) {
|
|
|
3041
3126
|
}
|
|
3042
3127
|
/**
|
|
3043
3128
|
* Creates a durable, bounded-concurrency agent-job queue — a {@link QueueInterface} over
|
|
3044
|
-
* serializable {@link AgentJobInput}s that
|
|
3129
|
+
* serializable {@link AgentJobInput}s that composes `createQueue`: each job is rehydrated
|
|
3045
3130
|
* through the `registry` into a live {@link AgentInterface}, run to its {@link AgentResult},
|
|
3046
3131
|
* and subjected to the partial-as-configurable-failure policy.
|
|
3047
3132
|
*
|
|
@@ -3054,9 +3139,9 @@ function createAgentRegistry(options) {
|
|
|
3054
3139
|
* (for example `createMemoryQueueStore` / `createDatabaseQueueStore`) persists outstanding
|
|
3055
3140
|
* jobs; `restore()` re-enqueues them after a restart and the `registry` rehydrates the
|
|
3056
3141
|
* live pieces from the names — so a job survives a crash.
|
|
3057
|
-
* - **Partial policy.** A partial result
|
|
3142
|
+
* - **Partial policy.** A partial result throws an
|
|
3058
3143
|
* {@link import('./errors.js').AgentJobError} by default, so a job cancelled by its
|
|
3059
|
-
* attempt deadline / a queue abort
|
|
3144
|
+
* attempt deadline / a queue abort retries while attempts remain; `partial: true`
|
|
3060
3145
|
* resolves the partial as success instead.
|
|
3061
3146
|
* - **Cancellation threads through.** The handler passes `context.signal` into
|
|
3062
3147
|
* `registry.build`, so a queue `abort()` or a per-attempt timeout cancels the in-flight
|
|
@@ -3089,21 +3174,21 @@ function createAgentQueue(options) {
|
|
|
3089
3174
|
}
|
|
3090
3175
|
/**
|
|
3091
3176
|
* Creates an agent-job runner — a {@link RunnerInterface} over serializable
|
|
3092
|
-
* {@link AgentJobInput}s that
|
|
3093
|
-
*
|
|
3094
|
-
*
|
|
3177
|
+
* {@link AgentJobInput}s that composes `createRunner` (one-shot, ordered, fail-fast), each unit
|
|
3178
|
+
* rehydrated through the `registry` and subjected to the partial policy. The runner also carries
|
|
3179
|
+
* sub-agent fan-out: a parent job's handler can `controller.spawn(childJob)`.
|
|
3095
3180
|
*
|
|
3096
3181
|
* @remarks
|
|
3097
3182
|
* - **Composes the substrate (no new engine).** Bounded `concurrency`, `retries`, the
|
|
3098
3183
|
* per-attempt `timeout`, ordered results, and fail-fast are all the backing Runner's;
|
|
3099
3184
|
* the handler adds only rehydration + the partial policy.
|
|
3100
3185
|
* - **Sub-agent fan-out.** Each unit's handler receives a `ControllerInterface` whose
|
|
3101
|
-
* `spawn(childJob)` launches a
|
|
3186
|
+
* `spawn(childJob)` launches a child agent job through the same bounded queue (the
|
|
3102
3187
|
* child's result joins the run after the declared units, in spawn order). On a bounded
|
|
3103
|
-
* runner,
|
|
3188
|
+
* runner, fan out and return — do not inline-`await` a spawn from within the handler (a
|
|
3104
3189
|
* slot-holding handler awaiting its own spawn can deadlock; see `ControllerInterface`).
|
|
3105
3190
|
* - **Partial policy + cancellation.** Same as `createAgentQueue`: a partial result
|
|
3106
|
-
*
|
|
3191
|
+
* throws by default (the run's fail-fast engages), `partial: true` resolves it; the
|
|
3107
3192
|
* handler threads `controller.signal` into `registry.build`, so a runner abort / a
|
|
3108
3193
|
* per-attempt timeout cancels the agent.
|
|
3109
3194
|
*
|