@clwnd/opencode 0.15.6 → 0.16.0
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 +115 -60
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -17,7 +17,8 @@ var DEFAULTS = {
|
|
|
17
17
|
smallModel: "",
|
|
18
18
|
permissionDusk: 6e4,
|
|
19
19
|
droned: false,
|
|
20
|
-
droneModel: { providerID: "opencode-clwnd", modelID: "claude-haiku-4-5" }
|
|
20
|
+
droneModel: { providerID: "opencode-clwnd", modelID: "claude-haiku-4-5" },
|
|
21
|
+
ocCompaction: false
|
|
21
22
|
};
|
|
22
23
|
var CONFIG_PATHS = [
|
|
23
24
|
join(process.env.XDG_CONFIG_HOME ?? join(process.env.HOME ?? "/", ".config"), "clwnd", "clwnd.json")
|
|
@@ -34,7 +35,8 @@ function loadConfig() {
|
|
|
34
35
|
smallModel: raw.smallModel ?? DEFAULTS.smallModel,
|
|
35
36
|
permissionDusk: raw.permissionDusk ?? DEFAULTS.permissionDusk,
|
|
36
37
|
droned: raw.droned ?? DEFAULTS.droned,
|
|
37
|
-
droneModel: raw.droneModel ?? DEFAULTS.droneModel
|
|
38
|
+
droneModel: raw.droneModel ?? DEFAULTS.droneModel,
|
|
39
|
+
ocCompaction: raw.ocCompaction ?? DEFAULTS.ocCompaction
|
|
38
40
|
};
|
|
39
41
|
return cached;
|
|
40
42
|
} catch {
|
|
@@ -388,11 +390,9 @@ function mapToolName(name) {
|
|
|
388
390
|
var BROKERED_TOOLS = /* @__PURE__ */ new Set(["webfetch", "websearch", "todowrite"]);
|
|
389
391
|
var KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
390
392
|
"read",
|
|
391
|
-
"
|
|
392
|
-
"
|
|
393
|
+
"do_code",
|
|
394
|
+
"do_noncode",
|
|
393
395
|
"bash",
|
|
394
|
-
"glob",
|
|
395
|
-
"grep",
|
|
396
396
|
"webfetch",
|
|
397
397
|
"websearch",
|
|
398
398
|
"todowrite",
|
|
@@ -410,15 +410,18 @@ var KNOWN_TOOLS = /* @__PURE__ */ new Set([
|
|
|
410
410
|
"notebookedit",
|
|
411
411
|
"codesearch",
|
|
412
412
|
"applypatch",
|
|
413
|
-
"ls"
|
|
413
|
+
"ls",
|
|
414
|
+
// Replaced-and-banned. Do not forward. Do not re-enable.
|
|
415
|
+
"edit",
|
|
416
|
+
"write",
|
|
417
|
+
"glob",
|
|
418
|
+
"grep"
|
|
414
419
|
]);
|
|
415
420
|
var INPUT_FIELD_MAP = {
|
|
416
421
|
read: { file_path: "filePath" },
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
bash: {}
|
|
420
|
-
glob: {},
|
|
421
|
-
grep: {}
|
|
422
|
+
do_code: { file_path: "filePath", new_source: "newSource" },
|
|
423
|
+
do_noncode: { file_path: "filePath" },
|
|
424
|
+
bash: {}
|
|
422
425
|
};
|
|
423
426
|
function mapToolInput(toolName, input) {
|
|
424
427
|
const ocName = mapToolName(toolName);
|
|
@@ -474,7 +477,7 @@ function defaultSocketPath() {
|
|
|
474
477
|
var HUM_PATH = (process.env.CLWND_SOCKET ?? defaultSocketPath()) + ".hum";
|
|
475
478
|
var humSocket = null;
|
|
476
479
|
var humEcho = "";
|
|
477
|
-
var
|
|
480
|
+
var humHearers = /* @__PURE__ */ new Map();
|
|
478
481
|
var humAlive = false;
|
|
479
482
|
var humReady = null;
|
|
480
483
|
var humAwaken = awakenHum();
|
|
@@ -552,7 +555,11 @@ async function awakenHum() {
|
|
|
552
555
|
trace("hum.pulse", { kind: msg.kind, sid: msg.sid });
|
|
553
556
|
continue;
|
|
554
557
|
}
|
|
555
|
-
|
|
558
|
+
const msgSid = typeof msg.sid === "string" ? msg.sid : void 0;
|
|
559
|
+
if (msgSid) {
|
|
560
|
+
const h = humHearers.get(msgSid);
|
|
561
|
+
if (h) h(msg);
|
|
562
|
+
}
|
|
556
563
|
} catch {
|
|
557
564
|
}
|
|
558
565
|
}
|
|
@@ -594,15 +601,15 @@ function hum(msg) {
|
|
|
594
601
|
writeLog("trace", "hum.send.failed", { err: String(e) });
|
|
595
602
|
}
|
|
596
603
|
}
|
|
597
|
-
function humHear(onMessage) {
|
|
604
|
+
function humHear(sid, onMessage) {
|
|
598
605
|
return new Promise((resolve) => {
|
|
599
|
-
|
|
606
|
+
humHearers.set(sid, (incoming) => {
|
|
600
607
|
onMessage(incoming);
|
|
601
608
|
if (incoming.chi === "finish" || incoming.chi === "error") {
|
|
602
|
-
|
|
609
|
+
humHearers.delete(sid);
|
|
603
610
|
resolve();
|
|
604
611
|
}
|
|
605
|
-
};
|
|
612
|
+
});
|
|
606
613
|
});
|
|
607
614
|
}
|
|
608
615
|
function extractContent(prompt, sessionId) {
|
|
@@ -742,14 +749,6 @@ function sanitizePrompt(text, word) {
|
|
|
742
749
|
while (kept.length > 0 && !kept[0].trim()) kept.shift();
|
|
743
750
|
return kept.join("");
|
|
744
751
|
}
|
|
745
|
-
function isAuxiliaryCall(opts) {
|
|
746
|
-
return opts.tools === void 0;
|
|
747
|
-
}
|
|
748
|
-
function pickAuxModel(primary) {
|
|
749
|
-
if (/^claude-opus/i.test(primary)) return "claude-sonnet-4-6";
|
|
750
|
-
if (/^claude-sonnet/i.test(primary)) return "claude-haiku-4-5";
|
|
751
|
-
return primary;
|
|
752
|
-
}
|
|
753
752
|
function isBrokeredToolReturn(prompt) {
|
|
754
753
|
if (prompt.length < 2) return false;
|
|
755
754
|
const last = prompt[prompt.length - 1];
|
|
@@ -763,6 +762,7 @@ function isBrokeredToolReturn(prompt) {
|
|
|
763
762
|
}
|
|
764
763
|
var sessionLastAgent = /* @__PURE__ */ new Map();
|
|
765
764
|
var sessionPetalCounts = /* @__PURE__ */ new Map();
|
|
765
|
+
var sessionJustCompacted = /* @__PURE__ */ new Map();
|
|
766
766
|
function detectAgent(sid, headers) {
|
|
767
767
|
const raw = headers?.["x-clwnd-agent"] ?? null;
|
|
768
768
|
if (!raw) return null;
|
|
@@ -833,18 +833,16 @@ var lastAllowedToolsHash = /* @__PURE__ */ new Map();
|
|
|
833
833
|
var pendingPenny = {
|
|
834
834
|
humDedup: 0,
|
|
835
835
|
reminderStripped: 0,
|
|
836
|
-
priorPetalsElided: 0
|
|
837
|
-
auxModelRouted: 0
|
|
836
|
+
priorPetalsElided: 0
|
|
838
837
|
};
|
|
839
838
|
function flushPenny() {
|
|
840
|
-
if (pendingPenny.humDedup === 0 && pendingPenny.reminderStripped === 0 && pendingPenny.priorPetalsElided === 0
|
|
839
|
+
if (pendingPenny.humDedup === 0 && pendingPenny.reminderStripped === 0 && pendingPenny.priorPetalsElided === 0) {
|
|
841
840
|
return void 0;
|
|
842
841
|
}
|
|
843
842
|
const snap = { ...pendingPenny };
|
|
844
843
|
pendingPenny.humDedup = 0;
|
|
845
844
|
pendingPenny.reminderStripped = 0;
|
|
846
845
|
pendingPenny.priorPetalsElided = 0;
|
|
847
|
-
pendingPenny.auxModelRouted = 0;
|
|
848
846
|
return snap;
|
|
849
847
|
}
|
|
850
848
|
function cheapHash(s) {
|
|
@@ -853,7 +851,7 @@ function cheapHash(s) {
|
|
|
853
851
|
return h.toString(36) + ":" + s.length;
|
|
854
852
|
}
|
|
855
853
|
var AGENT_DENY = {
|
|
856
|
-
plan: /* @__PURE__ */ new Set(["
|
|
854
|
+
plan: /* @__PURE__ */ new Set(["do_code", "do_noncode"])
|
|
857
855
|
};
|
|
858
856
|
function deriveAllowedTools(sid, opts) {
|
|
859
857
|
const agent = opts.headers?.["x-clwnd-agent"] ?? "";
|
|
@@ -864,7 +862,7 @@ function deriveAllowedTools(sid, opts) {
|
|
|
864
862
|
} catch {
|
|
865
863
|
}
|
|
866
864
|
const denied = AGENT_DENY[agentName] ?? /* @__PURE__ */ new Set();
|
|
867
|
-
const all = ["read", "
|
|
865
|
+
const all = ["read", "do_code", "do_noncode", "bash", "webfetch"];
|
|
868
866
|
const result = all.filter((t) => !denied.has(t));
|
|
869
867
|
const key = result.join(",");
|
|
870
868
|
const prev = lastAllowedTools.get(sid);
|
|
@@ -911,14 +909,6 @@ var ClwndModel = class {
|
|
|
911
909
|
warnings: []
|
|
912
910
|
};
|
|
913
911
|
}
|
|
914
|
-
if (isAuxiliaryCall(opts)) {
|
|
915
|
-
return {
|
|
916
|
-
content: [{ type: "text", text: "" }],
|
|
917
|
-
usage: zeroUsage(),
|
|
918
|
-
finishReason: { unified: "stop", raw: "stop" },
|
|
919
|
-
warnings: []
|
|
920
|
-
};
|
|
921
|
-
}
|
|
922
912
|
const { stream } = await this.doStream(opts);
|
|
923
913
|
const reader = stream.getReader();
|
|
924
914
|
const content = [];
|
|
@@ -960,7 +950,7 @@ var ClwndModel = class {
|
|
|
960
950
|
} catch {
|
|
961
951
|
}
|
|
962
952
|
const isTitleGen = agentName === "title";
|
|
963
|
-
const
|
|
953
|
+
const isCompaction = agentName === "compaction";
|
|
964
954
|
if (isTitleGen) {
|
|
965
955
|
trace("title.skip", { method: "doStream", sid });
|
|
966
956
|
return {
|
|
@@ -972,13 +962,8 @@ var ClwndModel = class {
|
|
|
972
962
|
})
|
|
973
963
|
};
|
|
974
964
|
}
|
|
975
|
-
const skipGraft =
|
|
976
|
-
if (skipGraft) trace("graft.skip", { method: "doStream", sid, reason: "
|
|
977
|
-
const effectiveModel = isEmptyTools ? pickAuxModel(self.modelId) : self.modelId;
|
|
978
|
-
if (effectiveModel !== self.modelId) {
|
|
979
|
-
pendingPenny.auxModelRouted++;
|
|
980
|
-
trace("aux.model.routed", { sid, primary: self.modelId, aux: effectiveModel });
|
|
981
|
-
}
|
|
965
|
+
const skipGraft = isCompaction;
|
|
966
|
+
if (skipGraft) trace("graft.skip", { method: "doStream", sid, reason: "compaction" });
|
|
982
967
|
const systemPromptHash = cheapHash(systemPrompt);
|
|
983
968
|
const permissionsHash = cheapHash(JSON.stringify(permissions));
|
|
984
969
|
const allowedToolsHash = cheapHash(allowedTools.join(","));
|
|
@@ -996,7 +981,8 @@ var ClwndModel = class {
|
|
|
996
981
|
if (!lt || !Array.isArray(lt.content)) return false;
|
|
997
982
|
for (const p of lt.content) {
|
|
998
983
|
if (p.type === "tool-result" && p.toolCallId?.startsWith("perm-")) {
|
|
999
|
-
const
|
|
984
|
+
const loose = p;
|
|
985
|
+
const rawOutput = loose.output ?? loose.result;
|
|
1000
986
|
try {
|
|
1001
987
|
const outer = typeof rawOutput === "string" ? JSON.parse(rawOutput) : rawOutput;
|
|
1002
988
|
const inner = outer?.value ?? outer;
|
|
@@ -1039,15 +1025,21 @@ var ClwndModel = class {
|
|
|
1039
1025
|
if (!skipGraft) {
|
|
1040
1026
|
prevPetalCount = sessionPetalCounts.get(sid) ?? 0;
|
|
1041
1027
|
sessionPetalCounts.set(sid, priorPetals.length);
|
|
1042
|
-
|
|
1043
|
-
|
|
1028
|
+
const dropped = prevPetalCount - priorPetals.length;
|
|
1029
|
+
const justCompacted = sessionJustCompacted.get(sid) === true;
|
|
1030
|
+
if (justCompacted || prevPetalCount > 0 && dropped >= 2) {
|
|
1031
|
+
trace("compaction.detected", { sid, prev: prevPetalCount, now: priorPetals.length, dropped, reason: justCompacted ? "agent" : "petal-drop" });
|
|
1044
1032
|
hum({ chi: "cancel", sid, reason: "compaction" });
|
|
1033
|
+
sessionJustCompacted.delete(sid);
|
|
1045
1034
|
} else if (prevPetalCount === priorPetals.length && prevPetalCount > 0) {
|
|
1046
1035
|
elidePriorPetals = true;
|
|
1047
1036
|
pendingPenny.priorPetalsElided++;
|
|
1048
1037
|
trace("priorPetals.elided", { sid, count: priorPetals.length });
|
|
1049
1038
|
}
|
|
1050
1039
|
}
|
|
1040
|
+
if (isCompaction) {
|
|
1041
|
+
sessionJustCompacted.set(sid, true);
|
|
1042
|
+
}
|
|
1051
1043
|
const externalTools = [];
|
|
1052
1044
|
const externalToolNames = /* @__PURE__ */ new Set();
|
|
1053
1045
|
if (opts.tools) {
|
|
@@ -1059,9 +1051,10 @@ var ClwndModel = class {
|
|
|
1059
1051
|
externalToolNames.add(name);
|
|
1060
1052
|
}
|
|
1061
1053
|
}
|
|
1062
|
-
const
|
|
1063
|
-
trace("tools.available", { sid, count:
|
|
1054
|
+
const ocToolNames = opts.tools ? opts.tools.filter((t) => t.type === "function").map((t) => t.name) : [];
|
|
1055
|
+
trace("tools.available", { sid, count: ocToolNames.length, names: ocToolNames.join(",") });
|
|
1064
1056
|
if (externalTools.length > 0) trace("external.tools.detected", { sid, names: [...externalToolNames].join(",") });
|
|
1057
|
+
const visibleExternalNames = [...externalToolNames];
|
|
1065
1058
|
let promptSent = false;
|
|
1066
1059
|
if (listenOnly && humAlive) {
|
|
1067
1060
|
const pd = flushPenny();
|
|
@@ -1069,7 +1062,7 @@ var ClwndModel = class {
|
|
|
1069
1062
|
chi: "prompt",
|
|
1070
1063
|
sid,
|
|
1071
1064
|
cwd,
|
|
1072
|
-
modelId:
|
|
1065
|
+
modelId: self.modelId,
|
|
1073
1066
|
listenOnly: true,
|
|
1074
1067
|
...pd ? { pennyDelta: pd } : {},
|
|
1075
1068
|
dusk: duskIn(3e4)
|
|
@@ -1085,7 +1078,7 @@ var ClwndModel = class {
|
|
|
1085
1078
|
chi: "prompt",
|
|
1086
1079
|
sid,
|
|
1087
1080
|
cwd,
|
|
1088
|
-
modelId:
|
|
1081
|
+
modelId: self.modelId,
|
|
1089
1082
|
content,
|
|
1090
1083
|
text,
|
|
1091
1084
|
...sendSystemPrompt ? { systemPrompt } : {},
|
|
@@ -1097,7 +1090,7 @@ var ClwndModel = class {
|
|
|
1097
1090
|
...elidePriorPetals ? {} : { priorPetals },
|
|
1098
1091
|
externalTools: externalTools.length > 0 ? externalTools : void 0,
|
|
1099
1092
|
mcpServerConfigs: await getMcpServerConfigs(this.config.client),
|
|
1100
|
-
visibleTools:
|
|
1093
|
+
visibleTools: visibleExternalNames,
|
|
1101
1094
|
...pd ? { pennyDelta: pd } : {},
|
|
1102
1095
|
dusk: duskIn(3e4)
|
|
1103
1096
|
});
|
|
@@ -1125,6 +1118,7 @@ var ClwndModel = class {
|
|
|
1125
1118
|
function wilt() {
|
|
1126
1119
|
if (done) return;
|
|
1127
1120
|
done = true;
|
|
1121
|
+
humHearers.delete(sid);
|
|
1128
1122
|
try {
|
|
1129
1123
|
controller.close();
|
|
1130
1124
|
} catch {
|
|
@@ -1145,7 +1139,7 @@ var ClwndModel = class {
|
|
|
1145
1139
|
return;
|
|
1146
1140
|
}
|
|
1147
1141
|
petal({ type: "stream-start", warnings: [] });
|
|
1148
|
-
const humFade = humHear(onHummin);
|
|
1142
|
+
const humFade = humHear(sid, onHummin);
|
|
1149
1143
|
if (!promptSent) {
|
|
1150
1144
|
hum({
|
|
1151
1145
|
chi: "prompt",
|
|
@@ -1448,8 +1442,11 @@ var clwndPlugin = async (input) => {
|
|
|
1448
1442
|
"chat.headers": async (ctx, output) => {
|
|
1449
1443
|
output.headers["x-clwnd-agent"] = typeof ctx.agent === "string" ? ctx.agent : ctx.agent?.name ?? JSON.stringify(ctx.agent);
|
|
1450
1444
|
},
|
|
1451
|
-
//
|
|
1452
|
-
//
|
|
1445
|
+
// Custom tools registered with OC's tool registry. Each one delegates
|
|
1446
|
+
// to clwnd's daemon via the same MCP HTTP endpoint that Claude CLI uses
|
|
1447
|
+
// — JSON-RPC POST to http://127.0.0.1:29147/s/<sid> with method
|
|
1448
|
+
// tools/call. No new transport, no new vocabulary: plugin tools and
|
|
1449
|
+
// Claude CLI tools share the exact same executeTool() dispatch.
|
|
1453
1450
|
tool: {
|
|
1454
1451
|
clwnd_permission: tool({
|
|
1455
1452
|
description: "Permission prompt for clwnd file system operations",
|
|
@@ -1462,7 +1459,7 @@ var clwndPlugin = async (input) => {
|
|
|
1462
1459
|
trace("permission.tool.invoked", { tool: args.tool, path: args.path });
|
|
1463
1460
|
const t0 = Date.now();
|
|
1464
1461
|
await ctx.ask({
|
|
1465
|
-
permission: args.tool === "
|
|
1462
|
+
permission: args.tool === "do_code" || args.tool === "do_noncode" ? "edit" : args.tool,
|
|
1466
1463
|
patterns: [args.path ?? "*"],
|
|
1467
1464
|
metadata: { tool: args.tool, filepath: args.path },
|
|
1468
1465
|
always: [args.path ?? "*"]
|
|
@@ -1472,10 +1469,68 @@ var clwndPlugin = async (input) => {
|
|
|
1472
1469
|
trace("permission.tool.approved", { tool: args.tool, askId, elapsed, autoAllowed: elapsed < 100 });
|
|
1473
1470
|
return JSON.stringify({ granted: true, tool: args.tool, askId });
|
|
1474
1471
|
}
|
|
1472
|
+
}),
|
|
1473
|
+
do_code: tool({
|
|
1474
|
+
description: `Author code in a code file via AST-grounded operations. Accepts: .ts/.tsx/.js/.jsx/.py/.go/.rs/.java/.cpp/... (code files only; non-code is rejected \u2014 use do_noncode for that). Five operations:
|
|
1475
|
+
- operation: 'create', new_source: '<code>' \u2014 create a new file with new_source as its content. Re-parsed for syntax, rejected if invalid.
|
|
1476
|
+
- operation: 'replace', symbol: 'Class.method', new_source: '<full new source of that symbol>' \u2014 byte-range splice replacing the symbol with new_source. Re-parsed.
|
|
1477
|
+
- operation: 'replace', new_source: '<whole file>' \u2014 full-file rewrite, re-parsed.
|
|
1478
|
+
- operation: 'insert_after' | 'insert_before', symbol: 'NAME', new_source: '<new code block>' \u2014 add a new symbol adjacent to an anchor. Re-parsed.
|
|
1479
|
+
- operation: 'delete', symbol: 'NAME' \u2014 remove a symbol. Re-parsed.
|
|
1480
|
+
Before calling do_code on an existing file, run read(file_path) or read(file_path, symbol: '...') first \u2014 clwnd's staleness guard rejects edits whose baseline is older than the current mtime. There is no old_string/new_string vocabulary here \u2014 this is NOT a string replace tool.`,
|
|
1481
|
+
args: {
|
|
1482
|
+
file_path: tool.schema.string().describe("Absolute path to the code file"),
|
|
1483
|
+
operation: tool.schema.enum(["create", "replace", "insert_before", "insert_after", "delete"]).optional().describe("Operation to perform (default: replace)"),
|
|
1484
|
+
symbol: tool.schema.string().optional().describe("Target symbol name (required for insert/delete, optional for replace)"),
|
|
1485
|
+
new_source: tool.schema.string().optional().describe("New source code (required for create/replace/insert)")
|
|
1486
|
+
},
|
|
1487
|
+
async execute(args, ctx) {
|
|
1488
|
+
return callClwndTool("do_code", args, ctx.sessionID);
|
|
1489
|
+
}
|
|
1490
|
+
}),
|
|
1491
|
+
do_noncode: tool({
|
|
1492
|
+
description: `Author non-code files (configs, docs, JSON, YAML, Markdown, txt, \u2026). Rejects any file with a code extension \u2014 use do_code for those. Modes:
|
|
1493
|
+
- mode: 'write' (default) \u2014 create or overwrite with content
|
|
1494
|
+
- mode: 'append' \u2014 add content to the end of an existing file
|
|
1495
|
+
- mode: 'prepend' \u2014 add content to the start of an existing file
|
|
1496
|
+
For existing files, read(file_path) first so clwnd's staleness guard knows your baseline.`,
|
|
1497
|
+
args: {
|
|
1498
|
+
file_path: tool.schema.string().describe("Absolute path to the non-code file"),
|
|
1499
|
+
content: tool.schema.string().describe("Content to write/append/prepend"),
|
|
1500
|
+
mode: tool.schema.enum(["write", "append", "prepend"]).optional().describe("Write mode (default: write)")
|
|
1501
|
+
},
|
|
1502
|
+
async execute(args, ctx) {
|
|
1503
|
+
return callClwndTool("do_noncode", args, ctx.sessionID);
|
|
1504
|
+
}
|
|
1475
1505
|
})
|
|
1476
1506
|
}
|
|
1477
1507
|
};
|
|
1478
1508
|
};
|
|
1509
|
+
var CLWND_MCP_PORT = parseInt(process.env.CLWND_MCP_PORT ?? "29147") || 29147;
|
|
1510
|
+
var CLWND_MCP_HOST = process.env.CLWND_MCP_HOST ?? "127.0.0.1";
|
|
1511
|
+
async function callClwndTool(name, args, sessionID) {
|
|
1512
|
+
const url = `http://${CLWND_MCP_HOST}:${CLWND_MCP_PORT}/s/${sessionID}`;
|
|
1513
|
+
const body = {
|
|
1514
|
+
jsonrpc: "2.0",
|
|
1515
|
+
id: 1,
|
|
1516
|
+
method: "tools/call",
|
|
1517
|
+
params: { name, arguments: args }
|
|
1518
|
+
};
|
|
1519
|
+
try {
|
|
1520
|
+
const response = await fetch(url, {
|
|
1521
|
+
method: "POST",
|
|
1522
|
+
headers: { "Content-Type": "application/json" },
|
|
1523
|
+
body: JSON.stringify(body)
|
|
1524
|
+
});
|
|
1525
|
+
if (!response.ok) return `Error: clwnd MCP endpoint returned HTTP ${response.status}`;
|
|
1526
|
+
const data = await response.json();
|
|
1527
|
+
if (data.error) return `Error: ${data.error.message}`;
|
|
1528
|
+
const content = data.result?.content ?? [];
|
|
1529
|
+
return content.filter((c) => c.type === "text").map((c) => c.text ?? "").join("\n") || "(no output)";
|
|
1530
|
+
} catch (e) {
|
|
1531
|
+
return `Error: failed to reach clwnd daemon at ${url} \u2014 ${e instanceof Error ? e.message : String(e)}`;
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1479
1534
|
export {
|
|
1480
1535
|
clwndPlugin,
|
|
1481
1536
|
createClwnd
|