@theokit/sdk 3.2.0 → 3.2.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +35 -0
- package/dist/a2a/index.cjs +85 -25
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +85 -25
- package/dist/a2a/index.js.map +1 -1
- package/dist/cron.cjs +85 -25
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.js +85 -25
- package/dist/cron.js.map +1 -1
- package/dist/eval.cjs +85 -40
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +85 -40
- package/dist/eval.js.map +1 -1
- package/dist/index.cjs +94 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +94 -26
- package/dist/index.js.map +1 -1
- package/dist/internal/persistence/pagination.d.cts +14 -1
- package/dist/internal/persistence/pagination.d.ts +14 -1
- package/dist/sandbox/index.cjs +8 -9
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.js +8 -9
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/types.d.cts +0 -1
- package/dist/sandbox/types.d.ts +0 -1
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 3.2.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- 1770aec: Fix (#59) — a stdio MCP client no longer permanently wedges after a transient outage exceeds the reconnect attempt bound. The bound is now LOCAL to each reconnect cycle (a bounded retry loop with backoff), so a later request re-arms a fresh cycle and reconnects once the server recovers — while a genuinely-broken server still surfaces a typed `mcp_disconnected` "reconnect exhausted". Adds the previously-missing HTTP-transport recovery test (stateless reconnect on the next request after a transport failure).
|
|
8
|
+
- cda0542: Fix (#60) — `Retry-After` now also parses the RFC-7231 HTTP-date form (`Retry-After: Wed, 21 Oct 2025 07:28:00 GMT`), converting it to seconds-until-then (clamped at 0 for a past date). Previously only the numeric-seconds form was honored; a date-form header was silently dropped. Clarified that the same-key 429 retry deliberately does not block on `Retry-After` (a multi-key pool rotates to a fresh key immediately; the cooldown is honored at pool-selection level).
|
|
9
|
+
- 510041b: Fix (#61) — the Anthropic streaming client now detects a truncated stream. A stream that closes cleanly-but-early (server FIN / proxy hiccup before the terminal `message_delta` carrying `stop_reason`) previously committed silently as a clean `end_turn`; it now throws a typed `NetworkError{code:"stream_truncated"}`, matching the OpenAI client's guard.
|
|
10
|
+
- f7d39c8: Fix (#63) — pagination now fails fast on invalid cursors. `paginate({ offset, limit })` rejected a `NaN` offset by silently returning the WHOLE list (and negative by returning empty); it now throws `ConfigurationError{code:"pagination_invalid"}` for any non-negative-integer offset/limit. Also adds real cross-process evidence for the conversation-storage file lock: two separate OS processes taking `withFileLock` on the same file are proven to serialize (previously only in-process concurrency was tested).
|
|
11
|
+
|
|
12
|
+
## 3.2.2
|
|
13
|
+
|
|
14
|
+
### Patch Changes
|
|
15
|
+
|
|
16
|
+
- 7f57c5a: Security (#55) — a delegated subagent now inherits the parent's code-registered plugins (e.g. `PermissionPlugin`), so its inner tool calls run under the SAME argument-level permission gate. Previously the child `Agent.create` received only apiKey/model/tools, so a parent that denied `shell` with a matching-arg rule did not stop a subagent it granted `shell` to — arg-level gating silently stopped at the delegation boundary.
|
|
17
|
+
- f81ac79: Fix (#58) — a run cancelled between tool iterations now reports `RunStatus: "cancelled"` instead of `"finished"`, so a caller can distinguish a cancellation from a clean completion. Previously the between-iteration abort break left the default `"finished"` status.
|
|
18
|
+
- 57cfcc8: Fix (#58) — `JobQueue.cancel()` on a running job that ignores its `AbortSignal` (never settles) previously leaked its concurrency slot, deadlocking a `maxConcurrency`-bounded queue. Cancel now frees the slot immediately; `#release` is idempotent so the job's eventual settle is a no-op (no double-free).
|
|
19
|
+
- bd06140: Fix (#65) — the `transform_llm_output` plugin hook now rewrites the FINAL user-visible / streamed assistant text, and fires on text-only terminal turns. Previously it ran only in the tool-call branch and folded only into internal message history, so a plugin could not scrub what the caller actually received. The transform is now applied once, up front, and flows into the emitted step, `finalText`/`result`, and message history alike.
|
|
20
|
+
|
|
21
|
+
## 3.2.1
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- a283dd4: Security (#54) — harden child-process env scrubbing.
|
|
26
|
+
|
|
27
|
+
- The `inherit-scrubbed` denylist now also drops the highest-signal VALUE-embedded-secret conventions: connection strings that carry `user:password@` (`DATABASE_URL`, `REDIS_URL`, `MONGODB_URI`, `DB_URL`, …), plus `DSN`, `WEBHOOK`, `COOKIE`, and `CONNECTION_STRING`. Generic non-secret URLs (`PUBLIC_BASE_URL`, `API_URL`, `PGHOST`) are deliberately preserved. A denylist still cannot catch every value-embedded secret — policy `core` (allowlist) remains the fail-closed mode for untrusted children.
|
|
28
|
+
- Removed dead `validateCommand` / `SHELL_METACHARACTERS` from the sandbox base — a never-invoked "guard" that provided a false sense of protection.
|
|
29
|
+
- Added an end-to-end test proving `LocalSandbox.execute` scrubs secret-like host env vars from the real child process.
|
|
30
|
+
|
|
31
|
+
- 826bca0: Security (#56) — close two residual cross-tenant active-recall cache leaks found by adversarial review.
|
|
32
|
+
|
|
33
|
+
- `@theokit/sdk-memory` (publishable) called `cache.get`/`cache.set` with no tenant context, so two callers with the same query text but different identity shared a cached recall — a cross-tenant leak for every consumer of the package. The cache read/write are now keyed by the `{namespace, userId, scope}` tenant tuple (the primitive already supported it).
|
|
34
|
+
- In `@theokit/sdk` the production caller hardcoded `namespace: "default"` and dropped `memoryContext.tenantId`, so two tenants sharing a `userId` collided on one cache entry. The caller now threads `memoryContext.tenantId` into the tenant partition (`namespace`). `sessionId` is intentionally not a key dimension — recall is cross-session by design.
|
|
35
|
+
|
|
36
|
+
- 7bcc872: Security/correctness (#59) — the HTTP MCP body read (`response.json()`) was outside the abort try/catch, so a server that returned headers then stalled the body surfaced a raw `DOMException(TimeoutError)` instead of the typed `NetworkError{code:"mcp_timeout"}`. The request was still bounded (no hang), but the typed-timeout contract now holds across both the header and body phases.
|
|
37
|
+
|
|
3
38
|
## 3.2.0
|
|
4
39
|
|
|
5
40
|
### Minor Changes
|
package/dist/a2a/index.cjs
CHANGED
|
@@ -4743,7 +4743,14 @@ var init_env_policy = __esm({
|
|
|
4743
4743
|
/[_-]PWD/i,
|
|
4744
4744
|
/CREDENTIAL/i,
|
|
4745
4745
|
/PRIVATE/i,
|
|
4746
|
-
/_AUTH/i
|
|
4746
|
+
/_AUTH/i,
|
|
4747
|
+
// #54-a — value-embedded-secret conventions (no generic `*_URL` — see keep-list test).
|
|
4748
|
+
/DSN/i,
|
|
4749
|
+
/WEBHOOK/i,
|
|
4750
|
+
/COOKIE/i,
|
|
4751
|
+
/CONNECTION[_-]?STRING/i,
|
|
4752
|
+
// Known DB / message-broker connection-string vars (carry `user:pass@`).
|
|
4753
|
+
/(?:^|[_-])(?:DATABASE|DB|REDIS|MONGO(?:DB)?|POSTGRES(?:QL)?|MYSQL|MARIADB|AMQP|RABBITMQ|CLICKHOUSE|ELASTIC(?:SEARCH)?|CASSANDRA|COUCHDB|MEMCACHED|NATS|KAFKA)[_-]?(?:URL|URI|DSN|CONNECTION)/i
|
|
4747
4754
|
];
|
|
4748
4755
|
CORE_VARS = [
|
|
4749
4756
|
"PATH",
|
|
@@ -5152,12 +5159,23 @@ var init_objective_coerce = __esm({
|
|
|
5152
5159
|
// src/internal/persistence/pagination.ts
|
|
5153
5160
|
function paginate(items, opts) {
|
|
5154
5161
|
if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
|
|
5155
|
-
const start =
|
|
5156
|
-
const
|
|
5162
|
+
const start = opts.offset === void 0 ? 0 : requireNonNegativeInt(opts.offset, "offset");
|
|
5163
|
+
const limit = opts.limit === void 0 ? void 0 : requireNonNegativeInt(opts.limit, "limit");
|
|
5164
|
+
const end = limit === void 0 ? items.length : start + limit;
|
|
5157
5165
|
return items.slice(start, end);
|
|
5158
5166
|
}
|
|
5167
|
+
function requireNonNegativeInt(value, field) {
|
|
5168
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
5169
|
+
throw new ConfigurationError(
|
|
5170
|
+
`Invalid pagination ${field}: expected a non-negative integer, got ${value}`,
|
|
5171
|
+
{ code: "pagination_invalid" }
|
|
5172
|
+
);
|
|
5173
|
+
}
|
|
5174
|
+
return value;
|
|
5175
|
+
}
|
|
5159
5176
|
var init_pagination = __esm({
|
|
5160
5177
|
"src/internal/persistence/pagination.ts"() {
|
|
5178
|
+
init_errors();
|
|
5161
5179
|
}
|
|
5162
5180
|
});
|
|
5163
5181
|
|
|
@@ -9498,7 +9516,10 @@ async function runAgentLoop(inputs) {
|
|
|
9498
9516
|
}
|
|
9499
9517
|
budget.consume();
|
|
9500
9518
|
inputs.budgetTracker?.nextIteration?.();
|
|
9501
|
-
if (inputs.signal?.aborted === true)
|
|
9519
|
+
if (inputs.signal?.aborted === true) {
|
|
9520
|
+
ctx.finalStatus = "cancelled";
|
|
9521
|
+
break;
|
|
9522
|
+
}
|
|
9502
9523
|
}
|
|
9503
9524
|
if (lastTurnDecision === "continue" && budget.shouldContinue() === false) {
|
|
9504
9525
|
ctx.stoppedAtIterationLimit = true;
|
|
@@ -9683,15 +9704,15 @@ async function guardAndTransformToolResults(inputs, raw, ctx) {
|
|
|
9683
9704
|
}
|
|
9684
9705
|
async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
9685
9706
|
if (llmOutput.errored) return "error";
|
|
9686
|
-
|
|
9687
|
-
|
|
9707
|
+
const tCtx = { agentId: inputs.agentId, runId: inputs.runId };
|
|
9708
|
+
const text = llmOutput.text.length > 0 ? await transformLlmOutputText(inputs, llmOutput.text, tCtx) : llmOutput.text;
|
|
9709
|
+
if (text.length > 0) {
|
|
9710
|
+
await emitAssistantTextStep(inputs, ctx, text);
|
|
9688
9711
|
}
|
|
9689
9712
|
if (llmOutput.stopReason !== "tool_use" || llmOutput.toolCalls.length === 0) {
|
|
9690
9713
|
return finishOrReflect(inputs, ctx, llmOutput);
|
|
9691
9714
|
}
|
|
9692
|
-
|
|
9693
|
-
const outText = await transformLlmOutputText(inputs, llmOutput.text, tCtx);
|
|
9694
|
-
ctx.messages.push(buildAssistantTurn(outText, llmOutput.toolCalls));
|
|
9715
|
+
ctx.messages.push(buildAssistantTurn(text, llmOutput.toolCalls));
|
|
9695
9716
|
const rawResults = await dispatchTools(
|
|
9696
9717
|
// SE12 — forward a read-only text projection of the transcript-so-far to tool
|
|
9697
9718
|
// handlers via `ctx.messages` (consumed by defineSubAgent's messageFilter).
|
|
@@ -10155,6 +10176,8 @@ function parseRetryAfter(headers) {
|
|
|
10155
10176
|
if (raw === null) return void 0;
|
|
10156
10177
|
const n = Number(raw);
|
|
10157
10178
|
if (Number.isFinite(n) && n >= 0) return Math.ceil(n);
|
|
10179
|
+
const dateMs = Date.parse(raw);
|
|
10180
|
+
if (Number.isFinite(dateMs)) return Math.max(0, Math.ceil((dateMs - Date.now()) / 1e3));
|
|
10158
10181
|
return void 0;
|
|
10159
10182
|
}
|
|
10160
10183
|
function truncateRaw(body) {
|
|
@@ -10475,6 +10498,7 @@ function buildAnthropicBody(request) {
|
|
|
10475
10498
|
var AnthropicClient, AnthropicStreamAccumulator;
|
|
10476
10499
|
var init_anthropic3 = __esm({
|
|
10477
10500
|
"src/internal/llm/anthropic.ts"() {
|
|
10501
|
+
init_errors();
|
|
10478
10502
|
init_anthropic2();
|
|
10479
10503
|
init_anthropic_shared();
|
|
10480
10504
|
init_finish();
|
|
@@ -10528,12 +10552,23 @@ var init_anthropic3 = __esm({
|
|
|
10528
10552
|
const events = accumulator.consume(parsed);
|
|
10529
10553
|
for (const event of events) yield event;
|
|
10530
10554
|
}
|
|
10555
|
+
if (!accumulator.finishReasonSeen) {
|
|
10556
|
+
throw new NetworkError("Anthropic SSE stream truncated (no stop_reason)", {
|
|
10557
|
+
code: "stream_truncated"
|
|
10558
|
+
});
|
|
10559
|
+
}
|
|
10531
10560
|
return accumulator.finish();
|
|
10532
10561
|
}
|
|
10533
10562
|
};
|
|
10534
10563
|
AnthropicStreamAccumulator = class {
|
|
10535
10564
|
text = "";
|
|
10536
10565
|
stopReason = "end_turn";
|
|
10566
|
+
/**
|
|
10567
|
+
* M2 #61 — whether a `message_delta` carrying a real `stop_reason` was seen.
|
|
10568
|
+
* A stream that closes before it is a truncation (server FIN / proxy hiccup),
|
|
10569
|
+
* not a clean `end_turn` — the caller throws `stream_truncated` on `false`.
|
|
10570
|
+
*/
|
|
10571
|
+
sawStopReason = false;
|
|
10537
10572
|
inputTokens;
|
|
10538
10573
|
outputTokens;
|
|
10539
10574
|
cacheReadTokens;
|
|
@@ -10580,6 +10615,9 @@ var init_anthropic3 = __esm({
|
|
|
10580
10615
|
* spinning the SSE parser.
|
|
10581
10616
|
*/
|
|
10582
10617
|
handleMessageDelta(md) {
|
|
10618
|
+
if (md.delta.stop_reason !== void 0 && md.delta.stop_reason !== null) {
|
|
10619
|
+
this.sawStopReason = true;
|
|
10620
|
+
}
|
|
10583
10621
|
this.stopReason = mapAnthropicStopReason(md.delta.stop_reason);
|
|
10584
10622
|
if (md.usage?.input_tokens !== void 0) this.inputTokens = md.usage.input_tokens;
|
|
10585
10623
|
if (md.usage?.output_tokens !== void 0) this.outputTokens = md.usage.output_tokens;
|
|
@@ -10590,6 +10628,10 @@ var init_anthropic3 = __esm({
|
|
|
10590
10628
|
this.cacheReadTokens = md.usage.cache_read_input_tokens;
|
|
10591
10629
|
}
|
|
10592
10630
|
}
|
|
10631
|
+
/** M2 #61 — whether the terminal `message_delta` (stop_reason) was seen. */
|
|
10632
|
+
get finishReasonSeen() {
|
|
10633
|
+
return this.sawStopReason;
|
|
10634
|
+
}
|
|
10593
10635
|
finish() {
|
|
10594
10636
|
const toolCalls = [];
|
|
10595
10637
|
for (const [index, tool] of this.toolCalls.entries()) {
|
|
@@ -12823,7 +12865,6 @@ var init_client = __esm({
|
|
|
12823
12865
|
// concurrent request so parallel tool dispatch after a drop awaits one handshake
|
|
12824
12866
|
// instead of racing (or spuriously failing with mcp_not_init).
|
|
12825
12867
|
dropped = false;
|
|
12826
|
-
reconnectAttempts = 0;
|
|
12827
12868
|
reconnectPromise;
|
|
12828
12869
|
get timeoutMs() {
|
|
12829
12870
|
return this.config.requestTimeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;
|
|
@@ -12876,15 +12917,22 @@ var init_client = __esm({
|
|
|
12876
12917
|
return this.reconnectPromise;
|
|
12877
12918
|
}
|
|
12878
12919
|
async reconnect() {
|
|
12879
|
-
|
|
12880
|
-
|
|
12920
|
+
let lastErr;
|
|
12921
|
+
for (let attempt = 0; attempt < MAX_RECONNECT_ATTEMPTS; attempt += 1) {
|
|
12922
|
+
await reconnectDelay(attempt);
|
|
12923
|
+
this.spawnChild();
|
|
12924
|
+
try {
|
|
12925
|
+
await super.initialize();
|
|
12926
|
+
this.dropped = false;
|
|
12927
|
+
return;
|
|
12928
|
+
} catch (err) {
|
|
12929
|
+
lastErr = err;
|
|
12930
|
+
}
|
|
12881
12931
|
}
|
|
12882
|
-
|
|
12883
|
-
|
|
12884
|
-
|
|
12885
|
-
|
|
12886
|
-
this.dropped = false;
|
|
12887
|
-
this.reconnectAttempts = 0;
|
|
12932
|
+
throw new NetworkError(`MCP ${this.name} reconnect exhausted`, {
|
|
12933
|
+
code: "mcp_disconnected",
|
|
12934
|
+
...lastErr instanceof Error ? { cause: lastErr } : {}
|
|
12935
|
+
});
|
|
12888
12936
|
}
|
|
12889
12937
|
async close() {
|
|
12890
12938
|
this.rejectAllPending(new NetworkError(`MCP ${this.name} closed`, { code: "mcp_closed" }));
|
|
@@ -13015,7 +13063,12 @@ var init_client = __esm({
|
|
|
13015
13063
|
code: "mcp_http_error"
|
|
13016
13064
|
});
|
|
13017
13065
|
}
|
|
13018
|
-
|
|
13066
|
+
try {
|
|
13067
|
+
return await response.json();
|
|
13068
|
+
} catch (cause) {
|
|
13069
|
+
if (isAbortLike(cause)) throw mcpTimeoutError(this.name, timeoutMs);
|
|
13070
|
+
throw cause;
|
|
13071
|
+
}
|
|
13019
13072
|
}
|
|
13020
13073
|
};
|
|
13021
13074
|
}
|
|
@@ -13255,9 +13308,11 @@ function declarativeSubagentTools(agentOptions, parentTools) {
|
|
|
13255
13308
|
return subAgentToolsFromDefinitions(agents2, parentTools);
|
|
13256
13309
|
}
|
|
13257
13310
|
function bindParentCredentials(tools, agentOptions) {
|
|
13311
|
+
const parentPlugins = Array.isArray(agentOptions.plugins) ? agentOptions.plugins : void 0;
|
|
13258
13312
|
const credentials = {
|
|
13259
13313
|
...agentOptions.apiKey !== void 0 ? { apiKey: agentOptions.apiKey } : {},
|
|
13260
|
-
...typeof agentOptions.model === "object" ? { model: agentOptions.model } : {}
|
|
13314
|
+
...typeof agentOptions.model === "object" ? { model: agentOptions.model } : {},
|
|
13315
|
+
...parentPlugins !== void 0 ? { plugins: parentPlugins } : {}
|
|
13261
13316
|
};
|
|
13262
13317
|
for (const tool of tools) inheritSubAgentCredentials(tool, credentials);
|
|
13263
13318
|
}
|
|
@@ -15782,10 +15837,11 @@ var init_local_agent_memory = __esm({
|
|
|
15782
15837
|
}
|
|
15783
15838
|
buildTelemetryRecallArgs(telemetry) {
|
|
15784
15839
|
const userId = typeof this.options.memoryContext?.userId === "string" ? this.options.memoryContext.userId : void 0;
|
|
15840
|
+
const tenantId = typeof this.options.memoryContext?.tenantId === "string" ? this.options.memoryContext.tenantId : void 0;
|
|
15785
15841
|
return {
|
|
15786
15842
|
...telemetry !== void 0 ? { telemetry } : {},
|
|
15787
15843
|
...userId !== void 0 ? { userId } : {},
|
|
15788
|
-
namespace: "default",
|
|
15844
|
+
namespace: tenantId ?? "default",
|
|
15789
15845
|
scope: "session"
|
|
15790
15846
|
};
|
|
15791
15847
|
}
|
|
@@ -19119,15 +19175,19 @@ async function collectChildToolResults(run) {
|
|
|
19119
19175
|
${lines.join("\n")}
|
|
19120
19176
|
</subagent-tool-results>`;
|
|
19121
19177
|
}
|
|
19122
|
-
|
|
19123
|
-
const { Agent: Agent2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
|
|
19178
|
+
function buildChildCreateOptions(spec, inherited) {
|
|
19124
19179
|
const model = spec.model ? { id: spec.model } : inherited?.model;
|
|
19125
|
-
|
|
19180
|
+
return {
|
|
19126
19181
|
...inherited?.apiKey !== void 0 ? { apiKey: inherited.apiKey } : {},
|
|
19127
19182
|
...model !== void 0 ? { model } : {},
|
|
19183
|
+
...inherited?.plugins !== void 0 ? { plugins: inherited.plugins } : {},
|
|
19128
19184
|
systemPrompt: spec.instructions,
|
|
19129
19185
|
tools: spec.tools ?? []
|
|
19130
|
-
}
|
|
19186
|
+
};
|
|
19187
|
+
}
|
|
19188
|
+
async function runChildAgent(spec, input, signal, maxSteps, inherited) {
|
|
19189
|
+
const { Agent: Agent2 } = await Promise.resolve().then(() => (init_agent(), agent_exports));
|
|
19190
|
+
const agent = await Agent2.create(buildChildCreateOptions(spec, inherited));
|
|
19131
19191
|
try {
|
|
19132
19192
|
const sendOptions = {
|
|
19133
19193
|
...signal !== void 0 ? { signal } : {},
|