@adhdev/daemon-standalone 1.0.29 → 1.0.30-rc.2
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/index.js +562 -94
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/public/assets/{index-CymiuPZJ.js → index-NkLGCvUY.js} +3 -3
- package/public/assets/{terminal-BZj_X5zy.js → terminal-DiXj2eIR.js} +1 -1
- package/public/assets/{vendor-cL4V2vaO.js → vendor-DyCWA2YZ.js} +57 -49
- package/public/index.html +2 -2
- package/vendor/mcp-server/index.js +277 -2
- package/vendor/mcp-server/index.js.map +1 -1
- package/vendor/session-host-daemon/index.d.mts +5 -0
- package/vendor/session-host-daemon/index.d.ts +5 -0
- package/vendor/session-host-daemon/index.js +24 -3
- package/vendor/session-host-daemon/index.js.map +1 -1
- package/vendor/session-host-daemon/index.mjs +26 -4
- package/vendor/session-host-daemon/index.mjs.map +1 -1
package/public/index.html
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
<meta name="description" content="ADHDev self-hosted dashboard for controlling AI agents" />
|
|
8
8
|
<link rel="icon" href="/otter-logo.png" />
|
|
9
9
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap" rel="stylesheet" />
|
|
10
|
-
<script type="module" crossorigin src="/assets/index-
|
|
11
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
10
|
+
<script type="module" crossorigin src="/assets/index-NkLGCvUY.js"></script>
|
|
11
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-DyCWA2YZ.js">
|
|
12
12
|
<link rel="stylesheet" crossorigin href="/assets/index-DaANjDZS.css">
|
|
13
13
|
</head>
|
|
14
14
|
<body>
|
|
@@ -406,6 +406,160 @@ function normalizeNodeCapabilitySlots(raw) {
|
|
|
406
406
|
}
|
|
407
407
|
return out;
|
|
408
408
|
}
|
|
409
|
+
var CLI_SLOT_RECIPES = Object.freeze({
|
|
410
|
+
"claude-cli": [
|
|
411
|
+
{
|
|
412
|
+
model: "sonnet",
|
|
413
|
+
thinkingLevel: "high",
|
|
414
|
+
difficulty: ["medium", "easy"],
|
|
415
|
+
maxParallel: 5,
|
|
416
|
+
rationale: "Primary workhorse \u2014 widest parallelism for routine work."
|
|
417
|
+
},
|
|
418
|
+
{
|
|
419
|
+
model: "opus",
|
|
420
|
+
thinkingLevel: "high",
|
|
421
|
+
difficulty: ["difficult"],
|
|
422
|
+
maxParallel: 1,
|
|
423
|
+
rationale: "Reserved for hard tasks; capped at 1 to bound cost."
|
|
424
|
+
}
|
|
425
|
+
],
|
|
426
|
+
"kimi": [
|
|
427
|
+
{
|
|
428
|
+
model: "kimi-code/k3",
|
|
429
|
+
difficulty: ["medium", "difficult"],
|
|
430
|
+
maxParallel: 2,
|
|
431
|
+
rationale: "Independent second opinion on mid/hard work."
|
|
432
|
+
}
|
|
433
|
+
],
|
|
434
|
+
"codex-cli": [
|
|
435
|
+
{
|
|
436
|
+
difficulty: ["medium", "difficult", "freeform"],
|
|
437
|
+
maxParallel: 2,
|
|
438
|
+
rationale: "Broad range including freeform; no model pin."
|
|
439
|
+
}
|
|
440
|
+
],
|
|
441
|
+
"antigravity-cli": [
|
|
442
|
+
{
|
|
443
|
+
model: "Gemini 3.1 Pro (High)",
|
|
444
|
+
difficulty: ["easy"],
|
|
445
|
+
maxParallel: 2,
|
|
446
|
+
rationale: "Cheap capacity for easy tasks."
|
|
447
|
+
}
|
|
448
|
+
],
|
|
449
|
+
"cursor-cli": [
|
|
450
|
+
{
|
|
451
|
+
model: "auto",
|
|
452
|
+
difficulty: ["easy"],
|
|
453
|
+
maxParallel: 1,
|
|
454
|
+
rationale: "Easy tasks only; auto model selection."
|
|
455
|
+
}
|
|
456
|
+
],
|
|
457
|
+
"hermes-cli": [
|
|
458
|
+
{
|
|
459
|
+
difficulty: ["medium"],
|
|
460
|
+
maxParallel: 2,
|
|
461
|
+
provisional: true,
|
|
462
|
+
// NOTE: ESTIMATE, NOT OBSERVED. hermes-cli is absent from the live
|
|
463
|
+
// slot configuration this table was seeded from, so `medium` is a
|
|
464
|
+
// conservative placement rather than a transcription. Revisit once
|
|
465
|
+
// it has real usage data.
|
|
466
|
+
rationale: "ESTIMATE \u2014 no live slot to transcribe; conservative mid placement. Adjust after real use."
|
|
467
|
+
}
|
|
468
|
+
]
|
|
469
|
+
});
|
|
470
|
+
var UNKNOWN_CLI_SLOT_RECIPE = Object.freeze({
|
|
471
|
+
difficulty: ["medium"],
|
|
472
|
+
maxParallel: 1,
|
|
473
|
+
provisional: true,
|
|
474
|
+
rationale: "Unrecognized provider \u2014 conservative default (medium, maxParallel 1). Review before relying on it."
|
|
475
|
+
});
|
|
476
|
+
function slotKey(slot) {
|
|
477
|
+
return [
|
|
478
|
+
slot.provider,
|
|
479
|
+
slot.model ?? "",
|
|
480
|
+
slot.thinkingLevel ?? "",
|
|
481
|
+
[...slot.difficulty ?? []].sort().join("|"),
|
|
482
|
+
[...slot.capability ?? []].sort().join("|"),
|
|
483
|
+
slot.maxParallel ?? ""
|
|
484
|
+
].join("\0");
|
|
485
|
+
}
|
|
486
|
+
function dedupeDetected(detected) {
|
|
487
|
+
const seen = /* @__PURE__ */ new Set();
|
|
488
|
+
const out = [];
|
|
489
|
+
for (const d of detected) {
|
|
490
|
+
const type = typeof d?.type === "string" ? d.type.trim() : "";
|
|
491
|
+
if (!type || seen.has(type)) continue;
|
|
492
|
+
seen.add(type);
|
|
493
|
+
out.push({ ...d, type });
|
|
494
|
+
}
|
|
495
|
+
return out;
|
|
496
|
+
}
|
|
497
|
+
function buildSlotProposal(detected, currentSlots = []) {
|
|
498
|
+
const providers = dedupeDetected(detected ?? []);
|
|
499
|
+
const proposedSlots = [];
|
|
500
|
+
const entries = [];
|
|
501
|
+
const unknownProviders = [];
|
|
502
|
+
const provisionalProviders = [];
|
|
503
|
+
for (const provider of providers) {
|
|
504
|
+
const known = CLI_SLOT_RECIPES[provider.type];
|
|
505
|
+
const recipes = known ?? [UNKNOWN_CLI_SLOT_RECIPE];
|
|
506
|
+
const isUnknown = !known;
|
|
507
|
+
if (isUnknown) unknownProviders.push(provider.type);
|
|
508
|
+
let providerProvisional = false;
|
|
509
|
+
for (const recipe of recipes) {
|
|
510
|
+
const slot = normalizeNodeCapabilitySlot({
|
|
511
|
+
provider: provider.type,
|
|
512
|
+
model: recipe.model,
|
|
513
|
+
thinkingLevel: recipe.thinkingLevel,
|
|
514
|
+
difficulty: recipe.difficulty,
|
|
515
|
+
maxParallel: recipe.maxParallel
|
|
516
|
+
});
|
|
517
|
+
if (!slot) continue;
|
|
518
|
+
const provisional = recipe.provisional === true;
|
|
519
|
+
if (provisional) providerProvisional = true;
|
|
520
|
+
proposedSlots.push(slot);
|
|
521
|
+
entries.push({
|
|
522
|
+
slot,
|
|
523
|
+
unknownProvider: isUnknown,
|
|
524
|
+
provisional,
|
|
525
|
+
...recipe.rationale ? { rationale: recipe.rationale } : {}
|
|
526
|
+
});
|
|
527
|
+
}
|
|
528
|
+
if (providerProvisional) provisionalProviders.push(provider.type);
|
|
529
|
+
}
|
|
530
|
+
const proposedKeys = new Set(proposedSlots.map(slotKey));
|
|
531
|
+
const droppedSlots = currentSlots.filter((slot) => !proposedKeys.has(slotKey(slot)));
|
|
532
|
+
const proposedProviders = new Set(proposedSlots.map((s) => s.provider));
|
|
533
|
+
const droppedProviders = [...new Set(
|
|
534
|
+
droppedSlots.map((s) => s.provider).filter((p) => !proposedProviders.has(p))
|
|
535
|
+
)];
|
|
536
|
+
return {
|
|
537
|
+
proposedSlots,
|
|
538
|
+
entries,
|
|
539
|
+
unknownProviders,
|
|
540
|
+
provisionalProviders,
|
|
541
|
+
droppedSlots,
|
|
542
|
+
droppedProviders,
|
|
543
|
+
destructive: droppedSlots.length > 0
|
|
544
|
+
};
|
|
545
|
+
}
|
|
546
|
+
function buildMagiPanelProposal(detected, opts = {}) {
|
|
547
|
+
const providers = dedupeDetected(detected ?? []);
|
|
548
|
+
const tableOrder = Object.keys(CLI_SLOT_RECIPES);
|
|
549
|
+
const rank = (type) => {
|
|
550
|
+
const i = tableOrder.indexOf(type);
|
|
551
|
+
return i === -1 ? Number.MAX_SAFE_INTEGER : i;
|
|
552
|
+
};
|
|
553
|
+
const ordered = [...providers].sort((a, b) => rank(a.type) - rank(b.type));
|
|
554
|
+
const limit = Number.isFinite(opts.maxSlots) && opts.maxSlots > 0 ? Math.floor(opts.maxSlots) : ordered.length;
|
|
555
|
+
return ordered.slice(0, limit).map((p) => ({
|
|
556
|
+
...opts.nodeId ? { nodeId: opts.nodeId } : {},
|
|
557
|
+
provider: p.type
|
|
558
|
+
// A model is intentionally NOT pinned: the panel's job is cross-provider
|
|
559
|
+
// independence, and pinning models here would silently couple the panel
|
|
560
|
+
// to this table's cost assumptions rather than to review quality.
|
|
561
|
+
}));
|
|
562
|
+
}
|
|
409
563
|
var CANONICAL_MESH_TOOL_NAMES = [
|
|
410
564
|
"mesh_status",
|
|
411
565
|
"mesh_list_nodes",
|
|
@@ -457,7 +611,8 @@ var CANONICAL_MESH_TOOL_NAMES = [
|
|
|
457
611
|
"mesh_magi_kind_panel_set",
|
|
458
612
|
"mesh_magi_kind_panel_list",
|
|
459
613
|
"mesh_node_slots_set",
|
|
460
|
-
"mesh_node_slots_list"
|
|
614
|
+
"mesh_node_slots_list",
|
|
615
|
+
"mesh_node_slots_propose"
|
|
461
616
|
];
|
|
462
617
|
var CANONICAL_MESH_TOOL_COUNT = CANONICAL_MESH_TOOL_NAMES.length;
|
|
463
618
|
var STATUS_PROBE_ARG_KEY = "_statusProbe";
|
|
@@ -1619,6 +1774,18 @@ var MESH_NODE_SLOTS_LIST_TOOL = {
|
|
|
1619
1774
|
required: ["node_id"]
|
|
1620
1775
|
}
|
|
1621
1776
|
};
|
|
1777
|
+
var MESH_NODE_SLOTS_PROPOSE_TOOL = {
|
|
1778
|
+
name: "mesh_node_slots_propose",
|
|
1779
|
+
description: 'AUTO-DETECT a node\'s installed CLI agents and DRAFT a capability-slot profile from them \u2014 the "just allow it and it figures out the slots" path. READ-ONLY: it probes the node (get_status_metadata \u2192 availableProviders, filtered to category=cli + installed=true), maps each detected CLI through a seeded provider\u2192(model/thinkingLevel/difficulty/maxParallel) table, and returns `proposedSlots` plus per-slot rationale. It NEVER writes \u2014 apply the draft with mesh_node_slots_set({ node_id, slots: proposedSlots, write: true }) after user approval. CRITICAL: slot writes are WHOLESALE replacements, so the response computes `droppedSlots` / `droppedProviders` / `destructive` \u2014 existing hand-tuned slots (capability tags, tuned maxParallel, providers not currently on PATH) are NOT preserved by the draft. Present those before approving. Detects nothing \u2192 proposes nothing (it will NOT propose an empty list that would wipe the profile). Optional include_magi drafts one cross-provider MAGI panel of the detected providers.',
|
|
1780
|
+
inputSchema: {
|
|
1781
|
+
type: "object",
|
|
1782
|
+
properties: {
|
|
1783
|
+
node_id: { type: "string", description: "REQUIRED \u2014 the mesh node id to detect installed CLI agents on and draft slots for." },
|
|
1784
|
+
include_magi: { type: "boolean", description: "When true, also draft a MAGI panel (one slot per detected provider, pinned to this node, models unpinned) for binding via mesh_magi_kind_panel_set. Defaults false. Deliberately NOT a per-task_kind assignment \u2014 provider manifests carry no rca/design/claim_audit suitability data." }
|
|
1785
|
+
},
|
|
1786
|
+
required: ["node_id"]
|
|
1787
|
+
}
|
|
1788
|
+
};
|
|
1622
1789
|
var MESH_WRITE_MESH_JSON_CONFIG_TOOL = {
|
|
1623
1790
|
name: "mesh_write_mesh_json_config",
|
|
1624
1791
|
description: "Write `.adhdev/mesh.json` (the repo-committed coordinator prompt override/append + declarative config) from the machine-local mesh entry. Gated WRITE sibling of the draft-only export_mesh_json_config. Follows the mesh_init write/overwrite/dry-run precedent: defaults to dry-run (write=false), never clobbers an existing repo mesh.json unless overwrite=true, and validates before writing. Overwrite silently replaces the file, so present a current-vs-suggested diff and get explicit approval first. REPO-COMMITTED scope (commit target) \u2014 distinct from the machine-local MAGI kind-panel writes.",
|
|
@@ -1682,7 +1849,8 @@ var ALL_MESH_TOOLS = [
|
|
|
1682
1849
|
MESH_MAGI_KIND_PANEL_SET_TOOL,
|
|
1683
1850
|
MESH_MAGI_KIND_PANEL_LIST_TOOL,
|
|
1684
1851
|
MESH_NODE_SLOTS_SET_TOOL,
|
|
1685
|
-
MESH_NODE_SLOTS_LIST_TOOL
|
|
1852
|
+
MESH_NODE_SLOTS_LIST_TOOL,
|
|
1853
|
+
MESH_NODE_SLOTS_PROPOSE_TOOL
|
|
1686
1854
|
];
|
|
1687
1855
|
|
|
1688
1856
|
// src/tools/mesh-compact.ts
|
|
@@ -6483,6 +6651,110 @@ async function meshNodeSlotsSet(ctx, args) {
|
|
|
6483
6651
|
}
|
|
6484
6652
|
}
|
|
6485
6653
|
|
|
6654
|
+
// src/tools/mesh-tools-slot-autodetect.ts
|
|
6655
|
+
function extractInstalledCliProviders(raw) {
|
|
6656
|
+
const payload = unwrapCommandPayload(raw);
|
|
6657
|
+
const list = payload?.status?.availableProviders ?? payload?.availableProviders ?? raw?.status?.availableProviders;
|
|
6658
|
+
if (!Array.isArray(list)) return [];
|
|
6659
|
+
const out = [];
|
|
6660
|
+
for (const entry of list) {
|
|
6661
|
+
if (!entry || typeof entry !== "object") continue;
|
|
6662
|
+
if (entry.category !== "cli") continue;
|
|
6663
|
+
if (entry.installed !== true) continue;
|
|
6664
|
+
const type = typeof entry.type === "string" ? entry.type.trim() : "";
|
|
6665
|
+
if (!type) continue;
|
|
6666
|
+
const displayName = typeof entry.displayName === "string" && entry.displayName.trim() ? entry.displayName.trim() : typeof entry.name === "string" && entry.name.trim() ? entry.name.trim() : void 0;
|
|
6667
|
+
const version = typeof entry.providerVersion === "string" && entry.providerVersion.trim() ? entry.providerVersion.trim() : void 0;
|
|
6668
|
+
out.push({ type, ...displayName ? { displayName } : {}, ...version ? { version } : {} });
|
|
6669
|
+
}
|
|
6670
|
+
return out;
|
|
6671
|
+
}
|
|
6672
|
+
async function meshNodeSlotsPropose(ctx, args = {}) {
|
|
6673
|
+
const nodeId = String(args.node_id || args.nodeId || "").trim();
|
|
6674
|
+
if (!nodeId) return JSON.stringify({ success: false, error: "node_id required" });
|
|
6675
|
+
const includeMagi = args.include_magi === true || args.includeMagi === true;
|
|
6676
|
+
try {
|
|
6677
|
+
const node = await findNodeWithRefresh(ctx, nodeId);
|
|
6678
|
+
let statusResult;
|
|
6679
|
+
try {
|
|
6680
|
+
statusResult = await commandForNode(ctx, node, "get_status_metadata", {}, { statusProbe: true });
|
|
6681
|
+
} catch (e) {
|
|
6682
|
+
return JSON.stringify({
|
|
6683
|
+
success: false,
|
|
6684
|
+
nodeId: node.id,
|
|
6685
|
+
code: "detection_unavailable",
|
|
6686
|
+
error: `Could not probe node for installed providers: ${e?.message || String(e)}`,
|
|
6687
|
+
nextAction: "Node may be offline. Retry when it is online, or set slots manually with mesh_node_slots_set."
|
|
6688
|
+
}, null, 2);
|
|
6689
|
+
}
|
|
6690
|
+
const detected = extractInstalledCliProviders(statusResult);
|
|
6691
|
+
const currentSlots = normalizeNodeCapabilitySlots(node?.policy?.slots);
|
|
6692
|
+
if (detected.length === 0) {
|
|
6693
|
+
return JSON.stringify({
|
|
6694
|
+
success: true,
|
|
6695
|
+
nodeId: node.id,
|
|
6696
|
+
detectedCliProviders: [],
|
|
6697
|
+
currentSlots,
|
|
6698
|
+
proposedSlots: [],
|
|
6699
|
+
note: "No installed CLI providers detected on this node \u2014 nothing to propose. This is NOT a proposal to clear the node's slots: applying an empty slot list would wipe the existing profile. Left unchanged.",
|
|
6700
|
+
nextAction: currentSlots.length ? "Node keeps its current slots. If detection is wrong, check the daemon's provider list (older daemons may not report `installed`)." : "Install a CLI agent on the node, or configure slots manually with mesh_node_slots_set."
|
|
6701
|
+
}, null, 2);
|
|
6702
|
+
}
|
|
6703
|
+
const proposal = buildSlotProposal(detected, currentSlots);
|
|
6704
|
+
const magiPanel = includeMagi ? buildMagiPanelProposal(detected, { nodeId: node.id }) : void 0;
|
|
6705
|
+
const warnings = [];
|
|
6706
|
+
if (proposal.destructive) {
|
|
6707
|
+
warnings.push(
|
|
6708
|
+
`DESTRUCTIVE: applying this proposal would REMOVE ${proposal.droppedSlots.length} existing slot(s)${proposal.droppedProviders.length ? `, dropping provider(s) entirely: ${proposal.droppedProviders.join(", ")}` : ""}. mesh_node_slots_set replaces the slot list wholesale \u2014 hand-tuned slots (capability tags, tuned maxParallel, providers not currently on PATH) are NOT preserved. Review droppedSlots before approving.`
|
|
6709
|
+
);
|
|
6710
|
+
}
|
|
6711
|
+
if (proposal.unknownProviders.length) {
|
|
6712
|
+
warnings.push(
|
|
6713
|
+
`Unrecognized provider(s) with no mapping entry: ${proposal.unknownProviders.join(", ")}. Proposed with a conservative default (difficulty medium, maxParallel 1).`
|
|
6714
|
+
);
|
|
6715
|
+
}
|
|
6716
|
+
if (proposal.provisionalProviders.length) {
|
|
6717
|
+
warnings.push(
|
|
6718
|
+
`Provisional (estimated, not observed) placement for: ${proposal.provisionalProviders.join(", ")}. Adjust after real usage.`
|
|
6719
|
+
);
|
|
6720
|
+
}
|
|
6721
|
+
return JSON.stringify({
|
|
6722
|
+
success: true,
|
|
6723
|
+
dryRun: true,
|
|
6724
|
+
nodeId: node.id,
|
|
6725
|
+
detectionSource: "get_status_metadata \u2192 status.availableProviders (category=cli, installed=true)",
|
|
6726
|
+
detectedCliProviders: detected,
|
|
6727
|
+
currentSlots,
|
|
6728
|
+
proposedSlots: proposal.proposedSlots,
|
|
6729
|
+
rationale: proposal.entries.map((e) => ({
|
|
6730
|
+
provider: e.slot.provider,
|
|
6731
|
+
...e.slot.model ? { model: e.slot.model } : {},
|
|
6732
|
+
...e.slot.difficulty ? { difficulty: e.slot.difficulty } : {},
|
|
6733
|
+
...e.slot.maxParallel !== void 0 ? { maxParallel: e.slot.maxParallel } : {},
|
|
6734
|
+
...e.unknownProvider ? { unknownProvider: true } : {},
|
|
6735
|
+
...e.provisional ? { provisional: true } : {},
|
|
6736
|
+
...e.rationale ? { why: e.rationale } : {}
|
|
6737
|
+
})),
|
|
6738
|
+
droppedSlots: proposal.droppedSlots,
|
|
6739
|
+
droppedProviders: proposal.droppedProviders,
|
|
6740
|
+
destructive: proposal.destructive,
|
|
6741
|
+
...warnings.length ? { warnings } : {},
|
|
6742
|
+
...magiPanel ? {
|
|
6743
|
+
magiPanelProposal: {
|
|
6744
|
+
slots: magiPanel,
|
|
6745
|
+
scope: "ONE panel of the detected providers, pinned to this node. Not a per-kind assignment.",
|
|
6746
|
+
rationale: "MAGI's value is cross-provider independence, and detection supports exactly that: one panel of distinct installed providers. Nothing in a provider manifest grades a provider for rca vs design vs claim_audit, so no per-kind split is proposed \u2014 you choose the task_kind to bind this to. Models are intentionally left unpinned.",
|
|
6747
|
+
nextAction: "Bind with mesh_magi_kind_panel_set({ task_kind, slots }) \u2014 dry-run first, then write=true after approval."
|
|
6748
|
+
}
|
|
6749
|
+
} : {},
|
|
6750
|
+
note: "PROPOSAL ONLY \u2014 nothing was written. This tool never mutates node config.",
|
|
6751
|
+
nextAction: "Present this diff to the user. On approval, apply with mesh_node_slots_set({ node_id, slots: proposedSlots, write: true }). Its own dry-run (write omitted) will restate the same current-vs-proposed diff."
|
|
6752
|
+
}, null, 2);
|
|
6753
|
+
} catch (e) {
|
|
6754
|
+
return JSON.stringify({ success: false, error: e?.message || String(e) });
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
|
|
6486
6758
|
// src/tools/mesh-tools-session.ts
|
|
6487
6759
|
function computeIdleDispatchAckRisk(sessionWasIdle, dispatchPreRecorded, sessionId) {
|
|
6488
6760
|
if (!sessionWasIdle || dispatchPreRecorded) return {};
|
|
@@ -9343,6 +9615,9 @@ async function startMcpServer(opts) {
|
|
|
9343
9615
|
case "mesh_node_slots_list":
|
|
9344
9616
|
text = await meshNodeSlotsList(meshCtx, a);
|
|
9345
9617
|
break;
|
|
9618
|
+
case "mesh_node_slots_propose":
|
|
9619
|
+
text = await meshNodeSlotsPropose(meshCtx, a);
|
|
9620
|
+
break;
|
|
9346
9621
|
default:
|
|
9347
9622
|
return { content: [{ type: "text", text: `Unknown tool: ${name}` }], isError: true };
|
|
9348
9623
|
}
|