dsh-packycode-api 1.1.1 → 1.1.3

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/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.3
4
+
5
+ - **Plan review when the CLI has no ExitPlanMode.** Print mode often does not offer that tool, so a plan-mode turn wrote `~/.dsh/packycode-claude-cli/plans/*.md` and came back as ordinary text. A plan file written during the run is now submitted through DSH's `exit_plan_mode` when the CLI returns. An older file in that directory is ignored.
6
+
7
+ ## 1.1.2
8
+
9
+ - **Claude CLI follows DSH plan mode.** While the current session's plan mode is on (including a selection that applies on this step but is not logged yet), a Claude model turn and the `claude_code` tool run the local CLI with `--permission-mode plan`. That wins over the session permission preset and over an explicit `claudeCliMode` pin. The CLI may explore read-only; other permission prompts are denied, and after eight denials the run stops instead of hanging. `ExitPlanMode` is never allowed inside the CLI process. A submitted plan becomes the assistant's `exit_plan_mode` tool call, so DSH's existing review approves it and the next step implements. The delegation tool returns the plan text for the parent model to submit. Session titles stay read-only. A host without the plan-mode service behaves as before.
10
+
3
11
  ## 1.1.1
4
12
 
5
13
  - **Claude CLI search through MCP.** CLI-backed Claude turns and the `claude_code` delegation tool now get a temporary, authenticated, loopback HTTP MCP `web_search` tool calling the plugin's selected DeepSeek/GPT backend. Native CLI `WebSearch` is explicitly denied in every permission mode: it makes a separate Claude server-tool request that PackyCode can refuse even when ordinary turns work. The official CLI/system identity is preserved; no vendor headers or identities are forged. Backend selection applies to CLI searches even when DSH routing is set to Off/Do not take over; the DSH routing mode remains independent, and headless profiles need no `web` service. MCP callbacks capture their originating session ID before losing agent context and always call the direct PackyCode provider, never an auto-router fallback.
package/README.en.md CHANGED
@@ -44,7 +44,7 @@ Open the desktop application's **Plugins** page, click **Add plugin**, and enter
44
44
  dsh-packycode-api
45
45
  ```
46
46
 
47
- Choose the **official** install source, then click **Install**. To pin a version, enter `dsh-packycode-api@1.1.1`.
47
+ Choose the **official** install source, then click **Install**. To pin a version, enter `dsh-packycode-api@1.1.3`.
48
48
 
49
49
  Then **manually restart the desktop application**. Refreshing the page does not reload the plugin inside the Host process.
50
50
 
@@ -63,7 +63,7 @@ Choose a model from the `PackyCode` group. `deepseek-flash`, `gpt-6.1-sol`, and
63
63
 
64
64
  The **Plugins** page shows the installed state and version, and is also where you enable, disable, or uninstall.
65
65
 
66
- **For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.1`. Do not use it for `desktop` — the CLI refuses it (`profile "desktop" is managed exclusively by the Electron application`).
66
+ **For non-desktop profiles only:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.3`. Do not use it for `desktop` — the CLI refuses it (`profile "desktop" is managed exclusively by the Electron application`).
67
67
 
68
68
  A local checkout can be linked with `dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`.
69
69
 
@@ -153,14 +153,14 @@ A model in the list does not mean the current token group allows it. When changi
153
153
  - The `cc` group accepts only the official Claude CLI. A normal API client gets `403 This API endpoint is only accessible via the official Claude CLI`. This plugin runs the local `claude` binary for that group instead of switching the token to `cc-sale` or pretending to be the official client. If the token has no group the official CLI may use, the three Claude models still will not run.
154
154
  - Selecting a Claude model when the CLI is missing fails that turn with an install hint. It does not surface as a missing binary name alone.
155
155
  - The CLI receives this plugin's endpoint and current key (`ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN`) and an isolated config directory, `~/.dsh/packycode-claude-cli`. Without that isolation, `env` in `~/.claude/settings.json` would override the plugin key.
156
- - CLI permission follows the session by default: `read-only` stays read-only, `workspace-write` may edit, and `danger-full-access` with approval disabled may use `full`. The CLI cannot ask the DSH user for approval, so a session that still asks never gets `full`. `claudeCliMode` can pin `read-only`, `edit`, or `full`. An empty `claudeCliPath` is looked up automatically; an empty `claudeCliCwd` uses the session workspace.
156
+ - CLI permission follows the session by default: `read-only` stays read-only, `workspace-write` may edit, and `danger-full-access` with approval disabled may use `full`. The CLI cannot ask the DSH user for approval, so a session that still asks never gets `full`. `claudeCliMode` can pin `read-only`, `edit`, or `full`. DSH plan mode wins over that mapping and over the pin: the CLI runs with `--permission-mode plan`. Calling `ExitPlanMode`, or returning after writing the plan file when that tool is absent, submits the plan to DSH's review instead of editing in the same process. The previous mode returns after approval or `/plan off`. Session titles stay out of plan mode. An empty `claudeCliPath` is looked up automatically; an empty `claudeCliCwd` uses the session workspace.
157
157
  - Thinking and tool activity (`▸ Bash`, `▸ Read`, and `✗` on failure) appear in that turn's reasoning block. They are progress, not DSH tool calls. The CLI's own tools are outside the DSH sandbox.
158
158
  - The reasoning block opens with `▸ 启动 Claude Code CLI…` and then shows the CLI's version and model once it is up. When the gateway throttles or is busy (429, 503 `账号并发排队已满` / per-account wait queue full, and similar), the CLI retries up to 10 times on its own; each retry now shows as `↻ 网关返回 HTTP 503,CLI 自动重试 n/10` instead of looking hung. When the retries run out, the turn says PackyCode's Claude upstream is busy, not that the key is bad. A CLI that prints nothing for 2 minutes after starting is stopped and reported.
159
159
  - The conversation and its images reach the CLI over stdin (`--input-format stream-json`), not argv: a Windows command line is length-limited and cannot carry an image. Images in the conversation are sent to Claude as images, at the HTTP route's request size (at most 2048×2048, about 1 MiB each; at most 20 images and 20 MiB per turn, dropping the oldest first). An image left out is named with its read-only local copy, which the CLI can open with Read.
160
160
  - Every run carries `--append-system-prompt` (the delegation note). In `-p` mode the official CLI introduces itself as "You are Claude Code, Anthropic's official CLI for Claude …" when it has appended instructions and as "You are a Claude agent, built on Anthropic's Claude Agent SDK." when it has none; the latter was refused upstream as not coming from the official CLI (403 `only accepts requests from the official Claude Code CLI`). That is the official CLI's own behavior; the plugin does not rewrite its system prompt.
161
161
  - While the session model is a Claude model, titles are generated with `claude-haiku-5-5`, read-only, in a temporary directory, instead of a full Opus turn.
162
162
  - While the session model is `gpt-6-astra`, titles are generated with `gpt-6.1-sol` (same Codex group); the turns themselves stay on Astra. Other models title their own sessions.
163
- - The `claude_code` tool can delegate one task to the local CLI while the session model is `gpt-6.1-sol`, `gpt-6-astra`, or `deepseek-flash`. The requested `mode` cannot exceed what the session preset allows.
163
+ - The `claude_code` tool can delegate one task to the local CLI while the session model is `gpt-6.1-sol`, `gpt-6-astra`, or `deepseek-flash`. The requested `mode` cannot exceed what the session preset allows. While the session is in plan mode the delegation plans too, and returns the plan text to the parent model.
164
164
  - An effort level the model does not support is rejected before the request is sent. Do not edit a model's effort map casually.
165
165
 
166
166
  </details>
package/README.md CHANGED
@@ -48,7 +48,7 @@
48
48
  dsh-packycode-api
49
49
  ```
50
50
 
51
- 在**安装源**中选择**官方源**,然后点击**安装**。需要固定版本时填入 `dsh-packycode-api@1.1.1`。
51
+ 在**安装源**中选择**官方源**,然后点击**安装**。需要固定版本时填入 `dsh-packycode-api@1.1.3`。
52
52
 
53
53
  完成后**手动重启桌面应用**。仅刷新页面不会重新加载 Host 里的插件。
54
54
 
@@ -67,7 +67,7 @@ dsh-packycode-api
67
67
 
68
68
  桌面应用的**插件**页可以看到已安装状态与版本,也可以在那里启用、停用或卸载。
69
69
 
70
- **仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.1`。不要对 `desktop` 使用它 —— CLI 会直接拒绝(`profile "desktop" is managed exclusively by the Electron application`)。
70
+ **仅用于非 desktop profile:** `dsh plugin --profile <name> add dsh-packycode-api@1.1.3`。不要对 `desktop` 使用它 —— CLI 会直接拒绝(`profile "desktop" is managed exclusively by the Electron application`)。
71
71
 
72
72
  本地开发可以用绝对路径:`dsh plugin --profile <name> add link:/path/to/dsh-packycode-api`。
73
73
 
@@ -157,14 +157,14 @@ Claude Code CLI → 本机临时 MCP web_search → 插件选定的 DeepSeek/GPT
157
157
  - `cc`(Claude Code 专用)只接受官方 Claude CLI。用普通 API 客户端调 Claude 会 403:`This API endpoint is only accessible via the official Claude CLI`。本插件因此调用本机 `claude`,而不是改去 `cc-sale`,也不是伪造客户端身份。令牌如果没有开放给官方 CLI 的分组,这三个模型仍然不可用。
158
158
  - 选了 Claude 模型但本机没有 CLI 时,这一回合会直接说明未安装,并指向设置页的安装按钮。
159
159
  - CLI 使用插件配置的地址和当前 Key(`ANTHROPIC_BASE_URL` / `ANTHROPIC_AUTH_TOKEN`),以及独立配置目录 `~/.dsh/packycode-claude-cli`。不隔离的话,`~/.claude/settings.json` 里的 `env` 会盖掉插件 Key。
160
- - CLI 权限默认跟随当前会话:`read-only` → 只读,`workspace-write` → 可编辑,`danger-full-access` 且不再审批 → `full`。CLI 不能向 DSH 弹审批,所以只要会话还要审批,就不会给它 `full`。也可把设置项 `claudeCliMode` 固定成 `read-only` / `edit` / `full`。`claudeCliPath` 留空则自动查找;`claudeCliCwd` 留空则用当前会话工作区。
160
+ - CLI 权限默认跟随当前会话:`read-only` → 只读,`workspace-write` → 可编辑,`danger-full-access` 且不再审批 → `full`。CLI 不能向 DSH 弹审批,所以只要会话还要审批,就不会给它 `full`。也可把设置项 `claudeCliMode` 固定成 `read-only` / `edit` / `full`。DSH 计划模式优先于该映射和这个固定档:CLI 以 `--permission-mode plan` 只读探索。它调用 `ExitPlanMode`,或在没有该工具时把计划写进计划文件后返回,都会交给 DSH 审阅;批准或 `/plan off` 之后才恢复原来的档位。会话标题不进入计划模式。`claudeCliPath` 留空则自动查找;`claudeCliCwd` 留空则用当前会话工作区。
161
161
  - 思考过程和工具调用(`▸ Bash`、`▸ Read`,失败时带 `✗`)会出现在这一回合的思考区。它们是进度,不是 DSH 的工具调用。CLI 自己的工具不受 DSH 沙箱约束。
162
162
  - 思考区第一行是「▸ 启动 Claude Code CLI…」,CLI 起来后显示版本和模型。网关限流或繁忙(429、503 `账号并发排队已满` 等)时,CLI 会自己重试最多 10 次,每次重试都会显示成 `↻ 网关返回 HTTP 503,CLI 自动重试 n/10`,不再像卡住。重试用完仍失败时,会说明是 PackyCode 的 Claude 上游繁忙,不是 Key 的问题。CLI 启动后 2 分钟内没有任何输出会被停止并报告原因。
163
163
  - 对话记录和图片通过 stdin 交给 CLI(`--input-format stream-json`),不走命令行参数:Windows 命令行长度有上限,而且命令行带不了图片。对话里的图片会以图片形式发给 Claude(与 HTTP 路由相同的压缩规格:每张不超过 2048×2048、约 1 MiB;一回合最多 20 张、合计 20 MiB,超出时先舍弃最早的)。没随消息发出的图片会写明本机只读副本的路径,CLI 需要时可以用 Read 读取。
164
164
  - 每次运行都带 `--append-system-prompt`(委派说明)。在 `-p` 模式下,官方 CLI 有追加说明时会以 "You are Claude Code, Anthropic's official CLI for Claude …" 自我介绍,没有时是 "You are a Claude agent, built on Anthropic's Claude Agent SDK.";后者曾被上游当成非官方 CLI 拒绝(403 `only accepts requests from the official Claude Code CLI`)。这是官方 CLI 自己的行为,插件没有改写它的系统提示词。
165
165
  - 会话跑在 Claude 模型上时,生成标题改用 `claude-haiku-5-5`,只读、临时目录,避免为起标题跑一整轮 Opus。
166
166
  - 会话跑在 `gpt-6-astra` 上时,标题改由 `gpt-6.1-sol` 生成(同一个 Codex 分组);对话本身仍然用 Astra。其它模型的标题由会话模型自己生成。
167
- - 另有 `claude_code` 工具:当前模型是 `gpt-6.1-sol`、`gpt-6-astra` 或 `deepseek-flash` 时,可以把一个任务委派给本机 Claude Code。请求的 `mode` 不会超过会话权限允许的上限。
167
+ - 另有 `claude_code` 工具:当前模型是 `gpt-6.1-sol`、`gpt-6-astra` 或 `deepseek-flash` 时,可以把一个任务委派给本机 Claude Code。请求的 `mode` 不会超过会话权限允许的上限;会话处于计划模式时,这个委派也只规划,并把计划正文交回父模型。
168
168
  - 声明了模型不支持的推理档位会被 harness 在发请求前拒绝,不要随手改档位映射。
169
169
 
170
170
  </details>
package/lib/index.js CHANGED
@@ -1,10 +1,10 @@
1
1
  import { randomBytes, randomUUID, timingSafeEqual } from "node:crypto";
2
2
  import { homedir, tmpdir } from "node:os";
3
- import { isAbsolute, join, posix, win32 } from "node:path";
3
+ import { isAbsolute, join, posix, sep, win32 } from "node:path";
4
4
  import z from "@deepseek-ai/schemastery";
5
5
  import { HarnessError, LlmAdapter, LlmError, QUOTA_EXCEEDED_CODE, resolveRetryPolicy } from "@deepseek-ai/dsh-llm";
6
6
  import { spawn } from "node:child_process";
7
- import { existsSync, readFileSync, readdirSync } from "node:fs";
7
+ import { existsSync, readFileSync, readdirSync, realpathSync, statSync } from "node:fs";
8
8
  import { mkdtemp, readFile, readdir, realpath, rename, rm, writeFile } from "node:fs/promises";
9
9
  import { createServer } from "node:http";
10
10
  import { defineTool } from "@deepseek-ai/dsh-tools";
@@ -1427,10 +1427,10 @@ const plainDescription = (part) => part.kind === "image" ? `[图片 ${attachment
1427
1427
  * @param cwd - the working directory the CLI will run in, when known.
1428
1428
  * @returns the instructions text.
1429
1429
  */
1430
- function delegationInstructions(options, cwd) {
1430
+ function delegationInstructions(options, cwd, { plan = false } = {}) {
1431
1431
  const system = typeof options?.system === "string" && options.system.trim() !== "" ? options.system.trim() : "";
1432
1432
  return [
1433
- "本回合由 DeepSeek Harness 委派给 Claude Code 执行。用户消息是 DeepSeek Harness 会话的近期对话记录,最后一条是当前请求;其中的图片已作为图片随消息附上,「只读副本」路径也可以用 Read 工具读取。请用你自己的工具完成任务,直接输出最终结果;不要反问、不要复述流程。",
1433
+ plan ? "本回合 DeepSeek Harness 处于计划模式。只做只读探索,把完整实施计划写成以一级标题开头的 Markdown,写入 CLI 指定的计划文件。若有 ExitPlanMode 就调用它;没有也在写完后停下来。不要修改项目、不要开始实施。DeepSeek Harness 会用这份计划打开审阅。用户消息是 DeepSeek Harness 会话的近期对话记录,最后一条是当前请求;其中的图片已作为图片随消息附上,「只读副本」路径也可以用 Read 工具读取。" : "本回合由 DeepSeek Harness 委派给 Claude Code 执行。用户消息是 DeepSeek Harness 会话的近期对话记录,最后一条是当前请求;其中的图片已作为图片随消息附上,「只读副本」路径也可以用 Read 工具读取。请用你自己的工具完成任务,直接输出最终结果;不要反问、不要复述流程。",
1434
1434
  cwd === void 0 ? void 0 : `工作目录:${cwd}`,
1435
1435
  system === "" ? void 0 : `补充说明(来自 DeepSeek Harness,其中提到的工具你未必拥有):${system.slice(0, 2e3)}`
1436
1436
  ].filter((part) => part !== void 0).join("\n");
@@ -1642,6 +1642,34 @@ function createBlockWriter() {
1642
1642
  if (open?.kind === "reasoning") open.sealed = false;
1643
1643
  yield* write("reasoning", `${separator}${line}`);
1644
1644
  },
1645
+ /** One complete tool call after whatever text or reasoning is open. */
1646
+ *toolCall(id, name, args) {
1647
+ yield* close();
1648
+ index += 1;
1649
+ const block = {
1650
+ type: "tool-call",
1651
+ id,
1652
+ name,
1653
+ arguments: args
1654
+ };
1655
+ yield {
1656
+ type: "block-start",
1657
+ index,
1658
+ blockType: "tool-call"
1659
+ };
1660
+ yield {
1661
+ type: "tool-call-delta",
1662
+ index,
1663
+ id,
1664
+ name,
1665
+ argumentsDelta: args
1666
+ };
1667
+ yield {
1668
+ type: "block-end",
1669
+ index,
1670
+ block
1671
+ };
1672
+ },
1645
1673
  close
1646
1674
  };
1647
1675
  }
@@ -1706,15 +1734,16 @@ async function cliCall(options, { titleModel, cwdOf, modeOf, attachmentsOf, imag
1706
1734
  cwd: tmpdir()
1707
1735
  };
1708
1736
  const cwd = cwdOf();
1737
+ const mode = modeOf();
1709
1738
  return {
1710
1739
  content: await buildDelegatedContent(options, {
1711
1740
  attachments: attachmentsOf(),
1712
1741
  limits: imageLimits,
1713
1742
  signal: options?.signal
1714
1743
  }),
1715
- appendSystemPrompt: delegationInstructions(options, cwd),
1744
+ appendSystemPrompt: delegationInstructions(options, cwd, { plan: mode === "plan" }),
1716
1745
  model: options.model,
1717
- mode: modeOf(),
1746
+ mode,
1718
1747
  cwd
1719
1748
  };
1720
1749
  }
@@ -1816,6 +1845,8 @@ function createClaudeCliAdapter({ inner, runner, modelIds = CLI_BACKED_MODEL_IDS
1816
1845
  let retries = 0;
1817
1846
  /** The key this run uses, as the runner described it (never the token). */
1818
1847
  let runKey;
1848
+ /** Set when the CLI submitted a plan; the turn then ends as DSH's review tool. */
1849
+ let submittedPlan;
1819
1850
  try {
1820
1851
  let finished;
1821
1852
  let lastRequest;
@@ -1858,11 +1889,26 @@ function createClaudeCliAdapter({ inner, runner, modelIds = CLI_BACKED_MODEL_IDS
1858
1889
  case "request-usage":
1859
1890
  lastRequest = event.usage;
1860
1891
  break;
1892
+ case "plan":
1893
+ if (typeof event.plan === "string" && event.plan !== "") submittedPlan = event.plan;
1894
+ break;
1861
1895
  case "result":
1862
1896
  finished = event.result;
1863
1897
  break;
1864
1898
  default: break;
1865
1899
  }
1900
+ if (submittedPlan !== void 0) {
1901
+ yield* blocks.toolCall(`plan-${randomUUID()}`, "exit_plan_mode", JSON.stringify({ plan: submittedPlan }));
1902
+ yield {
1903
+ type: "usage",
1904
+ usage: turnUsage(lastRequest, finished)
1905
+ };
1906
+ yield {
1907
+ type: "finish",
1908
+ reason: { kind: "tool-calls" }
1909
+ };
1910
+ return;
1911
+ }
1866
1912
  yield* blocks.close();
1867
1913
  yield {
1868
1914
  type: "usage",
@@ -2098,7 +2144,12 @@ const CLAUDE_CLI_MODES = Object.freeze({
2098
2144
  "Write",
2099
2145
  "NotebookEdit"
2100
2146
  ]) }),
2101
- full: Object.freeze({ skipPermissions: true })
2147
+ full: Object.freeze({ skipPermissions: true }),
2148
+ /**
2149
+ * DSH plan mode. Not a permission rank: file edits stay blocked, and
2150
+ * `ExitPlanMode` is handed back to DSH instead of letting the CLI implement.
2151
+ */
2152
+ plan: Object.freeze({ permissionMode: "plan" })
2102
2153
  });
2103
2154
  const DEFAULT_CLAUDE_MODE = "edit";
2104
2155
  /** Isolated CLI home; the plugin owns the endpoint and key it runs with. */
@@ -2279,11 +2330,128 @@ function buildClaudeArgs({ mode = DEFAULT_CLAUDE_MODE, model, stream = false, ap
2279
2330
  if (searchBridge !== void 0 && (!/^mcp__packycode_search_[a-f0-9]+__web_search$/u.test(searchBridge.toolName ?? "") || typeof searchBridge.configPath !== "string")) throw new Error("PackyCode CLI search bridge configuration is invalid");
2280
2331
  args.push("--append-system-prompt", searchBridge === void 0 ? instructions : `${instructions}\n\n${searchBridge.instructions}`);
2281
2332
  if (spec.skipPermissions === true) args.push("--dangerously-skip-permissions");
2282
- else args.push("--allowedTools", ...spec.allowedTools, ...searchBridge === void 0 ? [] : [searchBridge.toolName]);
2333
+ else if (typeof spec.permissionMode === "string") {
2334
+ args.push("--permission-mode", spec.permissionMode);
2335
+ if (searchBridge !== void 0) args.push("--allowedTools", searchBridge.toolName);
2336
+ } else args.push("--allowedTools", ...spec.allowedTools, ...searchBridge === void 0 ? [] : [searchBridge.toolName]);
2283
2337
  if (disableNativeSearch || searchBridge !== void 0) args.push("--disallowedTools", "WebSearch");
2284
2338
  if (searchBridge !== void 0) args.push("--mcp-config", searchBridge.configPath);
2285
2339
  return args;
2286
2340
  }
2341
+ /** How many permission prompts one plan-mode run will refuse before stopping. */
2342
+ const MAX_PLAN_DENIALS = 8;
2343
+ /** A plan file larger than this is not read into the turn. */
2344
+ const PLAN_FILE_MAX_BYTES = 2e5;
2345
+ const PLAN_FILE_CLOCK_SKEW_MS = 2e3;
2346
+ const PLAN_EXIT_DENY = "Write the plan to the plan file first, starting with a # heading, then call ExitPlanMode again.";
2347
+ const PLAN_ACTION_DENY = "Plan mode is active. Do not edit files or run commands that change the system. Continue read-only exploration, write the plan file, and call ExitPlanMode.";
2348
+ const PLAN_DIALOG_ERROR = "Plan mode cannot ask the user from the CLI. Decide and continue, or call ExitPlanMode.";
2349
+ const PLAN_DENY_LIMIT_MESSAGE = "claude_code plan mode stopped after too many denied permission prompts";
2350
+ /**
2351
+ * Markdown DSH's `exit_plan_mode` will accept. A body without a heading gets
2352
+ * one, because the review tool requires the plan to start with `#`.
2353
+ * @param value - candidate plan text.
2354
+ * @returns the plan, or undefined when there is nothing to submit.
2355
+ */
2356
+ function planMarkdown(value) {
2357
+ if (typeof value !== "string") return void 0;
2358
+ const trimmed = value.trim();
2359
+ if (trimmed === "") return void 0;
2360
+ const bounded = trimmed.length > PLAN_FILE_MAX_BYTES ? trimmed.slice(0, PLAN_FILE_MAX_BYTES) : trimmed;
2361
+ return /^#\s+\S/u.test(bounded) ? bounded : `# Plan\n\n${bounded}`;
2362
+ }
2363
+ /** Whether `file` is the root itself or a path inside it, after resolving links. */
2364
+ function pathInside(root, file, realpath) {
2365
+ let rootReal;
2366
+ let fileReal;
2367
+ try {
2368
+ rootReal = realpath(root);
2369
+ fileReal = realpath(file);
2370
+ } catch {
2371
+ return false;
2372
+ }
2373
+ const prefix = rootReal.endsWith(sep) ? rootReal : `${rootReal}${sep}`;
2374
+ return fileReal === rootReal || fileReal.startsWith(prefix);
2375
+ }
2376
+ /**
2377
+ * The plan a CLI `ExitPlanMode` request is submitting.
2378
+ *
2379
+ * The tool's own schema does not carry the plan; the CLI injects `plan` and
2380
+ * `planFilePath` from the file it told the model to write. Text on the request
2381
+ * wins. A path is read only when it resolves inside this run's config home or
2382
+ * `~/.claude/plans`, is a markdown file written for this run, and is bounded.
2383
+ * Anything else is ignored — a model-supplied path is not a licence to read
2384
+ * the disk.
2385
+ * @param input - the control request's `input`.
2386
+ * @param options - config home, when the run started, and file-system seams.
2387
+ * @returns plan markdown, or undefined when the request has none yet.
2388
+ */
2389
+ function readSubmittedPlan(input, { configDir, startedAt = Date.now(), home = homedir(), readFile = (file) => readFileSync(file, "utf8"), stat = statSync, realpath = realpathSync } = {}) {
2390
+ const inline = planMarkdown(input?.plan);
2391
+ if (inline !== void 0) return inline;
2392
+ const requested = typeof input?.planFilePath === "string" ? input.planFilePath : "";
2393
+ if (requested === "") return void 0;
2394
+ if (![configDir, join(home, ".claude", "plans")].filter((root) => typeof root === "string" && root !== "").some((root) => pathInside(root, requested, realpath))) return void 0;
2395
+ let file;
2396
+ try {
2397
+ file = realpath(requested);
2398
+ const info = stat(file);
2399
+ if (!info.isFile() || info.size > PLAN_FILE_MAX_BYTES || info.mtimeMs < startedAt - PLAN_FILE_CLOCK_SKEW_MS) return void 0;
2400
+ } catch {
2401
+ return;
2402
+ }
2403
+ if (!file.toLowerCase().endsWith(".md")) return void 0;
2404
+ try {
2405
+ return planMarkdown(readFile(file));
2406
+ } catch {
2407
+ return;
2408
+ }
2409
+ }
2410
+ /**
2411
+ * The plan file a print-mode CLI wrote and then returned without calling
2412
+ * `ExitPlanMode`. `-p` often has no interactive plan tool, so the file is
2413
+ * what DSH reviews. Only a markdown file under this run's config `plans`
2414
+ * directory or `~/.claude/plans`, newer than the run, is eligible. The newest
2415
+ * one wins.
2416
+ * @param options - config home, when the run started, and file-system seams.
2417
+ * @returns plan markdown, or undefined when this run wrote none.
2418
+ */
2419
+ function findPlanWrittenDuringRun({ configDir, startedAt = Date.now(), home = homedir(), readdir = readdirSync, readFile = (file) => readFileSync(file, "utf8"), stat = statSync, realpath = realpathSync } = {}) {
2420
+ const roots = [typeof configDir === "string" && configDir !== "" ? join(configDir, "plans") : "", join(home, ".claude", "plans")].filter((root) => root !== "");
2421
+ let newest;
2422
+ let newestMtime = -Infinity;
2423
+ for (const root of roots) {
2424
+ let names;
2425
+ try {
2426
+ names = readdir(root);
2427
+ } catch {
2428
+ continue;
2429
+ }
2430
+ for (const name of names) {
2431
+ if (typeof name !== "string" || !name.toLowerCase().endsWith(".md")) continue;
2432
+ const candidate = join(root, name);
2433
+ let info;
2434
+ try {
2435
+ info = stat(candidate);
2436
+ } catch {
2437
+ continue;
2438
+ }
2439
+ if (!info.isFile() || info.mtimeMs < startedAt - PLAN_FILE_CLOCK_SKEW_MS || info.mtimeMs < newestMtime) continue;
2440
+ const text = readSubmittedPlan({ planFilePath: candidate }, {
2441
+ configDir,
2442
+ startedAt,
2443
+ home,
2444
+ readFile,
2445
+ stat,
2446
+ realpath
2447
+ });
2448
+ if (text === void 0) continue;
2449
+ newest = text;
2450
+ newestMtime = info.mtimeMs;
2451
+ }
2452
+ }
2453
+ return newest;
2454
+ }
2287
2455
  /**
2288
2456
  * Build what the CLI reads from stdin for one run.
2289
2457
  *
@@ -2557,13 +2725,16 @@ function createClaudeRunner({ binary = findClaudeBinary(), configDir = DEFAULT_C
2557
2725
  if (disposed || signal?.aborted) throw new Error("claude_code was aborted");
2558
2726
  const cancellation = new AbortController();
2559
2727
  const runSignal = signal === void 0 ? cancellation.signal : AbortSignal.any([signal, cancellation.signal]);
2728
+ const startedAt = Date.now();
2560
2729
  const state = {
2561
2730
  stdout: "",
2562
2731
  stderr: "",
2563
2732
  timedOut: false,
2564
2733
  silent: false,
2565
2734
  sawOutput: false,
2566
- settled: false
2735
+ settled: false,
2736
+ denies: 0,
2737
+ startedAt
2567
2738
  };
2568
2739
  let child;
2569
2740
  let bridge;
@@ -2704,12 +2875,126 @@ function createClaudeRunner({ binary = findClaudeBinary(), configDir = DEFAULT_C
2704
2875
  }
2705
2876
  child.stdout?.setEncoding?.("utf8");
2706
2877
  child.stderr?.setEncoding?.("utf8");
2878
+ let controlBuffer = "";
2879
+ let stdinEnded = false;
2880
+ const endStdin = () => {
2881
+ if (stdinEnded) return;
2882
+ stdinEnded = true;
2883
+ try {
2884
+ child.stdin?.end?.();
2885
+ } catch {}
2886
+ };
2887
+ const writeStdin = (frame) => {
2888
+ try {
2889
+ child.stdin?.write?.(frame);
2890
+ } catch {}
2891
+ };
2892
+ const controlResponse = (requestId, response) => {
2893
+ writeStdin(`${JSON.stringify({
2894
+ type: "control_response",
2895
+ response: {
2896
+ subtype: "success",
2897
+ request_id: requestId,
2898
+ response
2899
+ }
2900
+ })}\n`);
2901
+ };
2902
+ const stopForDenials = () => {
2903
+ state.denyExhausted = true;
2904
+ endStdin();
2905
+ kill();
2906
+ };
2907
+ const refuseControl = (requestId, kind) => {
2908
+ state.denies += 1;
2909
+ if (state.denies > MAX_PLAN_DENIALS) {
2910
+ stopForDenials();
2911
+ return;
2912
+ }
2913
+ if (kind === "dialog") {
2914
+ writeStdin(`${JSON.stringify({
2915
+ type: "control_response",
2916
+ response: {
2917
+ subtype: "error",
2918
+ request_id: requestId,
2919
+ error: PLAN_DIALOG_ERROR
2920
+ }
2921
+ })}\n`);
2922
+ return;
2923
+ }
2924
+ controlResponse(requestId, {
2925
+ behavior: "deny",
2926
+ message: PLAN_ACTION_DENY
2927
+ });
2928
+ };
2929
+ /**
2930
+ * Plan mode keeps stdin open so a permission prompt can be answered.
2931
+ * `ExitPlanMode` with a plan stops the child; it is never allowed,
2932
+ * because allowing it would make the CLI start editing in this process.
2933
+ */
2934
+ const handlePlanLine = (line) => {
2935
+ const trimmed = line.trim();
2936
+ if (!trimmed.startsWith("{")) return;
2937
+ let message;
2938
+ try {
2939
+ message = JSON.parse(trimmed);
2940
+ } catch {
2941
+ return;
2942
+ }
2943
+ if (message?.type === "result") {
2944
+ endStdin();
2945
+ return;
2946
+ }
2947
+ if (message?.type !== "control_request") return;
2948
+ const requestId = message.request_id;
2949
+ const request = message.request;
2950
+ if (typeof requestId !== "string" || request === null || typeof request !== "object") {
2951
+ state.denies += 1;
2952
+ if (state.denies > MAX_PLAN_DENIALS) stopForDenials();
2953
+ return;
2954
+ }
2955
+ if (request.subtype !== "can_use_tool") {
2956
+ refuseControl(requestId, "dialog");
2957
+ return;
2958
+ }
2959
+ if (request.tool_name === "ExitPlanMode") {
2960
+ const plan = readSubmittedPlan(request.input, {
2961
+ configDir,
2962
+ startedAt
2963
+ });
2964
+ if (plan !== void 0) {
2965
+ state.plan = scrub(plan);
2966
+ endStdin();
2967
+ kill();
2968
+ return;
2969
+ }
2970
+ state.denies += 1;
2971
+ if (state.denies > MAX_PLAN_DENIALS) {
2972
+ stopForDenials();
2973
+ return;
2974
+ }
2975
+ controlResponse(requestId, {
2976
+ behavior: "deny",
2977
+ message: PLAN_EXIT_DENY
2978
+ });
2979
+ return;
2980
+ }
2981
+ refuseControl(requestId, "tool");
2982
+ };
2707
2983
  child.stdout?.on("data", (chunk) => {
2708
2984
  state.sawOutput = true;
2709
2985
  clearStartup();
2710
2986
  const text = String(chunk);
2711
2987
  state.stdout = scrub((state.stdout + text).slice(-4194304));
2712
2988
  onStdout?.(text);
2989
+ if (mode !== "plan") return;
2990
+ controlBuffer += text;
2991
+ let index = controlBuffer.indexOf("\n");
2992
+ while (index !== -1) {
2993
+ const line = controlBuffer.slice(0, index);
2994
+ controlBuffer = controlBuffer.slice(index + 1);
2995
+ handlePlanLine(line);
2996
+ index = controlBuffer.indexOf("\n");
2997
+ }
2713
2998
  });
2714
2999
  child.stderr?.on("data", (chunk) => {
2715
3000
  state.stderr = scrub((state.stderr + String(chunk)).slice(-4194304));
@@ -2728,6 +3013,12 @@ function createClaudeRunner({ binary = findClaudeBinary(), configDir = DEFAULT_C
2728
3013
  const detail = state.stderr.trim().split("\n").slice(-4).join(" ").slice(0, 400);
2729
3014
  return reject(/* @__PURE__ */ new Error(`Claude Code CLI printed nothing within ${Math.round(startupTimeoutMs / 1e3)}s of starting and was stopped${detail === "" ? "" : `: ${detail}`}`));
2730
3015
  }
3016
+ if (signal?.aborted) return reject(/* @__PURE__ */ new Error("claude_code was aborted"));
3017
+ if (state.denyExhausted === true) return reject(/* @__PURE__ */ new Error(PLAN_DENY_LIMIT_MESSAGE));
3018
+ if (typeof state.plan === "string") return resolve({
3019
+ code,
3020
+ killSignal
3021
+ });
2731
3022
  if (runSignal.aborted) return reject(/* @__PURE__ */ new Error("claude_code was aborted"));
2732
3023
  resolve({
2733
3024
  code,
@@ -2743,7 +3034,8 @@ function createClaudeRunner({ binary = findClaudeBinary(), configDir = DEFAULT_C
2743
3034
  });
2744
3035
  closed.catch(() => void 0);
2745
3036
  child.stdin?.on?.("error", () => void 0);
2746
- child.stdin?.end?.(input);
3037
+ if (mode === "plan") writeStdin(input);
3038
+ else child.stdin?.end?.(input);
2747
3039
  if (runSignal.aborted) kill();
2748
3040
  return {
2749
3041
  child,
@@ -2769,7 +3061,30 @@ function createClaudeRunner({ binary = findClaudeBinary(), configDir = DEFAULT_C
2769
3061
  * @param options - prompt, mode, model, working directory, and cancellation.
2770
3062
  * @returns the CLI's answer plus its own accounting.
2771
3063
  */
2772
- async run({ prompt, mode = DEFAULT_CLAUDE_MODE, model, appendSystemPrompt, cwd, signal, enableSearch } = {}) {
3064
+ async run({ prompt, content, mode = DEFAULT_CLAUDE_MODE, model, appendSystemPrompt, cwd, signal, enableSearch } = {}) {
3065
+ if (mode === "plan") {
3066
+ let finished;
3067
+ for await (const event of this.stream({
3068
+ prompt,
3069
+ content,
3070
+ mode,
3071
+ model,
3072
+ appendSystemPrompt,
3073
+ cwd,
3074
+ signal,
3075
+ enableSearch
3076
+ })) if (event.type === "result") finished = event.result;
3077
+ if (finished === void 0) throw new Error("claude_code plan mode ended without a result");
3078
+ return {
3079
+ result: finished.result,
3080
+ model: finished.model,
3081
+ mode: "plan",
3082
+ costUsd: finished.costUsd,
3083
+ durationMs: finished.durationMs,
3084
+ inputTokens: finished.inputTokens,
3085
+ outputTokens: finished.outputTokens
3086
+ };
3087
+ }
2773
3088
  const startedAt = Date.now();
2774
3089
  const { state, closed } = await start({
2775
3090
  prompt,
@@ -2902,6 +3217,42 @@ function createClaudeRunner({ binary = findClaudeBinary(), configDir = DEFAULT_C
2902
3217
  const event = readClaudeStreamEvents(JSON.parse(scrub(buffer.trim()))).find((item) => item.type === "result");
2903
3218
  if (event !== void 0) sawResult = event.result;
2904
3219
  } catch {}
3220
+ if (mode === "plan" && typeof state.plan !== "string" && exitStatus?.code === 0) {
3221
+ const written = findPlanWrittenDuringRun({
3222
+ configDir,
3223
+ startedAt: state.startedAt
3224
+ });
3225
+ if (written !== void 0) state.plan = scrub(written);
3226
+ }
3227
+ if (typeof state.plan === "string") {
3228
+ if (deltaKind !== void 0) {
3229
+ const tail = deltaRedactor.flush({ boundary: true });
3230
+ if (tail !== "") yield {
3231
+ type: deltaKind,
3232
+ text: tail
3233
+ };
3234
+ }
3235
+ const usage = sawResult?.usage ?? {};
3236
+ yield {
3237
+ type: "plan",
3238
+ plan: state.plan
3239
+ };
3240
+ yield {
3241
+ type: "result",
3242
+ result: {
3243
+ result: state.plan,
3244
+ model: typeof sawResult?.model === "string" && sawResult.model !== "" ? sawResult.model : model ?? "",
3245
+ mode: "plan",
3246
+ costUsd: typeof sawResult?.total_cost_usd === "number" ? sawResult.total_cost_usd : 0,
3247
+ durationMs: typeof sawResult?.duration_ms === "number" ? sawResult.duration_ms : Date.now() - startedAt,
3248
+ inputTokens: typeof usage.input_tokens === "number" ? usage.input_tokens : 0,
3249
+ outputTokens: typeof usage.output_tokens === "number" ? usage.output_tokens : 0,
3250
+ cacheReadTokens: typeof usage.cache_read_input_tokens === "number" ? usage.cache_read_input_tokens : 0,
3251
+ cacheWriteTokens: typeof usage.cache_creation_input_tokens === "number" ? usage.cache_creation_input_tokens : 0
3252
+ }
3253
+ };
3254
+ return;
3255
+ }
2905
3256
  if (sawResult === void 0) throw new Error(failureMessage(void 0, void 0, state.stderr));
2906
3257
  if (sawResult.is_error === true || exitStatus?.code !== 0) throw new Error(failureMessage(sawResult, exitStatus?.code ?? exitStatus?.killSignal, state.stderr));
2907
3258
  const usage = sawResult.usage ?? {};
@@ -3733,6 +4084,36 @@ function claudeModeFor(sandbox, approval) {
3733
4084
  }
3734
4085
  }
3735
4086
  /**
4087
+ * Whether this session should run the CLI in plan mode.
4088
+ *
4089
+ * The model request runs inside `agent/pre-step`, before the plan-mode service
4090
+ * appends `plan/mode`. `pending` is that not-yet-logged selection and wins over
4091
+ * the logged `active` bit, matching the `plan:policy` section. Without the
4092
+ * service, the logged projection is the only signal. A missing session, a
4093
+ * missing service, or a throw means plan mode is off — never a failed turn.
4094
+ * @param ctx - the plugin context; services are read without an inject requirement.
4095
+ * @param session - the session whose plan state is read.
4096
+ * @returns true when the CLI must plan instead of acting.
4097
+ */
4098
+ function planModeForSession(ctx, session) {
4099
+ if (session === void 0 || session === null) return false;
4100
+ try {
4101
+ const agent = ctx.get?.("agents")?.currentInitiator?.();
4102
+ if (agent?.session === session) {
4103
+ const state = ctx.get?.("planMode")?.get?.(agent);
4104
+ if (state !== void 0 && state !== null) return planSelected(state);
4105
+ }
4106
+ return ctx.get?.("sessionProjections")?.stateOf?.(session, "plan")?.active === true;
4107
+ } catch {
4108
+ return false;
4109
+ }
4110
+ }
4111
+ /** `pending` is the selection the in-flight request was assembled with. */
4112
+ function planSelected(state) {
4113
+ if (typeof state?.pending === "boolean") return state.pending;
4114
+ return state?.active === true;
4115
+ }
4116
+ /**
3736
4117
  * Read a session's CLI mode from the host's permission services.
3737
4118
  * @param ctx - the plugin context; services are read without an inject requirement.
3738
4119
  * @param session - the session whose permission state is read.
@@ -3768,16 +4149,25 @@ function capClaudeMode(requested, ceiling) {
3768
4149
  *
3769
4150
  * @module dsh-packycode-api/claude-tool
3770
4151
  */
4152
+ /** Modes a caller of `claude_code` may ask for. Plan mode is not one of them. */
4153
+ const CLAUDE_TOOL_MODES = Object.freeze([
4154
+ "read-only",
4155
+ "edit",
4156
+ "full"
4157
+ ]);
3771
4158
  /** What a `claude_code` tool run is told about where its task came from. */
3772
4159
  const CLAUDE_TOOL_INSTRUCTIONS = "This task was delegated to Claude Code by another agent in DeepSeek Harness through its claude_code tool. The task text is self-contained; complete it with your own tools and reply with the final result only.";
4160
+ /** What the same run is told while the parent session is in DSH plan mode. */
4161
+ const CLAUDE_TOOL_PLAN_INSTRUCTIONS = "This task was delegated to Claude Code while DeepSeek Harness is in plan mode. Explore read-only, write a complete markdown plan that starts with a # heading to the CLI plan file, then call ExitPlanMode. Do not edit the project or start implementing. Reply with the plan text only.";
3773
4162
  /**
3774
- * @param options - the CLI runner, and the most a run may do for a session.
4163
+ * @param options - the CLI runner, the most a run may do for a session, and
4164
+ * whether that session is in DSH plan mode.
3775
4165
  * @returns the tool definition to register.
3776
4166
  */
3777
- function createClaudeCodeTool({ runner, modeCeiling }) {
4167
+ function createClaudeCodeTool({ runner, modeCeiling, planActive }) {
3778
4168
  return defineTool({
3779
4169
  name: "claude_code",
3780
- description: "Delegate a task to the local Claude Code CLI (the official `claude` binary), which runs it with its own tools and returns its final answer. Use it when the user asks for Claude or when a task suits Claude Code. The child runs with this plugin's PackyCode endpoint and API key, in an isolated config home. `mode` bounds what it may do on its own: \"read-only\" (Read/Grep/Glob), \"edit\" (also Edit/Write), \"full\" (bypasses the CLI's permission checks, including shell commands); it defaults to, and is capped at, what this session's permission preset allows. It starts a fresh CLI session per call, takes tens of seconds, and bills through this plugin's stored PackyCode keys. Web searches use the selected DeepSeek/GPT backend through a read-only MCP tool rather than native WebSearch. Its internal tool calls are not part of this session's transcript — only the returned answer is.",
4170
+ description: "Delegate a task to the local Claude Code CLI (the official `claude` binary), which runs it with its own tools and returns its final answer. Use it when the user asks for Claude or when a task suits Claude Code. The child runs with this plugin's PackyCode endpoint and API key, in an isolated config home. `mode` bounds what it may do on its own: \"read-only\" (Read/Grep/Glob), \"edit\" (also Edit/Write), \"full\" (bypasses the CLI's permission checks, including shell commands); it defaults to, and is capped at, what this session's permission preset allows. While this session is in DSH plan mode the CLI plans instead, whatever `mode` says, and the returned result is the plan for you to submit with exit_plan_mode — do not treat it as permission to edit. It starts a fresh CLI session per call, takes tens of seconds, and bills through this plugin's stored PackyCode keys. Web searches use the selected DeepSeek/GPT backend through a read-only MCP tool rather than native WebSearch. Its internal tool calls are not part of this session's transcript — only the returned answer is.",
3781
4171
  parameters: {
3782
4172
  prompt: {
3783
4173
  type: "string",
@@ -3786,7 +4176,7 @@ function createClaudeCodeTool({ runner, modeCeiling }) {
3786
4176
  },
3787
4177
  mode: {
3788
4178
  type: "string",
3789
- enum: Object.keys(CLAUDE_CLI_MODES),
4179
+ enum: [...CLAUDE_TOOL_MODES],
3790
4180
  description: "What the CLI may do on its own. Defaults to the most this session's permission preset allows; a wider mode is capped to it. Ask for \"full\" only when the task genuinely needs shell commands."
3791
4181
  },
3792
4182
  model: {
@@ -3834,11 +4224,13 @@ function createClaudeCodeTool({ runner, modeCeiling }) {
3834
4224
  const sessionCwd = exec?.agent?.session?.header?.cwd;
3835
4225
  const requested = typeof args.cwd === "string" ? args.cwd.trim() : "";
3836
4226
  const cwd = requested === "" ? sessionCwd : isAbsolute(requested) ? requested : sessionCwd === void 0 ? requested : join(sessionCwd, requested);
4227
+ const session = exec?.agent?.session;
4228
+ const planning = planActive?.(session) === true;
3837
4229
  return await runner.run({
3838
4230
  prompt: args.prompt,
3839
- mode: capClaudeMode(args.mode, modeCeiling(exec?.agent?.session)) ?? "edit",
4231
+ mode: planning ? "plan" : capClaudeMode(args.mode, modeCeiling(session)) ?? "edit",
3840
4232
  model: args.model,
3841
- appendSystemPrompt: CLAUDE_TOOL_INSTRUCTIONS,
4233
+ appendSystemPrompt: planning ? CLAUDE_TOOL_PLAN_INSTRUCTIONS : CLAUDE_TOOL_INSTRUCTIONS,
3842
4234
  cwd,
3843
4235
  signal: exec?.signal
3844
4236
  });
@@ -6036,7 +6428,10 @@ function apply(ctx, config) {
6036
6428
  resolveAttachments: attachments
6037
6429
  }),
6038
6430
  runner: claudeRunner,
6039
- resolveMode: () => cliModeCeiling(currentSession()) ?? "edit",
6431
+ resolveMode: () => {
6432
+ const session = currentSession();
6433
+ return planModeForSession(ctx, session) ? "plan" : cliModeCeiling(session) ?? "edit";
6434
+ },
6040
6435
  resolveCwd: resolveCliCwd,
6041
6436
  resolveAttachments: attachments,
6042
6437
  explainKey: (runKey) => keys.describe(runKey)
@@ -6135,9 +6530,10 @@ function apply(ctx, config) {
6135
6530
  ctx.inject(["tools"], (toolsCtx) => {
6136
6531
  ctx.effect(() => toolsCtx.tools.register(createClaudeCodeTool({
6137
6532
  runner: claudeRunner,
6138
- modeCeiling: cliModeCeiling
6533
+ modeCeiling: cliModeCeiling,
6534
+ planActive: (session) => planModeForSession(ctx, session)
6139
6535
  })), "packycode-api: claude_code tool");
6140
6536
  });
6141
6537
  }
6142
6538
  //#endregion
6143
- export { AUTO_CLAUDE_MODE, CHANNEL, CLAUDE_CLI_MODES, CLAUDE_INSTALL_COMMAND, CLAUDE_MISSING_MESSAGE, CLAUDE_NPM_PACKAGE, CLAUDE_TOOL_INSTRUCTIONS, CLI_BACKED_MODEL_IDS, CLI_IMAGE_LIMITS, Config, DEFAULT_API_HOST, DEFAULT_APPEND_SYSTEM_PROMPT, DEFAULT_SEARCH_BACKEND, DEFAULT_STATUS_HOST, DISPLAY_NAME, LEGACY_STATUS_HOSTS, PACKYCODE_AUTO_SEARCH_PROVIDER_ID, PACKYCODE_MODELS, PACKYCODE_MODEL_IDS, PACKYCODE_SEARCH_PROVIDER_ID, PLUGIN_ID, PROVIDER, PackyCodeKeyRing, SEARCH_BACKENDS, SEARCH_BACKEND_MODELS, SEARCH_MODES, SETTINGS_NAMESPACE, TITLE_MODEL_ID, TITLE_MODEL_ROUTES, accountHostOrder, apply, assertApiKey, buildClaudeArgs, buildClaudeEnvironment, buildClaudeInput, buildDelegatedContent, buildSearchRequest, capClaudeMode, catalogCoverage, classifyClientPolicy, classifyGatewayFailure, claudeBinaryCandidates, claudeModeFor, claudeModeForSession, cliSearchInstructions, createBalanceReader, createClaudeCliAdapter, createClaudeCliManager, createClaudeCodeTool, createClaudeRunner, createClaudeSearchBridge, createGroupReader, createKeyChooser, createPackyCodeAutoSearchProvider, createPackyCodeProfile, createPackyCodeProvider, createPackyCodeRpc, createPackyCodeSearchProvider, createProfileSearchComposition, createRouteAdapter, createSearchComposition, createSearchController, delegationInstructions, describeRunKey, explainCliClientPolicyFailure, explainCliGroupFailure, explainClientPolicyFailure, explainNpmFailure, findClaudeBinary, guardKeyRotation, inject, keyNote, locateClaudeBinary, locateNpm, name, normalizeFailureMessage, normalizeHost, npmCandidates, parseBillingSubscription, parseBillingUsage, parseClaudeResult, parseClaudeVersion, parseMessagesSearchResponse, parsePricing, parseResponsesSearchResponse, parseSelectedGroups, parseTokenUsage, pickKeyForModel, readClaudeStreamEvents, readSearchChoice, renderAuxiliaryPrompt, renderDelegatedPrompt, resolveSpawnCommand, statusHostOrder, summarizeToolUse };
6539
+ export { AUTO_CLAUDE_MODE, CHANNEL, CLAUDE_CLI_MODES, CLAUDE_INSTALL_COMMAND, CLAUDE_MISSING_MESSAGE, CLAUDE_NPM_PACKAGE, CLAUDE_TOOL_INSTRUCTIONS, CLI_BACKED_MODEL_IDS, CLI_IMAGE_LIMITS, Config, DEFAULT_API_HOST, DEFAULT_APPEND_SYSTEM_PROMPT, DEFAULT_SEARCH_BACKEND, DEFAULT_STATUS_HOST, DISPLAY_NAME, LEGACY_STATUS_HOSTS, PACKYCODE_AUTO_SEARCH_PROVIDER_ID, PACKYCODE_MODELS, PACKYCODE_MODEL_IDS, PACKYCODE_SEARCH_PROVIDER_ID, PLUGIN_ID, PROVIDER, PackyCodeKeyRing, SEARCH_BACKENDS, SEARCH_BACKEND_MODELS, SEARCH_MODES, SETTINGS_NAMESPACE, TITLE_MODEL_ID, TITLE_MODEL_ROUTES, accountHostOrder, apply, assertApiKey, buildClaudeArgs, buildClaudeEnvironment, buildClaudeInput, buildDelegatedContent, buildSearchRequest, capClaudeMode, catalogCoverage, classifyClientPolicy, classifyGatewayFailure, claudeBinaryCandidates, claudeModeFor, claudeModeForSession, cliSearchInstructions, createBalanceReader, createClaudeCliAdapter, createClaudeCliManager, createClaudeCodeTool, createClaudeRunner, createClaudeSearchBridge, createGroupReader, createKeyChooser, createPackyCodeAutoSearchProvider, createPackyCodeProfile, createPackyCodeProvider, createPackyCodeRpc, createPackyCodeSearchProvider, createProfileSearchComposition, createRouteAdapter, createSearchComposition, createSearchController, delegationInstructions, describeRunKey, explainCliClientPolicyFailure, explainCliGroupFailure, explainClientPolicyFailure, explainNpmFailure, findClaudeBinary, guardKeyRotation, inject, keyNote, locateClaudeBinary, locateNpm, name, normalizeFailureMessage, normalizeHost, npmCandidates, parseBillingSubscription, parseBillingUsage, parseClaudeResult, parseClaudeVersion, parseMessagesSearchResponse, parsePricing, parseResponsesSearchResponse, parseSelectedGroups, parseTokenUsage, pickKeyForModel, planModeForSession, readClaudeStreamEvents, readSearchChoice, renderAuxiliaryPrompt, renderDelegatedPrompt, resolveSpawnCommand, statusHostOrder, summarizeToolUse };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-packycode-api",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "packageManager": "pnpm@10.32.1",
5
5
  "description": "Use a PackyCode (PackyAPI) API key in DeepSeek Harness through a built-in multi-protocol model route with key rotation and balance display",
6
6
  "type": "module",