dsh-agentic-pet 0.3.2 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -287,7 +287,7 @@ function spawnHostChild() {
287
287
  * The pet-surface locator: pet-app bundles inside a running runtime publish
288
288
  * their authenticated root here (the desktop shell keeps its URL in process
289
289
  * memory only). A published URL wins over spawning a dedicated profile, so the
290
- * pet follows whichever live runtime serves /pet/ — the user's desktop when it
290
+ * pet follows whichever live runtime serves /whale598/ — the user's desktop when it
291
291
  * runs, otherwise the last publisher still alive.
292
292
  */
293
293
  async function readLocatorUrl() {
@@ -311,14 +311,16 @@ async function readLocatorUrl() {
311
311
  }
312
312
  }
313
313
  /**
314
- * The pet surface URL for an authenticated web root: path `/pet/` with the
314
+ * The pet surface URL for an authenticated web root: path `/whale598/` with the
315
315
  * token **dropped** — authorizeIndex mints the root cookie from the token and
316
316
  * rejects any token-carrying request on a subpath, so the pet page itself is
317
317
  * entered cookie-only after the root mint.
318
318
  */
319
319
  function petSurfaceUrl(authenticated) {
320
320
  const parsed = new URL(authenticated);
321
- parsed.pathname = '/pet/';
321
+ // MUST stay in lockstep with the plugin's PET_PATH_PREFIX (/whale598) —
322
+ // separate builds, so the literal is duplicated here by design.
323
+ parsed.pathname = '/whale598/';
322
324
  parsed.search = '';
323
325
  parsed.hash = '';
324
326
  return parsed.toString();
@@ -465,7 +467,7 @@ async function main() {
465
467
  });
466
468
  // Connection's token exchange is root-path-only (authorizeIndex admits
467
469
  // `/?token=…`, never a subpath): load the authenticated root once to mint
468
- // the browser cookie, then enter /pet/ with it. The root load starts the
470
+ // the browser cookie, then enter /whale598/ with it. The root load starts the
469
471
  // Web GUI; the second navigation aborts it, so the web app is at most a
470
472
  // flash.
471
473
  await window.loadURL(authenticated).catch(() => { });
@@ -979,7 +981,9 @@ async function main() {
979
981
  // swap blindness). The legacy single file keeps being written during the
980
982
  // transition so pre-registry readers still see this carrier.
981
983
  const manifest = `${JSON.stringify({ port, pid: process.pid, since: Date.now(), pet: 'electron' })}\n`;
982
- const registryDir = join(DSH_HOME, 'pet-http.d');
984
+ // Namespaced under whale598/ (collision avoidance); the driver scans
985
+ // the legacy pet-http.d/ as a read fallback for older pet builds.
986
+ const registryDir = join(DSH_HOME, 'whale598', 'pet-http.d');
983
987
  try {
984
988
  mkdirSync(registryDir, { recursive: true });
985
989
  }
@@ -1021,7 +1025,7 @@ async function main() {
1021
1025
  // find a dead port in pet-http.d/ — stale entries were the "旧端点残留"
1022
1026
  // incident class. Best effort: a killed carrier leaves the file, and the
1023
1027
  // readers' pid+probe validation is the backstop.
1024
- for (const file of ['pet-http.d/electron.json', 'pet-http-electron.json']) {
1028
+ for (const file of ['whale598/pet-http.d/electron.json', 'pet-http.d/electron.json', 'pet-http-electron.json']) {
1025
1029
  void rm(join(DSH_HOME, file), { force: true }).catch(() => { });
1026
1030
  }
1027
1031
  if (handle === undefined)
@@ -96,6 +96,34 @@ window.__ModuleLoader__.load({
96
96
  var pets = (data && data.pets) || []
97
97
  if (!(pets && pets.length) && data && data.pet) pets = [Object.assign({ id: 'electron' }, data.pet)]
98
98
  var moodOf = function (a) { return a >= 80 ? '开心' : a >= 40 ? '满足' : '平静' }
99
+ // 显示名:用户自定义(settings.names)优先,缺省回落内置名;
100
+ // 宠物区改名后好感度区自动跟随(同一状态、同一渲染趟)。
101
+ var defaultName = function (id) { return id === 'electron' ? '鲸鱼娘' : id === 'pure' ? 'Pure 宠物' : id }
102
+ var BOND_TIERS = [[0, "陌生"], [5, "认识"], [15, "熟了"], [40, "亲近"], [90, "离不开"], [180, "只有你"]]
103
+ var bondTier = function (days) {
104
+ var name = "陌生"
105
+ BOND_TIERS.forEach(function (t) { if (days >= t[0]) name = t[1] })
106
+ return name
107
+ }
108
+ var petName = function (id) {
109
+ var custom = settings.names && settings.names[id]
110
+ return custom ? String(custom) : defaultName(id)
111
+ }
112
+ // 提交一次改名(失焦/回车调用):与当前值比较,无变化零开销;
113
+ // 空值删键(回落默认)。一次编辑 = 至多一次 update = 至多一条 [改名]。
114
+ var commitName = function (id, raw) {
115
+ var v = (raw || '').trim()
116
+ var next = Object.assign({}, (settings.names || {}))
117
+ var current = next[id]
118
+ if (v !== '' && v !== defaultName(id)) {
119
+ if (current === v) return
120
+ next[id] = v
121
+ } else {
122
+ if (current === undefined) return
123
+ delete next[id]
124
+ }
125
+ update({ names: next })
126
+ }
99
127
 
100
128
  function update(patch, doneMessage) { act(UPDATE, { patch: patch }, doneMessage) }
101
129
 
@@ -111,9 +139,13 @@ window.__ModuleLoader__.load({
111
139
  h(Section, { title: '宠物' },
112
140
  pets.length
113
141
  ? pets.map(function (p) {
114
- var name = p.id === 'electron' ? 'TS 宠物' : p.id === 'pure' ? 'Pure 宠物' : String(p.id)
115
142
  return h('div', { key: p.id, style: row },
116
- h('span', { style: { fontWeight: 700 } }, name),
143
+ // Uncontrolled + commit-on-blur/Enter: typing is LOCAL until the
144
+ // edit completes — a per-keystroke update would fire a [改名]
145
+ // turn per character.清空 + 失焦 = 恢复默认名。
146
+ h('input', { style: Object.assign({}, inputStyle, { width: 120, fontWeight: 700 }), defaultValue: petName(p.id), title: '改名后点击空白处或回车生效;清空恢复默认',
147
+ onBlur: function (e) { commitName(p.id, e.target.value) },
148
+ onKeyDown: function (e) { if (e.key === 'Enter') { commitName(p.id, e.target.value); e.target.blur() } } }),
117
149
  p.online
118
150
  ? h('span', null, '在线',
119
151
  h('span', { style: muted }, 'pid ' + String(p.pid ?? '?')),
@@ -121,7 +153,7 @@ window.__ModuleLoader__.load({
121
153
  p.animation ? h('span', { style: muted }, String(p.animation)) : null)
122
154
  : h('span', { style: muted }, '离线'))
123
155
  })
124
- : h('div', null, '没有在线的宠物', h(Hint, null, '启动 DeepSeek Harness 后 TS 宠物会自动上桌;Pure 宠物需自行启动。'))),
156
+ : h('div', null, '没有在线的宠物', h(Hint, null, '启动 DeepSeek Harness 后鲸鱼娘会自动上桌;Pure 宠物需自行启动。'))),
125
157
 
126
158
  // ── 好感度(每宠独立账本) ──
127
159
  h(Section, { title: '好感度' },
@@ -133,18 +165,20 @@ window.__ModuleLoader__.load({
133
165
  if (!ids.length) return h('div', { style: muted }, '还没有互动记录')
134
166
  return ids.map(function (id) {
135
167
  var book = (affection && affection[id]) || { affection: 0, total: 0 }
136
- var name = id === 'electron' ? 'TS 宠物' : id === 'pure' ? 'Pure 宠物' : id
168
+ var name = petName(id)
137
169
  return h('div', { key: id, style: row },
138
170
  h('span', { style: { fontWeight: 700 } }, name),
139
171
  h('span', { style: { fontSize: 18, fontWeight: 700 } }, String(book.affection ?? 0)),
140
- h('span', { style: muted }, '/ 100 · ' + moodOf(book.affection ?? 0)),
172
+ h('span', { style: muted }, '/ 100 · ' + moodOf(book.affection ?? 0) + ' · 羁绊:' + bondTier(book.bond ? (book.bond.value ?? book.bond.days ?? 0) : 0) + '(' + String(book.bond ? (book.bond.value ?? book.bond.days ?? 0) : 0) + ' 天)'),
141
173
  h('span', { style: muted }, '累计 ' + String(book.total ?? 0) + ' 次'))
142
174
  })
143
175
  })(),
144
176
  h('div', { style: row }, h(SButton, { variant: 'danger', disabled: busy, onClick: function () {
145
177
  if (window.confirm('清空所有好感度账本?此操作不可撤销。')) act('/api/pet-settings/reset-affection', {}, '账本已清空')
146
178
  } }, '重置账本'),
147
- h('span', { style: muted }, '每跨 10 点该宠物会自主庆祝一次'))),
179
+ h('span', { style: muted }, '每 10 次触摸该宠物会自主庆祝一次'))),
180
+
181
+
148
182
 
149
183
  // ── 内心戏(宠物自主说话)──
150
184
  h(Section, { title: h('span', null, '自主表达 ', h('span', { style: { fontSize: 11, fontWeight: 400, color: 'rgba(127,127,127,.9)' } }, '模型驱动')) },
@@ -190,11 +224,11 @@ window.__ModuleLoader__.load({
190
224
  marginLeft: side === 'right' ? -1 : 0 }
191
225
  }
192
226
  return h('div', { style: { display: 'inline-flex' } },
193
- h('button', { style: seg(iv.preset !== 'pet', 'left'), onClick: function () {
194
- if (iv.preset !== 'pet-inner-voice') update({ innerVoice: { preset: 'pet-inner-voice' } })
227
+ h('button', { style: seg(iv.preset === 'whale598', 'left'), onClick: function () {
228
+ if (iv.preset !== 'whale598') update({ innerVoice: { preset: 'whale598' } })
195
229
  } }, '精简(仅表达)'),
196
- h('button', { style: seg(iv.preset === 'pet', 'right'), onClick: function () {
197
- if (iv.preset !== 'pet') update({ innerVoice: { preset: 'pet' } })
230
+ h('button', { style: seg(iv.preset === 'whale598-standard', 'right'), onClick: function () {
231
+ if (iv.preset !== 'whale598-standard') update({ innerVoice: { preset: 'whale598-standard' } })
198
232
  } }, '普通(含工作工具)'))
199
233
  })()),
200
234
  h('div', { style: row }, '指定会话(高级)',
package/lib/index.js CHANGED
@@ -1,4 +1,4 @@
1
- import { i as installPetDriver, n as installPetService } from "./service-Vj-lLpIv.js";
1
+ import { c as readAffectionRaw, d as toolCallsFile, f as writeAffectionRaw, i as installPetDriver, l as readJsonl, n as installPetService, p as writePetName, s as eventsFile, u as readPetName } from "./service-D-b8UzM2.js";
2
2
  import { createRequire } from "node:module";
3
3
  import { spawn } from "node:child_process";
4
4
  import { createHash } from "node:crypto";
@@ -20,6 +20,7 @@ import { fileURLToPath } from "node:url";
20
20
  * dispatch — the webserver throws on duplicate path registration.
21
21
  * @module dsh-agentic-pet/settings-page
22
22
  */
23
+ /** The touch ledger the driver owns; reset writes the driver's empty shape. */
23
24
  const DEFAULT_SETTINGS = {
24
25
  proactivity: {
25
26
  enabled: false,
@@ -30,7 +31,7 @@ const DEFAULT_SETTINGS = {
30
31
  },
31
32
  innerVoice: {
32
33
  enabled: true,
33
- preset: "pet-inner-voice",
34
+ preset: "whale598",
34
35
  wakeIntervalMinutes: 2,
35
36
  speakProbability: .5,
36
37
  touchReplyProbability: .3,
@@ -39,7 +40,6 @@ const DEFAULT_SETTINGS = {
39
40
  },
40
41
  debug: { cdp: false }
41
42
  };
42
- /** The touch ledger the driver owns; reset writes the driver's empty shape. */
43
43
  const EMPTY_LEDGER = {
44
44
  affection: 0,
45
45
  total: 0,
@@ -169,7 +169,6 @@ function openPathInFileManager(path) {
169
169
  */
170
170
  function installSettingsPage(ctx, home, pet) {
171
171
  const settingsPath = join(home, "pet-settings.json");
172
- const ledgerPath = join(home, "pet-affection.json");
173
172
  const windowPath = join(home, "pet-window.json");
174
173
  /** Summarize EVERY live pet (id, online flag, normalized /status). */
175
174
  const petStatus = async () => {
@@ -224,18 +223,49 @@ function installSettingsPage(ctx, home, pet) {
224
223
  petStatus()
225
224
  ]);
226
225
  const affection = {};
227
- const legacyBook = await readFile(ledgerPath, "utf8").then((raw) => JSON.parse(raw), () => null);
228
226
  const bookIds = new Set(status.pets.map((pet) => String(pet.id)));
229
227
  bookIds.add("electron");
230
228
  for (const id of bookIds) {
231
- const book = await readFile(id === "electron" && legacyBook !== null ? ledgerPath : join(home, "pet-affection.d", `${id}.json`), "utf8").then((raw) => JSON.parse(raw), () => null);
229
+ const book = await readAffectionRaw(home, id);
232
230
  if (book !== null) affection[id] = book;
233
231
  }
232
+ const names = {};
233
+ for (const id of bookIds) {
234
+ const label = await readPetName(home, id);
235
+ if (label !== void 0) names[id] = label;
236
+ }
237
+ const composed = {
238
+ ...settings,
239
+ names
240
+ };
241
+ const history = [];
242
+ const touchTotals = {};
243
+ for (const id of bookIds) {
244
+ const events = await readJsonl(eventsFile(home, id), 600);
245
+ const totals = {
246
+ total: 0,
247
+ byPart: {}
248
+ };
249
+ for (const event of events) {
250
+ if (event.type !== "touch") continue;
251
+ const part = typeof event.part === "string" ? event.part : "miss";
252
+ totals.total += 1;
253
+ totals.byPart[part] = (totals.byPart[part] ?? 0) + 1;
254
+ }
255
+ if (totals.total > 0) touchTotals[id] = totals;
256
+ for (const call of await readJsonl(toolCallsFile(home, id), 12)) history.push({
257
+ ...call,
258
+ petId: id
259
+ });
260
+ }
261
+ history.sort((a, b) => Number(b.at ?? 0) - Number(a.at ?? 0));
234
262
  writeJson(res, 200, {
235
- settings,
263
+ settings: composed,
236
264
  affection,
237
265
  windowPosition,
238
- pets: status.pets
266
+ pets: status.pets,
267
+ history: history.slice(0, 12),
268
+ touchTotals
239
269
  });
240
270
  });
241
271
  route("POST", "/api/pet-settings/update", async (req, res) => {
@@ -245,6 +275,11 @@ function installSettingsPage(ctx, home, pet) {
245
275
  writeJson(res, 400, { error: "body must be {patch:{...}}" });
246
276
  return;
247
277
  }
278
+ const patchNames = patch.names;
279
+ if (typeof patchNames === "object" && patchNames !== null) {
280
+ for (const [petId, label] of Object.entries(patchNames)) await writePetName(home, petId, typeof label === "string" && label.trim() !== "" ? label : null);
281
+ delete patch.names;
282
+ }
248
283
  const next = mergeSettings(await readSettings(), patch);
249
284
  await writeFile(settingsPath, `${JSON.stringify(next, null, 2)}\n`, "utf8");
250
285
  ctx.emit("pet-settings/changed");
@@ -254,7 +289,7 @@ function installSettingsPage(ctx, home, pet) {
254
289
  });
255
290
  });
256
291
  route("POST", "/api/pet-settings/reset-affection", async (_req, res) => {
257
- await writeFile(ledgerPath, `${JSON.stringify(EMPTY_LEDGER)}\n`, "utf8");
292
+ await writeAffectionRaw(home, "electron", EMPTY_LEDGER);
258
293
  writeJson(res, 200, {
259
294
  ok: true,
260
295
  affection: EMPTY_LEDGER
@@ -311,7 +346,7 @@ const inject = [
311
346
  "workspaceController"
312
347
  ];
313
348
  /** The URL path prefix the pet renderer is served under. No trailing slash. */
314
- const PET_PATH_PREFIX = "/pet";
349
+ const PET_PATH_PREFIX = "/whale598";
315
350
  /**
316
351
  * Locator file naming this runtime's authenticated root for the pet carrier.
317
352
  * The desktop shell keeps its authenticated URL in process memory only, so a
@@ -367,7 +402,7 @@ function bytesBody(bytes) {
367
402
  /**
368
403
  * Answer one GET or HEAD `/pet` request. The URL path is the full server
369
404
  * pathname (`/pet` prefix included); it must be exactly the prefix or stay
370
- * under it. The index entry (`/pet`, `/pet/`, `/pet/index.html`) passes
405
+ * under it. The index entry (`/whale598`, `/whale598/`, `/whale598/index.html`) passes
371
406
  * Connection's browser authentication; every other file is public with a weak
372
407
  * ETag and `cache-control: no-cache`, answering a matching `If-None-Match`
373
408
  * with 304. Traversal outside the dist root is 403, missing paths are 404, a
@@ -379,7 +414,8 @@ function bytesBody(bytes) {
379
414
  */
380
415
  async function servePetStatic(request, distRoot, distIndex, authorizeIndex) {
381
416
  const pathname = decodeURIComponent(new URL(request.url).pathname);
382
- const target = resolve(normalize(join(distRoot, pathname === "/pet" || pathname === `/pet/` ? "/" : pathname.slice(4))));
417
+ if (!(pathname === "/whale598" || pathname.startsWith(`/whale598/`))) return new Response(null, { status: 404 });
418
+ const target = resolve(normalize(join(distRoot, pathname === "/whale598" || pathname === `/whale598/` ? "/" : pathname.slice(9))));
383
419
  if (target !== distRoot && !target.startsWith(distRoot + sep)) return new Response(null, { status: 403 });
384
420
  let body;
385
421
  let headers;