@khalilgharbaoui/opencode-claude-code-plugin 0.4.5 → 0.4.7

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 CHANGED
@@ -2,7 +2,33 @@
2
2
  import { generateId } from "@ai-sdk/provider-utils";
3
3
 
4
4
  // src/logger.ts
5
+ import { appendFileSync, mkdirSync, renameSync, statSync } from "fs";
6
+ import { homedir } from "os";
7
+ import { dirname, join } from "path";
5
8
  var DEBUG = process.env.DEBUG?.includes("opencode-claude-code") ?? false;
9
+ var LOG_DIR = process.env.OPENCODE_CLAUDE_CODE_LOG_DIR ?? join(homedir(), ".local", "share", "opencode-claude-code");
10
+ var LOG_FILE = join(LOG_DIR, "plugin.log");
11
+ var MAX_LOG_BYTES = 5 * 1024 * 1024;
12
+ var fileLoggingDisabled = false;
13
+ function rotateIfNeeded() {
14
+ try {
15
+ const stat = statSync(LOG_FILE);
16
+ if (stat.size > MAX_LOG_BYTES) {
17
+ renameSync(LOG_FILE, `${LOG_FILE}.1`);
18
+ }
19
+ } catch {
20
+ }
21
+ }
22
+ function writeToFile(line) {
23
+ if (fileLoggingDisabled) return;
24
+ try {
25
+ mkdirSync(dirname(LOG_FILE), { recursive: true });
26
+ rotateIfNeeded();
27
+ appendFileSync(LOG_FILE, line + "\n", "utf8");
28
+ } catch {
29
+ fileLoggingDisabled = true;
30
+ }
31
+ }
6
32
  function fmt(level, msg, data) {
7
33
  const ts = (/* @__PURE__ */ new Date()).toISOString();
8
34
  const base = `[${ts}] [opencode-claude-code] ${level}: ${msg}`;
@@ -11,21 +37,30 @@ function fmt(level, msg, data) {
11
37
  }
12
38
  return base;
13
39
  }
40
+ function emit(level, msg, data, alwaysStderr = false) {
41
+ const line = fmt(level, msg, data);
42
+ if (alwaysStderr || DEBUG) {
43
+ console.error(line);
44
+ }
45
+ writeToFile(line);
46
+ }
14
47
  var log = {
15
48
  info(msg, data) {
16
- if (DEBUG) console.error(fmt("INFO", msg, data));
49
+ if (DEBUG) emit("INFO", msg, data);
50
+ else writeToFile(fmt("INFO", msg, data));
17
51
  },
18
52
  notice(msg, data) {
19
- console.error(fmt("NOTICE", msg, data));
53
+ emit("NOTICE", msg, data, true);
20
54
  },
21
55
  warn(msg, data) {
22
- console.error(fmt("WARN", msg, data));
56
+ emit("WARN", msg, data, true);
23
57
  },
24
58
  error(msg, data) {
25
- console.error(fmt("ERROR", msg, data));
59
+ emit("ERROR", msg, data, true);
26
60
  },
27
61
  debug(msg, data) {
28
- if (DEBUG) console.error(fmt("DEBUG", msg, data));
62
+ if (DEBUG) emit("DEBUG", msg, data);
63
+ else writeToFile(fmt("DEBUG", msg, data));
29
64
  }
30
65
  };
31
66
 
@@ -1460,13 +1495,22 @@ function rejectAllPendingProxyCallsForSession(sessionKey2, error) {
1460
1495
  // src/claude-code-language-model.ts
1461
1496
  import { readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
1462
1497
  import { unlink as unlink2 } from "fs/promises";
1463
- import { homedir as homedir2, tmpdir as tmpdir2 } from "os";
1498
+ import { homedir as homedir3, tmpdir as tmpdir2 } from "os";
1464
1499
  import { randomUUID as randomUUID2 } from "crypto";
1465
- import { dirname as dirname2, join as join4 } from "path";
1500
+ import { dirname as dirname3, join as join5 } from "path";
1466
1501
  function hasNewUserContent(prompt) {
1467
1502
  for (let i = prompt.length - 1; i >= 0; i--) {
1468
1503
  const msg = prompt[i];
1469
1504
  if (msg.role === "assistant") return false;
1505
+ if (msg.role === "tool") {
1506
+ const content2 = msg.content;
1507
+ if (Array.isArray(content2)) {
1508
+ for (const part of content2) {
1509
+ if (part?.type === "tool-result") return true;
1510
+ }
1511
+ }
1512
+ continue;
1513
+ }
1470
1514
  if (msg.role !== "user") continue;
1471
1515
  const content = msg.content;
1472
1516
  if (typeof content === "string") {
@@ -1528,9 +1572,10 @@ function shouldAutoContinueIncompleteTurn(state, snapshot) {
1528
1572
  return { continue: false, reason: "max-elapsed" };
1529
1573
  }
1530
1574
  const text = normalizeVisibleText(snapshot.text);
1575
+ const lastText = normalizeVisibleText(snapshot.lastVisibleText);
1531
1576
  if (looksLikeQuestion(text)) return { continue: false, reason: "question" };
1532
1577
  if (looksLikeBlocker(text)) return { continue: false, reason: "blocker" };
1533
- if (looksLikeFinalAnswer(text)) {
1578
+ if (looksLikeFinalAnswer(lastText)) {
1534
1579
  return { continue: false, reason: "final-answer" };
1535
1580
  }
1536
1581
  const hadActivity = snapshot.hadReasoning || snapshot.hadToolActivity || snapshot.hadProxyActivity;
@@ -1565,9 +1610,9 @@ function readPromptFileIfPresent(path5) {
1565
1610
  function nearestWorkspaceAgentsPrompt(cwd) {
1566
1611
  let dir = cwd;
1567
1612
  while (true) {
1568
- const content = readPromptFileIfPresent(join4(dir, "AGENTS.md"));
1613
+ const content = readPromptFileIfPresent(join5(dir, "AGENTS.md"));
1569
1614
  if (content) return content;
1570
- const parent = dirname2(dir);
1615
+ const parent = dirname3(dir);
1571
1616
  if (parent === dir) return void 0;
1572
1617
  dir = parent;
1573
1618
  }
@@ -1582,15 +1627,15 @@ blocker. The user can interrupt or abort at any time; turn endings should
1582
1627
  mark meaningful checkpoints, not every completed substep.`;
1583
1628
  function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true) {
1584
1629
  const parts = [];
1585
- const configRoot = process.env.XDG_CONFIG_HOME ?? join4(homedir2(), ".config");
1586
- const globalAgents = readPromptFileIfPresent(join4(configRoot, "opencode", "AGENTS.md"));
1630
+ const configRoot = process.env.XDG_CONFIG_HOME ?? join5(homedir3(), ".config");
1631
+ const globalAgents = readPromptFileIfPresent(join5(configRoot, "opencode", "AGENTS.md"));
1587
1632
  const workspaceAgents = nearestWorkspaceAgentsPrompt(cwd);
1588
1633
  if (globalAgents) parts.push(globalAgents);
1589
1634
  if (workspaceAgents && workspaceAgents !== globalAgents) parts.push(workspaceAgents);
1590
1635
  if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
1591
1636
  const content = parts.join("\n\n");
1592
1637
  if (!content) return void 0;
1593
- const path5 = join4(tmpdir2(), `opencode-cc-sys-${randomUUID2()}.md`);
1638
+ const path5 = join5(tmpdir2(), `opencode-cc-sys-${randomUUID2()}.md`);
1594
1639
  try {
1595
1640
  writeFileSync3(path5, content, "utf8");
1596
1641
  return path5;
@@ -2492,6 +2537,7 @@ ${plan}
2492
2537
  let resultFallbackTimer = null;
2493
2538
  let hasReceivedContent = false;
2494
2539
  let visibleTextSinceContinue = "";
2540
+ let lastVisibleTextSinceContinue = "";
2495
2541
  let hadReasoningSinceContinue = false;
2496
2542
  let hadToolActivitySinceContinue = false;
2497
2543
  let hadProxyActivitySinceContinue = false;
@@ -2575,6 +2621,10 @@ ${plan}
2575
2621
  };
2576
2622
  const noteVisibleText = (text) => {
2577
2623
  visibleTextSinceContinue += text;
2624
+ lastVisibleTextSinceContinue += text;
2625
+ };
2626
+ const resetLastVisibleTextBlock = () => {
2627
+ lastVisibleTextSinceContinue = "";
2578
2628
  };
2579
2629
  const noteReasoning = () => {
2580
2630
  hadReasoningSinceContinue = true;
@@ -2587,6 +2637,7 @@ ${plan}
2587
2637
  };
2588
2638
  const resetAutoContinueWindow = () => {
2589
2639
  visibleTextSinceContinue = "";
2640
+ lastVisibleTextSinceContinue = "";
2590
2641
  hadReasoningSinceContinue = false;
2591
2642
  hadToolActivitySinceContinue = false;
2592
2643
  hadProxyActivitySinceContinue = false;
@@ -2632,6 +2683,7 @@ ${plan}
2632
2683
  }
2633
2684
  if (block.type === "text") {
2634
2685
  textBlockIndices.add(idx);
2686
+ resetLastVisibleTextBlock();
2635
2687
  if (block.text) {
2636
2688
  if (!currentTextId) startTextBlock();
2637
2689
  controller.enqueue({
@@ -2816,6 +2868,7 @@ ${plan}
2816
2868
  }
2817
2869
  for (const block of msg.message.content) {
2818
2870
  if (block.type === "text" && block.text) {
2871
+ resetLastVisibleTextBlock();
2819
2872
  const blockId = startTextBlock();
2820
2873
  controller.enqueue({
2821
2874
  type: "text-delta",
@@ -3026,6 +3079,7 @@ ${plan}
3026
3079
  autoContinueState,
3027
3080
  {
3028
3081
  text: visibleTextSinceContinue,
3082
+ lastVisibleText: lastVisibleTextSinceContinue,
3029
3083
  hadReasoning: hadReasoningSinceContinue,
3030
3084
  hadToolActivity: hadToolActivitySinceContinue,
3031
3085
  hadProxyActivity: hadProxyActivitySinceContinue,
@@ -3035,6 +3089,7 @@ ${plan}
3035
3089
  if (autoDecision.continue) {
3036
3090
  const signature = continuationSignature({
3037
3091
  text: visibleTextSinceContinue,
3092
+ lastVisibleText: lastVisibleTextSinceContinue,
3038
3093
  hadReasoning: hadReasoningSinceContinue,
3039
3094
  hadToolActivity: hadToolActivitySinceContinue,
3040
3095
  hadProxyActivity: hadProxyActivitySinceContinue,
@@ -3043,11 +3098,12 @@ ${plan}
3043
3098
  autoContinueState.noProgressCount = signature === autoContinueState.lastSignature ? autoContinueState.noProgressCount + 1 : 0;
3044
3099
  autoContinueState.lastSignature = signature;
3045
3100
  autoContinueState.attempts++;
3046
- log.info("auto-continuing incomplete claude result", {
3101
+ log.notice("auto-continuing incomplete claude result", {
3047
3102
  sessionKey: sk,
3048
3103
  reason: autoDecision.reason,
3049
3104
  attempts: autoContinueState.attempts,
3050
3105
  textLength: visibleTextSinceContinue.length,
3106
+ lastTextLength: lastVisibleTextSinceContinue.length,
3051
3107
  hadReasoning: hadReasoningSinceContinue,
3052
3108
  hadToolActivity: hadToolActivitySinceContinue,
3053
3109
  hadProxyActivity: hadProxyActivitySinceContinue
@@ -3057,10 +3113,15 @@ ${plan}
3057
3113
  proc.stdin?.write(makeAutoContinueMessage() + "\n");
3058
3114
  return;
3059
3115
  }
3060
- log.info("auto-continuation stopped", {
3116
+ log.notice("auto-continuation stopped", {
3061
3117
  sessionKey: sk,
3062
3118
  reason: autoDecision.reason,
3063
- attempts: autoContinueState.attempts
3119
+ attempts: autoContinueState.attempts,
3120
+ textLength: visibleTextSinceContinue.length,
3121
+ lastTextLength: lastVisibleTextSinceContinue.length,
3122
+ hadReasoning: hadReasoningSinceContinue,
3123
+ hadToolActivity: hadToolActivitySinceContinue,
3124
+ hadProxyActivity: hadProxyActivitySinceContinue
3064
3125
  });
3065
3126
  for (const [idx, reasoningId] of reasoningIds) {
3066
3127
  if (reasoningStarted.get(idx)) {
@@ -3568,8 +3629,8 @@ import {
3568
3629
  rmSync as rmSync2,
3569
3630
  writeFileSync as writeFileSync4
3570
3631
  } from "fs";
3571
- import { homedir as homedir3 } from "os";
3572
- import { join as join5, resolve as resolve2 } from "path";
3632
+ import { homedir as homedir4 } from "os";
3633
+ import { join as join6, resolve as resolve2 } from "path";
3573
3634
  import { fileURLToPath } from "url";
3574
3635
  var STALE_PACKAGE_NAME = "opencode-claude-code-plugin";
3575
3636
  var SUSPECT_DESCRIPTION_TOKEN = "Claude Code";
@@ -3577,14 +3638,14 @@ var alreadyRan = false;
3577
3638
  function candidateCacheRoots() {
3578
3639
  const xdg = process.env.XDG_CACHE_HOME;
3579
3640
  return [
3580
- xdg ? join5(xdg, "opencode") : null,
3581
- join5(homedir3(), ".cache", "opencode"),
3582
- join5(homedir3(), "Library", "Caches", "opencode")
3641
+ xdg ? join6(xdg, "opencode") : null,
3642
+ join6(homedir4(), ".cache", "opencode"),
3643
+ join6(homedir4(), "Library", "Caches", "opencode")
3583
3644
  ].filter((p) => Boolean(p));
3584
3645
  }
3585
3646
  function userOpencodeJsonPath() {
3586
- const xdgConfig = process.env.XDG_CONFIG_HOME ?? join5(homedir3(), ".config");
3587
- return join5(xdgConfig, "opencode", "opencode.json");
3647
+ const xdgConfig = process.env.XDG_CONFIG_HOME ?? join6(homedir4(), ".config");
3648
+ return join6(xdgConfig, "opencode", "opencode.json");
3588
3649
  }
3589
3650
  function userIntendsToUseUnscoped() {
3590
3651
  const cfg = userOpencodeJsonPath();
@@ -3627,7 +3688,7 @@ function cleanupStaleUnscopedInstall() {
3627
3688
  }
3628
3689
  function cleanupOne(cacheRoot, ourDir) {
3629
3690
  if (!existsSync3(cacheRoot)) return;
3630
- const stalePath = join5(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
3691
+ const stalePath = join6(cacheRoot, "node_modules", STALE_PACKAGE_NAME);
3631
3692
  if (!existsSync3(stalePath)) return;
3632
3693
  let realStalePath = stalePath;
3633
3694
  try {
@@ -3635,7 +3696,7 @@ function cleanupOne(cacheRoot, ourDir) {
3635
3696
  } catch {
3636
3697
  }
3637
3698
  if (ourDir && realStalePath === ourDir) return;
3638
- const pkgJsonPath = join5(stalePath, "package.json");
3699
+ const pkgJsonPath = join6(stalePath, "package.json");
3639
3700
  if (!existsSync3(pkgJsonPath)) return;
3640
3701
  let pkg = {};
3641
3702
  try {
@@ -3655,7 +3716,7 @@ function cleanupOne(cacheRoot, ourDir) {
3655
3716
  });
3656
3717
  return;
3657
3718
  }
3658
- const cachePkgJson = join5(cacheRoot, "package.json");
3719
+ const cachePkgJson = join6(cacheRoot, "package.json");
3659
3720
  if (!existsSync3(cachePkgJson)) return;
3660
3721
  try {
3661
3722
  const cfg = JSON.parse(readFileSync3(cachePkgJson, "utf8"));