@robota-sdk/agent-core 3.0.0-beta.83 → 3.0.0-beta.84
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/CHANGELOG.md +47 -0
- package/README.md +154 -116
- package/dist/browser/bounded-output-D3cgiOrh.js +2 -0
- package/dist/browser/bounded-output-D3cgiOrh.js.map +1 -0
- package/dist/browser/{command-executor-cI7XJ374.js → command-executor-BsBTcghZ.js} +2 -2
- package/dist/browser/{command-executor-cI7XJ374.js.map → command-executor-BsBTcghZ.js.map} +1 -1
- package/dist/browser/index.d.ts +1206 -902
- package/dist/browser/index.d.ts.map +1 -1
- package/dist/browser/index.js +11 -11
- package/dist/browser/index.js.map +1 -1
- package/dist/node/bounded-output-D3cgiOrh.js +2 -0
- package/dist/node/bounded-output-D3cgiOrh.js.map +1 -0
- package/dist/node/{command-executor-DV-H2oD6.js → command-executor-BdE1zg3q.js} +2 -2
- package/dist/node/{command-executor-DV-H2oD6.js.map → command-executor-BdE1zg3q.js.map} +1 -1
- package/dist/node/{command-executor-Aat_q7P2.cjs → command-executor-CPGK8I8l.cjs} +1 -1
- package/dist/node/{http-executor-71JiIdFP.cjs → http-executor-BYiKn8h3.cjs} +1 -1
- package/dist/node/{index-D1_Mak-n.d.cts → index-DXZ-pyEr.d.ts} +1207 -902
- package/dist/node/index-DXZ-pyEr.d.ts.map +1 -0
- package/dist/node/{index-D4iJ5IsN.d.ts → index-gEUh4G-t.d.cts} +1207 -902
- package/dist/node/index-gEUh4G-t.d.cts.map +1 -0
- package/dist/node/index.cjs +9 -9
- package/dist/node/index.d.cts +3 -3
- package/dist/node/index.d.ts +3 -3
- package/dist/node/index.js +9 -9
- package/dist/node/index.js.map +1 -1
- package/dist/node/node.cjs +1 -1
- package/dist/node/node.d.cts +1 -1
- package/dist/node/node.d.ts +1 -1
- package/dist/node/node.js +1 -1
- package/dist/node/testing/index.d.cts +1 -1
- package/dist/node/testing/index.d.ts +1 -1
- package/dist/node/{types-gNrckgFE.d.ts → types-BQ-sKCQS.d.cts} +3 -4
- package/dist/node/types-BQ-sKCQS.d.cts.map +1 -0
- package/dist/node/{types-gNrckgFE.d.cts → types-BQ-sKCQS.d.ts} +3 -4
- package/dist/node/types-BQ-sKCQS.d.ts.map +1 -0
- package/dist/node/verdict-decoder-D412SAGd.cjs +1 -0
- package/package.json +3 -3
- package/dist/browser/bounded-output-DpjpS98g.js +0 -2
- package/dist/browser/bounded-output-DpjpS98g.js.map +0 -1
- package/dist/node/bounded-output-DpjpS98g.js +0 -2
- package/dist/node/bounded-output-DpjpS98g.js.map +0 -1
- package/dist/node/index-D1_Mak-n.d.cts.map +0 -1
- package/dist/node/index-D4iJ5IsN.d.ts.map +0 -1
- package/dist/node/types-gNrckgFE.d.cts.map +0 -1
- package/dist/node/types-gNrckgFE.d.ts.map +0 -1
- package/dist/node/verdict-decoder-Estx9MtY.cjs +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,52 @@
|
|
|
1
1
|
# @robota-sdk/agent-core
|
|
2
2
|
|
|
3
|
+
## 3.0.0-beta.84
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9f46375: A run can record its model calls and tool effects in a journal the host owns, and continue from it
|
|
8
|
+
later. Pass `executionJournal` in the run options: each model request and response, and each tool
|
|
9
|
+
call's intent, start and result, is written and awaited before the run moves on. If a write fails,
|
|
10
|
+
the run stops with an `ExecutionJournalError`, even when it is also being cancelled; the model never
|
|
11
|
+
sees it as a tool failure it could retry. A journaled run waits for a cancelled model call to
|
|
12
|
+
finish, because its late reply can still carry usage or tool calls that must be recorded.
|
|
13
|
+
|
|
14
|
+
With a journal that can also read its records back (`IRecoverableExecutionJournal`), a new `Robota`
|
|
15
|
+
for the same conversation continues where the old one stopped, without new user input and without
|
|
16
|
+
repeating calls whose results were saved:
|
|
17
|
+
|
|
18
|
+
- `Robota.resumeToolCalls()` runs the latest saved batch of tool calls without calling the model.
|
|
19
|
+
- `Robota.resume()` continues the whole execution, keeping its remaining round and repeated-input
|
|
20
|
+
limits. It refuses a different provider or model. Turn-level lifecycle hooks do not run again, and
|
|
21
|
+
structured output cannot be recovered. A deferred tool the execution had loaded that is no longer
|
|
22
|
+
registered raises `ExecutionRecoveryError`.
|
|
23
|
+
|
|
24
|
+
A tool can ask for an answer before it acts, through `continuation` on its execution context. The
|
|
25
|
+
request is saved and the run ends with `ExecutionSuspendedError`, without a completion or failure.
|
|
26
|
+
Tools already running beside it finish and save their real results; only calls not yet started wait.
|
|
27
|
+
Answering it through `toolResponses` on a later `resume()` continues the same action; an unanswered
|
|
28
|
+
request never lets the tool act. A tool call that may have started without a saved result raises
|
|
29
|
+
`ExecutionRecoveryError` and must be reconciled by the host: it is never run again automatically.
|
|
30
|
+
`isExecutionControlError` recognizes these errors. `callJournaledProvider`, with an
|
|
31
|
+
`IModelJournalContext`, journals a model call made outside a run.
|
|
32
|
+
|
|
33
|
+
### Patch Changes
|
|
34
|
+
|
|
35
|
+
- 9c6a8db: Provider adapters now throw the specific typed failure a vendor response describes, instead of a
|
|
36
|
+
generic `ProviderError` carrying only its status. A 401/403 (or a vendor-specific authentication type)
|
|
37
|
+
becomes an `AuthenticationError`; a response naming an unavailable model — by code or, when the vendor
|
|
38
|
+
gives neither (Anthropic's and Gemini's real bodies carry no distinguishing code), by its message —
|
|
39
|
+
becomes a `ModelNotAvailableError`, whose `model` is now optional since a bare HTTP status does not
|
|
40
|
+
always name one; a transport failure becomes a `NetworkError`, which now also carries the provider
|
|
41
|
+
name. A 429's `retry-after` header (a delta or an HTTP-date) is read into `RateLimitError.retryAfter`
|
|
42
|
+
when the vendor sends one.
|
|
43
|
+
|
|
44
|
+
Vendor error text is scrubbed of anything that reads like a credential — an `Authorization`/`Bearer`
|
|
45
|
+
value, an API key in a header, query string or JSON body, a bare `sk-`/`AIza…` token — before it is
|
|
46
|
+
kept on any of these typed errors, so a misconfigured self-hosted gateway that echoes a request back
|
|
47
|
+
can no longer leak one into a message or a "Details" disclosure. The new `scrubSecrets` export applies
|
|
48
|
+
the same scrubbing to arbitrary text.
|
|
49
|
+
|
|
3
50
|
## 3.0.0-beta.83
|
|
4
51
|
|
|
5
52
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @robota-sdk/agent-core
|
|
2
2
|
|
|
3
|
-
The foundation
|
|
3
|
+
The foundation of the Robota SDK. It provides the `Robota` agent class (a conversation loop with
|
|
4
|
+
tool calling), the provider, tool and plugin contracts with their abstract base classes, the
|
|
5
|
+
permission evaluator, the hook runner, event services, typed errors, model metadata, and structured
|
|
6
|
+
output. It has no `@robota-sdk/*` dependencies; every other Robota package builds on it.
|
|
4
7
|
|
|
5
8
|
## Installation
|
|
6
9
|
|
|
@@ -8,6 +11,9 @@ The foundation layer of the Robota SDK. Provides the `Robota` agent class, abstr
|
|
|
8
11
|
npm install @robota-sdk/agent-core
|
|
9
12
|
```
|
|
10
13
|
|
|
14
|
+
Requires Node.js 22.12 or later. A provider package supplies the model connection, for example
|
|
15
|
+
`@robota-sdk/agent-provider-anthropic`.
|
|
16
|
+
|
|
11
17
|
## Quick Start
|
|
12
18
|
|
|
13
19
|
```typescript
|
|
@@ -30,24 +36,34 @@ const response = await agent.run('Hello, world!');
|
|
|
30
36
|
console.log(response);
|
|
31
37
|
```
|
|
32
38
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
- **
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
- **
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
- **
|
|
47
|
-
|
|
48
|
-
- **
|
|
49
|
-
|
|
50
|
-
- **
|
|
39
|
+
`defaultModel.provider` names one of the `aiProviders` by its `name` (`'anthropic'` here).
|
|
40
|
+
|
|
41
|
+
## What it provides
|
|
42
|
+
|
|
43
|
+
- **`Robota`** — an agent with conversation history, automatic tool execution, plugins, streaming
|
|
44
|
+
(`runStream`) and structured output. By default history accumulates across runs; set
|
|
45
|
+
`retainHistory: false` to start every run from the system prompt.
|
|
46
|
+
- **Messages** — every message (`TUniversalMessage`) has a unique `id` and a `state`
|
|
47
|
+
(`'complete'` or `'interrupted'`). `ConversationStore` is the append-only history with a streaming
|
|
48
|
+
buffer.
|
|
49
|
+
- **Multiple providers** — register several providers and switch with `setModel()`. Providers extend
|
|
50
|
+
`AbstractAIProvider` and report capabilities (`getProviderCapabilities`), including whether they
|
|
51
|
+
offer hosted web search/fetch.
|
|
52
|
+
- **Tools** — `FunctionTool` and `ToolRegistry` live here, next to the `AbstractTool` base class and
|
|
53
|
+
the tool schema contract. Ready-made tools are in `@robota-sdk/agent-tools`.
|
|
54
|
+
- **Permissions** — `evaluatePermission` decides a tool call from the permission mode and
|
|
55
|
+
allow/deny/ask rules; it is the one evaluator every Robota caller uses.
|
|
56
|
+
- **Hooks** — `runHooks` runs the lifecycle hooks configured for an event. See the
|
|
57
|
+
[hook event catalog](./docs/HOOK-CATALOG.md).
|
|
58
|
+
- **Plugins** — `AbstractPlugin` with lifecycle hooks (`beforeRun`, `afterRun`, `onError`, …).
|
|
59
|
+
Ready-made plugins are in `@robota-sdk/agent-plugin`.
|
|
60
|
+
- **Events** — event services with owner-path tracking and `EventEmitterPlugin`.
|
|
61
|
+
- **Errors** — typed errors extending `RobotaError`, such as `ProviderError`, `RateLimitError`,
|
|
62
|
+
`ToolExecutionError` and `StructuredOutputError`.
|
|
63
|
+
- **Model metadata** — providers register their own models with `registerModelMetadata()`; core
|
|
64
|
+
owns the registry and the lookups (`getModelContextWindow()`, `getModelMaxOutput()`, …), not any
|
|
65
|
+
vendor's catalogue.
|
|
66
|
+
- **Cancellation** — an `AbortSignal` passed in run options reaches the provider call.
|
|
51
67
|
|
|
52
68
|
## Robota API
|
|
53
69
|
|
|
@@ -58,23 +74,26 @@ import type { IAgentConfig } from '@robota-sdk/agent-core';
|
|
|
58
74
|
declare const config: IAgentConfig;
|
|
59
75
|
const agent = new Robota(config);
|
|
60
76
|
|
|
61
|
-
// Send a message (
|
|
77
|
+
// Send a message (tool calls are executed automatically)
|
|
62
78
|
const response = await agent.run('Hello');
|
|
63
79
|
|
|
64
80
|
// Conversation history
|
|
65
81
|
const history = agent.getHistory(); // TUniversalMessage[]
|
|
66
82
|
agent.clearHistory();
|
|
67
83
|
|
|
68
|
-
// Switch provider/model mid-conversation
|
|
84
|
+
// Switch provider/model mid-conversation (the provider must be registered in aiProviders)
|
|
69
85
|
agent.setModel({ provider: 'openai', model: 'gpt-4o' });
|
|
86
|
+
|
|
87
|
+
// Release the agent's resources when you are done
|
|
88
|
+
await agent.destroy();
|
|
70
89
|
```
|
|
71
90
|
|
|
72
|
-
### Structured
|
|
91
|
+
### Structured output
|
|
73
92
|
|
|
74
|
-
`run(input, { output })` returns a schema-validated object instead of a string. Zod
|
|
75
|
-
typed result
|
|
76
|
-
exists, and the response is always validated core
|
|
77
|
-
(`outputRetries`, default 2).
|
|
93
|
+
`run(input, { output })` returns a schema-validated object instead of a string. A Zod schema gives a
|
|
94
|
+
typed result. The schema is sent to the provider's native structured-output support where one
|
|
95
|
+
exists, and the response is always validated by core with a bounded retry on violation
|
|
96
|
+
(`outputRetries`, default 2). When the retries are used up, `run` throws `StructuredOutputError`.
|
|
78
97
|
|
|
79
98
|
```typescript
|
|
80
99
|
import { z } from 'zod';
|
|
@@ -95,7 +114,7 @@ const report = await agent.run('Summarize the meeting as a report.', {
|
|
|
95
114
|
output: reportSchema,
|
|
96
115
|
});
|
|
97
116
|
|
|
98
|
-
// Streaming variant: deltas stream as usual; the validated object is the
|
|
117
|
+
// Streaming variant: text deltas stream as usual; the validated object is the
|
|
99
118
|
// generator's return value (the final { done: true, value } iterator result).
|
|
100
119
|
const stream = agent.runStream('Summarize again.', { output: reportSchema });
|
|
101
120
|
const iterator = stream[Symbol.asyncIterator]();
|
|
@@ -105,20 +124,20 @@ while (!next.done) {
|
|
|
105
124
|
next = await iterator.next();
|
|
106
125
|
}
|
|
107
126
|
const streamedReport = next.value;
|
|
108
|
-
console.log(streamedReport.title);
|
|
127
|
+
console.log(report.title, streamedReport.title);
|
|
109
128
|
```
|
|
110
129
|
|
|
111
|
-
A raw JSON-schema wrapper is also accepted:
|
|
130
|
+
A raw JSON-schema wrapper is also accepted:
|
|
131
|
+
`{ output: { jsonSchema: { type: 'object', properties: { answer: { type: 'string' } }, required: ['answer'] } } }`.
|
|
112
132
|
|
|
113
|
-
### Model
|
|
133
|
+
### Model options per run
|
|
114
134
|
|
|
115
|
-
`run
|
|
116
|
-
`
|
|
117
|
-
`'
|
|
118
|
-
tool)
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
consume the results and finish.
|
|
135
|
+
`run` and `runStream` accept run-scoped model options that win over `defaultModel.*`: `maxTokens`,
|
|
136
|
+
`temperature` and `toolChoice`. `toolChoice` controls tool calls: `'auto'` (the model decides),
|
|
137
|
+
`'none'` (no tool calls), `'required'` (must call some tool), or `{ tool: name }` (must call that
|
|
138
|
+
tool). Naming a tool that is not in the run's tool list throws immediately. A forcing directive
|
|
139
|
+
applies to the run's first model call only; later rounds revert to `'auto'` so the model can use the
|
|
140
|
+
tool results and finish.
|
|
122
141
|
|
|
123
142
|
```typescript
|
|
124
143
|
import { Robota } from '@robota-sdk/agent-core';
|
|
@@ -127,7 +146,7 @@ import type { IAgentConfig } from '@robota-sdk/agent-core';
|
|
|
127
146
|
declare const config: IAgentConfig;
|
|
128
147
|
const agent = new Robota(config);
|
|
129
148
|
|
|
130
|
-
// Force the model to answer
|
|
149
|
+
// Force the model to answer through a router tool (decision-agent pattern)
|
|
131
150
|
const decision = await agent.run('Route this request.', {
|
|
132
151
|
toolChoice: { tool: 'route-request' },
|
|
133
152
|
allowToolOnlyCompletion: true,
|
|
@@ -141,106 +160,125 @@ const summary = await agent.run('Summarize the discussion.', {
|
|
|
141
160
|
console.log(decision, summary);
|
|
142
161
|
```
|
|
143
162
|
|
|
144
|
-
The same directive can be set
|
|
163
|
+
The same directive can be set for every run with `defaultModel.toolChoice`.
|
|
164
|
+
|
|
165
|
+
### Model effort
|
|
145
166
|
|
|
146
|
-
|
|
167
|
+
`defaultModel.effort` and the per-call `IChatOptions.effort` accept `auto`, `none`, `minimal`,
|
|
168
|
+
`low`, `medium`, `high`, `xhigh` or `max`. `auto` is passed through to the provider adapter, which
|
|
169
|
+
applies the model's documented default. The adapter reports what it actually sent through
|
|
170
|
+
`onModelEffortOutcome`; core does not guess vendor field names or defaults.
|
|
147
171
|
|
|
148
|
-
|
|
149
|
-
`medium`, `high`, `xhigh`, or `max`. `auto` is preserved to the provider adapter, which resolves its
|
|
150
|
-
documented exact-model default from a source-dated capability table. The adapter reports the terminal
|
|
151
|
-
resolution, native-control, and dispatch outcome through `onModelEffortOutcome`; Core does not guess
|
|
152
|
-
vendor field names or defaults.
|
|
172
|
+
### Execution events and usage
|
|
153
173
|
|
|
154
|
-
|
|
174
|
+
`run()` accepts an `onExecutionEvent` callback in its options. The execution loop reports
|
|
175
|
+
provider-neutral events that a higher layer can persist as an append-only record of the run,
|
|
176
|
+
including `provider_request`, `provider_native_raw_payload`, `provider_stream_raw_delta`,
|
|
177
|
+
`provider_response_raw`, `provider_response_normalized`, `assistant_message_committed`,
|
|
178
|
+
`tool_batch_started`, `tool_execution_request`, `tool_execution_result`, `tool_message_committed`
|
|
179
|
+
and `history_mutation`. `@robota-sdk/agent-session` writes these events to its session log.
|
|
155
180
|
|
|
156
|
-
|
|
181
|
+
Capturing a vendor SDK's exact payloads stays in the provider package: a provider calls
|
|
182
|
+
`IChatOptions.onProviderNativeRawPayload`, and `Robota` forwards it as a
|
|
183
|
+
`provider_native_raw_payload` event without importing vendor SDK types.
|
|
157
184
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
- `provider_stream_raw_delta`
|
|
161
|
-
- `provider_response_raw`
|
|
162
|
-
- `provider_response_normalized`
|
|
163
|
-
- `assistant_message_committed`
|
|
164
|
-
- `tool_batch_started`
|
|
165
|
-
- `tool_execution_request`
|
|
166
|
-
- `tool_execution_result`
|
|
167
|
-
- `tool_message_committed`
|
|
168
|
-
- `history_mutation`
|
|
185
|
+
Each provider call gets a `usageObservationId`. Streaming fragments of one call share it, and a
|
|
186
|
+
separately billed call gets a new one, so stored analytics can deduplicate usage by identity.
|
|
169
187
|
|
|
170
|
-
|
|
188
|
+
`sumMessagesUsage(messages)` sums the usage providers reported on assistant messages. The usage of
|
|
189
|
+
one run, tool rounds included, is the sum over the messages that run appended:
|
|
171
190
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
191
|
+
```typescript
|
|
192
|
+
import { Robota, sumMessagesUsage } from '@robota-sdk/agent-core';
|
|
193
|
+
import type { IAgentConfig } from '@robota-sdk/agent-core';
|
|
175
194
|
|
|
176
|
-
|
|
177
|
-
|
|
195
|
+
declare const config: IAgentConfig;
|
|
196
|
+
const agent = new Robota(config);
|
|
178
197
|
|
|
179
|
-
```ts
|
|
180
198
|
const before = agent.getHistory().length;
|
|
181
|
-
await agent.run(
|
|
199
|
+
await agent.run('Summarize the open issues.');
|
|
182
200
|
const usage = sumMessagesUsage(agent.getHistory().slice(before));
|
|
183
201
|
```
|
|
184
202
|
|
|
185
|
-
`cacheReadTokens
|
|
186
|
-
only when a provider
|
|
203
|
+
`cacheReadTokens`, the part of `promptTokens` the provider served from its prompt cache, is present
|
|
204
|
+
only when a provider reports it (the OpenAI and OpenAI-compatible providers do).
|
|
187
205
|
|
|
188
206
|
## IAgentConfig
|
|
189
207
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
|
193
|
-
|
|
|
194
|
-
| `
|
|
195
|
-
| `
|
|
196
|
-
| `
|
|
197
|
-
| `
|
|
198
|
-
| `
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
The main fields:
|
|
209
|
+
|
|
210
|
+
| Field | Type | Description |
|
|
211
|
+
| ----------------------- | -------------------------- | ---------------------------------------------------------- |
|
|
212
|
+
| `name` | `string` | Agent name |
|
|
213
|
+
| `aiProviders` | `IAIProvider[]` | One or more provider instances |
|
|
214
|
+
| `defaultModel.provider` | `string` | Name of the provider to use |
|
|
215
|
+
| `defaultModel.model` | `string` | Model identifier |
|
|
216
|
+
| `systemMessage` | `string?` | System prompt |
|
|
217
|
+
| `tools` | `IToolWithEventService[]?` | Tools the agent can call |
|
|
218
|
+
| `plugins` | `IPluginContract[]?` | Plugins that receive lifecycle hooks |
|
|
219
|
+
| `retainHistory` | `boolean?` | Keep history across runs (default `true`) |
|
|
220
|
+
| `maxExecutionRounds` | `number?` | Cap on model-call rounds per run (`0` means no cap) |
|
|
221
|
+
| `isToolVisible` | `(name) => boolean` | Hide a registered tool from the model, checked every round |
|
|
222
|
+
|
|
223
|
+
## Entry points
|
|
224
|
+
|
|
225
|
+
| Import path | What it contains |
|
|
226
|
+
| -------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
227
|
+
| `@robota-sdk/agent-core` | The main API, listed under Main exports. It also has a browser build: nothing in its static import graph uses Node built-ins. |
|
|
228
|
+
| `@robota-sdk/agent-core/node` | Node-only pieces: the `CommandExecutor` and `HttpExecutor` hook executors, path containment (`canonicalizePath`, `isPathInside`), owner-only file writes, and the egress policy (`fetchWithEgressPolicy`). |
|
|
229
|
+
| `@robota-sdk/agent-core/testing` | Test fixtures: `createScriptedProvider`, `createRecordingProvider`, `createReplayProvider`. |
|
|
230
|
+
|
|
231
|
+
When you pass no executors, `runHooks` loads `CommandExecutor` and `HttpExecutor` on demand (this
|
|
232
|
+
needs Node). Import them from `@robota-sdk/agent-core/node` only to pass them explicitly.
|
|
233
|
+
|
|
234
|
+
## Main exports
|
|
235
|
+
|
|
236
|
+
| Area | Exports |
|
|
237
|
+
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
238
|
+
| **Agent** | `Robota`, `ConversationStore`, `ConversationHistory`, `AbstractAgent`, `AgentFactory`, `AgentTemplates` |
|
|
239
|
+
| **Providers** | `AbstractAIProvider`, `IAIProvider`, `IChatOptions`, `IProviderCapabilities`, `getProviderCapabilities`, `assertProviderNativeWebToolsAvailable`, `IProviderDefinition`, `createProviderFromConfig` |
|
|
240
|
+
| **Media providers** | `IImageGenerationProvider`, `IVideoGenerationProvider`, `isImageGenerationProvider`, `isVideoGenerationProvider`, `createImageProviderFromDefinition`, `createVideoProviderFromDefinition`, `resolveMediaProviderConfig` |
|
|
241
|
+
| **Tools** | `FunctionTool`, `ToolRegistry`, `AbstractTool`, `IToolSchema`, `IToolResult`, `zodToJsonSchema` |
|
|
242
|
+
| **Execution** | `AbstractExecutor`, `LocalExecutor`, `IExecutor` |
|
|
243
|
+
| **Permissions** | `evaluatePermission`, `projectPermissionPolicy`, `RISK_CLASS_POLICY`, `UNCLASSIFIED_TOOL_FALLBACK`, `TRUST_TO_MODE`, `TPermissionMode`, `TTrustLevel`, `TPermissionDecision`, `TToolArgs`, `IPermissionLists` |
|
|
244
|
+
| **Hooks** | `runHooks`, `GuardrailExecutor`, `decodeHookVerdict`, `isEnforcing`, `THookEvent`, `THooksConfig`, `IHookGroup`, `THookDefinition`, `IHookInput`, `THookOutcome`, `IHookTypeExecutor` |
|
|
245
|
+
| **Plugins and events** | `AbstractPlugin`, `EventEmitterPlugin`, `IEventService`, `IOwnerPathSegment` |
|
|
246
|
+
| **Orchestration** | Contracts for multi-agent orchestration (`ISequentialOrchestrationSpec`, `IParallelOrchestrationSpec`, …, `ORCHESTRATION_EVENTS`); the runners are in `@robota-sdk/agent-framework` |
|
|
247
|
+
| **Models** | `registerModelMetadata`, `getModelContextWindow`, `getModelMaxOutput`, `getModelName`, `formatTokenCount`, `DEFAULT_CONTEXT_WINDOW`, `DEFAULT_MAX_OUTPUT`, `IModelDefinition` |
|
|
248
|
+
| **Context and usage** | `estimateContextTokensFromMessages`, `estimateSerializedContextTokens`, `readTokenUsageFromMessage`, `sumMessagesUsage`, `IContextWindowState`, `IContextTokenUsage` |
|
|
249
|
+
| **Messages** | `TUniversalMessage`, `IBaseMessage`, `TMessageState`, `createUserMessage`, `createAssistantMessage`, `isAssistantMessage`, … |
|
|
250
|
+
| **Errors** | `RobotaError`, `ProviderError`, `RateLimitError`, `AuthenticationError`, `ToolExecutionError`, `StructuredOutputError`, `classifyProviderFailure`, `toProviderError` |
|
|
251
|
+
|
|
252
|
+
## Where it sits
|
|
201
253
|
|
|
202
254
|
```
|
|
203
|
-
agent-core
|
|
255
|
+
agent-core ← this package (no workspace dependencies)
|
|
204
256
|
↑
|
|
205
|
-
agent-session
|
|
206
|
-
agent-tools ← Tool implementations
|
|
207
|
-
agent-provider-* ← AI provider implementations (one package per vendor)
|
|
208
|
-
agent-plugin ← Plugin implementations (8 plugins, consolidated)
|
|
257
|
+
agent-tools, agent-session, agent-plugin, agent-mcp, agent-provider-* (one package per vendor)
|
|
209
258
|
↑
|
|
210
|
-
agent-framework
|
|
259
|
+
agent-framework ← assembles sessions, commands, permissions and hooks
|
|
211
260
|
↑
|
|
212
|
-
agent-cli
|
|
261
|
+
agent-cli ← the `robota` reference app
|
|
213
262
|
```
|
|
214
263
|
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
| `FunctionTool`, `ToolRegistry`, `OpenAPITool` | `@robota-sdk/agent-tools` |
|
|
234
|
-
| `MCPTool`, `RelayMcpTool` | `@robota-sdk/agent-tool-mcp` |
|
|
235
|
-
| 8 plugins (logging, usage, performance, etc.) | `@robota-sdk/agent-plugin` |
|
|
236
|
-
|
|
237
|
-
## Repository Examples
|
|
238
|
-
|
|
239
|
-
Repository contributors can run the owner-local hook examples after building this package:
|
|
240
|
-
`node examples/hook-block-demo.mjs`, `node examples/hook-json-response-demo.mjs`,
|
|
241
|
-
`node examples/hook-permission-mode-demo.mjs`, and `node examples/hook-timeout-demo.mjs`,
|
|
242
|
-
from `packages/agent-core`. They exercise hooks without a live model; see the
|
|
243
|
-
[hook contract](./docs/SPEC.md) for each example's verification limits.
|
|
264
|
+
- Built-in tools (Shell, Read, Edit, …): `@robota-sdk/agent-tools`
|
|
265
|
+
- Ready-made plugins (logging, usage, limits, …): `@robota-sdk/agent-plugin`
|
|
266
|
+
- MCP servers and tools: `@robota-sdk/agent-mcp`
|
|
267
|
+
- Sessions with permissions, hooks and compaction: `@robota-sdk/agent-session`
|
|
268
|
+
|
|
269
|
+
## Repository examples
|
|
270
|
+
|
|
271
|
+
From `packages/agent-core` in a built checkout of the repository, these scripts exercise hooks
|
|
272
|
+
without a live model: `node examples/hook-block-demo.mjs`, `node examples/hook-json-response-demo.mjs`,
|
|
273
|
+
`node examples/hook-permission-mode-demo.mjs` and `node examples/hook-timeout-demo.mjs`.
|
|
274
|
+
|
|
275
|
+
## Documentation
|
|
276
|
+
|
|
277
|
+
- [docs/SPEC.md](./docs/SPEC.md) — package contract and design decisions
|
|
278
|
+
- [docs/HOOK-CATALOG.md](./docs/HOOK-CATALOG.md) — hook events, input fields and blocking semantics
|
|
279
|
+
- [Building agents](../../content/guide/building-agents.md) — a walkthrough of `Robota`, tools and
|
|
280
|
+
structured output
|
|
281
|
+
- [Permissions and hooks](../../content/guide/permissions-and-hooks.md)
|
|
244
282
|
|
|
245
283
|
## License
|
|
246
284
|
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
var e=class extends Error{context;constructor(e,t){super(e),this.context=t,this.name=this.constructor.name,Object.setPrototypeOf(this,new.target.prototype)}},t=class extends e{code=`CONFIGURATION_ERROR`;category=`user`;recoverable=!1;constructor(e,t){super(`Configuration Error: ${e}`,t)}},n=class extends e{field;code=`VALIDATION_ERROR`;category=`user`;recoverable=!1;constructor(e,t,n){super(`Validation Error: ${e}`,n),this.field=t}},r=class extends e{issues;attempts;code=`STRUCTURED_OUTPUT_ERROR`;category=`provider`;recoverable=!0;constructor(e,t,n,r){super(`Structured Output Error: ${e}`,r),this.issues=t,this.attempts=n}},i=class extends e{provider;originalError;code=`PROVIDER_ERROR`;category=`provider`;recoverable=!0;status;type;constructor(e,t,n,r,i){super(`Provider Error (${t}): ${e}`,r),this.provider=t,this.originalError=n,i?.status!==void 0&&(this.status=i.status),i?.type!==void 0&&(this.type=i.type)}},a=class extends e{provider;code=`AUTHENTICATION_ERROR`;category=`user`;recoverable=!1;constructor(e,t,n){super(`Authentication Error: ${e}`,n),this.provider=t}},o=class extends e{retryAfter;provider;code=`RATE_LIMIT_ERROR`;category=`provider`;recoverable=!0;constructor(e,t,n,r){super(`Rate Limit Error: ${e}`,r),this.retryAfter=t,this.provider=n}},s=class extends e{originalError;provider;code=`NETWORK_ERROR`;category=`system`;recoverable=!0;constructor(e,t,n,r){super(`Network Error: ${e}`,n),this.originalError=t,this.provider=r}},c=class extends e{toolName;originalError;code=`TOOL_EXECUTION_ERROR`;category=`system`;recoverable=!1;constructor(e,t,n,r){super(`Tool Execution Error (${t}): ${e}`,r),this.toolName=t,this.originalError=n}},l=class extends e{toolName;callCount;maxSameToolInputs;code=`SAME_TOOL_INPUT_LOOP`;category=`system`;recoverable=!0;constructor(e,t,n,r){super(`Tool "${e}" was called with identical input ${t} times, past the maxSameToolInputs limit of ${n} — stopping the turn to break the loop`,r),this.toolName=e,this.callCount=t,this.maxSameToolInputs=n}},u=class extends e{model;provider;availableModels;code=`MODEL_NOT_AVAILABLE`;category=`user`;recoverable=!1;constructor(e,t,n,r){let i=e===void 0?`No model available for provider "${t}"`:`Model "${e}" is not available for provider "${t}"`,a=r?.originalMessage;super(typeof a==`string`&&a.length>0?`${i}: ${a}`:i,r),this.model=e,this.provider=t,this.availableModels=n}},d=class extends e{code=`CIRCUIT_BREAKER_OPEN`;category=`system`;recoverable=!0;constructor(e=`Circuit breaker is open`,t){super(e,t)}},f=class extends e{pluginName;code=`PLUGIN_ERROR`;category=`system`;recoverable=!1;constructor(e,t,n){super(`Plugin Error (${t}): ${e}`,n),this.pluginName=t}},p=class extends e{code=`STORAGE_ERROR`;category=`system`;recoverable=!0;constructor(e,t){super(`Storage Error: ${e}`,t)}},m=class extends e{code=`CACHE_INTEGRITY_ERROR`;category=`system`;recoverable=!1;constructor(e,t){super(`Cache Integrity Error: ${e}`,t)}},h=class extends e{executable;code=`UNSUPPORTED_SHELL`;category=`user`;recoverable=!1;constructor(e){super(`Unsupported shell executable: ${e}`,{executable:e}),this.executable=e}};function g(e){return e===`darwin`?'Host OS: macOS (POSIX, BSD userland). Use BSD-flavored utilities: `sed -i ""` needs an empty backup arg, `date`/`grep`/`stat` flags differ from GNU. Prefer portable POSIX flags; avoid Linux/GNU-only options.':e===`linux`?"Host OS: Linux (POSIX, usually GNU coreutils, but distro/user setup varies). Prefer portable POSIX flags; do not assume a specific distro, and probe with `command -v` before relying on a non-standard tool.":`Host OS: ${e}; active shell family: POSIX sh/bash. Write portable POSIX syntax and avoid host-specific flags.`}function _(e,t){let n=b(e)===`bash`?`bash`:`sh`;return{command:e,kind:n,platform:t,commandArgs:e=>[`-c`,e],interactiveArgs:[],label:`${n} on ${t}`,syntaxHint:g(t)}}function v(e,t){return{command:e,kind:`powershell`,platform:t,commandArgs:e=>[`-NoProfile`,`-Command`,e],interactiveArgs:[`-NoProfile`],label:`PowerShell on ${t}`,syntaxHint:`Host OS: ${t}; active shell family: PowerShell. Write PowerShell syntax (not bash): e.g. \`Get-ChildItem\` not \`ls -la\`, \`\$env:VAR\` not \`\$VAR\`.`}}function y(e,t){return{command:e,kind:`cmd`,platform:t,commandArgs:e=>[`/d`,`/s`,`/c`,e],interactiveArgs:[],label:`cmd.exe on ${t}`,syntaxHint:`Host OS: ${t}; active shell family: Windows cmd.exe. Write cmd.exe syntax.`}}function b(e){return(e.split(/[\\/]/).at(-1)?.toLowerCase()??``).replace(/\.exe$/i,``)}function x(e,t){switch(b(e)){case`sh`:case`bash`:return _(e,t);case`powershell`:case`pwsh`:return v(e,t);case`cmd`:return y(e,t);default:throw new h(e)}}function S(e){let t=e?.trim();return t===void 0||t.length===0?void 0:t}function C(e={}){let t=e.env??process.env,n=e.platform??process.platform,r=S(e.executable);return r===void 0?n===`win32`?v(`powershell.exe`,n):_(S(t.SHELL)??`/bin/sh`,n):x(r,n)}const w=/^(?!0{32}$)[0-9a-f]{32}$/,T=/^(?!0{16}$)[0-9a-f]{16}$/;function E(e){return e.replaceAll(`-`,``).slice(0,16)}const D=E;function O(e){return T.test(e)}function k(e,t){if(!(!w.test(e)||!T.test(t)))return`00-${e}-${t}-01`}function A(e,t){if(e.allowedOrigins.length===0)return;let n=k(e.traceId,E(t));if(n!==void 0)return{traceparent:n,allowedOrigins:[...e.allowedOrigins]}}function j(e,t){return A(e,t)}function M(e,t,n){if(t?.subprocessClasses?.includes(e)!==!0)return;let r=k(t.traceId,n);return r===void 0?void 0:{TRACEPARENT:r}}function N(e,t,n={}){if(t===void 0||n.TRACEPARENT!==void 0)return{...e,...n};let{TRACESTATE:r,...i}=e;return{...i,...t,...n}}function P(e,t){if(e===void 0||t===void 0)return{};let n;try{n=new URL(e).origin}catch{return{}}return n===`null`||!t.allowedOrigins.includes(n)?{}:{traceparent:t.traceparent}}const F=e=>`\n…[output truncated: ${e} byte(s) dropped]`;function I(e){let t=e.retain??`head`,n=e.truncationMarker??F,r=Math.max(0,Math.floor(e.maxBytes)),i=new TextEncoder,a=[],o=0,s=0,c=e=>{let t=r-o;if(t<=0)return;let n=e.byteLength<=t?e:e.subarray(0,t);a.push(n),o+=n.byteLength},l=e=>{let t=e.byteLength<=r?e:e.subarray(e.byteLength-r);for(a.push(t),o+=t.byteLength;o>r&&a.length>0;){let e=a[0],t=o-r;e.byteLength<=t?(a.shift(),o-=e.byteLength):(a[0]=e.subarray(t),o-=t)}};return{append(e){let n=typeof e==`string`?i.encode(e):e;s+=n.byteLength,t===`head`?c(n):l(n)},get retainedBytes(){return o},get totalBytes(){return s},get droppedBytes(){return s-o},get truncated(){return s>o},toString(){let e=new Uint8Array(o),r=0;for(let t of a)e.set(t,r),r+=t.byteLength;a=a.length>1?[e]:a;let i=new TextDecoder().decode(e);if(s<=o)return i;let c=n(s-o);return t===`head`?`${i}${c}`:`${c.replace(/^\n/,``)}\n${i}`}}}export{l as C,n as D,c as E,e as S,r as T,u as _,D as a,i as b,j as c,h as d,C as f,t as g,d as h,A as i,M as l,m,k as n,E as o,a as p,O as r,N as s,I as t,P as u,s as v,p as w,o as x,f as y};
|
|
2
|
+
//# sourceMappingURL=bounded-output-D3cgiOrh.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bounded-output-D3cgiOrh.js","names":[],"sources":["../../src/utils/errors.ts","../../src/utils/platform-shell.ts","../../src/utils/trace-context.ts","../../src/utils/bounded-output.ts"],"sourcesContent":["/**\n * Reusable type definitions for error utilities\n */\n\n/**\n * Error context data type\n * Used for storing contextual information in error instances\n */\nexport type TErrorContextData = Record<\n string,\n string | number | boolean | Date | Error | string[] | undefined\n>;\n\n/**\n * Error external input type\n * Used for handling external errors from unknown sources\n */\nexport type TErrorExternalInput =\n Error | string | Record<string, string | number | boolean> | null | undefined;\n\n/**\n * Base error class for all Robota errors\n */\nexport abstract class RobotaError extends Error {\n abstract readonly code: string;\n abstract readonly category: 'user' | 'system' | 'provider';\n abstract readonly recoverable: boolean;\n\n constructor(\n message: string,\n public readonly context?: TErrorContextData,\n ) {\n super(message);\n this.name = this.constructor.name;\n\n // Ensure proper prototype chain for instanceof checks\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/**\n * Configuration related errors\n */\nexport class ConfigurationError extends RobotaError {\n readonly code = 'CONFIGURATION_ERROR';\n readonly category = 'user' as const;\n readonly recoverable = false;\n\n constructor(message: string, context?: TErrorContextData) {\n super(`Configuration Error: ${message}`, context);\n }\n}\n\n/**\n * Input validation errors\n */\nexport class ValidationError extends RobotaError {\n readonly code = 'VALIDATION_ERROR';\n readonly category = 'user' as const;\n readonly recoverable = false;\n\n constructor(\n message: string,\n public readonly field?: string,\n context?: TErrorContextData,\n ) {\n super(`Validation Error: ${message}`, context);\n }\n}\n\n/**\n * Structured output validation exhausted its retry budget (CORE-015).\n *\n * Thrown by `run(input, { output })` when the model's final response still fails\n * schema validation after the configured number of retries. `issues` holds the\n * validation messages from the last attempt; `attempts` is the total number of\n * provider turns spent (initial + retries).\n */\nexport class StructuredOutputError extends RobotaError {\n readonly code = 'STRUCTURED_OUTPUT_ERROR';\n readonly category = 'provider' as const;\n readonly recoverable = true;\n\n constructor(\n message: string,\n public readonly issues: string[],\n public readonly attempts: number,\n context?: TErrorContextData,\n ) {\n super(`Structured Output Error: ${message}`, context);\n }\n}\n\n/** What the vendor said about a failed call, as far as it said anything. */\nexport interface IProviderFailureDetails {\n /** The HTTP status of the failed response; absent for a failure with no response (e.g. an SSE error event). */\n status?: number;\n /** The vendor's own error type, e.g. Anthropic's `overloaded_error`. */\n type?: string;\n}\n\n/**\n * Provider related errors\n */\nexport class ProviderError extends RobotaError {\n readonly code = 'PROVIDER_ERROR';\n readonly category = 'provider' as const;\n readonly recoverable = true;\n readonly status?: number;\n readonly type?: string;\n\n constructor(\n message: string,\n public readonly provider: string,\n public readonly originalError?: Error,\n context?: TErrorContextData,\n details?: IProviderFailureDetails,\n ) {\n super(`Provider Error (${provider}): ${message}`, context);\n if (details?.status !== undefined) this.status = details.status;\n if (details?.type !== undefined) this.type = details.type;\n }\n}\n\n/**\n * Authentication errors\n */\nexport class AuthenticationError extends RobotaError {\n readonly code = 'AUTHENTICATION_ERROR';\n readonly category = 'user' as const;\n readonly recoverable = false;\n\n constructor(\n message: string,\n public readonly provider?: string,\n context?: TErrorContextData,\n ) {\n super(`Authentication Error: ${message}`, context);\n }\n}\n\n/**\n * Rate limit errors\n */\nexport class RateLimitError extends RobotaError {\n readonly code = 'RATE_LIMIT_ERROR';\n readonly category = 'provider' as const;\n readonly recoverable = true;\n\n constructor(\n message: string,\n public readonly retryAfter?: number,\n public readonly provider?: string,\n context?: TErrorContextData,\n ) {\n super(`Rate Limit Error: ${message}`, context);\n }\n}\n\n/**\n * Network/connectivity errors\n */\nexport class NetworkError extends RobotaError {\n readonly code = 'NETWORK_ERROR';\n readonly category = 'system' as const;\n readonly recoverable = true;\n\n constructor(\n message: string,\n public readonly originalError?: Error,\n context?: TErrorContextData,\n /** `undefined` for a network failure with no provider context (e.g. a generic timeout). */\n public readonly provider?: string,\n ) {\n super(`Network Error: ${message}`, context);\n }\n}\n\n/**\n * Tool execution errors\n */\nexport class ToolExecutionError extends RobotaError {\n readonly code = 'TOOL_EXECUTION_ERROR';\n readonly category = 'system' as const;\n readonly recoverable = false;\n\n constructor(\n message: string,\n public readonly toolName: string,\n public readonly originalError?: Error,\n context?: TErrorContextData,\n ) {\n super(`Tool Execution Error (${toolName}): ${message}`, context);\n }\n}\n\n/**\n * The same-tool-input loop guard tripped — CORE-035.\n *\n * A tool was invoked with byte-identical serialized inputs more times than `maxSameToolInputs`\n * allows, so the turn stopped rather than looping. This is the AGENT giving up, not the user\n * cancelling: the SPEC used to promise an `AbortError` here, which `isAbortFailure` resolves as\n * `success: true, interrupted: true` — reporting a run that produced no answer as a success.\n *\n * Named rather than a bare `Error` for the reason the SPEC was reaching for by naming a type at all:\n * a caller must be able to tell \"the agent looped\" from \"the network died\". `RobotaError` carries\n * `code`/`category`/`recoverable` out through CORE-027's failure path intact.\n *\n * `recoverable` is TRUE: the loop is a property of this turn's prompt and tool set, not of the\n * system, and a caller that varies either can reasonably try again.\n */\nexport class SameToolInputLoopError extends RobotaError {\n readonly code = 'SAME_TOOL_INPUT_LOOP';\n readonly category = 'system' as const;\n readonly recoverable = true;\n\n constructor(\n public readonly toolName: string,\n public readonly callCount: number,\n public readonly maxSameToolInputs: number,\n context?: TErrorContextData,\n ) {\n super(\n `Tool \"${toolName}\" was called with identical input ${callCount} times, past the ` +\n `maxSameToolInputs limit of ${maxSameToolInputs} — stopping the turn to break the loop`,\n context,\n );\n }\n}\n\n/**\n * Model not available errors\n */\nexport class ModelNotAvailableError extends RobotaError {\n readonly code = 'MODEL_NOT_AVAILABLE';\n readonly category = 'user' as const;\n readonly recoverable = false;\n\n constructor(\n /** `undefined` when the failure names no model — a provider adapter classifying a raw HTTP\n * response by status alone does not always know which model the vendor meant. */\n public readonly model: string | undefined,\n public readonly provider: string,\n public readonly availableModels?: string[],\n /**\n * `context.originalMessage`, when given, is the vendor's own text (already scrubbed of any\n * credential by the caller) — folded into `message` so a \"Details\" disclosure built from\n * `message` alone still shows the real cause instead of just this class's fixed sentence.\n */\n context?: TErrorContextData,\n ) {\n const base =\n model !== undefined\n ? `Model \"${model}\" is not available for provider \"${provider}\"`\n : `No model available for provider \"${provider}\"`;\n const detail = context?.['originalMessage'];\n super(typeof detail === 'string' && detail.length > 0 ? `${base}: ${detail}` : base, context);\n }\n}\n\n/**\n * Circuit breaker open error\n */\nexport class CircuitBreakerOpenError extends RobotaError {\n readonly code = 'CIRCUIT_BREAKER_OPEN';\n readonly category = 'system' as const;\n readonly recoverable = true;\n\n constructor(message: string = 'Circuit breaker is open', context?: TErrorContextData) {\n super(message, context);\n }\n}\n\n/**\n * Plugin errors\n */\nexport class PluginError extends RobotaError {\n readonly code = 'PLUGIN_ERROR';\n readonly category = 'system' as const;\n readonly recoverable = false;\n\n constructor(\n message: string,\n public readonly pluginName: string,\n context?: TErrorContextData,\n ) {\n super(`Plugin Error (${pluginName}): ${message}`, context);\n }\n}\n\n/**\n * Storage related errors\n */\nexport class StorageError extends RobotaError {\n readonly code = 'STORAGE_ERROR';\n readonly category = 'system' as const;\n readonly recoverable = true;\n\n constructor(message: string, context?: TErrorContextData) {\n super(`Storage Error: ${message}`, context);\n }\n}\n\n/**\n * Cache integrity validation errors\n */\nexport class CacheIntegrityError extends RobotaError {\n readonly code = 'CACHE_INTEGRITY_ERROR';\n readonly category = 'system' as const;\n readonly recoverable = false;\n\n constructor(message: string, context?: TErrorContextData) {\n super(`Cache Integrity Error: ${message}`, context);\n }\n}\n","/**\n * TERM-008: cross-platform shell resolution (SSOT).\n *\n * Single source of truth for \"which shell do we spawn, and how\", shared by every shell-running site.\n * Resolution is a pure function of one request so every branch is testable without the host shell.\n */\n\nimport { RobotaError } from './errors.js';\n\n/** Shell family — drives non-interactive arg shape, quoting, and LLM syntax guidance. */\nexport type TShellKind = 'bash' | 'sh' | 'powershell' | 'cmd';\n\n/** The active shell resolved for a platform, plus the metadata callers need to drive and describe it. */\nexport interface IPlatformShell {\n /** Executable to spawn (e.g. `/bin/sh`, `powershell.exe`, or `$SHELL`). */\n readonly command: string;\n /** Shell family. */\n readonly kind: TShellKind;\n /** Node platform this was resolved for (`process.platform`). */\n readonly platform: NodeJS.Platform;\n /** Args to run a single command string non-interactively. */\n commandArgs(command: string): string[];\n /** Args for an interactive shell session (drop-to-shell). */\n readonly interactiveArgs: string[];\n /** Human label for the active shell, for tool/UI descriptions (e.g. `PowerShell (Windows)`). */\n readonly label: string;\n /** One-line syntax guidance for an LLM authoring commands. */\n readonly syntaxHint: string;\n}\n\n/** Pure inputs for resolving one executable together with its command argument family. */\nexport interface IPlatformShellResolutionRequest {\n /** Request-local executable. A non-blank value has the highest precedence. */\n readonly executable?: string;\n /** Environment used for SHELL resolution. */\n readonly env?: NodeJS.ProcessEnv;\n /** Actual host platform retained in the resolved metadata. */\n readonly platform?: NodeJS.Platform;\n}\n\n/** An explicit executable whose basename has no supported command argument family. */\nexport class UnsupportedShellError extends RobotaError {\n readonly code = 'UNSUPPORTED_SHELL';\n readonly category = 'user' as const;\n readonly recoverable = false;\n\n constructor(public readonly executable: string) {\n super(`Unsupported shell executable: ${executable}`, { executable });\n }\n}\n\n/**\n * OS-family syntax guidance for the LLM. macOS and Linux are both POSIX but ship different userlands\n * (macOS = BSD coreutils, most Linux = GNU coreutils), so flags like `sed -i`, `date`, and `grep`\n * differ — naming the OS lets the model avoid Linux-only invocations on macOS and vice versa.\n */\nfunction posixSyntaxHint(platform: NodeJS.Platform): string {\n if (platform === 'darwin') {\n return 'Host OS: macOS (POSIX, BSD userland). Use BSD-flavored utilities: `sed -i \"\"` needs an empty backup arg, `date`/`grep`/`stat` flags differ from GNU. Prefer portable POSIX flags; avoid Linux/GNU-only options.';\n }\n if (platform === 'linux') {\n return 'Host OS: Linux (POSIX, usually GNU coreutils, but distro/user setup varies). Prefer portable POSIX flags; do not assume a specific distro, and probe with `command -v` before relying on a non-standard tool.';\n }\n return `Host OS: ${platform}; active shell family: POSIX sh/bash. Write portable POSIX syntax and avoid host-specific flags.`;\n}\n\nfunction posixShell(command: string, platform: NodeJS.Platform): IPlatformShell {\n const isBash = shellBasename(command) === 'bash';\n const kind: TShellKind = isBash ? 'bash' : 'sh';\n return {\n command,\n kind,\n platform,\n commandArgs: (cmd: string): string[] => ['-c', cmd],\n interactiveArgs: [],\n label: `${kind} on ${platform}`,\n syntaxHint: posixSyntaxHint(platform),\n };\n}\n\nfunction powerShell(command: string, platform: NodeJS.Platform): IPlatformShell {\n return {\n command,\n kind: 'powershell',\n platform,\n commandArgs: (cmd: string): string[] => ['-NoProfile', '-Command', cmd],\n interactiveArgs: ['-NoProfile'],\n label: `PowerShell on ${platform}`,\n syntaxHint:\n `Host OS: ${platform}; active shell family: PowerShell. Write PowerShell syntax (not bash): e.g. ` +\n '`Get-ChildItem` not `ls -la`, `$env:VAR` not `$VAR`.',\n };\n}\n\nfunction cmdShell(command: string, platform: NodeJS.Platform): IPlatformShell {\n return {\n command,\n kind: 'cmd',\n platform,\n commandArgs: (cmd: string): string[] => ['/d', '/s', '/c', cmd],\n interactiveArgs: [],\n label: `cmd.exe on ${platform}`,\n syntaxHint: `Host OS: ${platform}; active shell family: Windows cmd.exe. Write cmd.exe syntax.`,\n };\n}\n\nfunction shellBasename(command: string): string {\n const basename = command.split(/[\\\\/]/).at(-1)?.toLowerCase() ?? '';\n return basename.replace(/\\.exe$/i, '');\n}\n\nfunction explicitShell(command: string, platform: NodeJS.Platform): IPlatformShell {\n switch (shellBasename(command)) {\n case 'sh':\n case 'bash':\n return posixShell(command, platform);\n case 'powershell':\n case 'pwsh':\n return powerShell(command, platform);\n case 'cmd':\n return cmdShell(command, platform);\n default:\n throw new UnsupportedShellError(command);\n }\n}\n\nfunction nonBlank(value: string | undefined): string | undefined {\n const trimmed = value?.trim();\n return trimmed === undefined || trimmed.length === 0 ? undefined : trimmed;\n}\n\n/**\n * Resolve the shell to spawn for the current (or a given) platform.\n *\n * - request `executable` wins when non-blank.\n * - **win32:** PowerShell.\n * - **posix:** `$SHELL` if set, else `/bin/sh`.\n */\nexport function resolvePlatformShell(\n request: IPlatformShellResolutionRequest = {},\n): IPlatformShell {\n const env = request.env ?? process.env;\n const platform = request.platform ?? process.platform;\n const requestedExecutable = nonBlank(request.executable);\n if (requestedExecutable !== undefined) return explicitShell(requestedExecutable, platform);\n\n if (platform === 'win32') return powerShell('powershell.exe', platform);\n\n return posixShell(nonBlank(env['SHELL']) ?? '/bin/sh', platform);\n}\n","/**\n * Pure helpers for trusted W3C trace context. Strings and the WHATWG `URL` only, so they are the\n * same in the browser build and in every adapter that attaches the header.\n */\nimport type {\n IOutboundTraceContext,\n IRunTraceContext,\n ISubprocessTraceEnv,\n TSubprocessTraceClass,\n} from '../interfaces/trace-context';\n\nconst TRACE_ID = /^(?!0{32}$)[0-9a-f]{32}$/;\nconst SPAN_ID = /^(?!0{16}$)[0-9a-f]{16}$/;\n\n/**\n * The span ID an operation is exported under, derived from the ID core mints before it runs (a\n * provider call's call ID, a tool body's body ID). One derivation for both the exporter and the\n * outgoing header, so the span a recipient sees as its parent is the span the trace contains.\n */\nexport function spanIdFromMintedId(mintedId: string): string {\n return mintedId.replaceAll('-', '').slice(0, 16);\n}\n\n/** The provider-call name for {@link spanIdFromMintedId}, kept so existing callers keep working. */\nexport const providerCallSpanId: (callId: string) => string = spanIdFromMintedId;\n\n/** Whether `spanId` is a well-formed minted span ID: 16 lowercase hex characters, not all zero. */\nexport function isMintedSpanId(spanId: string): boolean {\n return SPAN_ID.test(spanId);\n}\n\n/** A sampled version-00 `traceparent`, or undefined when either identifier is invalid. */\nexport function buildTraceparent(traceId: string, spanId: string): string | undefined {\n if (!TRACE_ID.test(traceId) || !SPAN_ID.test(spanId)) return undefined;\n return `00-${traceId}-${spanId}-01`;\n}\n\n/** The outbound context for one invoked call, or undefined when there is nothing valid to send. */\nexport function outboundTraceContextFor(\n traceContext: IRunTraceContext,\n callId: string,\n): IOutboundTraceContext | undefined {\n if (traceContext.allowedOrigins.length === 0) return undefined;\n const traceparent = buildTraceparent(traceContext.traceId, spanIdFromMintedId(callId));\n if (traceparent === undefined) return undefined;\n return { traceparent, allowedOrigins: [...traceContext.allowedOrigins] };\n}\n\n/**\n * The outbound context for one tool body, keyed by the body ID core minted for it — never the\n * vendor's tool call ID, which is neither unique nor hex.\n */\nexport function toolTraceContextFor(\n traceContext: IRunTraceContext,\n toolBodyId: string,\n): IOutboundTraceContext | undefined {\n return outboundTraceContextFor(traceContext, toolBodyId);\n}\n\n/**\n * The environment one child process of `subprocessClass` receives, naming `spanId` as its parent,\n * or undefined when the host did not enable that class. Independent of the origin allowlist.\n */\nexport function traceEnvFor(\n subprocessClass: TSubprocessTraceClass,\n traceContext: IRunTraceContext | undefined,\n spanId: string,\n): ISubprocessTraceEnv | undefined {\n if (traceContext?.subprocessClasses?.includes(subprocessClass) !== true) return undefined;\n const traceparent = buildTraceparent(traceContext.traceId, spanId);\n return traceparent === undefined ? undefined : { TRACEPARENT: traceparent };\n}\n\n/**\n * A fresh child environment: `base`, then Robota's trace, then `overrides`. When Robota's value\n * applies, the ambient `TRACESTATE` is dropped because it belongs to a different parent. When\n * `overrides` sets its own `TRACEPARENT`, Robota's value does not apply and `base` passes unchanged.\n * `base` itself is never modified.\n */\nexport function subprocessTraceEnvironment(\n base: Readonly<Record<string, string | undefined>>,\n traceEnv: ISubprocessTraceEnv | undefined,\n overrides: Readonly<Record<string, string>> = {},\n): Record<string, string | undefined> {\n if (traceEnv === undefined || overrides['TRACEPARENT'] !== undefined) return { ...base, ...overrides };\n const { TRACESTATE: _ambientState, ...rest } = base;\n return { ...rest, ...traceEnv, ...overrides };\n}\n\n/**\n * The headers to add to a request whose effective base URL is `baseUrl`: `traceparent` when its\n * origin exactly equals a listed origin, nothing otherwise. Scheme, host and port must all match;\n * a subdomain or a different port is a different origin.\n */\nexport function traceHeadersFor(\n baseUrl: string | undefined,\n traceContext: IOutboundTraceContext | undefined,\n): Readonly<Record<string, string>> {\n if (baseUrl === undefined || traceContext === undefined) return {};\n let origin: string;\n try {\n origin = new URL(baseUrl).origin;\n } catch {\n return {};\n }\n if (origin === 'null' || !traceContext.allowedOrigins.includes(origin)) return {};\n return { traceparent: traceContext.traceparent };\n}\n","/**\n * ARCH-056 (#2161): the bounded output-retention contract for child-process execution.\n *\n * Every path that reads a child's stdout/stderr must bound memory WHILE the output is being read —\n * not cap a summary afterwards while retaining the whole log. Before this existed, `shell-tool`\n * could buffer for ten minutes, the hook `CommandExecutor` repeated the pattern, and a subagent\n * runner had rediscovered a 4 KiB tail on its own. This module names the policy once:\n *\n * - **Budget.** `maxBytes` is the most that is ever RETAINED; bytes beyond it are dropped as they\n * arrive, so memory is bounded by the budget, not by the child's lifetime.\n * - **Retention.** `head` keeps the first `maxBytes` (a tool result: what the command said first);\n * `tail` keeps the last `maxBytes` (a diagnostic: how it ended).\n * - **Truncation is visible.** `truncated`, `droppedBytes` and a marker in `toString()` say that\n * output was dropped and how much — never a silently shorter string.\n * - **No spill.** This contract never writes to disk; a caller that needs the whole stream owns a\n * log file (background tasks do), and this buffer is what stays in memory beside it.\n * - **Termination is not here.** Exceeding the budget drops bytes; whether the child is killed is\n * the caller's policy (CORE-023 owns process-tree termination).\n *\n * Chunks are `Uint8Array` (a Node `Buffer` is one), and decoding happens once at `toString()`, so a\n * multi-byte character split across a chunk boundary is never decoded twice.\n */\n\nexport type TOutputRetention = 'head' | 'tail';\n\nexport interface IBoundedOutputOptions {\n /** Most bytes ever retained. */\n readonly maxBytes: number;\n /** Which end to keep once over budget. Default `head`. */\n readonly retain?: TOutputRetention;\n /** Marker appended (head) or prepended (tail) to `toString()` when bytes were dropped. */\n readonly truncationMarker?: (droppedBytes: number) => string;\n}\n\nexport interface IBoundedOutput {\n append(chunk: Uint8Array | string): void;\n /** Bytes currently retained (≤ `maxBytes`). */\n readonly retainedBytes: number;\n /** Bytes the child produced in total, retained or not. */\n readonly totalBytes: number;\n readonly droppedBytes: number;\n readonly truncated: boolean;\n /** The retained output, decoded as UTF-8, with the truncation marker when bytes were dropped. */\n toString(): string;\n}\n\nconst DEFAULT_MARKER = (dropped: number): string =>\n `\\n…[output truncated: ${dropped} byte(s) dropped]`;\n\nexport function createBoundedOutput(options: IBoundedOutputOptions): IBoundedOutput {\n const retain = options.retain ?? 'head';\n const marker = options.truncationMarker ?? DEFAULT_MARKER;\n const maxBytes = Math.max(0, Math.floor(options.maxBytes));\n const encoder = new TextEncoder();\n let chunks: Uint8Array[] = [];\n let retained = 0;\n let total = 0;\n\n const appendHead = (bytes: Uint8Array): void => {\n const room = maxBytes - retained;\n if (room <= 0) return;\n const kept = bytes.byteLength <= room ? bytes : bytes.subarray(0, room);\n chunks.push(kept);\n retained += kept.byteLength;\n };\n\n const appendTail = (bytes: Uint8Array): void => {\n const kept = bytes.byteLength <= maxBytes ? bytes : bytes.subarray(bytes.byteLength - maxBytes);\n chunks.push(kept);\n retained += kept.byteLength;\n // Drop whole leading chunks first, then trim the oldest survivor — O(chunks), no re-copying.\n while (retained > maxBytes && chunks.length > 0) {\n const oldest = chunks[0] as Uint8Array;\n const excess = retained - maxBytes;\n if (oldest.byteLength <= excess) {\n chunks.shift();\n retained -= oldest.byteLength;\n } else {\n chunks[0] = oldest.subarray(excess);\n retained -= excess;\n }\n }\n };\n\n return {\n append(chunk) {\n const bytes = typeof chunk === 'string' ? encoder.encode(chunk) : chunk;\n total += bytes.byteLength;\n if (retain === 'head') appendHead(bytes);\n else appendTail(bytes);\n },\n get retainedBytes() {\n return retained;\n },\n get totalBytes() {\n return total;\n },\n get droppedBytes() {\n return total - retained;\n },\n get truncated() {\n return total > retained;\n },\n toString() {\n const joined = new Uint8Array(retained);\n let offset = 0;\n for (const chunk of chunks) {\n joined.set(chunk, offset);\n offset += chunk.byteLength;\n }\n chunks = chunks.length > 1 ? [joined] : chunks;\n const text = new TextDecoder().decode(joined);\n if (total <= retained) return text;\n const note = marker(total - retained);\n return retain === 'head' ? `${text}${note}` : `${note.replace(/^\\n/, '')}\\n${text}`;\n },\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{f as e,s as t,t as n}from"./bounded-output-
|
|
2
|
-
//# sourceMappingURL=command-executor-
|
|
1
|
+
import{f as e,s as t,t as n}from"./bounded-output-D3cgiOrh.js";import{spawn as r}from"node:child_process";const i=1e6;function a(e,t){let r=n({maxBytes:i}),a=n({maxBytes:i,retain:`tail`});return e.stdout?.on(`data`,e=>r.append(e)),e.stderr?.on(`data`,e=>a.append(e)),e.stdin?.on(`error`,()=>{}),e.stdin?.write(t),e.stdin?.end(),{stdoutOutput:r,stderrOutput:a}}function o(e,t,n,r){if(e===0)return{outcome:`allow`,source:`command`,stdout:n};if(e===2)return{outcome:`deny`,source:`command`,reason:r.trim()||`Blocked by hook`};let i=r.trim()?`: ${r.trim()}`:``;return{outcome:`error`,source:`command`,kind:`nonzero-exit`,reason:e===null?`Hook terminated by signal ${t??`unknown`}${i}`:`Hook exited ${e}${i}`}}var s=class{shellExecutable;type=`command`;constructor(e){this.shellExecutable=e}execute(n,i,s){let c=n.timeout??600,l=c*1e3,u=JSON.stringify(i);return new Promise(d=>{let f=!1,p=e({executable:this.shellExecutable}),m=r(p.command,p.commandArgs(n.command),{cwd:i.cwd,env:t(process.env,s,i.env)}),{stdoutOutput:h,stderrOutput:g}=a(m,u),_=setTimeout(()=>{f||(f=!0,m.kill(`SIGTERM`),d({outcome:`error`,source:`command`,kind:`timeout`,reason:`Hook timed out after ${c}s`}))},l);m.on(`close`,(e,t)=>{f||(f=!0,clearTimeout(_),d(o(e,t,h.toString(),g.toString())))}),m.on(`error`,e=>{f||(f=!0,clearTimeout(_),d({outcome:`error`,source:`command`,kind:`spawn-failure`,reason:e.message}))})})}};export{s as CommandExecutor};
|
|
2
|
+
//# sourceMappingURL=command-executor-BsBTcghZ.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"command-executor-
|
|
1
|
+
{"version":3,"file":"command-executor-BsBTcghZ.js","names":[],"sources":["../../src/hooks/executors/command-executor.ts"],"sourcesContent":["/**\n * Command hook executor — executes shell commands with JSON input on stdin.\n *\n * Exit-code mapping (SEC-015):\n * - `0` → `allow`, stdout carried for the runner's response-protocol decode\n * - `2` → `deny`, stderr carried as the reason\n * - any other code → `error` / `nonzero-exit`\n * - killed by a signal (exit code `null`) → `error` / `nonzero-exit`, signal named in the reason\n * - deadline elapsed → `error` / `timeout`\n * - the process never started → `error` / `spawn-failure`\n *\n * The last four used to be one number. A missing binary exits `127` and a timeout resolved `1`, and\n * the runner discarded both alike, so a gate that never ran was indistinguishable from one that\n * approved.\n */\n\nimport { spawn } from 'node:child_process';\n\nimport { createBoundedOutput, type IBoundedOutput } from '../../utils/bounded-output.js';\nimport { resolvePlatformShell } from '../../utils/platform-shell.js';\nimport { subprocessTraceEnvironment } from '../../utils/trace-context.js';\n\nimport type { ISubprocessTraceEnv } from '../../interfaces/trace-context.js';\nimport type {\n ICommandHookDefinition,\n IHookInput,\n THookOutcome,\n IHookTypeExecutor,\n} from '../types.js';\n\n/** Default timeout in seconds — matches Claude Code's 600s default */\nconst DEFAULT_TIMEOUT_SECONDS = 600;\n/** ARCH-056: most bytes retained per hook stream (stdout head, stderr tail). */\nconst MAX_HOOK_OUTPUT_BYTES = 1_000_000;\n\n/**\n * How a finished process maps onto an outcome — a pure function of what the process reported.\n *\n * Extracted from `execute` because it is the part that carries the contract: `execute` owns spawning\n * and settling exactly once, this owns what the result MEANS, and the two change for different\n * reasons.\n */\n/**\n * Attach the output collectors and feed the hook input to stdin.\n *\n * Separated from `execute` so the promise body reads as the three things that can settle it —\n * timeout, close, spawn error — rather than as stream plumbing interleaved with them. The\n * `settled` guard those three share is the invariant worth keeping visible in one place.\n */\nfunction wireChildIo(\n child: ReturnType<typeof spawn>,\n inputJson: string,\n): { stdoutOutput: IBoundedOutput; stderrOutput: IBoundedOutput } {\n // ARCH-056: bounded while reading; a hook that floods stdout cannot grow the parent's memory.\n const stdoutOutput = createBoundedOutput({ maxBytes: MAX_HOOK_OUTPUT_BYTES });\n const stderrOutput = createBoundedOutput({ maxBytes: MAX_HOOK_OUTPUT_BYTES, retain: 'tail' });\n child.stdout?.on('data', (chunk: Buffer) => stdoutOutput.append(chunk));\n child.stderr?.on('data', (chunk: Buffer) => stderrOutput.append(chunk));\n child.stdin?.on('error', () => {\n // EPIPE: child closed stdin before we finished writing — safe to ignore\n });\n child.stdin?.write(inputJson);\n child.stdin?.end();\n return { stdoutOutput, stderrOutput };\n}\n\nfunction outcomeOfExit(\n code: number | null,\n signal: NodeJS.Signals | null,\n stdout: string,\n stderr: string,\n): THookOutcome {\n if (code === 0) return { outcome: 'allow', source: 'command', stdout };\n if (code === 2) {\n return { outcome: 'deny', source: 'command', reason: stderr.trim() || 'Blocked by hook' };\n }\n const detail = stderr.trim() ? `: ${stderr.trim()}` : '';\n return {\n outcome: 'error',\n source: 'command',\n kind: 'nonzero-exit',\n // `code === null` means a signal ended it. That is not an exit code and must not be read as\n // one — the old `code ?? 1` made a SIGKILLed hook indistinguishable from one that exited 1.\n reason:\n code === null\n ? `Hook terminated by signal ${signal ?? 'unknown'}${detail}`\n : `Hook exited ${code}${detail}`,\n };\n}\n\nexport class CommandExecutor implements IHookTypeExecutor {\n readonly type = 'command' as const;\n\n constructor(private readonly shellExecutable?: string) {}\n\n /**\n * `traceEnv` is the prompt's trace for this child, handed separately so it never reaches the\n * stdin JSON. The child sees `process.env`, then Robota's value, then the hook's own `env`; a hook\n * that sets its own `TRACEPARENT` wins and gets the ambient environment unchanged.\n */\n execute(\n definition: ICommandHookDefinition,\n input: IHookInput,\n traceEnv?: ISubprocessTraceEnv,\n ): Promise<THookOutcome> {\n const timeoutSeconds = definition.timeout ?? DEFAULT_TIMEOUT_SECONDS;\n const timeoutMs = timeoutSeconds * 1000;\n const inputJson = JSON.stringify(input);\n\n return new Promise<THookOutcome>((resolve) => {\n let settled = false;\n const shell = resolvePlatformShell({ executable: this.shellExecutable });\n const child = spawn(shell.command, shell.commandArgs(definition.command), {\n cwd: input.cwd,\n env: subprocessTraceEnvironment(process.env, traceEnv, input.env),\n });\n const { stdoutOutput, stderrOutput } = wireChildIo(child, inputJson);\n\n const timer = setTimeout(() => {\n if (!settled) {\n settled = true;\n child.kill('SIGTERM');\n resolve({\n outcome: 'error',\n source: 'command',\n kind: 'timeout',\n reason: `Hook timed out after ${timeoutSeconds}s`,\n });\n }\n }, timeoutMs);\n\n child.on('close', (code: number | null, signal: NodeJS.Signals | null) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve(outcomeOfExit(code, signal, stdoutOutput.toString(), stderrOutput.toString()));\n });\n\n child.on('error', (err: Error) => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n resolve({\n outcome: 'error',\n source: 'command',\n kind: 'spawn-failure',\n reason: err.message,\n });\n });\n });\n }\n}\n"],"mappings":"0GA+BA,MAEM,EAAwB,IAgB9B,SAAS,EACP,EACA,EACgE,CAEhE,IAAM,EAAe,EAAoB,CAAE,SAAU,CAAsB,CAAC,EACtE,EAAe,EAAoB,CAAE,SAAU,EAAuB,OAAQ,MAAO,CAAC,EAQ5F,OAPA,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,OAAO,CAAK,CAAC,EACtE,EAAM,QAAQ,GAAG,OAAS,GAAkB,EAAa,OAAO,CAAK,CAAC,EACtE,EAAM,OAAO,GAAG,YAAe,CAE/B,CAAC,EACD,EAAM,OAAO,MAAM,CAAS,EAC5B,EAAM,OAAO,IAAI,EACV,CAAE,eAAc,cAAa,CACtC,CAEA,SAAS,EACP,EACA,EACA,EACA,EACc,CACd,GAAI,IAAS,EAAG,MAAO,CAAE,QAAS,QAAS,OAAQ,UAAW,QAAO,EACrE,GAAI,IAAS,EACX,MAAO,CAAE,QAAS,OAAQ,OAAQ,UAAW,OAAQ,EAAO,KAAK,GAAK,iBAAkB,EAE1F,IAAM,EAAS,EAAO,KAAK,EAAI,KAAK,EAAO,KAAK,IAAM,GACtD,MAAO,CACL,QAAS,QACT,OAAQ,UACR,KAAM,eAGN,OACE,IAAS,KACL,6BAA6B,GAAU,YAAY,IACnD,eAAe,IAAO,GAC9B,CACF,CAEA,IAAa,EAAb,KAA0D,CAG3B,gBAF7B,KAAgB,UAEhB,YAAY,EAA2C,CAA1B,KAAA,gBAAA,CAA2B,CAOxD,QACE,EACA,EACA,EACuB,CACvB,IAAM,EAAiB,EAAW,SAAW,IACvC,EAAY,EAAiB,IAC7B,EAAY,KAAK,UAAU,CAAK,EAEtC,OAAO,IAAI,QAAuB,GAAY,CAC5C,IAAI,EAAU,GACR,EAAQ,EAAqB,CAAE,WAAY,KAAK,eAAgB,CAAC,EACjE,EAAQ,EAAM,EAAM,QAAS,EAAM,YAAY,EAAW,OAAO,EAAG,CACxE,IAAK,EAAM,IACX,IAAK,EAA2B,QAAQ,IAAK,EAAU,EAAM,GAAG,CAClE,CAAC,EACK,CAAE,eAAc,gBAAiB,EAAY,EAAO,CAAS,EAE7D,EAAQ,eAAiB,CACxB,IACH,EAAU,GACV,EAAM,KAAK,SAAS,EACpB,EAAQ,CACN,QAAS,QACT,OAAQ,UACR,KAAM,UACN,OAAQ,wBAAwB,EAAe,EACjD,CAAC,EAEL,EAAG,CAAS,EAEZ,EAAM,GAAG,SAAU,EAAqB,IAAkC,CACpE,IACJ,EAAU,GACV,aAAa,CAAK,EAClB,EAAQ,EAAc,EAAM,EAAQ,EAAa,SAAS,EAAG,EAAa,SAAS,CAAC,CAAC,EACvF,CAAC,EAED,EAAM,GAAG,QAAU,GAAe,CAC5B,IACJ,EAAU,GACV,aAAa,CAAK,EAClB,EAAQ,CACN,QAAS,QACT,OAAQ,UACR,KAAM,gBACN,OAAQ,EAAI,OACd,CAAC,EACH,CAAC,CACH,CAAC,CACH,CACF"}
|