@khalilgharbaoui/opencode-claude-code-plugin 0.43.0 → 0.45.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.d.ts CHANGED
@@ -362,6 +362,12 @@ interface ClaudeCodeConfig {
362
362
  interactiveAllowTools?: string[];
363
363
  /** With interactive: append this plugin's own prompts via --append-system-prompt-file. Defaults to true. */
364
364
  interactiveSystemPrompt?: boolean;
365
+ /**
366
+ * With the interactive transport: also append what the operator wrote that
367
+ * opencode forwarded (instruction files and the active agent's own prompt),
368
+ * verbatim and chosen by provenance. Off by default (h #g212).
369
+ */
370
+ interactiveUserInstructions?: boolean;
365
371
  cwd?: string;
366
372
  account?: string;
367
373
  configDir?: string;
@@ -481,6 +487,12 @@ interface ClaudeCodeProviderSettings {
481
487
  interactiveAllowTools?: string[];
482
488
  /** With interactive: append this plugin's own prompts via --append-system-prompt-file. Defaults to true. */
483
489
  interactiveSystemPrompt?: boolean;
490
+ /**
491
+ * With the interactive transport: also append what the operator wrote that
492
+ * opencode forwarded (instruction files and the active agent's own prompt),
493
+ * verbatim and chosen by provenance. Off by default (h #g212).
494
+ */
495
+ interactiveUserInstructions?: boolean;
484
496
  cwd?: string;
485
497
  name?: string;
486
498
  providerID?: string;
@@ -1370,6 +1382,11 @@ type AgentRecord = {
1370
1382
  * refused. Same account throughout; see `src/model-fallback.ts`.
1371
1383
  */
1372
1384
  fallbackModels?: string[];
1385
+ /**
1386
+ * The agent's own prompt as the operator wrote it: the markdown body, or
1387
+ * `prompt` in opencode.json. Read only by `interactiveUserInstructions`.
1388
+ */
1389
+ prompt?: string;
1373
1390
  };
1374
1391
  declare function getAgentRegistry(): Record<string, AgentRecord>;
1375
1392
  declare function getDefaultSubagentModel(): string | undefined;
package/dist/index.js CHANGED
@@ -5897,7 +5897,7 @@ var PLUGIN_LOG_MESSAGES = /* @__PURE__ */ new Set([
5897
5897
  "two different skills share a name; bridging the one the nearest root claimed",
5898
5898
  "unknown permissionPreset; no preset applied and every permission setting is left exactly as configured",
5899
5899
  "unsupported file part data type",
5900
- "unsupported media type for Claude image block, skipping"
5900
+ "unsupported media type for Claude attachment block, skipping"
5901
5901
  ]);
5902
5902
 
5903
5903
  // src/diagnostic-bundle.ts
@@ -7253,7 +7253,7 @@ var baseCapabilities = {
7253
7253
  temperature: false,
7254
7254
  attachment: true,
7255
7255
  toolcall: true,
7256
- input: { text: true, audio: false, image: true, video: false, pdf: false },
7256
+ input: { text: true, audio: false, image: true, video: false, pdf: true },
7257
7257
  output: { text: true, audio: false, image: false, video: false, pdf: false },
7258
7258
  interleaved: false
7259
7259
  };
@@ -7733,6 +7733,13 @@ function parseAgentFrontmatter(text) {
7733
7733
  }
7734
7734
  return record;
7735
7735
  }
7736
+ function agentMarkdownBody(text) {
7737
+ if (!text.startsWith("---")) return text.trim() || void 0;
7738
+ const lines = text.split(/\r?\n/);
7739
+ const close = lines.findIndex((line, index) => index > 0 && line.trim() === "---");
7740
+ if (close === -1) return void 0;
7741
+ return lines.slice(close + 1).join("\n").trim() || void 0;
7742
+ }
7736
7743
  function parseFallbackModelList(value) {
7737
7744
  if (Array.isArray(value)) {
7738
7745
  return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter(Boolean);
@@ -7756,6 +7763,8 @@ async function readAgentMarkdownRecords(directories) {
7756
7763
  try {
7757
7764
  const text = await readFile(path7.join(directory, entry), "utf8");
7758
7765
  records2[name] = parseAgentFrontmatter(text);
7766
+ const prompt = agentMarkdownBody(text);
7767
+ if (prompt) records2[name].prompt = prompt;
7759
7768
  } catch (err) {
7760
7769
  log.debug("failed to read agent markdown", {
7761
7770
  file: path7.join(directory, entry),
@@ -8001,13 +8010,14 @@ function stripContextReminders(prompt) {
8001
8010
  });
8002
8011
  return removed > 0 ? { prompt: out, removed } : { prompt, removed: 0 };
8003
8012
  }
8013
+ var PDF_MEDIA_TYPE = "application/pdf";
8004
8014
  var SUPPORTED_IMAGE_TYPES = /* @__PURE__ */ new Set([
8005
8015
  "image/jpeg",
8006
8016
  "image/png",
8007
8017
  "image/gif",
8008
8018
  "image/webp"
8009
8019
  ]);
8010
- function toImageBlock(part) {
8020
+ function toAttachmentBlock(part) {
8011
8021
  const raw = part.image ?? part.data ?? part.url ?? part.source?.data;
8012
8022
  if (!raw) {
8013
8023
  log.warn("file part without data, skipping");
@@ -8039,8 +8049,14 @@ function toImageBlock(part) {
8039
8049
  log.warn("unsupported file part data type", { dataType: typeof raw });
8040
8050
  return null;
8041
8051
  }
8052
+ if (resolvedMediaType.toLowerCase() === PDF_MEDIA_TYPE) {
8053
+ return {
8054
+ type: "document",
8055
+ source: { type: "base64", media_type: PDF_MEDIA_TYPE, data: base64 }
8056
+ };
8057
+ }
8042
8058
  if (!resolvedMediaType || !SUPPORTED_IMAGE_TYPES.has(resolvedMediaType)) {
8043
- log.warn("unsupported media type for Claude image block, skipping", {
8059
+ log.warn("unsupported media type for Claude attachment block, skipping", {
8044
8060
  mediaType: resolvedMediaType
8045
8061
  });
8046
8062
  return null;
@@ -8292,11 +8308,11 @@ Now continuing with the current message:
8292
8308
  content.push({ type: "text", text: part.text });
8293
8309
  }
8294
8310
  } else if (part.type === "file" || part.type === "image") {
8295
- const block = toImageBlock(part);
8311
+ const block = toAttachmentBlock(part);
8296
8312
  if (block) {
8297
8313
  content.push(block);
8298
8314
  } else {
8299
- log.debug("skipped non-image file part", {
8315
+ log.debug("skipped unsupported file part", {
8300
8316
  mediaType: part.mediaType
8301
8317
  });
8302
8318
  }
@@ -8356,7 +8372,7 @@ function getTrailingUserMessages(prompt, opts = {}) {
8356
8372
  if (part.type === "text") {
8357
8373
  if (part.text && part.text.trim()) blocks.push({ type: "text", text: part.text });
8358
8374
  } else if (part.type === "file" || part.type === "image") {
8359
- const block = toImageBlock(part);
8375
+ const block = toAttachmentBlock(part);
8360
8376
  if (block) blocks.push(block);
8361
8377
  }
8362
8378
  }
@@ -9771,12 +9787,14 @@ function decodeUserEnvelope(chunk, saveImage) {
9771
9787
  const images = [];
9772
9788
  let dropped = 0;
9773
9789
  for (const block of content) {
9774
- const extension = block?.type === "image" && block.source?.type === "base64" && typeof block.source.data === "string" ? IMAGE_EXTENSIONS[String(block.source.media_type).toLowerCase()] : void 0;
9790
+ const base64 = block?.source?.type === "base64" && typeof block.source.data === "string";
9791
+ const mediaType = String(block?.source?.media_type).toLowerCase();
9792
+ const extension = base64 && block.type === "image" ? IMAGE_EXTENSIONS[mediaType] : base64 && block.type === "document" && mediaType === "application/pdf" ? "pdf" : void 0;
9775
9793
  if (block?.type === "text" && typeof block.text === "string") {
9776
9794
  parts.push(block.text);
9777
9795
  } else if (extension && saveImage) {
9778
9796
  const file = saveImage(Buffer.from(block.source.data, "base64"), extension);
9779
- if (file) images.push(file);
9797
+ if (file) images.push(extension === "pdf" ? `@${file}` : file);
9780
9798
  else dropped++;
9781
9799
  } else if (block?.type === "tool_result") {
9782
9800
  const v = block.content;
@@ -9799,7 +9817,7 @@ ${text2}`
9799
9817
  }
9800
9818
  function stageImage(data, extension) {
9801
9819
  try {
9802
- const file = join10(pluginTmpDir(), `image-${randomUUID5()}.${extension}`);
9820
+ const file = join10(pluginTmpDir(), `attachment-${randomUUID5()}.${extension}`);
9803
9821
  writeFileSync5(file, data, { mode: 384, flag: "wx" });
9804
9822
  return file;
9805
9823
  } catch (err) {
@@ -10284,7 +10302,7 @@ function spawnInteractiveProcess(opts) {
10284
10302
  import { readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
10285
10303
  import { homedir as homedir8 } from "os";
10286
10304
  import { randomUUID as randomUUID6 } from "crypto";
10287
- import { dirname as dirname6, join as join11 } from "path";
10305
+ import { basename as basename2, dirname as dirname6, isAbsolute as isAbsolute3, join as join11 } from "path";
10288
10306
  function readPromptFileIfPresent(path11) {
10289
10307
  try {
10290
10308
  const content = readFileSync7(path11, "utf8").trim();
@@ -10390,6 +10408,36 @@ function extractSystemMessages(prompt) {
10390
10408
  }
10391
10409
  return out;
10392
10410
  }
10411
+ var CLAUDE_NATIVE_INSTRUCTION_FILES = /* @__PURE__ */ new Set(["CLAUDE.md", "CLAUDE.local.md"]);
10412
+ function userAuthoredInstructions(forwarded, opts = {}) {
10413
+ const readFile2 = opts.readFile ?? ((path11) => {
10414
+ try {
10415
+ return readFileSync7(path11, "utf8");
10416
+ } catch {
10417
+ return null;
10418
+ }
10419
+ });
10420
+ const out = [];
10421
+ const agentPrompt = opts.agentPrompt?.trim();
10422
+ if (agentPrompt && forwarded.includes(agentPrompt)) {
10423
+ out.push({ text: agentPrompt, content: agentPrompt });
10424
+ }
10425
+ const seen = /* @__PURE__ */ new Set();
10426
+ for (const match of forwarded.matchAll(/^Instructions from: (.+)$/gm)) {
10427
+ const path11 = match[1].trim();
10428
+ if (seen.has(path11) || !isAbsolute3(path11)) continue;
10429
+ if (CLAUDE_NATIVE_INSTRUCTION_FILES.has(basename2(path11))) continue;
10430
+ seen.add(path11);
10431
+ const file = readFile2(path11);
10432
+ if (!file?.trim()) continue;
10433
+ const start = match.index + match[0].length + 1;
10434
+ const content = forwarded.startsWith(file, start) ? file : forwarded.startsWith(file.trimEnd(), start) ? file.trimEnd() : null;
10435
+ if (content === null) continue;
10436
+ out.push({ text: `Instructions from: ${path11}
10437
+ ${content}`, content });
10438
+ }
10439
+ return out;
10440
+ }
10393
10441
  function buildAppendedSystemPrompt(cwd, includeMultiStepHint = true, extraSystemContent = [], options = {}) {
10394
10442
  const parts = [];
10395
10443
  if (options.compressionSummary?.trim()) {
@@ -10414,6 +10462,11 @@ ${options.compressionSummary.trim()}`
10414
10462
  if (pushGlobal) parts.push(globalAgents);
10415
10463
  if (pushWorkspace) parts.push(workspaceAgents);
10416
10464
  if (pushGlobal || pushWorkspace) parts.push(AGENTS_MAINTENANCE_HINT);
10465
+ for (const instruction of options.userInstructions ?? []) {
10466
+ const authored = instruction.content.trim();
10467
+ if (authored === globalAgents || authored === workspaceAgents) continue;
10468
+ parts.push(instruction.text);
10469
+ }
10417
10470
  if (includeMultiStepHint) parts.push(MULTI_STEP_TASK_HINT);
10418
10471
  const content = parts.join("\n\n");
10419
10472
  if (!content) return void 0;
@@ -13906,6 +13959,14 @@ var ClaudeCodeLanguageModel = class {
13906
13959
  "WebFetch"
13907
13960
  ]
13908
13961
  ];
13962
+ const userInstructions = self.config.interactiveUserInstructions === true ? userAuthoredInstructions(extractSystemMessages(options.prompt).join("\n\n"), {
13963
+ agentPrompt: getAgentRegistry()[self.getOpencodeAgent(options) ?? ""]?.prompt
13964
+ }) : [];
13965
+ if (userInstructions.length > 0) {
13966
+ log.info("forwarding user-authored instructions to the interactive TUI", {
13967
+ count: userInstructions.length
13968
+ });
13969
+ }
13909
13970
  const systemPromptFile = self.config.interactiveSystemPrompt === false ? void 0 : buildAppendedSystemPrompt(
13910
13971
  cwd,
13911
13972
  self.config.multiStepContinuation !== false,
@@ -13927,7 +13988,8 @@ var ClaudeCodeLanguageModel = class {
13927
13988
  opencodeCompressEnabled: wiring.opencodeToolDefs.some(
13928
13989
  (t) => t.name === "compress"
13929
13990
  ),
13930
- compressionSummary: getCompressionSummary(sk)
13991
+ compressionSummary: getCompressionSummary(sk),
13992
+ userInstructions
13931
13993
  }
13932
13994
  );
13933
13995
  if (self.config.interactiveSystemPrompt === false) {
@@ -15461,7 +15523,8 @@ function createClaudeCode(settings = {}) {
15461
15523
  interactive: settings.interactive,
15462
15524
  interactiveBypass: settings.interactiveBypass,
15463
15525
  interactiveAllowTools: settings.interactiveAllowTools,
15464
- interactiveSystemPrompt: settings.interactiveSystemPrompt
15526
+ interactiveSystemPrompt: settings.interactiveSystemPrompt,
15527
+ interactiveUserInstructions: settings.interactiveUserInstructions
15465
15528
  });
15466
15529
  };
15467
15530
  const provider = function(modelId) {
@@ -15680,7 +15743,8 @@ async function buildAgentRegistry(config) {
15680
15743
  forceModel: pick("forceModel") ?? records2[name]?.forceModel,
15681
15744
  reasoningEffort: pick("reasoningEffort") ?? records2[name]?.reasoningEffort,
15682
15745
  cacheTtl: pick("cacheTtl") ?? records2[name]?.cacheTtl,
15683
- fallbackModels: declaredChain.length ? declaredChain : records2[name]?.fallbackModels
15746
+ fallbackModels: declaredChain.length ? declaredChain : records2[name]?.fallbackModels,
15747
+ prompt: pick("prompt") ?? records2[name]?.prompt
15684
15748
  };
15685
15749
  }
15686
15750
  setAgentRegistry(records2);