dsh-codex-subscription 3.0.0-beta.3 → 3.0.0-beta.4
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 +8 -2
- package/README.md +8 -2
- package/lib/client.js +109 -8
- package/lib/index.js +1031 -151
- package/package.json +2 -1
package/README.en.md
CHANGED
|
@@ -80,7 +80,7 @@ Subscription access, model controls, quota management, and image creation in one
|
|
|
80
80
|
|
|
81
81
|
| Feature | What you can do | Availability / setup |
|
|
82
82
|
| --- | --- | --- |
|
|
83
|
-
| **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.
|
|
83
|
+
| **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.4); off by default; Settings → Codex → Models & runtime → Phone remote control, pair by QR or manual code |
|
|
84
84
|
| **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 |
|
|
85
85
|
| **Subtask component management** | Install, disable, or uninstall the official Codex subtask component from settings | Managed by supported DSH hosts; no extra login |
|
|
86
86
|
| **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 |
|
|
@@ -162,9 +162,15 @@ Turn on **Settings → Codex → Models & runtime → Phone remote control** and
|
|
|
162
162
|
- switch model, reasoning level and permissions (workspace write / full access) from the phone;
|
|
163
163
|
- when DSH needs a tool approval or asks you a question, it pops up on the phone; if the phone dropped, it is asked again when you return, otherwise the computer handles it;
|
|
164
164
|
- replies appear on the phone as they are written;
|
|
165
|
+
- commands, file edits (with diffs), the task list and generated images use the phone's native views, and file-edit approvals show the diff;
|
|
166
|
+
- DSH workspaces appear as projects on the phone: start chats in them, fork a conversation, see usage limits and context usage;
|
|
167
|
+
- on the phone, @ searches files on the computer, skills can be picked, and the changes view works (read-only git);
|
|
168
|
+
- Settings lists the paired phones and lets you remove any of them;
|
|
169
|
+
- send photos from the phone (with or without text), and images DSH produces show up on the phone;
|
|
170
|
+
- the phone folder picker browses real folders on this computer, and a new chat can start in one of them;
|
|
165
171
|
- model errors are shown on the phone.
|
|
166
172
|
|
|
167
|
-
Off by default. The computer must stay on, online and running DSH. This uses a ChatGPT capability OpenAI has not opened to third parties and may change at any time, hence Beta. Currently available in the pre-release `dsh-codex-subscription@3.0.0-beta.
|
|
173
|
+
Off by default. The computer must stay on, online and running DSH. This uses a ChatGPT capability OpenAI has not opened to third parties and may change at any time, hence Beta. Currently available in the pre-release `dsh-codex-subscription@3.0.0-beta.4`.
|
|
168
174
|
|
|
169
175
|
<p align="center"><img src="docs/assets/remote-control-en.png" width="560" alt="Official desktop app settings: phone remote control connected, showing a pairing QR code and manual code"></p>
|
|
170
176
|
|
package/README.md
CHANGED
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
|
|
81
81
|
| 功能 | 你可以做什么 | 使用方式 / 条件 |
|
|
82
82
|
| --- | --- | --- |
|
|
83
|
-
| **手机远程控制** | 在 ChatGPT 手机 App 的 Codex 里直接指挥电脑上的 DSH:新建、续聊、归档对话,切换模型和权限,回答审批和提问,DeepSeek 等任意 DSH 模型都能用 | Beta(3.0.0-beta.
|
|
83
|
+
| **手机远程控制** | 在 ChatGPT 手机 App 的 Codex 里直接指挥电脑上的 DSH:新建、续聊、归档对话,切换模型和权限,回答审批和提问,DeepSeek 等任意 DSH 模型都能用 | Beta(3.0.0-beta.4);默认关闭,设置 → Codex 订阅 → 模型与运行 → 手机远程控制,扫码或输入手动码配对 |
|
|
84
84
|
| **Codex 独立子任务** | 复用订阅登录和工作区权限执行独立任务,可指定允许的模型与推理档位 | Beta;默认 DSH,Codex 需安装可选组件并配置模型选择 |
|
|
85
85
|
| **子任务组件管理** | 在设置里安装、停用或卸载官方 Codex 子任务组件 | 由支持的 DSH 宿主管理,无需额外登录 |
|
|
86
86
|
| **云端上下文压缩** | 使用 Codex 云端压缩继续长对话,同时保留 DSH 历史与原生压缩 | Beta / 实验性;默认关闭,开启的请求使用 SSE |
|
|
@@ -162,9 +162,15 @@ dsh --profile headless "只回复:ok"
|
|
|
162
162
|
- 在手机上切换模型、推理档位和权限(工作区内修改 / 完全访问);
|
|
163
163
|
- DSH 需要批准工具调用或向你提问时,手机会直接弹出;手机掉线会在重新打开时再弹一次,否则交回电脑处理;
|
|
164
164
|
- 回复边生成边显示在手机上;
|
|
165
|
+
- 命令、文件修改(带差异)、任务清单、生成的图片在手机上用原生视图显示,文件修改的审批也能看到差异;
|
|
166
|
+
- DSH 工作区对应手机上的项目,可在项目里新建对话,也能从手机分叉对话、看到用量额度和上下文用量;
|
|
167
|
+
- 手机上可以用 @ 搜索电脑上的文件、用 / 选择 DSH 技能,也能在手机上查看改动(只读 git);
|
|
168
|
+
- 设置里可以看到已配对的手机,并能随时移除;
|
|
169
|
+
- 在手机上发图片(可配文字),DSH 生成的图片也会显示在手机上;
|
|
170
|
+
- 手机的文件夹选择器能浏览这台电脑上真实的文件夹,新对话可以直接在其中开始;
|
|
165
171
|
- 模型报错会显示在手机上。
|
|
166
172
|
|
|
167
|
-
默认关闭。电脑需要保持开机、联网并打开 DSH。这是 OpenAI 尚未向第三方开放的 ChatGPT 能力,随时可能变化,所以标为 Beta。目前在预发布版中提供:`dsh-codex-subscription@3.0.0-beta.
|
|
173
|
+
默认关闭。电脑需要保持开机、联网并打开 DSH。这是 OpenAI 尚未向第三方开放的 ChatGPT 能力,随时可能变化,所以标为 Beta。目前在预发布版中提供:`dsh-codex-subscription@3.0.0-beta.4`。
|
|
168
174
|
|
|
169
175
|
<p align="center"><img src="docs/assets/remote-control.png" width="560" alt="官方桌面版设置页:手机远程控制已连接,显示配对二维码和手动码"></p>
|
|
170
176
|
|
package/lib/client.js
CHANGED
|
@@ -1091,6 +1091,8 @@ window.__ModuleLoader__.load({
|
|
|
1091
1091
|
"remote/enable",
|
|
1092
1092
|
"remote/disable",
|
|
1093
1093
|
"remote/pair",
|
|
1094
|
+
"remote/clients",
|
|
1095
|
+
"remote/revoke",
|
|
1094
1096
|
"reset-credit/inspect",
|
|
1095
1097
|
"reset-credit/prepare",
|
|
1096
1098
|
"reset-credit/consume",
|
|
@@ -5688,6 +5690,11 @@ window.__ModuleLoader__.load({
|
|
|
5688
5690
|
remoteConnecting: "正在连接",
|
|
5689
5691
|
remoteError: "连接失败,会自动重试",
|
|
5690
5692
|
remoteStopped: "已停止",
|
|
5693
|
+
remotePaired: "已配对的手机",
|
|
5694
|
+
remoteNoPhones: "还没有手机配对。",
|
|
5695
|
+
remoteRevoke: "移除",
|
|
5696
|
+
remoteLastSeen: "最近 {date}",
|
|
5697
|
+
remotePhoneUnknown: "未命名手机",
|
|
5691
5698
|
remotePhones: "{count} 个手机连接",
|
|
5692
5699
|
remotePair: "显示配对码",
|
|
5693
5700
|
remotePairAgain: "重新生成配对码",
|
|
@@ -5697,7 +5704,7 @@ window.__ModuleLoader__.load({
|
|
|
5697
5704
|
remoteQrLabel: "配对二维码",
|
|
5698
5705
|
remoteExpires: "约 {minutes} 分钟内有效",
|
|
5699
5706
|
remoteExpired: "配对码已过期,请重新生成。",
|
|
5700
|
-
remoteLimits: "手机上可以查看和继续 DSH
|
|
5707
|
+
remoteLimits: "手机上可以查看和继续 DSH 里的对话、新建对话、发图片、切换模型、选择权限并审批,也能查看改动。手机不能在电脑上执行其他命令。",
|
|
5701
5708
|
reviewModelTitle: "Auto review 审查模型",
|
|
5702
5709
|
reviewModel_session: "当前模型",
|
|
5703
5710
|
reviewModel_official: "Codex 官方审查模型",
|
|
@@ -6293,6 +6300,11 @@ window.__ModuleLoader__.load({
|
|
|
6293
6300
|
remoteConnecting: "Connecting",
|
|
6294
6301
|
remoteError: "Connection failed, retrying",
|
|
6295
6302
|
remoteStopped: "Stopped",
|
|
6303
|
+
remotePaired: "Paired phones",
|
|
6304
|
+
remoteNoPhones: "No phone is paired yet.",
|
|
6305
|
+
remoteRevoke: "Remove",
|
|
6306
|
+
remoteLastSeen: "last seen {date}",
|
|
6307
|
+
remotePhoneUnknown: "Unnamed phone",
|
|
6296
6308
|
remotePhones: "{count} phone(s) connected",
|
|
6297
6309
|
remotePair: "Show pairing code",
|
|
6298
6310
|
remotePairAgain: "New pairing code",
|
|
@@ -6302,7 +6314,7 @@ window.__ModuleLoader__.load({
|
|
|
6302
6314
|
remoteQrLabel: "Pairing QR code",
|
|
6303
6315
|
remoteExpires: "Valid for about {minutes} minute(s)",
|
|
6304
6316
|
remoteExpired: "The pairing code expired. Create a new one.",
|
|
6305
|
-
remoteLimits: "From the phone you can follow and continue DSH conversations, start new ones, switch models, choose permissions and
|
|
6317
|
+
remoteLimits: "From the phone you can follow and continue DSH conversations, start new ones, send photos, switch models, choose permissions, approve actions and view changes. The phone cannot run other commands on the computer.",
|
|
6306
6318
|
reviewModelTitle: "Auto review model",
|
|
6307
6319
|
reviewModel_session: "Current model",
|
|
6308
6320
|
reviewModel_official: "Codex reviewer",
|
|
@@ -8986,7 +8998,6 @@ window.__ModuleLoader__.load({
|
|
|
8986
8998
|
const NS = "settings.codexSubscription";
|
|
8987
8999
|
const SUPPORT_ISSUE_URL = "https://github.com/WSL043/dsh-codex-subscription/issues/new?template=install-problem.yml";
|
|
8988
9000
|
const QUICK_QUOTA_REFRESH_EVENT = "dsh-codex-subscription:refresh-quick-quota";
|
|
8989
|
-
const QUICK_QUOTA_REFRESH_MS = 6e4;
|
|
8990
9001
|
const accountStatusErrorText = (error, t) => {
|
|
8991
9002
|
const key = {
|
|
8992
9003
|
"credential-unavailable": "accountCredentialUnavailable",
|
|
@@ -9082,7 +9093,7 @@ window.__ModuleLoader__.load({
|
|
|
9082
9093
|
}
|
|
9083
9094
|
//#endregion
|
|
9084
9095
|
//#region src/version.js
|
|
9085
|
-
const PACKAGE_VERSION = "3.0.0-beta.
|
|
9096
|
+
const PACKAGE_VERSION = "3.0.0-beta.4";
|
|
9086
9097
|
//#endregion
|
|
9087
9098
|
//#region src/client-recovery.js
|
|
9088
9099
|
async function recoveryCall(rpc, endpoint, payload = {}, timeoutMs = 1e4) {
|
|
@@ -9110,6 +9121,21 @@ window.__ModuleLoader__.load({
|
|
|
9110
9121
|
};
|
|
9111
9122
|
}
|
|
9112
9123
|
//#endregion
|
|
9124
|
+
//#region src/quota-cadence.js
|
|
9125
|
+
/** Milliseconds until the next quota read, from the highest used percentage of the windows that matter. */
|
|
9126
|
+
function quotaRefreshMs(percent) {
|
|
9127
|
+
if (!Number.isFinite(percent)) return 6e4;
|
|
9128
|
+
if (percent >= 99) return 5e3;
|
|
9129
|
+
if (percent >= 90) return 15e3;
|
|
9130
|
+
if (percent >= 75) return 3e4;
|
|
9131
|
+
return 6e4;
|
|
9132
|
+
}
|
|
9133
|
+
/** Highest used percentage among windows (objects with usedPercent), or undefined when there are none. */
|
|
9134
|
+
function highestUsed(windows) {
|
|
9135
|
+
const values = (Array.isArray(windows) ? windows : []).map((window) => window?.usedPercent).filter(Number.isFinite);
|
|
9136
|
+
return values.length === 0 ? void 0 : Math.max(...values);
|
|
9137
|
+
}
|
|
9138
|
+
//#endregion
|
|
9113
9139
|
//#region src/client-quota.jsx
|
|
9114
9140
|
function useQuickQuota(rpc, enabled, model) {
|
|
9115
9141
|
const [quota, setQuota] = (0, react.useState)();
|
|
@@ -9121,6 +9147,8 @@ window.__ModuleLoader__.load({
|
|
|
9121
9147
|
setQuota(void 0);
|
|
9122
9148
|
let live = true;
|
|
9123
9149
|
let loading = false;
|
|
9150
|
+
let timer;
|
|
9151
|
+
let latest;
|
|
9124
9152
|
const load = async () => {
|
|
9125
9153
|
if (loading) return;
|
|
9126
9154
|
loading = true;
|
|
@@ -9132,25 +9160,31 @@ window.__ModuleLoader__.load({
|
|
|
9132
9160
|
return;
|
|
9133
9161
|
}
|
|
9134
9162
|
const usage = await recoveryCall(rpc, "usage", { force: false });
|
|
9135
|
-
|
|
9163
|
+
const windows = selectModelQuotaWindows(usage, model)?.map((window) => ({
|
|
9136
9164
|
...window,
|
|
9137
9165
|
fetchedAt: usage.fetchedAt
|
|
9138
|
-
}))
|
|
9166
|
+
}));
|
|
9167
|
+
latest = windows;
|
|
9168
|
+
if (live) setQuota(windows);
|
|
9139
9169
|
} catch {
|
|
9170
|
+
latest = void 0;
|
|
9140
9171
|
if (live) setQuota(void 0);
|
|
9141
9172
|
} finally {
|
|
9142
9173
|
loading = false;
|
|
9174
|
+
if (live) {
|
|
9175
|
+
window.clearTimeout(timer);
|
|
9176
|
+
timer = window.setTimeout(refresh, quotaRefreshMs(highestUsed(latest)));
|
|
9177
|
+
}
|
|
9143
9178
|
}
|
|
9144
9179
|
};
|
|
9145
9180
|
const refresh = () => {
|
|
9146
9181
|
load();
|
|
9147
9182
|
};
|
|
9148
9183
|
load();
|
|
9149
|
-
const timer = window.setInterval(refresh, QUICK_QUOTA_REFRESH_MS);
|
|
9150
9184
|
window.addEventListener(QUICK_QUOTA_REFRESH_EVENT, refresh);
|
|
9151
9185
|
return () => {
|
|
9152
9186
|
live = false;
|
|
9153
|
-
window.
|
|
9187
|
+
window.clearTimeout(timer);
|
|
9154
9188
|
window.removeEventListener(QUICK_QUOTA_REFRESH_EVENT, refresh);
|
|
9155
9189
|
};
|
|
9156
9190
|
}, [
|
|
@@ -12422,6 +12456,8 @@ window.__ModuleLoader__.load({
|
|
|
12422
12456
|
const [busy, setBusy] = (0, react.useState)(false);
|
|
12423
12457
|
const [failed, setFailed] = (0, react.useState)(false);
|
|
12424
12458
|
const [now, setNow] = (0, react.useState)(Date.now());
|
|
12459
|
+
const [clients, setClients] = (0, react.useState)();
|
|
12460
|
+
const [removing, setRemoving] = (0, react.useState)();
|
|
12425
12461
|
const alive = (0, react.useRef)(false);
|
|
12426
12462
|
const on = snapshot.remoteControl === "on";
|
|
12427
12463
|
const read = async () => {
|
|
@@ -12430,12 +12466,49 @@ window.__ModuleLoader__.load({
|
|
|
12430
12466
|
if (alive.current && result.ok) setState(result.value);
|
|
12431
12467
|
} catch {}
|
|
12432
12468
|
};
|
|
12469
|
+
const readClients = async () => {
|
|
12470
|
+
try {
|
|
12471
|
+
const result = await rpc.call(CHANNEL, "remote/clients", {});
|
|
12472
|
+
if (alive.current && result.ok) setClients(result.value);
|
|
12473
|
+
} catch {}
|
|
12474
|
+
};
|
|
12433
12475
|
(0, react.useEffect)(() => {
|
|
12434
12476
|
alive.current = true;
|
|
12435
12477
|
return () => {
|
|
12436
12478
|
alive.current = false;
|
|
12437
12479
|
};
|
|
12438
12480
|
}, []);
|
|
12481
|
+
(0, react.useEffect)(() => {
|
|
12482
|
+
if (!on) {
|
|
12483
|
+
setClients(void 0);
|
|
12484
|
+
return;
|
|
12485
|
+
}
|
|
12486
|
+
let stopped = false, timer;
|
|
12487
|
+
const poll = async () => {
|
|
12488
|
+
await readClients();
|
|
12489
|
+
if (!stopped) timer = setTimeout(poll, 2e4);
|
|
12490
|
+
};
|
|
12491
|
+
poll();
|
|
12492
|
+
return () => {
|
|
12493
|
+
stopped = true;
|
|
12494
|
+
clearTimeout(timer);
|
|
12495
|
+
};
|
|
12496
|
+
}, [on, rpc]);
|
|
12497
|
+
const remove = async (client) => {
|
|
12498
|
+
if (removing) return;
|
|
12499
|
+
setRemoving(client.clientId);
|
|
12500
|
+
setFailed(false);
|
|
12501
|
+
try {
|
|
12502
|
+
const result = await rpc.call(CHANNEL, "remote/revoke", { clientId: client.clientId });
|
|
12503
|
+
if (!alive.current) return;
|
|
12504
|
+
if (result.ok) setClients(result.value);
|
|
12505
|
+
else setFailed(true);
|
|
12506
|
+
} catch {
|
|
12507
|
+
if (alive.current) setFailed(true);
|
|
12508
|
+
} finally {
|
|
12509
|
+
if (alive.current) setRemoving(void 0);
|
|
12510
|
+
}
|
|
12511
|
+
};
|
|
12439
12512
|
(0, react.useEffect)(() => {
|
|
12440
12513
|
if (!on) {
|
|
12441
12514
|
setState(void 0);
|
|
@@ -12567,6 +12640,34 @@ window.__ModuleLoader__.load({
|
|
|
12567
12640
|
className: "codexSubscriptionHelp",
|
|
12568
12641
|
children: t("remoteExpired")
|
|
12569
12642
|
}) : null,
|
|
12643
|
+
clients ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
12644
|
+
className: "codexSubscriptionRemoteClients",
|
|
12645
|
+
role: "list",
|
|
12646
|
+
"aria-label": t("remotePaired"),
|
|
12647
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
12648
|
+
className: "codexSubscriptionPreferenceLabel",
|
|
12649
|
+
children: t("remotePaired")
|
|
12650
|
+
}), clients.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
12651
|
+
className: "codexSubscriptionHelp",
|
|
12652
|
+
children: t("remoteNoPhones")
|
|
12653
|
+
}) : clients.map((client) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
12654
|
+
role: "listitem",
|
|
12655
|
+
className: "codexSubscriptionPreference",
|
|
12656
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: [
|
|
12657
|
+
client.name ?? t("remotePhoneUnknown"),
|
|
12658
|
+
client.platform,
|
|
12659
|
+
client.lastSeenAt ? t("remoteLastSeen").replace("{date}", new Date(client.lastSeenAt).toLocaleString()) : null
|
|
12660
|
+
].filter(Boolean).join(" · ") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Button, {
|
|
12661
|
+
type: "button",
|
|
12662
|
+
variant: "outline",
|
|
12663
|
+
disabled: Boolean(removing),
|
|
12664
|
+
onClick: () => {
|
|
12665
|
+
remove(client);
|
|
12666
|
+
},
|
|
12667
|
+
children: t("remoteRevoke")
|
|
12668
|
+
})]
|
|
12669
|
+
}, client.clientId))]
|
|
12670
|
+
}) : null,
|
|
12570
12671
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
12571
12672
|
className: "codexSubscriptionHelp",
|
|
12572
12673
|
children: t("remoteLimits")
|