@wrongstack/core 0.306.2 → 0.306.4
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/dist/coordination/index.js +184 -86
- package/dist/coordination/mail-tools.d.ts +1 -1
- package/dist/coordination/mailbox-project-server.js +38 -16
- package/dist/coordination/sqlite-mailbox.d.ts +1 -0
- package/dist/core/conversation-state.d.ts +1 -2
- package/dist/core/fallback-model.d.ts +19 -1
- package/dist/core/fallback-profile-manager.d.ts +21 -3
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +152 -46
- package/dist/defaults/index.js +347 -139
- package/dist/execution/auto-compaction-middleware.d.ts +64 -0
- package/dist/execution/compaction-core.d.ts +4 -0
- package/dist/execution/index.d.ts +1 -1
- package/dist/execution/index.js +234 -91
- package/dist/execution/retry-policy.d.ts +27 -0
- package/dist/hq/index.js +50 -15
- package/dist/index.d.ts +1 -1
- package/dist/index.js +642 -184
- package/dist/infrastructure/index.js +21 -0
- package/dist/plugin/index.js +195 -19
- package/dist/security/index.js +52 -16
- package/dist/security/kanban-boundary.d.ts +3 -1
- package/dist/session-catalog/index.js +50 -15
- package/dist/session-catalog/project-server.js +50 -15
- package/dist/storage/cloud-config-sync/sanitize.d.ts +18 -0
- package/dist/storage/cloud-config-sync.d.ts +1 -1
- package/dist/storage/index.js +326 -71
- package/dist/storage/provider-config-watcher.d.ts +9 -0
- package/dist/storage/session-store/strict-empty-check.d.ts +7 -0
- package/dist/storage/session-store.d.ts +1 -0
- package/dist/tools/index.d.ts +1 -1
- package/dist/tools/index.js +83 -22
- package/dist/types/blocks.d.ts +9 -0
- package/dist/types/config/root.d.ts +14 -0
- package/dist/types/session.d.ts +7 -0
- package/instructions/agents/code-reviewer.md +3 -0
- package/instructions/coordination/subagent-baseline.md +10 -1
- package/instructions/system-lite.md +8 -5
- package/instructions/system-pro.md +26 -0
- package/instructions/system.md +21 -0
- package/package.json +3 -3
|
@@ -15,6 +15,13 @@
|
|
|
15
15
|
*
|
|
16
16
|
* Stripping is therefore structural (projection), with `stripSecretMaterial` as a final
|
|
17
17
|
* defense-in-depth pass mirroring the server's own scan.
|
|
18
|
+
*
|
|
19
|
+
* The two directions do NOT share one gate. "What may leave this machine" and "what may
|
|
20
|
+
* a remote peer write into the trusted profile config" are different questions, and the
|
|
21
|
+
* outbound tree is a wrong — dangerously permissive — answer to the second: it names
|
|
22
|
+
* `mcpServers.*.command`, `plugins`, `providers.*.baseUrl` and `yolo` as contract-owned,
|
|
23
|
+
* because pushing them is harmless. Pulls are therefore gated on `INBOUND_CONTRACT`, the
|
|
24
|
+
* outbound tree minus `INBOUND_DENIED_PATHS`.
|
|
18
25
|
*/
|
|
19
26
|
/** `true` = the whole value is contract-owned; an object = recurse per key; `'*'` = record wildcard. */
|
|
20
27
|
export type ContractNode = true | {
|
|
@@ -23,6 +30,17 @@ export type ContractNode = true | {
|
|
|
23
30
|
/** Namespace → the contract tree over the `config.json` top-level keys it owns. */
|
|
24
31
|
export declare const CLOUD_SYNC_CONTRACT: Readonly<Record<string, ContractNode>>;
|
|
25
32
|
export declare const CLOUD_SYNC_NAMESPACES: string[];
|
|
33
|
+
/**
|
|
34
|
+
* Every inbound-denied path must still resolve against the outbound contract.
|
|
35
|
+
*
|
|
36
|
+
* A denylist that silently stops matching is worse than no denylist: it reads
|
|
37
|
+
* as protection while granting the field. Renaming `mcpServers.*.command` in
|
|
38
|
+
* the contract without updating the entry here must break the build, not
|
|
39
|
+
* quietly re-open remote code execution.
|
|
40
|
+
*/
|
|
41
|
+
export declare function assertInboundDenyListResolves(): void;
|
|
42
|
+
/** Exposed for tests and diagnostics — the effective pull-side allow tree. */
|
|
43
|
+
export declare function inboundContractFor(namespace: string): ContractNode | undefined;
|
|
26
44
|
/** Server-side schema versions, sent in every push envelope. */
|
|
27
45
|
export declare const NAMESPACE_SCHEMA_VERSIONS: Readonly<Record<string, number>>;
|
|
28
46
|
/**
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { applyNamespacePayload, buildNamespacePayloads, CLOUD_SYNC_CONTRACT, CLOUD_SYNC_NAMESPACES, LOCAL_ONLY_TOP_LEVEL, NAMESPACE_SCHEMA_VERSIONS, stripSecretMaterial, } from './cloud-config-sync/sanitize.js';
|
|
1
|
+
export { applyNamespacePayload, assertInboundDenyListResolves, buildNamespacePayloads, CLOUD_SYNC_CONTRACT, CLOUD_SYNC_NAMESPACES, inboundContractFor, LOCAL_ONLY_TOP_LEVEL, NAMESPACE_SCHEMA_VERSIONS, stripSecretMaterial, } from './cloud-config-sync/sanitize.js';
|
|
2
2
|
/**
|
|
3
3
|
* CloudConfigSync — synchronizes the profile config with my.wrongstack.com over the
|
|
4
4
|
* namespaced sync API v1 (server repo: docs/CLIENT_SYNC_CONTRACT.md).
|
package/dist/storage/index.js
CHANGED
|
@@ -1710,6 +1710,24 @@ function resolveWstackPaths(opts) {
|
|
|
1710
1710
|
}
|
|
1711
1711
|
|
|
1712
1712
|
// src/security/secret-scrubber.ts
|
|
1713
|
+
var JSON_CREDENTIAL_KEY_ANCHORS = [
|
|
1714
|
+
'Key"',
|
|
1715
|
+
'key"',
|
|
1716
|
+
'KEY"',
|
|
1717
|
+
'token"',
|
|
1718
|
+
'Token"',
|
|
1719
|
+
'TOKEN"',
|
|
1720
|
+
'secret"',
|
|
1721
|
+
'Secret"',
|
|
1722
|
+
'SECRET"',
|
|
1723
|
+
'password"',
|
|
1724
|
+
'Password"',
|
|
1725
|
+
'PASSWORD"',
|
|
1726
|
+
'authorization"',
|
|
1727
|
+
'Authorization"',
|
|
1728
|
+
'bearer"',
|
|
1729
|
+
'Bearer"'
|
|
1730
|
+
];
|
|
1713
1731
|
var PATTERNS = [
|
|
1714
1732
|
// Anchored at the start where possible so partial matches inside larger
|
|
1715
1733
|
// strings don't trigger false positives.
|
|
@@ -1812,6 +1830,30 @@ var PATTERNS = [
|
|
|
1812
1830
|
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
1813
1831
|
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
|
|
1814
1832
|
},
|
|
1833
|
+
{
|
|
1834
|
+
type: "json_credential_key",
|
|
1835
|
+
// The JSON counterpart to `high_entropy_env`, and the pattern that the
|
|
1836
|
+
// now-deleted `JSON_KEY_ANCHORS` list was written for. Without it those
|
|
1837
|
+
// anchors only widened the cheap pre-scan — `hasCredentialAnchors` said
|
|
1838
|
+
// "this text may hold a secret", every pattern then declined to match, and
|
|
1839
|
+
// the value went out verbatim. `high_entropy_env` cannot cover these: it
|
|
1840
|
+
// requires an UPPERCASE unquoted key (`API_KEY=…`), so `{"apiKey":"…"}`
|
|
1841
|
+
// never matched.
|
|
1842
|
+
//
|
|
1843
|
+
// Tool results are routinely serialised as JSON, and a credential with no
|
|
1844
|
+
// recognisable prefix (Azure, self-hosted gateways, Anthropic/Codex OAuth)
|
|
1845
|
+
// has no other pattern that can catch it — this is the only thing standing
|
|
1846
|
+
// between such a value and the session JSONL, chronicle, HQ broadcast and
|
|
1847
|
+
// the model's own context.
|
|
1848
|
+
//
|
|
1849
|
+
// The key may carry a prefix (`"anthropicApiKey"`), but the credential word
|
|
1850
|
+
// must END the key: `"tokenCount"` and `"maxTokens"` do not match, because
|
|
1851
|
+
// the closing quote has to follow the word immediately.
|
|
1852
|
+
// Value floor of 8 chars keeps enum-ish values (`"authorization":"none"`)
|
|
1853
|
+
// out. Capture groups: 1=key + punctuation, 2=value, 3=closing quote.
|
|
1854
|
+
regex: /("[A-Za-z0-9_]*(?:apiKey|api_key|token|secret|password|authorization|bearer|private_key|access_token|refresh_token|client_secret)"\s*:\s*")([^"\\]{8,512})(")/gi,
|
|
1855
|
+
anchor: JSON_CREDENTIAL_KEY_ANCHORS
|
|
1856
|
+
},
|
|
1815
1857
|
// ── Ported from packages/plugins credential-patterns.ts (WS-034) ─────────
|
|
1816
1858
|
// The plugin runtime carried 37 patterns while this scrubber — the one that
|
|
1817
1859
|
// guards session JSONL, chronicle, HQ broadcast, WebUI events and the auth
|
|
@@ -1894,9 +1936,12 @@ var PATTERNS = [
|
|
|
1894
1936
|
anchor: "GOCSPX-"
|
|
1895
1937
|
}
|
|
1896
1938
|
];
|
|
1897
|
-
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
1939
|
+
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
1940
|
+
(p) => p.type !== "high_entropy_env" && p.type !== "json_credential_key"
|
|
1941
|
+
);
|
|
1898
1942
|
var COMBINED_REGEX = new RegExp(SIMPLE_PATTERNS.map((p) => `(${p.regex.source})`).join("|"), "g");
|
|
1899
1943
|
var HIGH_ENTROPY_REGEX = PATTERNS.find((p) => p.type === "high_entropy_env").regex;
|
|
1944
|
+
var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key").regex;
|
|
1900
1945
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
1901
1946
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
1902
1947
|
var SCRUB_OVERLAP_BYTES = 1024;
|
|
@@ -1907,20 +1952,7 @@ var PATTERN_ANCHORS = [
|
|
|
1907
1952
|
)
|
|
1908
1953
|
)
|
|
1909
1954
|
];
|
|
1910
|
-
var
|
|
1911
|
-
'"apiKey"',
|
|
1912
|
-
'"api_key"',
|
|
1913
|
-
'"token"',
|
|
1914
|
-
'"secret"',
|
|
1915
|
-
'"password"',
|
|
1916
|
-
'"authorization"',
|
|
1917
|
-
'"bearer"',
|
|
1918
|
-
'"private_key"',
|
|
1919
|
-
'"access_token"',
|
|
1920
|
-
'"refresh_token"',
|
|
1921
|
-
'"client_secret"'
|
|
1922
|
-
];
|
|
1923
|
-
var ALL_ANCHORS = [...PATTERN_ANCHORS, ...JSON_KEY_ANCHORS];
|
|
1955
|
+
var ALL_ANCHORS = PATTERN_ANCHORS;
|
|
1924
1956
|
function hasCredentialAnchors(text) {
|
|
1925
1957
|
for (const anchor of ALL_ANCHORS) {
|
|
1926
1958
|
if (text.includes(anchor)) return true;
|
|
@@ -1969,6 +2001,9 @@ var DefaultSecretScrubber = class {
|
|
|
1969
2001
|
out = out.replace(HIGH_ENTROPY_REGEX, (_match, lead, key, _value) => {
|
|
1970
2002
|
return `${lead}${key}=[REDACTED:high_entropy_env]`;
|
|
1971
2003
|
});
|
|
2004
|
+
out = out.replace(JSON_CREDENTIAL_REGEX, (_match, keyPrefix, _value, closingQuote) => {
|
|
2005
|
+
return `${keyPrefix}[REDACTED:json_credential_key]${closingQuote}`;
|
|
2006
|
+
});
|
|
1972
2007
|
return out;
|
|
1973
2008
|
}
|
|
1974
2009
|
/**
|
|
@@ -2427,8 +2462,8 @@ var SessionRegistry = class {
|
|
|
2427
2462
|
if (id !== entry.sessionId) delete registry[id];
|
|
2428
2463
|
continue;
|
|
2429
2464
|
}
|
|
2430
|
-
const
|
|
2431
|
-
if (
|
|
2465
|
+
const heartbeatAt = Date.parse(existing.lastHeartbeatAt);
|
|
2466
|
+
if (!Number.isFinite(heartbeatAt) || now - heartbeatAt > PID_CHECK_AFTER_MS && !pidAlive2(existing.pid)) {
|
|
2432
2467
|
delete registry[id];
|
|
2433
2468
|
}
|
|
2434
2469
|
}
|
|
@@ -3446,6 +3481,156 @@ var CLOUD_SYNC_CONTRACT = {
|
|
|
3446
3481
|
"extensions.plugins": EXTENSIONS_PLUGINS_TREE
|
|
3447
3482
|
};
|
|
3448
3483
|
var CLOUD_SYNC_NAMESPACES = Object.keys(CLOUD_SYNC_CONTRACT);
|
|
3484
|
+
var INBOUND_DENIED_PATHS = [
|
|
3485
|
+
// ── Code execution ──────────────────────────────────────────────────────
|
|
3486
|
+
{
|
|
3487
|
+
namespace: "mcp.servers",
|
|
3488
|
+
path: "mcpServers.*.command",
|
|
3489
|
+
reason: "Executable spawned for a stdio MCP server."
|
|
3490
|
+
},
|
|
3491
|
+
{
|
|
3492
|
+
namespace: "mcp.servers",
|
|
3493
|
+
path: "mcpServers.*.args",
|
|
3494
|
+
reason: "Argv for that executable."
|
|
3495
|
+
},
|
|
3496
|
+
{
|
|
3497
|
+
namespace: "mcp.servers",
|
|
3498
|
+
path: "mcpServers.*.transport",
|
|
3499
|
+
reason: "Switching transport to stdio selects the spawning code path."
|
|
3500
|
+
},
|
|
3501
|
+
{
|
|
3502
|
+
namespace: "extensions.plugins",
|
|
3503
|
+
path: "plugins",
|
|
3504
|
+
reason: "Plugin list is resolved and `await import`ed."
|
|
3505
|
+
},
|
|
3506
|
+
// `extensions` is deliberately NOT denied: it is per-plugin settings read via
|
|
3507
|
+
// `ConfigStore.getExtension(name)`, not a loader list, so it grants no import.
|
|
3508
|
+
// Syncing it is the feature (`extensions.telegram.notifyChatId` and friends).
|
|
3509
|
+
// Residual risk accepted: a plugin whose own settings include a URL can have
|
|
3510
|
+
// that URL rewritten by the portal. Constrain that in the plugin's
|
|
3511
|
+
// `configSchema`, which is where the loader validates it.
|
|
3512
|
+
// ── Credential redirection / exfiltration ───────────────────────────────
|
|
3513
|
+
{
|
|
3514
|
+
namespace: "providers.catalog",
|
|
3515
|
+
path: "providers.*.baseUrl",
|
|
3516
|
+
reason: "Repoints the provider endpoint; reinjectLocalSecrets keeps the local apiKey, so the real key follows the redirect."
|
|
3517
|
+
},
|
|
3518
|
+
{
|
|
3519
|
+
namespace: "providers.catalog",
|
|
3520
|
+
path: "providers.*.envVars",
|
|
3521
|
+
reason: "Chooses which environment variable is read for the key."
|
|
3522
|
+
},
|
|
3523
|
+
{
|
|
3524
|
+
namespace: "providers.catalog",
|
|
3525
|
+
path: "providers.*.activeKey",
|
|
3526
|
+
reason: "Selects which stored key is sent."
|
|
3527
|
+
},
|
|
3528
|
+
{
|
|
3529
|
+
namespace: "mcp.servers",
|
|
3530
|
+
path: "mcpServers.*.url",
|
|
3531
|
+
reason: "Remote MCP endpoint; receives whatever the transport carries."
|
|
3532
|
+
},
|
|
3533
|
+
{
|
|
3534
|
+
namespace: "mcp.servers",
|
|
3535
|
+
path: "mcpServers.*.envVars",
|
|
3536
|
+
reason: "Names of environment variables forwarded to the server process."
|
|
3537
|
+
},
|
|
3538
|
+
// ── Operator-owned safety switches ──────────────────────────────────────
|
|
3539
|
+
{
|
|
3540
|
+
namespace: "mcp.servers",
|
|
3541
|
+
path: "mcpServers.*.permission",
|
|
3542
|
+
reason: "Approval requirement for that server\u2019s tools."
|
|
3543
|
+
},
|
|
3544
|
+
{ namespace: "core.runtime", path: "yolo", reason: "Disables every permission prompt." },
|
|
3545
|
+
{
|
|
3546
|
+
namespace: "core.runtime",
|
|
3547
|
+
path: "features.allowOutsideProjectRoot",
|
|
3548
|
+
reason: "Short-circuits project-root containment and the symlink realpath check."
|
|
3549
|
+
},
|
|
3550
|
+
{
|
|
3551
|
+
namespace: "core.runtime",
|
|
3552
|
+
path: "tools.restrictToProjectRoot",
|
|
3553
|
+
reason: "The other half of the filesystem confinement switch."
|
|
3554
|
+
},
|
|
3555
|
+
{
|
|
3556
|
+
namespace: "core.runtime",
|
|
3557
|
+
path: "features.developerMode",
|
|
3558
|
+
reason: "Loosens guardrails; an operator opt-in, not a synced preference."
|
|
3559
|
+
},
|
|
3560
|
+
{
|
|
3561
|
+
namespace: "core.runtime",
|
|
3562
|
+
path: "tools.disabledTools",
|
|
3563
|
+
reason: "Could re-enable a tool the operator deliberately switched off."
|
|
3564
|
+
},
|
|
3565
|
+
{
|
|
3566
|
+
namespace: "ui.preferences",
|
|
3567
|
+
path: "autonomy.defaultMode",
|
|
3568
|
+
reason: "Autonomy is user-owned, never remote-owned."
|
|
3569
|
+
},
|
|
3570
|
+
{
|
|
3571
|
+
namespace: "ui.preferences",
|
|
3572
|
+
path: "autonomy.yolo",
|
|
3573
|
+
reason: "Alias for the denied top-level `yolo`, and it wins over the user setting."
|
|
3574
|
+
},
|
|
3575
|
+
{
|
|
3576
|
+
namespace: "ui.preferences",
|
|
3577
|
+
path: "launch.autonomy",
|
|
3578
|
+
reason: "Launch-time autonomy mode; same user-owned boundary."
|
|
3579
|
+
},
|
|
3580
|
+
{
|
|
3581
|
+
namespace: "models.routing",
|
|
3582
|
+
path: "brain.mode",
|
|
3583
|
+
reason: "Selects the policy/LLM/human decision ladder."
|
|
3584
|
+
},
|
|
3585
|
+
{
|
|
3586
|
+
namespace: "models.routing",
|
|
3587
|
+
path: "brain.maxAutoRisk",
|
|
3588
|
+
reason: "The risk ceiling below which actions proceed without asking."
|
|
3589
|
+
}
|
|
3590
|
+
];
|
|
3591
|
+
function contractHasPath(tree, segments) {
|
|
3592
|
+
if (segments.length === 0) return true;
|
|
3593
|
+
if (tree === true) return false;
|
|
3594
|
+
const [head, ...rest] = segments;
|
|
3595
|
+
if (head === void 0 || !Object.hasOwn(tree, head)) return false;
|
|
3596
|
+
const child = tree[head];
|
|
3597
|
+
return child === void 0 ? false : contractHasPath(child, rest);
|
|
3598
|
+
}
|
|
3599
|
+
function pruneContractPath(tree, segments) {
|
|
3600
|
+
if (tree === true || segments.length === 0) return tree;
|
|
3601
|
+
const [head, ...rest] = segments;
|
|
3602
|
+
if (head === void 0 || !Object.hasOwn(tree, head)) return tree;
|
|
3603
|
+
const next = { ...tree };
|
|
3604
|
+
if (rest.length === 0) {
|
|
3605
|
+
delete next[head];
|
|
3606
|
+
return next;
|
|
3607
|
+
}
|
|
3608
|
+
const child = next[head];
|
|
3609
|
+
if (child === void 0) return tree;
|
|
3610
|
+
next[head] = pruneContractPath(child, rest);
|
|
3611
|
+
return next;
|
|
3612
|
+
}
|
|
3613
|
+
function assertInboundDenyListResolves() {
|
|
3614
|
+
const unresolved = INBOUND_DENIED_PATHS.filter((entry) => {
|
|
3615
|
+
const tree = CLOUD_SYNC_CONTRACT[entry.namespace];
|
|
3616
|
+
return tree === void 0 || !contractHasPath(tree, entry.path.split("."));
|
|
3617
|
+
});
|
|
3618
|
+
if (unresolved.length > 0) {
|
|
3619
|
+
throw new Error(
|
|
3620
|
+
"INBOUND_DENIED_PATHS entr(ies) no longer resolve against CLOUD_SYNC_CONTRACT \u2014 a rename would silently re-open them: " + unresolved.map((entry) => `${entry.namespace}:${entry.path}`).join(", ")
|
|
3621
|
+
);
|
|
3622
|
+
}
|
|
3623
|
+
}
|
|
3624
|
+
var INBOUND_CONTRACT = (() => {
|
|
3625
|
+
assertInboundDenyListResolves();
|
|
3626
|
+
const out = { ...CLOUD_SYNC_CONTRACT };
|
|
3627
|
+
for (const entry of INBOUND_DENIED_PATHS) {
|
|
3628
|
+
const tree = out[entry.namespace];
|
|
3629
|
+
if (tree === void 0) continue;
|
|
3630
|
+
out[entry.namespace] = pruneContractPath(tree, entry.path.split("."));
|
|
3631
|
+
}
|
|
3632
|
+
return out;
|
|
3633
|
+
})();
|
|
3449
3634
|
var NAMESPACE_SCHEMA_VERSIONS = {
|
|
3450
3635
|
"core.runtime": 1,
|
|
3451
3636
|
"ui.preferences": 1,
|
|
@@ -3547,7 +3732,7 @@ function mergeAtContract(local, incoming, tree) {
|
|
|
3547
3732
|
return base;
|
|
3548
3733
|
}
|
|
3549
3734
|
function applyNamespacePayload(config, namespace, payload) {
|
|
3550
|
-
const tree =
|
|
3735
|
+
const tree = INBOUND_CONTRACT[namespace];
|
|
3551
3736
|
if (!tree || tree === true) return config;
|
|
3552
3737
|
const next = { ...config };
|
|
3553
3738
|
for (const [key, incoming] of Object.entries(payload)) {
|
|
@@ -4978,6 +5163,10 @@ var IN_PROJECT_ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
|
4978
5163
|
"fallbackModels",
|
|
4979
5164
|
"fallbackBridge",
|
|
4980
5165
|
"fallbackProfiles",
|
|
5166
|
+
// The profile SELECTOR. No broader than its siblings: a repo that can write
|
|
5167
|
+
// `fallbackModels` and `fallbackProfiles` already controls the chain outright,
|
|
5168
|
+
// and this one can only name a profile the user already defined.
|
|
5169
|
+
"fallbackProfile",
|
|
4981
5170
|
"favoriteModels",
|
|
4982
5171
|
"favoriteModelsOnly",
|
|
4983
5172
|
"modelAvailabilitySchedule",
|
|
@@ -5025,6 +5214,10 @@ var KNOWN_DENIED_IN_PROJECT = [
|
|
|
5025
5214
|
{
|
|
5026
5215
|
key: "git",
|
|
5027
5216
|
reason: "Carries git.identity (GIT_AUTHOR_*/GIT_COMMITTER_* injection): a repo-committed config could spoof the author identity written into the victim's commit history (impersonation)."
|
|
5217
|
+
},
|
|
5218
|
+
{
|
|
5219
|
+
key: "fallbackMaxLastResortCandidates",
|
|
5220
|
+
reason: "Bounds how many of the user's OTHER configured providers may be swept in as last-resort failover. Setting it to 0 from a repo-committed config would silently strip that depth during an outage. It was already stripped in practice (absent from the allow-list) but was missing from the key registry, so this gate never checked it."
|
|
5028
5221
|
}
|
|
5029
5222
|
];
|
|
5030
5223
|
var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -5045,11 +5238,13 @@ var KNOWN_CONFIG_TOP_LEVEL_KEYS = /* @__PURE__ */ new Set([
|
|
|
5045
5238
|
"fallbackModels",
|
|
5046
5239
|
"fallbackBridge",
|
|
5047
5240
|
"fallbackProfiles",
|
|
5241
|
+
"fallbackProfile",
|
|
5048
5242
|
"favoriteModels",
|
|
5049
5243
|
"favoriteModelsOnly",
|
|
5050
5244
|
"modelAvailabilitySchedule",
|
|
5051
5245
|
"fallbackAuto",
|
|
5052
5246
|
"fallbackStickiness",
|
|
5247
|
+
"fallbackMaxLastResortCandidates",
|
|
5053
5248
|
"hooks",
|
|
5054
5249
|
"plugins",
|
|
5055
5250
|
"pluginManager",
|
|
@@ -5135,6 +5330,17 @@ var IN_PROJECT_DENIED_PATHS = [
|
|
|
5135
5330
|
// operator owns, not the checked-out repository.
|
|
5136
5331
|
path: "tools.kanbanGovernance",
|
|
5137
5332
|
reason: "Repo-committed config could disable a Kanban governance gate the operator switched on, letting product mutations run outside any managed card."
|
|
5333
|
+
},
|
|
5334
|
+
{
|
|
5335
|
+
// The bridge spawn path resolves the CLI entry by walking UP from the
|
|
5336
|
+
// project root, so a repo that ships its own `packages/cli/dist/index.js`
|
|
5337
|
+
// gets that file spawned with `process.execPath` on WebUI boot — no
|
|
5338
|
+
// prompt, no banner. Turning the feature on is therefore equivalent to
|
|
5339
|
+
// arbitrary code execution for a hostile checkout, which makes this an
|
|
5340
|
+
// operator-owned switch and never a repo-owned one.
|
|
5341
|
+
// See discover-mailbox-bridge.ts:findWorkspaceCliEntry.
|
|
5342
|
+
path: "features.mailboxBridge",
|
|
5343
|
+
reason: "Enables the mailbox bridge, whose CLI-entry resolution walks up from the project root \u2014 a repo-supplied packages/cli/dist/index.js would be spawned on WebUI boot."
|
|
5138
5344
|
}
|
|
5139
5345
|
];
|
|
5140
5346
|
function deleteNestedPath(target, path34) {
|
|
@@ -8767,6 +8973,15 @@ async function readProviderSnapshot(configPath, vault, warn) {
|
|
|
8767
8973
|
snapshot.fallbackBridge = decrypted.fallbackBridge.trim();
|
|
8768
8974
|
}
|
|
8769
8975
|
if (decrypted.fallbackProfiles) snapshot.fallbackProfiles = decrypted.fallbackProfiles;
|
|
8976
|
+
if (typeof decrypted.fallbackProfile === "string" && decrypted.fallbackProfile.trim()) {
|
|
8977
|
+
snapshot.fallbackProfile = decrypted.fallbackProfile.trim();
|
|
8978
|
+
}
|
|
8979
|
+
if (decrypted.fallbackStickiness && typeof decrypted.fallbackStickiness === "object") {
|
|
8980
|
+
snapshot.fallbackStickiness = decrypted.fallbackStickiness;
|
|
8981
|
+
}
|
|
8982
|
+
if (typeof decrypted.fallbackMaxLastResortCandidates === "number" && Number.isFinite(decrypted.fallbackMaxLastResortCandidates)) {
|
|
8983
|
+
snapshot.fallbackMaxLastResortCandidates = decrypted.fallbackMaxLastResortCandidates;
|
|
8984
|
+
}
|
|
8770
8985
|
if (Array.isArray(decrypted.favoriteModels)) snapshot.favoriteModels = decrypted.favoriteModels;
|
|
8771
8986
|
if (typeof decrypted.favoriteModelsOnly === "boolean")
|
|
8772
8987
|
snapshot.favoriteModelsOnly = decrypted.favoriteModelsOnly;
|
|
@@ -8785,10 +9000,13 @@ function serializeSnapshot(s) {
|
|
|
8785
9000
|
fallbackModels: s.fallbackModels ?? null,
|
|
8786
9001
|
fallbackBridge: s.fallbackBridge ?? null,
|
|
8787
9002
|
fallbackProfiles: s.fallbackProfiles ?? null,
|
|
9003
|
+
fallbackProfile: s.fallbackProfile ?? null,
|
|
8788
9004
|
favoriteModels: s.favoriteModels ?? null,
|
|
8789
9005
|
favoriteModelsOnly: s.favoriteModelsOnly ?? null,
|
|
8790
9006
|
modelMatrix: s.modelMatrix ?? null,
|
|
8791
9007
|
fallbackAuto: s.fallbackAuto ?? null,
|
|
9008
|
+
fallbackStickiness: s.fallbackStickiness ?? null,
|
|
9009
|
+
fallbackMaxLastResortCandidates: s.fallbackMaxLastResortCandidates ?? null,
|
|
8792
9010
|
modelAvailabilitySchedule: s.modelAvailabilitySchedule ?? null
|
|
8793
9011
|
});
|
|
8794
9012
|
}
|
|
@@ -11491,6 +11709,44 @@ import * as fsp11 from "node:fs/promises";
|
|
|
11491
11709
|
import * as path27 from "node:path";
|
|
11492
11710
|
import { createInterface as createInterface2 } from "node:readline";
|
|
11493
11711
|
|
|
11712
|
+
// src/storage/session-writer-scrubber.ts
|
|
11713
|
+
function scrubSessionWriterEvent(event, secretScrubber) {
|
|
11714
|
+
const persistMessage = (message) => {
|
|
11715
|
+
const { _estTokens: _ignored, ...persisted } = message;
|
|
11716
|
+
return {
|
|
11717
|
+
...persisted,
|
|
11718
|
+
content: typeof persisted.content === "string" ? secretScrubber?.scrub(persisted.content) ?? persisted.content : secretScrubber?.scrubObject(persisted.content) ?? persisted.content
|
|
11719
|
+
};
|
|
11720
|
+
};
|
|
11721
|
+
if (event.type === "context_snapshot" || event.type === "messages_replaced") {
|
|
11722
|
+
return { ...event, messages: event.messages.map(persistMessage) };
|
|
11723
|
+
}
|
|
11724
|
+
if (event.type === "message_appended" || event.type === "message_updated") {
|
|
11725
|
+
return { ...event, message: persistMessage(event.message) };
|
|
11726
|
+
}
|
|
11727
|
+
if (!secretScrubber) return event;
|
|
11728
|
+
if (event.type === "user_input") {
|
|
11729
|
+
return {
|
|
11730
|
+
...event,
|
|
11731
|
+
content: typeof event.content === "string" ? secretScrubber.scrub(event.content) : secretScrubber.scrubObject(event.content)
|
|
11732
|
+
};
|
|
11733
|
+
}
|
|
11734
|
+
if (event.type === "llm_response") {
|
|
11735
|
+
return { ...event, content: secretScrubber.scrubObject(event.content) };
|
|
11736
|
+
}
|
|
11737
|
+
if (event.type === "file_snapshot") {
|
|
11738
|
+
return {
|
|
11739
|
+
...event,
|
|
11740
|
+
files: event.files.map((f) => ({
|
|
11741
|
+
...f,
|
|
11742
|
+
before: f.before !== null ? secretScrubber.scrub(f.before) : null,
|
|
11743
|
+
after: f.after !== null ? secretScrubber.scrub(f.after) : null
|
|
11744
|
+
}))
|
|
11745
|
+
};
|
|
11746
|
+
}
|
|
11747
|
+
return event;
|
|
11748
|
+
}
|
|
11749
|
+
|
|
11494
11750
|
// src/storage/session-writer-truncate.ts
|
|
11495
11751
|
import * as fsp10 from "node:fs/promises";
|
|
11496
11752
|
var CHUNK_SIZE = 65536;
|
|
@@ -11635,45 +11891,11 @@ async function rewriteSessionToCheckpoint(filePath, checkpointByteOffset) {
|
|
|
11635
11891
|
}
|
|
11636
11892
|
}
|
|
11637
11893
|
|
|
11638
|
-
// src/storage/session-writer-scrubber.ts
|
|
11639
|
-
function scrubSessionWriterEvent(event, secretScrubber) {
|
|
11640
|
-
const persistMessage = (message) => {
|
|
11641
|
-
const { _estTokens: _ignored, ...persisted } = message;
|
|
11642
|
-
return {
|
|
11643
|
-
...persisted,
|
|
11644
|
-
content: typeof persisted.content === "string" ? secretScrubber?.scrub(persisted.content) ?? persisted.content : secretScrubber?.scrubObject(persisted.content) ?? persisted.content
|
|
11645
|
-
};
|
|
11646
|
-
};
|
|
11647
|
-
if (event.type === "context_snapshot" || event.type === "messages_replaced") {
|
|
11648
|
-
return { ...event, messages: event.messages.map(persistMessage) };
|
|
11649
|
-
}
|
|
11650
|
-
if (event.type === "message_appended" || event.type === "message_updated") {
|
|
11651
|
-
return { ...event, message: persistMessage(event.message) };
|
|
11652
|
-
}
|
|
11653
|
-
if (!secretScrubber) return event;
|
|
11654
|
-
if (event.type === "user_input") {
|
|
11655
|
-
return {
|
|
11656
|
-
...event,
|
|
11657
|
-
content: typeof event.content === "string" ? secretScrubber.scrub(event.content) : secretScrubber.scrubObject(event.content)
|
|
11658
|
-
};
|
|
11659
|
-
}
|
|
11660
|
-
if (event.type === "llm_response") {
|
|
11661
|
-
return { ...event, content: secretScrubber.scrubObject(event.content) };
|
|
11662
|
-
}
|
|
11663
|
-
if (event.type === "file_snapshot") {
|
|
11664
|
-
return {
|
|
11665
|
-
...event,
|
|
11666
|
-
files: event.files.map((f) => ({
|
|
11667
|
-
...f,
|
|
11668
|
-
before: f.before !== null ? secretScrubber.scrub(f.before) : null,
|
|
11669
|
-
after: f.after !== null ? secretScrubber.scrub(f.after) : null
|
|
11670
|
-
}))
|
|
11671
|
-
};
|
|
11672
|
-
}
|
|
11673
|
-
return event;
|
|
11674
|
-
}
|
|
11675
|
-
|
|
11676
11894
|
// src/storage/file-session-writer.ts
|
|
11895
|
+
function isClosedHandleError(err) {
|
|
11896
|
+
const code = err?.code;
|
|
11897
|
+
return code === "EBADF" || code === "ERR_CLOSED_RESOURCE" || code === "ERR_INVALID_HANDLE";
|
|
11898
|
+
}
|
|
11677
11899
|
var FileSessionWriter = class _FileSessionWriter {
|
|
11678
11900
|
constructor(id, handle, startedAt, meta, events, opts = {}, traceId) {
|
|
11679
11901
|
this.id = id;
|
|
@@ -11845,8 +12067,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
11845
12067
|
try {
|
|
11846
12068
|
return await this.handle.appendFile(data, "utf8");
|
|
11847
12069
|
} catch (err) {
|
|
11848
|
-
|
|
11849
|
-
if (nodeErr?.code === "EBADF") {
|
|
12070
|
+
if (isClosedHandleError(err)) {
|
|
11850
12071
|
this.handle = await fsp11.open(this.filePath, "a", 384);
|
|
11851
12072
|
return await this.handle.appendFile(data, "utf8");
|
|
11852
12073
|
}
|
|
@@ -11886,8 +12107,8 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
11886
12107
|
bufferSynchronousEvent(event) {
|
|
11887
12108
|
if (this.closed) return;
|
|
11888
12109
|
void this.ensureInit();
|
|
11889
|
-
this.
|
|
11890
|
-
|
|
12110
|
+
const appendEvent = scrubSessionWriterEvent(event, this.secretScrubber);
|
|
12111
|
+
this.observeForSummary(appendEvent);
|
|
11891
12112
|
try {
|
|
11892
12113
|
this._onAppend?.(appendEvent);
|
|
11893
12114
|
} catch {
|
|
@@ -12040,8 +12261,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12040
12261
|
try {
|
|
12041
12262
|
await this.handle.datasync();
|
|
12042
12263
|
} catch (err) {
|
|
12043
|
-
|
|
12044
|
-
if (nodeErr?.code === "EBADF") {
|
|
12264
|
+
if (isClosedHandleError(err)) {
|
|
12045
12265
|
this.handle = await fsp11.open(this.filePath, "a", 384);
|
|
12046
12266
|
return;
|
|
12047
12267
|
}
|
|
@@ -12278,6 +12498,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12278
12498
|
return this.closePromise;
|
|
12279
12499
|
}
|
|
12280
12500
|
async doClose() {
|
|
12501
|
+
await this.ensureInit();
|
|
12281
12502
|
if (this.pendingFileSnapshots.length > 0) {
|
|
12282
12503
|
await this.writeFileSnapshot(this.activePromptIndex ?? 0, [...this.pendingFileSnapshots]);
|
|
12283
12504
|
this.pendingFileSnapshots = [];
|
|
@@ -12293,8 +12514,7 @@ var FileSessionWriter = class _FileSessionWriter {
|
|
|
12293
12514
|
try {
|
|
12294
12515
|
await this.handle.datasync();
|
|
12295
12516
|
} catch (err) {
|
|
12296
|
-
|
|
12297
|
-
if (nodeErr?.code !== "EBADF") throw err;
|
|
12517
|
+
if (!isClosedHandleError(err)) throw err;
|
|
12298
12518
|
}
|
|
12299
12519
|
const endedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
12300
12520
|
const observedActivityMs = Date.parse(this.lastActivityAt);
|
|
@@ -13494,9 +13714,40 @@ async function readOrBuildShardManifestEntry(opts) {
|
|
|
13494
13714
|
return entry;
|
|
13495
13715
|
}
|
|
13496
13716
|
|
|
13497
|
-
// src/storage/session-store/
|
|
13717
|
+
// src/storage/session-store/strict-empty-check.ts
|
|
13498
13718
|
import { createReadStream as createReadStream4 } from "node:fs";
|
|
13499
13719
|
import { createInterface as createInterface4 } from "node:readline";
|
|
13720
|
+
var EMPTY_SESSION_EVENT_TYPES = /* @__PURE__ */ new Set(["session_start", "session_resumed", "session_end"]);
|
|
13721
|
+
async function isStrictlyEmptySessionFile(file) {
|
|
13722
|
+
const input = createReadStream4(file, { encoding: "utf8" });
|
|
13723
|
+
const lines = createInterface4({ input, crlfDelay: Infinity });
|
|
13724
|
+
let sawSessionStart = false;
|
|
13725
|
+
try {
|
|
13726
|
+
for await (const line of lines) {
|
|
13727
|
+
if (!line.trim()) continue;
|
|
13728
|
+
let event;
|
|
13729
|
+
try {
|
|
13730
|
+
event = JSON.parse(line);
|
|
13731
|
+
} catch {
|
|
13732
|
+
return false;
|
|
13733
|
+
}
|
|
13734
|
+
if (event === null || typeof event !== "object" || Array.isArray(event)) return false;
|
|
13735
|
+
const type = event.type;
|
|
13736
|
+
if (typeof type !== "string" || !EMPTY_SESSION_EVENT_TYPES.has(type)) return false;
|
|
13737
|
+
if (type === "session_start") sawSessionStart = true;
|
|
13738
|
+
}
|
|
13739
|
+
} catch {
|
|
13740
|
+
return false;
|
|
13741
|
+
} finally {
|
|
13742
|
+
lines.close();
|
|
13743
|
+
input.destroy();
|
|
13744
|
+
}
|
|
13745
|
+
return sawSessionStart;
|
|
13746
|
+
}
|
|
13747
|
+
|
|
13748
|
+
// src/storage/session-store/summary-builder.ts
|
|
13749
|
+
import { createReadStream as createReadStream5 } from "node:fs";
|
|
13750
|
+
import { createInterface as createInterface5 } from "node:readline";
|
|
13500
13751
|
async function summarizeSessionFile(opts) {
|
|
13501
13752
|
return summarizeSessionEventSequence({
|
|
13502
13753
|
id: opts.id,
|
|
@@ -13613,8 +13864,8 @@ async function summarizeSessionEventSequence(opts) {
|
|
|
13613
13864
|
}
|
|
13614
13865
|
}
|
|
13615
13866
|
async function* iterateSessionEvents(file, secretScrubber) {
|
|
13616
|
-
const stream =
|
|
13617
|
-
const lines =
|
|
13867
|
+
const stream = createReadStream5(file, { encoding: "utf8" });
|
|
13868
|
+
const lines = createInterface5({ input: stream, crlfDelay: Infinity });
|
|
13618
13869
|
try {
|
|
13619
13870
|
for await (const line of lines) {
|
|
13620
13871
|
if (!line.trim()) continue;
|
|
@@ -14510,6 +14761,10 @@ var DefaultSessionStore = class _DefaultSessionStore {
|
|
|
14510
14761
|
await deleteSessionArtifacts({ rootDir: this.dir, id, jsonlPath });
|
|
14511
14762
|
await this.writeTombstone(id);
|
|
14512
14763
|
}
|
|
14764
|
+
async isEmpty(id) {
|
|
14765
|
+
const canonicalId = await this.resolveId(id);
|
|
14766
|
+
return isStrictlyEmptySessionFile(this.sessionPath(canonicalId, ".jsonl"));
|
|
14767
|
+
}
|
|
14513
14768
|
async delete(id) {
|
|
14514
14769
|
if (this.catalogClient) {
|
|
14515
14770
|
const canonical = await this.resolveId(id);
|
|
@@ -14722,10 +14977,10 @@ async function applyRewindToConversation(opts) {
|
|
|
14722
14977
|
}
|
|
14723
14978
|
|
|
14724
14979
|
// src/storage/session-rewinder.ts
|
|
14725
|
-
import { createReadStream as
|
|
14980
|
+
import { createReadStream as createReadStream6 } from "node:fs";
|
|
14726
14981
|
import * as fsp20 from "node:fs/promises";
|
|
14727
14982
|
import * as path33 from "node:path";
|
|
14728
|
-
import { createInterface as
|
|
14983
|
+
import { createInterface as createInterface6 } from "node:readline";
|
|
14729
14984
|
var DefaultSessionRewinder = class {
|
|
14730
14985
|
constructor(sessionsDir, projectRoot) {
|
|
14731
14986
|
this.sessionsDir = sessionsDir;
|
|
@@ -14737,8 +14992,8 @@ var DefaultSessionRewinder = class {
|
|
|
14737
14992
|
return sessionScopedPath(this.sessionsDir, sessionId, ".jsonl");
|
|
14738
14993
|
}
|
|
14739
14994
|
async *readEvents(file) {
|
|
14740
|
-
const stream =
|
|
14741
|
-
const lines =
|
|
14995
|
+
const stream = createReadStream6(file, { encoding: "utf8" });
|
|
14996
|
+
const lines = createInterface6({ input: stream, crlfDelay: Infinity });
|
|
14742
14997
|
try {
|
|
14743
14998
|
for await (const line of lines) {
|
|
14744
14999
|
if (!line.trim()) continue;
|
|
@@ -24,10 +24,19 @@ export interface ProviderConfigSnapshot {
|
|
|
24
24
|
fallbackModels?: string[];
|
|
25
25
|
fallbackBridge?: string;
|
|
26
26
|
fallbackProfiles?: Record<string, string[]>;
|
|
27
|
+
/** Selected named profile (Config.fallbackProfile). */
|
|
28
|
+
fallbackProfile?: string;
|
|
27
29
|
favoriteModels?: string[];
|
|
28
30
|
favoriteModelsOnly?: boolean;
|
|
29
31
|
modelMatrix?: Record<string, unknown>;
|
|
30
32
|
fallbackAuto?: boolean;
|
|
33
|
+
/** Primary-probe cooldown / sticky-dwell tuning (Config.fallbackStickiness). */
|
|
34
|
+
fallbackStickiness?: {
|
|
35
|
+
primaryProbeInterval?: number;
|
|
36
|
+
stickyFallbackTurns?: number;
|
|
37
|
+
};
|
|
38
|
+
/** Last-resort append cap (Config.fallbackMaxLastResortCandidates). */
|
|
39
|
+
fallbackMaxLastResortCandidates?: number;
|
|
31
40
|
modelAvailabilitySchedule?: import('../core/model-availability-calendar.js').ModelBlackoutRule[];
|
|
32
41
|
}
|
|
33
42
|
export interface WatchProviderConfigOptions {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Return true only for a well-formed journal containing lifecycle envelope
|
|
3
|
+
* events and nothing else. Parsing fails closed so a damaged or newer journal
|
|
4
|
+
* can never be mistaken for an empty session and deleted.
|
|
5
|
+
*/
|
|
6
|
+
export declare function isStrictlyEmptySessionFile(file: string): Promise<boolean>;
|
|
7
|
+
//# sourceMappingURL=strict-empty-check.d.ts.map
|
|
@@ -205,6 +205,7 @@ export declare class DefaultSessionStore implements SessionStore {
|
|
|
205
205
|
* to the caller so prune() can report it.
|
|
206
206
|
*/
|
|
207
207
|
private deleteSession;
|
|
208
|
+
isEmpty(id: string): Promise<boolean>;
|
|
208
209
|
delete(id: string): Promise<void>;
|
|
209
210
|
rename(id: string, name: string): Promise<SessionSummary>;
|
|
210
211
|
prune(maxAgeDays?: number): Promise<number>;
|
package/dist/tools/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { COUNCIL_TOOL_NAME, type CouncilToolInput, type CreateCouncilToolOptions, createCouncilTool, MAX_COUNCIL_CONTEXT_CHARS, MAX_COUNCIL_QUESTION_CHARS, MAX_COUNCIL_TOOL_OPTIONS, } from './council-tool.js';
|
|
2
|
-
export { AGENT_MODEL_ASSIGN_TOOL_NAME, createFallbackManageTools, FALLBACK_CHAIN_MANAGE_TOOL_NAME, FALLBACK_PROFILE_MANAGE_TOOL_NAME, FAVORITE_MANAGE_TOOL_NAME, type FallbackManageToolOptions, LEADER_MODEL_SET_TOOL_NAME, PROVIDER_KEY_SET_TOOL_NAME, PROVIDER_MANAGE_TOOL_NAME, SYSTEM_CONFIG_VIEW_TOOL_NAME, } from './fallback-manage-tools.js';
|
|
2
|
+
export { AGENT_MODEL_ASSIGN_TOOL_NAME, createFallbackManageTools, FALLBACK_CHAIN_MANAGE_TOOL_NAME, FALLBACK_PROFILE_MANAGE_TOOL_NAME, FAVORITE_MANAGE_TOOL_NAME, type FallbackManageToolOptions, LEADER_MODEL_SET_TOOL_NAME, PROVIDER_KEY_SET_TOOL_NAME, PROVIDER_MANAGE_TOOL_NAME, SYSTEM_CONFIG_VIEW_TOOL_NAME, validateProviderBaseUrl, } from './fallback-manage-tools.js';
|
|
3
3
|
export { createMcpControlTool, type MCPRegistryHandle } from './mcp-control.js';
|
|
4
4
|
export { createMcpUseTool } from './mcp-use.js';
|
|
5
5
|
export { type CreateOneShotLLMToolOptions, createOneShotLLMTool, ONE_SHOT_LLM_TOOL_NAME, } from './one-shot-llm-tool.js';
|