dsh-codex-subscription 3.0.0-beta.6 → 3.0.0-beta.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/README.en.md CHANGED
@@ -106,10 +106,10 @@ Pair the ChatGPT mobile app's Codex section with a QR code and the DSH on your c
106
106
  - approvals and questions pop up on the phone; if it drops, they pop up again when you reopen it, otherwise they go back to the computer;
107
107
  - replies appear as they are written; commands, file edits with diffs, the task list, web searches, connector calls and generated images use the phone's native views;
108
108
  - send photos, @ search files on the computer, / pick DSH skills, view changes with read-only git, and start chats in a real folder picker;
109
- - DSH workspaces appear as projects; fork a conversation, see usage limits, lifetime usage and context usage;
109
+ - DSH workspaces appear as projects; fork a conversation, see usage limits, lifetime usage and context usage; where DSH has `/compact` (0.2.0-rc.2 onwards), compact the context from the phone;
110
110
  - model errors show on the phone; paired phones are listed in settings and can be removed.
111
111
 
112
- Off by default: **Settings → Codex → Models & runtime → Phone remote control**. The computer must stay on, online and running DSH. This is a ChatGPT capability OpenAI has not opened to third parties and may change at any time, hence Beta. Currently in the pre-release: `dsh-codex-subscription@3.0.0-beta.5`.
112
+ Off by default: **Settings → Codex → Models & runtime → Phone remote control**. The computer must stay on, online and running DSH. This is a ChatGPT capability OpenAI has not opened to third parties and may change at any time, hence Beta. Currently in the pre-release: `dsh-codex-subscription@3.0.0-beta.7`.
113
113
 
114
114
  <p align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/assets/ui-remote-en-dark.png"><img src="docs/assets/ui-remote-en-light.png" width="560" alt="Phone remote control in settings: the switch, pairing QR code and manual code, paired phones"></picture></p>
115
115
 
@@ -199,7 +199,7 @@ Model access and quota depend on your account.
199
199
 
200
200
  | Feature | What you can do | Availability / setup |
201
201
  | --- | --- | --- |
202
- | **Phone remote control** | Drive the DSH on your computer from the Codex section of the ChatGPT mobile app: start, continue and archive conversations, switch model and permissions, answer approvals and questions; DeepSeek and any other DSH model work | Beta (3.0.0-beta.5); off by default; Settings → Codex → Models & runtime → Phone remote control, pair by QR or manual code |
202
+ | **Phone remote control** | Drive the DSH on your computer from the Codex section of the ChatGPT mobile app: start, continue and archive conversations, switch model and permissions, answer approvals and questions; DeepSeek and any other DSH model work | Beta (3.0.0-beta.7); off by default; Settings → Codex → Models & runtime → Phone remote control, pair by QR or manual code |
203
203
  | **Codex independent subtasks** | Reuse subscription sign-in and workspace permissions; select allowed models and reasoning levels | Beta; DSH by default, Codex requires the optional component and model-selection setup |
204
204
  | **Subtask component management** | Install, disable, or uninstall the official Codex subtask component from settings | Managed by supported DSH hosts; no extra login |
205
205
  | **Cloud context compaction** | Continue long conversations using Codex compaction while retaining DSH history and native compaction | Beta / experimental; off by default, enabled requests use SSE |
package/README.md CHANGED
@@ -106,10 +106,10 @@ dsh --profile headless "只回复:ok"
106
106
  - 批准工具调用或回答提问时手机直接弹出;手机掉线,重新打开会再弹一次,否则交回电脑处理;
107
107
  - 回复边生成边显示;命令、文件修改(带差异)、任务清单、联网搜索、连接器调用和生成的图片用手机原生视图显示;
108
108
  - 发图片,@ 搜电脑上的文件,/ 选 DSH 技能,只读 git 查看改动,用真实的文件夹选择器新建对话;
109
- - DSH 工作区对应手机上的项目;可以从手机分叉对话,看用量额度、累计用量和上下文用量;
109
+ - DSH 工作区对应手机上的项目;可以从手机分叉对话,看用量额度、累计用量和上下文用量;DSH 带有 `/compact` 时(0.2.0-rc.2 起)还能从手机压缩上下文;
110
110
  - 模型报错会显示在手机上;设置里能看到已配对的手机,随时移除。
111
111
 
112
- 默认关闭:**设置 → Codex 订阅 → 模型与运行 → 手机远程控制**。电脑需要保持开机、联网并打开 DSH。这是 OpenAI 尚未向第三方开放的 ChatGPT 能力,随时可能变化,所以标为 Beta,目前在预发布版中提供:`dsh-codex-subscription@3.0.0-beta.5`。
112
+ 默认关闭:**设置 → Codex 订阅 → 模型与运行 → 手机远程控制**。电脑需要保持开机、联网并打开 DSH。这是 OpenAI 尚未向第三方开放的 ChatGPT 能力,随时可能变化,所以标为 Beta,目前在预发布版中提供:`dsh-codex-subscription@3.0.0-beta.7`。
113
113
 
114
114
  <p align="center"><picture><source media="(prefers-color-scheme: dark)" srcset="docs/assets/ui-remote-zh-dark.png"><img src="docs/assets/ui-remote-zh-light.png" width="560" alt="设置里的手机远程控制:开关、配对二维码和手动码、已配对的手机"></picture></p>
115
115
 
@@ -199,7 +199,7 @@ dsh --profile headless "只回复:ok"
199
199
 
200
200
  | 功能 | 你可以做什么 | 使用方式 / 条件 |
201
201
  | --- | --- | --- |
202
- | **手机远程控制** | 在 ChatGPT 手机 App 的 Codex 里直接指挥电脑上的 DSH:新建、续聊、归档对话,切换模型和权限,回答审批和提问,DeepSeek 等任意 DSH 模型都能用 | Beta(3.0.0-beta.5);默认关闭,设置 → Codex 订阅 → 模型与运行 → 手机远程控制,扫码或输入手动码配对 |
202
+ | **手机远程控制** | 在 ChatGPT 手机 App 的 Codex 里直接指挥电脑上的 DSH:新建、续聊、归档对话,切换模型和权限,回答审批和提问,DeepSeek 等任意 DSH 模型都能用 | Beta(3.0.0-beta.7);默认关闭,设置 → Codex 订阅 → 模型与运行 → 手机远程控制,扫码或输入手动码配对 |
203
203
  | **Codex 独立子任务** | 复用订阅登录和工作区权限执行独立任务,可指定允许的模型与推理档位 | Beta;默认 DSH,Codex 需安装可选组件并配置模型选择 |
204
204
  | **子任务组件管理** | 在设置里安装、停用或卸载官方 Codex 子任务组件 | 由支持的 DSH 宿主管理,无需额外登录 |
205
205
  | **云端上下文压缩** | 使用 Codex 云端压缩继续长对话,同时保留 DSH 历史与原生压缩 | Beta / 实验性;默认关闭,开启的请求使用 SSE |
package/lib/client.js CHANGED
@@ -9282,7 +9282,7 @@ window.__ModuleLoader__.load({
9282
9282
  }
9283
9283
  //#endregion
9284
9284
  //#region src/version.js
9285
- const PACKAGE_VERSION = "3.0.0-beta.6";
9285
+ const PACKAGE_VERSION = "3.0.0-beta.7";
9286
9286
  //#endregion
9287
9287
  //#region src/client-recovery.js
9288
9288
  async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
package/lib/index.js CHANGED
@@ -12,7 +12,7 @@ import { clientRequestSchema } from "@deepseek-ai/dsh-client-connection";
12
12
  import { lstat, mkdir, open, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
13
13
  import { constants, readFileSync, readdirSync, statSync } from "node:fs";
14
14
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
15
- import { pathToFileURL } from "node:url";
15
+ import { fileURLToPath, pathToFileURL } from "node:url";
16
16
  import { defineTool } from "@deepseek-ai/dsh-tools";
17
17
  import * as dshLlm from "@deepseek-ai/dsh-llm";
18
18
  import { createUserMessage } from "@deepseek-ai/dsh-llm";
@@ -2158,7 +2158,7 @@ function inheritedOriginalImageRef(session, assetId) {
2158
2158
  }
2159
2159
  //#endregion
2160
2160
  //#region src/version.js
2161
- const PACKAGE_VERSION = "3.0.0-beta.6";
2161
+ const PACKAGE_VERSION = "3.0.0-beta.7";
2162
2162
  const USER_AGENT = `dsh-codex-subscription/${PACKAGE_VERSION}`;
2163
2163
  //#endregion
2164
2164
  //#region src/codex-images.js
@@ -3969,7 +3969,7 @@ const emptyTurn = () => ({
3969
3969
  completedAt: null,
3970
3970
  durationMs: null
3971
3971
  });
3972
- const textOf = (content) => (content ?? []).map((block) => block?.type === "text" ? block.text : "").filter(Boolean).join("\n");
3972
+ const textOf$1 = (content) => (content ?? []).map((block) => block?.type === "text" ? block.text : "").filter(Boolean).join("\n");
3973
3973
  const inputText = (input) => (Array.isArray(input) ? input : []).map((part) => part?.type === "text" ? part.text : "").filter(Boolean).join("\n");
3974
3974
  /** Skills the phone picked, written the way DSH invokes a skill: a /name gesture ahead of the text. */
3975
3975
  const skillGestures = (input) => (Array.isArray(input) ? input : []).filter((part) => part?.type === "skill" && /^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(String(part.name))).map((part) => `/${part.name}`);
@@ -3996,7 +3996,7 @@ function parseArguments(value) {
3996
3996
  }
3997
3997
  }
3998
3998
  /** What the person typed: DSH appends its own system reminders (skills, context) to user messages. */
3999
- const visibleText = (content) => textOf(content).replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gu, "").trim();
3999
+ const visibleText = (content) => textOf$1(content).replace(/<system-reminder>[\s\S]*?<\/system-reminder>/gu, "").trim();
4000
4000
  /** Text of a user-role message the person wrote; DSH's own injected ones (runtime context, notices) carry another source kind. */
4001
4001
  const typedText = (message) => message.role === "user" && (message.source === void 0 || message.source?.kind === "user") ? visibleText(message.content) : "";
4002
4002
  const lines = (text) => String(text ?? "").split("\n");
@@ -4101,7 +4101,7 @@ function finishTool(item, message, imageUrls) {
4101
4101
  const failed = Boolean(message.isError);
4102
4102
  item.status = failed ? "failed" : "completed";
4103
4103
  if (item.type === "commandExecution") {
4104
- item.aggregatedOutput = textOf(message.content);
4104
+ item.aggregatedOutput = textOf$1(message.content);
4105
4105
  item.exitCode = failed ? 1 : 0;
4106
4106
  } else if (item.type === "imageGeneration") {
4107
4107
  const url = imageBlocks(message).map((block) => imageUrls.get(block.attachment.attachmentId)).find(Boolean);
@@ -4111,7 +4111,7 @@ function finishTool(item, message, imageUrls) {
4111
4111
  item.contentItems = toolOutput(message, imageUrls);
4112
4112
  item.success = !failed;
4113
4113
  } else if (item.type === "mcpToolCall") {
4114
- const text = textOf(message.content);
4114
+ const text = textOf$1(message.content);
4115
4115
  if (failed) item.error = { message: text.slice(0, 500) || "The connector tool failed" };
4116
4116
  else item.result = {
4117
4117
  content: [{
@@ -4125,7 +4125,7 @@ function finishTool(item, message, imageUrls) {
4125
4125
  const imageBlocks = (message) => (message.content ?? []).filter((block) => block?.type === "image" && block.attachment);
4126
4126
  const toolOutput = (message, imageUrls) => [{
4127
4127
  type: "inputText",
4128
- text: textOf(message.content)
4128
+ text: textOf$1(message.content)
4129
4129
  }, ...imageBlocks(message).map((block) => imageUrls.get(block.attachment.attachmentId)).filter(Boolean).map((imageUrl) => ({
4130
4130
  type: "inputImage",
4131
4131
  imageUrl
@@ -4242,7 +4242,7 @@ function thread({ id, title, preview, cwd, createdAt, updatedAt, running, turns
4242
4242
  */
4243
4243
  const PHONE_RETURN_MS = 12e4;
4244
4244
  const ADOPT_WITHIN_MS = 6e5;
4245
- function createDshRemoteControl({ controller, agents, permissions = () => void 0, workspaces = () => void 0, attachments = () => void 0, usage = () => void 0, usageProfile = () => void 0, projections = () => void 0, skills = () => void 0, llm = () => void 0, trace = () => {}, phoneReturnMs = PHONE_RETURN_MS, userAgent }) {
4245
+ function createDshRemoteControl({ controller, agents, permissions = () => void 0, workspaces = () => void 0, attachments = () => void 0, usage = () => void 0, usageProfile = () => void 0, projections = () => void 0, skills = () => void 0, commands = () => void 0, llm = () => void 0, trace = () => {}, phoneReturnMs = PHONE_RETURN_MS, userAgent }) {
4246
4246
  const subscribers = /* @__PURE__ */ new Map();
4247
4247
  const active = /* @__PURE__ */ new Map();
4248
4248
  const queues = /* @__PURE__ */ new Map();
@@ -5051,6 +5051,92 @@ function createDshRemoteControl({ controller, agents, permissions = () => void 0
5051
5051
  "turn/start": (params, { notify }) => titles.has(params?.threadId) ? titleTurn(params, notify) : startTurn(params, notify),
5052
5052
  "turn/interrupt": (params) => {
5053
5053
  service().cancel({ sessionId: params?.threadId });
5054
+ active.get(params?.threadId)?.abort?.abort();
5055
+ return {};
5056
+ },
5057
+ "thread/compact/start": async (params, { notify }) => {
5058
+ const id = params?.threadId;
5059
+ const registry = commands();
5060
+ const agent = await agentFor(id);
5061
+ if (typeof registry?.execute !== "function" || registry.find?.(agent, "compact") === void 0) throw new RpcError(-32601, "This DSH version cannot compact a conversation on request");
5062
+ if (active.has(id)) throw new RpcError(-32602, "A turn is already running in this conversation");
5063
+ subscribe(id, notify);
5064
+ const turn = emptyTurn();
5065
+ const abort = new AbortController();
5066
+ active.set(id, {
5067
+ turn,
5068
+ ended: void 0,
5069
+ abort
5070
+ });
5071
+ emit(id, "turn/started", {
5072
+ threadId: id,
5073
+ turn
5074
+ });
5075
+ emit(id, "thread/status/changed", {
5076
+ threadId: id,
5077
+ status: {
5078
+ type: "active",
5079
+ activeFlags: []
5080
+ }
5081
+ });
5082
+ (async () => {
5083
+ try {
5084
+ const result = (await registry.execute(agent, "/compact", [], abort.signal))?.result;
5085
+ if (result?.kind === "error") throw new Error(result.text || "Compaction failed");
5086
+ if (result?.sourceEventSeq !== void 0 && turn.items.length === 0) {
5087
+ const item = {
5088
+ type: "contextCompaction",
5089
+ id: randomUUID()
5090
+ };
5091
+ turn.items.push(item);
5092
+ emit(id, "item/started", {
5093
+ threadId: id,
5094
+ turnId: turn.id,
5095
+ item,
5096
+ startedAtMs: Date.now()
5097
+ });
5098
+ emit(id, "item/completed", {
5099
+ threadId: id,
5100
+ turnId: turn.id,
5101
+ item,
5102
+ completedAtMs: Date.now()
5103
+ });
5104
+ }
5105
+ turn.status = "completed";
5106
+ } catch (error) {
5107
+ turn.status = abort.signal.aborted ? "interrupted" : "failed";
5108
+ if (turn.status === "failed") {
5109
+ turn.error = {
5110
+ message: String(error?.message ?? "Compaction failed").slice(0, 500),
5111
+ codexErrorInfo: null,
5112
+ additionalDetails: null
5113
+ };
5114
+ emit(id, "error", {
5115
+ threadId: id,
5116
+ turnId: turn.id,
5117
+ willRetry: false,
5118
+ error: turn.error
5119
+ });
5120
+ }
5121
+ }
5122
+ turn.completedAt = seconds(Date.now());
5123
+ active.delete(id);
5124
+ compactions.delete(id);
5125
+ const usageNow = tokenUsage(agent.session);
5126
+ if (usageNow) emit(id, "thread/tokenUsage/updated", {
5127
+ threadId: id,
5128
+ turnId: turn.id,
5129
+ tokenUsage: usageNow
5130
+ });
5131
+ emit(id, "turn/completed", {
5132
+ threadId: id,
5133
+ turn
5134
+ });
5135
+ emit(id, "thread/status/changed", {
5136
+ threadId: id,
5137
+ status: { type: "idle" }
5138
+ });
5139
+ })();
5054
5140
  return {};
5055
5141
  },
5056
5142
  "turn/steer": async (params) => {
@@ -6431,6 +6517,62 @@ function redactRequest(value) {
6431
6517
  return walk(value, "", 0);
6432
6518
  }
6433
6519
  //#endregion
6520
+ //#region src/pi-ai-compat.js
6521
+ const versionOf = (specifier) => {
6522
+ try {
6523
+ let path = fileURLToPath(import.meta.resolve(specifier));
6524
+ for (let depth = 0; depth < 6; depth += 1) {
6525
+ path = path.replace(/[\\/][^\\/]+$/u, "");
6526
+ try {
6527
+ const manifest = JSON.parse(readFileSync(`${path}/package.json`, "utf8"));
6528
+ if (typeof manifest.version === "string") return manifest.version;
6529
+ } catch {}
6530
+ }
6531
+ } catch {}
6532
+ };
6533
+ let cached;
6534
+ /** Versions of the pi-ai library and of DSH's adapter this plugin runs against. */
6535
+ function runtimeVersions() {
6536
+ cached ??= {
6537
+ piAi: versionOf("@earendil-works/pi-ai"),
6538
+ adapter: versionOf("@deepseek-ai/dsh-llm-pi-ai")
6539
+ };
6540
+ return cached;
6541
+ }
6542
+ const atLeast087 = (version) => {
6543
+ const match = /^(\d+)\.(\d+)/u.exec(version ?? "");
6544
+ return match !== null && (Number(match[1]) > 0 || Number(match[2]) >= 87);
6545
+ };
6546
+ /** Whether the installed pi-ai understands system messages inside the transcript. */
6547
+ const understandsSystemMessages = (version) => atLeast087(version);
6548
+ const textOf = (content) => typeof content === "string" ? content : Array.isArray(content) ? content.filter((block) => block?.type === "text" && typeof block.text === "string").map((block) => block.text).join("\n") : "";
6549
+ /**
6550
+ * Fold the transcript's leading system messages back into `systemPrompt` and `tools`,
6551
+ * the form every pi-ai release accepts. Messages after the first non-system one stay.
6552
+ */
6553
+ function hoistLeadingSystem(context) {
6554
+ const messages = context?.messages;
6555
+ if (!Array.isArray(messages) || messages[0]?.role !== "system") return context;
6556
+ let end = 0;
6557
+ const texts = context.systemPrompt ? [context.systemPrompt] : [];
6558
+ const tools = [...Array.isArray(context.tools) ? context.tools : []];
6559
+ while (messages[end]?.role === "system") {
6560
+ const message = messages[end];
6561
+ const text = [textOf(message.content), ...Object.values(message.sections ?? {}).filter((value) => typeof value === "string")].filter(Boolean).join("\n\n");
6562
+ if (text !== "") texts.push(text);
6563
+ if (Array.isArray(message.toolsAdded)) tools.push(...message.toolsAdded);
6564
+ end += 1;
6565
+ }
6566
+ const { systemPrompt: _systemPrompt, tools: _tools, ...rest } = context;
6567
+ const systemPrompt = texts.join("\n\n");
6568
+ return {
6569
+ ...rest,
6570
+ ...systemPrompt === "" ? {} : { systemPrompt },
6571
+ ...tools.length === 0 ? {} : { tools },
6572
+ messages: messages.slice(end)
6573
+ };
6574
+ }
6575
+ //#endregion
6434
6576
  //#region src/transport-failure.js
6435
6577
  const TRANSIENT_WEBSOCKET_CLOSE = /^WebSocket closed (?:1000|1006|1011|1012|1013)(?:\s|$)/;
6436
6578
  const GATEWAY_REPLAY_BUFFER_EXCEEDED = /^\s*exceeded request buffer limit while retrying upstream\s*$/;
@@ -6724,7 +6866,8 @@ function openaiCodexSubscriptionProvider({ resolveSpeedMode = () => void 0, reso
6724
6866
  throw: (error) => iterator ? step("throw", error) : Promise.reject(error)
6725
6867
  };
6726
6868
  };
6727
- const routed = (method, model, context, options) => {
6869
+ const routed = (method, model, transcript, options) => {
6870
+ const context = understandsSystemMessages(runtimeVersions().piAi) ? transcript : hoistLeadingSystem(transcript);
6728
6871
  const route = reviewRoute(model, context);
6729
6872
  const source = {
6730
6873
  method,
@@ -7691,6 +7834,15 @@ function safeRequests(network) {
7691
7834
  }
7692
7835
  return result;
7693
7836
  }
7837
+ /** pi-ai and DSH's adapter come from the host; a profile can pair an adapter with an older library, so the report names both. */
7838
+ function libraryVersions() {
7839
+ const { piAi, adapter } = runtimeVersions();
7840
+ const valid = (value) => typeof value === "string" && /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]{1,32})?$/u.test(value);
7841
+ return {
7842
+ ...valid(piAi) ? { piAi } : {},
7843
+ ...valid(adapter) ? { adapter } : {}
7844
+ };
7845
+ }
7694
7846
  /** Build a support report that deliberately excludes OAuth and account metadata. */
7695
7847
  async function createSubscriptionDiagnostics({ auth, preferences, login = { phase: "idle" }, network, modelCatalog, connection, compaction, review, inspectionOptions, operations, runtimeManagement, storage, tools }) {
7696
7848
  const collected = {};
@@ -7869,7 +8021,8 @@ async function createSubscriptionDiagnostics({ auth, preferences, login = { phas
7869
8021
  node: process.version,
7870
8022
  platform: process.platform,
7871
8023
  arch: process.arch,
7872
- ...hostDshVersion() ? { dsh: hostDshVersion() } : {}
8024
+ ...hostDshVersion() ? { dsh: hostDshVersion() } : {},
8025
+ ...libraryVersions()
7873
8026
  },
7874
8027
  account,
7875
8028
  login,
@@ -10227,6 +10380,7 @@ function apply(ctx, config = {}) {
10227
10380
  usageProfile: () => usageProfileReader,
10228
10381
  projections: () => ctx.get?.("sessionProjections"),
10229
10382
  skills: () => ctx.get?.("skills"),
10383
+ commands: () => ctx.get?.("commands"),
10230
10384
  llm: () => ctx.llm,
10231
10385
  trace: (entry) => {
10232
10386
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-codex-subscription",
3
- "version": "3.0.0-beta.6",
3
+ "version": "3.0.0-beta.7",
4
4
  "description": "Use ChatGPT and Codex subscriptions in DeepSeek Harness with OAuth, quota, safe resets, web search, images, and Fast mode",
5
5
  "type": "module",
6
6
  "main": "./lib/index.js",