@michengai/dsh-codex-pet 0.1.13 → 0.1.15

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
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## [0.1.15] - 2026-10-07
6
+
7
+ - Official Desktop can update this plugin in place. It no longer targets the web profile by mistake.
8
+ - A new version is marked with a warning color. Version numbers keep their normal color.
9
+ - After updating, fully quit and reopen DSH Desktop.
10
+
5
11
  ## [0.1.13] - 2026-10-06
6
12
 
7
13
  - The installed-plugin list shows “Codex Pet” and a localized description instead of the package name. Titles and descriptions come from `meta.title` and `meta.description` in `locale/zh.json` and `locale/en.json`.
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## 未发布
4
4
 
5
+ ## [0.1.15] - 2026-10-07
6
+
7
+ - 官方桌面版可以在线更新这个插件,不再误装到 web profile。
8
+ - 发现新版本时,提示文字会标成提醒色;版本号颜色不变。
9
+ - 更新完成后需要完全退出并重新打开 DSH Desktop。
10
+
5
11
  ## [0.1.13] - 2026-10-06
6
12
 
7
13
  - 已安装插件列表显示「Codex Pet」和中文简介,不再只用包名。显示名来自 `locale/zh.json` 和 `locale/en.json` 的 `meta.title`、`meta.description`。
package/lib/client.js CHANGED
@@ -518,7 +518,7 @@ var UPDATE_HEADER = "x-michengai-plugin-update";
518
518
  var STYLE_ID = "michengai-plugin-update-ui";
519
519
  var CSS = `
520
520
  .mpi-version{margin-left:8px;color:var(--dsw-alias-label-tertiary,#9da1aa);font-family:inherit;font-size:12px;font-weight:500;line-height:18px;letter-spacing:0;white-space:nowrap;vertical-align:baseline}.mpi-check{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:28px;padding:0 8px;border:1px solid var(--dsw-alias-border-l2,#4b4d52);border-radius:7px;background:transparent;color:var(--dsw-alias-label-secondary,#b8bbc2);font:inherit;font-size:12px;font-weight:500;line-height:18px;white-space:nowrap;cursor:pointer}.mpi-check:hover{background:var(--dsw-alias-interactive-bg-hover,#3a3b3f);color:var(--dsw-alias-label-primary,#fff)}.mpi-check:focus-visible,.mpi-dialog .mpi-action:focus-visible,.mpi-dialog .mpi-dialog-close:focus-visible{outline:2px solid var(--dsw-alias-state-business-primary,#4f8cff);outline-offset:2px}.mpi-icon{display:inline-flex;flex:0 0 auto;width:16px;height:16px;align-items:center;justify-content:center;pointer-events:none}.mpi-icon svg{display:block;width:16px;height:16px}
521
- .mpi-overlay{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.62)}.mpi-dialog{position:relative;box-sizing:border-box;width:min(680px,100%);max-height:calc(100vh - 48px);overflow:auto;border:1px solid var(--dsw-alias-border-l2,#4b4d52);border-radius:14px;padding:22px;background:var(--dsw-alias-bg-layer-2,var(--dsw-specific-menu,#202124));color:var(--dsw-alias-label-primary,#fff);box-shadow:var(--dsw-shadow-lv3,0 16px 48px rgba(0,0,0,.24));font-family:inherit}.mpi-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.mpi-dialog h2{margin:0;font-size:18px;line-height:26px}.mpi-dialog .mpi-dialog-close{display:inline-flex;flex:0 0 28px;width:28px;height:28px;align-items:center;justify-content:center;padding:0;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary,#b8bbc2);cursor:pointer}.mpi-dialog .mpi-dialog-close:hover{background:var(--dsw-alias-interactive-bg-hover,#3a3b3f);color:var(--dsw-alias-label-primary,#fff)}.mpi-intro{margin:8px 0 18px;color:var(--dsw-alias-label-secondary,#c2c4ca);font-size:13px;line-height:20px}.mpi-meta{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:8px 18px;margin:0 0 16px;font-size:12px;line-height:18px}.mpi-meta dt{color:var(--dsw-alias-label-secondary,#c2c4ca)}.mpi-meta dd{margin:0;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.mpi-status{margin:0 0 18px;border-radius:7px;padding:12px 14px;background:var(--dsw-alias-bg-layer-3,var(--dsw-specific-menu-item-hover,#252527));font-size:13px;font-weight:600;line-height:20px}.mpi-status[data-kind=error]{color:var(--dsw-alias-state-error-primary,#ff6464)}.mpi-status[data-kind=success]{color:var(--dsw-alias-state-success-primary,#36d67a)}.mpi-manual{border-top:1px solid var(--dsw-alias-border-l2,#4b4d52);padding-top:16px}.mpi-manual h3{margin:0 0 6px;font-size:14px;line-height:20px}.mpi-manual p{margin:0 0 10px;color:var(--dsw-alias-label-secondary,#c2c4ca);font-size:12px;line-height:18px}.mpi-command{display:flex;align-items:center;gap:8px;border:1px solid var(--dsw-alias-border-l2,#4b4d52);border-radius:7px;padding:10px;background:var(--dsw-alias-bg-layer-3,var(--dsw-specific-menu-item-hover,#252527))}.mpi-command code{min-width:0;flex:1;overflow:auto;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;line-height:18px;white-space:nowrap}.mpi-actions{display:flex;align-items:center;justify-content:flex-end;gap:12px;margin-top:18px}.mpi-actions-group{display:flex;gap:8px}.mpi-dialog .mpi-action{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;border:1px solid var(--dsw-alias-border-l2,#4b4d52);border-radius:7px;padding:6px 10px;background:transparent;color:inherit;font:inherit;font-size:12px;cursor:pointer}.mpi-dialog .mpi-action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#414247)}.mpi-dialog .mpi-action:disabled{cursor:not-allowed;opacity:.55}.mpi-dialog .mpi-primary{border-color:var(--dsw-alias-state-business-primary,#4f8cff);background:var(--dsw-alias-state-business-primary,#4f8cff);color:#fff}.mpi-progress{height:4px;margin-top:10px;overflow:hidden;border-radius:99px;background:var(--dsw-alias-border-l2,#4b4d52)}.mpi-progress::after{display:block;width:32%;height:100%;background:var(--dsw-alias-state-business-primary,#4f8cff);content:'';animation:mpi-wave 1.15s ease-in-out infinite}@keyframes mpi-wave{from{transform:translateX(-110%)}to{transform:translateX(330%)}}@media(max-width:560px){.mpi-overlay{padding:10px}.mpi-dialog{max-height:calc(100vh - 20px);padding:16px}.mpi-actions{align-items:stretch}.mpi-actions-group{justify-content:flex-end;flex-wrap:wrap}.mpi-meta{grid-template-columns:1fr;gap:2px}.mpi-meta dd{margin-bottom:6px}}
521
+ .mpi-overlay{position:fixed;inset:0;z-index:2147483647;display:flex;align-items:center;justify-content:center;padding:24px;background:rgba(0,0,0,.62)}.mpi-dialog{position:relative;box-sizing:border-box;width:min(680px,100%);max-height:calc(100vh - 48px);overflow:auto;border:1px solid var(--dsw-alias-border-l2,#4b4d52);border-radius:14px;padding:22px;background:var(--dsw-alias-bg-layer-2,var(--dsw-specific-menu,#202124));color:var(--dsw-alias-label-primary,#fff);box-shadow:var(--dsw-shadow-lv3,0 16px 48px rgba(0,0,0,.24));font-family:inherit}.mpi-head{display:flex;align-items:center;justify-content:space-between;gap:12px}.mpi-dialog h2{margin:0;font-size:18px;line-height:26px}.mpi-dialog .mpi-dialog-close{display:inline-flex;flex:0 0 28px;width:28px;height:28px;align-items:center;justify-content:center;padding:0;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary,#b8bbc2);cursor:pointer}.mpi-dialog .mpi-dialog-close:hover{background:var(--dsw-alias-interactive-bg-hover,#3a3b3f);color:var(--dsw-alias-label-primary,#fff)}.mpi-intro{margin:8px 0 18px;color:var(--dsw-alias-label-secondary,#c2c4ca);font-size:13px;line-height:20px}.mpi-meta{display:grid;grid-template-columns:max-content minmax(0,1fr);gap:8px 18px;margin:0 0 16px;font-size:12px;line-height:18px}.mpi-meta dt{color:var(--dsw-alias-label-secondary,#c2c4ca)}.mpi-meta dd{margin:0;font-family:ui-monospace,SFMono-Regular,Consolas,monospace}.mpi-status{margin:0 0 18px;border-radius:7px;padding:12px 14px;background:var(--dsw-alias-bg-layer-3,var(--dsw-specific-menu-item-hover,#252527));font-size:13px;font-weight:600;line-height:20px}.mpi-status[data-kind=error]{color:var(--dsw-alias-state-error-primary,#ff6464)}.mpi-status[data-kind=success]{color:var(--dsw-alias-state-success-primary,#36d67a)}.mpi-status[data-kind=update]{color:#e8b15a !important}.mpi-manual{border-top:1px solid var(--dsw-alias-border-l2,#4b4d52);padding-top:16px}.mpi-manual h3{margin:0 0 6px;font-size:14px;line-height:20px}.mpi-manual p{margin:0 0 10px;color:var(--dsw-alias-label-secondary,#c2c4ca);font-size:12px;line-height:18px}.mpi-command{display:flex;align-items:center;gap:8px;border:1px solid var(--dsw-alias-border-l2,#4b4d52);border-radius:7px;padding:10px;background:var(--dsw-alias-bg-layer-3,var(--dsw-specific-menu-item-hover,#252527))}.mpi-command code{min-width:0;flex:1;overflow:auto;font-family:ui-monospace,SFMono-Regular,Consolas,monospace;font-size:12px;line-height:18px;white-space:nowrap}.mpi-actions{display:flex;align-items:center;justify-content:flex-end;gap:12px;margin-top:18px}.mpi-actions-group{display:flex;gap:8px}.mpi-dialog .mpi-action{display:inline-flex;align-items:center;justify-content:center;gap:6px;min-height:32px;border:1px solid var(--dsw-alias-border-l2,#4b4d52);border-radius:7px;padding:6px 10px;background:transparent;color:inherit;font:inherit;font-size:12px;cursor:pointer}.mpi-dialog .mpi-action:hover:not(:disabled){background:var(--dsw-alias-interactive-bg-hover,#414247)}.mpi-dialog .mpi-action:disabled{cursor:not-allowed;opacity:.55}.mpi-dialog .mpi-primary{border-color:var(--dsw-alias-state-business-primary,#4f8cff);background:var(--dsw-alias-state-business-primary,#4f8cff);color:#fff}.mpi-progress{height:4px;margin-top:10px;overflow:hidden;border-radius:99px;background:var(--dsw-alias-border-l2,#4b4d52)}.mpi-progress::after{display:block;width:32%;height:100%;background:var(--dsw-alias-state-business-primary,#4f8cff);content:'';animation:mpi-wave 1.15s ease-in-out infinite}@keyframes mpi-wave{from{transform:translateX(-110%)}to{transform:translateX(330%)}}@media(max-width:560px){.mpi-overlay{padding:10px}.mpi-dialog{max-height:calc(100vh - 20px);padding:16px}.mpi-actions{align-items:stretch}.mpi-actions-group{justify-content:flex-end;flex-wrap:wrap}.mpi-meta{grid-template-columns:1fr;gap:2px}.mpi-meta dd{margin-bottom:6px}}
522
522
  `;
523
523
  var ZH = {
524
524
  unpublished: "\u5C1A\u672A\u53D1\u5E03\u5230 npm\uFF0C\u53D1\u5E03\u540E\u5373\u53EF\u68C0\u67E5\u5E76\u5B89\u88C5\u66F4\u65B0\u3002",
@@ -541,8 +541,10 @@ var ZH = {
541
541
  unknown: "\u672A\u77E5",
542
542
  manual: "\u624B\u5DE5\u66F4\u65B0",
543
543
  manualHint: "\u81EA\u52A8\u66F4\u65B0\u5931\u8D25\u65F6\uFF0C\u53EF\u5728\u5F53\u524D DSH \u7EC8\u7AEF\u6267\u884C\u4EE5\u4E0B\u547D\u4EE4\uFF0C\u5B8C\u6210\u540E\u91CD\u542F DSH Web\u3002",
544
+ manualHintDesktop: "\u81EA\u52A8\u66F4\u65B0\u5931\u8D25\u65F6\uFF0C\u8BF7\u5148\u5B8C\u5168\u9000\u51FA DSH Desktop\uFF0C\u518D\u7528 Desktop \u81EA\u5E26\u7684 dsh \u6267\u884C\u4EE5\u4E0B\u547D\u4EE4\u3002\u666E\u901A dsh \u4F1A\u62D2\u7EDD desktop profile\u3002",
544
545
  intro: "\u4EC5\u68C0\u67E5\u5E76\u66F4\u65B0\u5F53\u524D\u63D2\u4EF6\uFF0C\u4E0D\u4F1A\u8054\u52A8\u5B89\u88C5\u5176\u4ED6\u63D2\u4EF6\u3002",
545
546
  restart: "\u66F4\u65B0\u5B8C\u6210\uFF0C\u8BF7\u91CD\u542F DSH Web\u3002",
547
+ restartDesktop: "\u66F4\u65B0\u5B8C\u6210\uFF0C\u8BF7\u5B8C\u5168\u9000\u51FA\u5E76\u91CD\u65B0\u6253\u5F00 DSH Desktop\u3002",
546
548
  restarting: "\u66F4\u65B0\u5B8C\u6210\uFF0C\u6B63\u5728\u91CD\u65B0\u52A0\u8F7D DSH\u2026",
547
549
  unavailable: "\u5F53\u524D\u73AF\u5883\u4E0D\u652F\u6301\u81EA\u52A8\u66F4\u65B0\uFF0C\u8BF7\u4F7F\u7528\u624B\u5DE5\u66F4\u65B0\u547D\u4EE4\u3002"
548
550
  };
@@ -567,8 +569,10 @@ var EN = {
567
569
  unknown: "Unknown",
568
570
  manual: "Manual update",
569
571
  manualHint: "If automatic update fails, run this command in the current DSH terminal, then restart DSH Web.",
572
+ manualHintDesktop: "If automatic update fails, fully quit DSH Desktop, then run this command with Desktop's own dsh. A regular dsh rejects the desktop profile.",
570
573
  intro: "Only this plugin is checked and updated. Other plugins are not changed.",
571
574
  restart: "Update complete. Restart DSH Web.",
575
+ restartDesktop: "Update complete. Fully quit and reopen DSH Desktop.",
572
576
  restarting: "Update complete. Reloading DSH\u2026",
573
577
  unavailable: "Automatic update is unavailable. Use the manual command."
574
578
  };
@@ -684,7 +688,7 @@ function observePluginUpdate(options) {
684
688
  terms[1].textContent = text.latestLabel;
685
689
  terms[2].textContent = text.profile;
686
690
  dialog.querySelector(".mpi-manual h3").textContent = text.manual;
687
- dialog.querySelector(".mpi-manual p").textContent = text.manualHint;
691
+ dialog.querySelector(".mpi-manual p").textContent = payload?.profileName === "desktop" ? text.manualHintDesktop : text.manualHint;
688
692
  const status = dialog.querySelector(".mpi-status");
689
693
  const progress = dialog.querySelector(".mpi-progress");
690
694
  const command = dialog.querySelector(".mpi-command code");
@@ -709,6 +713,7 @@ function observePluginUpdate(options) {
709
713
  const setMessage = (message, kind = "") => {
710
714
  status.textContent = message;
711
715
  status.dataset.kind = kind;
716
+ status.style.color = kind === "update" ? "#e8b15a" : "";
712
717
  };
713
718
  const setBusy = (value) => {
714
719
  busy = value;
@@ -721,6 +726,9 @@ function observePluginUpdate(options) {
721
726
  dialog.querySelector("[data-role=current]").textContent = payload === void 0 ? text.unknown : `v${payload.currentVersion}`;
722
727
  dialog.querySelector("[data-role=latest]").textContent = payload?.latestVersion === void 0 ? text.unknown : `v${payload.latestVersion}`;
723
728
  dialog.querySelector("[data-role=profile]").textContent = payload?.profileName ?? text.unknown;
729
+ const hint = dialog.querySelector(".mpi-manual p");
730
+ if (hint !== null)
731
+ hint.textContent = payload?.profileName === "desktop" ? text.manualHintDesktop : text.manualHint;
724
732
  command.textContent = manualPluginUpdateCommand(
725
733
  payload?.profileName ?? "",
726
734
  options.packageName,
@@ -731,10 +739,10 @@ function observePluginUpdate(options) {
731
739
  else if (payload.notPublished) setMessage(text.unpublished);
732
740
  else if (payload.latestCheckFailed) setMessage(text.failed, "error");
733
741
  else if (payload.updateAvailable)
734
- setMessage(`${text.found}: v${payload.latestVersion ?? text.unknown}`);
742
+ setMessage(`${text.found}: v${payload.latestVersion ?? text.unknown}`, "update");
735
743
  else setMessage(text.latest, "success");
736
744
  if (payload !== void 0 && !payload.canAutoUpdate && payload.updateAvailable)
737
- setMessage(text.unavailable);
745
+ setMessage(text.unavailable, "update");
738
746
  };
739
747
  const checkNow = async () => {
740
748
  if (busy) return;
@@ -768,7 +776,7 @@ function observePluginUpdate(options) {
768
776
  applyControls();
769
777
  render();
770
778
  setMessage(
771
- payload.autoReload === true ? text.restarting : text.restart,
779
+ payload.autoReload === true ? text.restarting : payload.profileName === "desktop" || payload.restartDesktop === true ? text.restartDesktop : text.restart,
772
780
  "success"
773
781
  );
774
782
  } catch (error) {
@@ -948,7 +956,7 @@ function PluginUpdateHeader({ locale }) {
948
956
  zh2 ? "\u5BA0\u7269" : "Pets",
949
957
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "dcp-version", children: [
950
958
  "v",
951
- "0.1.13"
959
+ "0.1.15"
952
960
  ] })
953
961
  ] }),
954
962
  /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("nav", { className: "dcp-project-links", children: [
package/lib/index.js CHANGED
@@ -52,7 +52,7 @@ import { spawn } from "node:child_process";
52
52
  import { existsSync, readFileSync } from "node:fs";
53
53
  import { readFile } from "node:fs/promises";
54
54
  import { homedir } from "node:os";
55
- import { basename, dirname, isAbsolute, resolve } from "node:path";
55
+ import { basename, delimiter, dirname, isAbsolute, resolve } from "node:path";
56
56
  import { fileURLToPath } from "node:url";
57
57
  var PLUGIN_UPDATE_HEADER = "x-michengai-plugin-update";
58
58
  function header(request, name2) {
@@ -68,9 +68,16 @@ function isTrustedUpdateRequest(request) {
68
68
  if (!isLoopbackAddress(request.socket?.remoteAddress)) return false;
69
69
  const site = header(request, "sec-fetch-site");
70
70
  if (site !== void 0 && site !== "same-origin") return false;
71
- const origin = header(request, "origin");
72
71
  const host = header(request, "host");
73
- if (origin === void 0 || host === void 0) return false;
72
+ if (host === void 0 || host === "") return false;
73
+ const origin = header(request, "origin");
74
+ if (origin === void 0 || origin === "") {
75
+ try {
76
+ return isLoopbackAddress(new URL(`http://${host}`).hostname);
77
+ } catch {
78
+ return false;
79
+ }
80
+ }
74
81
  try {
75
82
  const url = new URL(origin);
76
83
  return (url.protocol === "http:" || url.protocol === "https:") && isLoopbackAddress(url.hostname) && url.host === host;
@@ -96,14 +103,14 @@ function isDshCliEntry(entry, manifest, packageRoot2) {
96
103
  const bin = typeof value.bin === "string" ? value.bin : typeof value.bin === "object" && value.bin !== null ? value.bin.dsh : void 0;
97
104
  return typeof bin === "string" && bin !== "" && !isAbsolute(bin) && resolve(packageRoot2, bin) === resolve(entry);
98
105
  }
99
- function cliEntry() {
100
- const value = process.argv[1];
106
+ function cliEntry(argv = process.argv, cwd = process.cwd(), exists = existsSync) {
107
+ const value = argv[1];
101
108
  if (value === void 0 || value === "") return void 0;
102
- const entry = value.startsWith("file:") ? fileURLToPath(value) : resolve(process.cwd(), value);
103
- if (!existsSync(entry)) return void 0;
109
+ const entry = value.startsWith("file:") ? fileURLToPath(value) : resolve(cwd, value);
110
+ if (!exists(entry)) return void 0;
104
111
  for (let directory = dirname(entry); ; ) {
105
112
  const manifestPath = resolve(directory, "package.json");
106
- if (existsSync(manifestPath)) {
113
+ if (exists(manifestPath)) {
107
114
  try {
108
115
  if (isDshCliEntry(
109
116
  entry,
@@ -119,19 +126,88 @@ function cliEntry() {
119
126
  directory = parent;
120
127
  }
121
128
  }
122
- function runtime() {
123
- const profileDir = resolve(
124
- process.env.DSH_PROFILE_DIR ?? resolve(homedir(), ".dsh", "profiles", "web")
125
- );
126
- const selected = profileNameFromArgv(process.argv);
127
- const profileName = validProfileName(selected) ? selected : validProfileName(basename(profileDir)) ? basename(profileDir) : "web";
128
- const entry = cliEntry();
129
+ function optionalService(ctx, name2) {
130
+ try {
131
+ return typeof ctx.get === "function" ? ctx.get(name2) : void 0;
132
+ } catch {
133
+ return void 0;
134
+ }
135
+ }
136
+ function isOfficialDesktopHostEntry(entry) {
137
+ return entry !== void 0 && entry !== "" && entry.replaceAll("\\", "/").includes("/dsh-desktop-host/");
138
+ }
139
+ function stringEnvironment(value) {
140
+ if (value === null || typeof value !== "object") return void 0;
141
+ const env = {};
142
+ for (const [key, item] of Object.entries(value))
143
+ if (typeof item === "string") env[key] = item;
144
+ return Object.keys(env).length === 0 ? void 0 : env;
145
+ }
146
+ function packageManagerFrom(value) {
147
+ if (value === null || typeof value !== "object") return void 0;
148
+ const item = value;
149
+ if (typeof item.command !== "string" || item.command === "") return void 0;
150
+ if (!Array.isArray(item.args) || !item.args.every((arg) => typeof arg === "string"))
151
+ return void 0;
152
+ const env = stringEnvironment(item.env);
153
+ return {
154
+ command: item.command,
155
+ args: item.args,
156
+ ...env === void 0 ? {} : { env }
157
+ };
158
+ }
159
+ function packageManagerFromArgv(argv, exists, execPath, pathEnv) {
160
+ if (!isOfficialDesktopHostEntry(argv[1])) return void 0;
161
+ const pnpm = argv[5];
162
+ if (typeof pnpm !== "string" || !isAbsolute(pnpm) || !exists(pnpm))
163
+ return void 0;
164
+ const bin = argv[6];
165
+ const path = typeof bin === "string" && bin !== "" ? `${bin}${delimiter}${pathEnv ?? ""}` : pathEnv;
166
+ return {
167
+ command: execPath,
168
+ args: ["--expose-internals", pnpm],
169
+ env: {
170
+ ELECTRON_RUN_AS_NODE: "1",
171
+ ...typeof path === "string" && path !== "" ? { PATH: path } : {}
172
+ }
173
+ };
174
+ }
175
+ function pluginManagerFrom(value) {
176
+ if (value === null || typeof value !== "object" || typeof value.installBundle !== "function")
177
+ return void 0;
178
+ return value;
179
+ }
180
+ function resolveUpdateRuntime(ctx, options = {}) {
181
+ const argv = options.argv ?? process.argv;
182
+ const env = options.env ?? process.env;
183
+ const cwd = options.cwd ?? process.cwd();
184
+ const home = options.homeDir ?? homedir();
185
+ const exists = options.exists ?? existsSync;
186
+ const execPath = options.execPath ?? process.execPath;
187
+ const launched = optionalService(ctx, "profileContext");
188
+ const official = isOfficialDesktopHostEntry(argv[1]) || launched?.name === "desktop";
189
+ const launchedDir = typeof launched?.dir === "string" && isAbsolute(launched.dir) ? resolve(launched.dir) : void 0;
190
+ const projectDir = official && typeof argv[3] === "string" && isAbsolute(argv[3]) ? resolve(argv[3]) : void 0;
191
+ const cwdProfile = official && exists(resolve(cwd, "package.json")) ? resolve(cwd) : void 0;
192
+ const profileDir = launchedDir ?? projectDir ?? cwdProfile ?? resolve(env.DSH_PROFILE_DIR ?? resolve(home, ".dsh", "profiles", "web"));
193
+ const selected = profileNameFromArgv(argv);
194
+ const profileName = validProfileName(launched?.name) ? launched.name : official ? "desktop" : validProfileName(selected) ? selected : validProfileName(basename(profileDir)) ? basename(profileDir) : "web";
195
+ const packageManager = official ? packageManagerFrom(launched?.packageManager) ?? packageManagerFromArgv(argv, exists, execPath, env.PATH) : void 0;
196
+ const pluginManager = official && packageManager === void 0 ? pluginManagerFrom(optionalService(ctx, "pluginManager")) : void 0;
197
+ const entry = official ? void 0 : cliEntry(argv, cwd, exists);
129
198
  return {
130
199
  profileName,
131
200
  profileDir,
201
+ officialDesktop: official,
202
+ canAutoUpdate: packageManager !== void 0 || pluginManager !== void 0 || entry !== void 0,
203
+ ...packageManager === void 0 ? {} : { packageManager },
204
+ ...pluginManager === void 0 ? {} : { pluginManager },
132
205
  ...entry === void 0 ? {} : { cliEntry: entry }
133
206
  };
134
207
  }
208
+ function runtime(ctx) {
209
+ return resolveUpdateRuntime(ctx);
210
+ }
135
211
  function parseSemver(value) {
136
212
  const match = /^v?(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(
137
213
  value
@@ -216,10 +292,64 @@ async function status(options, target) {
216
292
  latestCheckFailed: latest === void 0,
217
293
  updateAvailable: latest != null && isNewerVersion(current, latest),
218
294
  profileName: target.profileName,
219
- canAutoUpdate: target.cliEntry !== void 0
295
+ canAutoUpdate: target.canAutoUpdate
220
296
  };
221
297
  }
222
298
  async function install(target, packageSpec, track) {
299
+ if (target.packageManager !== void 0) {
300
+ const manager = target.packageManager;
301
+ const child2 = spawn(
302
+ manager.command,
303
+ [
304
+ ...manager.args,
305
+ "add",
306
+ "--config.minimumReleaseAge=0",
307
+ packageSpec,
308
+ "--registry=https://registry.npmjs.org/"
309
+ ],
310
+ {
311
+ cwd: target.profileDir,
312
+ windowsHide: true,
313
+ stdio: ["ignore", "pipe", "pipe"],
314
+ env: { ...process.env, ...manager.env, NO_COLOR: "1" }
315
+ }
316
+ );
317
+ let detail2 = "";
318
+ child2.stdout.on("data", (chunk) => {
319
+ detail2 = (detail2 + String(chunk)).slice(-4e3);
320
+ });
321
+ child2.stderr.on("data", (chunk) => {
322
+ detail2 = (detail2 + String(chunk)).slice(-4e3);
323
+ });
324
+ const done2 = new Promise((resolve3, reject) => {
325
+ child2.once("error", reject);
326
+ child2.once(
327
+ "close",
328
+ (code) => code === 0 ? resolve3() : reject(
329
+ new UpdateFailure(
330
+ "UPDATE_FAILED",
331
+ detail2.trim() || `\u66F4\u65B0\u8FDB\u7A0B\u9000\u51FA\u7801 ${code}`
332
+ )
333
+ )
334
+ );
335
+ });
336
+ track(done2);
337
+ let timer2;
338
+ try {
339
+ await Promise.race([
340
+ done2,
341
+ new Promise((_, reject) => {
342
+ timer2 = setTimeout(() => {
343
+ child2.kill();
344
+ reject(new UpdateFailure("UPDATE_TIMEOUT"));
345
+ }, 10 * 6e4);
346
+ })
347
+ ]);
348
+ } finally {
349
+ clearTimeout(timer2);
350
+ }
351
+ return;
352
+ }
223
353
  if (!target.cliEntry) throw new UpdateFailure("AUTO_UPDATE_UNAVAILABLE");
224
354
  const child = spawn(
225
355
  process.execPath,
@@ -290,7 +420,7 @@ function registerPluginUpdater(ctx, options) {
290
420
  path: options.endpoint,
291
421
  handler: async (request, response) => {
292
422
  try {
293
- const target = runtime();
423
+ const target = runtime(ctx);
294
424
  if (request.method === "GET" || request.method === "HEAD") {
295
425
  const payload = await status(options, target);
296
426
  response.writeHead(200, {
@@ -345,7 +475,8 @@ function registerPluginUpdater(ctx, options) {
345
475
  ...before,
346
476
  updatedVersion: before.latestVersion,
347
477
  restartRequired: true,
348
- autoReload: false
478
+ autoReload: false,
479
+ ...target.officialDesktop ? { restartDesktop: true } : {}
349
480
  });
350
481
  } finally {
351
482
  if (pendingInstall)
@@ -590,7 +721,7 @@ async function body(req) {
590
721
  throw new Error("\u8BF7\u6C42\u5FC5\u987B\u662F JSON \u5BF9\u8C61");
591
722
  return value;
592
723
  }
593
- async function createHost(options = {}) {
724
+ async function createHost(options = {}, host) {
594
725
  const root = options.root ?? packageRoot;
595
726
  const library = new PetLibrary(
596
727
  join2(root, "assets", "codex"),
@@ -611,7 +742,8 @@ async function createHost(options = {}) {
611
742
  updateHandler = void 0;
612
743
  };
613
744
  }
614
- }
745
+ },
746
+ get: (name2) => host?.get?.(name2)
615
747
  },
616
748
  {
617
749
  endpoint: `${BASE}/api/update`,
@@ -744,7 +876,7 @@ async function createHost(options = {}) {
744
876
  function apply(ctx) {
745
877
  ctx.effect(() => {
746
878
  let disposed = false, remove, host;
747
- void createHost().then((value) => {
879
+ void createHost({}, ctx).then((value) => {
748
880
  host = value;
749
881
  if (disposed) {
750
882
  host.dispose();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@michengai/dsh-codex-pet",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "license": "Apache-2.0",
5
5
  "description": "DSH Codex Pet — 为 DeepSeek Harness 提供宠物陪伴、多任务提醒与交互,以及 Skill 自定义宠物 · A pet companion with multi-task notifications, interactions, and Skill-based custom pets for DeepSeek Harness",
6
6
  "type": "module",