@clwnd/opencode 0.11.0 → 0.12.1

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.
Files changed (2) hide show
  1. package/dist/index.js +132 -36
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -14,7 +14,6 @@ import { join } from "path";
14
14
  var DEFAULTS = {
15
15
  maxProcs: 4,
16
16
  idleTimeout: 3e4,
17
- ocCompaction: false,
18
17
  smallModel: "",
19
18
  permissionDusk: 6e4,
20
19
  droned: false,
@@ -32,7 +31,6 @@ function loadConfig() {
32
31
  cached = {
33
32
  maxProcs: raw.maxProcs ?? DEFAULTS.maxProcs,
34
33
  idleTimeout: raw.idleTimeout ?? DEFAULTS.idleTimeout,
35
- ocCompaction: raw.ocCompaction ?? DEFAULTS.ocCompaction,
36
34
  smallModel: raw.smallModel ?? DEFAULTS.smallModel,
37
35
  permissionDusk: raw.permissionDusk ?? DEFAULTS.permissionDusk,
38
36
  droned: raw.droned ?? DEFAULTS.droned,
@@ -381,6 +379,32 @@ function mapToolName(name) {
381
379
  return TOOL_NAME_MAP[name] ?? name;
382
380
  }
383
381
  var BROKERED_TOOLS = /* @__PURE__ */ new Set(["webfetch", "websearch", "todowrite"]);
382
+ var KNOWN_TOOLS = /* @__PURE__ */ new Set([
383
+ "read",
384
+ "edit",
385
+ "write",
386
+ "bash",
387
+ "glob",
388
+ "grep",
389
+ "webfetch",
390
+ "websearch",
391
+ "todowrite",
392
+ "task",
393
+ "skill",
394
+ "todoread",
395
+ "taskoutput",
396
+ "taskstop",
397
+ "question",
398
+ "clwnd_permission",
399
+ "permission_prompt",
400
+ "cronCreate",
401
+ "cronDelete",
402
+ "cronList",
403
+ "notebookedit",
404
+ "codesearch",
405
+ "applypatch",
406
+ "ls"
407
+ ]);
384
408
  var INPUT_FIELD_MAP = {
385
409
  read: { file_path: "filePath" },
386
410
  edit: { file_path: "filePath", old_string: "oldString", new_string: "newString", replace_all: "replaceAll" },
@@ -652,6 +676,7 @@ function isBrokeredToolReturn(prompt) {
652
676
  return false;
653
677
  }
654
678
  var sessionLastAgent = /* @__PURE__ */ new Map();
679
+ var sessionPetalCounts = /* @__PURE__ */ new Map();
655
680
  function detectAgent(sid, headers) {
656
681
  const raw = headers?.["x-clwnd-agent"] ?? null;
657
682
  if (!raw) return null;
@@ -694,6 +719,27 @@ async function getSessionPermissions(client, sessionId) {
694
719
  return [];
695
720
  }
696
721
  }
722
+ var mcpConfigCache = null;
723
+ async function getMcpServerConfigs(client) {
724
+ if (mcpConfigCache) return mcpConfigCache;
725
+ if (!client) return [];
726
+ try {
727
+ const resp = await client.config.get();
728
+ const mcp = resp.data?.mcp;
729
+ if (!mcp) return [];
730
+ const configs = [];
731
+ for (const [name, cfg] of Object.entries(mcp)) {
732
+ if (cfg.type === "local" && Array.isArray(cfg.command)) {
733
+ configs.push({ name, type: "local", command: cfg.command, environment: cfg.environment });
734
+ }
735
+ }
736
+ mcpConfigCache = configs;
737
+ if (configs.length > 0) trace("mcp.configs.loaded", { servers: configs.map((c) => c.name).join(",") });
738
+ return configs;
739
+ } catch {
740
+ return [];
741
+ }
742
+ }
697
743
  var lastAllowedTools = /* @__PURE__ */ new Map();
698
744
  var AGENT_DENY = {
699
745
  plan: /* @__PURE__ */ new Set(["edit", "write"])
@@ -768,6 +814,8 @@ var ClwndModel = class {
768
814
  }
769
815
  async doStream(opts) {
770
816
  const sid = opts.headers?.["x-opencode-session"] ?? sigil(Date.now().toString());
817
+ const lastRole = opts.prompt.length > 0 ? opts.prompt[opts.prompt.length - 1].role : "none";
818
+ trace("doStream.enter", { sid, promptLen: opts.prompt.length, lastRole });
771
819
  const content = extractContent(opts.prompt, sid);
772
820
  const text = content.filter((p) => p.type === "text").map((p) => p.text).join("\n\n");
773
821
  const systemPrompt = extractSystemPrompt(opts.prompt);
@@ -777,8 +825,30 @@ var ClwndModel = class {
777
825
  const sap = /* @__PURE__ */ new Map();
778
826
  const permissions = await getSessionPermissions(this.config.client, sid);
779
827
  const allowedTools = deriveAllowedTools(sid, opts);
780
- if (isAuxiliaryCall(opts) && !loadConfig().ocCompaction) {
781
- trace("auxiliary.reject", { method: "doStream" });
828
+ const isAux = isAuxiliaryCall(opts);
829
+ if (isAux) trace("auxiliary.passthrough", { method: "doStream", sid });
830
+ let permAskId = null;
831
+ const isPermReturn = isBrokeredToolReturn(opts.prompt) && (() => {
832
+ const lt = opts.prompt.findLast((m) => m.role === "tool");
833
+ if (!lt || !Array.isArray(lt.content)) return false;
834
+ for (const p of lt.content) {
835
+ if (p.type === "tool-result" && p.toolCallId?.startsWith("perm-")) {
836
+ const rawOutput = p.output ?? p.result;
837
+ try {
838
+ const outer = typeof rawOutput === "string" ? JSON.parse(rawOutput) : rawOutput;
839
+ const inner = outer?.value ?? outer;
840
+ const str = typeof inner === "string" ? inner : JSON.stringify(inner ?? "");
841
+ const parsed = JSON.parse(str);
842
+ if (parsed.askId) permAskId = parsed.askId;
843
+ } catch {
844
+ }
845
+ return true;
846
+ }
847
+ }
848
+ return false;
849
+ })();
850
+ if (isBrokeredToolReturn(opts.prompt) && !isPermReturn) {
851
+ trace("brokered.return", { sid });
782
852
  return {
783
853
  stream: new ReadableStream({
784
854
  start(controller) {
@@ -788,36 +858,47 @@ var ClwndModel = class {
788
858
  })
789
859
  };
790
860
  }
791
- if (isBrokeredToolReturn(opts.prompt)) {
792
- trace("brokered.return", { sid });
793
- let isPermissionReturn = false;
794
- const lastTool = opts.prompt.findLast((m) => m.role === "tool");
795
- if (lastTool && Array.isArray(lastTool.content)) {
796
- for (const part of lastTool.content) {
797
- if (part.type === "tool-result" && part.toolCallId?.startsWith("perm-")) {
798
- isPermissionReturn = true;
799
- }
800
- }
861
+ const listenOnly = !!isPermReturn;
862
+ const priorPetals = opts.prompt.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "tool");
863
+ trace("priorPetals", { sid, count: priorPetals.length, roles: priorPetals.map((m) => m.role).join(",") });
864
+ if (!isAux) {
865
+ const prev = sessionPetalCounts.get(sid) ?? 0;
866
+ sessionPetalCounts.set(sid, priorPetals.length);
867
+ if (prev > 0 && priorPetals.length < prev * 0.5) {
868
+ trace("compaction.detected", { sid, prev, now: priorPetals.length });
869
+ hum({ chi: "cancel", sid, reason: "compaction" });
801
870
  }
802
- if (!isPermissionReturn) {
803
- return {
804
- stream: new ReadableStream({
805
- start(controller) {
806
- controller.enqueue({ type: "finish", finishReason: { unified: "stop", raw: "stop" }, usage: zeroUsage() });
807
- controller.close();
808
- }
809
- })
810
- };
871
+ }
872
+ const externalTools = [];
873
+ const externalToolNames = /* @__PURE__ */ new Set();
874
+ if (opts.tools) {
875
+ for (const t of opts.tools) {
876
+ if (t.type !== "function") continue;
877
+ const name = t.name;
878
+ if (KNOWN_TOOLS.has(name)) continue;
879
+ externalTools.push({ name, description: t.description, inputSchema: t.inputSchema });
880
+ externalToolNames.add(name);
811
881
  }
812
882
  }
813
- const listenOnly = isBrokeredToolReturn(opts.prompt) && (() => {
814
- const lt = opts.prompt.findLast((m) => m.role === "tool");
815
- return lt && Array.isArray(lt.content) && lt.content.some((p) => p.type === "tool-result" && p.toolCallId?.startsWith("perm-"));
816
- })();
817
- const priorPetals = opts.prompt.filter((m) => m.role === "user" || m.role === "assistant" || m.role === "tool");
818
- trace("priorPetals", { sid, count: priorPetals.length, roles: priorPetals.map((m) => m.role).join(",") });
883
+ const allToolNames = opts.tools ? opts.tools.filter((t) => t.type === "function").map((t) => t.name) : [];
884
+ trace("tools.available", { sid, count: allToolNames.length, names: allToolNames.join(",") });
885
+ if (externalTools.length > 0) trace("external.tools.detected", { sid, names: [...externalToolNames].join(",") });
819
886
  let promptSent = false;
820
- if (!listenOnly && humAlive) {
887
+ if (listenOnly && humAlive) {
888
+ hum({
889
+ chi: "prompt",
890
+ sid,
891
+ cwd,
892
+ modelId: self.modelId,
893
+ listenOnly: true,
894
+ dusk: duskIn(3e4)
895
+ });
896
+ promptSent = true;
897
+ if (permAskId) {
898
+ trace("permission.hold.releasing", { sid, askId: permAskId });
899
+ hum({ chi: "release-permit", askId: permAskId, decision: "allow" });
900
+ }
901
+ } else if (!listenOnly && humAlive) {
821
902
  hum({
822
903
  chi: "prompt",
823
904
  sid,
@@ -829,8 +910,13 @@ var ClwndModel = class {
829
910
  permissions,
830
911
  allowedTools,
831
912
  listenOnly,
913
+ auxiliary: isAux || void 0,
914
+ // skip graft for compaction/title gen
832
915
  ocServerUrl: self.config.pluginInput?.serverUrl?.toString(),
833
916
  priorPetals,
917
+ externalTools: externalTools.length > 0 ? externalTools : void 0,
918
+ mcpServerConfigs: await getMcpServerConfigs(this.config.client),
919
+ visibleTools: allToolNames,
834
920
  dusk: duskIn(3e4)
835
921
  });
836
922
  promptSent = true;
@@ -839,6 +925,7 @@ var ClwndModel = class {
839
925
  async start(controller) {
840
926
  let done = false;
841
927
  const tendrils = /* @__PURE__ */ new Set();
928
+ const streamBrokered = new Set(BROKERED_TOOLS);
842
929
  const buds = [];
843
930
  const metaQueue = [];
844
931
  let textId = "t0";
@@ -908,6 +995,7 @@ var ClwndModel = class {
908
995
  const ct = raw.chunkType;
909
996
  if (ct === "text_start" || ct === "text_delta" && !textStarted) {
910
997
  if (!textStarted) {
998
+ textId = `t${Date.now()}`;
911
999
  textStarted = true;
912
1000
  petal({ type: "text-start", id: textId });
913
1001
  }
@@ -917,6 +1005,7 @@ var ClwndModel = class {
917
1005
  }
918
1006
  if (ct === "reasoning_start" || ct === "reasoning_delta" && !reasoningStarted) {
919
1007
  if (!reasoningStarted) {
1008
+ reasoningId = `r${Date.now()}`;
920
1009
  reasoningStarted = true;
921
1010
  petal({ type: "reasoning-start", id: reasoningId });
922
1011
  }
@@ -929,6 +1018,14 @@ var ClwndModel = class {
929
1018
  reasoningStarted = false;
930
1019
  }
931
1020
  if (ct === "tool_input_start" && raw.toolCallId && raw.toolName) {
1021
+ if (textStarted) {
1022
+ petal({ type: "text-end", id: textId });
1023
+ textStarted = false;
1024
+ }
1025
+ if (reasoningStarted) {
1026
+ petal({ type: "reasoning-end", id: reasoningId });
1027
+ reasoningStarted = false;
1028
+ }
932
1029
  sap.set(raw.toolCallId, "");
933
1030
  buds.push({ type: "tool-input-start", id: raw.toolCallId, toolName: mapToolName(raw.toolName) });
934
1031
  }
@@ -951,7 +1048,7 @@ var ClwndModel = class {
951
1048
  } else {
952
1049
  rawInput = "{}";
953
1050
  }
954
- const isBrokered = BROKERED_TOOLS.has(ocToolName);
1051
+ const isBrokered = streamBrokered.has(ocToolName);
955
1052
  if (isBrokered) tendrils.add(raw.toolCallId);
956
1053
  buds.push({
957
1054
  type: "tool-call",
@@ -1177,18 +1274,17 @@ var clwndPlugin = async (input) => {
1177
1274
  },
1178
1275
  async execute(args, ctx) {
1179
1276
  trace("permission.tool.invoked", { tool: args.tool, path: args.path });
1277
+ const t0 = Date.now();
1180
1278
  await ctx.ask({
1181
1279
  permission: args.tool === "edit" || args.tool === "write" ? "edit" : args.tool,
1182
1280
  patterns: [args.path ?? "*"],
1183
1281
  metadata: { tool: args.tool, filepath: args.path },
1184
1282
  always: [args.path ?? "*"]
1185
1283
  });
1284
+ const elapsed = Date.now() - t0;
1186
1285
  const askId = args.askId;
1187
- trace("permission.tool.approved", { tool: args.tool, askId });
1188
- if (askId) {
1189
- hum({ chi: "release-permit", askId, decision: "allow" });
1190
- }
1191
- return `Permission granted for ${args.tool}`;
1286
+ trace("permission.tool.approved", { tool: args.tool, askId, elapsed, autoAllowed: elapsed < 100 });
1287
+ return JSON.stringify({ granted: true, tool: args.tool, askId });
1192
1288
  }
1193
1289
  })
1194
1290
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clwnd/opencode",
3
- "version": "0.11.0",
3
+ "version": "0.12.1",
4
4
  "description": "clwnd for opencode",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",